[glib] Add g_spawn_check_exit_status()



commit f7abd3ce130ae3a6da8502c2dce8d773d7514464
Author: Colin Walters <walters verbum org>
Date:   Tue Jul 10 11:27:22 2012 -0400

    Add g_spawn_check_exit_status()
    
    Many (if not "almost all") programs that spawn other programs via
    g_spawn_sync() or the like simply want to check whether or not the
    child exited successfully, but doing so requires use of
    platform-specific functionality and there's actually a fair amount of
    boilerplate involved.
    
    This new API will help drain a *lot* of mostly duplicated code in
    GNOME, from gnome-session to gdm.  And we can see that some bits even
    inside GLib were doing it wrong; for example checking the exit status
    on Unix, but ignoring it on Windows.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=679691

 docs/reference/glib/glib-sections.txt |    1 +
 gio/gdbusaddress.c                    |   29 +-------
 gio/gdesktopappinfo.c                 |    4 +-
 gio/glib-compile-resources.c          |   14 ++---
 gio/tests/gdbus-connection-slow.c     |    9 +--
 gio/tests/gdbus-connection.c          |    2 +-
 glib/glib.symbols                     |    2 +
 glib/gmain.c                          |   12 ++-
 glib/gmain.h                          |    8 ++-
 glib/gspawn.c                         |  128 ++++++++++++++++++++++++++++++---
 glib/gspawn.h                         |   13 ++++
 11 files changed, 158 insertions(+), 64 deletions(-)
---
diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt
index 583b127..b78fdf9 100644
--- a/docs/reference/glib/glib-sections.txt
+++ b/docs/reference/glib/glib-sections.txt
@@ -1093,6 +1093,7 @@ GSpawnChildSetupFunc
 g_spawn_async_with_pipes
 g_spawn_async
 g_spawn_sync
+g_spawn_check_exit_status
 g_spawn_command_line_async
 g_spawn_command_line_sync
 g_spawn_close_pid
diff --git a/gio/gdbusaddress.c b/gio/gdbusaddress.c
index 4aa13b9..36997b1 100644
--- a/gio/gdbusaddress.c
+++ b/gio/gdbusaddress.c
@@ -43,7 +43,6 @@
 
 #ifdef G_OS_UNIX
 #include <gio/gunixsocketaddress.h>
-#include <sys/wait.h>
 #endif
 
 #ifdef G_OS_WIN32
@@ -1063,36 +1062,12 @@ get_session_address_dbus_launch (GError **error)
                                   &exit_status,
                                   error))
     {
-      g_prefix_error (error, _("Error spawning command line `%s': "), command_line);
-      goto out;
-    }
-
-  if (!WIFEXITED (exit_status))
-    {
-      gchar *escaped_stderr;
-      escaped_stderr = g_strescape (launch_stderr, "");
-      g_set_error (error,
-                   G_IO_ERROR,
-                   G_IO_ERROR_FAILED,
-                   _("Abnormal program termination spawning command line `%s': %s"),
-                   command_line,
-                   escaped_stderr);
-      g_free (escaped_stderr);
       goto out;
     }
 
-  if (WEXITSTATUS (exit_status) != 0)
+  if (!g_spawn_check_exit_status (exit_status, error))
     {
-      gchar *escaped_stderr;
-      escaped_stderr = g_strescape (launch_stderr, "");
-      g_set_error (error,
-                   G_IO_ERROR,
-                   G_IO_ERROR_FAILED,
-                   _("Command line `%s' exited with non-zero exit status %d: %s"),
-                   command_line,
-                   WEXITSTATUS (exit_status),
-                   escaped_stderr);
-      g_free (escaped_stderr);
+      g_prefix_error (error, _("Error spawning command line `%s': "), command_line);
       goto out;
     }
 
diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c
index 745940b..cde2d4a 100644
--- a/gio/gdesktopappinfo.c
+++ b/gio/gdesktopappinfo.c
@@ -26,7 +26,6 @@
 #include <errno.h>
 #include <string.h>
 #include <unistd.h>
-#include <sys/wait.h>
 
 #ifdef HAVE_CRT_EXTERNS_H
 #include <crt_externs.h>
