subprocess-posix: use clone when available

This commit is contained in:
Misaki Kasumi authored and sfan5 committed 2026-06-17 10:47:16 +02:00
1 parent 53796ca2ea
commit 5fb6a76ddf
3 files changed
+81 -24

No files matched your search

+1
View File
@@ -465,6 +465,7 @@ features += {'ppoll': cc.has_function('ppoll', args: '-D_GNU_SOURCE',
prefix: '#include <poll.h>')}
features += {'memrchr': cc.has_function('memrchr', args: '-D_GNU_SOURCE',
prefix: '#include <string.h>')}
features += {'clone': cc.has_header_symbol('sched.h', 'clone', args: '-D_GNU_SOURCE')}
optical_devices = {
'windows': 'D:',
+2
View File
@@ -16,11 +16,13 @@
#define MP_FALLTHROUGH __attribute__((fallthrough))
#define MP_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
#define MP_UNUSED __attribute__((unused))
#define MP_NO_ASAN __attribute__((no_sanitize("address")))
#else
#define MP_NORETURN
#define MP_FALLTHROUGH do {} while (0)
#define MP_WARN_UNUSED_RESULT
#define MP_UNUSED
#define MP_NO_ASAN
#endif
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ < 202311L) && !defined(thread_local)
+78 -24
View File
@@ -32,6 +32,11 @@
extern char **environ;
#if HAVE_CLONE
#include <sched.h>
#include <sys/mman.h>
#endif
#ifdef SIGRTMAX
#define SIGNAL_MAX SIGRTMAX
#else
@@ -84,17 +89,78 @@ static void reset_signals_child(void)
sigprocmask(SIG_SETMASK, &sigmask, NULL);
}
struct child_args {
const char *path;
struct mp_subprocess_opts *opts;
int *src_fds;
int pipe_end;
bool failed;
};
// This function is called from a clone(CLONE_VM) context where the child
// shares the parent's address space. Use MP_NO_ASAN to avoid false positives
// from ASan when the child writes to shared memory.
MP_NO_ASAN static int child_main(void* args)
{
struct child_args *child_args = args;
const char *path = child_args->path;
struct mp_subprocess_opts *opts = child_args->opts;
int *src_fds = child_args->src_fds;
int pipe_end = child_args->pipe_end;
reset_signals_child();
for (int n = 0; n < opts->num_fds; n++) {
if (src_fds[n] == opts->fds[n].fd) {
int flags = fcntl(opts->fds[n].fd, F_GETFD);
if (flags == -1)
goto child_failed;
flags &= ~(unsigned)FD_CLOEXEC;
if (fcntl(opts->fds[n].fd, F_SETFD, flags) == -1)
goto child_failed;
} else if (dup2(src_fds[n], opts->fds[n].fd) < 0) {
goto child_failed;
}
}
as_execvpe(path, opts->exe, opts->args, opts->env ? opts->env : environ);
child_failed:
child_args->failed = true;
if (pipe_end >= 0)
(void)write(pipe_end, &(char){1}, 1); // shouldn't be able to fail
return 1;
}
// Returns 0 on any error, valid PID on success.
// This function must be async-signal-safe, as it may be called from a fork().
static pid_t spawn_process(const char *path, struct mp_subprocess_opts *opts,
int src_fds[])
{
int p[2] = {-1, -1};
pid_t fres = 0;
sigset_t sigmask, oldmask;
int r = 0;
sigfillset(&sigmask);
pthread_sigmask(SIG_BLOCK, &sigmask, &oldmask);
struct child_args child_args = {
.path = path,
.opts = opts,
.src_fds = src_fds,
.pipe_end = -1,
.failed = false,
};
int p[2] = {-1, -1};
#if HAVE_CLONE
const size_t stack_size = 0x8000;
void* stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
if (stack == MAP_FAILED)
goto done;
fres = clone(child_main, (int8_t*)stack + stack_size, CLONE_VM | CLONE_VFORK | SIGCHLD, &child_args);
#else
// We setup a communication pipe to signal failure. Since the child calls
// exec() and becomes the calling process, we don't know if or when the
// child process successfully ran exec() just from the PID.
@@ -107,51 +173,39 @@ static pid_t spawn_process(const char *path, struct mp_subprocess_opts *opts,
int p_flags = fcntl(p[0], F_GETFD);
if (p_flags == -1 || !FD_CLOEXEC || !(p_flags & FD_CLOEXEC))
goto done; // require CLOEXEC; unknown if fallback would be worth it
child_args.pipe_end = p[1];
fres = fork();
#endif
if (fres < 0) {
fres = 0;
goto done;
}
#if !HAVE_CLONE
if (fres == 0) {
// child
reset_signals_child();
for (int n = 0; n < opts->num_fds; n++) {
if (src_fds[n] == opts->fds[n].fd) {
int flags = fcntl(opts->fds[n].fd, F_GETFD);
if (flags == -1)
goto child_failed;
flags &= ~(unsigned)FD_CLOEXEC;
if (fcntl(opts->fds[n].fd, F_SETFD, flags) == -1)
goto child_failed;
} else if (dup2(src_fds[n], opts->fds[n].fd) < 0) {
goto child_failed;
}
}
as_execvpe(path, opts->exe, opts->args, opts->env ? opts->env : environ);
child_failed:
(void)write(p[1], &(char){1}, 1); // shouldn't be able to fail
_exit(1);
_exit(child_main(&child_args));
}
SAFE_CLOSE(p[1]);
int r;
do {
r = read(p[0], &(char){0}, 1);
} while (r < 0 && errno == EINTR);
#endif
// If exec()ing child failed, collect it immediately.
if (r != 0) {
if (child_args.failed || r != 0) {
while (waitpid(fres, &(int){0}, 0) < 0 && errno == EINTR) {}
fres = 0;
}
done:
pthread_sigmask(SIG_SETMASK, &oldmask, NULL);
#if HAVE_CLONE
munmap(stack, stack_size);
#endif
SAFE_CLOSE(p[0]);
SAFE_CLOSE(p[1]);