g_exec_* and shell quote/unquote commit candidate



Hi,

OK, this version addresses all issues raised so far.

Changes since the last:

 - docs

 - g_shell_quote() and g_shell_unquote() work

 - g_parse_argv() totally rewritten based on UNIX98 spec; now produces
   same result as bash in all cases I've come up with.

 - the _simple() versions of the functions default to searching the 
   path. This is because they take a command line, and so it seems
   appropriate. However it might be a security problem. Possibly 
   the _simple() versions should just have a flags argument, making
   them somewhat less simple.

 - test cases for g_parse_argv() in the test program

g_parse_argv() test cases I have so far are:

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
};

If you can think of other important cases, say so.

If you want to know far too much about this, see 
http://www.opengroup.org/onlinepubs/007908799/xcu/chap2.html

Shell unquoting and argv parsing are guaranteed to produce the same
result as the shell if the string contains no: tilde expansion,
parameter expansion, command substitutions, arithmetic expansion, or
pathname expansion.  If the string contains those things, then they
will get passed through as any other character instead of getting
expanded/evaluated. The shell result will also be different if IFS is
set.

Shell quoting is guaranteed to produce a string that the shell will
evaluate to the original, unquoted string. There are no guarantees
about the type of quotes or escapes that it will use.

Havoc

/* gexec.h - Process launching
 *
 *  Copyright 2000 Red Hat, Inc.
 *
 * GLib is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * GLib is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with GLib; see the file COPYING.LIB.  If not, write
 * to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#ifndef __GEXEC_H__
#define __GEXEC_H__

#include <gerror.h>

#ifdef __cplusplus
extern "C"
{
#endif

/* I'm not sure I remember our proposed naming convention here. */
#define G_EXEC_ERROR g_exec_error_quark ()

typedef enum
{
  G_EXEC_ERROR_PARSE,  /* failed to parse command line */
  G_EXEC_ERROR_FORK,   /* fork failed due to lack of memory */
  G_EXEC_ERROR_READ,   /* read or select on pipes failed */
  G_EXEC_ERROR_CHDIR,  /* changing to working dir failed */
  G_EXEC_ERROR_ACCES,  /* execv() returned EACCES */
  G_EXEC_ERROR_PERM,   /* execv() returned EPERM */
  G_EXEC_ERROR_2BIG,   /* execv() returned E2BIG */
  G_EXEC_ERROR_NOEXEC, /* execv() returned ENOEXEC */
  G_EXEC_ERROR_NAMETOOLONG, /* ""  "" ENAMETOOLONG */
  G_EXEC_ERROR_NOENT,       /* ""  "" ENOENT */
  G_EXEC_ERROR_NOMEM,       /* ""  "" ENOMEM */
  G_EXEC_ERROR_NOTDIR,      /* ""  "" ENOTDIR */
  G_EXEC_ERROR_LOOP,        /* ""  "" ELOOP   */
  G_EXEC_ERROR_TXTBUSY,     /* ""  "" ETXTBUSY */
  G_EXEC_ERROR_IO,          /* ""  "" EIO */
  G_EXEC_ERROR_NFILE,       /* ""  "" ENFILE */
  G_EXEC_ERROR_MFILE,       /* ""  "" EMFLE */
  G_EXEC_ERROR_INVAL,       /* ""  "" EINVAL */
  G_EXEC_ERROR_ISDIR,       /* ""  "" EISDIR */
  G_EXEC_ERROR_LIBBAD,      /* ""  "" ELIBBAD */
  G_EXEC_ERROR_FAILED       /* other fatal failure, error->message
                             * should explain
                             */
} GExecError;

typedef void (* GExecChildSetupFunc) (gpointer user_data);

typedef enum
{
  G_EXEC_LEAVE_DESCRIPTORS_OPEN = 1 << 0,
  G_EXEC_DO_NOT_REAP_CHILD = 1 << 1,
  /* look for argv[0] in the path i.e. use execvp() */
  G_EXEC_SEARCH_PATH = 1 << 2,
  /* Dump output to /dev/null */
  G_EXEC_STDOUT_TO_DEV_NULL = 1 << 3,
  G_EXEC_STDERR_TO_DEV_NULL = 1 << 4,
  G_EXEC_CHILD_INHERITS_STDIN = 1 << 5
} GExecFlags;

GQuark g_exec_error_quark (void);

gchar* g_shell_quote   (const gchar  *unquoted_string);
gchar* g_shell_unquote (const gchar  *quoted_string,
                        GError      **error);


