The typical way to count the number of open file descriptors by current process on macOS is to check the contents of the
/dev/fd directory. This directory is not documented by Apple, but the code for populating this directory is in the XNU sources on GitHub. The code is borrowed from FreeBSD and this directory is documented in the FreeBSD documentation. The
fdescfs(5) man page contains two facts below.
The file system’s contents appear as a list of numbered files which correspond to the open files of the process reading the directory. The files /dev/fd/0 through /dev/fd/# refer to file descriptors which can be accessed through the file system.
Note: /dev/fd/0, /dev/fd/1 and /dev/fd/2 files are created by default when devfs alone is mounted. fdescfs creates entries for all file descriptors opened by the process.
Based on the above, it seems counting the number of entries under
/dev/fd would return the number of open file descriptors that reference files on the file system. By default a process would have exactly three file descriptors open, for stdin, stdout and stderr.
Surprisingly a the below program doesn’t return 3.
The problem here is that in order to read the contents of
/dev/fs the program has to allocate another file descriptor. This additional file descriptor causes this method to always return one more than the expected number of file descriptors. This limitation combined with
/dev/fs will not show other kinds of file descriptors like sockets, means this method is limited and inaccurate.
A Better Approach§
A better approach on macOS is to use the completely undocumented
libproc.h header in the macOS SDK. Within this header there is the
proc_pidinfo function with the following signature.
Although undocumented, this is the same function that lsof uses. The
pid argument is self explanatory. The
flavor argument comes from the
sys/proc_info.h header. The header lists many possible values but
PROC_PIDLISTFDS appears to be the value needed for counting file descriptors.
In our case
PROC_PIDLISTFDS is the desired flavor and the
proc_fdinfo struct shows some promising fields.
Already this approach appears to be better for two reasons. First,
proc_pidinfo accepts an arbitrary pid, allowing for inspection of an arbitrary process. Two, all kinds of file descriptors can be counted and inspected. The header indicates that file descriptors related to files, sockets, pipes, kqueue, AppleTalk and more will be returned.
buffersize and the return value are all undocumented. However the source code of
proc_pidinfo function is released by Apple. So it’s possible to understand these arguments by looking at the source.
__proc_info appears to be a kernel system call. Inspecting the XNU sources eventually leads to the complete implementation of the
PROC_PIDLISTFDS flavor in a function called
Based on this code it’s clear that
buffer is supposed to be filled with
proc_fdinfo structs that are returned from the call and
buffersize is the size of this buffer in bytes. The return value is the number of entries successfully written to the buffer. If the function is called with
NULL for the buffer, it returns the number of file descriptors plus 20 as a suggested buffer size.
arg appears to be completely unused.
Given all of this, writing a program to use
proc_pidinfo to accurately count the number of open fds in the current process is straight forward.
Running this program now returns the expected output.
proc_pidinfo function is a better way of counting the number of open file descriptors for the current process on macOS.
proc_pidinfo allows for inspecting an arbitrary process and allows for accurate results. In addition, the results can be filtered on type of file descriptor such as sockets or pipes.