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