X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=lib%2Fprocess.c;h=3d6c11ae2018079d00096978d2b3b97df6b3b5a6;hb=3e42dfdc39de77a408afc4f812e01d9be5ceda66;hp=79b5659f4cbb74fd7d4865d82f71b5447011be41;hpb=064af42167bf4fc9aaea2702d80ce08074b889c0;p=sliver-openvswitch.git diff --git a/lib/process.c b/lib/process.c index 79b5659f4..3d6c11ae2 100644 --- a/lib/process.c +++ b/lib/process.c @@ -1,17 +1,17 @@ /* - * Copyright (c) 2008, 2009 Nicira Networks. + * Copyright (c) 2008, 2009, 2010, 2011 Nicira Networks. * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #include @@ -27,14 +27,21 @@ #include #include "coverage.h" #include "dynamic-string.h" +#include "fatal-signal.h" #include "list.h" #include "poll-loop.h" +#include "signals.h" #include "socket-util.h" #include "util.h" - -#define THIS_MODULE VLM_process #include "vlog.h" +VLOG_DEFINE_THIS_MODULE(process); + +COVERAGE_DEFINE(process_run); +COVERAGE_DEFINE(process_run_capture); +COVERAGE_DEFINE(process_sigchld); +COVERAGE_DEFINE(process_start); + struct process { struct list node; char *name; @@ -51,9 +58,10 @@ static int fds[2]; /* All processes. */ static struct list all_processes = LIST_INITIALIZER(&all_processes); +static bool sigchld_is_blocked(void); static void block_sigchld(sigset_t *); static void unblock_sigchld(const sigset_t *); -static void sigchld_handler(int signr UNUSED); +static void sigchld_handler(int signr OVS_UNUSED); static bool is_member(int x, const int *array, size_t); /* Initializes the process subsystem (if it is not already initialized). Calls @@ -74,9 +82,7 @@ process_init(void) inited = true; /* Create notification pipe. */ - if (pipe(fds)) { - ovs_fatal(errno, "could not create pipe"); - } + xpipe(fds); set_nonblocking(fds[0]); set_nonblocking(fds[1]); @@ -85,9 +91,7 @@ process_init(void) sa.sa_handler = sigchld_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_NOCLDSTOP | SA_RESTART; - if (sigaction(SIGCHLD, &sa, NULL)) { - ovs_fatal(errno, "sigaction(SIGCHLD) failed"); - } + xsigaction(SIGCHLD, &sa, NULL); } char * @@ -101,7 +105,7 @@ process_escape_args(char **argv) if (argp != argv) { ds_put_char(&ds, ' '); } - if (arg[strcspn(arg, " \t\r\n\v\\")]) { + if (arg[strcspn(arg, " \t\r\n\v\\\'\"")]) { ds_put_char(&ds, '"'); for (p = arg; *p; p++) { if (*p == '\\' || *p == '\"') { @@ -117,6 +121,59 @@ process_escape_args(char **argv) return ds_cstr(&ds); } +/* Prepare to start a process whose command-line arguments are given by the + * null-terminated 'argv' array. Returns 0 if successful, otherwise a + * positive errno value. */ +static int +process_prestart(char **argv) +{ + char *binary; + + process_init(); + + /* Log the process to be started. */ + if (VLOG_IS_DBG_ENABLED()) { + char *args = process_escape_args(argv); + VLOG_DBG("starting subprocess: %s", args); + free(args); + } + + /* execvp() will search PATH too, but the error in that case is more + * obscure, since it is only reported post-fork. */ + binary = process_search_path(argv[0]); + if (!binary) { + VLOG_ERR("%s not found in PATH", argv[0]); + return ENOENT; + } + free(binary); + + return 0; +} + +/* Creates and returns a new struct process with the specified 'name' and + * 'pid'. + * + * This is racy unless SIGCHLD is blocked (and has been blocked since before + * the fork()) that created the subprocess. */ +static struct process * +process_register(const char *name, pid_t pid) +{ + struct process *p; + const char *slash; + + assert(sigchld_is_blocked()); + + p = xzalloc(sizeof *p); + p->pid = pid; + slash = strrchr(name, '/'); + p->name = xstrdup(slash ? slash + 1 : name); + p->exited = false; + + list_push_back(&all_processes, &p->node); + + return p; +} + /* Starts a subprocess with the arguments in the null-terminated argv[] array. * argv[0] is used as the name of the process. Searches the PATH environment * variable to find the program to execute. @@ -135,27 +192,25 @@ process_start(char **argv, struct process **pp) { sigset_t oldsigs; - char *binary; + int nullfd; pid_t pid; + int error; *pp = NULL; - process_init(); COVERAGE_INC(process_start); - - if (VLOG_IS_DBG_ENABLED()) { - char *args = process_escape_args(argv); - VLOG_DBG("starting subprocess: %s", args); - free(args); + error = process_prestart(argv); + if (error) { + return error; } - /* execvp() will search PATH too, but the error in that case is more - * obscure, since it is only reported post-fork. */ - binary = process_search_path(argv[0]); - if (!binary) { - VLOG_ERR("%s not found in PATH", argv[0]); - return ENOENT; + if (n_null_fds) { + nullfd = get_null_fd(); + if (nullfd < 0) { + return -nullfd; + } + } else { + nullfd = -1; } - free(binary); block_sigchld(&oldsigs); pid = fork(); @@ -165,35 +220,29 @@ process_start(char **argv, return errno; } else if (pid) { /* Running in parent process. */ - struct process *p; - const char *slash; - - p = xcalloc(1, sizeof *p); - p->pid = pid; - slash = strrchr(argv[0], '/'); - p->name = xstrdup(slash ? slash + 1 : argv[0]); - p->exited = false; - - list_push_back(&all_processes, &p->node); + *pp = process_register(argv[0], pid); unblock_sigchld(&oldsigs); - - *pp = p; return 0; } else { /* Running in child process. */ int fd_max = get_max_fds(); int fd; + fatal_signal_fork(); unblock_sigchld(&oldsigs); for (fd = 0; fd < fd_max; fd++) { if (is_member(fd, null_fds, n_null_fds)) { - int nullfd = open("/dev/null", O_RDWR); dup2(nullfd, fd); - close(nullfd); - } else if (fd >= 3 && !is_member(fd, keep_fds, n_keep_fds)) { + } else if (fd >= 3 && fd != nullfd + && !is_member(fd, keep_fds, n_keep_fds)) { close(fd); } } + if (nullfd >= 0 + && !is_member(nullfd, keep_fds, n_keep_fds) + && !is_member(nullfd, null_fds, n_null_fds)) { + close(nullfd); + } execvp(argv[0], argv); fprintf(stderr, "execvp(\"%s\") failed: %s\n", argv[0], strerror(errno)); @@ -250,7 +299,7 @@ process_exited(struct process *p) return true; } else { char buf[_POSIX_PIPE_BUF]; - read(fds[0], buf, sizeof buf); + ignore(read(fds[0], buf, sizeof buf)); return false; } } @@ -301,17 +350,10 @@ process_status_msg(int status) struct ds ds = DS_EMPTY_INITIALIZER; if (WIFEXITED(status)) { ds_put_format(&ds, "exit status %d", WEXITSTATUS(status)); - } else if (WIFSIGNALED(status) || WIFSTOPPED(status)) { - int signr = WIFSIGNALED(status) ? WTERMSIG(status) : WSTOPSIG(status); - const char *name = NULL; -#ifdef HAVE_STRSIGNAL - name = strsignal(signr); -#endif - ds_put_format(&ds, "%s by signal %d", - WIFSIGNALED(status) ? "killed" : "stopped", signr); - if (name) { - ds_put_format(&ds, " (%s)", name); - } + } else if (WIFSIGNALED(status)) { + ds_put_format(&ds, "killed (%s)", signal_name(WTERMSIG(status))); + } else if (WIFSTOPPED(status)) { + ds_put_format(&ds, "stopped (%s)", signal_name(WSTOPSIG(status))); } else { ds_put_format(&ds, "terminated abnormally (%x)", status); } @@ -358,13 +400,206 @@ process_search_path(const char *name) return NULL; } +/* process_run_capture() and supporting functions. */ + +struct stream { + size_t max_size; + struct ds log; + int fds[2]; +}; + +static int +stream_open(struct stream *s, size_t max_size) +{ + s->max_size = max_size; + 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) +{ + if (s->fds[0] < 0) { + return; + } + + for (;;) { + char buffer[512]; + int error; + 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 > s->max_size) { + VLOG_WARN("subprocess output overflowed %zu-byte buffer", + s->max_size); + 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 'log_max' 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, + size_t max_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, max_log); + if (error) { + return error; + } + + error = stream_open(&s_stderr, max_log); + if (error) { + stream_close(&s_stdout); + return error; + } + + block_sigchld(&oldsigs); + pid = fork(); + if (pid < 0) { + error = errno; + + 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); + 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(); + 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) +sigchld_handler(int signr OVS_UNUSED) { struct process *p; COVERAGE_INC(process_sigchld); - LIST_FOR_EACH (p, struct process, node, &all_processes) { + LIST_FOR_EACH (p, node, &all_processes) { if (!p->exited) { int retval, status; do { @@ -381,7 +616,7 @@ sigchld_handler(int signr UNUSED) } } } - write(fds[1], "", 1); + ignore(write(fds[1], "", 1)); } static bool @@ -397,21 +632,27 @@ is_member(int x, const int *array, size_t n) return false; } +static bool +sigchld_is_blocked(void) +{ + sigset_t sigs; + + xsigprocmask(SIG_SETMASK, NULL, &sigs); + return sigismember(&sigs, SIGCHLD); +} + static void block_sigchld(sigset_t *oldsigs) { sigset_t sigchld; + sigemptyset(&sigchld); sigaddset(&sigchld, SIGCHLD); - if (sigprocmask(SIG_BLOCK, &sigchld, oldsigs)) { - ovs_fatal(errno, "sigprocmask"); - } + xsigprocmask(SIG_BLOCK, &sigchld, oldsigs); } static void unblock_sigchld(const sigset_t *oldsigs) { - if (sigprocmask(SIG_SETMASK, oldsigs, NULL)) { - ovs_fatal(errno, "sigprocmask"); - } + xsigprocmask(SIG_SETMASK, oldsigs, NULL); }