From d8dfffbe37dd5f40e00ca6261d4ca7c13d3d36b0 Mon Sep 17 00:00:00 2001 From: eldritch horrors Date: Mon, 21 Jul 2025 17:46:53 +0200 Subject: [PATCH] daemon: use kj for splicing stdio connections drop our reimplementation of splice for non-linux in favor of using kj pumpTo. this avoids select() for its O(maxfd) behavior, and if kj ever uses something more efficient than read/write loops we'll benefit too. Change-Id: Id01ba84bf8831455af2d9755bf1a3039d215bb47 --- lix/nix/daemon.cc | 63 ++++++++------------------------ tests/functional/remote-store.sh | 3 ++ 2 files changed, 18 insertions(+), 48 deletions(-) diff --git a/lix/nix/daemon.cc b/lix/nix/daemon.cc index 587085f65..6a2863220 100644 --- a/lix/nix/daemon.cc +++ b/lix/nix/daemon.cc @@ -81,27 +81,6 @@ 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) -{ - // 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; -} -#endif - - static void sigChldHandler(int sigNo) { // Ensure we don't modify errno of whatever we've interrupted @@ -470,34 +449,22 @@ static void daemonInstance(AsyncIoRoot & aio, std::optional forceTr * * Loops until standard input disconnects, or an error is encountered. */ -static void forwardStdioConnection(RemoteStore & store) { +static void forwardStdioConnection(AsyncIoRoot & aio, RemoteStore & store) +{ auto conn = store.openConnectionWrapper(); - int from = conn->getFD(); - int to = conn->getFD(); + auto connSocket = AIO().lowLevelProvider.wrapSocketFd(conn->getFD()); + auto stdin = AIO().lowLevelProvider.wrapInputFd(STDIN_FILENO); + auto stdout = AIO().lowLevelProvider.wrapOutputFd(STDOUT_FILENO); - auto nfds = std::max(from, STDIN_FILENO) + 1; - while (true) { - fd_set fds; - FD_ZERO(&fds); - FD_SET(from, &fds); - FD_SET(STDIN_FILENO, &fds); - if (select(nfds, &fds, nullptr, nullptr, nullptr) == -1) - throw SysError("waiting for data from client or server"); - if (FD_ISSET(from, &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"); - } - if (FD_ISSET(STDIN_FILENO, &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; - } - } + aio.blockOn(connSocket->pumpTo(*stdout) + .then([](auto) -> Result { + return { + std::make_exception_ptr(EndOfFile("unexpected EOF from daemon socket")) + }; + }) + .exclusiveJoin(stdin->pumpTo(*connSocket).then([](auto) -> Result { + return result::success(); + }))); } /** @@ -534,7 +501,7 @@ runDaemon(AsyncIoRoot & aio, bool stdio, std::optional forceTrustCl // must process it ourselves (before delegating to the next store) to // force untrusting the client. if (auto remoteStore = store.try_cast_shared(); remoteStore && (!forceTrustClientOpt || *forceTrustClientOpt != NotTrusted)) - forwardStdioConnection(*remoteStore); + forwardStdioConnection(aio, *remoteStore); else // `Trusted` is passed in the auto (no override case) because we // cannot see who is on the other side of a plain pipe. Limiting diff --git a/tests/functional/remote-store.sh b/tests/functional/remote-store.sh index 856622e79..3ec7f25be 100644 --- a/tests/functional/remote-store.sh +++ b/tests/functional/remote-store.sh @@ -31,4 +31,7 @@ nix-store --dump-db > $TEST_ROOT/d1 NIX_REMOTE= nix-store --dump-db > $TEST_ROOT/d2 cmp $TEST_ROOT/d1 $TEST_ROOT/d2 +# check that proxying one daemon through another works +nix --store 'ssh-ng://localhost?remote-store=ssh-ng%3a%2f%2flocalhost' store add-file ./ifd.nix + killDaemon