bug-gnulib@gnu.org mirror (unofficial)
 help / color / mirror / Atom feed
* posix_spawn[p]: implement for native Windows
@ 2020-12-24 22:03 Bruno Haible
  0 siblings, 0 replies; only message in thread
From: Bruno Haible @ 2020-12-24 22:03 UTC (permalink / raw)
  To: bug-gnulib

[-- Attachment #1: Type: text/plain, Size: 3673 bytes --]

The attached patches implement posix_spawn and posix_spawnp for native
Windows.

To my knowledge, it's a world's first. While the posix_spawn facility
was designed to be portable to other operating systems [1], the mingw
people did not and do not provide it.

[1] https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn.html
    section "RATIONALE"


2020-12-24  Bruno Haible  <bruno@clisp.org>

	posix_spawn-internal: Implement for native Windows.
	* lib/spawni.c (grow_inheritable_handles, shrink_inheritable_handles,
	close_inheritable_handles, memiszero, sigisempty, open_handle, do_open,
	do_dup2, do_close): New functions.
	(__spawni): Implement on native Windows.
	* modules/posix_spawn-internal (Depends-on): Add filename,
	concat-filename, findprog-in, malloca, windows-spawn.
	* doc/posix-functions/posix_spawn.texi: Update.
	* doc/posix-functions/posix_spawnp.texi: Likewise.

2020-12-24  Bruno Haible  <bruno@clisp.org>

	windows-spawn: Export another auxiliary function.
	* lib/windows-spawn.h (convert_CreateProcess_error): New declaration.
	* lib/windows-spawn.c (convert_CreateProcess_error): New function,
	extracted from spawnpvech.
	(spawnpvech): Use it.

2020-12-24  Bruno Haible  <bruno@clisp.org>

	windows-spawn: Export some more auxiliary functions.
	* lib/windows-spawn.h: Include <stdbool.h>.
	(struct inheritable_handles): New type.
	(init_inheritable_handles, compose_handles_block,
	free_inheritable_handles): New declarations.
	* lib/windows-spawn.c (init_inheritable_handles, compose_handles_block):
	New functions, based on spawnvech.
	(free_inheritable_handles): New function.
	(spawnpvech): Use them.

2020-12-24  Bruno Haible  <bruno@clisp.org>

	windows-spawn: Export another auxiliary function.
	* lib/windows-spawn.h (compose_envblock): New declaration.
	* lib/windows-spawn.c (compose_envblock): New function, extracted from
	spawnpvech.
	(spawnpvech): Use it.

2020-12-24  Bruno Haible  <bruno@clisp.org>

	windows-spawn: Export an auxiliary function.
	* lib/windows-spawn.h (compose_command): New declaration.
	* lib/windows-spawn.c (compose_command): New function, extracted from
	spawnpvech.
	(spawnpvech): Use it.

2020-12-24  Bruno Haible  <bruno@clisp.org>

	posix_spawn* tests: Add support for native Windows.
	* tests/test-posix_spawn-open1.c (DATA_FILENAME): Treat native Windows
	like Cygwin.
	* tests/test-posix_spawn-dup2-stdin.c (main): Don't assume the signals
	SIGHUP and SIGPIPE. On native Windows, don't call
	posix_spawnattr_setsigmask.
	* tests/test-posix_spawn-dup2-stdout.c (main): Likewise.
	* tests/test-posix_spawn-fchdir.c (main): Likewise.
	* tests/test-posix_spawn-chdir.c (test): Likewise. Accept the child
	output from Cygwin's 'pwd' program.
	* tests/test-posix_spawn-script.c (main): On native Windows, skip the
	executable-shell-script part of the test.
	* tests/test-posix_spawnp-script.c (main): Likewise.
	* modules/posix_spawn-tests (Depends-on): Add freopen, waitpid.
	(configure.ac): Don't define the POSIX_SPAWN_PORTED conditional.
	(Makefile.am): Don't test the POSIX_SPAWN_PORTED conditional.
	* modules/posix_spawnp-tests (Depends-on): Add  waitpid.
	(configure.ac): Don't define the POSIX_SPAWN_PORTED conditional.
	(Makefile.am): Don't test the POSIX_SPAWN_PORTED conditional.
	* modules/posix_spawn_file_actions_addchdir-tests (Makefile.am): Don't
	test the POSIX_SPAWN_PORTED conditional.
	* modules/posix_spawn_file_actions_addfchdir-tests (configure.ac):
	Define the POSIX_SPAWN_PORTED conditional here.

2020-12-24  Bruno Haible  <bruno@clisp.org>

	sh-filename: Add support for native Windows.
	* m4/sh-filename.m4 (gl_SH_FILENAME): Treat native Windows like Cygwin.


[-- Attachment #2: 0001-sh-filename-Add-support-for-native-Windows.patch --]
[-- Type: text/x-patch, Size: 1742 bytes --]

From c9ed8d9a58bae5a90fc1410232354cf8e27e2e3c Mon Sep 17 00:00:00 2001
From: Bruno Haible <bruno@clisp.org>
Date: Thu, 24 Dec 2020 22:13:49 +0100
Subject: [PATCH 1/7] sh-filename: Add support for native Windows.

* m4/sh-filename.m4 (gl_SH_FILENAME): Treat native Windows like Cygwin.
---
 ChangeLog         | 5 +++++
 m4/sh-filename.m4 | 6 ++++--
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 5b05bb3..d93aa47 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2020-12-24  Bruno Haible  <bruno@clisp.org>
+
+	sh-filename: Add support for native Windows.
+	* m4/sh-filename.m4 (gl_SH_FILENAME): Treat native Windows like Cygwin.
+
 2020-12-24  Paul Eggert  <eggert@cs.ucla.edu>
 
 	careadlinkat: improve warning line number
diff --git a/m4/sh-filename.m4 b/m4/sh-filename.m4
index f7b3154..63bbd67 100644
--- a/m4/sh-filename.m4
+++ b/m4/sh-filename.m4
@@ -1,4 +1,4 @@
-# sh-filename.m4 serial 2
+# sh-filename.m4 serial 3
 dnl Copyright (C) 2018-2020 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -10,8 +10,10 @@ AC_DEFUN([gl_SH_FILENAME],
 [
   AH_VERBATIM([SH_FILENAME],
 [/* File name of the Bourne shell.  */
-#if defined __CYGWIN__ || defined __ANDROID__
+#if (defined _WIN32 && !defined __CYGWIN__) || defined __CYGWIN__ || defined __ANDROID__
 /* Omit the directory part because
+   - For native Windows programs in a Cygwin environment, the Cygwin mounts
+     are not visible.
    - For 32-bit Cygwin programs in a 64-bit Cygwin environment, the Cygwin
      mounts are not visible.
    - On Android, /bin/sh does not exist. It's /system/bin/sh instead.  */
-- 
2.7.4


[-- Attachment #3: 0002-posix_spawn-tests-Add-support-for-native-Windows.patch --]
[-- Type: text/x-patch, Size: 16415 bytes --]

From 8cf7746a2b5c5fe50e8369951619a87ecb3786bd Mon Sep 17 00:00:00 2001
From: Bruno Haible <bruno@clisp.org>
Date: Thu, 24 Dec 2020 22:14:38 +0100
Subject: [PATCH 2/7] posix_spawn* tests: Add support for native Windows.

* tests/test-posix_spawn-open1.c (DATA_FILENAME): Treat native Windows
like Cygwin.
* tests/test-posix_spawn-dup2-stdin.c (main): Don't assume the signals
SIGHUP and SIGPIPE. On native Windows, don't call
posix_spawnattr_setsigmask.
* tests/test-posix_spawn-dup2-stdout.c (main): Likewise.
* tests/test-posix_spawn-fchdir.c (main): Likewise.
* tests/test-posix_spawn-chdir.c (test): Likewise. Accept the child
output from Cygwin's 'pwd' program.
* tests/test-posix_spawn-script.c (main): On native Windows, skip the
executable-shell-script part of the test.
* tests/test-posix_spawnp-script.c (main): Likewise.
* modules/posix_spawn-tests (Depends-on): Add freopen, waitpid.
(configure.ac): Don't define the POSIX_SPAWN_PORTED conditional.
(Makefile.am): Don't test the POSIX_SPAWN_PORTED conditional.
* modules/posix_spawnp-tests (Depends-on): Add  waitpid.
(configure.ac): Don't define the POSIX_SPAWN_PORTED conditional.
(Makefile.am): Don't test the POSIX_SPAWN_PORTED conditional.
* modules/posix_spawn_file_actions_addchdir-tests (Makefile.am): Don't
test the POSIX_SPAWN_PORTED conditional.
* modules/posix_spawn_file_actions_addfchdir-tests (configure.ac):
Define the POSIX_SPAWN_PORTED conditional here.
---
 ChangeLog                                        | 26 +++++++++++++++++
 modules/posix_spawn-tests                        | 12 ++------
 modules/posix_spawn_file_actions_addchdir-tests  | 13 ++++-----
 modules/posix_spawn_file_actions_addfchdir-tests |  8 ++++++
 modules/posix_spawnp-tests                       | 11 +-------
 tests/test-posix_spawn-chdir.c                   | 36 ++++++++++++++++++------
 tests/test-posix_spawn-dup2-stdin.c              | 11 +++++++-
 tests/test-posix_spawn-dup2-stdout.c             | 13 +++++++--
 tests/test-posix_spawn-fchdir.c                  | 13 +++++++--
 tests/test-posix_spawn-open1.c                   |  5 ++--
 tests/test-posix_spawn-script.c                  |  7 +++++
 tests/test-posix_spawnp-script.c                 |  7 +++++
 12 files changed, 120 insertions(+), 42 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index d93aa47..b716a26 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,31 @@
 2020-12-24  Bruno Haible  <bruno@clisp.org>
 
+	posix_spawn* tests: Add support for native Windows.
+	* tests/test-posix_spawn-open1.c (DATA_FILENAME): Treat native Windows
+	like Cygwin.
+	* tests/test-posix_spawn-dup2-stdin.c (main): Don't assume the signals
+	SIGHUP and SIGPIPE. On native Windows, don't call
+	posix_spawnattr_setsigmask.
+	* tests/test-posix_spawn-dup2-stdout.c (main): Likewise.
+	* tests/test-posix_spawn-fchdir.c (main): Likewise.
+	* tests/test-posix_spawn-chdir.c (test): Likewise. Accept the child
+	output from Cygwin's 'pwd' program.
+	* tests/test-posix_spawn-script.c (main): On native Windows, skip the
+	executable-shell-script part of the test.
+	* tests/test-posix_spawnp-script.c (main): Likewise.
+	* modules/posix_spawn-tests (Depends-on): Add freopen, waitpid.
+	(configure.ac): Don't define the POSIX_SPAWN_PORTED conditional.
+	(Makefile.am): Don't test the POSIX_SPAWN_PORTED conditional.
+	* modules/posix_spawnp-tests (Depends-on): Add  waitpid.
+	(configure.ac): Don't define the POSIX_SPAWN_PORTED conditional.
+	(Makefile.am): Don't test the POSIX_SPAWN_PORTED conditional.
+	* modules/posix_spawn_file_actions_addchdir-tests (Makefile.am): Don't
+	test the POSIX_SPAWN_PORTED conditional.
+	* modules/posix_spawn_file_actions_addfchdir-tests (configure.ac):
+	Define the POSIX_SPAWN_PORTED conditional here.
+
+2020-12-24  Bruno Haible  <bruno@clisp.org>
+
 	sh-filename: Add support for native Windows.
 	* m4/sh-filename.m4 (gl_SH_FILENAME): Treat native Windows like Cygwin.
 
diff --git a/modules/posix_spawn-tests b/modules/posix_spawn-tests
index 06a8434..ee505c0 100644
--- a/modules/posix_spawn-tests
+++ b/modules/posix_spawn-tests
@@ -18,19 +18,12 @@ unistd
 sys_wait
 environ
 fflush
+freopen
+waitpid
 
 configure.ac:
-AC_EGREP_CPP([notposix], [[
-#if defined _MSC_VER || defined __MINGW32__
-  notposix
-#endif
-  ]],
-  [posix_spawn_ported=no],
-  [posix_spawn_ported=yes])
-AM_CONDITIONAL([POSIX_SPAWN_PORTED], [test $posix_spawn_ported = yes])
 
 Makefile.am:
-if POSIX_SPAWN_PORTED
 TESTS += \
   test-posix_spawn-open1 \
   test-posix_spawn-open2 \
@@ -44,4 +37,3 @@ check_PROGRAMS += \
   test-posix_spawn-inherit1 \
   test-posix_spawn-script
 test_posix_spawn_script_CPPFLAGS = $(AM_CPPFLAGS) -DSRCDIR=\"$(srcdir)/\"
-endif
diff --git a/modules/posix_spawn_file_actions_addchdir-tests b/modules/posix_spawn_file_actions_addchdir-tests
index 400c1f7..1c56b1c 100644
--- a/modules/posix_spawn_file_actions_addchdir-tests
+++ b/modules/posix_spawn_file_actions_addchdir-tests
@@ -12,10 +12,9 @@ findprog
 configure.ac:
 
 Makefile.am:
-TESTS += test-posix_spawn_file_actions_addchdir
-check_PROGRAMS += test-posix_spawn_file_actions_addchdir
-
-if POSIX_SPAWN_PORTED
-TESTS += test-posix_spawn-chdir
-check_PROGRAMS += test-posix_spawn-chdir
-endif
+TESTS += \
+  test-posix_spawn_file_actions_addchdir \
+  test-posix_spawn-chdir
+check_PROGRAMS += \
+  test-posix_spawn_file_actions_addchdir \
+  test-posix_spawn-chdir
diff --git a/modules/posix_spawn_file_actions_addfchdir-tests b/modules/posix_spawn_file_actions_addfchdir-tests
index 0d8c990..45f4230 100644
--- a/modules/posix_spawn_file_actions_addfchdir-tests
+++ b/modules/posix_spawn_file_actions_addfchdir-tests
@@ -10,6 +10,14 @@ posix_spawnp-tests
 findprog
 
 configure.ac:
+AC_EGREP_CPP([notposix], [[
+#if defined _MSC_VER || defined __MINGW32__
+  notposix
+#endif
+  ]],
+  [posix_spawn_ported=no],
+  [posix_spawn_ported=yes])
+AM_CONDITIONAL([POSIX_SPAWN_PORTED], [test $posix_spawn_ported = yes])
 
 Makefile.am:
 TESTS += test-posix_spawn_file_actions_addfchdir
diff --git a/modules/posix_spawnp-tests b/modules/posix_spawnp-tests
index 887569c..e84d89d 100644
--- a/modules/posix_spawnp-tests
+++ b/modules/posix_spawnp-tests
@@ -26,19 +26,11 @@ dup
 environ
 sh-filename
 sigprocmask
+waitpid
 
 configure.ac:
-AC_EGREP_CPP([notposix], [[
-#if defined _MSC_VER || defined __MINGW32__
-  notposix
-#endif
-  ]],
-  [posix_spawn_ported=no],
-  [posix_spawn_ported=yes])
-AM_CONDITIONAL([POSIX_SPAWN_PORTED], [test $posix_spawn_ported = yes])
 
 Makefile.am:
-if POSIX_SPAWN_PORTED
 TESTS += \
   test-posix_spawn-dup2-stdout \
   test-posix_spawn-dup2-stdin \
@@ -63,4 +55,3 @@ test-posix_spawn-dup2-stdin.sh: test-posix_spawn-dup2-stdin.in.sh
 MOSTLYCLEANFILES += test-posix_spawn-dup2-stdin.sh test-posix_spawn-dup2-stdin.sh-t
 
 test_posix_spawnp_script_CPPFLAGS = $(AM_CPPFLAGS) -DSRCDIR=\"$(srcdir)/\"
-endif
diff --git a/tests/test-posix_spawn-chdir.c b/tests/test-posix_spawn-chdir.c
index 91a5497..62c60ee 100644
--- a/tests/test-posix_spawn-chdir.c
+++ b/tests/test-posix_spawn-chdir.c
@@ -64,6 +64,7 @@ test (const char *pwd_prog)
   int fd;
   FILE *fp;
   char line[80];
+  int line_len;
   int status;
   int exitstatus;
 
@@ -76,8 +77,12 @@ test (const char *pwd_prog)
   sigemptyset (&fatal_signal_set);
   sigaddset (&fatal_signal_set, SIGINT);
   sigaddset (&fatal_signal_set, SIGTERM);
+  #ifdef SIGHUP
   sigaddset (&fatal_signal_set, SIGHUP);
+  #endif
+  #ifdef SIGPIPE
   sigaddset (&fatal_signal_set, SIGPIPE);
+  #endif
   sigprocmask (SIG_BLOCK, &fatal_signal_set, NULL);
   actions_allocated = false;
   attrs_allocated = false;
@@ -90,8 +95,13 @@ test (const char *pwd_prog)
           || (err = posix_spawn_file_actions_addchdir (&actions, "/")) != 0
           || (err = posix_spawnattr_init (&attrs)) != 0
           || (attrs_allocated = true,
+              #if defined _WIN32 && !defined __CYGWIN__
+              0
+              #else
               (err = posix_spawnattr_setsigmask (&attrs, &blocked_signals)) != 0
-              || (err = posix_spawnattr_setflags (&attrs, POSIX_SPAWN_SETSIGMASK)) != 0)
+              || (err = posix_spawnattr_setflags (&attrs, POSIX_SPAWN_SETSIGMASK)) != 0
+              #endif
+             )
           || (err = posix_spawnp (&child, pwd_prog, &actions, &attrs, argv, environ)) != 0))
     {
       if (actions_allocated)
@@ -108,22 +118,32 @@ test (const char *pwd_prog)
   sigprocmask (SIG_UNBLOCK, &fatal_signal_set, NULL);
   close (ifd[1]);
   fd = ifd[0];
-  fp = fdopen (fd, "r");
+  fp = fdopen (fd, "rb");
   if (fp == NULL)
     {
       fprintf (stderr, "fdopen() failed\n");
       exit (1);
     }
-  if (fread (line, 1, 80, fp) < 2)
+  line_len = fread (line, 1, 80, fp);
+  if (line_len < 2)
     {
       fprintf (stderr, "could not read expected output\n");
       exit (1);
     }
-  if (memcmp (line, "/\n", 2) != 0)
-    {
-      fprintf (stderr, "read output is not the expected output");
-      exit (1);
-    }
+  if (!(line_len == 2 && memcmp (line, "/\n", 2) == 0))
+#if defined _WIN32 && !defined __CYGWIN__
+    /* If the pwd program is Cygwin's pwd, its output in the root directory is
+       "/cygdrive/N", where N is a lowercase letter.  */
+    if (!(line_len > 11
+          && memcmp (line, "/cygdrive/", 10) == 0
+          && line[10] >= 'a' && line[10] <= 'z'
+          && ((line_len == 12 && line[11] == '\n')
+              || (line_len == 13 && line[11] == '\r' && line[12] == '\n'))))
+#endif
+      {
+        fprintf (stderr, "read output is not the expected output\n");
+        exit (1);
+      }
   fclose (fp);
   status = 0;
   while (waitpid (child, &status, 0) != child)
diff --git a/tests/test-posix_spawn-dup2-stdin.c b/tests/test-posix_spawn-dup2-stdin.c
index 8af1e3f..fe039bf 100644
--- a/tests/test-posix_spawn-dup2-stdin.c
+++ b/tests/test-posix_spawn-dup2-stdin.c
@@ -76,8 +76,12 @@ main ()
   sigemptyset (&fatal_signal_set);
   sigaddset (&fatal_signal_set, SIGINT);
   sigaddset (&fatal_signal_set, SIGTERM);
+  #ifdef SIGHUP
   sigaddset (&fatal_signal_set, SIGHUP);
+  #endif
+  #ifdef SIGPIPE
   sigaddset (&fatal_signal_set, SIGPIPE);
+  #endif
   sigprocmask (SIG_BLOCK, &fatal_signal_set, NULL);
   actions_allocated = false;
   attrs_allocated = false;
@@ -88,8 +92,13 @@ main ()
           || (err = posix_spawn_file_actions_addclose (&actions, ofd[1])) != 0
           || (err = posix_spawnattr_init (&attrs)) != 0
           || (attrs_allocated = true,
+              #if defined _WIN32 && !defined __CYGWIN__
+              0
+              #else
               (err = posix_spawnattr_setsigmask (&attrs, &blocked_signals)) != 0
-              || (err = posix_spawnattr_setflags (&attrs, POSIX_SPAWN_SETSIGMASK)) != 0)
+              || (err = posix_spawnattr_setflags (&attrs, POSIX_SPAWN_SETSIGMASK)) != 0
+              #endif
+             )
           || (err = posix_spawnp (&child, BOURNE_SHELL, &actions, &attrs, argv, environ)) != 0))
     {
       if (actions_allocated)
diff --git a/tests/test-posix_spawn-dup2-stdout.c b/tests/test-posix_spawn-dup2-stdout.c
index c67e143..ce09844 100644
--- a/tests/test-posix_spawn-dup2-stdout.c
+++ b/tests/test-posix_spawn-dup2-stdout.c
@@ -98,8 +98,12 @@ main ()
   sigemptyset (&fatal_signal_set);
   sigaddset (&fatal_signal_set, SIGINT);
   sigaddset (&fatal_signal_set, SIGTERM);
+  #ifdef SIGHUP
   sigaddset (&fatal_signal_set, SIGHUP);
+  #endif
+  #ifdef SIGPIPE
   sigaddset (&fatal_signal_set, SIGPIPE);
+  #endif
   sigprocmask (SIG_BLOCK, &fatal_signal_set, NULL);
   actions_allocated = false;
   attrs_allocated = false;
@@ -111,8 +115,13 @@ main ()
           || (err = posix_spawn_file_actions_addopen (&actions, STDIN_FILENO, "/dev/null", O_RDONLY, 0)) != 0
           || (err = posix_spawnattr_init (&attrs)) != 0
           || (attrs_allocated = true,
+              #if defined _WIN32 && !defined __CYGWIN__
+              0
+              #else
               (err = posix_spawnattr_setsigmask (&attrs, &blocked_signals)) != 0
-              || (err = posix_spawnattr_setflags (&attrs, POSIX_SPAWN_SETSIGMASK)) != 0)
+              || (err = posix_spawnattr_setflags (&attrs, POSIX_SPAWN_SETSIGMASK)) != 0
+              #endif
+             )
           || (err = posix_spawnp (&child, BOURNE_SHELL, &actions, &attrs, argv, environ)) != 0))
     {
       if (actions_allocated)
@@ -142,7 +151,7 @@ main ()
     }
   if (memcmp (line, "Halle Potta", 11) != 0)
     {
-      fprintf (stderr, "read output is not the expected output");
+      fprintf (stderr, "read output is not the expected output\n");
       exit (1);
     }
   fclose (fp);
