Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Closing opened file descriptors in child process

Is there a way to iterate through already open file descriptors (opened by parent process) and close them one by one in child process?

OS: Unix.

Reason for closure: RLIMIT_NOFILE limit of the setrlimit() constrains the number of file descriptors that a process may allocate.If we want to restrict our child process by setting this limit, it depends on the already allocated file descriptors.

Trying to set this limit in a child process is restricted as the parent process has some open file descriptors and hence we cannot set this limit lesser than that number.

Example: If parent process has 10 file descriptors allocated and we wish to limit the child process file descriptor number to less than 10 (Say 3), we would need to close 7 file descriptors inside the child process.

The solution to this can benefit all those who want to restrict their child process from creating new files or opening new network connections.

like image 459
learner Avatar asked Mar 07 '13 08:03

learner


People also ask

Do you have to close file descriptors?

Yes, close your file descriptors and free all heap memory, even if you know that the OS will clean it up - that way, when you run valgrind or some similar tool, you don't get a lot of noise in the results, and you can easily recognize "legit" fd leaks.

What does closing a file descriptor do?

close() closes a file descriptor, so that it no longer refers to any file and may be reused. Any record locks (see fcntl(2)) held on the file it was associated with, and owned by the process, are removed (regardless of the file descriptor that was used to obtain the lock).

Does child process inherit file descriptors?

* The child inherits copies of the parent's set of open file descriptors. Each file descriptor in the child refers to the same open file description (see open(2)) as the corresponding file descriptor in the parent.

Are file descriptors closed automatically?

Files are automatically closed, but it's a good practice. Show activity on this post.


1 Answers

The following idiom is not uncommon (this is taken from the C part of MIMEDefang):

/* Number of file descriptors to close when forking */
#define CLOSEFDS 256
...

static void
closefiles(void)
{
    int i;
    for (i=0; i<CLOSEFDS; i++) {
        (void) close(i);
   }
}

(That's from mimedefang-2.78, the implementation has been changed slightly in later releases.)

It is something of a hack (as the MIMEDefang code freely admitted). In many cases it's more useful to start at FD 3 (or STDERR_FILENO+1) instead of 0. close() returns EBADF with an invalid FD, but this doesn't usually present problems (at least not in C, in other languages an exception may be thrown).

Since you can determine the file-descriptor upper limit with getrlimit(RLIMIT_NOFILE,...) which is defined as:

RLIMIT_NOFILE

This is a number one greater than the maximum value that the system may assign to a newly-created descriptor. If this limit is exceeded, functions that allocate a file descriptor shall fail with errno set to [EMFILE]. This limit constrains the number of file descriptors that a process may allocate.

you can use this (subtracting 1) as the upper limit of the loop. The above and ulimit -n, getconf OPEN_MAX and sysconf(OPEN_MAX) should all agree.

Since open() always assigns the lowest free FD, the maximum number of open files and the highest FD+1 are the same number.

To detemine what fds are open, instead of close() use a no-op lseek(fd, 0, SEEK_CUR) which will return EBADF if the fd is not open (there's no obvious benefit to calling lseek() for a conditional close() though). socat's filan loops over 0 .. FD_SETSIZE calling fstat()/fstat64().

The libslack daemon utility which daemonizes arbitrary processes also uses this brute-force approach (while making sure to keep the first three descriptors open when used under inetd).

In the case where your program can track file handles it is preferable to do so, or use FD_CLOEXEC where available. However, should you wish to code defensively, you might prefer to distrust your parent process, say for an external handler/viewer process started by a browser, e.g. like this long-lived and ancient Mozilla bug on Unix platforms.

For the paranoid (do you want your PDF viewer to inherit every open Firefox FD including your cache, and open TCP connections?):

#!/bin/bash
# you might want to use the value of "ulimit -n" instead of picking 255
for ((fd=3; fd<=255; fd++)); do
  exec {fd}<&- # close
done
exec /usr/local/bin/xpdf "$@"

Update after 15 years this issue was resolved in Firefox 58 (2018) when process creation was changed from the Netscape Portable Runtime (NSPR) API to use LaunchApp.

like image 68
mr.spuratic Avatar answered Sep 29 '22 10:09

mr.spuratic