3 use std
::os
::unix
::io
::{AsRawFd, FromRawFd}
;
6 use anyhow
::{bail, Error}
;
9 use crate::backup_info
::BackupDir
;
10 use crate::index
::IndexFile
;
11 use crate::fixed_index
::FixedIndexReader
;
12 use crate::dynamic_index
::DynamicIndexReader
;
13 use crate::manifest
::{archive_type, ArchiveType, CLIENT_LOG_BLOB_NAME, MANIFEST_BLOB_NAME}
;
15 use pbs_tools
::fs
::lock_dir_noblock_shared
;
17 /// Helper to access the contents of a datastore backup snapshot
19 /// This make it easy to iterate over all used chunks and files.
20 pub struct SnapshotReader
{
22 datastore_name
: String
,
23 file_list
: Vec
<String
>,
29 /// Lock snapshot, reads the manifest and returns a new instance
30 pub fn new(datastore
: Arc
<DataStore
>, snapshot
: BackupDir
) -> Result
<Self, Error
> {
32 let snapshot_path
= datastore
.snapshot_path(&snapshot
);
34 let locked_dir
= lock_dir_noblock_shared(
37 "locked by another operation")?
;
39 let datastore_name
= datastore
.name().to_string();
41 let manifest
= match datastore
.load_manifest(&snapshot
) {
42 Ok((manifest
, _
)) => manifest
,
44 bail
!("manifest load error on datastore '{}' snapshot '{}' - {}",
45 datastore_name
, snapshot
, err
);
49 let mut client_log_path
= snapshot_path
;
50 client_log_path
.push(CLIENT_LOG_BLOB_NAME
);
52 let mut file_list
= Vec
::new();
53 file_list
.push(MANIFEST_BLOB_NAME
.to_string());
54 for item
in manifest
.files() { file_list.push(item.filename.clone()); }
55 if client_log_path
.exists() {
56 file_list
.push(CLIENT_LOG_BLOB_NAME
.to_string());
59 Ok(Self { snapshot, datastore_name, file_list, locked_dir }
)
62 /// Return the snapshot directory
63 pub fn snapshot(&self) -> &BackupDir
{
67 /// Return the datastore name
68 pub fn datastore_name(&self) -> &str {
72 /// Returns the list of files the snapshot refers to.
73 pub fn file_list(&self) -> &Vec
<String
> {
77 /// Opens a file inside the snapshot (using openat) for reading
78 pub fn open_file(&self, filename
: &str) -> Result
<File
, Error
> {
79 let raw_fd
= nix
::fcntl
::openat(
80 self.locked_dir
.as_raw_fd(),
82 nix
::fcntl
::OFlag
::O_RDONLY
,
83 nix
::sys
::stat
::Mode
::empty(),
85 let file
= unsafe { File::from_raw_fd(raw_fd) }
;
89 /// Returns an iterator for all used chunks.
90 pub fn chunk_iterator(&self) -> Result
<SnapshotChunkIterator
, Error
> {
91 SnapshotChunkIterator
::new(&self)
95 /// Iterates over all chunks used by a backup snapshot
97 /// Note: The iterator returns a `Result`, and the iterator state is
98 /// undefined after the first error. So it make no sense to continue
99 /// iteration after the first error.
100 pub struct SnapshotChunkIterator
<'a
> {
101 snapshot_reader
: &'a SnapshotReader
,
102 todo_list
: Vec
<String
>,
103 current_index
: Option
<(Arc
<Box
<dyn IndexFile
+ Send
>>, usize, Vec
<(usize, u64)>)>,
106 impl <'a
> Iterator
for SnapshotChunkIterator
<'a
> {
107 type Item
= Result
<[u8; 32], Error
>;
109 fn next(&mut self) -> Option
<Self::Item
> {
110 proxmox_lang
::try_block
!({
112 if self.current_index
.is_none() {
113 if let Some(filename
) = self.todo_list
.pop() {
114 let file
= self.snapshot_reader
.open_file(&filename
)?
;
115 let index
: Box
<dyn IndexFile
+ Send
> = match archive_type(&filename
)?
{
116 ArchiveType
::FixedIndex
=> Box
::new(FixedIndexReader
::new(file
)?
),
117 ArchiveType
::DynamicIndex
=> Box
::new(DynamicIndexReader
::new(file
)?
),
118 _
=> bail
!("SnapshotChunkIterator: got unknown file type - internal error"),
122 DataStore
::lookup_datastore(self.snapshot_reader
.datastore_name())?
;
123 let order
= datastore
.get_chunks_in_order(&index
, |_
| false, |_
| Ok(()))?
;
125 self.current_index
= Some((Arc
::new(index
), 0, order
));
130 let (index
, pos
, list
) = self.current_index
.take().unwrap();
131 if pos
< list
.len() {
132 let (real_pos
, _
) = list
[pos
];
133 let digest
= *index
.index_digest(real_pos
).unwrap();
134 self.current_index
= Some((index
, pos
+ 1, list
));
135 return Ok(Some(digest
));
144 impl <'a
> SnapshotChunkIterator
<'a
> {
146 pub fn new(snapshot_reader
: &'a SnapshotReader
) -> Result
<Self, Error
> {
148 let mut todo_list
= Vec
::new();
150 for filename
in snapshot_reader
.file_list() {
151 match archive_type(filename
)?
{
152 ArchiveType
::FixedIndex
| ArchiveType
::DynamicIndex
=> {
153 todo_list
.push(filename
.to_owned());
155 ArchiveType
::Blob
=> { /* no chunks, do nothing */ }
,
159 Ok(Self { snapshot_reader, todo_list, current_index: None }
)