>From c5388bad32e897c410336d6a3d8258cea2e9a6ad Mon Sep 17 00:00:00 2001 From: Bruno Haible Date: Mon, 30 Nov 2020 17:58:57 +0100 Subject: [PATCH 2/2] execute, spawn-pipe: Make multithread-safe on native Windows. * lib/windows-spawn.h: Include , . (dup_safer_noinherit, undup_safer_noinherit): Remove declarations. (spawnpvech): New declaration. * lib/windows-spawn.c: Include , , findprog.h. Don't include , cloexec.h, error.h, gettext.h. (_): Remove macro. (dup_noinherit, fd_safer_noinherit, dup_safer_noinherit, undup_safer_noinherit): Remove functions. (spawnpvech): New function. * modules/windows-spawn (Depends-on): Add findprog-in, stdint. Remove cloexec, dup2, error, gettext-h. * lib/execute.c: Include msvc-nothrow.h. (execute) [WIN32]: Use _get_osfhandle, spawnpvech instead of _spawnvpe. * lib/spawn-pipe.c: Include msvc-nothrow.h. (create_pipe) [WIN32]: Use _get_osfhandle, DuplicateHandle, spawnpvech instead of _spawnvpe. * modules/execute (Depends-on): Add msvc-nothrow. * modules/spawn-pipe (Depends-on): Likewise. --- ChangeLog | 22 +++ lib/execute.c | 88 ++++------- lib/spawn-pipe.c | 122 +++++++++++++-- lib/windows-spawn.c | 427 ++++++++++++++++++++++++++++++++++++++++++-------- lib/windows-spawn.h | 34 +++- modules/execute | 5 +- modules/spawn-pipe | 1 + modules/windows-spawn | 6 +- 8 files changed, 558 insertions(+), 147 deletions(-) diff --git a/ChangeLog b/ChangeLog index ccb436f..774dbee 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,27 @@ 2020-11-30 Bruno Haible + execute, spawn-pipe: Make multithread-safe on native Windows. + * lib/windows-spawn.h: Include , . + (dup_safer_noinherit, undup_safer_noinherit): Remove declarations. + (spawnpvech): New declaration. + * lib/windows-spawn.c: Include , , findprog.h. + Don't include , 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 + execute, spawn-pipe: Improve documentation. * lib/execute.h: Describe progname, prog_path, prog_argv. * lib/spawn-pipe.h: Likewise. diff --git a/lib/execute.c b/lib/execute.c index 41e1e92..149cc89 100644 --- a/lib/execute.c +++ b/lib/execute.c @@ -38,6 +38,11 @@ #if defined _WIN32 && ! defined __CYGWIN__ /* Native Windows API. */ +# if GNULIB_MSVC_NOTHROW +# include "msvc-nothrow.h" +# else +# include +# endif # include # include "windows-spawn.h" @@ -86,12 +91,6 @@ nonintr_open (const char *pathname, int oflag, mode_t mode) #endif -/* Execute a command, optionally redirecting any of the three standard file - descriptors to /dev/null. Return its exit code. - If it didn't terminate correctly, exit if exit_on_error is true, otherwise - return 127. - If slave_process is true, the child process will be terminated when its - creator receives a catchable fatal signal. */ int execute (const char *progname, const char *prog_path, char **prog_argv, @@ -103,63 +102,46 @@ execute (const char *progname, #if defined _WIN32 && ! defined __CYGWIN__ /* Native Windows API. */ - int orig_stdin; - int orig_stdout; - int orig_stderr; - int exitcode; - int nullinfd; - int nulloutfd; /* FIXME: Need to free memory allocated by prepare_spawn. */ prog_argv = prepare_spawn (prog_argv); - /* Save standard file handles of parent process. */ - if (null_stdin) - orig_stdin = dup_safer_noinherit (STDIN_FILENO); - if (null_stdout) - orig_stdout = dup_safer_noinherit (STDOUT_FILENO); - if (null_stderr) - orig_stderr = dup_safer_noinherit (STDERR_FILENO); - exitcode = -1; + int exitcode = -1; /* Create standard file handles of child process. */ - nullinfd = -1; - nulloutfd = -1; + int nullinfd = -1; + int nulloutfd = -1; if ((!null_stdin - || ((nullinfd = open ("NUL", O_RDONLY, 0)) >= 0 - && (nullinfd == STDIN_FILENO - || (dup2 (nullinfd, STDIN_FILENO) >= 0 - && close (nullinfd) >= 0)))) + || (nullinfd = open ("NUL", O_RDONLY, 0)) >= 0) && (!(null_stdout || null_stderr) - || ((nulloutfd = open ("NUL", O_RDWR, 0)) >= 0 - && (!null_stdout - || nulloutfd == STDOUT_FILENO - || dup2 (nulloutfd, STDOUT_FILENO) >= 0) - && (!null_stderr - || nulloutfd == STDERR_FILENO - || dup2 (nulloutfd, STDERR_FILENO) >= 0) - && ((null_stdout && nulloutfd == STDOUT_FILENO) - || (null_stderr && nulloutfd == STDERR_FILENO) - || close (nulloutfd) >= 0)))) - /* Use _spawnvpe and pass the environment explicitly. This is needed if - the program has modified the environment using putenv() or [un]setenv(). - On Windows, programs have two environments, one in the "environment - block" of the process and managed through SetEnvironmentVariable(), and - one inside the process, in the location retrieved by the 'environ' - macro. When using _spawnvp() without 'e', the child process inherits a - copy of the environment block - ignoring the effects of putenv() and - [un]setenv(). */ + || (nulloutfd = open ("NUL", O_RDWR, 0)) >= 0)) + /* Pass the environment explicitly. This is needed if the program has + modified the environment using putenv() or [un]setenv(). On Windows, + processes have two environments, one in the "environment block" of the + process and managed through SetEnvironmentVariable(), and one inside the + process, in the location retrieved by the 'environ' macro. If we were + to pass NULL, the child process would inherit a copy of the environment + block - ignoring the effects of putenv() and [un]setenv(). */ { - exitcode = _spawnvpe (P_WAIT, prog_path, (const char **) prog_argv, - (const char **) environ); - if (exitcode < 0 && errno == ENOEXEC) + HANDLE stdin_handle = + (HANDLE) _get_osfhandle (null_stdin ? nullinfd : STDIN_FILENO); + HANDLE stdout_handle = + (HANDLE) _get_osfhandle (null_stdout ? nulloutfd : STDOUT_FILENO); + HANDLE stderr_handle = + (HANDLE) _get_osfhandle (null_stderr ? nulloutfd : STDERR_FILENO); + + exitcode = spawnpvech (P_WAIT, prog_path, (const char **) prog_argv, + (const char **) environ, NULL, + stdin_handle, stdout_handle, stderr_handle); + if (exitcode == -1 && errno == ENOEXEC) { /* prog is not a native executable. Try to execute it as a shell script. Note that prepare_spawn() has already prepended a hidden element "sh.exe" to prog_argv. */ --prog_argv; - exitcode = _spawnvpe (P_WAIT, prog_argv[0], (const char **) prog_argv, - (const char **) environ); + exitcode = spawnpvech (P_WAIT, prog_argv[0], (const char **) prog_argv, + (const char **) environ, NULL, + stdin_handle, stdout_handle, stderr_handle); } } if (nulloutfd >= 0) @@ -167,14 +149,6 @@ execute (const char *progname, if (nullinfd >= 0) close (nullinfd); - /* Restore standard file handles of parent process. */ - if (null_stderr) - undup_safer_noinherit (orig_stderr, STDERR_FILENO); - if (null_stdout) - undup_safer_noinherit (orig_stdout, STDOUT_FILENO); - if (null_stdin) - undup_safer_noinherit (orig_stdin, STDIN_FILENO); - if (termsigp != NULL) *termsigp = 0; diff --git a/lib/spawn-pipe.c b/lib/spawn-pipe.c index b0f5314..a4c4d39 100644 --- a/lib/spawn-pipe.c +++ b/lib/spawn-pipe.c @@ -43,6 +43,11 @@ #if defined _WIN32 && ! defined __CYGWIN__ /* Native Windows API. */ +# if GNULIB_MSVC_NOTHROW +# include "msvc-nothrow.h" +# else +# include +# endif # include # include "windows-spawn.h" @@ -130,9 +135,6 @@ create_pipe (const char *progname, and cvs source code. */ int ifd[2]; int ofd[2]; - int orig_stdin; - int orig_stdout; - int orig_stderr; int child; int nulloutfd; int stdinfd; @@ -157,6 +159,107 @@ create_pipe (const char *progname, * */ + child = -1; + +# if defined _WIN32 && ! defined __CYGWIN__ + bool must_close_ifd1 = pipe_stdout; + bool must_close_ofd0 = pipe_stdin; + + /* Create standard file handles of child process. */ + nulloutfd = -1; + stdinfd = -1; + stdoutfd = -1; + if ((!null_stderr + || (nulloutfd = open ("NUL", O_RDWR, 0)) >= 0) + && (pipe_stdin + || prog_stdin == NULL + || (stdinfd = open (prog_stdin, O_RDONLY, 0)) >= 0) + && (pipe_stdout + || prog_stdout == NULL + || (stdoutfd = open (prog_stdout, O_WRONLY, 0)) >= 0)) + /* The child process doesn't inherit ifd[0], ifd[1], ofd[0], ofd[1], + but it inherits the three STD*_FILENO for which we pass the handles. */ + /* Pass the environment explicitly. This is needed if the program has + modified the environment using putenv() or [un]setenv(). On Windows, + processes have two environments, one in the "environment block" of the + process and managed through SetEnvironmentVariable(), and one inside the + process, in the location retrieved by the 'environ' macro. If we were + to pass NULL, the child process would inherit a copy of the environment + block - ignoring the effects of putenv() and [un]setenv(). */ + { + HANDLE stdin_handle = + (HANDLE) _get_osfhandle (pipe_stdin ? ofd[0] : + prog_stdin == NULL ? STDIN_FILENO : stdinfd); + if (pipe_stdin) + { + HANDLE curr_process = GetCurrentProcess (); + HANDLE duplicate; + if (!DuplicateHandle (curr_process, stdin_handle, + curr_process, &duplicate, + 0, TRUE, DUPLICATE_SAME_ACCESS)) + { + errno = EBADF; /* arbitrary */ + goto failed; + } + must_close_ofd0 = false; + close (ofd[0]); /* implies CloseHandle (stdin_handle); */ + stdin_handle = duplicate; + } + HANDLE stdout_handle = + (HANDLE) _get_osfhandle (pipe_stdout ? ifd[1] : + prog_stdout == NULL ? STDOUT_FILENO : stdoutfd); + if (pipe_stdout) + { + HANDLE curr_process = GetCurrentProcess (); + HANDLE duplicate; + if (!DuplicateHandle (curr_process, stdout_handle, + curr_process, &duplicate, + 0, TRUE, DUPLICATE_SAME_ACCESS)) + { + errno = EBADF; /* arbitrary */ + goto failed; + } + must_close_ifd1 = false; + close (ifd[1]); /* implies CloseHandle (stdout_handle); */ + stdout_handle = duplicate; + } + HANDLE stderr_handle = + (HANDLE) _get_osfhandle (null_stderr ? nulloutfd : STDERR_FILENO); + + child = spawnpvech (P_NOWAIT, prog_path, (const char **) prog_argv, + (const char **) environ, NULL, + stdin_handle, stdout_handle, stderr_handle); + if (child == -1 && errno == ENOEXEC) + { + /* prog is not a native executable. Try to execute it as a + shell script. Note that prepare_spawn() has already prepended + a hidden element "sh.exe" to prog_argv. */ + --prog_argv; + child = spawnpvech (P_NOWAIT, prog_argv[0], (const char **) prog_argv, + (const char **) environ, NULL, + stdin_handle, stdout_handle, stderr_handle); + } + } + failed: + if (child == -1) + saved_errno = errno; + if (stdinfd >= 0) + close (stdinfd); + if (stdoutfd >= 0) + close (stdoutfd); + if (nulloutfd >= 0) + close (nulloutfd); + + if (must_close_ofd0) + close (ofd[0]); + if (must_close_ifd1) + close (ifd[1]); + +# else /* __KLIBC__ */ + int orig_stdin; + int orig_stdout; + int orig_stderr; + /* Save standard file handles of parent process. */ if (pipe_stdin || prog_stdin != NULL) orig_stdin = dup_safer_noinherit (STDIN_FILENO); @@ -164,7 +267,6 @@ create_pipe (const char *progname, orig_stdout = dup_safer_noinherit (STDOUT_FILENO); if (null_stderr) orig_stderr = dup_safer_noinherit (STDERR_FILENO); - child = -1; /* Create standard file handles of child process. */ nulloutfd = -1; @@ -192,18 +294,10 @@ create_pipe (const char *progname, /* The child process doesn't inherit ifd[0], ifd[1], ofd[0], ofd[1], but it inherits all open()ed or dup2()ed file handles (which is what we want in the case of STD*_FILENO). */ - /* Use _spawnvpe and pass the environment explicitly. This is needed if - the program has modified the environment using putenv() or [un]setenv(). - On Windows, programs have two environments, one in the "environment - block" of the process and managed through SetEnvironmentVariable(), and - one inside the process, in the location retrieved by the 'environ' - macro. When using _spawnvp() without 'e', the child process inherits a - copy of the environment block - ignoring the effects of putenv() and - [un]setenv(). */ { child = _spawnvpe (P_NOWAIT, prog_path, (const char **) prog_argv, (const char **) environ); - if (child < 0 && errno == ENOEXEC) + if (child == -1 && errno == ENOEXEC) { /* prog is not a native executable. Try to execute it as a shell script. Note that prepare_spawn() has already prepended @@ -234,6 +328,8 @@ create_pipe (const char *progname, close (ofd[0]); if (pipe_stdout) close (ifd[1]); +# endif + if (child == -1) { if (exit_on_error || !null_stderr) diff --git a/lib/windows-spawn.c b/lib/windows-spawn.c index 776303a..2a59ff2 100644 --- a/lib/windows-spawn.c +++ b/lib/windows-spawn.c @@ -24,13 +24,10 @@ #define WIN32_LEAN_AND_MEAN #include -/* Get _open_osfhandle(). */ -#include - #include +#include #include #include -#include #include /* Get _get_osfhandle(). */ @@ -39,70 +36,16 @@ #else # include #endif +#include -#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 + */ + /* Regarding handle inheritance, see + */ + /* */ + DWORD flags = (mode == P_DETACH ? DETACHED_PROCESS : 0); + /* STARTUPINFO + */ + 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 + + Default value is 512. */ + unsigned int fdmax; + for (fdmax = _getmaxstdio (); fdmax > 0; fdmax--) + { + unsigned int fd = fdmax - 1; + /* _get_osfhandle + */ + HANDLE handle = (HANDLE) _get_osfhandle (fd); + if (handle != INVALID_HANDLE_VALUE) + { + DWORD hflags; + /* 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 + */ + HANDLE handle = (HANDLE) _get_osfhandle (fd); + if (handle != INVALID_HANDLE_VALUE) + { + DWORD hflags; + /* GetHandleInformation + */ + if (GetHandleInformation (handle, &hflags)) + { + if ((hflags & HANDLE_FLAG_INHERIT) != 0) + { + /* fd denotes an inheritable descriptor. */ + /* On Microsoft Windows, it would be sufficient to + set flags[fd] = 1. But on ReactOS or Wine, + adding the bit that indicates the handle type + may be necessary. So, just do it everywhere. */ + switch (GetFileType (handle)) + { + case FILE_TYPE_CHAR: + flags[fd] = 64 | 1; + break; + case FILE_TYPE_PIPE: + flags[fd] = 8 | 1; + break; + default: + flags[fd] = 1; + break; + } + handles_aligned[fd] = handle; + } + } + } + } + } + + if (handles != (char *) handles_aligned) + memmove (handles, (char *) handles_aligned, fdmax * sizeof (HANDLE)); + sinfo.lpReserved2 = (BYTE *) hblock; + } + else + { + sinfo.cbReserved2 = 0; + sinfo.lpReserved2 = NULL; + } + } +#endif + + PROCESS_INFORMATION pinfo; + if (!CreateProcess (resolved_progname, command, NULL, NULL, TRUE, + flags, envblock, currdir, &sinfo, &pinfo)) + { + DWORD error = GetLastError (); + + if (hblock != NULL) + free (hblock); + if (envblock != NULL) + free (envblock); + free (command); + if (resolved_progname != progname) + free ((char *) resolved_progname); + + /* Some of these errors probably cannot happen. But who knows... */ + switch (error) + { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + case ERROR_BAD_PATHNAME: + case ERROR_BAD_NET_NAME: + case ERROR_INVALID_NAME: + case ERROR_DIRECTORY: + errno = ENOENT; + break; + + case ERROR_ACCESS_DENIED: + case ERROR_SHARING_VIOLATION: + errno = EACCES; + break; + + case ERROR_OUTOFMEMORY: + errno = ENOMEM; + break; + + case ERROR_BUFFER_OVERFLOW: + case ERROR_FILENAME_EXCED_RANGE: + errno = ENAMETOOLONG; + break; + + default: + errno = EINVAL; + break; + } + + return -1; + } + + if (pinfo.hThread) + CloseHandle (pinfo.hThread); + if (hblock != NULL) + free (hblock); + if (envblock != NULL) + free (envblock); + free (command); + if (resolved_progname != progname) + free ((char *) resolved_progname); + + switch (mode) + { + case P_WAIT: + { + /* Wait until it terminates. Then get its exit status code. */ + switch (WaitForSingleObject (pinfo.hProcess, INFINITE)) + { + case WAIT_OBJECT_0: + break; + case WAIT_FAILED: + errno = ECHILD; + return -1; + default: + abort (); + } + + DWORD exit_code; + if (!GetExitCodeProcess (pinfo.hProcess, &exit_code)) + { + errno = ECHILD; + return -1; + } + CloseHandle (pinfo.hProcess); + return exit_code; + } + + case P_NOWAIT: + /* Return pinfo.hProcess, not pinfo.dwProcessId. */ + return (intptr_t) pinfo.hProcess; + + case P_DETACH: + case P_OVERLAY: + CloseHandle (pinfo.hProcess); + return 0; + + default: + /* Already checked above. */ + abort (); + } + + /*NOTREACHED*/ +#if 0 + out_of_memory_4: + if (hblock != NULL) + free (hblock); +#endif + out_of_memory_3: + if (envblock != NULL) + free (envblock); + out_of_memory_2: + free (command); + out_of_memory_1: + if (resolved_progname != progname) + free ((char *) resolved_progname); + errno = ENOMEM; + return -1; +} diff --git a/lib/windows-spawn.h b/lib/windows-spawn.h index 200f321..8801f83 100644 --- a/lib/windows-spawn.h +++ b/lib/windows-spawn.h @@ -18,13 +18,11 @@ #ifndef _WINDOWS_SPAWN_H #define _WINDOWS_SPAWN_H -/* Duplicates a file handle, making the copy uninheritable and ensuring the - result is none of STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO. - Returns -1 for a file handle that is equivalent to closed. */ -extern int dup_safer_noinherit (int fd); +#include -/* 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 /* Prepares an argument vector before calling spawn(). Note that spawn() does not by itself call the command interpreter @@ -58,4 +56,28 @@ extern void undup_safer_noinherit (int tempfd, int origfd); */ extern char ** prepare_spawn (char **argv); +/* Creates a subprocess. + MODE is either P_WAIT or P_NOWAIT. + PROGNAME is the program to invoke. + ARGV is the NULL-terminated array of arguments, ARGV[0] being PROGNAME by + convention. + ENVP is the NULL-terminated set of environment variable assignments, or NULL + to inherit the initial environ variable assignments from the caller and + ignore all calls to putenv(), setenv(), unsetenv() done in the caller. + CURRDIR is the directory in which to start the program, or NULL to inherit + the working directory from the caller. + STDIN_HANDLE, STDOUT_HANDLE, STDERR_HANDLE are the handles to use for the + first three file descriptors in the callee process. + Returns + - 0 for success (if MODE is P_WAIT), or + - a handle that be passed to _cwait (on Windows) or waitpid (on OS/2), or + - -1 upon error, with errno set. + */ +extern intptr_t spawnpvech (int mode, + const char *progname, const char * const *argv, + const char * const *envp, + const char *currdir, + HANDLE stdin_handle, HANDLE stdout_handle, + HANDLE stderr_handle); + #endif /* _WINDOWS_SPAWN_H */ diff --git a/modules/execute b/modules/execute index 4a7f845..52eee5f 100644 --- a/modules/execute +++ b/modules/execute @@ -8,9 +8,10 @@ m4/execute.m4 Depends-on: dup2 +environ error fatal-signal -wait-process +msvc-nothrow gettext-h spawn posix_spawnp @@ -24,7 +25,7 @@ posix_spawnattr_destroy stdbool stdlib unistd -environ +wait-process windows-spawn configure.ac: diff --git a/modules/spawn-pipe b/modules/spawn-pipe index 7845afa..0ddbc74 100644 --- a/modules/spawn-pipe +++ b/modules/spawn-pipe @@ -14,6 +14,7 @@ environ error fatal-signal gettext-h +msvc-nothrow open pipe2 pipe2-safer diff --git a/modules/windows-spawn b/modules/windows-spawn index 747f8bb..4702c50 100644 --- a/modules/windows-spawn +++ b/modules/windows-spawn @@ -6,12 +6,10 @@ lib/windows-spawn.h lib/windows-spawn.c Depends-on: -cloexec -dup2 -error -gettext-h +findprog-in msvc-nothrow stdbool +stdint stdlib strpbrk unistd -- 2.7.4