glib outstanding stuff



Hi,

This patch includes:

 - g_find_program_in_path ()

   Finds an executable in PATH. People suggested a function which
   would take the env variable name as the second arg, instead of 
   hardcoding a path; I didn't do that because PATH has to be 
   special cased in various ways, see the implementation. We could
   maybe add a different function that works that way.

 - g_file_test ()
   
   Only has simple tests that we definitely need; we can add more
   later.

 - g_file_get_contents ()

   Read an entire file into a string

 - fix g_unichar_isspace()

 - gspawn.[hc] (was gexec.[hc])
 
   Ullrich told me about the new spawn() routine being added to POSIX
   which is analagous to these routines, and Tor suggested that
   spawn() is more cross-platform, so I renamed this stuff to spawn
   rather than exec. Otherwise should be pretty much the same as
   previous postings.

These all have API docs, so read those in the patch for more details.

Tim said something like "I don't care about gspawn as long as it
works," Owen already reviewed gspawn in detail, and the other changes
are trivial things, so I'm planning to commit this patch in a few days
if no one objects.

Havoc

Index: ChangeLog
===================================================================
RCS file: /cvs/gnome/glib/ChangeLog,v
retrieving revision 1.480
diff -u -u -r1.480 ChangeLog
--- ChangeLog	2000/09/28 13:11:23	1.480
+++ ChangeLog	2000/10/04 22:53:06
@@ -1,3 +1,15 @@
+2000-10-04  Havoc Pennington  <hp redhat com>
+
+	* gspawn.h, gspawn.c: New fork/exec API, and routines to parse
+	argv and quote/unquote stuff for the shell
+
+	* guniprop.c (g_unichar_isspace): Return TRUE for the 
+	ASCII space characters isspace() returns TRUE for.
+
+	* gfileutils.c (g_file_get_contents): Convenience function 
+	to slurp entire file into a string and return it.
+	(g_file_test): file test function
+
 2000-09-28  Sebastian Wilhelmi  <wilhelmi ira uka de>
 
 	* configure.in: Adjusted the test for an unimplemented
Index: Makefile.am
===================================================================
RCS file: /cvs/gnome/glib/Makefile.am,v
retrieving revision 1.54
diff -u -u -r1.54 Makefile.am
--- Makefile.am	2000/09/25 15:46:12	1.54
+++ Makefile.am	2000/10/04 22:53:06
@@ -49,6 +49,7 @@
 	gdataset.c		\
 	gdate.c         	\
 	gerror.c		\
+	gfileutils.c		\
 	ghash.c			\
 	ghook.c			\
 	giochannel.c    	\
@@ -64,6 +65,7 @@
 	grand.c			\
 	gscanner.c		\
 	gslist.c		\
+	gspawn.c		\
 	gstrfuncs.c		\
 	gstring.c		\
 	gthread.c       	\
@@ -81,8 +83,10 @@
 glibincludedir=$(includedir)/glib-2.0
 glibinclude_HEADERS =   \
 	gerror.h	\
+	gfileutils.h	\
 	glib.h 		\
 	glib-object.h	\
+	gspawn.h	\
 	gunicode.h
 
 configexecincludedir = $(libdir)/glib-2.0/include
Index: gfileutils.c
===================================================================
RCS file: gfileutils.c
diff -N gfileutils.c
--- /dev/null	Tue May  5 16:32:27 1998
+++ gfileutils.c	Wed Oct  4 18:53:06 2000
@@ -0,0 +1,253 @@
+/* gfileutils.c - File utility functions
+ *
+ *  Copyright 2000 Red Hat, Inc.
+ *
+ * GLib is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * GLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GLib; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#include "glib.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#define _(x) x
+
+/**
+ * g_file_test:
+ * @filename: a filename to test
+ * @test: bitfield of #GFileTest flags
+ * 
+ * Returns TRUE if any of the tests in the bitfield @test are
+ * TRUE. For example, (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)
+ * will return TRUE if the file exists; the check whether it's
+ * a directory doesn't matter since the existence test is TRUE.
+ * With the current set of available tests, there's no point
+ * passing in more than one test at a time.
+ *
+ * Return value: whether a test was TRUE
+ **/
+gboolean
+g_file_test (const gchar *filename,
+             GFileTest    test)
+{
+  struct stat s;
+
+  if (test & G_FILE_TEST_EXISTS)
+    return (access (filename, F_OK) == 0);
+  else if (test & G_FILE_TEST_IS_EXECUTABLE)
+    return (access (filename, X_OK) == 0);
+  else
+    {
+      if (stat (filename, &s) < 0)
+        return FALSE;
+      
+      if ((test & G_FILE_TEST_IS_DIR) &&
+          S_ISDIR (s.st_mode))
+        return TRUE;
+      else if ((test & G_FILE_TEST_IS_SYMLINK) &&
+               S_ISLNK (s.st_mode))
+        return TRUE;
+      else
+        return FALSE;
+    }
+}
+
+GQuark
+g_file_error_quark ()
+{
+  static GQuark q = 0;
+  if (q == 0)
+    q = g_quark_from_static_string ("g-file-error-quark");
+
+  return q;
+}
+
+static GFileError
+errno_to_g_file_error (gint en)
+{
+  switch (en)
+    {
+#ifdef EEXIST
+    case EEXIST:
+      return G_FILE_ERROR_EXIST;
+      break;
+#endif
+
+#ifdef EISDIR
+    case EISDIR:
+      return G_FILE_ERROR_ISDIR;
+      break;
+#endif
+
+#ifdef EACCES
+    case EACCES:
+      return G_FILE_ERROR_ACCES;
+      break;
+#endif
+
+#ifdef ENAMETOOLONG
+    case ENAMETOOLONG:
+      return G_FILE_ERROR_NAMETOOLONG;
+      break;
+#endif
+
+#ifdef ENOENT
+    case ENOENT:
+      return G_FILE_ERROR_NOENT;
+      break;
+#endif
+
+#ifdef ENOTDIR
+    case ENOTDIR:
+      return G_FILE_ERROR_NOTDIR;
+      break;
+#endif
+
+#ifdef ENXIO
+    case ENXIO:
+      return G_FILE_ERROR_NXIO;
+      break;
+#endif
+
+#ifdef ENODEV
+    case ENODEV:
+      return G_FILE_ERROR_NODEV;
+      break;
+#endif
+
+#ifdef EROFS
+    case EROFS:
+      return G_FILE_ERROR_ROFS;
+      break;
+#endif
+
+#ifdef ETXTBSY
+    case ETXTBSY:
+      return G_FILE_ERROR_TXTBSY;
+      break;
+#endif
+
+#ifdef EFAULT
+    case EFAULT:
+      return G_FILE_ERROR_FAULT;
+      break;
+#endif
+
+#ifdef ELOOP
+    case ELOOP:
+      return G_FILE_ERROR_LOOP;
+      break;
+#endif
+
+#ifdef ENOSPC
+    case ENOSPC:
+      return G_FILE_ERROR_NOSPC;
+      break;
+#endif
+
+#ifdef ENOMEM
+    case ENOMEM:
+      return G_FILE_ERROR_NOMEM;
+      break;
+#endif
+
+#ifdef EMFILE
+    case EMFILE:
+      return G_FILE_ERROR_MFILE;
+      break;
+#endif
+
+#ifdef ENFILE
+    case ENFILE:
+      return G_FILE_ERROR_NFILE;
+      break;
+#endif
+
+    default:
+      return G_FILE_ERROR_FAILED;
+      break;
+    }
+
+  return G_FILE_ERROR_FAILED;
+}
+
+/**
+ * g_file_get_contents:
+ * @filename: a file to read contents from
+ * @error: return location for a #GError
+ * 
+ * Reads an entire file into allocated memory, with good error
+ * checking. Returns NULL on failure.
+ *
+ * FIXME currently crashes if the file is too big to fit in memory;
+ * should probably use g_try_malloc() when we have that function.
+ * 
+ * Return value: contents of file, or NULL
+ **/
+gchar*
+g_file_get_contents (const gchar *filename,
+                     GError     **error)
+{
+  FILE *f;
+  gchar buf[1024];
+  size_t bytes;
+  GString *str;
+  
+  g_return_val_if_fail (filename != NULL, NULL);
+  
+  f = fopen (filename, "r");
+
+  if (f == NULL)
+    {
+      g_set_error (error,
+                   G_FILE_ERROR,
+                   errno_to_g_file_error (errno),
+                   _("Failed to open file '%s': %s"),
+                   filename, strerror (errno));
+
+      return NULL;
+    }
+
+  str = g_string_new ("");
+  
+  while (!feof (f))
+    {
+      bytes = fread (buf, 1, 1024, f);
+      
+      if (ferror (f))
+        {
+          g_set_error (error,
+                       G_FILE_ERROR,
+                       errno_to_g_file_error (errno),
+                       _("Error reading file '%s': %s"),
+                       filename, strerror (errno));
+
+          g_string_free (str, TRUE);
+          
+          return NULL;
+        }
+
+      g_string_append_len (str, buf, bytes);
+    }
+
+  fclose (f);
+
+  return g_string_free (str, FALSE);
+}
+
Index: gfileutils.h
===================================================================
RCS file: gfileutils.h
diff -N gfileutils.h
--- /dev/null	Tue May  5 16:32:27 1998
+++ gfileutils.h	Wed Oct  4 18:53:06 2000
@@ -0,0 +1,74 @@
+/* gfileutils.h - File utility functions
+ *
+ *  Copyright 2000 Red Hat, Inc.
+ *
+ * GLib is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * GLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GLib; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GFILEUTILS_H__
+#define __GFILEUTILS_H__
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define G_FILE_ERROR g_file_error_quark ()
+
+typedef enum
+{
+  G_FILE_ERROR_EXIST,
+  G_FILE_ERROR_ISDIR,
+  G_FILE_ERROR_ACCES,
+  G_FILE_ERROR_NAMETOOLONG,
+  G_FILE_ERROR_NOENT,
+  G_FILE_ERROR_NOTDIR,
+  G_FILE_ERROR_NXIO,
+  G_FILE_ERROR_NODEV,
+  G_FILE_ERROR_ROFS,
+  G_FILE_ERROR_TXTBSY,
+  G_FILE_ERROR_FAULT,
+  G_FILE_ERROR_LOOP,
+  G_FILE_ERROR_NOSPC,
+  G_FILE_ERROR_NOMEM,
+  G_FILE_ERROR_MFILE,
+  G_FILE_ERROR_NFILE,
+  G_FILE_ERROR_FAILED
+} GFileError;
+
+typedef enum
+{
+  G_FILE_TEST_IS_DIR        = 1 << 1,
+  G_FILE_TEST_EXISTS        = 1 << 2,
+  G_FILE_TEST_IS_EXECUTABLE = 1 << 3,
+  G_FILE_TEST_IS_SYMLINK    = 1 << 4
+} GFileTest;
+
+GQuark g_file_error_quark ();
+
+gboolean g_file_test         (const gchar  *filename,
+                              GFileTest     test);
+gchar*   g_file_get_contents (const gchar  *filename,
+                              GError      **error);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __GFILEUTILS_H__ */
+
+
Index: glib.h
===================================================================
RCS file: /cvs/gnome/glib/glib.h,v
retrieving revision 1.201
diff -u -u -r1.201 glib.h
--- glib.h	2000/09/26 16:56:52	1.201
+++ glib.h	2000/10/04 22:53:06
@@ -1831,6 +1831,8 @@
  */
 void	g_atexit		(GVoidFunc    func);
 
