Fix handling of paths with dirfd on Windows

This change fixes an issue with all system calls ending with *at(), when
the caller passes `dirfd != AT_FDCWD` and an absolute path. It's because
the old code was turning paths like C:\bin\ls into \\C:\bin\ls\C:\bin\ls
after being converted from paths like /C/bin/ls. I noticed this when the
Emacs dired mode stopped working. It's unclear if it's a regression with
Cosmopolitan Libc or if this was introduced by the Emacs v29 upgrade. It
also impacted posix_spawn() for which a newly minted example now exists.
This commit is contained in:
Justine Tunney 2024-09-01 16:35:48 -07:00
parent a089c07ddc
commit 39e7f24947
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
10 changed files with 373 additions and 46 deletions

232
examples/spawn.c Normal file
View file

@ -0,0 +1,232 @@
#if 0
/*─────────────────────────────────────────────────────────────────╗
To the extent possible under law, Justine Tunney has waived
all copyright and related or neighboring rights to this file,
as it is written in the following disclaimers:
http://unlicense.org/ │
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
// posix_spawn() example
//
// This program demonstrates the use of posix_spawn() to run the command
// `ls --dired` and capture its output. It teaches several key features:
//
// - Changing the working directory for the child process
// - Redirecting stdout and stderr to pipes
// - Handling the output from the child process
//
// The primary advantage of using posix_spawn() instead of the
// traditional fork()/execve() combination for launching processes is
// safety, 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, freebsd, and apple too.
//
// These benefits make posix_spawn() a preferred choice for efficient
// and portable process creation in many scenarios, especially when
// launching many processes or on systems where process creation
// performance is critical.
#define _GNU_SOURCE
#include <fcntl.h>
#include <spawn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#define PIPE_READ 0
#define PIPE_WRITE 1
int main() {
pid_t pid;
int status, ret;
posix_spawnattr_t attr;
posix_spawn_file_actions_t actions;
char *const argv[] = {"ls", "--dired", NULL};
int pipe_stdout[2], pipe_stderr[2];
// Initialize file actions
ret = posix_spawnattr_init(&attr);
if (ret != 0) {
fprintf(stderr, "posix_spawnattr_init failed: %s\n", strerror(ret));
return 1;
}
// Explicitly request vfork() from posix_spawn() implementation
//
// This is currently the default for Cosmopolitan Libc, however you
// may want to set this anyway, for portability with other platforms.
// Please note that vfork() isn't officially specified by POSIX, so
// portable code may want to omit this and just use the default.
ret = posix_spawnattr_setflags(&attr, POSIX_SPAWN_USEVFORK);
if (ret != 0) {
fprintf(stderr, "posix_spawnattr_setflags failed: %s\n", strerror(ret));
return 1;
}
// Initialize file actions
ret = posix_spawn_file_actions_init(&actions);
if (ret != 0) {
fprintf(stderr, "posix_spawn_file_actions_init failed: %s\n",
strerror(ret));
return 1;
}
// Change directory to $HOME
ret = posix_spawn_file_actions_addchdir_np(&actions, getenv("HOME"));
if (ret != 0) {
fprintf(stderr, "posix_spawn_file_actions_addchdir_np failed: %s\n",
strerror(ret));
return 1;
}
// Create pipes for stdout and stderr
if (pipe(pipe_stdout) == -1 || pipe(pipe_stderr) == -1) {
perror("pipe");
return 1;
}
// Redirect child's stdout to pipe
ret = posix_spawn_file_actions_adddup2(&actions, pipe_stdout[PIPE_WRITE],
STDOUT_FILENO);
if (ret != 0) {
fprintf(stderr, "posix_spawn_file_actions_adddup2 (stdout) failed: %s\n",
strerror(ret));
return 1;
}
// Redirect child's stderr to pipe
ret = posix_spawn_file_actions_adddup2(&actions, pipe_stderr[PIPE_WRITE],
STDERR_FILENO);
if (ret != 0) {
fprintf(stderr, "posix_spawn_file_actions_adddup2 (stderr) failed: %s\n",
strerror(ret));
return 1;
}
// Close unused write ends of pipes in the child process
ret = posix_spawn_file_actions_addclose(&actions, pipe_stdout[PIPE_READ]);
if (ret != 0) {
fprintf(stderr,
"posix_spawn_file_actions_addclose (stdout read) failed: %s\n",
strerror(ret));
return 1;
}
ret = posix_spawn_file_actions_addclose(&actions, pipe_stderr[PIPE_READ]);
if (ret != 0) {
fprintf(stderr,
"posix_spawn_file_actions_addclose (stderr read) failed: %s\n",
strerror(ret));
return 1;
}
// Spawn the child process
ret = posix_spawnp(&pid, "ls", &actions, NULL, argv, NULL);
if (ret != 0) {
fprintf(stderr, "posix_spawn failed: %s\n", strerror(ret));
return 1;
}
// Close unused write ends of pipes in the parent process
close(pipe_stdout[PIPE_WRITE]);
close(pipe_stderr[PIPE_WRITE]);
// Read and print output from child process
char buffer[4096];
ssize_t bytes_read;
printf("Stdout from child process:\n");
while ((bytes_read = read(pipe_stdout[PIPE_READ], buffer, sizeof(buffer))) >
0) {
write(STDOUT_FILENO, buffer, bytes_read);
}
printf("\nStderr from child process:\n");
while ((bytes_read = read(pipe_stderr[PIPE_READ], buffer, sizeof(buffer))) >
0) {
write(STDERR_FILENO, buffer, bytes_read);
}
// Wait for the child process to complete
if (waitpid(pid, &status, 0) == -1) {
perror("waitpid");
return 1;
}
// Clean up
posix_spawn_file_actions_destroy(&actions);
posix_spawnattr_destroy(&attr);
close(pipe_stdout[PIPE_READ]);
close(pipe_stderr[PIPE_READ]);
if (WIFEXITED(status)) {
printf("Child process exited with status %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("Child process terminated with signal %s\n",
strsignal(WTERMSIG(status)));
} else {
printf("Child process did not exit normally\n");
}
return 0;
}

View file

@ -18,6 +18,7 @@
*/ */
#include "libc/calls/internal.h" #include "libc/calls/internal.h"
#include "libc/calls/syscall_support-nt.internal.h" #include "libc/calls/syscall_support-nt.internal.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/strace.h" #include "libc/intrin/strace.h"
#include "libc/macros.h" #include "libc/macros.h"
#include "libc/nt/enum/fileflagandattributes.h" #include "libc/nt/enum/fileflagandattributes.h"
@ -27,6 +28,18 @@
#include "libc/sysv/consts/at.h" #include "libc/sysv/consts/at.h"
#include "libc/sysv/errfuns.h" #include "libc/sysv/errfuns.h"
static int IsAlpha(int c) {
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
}
static bool IsAbsolutePathWin32(char16_t *path) {
if (path[0] == '\\')
return true;
if (IsAlpha(path[0]) && path[1] == ':')
return true;
return false;
}
static textwindows int __mkntpathath_impl(int64_t dirhand, const char *path, static textwindows int __mkntpathath_impl(int64_t dirhand, const char *path,
int flags, int flags,
char16_t file[hasatleast PATH_MAX]) { char16_t file[hasatleast PATH_MAX]) {
@ -39,7 +52,7 @@ static textwindows int __mkntpathath_impl(int64_t dirhand, const char *path,
return -1; return -1;
if (!filelen) if (!filelen)
return enoent(); return enoent();
if (file[0] != u'\\' && dirhand != AT_FDCWD) { // ProTip: \\?\C:\foo if (dirhand != AT_FDCWD && !IsAbsolutePathWin32(file)) {
dirlen = GetFinalPathNameByHandle(dirhand, dir, ARRAYLEN(dir), dirlen = GetFinalPathNameByHandle(dirhand, dir, ARRAYLEN(dir),
kNtFileNameNormalized | kNtVolumeNameDos); kNtFileNameNormalized | kNtVolumeNameDos);
if (!dirlen) if (!dirlen)
@ -49,7 +62,8 @@ static textwindows int __mkntpathath_impl(int64_t dirhand, const char *path,
dir[dirlen] = u'\\'; dir[dirlen] = u'\\';
memcpy(dir + dirlen + 1, file, (filelen + 1) * sizeof(char16_t)); memcpy(dir + dirlen + 1, file, (filelen + 1) * sizeof(char16_t));
memcpy(file, dir, ((n = dirlen + 1 + filelen) + 1) * sizeof(char16_t)); memcpy(file, dir, ((n = dirlen + 1 + filelen) + 1) * sizeof(char16_t));
return __normntpath(file, n); int res = __normntpath(file, n);
return res;
} else { } else {
return filelen; return filelen;
} }

View file

@ -56,7 +56,7 @@ TryAgain:
hHandle = __imp_CreateFileW(lpFileName, dwDesiredAccess, dwShareMode, hHandle = __imp_CreateFileW(lpFileName, dwDesiredAccess, dwShareMode,
opt_lpSecurity, dwCreationDisposition, opt_lpSecurity, dwCreationDisposition,
dwFlagsAndAttributes, opt_hTemplateFile); dwFlagsAndAttributes, opt_hTemplateFile);
NTTRACE("CreateFile(%#hs, %s, %s, %s, %s, %s, %ld) → {%ld, %d}", lpFileName, NTTRACE("CreateFile(%#!hs, %s, %s, %s, %s, %s, %ld) → {%ld, %d}", lpFileName,
_DescribeNtFileAccessFlags(buf_accessflags, dwDesiredAccess), _DescribeNtFileAccessFlags(buf_accessflags, dwDesiredAccess),
_DescribeNtFileShareFlags(buf_shareflags, dwShareMode), _DescribeNtFileShareFlags(buf_shareflags, dwShareMode),
_DescribeNtSecurityAttributes(buf_secattr, opt_lpSecurity), _DescribeNtSecurityAttributes(buf_secattr, opt_lpSecurity),

View file

@ -23,6 +23,7 @@
#include "libc/intrin/likely.h" #include "libc/intrin/likely.h"
#include "libc/intrin/strace.h" #include "libc/intrin/strace.h"
#include "libc/nt/runtime.h" #include "libc/nt/runtime.h"
#include "libc/nt/struct/iovec.h"
#include "libc/nt/thunk/msabi.h" #include "libc/nt/thunk/msabi.h"
#include "libc/nt/winsock.h" #include "libc/nt/winsock.h"
#include "libc/runtime/runtime.h" #include "libc/runtime/runtime.h"
@ -54,8 +55,8 @@ textwindows int WSARecvFrom(
} }
if (UNLIKELY(__strace > 0) && strace_enabled(0) > 0) { if (UNLIKELY(__strace > 0) && strace_enabled(0) > 0) {
kprintf(STRACE_PROLOGUE "WSARecvFrom(%lu, [", s); kprintf(STRACE_PROLOGUE "WSARecvFrom(%lu, [", s);
DescribeIovNt(inout_lpBuffers, dwBufferCount, _DescribeIovNt(inout_lpBuffers, dwBufferCount,
rc != -1 ? NumberOfBytesRecvd : 0); rc != -1 ? NumberOfBytesRecvd : 0);
kprintf("], %u, [%'u], %p, %p, %p, %s, %p) → %d %d\n", dwBufferCount, kprintf("], %u, [%'u], %p, %p, %p, %s, %p) → %d %d\n", dwBufferCount,
NumberOfBytesRecvd, opt_out_fromsockaddr, opt_inout_fromsockaddrlen, NumberOfBytesRecvd, opt_out_fromsockaddr, opt_inout_fromsockaddrlen,
inout_lpFlags, DescribeNtOverlapped(opt_inout_lpOverlapped), inout_lpFlags, DescribeNtOverlapped(opt_inout_lpOverlapped),

View file

@ -450,23 +450,101 @@ static textwindows dontinline errno_t posix_spawn_nt(
* posix_spawnattr_destroy(&sa); * posix_spawnattr_destroy(&sa);
* while (wait(&status) != -1); * while (wait(&status) != -1);
* *
* This provides superior process creation performance across systems * 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.
* *
* Processes are normally spawned by calling fork() and execve(), but * 1. On Linux, FreeBSD, and NetBSD:
* that goes slow on Windows if the caller has allocated a nontrivial *
* number of memory mappings, all of which need to be copied into the * Cosmopolitan Libc's posix_spawn() uses vfork() under the hood on
* forked child, only to be destroyed a moment later. On UNIX systems * these platforms automatically, since it's faster than fork(). It's
* fork() bears a similar cost that's 100x less bad, which is copying * because vfork() creates a child process without needing to copy
* the page tables. So what this implementation does is on Windows it * the parent's page tables, making it more efficient, especially for
* calls CreateProcess() directly and on UNIX it uses vfork() if it's * large processes. Furthermore, vfork() avoids the need to acquire
* possible (XNU and OpenBSD don't have it). On UNIX this API has the * every single mutex (see pthread_atfork() for more details) which
* benefit of avoiding the footguns of using vfork() directly because * makes it scalable in multi-threaded apps, since the other threads
* this implementation will ensure signal handlers can't be called in * in your app can keep going while the spawning thread waits for the
* the child process since that'd likely corrupt the parent's memory. * subprocess to call execve(). Normally vfork() is error-prone since
* On systems with a real vfork() implementation, the execve() status * there exists few functions that are @vforksafe. the posix_spawn()
* code is returned by this function via shared memory; otherwise, it * API is designed to offer maximum assurance that you can't shoot
* gets passed via a temporary pipe (on systems like QEmu, Blink, and * yourself in the foot. If you do, then file a bug with Cosmo.
* XNU/OpenBSD) whose support is auto-detected at runtime. *
* 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 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 path is resolved path of program which is not `$PATH` searched
@ -496,31 +574,30 @@ errno_t posix_spawn(int *pid, const char *path,
sigset_t blockall, oldmask; sigset_t blockall, oldmask;
int child, res, cs, e = errno; int child, res, cs, e = errno;
volatile bool can_clobber = false; volatile bool can_clobber = false;
short flags = attrp && *attrp ? (*attrp)->flags : 0;
sigfillset(&blockall); sigfillset(&blockall);
sigprocmask(SIG_SETMASK, &blockall, &oldmask); sigprocmask(SIG_SETMASK, &blockall, &oldmask);
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs); pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
if ((use_pipe = !atomic_load_explicit(&has_vfork, memory_order_acquire))) { if ((use_pipe = (flags & POSIX_SPAWN_USEFORK) ||
!atomic_load_explicit(&has_vfork, memory_order_acquire))) {
if (pipe2(pfds, O_CLOEXEC)) { if (pipe2(pfds, O_CLOEXEC)) {
res = errno; res = errno;
goto ParentFailed; goto ParentFailed;
} }
} }
if (!(child = vfork())) { if (!(child = (flags & POSIX_SPAWN_USEFORK) ? fork() : vfork())) {
can_clobber = true; can_clobber = true;
sigset_t childmask; sigset_t childmask;
bool lost_cloexec = 0; bool lost_cloexec = 0;
struct sigaction dfl = {0}; struct sigaction dfl = {0};
short flags = attrp && *attrp ? (*attrp)->flags : 0;
if (use_pipe) if (use_pipe)
close(pfds[0]); close(pfds[0]);
for (int sig = 1; sig < _NSIG; sig++) { for (int sig = 1; sig < _NSIG; sig++)
if (__sighandrvas[sig] != (long)SIG_DFL && if (__sighandrvas[sig] != (long)SIG_DFL &&
(__sighandrvas[sig] != (long)SIG_IGN || (__sighandrvas[sig] != (long)SIG_IGN ||
((flags & POSIX_SPAWN_SETSIGDEF) && ((flags & POSIX_SPAWN_SETSIGDEF) &&
sigismember(&(*attrp)->sigdefault, sig) == 1))) { sigismember(&(*attrp)->sigdefault, sig) == 1)))
sigaction(sig, &dfl, 0); sigaction(sig, &dfl, 0);
}
}
if (flags & POSIX_SPAWN_SETSID) if (flags & POSIX_SPAWN_SETSID)
setsid(); setsid();
if ((flags & POSIX_SPAWN_SETPGROUP) && setpgid(0, (*attrp)->pgroup)) if ((flags & POSIX_SPAWN_SETPGROUP) && setpgid(0, (*attrp)->pgroup))
@ -585,7 +662,7 @@ errno_t posix_spawn(int *pid, const char *path,
if (sched_setparam(0, &(*attrp)->schedparam)) if (sched_setparam(0, &(*attrp)->schedparam))
goto ChildFailed; goto ChildFailed;
} }
if (flags & POSIX_SPAWN_SETRLIMIT) { if (flags & POSIX_SPAWN_SETRLIMIT_NP) {
int rlimset = (*attrp)->rlimset; int rlimset = (*attrp)->rlimset;
while (rlimset) { while (rlimset) {
int resource = bsf(rlimset); int resource = bsf(rlimset);
@ -618,9 +695,8 @@ errno_t posix_spawn(int *pid, const char *path,
} }
_Exit(127); _Exit(127);
} }
if (use_pipe) { if (use_pipe)
close(pfds[1]); close(pfds[1]);
}
if (child != -1) { if (child != -1) {
if (!use_pipe) { if (!use_pipe) {
res = status; res = status;

View file

@ -12,7 +12,8 @@
#define POSIX_SPAWN_SETSCHEDPARAM 16 #define POSIX_SPAWN_SETSCHEDPARAM 16
#define POSIX_SPAWN_SETSCHEDULER 32 #define POSIX_SPAWN_SETSCHEDULER 32
#define POSIX_SPAWN_SETSID 128 #define POSIX_SPAWN_SETSID 128
#define POSIX_SPAWN_SETRLIMIT 256 #define POSIX_SPAWN_SETRLIMIT_NP 256
#define POSIX_SPAWN_USEFORK 512
COSMOPOLITAN_C_START_ COSMOPOLITAN_C_START_
@ -55,10 +56,10 @@ int posix_spawnattr_getsigdefault(const posix_spawnattr_t *,
sigset_t *) libcesque; sigset_t *) libcesque;
int posix_spawnattr_setsigdefault(posix_spawnattr_t *, int posix_spawnattr_setsigdefault(posix_spawnattr_t *,
const sigset_t *) libcesque; const sigset_t *) libcesque;
int posix_spawnattr_getrlimit(const posix_spawnattr_t *, int, int posix_spawnattr_getrlimit_np(const posix_spawnattr_t *, int,
struct rlimit *) libcesque; struct rlimit *) libcesque;
int posix_spawnattr_setrlimit(posix_spawnattr_t *, int, int posix_spawnattr_setrlimit_np(posix_spawnattr_t *, int,
const struct rlimit *) libcesque; const struct rlimit *) libcesque;
COSMOPOLITAN_C_END_ COSMOPOLITAN_C_END_
#endif /* COSMOPOLITAN_LIBC_STDIO_SPAWN_H_ */ #endif /* COSMOPOLITAN_LIBC_STDIO_SPAWN_H_ */

View file

@ -31,8 +31,8 @@
* @raise EINVAL if `resource` is invalid or unsupported by host * @raise EINVAL if `resource` is invalid or unsupported by host
* @raise ENOENT if `resource` is absent * @raise ENOENT if `resource` is absent
*/ */
int posix_spawnattr_getrlimit(const posix_spawnattr_t *attr, int resource, int posix_spawnattr_getrlimit_np(const posix_spawnattr_t *attr, int resource,
struct rlimit *rlim) { struct rlimit *rlim) {
if (0 <= resource && resource < MIN(RLIM_NLIMITS, ARRAYLEN((*attr)->rlim))) { if (0 <= resource && resource < MIN(RLIM_NLIMITS, ARRAYLEN((*attr)->rlim))) {
if (((*attr)->rlimset & (1u << resource))) { if (((*attr)->rlimset & (1u << resource))) {
*rlim = (*attr)->rlim[resource]; *rlim = (*attr)->rlim[resource];

View file

@ -25,6 +25,8 @@
* *
* @param attr was initialized by posix_spawnattr_init() * @param attr was initialized by posix_spawnattr_init()
* @param flags may have any of the following * @param flags may have any of the following
* - `POSIX_SPAWN_USEFORK`
* - `POSIX_SPAWN_USEVFORK`
* - `POSIX_SPAWN_RESETIDS` * - `POSIX_SPAWN_RESETIDS`
* - `POSIX_SPAWN_SETPGROUP` * - `POSIX_SPAWN_SETPGROUP`
* - `POSIX_SPAWN_SETSIGDEF` * - `POSIX_SPAWN_SETSIGDEF`
@ -32,12 +34,13 @@
* - `POSIX_SPAWN_SETSCHEDPARAM` * - `POSIX_SPAWN_SETSCHEDPARAM`
* - `POSIX_SPAWN_SETSCHEDULER` * - `POSIX_SPAWN_SETSCHEDULER`
* - `POSIX_SPAWN_SETSID` * - `POSIX_SPAWN_SETSID`
* - `POSIX_SPAWN_SETRLIMIT` * - `POSIX_SPAWN_SETRLIMIT_NP`
* @return 0 on success, or errno on error * @return 0 on success, or errno on error
* @raise EINVAL if `flags` has invalid bits * @raise EINVAL if `flags` has invalid bits
*/ */
int posix_spawnattr_setflags(posix_spawnattr_t *attr, short flags) { int posix_spawnattr_setflags(posix_spawnattr_t *attr, short flags) {
if (flags & ~(POSIX_SPAWN_USEVFORK | // if (flags & ~(POSIX_SPAWN_USEFORK | //
POSIX_SPAWN_USEVFORK | //
POSIX_SPAWN_RESETIDS | // POSIX_SPAWN_RESETIDS | //
POSIX_SPAWN_SETPGROUP | // POSIX_SPAWN_SETPGROUP | //
POSIX_SPAWN_SETSIGDEF | // POSIX_SPAWN_SETSIGDEF | //
@ -45,7 +48,7 @@ int posix_spawnattr_setflags(posix_spawnattr_t *attr, short flags) {
POSIX_SPAWN_SETSCHEDPARAM | // POSIX_SPAWN_SETSCHEDPARAM | //
POSIX_SPAWN_SETSCHEDULER | // POSIX_SPAWN_SETSCHEDULER | //
POSIX_SPAWN_SETSID | // POSIX_SPAWN_SETSID | //
POSIX_SPAWN_SETRLIMIT)) { POSIX_SPAWN_SETRLIMIT_NP)) {
return EINVAL; return EINVAL;
} }
(*attr)->flags = flags; (*attr)->flags = flags;

View file

@ -26,14 +26,14 @@
/** /**
* Sets resource limit on spawned process. * Sets resource limit on spawned process.
* *
* You also need to pass `POSIX_SPAWN_SETRLIMIT` to * You also need to pass `POSIX_SPAWN_SETRLIMIT_NP` to
* posix_spawnattr_setflags() for it to take effect. * posix_spawnattr_setflags() for it to take effect.
* *
* @return 0 on success, or errno on error * @return 0 on success, or errno on error
* @raise EINVAL if resource is invalid * @raise EINVAL if resource is invalid
*/ */
int posix_spawnattr_setrlimit(posix_spawnattr_t *attr, int resource, int posix_spawnattr_setrlimit_np(posix_spawnattr_t *attr, int resource,
const struct rlimit *rlim) { const struct rlimit *rlim) {
if (0 <= resource && resource < MIN(RLIM_NLIMITS, ARRAYLEN((*attr)->rlim))) { if (0 <= resource && resource < MIN(RLIM_NLIMITS, ARRAYLEN((*attr)->rlim))) {
(*attr)->rlimset |= 1u << resource; (*attr)->rlimset |= 1u << resource;
(*attr)->rlim[resource] = *rlim; (*attr)->rlim[resource] = *rlim;

View file

@ -529,7 +529,7 @@ void PlanResource(int resource, struct rlimit rlim) {
return; return;
rlim.rlim_cur = MIN(rlim.rlim_cur, prior.rlim_max); rlim.rlim_cur = MIN(rlim.rlim_cur, prior.rlim_max);
rlim.rlim_max = MIN(rlim.rlim_max, prior.rlim_max); rlim.rlim_max = MIN(rlim.rlim_max, prior.rlim_max);
posix_spawnattr_setrlimit(&spawnattr, resource, &rlim); posix_spawnattr_setrlimit_np(&spawnattr, resource, &rlim);
} }
void SetCpuLimit(int secs) { void SetCpuLimit(int secs) {
@ -651,7 +651,7 @@ int Launch(void) {
posix_spawnattr_init(&spawnattr); posix_spawnattr_init(&spawnattr);
posix_spawnattr_setsigmask(&spawnattr, &savemask); posix_spawnattr_setsigmask(&spawnattr, &savemask);
posix_spawnattr_setflags(&spawnattr, posix_spawnattr_setflags(&spawnattr,
POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETRLIMIT); POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETRLIMIT_NP);
SetCpuLimit(cpuquota); SetCpuLimit(cpuquota);
SetFszLimit(fszquota); SetFszLimit(fszquota);
SetMemLimit(memquota); SetMemLimit(memquota);