|
86 | 86 | // performance is critical.
|
87 | 87 |
|
88 | 88 | #define _GNU_SOURCE
|
| 89 | +#include <errno.h> |
89 | 90 | #include <fcntl.h>
|
| 91 | +#include <poll.h> |
| 92 | +#include <signal.h> |
90 | 93 | #include <spawn.h>
|
91 | 94 | #include <stdio.h>
|
92 | 95 | #include <stdlib.h>
|
93 | 96 | #include <string.h>
|
| 97 | +#include <sys/select.h> |
94 | 98 | #include <sys/wait.h>
|
95 | 99 | #include <unistd.h>
|
96 | 100 |
|
| 101 | +#define max(X, Y) ((Y) < (X) ? (X) : (Y)) |
| 102 | + |
| 103 | +#define USE_SELECT 0 // want poll() or select()? they both work great |
| 104 | + |
97 | 105 | #define PIPE_READ 0
|
98 | 106 | #define PIPE_WRITE 1
|
99 | 107 |
|
100 | 108 | int main() {
|
101 |
| - pid_t pid; |
102 |
| - int status, ret; |
103 |
| - posix_spawnattr_t attr; |
104 |
| - posix_spawn_file_actions_t actions; |
105 |
| - char *const argv[] = {"ls", "--dired", NULL}; |
106 |
| - int pipe_stdout[2], pipe_stderr[2]; |
| 109 | + errno_t err; |
107 | 110 |
|
108 |
| - // Initialize file actions |
109 |
| - ret = posix_spawnattr_init(&attr); |
110 |
| - if (ret != 0) { |
111 |
| - fprintf(stderr, "posix_spawnattr_init failed: %s\n", strerror(ret)); |
112 |
| - return 1; |
| 111 | + // Create spawn attributes object. |
| 112 | + posix_spawnattr_t attr; |
| 113 | + err = posix_spawnattr_init(&attr); |
| 114 | + if (err != 0) { |
| 115 | + fprintf(stderr, "posix_spawnattr_init failed: %s\n", strerror(err)); |
| 116 | + exit(1); |
113 | 117 | }
|
114 | 118 |
|
115 |
| - // Explicitly request vfork() from posix_spawn() implementation |
| 119 | + // Explicitly request vfork() from posix_spawn() implementation. |
116 | 120 | //
|
117 | 121 | // This is currently the default for Cosmopolitan Libc, however you
|
118 | 122 | // may want to set this anyway, for portability with other platforms.
|
119 | 123 | // Please note that vfork() isn't officially specified by POSIX, so
|
120 | 124 | // portable code may want to omit this and just use the default.
|
121 |
| - ret = posix_spawnattr_setflags(&attr, POSIX_SPAWN_USEVFORK); |
122 |
| - if (ret != 0) { |
123 |
| - fprintf(stderr, "posix_spawnattr_setflags failed: %s\n", strerror(ret)); |
124 |
| - return 1; |
| 125 | + err = posix_spawnattr_setflags(&attr, POSIX_SPAWN_USEVFORK); |
| 126 | + if (err != 0) { |
| 127 | + fprintf(stderr, "posix_spawnattr_setflags: %s\n", strerror(err)); |
| 128 | + exit(2); |
125 | 129 | }
|
126 | 130 |
|
127 |
| - // Initialize file actions |
128 |
| - ret = posix_spawn_file_actions_init(&actions); |
129 |
| - if (ret != 0) { |
130 |
| - fprintf(stderr, "posix_spawn_file_actions_init failed: %s\n", |
131 |
| - strerror(ret)); |
132 |
| - return 1; |
| 131 | + // Create file actions object. |
| 132 | + posix_spawn_file_actions_t actions; |
| 133 | + err = posix_spawn_file_actions_init(&actions); |
| 134 | + if (err != 0) { |
| 135 | + fprintf(stderr, "posix_spawn_file_actions_init: %s\n", strerror(err)); |
| 136 | + exit(3); |
133 | 137 | }
|
134 | 138 |
|
135 |
| - // Change directory to $HOME |
136 |
| - ret = posix_spawn_file_actions_addchdir_np(&actions, getenv("HOME")); |
137 |
| - if (ret != 0) { |
138 |
| - fprintf(stderr, "posix_spawn_file_actions_addchdir_np failed: %s\n", |
139 |
| - strerror(ret)); |
140 |
| - return 1; |
| 139 | + // Change directory to root directory in child process. |
| 140 | + err = posix_spawn_file_actions_addchdir_np(&actions, "/"); |
| 141 | + if (err != 0) { |
| 142 | + fprintf(stderr, "posix_spawn_file_actions_addchdir_np: %s\n", |
| 143 | + strerror(err)); |
| 144 | + exit(4); |
141 | 145 | }
|
142 | 146 |
|
143 |
| - // Create pipes for stdout and stderr |
144 |
| - if (pipe(pipe_stdout) == -1 || pipe(pipe_stderr) == -1) { |
145 |
| - perror("pipe"); |
146 |
| - return 1; |
| 147 | + // Disable stdin in child process. |
| 148 | + // |
| 149 | + // By default, if you launch this example in your terminal, then child |
| 150 | + // processes can read from your teletypewriter's keyboard too. You can |
| 151 | + // avoid this by assigning /dev/null to standard input so if the child |
| 152 | + // tries to read input, read() will return zero, indicating eof. |
| 153 | + if ((err = posix_spawn_file_actions_addopen(&actions, STDIN_FILENO, |
| 154 | + "/dev/null", O_RDONLY, 0644))) { |
| 155 | + fprintf(stderr, "posix_spawn_file_actions_addopen: %s\n", strerror(err)); |
| 156 | + exit(5); |
147 | 157 | }
|
148 | 158 |
|
149 |
| - // Redirect child's stdout to pipe |
150 |
| - ret = posix_spawn_file_actions_adddup2(&actions, pipe_stdout[PIPE_WRITE], |
151 |
| - STDOUT_FILENO); |
152 |
| - if (ret != 0) { |
153 |
| - fprintf(stderr, "posix_spawn_file_actions_adddup2 (stdout) failed: %s\n", |
154 |
| - strerror(ret)); |
155 |
| - return 1; |
| 159 | + // Create pipes for stdout and stderr. |
| 160 | + // |
| 161 | + // Using O_DIRECT puts the pipe in message mode. This way we have some |
| 162 | + // visibility into how the child process is using write(). It can also |
| 163 | + // help ensure that logged lines won't be chopped up here, which could |
| 164 | + // happen more frequently on platforms like Windows, which is somewhat |
| 165 | + // less sophisticated than Linux with how it performs buffering. |
| 166 | + // |
| 167 | + // You can also specify O_CLOEXEC, which is a nice touch that lets you |
| 168 | + // avoid needing to call posix_spawn_file_actions_addclose() later on. |
| 169 | + // That's because all file descriptors are inherited by child programs |
| 170 | + // by default. This is even the case with Cosmopolitan Libc on Windows |
| 171 | + // |
| 172 | + // XXX: We assume that stdin/stdout/stderr exist in this process. It's |
| 173 | + // possible for a rogue parent process to launch this example, in |
| 174 | + // a way where the following spawn logic will break. |
| 175 | + int pipe_stdout[2]; |
| 176 | + int pipe_stderr[2]; |
| 177 | + if (pipe2(pipe_stdout, O_DIRECT) == -1 || |
| 178 | + pipe2(pipe_stderr, O_DIRECT) == -1) { |
| 179 | + perror("pipe"); |
| 180 | + exit(6); |
156 | 181 | }
|
157 | 182 |
|
158 |
| - // Redirect child's stderr to pipe |
159 |
| - ret = posix_spawn_file_actions_adddup2(&actions, pipe_stderr[PIPE_WRITE], |
160 |
| - STDERR_FILENO); |
161 |
| - if (ret != 0) { |
162 |
| - fprintf(stderr, "posix_spawn_file_actions_adddup2 (stderr) failed: %s\n", |
163 |
| - strerror(ret)); |
164 |
| - return 1; |
| 183 | + // Redirect child's stdout/stderr to pipes |
| 184 | + if ((err = posix_spawn_file_actions_adddup2(&actions, pipe_stdout[PIPE_WRITE], |
| 185 | + STDOUT_FILENO)) || |
| 186 | + (err = posix_spawn_file_actions_adddup2(&actions, pipe_stderr[PIPE_WRITE], |
| 187 | + STDERR_FILENO))) { |
| 188 | + fprintf(stderr, "posix_spawn_file_actions_adddup2: %s\n", strerror(err)); |
| 189 | + exit(7); |
165 | 190 | }
|
166 | 191 |
|
167 |
| - // Close unused write ends of pipes in the child process |
168 |
| - ret = posix_spawn_file_actions_addclose(&actions, pipe_stdout[PIPE_READ]); |
169 |
| - if (ret != 0) { |
170 |
| - fprintf(stderr, |
171 |
| - "posix_spawn_file_actions_addclose (stdout read) failed: %s\n", |
172 |
| - strerror(ret)); |
173 |
| - return 1; |
174 |
| - } |
175 |
| - ret = posix_spawn_file_actions_addclose(&actions, pipe_stderr[PIPE_READ]); |
176 |
| - if (ret != 0) { |
177 |
| - fprintf(stderr, |
178 |
| - "posix_spawn_file_actions_addclose (stderr read) failed: %s\n", |
179 |
| - strerror(ret)); |
180 |
| - return 1; |
181 |
| - } |
| 192 | + // Close unwanted write ends of pipes in the child process |
| 193 | + if ((err = posix_spawn_file_actions_addclose(&actions, |
| 194 | + pipe_stdout[PIPE_READ])) || |
| 195 | + (err = posix_spawn_file_actions_addclose(&actions, |
| 196 | + pipe_stderr[PIPE_READ]))) { |
| 197 | + fprintf(stderr, "posix_spawn_file_actions_addclose: %s\n", strerror(err)); |
| 198 | + exit(8); |
| 199 | + }; |
182 | 200 |
|
183 |
| - // Spawn the child process |
184 |
| - ret = posix_spawnp(&pid, "ls", &actions, NULL, argv, NULL); |
185 |
| - if (ret != 0) { |
186 |
| - fprintf(stderr, "posix_spawn failed: %s\n", strerror(ret)); |
187 |
| - return 1; |
| 201 | + // Asynchronously launch the child process. |
| 202 | + pid_t pid; |
| 203 | + char *const argv[] = {"ls", "--dired", NULL}; |
| 204 | + printf("** Launching `ls --dired` in root directory\n"); |
| 205 | + err = posix_spawnp(&pid, argv[0], &actions, NULL, argv, NULL); |
| 206 | + if (err) { |
| 207 | + fprintf(stderr, "posix_spawn: %s\n", strerror(err)); |
| 208 | + exit(9); |
188 | 209 | }
|
189 | 210 |
|
190 | 211 | // Close unused write ends of pipes in the parent process
|
191 | 212 | close(pipe_stdout[PIPE_WRITE]);
|
192 | 213 | close(pipe_stderr[PIPE_WRITE]);
|
193 | 214 |
|
194 |
| - // Read and print output from child process |
195 |
| - char buffer[4096]; |
196 |
| - ssize_t bytes_read; |
197 |
| - |
198 |
| - printf("Stdout from child process:\n"); |
199 |
| - while ((bytes_read = read(pipe_stdout[PIPE_READ], buffer, sizeof(buffer))) > |
200 |
| - 0) { |
201 |
| - write(STDOUT_FILENO, buffer, bytes_read); |
| 215 | + // we need poll() or select() because we're multiplexing output |
| 216 | + // both poll() and select() work across all supported platforms |
| 217 | +#if USE_SELECT |
| 218 | + // Relay output from child process using select() |
| 219 | + char buffer[512]; |
| 220 | + ssize_t got_stdout = 1; |
| 221 | + ssize_t got_stderr = 1; |
| 222 | + while (got_stdout > 0 || got_stderr > 0) { |
| 223 | + fd_set rfds; |
| 224 | + FD_ZERO(&rfds); |
| 225 | + if (got_stdout > 0) |
| 226 | + FD_SET(pipe_stdout[PIPE_READ], &rfds); |
| 227 | + if (got_stderr > 0) |
| 228 | + FD_SET(pipe_stderr[PIPE_READ], &rfds); |
| 229 | + int nfds = max(pipe_stdout[PIPE_READ], pipe_stderr[PIPE_READ]) + 1; |
| 230 | + if (select(nfds, &rfds, 0, 0, 0) == -1) { |
| 231 | + perror("select"); |
| 232 | + exit(10); |
| 233 | + } |
| 234 | + if (FD_ISSET(pipe_stdout[PIPE_READ], &rfds)) { |
| 235 | + got_stdout = read(pipe_stdout[PIPE_READ], buffer, sizeof(buffer)); |
| 236 | + printf("\n"); |
| 237 | + if (got_stdout > 0) { |
| 238 | + printf("** Got stdout from child process:\n"); |
| 239 | + fflush(stdout); |
| 240 | + write(STDOUT_FILENO, buffer, got_stdout); |
| 241 | + } else if (!got_stdout) { |
| 242 | + printf("** Got stdout EOF from child process\n"); |
| 243 | + } else { |
| 244 | + printf("** Got stdout read() error from child process: %s\n", |
| 245 | + strerror(errno)); |
| 246 | + } |
| 247 | + } |
| 248 | + if (FD_ISSET(pipe_stderr[PIPE_READ], &rfds)) { |
| 249 | + got_stderr = read(pipe_stderr[PIPE_READ], buffer, sizeof(buffer)); |
| 250 | + printf("\n"); |
| 251 | + if (got_stderr > 0) { |
| 252 | + printf("** Got stderr from child process:\n"); |
| 253 | + fflush(stdout); |
| 254 | + write(STDOUT_FILENO, buffer, got_stderr); |
| 255 | + } else if (!got_stderr) { |
| 256 | + printf("** Got stderr EOF from child process\n"); |
| 257 | + } else { |
| 258 | + printf("** Got stderr read() error from child process: %s\n", |
| 259 | + strerror(errno)); |
| 260 | + } |
| 261 | + } |
202 | 262 | }
|
203 | 263 |
|
204 |
| - printf("\nStderr from child process:\n"); |
205 |
| - while ((bytes_read = read(pipe_stderr[PIPE_READ], buffer, sizeof(buffer))) > |
206 |
| - 0) { |
207 |
| - write(STDERR_FILENO, buffer, bytes_read); |
| 264 | +#else |
| 265 | + // Relay output from child process using poll() |
| 266 | + char buffer[512]; |
| 267 | + ssize_t got_stdout = 1; |
| 268 | + ssize_t got_stderr = 1; |
| 269 | + while (got_stdout > 0 || got_stderr > 0) { |
| 270 | + struct pollfd fds[2]; |
| 271 | + fds[0].fd = got_stdout > 0 ? pipe_stdout[PIPE_READ] : -1; |
| 272 | + fds[0].events = POLLIN; // POLLHUP, POLLNVAL, and POLLERR are implied |
| 273 | + fds[1].fd = got_stderr > 0 ? pipe_stderr[PIPE_READ] : -1; |
| 274 | + fds[1].events = POLLIN; // POLLHUP, POLLNVAL, and POLLERR are implied |
| 275 | + if (poll(fds, 2, -1) == -1) { |
| 276 | + perror("select"); |
| 277 | + exit(10); |
| 278 | + } |
| 279 | + if (fds[0].revents) { |
| 280 | + printf("\n"); |
| 281 | + if (fds[0].revents & POLLIN) |
| 282 | + printf("** Got POLLIN on stdout from child process\n"); |
| 283 | + if (fds[0].revents & POLLHUP) |
| 284 | + printf("** Got POLLHUP on stdout from child process\n"); |
| 285 | + if (fds[0].revents & POLLERR) |
| 286 | + printf("** Got POLLERR on stdout from child process\n"); |
| 287 | + if (fds[0].revents & POLLNVAL) |
| 288 | + printf("** Got POLLNVAL on stdout from child process\n"); |
| 289 | + got_stdout = read(pipe_stdout[PIPE_READ], buffer, sizeof(buffer)); |
| 290 | + if (got_stdout > 0) { |
| 291 | + printf("** Got stdout from child process:\n"); |
| 292 | + fflush(stdout); |
| 293 | + write(STDOUT_FILENO, buffer, got_stdout); |
| 294 | + } else if (!got_stdout) { |
| 295 | + printf("** Got stdout EOF from child process\n"); |
| 296 | + } else { |
| 297 | + printf("** Got stdout read() error from child process: %s\n", |
| 298 | + strerror(errno)); |
| 299 | + } |
| 300 | + } |
| 301 | + if (fds[1].revents) { |
| 302 | + printf("\n"); |
| 303 | + if (fds[1].revents & POLLIN) |
| 304 | + printf("** Got POLLIN on stderr from child process\n"); |
| 305 | + if (fds[1].revents & POLLHUP) |
| 306 | + printf("** Got POLLHUP on stderr from child process\n"); |
| 307 | + if (fds[1].revents & POLLERR) |
| 308 | + printf("** Got POLLERR on stderr from child process\n"); |
| 309 | + if (fds[1].revents & POLLNVAL) |
| 310 | + printf("** Got POLLNVAL on stderr from child process\n"); |
| 311 | + got_stderr = read(pipe_stderr[PIPE_READ], buffer, sizeof(buffer)); |
| 312 | + if (got_stderr > 0) { |
| 313 | + printf("** Got stderr from child process:\n"); |
| 314 | + fflush(stdout); |
| 315 | + write(STDOUT_FILENO, buffer, got_stderr); |
| 316 | + } else if (!got_stderr) { |
| 317 | + printf("** Got stderr EOF from child process\n"); |
| 318 | + } else { |
| 319 | + printf("** Got stderr read() error from child process: %s\n", |
| 320 | + strerror(errno)); |
| 321 | + } |
| 322 | + } |
208 | 323 | }
|
| 324 | +#endif |
209 | 325 |
|
210 |
| - // Wait for the child process to complete |
211 |
| - if (waitpid(pid, &status, 0) == -1) { |
| 326 | + // Wait for child process to die. |
| 327 | + int wait_status; |
| 328 | + if (waitpid(pid, &wait_status, 0) == -1) { |
212 | 329 | perror("waitpid");
|
213 |
| - return 1; |
| 330 | + exit(11); |
214 | 331 | }
|
215 | 332 |
|
216 |
| - // Clean up |
| 333 | + // Clean up resources. |
217 | 334 | posix_spawn_file_actions_destroy(&actions);
|
218 | 335 | posix_spawnattr_destroy(&attr);
|
219 | 336 | close(pipe_stdout[PIPE_READ]);
|
220 | 337 | close(pipe_stderr[PIPE_READ]);
|
221 | 338 |
|
222 |
| - if (WIFEXITED(status)) { |
223 |
| - printf("Child process exited with status %d\n", WEXITSTATUS(status)); |
224 |
| - } else if (WIFSIGNALED(status)) { |
225 |
| - printf("Child process terminated with signal %s\n", |
226 |
| - strsignal(WTERMSIG(status))); |
| 339 | + // Report wait status. |
| 340 | + // |
| 341 | + // When a process dies, it's almost always due to calling _Exit() or |
| 342 | + // being killed due to an unhandled signal. On both UNIX and Windows |
| 343 | + // this information will be propagated to the parent. That status is |
| 344 | + // able to be propagated to the parent of this process too. |
| 345 | + printf("\n"); |
| 346 | + if (WIFEXITED(wait_status)) { |
| 347 | + printf("** Child process exited with exit code %d\n", |
| 348 | + WEXITSTATUS(wait_status)); |
| 349 | + exit(WEXITSTATUS(wait_status)); |
| 350 | + } else if (WIFSIGNALED(wait_status)) { |
| 351 | + printf("** Child process terminated with signal %s\n", |
| 352 | + strsignal(WTERMSIG(wait_status))); |
| 353 | + fflush(stdout); |
| 354 | + sigset_t sm; |
| 355 | + sigemptyset(&sm); |
| 356 | + sigaddset(&sm, WTERMSIG(wait_status)); |
| 357 | + sigprocmask(SIG_UNBLOCK, &sm, 0); |
| 358 | + signal(SIGABRT, SIG_DFL); |
| 359 | + raise(WTERMSIG(wait_status)); |
| 360 | + exit(128 + WTERMSIG(wait_status)); |
227 | 361 | } else {
|
228 |
| - printf("Child process did not exit normally\n"); |
| 362 | + printf("** Child process exited weirdly with wait status 0x%08x\n", |
| 363 | + wait_status); |
| 364 | + exit(12); |
229 | 365 | }
|
230 |
| - |
231 |
| - return 0; |
232 | 366 | }
|
0 commit comments