是否有可移植的方式(POSIX)来获取当前进程的最高分配文件描述符号?
我知道在AIX上有一个很好的方法来获取数字,但我正在寻找一种可移植的方法.
我问的原因是我想要关闭所有打开的文件描述符.我的程序是一个以root用户身份运行的服务器,为非root用户分叉和执行子程序.在子进程中保留特权文件描述符是一个安全问题.有些文件描述符可能是由我无法控制的代码(C库,第三方库等)打开的,所以我不能依赖它们FD_CLOEXEC
.
虽然是可移植的,但是关闭所有文件描述符sysconf(_SC_OPEN_MAX)
是不可靠的,因为在大多数系统上,此调用返回当前文件描述符软限制,该限制可能已降低到最高使用文件描述符之下.另一个问题是许多系统sysconf(_SC_OPEN_MAX)
可能会返回INT_MAX
,这可能导致这种方法变得无法接受.不幸的是,没有可靠的,可移植的替代方案,不涉及迭代每个可能的非负int文件描述符.
虽然不可移植,但目前常用的大多数操作系统都为此问题提供了以下一种或多种解决方案:
用于关闭所有文件描述符的库函数> = fd.对于关闭所有文件描述符的常见情况,这是最简单的解决方案,尽管它不能用于其他许多方面.要关闭除某个集合之外的所有文件描述符,dup2
可以使用它们预先将它们移动到低端,并在必要时将它们移回.
closefrom(fd)
(Solaris 9或更高版本,FreeBSD 7.3或8.0及更高版本,NetBSD 3.0或更高版本,OpenBSD 3.5或更高版本.)
fcntl(fd, F_CLOSEM, 0)
(AIX,IRIX,NetBSD)
一个库函数,用于提供进程当前使用的最大文件描述符.要关闭某个数字以上的所有文件描述符,要么将所有文件描述符关闭到最大值,要么在循环中连续获取和关闭最高文件描述符,直到达到下限.哪个更有效取决于文件描述符密度.
fcntl(0, F_MAXFD)
(NetBSD的)
pstat_getproc(&ps, sizeof(struct pst_status), (size_t)0, (int)getpid())
返回有关进程的信息,包括当前打开的最高文件描述符ps.pst_highestfd
.(HP-UX)
包含每个打开文件描述符的条目的目录.这是最灵活的方法,因为它允许关闭所有文件描述符,查找最高文件描述符,或者对每个打开的文件描述符执行任何其他操作,甚至是其他进程(在大多数系统上).然而,这可能比常用的其他方法更复杂.此外,它可能由于各种原因而失败,例如未安装proc/fdescfs,chroot环境或没有可用于打开目录的文件描述符(进程或系统限制).因此,这种方法的使用通常与回退机制相结合. 示例(OpenSSH), 另一个示例(glib).
proc_pidinfo(getpid(), PROC_PIDLISTFDS, 0, fdinfo_buf, sz)
pid/proc/
或/fd/
(Linux,Solaris,AIX,Cygwin,NetBSD)
(AIX不支持" /proc/self/fd/
")
self
(FreeBSD,Darwin,OS X)
使用这种方法可以很难可靠地处理所有角落情况.例如,考虑要关闭所有文件描述符> = fd的情况,但是使用所有文件描述符< fd,当前进程资源限制为fd,并且正在使用文件描述符> = fd.由于已达到进程资源限制,因此无法打开目录.如果从fd关闭每个文件描述符到资源限制或/dev/fd/
用作回退,则不会关闭任何内容.
POSIX的方式是:
int maxfd=sysconf(_SC_OPEN_MAX);
for(int fd=3; fd
(请注意,从3开始关闭,以保持stdin/stdout/stderr打开)
如果文件描述符未打开,close()将无害地返回EBADF.没有必要浪费另一个系统调用检查.
有些Unix支持closefrom().这可以避免对close()的过多调用,具体取决于最大可能的文件描述符编号.虽然我知道最好的解决方案,但它完全不可移植.
3> Hongli..:
我编写了代码来处理所有特定于平台的功能.所有功能都是异步信号安全的.以为人们可能会觉得这很有用.现在只在OS X上测试,随时改进/修复.
// Async-signal safe way to get the current process's hard file descriptor limit.
static int
getFileDescriptorLimit() {
long long sysconfResult = sysconf(_SC_OPEN_MAX);
struct rlimit rl;
long long rlimitResult;
if (getrlimit(RLIMIT_NOFILE, &rl) == -1) {
rlimitResult = 0;
} else {
rlimitResult = (long long) rl.rlim_max;
}
long result;
if (sysconfResult > rlimitResult) {
result = sysconfResult;
} else {
result = rlimitResult;
}
if (result < 0) {
// Both calls returned errors.
result = 9999;
} else if (result < 2) {
// The calls reported broken values.
result = 2;
}
return result;
}
// Async-signal safe function to get the highest file
// descriptor that the process is currently using.
// See also http://stackoverflow.com/questions/899038/getting-the-highest-allocated-file-descriptor
static int
getHighestFileDescriptor() {
#if defined(F_MAXFD)
int ret;
do {
ret = fcntl(0, F_MAXFD);
} while (ret == -1 && errno == EINTR);
if (ret == -1) {
ret = getFileDescriptorLimit();
}
return ret;
#else
int p[2], ret, flags;
pid_t pid = -1;
int result = -1;
/* Since opendir() may not be async signal safe and thus may lock up
* or crash, we use it in a child process which we kill if we notice
* that things are going wrong.
*/
// Make a pipe.
p[0] = p[1] = -1;
do {
ret = pipe(p);
} while (ret == -1 && errno == EINTR);
if (ret == -1) {
goto done;
}
// Make the read side non-blocking.
do {
flags = fcntl(p[0], F_GETFL);
} while (flags == -1 && errno == EINTR);
if (flags == -1) {
goto done;
}
do {
fcntl(p[0], F_SETFL, flags | O_NONBLOCK);
} while (ret == -1 && errno == EINTR);
if (ret == -1) {
goto done;
}
do {
pid = fork();
} while (pid == -1 && errno == EINTR);
if (pid == 0) {
// Don't close p[0] here or it might affect the result.
resetSignalHandlersAndMask();
struct sigaction action;
action.sa_handler = _exit;
action.sa_flags = SA_RESTART;
sigemptyset(&action.sa_mask);
sigaction(SIGSEGV, &action, NULL);
sigaction(SIGPIPE, &action, NULL);
sigaction(SIGBUS, &action, NULL);
sigaction(SIGILL, &action, NULL);
sigaction(SIGFPE, &action, NULL);
sigaction(SIGABRT, &action, NULL);
DIR *dir = NULL;
#ifdef __APPLE__
/* /dev/fd can always be trusted on OS X. */
dir = opendir("/dev/fd");
#else
/* On FreeBSD and possibly other operating systems, /dev/fd only
* works if fdescfs is mounted. If it isn't mounted then /dev/fd
* still exists but always returns [0, 1, 2] and thus can't be
* trusted. If /dev and /dev/fd are on different filesystems
* then that probably means fdescfs is mounted.
*/
struct stat dirbuf1, dirbuf2;
if (stat("/dev", &dirbuf1) == -1
|| stat("/dev/fd", &dirbuf2) == -1) {
_exit(1);
}
if (dirbuf1.st_dev != dirbuf2.st_dev) {
dir = opendir("/dev/fd");
}
#endif
if (dir == NULL) {
dir = opendir("/proc/self/fd");
if (dir == NULL) {
_exit(1);
}
}
struct dirent *ent;
union {
int highest;
char data[sizeof(int)];
} u;
u.highest = -1;
while ((ent = readdir(dir)) != NULL) {
if (ent->d_name[0] != '.') {
int number = atoi(ent->d_name);
if (number > u.highest) {
u.highest = number;
}
}
}
if (u.highest != -1) {
ssize_t ret, written = 0;
do {
ret = write(p[1], u.data + written, sizeof(int) - written);
if (ret == -1) {
_exit(1);
}
written += ret;
} while (written < (ssize_t) sizeof(int));
}
closedir(dir);
_exit(0);
} else if (pid == -1) {
goto done;
} else {
do {
ret = close(p[1]);
} while (ret == -1 && errno == EINTR);
p[1] = -1;
union {
int highest;
char data[sizeof(int)];
} u;
ssize_t ret, bytesRead = 0;
struct pollfd pfd;
pfd.fd = p[0];
pfd.events = POLLIN;
do {
do {
// The child process must finish within 30 ms, otherwise
// we might as well query sysconf.
ret = poll(&pfd, 1, 30);
} while (ret == -1 && errno == EINTR);
if (ret <= 0) {
goto done;
}
do {
ret = read(p[0], u.data + bytesRead, sizeof(int) - bytesRead);
} while (ret == -1 && ret == EINTR);
if (ret == -1) {
if (errno != EAGAIN) {
goto done;
}
} else if (ret == 0) {
goto done;
} else {
bytesRead += ret;
}
} while (bytesRead < (ssize_t) sizeof(int));
result = u.highest;
goto done;
}
done:
if (p[0] != -1) {
do {
ret = close(p[0]);
} while (ret == -1 && errno == EINTR);
}
if (p[1] != -1) {
do {
close(p[1]);
} while (ret == -1 && errno == EINTR);
}
if (pid != -1) {
do {
ret = kill(pid, SIGKILL);
} while (ret == -1 && errno == EINTR);
do {
ret = waitpid(pid, NULL, 0);
} while (ret == -1 && errno == EINTR);
}
if (result == -1) {
result = getFileDescriptorLimit();
}
return result;
#endif
}
void
closeAllFileDescriptors(int lastToKeepOpen) {
#if defined(F_CLOSEM)
int ret;
do {
ret = fcntl(lastToKeepOpen + 1, F_CLOSEM);
} while (ret == -1 && errno == EINTR);
if (ret != -1) {
return;
}
#elif defined(HAS_CLOSEFROM)
closefrom(lastToKeepOpen + 1);
return;
#endif
for (int i = getHighestFileDescriptor(); i > lastToKeepOpen; i--) {
int ret;
do {
ret = close(i);
} while (ret == -1 && errno == EINTR);
}
}