gnu: kdenlive: Add ffmpeg to input list
[jackhill/guix/guix.git] / nix / nix-daemon / nix-daemon.cc
index 9b29b3e..3dd156b 100644 (file)
@@ -1,3 +1,4 @@
+#include "config.h"
 #include "shared.hh"
 #include "local-store.hh"
 #include "util.hh"
 #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>
@@ -50,7 +55,9 @@ 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.,
@@ -203,11 +210,11 @@ static void stopWork(bool success = true, const string & msg = "", unsigned int
 }
 
 
-struct TunnelSink : Sink
+struct TunnelSink : BufferedSink
 {
     Sink & to;
-    TunnelSink(Sink & to) : to(to) { }
-    virtual void operator () (const unsigned char * data, size_t len)
+    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);
@@ -432,7 +439,16 @@ static void performOp(bool trusted, unsigned int clientVersion,
         bool sign = readInt(from) == 1;
         startWork();
         TunnelSink sink(to);
-        store->exportPath(path, sign, sink);
+       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;
@@ -459,7 +475,7 @@ static void performOp(bool trusted, unsigned int clientVersion,
            /* Repairing is not atomic, so disallowed for "untrusted"
               clients.  */
             if (mode == bmRepair && !trusted)
-                throw Error("repairing is not supported when building through the Nix daemon");
+                throw Error("repairing is a privileged operation");
         }
         startWork();
         store->buildPaths(drvs, mode);
@@ -516,6 +532,11 @@ static void performOp(bool trusted, unsigned int clientVersion,
     }
 
     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);
@@ -545,29 +566,64 @@ static void performOp(bool trusted, unsigned int clientVersion,
 
     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);
-        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)
+
+        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)
+        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)
-            settings.set("build-use-substitutes", readInt(from) ? "true" : "false");
+        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-repeat" || name == "use-ssh-substituter")
+                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);
             }
@@ -686,7 +742,7 @@ static void performOp(bool trusted, unsigned int clientVersion,
 }
 
 
-static void processConnection(bool trusted)
+static void processConnection(bool trusted, uid_t userId)
 {
     canSendStderr = false;
     _writeToStderr = tunnelStderr;
@@ -733,6 +789,15 @@ static void processConnection(bool trusted)
         /* 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();
 
@@ -767,7 +832,7 @@ static void processConnection(bool trusted)
             stopWork(false, e.msg(), GET_PROTOCOL_MINOR(clientVersion) >= 8 ? e.status : 0);
             if (!errorAllowed) throw;
         } catch (std::bad_alloc & e) {
-            stopWork(false, "Nix daemon out of memory", GET_PROTOCOL_MINOR(clientVersion) >= 8 ? 1 : 0);
+            stopWork(false, "build daemon out of memory", GET_PROTOCOL_MINOR(clientVersion) >= 8 ? 1 : 0);
             throw;
         }
 
@@ -800,151 +865,100 @@ static void setSigChldAction(bool autoReap)
 }
 
 
-bool matchUser(const string & user, const string & group, const Strings & users)
-{
-    if (find(users.begin(), users.end(), "*") != users.end())
-        return true;
-
-    if (find(users.begin(), users.end(), user) != users.end())
-        return true;
-
-    for (auto & i : users)
-        if (string(i, 0, 1) == "@") {
-            if (group == string(i, 1)) return true;
-            struct group * gr = getgrnam(i.c_str() + 1);
-            if (!gr) continue;
-            for (char * * mem = gr->gr_mem; *mem; mem++)
-                if (user == string(*mem)) return true;
-        }
-
-    return false;
-}
-
-
-#define SD_LISTEN_FDS_START 3
-
-
-static void daemonLoop()
+/* Accept a connection on FDSOCKET and fork a server process to process the
+   new connection.  */
+static void acceptConnection(int fdSocket)
 {
-    if (chdir("/") == -1)
-        throw SysError("cannot change current directory");
-
-    /* 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") != std::to_string(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. */
-        if (chdir(dirOf(socketPath).c_str()) == -1)
-            throw SysError("cannot change current directory");
-        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);
-
-        if (chdir("/") == -1) /* back to the root */
-            throw SysError("cannot change current directory");
+    uid_t clientUid = (uid_t) -1;
+    gid_t clientGid = (gid_t) -1;
 
-        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);
+    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
+       }
 
-            bool trusted = false;
-            pid_t clientPid = -1;
+       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)
-            /* Get the identity of the caller, if possible. */
-            ucred cred;
-            socklen_t credLen = sizeof(cred);
-            if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED, &cred, &credLen) == -1)
-                throw SysError("getting peer credentials");
+           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;
+           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);
 
-            struct group * gr = getgrgid(cred.gid);
-            string group = gr ? gr->gr_name : std::to_string(cred.gid);
-
-            Strings trustedUsers = settings.get("trusted-users", Strings({"root"}));
-            Strings allowedUsers = settings.get("allowed-users", Strings({"*"}));
-
-            if (matchUser(user, group, trustedUsers))
-                trusted = true;
-
-            if (!trusted && !matchUser(user, group, allowedUsers))
-                throw Error(format("user `%1%' is not allowed to connect to the Nix daemon") % user);
-
-            printMsg(lvlInfo, format((string) "accepted connection from pid %1%, user %2%"
-                    + (trusted ? " (trusted)" : "")) % clientPid % user);
+           printMsg(lvlInfo,
+                    format((string) "accepted connection from pid %1%, user %2%")
+                    % clientPid % user);
 #endif
-
-            /* Fork a child to handle the connection. */
-            startProcess([&]() {
-                fdSocket.close();
+       } 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)
@@ -959,31 +973,78 @@ static void daemonLoop()
                     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);
+                processConnection(trusted, clientUid);
 
                 exit(0);
-            }, false, "unexpected Nix daemon error: ", true);
+            }, false, "unexpected build daemon error: ", true);
 
-        } catch (Interrupted & e) {
-            throw;
-        } catch (Error & e) {
-            printMsg(lvlError, format("error processing connection: %1%") % e.msg());
-        }
+    } catch (Interrupted & e) {
+       throw;
+    } catch (Error & e) {
+       printMsg(lvlError, format("error processing connection: %1%") % e.msg());
     }
 }
 
-
-void run(Strings args)
+static void daemonLoop(const std::vector<int>& sockets)
 {
-    for (Strings::iterator i = args.begin(); i != args.end(); ) {
-        string arg = *i++;
-        if (arg == "--daemon") /* ignored for backwards compatibility */;
+    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);
     }
 
-    daemonLoop();
+    /* 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);
 }