aboutsummaryrefslogtreecommitdiff
path: root/src/libstore
diff options
context:
space:
mode:
authorJohn Ericson <John.Ericson@Obsidian.Systems>2020-10-12 20:47:22 +0000
committerJohn Ericson <John.Ericson@Obsidian.Systems>2020-10-12 20:47:22 +0000
commit1b8ebe92dc9c7d7db5d7001a84e85c5603056823 (patch)
tree0d252ad5a448982614c3bc2fbf9fd62c89ca3002 /src/libstore
parent5c74a6147b4b81dc5b173f190f02f6681ec4b0fe (diff)
parenta73959e6beef05e84c283a2117bbe7dc5801bade (diff)
Merge remote-tracking branch 'obsidian/split_build_cc' into typed-goal-maps
Diffstat (limited to 'src/libstore')
-rw-r--r--src/libstore/build.hh783
-rw-r--r--src/libstore/build/derivation-goal.cc27
-rw-r--r--src/libstore/build/derivation-goal.hh379
-rw-r--r--src/libstore/build/goal.cc3
-rw-r--r--src/libstore/build/goal.hh107
-rw-r--r--src/libstore/build/hook-instance.cc3
-rw-r--r--src/libstore/build/hook-instance.hh31
-rw-r--r--src/libstore/build/local-store-build.cc5
-rw-r--r--src/libstore/build/substitution-goal.cc3
-rw-r--r--src/libstore/build/substitution-goal.hh89
-rw-r--r--src/libstore/build/worker.cc11
-rw-r--r--src/libstore/build/worker.hh198
-rw-r--r--src/libstore/gc.cc1
-rw-r--r--src/libstore/local-fs-store.cc1
-rw-r--r--src/libstore/local-fs-store.hh48
-rw-r--r--src/libstore/local-store.hh1
-rw-r--r--src/libstore/local.mk5
-rw-r--r--src/libstore/profiles.cc1
-rw-r--r--src/libstore/remote-store.cc75
-rw-r--r--src/libstore/remote-store.hh44
-rw-r--r--src/libstore/store-api.cc2
-rw-r--r--src/libstore/store-api.hh41
-rw-r--r--src/libstore/uds-remote-store.cc81
-rw-r--r--src/libstore/uds-remote-store.hh52
24 files changed, 1035 insertions, 956 deletions
diff --git a/src/libstore/build.hh b/src/libstore/build.hh
deleted file mode 100644
index 894feab5b..000000000
--- a/src/libstore/build.hh
+++ /dev/null
@@ -1,783 +0,0 @@
-#pragma once
-
-#include "machines.hh"
-#include "parsed-derivations.hh"
-#include "lock.hh"
-#include "local-store.hh"
-
-#include <memory>
-#include <algorithm>
-#include <iostream>
-#include <map>
-#include <sstream>
-#include <thread>
-#include <future>
-#include <chrono>
-#include <regex>
-#include <queue>
-
-namespace nix {
-
-using std::map;
-
-
-/* Forward definition. */
-class Worker;
-struct HookInstance;
-
-
-/* A pointer to a goal. */
-struct Goal;
-typedef std::shared_ptr<Goal> GoalPtr;
-typedef std::weak_ptr<Goal> WeakGoalPtr;
-
-struct CompareGoalPtrs {
- bool operator() (const GoalPtr & a, const GoalPtr & b) const;
-};
-
-/* Set of goals. */
-typedef set<GoalPtr, CompareGoalPtrs> Goals;
-typedef list<WeakGoalPtr> WeakGoals;
-
-/* A map of paths to goals (and the other way around). */
-typedef std::map<StorePath, WeakGoalPtr> WeakGoalMap;
-
-
-
-struct Goal : public std::enable_shared_from_this<Goal>
-{
- typedef enum {ecBusy, ecSuccess, ecFailed, ecNoSubstituters, ecIncompleteClosure} ExitCode;
-
- /* Backlink to the worker. */
- Worker & worker;
-
- /* Goals that this goal is waiting for. */
- Goals waitees;
-
- /* Goals waiting for this one to finish. Must use weak pointers
- here to prevent cycles. */
- WeakGoals waiters;
-
- /* Number of goals we are/were waiting for that have failed. */
- unsigned int nrFailed;
-
- /* Number of substitution goals we are/were waiting for that
- failed because there are no substituters. */
- unsigned int nrNoSubstituters;
-
- /* Number of substitution goals we are/were waiting for that
- failed because othey had unsubstitutable references. */
- unsigned int nrIncompleteClosure;
-
- /* Name of this goal for debugging purposes. */
- string name;
-
- /* Whether the goal is finished. */
- ExitCode exitCode;
-
- /* Exception containing an error message, if any. */
- std::optional<Error> ex;
-
- Goal(Worker & worker) : worker(worker)
- {
- nrFailed = nrNoSubstituters = nrIncompleteClosure = 0;
- exitCode = ecBusy;
- }
-
- virtual ~Goal()
- {
- trace("goal destroyed");
- }
-
- virtual void work() = 0;
-
- void addWaitee(GoalPtr waitee);
-
- virtual void waiteeDone(GoalPtr waitee, ExitCode result);
-
- virtual void handleChildOutput(int fd, const string & data)
- {
- abort();
- }
-
- virtual void handleEOF(int fd)
- {
- abort();
- }
-
- void trace(const FormatOrString & fs);
-
- string getName()
- {
- return name;
- }
-
- /* Callback in case of a timeout. It should wake up its waiters,
- get rid of any running child processes that are being monitored
- by the worker (important!), etc. */
- virtual void timedOut(Error && ex) = 0;
-
- virtual string key() = 0;
-
- void amDone(ExitCode result, std::optional<Error> ex = {});
-};
-
-typedef std::chrono::time_point<std::chrono::steady_clock> steady_time_point;
-
-
-/* A mapping used to remember for each child process to what goal it
- belongs, and file descriptors for receiving log data and output
- path creation commands. */
-struct Child
-{
- WeakGoalPtr goal;
- Goal * goal2; // ugly hackery
- set<int> fds;
- bool respectTimeouts;
- bool inBuildSlot;
- steady_time_point lastOutput; /* time we last got output on stdout/stderr */
- steady_time_point timeStarted;
-};
-
-class DerivationGoal;
-class SubstitutionGoal;
-
-/* The worker class. */
-class Worker
-{
-private:
-
- /* Note: the worker should only have strong pointers to the
- top-level goals. */
-
- /* The top-level goals of the worker. */
- Goals topGoals;
-
- /* Goals that are ready to do some work. */
- WeakGoals awake;
-
- /* Goals waiting for a build slot. */
- WeakGoals wantingToBuild;
-
- /* Child processes currently running. */
- std::list<Child> children;
-
- /* Number of build slots occupied. This includes local builds and
- substitutions but not remote builds via the build hook. */
- unsigned int nrLocalBuilds;
-
- /* Maps used to prevent multiple instantiations of a goal for the
- same derivation / path. */
- std::map<StorePath, std::weak_ptr<DerivationGoal>> derivationGoals;
- std::map<StorePath, std::weak_ptr<SubstitutionGoal>> substitutionGoals;
-
- /* Goals waiting for busy paths to be unlocked. */
- WeakGoals waitingForAnyGoal;
-
- /* Goals sleeping for a few seconds (polling a lock). */
- WeakGoals waitingForAWhile;
-
- /* Last time the goals in `waitingForAWhile' where woken up. */
- steady_time_point lastWokenUp;
-
- /* Cache for pathContentsGood(). */
- std::map<StorePath, bool> pathContentsGoodCache;
-
-public:
-
- const Activity act;
- const Activity actDerivations;
- const Activity actSubstitutions;
-
- /* Set if at least one derivation had a BuildError (i.e. permanent
- failure). */
- bool permanentFailure;
-
- /* Set if at least one derivation had a timeout. */
- bool timedOut;
-
- /* Set if at least one derivation fails with a hash mismatch. */
- bool hashMismatch;
-
- /* Set if at least one derivation is not deterministic in check mode. */
- bool checkMismatch;
-
- LocalStore & store;
-
- std::unique_ptr<HookInstance> hook;
-
- uint64_t expectedBuilds = 0;
- uint64_t doneBuilds = 0;
- uint64_t failedBuilds = 0;
- uint64_t runningBuilds = 0;
-
- uint64_t expectedSubstitutions = 0;
- uint64_t doneSubstitutions = 0;
- uint64_t failedSubstitutions = 0;
- uint64_t runningSubstitutions = 0;
- uint64_t expectedDownloadSize = 0;
- uint64_t doneDownloadSize = 0;
- uint64_t expectedNarSize = 0;
- uint64_t doneNarSize = 0;
-
- /* Whether to ask the build hook if it can build a derivation. If
- it answers with "decline-permanently", we don't try again. */
- bool tryBuildHook = true;
-
- Worker(LocalStore & store);
- ~Worker();
-
- /* Make a goal (with caching). */
-
- /* derivation goal */
-private:
- std::shared_ptr<DerivationGoal> makeDerivationGoalCommon(
- const StorePath & drvPath, const StringSet & wantedOutputs,
- std::function<std::shared_ptr<DerivationGoal>()> mkDrvGoal);
-public:
- std::shared_ptr<DerivationGoal> makeDerivationGoal(
- const StorePath & drvPath,
- const StringSet & wantedOutputs, BuildMode buildMode = bmNormal);
- std::shared_ptr<DerivationGoal> makeBasicDerivationGoal(
- const StorePath & drvPath, const BasicDerivation & drv,
- const StringSet & wantedOutputs, BuildMode buildMode = bmNormal);
-
- /* substitution goal */
- std::shared_ptr<SubstitutionGoal> makeSubstitutionGoal(const StorePath & storePath, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
-
- /* Remove a dead goal. */
- void removeGoal(GoalPtr goal);
-
- /* Wake up a goal (i.e., there is something for it to do). */
- void wakeUp(GoalPtr goal);
-
- /* Return the number of local build and substitution processes
- currently running (but not remote builds via the build
- hook). */
- unsigned int getNrLocalBuilds();
-
- /* Registers a running child process. `inBuildSlot' means that
- the process counts towards the jobs limit. */
- void childStarted(GoalPtr goal, const set<int> & fds,
- bool inBuildSlot, bool respectTimeouts);
-
- /* Unregisters a running child process. `wakeSleepers' should be
- false if there is no sense in waking up goals that are sleeping
- because they can't run yet (e.g., there is no free build slot,
- or the hook would still say `postpone'). */
- void childTerminated(Goal * goal, bool wakeSleepers = true);
-
- /* Put `goal' to sleep until a build slot becomes available (which
- might be right away). */
- void waitForBuildSlot(GoalPtr goal);
-
- /* Wait for any goal to finish. Pretty indiscriminate way to
- wait for some resource that some other goal is holding. */
- void waitForAnyGoal(GoalPtr goal);
-
- /* Wait for a few seconds and then retry this goal. Used when
- waiting for a lock held by another process. This kind of
- polling is inefficient, but POSIX doesn't really provide a way
- to wait for multiple locks in the main select() loop. */
- void waitForAWhile(GoalPtr goal);
-
- /* Loop until the specified top-level goals have finished. */
- void run(const Goals & topGoals);
-
- /* Wait for input to become available. */
- void waitForInput();
-
- unsigned int exitStatus();
-
- /* Check whether the given valid path exists and has the right
- contents. */
- bool pathContentsGood(const StorePath & path);
-
- void markContentsGood(const StorePath & path);
-
- void updateProgress()
- {
- actDerivations.progress(doneBuilds, expectedBuilds + doneBuilds, runningBuilds, failedBuilds);
- actSubstitutions.progress(doneSubstitutions, expectedSubstitutions + doneSubstitutions, runningSubstitutions, failedSubstitutions);
- act.setExpected(actFileTransfer, expectedDownloadSize + doneDownloadSize);
- act.setExpected(actCopyPath, expectedNarSize + doneNarSize);
- }
-};
-
-typedef enum {rpAccept, rpDecline, rpPostpone} HookReply;
-
-/* Unless we are repairing, we don't both to test validity and just assume it,
- so the choices are `Absent` or `Valid`. */
-enum struct PathStatus {
- Corrupt,
- Absent,
- Valid,
-};
-
-struct InitialOutputStatus {
- StorePath path;
- PathStatus status;
- /* Valid in the store, and additionally non-corrupt if we are repairing */
- bool isValid() const {
- return status == PathStatus::Valid;
- }
- /* Merely present, allowed to be corrupt */
- bool isPresent() const {
- return status == PathStatus::Corrupt
- || status == PathStatus::Valid;
- }
-};
-
-struct InitialOutput {
- bool wanted;
- std::optional<InitialOutputStatus> known;
-};
-
-class DerivationGoal : public Goal
-{
-private:
- /* Whether to use an on-disk .drv file. */
- bool useDerivation;
-
- /* The path of the derivation. */
- StorePath drvPath;
-
- /* The specific outputs that we need to build. Empty means all of
- them. */
- StringSet wantedOutputs;
-
- /* Whether additional wanted outputs have been added. */
- bool needRestart = false;
-
- /* Whether to retry substituting the outputs after building the
- inputs. */
- bool retrySubstitution;
-
- /* The derivation stored at drvPath. */
- std::unique_ptr<BasicDerivation> drv;
-
- std::unique_ptr<ParsedDerivation> parsedDrv;
-
- /* The remainder is state held during the build. */
-
- /* Locks on (fixed) output paths. */
- PathLocks outputLocks;
-
- /* All input paths (that is, the union of FS closures of the
- immediate input paths). */
- StorePathSet inputPaths;
-
- std::map<std::string, InitialOutput> initialOutputs;
-
- /* User selected for running the builder. */
- std::unique_ptr<UserLock> buildUser;
-
- /* The process ID of the builder. */
- Pid pid;
-
- /* The temporary directory. */
- Path tmpDir;
-
- /* The path of the temporary directory in the sandbox. */
- Path tmpDirInSandbox;
-
- /* File descriptor for the log file. */
- AutoCloseFD fdLogFile;
- std::shared_ptr<BufferedSink> logFileSink, logSink;
-
- /* Number of bytes received from the builder's stdout/stderr. */
- unsigned long logSize;
-
- /* The most recent log lines. */
- std::list<std::string> logTail;
-
- std::string currentLogLine;
- size_t currentLogLinePos = 0; // to handle carriage return
-
- std::string currentHookLine;
-
- /* Pipe for the builder's standard output/error. */
- Pipe builderOut;
-
- /* Pipe for synchronising updates to the builder namespaces. */
- Pipe userNamespaceSync;
-
- /* The mount namespace of the builder, used to add additional
- paths to the sandbox as a result of recursive Nix calls. */
- AutoCloseFD sandboxMountNamespace;
-
- /* On Linux, whether we're doing the build in its own user
- namespace. */
- bool usingUserNamespace = true;
-
- /* The build hook. */
- std::unique_ptr<HookInstance> hook;
-
- /* Whether we're currently doing a chroot build. */
- bool useChroot = false;
-
- Path chrootRootDir;
-
- /* RAII object to delete the chroot directory. */
- std::shared_ptr<AutoDelete> autoDelChroot;
-
- /* The sort of derivation we are building. */
- DerivationType derivationType;
-
- /* Whether to run the build in a private network namespace. */
- bool privateNetwork = false;
-
- typedef void (DerivationGoal::*GoalState)();
- GoalState state;
-
- /* Stuff we need to pass to initChild(). */
- struct ChrootPath {
- Path source;
- bool optional;
- ChrootPath(Path source = "", bool optional = false)
- : source(source), optional(optional)
- { }
- };
- typedef map<Path, ChrootPath> DirsInChroot; // maps target path to source path
- DirsInChroot dirsInChroot;
-
- typedef map<string, string> Environment;
- Environment env;
-
-#if __APPLE__
- typedef string SandboxProfile;
- SandboxProfile additionalSandboxProfile;
-#endif
-
- /* Hash rewriting. */
- StringMap inputRewrites, outputRewrites;
- typedef map<StorePath, StorePath> RedirectedOutputs;
- RedirectedOutputs redirectedOutputs;
-
- /* The outputs paths used during the build.
-
- - Input-addressed derivations or fixed content-addressed outputs are
- sometimes built when some of their outputs already exist, and can not
- be hidden via sandboxing. We use temporary locations instead and
- rewrite after the build. Otherwise the regular predetermined paths are
- put here.
-
- - Floating content-addressed derivations do not know their final build
- output paths until the outputs are hashed, so random locations are
- used, and then renamed. The randomness helps guard against hidden
- self-references.
- */
- OutputPathMap scratchOutputs;
-
- /* The final output paths of the build.
-
- - For input-addressed derivations, always the precomputed paths
-
- - For content-addressed derivations, calcuated from whatever the hash
- ends up being. (Note that fixed outputs derivations that produce the
- "wrong" output still install that data under its true content-address.)
- */
- OutputPathMap finalOutputs;
-
- BuildMode buildMode;
-
- /* If we're repairing without a chroot, there may be outputs that
- are valid but corrupt. So we redirect these outputs to
- temporary paths. */
- StorePathSet redirectedBadOutputs;
-
- BuildResult result;
-
- /* The current round, if we're building multiple times. */
- size_t curRound = 1;
-
- size_t nrRounds;
-
- /* Path registration info from the previous round, if we're
- building multiple times. Since this contains the hash, it
- allows us to compare whether two rounds produced the same
- result. */
- std::map<Path, ValidPathInfo> prevInfos;
-
- uid_t sandboxUid() { return usingUserNamespace ? 1000 : buildUser->getUID(); }
- gid_t sandboxGid() { return usingUserNamespace ? 100 : buildUser->getGID(); }
-
- const static Path homeDir;
-
- std::unique_ptr<MaintainCount<uint64_t>> mcExpectedBuilds, mcRunningBuilds;
-
- std::unique_ptr<Activity> act;
-
- /* Activity that denotes waiting for a lock. */
- std::unique_ptr<Activity> actLock;
-
- std::map<ActivityId, Activity> builderActivities;
-
- /* The remote machine on which we're building. */
- std::string machineName;
-
- /* The recursive Nix daemon socket. */
- AutoCloseFD daemonSocket;
-
- /* The daemon main thread. */
- std::thread daemonThread;
-
- /* The daemon worker threads. */
- std::vector<std::thread> daemonWorkerThreads;
-
- /* Paths that were added via recursive Nix calls. */
- StorePathSet addedPaths;
-
- /* Recursive Nix calls are only allowed to build or realize paths
- in the original input closure or added via a recursive Nix call
- (so e.g. you can't do 'nix-store -r /nix/store/<bla>' where
- /nix/store/<bla> is some arbitrary path in a binary cache). */
- bool isAllowed(const StorePath & path)
- {
- return inputPaths.count(path) || addedPaths.count(path);
- }
-
- friend struct RestrictedStore;
-
-public:
- DerivationGoal(const StorePath & drvPath,
- const StringSet & wantedOutputs, Worker & worker,
- BuildMode buildMode = bmNormal);
- DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
- const StringSet & wantedOutputs, Worker & worker,
- BuildMode buildMode = bmNormal);
- ~DerivationGoal();
-
- /* Whether we need to perform hash rewriting if there are valid output paths. */
- bool needsHashRewrite();
-
- void timedOut(Error && ex) override;
-
- string key() override
- {
- /* Ensure that derivations get built in order of their name,
- i.e. a derivation named "aardvark" always comes before
- "baboon". And substitution goals always happen before
- derivation goals (due to "b$"). */
- return "b$" + std::string(drvPath.name()) + "$" + worker.store.printStorePath(drvPath);
- }
-
- void work() override;
-
- StorePath getDrvPath()
- {
- return drvPath;
- }
-
- /* Add wanted outputs to an already existing derivation goal. */
- void addWantedOutputs(const StringSet & outputs);
-
- BuildResult getResult() { return result; }
-
-private:
- /* The states. */
- void getDerivation();
- void loadDerivation();
- void haveDerivation();
- void outputsSubstitutionTried();
- void gaveUpOnSubstitution();
- void closureRepaired();
- void inputsRealised();
- void tryToBuild();
- void tryLocalBuild();
- void buildDone();
-
- void resolvedFinished();
-
- /* Is the build hook willing to perform the build? */
- HookReply tryBuildHook();
-
- /* Start building a derivation. */
- void startBuilder();
-
- /* Fill in the environment for the builder. */
- void initEnv();
-
- /* Setup tmp dir location. */
- void initTmpDir();
-
- /* Write a JSON file containing the derivation attributes. */
- void writeStructuredAttrs();
-
- void startDaemon();
-
- void stopDaemon();
-
- /* Add 'path' to the set of paths that may be referenced by the
- outputs, and make it appear in the sandbox. */
- void addDependency(const StorePath & path);
-
- /* Make a file owned by the builder. */
- void chownToBuilder(const Path & path);
-
- /* Run the builder's process. */
- void runChild();
-
- friend int childEntry(void *);
-
- /* Check that the derivation outputs all exist and register them
- as valid. */
- void registerOutputs();
-
- /* Check that an output meets the requirements specified by the
- 'outputChecks' attribute (or the legacy
- '{allowed,disallowed}{References,Requisites}' attributes). */
- void checkOutputs(const std::map<std::string, ValidPathInfo> & outputs);
-
- /* Open a log file and a pipe to it. */
- Path openLogFile();
-
- /* Close the log file. */
- void closeLogFile();
-
- /* Delete the temporary directory, if we have one. */
- void deleteTmpDir(bool force);
-
- /* Callback used by the worker to write to the log. */
- void handleChildOutput(int fd, const string & data) override;
- void handleEOF(int fd) override;
- void flushLine();
-
- /* Wrappers around the corresponding Store methods that first consult the
- derivation. This is currently needed because when there is no drv file
- there also is no DB entry. */
- std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap();
- OutputPathMap queryDerivationOutputMap();
-
- /* Return the set of (in)valid paths. */
- void checkPathValidity();
-
- /* Forcibly kill the child process, if any. */
- void killChild();
-
- /* Create alternative path calculated from but distinct from the
- input, so we can avoid overwriting outputs (or other store paths)
- that already exist. */
- StorePath makeFallbackPath(const StorePath & path);
- /* Make a path to another based on the output name along with the
- derivation hash. */
- /* FIXME add option to randomize, so we can audit whether our
- rewrites caught everything */
- StorePath makeFallbackPath(std::string_view outputName);
-
- void repairClosure();
-
- void started();
-
- void done(
- BuildResult::Status status,
- std::optional<Error> ex = {});
-
- StorePathSet exportReferences(const StorePathSet & storePaths);
-};
-
-class SubstitutionGoal : public Goal
-{
- friend class Worker;
-
-private:
- /* The store path that should be realised through a substitute. */
- StorePath storePath;
-
- /* The path the substituter refers to the path as. This will be
- * different when the stores have different names. */
- std::optional<StorePath> subPath;
-
- /* The remaining substituters. */
- std::list<ref<Store>> subs;
-
- /* The current substituter. */
- std::shared_ptr<Store> sub;
-
- /* Whether a substituter failed. */
- bool substituterFailed = false;
-
- /* Path info returned by the substituter's query info operation. */
- std::shared_ptr<const ValidPathInfo> info;
-
- /* Pipe for the substituter's standard output. */
- Pipe outPipe;
-
- /* The substituter thread. */
- std::thread thr;
-
- std::promise<void> promise;
-
- /* Whether to try to repair a valid path. */
- RepairFlag repair;
-
- /* Location where we're downloading the substitute. Differs from
- storePath when doing a repair. */
- Path destPath;
-
- std::unique_ptr<MaintainCount<uint64_t>> maintainExpectedSubstitutions,
- maintainRunningSubstitutions, maintainExpectedNar, maintainExpectedDownload;
-
- typedef void (SubstitutionGoal::*GoalState)();
- GoalState state;
-
- /* Content address for recomputing store path */
- std::optional<ContentAddress> ca;
-
-public:
- SubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
- ~SubstitutionGoal();
-
- void timedOut(Error && ex) override { abort(); };
-
- string key() override
- {
- /* "a$" ensures substitution goals happen before derivation
- goals. */
- return "a$" + std::string(storePath.name()) + "$" + worker.store.printStorePath(storePath);
- }
-
- void work() override;
-
- /* The states. */
- void init();
- void tryNext();
- void gotInfo();
- void referencesValid();
- void tryToRun();
- void finished();
-
- /* Callback used by the worker to write to the log. */
- void handleChildOutput(int fd, const string & data) override;
- void handleEOF(int fd) override;
-
- StorePath getStorePath() { return storePath; }
-};
-
-struct HookInstance
-{
- /* Pipes for talking to the build hook. */
- Pipe toHook;
-
- /* Pipe for the hook's standard output/error. */
- Pipe fromHook;
-
- /* Pipe for the builder's standard output/error. */
- Pipe builderOut;
-
- /* The process ID of the hook. */
- Pid pid;
-
- FdSink sink;
-
- std::map<ActivityId, Activity> activities;
-
- HookInstance();
-
- ~HookInstance();
-};
-
-
-void addToWeakGoals(WeakGoals & goals, GoalPtr p);
-
-}
diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc
index bb8921759..0834e578d 100644
--- a/src/libstore/build/derivation-goal.cc
+++ b/src/libstore/build/derivation-goal.cc
@@ -1,4 +1,6 @@
-#include "build.hh"
+#include "derivation-goal.hh"
+#include "hook-instance.hh"
+#include "worker.hh"
#include "builtins.hh"
#include "builtins/buildenv.hh"
#include "references.hh"
@@ -12,6 +14,9 @@
#include "topo-sort.hh"
#include "callback.hh"
+#include <regex>
+#include <queue>
+
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
@@ -140,6 +145,16 @@ DerivationGoal::~DerivationGoal()
}
+string DerivationGoal::key()
+{
+ /* Ensure that derivations get built in order of their name,
+ i.e. a derivation named "aardvark" always comes before
+ "baboon". And substitution goals always happen before
+ derivation goals (due to "b$"). */
+ return "b$" + std::string(drvPath.name()) + "$" + worker.store.printStorePath(drvPath);
+}
+
+
inline bool DerivationGoal::needsHashRewrite()
{
#if __linux__
@@ -216,7 +231,7 @@ void DerivationGoal::getDerivation()
return;
}
- addWaitee(worker.makeSubstitutionGoal(drvPath));
+ addWaitee(upcast_goal(worker.makeSubstitutionGoal(drvPath)));
state = &DerivationGoal::loadDerivation;
}
@@ -289,10 +304,10 @@ void DerivationGoal::haveDerivation()
/* Nothing to wait for; tail call */
return DerivationGoal::gaveUpOnSubstitution();
}
- addWaitee(worker.makeSubstitutionGoal(
+ addWaitee(upcast_goal(worker.makeSubstitutionGoal(
status.known->path,
buildMode == bmRepair ? Repair : NoRepair,
- getDerivationCA(*drv)));
+ getDerivationCA(*drv))));
}
if (waitees.empty()) /* to prevent hang (no wake-up event) */
@@ -368,7 +383,7 @@ void DerivationGoal::gaveUpOnSubstitution()
if (!settings.useSubstitutes)
throw Error("dependency '%s' of '%s' does not exist, and substitution is disabled",
worker.store.printStorePath(i), worker.store.printStorePath(drvPath));
- addWaitee(worker.makeSubstitutionGoal(i));
+ addWaitee(upcast_goal(worker.makeSubstitutionGoal(i)));
}
if (waitees.empty()) /* to prevent hang (no wake-up event) */
@@ -422,7 +437,7 @@ void DerivationGoal::repairClosure()
});
auto drvPath2 = outputsToDrv.find(i);
if (drvPath2 == outputsToDrv.end())
- addWaitee(worker.makeSubstitutionGoal(i, Repair));
+ addWaitee(upcast_goal(worker.makeSubstitutionGoal(i, Repair)));
else
addWaitee(worker.makeDerivationGoal(drvPath2->second, StringSet(), bmRepair));
}
diff --git a/src/libstore/build/derivation-goal.hh b/src/libstore/build/derivation-goal.hh
new file mode 100644
index 000000000..4976207e0
--- /dev/null
+++ b/src/libstore/build/derivation-goal.hh
@@ -0,0 +1,379 @@
+#pragma once
+
+#include "parsed-derivations.hh"
+#include "lock.hh"
+#include "local-store.hh"
+#include "goal.hh"
+
+namespace nix {
+
+using std::map;
+
+struct HookInstance;
+
+typedef enum {rpAccept, rpDecline, rpPostpone} HookReply;
+
+/* Unless we are repairing, we don't both to test validity and just assume it,
+ so the choices are `Absent` or `Valid`. */
+enum struct PathStatus {
+ Corrupt,
+ Absent,
+ Valid,
+};
+
+struct InitialOutputStatus {
+ StorePath path;
+ PathStatus status;
+ /* Valid in the store, and additionally non-corrupt if we are repairing */
+ bool isValid() const {
+ return status == PathStatus::Valid;
+ }
+ /* Merely present, allowed to be corrupt */
+ bool isPresent() const {
+ return status == PathStatus::Corrupt
+ || status == PathStatus::Valid;
+ }
+};
+
+struct InitialOutput {
+ bool wanted;
+ std::optional<InitialOutputStatus> known;
+};
+
+class DerivationGoal : public Goal
+{
+private:
+ /* Whether to use an on-disk .drv file. */
+ bool useDerivation;
+
+ /* The path of the derivation. */
+ StorePath drvPath;
+
+ /* The specific outputs that we need to build. Empty means all of
+ them. */
+ StringSet wantedOutputs;
+
+ /* Whether additional wanted outputs have been added. */
+ bool needRestart = false;
+
+ /* Whether to retry substituting the outputs after building the
+ inputs. */
+ bool retrySubstitution;
+
+ /* The derivation stored at drvPath. */
+ std::unique_ptr<BasicDerivation> drv;
+
+ std::unique_ptr<ParsedDerivation> parsedDrv;
+
+ /* The remainder is state held during the build. */
+
+ /* Locks on (fixed) output paths. */
+ PathLocks outputLocks;
+
+ /* All input paths (that is, the union of FS closures of the
+ immediate input paths). */
+ StorePathSet inputPaths;
+
+ std::map<std::string, InitialOutput> initialOutputs;
+
+ /* User selected for running the builder. */
+ std::unique_ptr<UserLock> buildUser;
+
+ /* The process ID of the builder. */
+ Pid pid;
+
+ /* The temporary directory. */
+ Path tmpDir;
+
+ /* The path of the temporary directory in the sandbox. */
+ Path tmpDirInSandbox;
+
+ /* File descriptor for the log file. */
+ AutoCloseFD fdLogFile;
+ std::shared_ptr<BufferedSink> logFileSink, logSink;
+
+ /* Number of bytes received from the builder's stdout/stderr. */
+ unsigned long logSize;
+
+ /* The most recent log lines. */
+ std::list<std::string> logTail;
+
+ std::string currentLogLine;
+ size_t currentLogLinePos = 0; // to handle carriage return
+
+ std::string currentHookLine;
+
+ /* Pipe for the builder's standard output/error. */
+ Pipe builderOut;
+
+ /* Pipe for synchronising updates to the builder namespaces. */
+ Pipe userNamespaceSync;
+
+ /* The mount namespace of the builder, used to add additional
+ paths to the sandbox as a result of recursive Nix calls. */
+ AutoCloseFD sandboxMountNamespace;
+
+ /* On Linux, whether we're doing the build in its own user
+ namespace. */
+ bool usingUserNamespace = true;
+
+ /* The build hook. */
+ std::unique_ptr<HookInstance> hook;
+
+ /* Whether we're currently doing a chroot build. */
+ bool useChroot = false;
+
+ Path chrootRootDir;
+
+ /* RAII object to delete the chroot directory. */
+ std::shared_ptr<AutoDelete> autoDelChroot;
+
+ /* The sort of derivation we are building. */
+ DerivationType derivationType;
+
+ /* Whether to run the build in a private network namespace. */
+ bool privateNetwork = false;
+
+ typedef void (DerivationGoal::*GoalState)();
+ GoalState state;
+
+ /* Stuff we need to pass to initChild(). */
+ struct ChrootPath {
+ Path source;
+ bool optional;
+ ChrootPath(Path source = "", bool optional = false)
+ : source(source), optional(optional)
+ { }
+ };
+ typedef map<Path, ChrootPath> DirsInChroot; // maps target path to source path
+ DirsInChroot dirsInChroot;
+
+ typedef map<string, string> Environment;
+ Environment env;
+
+#if __APPLE__
+ typedef string SandboxProfile;
+ SandboxProfile additionalSandboxProfile;
+#endif
+
+ /* Hash rewriting. */
+ StringMap inputRewrites, outputRewrites;
+ typedef map<StorePath, StorePath> RedirectedOutputs;
+ RedirectedOutputs redirectedOutputs;
+
+ /* The outputs paths used during the build.
+
+ - Input-addressed derivations or fixed content-addressed outputs are
+ sometimes built when some of their outputs already exist, and can not
+ be hidden via sandboxing. We use temporary locations instead and
+ rewrite after the build. Otherwise the regular predetermined paths are
+ put here.
+
+ - Floating content-addressed derivations do not know their final build
+ output paths until the outputs are hashed, so random locations are
+ used, and then renamed. The randomness helps guard against hidden
+ self-references.
+ */
+ OutputPathMap scratchOutputs;
+
+ /* The final output paths of the build.
+
+ - For input-addressed derivations, always the precomputed paths
+
+ - For content-addressed derivations, calcuated from whatever the hash
+ ends up being. (Note that fixed outputs derivations that produce the
+ "wrong" output still install that data under its true content-address.)
+ */
+ OutputPathMap finalOutputs;
+
+ BuildMode buildMode;
+
+ /* If we're repairing without a chroot, there may be outputs that
+ are valid but corrupt. So we redirect these outputs to
+ temporary paths. */
+ StorePathSet redirectedBadOutputs;
+
+ BuildResult result;
+
+ /* The current round, if we're building multiple times. */
+ size_t curRound = 1;
+
+ size_t nrRounds;
+
+ /* Path registration info from the previous round, if we're
+ building multiple times. Since this contains the hash, it
+ allows us to compare whether two rounds produced the same
+ result. */
+ std::map<Path, ValidPathInfo> prevInfos;
+
+ uid_t sandboxUid() { return usingUserNamespace ? 1000 : buildUser->getUID(); }
+ gid_t sandboxGid() { return usingUserNamespace ? 100 : buildUser->getGID(); }
+
+ const static Path homeDir;
+
+ std::unique_ptr<MaintainCount<uint64_t>> mcExpectedBuilds, mcRunningBuilds;
+
+ std::unique_ptr<Activity> act;
+
+ /* Activity that denotes waiting for a lock. */
+ std::unique_ptr<Activity> actLock;
+
+ std::map<ActivityId, Activity> builderActivities;
+
+ /* The remote machine on which we're building. */
+ std::string machineName;
+
+ /* The recursive Nix daemon socket. */
+ AutoCloseFD daemonSocket;
+
+ /* The daemon main thread. */
+ std::thread daemonThread;
+
+ /* The daemon worker threads. */
+ std::vector<std::thread> daemonWorkerThreads;
+
+ /* Paths that were added via recursive Nix calls. */
+ StorePathSet addedPaths;
+
+ /* Recursive Nix calls are only allowed to build or realize paths
+ in the original input closure or added via a recursive Nix call
+ (so e.g. you can't do 'nix-store -r /nix/store/<bla>' where
+ /nix/store/<bla> is some arbitrary path in a binary cache). */
+ bool isAllowed(const StorePath & path)
+ {
+ return inputPaths.count(path) || addedPaths.count(path);
+ }
+
+ friend struct RestrictedStore;
+
+public:
+ DerivationGoal(const StorePath & drvPath,
+ const StringSet & wantedOutputs, Worker & worker,
+ BuildMode buildMode = bmNormal);
+ DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
+ const StringSet & wantedOutputs, Worker & worker,
+ BuildMode buildMode = bmNormal);
+ ~DerivationGoal();
+
+ /* Whether we need to perform hash rewriting if there are valid output paths. */
+ bool needsHashRewrite();
+
+ void timedOut(Error && ex) override;
+
+ string key() override;
+
+ void work() override;
+
+ StorePath getDrvPath()
+ {
+ return drvPath;
+ }
+
+ /* Add wanted outputs to an already existing derivation goal. */
+ void addWantedOutputs(const StringSet & outputs);
+
+ BuildResult getResult() { return result; }
+
+private:
+ /* The states. */
+ void getDerivation();
+ void loadDerivation();
+ void haveDerivation();
+ void outputsSubstitutionTried();
+ void gaveUpOnSubstitution();
+ void closureRepaired();
+ void inputsRealised();
+ void tryToBuild();
+ void tryLocalBuild();
+ void buildDone();
+
+ void resolvedFinished();
+
+ /* Is the build hook willing to perform the build? */
+ HookReply tryBuildHook();
+
+ /* Start building a derivation. */
+ void startBuilder();
+
+ /* Fill in the environment for the builder. */
+ void initEnv();
+
+ /* Setup tmp dir location. */
+ void initTmpDir();
+
+ /* Write a JSON file containing the derivation attributes. */
+ void writeStructuredAttrs();
+
+ void startDaemon();
+
+ void stopDaemon();
+
+ /* Add 'path' to the set of paths that may be referenced by the
+ outputs, and make it appear in the sandbox. */
+ void addDependency(const StorePath & path);
+
+ /* Make a file owned by the builder. */
+ void chownToBuilder(const Path & path);
+
+ /* Run the builder's process. */
+ void runChild();
+
+ friend int childEntry(void *);
+
+ /* Check that the derivation outputs all exist and register them
+ as valid. */
+ void registerOutputs();
+
+ /* Check that an output meets the requirements specified by the
+ 'outputChecks' attribute (or the legacy
+ '{allowed,disallowed}{References,Requisites}' attributes). */
+ void checkOutputs(const std::map<std::string, ValidPathInfo> & outputs);
+
+ /* Open a log file and a pipe to it. */
+ Path openLogFile();
+
+ /* Close the log file. */
+ void closeLogFile();
+
+ /* Delete the temporary directory, if we have one. */
+ void deleteTmpDir(bool force);
+
+ /* Callback used by the worker to write to the log. */
+ void handleChildOutput(int fd, const string & data) override;
+ void handleEOF(int fd) override;
+ void flushLine();
+
+ /* Wrappers around the corresponding Store methods that first consult the
+ derivation. This is currently needed because when there is no drv file
+ there also is no DB entry. */
+ std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap();
+ OutputPathMap queryDerivationOutputMap();
+
+ /* Return the set of (in)valid paths. */
+ void checkPathValidity();
+
+ /* Forcibly kill the child process, if any. */
+ void killChild();
+
+ /* Create alternative path calculated from but distinct from the
+ input, so we can avoid overwriting outputs (or other store paths)
+ that already exist. */
+ StorePath makeFallbackPath(const StorePath & path);
+ /* Make a path to another based on the output name along with the
+ derivation hash. */
+ /* FIXME add option to randomize, so we can audit whether our
+ rewrites caught everything */
+ StorePath makeFallbackPath(std::string_view outputName);
+
+ void repairClosure();
+
+ void started();
+
+ void done(
+ BuildResult::Status status,
+ std::optional<Error> ex = {});
+
+ StorePathSet exportReferences(const StorePathSet & storePaths);
+};
+
+}
diff --git a/src/libstore/build/goal.cc b/src/libstore/build/goal.cc
index 404a430f7..2dd7a4d37 100644
--- a/src/libstore/build/goal.cc
+++ b/src/libstore/build/goal.cc
@@ -1,4 +1,5 @@
-#include "build.hh"
+#include "goal.hh"
+#include "worker.hh"
namespace nix {
diff --git a/src/libstore/build/goal.hh b/src/libstore/build/goal.hh
new file mode 100644
index 000000000..360c160ce
--- /dev/null
+++ b/src/libstore/build/goal.hh
@@ -0,0 +1,107 @@
+#pragma once
+
+#include "types.hh"
+#include "store-api.hh"
+
+namespace nix {
+
+/* Forward definition. */
+struct Goal;
+struct Worker;
+
+/* A pointer to a goal. */
+typedef std::shared_ptr<Goal> GoalPtr;
+typedef std::weak_ptr<Goal> WeakGoalPtr;
+
+struct CompareGoalPtrs {
+ bool operator() (const GoalPtr & a, const GoalPtr & b) const;
+};
+
+/* Set of goals. */
+typedef set<GoalPtr, CompareGoalPtrs> Goals;
+typedef list<WeakGoalPtr> WeakGoals;
+
+/* A map of paths to goals (and the other way around). */
+typedef std::map<StorePath, WeakGoalPtr> WeakGoalMap;
+
+struct Goal : public std::enable_shared_from_this<Goal>
+{
+ typedef enum {ecBusy, ecSuccess, ecFailed, ecNoSubstituters, ecIncompleteClosure} ExitCode;
+
+ /* Backlink to the worker. */
+ Worker & worker;
+
+ /* Goals that this goal is waiting for. */
+ Goals waitees;
+
+ /* Goals waiting for this one to finish. Must use weak pointers
+ here to prevent cycles. */
+ WeakGoals waiters;
+
+ /* Number of goals we are/were waiting for that have failed. */
+ unsigned int nrFailed;
+
+ /* Number of substitution goals we are/were waiting for that
+ failed because there are no substituters. */
+ unsigned int nrNoSubstituters;
+
+ /* Number of substitution goals we are/were waiting for that
+ failed because othey had unsubstitutable references. */
+ unsigned int nrIncompleteClosure;
+
+ /* Name of this goal for debugging purposes. */
+ string name;
+
+ /* Whether the goal is finished. */
+ ExitCode exitCode;
+
+ /* Exception containing an error message, if any. */
+ std::optional<Error> ex;
+
+ Goal(Worker & worker) : worker(worker)
+ {
+ nrFailed = nrNoSubstituters = nrIncompleteClosure = 0;
+ exitCode = ecBusy;
+ }
+
+ virtual ~Goal()
+ {
+ trace("goal destroyed");
+ }
+
+ virtual void work() = 0;
+
+ void addWaitee(GoalPtr waitee);
+
+ virtual void waiteeDone(GoalPtr waitee, ExitCode result);
+
+ virtual void handleChildOutput(int fd, const string & data)
+ {
+ abort();
+ }
+
+ virtual void handleEOF(int fd)
+ {
+ abort();
+ }
+
+ void trace(const FormatOrString & fs);
+
+ string getName()
+ {
+ return name;
+ }
+
+ /* Callback in case of a timeout. It should wake up its waiters,
+ get rid of any running child processes that are being monitored
+ by the worker (important!), etc. */
+ virtual void timedOut(Error && ex) = 0;
+
+ virtual string key() = 0;
+
+ void amDone(ExitCode result, std::optional<Error> ex = {});
+};
+
+void addToWeakGoals(WeakGoals & goals, GoalPtr p);
+
+}
diff --git a/src/libstore/build/hook-instance.cc b/src/libstore/build/hook-instance.cc
index a33d5e22b..0f6f580be 100644
--- a/src/libstore/build/hook-instance.cc
+++ b/src/libstore/build/hook-instance.cc
@@ -1,4 +1,5 @@
-#include "build.hh"
+#include "globals.hh"
+#include "hook-instance.hh"
namespace nix {
diff --git a/src/libstore/build/hook-instance.hh b/src/libstore/build/hook-instance.hh
new file mode 100644
index 000000000..9e8cff128
--- /dev/null
+++ b/src/libstore/build/hook-instance.hh
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "logging.hh"
+#include "serialise.hh"
+
+namespace nix {
+
+struct HookInstance
+{
+ /* Pipes for talking to the build hook. */
+ Pipe toHook;
+
+ /* Pipe for the hook's standard output/error. */
+ Pipe fromHook;
+
+ /* Pipe for the builder's standard output/error. */
+ Pipe builderOut;
+
+ /* The process ID of the hook. */
+ Pid pid;
+
+ FdSink sink;
+
+ std::map<ActivityId, Activity> activities;
+
+ HookInstance();
+
+ ~HookInstance();
+};
+
+}
diff --git a/src/libstore/build/local-store-build.cc b/src/libstore/build/local-store-build.cc
index 407979b1e..a05fb5805 100644
--- a/src/libstore/build/local-store-build.cc
+++ b/src/libstore/build/local-store-build.cc
@@ -1,4 +1,7 @@
-#include "build.hh"
+#include "machines.hh"
+#include "worker.hh"
+#include "substitution-goal.hh"
+#include "derivation-goal.hh"
namespace nix {
diff --git a/src/libstore/build/substitution-goal.cc b/src/libstore/build/substitution-goal.cc
index dd2ce94a6..d16584f65 100644
--- a/src/libstore/build/substitution-goal.cc
+++ b/src/libstore/build/substitution-goal.cc
@@ -1,4 +1,5 @@
-#include "build.hh"
+#include "worker.hh"
+#include "substitution-goal.hh"
#include "nar-info.hh"
#include "finally.hh"
diff --git a/src/libstore/build/substitution-goal.hh b/src/libstore/build/substitution-goal.hh
new file mode 100644
index 000000000..3ae9a9e6b
--- /dev/null
+++ b/src/libstore/build/substitution-goal.hh
@@ -0,0 +1,89 @@
+#pragma once
+
+#include "lock.hh"
+#include "store-api.hh"
+#include "goal.hh"
+
+namespace nix {
+
+class Worker;
+
+class SubstitutionGoal : public Goal
+{
+ friend class Worker;
+
+private:
+ /* The store path that should be realised through a substitute. */
+ StorePath storePath;
+
+ /* The path the substituter refers to the path as. This will be
+ * different when the stores have different names. */
+ std::optional<StorePath> subPath;
+
+ /* The remaining substituters. */
+ std::list<ref<Store>> subs;
+
+ /* The current substituter. */
+ std::shared_ptr<Store> sub;
+
+ /* Whether a substituter failed. */
+ bool substituterFailed = false;
+
+ /* Path info returned by the substituter's query info operation. */
+ std::shared_ptr<const ValidPathInfo> info;
+
+ /* Pipe for the substituter's standard output. */
+ Pipe outPipe;
+
+ /* The substituter thread. */
+ std::thread thr;
+
+ std::promise<void> promise;
+
+ /* Whether to try to repair a valid path. */
+ RepairFlag repair;
+
+ /* Location where we're downloading the substitute. Differs from
+ storePath when doing a repair. */
+ Path destPath;
+
+ std::unique_ptr<MaintainCount<uint64_t>> maintainExpectedSubstitutions,
+ maintainRunningSubstitutions, maintainExpectedNar, maintainExpectedDownload;
+
+ typedef void (SubstitutionGoal::*GoalState)();
+ GoalState state;
+
+ /* Content address for recomputing store path */
+ std::optional<ContentAddress> ca;
+
+public:
+ SubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
+ ~SubstitutionGoal();
+
+ void timedOut(Error && ex) override { abort(); };
+
+ string key() override
+ {
+ /* "a$" ensures substitution goals happen before derivation
+ goals. */
+ return "a$" + std::string(storePath.name()) + "$" + worker.store.printStorePath(storePath);
+ }
+
+ void work() override;
+
+ /* The states. */
+ void init();
+ void tryNext();
+ void gotInfo();
+ void referencesValid();
+ void tryToRun();
+ void finished();
+
+ /* Callback used by the worker to write to the log. */
+ void handleChildOutput(int fd, const string & data) override;
+ void handleEOF(int fd) override;
+
+ StorePath getStorePath() { return storePath; }
+};
+
+}
diff --git a/src/libstore/build/worker.cc b/src/libstore/build/worker.cc
index 47403580e..97ffb9a34 100644
--- a/src/libstore/build/worker.cc
+++ b/src/libstore/build/worker.cc
@@ -1,4 +1,8 @@
-#include "build.hh"
+#include "machines.hh"
+#include "worker.hh"
+#include "substitution-goal.hh"
+#include "derivation-goal.hh"
+#include "hook-instance.hh"
#include <poll.h>
@@ -449,4 +453,9 @@ void Worker::markContentsGood(const StorePath & path)
pathContentsGoodCache.insert_or_assign(path, true);
}
+
+GoalPtr upcast_goal(std::shared_ptr<SubstitutionGoal> subGoal) {
+ return subGoal;
+}
+
}
diff --git a/src/libstore/build/worker.hh b/src/libstore/build/worker.hh
new file mode 100644
index 000000000..07c0c0f16
--- /dev/null
+++ b/src/libstore/build/worker.hh
@@ -0,0 +1,198 @@
+#pragma once
+
+#include "types.hh"
+#include "lock.hh"
+#include "local-store.hh"
+#include "goal.hh"
+
+namespace nix {
+
+/* Forward definition. */
+class DerivationGoal;
+class SubstitutionGoal;
+
+GoalPtr upcast_goal(std::shared_ptr<SubstitutionGoal> subGoal);
+
+typedef std::chrono::time_point<std::chrono::steady_clock> steady_time_point;
+
+
+/* A mapping used to remember for each child process to what goal it
+ belongs, and file descriptors for receiving log data and output
+ path creation commands. */
+struct Child
+{
+ WeakGoalPtr goal;
+ Goal * goal2; // ugly hackery
+ set<int> fds;
+ bool respectTimeouts;
+ bool inBuildSlot;
+ steady_time_point lastOutput; /* time we last got output on stdout/stderr */
+ steady_time_point timeStarted;
+};
+
+/* Forward definition. */
+struct HookInstance;
+
+/* The worker class. */
+class Worker
+{
+private:
+
+ /* Note: the worker should only have strong pointers to the
+ top-level goals. */
+
+ /* The top-level goals of the worker. */
+ Goals topGoals;
+
+ /* Goals that are ready to do some work. */
+ WeakGoals awake;
+
+ /* Goals waiting for a build slot. */
+ WeakGoals wantingToBuild;
+
+ /* Child processes currently running. */
+ std::list<Child> children;
+
+ /* Number of build slots occupied. This includes local builds and
+ substitutions but not remote builds via the build hook. */
+ unsigned int nrLocalBuilds;
+
+ /* Maps used to prevent multiple instantiations of a goal for the
+ same derivation / path. */
+ std::map<StorePath, std::weak_ptr<DerivationGoal>> derivationGoals;
+ std::map<StorePath, std::weak_ptr<SubstitutionGoal>> substitutionGoals;
+
+ /* Goals waiting for busy paths to be unlocked. */
+ WeakGoals waitingForAnyGoal;
+
+ /* Goals sleeping for a few seconds (polling a lock). */
+ WeakGoals waitingForAWhile;
+
+ /* Last time the goals in `waitingForAWhile' where woken up. */
+ steady_time_point lastWokenUp;
+
+ /* Cache for pathContentsGood(). */
+ std::map<StorePath, bool> pathContentsGoodCache;
+
+public:
+
+ const Activity act;
+ const Activity actDerivations;
+ const Activity actSubstitutions;
+
+ /* Set if at least one derivation had a BuildError (i.e. permanent
+ failure). */
+ bool permanentFailure;
+
+ /* Set if at least one derivation had a timeout. */
+ bool timedOut;
+
+ /* Set if at least one derivation fails with a hash mismatch. */
+ bool hashMismatch;
+
+ /* Set if at least one derivation is not deterministic in check mode. */
+ bool checkMismatch;
+
+ LocalStore & store;
+
+ std::unique_ptr<HookInstance> hook;
+
+ uint64_t expectedBuilds = 0;
+ uint64_t doneBuilds = 0;
+ uint64_t failedBuilds = 0;
+ uint64_t runningBuilds = 0;
+
+ uint64_t expectedSubstitutions = 0;
+ uint64_t doneSubstitutions = 0;
+ uint64_t failedSubstitutions = 0;
+ uint64_t runningSubstitutions = 0;
+ uint64_t expectedDownloadSize = 0;
+ uint64_t doneDownloadSize = 0;
+ uint64_t expectedNarSize = 0;
+ uint64_t doneNarSize = 0;
+
+ /* Whether to ask the build hook if it can build a derivation. If
+ it answers with "decline-permanently", we don't try again. */
+ bool tryBuildHook = true;
+
+ Worker(LocalStore & store);
+ ~Worker();
+
+ /* Make a goal (with caching). */
+
+ /* derivation goal */
+private:
+ std::shared_ptr<DerivationGoal> makeDerivationGoalCommon(
+ const StorePath & drvPath, const StringSet & wantedOutputs,
+ std::function<std::shared_ptr<DerivationGoal>()> mkDrvGoal);
+public:
+ std::shared_ptr<DerivationGoal> makeDerivationGoal(
+ const StorePath & drvPath,
+ const StringSet & wantedOutputs, BuildMode buildMode = bmNormal);
+ std::shared_ptr<DerivationGoal> makeBasicDerivationGoal(
+ const StorePath & drvPath, const BasicDerivation & drv,
+ const StringSet & wantedOutputs, BuildMode buildMode = bmNormal);
+
+ /* substitution goal */
+ std::shared_ptr<SubstitutionGoal> makeSubstitutionGoal(const StorePath & storePath, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
+
+ /* Remove a dead goal. */
+ void removeGoal(GoalPtr goal);
+
+ /* Wake up a goal (i.e., there is something for it to do). */
+ void wakeUp(GoalPtr goal);
+
+ /* Return the number of local build and substitution processes
+ currently running (but not remote builds via the build
+ hook). */
+ unsigned int getNrLocalBuilds();
+
+ /* Registers a running child process. `inBuildSlot' means that
+ the process counts towards the jobs limit. */
+ void childStarted(GoalPtr goal, const set<int> & fds,
+ bool inBuildSlot, bool respectTimeouts);
+
+ /* Unregisters a running child process. `wakeSleepers' should be
+ false if there is no sense in waking up goals that are sleeping
+ because they can't run yet (e.g., there is no free build slot,
+ or the hook would still say `postpone'). */
+ void childTerminated(Goal * goal, bool wakeSleepers = true);
+
+ /* Put `goal' to sleep until a build slot becomes available (which
+ might be right away). */
+ void waitForBuildSlot(GoalPtr goal);
+
+ /* Wait for any goal to finish. Pretty indiscriminate way to
+ wait for some resource that some other goal is holding. */
+ void waitForAnyGoal(GoalPtr goal);
+
+ /* Wait for a few seconds and then retry this goal. Used when
+ waiting for a lock held by another process. This kind of
+ polling is inefficient, but POSIX doesn't really provide a way
+ to wait for multiple locks in the main select() loop. */
+ void waitForAWhile(GoalPtr goal);
+
+ /* Loop until the specified top-level goals have finished. */
+ void run(const Goals & topGoals);
+
+ /* Wait for input to become available. */
+ void waitForInput();
+
+ unsigned int exitStatus();
+
+ /* Check whether the given valid path exists and has the right
+ contents. */
+ bool pathContentsGood(const StorePath & path);
+
+ void markContentsGood(const StorePath & path);
+
+ void updateProgress()
+ {
+ actDerivations.progress(doneBuilds, expectedBuilds + doneBuilds, runningBuilds, failedBuilds);
+ actSubstitutions.progress(doneSubstitutions, expectedSubstitutions + doneSubstitutions, runningSubstitutions, failedSubstitutions);
+ act.setExpected(actFileTransfer, expectedDownloadSize + doneDownloadSize);
+ act.setExpected(actCopyPath, expectedNarSize + doneNarSize);
+ }
+};
+
+}
diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc
index 518a357ef..7a04b2f89 100644
--- a/src/libstore/gc.cc
+++ b/src/libstore/gc.cc
@@ -1,6 +1,7 @@
#include "derivations.hh"
#include "globals.hh"
#include "local-store.hh"
+#include "local-fs-store.hh"
#include "finally.hh"
#include <functional>
diff --git a/src/libstore/local-fs-store.cc b/src/libstore/local-fs-store.cc
index 2f1d9663a..e7c3dae92 100644
--- a/src/libstore/local-fs-store.cc
+++ b/src/libstore/local-fs-store.cc
@@ -1,6 +1,7 @@
#include "archive.hh"
#include "fs-accessor.hh"
#include "store-api.hh"
+#include "local-fs-store.hh"
#include "globals.hh"
#include "compression.hh"
#include "derivations.hh"
diff --git a/src/libstore/local-fs-store.hh b/src/libstore/local-fs-store.hh
new file mode 100644
index 000000000..8eccd8236
--- /dev/null
+++ b/src/libstore/local-fs-store.hh
@@ -0,0 +1,48 @@
+#pragma once
+
+#include "store-api.hh"
+
+namespace nix {
+
+struct LocalFSStoreConfig : virtual StoreConfig
+{
+ using StoreConfig::StoreConfig;
+ // FIXME: the (StoreConfig*) cast works around a bug in gcc that causes
+ // it to omit the call to the Setting constructor. Clang works fine
+ // either way.
+ const PathSetting rootDir{(StoreConfig*) this, true, "",
+ "root", "directory prefixed to all other paths"};
+ const PathSetting stateDir{(StoreConfig*) this, false,
+ rootDir != "" ? rootDir + "/nix/var/nix" : settings.nixStateDir,
+ "state", "directory where Nix will store state"};
+ const PathSetting logDir{(StoreConfig*) this, false,
+ rootDir != "" ? rootDir + "/nix/var/log/nix" : settings.nixLogDir,
+ "log", "directory where Nix will store state"};
+};
+
+class LocalFSStore : public virtual Store, public virtual LocalFSStoreConfig
+{
+public:
+
+ const static string drvsLogDir;
+
+ LocalFSStore(const Params & params);
+
+ void narFromPath(const StorePath & path, Sink & sink) override;
+ ref<FSAccessor> getFSAccessor() override;
+
+ /* Register a permanent GC root. */
+ Path addPermRoot(const StorePath & storePath, const Path & gcRoot);
+
+ virtual Path getRealStoreDir() { return storeDir; }
+
+ Path toRealPath(const Path & storePath) override
+ {
+ assert(isInStore(storePath));
+ return getRealStoreDir() + "/" + std::string(storePath, storeDir.size() + 1);
+ }
+
+ std::shared_ptr<std::string> getBuildLog(const StorePath & path) override;
+};
+
+}
diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh
index e7c9d1605..f1e2ab7f9 100644
--- a/src/libstore/local-store.hh
+++ b/src/libstore/local-store.hh
@@ -4,6 +4,7 @@
#include "pathlocks.hh"
#include "store-api.hh"
+#include "local-fs-store.hh"
#include "sync.hh"
#include "util.hh"
diff --git a/src/libstore/local.mk b/src/libstore/local.mk
index 212a725af..dfe1e2cc4 100644
--- a/src/libstore/local.mk
+++ b/src/libstore/local.mk
@@ -32,7 +32,7 @@ ifeq ($(HAVE_SECCOMP), 1)
endif
libstore_CXXFLAGS += \
- -I src/libutil -I src/libstore \
+ -I src/libutil -I src/libstore -I src/libstore/build \
-DNIX_PREFIX=\"$(prefix)\" \
-DNIX_STORE_DIR=\"$(storedir)\" \
-DNIX_DATA_DIR=\"$(datadir)\" \
@@ -64,3 +64,6 @@ $(eval $(call install-file-in, $(d)/nix-store.pc, $(prefix)/lib/pkgconfig, 0644)
$(foreach i, $(wildcard src/libstore/builtins/*.hh), \
$(eval $(call install-file-in, $(i), $(includedir)/nix/builtins, 0644)))
+
+$(foreach i, $(wildcard src/libstore/build/*.hh), \
+ $(eval $(call install-file-in, $(i), $(includedir)/nix/build, 0644)))
diff --git a/src/libstore/profiles.cc b/src/libstore/profiles.cc
index c3809bad7..ed10dd519 100644
--- a/src/libstore/profiles.cc
+++ b/src/libstore/profiles.cc
@@ -1,5 +1,6 @@
#include "profiles.hh"
#include "store-api.hh"
+#include "local-fs-store.hh"
#include "util.hh"
#include <sys/types.h>
diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index 23b1942ce..488270f48 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -12,16 +12,6 @@
#include "logging.hh"
#include "callback.hh"
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <unistd.h>
-
-#include <cstring>
-
namespace nix {
namespace worker_proto {
@@ -125,69 +115,6 @@ ref<RemoteStore::Connection> RemoteStore::openConnectionWrapper()
}
-UDSRemoteStore::UDSRemoteStore(const Params & params)
- : StoreConfig(params)
- , Store(params)
- , LocalFSStore(params)
- , RemoteStore(params)
-{
-}
-
-
-UDSRemoteStore::UDSRemoteStore(
- const std::string scheme,
- std::string socket_path,
- const Params & params)
- : UDSRemoteStore(params)
-{
- path.emplace(socket_path);
-}
-
-
-std::string UDSRemoteStore::getUri()
-{
- if (path) {
- return std::string("unix://") + *path;
- } else {
- return "daemon";
- }
-}
-
-
-ref<RemoteStore::Connection> UDSRemoteStore::openConnection()
-{
- auto conn = make_ref<Connection>();
-
- /* Connect to a daemon that does the privileged work for us. */
- conn->fd = socket(PF_UNIX, SOCK_STREAM
- #ifdef SOCK_CLOEXEC
- | SOCK_CLOEXEC
- #endif
- , 0);
- if (!conn->fd)
- throw SysError("cannot create Unix domain socket");
- closeOnExec(conn->fd.get());
-
- string socketPath = path ? *path : settings.nixDaemonSocketFile;
-
- struct sockaddr_un addr;
- addr.sun_family = AF_UNIX;
- if (socketPath.size() + 1 >= sizeof(addr.sun_path))
- throw Error("socket path '%1%' is too long", socketPath);
- strcpy(addr.sun_path, socketPath.c_str());
-
- if (::connect(conn->fd.get(), (struct sockaddr *) &addr, sizeof(addr)) == -1)
- throw SysError("cannot connect to daemon at '%1%'", socketPath);
-
- conn->from.fd = conn->fd.get();
- conn->to.fd = conn->fd.get();
-
- conn->startTime = std::chrono::steady_clock::now();
-
- return conn;
-}
-
-
void RemoteStore::initConnection(Connection & conn)
{
/* Send the magic greeting, check for the reply. */
@@ -1012,6 +939,4 @@ void ConnectionHandle::withFramedSink(std::function<void(Sink &sink)> fun)
}
-static RegisterStoreImplementation<UDSRemoteStore, UDSRemoteStoreConfig> regUDSRemoteStore;
-
}
diff --git a/src/libstore/remote-store.hh b/src/libstore/remote-store.hh
index ec04be985..9f78fcb02 100644
--- a/src/libstore/remote-store.hh
+++ b/src/libstore/remote-store.hh
@@ -155,49 +155,5 @@ private:
};
-struct UDSRemoteStoreConfig : virtual LocalFSStoreConfig, virtual RemoteStoreConfig
-{
- UDSRemoteStoreConfig(const Store::Params & params)
- : StoreConfig(params)
- , LocalFSStoreConfig(params)
- , RemoteStoreConfig(params)
- {
- }
-
- UDSRemoteStoreConfig()
- : UDSRemoteStoreConfig(Store::Params({}))
- {
- }
-
- const std::string name() override { return "Local Daemon Store"; }
-};
-
-class UDSRemoteStore : public LocalFSStore, public RemoteStore, public virtual UDSRemoteStoreConfig
-{
-public:
-
- UDSRemoteStore(const Params & params);
- UDSRemoteStore(const std::string scheme, std::string path, const Params & params);
-
- std::string getUri() override;
-
- static std::set<std::string> uriSchemes()
- { return {"unix"}; }
-
- bool sameMachine() override
- { return true; }
-
- ref<FSAccessor> getFSAccessor() override
- { return LocalFSStore::getFSAccessor(); }
-
- void narFromPath(const StorePath & path, Sink & sink) override
- { LocalFSStore::narFromPath(path, sink); }
-
-private:
-
- ref<RemoteStore::Connection> openConnection() override;
- std::optional<std::string> path;
-};
-
}
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index 1bbc74db8..9f21f0434 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -1011,7 +1011,7 @@ Derivation Store::readDerivation(const StorePath & drvPath)
#include "local-store.hh"
-#include "remote-store.hh"
+#include "uds-remote-store.hh"
namespace nix {
diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh
index 450c0f554..f77bc21d1 100644
--- a/src/libstore/store-api.hh
+++ b/src/libstore/store-api.hh
@@ -715,47 +715,6 @@ protected:
};
-struct LocalFSStoreConfig : virtual StoreConfig
-{
- using StoreConfig::StoreConfig;
- // FIXME: the (StoreConfig*) cast works around a bug in gcc that causes
- // it to omit the call to the Setting constructor. Clang works fine
- // either way.
- const PathSetting rootDir{(StoreConfig*) this, true, "",
- "root", "directory prefixed to all other paths"};
- const PathSetting stateDir{(StoreConfig*) this, false,
- rootDir != "" ? rootDir + "/nix/var/nix" : settings.nixStateDir,
- "state", "directory where Nix will store state"};
- const PathSetting logDir{(StoreConfig*) this, false,
- rootDir != "" ? rootDir + "/nix/var/log/nix" : settings.nixLogDir,
- "log", "directory where Nix will store state"};
-};
-
-class LocalFSStore : public virtual Store, public virtual LocalFSStoreConfig
-{
-public:
-
- const static string drvsLogDir;
-
- LocalFSStore(const Params & params);
-
- void narFromPath(const StorePath & path, Sink & sink) override;
- ref<FSAccessor> getFSAccessor() override;
-
- /* Register a permanent GC root. */
- Path addPermRoot(const StorePath & storePath, const Path & gcRoot);
-
- virtual Path getRealStoreDir() { return storeDir; }
-
- Path toRealPath(const Path & storePath) override
- {
- assert(isInStore(storePath));
- return getRealStoreDir() + "/" + std::string(storePath, storeDir.size() + 1);
- }
-
- std::shared_ptr<std::string> getBuildLog(const StorePath & path) override;
-};
-
/* Copy a path from one store to another. */
void copyStorePath(ref<Store> srcStore, ref<Store> dstStore,
diff --git a/src/libstore/uds-remote-store.cc b/src/libstore/uds-remote-store.cc
new file mode 100644
index 000000000..24f3e9c6d
--- /dev/null
+++ b/src/libstore/uds-remote-store.cc
@@ -0,0 +1,81 @@
+#include "uds-remote-store.hh"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <cstring>
+
+
+namespace nix {
+
+UDSRemoteStore::UDSRemoteStore(const Params & params)
+ : StoreConfig(params)
+ , Store(params)
+ , LocalFSStore(params)
+ , RemoteStore(params)
+{
+}
+
+
+UDSRemoteStore::UDSRemoteStore(
+ const std::string scheme,
+ std::string socket_path,
+ const Params & params)
+ : UDSRemoteStore(params)
+{
+ path.emplace(socket_path);
+}
+
+
+std::string UDSRemoteStore::getUri()
+{
+ if (path) {
+ return std::string("unix://") + *path;
+ } else {
+ return "daemon";
+ }
+}
+
+
+ref<RemoteStore::Connection> UDSRemoteStore::openConnection()
+{
+ auto conn = make_ref<Connection>();
+
+ /* Connect to a daemon that does the privileged work for us. */
+ conn->fd = socket(PF_UNIX, SOCK_STREAM
+ #ifdef SOCK_CLOEXEC
+ | SOCK_CLOEXEC
+ #endif
+ , 0);
+ if (!conn->fd)
+ throw SysError("cannot create Unix domain socket");
+ closeOnExec(conn->fd.get());
+
+ string socketPath = path ? *path : settings.nixDaemonSocketFile;
+
+ struct sockaddr_un addr;
+ addr.sun_family = AF_UNIX;
+ if (socketPath.size() + 1 >= sizeof(addr.sun_path))
+ throw Error("socket path '%1%' is too long", socketPath);
+ strcpy(addr.sun_path, socketPath.c_str());
+
+ if (::connect(conn->fd.get(), (struct sockaddr *) &addr, sizeof(addr)) == -1)
+ throw SysError("cannot connect to daemon at '%1%'", socketPath);
+
+ conn->from.fd = conn->fd.get();
+ conn->to.fd = conn->fd.get();
+
+ conn->startTime = std::chrono::steady_clock::now();
+
+ return conn;
+}
+
+
+static RegisterStoreImplementation<UDSRemoteStore, UDSRemoteStoreConfig> regUDSRemoteStore;
+
+}
diff --git a/src/libstore/uds-remote-store.hh b/src/libstore/uds-remote-store.hh
new file mode 100644
index 000000000..e5de104c9
--- /dev/null
+++ b/src/libstore/uds-remote-store.hh
@@ -0,0 +1,52 @@
+#pragma once
+
+#include "remote-store.hh"
+#include "local-fs-store.hh"
+
+namespace nix {
+
+struct UDSRemoteStoreConfig : virtual LocalFSStoreConfig, virtual RemoteStoreConfig
+{
+ UDSRemoteStoreConfig(const Store::Params & params)
+ : StoreConfig(params)
+ , LocalFSStoreConfig(params)
+ , RemoteStoreConfig(params)
+ {
+ }
+
+ UDSRemoteStoreConfig()
+ : UDSRemoteStoreConfig(Store::Params({}))
+ {
+ }
+
+ const std::string name() override { return "Local Daemon Store"; }
+};
+
+class UDSRemoteStore : public LocalFSStore, public RemoteStore, public virtual UDSRemoteStoreConfig
+{
+public:
+
+ UDSRemoteStore(const Params & params);
+ UDSRemoteStore(const std::string scheme, std::string path, const Params & params);
+
+ std::string getUri() override;
+
+ static std::set<std::string> uriSchemes()
+ { return {"unix"}; }
+
+ bool sameMachine() override
+ { return true; }
+
+ ref<FSAccessor> getFSAccessor() override
+ { return LocalFSStore::getFSAccessor(); }
+
+ void narFromPath(const StorePath & path, Sink & sink) override
+ { LocalFSStore::narFromPath(path, sink); }
+
+private:
+
+ ref<RemoteStore::Connection> openConnection() override;
+ std::optional<std::string> path;
+};
+
+}