3 use std
::os
::unix
::io
::{AsRawFd, FromRawFd}
;
6 use anyhow
::{bail, Error}
;
9 use pbs_datastore
::backup_info
::BackupDir
;
10 use pbs_datastore
::index
::IndexFile
;
11 use pbs_datastore
::fixed_index
::FixedIndexReader
;
12 use pbs_datastore
::dynamic_index
::DynamicIndexReader
;
13 use pbs_datastore
::manifest
::{archive_type, ArchiveType, CLIENT_LOG_BLOB_NAME, MANIFEST_BLOB_NAME}
;
14 use pbs_tools
::fs
::lock_dir_noblock_shared
;
16 use crate::backup
::DataStore
;
18 /// Helper to access the contents of a datastore backup snapshot
20 /// This make it easy to iterate over all used chunks and files.
21 pub struct SnapshotReader
{
23 datastore_name
: String
,
24 file_list
: Vec
<String
>,
30 /// Lock snapshot, reads the manifest and returns a new instance
31 pub fn new(datastore
: Arc
<DataStore
>, snapshot
: BackupDir
) -> Result
<Self, Error
> {
33 let snapshot_path
= datastore
.snapshot_path(&snapshot
);
35 let locked_dir
= lock_dir_noblock_shared(
38 "locked by another operation")?
;
40 let datastore_name
= datastore
.name().to_string();
42 let manifest
= match datastore
.load_manifest(&snapshot
) {
43 Ok((manifest
, _
)) => manifest
,
45 bail
!("manifest load error on datastore '{}' snapshot '{}' - {}",
46 datastore_name
, snapshot
, err
);
50 let mut client_log_path
= snapshot_path
;
51 client_log_path
.push(CLIENT_LOG_BLOB_NAME
);
53 let mut file_list
= Vec
::new();
54 file_list
.push(MANIFEST_BLOB_NAME
.to_string());
55 for item
in manifest
.files() { file_list.push(item.filename.clone()); }
56 if client_log_path
.exists() {
57 file_list
.push(CLIENT_LOG_BLOB_NAME
.to_string());
60 Ok(Self { snapshot, datastore_name, file_list, locked_dir }
)
63 /// Return the snapshot directory
64 pub fn snapshot(&self) -> &BackupDir
{
68 /// Return the datastore name
69 pub fn datastore_name(&self) -> &str {
73 /// Returns the list of files the snapshot refers to.
74 pub fn file_list(&self) -> &Vec
<String
> {
78 /// Opens a file inside the snapshot (using openat) for reading
79 pub fn open_file(&self, filename
: &str) -> Result
<File
, Error
> {
80 let raw_fd
= nix
::fcntl
::openat(
81 self.locked_dir
.as_raw_fd(),
83 nix
::fcntl
::OFlag
::O_RDONLY
,
84 nix
::sys
::stat
::Mode
::empty(),
86 let file
= unsafe { File::from_raw_fd(raw_fd) }
;
90 /// Returns an iterator for all used chunks.
91 pub fn chunk_iterator(&self) -> Result
<SnapshotChunkIterator
, Error
> {
92 SnapshotChunkIterator
::new(&self)
96 /// Iterates over all chunks used by a backup snapshot
98 /// Note: The iterator returns a `Result`, and the iterator state is
99 /// undefined after the first error. So it make no sense to continue
100 /// iteration after the first error.
101 pub struct SnapshotChunkIterator
<'a
> {
102 snapshot_reader
: &'a SnapshotReader
,
103 todo_list
: Vec
<String
>,
104 current_index
: Option
<(Arc
<Box
<dyn IndexFile
+ Send
>>, usize, Vec
<(usize, u64)>)>,
107 impl <'a
> Iterator
for SnapshotChunkIterator
<'a
> {
108 type Item
= Result
<[u8; 32], Error
>;
110 fn next(&mut self) -> Option
<Self::Item
> {
111 proxmox
::try_block
!({
113 if self.current_index
.is_none() {
114 if let Some(filename
) = self.todo_list
.pop() {
115 let file
= self.snapshot_reader
.open_file(&filename
)?
;
116 let index
: Box
<dyn IndexFile
+ Send
> = match archive_type(&filename
)?
{
117 ArchiveType
::FixedIndex
=> Box
::new(FixedIndexReader
::new(file
)?
),
118 ArchiveType
::DynamicIndex
=> Box
::new(DynamicIndexReader
::new(file
)?
),
119 _
=> bail
!("SnapshotChunkIterator: got unknown file type - internal error"),
123 DataStore
::lookup_datastore(self.snapshot_reader
.datastore_name())?
;
124 let order
= datastore
.get_chunks_in_order(&index
, |_
| false, |_
| Ok(()))?
;
126 self.current_index
= Some((Arc
::new(index
), 0, order
));
131 let (index
, pos
, list
) = self.current_index
.take().unwrap();
132 if pos
< list
.len() {
133 let (real_pos
, _
) = list
[pos
];
134 let digest
= *index
.index_digest(real_pos
).unwrap();
135 self.current_index
= Some((index
, pos
+ 1, list
));
136 return Ok(Some(digest
));
145 impl <'a
> SnapshotChunkIterator
<'a
> {
147 pub fn new(snapshot_reader
: &'a SnapshotReader
) -> Result
<Self, Error
> {
149 let mut todo_list
= Vec
::new();
151 for filename
in snapshot_reader
.file_list() {
152 match archive_type(filename
)?
{
153 ArchiveType
::FixedIndex
| ArchiveType
::DynamicIndex
=> {
154 todo_list
.push(filename
.to_owned());
156 ArchiveType
::Blob
=> { /* no chunks, do nothing */ }
,
160 Ok(Self { snapshot_reader, todo_list, current_index: None }
)