audk/Ext4Pkg/Ext4Dxe/Superblock.c
Mike Beaton 4f8ce12f1e
Ext4Dxe: Convert DEBUG_WARN to DEBUG_INFO (#81)
The issues which are warned about can happen as a
normal part of life when using Linux, especially
`Needs journal recovery, mounting read-only`.
This means that when using Ext4Dxe with OpenCore
it becomes necessary not to include WARN in HaltLevel,
whereas in general, to keep a clean system, one
might want to include it.

More generally this PR tries to reflect the idea that
WARNs should be to let users know of config (and
other) errors which they should really fix. Whereas
here we have a state which occurs quite commonly
and which the user can do nothing about.

I am pretty certain that (from the point of view
described above) the changes in Superblock.c are
correct. I am less certain about the changes in
Symlink.c - but all the same, if they are not errors
and the system can continue, and the user can do
nothing about it, then probably the same logic applies.
2025-10-12 13:42:27 +03:00

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_INFO, "[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_INFO, "[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_INFO, "[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;
}
}