diff --git a/src/nix/daemon.cc b/src/nix/daemon.cc index c1a91c63d8c..36da109ffe7 100644 --- a/src/nix/daemon.cc +++ b/src/nix/daemon.cc @@ -17,17 +17,19 @@ #include #include -#include +#include +#include +#include +#include +#include +#include #include -#include -#include -#include #include +#include +#include #include -#include -#include -#include -#include +#include +#include #if __APPLE__ || __FreeBSD__ #include @@ -180,29 +182,28 @@ static bool matchUser(const std::string & user, const std::string & group, const struct PeerInfo { - bool pidKnown; - pid_t pid; - bool uidKnown; - uid_t uid; - bool gidKnown; - gid_t gid; + std::optional pid; + std::optional uid; + std::optional gid; }; /** * Get the identity of the caller, if possible. */ -static PeerInfo getPeerInfo(int remote) +static PeerInfo getPeerInfo(int fd) { - PeerInfo peer = { false, 0, false, 0, false, 0 }; + PeerInfo peer; #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"); - peer = { true, cred.pid, true, cred.uid, true, cred.gid }; + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &credLen) == 0) { + peer.pid = cred.pid; + peer.uid = cred.uid; + peer.gid = cred.gid; + } #elif defined(LOCAL_PEERCRED) @@ -212,9 +213,8 @@ static PeerInfo getPeerInfo(int remote) xucred cred; socklen_t credLen = sizeof(cred); - if (getsockopt(remote, SOL_LOCAL, LOCAL_PEERCRED, &cred, &credLen) == -1) - throw SysError("getting peer credentials"); - peer = { false, 0, true, cred.cr_uid, false, 0 }; + if (getsockopt(fd, SOL_LOCAL, LOCAL_PEERCRED, &cred, &credLen) == 0) + peer.uid = cred.cr_uid; #endif @@ -251,11 +251,17 @@ static std::pair authPeer(const PeerInfo & peer) { TrustedFlag trusted = NotTrusted; - struct passwd * pw = peer.uidKnown ? getpwuid(peer.uid) : 0; - std::string user = pw ? pw->pw_name : std::to_string(peer.uid); + std::string user, group; + + if (peer.uid) { + auto pw = getpwuid(*peer.uid); + user = pw ? pw->pw_name : std::to_string(*peer.uid); + } - struct group * gr = peer.gidKnown ? getgrgid(peer.gid) : 0; - std::string group = gr ? gr->gr_name : std::to_string(peer.gid); + if (peer.gid) { + auto gr = getgrgid(*peer.gid); + group = gr ? gr->gr_name : std::to_string(*peer.gid); + } const Strings & trustedUsers = authorizationSettings.trustedUsers; const Strings & allowedUsers = authorizationSettings.allowedUsers; @@ -283,23 +289,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; - closeOnExec(fdSocket.get()); + auto count = string2Int(*listenFds); + assert(count); + for (auto i = 0; i < count; ++i) { + AutoCloseFD fdSocket(SD_LISTEN_FDS_START + i); + 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); @@ -307,66 +322,78 @@ 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("poll"); } - closeOnExec(remote.get()); - - PeerInfo peer { .pidKnown = false }; - TrustedFlag trusted; - std::string user; - - if (forceTrustClientOpt) - trusted = *forceTrustClientOpt; - else { - peer = getPeerInfo(remote.get()); - auto [_trusted, _user] = authPeer(peer); - trusted = _trusted; - user = _user; - }; - - printInfo((std::string) "accepted connection from pid %1%, user %2%" + (trusted ? " (trusted)" : ""), - peer.pidKnown ? std::to_string(peer.pid) : "", - peer.uidKnown ? user : ""); - - // 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; - - // Background the daemon. - if (setsid() == -1) - throw SysError("creating a new session"); - - // Restore normal handling of SIGCHLD. - setSigChldAction(false); - - // For debugging, stuff the pid into argv[1]. - if (peer.pidKnown && savedArgv[1]) { - auto processName = std::to_string(peer.pid); - strncpy(savedArgv[1], processName.c_str(), strlen(savedArgv[1])); + for (auto & fd : fds) { + if (!fd.revents) continue; + + // Accept a connection. + AutoCloseFD remote = accept(fd.fd, nullptr, nullptr); + checkInterrupt(); + if (!remote) { + if (errno == EINTR) continue; + throw SysError("accepting connection"); } - // Handle the connection. - FdSource from(remote.get()); - FdSink to(remote.get()); - processConnection(openUncachedStore(), from, to, trusted, NotRecursive); + closeOnExec(remote.get()); + + PeerInfo peer; + TrustedFlag trusted; + std::string user; + + if (forceTrustClientOpt) + trusted = *forceTrustClientOpt; + else { + peer = getPeerInfo(remote.get()); + auto [_trusted, _user] = authPeer(peer); + trusted = _trusted; + user = _user; + }; + + printInfo( + "accepted connection from %s%s", + peer.pid && peer.uid + ? fmt("pid %s, user %s", std::to_string(*peer.pid), user) + : "", + trusted ? " (trusted)" : ""); + + // 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(); + + // Background the daemon. + if (setsid() == -1) + throw SysError("creating a new session"); + + // Restore normal handling of SIGCHLD. + setSigChldAction(false); + + // For debugging, stuff the pid into argv[1]. + if (peer.pid && savedArgv[1]) { + std::string processName = std::to_string(*peer.pid); + strncpy(savedArgv[1], processName.c_str(), strlen(savedArgv[1])); + } + + // Handle the connection. + FdSource from(remote.get()); + FdSink to(remote.get()); + processConnection(openUncachedStore(), from, to, trusted, NotRecursive); + + exit(0); + }, options); - exit(0); - }, options); + } } catch (Interrupted & e) { return; @@ -497,6 +524,17 @@ static RegisterLegacyCommand r_nix_daemon("nix-daemon", main_nix_daemon); struct CmdDaemon : StoreCommand { + bool stdio = false; + + CmdDaemon() + { + addFlag({ + .longName = "stdio", + .description = "Handle a single connection on stdin/stdout.", + .handler = {&stdio, true}, + }); + } + std::string description() override { return "daemon to perform store operations on behalf of non-root clients"; @@ -513,7 +551,7 @@ struct CmdDaemon : StoreCommand void run(ref store) override { - runDaemon(false, std::nullopt); + runDaemon(stdio, std::nullopt); } }; diff --git a/src/nix/daemon.md b/src/nix/daemon.md index d5cdadf08e1..99ec7a73d81 100644 --- a/src/nix/daemon.md +++ b/src/nix/daemon.md @@ -18,4 +18,18 @@ management framework such as `systemd`. 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/daemon-socket/socket +ListenStream=/nix/var/nix/daemon-socket/socket-2 +``` + )""