Skip to content

Commit

Permalink
Store::getFSAccessor: Do not include the store dir
Browse files Browse the repository at this point in the history
Rather than "mounting" the store inside an empty virtual filesystem,
just return the store as a virtual filesystem. This is more modular.

(FWIW, it also supports two long term hopes of mind:

1. More capability-based Nix language mode. I dream of a "super pure
   eval" where you can only use relative path literals (See #8738), and
   any `fetchTree`-fetched stuff + the store are all disjoint (none is
   mounted in another) file systems.

2. Windows, where the store dir may include drive letters, etc., and is
   thus unsuitable to be the prefix of any `CanonPath`s.

)
  • Loading branch information
Ericson2314 committed Feb 20, 2025
1 parent c47ba7d commit e98041b
Show file tree
Hide file tree
Showing 16 changed files with 173 additions and 49 deletions.
6 changes: 1 addition & 5 deletions src/libfetchers/store-path-accessor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ namespace nix {

ref<SourceAccessor> makeStorePathAccessor(ref<Store> store, const StorePath & storePath)
{
// FIXME: should use `store->getFSAccessor()`
auto root = std::filesystem::path{store->toRealPath(storePath)};
auto accessor = makeFSSourceAccessor(root);
accessor->setPathDisplay(root.string());
return accessor;
return projectSubdirSourceAccessor(store->getFSAccessor(), storePath.to_string());
}

}
2 changes: 1 addition & 1 deletion src/libstore/build/worker.cc
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ bool Worker::pathContentsGood(const StorePath & path)
res = false;
else {
auto current = hashPath(
{store.getFSAccessor(), CanonPath(store.printStorePath(path))},
{store.getFSAccessor(), CanonPath(path.to_string())},
FileIngestionMethod::NixArchive, info->narHash.algo).first;
Hash nullHash(HashAlgorithm::SHA256);
res = info->narHash == nullHash || info->narHash == current;
Expand Down
30 changes: 17 additions & 13 deletions src/libstore/local-fs-store.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,43 +33,47 @@ struct LocalStoreAccessor : PosixSourceAccessor
bool requireValidPath;

LocalStoreAccessor(ref<LocalFSStore> store, bool requireValidPath)
: store(store)
: PosixSourceAccessor(std::filesystem::path{store->realStoreDir.get()})
, store(store)
, requireValidPath(requireValidPath)
{ }
{
// Not `realStoreDir`, because this is where the store objects "morally" reside.
ownLocationForSymlinkResolution = store->storeDir;
}

CanonPath toRealPath(const CanonPath & path)

void requireStoreObject(const CanonPath & path)
{
auto [storePath, rest] = store->toStorePath(path.abs());
auto [storePath, rest] = store->toStorePath(store->storeDir + path.abs());
if (requireValidPath && !store->isValidPath(storePath))
throw InvalidPath("path '%1%' is not a valid store path", store->printStorePath(storePath));
return CanonPath(store->getRealStoreDir()) / storePath.to_string() / CanonPath(rest);
}

std::optional<Stat> maybeLstat(const CanonPath & path) override
{
/* Handle the case where `path` is (a parent of) the store. */
if (isDirOrInDir(store->storeDir, path.abs()))
return Stat{ .type = tDirectory };

return PosixSourceAccessor::maybeLstat(toRealPath(path));
requireStoreObject(path);
return PosixSourceAccessor::maybeLstat(path);
}

DirEntries readDirectory(const CanonPath & path) override
{
return PosixSourceAccessor::readDirectory(toRealPath(path));
requireStoreObject(path);
return PosixSourceAccessor::readDirectory(path);
}

void readFile(
const CanonPath & path,
Sink & sink,
std::function<void(uint64_t)> sizeCallback) override
{
return PosixSourceAccessor::readFile(toRealPath(path), sink, sizeCallback);
requireStoreObject(path);
return PosixSourceAccessor::readFile(path, sink, sizeCallback);
}

std::string readLink(const CanonPath & path) override
{
return PosixSourceAccessor::readLink(toRealPath(path));
requireStoreObject(path);
return PosixSourceAccessor::readLink(path);
}
};