gboolean g_parse_argv (const gchar           *command_line,
                       gint                  *argc,
                       gchar               ***argv,
                       GError               **error);
gboolean g_exec_async (const gchar           *working_directory,
                       gchar                **argv,
                       gchar                **envp,
                       GExecFlags             flags,
                       GExecChildSetupFunc    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_exec_async_with_pipes (const gchar          *working_directory,
                                  gchar               **argv,
                                  gchar               **envp,
                                  GExecFlags            flags,
                                  GExecChildSetupFunc   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_exec_sync         (const gchar          *working_directory,
                              gchar               **argv,
                              gchar               **envp,
                              GExecFlags            flags,
                              GExecChildSetupFunc   child_setup,
                              gpointer              user_data,
                              gchar               **standard_output,
                              gchar               **standard_error,
                              gint                 *exit_status,
                              GError              **error);
gboolean g_exec_sync_simple  (const gchar          *command_line,
                              gchar               **standard_output,
                              gchar               **standard_error,
                              gint                 *exit_status,
                              GError              **error);
gboolean g_exec_async_simple (const gchar          *command_line,
                              GError              **error);


#ifdef __cplusplus
}
#endif

#endif /* __GERROR_H__ */

/* gexec.c - 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.
 */

#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>

#define _(x) x

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,
                                      GExecChildSetupFunc   child_setup,
                                      gpointer              user_data,
                                      gint                 *child_pid,
                                      gint                 *standard_input,
                                      gint                 *standard_output,
                                      gint                 *standard_error,
                                      GError              **error);


GQuark
g_exec_error_quark (void)
{
  static GQuark quark = 0;
  if (quark == 0)
    quark = g_quark_from_static_string ("g-exec-error-quark");
  return quark;
}


/* Single quotes preserve the literal string exactly. escape
 * sequences are not allowed; not even \' - if you want a '
 * in the quoted text, you have to do something like 'foo'\''bar'
 *
 * Double quotes allow $ ` " \ and newline to be escaped with backslash.
 * Otherwise double quotes preserve things literally.
 */

gboolean 
unquote_string_inplace (gchar* str, gchar** end, GError** err)
{
  gchar* dest;
  gchar* s;
  gchar quote_char;
  
  g_return_val_if_fail(end != NULL, FALSE);
  g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
  g_return_val_if_fail(str != NULL, FALSE);
  
  dest = s = str;

  quote_char = *s;
  
  if (!(*s == '"' || *s == '\''))
    {
      if (err)
        *err = g_error_new(G_EXEC_ERROR,
                           G_EXEC_ERROR_PARSE,
                           _("Quoted text doesn't begin with a quotation mark"));
      *end = str;
      return FALSE;
    }

  /* Skip the initial quote mark */
  ++s;

  if (quote_char == '"')
    {
      while (*s)
        {
          g_assert(s > dest); /* loop invariant */
      
          switch (*s)
            {
            case '"':
              /* End of the string, return now */
              *dest = '\0';
              ++s;
              *end = s;
              return TRUE;
              break;

            case '\\':
              /* Possible escaped quote or \ */
              ++s;
              switch (*s)
                {
                case '"':
                case '\\':
                case '`':
                case '$':
                case '\n':
                  *dest = *s;
                  ++s;
                  ++dest;
                  break;

                default:
                  /* not an escaped char */
                  *dest = '\\';
                  ++dest;
                  /* ++s already done. */
                  break;
                }
              break;

            default:
              *dest = *s;
              ++dest;
              ++s;
              break;
            }

          g_assert(s > dest); /* loop invariant */
        }
    }
  else
    {
      while (*s)
        {
          g_assert(s > dest); /* loop invariant */
          
          if (*s == '\'')
            {
              /* End of the string, return now */
              *dest = '\0';
              ++s;
              *end = s;
              return TRUE;
            }
          else
            {
              *dest = *s;
              ++dest;
              ++s;
            }

          g_assert(s > dest); /* loop invariant */
        }
    }
  
  /* If we reach here this means the close quote was never encountered */

  *dest = '\0';
  
  if (err)
    *err = g_error_new(G_EXEC_ERROR,
                       G_EXEC_ERROR_PARSE,
                       _("Unmatched quotation mark in command line or other shell-quoted text"));
  *end = s;
  return FALSE;
}

/**
 * g_shell_quote:
 * @unquoted_string: a literal string
 * 
 * Quotes a string so that the shell (/bin/sh) will interpret the
 * quoted string to mean @unquoted_string. If you pass a filename to
 * the shell, for example, you should first quote it with this
 * function.  The return value must be freed with g_free(). The
 * quoting style used is undefined (single or double quotes may be
 * used).
 * 
 * Return value: quoted string
 **/