diff --git a/tests/test-posix_spawn-fchdir.c b/tests/test-posix_spawn-fchdir.c
index c5ca2a3..3206ad5 100644
--- a/tests/test-posix_spawn-fchdir.c
+++ b/tests/test-posix_spawn-fchdir.c
@@ -83,8 +83,12 @@ test (const char *pwd_prog)
   sigemptyset (&fatal_signal_set);
   sigaddset (&fatal_signal_set, SIGINT);
   sigaddset (&fatal_signal_set, SIGTERM);
+  #ifdef SIGHUP
   sigaddset (&fatal_signal_set, SIGHUP);
+  #endif
+  #ifdef SIGPIPE
   sigaddset (&fatal_signal_set, SIGPIPE);
+  #endif
   sigprocmask (SIG_BLOCK, &fatal_signal_set, NULL);
   actions_allocated = false;
   attrs_allocated = false;
@@ -97,8 +101,13 @@ test (const char *pwd_prog)
           || (err = posix_spawn_file_actions_addfchdir (&actions, rootfd)) != 0
           || (err = posix_spawnattr_init (&attrs)) != 0
           || (attrs_allocated = true,
+              #if defined _WIN32 && !defined __CYGWIN__
+              0
+              #else
               (err = posix_spawnattr_setsigmask (&attrs, &blocked_signals)) != 0
-              || (err = posix_spawnattr_setflags (&attrs, POSIX_SPAWN_SETSIGMASK)) != 0)
+              || (err = posix_spawnattr_setflags (&attrs, POSIX_SPAWN_SETSIGMASK)) != 0
+              #endif
+             )
           || (err = posix_spawnp (&child, pwd_prog, &actions, &attrs, argv, environ)) != 0))
     {
       if (actions_allocated)
@@ -128,7 +137,7 @@ test (const char *pwd_prog)
     }
   if (memcmp (line, "/\n", 2) != 0)
     {
-      fprintf (stderr, "read output is not the expected output");
+      fprintf (stderr, "read output is not the expected output\n");
       exit (1);
     }
   fclose (fp);
