#![forbid(unsafe_code)]
#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))]
use std::collections::HashSet;
use std::io::{Read, Seek, SeekFrom};
pub mod block;
mod bytes;
pub mod catalog;
pub mod error;
pub mod guid;
pub mod reconstruct;
pub mod store;
#[cfg(test)]
mod tests;
pub use block::{BlockDescriptor, BlockDescriptorFlags, StoreBlockRange};
pub use catalog::{StoreDescriptor, VolumeHeader};
pub use error::VssError;
pub use guid::{format_guid, VSS_IDENTIFIER};
pub use reconstruct::Snapshot;
pub use store::{AttributeFlags, StoreBlockHeader, StoreInfo};
use catalog::{
catalog_next_block_offset, is_catalog_block, parse_catalog_entry, CatalogEntry, BLOCK_SIZE,
CATALOG_BLOCK_HEADER_LEN, CATALOG_ENTRY_LEN, MAX_CATALOG_BLOCKS, VSS_VOLUME_HEADER_OFFSET,
};
use store::{MAX_STORE_INFO_LEN, STORE_BLOCK_HEADER_LEN};
#[derive(Debug)]
pub struct VssVolume<R> {
reader: R,
volume_size: u64,
has_vss_header: bool,
catalog_offset: u64,
stores: Vec<StoreDescriptor>,
}
impl<R: Read + Seek> VssVolume<R> {
pub fn open(mut reader: R) -> Result<Self, VssError> {
let volume_size = reader.seek(SeekFrom::End(0))?;
let mut has_vss_header = false;
let mut catalog_offset = 0u64;
if volume_size >= VSS_VOLUME_HEADER_OFFSET + CATALOG_BLOCK_HEADER_LEN as u64 {
reader.seek(SeekFrom::Start(VSS_VOLUME_HEADER_OFFSET))?;
let mut hdr = [0u8; CATALOG_BLOCK_HEADER_LEN];
reader.read_exact(&mut hdr)?;
let vh = VolumeHeader::parse(&hdr);
has_vss_header = vh.has_vss_identifier;
catalog_offset = vh.catalog_offset;
}
let stores = if has_vss_header && catalog_offset != 0 {
walk_catalog(&mut reader, catalog_offset, volume_size)?
} else {
Vec::new()
};
Ok(VssVolume {
reader,
volume_size,
has_vss_header,
catalog_offset,
stores,
})
}
#[must_use]
pub fn has_vss_header(&self) -> bool {
self.has_vss_header
}
#[must_use]
pub fn stores(&self) -> &[StoreDescriptor] {
&self.stores
}
#[must_use]
pub fn store_count(&self) -> usize {
self.stores.len()
}
#[must_use]
pub fn catalog_offset(&self) -> u64 {
self.catalog_offset
}
#[must_use]
pub fn volume_size(&self) -> u64 {
self.volume_size
}
pub fn store_info(&mut self, index: usize) -> Result<StoreInfo, VssError> {
let count = self.stores.len();
let header_off = self
.stores
.get(index)
.ok_or(VssError::StoreIndexOutOfRange { index, count })?
.store_header_offset
.ok_or(VssError::StoreInfoUnavailable { index })?;
let header_end = header_off
.checked_add(STORE_BLOCK_HEADER_LEN as u64)
.filter(|end| *end <= self.volume_size)
.ok_or(VssError::StoreOffsetOutOfBounds {
index,
offset: header_off,
volume_size: self.volume_size,
})?;
self.reader.seek(SeekFrom::Start(header_off))?;
let mut hbuf = [0u8; STORE_BLOCK_HEADER_LEN];
self.reader.read_exact(&mut hbuf)?;
let header = StoreBlockHeader::parse(&hbuf);
let remaining = self.volume_size.saturating_sub(header_end) as usize;
let want = usize::try_from(header.store_information_size).unwrap_or(usize::MAX);
let info_len = want.min(MAX_STORE_INFO_LEN).min(remaining);
let mut ibuf = vec![0u8; info_len];
self.reader.read_exact(&mut ibuf)?;
Ok(StoreInfo::parse(&ibuf))
}
}
fn walk_catalog<R: Read + Seek>(
reader: &mut R,
first: u64,
volume_size: u64,
) -> Result<Vec<StoreDescriptor>, VssError> {
let mut stores: Vec<StoreDescriptor> = Vec::new();
let mut visited: HashSet<u64> = HashSet::new();
let mut next = first;
let mut blocks = 0usize;
while next != 0 && blocks < MAX_CATALOG_BLOCKS {
if !visited.insert(next) {
break; }
match next.checked_add(BLOCK_SIZE as u64) {
Some(end) if end <= volume_size => {}
_ => break, }
reader.seek(SeekFrom::Start(next))?;
let mut block = vec![0u8; BLOCK_SIZE];
reader.read_exact(&mut block)?;
if !is_catalog_block(&block) {
break;
}
let mut off = CATALOG_BLOCK_HEADER_LEN;
while off + CATALOG_ENTRY_LEN <= BLOCK_SIZE {
match parse_catalog_entry(&block[off..off + CATALOG_ENTRY_LEN]) {
CatalogEntry::Snapshot(descriptor) => stores.push(descriptor),
CatalogEntry::StorePointer {
store_id,
store_header_offset,
store_bitmap_offset,
} => {
if let Some(descriptor) = stores
.iter_mut()
.rev()
.find(|d| d.store_id == store_id && d.store_header_offset.is_none())
{
descriptor.store_header_offset = Some(store_header_offset);
descriptor.store_bitmap_offset = Some(store_bitmap_offset);
}
}
CatalogEntry::Empty | CatalogEntry::Other => {}
}
off += CATALOG_ENTRY_LEN;
}
blocks += 1;
next = catalog_next_block_offset(&block);
}
Ok(stores)
}