ic_backup/ops/persistence/download_journal/manifest/
mod.rs1use super::super::json::{check_json_size, create_encoded, encode_json};
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_json,
13 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_encoded)
63 }
64
65 fn publish_manifest_with(
66 &self,
67 plan: &OperationPlanRecord,
68 writer: impl FnOnce(&Path, &[u8]) -> 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 let bytes = encode_json(self.record()?, MAX_DOWNLOAD_JOURNAL_BYTES)?;
75 writer(&path, &bytes)?;
76 Ok(self.record()?.digest())
77 }
78}
79
80pub fn read_download_manifest(
90 layout: &BackupLayoutGuard,
91 plan: &OperationPlanRecord,
92 expected: &ArtifactChecksumRecord,
93) -> Result<DownloadJournalRecord, DownloadManifestError> {
94 layout.check_root()?;
95 let path = layout.root().join(MANIFEST_FILE);
96 let _lock = JournalLock::acquire(&path)?;
97 let record = read_manifest_record(layout, plan, expected)?;
98 let journal = DownloadJournalGuard::open(layout, plan.digest().hash())?;
99 if journal.record()? != &record {
100 return Err(DownloadManifestError::JournalChanged);
101 }
102 layout.check_root()?;
103 Ok(record)
104}
105
106fn read_manifest_record(
107 layout: &BackupLayoutGuard,
108 plan: &OperationPlanRecord,
109 expected: &ArtifactChecksumRecord,
110) -> Result<DownloadJournalRecord, DownloadManifestError> {
111 let path = layout.root().join(MANIFEST_FILE);
112 let record: DownloadJournalRecord = read_json(&path, MAX_DOWNLOAD_JOURNAL_BYTES)?;
113 check_json_size(&record, MAX_DOWNLOAD_JOURNAL_BYTES)?;
114 if &record.digest() != expected {
115 return Err(DownloadManifestError::DigestMismatch);
116 }
117 read_operation_plan(layout, &plan.digest()).map_err(DownloadIntegrityError::from)?;
118 validate(plan, &record).map_err(DownloadIntegrityError::from)?;
119 Ok(record)
120}
121
122#[derive(Debug, Error)]
124pub enum DownloadManifestError {
125 #[error("download manifest digest mismatch")]
127 DigestMismatch,
128 #[error("download manifest differs from original retained journal")]
130 JournalChanged,
131 #[error(transparent)]
133 Integrity(#[from] DownloadIntegrityError),
134 #[error(transparent)]
136 Journal(#[from] DownloadJournalError),
137 #[error(transparent)]
139 Lock(#[from] JournalLockError),
140 #[error(transparent)]
142 Persistence(#[from] PersistenceError),
143}
144
145#[cfg(all(test, unix))]
146mod tests;