use super::{DownloadIntegrityError, DownloadJournalError, DownloadJournalGuard, check_size};
use crate::{
model::{
artifacts::ArtifactChecksumRecord,
download_journal::{DownloadJournalRecord, MAX_DOWNLOAD_JOURNAL_BYTES},
operation_plan::OperationPlanRecord,
},
ops::persistence::{
BackupLayoutGuard, JournalLock, JournalLockError, PersistenceError, create_json_durable,
read_json, read_operation_plan,
},
policy::download_integrity::validate,
};
use std::path::Path;
use thiserror::Error;
const MANIFEST_FILE: &str = "download-manifest.json";
impl DownloadJournalGuard<'_> {
pub fn read_download_manifest(
&self,
plan: &OperationPlanRecord,
expected: &ArtifactChecksumRecord,
) -> Result<DownloadJournalRecord, DownloadManifestError> {
self.check_usable()?;
self.require_unchanged_integrity_journal()?;
let path = self.layout.root().join(MANIFEST_FILE);
let _lock = JournalLock::acquire(&path)?;
let record = read_manifest_record(self.layout, plan, expected)?;
if self.record()? != &record {
return Err(DownloadManifestError::JournalChanged);
}
self.require_unchanged_integrity_journal()?;
self.layout.check_root()?;
Ok(record)
}
pub fn publish_download_manifest(
&self,
plan: &OperationPlanRecord,
) -> Result<ArtifactChecksumRecord, DownloadManifestError> {
self.publish_manifest_with(plan, create_json_durable)
}
fn publish_manifest_with(
&self,
plan: &OperationPlanRecord,
writer: impl FnOnce(&Path, &DownloadJournalRecord) -> Result<(), PersistenceError>,
) -> Result<ArtifactChecksumRecord, DownloadManifestError> {
self.check_usable()?;
let path = self.layout.root().join(MANIFEST_FILE);
let _lock = JournalLock::acquire(&path)?;
self.verify_durable_artifacts(plan)?;
writer(&path, self.record()?)?;
Ok(self.record()?.digest())
}
}
pub fn read_download_manifest(
layout: &BackupLayoutGuard,
plan: &OperationPlanRecord,
expected: &ArtifactChecksumRecord,
) -> Result<DownloadJournalRecord, DownloadManifestError> {
layout.check_root()?;
let path = layout.root().join(MANIFEST_FILE);
let _lock = JournalLock::acquire(&path)?;
let record = read_manifest_record(layout, plan, expected)?;
let journal = DownloadJournalGuard::open(layout, plan.digest().hash())?;
if journal.record()? != &record {
return Err(DownloadManifestError::JournalChanged);
}
layout.check_root()?;
Ok(record)
}
fn read_manifest_record(
layout: &BackupLayoutGuard,
plan: &OperationPlanRecord,
expected: &ArtifactChecksumRecord,
) -> Result<DownloadJournalRecord, DownloadManifestError> {
let path = layout.root().join(MANIFEST_FILE);
let record: DownloadJournalRecord = read_json(&path, MAX_DOWNLOAD_JOURNAL_BYTES)?;
check_size(&record)?;
if &record.digest() != expected {
return Err(DownloadManifestError::DigestMismatch);
}
read_operation_plan(layout, &plan.digest()).map_err(DownloadIntegrityError::from)?;
validate(plan, &record).map_err(DownloadIntegrityError::from)?;
Ok(record)
}
#[derive(Debug, Error)]
pub enum DownloadManifestError {
#[error("download manifest digest mismatch")]
DigestMismatch,
#[error("download manifest differs from original retained journal")]
JournalChanged,
#[error(transparent)]
Integrity(#[from] DownloadIntegrityError),
#[error(transparent)]
Journal(#[from] DownloadJournalError),
#[error(transparent)]
Lock(#[from] JournalLockError),
#[error(transparent)]
Persistence(#[from] PersistenceError),
}
#[cfg(all(test, unix))]
mod tests;