bug-gnulib
[Top][All Lists]
Advanced

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

[Bug-gnulib] addition: execute.h, execute.c


From: Bruno Haible
Subject: [Bug-gnulib] addition: execute.h, execute.c
Date: Sat, 24 Jan 2004 19:16:29 +0100
User-agent: KMail/1.5

Hi,

Continuing in the series of module for creating subprocesses, here comes
a very basic module: Executing a program. It's in use within gettext
for ca. 2 years.

Any objections or comments?

Bruno

======================== modules/execute ========================
Description:
Creation of autonomous subprocesses.

Files:
lib/execute.h
lib/execute.c
lib/w32spawn.h
m4/execute.m4

Depends-on:
error
exit
fatal-signal
wait-process
gettext
stdbool
strpbrk

configure.ac:
gl_EXECUTE

Makefile.am:
lib_SOURCES += execute.h execute.c w32spawn.h

Include:
"execute.h"

Maintainer:
Bruno Haible

======================== lib/execute.h ========================
/* Creation of autonomous subprocesses.
   Copyright (C) 2001-2003 Free Software Foundation, Inc.
   Written by Bruno Haible <address@hidden>, 2001.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */

#ifndef _EXECUTE_H
#define _EXECUTE_H

#include <stdbool.h>

/* Execute a command, optionally redirecting any of the three standard file
   descriptors to /dev/null.  Return its exit code.
   If it didn't terminate correctly, exit if exit_on_error is true, otherwise
   return 127.
   If ignore_sigpipe is true, consider a subprocess termination due to SIGPIPE
   as equivalent to a success.  This is suitable for processes whose only
   purpose is to write to standard output.
   If slave_process is true, the child process will be terminated when its
   creator receives a catchable fatal signal.
   It is recommended that no signal is blocked or ignored while execute()
   is called.  See pipe.h for the reason.  */
extern int execute (const char *progname,
                    const char *prog_path, char **prog_argv,
                    bool ignore_sigpipe,
                    bool null_stdin, bool null_stdout, bool null_stderr,
                    bool slave_process, bool exit_on_error);

#endif /* _EXECUTE_H */
======================== lib/execute.c ========================
/* Creation of autonomous subprocesses.
   Copyright (C) 2001-2003 Free Software Foundation, Inc.
   Written by Bruno Haible <address@hidden>, 2001.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */


#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

/* Specification.  */
#include "execute.h"

#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdlib.h>
#include <signal.h>

#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif

#include "error.h"
#include "exit.h"
#include "fatal-signal.h"
#include "wait-process.h"
#include "gettext.h"

#define _(str) gettext (str)

#if defined _MSC_VER || defined __MINGW32__

/* Native Woe32 API.  */
# include <process.h>
# include "w32spawn.h"

#else

/* Unix API.  */
# ifdef HAVE_POSIX_SPAWN
#  include <spawn.h>
# else
#  ifdef HAVE_VFORK_H
#   include <vfork.h>
#  endif
# endif

#endif

#ifndef STDIN_FILENO
# define STDIN_FILENO 0
#endif
#ifndef STDOUT_FILENO
# define STDOUT_FILENO 1
#endif
#ifndef STDERR_FILENO
# define STDERR_FILENO 2
#endif


#ifdef EINTR

/* EINTR handling for close(), open().
   These functions can return -1/EINTR even though we don't have any
   signal handlers set up, namely when we get interrupted via SIGSTOP.  */

static inline int
nonintr_close (int fd)
{
  int retval;

  do
    retval = close (fd);
  while (retval < 0 && errno == EINTR);

  return retval;
}
#define close nonintr_close

static inline int
nonintr_open (const char *pathname, int oflag, mode_t mode)
{
  int retval;

  do
    retval = open (pathname, oflag, mode);
  while (retval < 0 && errno == EINTR);

  return retval;
}
#undef open /* avoid warning on VMS */
#define open nonintr_open

#endif


/* Execute a command, optionally redirecting any of the three standard file
   descriptors to /dev/null.  Return its exit code.
   If it didn't terminate correctly, exit if exit_on_error is true, otherwise
   return 127.
   If slave_process is true, the child process will be terminated when its
   creator receives a catchable fatal signal.  */
