g_exec_* implementation



Hi,

It's time to play "find the bugs"! This is pretty complicated, and I'm
hardly a god of UNIX system programming, so - if you see anything,
please say so. I even cc'd Alan in case he feels like checking for
security bugs. ;-)

Also of course, comment on the API I have so far. I took the "this is
just a UNIX thing" approach for now; I think a cross-platform
abstraction would be useful also, but I'm not sure how to write that,
and probably it wouldn't be as powerful anyway so the UNIX one would
still be useful. Perhaps call the cross-platform one g_run_process_*
and have a GProcess object.

Since my last post, I fixed some obviously broken things, 
added GExecFlags which enable some useful features (but passing 0 for
the flags field results in the most secure/safe/sane defaults), 
added ChildSetupFunc for doing things like changing the child's
environment, and added _simple() variants. Also of course I
implemented the thing.

I think these are pretty powerful, and should enable people to get
fork/exec really correct, secure, and safe with nice error reporting.

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

GQuark g_exec_error_quark (void);

gboolean g_parse_argv (const gchar           *command_line,
                       gint                  *argc,
                       gchar               ***argv,
                       GError               **error);
gboolean g_exec_async (const gchar           *working_directory,
                       gint                   argc,
                       gchar                **argv,
                       GExecFlags             flags,
                       gint                  *child_pid,
                       GExecChildSetupFunc    child_setup,
                       gpointer               user_data,
                       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,
                                  gint                  argc,
                                  gchar               **argv,
                                  GExecFlags            flags,
                                  gint                 *child_pid,
                                  GExecChildSetupFunc   child_setup,
                                  gpointer              user_data,
                                  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,
                              gint                  argc,
                              gchar               **argv,
                              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,
                                      gint                  argc,
                                      gchar               **argv,
                                      gboolean              close_descriptors,
                                      gboolean              search_path,
                                      gboolean              stdout_to_null,
                                      gboolean              stderr_to_null,
                                      gint                 *child_pid,
                                      GExecChildSetupFunc   child_setup,
                                      gpointer              user_data,
                                      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;
}

static inline void
advance_char (const gchar **c,
              const gchar **cend)
{
  *c = *cend;
  if (*c)
    *cend = g_utf8_next_char (*c);
  else
    *cend = *c;
}

static inline void
append_char (GString     *str,
             const gchar *c,
             const gchar *cend)
{
  g_string_append_len (str, c, cend - c);
}

