1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
|
/** @file
* @brief Implementation of closefrom() function.
*/
/* Copyright (C) 2010,2011,2012,2016,2018,2019 Olly Betts
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <config.h>
// We don't currently need closefrom() on __WIN32__.
#if !defined HAVE_CLOSEFROM && !defined __WIN32__
#include "closefrom.h"
#include <cerrno>
#include "safefcntl.h"
#include "safeunistd.h"
#ifdef HAVE_SYS_RESOURCE_H
# include <sys/types.h>
# include <sys/resource.h>
#endif
#if defined __linux__
# include "alignment_cast.h"
# include "safedirent.h"
# include "parseint.h"
#elif defined __APPLE__
# include <sys/attr.h>
# include <cstring>
# include "parseint.h"
#endif
static int
get_maxfd() {
#ifdef F_MAXFD
// May only be supported by NetBSD, modern versions of which implement
// closefrom(). Leave this in so that if other platforms have it or add
// it they will benefit.
int maxfd = fcntl(0, F_MAXFD);
if (maxfd >= 0) return maxfd;
#endif
#ifdef HAVE_GETRLIMIT
struct rlimit rl;
if (getrlimit(RLIMIT_NOFILE, &rl) == 0 &&
rl.rlim_max != RLIM_INFINITY) {
return static_cast<int>(rl.rlim_max) - 1;
}
#endif
return static_cast<int>(sysconf(_SC_OPEN_MAX)) - 1;
}
// These platforms are known to provide closefrom():
// FreeBSD >= 8.0, NetBSD >= 3.0, OpenBSD >= 3.5, Solaris >= 9,
// Linux >= 5.9 with glibc >= 2.34
//
// These platforms are known to support fcntl() with F_CLOSEM:
// AIX, NetBSD >= 2.0
//
// These platforms have getdirentries() and a "magic" directory with an entry
// for each FD open in the current process:
// Linux (at least with glibc)
//
// These platforms have getdirentriesattr() and a "magic" directory with an
// entry for each FD open in the current process:
// macOS
//
// Other platforms just use a loop up to a limit obtained from
// fcntl(0, F_MAXFD), getrlimit(RLIMIT_NOFILE, ...), or sysconf(_SC_OPEN_MAX)
// - known examples:
// Android (bionic libc doesn't provide getdirentries())
void
Xapian::Internal::closefrom(int fd)
{
int maxfd = -1;
#ifdef F_CLOSEM
if (fcntl(fd, F_CLOSEM, 0) >= 0)
return;
#elif defined HAVE_GETDIRENTRIES && defined __linux__
const char* path = "/proc/self/fd";
int dir = open(path, O_RDONLY|O_DIRECTORY);
if (dir >= 0) {
off_t base = 0;
while (true) {
char buf[1024];
errno = 0;
// We use getdirentries() instead of opendir()/readdir() here
// because the latter can call malloc(), which isn't safe to do
// between fork() and exec() in a multi-threaded program.
ssize_t c = getdirentries(dir, buf, sizeof(buf), &base);
if (c == 0) {
close(dir);
return;
}
if (c < 0) {
// Fallback if getdirentries() fails.
break;
}
struct dirent* d;
for (ssize_t pos = 0; pos < c; pos += d->d_reclen) {
d = alignment_cast<struct dirent*>(buf + pos);
const char* leaf = d->d_name;
int n;
if (!parse_signed(leaf, n)) {
// Skip '.' and '..'.
continue;
}
if (n < fd) {
// FD below threshold.
continue;
}
if (n == dir) {
// Don't close the fd open on the directory.
continue;
}
// Running under valgrind causes some entries above the
// reported RLIMIT_NOFILE value to appear in
// /proc/self/fd - see:
// https://bugs.kde.org/show_bug.cgi?id=191758
//
// If we try to close these, valgrind issues a warning about
// trying to close an invalid file descriptor. These entries
// start at 1024, so we check that value first so we can
// usually avoid having to read the fd limit when we're not
// running under valgrind.
if (n >= 1024) {
if (maxfd < 0)
maxfd = get_maxfd();
if (n > maxfd)
continue;
}
// Retry on EINTR.
while (close(n) < 0 && errno == EINTR) { }
}
}
close(dir);
}
#elif defined __APPLE__ // macOS
const char* path = "/dev/fd";
#ifdef __LP64__
typedef unsigned int gdea_type;
#else
typedef unsigned long gdea_type;
#endif
int dir = open(path, O_RDONLY|O_DIRECTORY);
if (dir >= 0) {
gdea_type base = 0;
struct attrlist alist;
std::memset(&alist, 0, sizeof(alist));
alist.bitmapcount = ATTR_BIT_MAP_COUNT;
alist.commonattr = ATTR_CMN_NAME;
while (true) {
char buf[1024];
errno = 0;
// We use getdirentriesattr() instead of opendir()/readdir() here
// because the latter can call malloc(), which isn't safe to do
// between fork() and exec() in a multi-threaded program. We only
// want filename, but can't use getdirentries() because it's not
// available with 64-bit inode_t, which seems to be tied to LFS.
gdea_type count = sizeof(buf);
gdea_type new_state;
int r = getdirentriesattr(dir, &alist, buf, sizeof(buf),
&count, &base, &new_state, 0);
(void)new_state;
if (r < 0) {
// Fallback if getdirentriesattr() fails.
break;
}
char* p = buf;
while (count-- > 0) {
const char* leaf = p + sizeof(u_int32_t);
p += *static_cast<u_int32_t*>(static_cast<void*>(p));
int n;
if (!parse_signed(leaf, n)) {
// Skip '.' and '..'.
continue;
}
if (n < fd) {
// FD below threshold.
continue;
}
if (n == dir) {
// Don't close the fd open on the directory.
continue;
}
// Retry on EINTR.
while (close(n) < 0 && errno == EINTR) { }
}
if (r == 1) {
// We've had the last entry.
close(dir);
return;
}
}
close(dir);
}
#elif 0
// Some platforms have /proc/<pid>/fd but not /proc/self - if any such
// platforms don't have either closefrom() or F_CLOSEM but do have
// getdirentries() then this code can be used. AIX is an example of
// a platform of the former, but apparently has F_CLOSEM.
char path[6 + sizeof(pid_t) * 3 + 4];
# ifdef SNPRINTF
snprintf(path, sizeof(path), "/proc/%ld/fd", long(getpid()));
path[sizeof(path) - 1] = '\0';
# else
sprintf(path, "/proc/%ld/fd", long(getpid()));
# endif
#endif
if (maxfd < 0)
maxfd = get_maxfd();
while (fd <= maxfd) {
// Retry on EINTR; just ignore other errors (we'll get EBADF if fd
// isn't open so that's OK).
while (close(fd) < 0 && errno == EINTR) { }
++fd;
}
}
#endif
|