diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc index cb820e2d5..2cd9de1d3 100644 --- a/src/libstore/gc.cc +++ b/src/libstore/gc.cc @@ -31,6 +31,7 @@ namespace nix { static std::string gcSocketPath = "/gc-socket/socket"; +static std::string rootsSocketPath = "/gc-roots-socket/socket"; static std::string gcRootsDir = "gcroots"; @@ -143,7 +144,7 @@ void LocalStore::addTempRoot(const StorePath & path) auto fdRootsSocket(_fdRootsSocket.lock()); if (!*fdRootsSocket) { - auto socketPath = stateDir.get() + gcSocketPath; + auto socketPath = stateDir.get() + rootsSocketPath; debug("connecting to '%s'", socketPath); *fdRootsSocket = createUnixDomainSocket(); try { @@ -304,8 +305,10 @@ void LocalStore::findRoots(const Path & path, unsigned char type, Roots & roots) } -void LocalStore::findRootsNoTemp(Roots & roots, bool censor) +void LocalStore::findRootsNoTempNoExternalDaemon(Roots & roots, bool censor) { + debug("Can’t connect to the tracing daemon socket, fallback to the internal trace"); + /* Process direct roots in {gcroots,profiles}. */ findRoots(stateDir + "/" + gcRootsDir, DT_UNKNOWN, roots); findRoots(stateDir + "/profiles", DT_UNKNOWN, roots); @@ -322,11 +325,57 @@ Roots LocalStore::findRoots(bool censor) Roots roots; findRootsNoTemp(roots, censor); - findTempRoots(roots, censor); + FDs fds; + findTempRoots(fds, roots, censor); return roots; } +void LocalStore::findRootsNoTemp(Roots & roots, bool censor) +{ + + auto fd = createUnixDomainSocket(); + + std::string socketPath = settings.gcSocketPath.get() != "auto" + ? settings.gcSocketPath.get() + : stateDir.get() + gcSocketPath; + + try { + nix::connect(fd.get(), socketPath); + } catch (SysError & e) { + return findRootsNoTempNoExternalDaemon(roots, censor); + } + + experimentalFeatureSettings.require(Xp::ExternalGCDaemon); + + try { + StringMap unescapes = { + { "\\n", "\n"}, + { "\\t", "\t"}, + }; + while (true) { + auto line = readLine(fd.get()); + if (line.empty()) break; // TODO: Handle the broken symlinks + auto parsedLine = tokenizeString>(line, "\t"); + if (parsedLine.size() != 2) + throw Error("Invalid result from the gc helper"); + auto rawDestPath = rewriteStrings(parsedLine[0], unescapes); + auto retainer = rewriteStrings(parsedLine[1], unescapes); + if (!isInStore(rawDestPath)) continue; + try { + auto destPath = toStorePath(rawDestPath).first; + if (!isValidPath(destPath)) continue; + roots[destPath].insert( + (!censor || isInDir(retainer, stateDir)) ? retainer : censored); + } catch (Error &) { + } + } + } catch (EndOfFile &) { + } + + findRuntimeRoots(roots, censor); +} + typedef std::unordered_map> UncheckedRoots; static void readProcLink(const std::string & file, UncheckedRoots & roots) @@ -516,7 +565,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results) readFile(*p); /* Start the server for receiving new roots. */ - auto socketPath = stateDir.get() + gcSocketPath; + auto socketPath = stateDir.get() + rootsSocketPath; createDirs(dirOf(socketPath)); auto fdServer = createUnixDomainSocket(socketPath, 0666); @@ -625,7 +674,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results) printInfo("finding garbage collector roots..."); Roots rootMap; if (!options.ignoreLiveness) - findRootsNoTemp(rootMap, true); + rootMap = findRoots(true); for (auto & i : rootMap) roots.insert(i.first); diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index 8330d6571..2d2762ea4 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -124,6 +124,13 @@ public: section of the manual for supported store types and settings. )"}; + Setting gcSocketPath { + this, + getEnv("NIX_GC_SOCKET_PATH").value_or("auto"), + "gc-socket-path", + "Path to the socket used to communicate with an external GC." + }; + Setting keepFailed{this, false, "keep-failed", "Whether to keep temporary directories of failed builds."}; diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh index ba56d3ead..c495e9391 100644 --- a/src/libstore/local-store.hh +++ b/src/libstore/local-store.hh @@ -328,6 +328,8 @@ private: void findRootsNoTemp(Roots & roots, bool censor); + void findRootsNoTempNoExternalDaemon(Roots & roots, bool censor); + void findRuntimeRoots(Roots & roots, bool censor); std::pair createTempDirInStore(); diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc index 9b46fc5b0..afa912f02 100644 --- a/src/libutil/experimental-features.cc +++ b/src/libutil/experimental-features.cc @@ -268,6 +268,20 @@ constexpr std::array xpFeatureDetails Allow the use of the [`mounted SSH store`](@docroot@/command-ref/new-cli/nix3-help-stores.html#experimental-ssh-store-with-filesytem-mounted). )", }, + { + .tag = Xp::ExternalGCDaemon, + .name = "external-gc-daemon", + .description = R"( + Make the garbage collector use an external daemon for the tracing. + + This makes it possible to run a multi-user Nix daemon as a non-root + user (only the tracing daemon needs to be root), reducing the attack + surface a lot. + + This requires more infrastructure and isn't directly supported by the + installer. + )" + }, { .tag = Xp::VerifiedFetches, .name = "verified-fetches", diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh index eae4fa9b8..bd6358859 100644 --- a/src/libutil/experimental-features.hh +++ b/src/libutil/experimental-features.hh @@ -35,6 +35,7 @@ enum struct ExperimentalFeature ReadOnlyLocalStore, ConfigurableImpureEnv, MountedSSHStore, + ExternalGCDaemon, VerifiedFetches, };