From 1c34b9fb8e92860d10c7c7f1c5ff2944f89d62d5 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Thu, 2 Feb 2023 20:28:52 -0500 Subject: [PATCH] WIP move settings from `Settings` to `LocalStoreConfig` Trying to do #5638 but this is not working very well at all. --- src/libstore/build/local-derivation-goal.cc | 54 ++--- src/libstore/build/worker.cc | 10 +- src/libstore/gc.cc | 6 +- src/libstore/globals.cc | 54 ----- src/libstore/globals.hh | 216 -------------------- src/libstore/local-store.cc | 21 +- src/libstore/local-store.hh | 16 +- src/libstore/local-store/config.cc | 107 ++++++++++ src/libstore/local-store/config.hh | 210 +++++++++++++++++++ src/libstore/local.mk | 14 +- src/libstore/store-api.cc | 2 + src/libstore/store-api.hh | 11 +- src/libutil/config.cc | 27 +++ src/libutil/config.hh | 4 + src/nix-store/nix-store.cc | 3 +- 15 files changed, 428 insertions(+), 327 deletions(-) create mode 100644 src/libstore/local-store/config.cc create mode 100644 src/libstore/local-store/config.hh diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index 8ff83f748d9..0d5b9584f68 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -62,10 +62,11 @@ namespace nix { void handleDiffHook( uid_t uid, uid_t gid, const Path & tryA, const Path & tryB, - const Path & drvPath, const Path & tmpDir) + const Path & drvPath, const Path & tmpDir, + const LocalStore & store) { - auto diffHook = settings.diffHook; - if (diffHook != "" && settings.runDiffHook) { + auto diffHook = store.diffHook; + if (diffHook != "" && store.runDiffHook) { try { auto diffRes = runProgram(RunOptions { .program = diffHook, @@ -176,10 +177,12 @@ void LocalDerivationGoal::tryLocalBuild() { return; } + auto & localStore = getLocalStore(); + /* Are we doing a chroot build? */ { auto noChroot = parsedDrv->getBoolAttr("__noChroot"); - if (settings.sandboxMode == smEnabled) { + if (localStore.sandboxMode == smEnabled) { if (noChroot) throw Error("derivation '%s' has '__noChroot' set, " "but that's not allowed when 'sandbox' is 'true'", worker.store.printStorePath(drvPath)); @@ -190,13 +193,12 @@ void LocalDerivationGoal::tryLocalBuild() { #endif useChroot = true; } - else if (settings.sandboxMode == smDisabled) + else if (localStore.sandboxMode == smDisabled) useChroot = false; - else if (settings.sandboxMode == smRelaxed) + else if (localStore.sandboxMode == smRelaxed) useChroot = derivationType.isSandboxed() && !noChroot; } - auto & localStore = getLocalStore(); if (localStore.storeDir != localStore.realStoreDir.get()) { #if __linux__ useChroot = true; @@ -399,6 +401,8 @@ static void linkOrCopy(const Path & from, const Path & to) void LocalDerivationGoal::startBuilder() { + auto & localStore = getLocalStore(); + if ((buildUser && buildUser->getUIDCount() != 1) #if __linux__ || settings.useCgroups @@ -573,7 +577,7 @@ void LocalDerivationGoal::startBuilder() host file system. */ dirsInChroot.clear(); - for (auto i : settings.sandboxPaths.get()) { + for (auto i : localStore.sandboxPaths.get()) { if (i.empty()) continue; bool optional = false; if (i[i.size() - 1] == '?') { @@ -604,7 +608,7 @@ void LocalDerivationGoal::startBuilder() dirsInChroot.insert_or_assign(p, p); } - PathSet allowedPaths = settings.allowedImpureHostPrefixes; + PathSet allowedPaths = localStore.allowedImpureHostPrefixes; /* This works like the above, except on a per-derivation level */ auto impurePaths = parsedDrv->getStringsAttr("__impureHostDeps").value_or(Strings()); @@ -744,9 +748,9 @@ void LocalDerivationGoal::startBuilder() if (needsHashRewrite() && pathExists(homeDir)) throw Error("home directory '%1%' exists; please remove it to assure purity of builds without sandboxing", homeDir); - if (useChroot && settings.preBuildHook != "" && dynamic_cast(drv.get())) { + if (useChroot && localStore.preBuildHook != "" && dynamic_cast(drv.get())) { printMsg(lvlChatty, format("executing pre-build hook '%1%'") - % settings.preBuildHook); + % localStore.preBuildHook); auto args = useChroot ? Strings({worker.store.printStorePath(drvPath), chrootRootDir}) : Strings({ worker.store.printStorePath(drvPath) }); enum BuildHookState { @@ -754,7 +758,7 @@ void LocalDerivationGoal::startBuilder() stExtraChrootDirs }; auto state = stBegin; - auto lines = runProgram(settings.preBuildHook, false, args); + auto lines = runProgram(localStore.preBuildHook, false, args); auto lastPos = std::string::size_type{0}; for (auto nlPos = lines.find('\n'); nlPos != std::string::npos; nlPos = lines.find('\n', lastPos)) @@ -956,7 +960,7 @@ void LocalDerivationGoal::startBuilder() /* Otherwise exit with EPERM so we can handle this in the parent. This is only done when sandbox-fallback is set to true (the default). */ - if (settings.sandboxFallback) + if (localStore.sandboxFallback) _exit(1); /* Mention sandbox-fallback in the error message so the user knows that having it disabled contributed to the @@ -973,7 +977,7 @@ void LocalDerivationGoal::startBuilder() }); int res = helper.wait(); - if (res != 0 && settings.sandboxFallback) { + if (res != 0 && localStore.sandboxFallback) { useChroot = false; initTmpDir(); goto fallback; @@ -1021,7 +1025,7 @@ void LocalDerivationGoal::startBuilder() "root:x:0:0:Nix build user:%3%:/noshell\n" "nixbld:x:%1%:%2%:Nix build user:%3%:/noshell\n" "nobody:x:65534:65534:Nobody:/:/noshell\n", - sandboxUid(), sandboxGid(), settings.sandboxBuildDir)); + sandboxUid(), sandboxGid(), localStore.sandboxBuildDir)); /* Save the mount- and user namespace of the child. We have to do this *before* the child does a chroot. */ @@ -1090,7 +1094,8 @@ void LocalDerivationGoal::initTmpDir() { /* In a sandbox, for determinism, always use the same temporary directory. */ #if __linux__ - tmpDirInSandbox = useChroot ? settings.sandboxBuildDir : tmpDir; + const auto & localStore = getLocalStore(); + tmpDirInSandbox = useChroot ? localStore.sandboxBuildDir : tmpDir; #else tmpDirInSandbox = tmpDir; #endif @@ -1617,10 +1622,10 @@ void LocalDerivationGoal::chownToBuilder(const Path & path) } -void setupSeccomp() +void setupSeccomp(const LocalStoreConfig & localStore) { #if __linux__ - if (!settings.filterSyscalls) return; + if (!localStore.filterSyscalls) return; #if HAVE_SECCOMP scmp_filter_ctx ctx; @@ -1682,7 +1687,7 @@ void setupSeccomp() seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOTSUP), SCMP_SYS(fsetxattr), 0) != 0) throw SysError("unable to add seccomp rule"); - if (seccomp_attr_set(ctx, SCMP_FLTATR_CTL_NNP, settings.allowNewPrivileges ? 0 : 1) != 0) + if (seccomp_attr_set(ctx, SCMP_FLTATR_CTL_NNP, localStore.allowNewPrivileges ? 0 : 1) != 0) throw SysError("unable to set 'no new privileges' seccomp attribute"); if (seccomp_load(ctx) != 0) @@ -1708,7 +1713,7 @@ void LocalDerivationGoal::runChild() commonChildInit(builderOut); try { - setupSeccomp(); + setupSeccomp(getLocalStore()); } catch (...) { if (buildUser) throw; } @@ -1726,6 +1731,8 @@ void LocalDerivationGoal::runChild() #if __linux__ if (useChroot) { + auto & localStore = getLocalStore(); + userNamespaceSync.writeSide = -1; if (drainFD(userNamespaceSync.readSide.get()) != "1") @@ -1880,7 +1887,7 @@ void LocalDerivationGoal::runChild() /* Mount a new tmpfs on /dev/shm to ensure that whatever the builder puts in /dev/shm is cleaned up automatically. */ if (pathExists("/dev/shm") && mount("none", (chrootRootDir + "/dev/shm").c_str(), "tmpfs", 0, - fmt("size=%s", settings.sandboxShmSize).c_str()) == -1) + fmt("size=%s", localStore.sandboxShmSize).c_str()) == -1) throw SysError("mounting /dev/shm"); /* Mount a new devpts on /dev/pts. Note that this @@ -2645,7 +2652,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs() ValidPathInfo oldInfo(*worker.store.queryPathInfo(newInfo.path)); if (newInfo.narHash != oldInfo.narHash) { worker.checkMismatch = true; - if (settings.runDiffHook || settings.keepFailed) { + if (localStore.runDiffHook || settings.keepFailed) { auto dst = worker.store.toRealPath(finalDestPath + checkSuffix); deletePath(dst); movePath(actualPath, dst); @@ -2653,7 +2660,8 @@ DrvOutputs LocalDerivationGoal::registerOutputs() handleDiffHook( buildUser ? buildUser->getUID() : getuid(), buildUser ? buildUser->getGID() : getgid(), - finalDestPath, dst, worker.store.printStorePath(drvPath), tmpDir); + finalDestPath, dst, worker.store.printStorePath(drvPath), tmpDir, + localStore); throw NotDeterministic("derivation '%s' may not be deterministic: output '%s' differs from '%s'", worker.store.printStorePath(drvPath), worker.store.toRealPath(finalDestPath), dst); diff --git a/src/libstore/build/worker.cc b/src/libstore/build/worker.cc index f775f8486a0..ab0e4bcc2ae 100644 --- a/src/libstore/build/worker.cc +++ b/src/libstore/build/worker.cc @@ -251,7 +251,7 @@ void Worker::run(const Goals & _topGoals) checkInterrupt(); // TODO GC interface? - if (auto localStore = dynamic_cast(&store)) + if (auto * localStore = dynamic_cast(&store)) localStore->autoGC(false); /* Call every wake goal (in the ordering established by @@ -318,9 +318,11 @@ void Worker::waitForInput() is a build timeout, then wait for input until the first deadline for any child. */ auto nearest = steady_time_point::max(); // nearest deadline - if (settings.minFree.get() != 0) - // Periodicallty wake up to see if we need to run the garbage collector. - nearest = before + std::chrono::seconds(10); + // TODO GC interface? + if (auto * localStore = dynamic_cast(&store)) + if (localStore->minFree.get() != 0) + // Periodicallty wake up to see if we need to run the garbage collector. + nearest = before + std::chrono::seconds(10); for (auto & i : children) { if (!i.respectTimeouts) continue; if (0 != settings.maxSilentTime) diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc index 996f26a95c9..55b27696b7c 100644 --- a/src/libstore/gc.cc +++ b/src/libstore/gc.cc @@ -914,13 +914,13 @@ void LocalStore::autoGC(bool sync) auto now = std::chrono::steady_clock::now(); - if (now < state->lastGCCheck + std::chrono::seconds(settings.minFreeCheckInterval)) return; + if (now < state->lastGCCheck + std::chrono::seconds(minFreeCheckInterval)) return; auto avail = getAvail(); state->lastGCCheck = now; - if (avail >= settings.minFree || avail >= settings.maxFree) return; + if (avail >= minFree || avail >= maxFree) return; if (avail > state->availAfterGC * 0.97) return; @@ -942,7 +942,7 @@ void LocalStore::autoGC(bool sync) }); GCOptions options; - options.maxFreed = settings.maxFree - avail; + options.maxFreed = this->maxFree - avail; printInfo("running auto-GC to free %d bytes", options.maxFreed); diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc index 8e33a3deca6..ef2ff4f0756 100644 --- a/src/libstore/globals.cc +++ b/src/libstore/globals.cc @@ -42,7 +42,6 @@ Settings::Settings() { buildUsersGroup = getuid() == 0 ? "nixbld" : ""; lockCPU = getEnv("NIX_AFFINITY_HACK") == "1"; - allowSymlinkedStore = getEnv("NIX_IGNORE_SYMLINK_STORE") == "1"; caFile = getEnv("NIX_SSL_CERT_FILE").value_or(getEnv("SSL_CERT_FILE").value_or("")); if (caFile == "") { @@ -62,10 +61,6 @@ Settings::Settings() builders = concatStringsSep(" ", ss); } -#if defined(__linux__) && defined(SANDBOX_SHELL) - sandboxPaths = tokenizeString("/bin/sh=" SANDBOX_SHELL); -#endif - /* chroot-like behavior from Apple's sandbox */ #if __APPLE__ sandboxPaths = tokenizeString("/System/Library/Frameworks /System/Library/PrivateFrameworks /bin/sh /bin/bash /private/tmp /private/var/tmp /usr/lib"); @@ -189,55 +184,6 @@ bool Settings::isWSL1() const std::string nixVersion = PACKAGE_VERSION; -NLOHMANN_JSON_SERIALIZE_ENUM(SandboxMode, { - {SandboxMode::smEnabled, true}, - {SandboxMode::smRelaxed, "relaxed"}, - {SandboxMode::smDisabled, false}, -}); - -template<> void BaseSetting::set(const std::string & str, bool append) -{ - if (str == "true") value = smEnabled; - else if (str == "relaxed") value = smRelaxed; - else if (str == "false") value = smDisabled; - else throw UsageError("option '%s' has invalid value '%s'", name, str); -} - -template<> bool BaseSetting::isAppendable() -{ - return false; -} - -template<> std::string BaseSetting::to_string() const -{ - if (value == smEnabled) return "true"; - else if (value == smRelaxed) return "relaxed"; - else if (value == smDisabled) return "false"; - else abort(); -} - -template<> void BaseSetting::convertToArg(Args & args, const std::string & category) -{ - args.addFlag({ - .longName = name, - .description = "Enable sandboxing.", - .category = category, - .handler = {[this]() { override(smEnabled); }} - }); - args.addFlag({ - .longName = "no-" + name, - .description = "Disable sandboxing.", - .category = category, - .handler = {[this]() { override(smDisabled); }} - }); - args.addFlag({ - .longName = "relaxed-" + name, - .description = "Enable sandboxing, but allow builds to disable it.", - .category = category, - .handler = {[this]() { override(smRelaxed); }} - }); -} - void MaxBuildJobsSetting::set(const std::string & str, bool append) { if (str == "auto") value = std::max(1U, std::thread::hardware_concurrency()); diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index 42981219d61..12580aa9e62 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -12,8 +12,6 @@ namespace nix { -typedef enum { smEnabled, smRelaxed, smDisabled } SandboxMode; - struct MaxBuildJobsSetting : public BaseSetting { MaxBuildJobsSetting(Config * options, @@ -445,126 +443,6 @@ public: /* Whether to lock the Nix client and worker to the same CPU. */ bool lockCPU; - Setting sandboxMode{ - this, - #if __linux__ - smEnabled - #else - smDisabled - #endif - , "sandbox", - R"( - If set to `true`, builds will be performed in a *sandboxed - environment*, i.e., they’re isolated from the normal file system - hierarchy and will only see their dependencies in the Nix store, - the temporary build directory, private versions of `/proc`, - `/dev`, `/dev/shm` and `/dev/pts` (on Linux), and the paths - configured with the `sandbox-paths` option. This is useful to - prevent undeclared dependencies on files in directories such as - `/usr/bin`. In addition, on Linux, builds run in private PID, - mount, network, IPC and UTS namespaces to isolate them from other - processes in the system (except that fixed-output derivations do - not run in private network namespace to ensure they can access the - network). - - Currently, sandboxing only work on Linux and macOS. The use of a - sandbox requires that Nix is run as root (so you should use the - “build users” feature to perform the actual builds under different - users than root). - - If this option is set to `relaxed`, then fixed-output derivations - and derivations that have the `__noChroot` attribute set to `true` - do not run in sandboxes. - - The default is `true` on Linux and `false` on all other platforms. - )", - {"build-use-chroot", "build-use-sandbox"}}; - - Setting sandboxPaths{ - this, {}, "sandbox-paths", - R"( - A list of paths bind-mounted into Nix sandbox environments. You can - use the syntax `target=source` to mount a path in a different - location in the sandbox; for instance, `/bin=/nix-bin` will mount - the path `/nix-bin` as `/bin` inside the sandbox. If *source* is - followed by `?`, then it is not an error if *source* does not exist; - for example, `/dev/nvidiactl?` specifies that `/dev/nvidiactl` will - only be mounted in the sandbox if it exists in the host filesystem. - - If the source is in the Nix store, then its closure will be added to - the sandbox as well. - - Depending on how Nix was built, the default value for this option - may be empty or provide `/bin/sh` as a bind-mount of `bash`. - )", - {"build-chroot-dirs", "build-sandbox-paths"}}; - - Setting sandboxFallback{this, true, "sandbox-fallback", - "Whether to disable sandboxing when the kernel doesn't allow it."}; - -#if __linux__ - Setting sandboxShmSize{ - this, "50%", "sandbox-dev-shm-size", - R"( - This option determines the maximum size of the `tmpfs` filesystem - mounted on `/dev/shm` in Linux sandboxes. For the format, see the - description of the `size` option of `tmpfs` in mount8. The default - is `50%`. - )"}; - - Setting sandboxBuildDir{this, "/build", "sandbox-build-dir", - "The build directory inside the sandbox."}; -#endif - - Setting allowedImpureHostPrefixes{this, {}, "allowed-impure-host-deps", - "Which prefixes to allow derivations to ask for access to (primarily for Darwin)."}; - -#if __APPLE__ - Setting darwinLogSandboxViolations{this, false, "darwin-log-sandbox-violations", - "Whether to log Darwin sandbox access violations to the system log."}; -#endif - - Setting runDiffHook{ - this, false, "run-diff-hook", - R"( - If true, enable the execution of the `diff-hook` program. - - When using the Nix daemon, `run-diff-hook` must be set in the - `nix.conf` configuration file, and cannot be passed at the command - line. - )"}; - - PathSetting diffHook{ - this, true, "", "diff-hook", - R"( - Absolute path to an executable capable of diffing build - results. The hook is executed if `run-diff-hook` is true, and the - output of a build is known to not be the same. This program is not - executed to determine if two results are the same. - - The diff hook is executed by the same user and group who ran the - build. However, the diff hook does not have write access to the - store path just built. - - The diff hook program receives three parameters: - - 1. A path to the previous build's results - - 2. A path to the current build's results - - 3. The path to the build's derivation - - 4. The path to the build's scratch directory. This directory will - exist only if the build was run with `--keep-failed`. - - The stderr and stdout output from the diff hook will not be - displayed to the user. Instead, it will print to the nix-daemon's - log. - - When using the Nix daemon, `diff-hook` must be set in the `nix.conf` - configuration file, and cannot be passed at the command line. - )"}; - Setting trustedPublicKeys{ this, {"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="}, @@ -609,22 +487,6 @@ public: `fetchTarball`, and `fetchurl` respect this TTL. )"}; - Setting requireSigs{ - this, true, "require-sigs", - R"( - If set to `true` (the default), any non-content-addressed path added - or copied to the Nix store (e.g. when substituting from a binary - cache) must have a signature by a trusted key. A trusted key is one - listed in `trusted-public-keys`, or a public key counterpart to a - private key stored in a file listed in `secret-key-files`. - - Set to `false` to disable signature checking and trust all - non-content-addressed paths unconditionally. - - (Content-addressed paths are inherently trustworthy and thus - unaffected by this configuration option.) - )"}; - Setting extraPlatforms{ this, getDefaultExtraPlatforms(), @@ -721,25 +583,6 @@ public: Setting printMissing{this, true, "print-missing", "Whether to print what paths need to be built or downloaded."}; - Setting preBuildHook{ - this, "", "pre-build-hook", - R"( - If set, the path to a program that can set extra derivation-specific - settings for this system. This is used for settings that can't be - captured by the derivation model itself and are too variable between - different versions of the same system to be hard-coded into nix. - - The hook is passed the derivation path and, if sandboxes are - enabled, the sandbox directory. It can then modify the sandbox and - send a series of commands to modify various settings to stdout. The - currently recognized commands are: - - - `extra-sandbox-paths`\ - Pass a list of files and directories to be included in the - sandbox for this build. One entry per line, terminated by an - empty line. Entries have the same format as `sandbox-paths`. - )"}; - Setting postBuildHook{ this, "", "post-build-hook", R"( @@ -821,29 +664,6 @@ public: Path caFile; #if __linux__ - Setting filterSyscalls{ - this, true, "filter-syscalls", - R"( - Whether to prevent certain dangerous system calls, such as - creation of setuid/setgid files or adding ACLs or extended - attributes. Only disable this if you're aware of the - security implications. - )"}; - - Setting allowNewPrivileges{ - this, false, "allow-new-privileges", - R"( - (Linux-specific.) By default, builders on Linux cannot acquire new - privileges by calling setuid/setgid programs or programs that have - file capabilities. For example, programs such as `sudo` or `ping` - will fail. (Note that in sandbox builds, no such programs are - available unless you bind-mount them into the sandbox via the - `sandbox-paths` option.) You can allow the use of such programs by - enabling this option. This is impure and usually undesirable, but - may be useful in certain scenarios (e.g. to spin up containers or - set up userspace network interfaces in tests). - )"}; - Setting ignoredAcls{ this, {"security.selinux", "system.nfs4_acl", "security.csm"}, "ignored-acls", R"( @@ -877,26 +697,6 @@ public: first. If it is not available there, if will try the original URI. )"}; - Setting minFree{ - this, 0, "min-free", - R"( - When free disk space in `/nix/store` drops below `min-free` during a - build, Nix performs a garbage-collection until `max-free` bytes are - available or there is no more garbage. A value of `0` (the default) - disables this feature. - )"}; - - Setting maxFree{ - this, std::numeric_limits::max(), "max-free", - R"( - When a garbage collection is triggered by the `min-free` option, it - stops as soon as `max-free` bytes are available. The default is - infinity (i.e. delete all garbage). - )"}; - - Setting minFreeCheckInterval{this, 5, "min-free-check-interval", - "Number of seconds between checking free disk space."}; - PluginFilesSetting pluginFiles{ this, {}, "plugin-files", R"( @@ -929,22 +729,6 @@ public: bool isExperimentalFeatureEnabled(const ExperimentalFeature &); void requireExperimentalFeature(const ExperimentalFeature &); - - Setting narBufferSize{this, 32 * 1024 * 1024, "nar-buffer-size", - "Maximum size of NARs before spilling them to disk."}; - - Setting allowSymlinkedStore{ - this, false, "allow-symlinked-store", - R"( - If set to `true`, Nix will stop complaining if the store directory - (typically /nix/store) contains symlink components. - - This risks making some builds "impure" because builders sometimes - "canonicalise" paths by resolving all symlink components. Problems - occur if those builds are then deployed to machines where /nix/store - resolves to a different location from that of the build machine. You - can enable this setting if you are sure you're not going to do that. - )"}; }; diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index 82edaa9bfaa..2f812dd3b1b 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -164,6 +164,20 @@ void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd) } } +static StringMap withRoot(const StringMap & params, std::string rootDir) +{ + StringMap params2 { params }; + params2["root"] = rootDir; + return params2; +} + +LocalStore::LocalStore( + const std::string scheme, + std::string rootDir, + const Params & params) + : LocalStore(withRoot(params, rootDir)) +{ } + LocalStore::LocalStore(const Params & params) : StoreConfig(params) , LocalFSStoreConfig(params) @@ -224,7 +238,7 @@ LocalStore::LocalStore(const Params & params) } /* Ensure that the store and its parents are not symlinks. */ - if (!settings.allowSymlinkedStore) { + if (!allowSymlinkedStore) { Path path = realStoreDir; struct stat st; while (path != "/") { @@ -1370,10 +1384,10 @@ StorePath LocalStore::addToStoreFromDump(Source & source0, std::string_view name /* Fill out buffer, and decide whether we are working strictly in memory based on whether we break out because the buffer is full or the original source is empty */ - while (dump.size() < settings.narBufferSize) { + while (dump.size() < narBufferSize) { auto oldSize = dump.size(); constexpr size_t chunkSize = 65536; - auto want = std::min(chunkSize, settings.narBufferSize - oldSize); + auto want = std::min(chunkSize, narBufferSize - oldSize); dump.resize(oldSize + want); auto got = 0; Finally cleanup([&]() { @@ -1950,5 +1964,6 @@ std::optional LocalStore::getVersion() return nixVersion; } +static RegisterStoreImplementation regLocalStoreStore; } // namespace nix diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh index a84eb7c26ef..71143a3d562 100644 --- a/src/libstore/local-store.hh +++ b/src/libstore/local-store.hh @@ -8,6 +8,7 @@ #include "gc-store.hh" #include "sync.hh" #include "util.hh" +#include "local-store/config.hh" #include #include @@ -32,17 +33,6 @@ struct OptimiseStats uint64_t blocksFreed = 0; }; -struct LocalStoreConfig : virtual LocalFSStoreConfig -{ - using LocalFSStoreConfig::LocalFSStoreConfig; - - Setting requireSigs{(StoreConfig*) this, - settings.requireSigs, - "require-sigs", "whether store paths should have a trusted signature on import"}; - - const std::string name() override { return "Local Store"; } -}; - class LocalStore : public virtual LocalStoreConfig, public virtual LocalFSStore, public virtual GcStore { @@ -100,6 +90,7 @@ public: /* Initialise the local store, upgrading the schema if necessary. */ LocalStore(const Params & params); + LocalStore(const std::string scheme, std::string path, const Params & params); ~LocalStore(); @@ -107,6 +98,9 @@ public: std::string getUri() override; + static std::set uriSchemes() + { return {"local"}; } + bool isValidPathUncached(const StorePath & path) override; StorePathSet queryValidPaths(const StorePathSet & paths, diff --git a/src/libstore/local-store/config.cc b/src/libstore/local-store/config.cc new file mode 100644 index 00000000000..878738ee8fc --- /dev/null +++ b/src/libstore/local-store/config.cc @@ -0,0 +1,107 @@ +#include + +#include "args.hh" +#include "local-store/config.hh" +#include "abstract-setting-to-json.hh" + +namespace nix { + +NLOHMANN_JSON_SERIALIZE_ENUM(SandboxMode, { + {SandboxMode::smEnabled, true}, + {SandboxMode::smRelaxed, "relaxed"}, + {SandboxMode::smDisabled, false}, +}); + +template<> void BaseSetting::set(const std::string & str, bool append) +{ + if (str == "true") value = smEnabled; + else if (str == "relaxed") value = smRelaxed; + else if (str == "false") value = smDisabled; + else throw UsageError("option '%s' has invalid value '%s'", name, str); +} + +template<> bool BaseSetting::isAppendable() +{ + return false; +} + +template<> std::string BaseSetting::to_string() const +{ + if (value == smEnabled) return "true"; + else if (value == smRelaxed) return "relaxed"; + else if (value == smDisabled) return "false"; + else abort(); +} + +template<> void BaseSetting::convertToArg(Args & args, const std::string & category) +{ + args.addFlag({ + .longName = name, + .description = "Enable sandboxing.", + .category = category, + .handler = {[this]() { override(smEnabled); }} + }); + args.addFlag({ + .longName = "no-" + name, + .description = "Disable sandboxing.", + .category = category, + .handler = {[this]() { override(smDisabled); }} + }); + args.addFlag({ + .longName = "relaxed-" + name, + .description = "Enable sandboxing, but allow builds to disable it.", + .category = category, + .handler = {[this]() { override(smRelaxed); }} + }); +} + +template class BaseSetting; + +LocalStoreConfig::LocalStoreConfig(const Params & params) + : StoreConfig(params) + , LocalFSStoreConfig(params) + , sandboxPaths{ + (StoreConfig*) this, +#if defined(__linux__) && defined(SANDBOX_SHELL) + tokenizeString("/bin/sh=" SANDBOX_SHELL), +#else + {}, +#endif + "sandbox-paths", + R"( + A list of paths bind-mounted into Nix sandbox environments. You can + use the syntax `target=source` to mount a path in a different + location in the sandbox; for instance, `/bin=/nix-bin` will mount + the path `/nix-bin` as `/bin` inside the sandbox. If *source* is + followed by `?`, then it is not an error if *source* does not exist; + for example, `/dev/nvidiactl?` specifies that `/dev/nvidiactl` will + only be mounted in the sandbox if it exists in the host filesystem. + + If the source is in the Nix store, then its closure will be added to + the sandbox as well. + + Depending on how Nix was built, the default value for this option + may be empty or provide `/bin/sh` as a bind-mount of `bash`. + )", + {"build-chroot-dirs", "build-sandbox-paths"}} + , allowSymlinkedStore{ + (StoreConfig*) this, + getEnv("NIX_IGNORE_SYMLINK_STORE") == "1", + "allow-symlinked-store", + R"( + If set to `true`, Nix will stop complaining if the store directory + (typically /nix/store) contains symlink components. + + This risks making some builds "impure" because builders sometimes + "canonicalise" paths by resolving all symlink components. Problems + occur if those builds are then deployed to machines where /nix/store + resolves to a different location from that of the build machine. You + can enable this setting if you are sure you're not going to do that. + )"} +{ } + +//LocalStoreConfig hack { StringMap {} }; + +//GlobalConfig::Register rHack(&hack); + +} diff --git a/src/libstore/local-store/config.hh b/src/libstore/local-store/config.hh new file mode 100644 index 00000000000..2af6228fda7 --- /dev/null +++ b/src/libstore/local-store/config.hh @@ -0,0 +1,210 @@ +#pragma once + +#include "config.hh" +#include "store-api.hh" +#include "local-fs-store.hh" +#include "gc-store.hh" + +namespace nix { + +typedef enum { smEnabled, smRelaxed, smDisabled } SandboxMode; + +struct LocalStoreConfig : virtual LocalFSStoreConfig +{ + LocalStoreConfig(const Params &); + + const std::string name() override { return "Local Store"; } + + Setting sandboxMode{ + (StoreConfig*) this, + #if __linux__ + smEnabled + #else + smDisabled + #endif + , "sandbox", + R"( + If set to `true`, builds will be performed in a *sandboxed + environment*, i.e., they’re isolated from the normal file system + hierarchy and will only see their dependencies in the Nix store, + the temporary build directory, private versions of `/proc`, + `/dev`, `/dev/shm` and `/dev/pts` (on Linux), and the paths + configured with the `sandbox-paths` option. This is useful to + prevent undeclared dependencies on files in directories such as + `/usr/bin`. In addition, on Linux, builds run in private PID, + mount, network, IPC and UTS namespaces to isolate them from other + processes in the system (except that fixed-output derivations do + not run in private network namespace to ensure they can access the + network). + + Currently, sandboxing only work on Linux and macOS. The use of a + sandbox requires that Nix is run as root (so you should use the + “build users” feature to perform the actual builds under different + users than root). + + If this option is set to `relaxed`, then fixed-output derivations + and derivations that have the `__noChroot` attribute set to `true` + do not run in sandboxes. + + The default is `true` on Linux and `false` on all other platforms. + )", + {"build-use-chroot", "build-use-sandbox"}}; + + Setting sandboxPaths; + + Setting sandboxFallback{this, true, "sandbox-fallback", + "Whether to disable sandboxing when the kernel doesn't allow it."}; + +#if __linux__ + Setting sandboxShmSize{ + (StoreConfig*) this, "50%", "sandbox-dev-shm-size", + R"( + This option determines the maximum size of the `tmpfs` filesystem + mounted on `/dev/shm` in Linux sandboxes. For the format, see the + description of the `size` option of `tmpfs` in mount8. The default + is `50%`. + )"}; + + Setting sandboxBuildDir{this, "/build", "sandbox-build-dir", + "The build directory inside the sandbox."}; +#endif + + Setting allowedImpureHostPrefixes{this, {}, "allowed-impure-host-deps", + "Which prefixes to allow derivations to ask for access to (primarily for Darwin)."}; + +#if __APPLE__ + Setting darwinLogSandboxViolations{this, false, "darwin-log-sandbox-violations", + "Whether to log Darwin sandbox access violations to the system log."}; +#endif + + Setting runDiffHook{ + (StoreConfig*) this, false, "run-diff-hook", + R"( + If true, enable the execution of the `diff-hook` program. + + When using the Nix daemon, `run-diff-hook` must be set in the + `nix.conf` configuration file, and cannot be passed at the command + line. + )"}; + + PathSetting diffHook{ + (StoreConfig*) this, true, "", "diff-hook", + R"( + Absolute path to an executable capable of diffing build + results. The hook is executed if `run-diff-hook` is true, and the + output of a build is known to not be the same. This program is not + executed to determine if two results are the same. + + The diff hook is executed by the same user and group who ran the + build. However, the diff hook does not have write access to the + store path just built. + + The diff hook program receives three parameters: + + 1. A path to the previous build's results + + 2. A path to the current build's results + + 3. The path to the build's derivation + + 4. The path to the build's scratch directory. This directory will + exist only if the build was run with `--keep-failed`. + + The stderr and stdout output from the diff hook will not be + displayed to the user. Instead, it will print to the nix-daemon's + log. + + When using the Nix daemon, `diff-hook` must be set in the `nix.conf` + configuration file, and cannot be passed at the command line. + )"}; + + Setting requireSigs{ + (StoreConfig*) this, true, "require-sigs", + R"( + If set to `true` (the default), any non-content-addressed path added + or copied to the Nix store (e.g. when substituting from a binary + cache) must have a signature by a trusted key. A trusted key is one + listed in `trusted-public-keys`, or a public key counterpart to a + private key stored in a file listed in `secret-key-files`. + + Set to `false` to disable signature checking and trust all + non-content-addressed paths unconditionally. + + (Content-addressed paths are inherently trustworthy and thus + unaffected by this configuration option.) + )"}; + + Setting preBuildHook{ + (StoreConfig*) this, "", "pre-build-hook", + R"( + If set, the path to a program that can set extra derivation-specific + settings for this system. This is used for settings that can't be + captured by the derivation model itself and are too variable between + different versions of the same system to be hard-coded into nix. + + The hook is passed the derivation path and, if sandboxes are + enabled, the sandbox directory. It can then modify the sandbox and + send a series of commands to modify various settings to stdout. The + currently recognized commands are: + + - `extra-sandbox-paths`\ + Pass a list of files and directories to be included in the + sandbox for this build. One entry per line, terminated by an + empty line. Entries have the same format as `sandbox-paths`. + )"}; + +#if __linux__ + Setting filterSyscalls{ + (StoreConfig*) this, true, "filter-syscalls", + R"( + Whether to prevent certain dangerous system calls, such as + creation of setuid/setgid files or adding ACLs or extended + attributes. Only disable this if you're aware of the + security implications. + )"}; + + Setting allowNewPrivileges{ + (StoreConfig*) this, false, "allow-new-privileges", + R"( + (Linux-specific.) By default, builders on Linux cannot acquire new + privileges by calling setuid/setgid programs or programs that have + file capabilities. For example, programs such as `sudo` or `ping` + will fail. (Note that in sandbox builds, no such programs are + available unless you bind-mount them into the sandbox via the + `sandbox-paths` option.) You can allow the use of such programs by + enabling this option. This is impure and usually undesirable, but + may be useful in certain scenarios (e.g. to spin up containers or + set up userspace network interfaces in tests). + )"}; +#endif + + Setting minFree{ + (StoreConfig*) this, 0, "min-free", + R"( + When free disk space in `/nix/store` drops below `min-free` during a + build, Nix performs a garbage-collection until `max-free` bytes are + available or there is no more garbage. A value of `0` (the default) + disables this feature. + )"}; + + Setting maxFree{ + (StoreConfig*) this, std::numeric_limits::max(), "max-free", + R"( + When a garbage collection is triggered by the `min-free` option, it + stops as soon as `max-free` bytes are available. The default is + infinity (i.e. delete all garbage). + )"}; + + Setting minFreeCheckInterval{ + (StoreConfig*) this, 5, "min-free-check-interval", + "Number of seconds between checking free disk space."}; + + Setting narBufferSize{ + (StoreConfig*) this, 32 * 1024 * 1024, "nar-buffer-size", + "Maximum size of NARs before spilling them to disk."}; + + Setting allowSymlinkedStore; + ; +}; + +} diff --git a/src/libstore/local.mk b/src/libstore/local.mk index e5e24501e52..79fff08cf7e 100644 --- a/src/libstore/local.mk +++ b/src/libstore/local.mk @@ -4,7 +4,11 @@ libstore_NAME = libnixstore libstore_DIR := $(d) -libstore_SOURCES := $(wildcard $(d)/*.cc $(d)/builtins/*.cc $(d)/build/*.cc) +libstore_SOURCES := \ + $(wildcard $(d)/*.cc) \ + $(wildcard $(d)/builtins/*.cc) \ + $(wildcard $(d)/build/*.cc) \ + $(wildcard $(d)/local-store/*.cc) libstore_LIBS = libutil @@ -67,8 +71,6 @@ clean-files += $(d)/schema.sql.gen.hh $(d)/ca-specific-schema.sql.gen.hh $(eval $(call install-file-in, $(d)/nix-store.pc, $(libdir)/pkgconfig, 0644)) -$(foreach i, $(wildcard src/libstore/builtins/*.hh), \ - $(eval $(call install-file-in, $(i), $(includedir)/nix/builtins, 0644))) - -$(foreach i, $(wildcard src/libstore/build/*.hh), \ - $(eval $(call install-file-in, $(i), $(includedir)/nix/build, 0644))) +$(foreach sd, builtins build local-store, \ + $(foreach i, $(wildcard src/libstore/$(sd)/*.hh), \ + $(eval $(call install-file-in, $(i), $(includedir)/nix/$(sd), 0644)))) diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc index 601efa1ccc4..48408967565 100644 --- a/src/libstore/store-api.cc +++ b/src/libstore/store-api.cc @@ -459,6 +459,8 @@ Store::Store(const Params & params) , state({(size_t) pathInfoCacheSize}) { assertLibStoreInitialized(); + GlobalConfig::registerWeak( + std::static_ptr_cast(enable_weak_from_this())); } diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh index 4d8db35960d..a2405ef599d 100644 --- a/src/libstore/store-api.hh +++ b/src/libstore/store-api.hh @@ -91,6 +91,8 @@ typedef std::map> StorePathCAMap; struct StoreConfig : public Config { + typedef std::map Params; + using Config::Config; StoreConfig() = delete; @@ -121,12 +123,6 @@ struct StoreConfig : public Config class Store : public std::enable_shared_from_this, public virtual StoreConfig { -public: - - typedef std::map Params; - - - protected: struct PathInfoCacheValue { @@ -751,6 +747,9 @@ OutputPathMap resolveDerivedPath(Store &, const DerivedPath::Built &, Store * ev whether the user has write access to the local Nix store/database. + * ‘local://’: A local filesystem store store at that location but + (probably) mounted in the conventional location. + * ‘file://’: A binary cache stored in . * ‘https://’: A binary cache accessed via HTTP. diff --git a/src/libutil/config.cc b/src/libutil/config.cc index b349f2d80ed..eb919097bb0 100644 --- a/src/libutil/config.cc +++ b/src/libutil/config.cc @@ -391,6 +391,9 @@ bool GlobalConfig::set(const std::string & name, const std::string & value) { for (auto & config : *configRegistrations) if (config->set(name, value)) return true; + for (auto & w : *weakConfigRegistrations) + if (auto config = w.lock()) + if (config->set(name, value)) return true; unknownSettings.emplace(name, value); @@ -401,12 +404,18 @@ void GlobalConfig::getSettings(std::map & res, bool ov { for (auto & config : *configRegistrations) config->getSettings(res, overriddenOnly); + for (auto & w : *weakConfigRegistrations) + if (auto config = w.lock()) + config->getSettings(res, overriddenOnly); } void GlobalConfig::resetOverridden() { for (auto & config : *configRegistrations) config->resetOverridden(); + for (auto & w : *weakConfigRegistrations) + if (auto config = w.lock()) + config->resetOverridden(); } nlohmann::json GlobalConfig::toJSON() @@ -414,6 +423,9 @@ nlohmann::json GlobalConfig::toJSON() auto res = nlohmann::json::object(); for (auto & config : *configRegistrations) res.update(config->toJSON()); + for (auto & w : *weakConfigRegistrations) + if (auto config = w.lock()) + res.update(config->toJSON()); return res; } @@ -431,17 +443,32 @@ void GlobalConfig::convertToArgs(Args & args, const std::string & category) { for (auto & config : *configRegistrations) config->convertToArgs(args, category); + for (auto & w : *weakConfigRegistrations) + if (auto config = w.lock()) + config->convertToArgs(args, category); } GlobalConfig globalConfig; GlobalConfig::ConfigRegistrations * GlobalConfig::configRegistrations; +GlobalConfig::WeakConfigRegistrations * GlobalConfig::weakConfigRegistrations; GlobalConfig::Register::Register(Config * config) { if (!configRegistrations) configRegistrations = new ConfigRegistrations; + if (!weakConfigRegistrations) + weakConfigRegistrations = new WeakConfigRegistrations; configRegistrations->emplace_back(config); } +void GlobalConfig::registerWeak(std::weak_ptr config) +{ + if (!configRegistrations) + configRegistrations = new ConfigRegistrations; + if (!weakConfigRegistrations) + weakConfigRegistrations = new WeakConfigRegistrations; + weakConfigRegistrations->emplace_back(config); +} + } diff --git a/src/libutil/config.hh b/src/libutil/config.hh index 7ac43c85475..86e1898243c 100644 --- a/src/libutil/config.hh +++ b/src/libutil/config.hh @@ -336,6 +336,8 @@ struct GlobalConfig : public AbstractConfig { typedef std::vector ConfigRegistrations; static ConfigRegistrations * configRegistrations; + typedef std::vector> WeakConfigRegistrations; + static WeakConfigRegistrations * weakConfigRegistrations; bool set(const std::string & name, const std::string & value) override; @@ -353,6 +355,8 @@ struct GlobalConfig : public AbstractConfig { Register(Config * config); }; + + static void registerWeak(std::weak_ptr config); }; extern GlobalConfig globalConfig; diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index 3bbefedbe07..9bfb7eb78ac 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -820,7 +820,8 @@ static void opServe(Strings opFlags, Strings opArgs) // asked for. readInt(in); - settings.runDiffHook = true; + if (auto store2 = std::dynamic_pointer_cast(store)) + store2->runDiffHook = true; } if (GET_PROTOCOL_MINOR(clientVersion) >= 7) { settings.keepFailed = (bool) readInt(in);