1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-06-28 09:31:16 +02:00

Backport SourcePath from the lazy-trees branch

This introduces the SourcePath type from lazy-trees as an abstraction
for accessing files from inputs that may not be materialized in the
real filesystem (e.g. Git repositories). Currently, however, it's just
a wrapper around CanonPath, so it shouldn't change any behaviour. (On
lazy-trees, SourcePath is a <InputAccessor, CanonPath> tuple.)
This commit is contained in:
Eelco Dolstra 2023-04-06 13:15:50 +02:00
parent 5256ba6d87
commit 94812cca98
35 changed files with 567 additions and 263 deletions

View file

@ -106,7 +106,7 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
}
std::pair<std::string, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what)
std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what)
{
Value * v2;
try {
@ -118,21 +118,25 @@ std::pair<std::string, uint32_t> findPackageFilename(EvalState & state, Value &
// FIXME: is it possible to extract the Pos object instead of doing this
// toString + parsing?
auto pos = state.forceString(*v2, noPos, "while evaluating the 'meta.position' attribute of a derivation");
PathSet context;
auto path = state.coerceToPath(noPos, *v2, context, "while evaluating the 'meta.position' attribute of a derivation");
auto colon = pos.rfind(':');
if (colon == std::string::npos)
throw ParseError("cannot parse meta.position attribute '%s'", pos);
auto fn = path.path.abs();
auto fail = [fn]() {
throw ParseError("cannot parse 'meta.position' attribute '%s'", fn);
};
std::string filename(pos, 0, colon);
unsigned int lineno;
try {
lineno = std::stoi(std::string(pos, colon + 1, std::string::npos));
auto colon = fn.rfind(':');
if (colon == std::string::npos) fail();
std::string filename(fn, 0, colon);
auto lineno = std::stoi(std::string(fn, colon + 1, std::string::npos));
return {CanonPath(fn.substr(0, colon)), lineno};
} catch (std::invalid_argument & e) {
throw ParseError("cannot parse line number '%s'", pos);
fail();
abort();
}
return { std::move(filename), lineno };
}

View file

@ -18,7 +18,7 @@ std::pair<Value *, PosIdx> findAlongAttrPath(
Value & vIn);
/* Heuristic to find the filename and lineno or a nix value. */
std::pair<std::string, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what);
std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what);
std::vector<Symbol> parseAttrPath(EvalState & state, std::string_view s);

View file

@ -442,8 +442,10 @@ Value & AttrCursor::forceValue()
if (v.type() == nString)
cachedValue = {root->db->setString(getKey(), v.string.s, v.string.context),
string_t{v.string.s, {}}};
else if (v.type() == nPath)
cachedValue = {root->db->setString(getKey(), v.path), string_t{v.path, {}}};
else if (v.type() == nPath) {
auto path = v.path().path;
cachedValue = {root->db->setString(getKey(), path.abs()), string_t{path.abs(), {}}};
}
else if (v.type() == nBool)
cachedValue = {root->db->setBool(getKey(), v.boolean), v.boolean};
else if (v.type() == nInt)
@ -580,7 +582,7 @@ std::string AttrCursor::getString()
if (v.type() != nString && v.type() != nPath)
root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
return v.type() == nString ? v.string.s : v.path;
return v.type() == nString ? v.string.s : v.path().to_string();
}
string_t AttrCursor::getStringWithContext()
@ -622,7 +624,7 @@ string_t AttrCursor::getStringWithContext()
if (v.type() == nString)
return {v.string.s, v.getContext(*root->state.store)};
else if (v.type() == nPath)
return {v.path, {}};
return {v.path().to_string(), {}};
else
root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
}

View file

