mirror of
https://github.com/CloverHackyColor/CloverBootloader.git
synced 2024-12-01 12:53:27 +01:00
364 lines
11 KiB
C
364 lines
11 KiB
C
/** @file
|
|
Superblock managing routines
|
|
|
|
Copyright (c) 2021 - 2023 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 | EXT4_FEATURE_INCOMPAT_CSUM_SEED;
|
|
|
|
// 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;
|
|
}
|
|
}
|