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