ofp-util: Work on decoding OF1.1 flow_mods.
[openvswitch] / lib / process.c
index 79b5659f4cbb74fd7d4865d82f71b5447011be41..91dfc064ca8c2ce3fb7e01cc29dce76ec9ef9608 100644 (file)
@@ -1,17 +1,17 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012 Nicira, Inc.
  *
- * 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.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
  *
- * 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.
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #include <config.h>
 #include <unistd.h>
 #include "coverage.h"
 #include "dynamic-string.h"
+#include "fatal-signal.h"
 #include "list.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_run);
+COVERAGE_DEFINE(process_run_capture);
+COVERAGE_DEFINE(process_sigchld);
+COVERAGE_DEFINE(process_start);
+
 struct process {
     struct list node;
     char *name;
@@ -51,9 +58,10 @@ 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 void sigchld_handler(int signr OVS_UNUSED);
 static bool is_member(int x, const int *array, size_t);
 
 /* Initializes the process subsystem (if it is not already initialized).  Calls
@@ -74,20 +82,16 @@ process_init(void)
     inited = true;
 
     /* Create notification pipe. */
-    if (pipe(fds)) {
-        ovs_fatal(errno, "could not create pipe");
-    }
-    set_nonblocking(fds[0]);
-    set_nonblocking(fds[1]);
+    xpipe(fds);
+    xset_nonblocking(fds[0]);
+    xset_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");
-    }
+    xsigaction(SIGCHLD, &sa, NULL);
 }
 
 char *
@@ -101,7 +105,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,6 +121,59 @@ process_escape_args(char **argv)
     return ds_cstr(&ds);
 }
 
+/* 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)
+{
+    char *binary;
+
+    process_init();
+
+    /* Log the process to be started. */
+    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);
+
+    return 0;
+}
+
+/* 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.  */
+static struct process *
+process_register(const char *name, pid_t pid)
+{
+    struct process *p;
+    const char *slash;
+
+    assert(sigchld_is_blocked());
+
+    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;
+}
+
 /* 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.
@@ -135,27 +192,25 @@ process_start(char **argv,
               struct process **pp)
 {
     sigset_t oldsigs;
-    char *binary;
+    int nullfd;
     pid_t pid;
+    int error;
 
     *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);
+    error = process_prestart(argv);
+    if (error) {
+        return error;
     }
 
-    /* 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;
+    if (n_null_fds) {
+        nullfd = get_null_fd();
+        if (nullfd < 0) {
+            return -nullfd;
+        }
+    } else {
+        nullfd = -1;
     }
-    free(binary);
 
     block_sigchld(&oldsigs);
     pid = fork();
@@ -165,35 +220,29 @@ process_start(char **argv,
         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);
+        *pp = process_register(argv[0], pid);
         unblock_sigchld(&oldsigs);
-
-        *pp = p;
         return 0;
     } else {
         /* Running in child process. */
         int fd_max = get_max_fds();
         int fd;
 
+        fatal_signal_fork();
         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)) {
+            } else if (fd >= 3 && fd != nullfd
+                       && !is_member(fd, keep_fds, n_keep_fds)) {
                 close(fd);
             }
         }
+        if (nullfd >= 0
+            && !is_member(nullfd, keep_fds, n_keep_fds)
+            && !is_member(nullfd, null_fds, n_null_fds)) {
+            close(nullfd);
+        }
         execvp(argv[0], argv);
         fprintf(stderr, "execvp(\"%s\") failed: %s\n",
                 argv[0], strerror(errno));
@@ -250,7 +299,7 @@ process_exited(struct process *p)
         return true;
     } else {
         char buf[_POSIX_PIPE_BUF];
-        read(fds[0], buf, sizeof buf);
+        ignore(read(fds[0], buf, sizeof buf));
         return false;
     }
 }
@@ -301,17 +350,10 @@ 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 if (WIFSIGNALED(status)) {
+        ds_put_format(&ds, "killed (%s)", signal_name(WTERMSIG(status)));
+    } else if (WIFSTOPPED(status)) {
+        ds_put_format(&ds, "stopped (%s)", signal_name(WSTOPSIG(status)));
     } else {
         ds_put_format(&ds, "terminated abnormally (%x)", status);
     }
@@ -358,13 +400,206 @@ process_search_path(const char *name)
     return NULL;
 }
 \f
