diff --git a/cmd/w6c/main.c b/cmd/w6c/main.c index 451bd3a3..fbdced9c 100644 --- a/cmd/w6c/main.c +++ b/cmd/w6c/main.c @@ -13,6 +13,7 @@ struct publication { char *backup; int had_old; int installed; + int passthrough; }; static char * @@ -30,9 +31,37 @@ publish_path(const char *dst, const char *kind) return p; } +static int +copy_stream(FILE *src, FILE *out) +{ + int bad = fflush(src) != 0 || fseek(src, 0, SEEK_SET) != 0; + unsigned char buf[65536]; + while (!bad) { + size_t n = fread(buf, 1, sizeof buf, src); + if (n != 0 && fwrite(buf, 1, n, out) != n) bad = 1; + if (n < sizeof buf) { + if (ferror(src)) bad = 1; + break; + } + } + return bad ? -1 : 0; +} + static int materialize_stream(FILE *src, struct publication *p) { + struct stat st; + if (lstat(p->dst, &st) == 0 && S_ISCHR(st.st_mode)) { + FILE *out = fopen(p->dst, "wb"); + int bad = out == NULL || copy_stream(src, out) < 0; + if (out != NULL && fclose(out) != 0) bad = 1; + if (bad) { + fprintf(stderr, "w6c: cannot write %s\n", p->dst); + return -1; + } + p->passthrough = 1; + return 0; + } p->stage = publish_path(p->dst, "new"); p->backup = publish_path(p->dst, "old"); if (p->stage == NULL || p->backup == NULL) { @@ -50,16 +79,7 @@ materialize_stream(FILE *src, struct publication *p) unlink(p->stage); return -1; } - int bad = fflush(src) != 0 || fseek(src, 0, SEEK_SET) != 0; - unsigned char buf[65536]; - while (!bad) { - size_t n = fread(buf, 1, sizeof buf, src); - if (n != 0 && fwrite(buf, 1, n, out) != n) bad = 1; - if (n < sizeof buf) { - if (ferror(src)) bad = 1; - break; - } - } + int bad = copy_stream(src, out) < 0; if (fclose(out) != 0) bad = 1; if (bad) { unlink(p->stage); @@ -100,6 +120,7 @@ static int publish_all(struct publication *p, int n) { for (int i = 0; i < n; i++) { + if (p[i].passthrough) continue; if (!path_is_regular_nofollow(p[i].stage)) { fprintf(stderr, "w6c: publication stage is not a regular file for %s\n", p[i].dst); @@ -120,6 +141,7 @@ publish_all(struct publication *p, int n) } } for (int i = 0; i < n; i++) { + if (p[i].passthrough) continue; int exists = path_exists_nofollow(p[i].backup); if (exists != 0) { if (exists < 0) @@ -132,6 +154,7 @@ publish_all(struct publication *p, int n) } } for (int i = 0; i < n; i++) { + if (p[i].passthrough) continue; if (rename(p[i].dst, p[i].backup) == 0) p[i].had_old = 1; else if (errno != ENOENT) { @@ -140,6 +163,7 @@ publish_all(struct publication *p, int n) } } for (int i = 0; i < n; i++) { + if (p[i].passthrough) continue; if (rename(p[i].stage, p[i].dst) != 0) { fprintf(stderr, "w6c: cannot publish %s\n", p[i].dst); goto rollback; @@ -149,13 +173,15 @@ publish_all(struct publication *p, int n) /* Installation is the commit point. Backup cleanup cannot truthfully * turn a completely installed pair into a rejected compilation. */ for (int i = 0; i < n; i++) - if (p[i].had_old && unlink(p[i].backup) != 0) { + if (!p[i].passthrough && p[i].had_old + && unlink(p[i].backup) != 0) { fprintf(stderr, "w6c: cannot remove backup for %s\n", p[i].dst); } return 0; rollback: for (int i = n - 1; i >= 0; i--) { + if (p[i].passthrough) continue; if (p[i].installed) (void)unlink(p[i].dst); if (p[i].had_old) (void)rename(p[i].backup, p[i].dst); if (p[i].stage != NULL) (void)unlink(p[i].stage); diff --git a/docs/build-system.md b/docs/build-system.md index 9cf2652f..9ea6857d 100644 --- a/docs/build-system.md +++ b/docs/build-system.md @@ -5147,10 +5147,12 @@ and is never followed or removed. A committed dispatcher voucher must itself be a regular file before it can authorize reuse. Compiler assembly and interface bytes are first generated through anonymous files with checked full writes and then published as their own rollback group. A non-regular compiler -destination is rejected before preservation, and installation—not cleanup of -a recoverable old backup—is the commit point. Cold rejection removes the exact -request-owned scratch tree. Stale init code, stale closure metadata, and mixed -committed generations therefore cannot be reused. +destination is rejected before preservation, except that an already existing +character-device sink such as `/dev/null` receives a checked passthrough and is +never renamed or treated as a persistent artifact. Installation—not cleanup +of a recoverable old backup—is the commit point. Cold rejection removes the +exact request-owned scratch tree. Stale init code, stale closure metadata, and +mixed committed generations therefore cannot be reused. #### Stage and observer ownership diff --git a/selfhost/cmd/w6c/main.ww b/selfhost/cmd/w6c/main.ww index ca2f3757..49d6fc64 100644 --- a/selfhost/cmd/w6c/main.ww +++ b/selfhost/cmd/w6c/main.ww @@ -14,6 +14,7 @@ type publication = struct { backup: str, hadold: bool, installed: bool, + passthrough: bool, }; fn publicationpath(dst: *u8, kind: str) str = { @@ -21,6 +22,17 @@ fn publicationpath(dst: *u8, kind: str) str = { strconv.i32tos(os.getpid(), strconv.base.DEC), ".", kind); }; +fn pathischardevice(path: *u8) bool = { + let fi: os.filestat; + match (os.lstat(&fi, pathstr(path))) { + case void => { + return (((fi.mode: u32) & 61440u32) == (os.mode.CHR: u32)); + }; + case let e: os.oserror => return false; + }; + return false; +}; + // Open a destination-adjacent file and immediately unlink its name. Cgen's // fatal exit then leaves only a kernel-owned fd, never a partial destination // or named staging artifact. @@ -28,6 +40,12 @@ fn anonymousfd(dst: *u8, kind: str) i32 = { let path: str = publicationpath(dst, kind); let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.EXCL, 384i32); + if (fd < 0 && pathischardevice(dst)) { + path = strings.concat("/tmp/ww-w6c.", + strconv.i32tos(os.getpid(), strconv.base.DEC), ".", kind); + fd = os.open(path, + os.flag.RDWR | os.flag.CREATE | os.flag.EXCL, 384i32); + }; if (fd < 0) { os.write(2, "w6c: cannot create anonymous output\n".ptr, "w6c: cannot create anonymous output\n".len: u64); @@ -60,6 +78,21 @@ fn copystream(src: i32, dst: i32) bool = { fn materializestream(src: i32, dst: *u8, p: *publication) bool = { p.dst = dst; + p.passthrough = false; + let fi: os.filestat; + match (os.lstat(&fi, pathstr(dst))) { + case void => { + if (((fi.mode: u32) & 61440u32) == (os.mode.CHR: u32)) { + let sink: i32 = os.open(pathstr(dst), os.flag.WRONLY, 0i32); + if (sink < 0) { return false; }; + let sinkok: bool = copystream(src, sink); + if (os.close(sink) != 0) { sinkok = false; }; + if (sinkok) { p.passthrough = true; }; + return sinkok; + }; + }; + case let e: os.oserror => void; + }; p.stage = publicationpath(dst, "new"); p.backup = publicationpath(dst, "old"); p.hadold = false; @@ -110,6 +143,7 @@ fn publishall(p: *publication, n: i32) bool = { let i: i32 = 0; let ok: bool = true; for (i < n) { + if (p[i].passthrough) { i += 1; continue; }; if (!pathisregularnofollow(p[i].stage)) { publishdiag("w6c: publication stage is not a regular file for ", p[i].dst); @@ -140,6 +174,7 @@ fn publishall(p: *publication, n: i32) bool = { if (ok) { i = 0; for (i < n) { + if (p[i].passthrough) { i += 1; continue; }; let exists: i32 = pathexistsnofollow(p[i].backup); if (exists != 0) { if (exists < 0) { @@ -157,6 +192,7 @@ fn publishall(p: *publication, n: i32) bool = { if (ok) { i = 0; for (i < n) { + if (p[i].passthrough) { i += 1; continue; }; let rc: i32 = os.rename(pathstr(p[i].dst), p[i].backup); if (rc == 0) { p[i].hadold = true; } else { if (rc != -2) { @@ -170,6 +206,7 @@ fn publishall(p: *publication, n: i32) bool = { if (ok) { i = 0; for (i < n) { + if (p[i].passthrough) { i += 1; continue; }; if (os.rename(p[i].stage, pathstr(p[i].dst)) != 0) { publishdiag("w6c: cannot publish ", p[i].dst); ok = false; @@ -183,7 +220,8 @@ fn publishall(p: *publication, n: i32) bool = { // truthfully turn a completely installed pair into rejection. i = 0; for (i < n) { - if (p[i].hadold && os.remove(p[i].backup) != 0) { + if (!p[i].passthrough && p[i].hadold + && os.remove(p[i].backup) != 0) { publishdiag("w6c: cannot remove backup for ", p[i].dst); }; i += 1; @@ -193,6 +231,7 @@ fn publishall(p: *publication, n: i32) bool = { }; i = n - 1; for (i >= 0) { + if (p[i].passthrough) { i -= 1; continue; }; if (p[i].installed) { os.remove(pathstr(p[i].dst)); }; if (p[i].hadold) { os.rename(p[i].backup, pathstr(p[i].dst)); }; if (p[i].stage.len != 0) { os.remove(p[i].stage); }; @@ -882,6 +921,7 @@ export fn main(argc: i32, argv: **u8) i32 = { pubs[pi].backup = ""; pubs[pi].hadold = false; pubs[pi].installed = false; + pubs[pi].passthrough = false; pi += 1; }; let npub: i32 = 0;