]> git.proxmox.com Git - proxmox-backup.git/blame - pbs-datastore/src/snapshot_reader.rs
tree-wide: fix needless borrows
[proxmox-backup.git] / pbs-datastore / src / snapshot_reader.rs
CommitLineData
b532dd00
DM
1use std::path::Path;
2use std::sync::Arc;
3use std::os::unix::io::{AsRawFd, FromRawFd};
4use std::fs::File;
5
6use anyhow::{bail, Error};
7use nix::dir::Dir;
8
25877d05
DM
9use proxmox_sys::fs::lock_dir_noblock_shared;
10
c95c1c83
DM
11use crate::backup_info::BackupDir;
12use crate::index::IndexFile;
13use crate::fixed_index::FixedIndexReader;
14use crate::dynamic_index::DynamicIndexReader;
15use crate::manifest::{archive_type, ArchiveType, CLIENT_LOG_BLOB_NAME, MANIFEST_BLOB_NAME};
16use crate::DataStore;
770a36e5 17
b532dd00
DM
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.
21pub struct SnapshotReader {
b9ee86ef 22 snapshot: BackupDir,
0e2bf3aa 23 datastore_name: String,
b532dd00
DM
24 file_list: Vec<String>,
25 locked_dir: Dir,
26}
27
28impl 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
0e2bf3aa
DM
40 let datastore_name = datastore.name().to_string();
41
b532dd00
DM
42 let manifest = match datastore.load_manifest(&snapshot) {
43 Ok((manifest, _)) => manifest,
44 Err(err) => {
45 bail!("manifest load error on datastore '{}' snapshot '{}' - {}",
0e2bf3aa 46 datastore_name, snapshot, err);
b532dd00
DM
47 }
48 };
49
44288184 50 let mut client_log_path = snapshot_path;
b532dd00
DM
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
0e2bf3aa 60 Ok(Self { snapshot, datastore_name, file_list, locked_dir })
b9ee86ef
DM
61 }
62
63 /// Return the snapshot directory
64 pub fn snapshot(&self) -> &BackupDir {
65 &self.snapshot
b532dd00
DM
66 }
67
0e2bf3aa
DM
68 /// Return the datastore name
69 pub fn datastore_name(&self) -> &str {
70 &self.datastore_name
71 }
72
b532dd00
DM
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
d1d74c43 90 /// Returns an iterator for all used chunks.
b532dd00 91 pub fn chunk_iterator(&self) -> Result<SnapshotChunkIterator, Error> {
9a37bd6c 92 SnapshotChunkIterator::new(self)
b532dd00
DM
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.
b532dd00
DM
101pub struct SnapshotChunkIterator<'a> {
102 snapshot_reader: &'a SnapshotReader,
103 todo_list: Vec<String>,
3d376983 104 current_index: Option<(Arc<Box<dyn IndexFile + Send>>, usize, Vec<(usize, u64)>)>,
b532dd00
DM
105}
106
107impl <'a> Iterator for SnapshotChunkIterator<'a> {
108 type Item = Result<[u8; 32], Error>;
109
110 fn next(&mut self) -> Option<Self::Item> {
6ef1b649 111 proxmox_lang::try_block!({
b532dd00
DM
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)?;
3d376983 116 let index: Box<dyn IndexFile + Send> = match archive_type(&filename)? {
b532dd00
DM
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 };
3d376983
DC
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));
b532dd00
DM
127 } else {
128 return Ok(None);
129 }
130 }
3d376983
DC
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));
b532dd00
DM
136 return Ok(Some(digest));
137 } else {
138 // pop next index
139 }
140 }
141 }).transpose()
142 }
143}
144
145impl <'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}