From e35d6f78dc797150451f5134833afa0ecdf4a241 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 3 Oct 2012 17:57:20 -0400 Subject: Rename nix-worker to nix-daemon --- .gitignore | 4 +- configure.ac | 2 +- doc/manual/Makefile.am | 2 +- doc/manual/env-common.xml | 4 +- doc/manual/installation.xml | 10 +- doc/manual/manual.xml | 2 +- doc/manual/nix-daemon.xml | 34 ++ doc/manual/nix-worker.xml | 35 -- doc/manual/release-notes.xml | 8 +- misc/systemd/nix-daemon.service | 10 + misc/systemd/nix-worker.service | 10 - nix.spec.in | 8 +- src/Makefile.am | 2 +- src/nix-daemon/Makefile.am | 12 + src/nix-daemon/nix-daemon.cc | 914 ++++++++++++++++++++++++++++++++++++++++ src/nix-worker/Makefile.am | 9 - src/nix-worker/nix-worker.cc | 914 ---------------------------------------- tests/common.sh.in | 4 +- 18 files changed, 996 insertions(+), 988 deletions(-) create mode 100644 doc/manual/nix-daemon.xml delete mode 100644 doc/manual/nix-worker.xml create mode 100644 misc/systemd/nix-daemon.service delete mode 100644 misc/systemd/nix-worker.service create mode 100644 src/nix-daemon/Makefile.am create mode 100644 src/nix-daemon/nix-daemon.cc delete mode 100644 src/nix-worker/Makefile.am delete mode 100644 src/nix-worker/nix-worker.cc diff --git a/.gitignore b/.gitignore index bd66cabef24c..8a7e99d0676b 100644 --- a/.gitignore +++ b/.gitignore @@ -106,8 +106,8 @@ Makefile.in # /src/nix-store/ /src/nix-store/nix-store -# /src/nix-worker/ -/src/nix-worker/nix-worker +# /src/nix-daemon/ +/src/nix-daemon/nix-daemon # /tests/ /tests/test-tmp diff --git a/configure.ac b/configure.ac index 4b13ba276e3b..11a5390fa44c 100644 --- a/configure.ac +++ b/configure.ac @@ -364,7 +364,7 @@ AC_CONFIG_FILES([Makefile src/libexpr/Makefile src/nix-instantiate/Makefile src/nix-env/Makefile - src/nix-worker/Makefile + src/nix-daemon/Makefile src/nix-setuid-helper/Makefile src/nix-log2xml/Makefile src/bsdiff-4.3/Makefile diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index 42da3c8b0c09..eedc992a3851 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -23,7 +23,7 @@ man1_MANS = nix-env.1 nix-build.1 nix-store.1 nix-instantiate.1 \ man5_MANS = nix.conf.5 -man8_MANS = nix-worker.8 +man8_MANS = nix-daemon.8 FIGURES = figures/user-environments.png diff --git a/doc/manual/env-common.xml b/doc/manual/env-common.xml index c4c0f9d2df63..fcdd2c268399 100644 --- a/doc/manual/env-common.xml +++ b/doc/manual/env-common.xml @@ -304,9 +304,9 @@ $ mount -o bind /mnt/otherdisk/nix /nix /var/run/nix/remote-stores. Note that if you’re building through the Nix daemon, the only setting for + linkend="sec-nix-daemon">Nix daemon, the only setting for this variable that matters is the one that the - nix-worker process uses. So if you want to + nix-daemon process uses. So if you want to change it, you have to restart the daemon. diff --git a/doc/manual/installation.xml b/doc/manual/installation.xml index bbcc057f1d04..fdab71fc0f37 100644 --- a/doc/manual/installation.xml +++ b/doc/manual/installation.xml @@ -412,11 +412,11 @@ $ chown -R root /nix/store /nix/var/nix -The Nix daemon should be +The Nix daemon should be started as follows (as root): -$ nix-worker --daemon +$ nix-daemon You’ll want to put that line somewhere in your system’s boot scripts. @@ -450,11 +450,11 @@ named anything. It should own the Nix store and database: $ chown -R nix /nix/store /nix/var/nix -and of course nix-worker --daemon should be started -under that user, e.g., +and of course nix-daemon should be started under +that user, e.g., -$ su - nix -c "exec /nix/bin/nix-worker --daemon" +$ su - nix -c "exec /nix/bin/nix-daemon" diff --git a/doc/manual/manual.xml b/doc/manual/manual.xml index ccff9423cf17..aa461d7081bf 100644 --- a/doc/manual/manual.xml +++ b/doc/manual/manual.xml @@ -59,7 +59,7 @@ - +
diff --git a/doc/manual/nix-daemon.xml b/doc/manual/nix-daemon.xml new file mode 100644 index 000000000000..c68605fd6566 --- /dev/null +++ b/doc/manual/nix-daemon.xml @@ -0,0 +1,34 @@ + + + + nix-daemon + 8 + Nix + + + + + nix-daemon + Nix multi-user support daemon + + + + + nix-daemon + + + + +Description + +The Nix daemon is necessary in multi-user Nix installations. It +performs build actions and other operations on the Nix store on behalf +of unprivileged users. + + + + + diff --git a/doc/manual/nix-worker.xml b/doc/manual/nix-worker.xml deleted file mode 100644 index 5ae9d59bddc7..000000000000 --- a/doc/manual/nix-worker.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - nix-worker - 8 - Nix - - - - - nix-worker - Nix multi-user support daemon - - - - - nix-worker - - - - - -Description - -The Nix daemon is necessary in multi-user Nix installations. It -performs build actions and other operations on the Nix store on behalf -of unprivileged users. - - - - - diff --git a/doc/manual/release-notes.xml b/doc/manual/release-notes.xml index 43d5080afabd..8d0840d06e5f 100644 --- a/doc/manual/release-notes.xml +++ b/doc/manual/release-notes.xml @@ -10,7 +10,7 @@
Release 1.2 (TBA) -This release has the following improvements: +This release has the following improvements and changes: @@ -47,6 +47,12 @@ $ mount -o remount,ro,bind /nix/store modifications. + + The command nix-worker has been renamed + to nix-daemon. Support for running the Nix + worker in “slave” mode has been removed. + +
diff --git a/misc/systemd/nix-daemon.service b/misc/systemd/nix-daemon.service new file mode 100644 index 000000000000..ee28209f090e --- /dev/null +++ b/misc/systemd/nix-daemon.service @@ -0,0 +1,10 @@ +[Unit] +Description=Helper daemon for managing secure, multi-user Nix stores +After=syslog.target + +[Service] +Type=simple +ExecStart=/usr/bin/nix-daemon + +[Install] +WantedBy=multi-user.target diff --git a/misc/systemd/nix-worker.service b/misc/systemd/nix-worker.service deleted file mode 100644 index 2ededfea8068..000000000000 --- a/misc/systemd/nix-worker.service +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=Helper daemon for managing secure, multi-user Nix stores -After=syslog.target - -[Service] -Type=simple -ExecStart=/usr/bin/nix-worker --daemon - -[Install] -WantedBy=multi-user.target diff --git a/nix.spec.in b/nix.spec.in index 9d93c654d05e..822684c5e16a 100644 --- a/nix.spec.in +++ b/nix.spec.in @@ -139,7 +139,7 @@ chmod -x $RPM_BUILD_ROOT%{_sysconfdir}/profile.d/nix.sh %if ! 0%{?rhel} # install systemd service descriptor mkdir -p $RPM_BUILD_ROOT%{_prefix}/lib/systemd/system -cp -p misc/systemd/nix-worker.service \ +cp -p misc/systemd/nix-daemon.service \ $RPM_BUILD_ROOT%{_prefix}/lib/systemd/system/ %endif @@ -173,8 +173,8 @@ chgrp %{nixbld_group} /nix/store chmod 1775 /nix/store %if ! 0%{?rhel} # Enable and start Nix worker -systemctl enable nix-worker.service -systemctl start nix-worker.service +systemctl enable nix-daemon.service +systemctl start nix-daemon.service %endif %files @@ -186,7 +186,7 @@ systemctl start nix-worker.service %exclude %dir %{perl_vendorarch}/auto/ %{_prefix}/libexec/* %if ! 0%{?rhel} -%{_prefix}/lib/systemd/system/nix-worker.service +%{_prefix}/lib/systemd/system/nix-daemon.service %endif %{_datadir}/emacs/site-lisp/nix-mode.el %{_datadir}/nix diff --git a/src/Makefile.am b/src/Makefile.am index 25ae67996b2f..0ae407c573d1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,3 +1,3 @@ SUBDIRS = boost libutil libstore libmain nix-store nix-hash \ - libexpr nix-instantiate nix-env nix-worker nix-setuid-helper \ + libexpr nix-instantiate nix-env nix-daemon nix-setuid-helper \ nix-log2xml bsdiff-4.3 diff --git a/src/nix-daemon/Makefile.am b/src/nix-daemon/Makefile.am new file mode 100644 index 000000000000..b8e9f4a064ad --- /dev/null +++ b/src/nix-daemon/Makefile.am @@ -0,0 +1,12 @@ +bin_PROGRAMS = nix-daemon + +nix_daemon_SOURCES = nix-daemon.cc +nix_daemon_LDADD = ../libmain/libmain.la ../libstore/libstore.la ../libutil/libutil.la \ + ../boost/format/libformat.la + +AM_CXXFLAGS = \ + -I$(srcdir)/.. -I$(srcdir)/../libutil \ + -I$(srcdir)/../libstore -I$(srcdir)/../libmain + +install-exec-local: + ln -sf nix-daemon $(DESTDIR)$(bindir)/nix-worker diff --git a/src/nix-daemon/nix-daemon.cc b/src/nix-daemon/nix-daemon.cc new file mode 100644 index 000000000000..6256258ec396 --- /dev/null +++ b/src/nix-daemon/nix-daemon.cc @@ -0,0 +1,914 @@ +#include "shared.hh" +#include "local-store.hh" +#include "util.hh" +#include "serialise.hh" +#include "worker-protocol.hh" +#include "archive.hh" +#include "globals.hh" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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. */ +#ifdef O_ASYNC +#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(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: { + Path path = readStorePath(from); + startWork(); + bool result = store->isValidPath(path); + stopWork(); + writeInt(result, to); + break; + } + + case wopQueryValidPaths: { + PathSet paths = readStorePaths(from); + startWork(); + PathSet res = store->queryValidPaths(paths); + stopWork(); + writeStrings(res, to); + break; + } + + case wopHasSubstitutes: { + Path path = readStorePath(from); + startWork(); + PathSet res = store->querySubstitutablePaths(singleton(path)); + stopWork(); + writeInt(res.find(path) != res.end(), to); + break; + } + + case wopQuerySubstitutablePaths: { + PathSet paths = readStorePaths(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 wopQueryDerivationOutputs: { + Path path = readStorePath(from); + startWork(); + PathSet paths; + if (op == wopQueryReferences) + store->queryReferences(path, paths); + else if (op == wopQueryReferrers) + store->queryReferrers(path, paths); + 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(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(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(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(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.tryFallback = readInt(from) != 0; + verbosity = (Verbosity) readInt(from); + settings.maxBuildJobs = readInt(from); + settings.maxSilentTime = 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.buildCores = readInt(from); + if (GET_PROTOCOL_MINOR(clientVersion) >= 10) + settings.useSubstitutes = readInt(from) != 0; + 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); + settings.set("untrusted-" + name, value); + } + } + startWork(); + stopWork(); + break; + } + + case wopQuerySubstitutablePathInfo: { + Path path = absPath(readString(from)); + startWork(); + SubstitutablePathInfos infos; + store->querySubstitutablePathInfos(singleton(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(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(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() +{ + 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); + + 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 = boost::shared_ptr(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(clientVersion, from, to, op); + } catch (Error & e) { + /* If we're not in a state were 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; + } + + 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.nixStateDir + DEFAULT_SOCKET_PATH; + + 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; + +#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; + } +#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(); + + } catch (std::exception & e) { + std::cerr << format("child error: %1%\n") % e.what(); + } + exit(0); + } + + } catch (Interrupted & e) { + throw; + } catch (Error & e) { + printMsg(lvlError, format("error processing connection: %1%") % e.msg()); + } + } +} + + +void run(Strings args) +{ + bool daemon = false; + + 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"; diff --git a/src/nix-worker/Makefile.am b/src/nix-worker/Makefile.am deleted file mode 100644 index 6b1b2827cbf7..000000000000 --- a/src/nix-worker/Makefile.am +++ /dev/null @@ -1,9 +0,0 @@ -bin_PROGRAMS = nix-worker - -nix_worker_SOURCES = nix-worker.cc -nix_worker_LDADD = ../libmain/libmain.la ../libstore/libstore.la ../libutil/libutil.la \ - ../boost/format/libformat.la - -AM_CXXFLAGS = \ - -I$(srcdir)/.. -I$(srcdir)/../libutil \ - -I$(srcdir)/../libstore -I$(srcdir)/../libmain diff --git a/src/nix-worker/nix-worker.cc b/src/nix-worker/nix-worker.cc deleted file mode 100644 index 833fc3518415..000000000000 --- a/src/nix-worker/nix-worker.cc +++ /dev/null @@ -1,914 +0,0 @@ -#include "shared.hh" -#include "local-store.hh" -#include "util.hh" -#include "serialise.hh" -#include "worker-protocol.hh" -#include "archive.hh" -#include "globals.hh" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 worker will never notice - that the client has disconnected until the builder terminates. */ -#ifdef O_ASYNC -#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(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: { - Path path = readStorePath(from); - startWork(); - bool result = store->isValidPath(path); - stopWork(); - writeInt(result, to); - break; - } - - case wopQueryValidPaths: { - PathSet paths = readStorePaths(from); - startWork(); - PathSet res = store->queryValidPaths(paths); - stopWork(); - writeStrings(res, to); - break; - } - - case wopHasSubstitutes: { - Path path = readStorePath(from); - startWork(); - PathSet res = store->querySubstitutablePaths(singleton(path)); - stopWork(); - writeInt(res.find(path) != res.end(), to); - break; - } - - case wopQuerySubstitutablePaths: { - PathSet paths = readStorePaths(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 wopQueryDerivationOutputs: { - Path path = readStorePath(from); - startWork(); - PathSet paths; - if (op == wopQueryReferences) - store->queryReferences(path, paths); - else if (op == wopQueryReferrers) - store->queryReferrers(path, paths); - 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(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(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(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(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.tryFallback = readInt(from) != 0; - verbosity = (Verbosity) readInt(from); - settings.maxBuildJobs = readInt(from); - settings.maxSilentTime = 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.buildCores = readInt(from); - if (GET_PROTOCOL_MINOR(clientVersion) >= 10) - settings.useSubstitutes = readInt(from) != 0; - 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); - settings.set("untrusted-" + name, value); - } - } - startWork(); - stopWork(); - break; - } - - case wopQuerySubstitutablePathInfo: { - Path path = absPath(readString(from)); - startWork(); - SubstitutablePathInfos infos; - store->querySubstitutablePathInfos(singleton(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(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(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() -{ - 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); - - 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-worker' as root, then you MUST set `build-users-group'!"); -#endif - - /* Open the store. */ - store = boost::shared_ptr(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(clientVersion, from, to, op); - } catch (Error & e) { - /* If we're not in a state were 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; - } - - to.flush(); - - assert(!canSendStderr); - }; - - printMsg(lvlError, format("%1% worker 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.nixStateDir + DEFAULT_SOCKET_PATH; - - 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; - -#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; - } -#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 worker. */ - 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(); - - } catch (std::exception & e) { - std::cerr << format("child error: %1%\n") % e.what(); - } - exit(0); - } - - } catch (Interrupted & e) { - throw; - } catch (Error & e) { - printMsg(lvlError, format("error processing connection: %1%") % e.msg()); - } - } -} - - -void run(Strings args) -{ - bool daemon = false; - - 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-worker"); -} - - -string programId = "nix-worker"; diff --git a/tests/common.sh.in b/tests/common.sh.in index ee0ddfbb69e6..f327ad727632 100644 --- a/tests/common.sh.in +++ b/tests/common.sh.in @@ -59,9 +59,9 @@ clearManifests() { startDaemon() { # Start the daemon, wait for the socket to appear. !!! - # ‘nix-worker’ should have an option to fork into the background. + # ‘nix-daemon’ should have an option to fork into the background. rm -f $NIX_STATE_DIR/daemon-socket/socket - nix-worker --daemon & + nix-daemon & for ((i = 0; i < 30; i++)); do if [ -e $NIX_STATE_DIR/daemon-socket/socket ]; then break; fi sleep 1 -- cgit 1.4.1