So far, the modules 'execute' and 'spawn-pipe' use, on native Windows,
the MSVCRT function _spawnvpe. But this function requires temporary
assignments to the stdin, stdout, or stderr file descriptors, which is
of course not multithread-safe.

With this patch, the modules use 'CreateProcess' directly. The new code
is multithread-safe.


2020-11-30  Bruno Haible  <br...@clisp.org>

        execute, spawn-pipe: Make multithread-safe on native Windows.
        * lib/windows-spawn.h: Include <stdint.h>, <windows.h>.
        (dup_safer_noinherit, undup_safer_noinherit): Remove declarations.
        (spawnpvech): New declaration.
        * lib/windows-spawn.c: Include <stdio.h>, <process.h>, findprog.h.
        Don't include <unistd.h>, cloexec.h, error.h, gettext.h.
        (_): Remove macro.
        (dup_noinherit, fd_safer_noinherit, dup_safer_noinherit,
        undup_safer_noinherit): Remove functions.
        (spawnpvech): New function.
        * modules/windows-spawn (Depends-on): Add findprog-in, stdint. Remove
        cloexec, dup2, error, gettext-h.
        * lib/execute.c: Include msvc-nothrow.h.
        (execute) [WIN32]: Use _get_osfhandle, spawnpvech instead of _spawnvpe.
        * lib/spawn-pipe.c: Include msvc-nothrow.h.
        (create_pipe) [WIN32]: Use _get_osfhandle, DuplicateHandle, spawnpvech
        instead of _spawnvpe.
        * modules/execute (Depends-on): Add msvc-nothrow.
        * modules/spawn-pipe (Depends-on): Likewise.

2020-11-30  Bruno Haible  <br...@clisp.org>

        execute, spawn-pipe: Improve documentation.
        * lib/execute.h: Describe progname, prog_path, prog_argv.
        * lib/spawn-pipe.h: Likewise.

>From c48f7d3e9dae0d7649ebf589d5202e997a89ac42 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Mon, 30 Nov 2020 03:01:22 +0100
Subject: [PATCH 1/2] execute, spawn-pipe: Improve documentation.

* lib/execute.h: Describe progname, prog_path, prog_argv.
* lib/spawn-pipe.h: Likewise.
---
 ChangeLog        | 6 ++++++
 lib/execute.h    | 8 ++++++++
 lib/spawn-pipe.h | 9 +++++++++
 3 files changed, 23 insertions(+)

diff --git a/ChangeLog b/ChangeLog
index 91b2d2d..ccb436f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2020-11-30  Bruno Haible  <br...@clisp.org>
 
+	execute, spawn-pipe: Improve documentation.
+	* lib/execute.h: Describe progname, prog_path, prog_argv.
+	* lib/spawn-pipe.h: Likewise.
+
+2020-11-30  Bruno Haible  <br...@clisp.org>
+
 	execute tests: Add more tests.
 	* tests/test-execute-main.c: Add tests for reading, writing, isatty on
 	inherited file descriptors >= 3.
diff --git a/lib/execute.h b/lib/execute.h
index 7e0ea0f..b31c4d1 100644
--- a/lib/execute.h
+++ b/lib/execute.h
@@ -24,6 +24,14 @@
    descriptors to /dev/null.  Return its exit code.
    If it didn't terminate correctly, exit if exit_on_error is true, otherwise
    return 127.
+   progname is the name of the program to be executed by the subprocess, used
+   for error messages.
+   prog_path is the file name of the program to be executed by the subprocess.
+   If it contains no slashes, a search is conducted in $PATH.  An operating
+   system dependent suffix is added, if necessary.
+   prog_argv is the array of strings that the subprocess shall receive in
+   argv[].  It is a NULL-terminated array.  prog_argv[0] should normally be
+   identical to prog_path.
    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.
