1 | //===-- Linux implementation of posix_spawn -------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "src/spawn/posix_spawn.h" |
10 | |
11 | #include "src/__support/CPP/optional.h" |
12 | #include "src/__support/OSUtil/syscall.h" // For internal syscall function. |
13 | #include "src/__support/common.h" |
14 | #include "src/spawn/file_actions.h" |
15 | |
16 | #include <fcntl.h> |
17 | #include <signal.h> // For SIGCHLD |
18 | #include <spawn.h> |
19 | #include <sys/syscall.h> // For syscall numbers. |
20 | |
21 | namespace LIBC_NAMESPACE { |
22 | |
23 | namespace { |
24 | |
25 | pid_t fork() { |
26 | // TODO: Use only the clone syscall and use a sperate small stack in the child |
27 | // to avoid duplicating the complete stack from the parent. A new stack will |
28 | // be created on exec anyway so duplicating the full stack is unnecessary. |
29 | #ifdef SYS_fork |
30 | return LIBC_NAMESPACE::syscall_impl<pid_t>(SYS_fork); |
31 | #elif defined(SYS_clone) |
32 | return LIBC_NAMESPACE::syscall_impl<pid_t>(SYS_clone, SIGCHLD, 0); |
33 | #else |
34 | #error "fork or clone syscalls not available." |
35 | #endif |
36 | } |
37 | |
38 | cpp::optional<int> open(const char *path, int oflags, mode_t mode) { |
39 | #ifdef SYS_open |
40 | int fd = LIBC_NAMESPACE::syscall_impl<int>(SYS_open, ts: path, ts: oflags, ts: mode); |
41 | #else |
42 | int fd = LIBC_NAMESPACE::syscall_impl<int>(SYS_openat, AT_FDCWD, path, oflags, |
43 | mode); |
44 | #endif |
45 | if (fd > 0) |
46 | return fd; |
47 | // The open function is called as part of the child process' preparatory |
48 | // steps. If an open fails, the child process just exits. So, unlike |
49 | // the public open function, we do not need to set errno here. |
50 | return cpp::nullopt; |
51 | } |
52 | |
53 | void close(int fd) { LIBC_NAMESPACE::syscall_impl<long>(SYS_close, ts: fd); } |
54 | |
55 | // We use dup3 if dup2 is not available, similar to our implementation of dup2 |
56 | bool dup2(int fd, int newfd) { |
57 | #ifdef SYS_dup2 |
58 | int ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_dup2, ts: fd, ts: newfd); |
59 | #elif defined(SYS_dup3) |
60 | int ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_dup3, fd, newfd, 0); |
61 | #else |
62 | #error "dup2 and dup3 syscalls not available." |
63 | #endif |
64 | return ret < 0 ? false : true; |
65 | } |
66 | |
67 | // All exits from child_process are error exits. So, we use a simple |
68 | // exit implementation which exits with code 127. |
69 | void exit() { |
70 | for (;;) { |
71 | LIBC_NAMESPACE::syscall_impl<long>(SYS_exit_group, ts: 127); |
72 | LIBC_NAMESPACE::syscall_impl<long>(SYS_exit, ts: 127); |
73 | } |
74 | } |
75 | |
76 | void child_process(const char *__restrict path, |
77 | const posix_spawn_file_actions_t *file_actions, |
78 | const posix_spawnattr_t *__restrict, // For now unused |
79 | char *const *__restrict argv, char *const *__restrict envp) { |
80 | // TODO: In the code below, the child_process just exits on error during |
81 | // processing |file_actions| and |attr|. The correct way would be to exit |
82 | // after conveying the information about the failure to the parent process |
83 | // (via a pipe for example). |
84 | // TODO: Handle |attr|. |
85 | |
86 | if (file_actions != nullptr) { |
87 | auto *act = reinterpret_cast<BaseSpawnFileAction *>(file_actions->__front); |
88 | while (act != nullptr) { |
89 | switch (act->type) { |
90 | case BaseSpawnFileAction::OPEN: { |
91 | auto *open_act = reinterpret_cast<SpawnFileOpenAction *>(act); |
92 | auto fd = open(open_act->path, open_act->oflag, open_act->mode); |
93 | if (!fd) |
94 | exit(); |
95 | int actual_fd = *fd; |
96 | if (actual_fd != open_act->fd) { |
97 | bool dup2_result = dup2(actual_fd, open_act->fd); |
98 | close(fd: actual_fd); // The old fd is not needed anymore. |
99 | if (!dup2_result) |
100 | exit(); |
101 | } |
102 | break; |
103 | } |
104 | case BaseSpawnFileAction::CLOSE: { |
105 | auto *close_act = reinterpret_cast<SpawnFileCloseAction *>(act); |
106 | close(close_act->fd); |
107 | break; |
108 | } |
109 | case BaseSpawnFileAction::DUP2: { |
110 | auto *dup2_act = reinterpret_cast<SpawnFileDup2Action *>(act); |
111 | if (!dup2(dup2_act->fd, dup2_act->newfd)) |
112 | exit(); |
113 | break; |
114 | } |
115 | } |
116 | act = act->next; |
117 | } |
118 | } |
119 | |
120 | if (LIBC_NAMESPACE::syscall_impl<long>(SYS_execve, ts: path, ts: argv, ts: envp) < 0) |
121 | exit(); |
122 | } |
123 | |
124 | } // anonymous namespace |
125 | |
126 | LLVM_LIBC_FUNCTION(int, posix_spawn, |
127 | (pid_t *__restrict pid, const char *__restrict path, |
128 | const posix_spawn_file_actions_t *file_actions, |
129 | const posix_spawnattr_t *__restrict attr, |
130 | char *const *__restrict argv, |
131 | char *const *__restrict envp)) { |
132 | pid_t cpid = fork(); |
133 | if (cpid == 0) |
134 | child_process(path, file_actions, attr, argv, envp); |
135 | else if (cpid < 0) |
136 | return -cpid; |
137 | |
138 | if (pid != nullptr) |
139 | *pid = cpid; |
140 | |
141 | // TODO: Before returning, one should wait for the child_process to startup |
142 | // successfully. For now, we will just return. Future changes will add proper |
143 | // wait (using pipes for example). |
144 | |
145 | return 0; |
146 | } |
147 | |
148 | } // namespace LIBC_NAMESPACE |
149 | |