ic_backup/ops/persistence/download_journal/manifest/
mod.rs1use super::super::json::check_json_size;
4use super::{DownloadIntegrityError, DownloadJournalError, DownloadJournalGuard};
5use crate::{
6 model::{
7 artifacts::ArtifactChecksumRecord,
8 download_journal::{DownloadJournalRecord, MAX_DOWNLOAD_JOURNAL_BYTES},
9 operation_plan::OperationPlanRecord,
10 },
11 ops::persistence::{
12 BackupLayoutGuard, JournalLock, JournalLockError, PersistenceError, create_json_durable,
13 read_json, read_operation_plan,
14 },
15 policy::download_integrity::validate,
16};
17use std::path::Path;
18use thiserror::Error;
19
20const MANIFEST_FILE: &str = "download-manifest.json";
21
22impl DownloadJournalGuard<'_> {
23 pub fn read_download_manifest(
31 &self,
32 plan: &OperationPlanRecord,
33 expected: &ArtifactChecksumRecord,
34 ) -> Result<DownloadJournalRecord, DownloadManifestError> {
35 self.check_usable()?;
36 self.require_unchanged_integrity_journal()?;
37 let path = self.layout.root().join(MANIFEST_FILE);
38 let _lock = JournalLock::acquire(&path)?;
39 let record = read_manifest_record(self.layout, plan, expected)?;
40 if self.record()? != &record {
41 return Err(DownloadManifestError::JournalChanged);
42 }
43 self.require_unchanged_integrity_journal()?;
44 self.layout.check_root()?;
45 Ok(record)
46 }
47
48 pub fn publish_download_manifest(
59 &self,
60 plan: &OperationPlanRecord,
61 ) -> Result<ArtifactChecksumRecord, DownloadManifestError> {
62 self.publish_manifest_with(plan, create_json_durable)
63 }
64
65 fn publish_manifest_with(
66 &self,
67 plan: &OperationPlanRecord,
68 writer: impl FnOnce(&Path, &DownloadJournalRecord) -> Result<(), PersistenceError>,
69 ) -> Result<ArtifactChecksumRecord, DownloadManifestError> {
70 self.check_usable()?;
71 let path = self.layout.root().join(MANIFEST_FILE);
72 let _lock = JournalLock::acquire(&path)?;
73 self.verify_durable_artifacts(plan)?;
74 writer(&path, self.record()?)?;
75 Ok(self.record()?.digest())
76 }
77}
78
79pub fn read_download_manifest(
89 layout: &BackupLayoutGuard,
90 plan: &OperationPlanRecord,
91 expected: &ArtifactChecksumRecord,
92) -> Result<DownloadJournalRecord, DownloadManifestError> {
93 layout.check_root()?;
94 let path = layout.root().join(MANIFEST_FILE);
95 let _lock = JournalLock::acquire(&path)?;
96 let record = read_manifest_record(layout, plan, expected)?;
97 let journal = DownloadJournalGuard::open(layout, plan.digest().hash())?;
98 if journal.record()? != &record {
99 return Err(DownloadManifestError::JournalChanged);
100 }
101 layout.check_root()?;
102 Ok(record)
103}
104
105fn read_manifest_record(
106 layout: &BackupLayoutGuard,
107 plan: &OperationPlanRecord,
108 expected: &ArtifactChecksumRecord,
109) -> Result<DownloadJournalRecord, DownloadManifestError> {
110 let path = layout.root().join(MANIFEST_FILE);
111 let record: DownloadJournalRecord = read_json(&path, MAX_DOWNLOAD_JOURNAL_BYTES)?;
112 check_json_size(&record, MAX_DOWNLOAD_JOURNAL_BYTES)?;
113 if &record.digest() != expected {
114 return Err(DownloadManifestError::DigestMismatch);
115 }
116 read_operation_plan(layout, &plan.digest()).map_err(DownloadIntegrityError::from)?;
117 validate(plan, &record).map_err(DownloadIntegrityError::from)?;
118 Ok(record)
119}
120
121#[derive(Debug, Error)]
123pub enum DownloadManifestError {
124 #[error("download manifest digest mismatch")]
126 DigestMismatch,
127 #[error("download manifest differs from original retained journal")]
129 JournalChanged,
130 #[error(transparent)]
132 Integrity(#[from] DownloadIntegrityError),
133 #[error(transparent)]
135 Journal(#[from] DownloadJournalError),
136 #[error(transparent)]
138 Lock(#[from] JournalLockError),
139 #[error(transparent)]
141 Persistence(#[from] PersistenceError),
142}
143
144#[cfg(all(test, unix))]
145mod tests;