| /* Guts of POSIX spawn interface. Generic POSIX.1 version. | |
| Copyright (C) 2000-2006, 2008-2026 Free Software Foundation, Inc. | |
| This file is part of the GNU C Library. | |
| This file is free software: you can redistribute it and/or modify | |
| it under the terms of the GNU Lesser General Public License as | |
| published by the Free Software Foundation; either version 2.1 of the | |
| License, or (at your option) any later version. | |
| This file is distributed in the hope that it will be useful, | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| GNU Lesser General Public License for more details. | |
| You should have received a copy of the GNU Lesser General Public License | |
| along with this program. If not, see <https://www.gnu.org/licenses/>. */ | |
| #include <config.h> | |
| /* Specification. */ | |
| #include <spawn.h> | |
| #include "spawn_int.h" | |
| #include <alloca.h> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #ifndef O_LARGEFILE | |
| # define O_LARGEFILE 0 | |
| #endif | |
| #if _LIBC || HAVE_PATHS_H | |
| # include <paths.h> | |
| #else | |
| # define _PATH_BSHELL BOURNE_SHELL | |
| #endif | |
| #include <signal.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #if _LIBC | |
| # include <not-cancel.h> | |
| #else | |
| # define close_not_cancel close | |
| # define open_not_cancel open | |
| #endif | |
| #if _LIBC | |
| # include <local-setxid.h> | |
| #else | |
| # if !HAVE_SETEUID | |
| # define seteuid(id) setresuid (-1, id, -1) | |
| # endif | |
| # if !HAVE_SETEGID | |
| # define setegid(id) setresgid (-1, id, -1) | |
| # endif | |
| # define local_seteuid(id) seteuid (id) | |
| # define local_setegid(id) setegid (id) | |
| #endif | |
| #if _LIBC | |
| # define alloca __alloca | |
| # define execve __execve | |
| # define dup2 __dup2 | |
| # define fork __fork | |
| # define getgid __getgid | |
| # define getuid __getuid | |
| # define sched_setparam __sched_setparam | |
| # define sched_setscheduler __sched_setscheduler | |
| # define setpgid __setpgid | |
| # define sigaction __sigaction | |
| # define sigismember __sigismember | |
| # define sigprocmask __sigprocmask | |
| # define strchrnul __strchrnul | |
| # define vfork __vfork | |
| #endif | |
| /* The Unix standard contains a long explanation of the way to signal | |
| an error after the fork() was successful. Since no new wait status | |
| was wanted there is no way to signal an error using one of the | |
| available methods. The committee chose to signal an error by a | |
| normal program exit with the exit code 127. */ | |
| #define SPAWN_ERROR 127 | |
| #if defined _WIN32 && ! defined __CYGWIN__ | |
| /* Native Windows API. */ | |
| /* Define to 1 to enable DuplicateHandle optimization. | |
| Define to 0 to disable this optimization. */ | |
| # ifndef SPAWN_INTERNAL_OPTIMIZE_DUPLICATEHANDLE | |
| # define SPAWN_INTERNAL_OPTIMIZE_DUPLICATEHANDLE 1 | |
| # endif | |
| /* Get declarations of the native Windows API functions. */ | |
| # define WIN32_LEAN_AND_MEAN | |
| # include <windows.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; | |
| struct IHANDLE *new_ih = | |
| (struct IHANDLE *) | |
| realloc (inh_handles->ih, new_allocated * sizeof (struct IHANDLE)); | |
| if (new_ih == NULL) | |
| { | |
| errno = ENOMEM; | |
| return -1; | |
| } | |
| inh_handles->allocated = new_allocated; | |
| inh_handles->ih = new_ih; | |
| } | |
| struct IHANDLE *ih = inh_handles->ih; | |
| for (; inh_handles->count <= newfd; inh_handles->count++) | |
| ih[inh_handles->count].handle = INVALID_HANDLE_VALUE; | |
| return 0; | |
| } | |
| # if SPAWN_INTERNAL_OPTIMIZE_DUPLICATEHANDLE | |
| /* Assuming inh_handles->ih[newfd].handle != INVALID_HANDLE_VALUE | |
| and (inh_handles->ih[newfd].flags & DELAYED_DUP2_NEWFD) != 0, | |
| actually performs the delayed dup2 (oldfd, newfd). | |
| Returns 0 upon success. In case of failure, -1 is returned, with errno set. | |
| */ | |
| static int | |
| do_delayed_dup2 (int newfd, struct inheritable_handles *inh_handles, | |
| HANDLE curr_process) | |
| { | |
| int oldfd = inh_handles->ih[newfd].linked_fd; | |
| /* Check invariants. */ | |
| if (!((inh_handles->ih[oldfd].flags & DELAYED_DUP2_OLDFD) != 0 | |
| && newfd == inh_handles->ih[oldfd].linked_fd | |
| && inh_handles->ih[newfd].handle == inh_handles->ih[oldfd].handle)) | |
| abort (); | |
| /* Duplicate the handle now. */ | |
| if (!DuplicateHandle (curr_process, inh_handles->ih[oldfd].handle, | |
| curr_process, &inh_handles->ih[newfd].handle, | |
| 0, TRUE, DUPLICATE_SAME_ACCESS)) | |
| { | |
| errno = EBADF; /* arbitrary */ | |
| return -1; | |
| } | |
| inh_handles->ih[oldfd].flags &= ~DELAYED_DUP2_OLDFD; | |
| inh_handles->ih[newfd].flags = | |
| (unsigned char) inh_handles->ih[oldfd].flags | KEEP_OPEN_IN_CHILD; | |
| return 0; | |
| } | |
| /* Performs the remaining delayed dup2 (oldfd, newfd). | |
| Returns 0 upon success. In case of failure, -1 is returned, with errno set. | |
| */ | |
| static int | |
| do_remaining_delayed_dup2 (struct inheritable_handles *inh_handles, | |
| HANDLE curr_process) | |
| { | |
| size_t handles_count = inh_handles->count; | |
| for (int newfd = 0; newfd < handles_count; newfd++) | |
| if (inh_handles->ih[newfd].handle != INVALID_HANDLE_VALUE | |
| && (inh_handles->ih[newfd].flags & DELAYED_DUP2_NEWFD) != 0) | |
| if (do_delayed_dup2 (newfd, inh_handles, curr_process) < 0) | |
| return -1; | |
| return 0; | |
| } | |
| # endif | |
| /* Closes the handles in inh_handles that are not meant to be preserved in the | |
| child process, and reduces inh_handles->count to the minimum needed. */ | |
| static void | |
| shrink_inheritable_handles (struct inheritable_handles *inh_handles) | |
| { | |
| struct IHANDLE *ih = inh_handles->ih; | |
| size_t handles_count = inh_handles->count; | |
| for (unsigned int fd = 0; fd < handles_count; fd++) | |
| { | |
| HANDLE handle = ih[fd].handle; | |
| if (handle != INVALID_HANDLE_VALUE | |
| && (ih[fd].flags & KEEP_OPEN_IN_CHILD) == 0) | |
| { | |
| if (!(ih[fd].flags & KEEP_OPEN_IN_PARENT)) | |
| CloseHandle (handle); | |
| ih[fd].handle = INVALID_HANDLE_VALUE; | |
| } | |
| } | |
| while (handles_count > 3 | |
| && ih[handles_count - 1].handle == INVALID_HANDLE_VALUE) | |
| handles_count--; | |
| inh_handles->count = handles_count; | |
| } | |
| /* Closes all handles in inh_handles. */ | |
| static void | |
| close_inheritable_handles (struct inheritable_handles *inh_handles) | |
| { | |
| struct IHANDLE *ih = inh_handles->ih; | |
| size_t handles_count = inh_handles->count; | |
| for (unsigned int fd = 0; fd < handles_count; fd++) | |
| { | |
| HANDLE handle = ih[fd].handle; | |
| if (handle != INVALID_HANDLE_VALUE | |
| && !(ih[fd].flags & DELAYED_DUP2_NEWFD) | |
| && !(ih[fd].flags & KEEP_OPEN_IN_PARENT)) | |
| 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)); | |
| } | |
| /* 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, bool ignore_EBADF) | |
| { | |
| if (!(fd >= 0 && fd < inh_handles->count | |
| && inh_handles->ih[fd].handle != INVALID_HANDLE_VALUE)) | |
| { | |
| if (ignore_EBADF) | |
| return 0; | |
| else | |
| { | |
| errno = EBADF; | |
| return -1; | |
| } | |
| } | |
| # if SPAWN_INTERNAL_OPTIMIZE_DUPLICATEHANDLE | |
| if ((inh_handles->ih[fd].flags & DELAYED_DUP2_NEWFD) != 0) | |
| { | |
| int dup2_oldfd = inh_handles->ih[fd].linked_fd; | |
| /* Check invariants. */ | |
| if (!((inh_handles->ih[dup2_oldfd].flags & DELAYED_DUP2_OLDFD) != 0 | |
| && fd == inh_handles->ih[dup2_oldfd].linked_fd | |
| && inh_handles->ih[fd].handle == inh_handles->ih[dup2_oldfd].handle)) | |
| abort (); | |
| /* Annihilate a delayed dup2 (..., fd) call. */ | |
| inh_handles->ih[dup2_oldfd].flags &= ~DELAYED_DUP2_OLDFD; | |
| } | |
| else if ((inh_handles->ih[fd].flags & DELAYED_DUP2_OLDFD) != 0) | |
| { | |
| int dup2_newfd = inh_handles->ih[fd].linked_fd; | |
| /* Check invariants. */ | |
| if (!((inh_handles->ih[dup2_newfd].flags & DELAYED_DUP2_NEWFD) != 0 | |
| && fd == inh_handles->ih[dup2_newfd].linked_fd | |
| && inh_handles->ih[fd].handle == inh_handles->ih[dup2_newfd].handle)) | |
| abort (); | |
| /* Optimize a delayed dup2 (fd, ...) call. */ | |
| inh_handles->ih[dup2_newfd].flags = | |
| (inh_handles->ih[fd].flags & ~DELAYED_DUP2_OLDFD) | KEEP_OPEN_IN_CHILD; | |
| } | |
| else | |
| # endif | |
| { | |
| if (!(inh_handles->ih[fd].flags & KEEP_OPEN_IN_PARENT) | |
| && !CloseHandle (inh_handles->ih[fd].handle)) | |
| { | |
| inh_handles->ih[fd].handle = INVALID_HANDLE_VALUE; | |
| errno = EIO; | |
| return -1; | |
| } | |
| } | |
| inh_handles->ih[fd].handle = INVALID_HANDLE_VALUE; | |
| return 0; | |
| } | |
| /* Opens an inheritable 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 (streq (name, "/dev/null")) | |
| 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> */ | |
| SECURITY_ATTRIBUTES sec_attr; | |
| sec_attr.nLength = sizeof (SECURITY_ATTRIBUTES); | |
| sec_attr.lpSecurityDescriptor = NULL; | |
| sec_attr.bInheritHandle = TRUE; | |
| 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, | |
| &sec_attr, | |
| ((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) | |
| { | |
| if (!(newfd >= 0 && newfd < _getmaxstdio ())) | |
| { | |
| errno = EBADF; | |
| return -1; | |
| } | |
| if (grow_inheritable_handles (inh_handles, newfd) < 0) | |
| return -1; | |
| if (do_close (inh_handles, newfd, true) < 0) | |
| 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) | |
| { | |
| free (filename_to_free); | |
| return -1; | |
| } | |
| free (filename_to_free); | |
| inh_handles->ih[newfd].handle = handle; | |
| inh_handles->ih[newfd].flags = | |
| ((flags & O_APPEND) != 0 ? 32 : 0) | KEEP_OPEN_IN_CHILD; | |
| 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->ih[oldfd].handle != 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 (do_close (inh_handles, newfd, true) < 0) | |
| return -1; | |
| /* We may need to duplicate the handle, so that a forthcoming do_close | |
| action on oldfd has no effect on newfd. */ | |
| # if SPAWN_INTERNAL_OPTIMIZE_DUPLICATEHANDLE | |
| /* But try to not do it now; delay it if possible. In many cases, the | |
| DuplicateHandle call can be optimized away. */ | |
| if ((inh_handles->ih[oldfd].flags & DELAYED_DUP2_NEWFD) != 0) | |
| if (do_delayed_dup2 (oldfd, inh_handles, curr_process) < 0) | |
| return -1; | |
| if ((inh_handles->ih[oldfd].flags & DELAYED_DUP2_OLDFD) != 0) | |
| { | |
| /* Check invariants. */ | |
| int dup2_newfd = inh_handles->ih[oldfd].linked_fd; | |
| if (!((inh_handles->ih[dup2_newfd].flags & DELAYED_DUP2_NEWFD) != 0 | |
| && oldfd == inh_handles->ih[dup2_newfd].linked_fd | |
| && inh_handles->ih[oldfd].handle == inh_handles->ih[dup2_newfd].handle)) | |
| abort (); | |
| /* We can't delay two or more dup2 calls from the same oldfd. */ | |
| if (!DuplicateHandle (curr_process, inh_handles->ih[oldfd].handle, | |
| curr_process, &inh_handles->ih[newfd].handle, | |
| 0, TRUE, DUPLICATE_SAME_ACCESS)) | |
| { | |
| errno = EBADF; /* arbitrary */ | |
| return -1; | |
| } | |
| inh_handles->ih[newfd].flags = | |
| (unsigned char) inh_handles->ih[oldfd].flags | KEEP_OPEN_IN_CHILD; | |
| } | |
| else | |
| { | |
| /* Delay the dup2 (oldfd, newfd) action. */ | |
| inh_handles->ih[oldfd].flags |= DELAYED_DUP2_OLDFD; | |
| inh_handles->ih[oldfd].linked_fd = newfd; | |
| inh_handles->ih[newfd].handle = inh_handles->ih[oldfd].handle; | |
| inh_handles->ih[newfd].flags = DELAYED_DUP2_NEWFD; | |
| inh_handles->ih[newfd].linked_fd = oldfd; | |
| } | |
| # else | |
| if (!DuplicateHandle (curr_process, inh_handles->ih[oldfd].handle, | |
| curr_process, &inh_handles->ih[newfd].handle, | |
| 0, TRUE, DUPLICATE_SAME_ACCESS)) | |
| { | |
| errno = EBADF; /* arbitrary */ | |
| return -1; | |
| } | |
| inh_handles->ih[newfd].flags = | |
| (unsigned char) inh_handles->ih[oldfd].flags | KEEP_OPEN_IN_CHILD; | |
| # endif | |
| } | |
| 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, NULL); | |
| 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 (); | |
| for (int 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, false) < 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) | |
| < 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; | |
| } | |
| } | |
| # if SPAWN_INTERNAL_OPTIMIZE_DUPLICATEHANDLE | |
| /* Do the remaining delayed dup2 invocations. */ | |
| if (do_remaining_delayed_dup2 (&inh_handles, curr_process) < 0) | |
| goto failed_2; | |
| # endif | |
| } | |
| /* Close the handles in inh_handles that are not meant to be preserved in the | |
| child process, and 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: | |
| free (envblock); | |
| free (command); | |
| free (argv_mem_to_free); | |
| return errno; | |
| } | |
| #else | |
| /* The warning "warning: 'vfork' is deprecated: Use posix_spawn or fork" seen | |
| on macOS 12 is pointless, as we use vfork only when it is safe or when the | |
| user has explicitly requested it. Silence this warning. */ | |
| #if _GL_GNUC_PREREQ (4, 2) | |
| # pragma GCC diagnostic ignored "-Wdeprecated-declarations" | |
| #endif | |
| /* 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, const char *const argv[], | |
| const char *const envp[], int use_path) | |
| { | |
| /* Do this once. */ | |
| short int flags = attrp == NULL ? 0 : attrp->_flags; | |
| /* Avoid gcc warning | |
| "variable 'flags' might be clobbered by 'longjmp' or 'vfork'" */ | |
| (void) &flags; | |
| use_path = use_path && strchr (file, '/') == NULL; | |
| /* Prepare a stack-allocated copy of $PATH and FILE, for iterating through | |
| $PATH. We do this already in the parent, because on Linux/SPARC, | |
| Linux/ppc64, Linux/ppc64le, and Solaris/SPARC, it causes a SIGBUS or | |
| SIGSEGV when done in the child process after vfork() and when $PATH is long | |
| (ca. 4 KB or so). */ | |
| char *path; | |
| char *name; | |
| if (use_path) | |
| { | |
| /* We have to search for FILE on the path. */ | |
| path = getenv ("PATH"); | |
| if (path == NULL) | |
| { | |
| #if HAVE_CONFSTR | |
| /* There is no 'PATH' in the environment. | |
| The default search path is the current directory | |
| followed by the path 'confstr' returns for '_CS_PATH'. */ | |
| size_t len = confstr (_CS_PATH, (char *) NULL, 0); | |
| path = (char *) alloca (1 + len); | |
| path[0] = ':'; | |
| (void) confstr (_CS_PATH, path + 1, len); | |
| #else | |
| /* Pretend that the PATH contains only the current directory. */ | |
| path = ""; | |
| #endif | |
| } | |
| size_t len = strlen (file) + 1; | |
| size_t pathlen = strlen (path); | |
| name = alloca (pathlen + len + 1); | |
| /* Copy the file name at the top. */ | |
| name = (char *) memcpy (name + pathlen + 1, file, len); | |
| /* And add the slash. */ | |
| *--name = '/'; | |
| } | |
| /* Generate the new process. */ | |
| pid_t new_pid; | |
| #if HAVE_VFORK | |
| if ((flags & POSIX_SPAWN_USEVFORK) != 0 | |
| /* If no major work is done, allow using vfork. */ | |
| || ((flags & (POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF | |
| | POSIX_SPAWN_SETSCHEDPARAM | POSIX_SPAWN_SETSCHEDULER | |
| | POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_RESETIDS)) == 0 | |
| && file_actions == NULL)) | |
| new_pid = vfork (); | |
| else | |
| #endif | |
| new_pid = fork (); | |
| if (new_pid != 0) | |
| { | |
| if (new_pid < 0) | |
| return errno; | |
| /* The call was successful. Store the PID if necessary. */ | |
| if (pid != NULL) | |
| *pid = new_pid; | |
| return 0; | |
| } | |
| /* Set signal mask. */ | |
| if ((flags & POSIX_SPAWN_SETSIGMASK) != 0 | |
| && sigprocmask (SIG_SETMASK, &attrp->_ss, NULL) != 0) | |
| _exit (SPAWN_ERROR); | |
| /* Set signal default action. */ | |
| if ((flags & POSIX_SPAWN_SETSIGDEF) != 0) | |
| { | |
| /* We have to iterate over all signals. This could possibly be | |
| done better but it requires system specific solutions since | |
| the sigset_t data type can be very different on different | |
| architectures. */ | |
| struct sigaction sa; | |
| memset (&sa, '\0', sizeof (sa)); | |
| sa.sa_handler = SIG_DFL; | |
| for (int sig = 1; sig < NSIG; ++sig) | |
| if (sigismember (&attrp->_sd, sig) != 0 | |
| && sigaction (sig, &sa, NULL) != 0) | |
| _exit (SPAWN_ERROR); | |
| } | |
| #if (_LIBC ? defined _POSIX_PRIORITY_SCHEDULING : HAVE_SCHED_SETPARAM && HAVE_SCHED_SETSCHEDULER) | |
| /* Set the scheduling algorithm and parameters. */ | |
| if ((flags & (POSIX_SPAWN_SETSCHEDPARAM | POSIX_SPAWN_SETSCHEDULER)) | |
| == POSIX_SPAWN_SETSCHEDPARAM) | |
| { | |
| if (sched_setparam (0, &attrp->_sp) == -1) | |
| _exit (SPAWN_ERROR); | |
| } | |
| else if ((flags & POSIX_SPAWN_SETSCHEDULER) != 0) | |
| { | |
| if (sched_setscheduler (0, attrp->_policy, | |
| (flags & POSIX_SPAWN_SETSCHEDPARAM) != 0 | |
| ? &attrp->_sp : NULL) == -1) | |
| _exit (SPAWN_ERROR); | |
| } | |
| #endif | |
| /* Set the process group ID. */ | |
| if ((flags & POSIX_SPAWN_SETPGROUP) != 0 | |
| && setpgid (0, attrp->_pgrp) != 0) | |
| _exit (SPAWN_ERROR); | |
| /* Set the effective user and group IDs. */ | |
| if ((flags & POSIX_SPAWN_RESETIDS) != 0 | |
| && (local_seteuid (getuid ()) != 0 | |
| || local_setegid (getgid ()) != 0)) | |
| _exit (SPAWN_ERROR); | |
| /* Execute the file actions. */ | |
| if (file_actions != NULL) | |
| for (int cnt = 0; cnt < file_actions->_used; ++cnt) | |
| { | |
| struct __spawn_action *action = &file_actions->_actions[cnt]; | |
| switch (action->tag) | |
| { | |
| case spawn_do_close: | |
| if (close_not_cancel (action->action.close_action.fd) != 0) | |
| /* Signal the error. */ | |
| _exit (SPAWN_ERROR); | |
| break; | |
| case spawn_do_open: | |
| { | |
| int new_fd = open_not_cancel (action->action.open_action.path, | |
| action->action.open_action.oflag | |
| | O_LARGEFILE, | |
| action->action.open_action.mode); | |
| if (new_fd == -1) | |
| /* The 'open' call failed. */ | |
| _exit (SPAWN_ERROR); | |
| /* Make sure the desired file descriptor is used. */ | |
| if (new_fd != action->action.open_action.fd) | |
| { | |
| if (dup2 (new_fd, action->action.open_action.fd) | |
| != action->action.open_action.fd) | |
| /* The 'dup2' call failed. */ | |
| _exit (SPAWN_ERROR); | |
| if (close_not_cancel (new_fd) != 0) | |
| /* The 'close' call failed. */ | |
| _exit (SPAWN_ERROR); | |
| } | |
| } | |
| break; | |
| case spawn_do_dup2: | |
| if (dup2 (action->action.dup2_action.fd, | |
| action->action.dup2_action.newfd) | |
| != action->action.dup2_action.newfd) | |
| /* The 'dup2' call failed. */ | |
| _exit (SPAWN_ERROR); | |
| break; | |
| case spawn_do_chdir: | |
| if (chdir (action->action.chdir_action.path) < 0) | |
| /* 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; | |
| } | |
| } | |
| if (! use_path) | |
| { | |
| /* No need to iterate through $PATH. Use FILE directly. */ | |
| execve (file, (char * const *) argv, (char * const *) envp); | |
| /* Oh, oh. 'execve' returns. This is bad. */ | |
| _exit (SPAWN_ERROR); | |
| } | |
| char *p = path; | |
| do | |
| { | |
| char *startp; | |
| path = p; | |
| p = strchrnul (path, ':'); | |
| if (p == path) | |
| /* Two adjacent colons, or a colon at the beginning or the end | |
| of 'PATH' means to search the current directory. */ | |
| startp = name + 1; | |
| else | |
| startp = (char *) memcpy (name - (p - path), path, p - path); | |
| /* Try to execute this name. If it works, execv will not return. */ | |
| execve (startp, (char * const *) argv, (char * const *) envp); | |
| switch (errno) | |
| { | |
| case EACCES: | |
| case ENOENT: | |
| case ESTALE: | |
| case ENOTDIR: | |
| /* Those errors indicate the file is missing or not executable | |
| by us, in which case we want to just try the next path | |
| directory. */ | |
| break; | |
| default: | |
| /* Some other error means we found an executable file, but | |
| something went wrong executing it; return the error to our | |
| caller. */ | |
| _exit (SPAWN_ERROR); | |
| } | |
| } | |
| while (*p++ != '\0'); | |
| /* Return with an error. */ | |
| _exit (SPAWN_ERROR); | |
| } | |
| #endif |