int
execute (const char *progname,
         const char *prog_path, char **prog_argv,
         bool ignore_sigpipe,
         bool null_stdin, bool null_stdout, bool null_stderr,
         bool slave_process, bool exit_on_error)
{
#if defined _MSC_VER || defined __MINGW32__

  /* Native Woe32 API.  */
  int orig_stdin;
  int orig_stdout;
  int orig_stderr;
  int exitcode;
  int nullinfd;
  int nulloutfd;

  prog_argv = prepare_spawn (prog_argv);

  /* Save standard file handles of parent process.  */
  if (null_stdin)
    orig_stdin = dup_noinherit (STDIN_FILENO);
  if (null_stdout)
    orig_stdout = dup_noinherit (STDOUT_FILENO);
  if (null_stderr)
    orig_stderr = dup_noinherit (STDERR_FILENO);
  exitcode = -1;

  /* Create standard file handles of child process.  */
  nullinfd = -1;
  nulloutfd = -1;
  if ((!null_stdin
       || ((nullinfd = open ("NUL", O_RDONLY, 0)) >= 0
           && (nullinfd == STDIN_FILENO
               || (dup2 (nullinfd, STDIN_FILENO) >= 0
                   && close (nullinfd) >= 0))))
      && (!(null_stdout || null_stderr)
          || ((nulloutfd = open ("NUL", O_RDWR, 0)) >= 0
              && (!null_stdout
                  || nulloutfd == STDOUT_FILENO
                  || dup2 (nulloutfd, STDOUT_FILENO) >= 0)
              && (!null_stderr
                  || nulloutfd == STDERR_FILENO
                  || dup2 (nulloutfd, STDERR_FILENO) >= 0)
              && ((null_stdout && nulloutfd == STDOUT_FILENO)
                  || (null_stderr && nulloutfd == STDERR_FILENO)
                  || close (nulloutfd) >= 0))))
    exitcode = spawnvp (P_WAIT, prog_path, prog_argv);
  if (nulloutfd >= 0)
    close (nulloutfd);
  if (nullinfd >= 0)
    close (nullinfd);

  /* Restore standard file handles of parent process.  */
  if (null_stderr)
    dup2 (orig_stderr, STDERR_FILENO), close (orig_stderr);
  if (null_stdout)
    dup2 (orig_stdout, STDOUT_FILENO), close (orig_stdout);
  if (null_stdin)
    dup2 (orig_stdin, STDIN_FILENO), close (orig_stdin);

  if (exitcode == -1)
    {
      if (exit_on_error || !null_stderr)
        error (exit_on_error ? EXIT_FAILURE : 0, errno,
               _("%s subprocess failed"), progname);
      return 127;
    }

  return exitcode;

#else

  /* Unix API.  */
  /* Note about 127: Some errors during posix_spawnp() cause the function
     posix_spawnp() to return an error code; some other errors cause the
     subprocess to exit with return code 127.  It is implementation
     dependent which error is reported which way.  We treat both cases as
     equivalent.  */
#if HAVE_POSIX_SPAWN
  sigset_t blocked_signals;
  posix_spawn_file_actions_t actions;
  bool actions_allocated;
  posix_spawnattr_t attrs;
  bool attrs_allocated;
  int err;
  pid_t child;
#else
  int child;
#endif

#if HAVE_POSIX_SPAWN
  if (slave_process)
    {
      sigprocmask (SIG_SETMASK, NULL, &blocked_signals);
      block_fatal_signals ();
    }
  actions_allocated = false;
  attrs_allocated = false;
  if ((err = posix_spawn_file_actions_init (&actions)) != 0
      || (actions_allocated = true,
          (null_stdin
            && (err = posix_spawn_file_actions_addopen (&actions,
                                                        STDIN_FILENO,
                                                        "/dev/null", O_RDONLY,
                                                        0))
               != 0)
          || (null_stdout
              && (err = posix_spawn_file_actions_addopen (&actions,
                                                          STDOUT_FILENO,
                                                          "/dev/null", O_RDWR,
                                                          0))
                 != 0)
          || (null_stderr
              && (err = posix_spawn_file_actions_addopen (&actions,
                                                          STDERR_FILENO,
                                                          "/dev/null", O_RDWR,
                                                          0))
                 != 0)
          || (slave_process
              && ((err = posix_spawnattr_init (&attrs)) != 0
                  || (attrs_allocated = true,
                      (err = posix_spawnattr_setsigmask (&attrs,
                                                         &blocked_signals))
                      != 0
                      || (err = posix_spawnattr_setflags (&attrs,
                                                        POSIX_SPAWN_SETSIGMASK))
                         != 0)))
          || (err = posix_spawnp (&child, prog_path, &actions,
                                  attrs_allocated ? &attrs : NULL, prog_argv,
                                  environ))
             != 0))
    {
      if (actions_allocated)
        posix_spawn_file_actions_destroy (&actions);
      if (attrs_allocated)
        posix_spawnattr_destroy (&attrs);
      if (slave_process)
        unblock_fatal_signals ();
      if (exit_on_error || !null_stderr)
        error (exit_on_error ? EXIT_FAILURE : 0, err,
               _("%s subprocess failed"), progname);
      return 127;
    }
  posix_spawn_file_actions_destroy (&actions);
  if (attrs_allocated)
    posix_spawnattr_destroy (&attrs);
#else
  if (slave_process)
    block_fatal_signals ();
  /* Use vfork() instead of fork() for efficiency.  */
  if ((child = vfork ()) == 0)
    {
      /* Child process code.  */
      int nullinfd;
      int nulloutfd;

      if ((!null_stdin
           || ((nullinfd = open ("/dev/null", O_RDONLY, 0)) >= 0
               && (nullinfd == STDIN_FILENO
                   || (dup2 (nullinfd, STDIN_FILENO) >= 0
                       && close (nullinfd) >= 0))))
          && (!(null_stdout || null_stderr)
              || ((nulloutfd = open ("/dev/null", O_RDWR, 0)) >= 0
                  && (!null_stdout
                      || nulloutfd == STDOUT_FILENO
                      || dup2 (nulloutfd, STDOUT_FILENO) >= 0)
                  && (!null_stderr
                      || nulloutfd == STDERR_FILENO
                      || dup2 (nulloutfd, STDERR_FILENO) >= 0)
                  && ((null_stdout && nulloutfd == STDOUT_FILENO)
                      || (null_stderr && nulloutfd == STDERR_FILENO)
                      || close (nulloutfd) >= 0)))
          && (!slave_process || (unblock_fatal_signals (), true)))
        execvp (prog_path, prog_argv);
      _exit (127);
    }
  if (child == -1)
    {
      if (slave_process)
        unblock_fatal_signals ();
      if (exit_on_error || !null_stderr)
        error (exit_on_error ? EXIT_FAILURE : 0, errno,
               _("%s subprocess failed"), progname);
      return 127;
    }
#endif
  if (slave_process)
    {
      register_slave_subprocess (child);
      unblock_fatal_signals ();
    }

  return wait_subprocess (child, progname, ignore_sigpipe, null_stderr,
                          slave_process, exit_on_error);

#endif
}
======================== lib/w32spawn.h ========================
/* Auxiliary functions for the creation of subprocesses.  Native Woe32 API.
   Copyright (C) 2003 Free Software Foundation, Inc.
   Written by Bruno Haible <address@hidden>, 2003.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */

