From 03b9ca6d4ecf2577958530b8390d675c73a58825 Mon Sep 17 00:00:00 2001 From: Havoc Pennington Date: Sat, 5 Apr 2003 00:37:17 +0000 Subject: 2003-04-04 Havoc Pennington * 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() --- ChangeLog | 12 + bus/activation.c | 6 +- configure.in | 15 +- dbus/dbus-errors.h | 3 + dbus/dbus-server-debug-pipe.c | 2 +- dbus/dbus-spawn.c | 1311 ++++++++++++++++++++++++++++++++++++----- dbus/dbus-spawn.h | 28 + dbus/dbus-sysdeps.c | 36 +- dbus/dbus-sysdeps.h | 9 +- dbus/dbus-test.c | 6 + dbus/dbus-test.h | 1 + dbus/dbus-watch.c | 1 - doc/TODO | 10 + test/Makefile.am | 11 +- test/spawn-test.c | 3 +- test/test-exit.c | 8 + test/test-segfault.c | 14 + test/test-sleep-forever.c | 12 + 18 files changed, 1294 insertions(+), 194 deletions(-) create mode 100644 test/test-exit.c create mode 100644 test/test-segfault.c create mode 100644 test/test-sleep-forever.c diff --git a/ChangeLog b/ChangeLog index d3ca8ede..ed29a789 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2003-04-04 Havoc Pennington + + * 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 * 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 #include @@ -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; } + + 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 - { - _dbus_fd_set_close_on_exec (p[0]); - _dbus_fd_set_close_on_exec (p[1]); - return TRUE; - } + bytes_written += ret; + + if (bytes_written < count) + goto again; } -enum -{ - CHILD_CHDIR_FAILED, - CHILD_EXEC_FAILED, - CHILD_DUP2_FAILED, - CHILD_FORK_FAILED -}; - 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); + + return TRUE; +} + +static dbus_bool_t +check_spawn_segfault (void *data) +{ + char *argv[4] = { NULL, NULL, NULL, NULL }; + DBusBabysitter *sitter; + DBusError error; - close_and_invalidate (&child_err_report_pipe[0]); - close_and_invalidate (&child_err_report_pipe[1]); + sitter = NULL; + + dbus_error_init (&error); - return FALSE; + /*** 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 #include +#include 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; } @@ -2556,27 +2556,6 @@ _dbus_generate_random_bytes (DBusString *str, return FALSE; } -/** - * 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 +#include #undef DBUS_COMPILATION #include @@ -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 + +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 + +int +main (int argc, char **argv) +{ + while (1) + sleep (10000000); + + return 1; +} -- cgit