/*
- * 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 "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_sigchld);
+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. */
/* 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. */
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);
}
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 == '\"') {
}
/* 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);
* 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;
pid_t pid;
int error;
+ assert_single_threaded();
+
*pp = NULL;
COVERAGE_INC(process_start);
error = process_prestart(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. */
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);
}
}
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);
}
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).
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
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)) {
+ 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);
}
return ds_cstr(&ds);
}
+/* Executes periodic maintenance activities required by the process module. */
+void
+process_run(void)
+{
+ 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;
+ }
+ }
+ }
+ }
+}
+
+
/* Causes the next call to poll_block() to wake up when process 'p' has
* exited. */
void
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)
+sigchld_handler(int signr OVS_UNUSED)
{
- 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)
-{
- 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)
-{
- 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));
}