gboolean
g_parse_argv (const gchar *command_line,
              gint        *argcp,
              gchar     ***argvp,
              GError     **error)
{
  /* Code based on poptParseArgvString() from libpopt */
  const gchar *c;
  const gchar *cend;
  gunichar quote = '\0';
  GString *argstr;
  gint argc = 0;
  gchar **argv = NULL;

  g_return_val_if_fail (command_line != NULL, FALSE);
  
  /* Assuming UTF8 is already validated */
  
  argstr = g_string_new ("");
  
  c = command_line;
  cend = g_utf8_next_char (c);
    
  while (*c)
    {
      if (quote == *c)
        {
          /* closing quote */
          quote = '\0';
        }
      else if (quote != '\0')
        {
          /* Within quotes, we just append everything to the
           * current arg, except that escaped quotes need the '\'
           * part filtered out.
           */
          
          if (*c == '\\')
            {              
              if (*cend == '\0')
                {
                  g_set_error (error,
                               G_EXEC_ERROR,
                               G_EXEC_ERROR_PARSE,
                               _("'\' character not allowed at the end of a command line"));
                  g_strfreev (argv);
                  g_string_free (argstr, TRUE);
                  return FALSE;
                }
              
              if (*cend == quote) /* skip past the \ escape */
                advance_char (&c, &cend);
            }
          
          append_char (argstr, c, cend);
        }
      else if (g_unichar_isspace (g_utf8_get_char (c)))
        {
          /* End of an arg, append it to argv,
           */
          
          argv = g_realloc (argv, (argc + 2) * sizeof(gchar*));
          argv[argc] = g_strdup (argstr->str);
          g_string_truncate (argstr, 0);
          ++argc;
          argv[argc] = NULL; /* constantly keep it valid
                              * for g_strfreev()
                              */
          
          /* Skip contiguous spaces */
          while (*cend && g_unichar_isspace (g_utf8_get_char (cend)))
            advance_char (&c, &cend);
        }
      else
        {
          /* Check for quote */
          switch (*c)
            {
            case '"':
            case '\'':
              quote = *c;
              break;

            case '\\':
              if (*cend == '\0')
                {
                  g_set_error (error,
                               G_EXEC_ERROR,
                               G_EXEC_ERROR_PARSE,
                               _("'\' character not allowed at the end of a command line"));
                  g_strfreev (argv);
                  g_string_free (argstr, TRUE);
                  return FALSE;
                }

              /* FALL THRU */
              
            default:
              append_char (argstr, c, cend);
              break;
            }
        }

      advance_char (&c, &cend);
    }

  /* If we have a nonempty arg, append it */
  if (argstr->len > 0)
    {
      argv = g_realloc (argv, (argc + 2) * sizeof(gchar*));
      argv[argc] = argstr->str;
      g_string_free (argstr, FALSE);
      ++argc;
      argv[argc] = NULL;
    }
  else
    g_string_free (argstr, TRUE);

  if (quote != '\0')
    {
      g_set_error (error,
                   G_EXEC_ERROR,
                   G_EXEC_ERROR_PARSE,
                   _("Unclosed quote in command line string"));

      g_strfreev (argv);
      
      return FALSE;
    }
  
  if (argcp)
    *argcp = argc;

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

  return TRUE;
}

gboolean
g_exec_async (const gchar          *working_directory,
              gint                  argc,
              gchar               **argv,
              GExecFlags            flags,
              gint                 *child_pid,
              GExecChildSetupFunc   child_setup,
              gpointer              user_data,
              GError              **error)
{
  g_return_val_if_fail (argc > 0, FALSE);
  g_return_val_if_fail (argv != NULL, FALSE);
  
  return g_exec_async_with_pipes (working_directory,
                                  argc, argv,
                                  flags,
                                  child_pid,
                                  child_setup,
                                  user_data,
                                  NULL, NULL, NULL,
                                  error);
}

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)"),
                   strerror (errno));
      
      return READ_FAILED;
    }
  else
    return READ_OK;
}

gboolean
g_exec_sync (const gchar          *working_directory,
             gint                  argc,
             gchar               **argv,
             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 (argc > 0, FALSE);
  g_return_val_if_fail (argv != NULL, FALSE);
  g_return_val_if_fail ((flags & G_EXEC_DO_NOT_REAP_CHILD) == 0, FALSE);
  g_return_val_if_fail (standard_output == NULL ||
                        (flags & G_EXEC_STDOUT_TO_DEV_NULL) == 0, FALSE);
  g_return_val_if_fail (standard_error == NULL ||
                        (flags & G_EXEC_STDERR_TO_DEV_NULL) == 0, 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,
                             argc,
                             argv,
                             (flags & G_EXEC_LEAVE_DESCRIPTORS_OPEN) == 0,
                             (flags & G_EXEC_SEARCH_PATH) != 0,
                             (flags & G_EXEC_STDOUT_TO_DEV_NULL) != 0,
                             (flags & G_EXEC_STDERR_TO_DEV_NULL) != 0,
                             &pid,
                             child_setup,
                             user_data,
                             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)"),
                       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 (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 (errpipe);
              errpipe = -1;
              break;
            default:
              break;
            }

          if (failed)
            break;
        }
    }

  /* These should only be open still if we had an error.  */
  
  if (outpipe >= 0)
    close (outpipe);
  if (errpipe >= 0)
    close (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 (!failed) /* avoid error pileups */
            {
              failed = TRUE;
                  
              g_set_error (error,
                           G_EXEC_ERROR,
                           G_EXEC_ERROR_READ,
                           _("Unexpected error in waitpid() (%s)"),
                           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)        
        {
          g_assert (outstr);
          *standard_output = outstr->str;
          g_string_free (outstr, FALSE);
        }
      else
        g_assert (outstr == NULL); /* no leaks! */

      if (standard_error)
        {
          g_assert (errstr);
          *standard_error = errstr->str;
          g_string_free (errstr, FALSE);
        }
      else
        g_assert (errstr == NULL); /* no leaks! */

      return TRUE;
    }
}

