/*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012 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 <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;
/* 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
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 *
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 == '\"') {
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.
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();
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)) {
- /* 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)) {
+ } 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));
return true;
} else {
char buf[_POSIX_PIPE_BUF];
- read(fds[0], buf, sizeof buf);
+ ignore(read(fds[0], buf, sizeof buf));
return false;
}
}
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);
}
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
-sigchld_handler(int signr UNUSED)
+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 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 {
}
}
}
- write(fds[1], "", 1);
+ ignore(write(fds[1], "", 1));
}
static bool
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);
}