X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=lib%2Fprocess.c;h=0c7f424fc84cb01324d70b3b15147978b59029b3;hb=2d2d6d4a71776813f8d2fd1af1051f22b836befc;hp=eec5965ec728cb1b357ccaf419f15d1556edeeac;hpb=1fa39e1d50ea5f836d24c91d0651dda315e91c42;p=openvswitch diff --git a/lib/process.c b/lib/process.c index eec5965e..0c7f424f 100644 --- a/lib/process.c +++ b/lib/process.c @@ -161,7 +161,7 @@ process_register(const char *name, pid_t pid) assert(sigchld_is_blocked()); - p = xcalloc(1, sizeof *p); + p = xzalloc(sizeof *p); p->pid = pid; slash = strrchr(name, '/'); p->name = xstrdup(slash ? slash + 1 : name); @@ -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) {