9a9b91c940
It's finally done.. Signed-off-by: kaguya <vpshinomiya@protonmail.com>
377 lines
8.8 KiB
C++
377 lines
8.8 KiB
C++
|
|
#include <spawn.h>
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <limits.h>
|
|
#include <sched.h>
|
|
#include <stdlib.h>
|
|
#include <signal.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include <bits/ensure.h>
|
|
#include <mlibc/debug.hpp>
|
|
|
|
/*
|
|
* Musl places this in a seperate header called fdop.h
|
|
* This header isn't present in glibc, or on my host, so I
|
|
* include it's contents here
|
|
*/
|
|
|
|
#define FDOP_CLOSE 1
|
|
#define FDOP_DUP2 2
|
|
#define FDOP_OPEN 3
|
|
#define FDOP_CHDIR 4
|
|
#define FDOP_FCHDIR 5
|
|
|
|
struct fdop {
|
|
struct fdop *next, *prev;
|
|
int cmd, fd, srcfd, oflag;
|
|
mode_t mode;
|
|
char path[];
|
|
};
|
|
|
|
/*
|
|
* This posix_spawn implementation is taken from musl
|
|
*/
|
|
|
|
static unsigned long handler_set[NSIG / (8 * sizeof(long))];
|
|
|
|
static void __get_handler_set(sigset_t *set) {
|
|
memcpy(set, handler_set, sizeof handler_set);
|
|
}
|
|
|
|
struct args {
|
|
int p[2];
|
|
sigset_t oldmask;
|
|
const char *path;
|
|
const posix_spawn_file_actions_t *fa;
|
|
const posix_spawnattr_t *__restrict attr;
|
|
char *const *argv, *const *envp;
|
|
};
|
|
|
|
static int child(void *args_vp) {
|
|
int i, ret;
|
|
struct sigaction sa = {};
|
|
struct args *args = (struct args *)args_vp;
|
|
int p = args->p[1];
|
|
const posix_spawn_file_actions_t *fa = args->fa;
|
|
const posix_spawnattr_t *__restrict attr = args->attr;
|
|
sigset_t hset;
|
|
bool use_execvpe = false;
|
|
|
|
if(attr->__fn)
|
|
use_execvpe = true;
|
|
|
|
close(args->p[0]);
|
|
|
|
/* All signal dispositions must be either SIG_DFL or SIG_IGN
|
|
* before signals are unblocked. Otherwise a signal handler
|
|
* from the parent might get run in the child while sharing
|
|
* memory, with unpredictable and dangerous results. To
|
|
* reduce overhead, sigaction has tracked for us which signals
|
|
* potentially have a signal handler. */
|
|
__get_handler_set(&hset);
|
|
for(i = 1; i < NSIG; i++) {
|
|
if((attr->__flags & POSIX_SPAWN_SETSIGDEF) && sigismember(&attr->__def, i)) {
|
|
sa.sa_handler = SIG_DFL;
|
|
} else if(sigismember(&hset, i)) {
|
|
if (i - 32 < 3) {
|
|
sa.sa_handler = SIG_IGN;
|
|
} else {;
|
|
sigaction(i, nullptr, &sa);
|
|
if(sa.sa_handler == SIG_IGN)
|
|
continue;
|
|
sa.sa_handler = SIG_DFL;
|
|
}
|
|
} else {
|
|
continue;
|
|
}
|
|
sigaction(i, &sa, nullptr);
|
|
}
|
|
|
|
if(attr->__flags & POSIX_SPAWN_SETSID) {
|
|
if((ret = setsid()) < 0)
|
|
goto fail;
|
|
}
|
|
|
|
if(attr->__flags & POSIX_SPAWN_SETPGROUP) {
|
|
mlibc::infoLogger() << "mlibc: posix_spawn: ignoring SETPGROUP" << frg::endlog;
|
|
//if((ret = setpgid(0, attr->__pgrp)))
|
|
// goto fail;
|
|
}
|
|
|
|
if(attr->__flags & POSIX_SPAWN_RESETIDS) {
|
|
if((ret = setgid(getgid())) || (ret = setuid(getuid())) )
|
|
goto fail;
|
|
}
|
|
|
|
if(fa && fa->__actions) {
|
|
struct fdop *op;
|
|
int fd;
|
|
for(op = (struct fdop *)fa->__actions; op->next; op = op->next);
|
|
for(; op; op = op->prev) {
|
|
/* It's possible that a file operation would clobber
|
|
* the pipe fd used for synchronizing with the
|
|
* parent. To avoid that, we dup the pipe onto
|
|
* an unoccupied fd. */
|
|
if(op->fd == p) {
|
|
ret = dup(p);
|
|
if(ret < 0)
|
|
goto fail;
|
|
close(p);
|
|
p = ret;
|
|
}
|
|
switch(op->cmd) {
|
|
case FDOP_CLOSE:
|
|
close(op->fd);
|
|
break;
|
|
case FDOP_DUP2:
|
|
fd = op->srcfd;
|
|
if(fd == p) {
|
|
ret = -EBADF;
|
|
goto fail;
|
|
}
|
|
if(fd != op->fd) {
|
|
if((ret = dup2(fd, op->fd)) < 0)
|
|
goto fail;
|
|
} else {
|
|
ret = fcntl(fd, F_GETFD);
|
|
ret = fcntl(fd, F_SETFD, ret & ~FD_CLOEXEC);
|
|
if(ret < 0)
|
|
goto fail;
|
|
}
|
|
break;
|
|
case FDOP_OPEN:
|
|
fd = open(op->path, op->oflag, op->mode);
|
|
if((ret = fd) < 0)
|
|
goto fail;
|
|
if(fd != op->fd) {
|
|
if((ret = dup2(fd, op->fd)) < 0)
|
|
goto fail;
|
|
close(fd);
|
|
}
|
|
break;
|
|
case FDOP_CHDIR:
|
|
ret = chdir(op->path);
|
|
if(ret < 0)
|
|
goto fail;
|
|
break;
|
|
case FDOP_FCHDIR:
|
|
ret = fchdir(op->fd);
|
|
if(ret < 0)
|
|
goto fail;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Close-on-exec flag may have been lost if we moved the pipe
|
|
* to a different fd. */
|
|
fcntl(p, F_SETFD, FD_CLOEXEC);
|
|
|
|
pthread_sigmask(SIG_SETMASK, (attr->__flags & POSIX_SPAWN_SETSIGMASK)
|
|
? &attr->__mask : &args->oldmask, nullptr);
|
|
|
|
if(use_execvpe)
|
|
execvpe(args->path, args->argv, args->envp);
|
|
else
|
|
execve(args->path, args->argv, args->envp);
|
|
ret = -errno;
|
|
|
|
fail:
|
|
/* Since sizeof errno < PIPE_BUF, the write is atomic. */
|
|
ret = -ret;
|
|
if(ret)
|
|
while(write(p, &ret, sizeof ret) < 0);
|
|
_exit(127);
|
|
}
|
|
|
|
int posix_spawn(pid_t *__restrict res, const char *__restrict path,
|
|
const posix_spawn_file_actions_t *file_actions,
|
|
const posix_spawnattr_t *__restrict attrs,
|
|
char *const argv[], char *const envp[]) {
|
|
pid_t pid;
|
|
int ec = 0, cs;
|
|
struct args args;
|
|
const posix_spawnattr_t empty_attr = {};
|
|
sigset_t full_sigset;
|
|
sigfillset(&full_sigset);
|
|
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
|
|
|
|
args.path = path;
|
|
args.fa = file_actions;
|
|
args.attr = attrs ? attrs : &empty_attr;
|
|
args.argv = argv;
|
|
args.envp = envp;
|
|
pthread_sigmask(SIG_BLOCK, &full_sigset, &args.oldmask);
|
|
|
|
/* The lock guards both against seeing a SIGABRT disposition change
|
|
* by abort and against leaking the pipe fd to fork-without-exec. */
|
|
//LOCK(__abort_lock);
|
|
|
|
if(pipe2(args.p, O_CLOEXEC)) {
|
|
//UNLOCK(__abort_lock);
|
|
ec = errno;
|
|
goto fail;
|
|
}
|
|
|
|
/* Mlibc change: We use fork + execve, as clone is not implemented.
|
|
* This yields the same result in the end. */
|
|
//pid = clone(child, stack + sizeof stack, CLONE_VM | CLONE_VFORK | SIGCHLD, &args);
|
|
pid = fork();
|
|
if(!pid) {
|
|
child(&args);
|
|
}
|
|
close(args.p[1]);
|
|
//UNLOCK(__abort_lock);
|
|
|
|
if(pid > 0) {
|
|
if(read(args.p[0], &ec, sizeof ec) != sizeof ec)
|
|
ec = 0;
|
|
else
|
|
waitpid(pid, nullptr, 0);
|
|
} else {
|
|
ec = -pid;
|
|
}
|
|
|
|
close(args.p[0]);
|
|
|
|
if(!ec && res)
|
|
*res = pid;
|
|
|
|
fail:
|
|
pthread_sigmask(SIG_SETMASK, &args.oldmask, nullptr);
|
|
pthread_setcancelstate(cs, nullptr);
|
|
|
|
return ec;
|
|
}
|
|
|
|
int posix_spawnattr_init(posix_spawnattr_t *attr) {
|
|
*attr = (posix_spawnattr_t){};
|
|
return 0;
|
|
}
|
|
|
|
int posix_spawnattr_destroy(posix_spawnattr_t *) {
|
|
return 0;
|
|
}
|
|
|
|
int posix_spawnattr_setflags(posix_spawnattr_t *attr, short flags) {
|
|
const unsigned all_flags =
|
|
POSIX_SPAWN_RESETIDS |
|
|
POSIX_SPAWN_SETPGROUP |
|
|
POSIX_SPAWN_SETSIGDEF |
|
|
POSIX_SPAWN_SETSIGMASK |
|
|
POSIX_SPAWN_SETSCHEDPARAM |
|
|
POSIX_SPAWN_SETSCHEDULER |
|
|
POSIX_SPAWN_USEVFORK |
|
|
POSIX_SPAWN_SETSID;
|
|
if(flags & ~all_flags)
|
|
return EINVAL;
|
|
attr->__flags = flags;
|
|
return 0;
|
|
}
|
|
|
|
int posix_spawnattr_setsigdefault(posix_spawnattr_t *__restrict attr,
|
|
const sigset_t *__restrict sigdefault) {
|
|
attr->__def = *sigdefault;
|
|
return 0;
|
|
}
|
|
|
|
int posix_spawnattr_setschedparam(posix_spawnattr_t *__restrict,
|
|
const struct sched_param *__restrict) {
|
|
__ensure(!"Not implemented");
|
|
__builtin_unreachable();
|
|
}
|
|
|
|
int posix_spawnattr_setschedpolicy(posix_spawnattr_t *, int) {
|
|
__ensure(!"Not implemented");
|
|
__builtin_unreachable();
|
|
}
|
|
|
|
int posix_spawnattr_setsigmask(posix_spawnattr_t *__restrict attr,
|
|
const sigset_t *__restrict sigmask) {
|
|
attr->__mask = *sigmask;
|
|
return 0;
|
|
}
|
|
|
|
int posix_spawnattr_setpgroup(posix_spawnattr_t *attr, pid_t pgroup) {
|
|
attr->__pgrp = pgroup;
|
|
return 0;
|
|
}
|
|
|
|
int posix_spawn_file_actions_init(posix_spawn_file_actions_t *file_actions) {
|
|
file_actions->__actions = nullptr;
|
|
return 0;
|
|
}
|
|
|
|
int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *file_actions) {
|
|
struct fdop *op = (struct fdop *)file_actions->__actions, *next;
|
|
while(op) {
|
|
next = op->next;
|
|
free(op);
|
|
op = next;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t *file_actions,
|
|
int fildes, int newfildes) {
|
|
struct fdop *op = (struct fdop *)malloc(sizeof *op);
|
|
if(!op)
|
|
return ENOMEM;
|
|
op->cmd = FDOP_DUP2;
|
|
op->srcfd = fildes;
|
|
op->fd = newfildes;
|
|
if((op->next = (struct fdop *)file_actions->__actions))
|
|
op->next->prev = op;
|
|
op->prev = nullptr;
|
|
file_actions->__actions = op;
|
|
return 0;
|
|
}
|
|
|
|
int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t *file_actions,
|
|
int fildes) {
|
|
struct fdop *op = (struct fdop *)malloc(sizeof *op);
|
|
if(!op)
|
|
return ENOMEM;
|
|
op->cmd = FDOP_CLOSE;
|
|
op->fd = fildes;
|
|
if((op->next = (struct fdop *)file_actions->__actions))
|
|
op->next->prev = op;
|
|
op->prev = nullptr;
|
|
file_actions->__actions = op;
|
|
return 0;
|
|
}
|
|
|
|
int posix_spawn_file_actions_addopen(posix_spawn_file_actions_t *__restrict file_actions,
|
|
int fildes, const char *__restrict path, int oflag, mode_t mode) {
|
|
struct fdop *op = (struct fdop *)malloc(sizeof *op + strlen(path) + 1);
|
|
if(!op)
|
|
return ENOMEM;
|
|
op->cmd = FDOP_OPEN;
|
|
op->fd = fildes;
|
|
op->oflag = oflag;
|
|
op->mode = mode;
|
|
strcpy(op->path, path);
|
|
if((op->next = (struct fdop *)file_actions->__actions))
|
|
op->next->prev = op;
|
|
op->prev = nullptr;
|
|
file_actions->__actions = op;
|
|
return 0;
|
|
}
|
|
|
|
int posix_spawnp(pid_t *__restrict pid, const char *__restrict file,
|
|
const posix_spawn_file_actions_t *file_actions,
|
|
const posix_spawnattr_t *__restrict attrp,
|
|
char *const argv[], char *const envp[]) {
|
|
posix_spawnattr_t spawnp_attr = {};
|
|
if(attrp)
|
|
spawnp_attr = *attrp;
|
|
spawnp_attr.__fn = (void *)execvpe;
|
|
return posix_spawn(pid, file, file_actions, &spawnp_attr, argv, envp);
|
|
}
|
|
|