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