Merge pull request #9985 from alois31/symlink-resolution

Restore `builtins.pathExists` behavior on broken symlinks
This commit is contained in:
John Ericson 2024-02-16 09:24:03 -05:00 committed by GitHub
commit d53c8901ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 53 additions and 19 deletions

View file

@ -115,7 +115,7 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
return res; return res;
} }
static SourcePath realisePath(EvalState & state, const PosIdx pos, Value & v, bool resolveSymlinks = true) static SourcePath realisePath(EvalState & state, const PosIdx pos, Value & v, std::optional<SymlinkResolution> resolveSymlinks = SymlinkResolution::Full)
{ {
NixStringContext context; NixStringContext context;
@ -127,7 +127,7 @@ static SourcePath realisePath(EvalState & state, const PosIdx pos, Value & v, bo
auto realPath = state.toRealPath(rewriteStrings(path.path.abs(), rewrites), context); auto realPath = state.toRealPath(rewriteStrings(path.path.abs(), rewrites), context);
path = {path.accessor, CanonPath(realPath)}; path = {path.accessor, CanonPath(realPath)};
} }
return resolveSymlinks ? path.resolveSymlinks() : path; return resolveSymlinks ? path.resolveSymlinks(*resolveSymlinks) : path;
} catch (Error & e) { } catch (Error & e) {
e.addTrace(state.positions[pos], "while realising the context of path '%s'", path); e.addTrace(state.positions[pos], "while realising the context of path '%s'", path);
throw; throw;
@ -167,7 +167,7 @@ static void mkOutputString(
argument. */ argument. */
static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * vScope, Value & v) static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * vScope, Value & v)
{ {
auto path = realisePath(state, pos, vPath, false); auto path = realisePath(state, pos, vPath, std::nullopt);
auto path2 = path.path.abs(); auto path2 = path.path.abs();
// FIXME // FIXME
@ -1521,13 +1521,16 @@ static void prim_pathExists(EvalState & state, const PosIdx pos, Value * * args,
try { try {
auto & arg = *args[0]; auto & arg = *args[0];
auto path = realisePath(state, pos, arg);
/* SourcePath doesn't know about trailing slash. */ /* SourcePath doesn't know about trailing slash. */
state.forceValue(arg, pos);
auto mustBeDir = arg.type() == nString auto mustBeDir = arg.type() == nString
&& (arg.string_view().ends_with("/") && (arg.string_view().ends_with("/")
|| arg.string_view().ends_with("/.")); || arg.string_view().ends_with("/."));
auto symlinkResolution =
mustBeDir ? SymlinkResolution::Full : SymlinkResolution::Ancestors;
auto path = realisePath(state, pos, arg, symlinkResolution);
auto st = path.maybeLstat(); auto st = path.maybeLstat();
auto exists = st && (!mustBeDir || st->type == SourceAccessor::tDirectory); auto exists = st && (!mustBeDir || st->type == SourceAccessor::tDirectory);
v.mkBool(exists); v.mkBool(exists);
@ -1765,7 +1768,7 @@ static std::string_view fileTypeToString(InputAccessor::Type type)
static void prim_readFileType(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_readFileType(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
auto path = realisePath(state, pos, *args[0], false); auto path = realisePath(state, pos, *args[0], std::nullopt);
/* Retrieve the directory entry type and stringize it. */ /* Retrieve the directory entry type and stringize it. */
v.mkString(fileTypeToString(path.lstat().type)); v.mkString(fileTypeToString(path.lstat().type));
} }

View file

@ -62,7 +62,7 @@ bool SourcePath::operator<(const SourcePath & x) const
return std::tie(*accessor, path) < std::tie(*x.accessor, x.path); return std::tie(*accessor, path) < std::tie(*x.accessor, x.path);
} }
SourcePath SourcePath::resolveSymlinks() const SourcePath SourcePath::resolveSymlinks(SymlinkResolution mode) const
{ {
auto res = SourcePath(accessor); auto res = SourcePath(accessor);
@ -72,6 +72,8 @@ SourcePath SourcePath::resolveSymlinks() const
for (auto & c : path) for (auto & c : path)
todo.push_back(std::string(c)); todo.push_back(std::string(c));
bool resolve_last = mode == SymlinkResolution::Full;
while (!todo.empty()) { while (!todo.empty()) {
auto c = *todo.begin(); auto c = *todo.begin();
todo.pop_front(); todo.pop_front();
@ -81,14 +83,16 @@ SourcePath SourcePath::resolveSymlinks() const
res.path.pop(); res.path.pop();
else { else {
res.path.push(c); res.path.push(c);
if (auto st = res.maybeLstat(); st && st->type == InputAccessor::tSymlink) { if (resolve_last || !todo.empty()) {
if (!linksAllowed--) if (auto st = res.maybeLstat(); st && st->type == InputAccessor::tSymlink) {
throw Error("infinite symlink recursion in path '%s'", path); if (!linksAllowed--)
auto target = res.readLink(); throw Error("infinite symlink recursion in path '%s'", path);
res.path.pop(); auto target = res.readLink();
if (hasPrefix(target, "/")) res.path.pop();
res.path = CanonPath::root; if (hasPrefix(target, "/"))
todo.splice(todo.begin(), tokenizeString<std::list<std::string>>(target, "/")); res.path = CanonPath::root;
todo.splice(todo.begin(), tokenizeString<std::list<std::string>>(target, "/"));
}
} }
} }
} }

View file

@ -11,6 +11,26 @@
namespace nix { namespace nix {
/**
* Note there is a decent chance this type soon goes away because the problem is solved another way.
* See the discussion in https://github.com/NixOS/nix/pull/9985.
*/
enum class SymlinkResolution {
/**
* Resolve symlinks in the ancestors only.
*
* Only the last component of the result is possibly a symlink.
*/
Ancestors,
/**
* Resolve symlinks fully, realpath(3)-style.
*
* No component of the result will be a symlink.
*/
Full,
};
/** /**
* An abstraction for accessing source files during * An abstraction for accessing source files during
* evaluation. Currently, it's just a wrapper around `CanonPath` that * evaluation. Currently, it's just a wrapper around `CanonPath` that
@ -103,11 +123,14 @@ struct SourcePath
bool operator<(const SourcePath & x) const; bool operator<(const SourcePath & x) const;
/** /**
* Resolve any symlinks in this `SourcePath` (including its * Resolve any symlinks in this `SourcePath` according to the
* parents). The result is a `SourcePath` in which no element is a * given resolution mode.
* symlink. *
* @param mode might only be a temporary solution for this.
* See the discussion in https://github.com/NixOS/nix/pull/9985.
*/ */
SourcePath resolveSymlinks() const; SourcePath resolveSymlinks(
SymlinkResolution mode = SymlinkResolution::Full) const;
}; };
std::ostream & operator << (std::ostream & str, const SourcePath & path); std::ostream & operator << (std::ostream & str, const SourcePath & path);

View file

@ -29,3 +29,6 @@ builtins.pathExists (./lib.nix)
&& builtins.pathExists (builtins.toPath { outPath = builtins.toString ./lib.nix; }) && builtins.pathExists (builtins.toPath { outPath = builtins.toString ./lib.nix; })
&& builtins.pathExists ./lib.nix && builtins.pathExists ./lib.nix
&& !builtins.pathExists ./bla.nix && !builtins.pathExists ./bla.nix
&& builtins.pathExists ./symlink-resolution/foo/overlays/overlay.nix
&& builtins.pathExists ./symlink-resolution/broken
&& builtins.pathExists (builtins.toString ./symlink-resolution/foo/overlays + "/.")

View file

@ -0,0 +1 @@
nonexistent