#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"
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 OVS_UNUSED);
-static bool is_member(int x, const int *array, size_t);
/* 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;
}
}
/* 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;
- ovs_assert(sigchld_is_blocked());
-
p = xzalloc(sizeof *p);
p->pid = pid;
slash = strrchr(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;
- int nullfd;
pid_t pid;
int error;
+ assert_single_threaded();
+
*pp = NULL;
COVERAGE_INC(process_start);
error = process_prestart(argv);
return error;
}
- if (n_null_fds) {
- nullfd = get_null_fd();
- if (nullfd < 0) {
- return -nullfd;
- }
- } else {
- nullfd = -1;
- }
-
- block_sigchld(&oldsigs);
pid = fork();
if (pid < 0) {
- 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);
- unblock_sigchld(&oldsigs);
return 0;
} else {
/* Running in child process. */
int fd;
fatal_signal_fork();
- unblock_sigchld(&oldsigs);
- for (fd = 0; fd < fd_max; fd++) {
- if (is_member(fd, null_fds, n_null_fds)) {
- dup2(nullfd, fd);
- } 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);
+ 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];
- ignore(read(fds[0], buf, sizeof buf));
- return false;
- }
+ return p->exited;
}
/* Returns process 'p''s exit status, as reported by waitpid(2).
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
static void
sigchld_handler(int signr OVS_UNUSED)
{
- struct process *p;
-
- COVERAGE_INC(process_sigchld);
- 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) {
- /* XXX We want to log something but we're in a signal
- * handler. */
- p->exited = true;
- p->status = -1;
- }
- }
- }
ignore(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;
-
- xpthread_sigmask(SIG_SETMASK, NULL, &sigs);
- return sigismember(&sigs, SIGCHLD);
-}
-
-static void
-block_sigchld(sigset_t *oldsigs)
-{
- sigset_t sigchld;
-
- sigemptyset(&sigchld);
- sigaddset(&sigchld, SIGCHLD);
- xpthread_sigmask(SIG_BLOCK, &sigchld, oldsigs);
-}
-
-static void
-unblock_sigchld(const sigset_t *oldsigs)
-{
- xpthread_sigmask(SIG_SETMASK, oldsigs, NULL);
-}