gchar*
g_shell_quote (const gchar *unquoted_string)
{
  /* We always use single quotes, because the algorithm is cheesier.
   * We could use double if we felt like it, that might be more
   * human-readable.
   */

  const gchar *p;
  GString *dest;

  g_return_val_if_fail (unquoted_string != NULL, NULL);
  
  dest = g_string_new ("'");

  p = unquoted_string;

  /* could speed this up a lot by appending chunks of text at a
   * time.
   */
  while (*p)
    {
      /* Replace literal ' with a close ', a \', and a open ' */
      if (*p == '\'')
        g_string_append (dest, "'\''");
      else
        g_string_append_c (dest, *p);

      ++p;
    }

  /* close the quote */
  g_string_append_c (dest, '\'');
  
  return g_string_free (dest, FALSE);
}

/**
 * g_shell_unquote:
 * @quoted_string: shell-quoted string
 * @error: error return location or NULL
 * 
 * Unquotes a string as the shell (/bin/sh) would. Only handles
 * quotes; if a string contains file globs, arithmetic operators,
 * variables, backticks, redirections, or other special-to-the-shell
 * features, the result will be different from the result a real shell
 * would produce (the variables, backticks, etc. will be passed
 * through literally instead of being expanded). This function is
 * guaranteed to succeed if applied to the result of
 * g_shell_quote(). If it fails, it returns NULL and sets the
 * error. The @quoted_string need not actually contain quoted or
 * escaped text; g_shell_unquote() simply goes through the string and
 * unquotes/unescapes anything that the shell would. Both single and
 * double quotes are handled, as are escapes including escaped
 * newlines. The return value must be freed with g_free().
 * 
 * Return value: an unquoted string
 **/
gchar*
g_shell_unquote (const gchar *quoted_string,
                 GError **error)
{
  gchar *unquoted;
  gchar *end;
  gchar *start;
  GString *retval;
  
  g_return_val_if_fail (quoted_string != NULL, NULL);
  
  unquoted = g_strdup (quoted_string);

  start = unquoted;
  end = unquoted;
  retval = g_string_new ("");

  /* The loop allows cases such as
   * "foo"blah blah'bar'woo foo"baz"la la la\'\''foo'
   */
  while (*start)
    {
      /* Append all non-quoted chars, honoring backslash escape
       */
      
      while (*start && !(*start == '"' || *start == '\''))
        {
          if (*start == '\\')
            {
              /* all characters can get escaped by backslash,
               * except newline, which is removed if it follows
               * a backslash outside of quotes
               */
              
              ++start;
              if (*start)
                {
                  if (*start != '\n')
                    g_string_append_c (retval, *start);
                  ++start;
                }
            }
          else
            {
              g_string_append_c (retval, *start);
              ++start;
            }
        }

      if (*start)
        {
          if (!unquote_string_inplace (start, &end, error))
            {
              goto error;
            }
          else
            {
              g_string_append (retval, start);
              start = end;
            }
        }
    }

  return g_string_free (retval, FALSE);
  
 error:
  g_assert (error == NULL || *error != NULL);
  
  g_free (unquoted);
  g_string_free (retval, TRUE);
  return NULL;
}

