File: fork_exec.c

package info (click to toggle)
ghc 9.6.6-4
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 158,216 kB
  • sloc: haskell: 648,228; ansic: 81,656; cpp: 11,808; javascript: 8,444; sh: 5,831; fortran: 3,527; python: 3,277; asm: 2,523; makefile: 2,298; yacc: 1,570; lisp: 532; xml: 196; perl: 145; csh: 2
file content (356 lines) | stat: -rw-r--r-- 11,027 bytes parent folder | download | duplicates (2)
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
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
/* Ensure that execvpe and pipe2 are provided if possible */
#define _GNU_SOURCE 1

/* Ensure getpwuid_r(3) is available on Solaris. */
#if defined(__sun)
#define _POSIX_PTHREAD_SEMANTICS
#endif

#include "common.h"

#if defined(HAVE_FORK)

#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

#include <Rts.h>

#if !defined(HAVE_WORKING_FORK)
#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 && errno != EBADF) {
            child_failed(pipe, "close");
        }
        return 0;

    case STD_HANDLE_USE_FD:
        // N.B. POSIX specifies that dup2(x,x) should be a no-op, but
        // naturally Apple ignores this and rather fails in posix_spawn on Big
        // Sur.
        if (b->use_fd != 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)");
    }
}

/* This will `dup` the given fd such that it does not fall in the range of
 * stdin/stdout/stderr, if necessary. The new handle will have O_CLOEXEC.
 *
 * This is necessary as we must ensure that the fork communications pipe does
 * not inhabit fds 0 through 2 since we will need to manipulate these fds in
 * setup_std_handle_fork while keeping the pipe available so that it can report
 * errors. See #266.
 */
int unshadow_pipe_fd(int fd, char **failed_doing) {
    if (fd > 2) {
        return fd;
    }

    int new_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
    if (new_fd == -1) {
        *failed_doing = "fcntl(F_DUP_FD)";
        return -1;
    }
    close(fd);
    return new_fd;
}

/* 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;

#if defined(HAVE_PIPE2)
    r = pipe2(forkCommunicationFds, O_CLOEXEC);
#else
    r = pipe(forkCommunicationFds);
#endif
    if (r == -1) {
        *failed_doing = "pipe";
        return -1;
    }

    // Ensure that the pipe fds don't shadow stdin/stdout/stderr
    forkCommunicationFds[0] = unshadow_pipe_fd(forkCommunicationFds[0], failed_doing);
    if (forkCommunicationFds[0] == -1) {
        return -1;
    }
    forkCommunicationFds[1] = unshadow_pipe_fd(forkCommunicationFds[1], failed_doing);
    if (forkCommunicationFds[1] == -1) {
        return -1;
    }

    // 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

    // 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();

    int pid = fork();
    switch(pid)
    {
    case -1:
        unblockUserSignals();
        startTimer();
        close(forkCommunicationFds[0]);
        close(forkCommunicationFds[1]);
        *failed_doing = "fork";
        return -1;

    case 0:
        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);
            child_failed(forkCommunicationFds[1], "execvpe");
#else
            // XXX Check result
            execve(exec_path, args, environment);
            child_failed(forkCommunicationFds[1], "execve");
#endif
        } else {
            // XXX Check result
            execvp(args[0], args);
            child_failed(forkCommunicationFds[1], "execvp");
        }

    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);

        // No need to close stdin, et al. here as runInteractiveProcess will
        // handle this. See #306.

        pid = -1;
    }
    else if (r != 0) {
        *failed_doing = "read pipe bad length";
        pid = -1;
    }

    close(forkCommunicationFds[0]);

    unblockUserSignals();
    startTimer();

    return pid;
}

#endif // HAVE_FORK