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