/* g_parse_argv() does a semi-arbitrary weird subset of the way
 * the shell parses a command line. We don't do variable expansion,
 * don't understand that operators are tokens, don't do tilde expansion,
 * don't do command substitution, no arithmetic expansion, IFS gets ignored,
 * don't do filename globs, don't remove redirection stuff, etc.
 *
 * READ THE UNIX98 SPEC on "Shell Command Language" before changing
 * the behavior of this code.
 *
 * Steps to parsing the argv string:
 *
 *  - tokenize the string (but since we ignore operators,
 *    our tokenization may diverge from what the shell would do)
 *    note that tokenization ignores the internals of a quoted
 *    word and it always splits on spaces, not on IFS even
 *    if we used IFS. We also ignore "end of input indicator"
 *    (I guess this is control-D?)
 *
 *    Tokenization steps, from UNIX98 with operator stuff removed,
 *    are:
 * 
 *    1) "If the current character is backslash, single-quote or
 *        double-quote (\, ' or ") and it is not quoted, it will affect
 *        quoting for subsequent characters up to the end of the quoted
 *        text. The rules for quoting are as described in Quoting
 *        . During token recognition no substitutions will be actually
 *        performed, and the result token will contain exactly the
 *        characters that appear in the input (except for newline
 *        character joining), unmodified, including any embedded or
 *        enclosing quotes or substitution operators, between the quote
 *        mark and the end of the quoted text. The token will not be
 *        delimited by the end of the quoted field."
 *
 *    2) "If the current character is an unquoted newline character,
 *        the current token will be delimited."
 *
 *    3) "If the current character is an unquoted blank character, any
 *        token containing the previous character is delimited and the
 *        current character will be discarded."
 *
 *    4) "If the previous character was part of a word, the current
 *        character will be appended to that word."
 *
 *    5) "If the current character is a "#", it and all subsequent
 *        characters up to, but excluding, the next newline character
 *        will be discarded as a comment. The newline character that
 *        ends the line is not considered part of the comment. The
 *        "#" starts a comment only when it is at the beginning of a
 *        token. Since the search for the end-of-comment does not
 *        consider an escaped newline character specially, a comment
 *        cannot be continued to the next line."
 *
 *    6) "The current character will be used as the start of a new word."
 *
 *
 *  - for each token (word), perform portions of word expansion, namely
 *    field splitting (using default whitespace IFS) and quote
 *    removal.  Field splitting may increase the number of words.
 *    Quote removal does not increase the number of words.
 *
 *   "If the complete expansion appropriate for a word results in an
 *   empty field, that empty field will be deleted from the list of
 *   fields that form the completely expanded command, unless the
 *   original word contained single-quote or double-quote characters."
 *    - UNIX98 spec
 *
 *
 */

static inline void
ensure_token (GString **token)
{
  if (*token == NULL)
    *token = g_string_new ("");
}

static void
delimit_token (GString **token,
               GSList **retval)
{
  if (*token == NULL)
    return;

  *retval = g_slist_prepend (*retval, g_string_free (*token, FALSE));

  *token = NULL;
}

static GSList*
tokenize_command_line (const gchar *command_line,
                       GError **error)
{
  gchar current_quote;
  const gchar *p;
  GString *current_token = NULL;
  GSList *retval = NULL;
  
  current_quote = '\0';
  p = command_line;

  while (*p)
    {
      if (current_quote == '\\')
        {
          if (*p == '\n')
            {
              /* we append nothing; backslash-newline become nothing */
            }
          else
            {
              /* we append the backslash and the current char,
               * to be interpreted later after tokenization
               */
              ensure_token (&current_token);
              g_string_append_c (current_token, '\\');
              g_string_append_c (current_token, *p);
            }

          current_quote = '\0';
        }
      else if (current_quote == '#')
        {
          /* Discard up to and including next newline */
          while (*p && *p != '\n')
            ++p;

          current_quote = '\0';
          
          if (*p == '\0')
            break;
        }
      else if (current_quote)
        {
          if (*p == current_quote &&
              /* check that it isn't an escaped double quote */
              !(current_quote == '"' && p != command_line && *(p - 1) == '\\'))
            {
              /* close the quote */
              current_quote = '\0';
            }

          /* Everything inside quotes, and the close quote,
           * gets appended literally.
           */

          ensure_token (&current_token);
          g_string_append_c (current_token, *p);
        }
      else
        {
          switch (*p)
            {
            case '\n':
              delimit_token (&current_token, &retval);
              break;

            case ' ':
            case '\t':
              /* If the current token contains the previous char, delimit
               * the current token. A nonzero length
               * token should always contain the previous char.
               */
              if (current_token &&
                  current_token->len > 0)
                {
                  delimit_token (&current_token, &retval);
                }
              
              /* discard all unquoted blanks (don't add them to a token) */
              break;


              /* single/double quotes are appended to the token,
               * escapes are maybe appended next time through the loop,
               * comment chars are never appended.
               */
              
            case '\'':
            case '"':
              ensure_token (&current_token);
              g_string_append_c (current_token, *p);

              /* FALL THRU */
              
            case '#':
            case '\\':
              current_quote = *p;
              break;

            default:
              /* Combines rules 4) and 6) - if we have a token, append to it,
               * otherwise create a new token.
               */
              ensure_token (&current_token);
              g_string_append_c (current_token, *p);
              break;
            }
        }

      ++p;
    }

  delimit_token (&current_token, &retval);

  if (current_quote)
    {
      if (current_quote == '\\')
        g_set_error (error,
                     G_EXEC_ERROR,
                     G_EXEC_ERROR_PARSE,
                     _("Text ended just after a '\' character."
                       " (The text was '%s')"),
                     command_line);
      else
        g_set_error (error,
                     G_EXEC_ERROR,
                     G_EXEC_ERROR_PARSE,
                     _("Text ended before matching quote was found for %c."
                       " (The text was '%s')"),
                     current_quote, command_line);
      
      goto error;
    }

  if (retval == NULL)
    {
      g_set_error (error,
                   G_EXEC_ERROR,
                   G_EXEC_ERROR_PARSE,
                   _("Text was empty (or contained only whitespace)"));

      goto error;
    }
  
  /* we appended backward */
  retval = g_slist_reverse (retval);

  return retval;

 error:
  g_assert (error == NULL || *error != NULL);
  
  if (retval)
    {
      g_slist_foreach (retval, (GFunc)g_free, NULL);
      g_slist_free (retval);
    }

  return NULL;
}

