aboutsummaryrefslogtreecommitdiff
path: root/src/libthread/exec-unix.c
blob: 91639bbb3e6c0bc9118334b7fffa0a6cd6330ecf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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
145
146
147
148
149
150
151
152
153
154
155
156
157
#include <u.h>
#include <fcntl.h>
#include <unistd.h>
#include "threadimpl.h"

static void efork(int[3], int[2], char*, char**);
static int
_threadexec(Channel *pidc, int fd[3], char *prog, char *args[], int freeargs)
{
	int pfd[2];
	int n, pid;
	char exitstr[ERRMAX];
	static int firstexec = 1;
	static Lock lk;

	_threaddebug(DBGEXEC, "threadexec %s", prog);

	if(firstexec){
		lock(&lk);
		if(firstexec){
			firstexec = 0;
			_threadfirstexec();
		}
		unlock(&lk);
	}

	/*
	 * We want threadexec to behave like exec; if exec succeeds,
	 * never return, and if it fails, return with errstr set.
	 * Unfortunately, the exec happens in another proc since
	 * we have to wait for the exec'ed process to finish.
	 * To provide the semantics, we open a pipe with the 
	 * write end close-on-exec and hand it to the proc that
	 * is doing the exec.  If the exec succeeds, the pipe will
	 * close so that our read below fails.  If the exec fails,
	 * then the proc doing the exec sends the errstr down the
	 * pipe to us.
	 */
	if(pipe(pfd) < 0)
		goto Bad;
	if(fcntl(pfd[0], F_SETFD, 1) < 0)
		goto Bad;
	if(fcntl(pfd[1], F_SETFD, 1) < 0)
		goto Bad;

	switch(pid = fork()){
	case -1:
		close(pfd[0]);
		close(pfd[1]);
		goto Bad;
	case 0:
		efork(fd, pfd, prog, args);
		_exit(0);
	default:
		_threadafterexec();
		if(freeargs)
			free(args);
		break;
	}

	close(pfd[1]);
	if((n = read(pfd[0], exitstr, ERRMAX-1)) > 0){	/* exec failed */
		exitstr[n] = '\0';
		errstr(exitstr, ERRMAX);
		close(pfd[0]);
		goto Bad;
	}
	close(pfd[0]);
	close(fd[0]);
	if(fd[1] != fd[0])
		close(fd[1]);
	if(fd[2] != fd[1] && fd[2] != fd[0])
		close(fd[2]);
	if(pidc)
		sendul(pidc, pid);

	_threaddebug(DBGEXEC, "threadexec schedexecwait");
	return pid;

Bad:
	_threaddebug(DBGEXEC, "threadexec bad %r");
	if(pidc)
		sendul(pidc, ~0);
	return -1;
}

void
threadexec(Channel *pidc, int fd[3], char *prog, char *args[])
{
	if(_threadexec(pidc, fd, prog, args, 0) >= 0)
		threadexits(nil);
}

int
threadspawn(int fd[3], char *prog, char *args[])
{
	return _threadexec(nil, fd, prog, args, 0);
}

/*
 * The &f+1 trick doesn't work on SunOS, so we might 
 * as well bite the bullet and do this correctly.
 */
void
threadexecl(Channel *pidc, int fd[3], char *f, ...)
{
	char **args, *s;
	int n;
	va_list arg;

	va_start(arg, f);
	for(n=0; va_arg(arg, char*) != 0; n++)
		;
	n++;
	va_end(arg);

	args = malloc(n*sizeof(args[0]));
	if(args == nil){
		if(pidc)
			sendul(pidc, ~0);
		return;
	}

	va_start(arg, f);
	for(n=0; (s=va_arg(arg, char*)) != 0; n++)
		args[n] = s;
	args[n] = 0;
	va_end(arg);

	if(_threadexec(pidc, fd, f, args, 1) >= 0)
		threadexits(nil);
}

static void
efork(int stdfd[3], int fd[2], char *prog, char **args)
{
	char buf[ERRMAX];
	int i;

	_threaddebug(DBGEXEC, "_schedexec %s -- calling execv", prog);
	dup(stdfd[0], 0);
	dup(stdfd[1], 1);
	dup(stdfd[2], 2);
	for(i=3; i<40; i++)
		if(i != fd[1])
			close(i);
	rfork(RFNOTEG);
	execvp(prog, args);
	_threaddebug(DBGEXEC, "_schedexec failed: %r");
	rerrstr(buf, sizeof buf);
	if(buf[0]=='\0')
		strcpy(buf, "exec failed");
	write(fd[1], buf, strlen(buf));
	close(fd[1]);
	_exits(buf);
}