Improve comments re closefrom() platforms
[xapian.git] / xapian-core / common / closefrom.cc
blob6e7468aec189f5d120669ec1b2befeb8c8006c96
1 /** @file closefrom.cc
2 * @brief Implementation of closefrom() function.
3 */
4 /* Copyright (C) 2010,2011,2012,2016 Olly Betts
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 #include <config.h>
23 // We don't currently need closefrom() on __WIN32__.
24 #if !defined HAVE_CLOSEFROM && !defined __WIN32__
26 #include "closefrom.h"
28 #include "safeerrno.h"
29 #include "safefcntl.h"
30 #include "safeunistd.h"
32 #ifdef HAVE_SYS_RESOURCE_H
33 # include <sys/types.h>
34 # include <sys/resource.h>
35 #endif
37 #if defined __linux__ || defined __APPLE__
38 # include "safedirent.h"
39 # include <cstdlib>
41 using namespace std;
42 #endif
44 static int
45 get_maxfd() {
46 #ifdef F_MAXFD
47 // May only be supported by NetBSD, modern versions of which implement
48 // closefrom(). Leave this in so that if other platforms have it or add
49 // it they will benefit.
50 int maxfd = fcntl(0, F_MAXFD);
51 if (maxfd >= 0) return maxfd;
52 #endif
53 #ifdef HAVE_GETRLIMIT
54 struct rlimit rl;
55 if (getrlimit(RLIMIT_NOFILE, &rl) == 0 &&
56 rl.rlim_max != RLIM_INFINITY) {
57 return static_cast<int>(rl.rlim_max) - 1;
59 #endif
60 return static_cast<int>(sysconf(_SC_OPEN_MAX)) - 1;
63 // These platforms are known to provide closefrom():
64 // FreeBSD >= 8.0, NetBSD >= 3.0, OpenBSD >= 3.5, Solaris >= 9
66 // These platforms are known to support fcntl() with F_CLOSEM:
67 // AIX, IRIX, NetBSD >= 2.0
69 // These platforms have getdirentries() and a "magic" directory with an entry
70 // for each FD open in the current process:
71 // Linux, OS X
73 // Other platforms just use a loop up to a limit obtained from
74 // fcntl(0, F_MAXFD), getrlimit(RLIMIT_NOFILE, ...), or sysconf(_SC_OPEN_MAX).
76 void
77 Xapian::Internal::closefrom(int fd)
79 int maxfd = -1;
80 #ifdef F_CLOSEM
81 if (fcntl(fd, F_CLOSEM, 0) >= 0)
82 return;
83 #elif defined __linux__ || defined __APPLE__
84 #if 0
85 // Some platforms have /proc/<pid>/fd but not /proc/self - if any such
86 // platforms don't have either closefrom() or F_CLOSEM but do have
87 // getdirentries() then this code can be used. AIX is an example of
88 // a platform of the former, but apparently has F_CLOSEM.
89 char path[6 + sizeof(pid_t) * 3 + 4];
90 sprintf(path, "/proc/%ld/fd", long(getpid()));
91 #elif defined __linux__
92 const char * path = "/proc/self/fd";
93 #elif defined __APPLE__ // Mac OS X
94 const char * path = "/dev/fd";
95 #endif
96 int dir = open(path, O_RDONLY|O_DIRECTORY);
97 if (dir >= 0) {
98 off_t base = 0;
99 while (true) {
100 char buf[1024];
101 errno = 0;
102 // We use getdirentries() instead of opendir()/readdir() here
103 // because the latter can call malloc(), which isn't safe to do
104 // between fork() and exec() in a multi-threaded program.
105 ssize_t c = getdirentries(dir, buf, sizeof(buf), &base);
106 if (c == 0) {
107 close(dir);
108 return;
110 if (c < 0) {
111 // Fallback if getdirentries() fails.
112 break;
114 struct dirent *d;
115 for (ssize_t pos = 0; pos < c; pos += d->d_reclen) {
116 d = reinterpret_cast<struct dirent*>(buf + pos);
117 const char * leaf = d->d_name;
118 if (leaf[0] < '0' || leaf[0] > '9') {
119 // Skip '.' and '..'.
120 continue;
122 int n = atoi(leaf);
123 if (n < fd) {
124 // FD below threshold.
125 continue;
127 if (n == dir) {
128 // Don't close the fd open on the directory.
129 continue;
131 #ifdef __linux__
132 // Running under valgrind causes some entries above the
133 // reported RLIMIT_NOFILE value to appear in
134 // /proc/self/fd - see:
135 // https://bugs.kde.org/show_bug.cgi?id=191758
137 // If we try to close these, valgrind issues a warning about
138 // trying to close an invalid file descriptor. These entries
139 // start at 1024, so we check that value first so we can
140 // usually avoid having to read the fd limit when we're not
141 // running under valgrind.
142 if (n >= 1024) {
143 if (maxfd < 0)
144 maxfd = get_maxfd();
145 if (n > maxfd)
146 continue;
148 #endif
149 // Retry on EINTR.
150 while (close(n) < 0 && errno == EINTR) { }
153 close(dir);
155 #endif
156 if (maxfd < 0)
157 maxfd = get_maxfd();
158 while (fd <= maxfd) {
159 // Retry on EINTR; just ignore other errors (we'll get EBADF if fd
160 // isn't open so that's OK).
161 while (close(fd) < 0 && errno == EINTR) { }
162 ++fd;
166 #endif