X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=lib%2Fprocess.c;h=313f11fa9d97aca99b790e8a5fac8dc02fe7296e;hb=HEAD;hp=eec5965ec728cb1b357ccaf419f15d1556edeeac;hpb=1fa39e1d50ea5f836d24c91d0651dda315e91c42;p=sliver-openvswitch.git diff --git a/lib/process.c b/lib/process.c index eec5965ec..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,12 +16,12 @@ #include #include "process.h" -#include #include #include #include #include #include +#include #include #include #include @@ -29,21 +29,25 @@ #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. */ @@ -52,44 +56,39 @@ 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 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 * @@ -103,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 == '\"') { @@ -149,19 +148,14 @@ process_prestart(char **argv) } /* 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. */ + * 'pid'. */ static struct process * process_register(const char *name, pid_t pid) { struct process *p; const char *slash; - 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); @@ -172,27 +166,70 @@ process_register(const char *name, pid_t pid) 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 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. + * 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, - const int keep_fds[], size_t n_keep_fds, - const int null_fds[], size_t n_null_fds, - struct process **pp) +process_start(char **argv, struct process **pp) { - sigset_t oldsigs; +#ifndef _WIN32 pid_t pid; int error; + assert_single_threaded(); + *pp = NULL; COVERAGE_INC(process_start); error = process_prestart(argv); @@ -200,19 +237,13 @@ process_start(char **argv, return error; } - block_sigchld(&oldsigs); - fatal_signal_block(); pid = fork(); if (pid < 0) { - fatal_signal_unblock(); - 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. */ *pp = process_register(argv[0], pid); - fatal_signal_unblock(); - unblock_sigchld(&oldsigs); return 0; } else { /* Running in child process. */ @@ -220,24 +251,18 @@ process_start(char **argv, int fd; fatal_signal_fork(); - fatal_signal_unblock(); - unblock_sigchld(&oldsigs); - for (fd = 0; fd < fd_max; fd++) { - if (is_member(fd, null_fds, n_null_fds)) { - /* We can't use get_null_fd() here because we might have - * already closed its fd. */ - 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); - } + 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'. */ @@ -245,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); } @@ -261,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'. */ @@ -285,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). @@ -300,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 @@ -338,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 * @@ -398,69 +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 bool -sigchld_is_blocked(void) -{ - sigset_t sigs; - if (sigprocmask(SIG_SETMASK, NULL, &sigs)) { - ovs_fatal(errno, "sigprocmask"); - } - return sigismember(&sigs, SIGCHLD); -} - -static void -block_sigchld(sigset_t *oldsigs) +sigchld_handler(int signr OVS_UNUSED) { - 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"); - } + ignore(write(fds[1], "", 1)); }