3 use anyhow
::{bail, format_err, Error}
;
5 use std
::os
::unix
::io
::RawFd
;
8 use std
::time
::Duration
;
9 use chrono
::{DateTime, TimeZone, SecondsFormat, Utc}
;
11 use std
::path
::{PathBuf, Path}
;
12 use lazy_static
::lazy_static
;
14 use proxmox
::sys
::error
::SysError
;
16 use super::manifest
::MANIFEST_BLOB_NAME
;
18 macro_rules
! BACKUP_ID_RE { () => (r"[A-Za-z0-9][A-Za-z0-9_-]+") }
19 macro_rules
! BACKUP_TYPE_RE { () => (r"(?:host|vm|ct)") }
20 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
") }
23 static ref BACKUP_FILE_REGEX: Regex = Regex::new(
24 r"^
.*\.([fd
]idx
|blob
)$
").unwrap();
26 static ref BACKUP_TYPE_REGEX: Regex = Regex::new(
27 concat!(r"^
(", BACKUP_TYPE_RE!(), r")$
")).unwrap();
29 static ref BACKUP_ID_REGEX: Regex = Regex::new(
30 concat!(r"^
", BACKUP_ID_RE!(), r"$
")).unwrap();
32 static ref BACKUP_DATE_REGEX: Regex = Regex::new(
33 concat!(r"^
", BACKUP_TIME_RE!() ,r"$
")).unwrap();
35 static ref GROUP_PATH_REGEX: Regex = Regex::new(
36 concat!(r"^
(", BACKUP_TYPE_RE!(), ")/(", BACKUP_ID_RE!(), r")$
")).unwrap();
38 static ref SNAPSHOT_PATH_REGEX: Regex = Regex::new(
39 concat!(r"^
(", BACKUP_TYPE_RE!(), ")/(", BACKUP_ID_RE!(), ")/(", BACKUP_TIME_RE!(), r")$
")).unwrap();
43 /// Opaque type releasing the corresponding flock when dropped
44 pub type BackupGroupGuard = Dir;
46 /// BackupGroup is a directory containing a list of BackupDir
47 #[derive(Debug, Eq, PartialEq, Hash, Clone)]
48 pub struct BackupGroup {
51 /// Unique (for this type) ID
57 pub fn new<T: Into<String>, U: Into<String>>(backup_type: T, backup_id: U) -> Self {
58 Self { backup_type: backup_type.into(), backup_id: backup_id.into() }
61 pub fn backup_type(&self) -> &str {
65 pub fn backup_id(&self) -> &str {
69 pub fn group_path(&self) -> PathBuf {
71 let mut relative_path = PathBuf::new();
73 relative_path.push(&self.backup_type);
75 relative_path.push(&self.backup_id);
80 pub fn list_backups(&self, base_path: &Path) -> Result<Vec<BackupInfo>, Error> {
82 let mut list = vec![];
84 let mut path = base_path.to_owned();
85 path.push(self.group_path());
87 tools::scandir(libc::AT_FDCWD, &path, &BACKUP_DATE_REGEX, |l2_fd, backup_time, file_type| {
88 if file_type != nix::dir::Type::Directory { return Ok(()); }
90 let dt = backup_time.parse::<DateTime<Utc>>()?;
91 let backup_dir = BackupDir::new(self.backup_type.clone(), self.backup_id.clone(), dt.timestamp());
92 let files = list_backup_files(l2_fd, backup_time)?;
94 list.push(BackupInfo { backup_dir, files });
101 pub fn last_successful_backup(&self, base_path: &Path) -> Result<Option<DateTime<Utc>>, Error> {
105 let mut path = base_path.to_owned();
106 path.push(self.group_path());
108 tools::scandir(libc::AT_FDCWD, &path, &BACKUP_DATE_REGEX, |l2_fd, backup_time, file_type| {
109 if file_type != nix::dir::Type::Directory { return Ok(()); }
111 let mut manifest_path = PathBuf::from(backup_time);
112 manifest_path.push(MANIFEST_BLOB_NAME);
114 use nix::fcntl::{openat, OFlag};
115 match openat(l2_fd, &manifest_path, OFlag::O_RDONLY, nix::sys::stat::Mode::empty()) {
117 /* manifest exists --> assume backup was successful */
118 /* close else this leaks! */
119 nix::unistd::close(rawfd)?;
121 Err(nix::Error::Sys(nix::errno::Errno::ENOENT)) => { return Ok(()); }
123 bail!("last_successful_backup
: unexpected error
- {}
", err);
127 let dt = backup_time.parse::<DateTime<Utc>>()?;
128 if let Some(last_dt) = last {
129 if dt > last_dt { last = Some(dt); }
140 pub fn lock(&self, base_path: &Path) -> Result<BackupGroupGuard, Error> {
141 use nix::fcntl::OFlag;
142 use nix::sys::stat::Mode;
144 let mut path = base_path.to_owned();
145 path.push(self.group_path());
147 let mut handle = Dir::open(&path, OFlag::O_RDONLY, Mode::empty())
150 "unable to open backup group directory {:?}
for locking
- {}
",
156 // acquire in non-blocking mode, no point in waiting here since other
157 // backups could still take a very long time
158 tools::lock_file(&mut handle, true, Some(Duration::from_nanos(0)))
161 "unable to acquire lock on backup group {:?}
- {}
",
163 if err.would_block() {
164 String::from("another backup is already running
")
174 pub fn list_groups(base_path: &Path) -> Result<Vec<BackupGroup>, Error> {
175 let mut list = Vec::new();
177 tools::scandir(libc::AT_FDCWD, base_path, &BACKUP_TYPE_REGEX, |l0_fd, backup_type, file_type| {
178 if file_type != nix::dir::Type::Directory { return Ok(()); }
179 tools::scandir(l0_fd, backup_type, &BACKUP_ID_REGEX, |_l1_fd, backup_id, file_type| {
180 if file_type != nix::dir::Type::Directory { return Ok(()); }
181 list.push(BackupGroup::new(backup_type, backup_id));
189 impl std::fmt::Display for BackupGroup {
190 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191 let backup_type = self.backup_type();
192 let id = self.backup_id();
193 write!(f, "{}
/{}
", backup_type, id)
197 impl std::str::FromStr for BackupGroup {
200 /// Parse a backup group path
202 /// This parses strings like `vm/100".
203 fn from_str(path
: &str) -> Result
<Self, Self::Err
> {
204 let cap
= GROUP_PATH_REGEX
.captures(path
)
205 .ok_or_else(|| format_err
!("unable to parse backup group path '{}'", path
))?
;
208 backup_type
: cap
.get(1).unwrap().as_str().to_owned(),
209 backup_id
: cap
.get(2).unwrap().as_str().to_owned(),
214 /// Uniquely identify a Backup (relative to data store)
216 /// We also call this a backup snaphost.
217 #[derive(Debug, Eq, PartialEq, Hash, Clone)]
218 pub struct BackupDir
{
222 backup_time
: DateTime
<Utc
>,
227 pub fn new
<T
, U
>(backup_type
: T
, backup_id
: U
, timestamp
: i64) -> Self
232 // Note: makes sure that nanoseconds is 0
234 group
: BackupGroup
::new(backup_type
.into(), backup_id
.into()),
235 backup_time
: Utc
.timestamp(timestamp
, 0),
238 pub fn new_with_group(group
: BackupGroup
, timestamp
: i64) -> Self {
239 Self { group, backup_time: Utc.timestamp(timestamp, 0) }
242 pub fn group(&self) -> &BackupGroup
{
246 pub fn backup_time(&self) -> DateTime
<Utc
> {
250 pub fn relative_path(&self) -> PathBuf
{
252 let mut relative_path
= self.group
.group_path();
254 relative_path
.push(Self::backup_time_to_string(self.backup_time
));
259 pub fn backup_time_to_string(backup_time
: DateTime
<Utc
>) -> String
{
260 backup_time
.to_rfc3339_opts(SecondsFormat
::Secs
, true)
264 impl std
::str::FromStr
for BackupDir
{
267 /// Parse a snapshot path
269 /// This parses strings like `host/elsa/2020-06-15T05:18:33Z".
270 fn from_str(path
: &str) -> Result
<Self, Self::Err
> {
271 let cap
= SNAPSHOT_PATH_REGEX
.captures(path
)
272 .ok_or_else(|| format_err
!("unable to parse backup snapshot path '{}'", path
))?
;
274 let group
= BackupGroup
::new(cap
.get(1).unwrap().as_str(), cap
.get(2).unwrap().as_str());
275 let backup_time
= cap
.get(3).unwrap().as_str().parse
::<DateTime
<Utc
>>()?
;
276 Ok(BackupDir
::from((group
, backup_time
.timestamp())))
280 impl std
::fmt
::Display
for BackupDir
{
281 fn fmt(&self, f
: &mut std
::fmt
::Formatter
<'_
>) -> std
::fmt
::Result
{
282 let backup_type
= self.group
.backup_type();
283 let id
= self.group
.backup_id();
284 let time
= Self::backup_time_to_string(self.backup_time
);
285 write
!(f
, "{}/{}/{}", backup_type
, id
, time
)
289 impl From
<(BackupGroup
, i64)> for BackupDir
{
290 fn from((group
, timestamp
): (BackupGroup
, i64)) -> Self {
291 Self { group, backup_time: Utc.timestamp(timestamp, 0) }
295 /// Detailed Backup Information, lists files inside a BackupDir
296 #[derive(Debug, Clone)]
297 pub struct BackupInfo
{
298 /// the backup directory
299 pub backup_dir
: BackupDir
,
300 /// List of data files
301 pub files
: Vec
<String
>,
306 pub fn new(base_path
: &Path
, backup_dir
: BackupDir
) -> Result
<BackupInfo
, Error
> {
307 let mut path
= base_path
.to_owned();
308 path
.push(backup_dir
.relative_path());
310 let files
= list_backup_files(libc
::AT_FDCWD
, &path
)?
;
312 Ok(BackupInfo { backup_dir, files }
)
315 /// Finds the latest backup inside a backup group
316 pub fn last_backup(base_path
: &Path
, group
: &BackupGroup
) -> Result
<Option
<BackupInfo
>, Error
> {
317 let backups
= group
.list_backups(base_path
)?
;
318 Ok(backups
.into_iter().max_by_key(|item
| item
.backup_dir
.backup_time()))
321 pub fn sort_list(list
: &mut Vec
<BackupInfo
>, ascendending
: bool
) {
322 if ascendending
{ // oldest first
323 list
.sort_unstable_by(|a
, b
| a
.backup_dir
.backup_time
.cmp(&b
.backup_dir
.backup_time
));
324 } else { // newest first
325 list
.sort_unstable_by(|a
, b
| b
.backup_dir
.backup_time
.cmp(&a
.backup_dir
.backup_time
));
329 pub fn list_files(base_path
: &Path
, backup_dir
: &BackupDir
) -> Result
<Vec
<String
>, Error
> {
330 let mut path
= base_path
.to_owned();
331 path
.push(backup_dir
.relative_path());
333 let files
= list_backup_files(libc
::AT_FDCWD
, &path
)?
;
338 pub fn list_backups(base_path
: &Path
) -> Result
<Vec
<BackupInfo
>, Error
> {
339 let mut list
= Vec
::new();
341 tools
::scandir(libc
::AT_FDCWD
, base_path
, &BACKUP_TYPE_REGEX
, |l0_fd
, backup_type
, file_type
| {
342 if file_type
!= nix
::dir
::Type
::Directory { return Ok(()); }
343 tools
::scandir(l0_fd
, backup_type
, &BACKUP_ID_REGEX
, |l1_fd
, backup_id
, file_type
| {
344 if file_type
!= nix
::dir
::Type
::Directory { return Ok(()); }
345 tools
::scandir(l1_fd
, backup_id
, &BACKUP_DATE_REGEX
, |l2_fd
, backup_time
, file_type
| {
346 if file_type
!= nix
::dir
::Type
::Directory { return Ok(()); }
348 let dt
= backup_time
.parse
::<DateTime
<Utc
>>()?
;
349 let backup_dir
= BackupDir
::new(backup_type
, backup_id
, dt
.timestamp());
351 let files
= list_backup_files(l2_fd
, backup_time
)?
;
353 list
.push(BackupInfo { backup_dir, files }
);
362 pub fn is_finished(&self) -> bool
{
363 // backup is considered unfinished if there is no manifest
364 self.files
.iter().any(|name
| name
== super::MANIFEST_BLOB_NAME
)
368 fn list_backup_files
<P
: ?Sized
+ nix
::NixPath
>(dirfd
: RawFd
, path
: &P
) -> Result
<Vec
<String
>, Error
> {
369 let mut files
= vec
![];
371 tools
::scandir(dirfd
, path
, &BACKUP_FILE_REGEX
, |_
, filename
, file_type
| {
372 if file_type
!= nix
::dir
::Type
::File { return Ok(()); }
373 files
.push(filename
.to_owned());