Many versions of Unix provide a /dev/fd directory to work with open file handles as if they were regular files. As usual, the devil is in the details.
The /dev/fd feature was developed by Tom Duff and appeared in the 8th Edition of the Research Unix System. It is supported by SVR4 and 4.3+BSD. It is not part of POSIX.1.
To the dispassionate hacker (or a reader of Stevens),
it's pretty clear that a syscall like
O_RDONLY) should be similar to
on Linux that is not the case: in fact,
it's not possible to open a /dev/fd entry of which you don't have permission to read,
even if you could just read from the open file handle.
Let's verify this quickly:
# uname -sr Linux 6.4.7_1 # su amber -c "date > /dev/fd/1" >/tmp/out zsh:1: permission denied: /dev/fd/1
The shell creates /tmp/out as root, then
su switches to my user account, then the inner shell tries to open /dev/fd/1, but this now fails.
That is particularily funny as the /dev/fd entry appears to have write permission:
# su amber -c "ls -l /dev/fd/1" >/tmp/out # cat /tmp/out l-wx------ 1 amber amber 64 Oct 17 17:39 /dev/fd/1 -> /tmp/out
(But note that
ls itself prints fine to standard output, as expected.)
Meanwhile, on FreeBSD this just works:
# uname -sr FreeBSD 13.2-RELEASE # su amber -c "date > /dev/fd/1" >/tmp/out # cat /tmp/out Wed Sep 13 18:15:24 UTC 2023
However, FreeBSD by default only has three entries in /dev/fd, with fixed device nodes:
# ls -l /dev/fd/ total 0 crw-rw-rw- 1 root wheel 0xb Aug 20 14:48 0 crw-rw-rw- 1 root wheel 0xd Aug 20 14:48 1 crw-rw-rw- 1 root wheel 0xf Aug 20 14:48 2
If we want a proper /dev/fd, we need to mount the fdescfs(5) pseudo file system:
# mount -t fdescfs fdesc /dev/fd # ls -l /dev/fd/ total 0 cr-xr-xr-x 1 root wheel 0x3 Aug 20 14:48 0 cr-xr-xr-x 1 root wheel 0x4 Aug 20 14:48 1 cr-xr-xr-x 1 root wheel 0x5 Aug 20 14:48 2 cr-xr-xr-x 1 root wheel 0x6 Aug 20 14:48 3 cr-xr-xr-x 1 root wheel 0x7 Aug 20 14:48 4
Now we also see the two file descriptors
ls needed to open . (why?) and /dev/fd.
Reading the manpage for fdescfs(5) we can also find the mount option
nodup For file descriptors referencing vnodes, instead of the dup(2) semantic described above, implement re-opening of the referenced vnode.
With this option, a Linux-style permission check happens again. Finally, the man page suggests why this may be useful:
In particular, if the file descriptor was opened with the O_PATH flag, then either O_EMPTY_PATH or open() over fdescfs mount with nodup option allows one to convert it to a regularly opened file, assuming that the current permissions allow the requested mode.
Now, Linux doesn't have
O_EMPTY_PATH, at least not yet.
So in total this feature seems questionable at best to me, and simple
dup semantics are easier.
Let's spend a short glimpse at Solar^WIllumos, and we see it does a simple
dup as well.
P.S.: Why do I care about this? I noticed it when I saw that
zsh on FreeBSD uses FIFOs by default for process redirections:
# echo <(date) /tmp/zshD2nZ31
But this is inherently racy (and lots of fun if nothing opens the FIFO... think about it), and /dev/fd should be used instead to make this robust, but because ports are not built with
fdescfs enabled, zsh's configure script doesn't detect it and falls back to creating FIFOs.
ksh93 fails in the other way, finds /dev/fd during configure and assumes it works properly... but in the default configuration only fd 0, 1, 2 exist there, so process redirection is broken by default.
I have thus recompiled
zsh locally and added
fdescfs to my fstab(5).