X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=lib%2Fprocess.c;h=313f11fa9d97aca99b790e8a5fac8dc02fe7296e;hb=HEAD;hp=f752a3560c39ee7fbcf2e385c1cfd1c41ce6b8f5;hpb=34e63086edddcae06d7c1a4fa84fec0861e50758;p=sliver-openvswitch.git diff --git a/lib/process.c b/lib/process.c index f752a3560..313f11fa9 100644 --- a/lib/process.c +++ b/lib/process.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2009 Nicira Networks. + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013 Nicira, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,33 +16,38 @@ #include #include "process.h" -#include #include #include #include #include #include +#include #include #include #include #include "coverage.h" #include "dynamic-string.h" +#include "fatal-signal.h" #include "list.h" +#include "ovs-thread.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_start); + struct process { struct list node; char *name; pid_t pid; - /* Modified by signal handler. */ - volatile bool exited; - volatile int status; + /* State. */ + bool exited; + int status; }; /* Pipe used to signal child termination. */ @@ -51,43 +56,39 @@ 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 void sigchld_handler(int signr OVS_UNUSED); /* Initializes the process subsystem (if it is not already initialized). Calls * exit() if initialization fails. * + * This function may not be called after creating any additional threads. + * * 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) { +#ifndef _WIN32 static bool inited; struct sigaction sa; + assert_single_threaded(); 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]); + xpipe_nonblocking(fds); /* 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"); - } + xsigaction(SIGCHLD, &sa, NULL); +#endif } char * @@ -101,7 +102,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,31 +118,17 @@ process_escape_args(char **argv) 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) +/* 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) { - sigset_t oldsigs; char *binary; - pid_t pid; - *pp = NULL; process_init(); - COVERAGE_INC(process_start); + /* Log the process to be started. */ if (VLOG_IS_DBG_ENABLED()) { char *args = process_escape_args(argv); VLOG_DBG("starting subprocess: %s", args); @@ -157,48 +144,125 @@ process_start(char **argv, } free(binary); - block_sigchld(&oldsigs); + return 0; +} + +/* Creates and returns a new struct process with the specified 'name' and + * 'pid'. */ +static struct process * +process_register(const char *name, pid_t pid) +{ + struct process *p; + const char *slash; + + 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; +} + +#ifndef _WIN32 +static bool +rlim_is_finite(rlim_t limit) +{ + if (limit == RLIM_INFINITY) { + return false; + } + +#ifdef RLIM_SAVED_CUR /* FreeBSD 8.0 lacks RLIM_SAVED_CUR. */ + if (limit == RLIM_SAVED_CUR) { + return false; + } +#endif + +#ifdef RLIM_SAVED_MAX /* FreeBSD 8.0 lacks RLIM_SAVED_MAX. */ + if (limit == RLIM_SAVED_MAX) { + return false; + } +#endif + + return true; +} + +/* Returns the maximum valid FD value, plus 1. */ +static int +get_max_fds(void) +{ + static int max_fds; + + if (!max_fds) { + struct rlimit r; + if (!getrlimit(RLIMIT_NOFILE, &r) && rlim_is_finite(r.rlim_cur)) { + max_fds = r.rlim_cur; + } else { + VLOG_WARN("failed to obtain fd limit, defaulting to 1024"); + max_fds = 1024; + } + } + + return max_fds; +} +#endif /* _WIN32 */ + +/* 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. + * + * This function may not be called after creating any additional threads. + * + * All file descriptors are closed before executing the subprocess, except for + * fds 0, 1, and 2. + * + * 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, struct process **pp) +{ +#ifndef _WIN32 + pid_t pid; + int error; + + assert_single_threaded(); + + *pp = NULL; + COVERAGE_INC(process_start); + error = process_prestart(argv); + if (error) { + return error; + } + pid = fork(); if (pid < 0) { - unblock_sigchld(&oldsigs); - VLOG_WARN("fork failed: %s", strerror(errno)); + VLOG_WARN("fork failed: %s", ovs_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; + *pp = process_register(argv[0], pid); 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); - } + fatal_signal_fork(); + for (fd = 3; fd < fd_max; fd++) { + close(fd); } execvp(argv[0], argv); fprintf(stderr, "execvp(\"%s\") failed: %s\n", - argv[0], strerror(errno)); + argv[0], ovs_strerror(errno)); _exit(1); } +#else + *pp = NULL; + return ENOSYS; +#endif } /* Destroys process 'p'. */ @@ -206,12 +270,7 @@ 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); } @@ -222,9 +281,13 @@ process_destroy(struct process *p) int process_kill(const struct process *p, int signr) { +#ifndef _WIN32 return (p->exited ? ESRCH : !kill(p->pid, signr) ? 0 : errno); +#else + return ENOSYS; +#endif } /* Returns the pid of process 'p'. */ @@ -246,13 +309,7 @@ process_name(const struct process *p) 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; - } + return p->exited; } /* Returns process 'p''s exit status, as reported by waitpid(2). @@ -261,36 +318,10 @@ process_exited(struct process *p) int process_status(const struct process *p) { - assert(p->exited); + ovs_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 @@ -299,38 +330,76 @@ char * process_status_msg(int status) { struct ds ds = DS_EMPTY_INITIALIZER; +#ifndef _WIN32 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)) { + char namebuf[SIGNAL_NAME_BUFSIZE]; + + ds_put_format(&ds, "killed (%s)", + signal_name(WTERMSIG(status), namebuf, sizeof namebuf)); + } else if (WIFSTOPPED(status)) { + char namebuf[SIGNAL_NAME_BUFSIZE]; + + ds_put_format(&ds, "stopped (%s)", + signal_name(WSTOPSIG(status), namebuf, sizeof namebuf)); } else { ds_put_format(&ds, "terminated abnormally (%x)", status); } if (WCOREDUMP(status)) { ds_put_cstr(&ds, ", core dumped"); } +#else + ds_put_cstr(&ds, "function not supported."); +#endif return ds_cstr(&ds); } +/* Executes periodic maintenance activities required by the process module. */ +void +process_run(void) +{ +#ifndef _WIN32 + char buf[_POSIX_PIPE_BUF]; + + if (!list_is_empty(&all_processes) && read(fds[0], buf, sizeof buf) > 0) { + struct process *p; + + LIST_FOR_EACH (p, 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) { + VLOG_WARN("waitpid: %s", ovs_strerror(errno)); + p->exited = true; + p->status = -1; + } + } + } + } +#endif +} + + /* Causes the next call to poll_block() to wake up when process 'p' has * exited. */ void process_wait(struct process *p) { +#ifndef _WIN32 if (p->exited) { poll_immediate_wake(); } else { poll_fd_wait(fds[0], POLLIN); } +#else + OVS_NOT_REACHED(); +#endif } char * @@ -359,59 +428,7 @@ process_search_path(const char *name) } 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) +sigchld_handler(int signr OVS_UNUSED) { - if (sigprocmask(SIG_SETMASK, oldsigs, NULL)) { - ovs_fatal(errno, "sigprocmask"); - } + ignore(write(fds[1], "", 1)); }