gboolean
g_exec_async_with_pipes (const gchar          *working_directory,
                         gint                  argc,
                         gchar               **argv,
                         GExecFlags            flags,
                         gint                 *child_pid,
                         GExecChildSetupFunc   child_setup,
                         gpointer              user_data,
                         gint                 *standard_input,
                         gint                 *standard_output,
                         gint                 *standard_error,
                         GError              **error)
{
  g_return_val_if_fail (argc > 0, FALSE);
  g_return_val_if_fail (argv != NULL, FALSE);
  g_return_val_if_fail (standard_output == NULL ||
                        (flags & G_EXEC_STDOUT_TO_DEV_NULL) == 0, FALSE);
  g_return_val_if_fail (standard_error == NULL ||
                        (flags & G_EXEC_STDERR_TO_DEV_NULL) == 0, FALSE);
  
  return fork_exec_with_pipes ((flags & G_EXEC_DO_NOT_REAP_CHILD) == 0,
                               working_directory,
                               argc,
                               argv,
                               (flags & G_EXEC_LEAVE_DESCRIPTORS_OPEN) == 0,
                               (flags & G_EXEC_SEARCH_PATH) != 0,
                               (flags & G_EXEC_STDOUT_TO_DEV_NULL) != 0,
                               (flags & G_EXEC_STDERR_TO_DEV_NULL) != 0,
                               child_pid,
                               child_setup,
                               user_data,
                               standard_input,
                               standard_output,
                               standard_error,
                               error);
}

gboolean
g_exec_sync_simple (const gchar  *command_line,
                    gchar       **standard_output,
                    gchar       **standard_error,
                    gint         *exit_status,
                    GError      **error)
{
  gboolean retval;
  gint argc = 0;
  gchar **argv = 0;

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

  return retval;
}

gboolean
g_exec_async_simple (const gchar *command_line,
                     GError     **error)
{
  gboolean retval;
  gint argc = 0;
  gchar **argv = 0;

  g_return_val_if_fail (command_line != NULL, FALSE);

  if (!g_parse_argv (command_line,
                     &argc, &argv,
                     error))
    return FALSE;
  
  retval = g_exec_async (NULL,
                         argc,
                         argv,
                         0,
                         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;

#if 0
    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);
}

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,
         gint                  argc,
         gchar               **argv,
         gboolean              close_descriptors,
         gboolean              search_path,
         gboolean              stdout_to_null,
         gboolean              stderr_to_null,
         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 (dup2 (stdin_fd, 0) < 0)
        write_err_and_exit (child_err_report_fd,
                            CHILD_DUP2_FAILED);

      /* ignore this if it doesn't work */
      close (stdin_fd);
    }
  else
    {
      /* Keep process from blocking on a read of stdin */
      gint read_null = open ("/dev/null", O_RDONLY);
      dup2 (read_null, 0);
      close (read_null);
    }

  if (stdout_fd >= 0)
    {
      /* dup2 can't actually fail here I don't think */
          
      if (dup2 (stdout_fd, 1) < 0)
        write_err_and_exit (child_err_report_fd,
                            CHILD_DUP2_FAILED);

      /* ignore this if it doesn't work */
      close (stdout_fd);
    }
  else if (stdout_to_null)
    {
      gint write_null = open ("/dev/null", O_WRONLY);
      dup2 (write_null, 1);
      close (write_null);
    }

  if (stderr_fd >= 0)
    {
      /* dup2 can't actually fail here I don't think */
          
      if (dup2 (stderr_fd, 2) < 0)
        write_err_and_exit (child_err_report_fd,
                            CHILD_DUP2_FAILED);

      /* ignore this if it doesn't work */
      close (stderr_fd);
    }
  else if (stderr_to_null)
    {
      gint write_null = open ("/dev/null", O_WRONLY);
      dup2 (write_null, 2);
      close (write_null);
    }
  
  /* Call user function just before we exec */
  if (child_setup)
    {
      (* child_setup) (user_data);
    }

  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
