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