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