@ -118,7 +118,7 @@ void Value::print(const SymbolTable & symbols, std::ostream & str,
str << "\"";
break;
case tPath:
str << path; // !!! escaping?
str << path().to_string(); // !!! escaping?
break;
case tNull:
str << "null";
@ -577,11 +577,11 @@ void EvalState::allowAndSetStorePathString(const StorePath & storePath, Value &
v.mkString(path, PathSet({path}));
}
Path EvalState::checkSourcePath(const Path & path_)
SourcePath EvalState::checkSourcePath(const SourcePath & path_)
{
if (!allowedPaths) return path_;
auto i = resolvedPaths.find(path_);
auto i = resolvedPaths.find(path_.path.abs());
if (i != resolvedPaths.end())
return i->second;
@ -591,9 +591,9 @@ Path EvalState::checkSourcePath(const Path & path_)
* attacker can't append ../../... to a path that would be in allowedPaths
* and thus leak symlink targets.
*/
Path abspath = canonPath(path_);
Path abspath = canonPath(path_.path.abs());
if (hasPrefix(abspath, corepkgsPrefix)) return abspath;
if (hasPrefix(abspath, corepkgsPrefix)) return CanonPath(abspath);
for (auto & i : *allowedPaths) {
if (isDirOrInDir(abspath, i)) {
@ -611,11 +611,11 @@ Path EvalState::checkSourcePath(const Path & path_)
/* Resolve symlinks. */
debug("checking access to '%s'", abspath);
Path path = canonPath(abspath, true);
SourcePath path = CanonPath(canonPath(abspath, true));
for (auto & i : *allowedPaths) {
if (isDirOrInDir(path, i)) {
resolvedPaths[path_] = path;
if (isDirOrInDir(path.path.abs(), i)) {
resolvedPaths.insert_or_assign(path_.path.abs(), path);
return path;
}
}
@ -643,12 +643,12 @@ void EvalState::checkURI(const std::string & uri)
/* If the URI is a path, then check it against allowedPaths as
well. */
if (hasPrefix(uri, "/")) {
checkSourcePath(uri);
checkSourcePath(CanonPath(uri));
return;
}
if (hasPrefix(uri, "file://")) {
checkSourcePath(std::string(uri, 7));
checkSourcePath(CanonPath(std::string(uri, 7)));
return;
}
@ -933,9 +933,9 @@ void Value::mkStringMove(const char * s, const PathSet & context)
}
void Value::mkPath(std::string_view s)
void Value::mkPath(const SourcePath & path)
{
mkPath(makeImmutableString(s));
mkPath(makeImmutableString(path.path.abs()));
}
@ -1049,7 +1049,7 @@ Value * ExprPath::maybeThunk(EvalState & state, Env & env)
}
void EvalState::evalFile(const Path & path_, Value & v, bool mustBeTrivial)
void EvalState::evalFile(const SourcePath & path_, Value & v, bool mustBeTrivial)
{
auto path = checkSourcePath(path_);
@ -1059,7 +1059,7 @@ void EvalState::evalFile(const Path & path_, Value & v, bool mustBeTrivial)
return;
}
Path resolvedPath = resolveExprPath(path);
auto resolvedPath = resolveExprPath(path);
if ((i = fileEvalCache.find(resolvedPath)) != fileEvalCache.end()) {
v = i->second;
return;
@ -1087,8 +1087,8 @@ void EvalState::resetFileCache()
void EvalState::cacheFile(
const Path & path,
const Path & resolvedPath,
const SourcePath & path,
const SourcePath & resolvedPath,
Expr * e,
Value & v,
bool mustBeTrivial)
@ -1102,7 +1102,7 @@ void EvalState::cacheFile(
*e,
this->baseEnv,
e->getPos() ? static_cast<std::shared_ptr<AbstractPos>>(positions[e->getPos()]) : nullptr,
"while evaluating the file '%1%':", resolvedPath)
"while evaluating the file '%1%':", resolvedPath.to_string())
: nullptr;
// Enforce that 'flake.nix' is a direct attrset, not a
@ -1112,7 +1112,7 @@ void EvalState::cacheFile(
error("file '%s' must be an attribute set", path).debugThrow<EvalError>();
eval(e, v);
} catch (Error & e) {
addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath);
addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath.to_string());
throw;
}
@ -1947,7 +1947,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
else if (firstType == nPath) {
if (!context.empty())
state.error("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow<EvalError>();
v.mkPath(canonPath(str()));
v.mkPath(CanonPath(canonPath(str())));
} else
v.mkStringMove(c_str(), context);
}
@ -2136,8 +2136,14 @@ std::optional<std::string> EvalState::tryAttrsToString(const PosIdx pos, Value &
return {};
}
BackedStringView EvalState::coerceToString(const PosIdx pos, Value &v, PathSet &context,
std::string_view errorCtx, bool coerceMore, bool copyToStore, bool canonicalizePath)
BackedStringView EvalState::coerceToString(
const PosIdx pos,
Value & v,
PathSet & context,
std::string_view errorCtx,
bool coerceMore,
bool copyToStore,
bool canonicalizePath)
{
forceValue(v, pos);
@ -2147,12 +2153,14 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value &v, PathSet &
}
if (v.type() == nPath) {
BackedStringView path(PathView(v.path));
if (canonicalizePath)
path = canonPath(*path);
if (copyToStore)
path = store->printStorePath(copyPathToStore(context, std::move(path).toOwned()));
return path;
return
!canonicalizePath && !copyToStore
? // FIXME: hack to preserve path literals that end in a
// slash, as in /foo/${x}.
v._path
: copyToStore
? store->printStorePath(copyPathToStore(context, v.path()))
: std::string(v.path().path.abs());
}
if (v.type() == nAttrs) {
@ -2213,36 +2221,34 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value &v, PathSet &
}
StorePath EvalState::copyPathToStore(PathSet & context, const Path & path)
StorePath EvalState::copyPathToStore(PathSet & context, const SourcePath & path)
{
if (nix::isDerivation(path))
if (nix::isDerivation(path.path.abs()))
error("file names are not allowed to end in '%1%'", drvExtension).debugThrow<EvalError>();
auto dstPath = [&]() -> StorePath
{
auto i = srcToStore.find(path);
if (i != srcToStore.end()) return i->second;
auto i = srcToStore.find(path);
auto dstPath = settings.readOnlyMode
? store->computeStorePathForPath(std::string(baseNameOf(path)), checkSourcePath(path)).first
: store->addToStore(std::string(baseNameOf(path)), checkSourcePath(path), FileIngestionMethod::Recursive, htSHA256, defaultPathFilter, repair);
allowPath(dstPath);
srcToStore.insert_or_assign(path, dstPath);
printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, store->printStorePath(dstPath));
return dstPath;
}();
auto dstPath = i != srcToStore.end()
? i->second
: [&]() {
auto dstPath = path.fetchToStore(store, path.baseName(), nullptr, repair);
allowPath(dstPath);
srcToStore.insert_or_assign(path, dstPath);
printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, store->printStorePath(dstPath));
return dstPath;
}();
context.insert(store->printStorePath(dstPath));
return dstPath;
}
Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx)
SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx)
{
auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
if (path == "" || path[0] != '/')
error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
return path;
return CanonPath(path);
}
@ -2285,7 +2291,7 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
return strcmp(v1.string.s, v2.string.s) == 0;
case nPath:
return strcmp(v1.path, v2.path) == 0;
return strcmp(v1._path, v2._path) == 0;
case nNull:
return true;