diff --git a/lib/spawn-pipe.h b/lib/spawn-pipe.h
index 6a95650..be0f1c8 100644
--- a/lib/spawn-pipe.h
+++ b/lib/spawn-pipe.h
@@ -42,6 +42,15 @@ extern "C" {
    After finishing communication, the caller should call wait_subprocess()
    to get rid of the subprocess in the process table.
 
+   progname is the name of the program to be executed by the subprocess, used
+   for error messages.
+   prog_path is the file name of the program to be executed by the subprocess.
+   If it contains no slashes, a search is conducted in $PATH.  An operating
+   system dependent suffix is added, if necessary.
+   prog_argv is the array of strings that the subprocess shall receive in
+   argv[].  It is a NULL-terminated array.  prog_argv[0] should normally be
+   identical to prog_path.
+
    If slave_process is true, the child process will be terminated when its
    creator receives a catchable fatal signal or exits normally.  If
    slave_process is false, the child process will continue running in this
-- 
2.7.4

>From c5388bad32e897c410336d6a3d8258cea2e9a6ad Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Mon, 30 Nov 2020 17:58:57 +0100
Subject: [PATCH 2/2] execute, spawn-pipe: Make multithread-safe on native
 Windows.

* lib/windows-spawn.h: Include <stdint.h>, <windows.h>.
(dup_safer_noinherit, undup_safer_noinherit): Remove declarations.
(spawnpvech): New declaration.
* lib/windows-spawn.c: Include <stdio.h>, <process.h>, findprog.h.
Don't include <unistd.h>, cloexec.h, error.h, gettext.h.
(_): Remove macro.
(dup_noinherit, fd_safer_noinherit, dup_safer_noinherit,
undup_safer_noinherit): Remove functions.
(spawnpvech): New function.
* modules/windows-spawn (Depends-on): Add findprog-in, stdint. Remove
cloexec, dup2, error, gettext-h.
* lib/execute.c: Include msvc-nothrow.h.
(execute) [WIN32]: Use _get_osfhandle, spawnpvech instead of _spawnvpe.
* lib/spawn-pipe.c: Include msvc-nothrow.h.
(create_pipe) [WIN32]: Use _get_osfhandle, DuplicateHandle, spawnpvech
instead of _spawnvpe.
* modules/execute (Depends-on): Add msvc-nothrow.
* modules/spawn-pipe (Depends-on): Likewise.
---
 ChangeLog             |  22 +++
 lib/execute.c         |  88 ++++-------
 lib/spawn-pipe.c      | 122 +++++++++++++--
 lib/windows-spawn.c   | 427 ++++++++++++++++++++++++++++++++++++++++++--------
 lib/windows-spawn.h   |  34 +++-
 modules/execute       |   5 +-
 modules/spawn-pipe    |   1 +
 modules/windows-spawn |   6 +-
 8 files changed, 558 insertions(+), 147 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index ccb436f..774dbee 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,27 @@
 2020-11-30  Bruno Haible  <br...@clisp.org>
 
+	execute, spawn-pipe: Make multithread-safe on native Windows.
+	* lib/windows-spawn.h: Include <stdint.h>, <windows.h>.
+	(dup_safer_noinherit, undup_safer_noinherit): Remove declarations.
+	(spawnpvech): New declaration.
+	* lib/windows-spawn.c: Include <stdio.h>, <process.h>, findprog.h.
+	Don't include <unistd.h>, cloexec.h, error.h, gettext.h.
+	(_): Remove macro.
+	(dup_noinherit, fd_safer_noinherit, dup_safer_noinherit,
+	undup_safer_noinherit): Remove functions.
+	(spawnpvech): New function.
+	* modules/windows-spawn (Depends-on): Add findprog-in, stdint. Remove
+	cloexec, dup2, error, gettext-h.
+	* lib/execute.c: Include msvc-nothrow.h.
+	(execute) [WIN32]: Use _get_osfhandle, spawnpvech instead of _spawnvpe.
+	* lib/spawn-pipe.c: Include msvc-nothrow.h.
+	(create_pipe) [WIN32]: Use _get_osfhandle, DuplicateHandle, spawnpvech
+	instead of _spawnvpe.
+	* modules/execute (Depends-on): Add msvc-nothrow.
+	* modules/spawn-pipe (Depends-on): Likewise.
+
+2020-11-30  Bruno Haible  <br...@clisp.org>
+
 	execute, spawn-pipe: Improve documentation.
 	* lib/execute.h: Describe progname, prog_path, prog_argv.
 	* lib/spawn-pipe.h: Likewise.
diff --git a/lib/execute.c b/lib/execute.c
index 41e1e92..149cc89 100644
--- a/lib/execute.c
+++ b/lib/execute.c
@@ -38,6 +38,11 @@
 #if defined _WIN32 && ! defined __CYGWIN__
 
 /* Native Windows API.  */
+# if GNULIB_MSVC_NOTHROW
+#  include "msvc-nothrow.h"
+# else
+#  include <io.h>
+# endif
 # include <process.h>
 # include "windows-spawn.h"
 
@@ -86,12 +91,6 @@ nonintr_open (const char *pathname, int oflag, mode_t mode)
 #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,
@@ -103,63 +102,46 @@ execute (const char *progname,
 #if defined _WIN32 && ! defined __CYGWIN__
 
   /* Native Windows API.  */
-  int orig_stdin;
-  int orig_stdout;
-  int orig_stderr;
-  int exitcode;
-  int nullinfd;
-  int nulloutfd;
 
   /* FIXME: Need to free memory allocated by prepare_spawn.  */
   prog_argv = prepare_spawn (prog_argv);
 
-  /* Save standard file handles of parent process.  */
-  if (null_stdin)
-    orig_stdin = dup_safer_noinherit (STDIN_FILENO);
-  if (null_stdout)
-    orig_stdout = dup_safer_noinherit (STDOUT_FILENO);
-  if (null_stderr)
-    orig_stderr = dup_safer_noinherit (STDERR_FILENO);
-  exitcode = -1;
+  int exitcode = -1;
 
   /* Create standard file handles of child process.  */
-  nullinfd = -1;
-  nulloutfd = -1;
+  int nullinfd = -1;
+  int nulloutfd = -1;
   if ((!null_stdin
-       || ((nullinfd = open ("NUL", O_RDONLY, 0)) >= 0
-           && (nullinfd == STDIN_FILENO
-               || (dup2 (nullinfd, STDIN_FILENO) >= 0
-                   && close (nullinfd) >= 0))))
+       || (nullinfd = open ("NUL", O_RDONLY, 0)) >= 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))))
-    /* Use _spawnvpe and pass the environment explicitly.  This is needed if
-       the program has modified the environment using putenv() or [un]setenv().
-       On Windows, programs have two environments, one in the "environment
-       block" of the process and managed through SetEnvironmentVariable(), and
-       one inside the process, in the location retrieved by the 'environ'
-       macro.  When using _spawnvp() without 'e', the child process inherits a
-       copy of the environment block - ignoring the effects of putenv() and
-       [un]setenv().  */
+          || (nulloutfd = open ("NUL", O_RDWR, 0)) >= 0))
+    /* Pass the environment explicitly.  This is needed if the program has
+       modified the environment using putenv() or [un]setenv().  On Windows,
+       processes have two environments, one in the "environment block" of the
+       process and managed through SetEnvironmentVariable(), and one inside the
+       process, in the location retrieved by the 'environ' macro.  If we were
+       to pass NULL, the child process would inherit a copy of the environment
+       block - ignoring the effects of putenv() and [un]setenv().  */
     {
-      exitcode = _spawnvpe (P_WAIT, prog_path, (const char **) prog_argv,
-                            (const char **) environ);
-      if (exitcode < 0 && errno == ENOEXEC)
+      HANDLE stdin_handle =
+        (HANDLE) _get_osfhandle (null_stdin ? nullinfd : STDIN_FILENO);
+      HANDLE stdout_handle =
+        (HANDLE) _get_osfhandle (null_stdout ? nulloutfd : STDOUT_FILENO);
+      HANDLE stderr_handle =
+        (HANDLE) _get_osfhandle (null_stderr ? nulloutfd : STDERR_FILENO);
+
+      exitcode = spawnpvech (P_WAIT, prog_path, (const char **) prog_argv,
+                             (const char **) environ, NULL,
+                             stdin_handle, stdout_handle, stderr_handle);
+      if (exitcode == -1 && errno == ENOEXEC)
         {
           /* prog is not a native executable.  Try to execute it as a
              shell script.  Note that prepare_spawn() has already prepended
              a hidden element "sh.exe" to prog_argv.  */
           --prog_argv;
-          exitcode = _spawnvpe (P_WAIT, prog_argv[0], (const char **) prog_argv,
-                                (const char **) environ);
+          exitcode = spawnpvech (P_WAIT, prog_argv[0], (const char **) prog_argv,
+                                 (const char **) environ, NULL,
+                                 stdin_handle, stdout_handle, stderr_handle);
         }
     }
   if (nulloutfd >= 0)
