summaryrefslogtreecommitdiff
path: root/lib/spawni.c
diff options
context:
space:
mode:
authorBruno Haible <bruno@clisp.org>2020-12-24 22:18:36 +0100
committerBruno Haible <bruno@clisp.org>2020-12-24 22:18:36 +0100
commitf2ca14d1555c7f8de305c21993fd1f805f48a9a7 (patch)
tree14b7f07e4f78e64192092fb9bdf53bac5920bd5e /lib/spawni.c
parent48e0a23aa5ddd288856268af751b73218ba31777 (diff)
downloadgnulib-f2ca14d1555c7f8de305c21993fd1f805f48a9a7.tar.gz
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.
Diffstat (limited to 'lib/spawni.c')
-rw-r--r--lib/spawni.c631
1 files changed, 626 insertions, 5 deletions
diff --git a/lib/spawni.c b/lib/spawni.c
index 182d13ff24..2f0d25f38a 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