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)) {
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);

View file

@ -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};

View file

@ -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>(PathSet())
? std::optional<std::set<CanonPath>>(std::set<CanonPath>())
: 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)

View file

@ -160,7 +160,7 @@ public:
SearchPath getSearchPath() { return searchPath; }
SourcePath rootPath(Path path);
SourcePath rootPath(const Path & path);
InputAccessor & registerAccessor(ref<InputAccessor> 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]]

View file

@ -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,

View file

@ -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);

View file

@ -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<SourcePath> 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)

View file

@ -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<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)
{
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"

View file

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

View file

@ -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;
}

View file

@ -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);
}
};

View file

@ -315,7 +315,7 @@ std::pair<ref<InputAccessor>, Input> InputScheme::lazyFetch(ref<Store> store, co
{
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;
}
std::set<std::string> listFiles(const RepoInfo & repoInfo)
std::set<CanonPath> listFiles(const RepoInfo & repoInfo)
{
auto gitOpts = Strings({ "-C", repoInfo.url, "ls-files", "-z" });
if (repoInfo.submodules)
gitOpts.emplace_back("--recurse-submodules");
std::set<std::string> res;
std::set<CanonPath> res;
for (auto & p : tokenizeString<std::set<std::string>>(
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};
}
};

View file

@ -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};
}
};

View file

@ -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<void(const std::string & path)> dump;
std::function<void(const CanonPath & path)> 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<PathSet> allowedPaths;
CanonPath root;
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)
, 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> 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<FSInputAccessor> makeFSInputAccessor(
const Path & root,
std::optional<PathSet> && allowedPaths)
const CanonPath & root,
std::optional<std::set<CanonPath>> && allowedPaths)
{
return make_ref<FSInputAccessorImpl>(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<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())
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<MemoryInputAccessor> 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)};
}
}

View file

@ -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<Type> DirEntry;
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(
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<FSInputAccessor> makeFSInputAccessor(
const Path & root,
std::optional<PathSet> && allowedPaths = {});
const CanonPath & root,
std::optional<std::set<CanonPath>> && allowedPaths = {});
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<InputAccessor> makeZipInputAccessor(PathView path);
ref<InputAccessor> makeZipInputAccessor(const CanonPath & path);
ref<InputAccessor> makePatchingInputAccessor(
ref<InputAccessor> next,
@ -85,7 +86,7 @@ ref<InputAccessor> 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
{

View file

@ -7,7 +7,7 @@ struct PatchingInputAccessor : InputAccessor
{
ref<InputAccessor> next;
std::map<Path, std::vector<std::string>> patchesPerFile;
std::map<CanonPath, std::vector<std::string>> patchesPerFile;
PatchingInputAccessor(
ref<InputAccessor> 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<std::string>())
patchesPerFile.emplace(fileName, std::vector<std::string>())
.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);
}

View file

@ -81,25 +81,25 @@ struct PathInputScheme : InputScheme
// 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");
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)};
}
};

View file

@ -22,13 +22,13 @@ struct ZipMember
struct ZipInputAccessor : InputAccessor
{
Path zipPath;
CanonPath zipPath;
struct zip * zipFile = nullptr;
typedef std::map<const char *, struct zip_stat, cmp_str> 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<InputAccessor> makeZipInputAccessor(PathView path)
ref<InputAccessor> makeZipInputAccessor(const CanonPath & path)
{
return make_ref<ZipInputAccessor>(path);
}

View file

@ -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);
}

View file

@ -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.

View file

@ -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<StorePath, Path> Store::toStorePath(const Path & path) const
std::pair<StorePath, Path> 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)};
}

View file

@ -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/<hash>-<name>/<bla> into
/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. */
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;
}
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));

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;
}
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 != "")
// 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);

View file

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