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
  • ๐Ÿ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms