Add new files.
authorHavoc Pennington <hp@redhat.com>
Mon, 9 Oct 2000 16:24:57 +0000 (16:24 +0000)
committerHavoc Pennington <hp@src.gnome.org>
Mon, 9 Oct 2000 16:24:57 +0000 (16:24 +0000)
2000-10-09  Havoc Pennington  <hp@redhat.com>

        * Makefile.am, tests/Makefile.am: Add new files.

* tests/spawn-test.c, tests/shell-test.c: new tests for
the shell/spawn stuff

* gutils.c (g_find_program_in_path): convert a relative
program name into an absolute pathname to an existing
executable

* gspawn.h, gspawn.c: New fork/exec API

* gshell.h, gshell.c: Shell-related utilities, at the moment
simply routines to parse argv and quote/unquote strings

* 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. Partially
written by Joel Becker.
(g_file_test): file test function

31 files changed:
ChangeLog
ChangeLog.pre-2-0
ChangeLog.pre-2-10
ChangeLog.pre-2-12
ChangeLog.pre-2-2
ChangeLog.pre-2-4
ChangeLog.pre-2-6
ChangeLog.pre-2-8
Makefile.am
gfileutils.c [new file with mode: 0644]
gfileutils.h [new file with mode: 0644]
glib.h
glib/Makefile.am
glib/gfileutils.c [new file with mode: 0644]
glib/gfileutils.h [new file with mode: 0644]
glib/glib.h
glib/gshell.c [new file with mode: 0644]
glib/gshell.h [new file with mode: 0644]
glib/gspawn.c [new file with mode: 0644]
glib/gspawn.h [new file with mode: 0644]
glib/guniprop.c
glib/gutils.c
gshell.c [new file with mode: 0644]
gshell.h [new file with mode: 0644]
gspawn.c [new file with mode: 0644]
gspawn.h [new file with mode: 0644]
guniprop.c
gutils.c
tests/Makefile.am
tests/shell-test.c [new file with mode: 0644]
tests/spawn-test.c [new file with mode: 0644]

index 88f87d7..b8738da 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,27 @@
+2000-10-09  Havoc Pennington  <hp@redhat.com>
+
+        * Makefile.am, tests/Makefile.am: Add new files.
+       
+       * tests/spawn-test.c, tests/shell-test.c: new tests for
+       the shell/spawn stuff
+
+       * gutils.c (g_find_program_in_path): convert a relative
+       program name into an absolute pathname to an existing 
+       executable
+
+       * gspawn.h, gspawn.c: New fork/exec API
+
+       * gshell.h, gshell.c: Shell-related utilities, at the moment
+       simply routines to parse argv and quote/unquote strings
+
+       * 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. Partially
+       written by Joel Becker.
+       (g_file_test): file test function
+
 2000-10-06  Tor Lillqvist  <tml@iki.fi>
 
        * makefile.msc.in: Revamp to be like makefile.mingw.in, make
index 88f87d7..b8738da 100644 (file)
@@ -1,3 +1,27 @@
+2000-10-09  Havoc Pennington  <hp@redhat.com>
+
+        * Makefile.am, tests/Makefile.am: Add new files.
+       
+       * tests/spawn-test.c, tests/shell-test.c: new tests for
+       the shell/spawn stuff
+
+       * gutils.c (g_find_program_in_path): convert a relative
+       program name into an absolute pathname to an existing 
+       executable
+
+       * gspawn.h, gspawn.c: New fork/exec API
+
+       * gshell.h, gshell.c: Shell-related utilities, at the moment
+       simply routines to parse argv and quote/unquote strings
+
+       * 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. Partially
+       written by Joel Becker.
+       (g_file_test): file test function
+
 2000-10-06  Tor Lillqvist  <tml@iki.fi>
 
        * makefile.msc.in: Revamp to be like makefile.mingw.in, make
index 88f87d7..b8738da 100644 (file)
@@ -1,3 +1,27 @@
+2000-10-09  Havoc Pennington  <hp@redhat.com>
+
+        * Makefile.am, tests/Makefile.am: Add new files.
+       
+       * tests/spawn-test.c, tests/shell-test.c: new tests for
+       the shell/spawn stuff
+
+       * gutils.c (g_find_program_in_path): convert a relative
+       program name into an absolute pathname to an existing 
+       executable
+
+       * gspawn.h, gspawn.c: New fork/exec API
+
+       * gshell.h, gshell.c: Shell-related utilities, at the moment
+       simply routines to parse argv and quote/unquote strings
+
+       * 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. Partially
+       written by Joel Becker.
+       (g_file_test): file test function
+
 2000-10-06  Tor Lillqvist  <tml@iki.fi>
 
        * makefile.msc.in: Revamp to be like makefile.mingw.in, make
index 88f87d7..b8738da 100644 (file)
@@ -1,3 +1,27 @@
+2000-10-09  Havoc Pennington  <hp@redhat.com>
+
+        * Makefile.am, tests/Makefile.am: Add new files.
+       
+       * tests/spawn-test.c, tests/shell-test.c: new tests for
+       the shell/spawn stuff
+
+       * gutils.c (g_find_program_in_path): convert a relative
+       program name into an absolute pathname to an existing 
+       executable
+
+       * gspawn.h, gspawn.c: New fork/exec API
+
+       * gshell.h, gshell.c: Shell-related utilities, at the moment
+       simply routines to parse argv and quote/unquote strings
+
+       * 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. Partially
+       written by Joel Becker.
+       (g_file_test): file test function
+
 2000-10-06  Tor Lillqvist  <tml@iki.fi>
 
        * makefile.msc.in: Revamp to be like makefile.mingw.in, make
