diff options
author | Ben Pfaff <blp@nicira.com> | 2009-07-08 13:19:16 -0700 |
---|---|---|
committer | Ben Pfaff <blp@nicira.com> | 2009-07-08 13:19:16 -0700 |
commit | 064af42167bf4fc9aaea2702d80ce08074b889c0 (patch) | |
tree | efd15a6dc2402eeec273bb34db3b2445687589e5 /lib/process.c |
Import from old repository commit 61ef2b42a9c4ba8e1600f15bb0236765edc2ad45.v0.90.0
Diffstat (limited to 'lib/process.c')
-rw-r--r-- | lib/process.c | 417 |
1 files changed, 417 insertions, 0 deletions
diff --git a/lib/process.c b/lib/process.c new file mode 100644 index 00000000..79b5659f --- /dev/null +++ b/lib/process.c @@ -0,0 +1,417 @@ +/* + * Copyright (c) 2008, 2009 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. + * + * 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. + */ + +#include <config.h> +#include "process.h" +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> +#include "coverage.h" +#include "dynamic-string.h" +#include "list.h" +#include "poll-loop.h" +#include "socket-util.h" +#include "util.h" + +#define THIS_MODULE VLM_process +#include "vlog.h" + +struct process { + struct list node; + char *name; + pid_t pid; + + /* Modified by signal handler. */ + volatile bool exited; + volatile int status; +}; + +/* Pipe used to signal child termination. */ +static int fds[2]; + +/* All processes. */ +static struct list all_processes = LIST_INITIALIZER(&all_processes); + +static void block_sigchld(sigset_t *); +static void unblock_sigchld(const sigset_t *); +static void sigchld_handler(int signr UNUSED); +static bool is_member(int x, const int *array, size_t); + +/* Initializes the process subsystem (if it is not already initialized). Calls + * exit() if initialization fails. + * + * Calling this function is optional; it will be called automatically by + * process_start() if necessary. Calling it explicitly allows the client to + * prevent the process from exiting at an unexpected time. */ +void +process_init(void) +{ + static bool inited; + struct sigaction sa; + + if (inited) { + return; + } + inited = true; + + /* Create notification pipe. */ + if (pipe(fds)) { + ovs_fatal(errno, "could not create pipe"); + } + set_nonblocking(fds[0]); + set_nonblocking(fds[1]); + + /* Set up child termination signal handler. */ + memset(&sa, 0, sizeof sa); + 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"); + } +} + +char * +process_escape_args(char **argv) +{ + struct ds ds = DS_EMPTY_INITIALIZER; + char **argp; + for (argp = argv; *argp; argp++) { + const char *arg = *argp; + const char *p; + if (argp != argv) { + ds_put_char(&ds, ' '); + } + if (arg[strcspn(arg, " \t\r\n\v\\")]) { + ds_put_char(&ds, '"'); + for (p = arg; *p; p++) { + if (*p == '\\' || *p == '\"') { + ds_put_char(&ds, '\\'); + } + ds_put_char(&ds, *p); + } + ds_put_char(&ds, '"'); + } else { + ds_put_cstr(&ds, arg); + } + } + return ds_cstr(&ds); +} + +/* 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. + * + * All file descriptors are closed before executing the subprocess, except for + * fds 0, 1, and 2 and the 'n_keep_fds' fds listed in 'keep_fds'. Also, any of + * the 'n_null_fds' fds listed in 'null_fds' are replaced by /dev/null. + * + * Returns 0 if successful, otherwise a positive errno value indicating the + * error. If successful, '*pp' is assigned a new struct process that may be + * used to query the process's status. On failure, '*pp' is set to NULL. */ +int +process_start(char **argv, + const int keep_fds[], size_t n_keep_fds, + const int null_fds[], size_t n_null_fds, + struct process **pp) +{ + sigset_t oldsigs; + char *binary; + pid_t pid; + + *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); + } + + /* 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); + + block_sigchld(&oldsigs); + pid = fork(); + if (pid < 0) { + unblock_sigchld(&oldsigs); + VLOG_WARN("fork failed: %s", strerror(errno)); + 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); + unblock_sigchld(&oldsigs); + + *pp = p; + return 0; + } else { + /* Running in child process. */ + int fd_max = get_max_fds(); + int fd; + + 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)) { + close(fd); + } + } + execvp(argv[0], argv); + fprintf(stderr, "execvp(\"%s\") failed: %s\n", + argv[0], strerror(errno)); + _exit(1); + } +} + +/* Destroys process 'p'. */ +void +process_destroy(struct process *p) +{ + if (p) { + sigset_t oldsigs; + + block_sigchld(&oldsigs); + list_remove(&p->node); + unblock_sigchld(&oldsigs); + + free(p->name); + free(p); + } +} + +/* Sends signal 'signr' to process 'p'. Returns 0 if successful, otherwise a + * positive errno value. */ +int +process_kill(const struct process *p, int signr) +{ + return (p->exited ? ESRCH + : !kill(p->pid, signr) ? 0 + : errno); +} + +/* Returns the pid of process 'p'. */ +pid_t +process_pid(const struct process *p) +{ + return p->pid; +} + +/* Returns the name of process 'p' (the name passed to process_start() with any + * leading directories stripped). */ +const char * +process_name(const struct process *p) +{ + return p->name; +} + +/* Returns true if process 'p' has exited, false otherwise. */ +bool +process_exited(struct process *p) +{ + if (p->exited) { + return true; + } else { + char buf[_POSIX_PIPE_BUF]; + read(fds[0], buf, sizeof buf); + return false; + } +} + +/* Returns process 'p''s exit status, as reported by waitpid(2). + * process_status(p) may be called only after process_exited(p) has returned + * true. */ +int +process_status(const struct process *p) +{ + assert(p->exited); + return p->status; +} + +int +process_run(char **argv, + const int keep_fds[], size_t n_keep_fds, + const int null_fds[], size_t n_null_fds, + int *status) +{ + struct process *p; + int retval; + + COVERAGE_INC(process_run); + retval = process_start(argv, keep_fds, n_keep_fds, null_fds, n_null_fds, + &p); + if (retval) { + *status = 0; + return retval; + } + + while (!process_exited(p)) { + process_wait(p); + poll_block(); + } + *status = process_status(p); + process_destroy(p); + return 0; +} + +/* Given 'status', which is a process status in the form reported by waitpid(2) + * and returned by process_status(), returns a string describing how the + * process terminated. The caller is responsible for freeing the string when + * it is no longer needed. */ +char * +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 { + ds_put_format(&ds, "terminated abnormally (%x)", status); + } + if (WCOREDUMP(status)) { + ds_put_cstr(&ds, ", core dumped"); + } + return ds_cstr(&ds); +} + +/* Causes the next call to poll_block() to wake up when process 'p' has + * exited. */ +void +process_wait(struct process *p) +{ + if (p->exited) { + poll_immediate_wake(); + } else { + poll_fd_wait(fds[0], POLLIN); + } +} + +char * +process_search_path(const char *name) +{ + char *save_ptr = NULL; + char *path, *dir; + struct stat s; + + if (strchr(name, '/') || !getenv("PATH")) { + return stat(name, &s) == 0 ? xstrdup(name) : NULL; + } + + path = xstrdup(getenv("PATH")); + for (dir = strtok_r(path, ":", &save_ptr); dir; + dir = strtok_r(NULL, ":", &save_ptr)) { + char *file = xasprintf("%s/%s", dir, name); + if (stat(file, &s) == 0) { + free(path); + return file; + } + free(file); + } + free(path); + return NULL; +} + +static void +sigchld_handler(int signr UNUSED) +{ + struct process *p; + + COVERAGE_INC(process_sigchld); + LIST_FOR_EACH (p, struct process, node, &all_processes) { + if (!p->exited) { + int retval, status; + do { + retval = waitpid(p->pid, &status, WNOHANG); + } while (retval == -1 && errno == EINTR); + if (retval == p->pid) { + p->exited = true; + p->status = status; + } else if (retval < 0) { + /* XXX We want to log something but we're in a signal + * handler. */ + p->exited = true; + p->status = -1; + } + } + } + write(fds[1], "", 1); +} + +static bool +is_member(int x, const int *array, size_t n) +{ + size_t i; + + for (i = 0; i < n; i++) { + if (array[i] == x) { + return true; + } + } + return false; +} + +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"); + } +} + +static void +unblock_sigchld(const sigset_t *oldsigs) +{ + if (sigprocmask(SIG_SETMASK, oldsigs, NULL)) { + ovs_fatal(errno, "sigprocmask"); + } +} |