pkgcache: reject 0-byte artifacts on store and lookup, self-heal torn writes (#10)

A torn producer write (e.g. disk-full mid-copy) could leave a 0-byte P.wwi or
P.o in out/.pkgcache under a self-consistent key; cache_lookup checked only
existence, so every later build HIT and served the empty artifact forever
(silent serve-wrong). Reject size==0 on both sides, symmetric across stages:
store refuses to commit a 0-byte temp before the key write, lookup treats a
0-byte cached artifact as a MISS so existing poison self-heals on re-derive.
A valid .wwi/.o is never 0 bytes, so the guard cannot misfire.

Regression: test/wcc/989_pkgcache_poison_run.c, table-driven over
{poison P.wwi | P.o | both}, non-vacuity proven by guard-neuter.
This commit is contained in:
2026-06-22 20:52:11 +09:00
parent c0383274d1
commit a1484aef28
4 changed files with 291 additions and 0 deletions

View File

@@ -780,6 +780,17 @@ pkgcache_dir(struct sepgraph *g, int pi, char *out, size_t outsz)
snprintf(out, outsz, "%s/%s", pkgcache_root(), base);
}
/* A torn producer write (e.g. disk-full mid-copy) can leave a 0-byte P.wwi/P.o
* under a self-consistent key; size==0 is unambiguous poison (wwi_emit always
* writes >=1 line, a valid .o is never empty), so both store and lookup reject
* it — self-healing, Go-build-cache style (#10). */
static int
filenonempty(const char *p)
{
struct stat st;
return stat(p, &st) == 0 && st.st_size > 0;
}
/* HIT iff a freshly recomputed manifest equals the stored P.key byte-for-byte
* AND both cached artifacts exist; on HIT copy them into the scratch wwi/obj
* paths so the producer loop can skip compose+w6c+w6a. */
@@ -802,6 +813,7 @@ cache_lookup(struct sepgraph *g, int pi, const char *manifest,
if (more || sn != strlen(manifest) || memcmp(stored, manifest, sn) != 0)
return 0;
if (access(cwwi, 0) != 0 || access(cobj, 0) != 0) return 0;
if (!filenonempty(cwwi) || !filenonempty(cobj)) return 0;
snprintf(cmd, sizeof cmd, "cp -f '%s' '%s'", cwwi, wwi);
if (run(cmd) != 0) return 0;
snprintf(cmd, sizeof cmd, "cp -f '%s' '%s'", cobj, obj);
@@ -847,6 +859,7 @@ cache_store(struct sepgraph *g, int pi, const char *manifest,
if (run(cmd) != 0) goto cleanup;
snprintf(cmd, sizeof cmd, "cp -f '%s' '%s'", obj, tobj);
if (run(cmd) != 0) goto cleanup;
if (!filenonempty(twwi) || !filenonempty(tobj)) goto cleanup;
f = fopen(tkey, "wb");
if (f == NULL) goto cleanup;
fputs(manifest, f);