1use crate::block_index::FsBlockIndex;
10use crate::block_size::BlockSize;
11use crate::dir_entry::DirEntryNameError;
12use crate::features::IncompatibleFeatures;
13use crate::inode::{InodeIndex, InodeMode};
14use alloc::boxed::Box;
15use core::error::Error;
16use core::fmt::{self, Debug, Display, Formatter};
17use core::num::NonZero;
18
19pub(crate) type BoxedError = Box<dyn Error + Send + Sync + 'static>;
22
23#[derive(Debug)]
27#[non_exhaustive]
28pub enum Ext4Error {
29 NotAbsolute,
32
33 NotASymlink,
36
37 NotFound,
39
40 IsADirectory,
43
44 NotADirectory,
47
48 IsASpecialFile,
52
53 FileTooLarge,
55
56 NotUtf8,
58
59 MalformedPath,
61
62 PathTooLong,
68
69 TooManySymlinks,
72
73 Encrypted,
80
81 Io(
87 BoxedError,
89 ),
90
91 Incompatible(Incompatible),
96
97 Corrupt(Corrupt),
99}
100
101impl Ext4Error {
102 #[must_use]
104 pub fn as_io(&self) -> Option<&(dyn Error + Send + Sync + 'static)> {
105 if let Self::Io(err) = self {
106 Some(&**err)
107 } else {
108 None
109 }
110 }
111}
112
113impl Display for Ext4Error {
114 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
115 match self {
116 Self::NotAbsolute => write!(f, "path is not absolute"),
117 Self::NotASymlink => write!(f, "path is not a symlink"),
118 Self::NotFound => write!(f, "file not found"),
119 Self::IsADirectory => write!(f, "path is a directory"),
120 Self::NotADirectory => write!(f, "path is not a directory"),
121 Self::IsASpecialFile => write!(f, "path is a special file"),
122 Self::FileTooLarge => {
123 write!(f, "file is too large to store in memory")
124 }
125 Self::NotUtf8 => write!(f, "data is not utf-8"),
126 Self::MalformedPath => write!(f, "data is not a valid path"),
127 Self::PathTooLong => write!(f, "path is too long"),
128 Self::TooManySymlinks => {
129 write!(f, "too many levels of symbolic links")
130 }
131 Self::Encrypted => write!(f, "file is encrypted"),
132 Self::Io(err) => write!(f, "io error: {err}"),
135 Self::Incompatible(i) => write!(f, "incompatible filesystem: {i}"),
136 Self::Corrupt(c) => write!(f, "corrupt filesystem: {c}"),
137 }
138 }
139}
140
141impl Error for Ext4Error {}
142
143#[cfg(feature = "std")]
144impl From<Ext4Error> for std::io::Error {
145 fn from(e: Ext4Error) -> Self {
146 use std::io::ErrorKind::*;
147
148 match e {
150 Ext4Error::IsASpecialFile
151 | Ext4Error::MalformedPath
152 | Ext4Error::NotASymlink
153 | Ext4Error::NotAbsolute => InvalidInput.into(),
154
155 Ext4Error::Corrupt(_)
156 | Ext4Error::Incompatible(_)
157 | Ext4Error::PathTooLong
158 | Ext4Error::TooManySymlinks => Self::other(e),
159
160 Ext4Error::FileTooLarge => FileTooLarge.into(),
161 Ext4Error::Io(inner) => Self::other(inner),
162 Ext4Error::IsADirectory => IsADirectory.into(),
163 Ext4Error::NotADirectory => NotADirectory.into(),
164 Ext4Error::NotFound => NotFound.into(),
165 Ext4Error::NotUtf8 => InvalidData.into(),
166 Ext4Error::Encrypted => PermissionDenied.into(),
167 }
168 }
169}
170
171#[derive(Clone, Eq, PartialEq)]
174pub struct Corrupt(CorruptKind);
175
176impl Debug for Corrupt {
177 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
178 <CorruptKind as Debug>::fmt(&self.0, f)
179 }
180}
181
182impl Display for Corrupt {
183 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
184 <CorruptKind as Display>::fmt(&self.0, f)
185 }
186}
187
188#[derive(Clone, Debug, Eq, PartialEq)]
189#[non_exhaustive]
190pub(crate) enum CorruptKind {
191 SuperblockMagic,
193
194 SuperblockChecksum,
196
197 InvalidBlockSize,
199
200 TooManyBlockGroups,
202
203 InodesPerBlockGroup,
205
206 InodeSize,
208
209 JournalInode,
211
212 FirstDataBlock(
214 u32,
216 ),
217
218 BlockGroupDescriptor(
220 u32,
222 ),
223
224 BlockGroupDescriptorChecksum(
226 u32,
228 ),
229
230 JournalSize,
232
233 JournalMagic,
235
236 JournalSuperblockChecksum,
238
239 JournalBlockSize,
241
242 JournalTruncated,
244
245 JournalSequence,
247
248 JournalCommitBlockChecksum,
250
251 JournalDescriptorBlockChecksum,
253
254 JournalDescriptorTagChecksum,
256
257 JournalRevocationBlockChecksum,
259
260 JournalRevocationBlockInvalidTableSize(usize),
262
263 JournalSequenceOverflow,
265
266 JournalDescriptorBlockTruncated,
271
272 InodeChecksum(InodeIndex),
274
275 InodeTruncated { inode: InodeIndex, size: usize },
277
278 InodeBlockGroup {
280 inode: InodeIndex,
281 block_group: u32,
282 num_block_groups: usize,
283 },
284
285 InodeLocation {
291 inode: InodeIndex,
292 block_group: u32,
293 inodes_per_block_group: NonZero<u32>,
294 inode_size: u16,
295 block_size: BlockSize,
296 inode_table_first_block: FsBlockIndex,
297 },
298
299 InodeFileType { inode: InodeIndex, mode: InodeMode },
301
302 SymlinkTarget(InodeIndex),
304
305 TooManyBlocksInFile,
307
308 ExtentMagic(InodeIndex),
310
311 ExtentChecksum(InodeIndex),
313
314 ExtentDepth(InodeIndex),
316
317 ExtentNotEnoughData(InodeIndex),
319
320 ExtentBlock(InodeIndex),
322
323 ExtentNodeSize(InodeIndex),
325
326 DirBlockChecksum(InodeIndex),
328
329 DirEntryMissingHeader(InodeIndex, usize),
331
332 DirEntryRecordTooSmall(InodeIndex, usize),
334
335 DirEntryNameTooLarge(InodeIndex, u8),
337
338 DirEntryInvalidFileType(InodeIndex, u8),
340
341 DirEntryInvalidName(InodeIndex, DirEntryNameError),
343
344 HtreeInternalNodeMissingHeader { inode: InodeIndex, num_bytes: usize },
346
347 HtreeInternalNodeCountTooLarge {
350 inode: InodeIndex,
351 num_bytes: usize,
352 count: usize,
353 },
354
355 DirEntry(InodeIndex),
358
359 BlockRead {
361 block_index: FsBlockIndex,
363
364 original_block_index: FsBlockIndex,
368
369 offset_within_block: u32,
371
372 read_len: usize,
374 },
375
376 BlockCacheReadTooLarge {
378 num_blocks: u32,
379 block_size: BlockSize,
380 },
381}
382
383impl Display for CorruptKind {
384 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
385 match self {
386 Self::SuperblockMagic => write!(f, "invalid superblock magic"),
387 Self::SuperblockChecksum => {
388 write!(f, "invalid superblock checksum")
389 }
390 Self::InvalidBlockSize => write!(f, "invalid block size"),
391 Self::TooManyBlockGroups => write!(f, "too many block groups"),
392 Self::InodesPerBlockGroup => {
393 write!(f, "inodes per block group is zero")
394 }
395 Self::InodeSize => write!(f, "inode size is invalid"),
396 Self::JournalInode => write!(f, "invalid journal inode"),
397 Self::FirstDataBlock(block) => {
398 write!(f, "invalid first data block: {block}")
399 }
400 Self::BlockGroupDescriptor(block_group_num) => {
401 write!(f, "block group descriptor {block_group_num} is invalid")
402 }
403 Self::BlockGroupDescriptorChecksum(block_group_num) => write!(
404 f,
405 "invalid checksum for block group descriptor {block_group_num}"
406 ),
407 Self::JournalSize => {
408 write!(f, "journal size is invalid")
409 }
410 Self::JournalMagic => {
411 write!(f, "journal magic is invalid")
412 }
413 Self::JournalSuperblockChecksum => {
414 write!(f, "journal superblock checksum is invalid")
415 }
416 Self::JournalBlockSize => {
417 write!(
418 f,
419 "journal block size does not match filesystem block size"
420 )
421 }
422 Self::JournalTruncated => write!(f, "journal is truncated"),
423 Self::JournalSequence => write!(
424 f,
425 "journal's first commit doesn't match the expected sequence"
426 ),
427 Self::JournalCommitBlockChecksum => {
428 write!(f, "journal commit block checksum is invalid")
429 }
430 Self::JournalDescriptorBlockChecksum => {
431 write!(f, "journal descriptor block checksum is invalid")
432 }
433 Self::JournalDescriptorTagChecksum => {
434 write!(f, "journal descriptor tag checksum is invalid")
435 }
436 Self::JournalRevocationBlockChecksum => {
437 write!(f, "journal revocation block checksum is invalid")
438 }
439 Self::JournalRevocationBlockInvalidTableSize(size) => {
440 write!(
441 f,
442 "journal revocation block table size is invalid: {size}"
443 )
444 }
445 Self::JournalSequenceOverflow => {
446 write!(f, "journal sequence number overflowed")
447 }
448 Self::JournalDescriptorBlockTruncated => {
449 write!(f, "journal descriptor block is truncated")
450 }
451 Self::InodeChecksum(inode) => {
452 write!(f, "invalid checksum for inode {inode}")
453 }
454 Self::InodeTruncated { inode, size } => {
455 write!(f, "inode {inode} is truncated: size={size}")
456 }
457 Self::InodeBlockGroup {
458 inode,
459 block_group,
460 num_block_groups,
461 } => {
462 write!(
463 f,
464 "inode {inode} has an invalid block group index: block_group={block_group}, num_block_groups={num_block_groups}"
465 )
466 }
467 Self::InodeLocation {
468 inode,
469 block_group,
470 inodes_per_block_group,
471 inode_size,
472 block_size,
473 inode_table_first_block,
474 } => {
475 write!(
476 f,
477 "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}"
478 )
479 }
480 Self::InodeFileType { inode, mode } => {
481 write!(
482 f,
483 "inode {inode} has invalid file type: mode=0x{mode:04x}",
484 mode = mode.bits()
485 )
486 }
487 Self::SymlinkTarget(inode) => {
488 write!(f, "inode {inode} has an invalid symlink path")
489 }
490 Self::TooManyBlocksInFile => write!(f, "too many blocks in file"),
491 Self::ExtentMagic(inode) => {
492 write!(f, "extent in inode {inode} has invalid magic")
493 }
494 Self::ExtentChecksum(inode) => {
495 write!(f, "extent in inode {inode} has an invalid checksum")
496 }
497 Self::ExtentDepth(inode) => {
498 write!(f, "extent in inode {inode} has an invalid depth")
499 }
500 Self::ExtentNotEnoughData(inode) => {
501 write!(f, "extent data in inode {inode} is invalid")
502 }
503 Self::ExtentBlock(inode) => {
504 write!(f, "extent in inode {inode} points to an invalid block")
505 }
506 Self::ExtentNodeSize(inode) => {
507 write!(
508 f,
509 "extent in inode {inode} has a node with an invalid size"
510 )
511 }
512 Self::DirBlockChecksum(inode) => write!(
513 f,
514 "directory block in inode {inode} has an invalid checksum"
515 ),
516 Self::DirEntryMissingHeader(inode, num_bytes) => {
517 write!(
518 f,
519 "directory in inode {inode} is too small to contain header: {num_bytes}"
520 )
521 }
522 Self::DirEntryRecordTooSmall(inode, len) => {
523 write!(
524 f,
525 "directory entry in inode {inode} record length is too small: {len}"
526 )
527 }
528 Self::DirEntryNameTooLarge(inode, len) => {
529 write!(
530 f,
531 "name of directory entry in inode {inode} is too large: {len}"
532 )
533 }
534 Self::DirEntryInvalidFileType(inode, value) => {
535 write!(
536 f,
537 "directory entry in inode {inode} has invalid file type: {value}"
538 )
539 }
540 Self::DirEntryInvalidName(inode, err) => {
541 write!(
542 f,
543 "directory entry in inode {inode} has invalid name: {err}"
544 )
545 }
546 Self::HtreeInternalNodeMissingHeader { inode, num_bytes } => {
547 write!(
548 f,
549 "htree internal node in inode {inode} is too small to contain header: {num_bytes}"
550 )
551 }
552 Self::HtreeInternalNodeCountTooLarge {
553 inode,
554 num_bytes,
555 count,
556 } => {
557 write!(
558 f,
559 "htree internal node in inode {inode} has too large many entries: num_bytes={num_bytes}, count={count}"
560 )
561 }
562 Self::DirEntry(inode) => {
563 write!(f, "invalid directory entry in inode {inode}")
564 }
565 Self::BlockRead {
566 block_index,
567 original_block_index,
568 offset_within_block,
569 read_len,
570 } => {
571 write!(
572 f,
573 "invalid read of length {read_len} from block {block_index} (originally {original_block_index}) at offset {offset_within_block}"
574 )
575 }
576 Self::BlockCacheReadTooLarge {
577 num_blocks,
578 block_size,
579 } => write!(
580 f,
581 "attempted to read {num_blocks} blocks with block_size {block_size}"
582 ),
583 }
584 }
585}
586
587impl PartialEq<CorruptKind> for Ext4Error {
588 fn eq(&self, ck: &CorruptKind) -> bool {
589 if let Self::Corrupt(c) = self {
590 c.0 == *ck
591 } else {
592 false
593 }
594 }
595}
596
597impl From<CorruptKind> for Ext4Error {
598 fn from(c: CorruptKind) -> Self {
599 Self::Corrupt(Corrupt(c))
600 }
601}
602
603#[derive(Clone, Eq, PartialEq)]
606pub struct Incompatible(IncompatibleKind);
607
608impl Debug for Incompatible {
609 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
610 <IncompatibleKind as Debug>::fmt(&self.0, f)
611 }
612}
613
614impl Display for Incompatible {
615 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
616 <IncompatibleKind as Display>::fmt(&self.0, f)
617 }
618}
619
620#[derive(Clone, Debug, Eq, PartialEq)]
621#[non_exhaustive]
622pub(crate) enum IncompatibleKind {
623 MissingRequiredFeatures(
625 IncompatibleFeatures,
627 ),
628
629 #[allow(clippy::enum_variant_names)]
631 UnsupportedFeatures(
632 IncompatibleFeatures,
634 ),
635
636 DirectoryHash(
638 u8,
640 ),
641
642 JournalSuperblockType(
644 u32,
646 ),
647
648 JournalChecksumType(
650 u8,
652 ),
653
654 MissingRequiredJournalFeatures(
656 u32,
658 ),
659
660 #[allow(clippy::enum_variant_names)]
662 UnsupportedJournalFeatures(
663 u32,
665 ),
666
667 JournalBlockType(
669 u32,
671 ),
672
673 JournalBlockEscaped,
675}
676
677impl Display for IncompatibleKind {
678 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
679 match self {
680 Self::MissingRequiredFeatures(feat) => {
681 write!(f, "missing required features: {feat:?}")
682 }
683 Self::UnsupportedFeatures(feat) => {
684 write!(f, "unsupported features: {feat:?}")
685 }
686 Self::DirectoryHash(algorithm) => {
687 write!(f, "unsupported directory hash algorithm: {algorithm}")
688 }
689 Self::JournalSuperblockType(val) => {
690 write!(f, "journal superblock type is not supported: {val}")
691 }
692 Self::JournalBlockType(val) => {
693 write!(f, "journal block type is not supported: {val}")
694 }
695 Self::JournalBlockEscaped => {
696 write!(f, "journal contains an escaped data block")
697 }
698 Self::JournalChecksumType(val) => {
699 write!(f, "journal checksum type is not supported: {val}")
700 }
701 Self::MissingRequiredJournalFeatures(feat) => {
702 write!(f, "missing required journal features: {feat:?}")
703 }
704 Self::UnsupportedJournalFeatures(feat) => {
705 write!(f, "unsupported journal features: {feat:?}")
706 }
707 }
708 }
709}
710
711impl PartialEq<IncompatibleKind> for Ext4Error {
712 fn eq(&self, other: &IncompatibleKind) -> bool {
713 if let Self::Incompatible(Incompatible(i)) = self {
714 i == other
715 } else {
716 false
717 }
718 }
719}
720
721impl From<IncompatibleKind> for Ext4Error {
722 fn from(k: IncompatibleKind) -> Self {
723 Self::Incompatible(Incompatible(k))
724 }
725}
726
727#[cfg(test)]
728mod tests {
729 use super::*;
730
731 #[test]
737 fn test_corrupt_format() {
738 let err: Ext4Error = CorruptKind::BlockRead {
739 block_index: 123,
740 original_block_index: 124,
741 offset_within_block: 456,
742 read_len: 789,
743 }
744 .into();
745
746 assert_eq!(
747 format!("{err}"),
748 "corrupt filesystem: invalid read of length 789 from block 123 (originally 124) at offset 456"
749 );
750
751 assert_eq!(
752 format!("{err:?}"),
753 "Corrupt(BlockRead { block_index: 123, original_block_index: 124, offset_within_block: 456, read_len: 789 })"
754 );
755 }
756
757 #[test]
763 fn test_incompatible_format() {
764 let err: Ext4Error = IncompatibleKind::DirectoryHash(123).into();
765
766 assert_eq!(
767 format!("{err}"),
768 "incompatible filesystem: unsupported directory hash algorithm: 123"
769 );
770
771 assert_eq!(format!("{err:?}"), "Incompatible(DirectoryHash(123))");
772 }
773}