1 #include "references.hh"
2 #include "pathlocks.hh"
3 #include "globals.hh"
4 #include "local-store.hh"
5 #include "util.hh"
6 #include "archive.hh"
7 #include "affinity.hh"
8 #include "builtins.hh"
9 #include "download.hh"
10 #include "finally.hh"
11 #include "compression.hh"
12 #include "json.hh"
13 #include "nar-info.hh"
14 #include "parsed-derivations.hh"
15 #include "machines.hh"
16
17 #include <algorithm>
18 #include <iostream>
19 #include <map>
20 #include <sstream>
21 #include <thread>
22 #include <future>
23 #include <chrono>
24 #include <regex>
25 #include <queue>
26
27 #include <limits.h>
28 #include <sys/time.h>
29 #include <sys/wait.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <sys/utsname.h>
33 #include <sys/select.h>
34 #include <sys/resource.h>
35 #include <sys/socket.h>
36 #include <fcntl.h>
37 #include <netdb.h>
38 #include <unistd.h>
39 #include <errno.h>
40 #include <cstring>
41 #include <termios.h>
42
43 #include <pwd.h>
44 #include <grp.h>
45
46 /* Includes required for chroot support. */
47 #if __linux__ || __FreeBSD__
48 #include <sys/socket.h>
49 #include <sys/ioctl.h>
50 #include <net/if.h>
51 #endif
52 #if __linux__
53 #include <netinet/ip.h>
54 #include <sys/personality.h>
55 #endif
56 #if __linux__ || __FreeBSD__
57 #include <sys/mman.h>
58 #include <sched.h>
59 #include <sys/param.h>
60 #include <sys/mount.h>
61 #include <sys/syscall.h>
62 #if HAVE_SECCOMP
63 #include <seccomp.h>
64 #endif
65 #define pivot_root(new_root, put_old) (syscall(SYS_pivot_root, new_root, put_old))
66 #endif
67
68 #if HAVE_STATVFS
69 #include <sys/statvfs.h>
70 #endif
71
72 #include <nlohmann/json.hpp>
73
74
75 namespace nix {
76
77 using std::map;
78
79
80 static string pathNullDevice = "/dev/null";
81
82
83 /* Forward definition. */
84 class Worker;
85 struct HookInstance;
86
87
88 /* A pointer to a goal. */
89 class Goal;
90 class DerivationGoal;
91 typedef std::shared_ptr<Goal> GoalPtr;
92 typedef std::weak_ptr<Goal> WeakGoalPtr;
93
94 struct CompareGoalPtrs {
95 bool operator() (const GoalPtr & a, const GoalPtr & b) const;
96 };
97
98 /* Set of goals. */
99 typedef set<GoalPtr, CompareGoalPtrs> Goals;
100 typedef list<WeakGoalPtr> WeakGoals;
101
102 /* A map of paths to goals (and the other way around). */
103 typedef map<Path, WeakGoalPtr> WeakGoalMap;
104
105
106
107 class Goal : public std::enable_shared_from_this<Goal>
108 {
109 public:
110 typedef enum {ecBusy, ecSuccess, ecFailed, ecNoSubstituters, ecIncompleteClosure} ExitCode;
111
112 protected:
113
114 /* Backlink to the worker. */
115 Worker & worker;
116
117 /* Goals that this goal is waiting for. */
118 Goals waitees;
119
120 /* Goals waiting for this one to finish. Must use weak pointers
121 here to prevent cycles. */
122 WeakGoals waiters;
123
124 /* Number of goals we are/were waiting for that have failed. */
125 unsigned int nrFailed;
126
127 /* Number of substitution goals we are/were waiting for that
128 failed because there are no substituters. */
129 unsigned int nrNoSubstituters;
130
131 /* Number of substitution goals we are/were waiting for that
132 failed because othey had unsubstitutable references. */
133 unsigned int nrIncompleteClosure;
134
135 /* Name of this goal for debugging purposes. */
136 string name;
137
138 /* Whether the goal is finished. */
139 ExitCode exitCode;
140
Goal(Worker & worker)141 Goal(Worker & worker) : worker(worker)
142 {
143 nrFailed = nrNoSubstituters = nrIncompleteClosure = 0;
144 exitCode = ecBusy;
145 }
146
~Goal()147 virtual ~Goal()
148 {
149 trace("goal destroyed");
150 }
151
152 public:
153 virtual void work() = 0;
154
155 void addWaitee(GoalPtr waitee);
156
157 virtual void waiteeDone(GoalPtr waitee, ExitCode result);
158
handleChildOutput(int fd,const string & data)159 virtual void handleChildOutput(int fd, const string & data)
160 {
161 abort();
162 }
163
handleEOF(int fd)164 virtual void handleEOF(int fd)
165 {
166 abort();
167 }
168
169 void trace(const FormatOrString & fs);
170
getName()171 string getName()
172 {
173 return name;
174 }
175
getExitCode()176 ExitCode getExitCode()
177 {
178 return exitCode;
179 }
180
181 /* Callback in case of a timeout. It should wake up its waiters,
182 get rid of any running child processes that are being monitored
183 by the worker (important!), etc. */
184 virtual void timedOut() = 0;
185
186 virtual string key() = 0;
187
188 protected:
189
190 virtual void amDone(ExitCode result);
191 };
192
193
operator ()(const GoalPtr & a,const GoalPtr & b) const194 bool CompareGoalPtrs::operator() (const GoalPtr & a, const GoalPtr & b) const {
195 string s1 = a->key();
196 string s2 = b->key();
197 return s1 < s2;
198 }
199
200
201 typedef std::chrono::time_point<std::chrono::steady_clock> steady_time_point;
202
203
204 /* A mapping used to remember for each child process to what goal it
205 belongs, and file descriptors for receiving log data and output
206 path creation commands. */
207 struct Child
208 {
209 WeakGoalPtr goal;
210 Goal * goal2; // ugly hackery
211 set<int> fds;
212 bool respectTimeouts;
213 bool inBuildSlot;
214 steady_time_point lastOutput; /* time we last got output on stdout/stderr */
215 steady_time_point timeStarted;
216 };
217
218
219 /* The worker class. */
220 class Worker
221 {
222 private:
223
224 /* Note: the worker should only have strong pointers to the
225 top-level goals. */
226
227 /* The top-level goals of the worker. */
228 Goals topGoals;
229
230 /* Goals that are ready to do some work. */
231 WeakGoals awake;
232
233 /* Goals waiting for a build slot. */
234 WeakGoals wantingToBuild;
235
236 /* Child processes currently running. */
237 std::list<Child> children;
238
239 /* Number of build slots occupied. This includes local builds and
240 substitutions but not remote builds via the build hook. */
241 unsigned int nrLocalBuilds;
242
243 /* Maps used to prevent multiple instantiations of a goal for the
244 same derivation / path. */
245 WeakGoalMap derivationGoals;
246 WeakGoalMap substitutionGoals;
247
248 /* Goals waiting for busy paths to be unlocked. */
249 WeakGoals waitingForAnyGoal;
250
251 /* Goals sleeping for a few seconds (polling a lock). */
252 WeakGoals waitingForAWhile;
253
254 /* Last time the goals in `waitingForAWhile' where woken up. */
255 steady_time_point lastWokenUp;
256
257 /* Cache for pathContentsGood(). */
258 std::map<Path, bool> pathContentsGoodCache;
259
260 public:
261
262 const Activity act;
263 const Activity actDerivations;
264 const Activity actSubstitutions;
265
266 /* Set if at least one derivation had a BuildError (i.e. permanent
267 failure). */
268 bool permanentFailure;
269
270 /* Set if at least one derivation had a timeout. */
271 bool timedOut;
272
273 /* Set if at least one derivation fails with a hash mismatch. */
274 bool hashMismatch;
275
276 /* Set if at least one derivation is not deterministic in check mode. */
277 bool checkMismatch;
278
279 LocalStore & store;
280
281 std::unique_ptr<HookInstance> hook;
282
283 uint64_t expectedBuilds = 0;
284 uint64_t doneBuilds = 0;
285 uint64_t failedBuilds = 0;
286 uint64_t runningBuilds = 0;
287
288 uint64_t expectedSubstitutions = 0;
289 uint64_t doneSubstitutions = 0;
290 uint64_t failedSubstitutions = 0;
291 uint64_t runningSubstitutions = 0;
292 uint64_t expectedDownloadSize = 0;
293 uint64_t doneDownloadSize = 0;
294 uint64_t expectedNarSize = 0;
295 uint64_t doneNarSize = 0;
296
297 /* Whether to ask the build hook if it can build a derivation. If
298 it answers with "decline-permanently", we don't try again. */
299 bool tryBuildHook = true;
300
301 Worker(LocalStore & store);
302 ~Worker();
303
304 /* Make a goal (with caching). */
305 GoalPtr makeDerivationGoal(const Path & drvPath, const StringSet & wantedOutputs, BuildMode buildMode = bmNormal);
306 std::shared_ptr<DerivationGoal> makeBasicDerivationGoal(const Path & drvPath,
307 const BasicDerivation & drv, BuildMode buildMode = bmNormal);
308 GoalPtr makeSubstitutionGoal(const Path & storePath, RepairFlag repair = NoRepair);
309
310 /* Remove a dead goal. */
311 void removeGoal(GoalPtr goal);
312
313 /* Wake up a goal (i.e., there is something for it to do). */
314 void wakeUp(GoalPtr goal);
315
316 /* Return the number of local build and substitution processes
317 currently running (but not remote builds via the build
318 hook). */
319 unsigned int getNrLocalBuilds();
320
321 /* Registers a running child process. `inBuildSlot' means that
322 the process counts towards the jobs limit. */
323 void childStarted(GoalPtr goal, const set<int> & fds,
324 bool inBuildSlot, bool respectTimeouts);
325
326 /* Unregisters a running child process. `wakeSleepers' should be
327 false if there is no sense in waking up goals that are sleeping
328 because they can't run yet (e.g., there is no free build slot,
329 or the hook would still say `postpone'). */
330 void childTerminated(Goal * goal, bool wakeSleepers = true);
331
332 /* Put `goal' to sleep until a build slot becomes available (which
333 might be right away). */
334 void waitForBuildSlot(GoalPtr goal);
335
336 /* Wait for any goal to finish. Pretty indiscriminate way to
337 wait for some resource that some other goal is holding. */
338 void waitForAnyGoal(GoalPtr goal);
339
340 /* Wait for a few seconds and then retry this goal. Used when
341 waiting for a lock held by another process. This kind of
342 polling is inefficient, but POSIX doesn't really provide a way
343 to wait for multiple locks in the main select() loop. */
344 void waitForAWhile(GoalPtr goal);
345
346 /* Loop until the specified top-level goals have finished. */
347 void run(const Goals & topGoals);
348
349 /* Wait for input to become available. */
350 void waitForInput();
351
352 unsigned int exitStatus();
353
354 /* Check whether the given valid path exists and has the right
355 contents. */
356 bool pathContentsGood(const Path & path);
357
358 void markContentsGood(const Path & path);
359
updateProgress()360 void updateProgress()
361 {
362 actDerivations.progress(doneBuilds, expectedBuilds + doneBuilds, runningBuilds, failedBuilds);
363 actSubstitutions.progress(doneSubstitutions, expectedSubstitutions + doneSubstitutions, runningSubstitutions, failedSubstitutions);
364 act.setExpected(actDownload, expectedDownloadSize + doneDownloadSize);
365 act.setExpected(actCopyPath, expectedNarSize + doneNarSize);
366 }
367 };
368
369
370 //////////////////////////////////////////////////////////////////////
371
372
addToWeakGoals(WeakGoals & goals,GoalPtr p)373 void addToWeakGoals(WeakGoals & goals, GoalPtr p)
374 {
375 // FIXME: necessary?
376 // FIXME: O(n)
377 for (auto & i : goals)
378 if (i.lock() == p) return;
379 goals.push_back(p);
380 }
381
382
addWaitee(GoalPtr waitee)383 void Goal::addWaitee(GoalPtr waitee)
384 {
385 waitees.insert(waitee);
386 addToWeakGoals(waitee->waiters, shared_from_this());
387 }
388
389
waiteeDone(GoalPtr waitee,ExitCode result)390 void Goal::waiteeDone(GoalPtr waitee, ExitCode result)
391 {
392 assert(waitees.find(waitee) != waitees.end());
393 waitees.erase(waitee);
394
395 trace(format("waitee '%1%' done; %2% left") %
396 waitee->name % waitees.size());
397
398 if (result == ecFailed || result == ecNoSubstituters || result == ecIncompleteClosure) ++nrFailed;
399
400 if (result == ecNoSubstituters) ++nrNoSubstituters;
401
402 if (result == ecIncompleteClosure) ++nrIncompleteClosure;
403
404 if (waitees.empty() || (result == ecFailed && !settings.keepGoing)) {
405
406 /* If we failed and keepGoing is not set, we remove all
407 remaining waitees. */
408 for (auto & goal : waitees) {
409 WeakGoals waiters2;
410 for (auto & j : goal->waiters)
411 if (j.lock() != shared_from_this()) waiters2.push_back(j);
412 goal->waiters = waiters2;
413 }
414 waitees.clear();
415
416 worker.wakeUp(shared_from_this());
417 }
418 }
419
420
amDone(ExitCode result)421 void Goal::amDone(ExitCode result)
422 {
423 trace("done");
424 assert(exitCode == ecBusy);
425 assert(result == ecSuccess || result == ecFailed || result == ecNoSubstituters || result == ecIncompleteClosure);
426 exitCode = result;
427 for (auto & i : waiters) {
428 GoalPtr goal = i.lock();
429 if (goal) goal->waiteeDone(shared_from_this(), result);
430 }
431 waiters.clear();
432 worker.removeGoal(shared_from_this());
433 }
434
435
trace(const FormatOrString & fs)436 void Goal::trace(const FormatOrString & fs)
437 {
438 debug("%1%: %2%", name, fs.s);
439 }
440
441
442
443 //////////////////////////////////////////////////////////////////////
444
445
446 /* Common initialisation performed in child processes. */
commonChildInit(Pipe & logPipe)447 static void commonChildInit(Pipe & logPipe)
448 {
449 restoreSignals();
450
451 /* Put the child in a separate session (and thus a separate
452 process group) so that it has no controlling terminal (meaning
453 that e.g. ssh cannot open /dev/tty) and it doesn't receive
454 terminal signals. */
455 if (setsid() == -1)
456 throw SysError(format("creating a new session"));
457
458 /* Dup the write side of the logger pipe into stderr. */
459 if (dup2(logPipe.writeSide.get(), STDERR_FILENO) == -1)
460 throw SysError("cannot pipe standard error into log file");
461
462 /* Dup stderr to stdout. */
463 if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1)
464 throw SysError("cannot dup stderr into stdout");
465
466 /* Reroute stdin to /dev/null. */
467 int fdDevNull = open(pathNullDevice.c_str(), O_RDWR);
468 if (fdDevNull == -1)
469 throw SysError(format("cannot open '%1%'") % pathNullDevice);
470 if (dup2(fdDevNull, STDIN_FILENO) == -1)
471 throw SysError("cannot dup null device into stdin");
472 close(fdDevNull);
473 }
474
handleDiffHook(uid_t uid,uid_t gid,Path tryA,Path tryB,Path drvPath,Path tmpDir)475 void handleDiffHook(uid_t uid, uid_t gid, Path tryA, Path tryB, Path drvPath, Path tmpDir)
476 {
477 auto diffHook = settings.diffHook;
478 if (diffHook != "" && settings.runDiffHook) {
479 try {
480 RunOptions diffHookOptions(diffHook,{tryA, tryB, drvPath, tmpDir});
481 diffHookOptions.searchPath = true;
482 diffHookOptions.uid = uid;
483 diffHookOptions.gid = gid;
484 diffHookOptions.chdir = "/";
485
486 auto diffRes = runProgram(diffHookOptions);
487 if (!statusOk(diffRes.first))
488 throw ExecError(diffRes.first, fmt("diff-hook program '%1%' %2%", diffHook, statusToString(diffRes.first)));
489
490 if (diffRes.second != "")
491 printError(chomp(diffRes.second));
492 } catch (Error & error) {
493 printError("diff hook execution failed: %s", error.what());
494 }
495 }
496 }
497
498 //////////////////////////////////////////////////////////////////////
499
500
501 class UserLock
502 {
503 private:
504 /* POSIX locks suck. If we have a lock on a file, and we open and
505 close that file again (without closing the original file
506 descriptor), we lose the lock. So we have to be *very* careful
507 not to open a lock file on which we are holding a lock. */
508 static Sync<PathSet> lockedPaths_;
509
510 Path fnUserLock;
511 AutoCloseFD fdUserLock;
512
513 string user;
514 uid_t uid;
515 gid_t gid;
516 std::vector<gid_t> supplementaryGIDs;
517
518 public:
519 UserLock();
520 ~UserLock();
521
522 void kill();
523
getUser()524 string getUser() { return user; }
getUID()525 uid_t getUID() { assert(uid); return uid; }
getGID()526 uid_t getGID() { assert(gid); return gid; }
getSupplementaryGIDs()527 std::vector<gid_t> getSupplementaryGIDs() { return supplementaryGIDs; }
528
enabled()529 bool enabled() { return uid != 0; }
530
531 };
532
533
534 Sync<PathSet> UserLock::lockedPaths_;
535
536
UserLock()537 UserLock::UserLock()
538 {
539 assert(settings.buildUsersGroup != "");
540
541 /* Get the members of the build-users-group. */
542 struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str());
543 if (!gr)
544 throw Error(format("the group '%1%' specified in 'build-users-group' does not exist")
545 % settings.buildUsersGroup);
546 gid = gr->gr_gid;
547
548 /* Copy the result of getgrnam. */
549 Strings users;
550 for (char * * p = gr->gr_mem; *p; ++p) {
551 debug(format("found build user '%1%'") % *p);
552 users.push_back(*p);
553 }
554
555 if (users.empty())
556 throw Error(format("the build users group '%1%' has no members")
557 % settings.buildUsersGroup);
558
559 /* Find a user account that isn't currently in use for another
560 build. */
561 for (auto & i : users) {
562 debug(format("trying user '%1%'") % i);
563
564 struct passwd * pw = getpwnam(i.c_str());
565 if (!pw)
566 throw Error(format("the user '%1%' in the group '%2%' does not exist")
567 % i % settings.buildUsersGroup);
568
569 createDirs(settings.nixStateDir + "/userpool");
570
571 fnUserLock = (format("%1%/userpool/%2%") % settings.nixStateDir % pw->pw_uid).str();
572
573 {
574 auto lockedPaths(lockedPaths_.lock());
575 if (lockedPaths->count(fnUserLock))
576 /* We already have a lock on this one. */
577 continue;
578 lockedPaths->insert(fnUserLock);
579 }
580
581 try {
582
583 AutoCloseFD fd = open(fnUserLock.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600);
584 if (!fd)
585 throw SysError(format("opening user lock '%1%'") % fnUserLock);
586
587 if (lockFile(fd.get(), ltWrite, false)) {
588 fdUserLock = std::move(fd);
589 user = i;
590 uid = pw->pw_uid;
591
592 /* Sanity check... */
593 if (uid == getuid() || uid == geteuid())
594 throw Error(format("the Nix user should not be a member of '%1%'")
595 % settings.buildUsersGroup);
596
597 #if __linux__
598 /* Get the list of supplementary groups of this build user. This
599 is usually either empty or contains a group such as "kvm". */
600 supplementaryGIDs.resize(10);
601 int ngroups = supplementaryGIDs.size();
602 int err = getgrouplist(pw->pw_name, pw->pw_gid,
603 supplementaryGIDs.data(), &ngroups);
604 if (err == -1)
605 throw Error(format("failed to get list of supplementary groups for '%1%'") % pw->pw_name);
606
607 supplementaryGIDs.resize(ngroups);
608 #endif
609
610 return;
611 }
612
613 } catch (...) {
614 lockedPaths_.lock()->erase(fnUserLock);
615 }
616 }
617
618 throw Error(format("all build users are currently in use; "
619 "consider creating additional users and adding them to the '%1%' group")
620 % settings.buildUsersGroup);
621 }
622
623
~UserLock()624 UserLock::~UserLock()
625 {
626 auto lockedPaths(lockedPaths_.lock());
627 assert(lockedPaths->count(fnUserLock));
628 lockedPaths->erase(fnUserLock);
629 }
630
631
kill()632 void UserLock::kill()
633 {
634 killUser(uid);
635 }
636
637
638 //////////////////////////////////////////////////////////////////////
639
640
641 struct HookInstance
642 {
643 /* Pipes for talking to the build hook. */
644 Pipe toHook;
645
646 /* Pipe for the hook's standard output/error. */
647 Pipe fromHook;
648
649 /* Pipe for the builder's standard output/error. */
650 Pipe builderOut;
651
652 /* The process ID of the hook. */
653 Pid pid;
654
655 FdSink sink;
656
657 std::map<ActivityId, Activity> activities;
658
659 HookInstance();
660
661 ~HookInstance();
662 };
663
664
HookInstance()665 HookInstance::HookInstance()
666 {
667 debug("starting build hook '%s'", settings.buildHook);
668
669 /* Create a pipe to get the output of the child. */
670 fromHook.create();
671
672 /* Create the communication pipes. */
673 toHook.create();
674
675 /* Create a pipe to get the output of the builder. */
676 builderOut.create();
677
678 /* Fork the hook. */
679 pid = startProcess([&]() {
680
681 commonChildInit(fromHook);
682
683 if (chdir("/") == -1) throw SysError("changing into /");
684
685 /* Dup the communication pipes. */
686 if (dup2(toHook.readSide.get(), STDIN_FILENO) == -1)
687 throw SysError("dupping to-hook read side");
688
689 /* Use fd 4 for the builder's stdout/stderr. */
690 if (dup2(builderOut.writeSide.get(), 4) == -1)
691 throw SysError("dupping builder's stdout/stderr");
692
693 /* Hack: pass the read side of that fd to allow build-remote
694 to read SSH error messages. */
695 if (dup2(builderOut.readSide.get(), 5) == -1)
696 throw SysError("dupping builder's stdout/stderr");
697
698 Strings args = {
699 baseNameOf(settings.buildHook),
700 std::to_string(verbosity),
701 };
702
703 execv(settings.buildHook.get().c_str(), stringsToCharPtrs(args).data());
704
705 throw SysError("executing '%s'", settings.buildHook);
706 });
707
708 pid.setSeparatePG(true);
709 fromHook.writeSide = -1;
710 toHook.readSide = -1;
711
712 sink = FdSink(toHook.writeSide.get());
713 std::map<std::string, Config::SettingInfo> settings;
714 globalConfig.getSettings(settings);
715 for (auto & setting : settings)
716 sink << 1 << setting.first << setting.second.value;
717 sink << 0;
718 }
719
720
~HookInstance()721 HookInstance::~HookInstance()
722 {
723 try {
724 toHook.writeSide = -1;
725 if (pid != -1) pid.kill();
726 } catch (...) {
727 ignoreException();
728 }
729 }
730
731
732 //////////////////////////////////////////////////////////////////////
733
734
735 typedef map<std::string, std::string> StringRewrites;
736
737
rewriteStrings(std::string s,const StringRewrites & rewrites)738 std::string rewriteStrings(std::string s, const StringRewrites & rewrites)
739 {
740 for (auto & i : rewrites) {
741 size_t j = 0;
742 while ((j = s.find(i.first, j)) != string::npos)
743 s.replace(j, i.first.size(), i.second);
744 }
745 return s;
746 }
747
748
749 //////////////////////////////////////////////////////////////////////
750
751
752 typedef enum {rpAccept, rpDecline, rpPostpone} HookReply;
753
754 class SubstitutionGoal;
755
756 class DerivationGoal : public Goal
757 {
758 private:
759 /* Whether to use an on-disk .drv file. */
760 bool useDerivation;
761
762 /* The path of the derivation. */
763 Path drvPath;
764
765 /* The specific outputs that we need to build. Empty means all of
766 them. */
767 StringSet wantedOutputs;
768
769 /* Whether additional wanted outputs have been added. */
770 bool needRestart = false;
771
772 /* Whether to retry substituting the outputs after building the
773 inputs. */
774 bool retrySubstitution;
775
776 /* The derivation stored at drvPath. */
777 std::unique_ptr<BasicDerivation> drv;
778
779 std::unique_ptr<ParsedDerivation> parsedDrv;
780
781 /* The remainder is state held during the build. */
782
783 /* Locks on the output paths. */
784 PathLocks outputLocks;
785
786 /* All input paths (that is, the union of FS closures of the
787 immediate input paths). */
788 PathSet inputPaths;
789
790 /* Referenceable paths (i.e., input and output paths). */
791 PathSet allPaths;
792
793 /* Outputs that are already valid. If we're repairing, these are
794 the outputs that are valid *and* not corrupt. */
795 PathSet validPaths;
796
797 /* Outputs that are corrupt or not valid. */
798 PathSet missingPaths;
799
800 /* User selected for running the builder. */
801 std::unique_ptr<UserLock> buildUser;
802
803 /* The process ID of the builder. */
804 Pid pid;
805
806 /* The temporary directory. */
807 Path tmpDir;
808
809 /* The path of the temporary directory in the sandbox. */
810 Path tmpDirInSandbox;
811
812 /* File descriptor for the log file. */
813 AutoCloseFD fdLogFile;
814 std::shared_ptr<BufferedSink> logFileSink, logSink;
815
816 /* Number of bytes received from the builder's stdout/stderr. */
817 unsigned long logSize;
818
819 /* The most recent log lines. */
820 std::list<std::string> logTail;
821
822 std::string currentLogLine;
823 size_t currentLogLinePos = 0; // to handle carriage return
824
825 std::string currentHookLine;
826
827 /* Pipe for the builder's standard output/error. */
828 Pipe builderOut;
829
830 /* Pipe for synchronising updates to the builder user namespace. */
831 Pipe userNamespaceSync;
832
833 /* The build hook. */
834 std::unique_ptr<HookInstance> hook;
835
836 /* Whether we're currently doing a chroot build. */
837 bool useChroot = false;
838
839 Path chrootRootDir;
840
841 /* RAII object to delete the chroot directory. */
842 std::shared_ptr<AutoDelete> autoDelChroot;
843
844 /* Whether this is a fixed-output derivation. */
845 bool fixedOutput;
846
847 /* Whether to run the build in a private network namespace. */
848 bool privateNetwork = false;
849
850 typedef void (DerivationGoal::*GoalState)();
851 GoalState state;
852
853 /* Stuff we need to pass to initChild(). */
854 struct ChrootPath {
855 Path source;
856 bool optional;
ChrootPathnix::DerivationGoal::ChrootPath857 ChrootPath(Path source = "", bool optional = false)
858 : source(source), optional(optional)
859 { }
860 };
861 typedef map<Path, ChrootPath> DirsInChroot; // maps target path to source path
862 DirsInChroot dirsInChroot;
863
864 typedef map<string, string> Environment;
865 Environment env;
866
867 #if __APPLE__
868 typedef string SandboxProfile;
869 SandboxProfile additionalSandboxProfile;
870 #endif
871
872 /* Hash rewriting. */
873 StringRewrites inputRewrites, outputRewrites;
874 typedef map<Path, Path> RedirectedOutputs;
875 RedirectedOutputs redirectedOutputs;
876
877 BuildMode buildMode;
878
879 /* If we're repairing without a chroot, there may be outputs that
880 are valid but corrupt. So we redirect these outputs to
881 temporary paths. */
882 PathSet redirectedBadOutputs;
883
884 BuildResult result;
885
886 /* The current round, if we're building multiple times. */
887 size_t curRound = 1;
888
889 size_t nrRounds;
890
891 /* Path registration info from the previous round, if we're
892 building multiple times. Since this contains the hash, it
893 allows us to compare whether two rounds produced the same
894 result. */
895 std::map<Path, ValidPathInfo> prevInfos;
896
897 const uid_t sandboxUid = 1000;
898 const gid_t sandboxGid = 100;
899
900 const static Path homeDir;
901
902 std::unique_ptr<MaintainCount<uint64_t>> mcExpectedBuilds, mcRunningBuilds;
903
904 std::unique_ptr<Activity> act;
905
906 std::map<ActivityId, Activity> builderActivities;
907
908 /* The remote machine on which we're building. */
909 std::string machineName;
910
911 public:
912 DerivationGoal(const Path & drvPath, const StringSet & wantedOutputs,
913 Worker & worker, BuildMode buildMode = bmNormal);
914 DerivationGoal(const Path & drvPath, const BasicDerivation & drv,
915 Worker & worker, BuildMode buildMode = bmNormal);
916 ~DerivationGoal();
917
918 /* Whether we need to perform hash rewriting if there are valid output paths. */
919 bool needsHashRewrite();
920
921 void timedOut() override;
922
key()923 string key() override
924 {
925 /* Ensure that derivations get built in order of their name,
926 i.e. a derivation named "aardvark" always comes before
927 "baboon". And substitution goals always happen before
928 derivation goals (due to "b$"). */
929 return "b$" + storePathToName(drvPath) + "$" + drvPath;
930 }
931
932 void work() override;
933
getDrvPath()934 Path getDrvPath()
935 {
936 return drvPath;
937 }
938
939 /* Add wanted outputs to an already existing derivation goal. */
940 void addWantedOutputs(const StringSet & outputs);
941
getResult()942 BuildResult getResult() { return result; }
943
944 private:
945 /* The states. */
946 void getDerivation();
947 void loadDerivation();
948 void haveDerivation();
949 void outputsSubstituted();
950 void closureRepaired();
951 void inputsRealised();
952 void tryToBuild();
953 void buildDone();
954
955 /* Is the build hook willing to perform the build? */
956 HookReply tryBuildHook();
957
958 /* Start building a derivation. */
959 void startBuilder();
960
961 /* Fill in the environment for the builder. */
962 void initEnv();
963
964 /* Setup tmp dir location. */
965 void initTmpDir();
966
967 /* Write a JSON file containing the derivation attributes. */
968 void writeStructuredAttrs();
969
970 /* Make a file owned by the builder. */
971 void chownToBuilder(const Path & path);
972
973 /* Run the builder's process. */
974 void runChild();
975
976 friend int childEntry(void *);
977
978 /* Check that the derivation outputs all exist and register them
979 as valid. */
980 void registerOutputs();
981
982 /* Check that an output meets the requirements specified by the
983 'outputChecks' attribute (or the legacy
984 '{allowed,disallowed}{References,Requisites}' attributes). */
985 void checkOutputs(const std::map<std::string, ValidPathInfo> & outputs);
986
987 /* Open a log file and a pipe to it. */
988 Path openLogFile();
989
990 /* Close the log file. */
991 void closeLogFile();
992
993 /* Delete the temporary directory, if we have one. */
994 void deleteTmpDir(bool force);
995
996 /* Callback used by the worker to write to the log. */
997 void handleChildOutput(int fd, const string & data) override;
998 void handleEOF(int fd) override;
999 void flushLine();
1000
1001 /* Return the set of (in)valid paths. */
1002 PathSet checkPathValidity(bool returnValid, bool checkHash);
1003
1004 /* Abort the goal if `path' failed to build. */
1005 bool pathFailed(const Path & path);
1006
1007 /* Forcibly kill the child process, if any. */
1008 void killChild();
1009
1010 Path addHashRewrite(const Path & path);
1011
1012 void repairClosure();
1013
amDone(ExitCode result)1014 void amDone(ExitCode result) override
1015 {
1016 Goal::amDone(result);
1017 }
1018
1019 void done(BuildResult::Status status, const string & msg = "");
1020
1021 PathSet exportReferences(PathSet storePaths);
1022 };
1023
1024
1025 const Path DerivationGoal::homeDir = "/homeless-shelter";
1026
1027
DerivationGoal(const Path & drvPath,const StringSet & wantedOutputs,Worker & worker,BuildMode buildMode)1028 DerivationGoal::DerivationGoal(const Path & drvPath, const StringSet & wantedOutputs,
1029 Worker & worker, BuildMode buildMode)
1030 : Goal(worker)
1031 , useDerivation(true)
1032 , drvPath(drvPath)
1033 , wantedOutputs(wantedOutputs)
1034 , buildMode(buildMode)
1035 {
1036 state = &DerivationGoal::getDerivation;
1037 name = (format("building of '%1%'") % drvPath).str();
1038 trace("created");
1039
1040 mcExpectedBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.expectedBuilds);
1041 worker.updateProgress();
1042 }
1043
1044
DerivationGoal(const Path & drvPath,const BasicDerivation & drv,Worker & worker,BuildMode buildMode)1045 DerivationGoal::DerivationGoal(const Path & drvPath, const BasicDerivation & drv,
1046 Worker & worker, BuildMode buildMode)
1047 : Goal(worker)
1048 , useDerivation(false)
1049 , drvPath(drvPath)
1050 , buildMode(buildMode)
1051 {
1052 this->drv = std::unique_ptr<BasicDerivation>(new BasicDerivation(drv));
1053 state = &DerivationGoal::haveDerivation;
1054 name = (format("building of %1%") % showPaths(drv.outputPaths())).str();
1055 trace("created");
1056
1057 mcExpectedBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.expectedBuilds);
1058 worker.updateProgress();
1059
1060 /* Prevent the .chroot directory from being
1061 garbage-collected. (See isActiveTempFile() in gc.cc.) */
1062 worker.store.addTempRoot(drvPath);
1063 }
1064
1065
~DerivationGoal()1066 DerivationGoal::~DerivationGoal()
1067 {
1068 /* Careful: we should never ever throw an exception from a
1069 destructor. */
1070 try { killChild(); } catch (...) { ignoreException(); }
1071 try { deleteTmpDir(false); } catch (...) { ignoreException(); }
1072 try { closeLogFile(); } catch (...) { ignoreException(); }
1073 }
1074
1075
needsHashRewrite()1076 inline bool DerivationGoal::needsHashRewrite()
1077 {
1078 #if __linux__
1079 return !useChroot;
1080 #else
1081 /* Darwin requires hash rewriting even when sandboxing is enabled. */
1082 return true;
1083 #endif
1084 }
1085
1086
killChild()1087 void DerivationGoal::killChild()
1088 {
1089 if (pid != -1) {
1090 worker.childTerminated(this);
1091
1092 if (buildUser) {
1093 /* If we're using a build user, then there is a tricky
1094 race condition: if we kill the build user before the
1095 child has done its setuid() to the build user uid, then
1096 it won't be killed, and we'll potentially lock up in
1097 pid.wait(). So also send a conventional kill to the
1098 child. */
1099 ::kill(-pid, SIGKILL); /* ignore the result */
1100 buildUser->kill();
1101 pid.wait();
1102 } else
1103 pid.kill();
1104
1105 assert(pid == -1);
1106 }
1107
1108 hook.reset();
1109 }
1110
1111
timedOut()1112 void DerivationGoal::timedOut()
1113 {
1114 killChild();
1115 done(BuildResult::TimedOut);
1116 }
1117
1118
work()1119 void DerivationGoal::work()
1120 {
1121 (this->*state)();
1122 }
1123
1124
addWantedOutputs(const StringSet & outputs)1125 void DerivationGoal::addWantedOutputs(const StringSet & outputs)
1126 {
1127 /* If we already want all outputs, there is nothing to do. */
1128 if (wantedOutputs.empty()) return;
1129
1130 if (outputs.empty()) {
1131 wantedOutputs.clear();
1132 needRestart = true;
1133 } else
1134 for (auto & i : outputs)
1135 if (wantedOutputs.find(i) == wantedOutputs.end()) {
1136 wantedOutputs.insert(i);
1137 needRestart = true;
1138 }
1139 }
1140
1141
getDerivation()1142 void DerivationGoal::getDerivation()
1143 {
1144 trace("init");
1145
1146 /* The first thing to do is to make sure that the derivation
1147 exists. If it doesn't, it may be created through a
1148 substitute. */
1149 if (buildMode == bmNormal && worker.store.isValidPath(drvPath)) {
1150 loadDerivation();
1151 return;
1152 }
1153
1154 addWaitee(worker.makeSubstitutionGoal(drvPath));
1155
1156 state = &DerivationGoal::loadDerivation;
1157 }
1158
1159
loadDerivation()1160 void DerivationGoal::loadDerivation()
1161 {
1162 trace("loading derivation");
1163
1164 if (nrFailed != 0) {
1165 printError(format("cannot build missing derivation '%1%'") % drvPath);
1166 done(BuildResult::MiscFailure);
1167 return;
1168 }
1169
1170 /* `drvPath' should already be a root, but let's be on the safe
1171 side: if the user forgot to make it a root, we wouldn't want
1172 things being garbage collected while we're busy. */
1173 worker.store.addTempRoot(drvPath);
1174
1175 assert(worker.store.isValidPath(drvPath));
1176
1177 /* Get the derivation. */
1178 drv = std::unique_ptr<BasicDerivation>(new Derivation(worker.store.derivationFromPath(drvPath)));
1179
1180 haveDerivation();
1181 }
1182
1183
haveDerivation()1184 void DerivationGoal::haveDerivation()
1185 {
1186 trace("have derivation");
1187
1188 retrySubstitution = false;
1189
1190 for (auto & i : drv->outputs)
1191 worker.store.addTempRoot(i.second.path);
1192
1193 /* Check what outputs paths are not already valid. */
1194 PathSet invalidOutputs = checkPathValidity(false, buildMode == bmRepair);
1195
1196 /* If they are all valid, then we're done. */
1197 if (invalidOutputs.size() == 0 && buildMode == bmNormal) {
1198 done(BuildResult::AlreadyValid);
1199 return;
1200 }
1201
1202 parsedDrv = std::make_unique<ParsedDerivation>(drvPath, *drv);
1203
1204 /* We are first going to try to create the invalid output paths
1205 through substitutes. If that doesn't work, we'll build
1206 them. */
1207 if (settings.useSubstitutes && parsedDrv->substitutesAllowed())
1208 for (auto & i : invalidOutputs)
1209 addWaitee(worker.makeSubstitutionGoal(i, buildMode == bmRepair ? Repair : NoRepair));
1210
1211 if (waitees.empty()) /* to prevent hang (no wake-up event) */
1212 outputsSubstituted();
1213 else
1214 state = &DerivationGoal::outputsSubstituted;
1215 }
1216
1217
outputsSubstituted()1218 void DerivationGoal::outputsSubstituted()
1219 {
1220 trace("all outputs substituted (maybe)");
1221
1222 if (nrFailed > 0 && nrFailed > nrNoSubstituters + nrIncompleteClosure && !settings.tryFallback) {
1223 done(BuildResult::TransientFailure, (format("some substitutes for the outputs of derivation '%1%' failed (usually happens due to networking issues); try '--fallback' to build derivation from source ") % drvPath).str());
1224 return;
1225 }
1226
1227 /* If the substitutes form an incomplete closure, then we should
1228 build the dependencies of this derivation, but after that, we
1229 can still use the substitutes for this derivation itself. */
1230 if (nrIncompleteClosure > 0) retrySubstitution = true;
1231
1232 nrFailed = nrNoSubstituters = nrIncompleteClosure = 0;
1233
1234 if (needRestart) {
1235 needRestart = false;
1236 haveDerivation();
1237 return;
1238 }
1239
1240 auto nrInvalid = checkPathValidity(false, buildMode == bmRepair).size();
1241 if (buildMode == bmNormal && nrInvalid == 0) {
1242 done(BuildResult::Substituted);
1243 return;
1244 }
1245 if (buildMode == bmRepair && nrInvalid == 0) {
1246 repairClosure();
1247 return;
1248 }
1249 if (buildMode == bmCheck && nrInvalid > 0)
1250 throw Error(format("some outputs of '%1%' are not valid, so checking is not possible") % drvPath);
1251
1252 /* Otherwise, at least one of the output paths could not be
1253 produced using a substitute. So we have to build instead. */
1254
1255 /* Make sure checkPathValidity() from now on checks all
1256 outputs. */
1257 wantedOutputs = PathSet();
1258
1259 /* The inputs must be built before we can build this goal. */
1260 if (useDerivation)
1261 for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs)
1262 addWaitee(worker.makeDerivationGoal(i.first, i.second, buildMode == bmRepair ? bmRepair : bmNormal));
1263
1264 for (auto & i : drv->inputSrcs) {
1265 if (worker.store.isValidPath(i)) continue;
1266 if (!settings.useSubstitutes)
1267 throw Error(format("dependency '%1%' of '%2%' does not exist, and substitution is disabled")
1268 % i % drvPath);
1269 addWaitee(worker.makeSubstitutionGoal(i));
1270 }
1271
1272 if (waitees.empty()) /* to prevent hang (no wake-up event) */
1273 inputsRealised();
1274 else
1275 state = &DerivationGoal::inputsRealised;
1276 }
1277
1278
repairClosure()1279 void DerivationGoal::repairClosure()
1280 {
1281 /* If we're repairing, we now know that our own outputs are valid.
1282 Now check whether the other paths in the outputs closure are
1283 good. If not, then start derivation goals for the derivations
1284 that produced those outputs. */
1285
1286 /* Get the output closure. */
1287 PathSet outputClosure;
1288 for (auto & i : drv->outputs) {
1289 if (!wantOutput(i.first, wantedOutputs)) continue;
1290 worker.store.computeFSClosure(i.second.path, outputClosure);
1291 }
1292
1293 /* Filter out our own outputs (which we have already checked). */
1294 for (auto & i : drv->outputs)
1295 outputClosure.erase(i.second.path);
1296
1297 /* Get all dependencies of this derivation so that we know which
1298 derivation is responsible for which path in the output
1299 closure. */
1300 PathSet inputClosure;
1301 if (useDerivation) worker.store.computeFSClosure(drvPath, inputClosure);
1302 std::map<Path, Path> outputsToDrv;
1303 for (auto & i : inputClosure)
1304 if (isDerivation(i)) {
1305 Derivation drv = worker.store.derivationFromPath(i);
1306 for (auto & j : drv.outputs)
1307 outputsToDrv[j.second.path] = i;
1308 }
1309
1310 /* Check each path (slow!). */
1311 PathSet broken;
1312 for (auto & i : outputClosure) {
1313 if (worker.pathContentsGood(i)) continue;
1314 printError(format("found corrupted or missing path '%1%' in the output closure of '%2%'") % i % drvPath);
1315 Path drvPath2 = outputsToDrv[i];
1316 if (drvPath2 == "")
1317 addWaitee(worker.makeSubstitutionGoal(i, Repair));
1318 else
1319 addWaitee(worker.makeDerivationGoal(drvPath2, PathSet(), bmRepair));
1320 }
1321
1322 if (waitees.empty()) {
1323 done(BuildResult::AlreadyValid);
1324 return;
1325 }
1326
1327 state = &DerivationGoal::closureRepaired;
1328 }
1329
1330
closureRepaired()1331 void DerivationGoal::closureRepaired()
1332 {
1333 trace("closure repaired");
1334 if (nrFailed > 0)
1335 throw Error(format("some paths in the output closure of derivation '%1%' could not be repaired") % drvPath);
1336 done(BuildResult::AlreadyValid);
1337 }
1338
1339
inputsRealised()1340 void DerivationGoal::inputsRealised()
1341 {
1342 trace("all inputs realised");
1343
1344 if (nrFailed != 0) {
1345 if (!useDerivation)
1346 throw Error(format("some dependencies of '%1%' are missing") % drvPath);
1347 printError(
1348 format("cannot build derivation '%1%': %2% dependencies couldn't be built")
1349 % drvPath % nrFailed);
1350 done(BuildResult::DependencyFailed);
1351 return;
1352 }
1353
1354 if (retrySubstitution) {
1355 haveDerivation();
1356 return;
1357 }
1358
1359 /* Gather information necessary for computing the closure and/or
1360 running the build hook. */
1361
1362 /* The outputs are referenceable paths. */
1363 for (auto & i : drv->outputs) {
1364 debug(format("building path '%1%'") % i.second.path);
1365 allPaths.insert(i.second.path);
1366 }
1367
1368 /* Determine the full set of input paths. */
1369
1370 /* First, the input derivations. */
1371 if (useDerivation)
1372 for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs) {
1373 /* Add the relevant output closures of the input derivation
1374 `i' as input paths. Only add the closures of output paths
1375 that are specified as inputs. */
1376 assert(worker.store.isValidPath(i.first));
1377 Derivation inDrv = worker.store.derivationFromPath(i.first);
1378 for (auto & j : i.second)
1379 if (inDrv.outputs.find(j) != inDrv.outputs.end())
1380 worker.store.computeFSClosure(inDrv.outputs[j].path, inputPaths);
1381 else
1382 throw Error(
1383 format("derivation '%1%' requires non-existent output '%2%' from input derivation '%3%'")
1384 % drvPath % j % i.first);
1385 }
1386
1387 /* Second, the input sources. */
1388 worker.store.computeFSClosure(drv->inputSrcs, inputPaths);
1389
1390 debug(format("added input paths %1%") % showPaths(inputPaths));
1391
1392 allPaths.insert(inputPaths.begin(), inputPaths.end());
1393
1394 /* Is this a fixed-output derivation? */
1395 fixedOutput = drv->isFixedOutput();
1396
1397 /* Don't repeat fixed-output derivations since they're already
1398 verified by their output hash.*/
1399 nrRounds = fixedOutput ? 1 : settings.buildRepeat + 1;
1400
1401 /* Okay, try to build. Note that here we don't wait for a build
1402 slot to become available, since we don't need one if there is a
1403 build hook. */
1404 state = &DerivationGoal::tryToBuild;
1405 worker.wakeUp(shared_from_this());
1406
1407 result = BuildResult();
1408 }
1409
1410
tryToBuild()1411 void DerivationGoal::tryToBuild()
1412 {
1413 trace("trying to build");
1414
1415 /* Obtain locks on all output paths. The locks are automatically
1416 released when we exit this function or Nix crashes. If we
1417 can't acquire the lock, then continue; hopefully some other
1418 goal can start a build, and if not, the main loop will sleep a
1419 few seconds and then retry this goal. */
1420 PathSet lockFiles;
1421 for (auto & outPath : drv->outputPaths())
1422 lockFiles.insert(worker.store.toRealPath(outPath));
1423
1424 if (!outputLocks.lockPaths(lockFiles, "", false)) {
1425 worker.waitForAWhile(shared_from_this());
1426 return;
1427 }
1428
1429 /* Now check again whether the outputs are valid. This is because
1430 another process may have started building in parallel. After
1431 it has finished and released the locks, we can (and should)
1432 reuse its results. (Strictly speaking the first check can be
1433 omitted, but that would be less efficient.) Note that since we
1434 now hold the locks on the output paths, no other process can
1435 build this derivation, so no further checks are necessary. */
1436 validPaths = checkPathValidity(true, buildMode == bmRepair);
1437 if (buildMode != bmCheck && validPaths.size() == drv->outputs.size()) {
1438 debug(format("skipping build of derivation '%1%', someone beat us to it") % drvPath);
1439 outputLocks.setDeletion(true);
1440 done(BuildResult::AlreadyValid);
1441 return;
1442 }
1443
1444 missingPaths = drv->outputPaths();
1445 if (buildMode != bmCheck)
1446 for (auto & i : validPaths) missingPaths.erase(i);
1447
1448 /* If any of the outputs already exist but are not valid, delete
1449 them. */
1450 for (auto & i : drv->outputs) {
1451 Path path = i.second.path;
1452 if (worker.store.isValidPath(path)) continue;
1453 debug(format("removing invalid path '%1%'") % path);
1454 deletePath(worker.store.toRealPath(path));
1455 }
1456
1457 /* Don't do a remote build if the derivation has the attribute
1458 `preferLocalBuild' set. Also, check and repair modes are only
1459 supported for local builds. */
1460 bool buildLocally = buildMode != bmNormal || parsedDrv->willBuildLocally();
1461
1462 auto started = [&]() {
1463 auto msg = fmt(
1464 buildMode == bmRepair ? "repairing outputs of '%s'" :
1465 buildMode == bmCheck ? "checking outputs of '%s'" :
1466 nrRounds > 1 ? "building '%s' (round %d/%d)" :
1467 "building '%s'", drvPath, curRound, nrRounds);
1468 fmt("building '%s'", drvPath);
1469 if (hook) msg += fmt(" on '%s'", machineName);
1470 act = std::make_unique<Activity>(*logger, lvlInfo, actBuild, msg,
1471 Logger::Fields{drvPath, hook ? machineName : "", curRound, nrRounds});
1472 mcRunningBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.runningBuilds);
1473 worker.updateProgress();
1474 };
1475
1476 /* Is the build hook willing to accept this job? */
1477 if (!buildLocally) {
1478 switch (tryBuildHook()) {
1479 case rpAccept:
1480 /* Yes, it has started doing so. Wait until we get
1481 EOF from the hook. */
1482 result.startTime = time(0); // inexact
1483 state = &DerivationGoal::buildDone;
1484 started();
1485 return;
1486 case rpPostpone:
1487 /* Not now; wait until at least one child finishes or
1488 the wake-up timeout expires. */
1489 worker.waitForAWhile(shared_from_this());
1490 outputLocks.unlock();
1491 return;
1492 case rpDecline:
1493 /* We should do it ourselves. */
1494 break;
1495 }
1496 }
1497
1498 /* Make sure that we are allowed to start a build. If this
1499 derivation prefers to be done locally, do it even if
1500 maxBuildJobs is 0. */
1501 unsigned int curBuilds = worker.getNrLocalBuilds();
1502 if (curBuilds >= settings.maxBuildJobs && !(buildLocally && curBuilds == 0)) {
1503 worker.waitForBuildSlot(shared_from_this());
1504 outputLocks.unlock();
1505 return;
1506 }
1507
1508 try {
1509
1510 /* Okay, we have to build. */
1511 startBuilder();
1512
1513 } catch (BuildError & e) {
1514 printError(e.msg());
1515 outputLocks.unlock();
1516 buildUser.reset();
1517 worker.permanentFailure = true;
1518 done(BuildResult::InputRejected, e.msg());
1519 return;
1520 }
1521
1522 /* This state will be reached when we get EOF on the child's
1523 log pipe. */
1524 state = &DerivationGoal::buildDone;
1525
1526 started();
1527 }
1528
1529
replaceValidPath(const Path & storePath,const Path tmpPath)1530 void replaceValidPath(const Path & storePath, const Path tmpPath)
1531 {
1532 /* We can't atomically replace storePath (the original) with
1533 tmpPath (the replacement), so we have to move it out of the
1534 way first. We'd better not be interrupted here, because if
1535 we're repairing (say) Glibc, we end up with a broken system. */
1536 Path oldPath = (format("%1%.old-%2%-%3%") % storePath % getpid() % random()).str();
1537 if (pathExists(storePath))
1538 rename(storePath.c_str(), oldPath.c_str());
1539 if (rename(tmpPath.c_str(), storePath.c_str()) == -1)
1540 throw SysError(format("moving '%1%' to '%2%'") % tmpPath % storePath);
1541 deletePath(oldPath);
1542 }
1543
1544
MakeError(NotDeterministic,BuildError)1545 MakeError(NotDeterministic, BuildError)
1546
1547
1548 void DerivationGoal::buildDone()
1549 {
1550 trace("build done");
1551
1552 /* Release the build user at the end of this function. We don't do
1553 it right away because we don't want another build grabbing this
1554 uid and then messing around with our output. */
1555 Finally releaseBuildUser([&]() { buildUser.reset(); });
1556
1557 /* Since we got an EOF on the logger pipe, the builder is presumed
1558 to have terminated. In fact, the builder could also have
1559 simply have closed its end of the pipe, so just to be sure,
1560 kill it. */
1561 int status = hook ? hook->pid.kill() : pid.kill();
1562
1563 debug(format("builder process for '%1%' finished") % drvPath);
1564
1565 result.timesBuilt++;
1566 result.stopTime = time(0);
1567
1568 /* So the child is gone now. */
1569 worker.childTerminated(this);
1570
1571 /* Close the read side of the logger pipe. */
1572 if (hook) {
1573 hook->builderOut.readSide = -1;
1574 hook->fromHook.readSide = -1;
1575 } else
1576 builderOut.readSide = -1;
1577
1578 /* Close the log file. */
1579 closeLogFile();
1580
1581 /* When running under a build user, make sure that all processes
1582 running under that uid are gone. This is to prevent a
1583 malicious user from leaving behind a process that keeps files
1584 open and modifies them after they have been chown'ed to
1585 root. */
1586 if (buildUser) buildUser->kill();
1587
1588 bool diskFull = false;
1589
1590 try {
1591
1592 /* Check the exit status. */
1593 if (!statusOk(status)) {
1594
1595 /* Heuristically check whether the build failure may have
1596 been caused by a disk full condition. We have no way
1597 of knowing whether the build actually got an ENOSPC.
1598 So instead, check if the disk is (nearly) full now. If
1599 so, we don't mark this build as a permanent failure. */
1600 #if HAVE_STATVFS
1601 unsigned long long required = 8ULL * 1024 * 1024; // FIXME: make configurable
1602 struct statvfs st;
1603 if (statvfs(worker.store.realStoreDir.c_str(), &st) == 0 &&
1604 (unsigned long long) st.f_bavail * st.f_bsize < required)
1605 diskFull = true;
1606 if (statvfs(tmpDir.c_str(), &st) == 0 &&
1607 (unsigned long long) st.f_bavail * st.f_bsize < required)
1608 diskFull = true;
1609 #endif
1610
1611 deleteTmpDir(false);
1612
1613 /* Move paths out of the chroot for easier debugging of
1614 build failures. */
1615 if (useChroot && buildMode == bmNormal)
1616 for (auto & i : missingPaths)
1617 if (pathExists(chrootRootDir + i))
1618 rename((chrootRootDir + i).c_str(), i.c_str());
1619
1620 std::string msg = (format("builder for '%1%' %2%")
1621 % drvPath % statusToString(status)).str();
1622
1623 if (!settings.verboseBuild && !logTail.empty()) {
1624 msg += (format("; last %d log lines:") % logTail.size()).str();
1625 for (auto & line : logTail)
1626 msg += "\n " + line;
1627 }
1628
1629 if (diskFull)
1630 msg += "\nnote: build failure may have been caused by lack of free disk space";
1631
1632 throw BuildError(msg);
1633 }
1634
1635 /* Compute the FS closure of the outputs and register them as
1636 being valid. */
1637 registerOutputs();
1638
1639 if (settings.postBuildHook != "") {
1640 Activity act(*logger, lvlInfo, actPostBuildHook,
1641 fmt("running post-build-hook '%s'", settings.postBuildHook),
1642 Logger::Fields{drvPath});
1643 PushActivity pact(act.id);
1644 auto outputPaths = drv->outputPaths();
1645 std::map<std::string, std::string> hookEnvironment = getEnv();
1646
1647 hookEnvironment.emplace("DRV_PATH", drvPath);
1648 hookEnvironment.emplace("OUT_PATHS", chomp(concatStringsSep(" ", outputPaths)));
1649
1650 RunOptions opts(settings.postBuildHook, {});
1651 opts.environment = hookEnvironment;
1652
1653 struct LogSink : Sink {
1654 Activity & act;
1655 std::string currentLine;
1656
1657 LogSink(Activity & act) : act(act) { }
1658
1659 void operator() (const unsigned char * data, size_t len) override {
1660 for (size_t i = 0; i < len; i++) {
1661 auto c = data[i];
1662
1663 if (c == '\n') {
1664 flushLine();
1665 } else {
1666 currentLine += c;
1667 }
1668 }
1669 }
1670
1671 void flushLine() {
1672 if (settings.verboseBuild) {
1673 printError("post-build-hook: " + currentLine);
1674 } else {
1675 act.result(resPostBuildLogLine, currentLine);
1676 }
1677 currentLine.clear();
1678 }
1679
1680 ~LogSink() {
1681 if (currentLine != "") {
1682 currentLine += '\n';
1683 flushLine();
1684 }
1685 }
1686 };
1687 LogSink sink(act);
1688
1689 opts.standardOut = &sink;
1690 opts.mergeStderrToStdout = true;
1691 runProgram2(opts);
1692 }
1693
1694 if (buildMode == bmCheck) {
1695 done(BuildResult::Built);
1696 return;
1697 }
1698
1699 /* Delete unused redirected outputs (when doing hash rewriting). */
1700 for (auto & i : redirectedOutputs)
1701 deletePath(i.second);
1702
1703 /* Delete the chroot (if we were using one). */
1704 autoDelChroot.reset(); /* this runs the destructor */
1705
1706 deleteTmpDir(true);
1707
1708 /* Repeat the build if necessary. */
1709 if (curRound++ < nrRounds) {
1710 outputLocks.unlock();
1711 state = &DerivationGoal::tryToBuild;
1712 worker.wakeUp(shared_from_this());
1713 return;
1714 }
1715
1716 /* It is now safe to delete the lock files, since all future
1717 lockers will see that the output paths are valid; they will
1718 not create new lock files with the same names as the old
1719 (unlinked) lock files. */
1720 outputLocks.setDeletion(true);
1721 outputLocks.unlock();
1722
1723 } catch (BuildError & e) {
1724 printError(e.msg());
1725
1726 outputLocks.unlock();
1727
1728 BuildResult::Status st = BuildResult::MiscFailure;
1729
1730 if (hook && WIFEXITED(status) && WEXITSTATUS(status) == 101)
1731 st = BuildResult::TimedOut;
1732
1733 else if (hook && (!WIFEXITED(status) || WEXITSTATUS(status) != 100)) {
1734 }
1735
1736 else {
1737 st =
1738 dynamic_cast<NotDeterministic*>(&e) ? BuildResult::NotDeterministic :
1739 statusOk(status) ? BuildResult::OutputRejected :
1740 fixedOutput || diskFull ? BuildResult::TransientFailure :
1741 BuildResult::PermanentFailure;
1742 }
1743
1744 done(st, e.msg());
1745 return;
1746 }
1747
1748 done(BuildResult::Built);
1749 }
1750
1751
tryBuildHook()1752 HookReply DerivationGoal::tryBuildHook()
1753 {
1754 if (!worker.tryBuildHook || !useDerivation) return rpDecline;
1755
1756 if (!worker.hook)
1757 worker.hook = std::make_unique<HookInstance>();
1758
1759 try {
1760
1761 /* Send the request to the hook. */
1762 worker.hook->sink
1763 << "try"
1764 << (worker.getNrLocalBuilds() < settings.maxBuildJobs ? 1 : 0)
1765 << drv->platform
1766 << drvPath
1767 << parsedDrv->getRequiredSystemFeatures();
1768 worker.hook->sink.flush();
1769
1770 /* Read the first line of input, which should be a word indicating
1771 whether the hook wishes to perform the build. */
1772 string reply;
1773 while (true) {
1774 string s = readLine(worker.hook->fromHook.readSide.get());
1775 if (handleJSONLogMessage(s, worker.act, worker.hook->activities, true))
1776 ;
1777 else if (string(s, 0, 2) == "# ") {
1778 reply = string(s, 2);
1779 break;
1780 }
1781 else {
1782 s += "\n";
1783 writeToStderr(s);
1784 }
1785 }
1786
1787 debug(format("hook reply is '%1%'") % reply);
1788
1789 if (reply == "decline")
1790 return rpDecline;
1791 else if (reply == "decline-permanently") {
1792 worker.tryBuildHook = false;
1793 worker.hook = 0;
1794 return rpDecline;
1795 }
1796 else if (reply == "postpone")
1797 return rpPostpone;
1798 else if (reply != "accept")
1799 throw Error(format("bad hook reply '%1%'") % reply);
1800
1801 } catch (SysError & e) {
1802 if (e.errNo == EPIPE) {
1803 printError("build hook died unexpectedly: %s",
1804 chomp(drainFD(worker.hook->fromHook.readSide.get())));
1805 worker.hook = 0;
1806 return rpDecline;
1807 } else
1808 throw;
1809 }
1810
1811 hook = std::move(worker.hook);
1812
1813 machineName = readLine(hook->fromHook.readSide.get());
1814
1815 /* Tell the hook all the inputs that have to be copied to the
1816 remote system. */
1817 hook->sink << inputPaths;
1818
1819 /* Tell the hooks the missing outputs that have to be copied back
1820 from the remote system. */
1821 hook->sink << missingPaths;
1822
1823 hook->sink = FdSink();
1824 hook->toHook.writeSide = -1;
1825
1826 /* Create the log file and pipe. */
1827 Path logFile = openLogFile();
1828
1829 set<int> fds;
1830 fds.insert(hook->fromHook.readSide.get());
1831 fds.insert(hook->builderOut.readSide.get());
1832 worker.childStarted(shared_from_this(), fds, false, false);
1833
1834 return rpAccept;
1835 }
1836
1837
chmod_(const Path & path,mode_t mode)1838 void chmod_(const Path & path, mode_t mode)
1839 {
1840 if (chmod(path.c_str(), mode) == -1)
1841 throw SysError(format("setting permissions on '%1%'") % path);
1842 }
1843
1844
childEntry(void * arg)1845 int childEntry(void * arg)
1846 {
1847 ((DerivationGoal *) arg)->runChild();
1848 return 1;
1849 }
1850
1851
exportReferences(PathSet storePaths)1852 PathSet DerivationGoal::exportReferences(PathSet storePaths)
1853 {
1854 PathSet paths;
1855
1856 for (auto storePath : storePaths) {
1857
1858 /* Check that the store path is valid. */
1859 if (!worker.store.isInStore(storePath))
1860 throw BuildError(format("'exportReferencesGraph' contains a non-store path '%1%'")
1861 % storePath);
1862
1863 storePath = worker.store.toStorePath(storePath);
1864
1865 if (!inputPaths.count(storePath))
1866 throw BuildError("cannot export references of path '%s' because it is not in the input closure of the derivation", storePath);
1867
1868 worker.store.computeFSClosure(storePath, paths);
1869 }
1870
1871 /* If there are derivations in the graph, then include their
1872 outputs as well. This is useful if you want to do things
1873 like passing all build-time dependencies of some path to a
1874 derivation that builds a NixOS DVD image. */
1875 PathSet paths2(paths);
1876
1877 for (auto & j : paths2) {
1878 if (isDerivation(j)) {
1879 Derivation drv = worker.store.derivationFromPath(j);
1880 for (auto & k : drv.outputs)
1881 worker.store.computeFSClosure(k.second.path, paths);
1882 }
1883 }
1884
1885 return paths;
1886 }
1887
1888 static std::once_flag dns_resolve_flag;
1889
preloadNSS()1890 static void preloadNSS() {
1891 /* builtin:fetchurl can trigger a DNS lookup, which with glibc can trigger a dynamic library load of
1892 one of the glibc NSS libraries in a sandboxed child, which will fail unless the library's already
1893 been loaded in the parent. So we force a lookup of an invalid domain to force the NSS machinery to
1894 load its lookup libraries in the parent before any child gets a chance to. */
1895 std::call_once(dns_resolve_flag, []() {
1896 struct addrinfo *res = NULL;
1897
1898 if (getaddrinfo("this.pre-initializes.the.dns.resolvers.invalid.", "http", NULL, &res) != 0) {
1899 if (res) freeaddrinfo(res);
1900 }
1901 });
1902 }
1903
startBuilder()1904 void DerivationGoal::startBuilder()
1905 {
1906 /* Right platform? */
1907 if (!parsedDrv->canBuildLocally())
1908 throw Error("a '%s' with features {%s} is required to build '%s', but I am a '%s' with features {%s}",
1909 drv->platform,
1910 concatStringsSep(", ", parsedDrv->getRequiredSystemFeatures()),
1911 drvPath,
1912 settings.thisSystem,
1913 concatStringsSep(", ", settings.systemFeatures));
1914
1915 if (drv->isBuiltin())
1916 preloadNSS();
1917
1918 #if __APPLE__
1919 additionalSandboxProfile = parsedDrv->getStringAttr("__sandboxProfile").value_or("");
1920 #endif
1921
1922 /* Are we doing a chroot build? */
1923 {
1924 auto noChroot = parsedDrv->getBoolAttr("__noChroot");
1925 if (settings.sandboxMode == smEnabled) {
1926 if (noChroot)
1927 throw Error(format("derivation '%1%' has '__noChroot' set, "
1928 "but that's not allowed when 'sandbox' is 'true'") % drvPath);
1929 #if __APPLE__
1930 if (additionalSandboxProfile != "")
1931 throw Error(format("derivation '%1%' specifies a sandbox profile, "
1932 "but this is only allowed when 'sandbox' is 'relaxed'") % drvPath);
1933 #endif
1934 useChroot = true;
1935 }
1936 else if (settings.sandboxMode == smDisabled)
1937 useChroot = false;
1938 else if (settings.sandboxMode == smRelaxed)
1939 useChroot = !fixedOutput && !noChroot;
1940 }
1941
1942 if (worker.store.storeDir != worker.store.realStoreDir) {
1943 #if __linux__
1944 useChroot = true;
1945 #else
1946 throw Error("building using a diverted store is not supported on this platform");
1947 #endif
1948 }
1949
1950 /* If `build-users-group' is not empty, then we have to build as
1951 one of the members of that group. */
1952 if (settings.buildUsersGroup != "" && getuid() == 0) {
1953 #if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
1954 buildUser = std::make_unique<UserLock>();
1955
1956 /* Make sure that no other processes are executing under this
1957 uid. */
1958 buildUser->kill();
1959 #else
1960 /* Don't know how to block the creation of setuid/setgid
1961 binaries on this platform. */
1962 throw Error("build users are not supported on this platform for security reasons");
1963 #endif
1964 }
1965
1966 /* Create a temporary directory where the build will take
1967 place. */
1968 auto drvName = storePathToName(drvPath);
1969 tmpDir = createTempDir("", "nix-build-" + drvName, false, false, 0700);
1970
1971 chownToBuilder(tmpDir);
1972
1973 /* Substitute output placeholders with the actual output paths. */
1974 for (auto & output : drv->outputs)
1975 inputRewrites[hashPlaceholder(output.first)] = output.second.path;
1976
1977 /* Construct the environment passed to the builder. */
1978 initEnv();
1979
1980 writeStructuredAttrs();
1981
1982 /* Handle exportReferencesGraph(), if set. */
1983 if (!parsedDrv->getStructuredAttrs()) {
1984 /* The `exportReferencesGraph' feature allows the references graph
1985 to be passed to a builder. This attribute should be a list of
1986 pairs [name1 path1 name2 path2 ...]. The references graph of
1987 each `pathN' will be stored in a text file `nameN' in the
1988 temporary build directory. The text files have the format used
1989 by `nix-store --register-validity'. However, the deriver
1990 fields are left empty. */
1991 string s = get(drv->env, "exportReferencesGraph");
1992 Strings ss = tokenizeString<Strings>(s);
1993 if (ss.size() % 2 != 0)
1994 throw BuildError(format("odd number of tokens in 'exportReferencesGraph': '%1%'") % s);
1995 for (Strings::iterator i = ss.begin(); i != ss.end(); ) {
1996 string fileName = *i++;
1997 checkStoreName(fileName); /* !!! abuse of this function */
1998 Path storePath = *i++;
1999
2000 /* Write closure info to <fileName>. */
2001 writeFile(tmpDir + "/" + fileName,
2002 worker.store.makeValidityRegistration(
2003 exportReferences({storePath}), false, false));
2004 }
2005 }
2006
2007 if (useChroot) {
2008
2009 /* Allow a user-configurable set of directories from the
2010 host file system. */
2011 PathSet dirs = settings.sandboxPaths;
2012 PathSet dirs2 = settings.extraSandboxPaths;
2013 dirs.insert(dirs2.begin(), dirs2.end());
2014
2015 dirsInChroot.clear();
2016
2017 for (auto i : dirs) {
2018 if (i.empty()) continue;
2019 bool optional = false;
2020 if (i[i.size() - 1] == '?') {
2021 optional = true;
2022 i.pop_back();
2023 }
2024 size_t p = i.find('=');
2025 if (p == string::npos)
2026 dirsInChroot[i] = {i, optional};
2027 else
2028 dirsInChroot[string(i, 0, p)] = {string(i, p + 1), optional};
2029 }
2030 dirsInChroot[tmpDirInSandbox] = tmpDir;
2031
2032 /* Add the closure of store paths to the chroot. */
2033 PathSet closure;
2034 for (auto & i : dirsInChroot)
2035 try {
2036 if (worker.store.isInStore(i.second.source))
2037 worker.store.computeFSClosure(worker.store.toStorePath(i.second.source), closure);
2038 } catch (InvalidPath & e) {
2039 } catch (Error & e) {
2040 throw Error(format("while processing 'sandbox-paths': %s") % e.what());
2041 }
2042 for (auto & i : closure)
2043 dirsInChroot[i] = i;
2044
2045 PathSet allowedPaths = settings.allowedImpureHostPrefixes;
2046
2047 /* This works like the above, except on a per-derivation level */
2048 auto impurePaths = parsedDrv->getStringsAttr("__impureHostDeps").value_or(Strings());
2049
2050 for (auto & i : impurePaths) {
2051 bool found = false;
2052 /* Note: we're not resolving symlinks here to prevent
2053 giving a non-root user info about inaccessible
2054 files. */
2055 Path canonI = canonPath(i);
2056 /* If only we had a trie to do this more efficiently :) luckily, these are generally going to be pretty small */
2057 for (auto & a : allowedPaths) {
2058 Path canonA = canonPath(a);
2059 if (canonI == canonA || isInDir(canonI, canonA)) {
2060 found = true;
2061 break;
2062 }
2063 }
2064 if (!found)
2065 throw Error(format("derivation '%1%' requested impure path '%2%', but it was not in allowed-impure-host-deps") % drvPath % i);
2066
2067 dirsInChroot[i] = i;
2068 }
2069
2070 #if __linux__
2071 /* Create a temporary directory in which we set up the chroot
2072 environment using bind-mounts. We put it in the Nix store
2073 to ensure that we can create hard-links to non-directory
2074 inputs in the fake Nix store in the chroot (see below). */
2075 chrootRootDir = worker.store.toRealPath(drvPath) + ".chroot";
2076 deletePath(chrootRootDir);
2077
2078 /* Clean up the chroot directory automatically. */
2079 autoDelChroot = std::make_shared<AutoDelete>(chrootRootDir);
2080
2081 printMsg(lvlChatty, format("setting up chroot environment in '%1%'") % chrootRootDir);
2082
2083 if (mkdir(chrootRootDir.c_str(), 0750) == -1)
2084 throw SysError(format("cannot create '%1%'") % chrootRootDir);
2085
2086 if (buildUser && chown(chrootRootDir.c_str(), 0, buildUser->getGID()) == -1)
2087 throw SysError(format("cannot change ownership of '%1%'") % chrootRootDir);
2088
2089 /* Create a writable /tmp in the chroot. Many builders need
2090 this. (Of course they should really respect $TMPDIR
2091 instead.) */
2092 Path chrootTmpDir = chrootRootDir + "/tmp";
2093 createDirs(chrootTmpDir);
2094 chmod_(chrootTmpDir, 01777);
2095
2096 /* Create a /etc/passwd with entries for the build user and the
2097 nobody account. The latter is kind of a hack to support
2098 Samba-in-QEMU. */
2099 createDirs(chrootRootDir + "/etc");
2100
2101 writeFile(chrootRootDir + "/etc/passwd", fmt(
2102 "root:x:0:0:Nix build user:%3%:/noshell\n"
2103 "nixbld:x:%1%:%2%:Nix build user:%3%:/noshell\n"
2104 "nobody:x:65534:65534:Nobody:/:/noshell\n",
2105 sandboxUid, sandboxGid, settings.sandboxBuildDir));
2106
2107 /* Declare the build user's group so that programs get a consistent
2108 view of the system (e.g., "id -gn"). */
2109 writeFile(chrootRootDir + "/etc/group",
2110 (format(
2111 "root:x:0:\n"
2112 "nixbld:!:%1%:\n"
2113 "nogroup:x:65534:\n") % sandboxGid).str());
2114
2115 /* Create /etc/hosts with localhost entry. */
2116 if (!fixedOutput)
2117 writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n::1 localhost\n");
2118
2119 /* Make the closure of the inputs available in the chroot,
2120 rather than the whole Nix store. This prevents any access
2121 to undeclared dependencies. Directories are bind-mounted,
2122 while other inputs are hard-linked (since only directories
2123 can be bind-mounted). !!! As an extra security
2124 precaution, make the fake Nix store only writable by the
2125 build user. */
2126 Path chrootStoreDir = chrootRootDir + worker.store.storeDir;
2127 createDirs(chrootStoreDir);
2128 chmod_(chrootStoreDir, 01775);
2129
2130 if (buildUser && chown(chrootStoreDir.c_str(), 0, buildUser->getGID()) == -1)
2131 throw SysError(format("cannot change ownership of '%1%'") % chrootStoreDir);
2132
2133 for (auto & i : inputPaths) {
2134 Path r = worker.store.toRealPath(i);
2135 struct stat st;
2136 if (lstat(r.c_str(), &st))
2137 throw SysError(format("getting attributes of path '%1%'") % i);
2138 if (S_ISDIR(st.st_mode))
2139 dirsInChroot[i] = r;
2140 else {
2141 Path p = chrootRootDir + i;
2142 debug("linking '%1%' to '%2%'", p, r);
2143 if (link(r.c_str(), p.c_str()) == -1) {
2144 /* Hard-linking fails if we exceed the maximum
2145 link count on a file (e.g. 32000 of ext3),
2146 which is quite possible after a `nix-store
2147 --optimise'. */
2148 if (errno != EMLINK)
2149 throw SysError(format("linking '%1%' to '%2%'") % p % i);
2150 StringSink sink;
2151 dumpPath(r, sink);
2152 StringSource source(*sink.s);
2153 restorePath(p, source);
2154 }
2155 }
2156 }
2157
2158 /* If we're repairing, checking or rebuilding part of a
2159 multiple-outputs derivation, it's possible that we're
2160 rebuilding a path that is in settings.dirsInChroot
2161 (typically the dependencies of /bin/sh). Throw them
2162 out. */
2163 for (auto & i : drv->outputs)
2164 dirsInChroot.erase(i.second.path);
2165
2166 #elif __APPLE__
2167 /* We don't really have any parent prep work to do (yet?)
2168 All work happens in the child, instead. */
2169 #else
2170 throw Error("sandboxing builds is not supported on this platform");
2171 #endif
2172 }
2173
2174 if (needsHashRewrite()) {
2175
2176 if (pathExists(homeDir))
2177 throw Error(format("directory '%1%' exists; please remove it") % homeDir);
2178
2179 /* We're not doing a chroot build, but we have some valid
2180 output paths. Since we can't just overwrite or delete
2181 them, we have to do hash rewriting: i.e. in the
2182 environment/arguments passed to the build, we replace the
2183 hashes of the valid outputs with unique dummy strings;
2184 after the build, we discard the redirected outputs
2185 corresponding to the valid outputs, and rewrite the
2186 contents of the new outputs to replace the dummy strings
2187 with the actual hashes. */
2188 if (validPaths.size() > 0)
2189 for (auto & i : validPaths)
2190 addHashRewrite(i);
2191
2192 /* If we're repairing, then we don't want to delete the
2193 corrupt outputs in advance. So rewrite them as well. */
2194 if (buildMode == bmRepair)
2195 for (auto & i : missingPaths)
2196 if (worker.store.isValidPath(i) && pathExists(i)) {
2197 addHashRewrite(i);
2198 redirectedBadOutputs.insert(i);
2199 }
2200 }
2201
2202 if (useChroot && settings.preBuildHook != "" && dynamic_cast<Derivation *>(drv.get())) {
2203 printMsg(lvlChatty, format("executing pre-build hook '%1%'")
2204 % settings.preBuildHook);
2205 auto args = useChroot ? Strings({drvPath, chrootRootDir}) :
2206 Strings({ drvPath });
2207 enum BuildHookState {
2208 stBegin,
2209 stExtraChrootDirs
2210 };
2211 auto state = stBegin;
2212 auto lines = runProgram(settings.preBuildHook, false, args);
2213 auto lastPos = std::string::size_type{0};
2214 for (auto nlPos = lines.find('\n'); nlPos != string::npos;
2215 nlPos = lines.find('\n', lastPos)) {
2216 auto line = std::string{lines, lastPos, nlPos - lastPos};
2217 lastPos = nlPos + 1;
2218 if (state == stBegin) {
2219 if (line == "extra-sandbox-paths" || line == "extra-chroot-dirs") {
2220 state = stExtraChrootDirs;
2221 } else {
2222 throw Error(format("unknown pre-build hook command '%1%'")
2223 % line);
2224 }
2225 } else if (state == stExtraChrootDirs) {
2226 if (line == "") {
2227 state = stBegin;
2228 } else {
2229 auto p = line.find('=');
2230 if (p == string::npos)
2231 dirsInChroot[line] = line;
2232 else
2233 dirsInChroot[string(line, 0, p)] = string(line, p + 1);
2234 }
2235 }
2236 }
2237 }
2238
2239 /* Run the builder. */
2240 printMsg(lvlChatty, format("executing builder '%1%'") % drv->builder);
2241
2242 /* Create the log file. */
2243 Path logFile = openLogFile();
2244
2245 /* Create a pipe to get the output of the builder. */
2246 //builderOut.create();
2247
2248 builderOut.readSide = posix_openpt(O_RDWR | O_NOCTTY);
2249 if (!builderOut.readSide)
2250 throw SysError("opening pseudoterminal master");
2251
2252 std::string slaveName(ptsname(builderOut.readSide.get()));
2253
2254 if (buildUser) {
2255 if (chmod(slaveName.c_str(), 0600))
2256 throw SysError("changing mode of pseudoterminal slave");
2257
2258 if (chown(slaveName.c_str(), buildUser->getUID(), 0))
2259 throw SysError("changing owner of pseudoterminal slave");
2260 } else {
2261 if (grantpt(builderOut.readSide.get()))
2262 throw SysError("granting access to pseudoterminal slave");
2263 }
2264
2265 #if 0
2266 // Mount the pt in the sandbox so that the "tty" command works.
2267 // FIXME: this doesn't work with the new devpts in the sandbox.
2268 if (useChroot)
2269 dirsInChroot[slaveName] = {slaveName, false};
2270 #endif
2271
2272 if (unlockpt(builderOut.readSide.get()))
2273 throw SysError("unlocking pseudoterminal");
2274
2275 builderOut.writeSide = open(slaveName.c_str(), O_RDWR | O_NOCTTY);
2276 if (!builderOut.writeSide)
2277 throw SysError("opening pseudoterminal slave");
2278
2279 // Put the pt into raw mode to prevent \n -> \r\n translation.
2280 struct termios term;
2281 if (tcgetattr(builderOut.writeSide.get(), &term))
2282 throw SysError("getting pseudoterminal attributes");
2283
2284 cfmakeraw(&term);
2285
2286 if (tcsetattr(builderOut.writeSide.get(), TCSANOW, &term))
2287 throw SysError("putting pseudoterminal into raw mode");
2288
2289 result.startTime = time(0);
2290
2291 /* Fork a child to build the package. */
2292 ProcessOptions options;
2293
2294 #if __linux__
2295 if (useChroot) {
2296 /* Set up private namespaces for the build:
2297
2298 - The PID namespace causes the build to start as PID 1.
2299 Processes outside of the chroot are not visible to those
2300 on the inside, but processes inside the chroot are
2301 visible from the outside (though with different PIDs).
2302
2303 - The private mount namespace ensures that all the bind
2304 mounts we do will only show up in this process and its
2305 children, and will disappear automatically when we're
2306 done.
2307
2308 - The private network namespace ensures that the builder
2309 cannot talk to the outside world (or vice versa). It
2310 only has a private loopback interface. (Fixed-output
2311 derivations are not run in a private network namespace
2312 to allow functions like fetchurl to work.)
2313
2314 - The IPC namespace prevents the builder from communicating
2315 with outside processes using SysV IPC mechanisms (shared
2316 memory, message queues, semaphores). It also ensures
2317 that all IPC objects are destroyed when the builder
2318 exits.
2319
2320 - The UTS namespace ensures that builders see a hostname of
2321 localhost rather than the actual hostname.
2322
2323 We use a helper process to do the clone() to work around
2324 clone() being broken in multi-threaded programs due to
2325 at-fork handlers not being run. Note that we use
2326 CLONE_PARENT to ensure that the real builder is parented to
2327 us.
2328 */
2329
2330 if (!fixedOutput)
2331 privateNetwork = true;
2332
2333 userNamespaceSync.create();
2334
2335 options.allowVfork = false;
2336
2337 Pid helper = startProcess([&]() {
2338
2339 /* Drop additional groups here because we can't do it
2340 after we've created the new user namespace. FIXME:
2341 this means that if we're not root in the parent
2342 namespace, we can't drop additional groups; they will
2343 be mapped to nogroup in the child namespace. There does
2344 not seem to be a workaround for this. (But who can tell
2345 from reading user_namespaces(7)?)
2346 See also https://lwn.net/Articles/621612/. */
2347 if (getuid() == 0 && setgroups(0, 0) == -1)
2348 throw SysError("setgroups failed");
2349
2350 size_t stackSize = 1 * 1024 * 1024;
2351 char * stack = (char *) mmap(0, stackSize,
2352 PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
2353 if (stack == MAP_FAILED) throw SysError("allocating stack");
2354
2355 int flags = CLONE_NEWUSER | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD;
2356 if (privateNetwork)
2357 flags |= CLONE_NEWNET;
2358
2359 pid_t child = clone(childEntry, stack + stackSize, flags, this);
2360 if (child == -1 && errno == EINVAL) {
2361 /* Fallback for Linux < 2.13 where CLONE_NEWPID and
2362 CLONE_PARENT are not allowed together. */
2363 flags &= ~CLONE_NEWPID;
2364 child = clone(childEntry, stack + stackSize, flags, this);
2365 }
2366 if (child == -1 && (errno == EPERM || errno == EINVAL)) {
2367 /* Some distros patch Linux to not allow unpriveleged
2368 * user namespaces. If we get EPERM or EINVAL, try
2369 * without CLONE_NEWUSER and see if that works.
2370 */
2371 flags &= ~CLONE_NEWUSER;
2372 child = clone(childEntry, stack + stackSize, flags, this);
2373 }
2374 /* Otherwise exit with EPERM so we can handle this in the
2375 parent. This is only done when sandbox-fallback is set
2376 to true (the default). */
2377 if (child == -1 && (errno == EPERM || errno == EINVAL) && settings.sandboxFallback)
2378 _exit(1);
2379 if (child == -1) throw SysError("cloning builder process");
2380
2381 writeFull(builderOut.writeSide.get(), std::to_string(child) + "\n");
2382 _exit(0);
2383 }, options);
2384
2385 int res = helper.wait();
2386 if (res != 0 && settings.sandboxFallback) {
2387 useChroot = false;
2388 initTmpDir();
2389 goto fallback;
2390 } else if (res != 0)
2391 throw Error("unable to start build process");
2392
2393 userNamespaceSync.readSide = -1;
2394
2395 /* Close the write side to prevent runChild() from hanging
2396 reading from this. */
2397 Finally cleanup([&]() {
2398 userNamespaceSync.writeSide = -1;
2399 });
2400
2401 pid_t tmp;
2402 if (!string2Int<pid_t>(readLine(builderOut.readSide.get()), tmp)) abort();
2403 pid = tmp;
2404
2405 /* Set the UID/GID mapping of the builder's user namespace
2406 such that the sandbox user maps to the build user, or to
2407 the calling user (if build users are disabled). */
2408 uid_t hostUid = buildUser ? buildUser->getUID() : getuid();
2409 uid_t hostGid = buildUser ? buildUser->getGID() : getgid();
2410
2411 writeFile("/proc/" + std::to_string(pid) + "/uid_map",
2412 (format("%d %d 1") % sandboxUid % hostUid).str());
2413
2414 writeFile("/proc/" + std::to_string(pid) + "/setgroups", "deny");
2415
2416 writeFile("/proc/" + std::to_string(pid) + "/gid_map",
2417 (format("%d %d 1") % sandboxGid % hostGid).str());
2418
2419 /* Signal the builder that we've updated its user
2420 namespace. */
2421 writeFull(userNamespaceSync.writeSide.get(), "1");
2422
2423 } else
2424 #endif
2425 {
2426 fallback:
2427 options.allowVfork = !buildUser && !drv->isBuiltin();
2428 pid = startProcess([&]() {
2429 runChild();
2430 }, options);
2431 }
2432
2433 /* parent */
2434 pid.setSeparatePG(true);
2435 builderOut.writeSide = -1;
2436 worker.childStarted(shared_from_this(), {builderOut.readSide.get()}, true, true);
2437
2438 /* Check if setting up the build environment failed. */
2439 while (true) {
2440 string msg = readLine(builderOut.readSide.get());
2441 if (string(msg, 0, 1) == "\1") {
2442 if (msg.size() == 1) break;
2443 throw Error(string(msg, 1));
2444 }
2445 debug(msg);
2446 }
2447 }
2448
2449
initTmpDir()2450 void DerivationGoal::initTmpDir() {
2451 /* In a sandbox, for determinism, always use the same temporary
2452 directory. */
2453 #if __linux__
2454 tmpDirInSandbox = useChroot ? settings.sandboxBuildDir : tmpDir;
2455 #else
2456 tmpDirInSandbox = tmpDir;
2457 #endif
2458
2459 /* In non-structured mode, add all bindings specified in the
2460 derivation via the environment, except those listed in the
2461 passAsFile attribute. Those are passed as file names pointing
2462 to temporary files containing the contents. Note that
2463 passAsFile is ignored in structure mode because it's not
2464 needed (attributes are not passed through the environment, so
2465 there is no size constraint). */
2466 if (!parsedDrv->getStructuredAttrs()) {
2467
2468 StringSet passAsFile = tokenizeString<StringSet>(get(drv->env, "passAsFile"));
2469 int fileNr = 0;
2470 for (auto & i : drv->env) {
2471 if (passAsFile.find(i.first) == passAsFile.end()) {
2472 env[i.first] = i.second;
2473 } else {
2474 string fn = ".attr-" + std::to_string(fileNr++);
2475 Path p = tmpDir + "/" + fn;
2476 writeFile(p, rewriteStrings(i.second, inputRewrites));
2477 chownToBuilder(p);
2478 env[i.first + "Path"] = tmpDirInSandbox + "/" + fn;
2479 }
2480 }
2481
2482 }
2483
2484 /* For convenience, set an environment pointing to the top build
2485 directory. */
2486 env["NIX_BUILD_TOP"] = tmpDirInSandbox;
2487
2488 /* Also set TMPDIR and variants to point to this directory. */
2489 env["TMPDIR"] = env["TEMPDIR"] = env["TMP"] = env["TEMP"] = tmpDirInSandbox;
2490
2491 /* Explicitly set PWD to prevent problems with chroot builds. In
2492 particular, dietlibc cannot figure out the cwd because the
2493 inode of the current directory doesn't appear in .. (because
2494 getdents returns the inode of the mount point). */
2495 env["PWD"] = tmpDirInSandbox;
2496 }
2497
initEnv()2498 void DerivationGoal::initEnv()
2499 {
2500 env.clear();
2501
2502 /* Most shells initialise PATH to some default (/bin:/usr/bin:...) when
2503 PATH is not set. We don't want this, so we fill it in with some dummy
2504 value. */
2505 env["PATH"] = "/path-not-set";
2506
2507 /* Set HOME to a non-existing path to prevent certain programs from using
2508 /etc/passwd (or NIS, or whatever) to locate the home directory (for
2509 example, wget looks for ~/.wgetrc). I.e., these tools use /etc/passwd
2510 if HOME is not set, but they will just assume that the settings file
2511 they are looking for does not exist if HOME is set but points to some
2512 non-existing path. */
2513 env["HOME"] = homeDir;
2514
2515 /* Tell the builder where the Nix store is. Usually they
2516 shouldn't care, but this is useful for purity checking (e.g.,
2517 the compiler or linker might only want to accept paths to files
2518 in the store or in the build directory). */
2519 env["NIX_STORE"] = worker.store.storeDir;
2520
2521 /* The maximum number of cores to utilize for parallel building. */
2522 env["NIX_BUILD_CORES"] = (format("%d") % settings.buildCores).str();
2523
2524 initTmpDir();
2525
2526 /* Compatibility hack with Nix <= 0.7: if this is a fixed-output
2527 derivation, tell the builder, so that for instance `fetchurl'
2528 can skip checking the output. On older Nixes, this environment
2529 variable won't be set, so `fetchurl' will do the check. */
2530 if (fixedOutput) env["NIX_OUTPUT_CHECKED"] = "1";
2531
2532 /* *Only* if this is a fixed-output derivation, propagate the
2533 values of the environment variables specified in the
2534 `impureEnvVars' attribute to the builder. This allows for
2535 instance environment variables for proxy configuration such as
2536 `http_proxy' to be easily passed to downloaders like
2537 `fetchurl'. Passing such environment variables from the caller
2538 to the builder is generally impure, but the output of
2539 fixed-output derivations is by definition pure (since we
2540 already know the cryptographic hash of the output). */
2541 if (fixedOutput) {
2542 for (auto & i : parsedDrv->getStringsAttr("impureEnvVars").value_or(Strings()))
2543 env[i] = getEnv(i);
2544 }
2545
2546 /* Currently structured log messages piggyback on stderr, but we
2547 may change that in the future. So tell the builder which file
2548 descriptor to use for that. */
2549 env["NIX_LOG_FD"] = "2";
2550
2551 /* Trigger colored output in various tools. */
2552 env["TERM"] = "xterm-256color";
2553 }
2554
2555
2556 static std::regex shVarName("[A-Za-z_][A-Za-z0-9_]*");
2557
2558
writeStructuredAttrs()2559 void DerivationGoal::writeStructuredAttrs()
2560 {
2561 auto & structuredAttrs = parsedDrv->getStructuredAttrs();
2562 if (!structuredAttrs) return;
2563
2564 auto json = *structuredAttrs;
2565
2566 /* Add an "outputs" object containing the output paths. */
2567 nlohmann::json outputs;
2568 for (auto & i : drv->outputs)
2569 outputs[i.first] = rewriteStrings(i.second.path, inputRewrites);
2570 json["outputs"] = outputs;
2571
2572 /* Handle exportReferencesGraph. */
2573 auto e = json.find("exportReferencesGraph");
2574 if (e != json.end() && e->is_object()) {
2575 for (auto i = e->begin(); i != e->end(); ++i) {
2576 std::ostringstream str;
2577 {
2578 JSONPlaceholder jsonRoot(str, true);
2579 PathSet storePaths;
2580 for (auto & p : *i)
2581 storePaths.insert(p.get<std::string>());
2582 worker.store.pathInfoToJSON(jsonRoot,
2583 exportReferences(storePaths), false, true);
2584 }
2585 json[i.key()] = nlohmann::json::parse(str.str()); // urgh
2586 }
2587 }
2588
2589 writeFile(tmpDir + "/.attrs.json", rewriteStrings(json.dump(), inputRewrites));
2590 chownToBuilder(tmpDir + "/.attrs.json");
2591
2592 /* As a convenience to bash scripts, write a shell file that
2593 maps all attributes that are representable in bash -
2594 namely, strings, integers, nulls, Booleans, and arrays and
2595 objects consisting entirely of those values. (So nested
2596 arrays or objects are not supported.) */
2597
2598 auto handleSimpleType = [](const nlohmann::json & value) -> std::optional<std::string> {
2599 if (value.is_string())
2600 return shellEscape(value);
2601
2602 if (value.is_number()) {
2603 auto f = value.get<float>();
2604 if (std::ceil(f) == f)
2605 return std::to_string(value.get<int>());
2606 }
2607
2608 if (value.is_null())
2609 return std::string("''");
2610
2611 if (value.is_boolean())
2612 return value.get<bool>() ? std::string("1") : std::string("");
2613
2614 return {};
2615 };
2616
2617 std::string jsonSh;
2618
2619 for (auto i = json.begin(); i != json.end(); ++i) {
2620
2621 if (!std::regex_match(i.key(), shVarName)) continue;
2622
2623 auto & value = i.value();
2624
2625 auto s = handleSimpleType(value);
2626 if (s)
2627 jsonSh += fmt("declare %s=%s\n", i.key(), *s);
2628
2629 else if (value.is_array()) {
2630 std::string s2;
2631 bool good = true;
2632
2633 for (auto i = value.begin(); i != value.end(); ++i) {
2634 auto s3 = handleSimpleType(i.value());
2635 if (!s3) { good = false; break; }
2636 s2 += *s3; s2 += ' ';
2637 }
2638
2639 if (good)
2640 jsonSh += fmt("declare -a %s=(%s)\n", i.key(), s2);
2641 }
2642
2643 else if (value.is_object()) {
2644 std::string s2;
2645 bool good = true;
2646
2647 for (auto i = value.begin(); i != value.end(); ++i) {
2648 auto s3 = handleSimpleType(i.value());
2649 if (!s3) { good = false; break; }
2650 s2 += fmt("[%s]=%s ", shellEscape(i.key()), *s3);
2651 }
2652
2653 if (good)
2654 jsonSh += fmt("declare -A %s=(%s)\n", i.key(), s2);
2655 }
2656 }
2657
2658 writeFile(tmpDir + "/.attrs.sh", rewriteStrings(jsonSh, inputRewrites));
2659 chownToBuilder(tmpDir + "/.attrs.sh");
2660 }
2661
2662
chownToBuilder(const Path & path)2663 void DerivationGoal::chownToBuilder(const Path & path)
2664 {
2665 if (!buildUser) return;
2666 if (chown(path.c_str(), buildUser->getUID(), buildUser->getGID()) == -1)
2667 throw SysError(format("cannot change ownership of '%1%'") % path);
2668 }
2669
2670
setupSeccomp()2671 void setupSeccomp()
2672 {
2673 #if __linux__
2674 if (!settings.filterSyscalls) return;
2675 #if HAVE_SECCOMP
2676 scmp_filter_ctx ctx;
2677
2678 if (!(ctx = seccomp_init(SCMP_ACT_ALLOW)))
2679 throw SysError("unable to initialize seccomp mode 2");
2680
2681 Finally cleanup([&]() {
2682 seccomp_release(ctx);
2683 });
2684
2685 if (nativeSystem == "x86_64-linux" &&
2686 seccomp_arch_add(ctx, SCMP_ARCH_X86) != 0)
2687 throw SysError("unable to add 32-bit seccomp architecture");
2688
2689 if (nativeSystem == "x86_64-linux" &&
2690 seccomp_arch_add(ctx, SCMP_ARCH_X32) != 0)
2691 throw SysError("unable to add X32 seccomp architecture");
2692
2693 if (nativeSystem == "aarch64-linux" &&
2694 seccomp_arch_add(ctx, SCMP_ARCH_ARM) != 0)
2695 printError("unable to add ARM seccomp architecture; this may result in spurious build failures if running 32-bit ARM processes");
2696
2697 /* Prevent builders from creating setuid/setgid binaries. */
2698 for (int perm : { S_ISUID, S_ISGID }) {
2699 if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(chmod), 1,
2700 SCMP_A1(SCMP_CMP_MASKED_EQ, (scmp_datum_t) perm, (scmp_datum_t) perm)) != 0)
2701 throw SysError("unable to add seccomp rule");
2702
2703 if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(fchmod), 1,
2704 SCMP_A1(SCMP_CMP_MASKED_EQ, (scmp_datum_t) perm, (scmp_datum_t) perm)) != 0)
2705 throw SysError("unable to add seccomp rule");
2706
2707 if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(fchmodat), 1,
2708 SCMP_A2(SCMP_CMP_MASKED_EQ, (scmp_datum_t) perm, (scmp_datum_t) perm)) != 0)
2709 throw SysError("unable to add seccomp rule");
2710 }
2711
2712 /* Prevent builders from creating EAs or ACLs. Not all filesystems
2713 support these, and they're not allowed in the Nix store because
2714 they're not representable in the NAR serialisation. */
2715 if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOTSUP), SCMP_SYS(setxattr), 0) != 0 ||
2716 seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOTSUP), SCMP_SYS(lsetxattr), 0) != 0 ||
2717 seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOTSUP), SCMP_SYS(fsetxattr), 0) != 0)
2718 throw SysError("unable to add seccomp rule");
2719
2720 if (seccomp_attr_set(ctx, SCMP_FLTATR_CTL_NNP, settings.allowNewPrivileges ? 0 : 1) != 0)
2721 throw SysError("unable to set 'no new privileges' seccomp attribute");
2722
2723 if (seccomp_load(ctx) != 0)
2724 throw SysError("unable to load seccomp BPF program");
2725 #else
2726 throw Error(
2727 "seccomp is not supported on this platform; "
2728 "you can bypass this error by setting the option 'filter-syscalls' to false, but note that untrusted builds can then create setuid binaries!");
2729 #endif
2730 #endif
2731 }
2732
2733
runChild()2734 void DerivationGoal::runChild()
2735 {
2736 /* Warning: in the child we should absolutely not make any SQLite
2737 calls! */
2738
2739 try { /* child */
2740
2741 commonChildInit(builderOut);
2742
2743 try {
2744 setupSeccomp();
2745 } catch (...) {
2746 if (buildUser) throw;
2747 }
2748
2749 bool setUser = true;
2750
2751 /* Make the contents of netrc available to builtin:fetchurl
2752 (which may run under a different uid and/or in a sandbox). */
2753 std::string netrcData;
2754 try {
2755 if (drv->isBuiltin() && drv->builder == "builtin:fetchurl")
2756 netrcData = readFile(settings.netrcFile);
2757 } catch (SysError &) { }
2758
2759 #if __linux__
2760 if (useChroot) {
2761
2762 userNamespaceSync.writeSide = -1;
2763
2764 if (drainFD(userNamespaceSync.readSide.get()) != "1")
2765 throw Error("user namespace initialisation failed");
2766
2767 userNamespaceSync.readSide = -1;
2768
2769 if (privateNetwork) {
2770
2771 /* Initialise the loopback interface. */
2772 AutoCloseFD fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP));
2773 if (!fd) throw SysError("cannot open IP socket");
2774
2775 struct ifreq ifr;
2776 strcpy(ifr.ifr_name, "lo");
2777 ifr.ifr_flags = IFF_UP | IFF_LOOPBACK | IFF_RUNNING;
2778 if (ioctl(fd.get(), SIOCSIFFLAGS, &ifr) == -1)
2779 throw SysError("cannot set loopback interface flags");
2780 }
2781
2782 /* Set the hostname etc. to fixed values. */
2783 char hostname[] = "localhost";
2784 if (sethostname(hostname, sizeof(hostname)) == -1)
2785 throw SysError("cannot set host name");
2786 char domainname[] = "(none)"; // kernel default
2787 if (setdomainname(domainname, sizeof(domainname)) == -1)
2788 throw SysError("cannot set domain name");
2789
2790 /* Make all filesystems private. This is necessary
2791 because subtrees may have been mounted as "shared"
2792 (MS_SHARED). (Systemd does this, for instance.) Even
2793 though we have a private mount namespace, mounting
2794 filesystems on top of a shared subtree still propagates
2795 outside of the namespace. Making a subtree private is
2796 local to the namespace, though, so setting MS_PRIVATE
2797 does not affect the outside world. */
2798 if (mount(0, "/", 0, MS_REC|MS_PRIVATE, 0) == -1) {
2799 throw SysError("unable to make '/' private mount");
2800 }
2801
2802 /* Bind-mount chroot directory to itself, to treat it as a
2803 different filesystem from /, as needed for pivot_root. */
2804 if (mount(chrootRootDir.c_str(), chrootRootDir.c_str(), 0, MS_BIND, 0) == -1)
2805 throw SysError(format("unable to bind mount '%1%'") % chrootRootDir);
2806
2807 /* Set up a nearly empty /dev, unless the user asked to
2808 bind-mount the host /dev. */
2809 Strings ss;
2810 if (dirsInChroot.find("/dev") == dirsInChroot.end()) {
2811 createDirs(chrootRootDir + "/dev/shm");
2812 createDirs(chrootRootDir + "/dev/pts");
2813 ss.push_back("/dev/full");
2814 if (settings.systemFeatures.get().count("kvm") && pathExists("/dev/kvm"))
2815 ss.push_back("/dev/kvm");
2816 ss.push_back("/dev/null");
2817 ss.push_back("/dev/random");
2818 ss.push_back("/dev/tty");
2819 ss.push_back("/dev/urandom");
2820 ss.push_back("/dev/zero");
2821 createSymlink("/proc/self/fd", chrootRootDir + "/dev/fd");
2822 createSymlink("/proc/self/fd/0", chrootRootDir + "/dev/stdin");
2823 createSymlink("/proc/self/fd/1", chrootRootDir + "/dev/stdout");
2824 createSymlink("/proc/self/fd/2", chrootRootDir + "/dev/stderr");
2825 }
2826
2827 /* Fixed-output derivations typically need to access the
2828 network, so give them access to /etc/resolv.conf and so
2829 on. */
2830 if (fixedOutput) {
2831 ss.push_back("/etc/resolv.conf");
2832
2833 // Only use nss functions to resolve hosts and
2834 // services. Don’t use it for anything else that may
2835 // be configured for this system. This limits the
2836 // potential impurities introduced in fixed outputs.
2837 writeFile(chrootRootDir + "/etc/nsswitch.conf", "hosts: files dns\nservices: files\n");
2838
2839 ss.push_back("/etc/services");
2840 ss.push_back("/etc/hosts");
2841 if (pathExists("/var/run/nscd/socket"))
2842 ss.push_back("/var/run/nscd/socket");
2843 }
2844
2845 for (auto & i : ss) dirsInChroot.emplace(i, i);
2846
2847 /* Bind-mount all the directories from the "host"
2848 filesystem that we want in the chroot
2849 environment. */
2850 auto doBind = [&](const Path & source, const Path & target, bool optional = false) {
2851 debug(format("bind mounting '%1%' to '%2%'") % source % target);
2852 struct stat st;
2853 if (stat(source.c_str(), &st) == -1) {
2854 if (optional && errno == ENOENT)
2855 return;
2856 else
2857 throw SysError("getting attributes of path '%1%'", source);
2858 }
2859 if (S_ISDIR(st.st_mode))
2860 createDirs(target);
2861 else {
2862 createDirs(dirOf(target));
2863 writeFile(target, "");
2864 }
2865 if (mount(source.c_str(), target.c_str(), "", MS_BIND | MS_REC, 0) == -1)
2866 throw SysError("bind mount from '%1%' to '%2%' failed", source, target);
2867 };
2868
2869 for (auto & i : dirsInChroot) {
2870 if (i.second.source == "/proc") continue; // backwards compatibility
2871 doBind(i.second.source, chrootRootDir + i.first, i.second.optional);
2872 }
2873
2874 /* Bind a new instance of procfs on /proc. */
2875 createDirs(chrootRootDir + "/proc");
2876 if (mount("none", (chrootRootDir + "/proc").c_str(), "proc", 0, 0) == -1)
2877 throw SysError("mounting /proc");
2878
2879 /* Mount a new tmpfs on /dev/shm to ensure that whatever
2880 the builder puts in /dev/shm is cleaned up automatically. */
2881 if (pathExists("/dev/shm") && mount("none", (chrootRootDir + "/dev/shm").c_str(), "tmpfs", 0,
2882 fmt("size=%s", settings.sandboxShmSize).c_str()) == -1)
2883 throw SysError("mounting /dev/shm");
2884
2885 /* Mount a new devpts on /dev/pts. Note that this
2886 requires the kernel to be compiled with
2887 CONFIG_DEVPTS_MULTIPLE_INSTANCES=y (which is the case
2888 if /dev/ptx/ptmx exists). */
2889 if (pathExists("/dev/pts/ptmx") &&
2890 !pathExists(chrootRootDir + "/dev/ptmx")
2891 && !dirsInChroot.count("/dev/pts"))
2892 {
2893 if (mount("none", (chrootRootDir + "/dev/pts").c_str(), "devpts", 0, "newinstance,mode=0620") == 0)
2894 {
2895 createSymlink("/dev/pts/ptmx", chrootRootDir + "/dev/ptmx");
2896
2897 /* Make sure /dev/pts/ptmx is world-writable. With some
2898 Linux versions, it is created with permissions 0. */
2899 chmod_(chrootRootDir + "/dev/pts/ptmx", 0666);
2900 } else {
2901 if (errno != EINVAL)
2902 throw SysError("mounting /dev/pts");
2903 doBind("/dev/pts", chrootRootDir + "/dev/pts");
2904 doBind("/dev/ptmx", chrootRootDir + "/dev/ptmx");
2905 }
2906 }
2907
2908 /* Do the chroot(). */
2909 if (chdir(chrootRootDir.c_str()) == -1)
2910 throw SysError(format("cannot change directory to '%1%'") % chrootRootDir);
2911
2912 if (mkdir("real-root", 0) == -1)
2913 throw SysError("cannot create real-root directory");
2914
2915 if (pivot_root(".", "real-root") == -1)
2916 throw SysError(format("cannot pivot old root directory onto '%1%'") % (chrootRootDir + "/real-root"));
2917
2918 if (chroot(".") == -1)
2919 throw SysError(format("cannot change root directory to '%1%'") % chrootRootDir);
2920
2921 if (umount2("real-root", MNT_DETACH) == -1)
2922 throw SysError("cannot unmount real root filesystem");
2923
2924 if (rmdir("real-root") == -1)
2925 throw SysError("cannot remove real-root directory");
2926
2927 /* Switch to the sandbox uid/gid in the user namespace,
2928 which corresponds to the build user or calling user in
2929 the parent namespace. */
2930 if (setgid(sandboxGid) == -1)
2931 throw SysError("setgid failed");
2932 if (setuid(sandboxUid) == -1)
2933 throw SysError("setuid failed");
2934
2935 setUser = false;
2936 }
2937 #endif
2938
2939 if (chdir(tmpDirInSandbox.c_str()) == -1)
2940 throw SysError(format("changing into '%1%'") % tmpDir);
2941
2942 /* Close all other file descriptors. */
2943 closeMostFDs({STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO});
2944
2945 #if __linux__
2946 /* Change the personality to 32-bit if we're doing an
2947 i686-linux build on an x86_64-linux machine. */
2948 struct utsname utsbuf;
2949 uname(&utsbuf);
2950 if (drv->platform == "i686-linux" &&
2951 (settings.thisSystem == "x86_64-linux" ||
2952 (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "x86_64")))) {
2953 if (personality(PER_LINUX32) == -1)
2954 throw SysError("cannot set i686-linux personality");
2955 }
2956
2957 /* Impersonate a Linux 2.6 machine to get some determinism in
2958 builds that depend on the kernel version. */
2959 if ((drv->platform == "i686-linux" || drv->platform == "x86_64-linux") && settings.impersonateLinux26) {
2960 int cur = personality(0xffffffff);
2961 if (cur != -1) personality(cur | 0x0020000 /* == UNAME26 */);
2962 }
2963
2964 /* Disable address space randomization for improved
2965 determinism. */
2966 int cur = personality(0xffffffff);
2967 if (cur != -1) personality(cur | ADDR_NO_RANDOMIZE);
2968 #endif
2969
2970 /* Disable core dumps by default. */
2971 struct rlimit limit = { 0, RLIM_INFINITY };
2972 setrlimit(RLIMIT_CORE, &limit);
2973
2974 // FIXME: set other limits to deterministic values?
2975
2976 /* Fill in the environment. */
2977 Strings envStrs;
2978 for (auto & i : env)
2979 envStrs.push_back(rewriteStrings(i.first + "=" + i.second, inputRewrites));
2980
2981 /* If we are running in `build-users' mode, then switch to the
2982 user we allocated above. Make sure that we drop all root
2983 privileges. Note that above we have closed all file
2984 descriptors except std*, so that's safe. Also note that
2985 setuid() when run as root sets the real, effective and
2986 saved UIDs. */
2987 if (setUser && buildUser) {
2988 /* Preserve supplementary groups of the build user, to allow
2989 admins to specify groups such as "kvm". */
2990 if (!buildUser->getSupplementaryGIDs().empty() &&
2991 setgroups(buildUser->getSupplementaryGIDs().size(),
2992 buildUser->getSupplementaryGIDs().data()) == -1)
2993 throw SysError("cannot set supplementary groups of build user");
2994
2995 if (setgid(buildUser->getGID()) == -1 ||
2996 getgid() != buildUser->getGID() ||
2997 getegid() != buildUser->getGID())
2998 throw SysError("setgid failed");
2999
3000 if (setuid(buildUser->getUID()) == -1 ||
3001 getuid() != buildUser->getUID() ||
3002 geteuid() != buildUser->getUID())
3003 throw SysError("setuid failed");
3004 }
3005
3006 /* Fill in the arguments. */
3007 Strings args;
3008
3009 const char *builder = "invalid";
3010
3011 if (drv->isBuiltin()) {
3012 ;
3013 }
3014 #if __APPLE__
3015 else if (getEnv("_NIX_TEST_NO_SANDBOX") == "") {
3016 /* This has to appear before import statements. */
3017 std::string sandboxProfile = "(version 1)\n";
3018
3019 if (useChroot) {
3020
3021 /* Lots and lots and lots of file functions freak out if they can't stat their full ancestry */
3022 PathSet ancestry;
3023
3024 /* We build the ancestry before adding all inputPaths to the store because we know they'll
3025 all have the same parents (the store), and there might be lots of inputs. This isn't
3026 particularly efficient... I doubt it'll be a bottleneck in practice */
3027 for (auto & i : dirsInChroot) {
3028 Path cur = i.first;
3029 while (cur.compare("/") != 0) {
3030 cur = dirOf(cur);
3031 ancestry.insert(cur);
3032 }
3033 }
3034
3035 /* And we want the store in there regardless of how empty dirsInChroot. We include the innermost
3036 path component this time, since it's typically /nix/store and we care about that. */
3037 Path cur = worker.store.storeDir;
3038 while (cur.compare("/") != 0) {
3039 ancestry.insert(cur);
3040 cur = dirOf(cur);
3041 }
3042
3043 /* Add all our input paths to the chroot */
3044 for (auto & i : inputPaths)
3045 dirsInChroot[i] = i;
3046
3047 /* Violations will go to the syslog if you set this. Unfortunately the destination does not appear to be configurable */
3048 if (settings.darwinLogSandboxViolations) {
3049 sandboxProfile += "(deny default)\n";
3050 } else {
3051 sandboxProfile += "(deny default (with no-log))\n";
3052 }
3053
3054 sandboxProfile += "(import \"sandbox-defaults.sb\")\n";
3055
3056 if (fixedOutput)
3057 sandboxProfile += "(import \"sandbox-network.sb\")\n";
3058
3059 /* Our rwx outputs */
3060 sandboxProfile += "(allow file-read* file-write* process-exec\n";
3061 for (auto & i : missingPaths) {
3062 sandboxProfile += (format("\t(subpath \"%1%\")\n") % i.c_str()).str();
3063 }
3064 /* Also add redirected outputs to the chroot */
3065 for (auto & i : redirectedOutputs) {
3066 sandboxProfile += (format("\t(subpath \"%1%\")\n") % i.second.c_str()).str();
3067 }
3068 sandboxProfile += ")\n";
3069
3070 /* Our inputs (transitive dependencies and any impurities computed above)
3071
3072 without file-write* allowed, access() incorrectly returns EPERM
3073 */
3074 sandboxProfile += "(allow file-read* file-write* process-exec\n";
3075 for (auto & i : dirsInChroot) {
3076 if (i.first != i.second.source)
3077 throw Error(format(
3078 "can't map '%1%' to '%2%': mismatched impure paths not supported on Darwin")
3079 % i.first % i.second.source);
3080
3081 string path = i.first;
3082 struct stat st;
3083 if (lstat(path.c_str(), &st)) {
3084 if (i.second.optional && errno == ENOENT)
3085 continue;
3086 throw SysError(format("getting attributes of path '%1%'") % path);
3087 }
3088 if (S_ISDIR(st.st_mode))
3089 sandboxProfile += (format("\t(subpath \"%1%\")\n") % path).str();
3090 else
3091 sandboxProfile += (format("\t(literal \"%1%\")\n") % path).str();
3092 }
3093 sandboxProfile += ")\n";
3094
3095 /* Allow file-read* on full directory hierarchy to self. Allows realpath() */
3096 sandboxProfile += "(allow file-read*\n";
3097 for (auto & i : ancestry) {
3098 sandboxProfile += (format("\t(literal \"%1%\")\n") % i.c_str()).str();
3099 }
3100 sandboxProfile += ")\n";
3101
3102 sandboxProfile += additionalSandboxProfile;
3103 } else
3104 sandboxProfile += "(import \"sandbox-minimal.sb\")\n";
3105
3106 debug("Generated sandbox profile:");
3107 debug(sandboxProfile);
3108
3109 Path sandboxFile = tmpDir + "/.sandbox.sb";
3110
3111 writeFile(sandboxFile, sandboxProfile);
3112
3113 bool allowLocalNetworking = parsedDrv->getBoolAttr("__darwinAllowLocalNetworking");
3114
3115 /* The tmpDir in scope points at the temporary build directory for our derivation. Some packages try different mechanisms
3116 to find temporary directories, so we want to open up a broader place for them to dump their files, if needed. */
3117 Path globalTmpDir = canonPath(getEnv("TMPDIR", "/tmp"), true);
3118
3119 /* They don't like trailing slashes on subpath directives */
3120 if (globalTmpDir.back() == '/') globalTmpDir.pop_back();
3121
3122 builder = "/usr/bin/sandbox-exec";
3123 args.push_back("sandbox-exec");
3124 args.push_back("-f");
3125 args.push_back(sandboxFile);
3126 args.push_back("-D");
3127 args.push_back("_GLOBAL_TMP_DIR=" + globalTmpDir);
3128 args.push_back("-D");
3129 args.push_back("IMPORT_DIR=" + settings.nixDataDir + "/nix/sandbox/");
3130 if (allowLocalNetworking) {
3131 args.push_back("-D");
3132 args.push_back(string("_ALLOW_LOCAL_NETWORKING=1"));
3133 }
3134 args.push_back(drv->builder);
3135 }
3136 #endif
3137 else {
3138 builder = drv->builder.c_str();
3139 string builderBasename = baseNameOf(drv->builder);
3140 args.push_back(builderBasename);
3141 }
3142
3143 for (auto & i : drv->args)
3144 args.push_back(rewriteStrings(i, inputRewrites));
3145
3146 /* Indicate that we managed to set up the build environment. */
3147 writeFull(STDERR_FILENO, string("\1\n"));
3148
3149 /* Execute the program. This should not return. */
3150 if (drv->isBuiltin()) {
3151 try {
3152 logger = makeJSONLogger(*logger);
3153
3154 BasicDerivation drv2(*drv);
3155 for (auto & e : drv2.env)
3156 e.second = rewriteStrings(e.second, inputRewrites);
3157
3158 if (drv->builder == "builtin:fetchurl")
3159 builtinFetchurl(drv2, netrcData);
3160 else if (drv->builder == "builtin:buildenv")
3161 builtinBuildenv(drv2);
3162 else
3163 throw Error(format("unsupported builtin function '%1%'") % string(drv->builder, 8));
3164 _exit(0);
3165 } catch (std::exception & e) {
3166 writeFull(STDERR_FILENO, "error: " + string(e.what()) + "\n");
3167 _exit(1);
3168 }
3169 }
3170
3171 execve(builder, stringsToCharPtrs(args).data(), stringsToCharPtrs(envStrs).data());
3172
3173 throw SysError(format("executing '%1%'") % drv->builder);
3174
3175 } catch (std::exception & e) {
3176 writeFull(STDERR_FILENO, "\1while setting up the build environment: " + string(e.what()) + "\n");
3177 _exit(1);
3178 }
3179 }
3180
3181
3182 /* Parse a list of reference specifiers. Each element must either be
3183 a store path, or the symbolic name of the output of the derivation
3184 (such as `out'). */
parseReferenceSpecifiers(Store & store,const BasicDerivation & drv,const Strings & paths)3185 PathSet parseReferenceSpecifiers(Store & store, const BasicDerivation & drv, const Strings & paths)
3186 {
3187 PathSet result;
3188 for (auto & i : paths) {
3189 if (store.isStorePath(i))
3190 result.insert(i);
3191 else if (drv.outputs.find(i) != drv.outputs.end())
3192 result.insert(drv.outputs.find(i)->second.path);
3193 else throw BuildError(
3194 format("derivation contains an illegal reference specifier '%1%'") % i);
3195 }
3196 return result;
3197 }
3198
3199
3200 /* Move/rename path 'src' to 'dst'. Temporarily make 'src' writable if
3201 it's a directory and we're not root (to be able to update the
3202 directory's parent link ".."). */
movePath(const Path & src,const Path & dst)3203 static void movePath(const Path & src, const Path & dst)
3204 {
3205 auto st = lstat(src);
3206
3207 bool changePerm = (geteuid() && S_ISDIR(st.st_mode) && !(st.st_mode & S_IWUSR));
3208
3209 if (changePerm)
3210 chmod_(src, st.st_mode | S_IWUSR);
3211
3212 if (rename(src.c_str(), dst.c_str()))
3213 throw SysError("renaming '%1%' to '%2%'", src, dst);
3214
3215 if (changePerm)
3216 chmod_(dst, st.st_mode);
3217 }
3218
3219
registerOutputs()3220 void DerivationGoal::registerOutputs()
3221 {
3222 /* When using a build hook, the build hook can register the output
3223 as valid (by doing `nix-store --import'). If so we don't have
3224 to do anything here. */
3225 if (hook) {
3226 bool allValid = true;
3227 for (auto & i : drv->outputs)
3228 if (!worker.store.isValidPath(i.second.path)) allValid = false;
3229 if (allValid) return;
3230 }
3231
3232 std::map<std::string, ValidPathInfo> infos;
3233
3234 /* Set of inodes seen during calls to canonicalisePathMetaData()
3235 for this build's outputs. This needs to be shared between
3236 outputs to allow hard links between outputs. */
3237 InodesSeen inodesSeen;
3238
3239 Path checkSuffix = ".check";
3240 bool keepPreviousRound = settings.keepFailed || settings.runDiffHook;
3241
3242 std::exception_ptr delayedException;
3243
3244 /* Check whether the output paths were created, and grep each
3245 output path to determine what other paths it references. Also make all
3246 output paths read-only. */
3247 for (auto & i : drv->outputs) {
3248 Path path = i.second.path;
3249 if (missingPaths.find(path) == missingPaths.end()) continue;
3250
3251 ValidPathInfo info;
3252
3253 Path actualPath = path;
3254 if (useChroot) {
3255 actualPath = chrootRootDir + path;
3256 if (pathExists(actualPath)) {
3257 /* Move output paths from the chroot to the Nix store. */
3258 if (buildMode == bmRepair)
3259 replaceValidPath(path, actualPath);
3260 else if (buildMode != bmCheck)
3261 movePath(actualPath, worker.store.toRealPath(path));
3262 }
3263 if (buildMode != bmCheck) actualPath = worker.store.toRealPath(path);
3264 }
3265
3266 if (needsHashRewrite()) {
3267 Path redirected = redirectedOutputs[path];
3268 if (buildMode == bmRepair
3269 && redirectedBadOutputs.find(path) != redirectedBadOutputs.end()
3270 && pathExists(redirected))
3271 replaceValidPath(path, redirected);
3272 if (buildMode == bmCheck && redirected != "")
3273 actualPath = redirected;
3274 }
3275
3276 struct stat st;
3277 if (lstat(actualPath.c_str(), &st) == -1) {
3278 if (errno == ENOENT)
3279 throw BuildError(
3280 format("builder for '%1%' failed to produce output path '%2%'")
3281 % drvPath % path);
3282 throw SysError(format("getting attributes of path '%1%'") % actualPath);
3283 }
3284
3285 #ifndef __CYGWIN__
3286 /* Check that the output is not group or world writable, as
3287 that means that someone else can have interfered with the
3288 build. Also, the output should be owned by the build
3289 user. */
3290 if ((!S_ISLNK(st.st_mode) && (st.st_mode & (S_IWGRP | S_IWOTH))) ||
3291 (buildUser && st.st_uid != buildUser->getUID()))
3292 throw BuildError(format("suspicious ownership or permission on '%1%'; rejecting this build output") % path);
3293 #endif
3294
3295 /* Apply hash rewriting if necessary. */
3296 bool rewritten = false;
3297 if (!outputRewrites.empty()) {
3298 printError(format("warning: rewriting hashes in '%1%'; cross fingers") % path);
3299
3300 /* Canonicalise first. This ensures that the path we're
3301 rewriting doesn't contain a hard link to /etc/shadow or
3302 something like that. */
3303 canonicalisePathMetaData(actualPath, buildUser ? buildUser->getUID() : -1, inodesSeen);
3304
3305 /* FIXME: this is in-memory. */
3306 StringSink sink;
3307 dumpPath(actualPath, sink);
3308 deletePath(actualPath);
3309 sink.s = make_ref<std::string>(rewriteStrings(*sink.s, outputRewrites));
3310 StringSource source(*sink.s);
3311 restorePath(actualPath, source);
3312
3313 rewritten = true;
3314 }
3315
3316 /* Check that fixed-output derivations produced the right
3317 outputs (i.e., the content hash should match the specified
3318 hash). */
3319 if (fixedOutput) {
3320
3321 bool recursive; Hash h;
3322 i.second.parseHashInfo(recursive, h);
3323
3324 if (!recursive) {
3325 /* The output path should be a regular file without
3326 execute permission. */
3327 if (!S_ISREG(st.st_mode) || (st.st_mode & S_IXUSR) != 0)
3328 throw BuildError(
3329 format("output path '%1%' should be a non-executable regular file") % path);
3330 }
3331
3332 /* Check the hash. In hash mode, move the path produced by
3333 the derivation to its content-addressed location. */
3334 Hash h2 = recursive ? hashPath(h.type, actualPath).first : hashFile(h.type, actualPath);
3335
3336 Path dest = worker.store.makeFixedOutputPath(recursive, h2, storePathToName(path));
3337
3338 if (h != h2) {
3339
3340 /* Throw an error after registering the path as
3341 valid. */
3342 worker.hashMismatch = true;
3343 delayedException = std::make_exception_ptr(
3344 BuildError("hash mismatch in fixed-output derivation '%s':\n wanted: %s\n got: %s",
3345 dest, h.to_string(), h2.to_string()));
3346
3347 Path actualDest = worker.store.toRealPath(dest);
3348
3349 if (worker.store.isValidPath(dest))
3350 std::rethrow_exception(delayedException);
3351
3352 if (actualPath != actualDest) {
3353 PathLocks outputLocks({actualDest});
3354 deletePath(actualDest);
3355 if (rename(actualPath.c_str(), actualDest.c_str()) == -1)
3356 throw SysError(format("moving '%1%' to '%2%'") % actualPath % dest);
3357 }
3358
3359 path = dest;
3360 actualPath = actualDest;
3361 }
3362 else
3363 assert(path == dest);
3364
3365 info.ca = makeFixedOutputCA(recursive, h2);
3366 }
3367
3368 /* Get rid of all weird permissions. This also checks that
3369 all files are owned by the build user, if applicable. */
3370 canonicalisePathMetaData(actualPath,
3371 buildUser && !rewritten ? buildUser->getUID() : -1, inodesSeen);
3372
3373 /* For this output path, find the references to other paths
3374 contained in it. Compute the SHA-256 NAR hash at the same
3375 time. The hash is stored in the database so that we can
3376 verify later on whether nobody has messed with the store. */
3377 debug("scanning for references inside '%1%'", path);
3378 HashResult hash;
3379 PathSet references = scanForReferences(actualPath, allPaths, hash);
3380
3381 if (buildMode == bmCheck) {
3382 if (!worker.store.isValidPath(path)) continue;
3383 auto info = *worker.store.queryPathInfo(path);
3384 if (hash.first != info.narHash) {
3385 worker.checkMismatch = true;
3386 if (settings.runDiffHook || settings.keepFailed) {
3387 Path dst = worker.store.toRealPath(path + checkSuffix);
3388 deletePath(dst);
3389 if (rename(actualPath.c_str(), dst.c_str()))
3390 throw SysError(format("renaming '%1%' to '%2%'") % actualPath % dst);
3391
3392 handleDiffHook(
3393 buildUser ? buildUser->getUID() : getuid(),
3394 buildUser ? buildUser->getGID() : getgid(),
3395 path, dst, drvPath, tmpDir);
3396
3397 throw NotDeterministic(format("derivation '%1%' may not be deterministic: output '%2%' differs from '%3%'")
3398 % drvPath % path % dst);
3399 } else
3400 throw NotDeterministic(format("derivation '%1%' may not be deterministic: output '%2%' differs")
3401 % drvPath % path);
3402 }
3403
3404 /* Since we verified the build, it's now ultimately
3405 trusted. */
3406 if (!info.ultimate) {
3407 info.ultimate = true;
3408 worker.store.signPathInfo(info);
3409 worker.store.registerValidPaths({info});
3410 }
3411
3412 continue;
3413 }
3414
3415 /* For debugging, print out the referenced and unreferenced
3416 paths. */
3417 for (auto & i : inputPaths) {
3418 PathSet::iterator j = references.find(i);
3419 if (j == references.end())
3420 debug(format("unreferenced input: '%1%'") % i);
3421 else
3422 debug(format("referenced input: '%1%'") % i);
3423 }
3424
3425 if (curRound == nrRounds) {
3426 worker.store.optimisePath(actualPath); // FIXME: combine with scanForReferences()
3427 worker.markContentsGood(path);
3428 }
3429
3430 info.path = path;
3431 info.narHash = hash.first;
3432 info.narSize = hash.second;
3433 info.references = references;
3434 info.deriver = drvPath;
3435 info.ultimate = true;
3436 worker.store.signPathInfo(info);
3437
3438 if (!info.references.empty()) info.ca.clear();
3439
3440 infos[i.first] = info;
3441 }
3442
3443 if (buildMode == bmCheck) return;
3444
3445 /* Apply output checks. */
3446 checkOutputs(infos);
3447
3448 /* Compare the result with the previous round, and report which
3449 path is different, if any.*/
3450 if (curRound > 1 && prevInfos != infos) {
3451 assert(prevInfos.size() == infos.size());
3452 for (auto i = prevInfos.begin(), j = infos.begin(); i != prevInfos.end(); ++i, ++j)
3453 if (!(*i == *j)) {
3454 result.isNonDeterministic = true;
3455 Path prev = i->second.path + checkSuffix;
3456 bool prevExists = keepPreviousRound && pathExists(prev);
3457 auto msg = prevExists
3458 ? fmt("output '%1%' of '%2%' differs from '%3%' from previous round", i->second.path, drvPath, prev)
3459 : fmt("output '%1%' of '%2%' differs from previous round", i->second.path, drvPath);
3460
3461 handleDiffHook(
3462 buildUser ? buildUser->getUID() : getuid(),
3463 buildUser ? buildUser->getGID() : getgid(),
3464 prev, i->second.path, drvPath, tmpDir);
3465
3466 if (settings.enforceDeterminism)
3467 throw NotDeterministic(msg);
3468
3469 printError(msg);
3470 curRound = nrRounds; // we know enough, bail out early
3471 }
3472 }
3473
3474 /* If this is the first round of several, then move the output out
3475 of the way. */
3476 if (nrRounds > 1 && curRound == 1 && curRound < nrRounds && keepPreviousRound) {
3477 for (auto & i : drv->outputs) {
3478 Path prev = i.second.path + checkSuffix;
3479 deletePath(prev);
3480 Path dst = i.second.path + checkSuffix;
3481 if (rename(i.second.path.c_str(), dst.c_str()))
3482 throw SysError(format("renaming '%1%' to '%2%'") % i.second.path % dst);
3483 }
3484 }
3485
3486 if (curRound < nrRounds) {
3487 prevInfos = infos;
3488 return;
3489 }
3490
3491 /* Remove the .check directories if we're done. FIXME: keep them
3492 if the result was not determistic? */
3493 if (curRound == nrRounds) {
3494 for (auto & i : drv->outputs) {
3495 Path prev = i.second.path + checkSuffix;
3496 deletePath(prev);
3497 }
3498 }
3499
3500 /* Register each output path as valid, and register the sets of
3501 paths referenced by each of them. If there are cycles in the
3502 outputs, this will fail. */
3503 {
3504 ValidPathInfos infos2;
3505 for (auto & i : infos) infos2.push_back(i.second);
3506 worker.store.registerValidPaths(infos2);
3507 }
3508
3509 /* In case of a fixed-output derivation hash mismatch, throw an
3510 exception now that we have registered the output as valid. */
3511 if (delayedException)
3512 std::rethrow_exception(delayedException);
3513 }
3514
3515
checkOutputs(const std::map<Path,ValidPathInfo> & outputs)3516 void DerivationGoal::checkOutputs(const std::map<Path, ValidPathInfo> & outputs)
3517 {
3518 std::map<Path, const ValidPathInfo &> outputsByPath;
3519 for (auto & output : outputs)
3520 outputsByPath.emplace(output.second.path, output.second);
3521
3522 for (auto & output : outputs) {
3523 auto & outputName = output.first;
3524 auto & info = output.second;
3525
3526 struct Checks
3527 {
3528 bool ignoreSelfRefs = false;
3529 std::optional<uint64_t> maxSize, maxClosureSize;
3530 std::optional<Strings> allowedReferences, allowedRequisites, disallowedReferences, disallowedRequisites;
3531 };
3532
3533 /* Compute the closure and closure size of some output. This
3534 is slightly tricky because some of its references (namely
3535 other outputs) may not be valid yet. */
3536 auto getClosure = [&](const Path & path)
3537 {
3538 uint64_t closureSize = 0;
3539 PathSet pathsDone;
3540 std::queue<Path> pathsLeft;
3541 pathsLeft.push(path);
3542
3543 while (!pathsLeft.empty()) {
3544 auto path = pathsLeft.front();
3545 pathsLeft.pop();
3546 if (!pathsDone.insert(path).second) continue;
3547
3548 auto i = outputsByPath.find(path);
3549 if (i != outputsByPath.end()) {
3550 closureSize += i->second.narSize;
3551 for (auto & ref : i->second.references)
3552 pathsLeft.push(ref);
3553 } else {
3554 auto info = worker.store.queryPathInfo(path);
3555 closureSize += info->narSize;
3556 for (auto & ref : info->references)
3557 pathsLeft.push(ref);
3558 }
3559 }
3560
3561 return std::make_pair(pathsDone, closureSize);
3562 };
3563
3564 auto applyChecks = [&](const Checks & checks)
3565 {
3566 if (checks.maxSize && info.narSize > *checks.maxSize)
3567 throw BuildError("path '%s' is too large at %d bytes; limit is %d bytes",
3568 info.path, info.narSize, *checks.maxSize);
3569
3570 if (checks.maxClosureSize) {
3571 uint64_t closureSize = getClosure(info.path).second;
3572 if (closureSize > *checks.maxClosureSize)
3573 throw BuildError("closure of path '%s' is too large at %d bytes; limit is %d bytes",
3574 info.path, closureSize, *checks.maxClosureSize);
3575 }
3576
3577 auto checkRefs = [&](const std::optional<Strings> & value, bool allowed, bool recursive)
3578 {
3579 if (!value) return;
3580
3581 PathSet spec = parseReferenceSpecifiers(worker.store, *drv, *value);
3582
3583 PathSet used = recursive ? getClosure(info.path).first : info.references;
3584
3585 if (recursive && checks.ignoreSelfRefs)
3586 used.erase(info.path);
3587
3588 PathSet badPaths;
3589
3590 for (auto & i : used)
3591 if (allowed) {
3592 if (!spec.count(i))
3593 badPaths.insert(i);
3594 } else {
3595 if (spec.count(i))
3596 badPaths.insert(i);
3597 }
3598
3599 if (!badPaths.empty()) {
3600 string badPathsStr;
3601 for (auto & i : badPaths) {
3602 badPathsStr += "\n ";
3603 badPathsStr += i;
3604 }
3605 throw BuildError("output '%s' is not allowed to refer to the following paths:%s", info.path, badPathsStr);
3606 }
3607 };
3608
3609 checkRefs(checks.allowedReferences, true, false);
3610 checkRefs(checks.allowedRequisites, true, true);
3611 checkRefs(checks.disallowedReferences, false, false);
3612 checkRefs(checks.disallowedRequisites, false, true);
3613 };
3614
3615 if (auto structuredAttrs = parsedDrv->getStructuredAttrs()) {
3616 auto outputChecks = structuredAttrs->find("outputChecks");
3617 if (outputChecks != structuredAttrs->end()) {
3618 auto output = outputChecks->find(outputName);
3619
3620 if (output != outputChecks->end()) {
3621 Checks checks;
3622
3623 auto maxSize = output->find("maxSize");
3624 if (maxSize != output->end())
3625 checks.maxSize = maxSize->get<uint64_t>();
3626
3627 auto maxClosureSize = output->find("maxClosureSize");
3628 if (maxClosureSize != output->end())
3629 checks.maxClosureSize = maxClosureSize->get<uint64_t>();
3630
3631 auto get = [&](const std::string & name) -> std::optional<Strings> {
3632 auto i = output->find(name);
3633 if (i != output->end()) {
3634 Strings res;
3635 for (auto j = i->begin(); j != i->end(); ++j) {
3636 if (!j->is_string())
3637 throw Error("attribute '%s' of derivation '%s' must be a list of strings", name, drvPath);
3638 res.push_back(j->get<std::string>());
3639 }
3640 checks.disallowedRequisites = res;
3641 return res;
3642 }
3643 return {};
3644 };
3645
3646 checks.allowedReferences = get("allowedReferences");
3647 checks.allowedRequisites = get("allowedRequisites");
3648 checks.disallowedReferences = get("disallowedReferences");
3649 checks.disallowedRequisites = get("disallowedRequisites");
3650
3651 applyChecks(checks);
3652 }
3653 }
3654 } else {
3655 // legacy non-structured-attributes case
3656 Checks checks;
3657 checks.ignoreSelfRefs = true;
3658 checks.allowedReferences = parsedDrv->getStringsAttr("allowedReferences");
3659 checks.allowedRequisites = parsedDrv->getStringsAttr("allowedRequisites");
3660 checks.disallowedReferences = parsedDrv->getStringsAttr("disallowedReferences");
3661 checks.disallowedRequisites = parsedDrv->getStringsAttr("disallowedRequisites");
3662 applyChecks(checks);
3663 }
3664 }
3665 }
3666
3667
openLogFile()3668 Path DerivationGoal::openLogFile()
3669 {
3670 logSize = 0;
3671
3672 if (!settings.keepLog) return "";
3673
3674 string baseName = baseNameOf(drvPath);
3675
3676 /* Create a log file. */
3677 Path dir = fmt("%s/%s/%s/", worker.store.logDir, worker.store.drvsLogDir, string(baseName, 0, 2));
3678 createDirs(dir);
3679
3680 Path logFileName = fmt("%s/%s%s", dir, string(baseName, 2),
3681 settings.compressLog ? ".bz2" : "");
3682
3683 fdLogFile = open(logFileName.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0666);
3684 if (!fdLogFile) throw SysError(format("creating log file '%1%'") % logFileName);
3685
3686 logFileSink = std::make_shared<FdSink>(fdLogFile.get());
3687
3688 if (settings.compressLog)
3689 logSink = std::shared_ptr<CompressionSink>(makeCompressionSink("bzip2", *logFileSink));
3690 else
3691 logSink = logFileSink;
3692
3693 return logFileName;
3694 }
3695
3696
closeLogFile()3697 void DerivationGoal::closeLogFile()
3698 {
3699 auto logSink2 = std::dynamic_pointer_cast<CompressionSink>(logSink);
3700 if (logSink2) logSink2->finish();
3701 if (logFileSink) logFileSink->flush();
3702 logSink = logFileSink = 0;
3703 fdLogFile = -1;
3704 }
3705
3706
deleteTmpDir(bool force)3707 void DerivationGoal::deleteTmpDir(bool force)
3708 {
3709 if (tmpDir != "") {
3710 /* Don't keep temporary directories for builtins because they
3711 might have privileged stuff (like a copy of netrc). */
3712 if (settings.keepFailed && !force && !drv->isBuiltin()) {
3713 printError(
3714 format("note: keeping build directory '%2%'")
3715 % drvPath % tmpDir);
3716 chmod(tmpDir.c_str(), 0755);
3717 }
3718 else
3719 deletePath(tmpDir);
3720 tmpDir = "";
3721 }
3722 }
3723
3724
handleChildOutput(int fd,const string & data)3725 void DerivationGoal::handleChildOutput(int fd, const string & data)
3726 {
3727 if ((hook && fd == hook->builderOut.readSide.get()) ||
3728 (!hook && fd == builderOut.readSide.get()))
3729 {
3730 logSize += data.size();
3731 if (settings.maxLogSize && logSize > settings.maxLogSize) {
3732 printError(
3733 format("%1% killed after writing more than %2% bytes of log output")
3734 % getName() % settings.maxLogSize);
3735 killChild();
3736 done(BuildResult::LogLimitExceeded);
3737 return;
3738 }
3739
3740 for (auto c : data)
3741 if (c == '\r')
3742 currentLogLinePos = 0;
3743 else if (c == '\n')
3744 flushLine();
3745 else {
3746 if (currentLogLinePos >= currentLogLine.size())
3747 currentLogLine.resize(currentLogLinePos + 1);
3748 currentLogLine[currentLogLinePos++] = c;
3749 }
3750
3751 if (logSink) (*logSink)(data);
3752 }
3753
3754 if (hook && fd == hook->fromHook.readSide.get()) {
3755 for (auto c : data)
3756 if (c == '\n') {
3757 handleJSONLogMessage(currentHookLine, worker.act, hook->activities, true);
3758 currentHookLine.clear();
3759 } else
3760 currentHookLine += c;
3761 }
3762 }
3763
3764
handleEOF(int fd)3765 void DerivationGoal::handleEOF(int fd)
3766 {
3767 if (!currentLogLine.empty()) flushLine();
3768 worker.wakeUp(shared_from_this());
3769 }
3770
3771
flushLine()3772 void DerivationGoal::flushLine()
3773 {
3774 if (handleJSONLogMessage(currentLogLine, *act, builderActivities, false))
3775 ;
3776
3777 else {
3778 if (settings.verboseBuild &&
3779 (settings.printRepeatedBuilds || curRound == 1))
3780 printError(currentLogLine);
3781 else {
3782 logTail.push_back(currentLogLine);
3783 if (logTail.size() > settings.logLines) logTail.pop_front();
3784 }
3785
3786 act->result(resBuildLogLine, currentLogLine);
3787 }
3788
3789 currentLogLine = "";
3790 currentLogLinePos = 0;
3791 }
3792
3793
checkPathValidity(bool returnValid,bool checkHash)3794 PathSet DerivationGoal::checkPathValidity(bool returnValid, bool checkHash)
3795 {
3796 PathSet result;
3797 for (auto & i : drv->outputs) {
3798 if (!wantOutput(i.first, wantedOutputs)) continue;
3799 bool good =
3800 worker.store.isValidPath(i.second.path) &&
3801 (!checkHash || worker.pathContentsGood(i.second.path));
3802 if (good == returnValid) result.insert(i.second.path);
3803 }
3804 return result;
3805 }
3806
3807
addHashRewrite(const Path & path)3808 Path DerivationGoal::addHashRewrite(const Path & path)
3809 {
3810 string h1 = string(path, worker.store.storeDir.size() + 1, 32);
3811 string h2 = string(hashString(htSHA256, "rewrite:" + drvPath + ":" + path).to_string(Base32, false), 0, 32);
3812 Path p = worker.store.storeDir + "/" + h2 + string(path, worker.store.storeDir.size() + 33);
3813 deletePath(p);
3814 assert(path.size() == p.size());
3815 inputRewrites[h1] = h2;
3816 outputRewrites[h2] = h1;
3817 redirectedOutputs[path] = p;
3818 return p;
3819 }
3820
3821
done(BuildResult::Status status,const string & msg)3822 void DerivationGoal::done(BuildResult::Status status, const string & msg)
3823 {
3824 result.status = status;
3825 result.errorMsg = msg;
3826 amDone(result.success() ? ecSuccess : ecFailed);
3827 if (result.status == BuildResult::TimedOut)
3828 worker.timedOut = true;
3829 if (result.status == BuildResult::PermanentFailure)
3830 worker.permanentFailure = true;
3831
3832 mcExpectedBuilds.reset();
3833 mcRunningBuilds.reset();
3834
3835 if (result.success()) {
3836 if (status == BuildResult::Built)
3837 worker.doneBuilds++;
3838 } else {
3839 if (status != BuildResult::DependencyFailed)
3840 worker.failedBuilds++;
3841 }
3842
3843 worker.updateProgress();
3844 }
3845
3846
3847 //////////////////////////////////////////////////////////////////////
3848
3849
3850 class SubstitutionGoal : public Goal
3851 {
3852 friend class Worker;
3853
3854 private:
3855 /* The store path that should be realised through a substitute. */
3856 Path storePath;
3857
3858 /* The remaining substituters. */
3859 std::list<ref<Store>> subs;
3860
3861 /* The current substituter. */
3862 std::shared_ptr<Store> sub;
3863
3864 /* Whether a substituter failed. */
3865 bool substituterFailed = false;
3866
3867 /* Path info returned by the substituter's query info operation. */
3868 std::shared_ptr<const ValidPathInfo> info;
3869
3870 /* Pipe for the substituter's standard output. */
3871 Pipe outPipe;
3872
3873 /* The substituter thread. */
3874 std::thread thr;
3875
3876 std::promise<void> promise;
3877
3878 /* Whether to try to repair a valid path. */
3879 RepairFlag repair;
3880
3881 /* Location where we're downloading the substitute. Differs from
3882 storePath when doing a repair. */
3883 Path destPath;
3884
3885 std::unique_ptr<MaintainCount<uint64_t>> maintainExpectedSubstitutions,
3886 maintainRunningSubstitutions, maintainExpectedNar, maintainExpectedDownload;
3887
3888 typedef void (SubstitutionGoal::*GoalState)();
3889 GoalState state;
3890
3891 public:
3892 SubstitutionGoal(const Path & storePath, Worker & worker, RepairFlag repair = NoRepair);
3893 ~SubstitutionGoal();
3894
timedOut()3895 void timedOut() override { abort(); };
3896
key()3897 string key() override
3898 {
3899 /* "a$" ensures substitution goals happen before derivation
3900 goals. */
3901 return "a$" + storePathToName(storePath) + "$" + storePath;
3902 }
3903
3904 void work() override;
3905
3906 /* The states. */
3907 void init();
3908 void tryNext();
3909 void gotInfo();
3910 void referencesValid();
3911 void tryToRun();
3912 void finished();
3913
3914 /* Callback used by the worker to write to the log. */
3915 void handleChildOutput(int fd, const string & data) override;
3916 void handleEOF(int fd) override;
3917
getStorePath()3918 Path getStorePath() { return storePath; }
3919
amDone(ExitCode result)3920 void amDone(ExitCode result) override
3921 {
3922 Goal::amDone(result);
3923 }
3924 };
3925
3926
SubstitutionGoal(const Path & storePath,Worker & worker,RepairFlag repair)3927 SubstitutionGoal::SubstitutionGoal(const Path & storePath, Worker & worker, RepairFlag repair)
3928 : Goal(worker)
3929 , repair(repair)
3930 {
3931 this->storePath = storePath;
3932 state = &SubstitutionGoal::init;
3933 name = (format("substitution of '%1%'") % storePath).str();
3934 trace("created");
3935 maintainExpectedSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.expectedSubstitutions);
3936 }
3937
3938
~SubstitutionGoal()3939 SubstitutionGoal::~SubstitutionGoal()
3940 {
3941 try {
3942 if (thr.joinable()) {
3943 // FIXME: signal worker thread to quit.
3944 thr.join();
3945 worker.childTerminated(this);
3946 }
3947 } catch (...) {
3948 ignoreException();
3949 }
3950 }
3951
3952
work()3953 void SubstitutionGoal::work()
3954 {
3955 (this->*state)();
3956 }
3957
3958
init()3959 void SubstitutionGoal::init()
3960 {
3961 trace("init");
3962
3963 worker.store.addTempRoot(storePath);
3964
3965 /* If the path already exists we're done. */
3966 if (!repair && worker.store.isValidPath(storePath)) {
3967 amDone(ecSuccess);
3968 return;
3969 }
3970
3971 if (settings.readOnlyMode)
3972 throw Error(format("cannot substitute path '%1%' - no write access to the Nix store") % storePath);
3973
3974 subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
3975
3976 tryNext();
3977 }
3978
3979
tryNext()3980 void SubstitutionGoal::tryNext()
3981 {
3982 trace("trying next substituter");
3983
3984 if (subs.size() == 0) {
3985 /* None left. Terminate this goal and let someone else deal
3986 with it. */
3987 debug(format("path '%1%' is required, but there is no substituter that can build it") % storePath);
3988
3989 /* Hack: don't indicate failure if there were no substituters.
3990 In that case the calling derivation should just do a
3991 build. */
3992 amDone(substituterFailed ? ecFailed : ecNoSubstituters);
3993
3994 if (substituterFailed) {
3995 worker.failedSubstitutions++;
3996 worker.updateProgress();
3997 }
3998
3999 return;
4000 }
4001
4002 sub = subs.front();
4003 subs.pop_front();
4004
4005 if (sub->storeDir != worker.store.storeDir) {
4006 tryNext();
4007 return;
4008 }
4009
4010 try {
4011 // FIXME: make async
4012 info = sub->queryPathInfo(storePath);
4013 } catch (InvalidPath &) {
4014 tryNext();
4015 return;
4016 } catch (SubstituterDisabled &) {
4017 if (settings.tryFallback) {
4018 tryNext();
4019 return;
4020 }
4021 throw;
4022 } catch (Error & e) {
4023 if (settings.tryFallback) {
4024 printError(e.what());
4025 tryNext();
4026 return;
4027 }
4028 throw;
4029 }
4030
4031 /* Update the total expected download size. */
4032 auto narInfo = std::dynamic_pointer_cast<const NarInfo>(info);
4033
4034 maintainExpectedNar = std::make_unique<MaintainCount<uint64_t>>(worker.expectedNarSize, info->narSize);
4035
4036 maintainExpectedDownload =
4037 narInfo && narInfo->fileSize
4038 ? std::make_unique<MaintainCount<uint64_t>>(worker.expectedDownloadSize, narInfo->fileSize)
4039 : nullptr;
4040
4041 worker.updateProgress();
4042
4043 /* Bail out early if this substituter lacks a valid
4044 signature. LocalStore::addToStore() also checks for this, but
4045 only after we've downloaded the path. */
4046 if (worker.store.requireSigs
4047 && !sub->isTrusted
4048 && !info->checkSignatures(worker.store, worker.store.getPublicKeys()))
4049 {
4050 printError("warning: substituter '%s' does not have a valid signature for path '%s'",
4051 sub->getUri(), storePath);
4052 tryNext();
4053 return;
4054 }
4055
4056 /* To maintain the closure invariant, we first have to realise the
4057 paths referenced by this one. */
4058 for (auto & i : info->references)
4059 if (i != storePath) /* ignore self-references */
4060 addWaitee(worker.makeSubstitutionGoal(i));
4061
4062 if (waitees.empty()) /* to prevent hang (no wake-up event) */
4063 referencesValid();
4064 else
4065 state = &SubstitutionGoal::referencesValid;
4066 }
4067
4068
referencesValid()4069 void SubstitutionGoal::referencesValid()
4070 {
4071 trace("all references realised");
4072
4073 if (nrFailed > 0) {
4074 debug(format("some references of path '%1%' could not be realised") % storePath);
4075 amDone(nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed);
4076 return;
4077 }
4078
4079 for (auto & i : info->references)
4080 if (i != storePath) /* ignore self-references */
4081 assert(worker.store.isValidPath(i));
4082
4083 state = &SubstitutionGoal::tryToRun;
4084 worker.wakeUp(shared_from_this());
4085 }
4086
4087
tryToRun()4088 void SubstitutionGoal::tryToRun()
4089 {
4090 trace("trying to run");
4091
4092 /* Make sure that we are allowed to start a build. Note that even
4093 if maxBuildJobs == 0 (no local builds allowed), we still allow
4094 a substituter to run. This is because substitutions cannot be
4095 distributed to another machine via the build hook. */
4096 if (worker.getNrLocalBuilds() >= std::max(1U, (unsigned int) settings.maxBuildJobs)) {
4097 worker.waitForBuildSlot(shared_from_this());
4098 return;
4099 }
4100
4101 maintainRunningSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.runningSubstitutions);
4102 worker.updateProgress();
4103
4104 outPipe.create();
4105
4106 promise = std::promise<void>();
4107
4108 thr = std::thread([this]() {
4109 try {
4110 /* Wake up the worker loop when we're done. */
4111 Finally updateStats([this]() { outPipe.writeSide = -1; });
4112
4113 Activity act(*logger, actSubstitute, Logger::Fields{storePath, sub->getUri()});
4114 PushActivity pact(act.id);
4115
4116 copyStorePath(ref<Store>(sub), ref<Store>(worker.store.shared_from_this()),
4117 storePath, repair, sub->isTrusted ? NoCheckSigs : CheckSigs);
4118
4119 promise.set_value();
4120 } catch (...) {
4121 promise.set_exception(std::current_exception());
4122 }
4123 });
4124
4125 worker.childStarted(shared_from_this(), {outPipe.readSide.get()}, true, false);
4126
4127 state = &SubstitutionGoal::finished;
4128 }
4129
4130
finished()4131 void SubstitutionGoal::finished()
4132 {
4133 trace("substitute finished");
4134
4135 thr.join();
4136 worker.childTerminated(this);
4137
4138 try {
4139 promise.get_future().get();
4140 } catch (std::exception & e) {
4141 printError(e.what());
4142
4143 /* Cause the parent build to fail unless --fallback is given,
4144 or the substitute has disappeared. The latter case behaves
4145 the same as the substitute never having existed in the
4146 first place. */
4147 try {
4148 throw;
4149 } catch (SubstituteGone &) {
4150 } catch (...) {
4151 substituterFailed = true;
4152 }
4153
4154 /* Try the next substitute. */
4155 state = &SubstitutionGoal::tryNext;
4156 worker.wakeUp(shared_from_this());
4157 return;
4158 }
4159
4160 worker.markContentsGood(storePath);
4161
4162 printMsg(lvlChatty,
4163 format("substitution of path '%1%' succeeded") % storePath);
4164
4165 maintainRunningSubstitutions.reset();
4166
4167 maintainExpectedSubstitutions.reset();
4168 worker.doneSubstitutions++;
4169
4170 if (maintainExpectedDownload) {
4171 auto fileSize = maintainExpectedDownload->delta;
4172 maintainExpectedDownload.reset();
4173 worker.doneDownloadSize += fileSize;
4174 }
4175
4176 worker.doneNarSize += maintainExpectedNar->delta;
4177 maintainExpectedNar.reset();
4178
4179 worker.updateProgress();
4180
4181 amDone(ecSuccess);
4182 }
4183
4184
handleChildOutput(int fd,const string & data)4185 void SubstitutionGoal::handleChildOutput(int fd, const string & data)
4186 {
4187 }
4188
4189
handleEOF(int fd)4190 void SubstitutionGoal::handleEOF(int fd)
4191 {
4192 if (fd == outPipe.readSide.get()) worker.wakeUp(shared_from_this());
4193 }
4194
4195
4196 //////////////////////////////////////////////////////////////////////
4197
4198
4199 static bool working = false;
4200
4201
Worker(LocalStore & store)4202 Worker::Worker(LocalStore & store)
4203 : act(*logger, actRealise)
4204 , actDerivations(*logger, actBuilds)
4205 , actSubstitutions(*logger, actCopyPaths)
4206 , store(store)
4207 {
4208 /* Debugging: prevent recursive workers. */
4209 if (working) abort();
4210 working = true;
4211 nrLocalBuilds = 0;
4212 lastWokenUp = steady_time_point::min();
4213 permanentFailure = false;
4214 timedOut = false;
4215 hashMismatch = false;
4216 checkMismatch = false;
4217 }
4218
4219
~Worker()4220 Worker::~Worker()
4221 {
4222 working = false;
4223
4224 /* Explicitly get rid of all strong pointers now. After this all
4225 goals that refer to this worker should be gone. (Otherwise we
4226 are in trouble, since goals may call childTerminated() etc. in
4227 their destructors). */
4228 topGoals.clear();
4229
4230 assert(expectedSubstitutions == 0);
4231 assert(expectedDownloadSize == 0);
4232 assert(expectedNarSize == 0);
4233 }
4234
4235
makeDerivationGoal(const Path & path,const StringSet & wantedOutputs,BuildMode buildMode)4236 GoalPtr Worker::makeDerivationGoal(const Path & path,
4237 const StringSet & wantedOutputs, BuildMode buildMode)
4238 {
4239 GoalPtr goal = derivationGoals[path].lock();
4240 if (!goal) {
4241 goal = std::make_shared<DerivationGoal>(path, wantedOutputs, *this, buildMode);
4242 derivationGoals[path] = goal;
4243 wakeUp(goal);
4244 } else
4245 (dynamic_cast<DerivationGoal *>(goal.get()))->addWantedOutputs(wantedOutputs);
4246 return goal;
4247 }
4248
4249
makeBasicDerivationGoal(const Path & drvPath,const BasicDerivation & drv,BuildMode buildMode)4250 std::shared_ptr<DerivationGoal> Worker::makeBasicDerivationGoal(const Path & drvPath,
4251 const BasicDerivation & drv, BuildMode buildMode)
4252 {
4253 auto goal = std::make_shared<DerivationGoal>(drvPath, drv, *this, buildMode);
4254 wakeUp(goal);
4255 return goal;
4256 }
4257
4258
makeSubstitutionGoal(const Path & path,RepairFlag repair)4259 GoalPtr Worker::makeSubstitutionGoal(const Path & path, RepairFlag repair)
4260 {
4261 GoalPtr goal = substitutionGoals[path].lock();
4262 if (!goal) {
4263 goal = std::make_shared<SubstitutionGoal>(path, *this, repair);
4264 substitutionGoals[path] = goal;
4265 wakeUp(goal);
4266 }
4267 return goal;
4268 }
4269
4270
removeGoal(GoalPtr goal,WeakGoalMap & goalMap)4271 static void removeGoal(GoalPtr goal, WeakGoalMap & goalMap)
4272 {
4273 /* !!! inefficient */
4274 for (WeakGoalMap::iterator i = goalMap.begin();
4275 i != goalMap.end(); )
4276 if (i->second.lock() == goal) {
4277 WeakGoalMap::iterator j = i; ++j;
4278 goalMap.erase(i);
4279 i = j;
4280 }
4281 else ++i;
4282 }
4283
4284
removeGoal(GoalPtr goal)4285 void Worker::removeGoal(GoalPtr goal)
4286 {
4287 nix::removeGoal(goal, derivationGoals);
4288 nix::removeGoal(goal, substitutionGoals);
4289 if (topGoals.find(goal) != topGoals.end()) {
4290 topGoals.erase(goal);
4291 /* If a top-level goal failed, then kill all other goals
4292 (unless keepGoing was set). */
4293 if (goal->getExitCode() == Goal::ecFailed && !settings.keepGoing)
4294 topGoals.clear();
4295 }
4296
4297 /* Wake up goals waiting for any goal to finish. */
4298 for (auto & i : waitingForAnyGoal) {
4299 GoalPtr goal = i.lock();
4300 if (goal) wakeUp(goal);
4301 }
4302
4303 waitingForAnyGoal.clear();
4304 }
4305
4306
wakeUp(GoalPtr goal)4307 void Worker::wakeUp(GoalPtr goal)
4308 {
4309 goal->trace("woken up");
4310 addToWeakGoals(awake, goal);
4311 }
4312
4313
getNrLocalBuilds()4314 unsigned Worker::getNrLocalBuilds()
4315 {
4316 return nrLocalBuilds;
4317 }
4318
4319
childStarted(GoalPtr goal,const set<int> & fds,bool inBuildSlot,bool respectTimeouts)4320 void Worker::childStarted(GoalPtr goal, const set<int> & fds,
4321 bool inBuildSlot, bool respectTimeouts)
4322 {
4323 Child child;
4324 child.goal = goal;
4325 child.goal2 = goal.get();
4326 child.fds = fds;
4327 child.timeStarted = child.lastOutput = steady_time_point::clock::now();
4328 child.inBuildSlot = inBuildSlot;
4329 child.respectTimeouts = respectTimeouts;
4330 children.emplace_back(child);
4331 if (inBuildSlot) nrLocalBuilds++;
4332 }
4333
4334
childTerminated(Goal * goal,bool wakeSleepers)4335 void Worker::childTerminated(Goal * goal, bool wakeSleepers)
4336 {
4337 auto i = std::find_if(children.begin(), children.end(),
4338 [&](const Child & child) { return child.goal2 == goal; });
4339 if (i == children.end()) return;
4340
4341 if (i->inBuildSlot) {
4342 assert(nrLocalBuilds > 0);
4343 nrLocalBuilds--;
4344 }
4345
4346 children.erase(i);
4347
4348 if (wakeSleepers) {
4349
4350 /* Wake up goals waiting for a build slot. */
4351 for (auto & j : wantingToBuild) {
4352 GoalPtr goal = j.lock();
4353 if (goal) wakeUp(goal);
4354 }
4355
4356 wantingToBuild.clear();
4357 }
4358 }
4359
4360
waitForBuildSlot(GoalPtr goal)4361 void Worker::waitForBuildSlot(GoalPtr goal)
4362 {
4363 debug("wait for build slot");
4364 if (getNrLocalBuilds() < settings.maxBuildJobs)
4365 wakeUp(goal); /* we can do it right away */
4366 else
4367 addToWeakGoals(wantingToBuild, goal);
4368 }
4369
4370
waitForAnyGoal(GoalPtr goal)4371 void Worker::waitForAnyGoal(GoalPtr goal)
4372 {
4373 debug("wait for any goal");
4374 addToWeakGoals(waitingForAnyGoal, goal);
4375 }
4376
4377
waitForAWhile(GoalPtr goal)4378 void Worker::waitForAWhile(GoalPtr goal)
4379 {
4380 debug("wait for a while");
4381 addToWeakGoals(waitingForAWhile, goal);
4382 }
4383
4384
run(const Goals & _topGoals)4385 void Worker::run(const Goals & _topGoals)
4386 {
4387 for (auto & i : _topGoals) topGoals.insert(i);
4388
4389 debug("entered goal loop");
4390
4391 while (1) {
4392
4393 checkInterrupt();
4394
4395 store.autoGC(false);
4396
4397 /* Call every wake goal (in the ordering established by
4398 CompareGoalPtrs). */
4399 while (!awake.empty() && !topGoals.empty()) {
4400 Goals awake2;
4401 for (auto & i : awake) {
4402 GoalPtr goal = i.lock();
4403 if (goal) awake2.insert(goal);
4404 }
4405 awake.clear();
4406 for (auto & goal : awake2) {
4407 checkInterrupt();
4408 goal->work();
4409 if (topGoals.empty()) break; // stuff may have been cancelled
4410 }
4411 }
4412
4413 if (topGoals.empty()) break;
4414
4415 /* Wait for input. */
4416 if (!children.empty() || !waitingForAWhile.empty())
4417 waitForInput();
4418 else {
4419 if (awake.empty() && 0 == settings.maxBuildJobs) throw Error(
4420 "unable to start any build; either increase '--max-jobs' "
4421 "or enable remote builds");
4422 assert(!awake.empty());
4423 }
4424 }
4425
4426 /* If --keep-going is not set, it's possible that the main goal
4427 exited while some of its subgoals were still active. But if
4428 --keep-going *is* set, then they must all be finished now. */
4429 assert(!settings.keepGoing || awake.empty());
4430 assert(!settings.keepGoing || wantingToBuild.empty());
4431 assert(!settings.keepGoing || children.empty());
4432 }
4433
4434
waitForInput()4435 void Worker::waitForInput()
4436 {
4437 printMsg(lvlVomit, "waiting for children");
4438
4439 /* Process output from the file descriptors attached to the
4440 children, namely log output and output path creation commands.
4441 We also use this to detect child termination: if we get EOF on
4442 the logger pipe of a build, we assume that the builder has
4443 terminated. */
4444
4445 bool useTimeout = false;
4446 struct timeval timeout;
4447 timeout.tv_usec = 0;
4448 auto before = steady_time_point::clock::now();
4449
4450 /* If we're monitoring for silence on stdout/stderr, or if there
4451 is a build timeout, then wait for input until the first
4452 deadline for any child. */
4453 auto nearest = steady_time_point::max(); // nearest deadline
4454 if (settings.minFree.get() != 0)
4455 // Periodicallty wake up to see if we need to run the garbage collector.
4456 nearest = before + std::chrono::seconds(10);
4457 for (auto & i : children) {
4458 if (!i.respectTimeouts) continue;
4459 if (0 != settings.maxSilentTime)
4460 nearest = std::min(nearest, i.lastOutput + std::chrono::seconds(settings.maxSilentTime));
4461 if (0 != settings.buildTimeout)
4462 nearest = std::min(nearest, i.timeStarted + std::chrono::seconds(settings.buildTimeout));
4463 }
4464 if (nearest != steady_time_point::max()) {
4465 timeout.tv_sec = std::max(1L, (long) std::chrono::duration_cast<std::chrono::seconds>(nearest - before).count());
4466 useTimeout = true;
4467 }
4468
4469 /* If we are polling goals that are waiting for a lock, then wake
4470 up after a few seconds at most. */
4471 if (!waitingForAWhile.empty()) {
4472 useTimeout = true;
4473 if (lastWokenUp == steady_time_point::min())
4474 printError("waiting for locks or build slots...");
4475 if (lastWokenUp == steady_time_point::min() || lastWokenUp > before) lastWokenUp = before;
4476 timeout.tv_sec = std::max(1L,
4477 (long) std::chrono::duration_cast<std::chrono::seconds>(
4478 lastWokenUp + std::chrono::seconds(settings.pollInterval) - before).count());
4479 } else lastWokenUp = steady_time_point::min();
4480
4481 if (useTimeout)
4482 vomit("sleeping %d seconds", timeout.tv_sec);
4483
4484 /* Use select() to wait for the input side of any logger pipe to
4485 become `available'. Note that `available' (i.e., non-blocking)
4486 includes EOF. */
4487 fd_set fds;
4488 FD_ZERO(&fds);
4489 int fdMax = 0;
4490 for (auto & i : children) {
4491 for (auto & j : i.fds) {
4492 if (j >= FD_SETSIZE)
4493 throw Error("reached FD_SETSIZE limit");
4494 FD_SET(j, &fds);
4495 if (j >= fdMax) fdMax = j + 1;
4496 }
4497 }
4498
4499 if (select(fdMax, &fds, 0, 0, useTimeout ? &timeout : 0) == -1) {
4500 if (errno == EINTR) return;
4501 throw SysError("waiting for input");
4502 }
4503
4504 auto after = steady_time_point::clock::now();
4505
4506 /* Process all available file descriptors. FIXME: this is
4507 O(children * fds). */
4508 decltype(children)::iterator i;
4509 for (auto j = children.begin(); j != children.end(); j = i) {
4510 i = std::next(j);
4511
4512 checkInterrupt();
4513
4514 GoalPtr goal = j->goal.lock();
4515 assert(goal);
4516
4517 set<int> fds2(j->fds);
4518 std::vector<unsigned char> buffer(4096);
4519 for (auto & k : fds2) {
4520 if (FD_ISSET(k, &fds)) {
4521 ssize_t rd = read(k, buffer.data(), buffer.size());
4522 // FIXME: is there a cleaner way to handle pt close
4523 // than EIO? Is this even standard?
4524 if (rd == 0 || (rd == -1 && errno == EIO)) {
4525 debug(format("%1%: got EOF") % goal->getName());
4526 goal->handleEOF(k);
4527 j->fds.erase(k);
4528 } else if (rd == -1) {
4529 if (errno != EINTR)
4530 throw SysError("%s: read failed", goal->getName());
4531 } else {
4532 printMsg(lvlVomit, format("%1%: read %2% bytes")
4533 % goal->getName() % rd);
4534 string data((char *) buffer.data(), rd);
4535 j->lastOutput = after;
4536 goal->handleChildOutput(k, data);
4537 }
4538 }
4539 }
4540
4541 if (goal->getExitCode() == Goal::ecBusy &&
4542 0 != settings.maxSilentTime &&
4543 j->respectTimeouts &&
4544 after - j->lastOutput >= std::chrono::seconds(settings.maxSilentTime))
4545 {
4546 printError(
4547 format("%1% timed out after %2% seconds of silence")
4548 % goal->getName() % settings.maxSilentTime);
4549 goal->timedOut();
4550 }
4551
4552 else if (goal->getExitCode() == Goal::ecBusy &&
4553 0 != settings.buildTimeout &&
4554 j->respectTimeouts &&
4555 after - j->timeStarted >= std::chrono::seconds(settings.buildTimeout))
4556 {
4557 printError(
4558 format("%1% timed out after %2% seconds")
4559 % goal->getName() % settings.buildTimeout);
4560 goal->timedOut();
4561 }
4562 }
4563
4564 if (!waitingForAWhile.empty() && lastWokenUp + std::chrono::seconds(settings.pollInterval) <= after) {
4565 lastWokenUp = after;
4566 for (auto & i : waitingForAWhile) {
4567 GoalPtr goal = i.lock();
4568 if (goal) wakeUp(goal);
4569 }
4570 waitingForAWhile.clear();
4571 }
4572 }
4573
4574
exitStatus()4575 unsigned int Worker::exitStatus()
4576 {
4577 /*
4578 * 1100100
4579 * ^^^^
4580 * |||`- timeout
4581 * ||`-- output hash mismatch
4582 * |`--- build failure
4583 * `---- not deterministic
4584 */
4585 unsigned int mask = 0;
4586 bool buildFailure = permanentFailure || timedOut || hashMismatch;
4587 if (buildFailure)
4588 mask |= 0x04; // 100
4589 if (timedOut)
4590 mask |= 0x01; // 101
4591 if (hashMismatch)
4592 mask |= 0x02; // 102
4593 if (checkMismatch) {
4594 mask |= 0x08; // 104
4595 }
4596
4597 if (mask)
4598 mask |= 0x60;
4599 return mask ? mask : 1;
4600 }
4601
4602
pathContentsGood(const Path & path)4603 bool Worker::pathContentsGood(const Path & path)
4604 {
4605 std::map<Path, bool>::iterator i = pathContentsGoodCache.find(path);
4606 if (i != pathContentsGoodCache.end()) return i->second;
4607 printInfo(format("checking path '%1%'...") % path);
4608 auto info = store.queryPathInfo(path);
4609 bool res;
4610 if (!pathExists(path))
4611 res = false;
4612 else {
4613 HashResult current = hashPath(info->narHash.type, path);
4614 Hash nullHash(htSHA256);
4615 res = info->narHash == nullHash || info->narHash == current.first;
4616 }
4617 pathContentsGoodCache[path] = res;
4618 if (!res) printError(format("path '%1%' is corrupted or missing!") % path);
4619 return res;
4620 }
4621
4622
markContentsGood(const Path & path)4623 void Worker::markContentsGood(const Path & path)
4624 {
4625 pathContentsGoodCache[path] = true;
4626 }
4627
4628
4629 //////////////////////////////////////////////////////////////////////
4630
4631
primeCache(Store & store,const PathSet & paths)4632 static void primeCache(Store & store, const PathSet & paths)
4633 {
4634 PathSet willBuild, willSubstitute, unknown;
4635 unsigned long long downloadSize, narSize;
4636 store.queryMissing(paths, willBuild, willSubstitute, unknown, downloadSize, narSize);
4637
4638 if (!willBuild.empty() && 0 == settings.maxBuildJobs && getMachines().empty())
4639 throw Error(
4640 "%d derivations need to be built, but neither local builds ('--max-jobs') "
4641 "nor remote builds ('--builders') are enabled", willBuild.size());
4642 }
4643
4644
buildPaths(const PathSet & drvPaths,BuildMode buildMode)4645 void LocalStore::buildPaths(const PathSet & drvPaths, BuildMode buildMode)
4646 {
4647 Worker worker(*this);
4648
4649 primeCache(*this, drvPaths);
4650
4651 Goals goals;
4652 for (auto & i : drvPaths) {
4653 DrvPathWithOutputs i2 = parseDrvPathWithOutputs(i);
4654 if (isDerivation(i2.first))
4655 goals.insert(worker.makeDerivationGoal(i2.first, i2.second, buildMode));
4656 else
4657 goals.insert(worker.makeSubstitutionGoal(i, buildMode == bmRepair ? Repair : NoRepair));
4658 }
4659
4660 worker.run(goals);
4661
4662 PathSet failed;
4663 for (auto & i : goals) {
4664 if (i->getExitCode() != Goal::ecSuccess) {
4665 DerivationGoal * i2 = dynamic_cast<DerivationGoal *>(i.get());
4666 if (i2) failed.insert(i2->getDrvPath());
4667 else failed.insert(dynamic_cast<SubstitutionGoal *>(i.get())->getStorePath());
4668 }
4669 }
4670
4671 if (!failed.empty())
4672 throw Error(worker.exitStatus(), "build of %s failed", showPaths(failed));
4673 }
4674
4675
buildDerivation(const Path & drvPath,const BasicDerivation & drv,BuildMode buildMode)4676 BuildResult LocalStore::buildDerivation(const Path & drvPath, const BasicDerivation & drv,
4677 BuildMode buildMode)
4678 {
4679 Worker worker(*this);
4680 auto goal = worker.makeBasicDerivationGoal(drvPath, drv, buildMode);
4681
4682 BuildResult result;
4683
4684 try {
4685 worker.run(Goals{goal});
4686 result = goal->getResult();
4687 } catch (Error & e) {
4688 result.status = BuildResult::MiscFailure;
4689 result.errorMsg = e.msg();
4690 }
4691
4692 return result;
4693 }
4694
4695
ensurePath(const Path & path)4696 void LocalStore::ensurePath(const Path & path)
4697 {
4698 /* If the path is already valid, we're done. */
4699 if (isValidPath(path)) return;
4700
4701 primeCache(*this, {path});
4702
4703 Worker worker(*this);
4704 GoalPtr goal = worker.makeSubstitutionGoal(path);
4705 Goals goals = {goal};
4706
4707 worker.run(goals);
4708
4709 if (goal->getExitCode() != Goal::ecSuccess)
4710 throw Error(worker.exitStatus(), "path '%s' does not exist and cannot be created", path);
4711 }
4712
4713
repairPath(const Path & path)4714 void LocalStore::repairPath(const Path & path)
4715 {
4716 Worker worker(*this);
4717 GoalPtr goal = worker.makeSubstitutionGoal(path, Repair);
4718 Goals goals = {goal};
4719
4720 worker.run(goals);
4721
4722 if (goal->getExitCode() != Goal::ecSuccess) {
4723 /* Since substituting the path didn't work, if we have a valid
4724 deriver, then rebuild the deriver. */
4725 auto deriver = queryPathInfo(path)->deriver;
4726 if (deriver != "" && isValidPath(deriver)) {
4727 goals.clear();
4728 goals.insert(worker.makeDerivationGoal(deriver, StringSet(), bmRepair));
4729 worker.run(goals);
4730 } else
4731 throw Error(worker.exitStatus(), "cannot repair path '%s'", path);
4732 }
4733 }
4734
4735
4736 }
4737