diff options
Diffstat (limited to 'lib/spawni.c')
-rw-r--r-- | lib/spawni.c | 672 |
1 files changed, 631 insertions, 41 deletions
diff --git a/lib/spawni.c b/lib/spawni.c index 39e99a0..2f0d25f 100644 --- a/lib/spawni.c +++ b/lib/spawni.c @@ -1,5 +1,5 @@ /* Guts of POSIX spawn interface. Generic POSIX.1 version. - Copyright (C) 2000-2006, 2008-2019 Free Software Foundation, Inc. + Copyright (C) 2000-2006, 2008-2020 Free Software Foundation, Inc. This file is part of the GNU C Library. This program is free software: you can redistribute it and/or modify @@ -75,9 +75,6 @@ # define sigprocmask __sigprocmask # define strchrnul __strchrnul # define vfork __vfork -#else -# undef internal_function -# define internal_function /* empty */ #endif @@ -90,56 +87,649 @@ #if defined _WIN32 && ! defined __CYGWIN__ - /* Native Windows API. */ -int -__spawni (pid_t *pid, const char *file, - const posix_spawn_file_actions_t *file_actions, - const posix_spawnattr_t *attrp, char *const argv[], - char *const envp[], int use_path) + +/* 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) { - /* Not yet implemented. */ - return ENOSYS; + 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; } -#else +/* 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--; +} -/* The file is accessible but it is not an executable file. Invoke - the shell to interpret it as a script. */ +/* Closes all handles in inh_handles. */ static void -internal_function -script_execute (const char *file, char *const argv[], char *const envp[]) +close_inheritable_handles (struct inheritable_handles *inh_handles) { - /* Count the arguments. */ - int argc = 0; - while (argv[argc++]) - ; + HANDLE *handles = inh_handles->handles; + size_t handles_count = inh_handles->count; + unsigned int fd; - /* Construct an argument list for the shell. */ - { - char **new_argv = (char **) alloca ((argc + 1) * sizeof (char *)); - new_argv[0] = (char *) _PATH_BSHELL; - new_argv[1] = (char *) file; - while (argc > 1) + 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 ()) { - new_argv[argc] = argv[argc - 1]; - --argc; + /* 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; } - /* Execute the shell. */ - execve (new_argv[0], new_argv, envp); + 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 *prog_filename, + const posix_spawn_file_actions_t *file_actions, + const posix_spawnattr_t *attrp, const char *const prog_argv[], + const char *const envp[], int use_path) +{ + /* 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 + /* Spawn a new process executing PATH with the attributes describes in *ATTRP. Before running the process perform the actions described in FILE-ACTIONS. */ int __spawni (pid_t *pid, const char *file, const posix_spawn_file_actions_t *file_actions, - const posix_spawnattr_t *attrp, char *const argv[], - char *const envp[], int use_path) + const posix_spawnattr_t *attrp, const char *const argv[], + const char *const envp[], int use_path) { pid_t new_pid; char *path, *p, *name; @@ -290,6 +880,12 @@ __spawni (pid_t *pid, const char *file, /* The 'chdir' call failed. */ _exit (SPAWN_ERROR); break; + + case spawn_do_fchdir: + if (fchdir (action->action.fchdir_action.fd) < 0) + /* The 'fchdir' call failed. */ + _exit (SPAWN_ERROR); + break; } } } @@ -297,10 +893,7 @@ __spawni (pid_t *pid, const char *file, if (! use_path || strchr (file, '/') != NULL) { /* The FILE parameter is actually a path. */ - execve (file, argv, envp); - - if (errno == ENOEXEC) - script_execute (file, argv, envp); + execve (file, (char * const *) argv, (char * const *) envp); /* Oh, oh. 'execve' returns. This is bad. */ _exit (SPAWN_ERROR); @@ -348,10 +941,7 @@ __spawni (pid_t *pid, const char *file, startp = (char *) memcpy (name - (p - path), path, p - path); /* Try to execute this name. If it works, execv will not return. */ - execve (startp, argv, envp); - - if (errno == ENOEXEC) - script_execute (startp, argv, envp); + execve (startp, (char * const *) argv, (char * const *) envp); switch (errno) { |