blob: e4f3bd85dec41d869ed674416b976122136c3530 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2019 Intel Corporation.
** Copyright (C) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
** copies of the Software, and to permit persons to whom the Software is
** furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in
** all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
** THE SOFTWARE.
**
****************************************************************************/
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include "forkfd.h"
#include <sys/types.h>
#if defined(__OpenBSD__) || defined(__NetBSD__)
# include <sys/param.h>
#endif
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <assert.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#ifdef __linux__
# define HAVE_WAIT4 1
# if defined(__BIONIC__) || (defined(__GLIBC__) && (__GLIBC__ << 8) + __GLIBC_MINOR__ >= 0x208 && \
(!defined(__UCLIBC__) || ((__UCLIBC_MAJOR__ << 16) + (__UCLIBC_MINOR__ << 8) + __UCLIBC_SUBLEVEL__ > 0x90201)))
# include <sys/eventfd.h>
# ifdef EFD_CLOEXEC
# define HAVE_EVENTFD 1
# endif
# endif
# if defined(__BIONIC__) || (defined(__GLIBC__) && (__GLIBC__ << 8) + __GLIBC_MINOR__ >= 0x209 && \
(!defined(__UCLIBC__) || ((__UCLIBC_MAJOR__ << 16) + (__UCLIBC_MINOR__ << 8) + __UCLIBC_SUBLEVEL__ > 0x90201)))
# define HAVE_PIPE2 1
# endif
#endif
#if _POSIX_VERSION-0 >= 200809L || _XOPEN_VERSION-0 >= 500
# define HAVE_WAITID 1
#endif
#if !defined(WEXITED) || !defined(WNOWAIT)
# undef HAVE_WAITID
#endif
#if (defined(__FreeBSD__) && defined(__FreeBSD_version) && __FreeBSD_version >= 1000032) || \
(defined(__OpenBSD__) && OpenBSD >= 201505) || \
(defined(__NetBSD__) && __NetBSD_Version__ >= 600000000)
# define HAVE_PIPE2 1
#endif
#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__) || \
defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
# define HAVE_WAIT4 1
#endif
#if defined(__APPLE__)
/* Up until OS X 10.7, waitid(P_ALL, ...) will return success, but will not
* fill in the details of the dead child. That means waitid is not useful to us.
* Therefore, we only enable waitid() support if we're targetting OS X 10.8 or
* later.
*/
# include <Availability.h>
# include <AvailabilityMacros.h>
# if MAC_OS_X_VERSION_MIN_REQUIRED <= 1070
# define HAVE_BROKEN_WAITID 1
# endif
#endif
#include "forkfd_atomic.h"
static int system_has_forkfd(void);
static int system_forkfd(int flags, pid_t *ppid, int *system);
static int system_forkfd_wait(int ffd, struct forkfd_info *info, struct rusage *rusage);
#define CHILDREN_IN_SMALL_ARRAY 16
#define CHILDREN_IN_BIG_ARRAY 256
#define sizeofarray(array) (sizeof(array)/sizeof(array[0]))
#define EINTR_LOOP(ret, call) \
do { \
ret = call; \
} while (ret == -1 && errno == EINTR)
struct pipe_payload
{
struct forkfd_info info;
struct rusage rusage;
};
typedef struct process_info
{
ffd_atomic_int pid;
int deathPipe;
} ProcessInfo;
struct BigArray;
typedef struct Header
{
ffd_atomic_pointer(struct BigArray) nextArray;
ffd_atomic_int busyCount;
} Header;
typedef struct BigArray
{
Header header;
ProcessInfo entries[CHILDREN_IN_BIG_ARRAY];
} BigArray;
typedef struct SmallArray
{
Header header;
ProcessInfo entries[CHILDREN_IN_SMALL_ARRAY];
} SmallArray;
static SmallArray children;
static struct sigaction old_sigaction;
static pthread_once_t forkfd_initialization = PTHREAD_ONCE_INIT;
static ffd_atomic_int forkfd_status = FFD_ATOMIC_INIT(0);
#ifdef HAVE_BROKEN_WAITID
static int waitid_works = 0;
#else
static const int waitid_works = 1;
#endif
static ProcessInfo *tryAllocateInSection(Header *header, ProcessInfo entries[], int maxCount)
{
/* we use ACQUIRE here because the signal handler might have released the PID */
int busyCount = ffd_atomic_add_fetch(&header->busyCount, 1, FFD_ATOMIC_ACQUIRE);
if (busyCount <= maxCount) {
/* there's an available entry in this section, find it and take it */
int i;
for (i = 0; i < maxCount; ++i) {
/* if the PID is 0, it's free; mark it as used by swapping it with -1 */
int expected_pid = 0;
if (ffd_atomic_compare_exchange(&entries[i].pid, &expected_pid,
-1, FFD_ATOMIC_RELAXED, FFD_ATOMIC_RELAXED))
return &entries[i];
}
}
/* there isn't an available entry, undo our increment */
(void)ffd_atomic_add_fetch(&header->busyCount, -1, FFD_ATOMIC_RELAXED);
return NULL;
}
static ProcessInfo *allocateInfo(Header **header)
{
Header *currentHeader = &children.header;
/* try to find an available entry in the small array first */
ProcessInfo *info =
tryAllocateInSection(currentHeader, children.entries, sizeofarray(children.entries));
/* go on to the next arrays */
while (info == NULL) {
BigArray *array = ffd_atomic_load(&currentHeader->nextArray, FFD_ATOMIC_ACQUIRE);
if (array == NULL) {
/* allocate an array and try to use it */
BigArray *allocatedArray = (BigArray *)calloc(1, sizeof(BigArray));
if (allocatedArray == NULL)
return NULL;
if (ffd_atomic_compare_exchange(&currentHeader->nextArray, &array, allocatedArray,
FFD_ATOMIC_RELEASE, FFD_ATOMIC_ACQUIRE)) {
/* success */
array = allocatedArray;
} else {
/* failed, the atomic updated 'array' */
free(allocatedArray);
}
}
currentHeader = &array->header;
info = tryAllocateInSection(currentHeader, array->entries, sizeofarray(array->entries));
}
*header = currentHeader;
return info;
}
#ifdef HAVE_WAITID
static int isChildReady(pid_t pid, siginfo_t *info)
{
info->si_pid = 0;
return waitid(P_PID, pid, info, WEXITED | WNOHANG | WNOWAIT) == 0 && info->si_pid == pid;
}
#endif
static void convertStatusToForkfdInfo(int status, struct forkfd_info *info)
{
if (WIFEXITED(status)) {
info->code = CLD_EXITED;
info->status = WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
info->code = CLD_KILLED;
# ifdef WCOREDUMP
if (WCOREDUMP(status))
info->code = CLD_DUMPED;
# endif
info->status = WTERMSIG(status);
}
}
static int tryReaping(pid_t pid, struct pipe_payload *payload)
{
/* reap the child */
#if defined(HAVE_WAIT4)
int status;
if (wait4(pid, &status, WNOHANG, &payload->rusage) <= 0)
return 0;
convertStatusToForkfdInfo(status, &payload->info);
#else
# if defined(HAVE_WAITID)
if (waitid_works) {
/* we have waitid(2), which gets us some payload values on some systems */
siginfo_t info;
info.si_pid = 0;
int ret = waitid(P_PID, pid, &info, WEXITED | WNOHANG) == 0 && info.si_pid == pid;
if (!ret)
return ret;
payload->info.code = info.si_code;
payload->info.status = info.si_status;
# ifdef __linux__
payload->rusage.ru_utime.tv_sec = info.si_utime / CLOCKS_PER_SEC;
payload->rusage.ru_utime.tv_usec = info.si_utime % CLOCKS_PER_SEC;
payload->rusage.ru_stime.tv_sec = info.si_stime / CLOCKS_PER_SEC;
payload->rusage.ru_stime.tv_usec = info.si_stime % CLOCKS_PER_SEC;
# endif
return 1;
}
# endif // HAVE_WAITID
int status;
if (waitpid(pid, &status, WNOHANG) <= 0)
return 0; // child did not change state
convertStatusToForkfdInfo(status, &payload->info);
#endif // !HAVE_WAIT4
return 1;
}
static void freeInfo(Header *header, ProcessInfo *entry)
{
entry->deathPipe = -1;
ffd_atomic_store(&entry->pid, 0, FFD_ATOMIC_RELEASE);
(void)ffd_atomic_add_fetch(&header->busyCount, -1, FFD_ATOMIC_RELEASE);
assert(header->busyCount >= 0);
}
static void notifyAndFreeInfo(Header *header, ProcessInfo *entry,
const struct pipe_payload *payload)
{
ssize_t ret;
EINTR_LOOP(ret, write(entry->deathPipe, payload, sizeof(*payload)));
EINTR_LOOP(ret, close(entry->deathPipe));
freeInfo(header, entry);
}
static void reapChildProcesses();
static void sigchld_handler(int signum, siginfo_t *handler_info, void *handler_context)
{
/*
* This is a signal handler, so we need to be careful about which functions
* we can call. See the full, official listing in the POSIX.1-2008
* specification at:
* http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03
*
* The handler_info and handler_context parameters may not be valid, if
* we're a chained handler from another handler that did not use
* SA_SIGINFO. Therefore, we must obtain the siginfo ourselves directly by
* calling waitid.
*
* But we pass them anyway. Let's call the chained handler first, while
* those two arguments have a chance of being correct.
*/
if (old_sigaction.sa_handler != SIG_IGN && old_sigaction.sa_handler != SIG_DFL) {
if (old_sigaction.sa_flags & SA_SIGINFO)
old_sigaction.sa_sigaction(signum, handler_info, handler_context);
else
old_sigaction.sa_handler(signum);
}
if (ffd_atomic_load(&forkfd_status, FFD_ATOMIC_RELAXED) == 1) {
int saved_errno = errno;
reapChildProcesses();
errno = saved_errno;
}
}
static inline void reapChildProcesses()
{
/* is this one of our children? */
BigArray *array;
siginfo_t info;
struct pipe_payload payload;
int i;
memset(&info, 0, sizeof info);
memset(&payload, 0, sizeof payload);
#ifdef HAVE_WAITID
if (waitid_works) {
/* be optimistic: try to see if we can get the child that exited */
search_next_child:
/* waitid returns -1 ECHILD if there are no further children at all;
* it returns 0 and sets si_pid to 0 if there are children but they are not ready
* to be waited (we're passing WNOHANG). We should not get EINTR because
* we're passing WNOHANG and we should definitely not get EINVAL or anything else.
* That means we can actually ignore the return code and only inspect si_pid.
*/
info.si_pid = 0;
waitid(P_ALL, 0, &info, WNOHANG | WNOWAIT | WEXITED);
if (info.si_pid == 0) {
/* there are no further un-waited-for children, so we can just exit.
*/
return;
}
for (i = 0; i < (int)sizeofarray(children.entries); ++i) {
/* acquire the child first: swap the PID with -1 to indicate it's busy */
int pid = info.si_pid;
if (ffd_atomic_compare_exchange(&children.entries[i].pid, &pid, -1,
FFD_ATOMIC_ACQUIRE, FFD_ATOMIC_RELAXED)) {
/* this is our child, send notification and free up this entry */
/* ### FIXME: what if tryReaping returns false? */
if (tryReaping(pid, &payload))
notifyAndFreeInfo(&children.header, &children.entries[i], &payload);
goto search_next_child;
}
}
/* try the arrays */
array = ffd_atomic_load(&children.header.nextArray, FFD_ATOMIC_ACQUIRE);
while (array != NULL) {
for (i = 0; i < (int)sizeofarray(array->entries); ++i) {
int pid = info.si_pid;
if (ffd_atomic_compare_exchange(&array->entries[i].pid, &pid, -1,
FFD_ATOMIC_ACQUIRE, FFD_ATOMIC_RELAXED)) {
/* this is our child, send notification and free up this entry */
/* ### FIXME: what if tryReaping returns false? */
if (tryReaping(pid, &payload))
notifyAndFreeInfo(&array->header, &array->entries[i], &payload);
goto search_next_child;
}
}
array = ffd_atomic_load(&array->header.nextArray, FFD_ATOMIC_ACQUIRE);
}
/* if we got here, we couldn't find this child in our list. That means this child
* belongs to one of the chained SIGCHLD handlers. However, there might be another
* child that exited and does belong to us, so we need to check each one individually.
*/
}
#endif
for (i = 0; i < (int)sizeofarray(children.entries); ++i) {
int pid = ffd_atomic_load(&children.entries[i].pid, FFD_ATOMIC_ACQUIRE);
if (pid <= 0)
continue;
#ifdef HAVE_WAITID
if (waitid_works) {
/* The child might have been reaped by the block above in another thread,
* so first check if it's ready and, if it is, lock it */
if (!isChildReady(pid, &info) ||
!ffd_atomic_compare_exchange(&children.entries[i].pid, &pid, -1,
FFD_ATOMIC_RELAXED, FFD_ATOMIC_RELAXED))
continue;
}
#endif
if (tryReaping(pid, &payload)) {
/* this is our child, send notification and free up this entry */
notifyAndFreeInfo(&children.header, &children.entries[i], &payload);
}
}
/* try the arrays */
array = ffd_atomic_load(&children.header.nextArray, FFD_ATOMIC_ACQUIRE);
while (array != NULL) {
for (i = 0; i < (int)sizeofarray(array->entries); ++i) {
int pid = ffd_atomic_load(&array->entries[i].pid, FFD_ATOMIC_ACQUIRE);
if (pid <= 0)
continue;
#ifdef HAVE_WAITID
if (waitid_works) {
/* The child might have been reaped by the block above in another thread,
* so first check if it's ready and, if it is, lock it */
if (!isChildReady(pid, &info) ||
!ffd_atomic_compare_exchange(&array->entries[i].pid, &pid, -1,
FFD_ATOMIC_RELAXED, FFD_ATOMIC_RELAXED))
continue;
}
#endif
if (tryReaping(pid, &payload)) {
/* this is our child, send notification and free up this entry */
notifyAndFreeInfo(&array->header, &array->entries[i], &payload);
}
}
array = ffd_atomic_load(&array->header.nextArray, FFD_ATOMIC_ACQUIRE);
}
}
static void ignore_sigpipe()
{
#ifdef O_NOSIGPIPE
static ffd_atomic_int done = FFD_ATOMIC_INIT(0);
if (ffd_atomic_load(&done, FFD_ATOMIC_RELAXED))
return;
#endif
struct sigaction action;
memset(&action, 0, sizeof action);
sigemptyset(&action.sa_mask);
action.sa_handler = SIG_IGN;
action.sa_flags = 0;
sigaction(SIGPIPE, &action, NULL);
#ifdef O_NOSIGPIPE
ffd_atomic_store(&done, 1, FFD_ATOMIC_RELAXED);
#endif
}
#if defined(__GNUC__) && (!defined(__FreeBSD__) || __FreeBSD__ < 10)
__attribute((destructor, unused)) static void cleanup();
#endif
static void cleanup()
{
BigArray *array;
/* This function is not thread-safe!
* It must only be called when the process is shutting down.
* At shutdown, we expect no one to be calling forkfd(), so we don't
* need to be thread-safe with what is done there.
*
* But SIGCHLD might be delivered to any thread, including this one.
* There's no way to prevent that. The correct solution would be to
* cooperatively delete. We don't do that.
*/
if (ffd_atomic_load(&forkfd_status, FFD_ATOMIC_RELAXED) == 0)
return;
/* notify the handler that we're no longer in operation */
ffd_atomic_store(&forkfd_status, 0, FFD_ATOMIC_RELAXED);
/* free any arrays we might have */
array = ffd_atomic_load(&children.header.nextArray, FFD_ATOMIC_ACQUIRE);
while (array != NULL) {
BigArray *next = ffd_atomic_load(&array->header.nextArray, FFD_ATOMIC_ACQUIRE);
free(array);
array = next;
}
}
static void forkfd_initialize()
{
#if defined(HAVE_BROKEN_WAITID)
pid_t pid = fork();
if (pid == 0) {
_exit(0);
} else if (pid > 0) {
siginfo_t info;
waitid(P_ALL, 0, &info, WNOWAIT | WEXITED);
waitid_works = (info.si_pid != 0);
info.si_pid = 0;
// now really reap the child
waitid(P_PID, pid, &info, WEXITED);
waitid_works = waitid_works && (info.si_pid != 0);
}
#endif
/* install our signal handler */
struct sigaction action;
memset(&action, 0, sizeof action);
sigemptyset(&action.sa_mask);
action.sa_flags = SA_NOCLDSTOP | SA_SIGINFO;
action.sa_sigaction = sigchld_handler;
/* ### RACE CONDITION
* The sigaction function does a memcpy from an internal buffer
* to old_sigaction, which we use in the SIGCHLD handler. If a
* SIGCHLD is delivered before or during that memcpy, the handler will
* see an inconsistent state.
*
* There is no solution. pthread_sigmask doesn't work here because the
* signal could be delivered to another thread.
*/
sigaction(SIGCHLD, &action, &old_sigaction);
#ifndef O_NOSIGPIPE
/* disable SIGPIPE too */
ignore_sigpipe();
#endif
#ifdef __GNUC__
(void) cleanup; /* suppress unused static function warning */
#else
atexit(cleanup);
#endif
ffd_atomic_store(&forkfd_status, 1, FFD_ATOMIC_RELAXED);
}
static int create_pipe(int filedes[], int flags)
{
int ret = -1;
#ifdef HAVE_PIPE2
/* use pipe2(2) whenever possible, since it can thread-safely create a
* cloexec pair of pipes. Without it, we have a race condition setting
* FD_CLOEXEC
*/
# ifdef O_NOSIGPIPE
/* try first with O_NOSIGPIPE */
ret = pipe2(filedes, O_CLOEXEC | O_NOSIGPIPE);
if (ret == -1) {
/* O_NOSIGPIPE not supported, ignore SIGPIPE */
ignore_sigpipe();
}
# endif
if (ret == -1)
ret = pipe2(filedes, O_CLOEXEC);
if (ret == -1)
return ret;
if ((flags & FFD_CLOEXEC) == 0)
fcntl(filedes[0], F_SETFD, 0);
#else
ret = pipe(filedes);
if (ret == -1)
return ret;
fcntl(filedes[1], F_SETFD, FD_CLOEXEC);
if (flags & FFD_CLOEXEC)
fcntl(filedes[0], F_SETFD, FD_CLOEXEC);
#endif
if (flags & FFD_NONBLOCK)
fcntl(filedes[0], F_SETFL, fcntl(filedes[0], F_GETFL) | O_NONBLOCK);
return ret;
}
#ifndef FORKFD_NO_FORKFD
/**
* @brief forkfd returns a file descriptor representing a child process
* @return a file descriptor, or -1 in case of failure
*
* forkfd() creates a file descriptor that can be used to be notified of when a
* child process exits. This file descriptor can be monitored using select(2),
* poll(2) or similar mechanisms.
*
* The @a flags parameter can contain the following values ORed to change the
* behaviour of forkfd():
*
* @li @c FFD_NONBLOCK Set the O_NONBLOCK file status flag on the new open file
* descriptor. Using this flag saves extra calls to fnctl(2) to achieve the same
* result.
*
* @li @c FFD_CLOEXEC Set the close-on-exec (FD_CLOEXEC) flag on the new file
* descriptor. You probably want to set this flag, since forkfd() does not work
* if the original parent process dies.
*
* The file descriptor returned by forkfd() supports the following operations:
*
* @li read(2) When the child process exits, then the buffer supplied to
* read(2) is used to return information about the status of the child in the
* form of one @c siginfo_t structure. The buffer must be at least
* sizeof(siginfo_t) bytes. The return value of read(2) is the total number of
* bytes read.
*
* @li poll(2), select(2) (and similar) The file descriptor is readable (the
* select(2) readfds argument; the poll(2) POLLIN flag) if the child has exited
* or signalled via SIGCHLD.
*
* @li close(2) When the file descriptor is no longer required it should be closed.
*/
int forkfd(int flags, pid_t *ppid)
{
Header *header;
ProcessInfo *info;
pid_t pid;
int fd = -1;
int death_pipe[2];
int sync_pipe[2];
int ret;
#ifdef __linux__
int efd;
#endif
fd = system_forkfd(flags, ppid, &ret);
if (ret)
return fd;
(void) pthread_once(&forkfd_initialization, forkfd_initialize);
info = allocateInfo(&header);
if (info == NULL) {
errno = ENOMEM;
return -1;
}
/* create the pipes before we fork */
if (create_pipe(death_pipe, flags) == -1)
goto err_free; /* failed to create the pipes, pass errno */
#ifdef HAVE_EVENTFD
/* try using an eventfd, which consumes less resources */
efd = eventfd(0, EFD_CLOEXEC);
if (efd == -1)
#endif
{
/* try a pipe */
if (create_pipe(sync_pipe, FFD_CLOEXEC) == -1) {
/* failed both at eventfd and pipe; fail and pass errno */
goto err_close;
}
}
/* now fork */
pid = fork();
if (pid == -1)
goto err_close2; /* failed to fork, pass errno */
if (ppid)
*ppid = pid;
/*
* We need to store the child's PID in the info structure, so
* the SIGCHLD handler knows that this child is present and it
* knows the writing end of the pipe to pass information on.
* However, the child process could exit before we stored the
* information (or the handler could run for other children exiting).
* We prevent that from happening by blocking the child process in
* a read(2) until we're finished storing the information.
*/
if (pid == 0) {
/* this is the child process */
/* first, wait for the all clear */
#ifdef HAVE_EVENTFD
if (efd != -1) {
eventfd_t val64;
EINTR_LOOP(ret, eventfd_read(efd, &val64));
EINTR_LOOP(ret, close(efd));
} else
#endif
{
char c;
EINTR_LOOP(ret, close(sync_pipe[1]));
EINTR_LOOP(ret, read(sync_pipe[0], &c, sizeof c));
EINTR_LOOP(ret, close(sync_pipe[0]));
}
/* now close the pipes and return to the caller */
EINTR_LOOP(ret, close(death_pipe[0]));
EINTR_LOOP(ret, close(death_pipe[1]));
fd = FFD_CHILD_PROCESS;
} else {
/* parent process */
info->deathPipe = death_pipe[1];
fd = death_pipe[0];
ffd_atomic_store(&info->pid, pid, FFD_ATOMIC_RELEASE);
/* release the child */
#ifdef HAVE_EVENTFD
if (efd != -1) {
eventfd_t val64 = 42;
EINTR_LOOP(ret, eventfd_write(efd, val64));
EINTR_LOOP(ret, close(efd));
} else
#endif
{
/*
* Usually, closing would be enough to make read(2) return and the child process
* continue. We need to write here: another thread could be calling forkfd at the
* same time, which means auxpipe[1] might be open in another child process.
*/
EINTR_LOOP(ret, close(sync_pipe[0]));
EINTR_LOOP(ret, write(sync_pipe[1], "", 1));
EINTR_LOOP(ret, close(sync_pipe[1]));
}
}
return fd;
err_close2:
#ifdef HAVE_EVENTFD
if (efd != -1) {
EINTR_LOOP(ret, close(efd));
} else
#endif
{
EINTR_LOOP(ret, close(sync_pipe[0]));
EINTR_LOOP(ret, close(sync_pipe[1]));
}
err_close:
EINTR_LOOP(ret, close(death_pipe[0]));
EINTR_LOOP(ret, close(death_pipe[1]));
err_free:
/* free the info pointer */
freeInfo(header, info);
return -1;
}
#endif // FORKFD_NO_FORKFD
#if _POSIX_SPAWN > 0 && !defined(FORKFD_NO_SPAWNFD)
int spawnfd(int flags, pid_t *ppid, const char *path, const posix_spawn_file_actions_t *file_actions,
posix_spawnattr_t *attrp, char *const argv[], char *const envp[])
{
Header *header;
ProcessInfo *info;
struct pipe_payload payload;
pid_t pid;
int death_pipe[2];
int ret = -1;
/* we can only do work if we have a way to start the child in stopped mode;
* otherwise, we have a major race condition. */
assert(!system_has_forkfd());
(void) pthread_once(&forkfd_initialization, forkfd_initialize);
info = allocateInfo(&header);
if (info == NULL) {
errno = ENOMEM;
goto out;
}
/* create the pipe before we spawn */
if (create_pipe(death_pipe, flags) == -1)
goto err_free; /* failed to create the pipes, pass errno */
/* start the process */
if (flags & FFD_SPAWN_SEARCH_PATH) {
/* use posix_spawnp */
if (posix_spawnp(&pid, path, file_actions, attrp, argv, envp) != 0)
goto err_close;
} else {
if (posix_spawn(&pid, path, file_actions, attrp, argv, envp) != 0)
goto err_close;
}
if (ppid)
*ppid = pid;
/* Store the child's PID in the info structure.
*/
info->deathPipe = death_pipe[1];
ffd_atomic_store(&info->pid, pid, FFD_ATOMIC_RELEASE);
/* check if the child has already exited */
if (tryReaping(pid, &payload))
notifyAndFreeInfo(header, info, &payload);
ret = death_pipe[0];
return ret;
err_close:
EINTR_LOOP(ret, close(death_pipe[0]));
EINTR_LOOP(ret, close(death_pipe[1]));
err_free:
/* free the info pointer */
freeInfo(header, info);
out:
return -1;
}
#endif // _POSIX_SPAWN && !FORKFD_NO_SPAWNFD
int forkfd_wait(int ffd, struct forkfd_info *info, struct rusage *rusage)
{
struct pipe_payload payload;
int ret;
if (system_has_forkfd())
return system_forkfd_wait(ffd, info, rusage);
ret = read(ffd, &payload, sizeof(payload));
if (ret == -1)
return ret; /* pass errno, probably EINTR, EBADF or EWOULDBLOCK */
assert(ret == sizeof(payload));
if (info)
*info = payload.info;
if (rusage)
*rusage = payload.rusage;
return 0; /* success */
}
int forkfd_close(int ffd)
{
return close(ffd);
}
#if defined(__FreeBSD__) && __FreeBSD__ >= 9
# include "forkfd_freebsd.c"
#else
int system_has_forkfd()
{
return 0;
}
int system_forkfd(int flags, pid_t *ppid, int *system)
{
(void)flags;
(void)ppid;
*system = 0;
return -1;
}
int system_forkfd_wait(int ffd, struct forkfd_info *info, struct rusage *rusage)
{
(void)ffd;
(void)info;
(void)rusage;
return -1;
}
#endif