-
Notifications
You must be signed in to change notification settings - Fork 79
/
fork_exec.c
324 lines (281 loc) · 9.73 KB
/
fork_exec.c
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
/* ensure that execvpe is provided if possible */
#define _GNU_SOURCE 1
#include "common.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#if !(defined(_MSC_VER) || defined(__MINGW32__) || defined(_WIN32))
#include <pwd.h>
#include <grp.h>
#endif
#if defined(HAVE_FCNTL_H)
#include <fcntl.h>
#endif
#if defined(HAVE_SIGNAL_H)
#include <signal.h>
#endif
#if defined(HAVE_VFORK_H)
#include <vfork.h>
#endif
#include <Rts.h>
#if defined(HAVE_WORKING_VFORK)
#define myfork vfork
#elif defined(HAVE_WORKING_FORK)
#define myfork fork
// We don't need a fork command on Windows
#else
#error Cannot find a working fork command
#endif
// Rts internal API, not exposed in a public header file:
extern void blockUserSignals(void);
extern void unblockUserSignals(void);
__attribute__((__noreturn__))
static void
child_failed(int pipe, const char *failed_doing) {
int err;
ssize_t unused __attribute__((unused));
err = errno;
// Having the child send the failed_doing pointer across the pipe is safe as
// we know that the child still has the same address space as the parent.
unused = write(pipe, &failed_doing, sizeof(failed_doing));
unused = write(pipe, &err, sizeof(err));
// As a fallback, exit
_exit(127);
}
static int
setup_std_handle_fork(int fd,
struct std_handle *b,
int pipe)
{
switch (b->behavior) {
case STD_HANDLE_CLOSE:
if (close(fd) == -1) {
child_failed(pipe, "close");
}
return 0;
case STD_HANDLE_USE_FD:
if (dup2(b->use_fd, fd) == -1) {
child_failed(pipe, "dup2");
}
return 0;
case STD_HANDLE_USE_PIPE:
if (b->use_pipe.child_end != fd) {
if (dup2(b->use_pipe.child_end, fd) == -1) {
child_failed(pipe, "dup2(child_end)");
}
if (close(b->use_pipe.child_end) == -1) {
child_failed(pipe, "close(child_end)");
}
}
if (close(b->use_pipe.parent_end) == -1) {
child_failed(pipe, "close(parent_end)");
}
return 0;
default:
// N.B. this should be unreachable but some compilers apparently can't
// see this.
child_failed(pipe, "setup_std_handle_fork(invalid behavior)");
}
}
/* Try spawning with fork. */
ProcHandle
do_spawn_fork (char *const args[],
char *workingDirectory, char **environment,
struct std_handle *stdInHdl,
struct std_handle *stdOutHdl,
struct std_handle *stdErrHdl,
gid_t *childGroup, uid_t *childUser,
int flags,
char **failed_doing)
{
int forkCommunicationFds[2];
int r = pipe(forkCommunicationFds);
if (r == -1) {
*failed_doing = "pipe";
return -1;
}
// Block signals with Haskell handlers. The danger here is that
// with the threaded RTS, a signal arrives in the child process,
// the RTS writes the signal information into the pipe (which is
// shared between parent and child), and the parent behaves as if
// the signal had been raised.
blockUserSignals();
// See #4074. Sometimes fork() gets interrupted by the timer
// signal and keeps restarting indefinitely.
stopTimer();
// N.B. execvpe is not supposed on some platforms. In this case
// we emulate this using fork and exec. However, to safely do so
// we need to perform all allocations *prior* to forking. Consequently, we
// need to find_executable before forking.
#if !defined(HAVE_EXECVPE)
char *exec_path;
if (environment) {
exec_path = find_executable(workingDirectory, args[0]);
if (exec_path == NULL) {
errno = -ENOENT;
*failed_doing = "find_executable";
return -1;
}
}
#endif
int pid = myfork();
switch(pid)
{
case -1:
unblockUserSignals();
startTimer();
close(forkCommunicationFds[0]);
close(forkCommunicationFds[1]);
*failed_doing = "fork";
return -1;
case 0:
// WARNING! We may now be in the child of vfork(), and any
// memory we modify below may also be seen in the parent
// process.
close(forkCommunicationFds[0]);
fcntl(forkCommunicationFds[1], F_SETFD, FD_CLOEXEC);
if ((flags & RUN_PROCESS_NEW_SESSION) != 0) {
setsid();
}
if ((flags & RUN_PROCESS_IN_NEW_GROUP) != 0) {
setpgid(0, 0);
}
if (childGroup) {
if (setgid( *childGroup) != 0) {
// ERROR
child_failed(forkCommunicationFds[1], "setgid");
}
}
if (childUser) {
// Using setuid properly first requires that we initgroups.
// However, to do this we must know the username of the user we are
// switching to.
struct passwd pw;
struct passwd *res = NULL;
int buf_len = sysconf(_SC_GETPW_R_SIZE_MAX);
// TODO: Strictly speaking malloc is a no-no after fork() since it
// may try to take a lock
char *buf = malloc(buf_len);
gid_t suppl_gid = childGroup ? *childGroup : getgid();
if ( getpwuid_r(*childUser, &pw, buf, buf_len, &res) != 0) {
child_failed(forkCommunicationFds[1], "getpwuid");
}
if ( res == NULL ) {
child_failed(forkCommunicationFds[1], "getpwuid");
}
if ( initgroups(res->pw_name, suppl_gid) != 0) {
child_failed(forkCommunicationFds[1], "initgroups");
}
if ( setuid( *childUser) != 0) {
// ERROR
child_failed(forkCommunicationFds[1], "setuid");
}
}
unblockUserSignals();
if (workingDirectory) {
if (chdir (workingDirectory) < 0) {
child_failed(forkCommunicationFds[1], "chdir");
}
}
// Note [Ordering of handle closing]
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Ordering matters here. If any of the FDs
// 0,1,2 were initially closed, then our pipes may have used
// these FDs. So when we dup2 the pipe FDs down to 0,1,2, we
// must do it in that order, otherwise we could overwrite an
// FD that we need later. See ticket #431.
setup_std_handle_fork(STDIN_FILENO, stdInHdl, forkCommunicationFds[1]);
setup_std_handle_fork(STDOUT_FILENO, stdOutHdl, forkCommunicationFds[1]);
setup_std_handle_fork(STDERR_FILENO, stdErrHdl, forkCommunicationFds[1]);
if ((flags & RUN_PROCESS_IN_CLOSE_FDS) != 0) {
int max_fd = get_max_fd();
// XXX Not the pipe
for (int i = 3; i < max_fd; i++) {
if (i != forkCommunicationFds[1]) {
close(i);
}
}
}
/* Reset the SIGINT/SIGQUIT signal handlers in the child, if requested
*/
if ((flags & RESET_INT_QUIT_HANDLERS) != 0) {
struct sigaction dfl;
(void)sigemptyset(&dfl.sa_mask);
dfl.sa_flags = 0;
dfl.sa_handler = SIG_DFL;
(void)sigaction(SIGINT, &dfl, NULL);
(void)sigaction(SIGQUIT, &dfl, NULL);
}
/* the child */
if (environment) {
#if defined(HAVE_EXECVPE)
// XXX Check result
execvpe(args[0], args, environment);
#else
// XXX Check result
execve(exec_path, args, environment);
#endif
} else {
// XXX Check result
execvp(args[0], args);
}
child_failed(forkCommunicationFds[1], "exec");
default:
if ((flags & RUN_PROCESS_IN_NEW_GROUP) != 0) {
setpgid(pid, pid);
}
close(forkCommunicationFds[1]);
fcntl(forkCommunicationFds[0], F_SETFD, FD_CLOEXEC);
break;
}
// If the child process had a problem, then it will tell us via the
// forkCommunicationFds pipe. First we try to read what the problem
// was. Note that if none of these conditionals match then we fall
// through and just return pid.
char *fail_reason;
r = read(forkCommunicationFds[0], &fail_reason, sizeof(fail_reason));
if (r == -1) {
*failed_doing = "read pipe";
pid = -1;
}
else if (r == sizeof(fail_reason)) {
*failed_doing = fail_reason;
// Now we try to get the errno from the child
int err;
r = read(forkCommunicationFds[0], &err, sizeof(err));
if (r == -1) {
*failed_doing = "read pipe";
} else if (r != sizeof(err)) {
*failed_doing = "read pipe bad length";
} else {
// If we succeed then we set errno. It'll be saved and
// restored again below. Note that in any other case we'll
// get the errno of whatever else went wrong instead.
errno = err;
}
// We forked the child, but the child had a problem and stopped so it's
// our responsibility to reap here as nobody else can.
waitpid(pid, NULL, 0);
// Already closed child ends above
if (stdInHdl->behavior == STD_HANDLE_USE_PIPE) {
close(stdInHdl->use_pipe.parent_end);
}
if (stdOutHdl->behavior == STD_HANDLE_USE_PIPE) {
close(stdOutHdl->use_pipe.parent_end);
}
if (stdErrHdl->behavior == STD_HANDLE_USE_PIPE) {
close(stdErrHdl->use_pipe.parent_end);
}
pid = -1;
}
else if (r != 0) {
*failed_doing = "read pipe bad length";
pid = -1;
}
close(forkCommunicationFds[0]);
unblockUserSignals();
startTimer();
return pid;
}