proc_pid_fd(5)
๐ proc_pid_fd โ the manual page, but make it cute โจ
okay so bestie, we're in the File Formats Manual section today and the page is called proc_pid_fd(5). buckle up, it's actually so good ๐
๐ธ NAME ๐ธ
/proc/pid/fd/ โ file descriptors. that's it. that's the whole vibe. ๐
โจ DESCRIPTION โจ
๐ฆ the directory itself
/proc/pid/fd/ is a little subdirectory with one entry per file the process has open, and each entry is named after its file descriptor. every single entry is a symbolic link pointing at the actual file. so like, literally: 0 is standard input, 1 is standard output, 2 is standard error, and so on forever. main character numbering fr fr ๐
๐ pipes and sockets
for file descriptors that belong to pipes and sockets, the entries are symbolic links whose content is the file type plus the inode. a readlink(2) call on one of these gives you a string shaped like this:
type:[inode]
so for example socket:[2248868] is lowkey a socket and its inode is 2248868. and for sockets specifically, you can take that inode and go find more info in one of the files under /proc/net/. iykyk ๐ฅ
๐ the no-inode girlies (anon inodes)
for file descriptors that have no corresponding inode โ like the ones produced by bpf(2), epoll_create(2), eventfd(2), inotify_init(2), perf_event_open(2), signalfd(2), timerfd_create(2), and userfaultfd(2) โ the entry is a symbolic link whose contents look like:
anon_inode:file-type
and in many cases (but not all), that file-type is wrapped in square brackets. obsessed with the inconsistency honestly ๐ญ
for example, an epoll file descriptor has a symbolic link whose content is the string anon_inode:[eventpoll]. iconic.
๐ multithreaded reality check
in a multithreaded process, the contents of this directory are not available if the main thread has already terminated โ typically by calling pthread_exit(3). so like, don't come for her after the main thread has left the group chat. not me finding this out the hard way ๐
๐ using fd files as command-line args (the hack that ate)
okay this is the good part. some programs take a filename as a command-line argument but won't read from standard input when no argument is given. same deal the other way: some programs write to a file named as a command-line argument but won't send output to standard output when no argument is given. whatever, we can still make them use standard input or standard output by literally passing /proc/pid/fd files as command-line arguments. slay.
so assuming -i is the flag for an input file and -o is the flag for an output file:
$ foobar -i /proc/self/fd/0 -o /proc/self/fd/1 ...
and boom, you have a working filter. periodt. โจ
๐ง /dev/fd and friends
/proc/self/fd/N is approximately the same as /dev/fd/N in some UNIX and UNIX-like systems. and most Linux MAKEDEV scripts symbolically link /dev/fd to /proc/self/fd, in fact. ๐
most systems also provide the symbolic links /dev/stdin, /dev/stdout, and /dev/stderr, which respectively link to the files 0, 1, and 2 in /proc/self/fd. which means the example command above could be written as:
$ foobar -i /dev/stdin -o /dev/stdout ...
so much cuter, no notes ๐ซถ
๐ธ permissions, the serious bit
permission to dereference or read (readlink(2)) the symbolic links in this directory is governed by a ptrace access mode PTRACE_MODE_READ_FSCREDS check; see ptrace(2). keep that in your notes bestie.
also, note that for file descriptors referring to inodes โ pipes and sockets, see above โ those inodes still have permission bits and ownership information distinct from those of the /proc/pid/fd entry, and the owner may differ from the user and group IDs of the process. an unprivileged process may simply lack permission to open them. like, watch:
$ echo test | sudo -u nobody cat
test
$ echo test | sudo -u nobody cat /proc/self/fd/0
cat: /proc/self/fd/0: Permission denied
๐
file descriptor 0 refers to the pipe created by the shell and owned by that shell's user, which is not nobody, so cat does not have permission to create a new file descriptor to read from that inode โ even though it can still read from its existing file descriptor 0. it's giving "you already have the thing but you can't make a new one." the audacity honestly ๐ญ
โญ SEE ALSO โญ
proc(5)โ go read it, bestie, it's the whole neighborhood ๐
lowkey reminder: every path, flag, syscall, inode number and string in here is copied exactly, no cap. the vibes are mine, the facts are the kernel's. ๐
and that's the page!! this one came from Linux man-pages 6.20, dated 2026-02-08. mwah, go read your own file descriptors, they're literally right there and they love you ๐โจ๐
๐ฆ where this page comes from
- arch rolling: man-pages 6.20-1 ยท BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
- nixpkgs unstable: man-pages 6.20
- ๐ the page's license: GPL-3.0-or-later
- Copyright 1994-1995, Daniel Quinlan <Daniel.Quinlan@linux.org>
- Copyright 2002-2017, Michael Kerrisk <mtk.manpages@gmail.com>
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms