kit

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

exec_windows.c (12809B)


      1 /*
      2  * Windows implementation of the shared KitExec subprocess interface
      3  * (<kit/exec.h>), via CreateProcessW so Unicode argv/env/paths are preserved.
      4  * One config-struct spawn serves both the build coordinator (hermetic: exact
      5  * argv[0], explicit env, mandatory cwd, capture to files) and `kit make`
      6  * (PATH-searched shell, optional cwd, capture to a buffer). The user pointer is
      7  * a DriverEnv* (for its heap).
      8  *
      9  * NOTE: this path compiles for Windows but is exercised only in the Windows VM
     10  * test lane; primary development/testing happens on POSIX.
     11  */
     12 #ifndef WIN32_LEAN_AND_MEAN
     13 #define WIN32_LEAN_AND_MEAN
     14 #endif
     15 #ifndef _WIN32_WINNT
     16 #define _WIN32_WINNT 0x0601
     17 #endif
     18 // clang-format off
     19 #include <windows.h>
     20 // clang-format on
     21 
     22 #include <stdint.h>
     23 #include <stdio.h>
     24 #include <stdlib.h>
     25 #include <string.h>
     26 
     27 #include <kit/exec.h>
     28 
     29 #include "env.h"
     30 
     31 struct KitExecProc {
     32   HANDLE process;
     33   HANDLE read_pipe; /* stdout capture pipe read end, or INVALID_HANDLE_VALUE */
     34 };
     35 
     36 static wchar_t* ex_widen(const char* utf8) {
     37   int need;
     38   wchar_t* w;
     39   if (!utf8) return NULL;
     40   need = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
     41   if (need <= 0) return NULL;
     42   w = (wchar_t*)malloc((size_t)need * sizeof(*w));
     43   if (!w) return NULL;
     44   if (MultiByteToWideChar(CP_UTF8, 0, utf8, -1, w, need) <= 0) {
     45     free(w);
     46     return NULL;
     47   }
     48   return w;
     49 }
     50 
     51 static char* ex_slice_dup(DriverEnv* env, KitSlice s) {
     52   char* out = (char*)driver_alloc(env, s.len + 1u);
     53   if (!out) return NULL;
     54   if (s.len) memcpy(out, s.s, s.len);
     55   out[s.len] = '\0';
     56   return out;
     57 }
     58 
     59 static char* ex_cwd_join(DriverEnv* env, KitSlice path) {
     60   char cwd[4096];
     61   char* out;
     62   size_t nc, np;
     63   if (!path.s || path.len == 0u) return NULL;
     64   if (path.s[0] == '/' || path.s[0] == '\\' ||
     65       (path.len >= 2u && path.s[1] == ':'))
     66     return ex_slice_dup(env, path);
     67   if (!GetCurrentDirectoryA(sizeof cwd, cwd)) return NULL;
     68   nc = strlen(cwd);
     69   np = path.len;
     70   out = (char*)driver_alloc(env, nc + 1u + np + 1u);
     71   if (!out) return NULL;
     72   memcpy(out, cwd, nc);
     73   out[nc] = '\\';
     74   memcpy(out + nc + 1u, path.s, np);
     75   out[nc + 1u + np] = '\0';
     76   return out;
     77 }
     78 
     79 static void ex_free_strv(DriverEnv* env, char** v, size_t n) {
     80   size_t i;
     81   if (!v) return;
     82   for (i = 0; i < n; ++i)
     83     if (v[i]) driver_free(env, v[i], strlen(v[i]) + 1u);
     84   driver_free(env, v, (n + 1u) * sizeof *v);
     85 }
     86 
     87 static wchar_t** ex_widen_strv(char** v, size_t n) {
     88   wchar_t** out = (wchar_t**)calloc(n + 1u, sizeof(*out));
     89   size_t i;
     90   if (!out) return NULL;
     91   for (i = 0; i < n; ++i) {
     92     out[i] = ex_widen(v[i]);
     93     if (!out[i]) {
     94       while (i > 0u) free(out[--i]);
     95       free(out);
     96       return NULL;
     97     }
     98   }
     99   return out;
    100 }
    101 
    102 static void ex_free_wstrv(wchar_t** v, size_t n) {
    103   size_t i;
    104   if (!v) return;
    105   for (i = 0; i < n; ++i) free(v[i]);
    106   free(v);
    107 }
    108 
    109 static int ex_arg_needs_quote(const wchar_t* s) {
    110   if (!s || !s[0]) return 1;
    111   for (; *s; ++s)
    112     if (*s == L' ' || *s == L'\t' || *s == L'"') return 1;
    113   return 0;
    114 }
    115 
    116 static size_t ex_arg_quoted_len(const wchar_t* s) {
    117   size_t len, slash = 0;
    118   if (!ex_arg_needs_quote(s)) return (size_t)lstrlenW(s);
    119   len = 2u;
    120   for (; *s; ++s) {
    121     if (*s == L'\\') {
    122       ++slash;
    123       ++len;
    124     } else if (*s == L'"') {
    125       len += slash + 2u;
    126       slash = 0;
    127     } else {
    128       slash = 0;
    129       ++len;
    130     }
    131   }
    132   return len + slash;
    133 }
    134 
    135 static wchar_t* ex_arg_quote_into(wchar_t* out, const wchar_t* s) {
    136   size_t slash = 0;
    137   if (!ex_arg_needs_quote(s)) {
    138     while (*s) *out++ = *s++;
    139     return out;
    140   }
    141   *out++ = L'"';
    142   for (; *s; ++s) {
    143     if (*s == L'\\') {
    144       *out++ = *s;
    145       ++slash;
    146     } else if (*s == L'"') {
    147       while (slash--) *out++ = L'\\';
    148       *out++ = L'\\';
    149       *out++ = L'"';
    150       slash = 0;
    151     } else {
    152       *out++ = *s;
    153       slash = 0;
    154     }
    155   }
    156   while (slash--) *out++ = L'\\';
    157   *out++ = L'"';
    158   return out;
    159 }
    160 
    161 static wchar_t* ex_command_line(wchar_t** argv, size_t argc) {
    162   size_t i, len = 1u;
    163   wchar_t *out, *p;
    164   for (i = 0; i < argc; ++i)
    165     len += ex_arg_quoted_len(argv[i]) + (i ? 1u : 0u);
    166   out = (wchar_t*)malloc(len * sizeof(*out));
    167   if (!out) return NULL;
    168   p = out;
    169   for (i = 0; i < argc; ++i) {
    170     if (i) *p++ = L' ';
    171     p = ex_arg_quote_into(p, argv[i]);
    172   }
    173   *p = L'\0';
    174   return out;
    175 }
    176 
    177 static int ex_wenv_cmp(const void* a, const void* b) {
    178   return lstrcmpiW(*(const wchar_t* const*)a, *(const wchar_t* const*)b);
    179 }
    180 
    181 static wchar_t* ex_env_block(wchar_t** envv, size_t nenv) {
    182   size_t i, total = 2u, off = 0;
    183   wchar_t* block;
    184   qsort(envv, nenv, sizeof(*envv), ex_wenv_cmp);
    185   for (i = 0; i < nenv; ++i) total += (size_t)lstrlenW(envv[i]) + 1u;
    186   block = (wchar_t*)malloc(total * sizeof(*block));
    187   if (!block) return NULL;
    188   for (i = 0; i < nenv; ++i) {
    189     size_t n = (size_t)lstrlenW(envv[i]) + 1u;
    190     memcpy(block + off, envv[i], n * sizeof(*block));
    191     off += n;
    192   }
    193   block[off++] = L'\0';
    194   block[off] = L'\0';
    195   return block;
    196 }
    197 
    198 static int ex_proc_exit_code(HANDLE h, int* exit_code) {
    199   DWORD code;
    200   if (!GetExitCodeProcess(h, &code)) return 1;
    201   *exit_code = code > 255u ? 128 : (int)code;
    202   return 0;
    203 }
    204 
    205 static int ex_spawn(void* user, const KitExecOpts* opts, KitExecProc** out) {
    206   DriverEnv* env = (DriverEnv*)user;
    207   char** av = NULL;
    208   char** ev = NULL;
    209   wchar_t** wav = NULL;
    210   wchar_t** wev = NULL;
    211   wchar_t* cmdline = NULL;
    212   wchar_t* envblock = NULL;
    213   char* cwd_s = NULL;
    214   wchar_t* wcwd = NULL;
    215   KitExecProc* proc = NULL;
    216   HANDLE pipe_r = INVALID_HANDLE_VALUE;
    217   HANDLE pipe_w = INVALID_HANDLE_VALUE;
    218   HANDLE fout = INVALID_HANDLE_VALUE;
    219   HANDLE ferr = INVALID_HANDLE_VALUE;
    220   HANDLE fnull = INVALID_HANDLE_VALUE;
    221   STARTUPINFOW si;
    222   PROCESS_INFORMATION pi;
    223   BOOL inherit = FALSE;
    224   size_t i;
    225 
    226   if (!env || !opts || !opts->argv || opts->argc == 0u || !out) return 1;
    227   *out = NULL;
    228   av = (char**)driver_alloc(env, (opts->argc + 1u) * sizeof *av);
    229   ev = (char**)driver_alloc(env, (opts->nenv + 1u) * sizeof *ev);
    230   if (!av || !ev) goto err;
    231   memset(av, 0, (opts->argc + 1u) * sizeof *av);
    232   memset(ev, 0, (opts->nenv + 1u) * sizeof *ev);
    233   for (i = 0; i < opts->argc; ++i) {
    234     av[i] = (i == 0u && !opts->search_path) ? ex_cwd_join(env, opts->argv[i])
    235                                             : ex_slice_dup(env, opts->argv[i]);
    236     if (!av[i]) goto err;
    237   }
    238   for (i = 0; i < opts->nenv; ++i) {
    239     size_t nk = opts->env[i].key.len, nv = opts->env[i].value.len;
    240     ev[i] = (char*)driver_alloc(env, nk + 1u + nv + 1u);
    241     if (!ev[i]) goto err;
    242     memcpy(ev[i], opts->env[i].key.s, nk);
    243     ev[i][nk] = '=';
    244     memcpy(ev[i] + nk + 1u, opts->env[i].value.s, nv);
    245     ev[i][nk + 1u + nv] = '\0';
    246   }
    247   wav = ex_widen_strv(av, opts->argc);
    248   wev = ex_widen_strv(ev, opts->nenv);
    249   if (!wav || !wev) goto err;
    250   cmdline = ex_command_line(wav, opts->argc);
    251   envblock = ex_env_block(wev, opts->nenv);
    252   if (opts->cwd.len) {
    253     cwd_s = ex_slice_dup(env, opts->cwd);
    254     if (cwd_s) wcwd = ex_widen(cwd_s);
    255   }
    256   proc = (KitExecProc*)driver_alloc_zeroed(env, sizeof *proc);
    257   if (!cmdline || !envblock || !proc || (opts->cwd.len && !wcwd)) goto err;
    258   proc->read_pipe = INVALID_HANDLE_VALUE;
    259 
    260   memset(&si, 0, sizeof si);
    261   memset(&pi, 0, sizeof pi);
    262   si.cb = sizeof si;
    263 
    264   if (opts->capture_stdout) {
    265     SECURITY_ATTRIBUTES sa;
    266     memset(&sa, 0, sizeof sa);
    267     sa.nLength = sizeof sa;
    268     sa.bInheritHandle = TRUE;
    269     if (!CreatePipe(&pipe_r, &pipe_w, &sa, 0)) goto err;
    270     SetHandleInformation(pipe_r, HANDLE_FLAG_INHERIT, 0);
    271     si.dwFlags = STARTF_USESTDHANDLES;
    272     si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
    273     si.hStdOutput = pipe_w;
    274     si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
    275     inherit = TRUE;
    276   } else if (opts->stdout_path.len) {
    277     SECURITY_ATTRIBUTES sa;
    278     wchar_t* wo = NULL;
    279     wchar_t* we = NULL;
    280     wchar_t* wn = NULL;
    281     char* o = ex_slice_dup(env, opts->stdout_path);
    282     char* e = opts->stderr_path.len ? ex_slice_dup(env, opts->stderr_path) : NULL;
    283     if (o) wo = ex_widen(o);
    284     if (e) we = ex_widen(e);
    285     wn = ex_widen("NUL");
    286     if (o) driver_free(env, o, strlen(o) + 1u);
    287     if (e) driver_free(env, e, strlen(e) + 1u);
    288     memset(&sa, 0, sizeof sa);
    289     sa.nLength = sizeof sa;
    290     sa.bInheritHandle = TRUE;
    291     if (wo)
    292       fout = CreateFileW(wo, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS,
    293                          FILE_ATTRIBUTE_NORMAL, NULL);
    294     if (we)
    295       ferr = CreateFileW(we, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS,
    296                          FILE_ATTRIBUTE_NORMAL, NULL);
    297     fnull = CreateFileW(wn, GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
    298                         FILE_ATTRIBUTE_NORMAL, NULL);
    299     free(wo);
    300     free(we);
    301     free(wn);
    302     if (fout == INVALID_HANDLE_VALUE || fnull == INVALID_HANDLE_VALUE) goto err;
    303     si.dwFlags = STARTF_USESTDHANDLES;
    304     si.hStdInput = fnull;
    305     si.hStdOutput = fout;
    306     si.hStdError = ferr != INVALID_HANDLE_VALUE ? ferr : fout;
    307     inherit = TRUE;
    308   } else {
    309     fflush(stdout);
    310   }
    311 
    312   if (!CreateProcessW(opts->search_path ? NULL : wav[0], cmdline, NULL, NULL,
    313                       inherit, CREATE_UNICODE_ENVIRONMENT, envblock, wcwd, &si,
    314                       &pi))
    315     goto err;
    316   CloseHandle(pi.hThread);
    317   if (pipe_w != INVALID_HANDLE_VALUE) CloseHandle(pipe_w);
    318   if (fout != INVALID_HANDLE_VALUE) CloseHandle(fout);
    319   if (ferr != INVALID_HANDLE_VALUE) CloseHandle(ferr);
    320   if (fnull != INVALID_HANDLE_VALUE) CloseHandle(fnull);
    321   proc->process = pi.hProcess;
    322   proc->read_pipe = pipe_r;
    323   *out = proc;
    324   ex_free_wstrv(wav, opts->argc);
    325   ex_free_wstrv(wev, opts->nenv);
    326   ex_free_strv(env, av, opts->argc);
    327   ex_free_strv(env, ev, opts->nenv);
    328   if (cwd_s) driver_free(env, cwd_s, strlen(cwd_s) + 1u);
    329   free(wcwd);
    330   free(cmdline);
    331   free(envblock);
    332   return 0;
    333 
    334 err:
    335   if (pipe_r != INVALID_HANDLE_VALUE) CloseHandle(pipe_r);
    336   if (pipe_w != INVALID_HANDLE_VALUE) CloseHandle(pipe_w);
    337   if (fout != INVALID_HANDLE_VALUE) CloseHandle(fout);
    338   if (ferr != INVALID_HANDLE_VALUE) CloseHandle(ferr);
    339   if (fnull != INVALID_HANDLE_VALUE) CloseHandle(fnull);
    340   if (proc) driver_free(env, proc, sizeof *proc);
    341   if (cwd_s) driver_free(env, cwd_s, strlen(cwd_s) + 1u);
    342   free(wcwd);
    343   free(cmdline);
    344   free(envblock);
    345   ex_free_wstrv(wav, opts ? opts->argc : 0u);
    346   ex_free_wstrv(wev, opts ? opts->nenv : 0u);
    347   ex_free_strv(env, av, opts ? opts->argc : 0u);
    348   ex_free_strv(env, ev, opts ? opts->nenv : 0u);
    349   return 1;
    350 }
    351 
    352 static int ex_wait(void* user, KitExecProc* proc, int* exit_code, uint8_t** out,
    353                    size_t* out_len) {
    354   DriverEnv* env = (DriverEnv*)user;
    355   uint8_t* buf = NULL;
    356   size_t len = 0;
    357   size_t cap = 0;
    358   int oom = 0;
    359 
    360   if (!env || !proc || !exit_code) return 1;
    361   if (out) *out = NULL;
    362   if (out_len) *out_len = 0;
    363 
    364   if (proc->read_pipe != INVALID_HANDLE_VALUE) {
    365     for (;;) {
    366       uint8_t tmp[4096];
    367       DWORD n = 0;
    368       if (!ReadFile(proc->read_pipe, tmp, (DWORD)sizeof tmp, &n, NULL) || n == 0)
    369         break;
    370       if (!oom && len + n > cap) {
    371         size_t ncap = cap ? cap : 8192;
    372         uint8_t* nb;
    373         while (ncap < len + n) ncap *= 2;
    374         nb = env->heap->realloc(env->heap, buf, cap, ncap, 8);
    375         if (!nb) {
    376           env->heap->free(env->heap, buf, cap);
    377           buf = NULL;
    378           cap = 0;
    379           len = 0;
    380           oom = 1;
    381         } else {
    382           buf = nb;
    383           cap = ncap;
    384         }
    385       }
    386       if (!oom) {
    387         memcpy(buf + len, tmp, n);
    388         len += n;
    389       }
    390     }
    391     CloseHandle(proc->read_pipe);
    392   }
    393 
    394   if (WaitForSingleObject(proc->process, INFINITE) != WAIT_OBJECT_0 ||
    395       ex_proc_exit_code(proc->process, exit_code) != 0) {
    396     CloseHandle(proc->process);
    397     driver_free(env, proc, sizeof *proc);
    398     if (buf) env->heap->free(env->heap, buf, cap);
    399     return 1;
    400   }
    401   CloseHandle(proc->process);
    402   driver_free(env, proc, sizeof *proc);
    403   if (out) {
    404     *out = buf;
    405     if (out_len) *out_len = len;
    406   } else if (buf) {
    407     env->heap->free(env->heap, buf, cap);
    408   }
    409   return 0;
    410 }
    411 
    412 static int ex_poll(void* user, KitExecProc* proc, int* done, int* exit_code) {
    413   DriverEnv* env = (DriverEnv*)user;
    414   DWORD r;
    415   if (!env || !proc || !done || !exit_code) return 1;
    416   *done = 0;
    417   r = WaitForSingleObject(proc->process, 0);
    418   if (r == WAIT_TIMEOUT) return 0;
    419   if (r != WAIT_OBJECT_0 || ex_proc_exit_code(proc->process, exit_code) != 0)
    420     return 1;
    421   CloseHandle(proc->process);
    422   if (proc->read_pipe != INVALID_HANDLE_VALUE) CloseHandle(proc->read_pipe);
    423   driver_free(env, proc, sizeof *proc);
    424   *done = 1;
    425   return 0;
    426 }
    427 
    428 static void ex_kill(void* user, KitExecProc* proc) {
    429   (void)user;
    430   if (proc) TerminateProcess(proc->process, 128u);
    431 }
    432 
    433 KitExec driver_exec(DriverEnv* env) {
    434   KitExec ex;
    435   ex.spawn = ex_spawn;
    436   ex.wait = ex_wait;
    437   ex.poll = ex_poll;
    438   ex.kill = ex_kill;
    439   ex.user = env;
    440   return ex;
    441 }