You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
664 lines
16 KiB
664 lines
16 KiB
/* -*- coding: utf-8 -*-
|
|
* ----------------------------------------------------------------------
|
|
* Copyright © 2012-2014, RedJack, LLC.
|
|
* All rights reserved.
|
|
*
|
|
* Please see the COPYING file in this distribution for license details.
|
|
* ----------------------------------------------------------------------
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
#ifndef __MINGW32__
|
|
#include <sys/select.h>
|
|
#include <sys/wait.h>
|
|
#endif
|
|
#include <unistd.h>
|
|
|
|
#include "libcork/core.h"
|
|
#include "libcork/ds.h"
|
|
#include "libcork/os/subprocess.h"
|
|
#include "libcork/threads/basics.h"
|
|
#include "libcork/helpers/errors.h"
|
|
#include "libcork/helpers/posix.h"
|
|
|
|
|
|
#if !defined(CORK_DEBUG_SUBPROCESS)
|
|
#define CORK_DEBUG_SUBPROCESS 0
|
|
#endif
|
|
|
|
#if CORK_DEBUG_SUBPROCESS
|
|
#include <stdio.h>
|
|
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)
|
|
#else
|
|
#define DEBUG(...) /* no debug messages */
|
|
#endif
|
|
|
|
|
|
/*-----------------------------------------------------------------------
|
|
* Subprocess groups
|
|
*/
|
|
|
|
#define BUF_SIZE 4096
|
|
|
|
struct cork_subprocess_group {
|
|
cork_array(struct cork_subprocess *) subprocesses;
|
|
};
|
|
|
|
struct cork_subprocess_group *
|
|
cork_subprocess_group_new(void)
|
|
{
|
|
struct cork_subprocess_group *group =
|
|
cork_new(struct cork_subprocess_group);
|
|
cork_pointer_array_init
|
|
(&group->subprocesses, (cork_free_f) cork_subprocess_free);
|
|
return group;
|
|
}
|
|
|
|
void
|
|
cork_subprocess_group_free(struct cork_subprocess_group *group)
|
|
{
|
|
cork_array_done(&group->subprocesses);
|
|
cork_delete(struct cork_subprocess_group, group);
|
|
}
|
|
|
|
void
|
|
cork_subprocess_group_add(struct cork_subprocess_group *group,
|
|
struct cork_subprocess *sub)
|
|
{
|
|
cork_array_append(&group->subprocesses, sub);
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------
|
|
* Pipes (parent reads)
|
|
*/
|
|
|
|
struct cork_read_pipe {
|
|
struct cork_stream_consumer *consumer;
|
|
int fds[2];
|
|
bool first;
|
|
};
|
|
|
|
static void
|
|
cork_read_pipe_init(struct cork_read_pipe *p, struct cork_stream_consumer *consumer)
|
|
{
|
|
p->consumer = consumer;
|
|
p->fds[0] = -1;
|
|
p->fds[1] = -1;
|
|
}
|
|
|
|
static int
|
|
cork_read_pipe_close_read(struct cork_read_pipe *p)
|
|
{
|
|
if (p->fds[0] != -1) {
|
|
DEBUG("Closing read pipe %d\n", p->fds[0]);
|
|
rii_check_posix(close(p->fds[0]));
|
|
p->fds[0] = -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
cork_read_pipe_close_write(struct cork_read_pipe *p)
|
|
{
|
|
if (p->fds[1] != -1) {
|
|
DEBUG("Closing write pipe %d\n", p->fds[1]);
|
|
rii_check_posix(close(p->fds[1]));
|
|
p->fds[1] = -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
cork_read_pipe_close(struct cork_read_pipe *p)
|
|
{
|
|
cork_read_pipe_close_read(p);
|
|
cork_read_pipe_close_write(p);
|
|
}
|
|
|
|
static void
|
|
cork_read_pipe_done(struct cork_read_pipe *p)
|
|
{
|
|
cork_read_pipe_close(p);
|
|
}
|
|
|
|
static int
|
|
cork_read_pipe_open(struct cork_read_pipe *p)
|
|
{
|
|
if (p->consumer != NULL) {
|
|
int flags;
|
|
|
|
/* We want the read end of the pipe to be non-blocking. */
|
|
DEBUG("[read] Opening pipe\n");
|
|
rii_check_posix(pipe(p->fds));
|
|
DEBUG("[read] Got read=%d write=%d\n", p->fds[0], p->fds[1]);
|
|
DEBUG("[read] Setting non-blocking flag on read pipe\n");
|
|
ei_check_posix(flags = fcntl(p->fds[0], F_GETFD));
|
|
flags |= O_NONBLOCK;
|
|
ei_check_posix(fcntl(p->fds[0], F_SETFD, flags));
|
|
}
|
|
|
|
p->first = true;
|
|
return 0;
|
|
|
|
error:
|
|
cork_read_pipe_close(p);
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
cork_read_pipe_dup(struct cork_read_pipe *p, int fd)
|
|
{
|
|
if (p->fds[1] != -1) {
|
|
rii_check_posix(dup2(p->fds[1], fd));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
cork_read_pipe_read(struct cork_read_pipe *p, char *buf, bool *progress)
|
|
{
|
|
if (p->fds[0] == -1) {
|
|
return 0;
|
|
}
|
|
|
|
do {
|
|
DEBUG("[read] Reading from pipe %d\n", p->fds[0]);
|
|
ssize_t bytes_read = read(p->fds[0], buf, BUF_SIZE);
|
|
if (bytes_read == -1) {
|
|
if (errno == EAGAIN) {
|
|
/* We've exhausted all of the data currently available. */
|
|
DEBUG("[read] No more bytes without blocking\n");
|
|
return 0;
|
|
} else if (errno == EINTR) {
|
|
/* Interrupted by a signal; return so that our wait loop can
|
|
* catch that. */
|
|
DEBUG("[read] Interrupted by signal\n");
|
|
return 0;
|
|
} else {
|
|
/* An actual error */
|
|
cork_system_error_set();
|
|
DEBUG("[read] Error: %s\n", cork_error_message());
|
|
return -1;
|
|
}
|
|
} else if (bytes_read == 0) {
|
|
DEBUG("[read] End of stream\n");
|
|
*progress = true;
|
|
rii_check(cork_stream_consumer_eof(p->consumer));
|
|
rii_check_posix(close(p->fds[0]));
|
|
p->fds[0] = -1;
|
|
return 0;
|
|
} else {
|
|
DEBUG("[read] Got %zd bytes\n", bytes_read);
|
|
*progress = true;
|
|
rii_check(cork_stream_consumer_data
|
|
(p->consumer, buf, bytes_read, p->first));
|
|
p->first = false;
|
|
}
|
|
} while (true);
|
|
}
|
|
|
|
static bool
|
|
cork_read_pipe_is_finished(struct cork_read_pipe *p)
|
|
{
|
|
return p->fds[0] == -1;
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------
|
|
* Pipes (parent writes)
|
|
*/
|
|
|
|
struct cork_write_pipe {
|
|
struct cork_stream_consumer consumer;
|
|
int fds[2];
|
|
};
|
|
|
|
static int
|
|
cork_write_pipe_close_read(struct cork_write_pipe *p)
|
|
{
|
|
if (p->fds[0] != -1) {
|
|
DEBUG("[write] Closing read pipe %d\n", p->fds[0]);
|
|
rii_check_posix(close(p->fds[0]));
|
|
p->fds[0] = -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
cork_write_pipe_close_write(struct cork_write_pipe *p)
|
|
{
|
|
if (p->fds[1] != -1) {
|
|
DEBUG("[write] Closing write pipe %d\n", p->fds[1]);
|
|
rii_check_posix(close(p->fds[1]));
|
|
p->fds[1] = -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
cork_write_pipe__data(struct cork_stream_consumer *consumer,
|
|
const void *buf, size_t size, bool is_first_chunk)
|
|
{
|
|
struct cork_write_pipe *p =
|
|
cork_container_of(consumer, struct cork_write_pipe, consumer);
|
|
rii_check_posix(write(p->fds[1], buf, size));
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
cork_write_pipe__eof(struct cork_stream_consumer *consumer)
|
|
{
|
|
struct cork_write_pipe *p =
|
|
cork_container_of(consumer, struct cork_write_pipe, consumer);
|
|
return cork_write_pipe_close_write(p);
|
|
}
|
|
|
|
static void
|
|
cork_write_pipe__free(struct cork_stream_consumer *consumer)
|
|
{
|
|
}
|
|
|
|
static void
|
|
cork_write_pipe_init(struct cork_write_pipe *p)
|
|
{
|
|
p->consumer.data = cork_write_pipe__data;
|
|
p->consumer.eof = cork_write_pipe__eof;
|
|
p->consumer.free = cork_write_pipe__free;
|
|
p->fds[0] = -1;
|
|
p->fds[1] = -1;
|
|
}
|
|
|
|
static void
|
|
cork_write_pipe_close(struct cork_write_pipe *p)
|
|
{
|
|
cork_write_pipe_close_read(p);
|
|
cork_write_pipe_close_write(p);
|
|
}
|
|
|
|
static void
|
|
cork_write_pipe_done(struct cork_write_pipe *p)
|
|
{
|
|
cork_write_pipe_close(p);
|
|
}
|
|
|
|
static int
|
|
cork_write_pipe_open(struct cork_write_pipe *p)
|
|
{
|
|
DEBUG("[write] Opening writer pipe\n");
|
|
rii_check_posix(pipe(p->fds));
|
|
DEBUG("[write] Got read=%d write=%d\n", p->fds[0], p->fds[1]);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
cork_write_pipe_dup(struct cork_write_pipe *p, int fd)
|
|
{
|
|
if (p->fds[0] != -1) {
|
|
rii_check_posix(dup2(p->fds[0], fd));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------
|
|
* Subprocesses
|
|
*/
|
|
|
|
struct cork_subprocess {
|
|
pid_t pid;
|
|
struct cork_write_pipe stdin_pipe;
|
|
struct cork_read_pipe stdout_pipe;
|
|
struct cork_read_pipe stderr_pipe;
|
|
void *user_data;
|
|
cork_free_f free_user_data;
|
|
cork_run_f run;
|
|
int *exit_code;
|
|
char buf[BUF_SIZE];
|
|
};
|
|
|
|
struct cork_subprocess *
|
|
cork_subprocess_new(void *user_data, cork_free_f free_user_data,
|
|
cork_run_f run,
|
|
struct cork_stream_consumer *stdout_consumer,
|
|
struct cork_stream_consumer *stderr_consumer,
|
|
int *exit_code)
|
|
{
|
|
struct cork_subprocess *self = cork_new(struct cork_subprocess);
|
|
cork_write_pipe_init(&self->stdin_pipe);
|
|
cork_read_pipe_init(&self->stdout_pipe, stdout_consumer);
|
|
cork_read_pipe_init(&self->stderr_pipe, stderr_consumer);
|
|
self->pid = 0;
|
|
self->user_data = user_data;
|
|
self->free_user_data = free_user_data;
|
|
self->run = run;
|
|
self->exit_code = exit_code;
|
|
return self;
|
|
}
|
|
|
|
void
|
|
cork_subprocess_free(struct cork_subprocess *self)
|
|
{
|
|
cork_free_user_data(self);
|
|
cork_write_pipe_done(&self->stdin_pipe);
|
|
cork_read_pipe_done(&self->stdout_pipe);
|
|
cork_read_pipe_done(&self->stderr_pipe);
|
|
cork_delete(struct cork_subprocess, self);
|
|
}
|
|
|
|
struct cork_stream_consumer *
|
|
cork_subprocess_stdin(struct cork_subprocess *self)
|
|
{
|
|
return &self->stdin_pipe.consumer;
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------
|
|
* Executing another program
|
|
*/
|
|
|
|
static int
|
|
cork_exec__run(void *vself)
|
|
{
|
|
struct cork_exec *exec = vself;
|
|
return cork_exec_run(exec);
|
|
}
|
|
|
|
static void
|
|
cork_exec__free(void *vself)
|
|
{
|
|
struct cork_exec *exec = vself;
|
|
cork_exec_free(exec);
|
|
}
|
|
|
|
struct cork_subprocess *
|
|
cork_subprocess_new_exec(struct cork_exec *exec,
|
|
struct cork_stream_consumer *out,
|
|
struct cork_stream_consumer *err,
|
|
int *exit_code)
|
|
{
|
|
return cork_subprocess_new
|
|
(exec, cork_exec__free,
|
|
cork_exec__run,
|
|
out, err, exit_code);
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------
|
|
* Running subprocesses
|
|
*/
|
|
|
|
int
|
|
cork_subprocess_start(struct cork_subprocess *self)
|
|
{
|
|
pid_t pid;
|
|
|
|
/* Create the stdout and stderr pipes. */
|
|
if (cork_write_pipe_open(&self->stdin_pipe) == -1) {
|
|
return -1;
|
|
}
|
|
if (cork_read_pipe_open(&self->stdout_pipe) == -1) {
|
|
cork_write_pipe_close(&self->stdin_pipe);
|
|
return -1;
|
|
}
|
|
if (cork_read_pipe_open(&self->stderr_pipe) == -1) {
|
|
cork_write_pipe_close(&self->stdin_pipe);
|
|
cork_read_pipe_close(&self->stdout_pipe);
|
|
return -1;
|
|
}
|
|
|
|
/* Fork the child process. */
|
|
DEBUG("Forking child process\n");
|
|
pid = fork();
|
|
if (pid == 0) {
|
|
/* Child process */
|
|
int rc;
|
|
|
|
/* Close the parent's end of the pipes */
|
|
DEBUG("[child] ");
|
|
cork_write_pipe_close_write(&self->stdin_pipe);
|
|
DEBUG("[child] ");
|
|
cork_read_pipe_close_read(&self->stdout_pipe);
|
|
DEBUG("[child] ");
|
|
cork_read_pipe_close_read(&self->stderr_pipe);
|
|
|
|
/* Bind the stdout and stderr pipes */
|
|
if (cork_write_pipe_dup(&self->stdin_pipe, STDIN_FILENO) == -1) {
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
if (cork_read_pipe_dup(&self->stdout_pipe, STDOUT_FILENO) == -1) {
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
if (cork_read_pipe_dup(&self->stderr_pipe, STDERR_FILENO) == -1) {
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* Run the subprocess */
|
|
rc = self->run(self->user_data);
|
|
if (CORK_LIKELY(rc == 0)) {
|
|
_exit(EXIT_SUCCESS);
|
|
} else {
|
|
fprintf(stderr, "%s\n", cork_error_message());
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
} else if (pid < 0) {
|
|
/* Error forking */
|
|
cork_system_error_set();
|
|
return -1;
|
|
} else {
|
|
/* Parent process */
|
|
DEBUG(" Child PID=%d\n", (int) pid);
|
|
self->pid = pid;
|
|
cork_write_pipe_close_read(&self->stdin_pipe);
|
|
cork_read_pipe_close_write(&self->stdout_pipe);
|
|
cork_read_pipe_close_write(&self->stderr_pipe);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int
|
|
cork_subprocess_reap(struct cork_subprocess *self, int flags, bool *progress)
|
|
{
|
|
int pid;
|
|
int status;
|
|
rii_check_posix(pid = waitpid(self->pid, &status, flags));
|
|
if (pid == self->pid) {
|
|
*progress = true;
|
|
self->pid = 0;
|
|
if (self->exit_code != NULL) {
|
|
*self->exit_code = WEXITSTATUS(status);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
cork_subprocess_abort(struct cork_subprocess *self)
|
|
{
|
|
if (self->pid > 0) {
|
|
CORK_ATTR_UNUSED bool progress;
|
|
DEBUG("Terminating child process %d\n", (int) self->pid);
|
|
kill(self->pid, SIGTERM);
|
|
return cork_subprocess_reap(self, 0, &progress);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
bool
|
|
cork_subprocess_is_finished(struct cork_subprocess *self)
|
|
{
|
|
return (self->pid == 0)
|
|
&& cork_read_pipe_is_finished(&self->stdout_pipe)
|
|
&& cork_read_pipe_is_finished(&self->stderr_pipe);
|
|
}
|
|
|
|
#if defined(__APPLE__) || defined(__MINGW32__) || defined(__CYGWIN__)
|
|
#include <pthread.h>
|
|
#define THREAD_YIELD pthread_yield_np
|
|
#elif defined(__linux__) || defined(BSD) || defined(__sun)
|
|
#include <sched.h>
|
|
#define THREAD_YIELD sched_yield
|
|
#else
|
|
#error "Unknown thread yield implementation"
|
|
#endif
|
|
|
|
static void
|
|
cork_subprocess_yield(unsigned int *spin_count)
|
|
{
|
|
/* Adapted from
|
|
* http://www.1024cores.net/home/lock-free-algorithms/tricks/spinning */
|
|
|
|
if (*spin_count < 10) {
|
|
/* Spin-wait */
|
|
cork_pause();
|
|
} else if (*spin_count < 20) {
|
|
/* A more intense spin-wait */
|
|
int i;
|
|
for (i = 0; i < 50; i++) {
|
|
cork_pause();
|
|
}
|
|
} else if (*spin_count < 22) {
|
|
THREAD_YIELD();
|
|
} else if (*spin_count < 24) {
|
|
usleep(0);
|
|
} else if (*spin_count < 50) {
|
|
usleep(1);
|
|
} else if (*spin_count < 75) {
|
|
usleep((*spin_count - 49) * 1000);
|
|
} else {
|
|
usleep(25000);
|
|
}
|
|
|
|
(*spin_count)++;
|
|
}
|
|
|
|
static int
|
|
cork_subprocess_drain_(struct cork_subprocess *self, bool *progress)
|
|
{
|
|
rii_check(cork_read_pipe_read(&self->stdout_pipe, self->buf, progress));
|
|
rii_check(cork_read_pipe_read(&self->stderr_pipe, self->buf, progress));
|
|
if (self->pid > 0) {
|
|
return cork_subprocess_reap(self, WNOHANG, progress);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
bool
|
|
cork_subprocess_drain(struct cork_subprocess *self)
|
|
{
|
|
bool progress;
|
|
cork_subprocess_drain_(self, &progress);
|
|
return progress;
|
|
}
|
|
|
|
int
|
|
cork_subprocess_wait(struct cork_subprocess *self)
|
|
{
|
|
unsigned int spin_count = 0;
|
|
bool progress;
|
|
while (!cork_subprocess_is_finished(self)) {
|
|
progress = false;
|
|
rii_check(cork_subprocess_drain_(self, &progress));
|
|
if (!progress) {
|
|
cork_subprocess_yield(&spin_count);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------
|
|
* Running subprocess groups
|
|
*/
|
|
|
|
static int
|
|
cork_subprocess_group_terminate(struct cork_subprocess_group *group)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < cork_array_size(&group->subprocesses); i++) {
|
|
struct cork_subprocess *sub = cork_array_at(&group->subprocesses, i);
|
|
rii_check(cork_subprocess_abort(sub));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
cork_subprocess_group_start(struct cork_subprocess_group *group)
|
|
{
|
|
size_t i;
|
|
DEBUG("Starting subprocess group\n");
|
|
/* Start each subprocess. */
|
|
for (i = 0; i < cork_array_size(&group->subprocesses); i++) {
|
|
struct cork_subprocess *sub = cork_array_at(&group->subprocesses, i);
|
|
ei_check(cork_subprocess_start(sub));
|
|
}
|
|
return 0;
|
|
|
|
error:
|
|
cork_subprocess_group_terminate(group);
|
|
return -1;
|
|
}
|
|
|
|
|
|
int
|
|
cork_subprocess_group_abort(struct cork_subprocess_group *group)
|
|
{
|
|
DEBUG("Aborting subprocess group\n");
|
|
return cork_subprocess_group_terminate(group);
|
|
}
|
|
|
|
|
|
bool
|
|
cork_subprocess_group_is_finished(struct cork_subprocess_group *group)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < cork_array_size(&group->subprocesses); i++) {
|
|
struct cork_subprocess *sub = cork_array_at(&group->subprocesses, i);
|
|
bool sub_finished = cork_subprocess_is_finished(sub);
|
|
if (!sub_finished) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int
|
|
cork_subprocess_group_drain_(struct cork_subprocess_group *group,
|
|
bool *progress)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < cork_array_size(&group->subprocesses); i++) {
|
|
struct cork_subprocess *sub = cork_array_at(&group->subprocesses, i);
|
|
rii_check(cork_subprocess_drain_(sub, progress));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool
|
|
cork_subprocess_group_drain(struct cork_subprocess_group *group)
|
|
{
|
|
bool progress = false;
|
|
cork_subprocess_group_drain_(group, &progress);
|
|
return progress;
|
|
}
|
|
|
|
int
|
|
cork_subprocess_group_wait(struct cork_subprocess_group *group)
|
|
{
|
|
unsigned int spin_count = 0;
|
|
bool progress;
|
|
DEBUG("Waiting for subprocess group to finish\n");
|
|
while (!cork_subprocess_group_is_finished(group)) {
|
|
progress = false;
|
|
rii_check(cork_subprocess_group_drain_(group, &progress));
|
|
if (!progress) {
|
|
cork_subprocess_yield(&spin_count);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|