+/* Look for an executable in PATH, following execvp() rules */
+gchar*  g_find_program_in_path  (const gchar *program);
 
 /* Bit tests
  */
@@ -3469,5 +3471,8 @@
 #endif /* __cplusplus */
 
 #include <gunicode.h>
+#include <gerror.h>
+#include <gspawn.h>
+#include <gfileutils.h>
 
 #endif /* __G_LIB_H__ */
Index: gspawn.c
===================================================================
RCS file: gspawn.c
diff -N gspawn.c
--- /dev/null	Tue May  5 16:32:27 1998
+++ gspawn.c	Wed Oct  4 18:53:06 2000
@@ -0,0 +1,1984 @@
+/* gexec.c - Process launching
+ *
+ *  Copyright 2000 Red Hat, Inc.
+ *  g_execvpe implementation based on GNU libc execvp:
+ *   Copyright 1991, 92, 95, 96, 97, 98, 99 Free Software Foundation, Inc.
+ *
+ * GLib is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * GLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GLib; see the file COPYING.LIB.  If not, write
+ * to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "glib.h"
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <string.h>
+
+#ifdef _
+#warning "FIXME remove gettext hack"
+#endif
+
+#define _(x) x
+
+static gint g_execute (const gchar  *file,
+                       gchar **argv,
+                       gchar **envp,
+                       gboolean search_path);
+
+static gboolean make_pipe            (gint                  p[2],
+                                      GError              **error);
+static gboolean fork_exec_with_pipes (gboolean              intermediate_child,
+                                      const gchar          *working_directory,
+                                      gchar               **argv,
+                                      gchar               **envp,
+                                      gboolean              close_descriptors,
+                                      gboolean              search_path,
+                                      gboolean              stdout_to_null,
+                                      gboolean              stderr_to_null,
+                                      gboolean              child_inherits_stdin,
+                                      GSpawnChildSetupFunc  child_setup,
+                                      gpointer              user_data,
+                                      gint                 *child_pid,
+                                      gint                 *standard_input,
+                                      gint                 *standard_output,
+                                      gint                 *standard_error,
+                                      GError              **error);
+
+
+GQuark
+g_spawn_error_quark (void)
+{
+  static GQuark quark = 0;
+  if (quark == 0)
+    quark = g_quark_from_static_string ("g-exec-error-quark");
+  return quark;
+}
+
+
+/* Single quotes preserve the literal string exactly. escape
+ * sequences are not allowed; not even \' - if you want a '
+ * in the quoted text, you have to do something like 'foo'\''bar'
+ *
+ * Double quotes allow $ ` " \ and newline to be escaped with backslash.
+ * Otherwise double quotes preserve things literally.
+ */
+
+gboolean 
+unquote_string_inplace (gchar* str, gchar** end, GError** err)
+{
+  gchar* dest;
+  gchar* s;
+  gchar quote_char;
+  
+  g_return_val_if_fail(end != NULL, FALSE);
+  g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
+  g_return_val_if_fail(str != NULL, FALSE);
+  
+  dest = s = str;
+
+  quote_char = *s;
+  
+  if (!(*s == '"' || *s == '\''))
+    {
+      if (err)
+        *err = g_error_new(G_SPAWN_ERROR,
+                           G_SPAWN_ERROR_PARSE,
+                           _("Quoted text doesn't begin with a quotation mark"));
+      *end = str;
+      return FALSE;
+    }
+
+  /* Skip the initial quote mark */
+  ++s;
+
+  if (quote_char == '"')
+    {
+      while (*s)
+        {
+          g_assert(s > dest); /* loop invariant */
+      
+          switch (*s)
+            {
+            case '"':
+              /* End of the string, return now */
+              *dest = '\0';
+              ++s;
+              *end = s;
+              return TRUE;
+              break;
+
+            case '\\':
+              /* Possible escaped quote or \ */
+              ++s;
+              switch (*s)
+                {
+                case '"':
+                case '\\':
+                case '`':
+                case '$':
+                case '\n':
+                  *dest = *s;
+                  ++s;
+                  ++dest;
+                  break;
+
+                default:
+                  /* not an escaped char */
+                  *dest = '\\';
+                  ++dest;
+                  /* ++s already done. */
+                  break;
+                }
+              break;
+
+            default:
+              *dest = *s;
+              ++dest;
+              ++s;
+              break;
+            }
+
+          g_assert(s > dest); /* loop invariant */
+        }
+    }
+  else
+    {
+      while (*s)
+        {
+          g_assert(s > dest); /* loop invariant */
+          
+          if (*s == '\'')
+            {
+              /* End of the string, return now */
+              *dest = '\0';
+              ++s;
+              *end = s;
+              return TRUE;
+            }
+          else
+            {
+              *dest = *s;
+              ++dest;
+              ++s;
+            }
+
+          g_assert(s > dest); /* loop invariant */
+        }
+    }
+  
+  /* If we reach here this means the close quote was never encountered */
+
+  *dest = '\0';
+  
+  if (err)
+    *err = g_error_new(G_SPAWN_ERROR,
+                       G_SPAWN_ERROR_PARSE,
+                       _("Unmatched quotation mark in command line or other shell-quoted text"));
+  *end = s;
+  return FALSE;
+}
+
+/**
+ * g_shell_quote:
+ * @unquoted_string: a literal string
+ * 
+ * Quotes a string so that the shell (/bin/sh) will interpret the
+ * quoted string to mean @unquoted_string. If you pass a filename to
+ * the shell, for example, you should first quote it with this
+ * function.  The return value must be freed with g_free(). The
+ * quoting style used is undefined (single or double quotes may be
+ * used).
+ * 
+ * Return value: quoted string
+ **/
+gchar*
+g_shell_quote (const gchar *unquoted_string)
+{
+  /* We always use single quotes, because the algorithm is cheesier.
+   * We could use double if we felt like it, that might be more
+   * human-readable.
+   */
+
+  const gchar *p;
+  GString *dest;
+
+  g_return_val_if_fail (unquoted_string != NULL, NULL);
+  
+  dest = g_string_new ("'");
+
+  p = unquoted_string;
+
+  /* could speed this up a lot by appending chunks of text at a
+   * time.
+   */
+  while (*p)
+    {
+      /* Replace literal ' with a close ', a \', and a open ' */
+      if (*p == '\'')
+        g_string_append (dest, "'\''");
+      else
+        g_string_append_c (dest, *p);
+
+      ++p;
+    }
+
+  /* close the quote */
+  g_string_append_c (dest, '\'');
+  
+  return g_string_free (dest, FALSE);
+}
+
+/**
+ * g_shell_unquote:
+ * @quoted_string: shell-quoted string
+ * @error: error return location or NULL
+ * 
+ * Unquotes a string as the shell (/bin/sh) would. Only handles
+ * quotes; if a string contains file globs, arithmetic operators,
+ * variables, backticks, redirections, or other special-to-the-shell
+ * features, the result will be different from the result a real shell
+ * would produce (the variables, backticks, etc. will be passed
+ * through literally instead of being expanded). This function is
+ * guaranteed to succeed if applied to the result of
+ * g_shell_quote(). If it fails, it returns NULL and sets the
+ * error. The @quoted_string need not actually contain quoted or
+ * escaped text; g_shell_unquote() simply goes through the string and
+ * unquotes/unescapes anything that the shell would. Both single and
+ * double quotes are handled, as are escapes including escaped
+ * newlines. The return value must be freed with g_free().
+ * 
+ * Return value: an unquoted string
+ **/
+gchar*
+g_shell_unquote (const gchar *quoted_string,
+                 GError **error)
+{
+  gchar *unquoted;
+  gchar *end;
+  gchar *start;
+  GString *retval;
+  
+  g_return_val_if_fail (quoted_string != NULL, NULL);
+  
+  unquoted = g_strdup (quoted_string);
+
+  start = unquoted;
+  end = unquoted;
+  retval = g_string_new ("");
+
+  /* The loop allows cases such as
+   * "foo"blah blah'bar'woo foo"baz"la la la\'\''foo'
+   */
+  while (*start)
+    {
+      /* Append all non-quoted chars, honoring backslash escape
+       */
+      
+      while (*start && !(*start == '"' || *start == '\''))
+        {
+          if (*start == '\\')
+            {
+              /* all characters can get escaped by backslash,
+               * except newline, which is removed if it follows
+               * a backslash outside of quotes
+               */
+              
+              ++start;
+              if (*start)
+                {
+                  if (*start != '\n')
+                    g_string_append_c (retval, *start);
+                  ++start;
+                }
+            }
+          else
+            {
+              g_string_append_c (retval, *start);
+              ++start;
+            }
+        }
+
+      if (*start)
+        {
+          if (!unquote_string_inplace (start, &end, error))
+            {
+              goto error;
+            }
+          else
+            {
+              g_string_append (retval, start);
+              start = end;
+            }
+        }
+    }
+
+  return g_string_free (retval, FALSE);
+  
+ error:
+  g_assert (error == NULL || *error != NULL);
+  
+  g_free (unquoted);
+  g_string_free (retval, TRUE);
+  return NULL;
+}
+
+/* g_parse_argv() does a semi-arbitrary weird subset of the way
+ * the shell parses a command line. We don't do variable expansion,
+ * don't understand that operators are tokens, don't do tilde expansion,
+ * don't do command substitution, no arithmetic expansion, IFS gets ignored,
+ * don't do filename globs, don't remove redirection stuff, etc.
+ *
+ * READ THE UNIX98 SPEC on "Shell Command Language" before changing
+ * the behavior of this code.
+ *
+ * Steps to parsing the argv string:
+ *
+ *  - tokenize the string (but since we ignore operators,
+ *    our tokenization may diverge from what the shell would do)
+ *    note that tokenization ignores the internals of a quoted
+ *    word and it always splits on spaces, not on IFS even
+ *    if we used IFS. We also ignore "end of input indicator"
+ *    (I guess this is control-D?)
+ *
+ *    Tokenization steps, from UNIX98 with operator stuff removed,
+ *    are:
+ * 
+ *    1) "If the current character is backslash, single-quote or
+ *        double-quote (\, ' or ") and it is not quoted, it will affect
+ *        quoting for subsequent characters up to the end of the quoted
+ *        text. The rules for quoting are as described in Quoting
+ *        . During token recognition no substitutions will be actually
+ *        performed, and the result token will contain exactly the
+ *        characters that appear in the input (except for newline
+ *        character joining), unmodified, including any embedded or
+ *        enclosing quotes or substitution operators, between the quote
+ *        mark and the end of the quoted text. The token will not be
+ *        delimited by the end of the quoted field."
+ *
+ *    2) "If the current character is an unquoted newline character,
+ *        the current token will be delimited."
+ *
+ *    3) "If the current character is an unquoted blank character, any
+ *        token containing the previous character is delimited and the
+ *        current character will be discarded."
+ *
+ *    4) "If the previous character was part of a word, the current
+ *        character will be appended to that word."
+ *
+ *    5) "If the current character is a "#", it and all subsequent
+ *        characters up to, but excluding, the next newline character
+ *        will be discarded as a comment. The newline character that
+ *        ends the line is not considered part of the comment. The
+ *        "#" starts a comment only when it is at the beginning of a
+ *        token. Since the search for the end-of-comment does not
+ *        consider an escaped newline character specially, a comment
+ *        cannot be continued to the next line."
+ *
+ *    6) "The current character will be used as the start of a new word."
+ *
+ *
+ *  - for each token (word), perform portions of word expansion, namely
+ *    field splitting (using default whitespace IFS) and quote
+ *    removal.  Field splitting may increase the number of words.
+ *    Quote removal does not increase the number of words.
+ *
+ *   "If the complete expansion appropriate for a word results in an
+ *   empty field, that empty field will be deleted from the list of
+ *   fields that form the completely expanded command, unless the
+ *   original word contained single-quote or double-quote characters."
+ *    - UNIX98 spec
+ *
+ *
+ */
+
+static inline void
+ensure_token (GString **token)
+{
+  if (*token == NULL)
+    *token = g_string_new ("");
+}
+
+static void
+delimit_token (GString **token,
+               GSList **retval)
+{
+  if (*token == NULL)
+    return;
+
+  *retval = g_slist_prepend (*retval, g_string_free (*token, FALSE));
+
+  *token = NULL;
+}
+
+static GSList*
+tokenize_command_line (const gchar *command_line,
+                       GError **error)
+{
+  gchar current_quote;
+  const gchar *p;
+  GString *current_token = NULL;
+  GSList *retval = NULL;
+  
+  current_quote = '\0';
+  p = command_line;
+
+  while (*p)
+    {
+      if (current_quote == '\\')
+        {
+          if (*p == '\n')
+            {
+              /* we append nothing; backslash-newline become nothing */
+            }
+          else
+            {
+              /* we append the backslash and the current char,
+               * to be interpreted later after tokenization
+               */
+              ensure_token (&current_token);
+              g_string_append_c (current_token, '\\');
+              g_string_append_c (current_token, *p);
+            }
+
+          current_quote = '\0';
+        }
+      else if (current_quote == '#')
+        {
+          /* Discard up to and including next newline */
+          while (*p && *p != '\n')
+            ++p;
+
+          current_quote = '\0';
+          
+          if (*p == '\0')
+            break;
+        }
+      else if (current_quote)
+        {
+          if (*p == current_quote &&
+              /* check that it isn't an escaped double quote */
+              !(current_quote == '"' && p != command_line && *(p - 1) == '\\'))
+            {
+              /* close the quote */
+              current_quote = '\0';
+            }
+
+          /* Everything inside quotes, and the close quote,
+           * gets appended literally.
+           */
+
+          ensure_token (&current_token);
+          g_string_append_c (current_token, *p);
+        }
+      else
+        {
+          switch (*p)
+            {
+            case '\n':
+              delimit_token (&current_token, &retval);
+              break;
+
+            case ' ':
+            case '\t':
+              /* If the current token contains the previous char, delimit
+               * the current token. A nonzero length
+               * token should always contain the previous char.
+               */
+              if (current_token &&
+                  current_token->len > 0)
+                {
+                  delimit_token (&current_token, &retval);
+                }
+              
+              /* discard all unquoted blanks (don't add them to a token) */
+              break;
+
+
+              /* single/double quotes are appended to the token,
+               * escapes are maybe appended next time through the loop,
+               * comment chars are never appended.
+               */
+              
+            case '\'':
+            case '"':
+              ensure_token (&current_token);
+              g_string_append_c (current_token, *p);
+
+              /* FALL THRU */
+              
+            case '#':
+            case '\\':
+              current_quote = *p;
+              break;
+
+            default:
+              /* Combines rules 4) and 6) - if we have a token, append to it,
+               * otherwise create a new token.
+               */
+              ensure_token (&current_token);
+              g_string_append_c (current_token, *p);
+              break;
+            }
+        }
+
+      ++p;
+    }
+
+  delimit_token (&current_token, &retval);
+
+  if (current_quote)
+    {
+      if (current_quote == '\\')
+        g_set_error (error,
+                     G_SPAWN_ERROR,
+                     G_SPAWN_ERROR_PARSE,
+                     _("Text ended just after a '\' character."
+                       " (The text was '%s')"),
+                     command_line);
+      else
+        g_set_error (error,
+                     G_SPAWN_ERROR,
+                     G_SPAWN_ERROR_PARSE,
+                     _("Text ended before matching quote was found for %c."
+                       " (The text was '%s')"),
+                     current_quote, command_line);
+      
+      goto error;
+    }
+
+  if (retval == NULL)
+    {
+      g_set_error (error,
+                   G_SPAWN_ERROR,
+                   G_SPAWN_ERROR_PARSE,
+                   _("Text was empty (or contained only whitespace)"));
+
+      goto error;
+    }
+  
+  /* we appended backward */
+  retval = g_slist_reverse (retval);
+
+  return retval;
+
+ error:
+  g_assert (error == NULL || *error != NULL);
+  
+  if (retval)
+    {
+      g_slist_foreach (retval, (GFunc)g_free, NULL);
+      g_slist_free (retval);
+    }
+
+  return NULL;
+}
+
+gboolean
+g_parse_argv (const gchar *command_line,
+              gint        *argcp,
+              gchar     ***argvp,
+              GError     **error)
+{
+  /* Code based on poptParseArgvString() from libpopt */
+  gint argc = 0;
+  gchar **argv = NULL;
+  GSList *tokens = NULL;
+  gint i;
+  GSList *tmp_list;
+  
+  g_return_val_if_fail (command_line != NULL, FALSE);
+
+  tokens = tokenize_command_line (command_line, error);
+  if (tokens == NULL)
+    return FALSE;
+
+  /* Because we can't have introduced any new blank space into the
+   * tokens (we didn't do any new expansions), we don't need to
+   * perform field splitting. If we were going to honor IFS or do any
+   * expansions, we would have to do field splitting on each word
+   * here. Also, if we were going to do any expansion we would need to
+   * remove any zero-length words that didn't contain quotes
+   * originally; but since there's no expansion we know all words have
+   * nonzero length, unless they contain quotes.
+   * 
+   * So, we simply remove quotes, and don't do any field splitting or
+   * empty word removal, since we know there was no way to introduce
+   * such things.
+   */
+
+  argc = g_slist_length (tokens);
+  argv = g_new0 (gchar*, argc + 1);
+  i = 0;
+  tmp_list = tokens;
+  while (tmp_list)
+    {
+      argv[i] = g_shell_unquote (tmp_list->data, error);
+
+      /* Since we already checked that quotes matched up in the
+       * tokenizer, this shouldn't be possible to reach I guess.
+       */
+      if (argv[i] == NULL)
+        goto failed;
+
+      tmp_list = g_slist_next (tmp_list);
+      ++i;
+    }
+  
+  g_slist_foreach (tokens, (GFunc)g_free, NULL);
+  g_slist_free (tokens);
+  
+  if (argcp)
+    *argcp = argc;
+
+  if (argvp)
+    *argvp = argv;
+  else
+    g_strfreev (argv);
+
+  return TRUE;
+
+ failed:
+
+  g_assert (error == NULL || *error != NULL);
+  g_strfreev (argv);
+  g_slist_foreach (tokens, (GFunc) g_free, NULL);
+  g_slist_free (tokens);
+  
+  return FALSE;
+}
+
+/**
+ * g_spawn_async:
+ * @working_directory: child's current working directory, or NULL to inherit parent's
+ * @argv: child's argument vector
+ * @envp: child's environment, or NULL to inherit parent's
+ * @flags: flags from #GSpawnFlags
+ * @child_setup: function to run in the child just before exec()
+ * @user_data: user data for @child_setup
+ * @child_pid: return location for child process ID, or NULL
+ * @error: return location for error
+ * 
+ * See g_spawn_async_with_pipes() for a full description; this function
+ * simply calls the g_spawn_async_with_pipes() without any pipes.
+ * 
+ * Return value: TRUE on success, FALSE if error is set
+ **/
+gboolean
+g_spawn_async (const gchar          *working_directory,
+               gchar               **argv,
+               gchar               **envp,
+               GSpawnFlags           flags,
+               GSpawnChildSetupFunc  child_setup,
+               gpointer              user_data,
+               gint                 *child_pid,
+               GError              **error)
+{
+  g_return_val_if_fail (argv != NULL, FALSE);
+  
+  return g_spawn_async_with_pipes (working_directory,
+                                   argv, envp,
+                                   flags,
+                                   child_setup,
+                                   user_data,
+                                   child_pid,
+                                   NULL, NULL, NULL,
+                                   error);
+}
+
+/* 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 gint
+close_and_invalidate (gint *fd)
+{
+  gint ret;
+
+  ret = close (*fd);
+  *fd = -1;
+
+  return ret;
+}
+
+typedef enum
+{
+  READ_FAILED = 0, /* FALSE */
+  READ_OK,
+  READ_EOF
+} ReadResult;
+
+static ReadResult
+read_data (GString *str,
+           gint     fd,
+           GError **error)
+{
+  gint bytes;
+  gchar buf[4096];
+
+ again:
+  
+  bytes = read (fd, &buf, 4096);
+
+  if (bytes == 0)
+    return READ_EOF;
+  else if (bytes > 0)
+    {
+      g_string_append_len (str, buf, bytes);
+      return READ_OK;
+    }
+  else if (bytes < 0 && errno == EINTR)
+    goto again;
+  else if (bytes < 0)
+    {
+      g_set_error (error,
+                   G_SPAWN_ERROR,
+                   G_SPAWN_ERROR_READ,
+                   _("Failed to read data from child process (%s)"),
+                   g_strerror (errno));
+      
+      return READ_FAILED;
+    }
+  else
+    return READ_OK;
+}
+
+/**
+ * g_spawn_sync:
+ * @working_directory: child's current working directory, or NULL to inherit parent's
+ * @argv: child's argument vector
+ * @envp: child's environment, or NULL to inherit parent's
+ * @flags: flags from #GSpawnFlags
+ * @child_setup: function to run in the child just before exec()
+ * @user_data: user data for @child_setup
+ * @standard_output: return location for child output 
+ * @standard_error: return location for child error messages
+ * @exit_status: child exit status, as returned by waitpid()
+ * @error: return location for error
+ *
+ * Executes a child synchronously (waits for the child to exit before returning).
+ * All output from the child is stored in @standard_output and @standard_error,
+ * if those parameters are non-NULL. If @exit_status is non-NULL, the exit status
+ * of the child is stored there as it would be by waitpid(); standard UNIX
+ * macros such as WIFEXITED() and WEXITSTATUS() must be used to evaluate the
+ * exit status. 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.
+ * 
+ * Return value: TRUE on success, FALSE if an error was set.
+ **/
+gboolean
+g_spawn_sync (const gchar          *working_directory,
+              gchar               **argv,
+              gchar               **envp,
+              GSpawnFlags           flags,
+              GSpawnChildSetupFunc  child_setup,
+              gpointer              user_data,
+              gchar               **standard_output,
+              gchar               **standard_error,
+              gint                 *exit_status,
+              GError              **error)     
+{
+  gint outpipe = -1;
+  gint errpipe = -1;
+  gint pid;
+  fd_set fds;
+  gint ret;
+  GString *outstr = NULL;
+  GString *errstr = NULL;
+  gboolean failed;
+  gint status;
+  
+  g_return_val_if_fail (argv != NULL, FALSE);
+  g_return_val_if_fail (!(flags & G_SPAWN_DO_NOT_REAP_CHILD), FALSE);
+  g_return_val_if_fail (standard_output == NULL ||
+                        !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE);
+  g_return_val_if_fail (standard_error == NULL ||
+                        !(flags & G_SPAWN_STDERR_TO_DEV_NULL), FALSE);
+  
+  /* Just to ensure segfaults if callers try to use
+   * these when an error is reported.
+   */
+  if (standard_output)
+    *standard_output = NULL;
+
+  if (standard_error)
+    *standard_error = NULL;
+  
+  if (!fork_exec_with_pipes (FALSE,
+                             working_directory,
+                             argv,
+                             envp,
+                             !(flags & G_SPAWN_LEAVE_DESCRIPTORS_OPEN),
+                             (flags & G_SPAWN_SEARCH_PATH) != 0,
+                             (flags & G_SPAWN_STDOUT_TO_DEV_NULL) != 0,
+                             (flags & G_SPAWN_STDERR_TO_DEV_NULL) != 0,
+                             (flags & G_SPAWN_CHILD_INHERITS_STDIN) != 0,
+                             child_setup,
+                             user_data,
+                             &pid,
+                             NULL,
+                             standard_output ? &outpipe : NULL,
+                             standard_error ? &errpipe : NULL,
+                             error))
+    return FALSE;
+
+  /* Read data from child. */
+  
+  failed = FALSE;
+
+  if (outpipe >= 0)
+    {
+      outstr = g_string_new ("");
+    }
+      
+  if (errpipe >= 0)
+    {
+      errstr = g_string_new ("");
+    }
+
+  /* Read data until we get EOF on both pipes. */
+  while (!failed &&
+         (outpipe >= 0 ||
+          errpipe >= 0))
+    {
+      ret = 0;
+          
+      FD_ZERO (&fds);
+      if (outpipe >= 0)
+        FD_SET (outpipe, &fds);
+      if (errpipe >= 0)
+        FD_SET (errpipe, &fds);
+          
+      ret = select (MAX (outpipe, errpipe) + 1,
+                    &fds,
+                    NULL, NULL,
+                    NULL /* no timeout */);
+
+      if (ret < 0 && errno != EINTR)
+        {
+          failed = TRUE;
+
+          g_set_error (error,
+                       G_SPAWN_ERROR,
+                       G_SPAWN_ERROR_READ,
+                       _("Unexpected error in select() reading data from a child process (%s)"),
+                       g_strerror (errno));
+              
+          break;
+        }
+
+      if (outpipe >= 0 && FD_ISSET (outpipe, &fds))
+        {
+          switch (read_data (outstr, outpipe, error))
+            {
+            case READ_FAILED:
+              failed = TRUE;
+              break;
+            case READ_EOF:
+              close_and_invalidate (&outpipe);
+              outpipe = -1;
+              break;
+            default:
+              break;
+            }
+
+          if (failed)
+            break;
+        }
+
+      if (errpipe >= 0 && FD_ISSET (errpipe, &fds))
+        {
+          switch (read_data (errstr, errpipe, error))
+            {
+            case READ_FAILED:
+              failed = TRUE;
+              break;
+            case READ_EOF:
+              close_and_invalidate (&errpipe);
+              errpipe = -1;
+              break;
+            default:
+              break;
+            }
+
+          if (failed)
+            break;
+        }
+    }
+
+  /* These should only be open still if we had an error.  */
+  
+  if (outpipe >= 0)
+    close_and_invalidate (&outpipe);
+  if (errpipe >= 0)
+    close_and_invalidate (&errpipe);
+  
+  /* Wait for child to exit, even if we have
+   * an error pending.
+   */
+ again:
+      
+  ret = waitpid (pid, &status, 0);
+
+  if (ret < 0)
+    {
+      if (errno == EINTR)
+        goto again;
+      else if (errno == ECHILD)
+        {
+          if (exit_status)
+            {
+              g_warning ("In call to g_spawn_sync(), exit status of a child process was requested but SIGCHLD action was set to SIG_IGN and ECHILD was received by waitpid(), so exit status can't be returned. This is a bug in the program calling g_spawn_sync(); either don't request the exit status, or don't set the SIGCHLD action.");
+            }
+          else
+            {
+              /* We don't need the exit status. */
+            }
+        }
+      else
+        {
+          if (!failed) /* avoid error pileups */
+            {
+              failed = TRUE;
+                  
+              g_set_error (error,
+                           G_SPAWN_ERROR,
+                           G_SPAWN_ERROR_READ,
+                           _("Unexpected error in waitpid() (%s)"),
+                           g_strerror (errno));
+            }
+        }
+    }
+  
+  if (failed)
+    {
+      if (outstr)
+        g_string_free (outstr, TRUE);
+      if (errstr)
+        g_string_free (errstr, TRUE);
+
+      return FALSE;
+    }
+  else
+    {
+      if (exit_status)
+        *exit_status = status;
+      
+      if (standard_output)        
+        *standard_output = g_string_free (outstr, FALSE);
+
+      if (standard_error)
+        *standard_error = g_string_free (errstr, FALSE);
+
+      return TRUE;
+    }
+}
+
+/**
+ * g_spawn_async_with_pipes:
+ * @working_directory: child's current working directory, or NULL to inherit parent's
+ * @argv: child's argument vector
+ * @envp: child's environment, or NULL to inherit parent's
+ * @flags: flags from #GSpawnFlags
+ * @child_setup: function to run in the child just before exec()
+ * @user_data: user data for @child_setup
+ * @child_pid: return location for child process ID, or NULL
+ * @standard_input: return location for file descriptor to write to child's stdin, or NULL
+ * @standard_output: return location for file descriptor to read child's stdout, or NULL
+ * @standard_error: return location for file descriptor to read child's stderr, or NULL
+ * @error: return location for error
+ *
+ * Executes a child program asynchronously (your program will not
+ * block waiting for the child to exit). The child program is
+ * specified by the only argument that must be provided, @argv. @argv
+ * should be a NULL-terminated array of strings, to be passed as the
+ * argument vector for the child. The first string in @argv is of
+ * course the name of the program to execute. By default, the name of
+ * the program must be a full path; the PATH shell variable will only
+ * be searched if you pass the %G_SPAWN_SEARCH_PATH flag.
+ *
+ * @envp is a NULL-terminated array of strings, where each string
+ * has the form <literal>KEY=VALUE</literal>. This will become
+ * the child's environment. If @envp is NULL, the child inherits its
+ * parent's environment.
+ *
+ * @flags should be the bitwise OR of any flags you want to affect the
+ * function's behavior. The %G_SPAWN_DO_NOT_REAP_CHILD means that the
+ * child will not be automatically reaped; you must call waitpid() or
+ * handle SIGCHLD yourself, or the child will become a zombie.
+ * %G_SPAWN_LEAVE_DESCRIPTORS_OPEN means that the parent's open file
+ * descriptors will be inherited by the child; otherwise all
+ * descriptors except stdin/stdout/stderr will be closed before
+ * calling exec() in the child. %G_SPAWN_SEARCH_PATH means that
+ * <literal>argv[0]</literal> need not be an absolute path, it
+ * will be looked for in the user's PATH. %G_SPAWN_STDOUT_TO_DEV_NULL
+ * means that the child's standad output will be discarded, instead
+ * of going to the same location as the parent's standard output.
+ * %G_SPAWN_STDERR_TO_DEV_NULL means that the child's standard error
+ * will be discarded. %G_SPAWN_CHILD_INHERITS_STDIN means that
+ * the child will inherit the parent's standard input (by default,
+ * the child's standard input is attached to /dev/null).
+ *
+ * @child_setup and @user_data are a function and user data to be
+ * called in the child after GLib has performed all the setup it plans
+ * to perform (including creating pipes, closing file descriptors,
+ * etc.) but before calling exec(). That is, @child_setup is called
+ * just before calling exec() in the child. Obviously actions taken in
+ * this function will only affect the child, not the parent. 
+ *
+ * If non-NULL, @child_pid will be filled with the child's process
+ * ID. You can use the process ID to send signals to the child, or
+ * to waitpid() if you specified the %G_SPAWN_DO_NOT_REAP_CHILD flag.
+ *
+ * If non-NULL, the @standard_input, @standard_output, @standard_error
+ * locations will be filled with file descriptors for writing to the child's
+ * standard input or reading from its standard output or standard error.
+ * The caller of g_spawn_async_with_pipes() must close these file descriptors
+ * when they are no longer in use. If these parameters are NULL, the
+ * corresponding pipe won't be created.
+ *
+ * @error can be NULL to ignore errors, or non-NULL to report errors.
+ * If an error is set, the function returns FALSE. Errors
+ * are reported even if they occur in the child (for example if the
+ * executable in <literal>argv[0]</literal> is not found). Typically
+ * the <literal>message</literal> field of returned errors should be displayed
+ * to users.
+ *
+ * If an error occurs, @child_pid, @standard_input, @standard_output,
+ * and @standard_error will not be filled with valid values.
+ * 
+ * Return value: TRUE on success, FALSE if an error was set
+ **/
+gboolean
+g_spawn_async_with_pipes (const gchar          *working_directory,
+                          gchar               **argv,
+                          gchar               **envp,
+                          GSpawnFlags           flags,
+                          GSpawnChildSetupFunc  child_setup,
+                          gpointer              user_data,
+                          gint                 *child_pid,
+                          gint                 *standard_input,
+                          gint                 *standard_output,
+                          gint                 *standard_error,
+                          GError              **error)
+{
+  g_return_val_if_fail (argv != NULL, FALSE);
+  g_return_val_if_fail (standard_output == NULL ||
+                        !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE);
+  g_return_val_if_fail (standard_error == NULL ||
+                        !(flags & G_SPAWN_STDERR_TO_DEV_NULL), FALSE);
+  /* can't inherit stdin if we have an input pipe. */
+  g_return_val_if_fail (standard_input == NULL ||
+                        !(flags & G_SPAWN_CHILD_INHERITS_STDIN), FALSE);
+  
+  return fork_exec_with_pipes (!(flags & G_SPAWN_DO_NOT_REAP_CHILD),
+                               working_directory,
+                               argv,
+                               envp,
+                               !(flags & G_SPAWN_LEAVE_DESCRIPTORS_OPEN),
+                               (flags & G_SPAWN_SEARCH_PATH) != 0,
+                               (flags & G_SPAWN_STDOUT_TO_DEV_NULL) != 0,
+                               (flags & G_SPAWN_STDERR_TO_DEV_NULL) != 0,
+                               (flags & G_SPAWN_CHILD_INHERITS_STDIN) != 0,
+                               child_setup,
+                               user_data,
+                               child_pid,
+                               standard_input,
+                               standard_output,
+                               standard_error,
+                               error);
+}
+
+/**
+ * g_spawn_command_line_sync:
+ * @command_line: a command line 
+ * @standard_output: return location for child output
+ * @standard_error: return location for child errors
+ * @exit_status: return location for child exit status
+ * @error: return location for errors
+ *
+ * A simple version of g_spawn_sync() with little-used parameters
+ * removed, taking a command line instead of an argument vector.  See
+ * g_spawn_sync() for full details. @command_line will be parsed by
+ * g_parse_argv(). Unlike g_spawn_sync(), the %G_SPAWN_SEARCH_PATH flag
+ * is enabled. Note that %G_SPAWN_SEARCH_PATH can have security
+ * implications, so consider using g_spawn_sync() directly if
+ * appropriate.
+ * 
+ * Return value: TRUE on success, FALSE if an error was set
+ **/
+gboolean
+g_spawn_command_line_sync (const gchar  *command_line,
+                           gchar       **standard_output,
+                           gchar       **standard_error,
+                           gint         *exit_status,
+                           GError      **error)
+{
+  gboolean retval;
+  gchar **argv = 0;
+
+  g_return_val_if_fail (command_line != NULL, FALSE);
+  
+  if (!g_parse_argv (command_line,
+                     NULL, &argv,
+                     error))
+    return FALSE;
+  
+  retval = g_spawn_sync (NULL,
+                         argv,
+                         NULL,
+                         G_SPAWN_SEARCH_PATH,
+                         NULL,
+                         NULL,
+                         standard_output,
+                         standard_error,
+                         exit_status,
+                         error);
+  g_strfreev (argv);
+
+  return retval;
+}
+
+/**
+ * g_spawn_command_line_async:
+ * @command_line: a command line
+ * @error: return location for errors
+ * 
+ * A simple version of g_spawn_async() that parses a command line with
+ * g_parse_argv() and passes it to g_spawn_async(). Runs a command line
+ * in the background. Unlike g_spawn_async(), the %G_SPAWN_SEARCH_PATH
+ * flag is enabled, other flags are not. Note that %G_SPAWN_SEARCH_PATH
+ * can have security implications, so consider using g_spawn_async()
+ * directly if appropriate.
+ * 
+ * Return value: TRUE on success, FALSE if error is set.
+ **/
+gboolean
+g_spawn_command_line_async (const gchar *command_line,
+                            GError     **error)
+{
+  gboolean retval;
+  gchar **argv = 0;
+
+  g_return_val_if_fail (command_line != NULL, FALSE);
+
+  if (!g_parse_argv (command_line,
+                     NULL, &argv,
+                     error))
+    return FALSE;
+  
+  retval = g_spawn_async (NULL,
+                          argv,
+                          NULL,
+                          G_SPAWN_SEARCH_PATH,
+                          NULL,
+                          NULL,
+                          NULL,
+                          error);
+  g_strfreev (argv);
+
+  return retval;
+}
+
+static gint
+exec_err_to_g_error (gint en)
+{
+  switch (en)
+    {
+#ifdef EACCES
+    case EACCES:
+      return G_SPAWN_ERROR_ACCES;
+      break;
+#endif
+
+#ifdef EPERM
+    case EPERM:
+      return G_SPAWN_ERROR_PERM;
+      break;
+#endif
+
+#ifdef E2BIG
+    case E2BIG:
+      return G_SPAWN_ERROR_2BIG;
+      break;
+#endif
+
+#ifdef ENOEXEC
+    case ENOEXEC:
+      return G_SPAWN_ERROR_NOEXEC;
+      break;
+#endif
+
+#ifdef ENAMETOOLONG
+    case ENAMETOOLONG:
+      return G_SPAWN_ERROR_NAMETOOLONG;
+      break;
+#endif
+
+#ifdef ENOENT
+    case ENOENT:
+      return G_SPAWN_ERROR_NOENT;
+      break;
+#endif
+
+#ifdef ENOMEM
+    case ENOMEM:
+      return G_SPAWN_ERROR_NOMEM;
+      break;
+#endif
+
+#ifdef ENOTDIR
+    case ENOTDIR:
+      return G_SPAWN_ERROR_NOTDIR;
+      break;
+#endif
+
+#ifdef ELOOP
+    case ELOOP:
+      return G_SPAWN_ERROR_LOOP;
+      break;
+#endif
+      
+#ifdef ETXTBUSY
+    case ETXTBUSY:
+      return G_SPAWN_ERROR_TXTBUSY;
+      break;
+#endif
+
+#ifdef EIO
+    case EIO:
+      return G_SPAWN_ERROR_IO;
+      break;
+#endif
+
+#ifdef ENFILE
+    case ENFILE:
+      return G_SPAWN_ERROR_NFILE;
+      break;
+#endif
+
+#ifdef EMFILE
+    case EMFILE:
+      return G_SPAWN_ERROR_MFILE;
+      break;
+#endif
+
+#ifdef EINVAL
+    case EINVAL:
+      return G_SPAWN_ERROR_INVAL;
+      break;
+#endif
+
+#ifdef EISDIR
+    case EISDIR:
+      return G_SPAWN_ERROR_ISDIR;
+      break;
+#endif
+
+#ifdef ELIBBAD
+    case ELIBBAD:
+      return G_SPAWN_ERROR_LIBBAD;
+      break;
+#endif
+      
+    default:
+      return G_SPAWN_ERROR_FAILED;
+      break;
+    }
+}
+
+static void
+write_err_and_exit (gint fd, gint msg)
+{
+  gint en = errno;
+  
+  write (fd, &msg, sizeof(msg));
+  write (fd, &en, sizeof(en));
+  
+  _exit (1);
+}
+
+static void
+set_cloexec (gint fd)
+{
+  fcntl (fd, F_SETFD, FD_CLOEXEC);
+}
+
+static gint
+sane_dup2 (gint fd1, gint fd2)
+{
+  gint ret;
+
+ retry:
+  ret = dup2 (fd1, fd2);
+  if (ret < 0 && errno == EINTR)
+    goto retry;
+
+  return ret;
+}
+
+enum
+{
+  CHILD_CHDIR_FAILED,
+  CHILD_EXEC_FAILED,
+  CHILD_DUP2_FAILED,
+  CHILD_FORK_FAILED
+};
+
+static void
+do_exec (gint                  child_err_report_fd,
+         gint                  stdin_fd,
+         gint                  stdout_fd,
+         gint                  stderr_fd,
+         const gchar          *working_directory,
+         gchar               **argv,
+         gchar               **envp,
+         gboolean              close_descriptors,
+         gboolean              search_path,
+         gboolean              stdout_to_null,
+         gboolean              stderr_to_null,
+         gboolean              child_inherits_stdin,
+         GSpawnChildSetupFunc  child_setup,
+         gpointer              user_data)
+{
+  if (working_directory && chdir (working_directory) < 0)
+    write_err_and_exit (child_err_report_fd,
+                        CHILD_CHDIR_FAILED);
+
+  /* Close all file descriptors but stdin stdout and stderr as
+   * soon as we exec. Note that this includes
+   * child_err_report_fd, which keeps the parent from blocking
+   * forever on the other end of that pipe.
+   */
+  if (close_descriptors)
+    {
+      gint open_max;
+      gint i;
+      
+      open_max = sysconf (_SC_OPEN_MAX);
+      for (i = 3; i < open_max; i++)
+        set_cloexec (i);
+    }
+  else
+    {
+      /* We need to do child_err_report_fd anyway */
+      set_cloexec (child_err_report_fd);
+    }
+  
+  /* Redirect pipes as required */
+  
+  if (stdin_fd >= 0)
+    {
+      /* dup2 can't actually fail here I don't think */
+          
+      if (sane_dup2 (stdin_fd, 0) < 0)
+        write_err_and_exit (child_err_report_fd,
+                            CHILD_DUP2_FAILED);
+
+      /* ignore this if it doesn't work */
+      close_and_invalidate (&stdin_fd);
+    }
+  else if (!child_inherits_stdin)
+    {
+      /* Keep process from blocking on a read of stdin */
+      gint read_null = open ("/dev/null", O_RDONLY);
+      sane_dup2 (read_null, 0);
+      close_and_invalidate (&read_null);
+    }
+
+  if (stdout_fd >= 0)
+    {
+      /* dup2 can't actually fail here I don't think */
+          
+      if (sane_dup2 (stdout_fd, 1) < 0)
+        write_err_and_exit (child_err_report_fd,
+                            CHILD_DUP2_FAILED);
+
+      /* ignore this if it doesn't work */
+      close_and_invalidate (&stdout_fd);
+    }
+  else if (stdout_to_null)
+    {
+      gint write_null = open ("/dev/null", O_WRONLY);
+      sane_dup2 (write_null, 1);
+      close_and_invalidate (&write_null);
+    }
+
+  if (stderr_fd >= 0)
+    {
+      /* dup2 can't actually fail here I don't think */
+          
+      if (sane_dup2 (stderr_fd, 2) < 0)
+        write_err_and_exit (child_err_report_fd,
+                            CHILD_DUP2_FAILED);
+
+      /* ignore this if it doesn't work */
+      close_and_invalidate (&stderr_fd);
+    }
+  else if (stderr_to_null)
+    {
+      gint write_null = open ("/dev/null", O_WRONLY);
+      sane_dup2 (write_null, 2);
+      close_and_invalidate (&write_null);
+    }
+  
+  /* Call user function just before we exec */
+  if (child_setup)
+    {
+      (* child_setup) (user_data);
+    }
+
+  g_execute (argv[0], argv, envp, search_path);
+
+  /* Exec failed */
+  write_err_and_exit (child_err_report_fd,
+                      CHILD_EXEC_FAILED);
+}
+
+static gboolean
+read_ints (int      fd,
+           gint*    buf,
+           gint     n_ints_in_buf,
+           gint    *n_ints_read,
+           GError **error)
+{
+  gint bytes = 0;
+  
+  while (TRUE)
+    {
+      gint chunk;
+
+      if (bytes >= sizeof(gint)*2)
+        break; /* give up, who knows what happened, should not be
+                * possible.
+                */
+          
+    again:
+      chunk = read (fd,
+                    ((gchar*)buf) + bytes,
+                    sizeof(gint)*n_ints_in_buf - bytes);
+      if (chunk < 0 && errno == EINTR)
+        goto again;
+          
+      if (chunk < 0)
+        {
+          /* Some weird shit happened, bail out */
+              
+          g_set_error (error,
+                       G_SPAWN_ERROR,
+                       G_SPAWN_ERROR_FAILED,
+                       _("Failed to read from child pipe (%s)"),
+                       g_strerror (errno));
+
+          return FALSE;
+        }
+      else if (chunk == 0)
+        break; /* EOF */
+      else
+        {
+          g_assert (chunk > 0);
+              
+          bytes += chunk;
+        }
+    }
+
+  *n_ints_read = bytes/4;
+
+  return TRUE;
+}
+
+static gboolean
+fork_exec_with_pipes (gboolean              intermediate_child,
+                      const gchar          *working_directory,
+                      gchar               **argv,
+                      gchar               **envp,
+                      gboolean              close_descriptors,
+                      gboolean              search_path,
+                      gboolean              stdout_to_null,
+                      gboolean              stderr_to_null,
+                      gboolean              child_inherits_stdin,
+                      GSpawnChildSetupFunc  child_setup,
+                      gpointer              user_data,
+                      gint                 *child_pid,
+                      gint                 *standard_input,
+                      gint                 *standard_output,
+                      gint                 *standard_error,
+                      GError              **error)     
+{
+  gint pid;
+  gint stdin_pipe[2] = { -1, -1 };
+  gint stdout_pipe[2] = { -1, -1 };
+  gint stderr_pipe[2] = { -1, -1 };
+  gint child_err_report_pipe[2] = { -1, -1 };
+  gint child_pid_report_pipe[2] = { -1, -1 };
+  gint status;
+  
+  if (!make_pipe (child_err_report_pipe, error))
+    return FALSE;
+
+  if (intermediate_child && !make_pipe (child_pid_report_pipe, error))
+    goto cleanup_and_fail;
+  
+  if (standard_input && !make_pipe (stdin_pipe, error))
+    goto cleanup_and_fail;
+  
+  if (standard_output && !make_pipe (stdout_pipe, error))
+    goto cleanup_and_fail;
+
+  if (standard_error && !make_pipe (stderr_pipe, error))
+    goto cleanup_and_fail;
+
+  pid = fork ();
+
+  if (pid < 0)
+    {      
+      g_set_error (error,
+                   G_SPAWN_ERROR,
+                   G_SPAWN_ERROR_FORK,
+                   _("Failed to fork (%s)"),
+                   g_strerror (errno));
+
+      goto cleanup_and_fail;
+    }
+  else if (pid == 0)
+    {
+      /* Immediate child. This may or may not be the child that
+       * actually execs the new process.
+       */
+      
+      /* 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]);
+      close_and_invalidate (&stdin_pipe[1]);
+      close_and_invalidate (&stdout_pipe[0]);
+      close_and_invalidate (&stderr_pipe[0]);
+      
+      if (intermediate_child)
+        {
+          /* 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.
+           */
+          gint grandchild_pid;
+
+          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],
+                       stdin_pipe[0],
+                       stdout_pipe[1],
+                       stderr_pipe[1],
+                       working_directory,
+                       argv,
+                       envp,
+                       close_descriptors,
+                       search_path,
+                       stdout_to_null,
+                       stderr_to_null,
+                       child_inherits_stdin,
+                       child_setup,
+                       user_data);
+            }
+          else
+            {
+              write (child_pid_report_pipe[1], &grandchild_pid, sizeof(grandchild_pid));
+              close_and_invalidate (&child_pid_report_pipe[1]);
+              
+              _exit (0);
+            }
+        }
+      else
+        {
+          /* Just run the child.
+           */
+
+          do_exec (child_err_report_pipe[1],
+                   stdin_pipe[0],
+                   stdout_pipe[1],
+                   stderr_pipe[1],
+                   working_directory,
+                   argv,
+                   envp,
+                   close_descriptors,
+                   search_path,
+                   stdout_to_null,
+                   stderr_to_null,
+                   child_inherits_stdin,
+                   child_setup,
+                   user_data);
+        }
+    }
+  else
+    {
+      /* Parent */
+      
+      gint buf[2];
+      gint 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]);
+      close_and_invalidate (&stdin_pipe[0]);
+      close_and_invalidate (&stdout_pipe[1]);
+      close_and_invalidate (&stderr_pipe[1]);
+
+      /* If we had an intermediate child, reap it */
+      if (intermediate_child)
+        {
+        wait_again:
+          if (waitpid (pid, &status, 0) < 0)
+            {
+              if (errno == EINTR)
+                goto wait_again;
+              else if (errno == ECHILD)
+                ; /* do nothing, child already reaped */
+              else
+                g_warning ("waitpid() should not fail in %s", __FUNCTION__);
+            }
+        }
+      
+
+      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])
+            {
+            case CHILD_CHDIR_FAILED:
+              g_set_error (error,
+                           G_SPAWN_ERROR,
+                           G_SPAWN_ERROR_CHDIR,
+                           _("Failed to change to directory '%s' (%s)"),
+                           working_directory,
+                           g_strerror (buf[1]));
+
+              break;
+              
+            case CHILD_EXEC_FAILED:
+              g_set_error (error,
+                           G_SPAWN_ERROR,
+                           exec_err_to_g_error (buf[1]),
+                           _("Failed to execute child process (%s)"),
+                           g_strerror (buf[1]));
+
+              break;
+              
+            case CHILD_DUP2_FAILED:
+              g_set_error (error,
+                           G_SPAWN_ERROR,
+                           G_SPAWN_ERROR_FAILED,
+                           _("Failed to redirect output or input of child process (%s)"),
+                           g_strerror (buf[1]));
+
+              break;
+
+            case CHILD_FORK_FAILED:
+              g_set_error (error,
+                           G_SPAWN_ERROR,
+                           G_SPAWN_ERROR_FORK,
+                           _("Failed to fork child process (%s)"),
+                           g_strerror (buf[1]));
+              break;
+              
+            default:
+              g_set_error (error,
+                           G_SPAWN_ERROR,
+                           G_SPAWN_ERROR_FAILED,
+                           _("Unknown error executing child process"));
+              break;
+            }
+
+          goto cleanup_and_fail;
+        }
+
+      /* Get child pid from intermediate child pipe. */
+      if (intermediate_child)
+        {
+          n_ints = 0;
+          
+          if (!read_ints (child_pid_report_pipe[0],
+                          buf, 1, &n_ints, error))
+            goto cleanup_and_fail;
+
+          if (n_ints < 1)
+            {
+              g_set_error (error,
+                           G_SPAWN_ERROR,
+                           G_SPAWN_ERROR_FAILED,
+                           _("Failed to read enough data from child pid pipe (%s)"),
+                           g_strerror (errno));
+              goto cleanup_and_fail;
+            }
+          else
+            {
+              /* we have the child pid */
+              pid = buf[0];
+            }
+        }
+      
+      /* Success against all odds! return the information */
+      
+      if (child_pid)
+        *child_pid = pid;
+
+      if (standard_input)
+        *standard_input = stdin_pipe[1];
+      if (standard_output)
+        *standard_output = stdout_pipe[0];
+      if (standard_error)
+        *standard_error = stderr_pipe[0];
+      
+      return TRUE;
+    }
+
+ cleanup_and_fail:
+  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]);
+  close_and_invalidate (&stdin_pipe[0]);
+  close_and_invalidate (&stdin_pipe[1]);
+  close_and_invalidate (&stdout_pipe[0]);
+  close_and_invalidate (&stdout_pipe[1]);
+  close_and_invalidate (&stderr_pipe[0]);
+  close_and_invalidate (&stderr_pipe[1]);
+
+  return FALSE;
+}
+
+static gboolean
+make_pipe (gint     p[2],
+           GError **error)
+{
+  if (pipe (p) < 0)
+    {
+      g_set_error (error,
+                   G_SPAWN_ERROR,
+                   G_SPAWN_ERROR_FAILED,
+                   _("Failed to create pipe for communicating with child process (%s)"),
+                   g_strerror (errno));
+      return FALSE;
+    }
+  else
+    return TRUE;
+}
+
+/* Based on execvp from GNU C Library */
+
+static void
+script_execute (const gchar *file,
+                gchar      **argv,
+                gchar      **envp,
+                gboolean     search_path)
+{
+  /* Count the arguments.  */
+  int argc = 0;
+  while (argv[argc])
+    ++argc;
+  
+  /* Construct an argument list for the shell.  */
+  {
+    gchar **new_argv;
+
+    new_argv = g_new0 (gchar*, argc + 1);
+    
+    new_argv[0] = (char *) "/bin/sh";
+    new_argv[1] = (char *) file;
+    while (argc > 1)
+      {
+	new_argv[argc] = argv[argc - 1];
+	--argc;
+      }
+
+    /* Execute the shell. */
+    if (envp)
+      execve (new_argv[0], new_argv, envp);
+    else
+      execv (new_argv[0], new_argv);
+    
+    g_free (new_argv);
+  }
+}
+
+static gchar*
+my_strchrnul (const gchar *str, gchar c)
+{
+  gchar *p = (gchar*) str;
+  while (*p && (*p != c))
+    ++p;
+
+  return p;
+}
+
+static gint
+g_execute (const gchar *file,
+           gchar      **argv,
+           gchar      **envp,
+           gboolean     search_path)
+{
+  if (*file == '\0')
+    {
+      /* We check the simple case first. */
+      errno = ENOENT;
+      return -1;
+    }
+
+  if (!search_path || strchr (file, '/') != NULL)
+    {
+      /* Don't search when it contains a slash. */
+      if (envp)
+        execve (file, argv, envp);
+      else
+        execv (file, argv);
+      
+      if (errno == ENOEXEC)
+	script_execute (file, argv, envp, FALSE);
+    }
+  else
+    {
+      gboolean got_eacces = 0;
+      char *path, *p, *name, *freeme;
+      size_t len;
+      size_t pathlen;
+
+      path = g_getenv ("PATH");
+      if (path == NULL)
+	{
+	  /* There is no `PATH' in the environment.  The default
+	   * search path in libc is the current directory followed by
+	   * the path `confstr' returns for `_CS_PATH'.
+           */
+
+          /* In GLib we put . last, for security, and don't use the
+           * unportable confstr(); UNIX98 does not actually specify
+           * what to search if PATH is unset. POSIX may, dunno.
+           */
+          
+          path = "/bin:/usr/bin:.";
+	}
+
+      len = strlen (file) + 1;
+      pathlen = strlen (path);
+      freeme = name = g_malloc (pathlen + len + 1);
+      
+      /* Copy the file name at the top, including '\0'  */
+      memcpy (name + pathlen + 1, file, len);
+      name = name + pathlen;
+      /* And add the slash before the filename  */
+      *name = '/';
+
+      p = path;
+      do
+	{
+	  char *startp;
+
+	  path = p;
+	  p = my_strchrnul (path, ':');
+
+	  if (p == path)
+	    /* Two adjacent colons, or a colon at the beginning or the end
+             * of `PATH' means to search the current directory.
+             */
+	    startp = name + 1;
+	  else
+	    startp = memcpy (name - (p - path), path, p - path);
+
+	  /* Try to execute this name.  If it works, execv will not return.  */
+          if (envp)
+            execve (startp, argv, envp);
+          else
+            execv (startp, argv);
+          
+	  if (errno == ENOEXEC)
+	    script_execute (startp, argv, envp, search_path);
+
+	  switch (errno)
+	    {
+	    case EACCES:
+	      /* Record the we got a `Permission denied' error.  If we end
+               * up finding no executable we can use, we want to diagnose
+               * that we did find one but were denied access.
+               */
+	      got_eacces = TRUE;
+
+              /* FALL THRU */
+              
+	    case ENOENT:
+#ifdef ESTALE
+	    case ESTALE:
+#endif
+#ifdef ENOTDIR
+	    case ENOTDIR:
+#endif
+	      /* Those errors indicate the file is missing or not executable
+               * by us, in which case we want to just try the next path
+               * directory.
+               */
+	      break;
+
+	    default:
+	      /* Some other error means we found an executable file, but
+               * something went wrong executing it; return the error to our
+               * caller.
+               */
+              g_free (freeme);
+	      return -1;
+	    }
+	}
+      while (*p++ != '\0');
+
+      /* We tried every element and none of them worked.  */
+      if (got_eacces)
+	/* At least one failure was due to permissions, so report that
+         * error.
+         */
+        errno = EACCES;
+
+      g_free (freeme);
+    }
+
+  /* Return the error from the last attempt (probably ENOENT).  */
+  return -1;
+}
Index: gspawn.h
===================================================================
RCS file: gspawn.h
diff -N gspawn.h
--- /dev/null	Tue May  5 16:32:27 1998
+++ gspawn.h	Wed Oct  4 18:53:06 2000
@@ -0,0 +1,142 @@
+/* gexec.h - Process launching
+ *
+ *  Copyright 2000 Red Hat, Inc.
+ *
+ * GLib is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * GLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GLib; see the file COPYING.LIB.  If not, write
+ * to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GSPAWN_H__
+#define __GSPAWN_H__
+
+#include <gerror.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/* I'm not sure I remember our proposed naming convention here. */
+#define G_SPAWN_ERROR g_spawn_error_quark ()
+
+typedef enum
+{
+  G_SPAWN_ERROR_PARSE,  /* failed to parse command line */
+  G_SPAWN_ERROR_FORK,   /* fork failed due to lack of memory */
+  G_SPAWN_ERROR_READ,   /* read or select on pipes failed */
+  G_SPAWN_ERROR_CHDIR,  /* changing to working dir failed */
+  G_SPAWN_ERROR_ACCES,  /* execv() returned EACCES */
+  G_SPAWN_ERROR_PERM,   /* execv() returned EPERM */
+  G_SPAWN_ERROR_2BIG,   /* execv() returned E2BIG */
+  G_SPAWN_ERROR_NOEXEC, /* execv() returned ENOEXEC */
+  G_SPAWN_ERROR_NAMETOOLONG, /* ""  "" ENAMETOOLONG */
+  G_SPAWN_ERROR_NOENT,       /* ""  "" ENOENT */
+  G_SPAWN_ERROR_NOMEM,       /* ""  "" ENOMEM */
+  G_SPAWN_ERROR_NOTDIR,      /* ""  "" ENOTDIR */
+  G_SPAWN_ERROR_LOOP,        /* ""  "" ELOOP   */
+  G_SPAWN_ERROR_TXTBUSY,     /* ""  "" ETXTBUSY */
+  G_SPAWN_ERROR_IO,          /* ""  "" EIO */
+  G_SPAWN_ERROR_NFILE,       /* ""  "" ENFILE */
+  G_SPAWN_ERROR_MFILE,       /* ""  "" EMFLE */
+  G_SPAWN_ERROR_INVAL,       /* ""  "" EINVAL */
+  G_SPAWN_ERROR_ISDIR,       /* ""  "" EISDIR */
+  G_SPAWN_ERROR_LIBBAD,      /* ""  "" ELIBBAD */
+  G_SPAWN_ERROR_FAILED       /* other fatal failure, error->message
+                              * should explain
+                              */
+} GSpawnError;
+
+typedef void (* GSpawnChildSetupFunc) (gpointer user_data);
+
+typedef enum
+{
+  G_SPAWN_LEAVE_DESCRIPTORS_OPEN = 1 << 0,
+  G_SPAWN_DO_NOT_REAP_CHILD = 1 << 1,
+  /* look for argv[0] in the path i.e. use execvp() */
+  G_SPAWN_SEARCH_PATH = 1 << 2,
+  /* Dump output to /dev/null */
+  G_SPAWN_STDOUT_TO_DEV_NULL = 1 << 3,
+  G_SPAWN_STDERR_TO_DEV_NULL = 1 << 4,
+  G_SPAWN_CHILD_INHERITS_STDIN = 1 << 5
+} GSpawnFlags;
+
+GQuark g_spawn_error_quark (void);
+
+gchar* g_shell_quote   (const gchar  *unquoted_string);
+gchar* g_shell_unquote (const gchar  *quoted_string,
+                        GError      **error);
+
+gboolean g_parse_argv (const gchar           *command_line,
+                       gint                  *argc,
+                       gchar               ***argv,
+                       GError               **error);
+
+gboolean g_spawn_async (const gchar           *working_directory,
+                        gchar                **argv,
+                        gchar                **envp,
+                        GSpawnFlags            flags,
+                        GSpawnChildSetupFunc   child_setup,
+                        gpointer               user_data,
+                        gint                  *child_pid,
+                        GError               **error);
+
+
+/* Opens pipes for non-NULL standard_output, standard_input, standard_error,
+ * and returns the parent's end of the pipes.
+ */
+gboolean g_spawn_async_with_pipes (const gchar          *working_directory,
+                                   gchar               **argv,
+                                   gchar               **envp,
+                                   GSpawnFlags           flags,
+                                   GSpawnChildSetupFunc  child_setup,
+                                   gpointer              user_data,
+                                   gint                 *child_pid,
+                                   gint                 *standard_input,
+                                   gint                 *standard_output,
+                                   gint                 *standard_error,
+                                   GError              **error);
+
+
+/* If standard_output or standard_error are non-NULL, the full
+ * standard output or error of the command will be placed there.
+ */
+
+gboolean g_spawn_sync         (const gchar          *working_directory,
+                               gchar               **argv,
+                               gchar               **envp,
+                               GSpawnFlags           flags,
+                               GSpawnChildSetupFunc  child_setup,
+                               gpointer              user_data,
+                               gchar               **standard_output,
+                               gchar               **standard_error,
+                               gint                 *exit_status,
+                               GError              **error);
+
+gboolean g_spawn_command_line_sync  (const gchar          *command_line,
+                                     gchar               **standard_output,
+                                     gchar               **standard_error,
+                                     gint                 *exit_status,
+                                     GError              **error);
+gboolean g_spawn_command_line_async (const gchar          *command_line,
+                                     GError              **error);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __GSPAWN_H__ */
+
+
Index: guniprop.c
===================================================================
RCS file: /cvs/gnome/glib/guniprop.c,v
retrieving revision 1.3
diff -u -u -r1.3 guniprop.c
--- guniprop.c	2000/09/06 14:42:13	1.3
+++ guniprop.c	2000/10/04 22:53:06
@@ -118,9 +118,16 @@
 gboolean
 g_unichar_isspace (gunichar c)
 {
-  int t = TYPE (c);
-  return (t == G_UNICODE_SPACE_SEPARATOR || t == G_UNICODE_LINE_SEPARATOR
-	  || t == G_UNICODE_PARAGRAPH_SEPARATOR);
+  /* special-case these since Unicode thinks they are not spaces */
+  if (c == ' ' || c == '\t' || c == '\n' || c == '\r' ||
+      c == '\f' || c == '\v') /* "the mythical vertical tab" */
+    return TRUE;
+  else
+    {
+      int t = TYPE (c);
+      return (t == G_UNICODE_SPACE_SEPARATOR || t == G_UNICODE_LINE_SEPARATOR
+              || t == G_UNICODE_PARAGRAPH_SEPARATOR);
+    }
 }
 
 /**
Index: gutils.c
===================================================================
RCS file: /cvs/gnome/glib/gutils.c,v
retrieving revision 1.69
diff -u -u -r1.69 gutils.c
--- gutils.c	2000/09/28 07:35:02	1.69
+++ gutils.c	2000/10/04 22:53:06
@@ -143,6 +143,84 @@
     g_error ("Could not register atexit() function: %s", error);
 }
 
+/* Based on execvp() from GNU Libc.
+ * Some of this code is cut-and-pasted into gspawn.c
+ */
+
+static gchar*
+my_strchrnul (const gchar *str, gchar c)
+{
+  gchar *p = (gchar*)str;
+  while (*p && (*p != c))
+    ++p;
+
+  return p;
+}
+
+gchar*
+g_find_program_in_path (const gchar *file)
+{
+  gchar *path, *p, *name, *freeme;
+  size_t len;
+  size_t pathlen;
+  
+  path = g_getenv ("PATH");
+  if (path == NULL)
+    {
+      /* There is no `PATH' in the environment.  The default
+       * search path in libc is the current directory followed by
+       * the path `confstr' returns for `_CS_PATH'.
+       */
+      
+      /* In GLib we put . last, for security, and don't use the
+       * unportable confstr(); UNIX98 does not actually specify
+       * what to search if PATH is unset. POSIX may, dunno.
+       */
+      
+      path = "/bin:/usr/bin:.";
+    }
+  
+  len = strlen (file) + 1;
+  pathlen = strlen (path);
+  freeme = name = g_malloc (pathlen + len + 1);
+  
+  /* Copy the file name at the top, including '\0'  */
+  memcpy (name + pathlen + 1, file, len);
+  name = name + pathlen;
+  /* And add the slash before the filename  */
+  *name = '/';
+  
+  p = path;
+  do
+    {
+      char *startp;
+
+      path = p;
+      p = my_strchrnul (path, ':');
+
+      if (p == path)
+        /* Two adjacent colons, or a colon at the beginning or the end
+         * of `PATH' means to search the current directory.
+         */
+        startp = name + 1;
+      else
+        startp = memcpy (name - (p - path), path, p - path);
+
+      if (g_file_test (startp, G_FILE_TEST_IS_EXECUTABLE))
+        {
+          gchar *ret;
+          ret = g_strdup (startp);
+          g_free (freeme);
+          return ret;
+        }
+    }
+  while (*p++ != '\0');
+  
+  g_free (freeme);
+
+  return NULL;
+}
+
 gint
 g_snprintf (gchar	*str,
 	    gulong	 n,
Index: tests/Makefile.am
===================================================================
RCS file: /cvs/gnome/glib/tests/Makefile.am,v
retrieving revision 1.20
diff -u -u -r1.20 Makefile.am
--- tests/Makefile.am	2000/07/29 20:59:07	1.20
+++ tests/Makefile.am	2000/10/04 22:53:06
@@ -1,6 +1,8 @@
 
 INCLUDES = -I$(top_srcdir) @GLIB_DEBUG_FLAGS@
 
+EFENCE=
+
 EXTRA_DIST = \
 	makefile.msc	\
 	makefile.msc.in	\
@@ -19,6 +21,7 @@
 	rand-test	\
 	relation-test	\
 	slist-test	\
+	spawn-test	\
 	strfunc-test	\
 	string-test	\
 	thread-test	\
@@ -28,7 +31,7 @@
 
 noinst_PROGRAMS = $(TESTS)
 
-progs_LDADD = $(top_builddir)/libglib-1.3.la
+progs_LDADD = $(EFENCE) $(top_builddir)/libglib-1.3.la $(EFENCE)
 thread_LDADD = $(progs_LDADD) $(top_builddir)/gthread/libgthread-1.3.la @G_THREAD_LIBS@
 
 array_test_LDADD = $(progs_LDADD)
@@ -42,6 +45,7 @@
 rand_test_LDADD = $(progs_LDADD)
 relation_test_LDADD = $(progs_LDADD)
 slist_test_LDADD = $(progs_LDADD)
+spawn_test_LDADD = $(progs_LDADD)
 strfunc_test_LDADD = $(progs_LDADD)
 string_test_LDADD = $(progs_LDADD)
 thread_test_LDADD = $(thread_LDADD)
Index: tests/spawn-test.c
===================================================================
RCS file: spawn-test.c
diff -N spawn-test.c
--- /dev/null	Tue May  5 16:32:27 1998
+++ spawn-test.c	Wed Oct  4 18:53:06 2000
@@ -0,0 +1,236 @@
+/* GLIB - Library of useful routines for C programming
+ * Copyright (C) 1995-1997  Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the GLib Team and others 1997-2000.  See the AUTHORS
+ * file for a list of people on the GLib Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GLib at ftp://ftp.gtk.org/pub/gtk/. 
+ */
+
+#undef G_LOG_DOMAIN
+
+#include <glib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+
+typedef struct _TestResult TestResult;
+
+struct _TestResult
+{
+  gint argc;
+  const gchar **argv;
+};
+
+static const gchar *
+test_command_lines[] =
+{
+  /*  0 */ "foo bar",
+  /*  1 */ "foo 'bar'",
+  /*  2 */ "foo \"bar\"",
+  /*  3 */ "foo '' 'bar'",
+  /*  4 */ "foo \"bar\"'baz'blah'foo'\\''blah'\"boo\"",
+  /*  5 */ "foo \t \tblah\tfoo\t\tbar  baz",
+  /*  6 */ "foo '    spaces more spaces lots of     spaces in this   '  \t",
+  /*  7 */ "foo \\\nbar",
+  /*  8 */ "foo '' ''",
+  /*  9 */ "foo \\\" la la la",
+  /* 10 */ "foo \\ foo woo woo\\ ",
+  /* 11 */ "foo \"yada yada \\$\\\"\"",
+  NULL
+};
+
+static const gchar *result0[] = { "foo", "bar", NULL };
+static const gchar *result1[] = { "foo", "bar", NULL };
+static const gchar *result2[] = { "foo", "bar", NULL };
+static const gchar *result3[] = { "foo", "", "bar", NULL };
+static const gchar *result4[] = { "foo", "barbazblahfoo'blahboo", NULL };
+static const gchar *result5[] = { "foo", "blah", "foo", "bar", "baz", NULL };
+static const gchar *result6[] = { "foo", "    spaces more spaces lots of     spaces in this   ", NULL };
+static const gchar *result7[] = { "foo", "bar", NULL };
+static const gchar *result8[] = { "foo", "", "", NULL };
+static const gchar *result9[] = { "foo", "\"", "la", "la", "la", NULL };
+static const gchar *result10[] = { "foo", " foo", "woo", "woo ", NULL };
+static const gchar *result11[] = { "foo", "yada yada $\"", NULL };
+
+static const TestResult
+correct_results[] =
+{
+  { G_N_ELEMENTS (result0) - 1, result0 },
+  { G_N_ELEMENTS (result1) - 1, result1 },
+  { G_N_ELEMENTS (result2) - 1, result2 },
+  { G_N_ELEMENTS (result3) - 1, result3 },
+  { G_N_ELEMENTS (result4) - 1, result4 },
+  { G_N_ELEMENTS (result5) - 1, result5 },
+  { G_N_ELEMENTS (result6) - 1, result6 },
+  { G_N_ELEMENTS (result7) - 1, result7 },
+  { G_N_ELEMENTS (result8) - 1, result8 },
+  { G_N_ELEMENTS (result9) - 1, result9 },
+  { G_N_ELEMENTS (result10) - 1, result10 },
+  { G_N_ELEMENTS (result11) - 1, result11 }
+};
+
+static void
+print_test (const gchar *cmdline, gint argc, gchar **argv,
+            const TestResult *result)
+{
+  gint i;
+  
+  printf ("\nCommand line was: '%s'\n", cmdline);
+
+  printf ("Expected result (%d args):\n", result->argc);
+  
+  i = 0;
+  while (result->argv[i])
+    {
+      printf (" %3d '%s'\n", i, result->argv[i]);
+
+      ++i;
+    }  
+
+  printf ("Actual result (%d args):\n", argc);
+  
+  i = 0;
+  while (argv[i])
+    {
+      printf (" %3d '%s'\n", i, argv[i]);
+
+      ++i;
+    }
+}
+
+static void
+do_argv_test (const gchar *cmdline, const TestResult *result)
+{
+  gint argc;
+  gchar **argv;
+  GError *err;
+  gint i;
+
+  err = NULL;
+  if (!g_parse_argv (cmdline, &argc, &argv, &err))
+    {
+      fprintf (stderr, "Error parsing command line that should work fine: %s\n",
+               err->message);
+      
+      exit (1);
+    }
+  
+  if (argc != result->argc)
+    {
+      fprintf (stderr, "Expected and actual argc don't match\n");
+      print_test (cmdline, argc, argv, result);
+      exit (1);
+    }
+
+  i = 0;
+  while (argv[i])
+    {
+      if (strcmp (argv[i], result->argv[i]) != 0)
+        {
+          fprintf (stderr, "Expected and actual arg %d do not match\n", i);
+          print_test (cmdline, argc, argv, result);
+          exit (1);
+        }
+      
+      ++i;
+    }
+
+  if (argv[i] != NULL)
+    {
+      fprintf (stderr, "argv didn't get NULL-terminated\n");
+      exit (1);
+    }
+}
+
+static void
+run_tests (void)
+{
+  GError *err;
+  gchar *output = NULL;
+  gint i;
+  
+  printf ("The following errors are supposed to occur:\n");
+
+  err = NULL;
+  if (!g_spawn_command_line_sync ("nonexistent_application foo 'bar baz' blah blah",
+                                  NULL, NULL, NULL,
+                                  &err))
+    {
+      fprintf (stderr, "Error (normal, supposed to happen): %s\n", err->message);
+      g_error_free (err);
+    }
+
+  err = NULL;
+  if (!g_spawn_command_line_async ("nonexistent_application foo bar baz \"blah blah\"",
+                                   &err))
+    {
+      fprintf (stderr, "Error (normal, supposed to happen): %s\n", err->message);
+      g_error_free (err);
+    }
+
+  printf ("Errors after this are not supposed to happen:\n");
+  
+  err = NULL;
+  if (!g_spawn_command_line_sync ("/bin/sh -c 'echo hello'",
+                                  &output, NULL, NULL,
+                                  &err))
+    {
+      fprintf (stderr, "Error: %s\n", err->message);
+      g_error_free (err);
+      exit (1);
+    }
+  else
+    {
+      g_assert (output != NULL);
+      
+      if (strcmp (output, "hello\n") != 0)
+        {
+          printf ("output was '%s', should have been 'hello'\n",
+                  output);
+
+          exit (1);
+        }
+
+      g_free (output);
+    }
+
+  i = 0;
+  while (test_command_lines[i])
+    {
+      printf ("g_parse_argv() test %d - ", i);
+      do_argv_test (test_command_lines[i], &correct_results[i]);
+      printf ("ok (%s)\n", test_command_lines[i]);
+      
+      ++i;
+    }
+}
+
+int
+main (int   argc,
+      char *argv[])
+{
+  run_tests ();
+  
+  return 0;
+}
+
+




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