1use super::storage_backup_list::list_backup_descriptors;
14use super::storage_file_fs::{
15 copy_atomic_file, create_real_directory_all, open_regular_file, path_exists_no_follow,
16 path_is_real_directory, resolve_under_root, tmp_path_for, write_atomic_file,
17};
18use super::storage_tree::{visit_bounded_tree, StorageTreeEntryKind};
19use super::*;
20use crate::storage::RemoteAuthStorageClient;
21use appcore_security::{TokenClaims, TokenProvider};
22use std::fs;
23use std::io::Read;
24use std::path::{Path, PathBuf};
25
26pub const DEFAULT_FILE_READ_MAX_BYTES: u64 = 64 * 1024 * 1024;
28
29pub const MAX_STORAGE_BACKUP_FILE_BYTES: u64 = 1024 * 1024 * 1024;
31
32#[derive(Debug, Clone)]
34pub struct FileStorageProvider {
35 pub(super) storage_path: PathBuf,
36 pub(super) backup_path: PathBuf,
37 opened: bool,
38}
39
40impl FileStorageProvider {
41 pub fn new(storage_path: impl Into<PathBuf>, backup_path: impl Into<PathBuf>) -> Self {
43 Self {
44 storage_path: storage_path.into(),
45 backup_path: backup_path.into(),
46 opened: false,
47 }
48 }
49
50 pub fn create_dirs(&self) -> StorageResult<()> {
52 if let Some(parent) = self.storage_path.parent() {
53 fs::create_dir_all(parent).map_err(|_| StorageError::NotAvailable)?;
54 }
55 create_real_directory_all(&self.backup_path)?;
56 if path_exists_no_follow(&self.storage_path)? {
57 super::storage_file_fs::ensure_real_directory(&self.storage_path)?;
58 }
59 self.recover_snapshot_restore()?;
60 create_real_directory_all(&self.storage_path)?;
61 Ok(())
62 }
63
64 pub fn write_bytes(&self, path: &str, bytes: &[u8]) -> StorageResult<()> {
66 self.write_bytes_atomic(path, bytes)
67 }
68
69 pub fn write_bytes_atomic(&self, path: &str, bytes: &[u8]) -> StorageResult<()> {
71 self.resolve_storage(path)?;
72 self.with_storage_lock(|| {
73 let full = self.resolve_storage(path)?;
74 if let Some(parent) = full.parent() {
75 fs::create_dir_all(parent).map_err(|_| StorageError::NotAvailable)?;
76 }
77 let full = self.resolve_storage(path)?;
78 let tmp = tmp_path_for(&full);
79 write_atomic_file(&tmp, &full, bytes)
80 .map_err(|_| StorageError::TransactionFailed(path.to_string()))
81 })?;
82 Ok(())
83 }
84
85 pub fn write_secure_bytes<P: TokenProvider>(
87 &self,
88 path: &str,
89 bytes: &[u8],
90 provider: &P,
91 claims: &TokenClaims,
92 ) -> StorageResult<()> {
93 let sealed = provider
94 .seal(bytes, claims)
95 .map_err(|_| StorageError::SecurityFailed(path.to_string()))?;
96 self.write_bytes_atomic(path, &sealed)
97 }
98
99 pub fn write_auth_required_bytes<P: TokenProvider>(
101 &self,
102 path: &str,
103 bytes: &[u8],
104 provider: Option<&P>,
105 claims: &TokenClaims,
106 ) -> StorageResult<()> {
107 let provider = require_auth_provider(path, provider)?;
108 self.write_secure_bytes(path, bytes, provider, claims)
109 }
110
111 pub fn write_remote_auth_required_bytes(
113 &self,
114 path: &str,
115 bytes: &[u8],
116 client: Option<&RemoteAuthStorageClient>,
117 ) -> StorageResult<()> {
118 let client = require_remote_auth_client(path, client)?;
119 let sealed = client.seal_resource(path, bytes)?;
120 self.write_bytes_atomic(path, &sealed)
121 }
122
123 pub fn read_bytes(&self, path: &str) -> StorageResult<Vec<u8>> {
125 self.read_bytes_bounded(path, DEFAULT_FILE_READ_MAX_BYTES)
126 }
127
128 pub(super) fn read_bytes_bounded(&self, path: &str, max_bytes: u64) -> StorageResult<Vec<u8>> {
129 self.resolve_storage(path)?;
130 self.with_storage_lock(|| {
131 let full = self.resolve_storage(path)?;
132 let file = open_regular_file(&full)
133 .map_err(|_| StorageError::RepositoryNotFound(path.to_string()))?;
134 let metadata = file
135 .metadata()
136 .map_err(|_| StorageError::RepositoryNotFound(path.to_string()))?;
137 if metadata.len() > max_bytes {
138 return Err(StorageError::TransactionFailed(path.to_string()));
139 }
140 let capacity = usize::try_from(metadata.len())
141 .map_err(|_| StorageError::TransactionFailed(path.to_string()))?;
142 let read_limit = max_bytes
143 .checked_add(1)
144 .ok_or_else(|| StorageError::TransactionFailed(path.to_string()))?;
145 let mut bytes = Vec::with_capacity(capacity);
146 file.take(read_limit)
147 .read_to_end(&mut bytes)
148 .map_err(|_| StorageError::RepositoryNotFound(path.to_string()))?;
149 if bytes.len() as u64 > max_bytes {
150 return Err(StorageError::TransactionFailed(path.to_string()));
151 }
152 Ok(bytes)
153 })
154 }
155
156 pub fn read_secure_bytes<P: TokenProvider>(
158 &self,
159 path: &str,
160 provider: &P,
161 claims: &TokenClaims,
162 ) -> StorageResult<Vec<u8>> {
163 let sealed = self.read_bytes(path)?;
164 provider
165 .open(&sealed, claims)
166 .map_err(|_| StorageError::SecurityFailed(path.to_string()))
167 }
168
169 pub fn read_auth_required_bytes<P: TokenProvider>(
171 &self,
172 path: &str,
173 provider: Option<&P>,
174 claims: &TokenClaims,
175 ) -> StorageResult<Vec<u8>> {
176 let provider = require_auth_provider(path, provider)?;
177 self.read_secure_bytes(path, provider, claims)
178 }
179
180 pub fn read_remote_auth_required_bytes(
182 &self,
183 path: &str,
184 client: Option<&RemoteAuthStorageClient>,
185 ) -> StorageResult<Vec<u8>> {
186 let client = require_remote_auth_client(path, client)?;
187 let sealed = self.read_bytes(path)?;
188 client.open_resource(path, &sealed)
189 }
190
191 pub fn exists(&self, path: &str) -> StorageResult<bool> {
193 self.resolve_storage(path)?;
194 self.with_storage_lock(|| {
195 let full = self.resolve_storage(path)?;
196 path_exists_no_follow(&full)
197 })
198 }
199
200 pub fn backup_file(&self, source: &str, backup_name: &str) -> StorageResult<()> {
202 self.backup_file_atomic(source, backup_name)
203 }
204
205 pub fn backup_file_atomic(&self, source: &str, backup_name: &str) -> StorageResult<()> {
207 self.resolve_storage(source)?;
208 self.resolve_backup(backup_name)?;
209 self.with_storage_lock(|| {
210 let source_full = self.resolve_storage(source)?;
211 if !path_exists_no_follow(&source_full)? {
212 return Err(StorageError::RepositoryNotFound(source.to_string()));
213 }
214 let backup_full = self.resolve_backup(backup_name)?;
215 if let Some(parent) = backup_full.parent() {
216 fs::create_dir_all(parent)
217 .map_err(|_| StorageError::BackupFailed(backup_name.to_string()))?;
218 }
219 let backup_full = self.resolve_backup(backup_name)?;
220 let mut source_file = open_regular_file(&source_full)
221 .map_err(|_| StorageError::BackupFailed(backup_name.to_string()))?;
222 let tmp = tmp_path_for(&backup_full);
223 copy_atomic_file(
224 &mut source_file,
225 &tmp,
226 &backup_full,
227 MAX_STORAGE_BACKUP_FILE_BYTES,
228 )
229 .map(|_| ())
230 .map_err(|_| StorageError::BackupFailed(backup_name.to_string()))
231 })?;
232 Ok(())
233 }
234
235 pub fn cleanup_temp_files(&self) -> StorageResult<usize> {
237 self.with_storage_lock(|| {
238 let mut removed = cleanup_tmp_in_dir(&self.storage_path)?;
239 removed += cleanup_tmp_in_dir(&self.backup_path)?;
240 Ok(removed)
241 })
242 }
243
244 fn resolve_storage(&self, relative: &str) -> StorageResult<PathBuf> {
245 resolve_under_root(&self.storage_path, relative)
246 }
247
248 fn resolve_backup(&self, relative: &str) -> StorageResult<PathBuf> {
249 resolve_under_root(&self.backup_path, relative)
250 }
251}
252
253fn require_auth_provider<'a, P: TokenProvider>(
254 path: &str,
255 provider: Option<&'a P>,
256) -> StorageResult<&'a P> {
257 provider.ok_or_else(|| StorageError::AuthUnavailable(path.to_string()))
258}
259
260fn require_remote_auth_client<'a>(
261 path: &str,
262 client: Option<&'a RemoteAuthStorageClient>,
263) -> StorageResult<&'a RemoteAuthStorageClient> {
264 client.ok_or_else(|| StorageError::AuthUnavailable(path.to_string()))
265}
266
267fn cleanup_tmp_in_dir(root: &Path) -> StorageResult<usize> {
268 if !path_exists_no_follow(root)? {
269 return Ok(0);
270 }
271 let mut temporary = Vec::new();
272 visit_bounded_tree(root, |path, kind| {
273 if is_temporary_file(path, kind) {
274 temporary.push(path.to_path_buf());
275 }
276 Ok(())
277 })?;
278 let removed = temporary.len();
279 for path in temporary {
280 fs::remove_file(path).map_err(|_| StorageError::NotAvailable)?;
281 }
282 Ok(removed)
283}
284
285fn count_tmp_in_dir(root: &Path) -> StorageResult<usize> {
286 if !path_exists_no_follow(root)? {
287 return Ok(0);
288 }
289 let mut count = 0usize;
290 visit_bounded_tree(root, |path, kind| {
291 if is_temporary_file(path, kind) {
292 count += 1;
293 }
294 Ok(())
295 })?;
296 Ok(count)
297}
298
299fn is_temporary_file(path: &Path, kind: StorageTreeEntryKind) -> bool {
300 kind == StorageTreeEntryKind::File
301 && path
302 .file_name()
303 .map(|name| name.to_string_lossy().ends_with(".tmp"))
304 .unwrap_or(false)
305}
306
307impl StorageProvider for FileStorageProvider {
308 fn status(&self) -> StorageStatus {
309 if path_is_real_directory(&self.storage_path) && path_is_real_directory(&self.backup_path) {
310 StorageStatus::Online
311 } else {
312 StorageStatus::Offline
313 }
314 }
315
316 fn health(&self) -> StorageHealth {
317 if path_is_real_directory(&self.storage_path) && path_is_real_directory(&self.backup_path) {
318 let orphan_tmp = match (
319 count_tmp_in_dir(&self.storage_path),
320 count_tmp_in_dir(&self.backup_path),
321 ) {
322 (Ok(storage), Ok(backups)) => storage.saturating_add(backups),
323 _ => {
324 return StorageHealth {
325 status: StorageStatus::Degraded,
326 message: Some("temporary file scan failed".to_string()),
327 };
328 }
329 };
330 if orphan_tmp > 0 {
331 return StorageHealth {
332 status: StorageStatus::Degraded,
333 message: Some(format!("found {orphan_tmp} orphan temp files")),
334 };
335 }
336 return StorageHealth {
337 status: StorageStatus::Online,
338 message: None,
339 };
340 }
341 StorageHealth {
342 status: StorageStatus::Degraded,
343 message: Some("storage or backup path is missing".to_string()),
344 }
345 }
346
347 fn open(&mut self) -> StorageResult<()> {
348 self.create_dirs()?;
349 self.opened = true;
350 Ok(())
351 }
352
353 fn close(&mut self) -> StorageResult<()> {
354 self.opened = false;
355 Ok(())
356 }
357
358 fn begin_transaction(&mut self) -> StorageResult<Box<dyn Transaction>> {
359 if !self.opened {
360 return Err(StorageError::NotAvailable);
361 }
362 Err(StorageError::TransactionsUnsupported)
363 }
364
365 fn list_backups(&self) -> Vec<BackupDescriptor> {
366 self.with_storage_lock(|| Ok(list_backup_descriptors(&self.backup_path)))
367 .unwrap_or_default()
368 }
369}
370
371impl StorageCapabilityProviderV1 for FileStorageProvider {
372 fn storage_capabilities_v1(
373 &self,
374 ) -> Result<StorageCapabilityDescriptorV1, StorageCapabilityError> {
375 file_storage_capability_descriptor_v1()
376 }
377}
378
379pub fn file_storage_capability_descriptor_v1(
381) -> Result<StorageCapabilityDescriptorV1, StorageCapabilityError> {
382 let provider_id = appcore_contracts::ProviderId::new("file")
383 .map_err(|_| StorageCapabilityError::InvalidDescriptor)?;
384 Ok(StorageCapabilityDescriptorV1::new(
385 provider_id,
386 [StorageCapabilityV1::Snapshot],
387 ))
388}