]> git.proxmox.com Git - proxmox-backup.git/blame - pbs-datastore/src/backup_info.rs
split the namespace out of BackupGroup/Dir api types
[proxmox-backup.git] / pbs-datastore / src / backup_info.rs
CommitLineData
133d718f 1use std::convert::TryFrom;
6da20161 2use std::fmt;
c0977501 3use std::os::unix::io::RawFd;
6da20161
WB
4use std::path::PathBuf;
5use std::sync::Arc;
038ee599 6
5c9c23b6 7use anyhow::{bail, format_err, Error};
770a36e5 8
f03649b8
TL
9use proxmox_sys::fs::lock_dir_noblock;
10
11ffd737 11use pbs_api_types::{
133d718f 12 Authid, BackupNamespace, BackupType, GroupFilter, BACKUP_DATE_REGEX, BACKUP_FILE_REGEX,
11ffd737 13};
5c9c23b6 14use pbs_config::{open_backup_lockfile, BackupLockGuard};
b3483782 15
133d718f 16use crate::manifest::{BackupManifest, MANIFEST_BLOB_NAME, MANIFEST_LOCK_NAME};
0f198b82 17use crate::{DataBlob, DataStore};
8f14e8fe 18
d57474e0 19/// BackupGroup is a directory containing a list of BackupDir
6da20161 20#[derive(Clone)]
b3483782 21pub 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
28impl 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 37impl 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
232impl 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
239impl 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
246impl 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
252impl 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
258impl 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
269impl 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
279impl 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 293pub 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
301impl 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 311impl 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
499impl 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
505impl 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
511impl 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
517impl 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
523impl 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
529impl From<&BackupDir> for pbs_api_types::BackupDir {
530 fn from(dir: &BackupDir) -> pbs_api_types::BackupDir {
531 dir.dir.clone()
532 }
533}
534
535impl 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
541impl 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
557pub 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
566impl 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
596fn 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}