2003-02-15 Anders Carlsson <andersca@codefactory.se>
authorAnders Carlsson <andersca@codefactory.se>
Sat, 15 Feb 2003 17:18:13 +0000 (17:18 +0000)
committerAnders Carlsson <andersca@codefactory.se>
Sat, 15 Feb 2003 17:18:13 +0000 (17:18 +0000)
* dbus/dbus-errors.c: (dbus_set_error):
* dbus/dbus-errors.h:
Add a few errors and make dbus_set_error void.

* dbus/dbus-sysdeps.c:
(_dbus_errno_to_string), (close_and_invalidate), (make_pipe),
(write_err_and_exit), (read_ints), (do_exec), (_dbus_spawn_async):
* dbus/dbus-sysdeps.h:
Add _dbus_spawn_async.

* test/spawn-test.c: (main):
Test for _dbus_spawn_async.

ChangeLog
dbus/dbus-errors.c
dbus/dbus-errors.h
dbus/dbus-sysdeps.c
dbus/dbus-sysdeps.h
test/Makefile.am
test/spawn-test.c [new file with mode: 0644]

index 24abc4e..6c5517b 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,20 @@
 2003-02-15  Anders Carlsson  <andersca@codefactory.se>
 
+       * dbus/dbus-errors.c: (dbus_set_error):
+       * dbus/dbus-errors.h:
+       Add a few errors and make dbus_set_error void.
+       
+       * dbus/dbus-sysdeps.c: 
+       (_dbus_errno_to_string), (close_and_invalidate), (make_pipe),
+       (write_err_and_exit), (read_ints), (do_exec), (_dbus_spawn_async):
+       * dbus/dbus-sysdeps.h:
+       Add _dbus_spawn_async.
+       
+       * test/spawn-test.c: (main):
+       Test for _dbus_spawn_async.
+       
+2003-02-15  Anders Carlsson  <andersca@codefactory.se>
+
        * dbus/dbus-internals.h:
        Fix build without tests.
        