@@ -1850,8 +1849,7 @@ update_program_done (GPid     pid,
 		     gpointer data)
 {
   /* Did the application exit correctly */
-  if (WIFEXITED (status) &&
-      WEXITSTATUS (status) == 0)
+  if (g_spawn_check_exit_status (status, NULL))
     {
       /* Here we could clean out any caches in use */
     }
diff --git a/gio/glib-compile-resources.c b/gio/glib-compile-resources.c
index a186563..fbe2c73 100644
--- a/gio/glib-compile-resources.c
+++ b/gio/glib-compile-resources.c
@@ -33,9 +33,6 @@
 #ifdef G_OS_WIN32
 #include <io.h>
 #endif
-#ifdef HAVE_SYS_WAIT_H
-#include <sys/wait.h>
-#endif
 
 #include <gio/gmemoryoutputstream.h>
 #include <gio/gzlibcompressor.h>
@@ -331,14 +328,14 @@ end_element (GMarkupParseContext  *context,
                   g_propagate_error (error, my_error);
                   goto cleanup;
                 }
-#ifdef HAVE_SYS_WAIT_H
-              if (!WIFEXITED (status) || WEXITSTATUS (status) != 0)
+	      
+	      /* Ugly...we shoud probably just let stderr be inherited */
+	      if (!g_spawn_check_exit_status (status, NULL))
                 {
                   g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                                _("Error processing input file with xmllint:\n%s"), stderr_child);
                   goto cleanup;
                 }
-#endif
 
               g_free (stderr_child);
               g_free (real_file);
@@ -387,14 +384,13 @@ end_element (GMarkupParseContext  *context,
                   g_propagate_error (error, my_error);
                   goto cleanup;
                 }
-#ifdef HAVE_SYS_WAIT_H
-              if (!WIFEXITED (status) || WEXITSTATUS (status) != 0)
+	      
+	      if (!g_spawn_check_exit_status (status, NULL))
                 {
                   g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
 			       _("Error processing input file with to-pixdata:\n%s"), stderr_child);
                   goto cleanup;
                 }
-#endif
 
               g_free (stderr_child);
               g_free (real_file);
diff --git a/gio/tests/gdbus-connection-slow.c b/gio/tests/gdbus-connection-slow.c
index a0a7b9c..b3c40f9 100644
--- a/gio/tests/gdbus-connection-slow.c
+++ b/gio/tests/gdbus-connection-slow.c
@@ -25,9 +25,6 @@
 #include <string.h>
 
 #include <sys/types.h>
-#ifdef HAVE_SYS_WAIT_H
-#include <sys/wait.h>
-#endif
 
 #include "gdbus-tests.h"
 
@@ -97,10 +94,8 @@ test_connection_flush (void)
                                        &exit_status,
                                        &error);
       g_assert_no_error (error);
-#ifdef HAVE_SYS_WAIT_H
-      g_assert (WIFEXITED (exit_status));
-      g_assert_cmpint (WEXITSTATUS (exit_status), ==, 0);
-#endif
+      g_spawn_check_exit_status (exit_status, &error);
+      g_assert_no_error (error);
       g_assert (ret);
 
       timeout_mainloop_id = g_timeout_add (1000, test_connection_flush_on_timeout, GUINT_TO_POINTER (n));
diff --git a/gio/tests/gdbus-connection.c b/gio/tests/gdbus-connection.c
index 353b09e..f4ba37d 100644
--- a/gio/tests/gdbus-connection.c
+++ b/gio/tests/gdbus-connection.c
@@ -141,7 +141,7 @@ test_connection_life_cycle (void)
    *
    */
   c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
-  _g_assert_error_domain (error, G_IO_ERROR);
+  g_assert (error != NULL);
   g_assert (!g_dbus_error_is_remote_error (error));
   g_assert (c == NULL);
   g_error_free (error);
diff --git a/glib/glib.symbols b/glib/glib.symbols
index 7f7a5ce..4d83f3d 100644
--- a/glib/glib.symbols
+++ b/glib/glib.symbols
@@ -980,6 +980,7 @@ g_spawn_command_line_async PRIVATE
 g_spawn_command_line_sync PRIVATE
 #endif
 g_spawn_error_quark
+g_spawn_exit_error_quark
 #ifndef _WIN64
 g_spawn_sync PRIVATE
 #endif
@@ -990,6 +991,7 @@ g_spawn_command_line_async_utf8
 g_spawn_command_line_sync_utf8
 g_spawn_sync_utf8
 #endif
+g_spawn_check_exit_status
 #if !defined(G_OS_UNIX) || defined(G_STDIO_NO_WRAP_ON_UNIX)
 /* gstdio wrappers */
 g_chmod
diff --git a/glib/gmain.c b/glib/gmain.c
index 682eb19..d9dc76e 100644
--- a/glib/gmain.c
+++ b/glib/gmain.c
@@ -4664,11 +4664,15 @@ g_child_watch_source_new (GPid pid)
  * If you obtain @pid from g_spawn_async() or g_spawn_async_with_pipes() 
  * you will need to pass #G_SPAWN_DO_NOT_REAP_CHILD as flag to 
  * the spawn function for the child watching to work.
+ *
+ * In many programs, you will want to call g_spawn_check_exit_status()
+ * in the callback to determine whether or not the child exited
+ * successfully.
  * 
