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