diff options
Diffstat (limited to 'src/libstore')
-rw-r--r-- | src/libstore/build.cc | 427 | ||||
-rw-r--r-- | src/libstore/gc.cc | 35 | ||||
-rw-r--r-- | src/libstore/local-store.cc | 39 | ||||
-rw-r--r-- | src/libstore/local-store.hh | 4 | ||||
-rw-r--r-- | src/libstore/optimise-store.cc | 1 | ||||
-rw-r--r-- | src/libstore/pathlocks.cc | 2 | ||||
-rw-r--r-- | src/libstore/remote-store.cc | 6 |
7 files changed, 347 insertions, 167 deletions
diff --git a/src/libstore/build.cc b/src/libstore/build.cc index 3c9db5f7a0e4..e0398e2fb4a3 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -50,6 +50,15 @@ #define CHROOT_ENABLED HAVE_CHROOT && HAVE_UNSHARE && HAVE_SYS_MOUNT_H && defined(MS_BIND) && defined(MS_PRIVATE) && defined(CLONE_NEWNS) +/* chroot-like behavior from Apple's sandbox */ +#if __APPLE__ + #define SANDBOX_ENABLED 1 + #define DEFAULT_ALLOWED_IMPURE_PREFIXES "/System/Library/Frameworks /usr/lib /dev /bin/sh" +#else + #define SANDBOX_ENABLED 0 + #define DEFAULT_ALLOWED_IMPURE_PREFIXES "/bin" "/usr/bin" +#endif + #if CHROOT_ENABLED #include <sys/socket.h> #include <sys/ioctl.h> @@ -84,8 +93,12 @@ class Goal; typedef std::shared_ptr<Goal> GoalPtr; typedef std::weak_ptr<Goal> WeakGoalPtr; +struct CompareGoalPtrs { + bool operator() (const GoalPtr & a, const GoalPtr & b); +}; + /* Set of goals. */ -typedef set<GoalPtr> Goals; +typedef set<GoalPtr, CompareGoalPtrs> Goals; typedef list<WeakGoalPtr> WeakGoals; /* A map of paths to goals (and the other way around). */ @@ -172,11 +185,20 @@ public: (important!), etc. */ virtual void cancel(bool timeout) = 0; + virtual string key() = 0; + protected: void amDone(ExitCode result); }; +bool CompareGoalPtrs::operator() (const GoalPtr & a, const GoalPtr & b) { + string s1 = a->key(); + string s2 = b->key(); + return s1 < s2; +} + + /* 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. */ @@ -303,6 +325,7 @@ public: void addToWeakGoals(WeakGoals & goals, GoalPtr p) { // FIXME: necessary? + // FIXME: O(n) foreach (WeakGoals::iterator, i, goals) if (i->lock() == p) return; goals.push_back(p); @@ -401,18 +424,6 @@ static void commonChildInit(Pipe & logPipe) } -/* Convert a string list to an array of char pointers. Careful: the - string list should outlive the array. */ -const char * * strings2CharPtrs(const Strings & ss) -{ - const char * * arr = new const char * [ss.size() + 1]; - const char * * p = arr; - foreach (Strings::const_iterator, i, ss) *p++ = i->c_str(); - *p = 0; - return arr; -} - - ////////////////////////////////////////////////////////////////////// @@ -767,6 +778,15 @@ public: void cancel(bool timeout); + string 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$" + storePathToName(drvPath) + "$" + drvPath; + } + void work(); Path getDrvPath() @@ -793,8 +813,8 @@ private: /* Start building a derivation. */ void startBuilder(); - /* Initialise the builder's process. */ - void initChild(); + /* Run the builder's process. */ + void runChild(); friend int childEntry(void *); @@ -851,13 +871,9 @@ DerivationGoal::~DerivationGoal() { /* Careful: we should never ever throw an exception from a destructor. */ - try { - killChild(); - deleteTmpDir(false); - closeLogFile(); - } catch (...) { - ignoreException(); - } + try { killChild(); } catch (...) { ignoreException(); } + try { deleteTmpDir(false); } catch (...) { ignoreException(); } + try { closeLogFile(); } catch (...) { ignoreException(); } } @@ -928,6 +944,11 @@ void DerivationGoal::init() /* The first thing to do is to make sure that the derivation exists. If it doesn't, it may be created through a substitute. */ + if (buildMode == bmNormal && worker.store.isValidPath(drvPath)) { + haveDerivation(); + return; + } + addWaitee(worker.makeSubstitutionGoal(drvPath)); state = &DerivationGoal::haveDerivation; @@ -1574,6 +1595,13 @@ void chmod_(const Path & path, mode_t mode) } +int childEntry(void * arg) +{ + ((DerivationGoal *) arg)->runChild(); + return 1; +} + + void DerivationGoal::startBuilder() { startNest(nest, lvlInfo, format( @@ -1717,21 +1745,6 @@ void DerivationGoal::startBuilder() /* Change ownership of the temporary build directory. */ if (chown(tmpDir.c_str(), buildUser.getUID(), buildUser.getGID()) == -1) throw SysError(format("cannot change ownership of ‘%1%’") % tmpDir); - - /* Check that the Nix store has the appropriate permissions, - i.e., owned by root and mode 1775 (sticky bit on so that - the builder can create its output but not mess with the - outputs of other processes). */ - struct stat st; - if (stat(settings.nixStore.c_str(), &st) == -1) - throw SysError(format("cannot stat ‘%1%’") % settings.nixStore); - if (!(st.st_mode & S_ISVTX) || - ((st.st_mode & S_IRWXG) != S_IRWXG) || - (st.st_gid != buildUser.getGID())) - throw Error(format( - "builder does not have write permission to ‘%2%’; " - "try ‘chgrp %1% %2%; chmod 1775 %2%’") - % buildUser.getGID() % settings.nixStore); } @@ -1748,6 +1761,47 @@ void DerivationGoal::startBuilder() if (get(drv.env, "__noChroot") == "1") useChroot = false; if (useChroot) { + /* Allow a user-configurable set of directories from the + host file system. */ + PathSet dirs = tokenizeString<StringSet>(settings.get("build-chroot-dirs", string(DEFAULT_CHROOT_DIRS))); + PathSet dirs2 = tokenizeString<StringSet>(settings.get("build-extra-chroot-dirs", string(""))); + dirs.insert(dirs2.begin(), dirs2.end()); + + for (auto & i : dirs) { + size_t p = i.find('='); + if (p == string::npos) + dirsInChroot[i] = i; + else + dirsInChroot[string(i, 0, p)] = string(i, p + 1); + } + dirsInChroot[tmpDir] = tmpDir; + + string allowed = settings.get("allowed-impure-host-deps", string(DEFAULT_ALLOWED_IMPURE_PREFIXES)); + PathSet allowedPaths = tokenizeString<StringSet>(allowed); + + /* This works like the above, except on a per-derivation level */ + Strings impurePaths = tokenizeString<Strings>(get(drv.env, "__impureHostDeps")); + + for (auto & i : impurePaths) { + bool found = false; + /* Note: we're not resolving symlinks here to prevent + giving a non-root user info about inaccessible + files. */ + Path canonI = canonPath(i); + /* If only we had a trie to do this more efficiently :) luckily, these are generally going to be pretty small */ + for (auto & a : allowedPaths) { + Path canonA = canonPath(a); + if (canonI == canonA || isInDir(canonI, canonA)) { + found = true; + break; + } + } + if (!found) + throw Error(format("derivation '%1%' requested impure path ‘%2%’, but it was not in allowed-impure-host-deps (‘%3%’)") % drvPath % i % allowed); + + dirsInChroot[i] = i; + } + #if CHROOT_ENABLED /* Create a temporary directory in which we set up the chroot environment using bind-mounts. We put it in the Nix store @@ -1789,20 +1843,6 @@ void DerivationGoal::startBuilder() /* Create /etc/hosts with localhost entry. */ writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n"); - /* Bind-mount a user-configurable set of directories from the - host file system. */ - PathSet dirs = tokenizeString<StringSet>(settings.get("build-chroot-dirs", string(DEFAULT_CHROOT_DIRS))); - PathSet dirs2 = tokenizeString<StringSet>(settings.get("build-extra-chroot-dirs", string(""))); - dirs.insert(dirs2.begin(), dirs2.end()); - for (auto & i : dirs) { - size_t p = i.find('='); - if (p == string::npos) - dirsInChroot[i] = i; - else - dirsInChroot[string(i, 0, p)] = string(i, p + 1); - } - dirsInChroot[tmpDir] = tmpDir; - /* Make the closure of the inputs available in the chroot, rather than the whole Nix store. This prevents any access to undeclared dependencies. Directories are bind-mounted, @@ -1846,6 +1886,9 @@ void DerivationGoal::startBuilder() foreach (DerivationOutputs::iterator, i, drv.outputs) dirsInChroot.erase(i->second.path); +#elif SANDBOX_ENABLED + /* We don't really have any parent prep work to do (yet?) + All work happens in the child, instead. */ #else throw Error("chroot builds are not supported on this platform"); #endif @@ -1890,9 +1933,61 @@ void DerivationGoal::startBuilder() builderOut.create(); /* Fork a child to build the package. */ - pid = startProcess([&]() { - initChild(); - }); +#if CHROOT_ENABLED + if (useChroot) { + /* Set up private namespaces for the build: + + - The PID namespace causes the build to start as PID 1. + Processes outside of the chroot are not visible to those + on the inside, but processes inside the chroot are + visible from the outside (though with different PIDs). + + - The private mount namespace ensures that all the bind + mounts we do will only show up in this process and its + children, and will disappear automatically when we're + done. + + - The private network namespace ensures that the builder + cannot talk to the outside world (or vice versa). It + only has a private loopback interface. + + - The IPC namespace prevents the builder from communicating + with outside processes using SysV IPC mechanisms (shared + memory, message queues, semaphores). It also ensures + that all IPC objects are destroyed when the builder + exits. + + - The UTS namespace ensures that builders see a hostname of + localhost rather than the actual hostname. + + We use a helper process to do the clone() to work around + clone() being broken in multi-threaded programs due to + at-fork handlers not being run. Note that we use + CLONE_PARENT to ensure that the real builder is parented to + us. + */ + Pid helper = startProcess([&]() { + char stack[32 * 1024]; + pid_t child = clone(childEntry, stack + sizeof(stack) - 8, + CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWNET | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD, this); + if (child == -1) throw SysError("cloning builder process"); + writeFull(builderOut.writeSide, int2String(child) + "\n"); + _exit(0); + }); + if (helper.wait(true) != 0) + throw Error("unable to start build process"); + pid_t tmp; + if (!string2Int<pid_t>(readLine(builderOut.readSide), tmp)) abort(); + pid = tmp; + } else +#endif + { + ProcessOptions options; + options.allowVfork = !buildUser.enabled(); + pid = startProcess([&]() { + runChild(); + }, options); + } /* parent */ pid.setSeparatePG(true); @@ -1908,11 +2003,10 @@ void DerivationGoal::startBuilder() printMsg(lvlError, format("@ build-started %1% - %2% %3%") % drvPath % drv.platform % logFile); } - } -void DerivationGoal::initChild() +void DerivationGoal::runChild() { /* Warning: in the child we should absolutely not make any SQLite calls! */ @@ -1924,36 +2018,6 @@ void DerivationGoal::initChild() #if CHROOT_ENABLED if (useChroot) { - /* Set up private namespaces for the build: - - - The PID namespace causes the build to start as PID 1. - Processes outside of the chroot are not visible to - those on the inside, but processes inside the chroot - are visible from the outside (though with different - PIDs). - - - The private mount namespace ensures that all the bind - mounts we do will only show up in this process and - its children, and will disappear automatically when - we're done. - - - The private network namespace ensures that the - builder cannot talk to the outside world (or vice - versa). It only has a private loopback interface. - - - The IPC namespace prevents the builder from - communicating with outside processes using SysV IPC - mechanisms (shared memory, message queues, - semaphores). It also ensures that all IPC objects - are destroyed when the builder exits. - - - The UTS namespace ensures that builders see a - hostname of localhost rather than the actual - hostname. - */ - if (unshare(CLONE_NEWNS | CLONE_NEWNET | CLONE_NEWIPC | CLONE_NEWUTS) == -1) - throw SysError("setting up private namespaces"); - /* Initialise the loopback interface. */ AutoCloseFD fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP)); if (fd == -1) throw SysError("cannot open IP socket"); @@ -1968,9 +2032,11 @@ void DerivationGoal::initChild() /* Set the hostname etc. to fixed values. */ char hostname[] = "localhost"; - sethostname(hostname, sizeof(hostname)); + if (sethostname(hostname, sizeof(hostname)) == -1) + throw SysError("cannot set host name"); char domainname[] = "(none)"; // kernel default - setdomainname(domainname, sizeof(domainname)); + if (setdomainname(domainname, sizeof(domainname)) == -1) + throw SysError("cannot set domain name"); /* Make all filesystems private. This is necessary because subtrees may have been mounted as "shared" @@ -2032,8 +2098,7 @@ void DerivationGoal::initChild() throw SysError(format("bind mount from ‘%1%’ to ‘%2%’ failed") % source % target); } - /* Bind a new instance of procfs on /proc to reflect our - private PID namespace. */ + /* Bind a new instance of procfs on /proc. */ createDirs(chrootRootDir + "/proc"); if (mount("none", (chrootRootDir + "/proc").c_str(), "proc", 0, 0) == -1) throw SysError("mounting /proc"); @@ -2084,7 +2149,7 @@ void DerivationGoal::initChild() if (drv.platform == "i686-linux" && (settings.thisSystem == "x86_64-linux" || (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "x86_64")))) { - if (personality(PER_LINUX32_3GB) == -1) + if (personality(PER_LINUX32) == -1) throw SysError("cannot set i686-linux personality"); } @@ -2105,11 +2170,7 @@ void DerivationGoal::initChild() Strings envStrs; foreach (Environment::const_iterator, i, env) envStrs.push_back(rewriteHashes(i->first + "=" + i->second, rewritesToTmp)); - const char * * envArr = strings2CharPtrs(envStrs); - - Path program = drv.builder.c_str(); - std::vector<const char *> args; /* careful with c_str()! */ - string user; /* must be here for its c_str()! */ + auto envArr = stringsToCharPtrs(envStrs); /* If we are running in `build-users' mode, then switch to the user we allocated above. Make sure that we drop all root @@ -2118,8 +2179,6 @@ void DerivationGoal::initChild() setuid() when run as root sets the real, effective and saved UIDs. */ if (buildUser.enabled()) { - printMsg(lvlChatty, format("switching to user ‘%1%’") % buildUser.getUser()); - if (setgroups(0, 0) == -1) throw SysError("cannot clear the set of supplementary groups"); @@ -2135,29 +2194,146 @@ void DerivationGoal::initChild() } /* Fill in the arguments. */ - string builderBasename = baseNameOf(drv.builder); - args.push_back(builderBasename.c_str()); - foreach (Strings::iterator, i, drv.args) { - auto re = rewriteHashes(*i, rewritesToTmp); - auto cstr = new char[re.length()+1]; - std::strcpy(cstr, re.c_str()); - - args.push_back(cstr); + Strings args; + + const char *builder = "invalid"; + + string sandboxProfile; + if (useChroot && SANDBOX_ENABLED) { + /* Lots and lots and lots of file functions freak out if they can't stat their full ancestry */ + PathSet ancestry; + + /* We build the ancestry before adding all inputPaths to the store because we know they'll + all have the same parents (the store), and there might be lots of inputs. This isn't + particularly efficient... I doubt it'll be a bottleneck in practice */ + for (auto & i : dirsInChroot) { + Path cur = i.first; + while (cur.compare("/") != 0) { + cur = dirOf(cur); + ancestry.insert(cur); + } + } + + /* And we want the store in there regardless of how empty dirsInChroot. We include the innermost + path component this time, since it's typically /nix/store and we care about that. */ + Path cur = settings.nixStore; + while (cur.compare("/") != 0) { + ancestry.insert(cur); + cur = dirOf(cur); + } + + /* Add all our input paths to the chroot */ + for (auto & i : inputPaths) + dirsInChroot[i] = i; + + + /* TODO: we should factor out the policy cleanly, so we don't have to repeat the constants every time... */ + sandboxProfile += "(version 1)\n"; + + /* Violations will go to the syslog if you set this. Unfortunately the destination does not appear to be configurable */ + if (settings.get("darwin-log-sandbox-violations", false)) { + sandboxProfile += "(deny default)\n"; + } else { + sandboxProfile += "(deny default (with no-log))\n"; + } + + sandboxProfile += "(allow file-read* file-write-data (literal \"/dev/null\"))\n"; + + sandboxProfile += "(allow file-read-metadata\n" + "\t(literal \"/var\")\n" + "\t(literal \"/tmp\")\n" + "\t(literal \"/etc\")\n" + "\t(literal \"/etc/nix\")\n" + "\t(literal \"/etc/nix/nix.conf\"))\n"; + + /* The tmpDir in scope points at the temporary build directory for our derivation. Some packages try different mechanisms + to find temporary directories, so we want to open up a broader place for them to dump their files, if needed. */ + Path globalTmpDir = canonPath(getEnv("TMPDIR", "/tmp"), true); + + /* They don't like trailing slashes on subpath directives */ + if (globalTmpDir.back() == '/') globalTmpDir.pop_back(); + + /* This is where our temp folders are and where most of the building will happen, so we want rwx on it. */ + sandboxProfile += (format("(allow file-read* file-write* process-exec (subpath \"%1%\") (subpath \"/private/tmp\"))\n") % globalTmpDir).str(); + + sandboxProfile += "(allow process-fork)\n"; + sandboxProfile += "(allow sysctl-read)\n"; + sandboxProfile += "(allow signal (target same-sandbox))\n"; + + /* Enables getpwuid (used by git and others) */ + sandboxProfile += "(allow mach-lookup (global-name \"com.apple.system.notification_center\") (global-name \"com.apple.system.opendirectoryd.libinfo\"))\n"; + + + /* Our rwx outputs */ + sandboxProfile += "(allow file-read* file-write* process-exec\n"; + for (auto & i : missingPaths) { + sandboxProfile += (format("\t(subpath \"%1%\")\n") % i.c_str()).str(); + } + sandboxProfile += ")\n"; + + /* Our inputs (transitive dependencies and any impurities computed above) + Note that the sandbox profile allows file-write* even though it isn't seemingly necessary. First of all, nix's standard user permissioning + mechanism still prevents builders from writing to input directories, so no security/purity is lost. The reason we allow file-write* is that + denying it means the `access` syscall will return EPERM instead of EACCESS, which confuses a few programs that assume (understandably, since + it appears to be a violation of the POSIX spec) that `access` won't do that, and don't deal with it nicely if it does. The most notable of + these is the entire GHC Haskell ecosystem. */ + sandboxProfile += "(allow file-read* file-write* process-exec\n"; + for (auto & i : dirsInChroot) { + if (i.first != i.second) + throw SysError(format("can't map '%1%' to '%2%': mismatched impure paths not supported on darwin")); + + string path = i.first; + struct stat st; + if (lstat(path.c_str(), &st)) + throw SysError(format("getting attributes of path ‘%1%’") % path); + if (S_ISDIR(st.st_mode)) + sandboxProfile += (format("\t(subpath \"%1%\")\n") % path).str(); + else + sandboxProfile += (format("\t(literal \"%1%\")\n") % path).str(); + } + sandboxProfile += ")\n"; + + /* Our ancestry. N.B: this uses literal on folders, instead of subpath. Without that, + you open up the entire filesystem because you end up with (subpath "/") */ + sandboxProfile += "(allow file-read-metadata\n"; + for (auto & i : ancestry) { + sandboxProfile += (format("\t(literal \"%1%\")\n") % i.c_str()).str(); + } + sandboxProfile += ")\n"; + + builder = "/usr/bin/sandbox-exec"; + args.push_back("sandbox-exec"); + args.push_back("-p"); + args.push_back(sandboxProfile); + args.push_back(drv.builder); + } else { + builder = drv.builder.c_str(); + string builderBasename = baseNameOf(drv.builder); + args.push_back(builderBasename); } - args.push_back(0); + + foreach (Strings::iterator, i, drv.args) + args.push_back(rewriteHashes(*i, rewritesToTmp)); + auto argArr = stringsToCharPtrs(args); restoreSIGPIPE(); /* Indicate that we managed to set up the build environment. */ - writeToStderr("\n"); + writeFull(STDERR_FILENO, "\n"); + + /* This needs to be after that fateful '\n', and I didn't want to duplicate code */ + if (useChroot && SANDBOX_ENABLED) { + printMsg(lvlDebug, "Generated sandbox profile:"); + printMsg(lvlDebug, sandboxProfile); + } /* Execute the program. This should not return. */ - execve(program.c_str(), (char * *) &args[0], (char * *) envArr); + execve(builder, (char * *) &argArr[0], (char * *) &envArr[0]); throw SysError(format("executing ‘%1%’") % drv.builder); } catch (std::exception & e) { - writeToStderr("while setting up the build environment: " + string(e.what()) + "\n"); + writeFull(STDERR_FILENO, "while setting up the build environment: " + string(e.what()) + "\n"); _exit(1); } } @@ -2308,7 +2484,7 @@ void DerivationGoal::registerOutputs() if (buildMode == bmCheck) { ValidPathInfo info = worker.store.queryPathInfo(path); if (hash.first != info.hash) - throw Error(format("derivation ‘%2%’ may not be deterministic: hash mismatch in output ‘%1%’") % drvPath % path); + throw Error(format("derivation ‘%1%’ may not be deterministic: hash mismatch in output ‘%2%’") % drvPath % path); continue; } @@ -2470,7 +2646,7 @@ void DerivationGoal::handleChildOutput(int fd, const string & data) BZ2_bzWrite(&err, bzLogFile, (unsigned char *) data.data(), data.size()); if (err != BZ_OK) throw Error(format("cannot write to compressed log file (BZip2 error = %1%)") % err); } else if (fdLogFile != -1) - writeFull(fdLogFile, (unsigned char *) data.data(), data.size()); + writeFull(fdLogFile, data); } if (hook && fd == hook->fromHook.readSide) @@ -2581,6 +2757,13 @@ public: void cancel(bool timeout); + string key() + { + /* "a$" ensures substitution goals happen before derivation + goals. */ + return "a$" + storePathToName(storePath) + "$" + storePath; + } + void work(); /* The states. */ @@ -2773,7 +2956,7 @@ void SubstitutionGoal::tryToRun() args.push_back("--substitute"); args.push_back(storePath); args.push_back(destPath); - const char * * argArr = strings2CharPtrs(args); + auto argArr = stringsToCharPtrs(args); /* Fork the substitute program. */ pid = startProcess([&]() { @@ -2783,7 +2966,7 @@ void SubstitutionGoal::tryToRun() if (dup2(outPipe.writeSide, STDOUT_FILENO) == -1) throw SysError("cannot dup output pipe into stdout"); - execv(sub.c_str(), (char * *) argArr); + execv(sub.c_str(), (char * *) &argArr[0]); throw SysError(format("executing ‘%1%’") % sub); }); @@ -3091,15 +3274,19 @@ void Worker::run(const Goals & _topGoals) checkInterrupt(); - /* Call every wake goal. */ + /* Call every wake goal (in the ordering established by + CompareGoalPtrs). */ while (!awake.empty() && !topGoals.empty()) { - WeakGoals awake2(awake); + Goals awake2; + for (auto & i : awake) { + GoalPtr goal = i.lock(); + if (goal) awake2.insert(goal); + } awake.clear(); - foreach (WeakGoals::iterator, i, awake2) { + for (auto & goal : awake2) { checkInterrupt(); - GoalPtr goal = i->lock(); - if (goal) goal->work(); - if (topGoals.empty()) break; + goal->work(); + if (topGoals.empty()) break; // stuff may have been cancelled } } diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc index e9db711e7ca0..7959a592d987 100644 --- a/src/libstore/gc.cc +++ b/src/libstore/gc.cc @@ -145,11 +145,6 @@ Path addPermRoot(StoreAPI & store, const Path & _storePath, } -/* The file to which we write our temporary roots. */ -static Path fnTempRoots; -static AutoCloseFD fdTempRoots; - - void LocalStore::addTempRoot(const Path & path) { /* Create the temporary roots file for this process. */ @@ -196,7 +191,7 @@ void LocalStore::addTempRoot(const Path & path) lockFile(fdTempRoots, ltWrite, true); string s = path + '\0'; - writeFull(fdTempRoots, (const unsigned char *) s.data(), s.size()); + writeFull(fdTempRoots, s); /* Downgrade to a read lock. */ debug(format("downgrading to read lock on ‘%1%’") % fnTempRoots); @@ -204,27 +199,6 @@ void LocalStore::addTempRoot(const Path & path) } -void removeTempRoots() -{ - if (fdTempRoots != -1) { - fdTempRoots.close(); - unlink(fnTempRoots.c_str()); - } -} - - -/* Automatically clean up the temporary roots file when we exit. */ -struct RemoveTempRoots -{ - ~RemoveTempRoots() - { - removeTempRoots(); - } -}; - -static RemoveTempRoots autoRemoveTempRoots __attribute__((unused)); - - typedef std::shared_ptr<AutoCloseFD> FDPtr; typedef list<FDPtr> FDs; @@ -257,7 +231,7 @@ static void readTempRoots(PathSet & tempRoots, FDs & fds) if (lockFile(*fd, ltWrite, false)) { printMsg(lvlError, format("removing stale temporary roots file ‘%1%’") % path); unlink(path.c_str()); - writeFull(*fd, (const unsigned char *) "d", 1); + writeFull(*fd, "d"); continue; } @@ -355,7 +329,8 @@ Roots LocalStore::findRoots() /* Process direct roots in {gcroots,manifests,profiles}. */ nix::findRoots(*this, settings.nixStateDir + "/" + gcRootsDir, DT_UNKNOWN, roots); - nix::findRoots(*this, settings.nixStateDir + "/manifests", DT_UNKNOWN, roots); + if (pathExists(settings.nixStateDir + "/manifests")) + nix::findRoots(*this, settings.nixStateDir + "/manifests", DT_UNKNOWN, roots); nix::findRoots(*this, settings.nixStateDir + "/profiles", DT_UNKNOWN, roots); return roots; @@ -749,7 +724,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results) } /* While we're at it, vacuum the database. */ - if (options.action == GCOptions::gcDeleteDead) vacuumDB(); + //if (options.action == GCOptions::gcDeleteDead) vacuumDB(); } diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index f08c877fe3d7..bc792baf296b 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -256,20 +256,23 @@ LocalStore::LocalStore(bool reserveSpace) if (chmod(perUserDir.c_str(), 01777) == -1) throw SysError(format("could not set permissions on ‘%1%’ to 1777") % perUserDir); + mode_t perm = 01735; + struct group * gr = getgrnam(settings.buildUsersGroup.c_str()); if (!gr) - throw Error(format("the group ‘%1%’ specified in ‘build-users-group’ does not exist") + printMsg(lvlError, format("warning: the group ‘%1%’ specified in ‘build-users-group’ does not exist") % settings.buildUsersGroup); - - struct stat st; - if (stat(settings.nixStore.c_str(), &st)) - throw SysError(format("getting attributes of path ‘%1%’") % settings.nixStore); - - if (st.st_uid != 0 || st.st_gid != gr->gr_gid || (st.st_mode & ~S_IFMT) != 01775) { - if (chown(settings.nixStore.c_str(), 0, gr->gr_gid) == -1) - throw SysError(format("changing ownership of path ‘%1%’") % settings.nixStore); - if (chmod(settings.nixStore.c_str(), 01775) == -1) - throw SysError(format("changing permissions on path ‘%1%’") % settings.nixStore); + else { + struct stat st; + if (stat(settings.nixStore.c_str(), &st)) + throw SysError(format("getting attributes of path ‘%1%’") % settings.nixStore); + + if (st.st_uid != 0 || st.st_gid != gr->gr_gid || (st.st_mode & ~S_IFMT) != perm) { + if (chown(settings.nixStore.c_str(), 0, gr->gr_gid) == -1) + throw SysError(format("changing ownership of path ‘%1%’") % settings.nixStore); + if (chmod(settings.nixStore.c_str(), perm) == -1) + throw SysError(format("changing permissions on path ‘%1%’") % settings.nixStore); + } } } @@ -358,7 +361,17 @@ LocalStore::~LocalStore() i->second.to.close(); i->second.from.close(); i->second.error.close(); - i->second.pid.wait(true); + if (i->second.pid != -1) + i->second.pid.wait(true); + } + } catch (...) { + ignoreException(); + } + + try { + if (fdTempRoots != -1) { + fdTempRoots.close(); + unlink(fnTempRoots.c_str()); } } catch (...) { ignoreException(); @@ -489,7 +502,7 @@ void LocalStore::makeStoreWritable() if (unshare(CLONE_NEWNS) == -1) throw SysError("setting up a private mount namespace"); - if (mount(0, settings.nixStore.c_str(), 0, MS_REMOUNT | MS_BIND, 0) == -1) + if (mount(0, settings.nixStore.c_str(), "none", MS_REMOUNT | MS_BIND, 0) == -1) throw SysError(format("remounting %1% writable") % settings.nixStore); } #endif diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh index dccdba533a0c..e0aabdba420d 100644 --- a/src/libstore/local-store.hh +++ b/src/libstore/local-store.hh @@ -244,6 +244,10 @@ private: bool didSetSubstituterEnv; + /* The file to which we write our temporary roots. */ + Path fnTempRoots; + AutoCloseFD fdTempRoots; + int getSchema(); void openDB(bool create); diff --git a/src/libstore/optimise-store.cc b/src/libstore/optimise-store.cc index dd18d66fa008..55c252b9b2e3 100644 --- a/src/libstore/optimise-store.cc +++ b/src/libstore/optimise-store.cc @@ -4,6 +4,7 @@ #include "local-store.hh" #include "globals.hh" +#include <cstdlib> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> diff --git a/src/libstore/pathlocks.cc b/src/libstore/pathlocks.cc index f26684afacb9..9db37e8f9aaa 100644 --- a/src/libstore/pathlocks.cc +++ b/src/libstore/pathlocks.cc @@ -33,7 +33,7 @@ void deleteLockFile(const Path & path, int fd) other processes waiting on this lock that the lock is stale (deleted). */ unlink(path.c_str()); - writeFull(fd, (const unsigned char *) "d", 1); + writeFull(fd, "d"); /* Note that the result of unlink() is ignored; removing the lock file is an optimisation, not a necessity. */ } diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc index a0e9f22410f7..d08913246321 100644 --- a/src/libstore/remote-store.cc +++ b/src/libstore/remote-store.cc @@ -10,6 +10,7 @@ #include <sys/stat.h> #include <sys/socket.h> #include <sys/un.h> +#include <errno.h> #include <fcntl.h> #include <iostream> @@ -87,8 +88,7 @@ void RemoteStore::openConnection(bool reserveSpace) processStderr(); } catch (Error & e) { - throw Error(format("cannot start worker (%1%)") - % e.msg()); + throw Error(format("cannot start daemon worker: %1%") % e.msg()); } setOptions(); @@ -110,7 +110,7 @@ void RemoteStore::connectToDaemon() applications... */ AutoCloseFD fdPrevDir = open(".", O_RDONLY); if (fdPrevDir == -1) throw SysError("couldn't open current directory"); - chdir(dirOf(socketPath).c_str()); + if (chdir(dirOf(socketPath).c_str()) == -1) throw SysError(format("couldn't change to directory of ‘%1%’") % socketPath); Path socketPathRel = "./" + baseNameOf(socketPath); struct sockaddr_un addr; |