summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog24
-rw-r--r--ChangeLog.pre-2-024
-rw-r--r--ChangeLog.pre-2-1024
-rw-r--r--ChangeLog.pre-2-1224
-rw-r--r--ChangeLog.pre-2-224
-rw-r--r--ChangeLog.pre-2-424
-rw-r--r--ChangeLog.pre-2-624
-rw-r--r--ChangeLog.pre-2-824
-rw-r--r--Makefile.am6
-rw-r--r--gfileutils.c470
-rw-r--r--gfileutils.h91
-rw-r--r--glib.h6
-rw-r--r--glib/Makefile.am6
-rw-r--r--glib/gfileutils.c470
-rw-r--r--glib/gfileutils.h91
-rw-r--r--glib/glib.h6
-rw-r--r--glib/gshell.c651
-rw-r--r--glib/gshell.h59
-rw-r--r--glib/gspawn.c1392
-rw-r--r--glib/gspawn.h132
-rw-r--r--glib/guniprop.c13
-rw-r--r--glib/gutils.c100
-rw-r--r--gshell.c651
-rw-r--r--gshell.h59
-rw-r--r--gspawn.c1392
-rw-r--r--gspawn.h132
-rw-r--r--guniprop.c13
-rw-r--r--gutils.c100
-rw-r--r--tests/Makefile.am8
-rw-r--r--tests/shell-test.c190
-rw-r--r--tests/spawn-test.c96
31 files changed, 6319 insertions, 7 deletions
diff --git a/ChangeLog b/ChangeLog
index 88f87d7a0..b8738da28 100644
--- 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
diff --git a/ChangeLog.pre-2-0 b/ChangeLog.pre-2-0
index 88f87d7a0..b8738da28 100644
--- a/ChangeLog.pre-2-0
+++ b/ChangeLog.pre-2-0
@@ -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
diff --git a/ChangeLog.pre-2-10 b/ChangeLog.pre-2-10
index 88f87d7a0..b8738da28 100644
--- a/ChangeLog.pre-2-10
+++ b/ChangeLog.pre-2-10
@@ -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
diff --git a/ChangeLog.pre-2-12 b/ChangeLog.pre-2-12
index 88f87d7a0..b8738da28 100644
--- a/ChangeLog.pre-2-12
+++ b/ChangeLog.pre-2-12
@@ -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
diff --git a/ChangeLog.pre-2-2 b/ChangeLog.pre-2-2
index 88f87d7a0..b8738da28 100644
--- a/ChangeLog.pre-2-2
+++ b/ChangeLog.pre-2-2
@@ -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
diff --git a/ChangeLog.pre-2-4 b/ChangeLog.pre-2-4
index 88f87d7a0..b8738da28 100644
--- a/ChangeLog.pre-2-4
+++ b/ChangeLog.pre-2-4
@@ -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
diff --git a/ChangeLog.pre-2-6 b/ChangeLog.pre-2-6
index 88f87d7a0..b8738da28 100644
--- a/ChangeLog.pre-2-6
+++ b/ChangeLog.pre-2-6
@@ -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
diff --git a/ChangeLog.pre-2-8 b/ChangeLog.pre-2-8
index 88f87d7a0..b8738da28 100644
--- a/ChangeLog.pre-2-8
+++ b/ChangeLog.pre-2-8
@@ -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
diff --git a/Makefile.am b/Makefile.am
index 104d783b1..127d248a1 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -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
index 000000000..11768526d
--- /dev/null
+++ b/gfileutils.c
@@ -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
index 000000000..f6edd431b
--- /dev/null
+++ b/gfileutils.h
@@ -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 42e9acae7..d6d53ae4f 100644
--- 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__ */
diff --git a/glib/Makefile.am b/glib/Makefile.am
index 104d783b1..127d248a1 100644
--- a/glib/Makefile.am
+++ b/glib/Makefile.am
@@ -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
index 000000000..11768526d
--- /dev/null
+++ b/glib/gfileutils.c
@@ -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
index 000000000..f6edd431b
--- /dev/null
+++ b/glib/gfileutils.h
@@ -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/glib.h b/glib/glib.h
index 42e9acae7..d6d53ae4f 100644
--- a/glib/glib.h
+++ b/glib/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__ */
diff --git a/glib/gshell.c b/glib/gshell.c
new file mode 100644
index 000000000..4bae260be
--- /dev/null
+++ b/glib/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/glib/gshell.h b/glib/gshell.h
new file mode 100644
index 000000000..0f7fd1f3b
--- /dev/null
+++ b/glib/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/glib/gspawn.c b/glib/gspawn.c
new file mode 100644
index 000000000..b6af58de7
--- /dev/null
+++ b/glib/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/glib/gspawn.h b/glib/gspawn.h
new file mode 100644
index 000000000..4ae0536b6
--- /dev/null
+++ b/glib/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__ */
+
+
diff --git a/glib/guniprop.c b/glib/guniprop.c
index 3c2c29e6a..c45fc7ab8 100644
--- a/glib/guniprop.c
+++ b/glib/guniprop.c
@@ -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);
+ }
}
/**
diff --git a/glib/gutils.c b/glib/gutils.c
index c7284ca29..4910beb0c 100644
--- a/glib/gutils.c
+++ b/glib/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,
diff --git a/gshell.c b/gshell.c
new file mode 100644
index 000000000..4bae260be
--- /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
index 000000000..0f7fd1f3b
--- /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
index 000000000..b6af58de7
--- /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
index 000000000..4ae0536b6
--- /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__ */
+
+
diff --git a/guniprop.c b/guniprop.c
index 3c2c29e6a..c45fc7ab8 100644
--- a/guniprop.c
+++ b/guniprop.c
@@ -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);
+ }
}
/**
diff --git a/gutils.c b/gutils.c
index c7284ca29..4910beb0c 100644
--- 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,
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 4e3d0fbf1..f2846e91b 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -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
index 000000000..aa5c8c655
--- /dev/null
+++ b/tests/shell-test.c
@@ -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
index 000000000..b4091faff
--- /dev/null
+++ b/tests/spawn-test.c
@@ -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;
+}
+
+