changeset 4936:0fb731aa9b43

New module 'execute'.
author Bruno Haible <bruno@clisp.org>
date Tue, 27 Jan 2004 11:10:45 +0000
parents a05c22d4840f
children 845f04569515
files ChangeLog MODULES.html.sh lib/ChangeLog lib/execute.c lib/execute.h lib/w32spawn.h m4/ChangeLog m4/execute.m4 modules/execute
diffstat 9 files changed, 587 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- 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:
--- a/MODULES.html.sh
+++ b/MODULES.html.sh
@@ -1873,7 +1873,7 @@
   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
--- a/lib/ChangeLog
+++ b/lib/ChangeLog
@@ -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.
new file mode 100644
--- /dev/null
+++ b/lib/execute.c
@@ -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
+}
new file mode 100644
--- /dev/null
+++ b/lib/execute.h
@@ -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 */
new file mode 100644
--- /dev/null
+++ b/lib/w32spawn.h
@@ -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;
+}
--- a/m4/ChangeLog
+++ b/m4/ChangeLog
@@ -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.
new file mode 100644
--- /dev/null
+++ b/m4/execute.m4
@@ -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)
+])
new file mode 100644
--- /dev/null
+++ b/modules/execute
@@ -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
+