Skip to main content

vhdx/
section.rs

1use crate::error::{Error, Result};
2use crate::medium::Medium;
3use std::io::{Read, Seek};
4use std::sync::{Arc, OnceLock};
5
6// Re-exports for convenience — `vhdx::section::*` mirrors the old section.rs API
7pub use crate::header::{
8    FileTypeIdentifier, Header, HeaderStructure, RegionTable, RegionTableEntry, RegionTableHeader,
9};
10
11// BAT section types
12pub use crate::bat::{Bat, BatEntry, BatState, PayloadBlockState, SectorBitmapState};
13
14// Metadata section types
15pub use crate::metadata::{
16    EntryFlags, FileParameters, KeyValueEntry, LocatorHeader, Metadata, MetadataItems,
17    MetadataTable, ParentLocator, StandardItems, TableEntry, TableHeader,
18};
19
20// Log section types
21pub use crate::log::{
22    DataDescriptor, DataSector, Descriptor, Entry, Log, LogEntryHeader, ZeroDescriptor,
23};
24
25/// Container for all VHDX sections.
26///
27/// This struct holds lightweight views of cached section data and provides
28/// parsed views of the header, BAT, metadata, and log sections on every call.
29pub struct Sections<'a, T = std::fs::File> {
30    header: Arc<[u8]>,
31    medium: &'a Medium<T>,
32    bat: OnceLock<Arc<[u8]>>,
33    metadata: OnceLock<Arc<[u8]>>,
34    log: OnceLock<Arc<[u8]>>,
35}
36
37impl<'a, T> Sections<'a, T>
38where
39    T: Read + Seek,
40{
41    /// Create a new Sections view from cached section buffers.
42    pub(crate) fn new(header: Arc<[u8]>, medium: &'a Medium<T>) -> Result<Self> {
43        Header::new(&header)?;
44        Ok(Self {
45            header,
46            medium,
47            bat: OnceLock::new(),
48            metadata: OnceLock::new(),
49            log: OnceLock::new(),
50        })
51    }
52
53    // ------------------------------------------------------------------
54    // Section accessors
55    // ------------------------------------------------------------------
56
57    /// Parse and return the Header section view.
58    ///
59    /// The header section includes the file type identifier, both VHDX headers,
60    /// and both region tables.
61    ///
62    /// # Errors
63    ///
64    /// Returns an error if sections are uninitialized or header parsing fails.
65    pub fn header(&self) -> Result<Header<'_>> {
66        Header::new(&self.header)
67    }
68
69    /// Parse and return the BAT (Block Allocation Table) section view.
70    ///
71    /// Parses the cached BAT region and uses the chunk ratio computed from the
72    /// cached metadata buffer.
73    ///
74    /// # Errors
75    ///
76    /// Returns an error if sections are uninitialized, BAT loading fails,
77    /// or metadata needed for chunk ratio is invalid.
78    pub fn bat(&self) -> Result<Bat<'_>> {
79        if self.metadata.get().is_none() {
80            let _ = self.metadata.set(self.medium.metadata_buf()?);
81        }
82        if self.bat.get().is_none() {
83            let _ = self.bat.set(self.medium.bat_buf()?);
84        }
85        let metadata = self
86            .metadata
87            .get()
88            .ok_or_else(|| Error::InvalidMetadata("metadata cache not loaded".into()))?;
89        let chunk_ratio = Self::compute_chunk_ratio(metadata)?;
90        let bat = self
91            .bat
92            .get()
93            .ok_or_else(|| Error::InvalidFile("BAT cache not loaded".into()))?;
94        Ok(Bat::new(bat, chunk_ratio))
95    }
96
97    /// Parse and return the Metadata section view.
98    ///
99    /// Parses the cached metadata region.
100    ///
101    /// # Errors
102    ///
103    /// Returns an error if sections are uninitialized or metadata parsing fails.
104    pub fn metadata(&self) -> Result<Metadata<'_>> {
105        if self.metadata.get().is_none() {
106            let _ = self.metadata.set(self.medium.metadata_buf()?);
107        }
108        let metadata = self
109            .metadata
110            .get()
111            .ok_or_else(|| Error::InvalidMetadata("metadata cache not loaded".into()))?;
112        Metadata::new(metadata)
113    }
114
115    /// Parse and return the Log section view.
116    ///
117    /// Parses the cached log region.
118    ///
119    /// # Errors
120    ///
121    /// Returns an error if sections are uninitialized or log parsing fails.
122    pub fn log(&self) -> Result<Log<'_>> {
123        if self.log.get().is_none() {
124            let _ = self.log.set(self.medium.log_buf()?);
125        }
126        let log = self
127            .log
128            .get()
129            .ok_or_else(|| Error::InvalidFile("log cache not loaded".into()))?;
130        Log::new(log)
131    }
132
133    // ------------------------------------------------------------------
134    // Internal helpers
135    // ------------------------------------------------------------------
136
137    /// Compute the chunk ratio from metadata:
138    /// `chunk_ratio = (2^23 * LogicalSectorSize) / BlockSize`.
139    fn compute_chunk_ratio(meta_buf: &[u8]) -> Result<u64> {
140        let metadata = Metadata::new(meta_buf)?;
141        let items = metadata.items();
142        let fp = items
143            .file_parameters()
144            .map_err(|_| Error::InvalidMetadata("FileParameters metadata item not found".into()))?;
145        let block_size = u64::from(fp.block_size());
146        if block_size == 0 {
147            return Err(Error::InvalidMetadata("block size must be non-zero".into()));
148        }
149        let logical_sector_size = u64::from(items.logical_sector_size().map_err(|_| {
150            Error::InvalidMetadata("LogicalSectorSize metadata item not found".into())
151        })?);
152        Ok(crate::common::compute_chunk_ratio(
153            block_size,
154            logical_sector_size,
155        ))
156    }
157}