Re: g_exec_* implementation



Owen Taylor <otaylor@redhat.com> writes: 
>  - I think we might need to have the environment as an (optional)
>    parameter. If you are writing a security-concious program
>    that needs to exec a child with a clean environment, you
>    can't do that via GExecChildSetupFunc, since the only
>    way to get a clean environment (as far as I know) is to
>    use execve.
>

Added. To avoid adding two args to the functions (env_count,
env_vector) I removed argc, so both argv and envp have to be NULL
terminated. 
 
>  gboolean g_exec_sync_with_callback (const gchar         *working_directory,
>                                      gint                argc,
>                                      gchar             **argv,
>                                      GExecFlags          flags,
>                                      GExecChildSetupFunc child_setup,
>                                      gpointer            user_data,
>                                      GExecCallback       callback,
>                                      GError            **error);
>

Seems very valuable to somehow imply in the function name that 
this will use the main loop.

Maybe even put it with the other main loop sources, i.e. 
make it a function analagous to g_idle_add()?
 
Or call it g_exec_nonblocking() maybe, I don't know. (It isn't really
synchronous is it? so _sync_ doesn't seem right to me.)
 
>  This shouldn't be too bad to implement on top of g_exec_async().
>

I'm not actually sure how to find out when the child exits without
calling blocking in waitpid() (a custom main loop source that calls
waitpid() with WNOHANG?). Don't want to use SIGCHLD I don't think.
 
>  - It's not completely clear to me that stdin == NULL or stdin
>    as a pipe is the complete set of interesting options. 
> 
>    Command line programs do sometimes want to simply let stdin
>    be shared between them and their subprocesses, to let the
>    subprocess do simple querying of the user. (More sophisticated 
>    programs will probably use the tty interface.)
> 
>    Should there be another flag for that?
>

I added G_EXEC_CHILD_INHERITS_STDIN that does this.
 
> child_pid should be at the end since it is an out parameter.
> (Same for g_exec_async_with_pipes.)
>

Moved.
 
> > #define _(x) x
> 
> Hmm, I guess we should go ahead and add gettext support to glib.
> (With GError, we are producing user visible errors now.)
> 

Yep.

> >   /* Assuming UTF8 is already validated */
> 
> Is the intent here to actually call g_utf8_validate, or do
> we simply fall over if not?
>    

The intent is g_return_if_fail (g_utf8_is_valid (command_line)), but
that seems too expensive if we aren't going to use G_DISABLE_CHECKS,
so it isn't there. Not sure what the right thing to do is.

> >           /* 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);
> 
> This results in ' foo bar' giving '' 'foo' 'bar'.
>

I don't think it does (this code only gets called outside of quotes,
right?)
 
> Also, you don't get 'foo ""' right (should be 'foo' '')
> 

Ugh, poptParseArgvString() screws this up too. I'm not sure I
understand what rule results in that happening, so I'll look into it.
Probably means rewriting the whole function.

I'll fix the issue with argstr->len > 0 then also ('' should become an
empty arg if it occurs at the end of a string).

> Since you don't otherwise handle '\' outside of quotes, I 
> don't think you should make this check.
>

Removed.
 
> You are missing g_string_free (argstr, TRUE) here (should move
> cleanup to a 'goto error:' type cleanup block.
> 

Done.

> I think it's generally clearer to write !(flags & G_EXEC_DO_NOT_REAP_CHILD)
> since it is a check for a condition _not_ being true.
>

Changed.
 
> We have g_strerror(), so you should probably use it. (I don't
> know to what platforms strerror() wasn't portable, but there
> apparently were some.)
>

Done.
 
> Using waitpid() and expecting to reliably have catch your
> child exiting, unfortunately is not a completely safe assumption
> in a library. 
> 
> There is not a whole lot you can do if the program is handling
> SIGCHLD itself, but your probably should at least handle
> things more gracefully if you get ECHILD. When exit_status
> is NULL, I think you can treat this as not an error at all.
> 
> When exit_status is non-NULL, then I'm not sure if it is better
> to pretend that it succeeded, pretend it succeeded but
> put a g_warning ("Child disappeared"), or to return a
> GError.
>

I'm going to say:

 - pretend that it succeeded doesn't look possible, since there 
   isn't anything sane to set *exit_status to (that I know of,
   there wouldn't be a portable value to assign to it)
 - returning a GError doesn't make sense since if the program 
   has SIG_IGN on SIGCHLD, they will get the GError 100% of the 
   time, right? So it's just a programming bug to call exec_sync() 
   in this case.

So I added:

       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. */
            }
        }