index a679e6c..dfc52fb 100644 (file)
@@ -214,12 +214,14 @@ dbus_set_error_const (DBusError  *error,
  * Assigns an error name and message to a DBusError.
  * Does nothing if error is #NULL.
  *
+ * If no memory can be allocated for the error message, 
+ * an out-of-memory error message will be set instead.
+ * 
  * @param error the error.
  * @param name the error name (not copied!!!)
  * @param format printf-style format string.
- * @returns #TRUE on success.
  */
-dbus_bool_t
+void
 dbus_set_error (DBusError  *error,
                const char *name,
                const char *format,
@@ -232,7 +234,7 @@ dbus_set_error (DBusError  *error,
   char c;
 
   if (error == NULL)
-    return TRUE;
+    return;
   
   va_start (args, format);
 
@@ -246,8 +248,12 @@ dbus_set_error (DBusError  *error,
   vsprintf (message, format, args2);
   
   if (!message)
-    return FALSE;
-
+    {
+      dbus_set_error_const (error, DBUS_ERROR_NO_MEMORY,
+                           "Failed to allocate memory for error message.");
+      return;
+    }
+  
   va_end (args);
 
   dbus_error_init (error);
@@ -256,8 +262,6 @@ dbus_set_error (DBusError  *error,
   real->name = name;
   real->message = message;
   real->const_message = FALSE;
-  
-  return TRUE;
 }
 
 /** @} */
index 639c6a7..5cc7749 100644 (file)
@@ -49,6 +49,10 @@ struct DBusError
   void *padding1; /**< placeholder */
 };
 
+#define DBUS_ERROR_SPAWN_FORK_FAILED "org.freedesktop.DBus.Error.Spawn.ForkFailed"
+#define DBUS_ERROR_SPAWN_FAILED      "org.freedesktop.DBus.Error.Spawn.Failed"
+#define DBUS_ERROR_NO_MEMORY         "org.freedesktop.DBus.Error.NoMemory"
+
 typedef enum
 {
   DBUS_RESULT_SUCCESS,         /**< Operation was successful. */
@@ -75,7 +79,7 @@ typedef enum
 
 void        dbus_error_init      (DBusError  *error);
 void        dbus_error_free      (DBusError  *error);
-dbus_bool_t dbus_set_error       (DBusError  *error,
+void        dbus_set_error       (DBusError  *error,
                                  const char *name,
                                  const char *message,
                                  ...);
index 53eebf7..e1ae16c 100644 (file)
@@ -39,6 +39,7 @@
 #include <time.h>
 #include <sys/time.h>
 #include <sys/stat.h>
+#include <sys/wait.h>
 
 #ifdef HAVE_WRITEV
 #include <sys/uio.h>
@@ -1450,4 +1451,275 @@ _dbus_generate_random_bytes (DBusString *str,
   return FALSE;
 }
 
+const char *
+_dbus_errno_to_string (int errnum)
+{
+  return strerror (errnum);
+}
+
+/* Avoids a danger in threaded situations (calling close()
+ * on a file descriptor twice, and another thread has
+ * re-opened it since the first close)
+ */
+static int
+close_and_invalidate (int *fd)
+{
+  int ret;
+
+  if (*fd < 0)
+    return -1;
+  else
+    {
+      ret = close (*fd);
+      *fd = -1;
+    }
+
+  return ret;
+}
+
+static dbus_bool_t
+make_pipe (int        p[2],
+           DBusError *error)
+{
+  if (pipe (p) < 0)
+    {
+      dbus_set_error (error,
+                     DBUS_ERROR_SPAWN_FAILED,
+                     "Failed to create pipe for communicating with child process (%s)",
+                     _dbus_errno_to_string (errno));
+      return FALSE;
+    }
+  else
+    return TRUE;
+}
+
+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));
+  
+  _exit (1);
+}
+
+static dbus_bool_t
+read_ints (int        fd,
+          int       *buf,
+          int        n_ints_in_buf,
+          int       *n_ints_read,
+          DBusError *error)
+{
+  size_t bytes = 0;    
+  
+  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));
+
+  return TRUE;
+}
+
+static void
+do_exec (int    child_err_report_fd,
+        char **argv)
+{
+  execvp (argv[0], argv);
+
+  /* Exec failed */
+  write_err_and_exit (child_err_report_fd,
+                      CHILD_EXEC_FAILED);
+  
+}
+
+dbus_bool_t
+_dbus_spawn_async (char      **argv,
+                  DBusError  *error)
+{
+  int pid = -1, grandchild_pid;
+  int child_err_report_pipe[2] = { -1, -1 };
+  int child_pid_report_pipe[2] = { -1, -1 };
+  int status;
+  
+  printf ("spawning application: %s\n", argv[0]);
+
+  if (!make_pipe (child_err_report_pipe, error))
+    return FALSE;
+
+  if (!make_pipe (child_pid_report_pipe, error))
+    goto cleanup_and_fail;
+  
+  pid = fork ();
+
+  if (pid < 0)
+    {
+      dbus_set_error (error,
+                     DBUS_ERROR_SPAWN_FORK_FAILED,
+                     "Failed to fork (%s)",
+                     _dbus_errno_to_string (errno));
+      return FALSE;
+    }
+  else if (pid == 0)
+    {
+      /* Immediate child. */
+      
+      /* 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]);
+      close_and_invalidate (&child_pid_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.
+       */
+      grandchild_pid = fork ();
+      
+      if (grandchild_pid < 0)
+       {
+         /* report -1 as child PID */
+         write (child_pid_report_pipe[1], &grandchild_pid,
+                sizeof(grandchild_pid));
+         
+         write_err_and_exit (child_err_report_pipe[1],
+                             CHILD_FORK_FAILED);              
+       }
+      else if (grandchild_pid == 0)
+       {
+         do_exec (child_err_report_pipe[1],
+                  argv);
+       }
+      else
+       {
+         write (child_pid_report_pipe[1], &grandchild_pid, sizeof(grandchild_pid));
+         close_and_invalidate (&child_pid_report_pipe[1]);
+              
+         _exit (0);
+       }
+    }
+  else
+    {
+      /* Parent */
+
+      int buf[2];
+      int n_ints = 0;    
+      
+      /* Close the uncared-about ends of the pipes */
+      close_and_invalidate (&child_err_report_pipe[1]);
+      close_and_invalidate (&child_pid_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'");
+       }
+
+      if (!read_ints (child_err_report_pipe[0],
+                      buf, 2, &n_ints,
+                      error))
+         goto cleanup_and_fail;
+      
+      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;
+       }
+
+
+      /* Success against all odds! return the information */
+      close_and_invalidate (&child_err_report_pipe[0]);
+
+      return TRUE;
+    }
+
+ cleanup_and_fail:
+
+  /* There was an error from the Child, reap the child to avoid it being
+     a zombie.
+  */
+  if (pid > 0)
+    {
+    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'");
+       }
+    }
+  
+  close_and_invalidate (&child_err_report_pipe[0]);
+  close_and_invalidate (&child_err_report_pipe[1]);
+  close_and_invalidate (&child_pid_report_pipe[0]);
+  close_and_invalidate (&child_pid_report_pipe[1]);
+
+  return FALSE;
+}
+
 /** @} end of sysdeps */
