/*
* Copyright © 2020 Christian Persch
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see .
*/
#include "config.h"
#ifdef HAVE_SYS_RESOURCE_H
#include
#endif
#include
#include
#ifdef __linux__
#include /* for syscall and SYS_getdents64 */
#endif
#ifdef __APPLE__
#include
#include
#endif
#include "missing.hh"
/* BEGIN copied from glib
*
* Code for fdwalk copied from glib/glib/gspawn.c, there under LGPL2.1+,
* and used here under LGPL3+.
*
* Copyright 2000 Red Hat, Inc.
*/
#ifndef HAVE_FDWALK
#ifdef __linux__
struct linux_dirent64
{
guint64 d_ino; /* 64-bit inode number */
guint64 d_off; /* 64-bit offset to next structure */
unsigned short d_reclen; /* Size of this dirent */
unsigned char d_type; /* File type */
char d_name[]; /* Filename (null-terminated) */
};
/* This function is called between fork and execve/_exit and so must be
* async-signal-safe; see man:signal-safety(7).
*/
static int
filename_to_fd (const char *p)
{
char c;
int fd = 0;
const int cutoff = G_MAXINT / 10;
const int cutlim = G_MAXINT % 10;
if (*p == '\0')
return -1;
while ((c = *p++) != '\0')
{
if (c < '0' || c > '9')
return -1;
c -= '0';
/* Check for overflow. */
if (fd > cutoff || (fd == cutoff && c > cutlim))
return -1;
fd = fd * 10 + c;
}
return fd;
}
#endif /* __linux__ */
/* This function is called between fork and execve/_exit and so must be
* async-signal-safe; see man:signal-safety(7).
*/
static rlim_t
getrlimit_NOFILE_max(void)
{
#ifdef HAVE_SYS_RESOURCE_H
#ifdef __linux__
{
struct rlimit rlim;
if (prlimit(0 /* this PID */, RLIMIT_NOFILE, nullptr, &rlim) == 0)
return rlim.rlim_max;
return RLIM_INFINITY;
}
#endif /* __linux__ */
#ifdef __GLIBC__
{
struct rlimit rlim;
/* Use getrlimit() function provided by the system if it is known to be
* async-signal safe.
*
* According to the glibc manual, getrlimit is AS-safe.
*/
if (getrlimit(RLIMIT_NOFILE, &rlim) == 0)
return rlim.rlim_max;
}
/* fallback */
#endif /* __GLIBC__ */
#endif /* HAVE_SYS_RESOURCE_H */
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__APPLE__)
/* Use sysconf() function provided by the system if it is known to be
* async-signal safe.
*/
auto const r = sysconf(_SC_OPEN_MAX);
if (r != -1)
return r;
/* fallback */
#endif
/* couldn't determine, so potentially infinite */
return RLIM_INFINITY;
}
#ifndef HAVE_CLOSE_RANGE
int
close_range(unsigned int first_fd,
unsigned int last_fd,
unsigned int flags)
{
#if defined(__linux__) && defined(SYS_close_range)
return syscall(SYS_close_range,
first_fd,
last_fd == unsigned(-1) ? ~0u : last_fd,
flags);
#else
errno = ENOSYS;
return -1;
#endif
}
#endif /* !HAVE_CLOSE_RANGE */
/* This function is called between fork and execve/_exit and so must be
* async-signal-safe; see man:signal-safety(7).
*/
int
fdwalk(int (*cb)(void *data, int fd),
void *data)
{
/* Fallback implementation of fdwalk. It should be async-signal safe, but it
* may be slow on non-Linux operating systems, especially on systems allowing
* very high number of open file descriptors.
*/
int fd;
int res = 0;
#ifdef __linux__
/* Fall back to iterating over /proc/self/fd.
* Avoid use of opendir/closedir since these are not async-signal-safe.
*/
int dir_fd = open ("/proc/self/fd", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
if (dir_fd >= 0)
{
char buf[4096];
int pos, nread;
struct linux_dirent64 *de;
while ((nread = syscall (SYS_getdents64, dir_fd, buf, sizeof(buf))) > 0)
{
for (pos = 0; pos < nread; pos += de->d_reclen)
{
de = reinterpret_cast(buf + pos);
fd = filename_to_fd (de->d_name);
if (fd < 0 || fd == dir_fd)
continue;
if ((res = cb (data, fd)) != 0)
break;
}
}
close (dir_fd);
return res;
}
/* If /proc is not mounted or not accessible we fall back to the old
* rlimit trick */
#endif
auto const open_max = getrlimit_NOFILE_max();
if (open_max == RLIM_INFINITY || open_max > G_MAXINT) {
/* We cannot close infinitely many FDs, but we also must not
* leak any FDs. Return an error.
*/
errno = ENFILE;
return -1;
}
#if defined(__APPLE__)
/* proc_pidinfo isn't documented as async-signal-safe but looking at the implementation
* in the darwin tree here:
*
* https://opensource.apple.com/source/Libc/Libc-498/darwin/libproc.c.auto.html
*
* It's just a thin wrapper around a syscall, so it's probably okay.
*/
{
char buffer[open_max * PROC_PIDLISTFD_SIZE];
ssize_t buffer_size;
buffer_size = proc_pidinfo(getpid(), PROC_PIDLISTFDS, 0, buffer, sizeof(buffer));
if (buffer_size > 0 &&
sizeof(buffer) >= (size_t)buffer_size &&
(buffer_size % PROC_PIDLISTFD_SIZE) == 0)
{
const struct proc_fdinfo *fd_info = (const struct proc_fdinfo *)buffer;
size_t number_of_fds = (size_t)buffer_size / PROC_PIDLISTFD_SIZE;
for (size_t i = 0; i < number_of_fds; i++)
if ((res = cb(data, fd_info[i].proc_fd)) != 0)
break;
return res;
}
}
#endif
for (fd = 0; fd < int(open_max); fd++)
if ((res = cb (data, fd)) != 0)
break;
return res;
}
#endif /* !HAVE_FDWALK */
#ifndef HAVE_STRCHRNUL
/* Copied from glib */
char*
strchrnul(char const* s,
int c)
{
char *p = (char *) s;
while (*p && (*p != c))
++p;
return p;
}
#endif /* !HAVE_STRCHRNUL */
/* END copied from glib */