From a71f209330d7f93713401a9c8c085536947aefd8 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 16 May 2022 23:27:04 +0200 Subject: [PATCH] Add CanonPath wrapper to represent canonicalized paths --- src/libcmd/common-eval-args.cc | 4 +- src/libexpr/eval-cache.cc | 2 +- src/libexpr/eval.cc | 31 ++--- src/libexpr/eval.hh | 4 +- src/libexpr/flake/flake.cc | 10 +- src/libexpr/nixexpr.hh | 2 +- src/libexpr/parser.y | 16 +-- src/libexpr/paths.cc | 4 +- src/libexpr/primops.cc | 21 ++-- src/libexpr/primops/fetchTree.cc | 2 +- src/libexpr/tests/libexprtests.hh | 2 +- src/libexpr/value.hh | 8 +- src/libfetchers/fetchers.cc | 2 +- src/libfetchers/git.cc | 11 +- src/libfetchers/github.cc | 2 +- src/libfetchers/input-accessor.cc | 117 ++++++++--------- src/libfetchers/input-accessor.hh | 35 +++--- src/libfetchers/patching-input-accessor.cc | 16 +-- src/libfetchers/path.cc | 20 +-- src/libfetchers/zip-input-accessor.cc | 43 +++---- src/libstore/derivations.cc | 2 +- src/libstore/derivations.hh | 2 +- src/libstore/store-api.cc | 8 +- src/libstore/store-api.hh | 4 +- src/libutil/canon-path.cc | 72 +++++++++++ src/libutil/canon-path.hh | 139 +++++++++++++++++++++ src/libutil/fmt.hh | 2 +- src/libutil/tests/canon-path.cc | 98 +++++++++++++++ src/libutil/util.hh | 7 ++ src/nix-env/nix-env.cc | 2 +- src/nix/main.cc | 2 +- 31 files changed, 503 insertions(+), 187 deletions(-) create mode 100644 src/libutil/canon-path.cc create mode 100644 src/libutil/canon-path.hh create mode 100644 src/libutil/tests/canon-path.cc diff --git a/src/libcmd/common-eval-args.cc b/src/libcmd/common-eval-args.cc index 6651b0da1..d094989af 100644 --- a/src/libcmd/common-eval-args.cc +++ b/src/libcmd/common-eval-args.cc @@ -94,8 +94,8 @@ SourcePath lookupFileArg(EvalState & state, std::string_view s) if (isUri(s)) { auto storePath = fetchers::downloadTarball( state.store, resolveUri(s), "source", false).first.storePath; - auto & accessor = state.registerAccessor(makeFSInputAccessor(state.store->toRealPath(storePath))); - return {accessor, "/"}; + auto & accessor = state.registerAccessor(makeFSInputAccessor(CanonPath(state.store->toRealPath(storePath)))); + return {accessor, CanonPath::root}; } else if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') { Path p(s.substr(1, s.size() - 2)); return state.findFile(p); diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index f057a49fa..e581be846 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -425,7 +425,7 @@ Value & AttrCursor::forceValue() else if (v.type() == nPath) { // FIXME: take accessor into account? 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) cachedValue = {root->db->setBool(getKey(), v.boolean), v.boolean}; diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 4e1de843c..1918a0c62 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -461,9 +461,9 @@ EvalState::EvalState( , sPrefix(symbols.create("prefix")) , repair(NoRepair) , emptyBindings(0) - , rootFS(makeFSInputAccessor("", + , rootFS(makeFSInputAccessor(CanonPath::root, evalSettings.restrictEval || evalSettings.pureEval - ? std::optional(PathSet()) + ? std::optional>(std::set()) : std::nullopt)) , corepkgsFS(makeMemoryInputAccessor()) , store(store) @@ -515,7 +515,7 @@ EvalState::EvalState( createBaseEnv(); corepkgsFS->addFile( - "/fetchurl.nix", + CanonPath("fetchurl.nix"), #include "fetchurl.nix.gen.hh" ); } @@ -528,15 +528,15 @@ EvalState::~EvalState() void EvalState::allowPath(const Path & path) { - rootFS->allowPath(path); + rootFS->allowPath(CanonPath(path)); // FIXME } 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); @@ -612,12 +612,12 @@ void EvalState::checkURI(const std::string & uri) /* If the URI is a path, then check it against allowedPaths as well. */ if (hasPrefix(uri, "/")) { - rootFS->checkAllowed(uri); + rootFS->checkAllowed(CanonPath(uri)); return; } if (hasPrefix(uri, "file://")) { - rootFS->checkAllowed(uri.substr(7)); + rootFS->checkAllowed(CanonPath(uri.substr(7))); 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 { .msg = hintfmt(s, s2), @@ -890,7 +890,7 @@ void Value::mkStringMove(const char * s, const PathSet & context) 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) { if (!context.empty()) 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 v.mkStringMove(c_str(), context); } @@ -2027,7 +2027,7 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet auto path = v.path(); return copyToStore ? store->printStorePath(copyPathToStore(context, path)) - : path.path; + : BackedStringView((Path) path.path.abs()); } 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) { - if (nix::isDerivation(path.path)) + if (nix::isDerivation(path.path.abs())) throw EvalError("file names are not allowed to end in '%s'", drvExtension); auto i = srcToStore.find(path); @@ -2103,7 +2103,10 @@ SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & contex if (v.type() == nString) { 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) diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 505f6e538..0d42bc122 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -160,7 +160,7 @@ public: SearchPath getSearchPath() { return searchPath; } - SourcePath rootPath(Path path); + SourcePath rootPath(const Path & path); InputAccessor & registerAccessor(ref accessor); @@ -256,7 +256,7 @@ public: void throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, const std::string & s2) const; [[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]] void throwEvalError(const char * s, const std::string & s2, const std::string & s3) const; [[gnu::noinline, gnu::noreturn]] diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index 1951dbd9a..792e205dc 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -206,8 +206,8 @@ static Flake readFlake( InputAccessor & accessor, const InputPath & lockRootPath) { - auto flakeDir = canonPath("/" + resolvedRef.subdir); - SourcePath flakePath{accessor, canonPath(flakeDir + "/flake.nix")}; + CanonPath flakeDir(resolvedRef.subdir); + SourcePath flakePath{accessor, flakeDir + CanonPath("flake.nix")}; if (!flakePath.pathExists()) 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"); 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"); @@ -337,7 +337,7 @@ Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup static LockFile readLockFile(const Flake & flake) { - auto lockFilePath = flake.path.parent().append("/flake.lock"); + auto lockFilePath = flake.path.parent() + "flake.lock"; return lockFilePath.pathExists() ? LockFile(lockFilePath.readFile(), fmt("%s", lockFilePath)) : LockFile(); @@ -721,7 +721,7 @@ void callFlake(EvalState & state, emitTreeAttrs( state, - {lockedFlake.flake.path.accessor, "/"}, + {lockedFlake.flake.path.accessor, CanonPath::root}, lockedFlake.flake.lockedRef.input, *vRootSrc, false, diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 2cb2aca3a..67e95b2f5 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -188,7 +188,7 @@ struct ExprPath : Expr ExprPath(SourcePath && _path) : path(_path) { - v.mkPath(&path.accessor, path.path.c_str()); + v.mkPath(&path.accessor, path.path.abs().data()); } COMMON_METHODS Value * maybeThunk(EvalState & state, Env & env); diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 4c10d137d..f3b8c01d7 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -508,10 +508,12 @@ string_parts_interpolated path_start : 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 */ if ($1.p[$1.l-1] == '/' && $1.l > 1) path.path += "/"; + #endif $$ = new ExprPath(std::move(path)); } | HPATH { @@ -699,7 +701,7 @@ SourcePath resolveExprPath(const SourcePath & path) #endif // FIXME - auto path2 = path.append("/default.nix"); + auto path2 = path + "default.nix"; 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 buffer.append("\0\0", 2); // 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)); } if (auto path = resolveSearchPathElem(i)) { - auto res = path->append("/" + suffix); + auto res = *path + CanonPath(suffix); if (res.pathExists()) return res; } } if (hasPrefix(path, "nix/")) - return {*corepkgsFS, (std::string) path.substr(3)}; + return {*corepkgsFS, CanonPath(path.substr(3))}; throw ThrownError({ .msg = hintfmt(evalSettings.pureEval @@ -808,8 +810,8 @@ std::optional EvalState::resolveSearchPathElem(const SearchPathElem try { auto storePath = fetchers::downloadTarball( store, resolveUri(elem.second), "source", false).first.storePath; - auto & accessor = registerAccessor(makeFSInputAccessor(store->toRealPath(storePath))); - res.emplace(SourcePath {accessor, "/"}); + auto & accessor = registerAccessor(makeFSInputAccessor(CanonPath(store->toRealPath(storePath)))); + res.emplace(SourcePath {accessor, CanonPath::root}); } catch (FileTransferError & e) { logWarning({ .msg = hintfmt("Nix search path entry '%1%' cannot be downloaded, ignoring", elem.second) diff --git a/src/libexpr/paths.cc b/src/libexpr/paths.cc index 950c2b41b..af5423e84 100644 --- a/src/libexpr/paths.cc +++ b/src/libexpr/paths.cc @@ -3,9 +3,9 @@ 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 accessor) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index eb973b2ec..3d0601e25 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -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) { PathSet 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({ @@ -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 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 = path.resolveSymlinks(); + if (!state.store->isInStore(path.abs())) throw 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({ @@ -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); // FIXME: only do queryPathInfo if path.accessor is the store accessor auto refs = - state.store->isInStore(path.path) ? - state.store->queryPathInfo(state.store->toStorePath(path.path).first)->references : + state.store->isInStore(path.path.abs()) ? + state.store->queryPathInfo(state.store->toStorePath(path.path.abs()).first)->references : StorePathSet{}; auto context = state.store->printStorePathSet(refs); v.mkString(s, context); @@ -1949,14 +1950,14 @@ static void addPath( #endif PathFilter filter = filterFun ? ([&](const Path & p) { - SourcePath path2{path.accessor, canonPath(p)}; + SourcePath path2{path.accessor, CanonPath(p)}; auto st = path2.lstat(); /* Call the filter function. The first argument is the path, the second is a string indicating the type of the file. */ Value arg1; - arg1.mkString(path2.path); + arg1.mkString(path2.path.abs()); Value arg2; // assert that type is not "unknown" diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc index b2f62c7af..248af9f2f 100644 --- a/src/libexpr/primops/fetchTree.cc +++ b/src/libexpr/primops/fetchTree.cc @@ -195,7 +195,7 @@ static void fetchTree( emitTreeAttrs( state, - {state.registerAccessor(accessor), "/"}, + {state.registerAccessor(accessor), CanonPath::root}, input2, v, params.emptyRevFallback, diff --git a/src/libexpr/tests/libexprtests.hh b/src/libexpr/tests/libexprtests.hh index 0286be3b3..8370a5912 100644 --- a/src/libexpr/tests/libexprtests.hh +++ b/src/libexpr/tests/libexprtests.hh @@ -104,7 +104,7 @@ namespace nix { return false; } else { 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; return false; } diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index fb1139647..39016fc7c 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -415,7 +415,13 @@ public: SourcePath path() const { 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); } }; diff --git a/src/libfetchers/fetchers.cc b/src/libfetchers/fetchers.cc index 557d1b17a..f26ee811b 100644 --- a/src/libfetchers/fetchers.cc +++ b/src/libfetchers/fetchers.cc @@ -315,7 +315,7 @@ std::pair, Input> InputScheme::lazyFetch(ref store, co { auto [storePath, input2] = fetch(store, input); - return {makeFSInputAccessor(store->toRealPath(storePath)), input2}; + return {makeFSInputAccessor(CanonPath(store->toRealPath(storePath))), input2}; } } diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index bdfb22612..3b0d0f74a 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -395,17 +395,17 @@ struct GitInputScheme : InputScheme return repoInfo; } - std::set listFiles(const RepoInfo & repoInfo) + std::set listFiles(const RepoInfo & repoInfo) { auto gitOpts = Strings({ "-C", repoInfo.url, "ls-files", "-z" }); if (repoInfo.submodules) gitOpts.emplace_back("--recurse-submodules"); - std::set res; + std::set res; for (auto & p : tokenizeString>( runProgram("git", true, gitOpts), "\0"s)) - res.insert(canonPath("/" + p)); + res.insert(CanonPath(p)); return res; } @@ -683,6 +683,8 @@ struct GitInputScheme : InputScheme auto files = listFiles(repoInfo); PathFilter filter = [&](const Path & p) -> bool { + abort(); +#if 0 assert(hasPrefix(p, repoInfo.url)); std::string file(p, repoInfo.url.size() + 1); @@ -695,6 +697,7 @@ struct GitInputScheme : InputScheme } return files.count(file); +#endif }; auto storePath = store->addToStore(input.getName(), repoInfo.url, FileIngestionMethod::Recursive, htSHA256, filter); @@ -724,7 +727,7 @@ struct GitInputScheme : InputScheme // FIXME: return updated input. - return {makeFSInputAccessor(repoInfo.url, listFiles(repoInfo)), input}; + return {makeFSInputAccessor(CanonPath(repoInfo.url), listFiles(repoInfo)), input}; } }; diff --git a/src/libfetchers/github.cc b/src/libfetchers/github.cc index 851b082c9..93138398a 100644 --- a/src/libfetchers/github.cc +++ b/src/libfetchers/github.cc @@ -233,7 +233,7 @@ struct GitArchiveInputScheme : InputScheme { auto [storePath, input2] = downloadArchive(store, input); - return {makeZipInputAccessor(store->toRealPath(storePath)), input2}; + return {makeZipInputAccessor(CanonPath(store->toRealPath(storePath))), input2}; } }; diff --git a/src/libfetchers/input-accessor.cc b/src/libfetchers/input-accessor.cc index 71eebcdbf..04c3c2618 100644 --- a/src/libfetchers/input-accessor.cc +++ b/src/libfetchers/input-accessor.cc @@ -17,11 +17,11 @@ const std::string narVersionMagic1 = "nix-archive-1"; static std::string caseHackSuffix = "~nix~case~hack~"; void InputAccessor::dumpPath( - const Path & path, + const CanonPath & path, Sink & sink, PathFilter & filter) { - auto dumpContents = [&](PathView path) + auto dumpContents = [&](const CanonPath & path) { // FIXME: pipe auto s = readFile(path); @@ -30,9 +30,9 @@ void InputAccessor::dumpPath( writePadding(s.size(), sink); }; - std::function dump; + std::function dump; - dump = [&](const std::string & path) { + dump = [&](const CanonPath & path) { checkInterrupt(); auto st = lstat(path); @@ -57,20 +57,20 @@ void InputAccessor::dumpPath( std::string name(i.first); size_t pos = i.first.find(caseHackSuffix); 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); } if (!unhacked.emplace(name, i.first).second) throw Error("file name collision in between '%s' and '%s'", - (path + "/" + unhacked[name]), - (path + "/" + i.first)); + (path + unhacked[name]), + (path + i.first)); } else unhacked.emplace(i.first, i.first); for (auto & i : unhacked) - if (filter(path + "/" + i.first)) { + if (filter((path + i.first).abs())) { sink << "entry" << "(" << "name" << i.first << "node"; - dump(path + "/" + i.second); + dump(path + i.second); sink << ")"; } } @@ -87,46 +87,39 @@ void InputAccessor::dumpPath( 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 { - Path root; - std::optional allowedPaths; + CanonPath root; + std::optional> allowedPaths; - FSInputAccessorImpl(const Path & root, std::optional && allowedPaths) + FSInputAccessorImpl(const CanonPath & root, std::optional> && allowedPaths) : root(root) , 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); 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); - 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); checkAllowed(absPath); - auto st = nix::lstat(absPath); + auto st = nix::lstat(absPath.abs()); return Stat { .type = 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); checkAllowed(absPath); DirEntries res; - for (auto & entry : nix::readDirectory(absPath)) { + for (auto & entry : nix::readDirectory(absPath.abs())) { std::optional type; switch (entry.type) { case DT_REG: type = Type::tRegular; break; case DT_LNK: type = Type::tSymlink; break; case DT_DIR: type = Type::tDirectory; break; } - if (isAllowed(absPath + "/" + entry.name)) + if (isAllowed(absPath + entry.name)) res.emplace(entry.name, type); } return res; } - std::string readLink(PathView path) override + std::string readLink(const CanonPath & path) override { auto absPath = makeAbsPath(path); 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 // intermediate path is allowed. - assert(hasPrefix(path, "/")); + auto p = root + path; try { - return canonPath(root + std::string(path), true); + return p.resolveSymlinks(); } catch (Error &) { - return canonPath(root + std::string(path)); + return p; } } - void checkAllowed(PathView absPath) override + void checkAllowed(const CanonPath & absPath) override { if (!isAllowed(absPath)) // 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); } - bool isAllowed(PathView absPath) + bool isAllowed(const CanonPath & absPath) { - if (!isDirOrInDir(absPath, root)) + if (!absPath.isWithin(root)) return false; if (allowedPaths) { @@ -205,7 +198,7 @@ struct FSInputAccessorImpl : FSInputAccessor return true; } - void allowPath(Path path) override + void allowPath(CanonPath path) override { if (allowedPaths) allowedPaths->insert(std::move(path)); @@ -216,15 +209,15 @@ struct FSInputAccessorImpl : FSInputAccessor return (bool) allowedPaths; } - std::string showPath(PathView path) override + std::string showPath(const CanonPath & path) override { - return root + path; + return (root + path).abs(); } }; ref makeFSInputAccessor( - const Path & root, - std::optional && allowedPaths) + const CanonPath & root, + std::optional> && allowedPaths) { return make_ref(root, std::move(allowedPaths)); } @@ -235,48 +228,42 @@ std::ostream & operator << (std::ostream & str, const SourcePath & path) return str; } -SourcePath SourcePath::append(std::string_view s) const -{ - // FIXME: canonicalize? - return {accessor, path + s}; -} - struct MemoryInputAccessorImpl : MemoryInputAccessor { - std::map files; + std::map 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()) throw Error("file '%s' does not exist", path); 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(); } - Stat lstat(PathView path) override + Stat lstat(const CanonPath & path) override { throw UnimplementedError("MemoryInputAccessor::lstat"); } - DirEntries readDirectory(PathView path) override + DirEntries readDirectory(const CanonPath & path) override { return {}; } - std::string readLink(PathView path) override + std::string readLink(const CanonPath & path) override { 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 makeMemoryInputAccessor() std::string_view SourcePath::baseName() const { - // FIXME - return path == "" || path == "/" ? "source" : baseNameOf(path); + return path.baseName().value_or("source"); } SourcePath SourcePath::parent() const { - // FIXME: - return {accessor, dirOf(path)}; + auto p = path.parent(); + assert(p); + return {accessor, std::move(*p)}; } } diff --git a/src/libfetchers/input-accessor.hh b/src/libfetchers/input-accessor.hh index d7fc3f11d..6f4c3ef6b 100644 --- a/src/libfetchers/input-accessor.hh +++ b/src/libfetchers/input-accessor.hh @@ -3,6 +3,7 @@ #include "ref.hh" #include "types.hh" #include "archive.hh" +#include "canon-path.hh" namespace nix { @@ -15,9 +16,9 @@ struct 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 }; @@ -28,18 +29,18 @@ struct InputAccessor bool isExecutable = false; // regular files only }; - virtual Stat lstat(PathView path) = 0; + virtual Stat lstat(const CanonPath & path) = 0; typedef std::optional DirEntry; typedef std::map 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( - const Path & path, + const CanonPath & path, Sink & sink, PathFilter & filter = defaultPathFilter); @@ -53,30 +54,30 @@ struct InputAccessor return number < x.number; } - virtual std::string showPath(PathView path); + virtual std::string showPath(const CanonPath & path); }; 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; }; ref makeFSInputAccessor( - const Path & root, - std::optional && allowedPaths = {}); + const CanonPath & root, + std::optional> && allowedPaths = {}); struct MemoryInputAccessor : InputAccessor { - virtual void addFile(PathView path, std::string && contents) = 0; + virtual void addFile(CanonPath path, std::string && contents) = 0; }; ref makeMemoryInputAccessor(); -ref makeZipInputAccessor(PathView path); +ref makeZipInputAccessor(const CanonPath & path); ref makePatchingInputAccessor( ref next, @@ -85,7 +86,7 @@ ref makePatchingInputAccessor( struct SourcePath { InputAccessor & accessor; - Path path; + CanonPath path; std::string_view baseName() const; @@ -111,7 +112,11 @@ struct SourcePath std::string to_string() const { 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 { diff --git a/src/libfetchers/patching-input-accessor.cc b/src/libfetchers/patching-input-accessor.cc index 269f13c22..07aa8316c 100644 --- a/src/libfetchers/patching-input-accessor.cc +++ b/src/libfetchers/patching-input-accessor.cc @@ -7,7 +7,7 @@ struct PatchingInputAccessor : InputAccessor { ref next; - std::map> patchesPerFile; + std::map> patchesPerFile; PatchingInputAccessor( ref next, @@ -29,7 +29,7 @@ struct PatchingInputAccessor : InputAccessor if (slash == fileName.npos) return; fileName = fileName.substr(slash); debug("found patch for '%s'", fileName); - patchesPerFile.emplace(Path(fileName), std::vector()) + patchesPerFile.emplace(fileName, std::vector()) .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 i = patchesPerFile.find((Path) path); + auto i = patchesPerFile.find(path); if (i != patchesPerFile.end()) { for (auto & patch : i->second) { auto tempDir = createTempDir(); @@ -84,22 +84,22 @@ struct PatchingInputAccessor : InputAccessor return contents; } - bool pathExists(PathView path) override + bool pathExists(const CanonPath & path) override { return next->pathExists(path); } - Stat lstat(PathView path) override + Stat lstat(const CanonPath & path) override { return next->lstat(path); } - DirEntries readDirectory(PathView path) override + DirEntries readDirectory(const CanonPath & path) override { return next->readDirectory(path); } - std::string readLink(PathView path) override + std::string readLink(const CanonPath & path) override { return next->readLink(path); } diff --git a/src/libfetchers/path.cc b/src/libfetchers/path.cc index cc51bf89a..d51a7f362 100644 --- a/src/libfetchers/path.cc +++ b/src/libfetchers/path.cc @@ -81,25 +81,25 @@ struct PathInputScheme : InputScheme // nothing to do } - Path getAbsPath(ref store, const Input & input) + CanonPath getAbsPath(ref store, const Input & input) { auto path = getStrAttr(input.attrs, "path"); if (path[0] == '/') - return path; + return CanonPath(path); if (!input.parent) 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 - 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 - if (store->isInStore(parent)) { - auto storePath = store->printStorePath(store->toStorePath(parent).first); - if (!isDirOrInDir(absPath, storePath)) + if (store->isInStore(parent.abs())) { + auto storePath = store->printStorePath(store->toStorePath(parent.abs()).first); + if (!absPath.isWithin(CanonPath(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)); // FIXME: check whether access to 'path' is allowed. - auto storePath = store->maybeParseStorePath(absPath); + auto storePath = store->maybeParseStorePath(absPath.abs()); if (storePath) store->addTempRoot(*storePath); @@ -124,7 +124,7 @@ struct PathInputScheme : InputScheme if (!storePath || storePath->name() != "source" || !store->isValidPath(*storePath)) { // FIXME: try to substitute storePath. auto src = sinkToSource([&](Sink & sink) { - mtime = dumpPathAndGetMtime(absPath, sink, defaultPathFilter); + mtime = dumpPathAndGetMtime(absPath.abs(), sink, defaultPathFilter); }); storePath = store->addToStoreFromDump(*src, "source"); } @@ -137,7 +137,7 @@ struct PathInputScheme : InputScheme { auto absPath = getAbsPath(store, input); auto input2(input); - input2.attrs.emplace("path", absPath); + input2.attrs.emplace("path", (std::string) absPath.abs()); return {makeFSInputAccessor(absPath), std::move(input2)}; } }; diff --git a/src/libfetchers/zip-input-accessor.cc b/src/libfetchers/zip-input-accessor.cc index d8babd4e5..ac640dec9 100644 --- a/src/libfetchers/zip-input-accessor.cc +++ b/src/libfetchers/zip-input-accessor.cc @@ -22,13 +22,13 @@ struct ZipMember struct ZipInputAccessor : InputAccessor { - Path zipPath; + CanonPath zipPath; struct zip * zipFile = nullptr; typedef std::map Members; Members members; - ZipInputAccessor(PathView _zipPath) + ZipInputAccessor(const CanonPath & _zipPath) : zipPath(_zipPath) { int error; @@ -58,9 +58,9 @@ struct ZipInputAccessor : InputAccessor 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()) throw Error("file '%s' does not exist", path); @@ -76,37 +76,32 @@ struct ZipInputAccessor : InputAccessor return buf; } - std::string readFile(PathView _path) override + std::string readFile(const CanonPath & path) override { - auto path = canonPath(_path); - if (lstat(path).type != tRegular) throw Error("file '%s' is not a regular file"); return _readFile(path); } - bool pathExists(PathView _path) override + bool pathExists(const CanonPath & path) override { - auto path = canonPath(_path); return - members.find(((std::string) path).c_str()) != members.end() - || members.find(((std::string) path + "/").c_str()) != members.end(); + members.find(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 == "/") + if (path.isRoot()) return Stat { .type = tDirectory }; Type type = tRegular; bool isExecutable = false; - auto i = members.find(((std::string) path).c_str()); + auto i = members.find(path.c_str()); if (i == members.end()) { - i = members.find(((std::string) path + "/").c_str()); + i = members.find(((std::string) path.abs() + "/").c_str()); type = tDirectory; } if (i == members.end()) @@ -138,12 +133,12 @@ struct ZipInputAccessor : InputAccessor 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 += "/"; - auto i = members.find(((std::string) path).c_str()); + auto i = members.find(path.c_str()); if (i == members.end()) throw Error("directory '%s' does not exist", path); @@ -162,18 +157,16 @@ struct ZipInputAccessor : InputAccessor return entries; } - std::string readLink(PathView _path) override + std::string readLink(const CanonPath & path) override { - auto path = canonPath(_path); - if (lstat(path).type != tSymlink) throw Error("file '%s' is not a symlink"); - return _readFile(canonPath(_path)); + return _readFile(path); } }; -ref makeZipInputAccessor(PathView path) +ref makeZipInputAccessor(const CanonPath & path) { return make_ref(path); } diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc index fe99c3c5e..42a53912e 100644 --- a/src/libstore/derivations.cc +++ b/src/libstore/derivations.cc @@ -448,7 +448,7 @@ std::string Derivation::unparse(const Store & store, bool maskOutputs, // FIXME: remove -bool isDerivation(const std::string & fileName) +bool isDerivation(std::string_view fileName) { return hasSuffix(fileName, drvExtension); } diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh index af198a767..f3cd87fb1 100644 --- a/src/libstore/derivations.hh +++ b/src/libstore/derivations.hh @@ -224,7 +224,7 @@ StorePath writeDerivation(Store & store, Derivation parseDerivation(const Store & store, std::string && s, std::string_view name); // 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 output. diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc index cb9932c11..15aa40a68 100644 --- a/src/libstore/store-api.cc +++ b/src/libstore/store-api.cc @@ -17,21 +17,21 @@ namespace nix { -bool Store::isInStore(const Path & path) const +bool Store::isInStore(PathView path) const { return isInDir(path, storeDir); } -std::pair Store::toStorePath(const Path & path) const +std::pair Store::toStorePath(PathView path) const { if (!isInStore(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) return {parseStorePath(path), ""}; else - return {parseStorePath(std::string_view(path).substr(0, slash)), path.substr(slash)}; + return {parseStorePath(path.substr(0, slash)), (Path) path.substr(slash)}; } diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh index 9d36ed93b..8b2ff8a14 100644 --- a/src/libstore/store-api.hh +++ b/src/libstore/store-api.hh @@ -178,7 +178,7 @@ public: /* Return true if ‘path’ is in the Nix store (but not the Nix 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 the Nix store. */ @@ -186,7 +186,7 @@ public: /* Split a path like /nix/store/-/ into /nix/store/- and /. */ - std::pair toStorePath(const Path & path) const; + std::pair toStorePath(PathView path) const; /* Follow symlinks until we end up with a path in the Nix store. */ Path followLinksToStore(std::string_view path) const; diff --git a/src/libutil/canon-path.cc b/src/libutil/canon-path.cc new file mode 100644 index 000000000..e00d5caa8 --- /dev/null +++ b/src/libutil/canon-path.cc @@ -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::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; +} + +} diff --git a/src/libutil/canon-path.hh b/src/libutil/canon-path.hh new file mode 100644 index 000000000..036de37e4 --- /dev/null +++ b/src/libutil/canon-path.hh @@ -0,0 +1,139 @@ +#pragma once + +#include +#include +#include +#include + +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 parent() const; + + std::optional dirOf() const + { + if (isRoot()) return std::nullopt; + return path.substr(0, path.rfind('/')); + } + + std::optional 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); + +} diff --git a/src/libutil/fmt.hh b/src/libutil/fmt.hh index 7664e5c04..e879fd3b8 100644 --- a/src/libutil/fmt.hh +++ b/src/libutil/fmt.hh @@ -148,7 +148,7 @@ inline hintformat hintfmt(const std::string & fs, const Args & ... args) 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 return hintfmt("%s", normaltxt(plain_string)); diff --git a/src/libutil/tests/canon-path.cc b/src/libutil/tests/canon-path.cc new file mode 100644 index 000000000..b1e343d5f --- /dev/null +++ b/src/libutil/tests/canon-path.cc @@ -0,0 +1,98 @@ +#include "canon-path.hh" + +#include + +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 ss; + for (auto & c : p) ss.push_back(c); + ASSERT_EQ(ss, std::vector({"a", "foo", "bar"})); + } + + { + CanonPath p("/"); + std::vector ss; + for (auto & c : p) ss.push_back(c); + ASSERT_EQ(ss, std::vector()); + } + } + + 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("/"))); + } + } +} diff --git a/src/libutil/util.hh b/src/libutil/util.hh index ea90f60e7..d0d2bc02f 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -721,4 +721,11 @@ inline std::string operator + (std::string && s, std::string_view s2) return s; } +inline std::string operator + (std::string_view s1, const char * s2) +{ + std::string s(s1); + s.append(s2); + return s; +} + } diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc index 21464440d..67d650e20 100644 --- a/src/nix-env/nix-env.cc +++ b/src/nix-env/nix-env.cc @@ -1477,7 +1477,7 @@ static int main_nix_env(int argc, char * * argv) if (file != "") // FIXME: check that the accessor returned by // 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); diff --git a/src/nix/main.cc b/src/nix/main.cc index 22bc79156..c83fd86f2 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -179,7 +179,7 @@ static void showHelp(std::vector subcommand, MultiCommand & topleve , state.rootPath("/")), *vGenerateManpage); state.corepkgsFS->addFile( - "/utils.nix", + CanonPath("utils.nix"), #include "utils.nix.gen.hh" );