1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-12-22 17:01:08 +01:00
nix/src/libutil/include/nix/util/source-accessor.hh
Sergei Zimmerman 017fae3f14
libutil-tests: Add tests for makeFSSourceAccessor
Should be pretty self-explanatory. We didn't really have unit tests
for the filesystem source accessor. Now we do and this will be immensely
useful for implementing a unix-only smarter accessor that doesn't suffer
from TOCTOU on symlinks.
2025-12-17 04:42:31 +03:00

262 lines
7.6 KiB
C++

#pragma once
#include <filesystem>
#include "nix/util/canon-path.hh"
#include "nix/util/hash.hh"
#include "nix/util/ref.hh"
namespace nix {
struct Sink;
/**
* Note there is a decent chance this type soon goes away because the problem is solved another way.
* See the discussion in https://github.com/NixOS/nix/pull/9985.
*/
enum class SymlinkResolution {
/**
* Resolve symlinks in the ancestors only.
*
* Only the last component of the result is possibly a symlink.
*/
Ancestors,
/**
* Resolve symlinks fully, realpath(3)-style.
*
* No component of the result will be a symlink.
*/
Full,
};
MakeError(FileNotFound, Error);
/**
* A read-only filesystem abstraction. This is used by the Nix
* evaluator and elsewhere for accessing sources in various
* filesystem-like entities (such as the real filesystem, tarballs or
* Git repositories).
*/
struct SourceAccessor : std::enable_shared_from_this<SourceAccessor>
{
const size_t number;
std::string displayPrefix, displaySuffix;
SourceAccessor();
virtual ~SourceAccessor() {}
/**
* Return the contents of a file as a string.
*
* @note Unlike Unix, this method should *not* follow symlinks. Nix
* by default wants to manipulate symlinks explicitly, and not
* implicitly follow them, as they are frequently untrusted user data
* and thus may point to arbitrary locations. Acting on the targets
* targets of symlinks should only occasionally be done, and only
* with care.
*/
virtual std::string readFile(const CanonPath & path);
/**
* Write the contents of a file as a sink. `sizeCallback` must be
* called with the size of the file before any data is written to
* the sink.
*
* @note Like the other `readFile`, this method should *not* follow
* symlinks.
*
* @note subclasses of `SourceAccessor` need to implement at least
* one of the `readFile()` variants.
*/
virtual void
readFile(const CanonPath & path, Sink & sink, std::function<void(uint64_t)> sizeCallback = [](uint64_t size) {});
virtual bool pathExists(const CanonPath & path);
enum Type {
tRegular,
tSymlink,
tDirectory,
/**
Any other node types that may be encountered on the file system, such as device nodes, sockets, named pipe,
and possibly even more exotic things.
Responsible for `"unknown"` from `builtins.readFileType "/dev/null"`.
Unlike `DT_UNKNOWN`, this must not be used for deferring the lookup of types.
*/
tChar,
tBlock,
tSocket,
tFifo,
tUnknown
};
struct Stat
{
Type type = tUnknown;
/**
* For regular files only: the size of the file. Not all
* accessors return this since it may be too expensive to
* compute.
*/
std::optional<uint64_t> fileSize;
/**
* For regular files only: whether this is an executable.
*/
bool isExecutable = false;
/**
* For regular files only: the position of the contents of this
* file in the NAR. Only returned by NAR accessors.
*/
std::optional<uint64_t> narOffset;
bool isNotNARSerialisable();
std::string typeString();
};
virtual Stat lstat(const CanonPath & path);
virtual std::optional<Stat> maybeLstat(const CanonPath & path) = 0;
typedef std::optional<Type> DirEntry;
typedef std::map<std::string, DirEntry> DirEntries;
/**
* @note Like `readFile`, this method should *not* follow symlinks.
*/
virtual DirEntries readDirectory(const CanonPath & path) = 0;
virtual std::string readLink(const CanonPath & path) = 0;
virtual void dumpPath(const CanonPath & path, Sink & sink, PathFilter & filter = defaultPathFilter);
Hash
hashPath(const CanonPath & path, PathFilter & filter = defaultPathFilter, HashAlgorithm ha = HashAlgorithm::SHA256);
/**
* Return a corresponding path in the root filesystem, if
* possible. This is only possible for filesystems that are
* materialized in the root filesystem.
*/
virtual std::optional<std::filesystem::path> getPhysicalPath(const CanonPath & path)
{
return std::nullopt;
}
bool operator==(const SourceAccessor & x) const
{
return number == x.number;
}
auto operator<=>(const SourceAccessor & x) const
{
return number <=> x.number;
}
void setPathDisplay(std::string displayPrefix, std::string displaySuffix = "");
virtual std::string showPath(const CanonPath & path);
/**
* Resolve any symlinks in `path` according to the given
* resolution mode.
*
* @param mode might only be a temporary solution for this.
* See the discussion in https://github.com/NixOS/nix/pull/9985.
*/
CanonPath resolveSymlinks(const CanonPath & path, SymlinkResolution mode = SymlinkResolution::Full);
/**
* A string that uniquely represents the contents of this
* accessor. This is used for caching lookups (see `fetchToStore()`).
*/
std::optional<std::string> fingerprint;
/**
* Return the fingerprint for `path`. This is usually the
* fingerprint of the current accessor, but for composite
* accessors (like `MountedSourceAccessor`), we want to return the
* fingerprint of the "inner" accessor if the current one lacks a
* fingerprint.
*
* So this method is intended to return the most-outer accessor
* that has a fingerprint for `path`. It also returns the path that `path`
* corresponds to in that accessor.
*
* For example: in a `MountedSourceAccessor` that has
* `/nix/store/foo` mounted,
* `getFingerprint("/nix/store/foo/bar")` will return the path
* `/bar` and the fingerprint of the `/nix/store/foo` accessor.
*/
virtual std::pair<CanonPath, std::optional<std::string>> getFingerprint(const CanonPath & path)
{
return {path, fingerprint};
}
/**
* Return the maximum last-modified time of the files in this
* tree, if available.
*/
virtual std::optional<time_t> getLastModified()
{
return std::nullopt;
}
};
/**
* Return a source accessor that contains only an empty root directory.
*/
ref<SourceAccessor> makeEmptySourceAccessor();
/**
* Exception thrown when accessing a filtered path (see
* `FilteringSourceAccessor`).
*/
MakeError(RestrictedPathError, Error);
struct SymlinkNotAllowed : public Error
{
CanonPath path;
SymlinkNotAllowed(CanonPath path)
: Error("relative path '%s' points to a symlink, which is not allowed", path.rel())
, path(std::move(path))
{
}
template<typename... Args>
SymlinkNotAllowed(CanonPath path, const std::string & fs, Args &&... args)
: Error(fs, std::forward<Args>(args)...)
, path(std::move(path))
{
}
};
/**
* Return an accessor for the root filesystem.
*/
ref<SourceAccessor> getFSSourceAccessor();
/**
* Construct an accessor for the filesystem rooted at `root`. Note
* that it is not possible to escape `root` by appending `..` path
* elements, and that absolute symlinks are resolved relative to
* `root`.
*/
ref<SourceAccessor> makeFSSourceAccessor(std::filesystem::path root, bool trackLastModified = false);
/**
* Construct an accessor that presents a "union" view of a vector of
* underlying accessors. Earlier accessors take precedence over later.
*/
ref<SourceAccessor> makeUnionSourceAccessor(std::vector<ref<SourceAccessor>> && accessors);
} // namespace nix