]>
Commit | Line | Data |
---|---|---|
b532dd00 DM |
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 | ||
c95c1c83 DM |
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; | |
770a36e5 WB |
15 | use pbs_tools::fs::lock_dir_noblock_shared; |
16 | ||
b532dd00 DM |
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 { | |
b9ee86ef | 21 | snapshot: BackupDir, |
0e2bf3aa | 22 | datastore_name: String, |
b532dd00 DM |
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 | ||
0e2bf3aa DM |
39 | let datastore_name = datastore.name().to_string(); |
40 | ||
b532dd00 DM |
41 | let manifest = match datastore.load_manifest(&snapshot) { |
42 | Ok((manifest, _)) => manifest, | |
43 | Err(err) => { | |
44 | bail!("manifest load error on datastore '{}' snapshot '{}' - {}", | |
0e2bf3aa | 45 | datastore_name, snapshot, err); |
b532dd00 DM |
46 | } |
47 | }; | |
48 | ||
44288184 | 49 | let mut client_log_path = snapshot_path; |
b532dd00 DM |
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 | ||
0e2bf3aa | 59 | Ok(Self { snapshot, datastore_name, file_list, locked_dir }) |
b9ee86ef DM |
60 | } |
61 | ||
62 | /// Return the snapshot directory | |
63 | pub fn snapshot(&self) -> &BackupDir { | |
64 | &self.snapshot | |
b532dd00 DM |
65 | } |
66 | ||
0e2bf3aa DM |
67 | /// Return the datastore name |
68 | pub fn datastore_name(&self) -> &str { | |
69 | &self.datastore_name | |
70 | } | |
71 | ||
b532dd00 DM |
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 | ||
d1d74c43 | 89 | /// Returns an iterator for all used chunks. |
b532dd00 DM |
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. | |
b532dd00 DM |
100 | pub struct SnapshotChunkIterator<'a> { |
101 | snapshot_reader: &'a SnapshotReader, | |
102 | todo_list: Vec<String>, | |
3d376983 | 103 | current_index: Option<(Arc<Box<dyn IndexFile + Send>>, usize, Vec<(usize, u64)>)>, |
b532dd00 DM |
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> { | |
6ef1b649 | 110 | proxmox_lang::try_block!({ |
b532dd00 DM |
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)?; | |
3d376983 | 115 | let index: Box<dyn IndexFile + Send> = match archive_type(&filename)? { |
b532dd00 DM |
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 | }; | |
3d376983 DC |
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)); | |
b532dd00 DM |
126 | } else { |
127 | return Ok(None); | |
128 | } | |
129 | } | |
3d376983 DC |
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)); | |
b532dd00 DM |
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 | } |