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