fix(bootstrap): resume downloads without discarding a valid archive

Resuming a partial download leaned entirely on the remote size from a
HEAD request. When that size could not be determined the code played it
safe by deleting the partial file and starting over, so a flaky HEAD (or
an offline mirror) threw away gigabytes that were fine to resume. It also
could not tell a real size from an error page, and if the server answered
a ranged request with the whole file (200, not 206) libcurl could append
it to the partial and silently corrupt the archive.

- The HEAD now returns transport status, HTTP code, and size together,
  and a size counts as authoritative only on a 2xx reply with a positive
  Content-Length.
- A pure helper decides what to do from the existing bytes and that
  authoritative flag: start fresh, truncate a stale oversized file, reuse
  a complete one, or resume with a ranged GET. When the size is not
  authoritative the partial is kept and a ranged GET is attempted rather
  than discarded.
- A ranged request that comes back as 200 instead of 206 now restarts
  cleanly from scratch instead of appending to the partial.
- The free-space pre-check runs only when the size is authoritative, and
  a failed download reports how many bytes were kept for the retry.

Tests cover the resume-decision helper across the fresh, stale, complete,
resuming, and non-authoritative cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-13 20:57:21 -05:00
co-authored by Claude Opus 4.8
parent 4d9d528566
commit 667c89ba51
3 changed files with 83 additions and 40 deletions
+14
View File
@@ -3961,6 +3961,19 @@ void testBootstrapParseChecksum()
EXPECT_EQ(Bootstrap::parseChecksumFile("", 64), std::string());
}
void testBootstrapDecideResume()
{
using R = dragonx::util::Bootstrap::ResumeAction;
using dragonx::util::Bootstrap;
EXPECT_TRUE(Bootstrap::decideResume(0, false, -1) == R::Fresh);
EXPECT_TRUE(Bootstrap::decideResume(0, true, 100) == R::Fresh);
EXPECT_TRUE(Bootstrap::decideResume(5, true, 3) == R::TruncateAndFresh);
EXPECT_TRUE(Bootstrap::decideResume(5, true, 5) == R::ReuseComplete);
EXPECT_TRUE(Bootstrap::decideResume(3, true, 5) == R::RangedGet);
EXPECT_TRUE(Bootstrap::decideResume(3, false, -1) == R::RangedGet); // offline: keep + try range
EXPECT_TRUE(Bootstrap::decideResume(3, false, 999) == R::RangedGet); // bogus error-page length ignored
}
void testBootstrapSpaceSufficient()
{
using dragonx::util::Bootstrap;
@@ -18448,6 +18461,7 @@ int main()
testXmrigProvisionPolicy();
testBootstrapSpaceSufficient();
testBootstrapParseChecksum();
testBootstrapDecideResume();
testBootstrapPlanArchive();
#ifndef _WIN32
testBootstrapReclaimable();