1use crate::types::Guid;
2
3#[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
15pub(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#[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 #[error("header log field not 1MB-aligned: {field}={value}")]
68 HeaderLogNotAligned {
69 field: String,
71 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 #[error("BAT state mismatch: state={state:#04x}, {description}")]
89 StateMismatch {
90 state: u8,
92 description: String,
94 },
95
96 #[error("BAT entry file offset not aligned: offset_mb={offset_mb}, block_size={block_size}")]
100 BatFileOffsetUnaligned {
101 offset_mb: u64,
103 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 #[error("metadata entry reserved field non-zero: {reserved:#010x}")]
144 MetadataEntryReservedNonzero { reserved: u32 },
145
146 #[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
213pub 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 Error::Io(io_err) => io_err,
221
222 Error::InvalidParameter(msg) => Self::new(std::io::ErrorKind::InvalidData, msg),
224
225 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 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 Error::ReadOnly | Error::LogReplayRequired => {
274 Self::new(std::io::ErrorKind::PermissionDenied, e.to_string())
275 }
276
277 Error::SectorOutOfBounds { .. } => {
279 Self::new(std::io::ErrorKind::UnexpectedEof, e.to_string())
280 }
281 }
282 }
283}