summaryrefslogtreecommitdiffstats
path: root/lib/spawni.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/spawni.c1108
1 files changed, 1108 insertions, 0 deletions
diff --git a/lib/spawni.c b/lib/spawni.c
new file mode 100644
index 0000000..cc9511f
--- /dev/null
+++ b/lib/spawni.c
@@ -0,0 +1,1108 @@
+/* Guts of POSIX spawn interface. Generic POSIX.1 version.
+ Copyright (C) 2000-2006, 2008-2023 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;
+ int newfd;
+
+ for (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;
+ unsigned int fd;
+
+ for (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;
+ unsigned int fd;
+
+ for (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 (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> */
+ 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);
+ 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, 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 __GNUC__ >= 3
+# 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)
+{
+ pid_t new_pid;
+ char *path, *p, *name;
+ size_t len;
+ size_t pathlen;
+
+ /* 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;
+
+ /* Generate the new process. */
+#if HAVE_VFORK
+ if ((flags & POSIX_SPAWN_USEVFORK) != 0
+ /* If no major work is done, allow using vfork. Note that we
+ might perform the path searching. But this would be done by
+ a call to execvp(), too, and such a call must be OK according
+ to POSIX. */
+ || ((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. */
+ int sig;
+ struct sigaction sa;
+
+ memset (&sa, '\0', sizeof (sa));
+ sa.sa_handler = SIG_DFL;
+
+ for (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)
+ {
+ 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:
+ 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 || strchr (file, '/') != NULL)
+ {
+ /* The FILE parameter is actually a path. */
+ execve (file, (char * const *) argv, (char * const *) envp);
+
+ /* Oh, oh. 'execve' returns. This is bad. */
+ _exit (SPAWN_ERROR);
+ }
+
+ /* 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'. */
+ 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
+ }
+
+ len = strlen (file) + 1;
+ 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 = '/';
+
+ 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