12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058 |
- #include "config.h"
- #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 "builtins.hh"
- #include <algorithm>
- #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 <arpa/inet.h>
- #include <netinet/in.h>
- #include <netinet/tcp.h>
- #include <fcntl.h>
- #include <errno.h>
- #include <pwd.h>
- #include <grp.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;
- /* This variable is used to keep track of whether a connection
- comes from a host other than the host running guix-daemon. */
- static bool isRemoteConnection;
- /* 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)
- {
- if (canSendStderr) {
- 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 : BufferedSink
- {
- Sink & to;
- TunnelSink(Sink & to) : BufferedSink(64 * 1024), to(to) { }
- virtual void write(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) {
- 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);
- try {
- store->exportPath(path, sign, sink);
- }
- catch (Error &e) {
- /* Flush SINK beforehand or its destructor will rightfully trigger
- an assertion failure. */
- sink.flush();
- throw e;
- }
- sink.flush();
- stopWork();
- writeInt(1, to);
- break;
- }
- case wopImportPaths: {
- startWork();
- TunnelSource source(from);
- /* Unlike Nix, always require a signature, even for "trusted"
- users. */
- Paths paths = store->importPaths(true, source);
- stopWork();
- writeStrings(paths, to);
- break;
- }
- case wopBuildPaths: {
- PathSet drvs = readStorePaths<PathSet>(from);
- BuildMode mode = bmNormal;
- if (GET_PROTOCOL_MINOR(clientVersion) >= 15) {
- mode = (BuildMode)readInt(from);
- /* Repairing is not atomic, so disallowed for "untrusted"
- clients. */
- if (mode == bmRepair && !trusted)
- throw Error("repairing is a privileged operation");
- }
- startWork();
- store->buildPaths(drvs, mode);
- 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: {
- if (isRemoteConnection) {
- throw Error("Garbage collection is disabled for remote hosts.");
- break;
- }
- 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;
- if (isRemoteConnection)
- /* When the client is remote, don't keep the failed build tree as
- it is presumably inaccessible to the client and could fill up
- our disk. */
- settings.keepFailed = 0;
- settings.keepGoing = readInt(from) != 0;
- settings.set("build-fallback", readInt(from) ? "true" : "false");
- verbosity = (Verbosity) readInt(from);
- if (GET_PROTOCOL_MINOR(clientVersion) < 0x61) {
- settings.set("build-max-jobs", std::to_string(readInt(from)));
- settings.set("build-max-silent-time", std::to_string(readInt(from)));
- }
- if (GET_PROTOCOL_MINOR(clientVersion) >= 2) {
- #ifdef HAVE_DAEMON_OFFLOAD_HOOK
- settings.useBuildHook = readInt(from) != 0;
- #else
- readInt(from); // ignore the user's setting
- #endif
- }
- 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
- && GET_PROTOCOL_MINOR(clientVersion) < 0x61)
- settings.set("build-cores", std::to_string(readInt(from)));
- if (GET_PROTOCOL_MINOR(clientVersion) >= 10) {
- if (settings.useSubstitutes)
- settings.set("build-use-substitutes", readInt(from) ? "true" : "false");
- else
- readInt(from); // substitutes remain disabled
- }
- 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 == "build-max-silent-time"
- || name == "build-max-jobs" || name == "build-cores"
- || name == "build-repeat"
- || name == "multiplexed-build-output")
- settings.set(name, value);
- else if (name == "user-name"
- && settings.clientUid == (uid_t) -1) {
- /* Create the user profile. This is necessary if
- clientUid = -1, for instance because the client
- connected over TCP. */
- struct passwd *pw = getpwnam(value.c_str());
- if (pw != NULL)
- store->createUser(value, pw->pw_uid);
- else
- printMsg(lvlInfo, format("user name %1% not found") % 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;
- }
- case wopOptimiseStore:
- startWork();
- store->optimiseStore();
- stopWork();
- writeInt(1, to);
- break;
- case wopVerifyStore: {
- bool checkContents = readInt(from) != 0;
- bool repair = readInt(from) != 0;
- startWork();
- if (repair && !trusted)
- throw Error("you are not privileged to repair paths");
- bool errors = store->verifyStore(checkContents, repair);
- stopWork();
- writeInt(errors, to);
- break;
- }
- case wopBuiltinBuilders: {
- startWork();
- auto names = builtinBuilderNames();
- stopWork();
- writeStrings(names, to);
- break;
- }
- default:
- throw Error(format("invalid operation %1%") % op);
- }
- }
- static void processConnection(bool trusted, uid_t userId)
- {
- canSendStderr = false;
- _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));
- if (userId != (uid_t) -1) {
- /* Create the user profile. */
- struct passwd *pw = getpwuid(userId);
- if (pw != NULL && pw->pw_name != NULL)
- store->createUser(pw->pw_name, userId);
- else
- printMsg(lvlInfo, format("user with UID %1% not found") % userId);
- }
- stopWork();
- to.flush();
- } catch (Error & e) {
- stopWork(false, e.msg(), GET_PROTOCOL_MINOR(clientVersion) >= 8 ? 1 : 0);
- 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;
- stopWork(false, e.msg(), GET_PROTOCOL_MINOR(clientVersion) >= 8 ? e.status : 0);
- if (!errorAllowed) throw;
- } catch (std::bad_alloc & e) {
- stopWork(false, "build daemon out of memory", GET_PROTOCOL_MINOR(clientVersion) >= 8 ? 1 : 0);
- throw;
- }
- to.flush();
- assert(!canSendStderr);
- };
- canSendStderr = false;
- _isInterrupted = false;
- printMsg(lvlDebug, 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");
- }
- /* Accept a connection on FDSOCKET and fork a server process to process the
- new connection. */
- static void acceptConnection(int fdSocket)
- {
- uid_t clientUid = (uid_t) -1;
- gid_t clientGid = (gid_t) -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_storage remoteAddr;
- socklen_t remoteAddrLen = sizeof(remoteAddr);
- try_again:
- AutoCloseFD remote = accept(fdSocket,
- (struct sockaddr *) &remoteAddr, &remoteAddrLen);
- checkInterrupt();
- if (remote == -1) {
- if (errno == EINTR)
- goto try_again;
- else
- throw SysError("accepting connection");
- }
- closeOnExec(remote);
- {
- int enabled = 1;
- /* If we're on a TCP connection, disable Nagle's algorithm so that
- data is sent as soon as possible. */
- (void) setsockopt(remote, SOL_TCP, TCP_NODELAY,
- &enabled, sizeof enabled);
- #if defined(TCP_QUICKACK)
- /* Enable TCP quick-ack if applicable; this might help a little. */
- (void) setsockopt(remote, SOL_TCP, TCP_QUICKACK,
- &enabled, sizeof enabled);
- #endif
- }
- pid_t clientPid = -1;
- bool trusted = false;
- /* Get the identity of the caller, if possible. */
- if (remoteAddr.ss_family == AF_UNIX) {
- #if defined(SO_PEERCRED)
- ucred cred;
- socklen_t credLen = sizeof(cred);
- if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED,
- &cred, &credLen) == -1)
- throw SysError("getting peer credentials");
- clientPid = cred.pid;
- clientUid = cred.uid;
- clientGid = cred.gid;
- trusted = clientUid == 0;
- struct passwd * pw = getpwuid(cred.uid);
- string user = pw ? pw->pw_name : std::to_string(cred.uid);
- printMsg(lvlInfo,
- format((string) "accepted connection from pid %1%, user %2%")
- % clientPid % user);
- #endif
- } else {
- char address_str[128];
- const char *result;
- if (remoteAddr.ss_family == AF_INET) {
- struct sockaddr_in *addr = (struct sockaddr_in *) &remoteAddr;
- result = inet_ntop(AF_INET, &addr->sin_addr,
- address_str, sizeof address_str);
- } else if (remoteAddr.ss_family == AF_INET6) {
- struct sockaddr_in6 *addr = (struct sockaddr_in6 *) &remoteAddr;
- result = inet_ntop(AF_INET6, &addr->sin6_addr,
- address_str, sizeof address_str);
- } else {
- result = NULL;
- }
- if (result != NULL) {
- printMsg(lvlInfo,
- format("accepted connection from %1%")
- % address_str);
- }
- }
- /* Fork a child to handle the connection. */
- startProcess([&]() {
- close(fdSocket);
- /* 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 = std::to_string(clientPid);
- strncpy(argvSaved[1], processName.c_str(), strlen(argvSaved[1]));
- }
- /* Store the client's user and group for this connection. This
- has to be done in the forked process since it is per
- connection. Setting these to -1 means: do not change. */
- settings.clientUid = clientUid;
- settings.clientGid = clientGid;
- isRemoteConnection = (remoteAddr.ss_family != AF_UNIX);
- /* Handle the connection. */
- from.fd = remote;
- to.fd = remote;
- processConnection(trusted, clientUid);
- exit(0);
- }, false, "unexpected build daemon error: ", true);
- } catch (Interrupted & e) {
- throw;
- } catch (Error & e) {
- printMsg(lvlError, format("error processing connection: %1%") % e.msg());
- }
- }
- static void daemonLoop(const std::vector<int>& sockets)
- {
- if (chdir("/") == -1)
- throw SysError("cannot change current directory");
- /* Get rid of children automatically; don't let them become
- zombies. */
- setSigChldAction(true);
- /* Mark sockets as close-on-exec. */
- for(int fd: sockets) {
- closeOnExec(fd);
- }
- /* Prepare the FD set corresponding to SOCKETS. */
- auto initializeFDSet = [&](fd_set *set) {
- FD_ZERO(set);
- for (int fd: sockets) {
- FD_SET(fd, set);
- }
- };
- /* Loop accepting connections. */
- while (1) {
- fd_set readfds;
- initializeFDSet(&readfds);
- int count =
- select(*std::max_element(sockets.begin(), sockets.end()) + 1,
- &readfds, NULL, NULL,
- NULL);
- if (count < 0) {
- int err = errno;
- if (err == EINTR)
- continue;
- throw SysError(format("select error: %1%") % strerror(err));
- }
- for (unsigned int i = 0; i < sockets.size(); i++) {
- if (FD_ISSET(sockets[i], &readfds)) {
- acceptConnection(sockets[i]);
- }
- }
- }
- }
- void run(const std::vector<int>& sockets)
- {
- daemonLoop(sockets);
- }
- void printHelp()
- {
- showManPage("nix-daemon");
- }
- string programId = "nix-daemon";
|