View file

@ -8,6 +8,7 @@
#include "symbol-table.hh"
#include "config.hh"
#include "experimental-features.hh"
#include "input-accessor.hh"
#include <map>
#include <optional>
@ -56,15 +57,11 @@ std::unique_ptr<ValMap> mapStaticEnvBindings(const SymbolTable & st, const Stati
void copyContext(const Value & v, PathSet & context);
/* Cache for calls to addToStore(); maps source paths to the store
paths. */
typedef std::map<Path, StorePath> SrcToStore;
std::string printValue(const EvalState & state, const Value & v);
std::ostream & operator << (std::ostream & os, const ValueType t);
// FIXME: maybe change this to an std::variant<SourcePath, URL>.
typedef std::pair<std::string, std::string> SearchPathElem;
typedef std::list<SearchPathElem> SearchPath;
@ -217,21 +214,24 @@ public:
}
private:
SrcToStore srcToStore;
/* Cache for calls to addToStore(); maps source paths to the store
paths. */
std::map<SourcePath, StorePath> srcToStore;
/* A cache from path names to parse trees. */
#if HAVE_BOEHMGC
typedef std::map<Path, Expr *, std::less<Path>, traceable_allocator<std::pair<const Path, Expr *>>> FileParseCache;
typedef std::map<SourcePath, Expr *, std::less<SourcePath>, traceable_allocator<std::pair<const SourcePath, Expr *>>> FileParseCache;
#else
typedef std::map<Path, Expr *> FileParseCache;
typedef std::map<SourcePath, Expr *> FileParseCache;
#endif
FileParseCache fileParseCache;
/* A cache from path names to values. */
#if HAVE_BOEHMGC
typedef std::map<Path, Value, std::less<Path>, traceable_allocator<std::pair<const Path, Value>>> FileEvalCache;
typedef std::map<SourcePath, Value, std::less<SourcePath>, traceable_allocator<std::pair<const SourcePath, Value>>> FileEvalCache;
#else
typedef std::map<Path, Value> FileEvalCache;
typedef std::map<SourcePath, Value> FileEvalCache;
#endif
FileEvalCache fileEvalCache;
@ -240,7 +240,7 @@ private:
std::map<std::string, std::pair<bool, std::string>> searchPathResolved;
/* Cache used by checkSourcePath(). */
std::unordered_map<Path, Path> resolvedPaths;
std::unordered_map<Path, SourcePath> resolvedPaths;
/* Cache used by prim_match(). */
std::shared_ptr<RegexCache> regexCache;
@ -265,6 +265,12 @@ public:
SearchPath getSearchPath() { return searchPath; }
/**
* Return a `SourcePath` that refers to `path` in the root
* filesystem.
*/
SourcePath rootPath(CanonPath path);
/* Allow access to a path. */
void allowPath(const Path & path);
@ -277,7 +283,7 @@ public:
/* Check whether access to a path is allowed and throw an error if
not. Otherwise return the canonicalised path. */
Path checkSourcePath(const Path & path);
SourcePath checkSourcePath(const SourcePath & path);
void checkURI(const std::string & uri);
@ -291,24 +297,24 @@ public:
Path toRealPath(const Path & path, const PathSet & context);
/* Parse a Nix expression from the specified file. */
Expr * parseExprFromFile(const Path & path);
Expr * parseExprFromFile(const Path & path, std::shared_ptr<StaticEnv> & staticEnv);
Expr * parseExprFromFile(const SourcePath & path);
Expr * parseExprFromFile(const SourcePath & path, std::shared_ptr<StaticEnv> & staticEnv);
/* Parse a Nix expression from the specified string. */
Expr * parseExprFromString(std::string s, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv);
Expr * parseExprFromString(std::string s, const Path & basePath);
Expr * parseExprFromString(std::string s, const SourcePath & basePath, std::shared_ptr<StaticEnv> & staticEnv);
Expr * parseExprFromString(std::string s, const SourcePath & basePath);
Expr * parseStdin();
/* Evaluate an expression read from the given file to normal
form. Optionally enforce that the top-level expression is
trivial (i.e. doesn't require arbitrary computation). */
void evalFile(const Path & path, Value & v, bool mustBeTrivial = false);
void evalFile(const SourcePath & path, Value & v, bool mustBeTrivial = false);
/* Like `evalFile`, but with an already parsed expression. */
void cacheFile(
const Path & path,
const Path & resolvedPath,
const SourcePath & path,
const SourcePath & resolvedPath,
Expr * e,
Value & v,
bool mustBeTrivial = false);
@ -316,8 +322,8 @@ public:
void resetFileCache();
/* Look up a file in the search path. */
Path findFile(const std::string_view path);
Path findFile(SearchPath & searchPath, const std::string_view path, const PosIdx pos = noPos);
SourcePath findFile(const std::string_view path);
SourcePath findFile(SearchPath & searchPath, const std::string_view path, const PosIdx pos = noPos);
/* If the specified search path element is a URI, download it. */
std::pair<bool, std::string> resolveSearchPathElem(const SearchPathElem & elem);
@ -383,12 +389,12 @@ public:
bool coerceMore = false, bool copyToStore = true,
bool canonicalizePath = true);
StorePath copyPathToStore(PathSet & context, const Path & path);
StorePath copyPathToStore(PathSet & context, const SourcePath & path);
/* Path coercion. Converts strings, paths and derivations to a
path. The result is guaranteed to be a canonicalised, absolute
path. Nothing is copied to the store. */
Path coerceToPath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx);
SourcePath coerceToPath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx);
/* Like coerceToPath, but the result must be a store path. */
StorePath coerceToStorePath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx);
@ -444,7 +450,7 @@ private:
char * text,
size_t length,
Pos::Origin origin,
Path basePath,
const SourcePath & basePath,
std::shared_ptr<StaticEnv> & staticEnv);
public:
@ -556,7 +562,7 @@ std::string_view showType(ValueType type);
std::string showType(const Value & v);
/* If `path' refers to a directory, then append "/default.nix". */
Path resolveExprPath(Path path);
SourcePath resolveExprPath(const SourcePath & path);
struct InvalidPathError : EvalError
{

View file

@ -218,7 +218,7 @@ static Flake getFlake(
throw Error("source tree referenced by '%s' does not contain a '%s/flake.nix' file", lockedRef, lockedRef.subdir);
Value vInfo;
state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack
state.evalFile(CanonPath(flakeFile), vInfo, true); // FIXME: symlink attack
expectType(state, nAttrs, vInfo, state.positions.add({flakeFile}, 1, 1));
@ -731,7 +731,7 @@ void callFlake(EvalState & state,
state.vCallFlake = allocRootValue(state.allocValue());
state.eval(state.parseExprFromString(
#include "call-flake.nix.gen.hh"
, "/"), **state.vCallFlake);
, CanonPath::root), **state.vCallFlake);
}
state.callFunction(**state.vCallFlake, *vLocks, *vTmp1, noPos);

