|
| 1 | +#if 0 |
| 2 | +/*─────────────────────────────────────────────────────────────────╗ |
| 3 | +│ To the extent possible under law, Justine Tunney has waived │ |
| 4 | +│ all copyright and related or neighboring rights to this file, │ |
| 5 | +│ as it is written in the following disclaimers: │ |
| 6 | +│ • http://unlicense.org/ │ |
| 7 | +│ • http://creativecommons.org/publicdomain/zero/1.0/ │ |
| 8 | +╚─────────────────────────────────────────────────────────────────*/ |
| 9 | +#endif |
| 10 | + |
| 11 | +// posix_spawn() example |
| 12 | +// |
| 13 | +// This program demonstrates the use of posix_spawn() to run the command |
| 14 | +// `ls --dired` and capture its output. It teaches several key features: |
| 15 | +// |
| 16 | +// - Changing the working directory for the child process |
| 17 | +// - Redirecting stdout and stderr to pipes |
| 18 | +// - Handling the output from the child process |
| 19 | +// |
| 20 | +// The primary advantage of using posix_spawn() instead of the |
| 21 | +// traditional fork()/execve() combination for launching processes is |
| 22 | +// safety, efficiency, and cross-platform compatibility. |
| 23 | +// |
| 24 | +// 1. On Linux, FreeBSD, and NetBSD: |
| 25 | +// |
| 26 | +// Cosmopolitan Libc's posix_spawn() uses vfork() under the hood on |
| 27 | +// these platforms automatically, since it's faster than fork(). It's |
| 28 | +// because vfork() creates a child process without needing to copy |
| 29 | +// the parent's page tables, making it more efficient, especially for |
| 30 | +// large processes. Furthermore, vfork() avoids the need to acquire |
| 31 | +// every single mutex (see pthread_atfork() for more details) which |
| 32 | +// makes it scalable in multi-threaded apps, since the other threads |
| 33 | +// in your app can keep going while the spawning thread waits for the |
| 34 | +// subprocess to call execve(). Normally vfork() is error-prone since |
| 35 | +// there exists few functions that are @vforksafe. the posix_spawn() |
| 36 | +// API is designed to offer maximum assurance that you can't shoot |
| 37 | +// yourself in the foot. If you do, then file a bug with Cosmo. |
| 38 | +// |
| 39 | +// 2. On Windows: |
| 40 | +// |
| 41 | +// posix_spawn() avoids fork() entirely. Windows doesn't natively |
| 42 | +// support fork(), and emulating it can be slow and memory-intensive. |
| 43 | +// By using posix_spawn(), we get a much faster process creation on |
| 44 | +// Windows systems, because it only needs to call CreateProcess(). |
| 45 | +// Your file actions are replayed beforehand in a simulated way. Only |
| 46 | +// Cosmopolitan Libc offers this level of quality. With Cygwin you'd |
| 47 | +// have to use its proprietary APIs to achieve the same performance. |
| 48 | +// |
| 49 | +// 3. Simplified error handling: |
| 50 | +// |
| 51 | +// posix_spawn() combines process creation and program execution in a |
| 52 | +// single call, reducing the points of failure and simplifying error |
| 53 | +// handling. One important thing that happens with Cosmopolitan's |
| 54 | +// posix_spawn() implementation is that the error code of execve() |
| 55 | +// inside your subprocess, should it fail, will be propagated to your |
| 56 | +// parent process. This will happen efficiently via vfork() shared |
| 57 | +// memory in the event your Linux environment supports this. If it |
| 58 | +// doesn't, then Cosmopolitan will fall back to a throwaway pipe(). |
| 59 | +// The pipe is needed on platforms like XNU and OpenBSD which do not |
| 60 | +// support vfork(). It's also needed under QEMU User. |
| 61 | +// |
| 62 | +// 4. Signal safety: |
| 63 | +// |
| 64 | +// posix_spawn() guarantees your signal handler callback functions |
| 65 | +// won't be executed in the child process. By default, it'll remove |
| 66 | +// sigaction() callbacks atomically. This ensures that if something |
| 67 | +// like a SIGTERM or SIGHUP is sent to the child process before it's |
| 68 | +// had a chance to call execve(), then the child process will simply |
| 69 | +// be terminated (like the spawned process would) instead of running |
| 70 | +// whatever signal handlers the spawning process has installed. If |
| 71 | +// you've set some signals to SIG_IGN, then that'll be preserved for |
| 72 | +// the child process by posix_spawn(), unless you explicitly call |
| 73 | +// posix_spawnattr_setsigdefault() to reset them. |
| 74 | +// |
| 75 | +// 5. Portability: |
| 76 | +// |
| 77 | +// posix_spawn() is part of the POSIX standard, making it more |
| 78 | +// portable across different UNIX-like systems and Windows (with |
| 79 | +// appropriate libraries). Even the non-POSIX APIs we use here are |
| 80 | +// portable; e.g. posix_spawn_file_actions_addchdir_np() is supported |
| 81 | +// by glibc, musl, freebsd, and apple too. |
| 82 | +// |
| 83 | +// These benefits make posix_spawn() a preferred choice for efficient |
| 84 | +// and portable process creation in many scenarios, especially when |
| 85 | +// launching many processes or on systems where process creation |
| 86 | +// performance is critical. |
| 87 | + |
| 88 | +#define _GNU_SOURCE |
| 89 | +#include <fcntl.h> |
| 90 | +#include <spawn.h> |
| 91 | +#include <stdio.h> |
| 92 | +#include <stdlib.h> |
| 93 | +#include <string.h> |
| 94 | +#include <sys/wait.h> |
| 95 | +#include <unistd.h> |
| 96 | + |
| 97 | +#define PIPE_READ 0 |
| 98 | +#define PIPE_WRITE 1 |
| 99 | + |
| 100 | +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]; |
| 107 | + |
| 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; |
| 113 | + } |
| 114 | + |
| 115 | + // Explicitly request vfork() from posix_spawn() implementation |
| 116 | + // |
| 117 | + // This is currently the default for Cosmopolitan Libc, however you |
| 118 | + // may want to set this anyway, for portability with other platforms. |
| 119 | + // Please note that vfork() isn't officially specified by POSIX, so |
| 120 | + // 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 | + } |
| 126 | + |
| 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; |
| 133 | + } |
| 134 | + |
| 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; |
| 141 | + } |
| 142 | + |
| 143 | + // Create pipes for stdout and stderr |
| 144 | + if (pipe(pipe_stdout) == -1 || pipe(pipe_stderr) == -1) { |
| 145 | + perror("pipe"); |
| 146 | + return 1; |
| 147 | + } |
| 148 | + |
| 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; |
| 156 | + } |
| 157 | + |
| 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; |
| 165 | + } |
| 166 | + |
| 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 | + } |
| 182 | + |
| 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; |
| 188 | + } |
| 189 | + |
| 190 | + // Close unused write ends of pipes in the parent process |
| 191 | + close(pipe_stdout[PIPE_WRITE]); |
| 192 | + close(pipe_stderr[PIPE_WRITE]); |
| 193 | + |
| 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); |
| 202 | + } |
| 203 | + |
| 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); |
| 208 | + } |
| 209 | + |
| 210 | + // Wait for the child process to complete |
| 211 | + if (waitpid(pid, &status, 0) == -1) { |
| 212 | + perror("waitpid"); |
| 213 | + return 1; |
| 214 | + } |
| 215 | + |
| 216 | + // Clean up |
| 217 | + posix_spawn_file_actions_destroy(&actions); |
| 218 | + posix_spawnattr_destroy(&attr); |
| 219 | + close(pipe_stdout[PIPE_READ]); |
| 220 | + close(pipe_stderr[PIPE_READ]); |
| 221 | + |
| 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))); |
| 227 | + } else { |
| 228 | + printf("Child process did not exit normally\n"); |
| 229 | + } |
| 230 | + |
| 231 | + return 0; |
| 232 | +} |
0 commit comments