For the call to waitpid() on the intermediate child in the async exec, 
I just pretend waitpid() succeeded if I get ECHILD.
 
> While I have nothing against assertions, I think this is
> going a bit far on the clumsy side for something that
> is quite easy to verify. 
> 
>  if (standard_output);
>    *standard_output = g_string_free (outstr->str, FALSE);
> 
> is a lot clearer.
> 

Changed.

> I think it might be guaranteed to be safe to do:
> 
> #ifdef ETXTBUSY
>      case ETXTBUSY:
>        return G_EXEC_ERROR_TXTBUSY;
>        break;
> #endif
>

OK, doing that (we'll see if it's safe).
        
> > static void
> > set_cloexec (gint fd)
> > {
> >   fcntl (fd, F_SETFD, FD_CLOEXEC);
> > }
> 
> As far as I know, FD_CLOEXEC is the only flag that ever can be present
> here, and quite a few programs would break if that weren't the
> case. But if you were _really_ paranoid, you might want to get the
> flags first and OR in FD_CLOEXEC.
> 
> Most likely not worth the extra syscall.
>

I think it's safe (I don't really know, but this is what's used in
gnome_exec_*, and we're about to close the fd anyway...)
 
> Actually, unix98 allows EINTR. (I have very little idea why.)
> You probably should handle it.
>

Changed to:

static gint
sane_dup2 (gint fd1, gint fd2)
{
  gint ret;

 retry:
  ret = dup2 (fd1, fd2);
  if (ret < 0 && errno == EINTR)
    goto retry;

  return ret;
}
 
> Actually, I think it is highly possible. Maybe you mean
> bytes > sizeof(gint)*2.
>

If bytes == sizeof(gint)*2 then we should have gotten EOF (chunk == 0)
and bailed out of the loop already. If we got bytes > sizeof(gint)*2
then there was unexpected data on the pipe, which most likely means
the code is broken. Either way we should quit the loop and stop
reading data.

This whole check is just paranoia against the code being broken, I
don't think it should actually be reached...
 
> [...]
>            
> >           if (bytes >= sizeof(gint))
> >             break; /* give up, who knows what happened, should not be
> >                     * possible.
> >                     */
> 
> Same here. I think you need a read_int function here to cut
> down on code duplication.
>

Done.
 
> Since you don't set the file descriptors to -1 on closing
> them, you are creating problems here in a threaded environment.
> (Another thread could get the same file descriptor and be using 
> it.) 
> 
> close (-1), as you have here already, strikes me as a little
> slapdash, but it should be safe.
> 

Now setting all fd's to -1 after close.

So the outstanding issues are:

 - Think through and implement the main-loop-using version of
   g_exec_sync()

 - Reimplement g_parse_argv() to properly handle nested quotes
   and a couple other issues

Appending the current implementation.

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

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

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 = NULL;
  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"));
                  goto failed;
                }
              
              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 '\\':

              /* 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"));

      goto failed;
    }
  
  if (argcp)
    *argcp = argc;

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

  return TRUE;

 failed:

  g_strfreev (argv);

  if (argstr)
    g_string_free (argstr, TRUE);

  return FALSE;
}

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

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

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

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

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





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