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