diff --git a/src/libstore/build.cc b/src/libstore/build.cc index cec03fee42a..e0d1e8e1fad 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -8,6 +8,7 @@ #include "util.hh" #include "archive.hh" #include "affinity.hh" +#include "worker-protocol.hh" #include #include @@ -733,6 +734,9 @@ class DerivationGoal : public Goal /* Pipe for the builder's standard output/error. */ Pipe builderOut; + /* Our end of the recursive paths reporting socket. */ + AutoCloseFD recursivePathsParentSide; + /* The build hook. */ boost::shared_ptr hook; @@ -1662,6 +1666,77 @@ void DerivationGoal::startBuilder() getdents returns the inode of the mount point). */ env["PWD"] = tmpDir; + /* Set up recursive nix */ + env["NIX_STORE_DIR"] = settings.nixStore; + /* NIX_DATA_DIR, NIX_LIBEXEC_DIR, NIX_BIN_DIR purposefully not set */ + /* If we want log reading from recursive nix, we'll + need to put the log dir in the chroot */ + env["NIX_LOG_DIR"] = "/var/empty"; + env["NIX_STATE_DIR"] = "/var/empty"; + env["NIX_DB_DIR"] = "/var/empty"; + /* We pass the settings pack. Would need to put this + in the chroot if we really wanted it */ + env["NIX_CONF_DIR"] = "/var/empty"; + env["NIX_SUBSTITUTERS"] = ""; + env["NIX_AFFINITY_HACK"] = settings.lockCPU ? "1" : ""; + env["_NIX_OPTIONS"] = settings.pack(); + env["NIX_REMOTE"] = "recursive"; + env["NIX_REMOTE_RECURSIVE_PROTOCOL_VERSION"] = int2String(RECURSIVE_PROTOCOL_VERSION); + if (worker.store.fdRecursiveDaemon == -1) { + /* This process isn't a daemon worker, and we haven't spawned a child daemon yet */ + int sockets[2]; + if (socketpair(PF_UNIX, SOCK_STREAM, 0, sockets) == -1) + throw SysError("creating recursive daemon socketpair"); + + if (shutdown(sockets[0], SHUT_WR) == -1) + throw SysError("disabling writes to listening side of daemon socketpair"); + + if (shutdown(sockets[1], SHUT_RD) == -1) + throw SysError("disabling reads from connecting side of daemon socketpair"); + + /* No portable way to kill children when parents die, so + we pass the daemon the read side of a pipe, when it gets + EOF there it knows we died and it exits */ + int close_pipe[2]; + closeOnExec(close_pipe[1]); + if (pipe(close_pipe) == -1) + throw SysError("creating recursive nix daemon close notification pipe"); + + set kept; + kept.insert(sockets[0]); + kept.insert(sockets[1]); + kept.insert(close_pipe[0]); + string recursiveFds = (format("%1% %2% %3%") % sockets[0] % sockets[1] % close_pipe[0]).str(); + Path progPath = (format("%1%/nix-daemon") % settings.nixBinDir).str(); + switch (fork()) { + case -1: + throw SysError("forking to run recursive nix-daemon"); + case 0: + setenv("NIX_RECURSIVE_FDS", recursiveFds.c_str(), true); + setenv("_NIX_OPTIONS", env["_NIX_OPTIONS"].c_str(), true); + closeMostFDs(kept); + execl(progPath.c_str(), "nix-daemon", NULL); + throw SysError("executing nix-daemon"); + } + close(close_pipe[0]); + /* close_pipe[1] purposefully leaked here */ + close(sockets[0]); + worker.store.fdRecursiveDaemon = sockets[1]; + } + env["NIX_REMOTE_RECURSIVE_SOCKET_FD"] = int2String((int) worker.store.fdRecursiveDaemon); + + int recursivePaths[2]; + if (socketpair(PF_UNIX, SOCK_STREAM, 0, recursivePaths) == -1) + throw SysError("creating recursive paths socketpair"); + + AutoCloseFD recursivePathsChildSide = recursivePaths[0]; + recursivePathsParentSide = recursivePaths[1]; + /* We set our end of the socket non-blocking, so a malicious + builder can't make us block writing the confirmation */ + setNonBlocking(recursivePathsParentSide); + closeOnExec(recursivePathsParentSide); + env["NIX_REMOTE_RECURSIVE_PATHS_FD"] = int2String((int) recursivePathsChildSide); + /* Compatibility hack with Nix <= 0.7: if this is a fixed-output derivation, tell the builder, so that for instance `fetchurl' can skip checking the output. On older Nixes, this environment @@ -1958,8 +2033,10 @@ void DerivationGoal::startBuilder() /* parent */ pid.setSeparatePG(true); builderOut.writeSide.close(); - worker.childStarted(shared_from_this(), pid, - singleton >(builderOut.readSide), true, true); + set fds; + fds.insert(builderOut.readSide); + fds.insert(recursivePathsParentSide); + worker.childStarted(shared_from_this(), pid, fds, true, true); if (settings.printBuildTrace) { printMsg(lvlError, format("@ build-started %1% - %2% %3%") @@ -2061,8 +2138,17 @@ void DerivationGoal::initChild() if (chdir(tmpDir.c_str()) == -1) throw SysError(format("changing into `%1%'") % tmpDir); - /* Close all other file descriptors. */ - closeMostFDs(set()); + /* Close all file descriptors except stdio and recursive nix files. */ + set kept; + int fd; + bool ok = string2Int(env["NIX_REMOTE_RECURSIVE_SOCKET_FD"], fd); + kept.insert(fd); + assert(ok); + ok = string2Int(env["NIX_REMOTE_RECURSIVE_PATHS_FD"], fd); + kept.insert(fd); + assert(ok); + + closeMostFDs(kept); #ifdef CAN_DO_LINUX32_BUILDS /* Change the personality to 32-bit if we're doing an @@ -2429,16 +2515,102 @@ void DerivationGoal::handleChildOutput(int fd, const string & data) 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()); - } + } else if (!hook && fd == recursivePathsParentSide) { +#if CHROOT_ENABLED + AutoCloseFD myNs; +#endif + try { + /* Extract the paths. */ + string::size_type pos = 0, end; + + PathSet newPaths; + + while ((end = data.find((char) 0, pos)) != string::npos) { + Path r(data, pos, end - pos); + debug(format("got recursive path `%1%'") % r); + computeFSClosure(worker.store, r, newPaths); + pos = end + 1; + } - if (hook && fd == hook->fromHook.readSide) +#if CHROOT_ENABLED + if (useChroot) { + /* Enter the child's mount namespace */ + myNs = open("/proc/self/ns/mnt", O_RDONLY); + if (myNs == -1) + throw SysError("opening own mount namespace"); + Path childNsPath = (format("/proc/%1%/ns/mnt") % pid).str(); + AutoCloseFD childNs = open(childNsPath.c_str(), O_RDONLY); + if (childNs == -1) + throw SysError("opening child's mount namespace"); + + if (setns(childNs, 0) == -1) + throw SysError("entering child's mount namespace"); + } +#endif + + foreach (PathSet::iterator, i, newPaths) { + worker.store.addTempRoot(*i); +#if CHROOT_ENABLED + if (useChroot) { + /* We need this path to be available in the chroot */ + Path target = chrootRootDir + *i; + if (access(target.c_str(), F_OK) == 0) + /* Path already exists, assume it's the right one */ + continue; + struct stat st; + if (lstat(i->c_str(), &st)) + throw SysError(format("getting attributes of path `%1%'") % *i); + if (S_ISDIR(st.st_mode)) { + debug(format("bind mounting `%1%' to `%2%'") % *i % target); + createDirs(target); + if (mount(i->c_str(), target.c_str(), "", MS_BIND, 0) == -1) + throw SysError(format("bind mount from `%1%' to `%2%' failed") % *i % target); + } else { + if (link(i->c_str(), target.c_str()) == -1) { + /* Hard-linking fails if we exceed the maximum + link count on a file (e.g. 32000 of ext3), + which is quite possible after a `nix-store + --optimise'. */ + if (errno != EMLINK) + throw SysError(format("linking `%1%' to `%2%'") % target % *i); + StringSink sink; + dumpPath(*i, sink); + StringSource source(sink.s); + restorePath(target, source); + } + } + } +#endif + } + + allPaths.insert(newPaths.begin(), newPaths.end()); + + /* Notify the child that we're done */ + unsigned char c = '\0'; + writeFull(fd, &c, sizeof c); + } catch (std::exception & e) { + /* Don't let a rogue builder kill us */ + printMsg(lvlError, format("error getting reported recursive paths: %1%") % e.what()); + } + +#if CHROOT_ENABLED + if (myNs != -1) { + /* Back in our own namespace */ + if (setns(myNs, 0) == -1) + throw SysError("leaving child's mount namespace"); + } +#endif + } else if (hook && fd == hook->fromHook.readSide) writeToStderr(data); } void DerivationGoal::handleEOF(int fd) { - worker.wakeUp(shared_from_this()); + if (!hook && fd == recursivePathsParentSide) + recursivePathsParentSide.close(); + else + worker.wakeUp(shared_from_this()); } diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index aca98412ae1..407a4f4f9a9 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -222,8 +222,9 @@ void checkStoreNotSymlink() } -LocalStore::LocalStore(bool reserveSpace) - : didSetSubstituterEnv(false) +LocalStore::LocalStore(bool reserveSpace, int fd) + : fdRecursiveDaemon(fd) + , didSetSubstituterEnv(false) { schemaPath = settings.nixDBPath + "/schema"; diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh index 09639e74cf4..7c2a5791fd8 100644 --- a/src/libstore/local-store.hh +++ b/src/libstore/local-store.hh @@ -93,7 +93,7 @@ public: /* Initialise the local store, upgrading the schema if necessary. */ - LocalStore(bool reserveSpace = true); + LocalStore(bool reserveSpace = true, int fd = -1); ~LocalStore(); @@ -210,6 +210,8 @@ public: void setSubstituterEnv(); + AutoCloseFD fdRecursiveDaemon; + private: Path schemaPath; diff --git a/src/libstore/pathlocks.cc b/src/libstore/pathlocks.cc index b858ed238de..a7a60fe123a 100644 --- a/src/libstore/pathlocks.cc +++ b/src/libstore/pathlocks.cc @@ -20,7 +20,8 @@ int openLockFile(const Path & path, bool create) if (fd == -1 && (create || errno != ENOENT)) throw SysError(format("opening lock file `%1%'") % path); - closeOnExec(fd); + if (fd != -1) + closeOnExec(fd); return fd.borrow(); } diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc index 46192069329..83912622401 100644 --- a/src/libstore/remote-store.cc +++ b/src/libstore/remote-store.cc @@ -5,6 +5,9 @@ #include "archive.hh" #include "affinity.hh" #include "globals.hh" +#include "pathlocks.hh" +#include "derivations.hh" +#include "errno.h" #include #include @@ -54,9 +57,61 @@ void RemoteStore::openConnection(bool reserveSpace) /* Connect to a daemon that does the privileged work for us. */ connectToDaemon(); - else + else if (remoteMode == "recursive") { + unsigned int daemonRecursiveVersion; + if (!string2Int(getEnv("NIX_REMOTE_RECURSIVE_PROTOCOL_VERSION"), daemonRecursiveVersion)) + throw Error("expected an integer in NIX_REMOTE_RECURSIVE_PROTOCOL_VERSION"); + + if (GET_PROTOCOL_MAJOR(daemonRecursiveVersion) != GET_PROTOCOL_MAJOR(RECURSIVE_PROTOCOL_VERSION)) + throw Error("nix daemon recursive protocol version not supported"); + + int sfd; + if (!string2Int(getEnv("NIX_REMOTE_RECURSIVE_SOCKET_FD"), sfd)) + throw Error("expected an fd in NIX_REMOTE_RECURSIVE_SOCKET_FD"); + + int fds[2]; + if (socketpair(PF_UNIX, SOCK_STREAM, 0, fds) == -1) + throw SysError("creating recursive daemon socketpair"); + + /* Send our protocol version and a socket to the daemon */ + unsigned char buf[sizeof RECURSIVE_PROTOCOL_VERSION]; + for (size_t byte = 0; byte < sizeof buf; ++byte) + buf[byte] = + (unsigned char) ((RECURSIVE_PROTOCOL_VERSION >> (8 * byte)) & 0xFF); + struct iovec iov; + iov.iov_base = buf; + iov.iov_len = sizeof buf; + struct msghdr msg; + memset(&msg, 0, sizeof msg); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + char data[CMSG_SPACE(sizeof fds[1])]; + msg.msg_control = data; + msg.msg_controllen = sizeof data; + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof fds[1]); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + memmove(CMSG_DATA(cmsg), &fds[1], sizeof fds[1]); + ssize_t count = sendmsg(sfd, &msg, 0); + if (count == -1) + throw SysError("sending socket descriptor to daemon"); + else if ((size_t) count != sizeof buf) + throw Error(format("tried to send %1% bytes to the daemon, but only %2% were sent") + % sizeof buf % count); + close(fds[1]); + + fdSocket = fds[0]; + + int pathsFd; + if (!string2Int(getEnv("NIX_REMOTE_RECURSIVE_PATHS_FD"), pathsFd)) + throw Error("expected an fd in NIX_REMOTE_RECURSIVE_PATHS_FD"); + fdRecursivePaths = pathsFd; + } else throw Error(format("invalid setting for NIX_REMOTE, `%1%'") % remoteMode); + closeOnExec(fdSocket); + from.fd = fdSocket; to.fd = fdSocket; @@ -100,7 +155,6 @@ void RemoteStore::connectToDaemon() fdSocket = socket(PF_UNIX, SOCK_STREAM, 0); if (fdSocket == -1) throw SysError("cannot create Unix domain socket"); - closeOnExec(fdSocket); string socketPath = settings.nixDaemonSocketFile; @@ -209,7 +263,9 @@ PathSet RemoteStore::queryAllValidPaths() openConnection(); writeInt(wopQueryAllValidPaths, to); processStderr(); - return readStorePaths(from); + PathSet res = readStorePaths(from); + reportRecursivePaths(res); + return res; } @@ -289,7 +345,11 @@ ValidPathInfo RemoteStore::queryPathInfo(const Path & path) ValidPathInfo info; info.path = path; info.deriver = readString(from); - if (info.deriver != "") assertStorePath(info.deriver); + if (info.deriver != "") { + assertStorePath(info.deriver); + if (fdRecursivePaths != -1 && isValidPath(info.deriver)) + _reportRecursivePath(info.deriver); + } info.hash = parseHash(htSHA256, readString(from)); info.references = readStorePaths(from); info.registrationTime = readInt(from); @@ -317,6 +377,7 @@ void RemoteStore::queryReferences(const Path & path, writeString(path, to); processStderr(); PathSet references2 = readStorePaths(from); + /* References are brought in automatically, no need to report */ references.insert(references2.begin(), references2.end()); } @@ -329,6 +390,7 @@ void RemoteStore::queryReferrers(const Path & path, writeString(path, to); processStderr(); PathSet referrers2 = readStorePaths(from); + reportRecursivePaths(referrers2); referrers.insert(referrers2.begin(), referrers2.end()); } @@ -340,7 +402,11 @@ Path RemoteStore::queryDeriver(const Path & path) writeString(path, to); processStderr(); Path drvPath = readString(from); - if (drvPath != "") assertStorePath(drvPath); + if (drvPath != "") { + assertStorePath(drvPath); + if (fdRecursivePaths != -1 && isValidPath(drvPath)) + _reportRecursivePath(drvPath); + } return drvPath; } @@ -351,7 +417,9 @@ PathSet RemoteStore::queryValidDerivers(const Path & path) writeInt(wopQueryValidDerivers, to); writeString(path, to); processStderr(); - return readStorePaths(from); + PathSet res = readStorePaths(from); + reportRecursivePaths(res); + return res; } @@ -361,6 +429,8 @@ PathSet RemoteStore::queryDerivationOutputs(const Path & path) writeInt(wopQueryDerivationOutputs, to); writeString(path, to); processStderr(); + /* Paths determined via queryDerivationOutputs can't be assumed valid, + so we don't report them */ return readStorePaths(from); } @@ -382,7 +452,10 @@ Path RemoteStore::queryPathFromHashPart(const string & hashPart) writeString(hashPart, to); processStderr(); Path path = readString(from); - if (!path.empty()) assertStorePath(path); + if (!path.empty()) { + assertStorePath(path); + reportRecursivePath(path); + } return path; } @@ -404,7 +477,9 @@ Path RemoteStore::addToStore(const Path & _srcPath, writeString(printHashType(hashAlgo), to); dumpPath(srcPath, to, filter); processStderr(); - return readStorePath(from); + Path res = readStorePath(from); + reportRecursivePath(res); + return res; } @@ -420,7 +495,9 @@ Path RemoteStore::addTextToStore(const string & name, const string & s, writeStrings(references, to); processStderr(); - return readStorePath(from); + Path res = readStorePath(from); + reportRecursivePath(res); + return res; } @@ -443,7 +520,9 @@ Paths RemoteStore::importPaths(bool requireSignature, Source & source) /* We ignore requireSignature, since the worker forces it to true anyway. */ processStderr(0, &source); - return readStorePaths(from); + Paths res = readStorePaths(from); + reportRecursivePaths(PathSet(res.begin(), res.end())); + return res; } @@ -464,6 +543,35 @@ void RemoteStore::buildPaths(const PathSet & drvPaths, BuildMode buildMode) } processStderr(); readInt(from); + + if (fdRecursivePaths != -1) { + PathSet recursivePaths; + foreach (PathSet::const_iterator, i, drvPaths) { + DrvPathWithOutputs i2 = parseDrvPathWithOutputs(*i); + if (isDerivation(i2.first)) { + Derivation drv; + try { + drv = parseDerivation(readFile(i2.first)); + recursivePaths.insert(i2.first); + } catch (SysError & e) { + if (e.errNo == ENOENT) { + /* Need to ask for the drv to be added to chroot */ + _reportRecursivePath(i2.first); + drv = parseDerivation(readFile(i2.first)); + } else + throw; + } + if (i2.second.empty()) + foreach (DerivationOutputs::iterator, j, drv.outputs) + recursivePaths.insert(j->second.path); + else + foreach (StringSet::iterator, j, i2.second) + recursivePaths.insert(drv.outputs[*j].path); + } else + recursivePaths.insert(*i); + } + _reportRecursivePaths(recursivePaths); + } } @@ -474,6 +582,7 @@ void RemoteStore::ensurePath(const Path & path) writeString(path, to); processStderr(); readInt(from); + reportRecursivePath(path); } @@ -513,11 +622,14 @@ Roots RemoteStore::findRoots() processStderr(); unsigned int count = readInt(from); Roots result; + PathSet recursivePaths; while (count--) { Path link = readString(from); Path target = readStorePath(from); + recursivePaths.insert(target); result[link] = target; } + reportRecursivePaths(recursivePaths); return result; } @@ -565,6 +677,56 @@ void RemoteStore::clearFailedPaths(const PathSet & paths) } +static void writeRecursivePaths(int fd, const string & s) { + lockFile(fd, ltWrite, true); + writeFull(fd, (const unsigned char *) s.data(), s.size()); + unsigned char c; + readFull(fd, &c, sizeof c); + lockFile(fd, ltNone, true); +} + + +void RemoteStore::_reportRecursivePath(const Path & path) +{ + if (path.size() >= 4096) + throw Error("reporting a path name bigger than 4096 bytes not allowed"); + string s = path + '\0'; + writeRecursivePaths(fdRecursivePaths, s); +} + + +void RemoteStore::reportRecursivePath(const Path & path) +{ + if (fdRecursivePaths != -1) + _reportRecursivePath(path); +} + + +void RemoteStore::_reportRecursivePaths(const PathSet & paths) +{ + if (!paths.empty()) { + string s = ""; + foreach (PathSet::const_iterator, i, paths) { + if (s.size() + i->size() >= 4096) { + if (i->size() >= 4096) + throw Error("reporting a path name bigger than 4096 bytes not allowed"); + writeRecursivePaths(fdRecursivePaths, s); + s = ""; + } + s += *i + '\0'; + } + writeRecursivePaths(fdRecursivePaths, s); + } +} + + +void RemoteStore::reportRecursivePaths(const PathSet & paths) +{ + if (fdRecursivePaths != -1) + _reportRecursivePaths(paths); +} + + void RemoteStore::processStderr(Sink * sink, Source * source) { to.flush(); diff --git a/src/libstore/remote-store.hh b/src/libstore/remote-store.hh index 04b60fce4b4..6a4fcb01b44 100644 --- a/src/libstore/remote-store.hh +++ b/src/libstore/remote-store.hh @@ -90,11 +90,20 @@ private: Pid child; unsigned int daemonVersion; bool initialised; + AutoCloseFD fdRecursivePaths; void openConnection(bool reserveSpace = true); void processStderr(Sink * sink = 0, Source * source = 0); + void _reportRecursivePath(const Path & path); + + void _reportRecursivePaths(const PathSet & paths); + + void reportRecursivePath(const Path & path); + + void reportRecursivePaths(const PathSet & paths); + void connectToDaemon(); void setOptions(); diff --git a/src/libstore/worker-protocol.hh b/src/libstore/worker-protocol.hh index 9317f89c376..4a366ab9b94 100644 --- a/src/libstore/worker-protocol.hh +++ b/src/libstore/worker-protocol.hh @@ -1,4 +1,5 @@ #pragma once +#include namespace nix { @@ -7,6 +8,7 @@ namespace nix { #define WORKER_MAGIC_2 0x6478696f #define PROTOCOL_VERSION 0x10e +#define RECURSIVE_PROTOCOL_VERSION 0x101 #define GET_PROTOCOL_MAJOR(x) ((x) & 0xff00) #define GET_PROTOCOL_MINOR(x) ((x) & 0x00ff) @@ -58,3 +60,23 @@ template T readStorePaths(Source & from); } + +/* Borrowed from http://swtch.com/usr/local/plan9/src/lib9/sendfd.c, should + really be part of POSIX. + Copyright (C) 2003, Lucent Technologies Inc. and others. All Rights Reserved. */ + +#ifndef CMSG_ALIGN +# ifdef __sun__ +# define CMSG_ALIGN _CMSG_DATA_ALIGN +# else +# define CMSG_ALIGN(len) (((len)+sizeof(long)-1) & ~(sizeof(long)-1)) +# endif +#endif + +#ifndef CMSG_SPACE +# define CMSG_SPACE(len) (CMSG_ALIGN(sizeof(struct cmsghdr))+CMSG_ALIGN(len)) +#endif + +#ifndef CMSG_LEN +# define CMSG_LEN(len) (CMSG_ALIGN(sizeof(struct cmsghdr))+(len)) +#endif diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 740d767a4ea..a1b0dc44968 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -921,6 +921,24 @@ void closeOnExec(int fd) } +void noCloseOnExec(int fd) +{ + int prev; + if ((prev = fcntl(fd, F_GETFD, 0)) == -1 || + fcntl(fd, F_SETFD, prev & ~FD_CLOEXEC) == -1) + throw SysError("unsetting close-on-exec flag"); +} + + +void setNonBlocking(int fd) +{ + int prev; + if ((prev = fcntl(fd, F_GETFL, 0)) == -1 || + fcntl(fd, F_SETFL, prev | O_NONBLOCK) == -1) + throw SysError("setting non-blocking flag"); +} + + #if HAVE_VFORK pid_t (*maybeVfork)() = vfork; #else diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 0351220c2a2..b782db547b4 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -261,6 +261,12 @@ void closeMostFDs(const set & exceptions); /* Set the close-on-exec flag for the given file descriptor. */ void closeOnExec(int fd); +/* Unset the close-on-exec flag for the given file descriptor. */ +void noCloseOnExec(int fd); + +/* Put the given file descriptor into non-blocking mode. */ +void setNonBlocking(int fd); + /* Call vfork() if available, otherwise fork(). */ extern pid_t (*maybeVfork)(); diff --git a/src/nix-daemon/nix-daemon.cc b/src/nix-daemon/nix-daemon.cc index f40cdd51b4b..5f8d22582b2 100644 --- a/src/nix-daemon/nix-daemon.cc +++ b/src/nix-daemon/nix-daemon.cc @@ -42,6 +42,7 @@ using namespace nix; static FdSource from(STDIN_FILENO); static FdSink to(STDOUT_FILENO); +static AutoCloseFD fdRecursiveTo; bool canSendStderr; pid_t myPid; @@ -695,8 +696,8 @@ static void processConnection(bool trusted) throw Error("if you run `nix-daemon' as root, then you MUST set `build-users-group'!"); #endif - /* Open the store. */ - store = boost::shared_ptr(new LocalStore(reserveSpace)); + /* Open the store, with this process's parent socket used for recursive nix */ + store = boost::shared_ptr(new LocalStore(reserveSpace, fdRecursiveTo.borrow())); stopWork(); to.flush(); @@ -776,6 +777,9 @@ static void daemonLoop() AutoCloseFD fdSocket; + AutoCloseFD fdRecursiveFrom; + AutoCloseFD fdRecursiveClose; + string fds; /* Handle socket-based activation by systemd. */ if (getEnv("LISTEN_FDS") != "") { if (getEnv("LISTEN_PID") != int2String(getpid()) || getEnv("LISTEN_FDS") != "1") @@ -783,10 +787,38 @@ static void daemonLoop() fdSocket = SD_LISTEN_FDS_START; } + /* Handle nix-daemon run as a child of build.cc for recursive nix */ + else if ((fds = getEnv("NIX_RECURSIVE_FDS")) != "") { + Strings tokenized = tokenizeString(fds); + + Strings::iterator token = tokenized.begin(); + if (token == tokenized.end()) + throw Error(format("invalid value for NIX_RECURSIVE_FDS, `%1%'") % fds); + int fd; + if (!string2Int(*token, fd)) + throw Error(format("invalid value for NIX_RECURSIVE_FDS, `%1%'") % fds); + fdRecursiveFrom = fd; + + ++token; + if (token == tokenized.end()) + throw Error(format("invalid value for NIX_RECURSIVE_FDS, `%1%'") % fds); + if (!string2Int(*token, fd)) + throw Error(format("invalid value for NIX_RECURSIVE_FDS, `%1%'") % fds); + fdRecursiveTo = fd; + + ++token; + if (token == tokenized.end()) + throw Error(format("invalid value for NIX_RECURSIVE_FDS, `%1%'") % fds); + if (!string2Int(*token, fd)) + throw Error(format("invalid value for NIX_RECURSIVE_FDS, `%1%'") % fds); + fdRecursiveClose = fd; + closeOnExec(fdRecursiveClose); + + unsetenv("NIX_RECURSIVE_FDS"); + } + /* Otherwise, create and bind to a Unix domain socket. */ else { - - /* Create and bind to a Unix domain socket. */ fdSocket = socket(PF_UNIX, SOCK_STREAM, 0); if (fdSocket == -1) throw SysError("cannot create Unix domain socket"); @@ -824,7 +856,24 @@ static void daemonLoop() throw SysError(format("cannot listen on socket `%1%'") % socketPath); } - closeOnExec(fdSocket); + if (fdRecursiveFrom == -1) { + int fds[2]; + if (socketpair(PF_UNIX, SOCK_STREAM, 0, fds) == -1) + throw SysError("creating recursive daemon socketpair"); + fdRecursiveFrom = fds[0]; + fdRecursiveTo = fds[1]; + if (shutdown(fdRecursiveFrom, SHUT_WR) == -1) + throw SysError("disabling writes to fdRecursiveFrom"); + if (shutdown(fdRecursiveTo, SHUT_RD) == -1) + throw SysError("disabling reads from fdRecursiveTo"); + } + + closeOnExec(fdRecursiveFrom); + + if (fdSocket != -1) { + setNonBlocking(fdSocket); + closeOnExec(fdSocket); + } /* Loop accepting connections. */ while (1) { @@ -834,18 +883,80 @@ static void daemonLoop() database, because it doesn't like forks very much. */ assert(!store); - /* Accept a connection. */ - struct sockaddr_un remoteAddr; - socklen_t remoteAddrLen = sizeof(remoteAddr); + fd_set set; + FD_ZERO(&set); + FD_SET(fdRecursiveFrom, &set); + /* Sigh, POSIX requires valid fds for FD_SET/FD_ISSET */ + int nfds; + if (fdSocket != -1) { + FD_SET(fdSocket, &set); + nfds = + ((fdSocket > fdRecursiveFrom) ? fdSocket : fdRecursiveFrom) + 1; + } else { + FD_SET(fdRecursiveClose, &set); + nfds = + ((fdRecursiveClose > fdRecursiveFrom) ? fdRecursiveClose : fdRecursiveFrom) + 1; + } - AutoCloseFD remote = accept(fdSocket, - (struct sockaddr *) &remoteAddr, &remoteAddrLen); + int res = select(nfds, &set, 0, 0, 0); checkInterrupt(); - if (remote == -1) { - if (errno == EINTR) - continue; - else - throw SysError("accepting connection"); + if (res == -1) { + if (errno == EINTR) continue; + throw SysError("waiting for a new connection"); + } + + AutoCloseFD remote; + if ((fdSocket != -1) && FD_ISSET(fdSocket, &set)) { + /* Accept a connection. */ + struct sockaddr_un remoteAddr; + socklen_t remoteAddrLen = sizeof(remoteAddr); + + remote = accept(fdSocket, + (struct sockaddr *) &remoteAddr, &remoteAddrLen); + checkInterrupt(); + if (remote == -1) { + if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) + continue; + else + throw SysError("accepting connection"); + } + + } else if (fdRecursiveClose != -1 && FD_ISSET(fdRecursiveClose, &set)) { + /* Parent died, so we're done */ + return; + } else { + /* Lots of connections on fdSocket could theoretically starve + the recursive socket, oh well */ + assert(FD_ISSET(fdRecursiveFrom, &set)); + + /* Get the socket from the other end */ + int fd; + char buf[sizeof RECURSIVE_PROTOCOL_VERSION]; + struct iovec iov; + iov.iov_base = buf; + iov.iov_len = sizeof buf; + struct msghdr msg; + memset(&msg, 0, sizeof msg); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + char data[CMSG_SPACE(sizeof fdRecursiveFrom)]; + msg.msg_control = data; + msg.msg_controllen = sizeof data; + ssize_t count = recvmsg(fdRecursiveFrom, &msg, 0); + if (count == -1) + throw SysError("recieving socket descriptor from client"); + else if (((size_t) count) != sizeof buf) + throw Error(format("expected to receive %1% bytes from client, got %2%") % sizeof buf % count); + + memmove(&fd, CMSG_DATA(CMSG_FIRSTHDR(&msg)), sizeof fd); + + remote = fd; + + unsigned int recursiveClientVersion = 0; + for (size_t byte = 0; byte < sizeof RECURSIVE_PROTOCOL_VERSION; ++byte) + recursiveClientVersion += ((unsigned int) buf[byte]) << (8 * byte); + if (GET_PROTOCOL_MAJOR(recursiveClientVersion) != GET_PROTOCOL_MAJOR(RECURSIVE_PROTOCOL_VERSION)) + throw Error("nix client recursive protocol version not supported"); } closeOnExec(remote); diff --git a/tests/config.nix b/tests/config.nix index 6244a15fa48..5ad6d20feab 100644 --- a/tests/config.nix +++ b/tests/config.nix @@ -1,7 +1,7 @@ with import ; rec { - inherit shell; + inherit shell nixBinDir; path = coreutils; diff --git a/tests/local.mk b/tests/local.mk index ab170dfe51e..c58496dad11 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -10,7 +10,7 @@ nix_tests = \ remote-store.sh export.sh export-graph.sh negative-caching.sh \ binary-patching.sh timeout.sh secure-drv-outputs.sh nix-channel.sh \ multiple-outputs.sh import-derivation.sh fetchurl.sh optimise-store.sh \ - binary-cache.sh nix-profile.sh repair.sh dump-db.sh + binary-cache.sh nix-profile.sh repair.sh dump-db.sh recursive.sh install-tests += $(foreach x, $(nix_tests), tests/$(x)) diff --git a/tests/recursive.builder.sh b/tests/recursive.builder.sh new file mode 100644 index 00000000000..aa1dd0a8a1f --- /dev/null +++ b/tests/recursive.builder.sh @@ -0,0 +1 @@ +ln -sv $(nix-store -r $(nix-instantiate $simple)) $out diff --git a/tests/recursive.nix b/tests/recursive.nix new file mode 100644 index 00000000000..3617dfa199c --- /dev/null +++ b/tests/recursive.nix @@ -0,0 +1,8 @@ +with import ./config.nix; + +mkDerivation { + name = "recursive"; + builder = ./recursive.builder.sh; + PATH = "${path}:${nixBinDir}"; + simple = builtins.toString ./simple.nix; +} diff --git a/tests/recursive.sh b/tests/recursive.sh new file mode 100644 index 00000000000..25f8c30c746 --- /dev/null +++ b/tests/recursive.sh @@ -0,0 +1,19 @@ +source common.sh + +drvPath=$(nix-instantiate recursive.nix) + +test "$(nix-store -q --binding system "$drvPath")" = "$system" + +echo "derivation is $drvPath" + +outPath=$(nix-store -rvv "$drvPath") + +echo "output path is $outPath" + +text=$(cat "$outPath"/hello) +if test "$text" != "Hello World!"; then exit 1; fi + +# Directed delete: $outPath is not reachable from a root, so it should +# be deleteable. +nix-store --delete $(readlink $outPath) +if test -e $outPath; then false; fi