diff --git a/tests/test-posix_spawn-open1.c b/tests/test-posix_spawn-open1.c
index e08c113..648fc1d 100644
--- a/tests/test-posix_spawn-open1.c
+++ b/tests/test-posix_spawn-open1.c
@@ -40,8 +40,9 @@ SIGNATURE_CHECK (posix_spawn, int, (pid_t *, char const *,
 
 #define CHILD_PROGRAM_FILENAME "test-posix_spawn-open1"
 #define DATA_FILENAME "t!#$%&'()*+,-;=?@[\\]^_`{|}~.tmp"
-/* On Cygwin, '*' '?' '\\' '|' cannot be used in file names.  */
-#if defined __CYGWIN__
+/* On Windows (including Cygwin), '*' '?' '\\' '|' cannot be used in file
+   names.  */
+#if defined _WIN32 || defined __CYGWIN__
 # undef DATA_FILENAME
 # define DATA_FILENAME "t!#$%&'()+,-;=@[]^_`{}~.tmp"
 #endif
diff --git a/tests/test-posix_spawn-script.c b/tests/test-posix_spawn-script.c
index e17c3b8..6fa8e48 100644
--- a/tests/test-posix_spawn-script.c
+++ b/tests/test-posix_spawn-script.c
@@ -90,6 +90,12 @@ main ()
       }
   }
 
+#if defined _WIN32 && !defined __CYGWIN__
+  /* On native Windows, scripts - even with '#!' marker - are not executable.
+     Only .bat and .cmd files are.  */
+  fprintf (stderr, "Skipping test: scripts are not executable on this platform.\n");
+  return 77;
+#else
   {
     const char *prog_path = SRCDIR "executable-shell-script";
     const char *prog_argv[2] = { prog_path, NULL };
@@ -141,6 +147,7 @@ main ()
         return 1;
       }
   }
+#endif
 
   /* Clean up data file.  */
   unlink (DATA_FILENAME);
diff --git a/tests/test-posix_spawnp-script.c b/tests/test-posix_spawnp-script.c
index b48e791..c2c3e57 100644
--- a/tests/test-posix_spawnp-script.c
+++ b/tests/test-posix_spawnp-script.c
@@ -90,6 +90,12 @@ main ()
       }
   }
 
+#if defined _WIN32 && !defined __CYGWIN__
+  /* On native Windows, scripts - even with '#!' marker - are not executable.
+     Only .bat and .cmd files are.  */
+  fprintf (stderr, "Skipping test: scripts are not executable on this platform.\n");
+  return 77;
+#else
   {
     const char *prog_path = SRCDIR "executable-shell-script";
     const char *prog_argv[2] = { prog_path, NULL };
@@ -141,6 +147,7 @@ main ()
         return 1;
       }
   }
+#endif
 
   /* Clean up data file.  */
   unlink (DATA_FILENAME);
-- 
2.7.4


[-- Attachment #4: 0003-windows-spawn-Export-an-auxiliary-function.patch --]
[-- Type: text/x-patch, Size: 4293 bytes --]

From f8199b3ae12a94f79994e1c1e2389fe024ffa6fb Mon Sep 17 00:00:00 2001
From: Bruno Haible <bruno@clisp.org>
Date: Thu, 24 Dec 2020 22:18:10 +0100
Subject: [PATCH 3/7] windows-spawn: Export an auxiliary function.

* lib/windows-spawn.h (compose_command): New declaration.
* lib/windows-spawn.c (compose_command): New function, extracted from
spawnpvech.
(spawnpvech): Use it.
---
 ChangeLog           |  8 ++++++
 lib/windows-spawn.c | 74 ++++++++++++++++++++++++++++++++---------------------
 lib/windows-spawn.h |  7 +++++
 3 files changed, 60 insertions(+), 29 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index b716a26..ba8a7d9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,13 @@
 2020-12-24  Bruno Haible  <bruno@clisp.org>
 
+	windows-spawn: Export an auxiliary function.
+	* lib/windows-spawn.h (compose_command): New declaration.
+	* lib/windows-spawn.c (compose_command): New function, extracted from
+	spawnpvech.
+	(spawnpvech): Use it.
+
+2020-12-24  Bruno Haible  <bruno@clisp.org>
+
 	posix_spawn* tests: Add support for native Windows.
 	* tests/test-posix_spawn-open1.c (DATA_FILENAME): Treat native Windows
 	like Cygwin.
diff --git a/lib/windows-spawn.c b/lib/windows-spawn.c
index 0385b1c..cc29196 100644
--- a/lib/windows-spawn.c
+++ b/lib/windows-spawn.c
@@ -203,6 +203,47 @@ prepare_spawn (const char * const *argv, char **mem_to_free)
   return new_argv;
 }
 
