]>
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 | ||
25877d05 DM |
9 | use proxmox_sys::fs::lock_dir_noblock_shared; |
10 | ||
c95c1c83 DM |
11 | use crate::backup_info::BackupDir; |
12 | use crate::index::IndexFile; | |
13 | use crate::fixed_index::FixedIndexReader; | |
14 | use crate::dynamic_index::DynamicIndexReader; | |
15 | use crate::manifest::{archive_type, ArchiveType, CLIENT_LOG_BLOB_NAME, MANIFEST_BLOB_NAME}; | |
16 | use 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. | |
21 | pub struct SnapshotReader { | |
b9ee86ef | 22 | snapshot: BackupDir, |
0e2bf3aa | 23 | datastore_name: String, |
b532dd00 DM |
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 | ||
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 |
101 | pub 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 | ||
107 | impl <'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 | ||
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 | } |