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 }