core: Restore get_max_fds optimization on Linux

* src/posix-io.c (get_max_fds): Restore Linux optimization, this time
using open/getdents/close rather than opendir/readdir/closedir.
--

opendir/readdir/closedir may allocate/free memory, and aren't required
to do so in an async-signal-safe way.  On the other hand, opening
/proc/self/fd directly and iterating over it using getdents is safe.

(getdents is not strictly speaking documented to be async-signal-safe
because it's not in POSIX.  However, the Linux implementation is
essentially just a souped-up read.  Python >= 3.2.3 makes the same
assumption.)

Signed-off-by: Colin Watson <cjwatson@debian.org>
This commit is contained in:
Colin Watson 2017-09-16 04:16:45 +01:00 committed by Werner Koch
parent bff9448428
commit b5b996b1a1
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B

View File

@ -48,6 +48,7 @@
#include <sys/resource.h> #include <sys/resource.h>
#if __linux__ #if __linux__
# include <sys/syscall.h>
# include <sys/types.h> # include <sys/types.h>
# include <dirent.h> # include <dirent.h>
#endif /*__linux__ */ #endif /*__linux__ */
@ -279,6 +280,20 @@ _gpgme_io_set_nonblocking (int fd)
} }
#ifdef __linux__
/* This is not declared in public headers; getdents(2) says that we must
* define it ourselves. */
struct linux_dirent
{
unsigned long d_ino;
unsigned long d_off;
unsigned short d_reclen;
char d_name[];
};
# define DIR_BUF_SIZE 1024
#endif /* __linux__ */
static long int static long int
get_max_fds (void) get_max_fds (void)
{ {
@ -291,39 +306,57 @@ get_max_fds (void)
* than for example doing 4096 close calls where almost all of them * than for example doing 4096 close calls where almost all of them
* will fail. * will fail.
* *
* Unfortunately we can't call opendir between fork and exec in a * We can't use the normal opendir/readdir/closedir interface between
* multi-threaded process because opendir uses malloc and thus a * fork and exec in a multi-threaded process because opendir uses
* mutex which may deadlock with a malloc in another thread. Thus * malloc and thus a mutex which may deadlock with a malloc in another
* the code is not used until we can have a opendir variant which * thread. However, the underlying getdents system call is safe. */
* does not use malloc. */ #ifdef __linux__
/* #ifdef __linux__ */ {
/* { */ int dir_fd;
/* DIR *dir = NULL; */ char dir_buf[DIR_BUF_SIZE];
/* struct dirent *dir_entry; */ struct linux_dirent *dir_entry;
/* const char *s; */ int r, pos;
/* int x; */ const char *s;
int x;
/* dir = opendir ("/proc/self/fd"); */ dir_fd = open ("/proc/self/fd", O_RDONLY | O_DIRECTORY);
/* if (dir) */ if (dir_fd != -1)
/* { */ {
/* while ((dir_entry = readdir (dir))) */ for (;;)
/* { */ {
/* s = dir_entry->d_name; */ r = syscall(SYS_getdents, dir_fd, dir_buf, DIR_BUF_SIZE);
/* if ( *s < '0' || *s > '9') */ if (r == -1)
/* continue; */ {
/* x = atoi (s); */ /* Fall back to other methods. */
/* if (x > fds) */ fds = -1;
/* fds = x; */ break;
/* } */ }
/* closedir (dir); */ if (r == 0)
/* } */ break;
/* if (fds != -1) */
/* { */ for (pos = 0; pos < r; pos += dir_entry->d_reclen)
/* fds++; */ {
/* source = "/proc"; */ dir_entry = (struct linux_dirent *) (dir_buf + pos);
/* } */ s = dir_entry->d_name;
/* } */ if (*s < '0' || *s > '9')
/* #endif /\* __linux__ *\/ */ continue;
/* atoi is not guaranteed to be async-signal-safe. */
for (x = 0; *s >= '0' && *s <= '9'; s++)
x = x * 10 + (*s - '0');
if (!*s && x > fds && x != dir_fd)
fds = x;
}
}
close (dir_fd);
}
if (fds != -1)
{
fds++;
source = "/proc";
}
}
#endif /* __linux__ */
#ifdef RLIMIT_NOFILE #ifdef RLIMIT_NOFILE
if (fds == -1) if (fds == -1)