canic_backup/persistence/
layout.rs1use crate::{
8 execution::BackupExecutionJournal,
9 journal::DownloadJournal,
10 manifest::DeploymentBackupManifest,
11 persistence::{
12 BackupExecutionGuard, BackupExecutionIntegrityReport, BackupIntegrityReport,
13 BackupLayoutGuard, JournalLock, JournalLockError, PersistenceError,
14 integrity::{verify_execution_integrity, verify_layout_integrity},
15 json::{create_json_durable, read_json, write_json_durable},
16 },
17 plan::BackupPlan,
18};
19
20use std::path::{Path, PathBuf};
21
22const MANIFEST_FILE_NAME: &str = "deployment-backup-manifest.json";
23const BACKUP_PLAN_FILE_NAME: &str = "backup-plan.json";
24const JOURNAL_FILE_NAME: &str = "download-journal.json";
25const EXECUTION_JOURNAL_FILE_NAME: &str = "backup-execution-journal.json";
26
27#[derive(Clone, Debug)]
35pub struct BackupLayout {
36 root: PathBuf,
37}
38
39impl BackupLayout {
40 #[must_use]
42 pub const fn new(root: PathBuf) -> Self {
43 Self { root }
44 }
45
46 pub(crate) fn resolve_root(&self) -> Result<Self, PersistenceError> {
48 let root = self.root.canonicalize()?;
49 if !root.is_dir() {
50 return Err(std::io::Error::from(std::io::ErrorKind::NotADirectory).into());
51 }
52 Ok(Self::new(root))
53 }
54
55 #[must_use]
57 pub fn root(&self) -> &Path {
58 &self.root
59 }
60
61 #[must_use]
63 pub fn manifest_path(&self) -> PathBuf {
64 self.root.join(MANIFEST_FILE_NAME)
65 }
66
67 #[must_use]
69 pub fn backup_plan_path(&self) -> PathBuf {
70 self.root.join(BACKUP_PLAN_FILE_NAME)
71 }
72
73 #[must_use]
75 pub fn journal_path(&self) -> PathBuf {
76 self.root.join(JOURNAL_FILE_NAME)
77 }
78
79 #[must_use]
81 pub fn execution_journal_path(&self) -> PathBuf {
82 self.root.join(EXECUTION_JOURNAL_FILE_NAME)
83 }
84
85 pub fn lock_execution(&self) -> Result<BackupExecutionGuard, JournalLockError> {
88 let lifetime = self.lock_lifetime()?;
89 let journal = JournalLock::acquire(&lifetime.root().join(EXECUTION_JOURNAL_FILE_NAME))?;
90 Ok(BackupExecutionGuard {
91 _lifetime: lifetime,
92 _journal: journal,
93 })
94 }
95
96 pub fn lock_lifetime(&self) -> Result<BackupLayoutGuard, JournalLockError> {
98 BackupLayoutGuard::acquire(&self.root)
99 }
100
101 pub fn publish_manifest(
103 &self,
104 manifest: &DeploymentBackupManifest,
105 ) -> Result<(), PersistenceError> {
106 manifest.validate()?;
107 let path = self.manifest_path();
108 if path.exists() {
109 return self.require_exact_manifest(manifest);
110 }
111 match create_json_durable(&path, manifest) {
112 Ok(()) => Ok(()),
113 Err(PersistenceError::Io(error))
114 if error.kind() == std::io::ErrorKind::AlreadyExists =>
115 {
116 self.require_exact_manifest(manifest)
117 }
118 Err(error) => Err(error),
119 }
120 }
121
122 pub fn read_manifest(&self) -> Result<DeploymentBackupManifest, PersistenceError> {
124 let path = self.manifest_path();
125 let metadata = std::fs::symlink_metadata(&path)?;
126 if metadata.file_type().is_symlink() || !metadata.is_file() {
127 return Err(PersistenceError::ManifestConflict {
128 path: path.display().to_string(),
129 });
130 }
131 let manifest = read_json(&path)?;
132 DeploymentBackupManifest::validate(&manifest)?;
133 Ok(manifest)
134 }
135
136 fn require_exact_manifest(
137 &self,
138 expected: &DeploymentBackupManifest,
139 ) -> Result<(), PersistenceError> {
140 let path = self.manifest_path();
141 let actual = self.read_manifest()?;
142 if serde_json::to_vec(&actual)? == serde_json::to_vec(expected)? {
143 std::fs::File::open(&path)?.sync_all()?;
144 if let Some(parent) = path.parent() {
145 std::fs::File::open(parent)?.sync_all()?;
146 }
147 Ok(())
148 } else {
149 Err(PersistenceError::ManifestConflict {
150 path: path.display().to_string(),
151 })
152 }
153 }
154
155 #[cfg(test)]
156 pub(crate) fn publish_manifest_at_barriers(
157 &self,
158 manifest: &DeploymentBackupManifest,
159 before_publication: impl FnMut(),
160 after_directory_sync: impl FnMut(),
161 ) -> Result<(), PersistenceError> {
162 use crate::persistence::json::create_json_durable_at_barriers;
163
164 manifest.validate()?;
165 create_json_durable_at_barriers(
166 &self.manifest_path(),
167 manifest,
168 before_publication,
169 after_directory_sync,
170 )
171 }
172
173 pub fn write_backup_plan(&self, plan: &BackupPlan) -> Result<(), PersistenceError> {
175 plan.validate()?;
176 write_json_durable(&self.backup_plan_path(), plan)
177 }
178
179 pub fn read_backup_plan(&self) -> Result<BackupPlan, PersistenceError> {
181 let plan = read_json(&self.backup_plan_path())?;
182 BackupPlan::validate(&plan)?;
183 Ok(plan)
184 }
185
186 pub fn write_journal(&self, journal: &DownloadJournal) -> Result<(), PersistenceError> {
188 journal.validate()?;
189 write_json_durable(&self.journal_path(), journal)
190 }
191
192 pub fn read_journal(&self) -> Result<DownloadJournal, PersistenceError> {
194 let journal = read_json(&self.journal_path())?;
195 DownloadJournal::validate(&journal)?;
196 Ok(journal)
197 }
198
199 pub fn write_execution_journal(
201 &self,
202 journal: &BackupExecutionJournal,
203 ) -> Result<(), PersistenceError> {
204 journal.validate()?;
205 write_json_durable(&self.execution_journal_path(), journal)
206 }
207
208 #[cfg(all(test, unix))]
209 pub(crate) fn write_execution_journal_at_barriers(
210 &self,
211 journal: &BackupExecutionJournal,
212 barriers: impl FnMut(crate::persistence::DurableWriteBarrier),
213 ) -> Result<(), PersistenceError> {
214 use crate::persistence::write_json_durable_at_barriers;
215
216 journal.validate()?;
217 write_json_durable_at_barriers(&self.execution_journal_path(), journal, barriers)
218 }
219
220 pub fn read_execution_journal(&self) -> Result<BackupExecutionJournal, PersistenceError> {
222 let journal = read_json(&self.execution_journal_path())?;
223 BackupExecutionJournal::validate(&journal)?;
224 Ok(journal)
225 }
226
227 pub fn verify_integrity(&self) -> Result<BackupIntegrityReport, PersistenceError> {
229 let layout = self.resolve_root()?;
230 let manifest = layout.read_manifest()?;
231 let journal = layout.read_journal()?;
232 verify_layout_integrity(&layout, &manifest, &journal)
233 }
234
235 pub fn verify_execution_integrity(
237 &self,
238 ) -> Result<BackupExecutionIntegrityReport, PersistenceError> {
239 let plan = self.read_backup_plan()?;
240 let journal = self.read_execution_journal()?;
241 verify_execution_integrity(&plan, &journal)
242 }
243}