2003-04-04 Havoc Pennington <hp@redhat.com>
authorHavoc Pennington <hp@redhat.com>
Sat, 5 Apr 2003 00:37:17 +0000 (00:37 +0000)
committerHavoc Pennington <hp@redhat.com>
Sat, 5 Apr 2003 00:37:17 +0000 (00:37 +0000)
* 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()

18 files changed:
ChangeLog
bus/activation.c
configure.in
dbus/dbus-errors.h
dbus/dbus-server-debug-pipe.c
dbus/dbus-spawn.c
dbus/dbus-spawn.h
dbus/dbus-sysdeps.c
dbus/dbus-sysdeps.h
dbus/dbus-test.c
dbus/dbus-test.h
dbus/dbus-watch.c
doc/TODO
test/Makefile.am
test/spawn-test.c
test/test-exit.c [new file with mode: 0644]
test/test-segfault.c [new file with mode: 0644]
test/test-sleep-forever.c [new file with mode: 0644]

index d3ca8ed..ed29a78 100644 (file)
--- 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 
index ef5c173..9ae4422 100644 (file)
@@ -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);
index 076b74c..0238282 100644 (file)
@@ -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
index e6e24a8..59fa1e4 100644 (file)
@@ -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"
index c606322..905349b 100644 (file)
@@ -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");
index 27fb6e6..5ced84f 100644 (file)
@@ -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>
  * @{
  */
 
+/*
+ * 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
index 04348d7..9cf108a 100644 (file)
 
 #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 */
index 7e635d9..624af89 100644 (file)
@@ -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),
index 5057f52..77d54c8 100644 (file)
@@ -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);
index 0b0893a..76776a9 100644 (file)
@@ -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 ())
index 0615448..68304ef 100644 (file)
@@ -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,
index c4f1af4..a5b9af9 100644 (file)
@@ -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);
index c4e3e83..4404b1f 100644 (file)
--- a/doc/TODO
+++ b/doc/TODO
 
  - 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()
index d0429aa..c46b279 100644 (file)
@@ -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)
index 18462eb..f323368 100644 (file)
@@ -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 (file)
index 0000000..abb9586
--- /dev/null
@@ -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 (file)
index 0000000..6c19782
--- /dev/null
@@ -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 (file)
index 0000000..7d9541e
--- /dev/null
@@ -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;
+}