- * Note that on platforms where #GPid must be explicitly closed
- * (see g_spawn_close_pid()) @pid must not be closed while the
- * source is still active. Typically, you will want to call
- * g_spawn_close_pid() in the callback function for the source.
+ * Also, note that on platforms where #GPid must be explicitly closed
+ * (see g_spawn_close_pid()) @pid must not be closed while the source
+ * is still active.  Typically, you should invoke g_spawn_close_pid()
+ * in the callback function for the source.
  * 
  * GLib supports only a single callback per process id.
  *
diff --git a/glib/gmain.h b/glib/gmain.h
index fadf7fc..4d4d504 100644
--- a/glib/gmain.h
+++ b/glib/gmain.h
@@ -139,11 +139,13 @@ typedef gboolean (*GSourceFunc)       (gpointer user_data);
 /**
  * GChildWatchFunc:
  * @pid: the process id of the child process
- * @status: Status information about the child process,
- *     see waitpid(2) for more information about this field
+ * @status: Status information about the child process, encoded
+ *     in a platform-specific manner
  * @user_data: user data passed to g_child_watch_add()
  *
- * The type of functions to be called when a child exists.
+ * Prototype of a #GChildWatchSource callback, called when a child
+ * process has exited.  To interpret @status, see the documentation
+ * for g_spawn_check_exit_status().
  */
 typedef void     (*GChildWatchFunc)   (GPid     pid,
                                        gint     status,
diff --git a/glib/gspawn.c b/glib/gspawn.c
index 5d8d57b..c8dbc4f 100644
--- a/glib/gspawn.c
+++ b/glib/gspawn.c
@@ -94,6 +94,12 @@ g_spawn_error_quark (void)
   return g_quark_from_static_string ("g-exec-error-quark");
 }
 
