kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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 }