+char *
+compose_command (const char * const *argv)
+{
+  /* 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);
+
+  /* Allocate the block of memory.  */
+  command = (char *) malloc (command_size);
+  if (command == NULL)
+    {
+      errno = ENOMEM;
+      return NULL;
+    }
+
+  /* Fill it.  */
+  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';
+
+  return command;
+}
+
 intptr_t
 spawnpvech (int mode,
             const char *progname, const char * const *argv,
@@ -227,35 +268,10 @@ spawnpvech (int mode,
   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';
-  }
+  /* Compose the command.  */
+  char *command = compose_command (argv);
+  if (command == NULL)
+    goto out_of_memory_1;
 
   /* Copy *ENVP into a contiguous block of memory.  */
   char *envblock;
diff --git a/lib/windows-spawn.h b/lib/windows-spawn.h
index 77720d5..cb9d3fa 100644
--- a/lib/windows-spawn.h
+++ b/lib/windows-spawn.h
@@ -65,6 +65,13 @@
 extern const char ** prepare_spawn (const char * const *argv,
                                     char **mem_to_free);
 
+/* Composes the command to be passed to CreateProcess().
+   ARGV must contain appropriately quoted arguments, as returned by
+   prepare_spawn.
+   Returns a freshly allocated string.  In case of memory allocation failure,
+   NULL is returned, with errno set.  */
+extern char * compose_command (const char * const *argv);
+
 /* Creates a subprocess.
    MODE is either P_WAIT or P_NOWAIT.
    PROGNAME is the program to invoke.
-- 
2.7.4


[-- Attachment #5: 0004-windows-spawn-Export-another-auxiliary-function.patch --]
[-- Type: text/x-patch, Size: 5914 bytes --]

From d1ae9c694e5418850c71884ccd22a293d9b3e29b Mon Sep 17 00:00:00 2001
From: Bruno Haible <bruno@clisp.org>
Date: Thu, 24 Dec 2020 22:18:18 +0100
Subject: [PATCH 4/7] windows-spawn: Export another auxiliary function.

* lib/windows-spawn.h (compose_envblock): New declaration.
* lib/windows-spawn.c (compose_envblock): New function, extracted from
spawnpvech.
(spawnpvech): Use it.
---
 ChangeLog           |  8 +++++
 lib/windows-spawn.c | 96 +++++++++++++++++++++++++++++++++--------------------
 lib/windows-spawn.h |  7 ++++
 3 files changed, 75 insertions(+), 36 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index ba8a7d9..d140497 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,13 @@
 2020-12-24  Bruno Haible  <bruno@clisp.org>
 
+	windows-spawn: Export another auxiliary function.
+	* lib/windows-spawn.h (compose_envblock): New declaration.
+	* lib/windows-spawn.c (compose_envblock): New function, extracted from
+	spawnpvech.
+	(spawnpvech): Use it.
+
+2020-12-24  Bruno Haible  <bruno@clisp.org>
+
 	windows-spawn: Export an auxiliary function.
 	* lib/windows-spawn.h (compose_command): New declaration.
 	* lib/windows-spawn.c (compose_command): New function, extracted from
diff --git a/lib/windows-spawn.c b/lib/windows-spawn.c
index cc29196..b0a6dda 100644
--- a/lib/windows-spawn.c
+++ b/lib/windows-spawn.c
@@ -244,6 +244,65 @@ compose_command (const char * const *argv)
   return command;
 }
 
+char *
+compose_envblock (const char * const *envp)
+{
+  /* This is a bit hairy, because we don't have a lock that would prevent other
+     threads from making modifications in ENVP.  So, just make sure we don't
+     crash; but if other threads are making modifications, part of the result
+     may be wrong.  */
+ 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;
+
+    /* Allocate the block of memory.  */
+    char *envblock = (char *) malloc (envblock_size + 1);
+    if (envblock == NULL)
+      {
+        errno = ENOMEM;
+        return NULL;
+      }
+    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)
+              {
+                free (envblock);
+                errno = ENOMEM;
+                return NULL;
+              }
+            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';
+    return envblock;
+  }
+}
+
 intptr_t
 spawnpvech (int mode,
             const char *progname, const char * const *argv,
@@ -278,45 +337,10 @@ spawnpvech (int mode,
   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);
+      envblock = compose_envblock (envp);
       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
diff --git a/lib/windows-spawn.h b/lib/windows-spawn.h
index cb9d3fa..90f45e1 100644
--- a/lib/windows-spawn.h
+++ b/lib/windows-spawn.h
@@ -72,6 +72,13 @@ extern const char ** prepare_spawn (const char * const *argv,
    NULL is returned, with errno set.  */
 extern char * compose_command (const char * const *argv);
 
+/* Composes the block of memory that contains the environment variables.
+   ENVP must contain an environment (a NULL-terminated array of string of the
+   form VARIABLE=VALUE).
+   Returns a freshly allocated block of memory.  In case of memory allocation
+   failure, NULL is returned, with errno set.  */
+extern char * compose_envblock (const char * const *envp);
+
 /* Creates a subprocess.
    MODE is either P_WAIT or P_NOWAIT.
    PROGNAME is the program to invoke.
-- 
2.7.4


[-- Attachment #6: 0005-windows-spawn-Export-some-more-auxiliary-functions.patch --]
[-- Type: text/x-patch, Size: 20793 bytes --]

From 2f1b8207e13b4bd1ca916626f25d8ac18bbdff33 Mon Sep 17 00:00:00 2001
From: Bruno Haible <bruno@clisp.org>
Date: Thu, 24 Dec 2020 22:18:21 +0100
Subject: [PATCH 5/7] windows-spawn: Export some more auxiliary functions.

* lib/windows-spawn.h: Include <stdbool.h>.
(struct inheritable_handles): New type.
(init_inheritable_handles, compose_handles_block,
free_inheritable_handles): New declarations.
* lib/windows-spawn.c (init_inheritable_handles, compose_handles_block):
New functions, based on spawnvech.
(free_inheritable_handles): New function.
(spawnpvech): Use them.
---
 ChangeLog           |  12 ++
 lib/windows-spawn.c | 394 ++++++++++++++++++++++++++++++++++------------------
 lib/windows-spawn.h |  43 ++++++
 3 files changed, 311 insertions(+), 138 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index d140497..39629f3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,17 @@
 2020-12-24  Bruno Haible  <bruno@clisp.org>
 
+	windows-spawn: Export some more auxiliary functions.
+	* lib/windows-spawn.h: Include <stdbool.h>.
+	(struct inheritable_handles): New type.
+	(init_inheritable_handles, compose_handles_block,
+	free_inheritable_handles): New declarations.
+	* lib/windows-spawn.c (init_inheritable_handles, compose_handles_block):
+	New functions, based on spawnvech.
+	(free_inheritable_handles): New function.
+	(spawnpvech): Use them.
+
+2020-12-24  Bruno Haible  <bruno@clisp.org>
+
 	windows-spawn: Export another auxiliary function.
 	* lib/windows-spawn.h (compose_envblock): New declaration.
 	* lib/windows-spawn.c (compose_envblock): New function, extracted from
diff --git a/lib/windows-spawn.c b/lib/windows-spawn.c
index b0a6dda..a2f63b9 100644
--- a/lib/windows-spawn.c
+++ b/lib/windows-spawn.c
@@ -303,6 +303,229 @@ compose_envblock (const char * const *envp)
   }
 }
 
+int
+init_inheritable_handles (struct inheritable_handles *inh_handles,
+                          bool duplicate)
+{
+  /* Determine the minimal count of handles we need to care about.  */
+  size_t handles_count;
+  {
+    /* _getmaxstdio
+       <https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/getmaxstdio>
+       Default value is 512.  */
+    unsigned int fdmax = _getmaxstdio ();
+    if (fdmax < 3)
+      fdmax = 3;
+    for (; fdmax > 3; 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;
+              }
+          }
+      }
+    handles_count = fdmax;
+  }
+  /* Note: handles_count >= 3.  */
+
+  /* Allocate the arrays.  */
+  size_t handles_allocated = handles_count;
+  HANDLE *handles_array =
+    (HANDLE *) malloc (handles_allocated * sizeof (HANDLE));
+  if (handles_array == NULL)
+    {
+      errno = ENOMEM;
+      return -1;
+    }
+  unsigned char *flags_array =
+    (unsigned char *) malloc (handles_allocated * sizeof (unsigned char));
+  if (flags_array == NULL)
+    {
+      free (handles_array);
+      errno = ENOMEM;
+      return -1;
+    }
+
+  /* Fill in the two arrays.  */
+  {
+    HANDLE curr_process = (duplicate ? GetCurrentProcess () : INVALID_HANDLE_VALUE);
+    unsigned int fd;
+    for (fd = 0; fd < handles_count; fd++)
+      {
+        handles_array[fd] = INVALID_HANDLE_VALUE;
+        /* _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.  */
+                    if (duplicate)
+                      {
+                        if (!DuplicateHandle (curr_process, handle,
+                                              curr_process, &handles_array[fd],
+                                              0, TRUE, DUPLICATE_SAME_ACCESS))
+                          {
+                            unsigned int i;
+                            for (i = 0; i < fd; i++)
+                              if (handles_array[i] != INVALID_HANDLE_VALUE)
+                                CloseHandle (handles_array[i]);
+                            free (flags_array);
+                            free (handles_array);
+                            errno = EBADF; /* arbitrary */
+                            return -1;
+                          }
+                      }
+                    else
+                      handles_array[fd] = handle;
+
+                    flags_array[fd] = 0;
+                  }
+              }
+          }
+      }
+  }
+
+  /* Return the result.  */
+  inh_handles->count = handles_count;
+  inh_handles->allocated = handles_allocated;
+  inh_handles->handles = handles_array;
+  inh_handles->flags = flags_array;
+  return 0;
+}
+
+int
+compose_handles_block (const struct inheritable_handles *inh_handles,
+                       STARTUPINFO *sinfo)
+{
+  /* STARTUPINFO
+     <https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa>  */
+  sinfo->dwFlags = STARTF_USESTDHANDLES;
+  sinfo->hStdInput  = inh_handles->handles[0];
+  sinfo->hStdOutput = inh_handles->handles[1];
+  sinfo->hStdError  = inh_handles->handles[2];
+
+  /* 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,
+       32 for O_APPEND.
+     For unused entries - this may include any of 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.  */
+
+  size_t handles_count = inh_handles->count;
+
+  sinfo->cbReserved2 =
+    sizeof (unsigned int)
+    + handles_count * sizeof (unsigned char)
+    + handles_count * sizeof (HANDLE);
+  /* Add some padding, so that we can work with a properly aligned HANDLE
+     array.  */
+  char *hblock = (char *) malloc (sinfo->cbReserved2 + (sizeof (HANDLE) - 1));
+  if (hblock == NULL)
+    {
+      errno = ENOMEM;
+      return -1;
+    }
+  unsigned char *flags = (unsigned char *) (hblock + sizeof (unsigned int));
+  char *handles = (char *) (flags + handles_count);
+  HANDLE *handles_aligned =
+    (HANDLE *) (((uintptr_t) handles + (sizeof (HANDLE) - 1))
+                & - (uintptr_t) sizeof (HANDLE));
+
+  * (unsigned int *) hblock = handles_count;
+  {
+    unsigned int fd;
+    for (fd = 0; fd < handles_count; fd++)
+      {
+        handles_aligned[fd] = INVALID_HANDLE_VALUE;
+        flags[fd] = 0;
+
+        HANDLE handle = inh_handles->handles[fd];
+        if (handle != INVALID_HANDLE_VALUE
+            /* The first three are possibly already passed above.
+               But they need to passed here as well, if they have some flags.  */
+            && (fd >= 3 || inh_handles->flags[fd] != 0))
+          {
+            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.  */
+                    handles_aligned[fd] = handle;
+                    /* 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.  */
+                    flags[fd] = 1 | inh_handles->flags[fd];
+                    switch (GetFileType (handle))
+                      {
+                      case FILE_TYPE_CHAR:
+                        flags[fd] |= 64;
+                        break;
+                      case FILE_TYPE_PIPE:
+                        flags[fd] |= 8;
+                        break;
+                      default:
+                        break;
+                      }
+                  }
+                else
+                  /* We shouldn't have any non-inheritable handles in
+                     inh_handles->handles.  */
+                  abort ();
+              }
+          }
+      }
+  }
+  if (handles != (char *) handles_aligned)
+    memmove (handles, (char *) handles_aligned, handles_count * sizeof (HANDLE));
+
+  sinfo->lpReserved2 = (BYTE *) hblock;
+
+  return 0;
+}
+
+void
+free_inheritable_handles (struct inheritable_handles *inh_handles)
+{
+  free (inh_handles->flags);
+  free (inh_handles->handles);
+}
+
 intptr_t
 spawnpvech (int mode,
             const char *progname, const char * const *argv,
@@ -343,10 +566,25 @@ spawnpvech (int mode,
         goto out_of_memory_2;
     }
 
+  /* Collect the inheritable handles.  */
+  struct inheritable_handles inh_handles;
+  if (init_inheritable_handles (&inh_handles, false) < 0)
+    {
+      int saved_errno = errno;
+      if (envblock != NULL)
+        free (envblock);
+      free (command);
+      if (resolved_progname != progname)
+        free ((char *) resolved_progname);
+      errno = saved_errno;
+      return -1;
+    }
+  inh_handles.handles[0] = stdin_handle;  inh_handles.flags[0] = 0;
+  inh_handles.handles[1] = stdout_handle; inh_handles.flags[1] = 0;
+  inh_handles.handles[2] = stderr_handle; inh_handles.flags[2] = 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 process_creation_flags = (mode == P_DETACH ? DETACHED_PROCESS : 0);
   /* STARTUPINFO
@@ -356,130 +594,18 @@ spawnpvech (int mode,
   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
+  if (compose_handles_block (&inh_handles, &sinfo) < 0)
+    {
+      int saved_errno = errno;
+      free_inheritable_handles (&inh_handles);
+      if (envblock != NULL)
+        free (envblock);
+      free (command);
+      if (resolved_progname != progname)
+        free ((char *) resolved_progname);
+      errno = saved_errno;
+      return -1;
+    }
 
   PROCESS_INFORMATION pinfo;
   if (!CreateProcess (resolved_progname, command, NULL, NULL, TRUE,
@@ -488,8 +614,8 @@ spawnpvech (int mode,
     {
       DWORD error = GetLastError ();
 
-      if (hblock != NULL)
-        free (hblock);
+      free (sinfo.lpReserved2);
+      free_inheritable_handles (&inh_handles);
       if (envblock != NULL)
         free (envblock);
       free (command);
@@ -537,8 +663,8 @@ spawnpvech (int mode,
 
   if (pinfo.hThread)
     CloseHandle (pinfo.hThread);
-  if (hblock != NULL)
-    free (hblock);
+  free (sinfo.lpReserved2);
+  free_inheritable_handles (&inh_handles);
   if (envblock != NULL)
     free (envblock);
   free (command);
@@ -586,14 +712,6 @@ spawnpvech (int mode,
     }
 
   /*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:
diff --git a/lib/windows-spawn.h b/lib/windows-spawn.h
index 90f45e1..6e06652 100644
--- a/lib/windows-spawn.h
+++ b/lib/windows-spawn.h
@@ -18,12 +18,14 @@
 #ifndef _WINDOWS_SPAWN_H
 #define _WINDOWS_SPAWN_H
 
+#include <stdbool.h>
 #include <stdint.h>
 
 /* 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
@@ -79,6 +81,47 @@ extern char * compose_command (const char * const *argv);
    failure, NULL is returned, with errno set.  */
 extern char * compose_envblock (const char * const *envp);
 
+
+/* This struct keeps track of which handles to pass to a subprocess, and with
+   which flags.  All of the handles here are inheritable.
+   Regarding handle inheritance, see
+   <https://docs.microsoft.com/en-us/windows/win32/sysinfo/handle-inheritance>  */
+struct inheritable_handles
+{
+  /* The number of occupied entries in the two arrays below.
+     3 <= count <= allocated.  */
+  size_t count;
+  /* The number of allocated entries in the two arrays below.  */
+  size_t allocated;
+  /* handles[0..count-1] are the occupied entries.
+     handles[fd] is either INVALID_HANDLE_VALUE or an inheritable handle.  */
+  HANDLE *handles;
+  /* flags[0..count-1] are the occupied entries.
+     flags[fd] is only relevant if handles[fd] != INVALID_HANDLE_VALUE.
+     It is a bit mask consisting of:
+       - 32 for O_APPEND.
+   */
+  unsigned char *flags;
+};
+
+/* Initializes a set of inheritable handles, filling in all inheritable handles
+   assigned to file descriptors.
+   If DUPLICATE is true, the handles stored in the set are duplicates.
+   Returns 0 upon success.  In case of failure, -1 is returned, with errno set.
+ */
+extern int init_inheritable_handles (struct inheritable_handles *inh_handles,
+                                     bool duplicate);
+
+/* Fills a set of inheritable handles into a STARTUPINFO for CreateProcess().
+   Returns 0 upon success.  In case of failure, -1 is returned, with errno set.
+ */
+extern int compose_handles_block (const struct inheritable_handles *inh_handles,
+                                  STARTUPINFO *sinfo);
+
+/* Frees the memory held by a set of inheritable handles.  */
+extern void free_inheritable_handles (struct inheritable_handles *inh_handles);
+
+
 /* Creates a subprocess.
    MODE is either P_WAIT or P_NOWAIT.
    PROGNAME is the program to invoke.
-- 
2.7.4


[-- Attachment #7: 0006-windows-spawn-Export-another-auxiliary-function.patch --]
[-- Type: text/x-patch, Size: 4074 bytes --]

From 48e0a23aa5ddd288856268af751b73218ba31777 Mon Sep 17 00:00:00 2001
From: Bruno Haible <bruno@clisp.org>
Date: Thu, 24 Dec 2020 22:18:27 +0100
Subject: [PATCH 6/7] windows-spawn: Export another auxiliary function.

* lib/windows-spawn.h (convert_CreateProcess_error): New declaration.
* lib/windows-spawn.c (convert_CreateProcess_error): New function,
extracted from spawnpvech.
(spawnpvech): Use it.
---
 ChangeLog           |  8 ++++++
 lib/windows-spawn.c | 77 ++++++++++++++++++++++++++++-------------------------
 lib/windows-spawn.h |  5 ++++
 3 files changed, 54 insertions(+), 36 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 39629f3..fceaaa6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,13 @@
 2020-12-24  Bruno Haible  <bruno@clisp.org>
 
+	windows-spawn: Export another auxiliary function.
+	* lib/windows-spawn.h (convert_CreateProcess_error): New declaration.
+	* lib/windows-spawn.c (convert_CreateProcess_error): New function,
+	extracted from spawnpvech.
+	(spawnpvech): Use it.
+
+2020-12-24  Bruno Haible  <bruno@clisp.org>
+
 	windows-spawn: Export some more auxiliary functions.
 	* lib/windows-spawn.h: Include <stdbool.h>.
 	(struct inheritable_handles): New type.
diff --git a/lib/windows-spawn.c b/lib/windows-spawn.c
index a2f63b9..a781090 100644
--- a/lib/windows-spawn.c
+++ b/lib/windows-spawn.c
@@ -526,6 +526,46 @@ free_inheritable_handles (struct inheritable_handles *inh_handles)
   free (inh_handles->handles);
 }
 
+int
+convert_CreateProcess_error (DWORD error)
+{
+  /* 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:
+      return ENOENT;
+      break;
+
+    case ERROR_ACCESS_DENIED:
+    case ERROR_SHARING_VIOLATION:
+      return EACCES;
+      break;
+
+    case ERROR_OUTOFMEMORY:
+      return ENOMEM;
+      break;
+
+    case ERROR_BUFFER_OVERFLOW:
+    case ERROR_FILENAME_EXCED_RANGE:
+      return ENAMETOOLONG;
+      break;
+
+    case ERROR_BAD_FORMAT:
+    case ERROR_BAD_EXE_FORMAT:
+      return ENOEXEC;
+      break;
+
+    default:
+      return EINVAL;
+      break;
+    }
+}
+
 intptr_t
 spawnpvech (int mode,
             const char *progname, const char * const *argv,
@@ -622,42 +662,7 @@ spawnpvech (int mode,
       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;
-
-        case ERROR_BAD_FORMAT:
-        case ERROR_BAD_EXE_FORMAT:
-          errno = ENOEXEC;
-          break;
-
-        default:
-          errno = EINVAL;
-          break;
-        }
-
+      errno = convert_CreateProcess_error (error);
       return -1;
     }
 
diff --git a/lib/windows-spawn.h b/lib/windows-spawn.h
index 6e06652..1ff76f6 100644
--- a/lib/windows-spawn.h
+++ b/lib/windows-spawn.h
@@ -122,6 +122,11 @@ extern int compose_handles_block (const struct inheritable_handles *inh_handles,
 extern void free_inheritable_handles (struct inheritable_handles *inh_handles);
 
 
+/* Converts a CreateProcess() error code (retrieved through GetLastError()) to
+   an errno value.  */
+extern int convert_CreateProcess_error (DWORD error);
+
+
 /* Creates a subprocess.
    MODE is either P_WAIT or P_NOWAIT.
    PROGNAME is the program to invoke.
-- 
2.7.4


[-- Attachment #8: 0007-posix_spawn-internal-Implement-for-native-Windows.patch --]
[-- Type: text/x-patch, Size: 23914 bytes --]

From f2ca14d1555c7f8de305c21993fd1f805f48a9a7 Mon Sep 17 00:00:00 2001
From: Bruno Haible <bruno@clisp.org>
Date: Thu, 24 Dec 2020 22:18:36 +0100
Subject: [PATCH 7/7] posix_spawn-internal: Implement for native Windows.

* lib/spawni.c (grow_inheritable_handles, shrink_inheritable_handles,
close_inheritable_handles, memiszero, sigisempty, open_handle, do_open,
do_dup2, do_close): New functions.
(__spawni): Implement on native Windows.
* modules/posix_spawn-internal (Depends-on): Add filename,
concat-filename, findprog-in, malloca, windows-spawn.
* doc/posix-functions/posix_spawn.texi: Update.
* doc/posix-functions/posix_spawnp.texi: Likewise.
---
 ChangeLog                             |  12 +
 doc/posix-functions/posix_spawn.texi  |   2 +-
 doc/posix-functions/posix_spawnp.texi |   2 +-
 lib/spawni.c                          | 631 +++++++++++++++++++++++++++++++++-
 modules/posix_spawn-internal          |   5 +
 5 files changed, 645 insertions(+), 7 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index fceaaa6..531c416 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,17 @@
 2020-12-24  Bruno Haible  <bruno@clisp.org>
 
+	posix_spawn-internal: Implement for native Windows.
+	* lib/spawni.c (grow_inheritable_handles, shrink_inheritable_handles,
+	close_inheritable_handles, memiszero, sigisempty, open_handle, do_open,
+	do_dup2, do_close): New functions.
+	(__spawni): Implement on native Windows.
+	* modules/posix_spawn-internal (Depends-on): Add filename,
+	concat-filename, findprog-in, malloca, windows-spawn.
+	* doc/posix-functions/posix_spawn.texi: Update.
+	* doc/posix-functions/posix_spawnp.texi: Likewise.
+
+2020-12-24  Bruno Haible  <bruno@clisp.org>
+
 	windows-spawn: Export another auxiliary function.
 	* lib/windows-spawn.h (convert_CreateProcess_error): New declaration.
 	* lib/windows-spawn.c (convert_CreateProcess_error): New function,
diff --git a/doc/posix-functions/posix_spawn.texi b/doc/posix-functions/posix_spawn.texi
index e0b39b0..d0cf1b9 100644
--- a/doc/posix-functions/posix_spawn.texi
+++ b/doc/posix-functions/posix_spawn.texi
@@ -26,7 +26,7 @@ Portability problems not fixed by Gnulib:
 @itemize
 @item
 This function does not work on some platforms:
-AIX 6.1 (under particular circumstances), mingw.
+AIX 6.1 (under particular circumstances).
 @end itemize
 
 The Gnulib modules @code{posix_spawn_file_actions_addchdir} and
diff --git a/doc/posix-functions/posix_spawnp.texi b/doc/posix-functions/posix_spawnp.texi
index 2dd9f68..9e65191 100644
--- a/doc/posix-functions/posix_spawnp.texi
+++ b/doc/posix-functions/posix_spawnp.texi
@@ -26,7 +26,7 @@ Portability problems not fixed by Gnulib:
 @itemize
 @item
 This function does not work on some platforms:
-AIX 6.1 (under particular circumstances), mingw.
+AIX 6.1 (under particular circumstances).
 @end itemize
 
 The Gnulib modules @code{posix_spawn_file_actions_addchdir} and
diff --git a/lib/spawni.c b/lib/spawni.c
index 182d13f..2f0d25f 100644
--- a/lib/spawni.c
+++ b/lib/spawni.c
@@ -87,16 +87,637 @@
 
 
 #if defined _WIN32 && ! defined __CYGWIN__
-
 /* Native Windows API.  */
+
+/* Get declarations of the native Windows API functions.  */
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+
+# include <stdbool.h>
+# include <stdio.h>
+
+# include "filename.h"
+# include "concat-filename.h"
+# include "findprog.h"
+# include "malloca.h"
+# include "windows-spawn.h"
+
+/* Don't assume that UNICODE is not defined.  */
+# undef CreateFile
+# define CreateFile CreateFileA
+# undef STARTUPINFO
+# define STARTUPINFO STARTUPINFOA
+# undef CreateProcess
+# define CreateProcess CreateProcessA
+
+/* Grows inh_handles->count so that it becomes > newfd.
+   Returns 0 upon success.  In case of failure, -1 is returned, with errno set.
+ */
+static int
+grow_inheritable_handles (struct inheritable_handles *inh_handles, int newfd)
+{
+  if (inh_handles->allocated <= newfd)
+    {
+      size_t new_allocated = 2 * inh_handles->allocated + 1;
+      if (new_allocated <= newfd)
+        new_allocated = newfd + 1;
+      HANDLE *new_handles_array =
+        (HANDLE *)
+        realloc (inh_handles->handles, new_allocated * sizeof (HANDLE));
+      if (new_handles_array == NULL)
+        {
+          errno = ENOMEM;
+          return -1;
+        }
+      unsigned char *new_flags_array =
+        (unsigned char *)
+        realloc (inh_handles->flags, new_allocated * sizeof (unsigned char));
+      if (new_flags_array == NULL)
+        {
+          free (new_handles_array);
+          errno = ENOMEM;
+          return -1;
+        }
+      inh_handles->allocated = new_allocated;
+      inh_handles->handles = new_handles_array;
+      inh_handles->flags = new_flags_array;
+    }
+
+  HANDLE *handles = inh_handles->handles;
+
+  for (; inh_handles->count <= newfd; inh_handles->count++)
+    handles[inh_handles->count] = INVALID_HANDLE_VALUE;
+
+  return 0;
+}
+
+/* Reduces inh_handles->count to the minimum needed.  */
+static void
+shrink_inheritable_handles (struct inheritable_handles *inh_handles)
+{
+  HANDLE *handles = inh_handles->handles;
+
+  while (inh_handles->count > 3
+         && handles[inh_handles->count - 1] == INVALID_HANDLE_VALUE)
+    inh_handles->count--;
+}
+
+/* Closes all handles in inh_handles.  */
+static void
+close_inheritable_handles (struct inheritable_handles *inh_handles)
+{
+  HANDLE *handles = inh_handles->handles;
+  size_t handles_count = inh_handles->count;
+  unsigned int fd;
+
+  for (fd = 0; fd < handles_count; fd++)
+    {
+      HANDLE handle = handles[fd];
+
+      if (handle != INVALID_HANDLE_VALUE)
+        CloseHandle (handle);
+    }
+}
+
+/* Tests whether a memory region, starting at P and N bytes long, contains only
+   zeroes.  */
+static bool
+memiszero (const void *p, size_t n)
+{
+  const char *cp = p;
+  for (; n > 0; cp++, n--)
+    if (*cp != 0)
+      return 0;
+  return 1;
+}
+
+/* Tests whether *S contains no signals.  */
+static bool
+sigisempty (const sigset_t *s)
+{
+  return memiszero (s, sizeof (sigset_t));
+}
+
+/* Opens a HANDLE to a file.
+   Upon failure, returns INVALID_HANDLE_VALUE with errno set.  */
+static HANDLE
+open_handle (const char *name, int flags, mode_t mode)
+{
+  /* To ease portability.  Like in open.c.  */
+  if (strcmp (name, "/dev/null") == 0)
+    name = "NUL";
+
+  /* POSIX <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>
+     specifies: "More than two leading <slash> characters shall be treated as
+     a single <slash> character."  */
+  if (ISSLASH (name[0]) && ISSLASH (name[1]) && ISSLASH (name[2]))
+    {
+      name += 2;
+      while (ISSLASH (name[1]))
+        name++;
+    }
+
+  size_t len = strlen (name);
+  size_t drive_prefix_len = (HAS_DEVICE (name) ? 2 : 0);
+
+  /* Remove trailing slashes (except the very first one, at position
+     drive_prefix_len), but remember their presence.  */
+  size_t rlen;
+  bool check_dir = false;
+
+  rlen = len;
+  while (rlen > drive_prefix_len && ISSLASH (name[rlen-1]))
+    {
+      check_dir = true;
+      if (rlen == drive_prefix_len + 1)
+        break;
+      rlen--;
+    }
+
+  /* Handle '' and 'C:'.  */
+  if (!check_dir && rlen == drive_prefix_len)
+    {
+      errno = ENOENT;
+      return INVALID_HANDLE_VALUE;
+    }
+
+  /* Handle '\\'.  */
+  if (rlen == 1 && ISSLASH (name[0]) && len >= 2)
+    {
+      errno = ENOENT;
+      return INVALID_HANDLE_VALUE;
+    }
+
+  const char *rname;
+  char *malloca_rname;
+  if (rlen == len)
+    {
+      rname = name;
+      malloca_rname = NULL;
+    }
+  else
+    {
+      malloca_rname = malloca (rlen + 1);
+      if (malloca_rname == NULL)
+        {
+          errno = ENOMEM;
+          return INVALID_HANDLE_VALUE;
+        }
+      memcpy (malloca_rname, name, rlen);
+      malloca_rname[rlen] = '\0';
+      rname = malloca_rname;
+    }
+
+  /* For the meaning of the flags, see
+     <https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/open-wopen>  */
+  /* Open a handle to the file.
+     CreateFile
+     <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-createfilea>
+     <https://docs.microsoft.com/en-us/windows/desktop/FileIO/creating-and-opening-files>  */
+  HANDLE handle =
+    CreateFile (rname,
+                ((flags & (O_WRONLY | O_RDWR)) != 0
+                 ? GENERIC_READ | GENERIC_WRITE
+                 : GENERIC_READ),
+                FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+                NULL,
+                ((flags & O_CREAT) != 0
+                 ? ((flags & O_EXCL) != 0
+                    ? CREATE_NEW
+                    : ((flags & O_TRUNC) != 0 ? CREATE_ALWAYS : OPEN_ALWAYS))
+                 : ((flags & O_TRUNC) != 0
+                    ? TRUNCATE_EXISTING
+                    : OPEN_EXISTING)),
+                /* FILE_FLAG_BACKUP_SEMANTICS is useful for opening directories,
+                   which is out-of-scope here.  */
+                /* FILE_FLAG_POSIX_SEMANTICS (treat file names that differ only
+                   in case as different) makes sense only when applied to *all*
+                   filesystem operations.  */
+                /* FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS */
+                FILE_ATTRIBUTE_NORMAL
+                | ((flags & O_TEMPORARY) != 0 ? FILE_FLAG_DELETE_ON_CLOSE : 0)
+                | ((flags & O_SEQUENTIAL ) != 0 ? FILE_FLAG_SEQUENTIAL_SCAN : 0)
+                | ((flags & O_RANDOM) != 0 ? FILE_FLAG_RANDOM_ACCESS : 0),
+                NULL);
+  if (handle == INVALID_HANDLE_VALUE)
+    switch (GetLastError ())
+      {
+      /* Some of these errors probably cannot happen with the specific flags
+         that we pass to CreateFile.  But who knows...  */
+      case ERROR_FILE_NOT_FOUND: /* The last component of rname does not exist.  */
+      case ERROR_PATH_NOT_FOUND: /* Some directory component in rname does not exist.  */
+      case ERROR_BAD_PATHNAME:   /* rname is such as '\\server'.  */
+      case ERROR_BAD_NETPATH:    /* rname is such as '\\nonexistentserver\share'.  */
+      case ERROR_BAD_NET_NAME:   /* rname is such as '\\server\nonexistentshare'.  */
+      case ERROR_INVALID_NAME:   /* rname contains wildcards, misplaced colon, etc.  */
+      case ERROR_DIRECTORY:
+        errno = ENOENT;
+        break;
+
+      case ERROR_ACCESS_DENIED:  /* rname is such as 'C:\System Volume Information\foo'.  */
+      case ERROR_SHARING_VIOLATION: /* rname is such as 'C:\pagefile.sys'.  */
+                                    /* XXX map to EACCES or EPERM? */
+        errno = EACCES;
+        break;
+
+      case ERROR_OUTOFMEMORY:
+        errno = ENOMEM;
+        break;
+
+      case ERROR_WRITE_PROTECT:
+        errno = EROFS;
+        break;
+
+      case ERROR_WRITE_FAULT:
+      case ERROR_READ_FAULT:
+      case ERROR_GEN_FAILURE:
+        errno = EIO;
+        break;
+
+      case ERROR_BUFFER_OVERFLOW:
+      case ERROR_FILENAME_EXCED_RANGE:
+        errno = ENAMETOOLONG;
+        break;
+
+      case ERROR_DELETE_PENDING: /* XXX map to EACCES or EPERM? */
+        errno = EPERM;
+        break;
+
+      default:
+        errno = EINVAL;
+        break;
+      }
+
+  if (malloca_rname != NULL)
+    {
+      int saved_errno = errno;
+      freea (malloca_rname);
+      errno = saved_errno;
+    }
+  return handle;
+}
+
+/* Executes an 'open' action.
+   Returns 0 upon success.  In case of failure, -1 is returned, with errno set.
+ */
+static int
+do_open (struct inheritable_handles *inh_handles, int newfd,
+         const char *filename, const char *directory,
+         int flags, mode_t mode, HANDLE curr_process)
+{
+  if (!(newfd >= 0 && newfd < _getmaxstdio ()))
+    {
+      errno = EBADF;
+      return -1;
+    }
+  if (grow_inheritable_handles (inh_handles, newfd) < 0)
+    return -1;
+  if (inh_handles->handles[newfd] != INVALID_HANDLE_VALUE
+      && !CloseHandle (inh_handles->handles[newfd]))
+    {
+      errno = EIO;
+      return -1;
+    }
+  if (filename == NULL)
+    {
+      errno = EINVAL;
+      return -1;
+    }
+  char *filename_to_free = NULL;
+  if (directory != NULL && IS_RELATIVE_FILE_NAME (filename))
+    {
+      char *real_filename = concatenated_filename (directory, filename, NULL);
+      if (real_filename == NULL)
+        {
+          errno = ENOMEM;
+          return -1;
+        }
+      filename = real_filename;
+      filename_to_free = real_filename;
+    }
+  HANDLE handle = open_handle (filename, flags, mode);
+  if (handle == INVALID_HANDLE_VALUE)
+    {
+      int saved_errno = errno;
+      free (filename_to_free);
+      errno = saved_errno;
+      return -1;
+    }
+  free (filename_to_free);
+  /* Duplicate the handle, so that it becomes inheritable.  */
+  if (!DuplicateHandle (curr_process, handle,
+                        curr_process, &inh_handles->handles[newfd],
+                        0, TRUE,
+                        DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS))
+    {
+      errno = EBADF; /* arbitrary */
+      return -1;
+    }
+  inh_handles->flags[newfd] = ((flags & O_APPEND) != 0 ? 32 : 0);
+  return 0;
+}
+
+/* Executes a 'dup2' action.
+   Returns 0 upon success.  In case of failure, -1 is returned, with errno set.
+ */
+static int
+do_dup2 (struct inheritable_handles *inh_handles, int oldfd, int newfd,
+         HANDLE curr_process)
+{
+  if (!(oldfd >= 0 && oldfd < inh_handles->count
+        && inh_handles->handles[oldfd] != INVALID_HANDLE_VALUE))
+    {
+      errno = EBADF;
+      return -1;
+    }
+  if (!(newfd >= 0 && newfd < _getmaxstdio ()))
+    {
+      errno = EBADF;
+      return -1;
+    }
+  if (newfd != oldfd)
+    {
+      if (grow_inheritable_handles (inh_handles, newfd) < 0)
+        return -1;
+      if (inh_handles->handles[newfd] != INVALID_HANDLE_VALUE
+          && !CloseHandle (inh_handles->handles[newfd]))
+        {
+          errno = EIO;
+          return -1;
+        }
+      /* Duplicate the handle, so that it a forthcoming do_close action on oldfd
+         has no effect on newfd.  */
+      if (!DuplicateHandle (curr_process, inh_handles->handles[oldfd],
+                            curr_process, &inh_handles->handles[newfd],
+                            0, TRUE, DUPLICATE_SAME_ACCESS))
+        {
+          errno = EBADF; /* arbitrary */
+          return -1;
+        }
+      inh_handles->flags[newfd] = 0;
+    }
+  return 0;
+}
+
+/* Executes a 'close' action.
+   Returns 0 upon success.  In case of failure, -1 is returned, with errno set.
+ */
+static int
+do_close (struct inheritable_handles *inh_handles, int fd)
+{
+  if (!(fd >= 0 && fd < inh_handles->count
+        && inh_handles->handles[fd] != INVALID_HANDLE_VALUE))
+    {
+      errno = EBADF;
+      return -1;
+    }
+  if (!CloseHandle (inh_handles->handles[fd]))
+    {
+      errno = EIO;
+      return -1;
+    }
+  inh_handles->handles[fd] = INVALID_HANDLE_VALUE;
+  return 0;
+}
+
 int
-__spawni (pid_t *pid, const char *file,
+__spawni (pid_t *pid, const char *prog_filename,
           const posix_spawn_file_actions_t *file_actions,
-          const posix_spawnattr_t *attrp, const char *const argv[],
+          const posix_spawnattr_t *attrp, const char *const prog_argv[],
           const char *const envp[], int use_path)
 {
-  /* Not yet implemented.  */
-  return ENOSYS;
+  /* Validate the arguments.  */
+  if (prog_filename == NULL
+      || (attrp != NULL
+          && ((attrp->_flags & ~POSIX_SPAWN_SETPGROUP) != 0
+              || attrp->_pgrp != 0
+              || ! sigisempty (&attrp->_sd)
+              || ! sigisempty (&attrp->_ss)
+              || attrp->_sp.sched_priority != 0
+              || attrp->_policy != 0)))
+    return EINVAL;
+
+  /* Process group handling:
+     Native Windows does not have the concept of process group, but it has the
+     concept of a console attached to a process.
+     So, we interpret the three cases as follows:
+       - Flag POSIX_SPAWN_SETPGROUP not set: Means, the child process is in the
+         same process group as the parent process.  We interpret this as a
+         request to reuse the same console.
+       - Flag POSIX_SPAWN_SETPGROUP set with attrp->_pgrp == 0: Means the child
+         process starts a process group of its own.  See
+         <https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_getpgroup.html>
+         <https://pubs.opengroup.org/onlinepubs/9699919799/functions/setpgrp.html>
+         We interpret this as a request to detach from the current console.
+       - Flag POSIX_SPAWN_SETPGROUP set with attrp->_pgrp != 0: Means the child
+         process joins another, existing process group.  See
+         <https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_getpgroup.html>
+         <https://pubs.opengroup.org/onlinepubs/9699919799/functions/setpgid.html>
+         We don't support this case; it produces error EINVAL above.  */
+  /* <https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags>  */
+  DWORD process_creation_flags =
+    (attrp != NULL && (attrp->_flags & POSIX_SPAWN_SETPGROUP) != 0 ? DETACHED_PROCESS : 0);
+
+  char *argv_mem_to_free;
+  const char **argv = prepare_spawn (prog_argv, &argv_mem_to_free);
+  if (argv == NULL)
+    return errno; /* errno is set here */
+  argv++;
+
+  /* Compose the command.  */
+  char *command = compose_command (argv);
+  if (command == NULL)
+    {
+      free (argv_mem_to_free);
+      return ENOMEM;
+    }
+
+  /* Copy *ENVP into a contiguous block of memory.  */
+  char *envblock;
+  if (envp == NULL)
+    envblock = NULL;
+  else
+    {
+      envblock = compose_envblock (envp);
+      if (envblock == NULL)
+        {
+          free (command);
+          free (argv_mem_to_free);
+          return ENOMEM;
+        }
+    }
+
+  /* Set up the array of handles to inherit.
+     Duplicate each handle, so that a spawn_do_close action (below) has no
+     effect on the file descriptors of the current process.  Alternatively,
+     we could store, for each handle, a bit that tells whether it is shared
+     with the current process.  But this is simpler.  */
+  struct inheritable_handles inh_handles;
+  if (init_inheritable_handles (&inh_handles, true) < 0)
+    goto failed_1;
+
+  /* Directory in which to execute the new process.  */
+  const char *directory = NULL;
+
+  /* Execute the file_actions, modifying the inh_handles instead of the
+     file descriptors of the current process.  */
+  if (file_actions != NULL)
+    {
+      HANDLE curr_process = GetCurrentProcess ();
+      int cnt;
+
+      for (cnt = 0; cnt < file_actions->_used; ++cnt)
+        {
+          struct __spawn_action *action = &file_actions->_actions[cnt];
+
+          switch (action->tag)
+            {
+            case spawn_do_close:
+              {
+                int fd = action->action.close_action.fd;
+                if (do_close (&inh_handles, fd) < 0)
+                  goto failed_2;
+              }
+              break;
+
+            case spawn_do_open:
+              {
+                int newfd = action->action.open_action.fd;
+                const char *filename = action->action.open_action.path;
+                int flags = action->action.open_action.oflag;
+                mode_t mode = action->action.open_action.mode;
+                if (do_open (&inh_handles, newfd, filename, directory,
+                             flags, mode, curr_process)
+                    < 0)
+                  goto failed_2;
+              }
+              break;
+
+            case spawn_do_dup2:
+              {
+                int oldfd = action->action.dup2_action.fd;
+                int newfd = action->action.dup2_action.newfd;
+                if (do_dup2 (&inh_handles, oldfd, newfd, curr_process) < 0)
+                  goto failed_2;
+              }
+              break;
+
+            case spawn_do_chdir:
+              {
+                char *newdir = action->action.chdir_action.path;
+                if (directory != NULL && IS_RELATIVE_FILE_NAME (newdir))
+                  {
+                    newdir = concatenated_filename (directory, newdir, NULL);
+                    if (newdir == NULL)
+                      {
+                        errno = ENOMEM;
+                        goto failed_2;
+                      }
+                  }
+                directory = newdir;
+              }
+              break;
+
+            case spawn_do_fchdir:
+              /* Not supported in this implementation.  */
+              errno = EINVAL;
+              goto failed_2;
+            }
+        }
+    }
+
+  /* Reduce inh_handles.count to the minimum needed.  */
+  shrink_inheritable_handles (&inh_handles);
+
+  /* CreateProcess
+     <https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa>  */
+  /* 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;
+  if (compose_handles_block (&inh_handles, &sinfo) < 0)
+    goto failed_2;
+
+  /* Perform the PATH search now, considering the final DIRECTORY.  */
+  char *resolved_prog_filename_to_free = NULL;
+  {
+    const char *resolved_prog_filename =
+      find_in_given_path (prog_filename, use_path ? getenv ("PATH") : "",
+                          directory, false);
+    if (resolved_prog_filename == NULL)
+      goto failed_3;
+    if (resolved_prog_filename != prog_filename)
+      resolved_prog_filename_to_free = (char *) resolved_prog_filename;
+    prog_filename = resolved_prog_filename;
+  }
+
+  PROCESS_INFORMATION pinfo;
+  if (!CreateProcess (prog_filename, command, NULL, NULL, TRUE,
+                      process_creation_flags, envblock, directory, &sinfo,
+                      &pinfo))
+    {
+      DWORD error = GetLastError ();
+
+      free (resolved_prog_filename_to_free);
+      free (sinfo.lpReserved2);
+      close_inheritable_handles (&inh_handles);
+      free_inheritable_handles (&inh_handles);
+      free (envblock);
+      free (command);
+      free (argv_mem_to_free);
+
+      return convert_CreateProcess_error (error);
+    }
+
+  if (pinfo.hThread)
+    CloseHandle (pinfo.hThread);
+
+  free (resolved_prog_filename_to_free);
+  free (sinfo.lpReserved2);
+  close_inheritable_handles (&inh_handles);
+  free_inheritable_handles (&inh_handles);
+  free (envblock);
+  free (command);
+  free (argv_mem_to_free);
+
+  if (pid != NULL)
+    *pid = (intptr_t) pinfo.hProcess;
+  return 0;
+
+ failed_3:
+  {
+    int saved_errno = errno;
+    free (sinfo.lpReserved2);
+    close_inheritable_handles (&inh_handles);
+    free_inheritable_handles (&inh_handles);
+    free (envblock);
+    free (command);
+    free (argv_mem_to_free);
+    return saved_errno;
+  }
+
+ failed_2:
+  {
+    int saved_errno = errno;
+    close_inheritable_handles (&inh_handles);
+    free_inheritable_handles (&inh_handles);
+    free (envblock);
+    free (command);
+    free (argv_mem_to_free);
+    return saved_errno;
+  }
+
+ failed_1:
+  {
+    int saved_errno = errno;
+    free (envblock);
+    free (command);
+    free (argv_mem_to_free);
+    return saved_errno;
+  }
 }
 
 #else
diff --git a/modules/posix_spawn-internal b/modules/posix_spawn-internal
index 920b4c4..2aeb019 100644
--- a/modules/posix_spawn-internal
+++ b/modules/posix_spawn-internal
@@ -15,6 +15,11 @@ open
 sh-filename
 strchrnul
 unistd
+filename        [test $HAVE_POSIX_SPAWN = 0]
+concat-filename [test $HAVE_POSIX_SPAWN = 0]
+findprog-in     [test $HAVE_POSIX_SPAWN = 0]
+malloca         [test $HAVE_POSIX_SPAWN = 0]
+windows-spawn   [test $HAVE_POSIX_SPAWN = 0]
 
 configure.ac:
 gl_POSIX_SPAWN
-- 
2.7.4


^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2020-12-24 22:03 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-12-24 22:03 posix_spawn[p]: implement for native Windows Bruno Haible

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).