use super::{
Error, ExpectedBinaryReferences, ExpectedGraph, FileAccess, Path, RegularFileIdentity, Result,
TarArchiveReader, held_file_identity, open_regular_file_no_follow, preserve_file_metadata,
require_held_file_identity, require_path_file_identity, validate_exact_archive_trailers,
};
pub(crate) struct StagedValidation<'validation> {
pub(crate) reader: &'validation TarArchiveReader,
pub(crate) staging_path: &'validation Path,
pub(crate) replacement_name: &'validation str,
pub(crate) survivors: &'validation [crate::tar_archive::index::SegmentIndexEntry],
pub(crate) expected_graph: &'validation ExpectedGraph,
pub(crate) expected_binary_references: &'validation ExpectedBinaryReferences,
pub(crate) source_metadata: &'validation std::fs::Metadata,
}
pub(crate) fn validate_staged_replacement(
validation: &StagedValidation<'_>,
) -> Result<RegularFileIdentity> {
let &StagedValidation {
reader,
staging_path,
replacement_name,
survivors,
expected_graph,
expected_binary_references,
source_metadata,
} = validation;
#[cfg(test)]
crate::writer::fault_injection::fail_if_armed("sweep.staging-before-validation-open")?;
let staging_file = open_regular_file_no_follow(staging_path, FileAccess::ReadWrite)?;
preserve_file_metadata(&staging_file, source_metadata)?;
let staging_identity = held_file_identity(&staging_file)?;
let staged_reader = TarArchiveReader::open_file(staging_path, &staging_file)?;
if let Err(error) = validate_open_swept_archive(
reader,
&staged_reader,
staging_path,
survivors,
expected_graph,
expected_binary_references,
) {
return Err(Error::InvalidFormat {
details: format!(
"staged rewrite for {} failed complete survivor/trailer validation ({error}); the original {} was left untouched",
replacement_name,
reader.file_name()
),
});
}
#[cfg(test)]
crate::writer::fault_injection::substitute_path_if_armed(
"sweep.staging-validated-before-publish",
staging_path,
)?;
require_held_file_identity(
&staging_file,
staging_identity,
"validated archive staging file",
)?;
require_path_file_identity(
staging_path,
staging_identity,
"validated archive staging file",
)?;
Ok(staging_identity)
}
#[derive(Clone, Copy)]
pub(crate) enum SurvivorPayloadProof {
CompareWithSource,
SameInodeAsValidatedStaging,
}
#[cfg(test)]
pub(in crate::writer::store_writer) fn validate_swept_archive(
source: &TarArchiveReader,
swept_path: &Path,
survivors: &[crate::tar_archive::index::SegmentIndexEntry],
expected_graph: &ExpectedGraph,
expected_binary_references: &ExpectedBinaryReferences,
) -> Result<()> {
let swept = TarArchiveReader::open(swept_path)?;
validate_open_swept_archive(
source,
&swept,
swept_path,
survivors,
expected_graph,
expected_binary_references,
)
}
pub(in crate::writer::store_writer) fn validate_open_swept_archive(
source: &TarArchiveReader,
swept: &TarArchiveReader,
swept_path: &Path,
survivors: &[crate::tar_archive::index::SegmentIndexEntry],
expected_graph: &ExpectedGraph,
expected_binary_references: &ExpectedBinaryReferences,
) -> Result<()> {
prove_survivor_count_order_and_entries(
source,
swept,
swept_path,
survivors,
SurvivorPayloadProof::CompareWithSource,
)?;
validate_exact_archive_trailers(
swept,
&swept_path.display().to_string(),
expected_graph,
expected_binary_references,
)
}
pub(in crate::writer::store_writer) fn validate_published_swept_archive(
source: &TarArchiveReader,
swept: &TarArchiveReader,
swept_path: &Path,
survivors: &[crate::tar_archive::index::SegmentIndexEntry],
expected_graph: &ExpectedGraph,
expected_binary_references: &ExpectedBinaryReferences,
) -> Result<()> {
prove_survivor_count_order_and_entries(
source,
swept,
swept_path,
survivors,
SurvivorPayloadProof::SameInodeAsValidatedStaging,
)?;
validate_exact_archive_trailers(
swept,
&swept_path.display().to_string(),
expected_graph,
expected_binary_references,
)
}
pub(crate) fn prove_survivor_count_order_and_entries(
source: &TarArchiveReader,
swept: &TarArchiveReader,
swept_path: &Path,
survivors: &[crate::tar_archive::index::SegmentIndexEntry],
payload_proof: SurvivorPayloadProof,
) -> Result<()> {
if swept.is_recovered() {
return Err(Error::InvalidFormat {
details: format!("{} has no valid index", swept_path.display()),
});
}
if swept.segment_count() != survivors.len() {
return Err(Error::InvalidFormat {
details: format!(
"{} contains {} segments, expected {}",
swept_path.display(),
swept.segment_count(),
survivors.len()
),
});
}
let mut actual_in_file_order = swept
.index()
.expect("a non-recovered archive has an index")
.entries()
.to_vec();
actual_in_file_order.sort_by_key(|entry| entry.position);
let actual_identifier_order: Vec<_> = actual_in_file_order
.iter()
.map(|entry| entry.segment_identifier)
.collect();
let expected_identifier_order: Vec<_> = survivors
.iter()
.map(|entry| entry.segment_identifier)
.collect();
if actual_identifier_order != expected_identifier_order {
return Err(Error::InvalidFormat {
details: format!(
"{} changed the physical order of surviving segments",
swept_path.display()
),
});
}
for expected in survivors {
if matches!(payload_proof, SurvivorPayloadProof::CompareWithSource) {
swept.validate_indexed_segment_entry(expected.segment_identifier)?;
}
let actual = swept
.index_entry(expected.segment_identifier)
.ok_or_else(|| Error::InvalidFormat {
details: format!(
"{} omits surviving segment {}",
swept_path.display(),
expected.segment_identifier
),
})?;
if actual.size != expected.size
|| actual.generation != expected.generation
|| actual.full_generation != expected.full_generation
|| actual.is_compacted != expected.is_compacted
{
return Err(Error::InvalidFormat {
details: format!(
"{} changed generation metadata for surviving segment {}",
swept_path.display(),
expected.segment_identifier
),
});
}
if matches!(payload_proof, SurvivorPayloadProof::CompareWithSource) {
let source_bytes =
source
.segment_data(expected.segment_identifier)
.ok_or(Error::SegmentNotFound {
segment_identifier: expected.segment_identifier,
})?;
let swept_bytes =
swept
.segment_data(expected.segment_identifier)
.ok_or(Error::SegmentNotFound {
segment_identifier: expected.segment_identifier,
})?;
if source_bytes != swept_bytes {
return Err(Error::InvalidFormat {
details: format!(
"{} changed the payload of surviving segment {}",
swept_path.display(),
expected.segment_identifier
),
});
}
}
}
Ok(())
}