Counting open file descriptors on macOS
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.
The buffer
and 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.
Where __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 proc_pidfdlist
.
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.
Conclusion
Using the 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.