ic_backup/ops/persistence/download_journal/manifest/
mod.rs1use super::super::json::{create_encoded, encode_json, read_record};
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, 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_encoded)
62 }
63
64 fn publish_manifest_with(
65 &self,
66 plan: &OperationPlanRecord,
67 writer: impl FnOnce(&Path, &[u8]) -> 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 let bytes = encode_json(self.record()?, MAX_DOWNLOAD_JOURNAL_BYTES)?;
74 writer(&path, &bytes)?;
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_record(&path, MAX_DOWNLOAD_JOURNAL_BYTES)?;
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;