cosmopolitan/libc/proc/posix_spawn.c
Justine Tunney af7bd80430
Eliminate cyclic locks in runtime
This change introduces a new deadlock detector for Cosmo's POSIX threads
implementation. Error check mutexes will now track a DAG of nested locks
and report EDEADLK when a deadlock is theoretically possible. These will
occur rarely, but it's important for production hardening your code. You
don't even need to change your mutexes to use the POSIX error check mode
because `cosmocc -mdbg` will enable error checking on mutexes by default
globally. When cycles are found, an error message showing your demangled
symbols describing the strongly connected component are printed and then
the SIGTRAP is raised, which means you'll also get a backtrace if you're
using ShowCrashReports() too. This new error checker is so low-level and
so pure that it's able to verify the relationships of every libc runtime
lock, including those locks upon which the mutex implementation depends.
2024-12-16 22:25:12 -08:00

745 lines
25 KiB
C

/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2021 Justine Alexandra Roberts Tunney │
│ │
│ Permission to use, copy, modify, and/or distribute this software for │
│ any purpose with or without fee is hereby granted, provided that the │
│ above copyright notice and this permission notice appear in all copies. │
│ │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/proc/posix_spawn.h"
#include "libc/assert.h"
#include "libc/atomic.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/rlimit.h"
#include "libc/calls/struct/rlimit.internal.h"
#include "libc/calls/struct/rusage.internal.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/fmt/itoa.h"
#include "libc/fmt/magnumstrs.internal.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/bsf.h"
#include "libc/intrin/describeflags.h"
#include "libc/intrin/dll.h"
#include "libc/intrin/fds.h"
#include "libc/intrin/strace.h"
#include "libc/intrin/weaken.h"
#include "libc/mem/alloca.h"
#include "libc/mem/mem.h"
#include "libc/nt/createfile.h"
#include "libc/nt/enum/accessmask.h"
#include "libc/nt/enum/creationdisposition.h"
#include "libc/nt/enum/fileflagandattributes.h"
#include "libc/nt/enum/filesharemode.h"
#include "libc/nt/enum/processcreationflags.h"
#include "libc/nt/enum/startf.h"
#include "libc/nt/files.h"
#include "libc/nt/runtime.h"
#include "libc/nt/struct/processinformation.h"
#include "libc/nt/struct/startupinfo.h"
#include "libc/proc/describefds.internal.h"
#include "libc/proc/ntspawn.h"
#include "libc/proc/posix_spawn.h"
#include "libc/proc/posix_spawn.internal.h"
#include "libc/proc/proc.internal.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/sock.h"
#include "libc/stdio/stdio.h"
#include "libc/stdio/sysparam.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/consts/f.h"
#include "libc/sysv/consts/fd.h"
#include "libc/sysv/consts/limits.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/ok.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
#ifndef SYSDEBUG
#define read sys_read
#define write sys_write
#define close sys_close
#define pipe2 sys_pipe2
#define getgid sys_getgid
#define setgid sys_setgid
#define getuid sys_getuid
#define setuid sys_setuid
#define setsid sys_setsid
#define setpgid sys_setpgid
#define fcntl __sys_fcntl
#define wait4 __sys_wait4
#define openat __sys_openat
#define setrlimit sys_setrlimit
#define sigprocmask sys_sigprocmask
#endif
#define CLOSER_CONTAINER(e) DLL_CONTAINER(struct Closer, elem, e)
static atomic_bool has_vfork; // i.e. not qemu/wsl/xnu/openbsd
#ifdef __x86_64__
struct Closer {
int64_t handle;
struct Dll elem;
};
struct SpawnFds {
int n;
struct Fd *p;
struct Dll *closers;
};
static textwindows int64_t spawnfds_handle(struct SpawnFds *fds, int fd) {
if (__is_cloexec(fds->p + fd))
return -1;
return fds->p[fd].handle;
}
static textwindows errno_t spawnfds_ensure(struct SpawnFds *fds, int fd) {
int n2;
struct Fd *p2;
if (fd < 0)
return EBADF;
if (fd < fds->n)
return 0;
n2 = fd + 1;
if (!(p2 = realloc(fds->p, n2 * sizeof(*fds->p))))
return ENOMEM;
bzero(p2 + fds->n, (n2 - fds->n) * sizeof(*fds->p));
fds->p = p2;
fds->n = n2;
return 0;
}
static textwindows void spawnfds_destroy(struct SpawnFds *fds) {
struct Dll *e;
while ((e = dll_first(fds->closers))) {
struct Closer *closer = CLOSER_CONTAINER(e);
dll_remove(&fds->closers, e);
CloseHandle(closer->handle);
free(closer);
}
free(fds->p);
}
static textwindows int spawnfds_closelater(struct SpawnFds *fds,
int64_t handle) {
struct Closer *closer;
if (!(closer = malloc(sizeof(struct Closer))))
return ENOMEM;
closer->handle = handle;
dll_init(&closer->elem);
dll_make_last(&fds->closers, &closer->elem);
return 0;
}
static textwindows bool spawnfds_exists(struct SpawnFds *fds, int fildes) {
return fildes + 0u < fds->n && fds->p[fildes].kind;
}
static textwindows errno_t spawnfds_close(struct SpawnFds *fds, int fildes) {
if (spawnfds_exists(fds, fildes)) {
fds->p[fildes] = (struct Fd){0};
}
return 0;
}
static textwindows errno_t spawnfds_dup2(struct SpawnFds *fds, int fildes,
int newfildes) {
errno_t err;
struct Fd *old;
if (spawnfds_exists(fds, fildes)) {
old = fds->p + fildes;
} else if (__isfdopen(fildes)) {
old = g_fds.p + fildes;
} else {
return EBADF;
}
if ((err = spawnfds_ensure(fds, newfildes)))
return err;
struct Fd *neu = fds->p + newfildes;
memcpy(neu, old, sizeof(struct Fd));
neu->flags &= ~O_CLOEXEC;
if (!DuplicateHandle(GetCurrentProcess(), neu->handle, GetCurrentProcess(),
&neu->handle, 0, true, kNtDuplicateSameAccess)) {
return EMFILE;
}
spawnfds_closelater(fds, neu->handle);
return 0;
}
static textwindows errno_t spawnfds_open(struct SpawnFds *fds, int64_t dirhand,
const char *path, int oflag, int mode,
int fildes) {
int64_t h;
errno_t err;
char16_t path16[PATH_MAX];
uint32_t perm, share, disp, attr;
if (!strcmp(path, "/dev/null")) {
strcpy16(path16, u"NUL");
} else if (!strcmp(path, "/dev/stdin")) {
return spawnfds_dup2(fds, 0, fildes);
} else if (!strcmp(path, "/dev/stdout")) {
return spawnfds_dup2(fds, 1, fildes);
} else if (!strcmp(path, "/dev/stderr")) {
return spawnfds_dup2(fds, 2, fildes);
} else {
if (__mkntpathath(dirhand, path, 0, path16) == -1)
return errno;
}
if ((err = spawnfds_ensure(fds, fildes)))
return err;
if (GetNtOpenFlags(oflag, mode, &perm, &share, &disp, &attr) != -1 &&
(h = CreateFile(path16, perm, share, &kNtIsInheritable, disp, attr, 0))) {
spawnfds_closelater(fds, h);
fds->p[fildes].kind = kFdFile;
fds->p[fildes].flags = oflag;
fds->p[fildes].mode = mode;
fds->p[fildes].handle = h;
return 0;
} else {
return errno;
}
}
static textwindows errno_t spawnfds_chdir(struct SpawnFds *fds, int64_t dirhand,
const char *path,
int64_t *out_dirhand) {
int64_t h;
char16_t path16[PATH_MAX];
if (__mkntpathath(dirhand, path, 0, path16) != -1 &&
(h = CreateFile(path16, kNtFileGenericRead,
kNtFileShareRead | kNtFileShareWrite | kNtFileShareDelete,
0, kNtOpenExisting,
kNtFileAttributeNormal | kNtFileFlagBackupSemantics,
0))) {
spawnfds_closelater(fds, h);
*out_dirhand = h;
return 0;
} else {
return errno;
}
}
static textwindows errno_t spawnfds_fchdir(struct SpawnFds *fds, int fildes,
int64_t *out_dirhand) {
int64_t h;
if (spawnfds_exists(fds, fildes)) {
h = fds->p[fildes].handle;
} else if (__isfdopen(fildes)) {
h = g_fds.p[fildes].handle;
} else {
return EBADF;
}
*out_dirhand = h;
return 0;
}
static textwindows errno_t posix_spawn_nt_impl(
int *pid, const char *path, const posix_spawn_file_actions_t *file_actions,
const posix_spawnattr_t *attrp, char *const argv[], char *const envp[]) {
// signals, locks, and resources
char *fdspec = 0;
errno_t e = errno;
struct Proc *proc = 0;
struct SpawnFds fds = {0};
int64_t dirhand = AT_FDCWD;
int64_t *lpExplicitHandles = 0;
sigset_t sigmask = __sig_block();
uint32_t dwExplicitHandleCount = 0;
int64_t hCreatorProcess = GetCurrentProcess();
// reserve process tracking object
__proc_lock();
proc = __proc_new();
__proc_unlock();
// setup return path
errno_t err;
if (!proc) {
err = ENOMEM;
ReturnErr:
__undescribe_fds(hCreatorProcess, lpExplicitHandles, dwExplicitHandleCount);
free(fdspec);
if (proc) {
__proc_lock();
dll_make_first(&__proc.free, &proc->elem);
__proc_unlock();
}
spawnfds_destroy(&fds);
__sig_unblock(sigmask);
errno = e;
return err;
}
// fork file descriptor table
for (int fd = g_fds.n; fd--;) {
if (__is_cloexec(g_fds.p + fd))
continue;
if ((err = spawnfds_ensure(&fds, fd)))
goto ReturnErr;
fds.p[fd] = g_fds.p[fd];
}
// apply user file actions
if (file_actions) {
for (struct _posix_faction *a = *file_actions; a && !err; a = a->next) {
char errno_buf[30];
char oflags_buf[128];
char openmode_buf[15];
switch (a->action) {
case _POSIX_SPAWN_CLOSE:
err = spawnfds_close(&fds, a->fildes);
STRACE("spawnfds_close(%d) → %s", a->fildes,
_DescribeErrno(errno_buf, err));
break;
case _POSIX_SPAWN_DUP2:
err = spawnfds_dup2(&fds, a->fildes, a->newfildes);
STRACE("spawnfds_dup2(%d, %d) → %s", a->fildes, a->newfildes,
_DescribeErrno(errno_buf, err));
break;
case _POSIX_SPAWN_OPEN:
err = spawnfds_open(&fds, dirhand, a->path, a->oflag, a->mode,
a->fildes);
STRACE("spawnfds_open(%#s, %s, %s, %d) → %s", a->path,
_DescribeOpenFlags(oflags_buf, a->oflag),
_DescribeOpenMode(openmode_buf, a->oflag, a->mode), a->fildes,
_DescribeErrno(errno_buf, err));
break;
case _POSIX_SPAWN_CHDIR:
err = spawnfds_chdir(&fds, dirhand, a->path, &dirhand);
STRACE("spawnfds_chdir(%#s) → %s", a->path,
_DescribeErrno(errno_buf, err));
break;
case _POSIX_SPAWN_FCHDIR:
err = spawnfds_fchdir(&fds, a->fildes, &dirhand);
STRACE("spawnfds_fchdir(%d) → %s", a->fildes,
_DescribeErrno(errno_buf, err));
break;
default:
__builtin_unreachable();
}
if (err) {
goto ReturnErr;
}
}
}
// figure out flags
uint32_t dwCreationFlags = 0;
short flags = attrp && *attrp ? (*attrp)->flags : 0;
if (flags & (POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSID))
dwCreationFlags |= kNtCreateNewProcessGroup;
// create process startinfo
struct NtStartupInfo startinfo = {
.cb = sizeof(struct NtStartupInfo),
.dwFlags = kNtStartfUsestdhandles,
.hStdInput = spawnfds_handle(&fds, 0),
.hStdOutput = spawnfds_handle(&fds, 1),
.hStdError = spawnfds_handle(&fds, 2),
};
// determine spawn directory
char16_t *lpCurrentDirectory = 0;
if (dirhand != AT_FDCWD) {
lpCurrentDirectory = alloca(PATH_MAX * sizeof(char16_t));
if (!GetFinalPathNameByHandle(dirhand, lpCurrentDirectory, PATH_MAX,
kNtFileNameNormalized | kNtVolumeNameDos)) {
err = GetLastError();
goto ReturnErr;
}
}
// UNC paths break some things when they are not needed.
if (lpCurrentDirectory) {
size_t n = strlen16(lpCurrentDirectory);
if (n > 4 && n < 260 && //
lpCurrentDirectory[0] == '\\' && //
lpCurrentDirectory[1] == '\\' && //
lpCurrentDirectory[2] == '?' && //
lpCurrentDirectory[3] == '\\') {
memmove(lpCurrentDirectory, lpCurrentDirectory + 4,
(n - 4 + 1) * sizeof(char16_t));
}
}
// inherit signal mask
sigset_t childmask;
char maskvar[6 + 21];
if (flags & POSIX_SPAWN_SETSIGMASK) {
childmask = (*attrp)->sigmask;
} else {
childmask = sigmask;
}
FormatUint64(stpcpy(maskvar, "_MASK="), childmask);
// launch process
int rc = -1;
struct NtProcessInformation procinfo;
if (!envp)
envp = environ;
if ((fdspec = __describe_fds(fds.p, fds.n, &startinfo, hCreatorProcess,
&lpExplicitHandles, &dwExplicitHandleCount))) {
rc = ntspawn(&(struct NtSpawnArgs){
dirhand, path, argv, envp, (char *[]){fdspec, maskvar, 0},
dwCreationFlags, lpCurrentDirectory, 0, lpExplicitHandles,
dwExplicitHandleCount, &startinfo, &procinfo});
}
if (rc == -1) {
err = errno;
goto ReturnErr;
}
// return result
CloseHandle(procinfo.hThread);
proc->pid = procinfo.dwProcessId;
proc->handle = procinfo.hProcess;
if (pid)
*pid = proc->pid;
__proc_lock();
__proc_add(proc);
__proc_unlock();
proc = 0;
err = 0;
goto ReturnErr;
}
static const char *DescribePid(char buf[12], int err, int *pid) {
if (err)
return "n/a";
if (!pid)
return "NULL";
FormatInt32(buf, *pid);
return buf;
}
static textwindows dontinline errno_t posix_spawn_nt(
int *pid, const char *path, const posix_spawn_file_actions_t *file_actions,
const posix_spawnattr_t *attrp, char *const argv[], char *const envp[]) {
int err;
if (!path || !argv) {
err = EFAULT;
} else {
err = posix_spawn_nt_impl(pid, path, file_actions, attrp, argv, envp);
}
STRACE("posix_spawn([%s], %#s, %s, %s) → %s",
DescribePid(alloca(12), err, pid), path, DescribeStringList(argv),
DescribeStringList(envp), !err ? "0" : _strerrno(err));
return err;
}
#endif // __x86_64__
/**
* Spawns process, the POSIX way, e.g.
*
* int pid, status;
* posix_spawnattr_t sa;
* posix_spawnattr_init(&sa);
* posix_spawnattr_setflags(&sa, POSIX_SPAWN_SETPGROUP);
* posix_spawn_file_actions_t fa;
* posix_spawn_file_actions_init(&fa);
* posix_spawn_file_actions_addopen(&fa, 0, "/dev/null", O_RDWR, 0644);
* posix_spawn_file_actions_adddup2(&fa, 0, 1);
* posix_spawnp(&pid, "lol", &fa, &sa, (char *[]){"lol", 0}, 0);
* posix_spawnp(&pid, "cat", &fa, &sa, (char *[]){"cat", 0}, 0);
* posix_spawn_file_actions_destroy(&fa);
* posix_spawnattr_destroy(&sa);
* while (wait(&status) != -1);
*
* The posix_spawn() function may be used to launch subprocesses. The
* primary advantage of using posix_spawn() instead of the traditional
* fork() / execve() combination for launching processes is efficiency
* and cross-platform compatibility.
*
* 1. On Linux, FreeBSD, and NetBSD:
*
* Cosmopolitan Libc's posix_spawn() uses vfork() under the hood on
* these platforms automatically, since it's faster than fork(). It's
* because vfork() creates a child process without needing to copy
* the parent's page tables, making it more efficient, especially for
* large processes. Furthermore, vfork() avoids the need to acquire
* every single mutex (see pthread_atfork() for more details) which
* makes it scalable in multi-threaded apps, since the other threads
* in your app can keep going while the spawning thread waits for the
* subprocess to call execve(). Normally vfork() is error-prone since
* there exists few functions that are @vforksafe. the posix_spawn()
* API is designed to offer maximum assurance that you can't shoot
* yourself in the foot. If you do, then file a bug with Cosmo.
*
* 2. On Windows:
*
* posix_spawn() avoids fork() entirely. Windows doesn't natively
* support fork(), and emulating it can be slow and memory-intensive.
* By using posix_spawn(), we get a much faster process creation on
* Windows systems, because it only needs to call CreateProcess().
* Your file actions are replayed beforehand in a simulated way. Only
* Cosmopolitan Libc offers this level of quality. With Cygwin you'd
* have to use its proprietary APIs to achieve the same performance.
*
* 3. Simplified error handling:
*
* posix_spawn() combines process creation and program execution in a
* single call, reducing the points of failure and simplifying error
* handling. One important thing that happens with Cosmopolitan's
* posix_spawn() implementation is that the error code of execve()
* inside your subprocess, should it fail, will be propagated to your
* parent process. This will happen efficiently via vfork() shared
* memory in the event your Linux environment supports this. If it
* doesn't, then Cosmopolitan will fall back to a throwaway pipe().
* The pipe is needed on platforms like XNU and OpenBSD which do not
* support vfork(). It's also needed under QEMU User.
*
* 4. Signal safety:
*
* posix_spawn() guarantees your signal handler callback functions
* won't be executed in the child process. By default, it'll remove
* sigaction() callbacks atomically. This ensures that if something
* like a SIGTERM or SIGHUP is sent to the child process before it's
* had a chance to call execve(), then the child process will simply
* be terminated (like the spawned process would) instead of running
* whatever signal handlers the spawning process has installed. If
* you've set some signals to SIG_IGN, then that'll be preserved for
* the child process by posix_spawn(), unless you explicitly call
* posix_spawnattr_setsigdefault() to reset them.
*
* 5. Portability:
*
* posix_spawn() is part of the POSIX standard, making it more
* portable across different UNIX-like systems and Windows (with
* appropriate libraries). Even the non-POSIX APIs we use here are
* portable; e.g. posix_spawn_file_actions_addchdir_np() is supported
* by glibc, musl libc, and apple libc too.
*
* When using posix_spawn() you have the option of passing an attributes
* object that specifies how the child process should be created. These
* functions are provided by Cosmopolitan Libc for setting attributes:
*
* - posix_spawnattr_init()
* - posix_spawnattr_destroy()
* - posix_spawnattr_setflags()
* - posix_spawnattr_getflags()
* - posix_spawnattr_setsigmask()
* - posix_spawnattr_getsigmask()
* - posix_spawnattr_setpgroup()
* - posix_spawnattr_getpgroup()
* - posix_spawnattr_setrlimit_np()
* - posix_spawnattr_getrlimit_np()
* - posix_spawnattr_setschedparam()
* - posix_spawnattr_getschedparam()
* - posix_spawnattr_setschedpolicy()
* - posix_spawnattr_getschedpolicy()
* - posix_spawnattr_setsigdefault()
* - posix_spawnattr_getsigdefault()
*
* You can also pass an ordered list of file actions to perform. The
* following APIs are provided by Cosmopolitan Libc for doing that:
*
* - posix_spawn_file_actions_init()
* - posix_spawn_file_actions_destroy()
* - posix_spawn_file_actions_adddup2()
* - posix_spawn_file_actions_addopen()
* - posix_spawn_file_actions_addclose()
* - posix_spawn_file_actions_addchdir_np()
* - posix_spawn_file_actions_addfchdir_np()
*
* @param pid if non-null shall be set to child pid on success
* @param path is resolved path of program which is not `$PATH` searched
* @param file_actions specifies close(), dup2(), and open() operations
* @param attrp specifies signal masks, user ids, scheduling, etc.
* @param envp is environment variables, or `environ` if null
* @return 0 on success or error number on failure
* @raise ETXTBSY if another process has `path` open in write mode
* @raise ENOEXEC if file is executable but not a valid format
* @raise ENOMEM if remaining stack memory is insufficient
* @raise EACCES if execute permission was denied
* @see posix_spawnp() for `$PATH` searching
* @returnserrno
* @tlsrequired
*/
errno_t posix_spawn(int *pid, const char *path,
const posix_spawn_file_actions_t *file_actions,
const posix_spawnattr_t *attrp, char *const argv[],
char *const envp[]) {
#ifdef __x86_64__
if (IsWindows())
return posix_spawn_nt(pid, path, file_actions, attrp, argv, envp);
#endif
int pfds[2];
bool use_pipe;
volatile int status = 0;
sigset_t blockall, oldmask;
int child, res, cs, e = errno;
volatile bool can_clobber = false;
short flags = attrp && *attrp ? (*attrp)->flags : 0;
sigfillset(&blockall);
sigprocmask(SIG_SETMASK, &blockall, &oldmask);
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
if ((use_pipe = (flags & POSIX_SPAWN_USEFORK) ||
!atomic_load_explicit(&has_vfork, memory_order_acquire))) {
if (pipe2(pfds, O_CLOEXEC)) {
res = errno;
goto ParentFailed;
}
}
if (!(child = (flags & POSIX_SPAWN_USEFORK) ? fork() : vfork())) {
can_clobber = true;
sigset_t childmask;
bool lost_cloexec = 0;
struct sigaction dfl = {0};
if (use_pipe)
close(pfds[0]);
for (int sig = 1; sig <= NSIG; sig++)
if (__sighandrvas[sig] != (long)SIG_DFL &&
(__sighandrvas[sig] != (long)SIG_IGN ||
((flags & POSIX_SPAWN_SETSIGDEF) &&
sigismember(&(*attrp)->sigdefault, sig) == 1)))
sigaction(sig, &dfl, 0);
if (flags & POSIX_SPAWN_SETSID)
setsid();
if ((flags & POSIX_SPAWN_SETPGROUP) && setpgid(0, (*attrp)->pgroup))
goto ChildFailed;
if ((flags & POSIX_SPAWN_RESETIDS) && setgid(getgid()))
goto ChildFailed;
if ((flags & POSIX_SPAWN_RESETIDS) && setuid(getuid()))
goto ChildFailed;
if (file_actions) {
struct _posix_faction *a;
for (a = *file_actions; a; a = a->next) {
if (use_pipe && pfds[1] == a->fildes) {
int p2;
if ((p2 = dup(pfds[1])) == -1)
goto ChildFailed;
lost_cloexec = true;
close(pfds[1]);
pfds[1] = p2;
}
switch (a->action) {
case _POSIX_SPAWN_CLOSE:
if (close(a->fildes))
goto ChildFailed;
break;
case _POSIX_SPAWN_DUP2:
if (dup2(a->fildes, a->newfildes) == -1)
goto ChildFailed;
break;
case _POSIX_SPAWN_OPEN: {
int t;
if ((t = openat(AT_FDCWD, a->path, a->oflag, a->mode)) == -1)
goto ChildFailed;
if (t != a->fildes) {
if (dup2(t, a->fildes) == -1) {
close(t);
goto ChildFailed;
}
if (close(t))
goto ChildFailed;
}
break;
}
case _POSIX_SPAWN_CHDIR:
if (chdir(a->path) == -1)
goto ChildFailed;
break;
case _POSIX_SPAWN_FCHDIR:
if (fchdir(a->fildes) == -1)
goto ChildFailed;
break;
default:
__builtin_unreachable();
}
}
}
if (IsLinux() || IsFreebsd() || IsNetbsd()) {
if (flags & POSIX_SPAWN_SETSCHEDULER)
if (sched_setscheduler(0, (*attrp)->schedpolicy,
&(*attrp)->schedparam) == -1)
goto ChildFailed;
if (flags & POSIX_SPAWN_SETSCHEDPARAM)
if (sched_setparam(0, &(*attrp)->schedparam))
goto ChildFailed;
}
if (flags & POSIX_SPAWN_SETRLIMIT_NP) {
int rlimset = (*attrp)->rlimset;
while (rlimset) {
int resource = bsf(rlimset);
rlimset &= ~(1u << resource);
if (setrlimit(resource, (*attrp)->rlim + resource)) {
// MacOS ARM64 RLIMIT_STACK always returns EINVAL
if (!IsXnuSilicon()) {
goto ChildFailed;
}
}
}
}
if (lost_cloexec)
fcntl(pfds[1], F_SETFD, FD_CLOEXEC);
if (flags & POSIX_SPAWN_SETSIGMASK) {
childmask = (*attrp)->sigmask;
} else {
childmask = oldmask;
}
sigprocmask(SIG_SETMASK, &childmask, 0);
if (!envp)
envp = environ;
execve(path, argv, envp);
ChildFailed:
res = errno;
if (!use_pipe) {
status = res;
} else {
write(pfds[1], &res, sizeof(res));
}
_Exit(127);
}
if (use_pipe)
close(pfds[1]);
if (child != -1) {
if (!use_pipe) {
res = status;
} else {
if (can_clobber)
atomic_store_explicit(&has_vfork, true, memory_order_release);
res = 0;
read(pfds[0], &res, sizeof(res));
}
if (!res) {
if (pid)
*pid = child;
} else {
wait4(child, 0, 0, 0);
}
} else {
res = errno;
}
if (use_pipe)
close(pfds[0]);
ParentFailed:
sigprocmask(SIG_SETMASK, &oldmask, 0);
pthread_setcancelstate(cs, 0);
errno = e;
return res;
}