racket/racket/src/rktio/rktio_process.c
Matthew Flatt 2b439fc554 rktio: most of switch to rktio fd
Doesn't work, yet...
2017-06-19 06:45:17 -06:00

1630 lines
41 KiB
C

#include "rktio.h"
#include "rktio_private.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#if defined(RKTIO_SYSTEM_UNIX)
# include <signal.h>
# include <sys/types.h>
# include <sys/wait.h>
# include <errno.h>
# include <unistd.h>
#endif
#if defined(RKTIO_SYSTEM_UNIX) && defined(RKTIO_USE_PTHREADS)
#define CENTRALIZED_SIGCHILD
#endif
/*========================================================================*/
/* Process data structure */
/*========================================================================*/
struct rktio_process_t {
void *handle;
int pid;
int is_group;
#ifdef CENTRALIZED_SIGCHILD
short done;
int status;
#endif
#ifdef RKTIO_SYSTEM_WINDOWS
int got_time;
#endif
};
/*========================================================================*/
/* Status helper */
/*========================================================================*/
#if defined(RKTIO_SYSTEM_UNIX)
static int extract_child_status(int status)
{
if (WIFEXITED(status))
status = WEXITSTATUS(status);
else if (WIFSIGNALED(status))
status = WTERMSIG(status) + 128;
else
status = -1;
return status;
}
#endif
/*========================================================================*/
/* SIGCHLD management for a multi-threaded environment */
/*========================================================================*/
/* If SIGCHLD is unblocked, it gets delivered to a random thread
--- not necessarily on in the right place for the subprocess.
To avoid that problem, we centralize SIGCHLD handling here, and
then dispatch back out to specific places as they request
information. */
#ifdef CENTRALIZED_SIGCHILD
typedef struct Child_Status {
int pid;
int status;
char done;
char unneeded; /* not in a group; result not needed */
char is_group;
rktio_signal_handle_t *signal_fd;
struct Child_Status *next;
struct Child_Status *next_unused; /* see unused_pid_statuses */
} Child_Status;
static Child_Status *child_statuses = NULL;
static pthread_mutex_t child_status_lock;
static pthread_mutex_t child_wait_lock; /* ordered before status lock */
static int started_thread, pending_children;
/* When the process value for a process in a different group
is released before a waitpid() on the process, then we
need to keep waiting on the pid to let the OS gc the process.
This list is especially needed for processes that we create in
their own group, but it's also needed for processes that put
themselves in their own group (which we conservatively assume
can be any child process).
This list is protected by the wait lock. */
static Child_Status *unused_pid_statuses = NULL;
static void add_group_signal_fd(rktio_signal_handle_t *signal_fd);
static void remove_group_signal_fd(rktio_signal_handle_t *signal_fd);
static void do_group_signal_fds();
static int centralized_get_child_status(int pid, int is_group, int can_check_group, int *status);
static void add_child_status(int pid, int status)
{
Child_Status *st;
/* Search for existing record, which will have a signal_fd: */
pthread_mutex_lock(&child_status_lock);
for (st = child_statuses; st; st = st->next) {
if (st->pid == pid)
break;
}
if (!st) {
/* must have terminated before it was registered
(and since we detected it, it must not be a group) */
st = malloc(sizeof(Child_Status));
st->pid = pid;
st->signal_fd = NULL;
st->next = child_statuses;
child_statuses = st;
st->next_unused = NULL;
st->unneeded = 0;
st->is_group = 0;
}
st->status = status;
st->done = 1;
if (st->signal_fd && st->is_group)
remove_group_signal_fd(st->signal_fd);
pthread_mutex_unlock(&child_status_lock);
if (st->signal_fd)
rktio_signal_received_at(st->signal_fd);
if (st->unneeded)
(void)centralized_get_child_status(st->pid, 0, 0, NULL);
}
static int raw_get_child_status(int pid, int *status, int done_only, int do_remove, int do_free)
{
Child_Status *st;
Child_Status *prev;
int found = 0;
for (st = child_statuses, prev = NULL; st; prev = st, st = st->next) {
if (st->pid == pid) {
if (!done_only || st->done) {
if (status)
*status = st->status;
found = 1;
if (do_remove) {
if (prev)
prev->next = st->next;
else
child_statuses = st->next;
}
if (do_free)
free(st);
}
break;
}
}
return found;
}
int centralized_get_child_status(int pid, int is_group, int can_check_group, int *status)
{
int found = 0;
/* Check specific pid, in case the child has its own group
(either given by Racket or given to itself): */
if (can_check_group) {
pid_t pid2;
int status;
do {
pid2 = waitpid((pid_t)pid, &status, WNOHANG);
} while ((pid2 == -1) && (errno == EINTR));
if (pid2 > 0)
add_child_status(pid, extract_child_status(status));
}
pthread_mutex_lock(&child_status_lock);
found = raw_get_child_status(pid, status, 1, 1, 1);
pthread_mutex_unlock(&child_status_lock);
/* printf("centralized_get_child_status found %i pid %i status %i\n", found, pid, *status); */
return found;
}
static int centralized_register_child(int pid, int is_group, rktio_signal_handle_t *signal_fd, int *status)
{
int found = 0;
pthread_mutex_lock(&child_status_lock);
/* The child may have terminated already: */
found = raw_get_child_status(pid, status, 0, 0, 0);
if (!found) {
/* Create a record for the child: */
Child_Status *st;
st = malloc(sizeof(Child_Status));
st->pid = pid;
st->signal_fd = signal_fd;
st->status = 0;
st->unneeded = 0;
st->done = 0;
st->is_group = is_group;
st->next = child_statuses;
child_statuses = st;
st->next_unused = NULL;
if (is_group)
add_group_signal_fd(signal_fd);
}
pthread_mutex_unlock(&child_status_lock);
return found;
}
static void *thread_signal_worker(void *data)
{
int status;
int pid, check_pid, is_group;
sigset_t set;
Child_Status *unused_status, *prev_unused, *next;
sigemptyset(&set);
sigaddset(&set, SIGCHLD);
while (1) {
int rc;
int signalid;
do {
rc = sigwait(&set, &signalid);
if (rc == -1) {
if (errno != EINTR) {
fprintf(stderr, "unexpected error from sigwait(): %d\n", errno);
}
}
} while (rc == -1 && errno == EINTR);
pthread_mutex_lock(&child_status_lock);
do_group_signal_fds();
pthread_mutex_unlock(&child_status_lock);
pthread_mutex_lock(&child_wait_lock);
unused_status = unused_pid_statuses;
prev_unused = NULL;
do {
if (unused_status) {
/* See unused_pid_statuses above */
check_pid = unused_status->pid;
is_group = 1;
} else {
/* We wait only on processes in the same group as the current process,
because detecting the termination of a group's main process
disables our ability to terminate all processes in the group. */
if (pending_children)
check_pid = 0; /* => processes in the same group as the current process */
else
check_pid = -1; /* don't check */
is_group = 0;
}
if (check_pid == -1) {
pid = -1;
errno = ECHILD;
} else
pid = waitpid(check_pid, &status, WNOHANG);
if (pid == -1) {
if (errno == EINTR) {
/* try again */
pid = 1;
} else if (!is_group && (errno == ECHILD)) {
/* no more to check */
} else {
fprintf(stderr, "unexpected error from waitpid(%d[%d]): %d\n",
check_pid, is_group, errno);
if (is_group) {
prev_unused = unused_status;
unused_status = unused_status->next;
}
}
} else if (pid > 0) {
/* printf("SIGCHILD pid %i with status %i %i\n", pid, status, WEXITSTATUS(status)); */
if (is_group) {
next = unused_status->next_unused;
if (prev_unused)
prev_unused->next_unused = next;
else
unused_pid_statuses = next;
free(unused_status);
unused_status = next;
} else {
/* Double-check for pid in unused_pid_statuses, since
it may have completed between the pid-specific waitpid and the
non-group waitpid: */
prev_unused = NULL;
for (unused_status = unused_pid_statuses; unused_status; unused_status = unused_status->next_unused) {
if (unused_status->pid == pid)
break;
prev_unused = unused_status;
}
if (!unused_status) {
/* not in unused_pid_statuses: */
add_child_status(pid, extract_child_status(status));
} else {
if (prev_unused)
prev_unused->next_unused = unused_status->next_unused;
else
unused_pid_statuses = unused_status->next_unused;
free(unused_status);
unused_status = NULL;
}
}
} else {
if (is_group) {
prev_unused = unused_status;
unused_status = unused_status->next_unused;
}
}
} while ((pid > 0) || is_group);
pthread_mutex_unlock(&child_wait_lock);
}
return NULL;
}
void centralized_done_with_process_id(int pid, int is_group)
{
Child_Status *st;
int keep_unused = 1; /* assume that any process can be in a new group */
pthread_mutex_lock(&child_wait_lock); /* protects unused_pid_statuses */
pthread_mutex_lock(&child_status_lock);
for (st = child_statuses; st; st = st->next) {
if (st->pid == pid) {
if (!st->done) {
if (keep_unused) {
st->next_unused = unused_pid_statuses;
unused_pid_statuses = st;
if (st->signal_fd)
remove_group_signal_fd(st->signal_fd);
} else
st->unneeded = 1;
st->signal_fd = NULL;
}
break;
}
}
if (st && (keep_unused || st->done)) {
/* remove it from normal list: */
raw_get_child_status(pid, NULL, 0, 1, st->done);
}
pthread_mutex_unlock(&child_status_lock);
pthread_mutex_unlock(&child_wait_lock);
}
static void got_sigchld()
{
/* handle doesn't need to to anything, since sigwait()
in a thread does the work. */
}
void centralized_block_child_signal()
{
/* SIGCHLD is always blocked, since it's managed via sigwait() */
}
void centralized_unblock_child_signal()
{
}
void centralized_start_child_signal_handler()
{
pthread_mutex_init(&child_status_lock, NULL);
pthread_mutex_init(&child_wait_lock, NULL);
}
void centralized_wait_suspend()
{
pthread_mutex_lock(&child_wait_lock);
}
void centralized_wait_resume()
{
pthread_mutex_unlock(&child_wait_lock);
}
void centralized_starting_child()
{
pthread_mutex_lock(&child_wait_lock);
if (!started_thread) {
sigset_t set;
pthread_t signal_thread;
/* Mac OS X seems to need a handler installed for SIGCHLD to be
delivered, since the default is to drop the signal. Also, this
handler serves as a back-up alert if some thread is created that
does not block SIGCHLD.
Solaris, meanwhile, seems to unmask SIGCHLD as a result of
setting a handler, so do this before masking the signal. */
signal(SIGCHLD, got_sigchld);
/* Block SIGCLHD, because the worker thread will use sigwait() */
sigemptyset(&set);
sigaddset(&set, SIGCHLD);
sigprocmask(SIG_BLOCK, &set, NULL);
(void)pthread_create(&signal_thread, NULL, thread_signal_worker, NULL);
(void)pthread_detach(signal_thread);
started_thread = 1;
}
pending_children++;
pthread_mutex_unlock(&child_wait_lock);
}
void centralized_ended_child()
{
pthread_mutex_lock(&child_wait_lock);
--pending_children;
pthread_mutex_unlock(&child_wait_lock);
}
/* ---------------------------------------------------------------------- */
/* When a place has a process-group that it may be waiting on, the we
need to wake up the place whenever any SIGCHLD is received, since
the SIGDCHLD may apply to one of those places.
The list of signal_fds is protected by the status lock. */
typedef struct Group_Signal_FD {
rktio_signal_handle_t *signal_fd;
int refcount;
} Group_Signal_FD;
static Group_Signal_FD *signal_fds;
static int signal_fd_count;
static void add_group_signal_fd(rktio_signal_handle_t *signal_fd)
{
int i, count = 0;
Group_Signal_FD *a;
for (i = 0; i < signal_fd_count; i++) {
if (signal_fds[i].refcount) {
count++;
if (signal_fds[i].signal_fd == signal_fd) {
signal_fds[i].refcount++;
return;
}
}
}
if (count == signal_fd_count) {
signal_fd_count = (signal_fd_count + 4) * 2;
a = malloc(sizeof(Group_Signal_FD) * signal_fd_count);
memset(a, 0, sizeof(Group_Signal_FD) * signal_fd_count);
memcpy(a, signal_fds, sizeof(Group_Signal_FD) * count);
if (signal_fds) free(signal_fds);
signal_fds = a;
}
for (i = 0; i < signal_fd_count; i++) {
if (!signal_fds[i].refcount) {
signal_fds[i].signal_fd = signal_fd;
signal_fds[i].refcount = 1;
break;
}
}
}
static void remove_group_signal_fd(rktio_signal_handle_t *signal_fd)
{
int i;
for (i = 0; i < signal_fd_count; i++) {
if (signal_fds[i].refcount) {
if (signal_fds[i].signal_fd == signal_fd) {
--signal_fds[i].refcount;
return;
}
}
}
}
static void do_group_signal_fds()
{
int i;
for (i = 0; i < signal_fd_count; i++) {
if (signal_fds[i].refcount) {
rktio_signal_received_at(signal_fds[i].signal_fd);
}
}
}
#endif
/*========================================================================*/
/* Pipes for stdout, stdin, and stderr */
/*========================================================================*/
/* Unix, and Windows support --- all mixed together */
#ifdef RKTIO_SYSTEM_WINDOWS
# ifndef USE_CYGWIN_PIPES
# define _EXTRA_PIPE_ARGS , rktio
static int MyPipe(intptr_t *ph, int near_index, rktio_t *rktio)
{
HANDLE r, w;
SECURITY_ATTRIBUTES saAttr;
/* Set the bInheritHandle flag so pipe handles are inherited. */
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
if (CreatePipe(&r, &w, &saAttr, 0)) {
HANDLE a[2], naya;
a[0] = r;
a[1] = w;
if (near_index != -1) {
/* Change the near end to make it non-inheritable, then
close the inheritable one: */
if (!DuplicateHandle(GetCurrentProcess(), a[near_index],
GetCurrentProcess(), &naya, 0,
0, /* not inherited */
DUPLICATE_SAME_ACCESS)) {
get_windows_error();
CloseHandle(a[0]);
CloseHandle(a[1]);
return 1;
} else {
CloseHandle(a[near_index]);
a[near_index] = naya;
}
}
ph[0] = (intptr_t)a[0];
ph[1] = (intptr_t)a[1];
return 0;
} else
return 1;
}
# define PIPE_FUNC MyPipe
# define PIPE_HANDLE_t intptr_t
# define GET_PIPE_ERROR() get_windows_error()
# else
# include <Process.h>
# include <fcntl.h>
# define PIPE_FUNC(pa, nearh) MSC_IZE(pipe)(pa)
# define PIPE_HANDLE_t int
# define _EXTRA_PIPE_ARGS , 256, _O_BINARY
# define GET_PIPE_ERROR() /* nothing */
# endif
#else
# define _EXTRA_PIPE_ARGS
# define PIPE_FUNC(pa, nearh) MSC_IZE(pipe)(pa)
# define PIPE_HANDLE_t int
# define GET_PIPE_ERROR() get_posix_error()
#endif
static int make_os_pipe(rktio_t *rktio, intptr_t *a, int nearh)
/* If nearh != -1, then the handle at the index
other than nearh is made inheritable so that
a subprocess can use it. */
{
PIPE_HANDLE_t la[2];
if (PIPE_FUNC(la, nearh _EXTRA_PIPE_ARGS)) {
GET_PIPE_ERROR();
return 1;
}
a[0] = la[0];
a[1] = la[1];
return 0;
}
/*========================================================================*/
/* Unix signal handling (without pthreads) */
/*========================================================================*/
#if defined(RKTIO_SYSTEM_UNIX) && !defined(CENTRALIZED_SIGCHILD)
typedef struct System_Child {
pid_t id;
short done;
int status;
struct System_Child *next;
} System_Child;
static void block_child_signals(rktio_t*rktio, int block)
{
sigset_t sigs;
sigemptyset(&sigs);
sigaddset(&sigs, SIGCHLD);
sigprocmask(block ? SIG_BLOCK : SIG_UNBLOCK, &sigs, NULL);
}
/* See `unused_pid_statuses' for
a reminder of why this is needed (in both
implementations): */
static void *unused_pids;
/* We get only on signal handler for all rktio instances,
so we have to chain them: */
static rktio_t *all_rktios;
static int sigchld_installed = 0;
static void child_done(int ingored)
{
rktio_t *rktio = all_rktios;
while (rktio) {
rktio->need_to_check_children = 1;
rktio_signal_received(rktio);
rktio = rktio->next;
}
# ifdef SIGNAL_NEEDS_REINSTALL
signal(SIGCHLD, child_done);
# endif
}
static void init_sigchld(rktio_t *rktio)
{
#if !defined(CENTRALIZED_SIGCHILD)
if (!sigchld_installed) {
signal(SIGCHLD, child_done);
sigchld_installed = 1;
}
if (!rktio->in_sigchld_chain) {
rktio->in_sigchld_chain = 1;
rktio->next = all_rktios;
all_rktios = rktio;
}
#endif
}
#if !defined(CENTRALIZED_SIGCHILD)
static void remove_from_sigchld_chain(rktio_t *rktio)
{
if (rktio->in_sigchld_chain) {
rktio_t *rio = all_rktios, *prev = NULL;
while (rio) {
if (rio == rktio) {
if (prev)
prev->next = rktio->next;
else
all_rktios = rktio->next;
return;
}
prev = rio;
rio = rio->next;
}
}
}
#endif
static void check_child_done(rktio_t *rktio, pid_t pid)
{
pid_t result, check_pid;
int status, is_unused;
System_Child *sc, *prev;
void **unused = (void **)unused_pids, **unused_prev = NULL;
if (pid && rktio->need_to_check_children) {
rktio->need_to_check_children = 0;
check_child_done(rktio, 0);
}
if (rktio->system_children) {
do {
if (!pid && unused) {
check_pid = (pid_t)(intptr_t)unused[0];
is_unused = 1;
} else {
check_pid = pid;
is_unused = 0;
}
do {
result = waitpid(check_pid, &status, WNOHANG);
} while ((result == -1) && (errno == EINTR));
if (result > 0) {
if (is_unused) {
/* done with an inaccessible group id */
void *next;
next = (void **)unused[1];
if (unused_prev)
unused_prev[1] = unused[1];
else
unused_pids = unused[1];
free(unused);
unused = (void **)next;
}
status = extract_child_status(status);
prev = NULL;
for (sc = rktio->system_children; sc; prev = sc, sc = sc->next) {
if (sc->id == result) {
sc->done = 1;
sc->status = status;
if (prev) {
prev->next = sc->next;
} else
rktio->system_children = sc->next;
}
}
} else {
if (is_unused) {
unused_prev = unused;
unused = unused[1];
}
}
} while ((result > 0) || is_unused);
}
}
#endif
/*========================================================================*/
/* Windows process times */
/*========================================================================*/
/* Unix provides a counter for time consumed by subprocesses, but
Windows doesn't. */
#if defined(RKTIO_SYSTEM_WINDOWS)
static void collect_process_time(rktio_t *rktio, DWORD w, rktio_process_t *sp)
{
if ((w != STILL_ACTIVE) && !sp->got_time) {
FILETIME cr, ex, kr, us;
if (GetProcessTimes(sp->handle, &cr, &ex, &kr, &us)) {
rktio_int64_t v;
uintptr_t msecs;
v = ((((rktio_int64_t)kr.dwHighDateTime << 32) + kr.dwLowDateTime)
+ (((rktio_int64_t)us.dwHighDateTime << 32) + us.dwLowDateTime));
msecs = (uintptr_t)(v / 10000);
rktio->process_children_msecs += msecs;
}
sp->got_time = 1;
}
}
#endif
/*========================================================================*/
/* Process status functions */
/*========================================================================*/
int rktio_poll_process_done(rktio_t *rktio, rktio_process_t *sp)
{
#if defined(RKTIO_SYSTEM_UNIX)
# if defined(CENTRALIZED_SIGCHILD)
{
int status;
if (!sp->done) {
if (centralized_get_child_status(sp->pid, sp->is_group, 1, &status)) {
sp->done = 1;
sp->status = status;
centralized_ended_child();
return 1;
}
return 0;
}
else
return RKTIO_PROCESS_DONE;
}
# else
{
System_Child *sc;
sc = (System_Child *)sp->handle;
/* Check specific pid, in case the child has its own group
(either given by us or given to itself): */
check_child_done(rktio, sp->pid);
return sc->done;
}
# endif
#endif
#ifdef RKTIO_SYSTEM_WINDOWS
{
HANDLE sci = (HANDLE)sp->handle;
DWORD w;
if (sci) {
if (GetExitCodeProcess(sci, &w)) {
collect_process_time(rktio, w, sp);
return (w != STILL_ACTIVE);
} else
return RKTIO_PROCESS_DONE;
} else
return RKTIO_PROCESS_DONE;
get_windows_error();
return RKTIO_PROCESS_ERROR;
}
#endif
}
void rktio_poll_add_process(rktio_t *rktio, rktio_process_t *sp, rktio_poll_set_t *fds)
{
if (rktio_poll_process_done(rktio, sp)) {
rktio_poll_set_add_nosleep(rktio, fds);
return;
}
#ifdef RKTIO_SYSTEM_WINDOWS
rktio_poll_set_add_handle(rktio, (intptr_t)sp->handle, fds, 0);
#endif
}
rktio_status_t *rktio_process_status(rktio_t *rktio, rktio_process_t *sp)
{
int going = 0, status = 0;
rktio_status_t *result;
#if defined(RKTIO_SYSTEM_UNIX)
# if defined(CENTRALIZED_SIGCHILD)
if (sp->done) {
status = sp->status;
} else {
if (!centralized_get_child_status(sp->pid, sp->is_group, 1, &status)) {
going = 1;
} else {
sp->done = 1;
sp->status = status;
centralized_ended_child();
}
}
# else
System_Child *sc = (System_Child *)sp->handle;
check_child_done(rktio, sp->pid);
if (sc->done) {
status = sc->status;
} else
going = 1;
# endif
#else
# ifdef RKTIO_SYSTEM_WINDOWS
DWORD w;
if (sp->handle) {
if (GetExitCodeProcess((HANDLE)sp->handle, &w)) {
collect_process_time(rktio, w, sp);
if (w == STILL_ACTIVE)
going = 1;
else
status = w;
} else {
get_windows_error();
return NULL;
}
}
# endif
#endif
result = malloc(sizeof(rktio_status_t));
result->running = going;
result->result = (going ? 0 : status);
return result;
}
static int do_subprocess_kill(rktio_t *rktio, rktio_process_t *sp, int as_kill)
{
#if defined(RKTIO_SYSTEM_UNIX)
# if defined(CENTRALIZED_SIGCHILD)
{
int status;
if (sp->done)
return 1;
centralized_wait_suspend();
/* Don't allow group checking, because we don't want to wait
on a group if we haven't already: */
if (centralized_get_child_status(sp->pid, 0, 0, &status)) {
sp->status = status;
sp->done = 1;
centralized_wait_resume();
centralized_ended_child();
return 1;
}
}
# else
{
System_Child *sc = (System_Child *)sp->handle;
/* Don't pass sp->pid, because we don't want to wait
on a group if we haven't already: */
check_child_done(rktio, 0);
if (sc->done)
return 1;
}
# define centralized_wait_resume() /* empty */
# endif
while (1) {
if (sp->is_group) {
if (!killpg(sp->pid, as_kill ? SIGKILL : SIGINT)) {
centralized_wait_resume();
return 1;
}
} else {
if (!kill(sp->pid, as_kill ? SIGKILL : SIGINT)) {
centralized_wait_resume();
return 1;
}
}
if (errno != EINTR)
break;
/* Otherwise we were interrupted. Try `kill' again. */
}
get_posix_error();
centralized_wait_resume();
return 0;
#endif
#if defined(RKTIO_SYSTEM_WINDOWS)
if (as_kill || sp->is_group) {
DWORD w;
if (!sp->handle)
return 1;
if (!as_kill) {
/* must be for a group; we don't care whether the
original process is still running */
if (GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, sp->pid))
return 1;
} else if (GetExitCodeProcess((HANDLE)sp->handle, &w)) {
collect_process_time(rktio, w, sp);
if (w != STILL_ACTIVE)
return 1;
if (TerminateProcess((HANDLE)sp->handle, 1))
return 1;
}
get_windows_error();
return 0;
} else
return 1;
#endif
}
int rktio_process_kill(rktio_t *rktio, rktio_process_t *sp)
{
return do_subprocess_kill(rktio, sp, 1);
}
int rktio_process_interrupt(rktio_t *rktio, rktio_process_t *sp)
{
return do_subprocess_kill(rktio, sp, 0);
}
void rktio_process_forget(rktio_t *rktio, rktio_process_t *sp)
{
#ifdef RKTIO_SYSTEM_UNIX
# if defined(CENTRALIZED_SIGCHILD)
if (!sp->done) {
centralized_done_with_process_id(sp->pid, sp->is_group);
centralized_ended_child();
}
# else
if (!((System_Child *)sp->handle)->done) {
void **unused_pid;
unused_pid = malloc(sizeof(void *) * 2);
unused_pid[0] = (void *)(intptr_t)sp->pid;
unused_pid[1] = unused_pids;
rktio->need_to_check_children = 1;
}
free(sp->handle);
# endif
#endif
#ifdef RKTIO_SYSTEM_WINDOWS
CloseHandle(sp->handle);
#endif
free(sp);
}
int rktio_process_init(rktio_t *rktio)
{
#if defined(CENTRALIZED_SIGCHILD)
centralized_start_child_signal_handler();
#endif
return 1;
}
void rktio_process_deinit(rktio_t *rktio)
{
#ifdef RKTIO_SYSTEM_WINDOWS
if (rktio->process_job_object) {
TerminateJobObject(rktio->process_job_object, 1);
CloseHandle(rktio->process_job_object);
rktio->process_job_object = NULL;
}
#endif
#if defined(RKTIO_SYSTEM_UNIX) && !defined(CENTRALIZED_SIGCHILD)
remove_from_sigchld_chain(rktio);
#endif
}
/*========================================================================*/
/* Windows command-line construction */
/*========================================================================*/
#ifdef RKTIO_SYSTEM_WINDOWS
static char *cmdline_protect(char *s)
{
char *naya;
int ds;
int has_space = 0, has_quote = 0, was_slash = 0;
if (!*s) return MSC_IZE(strdup)("\"\""); /* quote an empty argument */
for (ds = 0; s[ds]; ds++) {
if (isspace(s[ds]) || (s[ds] == '\'')) {
has_space = 1;
was_slash = 0;
} else if (s[ds] == '"') {
has_quote += 1 + (2 * was_slash);
was_slash = 0;
} else if (s[ds] == '\\') {
was_slash++;
} else
was_slash = 0;
}
if (has_space || has_quote) {
char *p;
int wrote_slash = 0;
naya = malloc(strlen(s) + 3 + 3*has_quote + was_slash);
naya[0] = '"';
for (p = naya + 1; *s; s++) {
if (*s == '"') {
while (wrote_slash--) {
*(p++) = '\\';
}
*(p++) = '"'; /* endquote */
*(p++) = '\\';
*(p++) = '"'; /* protected */
*(p++) = '"'; /* start quote again */
wrote_slash = 0;
} else if (*s == '\\') {
*(p++) = '\\';
wrote_slash++;
} else {
*(p++) = *s;
wrote_slash = 0;
}
}
while (wrote_slash--) {
*(p++) = '\\';
}
*(p++) = '"';
*p = 0;
return naya;
}
return MSC_IZE(strdup)(s);
}
static intptr_t do_spawnv(rktio_t *rktio,
const char *command, const char * const *argv,
int exact_cmdline, intptr_t sin, intptr_t sout, intptr_t serr, int *pid,
int new_process_group, int chain_termination_here_to_child,
void *env, const char *wd)
{
int i, l, len = 0, use_jo;
intptr_t cr_flag;
char *cmdline;
wchar_t *cmdline_w, *wd_w;
STARTUPINFOW startup;
PROCESS_INFORMATION info;
if (exact_cmdline) {
cmdline = (char *)argv[1];
} else {
for (i = 0; argv[i]; i++) {
len += strlen(argv[i]) + 1;
}
cmdline = malloc(len);
len = 0;
for (i = 0; argv[i]; i++) {
l = strlen(argv[i]);
memcpy(cmdline + len, argv[i], l);
cmdline[len + l] = ' ';
len += l + 1;
}
--len;
cmdline[len] = 0;
}
memset(&startup, 0, sizeof(startup));
startup.cb = sizeof(startup);
startup.dwFlags = STARTF_USESTDHANDLES;
startup.hStdInput = (HANDLE)sin;
startup.hStdOutput = (HANDLE)sout;
startup.hStdError = (HANDLE)serr;
/* If none of the stdio handles are consoles, specifically
create the subprocess without a console: */
if (!rktio_system_fd_is_terminal(rktio, (intptr_t)startup.hStdInput)
&& !rktio_system_fd_is_terminal(rktio, (intptr_t)startup.hStdOutput)
&& !rktio_system_fd_is_terminal(rktio, (intptr_t)startup.hStdError))
cr_flag = CREATE_NO_WINDOW;
else
cr_flag = 0;
if (new_process_group)
cr_flag |= CREATE_NEW_PROCESS_GROUP;
cr_flag |= CREATE_UNICODE_ENVIRONMENT;
use_jo = chain_termination_here_to_child;
if (use_jo) {
/* Use a job object to ensure that the new process will be terminated
if this process ends for any reason (including a crash) */
if (!rktio->process_job_object) {
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli;
rktio->process_job_object = CreateJobObject(NULL, NULL);
memset(&jeli, 0, sizeof(jeli));
jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
SetInformationJobObject(rktio->process_job_object,
JobObjectExtendedLimitInformation,
&jeli,
sizeof(jeli));
}
}
cmdline_w = WIDE_PATH_copy(cmdline);
if (!exact_cmdline)
free(cmdline);
wd_w = WIDE_PATH_copy(wd);
if (CreateProcessW(WIDE_PATH_temp(command), cmdline_w,
NULL, NULL, 1 /*inherit*/,
cr_flag, env, wd_w,
&startup, &info)) {
if (use_jo)
AssignProcessToJobObject(rktio->process_job_object, info.hProcess);
CloseHandle(info.hThread);
*pid = info.dwProcessId;
free(cmdline_w);
free(wd_w);
return (intptr_t)info.hProcess;
} else {
free(cmdline_w);
free(wd_w);
return -1;
}
}
static void CopyFileHandleForSubprocess(intptr_t *hs, int pos)
{
HANDLE h2;
int alt_pos = (pos ? 0 : 1);
if (DuplicateHandle(GetCurrentProcess(),
(HANDLE)hs[pos],
GetCurrentProcess(),
&h2,
0,
TRUE,
DUPLICATE_SAME_ACCESS)) {
hs[pos] = (intptr_t)h2;
hs[alt_pos] = 1;
} else {
hs[alt_pos] = 0;
}
}
static void CloseFileHandleForSubprocess(intptr_t *hs, int pos)
{
int alt_pos = (pos ? 0 : 1);
if (hs[alt_pos]) {
CloseHandle((HANDLE)hs[pos]);
}
}
#define RKTIO_COPY_FOR_SUBPROCESS(array, pos) CopyFileHandleForSubprocess(array, pos)
#define RKTIO_CLOSE_SUBPROCESS_COPY(array, pos) CloseFileHandleForSubprocess(array, pos)
#define RKTIO_CLOSE(fd) CloseHandle((HANDLE)fd)
#endif /* RKTIO_SYSTEM_WINDOWS */
#ifdef RKTIO_SYSTEM_UNIX
# define RKTIO_COPY_FOR_SUBPROCESS(array, pos) /* empty */
# define RKTIO_CLOSE_SUBPROCESS_COPY(array, pos) /* empty */
# define RKTIO_CLOSE(fd) rktio_reliably_close(fd)
#endif
int rktio_process_allowed_flags(rktio_t *rktio)
{
int flags = (RKTIO_PROCESS_NEW_GROUP
| RKTIO_PROCESS_STDOUT_AS_STDERR);
#ifdef RKTIO_SYSTEM_WINDOWS
flags |= (RKTIO_PROCESS_WINDOWS_EXACT_CMDLINE
| RKTIO_PROCESS_WINDOWS_CHAIN_TERMINATION)
#endif
}
/*========================================================================*/
/* Main process-creation function */
/*========================================================================*/
rktio_process_result_t *rktio_process(rktio_t *rktio,
const char *command, int argc, char **argv,
rktio_fd_t *stdout_fd, rktio_fd_t *stdin_fd, rktio_fd_t *stderr_fd,
const char *current_directory, rktio_envvars_t *envvars,
int flags,
void (*unix_child_process_callback)())
{
rktio_process_result_t *result;
intptr_t to_subprocess[2], from_subprocess[2], err_subprocess[2];
int pid;
#if defined(RKTIO_SYSTEM_UNIX)
# if !defined(CENTRALIZED_SIGCHILD)
System_Child *sc;
# endif
#else
void *sc = 0;
#endif
void *env;
rktio_process_t *subproc;
#if defined(RKTIO_SYSTEM_WINDOWS)
intptr_t spawn_status;
#endif
int new_process_group = (flags & RKTIO_PROCESS_NEW_GROUP);
int stderr_is_stdout = (flags & RKTIO_PROCESS_STDOUT_AS_STDERR);
#if defined(RKTIO_SYSTEM_WINDOWS)
int windows_exact_cmdline = (flags & RKTIO_PROCESS_WINDOWS_EXACT_CMDLINE);
int windows_chain_termination_to_child = (flags & RKTIO_PROCESS_WINDOWS_CHAIN_TERMINATION);
int i;
#endif
/* avoid compiler warnings: */
to_subprocess[0] = -1;
to_subprocess[1] = -1;
from_subprocess[0] = -1;
from_subprocess[1] = -1;
err_subprocess[0] = -1;
err_subprocess[1] = -1;
/*--------------------------------------*/
/* Create needed pipes */
/*--------------------------------------*/
if (stdout_fd) {
from_subprocess[1] = rktio_fd_system_fd(rktio, stdout_fd);
RKTIO_COPY_FOR_SUBPROCESS(from_subprocess, 1);
} else if (make_os_pipe(rktio, from_subprocess, 1)) {
return NULL;
}
if (stdin_fd) {
to_subprocess[0] = rktio_fd_system_fd(rktio, stdin_fd);
RKTIO_COPY_FOR_SUBPROCESS(to_subprocess, 0);
} else if (make_os_pipe(rktio, to_subprocess, 1)) {
if (stdout_fd) { RKTIO_CLOSE_SUBPROCESS_COPY(from_subprocess, 1); }
return NULL;
}
if (stderr_fd) {
err_subprocess[1] = rktio_fd_system_fd(rktio, stderr_fd);
RKTIO_COPY_FOR_SUBPROCESS(err_subprocess, 1);
} else if (stderr_is_stdout) {
err_subprocess[0] = from_subprocess[0];
err_subprocess[1] = from_subprocess[1];
} else if (make_os_pipe(rktio, err_subprocess, 1)) {
if (stdout_fd) { RKTIO_CLOSE_SUBPROCESS_COPY(from_subprocess, 1); }
if (stdin_fd) { RKTIO_CLOSE_SUBPROCESS_COPY(to_subprocess, 0); }
return NULL;
}
if (envvars)
env = rktio_envvars_to_block(rktio, envvars);
else
env = NULL;
#if defined(RKTIO_SYSTEM_WINDOWS)
/*--------------------------------------*/
/* Execute: Windows */
/*--------------------------------------*/
/* Windows: quasi-stdin is locked, and we'll say it doesn't matter */
fflush(stdin);
fflush(stdout);
fflush(stderr);
{
char **new_argv;
if (!windows_exact_cmdline) {
/* protect spaces, etc. in the arguments: */
new_argv = malloc(sizeof(char *) * argc);
for (i = 0; i < argc; i++) {
new_argv[i] = cmdline_protect(argv[i]);
}
argv = new_argv;
}
pid = 0;
spawn_status = do_spawnv(rktio,
command, (const char * const *)argv,
windows_exact_cmdline,
to_subprocess[0],
from_subprocess[1],
err_subprocess[1],
&pid,
new_process_group,
windows_chain_termination_to_child,
env, current_directory);
if (!windows_exact_cmdline) {
for (i = 0; i < argc; i++) {
free(argv[i]);
}
free(argv);
}
if (spawn_status != -1)
sc = (void *)spawn_status;
}
#else
/*--------------------------------------*/
/* Execute: Unix */
/*--------------------------------------*/
{
#if defined(CENTRALIZED_SIGCHILD)
centralized_starting_child();
#else
init_sigchld(rktio);
sc = malloc(sizeof(System_Child));
sc->id = 0;
sc->done = 0;
block_child_signals(rktio, 1);
#endif
#if defined(__QNX__)
pid = vfork();
#elif defined(SUBPROCESS_USE_FORK1)
pid = fork1();
#else
pid = fork();
#endif
if (pid > 0) {
/* This is the original process, which needs to manage the
newly created child process. */
if (new_process_group)
/* there's a race condition between this use and the exec(),
and there's a race condition between the other setpgid() in
the child processand sending signals from the parent
process; so, we set in both, and at least one will
succeed; we could perform better error checking, since
EACCES is the only expected error */
setpgid(pid, pid);
#if defined(CENTRALIZED_SIGCHILD)
{
rktio_signal_handle_t *signal_fd;
int status;
signal_fd = rktio_get_signal_handle(rktio);
centralized_register_child(pid, new_process_group, signal_fd, &status);
/* printf("SUBPROCESS %i\n", pid); */
}
#else
sc->next = rktio->system_children;
rktio->system_children = sc;
sc->id = pid;
#endif
} else if (!pid) {
/* This is the new child process */
if (unix_child_process_callback)
unix_child_process_callback();
if (new_process_group)
/* see also setpgid above */
setpgid(getpid(), getpid()); /* setpgid(0, 0) would work on some platforms */
} else {
get_posix_error();
}
#if !defined(CENTRALIZED_SIGCHILD)
block_child_signals(rktio, 0);
#else
if (!pid)
centralized_unblock_child_signal();
else if (pid == -1)
centralized_ended_child();
#endif
}
switch (pid)
{
case -1:
/* Close all created descriptors */
if (!stdin_fd) {
rktio_reliably_close(to_subprocess[0]);
rktio_reliably_close(to_subprocess[1]);
} else {
RKTIO_CLOSE_SUBPROCESS_COPY(to_subprocess, 0);
}
if (!stdout_fd) {
rktio_reliably_close(from_subprocess[0]);
rktio_reliably_close(from_subprocess[1]);
} else {
RKTIO_CLOSE_SUBPROCESS_COPY(from_subprocess, 1);
}
if (!stderr_fd) {
if (!stderr_is_stdout) {
rktio_reliably_close(err_subprocess[0]);
rktio_reliably_close(err_subprocess[1]);
}
} else {
RKTIO_CLOSE_SUBPROCESS_COPY(err_subprocess, 1);
}
return NULL;
case 0: /* child */
{
int errid;
/* Copy pipe descriptors to stdin and stdout */
do {
errid = MSC_IZE(dup2)(to_subprocess[0], 0);
} while (errid == -1 && errno == EINTR);
do {
errid = MSC_IZE(dup2)(from_subprocess[1], 1);
} while (errid == -1 && errno == EINTR);
do {
errid = MSC_IZE(dup2)(err_subprocess[1], 2);
} while (errid == -1 && errno == EINTR);
/* Close unwanted descriptors */
if (!stdin_fd) {
rktio_reliably_close(to_subprocess[0]);
rktio_reliably_close(to_subprocess[1]);
}
if (!stdout_fd) {
rktio_reliably_close(from_subprocess[0]);
rktio_reliably_close(from_subprocess[1]);
}
if (!stderr_fd) {
if (!stderr_is_stdout) {
rktio_reliably_close(err_subprocess[0]);
rktio_reliably_close(err_subprocess[1]);
}
}
rktio_close_fds_after_fork(0, 1, 2);
}
/* Set real CWD: */
if (!rktio_set_current_directory(rktio, current_directory)) {
fprintf(stderr, "racket: chdir failed to: %s\n", current_directory);
_exit(1);
}
/* Exec new process */
{
int err, i;
char **new_argv;
/* add a NULL terminator */
new_argv = malloc(sizeof(char *) * (argc + 1));
for (i = 0; i < argc; i++) {
new_argv[i] = argv[i];
}
new_argv[i] = NULL;
if (!env)
env = rktio_get_environ_array();
err = MSC_IZE(execve)(command, new_argv, (char **)env);
if (err)
err = errno;
if (envvars)
free(env);
/* If we get here it failed; give up */
fprintf(stderr, "exec failed (%s%serrno=%d)\n",
strerror(err), "; ",
err);
_exit(1);
}
default: /* parent */
break;
}
#endif
/*--------------------------------------*/
/* Close unneeded descriptors */
/*--------------------------------------*/
free(env);
if (!stdin_fd)
RKTIO_CLOSE(to_subprocess[0]);
else
RKTIO_CLOSE_SUBPROCESS_COPY(to_subprocess, 0);
if (!stdout_fd)
RKTIO_CLOSE(from_subprocess[1]);
else
RKTIO_CLOSE_SUBPROCESS_COPY(from_subprocess, 1);
if (!stderr_fd) {
if (!stderr_is_stdout)
RKTIO_CLOSE(err_subprocess[1]);
} else
RKTIO_CLOSE_SUBPROCESS_COPY(err_subprocess, 1);
/*--------------------------------------*/
/* Create new file-descriptor objects */
/*--------------------------------------*/
result = malloc(sizeof(rktio_process_result_t));
if (!stdout_fd)
result->stdout_fd = rktio_system_fd(rktio, from_subprocess[0], RKTIO_OPEN_READ);
else
result->stdout_fd = NULL;
if (!stdin_fd)
result->stdin_fd = rktio_system_fd(rktio, to_subprocess[1], RKTIO_OPEN_WRITE);
else
result->stdin_fd = NULL;
if (!stderr_fd && !stderr_is_stdout)
result->stderr_fd = rktio_system_fd(rktio, err_subprocess[0], RKTIO_OPEN_READ);
else
result->stderr_fd = NULL;
/*--------------------------------------*/
/* Return result info */
/*--------------------------------------*/
subproc = malloc(sizeof(rktio_process_t));
memset(subproc, 0, sizeof(rktio_process_t));
#if !defined(CENTRALIZED_SIGCHILD)
subproc->handle = (void *)sc;
#endif
subproc->pid = pid;
subproc->is_group = new_process_group;
result->process = subproc;
return result;
}
int rktio_process_pid(rktio_t *rktio, rktio_process_t *sp)
{
return sp->pid;
}
#ifdef RKTIO_SYSTEM_UNIX
void rktio_close_fds_after_fork(int skip1, int skip2, int skip3)
{
int i;
# ifdef USE_ULIMIT
i = ulimit(4, 0);
# elif defined(__ANDROID__)
i = sysconf(_SC_OPEN_MAX);
# else
i = getdtablesize();
# endif
while (i--) {
int cr;
if ((i != skip1) && (i != skip2) && (i != skip3)) {
do {
cr = close(i);
} while ((cr == -1) && (errno == EINTR));
}
}
}
#endif