index 88f87d7..b8738da 100644 (file)
@@ -1,3 +1,27 @@
+2000-10-09  Havoc Pennington  <hp@redhat.com>
+
+        * Makefile.am, tests/Makefile.am: Add new files.
+       
+       * tests/spawn-test.c, tests/shell-test.c: new tests for
+       the shell/spawn stuff
+
+       * gutils.c (g_find_program_in_path): convert a relative
+       program name into an absolute pathname to an existing 
+       executable
+
+       * gspawn.h, gspawn.c: New fork/exec API
+
+       * gshell.h, gshell.c: Shell-related utilities, at the moment
+       simply routines to parse argv and quote/unquote strings
+
+       * 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. Partially
+       written by Joel Becker.
+       (g_file_test): file test function
+
 2000-10-06  Tor Lillqvist  <tml@iki.fi>
 
        * makefile.msc.in: Revamp to be like makefile.mingw.in, make
index 88f87d7..b8738da 100644 (file)
@@ -1,3 +1,27 @@
+2000-10-09  Havoc Pennington  <hp@redhat.com>
+
+        * Makefile.am, tests/Makefile.am: Add new files.
+       
+       * tests/spawn-test.c, tests/shell-test.c: new tests for
+       the shell/spawn stuff
+
+       * gutils.c (g_find_program_in_path): convert a relative
+       program name into an absolute pathname to an existing 
+       executable
+
+       * gspawn.h, gspawn.c: New fork/exec API
+
+       * gshell.h, gshell.c: Shell-related utilities, at the moment
+       simply routines to parse argv and quote/unquote strings
+
+       * 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. Partially
+       written by Joel Becker.
+       (g_file_test): file test function
+
 2000-10-06  Tor Lillqvist  <tml@iki.fi>
 
        * makefile.msc.in: Revamp to be like makefile.mingw.in, make
index 88f87d7..b8738da 100644 (file)
@@ -1,3 +1,27 @@
+2000-10-09  Havoc Pennington  <hp@redhat.com>
+
+        * Makefile.am, tests/Makefile.am: Add new files.
+       
+       * tests/spawn-test.c, tests/shell-test.c: new tests for
+       the shell/spawn stuff
+
+       * gutils.c (g_find_program_in_path): convert a relative
+       program name into an absolute pathname to an existing 
+       executable
+
+       * gspawn.h, gspawn.c: New fork/exec API
+
+       * gshell.h, gshell.c: Shell-related utilities, at the moment
+       simply routines to parse argv and quote/unquote strings
+
+       * 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. Partially
+       written by Joel Becker.
+       (g_file_test): file test function
+
 2000-10-06  Tor Lillqvist  <tml@iki.fi>
 
        * makefile.msc.in: Revamp to be like makefile.mingw.in, make
index 88f87d7..b8738da 100644 (file)
@@ -1,3 +1,27 @@
+2000-10-09  Havoc Pennington  <hp@redhat.com>
+
+        * Makefile.am, tests/Makefile.am: Add new files.
+       
+       * tests/spawn-test.c, tests/shell-test.c: new tests for
+       the shell/spawn stuff
+
+       * gutils.c (g_find_program_in_path): convert a relative
+       program name into an absolute pathname to an existing 
+       executable
+
+       * gspawn.h, gspawn.c: New fork/exec API
+
+       * gshell.h, gshell.c: Shell-related utilities, at the moment
+       simply routines to parse argv and quote/unquote strings
+
+       * 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. Partially
+       written by Joel Becker.
+       (g_file_test): file test function
+
 2000-10-06  Tor Lillqvist  <tml@iki.fi>
 
        * makefile.msc.in: Revamp to be like makefile.mingw.in, make
index 104d783..127d248 100644 (file)
@@ -49,6 +49,7 @@ libglib_1_3_la_SOURCES =      \
        gdataset.c              \
        gdate.c                 \
        gerror.c                \
+       gfileutils.c            \
        ghash.c                 \
        ghook.c                 \
        giochannel.c            \
@@ -63,7 +64,9 @@ libglib_1_3_la_SOURCES =      \
        grel.c                  \
        grand.c                 \
        gscanner.c              \
+       gshell.c                \
        gslist.c                \
+       gspawn.c                \
        gstrfuncs.c             \
        gstring.c               \
        gthread.c               \
@@ -81,8 +84,11 @@ libglib_1_3_la_SOURCES =     \
 glibincludedir=$(includedir)/glib-2.0
 glibinclude_HEADERS =   \
        gerror.h        \
+       gfileutils.h    \
        glib.h          \
        glib-object.h   \
+       gshell.h        \
+       gspawn.h        \
        gunicode.h
 
 configexecincludedir = $(libdir)/glib-2.0/include
