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