Merge pull request #201 from Dennisbonke/posix_spawn

options/posix: Implement posix_spawn
This commit is contained in:
Alexander van der Grinten 2021-01-12 21:10:32 +01:00 committed by GitHub
commit 4ae723492f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 369 additions and 10 deletions

View file

@ -17,21 +17,24 @@ int sigfillset(sigset_t *sigset) {
// TODO: Return EINVAL instead of __ensure()ing.
int sigaddset(sigset_t *sigset, int signo) {
int sigaddset(sigset_t *sigset, int sig) {
int signo = sig - 1;
// TODO: do not hard code CHAR_BITS
__ensure((unsigned int)signo < sizeof(sigset_t) * 8);
*sigset |= sigset_t(1) << signo;
return 0;
}
int sigdelset(sigset_t *sigset, int signo) {
int sigdelset(sigset_t *sigset, int sig) {
int signo = sig - 1;
// TODO: do not hard code CHAR_BITS
__ensure((unsigned int)signo < sizeof(sigset_t) * 8);
*sigset &= ~(sigset_t(1) << signo);
return 0;
}
int sigismember(const sigset_t *set, int signo) {
int sigismember(const sigset_t *set, int sig) {
int signo = sig - 1;
// TODO: do not hard code CHAR_BITS
__ensure((unsigned int)signo < sizeof(sigset_t) * 8);
return (*set) & (sigset_t(1) << signo);

View file

@ -1,14 +1,251 @@
#include <spawn.h>
#include <errno.h>
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <sched.h>
#include <signal.h>
#include <sys/wait.h>
#include <bits/ensure.h>
#include <mlibc/debug.hpp>
int posix_spawn(pid_t *__restrict pid, const char *__restrict path,
/*
* 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 = {0};
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, 0, &sa);
if(sa.sa_handler == SIG_IGN)
continue;
sa.sa_handler = SIG_DFL;
}
} else {
continue;
}
sigaction(i, &sa, 0);
}
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, 0);
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[]) {
__ensure(!"Not implemented");
__builtin_unreachable();
pid_t pid;
int ec = 0, cs;
struct args args;
const posix_spawnattr_t empty_attr = {0};
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, 0, 0);
} else {
ec = -pid;
}
close(args.p[0]);
if(!ec && res)
*res = pid;
fail:
pthread_sigmask(SIG_SETMASK, &args.oldmask, 0);
//pthread_setcancelstate(cs, 0);
return ec;
}
int posix_spawnattr_init(posix_spawnattr_t *attr) {

View file

@ -162,6 +162,11 @@ int execvp(const char *file, char *const argv[]) {
return -1;
}
int execvpe(const char *path, char *const argv[], char *const envp[]) {
__ensure(!"Not implemented");
__builtin_unreachable();
}
int faccessat(int dirfd, const char *pathname, int mode, int flags) {
if(!mlibc::sys_faccessat) {
MLIBC_MISSING_SYSDEP();

View file

@ -15,7 +15,7 @@ int sigemptyset(sigset_t *);
int sigfillset(sigset_t *);
int sigaddset(sigset_t *, int);
int sigdelset(sigset_t *, int);
int sigismember(const sigset_t *set, int signo);
int sigismember(const sigset_t *set, int sig);
// functions to block / wait for signals
int sigsuspend(const sigset_t *);

View file

@ -11,8 +11,20 @@
extern "C" {
#endif
typedef struct { } posix_spawnattr_t;
typedef struct { } posix_spawn_file_actions_t;
typedef struct {
int __flags;
pid_t __pgrp;
sigset_t __def, __mask;
int __prio, __pol;
void *__fn;
char __pad[64 - sizeof(void *)];
} posix_spawnattr_t;
typedef struct {
int __pad0[2];
void *__actions;
int __pad[16];
} posix_spawn_file_actions_t;
// MISSIG: sigset_t

View file

@ -132,6 +132,7 @@ int execlp(const char *, const char *, ...);
int execv(const char *, char *const []);
int execve(const char *path, char *const argv[], char *const envp[]);
int execvp(const char *, char *const[]);
int execvpe(const char *path, char *const argv[], char *const envp[]);
int faccessat(int, const char *, int, int);
int fchdir(int fd);
int fchown(int fd, uid_t uid, gid_t gid);

View file

@ -2,6 +2,14 @@
extern "C" {
using sc_word_t = long;
static sc_word_t do_asm_syscall0(int sc) {
sc_word_t ret;
asm volatile ("syscall" : "=a"(ret)
: "a"(sc)
: "rcx", "r11", "memory");
return ret;
}
static sc_word_t do_asm_syscall1(int sc,
sc_word_t arg1) {
sc_word_t ret;
@ -71,6 +79,9 @@ extern "C" {
namespace mlibc {
// C++ wrappers for the extern "C" functions.
inline sc_word_t do_nargs_syscall(int sc) {
return do_asm_syscall0(sc);
}
inline sc_word_t do_nargs_syscall(int sc, sc_word_t arg1) {
return do_asm_syscall1(sc, arg1);
}

View file

@ -18,19 +18,25 @@
#define NR_lseek 8
#define NR_mmap 9
#define NR_sigaction 13
#define NR_rt_sigprocmask 14
#define NR_ioctl 16
#define NR_pipe 22
#define NR_select 23
#define NR_socket 41
#define NR_connect 42
#define NR_sendmsg 46
#define NR_recvmsg 47
#define NR_fork 57
#define NR_execve 59
#define NR_exit 60
#define NR_wait4 61
#define NR_fcntl 72
#define NR_unlink 87
#define NR_arch_prctl 158
#define NR_sys_futex 202
#define NR_clock_gettime 228
#define NR_pselect6 270
#define NR_pipe2 293
#define ARCH_SET_FS 0x1002
@ -231,6 +237,50 @@ int sys_pselect(int nfds, fd_set *readfds, fd_set *writefds,
return 0;
}
int sys_pipe(int *fds, int flags) {
if(flags) {
auto ret = do_syscall(NR_pipe2, fds, flags);
if (int e = sc_error(ret); e)
return e;
return 0;
} else {
auto ret = do_syscall(NR_pipe, fds);
if (int e = sc_error(ret); e)
return e;
return 0;
}
}
int sys_fork(pid_t *child) {
auto ret = do_syscall(NR_fork);
if (int e = sc_error(ret); e)
return e;
*child = sc_int_result<int>(ret);
return 0;
}
int sys_waitpid(pid_t pid, int *status, int flags, pid_t *ret_pid) {
auto ret = do_syscall(NR_wait4, pid, status, flags, 0);
if (int e = sc_error(ret); e)
return e;
*ret_pid = sc_int_result<int>(ret);
return 0;
}
int sys_execve(const char *path, char *const argv[], char *const envp[]) {
auto ret = do_syscall(NR_execve, path, argv, envp);
if (int e = sc_error(ret); e)
return e;
return 0;
}
int sys_sigprocmask(int how, const sigset_t *set, sigset_t *old) {
auto ret = do_syscall(NR_rt_sigprocmask, how, set, old, NSIG / 8);
if (int e = sc_error(ret); e)
return e;
return 0;
}
#endif // __MLIBC_POSIX_OPTION
void sys_exit(int status) {

View file

@ -10,7 +10,8 @@ posix_test_cases = [
'inet_pton',
'pthread_rwlock',
'getaddrinfo',
'dprintf'
'dprintf',
'posix_spawn'
]
glibc_test_cases = [

39
tests/posix/posix_spawn.c Normal file
View file

@ -0,0 +1,39 @@
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <spawn.h>
#include <sys/wait.h>
extern char **environ;
void run_cmd(char *cmd)
{
pid_t pid;
char *argv[] = {"sh", "-c", cmd, NULL};
int status;
printf("Run command: %s\n", cmd);
status = posix_spawn(&pid, "/bin/sh", NULL, NULL, argv, environ);
if(status == 0) {
printf("Child pid: %i\n", pid);
if(waitpid(pid, &status, 0) != -1) {
printf("Child exited with status %i\n", status);
printf("Child exit status: %i\n", WEXITSTATUS(status));
assert(WEXITSTATUS(status) == 0);
} else {
perror("waitpid");
assert(0 == 1);
}
} else {
printf("posix_spawn: %s\n", strerror(status));
assert(0 == 1);
}
}
int main(int argc, char* argv[])
{
run_cmd("/usr/bin/true");
return 0;
}