mirror of
https://github.com/privatevoid-net/nix-super.git
synced 2024-11-10 00:08:07 +02:00
nix shell: Handle output paths that are symlinks
This requires moving resolveSymlinks() into SourceAccessor. Also, it requires LocalStoreAccessor::maybeLstat() to work on parents of the store (to avoid an error like "/nix is not in the store"). Fixes #10375.
This commit is contained in:
parent
021488497d
commit
85b9f4ef4f
8 changed files with 97 additions and 72 deletions
|
@ -33,6 +33,10 @@ struct LocalStoreAccessor : PosixSourceAccessor
|
||||||
|
|
||||||
std::optional<Stat> maybeLstat(const CanonPath & path) override
|
std::optional<Stat> maybeLstat(const CanonPath & path) override
|
||||||
{
|
{
|
||||||
|
/* Handle the case where `path` is (a parent of) the store. */
|
||||||
|
if (isDirOrInDir(store->storeDir, path.abs()))
|
||||||
|
return Stat{ .type = tDirectory };
|
||||||
|
|
||||||
return PosixSourceAccessor::maybeLstat(toRealPath(path));
|
return PosixSourceAccessor::maybeLstat(toRealPath(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,9 +39,9 @@ void SourceAccessor::readFile(
|
||||||
}
|
}
|
||||||
|
|
||||||
Hash SourceAccessor::hashPath(
|
Hash SourceAccessor::hashPath(
|
||||||
const CanonPath & path,
|
const CanonPath & path,
|
||||||
PathFilter & filter,
|
PathFilter & filter,
|
||||||
HashAlgorithm ha)
|
HashAlgorithm ha)
|
||||||
{
|
{
|
||||||
HashSink sink(ha);
|
HashSink sink(ha);
|
||||||
dumpPath(path, sink, filter);
|
dumpPath(path, sink, filter);
|
||||||
|
@ -67,4 +67,42 @@ std::string SourceAccessor::showPath(const CanonPath & path)
|
||||||
return displayPrefix + path.abs() + displaySuffix;
|
return displayPrefix + path.abs() + displaySuffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CanonPath SourceAccessor::resolveSymlinks(
|
||||||
|
const CanonPath & path,
|
||||||
|
SymlinkResolution mode)
|
||||||
|
{
|
||||||
|
auto res = CanonPath::root;
|
||||||
|
|
||||||
|
int linksAllowed = 1024;
|
||||||
|
|
||||||
|
std::list<std::string> todo;
|
||||||
|
for (auto & c : path)
|
||||||
|
todo.push_back(std::string(c));
|
||||||
|
|
||||||
|
while (!todo.empty()) {
|
||||||
|
auto c = *todo.begin();
|
||||||
|
todo.pop_front();
|
||||||
|
if (c == "" || c == ".")
|
||||||
|
;
|
||||||
|
else if (c == "..")
|
||||||
|
res.pop();
|
||||||
|
else {
|
||||||
|
res.push(c);
|
||||||
|
if (mode == SymlinkResolution::Full || !todo.empty()) {
|
||||||
|
if (auto st = maybeLstat(res); st && st->type == SourceAccessor::tSymlink) {
|
||||||
|
if (!linksAllowed--)
|
||||||
|
throw Error("infinite symlink recursion in path '%s'", showPath(path));
|
||||||
|
auto target = readLink(res);
|
||||||
|
res.pop();
|
||||||
|
if (hasPrefix(target, "/"))
|
||||||
|
res = CanonPath::root;
|
||||||
|
todo.splice(todo.begin(), tokenizeString<std::list<std::string>>(target, "/"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,26 @@ namespace nix {
|
||||||
|
|
||||||
struct Sink;
|
struct Sink;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A read-only filesystem abstraction. This is used by the Nix
|
* A read-only filesystem abstraction. This is used by the Nix
|
||||||
* evaluator and elsewhere for accessing sources in various
|
* evaluator and elsewhere for accessing sources in various
|
||||||
|
@ -112,9 +132,9 @@ struct SourceAccessor
|
||||||
PathFilter & filter = defaultPathFilter);
|
PathFilter & filter = defaultPathFilter);
|
||||||
|
|
||||||
Hash hashPath(
|
Hash hashPath(
|
||||||
const CanonPath & path,
|
const CanonPath & path,
|
||||||
PathFilter & filter = defaultPathFilter,
|
PathFilter & filter = defaultPathFilter,
|
||||||
HashAlgorithm ha = HashAlgorithm::SHA256);
|
HashAlgorithm ha = HashAlgorithm::SHA256);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a corresponding path in the root filesystem, if
|
* Return a corresponding path in the root filesystem, if
|
||||||
|
@ -137,6 +157,17 @@ struct SourceAccessor
|
||||||
void setPathDisplay(std::string displayPrefix, std::string displaySuffix = "");
|
void setPathDisplay(std::string displayPrefix, std::string displaySuffix = "");
|
||||||
|
|
||||||
virtual std::string showPath(const CanonPath & path);
|
virtual std::string showPath(const CanonPath & path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve any symlinks in `path` 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.
|
||||||
|
*/
|
||||||
|
CanonPath resolveSymlinks(
|
||||||
|
const CanonPath & path,
|
||||||
|
SymlinkResolution mode = SymlinkResolution::Full);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,44 +62,6 @@ 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(SymlinkResolution mode) const
|
|
||||||
{
|
|
||||||
auto res = SourcePath(accessor);
|
|
||||||
|
|
||||||
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 (hasPrefix(target, "/"))
|
|
||||||
res.path = CanonPath::root;
|
|
||||||
todo.splice(todo.begin(), tokenizeString<std::list<std::string>>(target, "/"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream & operator<<(std::ostream & str, const SourcePath & path)
|
std::ostream & operator<<(std::ostream & str, const SourcePath & path)
|
||||||
{
|
{
|
||||||
str << path.to_string();
|
str << path.to_string();
|
||||||
|
|
|
@ -11,26 +11,6 @@
|
||||||
|
|
||||||
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
|
||||||
|
@ -123,14 +103,13 @@ struct SourcePath
|
||||||
bool operator<(const SourcePath & x) const;
|
bool operator<(const SourcePath & x) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve any symlinks in this `SourcePath` according to the
|
* Convenience wrapper around `SourceAccessor::resolveSymlinks()`.
|
||||||
* 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(
|
SourcePath resolveSymlinks(
|
||||||
SymlinkResolution mode = SymlinkResolution::Full) const;
|
SymlinkResolution mode = SymlinkResolution::Full) const
|
||||||
|
{
|
||||||
|
return {accessor, accessor->resolveSymlinks(path, mode)};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::ostream & operator << (std::ostream & str, const SourcePath & path);
|
std::ostream & operator << (std::ostream & str, const SourcePath & path);
|
||||||
|
|
|
@ -124,7 +124,8 @@ struct CmdShell : InstallablesCommand, MixEnvironment
|
||||||
if (true)
|
if (true)
|
||||||
pathAdditions.push_back(store->printStorePath(path) + "/bin");
|
pathAdditions.push_back(store->printStorePath(path) + "/bin");
|
||||||
|
|
||||||
auto propPath = CanonPath(store->printStorePath(path)) / "nix-support" / "propagated-user-env-packages";
|
auto propPath = accessor->resolveSymlinks(
|
||||||
|
CanonPath(store->printStorePath(path)) / "nix-support" / "propagated-user-env-packages");
|
||||||
if (auto st = accessor->maybeLstat(propPath); st && st->type == SourceAccessor::tRegular) {
|
if (auto st = accessor->maybeLstat(propPath); st && st->type == SourceAccessor::tRegular) {
|
||||||
for (auto & p : tokenizeString<Paths>(accessor->readFile(propPath)))
|
for (auto & p : tokenizeString<Paths>(accessor->readFile(propPath)))
|
||||||
todo.push(store->parseStorePath(p));
|
todo.push(store->parseStorePath(p));
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
with import ./config.nix;
|
with import ./config.nix;
|
||||||
|
|
||||||
{
|
rec {
|
||||||
hello = mkDerivation {
|
hello = mkDerivation {
|
||||||
name = "hello";
|
name = "hello";
|
||||||
outputs = [ "out" "dev" ];
|
outputs = [ "out" "dev" ];
|
||||||
|
@ -24,6 +24,14 @@ with import ./config.nix;
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
hello-symlink = mkDerivation {
|
||||||
|
name = "hello-symlink";
|
||||||
|
buildCommand =
|
||||||
|
''
|
||||||
|
ln -s ${hello} $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
salve-mundi = mkDerivation {
|
salve-mundi = mkDerivation {
|
||||||
name = "salve-mundi";
|
name = "salve-mundi";
|
||||||
outputs = [ "out" ];
|
outputs = [ "out" ];
|
||||||
|
|
|
@ -10,6 +10,8 @@ nix shell -f shell-hello.nix hello -c hello NixOS | grep 'Hello NixOS'
|
||||||
nix shell -f shell-hello.nix hello^dev -c hello2 | grep 'Hello2'
|
nix shell -f shell-hello.nix hello^dev -c hello2 | grep 'Hello2'
|
||||||
nix shell -f shell-hello.nix 'hello^*' -c hello2 | grep 'Hello2'
|
nix shell -f shell-hello.nix 'hello^*' -c hello2 | grep 'Hello2'
|
||||||
|
|
||||||
|
# Test output paths that are a symlink.
|
||||||
|
#nix shell -f shell-hello.nix hello-symlink -c hello | grep 'Hello World'
|
||||||
|
|
||||||
if isDaemonNewer "2.20.0pre20231220"; then
|
if isDaemonNewer "2.20.0pre20231220"; then
|
||||||
# Test that command line attribute ordering is reflected in the PATH
|
# Test that command line attribute ordering is reflected in the PATH
|
||||||
|
|
Loading…
Reference in a new issue