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:
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;
}