+GQuark
+g_spawn_exit_error_quark (void)
+{
+  return g_quark_from_static_string ("g-spawn-exit-error-quark");
+}
+
 /**
  * g_spawn_async:
  * @working_directory: (allow-none): child's current working directory, or %NULL to inherit parent's
@@ -232,13 +238,15 @@ read_data (GString *str,
  * if those parameters are non-%NULL. Note that you must set the  
  * %G_SPAWN_STDOUT_TO_DEV_NULL and %G_SPAWN_STDERR_TO_DEV_NULL flags when
  * passing %NULL for @standard_output and @standard_error.
- * If @exit_status is non-%NULL, the exit status of the child is stored
- * there as it would be returned by waitpid(); standard UNIX macros such 
- * as WIFEXITED() and WEXITSTATUS() must be used to evaluate the exit status.
- * Note that this function call waitpid() even if @exit_status is %NULL, and
- * does not accept the %G_SPAWN_DO_NOT_REAP_CHILD flag.
- * If an error occurs, no data is returned in @standard_output, 
- * @standard_error, or @exit_status. 
+ *
+ * If @exit_status is non-%NULL, the platform-specific exit status of
+ * the child is stored there; see the doucumentation of
+ * g_spawn_check_exit_status() for how to use and interpret this.
+ * Note that it is invalid to pass %G_SPAWN_DO_NOT_REAP_CHILD in
+ * @flags.
+ *
+ * If an error occurs, no data is returned in @standard_output,
+ * @standard_error, or @exit_status.
  *
  * This function calls g_spawn_async_with_pipes() internally; see that
  * function for full details on the other parameters and details on
@@ -701,9 +709,9 @@ g_spawn_async_with_pipes (const gchar          *working_directory,
  * appropriate. Possible errors are those from g_spawn_sync() and those
  * from g_shell_parse_argv().
  *
- * If @exit_status is non-%NULL, the exit status of the child is stored there as
- * it would be returned by waitpid(); standard UNIX macros such as WIFEXITED()
- * and WEXITSTATUS() must be used to evaluate the exit status.
+ * If @exit_status is non-%NULL, the platform-specific exit status of
+ * the child is stored there; see the documentation of
+ * g_spawn_check_exit_status() for how to use and interpret this.
  * 
  * On Windows, please note the implications of g_shell_parse_argv()
  * parsing @command_line. Parsing is done according to Unix shell rules, not 
@@ -793,6 +801,106 @@ g_spawn_command_line_async (const gchar *command_line,
   return retval;
 }
 
+/**
+ * g_spawn_check_exit_status:
+ * @exit_status: An exit code as returned from g_spawn_sync()
+ * @error: a #GError
+ *
+ * Set @error if @exit_status indicates the child exited abnormally
+ * (e.g. with a nonzero exit code, or via a fatal signal).
+ *
+ * The g_spawn_sync() and g_child_watch_add() family of APIs return an
+ * exit status for subprocesses encoded in a platform-specific way.
+ * On Unix, this is guaranteed to be in the same format
+ * <literal>waitpid(2)</literal> returns, and on Windows it is
+ * guaranteed to be the result of
+ * <literal>GetExitCodeProcess()</literal>.  Prior to the introduction
+ * of this function in GLib 2.34, interpreting @exit_status required
+ * use of platform-specific APIs, which is problematic for software
+ * using GLib as a cross-platform layer.
+ *
+ * Additionally, many programs simply want to determine whether or not
+ * the child exited successfully, and either propagate a #GError or
+ * print a message to standard error.  In that common case, this
+ * function can be used.  Note that the error message in @error will
+ * contain human-readable information about the exit status.
+ *
+ * The <literal>domain</literal> and <literal>code</literal> of @error
+ * have special semantics in the case where the process has an "exit
+ * code", as opposed to being killed by a signal.  On Unix, this
+ * happens if <literal>WIFEXITED</literal> would be true of
+ * @exit_status.  On Windows, it is always the case.
+ *
+ * The special semantics are that the actual exit code will be the
+ * code set in @error, and the domain will be %G_SPAWN_EXIT_ERROR.
+ * This allows you to differentiate between different exit codes.
+ *
+ * If the process was terminated by some means other than an exit
+ * status, the domain will be %G_SPAWN_ERROR, and the code will be
+ * %G_SPAWN_ERROR_FAILED.
+ *
+ * This function just offers convenience; you can of course also check
+ * the available platform via a macro such as %G_OS_UNIX, and use
+ * <literal>WIFEXITED()</literal> and <literal>WEXITSTATUS()</literal>
+ * on @exit_status directly.  Do not attempt to scan or parse the
+ * error message string; it may be translated and/or change in future
+ * versions of GLib.
+ *
+ * Returns: %TRUE if child exited successfully, %FALSE otherwise (and @error will be set)
+ * Since: 2.34
+ */
+gboolean
+g_spawn_check_exit_status (gint      exit_status,
+			   GError  **error)
+{
+  gboolean ret = FALSE;
+
+#ifdef G_OS_UNIX
+  if (WIFEXITED (exit_status))
+    {
+      if (WEXITSTATUS (exit_status) != 0)
+	{
+	  g_set_error (error, G_SPAWN_EXIT_ERROR, WEXITSTATUS (exit_status),
+		       _("Child process exited with code %ld"),
+		       (long) WEXITSTATUS (exit_status));
+	  goto out;
+	}
+    }
+  else if (WIFSIGNALED (exit_status))
+    {
+      g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
+		   _("Child process killed by signal %ld"),
+		   (long) WTERMSIG (exit_status));
+      goto out;
+    }
+  else if (WIFSTOPPED (exit_status))
+    {
+      g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
+		   _("Child process stopped by signal %ld"),
+		   (long) WSTOPSIG (exit_status));
+      goto out;
+    }
+  else
+    {
+      g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
+		   _("Child process exited abnormally"));
+      goto out;
+    }
+#else
+  if (exit_status != 0)
+    {
+      g_set_error (error, G_SPAWN_EXIT_ERROR, exit_status,
+		   _("Child process exited with code %ld"),
+		   (long) exit_status);
+      goto out;
+    }
+#endif
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
 static gint
 exec_err_to_g_error (gint en)
 {
diff --git a/glib/gspawn.h b/glib/gspawn.h
index a46db09..4036fa6 100644
--- a/glib/gspawn.h
+++ b/glib/gspawn.h
@@ -97,6 +97,14 @@ typedef enum
 } GSpawnError;
 
 /**
+ * G_SPAWN_EXIT_ERROR:
+ *
+ * Error domain used by g_spawn_check_exit_status().  The code
+ * will be the the program exit code.
+ */
+#define G_SPAWN_EXIT_ERROR g_spawn_exit_error_quark ()
+
+/**
  * GSpawnChildSetupFunc:
  * @user_data: user data to pass to the function.
  *
@@ -176,6 +184,7 @@ typedef enum
 } GSpawnFlags;
 
 GQuark g_spawn_error_quark (void);
+GQuark g_spawn_exit_error_quark (void);
 
 #ifndef __GTK_DOC_IGNORE__
 #ifdef G_OS_WIN32
@@ -236,6 +245,10 @@ gboolean g_spawn_command_line_sync  (const gchar          *command_line,
 gboolean g_spawn_command_line_async (const gchar          *command_line,
                                      GError              **error);
 
+GLIB_AVAILABLE_IN_2_34
+gboolean g_spawn_check_exit_status (gint      exit_status,
+				    GError  **error);
+
 void g_spawn_close_pid (GPid pid);
 
 G_END_DECLS



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]