Skip to content

Cache repair deletes the old cache before a rename it never checks - #817

Merged
xroche merged 13 commits into
masterfrom
fix/cache-repair-rename
Jul 27, 2026
Merged

Cache repair deletes the old cache before a rename it never checks#817
xroche merged 13 commits into
masterfrom
fix/cache-repair-rename

Conversation

@xroche

@xroche xroche commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Both cache-repair paths removed the damaged cache, moved repair.zip onto its name without looking at the result, and printed "successfully recovered" either way. When the move failed, the recovered entries sat under a name nothing reads and the user had no cache at all. Both now go through one cache_repair() that reports why it failed, and -#R exits nonzero rather than claiming a recovery it did not perform.

This also folds #824, which lives in the same block. unzRepair() returns Z_OK with zero entries for an input that never had a local file header, and its output can be short on a full disk, so both sites committed an empty 22-byte archive over a cache whose bytes were still carvable by hand. A recovery that holds no entry, or that does not open, no longer replaces anything.

Based on #816, and that is a correctness requirement rather than a merge convenience. On Windows the destination always exists, so every repair takes hts_rename_over()'s fallback, which without #816 still removes the cache before a retry that can fail. On its own this PR closes the window on POSIX, where the single rename() is atomic; on Windows it only reports the failure and keeps repair.zip for a manual retry until #816 lands.

The test drives both call sites through a rename shim, including the Windows-shaped refusal that goes through the fallback.

Closes #786
Closes #824

xroche and others added 2 commits July 27, 2026 14:06
Both zip-repair paths removed the damaged cache, moved repair.zip onto
its name without looking at the result, and announced a successful
recovery either way. A refused move left the entries under a name
nothing reads and no cache at all. Go through hts_rename_over(), which
keeps the destination when it cannot replace it, and report the failure
instead of claiming success.

Closes #786

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
The unlink-then-rename fallback leaves nothing in place of dst between
the unlink and the retry, so a retry that fails too loses it. Park dst
under a free scratch name instead, drop it once the move succeeded, and
put it back otherwise.

Closes #790

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
xroche and others added 3 commits July 27, 2026 14:14
The Windows job iterates a fixed glob, so a 106_engine-* test never ran
there; name it in the list. The move it exercises is the one whose
failure mode is Windows-specific.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
xroche and others added 2 commits July 27, 2026 14:32
…rantee

The move back out of the parked name was unchecked, so a retry that
failed for a reason that still applied left dst absent with the content
orphaned under a name nothing reported. Check it, retry once, and name
the parked copy in the log; hts_rename_over() takes an httrackp for that.

The aside probe used fexist(), which is not UTF-8 and consults the ANSI
codepage on Windows while the renames beside it are wide. It also reads
a directory as a free name, so the park now skips a name whose rename
refuses rather than giving up on it.

The header claimed a failure leaves dst as it was, which the crash
window between the two renames does not give.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
…hing

On Windows the rename destination always exists, so every cache repair
takes hts_rename_over()'s fallback; without #790 that fallback still
removes the cache before a retry that can fail. Stacking the two keeps
the repair path away from the destructive shape.

Carries the #824 fix in the same block: unzRepair() reports Z_OK with no
entries for an input that never had a local file header, and its output
can be short on a full disk, so both call sites committed an empty or
truncated archive over the cache. Both now go through one cache_repair()
that refuses a recovery holding nothing or failing to open.

Closes #824

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
@xroche
xroche changed the base branch from master to fix/rename-over-aside July 27, 2026 12:43
xroche and others added 3 commits July 27, 2026 15:59
#822 converted 01_engine-renameover.test's assertions to the here-string
form, since grep -q SIGPIPEs the echo feeding it and an assertion that
held reports 141. This branch had added its restore-outcome and retry
legs in the old pipe form. Resolved on master's form, with those legs
ported into it rather than kept alongside.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
Windows refuses every rename onto an existing target, so the fallback is
production code there rather than the rare path it is on POSIX. A
directory at the destination was renamed aside like a file, the move
then succeeded, and UNLINK could not drop the parked directory, so the
call reported success where master had reported failure and left an
orphan behind. 101_local-update-stale-bak plants exactly that shape and
caught it on both Windows legs.

Park a regular file only. A directory in the way is refused, as before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
Carries the here-string reconciliation with #822 and the aside
directory-refusal fix. TESTS tail keeps both new entries in order.

Signed-off-by: Xavier Roche <roche@httrack.com>
@xroche
xroche changed the base branch from fix/rename-over-aside to master July 27, 2026 14:51
xroche and others added 3 commits July 27, 2026 16:52
Resolve the tests/Makefile.am TESTS-tail conflict: keep 106, 110 and
111 in numeric order.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
Resolve tests/renamefail.c: keep the repair/repair-eexist narrowing
(#786) that master's simpler locked-only shim lacks; the aside-fails
and rename-over regimes carry through unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
Register 106_engine-repair-rename.test in tests-list.mk after the
#845 TESTS-list restructure moved it out of tests/Makefile.am.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
@xroche
xroche merged commit 2e67587 into master Jul 27, 2026
22 checks passed
@xroche
xroche deleted the fix/cache-repair-rename branch July 27, 2026 18:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant