mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 03:27:39 +00:00
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:
parent
a089c07ddc
commit
39e7f24947
10 changed files with 373 additions and 46 deletions
232
examples/spawn.c
Normal file
232
examples/spawn.c
Normal 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;
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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,7 +55,7 @@ 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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,9 +56,9 @@ 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_
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
* @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))) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -26,13 +26,13 @@
|
||||||
/**
|
/**
|
||||||
* 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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue