]> git.proxmox.com Git - proxmox-backup.git/blob - src/backup/backup_info.rs
lock_file: return std::io::Error
[proxmox-backup.git] / src / backup / backup_info.rs
1 use crate::tools;
2
3 use anyhow::{bail, format_err, Error};
4 use regex::Regex;
5 use std::os::unix::io::RawFd;
6 use nix::dir::Dir;
7
8 use std::time::Duration;
9 use chrono::{DateTime, TimeZone, SecondsFormat, Utc};
10
11 use std::path::{PathBuf, Path};
12 use lazy_static::lazy_static;
13
14 use proxmox::sys::error::SysError;
15
16 use super::manifest::MANIFEST_BLOB_NAME;
17
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") }
21
22 lazy_static!{
23 static ref BACKUP_FILE_REGEX: Regex = Regex::new(
24 r"^.*\.([fd]idx|blob)$").unwrap();
25
26 static ref BACKUP_TYPE_REGEX: Regex = Regex::new(
27 concat!(r"^(", BACKUP_TYPE_RE!(), r")$")).unwrap();
28
29 static ref BACKUP_ID_REGEX: Regex = Regex::new(
30 concat!(r"^", BACKUP_ID_RE!(), r"$")).unwrap();
31
32 static ref BACKUP_DATE_REGEX: Regex = Regex::new(
33 concat!(r"^", BACKUP_TIME_RE!() ,r"$")).unwrap();
34
35 static ref GROUP_PATH_REGEX: Regex = Regex::new(
36 concat!(r"^(", BACKUP_TYPE_RE!(), ")/(", BACKUP_ID_RE!(), r")$")).unwrap();
37
38 static ref SNAPSHOT_PATH_REGEX: Regex = Regex::new(
39 concat!(r"^(", BACKUP_TYPE_RE!(), ")/(", BACKUP_ID_RE!(), ")/(", BACKUP_TIME_RE!(), r")$")).unwrap();
40
41 }
42
43 /// Opaque type releasing the corresponding flock when dropped
44 pub type BackupGroupGuard = Dir;
45
46 /// BackupGroup is a directory containing a list of BackupDir
47 #[derive(Debug, Eq, PartialEq, Hash, Clone)]
48 pub struct BackupGroup {
49 /// Type of backup
50 backup_type: String,
51 /// Unique (for this type) ID
52 backup_id: String,
53 }
54
55 impl BackupGroup {
56
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() }
59 }
60
61 pub fn backup_type(&self) -> &str {
62 &self.backup_type
63 }
64
65 pub fn backup_id(&self) -> &str {
66 &self.backup_id
67 }
68
69 pub fn group_path(&self) -> PathBuf {
70
71 let mut relative_path = PathBuf::new();
72
73 relative_path.push(&self.backup_type);
74
75 relative_path.push(&self.backup_id);
76
77 relative_path
78 }
79
80 pub fn list_backups(&self, base_path: &Path) -> Result<Vec<BackupInfo>, Error> {
81
82 let mut list = vec![];
83
84 let mut path = base_path.to_owned();
85 path.push(self.group_path());
86
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(()); }
89
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)?;
93
94 list.push(BackupInfo { backup_dir, files });
95
96 Ok(())
97 })?;
98 Ok(list)
99 }
100
101 pub fn last_successful_backup(&self, base_path: &Path) -> Result<Option<DateTime<Utc>>, Error> {
102
103 let mut last = None;
104
105 let mut path = base_path.to_owned();
106 path.push(self.group_path());
107
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(()); }
110
111 let mut manifest_path = PathBuf::from(backup_time);
112 manifest_path.push(MANIFEST_BLOB_NAME);
113
114 use nix::fcntl::{openat, OFlag};
115 match openat(l2_fd, &manifest_path, OFlag::O_RDONLY, nix::sys::stat::Mode::empty()) {
116 Ok(rawfd) => {
117 /* manifest exists --> assume backup was successful */
118 /* close else this leaks! */
119 nix::unistd::close(rawfd)?;
120 },
121 Err(nix::Error::Sys(nix::errno::Errno::ENOENT)) => { return Ok(()); }
122 Err(err) => {
123 bail!("last_successful_backup: unexpected error - {}", err);
124 }
125 }
126
127 let dt = backup_time.parse::<DateTime<Utc>>()?;
128 if let Some(last_dt) = last {
129 if dt > last_dt { last = Some(dt); }
130 } else {
131 last = Some(dt);
132 }
133
134 Ok(())
135 })?;
136
137 Ok(last)
138 }
139
140 pub fn lock(&self, base_path: &Path) -> Result<BackupGroupGuard, Error> {
141 use nix::fcntl::OFlag;
142 use nix::sys::stat::Mode;
143
144 let mut path = base_path.to_owned();
145 path.push(self.group_path());
146
147 let mut handle = Dir::open(&path, OFlag::O_RDONLY, Mode::empty())
148 .map_err(|err| {
149 format_err!(
150 "unable to open backup group directory {:?} for locking - {}",
151 self.group_path(),
152 err,
153 )
154 })?;
155
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)))
159 .map_err(|err| {
160 format_err!(
161 "unable to acquire lock on backup group {:?} - {}",
162 self.group_path(),
163 if err.would_block() {
164 String::from("another backup is already running")
165 } else {
166 err.to_string()
167 }
168 )
169 })?;
170
171 Ok(handle)
172 }
173
174 pub fn list_groups(base_path: &Path) -> Result<Vec<BackupGroup>, Error> {
175 let mut list = Vec::new();
176
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));
182 Ok(())
183 })
184 })?;
185 Ok(list)
186 }
187 }
188
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)
194 }
195 }
196
197 impl std::str::FromStr for BackupGroup {
198 type Err = Error;
199
200 /// Parse a backup group path
201 ///
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))?;
206
207 Ok(Self {
208 backup_type: cap.get(1).unwrap().as_str().to_owned(),
209 backup_id: cap.get(2).unwrap().as_str().to_owned(),
210 })
211 }
212 }
213
214 /// Uniquely identify a Backup (relative to data store)
215 ///
216 /// We also call this a backup snaphost.
217 #[derive(Debug, Eq, PartialEq, Hash, Clone)]
218 pub struct BackupDir {
219 /// Backup group
220 group: BackupGroup,
221 /// Backup timestamp
222 backup_time: DateTime<Utc>,
223 }
224
225 impl BackupDir {
226
227 pub fn new<T, U>(backup_type: T, backup_id: U, timestamp: i64) -> Self
228 where
229 T: Into<String>,
230 U: Into<String>,
231 {
232 // Note: makes sure that nanoseconds is 0
233 Self {
234 group: BackupGroup::new(backup_type.into(), backup_id.into()),
235 backup_time: Utc.timestamp(timestamp, 0),
236 }
237 }
238 pub fn new_with_group(group: BackupGroup, timestamp: i64) -> Self {
239 Self { group, backup_time: Utc.timestamp(timestamp, 0) }
240 }
241
242 pub fn group(&self) -> &BackupGroup {
243 &self.group
244 }
245
246 pub fn backup_time(&self) -> DateTime<Utc> {
247 self.backup_time
248 }
249
250 pub fn relative_path(&self) -> PathBuf {
251
252 let mut relative_path = self.group.group_path();
253
254 relative_path.push(Self::backup_time_to_string(self.backup_time));
255
256 relative_path
257 }
258
259 pub fn backup_time_to_string(backup_time: DateTime<Utc>) -> String {
260 backup_time.to_rfc3339_opts(SecondsFormat::Secs, true)
261 }
262 }
263
264 impl std::str::FromStr for BackupDir {
265 type Err = Error;
266
267 /// Parse a snapshot path
268 ///
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))?;
273
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())))
277 }
278 }
279
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)
286 }
287 }
288
289 impl From<(BackupGroup, i64)> for BackupDir {
290 fn from((group, timestamp): (BackupGroup, i64)) -> Self {
291 Self { group, backup_time: Utc.timestamp(timestamp, 0) }
292 }
293 }
294
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>,
302 }
303
304 impl BackupInfo {
305
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());
309
310 let files = list_backup_files(libc::AT_FDCWD, &path)?;
311
312 Ok(BackupInfo { backup_dir, files })
313 }
314
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()))
319 }
320
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));
326 }
327 }
328
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());
332
333 let files = list_backup_files(libc::AT_FDCWD, &path)?;
334
335 Ok(files)
336 }
337
338 pub fn list_backups(base_path: &Path) -> Result<Vec<BackupInfo>, Error> {
339 let mut list = Vec::new();
340
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(()); }
347
348 let dt = backup_time.parse::<DateTime<Utc>>()?;
349 let backup_dir = BackupDir::new(backup_type, backup_id, dt.timestamp());
350
351 let files = list_backup_files(l2_fd, backup_time)?;
352
353 list.push(BackupInfo { backup_dir, files });
354
355 Ok(())
356 })
357 })
358 })?;
359 Ok(list)
360 }
361
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)
365 }
366 }
367
368 fn list_backup_files<P: ?Sized + nix::NixPath>(dirfd: RawFd, path: &P) -> Result<Vec<String>, Error> {
369 let mut files = vec![];
370
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());
374 Ok(())
375 })?;
376
377 Ok(files)
378 }