diff --git a/gfileutils.c b/gfileutils.c
new file mode 100644 (file)
index 0000000..1176852
--- /dev/null
@@ -0,0 +1,470 @@
+/* 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>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.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)
+{
+  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
+    {
+      struct stat s;
+      
+      if (stat (filename, &s) < 0)
+        return FALSE;
+
+      if ((test & G_FILE_TEST_IS_REGULAR) &&
+          S_ISREG (s.st_mode))
+        return TRUE;
+      else 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 (void)
+{
+  static GQuark q = 0;
+  if (q == 0)
+    q = g_quark_from_static_string ("g-file-error-quark");
+
+  return q;
+}
+
+GFileError
+g_file_error_from_errno (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
+
+#ifdef EBADF
+    case EBADF:
+      return G_FILE_ERROR_BADF;
+      break;
+#endif
+
+#ifdef EINVAL
+    case EINVAL:
+      return G_FILE_ERROR_INVAL;
+      break;
+#endif
+
+#ifdef EPIPE
+    case EPIPE:
+      return G_FILE_ERROR_PIPE;
+      break;
+#endif
+
+#ifdef EAGAIN
+    case EAGAIN:
+      return G_FILE_ERROR_AGAIN;
+      break;
+#endif
+
+#ifdef EINTR
+    case EINTR:
+      return G_FILE_ERROR_INTR;
+      break;
+#endif
+
+#ifdef EIO
+    case EIO:
+      return G_FILE_ERROR_IO;
+      break;
+#endif
+
+#ifdef EPERM
+    case EPERM:
+      return G_FILE_ERROR_PERM;
+      break;
+#endif
+      
+    default:
+      return G_FILE_ERROR_FAILED;
+      break;
+    }
+}
+
+static gboolean
+get_contents_stdio (const gchar *filename,
+                    FILE        *f,
+                    gchar      **contents,
+                    guint       *length,
+                    GError     **error)
+{
+  gchar buf[2048];
+  size_t bytes;
+  GString *str;
+
+  g_assert (f != NULL);
+  
+  str = g_string_new ("");
+  
+  while (!feof (f))
+    {
+      bytes = fread (buf, 1, 2048, f);
+      
+      if (ferror (f))
+        {
+          g_set_error (error,
+                       G_FILE_ERROR,
+                       g_file_error_from_errno (errno),
+                       _("Error reading file '%s': %s"),
+                       filename, strerror (errno));
+
+          g_string_free (str, TRUE);
+          
+          return FALSE;
+        }
+
+      g_string_append_len (str, buf, bytes);
+    }
+
+  fclose (f);
+
+  if (length)
+    *length = str->len;
+  
+  *contents = g_string_free (str, FALSE);
+
+  return TRUE;  
+}
+
+static gboolean
+get_contents_regfile (const gchar *filename,
+                      struct stat *stat_buf,
+                      gint         fd,
+                      gchar      **contents,
+                      guint       *length,
+                      GError     **error)
+{
+  gchar *buf;
+  size_t bytes_read;
+  size_t size;
+      
+  size = stat_buf->st_size;
+
+  buf = g_new (gchar, size + 1);
+      
+  bytes_read = 0;
+  while (bytes_read < size)
+    {
+      gint rc;
+          
+      rc = read (fd, buf + bytes_read, size - bytes_read);
+
+      if (rc < 0)
+        {
+          if (errno != EINTR) 
+            {
+              close (fd);
+
+              g_free (buf);
+                  
+              g_set_error (error,
+                           G_FILE_ERROR,
+                           g_file_error_from_errno (errno),
+                           _("Failed to read from file '%s': %s"),
+                           filename, strerror (errno));
+
+              return FALSE;
+            }
+        }
+      else if (rc == 0)
+        break;
+      else
+        bytes_read += rc;
+    }
+      
+  buf[bytes_read] = '\0';
+
+  if (length)
+    *length = bytes_read;
+  
+  *contents = buf;
+
+  return TRUE;
+}
+
+static gboolean
+get_contents_posix (const gchar *filename,
+                    gchar      **contents,
+                    guint       *length,
+                    GError     **error)
+{
+  struct stat stat_buf;
+  gint fd;
+  
+  fd = open (filename, O_RDONLY);
+
+  if (fd < 0)
+    {
+      g_set_error (error,
+                   G_FILE_ERROR,
+                   g_file_error_from_errno (errno),
+                   _("Failed to open file '%s': %s"),
+                   filename, strerror (errno));
+
+      return FALSE;
+    }
+
+  /* I don't think this will ever fail, aside from ENOMEM, but. */
+  if (fstat (fd, &stat_buf) < 0)
+    {
+      close (fd);
+      
+      g_set_error (error,
+                   G_FILE_ERROR,
+                   g_file_error_from_errno (errno),
+                   _("Failed to get attributes of file '%s': fstat() failed: %s"),
+                   filename, strerror (errno));
+
+      return FALSE;
+    }
+
+  if (stat_buf.st_size > 0 && S_ISREG (stat_buf.st_mode))
+    {
+      return get_contents_regfile (filename,
+                                   &stat_buf,
+                                   fd,
+                                   contents,
+                                   length,
+                                   error);
+    }
+  else
+    {
+      FILE *f;
+
+      f = fdopen (fd, "r");
+      
+      if (f == NULL)
+        {
+          g_set_error (error,
+                       G_FILE_ERROR,
+                       g_file_error_from_errno (errno),
+                       _("Failed to open file '%s': fdopen() failed: %s"),
+                       filename, strerror (errno));
+          
+          return FALSE;
+        }
+  
+      return get_contents_stdio (filename, f, contents, length, error);
+    }
+}
+
+#ifdef G_OS_WIN32
+static gboolean
+get_contents_win32 (const gchar *filename,
+                    gchar      **contents,
+                    guint       *length,
+                    GError     **error)
+{
+  FILE *f;
+
+  /* I guess you want binary mode; maybe you want text sometimes? */
+  f = fopen (filename, "rb");
+
+  if (f == NULL)
+    {
+      g_set_error (error,
+                   G_FILE_ERROR,
+                   g_file_error_from_errno (errno),
+                   _("Failed to open file '%s': %s"),
+                   filename, strerror (errno));
+      
+      return FALSE;
+    }
+  
+  return get_contents_stdio (filename, f, contents, length, error);
+}
+#endif
+
+/**
+ * g_file_get_contents:
+ * @filename: a file to read contents from
+ * @contents: location to store an allocated string
+ * @length: location to store length in bytes of the contents
+ * @error: return location for a #GError
+ * 
+ * Reads an entire file into allocated memory, with good error
+ * checking. If @error is set, FALSE is returned, and @contents is set
+ * to NULL. If TRUE is returned, @error will not be set, and @contents
+ * will be set to the file contents.  The string stored in @contents
+ * will be nul-terminated, so for text files you can pass NULL for the
+ * @length argument.  The error domain is #G_FILE_ERROR. Possible
+ * error codes are those in the #GFileError enumeration.
+ *
+ * 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: TRUE on success, FALSE if error is set
+ **/
+gboolean
+g_file_get_contents (const gchar *filename,
+                     gchar      **contents,
+                     guint       *length,
+                     GError     **error)
+{  
+  g_return_val_if_fail (filename != NULL, FALSE);
+  g_return_val_if_fail (contents != NULL, FALSE);
+
+  *contents = NULL;
+  if (length)
+    *length = 0;
+
+#ifdef G_OS_WIN32
+  return get_contents_win32 (filename, contents, length, error);
+#else
+  return get_contents_posix (filename, contents, length, error);
+#endif
+}
+
diff --git a/gfileutils.h b/gfileutils.h
new file mode 100644 (file)
index 0000000..f6edd43
--- /dev/null
@@ -0,0 +1,91 @@
+/* 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_BADF,
+  G_FILE_ERROR_INVAL,
+  G_FILE_ERROR_PIPE,
+  G_FILE_ERROR_AGAIN,
+  G_FILE_ERROR_INTR,
+  G_FILE_ERROR_IO,
+  G_FILE_ERROR_PERM,
+  G_FILE_ERROR_FAILED
+} GFileError;
+
+/* For backward-compat reasons, these are synced to an old 
+ * anonymous enum in libgnome. But don't use that enum
+ * in new code.
+ */
+typedef enum
+{
+  G_FILE_TEST_IS_REGULAR    = 1 << 0,
+  G_FILE_TEST_IS_SYMLINK    = 1 << 1,
+  G_FILE_TEST_IS_DIR        = 1 << 2,
+  G_FILE_TEST_IS_EXECUTABLE = 1 << 3,
+  G_FILE_TEST_EXISTS        = 1 << 4,
+} GFileTest;
+
+GQuark     g_file_error_quark      (void);
+/* So other code can generate a GFileError */
+GFileError g_file_error_from_errno (gint err_no);
+
+gboolean g_file_test         (const gchar  *filename,
+                              GFileTest     test);
+gboolean g_file_get_contents (const gchar  *filename,
+                              gchar       **contents,
+                              guint        *length,
+                              GError      **error);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __GFILEUTILS_H__ */
+
+
diff --git a/glib.h b/glib.h
index 42e9aca..d6d53ae 100644 (file)
--- a/glib.h
+++ b/glib.h
@@ -1831,6 +1831,8 @@ gchar*  g_getenv          (const gchar *variable);
  */
 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
  */
