posix_spawn.c 4.4 KB
Newer Older
1
#define _GNU_SOURCE
2
#include <spawn.h>
3
#include <sched.h>
4 5
#include <unistd.h>
#include <signal.h>
6
#include <fcntl.h>
7
#include <sys/wait.h>
8
#include "syscall.h"
9
#include "pthread_impl.h"
10
#include "fdop.h"
11
#include "libc.h"
12

13 14
struct args {
	int p[2];
15
	sigset_t oldmask;
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
	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;
};

static int child(void *args_vp)
{
	int i, ret;
	struct sigaction sa;
	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;

	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. */
	for (i=1; i<_NSIG; i++) {
39 40
		__libc_sigaction(i, 0, &sa);
		if (sa.sa_handler!=SIG_DFL && (sa.sa_handler!=SIG_IGN ||
41
		    ((attr->__flags & POSIX_SPAWN_SETSIGDEF)
42
		     && sigismember(&attr->__def, i) ))) {
43
			sa.sa_handler = SIG_DFL;
44
			__libc_sigaction(i, &sa, 0);
45 46 47
		}
	}

48 49 50
	if (attr->__flags & POSIX_SPAWN_SETPGROUP)
		if ((ret=__syscall(SYS_setpgid, 0, attr->__pgrp)))
			goto fail;
51

52 53 54 55 56 57 58
	/* Use syscalls directly because pthread state 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;
59

60
	if (fa && fa->__actions) {
61
		struct fdop *op;
62
		int fd;
63 64
		for (op = fa->__actions; op->next; op = op->next);
		for (; op; op = op->prev) {
65 66 67 68 69 70 71 72 73 74
			/* 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;
			}
75 76
			switch(op->cmd) {
			case FDOP_CLOSE:
77 78
				if ((ret=__syscall(SYS_close, op->fd)))
					goto fail;
79 80
				break;
			case FDOP_DUP2:
81 82
				if ((ret=__syscall(SYS_dup2, op->srcfd, op->fd))<0)
					goto fail;
83 84 85 86
				break;
			case FDOP_OPEN:
				fd = __syscall(SYS_open, op->path,
					op->oflag | O_LARGEFILE, op->mode);
87 88 89 90
				if ((ret=fd) < 0) goto fail;
				if (fd != op->fd) {
					if ((ret=__syscall(SYS_dup2, fd, op->fd))<0)
						goto fail;
91 92 93 94 95 96 97
					__syscall(SYS_close, fd);
				}
				break;
			}
		}
	}

98 99 100 101 102
	/* 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);
103

104 105 106 107 108 109 110 111 112
	pthread_sigmask(SIG_SETMASK, (attr->__flags & POSIX_SPAWN_SETSIGMASK)
		? &attr->__mask : &args->oldmask, 0);

	args->exec(args->path, args->argv, args->envp);

fail:
	/* Since sizeof errno < PIPE_BUF, the write is atomic. */
	ret = -ret;
	if (ret) while (write(p, &ret, sizeof ret) < 0);
113
	_exit(127);
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
}


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|SIGCHLD, &args);
	close(args.p[1]);

	if (pid > 0) {
145
		if (read(args.p[0], &ec, sizeof ec) != sizeof ec) ec = 0;
146 147 148 149 150 151 152 153 154 155 156
		else waitpid(pid, &(int){0}, 0);
	} else {
		ec = -pid;
	}

	close(args.p[0]);

	if (!ec) *res = pid;

	pthread_sigmask(SIG_SETMASK, &args.oldmask, 0);
	pthread_setcancelstate(cs, 0);
157

158
	return ec;
159 160
}

161
int posix_spawn(pid_t *restrict res, const char *restrict path,
162
	const posix_spawn_file_actions_t *fa,
163 164
	const posix_spawnattr_t *restrict attr,
	char *const argv[restrict], char *const envp[restrict])
165
{
166
	return __posix_spawnx(res, path, execve, fa, attr, argv, envp);
167
}