]> git.proxmox.com Git - proxmox-backup.git/blame - pbs-datastore/src/backup_info.rs
BackupGroup: add filter helper
[proxmox-backup.git] / pbs-datastore / src / backup_info.rs
CommitLineData
c0977501 1use std::os::unix::io::RawFd;
cded320e 2use std::path::{Path, PathBuf};
0ceb9753 3use std::str::FromStr;
038ee599 4
770a36e5
WB
5use anyhow::{bail, format_err, Error};
6
a5951b4f 7use pbs_api_types::{
04660893
DM
8 BACKUP_ID_REGEX,
9 BACKUP_TYPE_REGEX,
10 BACKUP_DATE_REGEX,
11 GROUP_PATH_REGEX,
12 SNAPSHOT_PATH_REGEX,
13 BACKUP_FILE_REGEX,
0ceb9753 14 GroupFilter,
04660893 15};
b3483782 16
8f14e8fe
DM
17use super::manifest::MANIFEST_BLOB_NAME;
18
d57474e0 19/// BackupGroup is a directory containing a list of BackupDir
11d89239 20#[derive(Debug, Eq, PartialEq, Hash, Clone)]
b3483782
DM
21pub struct BackupGroup {
22 /// Type of backup
23 backup_type: String,
24 /// Unique (for this type) ID
25 backup_id: String,
26}
27
4264c502 28impl std::cmp::Ord for BackupGroup {
4264c502
DM
29 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
30 let type_order = self.backup_type.cmp(&other.backup_type);
31 if type_order != std::cmp::Ordering::Equal {
32 return type_order;
33 }
34 // try to compare IDs numerically
35 let id_self = self.backup_id.parse::<u64>();
36 let id_other = other.backup_id.parse::<u64>();
37 match (id_self, id_other) {
38 (Ok(id_self), Ok(id_other)) => id_self.cmp(&id_other),
39 (Ok(_), Err(_)) => std::cmp::Ordering::Less,
40 (Err(_), Ok(_)) => std::cmp::Ordering::Greater,
cded320e 41 _ => self.backup_id.cmp(&other.backup_id),
4264c502
DM
42 }
43 }
44}
45
46impl std::cmp::PartialOrd for BackupGroup {
47 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
48 Some(self.cmp(other))
49 }
50}
51
b3483782 52impl BackupGroup {
93b49ce3 53 pub fn new<T: Into<String>, U: Into<String>>(backup_type: T, backup_id: U) -> Self {
cded320e
TL
54 Self {
55 backup_type: backup_type.into(),
56 backup_id: backup_id.into(),
57 }
b3483782
DM
58 }
59
60 pub fn backup_type(&self) -> &str {
61 &self.backup_type
62 }
63
64 pub fn backup_id(&self) -> &str {
65 &self.backup_id
66 }
67
cded320e 68 pub fn group_path(&self) -> PathBuf {
b3483782
DM
69 let mut relative_path = PathBuf::new();
70
71 relative_path.push(&self.backup_type);
72
73 relative_path.push(&self.backup_id);
74
75 relative_path
76 }
c0977501
DM
77
78 pub fn list_backups(&self, base_path: &Path) -> Result<Vec<BackupInfo>, Error> {
c0977501
DM
79 let mut list = vec![];
80
81 let mut path = base_path.to_owned();
82 path.push(self.group_path());
83
770a36e5 84 pbs_tools::fs::scandir(
cded320e
TL
85 libc::AT_FDCWD,
86 &path,
87 &BACKUP_DATE_REGEX,
88 |l2_fd, backup_time, file_type| {
89 if file_type != nix::dir::Type::Directory {
90 return Ok(());
91 }
c0977501 92
cded320e
TL
93 let backup_dir =
94 BackupDir::with_rfc3339(&self.backup_type, &self.backup_id, backup_time)?;
95 let files = list_backup_files(l2_fd, backup_time)?;
c0977501 96
92c5cf42
DC
97 let protected = backup_dir.is_protected(base_path.to_owned());
98
99 list.push(BackupInfo { backup_dir, files, protected });
c0977501 100
cded320e
TL
101 Ok(())
102 },
103 )?;
c0977501
DM
104 Ok(list)
105 }
aeeac29b 106
cded320e 107 pub fn last_successful_backup(&self, base_path: &Path) -> Result<Option<i64>, Error> {
8f14e8fe
DM
108 let mut last = None;
109
110 let mut path = base_path.to_owned();
111 path.push(self.group_path());
112
770a36e5 113 pbs_tools::fs::scandir(
cded320e
TL
114 libc::AT_FDCWD,
115 &path,
116 &BACKUP_DATE_REGEX,
117 |l2_fd, backup_time, file_type| {
118 if file_type != nix::dir::Type::Directory {
119 return Ok(());
8f14e8fe 120 }
8f14e8fe 121
cded320e
TL
122 let mut manifest_path = PathBuf::from(backup_time);
123 manifest_path.push(MANIFEST_BLOB_NAME);
124
125 use nix::fcntl::{openat, OFlag};
126 match openat(
127 l2_fd,
128 &manifest_path,
129 OFlag::O_RDONLY,
130 nix::sys::stat::Mode::empty(),
131 ) {
132 Ok(rawfd) => {
133 /* manifest exists --> assume backup was successful */
134 /* close else this leaks! */
135 nix::unistd::close(rawfd)?;
136 }
137 Err(nix::Error::Sys(nix::errno::Errno::ENOENT)) => {
138 return Ok(());
139 }
140 Err(err) => {
141 bail!("last_successful_backup: unexpected error - {}", err);
142 }
143 }
144
6ef1b649 145 let timestamp = proxmox_time::parse_rfc3339(backup_time)?;
cded320e
TL
146 if let Some(last_timestamp) = last {
147 if timestamp > last_timestamp {
148 last = Some(timestamp);
149 }
150 } else {
151 last = Some(timestamp);
152 }
8f14e8fe 153
cded320e
TL
154 Ok(())
155 },
156 )?;
8f14e8fe
DM
157
158 Ok(last)
159 }
0ceb9753
FG
160
161 pub fn matches(&self, filter: &GroupFilter) -> bool {
162 match filter {
163 GroupFilter::Group(backup_group) => match BackupGroup::from_str(&backup_group) {
164 Ok(group) => &group == self,
165 Err(_) => false, // shouldn't happen if value is schema-checked
166 },
167 GroupFilter::BackupType(backup_type) => self.backup_type() == backup_type,
168 GroupFilter::Regex(regex) => regex.is_match(&self.to_string()),
169 }
170 }
b3483782
DM
171}
172
23f74c19
DM
173impl std::fmt::Display for BackupGroup {
174 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175 let backup_type = self.backup_type();
176 let id = self.backup_id();
177 write!(f, "{}/{}", backup_type, id)
178 }
179}
180
d6d3b353
DM
181impl std::str::FromStr for BackupGroup {
182 type Err = Error;
183
184 /// Parse a backup group path
185 ///
186 /// This parses strings like `vm/100".
187 fn from_str(path: &str) -> Result<Self, Self::Err> {
cded320e
TL
188 let cap = GROUP_PATH_REGEX
189 .captures(path)
d6d3b353
DM
190 .ok_or_else(|| format_err!("unable to parse backup group path '{}'", path))?;
191
192 Ok(Self {
193 backup_type: cap.get(1).unwrap().as_str().to_owned(),
194 backup_id: cap.get(2).unwrap().as_str().to_owned(),
195 })
196 }
197}
198
b3483782 199/// Uniquely identify a Backup (relative to data store)
d57474e0
DM
200///
201/// We also call this a backup snaphost.
8b5f72b1 202#[derive(Debug, Eq, PartialEq, Clone)]
b3483782
DM
203pub struct BackupDir {
204 /// Backup group
205 group: BackupGroup,
206 /// Backup timestamp
6a7be83e
DM
207 backup_time: i64,
208 // backup_time as rfc3339
cded320e 209 backup_time_string: String,
b3483782
DM
210}
211
212impl BackupDir {
6a7be83e 213 pub fn new<T, U>(backup_type: T, backup_id: U, backup_time: i64) -> Result<Self, Error>
391d3107
WB
214 where
215 T: Into<String>,
216 U: Into<String>,
217 {
e0e5b442 218 let group = BackupGroup::new(backup_type.into(), backup_id.into());
d09db6c2 219 BackupDir::with_group(group, backup_time)
b3483782 220 }
e0e5b442 221
cded320e
TL
222 pub fn with_rfc3339<T, U, V>(
223 backup_type: T,
224 backup_id: U,
225 backup_time_string: V,
226 ) -> Result<Self, Error>
bc871bd1
DM
227 where
228 T: Into<String>,
229 U: Into<String>,
230 V: Into<String>,
231 {
cded320e 232 let backup_time_string = backup_time_string.into();
6ef1b649 233 let backup_time = proxmox_time::parse_rfc3339(&backup_time_string)?;
bc871bd1 234 let group = BackupGroup::new(backup_type.into(), backup_id.into());
cded320e
TL
235 Ok(Self {
236 group,
237 backup_time,
238 backup_time_string,
239 })
bc871bd1
DM
240 }
241
d09db6c2 242 pub fn with_group(group: BackupGroup, backup_time: i64) -> Result<Self, Error> {
6a7be83e 243 let backup_time_string = Self::backup_time_to_string(backup_time)?;
cded320e
TL
244 Ok(Self {
245 group,
246 backup_time,
247 backup_time_string,
248 })
51a4f63f 249 }
b3483782
DM
250
251 pub fn group(&self) -> &BackupGroup {
252 &self.group
253 }
254
6a7be83e 255 pub fn backup_time(&self) -> i64 {
b3483782
DM
256 self.backup_time
257 }
258
6a7be83e
DM
259 pub fn backup_time_string(&self) -> &str {
260 &self.backup_time_string
261 }
262
cded320e 263 pub fn relative_path(&self) -> PathBuf {
b3483782
DM
264 let mut relative_path = self.group.group_path();
265
6a7be83e 266 relative_path.push(self.backup_time_string.clone());
b3483782
DM
267
268 relative_path
269 }
fa5d6977 270
92c5cf42
DC
271 pub fn protected_file(&self, mut path: PathBuf) -> PathBuf {
272 path.push(self.relative_path());
273 path.push(".protected");
274 path
275 }
276
277 pub fn is_protected(&self, base_path: PathBuf) -> bool {
278 let path = self.protected_file(base_path);
279 path.exists()
280 }
281
6a7be83e
DM
282 pub fn backup_time_to_string(backup_time: i64) -> Result<String, Error> {
283 // fixme: can this fail? (avoid unwrap)
6ef1b649 284 Ok(proxmox_time::epoch_to_rfc3339_utc(backup_time)?)
fa5d6977 285 }
b3483782 286}
d6d3b353 287
a67f7d0a
DM
288impl std::str::FromStr for BackupDir {
289 type Err = Error;
290
291 /// Parse a snapshot path
292 ///
293 /// This parses strings like `host/elsa/2020-06-15T05:18:33Z".
294 fn from_str(path: &str) -> Result<Self, Self::Err> {
cded320e
TL
295 let cap = SNAPSHOT_PATH_REGEX
296 .captures(path)
a67f7d0a
DM
297 .ok_or_else(|| format_err!("unable to parse backup snapshot path '{}'", path))?;
298
bc871bd1
DM
299 BackupDir::with_rfc3339(
300 cap.get(1).unwrap().as_str(),
301 cap.get(2).unwrap().as_str(),
302 cap.get(3).unwrap().as_str(),
303 )
a67f7d0a
DM
304 }
305}
b3483782 306
abdb9763
DC
307impl std::fmt::Display for BackupDir {
308 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
309 let backup_type = self.group.backup_type();
310 let id = self.group.backup_id();
6a7be83e 311 write!(f, "{}/{}/{}", backup_type, id, self.backup_time_string)
391d3107
WB
312 }
313}
314
d57474e0 315/// Detailed Backup Information, lists files inside a BackupDir
b02a52e3 316#[derive(Debug, Clone)]
b3483782
DM
317pub struct BackupInfo {
318 /// the backup directory
319 pub backup_dir: BackupDir,
320 /// List of data files
321 pub files: Vec<String>,
92c5cf42
DC
322 /// Protection Status
323 pub protected: bool,
b3483782
DM
324}
325
326impl BackupInfo {
38a6cdda
DM
327 pub fn new(base_path: &Path, backup_dir: BackupDir) -> Result<BackupInfo, Error> {
328 let mut path = base_path.to_owned();
329 path.push(backup_dir.relative_path());
330
331 let files = list_backup_files(libc::AT_FDCWD, &path)?;
92c5cf42 332 let protected = backup_dir.is_protected(base_path.to_owned());
38a6cdda 333
92c5cf42 334 Ok(BackupInfo { backup_dir, files, protected })
38a6cdda
DM
335 }
336
51a4f63f 337 /// Finds the latest backup inside a backup group
cded320e
TL
338 pub fn last_backup(
339 base_path: &Path,
340 group: &BackupGroup,
341 only_finished: bool,
342 ) -> Result<Option<BackupInfo>, Error> {
51a4f63f 343 let backups = group.list_backups(base_path)?;
cded320e
TL
344 Ok(backups
345 .into_iter()
4dbe1292
SR
346 .filter(|item| !only_finished || item.is_finished())
347 .max_by_key(|item| item.backup_dir.backup_time()))
51a4f63f
DM
348 }
349
b3483782 350 pub fn sort_list(list: &mut Vec<BackupInfo>, ascendending: bool) {
cded320e
TL
351 if ascendending {
352 // oldest first
b3483782 353 list.sort_unstable_by(|a, b| a.backup_dir.backup_time.cmp(&b.backup_dir.backup_time));
cded320e
TL
354 } else {
355 // newest first
b3483782
DM
356 list.sort_unstable_by(|a, b| b.backup_dir.backup_time.cmp(&a.backup_dir.backup_time));
357 }
358 }
359
58e99e13
DM
360 pub fn list_files(base_path: &Path, backup_dir: &BackupDir) -> Result<Vec<String>, Error> {
361 let mut path = base_path.to_owned();
362 path.push(backup_dir.relative_path());
363
c0977501 364 let files = list_backup_files(libc::AT_FDCWD, &path)?;
58e99e13
DM
365
366 Ok(files)
367 }
368
0d08fcee 369 pub fn list_backup_groups(base_path: &Path) -> Result<Vec<BackupGroup>, Error> {
11d89239 370 let mut list = Vec::new();
b3483782 371
770a36e5 372 pbs_tools::fs::scandir(
cded320e
TL
373 libc::AT_FDCWD,
374 base_path,
375 &BACKUP_TYPE_REGEX,
376 |l0_fd, backup_type, file_type| {
377 if file_type != nix::dir::Type::Directory {
378 return Ok(());
379 }
770a36e5 380 pbs_tools::fs::scandir(
cded320e
TL
381 l0_fd,
382 backup_type,
383 &BACKUP_ID_REGEX,
384 |_, backup_id, file_type| {
385 if file_type != nix::dir::Type::Directory {
386 return Ok(());
387 }
388
389 list.push(BackupGroup::new(backup_type, backup_id));
390
391 Ok(())
392 },
393 )
394 },
395 )?;
0d08fcee 396
b3483782
DM
397 Ok(list)
398 }
c9756b40
SR
399
400 pub fn is_finished(&self) -> bool {
401 // backup is considered unfinished if there is no manifest
cded320e
TL
402 self.files
403 .iter()
a5951b4f 404 .any(|name| name == MANIFEST_BLOB_NAME)
c9756b40 405 }
b3483782 406}
c0977501 407
cded320e
TL
408fn list_backup_files<P: ?Sized + nix::NixPath>(
409 dirfd: RawFd,
410 path: &P,
411) -> Result<Vec<String>, Error> {
c0977501
DM
412 let mut files = vec![];
413
770a36e5 414 pbs_tools::fs::scandir(dirfd, path, &BACKUP_FILE_REGEX, |_, filename, file_type| {
cded320e
TL
415 if file_type != nix::dir::Type::File {
416 return Ok(());
417 }
c0977501
DM
418 files.push(filename.to_owned());
419 Ok(())
420 })?;
421
422 Ok(files)
423}