fork_exec_with_pipes (gboolean              intermediate_child,
                      const gchar          *working_directory,
                      gint                  argc,
                      gchar               **argv,
                      gboolean              close_descriptors,
                      gboolean              search_path,
                      gboolean              stdout_to_null,
                      gboolean              stderr_to_null,
                      gint                 *child_pid,
                      GExecChildSetupFunc   child_setup,
                      gpointer              user_data,
                      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)"),
                   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 (child_err_report_pipe[0]);
      close (child_pid_report_pipe[0]);
      close (stdin_pipe[1]);
      close (stdout_pipe[0]);
      close (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,
                       argc,
                       argv,
                       close_descriptors,
                       search_path,
                       stdout_to_null,
                       stderr_to_null,
                       child_setup,
                       user_data);
            }
          else
            {
              write (child_pid_report_pipe[1], &grandchild_pid, sizeof(grandchild_pid));
              close (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,
                   argc,
                   argv,
                   close_descriptors,
                   search_path,
                   stdout_to_null,
                   stderr_to_null,
                   child_setup,
                   user_data);
        }
    }
  else
    {
      /* Parent */
      
      gint buf[2];
      gint bytes = 0;

      /* Close the uncared-about ends of the pipes */
      close (child_err_report_pipe[1]);
      close (child_pid_report_pipe[1]);
      close (stdin_pipe[0]);
      close (stdout_pipe[1]);
      close (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
                g_warning ("waitpid() should not fail in %s", __FUNCTION__);
            }
        }
      
      /* Block until we get an error or EOF due
       * to the exec() succeeding. Read a max of
       * 128 bytes.
       */
      while (TRUE)
        {
          gint chunk;

          if (bytes >= sizeof(gint)*2)
            break; /* give up, who knows what happened, should not be
                    * possible.
                    */
          
        again:
          chunk = read (child_err_report_pipe[0],
                        ((gchar*)buf) + bytes,
                        sizeof(gint)*2 - 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)"),
                           strerror (errno));

              goto cleanup_and_fail;
            }
          else if (chunk == 0)
            break; /* EOF */
          else
            {
              g_assert (chunk > 0);
              
              bytes += chunk;
            }
        }

      if (bytes >= sizeof(gint)*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,
                           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)"),
                           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)"),
                           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)"),
                           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. */
      while (intermediate_child && TRUE)
        {
          gint chunk;

          if (bytes >= sizeof(gint))
            break; /* give up, who knows what happened, should not be
                    * possible.
                    */
          
        again2:
          chunk = read (child_pid_report_pipe[0],
                        ((gchar*)buf) + bytes,
                        sizeof(gint) - bytes);
          if (chunk < 0 && errno == EINTR)
            goto again2;
          
          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 pid pipe (%s)"),
                           strerror (errno));

              goto cleanup_and_fail;
            }
          else if (chunk == 0)
            break; /* EOF */
          else
            {
              g_assert (chunk > 0);
              
              bytes += chunk;
            }
        }

      if (intermediate_child)
        {
          if (bytes < sizeof(gint))
            {
              g_set_error (error,
                           G_EXEC_ERROR,
                           G_EXEC_ERROR_FAILED,
                           _("Failed to read enough data from child pid pipe (%s)"),
                           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 (child_err_report_pipe[0]);
  close (child_err_report_pipe[1]);
  close (child_pid_report_pipe[0]);
  close (child_pid_report_pipe[1]);
  close (stdin_pipe[0]);
  close (stdin_pipe[1]);
  close (stdout_pipe[0]);
  close (stdout_pipe[1]);
  close (stderr_pipe[0]);
  close (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)"),
                   strerror (errno));
      return FALSE;
    }
  else
    return TRUE;
}






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