readLink builtin #656

Open
opened 2025-02-03 20:13:55 +00:00 by quantenzitrone · 8 comments

I wanted to figure out if a symlink is a directory or a file, so i can either run builtins.readFile or builtins.readDir.
I found no way to solve this as the symlink is detected as "symlink" by builtins.readFileType and there is no way to find out the symlink target.

Describe the solution you'd like

Easy and simple a readLink that returns the content of the symlink.
Handling the relativeness of that symlink or the possibility of danglinglyness would then be handled with other functions like builtins.pathExists.

{
  symlinkDangles = x: !(builtins.pathExists (/${x}/../${builtins.readLink x}));
}

Describe alternatives you've considered

A readLink that errors on dangling symlinks and returns the resolved path to the original location.

## Is your feature request related to a problem? Please describe. I wanted to figure out if a symlink is a directory or a file, so i can either run `builtins.readFile` or `builtins.readDir`. I found no way to solve this as the symlink is detected as "symlink" by `builtins.readFileType` and there is no way to find out the symlink target. ## Describe the solution you'd like Easy and simple a `readLink` that returns the content of the symlink. Handling the relativeness of that symlink or the possibility of danglinglyness would then be handled with other functions like `builtins.pathExists`. ```nix { symlinkDangles = x: !(builtins.pathExists (/${x}/../${builtins.readLink x})); } ``` ## Describe alternatives you've considered A `readLink` that errors on dangling symlinks and returns the resolved path to the original location.
Member

This issue was mentioned on Gerrit on the following CLs:

  • commit message in cl/2462 ("libexpr: add readLink builtin")
<!-- GERRIT_LINKBOT: {"cls": [{"backlink": "https://gerrit.lix.systems/c/lix/+/2462", "number": 2462, "kind": "commit message"}], "cl_meta": {"2462": {"change_title": "libexpr: add readLink builtin"}}} --> This issue was mentioned on Gerrit on the following CLs: * commit message in [cl/2462](https://gerrit.lix.systems/c/lix/+/2462) ("libexpr: add readLink builtin")
Owner

figuring out whether a symlink points to a directory or not can be done by appending a / to the path and pathExistsing that extended path. adding a readlink function seems misguided because it doesn't tell you very useful for your specific question; absolute links will be broken in restricted/flake eval and relative links working depends greatly on how the path they are in is being accessed.

unfortunately the current filesystem access situation is mondo messy, and unless there's a real need for this we probably shouldn't be adding more things that are subtly inconsistent :/

figuring out whether a symlink points to a directory or not can be done by appending a `/` to the path and `pathExists`ing that extended path. adding a readlink function seems misguided because it doesn't tell you very useful for your specific question; absolute links will be broken in restricted/flake eval and relative links working depends greatly on how the path they are in is being accessed. unfortunately the current filesystem access situation is mondo messy, and unless there's a real need for this we probably shouldn't be adding more things that are subtly inconsistent :/
Author

paths cannot have trailing slashes and if i do it as a string it doesn't work either

$ ls -lad test*
drwxr-xr-x 2 zitrone users 4096 Feb  3 20:54 testdir
lrwxrwxrwx 1 zitrone users    7 Feb  3 20:56 testdirlink -> testdir
-rw-r--r-- 1 zitrone users    0 Feb  3 20:55 testfile
lrwxrwxrwx 1 zitrone users    8 Feb  3 20:56 testfilelink -> testfile
$ nix repl
Lix 2.93.0-dev-pre20250123-caafc3f
Type :? for help.
nix-repl> __pathExists ./testdir/
error: path has a trailing slash
       at «string»:1:24:
            1| __pathExists ./testdir/
             |                        ^

nix-repl> __pathExists ${./testdir}/
error: syntax error, expecting end of file
       at «string»:1:14:
            1| __pathExists ${./testdir}/
             |              ^

nix-repl> __pathExists "${./testdir}/"
true

nix-repl> __pathExists "${./testdirlink}/"
false

nix-repl> __pathExists "${./testfilelink}/"
false
paths cannot have trailing slashes and if i do it as a string it doesn't work either ``` $ ls -lad test* drwxr-xr-x 2 zitrone users 4096 Feb 3 20:54 testdir lrwxrwxrwx 1 zitrone users 7 Feb 3 20:56 testdirlink -> testdir -rw-r--r-- 1 zitrone users 0 Feb 3 20:55 testfile lrwxrwxrwx 1 zitrone users 8 Feb 3 20:56 testfilelink -> testfile $ nix repl Lix 2.93.0-dev-pre20250123-caafc3f Type :? for help. nix-repl> __pathExists ./testdir/ error: path has a trailing slash at «string»:1:24: 1| __pathExists ./testdir/ | ^ nix-repl> __pathExists ${./testdir}/ error: syntax error, expecting end of file at «string»:1:14: 1| __pathExists ${./testdir}/ | ^ nix-repl> __pathExists "${./testdir}/" true nix-repl> __pathExists "${./testdirlink}/" false nix-repl> __pathExists "${./testfilelink}/" false ```
Owner

you're demonstrating exactly why we don't want to add this: pathExists "${./testdirlink}/" imports the link to the store—where it promptly breaks because it is relative and not rewritten by the import process. if you drop both of those files into a directory and import that via path literals it works, and likewise it works if you use absolute paths in unrestricted eval mode.

filesystem handling is a gigantic mess currently, and solutions to that must be very well thought out (or we'll be stuck with another broken set of operations for the foreseeable future).

you're demonstrating exactly why we don't want to add this: `pathExists "${./testdirlink}/"` imports the link to the store—where it promptly breaks because it is relative and not rewritten by the import process. if you drop both of those files into a directory and import *that* via path literals it works, and likewise it works if you use absolute paths in unrestricted eval mode. filesystem handling is a gigantic mess currently, and solutions to that must be very well thought out (or we'll be stuck with another broken set of operations for the foreseeable future).
Author

But readLink ./testdirlink, the way I have implemented it would not import the path to the store, would it?

But `readLink ./testdirlink`, the way I have implemented it would not import the path to the store, would it?
Owner

by itself, no. but if that snippet is run in flake context the link will still have been copied into the store, subject to VCS ignore rules.

if you just need to figure out whether a symlink points to a directory, `pointsToDir = x: builtins.pathExists "${toString x}/" is sufficient (and we can't even change that for something more sensible because that would explode nixpkgs)

by *itself*, no. but if that snippet is run in flake context the link will still have been copied into the store, subject to VCS ignore rules. if you *just* need to figure out whether a symlink points to a directory, `pointsToDir = x: builtins.pathExists "${toString x}/" is sufficient (and we can't even change that for something more sensible because that would explode nixpkgs)
Author

oh, yeah it works with toString

oh, yeah it works with `toString`
Author

i still think a readLink builtin would be a good idea but i do agree that extending the, according to you, messy system is not worth it for this

i still think a `readLink` builtin would be a good idea but i do agree that extending the, according to you, messy system is not worth it for this
Sign in to join this conversation.
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lix-project/lix#656
No description provided.