@@ -167,14 +149,6 @@ execute (const char *progname,
   if (nullinfd >= 0)
     close (nullinfd);
 
-  /* Restore standard file handles of parent process.  */
-  if (null_stderr)
-    undup_safer_noinherit (orig_stderr, STDERR_FILENO);
-  if (null_stdout)
-    undup_safer_noinherit (orig_stdout, STDOUT_FILENO);
-  if (null_stdin)
-    undup_safer_noinherit (orig_stdin, STDIN_FILENO);
-
   if (termsigp != NULL)
     *termsigp = 0;
 
diff --git a/lib/spawn-pipe.c b/lib/spawn-pipe.c
index b0f5314..a4c4d39 100644
--- a/lib/spawn-pipe.c
+++ b/lib/spawn-pipe.c
@@ -43,6 +43,11 @@
 #if defined _WIN32 && ! defined __CYGWIN__
 
 /* Native Windows API.  */
+# if GNULIB_MSVC_NOTHROW
+#  include "msvc-nothrow.h"
+# else
+#  include <io.h>
+# endif
 # include <process.h>
 # include "windows-spawn.h"
 
@@ -130,9 +135,6 @@ create_pipe (const char *progname,
      and cvs source code.  */
   int ifd[2];
   int ofd[2];
-  int orig_stdin;
-  int orig_stdout;
-  int orig_stderr;
   int child;
   int nulloutfd;
   int stdinfd;
@@ -157,6 +159,107 @@ create_pipe (const char *progname,
  *
  */
 
+  child = -1;
+
+# if defined _WIN32 && ! defined __CYGWIN__
+  bool must_close_ifd1 = pipe_stdout;
+  bool must_close_ofd0 = pipe_stdin;
+
+  /* Create standard file handles of child process.  */
+  nulloutfd = -1;
+  stdinfd = -1;
+  stdoutfd = -1;
+  if ((!null_stderr
+       || (nulloutfd = open ("NUL", O_RDWR, 0)) >= 0)
+      && (pipe_stdin
+          || prog_stdin == NULL
+          || (stdinfd = open (prog_stdin, O_RDONLY, 0)) >= 0)
+      && (pipe_stdout
+          || prog_stdout == NULL
+          || (stdoutfd = open (prog_stdout, O_WRONLY, 0)) >= 0))
+    /* The child process doesn't inherit ifd[0], ifd[1], ofd[0], ofd[1],
+       but it inherits the three STD*_FILENO for which we pass the handles.  */
+    /* Pass the environment explicitly.  This is needed if the program has
+       modified the environment using putenv() or [un]setenv().  On Windows,
+       processes have two environments, one in the "environment block" of the
+       process and managed through SetEnvironmentVariable(), and one inside the
+       process, in the location retrieved by the 'environ' macro.  If we were
+       to pass NULL, the child process would inherit a copy of the environment
+       block - ignoring the effects of putenv() and [un]setenv().  */
+    {
+      HANDLE stdin_handle =
+        (HANDLE) _get_osfhandle (pipe_stdin ? ofd[0] :
+                                 prog_stdin == NULL ? STDIN_FILENO : stdinfd);
+      if (pipe_stdin)
+        {
+          HANDLE curr_process = GetCurrentProcess ();
+          HANDLE duplicate;
+          if (!DuplicateHandle (curr_process, stdin_handle,
+                                curr_process, &duplicate,
+                                0, TRUE, DUPLICATE_SAME_ACCESS))
+            {
+              errno = EBADF; /* arbitrary */
+              goto failed;
+            }
+          must_close_ofd0 = false;
+          close (ofd[0]); /* implies CloseHandle (stdin_handle); */
+          stdin_handle = duplicate;
+        }
+      HANDLE stdout_handle =
+        (HANDLE) _get_osfhandle (pipe_stdout ? ifd[1] :
+                                 prog_stdout == NULL ? STDOUT_FILENO : stdoutfd);
+      if (pipe_stdout)
+        {
+          HANDLE curr_process = GetCurrentProcess ();
+          HANDLE duplicate;
+          if (!DuplicateHandle (curr_process, stdout_handle,
+                                curr_process, &duplicate,
+                                0, TRUE, DUPLICATE_SAME_ACCESS))
+            {
+              errno = EBADF; /* arbitrary */
+              goto failed;
+            }
+          must_close_ifd1 = false;
+          close (ifd[1]); /* implies CloseHandle (stdout_handle); */
+          stdout_handle = duplicate;
+        }
+      HANDLE stderr_handle =
+        (HANDLE) _get_osfhandle (null_stderr ? nulloutfd : STDERR_FILENO);
+
+      child = spawnpvech (P_NOWAIT, prog_path, (const char **) prog_argv,
+                          (const char **) environ, NULL,
+                          stdin_handle, stdout_handle, stderr_handle);
+      if (child == -1 && errno == ENOEXEC)
+        {
+          /* prog is not a native executable.  Try to execute it as a
+             shell script.  Note that prepare_spawn() has already prepended
+             a hidden element "sh.exe" to prog_argv.  */
+          --prog_argv;
+          child = spawnpvech (P_NOWAIT, prog_argv[0], (const char **) prog_argv,
+                              (const char **) environ, NULL,
+                              stdin_handle, stdout_handle, stderr_handle);
+        }
+    }
+ failed:
+  if (child == -1)
+    saved_errno = errno;
+  if (stdinfd >= 0)
+    close (stdinfd);
+  if (stdoutfd >= 0)
+    close (stdoutfd);
+  if (nulloutfd >= 0)
+    close (nulloutfd);
+
+  if (must_close_ofd0)
+    close (ofd[0]);
+  if (must_close_ifd1)
+    close (ifd[1]);
+
+# else /* __KLIBC__ */
+  int orig_stdin;
+  int orig_stdout;
+  int orig_stderr;
+
   /* Save standard file handles of parent process.  */
   if (pipe_stdin || prog_stdin != NULL)
     orig_stdin = dup_safer_noinherit (STDIN_FILENO);
@@ -164,7 +267,6 @@ create_pipe (const char *progname,
     orig_stdout = dup_safer_noinherit (STDOUT_FILENO);
   if (null_stderr)
     orig_stderr = dup_safer_noinherit (STDERR_FILENO);
-  child = -1;
 
   /* Create standard file handles of child process.  */
   nulloutfd = -1;
@@ -192,18 +294,10 @@ create_pipe (const char *progname,
     /* The child process doesn't inherit ifd[0], ifd[1], ofd[0], ofd[1],
        but it inherits all open()ed or dup2()ed file handles (which is what
        we want in the case of STD*_FILENO).  */
-    /* Use _spawnvpe and pass the environment explicitly.  This is needed if
-       the program has modified the environment using putenv() or [un]setenv().
-       On Windows, programs have two environments, one in the "environment
-       block" of the process and managed through SetEnvironmentVariable(), and
-       one inside the process, in the location retrieved by the 'environ'
-       macro.  When using _spawnvp() without 'e', the child process inherits a
-       copy of the environment block - ignoring the effects of putenv() and
-       [un]setenv().  */
     {
       child = _spawnvpe (P_NOWAIT, prog_path, (const char **) prog_argv,
                          (const char **) environ);
-      if (child < 0 && errno == ENOEXEC)
+      if (child == -1 && errno == ENOEXEC)
         {
           /* prog is not a native executable.  Try to execute it as a
              shell script.  Note that prepare_spawn() has already prepended
@@ -234,6 +328,8 @@ create_pipe (const char *progname,
     close (ofd[0]);
   if (pipe_stdout)
     close (ifd[1]);
+# endif
+
   if (child == -1)
     {
       if (exit_on_error || !null_stderr)
diff --git a/lib/windows-spawn.c b/lib/windows-spawn.c
index 776303a..2a59ff2 100644
--- a/lib/windows-spawn.c
+++ b/lib/windows-spawn.c
@@ -24,13 +24,10 @@
 #define WIN32_LEAN_AND_MEAN
 #include <windows.h>
 
-/* Get _open_osfhandle().  */
-#include <io.h>
-
 #include <stdbool.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <unistd.h>
 #include <errno.h>
 
 /* Get _get_osfhandle().  */
@@ -39,70 +36,16 @@
 #else
 # include <io.h>
 #endif
+#include <process.h>
 
-#include "cloexec.h"
-#include "error.h"
+#include "findprog.h"
 #include "xalloc.h"
-#include "gettext.h"
-
-#define _(str) gettext (str)
-
-
-/* Duplicates a file handle, making the copy uninheritable.
-   Returns -1 for a file handle that is equivalent to closed.  */
-static int
-dup_noinherit (int fd)
-{
-  fd = dup_cloexec (fd);
-  if (fd < 0 && errno == EMFILE)
-    error (EXIT_FAILURE, errno, _("_open_osfhandle failed"));
 
-  return fd;
-}
-
-/* Returns a file descriptor equivalent to FD, except that the resulting file
-   descriptor is none of STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO.
-   FD must be open and non-inheritable.  The result will be non-inheritable as
-   well.
-   If FD < 0, FD itself is returned.  */
-static int
-fd_safer_noinherit (int fd)
-{
-  if (STDIN_FILENO <= fd && fd <= STDERR_FILENO)
-    {
-      /* The recursion depth is at most 3.  */
-      int nfd = fd_safer_noinherit (dup_noinherit (fd));
-      int saved_errno = errno;
-      close (fd);
-      errno = saved_errno;
-      return nfd;
-    }
-  return fd;
-}
-
-int
-dup_safer_noinherit (int fd)
-{
-  return fd_safer_noinherit (dup_noinherit (fd));
-}
-
-void
-undup_safer_noinherit (int tempfd, int origfd)
-{
-  if (tempfd >= 0)
-    {
-      if (dup2 (tempfd, origfd) < 0)
-        error (EXIT_FAILURE, errno, _("cannot restore fd %d: dup2 failed"),
-               origfd);
-      close (tempfd);
-    }
-  else
-    {
-      /* origfd was closed or open to no handle at all.  Set it to a closed
-         state.  This is (nearly) equivalent to the original state.  */
-      close (origfd);
-    }
-}
+/* Don't assume that UNICODE is not defined.  */
+#undef STARTUPINFO
+#define STARTUPINFO STARTUPINFOA
+#undef CreateProcess
+#define CreateProcess CreateProcessA
 
 #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"
@@ -201,3 +144,357 @@ prepare_spawn (char **argv)
 
   return new_argv;
 }
+
+intptr_t
+spawnpvech (int mode,
+            const char *progname, const char * const *argv,
+            const char * const *envp,
+            const char *currdir,
+            HANDLE stdin_handle, HANDLE stdout_handle, HANDLE stderr_handle)
+{
+  /* Validate the arguments.  */
+  if (!(mode == P_WAIT
+        || mode == P_NOWAIT
+        || mode == P_DETACH
+        || mode == P_OVERLAY)
+      || progname == NULL || argv == NULL)
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  /* Implement the 'p' letter: search for PROGNAME in getenv ("PATH").  */
+  const char *resolved_progname =
+    find_in_given_path (progname, getenv ("PATH"), false);
+  if (resolved_progname == NULL)
+    return -1;
+
+  /* Compose the command.
+     Just concatenate the argv[] strings, separated by spaces.  */
+  char *command;
+  {
+    /* Determine the size of the needed block of memory.  */
+    size_t total_size = 0;
+    const char * const *ap;
+    const char *p;
+    for (ap = argv; (p = *ap) != NULL; ap++)
+      total_size += strlen (p) + 1;
+    size_t command_size = (total_size > 0 ? total_size : 1);
+    command = (char *) malloc (command_size);
+    if (command == NULL)
+      goto out_of_memory_1;
+    if (total_size > 0)
+      {
+        char *cp = command;
+        for (ap = argv; (p = *ap) != NULL; ap++)
+          {
+            size_t size = strlen (p) + 1;
+            memcpy (cp, p, size - 1);
+            cp += size;
+            cp[-1] = ' ';
+          }
+        cp[-1] = '\0';
+      }
+    else
+      *command = '\0';
+  }
+
+  /* Copy *ENVP into a contiguous block of memory.  */
+  char *envblock;
+  if (envp == NULL)
+    envblock = NULL;
+  else
+   retry:
+    {
+      /* Guess the size of the needed block of memory.
+         The guess will be exact if other threads don't make modifications.  */
+      size_t total_size = 0;
+      const char * const *ep;
+      const char *p;
+      for (ep = envp; (p = *ep) != NULL; ep++)
+        total_size += strlen (p) + 1;
+      size_t envblock_size = total_size;
+      envblock = (char *) malloc (envblock_size + 1);
+      if (envblock == NULL)
+        goto out_of_memory_2;
+      size_t envblock_used = 0;
+      for (ep = envp; (p = *ep) != NULL; ep++)
+        {
+          size_t size = strlen (p) + 1;
+          if (envblock_used + size > envblock_size)
+            {
+              /* Other threads did modifications.  Need more memory.  */
+              envblock_size += envblock_size / 2;
+              if (envblock_used + size > envblock_size)
+                envblock_size = envblock_used + size;
+
+              char *new_envblock = (char *) realloc (envblock, envblock_size + 1);
+              if (new_envblock == NULL)
+                goto out_of_memory_3;
+              envblock = new_envblock;
+            }
+          memcpy (envblock + envblock_used, p, size);
+          envblock_used += size;
+          if (envblock[envblock_used - 1] != '\0')
+            {
+              /* Other threads did modifications.  Restart.  */
+              free (envblock);
+              goto retry;
+            }
+        }
+      envblock[envblock_used] = '\0';
+    }
+
+  /* CreateProcess
+     <https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa>  */
+  /* Regarding handle inheritance, see
+     <https://docs.microsoft.com/en-us/windows/win32/sysinfo/handle-inheritance>  */
+  /* <https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags>  */
+  DWORD flags = (mode == P_DETACH ? DETACHED_PROCESS : 0);
+  /* STARTUPINFO
+     <https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa>  */
+  STARTUPINFO sinfo;
+  sinfo.cb = sizeof (STARTUPINFO);
+  sinfo.lpReserved = NULL;
+  sinfo.lpDesktop = NULL;
+  sinfo.lpTitle = NULL;
+  sinfo.dwFlags = STARTF_USESTDHANDLES;
+  sinfo.hStdInput = stdin_handle;
+  sinfo.hStdOutput = stdout_handle;
+  sinfo.hStdError = stderr_handle;
+
+  char *hblock = NULL;
+#if 0
+  sinfo.cbReserved2 = 0;
+  sinfo.lpReserved2 = NULL;
+#else
+  /* On newer versions of Windows, more file descriptors / handles than the
+     first three can be passed.
+     The format is as follows: Let N be an exclusive upper bound for the file
+     descriptors to be passed. Two arrays are constructed in memory:
+       - flags[0..N-1], of element type 'unsigned char',
+       - handles[0..N-1], of element type 'HANDLE' or 'intptr_t'.
+     For used entries, handles[i] is the handle, and flags[i] is a set of flags,
+     a combination of:
+        1 for open file descriptors,
+       64 for handles of type FILE_TYPE_CHAR,
+        8 for handles of type FILE_TYPE_PIPE.
+     For unused entries - this includes the first three, since they are already
+     passed above -, handles[i] is INVALID_HANDLE_VALUE and flags[i] is zero.
+     lpReserved2 now is a pointer to the concatenation (without padding) of:
+       - an 'unsigned int' whose value is N,
+       - the contents of the flags[0..N-1] array,
+       - the contents of the handles[0..N-1] array.
+     cbReserved2 is the size (in bytes) of the object at lpReserved2.  */
+  {
+    /* _getmaxstdio
+       <https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/getmaxstdio>
+       Default value is 512.  */
+    unsigned int fdmax;
+    for (fdmax = _getmaxstdio (); fdmax > 0; fdmax--)
+      {
+        unsigned int fd = fdmax - 1;
+        /* _get_osfhandle
+           <https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/get-osfhandle>  */
+        HANDLE handle = (HANDLE) _get_osfhandle (fd);
+        if (handle != INVALID_HANDLE_VALUE)
+          {
+            DWORD hflags;
+            /* GetHandleInformation
+               <https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-gethandleinformation>  */
+            if (GetHandleInformation (handle, &hflags))
+              {
+                if ((hflags & HANDLE_FLAG_INHERIT) != 0)
+                  /* fd denotes an inheritable descriptor.  */
+                  break;
+              }
+          }
+      }
+    if (fdmax > 0)
+      {
+        sinfo.cbReserved2 =
+          sizeof (unsigned int)
+          + fdmax * sizeof (unsigned char)
+          + fdmax * sizeof (HANDLE);
+        /* Add some padding, so that we can work with a properly HANDLE array.  */
+        hblock = (char *) malloc (sinfo.cbReserved2 + (sizeof (HANDLE) - 1));
+        if (hblock == NULL)
+          goto out_of_memory_3;
+        * (unsigned int *) hblock = fdmax;
+        unsigned char *flags = (unsigned char *) (hblock + sizeof (unsigned int));
+        char *handles = (char *) (flags + fdmax);
+        HANDLE *handles_aligned =
+          (HANDLE *) (((uintptr_t) handles + (sizeof (HANDLE) - 1))
+                      & - (uintptr_t) sizeof (HANDLE));
+
+        unsigned int fd;
+        for (fd = 0; fd < fdmax; fd++)
+          {
+            flags[fd] = 0;
+            handles_aligned[fd] = INVALID_HANDLE_VALUE;
+            /* The first three are already passed above.  */
+            if (fd >= 3)
+              {
+                /* _get_osfhandle
+                   <https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/get-osfhandle>  */
+                HANDLE handle = (HANDLE) _get_osfhandle (fd);
+                if (handle != INVALID_HANDLE_VALUE)
+                  {
+                    DWORD hflags;
+                    /* GetHandleInformation
+                       <https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-gethandleinformation>  */
+                    if (GetHandleInformation (handle, &hflags))
+                      {
+                        if ((hflags & HANDLE_FLAG_INHERIT) != 0)
+                          {
+                            /* fd denotes an inheritable descriptor.  */
+                            /* On Microsoft Windows, it would be sufficient to
+                               set flags[fd] = 1.  But on ReactOS or Wine,
+                               adding the bit that indicates the handle type
+                               may be necessary.  So, just do it everywhere.  */
+                            switch (GetFileType (handle))
+                              {
+                              case FILE_TYPE_CHAR:
+                                flags[fd] = 64 | 1;
+                                break;
+                              case FILE_TYPE_PIPE:
+                                flags[fd] = 8 | 1;
+                                break;
+                              default:
+                                flags[fd] = 1;
+                                break;
+                              }
+                            handles_aligned[fd] = handle;
+                          }
+                      }
+                  }
+              }
+          }
+
+        if (handles != (char *) handles_aligned)
+          memmove (handles, (char *) handles_aligned, fdmax * sizeof (HANDLE));
+        sinfo.lpReserved2 = (BYTE *) hblock;
+      }
+    else
+      {
+        sinfo.cbReserved2 = 0;
+        sinfo.lpReserved2 = NULL;
+      }
+  }
+#endif
+
+  PROCESS_INFORMATION pinfo;
+  if (!CreateProcess (resolved_progname, command, NULL, NULL, TRUE,
+                      flags, envblock, currdir, &sinfo, &pinfo))
+    {
+      DWORD error = GetLastError ();
+
+      if (hblock != NULL)
+        free (hblock);
+      if (envblock != NULL)
+        free (envblock);
+      free (command);
+      if (resolved_progname != progname)
+        free ((char *) resolved_progname);
+
+      /* Some of these errors probably cannot happen.  But who knows...  */
+      switch (error)
+        {
+        case ERROR_FILE_NOT_FOUND:
+        case ERROR_PATH_NOT_FOUND:
+        case ERROR_BAD_PATHNAME:
+        case ERROR_BAD_NET_NAME:
+        case ERROR_INVALID_NAME:
+        case ERROR_DIRECTORY:
+          errno = ENOENT;
+          break;
+
+        case ERROR_ACCESS_DENIED:
+        case ERROR_SHARING_VIOLATION:
+          errno = EACCES;
+          break;
+
+        case ERROR_OUTOFMEMORY:
+          errno = ENOMEM;
+          break;
+
+        case ERROR_BUFFER_OVERFLOW:
+        case ERROR_FILENAME_EXCED_RANGE:
+          errno = ENAMETOOLONG;
+          break;
+
+        default:
+          errno = EINVAL;
+          break;
+        }
+
+      return -1;
+    }
+
+  if (pinfo.hThread)
+    CloseHandle (pinfo.hThread);
+  if (hblock != NULL)
+    free (hblock);
+  if (envblock != NULL)
+    free (envblock);
+  free (command);
+  if (resolved_progname != progname)
+    free ((char *) resolved_progname);
+
+  switch (mode)
+    {
+    case P_WAIT:
+      {
+        /* Wait until it terminates.  Then get its exit status code.  */
+        switch (WaitForSingleObject (pinfo.hProcess, INFINITE))
+          {
+          case WAIT_OBJECT_0:
+            break;
+          case WAIT_FAILED:
+            errno = ECHILD;
+            return -1;
+          default:
+            abort ();
+          }
+
+        DWORD exit_code;
+        if (!GetExitCodeProcess (pinfo.hProcess, &exit_code))
+          {
+            errno = ECHILD;
+            return -1;
+          }
+        CloseHandle (pinfo.hProcess);
+        return exit_code;
+      }
+
+    case P_NOWAIT:
+      /* Return pinfo.hProcess, not pinfo.dwProcessId.  */
+      return (intptr_t) pinfo.hProcess;
+
+    case P_DETACH:
+    case P_OVERLAY:
+      CloseHandle (pinfo.hProcess);
+      return 0;
+
+    default:
+      /* Already checked above.  */
+      abort ();
+    }
+
+  /*NOTREACHED*/
+#if 0
+ out_of_memory_4:
+  if (hblock != NULL)
+    free (hblock);
+#endif
+ out_of_memory_3:
+  if (envblock != NULL)
+    free (envblock);
+ out_of_memory_2:
+  free (command);
+ out_of_memory_1:
+  if (resolved_progname != progname)
+    free ((char *) resolved_progname);
+  errno = ENOMEM;
+  return -1;
+}
diff --git a/lib/windows-spawn.h b/lib/windows-spawn.h
index 200f321..8801f83 100644
--- a/lib/windows-spawn.h
+++ b/lib/windows-spawn.h
@@ -18,13 +18,11 @@
 #ifndef _WINDOWS_SPAWN_H
 #define _WINDOWS_SPAWN_H
 
-/* Duplicates a file handle, making the copy uninheritable and ensuring the
-   result is none of STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO.
-   Returns -1 for a file handle that is equivalent to closed.  */
-extern int dup_safer_noinherit (int fd);
+#include <stdint.h>
 
-/* Undoes the effect of TEMPFD = dup_safer_noinherit (ORIGFD);  */
-extern void undup_safer_noinherit (int tempfd, int origfd);
+/* Get declarations of the native Windows API functions.  */
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
 
 /* Prepares an argument vector before calling spawn().
    Note that spawn() does not by itself call the command interpreter
@@ -58,4 +56,28 @@ extern void undup_safer_noinherit (int tempfd, int origfd);
  */
 extern char ** prepare_spawn (char **argv);
 
+/* Creates a subprocess.
+   MODE is either P_WAIT or P_NOWAIT.
+   PROGNAME is the program to invoke.
+   ARGV is the NULL-terminated array of arguments, ARGV[0] being PROGNAME by
+   convention.
+   ENVP is the NULL-terminated set of environment variable assignments, or NULL
+   to inherit the initial environ variable assignments from the caller and
+   ignore all calls to putenv(), setenv(), unsetenv() done in the caller.
+   CURRDIR is the directory in which to start the program, or NULL to inherit
+   the working directory from the caller.
+   STDIN_HANDLE, STDOUT_HANDLE, STDERR_HANDLE are the handles to use for the
+   first three file descriptors in the callee process.
+   Returns
+     - 0 for success (if MODE is P_WAIT), or
+     - a handle that be passed to _cwait (on Windows) or waitpid (on OS/2), or
+     - -1 upon error, with errno set.
+ */
+extern intptr_t spawnpvech (int mode,
+                            const char *progname, const char * const *argv,
+                            const char * const *envp,
+                            const char *currdir,
+                            HANDLE stdin_handle, HANDLE stdout_handle,
+                            HANDLE stderr_handle);
+
 #endif /* _WINDOWS_SPAWN_H */
diff --git a/modules/execute b/modules/execute
index 4a7f845..52eee5f 100644
--- a/modules/execute
+++ b/modules/execute
@@ -8,9 +8,10 @@ m4/execute.m4
 
 Depends-on:
 dup2
+environ
 error
 fatal-signal
-wait-process
+msvc-nothrow
 gettext-h
 spawn
 posix_spawnp
@@ -24,7 +25,7 @@ posix_spawnattr_destroy
 stdbool
 stdlib
 unistd
-environ
+wait-process
 windows-spawn
 
 configure.ac:
diff --git a/modules/spawn-pipe b/modules/spawn-pipe
index 7845afa..0ddbc74 100644
--- a/modules/spawn-pipe
+++ b/modules/spawn-pipe
@@ -14,6 +14,7 @@ environ
 error
 fatal-signal
 gettext-h
+msvc-nothrow
 open
 pipe2
 pipe2-safer
diff --git a/modules/windows-spawn b/modules/windows-spawn
index 747f8bb..4702c50 100644
--- a/modules/windows-spawn
+++ b/modules/windows-spawn
@@ -6,12 +6,10 @@ lib/windows-spawn.h
 lib/windows-spawn.c
 
 Depends-on:
-cloexec
-dup2
-error
-gettext-h
+findprog-in
 msvc-nothrow
 stdbool
+stdint
 stdlib
 strpbrk
 unistd
-- 
2.7.4

Reply via email to