projects/guix·blob master · refs

1090 lines · 31.3 KiB

1#include "config.h"
2#include "nix-daemon.hh"
3#include "local-store.hh"
4#include "util.hh"
5#include "serialise.hh"
6#include "worker-protocol.hh"
7#include "archive.hh"
8#include "affinity.hh"
9#include "globals.hh"
10#include "builtins.hh"
11
12#include <algorithm>
13#include <format>
14
15#include <cstring>
16#include <cassert>
17#include <unistd.h>
18#include <signal.h>
19#include <sys/types.h>
20#include <sys/wait.h>
21#include <sys/stat.h>
22#include <sys/socket.h>
23#include <sys/un.h>
24#include <arpa/inet.h>
25#include <netinet/in.h>
26#include <netinet/tcp.h>
27
28#include <fcntl.h>
29#include <errno.h>
30#include <pwd.h>
31#include <grp.h>
32
33using namespace nix;
34
35
36/* On platforms that have O_ASYNC, we can detect when a client
37 disconnects and immediately kill any ongoing builds. On platforms
38 that lack it, we only notice the disconnection the next time we try
39 to write to the client. So if you have a builder that never
40 generates output on stdout/stderr, the daemon will never notice
41 that the client has disconnected until the builder terminates.
42
43 GNU/Hurd does have O_ASYNC, but its Unix-domain socket translator
44 (pflocal) does not implement F_SETOWN. See
45 <http://lists.gnu.org/archive/html/bug-guix/2013-07/msg00021.html> for
46 details.*/
47#if defined O_ASYNC && !defined __GNU__
48#define HAVE_HUP_NOTIFICATION
49#ifndef SIGPOLL
50#define SIGPOLL SIGIO
51#endif
52#endif
53
54
55static FdSource from(STDIN_FILENO);
56static FdSink to(STDOUT_FILENO);
57
58bool canSendStderr;
59
60/* This variable is used to keep track of whether a connection
61 comes from a host other than the host running guix-daemon. */
62static bool isRemoteConnection;
63
64/* This function is called anytime we want to write something to
65 stderr. If we're in a state where the protocol allows it (i.e.,
66 when canSendStderr), send the message to the client over the
67 socket. */
68static void tunnelStderr(const unsigned char * buf, size_t count)
69{
70 if (canSendStderr) {
71 try {
72 writeInt(STDERR_NEXT, to);
73 writeString(buf, count, to);
74 to.flush();
75 } catch (...) {
76 /* Write failed; that means that the other side is
77 gone. */
78 canSendStderr = false;
79 throw;
80 }
81 } else
82 writeFull(STDERR_FILENO, buf, count);
83}
84
85
86/* Return true if the remote side has closed its end of the
87 connection, false otherwise. Should not be called on any socket on
88 which we expect input! */
89static bool isFarSideClosed(int socket)
90{
91 struct timeval timeout;
92 timeout.tv_sec = timeout.tv_usec = 0;
93
94 fd_set fds;
95 FD_ZERO(&fds);
96 FD_SET(socket, &fds);
97
98 while (select(socket + 1, &fds, 0, 0, &timeout) == -1)
99 if (errno != EINTR) throw SysError("select()");
100
101 if (!FD_ISSET(socket, &fds)) return false;
102
103 /* Destructive read to determine whether the select() marked the
104 socket as readable because there is actual input or because
105 we've reached EOF (i.e., a read of size 0 is available). */
106 char c;
107 int rd;
108 if ((rd = read(socket, &c, 1)) > 0)
109 throw Error("EOF expected (protocol error?)");
110 else if (rd == -1 && errno != ECONNRESET)
111 throw SysError("expected connection reset or EOF");
112
113 return true;
114}
115
116
117/* A SIGPOLL signal is received when data is available on the client
118 communication socket, or when the client has closed its side of the
119 socket. This handler is enabled at precisely those moments in the
120 protocol when we're doing work and the client is supposed to be
121 quiet. Thus, if we get a SIGPOLL signal, it means that the client
122 has quit. So we should quit as well.
123
124 Too bad most operating systems don't support the POLL_HUP value for
125 si_code in siginfo_t. That would make most of the SIGPOLL
126 complexity unnecessary, i.e., we could just enable SIGPOLL all the
127 time and wouldn't have to worry about races. */
128static void sigPollHandler(int sigNo)
129{
130 using namespace std;
131 try {
132 /* Check that the far side actually closed. We're still
133 getting spurious signals every once in a while. I.e.,
134 there is no input available, but we get a signal with
135 POLL_IN set. Maybe it's delayed or something. */
136 if (isFarSideClosed(from.fd)) {
137 if (!blockInt) {
138 _isInterrupted = 1;
139 blockInt = 1;
140 canSendStderr = false;
141 const char * s = "SIGPOLL\n";
142 write(STDERR_FILENO, s, strlen(s));
143 }
144 } else {
145 const char * s = "spurious SIGPOLL\n";
146 write(STDERR_FILENO, s, strlen(s));
147 }
148 }
149 catch (Error & e) {
150 /* Shouldn't happen. */
151 string s = "impossible: " + e.msg() + '\n';
152 write(STDERR_FILENO, s.data(), s.size());
153 throw;
154 }
155}
156
157
158static void setSigPollAction(bool enable)
159{
160#ifdef HAVE_HUP_NOTIFICATION
161 struct sigaction act, oact;
162 act.sa_handler = enable ? sigPollHandler : SIG_IGN;
163 sigfillset(&act.sa_mask);
164 act.sa_flags = 0;
165 if (sigaction(SIGPOLL, &act, &oact))
166 throw SysError("setting handler for SIGPOLL");
167#endif
168}
169
170static void handleSignal(int signum)
171{
172 string name = program_invocation_short_name;
173 auto message = name + ": PID " + std::to_string(getpid())
174 + " caught signal " + std::to_string(signum) + "\n";
175 writeFull(STDERR_FILENO, (unsigned char *) message.c_str(), message.length());
176 _isInterrupted = 1;
177 blockInt = 1;
178}
179
180static void setTerminationSignalHandler()
181{
182 auto signals = { SIGINT, SIGTERM, SIGHUP };
183 for (int signum: signals) {
184 signal(signum, handleSignal);
185 }
186}
187
188
189/* startWork() means that we're starting an operation for which we
190 want to send out stderr to the client. */
191static void startWork()
192{
193 canSendStderr = true;
194
195 /* Handle client death asynchronously. */
196 setSigPollAction(true);
197
198 /* Of course, there is a race condition here: the socket could
199 have closed between when we last read from / wrote to it, and
200 between the time we set the handler for SIGPOLL. In that case
201 we won't get the signal. So do a non-blocking select() to find
202 out if any input is available on the socket. If there is, it
203 has to be the 0-byte read that indicates that the socket has
204 closed. */
205 if (isFarSideClosed(from.fd)) {
206 _isInterrupted = 1;
207 checkInterrupt();
208 }
209}
210
211
212/* stopWork() means that we're done; stop sending stderr to the
213 client. */
214static void stopWork(bool success = true, const string & msg = "", unsigned int status = 0)
215{
216 /* Stop handling async client death; we're going to a state where
217 we're either sending or receiving from the client, so we'll be
218 notified of client death anyway. */
219 setSigPollAction(false);
220
221 canSendStderr = false;
222
223 if (success)
224 writeInt(STDERR_LAST, to);
225 else {
226 writeInt(STDERR_ERROR, to);
227 writeString(msg, to);
228 if (status != 0) writeInt(status, to);
229 }
230}
231
232
233struct TunnelSink : BufferedSink
234{
235 Sink & to;
236 TunnelSink(Sink & to) : BufferedSink(64 * 1024), to(to) { }
237 virtual void write(const unsigned char * data, size_t len)
238 {
239 writeInt(STDERR_WRITE, to);
240 writeString(data, len, to);
241 }
242};
243
244
245struct TunnelSource : BufferedSource
246{
247 Source & from;
248 TunnelSource(Source & from) : from(from) { }
249 size_t readUnbuffered(unsigned char * data, size_t len)
250 {
251 /* Careful: we're going to receive data from the client now,
252 so we have to disable the SIGPOLL handler. */
253 setSigPollAction(false);
254 canSendStderr = false;
255
256 writeInt(STDERR_READ, to);
257 writeInt(len, to);
258 to.flush();
259 size_t n = readString(data, len, from);
260
261 startWork();
262 if (n == 0) throw EndOfFile("unexpected end-of-file");
263 return n;
264 }
265};
266
267
268/* If the NAR archive contains a single file at top-level, then save
269 the contents of the file to `s'. Otherwise barf. */
270struct RetrieveRegularNARSink : ParseSink
271{
272 bool regular;
273 string s;
274
275 RetrieveRegularNARSink() : regular(true) { }
276
277 void createDirectory(const Path & path)
278 {
279 regular = false;
280 }
281
282 void receiveContents(unsigned char * data, unsigned int len)
283 {
284 s.append((const char *) data, len);
285 }
286
287 void createSymlink(const Path & path, const string & target)
288 {
289 regular = false;
290 }
291};
292
293
294/* Adapter class of a Source that saves all data read to `s'. */
295struct SavingSourceAdapter : Source
296{
297 Source & orig;
298 string s;
299 SavingSourceAdapter(Source & orig) : orig(orig) { }
300 size_t read(unsigned char * data, size_t len)
301 {
302 size_t n = orig.read(data, len);
303 s.append((const char *) data, n);
304 return n;
305 }
306};
307
308
309static void performOp(bool trusted, unsigned int clientVersion,
310 Source & from, Sink & to, unsigned int op)
311{
312 switch (op) {
313
314 case wopIsValidPath: {
315 /* 'readStorePath' could raise an error leading to the connection
316 being closed. To be able to recover from an invalid path error,
317 call 'startWork' early, and do 'assertStorePath' afterwards so
318 that the 'Error' exception handler doesn't close the
319 connection. */
320 Path path = readString(from);
321 startWork();
322 assertStorePath(path);
323 bool result = store->isValidPath(path);
324 stopWork();
325 writeInt(result, to);
326 break;
327 }
328
329 case wopQueryValidPaths: {
330 PathSet paths = readStorePaths<PathSet>(from);
331 startWork();
332 PathSet res = store->queryValidPaths(paths);
333 stopWork();
334 writeStrings(res, to);
335 break;
336 }
337
338 case wopHasSubstitutes: {
339 Path path = readStorePath(from);
340 startWork();
341 PathSet res = store->querySubstitutablePaths(PathSet{path});
342 stopWork();
343 writeInt(res.find(path) != res.end(), to);
344 break;
345 }
346
347 case wopQuerySubstitutablePaths: {
348 PathSet paths = readStorePaths<PathSet>(from);
349 startWork();
350 PathSet res = store->querySubstitutablePaths(paths);
351 stopWork();
352 writeStrings(res, to);
353 break;
354 }
355
356 case wopQueryPathHash: {
357 Path path = readStorePath(from);
358 startWork();
359 Hash hash = store->queryPathHash(path);
360 stopWork();
361 writeString(printHash(hash), to);
362 break;
363 }
364
365 case wopQueryReferences:
366 case wopQueryReferrers:
367 case wopQueryValidDerivers:
368 case wopQueryDerivationOutputs: {
369 Path path = readStorePath(from);
370 startWork();
371 PathSet paths;
372 if (op == wopQueryReferences)
373 store->queryReferences(path, paths);
374 else if (op == wopQueryReferrers)
375 store->queryReferrers(path, paths);
376 else if (op == wopQueryValidDerivers)
377 paths = store->queryValidDerivers(path);
378 else paths = store->queryDerivationOutputs(path);
379 stopWork();
380 writeStrings(paths, to);
381 break;
382 }
383
384 case wopQueryDerivationOutputNames: {
385 Path path = readStorePath(from);
386 startWork();
387 StringSet names;
388 names = store->queryDerivationOutputNames(path);
389 stopWork();
390 writeStrings(names, to);
391 break;
392 }
393
394 case wopQueryDeriver: {
395 Path path = readStorePath(from);
396 startWork();
397 Path deriver = store->queryDeriver(path);
398 stopWork();
399 writeString(deriver, to);
400 break;
401 }
402
403 case wopQueryPathFromHashPart: {
404 string hashPart = readString(from);
405 startWork();
406 Path path = store->queryPathFromHashPart(hashPart);
407 stopWork();
408 writeString(path, to);
409 break;
410 }
411
412 case wopAddToStore: {
413 string baseName = readString(from);
414 bool fixed = readInt(from) == 1; /* obsolete */
415 bool recursive = readInt(from) == 1;
416 string s = readString(from);
417 /* Compatibility hack. */
418 if (!fixed) {
419 s = "sha256";
420 recursive = true;
421 }
422 HashType hashAlgo = parseHashType(s);
423
424 SavingSourceAdapter savedNAR(from);
425 RetrieveRegularNARSink savedRegular;
426
427 if (recursive) {
428 /* Get the entire NAR dump from the client and save it to
429 a string so that we can pass it to
430 addToStoreFromDump(). */
431 ParseSink sink; /* null sink; just parse the NAR */
432 parseDump(sink, savedNAR);
433 } else
434 parseDump(savedRegular, from);
435
436 startWork();
437 if (!savedRegular.regular) throw Error("regular file expected");
438 Path path = dynamic_cast<LocalStore *>(store.get())
439 ->addToStoreFromDump(recursive ? savedNAR.s : savedRegular.s, baseName, recursive, hashAlgo);
440 stopWork();
441
442 writeString(path, to);
443 break;
444 }
445
446 case wopAddTextToStore: {
447 string suffix = readString(from);
448 string s = readString(from);
449 PathSet refs = readStorePaths<PathSet>(from);
450 startWork();
451 Path path = store->addTextToStore(suffix, s, refs);
452 stopWork();
453 writeString(path, to);
454 break;
455 }
456
457 case wopExportPath: {
458 Path path = readStorePath(from);
459 bool sign = readInt(from) == 1;
460 startWork();
461 TunnelSink sink(to);
462 try {
463 store->exportPath(path, sign, sink);
464 }
465 catch (Error &e) {
466 /* Flush SINK beforehand or its destructor will rightfully trigger
467 an assertion failure. */
468 sink.flush();
469 throw e;
470 }
471 sink.flush();
472 stopWork();
473 writeInt(1, to);
474 break;
475 }
476
477 case wopImportPaths: {
478 startWork();
479 TunnelSource source(from);
480
481 /* Unlike Nix, always require a signature, even for "trusted"
482 users. */
483 Paths paths = store->importPaths(source);
484 stopWork();
485 writeStrings(paths, to);
486 break;
487 }
488
489 case wopBuildPaths: {
490 PathSet drvs = readStorePaths<PathSet>(from);
491 BuildMode mode = bmNormal;
492 if (GET_PROTOCOL_MINOR(clientVersion) >= 15) {
493 mode = (BuildMode)readInt(from);
494
495 /* Repairing is not atomic, so disallowed for "untrusted"
496 clients. */
497 if (mode == bmRepair && !trusted)
498 throw Error("repairing is a privileged operation");
499 }
500 startWork();
501 store->buildPaths(drvs, mode);
502 stopWork();
503 writeInt(1, to);
504 break;
505 }
506
507 case wopEnsurePath: {
508 Path path = readStorePath(from);
509 startWork();
510 store->ensurePath(path);
511 stopWork();
512 writeInt(1, to);
513 break;
514 }
515
516 case wopAddTempRoot: {
517 Path path = readStorePath(from);
518 startWork();
519 store->addTempRoot(path);
520 stopWork();
521 writeInt(1, to);
522 break;
523 }
524
525 case wopAddIndirectRoot: {
526 Path path = absPath(readString(from));
527 startWork();
528 store->addIndirectRoot(path);
529 stopWork();
530 writeInt(1, to);
531 break;
532 }
533
534 case wopSyncWithGC: {
535 startWork();
536 store->syncWithGC();
537 stopWork();
538 writeInt(1, to);
539 break;
540 }
541
542 case wopFindRoots: {
543 startWork();
544 Roots roots = store->findRoots();
545 stopWork();
546 writeInt(roots.size(), to);
547 for (Roots::iterator i = roots.begin(); i != roots.end(); ++i) {
548 writeString(i->first, to);
549 writeString(i->second, to);
550 }
551 break;
552 }
553
554 case wopCollectGarbage: {
555 if (isRemoteConnection) {
556 throw Error("Garbage collection is disabled for remote hosts.");
557 break;
558 }
559
560 GCOptions options;
561 options.action = (GCOptions::GCAction) readInt(from);
562 options.pathsToDelete = readStorePaths<PathSet>(from);
563 options.ignoreLiveness = readInt(from);
564 options.maxFreed = readLongLong(from);
565 readInt(from); // obsolete field
566 if (GET_PROTOCOL_MINOR(clientVersion) >= 5) {
567 /* removed options */
568 readInt(from);
569 readInt(from);
570 }
571
572 GCResults results;
573
574 startWork();
575 if (options.ignoreLiveness)
576 throw Error("you are not allowed to ignore liveness");
577 store->collectGarbage(options, results);
578 stopWork();
579
580 writeStrings(results.paths, to);
581 writeLongLong(results.bytesFreed, to);
582 writeLongLong(0, to); // obsolete
583
584 break;
585 }
586
587 case wopSetOptions: {
588 settings.keepFailed = readInt(from) != 0;
589 if (isRemoteConnection)
590 /* When the client is remote, don't keep the failed build tree as
591 it is presumably inaccessible to the client and could fill up
592 our disk. */
593 settings.keepFailed = 0;
594
595 settings.keepGoing = readInt(from) != 0;
596 settings.set("build-fallback", readInt(from) ? "true" : "false");
597 verbosity = (Verbosity) readInt(from);
598
599 if (GET_PROTOCOL_MINOR(clientVersion) < 0x61) {
600 settings.set("build-max-jobs", std::to_string(readInt(from)));
601 settings.set("build-max-silent-time", std::to_string(readInt(from)));
602 }
603
604 if (GET_PROTOCOL_MINOR(clientVersion) >= 2) {
605#ifdef HAVE_DAEMON_OFFLOAD_HOOK
606 settings.useBuildHook = readInt(from) != 0;
607#else
608 readInt(from); // ignore the user's setting
609#endif
610 }
611
612 if (GET_PROTOCOL_MINOR(clientVersion) >= 4) {
613 settings.buildVerbosity = (Verbosity) readInt(from);
614 logType = (LogType) readInt(from);
615 settings.printBuildTrace = readInt(from) != 0;
616 }
617 if (GET_PROTOCOL_MINOR(clientVersion) >= 6
618 && GET_PROTOCOL_MINOR(clientVersion) < 0x61)
619 settings.set("build-cores", std::to_string(readInt(from)));
620 if (GET_PROTOCOL_MINOR(clientVersion) >= 10) {
621 if (settings.useSubstitutes)
622 settings.set("build-use-substitutes", readInt(from) ? "true" : "false");
623 else
624 readInt(from); // substitutes remain disabled
625 }
626 if (GET_PROTOCOL_MINOR(clientVersion) >= 12) {
627 unsigned int n = readInt(from);
628 for (unsigned int i = 0; i < n; i++) {
629 string name = readString(from);
630 string value = readString(from);
631 if (name == "build-timeout" || name == "build-max-silent-time"
632 || name == "build-max-log-size"
633 || name == "build-max-jobs" || name == "build-cores"
634 || name == "build-repeat"
635 || name == "multiplexed-build-output")
636 settings.set(name, value);
637 else if (name == "user-name"
638 && settings.clientUid == (uid_t) -1) {
639 /* Create the user profile. This is necessary if
640 clientUid = -1, for instance because the client
641 connected over TCP. */
642 struct passwd *pw = getpwnam(value.c_str());
643 if (pw != NULL)
644 store->createUser(value, pw->pw_uid);
645 else
646 printMsg(lvlInfo, std::format("user name {} not found", value));
647 }
648 else
649 settings.set(trusted ? name : "untrusted-" + name, value);
650 }
651 }
652 settings.update();
653 startWork();
654 stopWork();
655 break;
656 }
657
658 case wopQuerySubstitutablePathInfo: {
659 Path path = absPath(readString(from));
660 startWork();
661 SubstitutablePathInfos infos;
662 store->querySubstitutablePathInfos(PathSet{path}, infos);
663 stopWork();
664 SubstitutablePathInfos::iterator i = infos.find(path);
665 if (i == infos.end())
666 writeInt(0, to);
667 else {
668 writeInt(1, to);
669 writeString(i->second.deriver, to);
670 writeStrings(i->second.references, to);
671 writeLongLong(i->second.downloadSize, to);
672 if (GET_PROTOCOL_MINOR(clientVersion) >= 7)
673 writeLongLong(i->second.narSize, to);
674 }
675 break;
676 }
677
678 case wopQuerySubstitutablePathInfos: {
679 PathSet paths = readStorePaths<PathSet>(from);
680 startWork();
681 SubstitutablePathInfos infos;
682 store->querySubstitutablePathInfos(paths, infos);
683 stopWork();
684 writeInt(infos.size(), to);
685 for (auto& i : infos) {
686 writeString(i.first, to);
687 writeString(i.second.deriver, to);
688 writeStrings(i.second.references, to);
689 writeLongLong(i.second.downloadSize, to);
690 writeLongLong(i.second.narSize, to);
691 }
692 break;
693 }
694
695 case wopQueryAllValidPaths: {
696 startWork();
697 PathSet paths = store->queryAllValidPaths();
698 stopWork();
699 writeStrings(paths, to);
700 break;
701 }
702
703 case wopQueryFailedPaths: {
704 startWork();
705 PathSet paths = store->queryFailedPaths();
706 stopWork();
707 writeStrings(paths, to);
708 break;
709 }
710
711 case wopClearFailedPaths: {
712 PathSet paths = readStrings<PathSet>(from);
713 startWork();
714 store->clearFailedPaths(paths);
715 stopWork();
716 writeInt(1, to);
717 break;
718 }
719
720 case wopQueryPathInfo: {
721 Path path = readStorePath(from);
722 startWork();
723 ValidPathInfo info = store->queryPathInfo(path);
724 stopWork();
725 writeString(info.deriver, to);
726 writeString(printHash(info.hash), to);
727 writeStrings(info.references, to);
728 writeInt(info.registrationTime, to);
729 writeLongLong(info.narSize, to);
730 break;
731 }
732
733 case wopOptimiseStore:
734 startWork();
735 store->optimiseStore();
736 stopWork();
737 writeInt(1, to);
738 break;
739
740 case wopVerifyStore: {
741 bool checkContents = readInt(from) != 0;
742 bool repair = readInt(from) != 0;
743 startWork();
744 if (repair && !trusted)
745 throw Error("you are not privileged to repair paths");
746 bool errors = store->verifyStore(checkContents, repair);
747 stopWork();
748 writeInt(errors, to);
749 break;
750 }
751
752 case wopBuiltinBuilders: {
753 startWork();
754 auto names = builtinBuilderNames();
755 stopWork();
756 writeStrings(names, to);
757 break;
758 }
759
760 case wopSubstituteURLs: {
761 startWork();
762 Strings urls;
763 if (settings.get("build-use-substitutes", std::string("false")) == "true") {
764 /* First check the client-provided substitute URLs, then those
765 passed to the daemon. */
766 auto str = settings.get("untrusted-substitute-urls", std::string(""));
767 if (str.empty()) {
768 str = settings.get("substitute-urls", std::string(""));
769 }
770 urls = tokenizeString<Strings>(str);
771 }
772 stopWork();
773 writeStrings(urls, to);
774 break;
775 }
776
777 default:
778 throw Error(std::format("invalid operation {}", op));
779 }
780}
781
782
783static void processConnection(bool trusted, uid_t userId)
784{
785 canSendStderr = false;
786 _writeToStderr = tunnelStderr;
787
788#ifdef HAVE_HUP_NOTIFICATION
789 /* Allow us to receive SIGPOLL for events on the client socket. */
790 setSigPollAction(false);
791 if (fcntl(from.fd, F_SETOWN, getpid()) == -1)
792 throw SysError("F_SETOWN");
793 if (fcntl(from.fd, F_SETFL, fcntl(from.fd, F_GETFL, 0) | O_ASYNC) == -1)
794 throw SysError("F_SETFL");
795#endif
796
797 /* Exchange the greeting. */
798 unsigned int magic = readInt(from);
799 if (magic != WORKER_MAGIC_1) throw Error("protocol mismatch");
800 writeInt(WORKER_MAGIC_2, to);
801 writeInt(PROTOCOL_VERSION, to);
802 to.flush();
803 unsigned int clientVersion = readInt(from);
804
805 if (GET_PROTOCOL_MINOR(clientVersion) >= 14 && readInt(from))
806 setAffinityTo(readInt(from));
807
808 bool reserveSpace = true;
809 if (GET_PROTOCOL_MINOR(clientVersion) >= 11)
810 reserveSpace = readInt(from) != 0;
811
812 /* Send startup error messages to the client. */
813 startWork();
814
815 try {
816
817 /* If we can't accept clientVersion, then throw an error
818 *here* (not above). */
819
820#if 0
821 /* Prevent users from doing something very dangerous. */
822 if (geteuid() == 0 &&
823 querySetting("build-users-group", "") == "")
824 throw Error("if you run `nix-daemon' as root, then you MUST set `build-users-group'!");
825#endif
826
827 /* Catch SIGTERM & co. to ensure proper termination: closing the store
828 and its database, thereby deleting its WAL file. */
829 setTerminationSignalHandler();
830
831 /* Open the store. */
832 store = std::shared_ptr<StoreAPI>(new LocalStore(reserveSpace));
833
834 if (userId != (uid_t) -1) {
835 /* Create the user profile. */
836 struct passwd *pw = getpwuid(userId);
837 if (pw != NULL && pw->pw_name != NULL)
838 store->createUser(pw->pw_name, userId);
839 else
840 printMsg(lvlInfo, std::format("user with UID {} not found", userId));
841 }
842
843 stopWork();
844 to.flush();
845
846 } catch (Error & e) {
847 stopWork(false, e.msg(), GET_PROTOCOL_MINOR(clientVersion) >= 8 ? 1 : 0);
848 to.flush();
849 return;
850 }
851
852 /* Process client requests. */
853 unsigned int opCount = 0;
854
855 while (true) {
856 WorkerOp op;
857 try {
858 op = (WorkerOp) readInt(from);
859 } catch (EndOfFile & e) {
860 break;
861 }
862
863 opCount++;
864
865 try {
866 performOp(trusted, clientVersion, from, to, op);
867 } catch (Error & e) {
868 /* If we're not in a state where we can send replies, then
869 something went wrong processing the input of the
870 client. This can happen especially if I/O errors occur
871 during addTextToStore() / importPath(). If that
872 happens, just send the error message and exit. */
873 bool errorAllowed = canSendStderr;
874 stopWork(false, e.msg(), GET_PROTOCOL_MINOR(clientVersion) >= 8 ? e.status : 0);
875 if (!errorAllowed) throw;
876 } catch (std::bad_alloc & e) {
877 stopWork(false, "build daemon out of memory", GET_PROTOCOL_MINOR(clientVersion) >= 8 ? 1 : 0);
878 throw;
879 }
880
881 to.flush();
882
883 assert(!canSendStderr);
884 };
885
886 canSendStderr = false;
887 _isInterrupted = false;
888 printMsg(lvlDebug, std::format("{} operations", opCount));
889}
890
891
892static void sigChldHandler(int sigNo)
893{
894 /* Reap all dead children. */
895 while (waitpid(-1, 0, WNOHANG) > 0) ;
896}
897
898
899static void setSigChldAction(bool autoReap)
900{
901 struct sigaction act, oact;
902 act.sa_handler = autoReap ? sigChldHandler : SIG_DFL;
903 sigfillset(&act.sa_mask);
904 act.sa_flags = 0;
905 if (sigaction(SIGCHLD, &act, &oact))
906 throw SysError("setting SIGCHLD handler");
907}
908
909
910/* Accept a connection on FDSOCKET and fork a server process to process the
911 new connection. */
912static void acceptConnection(int fdSocket)
913{
914 uid_t clientUid = (uid_t) -1;
915 gid_t clientGid = (gid_t) -1;
916
917 try {
918 /* Important: the server process *cannot* open the SQLite
919 database, because it doesn't like forks very much. */
920 assert(!store);
921
922 /* Accept a connection. */
923 struct sockaddr_storage remoteAddr;
924 socklen_t remoteAddrLen = sizeof(remoteAddr);
925
926 try_again:
927 AutoCloseFD remote = accept(fdSocket,
928 (struct sockaddr *) &remoteAddr, &remoteAddrLen);
929 checkInterrupt();
930 if (remote == -1) {
931 if (errno == EINTR)
932 goto try_again;
933 else
934 throw SysError("accepting connection");
935 }
936
937 closeOnExec(remote);
938
939 {
940 int enabled = 1;
941
942 /* If we're on a TCP connection, disable Nagle's algorithm so that
943 data is sent as soon as possible. */
944 (void) setsockopt(remote, SOL_TCP, TCP_NODELAY,
945 &enabled, sizeof enabled);
946
947#if defined(TCP_QUICKACK)
948 /* Enable TCP quick-ack if applicable; this might help a little. */
949 (void) setsockopt(remote, SOL_TCP, TCP_QUICKACK,
950 &enabled, sizeof enabled);
951#endif
952 }
953
954 pid_t clientPid = -1;
955 bool trusted = false;
956
957 /* Get the identity of the caller, if possible. */
958 if (remoteAddr.ss_family == AF_UNIX) {
959#if defined(SO_PEERCRED)
960 ucred cred;
961 socklen_t credLen = sizeof(cred);
962 if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED,
963 &cred, &credLen) == -1)
964 throw SysError("getting peer credentials");
965
966 clientPid = cred.pid;
967 clientUid = cred.uid;
968 clientGid = cred.gid;
969
970 /* The root user is always trusted; additionally, when running as
971 an unprivileged user, that user is also trusted. */
972 trusted = (clientUid == 0) || (clientUid == getuid());
973
974 struct passwd * pw = getpwuid(cred.uid);
975 string user = pw ? pw->pw_name : std::to_string(cred.uid);
976
977 printMsg(lvlInfo, std::format("accepted connection from pid {}, user {}",
978 clientPid, user));
979#endif
980 } else {
981 char address_str[128];
982 const char *result;
983
984 if (remoteAddr.ss_family == AF_INET) {
985 struct sockaddr_in *addr = (struct sockaddr_in *) &remoteAddr;
986 result = inet_ntop(AF_INET, &addr->sin_addr,
987 address_str, sizeof address_str);
988 } else if (remoteAddr.ss_family == AF_INET6) {
989 struct sockaddr_in6 *addr = (struct sockaddr_in6 *) &remoteAddr;
990 result = inet_ntop(AF_INET6, &addr->sin6_addr,
991 address_str, sizeof address_str);
992 } else {
993 result = NULL;
994 }
995
996 if (result != NULL) {
997 printMsg(lvlInfo, std::format("accepted connection from {}", address_str));
998 }
999 }
1000
1001 /* Fork a child to handle the connection. */
1002 startProcess([&]() {
1003 close(fdSocket);
1004
1005 /* Background the daemon. */
1006 if (setsid() == -1)
1007 throw SysError("creating a new session");
1008
1009 /* Restore normal handling of SIGCHLD. */
1010 setSigChldAction(false);
1011
1012 /* For debugging, stuff the pid into argv[1]. */
1013 if (clientPid != -1 && argvSaved[1]) {
1014 string processName = std::to_string(clientPid);
1015 strncpy(argvSaved[1], processName.c_str(), strlen(argvSaved[1]));
1016 }
1017
1018 /* Store the client's user and group for this connection. This
1019 has to be done in the forked process since it is per
1020 connection. Setting these to -1 means: do not change. */
1021 settings.clientUid = clientUid;
1022 settings.clientGid = clientGid;
1023 isRemoteConnection = (remoteAddr.ss_family != AF_UNIX);
1024
1025 /* Handle the connection. */
1026 from.fd = remote;
1027 to.fd = remote;
1028 processConnection(trusted, clientUid);
1029
1030 exit(0);
1031 }, false, "unexpected build daemon error: ", true);
1032
1033 } catch (Interrupted & e) {
1034 throw;
1035 } catch (Error & e) {
1036 printMsg(lvlError, std::format("error processing connection: {}", e.msg()));
1037 }
1038}
1039
1040static void daemonLoop(const std::vector<int>& sockets)
1041{
1042 if (chdir("/") == -1)
1043 throw SysError("cannot change current directory");
1044
1045 /* Get rid of children automatically; don't let them become
1046 zombies. */
1047 setSigChldAction(true);
1048
1049 /* Mark sockets as close-on-exec. */
1050 for(int fd: sockets) {
1051 closeOnExec(fd);
1052 }
1053
1054 /* Prepare the FD set corresponding to SOCKETS. */
1055 auto initializeFDSet = [&](fd_set *set) {
1056 FD_ZERO(set);
1057 for (int fd: sockets) {
1058 FD_SET(fd, set);
1059 }
1060 };
1061
1062 /* Loop accepting connections. */
1063 while (1) {
1064 fd_set readfds;
1065
1066 initializeFDSet(&readfds);
1067 int count =
1068 select(*std::max_element(sockets.begin(), sockets.end()) + 1,
1069 &readfds, NULL, NULL,
1070 NULL);
1071 if (count < 0) {
1072 int err = errno;
1073 if (err == EINTR)
1074 continue;
1075 throw SysError(std::format("select error: {}", strerror(err)));
1076 }
1077
1078 for (unsigned int i = 0; i < sockets.size(); i++) {
1079 if (FD_ISSET(sockets[i], &readfds)) {
1080 acceptConnection(sockets[i]);
1081 }
1082 }
1083 }
1084}
1085
1086
1087void run(const std::vector<int>& sockets)
1088{
1089 daemonLoop(sockets);
1090}