diff options
author | Ben Pfaff <blp@nicira.com> | 2009-01-13 14:20:42 -0800 |
---|---|---|
committer | Ben Pfaff <blp@nicira.com> | 2009-01-13 14:22:32 -0800 |
commit | a5313a70876e0bd629a8eea71bca1598bf2dc426 (patch) | |
tree | ddd49c73dda232b329e8a05685f1c6bbdf8fc6c0 | |
parent | 377cdbe1022b4557836e4efba2a748d450820ff2 (diff) | |
download | openvswitch-a5313a70876e0bd629a8eea71bca1598bf2dc426.tar.gz |
Crossport lib/process.[ch] from master branch.
This is a dependency of the switch UI improvements wanted on for-nox/0.4.
-rw-r--r-- | lib/automake.mk | 2 | ||||
-rw-r--r-- | lib/process.c | 417 | ||||
-rw-r--r-- | lib/process.h | 62 | ||||
-rw-r--r-- | lib/vlog-modules.def | 1 |
4 files changed, 482 insertions, 0 deletions
diff --git a/lib/automake.mk b/lib/automake.mk index 6b2af618c..e7f804545 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -42,6 +42,8 @@ lib_libopenflow_a_SOURCES = \ lib/poll-loop.h \ lib/port-array.c \ lib/port-array.h \ + lib/process.c \ + lib/process.h \ lib/queue.c \ lib/queue.h \ lib/random.c \ diff --git a/lib/process.c b/lib/process.c new file mode 100644 index 000000000..d787b7bf9 --- /dev/null +++ b/lib/process.c @@ -0,0 +1,417 @@ +/* Copyright (c) 2008, 2009 The Board of Trustees of The Leland Stanford + * Junior University + * + * We are making the OpenFlow specification and associated documentation + * (Software) available for public use and benefit with the expectation + * that others will use, modify and enhance the Software and contribute + * those enhancements back to the community. However, since we would + * like to make the Software available for broadest use, with as few + * restrictions as possible permission is hereby granted, free of + * charge, to any person obtaining a copy of this Software to deal in + * the Software under the copyrights without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * The name and trademarks of copyright holder(s) may NOT be used in + * advertising or publicity pertaining to the Software or any + * derivatives without specific, written prior permission. + */ + +#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 "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); +static bool find_in_path(const char *name); + +/* 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)) { + ofp_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)) { + ofp_fatal(errno, "sigaction(SIGCHLD) failed"); + } +} + +/* 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; + pid_t pid; + + *pp = NULL; + process_init(); + + if (VLOG_IS_DBG_ENABLED()) { + 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); + } + } + VLOG_DBG("starting subprocess: %s", ds_cstr(&ds)); + ds_destroy(&ds); + } + + /* execvp() will search PATH too, but the error in that case is more + * obscure, since it is only reported post-fork. */ + if (!find_in_path(argv[0])) { + VLOG_ERR("%s not found in PATH", argv[0]); + return ENOENT; + } + + 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; + + 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)) { + const char *name = NULL; +#ifdef HAVE_STRSIGNAL + name = strsignal(WTERMSIG(status)); +#endif + ds_put_format(&ds, "killed by signal %d", WTERMSIG(status)); + if (name) { + ds_put_format(&ds, " (%s)", name); + } + } + 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); + } +} + +static void +sigchld_handler(int signr UNUSED) +{ + for (;;) { + struct process *p; + int status; + pid_t pid; + + pid = waitpid(-1, &status, WNOHANG); + if (pid <= 0) { + break; + } + + LIST_FOR_EACH (p, struct process, node, &all_processes) { + if (p->pid == pid) { + p->exited = true; + p->status = status; + break; + } + } + } + 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)) { + ofp_fatal(errno, "sigprocmask"); + } +} + +static void +unblock_sigchld(const sigset_t *oldsigs) +{ + if (sigprocmask(SIG_SETMASK, oldsigs, NULL)) { + ofp_fatal(errno, "sigprocmask"); + } +} + +static bool +find_in_path(const char *name) +{ + char *save_ptr = NULL; + char *path, *dir; + struct stat s; + + if (strchr(name, '/') || !getenv("PATH")) { + return stat(name, &s) == 0; + } + + 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(file); + free(path); + return true; + } + free(file); + } + free(path); + return false; +} diff --git a/lib/process.h b/lib/process.h new file mode 100644 index 000000000..fcdd91303 --- /dev/null +++ b/lib/process.h @@ -0,0 +1,62 @@ +/* Copyright (c) 2008, 2009 The Board of Trustees of The Leland Stanford + * Junior University + * + * We are making the OpenFlow specification and associated documentation + * (Software) available for public use and benefit with the expectation + * that others will use, modify and enhance the Software and contribute + * those enhancements back to the community. However, since we would + * like to make the Software available for broadest use, with as few + * restrictions as possible permission is hereby granted, free of + * charge, to any person obtaining a copy of this Software to deal in + * the Software under the copyrights without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * The name and trademarks of copyright holder(s) may NOT be used in + * advertising or publicity pertaining to the Software or any + * derivatives without specific, written prior permission. + */ + +#ifndef PROCESS_H +#define PROCESS_H 1 + +#include <stdbool.h> +#include <sys/types.h> + +struct process; +void process_init(void); +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 **); +void process_destroy(struct process *); +int process_kill(const struct process *, int signr); + +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); + +pid_t process_pid(const struct process *); +const char *process_name(const struct process *); +bool process_exited(struct process *); +int process_status(const struct process *); +char *process_status_msg(int); + +void process_wait(struct process *); + +#endif /* process.h */ diff --git a/lib/vlog-modules.def b/lib/vlog-modules.def index f736cf913..b9eced10a 100644 --- a/lib/vlog-modules.def +++ b/lib/vlog-modules.def @@ -22,6 +22,7 @@ VLOG_MODULE(netlink) VLOG_MODULE(ofp_discover) VLOG_MODULE(poll_loop) VLOG_MODULE(port_watcher) +VLOG_MODULE(process) VLOG_MODULE(secchan) VLOG_MODULE(rconn) VLOG_MODULE(snat) |