subprocess-posix: use clone when available
This commit is contained in:
1 parent
53796ca2ea
commit
5fb6a76ddf
3 files changed
+81
-24
No files matched your search
@@ -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:',
|
||||
|
||||
@@ -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
@@ -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]);
|
||||
|
||||
|
||||
Reference in new issue
Block a user