summary refs log tree commit diff
path: root/nix/nix-daemon/nix-daemon.cc
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2014-12-17 23:00:42 +0100
committerLudovic Courtès <ludo@gnu.org>2014-12-19 22:47:37 +0100
commit36457566f9917dc7c0c348d012816a2ca333ef1b (patch)
tree6f1d22a195ea2483b9ce539227d65e8e2a9c137d /nix/nix-daemon/nix-daemon.cc
parent2c7ee1672029aa43afb509af5b5f7261244fa2d1 (diff)
downloadguix-36457566f9917dc7c0c348d012816a2ca333ef1b.tar.gz
Merge branch 'nix' into 'master'.
Diffstat (limited to 'nix/nix-daemon/nix-daemon.cc')
-rw-r--r--nix/nix-daemon/nix-daemon.cc939
1 files changed, 939 insertions, 0 deletions
diff --git a/nix/nix-daemon/nix-daemon.cc b/nix/nix-daemon/nix-daemon.cc
new file mode 100644
index 0000000000..8814fe3155
--- /dev/null
+++ b/nix/nix-daemon/nix-daemon.cc
@@ -0,0 +1,939 @@
+#include "shared.hh"
+#include "local-store.hh"
+#include "util.hh"
+#include "serialise.hh"
+#include "worker-protocol.hh"
+#include "archive.hh"
+#include "affinity.hh"
+#include "globals.hh"
+
+#include <cstring>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <fcntl.h>
+#include <errno.h>
+
+using namespace nix;
+
+
+/* On platforms that have O_ASYNC, we can detect when a client
+   disconnects and immediately kill any ongoing builds.  On platforms
+   that lack it, we only notice the disconnection the next time we try
+   to write to the client.  So if you have a builder that never
+   generates output on stdout/stderr, the daemon will never notice
+   that the client has disconnected until the builder terminates.
+
+   GNU/Hurd does have O_ASYNC, but its Unix-domain socket translator
+   (pflocal) does not implement F_SETOWN.  See
+   <http://lists.gnu.org/archive/html/bug-guix/2013-07/msg00021.html> for
+   details.*/
+#if defined O_ASYNC && !defined __GNU__
+#define HAVE_HUP_NOTIFICATION
+#ifndef SIGPOLL
+#define SIGPOLL SIGIO
+#endif
+#endif
+
+
+static FdSource from(STDIN_FILENO);
+static FdSink to(STDOUT_FILENO);
+
+bool canSendStderr;
+pid_t myPid;
+
+
+
+/* This function is called anytime we want to write something to
+   stderr.  If we're in a state where the protocol allows it (i.e.,
+   when canSendStderr), send the message to the client over the
+   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()) {
+        try {
+            writeInt(STDERR_NEXT, to);
+            writeString(buf, count, to);
+            to.flush();
+        } catch (...) {
+            /* Write failed; that means that the other side is
+               gone. */
+            canSendStderr = false;
+            throw;
+        }
+    } else
+        writeFull(STDERR_FILENO, buf, count);
+}
+
+
+/* Return true if the remote side has closed its end of the
+   connection, false otherwise.  Should not be called on any socket on
+   which we expect input! */
+static bool isFarSideClosed(int socket)
+{
+    struct timeval timeout;
+    timeout.tv_sec = timeout.tv_usec = 0;
+
+    fd_set fds;
+    FD_ZERO(&fds);
+    FD_SET(socket, &fds);
+
+    while (select(socket + 1, &fds, 0, 0, &timeout) == -1)
+        if (errno != EINTR) throw SysError("select()");
+
+    if (!FD_ISSET(socket, &fds)) return false;
+
+    /* Destructive read to determine whether the select() marked the
+       socket as readable because there is actual input or because
+       we've reached EOF (i.e., a read of size 0 is available). */
+    char c;
+    int rd;
+    if ((rd = read(socket, &c, 1)) > 0)
+        throw Error("EOF expected (protocol error?)");
+    else if (rd == -1 && errno != ECONNRESET)
+        throw SysError("expected connection reset or EOF");
+
+    return true;
+}
+
+
+/* A SIGPOLL signal is received when data is available on the client
+   communication socket, or when the client has closed its side of the
+   socket.  This handler is enabled at precisely those moments in the
+   protocol when we're doing work and the client is supposed to be
+   quiet.  Thus, if we get a SIGPOLL signal, it means that the client
+   has quit.  So we should quit as well.
+
+   Too bad most operating systems don't support the POLL_HUP value for
+   si_code in siginfo_t.  That would make most of the SIGPOLL
+   complexity unnecessary, i.e., we could just enable SIGPOLL all the
+   time and wouldn't have to worry about races. */
+static void sigPollHandler(int sigNo)
+{
+    using namespace std;
+    try {
+        /* Check that the far side actually closed.  We're still
+           getting spurious signals every once in a while.  I.e.,
+           there is no input available, but we get a signal with
+           POLL_IN set.  Maybe it's delayed or something. */
+        if (isFarSideClosed(from.fd)) {
+            if (!blockInt) {
+                _isInterrupted = 1;
+                blockInt = 1;
+                canSendStderr = false;
+                const char * s = "SIGPOLL\n";
+                write(STDERR_FILENO, s, strlen(s));
+            }
+        } else {
+            const char * s = "spurious SIGPOLL\n";
+            write(STDERR_FILENO, s, strlen(s));
+        }
+    }
+    catch (Error & e) {
+        /* Shouldn't happen. */
+        string s = "impossible: " + e.msg() + '\n';
+        write(STDERR_FILENO, s.data(), s.size());
+        throw;
+    }
+}
+
+
+static void setSigPollAction(bool enable)
+{
+#ifdef HAVE_HUP_NOTIFICATION
+    struct sigaction act, oact;
+    act.sa_handler = enable ? sigPollHandler : SIG_IGN;
+    sigfillset(&act.sa_mask);
+    act.sa_flags = 0;
+    if (sigaction(SIGPOLL, &act, &oact))
+        throw SysError("setting handler for SIGPOLL");
+#endif
+}
+
+
+/* startWork() means that we're starting an operation for which we
+   want to send out stderr to the client. */
+static void startWork()
+{
+    canSendStderr = true;
+
+    /* Handle client death asynchronously. */
+    setSigPollAction(true);
+
+    /* Of course, there is a race condition here: the socket could
+       have closed between when we last read from / wrote to it, and
+       between the time we set the handler for SIGPOLL.  In that case
+       we won't get the signal.  So do a non-blocking select() to find
+       out if any input is available on the socket.  If there is, it
+       has to be the 0-byte read that indicates that the socket has
+       closed. */
+    if (isFarSideClosed(from.fd)) {
+        _isInterrupted = 1;
+        checkInterrupt();
+    }
+}
+
+
+/* stopWork() means that we're done; stop sending stderr to the
+   client. */
+static void stopWork(bool success = true, const string & msg = "", unsigned int status = 0)
+{
+    /* Stop handling async client death; we're going to a state where
+       we're either sending or receiving from the client, so we'll be
+       notified of client death anyway. */
+    setSigPollAction(false);
+
+    canSendStderr = false;
+
+    if (success)
+        writeInt(STDERR_LAST, to);
+    else {
+        writeInt(STDERR_ERROR, to);
+        writeString(msg, to);
+        if (status != 0) writeInt(status, to);
+    }
+}
+
+
+struct TunnelSink : Sink
+{
+    Sink & to;
+    TunnelSink(Sink & to) : to(to) { }
+    virtual void operator () (const unsigned char * data, size_t len)
+    {
+        writeInt(STDERR_WRITE, to);
+        writeString(data, len, to);
+    }
+};
+
+
+struct TunnelSource : BufferedSource
+{
+    Source & from;
+    TunnelSource(Source & from) : from(from) { }
+    size_t readUnbuffered(unsigned char * data, size_t len)
+    {
+        /* Careful: we're going to receive data from the client now,
+           so we have to disable the SIGPOLL handler. */
+        setSigPollAction(false);
+        canSendStderr = false;
+
+        writeInt(STDERR_READ, to);
+        writeInt(len, to);
+        to.flush();
+        size_t n = readString(data, len, from);
+
+        startWork();
+        if (n == 0) throw EndOfFile("unexpected end-of-file");
+        return n;
+    }
+};
+
+
+/* If the NAR archive contains a single file at top-level, then save
+   the contents of the file to `s'.  Otherwise barf. */
+struct RetrieveRegularNARSink : ParseSink
+{
+    bool regular;
+    string s;
+
+    RetrieveRegularNARSink() : regular(true) { }
+
+    void createDirectory(const Path & path)
+    {
+        regular = false;
+    }
+
+    void receiveContents(unsigned char * data, unsigned int len)
+    {
+        s.append((const char *) data, len);
+    }
+
+    void createSymlink(const Path & path, const string & target)
+    {
+        regular = false;
+    }
+};
+
+
+/* Adapter class of a Source that saves all data read to `s'. */
+struct SavingSourceAdapter : Source
+{
+    Source & orig;
+    string s;
+    SavingSourceAdapter(Source & orig) : orig(orig) { }
+    size_t read(unsigned char * data, size_t len)
+    {
+        size_t n = orig.read(data, len);
+        s.append((const char *) data, n);
+        return n;
+    }
+};
+
+
+static void performOp(bool trusted, unsigned int clientVersion,
+    Source & from, Sink & to, unsigned int op)
+{
+    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,
+           call 'startWork' early, and do 'assertStorePath' afterwards so
+           that the 'Error' exception handler doesn't close the
+           connection.  */
+        Path path = readString(from);
+        startWork();
+        assertStorePath(path);
+        bool result = store->isValidPath(path);
+        stopWork();
+        writeInt(result, to);
+        break;
+    }
+
+    case wopQueryValidPaths: {
+        PathSet paths = readStorePaths<PathSet>(from);
+        startWork();
+        PathSet res = store->queryValidPaths(paths);
+        stopWork();
+        writeStrings(res, to);
+        break;
+    }
+
+    case wopHasSubstitutes: {
+        Path path = readStorePath(from);
+        startWork();
+        PathSet res = store->querySubstitutablePaths(singleton<PathSet>(path));
+        stopWork();
+        writeInt(res.find(path) != res.end(), to);
+        break;
+    }
+
+    case wopQuerySubstitutablePaths: {
+        PathSet paths = readStorePaths<PathSet>(from);
+        startWork();
+        PathSet res = store->querySubstitutablePaths(paths);
+        stopWork();
+        writeStrings(res, to);
+        break;
+    }
+
+    case wopQueryPathHash: {
+        Path path = readStorePath(from);
+        startWork();
+        Hash hash = store->queryPathHash(path);
+        stopWork();
+        writeString(printHash(hash), to);
+        break;
+    }
+
+    case wopQueryReferences:
+    case wopQueryReferrers:
+    case wopQueryValidDerivers:
+    case wopQueryDerivationOutputs: {
+        Path path = readStorePath(from);
+        startWork();
+        PathSet paths;
+        if (op == wopQueryReferences)
+            store->queryReferences(path, paths);
+        else if (op == wopQueryReferrers)
+            store->queryReferrers(path, paths);
+        else if (op == wopQueryValidDerivers)
+            paths = store->queryValidDerivers(path);
+        else paths = store->queryDerivationOutputs(path);
+        stopWork();
+        writeStrings(paths, to);
+        break;
+    }
+
+    case wopQueryDerivationOutputNames: {
+        Path path = readStorePath(from);
+        startWork();
+        StringSet names;
+        names = store->queryDerivationOutputNames(path);
+        stopWork();
+        writeStrings(names, to);
+        break;
+    }
+
+    case wopQueryDeriver: {
+        Path path = readStorePath(from);
+        startWork();
+        Path deriver = store->queryDeriver(path);
+        stopWork();
+        writeString(deriver, to);
+        break;
+    }
+
+    case wopQueryPathFromHashPart: {
+        string hashPart = readString(from);
+        startWork();
+        Path path = store->queryPathFromHashPart(hashPart);
+        stopWork();
+        writeString(path, to);
+        break;
+    }
+
+    case wopAddToStore: {
+        string baseName = readString(from);
+        bool fixed = readInt(from) == 1; /* obsolete */
+        bool recursive = readInt(from) == 1;
+        string s = readString(from);
+        /* Compatibility hack. */
+        if (!fixed) {
+            s = "sha256";
+            recursive = true;
+        }
+        HashType hashAlgo = parseHashType(s);
+
+        SavingSourceAdapter savedNAR(from);
+        RetrieveRegularNARSink savedRegular;
+
+        if (recursive) {
+            /* Get the entire NAR dump from the client and save it to
+               a string so that we can pass it to
+               addToStoreFromDump(). */
+            ParseSink sink; /* null sink; just parse the NAR */
+            parseDump(sink, savedNAR);
+        } else
+            parseDump(savedRegular, from);
+
+        startWork();
+        if (!savedRegular.regular) throw Error("regular file expected");
+        Path path = dynamic_cast<LocalStore *>(store.get())
+            ->addToStoreFromDump(recursive ? savedNAR.s : savedRegular.s, baseName, recursive, hashAlgo);
+        stopWork();
+
+        writeString(path, to);
+        break;
+    }
+
+    case wopAddTextToStore: {
+        string suffix = readString(from);
+        string s = readString(from);
+        PathSet refs = readStorePaths<PathSet>(from);
+        startWork();
+        Path path = store->addTextToStore(suffix, s, refs);
+        stopWork();
+        writeString(path, to);
+        break;
+    }
+
+    case wopExportPath: {
+        Path path = readStorePath(from);
+        bool sign = readInt(from) == 1;
+        startWork();
+        TunnelSink sink(to);
+        store->exportPath(path, sign, sink);
+        stopWork();
+        writeInt(1, to);
+        break;
+    }
+
+    case wopImportPaths: {
+        startWork();
+        TunnelSource source(from);
+        Paths paths = store->importPaths(true, source);
+        stopWork();
+        writeStrings(paths, to);
+        break;
+    }
+
+    case wopBuildPaths: {
+        PathSet drvs = readStorePaths<PathSet>(from);
+        startWork();
+        store->buildPaths(drvs);
+        stopWork();
+        writeInt(1, to);
+        break;
+    }
+
+    case wopEnsurePath: {
+        Path path = readStorePath(from);
+        startWork();
+        store->ensurePath(path);
+        stopWork();
+        writeInt(1, to);
+        break;
+    }
+
+    case wopAddTempRoot: {
+        Path path = readStorePath(from);
+        startWork();
+        store->addTempRoot(path);
+        stopWork();
+        writeInt(1, to);
+        break;
+    }
+
+    case wopAddIndirectRoot: {
+        Path path = absPath(readString(from));
+        startWork();
+        store->addIndirectRoot(path);
+        stopWork();
+        writeInt(1, to);
+        break;
+    }
+
+    case wopSyncWithGC: {
+        startWork();
+        store->syncWithGC();
+        stopWork();
+        writeInt(1, to);
+        break;
+    }
+
+    case wopFindRoots: {
+        startWork();
+        Roots roots = store->findRoots();
+        stopWork();
+        writeInt(roots.size(), to);
+        for (Roots::iterator i = roots.begin(); i != roots.end(); ++i) {
+            writeString(i->first, to);
+            writeString(i->second, to);
+        }
+        break;
+    }
+
+    case wopCollectGarbage: {
+        GCOptions options;
+        options.action = (GCOptions::GCAction) readInt(from);
+        options.pathsToDelete = readStorePaths<PathSet>(from);
+        options.ignoreLiveness = readInt(from);
+        options.maxFreed = readLongLong(from);
+        readInt(from); // obsolete field
+        if (GET_PROTOCOL_MINOR(clientVersion) >= 5) {
+            /* removed options */
+            readInt(from);
+            readInt(from);
+        }
+
+        GCResults results;
+
+        startWork();
+        if (options.ignoreLiveness)
+            throw Error("you are not allowed to ignore liveness");
+        store->collectGarbage(options, results);
+        stopWork();
+
+        writeStrings(results.paths, to);
+        writeLongLong(results.bytesFreed, to);
+        writeLongLong(0, to); // obsolete
+
+        break;
+    }
+
+    case wopSetOptions: {
+        settings.keepFailed = readInt(from) != 0;
+        settings.keepGoing = readInt(from) != 0;
+        settings.set("build-fallback", readInt(from) ? "true" : "false");
+        verbosity = (Verbosity) readInt(from);
+        settings.set("build-max-jobs", int2String(readInt(from)));
+        settings.set("build-max-silent-time", int2String(readInt(from)));
+        if (GET_PROTOCOL_MINOR(clientVersion) >= 2)
+            settings.useBuildHook = readInt(from) != 0;
+        if (GET_PROTOCOL_MINOR(clientVersion) >= 4) {
+            settings.buildVerbosity = (Verbosity) readInt(from);
+            logType = (LogType) readInt(from);
+            settings.printBuildTrace = readInt(from) != 0;
+        }
+        if (GET_PROTOCOL_MINOR(clientVersion) >= 6)
+            settings.set("build-cores", int2String(readInt(from)));
+        if (GET_PROTOCOL_MINOR(clientVersion) >= 10)
+            settings.set("build-use-substitutes", readInt(from) ? "true" : "false");
+        if (GET_PROTOCOL_MINOR(clientVersion) >= 12) {
+            unsigned int n = readInt(from);
+            for (unsigned int i = 0; i < n; i++) {
+                string name = readString(from);
+                string value = readString(from);
+                if (name == "build-timeout" || name == "use-ssh-substituter")
+                    settings.set(name, value);
+                else
+                    settings.set(trusted ? name : "untrusted-" + name, value);
+            }
+        }
+        settings.update();
+        startWork();
+        stopWork();
+        break;
+    }
+
+    case wopQuerySubstitutablePathInfo: {
+        Path path = absPath(readString(from));
+        startWork();
+        SubstitutablePathInfos infos;
+        store->querySubstitutablePathInfos(singleton<PathSet>(path), infos);
+        stopWork();
+        SubstitutablePathInfos::iterator i = infos.find(path);
+        if (i == infos.end())
+            writeInt(0, to);
+        else {
+            writeInt(1, to);
+            writeString(i->second.deriver, to);
+            writeStrings(i->second.references, to);
+            writeLongLong(i->second.downloadSize, to);
+            if (GET_PROTOCOL_MINOR(clientVersion) >= 7)
+                writeLongLong(i->second.narSize, to);
+        }
+        break;
+    }
+
+    case wopQuerySubstitutablePathInfos: {
+        PathSet paths = readStorePaths<PathSet>(from);
+        startWork();
+        SubstitutablePathInfos infos;
+        store->querySubstitutablePathInfos(paths, infos);
+        stopWork();
+        writeInt(infos.size(), to);
+        foreach (SubstitutablePathInfos::iterator, i, infos) {
+            writeString(i->first, to);
+            writeString(i->second.deriver, to);
+            writeStrings(i->second.references, to);
+            writeLongLong(i->second.downloadSize, to);
+            writeLongLong(i->second.narSize, to);
+        }
+        break;
+    }
+
+    case wopQueryAllValidPaths: {
+        startWork();
+        PathSet paths = store->queryAllValidPaths();
+        stopWork();
+        writeStrings(paths, to);
+        break;
+    }
+
+    case wopQueryFailedPaths: {
+        startWork();
+        PathSet paths = store->queryFailedPaths();
+        stopWork();
+        writeStrings(paths, to);
+        break;
+    }
+
+    case wopClearFailedPaths: {
+        PathSet paths = readStrings<PathSet>(from);
+        startWork();
+        store->clearFailedPaths(paths);
+        stopWork();
+        writeInt(1, to);
+        break;
+    }
+
+    case wopQueryPathInfo: {
+        Path path = readStorePath(from);
+        startWork();
+        ValidPathInfo info = store->queryPathInfo(path);
+        stopWork();
+        writeString(info.deriver, to);
+        writeString(printHash(info.hash), to);
+        writeStrings(info.references, to);
+        writeInt(info.registrationTime, to);
+        writeLongLong(info.narSize, to);
+        break;
+    }
+
+    default:
+        throw Error(format("invalid operation %1%") % op);
+    }
+}
+
+
+static void processConnection(bool trusted)
+{
+    canSendStderr = false;
+    myPid = getpid();
+    _writeToStderr = tunnelStderr;
+
+#ifdef HAVE_HUP_NOTIFICATION
+    /* Allow us to receive SIGPOLL for events on the client socket. */
+    setSigPollAction(false);
+    if (fcntl(from.fd, F_SETOWN, getpid()) == -1)
+        throw SysError("F_SETOWN");
+    if (fcntl(from.fd, F_SETFL, fcntl(from.fd, F_GETFL, 0) | O_ASYNC) == -1)
+        throw SysError("F_SETFL");
+#endif
+
+    /* Exchange the greeting. */
+    unsigned int magic = readInt(from);
+    if (magic != WORKER_MAGIC_1) throw Error("protocol mismatch");
+    writeInt(WORKER_MAGIC_2, to);
+    writeInt(PROTOCOL_VERSION, to);
+    to.flush();
+    unsigned int clientVersion = readInt(from);
+
+    if (GET_PROTOCOL_MINOR(clientVersion) >= 14 && readInt(from))
+        setAffinityTo(readInt(from));
+
+    bool reserveSpace = true;
+    if (GET_PROTOCOL_MINOR(clientVersion) >= 11)
+        reserveSpace = readInt(from) != 0;
+
+    /* Send startup error messages to the client. */
+    startWork();
+
+    try {
+
+        /* If we can't accept clientVersion, then throw an error
+           *here* (not above). */
+
+#if 0
+        /* Prevent users from doing something very dangerous. */
+        if (geteuid() == 0 &&
+            querySetting("build-users-group", "") == "")
+            throw Error("if you run `nix-daemon' as root, then you MUST set `build-users-group'!");
+#endif
+
+        /* Open the store. */
+        store = std::shared_ptr<StoreAPI>(new LocalStore(reserveSpace));
+
+        stopWork();
+        to.flush();
+
+    } catch (Error & e) {
+        stopWork(false, e.msg());
+        to.flush();
+        return;
+    }
+
+    /* Process client requests. */
+    unsigned int opCount = 0;
+
+    while (true) {
+        WorkerOp op;
+        try {
+            op = (WorkerOp) readInt(from);
+        } catch (EndOfFile & e) {
+            break;
+        }
+
+        opCount++;
+
+        try {
+            performOp(trusted, clientVersion, from, to, op);
+        } catch (Error & e) {
+            /* If we're not in a state where we can send replies, then
+               something went wrong processing the input of the
+               client.  This can happen especially if I/O errors occur
+               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;
+        } catch (std::bad_alloc & e) {
+            if (canSendStderr)
+                stopWork(false, "Nix daemon out of memory", GET_PROTOCOL_MINOR(clientVersion) >= 8 ? 1 : 0);
+            throw;
+        }
+
+        to.flush();
+
+        assert(!canSendStderr);
+    };
+
+    printMsg(lvlError, format("%1% operations") % opCount);
+}
+
+
+static void sigChldHandler(int sigNo)
+{
+    /* Reap all dead children. */
+    while (waitpid(-1, 0, WNOHANG) > 0) ;
+}
+
+
+static void setSigChldAction(bool autoReap)
+{
+    struct sigaction act, oact;
+    act.sa_handler = autoReap ? sigChldHandler : SIG_DFL;
+    sigfillset(&act.sa_mask);
+    act.sa_flags = 0;
+    if (sigaction(SIGCHLD, &act, &oact))
+        throw SysError("setting SIGCHLD handler");
+}
+
+
+#define SD_LISTEN_FDS_START 3
+
+
+static void daemonLoop()
+{
+    /* Get rid of children automatically; don't let them become
+       zombies. */
+    setSigChldAction(true);
+
+    AutoCloseFD fdSocket;
+
+    /* Handle socket-based activation by systemd. */
+    if (getEnv("LISTEN_FDS") != "") {
+        if (getEnv("LISTEN_PID") != int2String(getpid()) || getEnv("LISTEN_FDS") != "1")
+            throw Error("unexpected systemd environment variables");
+        fdSocket = SD_LISTEN_FDS_START;
+    }
+
+    /* 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");
+
+        string socketPath = settings.nixDaemonSocketFile;
+
+        createDirs(dirOf(socketPath));
+
+        /* Urgh, sockaddr_un allows path names of only 108 characters.
+           So chdir to the socket directory so that we can pass a
+           relative path name. */
+        chdir(dirOf(socketPath).c_str());
+        Path socketPathRel = "./" + baseNameOf(socketPath);
+
+        struct sockaddr_un addr;
+        addr.sun_family = AF_UNIX;
+        if (socketPathRel.size() >= sizeof(addr.sun_path))
+            throw Error(format("socket path `%1%' is too long") % socketPathRel);
+        strcpy(addr.sun_path, socketPathRel.c_str());
+
+        unlink(socketPath.c_str());
+
+        /* Make sure that the socket is created with 0666 permission
+           (everybody can connect --- provided they have access to the
+           directory containing the socket). */
+        mode_t oldMode = umask(0111);
+        int res = bind(fdSocket, (struct sockaddr *) &addr, sizeof(addr));
+        umask(oldMode);
+        if (res == -1)
+            throw SysError(format("cannot bind to socket `%1%'") % socketPath);
+
+        chdir("/"); /* back to the root */
+
+        if (listen(fdSocket, 5) == -1)
+            throw SysError(format("cannot listen on socket `%1%'") % socketPath);
+    }
+
+    closeOnExec(fdSocket);
+
+    /* Loop accepting connections. */
+    while (1) {
+
+        try {
+            /* Important: the server process *cannot* open the SQLite
+               database, because it doesn't like forks very much. */
+            assert(!store);
+
+            /* Accept a connection. */
+            struct sockaddr_un remoteAddr;
+            socklen_t remoteAddrLen = sizeof(remoteAddr);
+
+            AutoCloseFD remote = accept(fdSocket,
+                (struct sockaddr *) &remoteAddr, &remoteAddrLen);
+            checkInterrupt();
+            if (remote == -1) {
+                if (errno == EINTR)
+                    continue;
+                else
+                    throw SysError("accepting connection");
+            }
+
+            closeOnExec(remote);
+
+            /* Get the identity of the caller, if possible. */
+            uid_t clientUid = -1;
+            pid_t clientPid = -1;
+            bool trusted = false;
+
+#if defined(SO_PEERCRED)
+            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
+
+            printMsg(lvlInfo, format("accepted connection from pid %1%, uid %2%") % clientPid % clientUid);
+
+            /* Fork a child to handle the connection. */
+            pid_t child;
+            child = fork();
+
+            switch (child) {
+
+            case -1:
+                throw SysError("unable to fork");
+
+            case 0:
+                try { /* child */
+
+                    /* Background the daemon. */
+                    if (setsid() == -1)
+                        throw SysError(format("creating a new session"));
+
+                    /* Restore normal handling of SIGCHLD. */
+                    setSigChldAction(false);
+
+                    /* 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);
+
+                } catch (std::exception & e) {
+                    writeToStderr("unexpected Nix daemon error: " + string(e.what()) + "\n");
+                }
+                exit(0);
+            }
+
+        } catch (Interrupted & e) {
+            throw;
+        } catch (Error & e) {
+            printMsg(lvlError, format("error processing connection: %1%") % e.msg());
+        }
+    }
+}
+
+
+void run(Strings args)
+{
+    for (Strings::iterator i = args.begin(); i != args.end(); ) {
+        string arg = *i++;
+        if (arg == "--daemon") /* ignored for backwards compatibility */;
+    }
+
+    chdir("/");
+    daemonLoop();
+}
+
+
+void printHelp()
+{
+    showManPage("nix-daemon");
+}
+
+
+string programId = "nix-daemon";