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