diff --git a/crates/ecrou/src/download.rs b/crates/ecrou/src/download.rs index 52ba5fd..abecf07 100644 --- a/crates/ecrou/src/download.rs +++ b/crates/ecrou/src/download.rs @@ -257,7 +257,6 @@ fn download_file( progress.advance(n as u64); } - progress.finish(); hasher.verify(expected)?; if expected.is_some() { log::debug!("Verified content digest: {}", dest.display()); @@ -270,6 +269,11 @@ fn download_file( Ok(()) })(); + // The operation ended — success or not. A sink is told finish() + // exactly once per begin(), so a failed transfer leaves no bar + // hanging and no implicit open operation behind it. + progress.finish(); + if result.is_err() { // Best-effort cleanup; ignore errors (file may not exist if creation failed). let _ = std::fs::remove_file(&temp_dest); diff --git a/crates/ecrou/src/registry.rs b/crates/ecrou/src/registry.rs index 6079edd..0c27d24 100644 --- a/crates/ecrou/src/registry.rs +++ b/crates/ecrou/src/registry.rs @@ -487,18 +487,22 @@ fn download_blob_with_auth( } file.flush().context("Failed to flush the layer file")?; - progress.finish(); // The blob was addressed by this digest in the manifest; a mismatch means // truncated or tampered content. The blob lives in a temp directory that // is discarded on error, so a bad layer never reaches the cache. - hasher.verify(Some(expected_digest)).with_context(|| { + let verified = hasher.verify(Some(expected_digest)).with_context(|| { format!( "layer blob {} failed verification against manifest digest", url ) - })?; + }); + // The operation ended — success or not. A sink is told finish() + // exactly once per begin(), so a failed layer leaves no bar + // hanging and no implicit open operation behind it. + progress.finish(); + verified?; Ok(()) }