diff --git a/perl/lib/Nix/CopyClosure.pm b/perl/lib/Nix/CopyClosure.pm index 902ee1a1bc9f..f8e3ce8a8d78 100644 --- a/perl/lib/Nix/CopyClosure.pm +++ b/perl/lib/Nix/CopyClosure.pm @@ -15,7 +15,7 @@ sub copyToOpen { $useSubstitutes = 0 if $dryRun || !defined $useSubstitutes; # Get the closure of this path. - my @closure = reverse(topoSortPaths(computeFSClosure(0, $includeOutputs, + my @closure = reverse(topoSortPaths(computeFSClosure($includeOutputs, map { followLinksToStorePath $_ } @{$storePaths}))); # Send the "query valid paths" command with the "lock" option diff --git a/perl/lib/Nix/Store.xs b/perl/lib/Nix/Store.xs index 4964b8a34062..172b9dcd4bc9 100644 --- a/perl/lib/Nix/Store.xs +++ b/perl/lib/Nix/Store.xs @@ -144,12 +144,12 @@ SV * queryPathFromHashPart(char * hashPart) } -SV * computeFSClosure(int flipDirection, int includeOutputs, ...) +SV * computeFSClosure(int includeOutputs, ...) PPCODE: try { StorePathSet paths; for (int n = 2; n < items; ++n) - store()->computeFSClosure(store()->parseStorePath(SvPV_nolen(ST(n))), paths, flipDirection, includeOutputs); + store()->computeFSClosure(store()->parseStorePath(SvPV_nolen(ST(n))), paths, includeOutputs); for (auto & i : paths) XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(i).c_str(), 0))); } catch (Error & e) { diff --git a/src/libcmd/command.cc b/src/libcmd/command.cc index 369fa600441c..a1530a17b089 100644 --- a/src/libcmd/command.cc +++ b/src/libcmd/command.cc @@ -1,7 +1,9 @@ #include "command.hh" #include "markdown.hh" #include "store-api.hh" +#include "store-cast.hh" #include "local-fs-store.hh" +#include "visible-store.hh" #include "derivations.hh" #include "nixexpr.hh" #include "profiles.hh" @@ -185,10 +187,11 @@ void BuiltPathsCommand::run(ref store, Installables && installables) { BuiltPaths paths; if (all) { + auto & visibleStore = require(*store); if (installables.size()) throw UsageError("'--all' does not expect arguments"); // XXX: Only uses opaque paths, ignores all the realisations - for (auto & p : store->queryAllValidPaths()) + for (auto & p : visibleStore.queryAllValidPaths()) paths.emplace_back(BuiltPath::Opaque{p}); } else { paths = Installable::toBuiltPaths(getEvalStore(), store, realiseMode, operateOn, installables); diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index b01d9e237717..51bcef58a47f 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -1224,10 +1224,16 @@ struct RestrictedStoreConfig : virtual LocalFSStoreConfig const std::string name() { return "Restricted Store"; } }; -/* A wrapper around LocalStore that only allows building/querying of - paths that are in the input closures of the build or were added via - recursive Nix calls. */ -struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual IndirectRootStore, public virtual GcStore +/** + * A wrapper around `LocalStore` that only allows building/querying of + * paths that are in the input closures of the build or were added via + * recursive Nix calls. + */ +struct RestrictedStore + : public virtual RestrictedStoreConfig + , public virtual IndirectRootStore + , public virtual GcStore + , public virtual ReferrersStore { ref next; diff --git a/src/libstore/daemon.cc b/src/libstore/daemon.cc index 923ea6447d24..c9481de6bd33 100644 --- a/src/libstore/daemon.cc +++ b/src/libstore/daemon.cc @@ -8,6 +8,7 @@ #include "gc-store.hh" #include "log-store.hh" #include "indirect-root-store.hh" +#include "referrers-store.hh" #include "path-with-outputs.hh" #include "finally.hh" #include "archive.hh" @@ -342,9 +343,10 @@ static void performOp(TunnelLogger * logger, ref store, if (op == WorkerProto::Op::QueryReferences) for (auto & i : store->queryPathInfo(path)->references) paths.insert(i); - else if (op == WorkerProto::Op::QueryReferrers) - store->queryReferrers(path, paths); - else if (op == WorkerProto::Op::QueryValidDerivers) + else if (op == WorkerProto::Op::QueryReferrers) { + auto & referrersStore = require(*store); + referrersStore.queryReferrers(path, paths); + } else if (op == WorkerProto::Op::QueryValidDerivers) paths = store->queryValidDerivers(path); else paths = store->queryDerivationOutputs(path); logger->stopWork(); @@ -814,7 +816,8 @@ static void performOp(TunnelLogger * logger, ref store, case WorkerProto::Op::QueryAllValidPaths: { logger->startWork(); - auto paths = store->queryAllValidPaths(); + auto & visibleStore = require(*store); + auto paths = visibleStore.queryAllValidPaths(); logger->stopWork(); WorkerProto::write(*store, wconn, paths); break; diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc index 2bd3a2edca05..e45346bd671f 100644 --- a/src/libstore/gc.cc +++ b/src/libstore/gc.cc @@ -710,7 +710,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results) try { StorePathSet closure; computeFSClosure(*path, closure, - /* flipDirection */ false, gcKeepOutputs, gcKeepDerivations); + gcKeepOutputs, gcKeepDerivations); for (auto & p : closure) alive.insert(p); } catch (InvalidPath &) { } diff --git a/src/libstore/legacy-ssh-store.cc b/src/libstore/legacy-ssh-store.cc index 06bef9d08cfc..bb2fff9a06b7 100644 --- a/src/libstore/legacy-ssh-store.cc +++ b/src/libstore/legacy-ssh-store.cc @@ -303,11 +303,11 @@ void LegacySSHStore::buildPaths(const std::vector & drvPaths, Build void LegacySSHStore::computeFSClosure(const StorePathSet & paths, - StorePathSet & out, bool flipDirection, + StorePathSet & out, bool includeOutputs, bool includeDerivers) { - if (flipDirection || includeDerivers) { - Store::computeFSClosure(paths, out, flipDirection, includeOutputs, includeDerivers); + if (includeDerivers) { + Store::computeFSClosure(paths, out, includeOutputs, includeDerivers); return; } diff --git a/src/libstore/legacy-ssh-store.hh b/src/libstore/legacy-ssh-store.hh index c5a3ce677721..343219f89f57 100644 --- a/src/libstore/legacy-ssh-store.hh +++ b/src/libstore/legacy-ssh-store.hh @@ -98,7 +98,7 @@ public: { unsupported("repairPath"); } void computeFSClosure(const StorePathSet & paths, - StorePathSet & out, bool flipDirection = false, + StorePathSet & out, bool includeOutputs = false, bool includeDerivers = false) override; StorePathSet queryValidPaths(const StorePathSet & paths, diff --git a/src/libstore/local-binary-cache-store.cc b/src/libstore/local-binary-cache-store.cc index 5481dd762e27..d6c6c4302aab 100644 --- a/src/libstore/local-binary-cache-store.cc +++ b/src/libstore/local-binary-cache-store.cc @@ -1,4 +1,5 @@ #include "binary-cache-store.hh" +#include "visible-store.hh" #include "globals.hh" #include "nar-info-disk-cache.hh" @@ -20,7 +21,7 @@ struct LocalBinaryCacheStoreConfig : virtual BinaryCacheStoreConfig } }; -class LocalBinaryCacheStore : public virtual LocalBinaryCacheStoreConfig, public virtual BinaryCacheStore +class LocalBinaryCacheStore : public virtual LocalBinaryCacheStoreConfig, public virtual BinaryCacheStore, public virtual VisibleStore { private: diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh index ba56d3ead125..6d7c29f4fa6a 100644 --- a/src/libstore/local-store.hh +++ b/src/libstore/local-store.hh @@ -6,6 +6,7 @@ #include "pathlocks.hh" #include "store-api.hh" #include "indirect-root-store.hh" +#include "referrers-store.hh" #include "sync.hh" #include @@ -69,6 +70,7 @@ struct LocalStoreConfig : virtual LocalFSStoreConfig class LocalStore : public virtual LocalStoreConfig , public virtual IndirectRootStore , public virtual GcStore + , public virtual ReferrersStore { private: diff --git a/src/libstore/misc.cc b/src/libstore/misc.cc index cc8ad3d02739..f68f86cb1942 100644 --- a/src/libstore/misc.cc +++ b/src/libstore/misc.cc @@ -3,6 +3,8 @@ #include "globals.hh" #include "local-store.hh" #include "store-api.hh" +#include "referrers-store.hh" +#include "store-cast.hh" #include "thread-pool.hh" #include "realisation.hh" #include "topo-sort.hh" @@ -12,52 +14,18 @@ namespace nix { -void Store::computeFSClosure(const StorePathSet & startPaths, - StorePathSet & paths_, bool flipDirection, bool includeOutputs, bool includeDerivers) -{ - std::function(const StorePath & path, std::future> &)> queryDeps; - if (flipDirection) - queryDeps = [&](const StorePath& path, - std::future> & fut) { - StorePathSet res; - StorePathSet referrers; - queryReferrers(path, referrers); - for (auto& ref : referrers) - if (ref != path) - res.insert(ref); - - if (includeOutputs) - for (auto& i : queryValidDerivers(path)) - res.insert(i); - - if (includeDerivers && path.isDerivation()) - for (auto& [_, maybeOutPath] : queryPartialDerivationOutputMap(path)) - if (maybeOutPath && isValidPath(*maybeOutPath)) - res.insert(*maybeOutPath); - return res; - }; - else - queryDeps = [&](const StorePath& path, - std::future> & fut) { - StorePathSet res; - auto info = fut.get(); - for (auto& ref : info->references) - if (ref != path) - res.insert(ref); - - if (includeOutputs && path.isDerivation()) - for (auto& [_, maybeOutPath] : queryPartialDerivationOutputMap(path)) - if (maybeOutPath && isValidPath(*maybeOutPath)) - res.insert(*maybeOutPath); - - if (includeDerivers && info->deriver && isValidPath(*info->deriver)) - res.insert(*info->deriver); - return res; - }; +typedef std::set QueryDeps(const StorePath & path, std::future> &); +static void computeClosure( + Store & store, + std::function queryDeps, + const StorePathSet & startPaths, + StorePathSet & paths_, + bool includeOutputs, bool includeDerivers) +{ computeClosure( startPaths, paths_, - [&](const StorePath& path, + [&](const StorePath & path, std::function>&)> processEdges) { std::promise> promise; @@ -70,17 +38,75 @@ void Store::computeFSClosure(const StorePathSet & startPaths, promise.set_exception(std::current_exception()); } }; - queryPathInfo(path, getDependencies); + store.queryPathInfo(path, getDependencies); processEdges(promise); }); } +void ReferrersStore::computeFSCoClosure(const StorePathSet & startPaths, + StorePathSet & paths_, bool includeOutputs, bool includeDerivers) +{ + std::function queryDeps; + queryDeps = [&](const StorePath & path, + std::future> & fut) { + StorePathSet res; + StorePathSet referrers; + queryReferrers(path, referrers); + for (auto& ref : referrers) + if (ref != path) + res.insert(ref); + + if (includeOutputs) + for (auto& i : queryValidDerivers(path)) + res.insert(i); + + if (includeDerivers && path.isDerivation()) + for (auto& [_, maybeOutPath] : queryPartialDerivationOutputMap(path)) + if (maybeOutPath && isValidPath(*maybeOutPath)) + res.insert(*maybeOutPath); + return res; + }; + computeClosure(*this, queryDeps, startPaths, paths_, includeOutputs, includeDerivers); +} + +void Store::computeFSClosure(const StorePathSet & startPaths, + StorePathSet & paths_, bool includeOutputs, bool includeDerivers) +{ + std::function queryDeps; + queryDeps = [&](const StorePath& path, + std::future> & fut) { + StorePathSet res; + auto info = fut.get(); + for (auto& ref : info->references) + if (ref != path) + res.insert(ref); + + if (includeOutputs && path.isDerivation()) + for (auto& [_, maybeOutPath] : queryPartialDerivationOutputMap(path)) + if (maybeOutPath && isValidPath(*maybeOutPath)) + res.insert(*maybeOutPath); + + if (includeDerivers && info->deriver && isValidPath(*info->deriver)) + res.insert(*info->deriver); + return res; + }; + computeClosure(*this, queryDeps, startPaths, paths_, includeOutputs, includeDerivers); +} + +void ReferrersStore::computeFSCoClosure(const StorePath & startPath, + StorePathSet & paths_, bool includeOutputs, bool includeDerivers) +{ + StorePathSet paths; + paths.insert(startPath); + computeFSCoClosure(paths, paths_, includeOutputs, includeDerivers); +} + void Store::computeFSClosure(const StorePath & startPath, - StorePathSet & paths_, bool flipDirection, bool includeOutputs, bool includeDerivers) + StorePathSet & paths_, bool includeOutputs, bool includeDerivers) { StorePathSet paths; paths.insert(startPath); - computeFSClosure(paths, paths_, flipDirection, includeOutputs, includeDerivers); + computeFSClosure(paths, paths_, includeOutputs, includeDerivers); } diff --git a/src/libstore/referrers-store.hh b/src/libstore/referrers-store.hh new file mode 100644 index 000000000000..b1a099c9e878 --- /dev/null +++ b/src/libstore/referrers-store.hh @@ -0,0 +1,56 @@ +#pragma once +///@file + +#include "visible-store.hh" + +namespace nix { + +/** + * A store that allows querying referrers. + * + * The referrers relation is the dual of the references relation, the + * latter being the "regular" one we are usually interested in. + * + * This is no inherent reason why this should be a subclass of + * `VisibleStore`; it just so happens that every extent store object we + * have to day that implements `queryReferrers()` also implements + * `queryAllValidPaths()`. If that ceases to be the case, we can revisit + * this; until this having this interface inheritance means fewer + * interface combinations to think about. + */ +struct ReferrersStore : virtual VisibleStore +{ + inline static std::string operationName = "Query referrers"; + + /** + * Queries the set of incoming FS references for a store path. + * The result is not cleared. + * + * @param path The path of the store object we care about incoming + * references to. + * + * @param [out] referrers The set in which to collect the referrers + * of `path`. + */ + virtual void queryReferrers(const StorePath & path, StorePathSet & referrers) = 0; + + /** + * @param [out] out Place in here the set of all store paths in the + * file system co-closure of `storePath`; that is, all paths than + * directly or indirectly refer from it. `out` is not cleared. + * + * Whereas `Store::computeFSClosure` uses the `references` relation, + * this function uses the dual of it which is the `referrers` + * relation. + */ + virtual void computeFSCoClosure(const StorePathSet & paths, + StorePathSet & out, + bool includeOutputs = false, bool includeDerivers = false); + + void computeFSCoClosure(const StorePath & path, + StorePathSet & out, + bool includeOutputs = false, bool includeDerivers = false); + +}; + +} diff --git a/src/libstore/remote-store.hh b/src/libstore/remote-store.hh index 87704985b4dc..f15fc1c47c93 100644 --- a/src/libstore/remote-store.hh +++ b/src/libstore/remote-store.hh @@ -7,6 +7,7 @@ #include "store-api.hh" #include "gc-store.hh" #include "log-store.hh" +#include "referrers-store.hh" namespace nix { @@ -38,7 +39,9 @@ struct RemoteStoreConfig : virtual StoreConfig class RemoteStore : public virtual RemoteStoreConfig, public virtual Store, public virtual GcStore, - public virtual LogStore + public virtual LogStore, + public virtual ReferrersStore + { public: diff --git a/src/libstore/s3-binary-cache-store.cc b/src/libstore/s3-binary-cache-store.cc index 1a62d92d44ab..fe7cb8dd61fb 100644 --- a/src/libstore/s3-binary-cache-store.cc +++ b/src/libstore/s3-binary-cache-store.cc @@ -2,6 +2,7 @@ #include "s3.hh" #include "s3-binary-cache-store.hh" +#include "visible-store.hh" #include "nar-info.hh" #include "nar-info-disk-cache.hh" #include "globals.hh" @@ -260,7 +261,7 @@ struct S3BinaryCacheStoreConfig : virtual BinaryCacheStoreConfig } }; -struct S3BinaryCacheStoreImpl : virtual S3BinaryCacheStoreConfig, public virtual S3BinaryCacheStore +struct S3BinaryCacheStoreImpl : virtual S3BinaryCacheStoreConfig, public virtual S3BinaryCacheStore, public VisibleStore { std::string bucketName; diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh index 96a7ebd7b2d1..709d9a465309 100644 --- a/src/libstore/store-api.hh +++ b/src/libstore/store-api.hh @@ -256,17 +256,6 @@ public: virtual StorePathSet queryValidPaths(const StorePathSet & paths, SubstituteFlag maybeSubstitute = NoSubstitute); - /** - * Query the set of all valid paths. Note that for some store - * backends, the name part of store paths may be replaced by 'x' - * (i.e. you'll get /nix/store/-x rather than - * /nix/store/-). Use queryPathInfo() to obtain the - * full store path. FIXME: should return a set of - * std::variant to get rid of this hack. - */ - virtual StorePathSet queryAllValidPaths() - { unsupported("queryAllValidPaths"); } - constexpr static const char * MissingName = "x"; /** @@ -323,13 +312,6 @@ protected: public: - /** - * Queries the set of incoming FS references for a store path. - * The result is not cleared. - */ - virtual void queryReferrers(const StorePath & path, StorePathSet & referrers) - { unsupported("queryReferrers"); } - /** * @return all currently valid derivations that have `path` as an * output. @@ -633,11 +615,11 @@ public: * returned. */ virtual void computeFSClosure(const StorePathSet & paths, - StorePathSet & out, bool flipDirection = false, + StorePathSet & out, bool includeOutputs = false, bool includeDerivers = false); void computeFSClosure(const StorePath & path, - StorePathSet & out, bool flipDirection = false, + StorePathSet & out, bool includeOutputs = false, bool includeDerivers = false); /** diff --git a/src/libstore/visible-store.hh b/src/libstore/visible-store.hh new file mode 100644 index 000000000000..0d602a0c38af --- /dev/null +++ b/src/libstore/visible-store.hh @@ -0,0 +1,42 @@ +#pragma once +///@file + +#include "store-api.hh" + +namespace nix { + +/** + * A Store that exposes all store objects. + * + * ### Privacy and Security + * + * For the base `Store` class, we aim for `StorePath`s to act as + * capabilities: only store objects which are reachable from the store + * objects the user has (i.e. those directly-referenced objects and + * their reference closure). + * + * A `VisibleStore` breaks this by exposing these methods that allow + * discovering other store objects, outside the "reachable set" as + * defined above. This is necessary to implement certain operations, but + * care must taken exposing this functionality to the user as it makes + * e.g. secret management and other security properties trickier to get + * right. + */ +struct VisibleStore : virtual Store +{ + inline static std::string operationName = "Query all valid paths"; + + /** + * Query the set of all valid paths. Note that for some store + * backends, the name part of store paths may be replaced by 'x' + * (i.e. you'll get `/nix/store/-x` rather than + * `/nix/store/-`). Use `queryPathInfo()` to obtain the + * full store path. + * + * \todo should return a set of `std::variant` + * to get rid of this hack. + */ + virtual StorePathSet queryAllValidPaths() = 0; +}; + +} diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index 0a0a3ab1a043..e803e6f09695 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -6,6 +6,7 @@ #include "store-cast.hh" #include "gc-store.hh" #include "log-store.hh" +#include "referrers-store.hh" #include "local-store.hh" #include "monitor-fd.hh" #include "serve-protocol.hh" @@ -355,18 +356,22 @@ static void opQuery(Strings opFlags, Strings opArgs) for (auto & i : opArgs) { auto ps = maybeUseOutputs(store->followLinksToStorePath(i), useOutput, forceRealise); for (auto & j : ps) { - if (query == qRequisites) store->computeFSClosure(j, paths, false, includeOutputs); + if (query == qRequisites) store->computeFSClosure(j, paths, includeOutputs); else if (query == qReferences) { for (auto & p : store->queryPathInfo(j)->references) paths.insert(p); } else if (query == qReferrers) { + auto & referrersStore = require(*store); StorePathSet tmp; - store->queryReferrers(j, tmp); + referrersStore.queryReferrers(j, tmp); for (auto & i : tmp) paths.insert(i); } - else if (query == qReferrersClosure) store->computeFSClosure(j, paths, true); + else if (query == qReferrersClosure) { + auto & referrersStore = require(*store); + referrersStore.computeFSCoClosure(j, paths); + } } } auto sorted = store->topoSortPaths(paths); @@ -456,16 +461,17 @@ static void opQuery(Strings opFlags, Strings opArgs) } case qRoots: { + auto & referrersStore = require(*store); + auto & gcStore = require(*store); StorePathSet args; for (auto & i : opArgs) for (auto & p : maybeUseOutputs(store->followLinksToStorePath(i), useOutput, forceRealise)) args.insert(p); StorePathSet referrers; - store->computeFSClosure( - args, referrers, true, settings.gcKeepOutputs, settings.gcKeepDerivations); + referrersStore.computeFSCoClosure( + args, referrers, settings.gcKeepOutputs, settings.gcKeepDerivations); - auto & gcStore = require(*store); Roots roots = gcStore.findRoots(false); for (auto & [target, links] : roots) if (referrers.find(target) != referrers.end()) @@ -526,12 +532,13 @@ static void opReadLog(Strings opFlags, Strings opArgs) static void opDumpDB(Strings opFlags, Strings opArgs) { + auto & visibleStore = require(*store); if (!opFlags.empty()) throw UsageError("unknown flag"); if (!opArgs.empty()) { for (auto & i : opArgs) cout << store->makeValidityRegistration({store->followLinksToStorePath(i)}, true, true); } else { - for (auto & i : store->queryAllValidPaths()) + for (auto & i : visibleStore.queryAllValidPaths()) cout << store->makeValidityRegistration({i}, true, true); } }