1#include "libc.h"
2#include "stdio_impl.h"
3#include <errno.h>
4#include <fcntl.h>
5#include <spawn.h>
6#include <string.h>
7#include <unistd.h>
8
9FILE* popen(const char* cmd, const char* mode) {
10    int p[2], op, e;
11    pid_t pid;
12    FILE* f;
13    posix_spawn_file_actions_t fa;
14
15    if (*mode == 'r') {
16        op = 0;
17    } else if (*mode == 'w') {
18        op = 1;
19    } else {
20        errno = EINVAL;
21        return 0;
22    }
23
24    if (pipe2(p, O_CLOEXEC))
25        return NULL;
26    f = fdopen(p[op], mode);
27    if (!f) {
28        close(p[0]);
29        close(p[1]);
30        return NULL;
31    }
32    FLOCK(f);
33
34    /* If the child's end of the pipe happens to already be on the final
35     * fd number to which it will be assigned (either 0 or 1), it must
36     * be moved to a different fd. Otherwise, there is no safe way to
37     * remove the close-on-exec flag in the child without also creating
38     * a file descriptor leak race condition in the parent. */
39    if (p[1 - op] == 1 - op) {
40        int tmp = fcntl(1 - op, F_DUPFD_CLOEXEC, 0);
41        if (tmp < 0) {
42            e = errno;
43            goto fail;
44        }
45        close(p[1 - op]);
46        p[1 - op] = tmp;
47    }
48
49    e = ENOMEM;
50    if (!posix_spawn_file_actions_init(&fa)) {
51        if (!posix_spawn_file_actions_adddup2(&fa, p[1 - op], 1 - op)) {
52            if (!(e = posix_spawn(&pid, "/bin/sh", &fa, 0, (char* []){(char*)"sh", (char*)"-c", (char*)cmd, 0},
53                                  __environ))) {
54                posix_spawn_file_actions_destroy(&fa);
55                f->pipe_pid = pid;
56                if (!strchr(mode, 'e'))
57                    fcntl(p[op], F_SETFD, 0);
58                close(p[1 - op]);
59                FUNLOCK(f);
60                return f;
61            }
62        }
63        posix_spawn_file_actions_destroy(&fa);
64    }
65fail:
66    fclose(f);
67    close(p[1 - op]);
68
69    errno = e;
70    return 0;
71}
72