Add CanonPath wrapper to represent canonicalized paths

This commit is contained in:
Eelco Dolstra 2022-05-16 23:27:04 +02:00
parent de35e2d3b4
commit a71f209330
No known key found for this signature in database
GPG key ID: 8170B4726D7198DE
31 changed files with 503 additions and 187 deletions

View file

@ -94,8 +94,8 @@ SourcePath lookupFileArg(EvalState & state, std::string_view s)
if (isUri(s)) { if (isUri(s)) {
auto storePath = fetchers::downloadTarball( auto storePath = fetchers::downloadTarball(
state.store, resolveUri(s), "source", false).first.storePath; state.store, resolveUri(s), "source", false).first.storePath;
auto & accessor = state.registerAccessor(makeFSInputAccessor(state.store->toRealPath(storePath))); auto & accessor = state.registerAccessor(makeFSInputAccessor(CanonPath(state.store->toRealPath(storePath))));
return {accessor, "/"}; return {accessor, CanonPath::root};
} else if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') { } else if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') {
Path p(s.substr(1, s.size() - 2)); Path p(s.substr(1, s.size() - 2));
return state.findFile(p); return state.findFile(p);

View file

@ -425,7 +425,7 @@ Value & AttrCursor::forceValue()
else if (v.type() == nPath) { else if (v.type() == nPath) {
// FIXME: take accessor into account? // FIXME: take accessor into account?
auto path = v.path().path; auto path = v.path().path;
cachedValue = {root->db->setString(getKey(), path), string_t{path, {}}}; cachedValue = {root->db->setString(getKey(), path.abs()), string_t{path.abs(), {}}};
} }
else if (v.type() == nBool) else if (v.type() == nBool)
cachedValue = {root->db->setBool(getKey(), v.boolean), v.boolean}; cachedValue = {root->db->setBool(getKey(), v.boolean), v.boolean};

View file

@ -461,9 +461,9 @@ EvalState::EvalState(
, sPrefix(symbols.create("prefix")) , sPrefix(symbols.create("prefix"))
, repair(NoRepair) , repair(NoRepair)
, emptyBindings(0) , emptyBindings(0)
, rootFS(makeFSInputAccessor("", , rootFS(makeFSInputAccessor(CanonPath::root,
evalSettings.restrictEval || evalSettings.pureEval evalSettings.restrictEval || evalSettings.pureEval
? std::optional<PathSet>(PathSet()) ? std::optional<std::set<CanonPath>>(std::set<CanonPath>())
: std::nullopt)) : std::nullopt))
, corepkgsFS(makeMemoryInputAccessor()) , corepkgsFS(makeMemoryInputAccessor())
, store(store) , store(store)
@ -515,7 +515,7 @@ EvalState::EvalState(
createBaseEnv(); createBaseEnv();
corepkgsFS->addFile( corepkgsFS->addFile(
"/fetchurl.nix", CanonPath("fetchurl.nix"),
#include "fetchurl.nix.gen.hh" #include "fetchurl.nix.gen.hh"
); );
} }
@ -528,15 +528,15 @@ EvalState::~EvalState()
void EvalState::allowPath(const Path & path) void EvalState::allowPath(const Path & path)
{ {
rootFS->allowPath(path); rootFS->allowPath(CanonPath(path)); // FIXME
} }
void EvalState::allowPath(const StorePath & storePath) void EvalState::allowPath(const StorePath & storePath)
{ {
rootFS->allowPath(store->toRealPath(storePath)); rootFS->allowPath(CanonPath(store->toRealPath(storePath))); // FIXME
} }
void EvalState::allowAndSetStorePathString(const StorePath &storePath, Value & v) void EvalState::allowAndSetStorePathString(const StorePath & storePath, Value & v)
{ {
allowPath(storePath); allowPath(storePath);
@ -612,12 +612,12 @@ void EvalState::checkURI(const std::string & uri)
/* If the URI is a path, then check it against allowedPaths as /* If the URI is a path, then check it against allowedPaths as
well. */ well. */
if (hasPrefix(uri, "/")) { if (hasPrefix(uri, "/")) {
rootFS->checkAllowed(uri); rootFS->checkAllowed(CanonPath(uri));
return; return;
} }
if (hasPrefix(uri, "file://")) { if (hasPrefix(uri, "file://")) {
rootFS->checkAllowed(uri.substr(7)); rootFS->checkAllowed(CanonPath(uri.substr(7)));
return; return;
} }
@ -758,7 +758,7 @@ void EvalState::throwEvalError(const PosIdx pos, const Suggestions & suggestions
}); });
} }
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2) const void EvalState::throwEvalError(const PosIdx pos, const char * s, std::string_view s2) const
{ {
throw EvalError(ErrorInfo { throw EvalError(ErrorInfo {
.msg = hintfmt(s, s2), .msg = hintfmt(s, s2),
@ -890,7 +890,7 @@ void Value::mkStringMove(const char * s, const PathSet & context)
void Value::mkPath(const SourcePath & path) void Value::mkPath(const SourcePath & path)
{ {
mkPath(&path.accessor, makeImmutableString(path.path)); mkPath(&path.accessor, makeImmutableString(path.path.abs()));
} }
@ -1827,7 +1827,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
else if (firstType == nPath) { else if (firstType == nPath) {
if (!context.empty()) if (!context.empty())
state.throwEvalError(pos, "a string that refers to a store path cannot be appended to a path"); state.throwEvalError(pos, "a string that refers to a store path cannot be appended to a path");
v.mkPath({.accessor = *accessor, .path = canonPath(str())}); v.mkPath({.accessor = *accessor, .path = CanonPath(str())});
} else } else
v.mkStringMove(c_str(), context); v.mkStringMove(c_str(), context);
} }
@ -2027,7 +2027,7 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
auto path = v.path(); auto path = v.path();
return copyToStore return copyToStore
? store->printStorePath(copyPathToStore(context, path)) ? store->printStorePath(copyPathToStore(context, path))
: path.path; : BackedStringView((Path) path.path.abs());
} }
if (v.type() == nAttrs) { if (v.type() == nAttrs) {
@ -2071,7 +2071,7 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
StorePath EvalState::copyPathToStore(PathSet & context, const SourcePath & path) StorePath EvalState::copyPathToStore(PathSet & context, const SourcePath & path)
{ {
if (nix::isDerivation(path.path)) if (nix::isDerivation(path.path.abs()))
throw EvalError("file names are not allowed to end in '%s'", drvExtension); throw EvalError("file names are not allowed to end in '%s'", drvExtension);
auto i = srcToStore.find(path); auto i = srcToStore.find(path);
@ -2103,7 +2103,10 @@ SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & contex
if (v.type() == nString) { if (v.type() == nString) {
copyContext(v, context); copyContext(v, context);
return {*rootFS, v.string.s}; auto path = v.str();
if (path == "" || path[0] != '/')
throwEvalError(pos, "string '%1%' doesn't represent an absolute path", path);
return {*rootFS, CanonPath(path)};
} }
if (v.type() == nPath) if (v.type() == nPath)

View file

@ -160,7 +160,7 @@ public:
SearchPath getSearchPath() { return searchPath; } SearchPath getSearchPath() { return searchPath; }
SourcePath rootPath(Path path); SourcePath rootPath(const Path & path);
InputAccessor & registerAccessor(ref<InputAccessor> accessor); InputAccessor & registerAccessor(ref<InputAccessor> accessor);
@ -256,7 +256,7 @@ public:
void throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, void throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s,
const std::string & s2) const; const std::string & s2) const;
[[gnu::noinline, gnu::noreturn]] [[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2) const; void throwEvalError(const PosIdx pos, const char * s, std::string_view s2) const;
[[gnu::noinline, gnu::noreturn]] [[gnu::noinline, gnu::noreturn]]
void throwEvalError(const char * s, const std::string & s2, const std::string & s3) const; void throwEvalError(const char * s, const std::string & s2, const std::string & s3) const;
[[gnu::noinline, gnu::noreturn]] [[gnu::noinline, gnu::noreturn]]

View file

@ -206,8 +206,8 @@ static Flake readFlake(
InputAccessor & accessor, InputAccessor & accessor,
const InputPath & lockRootPath) const InputPath & lockRootPath)
{ {
auto flakeDir = canonPath("/" + resolvedRef.subdir); CanonPath flakeDir(resolvedRef.subdir);
SourcePath flakePath{accessor, canonPath(flakeDir + "/flake.nix")}; SourcePath flakePath{accessor, flakeDir + CanonPath("flake.nix")};
if (!flakePath.pathExists()) if (!flakePath.pathExists())
throw Error("source tree referenced by '%s' does not contain a file named '%s'", resolvedRef, flakePath.path); throw Error("source tree referenced by '%s' does not contain a file named '%s'", resolvedRef, flakePath.path);
@ -232,7 +232,7 @@ static Flake readFlake(
auto sInputs = state.symbols.create("inputs"); auto sInputs = state.symbols.create("inputs");
if (auto inputs = vInfo.attrs->get(sInputs)) if (auto inputs = vInfo.attrs->get(sInputs))
flake.inputs = parseFlakeInputs(state, inputs->value, inputs->pos, flakeDir, lockRootPath); flake.inputs = parseFlakeInputs(state, inputs->value, inputs->pos, flakeDir.abs(), lockRootPath);
auto sOutputs = state.symbols.create("outputs"); auto sOutputs = state.symbols.create("outputs");
@ -337,7 +337,7 @@ Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup
static LockFile readLockFile(const Flake & flake) static LockFile readLockFile(const Flake & flake)
{ {
auto lockFilePath = flake.path.parent().append("/flake.lock"); auto lockFilePath = flake.path.parent() + "flake.lock";
return lockFilePath.pathExists() return lockFilePath.pathExists()
? LockFile(lockFilePath.readFile(), fmt("%s", lockFilePath)) ? LockFile(lockFilePath.readFile(), fmt("%s", lockFilePath))
: LockFile(); : LockFile();
@ -721,7 +721,7 @@ void callFlake(EvalState & state,
emitTreeAttrs( emitTreeAttrs(
state, state,
{lockedFlake.flake.path.accessor, "/"}, {lockedFlake.flake.path.accessor, CanonPath::root},
lockedFlake.flake.lockedRef.input, lockedFlake.flake.lockedRef.input,
*vRootSrc, *vRootSrc,
false, false,

View file

@ -188,7 +188,7 @@ struct ExprPath : Expr
ExprPath(SourcePath && _path) ExprPath(SourcePath && _path)
: path(_path) : path(_path)
{ {
v.mkPath(&path.accessor, path.path.c_str()); v.mkPath(&path.accessor, path.path.abs().data());
} }
COMMON_METHODS COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env); Value * maybeThunk(EvalState & state, Env & env);

View file

@ -508,10 +508,12 @@ string_parts_interpolated
path_start path_start
: PATH { : PATH {
SourcePath path { data->basePath.accessor, absPath({$1.p, $1.l}, data->basePath.path) }; SourcePath path { data->basePath.accessor, CanonPath({$1.p, $1.l}, data->basePath.path) };
#if 0
/* add back in the trailing '/' to the first segment */ /* add back in the trailing '/' to the first segment */
if ($1.p[$1.l-1] == '/' && $1.l > 1) if ($1.p[$1.l-1] == '/' && $1.l > 1)
path.path += "/"; path.path += "/";
#endif
$$ = new ExprPath(std::move(path)); $$ = new ExprPath(std::move(path));
} }
| HPATH { | HPATH {
@ -699,7 +701,7 @@ SourcePath resolveExprPath(const SourcePath & path)
#endif #endif
// FIXME // FIXME
auto path2 = path.append("/default.nix"); auto path2 = path + "default.nix";
return path2.pathExists() ? path2 : path; return path2.pathExists() ? path2 : path;
} }
@ -716,7 +718,7 @@ Expr * EvalState::parseExprFromFile(const SourcePath & path, StaticEnv & staticE
// readFile hopefully have left some extra space for terminators // readFile hopefully have left some extra space for terminators
buffer.append("\0\0", 2); buffer.append("\0\0", 2);
// FIXME: pass SourcePaths // FIXME: pass SourcePaths
return parse(buffer.data(), buffer.size(), foFile, path.path, path.parent(), staticEnv); return parse(buffer.data(), buffer.size(), foFile, path.path.abs(), path.parent(), staticEnv);
} }
@ -779,13 +781,13 @@ SourcePath EvalState::findFile(SearchPath & searchPath, const std::string_view p
suffix = path.size() == s ? "" : concatStrings("/", path.substr(s)); suffix = path.size() == s ? "" : concatStrings("/", path.substr(s));
} }
if (auto path = resolveSearchPathElem(i)) { if (auto path = resolveSearchPathElem(i)) {
auto res = path->append("/" + suffix); auto res = *path + CanonPath(suffix);
if (res.pathExists()) return res; if (res.pathExists()) return res;
} }
} }
if (hasPrefix(path, "nix/")) if (hasPrefix(path, "nix/"))
return {*corepkgsFS, (std::string) path.substr(3)}; return {*corepkgsFS, CanonPath(path.substr(3))};
throw ThrownError({ throw ThrownError({
.msg = hintfmt(evalSettings.pureEval .msg = hintfmt(evalSettings.pureEval
@ -808,8 +810,8 @@ std::optional<SourcePath> EvalState::resolveSearchPathElem(const SearchPathElem
try { try {
auto storePath = fetchers::downloadTarball( auto storePath = fetchers::downloadTarball(
store, resolveUri(elem.second), "source", false).first.storePath; store, resolveUri(elem.second), "source", false).first.storePath;
auto & accessor = registerAccessor(makeFSInputAccessor(store->toRealPath(storePath))); auto & accessor = registerAccessor(makeFSInputAccessor(CanonPath(store->toRealPath(storePath))));
res.emplace(SourcePath {accessor, "/"}); res.emplace(SourcePath {accessor, CanonPath::root});
} catch (FileTransferError & e) { } catch (FileTransferError & e) {
logWarning({ logWarning({
.msg = hintfmt("Nix search path entry '%1%' cannot be downloaded, ignoring", elem.second) .msg = hintfmt("Nix search path entry '%1%' cannot be downloaded, ignoring", elem.second)

View file

@ -3,9 +3,9 @@
namespace nix { namespace nix {
SourcePath EvalState::rootPath(Path path) SourcePath EvalState::rootPath(const Path & path)
{ {
return {*rootFS, std::move(path)}; return {*rootFS, CanonPath(path)};
} }
InputAccessor & EvalState::registerAccessor(ref<InputAccessor> accessor) InputAccessor & EvalState::registerAccessor(ref<InputAccessor> accessor)

View file

@ -1335,12 +1335,13 @@ static RegisterPrimOp primop_placeholder({
*************************************************************/ *************************************************************/
/* Convert the argument to a path. !!! obsolete? */ /* Convert the argument to a path and then to a string (confusing,
eh?). !!! obsolete? */
static void prim_toPath(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_toPath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
PathSet context; PathSet context;
auto path = state.coerceToPath(pos, *args[0], context); auto path = state.coerceToPath(pos, *args[0], context);
v.mkString(canonPath(path.path), context); v.mkString(path.path.abs(), context);
} }
static RegisterPrimOp primop_toPath({ static RegisterPrimOp primop_toPath({
@ -1375,17 +1376,17 @@ static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args,
/* Resolve symlinks in path, unless path itself is a symlink /* Resolve symlinks in path, unless path itself is a symlink
directly in the store. The latter condition is necessary so directly in the store. The latter condition is necessary so
e.g. nix-push does the right thing. */ e.g. nix-push does the right thing. */
if (!state.store->isStorePath(path)) path = canonPath(path, true); if (!state.store->isStorePath(path.abs())) path = path.resolveSymlinks();
if (!state.store->isInStore(path)) if (!state.store->isInStore(path.abs()))
throw EvalError({ throw EvalError({
.msg = hintfmt("path '%1%' is not in the Nix store", path), .msg = hintfmt("path '%1%' is not in the Nix store", path),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); });
auto path2 = state.store->toStorePath(path).first; auto path2 = state.store->toStorePath(path.abs()).first;
if (!settings.readOnlyMode) if (!settings.readOnlyMode)
state.store->ensurePath(path2); state.store->ensurePath(path2);
context.insert(state.store->printStorePath(path2)); context.insert(state.store->printStorePath(path2));
v.mkString(path, context); v.mkString(path.abs(), context);
} }
static RegisterPrimOp primop_storePath({ static RegisterPrimOp primop_storePath({
@ -1492,8 +1493,8 @@ static void prim_readFile(EvalState & state, const PosIdx pos, Value * * args, V
throw Error("the contents of the file '%1%' cannot be represented as a Nix string", path); throw Error("the contents of the file '%1%' cannot be represented as a Nix string", path);
// FIXME: only do queryPathInfo if path.accessor is the store accessor // FIXME: only do queryPathInfo if path.accessor is the store accessor
auto refs = auto refs =
state.store->isInStore(path.path) ? state.store->isInStore(path.path.abs()) ?
state.store->queryPathInfo(state.store->toStorePath(path.path).first)->references : state.store->queryPathInfo(state.store->toStorePath(path.path.abs()).first)->references :
StorePathSet{}; StorePathSet{};
auto context = state.store->printStorePathSet(refs); auto context = state.store->printStorePathSet(refs);
v.mkString(s, context); v.mkString(s, context);
@ -1949,14 +1950,14 @@ static void addPath(
#endif #endif
PathFilter filter = filterFun ? ([&](const Path & p) { PathFilter filter = filterFun ? ([&](const Path & p) {
SourcePath path2{path.accessor, canonPath(p)}; SourcePath path2{path.accessor, CanonPath(p)};
auto st = path2.lstat(); auto st = path2.lstat();
/* Call the filter function. The first argument is the path, /* Call the filter function. The first argument is the path,
the second is a string indicating the type of the file. */ the second is a string indicating the type of the file. */
Value arg1; Value arg1;
arg1.mkString(path2.path); arg1.mkString(path2.path.abs());
Value arg2; Value arg2;
// assert that type is not "unknown" // assert that type is not "unknown"

View file

@ -195,7 +195,7 @@ static void fetchTree(
emitTreeAttrs( emitTreeAttrs(
state, state,
{state.registerAccessor(accessor), "/"}, {state.registerAccessor(accessor), CanonPath::root},
input2, input2,
v, v,
params.emptyRevFallback, params.emptyRevFallback,

View file

@ -104,7 +104,7 @@ namespace nix {
return false; return false;
} else { } else {
auto path = arg.path(); auto path = arg.path();
if (path.path != p) { if (path.path != CanonPath(p)) {
*result_listener << "Expected a path that equals \"" << p << "\" but got: " << path.path; *result_listener << "Expected a path that equals \"" << p << "\" but got: " << path.path;
return false; return false;
} }

View file

@ -415,7 +415,13 @@ public:
SourcePath path() const SourcePath path() const
{ {
assert(internalType == tPath); assert(internalType == tPath);
return SourcePath { .accessor = *_path.accessor, .path = _path.path }; return SourcePath { .accessor = *_path.accessor, .path = CanonPath(CanonPath::unchecked_t(), _path.path) };
}
std::string_view str() const
{
assert(internalType == tString);
return std::string_view(string.s);
} }
}; };

View file

@ -315,7 +315,7 @@ std::pair<ref<InputAccessor>, Input> InputScheme::lazyFetch(ref<Store> store, co
{ {
auto [storePath, input2] = fetch(store, input); auto [storePath, input2] = fetch(store, input);
return {makeFSInputAccessor(store->toRealPath(storePath)), input2}; return {makeFSInputAccessor(CanonPath(store->toRealPath(storePath))), input2};
} }
} }

View file

@ -395,17 +395,17 @@ struct GitInputScheme : InputScheme
return repoInfo; return repoInfo;
} }
std::set<std::string> listFiles(const RepoInfo & repoInfo) std::set<CanonPath> listFiles(const RepoInfo & repoInfo)
{ {
auto gitOpts = Strings({ "-C", repoInfo.url, "ls-files", "-z" }); auto gitOpts = Strings({ "-C", repoInfo.url, "ls-files", "-z" });
if (repoInfo.submodules) if (repoInfo.submodules)
gitOpts.emplace_back("--recurse-submodules"); gitOpts.emplace_back("--recurse-submodules");
std::set<std::string> res; std::set<CanonPath> res;
for (auto & p : tokenizeString<std::set<std::string>>( for (auto & p : tokenizeString<std::set<std::string>>(
runProgram("git", true, gitOpts), "\0"s)) runProgram("git", true, gitOpts), "\0"s))
res.insert(canonPath("/" + p)); res.insert(CanonPath(p));
return res; return res;
} }
@ -683,6 +683,8 @@ struct GitInputScheme : InputScheme
auto files = listFiles(repoInfo); auto files = listFiles(repoInfo);
PathFilter filter = [&](const Path & p) -> bool { PathFilter filter = [&](const Path & p) -> bool {
abort();
#if 0
assert(hasPrefix(p, repoInfo.url)); assert(hasPrefix(p, repoInfo.url));
std::string file(p, repoInfo.url.size() + 1); std::string file(p, repoInfo.url.size() + 1);
@ -695,6 +697,7 @@ struct GitInputScheme : InputScheme
} }
return files.count(file); return files.count(file);
#endif
}; };
auto storePath = store->addToStore(input.getName(), repoInfo.url, FileIngestionMethod::Recursive, htSHA256, filter); auto storePath = store->addToStore(input.getName(), repoInfo.url, FileIngestionMethod::Recursive, htSHA256, filter);
@ -724,7 +727,7 @@ struct GitInputScheme : InputScheme
// FIXME: return updated input. // FIXME: return updated input.
return {makeFSInputAccessor(repoInfo.url, listFiles(repoInfo)), input}; return {makeFSInputAccessor(CanonPath(repoInfo.url), listFiles(repoInfo)), input};
} }
}; };

View file

@ -233,7 +233,7 @@ struct GitArchiveInputScheme : InputScheme
{ {
auto [storePath, input2] = downloadArchive(store, input); auto [storePath, input2] = downloadArchive(store, input);
return {makeZipInputAccessor(store->toRealPath(storePath)), input2}; return {makeZipInputAccessor(CanonPath(store->toRealPath(storePath))), input2};
} }
}; };

View file

@ -17,11 +17,11 @@ const std::string narVersionMagic1 = "nix-archive-1";
static std::string caseHackSuffix = "~nix~case~hack~"; static std::string caseHackSuffix = "~nix~case~hack~";
void InputAccessor::dumpPath( void InputAccessor::dumpPath(
const Path & path, const CanonPath & path,
Sink & sink, Sink & sink,
PathFilter & filter) PathFilter & filter)
{ {
auto dumpContents = [&](PathView path) auto dumpContents = [&](const CanonPath & path)
{ {
// FIXME: pipe // FIXME: pipe
auto s = readFile(path); auto s = readFile(path);
@ -30,9 +30,9 @@ void InputAccessor::dumpPath(
writePadding(s.size(), sink); writePadding(s.size(), sink);
}; };
std::function<void(const std::string & path)> dump; std::function<void(const CanonPath & path)> dump;
dump = [&](const std::string & path) { dump = [&](const CanonPath & path) {
checkInterrupt(); checkInterrupt();
auto st = lstat(path); auto st = lstat(path);
@ -57,20 +57,20 @@ void InputAccessor::dumpPath(
std::string name(i.first); std::string name(i.first);
size_t pos = i.first.find(caseHackSuffix); size_t pos = i.first.find(caseHackSuffix);
if (pos != std::string::npos) { if (pos != std::string::npos) {
debug(format("removing case hack suffix from '%s'") % (path + "/" + i.first)); debug("removing case hack suffix from '%s'", path + i.first);
name.erase(pos); name.erase(pos);
} }
if (!unhacked.emplace(name, i.first).second) if (!unhacked.emplace(name, i.first).second)
throw Error("file name collision in between '%s' and '%s'", throw Error("file name collision in between '%s' and '%s'",
(path + "/" + unhacked[name]), (path + unhacked[name]),
(path + "/" + i.first)); (path + i.first));
} else } else
unhacked.emplace(i.first, i.first); unhacked.emplace(i.first, i.first);
for (auto & i : unhacked) for (auto & i : unhacked)
if (filter(path + "/" + i.first)) { if (filter((path + i.first).abs())) {
sink << "entry" << "(" << "name" << i.first << "node"; sink << "entry" << "(" << "name" << i.first << "node";
dump(path + "/" + i.second); dump(path + i.second);
sink << ")"; sink << ")";
} }
} }
@ -87,46 +87,39 @@ void InputAccessor::dumpPath(
dump(path); dump(path);
} }
std::string InputAccessor::showPath(PathView path) std::string InputAccessor::showPath(const CanonPath & path)
{ {
return "/virtual/" + std::to_string(number) + path; return "/virtual/" + std::to_string(number) + path.abs();
} }
struct FSInputAccessorImpl : FSInputAccessor struct FSInputAccessorImpl : FSInputAccessor
{ {
Path root; CanonPath root;
std::optional<PathSet> allowedPaths; std::optional<std::set<CanonPath>> allowedPaths;
FSInputAccessorImpl(const Path & root, std::optional<PathSet> && allowedPaths) FSInputAccessorImpl(const CanonPath & root, std::optional<std::set<CanonPath>> && allowedPaths)
: root(root) : root(root)
, allowedPaths(allowedPaths) , allowedPaths(allowedPaths)
{ { }
if (allowedPaths) {
for (auto & p : *allowedPaths) {
assert(hasPrefix(p, "/"));
assert(!hasSuffix(p, "/"));
}
}
}
std::string readFile(PathView path) override std::string readFile(const CanonPath & path) override
{ {
auto absPath = makeAbsPath(path); auto absPath = makeAbsPath(path);
checkAllowed(absPath); checkAllowed(absPath);
return nix::readFile(absPath); return nix::readFile(absPath.abs());
} }
bool pathExists(PathView path) override bool pathExists(const CanonPath & path) override
{ {
auto absPath = makeAbsPath(path); auto absPath = makeAbsPath(path);
return isAllowed(absPath) && nix::pathExists(absPath); return isAllowed(absPath) && nix::pathExists(absPath.abs());
} }
Stat lstat(PathView path) override Stat lstat(const CanonPath & path) override
{ {
auto absPath = makeAbsPath(path); auto absPath = makeAbsPath(path);
checkAllowed(absPath); checkAllowed(absPath);
auto st = nix::lstat(absPath); auto st = nix::lstat(absPath.abs());
return Stat { return Stat {
.type = .type =
S_ISREG(st.st_mode) ? tRegular : S_ISREG(st.st_mode) ? tRegular :
@ -137,44 +130,44 @@ struct FSInputAccessorImpl : FSInputAccessor
}; };
} }
DirEntries readDirectory(PathView path) override DirEntries readDirectory(const CanonPath & path) override
{ {
auto absPath = makeAbsPath(path); auto absPath = makeAbsPath(path);
checkAllowed(absPath); checkAllowed(absPath);
DirEntries res; DirEntries res;
for (auto & entry : nix::readDirectory(absPath)) { for (auto & entry : nix::readDirectory(absPath.abs())) {
std::optional<Type> type; std::optional<Type> type;
switch (entry.type) { switch (entry.type) {
case DT_REG: type = Type::tRegular; break; case DT_REG: type = Type::tRegular; break;
case DT_LNK: type = Type::tSymlink; break; case DT_LNK: type = Type::tSymlink; break;
case DT_DIR: type = Type::tDirectory; break; case DT_DIR: type = Type::tDirectory; break;
} }
if (isAllowed(absPath + "/" + entry.name)) if (isAllowed(absPath + entry.name))
res.emplace(entry.name, type); res.emplace(entry.name, type);
} }
return res; return res;
} }
std::string readLink(PathView path) override std::string readLink(const CanonPath & path) override
{ {
auto absPath = makeAbsPath(path); auto absPath = makeAbsPath(path);
checkAllowed(absPath); checkAllowed(absPath);
return nix::readLink(absPath); return nix::readLink(absPath.abs());
} }
Path makeAbsPath(PathView path) CanonPath makeAbsPath(const CanonPath & path)
{ {
// FIXME: resolve symlinks in 'path' and check that any // FIXME: resolve symlinks in 'path' and check that any
// intermediate path is allowed. // intermediate path is allowed.
assert(hasPrefix(path, "/")); auto p = root + path;
try { try {
return canonPath(root + std::string(path), true); return p.resolveSymlinks();
} catch (Error &) { } catch (Error &) {
return canonPath(root + std::string(path)); return p;
} }
} }
void checkAllowed(PathView absPath) override void checkAllowed(const CanonPath & absPath) override
{ {
if (!isAllowed(absPath)) if (!isAllowed(absPath))
// FIXME: for Git trees, show a custom error message like // FIXME: for Git trees, show a custom error message like
@ -182,9 +175,9 @@ struct FSInputAccessorImpl : FSInputAccessor
throw Error("access to path '%s' is forbidden", absPath); throw Error("access to path '%s' is forbidden", absPath);
} }
bool isAllowed(PathView absPath) bool isAllowed(const CanonPath & absPath)
{ {
if (!isDirOrInDir(absPath, root)) if (!absPath.isWithin(root))
return false; return false;
if (allowedPaths) { if (allowedPaths) {
@ -205,7 +198,7 @@ struct FSInputAccessorImpl : FSInputAccessor
return true; return true;
} }
void allowPath(Path path) override void allowPath(CanonPath path) override
{ {
if (allowedPaths) if (allowedPaths)
allowedPaths->insert(std::move(path)); allowedPaths->insert(std::move(path));
@ -216,15 +209,15 @@ struct FSInputAccessorImpl : FSInputAccessor
return (bool) allowedPaths; return (bool) allowedPaths;
} }
std::string showPath(PathView path) override std::string showPath(const CanonPath & path) override
{ {
return root + path; return (root + path).abs();
} }
}; };
ref<FSInputAccessor> makeFSInputAccessor( ref<FSInputAccessor> makeFSInputAccessor(
const Path & root, const CanonPath & root,
std::optional<PathSet> && allowedPaths) std::optional<std::set<CanonPath>> && allowedPaths)
{ {
return make_ref<FSInputAccessorImpl>(root, std::move(allowedPaths)); return make_ref<FSInputAccessorImpl>(root, std::move(allowedPaths));
} }
@ -235,48 +228,42 @@ std::ostream & operator << (std::ostream & str, const SourcePath & path)
return str; return str;
} }
SourcePath SourcePath::append(std::string_view s) const
{
// FIXME: canonicalize?
return {accessor, path + s};
}
struct MemoryInputAccessorImpl : MemoryInputAccessor struct MemoryInputAccessorImpl : MemoryInputAccessor
{ {
std::map<Path, std::string> files; std::map<CanonPath, std::string> files;
std::string readFile(PathView path) override std::string readFile(const CanonPath & path) override
{ {
auto i = files.find((Path) path); auto i = files.find(path);
if (i == files.end()) if (i == files.end())
throw Error("file '%s' does not exist", path); throw Error("file '%s' does not exist", path);
return i->second; return i->second;
} }
bool pathExists(PathView path) override bool pathExists(const CanonPath & path) override
{ {
auto i = files.find((Path) path); auto i = files.find(path);
return i != files.end(); return i != files.end();
} }
Stat lstat(PathView path) override Stat lstat(const CanonPath & path) override
{ {
throw UnimplementedError("MemoryInputAccessor::lstat"); throw UnimplementedError("MemoryInputAccessor::lstat");
} }
DirEntries readDirectory(PathView path) override DirEntries readDirectory(const CanonPath & path) override
{ {
return {}; return {};
} }
std::string readLink(PathView path) override std::string readLink(const CanonPath & path) override
{ {
throw UnimplementedError("MemoryInputAccessor::readLink"); throw UnimplementedError("MemoryInputAccessor::readLink");
} }
void addFile(PathView path, std::string && contents) override void addFile(CanonPath path, std::string && contents) override
{ {
files.emplace(path, std::move(contents)); files.emplace(std::move(path), std::move(contents));
} }
}; };
@ -287,14 +274,14 @@ ref<MemoryInputAccessor> makeMemoryInputAccessor()
std::string_view SourcePath::baseName() const std::string_view SourcePath::baseName() const
{ {
// FIXME return path.baseName().value_or("source");
return path == "" || path == "/" ? "source" : baseNameOf(path);
} }
SourcePath SourcePath::parent() const SourcePath SourcePath::parent() const
{ {
// FIXME: auto p = path.parent();
return {accessor, dirOf(path)}; assert(p);
return {accessor, std::move(*p)};
} }
} }

View file

@ -3,6 +3,7 @@
#include "ref.hh" #include "ref.hh"
#include "types.hh" #include "types.hh"
#include "archive.hh" #include "archive.hh"
#include "canon-path.hh"
namespace nix { namespace nix {
@ -15,9 +16,9 @@ struct InputAccessor
virtual ~InputAccessor() virtual ~InputAccessor()
{ } { }
virtual std::string readFile(PathView path) = 0; virtual std::string readFile(const CanonPath & path) = 0;
virtual bool pathExists(PathView path) = 0; virtual bool pathExists(const CanonPath & path) = 0;
enum Type { tRegular, tSymlink, tDirectory, tMisc }; enum Type { tRegular, tSymlink, tDirectory, tMisc };
@ -28,18 +29,18 @@ struct InputAccessor
bool isExecutable = false; // regular files only bool isExecutable = false; // regular files only
}; };
virtual Stat lstat(PathView path) = 0; virtual Stat lstat(const CanonPath & path) = 0;
typedef std::optional<Type> DirEntry; typedef std::optional<Type> DirEntry;
typedef std::map<std::string, DirEntry> DirEntries; typedef std::map<std::string, DirEntry> DirEntries;
virtual DirEntries readDirectory(PathView path) = 0; virtual DirEntries readDirectory(const CanonPath & path) = 0;
virtual std::string readLink(PathView path) = 0; virtual std::string readLink(const CanonPath & path) = 0;
virtual void dumpPath( virtual void dumpPath(
const Path & path, const CanonPath & path,
Sink & sink, Sink & sink,
PathFilter & filter = defaultPathFilter); PathFilter & filter = defaultPathFilter);
@ -53,30 +54,30 @@ struct InputAccessor
return number < x.number; return number < x.number;
} }
virtual std::string showPath(PathView path); virtual std::string showPath(const CanonPath & path);
}; };
struct FSInputAccessor : InputAccessor struct FSInputAccessor : InputAccessor
{ {
virtual void checkAllowed(PathView absPath) = 0; virtual void checkAllowed(const CanonPath & absPath) = 0;
virtual void allowPath(Path path) = 0; virtual void allowPath(CanonPath path) = 0;
virtual bool hasAccessControl() = 0; virtual bool hasAccessControl() = 0;
}; };
ref<FSInputAccessor> makeFSInputAccessor( ref<FSInputAccessor> makeFSInputAccessor(
const Path & root, const CanonPath & root,
std::optional<PathSet> && allowedPaths = {}); std::optional<std::set<CanonPath>> && allowedPaths = {});
struct MemoryInputAccessor : InputAccessor struct MemoryInputAccessor : InputAccessor
{ {
virtual void addFile(PathView path, std::string && contents) = 0; virtual void addFile(CanonPath path, std::string && contents) = 0;
}; };
ref<MemoryInputAccessor> makeMemoryInputAccessor(); ref<MemoryInputAccessor> makeMemoryInputAccessor();
ref<InputAccessor> makeZipInputAccessor(PathView path); ref<InputAccessor> makeZipInputAccessor(const CanonPath & path);
ref<InputAccessor> makePatchingInputAccessor( ref<InputAccessor> makePatchingInputAccessor(
ref<InputAccessor> next, ref<InputAccessor> next,
@ -85,7 +86,7 @@ ref<InputAccessor> makePatchingInputAccessor(
struct SourcePath struct SourcePath
{ {
InputAccessor & accessor; InputAccessor & accessor;
Path path; CanonPath path;
std::string_view baseName() const; std::string_view baseName() const;
@ -111,7 +112,11 @@ struct SourcePath
std::string to_string() const std::string to_string() const
{ return accessor.showPath(path); } { return accessor.showPath(path); }
SourcePath append(std::string_view s) const; SourcePath operator + (const CanonPath & x) const
{ return {accessor, path + x}; }
SourcePath operator + (std::string_view c) const
{ return {accessor, path + c}; }
bool operator == (const SourcePath & x) const bool operator == (const SourcePath & x) const
{ {

View file

@ -7,7 +7,7 @@ struct PatchingInputAccessor : InputAccessor
{ {
ref<InputAccessor> next; ref<InputAccessor> next;
std::map<Path, std::vector<std::string>> patchesPerFile; std::map<CanonPath, std::vector<std::string>> patchesPerFile;
PatchingInputAccessor( PatchingInputAccessor(
ref<InputAccessor> next, ref<InputAccessor> next,
@ -29,7 +29,7 @@ struct PatchingInputAccessor : InputAccessor
if (slash == fileName.npos) return; if (slash == fileName.npos) return;
fileName = fileName.substr(slash); fileName = fileName.substr(slash);
debug("found patch for '%s'", fileName); debug("found patch for '%s'", fileName);
patchesPerFile.emplace(Path(fileName), std::vector<std::string>()) patchesPerFile.emplace(fileName, std::vector<std::string>())
.first->second.push_back(std::string(contents)); .first->second.push_back(std::string(contents));
}; };
@ -60,11 +60,11 @@ struct PatchingInputAccessor : InputAccessor
} }
} }
std::string readFile(PathView path) override std::string readFile(const CanonPath & path) override
{ {
auto contents = next->readFile(path); auto contents = next->readFile(path);
auto i = patchesPerFile.find((Path) path); auto i = patchesPerFile.find(path);
if (i != patchesPerFile.end()) { if (i != patchesPerFile.end()) {
for (auto & patch : i->second) { for (auto & patch : i->second) {
auto tempDir = createTempDir(); auto tempDir = createTempDir();
@ -84,22 +84,22 @@ struct PatchingInputAccessor : InputAccessor
return contents; return contents;
} }
bool pathExists(PathView path) override bool pathExists(const CanonPath & path) override
{ {
return next->pathExists(path); return next->pathExists(path);
} }
Stat lstat(PathView path) override Stat lstat(const CanonPath & path) override
{ {
return next->lstat(path); return next->lstat(path);
} }
DirEntries readDirectory(PathView path) override DirEntries readDirectory(const CanonPath & path) override
{ {
return next->readDirectory(path); return next->readDirectory(path);
} }
std::string readLink(PathView path) override std::string readLink(const CanonPath & path) override
{ {
return next->readLink(path); return next->readLink(path);
} }

View file

@ -81,25 +81,25 @@ struct PathInputScheme : InputScheme
// nothing to do // nothing to do
} }
Path getAbsPath(ref<Store> store, const Input & input) CanonPath getAbsPath(ref<Store> store, const Input & input)
{ {
auto path = getStrAttr(input.attrs, "path"); auto path = getStrAttr(input.attrs, "path");
if (path[0] == '/') if (path[0] == '/')
return path; return CanonPath(path);
if (!input.parent) if (!input.parent)
throw Error("cannot fetch input '%s' because it uses a relative path", input.to_string()); throw Error("cannot fetch input '%s' because it uses a relative path", input.to_string());
auto parent = canonPath(*input.parent); CanonPath parent(*input.parent);
// the path isn't relative, prefix it // the path isn't relative, prefix it
auto absPath = nix::absPath(path, parent); auto absPath = CanonPath(path, parent);
// for security, ensure that if the parent is a store path, it's inside it // for security, ensure that if the parent is a store path, it's inside it
if (store->isInStore(parent)) { if (store->isInStore(parent.abs())) {
auto storePath = store->printStorePath(store->toStorePath(parent).first); auto storePath = store->printStorePath(store->toStorePath(parent.abs()).first);
if (!isDirOrInDir(absPath, storePath)) if (!absPath.isWithin(CanonPath(storePath)))
throw BadStorePath("relative path '%s' points outside of its parent's store path '%s'", path, storePath); throw BadStorePath("relative path '%s' points outside of its parent's store path '%s'", path, storePath);
} }
@ -115,7 +115,7 @@ struct PathInputScheme : InputScheme
Activity act(*logger, lvlTalkative, actUnknown, fmt("copying '%s'", absPath)); Activity act(*logger, lvlTalkative, actUnknown, fmt("copying '%s'", absPath));
// FIXME: check whether access to 'path' is allowed. // FIXME: check whether access to 'path' is allowed.
auto storePath = store->maybeParseStorePath(absPath); auto storePath = store->maybeParseStorePath(absPath.abs());
if (storePath) if (storePath)
store->addTempRoot(*storePath); store->addTempRoot(*storePath);
@ -124,7 +124,7 @@ struct PathInputScheme : InputScheme
if (!storePath || storePath->name() != "source" || !store->isValidPath(*storePath)) { if (!storePath || storePath->name() != "source" || !store->isValidPath(*storePath)) {
// FIXME: try to substitute storePath. // FIXME: try to substitute storePath.
auto src = sinkToSource([&](Sink & sink) { auto src = sinkToSource([&](Sink & sink) {
mtime = dumpPathAndGetMtime(absPath, sink, defaultPathFilter); mtime = dumpPathAndGetMtime(absPath.abs(), sink, defaultPathFilter);
}); });
storePath = store->addToStoreFromDump(*src, "source"); storePath = store->addToStoreFromDump(*src, "source");
} }
@ -137,7 +137,7 @@ struct PathInputScheme : InputScheme
{ {
auto absPath = getAbsPath(store, input); auto absPath = getAbsPath(store, input);
auto input2(input); auto input2(input);
input2.attrs.emplace("path", absPath); input2.attrs.emplace("path", (std::string) absPath.abs());
return {makeFSInputAccessor(absPath), std::move(input2)}; return {makeFSInputAccessor(absPath), std::move(input2)};
} }
}; };

View file

@ -22,13 +22,13 @@ struct ZipMember
struct ZipInputAccessor : InputAccessor struct ZipInputAccessor : InputAccessor
{ {
Path zipPath; CanonPath zipPath;
struct zip * zipFile = nullptr; struct zip * zipFile = nullptr;
typedef std::map<const char *, struct zip_stat, cmp_str> Members; typedef std::map<const char *, struct zip_stat, cmp_str> Members;
Members members; Members members;
ZipInputAccessor(PathView _zipPath) ZipInputAccessor(const CanonPath & _zipPath)
: zipPath(_zipPath) : zipPath(_zipPath)
{ {
int error; int error;
@ -58,9 +58,9 @@ struct ZipInputAccessor : InputAccessor
if (zipFile) zip_close(zipFile); if (zipFile) zip_close(zipFile);
} }
std::string _readFile(PathView path) std::string _readFile(const CanonPath & path)
{ {
auto i = members.find(((std::string) path).c_str()); auto i = members.find(((std::string) path.abs()).c_str());
if (i == members.end()) if (i == members.end())
throw Error("file '%s' does not exist", path); throw Error("file '%s' does not exist", path);
@ -76,37 +76,32 @@ struct ZipInputAccessor : InputAccessor
return buf; return buf;
} }
std::string readFile(PathView _path) override std::string readFile(const CanonPath & path) override
{ {
auto path = canonPath(_path);
if (lstat(path).type != tRegular) if (lstat(path).type != tRegular)
throw Error("file '%s' is not a regular file"); throw Error("file '%s' is not a regular file");
return _readFile(path); return _readFile(path);
} }
bool pathExists(PathView _path) override bool pathExists(const CanonPath & path) override
{ {
auto path = canonPath(_path);
return return
members.find(((std::string) path).c_str()) != members.end() members.find(path.c_str()) != members.end()
|| members.find(((std::string) path + "/").c_str()) != members.end(); || members.find(((std::string) path.abs() + "/").c_str()) != members.end();
} }
Stat lstat(PathView _path) override Stat lstat(const CanonPath & path) override
{ {
auto path = canonPath(_path); if (path.isRoot())
if (path == "/")
return Stat { .type = tDirectory }; return Stat { .type = tDirectory };
Type type = tRegular; Type type = tRegular;
bool isExecutable = false; bool isExecutable = false;
auto i = members.find(((std::string) path).c_str()); auto i = members.find(path.c_str());
if (i == members.end()) { if (i == members.end()) {
i = members.find(((std::string) path + "/").c_str()); i = members.find(((std::string) path.abs() + "/").c_str());
type = tDirectory; type = tDirectory;
} }
if (i == members.end()) if (i == members.end())
@ -138,12 +133,12 @@ struct ZipInputAccessor : InputAccessor
return Stat { .type = type, .isExecutable = isExecutable }; return Stat { .type = type, .isExecutable = isExecutable };
} }
DirEntries readDirectory(PathView _path) override DirEntries readDirectory(const CanonPath & _path) override
{ {
auto path = canonPath(_path); std::string path(_path.abs());
if (path != "/") path += "/"; if (path != "/") path += "/";
auto i = members.find(((std::string) path).c_str()); auto i = members.find(path.c_str());
if (i == members.end()) if (i == members.end())
throw Error("directory '%s' does not exist", path); throw Error("directory '%s' does not exist", path);
@ -162,18 +157,16 @@ struct ZipInputAccessor : InputAccessor
return entries; return entries;
} }
std::string readLink(PathView _path) override std::string readLink(const CanonPath & path) override
{ {
auto path = canonPath(_path);
if (lstat(path).type != tSymlink) if (lstat(path).type != tSymlink)
throw Error("file '%s' is not a symlink"); throw Error("file '%s' is not a symlink");
return _readFile(canonPath(_path)); return _readFile(path);
} }
}; };
ref<InputAccessor> makeZipInputAccessor(PathView path) ref<InputAccessor> makeZipInputAccessor(const CanonPath & path)
{ {
return make_ref<ZipInputAccessor>(path); return make_ref<ZipInputAccessor>(path);
} }

View file

@ -448,7 +448,7 @@ std::string Derivation::unparse(const Store & store, bool maskOutputs,
// FIXME: remove // FIXME: remove
bool isDerivation(const std::string & fileName) bool isDerivation(std::string_view fileName)
{ {
return hasSuffix(fileName, drvExtension); return hasSuffix(fileName, drvExtension);
} }

View file

@ -224,7 +224,7 @@ StorePath writeDerivation(Store & store,
Derivation parseDerivation(const Store & store, std::string && s, std::string_view name); Derivation parseDerivation(const Store & store, std::string && s, std::string_view name);
// FIXME: remove // FIXME: remove
bool isDerivation(const std::string & fileName); bool isDerivation(std::string_view fileName);
/* Calculate the name that will be used for the store path for this /* Calculate the name that will be used for the store path for this
output. output.

View file

@ -17,21 +17,21 @@
namespace nix { namespace nix {
bool Store::isInStore(const Path & path) const bool Store::isInStore(PathView path) const
{ {
return isInDir(path, storeDir); return isInDir(path, storeDir);
} }
std::pair<StorePath, Path> Store::toStorePath(const Path & path) const std::pair<StorePath, Path> Store::toStorePath(PathView path) const
{ {
if (!isInStore(path)) if (!isInStore(path))
throw Error("path '%1%' is not in the Nix store", path); throw Error("path '%1%' is not in the Nix store", path);
Path::size_type slash = path.find('/', storeDir.size() + 1); auto slash = path.find('/', storeDir.size() + 1);
if (slash == Path::npos) if (slash == Path::npos)
return {parseStorePath(path), ""}; return {parseStorePath(path), ""};
else else
return {parseStorePath(std::string_view(path).substr(0, slash)), path.substr(slash)}; return {parseStorePath(path.substr(0, slash)), (Path) path.substr(slash)};
} }

View file

@ -178,7 +178,7 @@ public:
/* Return true if path is in the Nix store (but not the Nix /* Return true if path is in the Nix store (but not the Nix
store itself). */ store itself). */
bool isInStore(const Path & path) const; bool isInStore(PathView path) const;
/* Return true if path is a store path, i.e. a direct child of /* Return true if path is a store path, i.e. a direct child of
the Nix store. */ the Nix store. */
@ -186,7 +186,7 @@ public:
/* Split a path like /nix/store/<hash>-<name>/<bla> into /* Split a path like /nix/store/<hash>-<name>/<bla> into
/nix/store/<hash>-<name> and /<bla>. */ /nix/store/<hash>-<name> and /<bla>. */
std::pair<StorePath, Path> toStorePath(const Path & path) const; std::pair<StorePath, Path> toStorePath(PathView path) const;
/* Follow symlinks until we end up with a path in the Nix store. */ /* Follow symlinks until we end up with a path in the Nix store. */
Path followLinksToStore(std::string_view path) const; Path followLinksToStore(std::string_view path) const;

72
src/libutil/canon-path.cc Normal file
View file

@ -0,0 +1,72 @@
#include "canon-path.hh"
#include "util.hh"
namespace nix {
CanonPath CanonPath::root = CanonPath("/");
CanonPath::CanonPath(std::string_view raw)
: path(absPath((Path) raw, "/"))
{ }
CanonPath::CanonPath(std::string_view raw, const CanonPath & root)
: path(absPath((Path) raw, root.abs()))
{ }
std::optional<CanonPath> CanonPath::parent() const
{
if (isRoot()) return std::nullopt;
return CanonPath(unchecked_t(), path.substr(0, path.rfind('/')));
}
CanonPath CanonPath::resolveSymlinks() const
{
return CanonPath(unchecked_t(), canonPath(abs(), true));
}
bool CanonPath::isWithin(const CanonPath & parent) const
{
return !(
path.size() < parent.path.size()
|| path.substr(0, parent.path.size()) != parent.path
|| (parent.path.size() > 1 && path.size() > parent.path.size()
&& path[parent.path.size()] != '/'));
}
void CanonPath::extend(const CanonPath & x)
{
if (x.isRoot()) return;
if (isRoot())
path += x.rel();
else
path += x.abs();
}
CanonPath CanonPath::operator + (const CanonPath & x) const
{
auto res = *this;
res.extend(x);
return res;
}
void CanonPath::push(std::string_view c)
{
assert(c.find('/') == c.npos);
if (!isRoot()) path += '/';
path += c;
}
CanonPath CanonPath::operator + (std::string_view c) const
{
auto res = *this;
res.push(c);
return res;
}
std::ostream & operator << (std::ostream & stream, const CanonPath & path)
{
stream << path.abs();
return stream;
}
}

139
src/libutil/canon-path.hh Normal file
View file

@ -0,0 +1,139 @@
#pragma once
#include <string>
#include <optional>
#include <cassert>
#include <iostream>
namespace nix {
/* A canonical representation of a path. It ensures the following:
- It always starts with a slash.
- It never ends with a slash, except if the path is "/".
- A slash is never followed by a slash (i.e. no empty components).
- There are no components equal to '.' or '..'.
Note that the path does not need to correspond to an actually
existing path, and there is no guarantee that symlinks are
resolved.
*/
class CanonPath
{
std::string path;
public:
/* Construct a canon path from a non-canonical path. Any '.', '..'
or empty components are removed. */
CanonPath(std::string_view raw);
explicit CanonPath(const char * raw)
: CanonPath(std::string_view(raw))
{ }
struct unchecked_t { };
CanonPath(unchecked_t _, std::string path)
: path(std::move(path))
{ }
static CanonPath root;
/* If `raw` starts with a slash, return
`CanonPath(raw)`. Otherwise return a `CanonPath` representing
`root + "/" + raw`. */
CanonPath(std::string_view raw, const CanonPath & root);
bool isRoot() const
{ return path.size() <= 1; }
explicit operator std::string_view() const
{ return path; }
const std::string & abs() const
{ return path; }
const char * c_str() const
{ return path.c_str(); }
std::string_view rel() const
{ return ((std::string_view) path).substr(1); }
struct Iterator
{
std::string_view remaining;
size_t slash;
Iterator(std::string_view remaining)
: remaining(remaining)
, slash(remaining.find('/'))
{ }
bool operator != (const Iterator & x) const
{ return remaining.data() != x.remaining.data(); }
const std::string_view operator * () const
{ return remaining.substr(0, slash); }
void operator ++ ()
{
if (slash == remaining.npos)
remaining = remaining.substr(remaining.size());
else {
remaining = remaining.substr(slash + 1);
slash = remaining.find('/');
}
}
};
Iterator begin() { return Iterator(rel()); }
Iterator end() { return Iterator(rel().substr(path.size() - 1)); }
std::optional<CanonPath> parent() const;
std::optional<std::string_view> dirOf() const
{
if (isRoot()) return std::nullopt;
return path.substr(0, path.rfind('/'));
}
std::optional<std::string_view> baseName() const
{
if (isRoot()) return std::nullopt;
return ((std::string_view) path).substr(path.rfind('/') + 1);
}
bool operator == (const CanonPath & x) const
{ return path == x.path; }
bool operator != (const CanonPath & x) const
{ return path != x.path; }
bool operator < (const CanonPath & x) const
{ return path < x.path; }
CanonPath resolveSymlinks() const;
/* Return true if `this` is equal to `parent` or a child of
`parent`. */
bool isWithin(const CanonPath & parent) const;
/* Append another path to this one. */
void extend(const CanonPath & x);
/* Concatenate two paths. */
CanonPath operator + (const CanonPath & x) const;
/* Add a path component to this one. It must not contain any slashes. */
void push(std::string_view c);
CanonPath operator + (std::string_view c) const;
};
std::ostream & operator << (std::ostream & stream, const CanonPath & path);
}

View file

@ -148,7 +148,7 @@ inline hintformat hintfmt(const std::string & fs, const Args & ... args)
return f; return f;
} }
inline hintformat hintfmt(std::string plain_string) inline hintformat hintfmt(const std::string & plain_string)
{ {
// we won't be receiving any args in this case, so just print the original string // we won't be receiving any args in this case, so just print the original string
return hintfmt("%s", normaltxt(plain_string)); return hintfmt("%s", normaltxt(plain_string));

View file

@ -0,0 +1,98 @@
#include "canon-path.hh"
#include <gtest/gtest.h>
namespace nix {
TEST(CanonPath, basic) {
{
CanonPath p("/");
ASSERT_EQ(p.abs(), "/");
ASSERT_EQ(p.rel(), "");
ASSERT_EQ(p.baseName(), std::nullopt);
ASSERT_EQ(p.dirOf(), std::nullopt);
}
{
CanonPath p("/foo//");
ASSERT_EQ(p.abs(), "/foo");
ASSERT_EQ(p.rel(), "foo");
ASSERT_EQ(*p.baseName(), "foo");
ASSERT_EQ(*p.dirOf(), ""); // FIXME: do we want this?
}
{
CanonPath p("foo/bar");
ASSERT_EQ(p.abs(), "/foo/bar");
ASSERT_EQ(p.rel(), "foo/bar");
ASSERT_EQ(*p.baseName(), "bar");
ASSERT_EQ(*p.dirOf(), "/foo");
}
{
CanonPath p("foo//bar/");
ASSERT_EQ(p.abs(), "/foo/bar");
ASSERT_EQ(p.rel(), "foo/bar");
ASSERT_EQ(*p.baseName(), "bar");
ASSERT_EQ(*p.dirOf(), "/foo");
}
}
TEST(CanonPath, iter) {
{
CanonPath p("a//foo/bar//");
std::vector<std::string_view> ss;
for (auto & c : p) ss.push_back(c);
ASSERT_EQ(ss, std::vector<std::string_view>({"a", "foo", "bar"}));
}
{
CanonPath p("/");
std::vector<std::string_view> ss;
for (auto & c : p) ss.push_back(c);
ASSERT_EQ(ss, std::vector<std::string_view>());
}
}
TEST(CanonPath, concat) {
{
CanonPath p1("a//foo/bar//");
CanonPath p2("xyzzy/bla");
ASSERT_EQ((p1 + p2).abs(), "/a/foo/bar/xyzzy/bla");
}
{
CanonPath p1("/");
CanonPath p2("/a/b");
ASSERT_EQ((p1 + p2).abs(), "/a/b");
}
{
CanonPath p1("/a/b");
CanonPath p2("/");
ASSERT_EQ((p1 + p2).abs(), "/a/b");
}
{
CanonPath p("/foo/bar");
ASSERT_EQ((p + "x").abs(), "/foo/bar/x");
}
{
CanonPath p("/");
ASSERT_EQ((p + "foo" + "bar").abs(), "/foo/bar");
}
}
TEST(CanonPath, within) {
{
ASSERT_TRUE(CanonPath("foo").isWithin(CanonPath("foo")));
ASSERT_FALSE(CanonPath("foo").isWithin(CanonPath("bar")));
ASSERT_FALSE(CanonPath("foo").isWithin(CanonPath("fo")));
ASSERT_TRUE(CanonPath("foo/bar").isWithin(CanonPath("foo")));
ASSERT_FALSE(CanonPath("foo").isWithin(CanonPath("foo/bar")));
ASSERT_TRUE(CanonPath("/foo/bar/default.nix").isWithin(CanonPath("/")));
ASSERT_TRUE(CanonPath("/").isWithin(CanonPath("/")));
}
}
}

View file

@ -721,4 +721,11 @@ inline std::string operator + (std::string && s, std::string_view s2)
return s; return s;
} }
inline std::string operator + (std::string_view s1, const char * s2)
{
std::string s(s1);
s.append(s2);
return s;
}
} }

View file

@ -1477,7 +1477,7 @@ static int main_nix_env(int argc, char * * argv)
if (file != "") if (file != "")
// FIXME: check that the accessor returned by // FIXME: check that the accessor returned by
// lookupFileArg() is the root FS. // lookupFileArg() is the root FS.
globals.instSource.nixExprPath = lookupFileArg(*globals.state, file).path; globals.instSource.nixExprPath = lookupFileArg(*globals.state, file).path.abs();
globals.instSource.autoArgs = myArgs.getAutoArgs(*globals.state); globals.instSource.autoArgs = myArgs.getAutoArgs(*globals.state);

View file

@ -179,7 +179,7 @@ static void showHelp(std::vector<std::string> subcommand, MultiCommand & topleve
, state.rootPath("/")), *vGenerateManpage); , state.rootPath("/")), *vGenerateManpage);
state.corepkgsFS->addFile( state.corepkgsFS->addFile(
"/utils.nix", CanonPath("utils.nix"),
#include "utils.nix.gen.hh" #include "utils.nix.gen.hh"
); );