diff --git a/src/libcmd/include/nix/cmd/unix-socket-server.hh b/src/libcmd/include/nix/cmd/unix-socket-server.hh index c1aed41f94c4..3954aeb0fdff 100644 --- a/src/libcmd/include/nix/cmd/unix-socket-server.hh +++ b/src/libcmd/include/nix/cmd/unix-socket-server.hh @@ -6,9 +6,15 @@ #include #include #include -#include -namespace nix::unix { +#ifndef _WIN32 +# include +#endif + +namespace nix { + +#ifndef _WIN32 +namespace unix { /** * Information about the identity of the peer on a Unix domain socket connection. @@ -25,6 +31,9 @@ struct PeerInfo */ PeerInfo getPeerInfo(Descriptor remote); +} // namespace unix +#endif + /** * Callback type for handling new connections. * @@ -76,4 +85,4 @@ struct ServeUnixSocketOptions */ [[noreturn]] void serveUnixSocket(const ServeUnixSocketOptions & options, UnixSocketHandler handler); -} // namespace nix::unix +} // namespace nix diff --git a/src/libcmd/meson.build b/src/libcmd/meson.build index ced5f0d9773d..e6dafd12e1c7 100644 --- a/src/libcmd/meson.build +++ b/src/libcmd/meson.build @@ -85,19 +85,18 @@ sources = files( 'network-proxy.cc', 'repl-interacter.cc', 'repl.cc', + 'unix-socket-server.cc', ) -if host_machine.system() != 'windows' - sources += files( - 'unix/unix-socket-server.cc', - ) -endif - subdir('include/nix/cmd') subdir('nix-meson-build-support/export-all-symbols') subdir('nix-meson-build-support/windows-version') +if host_machine.system() == 'windows' + deps_other += cxx.find_library('ws2_32') +endif + this_library = library( 'nixcmd', sources, diff --git a/src/libcmd/unix/unix-socket-server.cc b/src/libcmd/unix-socket-server.cc similarity index 73% rename from src/libcmd/unix/unix-socket-server.cc rename to src/libcmd/unix-socket-server.cc index 8cc2bd713d07..a8eec57bd685 100644 --- a/src/libcmd/unix/unix-socket-server.cc +++ b/src/libcmd/unix-socket-server.cc @@ -9,27 +9,37 @@ #include "nix/util/unix-domain-socket.hh" #include "nix/util/util.hh" -#include -#include -#include +#include + +#ifdef _WIN32 +# include +# include +#else +# include +# include +# include +#endif #if defined(__APPLE__) || defined(__FreeBSD__) # include #endif -namespace nix::unix { +namespace nix { + +#ifndef _WIN32 +namespace unix { PeerInfo getPeerInfo(Descriptor remote) { PeerInfo peer; -#if defined(SO_PEERCRED) +# if defined(SO_PEERCRED) -# if defined(__OpenBSD__) +# if defined(__OpenBSD__) struct sockpeercred cred; -# else +# else ucred cred; -# endif +# endif socklen_t credLen = sizeof(cred); if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED, &cred, &credLen) == 0) { peer.pid = cred.pid; @@ -37,26 +47,30 @@ PeerInfo getPeerInfo(Descriptor remote) peer.gid = cred.gid; } -#elif defined(LOCAL_PEERCRED) +# elif defined(LOCAL_PEERCRED) -# if !defined(SOL_LOCAL) -# define SOL_LOCAL 0 -# endif +# if !defined(SOL_LOCAL) +# define SOL_LOCAL 0 +# endif xucred cred; socklen_t credLen = sizeof(cred); if (getsockopt(remote, SOL_LOCAL, LOCAL_PEERCRED, &cred, &credLen) == 0) peer.uid = cred.cr_uid; -#endif +# endif return peer; } +} // namespace unix +#endif + [[noreturn]] void serveUnixSocket(const ServeUnixSocketOptions & options, UnixSocketHandler handler) { std::vector listeningSockets; +#ifndef _WIN32 static constexpr int SD_LISTEN_FDS_START = 3; // Handle socket-based activation by systemd. @@ -68,26 +82,32 @@ PeerInfo getPeerInfo(Descriptor remote) assert(count); for (unsigned int i = 0; i < count; ++i) { AutoCloseFD fdSocket(SD_LISTEN_FDS_START + i); - closeOnExec(fdSocket.get()); + unix::closeOnExec(fdSocket.get()); listeningSockets.push_back(std::move(fdSocket)); } } // Otherwise, create and bind to a Unix domain socket. else { +#else + { +#endif createDirs(options.socketPath.parent_path()); listeningSockets.push_back(createUnixDomainSocket(options.socketPath.string(), options.socketMode)); } +#ifndef _WIN32 std::vector fds; for (auto & i : listeningSockets) fds.push_back({.fd = i.get(), .events = POLLIN}); +#endif // Loop accepting connections. while (1) { try { checkInterrupt(); +#ifndef _WIN32 auto count = poll(fds.data(), fds.size(), -1); if (count == -1) { if (errno == EINTR) @@ -95,15 +115,26 @@ PeerInfo getPeerInfo(Descriptor remote) throw SysError("polling for incoming connections"); } - for (auto & fd : fds) { - if (!fd.revents) + for (auto & pollfd : fds) { + if (!pollfd.revents) continue; + Socket fd = toSocket(pollfd.fd); +#else + assert(listeningSockets.size() == 1); + { + Socket fd = toSocket(listeningSockets[0].get()); +#endif // Accept a connection. struct sockaddr_un remoteAddr; - socklen_t remoteAddrLen = sizeof(remoteAddr); +#ifndef _WIN32 + socklen_t +#else + int +#endif + remoteAddrLen = sizeof(remoteAddr); - AutoCloseFD remote = accept(fd.fd, (struct sockaddr *) &remoteAddr, &remoteAddrLen); + AutoCloseFD remote = fromSocket(accept(fd, (struct sockaddr *) &remoteAddr, &remoteAddrLen)); checkInterrupt(); if (!remote) { if (errno == EINTR) @@ -123,4 +154,4 @@ PeerInfo getPeerInfo(Descriptor remote) } } -} // namespace nix::unix +} // namespace nix diff --git a/src/libutil/include/nix/util/processes.hh b/src/libutil/include/nix/util/processes.hh index 15fc9fea0dcb..543a98ac3618 100644 --- a/src/libutil/include/nix/util/processes.hh +++ b/src/libutil/include/nix/util/processes.hh @@ -100,7 +100,7 @@ struct ProcessOptions }; #ifndef _WIN32 -pid_t startProcess(std::function fun, const ProcessOptions & options = ProcessOptions()); +pid_t startProcess(std::move_only_function fun, const ProcessOptions & options = ProcessOptions()); #endif /** diff --git a/src/libutil/include/nix/util/unix-domain-socket.hh b/src/libutil/include/nix/util/unix-domain-socket.hh index ca706a32c0c8..958b831ed9d9 100644 --- a/src/libutil/include/nix/util/unix-domain-socket.hh +++ b/src/libutil/include/nix/util/unix-domain-socket.hh @@ -5,7 +5,7 @@ #include "nix/util/file-descriptor.hh" #include "nix/util/socket.hh" -#include +#include #include diff --git a/src/libutil/unix/processes.cc b/src/libutil/unix/processes.cc index d9c4f24fae15..6676198be426 100644 --- a/src/libutil/unix/processes.cc +++ b/src/libutil/unix/processes.cc @@ -180,7 +180,7 @@ void killUser(uid_t uid) ////////////////////////////////////////////////////////////////////// -using ChildWrapperFunction = std::function; +using ChildWrapperFunction = std::move_only_function; /* Wrapper around vfork to prevent the child process from clobbering the caller's stack frame in the parent. */ @@ -208,7 +208,7 @@ static int childEntry(void * arg) } #endif -pid_t startProcess(std::function fun, const ProcessOptions & options) +pid_t startProcess(std::move_only_function fun, const ProcessOptions & options) { auto newLogger = makeSimpleLogger(); ChildWrapperFunction wrapper = [&] { diff --git a/src/nix/unix/daemon.cc b/src/nix/daemon.cc similarity index 78% rename from src/nix/unix/daemon.cc rename to src/nix/daemon.cc index 7fce2e8f6752..02955825afd3 100644 --- a/src/nix/unix/daemon.cc +++ b/src/nix/daemon.cc @@ -26,20 +26,31 @@ #include #include #include -#include #include -#include #include -#include -#include #include +#ifdef _WIN32 +# include +# include +# include +# include +#else +# include +# include +# include +# include +# include +# include +#endif + #ifdef __linux__ # include "nix/util/cgroup.hh" #endif -using namespace nix; -using namespace nix::daemon; +namespace nix { + +using namespace daemon; /** * Settings related to authenticating clients for the Nix daemon. @@ -99,26 +110,33 @@ AuthorizationSettings authorizationSettings; static GlobalConfig::Register rSettings(&authorizationSettings); -#ifndef __linux__ -# define SPLICE_F_MOVE 0 - -static ssize_t splice(int fd_in, void * off_in, int fd_out, void * off_out, size_t len, unsigned int flags) +/** + * Copy data from one file descriptor to another. + * + * @return bytes copied, 0 on EOF + * @throws SysError on read/write failure + */ +static size_t copyData(Descriptor from, Descriptor to) { - // We ignore most parameters, we just have them for conformance with the linux syscall - std::vector buf(8192); - auto read_count = read(fd_in, buf.data(), buf.size()); - if (read_count == -1) - return read_count; - auto write_count = decltype(read_count)(0); - while (write_count < read_count) { - auto res = write(fd_out, buf.data() + write_count, read_count - write_count); - if (res == -1) - return res; - write_count += res; - } - return read_count; -} +#ifdef __linux__ + auto res = splice(from, nullptr, to, nullptr, SSIZE_MAX, SPLICE_F_MOVE); + if (res == -1) + throw SysError("copying data between file descriptors"); + return res; +#else + std::array buf; + auto res = nix::read(from, buf); + if (res == 0) + return 0; + writeFull(to, std::string_view(reinterpret_cast(buf.data()), res), false); + return res; #endif +} + +#ifndef _WIN32 +namespace unix { + +namespace { static void sigChldHandler(int sigNo) { @@ -229,6 +247,68 @@ static std::pair> authPeer(const unix::P return {trusted, std::move(user)}; } +} // namespace + +} // namespace unix +#endif + +/** + * Run a function in a separate process (Unix) or thread (Windows). + * + * On Unix, forks a child process that runs the function and exits. + * On Windows, spawns a detached thread that runs the function. + * + * @param peerPid The peer's PID for debugging (Unix only, ignored on Windows). + * @param closeListeners Callback to close listening sockets (Unix only, ignored on Windows). + * @param f The function to run. Takes no arguments and returns void. + */ +static void forkOrThread( +#ifndef _WIN32 + std::optional peerPid, + std::function closeListeners, +#endif + auto && f) +{ +#ifdef _WIN32 + std::thread([f = std::forward(f)]() { + try { + f(); + } catch (Error & e) { + auto ei = e.info(); + ei.msg = HintFmt("unexpected Nix daemon error: %1%", ei.msg.str()); + logError(ei); + } + }).detach(); +#else + ProcessOptions options; + options.errorPrefix = "unexpected Nix daemon error: "; + options.dieWithParent = false; + options.runExitHandlers = true; + options.allowVfork = false; + startProcess( + [peerPid, closeListeners = std::move(closeListeners), f = std::forward(f)]() { + closeListeners(); + + // Background the daemon. + if (setsid() == -1) + throw SysError("creating a new session"); + + // Restore normal handling of SIGCHLD. + unix::setSigChldAction(false); + + // For debugging, stuff the pid into argv[1]. + if (peerPid && savedArgv[1]) { + auto processName = std::to_string(*peerPid); + strncpy(savedArgv[1], processName.c_str(), strlen(savedArgv[1])); + } + + f(); + exit(0); + }, + options); +#endif +} + /** * Run a server. The loop opens a socket and accepts new connections from that * socket. @@ -243,8 +323,10 @@ static void daemonLoop(ref storeConfig, std::optional storeConfig, std::optional closeListeners) { + +#ifndef _WIN32 unix::closeOnExec(remote.get()); unix::PeerInfo peer; +#endif TrustedFlag trusted; std::optional userName; if (forceTrustClientOpt) trusted = *forceTrustClientOpt; else { +#ifndef _WIN32 peer = unix::getPeerInfo(remote.get()); - auto [_trusted, _userName] = authPeer(peer); + auto [_trusted, _userName] = unix::authPeer(peer); trusted = _trusted; userName = _userName; +#else + warn("no peer cred on windows yet, defaulting to untrusted"); + trusted = NotTrusted; +#endif }; printInfo( (std::string) "accepted connection from pid %1%, user %2%" + (trusted ? " (trusted)" : ""), - peer.pid ? std::to_string(*peer.pid) : "", +#ifndef _WIN32 + peer.pid ? std::to_string(*peer.pid) : +#endif + "", userName.value_or("")); - // 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( - [&, storeConfig, closeListeners = std::move(closeListeners)]() { - closeListeners(); - - // 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]) { - auto processName = std::to_string(*peer.pid); - strncpy(savedArgv[1], processName.c_str(), strlen(savedArgv[1])); - } - + forkOrThread( +#ifndef _WIN32 + peer.pid, + std::move(closeListeners), +#endif + [remote = std::move(remote), trusted, storeConfig]() { // Handle the connection. auto store = storeConfig->openStore(); store->init(); processConnection(store, FdSource(remote.get()), FdSink(remote.get()), trusted, NotRecursive); - - exit(0); - }, - options); + }); }); } catch (Interrupted & e) { return; @@ -342,8 +412,8 @@ static void daemonLoop(ref storeConfig, std::optionalfrom.fd; - int to = conn->to.fd; + Descriptor from = conn->from.fd; + Descriptor to = conn->to.fd; Socket fromSock = toSocket(from), stdinSock = toSocket(getStandardInput()); auto nfds = std::max(fromSock, stdinSock) + 1; @@ -355,18 +425,22 @@ static void forwardStdioConnection(RemoteStore & store) if (select(nfds, &fds, nullptr, nullptr, nullptr) == -1) throw SysError("waiting for data from client or server"); if (FD_ISSET(fromSock, &fds)) { - auto res = splice(from, nullptr, STDOUT_FILENO, nullptr, SSIZE_MAX, SPLICE_F_MOVE); - if (res == -1) - throw SysError("splicing data from daemon socket to stdout"); - else if (res == 0) - throw EndOfFile("unexpected EOF from daemon socket"); + try { + if (copyData(from, getStandardOutput()) == 0) + throw EndOfFile("unexpected EOF from daemon socket"); + } catch (SysError & e) { + e.addTrace({}, "splicing data from daemon socket to stdout"); + throw; + } } if (FD_ISSET(stdinSock, &fds)) { - auto res = splice(STDIN_FILENO, nullptr, to, nullptr, SSIZE_MAX, SPLICE_F_MOVE); - if (res == -1) - throw SysError("splicing data from stdin to daemon socket"); - else if (res == 0) - return; + try { + if (copyData(getStandardInput(), to) == 0) + return; + } catch (SysError & e) { + e.addTrace({}, "splicing data from stdin to daemon socket"); + throw; + } } } } @@ -382,7 +456,7 @@ static void forwardStdioConnection(RemoteStore & store) */ static void processStdioConnection(ref store, TrustedFlag trustClient) { - processConnection(store, FdSource(STDIN_FILENO), FdSink(STDOUT_FILENO), trustClient, NotRecursive); + processConnection(store, FdSource(getStandardInput()), FdSink(getStandardOutput()), trustClient, NotRecursive); } /** @@ -537,3 +611,5 @@ struct CmdDaemon : StoreConfigCommand }; static auto rCmdDaemon = registerCommand2({"daemon"}); + +} // namespace nix diff --git a/src/nix/unix/daemon.md b/src/nix/daemon.md similarity index 100% rename from src/nix/unix/daemon.md rename to src/nix/daemon.md diff --git a/src/nix/meson.build b/src/nix/meson.build index 039b202b937d..e55dab1cf70a 100644 --- a/src/nix/meson.build +++ b/src/nix/meson.build @@ -34,6 +34,10 @@ subdir('nix-meson-build-support/subprojects') subdir('nix-meson-build-support/export-all-symbols') subdir('nix-meson-build-support/windows-version') +if host_machine.system() == 'windows' + deps_other += cxx.find_library('ws2_32') +endif + configdata = configuration_data() # The CLI has a more detailed version string than the libraries; see `nixVersion` @@ -68,6 +72,7 @@ nix_sources = [ config_priv_h ] + files( 'config.cc', 'copy.cc', 'crash-handler.cc', + 'daemon.cc', 'derivation-add.cc', 'derivation-show.cc', 'derivation.cc', @@ -104,19 +109,13 @@ nix_sources = [ config_priv_h ] + files( 'store-gc.cc', 'store-info.cc', 'store-repair.cc', + 'store-roots-daemon.cc', 'store.cc', 'upgrade-nix.cc', 'verify.cc', 'why-depends.cc', ) -if host_machine.system() != 'windows' - nix_sources += files( - 'unix/daemon.cc', - 'unix/store-roots-daemon.cc', - ) -endif - nix_sources += [ gen_header.process('doc/manual/generate-manpage.nix'), gen_header.process('doc/manual/generate-settings.nix'), diff --git a/src/nix/unix/store-roots-daemon.cc b/src/nix/store-roots-daemon.cc similarity index 98% rename from src/nix/unix/store-roots-daemon.cc rename to src/nix/store-roots-daemon.cc index c9728a2efe98..105fbe7dc97a 100644 --- a/src/nix/unix/store-roots-daemon.cc +++ b/src/nix/store-roots-daemon.cc @@ -40,7 +40,7 @@ struct CmdRootsDaemon : StoreConfigCommand auto gcSocketPath = localStoreConfig->getRootsSocketPath(); - unix::serveUnixSocket( + serveUnixSocket( { .socketPath = gcSocketPath, .socketMode = 0666, diff --git a/src/nix/unix/store-roots-daemon.md b/src/nix/store-roots-daemon.md similarity index 100% rename from src/nix/unix/store-roots-daemon.md rename to src/nix/store-roots-daemon.md