]> git.proxmox.com Git - proxmox-backup.git/blame - src/tape/helpers/snapshot_reader.rs
tape: add helper to read snapshot contents
[proxmox-backup.git] / src / tape / helpers / 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
9use crate::{
10 tools::fs::lock_dir_noblock_shared,
11 backup::{
12 DataStore,
13 BackupDir,
14 ArchiveType,
15 IndexFile,
16 FixedIndexReader,
17 DynamicIndexReader,
18 MANIFEST_BLOB_NAME,
19 CLIENT_LOG_BLOB_NAME,
20 archive_type,
21 },
22};
23
24/// Helper to access the contents of a datastore backup snapshot
25///
26/// This make it easy to iterate over all used chunks and files.
27pub struct SnapshotReader {
28 file_list: Vec<String>,
29 locked_dir: Dir,
30}
31
32impl SnapshotReader {
33
34 /// Lock snapshot, reads the manifest and returns a new instance
35 pub fn new(datastore: Arc<DataStore>, snapshot: BackupDir) -> Result<Self, Error> {
36
37 let snapshot_path = datastore.snapshot_path(&snapshot);
38
39 let locked_dir = lock_dir_noblock_shared(
40 &snapshot_path,
41 "snapshot",
42 "locked by another operation")?;
43
44 let manifest = match datastore.load_manifest(&snapshot) {
45 Ok((manifest, _)) => manifest,
46 Err(err) => {
47 bail!("manifest load error on datastore '{}' snapshot '{}' - {}",
48 datastore.name(), snapshot, err);
49 }
50 };
51
52 let mut client_log_path = snapshot_path.clone();
53 client_log_path.push(CLIENT_LOG_BLOB_NAME);
54
55 let mut file_list = Vec::new();
56 file_list.push(MANIFEST_BLOB_NAME.to_string());
57 for item in manifest.files() { file_list.push(item.filename.clone()); }
58 if client_log_path.exists() {
59 file_list.push(CLIENT_LOG_BLOB_NAME.to_string());
60 }
61
62 Ok(Self { file_list, locked_dir })
63 }
64
65 /// Returns the list of files the snapshot refers to.
66 pub fn file_list(&self) -> &Vec<String> {
67 &self.file_list
68 }
69
70 /// Opens a file inside the snapshot (using openat) for reading
71 pub fn open_file(&self, filename: &str) -> Result<File, Error> {
72 let raw_fd = nix::fcntl::openat(
73 self.locked_dir.as_raw_fd(),
74 Path::new(filename),
75 nix::fcntl::OFlag::O_RDONLY,
76 nix::sys::stat::Mode::empty(),
77 )?;
78 let file = unsafe { File::from_raw_fd(raw_fd) };
79 Ok(file)
80 }
81
82 /// Retunrs an iterator for all used chunks.
83 pub fn chunk_iterator(&self) -> Result<SnapshotChunkIterator, Error> {
84 SnapshotChunkIterator::new(&self)
85 }
86}
87
88/// Iterates over all chunks used by a backup snapshot
89///
90/// Note: The iterator returns a `Result`, and the iterator state is
91/// undefined after the first error. So it make no sense to continue
92/// iteration after the first error.
93#[derive(Clone)]
94pub struct SnapshotChunkIterator<'a> {
95 snapshot_reader: &'a SnapshotReader,
96 todo_list: Vec<String>,
97 current_index: Option<(Arc<Box<dyn IndexFile>>, usize)>,
98}
99
100impl <'a> Iterator for SnapshotChunkIterator<'a> {
101 type Item = Result<[u8; 32], Error>;
102
103 fn next(&mut self) -> Option<Self::Item> {
104 proxmox::try_block!({
105 loop {
106 if self.current_index.is_none() {
107 if let Some(filename) = self.todo_list.pop() {
108 let file = self.snapshot_reader.open_file(&filename)?;
109 let index: Box<dyn IndexFile> = match archive_type(&filename)? {
110 ArchiveType::FixedIndex => Box::new(FixedIndexReader::new(file)?),
111 ArchiveType::DynamicIndex => Box::new(DynamicIndexReader::new(file)?),
112 _ => bail!("SnapshotChunkIterator: got unknown file type - internal error"),
113 };
114 self.current_index = Some((Arc::new(index), 0));
115 } else {
116 return Ok(None);
117 }
118 }
119 let (index, pos) = self.current_index.take().unwrap();
120 if pos < index.index_count() {
121 let digest = *index.index_digest(pos).unwrap();
122 self.current_index = Some((index, pos + 1));
123 return Ok(Some(digest));
124 } else {
125 // pop next index
126 }
127 }
128 }).transpose()
129 }
130}
131
132impl <'a> SnapshotChunkIterator<'a> {
133
134 pub fn new(snapshot_reader: &'a SnapshotReader) -> Result<Self, Error> {
135
136 let mut todo_list = Vec::new();
137
138 for filename in snapshot_reader.file_list() {
139 match archive_type(filename)? {
140 ArchiveType::FixedIndex | ArchiveType::DynamicIndex => {
141 todo_list.push(filename.to_owned());
142 },
143 ArchiveType::Blob => { /* no chunks, do nothing */ },
144 }
145 }
146
147 Ok(Self { snapshot_reader, todo_list, current_index: None })
148 }
149}