gboolean
g_parse_argv (const gchar *command_line,
              gint        *argcp,
              gchar     ***argvp,
              GError     **error)
{
  /* Code based on poptParseArgvString() from libpopt */
  gint argc = 0;
  gchar **argv = NULL;
  GSList *tokens = NULL;
  gint i;
  GSList *tmp_list;
  
  g_return_val_if_fail (command_line != NULL, FALSE);

  tokens = tokenize_command_line (command_line, error);
  if (tokens == NULL)
    return FALSE;

  /* Because we can't have introduced any new blank space into the
   * tokens (we didn't do any new expansions), we don't need to
   * perform field splitting. If we were going to honor IFS or do any
   * expansions, we would have to do field splitting on each word
   * here. Also, if we were going to do any expansion we would need to
   * remove any zero-length words that didn't contain quotes
   * originally; but since there's no expansion we know all words have
   * nonzero length, unless they contain quotes.
   * 
   * So, we simply remove quotes, and don't do any field splitting or
   * empty word removal, since we know there was no way to introduce
   * such things.
   */

  argc = g_slist_length (tokens);
  argv = g_new0 (gchar*, argc + 1);
  i = 0;
  tmp_list = tokens;
  while (tmp_list)
    {
      argv[i] = g_shell_unquote (tmp_list->data, error);

      /* Since we already checked that quotes matched up in the
       * tokenizer, this shouldn't be possible to reach I guess.
       */
      if (argv[i] == NULL)
        goto failed;

      tmp_list = g_slist_next (tmp_list);
      ++i;
    }
  
  g_slist_foreach (tokens, (GFunc)g_free, NULL);
  g_slist_free (tokens);
  
  if (argcp)
    *argcp = argc;

  if (argvp)
    *argvp = argv;
  else
    g_strfreev (argv);

  return TRUE;

 failed:

  g_assert (error == NULL || *error != NULL);
  g_strfreev (argv);
  g_slist_foreach (tokens, (GFunc) g_free, NULL);
  g_slist_free (tokens);
  
  return FALSE;
}

/**
 * g_exec_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 #GExecFlags
 * @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_exec_async_with_pipes() for a full description; this function
 * simply calls the g_exec_async_with_pipes() without any pipes.
 * 
 * Return value: TRUE on success, FALSE if error is set
 **/
gboolean
g_exec_async (const gchar          *working_directory,
              gchar               **argv,
              gchar               **envp,
              GExecFlags            flags,
              GExecChildSetupFunc   child_setup,
              gpointer              user_data,
              gint                 *child_pid,
              GError              **error)
{
  g_return_val_if_fail (argv != NULL, FALSE);
  /* Can't call execvp() and execve() at the same time */
  g_return_val_if_fail (envp == NULL || !(G_EXEC_SEARCH_PATH & flags), FALSE);
  
  return g_exec_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_EXEC_ERROR,
                   G_EXEC_ERROR_READ,
                   _("Failed to read data from child process (%s)"),
                   g_strerror (errno));
      
      return READ_FAILED;
    }
  else
    return READ_OK;
}