/* Get declarations of the Win32 API functions.  */
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

/* Get _get_osfhandle() and _open_osfhandle().  */
#include <io.h>

#include <stdbool.h>
#include <errno.h>

#include "strpbrk.h"
#include "xalloc.h"

/* Duplicates a file handle, making the copy uninheritable.  */
static int
dup_noinherit (int fd)
{
  HANDLE curr_process = GetCurrentProcess ();
  HANDLE old_handle = (HANDLE) _get_osfhandle (fd);
  HANDLE new_handle;
  int nfd;

  if (!DuplicateHandle (curr_process,               /* SourceProcessHandle */
                        old_handle,                 /* SourceHandle */
                        curr_process,               /* TargetProcessHandle */
                        (PHANDLE) &new_handle,      /* TargetHandle */
                        (DWORD) 0,                  /* DesiredAccess */
                        FALSE,                      /* InheritHandle */
                        DUPLICATE_SAME_ACCESS))     /* Options */
    error (EXIT_FAILURE, 0, _("DuplicateHandle failed with error code 0x%08x"),
           GetLastError ());

  nfd = _open_osfhandle ((long) new_handle, O_BINARY);
  if (nfd < 0)
    error (EXIT_FAILURE, errno, _("_open_osfhandle failed"));

  return nfd;
}

