]>
Commit | Line | Data |
---|---|---|
133d718f | 1 | use std::convert::TryFrom; |
6da20161 | 2 | use std::fmt; |
c0977501 | 3 | use std::os::unix::io::RawFd; |
6da20161 WB |
4 | use std::path::PathBuf; |
5 | use std::sync::Arc; | |
038ee599 | 6 | |
5c9c23b6 | 7 | use anyhow::{bail, format_err, Error}; |
770a36e5 | 8 | |
f03649b8 TL |
9 | use proxmox_sys::fs::lock_dir_noblock; |
10 | ||
11ffd737 | 11 | use pbs_api_types::{ |
133d718f | 12 | Authid, BackupNamespace, BackupType, GroupFilter, BACKUP_DATE_REGEX, BACKUP_FILE_REGEX, |
11ffd737 | 13 | }; |
5c9c23b6 | 14 | use pbs_config::{open_backup_lockfile, BackupLockGuard}; |
b3483782 | 15 | |
133d718f | 16 | use crate::manifest::{BackupManifest, MANIFEST_BLOB_NAME, MANIFEST_LOCK_NAME}; |
0f198b82 | 17 | use crate::{DataBlob, DataStore}; |
8f14e8fe | 18 | |
d57474e0 | 19 | /// BackupGroup is a directory containing a list of BackupDir |
6da20161 | 20 | #[derive(Clone)] |
b3483782 | 21 | pub struct BackupGroup { |
6da20161 WB |
22 | store: Arc<DataStore>, |
23 | ||
133d718f | 24 | ns: BackupNamespace, |
db87d93e | 25 | group: pbs_api_types::BackupGroup, |
b3483782 DM |
26 | } |
27 | ||
6da20161 WB |
28 | impl fmt::Debug for BackupGroup { |
29 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
30 | f.debug_struct("BackupGroup") | |
31 | .field("store", &self.store.name()) | |
32 | .field("group", &self.group) | |
33 | .finish() | |
34 | } | |
35 | } | |
36 | ||
b3483782 | 37 | impl BackupGroup { |
133d718f WB |
38 | pub(crate) fn new( |
39 | store: Arc<DataStore>, | |
40 | ns: BackupNamespace, | |
41 | group: pbs_api_types::BackupGroup, | |
42 | ) -> Self { | |
43 | Self { store, ns, group } | |
b3483782 DM |
44 | } |
45 | ||
5116453b WB |
46 | /// Access the underlying [`BackupGroup`](pbs_api_types::BackupGroup). |
47 | #[inline] | |
48 | pub fn group(&self) -> &pbs_api_types::BackupGroup { | |
49 | &self.group | |
50 | } | |
51 | ||
11ffd737 WB |
52 | #[inline] |
53 | pub fn backup_ns(&self) -> &BackupNamespace { | |
133d718f | 54 | &self.ns |
11ffd737 WB |
55 | } |
56 | ||
57 | #[inline] | |
988d575d | 58 | pub fn backup_type(&self) -> BackupType { |
db87d93e | 59 | self.group.ty |
b3483782 DM |
60 | } |
61 | ||
11ffd737 | 62 | #[inline] |
b3483782 | 63 | pub fn backup_id(&self) -> &str { |
db87d93e | 64 | &self.group.id |
b3483782 DM |
65 | } |
66 | ||
6da20161 | 67 | pub fn full_group_path(&self) -> PathBuf { |
133d718f | 68 | self.store.group_path(&self.ns, &self.group) |
6da20161 WB |
69 | } |
70 | ||
1f6a45c9 | 71 | pub fn relative_group_path(&self) -> PathBuf { |
133d718f WB |
72 | let mut path = self.store.namespace_path(&self.ns); |
73 | path.push(self.group.ty.as_str()); | |
74 | path.push(&self.group.id); | |
75 | path | |
b3483782 | 76 | } |
c0977501 | 77 | |
6da20161 | 78 | pub fn list_backups(&self) -> Result<Vec<BackupInfo>, Error> { |
c0977501 DM |
79 | let mut list = vec![]; |
80 | ||
4b77d300 | 81 | let path = self.full_group_path(); |
c0977501 | 82 | |
25877d05 | 83 | proxmox_sys::fs::scandir( |
cded320e TL |
84 | libc::AT_FDCWD, |
85 | &path, | |
86 | &BACKUP_DATE_REGEX, | |
87 | |l2_fd, backup_time, file_type| { | |
88 | if file_type != nix::dir::Type::Directory { | |
89 | return Ok(()); | |
90 | } | |
c0977501 | 91 | |
db87d93e | 92 | let backup_dir = self.backup_dir_with_rfc3339(backup_time)?; |
cded320e | 93 | let files = list_backup_files(l2_fd, backup_time)?; |
c0977501 | 94 | |
6da20161 | 95 | let protected = backup_dir.is_protected(); |
92c5cf42 | 96 | |
42c2b5be TL |
97 | list.push(BackupInfo { |
98 | backup_dir, | |
99 | files, | |
100 | protected, | |
101 | }); | |
c0977501 | 102 | |
cded320e TL |
103 | Ok(()) |
104 | }, | |
105 | )?; | |
c0977501 DM |
106 | Ok(list) |
107 | } | |
aeeac29b | 108 | |
c4b2d26c | 109 | /// Finds the latest backup inside a backup group |
6da20161 WB |
110 | pub fn last_backup(&self, only_finished: bool) -> Result<Option<BackupInfo>, Error> { |
111 | let backups = self.list_backups()?; | |
c4b2d26c WB |
112 | Ok(backups |
113 | .into_iter() | |
114 | .filter(|item| !only_finished || item.is_finished()) | |
115 | .max_by_key(|item| item.backup_dir.backup_time())) | |
116 | } | |
117 | ||
6da20161 | 118 | pub fn last_successful_backup(&self) -> Result<Option<i64>, Error> { |
8f14e8fe DM |
119 | let mut last = None; |
120 | ||
4b77d300 | 121 | let path = self.full_group_path(); |
8f14e8fe | 122 | |
25877d05 | 123 | proxmox_sys::fs::scandir( |
cded320e TL |
124 | libc::AT_FDCWD, |
125 | &path, | |
126 | &BACKUP_DATE_REGEX, | |
127 | |l2_fd, backup_time, file_type| { | |
128 | if file_type != nix::dir::Type::Directory { | |
129 | return Ok(()); | |
8f14e8fe | 130 | } |
8f14e8fe | 131 | |
cded320e TL |
132 | let mut manifest_path = PathBuf::from(backup_time); |
133 | manifest_path.push(MANIFEST_BLOB_NAME); | |
134 | ||
135 | use nix::fcntl::{openat, OFlag}; | |
136 | match openat( | |
137 | l2_fd, | |
138 | &manifest_path, | |
139 | OFlag::O_RDONLY, | |
140 | nix::sys::stat::Mode::empty(), | |
141 | ) { | |
142 | Ok(rawfd) => { | |
143 | /* manifest exists --> assume backup was successful */ | |
144 | /* close else this leaks! */ | |
145 | nix::unistd::close(rawfd)?; | |
146 | } | |
147 | Err(nix::Error::Sys(nix::errno::Errno::ENOENT)) => { | |
148 | return Ok(()); | |
149 | } | |
150 | Err(err) => { | |
151 | bail!("last_successful_backup: unexpected error - {}", err); | |
152 | } | |
153 | } | |
154 | ||
6ef1b649 | 155 | let timestamp = proxmox_time::parse_rfc3339(backup_time)?; |
cded320e TL |
156 | if let Some(last_timestamp) = last { |
157 | if timestamp > last_timestamp { | |
158 | last = Some(timestamp); | |
159 | } | |
160 | } else { | |
161 | last = Some(timestamp); | |
162 | } | |
8f14e8fe | 163 | |
cded320e TL |
164 | Ok(()) |
165 | }, | |
166 | )?; | |
8f14e8fe DM |
167 | |
168 | Ok(last) | |
169 | } | |
0ceb9753 FG |
170 | |
171 | pub fn matches(&self, filter: &GroupFilter) -> bool { | |
db87d93e WB |
172 | self.group.matches(filter) |
173 | } | |
174 | ||
175 | pub fn backup_dir(&self, time: i64) -> Result<BackupDir, Error> { | |
176 | BackupDir::with_group(self.clone(), time) | |
177 | } | |
178 | ||
179 | pub fn backup_dir_with_rfc3339<T: Into<String>>( | |
180 | &self, | |
181 | time_string: T, | |
182 | ) -> Result<BackupDir, Error> { | |
183 | BackupDir::with_rfc3339(self.clone(), time_string.into()) | |
184 | } | |
6da20161 WB |
185 | |
186 | pub fn iter_snapshots(&self) -> Result<crate::ListSnapshots, Error> { | |
187 | crate::ListSnapshots::new(self.clone()) | |
188 | } | |
f03649b8 TL |
189 | |
190 | /// Destroy the group inclusive all its backup snapshots (BackupDir's) | |
191 | /// | |
192 | /// Returns true if all snapshots were removed, and false if some were protected | |
193 | pub fn destroy(&self) -> Result<bool, Error> { | |
194 | let path = self.full_group_path(); | |
195 | let _guard = | |
196 | proxmox_sys::fs::lock_dir_noblock(&path, "backup group", "possible running backup")?; | |
197 | ||
198 | log::info!("removing backup group {:?}", path); | |
199 | let mut removed_all_snaps = true; | |
200 | for snap in self.iter_snapshots()? { | |
201 | let snap = snap?; | |
202 | if snap.is_protected() { | |
203 | removed_all_snaps = false; | |
204 | continue; | |
205 | } | |
206 | snap.destroy(false)?; | |
207 | } | |
208 | ||
209 | if removed_all_snaps { | |
210 | std::fs::remove_dir_all(&path).map_err(|err| { | |
211 | format_err!("removing group directory {:?} failed - {}", path, err) | |
212 | })?; | |
213 | } | |
214 | ||
215 | Ok(removed_all_snaps) | |
216 | } | |
133d718f WB |
217 | |
218 | /// Returns the backup owner. | |
219 | /// | |
220 | /// The backup owner is the entity who first created the backup group. | |
221 | pub fn get_owner(&self) -> Result<Authid, Error> { | |
222 | self.store.get_owner(&self.ns, self.as_ref()) | |
223 | } | |
224 | ||
225 | /// Set the backup owner. | |
226 | pub fn set_owner(&self, auth_id: &Authid, force: bool) -> Result<(), Error> { | |
227 | self.store | |
228 | .set_owner(&self.ns, &self.as_ref(), auth_id, force) | |
229 | } | |
230 | } | |
231 | ||
232 | impl AsRef<pbs_api_types::BackupNamespace> for BackupGroup { | |
233 | #[inline] | |
234 | fn as_ref(&self) -> &pbs_api_types::BackupNamespace { | |
235 | &self.ns | |
236 | } | |
db87d93e WB |
237 | } |
238 | ||
239 | impl AsRef<pbs_api_types::BackupGroup> for BackupGroup { | |
240 | #[inline] | |
241 | fn as_ref(&self) -> &pbs_api_types::BackupGroup { | |
242 | &self.group | |
0ceb9753 | 243 | } |
b3483782 DM |
244 | } |
245 | ||
988d575d WB |
246 | impl From<&BackupGroup> for pbs_api_types::BackupGroup { |
247 | fn from(group: &BackupGroup) -> pbs_api_types::BackupGroup { | |
db87d93e | 248 | group.group.clone() |
988d575d WB |
249 | } |
250 | } | |
251 | ||
252 | impl From<BackupGroup> for pbs_api_types::BackupGroup { | |
253 | fn from(group: BackupGroup) -> pbs_api_types::BackupGroup { | |
db87d93e | 254 | group.group |
988d575d WB |
255 | } |
256 | } | |
257 | ||
8c74349b WB |
258 | impl fmt::Display for BackupGroup { |
259 | #[inline] | |
260 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
133d718f WB |
261 | if self.ns.is_root() { |
262 | fmt::Display::fmt(&self.group, f) | |
263 | } else { | |
264 | write!(f, "[{}]:{}", self.ns, self.group) | |
265 | } | |
23f74c19 DM |
266 | } |
267 | } | |
268 | ||
db87d93e WB |
269 | impl From<BackupDir> for BackupGroup { |
270 | fn from(dir: BackupDir) -> BackupGroup { | |
271 | BackupGroup { | |
6da20161 | 272 | store: dir.store, |
133d718f | 273 | ns: dir.ns, |
db87d93e WB |
274 | group: dir.dir.group, |
275 | } | |
276 | } | |
277 | } | |
d6d3b353 | 278 | |
db87d93e WB |
279 | impl From<&BackupDir> for BackupGroup { |
280 | fn from(dir: &BackupDir) -> BackupGroup { | |
281 | BackupGroup { | |
6da20161 | 282 | store: Arc::clone(&dir.store), |
133d718f | 283 | ns: dir.ns.clone(), |
db87d93e WB |
284 | group: dir.dir.group.clone(), |
285 | } | |
d6d3b353 DM |
286 | } |
287 | } | |
288 | ||
b3483782 | 289 | /// Uniquely identify a Backup (relative to data store) |
d57474e0 DM |
290 | /// |
291 | /// We also call this a backup snaphost. | |
6da20161 | 292 | #[derive(Clone)] |
b3483782 | 293 | pub struct BackupDir { |
6da20161 | 294 | store: Arc<DataStore>, |
133d718f | 295 | ns: BackupNamespace, |
db87d93e | 296 | dir: pbs_api_types::BackupDir, |
6a7be83e | 297 | // backup_time as rfc3339 |
cded320e | 298 | backup_time_string: String, |
b3483782 DM |
299 | } |
300 | ||
6da20161 WB |
301 | impl fmt::Debug for BackupDir { |
302 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
303 | f.debug_struct("BackupDir") | |
304 | .field("store", &self.store.name()) | |
305 | .field("dir", &self.dir) | |
306 | .field("backup_time_string", &self.backup_time_string) | |
307 | .finish() | |
308 | } | |
309 | } | |
310 | ||
b3483782 | 311 | impl BackupDir { |
db87d93e WB |
312 | /// Temporarily used for tests. |
313 | #[doc(hidden)] | |
314 | pub fn new_test(dir: pbs_api_types::BackupDir) -> Self { | |
315 | Self { | |
6da20161 | 316 | store: unsafe { DataStore::new_test() }, |
db87d93e | 317 | backup_time_string: Self::backup_time_to_string(dir.time).unwrap(), |
133d718f | 318 | ns: BackupNamespace::root(), |
db87d93e WB |
319 | dir, |
320 | } | |
321 | } | |
322 | ||
323 | pub(crate) fn with_group(group: BackupGroup, backup_time: i64) -> Result<Self, Error> { | |
324 | let backup_time_string = Self::backup_time_to_string(backup_time)?; | |
cded320e | 325 | Ok(Self { |
6da20161 | 326 | store: group.store, |
133d718f | 327 | ns: group.ns, |
db87d93e | 328 | dir: (group.group, backup_time).into(), |
cded320e TL |
329 | backup_time_string, |
330 | }) | |
bc871bd1 DM |
331 | } |
332 | ||
db87d93e WB |
333 | pub(crate) fn with_rfc3339( |
334 | group: BackupGroup, | |
335 | backup_time_string: String, | |
336 | ) -> Result<Self, Error> { | |
337 | let backup_time = proxmox_time::parse_rfc3339(&backup_time_string)?; | |
cded320e | 338 | Ok(Self { |
6da20161 | 339 | store: group.store, |
133d718f | 340 | ns: group.ns, |
db87d93e | 341 | dir: (group.group, backup_time).into(), |
cded320e TL |
342 | backup_time_string, |
343 | }) | |
51a4f63f | 344 | } |
b3483782 | 345 | |
11ffd737 WB |
346 | #[inline] |
347 | pub fn backup_ns(&self) -> &BackupNamespace { | |
133d718f | 348 | &self.ns |
11ffd737 WB |
349 | } |
350 | ||
db87d93e WB |
351 | #[inline] |
352 | pub fn backup_type(&self) -> BackupType { | |
353 | self.dir.group.ty | |
b3483782 DM |
354 | } |
355 | ||
db87d93e WB |
356 | #[inline] |
357 | pub fn backup_id(&self) -> &str { | |
358 | &self.dir.group.id | |
359 | } | |
360 | ||
361 | #[inline] | |
6a7be83e | 362 | pub fn backup_time(&self) -> i64 { |
db87d93e | 363 | self.dir.time |
b3483782 DM |
364 | } |
365 | ||
6a7be83e DM |
366 | pub fn backup_time_string(&self) -> &str { |
367 | &self.backup_time_string | |
368 | } | |
369 | ||
cded320e | 370 | pub fn relative_path(&self) -> PathBuf { |
133d718f WB |
371 | let mut path = self.store.namespace_path(&self.ns); |
372 | path.push(self.dir.group.ty.as_str()); | |
373 | path.push(&self.dir.group.id); | |
374 | path.push(&self.backup_time_string); | |
375 | path | |
db87d93e | 376 | } |
b3483782 | 377 | |
db87d93e | 378 | /// Returns the absolute path for backup_dir, using the cached formatted time string. |
6da20161 | 379 | pub fn full_path(&self) -> PathBuf { |
133d718f | 380 | self.store.snapshot_path(&self.ns, &self.dir) |
b3483782 | 381 | } |
fa5d6977 | 382 | |
6da20161 WB |
383 | pub fn protected_file(&self) -> PathBuf { |
384 | let mut path = self.store.base_path(); | |
92c5cf42 DC |
385 | path.push(self.relative_path()); |
386 | path.push(".protected"); | |
387 | path | |
388 | } | |
389 | ||
6da20161 WB |
390 | pub fn is_protected(&self) -> bool { |
391 | let path = self.protected_file(); | |
92c5cf42 DC |
392 | path.exists() |
393 | } | |
394 | ||
6a7be83e DM |
395 | pub fn backup_time_to_string(backup_time: i64) -> Result<String, Error> { |
396 | // fixme: can this fail? (avoid unwrap) | |
dcf5a0f6 | 397 | proxmox_time::epoch_to_rfc3339_utc(backup_time) |
fa5d6977 | 398 | } |
5c9c23b6 | 399 | |
1eef52c2 TL |
400 | /// load a `DataBlob` from this snapshot's backup dir. |
401 | pub fn load_blob(&self, filename: &str) -> Result<DataBlob, Error> { | |
402 | let mut path = self.full_path(); | |
403 | path.push(filename); | |
404 | ||
405 | proxmox_lang::try_block!({ | |
406 | let mut file = std::fs::File::open(&path)?; | |
407 | DataBlob::load_from_reader(&mut file) | |
408 | }) | |
409 | .map_err(|err| format_err!("unable to load blob '{:?}' - {}", path, err)) | |
410 | } | |
411 | ||
5c9c23b6 TL |
412 | /// Returns the filename to lock a manifest |
413 | /// | |
414 | /// Also creates the basedir. The lockfile is located in | |
415 | /// '/run/proxmox-backup/locks/{datastore}/{type}/{id}/{timestamp}.index.json.lck' | |
f03649b8 | 416 | fn manifest_lock_path(&self) -> Result<String, Error> { |
5c9c23b6 TL |
417 | let mut path = format!("/run/proxmox-backup/locks/{}/{self}", self.store.name()); |
418 | std::fs::create_dir_all(&path)?; | |
419 | use std::fmt::Write; | |
420 | let ts = self.backup_time_string(); | |
421 | write!(path, "/{ts}{}", &MANIFEST_LOCK_NAME)?; | |
422 | ||
423 | Ok(path) | |
424 | } | |
425 | ||
426 | /// Locks the manifest of a snapshot, for example, to update or delete it. | |
427 | pub(crate) fn lock_manifest(&self) -> Result<BackupLockGuard, Error> { | |
428 | let path = self.manifest_lock_path()?; | |
429 | ||
430 | // actions locking the manifest should be relatively short, only wait a few seconds | |
431 | open_backup_lockfile(&path, Some(std::time::Duration::from_secs(5)), true) | |
432 | .map_err(|err| format_err!("unable to acquire manifest lock {:?} - {}", &path, err)) | |
433 | } | |
f03649b8 TL |
434 | |
435 | /// Destroy the whole snapshot, bails if it's protected | |
436 | /// | |
437 | /// Setting `force` to true skips locking and thus ignores if the backup is currently in use. | |
438 | pub fn destroy(&self, force: bool) -> Result<(), Error> { | |
439 | let full_path = self.full_path(); | |
440 | ||
441 | let (_guard, _manifest_guard); | |
442 | if !force { | |
443 | _guard = lock_dir_noblock(&full_path, "snapshot", "possibly running or in use")?; | |
444 | _manifest_guard = self.lock_manifest()?; | |
445 | } | |
446 | ||
447 | if self.is_protected() { | |
448 | bail!("cannot remove protected snapshot"); // use special error type? | |
449 | } | |
450 | ||
451 | log::info!("removing backup snapshot {:?}", full_path); | |
452 | std::fs::remove_dir_all(&full_path).map_err(|err| { | |
453 | format_err!("removing backup snapshot {:?} failed - {}", full_path, err,) | |
454 | })?; | |
455 | ||
456 | // the manifest doesn't exist anymore, no need to keep the lock (already done by guard?) | |
457 | if let Ok(path) = self.manifest_lock_path() { | |
458 | let _ = std::fs::remove_file(path); // ignore errors | |
459 | } | |
460 | ||
461 | Ok(()) | |
462 | } | |
133d718f WB |
463 | |
464 | /// Get the datastore. | |
465 | pub fn datastore(&self) -> &Arc<DataStore> { | |
466 | &self.store | |
467 | } | |
468 | ||
469 | /// Returns the backup owner. | |
470 | /// | |
471 | /// The backup owner is the entity who first created the backup group. | |
472 | pub fn get_owner(&self) -> Result<Authid, Error> { | |
473 | self.store.get_owner(&self.ns, self.as_ref()) | |
474 | } | |
475 | ||
476 | /// Lock the snapshot and open a reader. | |
477 | pub fn locked_reader(&self) -> Result<crate::SnapshotReader, Error> { | |
478 | crate::SnapshotReader::new_do(self.clone()) | |
479 | } | |
480 | ||
481 | /// Load the manifest without a lock. Must not be written back. | |
482 | pub fn load_manifest(&self) -> Result<(BackupManifest, u64), Error> { | |
483 | let blob = self.load_blob(MANIFEST_BLOB_NAME)?; | |
484 | let raw_size = blob.raw_size(); | |
485 | let manifest = BackupManifest::try_from(blob)?; | |
486 | Ok((manifest, raw_size)) | |
487 | } | |
488 | ||
489 | /// Update the manifest of the specified snapshot. Never write a manifest directly, | |
490 | /// only use this method - anything else may break locking guarantees. | |
491 | pub fn update_manifest( | |
492 | &self, | |
493 | update_fn: impl FnOnce(&mut BackupManifest), | |
494 | ) -> Result<(), Error> { | |
495 | self.store.update_manifest(self, update_fn) | |
496 | } | |
497 | } | |
498 | ||
499 | impl AsRef<pbs_api_types::BackupNamespace> for BackupDir { | |
500 | fn as_ref(&self) -> &pbs_api_types::BackupNamespace { | |
501 | &self.ns | |
502 | } | |
b3483782 | 503 | } |
d6d3b353 | 504 | |
db87d93e WB |
505 | impl AsRef<pbs_api_types::BackupDir> for BackupDir { |
506 | fn as_ref(&self) -> &pbs_api_types::BackupDir { | |
507 | &self.dir | |
988d575d WB |
508 | } |
509 | } | |
510 | ||
db87d93e WB |
511 | impl AsRef<pbs_api_types::BackupGroup> for BackupDir { |
512 | fn as_ref(&self) -> &pbs_api_types::BackupGroup { | |
513 | &self.dir.group | |
988d575d WB |
514 | } |
515 | } | |
516 | ||
db87d93e WB |
517 | impl From<&BackupDir> for pbs_api_types::BackupGroup { |
518 | fn from(dir: &BackupDir) -> pbs_api_types::BackupGroup { | |
519 | dir.dir.group.clone() | |
520 | } | |
521 | } | |
a67f7d0a | 522 | |
db87d93e WB |
523 | impl From<BackupDir> for pbs_api_types::BackupGroup { |
524 | fn from(dir: BackupDir) -> pbs_api_types::BackupGroup { | |
525 | dir.dir.group.into() | |
526 | } | |
527 | } | |
a67f7d0a | 528 | |
db87d93e WB |
529 | impl From<&BackupDir> for pbs_api_types::BackupDir { |
530 | fn from(dir: &BackupDir) -> pbs_api_types::BackupDir { | |
531 | dir.dir.clone() | |
532 | } | |
533 | } | |
534 | ||
535 | impl From<BackupDir> for pbs_api_types::BackupDir { | |
536 | fn from(dir: BackupDir) -> pbs_api_types::BackupDir { | |
537 | dir.dir | |
a67f7d0a DM |
538 | } |
539 | } | |
b3483782 | 540 | |
8c74349b WB |
541 | impl fmt::Display for BackupDir { |
542 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
133d718f WB |
543 | if self.ns.is_root() { |
544 | write!(f, "{}/{}", self.dir.group, self.backup_time_string) | |
545 | } else { | |
546 | write!( | |
547 | f, | |
548 | "[{}]:{}/{}", | |
549 | self.ns, self.dir.group, self.backup_time_string | |
550 | ) | |
551 | } | |
391d3107 WB |
552 | } |
553 | } | |
554 | ||
d57474e0 | 555 | /// Detailed Backup Information, lists files inside a BackupDir |
6da20161 | 556 | #[derive(Clone, Debug)] |
b3483782 DM |
557 | pub struct BackupInfo { |
558 | /// the backup directory | |
559 | pub backup_dir: BackupDir, | |
560 | /// List of data files | |
561 | pub files: Vec<String>, | |
92c5cf42 DC |
562 | /// Protection Status |
563 | pub protected: bool, | |
b3483782 DM |
564 | } |
565 | ||
566 | impl BackupInfo { | |
6da20161 | 567 | pub fn new(backup_dir: BackupDir) -> Result<BackupInfo, Error> { |
4b77d300 | 568 | let path = backup_dir.full_path(); |
38a6cdda DM |
569 | |
570 | let files = list_backup_files(libc::AT_FDCWD, &path)?; | |
6da20161 | 571 | let protected = backup_dir.is_protected(); |
38a6cdda | 572 | |
42c2b5be TL |
573 | Ok(BackupInfo { |
574 | backup_dir, | |
575 | files, | |
576 | protected, | |
577 | }) | |
38a6cdda DM |
578 | } |
579 | ||
b3483782 | 580 | pub fn sort_list(list: &mut Vec<BackupInfo>, ascendending: bool) { |
cded320e TL |
581 | if ascendending { |
582 | // oldest first | |
db87d93e | 583 | list.sort_unstable_by(|a, b| a.backup_dir.dir.time.cmp(&b.backup_dir.dir.time)); |
cded320e TL |
584 | } else { |
585 | // newest first | |
db87d93e | 586 | list.sort_unstable_by(|a, b| b.backup_dir.dir.time.cmp(&a.backup_dir.dir.time)); |
b3483782 DM |
587 | } |
588 | } | |
589 | ||
c9756b40 SR |
590 | pub fn is_finished(&self) -> bool { |
591 | // backup is considered unfinished if there is no manifest | |
42c2b5be | 592 | self.files.iter().any(|name| name == MANIFEST_BLOB_NAME) |
c9756b40 | 593 | } |
b3483782 | 594 | } |
c0977501 | 595 | |
cded320e TL |
596 | fn list_backup_files<P: ?Sized + nix::NixPath>( |
597 | dirfd: RawFd, | |
598 | path: &P, | |
599 | ) -> Result<Vec<String>, Error> { | |
c0977501 DM |
600 | let mut files = vec![]; |
601 | ||
25877d05 | 602 | proxmox_sys::fs::scandir(dirfd, path, &BACKUP_FILE_REGEX, |_, filename, file_type| { |
cded320e TL |
603 | if file_type != nix::dir::Type::File { |
604 | return Ok(()); | |
605 | } | |
c0977501 DM |
606 | files.push(filename.to_owned()); |
607 | Ok(()) | |
608 | })?; | |
609 | ||
610 | Ok(files) | |
611 | } |