@@ -3506,5 +3508,9 @@ gchar* g_convert_with_fallback (const gchar  *str,
 #endif /* __cplusplus */
 
 #include <gunicode.h>
+#include <gerror.h>
+#include <gshell.h>
+#include <gspawn.h>
+#include <gfileutils.h>
 
 #endif /* __G_LIB_H__ */
index 104d783..127d248 100644 (file)
@@ -49,6 +49,7 @@ libglib_1_3_la_SOURCES =      \
        gdataset.c              \
        gdate.c                 \
        gerror.c                \
+       gfileutils.c            \
        ghash.c                 \
        ghook.c                 \
        giochannel.c            \
@@ -63,7 +64,9 @@ libglib_1_3_la_SOURCES =      \
        grel.c                  \
        grand.c                 \
        gscanner.c              \
+       gshell.c                \
        gslist.c                \
+       gspawn.c                \
        gstrfuncs.c             \
        gstring.c               \
        gthread.c               \
@@ -81,8 +84,11 @@ libglib_1_3_la_SOURCES =     \
 glibincludedir=$(includedir)/glib-2.0
 glibinclude_HEADERS =   \
        gerror.h        \
+       gfileutils.h    \
        glib.h          \
        glib-object.h   \
+       gshell.h        \
+       gspawn.h        \
        gunicode.h
 
 configexecincludedir = $(libdir)/glib-2.0/include
diff --git a/glib/gfileutils.c b/glib/gfileutils.c
new file mode 100644 (file)
index 0000000..1176852
--- /dev/null
@@ -0,0 +1,470 @@
+/* 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>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.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)
+{
+  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
+    {
+      struct stat s;
+      
+      if (stat (filename, &s) < 0)
+        return FALSE;
+
+      if ((test & G_FILE_TEST_IS_REGULAR) &&
+          S_ISREG (s.st_mode))
+        return TRUE;
+      else 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 (void)
+{
+  static GQuark q = 0;
+  if (q == 0)
+    q = g_quark_from_static_string ("g-file-error-quark");
+
+  return q;
+}
+
+GFileError
+g_file_error_from_errno (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
+
+#ifdef EBADF
+    case EBADF:
+      return G_FILE_ERROR_BADF;
+      break;
+#endif
+
+#ifdef EINVAL
+    case EINVAL:
+      return G_FILE_ERROR_INVAL;
+      break;
+#endif
+
+#ifdef EPIPE
+    case EPIPE:
+      return G_FILE_ERROR_PIPE;
+      break;
+#endif
+
+#ifdef EAGAIN
+    case EAGAIN:
+      return G_FILE_ERROR_AGAIN;
+      break;
+#endif
+
+#ifdef EINTR
+    case EINTR:
+      return G_FILE_ERROR_INTR;
+      break;
+#endif
+
+#ifdef EIO
+    case EIO:
+      return G_FILE_ERROR_IO;
+      break;
+#endif
+
+#ifdef EPERM
+    case EPERM:
+      return G_FILE_ERROR_PERM;
+      break;
+#endif
+      
+    default:
+      return G_FILE_ERROR_FAILED;
+      break;
+    }
+}
+
+static gboolean
+get_contents_stdio (const gchar *filename,
+                    FILE        *f,
+                    gchar      **contents,
+                    guint       *length,
+                    GError     **error)
+{
+  gchar buf[2048];
+  size_t bytes;
+  GString *str;
+
+  g_assert (f != NULL);
+  
+  str = g_string_new ("");
+  
+  while (!feof (f))
+    {
+      bytes = fread (buf, 1, 2048, f);
+      
+      if (ferror (f))
+        {
+          g_set_error (error,
+                       G_FILE_ERROR,
+                       g_file_error_from_errno (errno),
+                       _("Error reading file '%s': %s"),
+                       filename, strerror (errno));
+
+          g_string_free (str, TRUE);
+          
+          return FALSE;
+        }
+
+      g_string_append_len (str, buf, bytes);
+    }
+
+  fclose (f);
+
+  if (length)
+    *length = str->len;
+  
+  *contents = g_string_free (str, FALSE);
+
+  return TRUE;  
+}
+
+static gboolean
+get_contents_regfile (const gchar *filename,
+                      struct stat *stat_buf,
+                      gint         fd,
+                      gchar      **contents,
+                      guint       *length,
+                      GError     **error)
+{
+  gchar *buf;
+  size_t bytes_read;
+  size_t size;
+      
+  size = stat_buf->st_size;
+
+  buf = g_new (gchar, size + 1);
+      
+  bytes_read = 0;
+  while (bytes_read < size)
+    {
+      gint rc;
+          
+      rc = read (fd, buf + bytes_read, size - bytes_read);
+
+      if (rc < 0)
+        {
+          if (errno != EINTR) 
+            {
+              close (fd);
+
+              g_free (buf);
+                  
+              g_set_error (error,
+                           G_FILE_ERROR,
+                           g_file_error_from_errno (errno),
+                           _("Failed to read from file '%s': %s"),
+                           filename, strerror (errno));
+
+              return FALSE;
+            }
+        }
+      else if (rc == 0)
+        break;
+      else
+        bytes_read += rc;
+    }
+      
+  buf[bytes_read] = '\0';
+
+  if (length)
+    *length = bytes_read;
+  
+  *contents = buf;
+
+  return TRUE;
+}
+
+static gboolean
+get_contents_posix (const gchar *filename,
+                    gchar      **contents,
+                    guint       *length,
+                    GError     **error)
+{
+  struct stat stat_buf;
+  gint fd;
+  
+  fd = open (filename, O_RDONLY);
+
+  if (fd < 0)
+    {
+      g_set_error (error,
+                   G_FILE_ERROR,
+                   g_file_error_from_errno (errno),
+                   _("Failed to open file '%s': %s"),
+                   filename, strerror (errno));
+
+      return FALSE;
+    }
+
+  /* I don't think this will ever fail, aside from ENOMEM, but. */
+  if (fstat (fd, &stat_buf) < 0)
+    {
+      close (fd);
+      
+      g_set_error (error,
+                   G_FILE_ERROR,
+                   g_file_error_from_errno (errno),
+                   _("Failed to get attributes of file '%s': fstat() failed: %s"),
+                   filename, strerror (errno));
+
+      return FALSE;
+    }
+
+  if (stat_buf.st_size > 0 && S_ISREG (stat_buf.st_mode))
+    {
+      return get_contents_regfile (filename,
+                                   &stat_buf,
+                                   fd,
+                                   contents,
+                                   length,
+                                   error);
+    }
+  else
+    {
+      FILE *f;
+
+      f = fdopen (fd, "r");
+      
+      if (f == NULL)
+        {
+          g_set_error (error,
+                       G_FILE_ERROR,
+                       g_file_error_from_errno (errno),
+                       _("Failed to open file '%s': fdopen() failed: %s"),
+                       filename, strerror (errno));
+          
+          return FALSE;
+        }
+  
+      return get_contents_stdio (filename, f, contents, length, error);
+    }
+}
+
+#ifdef G_OS_WIN32
+static gboolean
+get_contents_win32 (const gchar *filename,
+                    gchar      **contents,
+                    guint       *length,
+                    GError     **error)
+{
+  FILE *f;
+
+  /* I guess you want binary mode; maybe you want text sometimes? */
+  f = fopen (filename, "rb");
+
+  if (f == NULL)
+    {
+      g_set_error (error,
+                   G_FILE_ERROR,
+                   g_file_error_from_errno (errno),
+                   _("Failed to open file '%s': %s"),
+                   filename, strerror (errno));
+      
+      return FALSE;
+    }
+  
+  return get_contents_stdio (filename, f, contents, length, error);
+}
+#endif
+
+/**
+ * g_file_get_contents:
+ * @filename: a file to read contents from
+ * @contents: location to store an allocated string
+ * @length: location to store length in bytes of the contents
+ * @error: return location for a #GError
+ * 
+ * Reads an entire file into allocated memory, with good error
+ * checking. If @error is set, FALSE is returned, and @contents is set
+ * to NULL. If TRUE is returned, @error will not be set, and @contents
+ * will be set to the file contents.  The string stored in @contents
+ * will be nul-terminated, so for text files you can pass NULL for the
+ * @length argument.  The error domain is #G_FILE_ERROR. Possible
+ * error codes are those in the #GFileError enumeration.
+ *
+ * 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: TRUE on success, FALSE if error is set
+ **/
+gboolean
+g_file_get_contents (const gchar *filename,
+                     gchar      **contents,
+                     guint       *length,
+                     GError     **error)
+{  
+  g_return_val_if_fail (filename != NULL, FALSE);
+  g_return_val_if_fail (contents != NULL, FALSE);
+
+  *contents = NULL;
+  if (length)
+    *length = 0;
+
+#ifdef G_OS_WIN32
+  return get_contents_win32 (filename, contents, length, error);
+#else
+  return get_contents_posix (filename, contents, length, error);
+#endif
+}
+
diff --git a/glib/gfileutils.h b/glib/gfileutils.h
new file mode 100644 (file)
index 0000000..f6edd43
--- /dev/null
@@ -0,0 +1,91 @@
+/* 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_BADF,
+  G_FILE_ERROR_INVAL,
+  G_FILE_ERROR_PIPE,
+  G_FILE_ERROR_AGAIN,
+  G_FILE_ERROR_INTR,
+  G_FILE_ERROR_IO,
+  G_FILE_ERROR_PERM,
+  G_FILE_ERROR_FAILED
+} GFileError;
+
+/* For backward-compat reasons, these are synced to an old 
+ * anonymous enum in libgnome. But don't use that enum
+ * in new code.
+ */
+typedef enum
+{
+  G_FILE_TEST_IS_REGULAR    = 1 << 0,
+  G_FILE_TEST_IS_SYMLINK    = 1 << 1,
+  G_FILE_TEST_IS_DIR        = 1 << 2,
+  G_FILE_TEST_IS_EXECUTABLE = 1 << 3,
+  G_FILE_TEST_EXISTS        = 1 << 4,
+} GFileTest;
+
+GQuark     g_file_error_quark      (void);
+/* So other code can generate a GFileError */
+GFileError g_file_error_from_errno (gint err_no);
+
+gboolean g_file_test         (const gchar  *filename,
+                              GFileTest     test);
+gboolean g_file_get_contents (const gchar  *filename,
+                              gchar       **contents,
+                              guint        *length,
+                              GError      **error);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __GFILEUTILS_H__ */
+
+
index 42e9aca..d6d53ae 100644 (file)
@@ -1831,6 +1831,8 @@ gchar*  g_getenv          (const gchar *variable);
  */
 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
  */