/* Prepares an argument vector before calling spawn().
   Note that spawn() does not by itself call the command interpreter
     (getenv ("COMSPEC") != NULL ? getenv ("COMSPEC") :
      ({ OSVERSIONINFO v; v.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
         GetVersionEx(&v);
         v.dwPlatformId == VER_PLATFORM_WIN32_NT;
      }) ? "cmd.exe" : "command.com").
   Instead it simply concatenates the arguments, separated by ' ', and calls
   CreateProcess().  We must quote the arguments since Win32 CreateProcess()
   interprets characters like ' ', '\t', '\\', '"' (but not '<' and '>') in a
   special way:
   - Space and tab are interpreted as delimiters. They are not treated as
     delimiters if they are surrounded by double quotes: "...".
   - Unescaped double quotes are removed from the input. Their only effect is
     that within double quotes, space and tab are treated like normal
     characters.
   - Backslashes not followed by double quotes are not special.
   - But 2*n+1 backslashes followed by a double quote become
     n backslashes followed by a double quote (n >= 0):
       \" -> "
       \\\" -> \"
       \\\\\" -> \\"
 */
#define SHELL_SPECIAL_CHARS "\"\\ 
\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037"
#define SHELL_SPACE_CHARS " 
\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037"
static char **
prepare_spawn (char **argv)
{
  size_t argc;
  char **new_argv;
  size_t i;

  /* Count number of arguments.  */
  for (argc = 0; argv[argc] != NULL; argc++)
    ;

  /* Allocate new argument vector.  */
  new_argv = (char **) xmalloc ((argc + 1) * sizeof (char *));

  /* Put quoted arguments into the new argument vector.  */
  for (i = 0; i < argc; i++)
    {
      const char *string = argv[i];

      if (string[0] == '\0')
        new_argv[i] = xstrdup ("\"\"");
      else if (strpbrk (string, SHELL_SPECIAL_CHARS) != NULL)
        {
          bool quote_around = (strpbrk (string, SHELL_SPACE_CHARS) != NULL);
          size_t length;
          unsigned int backslashes;
          const char *s;
          char *quoted_string;
          char *p;

          length = 0;
          backslashes = 0;
          if (quote_around)
            length++;
          for (s = string; *s != '\0'; s++)
            {
              char c = *s;
              if (c == '"')
                length += backslashes + 1;
              length++;
              if (c == '\\')
                backslashes++;
              else
                backslashes = 0;
            }
          if (quote_around)
            length += backslashes + 1;

          quoted_string = (char *) xmalloc (length + 1);

          p = quoted_string;
          backslashes = 0;
          if (quote_around)
            *p++ = '"';
          for (s = string; *s != '\0'; s++)
            {
              char c = *s;
              if (c == '"')
                {
                  unsigned int j;
                  for (j = backslashes + 1; j > 0; j--)
                    *p++ = '\\';
                }
              *p++ = c;
              if (c == '\\')
                backslashes++;
              else
                backslashes = 0;
            }
          if (quote_around)
            {
              unsigned int j;
              for (j = backslashes; j > 0; j--)
                *p++ = '\\';
              *p++ = '"';
            }
          *p = '\0';

          new_argv[i] = quoted_string;
        }
      else
        new_argv[i] = (char *) string;
    }
  new_argv[argc] = NULL;

  return new_argv;
}
======================== m4/execute.m4 ========================
# execute.m4 serial 1
dnl Copyright (C) 2003 Free Software Foundation, Inc.
dnl This file is free software, distributed under the terms of the GNU
dnl General Public License.  As a special exception to the GNU General
dnl Public License, this file may be distributed as part of a program
dnl that contains a configuration script generated by Autoconf, under
dnl the same distribution terms as the rest of that program.

AC_DEFUN([gl_EXECUTE],
[
  dnl Prerequisites of lib/execute.c.
  AC_REQUIRE([AC_C_INLINE])
  AC_REQUIRE([AC_TYPE_MODE_T])
  AC_CHECK_HEADERS_ONCE(unistd.h)
  AC_REQUIRE([AC_FUNC_FORK])
  AC_CHECK_FUNCS(posix_spawn)
])
===================================================================





reply via email to

[Prev in Thread] Current Thread [Next in Thread]