From: Ben Pfaff Date: Wed, 15 Jul 2009 19:45:44 +0000 (-0700) Subject: process: New function process_run_capture(). X-Git-Tag: v0.90.3~3^2~3 X-Git-Url: http://git.onelab.eu/?a=commitdiff_plain;h=b5dae6846aa511ce4347ce988beb685ad20afdcb;p=sliver-openvswitch.git process: New function process_run_capture(). In an upcoming commit, ovs-brcompatd will need to create a subprocess and capture both its stdout and stderr separately, which one cannot do with simple interfaces such as popen(). This function provides that ability. --- diff --git a/lib/process.c b/lib/process.c index eec5965ec..1fe3c1235 100644 --- a/lib/process.c +++ b/lib/process.c @@ -397,6 +397,203 @@ process_search_path(const char *name) return NULL; } +/* process_run_capture() and supporting functions. */ + +struct stream { + struct ds log; + int fds[2]; +}; + +static int +stream_open(struct stream *s) +{ + ds_init(&s->log); + if (pipe(s->fds)) { + VLOG_WARN("failed to create pipe: %s", strerror(errno)); + return errno; + } + set_nonblocking(s->fds[0]); + return 0; +} + +static void +stream_read(struct stream *s) +{ + int error = 0; + + if (s->fds[0] < 0) { + return; + } + + error = 0; + for (;;) { + char buffer[512]; + size_t n; + + error = read_fully(s->fds[0], buffer, sizeof buffer, &n); + ds_put_buffer(&s->log, buffer, n); + if (error) { + if (error == EAGAIN || error == EWOULDBLOCK) { + return; + } else { + if (error != EOF) { + VLOG_WARN("error reading subprocess pipe: %s", + strerror(error)); + } + break; + } + } else if (s->log.length > PROCESS_MAX_CAPTURE) { + VLOG_WARN("subprocess output overflowed %d-byte buffer", + PROCESS_MAX_CAPTURE); + break; + } + } + close(s->fds[0]); + s->fds[0] = -1; +} + +static void +stream_wait(struct stream *s) +{ + if (s->fds[0] >= 0) { + poll_fd_wait(s->fds[0], POLLIN); + } +} + +static void +stream_close(struct stream *s) +{ + ds_destroy(&s->log); + if (s->fds[0] >= 0) { + close(s->fds[0]); + } + if (s->fds[1] >= 0) { + close(s->fds[1]); + } +} + +/* Starts the process whose arguments are given in the null-terminated array + * 'argv' and waits for it to exit. On success returns 0 and stores the + * process exit value (suitable for passing to process_status_msg()) in + * '*status'. On failure, returns a positive errno value and stores 0 in + * '*status'. + * + * If 'stdout_log' is nonnull, then the subprocess's output to stdout (up to a + * limit of PROCESS_MAX_CAPTURE bytes) is captured in a memory buffer, which + * when this function returns 0 is stored as a null-terminated string in + * '*stdout_log'. The caller is responsible for freeing '*stdout_log' (by + * passing it to free()). When this function returns an error, '*stdout_log' + * is set to NULL. + * + * If 'stderr_log' is nonnull, then it is treated like 'stdout_log' except + * that it captures the subprocess's output to stderr. */ +int +process_run_capture(char **argv, char **stdout_log, char **stderr_log, + int *status) +{ + struct stream s_stdout, s_stderr; + sigset_t oldsigs; + pid_t pid; + int error; + + COVERAGE_INC(process_run_capture); + if (stdout_log) { + *stdout_log = NULL; + } + if (stderr_log) { + *stderr_log = NULL; + } + *status = 0; + error = process_prestart(argv); + if (error) { + return error; + } + + error = stream_open(&s_stdout); + if (error) { + return error; + } + + error = stream_open(&s_stderr); + if (error) { + stream_close(&s_stdout); + return error; + } + + block_sigchld(&oldsigs); + fatal_signal_block(); + pid = fork(); + if (pid < 0) { + int error = errno; + + fatal_signal_unblock(); + unblock_sigchld(&oldsigs); + VLOG_WARN("fork failed: %s", strerror(error)); + + stream_close(&s_stdout); + stream_close(&s_stderr); + *status = 0; + return error; + } else if (pid) { + /* Running in parent process. */ + struct process *p; + + p = process_register(argv[0], pid); + fatal_signal_unblock(); + unblock_sigchld(&oldsigs); + + close(s_stdout.fds[1]); + close(s_stderr.fds[1]); + while (!process_exited(p)) { + stream_read(&s_stdout); + stream_read(&s_stderr); + + stream_wait(&s_stdout); + stream_wait(&s_stderr); + process_wait(p); + poll_block(); + } + stream_read(&s_stdout); + stream_read(&s_stderr); + + if (stdout_log) { + *stdout_log = ds_steal_cstr(&s_stdout.log); + } + if (stderr_log) { + *stderr_log = ds_steal_cstr(&s_stderr.log); + } + + stream_close(&s_stdout); + stream_close(&s_stderr); + + *status = process_status(p); + process_destroy(p); + return 0; + } else { + /* Running in child process. */ + int max_fds; + int i; + + fatal_signal_fork(); + fatal_signal_unblock(); + unblock_sigchld(&oldsigs); + + dup2(get_null_fd(), 0); + dup2(s_stdout.fds[1], 1); + dup2(s_stderr.fds[1], 2); + + max_fds = get_max_fds(); + for (i = 3; i < max_fds; i++) { + close(i); + } + + execvp(argv[0], argv); + fprintf(stderr, "execvp(\"%s\") failed: %s\n", + argv[0], strerror(errno)); + exit(EXIT_FAILURE); + } +} + static void sigchld_handler(int signr UNUSED) { diff --git a/lib/process.h b/lib/process.h index cd5af41b9..94549f7c8 100644 --- a/lib/process.h +++ b/lib/process.h @@ -45,4 +45,8 @@ void process_wait(struct process *); char *process_search_path(const char *); +#define PROCESS_MAX_CAPTURE 65536 +int process_run_capture(char **argv, char **stdout_log, char **stderr_log, + int *status); + #endif /* process.h */