diff options
author | Justin Dickow <jjdickow@gmail.com> | 2014-10-20 17:44:41 -0400 |
---|---|---|
committer | Justin Dickow <jjdickow@gmail.com> | 2014-10-20 17:44:41 -0400 |
commit | 34e7256493ff0e6594029b9857d7e2aa31f5dbeb (patch) | |
tree | 367306b507c52d3af211533810adbc22004e0192 /src/3rd_party/dbus-1.7.8/dbus/dbus-spawn.c | |
parent | 2eef966e9b5fd4d94dd98820095eb765e200c64b (diff) | |
download | sdl_core-34e7256493ff0e6594029b9857d7e2aa31f5dbeb.tar.gz |
SDL 3.8!
Signed-off-by: Justin Dickow <jjdickow@gmail.com>
Diffstat (limited to 'src/3rd_party/dbus-1.7.8/dbus/dbus-spawn.c')
-rw-r--r-- | src/3rd_party/dbus-1.7.8/dbus/dbus-spawn.c | 1684 |
1 files changed, 1684 insertions, 0 deletions
diff --git a/src/3rd_party/dbus-1.7.8/dbus/dbus-spawn.c b/src/3rd_party/dbus-1.7.8/dbus/dbus-spawn.c new file mode 100644 index 0000000000..d1478f0081 --- /dev/null +++ b/src/3rd_party/dbus-1.7.8/dbus/dbus-spawn.c @@ -0,0 +1,1684 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-spawn.c Wrapper around fork/exec + * + * Copyright (C) 2002, 2003, 2004 Red Hat, Inc. + * Copyright (C) 2003 CodeFactory AB + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> + +#include "dbus-spawn.h" +#include "dbus-sysdeps-unix.h" +#include "dbus-internals.h" +#include "dbus-test.h" +#include "dbus-protocol.h" + +#include <unistd.h> +#include <fcntl.h> +#include <signal.h> +#include <sys/wait.h> +#include <stdlib.h> +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_SYSTEMD +#include <systemd/sd-journal.h> +#endif + +extern char **environ; + +/** + * @addtogroup DBusInternalsUtils + * @{ + */ + +/* + * I'm pretty sure this whole spawn file could be made simpler, + * if you thought about it a bit. + */ + +/** + * Enumeration for status of a read() + */ +typedef enum +{ + READ_STATUS_OK, /**< Read succeeded */ + READ_STATUS_ERROR, /**< Some kind of error */ + READ_STATUS_EOF /**< EOF returned */ +} ReadStatus; + +static ReadStatus +read_ints (int fd, + int *buf, + int n_ints_in_buf, + int *n_ints_read, + DBusError *error) +{ + size_t bytes = 0; + ReadStatus retval; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + retval = READ_STATUS_OK; + + while (TRUE) + { + ssize_t chunk; + size_t to_read; + + to_read = sizeof (int) * n_ints_in_buf - bytes; + + if (to_read == 0) + break; + + again: + + chunk = read (fd, + ((char*)buf) + bytes, + to_read); + + if (chunk < 0 && errno == EINTR) + goto again; + + if (chunk < 0) + { + dbus_set_error (error, + DBUS_ERROR_SPAWN_FAILED, + "Failed to read from child pipe (%s)", + _dbus_strerror (errno)); + + retval = READ_STATUS_ERROR; + break; + } + else if (chunk == 0) + { + retval = READ_STATUS_EOF; + break; /* EOF */ + } + else /* chunk > 0 */ + bytes += chunk; + } + + *n_ints_read = (int)(bytes / sizeof(int)); + + return retval; +} + +static ReadStatus +read_pid (int fd, + pid_t *buf, + DBusError *error) +{ + size_t bytes = 0; + ReadStatus retval; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + retval = READ_STATUS_OK; + + while (TRUE) + { + ssize_t chunk; + size_t to_read; + + to_read = sizeof (pid_t) - bytes; + + if (to_read == 0) + break; + + again: + + chunk = read (fd, + ((char*)buf) + bytes, + to_read); + if (chunk < 0 && errno == EINTR) + goto again; + + if (chunk < 0) + { + dbus_set_error (error, + DBUS_ERROR_SPAWN_FAILED, + "Failed to read from child pipe (%s)", + _dbus_strerror (errno)); + + retval = READ_STATUS_ERROR; + break; + } + else if (chunk == 0) + { + retval = READ_STATUS_EOF; + break; /* EOF */ + } + else /* chunk > 0 */ + bytes += chunk; + } + + return retval; +} + +/* The implementation uses an intermediate child between the main process + * and the grandchild. The grandchild is our spawned process. The intermediate + * child is a babysitter process; it keeps track of when the grandchild + * exits/crashes, and reaps the grandchild. + * + * We automatically reap the babysitter process, killing it if necessary, + * when the DBusBabysitter's refcount goes to zero. + * + * Processes: + * + * main process + * | fork() A + * \- babysitter + * | fork () B + * \- grandchild --> exec --> spawned process + * + * IPC: + * child_err_report_pipe + * /-----------<---------<--------------\ + * | ^ + * v | + * main process babysitter grandchild + * ^ ^ + * v v + * \-------<->-------/ + * babysitter_pipe + * + * child_err_report_pipe is genuinely a pipe. + * The READ_END (also called error_pipe_from_child) is used in the main + * process. The WRITE_END (also called child_err_report_fd) is used in + * the grandchild process. + * + * On failure, the grandchild process sends CHILD_EXEC_FAILED + errno. + * On success, the pipe just closes (because it's close-on-exec) without + * sending any bytes. + * + * babysitter_pipe is mis-named: it's really a bidirectional socketpair. + * The [0] end (also called socket_to_babysitter) is used in the main + * process, the [1] end (also called parent_pipe) is used in the babysitter. + * + * If the fork() labelled B in the diagram above fails, the babysitter sends + * CHILD_FORK_FAILED + errno. + * On success, the babysitter sends CHILD_PID + the grandchild's pid. + * On SIGCHLD, the babysitter sends CHILD_EXITED + the exit status. + * The main process doesn't explicitly send anything, but when it exits, + * the babysitter gets POLLHUP or POLLERR. + */ + +/* Messages from children to parents */ +enum +{ + CHILD_EXITED, /* This message is followed by the exit status int */ + CHILD_FORK_FAILED, /* Followed by errno */ + CHILD_EXEC_FAILED, /* Followed by errno */ + CHILD_PID /* Followed by pid_t */ +}; + +/** + * Babysitter implementation details + */ +struct DBusBabysitter +{ + int refcount; /**< Reference count */ + + char *log_name; /**< the name under which to log messages about this + process being spawned */ + + int socket_to_babysitter; /**< Connection to the babysitter process */ + int error_pipe_from_child; /**< Connection to the process that does the exec() */ + + pid_t sitter_pid; /**< PID Of the babysitter */ + pid_t grandchild_pid; /**< PID of the grandchild */ + + DBusWatchList *watches; /**< Watches */ + + DBusWatch *error_watch; /**< Error pipe watch */ + DBusWatch *sitter_watch; /**< Sitter pipe watch */ + + DBusBabysitterFinishedFunc finished_cb; + void *finished_data; + + int errnum; /**< Error number */ + int status; /**< Exit status code */ + unsigned int have_child_status : 1; /**< True if child status has been reaped */ + unsigned int have_fork_errnum : 1; /**< True if we have an error code from fork() */ + unsigned int have_exec_errnum : 1; /**< True if we have an error code from exec() */ +}; + +static DBusBabysitter* +_dbus_babysitter_new (void) +{ + DBusBabysitter *sitter; + + sitter = dbus_new0 (DBusBabysitter, 1); + if (sitter == NULL) + return NULL; + + sitter->refcount = 1; + + sitter->socket_to_babysitter = -1; + sitter->error_pipe_from_child = -1; + + sitter->sitter_pid = -1; + sitter->grandchild_pid = -1; + + sitter->watches = _dbus_watch_list_new (); + if (sitter->watches == NULL) + goto failed; + + return sitter; + + failed: + _dbus_babysitter_unref (sitter); + return NULL; +} + +/** + * Increment the reference count on the babysitter object. + * + * @param sitter the babysitter + * @returns the babysitter + */ +DBusBabysitter * +_dbus_babysitter_ref (DBusBabysitter *sitter) +{ + _dbus_assert (sitter != NULL); + _dbus_assert (sitter->refcount > 0); + + sitter->refcount += 1; + + return sitter; +} + +static void close_socket_to_babysitter (DBusBabysitter *sitter); +static void close_error_pipe_from_child (DBusBabysitter *sitter); + +/** + * Decrement the reference count on the babysitter object. + * When the reference count of the babysitter object reaches + * zero, the babysitter is killed and the child that was being + * babysat gets emancipated. + * + * @param sitter the babysitter + */ +void +_dbus_babysitter_unref (DBusBabysitter *sitter) +{ + _dbus_assert (sitter != NULL); + _dbus_assert (sitter->refcount > 0); + + sitter->refcount -= 1; + if (sitter->refcount == 0) + { + /* If we haven't forked other babysitters + * since this babysitter and socket were + * created then this close will cause the + * babysitter to wake up from poll with + * a hangup and then the babysitter will + * quit itself. + */ + close_socket_to_babysitter (sitter); + + close_error_pipe_from_child (sitter); + + if (sitter->sitter_pid > 0) + { + int status; + int ret; + + /* It's possible the babysitter died on its own above + * from the close, or was killed randomly + * by some other process, so first try to reap it + */ + ret = waitpid (sitter->sitter_pid, &status, WNOHANG); + + /* If we couldn't reap the child then kill it, and + * try again + */ + if (ret == 0) + kill (sitter->sitter_pid, SIGKILL); + + if (ret == 0) + { + do + { + ret = waitpid (sitter->sitter_pid, &status, 0); + } + while (_DBUS_UNLIKELY (ret < 0 && errno == EINTR)); + } + + if (ret < 0) + { + if (errno == ECHILD) + _dbus_warn ("Babysitter process not available to be reaped; should not happen\n"); + else + _dbus_warn ("Unexpected error %d in waitpid() for babysitter: %s\n", + errno, _dbus_strerror (errno)); + } + else + { + _dbus_verbose ("Reaped %ld, waiting for babysitter %ld\n", + (long) ret, (long) sitter->sitter_pid); + + if (WIFEXITED (sitter->status)) + _dbus_verbose ("Babysitter exited with status %d\n", + WEXITSTATUS (sitter->status)); + else if (WIFSIGNALED (sitter->status)) + _dbus_verbose ("Babysitter received signal %d\n", + WTERMSIG (sitter->status)); + else + _dbus_verbose ("Babysitter exited abnormally\n"); + } + + sitter->sitter_pid = -1; + } + + if (sitter->watches) + _dbus_watch_list_free (sitter->watches); + + dbus_free (sitter->log_name); + + dbus_free (sitter); + } +} + +static ReadStatus +read_data (DBusBabysitter *sitter, + int fd) +{ + int what; + int got; + DBusError error = DBUS_ERROR_INIT; + ReadStatus r; + + r = read_ints (fd, &what, 1, &got, &error); + + switch (r) + { + case READ_STATUS_ERROR: + _dbus_warn ("Failed to read data from fd %d: %s\n", fd, error.message); + dbus_error_free (&error); + return r; + + case READ_STATUS_EOF: + return r; + + case READ_STATUS_OK: + break; + } + + if (got == 1) + { + switch (what) + { + case CHILD_EXITED: + case CHILD_FORK_FAILED: + case CHILD_EXEC_FAILED: + { + int arg; + + r = read_ints (fd, &arg, 1, &got, &error); + + switch (r) + { + case READ_STATUS_ERROR: + _dbus_warn ("Failed to read arg from fd %d: %s\n", fd, error.message); + dbus_error_free (&error); + return r; + case READ_STATUS_EOF: + return r; + case READ_STATUS_OK: + break; + } + + if (got == 1) + { + if (what == CHILD_EXITED) + { + sitter->have_child_status = TRUE; + sitter->status = arg; + sitter->errnum = 0; + _dbus_verbose ("recorded child status exited = %d signaled = %d exitstatus = %d termsig = %d\n", + WIFEXITED (sitter->status), WIFSIGNALED (sitter->status), + WEXITSTATUS (sitter->status), WTERMSIG (sitter->status)); + } + else if (what == CHILD_FORK_FAILED) + { + sitter->have_fork_errnum = TRUE; + sitter->errnum = arg; + _dbus_verbose ("recorded fork errnum %d\n", sitter->errnum); + } + else if (what == CHILD_EXEC_FAILED) + { + sitter->have_exec_errnum = TRUE; + sitter->errnum = arg; + _dbus_verbose ("recorded exec errnum %d\n", sitter->errnum); + } + } + } + break; + case CHILD_PID: + { + pid_t pid = -1; + + r = read_pid (fd, &pid, &error); + + switch (r) + { + case READ_STATUS_ERROR: + _dbus_warn ("Failed to read PID from fd %d: %s\n", fd, error.message); + dbus_error_free (&error); + return r; + case READ_STATUS_EOF: + return r; + case READ_STATUS_OK: + break; + } + + sitter->grandchild_pid = pid; + + _dbus_verbose ("recorded grandchild pid %d\n", sitter->grandchild_pid); + } + break; + default: + _dbus_warn ("Unknown message received from babysitter process\n"); + break; + } + } + + return r; +} + +static void +close_socket_to_babysitter (DBusBabysitter *sitter) +{ + _dbus_verbose ("Closing babysitter\n"); + + if (sitter->sitter_watch != NULL) + { + _dbus_assert (sitter->watches != NULL); + _dbus_watch_list_remove_watch (sitter->watches, sitter->sitter_watch); + _dbus_watch_invalidate (sitter->sitter_watch); + _dbus_watch_unref (sitter->sitter_watch); + sitter->sitter_watch = NULL; + } + + if (sitter->socket_to_babysitter >= 0) + { + _dbus_close_socket (sitter->socket_to_babysitter, NULL); + sitter->socket_to_babysitter = -1; + } +} + +static void +close_error_pipe_from_child (DBusBabysitter *sitter) +{ + _dbus_verbose ("Closing child error\n"); + + if (sitter->error_watch != NULL) + { + _dbus_assert (sitter->watches != NULL); + _dbus_watch_list_remove_watch (sitter->watches, sitter->error_watch); + _dbus_watch_invalidate (sitter->error_watch); + _dbus_watch_unref (sitter->error_watch); + sitter->error_watch = NULL; + } + + if (sitter->error_pipe_from_child >= 0) + { + _dbus_close_socket (sitter->error_pipe_from_child, NULL); + sitter->error_pipe_from_child = -1; + } +} + +static void +handle_babysitter_socket (DBusBabysitter *sitter, + int revents) +{ + /* Even if we have POLLHUP, we want to keep reading + * data until POLLIN goes away; so this function only + * looks at HUP/ERR if no IN is set. + */ + if (revents & _DBUS_POLLIN) + { + _dbus_verbose ("Reading data from babysitter\n"); + if (read_data (sitter, sitter->socket_to_babysitter) != READ_STATUS_OK) + close_socket_to_babysitter (sitter); + } + else if (revents & (_DBUS_POLLERR | _DBUS_POLLHUP)) + { + close_socket_to_babysitter (sitter); + } +} + +static void +handle_error_pipe (DBusBabysitter *sitter, + int revents) +{ + if (revents & _DBUS_POLLIN) + { + _dbus_verbose ("Reading data from child error\n"); + if (read_data (sitter, sitter->error_pipe_from_child) != READ_STATUS_OK) + close_error_pipe_from_child (sitter); + } + else if (revents & (_DBUS_POLLERR | _DBUS_POLLHUP)) + { + close_error_pipe_from_child (sitter); + } +} + +/* returns whether there were any poll events handled */ +static dbus_bool_t +babysitter_iteration (DBusBabysitter *sitter, + dbus_bool_t block) +{ + DBusPollFD fds[2]; + int i; + dbus_bool_t descriptors_ready; + + descriptors_ready = FALSE; + + i = 0; + + if (sitter->error_pipe_from_child >= 0) + { + fds[i].fd = sitter->error_pipe_from_child; + fds[i].events = _DBUS_POLLIN; + fds[i].revents = 0; + ++i; + } + + if (sitter->socket_to_babysitter >= 0) + { + fds[i].fd = sitter->socket_to_babysitter; + fds[i].events = _DBUS_POLLIN; + fds[i].revents = 0; + ++i; + } + + if (i > 0) + { + int ret; + + do + { + ret = _dbus_poll (fds, i, 0); + } + while (ret < 0 && errno == EINTR); + + if (ret == 0 && block) + { + do + { + ret = _dbus_poll (fds, i, -1); + } + while (ret < 0 && errno == EINTR); + } + + if (ret > 0) + { + descriptors_ready = TRUE; + + while (i > 0) + { + --i; + if (fds[i].fd == sitter->error_pipe_from_child) + handle_error_pipe (sitter, fds[i].revents); + else if (fds[i].fd == sitter->socket_to_babysitter) + handle_babysitter_socket (sitter, fds[i].revents); + } + } + } + + return descriptors_ready; +} + +/** + * Macro returns #TRUE if the babysitter still has live sockets open to the + * babysitter child or the grandchild. + */ +#define LIVE_CHILDREN(sitter) ((sitter)->socket_to_babysitter >= 0 || (sitter)->error_pipe_from_child >= 0) + +/** + * Blocks until the babysitter process gives us the PID of the spawned grandchild, + * then kills the spawned grandchild. + * + * @param sitter the babysitter object + */ +void +_dbus_babysitter_kill_child (DBusBabysitter *sitter) +{ + /* be sure we have the PID of the child */ + while (LIVE_CHILDREN (sitter) && + sitter->grandchild_pid == -1) + babysitter_iteration (sitter, TRUE); + + _dbus_verbose ("Got child PID %ld for killing\n", + (long) sitter->grandchild_pid); + + if (sitter->grandchild_pid == -1) + return; /* child is already dead, or we're so hosed we'll never recover */ + + kill (sitter->grandchild_pid, SIGKILL); +} + +/** + * Checks whether the child has exited, without blocking. + * + * @param sitter the babysitter + */ +dbus_bool_t +_dbus_babysitter_get_child_exited (DBusBabysitter *sitter) +{ + + /* Be sure we're up-to-date */ + while (LIVE_CHILDREN (sitter) && + babysitter_iteration (sitter, FALSE)) + ; + + /* We will have exited the babysitter when the child has exited */ + return sitter->socket_to_babysitter < 0; +} + +/** + * Gets the exit status of the child. We do this so implementation specific + * detail is not cluttering up dbus, for example the system launcher code. + * This can only be called if the child has exited, i.e. call + * _dbus_babysitter_get_child_exited(). It returns FALSE if the child + * did not return a status code, e.g. because the child was signaled + * or we failed to ever launch the child in the first place. + * + * @param sitter the babysitter + * @param status the returned status code + * @returns #FALSE on failure + */ +dbus_bool_t +_dbus_babysitter_get_child_exit_status (DBusBabysitter *sitter, + int *status) +{ + if (!_dbus_babysitter_get_child_exited (sitter)) + _dbus_assert_not_reached ("Child has not exited"); + + if (!sitter->have_child_status || + !(WIFEXITED (sitter->status))) + return FALSE; + + *status = WEXITSTATUS (sitter->status); + return TRUE; +} + +/** + * Sets the #DBusError with an explanation of why the spawned + * child process exited (on a signal, or whatever). If + * the child process has not exited, does nothing (error + * will remain unset). + * + * @param sitter the babysitter + * @param error an error to fill in + */ +void +_dbus_babysitter_set_child_exit_error (DBusBabysitter *sitter, + DBusError *error) +{ + if (!_dbus_babysitter_get_child_exited (sitter)) + return; + + /* Note that if exec fails, we will also get a child status + * from the babysitter saying the child exited, + * so we need to give priority to the exec error + */ + if (sitter->have_exec_errnum) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_EXEC_FAILED, + "Failed to execute program %s: %s", + sitter->log_name, _dbus_strerror (sitter->errnum)); + } + else if (sitter->have_fork_errnum) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, + "Failed to fork a new process %s: %s", + sitter->log_name, _dbus_strerror (sitter->errnum)); + } + else if (sitter->have_child_status) + { + if (WIFEXITED (sitter->status)) + dbus_set_error (error, DBUS_ERROR_SPAWN_CHILD_EXITED, + "Process %s exited with status %d", + sitter->log_name, WEXITSTATUS (sitter->status)); + else if (WIFSIGNALED (sitter->status)) + dbus_set_error (error, DBUS_ERROR_SPAWN_CHILD_SIGNALED, + "Process %s received signal %d", + sitter->log_name, WTERMSIG (sitter->status)); + else + dbus_set_error (error, DBUS_ERROR_FAILED, + "Process %s exited abnormally", + sitter->log_name); + } + else + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Process %s exited, reason unknown", + sitter->log_name); + } +} + +/** + * Sets watch functions to notify us when the + * babysitter object needs to read/write file descriptors. + * + * @param sitter the babysitter + * @param add_function function to begin monitoring a new descriptor. + * @param remove_function function to stop monitoring a descriptor. + * @param toggled_function function to notify when the watch is enabled/disabled + * @param data data to pass to add_function and remove_function. + * @param free_data_function function to be called to free the data. + * @returns #FALSE on failure (no memory) + */ +dbus_bool_t +_dbus_babysitter_set_watch_functions (DBusBabysitter *sitter, + DBusAddWatchFunction add_function, + DBusRemoveWatchFunction remove_function, + DBusWatchToggledFunction toggled_function, + void *data, + DBusFreeFunction free_data_function) +{ + return _dbus_watch_list_set_functions (sitter->watches, + add_function, + remove_function, + toggled_function, + data, + free_data_function); +} + +static dbus_bool_t +handle_watch (DBusWatch *watch, + unsigned int condition, + void *data) +{ + DBusBabysitter *sitter = _dbus_babysitter_ref (data); + int revents; + int fd; + + revents = 0; + if (condition & DBUS_WATCH_READABLE) + revents |= _DBUS_POLLIN; + if (condition & DBUS_WATCH_ERROR) + revents |= _DBUS_POLLERR; + if (condition & DBUS_WATCH_HANGUP) + revents |= _DBUS_POLLHUP; + + fd = dbus_watch_get_socket (watch); + + if (fd == sitter->error_pipe_from_child) + handle_error_pipe (sitter, revents); + else if (fd == sitter->socket_to_babysitter) + handle_babysitter_socket (sitter, revents); + + while (LIVE_CHILDREN (sitter) && + babysitter_iteration (sitter, FALSE)) + ; + + /* fd.o #32992: if the handle_* methods closed their sockets, they previously + * didn't always remove the watches. Check that we don't regress. */ + _dbus_assert (sitter->socket_to_babysitter != -1 || sitter->sitter_watch == NULL); + _dbus_assert (sitter->error_pipe_from_child != -1 || sitter->error_watch == NULL); + + if (_dbus_babysitter_get_child_exited (sitter) && + sitter->finished_cb != NULL) + { + sitter->finished_cb (sitter, sitter->finished_data); + sitter->finished_cb = NULL; + } + + _dbus_babysitter_unref (sitter); + return TRUE; +} + +/** Helps remember which end of the pipe is which */ +#define READ_END 0 +/** Helps remember which end of the pipe is which */ +#define WRITE_END 1 + + +/* Avoids a danger in re-entrant situations (calling close() + * on a file descriptor twice, and another module has + * re-opened it since the first close). + * + * This previously claimed to be relevant for threaded situations, but by + * trivial inspection, it is not thread-safe. It doesn't actually + * matter, since this module is only used in the -util variant of the + * library, which is only used in single-threaded situations. + */ +static int +close_and_invalidate (int *fd) +{ + int ret; + + if (*fd < 0) + return -1; + else + { + ret = _dbus_close_socket (*fd, NULL); + *fd = -1; + } + + return ret; +} + +static dbus_bool_t +make_pipe (int p[2], + DBusError *error) +{ + int retval; + +#ifdef HAVE_PIPE2 + dbus_bool_t cloexec_done; + + retval = pipe2 (p, O_CLOEXEC); + cloexec_done = retval >= 0; + + /* Check if kernel seems to be too old to know pipe2(). We assume + that if pipe2 is available, O_CLOEXEC is too. */ + if (retval < 0 && errno == ENOSYS) +#endif + { + retval = pipe(p); + } + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (retval < 0) + { + dbus_set_error (error, + DBUS_ERROR_SPAWN_FAILED, + "Failed to create pipe for communicating with child process (%s)", + _dbus_strerror (errno)); + return FALSE; + } + +#ifdef HAVE_PIPE2 + if (!cloexec_done) +#endif + { + _dbus_fd_set_close_on_exec (p[0]); + _dbus_fd_set_close_on_exec (p[1]); + } + + return TRUE; +} + +static void +do_write (int fd, const void *buf, size_t count) +{ + size_t bytes_written; + int ret; + + bytes_written = 0; + + again: + + ret = write (fd, ((const char*)buf) + bytes_written, count - bytes_written); + + if (ret < 0) + { + if (errno == EINTR) + goto again; + else + { + _dbus_warn ("Failed to write data to pipe!\n"); + exit (1); /* give up, we suck */ + } + } + else + bytes_written += ret; + + if (bytes_written < count) + goto again; +} + +static void +write_err_and_exit (int fd, int msg) +{ + int en = errno; + + do_write (fd, &msg, sizeof (msg)); + do_write (fd, &en, sizeof (en)); + + exit (1); +} + +static void +write_pid (int fd, pid_t pid) +{ + int msg = CHILD_PID; + + do_write (fd, &msg, sizeof (msg)); + do_write (fd, &pid, sizeof (pid)); +} + +static void +write_status_and_exit (int fd, int status) +{ + int msg = CHILD_EXITED; + + do_write (fd, &msg, sizeof (msg)); + do_write (fd, &status, sizeof (status)); + + exit (0); +} + +static void +do_exec (int child_err_report_fd, + char **argv, + char **envp, + DBusSpawnChildSetupFunc child_setup, + void *user_data) +{ +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + int i, max_open; +#endif + + _dbus_verbose_reset (); + _dbus_verbose ("Child process has PID " DBUS_PID_FORMAT "\n", + _dbus_getpid ()); + + if (child_setup) + (* child_setup) (user_data); + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + max_open = sysconf (_SC_OPEN_MAX); + + for (i = 3; i < max_open; i++) + { + int retval; + + if (i == child_err_report_fd) + continue; + + retval = fcntl (i, F_GETFD); + + if (retval != -1 && !(retval & FD_CLOEXEC)) + _dbus_warn ("Fd %d did not have the close-on-exec flag set!\n", i); + } +#endif + + if (envp == NULL) + { + _dbus_assert (environ != NULL); + + envp = environ; + } + + execve (argv[0], argv, envp); + + /* Exec failed */ + write_err_and_exit (child_err_report_fd, + CHILD_EXEC_FAILED); +} + +static void +check_babysit_events (pid_t grandchild_pid, + int parent_pipe, + int revents) +{ + pid_t ret; + int status; + + do + { + ret = waitpid (grandchild_pid, &status, WNOHANG); + /* The man page says EINTR can't happen with WNOHANG, + * but there are reports of it (maybe only with valgrind?) + */ + } + while (ret < 0 && errno == EINTR); + + if (ret == 0) + { + _dbus_verbose ("no child exited\n"); + + ; /* no child exited */ + } + else if (ret < 0) + { + /* This isn't supposed to happen. */ + _dbus_warn ("unexpected waitpid() failure in check_babysit_events(): %s\n", + _dbus_strerror (errno)); + exit (1); + } + else if (ret == grandchild_pid) + { + /* Child exited */ + _dbus_verbose ("reaped child pid %ld\n", (long) ret); + + write_status_and_exit (parent_pipe, status); + } + else + { + _dbus_warn ("waitpid() reaped pid %d that we've never heard of\n", + (int) ret); + exit (1); + } + + if (revents & _DBUS_POLLIN) + { + _dbus_verbose ("babysitter got POLLIN from parent pipe\n"); + } + + if (revents & (_DBUS_POLLERR | _DBUS_POLLHUP)) + { + /* Parent is gone, so we just exit */ + _dbus_verbose ("babysitter got POLLERR or POLLHUP from parent\n"); + exit (0); + } +} + +static int babysit_sigchld_pipe = -1; + +static void +babysit_signal_handler (int signo) +{ + char b = '\0'; + again: + if (write (babysit_sigchld_pipe, &b, 1) <= 0) + if (errno == EINTR) + goto again; +} + +static void +babysit (pid_t grandchild_pid, + int parent_pipe) +{ + int sigchld_pipe[2]; + + /* We don't exec, so we keep parent state, such as the pid that + * _dbus_verbose() uses. Reset the pid here. + */ + _dbus_verbose_reset (); + + /* I thought SIGCHLD would just wake up the poll, but + * that didn't seem to work, so added this pipe. + * Probably the pipe is more likely to work on busted + * operating systems anyhow. + */ + if (pipe (sigchld_pipe) < 0) + { + _dbus_warn ("Not enough file descriptors to create pipe in babysitter process\n"); + exit (1); + } + + babysit_sigchld_pipe = sigchld_pipe[WRITE_END]; + + _dbus_set_signal_handler (SIGCHLD, babysit_signal_handler); + + write_pid (parent_pipe, grandchild_pid); + + check_babysit_events (grandchild_pid, parent_pipe, 0); + + while (TRUE) + { + DBusPollFD pfds[2]; + + pfds[0].fd = parent_pipe; + pfds[0].events = _DBUS_POLLIN; + pfds[0].revents = 0; + + pfds[1].fd = sigchld_pipe[READ_END]; + pfds[1].events = _DBUS_POLLIN; + pfds[1].revents = 0; + + if (_dbus_poll (pfds, _DBUS_N_ELEMENTS (pfds), -1) < 0 && errno != EINTR) + { + _dbus_warn ("_dbus_poll() error: %s\n", strerror (errno)); + exit (1); + } + + if (pfds[0].revents != 0) + { + check_babysit_events (grandchild_pid, parent_pipe, pfds[0].revents); + } + else if (pfds[1].revents & _DBUS_POLLIN) + { + char b; + if (read (sigchld_pipe[READ_END], &b, 1) == -1) + { + /* ignore */ + } + /* do waitpid check */ + check_babysit_events (grandchild_pid, parent_pipe, 0); + } + } + + exit (1); +} + +/** + * Spawns a new process. The child_setup + * function is passed the given user_data and is run in the child + * just before calling exec(). + * + * Also creates a "babysitter" which tracks the status of the + * child process, advising the parent if the child exits. + * If the spawn fails, no babysitter is created. + * If sitter_p is #NULL, no babysitter is kept. + * + * @param sitter_p return location for babysitter or #NULL + * @log_name the name under which to log messages about this process being spawned + * @param argv the executable and arguments + * @param env the environment, or #NULL to copy the parent's + * @param child_setup function to call in child pre-exec() + * @param user_data user data for setup function + * @param error error object to be filled in if function fails + * @returns #TRUE on success, #FALSE if error is filled in + */ +dbus_bool_t +_dbus_spawn_async_with_babysitter (DBusBabysitter **sitter_p, + const char *log_name, + char **argv, + char **env, + DBusSpawnChildSetupFunc child_setup, + void *user_data, + DBusError *error) +{ + DBusBabysitter *sitter; + int child_err_report_pipe[2] = { -1, -1 }; + int babysitter_pipe[2] = { -1, -1 }; + pid_t pid; +#ifdef HAVE_SYSTEMD + int fd_out = -1; + int fd_err = -1; +#endif + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + _dbus_assert (argv[0] != NULL); + + if (sitter_p != NULL) + *sitter_p = NULL; + + sitter = NULL; + + sitter = _dbus_babysitter_new (); + if (sitter == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return FALSE; + } + + sitter->log_name = _dbus_strdup (log_name); + if (sitter->log_name == NULL && log_name != NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto cleanup_and_fail; + } + + if (sitter->log_name == NULL) + sitter->log_name = _dbus_strdup (argv[0]); + + if (sitter->log_name == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto cleanup_and_fail; + } + + if (!make_pipe (child_err_report_pipe, error)) + goto cleanup_and_fail; + + if (!_dbus_full_duplex_pipe (&babysitter_pipe[0], &babysitter_pipe[1], TRUE, error)) + goto cleanup_and_fail; + + /* Setting up the babysitter is only useful in the parent, + * but we don't want to run out of memory and fail + * after we've already forked, since then we'd leak + * child processes everywhere. + */ + sitter->error_watch = _dbus_watch_new (child_err_report_pipe[READ_END], + DBUS_WATCH_READABLE, + TRUE, handle_watch, sitter, NULL); + if (sitter->error_watch == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto cleanup_and_fail; + } + + if (!_dbus_watch_list_add_watch (sitter->watches, sitter->error_watch)) + { + /* we need to free it early so the destructor won't try to remove it + * without it having been added, which DBusLoop doesn't allow */ + _dbus_watch_invalidate (sitter->error_watch); + _dbus_watch_unref (sitter->error_watch); + sitter->error_watch = NULL; + + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto cleanup_and_fail; + } + + sitter->sitter_watch = _dbus_watch_new (babysitter_pipe[0], + DBUS_WATCH_READABLE, + TRUE, handle_watch, sitter, NULL); + if (sitter->sitter_watch == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto cleanup_and_fail; + } + + if (!_dbus_watch_list_add_watch (sitter->watches, sitter->sitter_watch)) + { + /* we need to free it early so the destructor won't try to remove it + * without it having been added, which DBusLoop doesn't allow */ + _dbus_watch_invalidate (sitter->sitter_watch); + _dbus_watch_unref (sitter->sitter_watch); + sitter->sitter_watch = NULL; + + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto cleanup_and_fail; + } + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + +#ifdef HAVE_SYSTEMD + /* This may fail, but it's not critical. + * In particular, if we were compiled with journald support but are now + * running on a non-systemd system, this is going to fail, so we + * have to cope gracefully. */ + fd_out = sd_journal_stream_fd (sitter->log_name, LOG_INFO, FALSE); + fd_err = sd_journal_stream_fd (sitter->log_name, LOG_WARNING, FALSE); +#endif + + pid = fork (); + + if (pid < 0) + { + dbus_set_error (error, + DBUS_ERROR_SPAWN_FORK_FAILED, + "Failed to fork (%s)", + _dbus_strerror (errno)); + goto cleanup_and_fail; + } + else if (pid == 0) + { + /* Immediate child, this is the babysitter process. */ + int grandchild_pid; + + /* Be sure we crash if the parent exits + * and we write to the err_report_pipe + */ + signal (SIGPIPE, SIG_DFL); + + /* Close the parent's end of the pipes. */ + close_and_invalidate (&child_err_report_pipe[READ_END]); + close_and_invalidate (&babysitter_pipe[0]); + + /* Create the child that will exec () */ + grandchild_pid = fork (); + + if (grandchild_pid < 0) + { + write_err_and_exit (babysitter_pipe[1], + CHILD_FORK_FAILED); + _dbus_assert_not_reached ("Got to code after write_err_and_exit()"); + } + else if (grandchild_pid == 0) + { + /* Go back to ignoring SIGPIPE, since it's evil + */ + signal (SIGPIPE, SIG_IGN); + + close_and_invalidate (&babysitter_pipe[1]); +#ifdef HAVE_SYSTEMD + /* log to systemd journal if possible */ + if (fd_out >= 0) + dup2 (fd_out, STDOUT_FILENO); + if (fd_err >= 0) + dup2 (fd_err, STDERR_FILENO); + close_and_invalidate (&fd_out); + close_and_invalidate (&fd_err); +#endif + do_exec (child_err_report_pipe[WRITE_END], + argv, + env, + child_setup, user_data); + _dbus_assert_not_reached ("Got to code after exec() - should have exited on error"); + } + else + { + close_and_invalidate (&child_err_report_pipe[WRITE_END]); +#ifdef HAVE_SYSTEMD + close_and_invalidate (&fd_out); + close_and_invalidate (&fd_err); +#endif + babysit (grandchild_pid, babysitter_pipe[1]); + _dbus_assert_not_reached ("Got to code after babysit()"); + } + } + else + { + /* Close the uncared-about ends of the pipes */ + close_and_invalidate (&child_err_report_pipe[WRITE_END]); + close_and_invalidate (&babysitter_pipe[1]); +#ifdef HAVE_SYSTEMD + close_and_invalidate (&fd_out); + close_and_invalidate (&fd_err); +#endif + + sitter->socket_to_babysitter = babysitter_pipe[0]; + babysitter_pipe[0] = -1; + + sitter->error_pipe_from_child = child_err_report_pipe[READ_END]; + child_err_report_pipe[READ_END] = -1; + + sitter->sitter_pid = pid; + + if (sitter_p != NULL) + *sitter_p = sitter; + else + _dbus_babysitter_unref (sitter); + + dbus_free_string_array (env); + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + return TRUE; + } + + cleanup_and_fail: + + _DBUS_ASSERT_ERROR_IS_SET (error); + + close_and_invalidate (&child_err_report_pipe[READ_END]); + close_and_invalidate (&child_err_report_pipe[WRITE_END]); + close_and_invalidate (&babysitter_pipe[0]); + close_and_invalidate (&babysitter_pipe[1]); +#ifdef HAVE_SYSTEMD + close_and_invalidate (&fd_out); + close_and_invalidate (&fd_err); +#endif + + if (sitter != NULL) + _dbus_babysitter_unref (sitter); + + return FALSE; +} + +void +_dbus_babysitter_set_result_function (DBusBabysitter *sitter, + DBusBabysitterFinishedFunc finished, + void *user_data) +{ + sitter->finished_cb = finished; + sitter->finished_data = user_data; +} + +/** @} */ + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + +static char * +get_test_exec (const char *exe, + DBusString *scratch_space) +{ + const char *dbus_test_exec; + + dbus_test_exec = _dbus_getenv ("DBUS_TEST_EXEC"); + + if (dbus_test_exec == NULL) + dbus_test_exec = DBUS_TEST_EXEC; + + if (!_dbus_string_init (scratch_space)) + return NULL; + + if (!_dbus_string_append_printf (scratch_space, "%s/%s%s", + dbus_test_exec, exe, DBUS_EXEEXT)) + { + _dbus_string_free (scratch_space); + return NULL; + } + + return _dbus_string_get_data (scratch_space); +} + +static void +_dbus_babysitter_block_for_child_exit (DBusBabysitter *sitter) +{ + while (LIVE_CHILDREN (sitter)) + babysitter_iteration (sitter, TRUE); +} + +static dbus_bool_t +check_spawn_nonexistent (void *data) +{ + char *argv[4] = { NULL, NULL, NULL, NULL }; + DBusBabysitter *sitter = NULL; + DBusError error = DBUS_ERROR_INIT; + + /*** Test launching nonexistent binary */ + + argv[0] = "/this/does/not/exist/32542sdgafgafdg"; + if (_dbus_spawn_async_with_babysitter (&sitter, "spawn_nonexistent", argv, + NULL, NULL, NULL, + &error)) + { + _dbus_babysitter_block_for_child_exit (sitter); + _dbus_babysitter_set_child_exit_error (sitter, &error); + } + + if (sitter) + _dbus_babysitter_unref (sitter); + + if (!dbus_error_is_set (&error)) + { + _dbus_warn ("Did not get an error launching nonexistent executable\n"); + return FALSE; + } + + if (!(dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY) || + dbus_error_has_name (&error, DBUS_ERROR_SPAWN_EXEC_FAILED))) + { + _dbus_warn ("Not expecting error when launching nonexistent executable: %s: %s\n", + error.name, error.message); + dbus_error_free (&error); + return FALSE; + } + + dbus_error_free (&error); + + return TRUE; +} + +static dbus_bool_t +check_spawn_segfault (void *data) +{ + char *argv[4] = { NULL, NULL, NULL, NULL }; + DBusBabysitter *sitter = NULL; + DBusError error = DBUS_ERROR_INIT; + DBusString argv0; + + /*** Test launching segfault binary */ + + argv[0] = get_test_exec ("test-segfault", &argv0); + + if (argv[0] == NULL) + { + /* OOM was simulated, never mind */ + return TRUE; + } + + if (_dbus_spawn_async_with_babysitter (&sitter, "spawn_segfault", argv, + NULL, NULL, NULL, + &error)) + { + _dbus_babysitter_block_for_child_exit (sitter); + _dbus_babysitter_set_child_exit_error (sitter, &error); + } + + _dbus_string_free (&argv0); + + if (sitter) + _dbus_babysitter_unref (sitter); + + if (!dbus_error_is_set (&error)) + { + _dbus_warn ("Did not get an error launching segfaulting binary\n"); + return FALSE; + } + + if (!(dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY) || + dbus_error_has_name (&error, DBUS_ERROR_SPAWN_CHILD_SIGNALED))) + { + _dbus_warn ("Not expecting error when launching segfaulting executable: %s: %s\n", + error.name, error.message); + dbus_error_free (&error); + return FALSE; + } + + dbus_error_free (&error); + + return TRUE; +} + +static dbus_bool_t +check_spawn_exit (void *data) +{ + char *argv[4] = { NULL, NULL, NULL, NULL }; + DBusBabysitter *sitter = NULL; + DBusError error = DBUS_ERROR_INIT; + DBusString argv0; + + /*** Test launching exit failure binary */ + + argv[0] = get_test_exec ("test-exit", &argv0); + + if (argv[0] == NULL) + { + /* OOM was simulated, never mind */ + return TRUE; + } + + if (_dbus_spawn_async_with_babysitter (&sitter, "spawn_exit", argv, + NULL, NULL, NULL, + &error)) + { + _dbus_babysitter_block_for_child_exit (sitter); + _dbus_babysitter_set_child_exit_error (sitter, &error); + } + + _dbus_string_free (&argv0); + + if (sitter) + _dbus_babysitter_unref (sitter); + + if (!dbus_error_is_set (&error)) + { + _dbus_warn ("Did not get an error launching binary that exited with failure code\n"); + return FALSE; + } + + if (!(dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY) || + dbus_error_has_name (&error, DBUS_ERROR_SPAWN_CHILD_EXITED))) + { + _dbus_warn ("Not expecting error when launching exiting executable: %s: %s\n", + error.name, error.message); + dbus_error_free (&error); + return FALSE; + } + + dbus_error_free (&error); + + return TRUE; +} + +static dbus_bool_t +check_spawn_and_kill (void *data) +{ + char *argv[4] = { NULL, NULL, NULL, NULL }; + DBusBabysitter *sitter = NULL; + DBusError error = DBUS_ERROR_INIT; + DBusString argv0; + + /*** Test launching sleeping binary then killing it */ + + argv[0] = get_test_exec ("test-sleep-forever", &argv0); + + if (argv[0] == NULL) + { + /* OOM was simulated, never mind */ + return TRUE; + } + + if (_dbus_spawn_async_with_babysitter (&sitter, "spawn_and_kill", argv, + NULL, NULL, NULL, + &error)) + { + _dbus_babysitter_kill_child (sitter); + + _dbus_babysitter_block_for_child_exit (sitter); + + _dbus_babysitter_set_child_exit_error (sitter, &error); + } + + _dbus_string_free (&argv0); + + if (sitter) + _dbus_babysitter_unref (sitter); + + if (!dbus_error_is_set (&error)) + { + _dbus_warn ("Did not get an error after killing spawned binary\n"); + return FALSE; + } + + if (!(dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY) || + dbus_error_has_name (&error, DBUS_ERROR_SPAWN_CHILD_SIGNALED))) + { + _dbus_warn ("Not expecting error when killing executable: %s: %s\n", + error.name, error.message); + dbus_error_free (&error); + return FALSE; + } + + dbus_error_free (&error); + + return TRUE; +} + +dbus_bool_t +_dbus_spawn_test (const char *test_data_dir) +{ + if (!_dbus_test_oom_handling ("spawn_nonexistent", + check_spawn_nonexistent, + NULL)) + return FALSE; + + if (!_dbus_test_oom_handling ("spawn_segfault", + check_spawn_segfault, + NULL)) + return FALSE; + + if (!_dbus_test_oom_handling ("spawn_exit", + check_spawn_exit, + NULL)) + return FALSE; + + if (!_dbus_test_oom_handling ("spawn_and_kill", + check_spawn_and_kill, + NULL)) + return FALSE; + + return TRUE; +} +#endif |