libutil: remove SourcePath::resolveSymlinks

in pure mode it is entirely useless. in impure mode it's mostly useless
since the way in which it is used is either equivalent to not being run
at all, or is equivalent to turning the following lstat into a stat. we
add a stat method instead for all those who need final symlinks stat'd.

Change-Id: I801886d18eb34b26e62b4c05d53318c6421a69bf
This commit is contained in:
eldritch horrors
2025-01-10 15:20:27 -08:00
committed by Jade Lovelace
parent f93af1db1f
commit 5af069b248
5 changed files with 47 additions and 82 deletions
+2 -2
View File
@@ -123,7 +123,7 @@ static void getAllExprs(Evaluator & state,
InputAccessor::Stat st;
try {
st = path2.resolveSymlinks().lstat();
st = path2.stat();
} catch (Error &) {
continue; // ignore dangling symlinks in ~/.nix-defexpr
}
@@ -162,7 +162,7 @@ static void getAllExprs(Evaluator & state,
static void loadSourceExpr(EvalState & state, const SourcePath & path, Value & v)
{
auto st = path.resolveSymlinks().lstat();
auto st = path.stat();
if (isNixExpr(path, st))
state.evalFile(path, v);
+1 -1
View File
@@ -676,7 +676,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
// Reload right after exiting the editor if path is not in store
// Store is immutable, so there could be no changes, so there's no need to reload
if (!evaluator.store->isInStore(path.resolveSymlinks().canonical().abs())) {
if (!evaluator.store->isInStore(canonPath(path.canonical().abs(), true))) {
state.resetFileCache();
reloadFiles();
}
+6 -5
View File
@@ -1206,12 +1206,13 @@ static void prim_pathExists(EvalState & state, const PosIdx pos, Value * * args,
|| arg.str().ends_with("/."));
try {
auto checked = state
.ctx.paths
.checkSourcePath(path)
.resolveSymlinks(mustBeDir ? SymlinkResolution::Full : SymlinkResolution::Ancestors);
auto checked = state.ctx.paths.checkSourcePath(path);
auto st = checked.maybeLstat();
// previously we fully resolved symlinks in the mustBeDir case or in pure eval
// mode (by accident, since checkSourcePath does this in that case), and up to
// the last component otherwise. this is equivalent to calling stat and lstat,
// respectively. (in neither case do intermediate symlinks affect the result.)
auto st = mustBeDir ? checked.maybeStat() : checked.maybeLstat();
auto exists = st && (!mustBeDir || st->type == InputAccessor::tDirectory);
v.mkBool(exists);
} catch (SysError & e) {
+26 -44
View File
@@ -1,4 +1,5 @@
#include "lix/libutil/source-path.hh"
#include "file-system.hh"
#include "lix/libutil/strings.hh"
namespace nix {
@@ -21,9 +22,8 @@ SourcePath SourcePath::parent() const
return std::move(*p);
}
InputAccessor::Stat SourcePath::lstat() const
static InputAccessor::Stat convertStat(const struct stat & st)
{
auto st = nix::lstat(path.abs());
return InputAccessor::Stat {
.type =
S_ISREG(st.st_mode) ? InputAccessor::tRegular :
@@ -34,12 +34,32 @@ InputAccessor::Stat SourcePath::lstat() const
};
}
InputAccessor::Stat SourcePath::lstat() const
{
return convertStat(nix::lstat(path.abs()));
}
std::optional<InputAccessor::Stat> SourcePath::maybeLstat() const
{
// FIXME: merge these into one operation.
if (!pathExists())
return {};
return lstat();
if (auto st = nix::maybeLstat(path.abs())) {
return convertStat(*st);
} else {
return std::nullopt;
}
}
InputAccessor::Stat SourcePath::stat() const
{
return convertStat(nix::stat(path.abs()));
}
std::optional<InputAccessor::Stat> SourcePath::maybeStat() const
{
if (auto st = nix::maybeStat(path.abs())) {
return convertStat(*st);
} else {
return std::nullopt;
}
}
InputAccessor::DirEntries SourcePath::readDirectory() const
@@ -58,42 +78,4 @@ InputAccessor::DirEntries SourcePath::readDirectory() const
return res;
}
SourcePath SourcePath::resolveSymlinks(SymlinkResolution mode) const
{
SourcePath res(CanonPath::root);
int linksAllowed = 1024;
std::list<std::string> todo;
for (auto & c : path)
todo.push_back(std::string(c));
bool resolve_last = mode == SymlinkResolution::Full;
while (!todo.empty()) {
auto c = *todo.begin();
todo.pop_front();
if (c == "" || c == ".")
;
else if (c == "..")
res.path.pop();
else {
res.path.push(c);
if (resolve_last || !todo.empty()) {
if (auto st = res.maybeLstat(); st && st->type == InputAccessor::tSymlink) {
if (!linksAllowed--)
throw Error("infinite symlink recursion in path '%s'", path);
auto target = res.readLink();
res.path.pop();
if (target.starts_with("/"))
res.path = CanonPath::root;
todo.splice(todo.begin(), tokenizeString<std::list<std::string>>(target, "/"));
}
}
}
}
return res;
}
}
+12 -30
View File
@@ -14,26 +14,6 @@
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
* evaluation. Currently, it's just a wrapper around `CanonPath` that
@@ -84,6 +64,18 @@ public:
*/
std::optional<InputAccessor::Stat> maybeLstat() const;
/**
* Return stats about this `SourcePath`, or throw an exception if
* it doesn't exist. Symlinks are resolved by this function.
*/
InputAccessor::Stat stat() const;
/**
* Return stats about this `SourcePath`, or std::nullopt if it
* doesn't exist. Symlinks are resolved by this function.
*/
std::optional<InputAccessor::Stat> maybeStat() const;
/**
* If this `SourcePath` denotes a directory (not a symlink),
* return its directory entries; otherwise throw an error.
@@ -138,16 +130,6 @@ public:
{
return path < x.path;
}
/**
* Resolve any symlinks in this `SourcePath` according to the
* given resolution mode.
*
* @param mode might only be a temporary solution for this.
* See the discussion in https://github.com/NixOS/nix/pull/9985.
*/
SourcePath resolveSymlinks(
SymlinkResolution mode = SymlinkResolution::Full) const;
};
std::ostream & operator << (std::ostream & str, const SourcePath & path);