diff --git a/src/nix/unix/daemon.cc b/src/nix/unix/daemon.cc index 74f14a7a0..f45079e04 100644 --- a/src/nix/unix/daemon.cc +++ b/src/nix/unix/daemon.cc @@ -34,6 +34,8 @@ #include #include #include +#include +#include #ifdef __linux__ # include "nix/util/cgroup.hh" @@ -207,7 +209,7 @@ struct PeerInfo /** * Get the identity of the caller, if possible. */ -static PeerInfo getPeerInfo(int remote) +static PeerInfo getPeerInfo(Descriptor remote) { PeerInfo peer; @@ -304,23 +306,32 @@ static void daemonLoop(std::optional forceTrustClientOpt) if (chdir("/") == -1) throw SysError("cannot change current directory"); - AutoCloseFD fdSocket; + std::vector listeningSockets; // Handle socket-based activation by systemd. auto listenFds = getEnv("LISTEN_FDS"); if (listenFds) { - if (getEnv("LISTEN_PID") != std::to_string(getpid()) || listenFds != "1") + if (getEnv("LISTEN_PID") != std::to_string(getpid())) throw Error("unexpected systemd environment variables"); - fdSocket = SD_LISTEN_FDS_START; - unix::closeOnExec(fdSocket.get()); + auto count = string2Int(*listenFds); + assert(count); + for (auto i = 0; i < count; ++i) { + AutoCloseFD fdSocket(SD_LISTEN_FDS_START + i); + unix::closeOnExec(fdSocket.get()); + listeningSockets.push_back(std::move(fdSocket)); + } } // Otherwise, create and bind to a Unix domain socket. else { createDirs(dirOf(settings.nixDaemonSocketFile)); - fdSocket = createUnixDomainSocket(settings.nixDaemonSocketFile, 0666); + listeningSockets.push_back(createUnixDomainSocket(settings.nixDaemonSocketFile, 0666)); } + std::vector fds; + for (auto & i : listeningSockets) + fds.push_back({.fd = i.get(), .events = POLLIN}); + // Get rid of children automatically; don't let them become zombies. setSigChldAction(true); @@ -349,68 +360,82 @@ static void daemonLoop(std::optional forceTrustClientOpt) while (1) { try { - // Accept a connection. - struct sockaddr_un remoteAddr; - socklen_t remoteAddrLen = sizeof(remoteAddr); - - AutoCloseFD remote = accept(fdSocket.get(), (struct sockaddr *) &remoteAddr, &remoteAddrLen); checkInterrupt(); - if (!remote) { + + auto count = poll(fds.data(), fds.size(), -1); + if (count == -1) { if (errno == EINTR) continue; - throw SysError("accepting connection"); + throw SysError("polling for incomming connections"); } - unix::closeOnExec(remote.get()); + for (auto & fd : fds) { + if (!fd.revents) + continue; - PeerInfo peer; - TrustedFlag trusted; - std::optional userName; + // Accept a connection. + struct sockaddr_un remoteAddr; + socklen_t remoteAddrLen = sizeof(remoteAddr); - if (forceTrustClientOpt) - trusted = *forceTrustClientOpt; - else { - peer = getPeerInfo(remote.get()); - auto [_trusted, _userName] = authPeer(peer); - trusted = _trusted; - userName = _userName; - }; + AutoCloseFD remote = accept(fd.fd, (struct sockaddr *) &remoteAddr, &remoteAddrLen); + checkInterrupt(); + if (!remote) { + if (errno == EINTR) + continue; + throw SysError("accepting connection"); + } - printInfo( - (std::string) "accepted connection from pid %1%, user %2%" + (trusted ? " (trusted)" : ""), - peer.pid ? std::to_string(*peer.pid) : "", - userName.value_or("")); + unix::closeOnExec(remote.get()); - // Fork a child to handle the connection. - ProcessOptions options; - options.errorPrefix = "unexpected Nix daemon error: "; - options.dieWithParent = false; - options.runExitHandlers = true; - options.allowVfork = false; - startProcess( - [&]() { - fdSocket = -1; + PeerInfo peer; + TrustedFlag trusted; + std::optional userName; - // Background the daemon. - if (setsid() == -1) - throw SysError("creating a new session"); + if (forceTrustClientOpt) + trusted = *forceTrustClientOpt; + else { + peer = getPeerInfo(remote.get()); + auto [_trusted, _userName] = authPeer(peer); + trusted = _trusted; + userName = _userName; + }; - // Restore normal handling of SIGCHLD. - setSigChldAction(false); + printInfo( + (std::string) "accepted connection from pid %1%, user %2%" + (trusted ? " (trusted)" : ""), + peer.pid ? std::to_string(*peer.pid) : "", + userName.value_or("")); - // For debugging, stuff the pid into argv[1]. - if (peer.pid && savedArgv[1]) { - auto processName = std::to_string(*peer.pid); - strncpy(savedArgv[1], processName.c_str(), strlen(savedArgv[1])); - } + // Fork a child to handle the connection. + ProcessOptions options; + options.errorPrefix = "unexpected Nix daemon error: "; + options.dieWithParent = false; + options.runExitHandlers = true; + options.allowVfork = false; + startProcess( + [&]() { + listeningSockets.clear(); - // Handle the connection. - processConnection( - openUncachedStore(), FdSource(remote.get()), FdSink(remote.get()), trusted, NotRecursive); + // Background the daemon. + if (setsid() == -1) + throw SysError("creating a new session"); - exit(0); - }, - options); + // Restore normal handling of SIGCHLD. + setSigChldAction(false); + + // For debugging, stuff the pid into argv[1]. + if (peer.pid && savedArgv[1]) { + auto processName = std::to_string(*peer.pid); + strncpy(savedArgv[1], processName.c_str(), strlen(savedArgv[1])); + } + + // Handle the connection. + processConnection( + openUncachedStore(), FdSource(remote.get()), FdSink(remote.get()), trusted, NotRecursive); + + exit(0); + }, + options); + } } catch (Interrupted & e) { return; @@ -561,7 +586,7 @@ struct CmdDaemon : Command { addFlag({ .longName = "stdio", - .description = "Attach to standard I/O, instead of trying to bind to a UNIX socket.", + .description = "Attach to standard I/O, instead of using UNIX socket(s).", .handler = {&stdio, true}, }); diff --git a/src/nix/unix/daemon.md b/src/nix/unix/daemon.md index b1ea850ed..bd7b8bec7 100644 --- a/src/nix/unix/daemon.md +++ b/src/nix/unix/daemon.md @@ -42,4 +42,18 @@ management framework such as `systemd` on Linux, or `launchctl` on Darwin. Note that this daemon does not fork into the background. +# Systemd socket activation + +`nix daemon` supports systemd socket-based activation using the +`nix-daemon.socket` unit in the Nix distribution. It supports +listening on multiple addresses; for example, the following stanza in +`nix-daemon.socket` makes the daemon listen on two Unix domain +sockets: + +``` +[Socket] +ListenStream=/nix/var/nix/unix/daemon-socket/socket +ListenStream=/nix/var/nix/unix/daemon-socket/socket-2 +``` + )""