exec_posix.c (7863B)
1 /* 2 * POSIX implementation of the shared KitExec subprocess interface 3 * (<kit/exec.h>). One config-struct spawn serves both the build coordinator 4 * (hermetic: exact argv[0], explicit env, mandatory cwd, capture to files) and 5 * `kit make` (PATH-searched shell, ambient-style env, optional cwd, capture to 6 * a buffer). The user pointer is a DriverEnv* (for its heap). 7 */ 8 #include <errno.h> 9 #include <fcntl.h> 10 #include <signal.h> 11 #include <stdint.h> 12 #include <stdio.h> 13 #include <string.h> 14 #include <sys/wait.h> 15 #include <unistd.h> 16 17 #include <kit/exec.h> 18 19 #include "env.h" 20 21 struct KitExecProc { 22 pid_t pid; 23 int read_fd; /* stdout capture pipe read end, or -1 */ 24 }; 25 26 static char* ex_slice_dup(DriverEnv* env, KitSlice s) { 27 char* out = (char*)driver_alloc(env, s.len + 1u); 28 if (!out) return NULL; 29 if (s.len) memcpy(out, s.s, s.len); 30 out[s.len] = '\0'; 31 return out; 32 } 33 34 /* Join a relative argv[0] to the current directory (exact/execve path). */ 35 static char* ex_cwd_join(DriverEnv* env, KitSlice path) { 36 char cwd[4096]; 37 char* out; 38 size_t nc, np; 39 if (!path.s || path.len == 0u) return NULL; 40 if (path.s[0] == '/') return ex_slice_dup(env, path); 41 if (!getcwd(cwd, sizeof cwd)) return NULL; 42 nc = strlen(cwd); 43 np = path.len; 44 out = (char*)driver_alloc(env, nc + 1u + np + 1u); 45 if (!out) return NULL; 46 memcpy(out, cwd, nc); 47 out[nc] = '/'; 48 memcpy(out + nc + 1u, path.s, np); 49 out[nc + 1u + np] = '\0'; 50 return out; 51 } 52 53 static void ex_free_strv(DriverEnv* env, char** v, size_t n) { 54 size_t i; 55 if (!v) return; 56 for (i = 0; i < n; ++i) 57 if (v[i]) driver_free(env, v[i], strlen(v[i]) + 1u); 58 driver_free(env, v, (n + 1u) * sizeof *v); 59 } 60 61 static int ex_reap(pid_t pid, int* exit_code) { 62 int st; 63 for (;;) { 64 if (waitpid(pid, &st, 0) < 0) { 65 if (errno == EINTR) continue; 66 return 1; 67 } 68 break; 69 } 70 if (WIFEXITED(st)) 71 *exit_code = WEXITSTATUS(st); 72 else if (WIFSIGNALED(st)) 73 *exit_code = -WTERMSIG(st); 74 else 75 *exit_code = 1; 76 return 0; 77 } 78 79 static int ex_spawn(void* user, const KitExecOpts* opts, KitExecProc** out) { 80 DriverEnv* env = (DriverEnv*)user; 81 char** av = NULL; 82 char** ev = NULL; 83 char* cwd_s = NULL; 84 char* stdout_s = NULL; 85 char* stderr_s = NULL; 86 KitExecProc* proc = NULL; 87 int pipefd[2] = {-1, -1}; 88 pid_t pid; 89 size_t i; 90 91 if (!env || !opts || !opts->argv || opts->argc == 0u || !out) return 1; 92 *out = NULL; 93 94 av = (char**)driver_alloc(env, (opts->argc + 1u) * sizeof *av); 95 ev = (char**)driver_alloc(env, (opts->nenv + 1u) * sizeof *ev); 96 if (!av || !ev) goto err; 97 memset(av, 0, (opts->argc + 1u) * sizeof *av); 98 memset(ev, 0, (opts->nenv + 1u) * sizeof *ev); 99 for (i = 0; i < opts->argc; ++i) { 100 av[i] = (i == 0u && !opts->search_path) ? ex_cwd_join(env, opts->argv[i]) 101 : ex_slice_dup(env, opts->argv[i]); 102 if (!av[i]) goto err; 103 } 104 for (i = 0; i < opts->nenv; ++i) { 105 size_t nk = opts->env[i].key.len, nv = opts->env[i].value.len; 106 ev[i] = (char*)driver_alloc(env, nk + 1u + nv + 1u); 107 if (!ev[i]) goto err; 108 memcpy(ev[i], opts->env[i].key.s, nk); 109 ev[i][nk] = '='; 110 memcpy(ev[i] + nk + 1u, opts->env[i].value.s, nv); 111 ev[i][nk + 1u + nv] = '\0'; 112 } 113 if (opts->cwd.len) { 114 cwd_s = ex_slice_dup(env, opts->cwd); 115 if (!cwd_s) goto err; 116 } 117 if (opts->stdout_path.len) { 118 stdout_s = ex_slice_dup(env, opts->stdout_path); 119 if (!stdout_s) goto err; 120 } 121 if (opts->stderr_path.len) { 122 stderr_s = ex_slice_dup(env, opts->stderr_path); 123 if (!stderr_s) goto err; 124 } 125 proc = (KitExecProc*)driver_alloc_zeroed(env, sizeof *proc); 126 if (!proc) goto err; 127 proc->read_fd = -1; 128 129 if (opts->capture_stdout) { 130 if (pipe(pipefd) != 0) goto err; 131 } else { 132 /* Flush the parent's buffered stdout so a recipe echo precedes the child's 133 * own output on the shared fd. */ 134 fflush(stdout); 135 } 136 137 pid = fork(); 138 if (pid < 0) goto err; 139 if (pid == 0) { 140 extern char** environ; 141 if (opts->capture_stdout) { 142 close(pipefd[0]); 143 if (dup2(pipefd[1], STDOUT_FILENO) < 0) _exit(127); 144 close(pipefd[1]); 145 } else if (stdout_s) { 146 int fd = open(stdout_s, O_WRONLY | O_CREAT | O_TRUNC, 0666); 147 if (fd < 0 || dup2(fd, STDOUT_FILENO) < 0) _exit(127); 148 close(fd); 149 } 150 if (stderr_s) { 151 int fd = open(stderr_s, O_WRONLY | O_CREAT | O_TRUNC, 0666); 152 if (fd < 0 || dup2(fd, STDERR_FILENO) < 0) _exit(127); 153 close(fd); 154 } 155 if (cwd_s && chdir(cwd_s) != 0) _exit(127); 156 if (opts->search_path) { 157 environ = ev; /* execvp draws PATH and the child env from here */ 158 execvp(av[0], av); 159 } else { 160 execve(av[0], av, ev); 161 } 162 _exit(127); 163 } 164 proc->pid = pid; 165 if (opts->capture_stdout) { 166 close(pipefd[1]); 167 proc->read_fd = pipefd[0]; 168 } 169 *out = proc; 170 ex_free_strv(env, av, opts->argc); 171 ex_free_strv(env, ev, opts->nenv); 172 if (cwd_s) driver_free(env, cwd_s, strlen(cwd_s) + 1u); 173 if (stdout_s) driver_free(env, stdout_s, strlen(stdout_s) + 1u); 174 if (stderr_s) driver_free(env, stderr_s, strlen(stderr_s) + 1u); 175 return 0; 176 177 err: 178 if (pipefd[0] >= 0) close(pipefd[0]); 179 if (pipefd[1] >= 0) close(pipefd[1]); 180 ex_free_strv(env, av, opts ? opts->argc : 0u); 181 ex_free_strv(env, ev, opts ? opts->nenv : 0u); 182 if (cwd_s) driver_free(env, cwd_s, strlen(cwd_s) + 1u); 183 if (stdout_s) driver_free(env, stdout_s, strlen(stdout_s) + 1u); 184 if (stderr_s) driver_free(env, stderr_s, strlen(stderr_s) + 1u); 185 if (proc) driver_free(env, proc, sizeof *proc); 186 return 1; 187 } 188 189 static int ex_wait(void* user, KitExecProc* proc, int* exit_code, uint8_t** out, 190 size_t* out_len) { 191 DriverEnv* env = (DriverEnv*)user; 192 uint8_t* buf = NULL; 193 size_t len = 0; 194 size_t cap = 0; 195 int oom = 0; 196 int rc; 197 198 if (!env || !proc || !exit_code) return 1; 199 if (out) *out = NULL; 200 if (out_len) *out_len = 0; 201 202 if (proc->read_fd >= 0) { 203 for (;;) { 204 uint8_t tmp[4096]; 205 ssize_t n = read(proc->read_fd, tmp, sizeof tmp); 206 if (n < 0) { 207 if (errno == EINTR) continue; 208 break; 209 } 210 if (n == 0) break; 211 /* Keep draining after an allocation failure so the child never blocks. */ 212 if (!oom && len + (size_t)n > cap) { 213 size_t ncap = cap ? cap : 8192; 214 uint8_t* nb; 215 while (ncap < len + (size_t)n) ncap *= 2; 216 nb = env->heap->realloc(env->heap, buf, cap, ncap, 8); 217 if (!nb) { 218 env->heap->free(env->heap, buf, cap); 219 buf = NULL; 220 cap = 0; 221 len = 0; 222 oom = 1; 223 } else { 224 buf = nb; 225 cap = ncap; 226 } 227 } 228 if (!oom) { 229 memcpy(buf + len, tmp, (size_t)n); 230 len += (size_t)n; 231 } 232 } 233 close(proc->read_fd); 234 } 235 236 rc = ex_reap(proc->pid, exit_code); 237 driver_free(env, proc, sizeof *proc); 238 if (out) { 239 *out = buf; 240 if (out_len) *out_len = len; 241 } else if (buf) { 242 env->heap->free(env->heap, buf, cap); 243 } 244 return rc; 245 } 246 247 static int ex_poll(void* user, KitExecProc* proc, int* done, int* exit_code) { 248 DriverEnv* env = (DriverEnv*)user; 249 int st; 250 pid_t r; 251 if (!env || !proc || !done || !exit_code) return 1; 252 *done = 0; 253 do { 254 r = waitpid(proc->pid, &st, WNOHANG); 255 } while (r < 0 && errno == EINTR); 256 if (r < 0) return 1; 257 if (r == 0) return 0; 258 *done = 1; 259 if (WIFEXITED(st)) 260 *exit_code = WEXITSTATUS(st); 261 else if (WIFSIGNALED(st)) 262 *exit_code = -WTERMSIG(st); 263 else 264 *exit_code = 1; 265 if (proc->read_fd >= 0) close(proc->read_fd); 266 driver_free(env, proc, sizeof *proc); 267 return 0; 268 } 269 270 static void ex_kill(void* user, KitExecProc* proc) { 271 (void)user; 272 if (proc) kill(proc->pid, SIGTERM); 273 } 274 275 KitExec driver_exec(DriverEnv* env) { 276 KitExec ex; 277 ex.spawn = ex_spawn; 278 ex.wait = ex_wait; 279 ex.poll = ex_poll; 280 ex.kill = ex_kill; 281 ex.user = env; 282 return ex; 283 }