diff --git a/src/libstore/gc-store.hh b/src/libstore/gc-store.hh index ab1059fb1..88c997247 100644 --- a/src/libstore/gc-store.hh +++ b/src/libstore/gc-store.hh @@ -7,7 +7,14 @@ namespace nix { +/** + * Garbage-collector roots, referring to a store path + */ typedef std::unordered_map> Roots; +/** + * Possible garbage collector roots, referring to any path + */ +typedef std::unordered_map> UncheckedRoots; struct GCOptions diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc index 20519c1a2..535bbd251 100644 --- a/src/libstore/gc.cc +++ b/src/libstore/gc.cc @@ -321,105 +321,8 @@ Roots LocalStore::findRoots(bool censor) return roots; } -typedef std::unordered_map> UncheckedRoots; - -static void readProcLink(const std::string & file, UncheckedRoots & roots) +void LocalStore::findPlatformRoots(UncheckedRoots & unchecked) { - constexpr auto bufsiz = PATH_MAX; - char buf[bufsiz]; - auto res = readlink(file.c_str(), buf, bufsiz); - if (res == -1) { - if (errno == ENOENT || errno == EACCES || errno == ESRCH) - return; - throw SysError("reading symlink"); - } - if (res == bufsiz) { - throw Error("overly long symlink starting with '%1%'", std::string_view(buf, bufsiz)); - } - if (res > 0 && buf[0] == '/') - roots[std::string(static_cast(buf), res)] - .emplace(file); -} - -static std::string quoteRegexChars(const std::string & raw) -{ - static auto specialRegex = std::regex(R"([.^$\\*+?()\[\]{}|])"); - return std::regex_replace(raw, specialRegex, R"(\$&)"); -} - -#if __linux__ -static void readFileRoots(const char * path, UncheckedRoots & roots) -{ - try { - roots[readFile(path)].emplace(path); - } catch (SysError & e) { - if (e.errNo != ENOENT && e.errNo != EACCES) - throw; - } -} -#endif - -void LocalStore::findRuntimeRoots(Roots & roots, bool censor) -{ - UncheckedRoots unchecked; - - auto procDir = AutoCloseDir{opendir("/proc")}; - if (procDir) { - struct dirent * ent; - auto digitsRegex = std::regex(R"(^\d+$)"); - auto mapRegex = std::regex(R"(^\s*\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(/\S+)\s*$)"); - auto storePathRegex = std::regex(quoteRegexChars(storeDir) + R"(/[0-9a-z]+[0-9a-zA-Z\+\-\._\?=]*)"); - while (errno = 0, ent = readdir(procDir.get())) { - checkInterrupt(); - if (std::regex_match(ent->d_name, digitsRegex)) { - try { - readProcLink(fmt("/proc/%s/exe" ,ent->d_name), unchecked); - readProcLink(fmt("/proc/%s/cwd", ent->d_name), unchecked); - - auto fdStr = fmt("/proc/%s/fd", ent->d_name); - auto fdDir = AutoCloseDir(opendir(fdStr.c_str())); - if (!fdDir) { - if (errno == ENOENT || errno == EACCES) - continue; - throw SysError("opening %1%", fdStr); - } - struct dirent * fd_ent; - while (errno = 0, fd_ent = readdir(fdDir.get())) { - if (fd_ent->d_name[0] != '.') - readProcLink(fmt("%s/%s", fdStr, fd_ent->d_name), unchecked); - } - if (errno) { - if (errno == ESRCH) - continue; - throw SysError("iterating /proc/%1%/fd", ent->d_name); - } - fdDir.reset(); - - auto mapFile = fmt("/proc/%s/maps", ent->d_name); - auto mapLines = tokenizeString>(readFile(mapFile), "\n"); - for (const auto & line : mapLines) { - auto match = std::smatch{}; - if (std::regex_match(line, match, mapRegex)) - unchecked[match[1]].emplace(mapFile); - } - - auto envFile = fmt("/proc/%s/environ", ent->d_name); - auto envString = readFile(envFile); - auto env_end = std::sregex_iterator{}; - for (auto i = std::sregex_iterator{envString.begin(), envString.end(), storePathRegex}; i != env_end; ++i) - unchecked[i->str()].emplace(envFile); - } catch (SysError & e) { - if (errno == ENOENT || errno == EACCES || errno == ESRCH) - continue; - throw; - } - } - } - if (errno) - throw SysError("iterating /proc"); - } - -#if !defined(__linux__) // lsof is really slow on OS X. This actually causes the gc-concurrent.sh test to fail. // See: https://github.com/NixOS/nix/issues/3011 // Because of this we disable lsof when running the tests. @@ -437,13 +340,13 @@ void LocalStore::findRuntimeRoots(Roots & roots, bool censor) /* lsof not installed, lsof failed */ } } -#endif +} -#if __linux__ - readFileRoots("/proc/sys/kernel/modprobe", unchecked); - readFileRoots("/proc/sys/kernel/fbsplash", unchecked); - readFileRoots("/proc/sys/kernel/poweroff_cmd", unchecked); -#endif +void LocalStore::findRuntimeRoots(Roots & roots, bool censor) +{ + UncheckedRoots unchecked; + + findPlatformRoots(unchecked); for (auto & [target, links] : unchecked) { if (!isInStore(target)) continue; diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index f252b449c..2f59b3591 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -1940,6 +1940,4 @@ std::optional LocalStore::getVersion() return nixVersion; } -static RegisterStoreImplementation regLocalStore; - } // namespace nix diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh index fe26a0f27..b8d1f02ab 100644 --- a/src/libstore/local-store.hh +++ b/src/libstore/local-store.hh @@ -127,6 +127,17 @@ private: const PublicKeys & getPublicKeys(); +protected: + + /** + * Initialise the local store, upgrading the schema if + * necessary. + * Protected so that users don't accidentally create a LocalStore + * instead of a platform's subclass. + */ + LocalStore(const Params & params); + LocalStore(std::string scheme, std::string path, const Params & params); + public: /** @@ -134,18 +145,16 @@ public: */ PathSet locksHeld; - /** - * Initialise the local store, upgrading the schema if - * necessary. - */ - LocalStore(const Params & params); - LocalStore(std::string scheme, std::string path, const Params & params); - - ~LocalStore(); + virtual ~LocalStore(); static std::set uriSchemes() { return {}; } + /** + * Create a LocalStore, possibly a platform-specific subclass + */ + static std::shared_ptr makeLocalStore(const Params & params); + /** * Implementations of abstract store API methods. */ @@ -330,6 +339,12 @@ private: void findRootsNoTemp(Roots & roots, bool censor); + /** + * Find possible garbage collector roots in a platform-specific manner, + * e.g. by looking in `/proc` or using `lsof` + */ + virtual void findPlatformRoots(UncheckedRoots & unchecked); + void findRuntimeRoots(Roots & roots, bool censor); std::pair createTempDirInStore(); diff --git a/src/libstore/local.mk b/src/libstore/local.mk index 68ccdc409..6bd73965d 100644 --- a/src/libstore/local.mk +++ b/src/libstore/local.mk @@ -5,6 +5,11 @@ libstore_NAME = libnixstore libstore_DIR := $(d) libstore_SOURCES := $(wildcard $(d)/*.cc $(d)/builtins/*.cc $(d)/build/*.cc) +ifdef HOST_LINUX +libstore_SOURCES += $(d)/platform/linux.cc +else +libstore_SOURCES += $(d)/platform/fallback.cc +endif libstore_LIBS = libutil diff --git a/src/libstore/meson.build b/src/libstore/meson.build index e1c6c267a..0deedea42 100644 --- a/src/libstore/meson.build +++ b/src/libstore/meson.build @@ -66,6 +66,7 @@ libstore_sources = files( 'path-with-outputs.cc', 'path.cc', 'pathlocks.cc', + 'platform.cc', 'profiles.cc', 'realisation.cc', 'remote-fs-accessor.cc', @@ -158,6 +159,14 @@ libstore_headers = files( 'worker-protocol.hh', ) +if host_machine.system() == 'linux' + libstore_sources += files('platform/linux.cc') + libstore_headers += files('platform/linux.hh') +else + libstore_sources += files('platform/fallback.cc') + libstore_headers += files('platform/fallback.hh') +endif + # These variables (aside from LSOF) are created pseudo-dynamically, near the beginning of # the top-level meson.build. Aside from prefix itself, each of these was # made into an absolute path by joining it with prefix, unless it was already diff --git a/src/libstore/platform.cc b/src/libstore/platform.cc new file mode 100644 index 000000000..9c389ef55 --- /dev/null +++ b/src/libstore/platform.cc @@ -0,0 +1,18 @@ +#include "local-store.hh" + +#if __linux__ +#include "platform/linux.hh" +#else +#include "platform/fallback.hh" +#endif + +namespace nix { +std::shared_ptr LocalStore::makeLocalStore(const Params & params) +{ +#if __linux__ + return std::shared_ptr(new LinuxLocalStore(params)); +#else + return std::shared_ptr(new FallbackLocalStore(params)); +#endif +} +} diff --git a/src/libstore/platform/fallback.cc b/src/libstore/platform/fallback.cc new file mode 100644 index 000000000..5a01d64c8 --- /dev/null +++ b/src/libstore/platform/fallback.cc @@ -0,0 +1,5 @@ +#include "platform/fallback.hh" + +namespace nix { +static RegisterStoreImplementation regLocalStore; +} diff --git a/src/libstore/platform/fallback.hh b/src/libstore/platform/fallback.hh new file mode 100644 index 000000000..fd27edbe6 --- /dev/null +++ b/src/libstore/platform/fallback.hh @@ -0,0 +1,31 @@ +#pragma once +///@file + +#include "local-store.hh" + +namespace nix { + +/** + * Fallback platform implementation of LocalStore + * Exists so we can make LocalStore constructor protected + */ +class FallbackLocalStore : public LocalStore +{ +public: + FallbackLocalStore(const Params & params) + : StoreConfig(params) + , LocalFSStoreConfig(params) + , LocalStoreConfig(params) + , Store(params) + , LocalFSStore(params) + , LocalStore(params) + { + } + FallbackLocalStore(const std::string scheme, std::string path, const Params & params) + : FallbackLocalStore(params) + { + throw UnimplementedError("FallbackLocalStore"); + } +}; + +} diff --git a/src/libstore/platform/linux.cc b/src/libstore/platform/linux.cc new file mode 100644 index 000000000..9be3e47da --- /dev/null +++ b/src/libstore/platform/linux.cc @@ -0,0 +1,123 @@ +#include "gc-store.hh" +#include "signals.hh" +#include "platform/linux.hh" + +#include + +namespace nix { +static RegisterStoreImplementation regLocalStore; + +static void readProcLink(const std::string & file, UncheckedRoots & roots) +{ + constexpr auto bufsiz = PATH_MAX; + char buf[bufsiz]; + auto res = readlink(file.c_str(), buf, bufsiz); + if (res == -1) { + if (errno == ENOENT || errno == EACCES || errno == ESRCH) { + return; + } + throw SysError("reading symlink"); + } + if (res == bufsiz) { + throw Error("overly long symlink starting with '%1%'", std::string_view(buf, bufsiz)); + } + if (res > 0 && buf[0] == '/') { + roots[std::string(static_cast(buf), res)].emplace(file); + } +} + +static std::string quoteRegexChars(const std::string & raw) +{ + static auto specialRegex = std::regex(R"([.^$\\*+?()\[\]{}|])"); + return std::regex_replace(raw, specialRegex, R"(\$&)"); +} + +static void readFileRoots(const char * path, UncheckedRoots & roots) +{ + try { + roots[readFile(path)].emplace(path); + } catch (SysError & e) { + if (e.errNo != ENOENT && e.errNo != EACCES) { + throw; + } + } +} + +void LinuxLocalStore::findPlatformRoots(UncheckedRoots & unchecked) +{ + auto procDir = AutoCloseDir{opendir("/proc")}; + if (procDir) { + struct dirent * ent; + auto digitsRegex = std::regex(R"(^\d+$)"); + auto mapRegex = std::regex(R"(^\s*\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(/\S+)\s*$)"); + auto storePathRegex = + std::regex(quoteRegexChars(storeDir) + R"(/[0-9a-z]+[0-9a-zA-Z\+\-\._\?=]*)"); + while (errno = 0, ent = readdir(procDir.get())) { + checkInterrupt(); + if (std::regex_match(ent->d_name, digitsRegex)) { + try { + readProcLink(fmt("/proc/%s/exe", ent->d_name), unchecked); + readProcLink(fmt("/proc/%s/cwd", ent->d_name), unchecked); + + auto fdStr = fmt("/proc/%s/fd", ent->d_name); + auto fdDir = AutoCloseDir(opendir(fdStr.c_str())); + if (!fdDir) { + if (errno == ENOENT || errno == EACCES) { + continue; + } + throw SysError("opening %1%", fdStr); + } + struct dirent * fd_ent; + while (errno = 0, fd_ent = readdir(fdDir.get())) { + if (fd_ent->d_name[0] != '.') { + readProcLink(fmt("%s/%s", fdStr, fd_ent->d_name), unchecked); + } + } + if (errno) { + if (errno == ESRCH) { + continue; + } + throw SysError("iterating /proc/%1%/fd", ent->d_name); + } + fdDir.reset(); + + auto mapFile = fmt("/proc/%s/maps", ent->d_name); + auto mapLines = + tokenizeString>(readFile(mapFile), "\n"); + for (const auto & line : mapLines) { + auto match = std::smatch{}; + if (std::regex_match(line, match, mapRegex)) { + unchecked[match[1]].emplace(mapFile); + } + } + + auto envFile = fmt("/proc/%s/environ", ent->d_name); + auto envString = readFile(envFile); + auto env_end = std::sregex_iterator{}; + for (auto i = + std::sregex_iterator{ + envString.begin(), envString.end(), storePathRegex + }; + i != env_end; + ++i) + { + unchecked[i->str()].emplace(envFile); + } + } catch (SysError & e) { + if (errno == ENOENT || errno == EACCES || errno == ESRCH) { + continue; + } + throw; + } + } + } + if (errno) { + throw SysError("iterating /proc"); + } + } + + readFileRoots("/proc/sys/kernel/modprobe", unchecked); + readFileRoots("/proc/sys/kernel/fbsplash", unchecked); + readFileRoots("/proc/sys/kernel/poweroff_cmd", unchecked); +} +} diff --git a/src/libstore/platform/linux.hh b/src/libstore/platform/linux.hh new file mode 100644 index 000000000..8b97e17c5 --- /dev/null +++ b/src/libstore/platform/linux.hh @@ -0,0 +1,35 @@ +#pragma once +///@file + +#include "gc-store.hh" +#include "local-store.hh" + +namespace nix { + +/** + * Linux-specific implementation of LocalStore + */ +class LinuxLocalStore : public LocalStore +{ +public: + LinuxLocalStore(const Params & params) + : StoreConfig(params) + , LocalFSStoreConfig(params) + , LocalStoreConfig(params) + , Store(params) + , LocalFSStore(params) + , LocalStore(params) + { + } + LinuxLocalStore(const std::string scheme, std::string path, const Params & params) + : LinuxLocalStore(params) + { + throw UnimplementedError("LinuxLocalStore"); + } + +private: + + void findPlatformRoots(UncheckedRoots & unchecked) override; +}; + +} diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc index 94202d46e..69e89263b 100644 --- a/src/libstore/store-api.cc +++ b/src/libstore/store-api.cc @@ -1426,7 +1426,7 @@ std::shared_ptr openFromNonUri(const std::string & uri, const Store::Para if (uri == "" || uri == "auto") { auto stateDir = getOr(params, "state", settings.nixStateDir); if (access(stateDir.c_str(), R_OK | W_OK) == 0) - return std::make_shared(params); + return LocalStore::makeLocalStore(params); else if (pathExists(settings.nixDaemonSocketFile)) return std::make_shared(params); #if __linux__ @@ -1444,26 +1444,26 @@ std::shared_ptr openFromNonUri(const std::string & uri, const Store::Para try { createDirs(chrootStore); } catch (Error & e) { - return std::make_shared(params); + return LocalStore::makeLocalStore(params); } warn("'%s' does not exist, so Nix will use '%s' as a chroot store", stateDir, chrootStore); } else debug("'%s' does not exist, so Nix will use '%s' as a chroot store", stateDir, chrootStore); Store::Params params2; params2["root"] = chrootStore; - return std::make_shared(params2); + return LocalStore::makeLocalStore(params); } #endif else - return std::make_shared(params); + return LocalStore::makeLocalStore(params); } else if (uri == "daemon") { return std::make_shared(params); } else if (uri == "local") { - return std::make_shared(params); + return LocalStore::makeLocalStore(params); } else if (isNonUriPath(uri)) { Store::Params params2 = params; params2["root"] = absPath(uri); - return std::make_shared(params2); + return LocalStore::makeLocalStore(params2); } else { return nullptr; }