1#![forbid(unsafe_code)]
22#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))]
23
24use std::collections::HashSet;
25use std::io::{Read, Seek, SeekFrom};
26
27pub mod block;
28mod bytes;
29pub mod catalog;
30pub mod error;
31pub mod guid;
32pub mod reconstruct;
33pub mod store;
34
35#[cfg(test)]
36mod tests;
37
38pub use block::{BlockDescriptor, BlockDescriptorFlags, StoreBlockRange};
39pub use catalog::{StoreDescriptor, VolumeHeader};
40pub use error::VssError;
41pub use guid::{format_guid, VSS_IDENTIFIER};
42pub use reconstruct::Snapshot;
43pub use store::{AttributeFlags, StoreBlockHeader, StoreInfo};
44
45use catalog::{
46 catalog_next_block_offset, is_catalog_block, parse_catalog_entry, CatalogEntry, BLOCK_SIZE,
47 CATALOG_BLOCK_HEADER_LEN, CATALOG_ENTRY_LEN, MAX_CATALOG_BLOCKS, VSS_VOLUME_HEADER_OFFSET,
48};
49use store::{MAX_STORE_INFO_LEN, STORE_BLOCK_HEADER_LEN};
50
51#[derive(Debug)]
58pub struct VssVolume<R> {
59 reader: R,
60 volume_size: u64,
61 has_vss_header: bool,
62 catalog_offset: u64,
63 stores: Vec<StoreDescriptor>,
64}
65
66impl<R: Read + Seek> VssVolume<R> {
67 pub fn open(mut reader: R) -> Result<Self, VssError> {
77 let volume_size = reader.seek(SeekFrom::End(0))?;
78
79 let mut has_vss_header = false;
80 let mut catalog_offset = 0u64;
81 if volume_size >= VSS_VOLUME_HEADER_OFFSET + CATALOG_BLOCK_HEADER_LEN as u64 {
82 reader.seek(SeekFrom::Start(VSS_VOLUME_HEADER_OFFSET))?;
83 let mut hdr = [0u8; CATALOG_BLOCK_HEADER_LEN];
84 reader.read_exact(&mut hdr)?;
85 let vh = VolumeHeader::parse(&hdr);
86 has_vss_header = vh.has_vss_identifier;
87 catalog_offset = vh.catalog_offset;
88 }
89
90 let stores = if has_vss_header && catalog_offset != 0 {
91 walk_catalog(&mut reader, catalog_offset, volume_size)?
92 } else {
93 Vec::new()
94 };
95
96 Ok(VssVolume {
97 reader,
98 volume_size,
99 has_vss_header,
100 catalog_offset,
101 stores,
102 })
103 }
104
105 #[must_use]
107 pub fn has_vss_header(&self) -> bool {
108 self.has_vss_header
109 }
110
111 #[must_use]
113 pub fn stores(&self) -> &[StoreDescriptor] {
114 &self.stores
115 }
116
117 #[must_use]
119 pub fn store_count(&self) -> usize {
120 self.stores.len()
121 }
122
123 #[must_use]
125 pub fn catalog_offset(&self) -> u64 {
126 self.catalog_offset
127 }
128
129 #[must_use]
131 pub fn volume_size(&self) -> u64 {
132 self.volume_size
133 }
134
135 pub fn store_info(&mut self, index: usize) -> Result<StoreInfo, VssError> {
145 let count = self.stores.len();
146 let header_off = self
147 .stores
148 .get(index)
149 .ok_or(VssError::StoreIndexOutOfRange { index, count })?
150 .store_header_offset
151 .ok_or(VssError::StoreInfoUnavailable { index })?;
152
153 let header_end = header_off
156 .checked_add(STORE_BLOCK_HEADER_LEN as u64)
157 .filter(|end| *end <= self.volume_size)
158 .ok_or(VssError::StoreOffsetOutOfBounds {
159 index,
160 offset: header_off,
161 volume_size: self.volume_size,
162 })?;
163
164 self.reader.seek(SeekFrom::Start(header_off))?;
165 let mut hbuf = [0u8; STORE_BLOCK_HEADER_LEN];
166 self.reader.read_exact(&mut hbuf)?;
167 let header = StoreBlockHeader::parse(&hbuf);
168
169 let remaining = self.volume_size.saturating_sub(header_end) as usize;
172 let want = usize::try_from(header.store_information_size).unwrap_or(usize::MAX);
173 let info_len = want.min(MAX_STORE_INFO_LEN).min(remaining);
174 let mut ibuf = vec![0u8; info_len];
175 self.reader.read_exact(&mut ibuf)?;
176 Ok(StoreInfo::parse(&ibuf))
177 }
178}
179
180fn walk_catalog<R: Read + Seek>(
189 reader: &mut R,
190 first: u64,
191 volume_size: u64,
192) -> Result<Vec<StoreDescriptor>, VssError> {
193 let mut stores: Vec<StoreDescriptor> = Vec::new();
194 let mut visited: HashSet<u64> = HashSet::new();
195 let mut next = first;
196 let mut blocks = 0usize;
197
198 while next != 0 && blocks < MAX_CATALOG_BLOCKS {
199 if !visited.insert(next) {
200 break; }
202 match next.checked_add(BLOCK_SIZE as u64) {
203 Some(end) if end <= volume_size => {}
204 _ => break, }
206
207 reader.seek(SeekFrom::Start(next))?;
208 let mut block = vec![0u8; BLOCK_SIZE];
209 reader.read_exact(&mut block)?;
210 if !is_catalog_block(&block) {
211 break;
212 }
213
214 let mut off = CATALOG_BLOCK_HEADER_LEN;
215 while off + CATALOG_ENTRY_LEN <= BLOCK_SIZE {
216 match parse_catalog_entry(&block[off..off + CATALOG_ENTRY_LEN]) {
217 CatalogEntry::Snapshot(descriptor) => stores.push(descriptor),
218 CatalogEntry::StorePointer {
219 store_id,
220 store_header_offset,
221 store_bitmap_offset,
222 } => {
223 if let Some(descriptor) = stores
224 .iter_mut()
225 .rev()
226 .find(|d| d.store_id == store_id && d.store_header_offset.is_none())
227 {
228 descriptor.store_header_offset = Some(store_header_offset);
229 descriptor.store_bitmap_offset = Some(store_bitmap_offset);
230 }
231 }
232 CatalogEntry::Empty | CatalogEntry::Other => {}
233 }
234 off += CATALOG_ENTRY_LEN;
235 }
236
237 blocks += 1;
238 next = catalog_next_block_offset(&block);
239 }
240
241 Ok(stores)
242}