Could Stdout have a metadata() method?

Similar to File::metadata(). File::metadata(), to my understanding, maps to fstat and GetFileInformationByHandle, which could also be called with the file descriptor / handle of stdout.

The use-case for this is that in uutils (a rust coreutils rewrite, specifically the cat utility) we need to check if stdout is the same as an input file and adjust our behavior accordingly.

1 Like

Why can't you use the equivalent of STDIN_FILENO? Or is this problematic on Windows?

Sorry, could you elaborate how STDIN_FILENO could help me here?

I guess why do you need stdin to have .metadata() specifically? Why can't you just call fstat(0)? Is it just for more idiomatic code?

Yes. I should have clarified that it's not impossible to do in rust now, and I know how to work around it. i.e. The code I've written would be more idiomatic and cleaner if I hadn't had to write platform-dependent code and add a dependency per platform.

Can you stat (or the equivalent) the stdin handle on windows?

Edit: I guess it could just return Err if not.

If the stdin handle is a filesystem file then yes you can. If it's a device then some metadata can be queried (e.g. type, path) but GetFileInformationByHandle itself may fail.

Would it make sense to provide it in terms of BorrowedFd once that exists?

2 Likes

This use case is handled by the same-file crate. Here is how it's used in ripgrep.

Without the dependency, you do need to write platform specific code. One possible way to avoid that might be if stdin/stdout/stderr had methods that turned them into a File. That would give you the most flexibility. However, this approach doesn't work on its own, since once the File is dropped, the device will be closed. There are likely other issues. So a metadata method of some sort might be a decent compromise.

1 Like

Ah, I obviously should have searched crates.io before writing it myself. However, at first glance same-file doesn't allow to compare two symlinks (i.e. compare metadata obtained from lstat (unix) or a file handle opened with FILE_FLAG_OPEN_REPARSE_POINT set (windows)). It probably doesn't matter, but the use case here is cp, which does not allow the destination to be a symlink it just created.

It sounds like that might be easy to add in an API compatible way? Not sure.

It's a bit unorthodox, but I think returning a &'static File would work. That'd still let you do odd things, but at least it wouldn't be easy to misuse accidentally.

You could still get an owned File from it by using File::try_clone (approximately dup(2) under the hood), so it would also become possible to do reads and writes, bypassing the usual locks. But the existence of that method shows that that sort of funny business is possible already, if only for Files, so I'm not sure that's a dealbreaker.

Doing this means that stdin (and likely the other std* streams) cannot be closed (and likely dup2'd over). I don't think Rust's stdlib API could expose this without closing off those (valid) use cases.

Hmm. Is it actually invalid to have a File of a closed file descriptor, or one whose identity is replaced?

If you have a File to a closed file descriptor, any open call can result in the same file descriptor. This violates IO safety. 3128-io-safety - The Rust RFC Book

You're right, in the general case it's invalid.

I'm still not sure if it applies here. If you close/replace stdin it already affects Stdin, which effectively owns libc::STDIN_FILENO, so wouldn't that already violate I/O safety? Would providing a &'static File make that any worse?

Closing stdin, stdout or stderr on Unix makes many programs misbehave. On Windows, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE and STD_ERROR_HANDLE can't be accidentally overwritten by an open I believe. If you replace stdin it is your own responsibility to provide an fd that doesn't make the program misbehave when it tries to read/write to it. A File allows much more operations than Stdin/Stdout/Stderr and libraries may assume that a File is backed by an actual file, while they don't expect Stdin/Stdout/Stderr to always be backed by a pipe.

Closing (or, rather, dup2'ing over them) stdin and friends isn't that uncommon after a fork. It's bog standard behavior for a shell at least. However, this may be covered by the POSIX rules for what kinds of things may occur between fork and exec (especially in the presence of threads).