kit

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

commit 65c4e689262219f9f490a734e135fab821f098f0
parent cd546d874cb9961292d244f2f39c167e01e9b305
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 15 Jun 2026 15:07:36 -0700

driver: keep failed cc outputs atomic

Diffstat:
Mdriver/cmd/cc.c | 17++++++++++++++---
Mdriver/env.h | 6++++++
Mdriver/env/posix.c | 23+++++++++++++++++------
Mdriver/env/windows.c | 132++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
4 files changed, 164 insertions(+), 14 deletions(-)

diff --git a/driver/cmd/cc.c b/driver/cmd/cc.c @@ -1387,6 +1387,17 @@ static KitStatus cc_compiler_new(const CcOptions* o, const KitContext* ctx, return st; } +static void cc_close_output(KitWriter** writer, const char* output_path, + int* rc) { + if (writer && *writer && rc && kit_writer_status(*writer) != KIT_OK) *rc = 1; + if (writer && *writer && rc && *rc != 0 && output_path) + driver_writer_abort(*writer); + if (writer && *writer) { + kit_writer_close(*writer); + *writer = NULL; + } +} + static int cc_preprocess(DriverEnv* env, const CcOptions* o, const KitPreprocessOptions* pp_opts) { KitContext ctx = driver_env_to_context(env); @@ -1429,7 +1440,7 @@ static int cc_preprocess(DriverEnv* env, const CcOptions* o, out: if (compiler) driver_compiler_free(compiler); kit_target_free(target); - if (writer) kit_writer_close(writer); + cc_close_output(&writer, o->output_path, &rc); if (loaded) ctx.file_io->release(ctx.file_io->user, &fd); return rc; } @@ -1957,7 +1968,7 @@ static int cc_run_compile_one(DriverEnv* env, const CcOptions* o, out: if (compiler) driver_compiler_free(compiler); kit_target_free(target); - if (obj_w) kit_writer_close(obj_w); + cc_close_output(&obj_w, out_path, &rc); if (loaded) ctx.file_io->release(ctx.file_io->user, &fd); return rc; } @@ -2303,7 +2314,7 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o, } out: - if (out_w) kit_writer_close(out_w); + cc_close_output(&out_w, o->output_path, &rc); if (rc == 0 && o->output_path) { if (driver_mark_executable_output(o->output_path) != 0) { driver_errf(CC_TOOL, "failed to set executable mode: %.*s", diff --git a/driver/env.h b/driver/env.h @@ -215,6 +215,12 @@ int driver_create_symlink(const char* target, const char* link_path); * require `target` and `link_path` to live on the same filesystem/volume. */ int driver_create_hardlink(const char* target, const char* link_path); +/* Mark a DriverEnv-created writer as failed before close. Atomic file writers + * will remove their temp file and skip the final rename. Borrowed stdio writers + * just record a failed status; already-written stdout/stderr bytes cannot be + * recalled. */ +void driver_writer_abort(KitWriter* writer); + /* Remove the file or symlink at `path`. Returns 0 when the entry was removed or * was already absent, nonzero on any other failure. POSIX unlink(2) / Windows * DeleteFileW. */ diff --git a/driver/env/posix.c b/driver/env/posix.c @@ -354,6 +354,15 @@ KitWriter* driver_stderr_writer(DriverEnv* e) { return driver_stdio_writer(e, stderr); } +void driver_writer_abort(KitWriter* writer) { + if (!writer) return; + if (writer->close == fdw_close) { + ((DriverFdWriter*)writer)->status = KIT_ERR; + } else if (writer->close == stdio_w_close) { + ((DriverStdioWriter*)writer)->status = KIT_ERR; + } +} + const char* const* driver_environ(void) { return (const char* const*)environ; } /* ---------------- file_io (POSIX open/read/write/stat) ---------------- */ @@ -409,9 +418,8 @@ static void posix_release(void* user, KitFileData* d) { /* Open `path` for output, atomically: write to a sibling temp file and rename * it over `path` on a clean close (see DriverFdWriter.tmp_path). The temp lives * in `path`'s own directory so the rename stays within one filesystem (a - * cross-device rename would fail). Falls back to a plain in-place open when a - * temp can't be made — e.g. the target is a device/FIFO like /dev/null, or the - * directory rejects the temp create — preserving the old behavior there. */ + * cross-device rename would fail). Special files such as /dev/null are written + * in place; regular file outputs must get a temp file or fail to open. */ static KitStatus posix_open_writer(void* user, const char* path, KitWriter** out) { DriverEnv* env = (DriverEnv*)user; @@ -446,10 +454,12 @@ static KitStatus posix_open_writer(void* user, const char* path, (void)fchmod(fd, 0644); via_temp = 1; } else { - fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + return KIT_IO; } - } else { + } else if (special) { fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + } else { + return KIT_IO; } if (fd < 0) return KIT_IO; @@ -466,7 +476,8 @@ static KitStatus posix_open_writer(void* user, const char* path, if (!fw->tmp_path || !fw->final_path) { /* Without both paths we can't rename; degrade to leaving the temp in * place would be wrong, so fail cleanly. */ - if (fw->tmp_path) env->heap->free(env->heap, fw->tmp_path, strlen(tmpl) + 1u); + if (fw->tmp_path) + env->heap->free(env->heap, fw->tmp_path, strlen(tmpl) + 1u); if (fw->final_path) env->heap->free(env->heap, fw->final_path, strlen(path) + 1u); fw->tmp_path = NULL; diff --git a/driver/env/windows.c b/driver/env/windows.c @@ -49,7 +49,9 @@ * its OWN include group (blank line below) so a formatter's per-group alphabetic * sort cannot reorder it after psapi.h — which would leave EnumProcesses et al. * as bare `WINAPI` decls that the mingw cross-build's frontend then rejects. */ +// clang-format off #include <windows.h> +// clang-format on #include <io.h> #include <process.h> @@ -318,6 +320,8 @@ typedef struct DriverHandleWriter { HANDLE h; KitStatus status; uint64_t pos; + wchar_t* tmp_path; + wchar_t* final_path; } DriverHandleWriter; static KitStatus hw_write(KitWriter* w, const void* data, size_t n) { @@ -357,7 +361,21 @@ static KitStatus hw_status(KitWriter* w) { } static void hw_close(KitWriter* w) { DriverHandleWriter* fw = (DriverHandleWriter*)w; - if (fw->h && fw->h != INVALID_HANDLE_VALUE) CloseHandle(fw->h); + KitStatus st = fw->status; + if (fw->h && fw->h != INVALID_HANDLE_VALUE) { + if (!CloseHandle(fw->h) && st == KIT_OK) st = KIT_IO; + } + if (fw->tmp_path) { + if (st == KIT_OK && + !MoveFileExW(fw->tmp_path, fw->final_path, + MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { + st = KIT_IO; + fw->status = KIT_IO; + } + if (st != KIT_OK) DeleteFileW(fw->tmp_path); + free(fw->tmp_path); + free(fw->final_path); + } fw->heap->free(fw->heap, fw, sizeof(*fw)); } @@ -374,6 +392,8 @@ static KitWriter* driver_writer_handle(KitHeap* h, HANDLE fh) { fw->h = fh; fw->status = KIT_OK; fw->pos = 0; + fw->tmp_path = NULL; + fw->final_path = NULL; return &fw->base; } @@ -437,6 +457,15 @@ KitWriter* driver_stderr_writer(DriverEnv* e) { return driver_stdio_writer(e, stderr); } +void driver_writer_abort(KitWriter* writer) { + if (!writer) return; + if (writer->close == hw_close) { + ((DriverHandleWriter*)writer)->status = KIT_ERR; + } else if (writer->close == stdio_w_close) { + ((DriverStdioWriter*)writer)->status = KIT_ERR; + } +} + const char* const* driver_environ(void) { return (const char* const*)_environ; } /* ============================================================ @@ -495,22 +524,115 @@ static void win_release(void* user, KitFileData* d) { d->token = NULL; } +static wchar_t* win_wcsdup(const wchar_t* s) { + size_t n; + wchar_t* out; + if (!s) return NULL; + n = (size_t)lstrlenW(s) + 1u; + out = (wchar_t*)malloc(n * sizeof(*out)); + if (out) memcpy(out, s, n * sizeof(*out)); + return out; +} + +static int win_ascii_eq_ci(const char* a, const char* b) { + while (*a && *b) { + char ca = *a++; + char cb = *b++; + if (ca >= 'A' && ca <= 'Z') ca = (char)(ca - 'A' + 'a'); + if (cb >= 'A' && cb <= 'Z') cb = (char)(cb - 'A' + 'a'); + if (ca != cb) return 0; + } + return *a == '\0' && *b == '\0'; +} + +static int win_special_output_path(const char* path) { + const char* p; + if (!path || !*path) return 0; + for (p = path; *p; ++p) { + if (*p == '/' || *p == '\\') return 0; + } + return win_ascii_eq_ci(path, "nul") || win_ascii_eq_ci(path, "nul:"); +} + +static wchar_t* win_output_dir_wide(const char* path) { + const char* last = NULL; + const char* p; + char* dir; + wchar_t* wdir; + size_t n; + for (p = path; *p; ++p) { + if (*p == '/' || *p == '\\') last = p; + } + if (!last) return widen("."); + n = (size_t)(last - path); + if (n == 0) + n = 1u; + else if (n == 2u && path[1] == ':') + n = 3u; + dir = (char*)malloc(n + 1u); + if (!dir) return NULL; + memcpy(dir, path, n); + dir[n] = '\0'; + wdir = widen(dir); + free(dir); + return wdir; +} + static KitStatus win_open_writer(void* user, const char* path, KitWriter** out) { DriverEnv* env = (DriverEnv*)user; wchar_t* wpath = widen(path); + wchar_t* wdir = NULL; + wchar_t* wtmp_dup = NULL; + wchar_t tmp_path[MAX_PATH]; HANDLE h; KitWriter* w; + int special = win_special_output_path(path); if (!wpath) return KIT_IO; - h = CreateFileW(wpath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, NULL); - free(wpath); - if (h == INVALID_HANDLE_VALUE) return KIT_IO; + if (special) { + h = CreateFileW(wpath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, NULL); + } else { + wdir = win_output_dir_wide(path); + if (!wdir || !GetTempFileNameW(wdir, L"kit", 0, tmp_path)) { + free(wdir); + free(wpath); + return KIT_IO; + } + free(wdir); + h = CreateFileW(tmp_path, GENERIC_WRITE, FILE_SHARE_READ, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (h == INVALID_HANDLE_VALUE) { + DeleteFileW(tmp_path); + free(wpath); + return KIT_IO; + } + } + if (h == INVALID_HANDLE_VALUE) { + free(wpath); + return KIT_IO; + } w = driver_writer_handle(env->heap, h); if (!w) { CloseHandle(h); + if (!special) DeleteFileW(tmp_path); + free(wpath); return KIT_NOMEM; } + if (!special) { + DriverHandleWriter* hw = (DriverHandleWriter*)w; + wtmp_dup = win_wcsdup(tmp_path); + if (!wtmp_dup) { + kit_writer_close(w); + DeleteFileW(tmp_path); + free(wpath); + return KIT_NOMEM; + } + hw->tmp_path = wtmp_dup; + hw->final_path = wpath; + } else { + free(wpath); + } *out = w; return KIT_OK; }