From 9e68886b26a0c81821b55aaebbdafbd7861bc639 Mon Sep 17 00:00:00 2001 From: Matthew Flatt Date: Mon, 12 Jun 2017 09:01:39 -0600 Subject: [PATCH] rktio: processes --- racket/src/rktio/Makefile.in | 8 + racket/src/rktio/README.txt | 5 +- racket/src/rktio/demo.c | 56 +- racket/src/rktio/rktio.h | 55 +- racket/src/rktio/rktio_envvars.c | 12 + racket/src/rktio/rktio_filesystem.c | 6 +- racket/src/rktio/rktio_main.c | 8 +- racket/src/rktio/rktio_private.h | 19 + racket/src/rktio/rktio_process.c | 1597 +++++++++++++++++++++++++++ 9 files changed, 1751 insertions(+), 15 deletions(-) create mode 100644 racket/src/rktio/rktio_envvars.c create mode 100644 racket/src/rktio/rktio_process.c diff --git a/racket/src/rktio/Makefile.in b/racket/src/rktio/Makefile.in index e599b41b91..f6c2da162d 100644 --- a/racket/src/rktio/Makefile.in +++ b/racket/src/rktio/Makefile.in @@ -13,6 +13,8 @@ OBJS = rktio_filesystem.o \ rktio_poll_set.o \ rktio_ltps.o \ rktio_network.o \ + rktio_process.o \ + rktio_envvars.o \ rktio_error.o \ rktio_main.o @@ -42,6 +44,12 @@ rktio_ltps.o: $(srcdir)/rktio_ltps.c $(RKTIO_HEADERS) rktio_network.o: $(srcdir)/rktio_network.c $(RKTIO_HEADERS) $(CC) $(CFLAGS) -I$(srcdir) -I. -o rktio_network.o -c $(srcdir)/rktio_network.c +rktio_process.o: $(srcdir)/rktio_process.c $(RKTIO_HEADERS) + $(CC) $(CFLAGS) -I$(srcdir) -I. -o rktio_process.o -c $(srcdir)/rktio_process.c + +rktio_envvars.o: $(srcdir)/rktio_envvars.c $(RKTIO_HEADERS) + $(CC) $(CFLAGS) -I$(srcdir) -I. -o rktio_envvars.o -c $(srcdir)/rktio_envvars.c + rktio_error.o: $(srcdir)/rktio_error.c $(RKTIO_HEADERS) $(CC) $(CFLAGS) -I$(srcdir) -I. -o rktio_error.o -c $(srcdir)/rktio_error.c diff --git a/racket/src/rktio/README.txt b/racket/src/rktio/README.txt index 9432621fa7..4ac1fd197a 100644 --- a/racket/src/rktio/README.txt +++ b/racket/src/rktio/README.txt @@ -9,8 +9,9 @@ The library is meant to be * always non-blocking; * independent of global state (except on Windows, where internal - global state is managed appropriately with locks), so that it works - with or without threads; and + global state is managed appropriately with locks, and except for + Unix process handling without pthreads), so that it works with or + without threads; and * easily callable though a FFI. diff --git a/racket/src/rktio/demo.c b/racket/src/rktio/demo.c index 48dfc455ea..804635e139 100644 --- a/racket/src/rktio/demo.c +++ b/racket/src/rktio/demo.c @@ -5,6 +5,8 @@ static void do_check_valid(rktio_t *rktio, int ok, int where) { + /* Beware that a reported error is nonsense if the failure + was an unexpected result insteda of an error result. */ if (!ok) { printf("error at %d: %d@%d = %s\n", where, @@ -190,13 +192,12 @@ static void wait_read(rktio_t *rktio, rktio_fd_t *fd) rktio_poll_set_close(rktio, ps); } -static void check_read_write_pair(rktio_t *rktio, rktio_fd_t *fd, rktio_fd_t *fd2) +static void check_read_write_pair(rktio_t *rktio, rktio_fd_t *fd, rktio_fd_t *fd2, int immediate_available) { rktio_ltps_t *lt; rktio_ltps_handle_t *h1, *h2; intptr_t amt, i; char buffer[256]; - int immediate_available = (!rktio_fd_is_socket(rktio, fd) && !rktio_fd_is_socket(rktio, fd2)); lt = try_check_ltps(rktio, fd, fd2, &h1, &h2); /* We expect `lt` to work everywhere exception Windows and with kqueue on non-sockets: */ @@ -566,7 +567,7 @@ int main() check_valid(fd2); check_valid(!rktio_poll_read_ready(rktio, fd)); - check_read_write_pair(rktio, fd, fd2); + check_read_write_pair(rktio, fd, fd2, 1); /* Open pipe ends again: */ fd2 = rktio_open(rktio, "demo_fifo", RKTIO_OPEN_WRITE | RKTIO_OPEN_CAN_EXIST); @@ -637,7 +638,7 @@ int main() free(strs); } - check_read_write_pair(rktio, fd, fd2); + check_read_write_pair(rktio, fd, fd2, 0); fd = connect_loop(rktio, addr, NULL); rktio_free_addrinfo(rktio, addr); @@ -675,7 +676,7 @@ int main() check_valid(addr); check_valid(rktio_udp_connect(rktio, fd2, addr)); - check_read_write_pair(rktio, fd, fd2); + check_read_write_pair(rktio, fd, fd2, 0); /* Again, this time to fill & drain: */ @@ -701,6 +702,51 @@ int main() check_valid(rktio_close(rktio, fd)); check_valid(rktio_close(rktio, fd2)); } + + /* Processes */ + { + rktio_status_t *status; + rktio_process_result_t *result; + char *argv[2] = { "/bin/cat", NULL }; + rktio_envvars_t *envvars = NULL; + rktio_fd_t *err_fd = rktio_system_fd(rktio, 2, RKTIO_OPEN_WRITE); + int done; + + result = rktio_process(rktio, "/bin/cat", 1, argv, + NULL, NULL, err_fd, + rktio_get_current_directory(rktio), envvars, + 0, + NULL); + check_valid(result); + + status = rktio_process_status(rktio, result->process); + check_valid(status); + check_valid(status->running); + check_valid(!result->stderr_fd); + free(status); + + check_valid(!rktio_poll_subprocess_done(rktio, result->process)); + + check_read_write_pair(rktio, result->stdout_fd, result->stdin_fd, 0); + + check_valid(rktio_poll_subprocess_done(rktio, result->process) != RKTIO_PROCESS_ERROR); + + do { + rktio_poll_set_t *ps; + ps = rktio_make_poll_set(rktio); + check_valid(ps); + rktio_poll_add_process(rktio, result->process, ps); + rktio_sleep(rktio, 0, ps, NULL); + rktio_poll_set_close(rktio, ps); + done = rktio_poll_subprocess_done(rktio, result->process); + check_valid(done != RKTIO_PROCESS_ERROR); + } while (!done); + + rktio_process_forget(rktio, result->process); + free(result); + + check_valid(rktio_close(rktio, err_fd)); + } return 0; } diff --git a/racket/src/rktio/rktio.h b/racket/src/rktio/rktio.h index e562d1366b..0f67a7ea62 100644 --- a/racket/src/rktio/rktio.h +++ b/racket/src/rktio/rktio.h @@ -48,7 +48,6 @@ void rktio_forget(rktio_t *rktio, rktio_fd_t *fd); #define RKTIO_WRITE_ERROR (-2) #define RKTIO_POLL_ERROR (-2) #define RKTIO_POLL_READY 1 -#define RKTIO_PROP_ERROR (-2) intptr_t rktio_read(rktio_t *rktio, rktio_fd_t *fd, char *buffer, intptr_t len); intptr_t rktio_write(rktio_t *rktio, rktio_fd_t *fd, char *buffer, intptr_t len); @@ -110,6 +109,8 @@ typedef struct rktio_length_and_addrinfo_t { rktio_length_and_addrinfo_t *rktio_udp_recvfrom(rktio_t *rktio, rktio_fd_t *rfd, char *buffer, intptr_t len); +#define RKTIO_PROP_ERROR (-2) + /* The following accessors return RKTIO_PROP_ERROR on failure */ int rktio_udp_get_multicast_loopback(rktio_t *rktio, rktio_fd_t *rfd); int rktio_udp_set_multicast_loopback(rktio_t *rktio, rktio_fd_t *rfd, int on); @@ -133,6 +134,51 @@ int rktio_udp_change_multicast_group(rktio_t *rktio, rktio_fd_t *rfd, rktio_addrinfo_t *intf_addr, int action); +/*************************************************/ +/* Environment variables */ + +typedef struct rktio_envvars_t rktio_envvars_t; + +/*************************************************/ +/* Processes */ + +typedef struct rktio_process_t rktio_process_t; + +#define RKTIO_PROCESS_NEW_GROUP (1<<0) +#define RKTIO_PROCESS_STDOUT_AS_STDERR (1<<1) +#define RKTIO_PROCESS_WINDOWS_EXACT_CMDLINE (1<<2) +#define RKTIO_PROCESS_WINDOWS_CHAIN_TERMINATION (1<<3) + +typedef struct rktio_process_result_t { + rktio_process_t *process; + rktio_fd_t *stdin_fd, *stdout_fd, *stderr_fd; +} rktio_process_result_t; + +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)()); + +int rktio_process_kill(rktio_t *rktio, rktio_process_t *sp); +int rktio_process_interrupt(rktio_t *rktio, rktio_process_t *sp); +void rktio_process_forget(rktio_t *rktio, rktio_process_t *sp); + +#define RKTIO_PROCESS_ERROR (-2) +#define RKTIO_PROCESS_DONE 1 + +int rktio_poll_subprocess_done(rktio_t *rktio, rktio_process_t *sp); + +typedef struct rktio_status_t { + int running; + int result; +} rktio_status_t; + +rktio_status_t *rktio_process_status(rktio_t *rktio, rktio_process_t *sp); + +void rktio_block_child_signals(rktio_t*rktio, int block); + /*************************************************/ /* File-descriptor sets for polling */ @@ -151,12 +197,13 @@ void rktio_poll_add(rktio_t *rktio, rktio_fd_t *rfd, rktio_poll_set_t *fds, int void rktio_poll_add_receive(rktio_t *rktio, rktio_listener_t *listener, rktio_poll_set_t *fds); void rktio_poll_add_connect(rktio_t *rktio, rktio_connect_t *conn, rktio_poll_set_t *fds); void rktio_poll_add_addrinfo_lookup(rktio_t *rktio, rktio_addrinfo_lookup_t *lookup, rktio_poll_set_t *fds); +void rktio_poll_add_process(rktio_t *rktio, rktio_process_t *sp, rktio_poll_set_t *fds); void rktio_poll_set_add_nosleep(rktio_t *rktio, rktio_poll_set_t *fds); #ifdef RKTIO_SYSTEM_WINDOWS -void rktio_poll_set_add_handle(HANDLE h, rktio_poll_set_t *fds, int repost); -void rktio_poll_set_add_eventmask(rktio_poll_set_t *fds, int mask); +void rktio_poll_set_add_handle(rktio_t *rktio, HANDLE h, rktio_poll_set_t *fds, int repost); +void rktio_poll_set_add_eventmask(rktio_t *rktio, rktio_poll_set_t *fds, int mask); #endif /*************************************************/ @@ -204,7 +251,7 @@ int rktio_delete_file(rktio_t *rktio, char *fn, int enable_write_on_fail); int rktio_rename_file(rktio_t *rktio, char *dest, char *src, int exists_ok); char *rktio_get_current_directory(rktio_t *rktio); -int rktio_set_current_directory(rktio_t *rktio, char *expanded); +int rktio_set_current_directory(rktio_t *rktio, const char *path); int rktio_make_directory(rktio_t *rktio, char *filename); int rktio_delete_directory(rktio_t *rktio, char *filename, char *current_directory, int enable_write_on_fail); diff --git a/racket/src/rktio/rktio_envvars.c b/racket/src/rktio/rktio_envvars.c new file mode 100644 index 0000000000..f5999f7830 --- /dev/null +++ b/racket/src/rktio/rktio_envvars.c @@ -0,0 +1,12 @@ +#include "rktio.h" +#include "rktio_private.h" +#include +#include + +void *rktio_envvars_to_block(rktio_t *rktio, rktio_envvars_t *envvars) +{ + void *p; + p = malloc(sizeof(char *)); + *(char **)p = NULL; + return p; +} diff --git a/racket/src/rktio/rktio_filesystem.c b/racket/src/rktio/rktio_filesystem.c index 4a251b7b66..7240d7e033 100644 --- a/racket/src/rktio/rktio_filesystem.c +++ b/racket/src/rktio/rktio_filesystem.c @@ -630,12 +630,12 @@ char *rktio_get_current_directory(rktio_t *rktio) #endif } -int rktio_set_current_directory(rktio_t *rktio, char *expanded) +int rktio_set_current_directory(rktio_t *rktio, const char *path) { int err; while (1) { - err = MSC_W_IZE(chdir)(MSC_WIDE_PATH_temp(expanded)); + err = MSC_W_IZE(chdir)(MSC_WIDE_PATH_temp(path)); if (!err || (errno != EINTR)) break; } @@ -914,7 +914,7 @@ int rktio_delete_directory(rktio_t *rktio, char *filename, char *current_directo # ifdef RKTIO_SYSTEM_WINDOWS else if ((errno == EACCES) && !tried_cwd) { /* Maybe we're using the target directory. Try a real setcwd. */ - (void)rktio_set_current_directory(current_directory); + (void)rktio_set_current_directory(rktio, current_directory); tried_cwd = 1; } else if ((errno == EACCES) && !tried_perm && enable_write_on_fail) { /* Maybe the directory doesn't have write permission. */ diff --git a/racket/src/rktio/rktio_main.c b/racket/src/rktio/rktio_main.c index 297b4c1483..5c6586aa99 100644 --- a/racket/src/rktio/rktio_main.c +++ b/racket/src/rktio/rktio_main.c @@ -15,12 +15,18 @@ rktio_t *rktio_init(void) rktio_destroy(rktio); return NULL; } - + + if (!rktio_process_init(rktio)) { + rktio_destroy(rktio); + return NULL; + } + return rktio; } void rktio_destroy(rktio_t *rktio) { + rktio_process_deinit(rktio); rktio_free_ghbn(rktio); rktio_free_global_poll_set(rktio); free(rktio); diff --git a/racket/src/rktio/rktio_private.h b/racket/src/rktio/rktio_private.h index b9ec641a8f..3e7730bbd1 100644 --- a/racket/src/rktio/rktio_private.h +++ b/racket/src/rktio/rktio_private.h @@ -72,6 +72,16 @@ struct rktio_t { HANDLE ghbn_start; # endif #endif + +#if defined(RKTIO_SYSTEM_UNIX) && !defined(RKTIO_USE_PTHREADS) + struct System_Child *system_children; + int need_to_check_children; + int in_sigchld_chain; + struct rktio_t *next; /* chaining for SIGCHLD handling */ +#endif +#ifdef RKTIO_SYSTEM_WINDOWS + uintptr_t process_children_msecs; +#endif }; /*========================================================================*/ @@ -165,6 +175,13 @@ intptr_t rktio_socket_read(rktio_t *rktio, rktio_fd_t *rfd, char *buffer, intptr void rktio_free_ghbn(rktio_t *rktio); const char *rktio_gai_strerror(int errnum); + +/*========================================================================*/ +/* Processes */ +/*========================================================================*/ + +int rktio_process_init(rktio_t *rktio); +void rktio_process_deinit(rktio_t *rktio); /*========================================================================*/ /* Misc */ @@ -196,3 +213,5 @@ void rktio_get_windows_error(rktio_t *rktio); #else # define RKTIO_NONBLOCKING FNDELAY #endif + +void *rktio_envvars_to_block(rktio_t *rktio, rktio_envvars_t *envvars); diff --git a/racket/src/rktio/rktio_process.c b/racket/src/rktio/rktio_process.c new file mode 100644 index 0000000000..59351ca722 --- /dev/null +++ b/racket/src/rktio/rktio_process.c @@ -0,0 +1,1597 @@ +#include "rktio.h" +#include "rktio_private.h" +#include +#include +#include +#if defined(RKTIO_SYSTEM_UNIX) +# include +# include +# include +# include +# include +#endif + +#if defined(RKTIO_SYSTEM_UNIX) && defined(RKTIO_USE_PTHREADS) +#define CENTRALIZED_SIGCHILD +#endif + +#ifdef RKTIO_SYSTEM_UNIX +static void close_fds_after_fork(int skip1, int skip2, int skip3); +#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 +static int MyPipe(intptr_t *ph, int near_index) +{ + 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 +# include +# 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)) { + _int64 v; + uintptr_t msecs; + v = ((((_int64)kr.dwHighDateTime << 32) + kr.dwLowDateTime) + + (((_int65)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_subprocess_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_subprocess_done(rktio, sp)) { + rktio_poll_set_add_nosleep(rktio, fds); + return; + } + +#ifdef RKTIO_SYSTEM_WINDOWS + HANDLE sci = sp->handle; + rktio_poll_set_add_handle(rktio, 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; + int errid; + + 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; + } +# endif +#endif + +#ifdef RKTIO_SYSTEM_WINDOWS + CloseHandle(subproc->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 (process_job_object) { + TerminateJobObject((HANDLE)process_job_object, 1); + CloseHandle((HANDLE)process_job_object); + 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 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 strdup(s); +} + +static intptr_t do_spawnv(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, char *wd) +{ + int i, l, len = 0, use_jo; + intptr_t cr_flag; + char *cmdline; + wchar_t *cmdline_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 (!is_fd_terminal((intptr_t)startup.hStdInput) + && !is_fd_terminal((intptr_t)startup.hStdOutput) + && !is_fd_terminal((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 (!process_job_object) { + GC_CAN_IGNORE JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli; + + process_job_object = (void*)CreateJobObject(NULL, NULL); + + memset(&jeli, 0, sizeof(jeli)); + jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + SetInformationJobObject((HANDLE)process_job_object, + JobObjectExtendedLimitInformation, + &jeli, + sizeof(jeli)); + } + } + + + cmdline_w = WIDE_PATH_copy(cmdline); + if (!exact_cmdline) + free(cmdline); + + if (CreateProcessW(WIDE_PATH_temp(command), cmdline_w, + NULL, NULL, 1 /*inherit*/, + cr_flag, env, WIDE_PATH_COPY(wd), + &startup, &info)) { + if (use_jo) + AssignProcessToJobObject((HANDLE)process_job_object, info.hProcess); + CloseHandle(info.hThread); + *pid = info.dwProcessId; + free(cmdline_w); + return (intptr_t)info.hProcess; + } else { + free(cmdline_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) + +#endif /* RKTIO_SYSTEM_WINDOWS */ + +#ifdef RKTIO_SYSTEM_UNIX +# define RKTIO_COPY_FOR_SUBPROCESS(array, pos) /* empty */ +# define RKTIO_CLOSE_SUBPROCESS_COPY(array, pos) /* empty */ + +static void reliably_close(intptr_t s) { + int cr; + do { + cr = close(s); + } while ((cr == -1) && (errno == EINTR)); +} +#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 i, pid, errid; +#if defined(RKTIO_SYSTEM_UNIX) +# if !defined(CENTRALIZED_SIGCHILD) + System_Child *sc; +# endif + int fork_errno = 0; + void *env; +#else + void *sc = 0; +#endif + 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); + int windows_exact_cmdline = (flags & RKTIO_PROCESS_WINDOWS_EXACT_CMDLINE); + int windows_chain_termination_to_child = (flags & RKTIO_PROCESS_WINDOWS_CHAIN_TERMINATION); + + /* 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; + } + + env = rktio_envvars_to_block(rktio, envvars); + + +#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 (!exact_cmdline) { + /* protect spaces, etc. in the arguments: */ + new_argv = malloc(sizeof(char *) * argc); + for (i = 0; i < argv; i++) { + new_argv[i] = cmdline_protect(argv[i]); + } + argv = new_argv; + } + + spawn_status = do_spawnv(command, (const char * const *)new_argv, + exact_cmdline, + to_subprocess[0], + from_subprocess[1], + err_subprocess[1], + &pid, + new_process_group, + windows_chain_termination_to_child, + env, current_directory); + + if (!exact_cmdline) { + for (i = 0; i < argv; 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) { + reliably_close(to_subprocess[0]); + reliably_close(to_subprocess[1]); + } else { + RKTIO_CLOSE_SUBPROCESS_COPY(to_subprocess, 0); + } + if (!stdout_fd) { + reliably_close(from_subprocess[0]); + reliably_close(from_subprocess[1]); + } else { + RKTIO_CLOSE_SUBPROCESS_COPY(from_subprocess, 1); + } + if (!stderr_fd) { + if (!stderr_is_stdout) { + reliably_close(err_subprocess[0]); + reliably_close(err_subprocess[1]); + } + } else { + RKTIO_CLOSE_SUBPROCESS_COPY(err_subprocess, 1); + } + return NULL; + + case 0: /* child */ + + { + /* 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) { + reliably_close(to_subprocess[0]); + reliably_close(to_subprocess[1]); + } + if (!stdout_fd) { + reliably_close(from_subprocess[0]); + reliably_close(from_subprocess[1]); + } + if (!stderr_fd) { + if (!stderr_is_stdout) { + reliably_close(err_subprocess[0]); + reliably_close(err_subprocess[1]); + } + } + + 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; + + err = MSC_IZE(execve)(command, argv, (char **)env); + if (err) + err = errno; + + 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) { + reliably_close(to_subprocess[0]); + } else { + RKTIO_CLOSE_SUBPROCESS_COPY(to_subprocess, 0); + } + if (!stdout_fd) { + reliably_close(from_subprocess[1]); + } else { + RKTIO_CLOSE_SUBPROCESS_COPY(from_subprocess, 1); + } + if (!stderr_fd) { + if (!stderr_is_stdout) + reliably_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; +} + +static void 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)); + } + } +} +