]> git.proxmox.com Git - proxmox-backup.git/blob - src/backup/snapshot_reader.rs
d64d6f00385ad9c9b5d5b0520d4a4ffb5388e6eb
[proxmox-backup.git] / src / backup / snapshot_reader.rs
1 use std::path::Path;
2 use std::sync::Arc;
3 use std::os::unix::io::{AsRawFd, FromRawFd};
4 use std::fs::File;
5
6 use anyhow::{bail, Error};
7 use nix::dir::Dir;
8
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;
15
16 use crate::backup::DataStore;
17
18 /// Helper to access the contents of a datastore backup snapshot
19 ///
20 /// This make it easy to iterate over all used chunks and files.
21 pub struct SnapshotReader {
22 snapshot: BackupDir,
23 datastore_name: String,
24 file_list: Vec<String>,
25 locked_dir: Dir,
26 }
27
28 impl SnapshotReader {
29
30 /// Lock snapshot, reads the manifest and returns a new instance
31 pub fn new(datastore: Arc<DataStore>, snapshot: BackupDir) -> Result<Self, Error> {
32
33 let snapshot_path = datastore.snapshot_path(&snapshot);
34
35 let locked_dir = lock_dir_noblock_shared(
36 &snapshot_path,
37 "snapshot",
38 "locked by another operation")?;
39
40 let datastore_name = datastore.name().to_string();
41
42 let manifest = match datastore.load_manifest(&snapshot) {
43 Ok((manifest, _)) => manifest,
44 Err(err) => {
45 bail!("manifest load error on datastore '{}' snapshot '{}' - {}",
46 datastore_name, snapshot, err);
47 }
48 };
49
50 let mut client_log_path = snapshot_path;
51 client_log_path.push(CLIENT_LOG_BLOB_NAME);
52
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());
58 }
59
60 Ok(Self { snapshot, datastore_name, file_list, locked_dir })
61 }
62
63 /// Return the snapshot directory
64 pub fn snapshot(&self) -> &BackupDir {
65 &self.snapshot
66 }
67
68 /// Return the datastore name
69 pub fn datastore_name(&self) -> &str {
70 &self.datastore_name
71 }
72
73 /// Returns the list of files the snapshot refers to.
74 pub fn file_list(&self) -> &Vec<String> {
75 &self.file_list
76 }
77
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(),
82 Path::new(filename),
83 nix::fcntl::OFlag::O_RDONLY,
84 nix::sys::stat::Mode::empty(),
85 )?;
86 let file = unsafe { File::from_raw_fd(raw_fd) };
87 Ok(file)
88 }
89
90 /// Returns an iterator for all used chunks.
91 pub fn chunk_iterator(&self) -> Result<SnapshotChunkIterator, Error> {
92 SnapshotChunkIterator::new(&self)
93 }
94 }
95
96 /// Iterates over all chunks used by a backup snapshot
97 ///
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)>)>,
105 }
106
107 impl <'a> Iterator for SnapshotChunkIterator<'a> {
108 type Item = Result<[u8; 32], Error>;
109
110 fn next(&mut self) -> Option<Self::Item> {
111 proxmox::try_block!({
112 loop {
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"),
120 };
121
122 let datastore =
123 DataStore::lookup_datastore(self.snapshot_reader.datastore_name())?;
124 let order = datastore.get_chunks_in_order(&index, |_| false, |_| Ok(()))?;
125
126 self.current_index = Some((Arc::new(index), 0, order));
127 } else {
128 return Ok(None);
129 }
130 }
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));
137 } else {
138 // pop next index
139 }
140 }
141 }).transpose()
142 }
143 }
144
145 impl <'a> SnapshotChunkIterator<'a> {
146
147 pub fn new(snapshot_reader: &'a SnapshotReader) -> Result<Self, Error> {
148
149 let mut todo_list = Vec::new();
150
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());
155 },
156 ArchiveType::Blob => { /* no chunks, do nothing */ },
157 }
158 }
159
160 Ok(Self { snapshot_reader, todo_list, current_index: None })
161 }
162 }