Skip to main content

vhdx/
error.rs

1use crate::types::Guid;
2
3/// Signature error position.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum SignaturePosition {
6    FileTypeIdentifier,
7    Header,
8    RegionTable,
9    MetadataTable,
10    LogEntry,
11    Descriptor,
12    DataSector,
13}
14
15/// Pad a 4-byte signature to 8 bytes by zero-padding high bytes.
16///
17/// Used for `InvalidSignature` where the `expected` and `found` fields
18/// must be 8 bytes. 4-byte signatures (e.g. "head", "regi") are padded
19/// by setting the upper 4 bytes to zero.
20pub(crate) fn pad_signature_4to8(sig: [u8; 4]) -> [u8; 8] {
21    let mut out = [0u8; 8];
22    out[..4].copy_from_slice(&sig);
23    out
24}
25
26/// Error type for VHDX operations.
27#[derive(Debug, thiserror::Error)]
28#[non_exhaustive]
29pub enum Error {
30    #[error("IO error")]
31    Io(#[from] std::io::Error),
32
33    #[error("invalid VHDX file: {0}")]
34    InvalidFile(String),
35
36    #[error("signature mismatch at {position:?}: expected {expected:?}, found {found:?}")]
37    InvalidSignature {
38        position: SignaturePosition,
39        expected: [u8; 8],
40        found: [u8; 8],
41    },
42
43    #[error("corrupted header: {0}")]
44    CorruptedHeader(String),
45
46    #[error("header LogGuid mismatch: header1={header1_log_guid}, header2={header2_log_guid}")]
47    HeaderLogGuidMismatch {
48        header1_log_guid: Guid,
49        header2_log_guid: Guid,
50    },
51
52    #[error("header sequence number invalid: seq1={sequence_number_1}, seq2={sequence_number_2}")]
53    HeaderSequenceNumberInvalid {
54        sequence_number_1: u64,
55        sequence_number_2: u64,
56    },
57
58    #[error("unsupported VHDX version: {version}")]
59    UnsupportedVersion { version: u16 },
60
61    #[error("unsupported log version: {version}")]
62    UnsupportedLogVersion { version: u16 },
63
64    /// Header `LogLength` or `LogOffset` is not aligned to 1 MB.
65    ///
66    /// Standard: MS-VHDX-校验扩展标准 §4.1 — `HEADER_LOG_LENGTH_NOT_ALIGNED` / `HEADER_LOG_OFFSET_NOT_ALIGNED`
67    #[error("header log field not 1MB-aligned: {field}={value}")]
68    HeaderLogNotAligned {
69        /// Which field is misaligned ("`log_length`" or "`log_offset`").
70        field: String,
71        /// The value of the misaligned field.
72        value: u64,
73    },
74
75    #[error("checksum mismatch: expected {expected:#010x}, actual {actual:#010x}")]
76    InvalidChecksum { expected: u32, actual: u32 },
77
78    #[error("invalid BAT block state: {0:#04x}")]
79    InvalidBlockState(u8),
80
81    #[error("invalid sector bitmap block state: {0:#04x}")]
82    InvalidSectorBitmapState(u8),
83
84    /// BAT block state is valid but incompatible with the disk type
85    /// (e.g. Unmapped on non-differencing disk, or sector bitmap Present on non-differencing).
86    ///
87    /// Standard: MS-VHDX-校验扩展标准 §4.3 — `BAT_ENTRY_STATE_MISMATCH`
88    #[error("BAT state mismatch: state={state:#04x}, {description}")]
89    StateMismatch {
90        /// The raw 3-bit state value.
91        state: u8,
92        /// Human-readable description of the mismatch (e.g. "sector bitmap state not `NotPresent` on non-differencing disk").
93        description: String,
94    },
95
96    /// BAT entry file offset is not aligned to the block size boundary.
97    ///
98    /// Standard: MS-VHDX-校验扩展标准 §4.3 — `BAT_ENTRY_FILE_OFFSET_UNALIGNED`
99    #[error("BAT entry file offset not aligned: offset_mb={offset_mb}, block_size={block_size}")]
100    BatFileOffsetUnaligned {
101        /// The file offset in MB from the BAT entry.
102        offset_mb: u64,
103        /// The block size in bytes.
104        block_size: u32,
105    },
106
107    #[error("BAT entry count insufficient: actual={actual}, expected={expected}")]
108    BatEntryCountInsufficient { actual: u64, expected: u64 },
109
110    #[error("BAT file offset duplicate: offset_mb={offset_mb}")]
111    BatFileOffsetDuplicate { offset_mb: u64 },
112
113    #[error("invalid region table: {0}")]
114    InvalidRegionTable(String),
115
116    #[error("unknown required region: {guid}")]
117    RegionRequiredUnknown { guid: Guid },
118
119    #[error("unknown optional region: {guid}")]
120    RegionOptionalUnknown { guid: Guid },
121
122    #[error("invalid metadata: {0}")]
123    InvalidMetadata(String),
124
125    #[error("unknown metadata GUID: {guid}")]
126    MetadataGuidUnknown { guid: Guid },
127
128    #[error("required metadata item missing: {guid}")]
129    MetadataRequiredMissing { guid: Guid },
130
131    #[error("unknown required metadata item: {guid}")]
132    MetadataRequiredUnknown { guid: Guid },
133
134    #[error("unknown optional metadata item: {guid}")]
135    MetadataOptionalUnknown { guid: Guid },
136
137    #[error("metadata reserved flags set: {flags:#010x}")]
138    MetadataReservedFlagsSet { flags: u32 },
139
140    /// Metadata Table Entry Reserved field is not zero.
141    ///
142    /// Standard: MS-VHDX-校验扩展标准 §4.4 — `METADATA_ENTRY_RESERVED_NONZERO`
143    #[error("metadata entry reserved field non-zero: {reserved:#010x}")]
144    MetadataEntryReservedNonzero { reserved: u32 },
145
146    /// `FileParameters` item reserved flags (bits 2-31) are set.
147    ///
148    /// Standard: MS-VHDX-校验扩展标准 §4.4 — `METADATA_FILE_PARAMETERS_RESERVED_FLAGS`
149    #[error("FileParameters reserved flags set: {flags:#010x}")]
150    FileParametersReservedFlags { flags: u32 },
151
152    #[error("invalid parent locator: {0}")]
153    InvalidParentLocator(String),
154
155    #[error("metadata item not found: {guid}")]
156    MetadataNotFound { guid: Guid },
157
158    #[error("log replay is required")]
159    LogReplayRequired,
160
161    #[error("corrupted log entry: {0}")]
162    LogEntryCorrupted(String),
163
164    #[error("log sequence gap: expected={expected}, found={found}")]
165    LogSequenceGap { expected: u64, found: u64 },
166
167    #[error("log sequence GUID mismatch: entry={entry_log_guid}, header={header_log_guid}")]
168    LogSequenceGuidMismatch {
169        entry_log_guid: Guid,
170        header_log_guid: Guid,
171    },
172
173    #[error("log active sequence empty")]
174    LogActiveSequenceEmpty,
175
176    #[error("BAT entry not found: index {index}")]
177    BatEntryNotFound { index: u64 },
178
179    #[error("block not present: block {block_idx}, state={state}")]
180    BlockNotPresent { block_idx: u64, state: String },
181
182    #[error("sector out of bounds: {sector} (max={max})")]
183    SectorOutOfBounds { sector: u64, max: u64 },
184
185    #[error("parent disk not found")]
186    ParentNotFound,
187
188    #[error("parent resolver required")]
189    ParentResolverRequired,
190
191    #[error("parent logical sector size mismatch: child={child}, parent={parent}")]
192    ParentSectorSizeMismatch { child: u32, parent: u32 },
193
194    #[error("parent GUID mismatch: expected {expected}, found {actual}")]
195    ParentMismatch { expected: Guid, actual: Guid },
196
197    #[error("parent locator GUID mismatch: expected {expected}, found {actual}")]
198    ParentLocatorGuidMismatch { expected: Guid, actual: Guid },
199
200    #[error("parent linkage key missing")]
201    ParentLocatorMissingLinkage,
202
203    #[error("parent_linkage2 conflict (merge transition)")]
204    ParentLocatorLinkage2Conflict,
205
206    #[error("invalid parameter: {0}")]
207    InvalidParameter(String),
208
209    #[error("read-only mode")]
210    ReadOnly,
211}
212
213/// Result type alias for VHDX operations.
214pub type Result<T> = std::result::Result<T, Error>;
215
216impl From<Error> for std::io::Error {
217    fn from(e: Error) -> Self {
218        match e {
219            // Io variant — return inner error directly
220            Error::Io(io_err) => io_err,
221
222            // Invalid inputs
223            Error::InvalidParameter(msg) => Self::new(std::io::ErrorKind::InvalidData, msg),
224
225            // Invalid data / corruption
226            Error::InvalidFile(..)
227            | Error::InvalidSignature { .. }
228            | Error::CorruptedHeader(..)
229            | Error::HeaderLogGuidMismatch { .. }
230            | Error::HeaderSequenceNumberInvalid { .. }
231            | Error::UnsupportedVersion { .. }
232            | Error::UnsupportedLogVersion { .. }
233            | Error::HeaderLogNotAligned { .. }
234            | Error::InvalidChecksum { .. }
235            | Error::InvalidBlockState(..)
236            | Error::InvalidSectorBitmapState(..)
237            | Error::StateMismatch { .. }
238            | Error::InvalidRegionTable(..)
239            | Error::RegionRequiredUnknown { .. }
240            | Error::RegionOptionalUnknown { .. }
241            | Error::InvalidMetadata(..)
242            | Error::MetadataGuidUnknown { .. }
243            | Error::MetadataRequiredMissing { .. }
244            | Error::MetadataRequiredUnknown { .. }
245            | Error::MetadataOptionalUnknown { .. }
246            | Error::MetadataReservedFlagsSet { .. }
247            | Error::MetadataEntryReservedNonzero { .. }
248            | Error::FileParametersReservedFlags { .. }
249            | Error::InvalidParentLocator(..)
250            | Error::BatFileOffsetUnaligned { .. }
251            | Error::BatEntryCountInsufficient { .. }
252            | Error::BatFileOffsetDuplicate { .. }
253            | Error::LogEntryCorrupted(..)
254            | Error::LogSequenceGap { .. }
255            | Error::LogSequenceGuidMismatch { .. }
256            | Error::LogActiveSequenceEmpty
257            | Error::ParentLocatorMissingLinkage
258            | Error::ParentLocatorLinkage2Conflict
259            | Error::ParentSectorSizeMismatch { .. }
260            | Error::ParentLocatorGuidMismatch { .. }
261            | Error::ParentMismatch { .. } => {
262                Self::new(std::io::ErrorKind::InvalidData, e.to_string())
263            }
264
265            // Not found
266            Error::MetadataNotFound { .. }
267            | Error::BatEntryNotFound { .. }
268            | Error::BlockNotPresent { .. }
269            | Error::ParentResolverRequired
270            | Error::ParentNotFound => Self::new(std::io::ErrorKind::NotFound, e.to_string()),
271
272            // Permission
273            Error::ReadOnly | Error::LogReplayRequired => {
274                Self::new(std::io::ErrorKind::PermissionDenied, e.to_string())
275            }
276
277            // Bounds
278            Error::SectorOutOfBounds { .. } => {
279                Self::new(std::io::ErrorKind::UnexpectedEof, e.to_string())
280            }
281        }
282    }
283}