index 76c943b..dca12ed 100644 (file)
@@ -147,6 +147,11 @@ void         _dbus_directory_close         (DBusDirIter      *iter);
 dbus_bool_t _dbus_generate_random_bytes (DBusString *str,
                                          int         n_bytes);
 
+const char *_dbus_errno_to_string (int         errnum);
+dbus_bool_t _dbus_spawn_async     (char      **argv,
+                                  DBusError  *error);
+
+                              
 DBUS_END_DECLS;
 
 #endif /* DBUS_SYSDEPS_H */
index ad65782..604fd3f 100644 (file)
@@ -2,7 +2,7 @@
 INCLUDES=-I$(top_srcdir) $(DBUS_TEST_CFLAGS) 
 
 if DBUS_BUILD_TESTS
-TEST_BINARIES=echo-client echo-server unbase64 bus-test break-loader
+TEST_BINARIES=echo-client echo-server unbase64 bus-test break-loader spawn-test
 else
 TEST_BINARIES=
 endif
@@ -31,6 +31,9 @@ bus_test_SOURCES =                            \
 break_loader_SOURCES=                          \
        break-loader.c
 
+spawn_test_SOURCES=                            \
+       spawn-test.c
+
 TEST_LIBS=$(DBUS_TEST_LIBS) $(top_builddir)/dbus/libdbus-convenience.la $(top_builddir)/dbus/libdbus-1.la
 
 echo_client_LDADD=$(TEST_LIBS)
@@ -38,6 +41,7 @@ echo_server_LDADD=$(TEST_LIBS)
 unbase64_LDADD=$(TEST_LIBS)
 break_loader_LDADD= $(TEST_LIBS)
 bus_test_LDADD=$(TEST_LIBS) $(top_builddir)/bus/libdbus-daemon.la
+spawn_test_LDADD=$(TEST_LIBS)
 
 dist-hook:                                                                                        \
        DIRS="data data/valid-messages data/invalid-messages data/incomplete-messages data/auth" ; \
diff --git a/test/spawn-test.c b/test/spawn-test.c
new file mode 100644 (file)
index 0000000..fda0309
--- /dev/null
@@ -0,0 +1,34 @@
+#include <dbus/dbus.h>
+
+#define DBUS_COMPILATION /* cheat and use dbus-sysdeps */
+#include <dbus/dbus-sysdeps.h>
+#undef DBUS_COMPILATION
+#include <stdio.h>
+
+int
+main (int argc, char **argv)
+{
+  char **argv_copy;
+  int i;
+  DBusError error;
+  
+  if (argc < 2)
+    {
+      fprintf (stderr, "You need to specify a program to launch.\n");
+
+      return -1;
+    }
+
+  argv_copy = dbus_new (char *, argc);
+  for (i = 0; i < argc - 1; i++)
+    argv_copy [i] = argv[i + 1];
+  argv_copy[argc - 1] = NULL;
+  
+  if (!_dbus_spawn_async (argv_copy, &error))
+    {
+      fprintf (stderr, "Could not launch application: \"%s\"\n",
+              error.message);
+    }
+  
+  return 0;
+}