/**
 * g_exec_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 #GExecFlags
 * @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_exec_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_exec_sync (const gchar          *working_directory,
             gchar               **argv,
             gchar               **envp,
             GExecFlags            flags,
             GExecChildSetupFunc   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 (envp == NULL || !(G_EXEC_SEARCH_PATH & flags), FALSE);
  g_return_val_if_fail (!(flags & G_EXEC_DO_NOT_REAP_CHILD), FALSE);
  g_return_val_if_fail (standard_output == NULL ||
                        !(flags & G_EXEC_STDOUT_TO_DEV_NULL), FALSE);
  g_return_val_if_fail (standard_error == NULL ||
                        !(flags & G_EXEC_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_EXEC_LEAVE_DESCRIPTORS_OPEN),
                             (flags & G_EXEC_SEARCH_PATH) != 0,
                             (flags & G_EXEC_STDOUT_TO_DEV_NULL) != 0,
                             (flags & G_EXEC_STDERR_TO_DEV_NULL) != 0,
                             (flags & G_EXEC_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_EXEC_ERROR,
                       G_EXEC_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_exec_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_exec_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_EXEC_ERROR,
                           G_EXEC_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_exec_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 #GExecFlags
 * @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_EXEC_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_EXEC_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_EXEC_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_EXEC_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_EXEC_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_EXEC_STDERR_TO_DEV_NULL means that the child's standard error
 * will be discarded. %G_EXEC_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_EXEC_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_exec_async_with_pipes() must close these file descriptors
 * when they are no longer in use. If these parameters are NULL, the
 * corresponding pipe won't be created.
 *
 * @error can be NULL to ignore errors, or non-NULL to report errors.
 * If an error is set, the function returns FALSE. Errors
 * are reported even if they occur in the child (for example if the
 * executable in <literal>argv[0]</literal> is not found). Typically
 * the <literal>message</literal> field of returned errors should be displayed
 * to users.
 *
 * If an error occurs, @child_pid, @standard_input, @standard_output,
 * and @standard_error will not be filled with valid values.
 * 
 * Return value: TRUE on success, FALSE if an error was set
 **/
gboolean
g_exec_async_with_pipes (const gchar          *working_directory,
                         gchar               **argv,
                         gchar               **envp,
                         GExecFlags            flags,
                         GExecChildSetupFunc   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 (envp == NULL || !(G_EXEC_SEARCH_PATH & flags), FALSE);
  g_return_val_if_fail (standard_output == NULL ||
                        !(flags & G_EXEC_STDOUT_TO_DEV_NULL), FALSE);
  g_return_val_if_fail (standard_error == NULL ||
                        !(flags & G_EXEC_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_EXEC_CHILD_INHERITS_STDIN), FALSE);
  
  return fork_exec_with_pipes (!(flags & G_EXEC_DO_NOT_REAP_CHILD),
                               working_directory,
                               argv,
                               envp,
                               !(flags & G_EXEC_LEAVE_DESCRIPTORS_OPEN),
                               (flags & G_EXEC_SEARCH_PATH) != 0,
                               (flags & G_EXEC_STDOUT_TO_DEV_NULL) != 0,
                               (flags & G_EXEC_STDERR_TO_DEV_NULL) != 0,
                               (flags & G_EXEC_CHILD_INHERITS_STDIN) != 0,
                               child_setup,
                               user_data,
                               child_pid,
                               standard_input,
                               standard_output,
                               standard_error,
                               error);
}

/**
 * g_exec_sync_simple:
 * @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_exec_sync() with little-used parameters
 * removed, taking a command line instead of an argument vector.  See
 * g_exec_sync() for full details. @command_line will be parsed by
 * g_parse_argv(). The %G_EXEC_SEARCH_PATH flag is enabled, other
 * flags are not. Note that %G_EXEC_SEARCH_PATH can have security
 * implications, so consider using g_exec_sync() directly if
 * appropriate.
 * 
 * Return value: TRUE on success, FALSE if an error was set
 **/
gboolean
g_exec_sync_simple (const gchar  *command_line,
                    gchar       **standard_output,
                    gchar       **standard_error,
                    gint         *exit_status,
                    GError      **error)
{
  gboolean retval;
  gchar **argv = 0;

  g_return_val_if_fail (command_line != NULL, FALSE);
  
  if (!g_parse_argv (command_line,
                     NULL, &argv,
                     error))
    return FALSE;
  
  retval = g_exec_sync (NULL,
                        argv,
                        NULL,
                        G_EXEC_SEARCH_PATH,
                        NULL,
                        NULL,
                        standard_output,
                        standard_error,
                        exit_status,
                        error);
  g_strfreev (argv);

  return retval;
}

/**
 * g_exec_async_simple:
 * @command_line: a command line
 * @error: return location for errors
 * 
 * A simple version of g_exec_async() that parses a command line with
 * g_parse_argv() and passes it to g_exec_async(). Runs a command line
 * in the background. The %G_EXEC_SEARCH_PATH flag is enabled, other
 * flags are not. Note that %G_EXEC_SEARCH_PATH can have security
 * implications, so consider using g_exec_async() directly if
 * appropriate.
 * 
 * Return value: TRUE on success, FALSE if error is set.
 **/
