]>
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 | ||
9 | use 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. | |
27 | pub struct SnapshotReader { | |
28 | file_list: Vec<String>, | |
29 | locked_dir: Dir, | |
30 | } | |
31 | ||
32 | impl 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)] | |
94 | pub struct SnapshotChunkIterator<'a> { | |
95 | snapshot_reader: &'a SnapshotReader, | |
96 | todo_list: Vec<String>, | |
97 | current_index: Option<(Arc<Box<dyn IndexFile>>, usize)>, | |
98 | } | |
99 | ||
100 | impl <'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 | ||
132 | impl <'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 | } |