Expose path in File

Shouldn't File struct expose a method to show the underlying path ?

In systems like Linux, a path is not something that a file has. It's something that can be used to obtain access to a file, but the path may not be the canonical one, and may not even exist afterwards. For example, it's possible to open a File, then delete the path (unlink), and still read and write from the File successfully.

Currently File (at least on unix-like systems) is just a thin wrapper over fd and takes only 4 bytes, with no allocations on the Rust's side.

21 Likes

If you mean for diagnostic purposes when displaying a std::io::Error, there is an open issue and a draft PR showing it should be possible in theory. At this point I think it just needs a dedicated individual to spearhead it.

4 Likes

Note that this isn't quite what the original post asked for; the draft PR changes functions like File::open and File::create to include the path in the error message if they fail, but does not change things like File::metadata to include the path.

Basically, the draft PR says "if you passed in a path to this function call, and it fails, give the path back to you as part of the error"; for functions that didn't get a path, there's no path to return to you.

8 Likes

That's fair, and even on an OS where it can be retrieved after being discarded, I don't think users would be happy with the extra syscall during error generation.

If OP really wants it unconditionally available on File, the method either needs to return Option<PathBuf> (and have the docs clarify it is not a zero-cost getter) or it needs to be a method on something more like std::os::windows::fs::FileExt.

3 Likes

One can also create Files from non-files (on Unices at least).

fn main() -> Result<(), Box<dyn Error>> {
    let file = File::from(io::stdout().as_fd().try_clone_to_owned()?);
    writeln!(&file, "unbuffered")?;
    Ok(())
}
4 Likes

On Linux you can technically retrieve this information, via reading the symlink target path of /proc/self/fd/N where N is the fd number.

Of course, it might not be an actual file, or a file that still exists. And /proc may not be mounted. And even if everything works OK, that is still an overhead of a syscall, plus parsing logic to determine what sort of path it is.

So I don't think std should do this (especially since I don't know if you can even do this on other Unices).

5 Likes

procfs is available on some Unices, but it is a Linux-ism and so usually not a part of the base system. You can query a file descriptors path on macOS (through a fcntl flag) and FreeBSD (through a sysctl) but I'm less sure about NetBSD or OpenBSD. A quick google search leads me to believe that OpenBSD has intentionally not provided such an API.

1 Like

It might be a useful fallible API, abstracting over platform differences. But then you need the list of reasons why it’s probably not a good idea for most clients, and at that point it probably doesn’t need to be in std at all. A crate can collect all the approaches just as well.

4 Likes

nit: actually it originated on plan9, and linux copied it (with some modification) from there (it did a similar thing with FUSE)

Other than the issues listed already, I've played with this before. /proc is…a strange place[1]. If you delete a file, the symlink's target is updated to have a " (deleted)" suffix. If you then make this file, the readlink target exits, but it cannot be read via the symlink (because the kernel knows it is not readable). You can therefore detect whether tools read the symlink or do their own readlink following internally based on whether such paths "work" for them.


  1. Cue the Monty Python's Knights of the Roundtable scene. ↩︎

3 Likes

it also doesn't work for files in deeply nested directories when it exceeds PATH_MAX

Ah, that reminds me that mount namespaces can differ and the realpath go somewhere completely different even if it resolves. All one can really do is open() the path itself and not treat them like symlinks at all.

Debug implementation for File has get_path() which returns Option, may be we can make it public ?

I think it's been delegated to Debug only, because the function is unreliable, and has many platform-specific limitations.

        fn get_path(_fd: c_int) -> Option<PathBuf> {
            // FIXME(#24570): implement this for other Unix platforms
            None
        }

If this kinda mostly worked on Linux, I expect Rust programs would suffer Hyrum's Law. If it's public, people will use it. If it mostly works, people will rely on it. And then it will annoyingly break in some situations on some platforms where someone uses it in an argument to Command or saves as a path in config file.

I think io::Error could carry a path in cases where it's available via arguments to the function that failed, and that would fix a lot of complaints. For other cases, just carry (File, PathBuf) explicitly.

3 Likes