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