@@ -3506,5 +3508,9 @@ gchar* g_convert_with_fallback (const gchar  *str,
 #endif /* __cplusplus */
 
 #include <gunicode.h>
+#include <gerror.h>
+#include <gshell.h>
+#include <gspawn.h>
+#include <gfileutils.h>
 
 #endif /* __G_LIB_H__ */
diff --git a/glib/gshell.c b/glib/gshell.c
new file mode 100644 (file)
index 0000000..4bae260
--- /dev/null
@@ -0,0 +1,651 @@
+/* gshell.c - Shell-related utilities
+ *
+ *  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 <string.h>
+
+#ifdef _
+#warning "FIXME remove gettext hack"
+#endif
+
+#define _(x) x
+
+GQuark
+g_shell_error_quark (void)
+{
+  static GQuark quark = 0;
+  if (quark == 0)
+    quark = g_quark_from_static_string ("g-shell-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_SHELL_ERROR,
+                           G_SHELL_ERROR_BAD_QUOTING,
+                           _("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_SHELL_ERROR,
+                       G_SHELL_ERROR_BAD_QUOTING,
+                       _("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(). Possible
+ * errors are in the #G_SHELL_ERROR domain.
+ * 
+ * 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_SHELL_ERROR,
+                     G_SHELL_ERROR_BAD_QUOTING,
+                     _("Text ended just after a '\' character."
+                       " (The text was '%s')"),
+                     command_line);
+      else
+        g_set_error (error,
+                     G_SHELL_ERROR,
+                     G_SHELL_ERROR_BAD_QUOTING,
+                     _("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_SHELL_ERROR,
+                   G_SHELL_ERROR_EMPTY_STRING,
+                   _("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;
+}
+
+/**
+ * g_shell_parse_argv:
+ * @command_line: command line to parse
+ * @argcp: return location for number of args
+ * @argvp: return location for array of args
+ * @error: return location for error
+ * 
+ * Parses a command line into an argument vector, in much the same way
+ * the shell would, but without many of the expansions the shell would
+ * perform (variable expansion, globs, operators, filename expansion,
+ * etc. are not supported). The results are defined to be the same as
+ * those you would get from a UNIX98 /bin/sh, as long as the input
+ * contains none of the unsupported shell expansions. If the input
+ * does contain such expansions, they are passed through
+ * literally. Possible errors are those from the #G_SHELL_ERROR
+ * domain.
+ * 
+ * Return value: TRUE on success, FALSE if error set
+ **/
+gboolean
+g_shell_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;
+}
diff --git a/glib/gshell.h b/glib/gshell.h
new file mode 100644 (file)
index 0000000..0f7fd1f
--- /dev/null
@@ -0,0 +1,59 @@
+/* gshell.h - Shell-related utilities
+ *
+ *  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 __GSHELL_H__
+#define __GSHELL_H__
+
+#include <gerror.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define G_SHELL_ERROR g_shell_error_quark ()
+
+typedef enum
+{
+  /* mismatched or otherwise mangled quoting */
+  G_SHELL_ERROR_BAD_QUOTING,
+  /* string to be parsed was empty */
+  G_SHELL_ERROR_EMPTY_STRING,
+  G_SHELL_ERROR_FAILED
+} GShellError;
+
+GQuark g_shell_error_quark (void);
+
+gchar*   g_shell_quote      (const gchar   *unquoted_string);
+gchar*   g_shell_unquote    (const gchar   *quoted_string,
+                             GError       **error);
+gboolean g_shell_parse_argv (const gchar   *command_line,
+                             gint          *argc,
+                             gchar       ***argv,
+                             GError       **error);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __GSHELL_H__ */
+
+
diff --git a/glib/gspawn.c b/glib/gspawn.c
new file mode 100644 (file)
index 0000000..b6af58d
--- /dev/null
@@ -0,0 +1,1392 @@
+/* gspawn.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;
+}
+
+/**
+ * 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. Possible errors are those from the #G_SPAWN_ERROR domain.
+ *
+ * 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_shell_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. Possible errors are those from g_spawn_sync() and those
+ * from g_shell_parse_argv().
+ * 
+ * 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_shell_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_shell_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. Possible
+ * errors are those from g_shell_parse_argv() and g_spawn_async().
+ * 
+ * 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_shell_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;
+}
diff --git a/glib/gspawn.h b/glib/gspawn.h
new file mode 100644 (file)
index 0000000..4ae0536
--- /dev/null
@@ -0,0 +1,132 @@
+/* gspawn.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_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);
+
+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 3c2c29e..c45fc7a 100644 (file)
@@ -118,9 +118,16 @@ g_unichar_ispunct (gunichar c)
 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 c7284ca..4910beb 100644 (file)
@@ -143,6 +143,106 @@ g_atexit (GVoidFunc func)
     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;
+}
+
+/**
+ * g_find_program_in_path:
+ * @file: a program name
+ * 
+ * Locates the first executable named @file in the user's path, in the
+ * same way that execvp() would locate it. Returns an allocated string
+ * with the absolute path name, or NULL if the program is not found in
+ * the path. If @file is already an absolute path, returns a copy of
+ * @file if @file exists and is executable, and NULL otherwise.
+ * 
+ * Return value: absolute path, or NULL
+ **/
+gchar*
+g_find_program_in_path (const gchar *file)
+{
+  gchar *path, *p, *name, *freeme;
+  size_t len;
+  size_t pathlen;
+
+  g_return_val_if_fail (file != NULL, NULL);
+
+  if (*file == '/')
+    {
+      if (g_file_test (file, G_FILE_TEST_IS_EXECUTABLE))
+        return g_strdup (file);
+      else
+        return NULL;
+    }
+  
+  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,
diff --git a/gshell.c b/gshell.c
new file mode 100644 (file)
index 0000000..4bae260
--- /dev/null
+++ b/gshell.c
@@ -0,0 +1,651 @@
+/* gshell.c - Shell-related utilities
+ *
+ *  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 <string.h>
+
+#ifdef _
+#warning "FIXME remove gettext hack"
+#endif
+
+#define _(x) x
+
+GQuark
+g_shell_error_quark (void)
+{
+  static GQuark quark = 0;
+  if (quark == 0)
+    quark = g_quark_from_static_string ("g-shell-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_SHELL_ERROR,
+                           G_SHELL_ERROR_BAD_QUOTING,
+                           _("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_SHELL_ERROR,
+                       G_SHELL_ERROR_BAD_QUOTING,
+                       _("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(). Possible
+ * errors are in the #G_SHELL_ERROR domain.
+ * 
+ * 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_SHELL_ERROR,
+                     G_SHELL_ERROR_BAD_QUOTING,
+                     _("Text ended just after a '\' character."
+                       " (The text was '%s')"),
+                     command_line);
+      else
+        g_set_error (error,
+                     G_SHELL_ERROR,
+                     G_SHELL_ERROR_BAD_QUOTING,
+                     _("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_SHELL_ERROR,
+                   G_SHELL_ERROR_EMPTY_STRING,
+                   _("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;
+}
+
+/**
+ * g_shell_parse_argv:
+ * @command_line: command line to parse
+ * @argcp: return location for number of args
+ * @argvp: return location for array of args
+ * @error: return location for error
+ * 
+ * Parses a command line into an argument vector, in much the same way
+ * the shell would, but without many of the expansions the shell would
+ * perform (variable expansion, globs, operators, filename expansion,
+ * etc. are not supported). The results are defined to be the same as
+ * those you would get from a UNIX98 /bin/sh, as long as the input
+ * contains none of the unsupported shell expansions. If the input
+ * does contain such expansions, they are passed through
+ * literally. Possible errors are those from the #G_SHELL_ERROR
+ * domain.
+ * 
+ * Return value: TRUE on success, FALSE if error set
+ **/
+gboolean
+g_shell_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;
+}
diff --git a/gshell.h b/gshell.h
new file mode 100644 (file)
index 0000000..0f7fd1f
--- /dev/null
+++ b/gshell.h
@@ -0,0 +1,59 @@
+/* gshell.h - Shell-related utilities
+ *
+ *  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 __GSHELL_H__
+#define __GSHELL_H__
+
+#include <gerror.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define G_SHELL_ERROR g_shell_error_quark ()
+
+typedef enum
+{
+  /* mismatched or otherwise mangled quoting */
+  G_SHELL_ERROR_BAD_QUOTING,
+  /* string to be parsed was empty */
+  G_SHELL_ERROR_EMPTY_STRING,
+  G_SHELL_ERROR_FAILED
+} GShellError;
+
+GQuark g_shell_error_quark (void);
+
+gchar*   g_shell_quote      (const gchar   *unquoted_string);
+gchar*   g_shell_unquote    (const gchar   *quoted_string,
+                             GError       **error);
+gboolean g_shell_parse_argv (const gchar   *command_line,
+                             gint          *argc,
+                             gchar       ***argv,
+                             GError       **error);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __GSHELL_H__ */
+
+
diff --git a/gspawn.c b/gspawn.c
new file mode 100644 (file)
index 0000000..b6af58d
--- /dev/null
+++ b/gspawn.c
@@ -0,0 +1,1392 @@
+/* gspawn.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;
+}
+
+/**
+ * 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. Possible errors are those from the #G_SPAWN_ERROR domain.
+ *
+ * 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_shell_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. Possible errors are those from g_spawn_sync() and those
+ * from g_shell_parse_argv().
+ * 
+ * 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_shell_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_shell_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. Possible
+ * errors are those from g_shell_parse_argv() and g_spawn_async().
+ * 
+ * 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_shell_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;
+}
diff --git a/gspawn.h b/gspawn.h
new file mode 100644 (file)
index 0000000..4ae0536
--- /dev/null
+++ b/gspawn.h
@@ -0,0 +1,132 @@
+/* gspawn.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_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);
+
+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 3c2c29e..c45fc7a 100644 (file)
@@ -118,9 +118,16 @@ g_unichar_ispunct (gunichar c)
 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 c7284ca..4910beb 100644 (file)
--- a/gutils.c
+++ b/gutils.c
@@ -143,6 +143,106 @@ g_atexit (GVoidFunc func)
     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;
+}
+
+/**
+ * g_find_program_in_path:
+ * @file: a program name
+ * 
+ * Locates the first executable named @file in the user's path, in the
+ * same way that execvp() would locate it. Returns an allocated string
+ * with the absolute path name, or NULL if the program is not found in
+ * the path. If @file is already an absolute path, returns a copy of
+ * @file if @file exists and is executable, and NULL otherwise.
+ * 
+ * Return value: absolute path, or NULL
+ **/
+gchar*
+g_find_program_in_path (const gchar *file)
+{
+  gchar *path, *p, *name, *freeme;
+  size_t len;
+  size_t pathlen;
+
+  g_return_val_if_fail (file != NULL, NULL);
+
+  if (*file == '/')
+    {
+      if (g_file_test (file, G_FILE_TEST_IS_EXECUTABLE))
+        return g_strdup (file);
+      else
+        return NULL;
+    }
+  
+  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 4e3d0fb..f2846e9 100644 (file)
@@ -1,6 +1,8 @@
 
 INCLUDES = -I$(top_srcdir) @GLIB_DEBUG_FLAGS@
 
+EFENCE=
+
 EXTRA_DIST = \
        makefile.msc    \
        makefile.msc.in \
@@ -18,7 +20,9 @@ TESTS = \
        queue-test      \
        rand-test       \
        relation-test   \
+       shell-test      \
        slist-test      \
+       spawn-test      \
        strfunc-test    \
        string-test     \
        thread-test     \
@@ -28,7 +32,7 @@ TESTS = \
 
 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)
@@ -41,7 +45,9 @@ node_test_LDADD = $(progs_LDADD)
 queue_test_LDADD = $(progs_LDADD)
 rand_test_LDADD = $(progs_LDADD)
 relation_test_LDADD = $(progs_LDADD)
+shell_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)
diff --git a/tests/shell-test.c b/tests/shell-test.c
new file mode 100644 (file)
index 0000000..aa5c8c6
--- /dev/null
@@ -0,0 +1,190 @@
+/* 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_shell_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;
+  gint i;
+  
+  i = 0;
+  while (test_command_lines[i])
+    {
+      printf ("g_shell_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;
+}
+
+
diff --git a/tests/spawn-test.c b/tests/spawn-test.c
new file mode 100644 (file)
index 0000000..b4091fa
--- /dev/null
@@ -0,0 +1,96 @@
+/* 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>
+
+
+static void
+run_tests (void)
+{
+  GError *err;
+  gchar *output = NULL;
+  
+  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);
+    }
+}
+
+int
+main (int   argc,
+      char *argv[])
+{
+  run_tests ();
+  
+  return 0;
+}
+
+