Overhaul process spawning

This commit is contained in:
Justine Tunney 2023-09-10 08:12:43 -07:00
parent 99dc1281f5
commit 26e254fb4d
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
96 changed files with 1848 additions and 1541 deletions

View file

@ -1,7 +1,7 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2021 Justine Alexandra Roberts Tunney
Copyright 2023 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
@ -16,28 +16,16 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/nexgen32e/kcpuids.h"
#include "libc/nexgen32e/vendor.internal.h"
#include "libc/nexgen32e/x86feature.h"
#include "libc/nexgen32e/x86info.h"
#include "libc/stdio/rand.h"
#include "libc/paths.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/errfuns.h"
textstartup void rdrand_init(int argc, char **argv, char **envp,
intptr_t *auxv) {
extern unsigned kMutableCpuids[KCPUIDS_LEN][4] asm("kCpuids");
/*
* Clear RDRAND on AMD models before Zen and then some
* since it's not only slow but can freeze after sleep
* https://bugzilla.redhat.com/show_bug.cgi?id=1150286
*/
if ((X86_HAVE(RDRND) || X86_HAVE(RDSEED)) &&
(IsAuthenticAMD() &&
(kX86CpuFamily < 0x17 ||
(kX86CpuFamily == 0x17 &&
(0x70 <= kX86CpuModel && kX86CpuModel <= 0x7F))))) {
kMutableCpuids[KCPUIDS_1H][KCPUIDS_ECX] &= ~(1u << 30);
kMutableCpuids[KCPUIDS_7H][KCPUIDS_EBX] &= ~(1u << 18);
size_t confstr(int name, char *buf, size_t len) {
if (name == _CS_PATH) {
return strlcpy(buf, _PATH_DEFPATH, len) + 1;
} else {
einval();
return 0;
}
}
const void *const g_rdrand_init[] initarray = {rdrand_init};

View file

@ -1,276 +0,0 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2020 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/assert.h"
#include "libc/calls/blockcancel.internal.h"
#include "libc/calls/calls.h"
#include "libc/calls/cp.internal.h"
#include "libc/calls/internal.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/calls/syscall_support-sysv.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/asmflag.h"
#include "libc/intrin/bits.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/macros.internal.h"
#include "libc/nexgen32e/kcpuids.h"
#include "libc/nexgen32e/rdtsc.h"
#include "libc/nexgen32e/vendor.internal.h"
#include "libc/nexgen32e/x86feature.h"
#include "libc/nexgen32e/x86info.h"
#include "libc/nt/runtime.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/rand.h"
#include "libc/stdio/xorshift.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/consts/auxv.h"
#include "libc/sysv/consts/grnd.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/thread.h"
__static_yoink("rdrand_init");
int sys_getentropy(void *, size_t) asm("sys_getrandom");
static bool have_getrandom;
static bool GetRandomRdseed(uint64_t *out) {
int i;
char cf;
uint64_t x;
for (i = 0; i < 10; ++i) {
asm volatile(CFLAG_ASM("rdseed\t%1")
: CFLAG_CONSTRAINT(cf), "=r"(x)
: /* no inputs */
: "cc");
if (cf) {
*out = x;
return true;
}
asm volatile("pause");
}
return false;
}
static bool GetRandomRdrand(uint64_t *out) {
int i;
char cf;
uint64_t x;
for (i = 0; i < 10; ++i) {
asm volatile(CFLAG_ASM("rdrand\t%1")
: CFLAG_CONSTRAINT(cf), "=r"(x)
: /* no inputs */
: "cc");
if (cf) {
*out = x;
return true;
}
asm volatile("pause");
}
return false;
}
static ssize_t GetRandomCpu(char *p, size_t n, int f, bool impl(uint64_t *)) {
uint64_t x;
size_t i, j;
for (i = 0; i < n; i += j) {
TryAgain:
if (!impl(&x)) {
if (f || i >= 256) break;
goto TryAgain;
}
for (j = 0; j < 8 && i + j < n; ++j) {
p[i + j] = x;
x >>= 8;
}
}
return n;
}
static ssize_t GetRandomMetal(char *p, size_t n, int f) {
if (f & GRND_RANDOM) {
if (X86_HAVE(RDSEED)) {
return GetRandomCpu(p, n, f, GetRandomRdseed);
} else {
return enosys();
}
} else {
if (X86_HAVE(RDRND)) {
return GetRandomCpu(p, n, f, GetRandomRdrand);
} else {
return enosys();
}
}
}
static void GetRandomEntropy(char *p, size_t n) {
unassert(n <= 256);
if (sys_getentropy(p, n)) notpossible;
}
static void GetRandomArnd(char *p, size_t n) {
size_t m;
int cmd[2];
cmd[0] = 1; // CTL_KERN
cmd[1] = IsFreebsd() ? 37 : 81; // KERN_ARND
unassert((m = n) <= 256);
if (sys_sysctl(cmd, 2, p, &n, 0, 0) == -1) notpossible;
if (m != n) notpossible;
}
static ssize_t GetRandomBsd(char *p, size_t n, void impl(char *, size_t)) {
errno_t e;
size_t m, i;
if (_weaken(pthread_testcancel_np) &&
(e = _weaken(pthread_testcancel_np)())) {
errno = e;
return -1;
}
for (i = 0;;) {
m = MIN(n - i, 256);
impl(p + i, m);
if ((i += m) == n) {
return n;
}
if (_weaken(pthread_testcancel)) {
_weaken(pthread_testcancel)();
}
}
}
static ssize_t GetDevUrandom(char *p, size_t n) {
int fd;
ssize_t rc;
fd = sys_openat(AT_FDCWD, "/dev/urandom", O_RDONLY | O_CLOEXEC, 0);
if (fd == -1) return -1;
pthread_cleanup_push((void *)sys_close, (void *)(intptr_t)fd);
rc = sys_read(fd, p, n);
pthread_cleanup_pop(1);
return rc;
}
ssize_t __getrandom(void *p, size_t n, unsigned f) {
ssize_t rc;
if (IsWindows()) {
rc = RtlGenRandom(p, n) ? n : __winerr();
} else if (have_getrandom) {
if (IsXnu() || IsOpenbsd()) {
rc = GetRandomBsd(p, n, GetRandomEntropy);
} else {
BEGIN_CANCELLATION_POINT;
rc = sys_getrandom(p, n, f);
END_CANCELLATION_POINT;
}
} else if (IsFreebsd() || IsNetbsd()) {
rc = GetRandomBsd(p, n, GetRandomArnd);
} else if (IsMetal()) {
rc = GetRandomMetal(p, n, f);
} else {
BEGIN_CANCELLATION_POINT;
rc = GetDevUrandom(p, n);
END_CANCELLATION_POINT;
}
return rc;
}
/**
* Returns cryptographic random data.
*
* This random number seed generator obtains information from:
*
* - RtlGenRandom() on Windows
* - getentropy() on XNU and OpenBSD
* - getrandom() on Linux, FreeBSD, and NetBSD
* - sysctl(KERN_ARND) on older versions of FreeBSD and NetBSD
*
* Unlike getentropy() this function is interruptible. However EINTR
* shouldn't be possible if `f` is zero and `n` is no more than 256,
* noting that kernels are a bit vague with their promises here, and
* if you're willing to trade some performance for a more assurances
* that EINTR won't happen, then either consider using getentropy(),
* or using the `SA_RESTART` flag on your signal handlers.
*
* Unlike getentropy() you may specify an `n` greater than 256. When
* larger amounts are specified, the caller must be prepared for the
* case where fewer than `n` bytes are returned. In that case, it is
* likely that a signal delivery occured. Cancellations in mask mode
* also need to be suppressed while processing the bytes beyond 256.
* On BSD OSes, this entire process is uninterruptible so be careful
* when using large sizes if interruptibility is needed.
*
* Unlike getentropy() this function is a cancellation point. But it
* shouldn't be a problem, unless you're using masked mode, in which
* case extra care must be taken to consider the result.
*
* It's recommended that `f` be set to zero, although it may include
* the following flags:
*
* - `GRND_NONBLOCK` when you want to elevate the insecurity of your
* random data
*
* - `GRND_RANDOM` if you want to have the best possible chance your
* program will freeze and the system operator is paged to address
* the outage by driving to the data center and jiggling the mouse
*
* @note this function could block a nontrivial time on old computers
* @note this function is indeed intended for cryptography
* @note this function takes around 900 cycles
* @raise EINVAL if `f` is invalid
* @raise ECANCELED if thread was cancelled in masked mode
* @raise EFAULT if the `n` bytes at `p` aren't valid memory
* @raise EINTR if we needed to block and a signal was delivered instead
* @cancellationpoint
* @asyncsignalsafe
* @restartable
* @vforksafe
*/
ssize_t getrandom(void *p, size_t n, unsigned f) {
ssize_t rc;
if ((!p && n) || (IsAsan() && !__asan_is_valid(p, n))) {
rc = efault();
} else if ((f & ~(GRND_RANDOM | GRND_NONBLOCK))) {
rc = einval();
} else {
rc = __getrandom(p, n, f);
}
STRACE("getrandom(%p, %'zu, %#x) → %'ld% m", p, n, f, rc);
return rc;
}
__attribute__((__constructor__)) static textstartup void getrandom_init(void) {
int e, rc;
if (IsWindows() || IsMetal()) return;
BLOCK_CANCELLATIONS;
e = errno;
if (!(rc = sys_getrandom(0, 0, 0))) {
have_getrandom = true;
} else {
errno = e;
}
ALLOW_CANCELLATIONS;
STRACE("sys_getrandom(0,0,0) → %d% m", rc);
}

View file

@ -22,6 +22,7 @@
#include "libc/intrin/weaken.h"
#include "libc/paths.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/fflush.internal.h"
#include "libc/stdio/internal.h"
#include "libc/stdio/stdio.h"
#include "libc/sysv/consts/f.h"
@ -42,6 +43,7 @@
* @param mode can be:
* - `"r"` for reading from subprocess standard output
* - `"w"` for writing to subprocess standard input
* - `"e"` for `O_CLOEXEC` on returned file
* @raise EINVAL if `mode` is invalid or specifies read+write
* @raise EMFILE if process `RLIMIT_NOFILE` has been reached
* @raise ENFILE if system-wide file limit has been reached
@ -53,7 +55,7 @@
* @threadsafe
*/
FILE *popen(const char *cmdline, const char *mode) {
FILE *f;
FILE *f, *f2;
int e, rc, pid, dir, flags, pipefds[2];
flags = fopenflags(mode);
if ((flags & O_ACCMODE) == O_RDONLY) {
@ -77,6 +79,14 @@ FILE *popen(const char *cmdline, const char *mode) {
// we can't rely on cloexec because cocmd builtins don't execve
if (pipefds[0] != !dir) unassert(!close(pipefds[0]));
if (pipefds[1] != !dir) unassert(!close(pipefds[1]));
// "The popen() function shall ensure that any streams from
// previous popen() calls that remain open in the parent
// process are closed in the new child process." -POSIX
for (int i = 0; i < __fflush.handles.i; ++i) {
if ((f2 = __fflush.handles.p[i]) && f2->pid) {
close(f2->fd);
}
}
_Exit(_cocmd(3,
(char *[]){
"popen",
@ -88,18 +98,21 @@ FILE *popen(const char *cmdline, const char *mode) {
default:
f->pid = pid;
unassert(!close(pipefds[!dir]));
if (!(flags & O_CLOEXEC)) {
unassert(!fcntl(pipefds[dir], F_SETFD, 0));
}
return f;
case -1:
e = errno;
unassert(!fclose(f));
unassert(!close(pipefds[!dir]));
fclose(f);
close(pipefds[!dir]);
errno = e;
return NULL;
}
} else {
e = errno;
unassert(!close(pipefds[0]));
unassert(!close(pipefds[1]));
close(pipefds[0]);
close(pipefds[1]);
errno = e;
return NULL;
}

View file

@ -21,9 +21,13 @@
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/ntspawn.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/calls/struct/rlimit.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/dce.h"
#include "libc/errno.h"
#include "libc/fmt/itoa.h"
@ -42,8 +46,11 @@
#include "libc/sock/sock.h"
#include "libc/stdio/posix_spawn.h"
#include "libc/stdio/posix_spawn.internal.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.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/thread/thread.h"
#include "libc/thread/tls.h"
@ -149,13 +156,14 @@ static textwindows errno_t posix_spawn_windows_impl(
};
// figure out the flags
short flags;
short flags = 0;
bool bInheritHandles = false;
uint32_t dwCreationFlags = 0;
for (i = 0; i < 3; ++i) {
bInheritHandles |= stdio_handle[i] != -1;
}
if (attrp && *attrp && !posix_spawnattr_getflags(attrp, &flags)) {
if (attrp && *attrp) {
flags = (*attrp)->flags;
if (flags & POSIX_SPAWN_SETSID) {
dwCreationFlags |= kNtDetachedProcess;
}
@ -208,8 +216,11 @@ static textwindows dontinline errno_t posix_spawn_windows(
return err;
}
static wontreturn void posix_spawn_die(const char *fail_func) {
STRACE("posix_spawn: %s failed% m", fail_func);
static wontreturn void posix_spawn_die(const char *thing) {
const char *reason; // print b/c there's no other way
if (!(reason = _strerdoc(errno))) reason = "Unknown error";
tinyprint(2, program_invocation_short_name, ": posix_spawn ", thing,
" failed: ", reason, "\n", NULL);
_Exit(127);
}
@ -229,7 +240,7 @@ static void RunUnixFileActions(struct _posix_faction *a) {
case _POSIX_SPAWN_OPEN: {
int t;
if ((t = open(a->path, a->oflag, a->mode)) == -1) {
posix_spawn_die("open");
posix_spawn_die(a->path);
}
if (t != a->fildes) {
if (dup2(t, a->fildes) == -1) {
@ -251,8 +262,7 @@ static void RunUnixFileActions(struct _posix_faction *a) {
/**
* Spawns process, the POSIX way.
*
* This provides superior process creation performance across systems.
*
* This provides superior process creation performance across systems
* Processes are normally spawned by calling fork() and execve(), but
* 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
@ -260,10 +270,19 @@ static void RunUnixFileActions(struct _posix_faction *a) {
* fork() bears a similar cost that's 100x less bad, which is copying
* the page tables. So what this implementation does is on Windows it
* calls CreateProcess() directly and on UNIX it uses vfork() if it's
* possible (XNU and OpenBSD don't have it).
* possible (XNU and OpenBSD don't have it). On UNIX this API has the
* benefit of avoiding the footguns of using vfork() directly because
* this implementation will ensure signal handlers can't be called in
* the child process since that'd likely corrupt the parent's memory.
*
* If the child process exits with status 127 then use the `--strace`
* flag to get an explanation of failures that occurred during spawn.
* This implementation doesn't create a pipe like Musl Libc, and will
* report errors in the child process by printing the cause to stderr
* which ensures posix_spawn stays fast, and works w/ `RLIMIT_FILENO`
* There is however one particular case where knowing the execve code
* truly matters, which is ETXTBSY. Thankfully, there's a rarely used
* signal with the exact same magic number across supported platforms
* called SIGVTALRM which we send to wait4 when execve raises ETXTBSY
* Please note that on Windows posix_spawn() returns ETXTBSY directly
*
* @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
@ -280,67 +299,85 @@ 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[]) {
short flags = 0;
sigset_t sigmask;
int s, child, policy;
struct sched_param param;
struct sigaction dfl = {0};
if (IsWindows()) {
return posix_spawn_windows(pid, path, file_actions, attrp, argv, envp);
}
sigset_t blockall, oldmask;
int child, res, cs, e = errno;
if (access(path, X_OK)) {
res = errno;
errno = e;
return res;
}
sigfillset(&blockall);
sys_sigprocmask(SIG_SETMASK, &blockall, &oldmask);
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
if (!(child = vfork())) {
if (attrp && *attrp) {
posix_spawnattr_getflags(attrp, &flags);
if (flags & POSIX_SPAWN_SETSID) {
if (setsid()) {
posix_spawn_die("setsid");
}
}
if (flags & POSIX_SPAWN_SETPGROUP) {
if (setpgid(0, (*attrp)->pgroup)) {
posix_spawn_die("setpgid");
}
}
if (flags & POSIX_SPAWN_SETSIGMASK) {
posix_spawnattr_getsigmask(attrp, &sigmask);
sigprocmask(SIG_SETMASK, &sigmask, 0);
}
if ((flags & POSIX_SPAWN_RESETIDS) &&
(setgid(getgid()) || setuid(getuid()))) {
posix_spawn_die("setuid");
}
if (flags & POSIX_SPAWN_SETSIGDEF) {
for (s = 1; s < 32; s++) {
if (sigismember(&(*attrp)->sigdefault, s)) {
if (sigaction(s, &dfl, 0)) {
posix_spawn_die("sigaction");
}
}
}
sigset_t *childmask;
struct sigaction dfl = {0};
short flags = attrp && *attrp ? (*attrp)->flags : 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) ||
sig == SIGVTALRM)) {
sigaction(sig, &dfl, 0);
}
}
if (flags & POSIX_SPAWN_SETSIGMASK) {
childmask = &(*attrp)->sigmask;
} else {
childmask = &oldmask;
}
sys_sigprocmask(SIG_SETMASK, childmask, 0);
if (flags & POSIX_SPAWN_SETSID) {
setsid();
}
if ((flags & POSIX_SPAWN_SETPGROUP) && setpgid(0, (*attrp)->pgroup)) {
posix_spawn_die("setpgid");
}
if ((flags & POSIX_SPAWN_RESETIDS) && setgid(getgid())) {
posix_spawn_die("setgid");
}
if ((flags & POSIX_SPAWN_RESETIDS) && setgid(getgid())) {
posix_spawn_die("setuid");
}
if (file_actions) {
RunUnixFileActions(*file_actions);
}
if (attrp && *attrp) {
if (IsLinux() || IsFreebsd() || IsNetbsd()) {
if (flags & POSIX_SPAWN_SETSCHEDULER) {
posix_spawnattr_getschedpolicy(attrp, &policy);
posix_spawnattr_getschedparam(attrp, &param);
if (sched_setscheduler(0, policy, &param) == -1) {
if (sched_setscheduler(0, (*attrp)->schedpolicy,
&(*attrp)->schedparam) == -1) {
posix_spawn_die("sched_setscheduler");
}
}
if (flags & POSIX_SPAWN_SETSCHEDPARAM) {
posix_spawnattr_getschedparam(attrp, &param);
if (sched_setparam(0, &param)) {
if (sched_setparam(0, &(*attrp)->schedparam)) {
posix_spawn_die("sched_setparam");
}
}
}
if (flags & POSIX_SPAWN_SETRLIMIT) {
for (int rez = 0; rez <= ARRAYLEN((*attrp)->rlim); ++rez) {
if ((*attrp)->rlimset & (1u << rez)) {
if (setrlimit(rez, (*attrp)->rlim + rez)) {
posix_spawn_die("setrlimit");
}
}
}
}
if (!envp) envp = environ;
execve(path, argv, envp);
posix_spawn_die("execve");
} else if (child != -1) {
if (errno == ETXTBSY) {
sys_kill(getpid(), SIGVTALRM, 1);
}
posix_spawn_die(path);
}
sys_sigprocmask(SIG_SETMASK, &oldmask, 0);
pthread_setcancelstate(cs, 0);
if (child != -1) {
if (pid) *pid = child;
return 0;
} else {

View file

@ -1,8 +1,10 @@
#ifndef COSMOPOLITAN_LIBC_STDIO_SPAWN_H_
#define COSMOPOLITAN_LIBC_STDIO_SPAWN_H_
#include "libc/calls/struct/rlimit.h"
#include "libc/calls/struct/sched_param.h"
#include "libc/calls/struct/sigset.h"
#define POSIX_SPAWN_USEVFORK 0
#define POSIX_SPAWN_RESETIDS 1
#define POSIX_SPAWN_SETPGROUP 2
#define POSIX_SPAWN_SETSIGDEF 4
@ -10,6 +12,7 @@
#define POSIX_SPAWN_SETSCHEDPARAM 16
#define POSIX_SPAWN_SETSCHEDULER 32
#define POSIX_SPAWN_SETSID 128
#define POSIX_SPAWN_SETRLIMIT 256
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
@ -45,6 +48,8 @@ int posix_spawnattr_getsigmask(const posix_spawnattr_t *, sigset_t *);
int posix_spawnattr_setsigmask(posix_spawnattr_t *, const sigset_t *);
int posix_spawnattr_getsigdefault(const posix_spawnattr_t *, sigset_t *);
int posix_spawnattr_setsigdefault(posix_spawnattr_t *, const sigset_t *);
int posix_spawnattr_getrlimit(const posix_spawnattr_t *, int, struct rlimit *);
int posix_spawnattr_setrlimit(posix_spawnattr_t *, int, const struct rlimit *);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -1,5 +1,6 @@
#ifndef COSMOPOLITAN_LIBC_STDIO_SPAWNA_INTERNAL_H_
#define COSMOPOLITAN_LIBC_STDIO_SPAWNA_INTERNAL_H_
#include "libc/calls/struct/rlimit.h"
#include "libc/calls/struct/sched_param.h"
#include "libc/calls/struct/sigset.h"
@ -11,15 +12,16 @@
COSMOPOLITAN_C_START_
struct _posix_spawna {
char flags;
short flags;
bool schedparam_isset;
bool schedpolicy_isset;
bool sigmask_isset;
int pgroup;
int rlimset;
int schedpolicy;
struct sched_param schedparam;
sigset_t sigmask;
sigset_t sigdefault;
struct rlimit rlim[16];
};
struct _posix_faction {

View file

@ -21,6 +21,7 @@
#include "libc/calls/struct/sigset.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/macros.internal.h"
#include "libc/mem/mem.h"
#include "libc/stdio/posix_spawn.h"
#include "libc/stdio/posix_spawn.internal.h"
@ -76,11 +77,6 @@ int posix_spawnattr_getflags(const posix_spawnattr_t *attr, short *flags) {
/**
* Sets posix_spawn() flags.
*
* Setting these flags is needed in order for the other setters in this
* function to take effect. If a flag is known but unsupported by the
* host platform, it'll be silently removed from the flags. You can
* check for this by calling the getter afterwards.
*
* @param attr was initialized by posix_spawnattr_init()
* @param flags may have any of the following
* - `POSIX_SPAWN_RESETIDS`
@ -94,9 +90,6 @@ int posix_spawnattr_getflags(const posix_spawnattr_t *attr, short *flags) {
* @raise EINVAL if `flags` has invalid bits
*/
int posix_spawnattr_setflags(posix_spawnattr_t *attr, short flags) {
if (!(IsLinux() || IsFreebsd() || IsNetbsd())) {
flags &= ~(POSIX_SPAWN_SETSCHEDPARAM | POSIX_SPAWN_SETSCHEDULER);
}
if (flags &
~(POSIX_SPAWN_RESETIDS | POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGDEF |
POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSCHEDPARAM |
@ -107,134 +100,62 @@ int posix_spawnattr_setflags(posix_spawnattr_t *attr, short flags) {
return 0;
}
/**
* Gets process group id associated with attributes.
*
* @param attr was initialized by posix_spawnattr_init()
* @param pgroup receives the result on success
* @return 0 on success, or errno on error
*/
int posix_spawnattr_getpgroup(const posix_spawnattr_t *attr, int *pgroup) {
*pgroup = (*attr)->pgroup;
return 0;
}
/**
* Specifies process group into which child process is placed.
*
* Setting `pgroup` to zero will ensure newly created processes are
* placed within their own brand new process group.
*
* This setter also sets the `POSIX_SPAWN_SETPGROUP` flag.
*
* @param attr was initialized by posix_spawnattr_init()
* @param pgroup is the process group id, or 0 for self
* @return 0 on success, or errno on error
*/
int posix_spawnattr_setpgroup(posix_spawnattr_t *attr, int pgroup) {
(*attr)->flags |= POSIX_SPAWN_SETPGROUP;
(*attr)->pgroup = pgroup;
return 0;
}
/**
* Gets scheduler policy that'll be used for spawned process.
*
* If the setter wasn't called then this function will return the
* scheduling policy of the current process.
*
* @param attr was initialized by posix_spawnattr_init()
* @param schedpolicy receives the result
* @return 0 on success, or errno on error
* @raise ENOSYS if platform support isn't available
*/
int posix_spawnattr_getschedpolicy(const posix_spawnattr_t *attr,
int *schedpolicy) {
int rc, e = errno;
struct _posix_spawna *a = *(/*unconst*/ posix_spawnattr_t *)attr;
if (!a->schedpolicy_isset) {
rc = sched_getscheduler(0);
if (rc == -1) {
rc = errno;
errno = e;
return rc;
}
a->schedpolicy = rc;
a->schedpolicy_isset = true;
}
*schedpolicy = a->schedpolicy;
return 0;
}
/**
* Specifies scheduler policy override for spawned process.
*
* Scheduling policies are inherited by default. Use this to change it.
*
* @param attr was initialized by posix_spawnattr_init()
* @param schedpolicy receives the result
* @return 0 on success, or errno on error
*/
int posix_spawnattr_setschedpolicy(posix_spawnattr_t *attr, int schedpolicy) {
(*attr)->schedpolicy = schedpolicy;
(*attr)->schedpolicy_isset = true;
return 0;
}
/**
* Gets scheduler parameter that'll be used for spawned process.
*
* If the setter wasn't called then this function will return the
* scheduling parameter of the current process.
*
* @param attr was initialized by posix_spawnattr_init()
* @param schedparam receives the result
* @return 0 on success, or errno on error
* @raise ENOSYS if platform support isn't available
*/
int posix_spawnattr_getschedparam(const posix_spawnattr_t *attr,
struct sched_param *schedparam) {
int rc, e = errno;
struct _posix_spawna *a = *(/*unconst*/ posix_spawnattr_t *)attr;
if (!a->schedparam_isset) {
rc = sched_getparam(0, &a->schedparam);
if (rc == -1) {
rc = errno;
errno = e;
return rc;
}
a->schedparam_isset = true;
}
*schedparam = a->schedparam;
return 0;
}
/**
* Specifies scheduler parameter override for spawned process.
*
* Scheduling parameters are inherited by default. Use this to change it.
*
* @param attr was initialized by posix_spawnattr_init()
* @param schedparam receives the result
* @return 0 on success, or errno on error
*/
int posix_spawnattr_setschedparam(posix_spawnattr_t *attr,
const struct sched_param *schedparam) {
(*attr)->schedparam = *schedparam;
(*attr)->schedparam_isset = true;
return 0;
}
/**
* Gets signal mask for sigprocmask() in child process.
*
* If the setter wasn't called then this function will return the
* scheduling parameter of the current process.
* The signal mask is applied to the child process in such a way that
* signal handlers from the parent process can't get triggered in the
* child process.
*
* @return 0 on success, or errno on error
*/
int posix_spawnattr_getsigmask(const posix_spawnattr_t *attr,
sigset_t *sigmask) {
struct _posix_spawna *a = *(/*unconst*/ posix_spawnattr_t *)attr;
if (!a->sigmask_isset) {
npassert(!sigprocmask(SIG_SETMASK, 0, &a->sigmask));
a->sigmask_isset = true;
}
*sigmask = a->sigmask;
*sigmask = (*attr)->sigmask;
return 0;
}
/**
* Specifies signal mask for sigprocmask() in child process.
*
* Signal masks are inherited by default. Use this to change it.
* This setter also sets the `POSIX_SPAWN_SETSIGMASK` flag.
*
* @return 0 on success, or errno on error
*/
int posix_spawnattr_setsigmask(posix_spawnattr_t *attr,
const sigset_t *sigmask) {
(*attr)->flags |= POSIX_SPAWN_SETSIGMASK;
(*attr)->sigmask = *sigmask;
(*attr)->sigmask_isset = true;
return 0;
}
@ -252,10 +173,119 @@ int posix_spawnattr_getsigdefault(const posix_spawnattr_t *attr,
/**
* Specifies which signals should be restored to `SIG_DFL`.
*
* This routine isn't necessary in most cases, since posix_spawn() by
* default will try to avoid vfork() race conditions by tracking what
* signals have a handler function and then resets them automatically
* within the child process, before applying the child's signal mask.
* This function may be used to ensure the `SIG_IGN` disposition will
* not propagate across execve in cases where this process explicitly
* set the signals to `SIG_IGN` earlier (since posix_spawn() will not
* issue O(128) system calls just to be totally pedantic about that).
*
* Using this setter automatically sets `POSIX_SPAWN_SETSIGDEF`.
*
* @return 0 on success, or errno on error
*/
int posix_spawnattr_setsigdefault(posix_spawnattr_t *attr,
const sigset_t *sigdefault) {
(*attr)->flags |= POSIX_SPAWN_SETSIGDEF;
(*attr)->sigdefault = *sigdefault;
return 0;
}
/**
* Gets resource limit for spawned process.
*
* @return 0 on success, or errno on error
* @raise EINVAL if `resource` is invalid
* @raise ENOENT if `resource` is absent
*/
int posix_spawnattr_getrlimit(const posix_spawnattr_t *attr, int resource,
struct rlimit *rlim) {
if ((0 <= resource && resource < ARRAYLEN((*attr)->rlim))) {
if (((*attr)->rlimset & (1u << resource))) {
*rlim = (*attr)->rlim[resource];
return 0;
} else {
return ENOENT;
}
} else {
return EINVAL;
}
}
/**
* Sets resource limit on spawned process.
*
* Using this setter automatically sets `POSIX_SPAWN_SETRLIMIT`.
*
* @return 0 on success, or errno on error
* @raise EINVAL if resource is invalid
*/
int posix_spawnattr_setrlimit(posix_spawnattr_t *attr, int resource,
const struct rlimit *rlim) {
if (0 <= resource && resource < ARRAYLEN((*attr)->rlim)) {
(*attr)->flags |= POSIX_SPAWN_SETRLIMIT;
(*attr)->rlimset |= 1u << resource;
(*attr)->rlim[resource] = *rlim;
return 0;
} else {
return EINVAL;
}
}
/**
* Gets scheduler policy that'll be used for spawned process.
*
* @param attr was initialized by posix_spawnattr_init()
* @param schedpolicy receives the result
* @return 0 on success, or errno on error
*/
int posix_spawnattr_getschedpolicy(const posix_spawnattr_t *attr,
int *schedpolicy) {
*schedpolicy = (*attr)->schedpolicy;
return 0;
}
/**
* Specifies scheduler policy override for spawned process.
*
* Using this setter automatically sets `POSIX_SPAWN_SETSCHEDULER`.
*
* @param attr was initialized by posix_spawnattr_init()
* @return 0 on success, or errno on error
*/
int posix_spawnattr_setschedpolicy(posix_spawnattr_t *attr, int schedpolicy) {
(*attr)->flags |= POSIX_SPAWN_SETSCHEDULER;
(*attr)->schedpolicy = schedpolicy;
return 0;
}
/**
* Gets scheduler parameter.
*
* @param attr was initialized by posix_spawnattr_init()
* @param schedparam receives the result
* @return 0 on success, or errno on error
*/
int posix_spawnattr_getschedparam(const posix_spawnattr_t *attr,
struct sched_param *schedparam) {
*schedparam = (*attr)->schedparam;
return 0;
}
/**
* Specifies scheduler parameter override for spawned process.
*
* Using this setter automatically sets `POSIX_SPAWN_SETSCHEDPARAM`.
*
* @param attr was initialized by posix_spawnattr_init()
* @param schedparam receives the result
* @return 0 on success, or errno on error
*/
int posix_spawnattr_setschedparam(posix_spawnattr_t *attr,
const struct sched_param *schedparam) {
(*attr)->flags |= POSIX_SPAWN_SETSCHEDPARAM;
(*attr)->schedparam = *schedparam;
return 0;
}

View file

@ -1,81 +0,0 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2020 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/errno.h"
#include "libc/intrin/asmflag.h"
#include "libc/nexgen32e/x86feature.h"
#include "libc/stdio/rand.h"
#include "libc/sysv/consts/grnd.h"
__static_yoink("rdrand_init");
static dontinline uint64_t rdrand_failover(void) {
int f;
size_t i;
ssize_t r;
volatile uint64_t b;
register uint64_t x;
for (f = GRND_RANDOM | GRND_NONBLOCK, i = 0; i < 8; i += r) {
if ((r = getrandom((char *)&b + i, 8 - i, f)) <= 0) {
if (r == -1 && errno == EINTR) {
r = 0;
} else if (r == -1 && errno == EAGAIN) {
r = 0;
f = 0;
} else {
return _rand64();
}
}
}
x = b;
b = 0;
return x;
}
/**
* Retrieves 64-bits of hardware random data from RDRAND instruction.
*
* If RDRAND isn't available (we check CPUID and we also disable it
* automatically for microarchitectures where it's slow or buggy) then
* we try getrandom(), RtlGenRandom(), or sysctl(KERN_ARND). If those
* aren't available then we try /dev/urandom and if that fails, we try
* getauxval(AT_RANDOM), and if not we finally use RDTSC and getpid().
*
* @note this function could block a nontrivial time on old computers
* @note this function is indeed intended for cryptography
* @note this function takes around 300 cycles
* @see rngset(), rdseed(), _rand64()
* @asyncsignalsafe
* @vforksafe
*/
uint64_t rdrand(void) {
int i;
char cf;
uint64_t x;
if (X86_HAVE(RDRND)) {
for (i = 0; i < 10; ++i) {
asm volatile(CFLAG_ASM("rdrand\t%1")
: CFLAG_CONSTRAINT(cf), "=r"(x)
: /* no inputs */
: "cc");
if (cf) return x;
asm volatile("pause");
}
}
return rdrand_failover();
}

View file

@ -1,11 +1,12 @@
#ifndef COSMOPOLITAN_LIBC_STDIO_H_
#define COSMOPOLITAN_LIBC_STDIO_H_
#define EOF -1 /* end of file */
#define WEOF -1u /* end of file (multibyte) */
#define _IOFBF 0 /* fully buffered */
#define _IOLBF 1 /* line buffered */
#define _IONBF 2 /* no buffering */
#define EOF -1 /* end of file */
#define WEOF -1u /* end of file (multibyte) */
#define _IOFBF 0 /* fully buffered */
#define _IOLBF 1 /* line buffered */
#define _IONBF 2 /* no buffering */
#define _CS_PATH 0
#define L_tmpnam 20
#define L_ctermid 20
@ -80,6 +81,7 @@ int setvbuf(FILE *, char *, int, size_t);
int pclose(FILE *);
char *ctermid(char *);
void perror(const char *) relegated;
size_t confstr(int, char *, size_t);
typedef uint64_t fpos_t;
char *gets(char *) paramsnonnull();

View file

@ -34,7 +34,7 @@
* This creates a secure temporary file inside $TMPDIR. If it isn't
* defined, then /tmp is used on UNIX and GetTempPath() is used on the
* New Technology. This resolution of $TMPDIR happens once in a ctor,
* which is copied to the `kTmpDir` global.
* which is copied to the `kTmpPath` global.
*
* Once fclose() is called, the returned file is guaranteed to be
* deleted automatically. On UNIX the file is unlink()'d before this