New module 'execute'.
authorBruno Haible <bruno@clisp.org>
Tue, 27 Jan 2004 11:10:45 +0000 (11:10 +0000)
committerBruno Haible <bruno@clisp.org>
Tue, 27 Jan 2004 11:10:45 +0000 (11:10 +0000)
ChangeLog
MODULES.html.sh
lib/ChangeLog
lib/execute.c [new file with mode: 0644]
lib/execute.h [new file with mode: 0644]
lib/w32spawn.h [new file with mode: 0644]
m4/ChangeLog
m4/execute.m4 [new file with mode: 0644]
modules/execute [new file with mode: 0644]

index 90e9a716c2d4da809292521f1de588eb2ce5d02d..f179faf1a61e92dde4a436c25d0c1e2d247076a6 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2004-01-27  Bruno Haible  <bruno@clisp.org>
+
+       * modules/execute: New file.
+       * MODULES.html.sh (func_all_modules): Add execute.
+
 2004-01-23  Paul Eggert  <eggert@twinsun.com>
 
        * modules/argmatch, modules/obstack, modules/xstrtol:
index 0821cb4cce5f485cc15f8d8752b452546484f8b1..23e95e74b496a0eff9da3f1eb8a55ae531a7a591 100755 (executable)
@@ -1873,7 +1873,7 @@ func_all_modules ()
   func_begin_table
   func_module findprog
   func_module wait-process
-  #func_module execute
+  func_module execute
   #func_module pipe
   #func_module sh-quote
   func_end_table
index 6a0abc6a837477c8f09c94575c1dd32707cf7c6a..82231d5b90943ad82d70867fcdd60d556fe56aac 100644 (file)
@@ -1,3 +1,9 @@
+2004-01-27  Bruno Haible  <bruno@clisp.org>
+
+       * execute.h: New file, from GNU gettext.
+       * execute.c: New file, from GNU gettext.
+       * w32spawn.h: New file, from GNU gettext.
+
 2004-01-23  Paul Eggert  <eggert@twinsun.com>
 
        Exit-status fix from coreutils.
diff --git a/lib/execute.c b/lib/execute.c
new file mode 100644 (file)
index 0000000..86df308
--- /dev/null
@@ -0,0 +1,314 @@
+/* Creation of autonomous subprocesses.
+   Copyright (C) 2001-2003 Free Software Foundation, Inc.
+   Written by Bruno Haible <haible@clisp.cons.org>, 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
+}
diff --git a/lib/execute.h b/lib/execute.h
new file mode 100644 (file)
index 0000000..18c9c43
--- /dev/null
@@ -0,0 +1,41 @@
+/* Creation of autonomous subprocesses.
+   Copyright (C) 2001-2003 Free Software Foundation, Inc.
+   Written by Bruno Haible <haible@clisp.cons.org>, 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 */
diff --git a/lib/w32spawn.h b/lib/w32spawn.h
new file mode 100644 (file)
index 0000000..18e64d7
--- /dev/null
@@ -0,0 +1,169 @@
+/* Auxiliary functions for the creation of subprocesses.  Native Woe32 API.
+   Copyright (C) 2003 Free Software Foundation, Inc.
+   Written by Bruno Haible <bruno@clisp.org>, 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;
+}
index e6558389b67e006bc18b66bf00d4c9950a886f56..17352859dd992177cfab1be78980d5bc41c7f48a 100644 (file)
@@ -1,3 +1,7 @@
+2004-01-27  Bruno Haible  <bruno@clisp.org>
+
+       * execute.m4: New file, from GNU gettext.
+
 2003-11-24  Bruno Haible  <bruno@clisp.org>
 
        * allocsa.m4: New file, from GNU gettext.
diff --git a/m4/execute.m4 b/m4/execute.m4
new file mode 100644 (file)
index 0000000..ed01054
--- /dev/null
@@ -0,0 +1,17 @@
+# 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)
+])
diff --git a/modules/execute b/modules/execute
new file mode 100644 (file)
index 0000000..f28640f
--- /dev/null
@@ -0,0 +1,30 @@
+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
+