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