]> git.proxmox.com Git - proxmox-backup.git/blame - pbs-datastore/src/datastore.rs
datastore: add single-level and recursive namespace iterators
[proxmox-backup.git] / pbs-datastore / src / datastore.rs
CommitLineData
42c2b5be
TL
1use std::collections::{HashMap, HashSet};
2use std::convert::TryFrom;
54552dda 3use std::io::{self, Write};
367f002e 4use std::path::{Path, PathBuf};
cb4b721c 5use std::str::FromStr;
42c2b5be 6use std::sync::{Arc, Mutex};
367f002e 7
f7d4e4b5 8use anyhow::{bail, format_err, Error};
2c32fdde 9use lazy_static::lazy_static;
e4439025 10
fef61684
DC
11use proxmox_schema::ApiType;
12
42c2b5be
TL
13use proxmox_sys::fs::{file_read_optional_string, replace_file, CreateOptions};
14use proxmox_sys::fs::{lock_dir_noblock, DirLockGuard};
d5790a9f 15use proxmox_sys::process_locker::ProcessLockSharedGuard;
25877d05 16use proxmox_sys::WorkerTaskContext;
d5790a9f 17use proxmox_sys::{task_log, task_warn};
529de6c7 18
fef61684 19use pbs_api_types::{
8c74349b
WB
20 Authid, BackupNamespace, BackupType, ChunkOrder, DataStoreConfig, DatastoreTuning,
21 GarbageCollectionStatus, HumanByte, Operation, BACKUP_DATE_REGEX, BACKUP_ID_REGEX, UPID,
fef61684 22};
5c9c23b6 23use pbs_config::ConfigVersionCache;
529de6c7 24
42c2b5be 25use crate::backup_info::{BackupDir, BackupGroup};
6d5d305d
DM
26use crate::chunk_store::ChunkStore;
27use crate::dynamic_index::{DynamicIndexReader, DynamicIndexWriter};
28use crate::fixed_index::{FixedIndexReader, FixedIndexWriter};
29use crate::index::IndexFile;
30use crate::manifest::{
42c2b5be 31 archive_type, ArchiveType, BackupManifest, CLIENT_LOG_BLOB_NAME, MANIFEST_BLOB_NAME,
6d5d305d 32};
4bc84a65 33use crate::task_tracking::update_active_operations;
42c2b5be 34use crate::DataBlob;
6d5d305d 35
367f002e 36lazy_static! {
42c2b5be
TL
37 static ref DATASTORE_MAP: Mutex<HashMap<String, Arc<DataStoreImpl>>> =
38 Mutex::new(HashMap::new());
b3483782 39}
ff3d3100 40
9751ef4b
DC
41/// checks if auth_id is owner, or, if owner is a token, if
42/// auth_id is the user of the token
42c2b5be
TL
43pub fn check_backup_owner(owner: &Authid, auth_id: &Authid) -> Result<(), Error> {
44 let correct_owner =
45 owner == auth_id || (owner.is_token() && &Authid::from(owner.user().clone()) == auth_id);
9751ef4b
DC
46 if !correct_owner {
47 bail!("backup owner check failed ({} != {})", auth_id, owner);
48 }
49 Ok(())
50}
51
e5064ba6
DM
52/// Datastore Management
53///
54/// A Datastore can store severals backups, and provides the
55/// management interface for backup.
4bc84a65 56pub struct DataStoreImpl {
1629d2ad 57 chunk_store: Arc<ChunkStore>,
81b2a872 58 gc_mutex: Mutex<()>,
f2b99c34 59 last_gc_status: Mutex<GarbageCollectionStatus>,
0698f78d 60 verify_new: bool,
fef61684 61 chunk_order: ChunkOrder,
118deb4d
DC
62 last_generation: usize,
63 last_update: i64,
529de6c7
DM
64}
65
6da20161
WB
66impl DataStoreImpl {
67 // This one just panics on everything
68 #[doc(hidden)]
69 pub unsafe fn new_test() -> Arc<Self> {
70 Arc::new(Self {
71 chunk_store: Arc::new(unsafe { ChunkStore::panic_store() }),
72 gc_mutex: Mutex::new(()),
73 last_gc_status: Mutex::new(GarbageCollectionStatus::default()),
74 verify_new: false,
75 chunk_order: ChunkOrder::None,
76 last_generation: 0,
77 last_update: 0,
78 })
79 }
80}
81
4bc84a65
HL
82pub struct DataStore {
83 inner: Arc<DataStoreImpl>,
84 operation: Option<Operation>,
85}
86
87impl Clone for DataStore {
88 fn clone(&self) -> Self {
89 let mut new_operation = self.operation;
90 if let Some(operation) = self.operation {
91 if let Err(e) = update_active_operations(self.name(), operation, 1) {
92 log::error!("could not update active operations - {}", e);
93 new_operation = None;
94 }
95 }
96
97 DataStore {
98 inner: self.inner.clone(),
99 operation: new_operation,
100 }
101 }
102}
103
104impl Drop for DataStore {
105 fn drop(&mut self) {
106 if let Some(operation) = self.operation {
107 if let Err(e) = update_active_operations(self.name(), operation, -1) {
108 log::error!("could not update active operations - {}", e);
109 }
110 }
111 }
112}
113
529de6c7 114impl DataStore {
6da20161
WB
115 // This one just panics on everything
116 #[doc(hidden)]
117 pub unsafe fn new_test() -> Arc<Self> {
118 Arc::new(Self {
119 inner: unsafe { DataStoreImpl::new_test() },
120 operation: None,
121 })
122 }
123
e9d2fc93
HL
124 pub fn lookup_datastore(
125 name: &str,
126 operation: Option<Operation>,
127 ) -> Result<Arc<DataStore>, Error> {
118deb4d
DC
128 let version_cache = ConfigVersionCache::new()?;
129 let generation = version_cache.datastore_generation();
130 let now = proxmox_time::epoch_i64();
2c32fdde 131
e9d2fc93
HL
132 let (config, _digest) = pbs_config::datastore::config()?;
133 let config: DataStoreConfig = config.lookup("datastore", name)?;
e9d2fc93
HL
134
135 if let Some(maintenance_mode) = config.get_maintenance_mode() {
136 if let Err(error) = maintenance_mode.check(operation) {
137 bail!("datastore '{}' is in {}", name, error);
138 }
139 }
140
4bc84a65
HL
141 if let Some(operation) = operation {
142 update_active_operations(name, operation, 1)?;
143 }
144
515688d1 145 let mut map = DATASTORE_MAP.lock().unwrap();
118deb4d 146 let entry = map.get(name);
2c32fdde 147
118deb4d
DC
148 if let Some(datastore) = &entry {
149 if datastore.last_generation == generation && now < (datastore.last_update + 60) {
4bc84a65
HL
150 return Ok(Arc::new(Self {
151 inner: Arc::clone(datastore),
152 operation,
153 }));
2c32fdde
DM
154 }
155 }
156
6da20161
WB
157 let chunk_store = ChunkStore::open(name, &config.path)?;
158 let datastore = DataStore::with_store_and_config(chunk_store, config, generation, now)?;
f0a61124
DM
159
160 let datastore = Arc::new(datastore);
161 map.insert(name.to_string(), datastore.clone());
2c32fdde 162
4bc84a65
HL
163 Ok(Arc::new(Self {
164 inner: datastore,
165 operation,
166 }))
2c32fdde
DM
167 }
168
062cf75c 169 /// removes all datastores that are not configured anymore
42c2b5be 170 pub fn remove_unused_datastores() -> Result<(), Error> {
e7d4be9d 171 let (config, _digest) = pbs_config::datastore::config()?;
062cf75c
DC
172
173 let mut map = DATASTORE_MAP.lock().unwrap();
174 // removes all elements that are not in the config
42c2b5be 175 map.retain(|key, _| config.sections.contains_key(key));
062cf75c
DC
176 Ok(())
177 }
178
6da20161
WB
179 /// Open a raw database given a name and a path.
180 pub unsafe fn open_path(
181 name: &str,
182 path: impl AsRef<Path>,
183 operation: Option<Operation>,
184 ) -> Result<Arc<Self>, Error> {
185 let path = path
186 .as_ref()
187 .to_str()
188 .ok_or_else(|| format_err!("non-utf8 paths not supported"))?
189 .to_owned();
190 unsafe { Self::open_from_config(DataStoreConfig::new(name.to_owned(), path), operation) }
191 }
192
193 /// Open a datastore given a raw configuration.
194 pub unsafe fn open_from_config(
195 config: DataStoreConfig,
196 operation: Option<Operation>,
197 ) -> Result<Arc<Self>, Error> {
198 let name = config.name.clone();
199
200 let chunk_store = ChunkStore::open(&name, &config.path)?;
201 let inner = Arc::new(Self::with_store_and_config(chunk_store, config, 0, 0)?);
202
203 if let Some(operation) = operation {
204 update_active_operations(&name, operation, 1)?;
205 }
206
207 Ok(Arc::new(Self { inner, operation }))
208 }
209
210 fn with_store_and_config(
211 chunk_store: ChunkStore,
118deb4d
DC
212 config: DataStoreConfig,
213 last_generation: usize,
214 last_update: i64,
4bc84a65 215 ) -> Result<DataStoreImpl, Error> {
b683fd58
DC
216 let mut gc_status_path = chunk_store.base_path();
217 gc_status_path.push(".gc-status");
218
219 let gc_status = if let Some(state) = file_read_optional_string(gc_status_path)? {
220 match serde_json::from_str(&state) {
221 Ok(state) => state,
222 Err(err) => {
223 eprintln!("error reading gc-status: {}", err);
224 GarbageCollectionStatus::default()
225 }
226 }
227 } else {
228 GarbageCollectionStatus::default()
229 };
f2b99c34 230
fef61684 231 let tuning: DatastoreTuning = serde_json::from_value(
42c2b5be
TL
232 DatastoreTuning::API_SCHEMA
233 .parse_property_string(config.tuning.as_deref().unwrap_or(""))?,
fef61684
DC
234 )?;
235 let chunk_order = tuning.chunk_order.unwrap_or(ChunkOrder::Inode);
236
4bc84a65 237 Ok(DataStoreImpl {
1629d2ad 238 chunk_store: Arc::new(chunk_store),
81b2a872 239 gc_mutex: Mutex::new(()),
f2b99c34 240 last_gc_status: Mutex::new(gc_status),
0698f78d 241 verify_new: config.verify_new.unwrap_or(false),
fef61684 242 chunk_order,
118deb4d
DC
243 last_generation,
244 last_update,
529de6c7
DM
245 })
246 }
247
d59397e6
WB
248 pub fn get_chunk_iterator(
249 &self,
250 ) -> Result<
25877d05 251 impl Iterator<Item = (Result<proxmox_sys::fs::ReadDirEntry, Error>, usize, bool)>,
42c2b5be 252 Error,
d59397e6 253 > {
4bc84a65 254 self.inner.chunk_store.get_chunk_iterator()
d59397e6
WB
255 }
256
42c2b5be
TL
257 pub fn create_fixed_writer<P: AsRef<Path>>(
258 &self,
259 filename: P,
260 size: usize,
261 chunk_size: usize,
262 ) -> Result<FixedIndexWriter, Error> {
263 let index = FixedIndexWriter::create(
264 self.inner.chunk_store.clone(),
265 filename.as_ref(),
266 size,
267 chunk_size,
268 )?;
529de6c7
DM
269
270 Ok(index)
271 }
272
42c2b5be
TL
273 pub fn open_fixed_reader<P: AsRef<Path>>(
274 &self,
275 filename: P,
276 ) -> Result<FixedIndexReader, Error> {
277 let full_path = self.inner.chunk_store.relative_path(filename.as_ref());
a7c72ad9
DM
278
279 let index = FixedIndexReader::open(&full_path)?;
529de6c7
DM
280
281 Ok(index)
282 }
3d5c11e5 283
93d5d779 284 pub fn create_dynamic_writer<P: AsRef<Path>>(
42c2b5be
TL
285 &self,
286 filename: P,
93d5d779 287 ) -> Result<DynamicIndexWriter, Error> {
42c2b5be 288 let index = DynamicIndexWriter::create(self.inner.chunk_store.clone(), filename.as_ref())?;
0433db19
DM
289
290 Ok(index)
291 }
ff3d3100 292
42c2b5be
TL
293 pub fn open_dynamic_reader<P: AsRef<Path>>(
294 &self,
295 filename: P,
296 ) -> Result<DynamicIndexReader, Error> {
297 let full_path = self.inner.chunk_store.relative_path(filename.as_ref());
d48a9955
DM
298
299 let index = DynamicIndexReader::open(&full_path)?;
77703d95
DM
300
301 Ok(index)
302 }
303
5de2bced
WB
304 pub fn open_index<P>(&self, filename: P) -> Result<Box<dyn IndexFile + Send>, Error>
305 where
306 P: AsRef<Path>,
307 {
308 let filename = filename.as_ref();
42c2b5be
TL
309 let out: Box<dyn IndexFile + Send> = match archive_type(filename)? {
310 ArchiveType::DynamicIndex => Box::new(self.open_dynamic_reader(filename)?),
311 ArchiveType::FixedIndex => Box::new(self.open_fixed_reader(filename)?),
312 _ => bail!("cannot open index file of unknown type: {:?}", filename),
313 };
5de2bced
WB
314 Ok(out)
315 }
316
1369bcdb 317 /// Fast index verification - only check if chunks exists
28570d19
DM
318 pub fn fast_index_verification(
319 &self,
320 index: &dyn IndexFile,
42c2b5be 321 checked: &mut HashSet<[u8; 32]>,
28570d19 322 ) -> Result<(), Error> {
1369bcdb
DM
323 for pos in 0..index.index_count() {
324 let info = index.chunk_info(pos).unwrap();
28570d19
DM
325 if checked.contains(&info.digest) {
326 continue;
327 }
328
42c2b5be
TL
329 self.stat_chunk(&info.digest).map_err(|err| {
330 format_err!(
331 "fast_index_verification error, stat_chunk {} failed - {}",
332 hex::encode(&info.digest),
333 err,
334 )
335 })?;
28570d19
DM
336
337 checked.insert(info.digest);
1369bcdb
DM
338 }
339
340 Ok(())
341 }
342
60f9a6ea 343 pub fn name(&self) -> &str {
4bc84a65 344 self.inner.chunk_store.name()
60f9a6ea
DM
345 }
346
ff3d3100 347 pub fn base_path(&self) -> PathBuf {
4bc84a65 348 self.inner.chunk_store.base_path()
ff3d3100
DM
349 }
350
8c74349b
WB
351 pub fn namespace_path(&self, ns: &BackupNamespace) -> PathBuf {
352 let mut path = self.base_path();
353 path.reserve(ns.path_len());
354 for part in ns.components() {
355 path.push("ns");
356 path.push(part);
357 }
358 path
359 }
360
c47e294e 361 /// Cleanup a backup directory
7759eef5
DM
362 ///
363 /// Removes all files not mentioned in the manifest.
42c2b5be
TL
364 pub fn cleanup_backup_dir(
365 &self,
db87d93e
WB
366 backup_dir: impl AsRef<pbs_api_types::BackupDir>,
367 manifest: &BackupManifest,
368 ) -> Result<(), Error> {
369 self.cleanup_backup_dir_do(backup_dir.as_ref(), manifest)
370 }
371
372 fn cleanup_backup_dir_do(
373 &self,
374 backup_dir: &pbs_api_types::BackupDir,
42c2b5be
TL
375 manifest: &BackupManifest,
376 ) -> Result<(), Error> {
7759eef5 377 let mut full_path = self.base_path();
db87d93e 378 full_path.push(backup_dir.to_string());
7759eef5
DM
379
380 let mut wanted_files = HashSet::new();
381 wanted_files.insert(MANIFEST_BLOB_NAME.to_string());
1610c45a 382 wanted_files.insert(CLIENT_LOG_BLOB_NAME.to_string());
42c2b5be
TL
383 manifest.files().iter().for_each(|item| {
384 wanted_files.insert(item.filename.clone());
385 });
7759eef5 386
540fca5c
FG
387 for item in proxmox_sys::fs::read_subdir(libc::AT_FDCWD, &full_path)?.flatten() {
388 if let Some(file_type) = item.file_type() {
42c2b5be
TL
389 if file_type != nix::dir::Type::File {
390 continue;
391 }
540fca5c
FG
392 }
393 let file_name = item.file_name().to_bytes();
42c2b5be
TL
394 if file_name == b"." || file_name == b".." {
395 continue;
396 };
540fca5c 397 if let Ok(name) = std::str::from_utf8(file_name) {
42c2b5be
TL
398 if wanted_files.contains(name) {
399 continue;
400 }
7759eef5 401 }
540fca5c
FG
402 println!("remove unused file {:?}", item.file_name());
403 let dirfd = item.parent_fd();
404 let _res = unsafe { libc::unlinkat(dirfd, item.file_name().as_ptr(), 0) };
7759eef5
DM
405 }
406
407 Ok(())
408 }
4b4eba0b 409
90e38696
TL
410 /// Returns the absolute path for a backup namespace on this datastore
411 pub fn ns_path(&self, ns: &BackupNamespace) -> PathBuf {
412 let mut full_path = self.base_path();
413 full_path.push(ns.path());
414 full_path
415 }
416
41b373ec 417 /// Returns the absolute path for a backup_group
db87d93e 418 pub fn group_path(&self, backup_group: &pbs_api_types::BackupGroup) -> PathBuf {
4b4eba0b 419 let mut full_path = self.base_path();
db87d93e 420 full_path.push(backup_group.to_string());
41b373ec
DM
421 full_path
422 }
423
424 /// Returns the absolute path for backup_dir
db87d93e 425 pub fn snapshot_path(&self, backup_dir: &pbs_api_types::BackupDir) -> PathBuf {
41b373ec 426 let mut full_path = self.base_path();
db87d93e 427 full_path.push(backup_dir.to_string());
41b373ec
DM
428 full_path
429 }
430
f03649b8
TL
431 /// Remove a complete backup group including all snapshots.
432 ///
433 /// Returns true if all snapshots were removed, and false if some were protected
db87d93e 434 pub fn remove_backup_group(
6da20161 435 self: &Arc<Self>,
db87d93e
WB
436 backup_group: &pbs_api_types::BackupGroup,
437 ) -> Result<bool, Error> {
6b0c6492 438 let backup_group = self.backup_group(backup_group.clone());
db87d93e 439
f03649b8 440 backup_group.destroy()
4b4eba0b
DM
441 }
442
8f579717 443 /// Remove a backup directory including all content
db87d93e 444 pub fn remove_backup_dir(
6da20161 445 self: &Arc<Self>,
db87d93e
WB
446 backup_dir: &pbs_api_types::BackupDir,
447 force: bool,
448 ) -> Result<(), Error> {
6b0c6492 449 let backup_dir = self.backup_dir(backup_dir.clone())?;
db87d93e 450
f03649b8 451 backup_dir.destroy(force)
8f579717
DM
452 }
453
41b373ec
DM
454 /// Returns the time of the last successful backup
455 ///
456 /// Or None if there is no backup in the group (or the group dir does not exist).
db87d93e 457 pub fn last_successful_backup(
6da20161 458 self: &Arc<Self>,
db87d93e
WB
459 backup_group: &pbs_api_types::BackupGroup,
460 ) -> Result<Option<i64>, Error> {
6b0c6492 461 let backup_group = self.backup_group(backup_group.clone());
db87d93e 462
4b77d300 463 let group_path = backup_group.full_group_path();
41b373ec
DM
464
465 if group_path.exists() {
6da20161 466 backup_group.last_successful_backup()
41b373ec
DM
467 } else {
468 Ok(None)
469 }
470 }
471
54552dda
DM
472 /// Returns the backup owner.
473 ///
e6dc35ac 474 /// The backup owner is the entity who first created the backup group.
db87d93e 475 pub fn get_owner(&self, backup_group: &pbs_api_types::BackupGroup) -> Result<Authid, Error> {
54552dda 476 let mut full_path = self.base_path();
db87d93e 477 full_path.push(backup_group.to_string());
54552dda 478 full_path.push("owner");
25877d05 479 let owner = proxmox_sys::fs::file_read_firstline(full_path)?;
dcf5a0f6 480 owner.trim_end().parse() // remove trailing newline
54552dda
DM
481 }
482
db87d93e
WB
483 pub fn owns_backup(
484 &self,
485 backup_group: &pbs_api_types::BackupGroup,
486 auth_id: &Authid,
487 ) -> Result<bool, Error> {
9751ef4b
DC
488 let owner = self.get_owner(backup_group)?;
489
8e0b852f 490 Ok(check_backup_owner(&owner, auth_id).is_ok())
9751ef4b
DC
491 }
492
54552dda 493 /// Set the backup owner.
e7cb4dc5
WB
494 pub fn set_owner(
495 &self,
db87d93e 496 backup_group: &pbs_api_types::BackupGroup,
e6dc35ac 497 auth_id: &Authid,
e7cb4dc5
WB
498 force: bool,
499 ) -> Result<(), Error> {
54552dda 500 let mut path = self.base_path();
db87d93e 501 path.push(backup_group.to_string());
54552dda
DM
502 path.push("owner");
503
504 let mut open_options = std::fs::OpenOptions::new();
505 open_options.write(true);
506 open_options.truncate(true);
507
508 if force {
509 open_options.create(true);
510 } else {
511 open_options.create_new(true);
512 }
513
42c2b5be
TL
514 let mut file = open_options
515 .open(&path)
54552dda
DM
516 .map_err(|err| format_err!("unable to create owner file {:?} - {}", path, err))?;
517
e6dc35ac 518 writeln!(file, "{}", auth_id)
54552dda
DM
519 .map_err(|err| format_err!("unable to write owner file {:?} - {}", path, err))?;
520
521 Ok(())
522 }
523
1fc82c41 524 /// Create (if it does not already exists) and lock a backup group
54552dda
DM
525 ///
526 /// And set the owner to 'userid'. If the group already exists, it returns the
527 /// current owner (instead of setting the owner).
1fc82c41 528 ///
1ffe0301 529 /// This also acquires an exclusive lock on the directory and returns the lock guard.
e7cb4dc5
WB
530 pub fn create_locked_backup_group(
531 &self,
db87d93e 532 backup_group: &pbs_api_types::BackupGroup,
e6dc35ac
FG
533 auth_id: &Authid,
534 ) -> Result<(Authid, DirLockGuard), Error> {
8731e40a 535 // create intermediate path first:
44288184 536 let mut full_path = self.base_path();
8c74349b
WB
537 for ns in backup_group.ns.components() {
538 full_path.push("ns");
539 full_path.push(ns);
540 }
db87d93e 541 full_path.push(backup_group.ty.as_str());
8731e40a
WB
542 std::fs::create_dir_all(&full_path)?;
543
db87d93e 544 full_path.push(&backup_group.id);
54552dda
DM
545
546 // create the last component now
547 match std::fs::create_dir(&full_path) {
548 Ok(_) => {
42c2b5be
TL
549 let guard = lock_dir_noblock(
550 &full_path,
551 "backup group",
552 "another backup is already running",
553 )?;
e6dc35ac 554 self.set_owner(backup_group, auth_id, false)?;
54552dda 555 let owner = self.get_owner(backup_group)?; // just to be sure
1fc82c41 556 Ok((owner, guard))
54552dda
DM
557 }
558 Err(ref err) if err.kind() == io::ErrorKind::AlreadyExists => {
42c2b5be
TL
559 let guard = lock_dir_noblock(
560 &full_path,
561 "backup group",
562 "another backup is already running",
563 )?;
54552dda 564 let owner = self.get_owner(backup_group)?; // just to be sure
1fc82c41 565 Ok((owner, guard))
54552dda
DM
566 }
567 Err(err) => bail!("unable to create backup group {:?} - {}", full_path, err),
568 }
569 }
570
571 /// Creates a new backup snapshot inside a BackupGroup
572 ///
573 /// The BackupGroup directory needs to exist.
42c2b5be
TL
574 pub fn create_locked_backup_dir(
575 &self,
db87d93e 576 backup_dir: &pbs_api_types::BackupDir,
42c2b5be 577 ) -> Result<(PathBuf, bool, DirLockGuard), Error> {
db87d93e 578 let relative_path = PathBuf::from(backup_dir.to_string());
b3483782
DM
579 let mut full_path = self.base_path();
580 full_path.push(&relative_path);
ff3d3100 581
42c2b5be
TL
582 let lock = || {
583 lock_dir_noblock(
584 &full_path,
585 "snapshot",
586 "internal error - tried creating snapshot that's already in use",
587 )
588 };
f23f7543 589
8731e40a 590 match std::fs::create_dir(&full_path) {
f23f7543 591 Ok(_) => Ok((relative_path, true, lock()?)),
42c2b5be
TL
592 Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => {
593 Ok((relative_path, false, lock()?))
594 }
595 Err(e) => Err(e.into()),
8731e40a 596 }
ff3d3100
DM
597 }
598
90e38696
TL
599 /// Get a streaming iter over single-level backup namespaces of a datatstore
600 ///
601 /// The iterated item is still a Result that can contain errors from rather unexptected FS or
602 /// parsing errors.
603 pub fn iter_backup_ns(
604 self: &Arc<DataStore>,
605 ns: BackupNamespace,
606 ) -> Result<ListNamespaces, Error> {
607 ListNamespaces::new(Arc::clone(self), ns)
608 }
609
610 /// Get a streaming iter over single-level backup namespaces of a datatstore, filtered by Ok
611 ///
612 /// The iterated item's result is already unwrapped, if it contained an error it will be
613 /// logged. Can be useful in iterator chain commands
614 pub fn iter_backup_ns_ok(
615 self: &Arc<DataStore>,
616 ns: BackupNamespace,
617 ) -> Result<impl Iterator<Item = BackupNamespace> + 'static, Error> {
618 let this = Arc::clone(self);
619 Ok(
620 ListNamespaces::new(Arc::clone(&self), ns)?.filter_map(move |ns| match ns {
621 Ok(ns) => Some(ns),
622 Err(err) => {
623 log::error!("list groups error on datastore {} - {}", this.name(), err);
624 None
625 }
626 }),
627 )
628 }
629
630 /// Get a streaming iter over single-level backup namespaces of a datatstore
631 ///
632 /// The iterated item is still a Result that can contain errors from rather unexptected FS or
633 /// parsing errors.
634 pub fn recursive_iter_backup_ns(
635 self: &Arc<DataStore>,
636 ns: BackupNamespace,
637 ) -> Result<ListNamespacesRecursive, Error> {
638 ListNamespacesRecursive::new(Arc::clone(self), ns)
639 }
640
641 /// Get a streaming iter over single-level backup namespaces of a datatstore, filtered by Ok
642 ///
643 /// The iterated item's result is already unwrapped, if it contained an error it will be
644 /// logged. Can be useful in iterator chain commands
645 pub fn recursive_iter_backup_ns_ok(
646 self: &Arc<DataStore>,
647 ns: BackupNamespace,
648 ) -> Result<impl Iterator<Item = BackupNamespace> + 'static, Error> {
649 let this = Arc::clone(self);
650 Ok(
651 ListNamespacesRecursive::new(Arc::clone(&self), ns)?.filter_map(move |ns| match ns {
652 Ok(ns) => Some(ns),
653 Err(err) => {
654 log::error!("list groups error on datastore {} - {}", this.name(), err);
655 None
656 }
657 }),
658 )
659 }
660
7b125de3
TL
661 /// Get a streaming iter over top-level backup groups of a datatstore
662 ///
663 /// The iterated item is still a Result that can contain errors from rather unexptected FS or
664 /// parsing errors.
8c74349b
WB
665 pub fn iter_backup_groups(
666 self: &Arc<DataStore>,
667 ns: BackupNamespace,
668 ) -> Result<ListGroups, Error> {
669 ListGroups::new(Arc::clone(self), ns)
7b125de3
TL
670 }
671
672 /// Get a streaming iter over top-level backup groups of a datatstore, filtered by Ok results
673 ///
674 /// The iterated item's result is already unwrapped, if it contained an error it will be
675 /// logged. Can be useful in iterator chain commands
6da20161
WB
676 pub fn iter_backup_groups_ok(
677 self: &Arc<DataStore>,
8c74349b 678 ns: BackupNamespace,
6da20161
WB
679 ) -> Result<impl Iterator<Item = BackupGroup> + 'static, Error> {
680 let this = Arc::clone(self);
7b125de3 681 Ok(
8c74349b 682 ListGroups::new(Arc::clone(&self), ns)?.filter_map(move |group| match group {
7b125de3
TL
683 Ok(group) => Some(group),
684 Err(err) => {
6da20161 685 log::error!("list groups error on datastore {} - {}", this.name(), err);
7b125de3
TL
686 None
687 }
688 }),
689 )
690 }
691
c90dbb5c 692 /// Get a in-memory vector for all top-level backup groups of a datatstore
7b125de3
TL
693 ///
694 /// NOTE: using the iterator directly is most often more efficient w.r.t. memory usage
8c74349b
WB
695 pub fn list_backup_groups(
696 self: &Arc<DataStore>,
697 ns: BackupNamespace,
698 ) -> Result<Vec<BackupGroup>, Error> {
699 ListGroups::new(Arc::clone(self), ns)?.collect()
c90dbb5c
TL
700 }
701
3d5c11e5 702 pub fn list_images(&self) -> Result<Vec<PathBuf>, Error> {
ff3d3100 703 let base = self.base_path();
3d5c11e5
DM
704
705 let mut list = vec![];
706
95cea65b
DM
707 use walkdir::WalkDir;
708
84466003 709 let walker = WalkDir::new(&base).into_iter();
95cea65b
DM
710
711 // make sure we skip .chunks (and other hidden files to keep it simple)
712 fn is_hidden(entry: &walkdir::DirEntry) -> bool {
42c2b5be
TL
713 entry
714 .file_name()
95cea65b 715 .to_str()
d8d8af98 716 .map(|s| s.starts_with('.'))
95cea65b
DM
717 .unwrap_or(false)
718 }
c3b090ac
TL
719 let handle_entry_err = |err: walkdir::Error| {
720 if let Some(inner) = err.io_error() {
d08cff51
FG
721 if let Some(path) = err.path() {
722 if inner.kind() == io::ErrorKind::PermissionDenied {
c3b090ac
TL
723 // only allow to skip ext4 fsck directory, avoid GC if, for example,
724 // a user got file permissions wrong on datastore rsync to new server
725 if err.depth() > 1 || !path.ends_with("lost+found") {
d08cff51 726 bail!("cannot continue garbage-collection safely, permission denied on: {:?}", path)
c3b090ac 727 }
d08cff51 728 } else {
42c2b5be
TL
729 bail!(
730 "unexpected error on datastore traversal: {} - {:?}",
731 inner,
732 path
733 )
d08cff51
FG
734 }
735 } else {
736 bail!("unexpected error on datastore traversal: {}", inner)
c3b090ac
TL
737 }
738 }
739 Ok(())
740 };
95cea65b 741 for entry in walker.filter_entry(|e| !is_hidden(e)) {
c3b090ac
TL
742 let path = match entry {
743 Ok(entry) => entry.into_path(),
744 Err(err) => {
745 handle_entry_err(err)?;
42c2b5be
TL
746 continue;
747 }
c3b090ac 748 };
1e8da0a7 749 if let Ok(archive_type) = archive_type(&path) {
42c2b5be
TL
750 if archive_type == ArchiveType::FixedIndex
751 || archive_type == ArchiveType::DynamicIndex
752 {
95cea65b 753 list.push(path);
3d5c11e5
DM
754 }
755 }
756 }
757
758 Ok(list)
759 }
760
a660978c
DM
761 // mark chunks used by ``index`` as used
762 fn index_mark_used_chunks<I: IndexFile>(
763 &self,
764 index: I,
765 file_name: &Path, // only used for error reporting
766 status: &mut GarbageCollectionStatus,
c8449217 767 worker: &dyn WorkerTaskContext,
a660978c 768 ) -> Result<(), Error> {
a660978c
DM
769 status.index_file_count += 1;
770 status.index_data_bytes += index.index_bytes();
771
772 for pos in 0..index.index_count() {
f6b1d1cc 773 worker.check_abort()?;
0fd55b08 774 worker.fail_on_shutdown()?;
a660978c 775 let digest = index.index_digest(pos).unwrap();
4bc84a65 776 if !self.inner.chunk_store.cond_touch_chunk(digest, false)? {
c23192d3 777 task_warn!(
f6b1d1cc 778 worker,
d1d74c43 779 "warning: unable to access non-existent chunk {}, required by {:?}",
25877d05 780 hex::encode(digest),
f6b1d1cc 781 file_name,
f6b1d1cc 782 );
fd192564
SR
783
784 // touch any corresponding .bad files to keep them around, meaning if a chunk is
785 // rewritten correctly they will be removed automatically, as well as if no index
786 // file requires the chunk anymore (won't get to this loop then)
787 for i in 0..=9 {
788 let bad_ext = format!("{}.bad", i);
789 let mut bad_path = PathBuf::new();
790 bad_path.push(self.chunk_path(digest).0);
791 bad_path.set_extension(bad_ext);
4bc84a65 792 self.inner.chunk_store.cond_touch_path(&bad_path, false)?;
fd192564 793 }
a660978c
DM
794 }
795 }
796 Ok(())
797 }
798
f6b1d1cc
WB
799 fn mark_used_chunks(
800 &self,
801 status: &mut GarbageCollectionStatus,
c8449217 802 worker: &dyn WorkerTaskContext,
f6b1d1cc 803 ) -> Result<(), Error> {
3d5c11e5 804 let image_list = self.list_images()?;
8317873c
DM
805 let image_count = image_list.len();
806
8317873c
DM
807 let mut last_percentage: usize = 0;
808
cb4b721c
FG
809 let mut strange_paths_count: u64 = 0;
810
ea368a06 811 for (i, img) in image_list.into_iter().enumerate() {
f6b1d1cc 812 worker.check_abort()?;
0fd55b08 813 worker.fail_on_shutdown()?;
92da93b2 814
cb4b721c
FG
815 if let Some(backup_dir_path) = img.parent() {
816 let backup_dir_path = backup_dir_path.strip_prefix(self.base_path())?;
817 if let Some(backup_dir_str) = backup_dir_path.to_str() {
db87d93e 818 if pbs_api_types::BackupDir::from_str(backup_dir_str).is_err() {
cb4b721c
FG
819 strange_paths_count += 1;
820 }
821 }
822 }
823
efcac39d 824 match std::fs::File::open(&img) {
e0762002 825 Ok(file) => {
788d82d9 826 if let Ok(archive_type) = archive_type(&img) {
e0762002 827 if archive_type == ArchiveType::FixedIndex {
788d82d9 828 let index = FixedIndexReader::new(file).map_err(|e| {
efcac39d 829 format_err!("can't read index '{}' - {}", img.to_string_lossy(), e)
2f0b9235 830 })?;
788d82d9 831 self.index_mark_used_chunks(index, &img, status, worker)?;
e0762002 832 } else if archive_type == ArchiveType::DynamicIndex {
788d82d9 833 let index = DynamicIndexReader::new(file).map_err(|e| {
efcac39d 834 format_err!("can't read index '{}' - {}", img.to_string_lossy(), e)
2f0b9235 835 })?;
788d82d9 836 self.index_mark_used_chunks(index, &img, status, worker)?;
e0762002
DM
837 }
838 }
839 }
788d82d9 840 Err(err) if err.kind() == io::ErrorKind::NotFound => (), // ignore vanished files
efcac39d 841 Err(err) => bail!("can't open index {} - {}", img.to_string_lossy(), err),
77703d95 842 }
8317873c 843
ea368a06 844 let percentage = (i + 1) * 100 / image_count;
8317873c 845 if percentage > last_percentage {
c23192d3 846 task_log!(
f6b1d1cc 847 worker,
7956877f 848 "marked {}% ({} of {} index files)",
f6b1d1cc 849 percentage,
ea368a06 850 i + 1,
f6b1d1cc
WB
851 image_count,
852 );
8317873c
DM
853 last_percentage = percentage;
854 }
3d5c11e5
DM
855 }
856
cb4b721c 857 if strange_paths_count > 0 {
c23192d3 858 task_log!(
cb4b721c
FG
859 worker,
860 "found (and marked) {} index files outside of expected directory scheme",
861 strange_paths_count,
862 );
863 }
864
3d5c11e5 865 Ok(())
f2b99c34
DM
866 }
867
868 pub fn last_gc_status(&self) -> GarbageCollectionStatus {
4bc84a65 869 self.inner.last_gc_status.lock().unwrap().clone()
f2b99c34 870 }
3d5c11e5 871
8545480a 872 pub fn garbage_collection_running(&self) -> bool {
4bc84a65 873 !matches!(self.inner.gc_mutex.try_lock(), Ok(_))
8545480a
DM
874 }
875
42c2b5be
TL
876 pub fn garbage_collection(
877 &self,
878 worker: &dyn WorkerTaskContext,
879 upid: &UPID,
880 ) -> Result<(), Error> {
4bc84a65 881 if let Ok(ref mut _mutex) = self.inner.gc_mutex.try_lock() {
c6772c92
TL
882 // avoids that we run GC if an old daemon process has still a
883 // running backup writer, which is not save as we have no "oldest
884 // writer" information and thus no safe atime cutoff
42c2b5be 885 let _exclusive_lock = self.inner.chunk_store.try_exclusive_lock()?;
43b13033 886
6ef1b649 887 let phase1_start_time = proxmox_time::epoch_i64();
42c2b5be
TL
888 let oldest_writer = self
889 .inner
890 .chunk_store
891 .oldest_writer()
892 .unwrap_or(phase1_start_time);
11861a48 893
64e53b28 894 let mut gc_status = GarbageCollectionStatus::default();
f6b1d1cc
WB
895 gc_status.upid = Some(upid.to_string());
896
c23192d3 897 task_log!(worker, "Start GC phase1 (mark used chunks)");
f6b1d1cc
WB
898
899 self.mark_used_chunks(&mut gc_status, worker)?;
900
c23192d3 901 task_log!(worker, "Start GC phase2 (sweep unused chunks)");
4bc84a65 902 self.inner.chunk_store.sweep_unused_chunks(
f6b1d1cc
WB
903 oldest_writer,
904 phase1_start_time,
905 &mut gc_status,
906 worker,
907 )?;
908
c23192d3 909 task_log!(
f6b1d1cc
WB
910 worker,
911 "Removed garbage: {}",
912 HumanByte::from(gc_status.removed_bytes),
913 );
c23192d3 914 task_log!(worker, "Removed chunks: {}", gc_status.removed_chunks);
cf459b19 915 if gc_status.pending_bytes > 0 {
c23192d3 916 task_log!(
f6b1d1cc
WB
917 worker,
918 "Pending removals: {} (in {} chunks)",
919 HumanByte::from(gc_status.pending_bytes),
920 gc_status.pending_chunks,
921 );
cf459b19 922 }
a9767cf7 923 if gc_status.removed_bad > 0 {
c23192d3 924 task_log!(worker, "Removed bad chunks: {}", gc_status.removed_bad);
a9767cf7 925 }
cf459b19 926
b4fb2623 927 if gc_status.still_bad > 0 {
c23192d3 928 task_log!(worker, "Leftover bad chunks: {}", gc_status.still_bad);
b4fb2623
DM
929 }
930
c23192d3 931 task_log!(
f6b1d1cc
WB
932 worker,
933 "Original data usage: {}",
934 HumanByte::from(gc_status.index_data_bytes),
935 );
868c5852
DM
936
937 if gc_status.index_data_bytes > 0 {
42c2b5be
TL
938 let comp_per =
939 (gc_status.disk_bytes as f64 * 100.) / gc_status.index_data_bytes as f64;
c23192d3 940 task_log!(
f6b1d1cc
WB
941 worker,
942 "On-Disk usage: {} ({:.2}%)",
943 HumanByte::from(gc_status.disk_bytes),
944 comp_per,
945 );
868c5852
DM
946 }
947
c23192d3 948 task_log!(worker, "On-Disk chunks: {}", gc_status.disk_chunks);
868c5852 949
d6373f35 950 let deduplication_factor = if gc_status.disk_bytes > 0 {
42c2b5be 951 (gc_status.index_data_bytes as f64) / (gc_status.disk_bytes as f64)
d6373f35
DM
952 } else {
953 1.0
954 };
955
c23192d3 956 task_log!(worker, "Deduplication factor: {:.2}", deduplication_factor);
d6373f35 957
868c5852 958 if gc_status.disk_chunks > 0 {
42c2b5be 959 let avg_chunk = gc_status.disk_bytes / (gc_status.disk_chunks as u64);
c23192d3 960 task_log!(worker, "Average chunk size: {}", HumanByte::from(avg_chunk));
868c5852 961 }
64e53b28 962
b683fd58
DC
963 if let Ok(serialized) = serde_json::to_string(&gc_status) {
964 let mut path = self.base_path();
965 path.push(".gc-status");
966
21211748 967 let backup_user = pbs_config::backup_user()?;
b683fd58
DC
968 let mode = nix::sys::stat::Mode::from_bits_truncate(0o0644);
969 // set the correct owner/group/permissions while saving file
970 // owner(rw) = backup, group(r)= backup
971 let options = CreateOptions::new()
972 .perm(mode)
973 .owner(backup_user.uid)
974 .group(backup_user.gid);
975
976 // ignore errors
e0a19d33 977 let _ = replace_file(path, serialized.as_bytes(), options, false);
b683fd58
DC
978 }
979
4bc84a65 980 *self.inner.last_gc_status.lock().unwrap() = gc_status;
64e53b28 981 } else {
d4b59ae0 982 bail!("Start GC failed - (already running/locked)");
64e53b28 983 }
3d5c11e5
DM
984
985 Ok(())
986 }
3b7ade9e 987
ccc3896f 988 pub fn try_shared_chunk_store_lock(&self) -> Result<ProcessLockSharedGuard, Error> {
4bc84a65 989 self.inner.chunk_store.try_shared_lock()
1cf5178a
DM
990 }
991
42c2b5be 992 pub fn chunk_path(&self, digest: &[u8; 32]) -> (PathBuf, String) {
4bc84a65 993 self.inner.chunk_store.chunk_path(digest)
d48a9955
DM
994 }
995
b298e9f1 996 pub fn cond_touch_chunk(&self, digest: &[u8; 32], assert_exists: bool) -> Result<bool, Error> {
42c2b5be
TL
997 self.inner
998 .chunk_store
b298e9f1 999 .cond_touch_chunk(digest, assert_exists)
42c2b5be
TL
1000 }
1001
1002 pub fn insert_chunk(&self, chunk: &DataBlob, digest: &[u8; 32]) -> Result<(bool, u64), Error> {
4bc84a65 1003 self.inner.chunk_store.insert_chunk(chunk, digest)
3b7ade9e 1004 }
60f9a6ea 1005
7f394c80 1006 pub fn stat_chunk(&self, digest: &[u8; 32]) -> Result<std::fs::Metadata, Error> {
4bc84a65 1007 let (chunk_path, _digest_str) = self.inner.chunk_store.chunk_path(digest);
7f394c80
DC
1008 std::fs::metadata(chunk_path).map_err(Error::from)
1009 }
1010
39f18b30 1011 pub fn load_chunk(&self, digest: &[u8; 32]) -> Result<DataBlob, Error> {
4bc84a65 1012 let (chunk_path, digest_str) = self.inner.chunk_store.chunk_path(digest);
39f18b30 1013
6ef1b649 1014 proxmox_lang::try_block!({
39f18b30
DM
1015 let mut file = std::fs::File::open(&chunk_path)?;
1016 DataBlob::load_from_reader(&mut file)
42c2b5be
TL
1017 })
1018 .map_err(|err| {
1019 format_err!(
1020 "store '{}', unable to load chunk '{}' - {}",
1021 self.name(),
1022 digest_str,
1023 err,
1024 )
1025 })
1a374fcf
SR
1026 }
1027
1a374fcf 1028 /// Load the manifest without a lock. Must not be written back.
42c2b5be 1029 pub fn load_manifest(&self, backup_dir: &BackupDir) -> Result<(BackupManifest, u64), Error> {
1eef52c2 1030 let blob = backup_dir.load_blob(MANIFEST_BLOB_NAME)?;
39f18b30 1031 let raw_size = blob.raw_size();
60f9a6ea 1032 let manifest = BackupManifest::try_from(blob)?;
ff86ef00 1033 Ok((manifest, raw_size))
60f9a6ea 1034 }
e4439025 1035
1a374fcf
SR
1036 /// Update the manifest of the specified snapshot. Never write a manifest directly,
1037 /// only use this method - anything else may break locking guarantees.
1038 pub fn update_manifest(
e4439025
DM
1039 &self,
1040 backup_dir: &BackupDir,
1a374fcf 1041 update_fn: impl FnOnce(&mut BackupManifest),
e4439025 1042 ) -> Result<(), Error> {
5c9c23b6 1043 let _guard = backup_dir.lock_manifest()?;
9a37bd6c 1044 let (mut manifest, _) = self.load_manifest(backup_dir)?;
1a374fcf
SR
1045
1046 update_fn(&mut manifest);
1047
883aa6d5 1048 let manifest = serde_json::to_value(manifest)?;
e4439025
DM
1049 let manifest = serde_json::to_string_pretty(&manifest)?;
1050 let blob = DataBlob::encode(manifest.as_bytes(), None, true)?;
1051 let raw_data = blob.raw_data();
1052
4b77d300 1053 let mut path = backup_dir.full_path();
e4439025
DM
1054 path.push(MANIFEST_BLOB_NAME);
1055
1a374fcf 1056 // atomic replace invalidates flock - no other writes past this point!
e0a19d33 1057 replace_file(&path, raw_data, CreateOptions::new(), false)?;
e4439025
DM
1058
1059 Ok(())
1060 }
0698f78d 1061
8292d3d2 1062 /// Updates the protection status of the specified snapshot.
42c2b5be 1063 pub fn update_protection(&self, backup_dir: &BackupDir, protection: bool) -> Result<(), Error> {
6da20161 1064 let full_path = backup_dir.full_path();
8292d3d2
DC
1065
1066 let _guard = lock_dir_noblock(&full_path, "snapshot", "possibly running or in use")?;
1067
6da20161 1068 let protected_path = backup_dir.protected_file();
8292d3d2
DC
1069 if protection {
1070 std::fs::File::create(protected_path)
1071 .map_err(|err| format_err!("could not create protection file: {}", err))?;
1072 } else if let Err(err) = std::fs::remove_file(protected_path) {
1073 // ignore error for non-existing file
1074 if err.kind() != std::io::ErrorKind::NotFound {
1075 bail!("could not remove protection file: {}", err);
1076 }
1077 }
1078
1079 Ok(())
1080 }
1081
0698f78d 1082 pub fn verify_new(&self) -> bool {
4bc84a65 1083 self.inner.verify_new
0698f78d 1084 }
4921a411
DC
1085
1086 /// returns a list of chunks sorted by their inode number on disk
1087 /// chunks that could not be stat'ed are at the end of the list
1088 pub fn get_chunks_in_order<F, A>(
1089 &self,
1090 index: &Box<dyn IndexFile + Send>,
1091 skip_chunk: F,
1092 check_abort: A,
1093 ) -> Result<Vec<(usize, u64)>, Error>
1094 where
1095 F: Fn(&[u8; 32]) -> bool,
1096 A: Fn(usize) -> Result<(), Error>,
1097 {
1098 let index_count = index.index_count();
1099 let mut chunk_list = Vec::with_capacity(index_count);
1100 use std::os::unix::fs::MetadataExt;
1101 for pos in 0..index_count {
1102 check_abort(pos)?;
1103
1104 let info = index.chunk_info(pos).unwrap();
1105
1106 if skip_chunk(&info.digest) {
1107 continue;
1108 }
1109
4bc84a65 1110 let ino = match self.inner.chunk_order {
fef61684
DC
1111 ChunkOrder::Inode => {
1112 match self.stat_chunk(&info.digest) {
1113 Err(_) => u64::MAX, // could not stat, move to end of list
1114 Ok(metadata) => metadata.ino(),
1115 }
1116 }
1117 ChunkOrder::None => 0,
4921a411
DC
1118 };
1119
1120 chunk_list.push((pos, ino));
1121 }
1122
4bc84a65 1123 match self.inner.chunk_order {
fef61684
DC
1124 // sorting by inode improves data locality, which makes it lots faster on spinners
1125 ChunkOrder::Inode => {
1126 chunk_list.sort_unstable_by(|(_, ino_a), (_, ino_b)| ino_a.cmp(ino_b))
1127 }
1128 ChunkOrder::None => {}
1129 }
4921a411
DC
1130
1131 Ok(chunk_list)
1132 }
db87d93e 1133
6b0c6492 1134 /// Open a backup group from this datastore.
6da20161
WB
1135 pub fn backup_group(self: &Arc<Self>, group: pbs_api_types::BackupGroup) -> BackupGroup {
1136 BackupGroup::new(Arc::clone(&self), group)
db87d93e
WB
1137 }
1138
6b0c6492 1139 /// Open a backup group from this datastore.
8c74349b
WB
1140 pub fn backup_group_from_parts<T>(
1141 self: &Arc<Self>,
1142 ns: BackupNamespace,
1143 ty: BackupType,
1144 id: T,
1145 ) -> BackupGroup
6b0c6492
WB
1146 where
1147 T: Into<String>,
1148 {
8c74349b 1149 self.backup_group((ns, ty, id.into()).into())
6b0c6492
WB
1150 }
1151
1152 /// Open a backup group from this datastore by backup group path such as `vm/100`.
1153 ///
1154 /// Convenience method for `store.backup_group(path.parse()?)`
6da20161 1155 pub fn backup_group_from_path(self: &Arc<Self>, path: &str) -> Result<BackupGroup, Error> {
6b0c6492 1156 Ok(self.backup_group(path.parse()?))
db87d93e
WB
1157 }
1158
6b0c6492 1159 /// Open a snapshot (backup directory) from this datastore.
6da20161 1160 pub fn backup_dir(self: &Arc<Self>, dir: pbs_api_types::BackupDir) -> Result<BackupDir, Error> {
6b0c6492
WB
1161 BackupDir::with_group(self.backup_group(dir.group), dir.time)
1162 }
1163
1164 /// Open a snapshot (backup directory) from this datastore.
db87d93e 1165 pub fn backup_dir_from_parts<T>(
6da20161 1166 self: &Arc<Self>,
8c74349b 1167 ns: BackupNamespace,
db87d93e
WB
1168 ty: BackupType,
1169 id: T,
1170 time: i64,
1171 ) -> Result<BackupDir, Error>
1172 where
1173 T: Into<String>,
1174 {
8c74349b 1175 self.backup_dir((ns, ty, id.into(), time).into())
db87d93e
WB
1176 }
1177
6b0c6492 1178 /// Open a snapshot (backup directory) from this datastore with a cached rfc3339 time string.
db87d93e 1179 pub fn backup_dir_with_rfc3339<T: Into<String>>(
6da20161 1180 self: &Arc<Self>,
db87d93e
WB
1181 group: BackupGroup,
1182 time_string: T,
1183 ) -> Result<BackupDir, Error> {
1184 BackupDir::with_rfc3339(group, time_string.into())
1185 }
1186
6b0c6492 1187 /// Open a snapshot (backup directory) from this datastore by a snapshot path.
6da20161 1188 pub fn backup_dir_from_path(self: &Arc<Self>, path: &str) -> Result<BackupDir, Error> {
6b0c6492 1189 self.backup_dir(path.parse()?)
db87d93e 1190 }
529de6c7 1191}
de015ce7 1192
33eb23d5
TL
1193/// A iterator for all BackupDir's (Snapshots) in a BackupGroup
1194pub struct ListSnapshots {
1195 group: BackupGroup,
1196 fd: proxmox_sys::fs::ReadDir,
1197}
1198
1199impl ListSnapshots {
6da20161 1200 pub fn new(group: BackupGroup) -> Result<Self, Error> {
df5c6a11 1201 let group_path = group.full_group_path();
33eb23d5 1202 Ok(ListSnapshots {
df5c6a11
TL
1203 fd: proxmox_sys::fs::read_subdir(libc::AT_FDCWD, &group_path)
1204 .map_err(|err| format_err!("read dir {group_path:?} - {err}"))?,
33eb23d5
TL
1205 group,
1206 })
1207 }
1208}
1209
1210impl Iterator for ListSnapshots {
1211 type Item = Result<BackupDir, Error>;
1212
1213 fn next(&mut self) -> Option<Self::Item> {
1214 loop {
cc295e2c
TL
1215 let item = self.fd.next()?; // either get a entry to check or return None if exhausted
1216 let entry = match item {
33eb23d5 1217 Ok(ref entry) => {
cc295e2c
TL
1218 match entry.file_type() {
1219 Some(nix::dir::Type::Directory) => entry, // OK
1220 _ => continue,
33eb23d5 1221 }
33eb23d5
TL
1222 }
1223 Err(err) => return Some(Err(err)),
cc295e2c
TL
1224 };
1225 if let Ok(name) = entry.file_name().to_str() {
1226 if BACKUP_DATE_REGEX.is_match(name) {
1227 let backup_time = match proxmox_time::parse_rfc3339(&name) {
1228 Ok(time) => time,
1229 Err(err) => return Some(Err(err)),
1230 };
1231
1232 return Some(BackupDir::with_group(self.group.clone(), backup_time));
1233 }
33eb23d5
TL
1234 }
1235 }
1236 }
1237}
1238
de015ce7
TL
1239/// A iterator for a (single) level of Backup Groups
1240pub struct ListGroups {
6da20161 1241 store: Arc<DataStore>,
8c74349b 1242 ns: BackupNamespace,
de015ce7 1243 type_fd: proxmox_sys::fs::ReadDir,
988d575d 1244 id_state: Option<(BackupType, proxmox_sys::fs::ReadDir)>,
de015ce7
TL
1245}
1246
1247impl ListGroups {
8c74349b
WB
1248 pub fn new(store: Arc<DataStore>, ns: BackupNamespace) -> Result<Self, Error> {
1249 let mut base_path = store.base_path().to_owned();
1250 base_path.push(ns.path());
de015ce7 1251 Ok(ListGroups {
8c74349b 1252 type_fd: proxmox_sys::fs::read_subdir(libc::AT_FDCWD, &base_path)?,
6da20161 1253 store,
8c74349b 1254 ns,
de015ce7
TL
1255 id_state: None,
1256 })
1257 }
1258}
1259
1260impl Iterator for ListGroups {
1261 type Item = Result<BackupGroup, Error>;
1262
1263 fn next(&mut self) -> Option<Self::Item> {
1264 loop {
988d575d 1265 if let Some((group_type, ref mut id_fd)) = self.id_state {
de015ce7
TL
1266 let item = match id_fd.next() {
1267 Some(item) => item,
1268 None => {
1269 self.id_state = None;
1270 continue; // exhausted all IDs for the current group type, try others
1271 }
1272 };
cc295e2c 1273 let entry = match item {
de015ce7 1274 Ok(ref entry) => {
cc295e2c
TL
1275 match entry.file_type() {
1276 Some(nix::dir::Type::Directory) => entry, // OK
1277 _ => continue,
de015ce7 1278 }
de015ce7
TL
1279 }
1280 Err(err) => return Some(Err(err)),
cc295e2c
TL
1281 };
1282 if let Ok(name) = entry.file_name().to_str() {
1283 if BACKUP_ID_REGEX.is_match(name) {
1284 return Some(Ok(BackupGroup::new(
1285 Arc::clone(&self.store),
8c74349b 1286 (self.ns.clone(), group_type, name.to_owned()).into(),
cc295e2c
TL
1287 )));
1288 }
de015ce7
TL
1289 }
1290 } else {
1291 let item = self.type_fd.next()?;
cc295e2c
TL
1292 let entry = match item {
1293 // filter directories
de015ce7 1294 Ok(ref entry) => {
cc295e2c
TL
1295 match entry.file_type() {
1296 Some(nix::dir::Type::Directory) => entry, // OK
1297 _ => continue,
de015ce7 1298 }
de015ce7
TL
1299 }
1300 Err(err) => return Some(Err(err)),
cc295e2c
TL
1301 };
1302 if let Ok(name) = entry.file_name().to_str() {
1303 if let Ok(group_type) = BackupType::from_str(name) {
1304 // found a backup group type, descend into it to scan all IDs in it
1305 // by switching to the id-state branch
1306 let base_fd = entry.parent_fd();
1307 let id_dirfd = match proxmox_sys::fs::read_subdir(base_fd, name) {
1308 Ok(dirfd) => dirfd,
1309 Err(err) => return Some(Err(err.into())),
1310 };
1311 self.id_state = Some((group_type, id_dirfd));
1312 }
de015ce7
TL
1313 }
1314 }
1315 }
1316 }
1317}
90e38696
TL
1318
1319/// A iterator for a (single) level of Namespaces
1320pub struct ListNamespaces {
1321 ns: BackupNamespace,
1322 base_path: PathBuf,
1323 ns_state: Option<proxmox_sys::fs::ReadDir>,
1324}
1325
1326impl ListNamespaces {
1327 /// construct a new single-level namespace iterator on a datastore with an optional anchor ns
1328 pub fn new(store: Arc<DataStore>, ns: BackupNamespace) -> Result<Self, Error> {
1329 Ok(ListNamespaces {
1330 ns,
1331 base_path: store.base_path(),
1332 ns_state: None,
1333 })
1334 }
1335
1336 /// to allow constructing the iter directly on a path, e.g., provided by section config
1337 ///
1338 /// NOTE: it's recommended to use the datastore one constructor or go over the recursive iter
1339 pub fn new_from_path(path: PathBuf, ns: Option<BackupNamespace>) -> Result<Self, Error> {
1340 Ok(ListNamespaces {
1341 ns: ns.unwrap_or_default(),
1342 base_path: path,
1343 ns_state: None,
1344 })
1345 }
1346}
1347
1348impl Iterator for ListNamespaces {
1349 type Item = Result<BackupNamespace, Error>;
1350
1351 fn next(&mut self) -> Option<Self::Item> {
1352 loop {
1353 if let Some(ref mut id_fd) = self.ns_state {
1354 let item = id_fd.next()?; // if this returns none we are done
1355 let entry = match item {
1356 Ok(ref entry) => {
1357 match entry.file_type() {
1358 Some(nix::dir::Type::Directory) => entry, // OK
1359 _ => continue,
1360 }
1361 }
1362 Err(err) => return Some(Err(err)),
1363 };
1364 if let Ok(name) = entry.file_name().to_str() {
1365 if name != "." && name != ".." {
1366 return Some(BackupNamespace::from_parent_ns(&self.ns, name.to_string()));
1367 }
1368 }
1369 continue; // file did not match regex or isn't valid utf-8
1370 } else {
1371 let mut base_path = self.base_path.to_owned();
1372 if !self.ns.is_root() {
1373 base_path.push(self.ns.path());
1374 }
1375 base_path.push("ns");
1376
1377 let ns_dirfd = match proxmox_sys::fs::read_subdir(libc::AT_FDCWD, &base_path) {
1378 Ok(dirfd) => dirfd,
1379 Err(nix::Error::Sys(nix::errno::Errno::ENOENT)) => return None,
1380 Err(err) => return Some(Err(err.into())),
1381 };
1382 // found a ns directory, descend into it to scan all it's namespaces
1383 self.ns_state = Some(ns_dirfd);
1384 }
1385 }
1386 }
1387}
1388
1389/// A iterator for all Namespaces below an anchor namespace, most often that will be the
1390/// `BackupNamespace::root()` one.
1391///
1392/// Descends depth-first (pre-order) into the namespace hierachy yielding namespaces immediately as
1393/// it finds them.
1394///
1395/// Note: The anchor namespaces passed on creating the iterator will yielded as first element, this
1396/// can be usefull for searching all backup groups from a certain anchor, as that can contain
1397/// sub-namespaces but also groups on its own level, so otherwise one would need to special case
1398/// the ones from the own level.
1399pub struct ListNamespacesRecursive {
1400 store: Arc<DataStore>,
1401 /// the starting namespace we search downward from
1402 ns: BackupNamespace,
1403 state: Option<Vec<ListNamespaces>>, // vector to avoid code recursion
1404}
1405
1406impl ListNamespacesRecursive {
1407 /// Creates an recursive namespace iterator.
1408 pub fn new(store: Arc<DataStore>, ns: BackupNamespace) -> Result<Self, Error> {
1409 Ok(ListNamespacesRecursive {
1410 store: store,
1411 ns,
1412 state: None,
1413 })
1414 }
1415}
1416
1417impl Iterator for ListNamespacesRecursive {
1418 type Item = Result<BackupNamespace, Error>;
1419
1420 fn next(&mut self) -> Option<Self::Item> {
1421 loop {
1422 if let Some(ref mut state) = self.state {
1423 if state.is_empty() {
1424 return None; // there's a state but it's empty -> we're all done
1425 }
1426 let iter = match state.last_mut() {
1427 Some(iter) => iter,
1428 None => return None, // unexpected, should we just unwrap?
1429 };
1430 match iter.next() {
1431 Some(Ok(ns)) => {
1432 match ListNamespaces::new(Arc::clone(&self.store), ns.to_owned()) {
1433 Ok(iter) => state.push(iter),
1434 Err(err) => log::error!("failed to create child namespace iter {err}"),
1435 }
1436 return Some(Ok(ns));
1437 }
1438 Some(ns_err) => return Some(ns_err),
1439 None => {
1440 let _ = state.pop(); // done at this (and belows) level, continue in parent
1441 }
1442 }
1443 } else {
1444 // first next call ever: initialize state vector and start iterating at our level
1445 let mut state = Vec::with_capacity(pbs_api_types::MAX_NAMESPACE_DEPTH);
1446 match ListNamespaces::new(Arc::clone(&self.store), self.ns.to_owned()) {
1447 Ok(list_ns) => state.push(list_ns),
1448 Err(err) => {
1449 // yield the error but set the state to Some to avoid re-try, a future
1450 // next() will then see the state, and the empty check yield's None
1451 self.state = Some(state);
1452 return Some(Err(err));
1453 }
1454 }
1455 self.state = Some(state);
1456 return Some(Ok(self.ns.to_owned())); // return our anchor ns for convenience
1457 }
1458 }
1459 }
1460}