diff options
-rw-r--r-- | daemon.am | 7 | ||||
-rw-r--r-- | nix/libstore/build.cc | 313 | ||||
-rw-r--r-- | nix/libstore/gc.cc | 69 | ||||
-rw-r--r-- | nix/libstore/globals.cc | 92 | ||||
-rw-r--r-- | nix/libstore/globals.hh | 26 | ||||
-rw-r--r-- | nix/libstore/local-store.cc | 81 | ||||
-rw-r--r-- | nix/libstore/local-store.hh | 19 | ||||
-rw-r--r-- | nix/libstore/optimise-store.cc | 16 | ||||
-rw-r--r-- | nix/libstore/remote-store.cc | 31 | ||||
-rw-r--r-- | nix/libstore/remote-store.hh | 5 | ||||
-rw-r--r-- | nix/libstore/store-api.hh | 4 | ||||
-rw-r--r-- | nix/libstore/worker-protocol.hh | 2 | ||||
-rw-r--r-- | nix/libutil/archive.cc | 171 | ||||
-rw-r--r-- | nix/libutil/archive.hh | 12 | ||||
-rw-r--r-- | nix/libutil/serialise.cc | 27 | ||||
-rw-r--r-- | nix/libutil/serialise.hh | 11 | ||||
-rw-r--r-- | nix/libutil/types.hh | 17 | ||||
-rw-r--r-- | nix/libutil/util.cc | 176 | ||||
-rw-r--r-- | nix/libutil/util.hh | 30 | ||||
-rw-r--r-- | nix/nix-daemon/guix-daemon.cc | 25 | ||||
-rw-r--r-- | nix/nix-daemon/nix-daemon.cc | 134 | ||||
-rw-r--r-- | tests/guix-archive.sh | 3 |
22 files changed, 750 insertions, 521 deletions
diff --git a/daemon.am b/daemon.am index 9d6516ae6c..50b488923a 100644 --- a/daemon.am +++ b/daemon.am @@ -25,8 +25,8 @@ CLEANFILES += $(BUILT_SOURCES) noinst_LIBRARIES = libformat.a libutil.a libstore.a -# Use '-std=c++0x' for 'std::shared_ptr'. -AM_CXXFLAGS = -Wall -std=c++0x +# Use '-std=c++11' for 'std::shared_ptr', 'auto', lambdas, and more. +AM_CXXFLAGS = -Wall -std=c++11 libformat_a_SOURCES = \ nix/boost/format/free_funcs.cc \ @@ -112,7 +112,8 @@ libstore_a_CPPFLAGS = \ -DNIX_CONF_DIR=\"$(sysconfdir)/guix\" \ -DNIX_LIBEXEC_DIR=\"$(libexecdir)\" \ -DNIX_BIN_DIR=\"$(bindir)\" \ - -DOPENSSL_PATH="\"guix-authenticate\"" + -DOPENSSL_PATH="\"guix-authenticate\"" \ + -DDEFAULT_CHROOT_DIRS="\"\"" libstore_a_CXXFLAGS = $(AM_CXXFLAGS) \ $(SQLITE3_CFLAGS) $(LIBGCRYPT_CFLAGS) diff --git a/nix/libstore/build.cc b/nix/libstore/build.cc index f38cd29940..009fcb2c0c 100644 --- a/nix/libstore/build.cc +++ b/nix/libstore/build.cc @@ -57,9 +57,8 @@ #include <netinet/ip.h> #endif -#if HAVE_SYS_PERSONALITY_H +#if __linux__ #include <sys/personality.h> -#define CAN_DO_LINUX32_BUILDS #endif #if HAVE_STATVFS @@ -85,8 +84,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). */ @@ -173,11 +176,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. */ @@ -238,6 +250,9 @@ public: failure). */ bool permanentFailure; + /* Set if at least one derivation had a timeout. */ + bool timedOut; + LocalStore & store; std::shared_ptr<HookInstance> hook; @@ -301,6 +316,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); @@ -374,8 +390,6 @@ void Goal::trace(const format & f) /* Common initialisation performed in child processes. */ static void commonChildInit(Pipe & logPipe) { - restoreAffinity(); - /* Put the child in a separate session (and thus a separate process group) so that it has no controlling terminal (meaning that e.g. ssh cannot open /dev/tty) and it doesn't receive @@ -590,7 +604,9 @@ HookInstance::HookInstance() { debug("starting build hook"); - Path buildHook = absPath(getEnv("NIX_BUILD_HOOK")); + Path buildHook = getEnv("NIX_BUILD_HOOK"); + if (string(buildHook, 0, 1) != "/") buildHook = settings.nixLibexecDir + "/nix/" + buildHook; + buildHook = canonPath(buildHook); /* Create a pipe to get the output of the child. */ fromHook.create(); @@ -602,44 +618,30 @@ HookInstance::HookInstance() builderOut.create(); /* Fork the hook. */ - pid = maybeVfork(); - switch (pid) { - - case -1: - throw SysError("unable to fork"); + pid = startProcess([&]() { - case 0: - try { /* child */ + commonChildInit(fromHook); - commonChildInit(fromHook); + if (chdir("/") == -1) throw SysError("changing into `/"); - if (chdir("/") == -1) throw SysError("changing into `/"); + /* Dup the communication pipes. */ + if (dup2(toHook.readSide, STDIN_FILENO) == -1) + throw SysError("dupping to-hook read side"); - /* Dup the communication pipes. */ - if (dup2(toHook.readSide, STDIN_FILENO) == -1) - throw SysError("dupping to-hook read side"); + /* Use fd 4 for the builder's stdout/stderr. */ + if (dup2(builderOut.writeSide, 4) == -1) + throw SysError("dupping builder's stdout/stderr"); - /* Use fd 4 for the builder's stdout/stderr. */ - if (dup2(builderOut.writeSide, 4) == -1) - throw SysError("dupping builder's stdout/stderr"); + execl(buildHook.c_str(), buildHook.c_str(), settings.thisSystem.c_str(), + (format("%1%") % settings.maxSilentTime).str().c_str(), + (format("%1%") % settings.printBuildTrace).str().c_str(), + (format("%1%") % settings.buildTimeout).str().c_str(), + NULL); - execl(buildHook.c_str(), buildHook.c_str(), settings.thisSystem.c_str(), - (format("%1%") % settings.maxSilentTime).str().c_str(), - (format("%1%") % settings.printBuildTrace).str().c_str(), - (format("%1%") % settings.buildTimeout).str().c_str(), - NULL); + throw SysError(format("executing `%1%'") % buildHook); + }); - throw SysError(format("executing `%1%'") % buildHook); - - } catch (std::exception & e) { - writeToStderr("build hook error: " + string(e.what()) + "\n"); - } - _exit(1); - } - - /* parent */ pid.setSeparatePG(true); - pid.setKillSignal(SIGTERM); fromHook.writeSide.close(); toHook.readSide.close(); } @@ -648,7 +650,8 @@ HookInstance::HookInstance() HookInstance::~HookInstance() { try { - pid.kill(); + toHook.writeSide.close(); + pid.kill(true); } catch (...) { ignoreException(); } @@ -784,17 +787,21 @@ private: outputs to allow hard links between outputs. */ InodesSeen inodesSeen; - /* Magic exit code denoting that setting up the child environment - failed. (It's possible that the child actually returns the - exit code, but ah well.) */ - const static int childSetupFailed = 189; - public: DerivationGoal(const Path & drvPath, const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode = bmNormal); ~DerivationGoal(); 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() @@ -879,13 +886,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(); } } @@ -956,6 +959,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; @@ -1209,7 +1217,7 @@ static string get(const StringPairs & map, const string & key) static bool canBuildLocally(const string & platform) { return platform == settings.thisSystem -#ifdef CAN_DO_LINUX32_BUILDS +#if __linux__ || (platform == "i686-linux" && settings.thisSystem == "x86_64-linux") #endif ; @@ -1433,9 +1441,6 @@ void DerivationGoal::buildDone() if (pathExists(chrootRootDir + *i)) rename((chrootRootDir + *i).c_str(), i->c_str()); - if (WIFEXITED(status) && WEXITSTATUS(status) == childSetupFailed) - throw Error(format("failed to set up the build environment for `%1%'") % drvPath); - if (diskFull) printMsg(lvlError, "note: build failure may have been caused by lack of free disk space"); @@ -1469,37 +1474,41 @@ void DerivationGoal::buildDone() outputLocks.unlock(); } catch (BuildError & e) { - printMsg(lvlError, e.msg()); + if (!hook) + printMsg(lvlError, e.msg()); outputLocks.unlock(); buildUser.release(); - /* When using a build hook, the hook will return a remote - build failure using exit code 100. Anything else is a hook - problem. */ - bool hookError = hook && - (!WIFEXITED(status) || WEXITSTATUS(status) != 100); + if (hook && WIFEXITED(status) && WEXITSTATUS(status) == 101) { + if (settings.printBuildTrace) + printMsg(lvlError, format("@ build-failed %1% - timeout") % drvPath); + worker.timedOut = true; + } - if (settings.printBuildTrace) { - if (hook && hookError) + else if (hook && (!WIFEXITED(status) || WEXITSTATUS(status) != 100)) { + if (settings.printBuildTrace) printMsg(lvlError, format("@ hook-failed %1% - %2% %3%") % drvPath % status % e.msg()); - else + } + + else { + if (settings.printBuildTrace) printMsg(lvlError, format("@ build-failed %1% - %2% %3%") % drvPath % 1 % e.msg()); + worker.permanentFailure = !fixedOutput && !diskFull; + + /* Register the outputs of this build as "failed" so we + won't try to build them again (negative caching). + However, don't do this for fixed-output derivations, + since they're likely to fail for transient reasons + (e.g., fetchurl not being able to access the network). + Hook errors (like communication problems with the + remote machine) shouldn't be cached either. */ + if (settings.cacheFailure && !fixedOutput && !diskFull) + foreach (DerivationOutputs::iterator, i, drv.outputs) + worker.store.registerFailedPath(i->second.path); } - /* Register the outputs of this build as "failed" so we won't - try to build them again (negative caching). However, don't - do this for fixed-output derivations, since they're likely - to fail for transient reasons (e.g., fetchurl not being - able to access the network). Hook errors (like - communication problems with the remote machine) shouldn't - be cached either. */ - if (settings.cacheFailure && !hookError && !fixedOutput) - foreach (DerivationOutputs::iterator, i, drv.outputs) - worker.store.registerFailedPath(i->second.path); - - worker.permanentFailure = !hookError && !fixedOutput && !diskFull; amDone(ecFailed); return; } @@ -1825,12 +1834,15 @@ void DerivationGoal::startBuilder() /* Bind-mount a user-configurable set of directories from the host file system. */ - foreach (StringSet::iterator, i, settings.dirsInChroot) { - size_t p = i->find('='); + 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; + dirsInChroot[i] = i; else - dirsInChroot[string(*i, 0, p)] = string(*i, p + 1); + dirsInChroot[string(i, 0, p)] = string(i, p + 1); } dirsInChroot[tmpDir] = tmpDir; @@ -1969,10 +1981,15 @@ void DerivationGoal::startBuilder() worker.childStarted(shared_from_this(), pid, singleton<set<int> >(builderOut.readSide), true, true); + /* Check if setting up the build environment failed. */ + string msg = readLine(builderOut.readSide); + if (!msg.empty()) throw Error(msg); + if (settings.printBuildTrace) { printMsg(lvlError, format("@ build-started %1% - %2% %3%") % drvPath % drv.platform % logFile); } + } @@ -1981,10 +1998,14 @@ void DerivationGoal::initChild() /* Warning: in the child we should absolutely not make any SQLite calls! */ - bool inSetup = true; - try { /* child */ + _writeToStderr = 0; + + restoreAffinity(); + + commonChildInit(builderOut); + #if CHROOT_ENABLED if (useChroot) { /* Initialise the loopback interface. */ @@ -2088,9 +2109,9 @@ void DerivationGoal::initChild() throw SysError("mounting /dev/pts"); createSymlink("/dev/pts/ptmx", chrootRootDir + "/dev/ptmx"); - /* Make sure /dev/pts/ptmx is world-writable. With some - Linux versions, it is created with permissions 0. */ - chmod_(chrootRootDir + "/dev/pts/ptmx", 0666); + /* Make sure /dev/pts/ptmx is world-writable. With some + Linux versions, it is created with permissions 0. */ + chmod_(chrootRootDir + "/dev/pts/ptmx", 0666); } /* Do the chroot(). Below we do a chdir() to the @@ -2103,15 +2124,13 @@ void DerivationGoal::initChild() } #endif - commonChildInit(builderOut); - if (chdir(tmpDir.c_str()) == -1) throw SysError(format("changing into `%1%'") % tmpDir); /* Close all other file descriptors. */ closeMostFDs(set<int>()); -#ifdef CAN_DO_LINUX32_BUILDS +#if __linux__ /* Change the personality to 32-bit if we're doing an i686-linux build on an x86_64-linux machine. */ struct utsname utsbuf; @@ -2119,7 +2138,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(0x0008 | 0x8000000 /* == PER_LINUX32_3GB */) == -1) + if (personality(PER_LINUX32) == -1) throw SysError("cannot set i686-linux personality"); } @@ -2129,6 +2148,11 @@ void DerivationGoal::initChild() int cur = personality(0xffffffff); if (cur != -1) personality(cur | 0x0020000 /* == UNAME26 */); } + + /* Disable address space randomization for improved + determinism. */ + int cur = personality(0xffffffff); + if (cur != -1) personality(cur | ADDR_NO_RANDOMIZE); #endif /* Fill in the environment. */ @@ -2167,21 +2191,28 @@ void DerivationGoal::initChild() /* Fill in the arguments. */ string builderBasename = baseNameOf(drv.builder); args.push_back(builderBasename.c_str()); - foreach (Strings::iterator, i, drv.args) - args.push_back(rewriteHashes(*i, rewritesToTmp).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); + } args.push_back(0); restoreSIGPIPE(); + /* Indicate that we managed to set up the build environment. */ + writeToStderr("\n"); + /* Execute the program. This should not return. */ - inSetup = false; execve(program.c_str(), (char * *) &args[0], (char * *) envArr); throw SysError(format("executing `%1%'") % drv.builder); } catch (std::exception & e) { - writeToStderr("build error: " + string(e.what()) + "\n"); - _exit(inSetup ? childSetupFailed : 1); + writeToStderr("while setting up the build environment: " + string(e.what()) + "\n"); + _exit(1); } abort(); /* never reached */ @@ -2333,7 +2364,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; } @@ -2347,16 +2378,36 @@ void DerivationGoal::registerOutputs() debug(format("referenced input: `%1%'") % *i); } - /* If the derivation specifies an `allowedReferences' - attribute (containing a list of paths that the output may - refer to), check that all references are in that list. !!! - allowedReferences should really be per-output. */ - if (drv.env.find("allowedReferences") != drv.env.end()) { - PathSet allowed = parseReferenceSpecifiers(drv, get(drv.env, "allowedReferences")); - foreach (PathSet::iterator, i, references) - if (allowed.find(*i) == allowed.end()) - throw BuildError(format("output is not allowed to refer to path `%1%'") % *i); - } + /* Enforce `allowedReferences' and friends. */ + auto checkRefs = [&](const string & attrName, bool allowed, bool recursive) { + if (drv.env.find(attrName) == drv.env.end()) return; + + PathSet spec = parseReferenceSpecifiers(drv, get(drv.env, attrName)); + + PathSet used; + if (recursive) { + /* Our requisites are the union of the closures of our references. */ + for (auto & i : references) + /* Don't call computeFSClosure on ourselves. */ + if (actualPath != i) + computeFSClosure(worker.store, i, used); + } else + used = references; + + for (auto & i : used) + if (allowed) { + if (spec.find(i) == spec.end()) + throw BuildError(format("output (`%1%') is not allowed to refer to path `%2%'") % actualPath % i); + } else { + if (spec.find(i) != spec.end()) + throw BuildError(format("output (`%1%') is not allowed to refer to path `%2%'") % actualPath % i); + } + }; + + checkRefs("allowedReferences", true, false); + checkRefs("allowedRequisites", true, true); + checkRefs("disallowedReferences", false, false); + checkRefs("disallowedRequisites", false, true); worker.store.optimisePath(path); // FIXME: combine with scanForReferences() @@ -2586,6 +2637,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. */ @@ -2781,32 +2839,18 @@ void SubstitutionGoal::tryToRun() const char * * argArr = strings2CharPtrs(args); /* Fork the substitute program. */ - pid = maybeVfork(); - - switch (pid) { + pid = startProcess([&]() { - case -1: - throw SysError("unable to fork"); + commonChildInit(logPipe); - case 0: - try { /* child */ + if (dup2(outPipe.writeSide, STDOUT_FILENO) == -1) + throw SysError("cannot dup output pipe into stdout"); - commonChildInit(logPipe); + execv(sub.c_str(), (char * *) argArr); - if (dup2(outPipe.writeSide, STDOUT_FILENO) == -1) - throw SysError("cannot dup output pipe into stdout"); + throw SysError(format("executing `%1%'") % sub); + }); - execv(sub.c_str(), (char * *) argArr); - - throw SysError(format("executing `%1%'") % sub); - - } catch (std::exception & e) { - writeToStderr("substitute error: " + string(e.what()) + "\n"); - } - _exit(1); - } - - /* parent */ pid.setSeparatePG(true); pid.setKillSignal(SIGTERM); outPipe.writeSide.close(); @@ -2944,6 +2988,7 @@ Worker::Worker(LocalStore & store) nrLocalBuilds = 0; lastWokenUp = 0; permanentFailure = false; + timedOut = false; } @@ -3109,15 +3154,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 } } @@ -3255,6 +3304,7 @@ void Worker::waitForInput() format("%1% timed out after %2% seconds of silence") % goal->getName() % settings.maxSilentTime); goal->cancel(true); + timedOut = true; } else if (goal->getExitCode() == Goal::ecBusy && @@ -3266,6 +3316,7 @@ void Worker::waitForInput() format("%1% timed out after %2% seconds") % goal->getName() % settings.buildTimeout); goal->cancel(true); + timedOut = true; } } @@ -3282,7 +3333,7 @@ void Worker::waitForInput() unsigned int Worker::exitStatus() { - return permanentFailure ? 100 : 1; + return timedOut ? 101 : (permanentFailure ? 100 : 1); } diff --git a/nix/libstore/gc.cc b/nix/libstore/gc.cc index f90edac1cd..f98e02c1e2 100644 --- a/nix/libstore/gc.cc +++ b/nix/libstore/gc.cc @@ -115,7 +115,10 @@ Path addPermRoot(StoreAPI & store, const Path & _storePath, % gcRoot % rootsDir); } - makeSymlink(gcRoot, storePath); + if (baseNameOf(gcRoot) == baseNameOf(storePath)) + writeFile(gcRoot, ""); + else + makeSymlink(gcRoot, storePath); } /* Check that the root can be found by the garbage collector. @@ -142,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. */ @@ -201,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; @@ -230,11 +207,11 @@ static void readTempRoots(PathSet & tempRoots, FDs & fds) { /* Read the `temproots' directory for per-process temporary root files. */ - Strings tempRootFiles = readDirectory( + DirEntries tempRootFiles = readDirectory( (format("%1%/%2%") % settings.nixStateDir % tempRootsDir).str()); - foreach (Strings::iterator, i, tempRootFiles) { - Path path = (format("%1%/%2%/%3%") % settings.nixStateDir % tempRootsDir % *i).str(); + for (auto & i : tempRootFiles) { + Path path = (format("%1%/%2%/%3%") % settings.nixStateDir % tempRootsDir % i.name).str(); debug(format("reading temporary root file `%1%'") % path); FDPtr fd(new AutoCloseFD(open(path.c_str(), O_RDWR, 0666))); @@ -294,19 +271,19 @@ static void foundRoot(StoreAPI & store, } -static void findRoots(StoreAPI & store, const Path & path, Roots & roots) +static void findRoots(StoreAPI & store, const Path & path, unsigned char type, Roots & roots) { try { - struct stat st = lstat(path); + if (type == DT_UNKNOWN) + type = getFileType(path); - if (S_ISDIR(st.st_mode)) { - Strings names = readDirectory(path); - foreach (Strings::iterator, i, names) - findRoots(store, path + "/" + *i, roots); + if (type == DT_DIR) { + for (auto & i : readDirectory(path)) + findRoots(store, path + "/" + i.name, i.type, roots); } - else if (S_ISLNK(st.st_mode)) { + else if (type == DT_LNK) { Path target = readLink(path); if (isInStore(target)) foundRoot(store, path, target, roots); @@ -328,6 +305,12 @@ static void findRoots(StoreAPI & store, const Path & path, Roots & roots) } } + else if (type == DT_REG) { + Path storePath = settings.nixStore + "/" + baseNameOf(path); + if (store.isValidPath(storePath)) + roots[path] = storePath; + } + } catch (SysError & e) { @@ -345,9 +328,10 @@ Roots LocalStore::findRoots() Roots roots; /* Process direct roots in {gcroots,manifests,profiles}. */ - nix::findRoots(*this, settings.nixStateDir + "/" + gcRootsDir, roots); - nix::findRoots(*this, settings.nixStateDir + "/manifests", roots); - nix::findRoots(*this, settings.nixStateDir + "/profiles", roots); + nix::findRoots(*this, settings.nixStateDir + "/" + gcRootsDir, 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; } @@ -449,7 +433,6 @@ void LocalStore::deletePathRecursive(GCState & state, const Path & path) // if the path was not valid, need to determine the actual // size. state.bytesInvalidated += size; - // Mac OS X cannot rename directories if they are read-only. if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1) throw SysError(format("making `%1%' writable") % path); Path tmp = state.trashDir + "/" + baseNameOf(path); @@ -649,7 +632,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results) /* After this point the set of roots or temporary roots cannot increase, since we hold locks on everything. So everything - that is not reachable from `roots'. */ + that is not reachable from `roots' is garbage. */ if (state.shouldDelete) { if (pathExists(state.trashDir)) deleteGarbage(state, state.trashDir); @@ -741,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/nix/libstore/globals.cc b/nix/libstore/globals.cc index 86fa56739c..bb08a7d0b0 100644 --- a/nix/libstore/globals.cc +++ b/nix/libstore/globals.cc @@ -2,6 +2,7 @@ #include "globals.hh" #include "util.hh" +#include "archive.hh" #include <map> #include <algorithm> @@ -55,6 +56,7 @@ Settings::Settings() envKeepDerivations = false; lockCPU = getEnv("NIX_AFFINITY_HACK", "1") == "1"; showTrace = false; + enableImportNative = false; } @@ -112,35 +114,61 @@ void Settings::set(const string & name, const string & value) } +string Settings::get(const string & name, const string & def) +{ + auto i = settings.find(name); + if (i == settings.end()) return def; + return i->second; +} + + +Strings Settings::get(const string & name, const Strings & def) +{ + auto i = settings.find(name); + if (i == settings.end()) return def; + return tokenizeString<Strings>(i->second); +} + + +bool Settings::get(const string & name, bool def) +{ + bool res = def; + _get(res, name); + return res; +} + + void Settings::update() { - get(tryFallback, "build-fallback"); - get(maxBuildJobs, "build-max-jobs"); - get(buildCores, "build-cores"); - get(thisSystem, "system"); - get(maxSilentTime, "build-max-silent-time"); - get(buildTimeout, "build-timeout"); - get(reservedSize, "gc-reserved-space"); - get(fsyncMetadata, "fsync-metadata"); - get(useSQLiteWAL, "use-sqlite-wal"); - get(syncBeforeRegistering, "sync-before-registering"); - get(useSubstitutes, "build-use-substitutes"); - get(buildUsersGroup, "build-users-group"); - get(useChroot, "build-use-chroot"); - get(dirsInChroot, "build-chroot-dirs"); - get(impersonateLinux26, "build-impersonate-linux-26"); - get(keepLog, "build-keep-log"); - get(compressLog, "build-compress-log"); - get(maxLogSize, "build-max-log-size"); - get(cacheFailure, "build-cache-failure"); - get(pollInterval, "build-poll-interval"); - get(checkRootReachability, "gc-check-reachability"); - get(gcKeepOutputs, "gc-keep-outputs"); - get(gcKeepDerivations, "gc-keep-derivations"); - get(autoOptimiseStore, "auto-optimise-store"); - get(envKeepDerivations, "env-keep-derivations"); - get(sshSubstituterHosts, "ssh-substituter-hosts"); - get(useSshSubstituter, "use-ssh-substituter"); + _get(tryFallback, "build-fallback"); + _get(maxBuildJobs, "build-max-jobs"); + _get(buildCores, "build-cores"); + _get(thisSystem, "system"); + _get(maxSilentTime, "build-max-silent-time"); + _get(buildTimeout, "build-timeout"); + _get(reservedSize, "gc-reserved-space"); + _get(fsyncMetadata, "fsync-metadata"); + _get(useSQLiteWAL, "use-sqlite-wal"); + _get(syncBeforeRegistering, "sync-before-registering"); + _get(useSubstitutes, "build-use-substitutes"); + _get(buildUsersGroup, "build-users-group"); + _get(useChroot, "build-use-chroot"); + _get(impersonateLinux26, "build-impersonate-linux-26"); + _get(keepLog, "build-keep-log"); + _get(compressLog, "build-compress-log"); + _get(maxLogSize, "build-max-log-size"); + _get(cacheFailure, "build-cache-failure"); + _get(pollInterval, "build-poll-interval"); + _get(checkRootReachability, "gc-check-reachability"); + _get(gcKeepOutputs, "gc-keep-outputs"); + _get(gcKeepDerivations, "gc-keep-derivations"); + _get(autoOptimiseStore, "auto-optimise-store"); + _get(envKeepDerivations, "env-keep-derivations"); + _get(sshSubstituterHosts, "ssh-substituter-hosts"); + _get(useSshSubstituter, "use-ssh-substituter"); + _get(logServers, "log-servers"); + _get(enableImportNative, "allow-unsafe-native-code-during-evaluation"); + _get(useCaseHack, "use-case-hack"); string subs = getEnv("NIX_SUBSTITUTERS", "default"); if (subs == "default") { @@ -158,7 +186,7 @@ void Settings::update() } -void Settings::get(string & res, const string & name) +void Settings::_get(string & res, const string & name) { SettingsMap::iterator i = settings.find(name); if (i == settings.end()) return; @@ -166,7 +194,7 @@ void Settings::get(string & res, const string & name) } -void Settings::get(bool & res, const string & name) +void Settings::_get(bool & res, const string & name) { SettingsMap::iterator i = settings.find(name); if (i == settings.end()) return; @@ -177,7 +205,7 @@ void Settings::get(bool & res, const string & name) } -void Settings::get(StringSet & res, const string & name) +void Settings::_get(StringSet & res, const string & name) { SettingsMap::iterator i = settings.find(name); if (i == settings.end()) return; @@ -186,7 +214,7 @@ void Settings::get(StringSet & res, const string & name) res.insert(ss.begin(), ss.end()); } -void Settings::get(Strings & res, const string & name) +void Settings::_get(Strings & res, const string & name) { SettingsMap::iterator i = settings.find(name); if (i == settings.end()) return; @@ -194,7 +222,7 @@ void Settings::get(Strings & res, const string & name) } -template<class N> void Settings::get(N & res, const string & name) +template<class N> void Settings::_get(N & res, const string & name) { SettingsMap::iterator i = settings.find(name); if (i == settings.end()) return; diff --git a/nix/libstore/globals.hh b/nix/libstore/globals.hh index 711c365294..c17e10d7c3 100644 --- a/nix/libstore/globals.hh +++ b/nix/libstore/globals.hh @@ -21,6 +21,12 @@ struct Settings { void set(const string & name, const string & value); + string get(const string & name, const string & def); + + Strings get(const string & name, const Strings & def); + + bool get(const string & name, bool def); + void update(); string pack(); @@ -142,10 +148,6 @@ struct Settings { /* Whether to build in chroot. */ bool useChroot; - /* The directories from the host filesystem to be included in the - chroot. */ - StringSet dirsInChroot; - /* Set of ssh connection strings for the ssh substituter */ Strings sshSubstituterHosts; @@ -197,14 +199,20 @@ struct Settings { /* Whether to show a stack trace if Nix evaluation fails. */ bool showTrace; + /* A list of URL prefixes that can return Nix build logs. */ + Strings logServers; + + /* Whether the importNative primop should be enabled */ + bool enableImportNative; + private: SettingsMap settings, overrides; - void get(string & res, const string & name); - void get(bool & res, const string & name); - void get(StringSet & res, const string & name); - void get(Strings & res, const string & name); - template<class N> void get(N & res, const string & name); + void _get(string & res, const string & name); + void _get(bool & res, const string & name); + void _get(StringSet & res, const string & name); + void _get(Strings & res, const string & name); + template<class N> void _get(N & res, const string & name); }; diff --git a/nix/libstore/local-store.cc b/nix/libstore/local-store.cc index 2c3d65215c..a890ab56b3 100644 --- a/nix/libstore/local-store.cc +++ b/nix/libstore/local-store.cc @@ -358,7 +358,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(); @@ -551,9 +561,9 @@ static void canonicalisePathMetaData_(const Path & path, uid_t fromUid, InodesSe if (lstat(path.c_str(), &st)) throw SysError(format("getting attributes of path `%1%'") % path); - /* Really make sure that the path is of a supported type. This - has already been checked in dumpPath(). */ - assert(S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)); + /* Really make sure that the path is of a supported type. */ + if (!(S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))) + throw Error(format("file ‘%1%’ has an unsupported type") % path); /* Fail if the file is not owned by the build user. This prevents us from messing up the ownership/permissions of files @@ -593,9 +603,9 @@ static void canonicalisePathMetaData_(const Path & path, uid_t fromUid, InodesSe } if (S_ISDIR(st.st_mode)) { - Strings names = readDirectory(path); - foreach (Strings::iterator, i, names) - canonicalisePathMetaData_(path + "/" + *i, fromUid, inodesSeen); + DirEntries entries = readDirectory(path); + for (auto & i : entries) + canonicalisePathMetaData_(path + "/" + i.name, fromUid, inodesSeen); } } @@ -1083,31 +1093,16 @@ void LocalStore::startSubstituter(const Path & substituter, RunningSubstituter & setSubstituterEnv(); - run.pid = maybeVfork(); - - switch (run.pid) { - - case -1: - throw SysError("unable to fork"); - - case 0: /* child */ - try { - restoreAffinity(); - if (dup2(toPipe.readSide, STDIN_FILENO) == -1) - throw SysError("dupping stdin"); - if (dup2(fromPipe.writeSide, STDOUT_FILENO) == -1) - throw SysError("dupping stdout"); - if (dup2(errorPipe.writeSide, STDERR_FILENO) == -1) - throw SysError("dupping stderr"); - execl(substituter.c_str(), substituter.c_str(), "--query", NULL); - throw SysError(format("executing `%1%'") % substituter); - } catch (std::exception & e) { - std::cerr << "error: " << e.what() << std::endl; - } - _exit(1); - } - - /* Parent. */ + run.pid = startProcess([&]() { + if (dup2(toPipe.readSide, STDIN_FILENO) == -1) + throw SysError("dupping stdin"); + if (dup2(fromPipe.writeSide, STDOUT_FILENO) == -1) + throw SysError("dupping stdout"); + if (dup2(errorPipe.writeSide, STDERR_FILENO) == -1) + throw SysError("dupping stderr"); + execl(substituter.c_str(), substituter.c_str(), "--query", NULL); + throw SysError(format("executing `%1%'") % substituter); + }); run.program = baseNameOf(substituter); run.to = toPipe.writeSide.borrow(); @@ -1170,8 +1165,7 @@ string LocalStore::getLineFromSubstituter(RunningSubstituter & run) string::size_type p; while (((p = err.find('\n')) != string::npos) || ((p = err.find('\r')) != string::npos)) { - string thing(err, 0, p + 1); - writeToStderr(run.program + ": " + thing); + printMsg(lvlError, run.program + ": " + string(err, 0, p)); err = string(err, p + 1); } } @@ -1503,7 +1497,8 @@ void LocalStore::exportPath(const Path & path, bool sign, { assertStorePath(path); - addTempRoot(path); + printMsg(lvlInfo, format("exporting path `%1%'") % path); + if (!isValidPath(path)) throw Error(format("path `%1%' is not valid") % path); @@ -1613,8 +1608,6 @@ Path LocalStore::importPath(bool requireSignature, Source & source) Path dstPath = readStorePath(hashAndReadSource); - printMsg(lvlInfo, format("importing path `%1%'") % dstPath); - PathSet references = readStorePaths<PathSet>(hashAndReadSource); Path deriver = readString(hashAndReadSource); @@ -1747,8 +1740,8 @@ bool LocalStore::verifyStore(bool checkContents, bool repair) /* Acquire the global GC lock to prevent a garbage collection. */ AutoCloseFD fdGCLock = openGCLock(ltWrite); - Paths entries = readDirectory(settings.nixStore); - PathSet store(entries.begin(), entries.end()); + PathSet store; + for (auto & i : readDirectory(settings.nixStore)) store.insert(i.name); /* Check whether all valid paths actually exist. */ printMsg(lvlInfo, "checking path existence..."); @@ -1898,9 +1891,8 @@ void LocalStore::markContentsGood(const Path & path) PathSet LocalStore::queryValidPathsOld() { PathSet paths; - Strings entries = readDirectory(settings.nixDBPath + "/info"); - foreach (Strings::iterator, i, entries) - if (i->at(0) != '.') paths.insert(settings.nixStore + "/" + *i); + for (auto & i : readDirectory(settings.nixDBPath + "/info")) + if (i.name.at(0) != '.') paths.insert(settings.nixStore + "/" + i.name); return paths; } @@ -1987,9 +1979,8 @@ static void makeMutable(const Path & path) if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) return; if (S_ISDIR(st.st_mode)) { - Strings names = readDirectory(path); - foreach (Strings::iterator, i, names) - makeMutable(path + "/" + *i); + for (auto & i : readDirectory(path)) + makeMutable(path + "/" + i.name); } /* The O_NOFOLLOW is important to prevent us from changing the diff --git a/nix/libstore/local-store.hh b/nix/libstore/local-store.hh index 54331e448a..e0aabdba42 100644 --- a/nix/libstore/local-store.hh +++ b/nix/libstore/local-store.hh @@ -1,16 +1,12 @@ #pragma once #include <string> +#include <unordered_set> #include "store-api.hh" #include "util.hh" #include "pathlocks.hh" -#if HAVE_TR1_UNORDERED_SET -#include <tr1/unordered_set> -#endif - - class sqlite3; class sqlite3_stmt; @@ -171,6 +167,9 @@ public: files with the same contents. */ void optimiseStore(OptimiseStats & stats); + /* Generic variant of the above method. */ + void optimiseStore(); + /* Optimise a single store path. */ void optimisePath(const Path & path); @@ -245,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); @@ -306,11 +309,7 @@ private: void checkDerivationOutputs(const Path & drvPath, const Derivation & drv); -#if HAVE_TR1_UNORDERED_SET - typedef std::tr1::unordered_set<ino_t> InodeHash; -#else - typedef std::set<ino_t> InodeHash; -#endif + typedef std::unordered_set<ino_t> InodeHash; InodeHash loadInodeHash(); Strings readDirectoryIgnoringInodes(const Path & path, const InodeHash & inodeHash); diff --git a/nix/libstore/optimise-store.cc b/nix/libstore/optimise-store.cc index 67ee94a4bd..8ba9d1a263 100644 --- a/nix/libstore/optimise-store.cc +++ b/nix/libstore/optimise-store.cc @@ -225,6 +225,22 @@ void LocalStore::optimiseStore(OptimiseStats & stats) } } +static string showBytes(unsigned long long bytes) +{ + return (format("%.2f MiB") % (bytes / (1024.0 * 1024.0))).str(); +} + +void LocalStore::optimiseStore() +{ + OptimiseStats stats; + + optimiseStore(stats); + + printMsg(lvlError, + format("%1% freed by hard-linking %2% files") + % showBytes(stats.bytesFreed) + % stats.filesLinked); +} void LocalStore::optimisePath(const Path & path) { diff --git a/nix/libstore/remote-store.cc b/nix/libstore/remote-store.cc index 4619206932..448d9b6bc1 100644 --- a/nix/libstore/remote-store.cc +++ b/nix/libstore/remote-store.cc @@ -87,8 +87,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(); @@ -133,8 +132,6 @@ RemoteStore::~RemoteStore() try { to.flush(); fdSocket.close(); - if (child != -1) - child.wait(true); } catch (...) { ignoreException(); } @@ -402,8 +399,23 @@ Path RemoteStore::addToStore(const Path & _srcPath, writeInt((hashAlgo == htSHA256 && recursive) ? 0 : 1, to); writeInt(recursive ? 1 : 0, to); writeString(printHashType(hashAlgo), to); - dumpPath(srcPath, to, filter); - processStderr(); + + try { + to.written = 0; + to.warn = true; + dumpPath(srcPath, to, filter); + to.warn = false; + processStderr(); + } catch (SysError & e) { + /* Daemon closed while we were sending the path. Probably OOM + or I/O error. */ + if (e.errNo == EPIPE) + try { + processStderr(); + } catch (EndOfFile & e) { } + throw; + } + return readStorePath(from); } @@ -564,6 +576,13 @@ void RemoteStore::clearFailedPaths(const PathSet & paths) readInt(from); } +void RemoteStore::optimiseStore() +{ + openConnection(); + writeInt(wopOptimiseStore, to); + processStderr(); + readInt(from); +} void RemoteStore::processStderr(Sink * sink, Source * source) { diff --git a/nix/libstore/remote-store.hh b/nix/libstore/remote-store.hh index 04b60fce4b..98774c10b3 100644 --- a/nix/libstore/remote-store.hh +++ b/nix/libstore/remote-store.hh @@ -82,12 +82,13 @@ public: PathSet queryFailedPaths(); void clearFailedPaths(const PathSet & paths); - + + void optimiseStore(); + private: AutoCloseFD fdSocket; FdSink to; FdSource from; - Pid child; unsigned int daemonVersion; bool initialised; diff --git a/nix/libstore/store-api.hh b/nix/libstore/store-api.hh index b635fee2cf..3109f100ef 100644 --- a/nix/libstore/store-api.hh +++ b/nix/libstore/store-api.hh @@ -250,6 +250,10 @@ public: `nix-store --register-validity'. */ string makeValidityRegistration(const PathSet & paths, bool showDerivers, bool showHash); + + /* Optimise the disk space usage of the Nix store by hard-linking files + with the same contents. */ + virtual void optimiseStore() = 0; }; diff --git a/nix/libstore/worker-protocol.hh b/nix/libstore/worker-protocol.hh index 9317f89c37..4b040b77ce 100644 --- a/nix/libstore/worker-protocol.hh +++ b/nix/libstore/worker-protocol.hh @@ -12,7 +12,6 @@ namespace nix { typedef enum { - wopQuit = 0, wopIsValidPath = 1, wopHasSubstitutes = 3, wopQueryPathHash = 4, @@ -43,6 +42,7 @@ typedef enum { wopQueryValidPaths = 31, wopQuerySubstitutablePaths = 32, wopQueryValidDerivers = 33, + wopOptimiseStore = 34 } WorkerOp; diff --git a/nix/libutil/archive.cc b/nix/libutil/archive.cc index 70a1c580dd..6856ea0f28 100644 --- a/nix/libutil/archive.cc +++ b/nix/libutil/archive.cc @@ -1,10 +1,14 @@ +#define _XOPEN_SOURCE 600 + #include "config.h" #include <cerrno> #include <algorithm> #include <vector> +#include <map> + +#include <strings.h> // for strcasecmp -#define _XOPEN_SOURCE 600 #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> @@ -18,39 +22,21 @@ namespace nix { +bool useCaseHack = +#if __APPLE__ + true; +#else + false; +#endif + static string archiveVersion1 = "nix-archive-1"; +static string caseHackSuffix = "~nix~case~hack~"; PathFilter defaultPathFilter; -static void dump(const string & path, Sink & sink, PathFilter & filter); - - -static void dumpEntries(const Path & path, Sink & sink, PathFilter & filter) -{ - Strings names = readDirectory(path); - vector<string> names2(names.begin(), names.end()); - sort(names2.begin(), names2.end()); - - for (vector<string>::iterator i = names2.begin(); - i != names2.end(); ++i) - { - Path entry = path + "/" + *i; - if (filter(entry)) { - writeString("entry", sink); - writeString("(", sink); - writeString("name", sink); - writeString(*i, sink); - writeString("node", sink); - dump(entry, sink, filter); - writeString(")", sink); - } - } -} - - -static void dumpContents(const Path & path, size_t size, +static void dumpContents(const Path & path, size_t size, Sink & sink) { writeString("contents", sink); @@ -58,7 +44,7 @@ static void dumpContents(const Path & path, size_t size, AutoCloseFD fd = open(path.c_str(), O_RDONLY); if (fd == -1) throw SysError(format("opening file `%1%'") % path); - + unsigned char buf[65536]; size_t left = size; @@ -89,12 +75,40 @@ static void dump(const Path & path, Sink & sink, PathFilter & filter) writeString("", sink); } dumpContents(path, (size_t) st.st_size, sink); - } + } else if (S_ISDIR(st.st_mode)) { writeString("type", sink); writeString("directory", sink); - dumpEntries(path, sink, filter); + + /* If we're on a case-insensitive system like Mac OS X, undo + the case hack applied by restorePath(). */ + std::map<string, string> unhacked; + for (auto & i : readDirectory(path)) + if (useCaseHack) { + string name(i.name); + size_t pos = i.name.find(caseHackSuffix); + if (pos != string::npos) { + printMsg(lvlDebug, format("removing case hack suffix from `%1%'") % (path + "/" + i.name)); + name.erase(pos); + } + if (unhacked.find(name) != unhacked.end()) + throw Error(format("file name collision in between `%1%' and `%2%'") + % (path + "/" + unhacked[name]) % (path + "/" + i.name)); + unhacked[name] = i.name; + } else + unhacked[i.name] = i.name; + + for (auto & i : unhacked) + if (filter(path + "/" + i.first)) { + writeString("entry", sink); + writeString("(", sink); + writeString("name", sink); + writeString(i.first, sink); + writeString("node", sink); + dump(path + "/" + i.second, sink, filter); + writeString(")", sink); + } } else if (S_ISLNK(st.st_mode)) { @@ -123,6 +137,7 @@ static SerialisationError badArchive(string s) } +#if 0 static void skipGeneric(Source & source) { if (readString(source) == "(") { @@ -130,43 +145,13 @@ static void skipGeneric(Source & source) skipGeneric(source); } } - - -static void parse(ParseSink & sink, Source & source, const Path & path); - - - -static void parseEntry(ParseSink & sink, Source & source, const Path & path) -{ - string s, name; - - s = readString(source); - if (s != "(") throw badArchive("expected open tag"); - - while (1) { - checkInterrupt(); - - s = readString(source); - - if (s == ")") { - break; - } else if (s == "name") { - name = readString(source); - } else if (s == "node") { - if (s == "") throw badArchive("entry name missing"); - parse(sink, source, path + "/" + name); - } else { - throw badArchive("unknown field " + s); - skipGeneric(source); - } - } -} +#endif static void parseContents(ParseSink & sink, Source & source, const Path & path) { unsigned long long size = readLongLong(source); - + sink.preallocateContents(size); unsigned long long left = size; @@ -185,6 +170,15 @@ static void parseContents(ParseSink & sink, Source & source, const Path & path) } +struct CaseInsensitiveCompare +{ + bool operator() (const string & a, const string & b) const + { + return strcasecmp(a.c_str(), b.c_str()) < 0; + } +}; + + static void parse(ParseSink & sink, Source & source, const Path & path) { string s; @@ -194,6 +188,8 @@ static void parse(ParseSink & sink, Source & source, const Path & path) enum { tpUnknown, tpRegular, tpDirectory, tpSymlink } type = tpUnknown; + std::map<Path, int, CaseInsensitiveCompare> names; + while (1) { checkInterrupt(); @@ -221,9 +217,9 @@ static void parse(ParseSink & sink, Source & source, const Path & path) else if (t == "symlink") { type = tpSymlink; } - + else throw badArchive("unknown file type " + t); - + } else if (s == "contents" && type == tpRegular) { @@ -236,7 +232,40 @@ static void parse(ParseSink & sink, Source & source, const Path & path) } else if (s == "entry" && type == tpDirectory) { - parseEntry(sink, source, path); + string name, prevName; + + s = readString(source); + if (s != "(") throw badArchive("expected open tag"); + + while (1) { + checkInterrupt(); + + s = readString(source); + + if (s == ")") { + break; + } else if (s == "name") { + name = readString(source); + if (name.empty() || name == "." || name == ".." || name.find('/') != string::npos || name.find((char) 0) != string::npos) + throw Error(format("NAR contains invalid file name `%1%'") % name); + if (name <= prevName) + throw Error("NAR directory is not sorted"); + prevName = name; + if (useCaseHack) { + auto i = names.find(name); + if (i != names.end()) { + printMsg(lvlDebug, format("case collision between `%1%' and `%2%'") % i->first % name); + name += caseHackSuffix; + name += int2String(++i->second); + } else + names[name] = 0; + } + } else if (s == "node") { + if (s.empty()) throw badArchive("entry name missing"); + parse(sink, source, path + "/" + name); + } else + throw badArchive("unknown field " + s); + } } else if (s == "target" && type == tpSymlink) { @@ -244,17 +273,15 @@ static void parse(ParseSink & sink, Source & source, const Path & path) sink.createSymlink(path, target); } - else { + else throw badArchive("unknown field " + s); - skipGeneric(source); - } } } void parseDump(ParseSink & sink, Source & source) { - string version; + string version; try { version = readString(source); } catch (SerialisationError & e) { @@ -323,7 +350,7 @@ struct RestoreSink : ParseSink } }; - + void restorePath(const Path & path, Source & source) { RestoreSink sink; @@ -331,5 +358,5 @@ void restorePath(const Path & path, Source & source) parseDump(sink, source); } - + } diff --git a/nix/libutil/archive.hh b/nix/libutil/archive.hh index ccac92074d..c216e9768f 100644 --- a/nix/libutil/archive.hh +++ b/nix/libutil/archive.hh @@ -28,7 +28,7 @@ namespace nix { where: - attrs(as) = concat(map(attr, as)) + encN(0) + attrs(as) = concat(map(attr, as)) + encN(0) attrs((a, b)) = encS(a) + encS(b) encS(s) = encN(len(s)) + s + (padding until next 64-bit boundary) @@ -58,7 +58,7 @@ void dumpPath(const Path & path, Sink & sink, struct ParseSink { virtual void createDirectory(const Path & path) { }; - + virtual void createRegularFile(const Path & path) { }; virtual void isExecutable() { }; virtual void preallocateContents(unsigned long long size) { }; @@ -66,10 +66,14 @@ struct ParseSink virtual void createSymlink(const Path & path, const string & target) { }; }; - + void parseDump(ParseSink & sink, Source & source); void restorePath(const Path & path, Source & source); - + +// FIXME: global variables are bad m'kay. +extern bool useCaseHack; + + } diff --git a/nix/libutil/serialise.cc b/nix/libutil/serialise.cc index 6b71f52c15..9241750750 100644 --- a/nix/libutil/serialise.cc +++ b/nix/libutil/serialise.cc @@ -54,8 +54,24 @@ FdSink::~FdSink() } +size_t threshold = 256 * 1024 * 1024; + +static void warnLargeDump() +{ + printMsg(lvlError, "warning: dumping very large path (> 256 MiB); this may run out of memory"); +} + + void FdSink::write(const unsigned char * data, size_t len) { + static bool warned = false; + if (warn && !warned) { + written += len; + if (written > threshold) { + warnLargeDump(); + warned = true; + } + } writeFull(fd, data, len); } @@ -256,4 +272,15 @@ template Paths readStrings(Source & source); template PathSet readStrings(Source & source); +void StringSink::operator () (const unsigned char * data, size_t len) +{ + static bool warned = false; + if (!warned && s.size() > threshold) { + warnLargeDump(); + warned = true; + } + s.append((const char *) data, len); +} + + } diff --git a/nix/libutil/serialise.hh b/nix/libutil/serialise.hh index e5a9df1d05..6a6f028aa6 100644 --- a/nix/libutil/serialise.hh +++ b/nix/libutil/serialise.hh @@ -72,9 +72,11 @@ struct BufferedSource : Source struct FdSink : BufferedSink { int fd; + bool warn; + size_t written; - FdSink() : fd(-1) { } - FdSink(int fd) : fd(fd) { } + FdSink() : fd(-1), warn(false), written(0) { } + FdSink(int fd) : fd(fd), warn(false), written(0) { } ~FdSink(); void write(const unsigned char * data, size_t len); @@ -95,10 +97,7 @@ struct FdSource : BufferedSource struct StringSink : Sink { string s; - void operator () (const unsigned char * data, size_t len) - { - s.append((const char *) data, len); - } + void operator () (const unsigned char * data, size_t len); }; diff --git a/nix/libutil/types.hh b/nix/libutil/types.hh index 4b5ce9a78c..160884ee1a 100644 --- a/nix/libutil/types.hh +++ b/nix/libutil/types.hh @@ -8,6 +8,15 @@ #include <boost/format.hpp> +/* Before 4.7, gcc's std::exception uses empty throw() specifiers for + * its (virtual) destructor and what() in c++11 mode, in violation of spec + */ +#ifdef __GNUC__ +#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 7) +#define EXCEPTION_NEEDS_THROW_SPEC +#endif +#endif + namespace nix { @@ -39,10 +48,14 @@ protected: public: unsigned int status; // exit status BaseError(const FormatOrString & fs, unsigned int status = 1); +#ifdef EXCEPTION_NEEDS_THROW_SPEC ~BaseError() throw () { }; const char * what() const throw () { return err.c_str(); } - const string & msg() const throw () { return err; } - const string & prefix() const throw () { return prefix_; } +#else + const char * what() const noexcept { return err.c_str(); } +#endif + const string & msg() const { return err; } + const string & prefix() const { return prefix_; } BaseError & addPrefix(const FormatOrString & fs); }; diff --git a/nix/libutil/util.cc b/nix/libutil/util.cc index 846674a29d..a4a1ddb12a 100644 --- a/nix/libutil/util.cc +++ b/nix/libutil/util.cc @@ -1,5 +1,8 @@ #include "config.h" +#include "util.hh" +#include "affinity.hh" + #include <iostream> #include <cerrno> #include <cstdio> @@ -16,8 +19,6 @@ #include <sys/syscall.h> #endif -#include "util.hh" - extern char * * environ; @@ -125,7 +126,6 @@ Path canonPath(const Path & path, bool resolveSymlinks) i = temp.begin(); /* restart */ end = temp.end(); s = ""; - /* !!! potential for infinite loop */ } } } @@ -202,9 +202,10 @@ bool isLink(const Path & path) } -Strings readDirectory(const Path & path) +DirEntries readDirectory(const Path & path) { - Strings names; + DirEntries entries; + entries.reserve(64); AutoCloseDir dir = opendir(path.c_str()); if (!dir) throw SysError(format("opening directory `%1%'") % path); @@ -214,11 +215,21 @@ Strings readDirectory(const Path & path) checkInterrupt(); string name = dirent->d_name; if (name == "." || name == "..") continue; - names.push_back(name); + entries.emplace_back(name, dirent->d_ino, dirent->d_type); } if (errno) throw SysError(format("reading directory `%1%'") % path); - return names; + return entries; +} + + +unsigned char getFileType(const Path & path) +{ + struct stat st = lstat(path); + if (S_ISDIR(st.st_mode)) return DT_DIR; + if (S_ISLNK(st.st_mode)) return DT_LNK; + if (S_ISREG(st.st_mode)) return DT_REG; + return DT_UNKNOWN; } @@ -293,16 +304,14 @@ static void _deletePath(const Path & path, unsigned long long & bytesFreed) bytesFreed += st.st_blocks * 512; if (S_ISDIR(st.st_mode)) { - Strings names = readDirectory(path); - /* Make the directory writable. */ if (!(st.st_mode & S_IWUSR)) { if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1) throw SysError(format("making `%1%' writable") % path); } - for (Strings::iterator i = names.begin(); i != names.end(); ++i) - _deletePath(path + "/" + *i, bytesFreed); + for (auto & i : readDirectory(path)) + _deletePath(path + "/" + i.name, bytesFreed); } if (remove(path.c_str()) == -1) @@ -380,6 +389,9 @@ Paths createDirs(const Path & path) created.push_back(path); } + if (S_ISLNK(st.st_mode) && stat(path.c_str(), &st) == -1) + throw SysError(format("statting symlink `%1%'") % path); + if (!S_ISDIR(st.st_mode)) throw Error(format("`%1%' is not a directory") % path); return created; @@ -466,10 +478,18 @@ void warnOnce(bool & haveWarned, const FormatOrString & fs) } +static void defaultWriteToStderr(const unsigned char * buf, size_t count) +{ + writeFull(STDERR_FILENO, buf, count); +} + + void writeToStderr(const string & s) { try { - _writeToStderr((const unsigned char *) s.data(), s.size()); + auto p = _writeToStderr; + if (!p) p = defaultWriteToStderr; + p((const unsigned char *) s.data(), s.size()); } catch (SysError & e) { /* Ignore failing writes to stderr if we're in an exception handler, otherwise throw an exception. We need to ignore @@ -481,12 +501,6 @@ void writeToStderr(const string & s) } -static void defaultWriteToStderr(const unsigned char * buf, size_t count) -{ - writeFull(STDERR_FILENO, buf, count); -} - - void (*_writeToStderr) (const unsigned char * buf, size_t count) = defaultWriteToStderr; @@ -707,10 +721,14 @@ void AutoCloseDir::close() Pid::Pid() + : pid(-1), separatePG(false), killSignal(SIGKILL) +{ +} + + +Pid::Pid(pid_t pid) + : pid(pid), separatePG(false), killSignal(SIGKILL) { - pid = -1; - separatePG = false; - killSignal = SIGKILL; } @@ -734,11 +752,12 @@ Pid::operator pid_t() } -void Pid::kill() +void Pid::kill(bool quiet) { if (pid == -1 || pid == 0) return; - printMsg(lvlError, format("killing process %1%") % pid); + if (!quiet) + printMsg(lvlError, format("killing process %1%") % pid); /* Send the requested signal to the child. If it has its own process group, send the signal to every process in the child @@ -801,43 +820,30 @@ void killUser(uid_t uid) users to which the current process can send signals. So we fork a process, switch to uid, and send a mass kill. */ - Pid pid; - pid = fork(); - switch (pid) { + Pid pid = startProcess([&]() { - case -1: - throw SysError("unable to fork"); + if (setuid(uid) == -1) + throw SysError("setting uid"); - case 0: - try { /* child */ - - if (setuid(uid) == -1) - throw SysError("setting uid"); - - while (true) { + while (true) { #ifdef __APPLE__ - /* OSX's kill syscall takes a third parameter that, among other - things, determines if kill(-1, signo) affects the calling - process. In the OSX libc, it's set to true, which means - "follow POSIX", which we don't want here + /* OSX's kill syscall takes a third parameter that, among + other things, determines if kill(-1, signo) affects the + calling process. In the OSX libc, it's set to true, + which means "follow POSIX", which we don't want here */ - if (syscall(SYS_kill, -1, SIGKILL, false) == 0) break; + if (syscall(SYS_kill, -1, SIGKILL, false) == 0) break; #else - if (kill(-1, SIGKILL) == 0) break; + if (kill(-1, SIGKILL) == 0) break; #endif - if (errno == ESRCH) break; /* no more processes */ - if (errno != EINTR) - throw SysError(format("cannot kill processes for uid `%1%'") % uid); - } - - } catch (std::exception & e) { - writeToStderr((format("killing processes belonging to uid `%1%': %2%\n") % uid % e.what()).str()); - _exit(1); + if (errno == ESRCH) break; /* no more processes */ + if (errno != EINTR) + throw SysError(format("cannot kill processes for uid `%1%'") % uid); } + _exit(0); - } + }); - /* parent */ int status = pid.wait(true); if (status != 0) throw Error(format("cannot kill processes for uid `%1%': %2%") % uid % statusToString(status)); @@ -852,6 +858,32 @@ void killUser(uid_t uid) ////////////////////////////////////////////////////////////////////// +pid_t startProcess(std::function<void()> fun, + bool dieWithParent, const string & errorPrefix, bool runExitHandlers) +{ + pid_t pid = fork(); + if (pid == -1) throw SysError("unable to fork"); + + if (pid == 0) { + _writeToStderr = 0; + try { + restoreAffinity(); + fun(); + } catch (std::exception & e) { + try { + std::cerr << errorPrefix << e.what() << "\n"; + } catch (...) { } + } catch (...) { } + if (runExitHandlers) + exit(1); + else + _exit(1); + } + + return pid; +} + + string runProgram(Path program, bool searchPath, const Strings & args) { checkInterrupt(); @@ -867,32 +899,17 @@ string runProgram(Path program, bool searchPath, const Strings & args) pipe.create(); /* Fork. */ - Pid pid; - pid = maybeVfork(); - - switch (pid) { - - case -1: - throw SysError("unable to fork"); + Pid pid = startProcess([&]() { + if (dup2(pipe.writeSide, STDOUT_FILENO) == -1) + throw SysError("dupping stdout"); - case 0: /* child */ - try { - if (dup2(pipe.writeSide, STDOUT_FILENO) == -1) - throw SysError("dupping stdout"); - - if (searchPath) - execvp(program.c_str(), (char * *) &cargs[0]); - else - execv(program.c_str(), (char * *) &cargs[0]); - throw SysError(format("executing `%1%'") % program); - - } catch (std::exception & e) { - writeToStderr("error: " + string(e.what()) + "\n"); - } - _exit(1); - } + if (searchPath) + execvp(program.c_str(), (char * *) &cargs[0]); + else + execv(program.c_str(), (char * *) &cargs[0]); - /* Parent. */ + throw SysError(format("executing `%1%'") % program); + }); pipe.writeSide.close(); @@ -901,7 +918,7 @@ string runProgram(Path program, bool searchPath, const Strings & args) /* Wait for the child to finish. */ int status = pid.wait(true); if (!statusOk(status)) - throw Error(format("program `%1%' %2%") + throw ExecError(format("program `%1%' %2%") % program % statusToString(status)); return result; @@ -928,13 +945,6 @@ void closeOnExec(int fd) } -#if HAVE_VFORK -pid_t (*maybeVfork)() = vfork; -#else -pid_t (*maybeVfork)() = fork; -#endif - - ////////////////////////////////////////////////////////////////////// diff --git a/nix/libutil/util.hh b/nix/libutil/util.hh index ce2d77c19a..0ad0026711 100644 --- a/nix/libutil/util.hh +++ b/nix/libutil/util.hh @@ -7,6 +7,7 @@ #include <dirent.h> #include <unistd.h> #include <signal.h> +#include <functional> #include <cstdio> @@ -63,7 +64,20 @@ bool isLink(const Path & path); /* Read the contents of a directory. The entries `.' and `..' are removed. */ -Strings readDirectory(const Path & path); +struct DirEntry +{ + string name; + ino_t ino; + unsigned char type; // one of DT_* + DirEntry(const string & name, ino_t ino, unsigned char type) + : name(name), ino(ino), type(type) { } +}; + +typedef vector<DirEntry> DirEntries; + +DirEntries readDirectory(const Path & path); + +unsigned char getFileType(const Path & path); /* Read the contents of a file into a string. */ string readFile(int fd); @@ -237,10 +251,11 @@ class Pid int killSignal; public: Pid(); + Pid(pid_t pid); ~Pid(); void operator =(pid_t pid); operator pid_t(); - void kill(); + void kill(bool quiet = false); int wait(bool block); void setSeparatePG(bool separatePG); void setKillSignal(int signal); @@ -252,11 +267,19 @@ public: void killUser(uid_t uid); +/* Fork a process that runs the given function, and return the child + pid to the caller. */ +pid_t startProcess(std::function<void()> fun, bool dieWithParent = true, + const string & errorPrefix = "error: ", bool runExitHandlers = false); + + /* Run a program and return its stdout in a string (i.e., like the shell backtick operator). */ string runProgram(Path program, bool searchPath = false, const Strings & args = Strings()); +MakeError(ExecError, Error) + /* Close all file descriptors except stdin, stdout, stderr, and those listed in the given set. Good practice in child processes. */ void closeMostFDs(const set<int> & exceptions); @@ -264,9 +287,6 @@ void closeMostFDs(const set<int> & exceptions); /* Set the close-on-exec flag for the given file descriptor. */ void closeOnExec(int fd); -/* Call vfork() if available, otherwise fork(). */ -extern pid_t (*maybeVfork)(); - /* User interruption. */ diff --git a/nix/nix-daemon/guix-daemon.cc b/nix/nix-daemon/guix-daemon.cc index f096ed5a97..315744394f 100644 --- a/nix/nix-daemon/guix-daemon.cc +++ b/nix/nix-daemon/guix-daemon.cc @@ -154,8 +154,18 @@ parse_opt (int key, char *arg, struct argp_state *state) settings.useChroot = false; break; case GUIX_OPT_CHROOT_DIR: - settings.dirsInChroot.insert (arg); - break; + { + std::string chroot_dirs; + + chroot_dirs = settings.get ("build-extra-chroot-dirs", + (std::string) ""); + if (chroot_dirs == "") + chroot_dirs = arg; + else + chroot_dirs = chroot_dirs + " " + arg; + settings.set("build-extra-chroot-dirs", chroot_dirs); + break; + } case GUIX_OPT_DISABLE_LOG_COMPRESSION: settings.compressLog = false; break; @@ -328,11 +338,12 @@ main (int argc, char *argv[]) if (settings.useChroot) { - foreach (PathSet::iterator, i, settings.dirsInChroot) - { - printMsg (lvlDebug, - format ("directory `%1%' added to the chroot") % *i); - } + std::string chroot_dirs; + + chroot_dirs = settings.get ("build-extra-chroot-dirs", + (std::string) ""); + printMsg (lvlDebug, + format ("extra chroot directories: '%1%'") % chroot_dirs); } printMsg (lvlDebug, diff --git a/nix/nix-daemon/nix-daemon.cc b/nix/nix-daemon/nix-daemon.cc index 8814fe3155..e42d602a3a 100644 --- a/nix/nix-daemon/nix-daemon.cc +++ b/nix/nix-daemon/nix-daemon.cc @@ -7,6 +7,8 @@ #include "affinity.hh" #include "globals.hh" +#include <algorithm> + #include <cstring> #include <unistd.h> #include <signal.h> @@ -17,6 +19,8 @@ #include <sys/un.h> #include <fcntl.h> #include <errno.h> +#include <pwd.h> +#include <grp.h> using namespace nix; @@ -44,7 +48,6 @@ static FdSource from(STDIN_FILENO); static FdSink to(STDOUT_FILENO); bool canSendStderr; -pid_t myPid; @@ -54,11 +57,7 @@ pid_t myPid; socket. */ static void tunnelStderr(const unsigned char * buf, size_t count) { - /* Don't send the message to the client if we're a child of the - process handling the connection. Otherwise we could screw up - the protocol. It's up to the parent to redirect stderr and - send it to the client somehow (e.g., as in build.cc). */ - if (canSendStderr && myPid == getpid()) { + if (canSendStderr) { try { writeInt(STDERR_NEXT, to); writeString(buf, count, to); @@ -284,15 +283,6 @@ static void performOp(bool trusted, unsigned int clientVersion, { switch (op) { -#if 0 - case wopQuit: { - /* Close the database. */ - store.reset((StoreAPI *) 0); - writeInt(1, to); - break; - } -#endif - case wopIsValidPath: { /* 'readStorePath' could raise an error leading to the connection being closed. To be able to recover from an invalid path error, @@ -450,7 +440,7 @@ static void performOp(bool trusted, unsigned int clientVersion, case wopImportPaths: { startWork(); TunnelSource source(from); - Paths paths = store->importPaths(true, source); + Paths paths = store->importPaths(!trusted, source); stopWork(); writeStrings(paths, to); break; @@ -650,6 +640,13 @@ static void performOp(bool trusted, unsigned int clientVersion, break; } + case wopOptimiseStore: + startWork(); + store->optimiseStore(); + stopWork(); + writeInt(1, to); + break; + default: throw Error(format("invalid operation %1%") % op); } @@ -659,7 +656,6 @@ static void performOp(bool trusted, unsigned int clientVersion, static void processConnection(bool trusted) { canSendStderr = false; - myPid = getpid(); _writeToStderr = tunnelStderr; #ifdef HAVE_HUP_NOTIFICATION @@ -708,7 +704,7 @@ static void processConnection(bool trusted) to.flush(); } catch (Error & e) { - stopWork(false, e.msg()); + stopWork(false, e.msg(), GET_PROTOCOL_MINOR(clientVersion) >= 8 ? 1 : 0); to.flush(); return; } @@ -735,12 +731,10 @@ static void processConnection(bool trusted) during addTextToStore() / importPath(). If that happens, just send the error message and exit. */ bool errorAllowed = canSendStderr; - if (!errorAllowed) printMsg(lvlError, format("error processing client input: %1%") % e.msg()); stopWork(false, e.msg(), GET_PROTOCOL_MINOR(clientVersion) >= 8 ? e.status : 0); - if (!errorAllowed) break; + if (!errorAllowed) throw; } catch (std::bad_alloc & e) { - if (canSendStderr) - stopWork(false, "Nix daemon out of memory", GET_PROTOCOL_MINOR(clientVersion) >= 8 ? 1 : 0); + stopWork(false, "Nix daemon out of memory", GET_PROTOCOL_MINOR(clientVersion) >= 8 ? 1 : 0); throw; } @@ -749,7 +743,7 @@ static void processConnection(bool trusted) assert(!canSendStderr); }; - printMsg(lvlError, format("%1% operations") % opCount); + printMsg(lvlDebug, format("%1% operations") % opCount); } @@ -771,6 +765,27 @@ static void setSigChldAction(bool autoReap) } +bool matchUser(const string & user, const string & group, const Strings & users) +{ + if (find(users.begin(), users.end(), "*") != users.end()) + return true; + + if (find(users.begin(), users.end(), user) != users.end()) + return true; + + for (auto & i : users) + if (string(i, 0, 1) == "@") { + if (group == string(i, 1)) return true; + struct group * gr = getgrnam(i.c_str() + 1); + if (!gr) continue; + for (char * * mem = gr->gr_mem; *mem; mem++) + if (user == string(*mem)) return true; + } + + return false; +} + + #define SD_LISTEN_FDS_START 3 @@ -856,58 +871,61 @@ static void daemonLoop() closeOnExec(remote); - /* Get the identity of the caller, if possible. */ - uid_t clientUid = -1; - pid_t clientPid = -1; bool trusted = false; + pid_t clientPid = -1; #if defined(SO_PEERCRED) + /* Get the identity of the caller, if possible. */ ucred cred; socklen_t credLen = sizeof(cred); - if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED, &cred, &credLen) != -1) { - clientPid = cred.pid; - clientUid = cred.uid; - if (clientUid == 0) trusted = true; - } -#endif + if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED, &cred, &credLen) == -1) + throw SysError("getting peer credentials"); - printMsg(lvlInfo, format("accepted connection from pid %1%, uid %2%") % clientPid % clientUid); + clientPid = cred.pid; - /* Fork a child to handle the connection. */ - pid_t child; - child = fork(); + struct passwd * pw = getpwuid(cred.uid); + string user = pw ? pw->pw_name : int2String(cred.uid); - switch (child) { + struct group * gr = getgrgid(cred.gid); + string group = gr ? gr->gr_name : int2String(cred.gid); - case -1: - throw SysError("unable to fork"); + Strings trustedUsers = settings.get("trusted-users", Strings({"root"})); + Strings allowedUsers = settings.get("allowed-users", Strings({"*"})); - case 0: - try { /* child */ + if (matchUser(user, group, trustedUsers)) + trusted = true; - /* Background the daemon. */ - if (setsid() == -1) - throw SysError(format("creating a new session")); + if (!trusted && !matchUser(user, group, allowedUsers)) + throw Error(format("user `%1%' is not allowed to connect to the Nix daemon") % user); - /* Restore normal handling of SIGCHLD. */ - setSigChldAction(false); + printMsg(lvlInfo, format((string) "accepted connection from pid %1%, user %2%" + + (trusted ? " (trusted)" : "")) % clientPid % user); +#endif + + /* Fork a child to handle the connection. */ + startProcess([&]() { + fdSocket.close(); - /* For debugging, stuff the pid into argv[1]. */ - if (clientPid != -1 && argvSaved[1]) { - string processName = int2String(clientPid); - strncpy(argvSaved[1], processName.c_str(), strlen(argvSaved[1])); - } + /* Background the daemon. */ + if (setsid() == -1) + throw SysError(format("creating a new session")); - /* Handle the connection. */ - from.fd = remote; - to.fd = remote; - processConnection(trusted); + /* Restore normal handling of SIGCHLD. */ + setSigChldAction(false); - } catch (std::exception & e) { - writeToStderr("unexpected Nix daemon error: " + string(e.what()) + "\n"); + /* For debugging, stuff the pid into argv[1]. */ + if (clientPid != -1 && argvSaved[1]) { + string processName = int2String(clientPid); + strncpy(argvSaved[1], processName.c_str(), strlen(argvSaved[1])); } + + /* Handle the connection. */ + from.fd = remote; + to.fd = remote; + processConnection(trusted); + exit(0); - } + }, false, "unexpected Nix daemon error: ", true); } catch (Interrupted & e) { throw; diff --git a/tests/guix-archive.sh b/tests/guix-archive.sh index d4259b8677..8eacf89338 100644 --- a/tests/guix-archive.sh +++ b/tests/guix-archive.sh @@ -1,5 +1,5 @@ # GNU Guix --- Functional package management for GNU -# Copyright © 2013, 2014 Ludovic Courtès <ludo@gnu.org> +# Copyright © 2013, 2014, 2015 Ludovic Courtès <ludo@gnu.org> # # This file is part of GNU Guix. # @@ -41,7 +41,6 @@ cmp "$archive" "$archive_alt" # Check the exit value and stderr upon import. guix archive --import < "$archive" -guix archive --import < "$archive" 2>&1 | grep "import.*guile-bootstrap" if guix archive something-that-does-not-exist then false; else true; fi |