1use crate::block_index::FsBlockIndex;
10use crate::block_size::BlockSize;
11use crate::features::IncompatibleFeatures;
12use crate::inode::{InodeIndex, InodeMode};
13use alloc::boxed::Box;
14use core::error::Error;
15use core::fmt::{self, Debug, Display, Formatter};
16use core::num::NonZero;
17
18pub(crate) type BoxedError = Box<dyn Error + Send + Sync + 'static>;
21
22#[derive(Debug)]
26#[non_exhaustive]
27pub enum Ext4Error {
28 NotAbsolute,
31
32 NotASymlink,
35
36 NotFound,
38
39 IsADirectory,
42
43 NotADirectory,
46
47 IsASpecialFile,
51
52 FileTooLarge,
54
55 NotUtf8,
57
58 MalformedPath,
60
61 PathTooLong,
67
68 TooManySymlinks,
71
72 Encrypted,
79
80 Io(
86 BoxedError,
88 ),
89
90 Incompatible(Incompatible),
95
96 Corrupt(Corrupt),
98}
99
100impl Ext4Error {
101 #[must_use]
103 pub fn as_io(&self) -> Option<&(dyn Error + Send + Sync + 'static)> {
104 if let Self::Io(err) = self {
105 Some(&**err)
106 } else {
107 None
108 }
109 }
110}
111
112impl Display for Ext4Error {
113 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
114 match self {
115 Self::NotAbsolute => write!(f, "path is not absolute"),
116 Self::NotASymlink => write!(f, "path is not a symlink"),
117 Self::NotFound => write!(f, "file not found"),
118 Self::IsADirectory => write!(f, "path is a directory"),
119 Self::NotADirectory => write!(f, "path is not a directory"),
120 Self::IsASpecialFile => write!(f, "path is a special file"),
121 Self::FileTooLarge => {
122 write!(f, "file is too large to store in memory")
123 }
124 Self::NotUtf8 => write!(f, "data is not utf-8"),
125 Self::MalformedPath => write!(f, "data is not a valid path"),
126 Self::PathTooLong => write!(f, "path is too long"),
127 Self::TooManySymlinks => {
128 write!(f, "too many levels of symbolic links")
129 }
130 Self::Encrypted => write!(f, "file is encrypted"),
131 Self::Io(err) => write!(f, "io error: {err}"),
134 Self::Incompatible(i) => write!(f, "incompatible filesystem: {i}"),
135 Self::Corrupt(c) => write!(f, "corrupt filesystem: {c}"),
136 }
137 }
138}
139
140impl Error for Ext4Error {}
141
142#[cfg(feature = "std")]
143impl From<Ext4Error> for std::io::Error {
144 fn from(e: Ext4Error) -> Self {
145 use std::io::ErrorKind::*;
146
147 match e {
151 Ext4Error::IsADirectory
152 | Ext4Error::IsASpecialFile
153 | Ext4Error::MalformedPath
154 | Ext4Error::NotADirectory
155 | Ext4Error::NotASymlink
156 | Ext4Error::NotAbsolute => InvalidInput.into(),
157 Ext4Error::Corrupt(_)
158 | Ext4Error::FileTooLarge
159 | Ext4Error::Incompatible(_)
160 | Ext4Error::PathTooLong
161 | Ext4Error::TooManySymlinks => Self::other(e),
162 Ext4Error::Io(inner) => Self::other(inner),
163 Ext4Error::NotFound => NotFound.into(),
164 Ext4Error::NotUtf8 => InvalidData.into(),
165 Ext4Error::Encrypted => PermissionDenied.into(),
166 }
167 }
168}
169
170#[derive(Clone, Eq, PartialEq)]
173pub struct Corrupt(CorruptKind);
174
175impl Debug for Corrupt {
176 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
177 <CorruptKind as Debug>::fmt(&self.0, f)
178 }
179}
180
181impl Display for Corrupt {
182 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
183 <CorruptKind as Display>::fmt(&self.0, f)
184 }
185}
186
187#[derive(Clone, Debug, Eq, PartialEq)]
188#[non_exhaustive]
189pub(crate) enum CorruptKind {
190 SuperblockMagic,
192
193 SuperblockChecksum,
195
196 InvalidBlockSize,
198
199 TooManyBlockGroups,
201
202 InodesPerBlockGroup,
204
205 InodeSize,
207
208 JournalInode,
210
211 FirstDataBlock(
213 u32,
215 ),
216
217 BlockGroupDescriptor(
219 u32,
221 ),
222
223 BlockGroupDescriptorChecksum(
225 u32,
227 ),
228
229 JournalSize,
231
232 JournalMagic,
234
235 JournalSuperblockChecksum,
237
238 JournalBlockSize,
240
241 JournalTruncated,
243
244 JournalSequence,
246
247 JournalCommitBlockChecksum,
249
250 JournalDescriptorBlockChecksum,
252
253 JournalDescriptorTagChecksum,
255
256 JournalRevocationBlockChecksum,
258
259 JournalRevocationBlockInvalidTableSize(usize),
261
262 JournalSequenceOverflow,
264
265 JournalDescriptorBlockTruncated,
270
271 InodeChecksum(InodeIndex),
273
274 InodeTruncated { inode: InodeIndex, size: usize },
276
277 InodeBlockGroup {
279 inode: InodeIndex,
280 block_group: u32,
281 num_block_groups: usize,
282 },
283
284 InodeLocation {
290 inode: InodeIndex,
291 block_group: u32,
292 inodes_per_block_group: NonZero<u32>,
293 inode_size: u16,
294 block_size: BlockSize,
295 inode_table_first_block: FsBlockIndex,
296 },
297
298 InodeFileType { inode: InodeIndex, mode: InodeMode },
300
301 SymlinkTarget(InodeIndex),
303
304 TooManyBlocksInFile,
306
307 ExtentMagic(InodeIndex),
309
310 ExtentChecksum(InodeIndex),
312
313 ExtentDepth(InodeIndex),
315
316 ExtentNotEnoughData(InodeIndex),
318
319 ExtentBlock(InodeIndex),
321
322 ExtentNodeSize(InodeIndex),
324
325 DirBlockChecksum(InodeIndex),
327
328 DirEntry(InodeIndex),
331
332 BlockRead {
334 block_index: FsBlockIndex,
336
337 original_block_index: FsBlockIndex,
341
342 offset_within_block: u32,
344
345 read_len: usize,
347 },
348
349 BlockCacheReadTooLarge {
351 num_blocks: u32,
352 block_size: BlockSize,
353 },
354}
355
356impl Display for CorruptKind {
357 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
358 match self {
359 Self::SuperblockMagic => write!(f, "invalid superblock magic"),
360 Self::SuperblockChecksum => {
361 write!(f, "invalid superblock checksum")
362 }
363 Self::InvalidBlockSize => write!(f, "invalid block size"),
364 Self::TooManyBlockGroups => write!(f, "too many block groups"),
365 Self::InodesPerBlockGroup => {
366 write!(f, "inodes per block group is zero")
367 }
368 Self::InodeSize => write!(f, "inode size is invalid"),
369 Self::JournalInode => write!(f, "invalid journal inode"),
370 Self::FirstDataBlock(block) => {
371 write!(f, "invalid first data block: {block}")
372 }
373 Self::BlockGroupDescriptor(block_group_num) => {
374 write!(f, "block group descriptor {block_group_num} is invalid")
375 }
376 Self::BlockGroupDescriptorChecksum(block_group_num) => write!(
377 f,
378 "invalid checksum for block group descriptor {block_group_num}"
379 ),
380 Self::JournalSize => {
381 write!(f, "journal size is invalid")
382 }
383 Self::JournalMagic => {
384 write!(f, "journal magic is invalid")
385 }
386 Self::JournalSuperblockChecksum => {
387 write!(f, "journal superblock checksum is invalid")
388 }
389 Self::JournalBlockSize => {
390 write!(
391 f,
392 "journal block size does not match filesystem block size"
393 )
394 }
395 Self::JournalTruncated => write!(f, "journal is truncated"),
396 Self::JournalSequence => write!(
397 f,
398 "journal's first commit doesn't match the expected sequence"
399 ),
400 Self::JournalCommitBlockChecksum => {
401 write!(f, "journal commit block checksum is invalid")
402 }
403 Self::JournalDescriptorBlockChecksum => {
404 write!(f, "journal descriptor block checksum is invalid")
405 }
406 Self::JournalDescriptorTagChecksum => {
407 write!(f, "journal descriptor tag checksum is invalid")
408 }
409 Self::JournalRevocationBlockChecksum => {
410 write!(f, "journal revocation block checksum is invalid")
411 }
412 Self::JournalRevocationBlockInvalidTableSize(size) => {
413 write!(
414 f,
415 "journal revocation block table size is invalid: {size}"
416 )
417 }
418 Self::JournalSequenceOverflow => {
419 write!(f, "journal sequence number overflowed")
420 }
421 Self::JournalDescriptorBlockTruncated => {
422 write!(f, "journal descriptor block is truncated")
423 }
424 Self::InodeChecksum(inode) => {
425 write!(f, "invalid checksum for inode {inode}")
426 }
427 Self::InodeTruncated { inode, size } => {
428 write!(f, "inode {inode} is truncated: size={size}")
429 }
430 Self::InodeBlockGroup {
431 inode,
432 block_group,
433 num_block_groups,
434 } => {
435 write!(
436 f,
437 "inode {inode} has an invalid block group index: block_group={block_group}, num_block_groups={num_block_groups}"
438 )
439 }
440 Self::InodeLocation {
441 inode,
442 block_group,
443 inodes_per_block_group,
444 inode_size,
445 block_size,
446 inode_table_first_block,
447 } => {
448 write!(
449 f,
450 "inode {inode} has invalid location: block_group={block_group}, inodes_per_block_group={inodes_per_block_group}, inode_size={inode_size}, block_size={block_size}, inode_table_first_block={inode_table_first_block}"
451 )
452 }
453 Self::InodeFileType { inode, mode } => {
454 write!(
455 f,
456 "inode {inode} has invalid file type: mode=0x{mode:04x}",
457 mode = mode.bits()
458 )
459 }
460 Self::SymlinkTarget(inode) => {
461 write!(f, "inode {inode} has an invalid symlink path")
462 }
463 Self::TooManyBlocksInFile => write!(f, "too many blocks in file"),
464 Self::ExtentMagic(inode) => {
465 write!(f, "extent in inode {inode} has invalid magic")
466 }
467 Self::ExtentChecksum(inode) => {
468 write!(f, "extent in inode {inode} has an invalid checksum")
469 }
470 Self::ExtentDepth(inode) => {
471 write!(f, "extent in inode {inode} has an invalid depth")
472 }
473 Self::ExtentNotEnoughData(inode) => {
474 write!(f, "extent data in inode {inode} is invalid")
475 }
476 Self::ExtentBlock(inode) => {
477 write!(f, "extent in inode {inode} points to an invalid block")
478 }
479 Self::ExtentNodeSize(inode) => {
480 write!(
481 f,
482 "extent in inode {inode} has a node with an invalid size"
483 )
484 }
485 Self::DirBlockChecksum(inode) => write!(
486 f,
487 "directory block in inode {inode} has an invalid checksum"
488 ),
489 Self::DirEntry(inode) => {
490 write!(f, "invalid directory entry in inode {inode}")
491 }
492 Self::BlockRead {
493 block_index,
494 original_block_index,
495 offset_within_block,
496 read_len,
497 } => {
498 write!(
499 f,
500 "invalid read of length {read_len} from block {block_index} (originally {original_block_index}) at offset {offset_within_block}"
501 )
502 }
503 Self::BlockCacheReadTooLarge {
504 num_blocks,
505 block_size,
506 } => write!(
507 f,
508 "attempted to read {num_blocks} blocks with block_size {block_size}"
509 ),
510 }
511 }
512}
513
514impl PartialEq<CorruptKind> for Ext4Error {
515 fn eq(&self, ck: &CorruptKind) -> bool {
516 if let Self::Corrupt(c) = self {
517 c.0 == *ck
518 } else {
519 false
520 }
521 }
522}
523
524impl From<CorruptKind> for Ext4Error {
525 fn from(c: CorruptKind) -> Self {
526 Self::Corrupt(Corrupt(c))
527 }
528}
529
530#[derive(Clone, Eq, PartialEq)]
533pub struct Incompatible(IncompatibleKind);
534
535impl Debug for Incompatible {
536 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
537 <IncompatibleKind as Debug>::fmt(&self.0, f)
538 }
539}
540
541impl Display for Incompatible {
542 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
543 <IncompatibleKind as Display>::fmt(&self.0, f)
544 }
545}
546
547#[derive(Clone, Debug, Eq, PartialEq)]
548#[non_exhaustive]
549pub(crate) enum IncompatibleKind {
550 MissingRequiredFeatures(
552 IncompatibleFeatures,
554 ),
555
556 #[allow(clippy::enum_variant_names)]
558 UnsupportedFeatures(
559 IncompatibleFeatures,
561 ),
562
563 DirectoryHash(
565 u8,
567 ),
568
569 JournalSuperblockType(
571 u32,
573 ),
574
575 JournalChecksumType(
577 u8,
579 ),
580
581 MissingRequiredJournalFeatures(
583 u32,
585 ),
586
587 #[allow(clippy::enum_variant_names)]
589 UnsupportedJournalFeatures(
590 u32,
592 ),
593
594 JournalBlockType(
596 u32,
598 ),
599
600 JournalBlockEscaped,
602}
603
604impl Display for IncompatibleKind {
605 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
606 match self {
607 Self::MissingRequiredFeatures(feat) => {
608 write!(f, "missing required features: {feat:?}")
609 }
610 Self::UnsupportedFeatures(feat) => {
611 write!(f, "unsupported features: {feat:?}")
612 }
613 Self::DirectoryHash(algorithm) => {
614 write!(f, "unsupported directory hash algorithm: {algorithm}")
615 }
616 Self::JournalSuperblockType(val) => {
617 write!(f, "journal superblock type is not supported: {val}")
618 }
619 Self::JournalBlockType(val) => {
620 write!(f, "journal block type is not supported: {val}")
621 }
622 Self::JournalBlockEscaped => {
623 write!(f, "journal contains an escaped data block")
624 }
625 Self::JournalChecksumType(val) => {
626 write!(f, "journal checksum type is not supported: {val}")
627 }
628 Self::MissingRequiredJournalFeatures(feat) => {
629 write!(f, "missing required journal features: {feat:?}")
630 }
631 Self::UnsupportedJournalFeatures(feat) => {
632 write!(f, "unsupported journal features: {feat:?}")
633 }
634 }
635 }
636}
637
638impl PartialEq<IncompatibleKind> for Ext4Error {
639 fn eq(&self, other: &IncompatibleKind) -> bool {
640 if let Self::Incompatible(Incompatible(i)) = self {
641 i == other
642 } else {
643 false
644 }
645 }
646}
647
648impl From<IncompatibleKind> for Ext4Error {
649 fn from(k: IncompatibleKind) -> Self {
650 Self::Incompatible(Incompatible(k))
651 }
652}
653
654#[cfg(test)]
655mod tests {
656 use super::*;
657
658 #[test]
664 fn test_corrupt_format() {
665 let err: Ext4Error = CorruptKind::BlockRead {
666 block_index: 123,
667 original_block_index: 124,
668 offset_within_block: 456,
669 read_len: 789,
670 }
671 .into();
672
673 assert_eq!(
674 format!("{err}"),
675 "corrupt filesystem: invalid read of length 789 from block 123 (originally 124) at offset 456"
676 );
677
678 assert_eq!(
679 format!("{err:?}"),
680 "Corrupt(BlockRead { block_index: 123, original_block_index: 124, offset_within_block: 456, read_len: 789 })"
681 );
682 }
683
684 #[test]
690 fn test_incompatible_format() {
691 let err: Ext4Error = IncompatibleKind::DirectoryHash(123).into();
692
693 assert_eq!(
694 format!("{err}"),
695 "incompatible filesystem: unsupported directory hash algorithm: 123"
696 );
697
698 assert_eq!(format!("{err:?}"), "Incompatible(DirectoryHash(123))");
699 }
700}