123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 |
- /** @file
- Superblock managing routines
- Copyright (c) 2021 - 2022 Pedro Falcato All rights reserved.
- SPDX-License-Identifier: BSD-2-Clause-Patent
- **/
- #include "Ext4Dxe.h"
- STATIC CONST UINT32 gSupportedCompatFeat = EXT4_FEATURE_COMPAT_EXT_ATTR;
- STATIC CONST UINT32 gSupportedRoCompatFeat =
- EXT4_FEATURE_RO_COMPAT_DIR_NLINK | EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE |
- EXT4_FEATURE_RO_COMPAT_HUGE_FILE | EXT4_FEATURE_RO_COMPAT_LARGE_FILE |
- EXT4_FEATURE_RO_COMPAT_GDT_CSUM | EXT4_FEATURE_RO_COMPAT_METADATA_CSUM | EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER;
- STATIC CONST UINT32 gSupportedIncompatFeat =
- EXT4_FEATURE_INCOMPAT_64BIT | EXT4_FEATURE_INCOMPAT_DIRDATA |
- EXT4_FEATURE_INCOMPAT_FLEX_BG | EXT4_FEATURE_INCOMPAT_FILETYPE |
- EXT4_FEATURE_INCOMPAT_EXTENTS | EXT4_FEATURE_INCOMPAT_LARGEDIR |
- EXT4_FEATURE_INCOMPAT_MMP | EXT4_FEATURE_INCOMPAT_RECOVER;
- // Future features that may be nice additions in the future:
- // 1) Btree support: Required for write support and would speed up lookups in large directories.
- // 2) meta_bg: Required to mount meta_bg-enabled partitions.
- // Note: We ignore MMP because it's impossible that it's mapped elsewhere,
- // I think (unless there's some sort of network setup where we're accessing a remote partition).
- // Note on corruption signaling:
- // We (Ext4Dxe) could signal corruption by setting s_state to |= EXT4_FS_STATE_ERRORS_DETECTED.
- // I've decided against that, because right now the driver is read-only, and
- // that would mean we would need to writeback the superblock. If something like
- // this is desired, it's fairly trivial to look for EFI_VOLUME_CORRUPTED
- // references and add some Ext4SignalCorruption function + function call.
- /**
- Checks the superblock's magic value.
- @param[in] DiskIo Pointer to the DiskIo.
- @param[in] BlockIo Pointer to the BlockIo.
- @returns Whether the partition has a valid EXT4 superblock magic value.
- **/
- BOOLEAN
- Ext4SuperblockCheckMagic (
- IN EFI_DISK_IO_PROTOCOL *DiskIo,
- IN EFI_BLOCK_IO_PROTOCOL *BlockIo
- )
- {
- UINT16 Magic;
- EFI_STATUS Status;
- Status = DiskIo->ReadDisk (
- DiskIo,
- BlockIo->Media->MediaId,
- EXT4_SUPERBLOCK_OFFSET + OFFSET_OF (EXT4_SUPERBLOCK, s_magic),
- sizeof (Magic),
- &Magic
- );
- if (EFI_ERROR (Status)) {
- return FALSE;
- }
- if (Magic != EXT4_SIGNATURE) {
- return FALSE;
- }
- return TRUE;
- }
- /**
- Does brief validation of the ext4 superblock.
- @param[in] Sb Pointer to the read superblock.
- @return TRUE if a valid ext4 superblock, else FALSE.
- **/
- BOOLEAN
- Ext4SuperblockValidate (
- CONST EXT4_SUPERBLOCK *Sb
- )
- {
- if (Sb->s_magic != EXT4_SIGNATURE) {
- return FALSE;
- }
- if ((Sb->s_rev_level != EXT4_DYNAMIC_REV) && (Sb->s_rev_level != EXT4_GOOD_OLD_REV)) {
- return FALSE;
- }
- if ((Sb->s_state & EXT4_FS_STATE_UNMOUNTED) == 0) {
- DEBUG ((DEBUG_WARN, "[ext4] Filesystem was not unmounted cleanly\n"));
- }
- return TRUE;
- }
- /**
- Calculates the superblock's checksum.
- @param[in] Partition Pointer to the opened partition.
- @param[in] Sb Pointer to the superblock.
- @return The superblock's checksum.
- **/
- STATIC
- UINT32
- Ext4CalculateSuperblockChecksum (
- EXT4_PARTITION *Partition,
- CONST EXT4_SUPERBLOCK *Sb
- )
- {
- // Most checksums require us to go through a dummy 0 as part of the requirement
- // that the checksum is done over a structure with its checksum field = 0.
- UINT32 Checksum;
- Checksum = Ext4CalculateChecksum (
- Partition,
- Sb,
- OFFSET_OF (EXT4_SUPERBLOCK, s_checksum),
- ~0U
- );
- return Checksum;
- }
- /**
- Verifies that the superblock's checksum is valid.
- @param[in] Partition Pointer to the opened partition.
- @param[in] Sb Pointer to the superblock.
- @return The superblock's checksum.
- **/
- STATIC
- BOOLEAN
- Ext4VerifySuperblockChecksum (
- EXT4_PARTITION *Partition,
- CONST EXT4_SUPERBLOCK *Sb
- )
- {
- if (!EXT4_HAS_METADATA_CSUM (Partition)) {
- return TRUE;
- }
- return Sb->s_checksum == Ext4CalculateSuperblockChecksum (Partition, Sb);
- }
- /**
- Opens and parses the superblock.
- @param[out] Partition Partition structure to fill with filesystem details.
- @retval EFI_SUCCESS Parsing was successful and the partition is a
- valid ext4 partition.
- **/
- EFI_STATUS
- Ext4OpenSuperblock (
- OUT EXT4_PARTITION *Partition
- )
- {
- UINT32 Index;
- EFI_STATUS Status;
- EXT4_SUPERBLOCK *Sb;
- UINT32 NrBlocksRem;
- UINTN NrBlocks;
- UINT32 UnsupportedRoCompat;
- EXT4_BLOCK_GROUP_DESC *Desc;
- Status = Ext4ReadDiskIo (
- Partition,
- &Partition->SuperBlock,
- sizeof (EXT4_SUPERBLOCK),
- EXT4_SUPERBLOCK_OFFSET
- );
- if (EFI_ERROR (Status)) {
- return Status;
- }
- Sb = &Partition->SuperBlock;
- if (!Ext4SuperblockValidate (Sb)) {
- return EFI_VOLUME_CORRUPTED;
- }
- if (Sb->s_rev_level == EXT4_DYNAMIC_REV) {
- Partition->FeaturesCompat = Sb->s_feature_compat;
- Partition->FeaturesIncompat = Sb->s_feature_incompat;
- Partition->FeaturesRoCompat = Sb->s_feature_ro_compat;
- Partition->InodeSize = Sb->s_inode_size;
- // Check for proper alignment of InodeSize and that InodeSize is indeed larger than
- // the minimum size, 128 bytes.
- if (((Partition->InodeSize % 4) != 0) || (Partition->InodeSize < EXT4_GOOD_OLD_INODE_SIZE)) {
- return EFI_VOLUME_CORRUPTED;
- }
- } else {
- // GOOD_OLD_REV
- Partition->FeaturesCompat = Partition->FeaturesIncompat = Partition->FeaturesRoCompat = 0;
- Partition->InodeSize = EXT4_GOOD_OLD_INODE_SIZE;
- }
- // Now, check for the feature set of the filesystem
- // It's essential to check for this to avoid filesystem corruption and to avoid
- // accidentally opening an ext2/3/4 filesystem we don't understand, which would be disastrous.
- if (Partition->FeaturesIncompat & ~gSupportedIncompatFeat) {
- DEBUG ((
- DEBUG_ERROR,
- "[ext4] Unsupported features %lx\n",
- Partition->FeaturesIncompat & ~gSupportedIncompatFeat
- ));
- return EFI_UNSUPPORTED;
- }
- if (EXT4_HAS_INCOMPAT (Partition, EXT4_FEATURE_INCOMPAT_RECOVER)) {
- DEBUG ((DEBUG_WARN, "[ext4] Needs journal recovery, mounting read-only\n"));
- Partition->ReadOnly = TRUE;
- }
- // At the time of writing, it's the only supported checksum.
- if (EXT4_HAS_METADATA_CSUM (Partition) && (Sb->s_checksum_type != EXT4_CHECKSUM_CRC32C)) {
- return EFI_UNSUPPORTED;
- }
- if (EXT4_HAS_INCOMPAT (Partition, EXT4_FEATURE_INCOMPAT_CSUM_SEED)) {
- Partition->InitialSeed = Sb->s_checksum_seed;
- } else {
- Partition->InitialSeed = Ext4CalculateChecksum (Partition, Sb->s_uuid, 16, ~0U);
- }
- UnsupportedRoCompat = Partition->FeaturesRoCompat & ~gSupportedRoCompatFeat;
- if (UnsupportedRoCompat != 0) {
- DEBUG ((DEBUG_WARN, "[ext4] Unsupported ro compat %x\n", UnsupportedRoCompat));
- Partition->ReadOnly = TRUE;
- }
- // gSupportedCompatFeat is documentation-only since we never need to access it.
- // The line below avoids unused variable warnings.
- (VOID)gSupportedCompatFeat;
- DEBUG ((DEBUG_FS, "Read only = %u\n", Partition->ReadOnly));
- if (Sb->s_inodes_per_group == 0) {
- DEBUG ((DEBUG_ERROR, "[ext4] Inodes per group can not be zero\n"));
- return EFI_VOLUME_CORRUPTED;
- }
- if (Sb->s_log_block_size > EXT4_LOG_BLOCK_SIZE_MAX) {
- DEBUG ((DEBUG_ERROR, "[ext4] SuperBlock s_log_block_size %lu is too big\n", Sb->s_log_block_size));
- return EFI_UNSUPPORTED;
- }
- Partition->BlockSize = (UINT32)LShiftU64 (1024, Sb->s_log_block_size);
- // The size of a block group can also be calculated as 8 * Partition->BlockSize
- if (Sb->s_blocks_per_group != 8 * Partition->BlockSize) {
- return EFI_UNSUPPORTED;
- }
- Partition->NumberBlocks = EXT4_BLOCK_NR_FROM_HALFS (Partition, Sb->s_blocks_count, Sb->s_blocks_count_hi);
- Partition->NumberBlockGroups = DivU64x32 (Partition->NumberBlocks, Sb->s_blocks_per_group);
- DEBUG ((
- DEBUG_FS,
- "[ext4] Number of blocks = %lu\n[ext4] Number of block groups: %lu\n",
- Partition->NumberBlocks,
- Partition->NumberBlockGroups
- ));
- if (EXT4_IS_64_BIT (Partition)) {
- // s_desc_size should be 4 byte aligned and
- // 64 bit filesystems need DescSize to be 64 bytes
- if (((Sb->s_desc_size % 4) != 0) || (Sb->s_desc_size < EXT4_64BIT_BLOCK_DESC_SIZE)) {
- return EFI_VOLUME_CORRUPTED;
- }
- Partition->DescSize = Sb->s_desc_size;
- } else {
- Partition->DescSize = EXT4_OLD_BLOCK_DESC_SIZE;
- }
- if (!Ext4VerifySuperblockChecksum (Partition, Sb)) {
- DEBUG ((DEBUG_ERROR, "[ext4] Bad superblock checksum %lx\n", Ext4CalculateSuperblockChecksum (Partition, Sb)));
- return EFI_VOLUME_CORRUPTED;
- }
- NrBlocks = (UINTN)DivU64x32Remainder (
- MultU64x32 (Partition->NumberBlockGroups, Partition->DescSize),
- Partition->BlockSize,
- &NrBlocksRem
- );
- if (NrBlocksRem != 0) {
- NrBlocks++;
- }
- Partition->BlockGroups = Ext4AllocAndReadBlocks (Partition, NrBlocks, Partition->BlockSize == 1024 ? 2 : 1);
- if (Partition->BlockGroups == NULL) {
- return EFI_OUT_OF_RESOURCES;
- }
- for (Index = 0; Index < Partition->NumberBlockGroups; Index++) {
- Desc = Ext4GetBlockGroupDesc (Partition, Index);
- if (!Ext4VerifyBlockGroupDescChecksum (Partition, Desc, Index)) {
- DEBUG ((DEBUG_ERROR, "[ext4] Block group descriptor %u has an invalid checksum\n", Index));
- FreePool (Partition->BlockGroups);
- return EFI_VOLUME_CORRUPTED;
- }
- }
- // RootDentry will serve as the basis of our directory entry tree.
- Partition->RootDentry = Ext4CreateDentry (L"\\", NULL);
- if (Partition->RootDentry == NULL) {
- FreePool (Partition->BlockGroups);
- return EFI_OUT_OF_RESOURCES;
- }
- // Note that the cast below is completely safe, because EXT4_FILE is a specialization of EFI_FILE_PROTOCOL
- Status = Ext4OpenVolume (&Partition->Interface, (EFI_FILE_PROTOCOL **)&Partition->Root);
- if (EFI_ERROR (Status)) {
- Ext4UnrefDentry (Partition->RootDentry);
- FreePool (Partition->BlockGroups);
- }
- return Status;
- }
- /**
- Calculates the checksum of the given buffer.
- @param[in] Partition Pointer to the opened EXT4 partition.
- @param[in] Buffer Pointer to the buffer.
- @param[in] Length Length of the buffer, in bytes.
- @param[in] InitialValue Initial value of the CRC.
- @return The checksum.
- **/
- UINT32
- Ext4CalculateChecksum (
- IN CONST EXT4_PARTITION *Partition,
- IN CONST VOID *Buffer,
- IN UINTN Length,
- IN UINT32 InitialValue
- )
- {
- if (!EXT4_HAS_METADATA_CSUM (Partition)) {
- return 0;
- }
- switch (Partition->SuperBlock.s_checksum_type) {
- case EXT4_CHECKSUM_CRC32C:
- // For some reason, EXT4 really likes non-inverted CRC32C checksums, so we stick to that here.
- return ~CalculateCrc32c(Buffer, Length, ~InitialValue);
- default:
- ASSERT (FALSE);
- return 0;
- }
- }
|