1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-13 05:56:03 +01:00

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
This commit is contained in:
Théophane Hufschmitt 2022-04-11 10:20:36 +02:00
parent e1df6c220a
commit 1d5d30b12f

View file

@ -11,47 +11,73 @@
#include <iostream> #include <iostream>
#include <regex> #include <regex>
#include <set> #include <set>
#include <unistd.h>
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
#include <getopt.h> #include <getopt.h>
#include <fstream> #include <fstream>
#include <sys/socket.h>
#include <sys/un.h>
namespace fs = std::filesystem; namespace fs = std::filesystem;
using std::set, std::string; 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 { struct GlobalOpts {
fs::path storeDir = "/nix/store"; fs::path storeDir = "/nix/store";
fs::path stateDir = "/nix/var/nix"; fs::path stateDir = "/nix/var/nix";
fs::path socketPath = "/nix/var/nix/gc-socket/socket";
enum VerbosityLvl { enum VerbosityLvl {
Quiet, Quiet = 0,
Verbose Normal = 1,
Verbose = 2
}; };
VerbosityLvl verbosity = Quiet; 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; return;
std::cerr << msg << std::endl; 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 parseCmdLine(int argc, char** argv)
{ {
GlobalOpts res; GlobalOpts res;
auto usage = [&]() { 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); exit(1);
}; };
static struct option long_options[] = { static struct option long_options[] = {
{ "verbose", no_argument, (int*)&res.verbosity, GlobalOpts::Verbose }, { "verbose", no_argument, (int*)&res.verbosity, GlobalOpts::Verbose },
{ "socket_path", required_argument, 0, 'l' },
{ "store_dir", required_argument, 0, 's' }, { "store_dir", required_argument, 0, 's' },
{ "state_dir", required_argument, 0, 'd' }, { "state_dir", required_argument, 0, 'd' },
}; };
int option_index = 0; int option_index = 0;
int opt_char; 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) { long_options, &option_index)) != -1) {
switch (opt_char) { switch (opt_char) {
case 0: case 0:
@ -69,6 +95,9 @@ GlobalOpts parseCmdLine(int argc, char** argv)
case 'd': case 'd':
res.stateDir = fs::path(optarg); res.stateDir = fs::path(optarg);
break; break;
case 'l':
res.socketPath = fs::path(optarg);
break;
default: default:
std::cerr << "Got invalid char: " << (char)opt_char << std::endl; std::cerr << "Got invalid char: " << (char)opt_char << std::endl;
abort(); abort();
@ -106,9 +135,11 @@ void followPathToStore(
int recursionsLeft, int recursionsLeft,
TraceResult & res, TraceResult & res,
const fs::path & root, const fs::path & root,
const fs::file_status & status) const fs::file_status & status,
const std::optional<const fs::path> & parent = std::nullopt
)
{ {
log(opts.verbosity, "Considering file " + root.string()); debug(opts.verbosity, "Considering file " + root.string());
if (recursionsLeft < 0) if (recursionsLeft < 0)
return; return;
@ -125,8 +156,8 @@ void followPathToStore(
{ {
auto target = root.parent_path() / fs::read_symlink(root); auto target = root.parent_path() / fs::read_symlink(root);
auto not_found = [&](std::string msg) { auto not_found = [&](std::string msg) {
log(opts.verbosity, "Error accessing the file " + target.string() + ": " + msg); debug(opts.verbosity, "Error accessing the file " + target.string() + ": " + msg);
log(opts.verbosity, "(When resolving the symlink " + root.string() + ")"); debug(opts.verbosity, "(When resolving the symlink " + root.string() + ")");
res.deadLinks.insert(root); res.deadLinks.insert(root);
}; };
try { try {
@ -166,7 +197,7 @@ void followPathToStore(
auto status = fs::symlink_status(root); auto status = fs::symlink_status(root);
followPathToStore(opts, recursionsLeft, res, root, status); followPathToStore(opts, recursionsLeft, res, root, status);
} catch (fs::filesystem_error & e) { } 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()) || !procEntry.is_directory())
continue; 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 // A set of paths used by the executable and possibly symlinks to a
// path in the store // path in the store
@ -277,7 +308,7 @@ Roots getRuntimeRoots(GlobalOpts opts)
if (isInStore(opts.storeDir, realPath)) if (isInStore(opts.storeDir, realPath))
res[realPath].insert(path); res[realPath].insert(path);
} catch (fs::filesystem_error &e) { } catch (fs::filesystem_error &e) {
log(opts.verbosity, e.what()); debug(opts.verbosity, e.what());
} }
// Scan the environment of the executable // Scan the environment of the executable
@ -296,21 +327,64 @@ Roots getRuntimeRoots(GlobalOpts opts)
int main(int argc, char * * argv) int main(int argc, char * * argv)
{ {
GlobalOpts opts = parseCmdLine(argc, argv); const GlobalOpts opts = parseCmdLine(argc, argv);
set<fs::path> standardRoots = { const set<fs::path> standardRoots = {
opts.stateDir / fs::path("profiles"), opts.stateDir / fs::path("profiles"),
opts.stateDir / fs::path("gcroots"), opts.stateDir / fs::path("gcroots"),
}; };
int mySock = socket(PF_UNIX, SOCK_STREAM, 0);
if (mySock == 0) {
throw Error("Cannot create Unix domain socket");
}
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 traceResult = followPathsToStore(opts, standardRoots);
auto runtimeRoots = getRuntimeRoots(opts); auto runtimeRoots = getRuntimeRoots(opts);
traceResult.storeRoots.insert(runtimeRoots.begin(), runtimeRoots.end()); traceResult.storeRoots.insert(runtimeRoots.begin(), runtimeRoots.end());
for (auto & [rootInStore, externalRoots] : traceResult.storeRoots) { for (auto & [rootInStore, externalRoots] : traceResult.storeRoots) {
for (auto & externalRoot : externalRoots) for (auto & externalRoot : externalRoots) {
std::cout << rootInStore.string() << '\t' << externalRoot.string() << std::endl; 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);
} }
std::cout << std::endl;
}
send(remoteSocket, "\n", strlen("\n"), 0);
for (auto & deadLink : traceResult.deadLinks) { for (auto & deadLink : traceResult.deadLinks) {
std::cout << deadLink.string() << std::endl; send(remoteSocket, deadLink.string().c_str(), deadLink.string().size(), 0);
send(remoteSocket, "\n", strlen("\n"), 0);
}
close(remoteSocket);
} }
} }