gboolean
g_exec_async_simple (const gchar *command_line,
                     GError     **error)
{
  gboolean retval;
  gchar **argv = 0;

  g_return_val_if_fail (command_line != NULL, FALSE);

  if (!g_parse_argv (command_line,
                     NULL, &argv,
                     error))
    return FALSE;
  
  retval = g_exec_async (NULL,
                         argv,
                         NULL,
                         G_EXEC_SEARCH_PATH,
                         NULL,
                         NULL,
                         NULL,
                         error);
  g_strfreev (argv);

  return retval;
}

/* This is almost certainly a portability problem. */
static gint
exec_err_to_g_error (gint en)
{
  switch (en)
    {
    case EACCES:
      return G_EXEC_ERROR_ACCES;
      break;

    case EPERM:
      return G_EXEC_ERROR_PERM;
      break;

    case E2BIG:
      return G_EXEC_ERROR_2BIG;
      break;
        
    case ENOEXEC:
      return G_EXEC_ERROR_NOEXEC;
      break;

    case ENAMETOOLONG:
      return G_EXEC_ERROR_NAMETOOLONG;
      break;

    case ENOENT:
      return G_EXEC_ERROR_NOENT;
      break;

    case ENOMEM:
      return G_EXEC_ERROR_NOMEM;
      break;

    case ENOTDIR:
      return G_EXEC_ERROR_NOTDIR;
      break;

    case ELOOP:
      return G_EXEC_ERROR_LOOP;
      break;

#ifdef ETXTBUSY
    case ETXTBUSY:
      return G_EXEC_ERROR_TXTBUSY;
      break;
#endif
      
    case EIO:
      return G_EXEC_ERROR_IO;
      break;

    case ENFILE:
      return G_EXEC_ERROR_NFILE;
      break;

    case EMFILE:
      return G_EXEC_ERROR_MFILE;
      break;

    case EINVAL:
      return G_EXEC_ERROR_INVAL;
      break;

    case EISDIR:
      return G_EXEC_ERROR_ISDIR;
      break;

    case ELIBBAD:
      return G_EXEC_ERROR_LIBBAD;
      break;

    default:
      return G_EXEC_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,
         GExecChildSetupFunc   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);
    }

  if (envp)
    execve (argv[0], argv, envp);
  else if (search_path)
    execvp (argv[0], argv);
  else
    execv (argv[0], argv);

  /* 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_EXEC_ERROR,
                       G_EXEC_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,
                      GExecChildSetupFunc   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_EXEC_ERROR,
                   G_EXEC_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_EXEC_ERROR,
                           G_EXEC_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_EXEC_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_EXEC_ERROR,
                           G_EXEC_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_EXEC_ERROR,
                           G_EXEC_ERROR_FORK,
                           _("Failed to fork child process (%s)"),
                           g_strerror (buf[1]));
              break;
              
            default:
              g_set_error (error,
                           G_EXEC_ERROR,
                           G_EXEC_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_EXEC_ERROR,
                           G_EXEC_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_EXEC_ERROR,
                   G_EXEC_ERROR_FAILED,
                   _("Failed to create pipe for communicating with child process (%s)"),
                   g_strerror (errno));
      return FALSE;
    }
  else
    return TRUE;
}

/* GLIB - Library of useful routines for C programming
 * Copyright (C) 1995-1997  Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/*
 * Modified by the GLib Team and others 1997-2000.  See the AUTHORS
 * file for a list of people on the GLib Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
 * GLib at ftp://ftp.gtk.org/pub/gtk/. 
 */

#undef G_LOG_DOMAIN

#include <glib.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>


typedef struct _TestResult TestResult;

struct _TestResult
{
  gint argc;
  const gchar **argv;
};

static const gchar *
test_command_lines[] =
{
  /*  0 */ "foo bar",
  /*  1 */ "foo 'bar'",
  /*  2 */ "foo \"bar\"",
  /*  3 */ "foo '' 'bar'",
  /*  4 */ "foo \"bar\"'baz'blah'foo'\\''blah'\"boo\"",
  /*  5 */ "foo \t \tblah\tfoo\t\tbar  baz",
  /*  6 */ "foo '    spaces more spaces lots of     spaces in this   '  \t",
  /*  7 */ "foo \\\nbar",
  /*  8 */ "foo '' ''",
  /*  9 */ "foo \\\" la la la",
  /* 10 */ "foo \\ foo woo woo\\ ",
  /* 11 */ "foo \"yada yada \\$\\\"\"",
  NULL
};

