>From f2ca14d1555c7f8de305c21993fd1f805f48a9a7 Mon Sep 17 00:00:00 2001 From: Bruno Haible 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 + 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 + 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 + +# include +# include + +# 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 + specifies: "More than two leading characters shall be treated as + a single 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 + */ + /* Open a handle to the file. + CreateFile + + */ + 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 + + + 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 + + + We don't support this case; it produces error EINVAL above. */ + /* */ + 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 + */ + /* STARTUPINFO + */ + 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