Setting tag sliver-openvswitch-2.2.90-1
[sliver-openvswitch.git] / lib / process.c
index eec5965..313f11f 100644 (file)
@@ -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.
 
 #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/resource.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
 #include <unistd.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. */
@@ -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)
 }
 \f
 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));
 }