static const gchar *result0[] = { "foo", "bar", NULL };
static const gchar *result1[] = { "foo", "bar", NULL };
static const gchar *result2[] = { "foo", "bar", NULL };
static const gchar *result3[] = { "foo", "", "bar", NULL };
static const gchar *result4[] = { "foo", "barbazblahfoo'blahboo", NULL };
static const gchar *result5[] = { "foo", "blah", "foo", "bar", "baz", NULL };
static const gchar *result6[] = { "foo", "    spaces more spaces lots of     spaces in this   ", NULL };
static const gchar *result7[] = { "foo", "bar", NULL };
static const gchar *result8[] = { "foo", "", "", NULL };
static const gchar *result9[] = { "foo", "\"", "la", "la", "la", NULL };
static const gchar *result10[] = { "foo", " foo", "woo", "woo ", NULL };
static const gchar *result11[] = { "foo", "yada yada $\"", NULL };

static const TestResult
correct_results[] =
{
  { G_N_ELEMENTS (result0) - 1, result0 },
  { G_N_ELEMENTS (result1) - 1, result1 },
  { G_N_ELEMENTS (result2) - 1, result2 },
  { G_N_ELEMENTS (result3) - 1, result3 },
  { G_N_ELEMENTS (result4) - 1, result4 },
  { G_N_ELEMENTS (result5) - 1, result5 },
  { G_N_ELEMENTS (result6) - 1, result6 },
  { G_N_ELEMENTS (result7) - 1, result7 },
  { G_N_ELEMENTS (result8) - 1, result8 },
  { G_N_ELEMENTS (result9) - 1, result9 },
  { G_N_ELEMENTS (result10) - 1, result10 },
  { G_N_ELEMENTS (result11) - 1, result11 }
};

static void
print_test (const gchar *cmdline, gint argc, gchar **argv,
            const TestResult *result)
{
  gint i;
  
  printf ("\nCommand line was: '%s'\n", cmdline);

  printf ("Expected result (%d args):\n", result->argc);
  
  i = 0;
  while (result->argv[i])
    {
      printf (" %3d '%s'\n", i, result->argv[i]);

      ++i;
    }  

  printf ("Actual result (%d args):\n", argc);
  
  i = 0;
  while (argv[i])
    {
      printf (" %3d '%s'\n", i, argv[i]);

      ++i;
    }
}

static void
do_argv_test (const gchar *cmdline, const TestResult *result)
{
  gint argc;
  gchar **argv;
  GError *err;
  gint i;

  err = NULL;
  if (!g_parse_argv (cmdline, &argc, &argv, &err))
    {
      fprintf (stderr, "Error parsing command line that should work fine: %s\n",
               err->message);
      
      exit (1);
    }
  
  if (argc != result->argc)
    {
      fprintf (stderr, "Expected and actual argc don't match\n");
      print_test (cmdline, argc, argv, result);
      exit (1);
    }

  i = 0;
  while (argv[i])
    {
      if (strcmp (argv[i], result->argv[i]) != 0)
        {
          fprintf (stderr, "Expected and actual arg %d do not match\n", i);
          print_test (cmdline, argc, argv, result);
          exit (1);
        }
      
      ++i;
    }

  if (argv[i] != NULL)
    {
      fprintf (stderr, "argv didn't get NULL-terminated\n");
      exit (1);
    }
}

static void
run_tests (void)
{
  GError *err;
  gchar *output = NULL;
  gint i;
  
  printf ("The following errors are supposed to occur:\n");

  err = NULL;
  if (!g_exec_sync_simple ("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_exec_async_simple ("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_exec_sync_simple ("/bin/sh -c 'echo hello'",
                           &output, NULL, NULL,
                           &err))
    {
      fprintf (stderr, "Error: %s\n", err->message);
      g_error_free (err);
      exit (1);
    }
  else
    {
      g_assert (output != NULL);
      
      if (strcmp (output, "hello\n") != 0)
        {
          printf ("output was '%s', should have been 'hello'\n",
                  output);

          exit (1);
        }

      g_free (output);
    }

  i = 0;
  while (test_command_lines[i])
    {
      printf ("g_parse_argv() test %d - ", i);
      do_argv_test (test_command_lines[i], &correct_results[i]);
      printf ("ok (%s)\n", test_command_lines[i]);
      
      ++i;
    }
}

int
main (int   argc,
      char *argv[])
{
  run_tests ();
  
  return 0;
}





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