+/* process_run_capture() and supporting functions. */
+
+struct stream {
+    size_t max_size;
+    struct ds log;
+    int fds[2];
+};
+
+static int
+stream_open(struct stream *s, size_t max_size)
+{
+    s->max_size = max_size;
+    ds_init(&s->log);
+    if (pipe(s->fds)) {
+        VLOG_WARN("failed to create pipe: %s", strerror(errno));
+        return errno;
+    }
+    set_nonblocking(s->fds[0]);
+    return 0;
+}
+
+static void
+stream_read(struct stream *s)
+{
+    if (s->fds[0] < 0) {
+        return;
+    }
+
+    for (;;) {
+        char buffer[512];
+        int error;
+        size_t n;
+
+        error = read_fully(s->fds[0], buffer, sizeof buffer, &n);
+        ds_put_buffer(&s->log, buffer, n);
+        if (error) {
+            if (error == EAGAIN || error == EWOULDBLOCK) {
+                return;
+            } else {
+                if (error != EOF) {
+                    VLOG_WARN("error reading subprocess pipe: %s",
+                              strerror(error));
+                }
+                break;
+            }
+        } else if (s->log.length > s->max_size) {
+            VLOG_WARN("subprocess output overflowed %zu-byte buffer",
+                      s->max_size);
+            break;
+        }
+    }
+    close(s->fds[0]);
+    s->fds[0] = -1;
+}
+
+static void
+stream_wait(struct stream *s)
+{
+    if (s->fds[0] >= 0) {
+        poll_fd_wait(s->fds[0], POLLIN);
+    }
+}
+
+static void
+stream_close(struct stream *s)
+{
+    ds_destroy(&s->log);
+    if (s->fds[0] >= 0) {
+        close(s->fds[0]);
+    }
+    if (s->fds[1] >= 0) {
+        close(s->fds[1]);
+    }
+}
+
+/* Starts the process whose arguments are given in the null-terminated array
+ * 'argv' and waits for it to exit.  On success returns 0 and stores the
+ * process exit value (suitable for passing to process_status_msg()) in
+ * '*status'.  On failure, returns a positive errno value and stores 0 in
+ * '*status'.
+ *
+ * If 'stdout_log' is nonnull, then the subprocess's output to stdout (up to a
+ * limit of 'log_max' bytes) is captured in a memory buffer, which
+ * when this function returns 0 is stored as a null-terminated string in
+ * '*stdout_log'.  The caller is responsible for freeing '*stdout_log' (by
+ * passing it to free()).  When this function returns an error, '*stdout_log'
+ * is set to NULL.
+ *
+ * If 'stderr_log' is nonnull, then it is treated like 'stdout_log' except
+ * that it captures the subprocess's output to stderr. */
+int
+process_run_capture(char **argv, char **stdout_log, char **stderr_log,
+                    size_t max_log, int *status)
+{
+    struct stream s_stdout, s_stderr;
+    sigset_t oldsigs;
+    pid_t pid;
+    int error;
+
+    COVERAGE_INC(process_run_capture);
+    if (stdout_log) {
+        *stdout_log = NULL;
+    }
+    if (stderr_log) {
+        *stderr_log = NULL;
+    }
+    *status = 0;
+    error = process_prestart(argv);
+    if (error) {
+        return error;
+    }
+
+    error = stream_open(&s_stdout, max_log);
+    if (error) {
+        return error;
+    }
+
+    error = stream_open(&s_stderr, max_log);
+    if (error) {
+        stream_close(&s_stdout);
+        return error;
+    }
+
+    block_sigchld(&oldsigs);
+    pid = fork();
+    if (pid < 0) {
+        error = errno;
+
+        unblock_sigchld(&oldsigs);
+        VLOG_WARN("fork failed: %s", strerror(error));
+
+        stream_close(&s_stdout);
+        stream_close(&s_stderr);
+        *status = 0;
+        return error;
+    } else if (pid) {
+        /* Running in parent process. */
+        struct process *p;
+
+        p = process_register(argv[0], pid);
+        unblock_sigchld(&oldsigs);
+
+        close(s_stdout.fds[1]);
+        close(s_stderr.fds[1]);
+        while (!process_exited(p)) {
+            stream_read(&s_stdout);
+            stream_read(&s_stderr);
+
+            stream_wait(&s_stdout);
+            stream_wait(&s_stderr);
+            process_wait(p);
+            poll_block();
+        }
+        stream_read(&s_stdout);
+        stream_read(&s_stderr);
+
+        if (stdout_log) {
+            *stdout_log = ds_steal_cstr(&s_stdout.log);
+        }
+        if (stderr_log) {
+            *stderr_log = ds_steal_cstr(&s_stderr.log);
+        }
+
+        stream_close(&s_stdout);
+        stream_close(&s_stderr);
+
+        *status = process_status(p);
+        process_destroy(p);
+        return 0;
+    } else {
+        /* Running in child process. */
+        int max_fds;
+        int i;
+
+        fatal_signal_fork();
+        unblock_sigchld(&oldsigs);
+
+        dup2(get_null_fd(), 0);
+        dup2(s_stdout.fds[1], 1);
+        dup2(s_stderr.fds[1], 2);
+
+        max_fds = get_max_fds();
+        for (i = 3; i < max_fds; i++) {
+            close(i);
+        }
+
+        execvp(argv[0], argv);
+        fprintf(stderr, "execvp(\"%s\") failed: %s\n",
+                argv[0], strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+}
+\f
 static void
-sigchld_handler(int signr UNUSED)
+sigchld_handler(int signr OVS_UNUSED)
 {
     struct process *p;
 
     COVERAGE_INC(process_sigchld);
-    LIST_FOR_EACH (p, struct process, node, &all_processes) {
+    LIST_FOR_EACH (p, node, &all_processes) {
         if (!p->exited) {
             int retval, status;
             do {
@@ -381,7 +616,7 @@ sigchld_handler(int signr UNUSED)
             }
         }
     }
-    write(fds[1], "", 1);
+    ignore(write(fds[1], "", 1));
 }
 
 static bool
@@ -397,21 +632,27 @@ is_member(int x, const int *array, size_t n)
     return false;
 }
 
+static bool
+sigchld_is_blocked(void)
+{
+    sigset_t sigs;
+
+    xsigprocmask(SIG_SETMASK, NULL, &sigs);
+    return sigismember(&sigs, SIGCHLD);
+}
+
 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");
-    }
+    xsigprocmask(SIG_BLOCK, &sigchld, oldsigs);
 }
 
 static void
 unblock_sigchld(const sigset_t *oldsigs)
 {
-    if (sigprocmask(SIG_SETMASK, oldsigs, NULL)) {
-        ovs_fatal(errno, "sigprocmask");
-    }
+    xsigprocmask(SIG_SETMASK, oldsigs, NULL);
 }