Expand Down
2 changes: 1 addition & 1 deletion src/libstore/local-store.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1099,7 +1099,7 @@ void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
auto & specified = *info.ca;
auto actualHash = ({
auto accessor = getFSAccessor(false);
CanonPath path { printStorePath(info.path) };
CanonPath path { info.path.to_string() };
Hash h { HashAlgorithm::SHA256 }; // throwaway def to appease C++
auto fim = specified.method.getFileIngestionMethod();
switch (fim) {
Expand Down
5 changes: 4 additions & 1 deletion src/libstore/remote-fs-accessor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ RemoteFSAccessor::RemoteFSAccessor(ref<Store> store, bool requireValidPath, cons
{
if (cacheDir != "")
createDirs(cacheDir);

// Not `realStoreDir`, because this is where the store objects "morally" reside.
ownLocationForSymlinkResolution = store->storeDir;
}

Path RemoteFSAccessor::makeCacheFile(std::string_view hashPart, const std::string & ext)
Expand Down Expand Up @@ -51,7 +54,7 @@ ref<SourceAccessor> RemoteFSAccessor::addToCache(std::string_view hashPart, std:

std::pair<ref<SourceAccessor>, CanonPath> RemoteFSAccessor::fetch(const CanonPath & path)
{
auto [storePath, restPath_] = store->toStorePath(path.abs());
auto [storePath, restPath_] = store->toStorePath(store->storeDir + path.abs());
auto restPath = CanonPath(restPath_);

if (requireValidPath && !store->isValidPath(storePath))
Expand Down
2 changes: 1 addition & 1 deletion src/libstore/store-api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1232,7 +1232,7 @@ static Derivation readDerivationCommon(Store & store, const StorePath & drvPath,
auto accessor = store.getFSAccessor(requireValidPath);
try {
return parseDerivation(store,
accessor->readFile(CanonPath(store.printStorePath(drvPath))),
accessor->readFile(CanonPath(drvPath.to_string())),
Derivation::nameFromPath(drvPath));
} catch (FormatError & e) {
throw Error("error parsing derivation '%s': %s", store.printStorePath(drvPath), e.msg());
Expand Down
1 change: 1 addition & 0 deletions src/libutil/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ sources = files(
'signature/signer.cc',
'source-accessor.cc',
'source-path.cc',
'subdir-source-accessor.cc',
'strings.cc',
'suggestions.cc',
'tarfile.cc',
Expand Down
24 changes: 22 additions & 2 deletions src/libutil/source-accessor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,29 @@ CanonPath SourceAccessor::resolveSymlinks(
if (!linksAllowed--)
throw Error("infinite symlink recursion in path '%s'", showPath(path));
auto target = readLink(res);
res.pop();
if (isAbsolute(target))
if (isAbsolute(target)) {
auto prefixLen = ownLocationForSymlinkResolution.size();
/* TODO need to support windows paths without making
Unix vulnerability. The problem is we don't know whether
the target (which is *not* a `CanonPath`) is a
Windows or Unix path. Because of remote stores to
other OSes, and abstract stores (which should
have host-OS-agnostic designs), we cannot simply
use whether we are running on Unix or Windows for
this. */
if (!(hasPrefix(target, ownLocationForSymlinkResolution) && (target.size() == prefixLen || target[prefixLen] == '/'))) {
throw Error(
"cannot resolve '%s': "
"symlink target '%s' points outside source accessor that is considered to reside at '%s'",
showPath(path),
target,
ownLocationForSymlinkResolution);
}
target = std::move(target).substr(prefixLen);
res = CanonPath::root;
} else {
res.pop();
}
todo.splice(todo.begin(), tokenizeString<std::list<std::string>>(target, "/"));
}
}
Expand Down
39 changes: 39 additions & 0 deletions src/libutil/source-accessor.hh
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,39 @@ struct SourceAccessor : std::enable_shared_from_this<SourceAccessor>
const CanonPath & path,
SymlinkResolution mode = SymlinkResolution::Full);

/**
* When `resolveSymlinks` encounters an absolute target, that path
* may or may not denote a local inside the current source accessor
* --- there is no general answer to the question, it depends on
* what the current source accessor "represents".
*
* For now, we use this variable to decide: if the path has this
* path as a prefix, then the suffix points inside this store
* prefix. If the path does not have path as prefix, than the
* symlink escapes the source acccessor.
*
* Note, that we do *not* want this to become a general "location of
* this source accessor" variable: That would further violate the
* "don't know your own name principle. Indeed, the same source
* accessor may be used in multiple different contexts, at different
* locations, and so its "location" is a property of the caller
* rather than the thing itself.
*
* @todo Based on the above observations, we should instead get rid
* of this field, and separate out symlink resolving (other than the
* low-level, interpretation-*agnostic* `readLink`) from the source
* accessor itself.
*
* Until we do the above proper fix, the best we can do is *only*
* use this for `resolveSymlinks`.
*
* This variable does not include a trailing directory separator.
* Rather a directory separate must come after the prefix. For
* example, the empty string means the root path, i.e. any absolute
* path points within the source accessor.
*/
std::string ownLocationForSymlinkResolution = "";

/**
* A string that uniquely represents the contents of this
* accessor. This is used for caching lookups (see `fetchToStore()`).
Expand Down Expand Up @@ -216,4 +249,10 @@ ref<SourceAccessor> makeFSSourceAccessor(std::filesystem::path root);

ref<SourceAccessor> makeMountedSourceAccessor(std::map<CanonPath, ref<SourceAccessor>> mounts);

/**
* Creates a new source accessor which is confined to the subdirectory
* of the given source accessor.
*/
ref<SourceAccessor> projectSubdirSourceAccessor(ref<SourceAccessor>, CanonPath subdirectory);

}
56 changes: 56 additions & 0 deletions src/libutil/subdir-source-accessor.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#include "source-accessor.hh"

namespace nix {

struct SubdirSourceAccessor : SourceAccessor
{
ref<SourceAccessor> parent;

CanonPath subdirectory;

SubdirSourceAccessor(ref<SourceAccessor> && parent, CanonPath && subdirectory)
: parent(std::move(parent))
, subdirectory(std::move(subdirectory))
{
displayPrefix.clear();

ownLocationForSymlinkResolution = this->parent->ownLocationForSymlinkResolution + subdirectory.abs();
}

std::string readFile(const CanonPath & path) override
{
return parent->readFile(subdirectory / path);
}

bool pathExists(const CanonPath & path) override
{
return parent->pathExists(subdirectory / path);
}

std::optional<Stat> maybeLstat(const CanonPath & path) override
{
return parent->maybeLstat(subdirectory / path);
}

DirEntries readDirectory(const CanonPath & path) override
{
return parent->readDirectory(subdirectory / path);
}

std::string readLink(const CanonPath & path) override
{
return parent->readLink(subdirectory / path);
}

std::string showPath(const CanonPath & path) override
{
return displayPrefix + parent->showPath(subdirectory / path) + displaySuffix;
}
};

ref<SourceAccessor> projectSubdirSourceAccessor(ref<SourceAccessor> parent, CanonPath subdirectory)
{
return make_ref<SubdirSourceAccessor>(std::move(parent), std::move(subdirectory));
}

}
2 changes: 1 addition & 1 deletion src/nix-store/nix-store.cc
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,7 @@ static void registerValidity(bool reregister, bool hashGiven, bool canonicalise)
#endif
if (!hashGiven) {
HashResult hash = hashPath(
{store->getFSAccessor(false), CanonPath { store->printStorePath(info->path) }},
{store->getFSAccessor(false), CanonPath { info->path.to_string() }},
FileSerialisationMethod::NixArchive, HashAlgorithm::SHA256);
info->narHash = hash.first;
info->narSize = hash.second;
Expand Down
19 changes: 11 additions & 8 deletions src/nix/cat.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@ using namespace nix;

struct MixCat : virtual Args
{
std::string path;

void cat(ref<SourceAccessor> accessor)
void cat(ref<SourceAccessor> accessor, CanonPath path)
{
auto st = accessor->lstat(CanonPath(path));
auto st = accessor->lstat(path);
if (st.type != SourceAccessor::Type::tRegular)
throw Error("path '%1%' is not a regular file", path);
throw Error("path '%1%' is not a regular file", path.abs());
stopProgressBar();

writeFull(getStandardOutput(), accessor->readFile(CanonPath(path)));
writeFull(getStandardOutput(), accessor->readFile(path));
}
};

struct CmdCatStore : StoreCommand, MixCat
{
std::string path;

CmdCatStore()
{
expectArgs({
Expand All @@ -45,14 +45,17 @@ struct CmdCatStore : StoreCommand, MixCat

void run(ref<Store> store) override
{
cat(store->getFSAccessor());
auto [storePath, rest] = store->toStorePath(path);
cat(store->getFSAccessor(), CanonPath{storePath.to_string()} / CanonPath{rest});
}
};

struct CmdCatNar : StoreCommand, MixCat
{
Path narPath;

std::string path;

CmdCatNar()
{
expectArgs({
Expand All @@ -77,7 +80,7 @@ struct CmdCatNar : StoreCommand, MixCat

void run(ref<Store> store) override
{
cat(makeNarAccessor(readFile(narPath)));
cat(makeNarAccessor(readFile(narPath)), CanonPath{path});
}
};

Expand Down
4 changes: 2 additions & 2 deletions src/nix/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ struct CmdShell : InstallablesCommand, MixEnvironment
if (true)
pathAdditions.push_back(store->printStorePath(path) + "/bin");

auto propPath = accessor->resolveSymlinks(
CanonPath(store->printStorePath(path)) / "nix-support" / "propagated-user-env-packages");
auto propPath =
accessor->resolveSymlinks(CanonPath(path.to_string()) / "nix-support" / "propagated-user-env-packages");
if (auto st = accessor->maybeLstat(propPath); st && st->type == SourceAccessor::tRegular) {
for (auto & p : tokenizeString<Paths>(accessor->readFile(propPath)))
todo.push(store->parseStorePath(p));
Expand Down
Loading

0 comments on commit e98041b

Please sign in to comment.