summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog22
-rw-r--r--lib/execute.c88
-rw-r--r--lib/spawn-pipe.c122
-rw-r--r--lib/windows-spawn.c427
-rw-r--r--lib/windows-spawn.h34
-rw-r--r--modules/execute5
-rw-r--r--modules/spawn-pipe1
-rw-r--r--modules/windows-spawn6
8 files changed, 558 insertions, 147 deletions
diff --git a/ChangeLog b/ChangeLog
index ccb436fe77..774dbee781 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,27 @@
2020-11-30 Bruno Haible <bruno@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 <bruno@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 41e1e9222c..149cc89454 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 b0f5314655..a4c4d39c8c 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 776303abf1..2a59ff29f5 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 200f321b5a..8801f83e19 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 4a7f845961..52eee5fed0 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 7845afa5b3..0ddbc74d3d 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 747f8bb8a7..4702c50f08 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