View file

@ -31,7 +31,7 @@ namespace nix {
EvalState & state;
SymbolTable & symbols;
Expr * result;
Path basePath;
SourcePath basePath;
PosTable::Origin origin;
std::optional<ErrorInfo> error;
};
@ -509,7 +509,7 @@ string_parts_interpolated
path_start
: PATH {
Path path(absPath({$1.p, $1.l}, data->basePath));
Path path(absPath({$1.p, $1.l}, data->basePath.path.abs()));
/* add back in the trailing '/' to the first segment */
if ($1.p[$1.l-1] == '/' && $1.l > 1)
path += "/";
@ -651,7 +651,7 @@ Expr * EvalState::parse(
char * text,
size_t length,
Pos::Origin origin,
Path basePath,
const SourcePath & basePath,
std::shared_ptr<StaticEnv> & staticEnv)
{
yyscan_t scanner;
@ -675,48 +675,36 @@ Expr * EvalState::parse(
}
Path resolveExprPath(Path path)
SourcePath resolveExprPath(const SourcePath & path)
{
assert(path[0] == '/');
unsigned int followCount = 0, maxFollow = 1024;
/* If `path' is a symlink, follow it. This is so that relative
path references work. */
struct stat st;
while (true) {
// Basic cycle/depth limit to avoid infinite loops.
if (++followCount >= maxFollow)
throw Error("too many symbolic links encountered while traversing the path '%s'", path);
st = lstat(path);
if (!S_ISLNK(st.st_mode)) break;
path = absPath(readLink(path), dirOf(path));
}
auto path2 = path.resolveSymlinks();
/* If `path' refers to a directory, append `/default.nix'. */
if (S_ISDIR(st.st_mode))
path = canonPath(path + "/default.nix");
if (path2.lstat().type == InputAccessor::tDirectory)
return path2 + "default.nix";
return path;
return path2;
}
Expr * EvalState::parseExprFromFile(const Path & path)
Expr * EvalState::parseExprFromFile(const SourcePath & path)
{
return parseExprFromFile(path, staticBaseEnv);
}
Expr * EvalState::parseExprFromFile(const Path & path, std::shared_ptr<StaticEnv> & staticEnv)
Expr * EvalState::parseExprFromFile(const SourcePath & path, std::shared_ptr<StaticEnv> & staticEnv)
{
auto buffer = readFile(path);
// readFile should have left some extra space for terminators
auto buffer = path.readFile();
// readFile hopefully have left some extra space for terminators
buffer.append("\0\0", 2);
return parse(buffer.data(), buffer.size(), path, dirOf(path), staticEnv);
return parse(buffer.data(), buffer.size(), path.path.abs(), path.parent(), staticEnv);
}
Expr * EvalState::parseExprFromString(std::string s_, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv)
Expr * EvalState::parseExprFromString(std::string s_, const SourcePath & basePath, std::shared_ptr<StaticEnv> & staticEnv)
{
auto s = make_ref<std::string>(std::move(s_));
s->append("\0\0", 2);
@ -724,7 +712,7 @@ Expr * EvalState::parseExprFromString(std::string s_, const Path & basePath, std
}
Expr * EvalState::parseExprFromString(std::string s, const Path & basePath)
Expr * EvalState::parseExprFromString(std::string s, const SourcePath & basePath)
{
return parseExprFromString(std::move(s), basePath, staticBaseEnv);
}
@ -737,7 +725,7 @@ Expr * EvalState::parseStdin()
// drainFD should have left some extra space for terminators
buffer.append("\0\0", 2);
auto s = make_ref<std::string>(std::move(buffer));
return parse(s->data(), s->size(), Pos::Stdin{.source = s}, absPath("."), staticBaseEnv);
return parse(s->data(), s->size(), Pos::Stdin{.source = s}, rootPath(CanonPath::fromCwd()), staticBaseEnv);
}
@ -757,13 +745,13 @@ void EvalState::addToSearchPath(const std::string & s)
}
Path EvalState::findFile(const std::string_view path)
SourcePath EvalState::findFile(const std::string_view path)
{
return findFile(searchPath, path);
}
Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, const PosIdx pos)
SourcePath EvalState::findFile(SearchPath & searchPath, const std::string_view path, const PosIdx pos)
{
for (auto & i : searchPath) {
std::string suffix;
@ -779,11 +767,11 @@ Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, c
auto r = resolveSearchPathElem(i);
if (!r.first) continue;
Path res = r.second + suffix;
if (pathExists(res)) return canonPath(res);
if (pathExists(res)) return CanonPath(canonPath(res));
}
if (hasPrefix(path, "nix/"))
return concatStrings(corepkgsPrefix, path.substr(4));
return CanonPath(concatStrings(corepkgsPrefix, path.substr(4)));
debugThrow(ThrownError({
.msg = hintfmt(evalSettings.pureEval

10
src/libexpr/paths.cc Normal file
View file

@ -0,0 +1,10 @@
#include "eval.hh"
namespace nix {
SourcePath EvalState::rootPath(CanonPath path)
{
return std::move(path);
}
}

View file

@ -110,7 +110,7 @@ struct RealisePathFlags {
bool checkForPureEval = true;
};
static Path realisePath(EvalState & state, const PosIdx pos, Value & v, const RealisePathFlags flags = {})
static SourcePath realisePath(EvalState & state, const PosIdx pos, Value & v, const RealisePathFlags flags = {})
{
PathSet context;
@ -119,7 +119,7 @@ static Path realisePath(EvalState & state, const PosIdx pos, Value & v, const Re
try {
StringMap rewrites = state.realiseContext(context);
auto realPath = state.toRealPath(rewriteStrings(path, rewrites), context);
auto realPath = state.rootPath(CanonPath(state.toRealPath(rewriteStrings(path.path.abs(), rewrites), context)));
return flags.checkForPureEval
? state.checkSourcePath(realPath)
@ -166,13 +166,14 @@ static void mkOutputString(
static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * vScope, Value & v)
{
auto path = realisePath(state, pos, vPath);
auto path2 = path.path.abs();
// FIXME
auto isValidDerivationInStore = [&]() -> std::optional<StorePath> {
if (!state.store->isStorePath(path))
if (!state.store->isStorePath(path2))
return std::nullopt;
auto storePath = state.store->parseStorePath(path);
if (!(state.store->isValidPath(storePath) && isDerivation(path)))
auto storePath = state.store->parseStorePath(path2);
if (!(state.store->isValidPath(storePath) && isDerivation(path2)))
return std::nullopt;
return storePath;
};
@ -181,7 +182,7 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
auto storePath = *optStorePath;
Derivation drv = state.store->readDerivation(storePath);
auto attrs = state.buildBindings(3 + drv.outputs.size());
attrs.alloc(state.sDrvPath).mkString(path, {"=" + path});
attrs.alloc(state.sDrvPath).mkString(path2, {"=" + path2});
attrs.alloc(state.sName).mkString(drv.env["name"]);
auto & outputsVal = attrs.alloc(state.sOutputs);
state.mkList(outputsVal, drv.outputs.size());
@ -198,7 +199,7 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
state.vImportedDrvToDerivation = allocRootValue(state.allocValue());
state.eval(state.parseExprFromString(
#include "imported-drv-to-derivation.nix.gen.hh"
, "/"), **state.vImportedDrvToDerivation);
, CanonPath::root), **state.vImportedDrvToDerivation);
}
state.forceFunction(**state.vImportedDrvToDerivation, pos, "while evaluating imported-drv-to-derivation.nix.gen.hh");
@ -206,10 +207,10 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
state.forceAttrs(v, pos, "while calling imported-drv-to-derivation.nix.gen.hh");
}
else if (path == corepkgsPrefix + "fetchurl.nix") {
else if (path2 == corepkgsPrefix + "fetchurl.nix") {
state.eval(state.parseExprFromString(
#include "fetchurl.nix.gen.hh"
, "/"), v);
, CanonPath::root), v);
}
else {
@ -330,7 +331,7 @@ void prim_importNative(EvalState & state, const PosIdx pos, Value * * args, Valu
std::string sym(state.forceStringNoCtx(*args[1], pos, "while evaluating the second argument passed to builtins.importNative"));
void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
void *handle = dlopen(path.path.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!handle)
state.debugThrowLastTrace(EvalError("could not open '%1%': %2%", path, dlerror()));
@ -378,7 +379,7 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
auto output = runProgram(program, true, commandArgs);
Expr * parsed;
try {
parsed = state.parseExprFromString(std::move(output), "/");
parsed = state.parseExprFromString(std::move(output), state.rootPath(CanonPath::root));
} catch (Error & e) {
e.addTrace(state.positions[pos], "while parsing the output from '%1%'", program);
throw;
@ -585,7 +586,7 @@ struct CompareValues
case nString:
return strcmp(v1->string.s, v2->string.s) < 0;
case nPath:
return strcmp(v1->path, v2->path) < 0;
return strcmp(v1->_path, v2->_path) < 0;
case nList:
// Lexicographic comparison
for (size_t i = 0;; i++) {
@ -1428,8 +1429,8 @@ static RegisterPrimOp primop_placeholder({
static void prim_toPath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
Path path = state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.toPath");
v.mkString(canonPath(path), context);
auto path = state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.toPath");
v.mkString(path.path.abs(), context);
}
static RegisterPrimOp primop_toPath({
@ -1459,21 +1460,22 @@ static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args,
}));
PathSet context;
Path path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.storePath"));
auto path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.storePath")).path;
/* Resolve symlinks in path, unless path itself is a symlink
directly in the store. The latter condition is necessary so
e.g. nix-push does the right thing. */
if (!state.store->isStorePath(path)) path = canonPath(path, true);
if (!state.store->isInStore(path))
if (!state.store->isStorePath(path.abs()))
path = CanonPath(canonPath(path.abs(), true));
if (!state.store->isInStore(path.abs()))
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("path '%1%' is not in the Nix store", path),
.errPos = state.positions[pos]
}));
auto path2 = state.store->toStorePath(path).first;
auto path2 = state.store->toStorePath(path.abs()).first;
if (!settings.readOnlyMode)
state.store->ensurePath(path2);
context.insert(state.store->printStorePath(path2));
v.mkString(path, context);
v.mkString(path.abs(), context);
}
static RegisterPrimOp primop_storePath({
@ -1504,7 +1506,7 @@ static void prim_pathExists(EvalState & state, const PosIdx pos, Value * * args,
auto path = realisePath(state, pos, *args[0], { .checkForPureEval = false });
try {
v.mkBool(pathExists(state.checkSourcePath(path)));
v.mkBool(state.checkSourcePath(path).pathExists());
} catch (SysError & e) {
/* Don't give away info from errors while canonicalising
path in restricted mode. */
@ -1551,11 +1553,17 @@ static RegisterPrimOp primop_baseNameOf({
static void prim_dirOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
auto path = state.coerceToString(pos, *args[0], context,
"while evaluating the first argument passed to builtins.dirOf",
state.forceValue(*args[0], pos);
if (args[0]->type() == nPath) {
auto path = args[0]->path();
v.mkPath(path.path.isRoot() ? path : path.parent());
} else {
auto path = state.coerceToString(pos, *args[0], context,
"while evaluating the first argument passed to 'builtins.dirOf'",
false, false);
auto dir = dirOf(*path);
if (args[0]->type() == nPath) v.mkPath(dir); else v.mkString(dir, context);
auto dir = dirOf(*path);
v.mkString(dir, context);
}
}
static RegisterPrimOp primop_dirOf({
@ -1573,13 +1581,13 @@ static RegisterPrimOp primop_dirOf({
static void prim_readFile(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
auto path = realisePath(state, pos, *args[0]);
auto s = readFile(path);
auto s = path.readFile();
if (s.find((char) 0) != std::string::npos)
state.debugThrowLastTrace(Error("the contents of the file '%1%' cannot be represented as a Nix string", path));
StorePathSet refs;
if (state.store->isInStore(path)) {
if (state.store->isInStore(path.path.abs())) {
try {
refs = state.store->queryPathInfo(state.store->toStorePath(path).first)->references;
refs = state.store->queryPathInfo(state.store->toStorePath(path.path.abs()).first)->references;
} catch (Error &) { // FIXME: should be InvalidPathError
}
// Re-scan references to filter down to just the ones that actually occur in the file.
@ -1660,7 +1668,7 @@ static void prim_hashFile(EvalState & state, const PosIdx pos, Value * * args, V
auto path = realisePath(state, pos, *args[1]);
v.mkString(hashFile(*ht, path).to_string(Base16, false));
v.mkString(hashString(*ht, path.readFile()).to_string(Base16, false));
}
static RegisterPrimOp primop_hashFile({
@ -1674,26 +1682,20 @@ static RegisterPrimOp primop_hashFile({
.fun = prim_hashFile,
});
/* Stringize a directory entry enum. Used by `readFileType' and `readDir'. */
static const char * dirEntTypeToString(unsigned char dtType)
static std::string_view fileTypeToString(InputAccessor::Type type)
{
/* Enum DT_(DIR|LNK|REG|UNKNOWN) */
switch(dtType) {
case DT_REG: return "regular"; break;
case DT_DIR: return "directory"; break;
case DT_LNK: return "symlink"; break;
default: return "unknown"; break;
}
return "unknown"; /* Unreachable */
return
type == InputAccessor::Type::tRegular ? "regular" :
type == InputAccessor::Type::tDirectory ? "directory" :
type == InputAccessor::Type::tSymlink ? "symlink" :
"unknown";
}
static void prim_readFileType(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
auto path = realisePath(state, pos, *args[0]);
/* Retrieve the directory entry type and stringize it. */
v.mkString(dirEntTypeToString(getFileType(path)));
v.mkString(fileTypeToString(path.lstat().type));
}
static RegisterPrimOp primop_readFileType({
@ -1714,8 +1716,7 @@ static void prim_readDir(EvalState & state, const PosIdx pos, Value * * args, Va
// Retrieve directory entries for all nodes in a directory.
// This is similar to `getFileType` but is optimized to reduce system calls
// on many systems.
DirEntries entries = readDirectory(path);
auto entries = path.readDirectory();
auto attrs = state.buildBindings(entries.size());
// If we hit unknown directory entry types we may need to fallback to
@ -1724,22 +1725,21 @@ static void prim_readDir(EvalState & state, const PosIdx pos, Value * * args, Va
// `builtins.readFileType` application.
Value * readFileType = nullptr;
for (auto & ent : entries) {
auto & attr = attrs.alloc(ent.name);
if (ent.type == DT_UNKNOWN) {
for (auto & [name, type] : entries) {
auto & attr = attrs.alloc(name);
if (!type) {
// Some filesystems or operating systems may not be able to return
// detailed node info quickly in this case we produce a thunk to
// query the file type lazily.
auto epath = state.allocValue();
Path path2 = path + "/" + ent.name;
epath->mkString(path2);
epath->mkPath(path + name);
if (!readFileType)
readFileType = &state.getBuiltin("readFileType");
attr.mkApp(readFileType, epath);
} else {
// This branch of the conditional is much more likely.
// Here we just stringize the directory entry type.
attr.mkString(dirEntTypeToString(ent.type));
attr.mkString(fileTypeToString(*type));
}
}
@ -2045,7 +2045,7 @@ static RegisterPrimOp primop_toFile({
static void addPath(
EvalState & state,
const PosIdx pos,
const std::string & name,
std::string_view name,
Path path,
Value * filterFun,
FileIngestionMethod method,
@ -2073,7 +2073,7 @@ static void addPath(
path = evalSettings.pureEval && expectedHash
? path
: state.checkSourcePath(path);
: state.checkSourcePath(CanonPath(path)).path.abs();
PathFilter filter = filterFun ? ([&](const Path & path) {
auto st = lstat(path);
@ -2120,9 +2120,10 @@ static void addPath(
static void prim_filterSource(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
Path path = state.coerceToPath(pos, *args[1], context, "while evaluating the second argument (the path to filter) passed to builtins.filterSource");
auto path = state.coerceToPath(pos, *args[1], context,
"while evaluating the second argument (the path to filter) passed to builtins.filterSource");
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.filterSource");
addPath(state, pos, std::string(baseNameOf(path)), path, args[0], FileIngestionMethod::Recursive, std::nullopt, v, context);
addPath(state, pos, path.baseName(), path.path.abs(), args[0], FileIngestionMethod::Recursive, std::nullopt, v, context);
}
static RegisterPrimOp primop_filterSource({
@ -2182,18 +2183,19 @@ static RegisterPrimOp primop_filterSource({
static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.path");
Path path;
std::optional<SourcePath> path;
std::string name;
Value * filterFun = nullptr;
auto method = FileIngestionMethod::Recursive;
std::optional<Hash> expectedHash;
PathSet context;
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to 'builtins.path'");
for (auto & attr : *args[0]->attrs) {
auto n = state.symbols[attr.name];
if (n == "path")
path = state.coerceToPath(attr.pos, *attr.value, context, "while evaluating the `path` attribute passed to builtins.path");
path.emplace(state.coerceToPath(attr.pos, *attr.value, context, "while evaluating the 'path' attribute passed to 'builtins.path'"));
else if (attr.name == state.sName)
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.path");
else if (n == "filter")
@ -2208,15 +2210,15 @@ static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value
.errPos = state.positions[attr.pos]
}));
}
if (path.empty())
if (!path)
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("missing required 'path' attribute in the first argument to builtins.path"),
.errPos = state.positions[pos]
}));
if (name.empty())
name = baseNameOf(path);
name = path->baseName();
addPath(state, pos, name, path, filterFun, method, expectedHash, v, context);
addPath(state, pos, name, path->path.abs(), filterFun, method, expectedHash, v, context);
}
static RegisterPrimOp primop_path({
@ -4151,7 +4153,7 @@ void EvalState::createBaseEnv()
// the parser needs two NUL bytes as terminators; one of them
// is implied by being a C string.
"\0";
eval(parse(code, sizeof(code), derivationNixPath, "/", staticBaseEnv), *vDerivation);
eval(parse(code, sizeof(code), derivationNixPath, {CanonPath::root}, staticBaseEnv), *vDerivation);
}

View file

@ -28,7 +28,7 @@ namespace nix {
}
Value eval(std::string input, bool forceValue = true) {
Value v;
Expr * e = state.parseExprFromString(input, "");
Expr * e = state.parseExprFromString(input, state.rootPath(CanonPath::root));
assert(e);
state.eval(e, v);
if (forceValue)

View file

@ -12,7 +12,7 @@ libexpr-tests_SOURCES := \
$(wildcard $(d)/*.cc) \
$(wildcard $(d)/value/*.cc)
libexpr-tests_CXXFLAGS += -I src/libexpr -I src/libutil -I src/libstore -I src/libexpr/tests
libexpr-tests_CXXFLAGS += -I src/libexpr -I src/libutil -I src/libstore -I src/libexpr/tests -I src/libfetchers
libexpr-tests_LIBS = libstore-tests libutils-tests libexpr libutil libstore libfetchers

View file

@ -36,9 +36,10 @@ json printValueAsJSON(EvalState & state, bool strict,
case nPath:
if (copyToStore)
out = state.store->printStorePath(state.copyPathToStore(context, v.path));
out = state.store->printStorePath(
state.copyPathToStore(context, v.path()));
else
out = v.path;
out = v.path().path.abs();
break;
case nNull:

View file

@ -78,7 +78,7 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
break;
case nPath:
doc.writeEmptyElement("path", singletonAttrs("value", v.path));
doc.writeEmptyElement("path", singletonAttrs("value", v.path().to_string()));
break;
case nNull:

View file

@ -5,6 +5,7 @@
#include "symbol-table.hh"
#include "value/context.hh"
#include "input-accessor.hh"
#if HAVE_BOEHMGC
#include <gc/gc_allocator.h>
@ -171,7 +172,7 @@ public:
const char * * context; // must be in sorted order
} string;
const char * path;
const char * _path;
Bindings * attrs;
struct {
size_t size;
@ -251,15 +252,20 @@ public:
void mkStringMove(const char * s, const PathSet & context);
inline void mkPath(const char * s)
inline void mkString(const Symbol & s)
{
mkString(((const std::string &) s).c_str());
}
void mkPath(const SourcePath & path);
inline void mkPath(const char * path)
{
clearValue();
internalType = tPath;
path = s;
_path = path;
}
void mkPath(std::string_view s);
inline void mkNull()
{
clearValue();
@ -400,6 +406,18 @@ public:
auto begin = listElems();
return ConstListIterable { begin, begin + listSize() };
}
SourcePath path() const
{
assert(internalType == tPath);
return SourcePath{CanonPath(_path)};
}
std::string_view str() const
{
assert(internalType == tString);
return std::string_view(string.s);
}
};