summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHavoc Pennington <hp@redhat.com>2003-04-05 00:37:17 +0000
committerHavoc Pennington <hp@redhat.com>2003-04-05 00:37:17 +0000
commit03b9ca6d4ecf2577958530b8390d675c73a58825 (patch)
treebe0569a447ef9acaf5a0004fb9bfe1a2022c9eb1
parent45d1479fad0fb55f1775c394e696643dad3e8e4d (diff)
2003-04-04 Havoc Pennington <hp@redhat.com>
* dbus/dbus-spawn.c, dbus/dbus-spawn.h: Change dbus_spawn to return a "babysitter" object that is used to monitor the status of the spawned process and reap it when required. * test/test-segfault.c, test/test-exit.c, test/test-sleep-forever.c: binaries that do various lame things, used in the test suite. * dbus/dbus-sysdeps.c: kill _dbus_errno_to_string()
-rw-r--r--ChangeLog12
-rw-r--r--bus/activation.c6
-rw-r--r--configure.in15
-rw-r--r--dbus/dbus-errors.h3
-rw-r--r--dbus/dbus-server-debug-pipe.c2
-rw-r--r--dbus/dbus-spawn.c1309
-rw-r--r--dbus/dbus-spawn.h28
-rw-r--r--dbus/dbus-sysdeps.c36
-rw-r--r--dbus/dbus-sysdeps.h9
-rw-r--r--dbus/dbus-test.c6
-rw-r--r--dbus/dbus-test.h1
-rw-r--r--dbus/dbus-watch.c1
-rw-r--r--doc/TODO10
-rw-r--r--test/Makefile.am11
-rw-r--r--test/spawn-test.c3
-rw-r--r--test/test-exit.c8
-rw-r--r--test/test-segfault.c14
-rw-r--r--test/test-sleep-forever.c12
18 files changed, 1293 insertions, 193 deletions
diff --git a/ChangeLog b/ChangeLog
index d3ca8ede..ed29a789 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
+2003-04-04 Havoc Pennington <hp@redhat.com>
+
+ * dbus/dbus-spawn.c, dbus/dbus-spawn.h: Change dbus_spawn to
+ return a "babysitter" object that is used to monitor the status of
+ the spawned process and reap it when required.
+
+ * test/test-segfault.c, test/test-exit.c,
+ test/test-sleep-forever.c: binaries that do various lame things,
+ used in the test suite.
+
+ * dbus/dbus-sysdeps.c: kill _dbus_errno_to_string()
+
2003-04-03 Havoc Pennington <hp@pobox.com>
* dbus/dbus-spawn.c: Move dbus-spawn into a separate file
diff --git a/bus/activation.c b/bus/activation.c
index ef5c1730..9ae44225 100644
--- a/bus/activation.c
+++ b/bus/activation.c
@@ -619,9 +619,9 @@ bus_activation_activate_service (BusActivation *activation,
argv[0] = entry->exec;
argv[1] = NULL;
- if (!_dbus_spawn_async (argv,
- child_setup, activation,
- error))
+ if (!_dbus_spawn_async_with_babysitter (NULL, argv,
+ child_setup, activation,
+ error))
{
_dbus_hash_table_remove_string (activation->pending_activations,
pending_activation->service_name);
diff --git a/configure.in b/configure.in
index 076b74c3..0238282e 100644
--- a/configure.in
+++ b/configure.in
@@ -406,11 +406,18 @@ AM_CONDITIONAL(DBUS_INIT_SCRIPTS_RED_HAT, test x$with_init_scripts = xredhat)
#### Tell tests where to find certain stuff in builddir
ABSOLUTE_TOP_BUILDDIR=`cd ${ac_top_builddir}. && pwd`
-TEST_SERVICE_BINARY=${ABSOLUTE_TOP_BUILDDIR}/test/test-service
-AC_SUBST(TEST_SERVICE_BINARY)
+AC_DEFUN(TEST_PATH, [
+TEST_$1=${ABSOLUTE_TOP_BUILDDIR}/test/$2
+AC_DEFINE_UNQUOTED(TEST_$1, "$TEST_$1",
+ [Full path to test file test/$2 in builddir])
+AC_SUBST(TEST_$1)
+])
-TEST_SERVICE_DIR=${ABSOLUTE_TOP_BUILDDIR}/test/data/valid-service-files
-AC_SUBST(TEST_SERVICE_DIR)
+TEST_PATH(SERVICE_DIR, data/valid-service-files)
+TEST_PATH(SERVICE_BINARY, test-service)
+TEST_PATH(EXIT_BINARY, test-exit)
+TEST_PATH(SEGFAULT_BINARY, test-segfault)
+TEST_PATH(SLEEP_FOREVER_BINARY, test-sleep-forever)
AC_OUTPUT([
Doxyfile
diff --git a/dbus/dbus-errors.h b/dbus/dbus-errors.h
index e6e24a85..59fa1e49 100644
--- a/dbus/dbus-errors.h
+++ b/dbus/dbus-errors.h
@@ -51,7 +51,10 @@ struct DBusError
#define DBUS_ERROR_FAILED "org.freedesktop.DBus.Error.Failed"
#define DBUS_ERROR_ACTIVATE_SERVICE_NOT_FOUND "org.freedesktop.DBus.Activate.ServiceNotFound"
+#define DBUS_ERROR_SPAWN_EXEC_FAILED "org.freedesktop.DBus.Error.Spawn.ExecFailed"
#define DBUS_ERROR_SPAWN_FORK_FAILED "org.freedesktop.DBus.Error.Spawn.ForkFailed"
+#define DBUS_ERROR_SPAWN_CHILD_EXITED "org.freedesktop.DBus.Error.Spawn.ChildExited"
+#define DBUS_ERROR_SPAWN_CHILD_SIGNALED "org.freedesktop.DBus.Error.Spawn.ChildSignaled"
#define DBUS_ERROR_SPAWN_FAILED "org.freedesktop.DBus.Error.Spawn.Failed"
#define DBUS_ERROR_NO_MEMORY "org.freedesktop.DBus.Error.NoMemory"
#define DBUS_ERROR_SERVICE_DOES_NOT_EXIST "org.freedesktop.DBus.Error.ServiceDoesNotExist"
diff --git a/dbus/dbus-server-debug-pipe.c b/dbus/dbus-server-debug-pipe.c
index c6063220..905349be 100644
--- a/dbus/dbus-server-debug-pipe.c
+++ b/dbus/dbus-server-debug-pipe.c
@@ -244,7 +244,7 @@ _dbus_transport_debug_pipe_new (const char *server_name,
return NULL;
}
- if (!_dbus_full_duplex_pipe (&client_fd, &server_fd,
+ if (!_dbus_full_duplex_pipe (&client_fd, &server_fd, FALSE,
NULL))
{
_dbus_verbose ("failed to create full duplex pipe\n");
diff --git a/dbus/dbus-spawn.c b/dbus/dbus-spawn.c
index 27fb6e6c..5ced84fc 100644
--- a/dbus/dbus-spawn.c
+++ b/dbus/dbus-spawn.c
@@ -24,6 +24,7 @@
#include "dbus-spawn.h"
#include "dbus-sysdeps.h"
#include "dbus-internals.h"
+#include "dbus-test.h"
#include <unistd.h>
#include <fcntl.h>
@@ -36,6 +37,676 @@
* @{
*/
+/*
+ * I'm pretty sure this whole spawn file could be made simpler,
+ * if you thought about it a bit.
+ */
+
+typedef enum
+{
+ READ_STATUS_OK,
+ READ_STATUS_ERROR,
+ READ_STATUS_EOF
+} 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)
+ {
+ size_t chunk;
+ size_t to_read;
+
+ again:
+
+ to_read = sizeof (int) * n_ints_in_buf - bytes;
+
+ if (to_read == 0)
+ break;
+
+ 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)
+ {
+ size_t chunk;
+ size_t to_read;
+
+ again:
+ to_read = sizeof (pid_t) - bytes;
+
+ if (to_read == 0)
+ break;
+
+ 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.
+ */
+
+/* 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 */
+};
+
+struct DBusBabysitter
+{
+ int refcount;
+
+ char *executable; /**< executable name to use in error messages */
+
+ int socket_to_babysitter;
+ int error_pipe_from_child;
+
+ pid_t sitter_pid;
+ pid_t grandchild_pid;
+
+ DBusWatchList *watches;
+
+ DBusWatch *error_watch;
+ DBusWatch *sitter_watch;
+
+ int errnum;
+ int status;
+ unsigned int have_child_status : 1;
+ unsigned int have_fork_errnum : 1;
+ unsigned int have_exec_errnum : 1;
+};
+
+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
+ */
+void
+_dbus_babysitter_ref (DBusBabysitter *sitter)
+{
+ _dbus_assert (sitter != NULL);
+ _dbus_assert (sitter->refcount > 0);
+
+ sitter->refcount += 1;
+}
+
+/**
+ * Decrement the reference count on the babysitter object.
+ *
+ * @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 (sitter->socket_to_babysitter >= 0)
+ {
+ close (sitter->socket_to_babysitter);
+ sitter->socket_to_babysitter = -1;
+ }
+
+ if (sitter->error_pipe_from_child >= 0)
+ {
+ close (sitter->error_pipe_from_child);
+ sitter->error_pipe_from_child = -1;
+ }
+
+ if (sitter->sitter_pid != -1)
+ {
+ int status;
+ int ret;
+
+ /* Reap the babysitter */
+ again:
+ ret = waitpid (sitter->sitter_pid, &status, 0);
+ if (ret < 0)
+ {
+ if (errno == EINTR)
+ goto again;
+ else 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
+ {
+ 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");
+ }
+ }
+
+ if (sitter->error_watch)
+ {
+ _dbus_watch_invalidate (sitter->error_watch);
+ _dbus_watch_unref (sitter->error_watch);
+ sitter->error_watch = NULL;
+ }
+
+ if (sitter->sitter_watch)
+ {
+ _dbus_watch_invalidate (sitter->sitter_watch);
+ _dbus_watch_unref (sitter->sitter_watch);
+ sitter->sitter_watch = NULL;
+ }
+
+ if (sitter->watches)
+ _dbus_watch_list_free (sitter->watches);
+
+ dbus_free (sitter->executable);
+
+ dbus_free (sitter);
+ }
+}
+
+static ReadStatus
+read_data (DBusBabysitter *sitter,
+ int fd)
+{
+ int what;
+ int got;
+ DBusError error;
+ ReadStatus r;
+
+ dbus_error_init (&error);
+
+ 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;
+ _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");
+ close (sitter->socket_to_babysitter);
+ sitter->socket_to_babysitter = -1;
+}
+
+static void
+close_error_pipe_from_child (DBusBabysitter *sitter)
+{
+ _dbus_verbose ("Closing child error\n");
+ close (sitter->error_pipe_from_child);
+ 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;
+
+ ret = _dbus_poll (fds, i, 0);
+ if (ret == 0 && block)
+ ret = _dbus_poll (fds, i, -1);
+
+ 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);
+
+ 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;
+}
+
+static void
+_dbus_babysitter_block_for_child_exit (DBusBabysitter *sitter)
+{
+ while (LIVE_CHILDREN (sitter))
+ babysitter_iteration (sitter, 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->executable, _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->executable, _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->executable, WEXITSTATUS (sitter->status));
+ else if (WIFSIGNALED (sitter->status))
+ dbus_set_error (error, DBUS_ERROR_SPAWN_CHILD_SIGNALED,
+ "Process %s received signal %d",
+ sitter->executable, WTERMSIG (sitter->status));
+ else
+ dbus_set_error (error, DBUS_ERROR_FAILED,
+ "Process %s exited abnormally",
+ sitter->executable);
+ }
+ else
+ {
+ dbus_set_error (error, DBUS_ERROR_FAILED,
+ "Process %s exited, reason unknown",
+ sitter->executable);
+ }
+}
+
+/**
+ * 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);
+}
+
+/**
+ * Handles watch when descriptors are ready.
+ *
+ * @param sitter the babysitter.
+ * @param watch the watch object
+ * @param condition the descriptor conditions
+ * @returns #FALSE if there wasn't enough memory.
+ *
+ */
+dbus_bool_t
+_dbus_babysitter_handle_watch (DBusBabysitter *sitter,
+ DBusWatch *watch,
+ unsigned int condition)
+{
+ 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_fd (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);
+
+ return TRUE;
+}
+
+#define READ_END 0
+#define WRITE_END 1
+
+
/* Avoids a danger in threaded situations (calling close()
* on a file descriptor twice, and another thread has
* re-opened it since the first close)
@@ -57,8 +728,8 @@ close_and_invalidate (int *fd)
}
static dbus_bool_t
-make_pipe (int p[2],
- DBusError *error)
+make_pipe (int p[2],
+ DBusError *error)
{
_DBUS_ASSERT_ERROR_IS_CLEAR (error);
@@ -67,83 +738,71 @@ make_pipe (int p[2],
dbus_set_error (error,
DBUS_ERROR_SPAWN_FAILED,
"Failed to create pipe for communicating with child process (%s)",
- _dbus_errno_to_string (errno));
+ _dbus_strerror (errno));
return FALSE;
}
- else
- {
- _dbus_fd_set_close_on_exec (p[0]);
- _dbus_fd_set_close_on_exec (p[1]);
- return TRUE;
- }
+
+ return TRUE;
}
-enum
+static void
+do_write (int fd, const void *buf, size_t count)
{
- CHILD_CHDIR_FAILED,
- CHILD_EXEC_FAILED,
- CHILD_DUP2_FAILED,
- CHILD_FORK_FAILED
-};
+ 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;
-
- write (fd, &msg, sizeof(msg));
- write (fd, &en, sizeof(en));
+
+ do_write (fd, &msg, sizeof (msg));
+ do_write (fd, &en, sizeof (en));
_exit (1);
}
-static dbus_bool_t
-read_ints (int fd,
- int *buf,
- int n_ints_in_buf,
- int *n_ints_read,
- DBusError *error)
+static void
+write_pid (int fd, pid_t pid)
{
- size_t bytes = 0;
-
- _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+ int msg = CHILD_PID;
- while (TRUE)
- {
- size_t chunk;
-
- if (bytes >= sizeof(int)*2)
- break; /* give up, who knows what happened, should not be
- * possible.
- */
-
- again:
- chunk = read (fd,
- ((char*)buf) + bytes,
- sizeof(int) * n_ints_in_buf - bytes);
- if (chunk < 0 && errno == EINTR)
- goto again;
-
- if (chunk < 0)
- {
- /* Some weird shit happened, bail out */
-
- dbus_set_error (error,
- DBUS_ERROR_SPAWN_FAILED,
- "Failed to read from child pipe (%s)",
- _dbus_errno_to_string (errno));
-
- return FALSE;
- }
- else if (chunk == 0)
- break; /* EOF */
- else /* chunk > 0 */
- bytes += chunk;
- }
-
- *n_ints_read = (int)(bytes / sizeof(int));
+ do_write (fd, &msg, sizeof (msg));
+ do_write (fd, &pid, sizeof (pid));
+}
- return TRUE;
+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
@@ -166,6 +825,9 @@ do_exec (int child_err_report_fd,
{
int retval;
+ if (i == child_err_report_fd)
+ continue;
+
retval = fcntl (i, F_GETFD);
if (retval != -1 && !(retval & FD_CLOEXEC))
@@ -174,11 +836,131 @@ do_exec (int child_err_report_fd,
#endif
execv (argv[0], argv);
-
+
/* 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;
+ ret = waitpid (grandchild_pid, &status, WNOHANG);
+
+ 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 */
+ 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)
+ {
+ /* Data to read from parent */
+
+ }
+
+ if (revents & (_DBUS_POLLERR | _DBUS_POLLHUP))
+ {
+ /* Parent is gone, so we just exit */
+ _exit (0);
+ }
+}
+
+static int babysit_sigchld_pipe = -1;
+
+static void
+babysit_signal_handler (int signo)
+{
+ char b = '\0';
+ again:
+ write (babysit_sigchld_pipe, &b, 1);
+ if (errno == EINTR)
+ goto again;
+}
+
+static void
+babysit (pid_t grandchild_pid,
+ int parent_pipe)
+{
+ struct sigaction act;
+ sigset_t empty_mask;
+ int sigchld_pipe[2];
+
+ /* 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];
+
+ sigemptyset (&empty_mask);
+ act.sa_handler = babysit_signal_handler;
+ act.sa_mask = empty_mask;
+ act.sa_flags = 0;
+ sigaction (SIGCHLD, &act, 0);
+
+ 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;
+
+ _dbus_poll (pfds, _DBUS_N_ELEMENTS (pfds), -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;
+ read (sigchld_pipe[READ_END], &b, 1);
+ /* do waitpid check */
+ check_babysit_events (grandchild_pid, parent_pipe, 0);
+ }
+ }
+
+ _exit (1);
}
/**
@@ -187,9 +969,12 @@ do_exec (int child_err_report_fd,
* function is passed the given user_data and is run in the child
* just before calling exec().
*
- * @todo this code should be reviewed/double-checked as it's fairly
- * complex and no one has reviewed it yet.
+ * 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
* @param argv the executable and arguments
* @param child_setup function to call in child pre-exec()
* @param user_data user data for setup function
@@ -197,20 +982,47 @@ do_exec (int child_err_report_fd,
* @returns #TRUE on success, #FALSE if error is filled in
*/
dbus_bool_t
-_dbus_spawn_async (char **argv,
- DBusSpawnChildSetupFunc child_setup,
- void *user_data,
- DBusError *error)
+_dbus_spawn_async_with_babysitter (DBusBabysitter **sitter_p,
+ char **argv,
+ DBusSpawnChildSetupFunc child_setup,
+ void *user_data,
+ DBusError *error)
{
- int pid = -1, grandchild_pid;
+ DBusBabysitter *sitter;
int child_err_report_pipe[2] = { -1, -1 };
- int status;
-
+ int babysitter_pipe[2] = { -1, -1 };
+ pid_t pid;
+
_DBUS_ASSERT_ERROR_IS_CLEAR (error);
+
+ *sitter_p = NULL;
+ sitter = NULL;
+
+ sitter = _dbus_babysitter_new ();
+ if (sitter == NULL)
+ {
+ dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+ return FALSE;
+ }
+
+ sitter->executable = _dbus_strdup (argv[0]);
+ if (sitter->executable == NULL)
+ {
+ dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+ goto cleanup_and_fail;
+ }
if (!make_pipe (child_err_report_pipe, error))
- return FALSE;
+ goto cleanup_and_fail;
+
+ _dbus_fd_set_close_on_exec (child_err_report_pipe[READ_END]);
+
+ if (!_dbus_full_duplex_pipe (&babysitter_pipe[0], &babysitter_pipe[1], TRUE, error))
+ goto cleanup_and_fail;
+ _dbus_fd_set_close_on_exec (babysitter_pipe[0]);
+ _dbus_fd_set_close_on_exec (babysitter_pipe[1]);
+
pid = fork ();
if (pid < 0)
@@ -218,122 +1030,325 @@ _dbus_spawn_async (char **argv,
dbus_set_error (error,
DBUS_ERROR_SPAWN_FORK_FAILED,
"Failed to fork (%s)",
- _dbus_errno_to_string (errno));
- return FALSE;
+ _dbus_strerror (errno));
+ goto cleanup_and_fail;
}
else if (pid == 0)
{
- /* Immediate child. */
+ /* 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;
- * not needed in the close_descriptors case,
- * though
- */
- close_and_invalidate (&child_err_report_pipe[0]);
-
- /* We need to fork an intermediate child that launches the
- * final child. The purpose of the intermediate child
- * is to exit, so we can waitpid() it immediately.
- * Then the grandchild will not become a zombie.
- */
+ /* 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 (child_err_report_pipe[1],
- CHILD_FORK_FAILED);
+ 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)
{
- do_exec (child_err_report_pipe[1],
+ do_exec (child_err_report_pipe[WRITE_END],
argv,
child_setup, user_data);
+ _dbus_assert_not_reached ("Got to code after exec() - should have exited on error");
}
else
{
- _exit (0);
+ babysit (grandchild_pid, babysitter_pipe[1]);
+ _dbus_assert_not_reached ("Got to code after babysit()");
}
}
else
{
/* Parent */
-
- int buf[2];
- int n_ints = 0;
+ sitter->error_watch = _dbus_watch_new (child_err_report_pipe[READ_END],
+ DBUS_WATCH_READABLE,
+ TRUE);
+ 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))
+ {
+ 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);
+ 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))
+ {
+ dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+ goto cleanup_and_fail;
+ }
/* Close the uncared-about ends of the pipes */
- close_and_invalidate (&child_err_report_pipe[1]);
-
- wait_again:
- if (waitpid (pid, &status, 0) < 0)
- {
- if (errno == EINTR)
- goto wait_again;
- else if (errno == ECHILD)
- ; /* do nothing, child already reaped */
- else
- _dbus_warn ("waitpid() should not fail in "
- "'_dbus_spawn_async'");
- }
+ close_and_invalidate (&child_err_report_pipe[WRITE_END]);
+ close_and_invalidate (&babysitter_pipe[1]);
- if (!read_ints (child_err_report_pipe[0],
- buf, 2, &n_ints,
- error))
- goto cleanup_and_fail;
+ sitter->socket_to_babysitter = babysitter_pipe[0];
+ babysitter_pipe[0] = -1;
- if (n_ints >= 2)
- {
- /* Error from the child. */
- switch (buf[0])
- {
- default:
- dbus_set_error (error,
- DBUS_ERROR_SPAWN_FAILED,
- "Unknown error executing child process \"%s\"",
- argv[0]);
- break;
- }
-
- goto cleanup_and_fail;
- }
+ sitter->error_pipe_from_child = child_err_report_pipe[READ_END];
+ child_err_report_pipe[READ_END] = -1;
+ sitter->sitter_pid = pid;
- /* Success against all odds! return the information */
- close_and_invalidate (&child_err_report_pipe[0]);
+ if (sitter_p != NULL)
+ *sitter_p = sitter;
+ else
+ _dbus_babysitter_unref (sitter);
+ _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+
return TRUE;
}
cleanup_and_fail:
- /* There was an error from the Child, reap the child to avoid it being
- a zombie.
- */
- if (pid > 0)
+ _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]);
+
+ if (sitter != NULL)
+ _dbus_babysitter_unref (sitter);
+
+ return FALSE;
+}
+
+/** @} */
+
+#ifdef DBUS_BUILD_TESTS
+
+static dbus_bool_t
+check_spawn_nonexistent (void *data)
+{
+ char *argv[4] = { NULL, NULL, NULL, NULL };
+ DBusBabysitter *sitter;
+ DBusError error;
+
+ sitter = NULL;
+
+ dbus_error_init (&error);
+
+ /*** Test launching nonexistent binary */
+
+ argv[0] = "/this/does/not/exist/32542sdgafgafdg";
+ if (_dbus_spawn_async_with_babysitter (&sitter, argv,
+ NULL, NULL,
+ &error))
{
- wait_failed:
- if (waitpid (pid, NULL, 0) < 0)
- {
- if (errno == EINTR)
- goto wait_failed;
- else if (errno == ECHILD)
- ; /* do nothing, child already reaped */
- else
- _dbus_warn ("waitpid() should not fail in "
- "'_dbus_spawn_async'");
- }
+ _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);
- close_and_invalidate (&child_err_report_pipe[0]);
- close_and_invalidate (&child_err_report_pipe[1]);
+ return TRUE;
+}
- return FALSE;
+static dbus_bool_t
+check_spawn_segfault (void *data)
+{
+ char *argv[4] = { NULL, NULL, NULL, NULL };
+ DBusBabysitter *sitter;
+ DBusError error;
+
+ sitter = NULL;
+
+ dbus_error_init (&error);
+
+ /*** Test launching segfault binary */
+
+ argv[0] = TEST_SEGFAULT_BINARY;
+ if (_dbus_spawn_async_with_babysitter (&sitter, argv,
+ 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 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;
+ DBusError error;
+
+ sitter = NULL;
+
+ dbus_error_init (&error);
-/** @} */
+ /*** Test launching exit failure binary */
+
+ argv[0] = TEST_EXIT_BINARY;
+ if (_dbus_spawn_async_with_babysitter (&sitter, argv,
+ 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 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;
+ DBusError error;
+
+ sitter = NULL;
+
+ dbus_error_init (&error);
+
+ /*** Test launching sleeping binary then killing it */
+
+ argv[0] = TEST_SLEEP_FOREVER_BINARY;
+ if (_dbus_spawn_async_with_babysitter (&sitter, argv,
+ NULL, NULL,
+ &error))
+ {
+ _dbus_babysitter_kill_child (sitter);
+
+ _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 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
diff --git a/dbus/dbus-spawn.h b/dbus/dbus-spawn.h
index 04348d75..9cf108a7 100644
--- a/dbus/dbus-spawn.h
+++ b/dbus/dbus-spawn.h
@@ -27,9 +27,37 @@
#include <dbus/dbus-string.h>
#include <dbus/dbus-errors.h>
+#include <dbus/dbus-watch.h>
DBUS_BEGIN_DECLS;
+typedef void (* DBusSpawnChildSetupFunc) (void *user_data);
+
+typedef struct DBusBabysitter DBusBabysitter;
+
+dbus_bool_t _dbus_spawn_async_with_babysitter (DBusBabysitter **sitter_p,
+ char **argv,
+ DBusSpawnChildSetupFunc child_setup,
+ void *user_data,
+ DBusError *error);
+void _dbus_babysitter_ref (DBusBabysitter *sitter);
+void _dbus_babysitter_unref (DBusBabysitter *sitter);
+void _dbus_babysitter_kill_child (DBusBabysitter *sitter);
+dbus_bool_t _dbus_babysitter_get_child_exited (DBusBabysitter *sitter);
+void _dbus_babysitter_set_child_exit_error (DBusBabysitter *sitter,
+ DBusError *error);
+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);
+dbus_bool_t _dbus_babysitter_handle_watch (DBusBabysitter *sitter,
+ DBusWatch *watch,
+ unsigned int condition);
+
+
+
DBUS_END_DECLS;
#endif /* DBUS_SPAWN_H */
diff --git a/dbus/dbus-sysdeps.c b/dbus/dbus-sysdeps.c
index 7e635d9d..624af897 100644
--- a/dbus/dbus-sysdeps.c
+++ b/dbus/dbus-sysdeps.c
@@ -2197,7 +2197,7 @@ _dbus_create_file_exclusively (const DBusString *filename,
DBUS_ERROR_FAILED,
"Could not create file %s: %s\n",
filename_c,
- _dbus_errno_to_string (errno));
+ _dbus_strerror (errno));
return FALSE;
}
@@ -2207,7 +2207,7 @@ _dbus_create_file_exclusively (const DBusString *filename,
DBUS_ERROR_FAILED,
"Could not close file %s: %s\n",
filename_c,
- _dbus_errno_to_string (errno));
+ _dbus_strerror (errno));
return FALSE;
}
@@ -2557,27 +2557,6 @@ _dbus_generate_random_bytes (DBusString *str,
}
/**
- * A wrapper around strerror()
- *
- * @todo get rid of this function, it's the same as
- * _dbus_strerror().
- *
- * @param errnum the errno
- * @returns an error message (never #NULL)
- */
-const char *
-_dbus_errno_to_string (int errnum)
-{
- const char *msg;
-
- msg = strerror (errnum);
- if (msg == NULL)
- msg = "unknown";
-
- return msg;
-}
-
-/**
* A wrapper around strerror() because some platforms
* may be lame and not have strerror().
*
@@ -2781,13 +2760,15 @@ _dbus_stat (const DBusString *filename,
*
* @param fd1 return location for one end
* @param fd2 return location for the other end
+ * @param blocking #TRUE if pipe should be blocking
* @param error error return
* @returns #FALSE on failure (if error is set)
*/
dbus_bool_t
-_dbus_full_duplex_pipe (int *fd1,
- int *fd2,
- DBusError *error)
+_dbus_full_duplex_pipe (int *fd1,
+ int *fd2,
+ dbus_bool_t blocking,
+ DBusError *error)
{
#ifdef HAVE_SOCKETPAIR
int fds[2];
@@ -2801,7 +2782,8 @@ _dbus_full_duplex_pipe (int *fd1,
return FALSE;
}
- if (!_dbus_set_fd_nonblocking (fds[0], NULL) ||
+ if (!blocking &&
+ !_dbus_set_fd_nonblocking (fds[0], NULL) ||
!_dbus_set_fd_nonblocking (fds[1], NULL))
{
dbus_set_error (error, _dbus_error_from_errno (errno),
diff --git a/dbus/dbus-sysdeps.h b/dbus/dbus-sysdeps.h
index 5057f521..77d54c8c 100644
--- a/dbus/dbus-sysdeps.h
+++ b/dbus/dbus-sysdeps.h
@@ -184,14 +184,6 @@ dbus_bool_t _dbus_generate_random_bytes (DBusString *str,
const char *_dbus_errno_to_string (int errnum);
const char* _dbus_error_from_errno (int error_number);
-typedef void (* DBusSpawnChildSetupFunc) (void *user_data);
-
-dbus_bool_t _dbus_spawn_async (char **argv,
- DBusSpawnChildSetupFunc child_setup,
- void *user_data,
- DBusError *error);
-
-
void _dbus_disable_sigpipe (void);
void _dbus_fd_set_close_on_exec (int fd);
@@ -215,6 +207,7 @@ dbus_bool_t _dbus_stat (const DBusString *filename,
DBusError *error);
dbus_bool_t _dbus_full_duplex_pipe (int *fd1,
int *fd2,
+ dbus_bool_t blocking,
DBusError *error);
dbus_bool_t _dbus_close (int fd,
DBusError *error);
diff --git a/dbus/dbus-test.c b/dbus/dbus-test.c
index 0b0893ab..76776a95 100644
--- a/dbus/dbus-test.c
+++ b/dbus/dbus-test.c
@@ -84,6 +84,12 @@ dbus_internal_do_not_use_run_tests (const char *test_data_dir)
die ("sysdeps");
check_memleaks ();
+
+ printf ("%s: running spawn tests\n", "dbus-test");
+ if (!_dbus_spawn_test (test_data_dir))
+ die ("spawn");
+
+ check_memleaks ();
printf ("%s: running data slot tests\n", "dbus-test");
if (!_dbus_data_slot_test ())
diff --git a/dbus/dbus-test.h b/dbus/dbus-test.h
index 06154487..68304efa 100644
--- a/dbus/dbus-test.h
+++ b/dbus/dbus-test.h
@@ -49,6 +49,7 @@ dbus_bool_t _dbus_sha_test (const char *test_data_dir);
dbus_bool_t _dbus_keyring_test (void);
dbus_bool_t _dbus_data_slot_test (void);
dbus_bool_t _dbus_sysdeps_test (void);
+dbus_bool_t _dbus_spawn_test (const char *test_data_dir);
void dbus_internal_do_not_use_run_tests (const char *test_data_dir);
dbus_bool_t dbus_internal_do_not_use_try_message_file (const DBusString *filename,
diff --git a/dbus/dbus-watch.c b/dbus/dbus-watch.c
index c4f1af4a..a5b9af92 100644
--- a/dbus/dbus-watch.c
+++ b/dbus/dbus-watch.c
@@ -201,7 +201,6 @@ _dbus_watch_list_free (DBusWatchList *watch_list)
/* free watch_data and removes watches as a side effect */
_dbus_watch_list_set_functions (watch_list,
NULL, NULL, NULL, NULL, NULL);
-
_dbus_list_foreach (&watch_list->watches,
(DBusForeachFunction) _dbus_watch_unref,
NULL);
diff --git a/doc/TODO b/doc/TODO
index c4e3e830..4404b1f5 100644
--- a/doc/TODO
+++ b/doc/TODO
@@ -65,3 +65,13 @@
- there are various bits of code to manage ref/unref of data slots, that should
be merged into a generic facility
+
+ - add _dbus_return_if_fail, _dbus_return_val_if_fail() and use on public entry
+ points in place of _dbus_assert(). Add --disable-checks to control whether these
+ do anything.
+
+ - assorted _-prefixed symbols in libdbus aren't actually used by
+ libdbus, only by the message bus. These bloat up the library
+ size. Not sure how to fix, really.
+
+ - dbus_error_has_name(), dbus_message_name_is()
diff --git a/test/Makefile.am b/test/Makefile.am
index d0429aaf..c46b2797 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -2,7 +2,7 @@
INCLUDES=-I$(top_srcdir) $(DBUS_TEST_CFLAGS)
if DBUS_BUILD_TESTS
-TEST_BINARIES=test-service echo-client echo-server unbase64 break-loader spawn-test
+TEST_BINARIES=test-service echo-client echo-server unbase64 break-loader spawn-test test-segfault test-exit test-sleep-forever
else
TEST_BINARIES=
endif
@@ -40,6 +40,15 @@ break_loader_SOURCES= \
spawn_test_SOURCES= \
spawn-test.c
+test_exit_SOURCES = \
+ test-exit.c
+
+test_segfault_SOURCES = \
+ test-segfault.c
+
+test_sleep_forever_SOURCES = \
+ test-sleep-forever.c
+
TEST_LIBS=$(DBUS_TEST_LIBS) $(top_builddir)/dbus/libdbus-convenience.la
echo_client_LDADD=$(TEST_LIBS)
diff --git a/test/spawn-test.c b/test/spawn-test.c
index 18462eba..f323368f 100644
--- a/test/spawn-test.c
+++ b/test/spawn-test.c
@@ -2,6 +2,7 @@
#define DBUS_COMPILATION /* cheat and use dbus-sysdeps */
#include <dbus/dbus-sysdeps.h>
+#include <dbus/dbus-spawn.h>
#undef DBUS_COMPILATION
#include <stdio.h>
@@ -30,7 +31,7 @@ main (int argc, char **argv)
argv_copy [i] = argv[i + 1];
argv_copy[argc - 1] = NULL;
- if (!_dbus_spawn_async (argv_copy, setup_func, NULL, &error))
+ if (!_dbus_spawn_async_with_babysitter (NULL, argv_copy, setup_func, NULL, &error))
{
fprintf (stderr, "Could not launch application: \"%s\"\n",
error.message);
diff --git a/test/test-exit.c b/test/test-exit.c
new file mode 100644
index 00000000..abb95865
--- /dev/null
+++ b/test/test-exit.c
@@ -0,0 +1,8 @@
+/* This is a process that just exits with a failure code */
+
+int
+main (int argc, char **argv)
+{
+
+ return 1;
+}
diff --git a/test/test-segfault.c b/test/test-segfault.c
new file mode 100644
index 00000000..6c197823
--- /dev/null
+++ b/test/test-segfault.c
@@ -0,0 +1,14 @@
+/* This is simply a process that segfaults */
+#include <signal.h>
+
+int
+main (int argc, char **argv)
+{
+ char *p = 0;
+
+ raise (SIGSEGV);
+
+ *p = 'a';
+
+ return 0;
+}
diff --git a/test/test-sleep-forever.c b/test/test-sleep-forever.c
new file mode 100644
index 00000000..7d9541e8
--- /dev/null
+++ b/test/test-sleep-forever.c
@@ -0,0 +1,12 @@
+/* This is a process that just sleeps infinitely. */
+
+#include <unistd.h>
+
+int
+main (int argc, char **argv)
+{
+ while (1)
+ sleep (10000000);
+
+ return 1;
+}