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