Skip to content

feat: builtins.readSymlink primop#15093

Open
JustAGuyTryingHisBest wants to merge 4 commits into
NixOS:masterfrom
JustAGuyTryingHisBest:read-symlink-primop
Open

feat: builtins.readSymlink primop#15093
JustAGuyTryingHisBest wants to merge 4 commits into
NixOS:masterfrom
JustAGuyTryingHisBest:read-symlink-primop

Conversation

@JustAGuyTryingHisBest
Copy link
Copy Markdown
Contributor

@JustAGuyTryingHisBest JustAGuyTryingHisBest commented Jan 26, 2026

Motivation

#13111 (comment)

Context

This is a stepping stone to - #13111


Add 👍 to pull requests you find important.

The Nix maintainer team uses a GitHub project board to schedule and track reviews.

@github-actions github-actions Bot added the with-tests Issues related to testing. PRs with tests have some priority label Jan 26, 2026
Ericson2314
Ericson2314 previously approved these changes Jan 27, 2026
Copy link
Copy Markdown
Member

@Ericson2314 Ericson2314 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it

(and I was not at the meeting where this was originally idea-approved)

@JustAGuyTryingHisBest
Copy link
Copy Markdown
Contributor Author

@Ericson2314 - Installer CI failed on an authentication issue unrelated to my change. I don't have the option to rerun. Is that only available to admins?

@Ericson2314
Copy link
Copy Markdown
Member

Done

@JustAGuyTryingHisBest
Copy link
Copy Markdown
Contributor Author

Done

Rad. Much appreciated!

@Ericson2314 Ericson2314 requested a review from xokdvium January 27, 2026 17:47
Comment thread src/libexpr/primops.cc Outdated
static void prim_readSymlink(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{
auto path = state.realisePath(pos, *args[0], SymlinkResolution::Ancestors);
v.mkString(path.readLink(), state.mem);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh hmm actually this part might need more thinking hmm. I would rather that it be a path.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about reading symlinks inside trees fetched by fetchTree or similar - those might very well be dangling or relative.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense! I updated the implementation to return a path.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about reading symlinks inside trees fetched by fetchTree or similar - those might very well be dangling or relative.

See what I wrote in obsidiansystems@own-location-for-symlink, which @xokdvium and I have talked about before.

The issue, for example, that some source successors, like git repos for flakes for pure eval, shouldn't really support absolute paths at all, because the repo isn't necessary mounted at root or anywhere else. It should only support relative paths.

My suggestion that we should convert the raw target of the symlink into a path is a bit more complex than what you implemented if we take this concern into account, because we need to actually define this per-source-accessor policy of how symlinks should be interpreted / what should be allowed.

Sorry this is spiraling into something more complex than what you started with!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reread the original issue, and it appears the intended behavior is to match POSIX readlink returning a raw string.
Does it make sense to open a new ticket to differentiate this from the original ticket and POSIX realpath behavior?

#13111 (comment)

@Ericson2314 Ericson2314 dismissed their stale review January 27, 2026 18:12

Noticed one thing that I want to think about more

@@ -0,0 +1 @@
error: filesystem error: read_symlink: not a symlink ["/pwd/lang/readDir/bar"]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, this error really needs to be wrapped in the accessor IMO

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the suggestion here to catch the error in the primop and throw NotASymlink("file '%s' is not a symbolic link", showPath(path)); or add a cleaner error message in PosixSourceAccessor::readLink().

Copy link
Copy Markdown
Member

@Ericson2314 Ericson2314 Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear, @xokdivium is just noticing a preexisting issue with PosixSourceAcccesor, it is not the fault of this PR.

Comment thread src/libexpr/primops.cc Outdated
static void prim_readSymlink(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{
auto path = state.realisePath(pos, *args[0], SymlinkResolution::Ancestors);
v.mkString(path.readLink(), state.mem);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about reading symlinks inside trees fetched by fetchTree or similar - those might very well be dangling or relative.

Copy link
Copy Markdown
Member

@edolstra edolstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should be called builtins.readLink since that's more consistent with the readlink() POSIX function, the readlink GNU command, and readLink() in our own interfaces. It's unambiguous since you can't read hard links.

Comment thread src/libexpr/primops.cc
const auto path = state.realisePath(pos, *args[0], SymlinkResolution::Ancestors);
const auto target = path.readLink();
const auto parent = path.parent();
v.mkPath(SourcePath(path.accessor, CanonPath(target, parent.path)), state.mem);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit questionable whether this should return a path value or just the raw symlink as a string (without canonicalisation). If the former, maybe this function should be called builtins.canonPath or builtins.resolvePath or whatever (and accept non-symlink paths as well).

@roberth
Copy link
Copy Markdown
Member

roberth commented Feb 6, 2026

Maybe this should be called builtins.readLink

Counterpoints:

  • Explicit is good
  • Internal consistency:
    • FSO in the manual uses "symbolic link"
    • nix-repl> builtins.readFileType /etc/static
      "symlink"
    • NAR uses symlink

@roberth
Copy link
Copy Markdown
Member

roberth commented Feb 6, 2026

We have previously put some wisdom in the manual

An arbitrary string, known as the target of the symlink.

In general, Nix does not assign any semantics to symbolic links. Certain operations however, may make additional assumptions and attempt to use the target to find another file system object.

While I am a fan of path values, I haven't heard a compelling reason why we should diverge from this definition.
I am not sure that "symlink resolution is useful" counts, and implementing path resolution is another can of worms where we have very little room for error, risking eval repro when we inevitably find that we got some corner case wrong.
That kind of complexity is better implemented in Nixpkgs lib where iterating does not risk eval reproducibility.

I had opened that can of worms for the build-path path component security checking, but that solution was not chosen. Is this instance of the problem different? I would argue yes, but for the worse.

(I should still have that work somewhere if you insist)

@Ericson2314
Copy link
Copy Markdown
Member

The short answer is that I am worried that the nixpkgs lib will implement something that should stop working when our symlink dereferencing gets better, and that will cause problems / break expectations,

For example in some context absolute path symlinks should be followed, and in other contexts they should not be.

@JustAGuyTryingHisBest
Copy link
Copy Markdown
Contributor Author

@Ericson2314 @roberth @edolstra - Is the consensus here to change the name to readLink, return a raw string, and defer on path resolution/canonicalisation?

@tomberek tomberek requested a review from edolstra March 20, 2026 15:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

with-tests Issues related to testing. PRs with tests have some priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants