From 1d5d30b12f6d3308cea529ead57442e9bd4c6910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= Date: Mon, 11 Apr 2022 10:20:36 +0200 Subject: [PATCH] Make the root-tracer directly listen on a socket Complicates the code quite a bit (compared to letting systemd handle that for us), but makes things much easier to test --- src/nix-find-roots/nix-find-roots.cc | 122 +++++++++++++++++++++------ 1 file changed, 98 insertions(+), 24 deletions(-) diff --git a/src/nix-find-roots/nix-find-roots.cc b/src/nix-find-roots/nix-find-roots.cc index 4db24b29e..38b37e192 100644 --- a/src/nix-find-roots/nix-find-roots.cc +++ b/src/nix-find-roots/nix-find-roots.cc @@ -11,47 +11,73 @@ #include #include #include +#include #include #include #include #include +#include +#include namespace fs = std::filesystem; using std::set, std::string; +class Error : public std::exception { +private: + const string message; + +public: + Error(std::string message) + : message(message) + {} + + const char* what() const noexcept override + { + return message.c_str(); + } +}; + struct GlobalOpts { fs::path storeDir = "/nix/store"; fs::path stateDir = "/nix/var/nix"; + fs::path socketPath = "/nix/var/nix/gc-socket/socket"; enum VerbosityLvl { - Quiet, - Verbose + Quiet = 0, + Normal = 1, + Verbose = 2 }; VerbosityLvl verbosity = Quiet; }; -void log(GlobalOpts::VerbosityLvl verbosity, std::string_view msg) +void log(GlobalOpts::VerbosityLvl currentVerbosity, GlobalOpts::VerbosityLvl minVerbosity, std::string_view msg) { - if (verbosity == GlobalOpts::Quiet) + if (currentVerbosity < minVerbosity) return; std::cerr << msg << std::endl; } +void debug(GlobalOpts::VerbosityLvl currentVerbosity, std::string_view msg) +{ + log(currentVerbosity, GlobalOpts::Verbose, msg); +} + GlobalOpts parseCmdLine(int argc, char** argv) { GlobalOpts res; auto usage = [&]() { - std::cerr << "Usage: " << string(argv[0]) << " [--verbose|-v] [-s storeDir] [-d stateDir]" << std::endl; + std::cerr << "Usage: " << string(argv[0]) << " [--verbose|-v] [-s storeDir] [-d stateDir] [-l socketPath]" << std::endl; exit(1); }; static struct option long_options[] = { { "verbose", no_argument, (int*)&res.verbosity, GlobalOpts::Verbose }, + { "socket_path", required_argument, 0, 'l' }, { "store_dir", required_argument, 0, 's' }, { "state_dir", required_argument, 0, 'd' }, }; int option_index = 0; int opt_char; - while((opt_char = getopt_long(argc, argv, "vs:", + while((opt_char = getopt_long(argc, argv, "vd:s:l:", long_options, &option_index)) != -1) { switch (opt_char) { case 0: @@ -69,6 +95,9 @@ GlobalOpts parseCmdLine(int argc, char** argv) case 'd': res.stateDir = fs::path(optarg); break; + case 'l': + res.socketPath = fs::path(optarg); + break; default: std::cerr << "Got invalid char: " << (char)opt_char << std::endl; abort(); @@ -106,9 +135,11 @@ void followPathToStore( int recursionsLeft, TraceResult & res, const fs::path & root, - const fs::file_status & status) + const fs::file_status & status, + const std::optional & parent = std::nullopt + ) { - log(opts.verbosity, "Considering file " + root.string()); + debug(opts.verbosity, "Considering file " + root.string()); if (recursionsLeft < 0) return; @@ -125,8 +156,8 @@ void followPathToStore( { auto target = root.parent_path() / fs::read_symlink(root); auto not_found = [&](std::string msg) { - log(opts.verbosity, "Error accessing the file " + target.string() + ": " + msg); - log(opts.verbosity, "(When resolving the symlink " + root.string() + ")"); + debug(opts.verbosity, "Error accessing the file " + target.string() + ": " + msg); + debug(opts.verbosity, "(When resolving the symlink " + root.string() + ")"); res.deadLinks.insert(root); }; try { @@ -166,7 +197,7 @@ void followPathToStore( auto status = fs::symlink_status(root); followPathToStore(opts, recursionsLeft, res, root, status); } catch (fs::filesystem_error & e) { - log(opts.verbosity, "Error accessing the file " + root.string() + ": " + e.what()); + debug(opts.verbosity, "Error accessing the file " + root.string() + ": " + e.what()); } } @@ -257,7 +288,7 @@ Roots getRuntimeRoots(GlobalOpts opts) || !procEntry.is_directory()) continue; - log(opts.verbosity, "Considering path " + procEntry.path().string()); + debug(opts.verbosity, "Considering path " + procEntry.path().string()); // A set of paths used by the executable and possibly symlinks to a // path in the store @@ -277,7 +308,7 @@ Roots getRuntimeRoots(GlobalOpts opts) if (isInStore(opts.storeDir, realPath)) res[realPath].insert(path); } catch (fs::filesystem_error &e) { - log(opts.verbosity, e.what()); + debug(opts.verbosity, e.what()); } // Scan the environment of the executable @@ -296,21 +327,64 @@ Roots getRuntimeRoots(GlobalOpts opts) int main(int argc, char * * argv) { - GlobalOpts opts = parseCmdLine(argc, argv); - set standardRoots = { + const GlobalOpts opts = parseCmdLine(argc, argv); + const set standardRoots = { opts.stateDir / fs::path("profiles"), opts.stateDir / fs::path("gcroots"), }; - auto traceResult = followPathsToStore(opts, standardRoots); - auto runtimeRoots = getRuntimeRoots(opts); - traceResult.storeRoots.insert(runtimeRoots.begin(), runtimeRoots.end()); - for (auto & [rootInStore, externalRoots] : traceResult.storeRoots) { - for (auto & externalRoot : externalRoots) - std::cout << rootInStore.string() << '\t' << externalRoot.string() << std::endl; + + int mySock = socket(PF_UNIX, SOCK_STREAM, 0); + if (mySock == 0) { + throw Error("Cannot create Unix domain socket"); } - std::cout << std::endl; - for (auto & deadLink : traceResult.deadLinks) { - std::cout << deadLink.string() << std::endl; + struct sockaddr_un addr; + addr.sun_family = AF_UNIX; + + unlink(opts.socketPath.c_str()); + strcpy(addr.sun_path, opts.socketPath.c_str()); + if (bind(mySock, (struct sockaddr*) &addr, sizeof(addr)) == -1) { + throw Error("Cannot bind to socket"); + } + + if (listen(mySock, 5) == -1) + throw Error("cannot listen on socket " + opts.socketPath.string()); + + addr.sun_family = AF_UNIX; + while (1) { + struct sockaddr_un remoteAddr; + socklen_t remoteAddrLen = sizeof(remoteAddr); + int remoteSocket = accept( + mySock, + (struct sockaddr*) & remoteAddr, + &remoteAddrLen + ); + + if (remoteSocket == -1) { + if (errno == EINTR) continue; + throw Error("Error accepting the connection"); + } + + log(opts.verbosity, GlobalOpts::Quiet, "accepted connection"); + + auto traceResult = followPathsToStore(opts, standardRoots); + auto runtimeRoots = getRuntimeRoots(opts); + traceResult.storeRoots.insert(runtimeRoots.begin(), runtimeRoots.end()); + for (auto & [rootInStore, externalRoots] : traceResult.storeRoots) { + for (auto & externalRoot : externalRoots) { + send(remoteSocket, rootInStore.string().c_str(), rootInStore.string().size(), 0); + send(remoteSocket, "\t", strlen("\t"), 0); + send(remoteSocket, externalRoot.string().c_str(), externalRoot.string().size(), 0); + send(remoteSocket, "\n", strlen("\n"), 0); + } + + } + send(remoteSocket, "\n", strlen("\n"), 0); + for (auto & deadLink : traceResult.deadLinks) { + send(remoteSocket, deadLink.string().c_str(), deadLink.string().size(), 0); + send(remoteSocket, "\n", strlen("\n"), 0); + } + + close(remoteSocket); } }