diff --git a/src/backup/backup_info.rs b/src/backup/backup_info.rs index 5ff1a6f8..b0f6e31c 100644 --- a/src/backup/backup_info.rs +++ b/src/backup/backup_info.rs @@ -3,17 +3,29 @@ use crate::tools; use anyhow::{bail, format_err, Error}; use std::os::unix::io::RawFd; -use std::path::{PathBuf, Path}; +use std::path::{Path, PathBuf}; use proxmox::const_regex; use super::manifest::MANIFEST_BLOB_NAME; -macro_rules! BACKUP_ID_RE { () => (r"[A-Za-z0-9_][A-Za-z0-9._\-]*") } -macro_rules! BACKUP_TYPE_RE { () => (r"(?:host|vm|ct)") } -macro_rules! BACKUP_TIME_RE { () => (r"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z") } +macro_rules! BACKUP_ID_RE { + () => { + r"[A-Za-z0-9_][A-Za-z0-9._\-]*" + }; +} +macro_rules! BACKUP_TYPE_RE { + () => { + r"(?:host|vm|ct)" + }; +} +macro_rules! BACKUP_TIME_RE { + () => { + r"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z" + }; +} -const_regex!{ +const_regex! { BACKUP_FILE_REGEX = r"^.*\.([fd]idx|blob)$"; BACKUP_TYPE_REGEX = concat!(r"^(", BACKUP_TYPE_RE!(), r")$"); @@ -38,7 +50,6 @@ pub struct BackupGroup { } impl std::cmp::Ord for BackupGroup { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { let type_order = self.backup_type.cmp(&other.backup_type); if type_order != std::cmp::Ordering::Equal { @@ -51,7 +62,7 @@ impl std::cmp::Ord for BackupGroup { (Ok(id_self), Ok(id_other)) => id_self.cmp(&id_other), (Ok(_), Err(_)) => std::cmp::Ordering::Less, (Err(_), Ok(_)) => std::cmp::Ordering::Greater, - _ => self.backup_id.cmp(&other.backup_id), + _ => self.backup_id.cmp(&other.backup_id), } } } @@ -63,9 +74,11 @@ impl std::cmp::PartialOrd for BackupGroup { } impl BackupGroup { - pub fn new, U: Into>(backup_type: T, backup_id: U) -> Self { - Self { backup_type: backup_type.into(), backup_id: backup_id.into() } + Self { + backup_type: backup_type.into(), + backup_id: backup_id.into(), + } } pub fn backup_type(&self) -> &str { @@ -76,8 +89,7 @@ impl BackupGroup { &self.backup_id } - pub fn group_path(&self) -> PathBuf { - + pub fn group_path(&self) -> PathBuf { let mut relative_path = PathBuf::new(); relative_path.push(&self.backup_type); @@ -88,60 +100,82 @@ impl BackupGroup { } pub fn list_backups(&self, base_path: &Path) -> Result, Error> { - let mut list = vec![]; let mut path = base_path.to_owned(); path.push(self.group_path()); - tools::scandir(libc::AT_FDCWD, &path, &BACKUP_DATE_REGEX, |l2_fd, backup_time, file_type| { - if file_type != nix::dir::Type::Directory { return Ok(()); } + tools::scandir( + libc::AT_FDCWD, + &path, + &BACKUP_DATE_REGEX, + |l2_fd, backup_time, file_type| { + if file_type != nix::dir::Type::Directory { + return Ok(()); + } - let backup_dir = BackupDir::with_rfc3339(&self.backup_type, &self.backup_id, backup_time)?; - let files = list_backup_files(l2_fd, backup_time)?; + let backup_dir = + BackupDir::with_rfc3339(&self.backup_type, &self.backup_id, backup_time)?; + let files = list_backup_files(l2_fd, backup_time)?; - list.push(BackupInfo { backup_dir, files }); + list.push(BackupInfo { backup_dir, files }); - Ok(()) - })?; + Ok(()) + }, + )?; Ok(list) } - pub fn last_successful_backup(&self, base_path: &Path) -> Result, Error> { - + pub fn last_successful_backup(&self, base_path: &Path) -> Result, Error> { let mut last = None; let mut path = base_path.to_owned(); path.push(self.group_path()); - tools::scandir(libc::AT_FDCWD, &path, &BACKUP_DATE_REGEX, |l2_fd, backup_time, file_type| { - if file_type != nix::dir::Type::Directory { return Ok(()); } - - let mut manifest_path = PathBuf::from(backup_time); - manifest_path.push(MANIFEST_BLOB_NAME); - - use nix::fcntl::{openat, OFlag}; - match openat(l2_fd, &manifest_path, OFlag::O_RDONLY, nix::sys::stat::Mode::empty()) { - Ok(rawfd) => { - /* manifest exists --> assume backup was successful */ - /* close else this leaks! */ - nix::unistd::close(rawfd)?; - }, - Err(nix::Error::Sys(nix::errno::Errno::ENOENT)) => { return Ok(()); } - Err(err) => { - bail!("last_successful_backup: unexpected error - {}", err); + tools::scandir( + libc::AT_FDCWD, + &path, + &BACKUP_DATE_REGEX, + |l2_fd, backup_time, file_type| { + if file_type != nix::dir::Type::Directory { + return Ok(()); } - } - let timestamp = proxmox::tools::time::parse_rfc3339(backup_time)?; - if let Some(last_timestamp) = last { - if timestamp > last_timestamp { last = Some(timestamp); } - } else { - last = Some(timestamp); - } + let mut manifest_path = PathBuf::from(backup_time); + manifest_path.push(MANIFEST_BLOB_NAME); - Ok(()) - })?; + use nix::fcntl::{openat, OFlag}; + match openat( + l2_fd, + &manifest_path, + OFlag::O_RDONLY, + nix::sys::stat::Mode::empty(), + ) { + Ok(rawfd) => { + /* manifest exists --> assume backup was successful */ + /* close else this leaks! */ + nix::unistd::close(rawfd)?; + } + Err(nix::Error::Sys(nix::errno::Errno::ENOENT)) => { + return Ok(()); + } + Err(err) => { + bail!("last_successful_backup: unexpected error - {}", err); + } + } + + let timestamp = proxmox::tools::time::parse_rfc3339(backup_time)?; + if let Some(last_timestamp) = last { + if timestamp > last_timestamp { + last = Some(timestamp); + } + } else { + last = Some(timestamp); + } + + Ok(()) + }, + )?; Ok(last) } @@ -162,7 +196,8 @@ impl std::str::FromStr for BackupGroup { /// /// This parses strings like `vm/100". fn from_str(path: &str) -> Result { - let cap = GROUP_PATH_REGEX.captures(path) + let cap = GROUP_PATH_REGEX + .captures(path) .ok_or_else(|| format_err!("unable to parse backup group path '{}'", path))?; Ok(Self { @@ -182,11 +217,10 @@ pub struct BackupDir { /// Backup timestamp backup_time: i64, // backup_time as rfc3339 - backup_time_string: String + backup_time_string: String, } impl BackupDir { - pub fn new(backup_type: T, backup_id: U, backup_time: i64) -> Result where T: Into, @@ -196,21 +230,33 @@ impl BackupDir { BackupDir::with_group(group, backup_time) } - pub fn with_rfc3339(backup_type: T, backup_id: U, backup_time_string: V) -> Result + pub fn with_rfc3339( + backup_type: T, + backup_id: U, + backup_time_string: V, + ) -> Result where T: Into, U: Into, V: Into, { - let backup_time_string = backup_time_string.into(); + let backup_time_string = backup_time_string.into(); let backup_time = proxmox::tools::time::parse_rfc3339(&backup_time_string)?; let group = BackupGroup::new(backup_type.into(), backup_id.into()); - Ok(Self { group, backup_time, backup_time_string }) + Ok(Self { + group, + backup_time, + backup_time_string, + }) } pub fn with_group(group: BackupGroup, backup_time: i64) -> Result { let backup_time_string = Self::backup_time_to_string(backup_time)?; - Ok(Self { group, backup_time, backup_time_string }) + Ok(Self { + group, + backup_time, + backup_time_string, + }) } pub fn group(&self) -> &BackupGroup { @@ -225,8 +271,7 @@ impl BackupDir { &self.backup_time_string } - pub fn relative_path(&self) -> PathBuf { - + pub fn relative_path(&self) -> PathBuf { let mut relative_path = self.group.group_path(); relative_path.push(self.backup_time_string.clone()); @@ -247,7 +292,8 @@ impl std::str::FromStr for BackupDir { /// /// This parses strings like `host/elsa/2020-06-15T05:18:33Z". fn from_str(path: &str) -> Result { - let cap = SNAPSHOT_PATH_REGEX.captures(path) + let cap = SNAPSHOT_PATH_REGEX + .captures(path) .ok_or_else(|| format_err!("unable to parse backup snapshot path '{}'", path))?; BackupDir::with_rfc3339( @@ -276,7 +322,6 @@ pub struct BackupInfo { } impl BackupInfo { - pub fn new(base_path: &Path, backup_dir: BackupDir) -> Result { let mut path = base_path.to_owned(); path.push(backup_dir.relative_path()); @@ -287,19 +332,24 @@ impl BackupInfo { } /// Finds the latest backup inside a backup group - pub fn last_backup(base_path: &Path, group: &BackupGroup, only_finished: bool) - -> Result, Error> - { + pub fn last_backup( + base_path: &Path, + group: &BackupGroup, + only_finished: bool, + ) -> Result, Error> { let backups = group.list_backups(base_path)?; - Ok(backups.into_iter() + Ok(backups + .into_iter() .filter(|item| !only_finished || item.is_finished()) .max_by_key(|item| item.backup_dir.backup_time())) } pub fn sort_list(list: &mut Vec, ascendending: bool) { - if ascendending { // oldest first + if ascendending { + // oldest first list.sort_unstable_by(|a, b| a.backup_dir.backup_time.cmp(&b.backup_dir.backup_time)); - } else { // newest first + } else { + // newest first list.sort_unstable_by(|a, b| b.backup_dir.backup_time.cmp(&a.backup_dir.backup_time)); } } @@ -316,31 +366,52 @@ impl BackupInfo { pub fn list_backup_groups(base_path: &Path) -> Result, Error> { let mut list = Vec::new(); - tools::scandir(libc::AT_FDCWD, base_path, &BACKUP_TYPE_REGEX, |l0_fd, backup_type, file_type| { - if file_type != nix::dir::Type::Directory { return Ok(()); } - tools::scandir(l0_fd, backup_type, &BACKUP_ID_REGEX, |_, backup_id, file_type| { - if file_type != nix::dir::Type::Directory { return Ok(()); } + tools::scandir( + libc::AT_FDCWD, + base_path, + &BACKUP_TYPE_REGEX, + |l0_fd, backup_type, file_type| { + if file_type != nix::dir::Type::Directory { + return Ok(()); + } + tools::scandir( + l0_fd, + backup_type, + &BACKUP_ID_REGEX, + |_, backup_id, file_type| { + if file_type != nix::dir::Type::Directory { + return Ok(()); + } - list.push(BackupGroup::new(backup_type, backup_id)); + list.push(BackupGroup::new(backup_type, backup_id)); - Ok(()) - }) - })?; + Ok(()) + }, + ) + }, + )?; Ok(list) } pub fn is_finished(&self) -> bool { // backup is considered unfinished if there is no manifest - self.files.iter().any(|name| name == super::MANIFEST_BLOB_NAME) + self.files + .iter() + .any(|name| name == super::MANIFEST_BLOB_NAME) } } -fn list_backup_files(dirfd: RawFd, path: &P) -> Result, Error> { +fn list_backup_files( + dirfd: RawFd, + path: &P, +) -> Result, Error> { let mut files = vec![]; tools::scandir(dirfd, path, &BACKUP_FILE_REGEX, |_, filename, file_type| { - if file_type != nix::dir::Type::File { return Ok(()); } + if file_type != nix::dir::Type::File { + return Ok(()); + } files.push(filename.to_owned()); Ok(()) })?;