Home / Alt manpages / proc_pid_fd(5)

  • proc_pid_fd(5)
  • File format
  • linux

Inspect Linux File Descriptors with /proc/<pid>/fd

You will finish with a repeatable way to see what a Linux process has open, identify pipes and sockets, and pass an existing descriptor to a command without creating a second copy of the underlying file. The examples use the local proc_pid_fd(5) reference from the manpages package, version 6.7-2.

Allow about fifteen minutes. You need a shell and a process you are allowed to inspect. The first checks are ordinary, read-only commands. Looking at another user's process may require elevated privileges, but sudo does not make a descriptor readable when the process or its underlying inode denies access.

1. Confirm the local reference and your own descriptors

Start with the installed manual page, then list the descriptors belonging to the shell running the command:

$ man 5 proc_pid_fd
$ printf 'shell pid: %s\n' "$$"
shell pid: 24680
$ ls -l /proc/self/fd
total 0
lrwx------ 1 user user 64 Sep 26 12:00 0 -> /dev/pts/3
lrwx------ 1 user user 64 Sep 26 12:00 1 -> /dev/pts/3
lrwx------ 1 user user 64 Sep 26 12:00 2 -> /dev/pts/3
lr-x------ 1 user user 64 Sep 26 12:00 3 -> /proc/24680/fd

The names are descriptor numbers, not ordinary file names. Descriptors 0, 1 and 2 are standard input, standard output and standard error. The final entry is an example: shells and commands can open extra descriptors, and the exact list will vary. The /proc/self component means the process making the lookup, so it is useful inside scripts and avoids hard-coding your current PID.

Checkpoint: ls -l /proc/self/fd should return a list rather than a missing-directory error. If it does not, check that you are on Linux and that /proc is mounted. Do not create a replacement directory: it would not provide process descriptors.

2. Read the target of a descriptor

Use readlink when you need the link target without the visual formatting added by ls:

$ readlink /proc/self/fd/0
/dev/pts/3
$ readlink /proc/self/fd/1
/dev/pts/3

These results describe the terminal attached to this shell. A redirected command has different targets. Make a short-lived shell with an extra descriptor and inspect it from inside that process:

$ bash -c 'exec 3<<<"descriptor demo"; printf "fd 3: "; readlink /proc/self/fd/3; IFS= read -r line <&3; printf "read: %s\n" "$line"'
fd 3: pipe:[123456789]
read: descriptor demo

The inode number in the pipe target is illustrative and changes on every run. The descriptor is a live pipe endpoint, not a regular file. The shell can read from it through descriptor 3 because it already owns the open descriptor.

3. Inspect a separate process

To inspect a process that remains alive long enough to examine, start a harmless sleep child and save its PID:

$ sleep 60 &
[1] 24701
$ pid=$!
$ ls -l "/proc/$pid/fd"
total 0
lrwx------ 1 user user 64 Sep 26 12:01 0 -> /dev/pts/3
lrwx------ 1 user user 64 Sep 26 12:01 1 -> /dev/pts/3
lrwx------ 1 user user 64 Sep 26 12:01 2 -> /dev/pts/3
$ readlink "/proc/$pid/fd/0"
/dev/pts/3

List the directory before reading individual entries because the process can exit between those two operations. Treat a disappearing /proc/$pid path as a normal race, not as proof that the descriptor never existed.

Checkpoint: confirm that the process is still the one you started, then clean it up:

$ kill "$pid"
$ wait "$pid" 2>/dev/null || true
[1]+  Terminated              sleep 60

This is the only state-changing example in the guide, and it affects only the temporary child. Never substitute a production PID into kill without checking it first. If you terminate a service by mistake, use that service's normal recovery procedure rather than repeatedly sending signals.

4. Recognise pipes, sockets and anonymous descriptors

Not every target names a path. The manual describes pipe and socket links in the form type:[inode]. An entry such as socket:[2248868] identifies a socket inode; the number can be correlated with files under /proc/net when you are diagnosing a network process.

Kernel objects without a normal inode use an anon_inode: target. Examples include epoll, eventfd, inotify, timerfd and signalfd descriptors:

$ readlink "/proc/$TARGET_PID/fd/$FD_NUMBER"
anon_inode:[eventpoll]

Replace both placeholders after checking the directory listing. The descriptor number and object type are host-specific. Do not infer that every anonymous descriptor is an epoll instance, and do not treat a link target as permission to manipulate the object.

5. Use /proc/self/fd as a command-line input or output

A program can often be given an existing standard stream through /proc/self/fd/N. This helps when a tool insists on a file argument but your data is already on standard input or output. Use a command that treats its argument as an input file:

$ printf 'one\ntwo\n' | cat /proc/self/fd/0
one
two

Here, cat opens its own /proc/self/fd/0, which refers to the pipe it inherited. This is approximately the same idea as /dev/fd/0 on systems that provide that name. It is not universal application behaviour: a program that seeks may fail on a pipe, and a tool may treat a descriptor path differently from a regular file.

6. Understand the permission boundary

Reading a link under another process's fd directory is subject to the kernel's ptrace access check. Opening the object named by that link has another permission boundary: the underlying inode keeps its own owner and mode. These checks explain a common surprise: a process can read from its already-open standard input while a different, unprivileged process cannot open /proc/<pid>/fd/0 to obtain a new reference to the same pipe.

When an inspection fails, record which operation failed. "Cannot read the link" points at process inspection permissions. "Permission denied" while a command opens the resolved path may point at the pipe, socket or file itself. Try the same check against a process you own before using elevated privileges. If the descriptor belongs to a service, obtain approval before reading potentially sensitive data or exposing its target in logs.

7. Account for a multithreaded process

On a multithreaded process, the fd directory may no longer be available after the main thread has terminated, commonly after pthread_exit. If a long-running process appears alive but its descriptor directory is unavailable, check its thread and process state before assuming that /proc is broken. A supervisor restart can change every PID and descriptor, so capture evidence before restarting a service.

Done means

  • You can list your own descriptors with /proc/self/fd and explain entries 0, 1 and 2.
  • You can use readlink to distinguish paths, pipes, sockets and anon_inode objects.
  • You tested a temporary child and removed it without touching a service.
  • You know that reading a link and opening its target can fail for different permission reasons.
  • You will check for seek requirements before handing a /proc/self/fd path to another program.