process: New function process_run_capture().
authorBen Pfaff <blp@nicira.com>
Wed, 15 Jul 2009 19:45:44 +0000 (12:45 -0700)
committerBen Pfaff <blp@nicira.com>
Thu, 16 Jul 2009 16:17:06 +0000 (09:17 -0700)
In an upcoming commit, ovs-brcompatd will need to create a subprocess and
capture both its stdout and stderr separately, which one cannot do with
simple interfaces such as popen().  This function provides that ability.

lib/process.c
lib/process.h

index eec5965..1fe3c12 100644 (file)
@@ -397,6 +397,203 @@ process_search_path(const char *name)
     return NULL;
 }
 \f
+/* process_run_capture() and supporting functions. */
+
+struct stream {
+    struct ds log;
+    int fds[2];
+};
+
+static int
+stream_open(struct stream *s)
+{
+    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)
+{
+    int error = 0;
+
+    if (s->fds[0] < 0) {
+        return;
+    }
+
+    error = 0;
+    for (;;) {
+        char buffer[512];
+        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 > PROCESS_MAX_CAPTURE) {
+            VLOG_WARN("subprocess output overflowed %d-byte buffer",
+                      PROCESS_MAX_CAPTURE);
+            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 PROCESS_MAX_CAPTURE 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,
+                    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);
+    if (error) {
+        return error;
+    }
+
+    error = stream_open(&s_stderr);
+    if (error) {
+        stream_close(&s_stdout);
+        return error;
+    }
+
+    block_sigchld(&oldsigs);
+    fatal_signal_block();
+    pid = fork();
+    if (pid < 0) {
+        int error = errno;
+
+        fatal_signal_unblock();
+        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);
+        fatal_signal_unblock();
+        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();
+        fatal_signal_unblock();
+        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)
 {
index cd5af41..94549f7 100644 (file)
@@ -45,4 +45,8 @@ void process_wait(struct process *);
 
 char *process_search_path(const char *);
 
+#define PROCESS_MAX_CAPTURE 65536
+int process_run_capture(char **argv, char **stdout_log, char **stderr_log,
+                        int *status);
+
 #endif /* process.h */