Index: fusl/src/process/posix_spawn.c |
diff --git a/fusl/src/process/posix_spawn.c b/fusl/src/process/posix_spawn.c |
new file mode 100644 |
index 0000000000000000000000000000000000000000..0bdf71cd8d0437960ea0d7f31a80d0dd151bc651 |
--- /dev/null |
+++ b/fusl/src/process/posix_spawn.c |
@@ -0,0 +1,195 @@ |
+#define _GNU_SOURCE |
+#include <spawn.h> |
+#include <sched.h> |
+#include <unistd.h> |
+#include <signal.h> |
+#include <fcntl.h> |
+#include <sys/wait.h> |
+#include "syscall.h" |
+#include "pthread_impl.h" |
+#include "fdop.h" |
+#include "libc.h" |
+ |
+struct args { |
+ int p[2]; |
+ sigset_t oldmask; |
+ const char *path; |
+ int (*exec)(const char *, char *const *, char *const *); |
+ const posix_spawn_file_actions_t *fa; |
+ const posix_spawnattr_t *restrict attr; |
+ char *const *argv, *const *envp; |
+}; |
+ |
+void __get_handler_set(sigset_t *); |
+ |
+static int __sys_dup2(int old, int new) |
+{ |
+#ifdef SYS_dup2 |
+ return __syscall(SYS_dup2, old, new); |
+#else |
+ if (old==new) { |
+ int r = __syscall(SYS_fcntl, old, F_GETFD); |
+ return r<0 ? r : old; |
+ } else { |
+ return __syscall(SYS_dup3, old, new, 0); |
+ } |
+#endif |
+} |
+ |
+static int child(void *args_vp) |
+{ |
+ int i, ret; |
+ struct sigaction sa = {0}; |
+ struct args *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; |
+ |
+ 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<3U) { |
+ sa.sa_handler = SIG_IGN; |
+ } else { |
+ __libc_sigaction(i, 0, &sa); |
+ if (sa.sa_handler==SIG_IGN) continue; |
+ sa.sa_handler = SIG_DFL; |
+ } |
+ } else { |
+ continue; |
+ } |
+ __libc_sigaction(i, &sa, 0); |
+ } |
+ |
+ if (attr->__flags & POSIX_SPAWN_SETPGROUP) |
+ if ((ret=__syscall(SYS_setpgid, 0, attr->__pgrp))) |
+ goto fail; |
+ |
+ /* Use syscalls directly because the library functions attempt |
+ * to do a multi-threaded synchronized id-change, which would |
+ * trash the parent's state. */ |
+ if (attr->__flags & POSIX_SPAWN_RESETIDS) |
+ if ((ret=__syscall(SYS_setgid, __syscall(SYS_getgid))) || |
+ (ret=__syscall(SYS_setuid, __syscall(SYS_getuid))) ) |
+ goto fail; |
+ |
+ if (fa && fa->__actions) { |
+ struct fdop *op; |
+ int fd; |
+ for (op = 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 = __syscall(SYS_dup, p); |
+ if (ret < 0) goto fail; |
+ __syscall(SYS_close, p); |
+ p = ret; |
+ } |
+ switch(op->cmd) { |
+ case FDOP_CLOSE: |
+ __syscall(SYS_close, op->fd); |
+ break; |
+ case FDOP_DUP2: |
+ if ((ret=__sys_dup2(op->srcfd, op->fd))<0) |
+ goto fail; |
+ break; |
+ case FDOP_OPEN: |
+ fd = __sys_open(op->path, op->oflag, op->mode); |
+ if ((ret=fd) < 0) goto fail; |
+ if (fd != op->fd) { |
+ if ((ret=__sys_dup2(fd, op->fd))<0) |
+ goto fail; |
+ __syscall(SYS_close, fd); |
+ } |
+ break; |
+ } |
+ } |
+ } |
+ |
+ /* Close-on-exec flag may have been lost if we moved the pipe |
+ * to a different fd. We don't use F_DUPFD_CLOEXEC above because |
+ * it would fail on older kernels and atomicity is not needed -- |
+ * in this process there are no threads or signal handlers. */ |
+ __syscall(SYS_fcntl, p, F_SETFD, FD_CLOEXEC); |
+ |
+ pthread_sigmask(SIG_SETMASK, (attr->__flags & POSIX_SPAWN_SETSIGMASK) |
+ ? &attr->__mask : &args->oldmask, 0); |
+ |
+ args->exec(args->path, args->argv, args->envp); |
+ ret = -errno; |
+ |
+fail: |
+ /* Since sizeof errno < PIPE_BUF, the write is atomic. */ |
+ ret = -ret; |
+ if (ret) while (__syscall(SYS_write, p, &ret, sizeof ret) < 0); |
+ _exit(127); |
+} |
+ |
+ |
+int __posix_spawnx(pid_t *restrict res, const char *restrict path, |
+ int (*exec)(const char *, char *const *, char *const *), |
+ const posix_spawn_file_actions_t *fa, |
+ const posix_spawnattr_t *restrict attr, |
+ char *const argv[restrict], char *const envp[restrict]) |
+{ |
+ pid_t pid; |
+ char stack[1024]; |
+ int ec=0, cs; |
+ struct args args; |
+ |
+ if (pipe2(args.p, O_CLOEXEC)) |
+ return errno; |
+ |
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs); |
+ |
+ args.path = path; |
+ args.exec = exec; |
+ args.fa = fa; |
+ args.attr = attr ? attr : &(const posix_spawnattr_t){0}; |
+ args.argv = argv; |
+ args.envp = envp; |
+ pthread_sigmask(SIG_BLOCK, SIGALL_SET, &args.oldmask); |
+ |
+ pid = __clone(child, stack+sizeof stack, |
+ CLONE_VM|CLONE_VFORK|SIGCHLD, &args); |
+ close(args.p[1]); |
+ |
+ if (pid > 0) { |
+ if (read(args.p[0], &ec, sizeof ec) != sizeof ec) ec = 0; |
+ else waitpid(pid, &(int){0}, 0); |
+ } else { |
+ ec = -pid; |
+ } |
+ |
+ close(args.p[0]); |
+ |
+ if (!ec && res) *res = pid; |
+ |
+ pthread_sigmask(SIG_SETMASK, &args.oldmask, 0); |
+ pthread_setcancelstate(cs, 0); |
+ |
+ return ec; |
+} |
+ |
+int posix_spawn(pid_t *restrict res, const char *restrict path, |
+ const posix_spawn_file_actions_t *fa, |
+ const posix_spawnattr_t *restrict attr, |
+ char *const argv[restrict], char *const envp[restrict]) |
+{ |
+ return __posix_spawnx(res, path, execve, fa, attr, argv, envp); |
+} |