mirror of https://github.com/acidanthera/audk.git
Ext4Pkg: Add EDK II Ext4 package
Adds a Pedro Falcato's UEFI EXT4 filesystem driver that implements the EFI_FILE_PROTOCOL and EFI_SIMPLE_FILE_SYSTEM_PROTOCOL. Signed-off-by: Savva Mitrofanov <savvamtr@gmail.com>
This commit is contained in:
parent
3e722403cd
commit
0ca8d0738a
|
@ -0,0 +1,16 @@
|
|||
## @file
|
||||
# Ext4 DSC include file for Platform DSC
|
||||
#
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
#
|
||||
##
|
||||
|
||||
[Defines]
|
||||
!include Ext4Pkg/Ext4Defines.dsc.inc
|
||||
|
||||
[LibraryClasses]
|
||||
!include Ext4Pkg/Ext4Libs.dsc.inc
|
||||
|
||||
[Components.common]
|
||||
!include Ext4Pkg/Ext4Components.dsc.inc
|
|
@ -0,0 +1,11 @@
|
|||
## @file
|
||||
# Ext4 FDF include file for All Architectures.
|
||||
#
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
#
|
||||
##
|
||||
|
||||
!if $(EXT4_ENABLE) == TRUE
|
||||
INF Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
|
||||
!endif
|
|
@ -0,0 +1,14 @@
|
|||
## @file
|
||||
# Ext4 DSC include file for [Components] section of all Architectures.
|
||||
#
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
#
|
||||
##
|
||||
|
||||
!if $(EXT4_ENABLE) == TRUE
|
||||
Ext4Pkg/Ext4Dxe/Ext4Dxe.inf {
|
||||
<PcdsFixedAtBuild>
|
||||
gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0x80000007
|
||||
}
|
||||
!endif
|
|
@ -0,0 +1,14 @@
|
|||
## @file
|
||||
# Ext4 DSC include file for [Defines] section of all Architectures.
|
||||
#
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
#
|
||||
##
|
||||
|
||||
!ifndef EXT4_ENABLE
|
||||
#
|
||||
# This flag is to enable or disable the ext4 feature.
|
||||
#
|
||||
DEFINE EXT4_ENABLE = TRUE
|
||||
!endif
|
|
@ -0,0 +1,229 @@
|
|||
/** @file
|
||||
Block group related routines
|
||||
|
||||
Copyright (c) 2021 Pedro Falcato All rights reserved.
|
||||
Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.<BR>
|
||||
|
||||
SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
**/
|
||||
|
||||
#include "Ext4Dxe.h"
|
||||
|
||||
/**
|
||||
Retrieves a block group descriptor of the ext4 filesystem.
|
||||
|
||||
@param[in] Partition Pointer to the opened ext4 partition.
|
||||
@param[in] BlockGroup Block group number.
|
||||
|
||||
@return A pointer to the block group descriptor.
|
||||
**/
|
||||
EXT4_BLOCK_GROUP_DESC *
|
||||
Ext4GetBlockGroupDesc (
|
||||
IN EXT4_PARTITION *Partition,
|
||||
IN UINT32 BlockGroup
|
||||
)
|
||||
{
|
||||
// Maybe assert that the block group nr isn't a nonsense number?
|
||||
return (EXT4_BLOCK_GROUP_DESC *)((CHAR8 *)Partition->BlockGroups + BlockGroup * Partition->DescSize);
|
||||
}
|
||||
|
||||
/**
|
||||
Reads an inode from disk.
|
||||
|
||||
@param[in] Partition Pointer to the opened partition.
|
||||
@param[in] InodeNum Number of the desired Inode
|
||||
@param[out] OutIno Pointer to where it will be stored a pointer to the read inode.
|
||||
|
||||
@return Status of the inode read.
|
||||
**/
|
||||
EFI_STATUS
|
||||
Ext4ReadInode (
|
||||
IN EXT4_PARTITION *Partition,
|
||||
IN EXT4_INO_NR InodeNum,
|
||||
OUT EXT4_INODE **OutIno
|
||||
)
|
||||
{
|
||||
UINT64 InodeOffset;
|
||||
UINT32 BlockGroupNumber;
|
||||
EXT4_INODE *Inode;
|
||||
EXT4_BLOCK_GROUP_DESC *BlockGroup;
|
||||
EXT4_BLOCK_NR InodeTableStart;
|
||||
EFI_STATUS Status;
|
||||
|
||||
if (!EXT4_IS_VALID_INODE_NR (Partition, InodeNum)) {
|
||||
DEBUG ((DEBUG_ERROR, "[ext4] Error reading inode: inode number %lu isn't valid\n", InodeNum));
|
||||
return EFI_VOLUME_CORRUPTED;
|
||||
}
|
||||
|
||||
BlockGroupNumber = (UINT32)DivU64x64Remainder (
|
||||
InodeNum - 1,
|
||||
Partition->SuperBlock.s_inodes_per_group,
|
||||
&InodeOffset
|
||||
);
|
||||
|
||||
// Check for the block group number's correctness
|
||||
if (BlockGroupNumber >= Partition->NumberBlockGroups) {
|
||||
return EFI_VOLUME_CORRUPTED;
|
||||
}
|
||||
|
||||
Inode = Ext4AllocateInode (Partition);
|
||||
|
||||
if (Inode == NULL) {
|
||||
return EFI_OUT_OF_RESOURCES;
|
||||
}
|
||||
|
||||
BlockGroup = Ext4GetBlockGroupDesc (Partition, BlockGroupNumber);
|
||||
|
||||
// Note: We'll need to check INODE_UNINIT and friends when/if we add write support
|
||||
|
||||
InodeTableStart = EXT4_BLOCK_NR_FROM_HALFS (
|
||||
Partition,
|
||||
BlockGroup->bg_inode_table_lo,
|
||||
BlockGroup->bg_inode_table_hi
|
||||
);
|
||||
|
||||
Status = Ext4ReadDiskIo (
|
||||
Partition,
|
||||
Inode,
|
||||
Partition->InodeSize,
|
||||
EXT4_BLOCK_TO_BYTES (Partition, InodeTableStart) + MultU64x32 (InodeOffset, Partition->InodeSize)
|
||||
);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
DEBUG ((
|
||||
DEBUG_ERROR,
|
||||
"[ext4] Error reading inode: status %x; inode offset %lx"
|
||||
" inode table start %lu block group %lu\n",
|
||||
Status,
|
||||
InodeOffset,
|
||||
InodeTableStart,
|
||||
BlockGroupNumber
|
||||
));
|
||||
FreePool (Inode);
|
||||
return Status;
|
||||
}
|
||||
|
||||
if (!Ext4CheckInodeChecksum (Partition, Inode, InodeNum)) {
|
||||
DEBUG ((
|
||||
DEBUG_ERROR,
|
||||
"[ext4] Inode %llu has invalid checksum (calculated %x)\n",
|
||||
InodeNum,
|
||||
Ext4CalculateInodeChecksum (Partition, Inode, InodeNum)
|
||||
));
|
||||
FreePool (Inode);
|
||||
return EFI_VOLUME_CORRUPTED;
|
||||
}
|
||||
|
||||
*OutIno = Inode;
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
Calculates the checksum of the block group descriptor for METADATA_CSUM enabled filesystems.
|
||||
@param[in] Partition Pointer to the opened EXT4 partition.
|
||||
@param[in] BlockGroupDesc Pointer to the block group descriptor.
|
||||
@param[in] BlockGroupNum Number of the block group.
|
||||
|
||||
@return The checksum.
|
||||
**/
|
||||
STATIC
|
||||
UINT16
|
||||
Ext4CalculateBlockGroupDescChecksumMetadataCsum (
|
||||
IN CONST EXT4_PARTITION *Partition,
|
||||
IN CONST EXT4_BLOCK_GROUP_DESC *BlockGroupDesc,
|
||||
IN UINT32 BlockGroupNum
|
||||
)
|
||||
{
|
||||
UINT32 Csum;
|
||||
UINT16 Dummy;
|
||||
|
||||
Dummy = 0;
|
||||
|
||||
Csum = Ext4CalculateChecksum (Partition, &BlockGroupNum, sizeof (BlockGroupNum), Partition->InitialSeed);
|
||||
Csum = Ext4CalculateChecksum (Partition, BlockGroupDesc, OFFSET_OF (EXT4_BLOCK_GROUP_DESC, bg_checksum), Csum);
|
||||
Csum = Ext4CalculateChecksum (Partition, &Dummy, sizeof (Dummy), Csum);
|
||||
Csum =
|
||||
Ext4CalculateChecksum (
|
||||
Partition,
|
||||
&BlockGroupDesc->bg_block_bitmap_hi,
|
||||
Partition->DescSize - OFFSET_OF (EXT4_BLOCK_GROUP_DESC, bg_block_bitmap_hi),
|
||||
Csum
|
||||
);
|
||||
return (UINT16)Csum;
|
||||
}
|
||||
|
||||
/**
|
||||
Calculates the checksum of the block group descriptor for GDT_CSUM enabled filesystems.
|
||||
@param[in] Partition Pointer to the opened EXT4 partition.
|
||||
@param[in] BlockGroupDesc Pointer to the block group descriptor.
|
||||
@param[in] BlockGroupNum Number of the block group.
|
||||
|
||||
@return The checksum.
|
||||
**/
|
||||
STATIC
|
||||
UINT16
|
||||
Ext4CalculateBlockGroupDescChecksumGdtCsum (
|
||||
IN CONST EXT4_PARTITION *Partition,
|
||||
IN CONST EXT4_BLOCK_GROUP_DESC *BlockGroupDesc,
|
||||
IN UINT32 BlockGroupNum
|
||||
)
|
||||
{
|
||||
UINT16 Csum;
|
||||
|
||||
Csum = CalculateCrc16Ansi (Partition->SuperBlock.s_uuid, 16, CRC16ANSI_INIT);
|
||||
Csum = CalculateCrc16Ansi (&BlockGroupNum, sizeof (BlockGroupNum), Csum);
|
||||
Csum = CalculateCrc16Ansi (BlockGroupDesc, OFFSET_OF (EXT4_BLOCK_GROUP_DESC, bg_checksum), Csum);
|
||||
Csum =
|
||||
CalculateCrc16Ansi (
|
||||
&BlockGroupDesc->bg_block_bitmap_hi,
|
||||
Partition->DescSize - OFFSET_OF (EXT4_BLOCK_GROUP_DESC, bg_block_bitmap_hi),
|
||||
Csum
|
||||
);
|
||||
return Csum;
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if the checksum of the block group descriptor is correct.
|
||||
@param[in] Partition Pointer to the opened EXT4 partition.
|
||||
@param[in] BlockGroupDesc Pointer to the block group descriptor.
|
||||
@param[in] BlockGroupNum Number of the block group.
|
||||
|
||||
@return TRUE if checksum is correct, FALSE if there is corruption.
|
||||
**/
|
||||
BOOLEAN
|
||||
Ext4VerifyBlockGroupDescChecksum (
|
||||
IN CONST EXT4_PARTITION *Partition,
|
||||
IN CONST EXT4_BLOCK_GROUP_DESC *BlockGroupDesc,
|
||||
IN UINT32 BlockGroupNum
|
||||
)
|
||||
{
|
||||
if (!EXT4_HAS_METADATA_CSUM (Partition) && !EXT4_HAS_GDT_CSUM (Partition)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return Ext4CalculateBlockGroupDescChecksum (Partition, BlockGroupDesc, BlockGroupNum) == BlockGroupDesc->bg_checksum;
|
||||
}
|
||||
|
||||
/**
|
||||
Calculates the checksum of the block group descriptor.
|
||||
@param[in] Partition Pointer to the opened EXT4 partition.
|
||||
@param[in] BlockGroupDesc Pointer to the block group descriptor.
|
||||
@param[in] BlockGroupNum Number of the block group.
|
||||
|
||||
@return The checksum.
|
||||
**/
|
||||
UINT16
|
||||
Ext4CalculateBlockGroupDescChecksum (
|
||||
IN CONST EXT4_PARTITION *Partition,
|
||||
IN CONST EXT4_BLOCK_GROUP_DESC *BlockGroupDesc,
|
||||
IN UINT32 BlockGroupNum
|
||||
)
|
||||
{
|
||||
if (EXT4_HAS_METADATA_CSUM (Partition)) {
|
||||
return Ext4CalculateBlockGroupDescChecksumMetadataCsum (Partition, BlockGroupDesc, BlockGroupNum);
|
||||
} else if (EXT4_HAS_GDT_CSUM (Partition)) {
|
||||
return Ext4CalculateBlockGroupDescChecksumGdtCsum (Partition, BlockGroupDesc, BlockGroupNum);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,285 @@
|
|||
/** @file
|
||||
Implementation of routines that deal with ext2/3 block maps.
|
||||
|
||||
Copyright (c) 2022 Pedro Falcato All rights reserved.
|
||||
SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
**/
|
||||
|
||||
#include <Ext4Dxe.h>
|
||||
|
||||
// Note: The largest path we can take uses up 4 indices
|
||||
#define EXT4_MAX_BLOCK_PATH 4
|
||||
|
||||
typedef enum ext4_logical_block_type {
|
||||
EXT4_TYPE_DIRECT_BLOCK = 0,
|
||||
EXT4_TYPE_SINGLY_BLOCK,
|
||||
EXT4_TYPE_DOUBLY_BLOCK,
|
||||
EXT4_TYPE_TREBLY_BLOCK,
|
||||
EXT4_TYPE_BAD_BLOCK
|
||||
} EXT4_LOGICAL_BLOCK_TYPE;
|
||||
|
||||
/**
|
||||
@brief Detect the type of path the logical block will follow
|
||||
|
||||
@param[in] LogicalBlock The logical block
|
||||
@param[in] Partition Pointer to an EXT4_PARTITION
|
||||
@return The type of path the logical block will need to follow
|
||||
*/
|
||||
STATIC
|
||||
EXT4_LOGICAL_BLOCK_TYPE
|
||||
Ext4DetectBlockType (
|
||||
IN UINT32 LogicalBlock,
|
||||
IN CONST EXT4_PARTITION *Partition
|
||||
)
|
||||
{
|
||||
UINT32 Entries;
|
||||
UINT32 MinSinglyBlock;
|
||||
UINT32 MinDoublyBlock;
|
||||
UINT32 MinTreblyBlock;
|
||||
UINT32 MinQuadBlock;
|
||||
|
||||
Entries = (Partition->BlockSize / sizeof (UINT32));
|
||||
MinSinglyBlock = EXT4_DBLOCKS;
|
||||
MinDoublyBlock = Entries + MinSinglyBlock;
|
||||
MinTreblyBlock = Entries * Entries + MinDoublyBlock;
|
||||
MinQuadBlock = Entries * Entries * Entries + MinTreblyBlock; // Doesn't actually exist
|
||||
|
||||
if (LogicalBlock < MinSinglyBlock) {
|
||||
return EXT4_TYPE_DIRECT_BLOCK;
|
||||
} else if ((LogicalBlock >= MinSinglyBlock) && (LogicalBlock < MinDoublyBlock)) {
|
||||
return EXT4_TYPE_SINGLY_BLOCK;
|
||||
} else if ((LogicalBlock >= MinDoublyBlock) && (LogicalBlock < MinTreblyBlock)) {
|
||||
return EXT4_TYPE_DOUBLY_BLOCK;
|
||||
} else if (((LogicalBlock >= MinTreblyBlock) && (LogicalBlock < MinQuadBlock))) {
|
||||
return EXT4_TYPE_TREBLY_BLOCK;
|
||||
} else {
|
||||
return EXT4_TYPE_BAD_BLOCK;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get a block's path in indices
|
||||
|
||||
@param[in] Partition Pointer to an EXT4_PARTITION
|
||||
@param[in] LogicalBlock Logical block
|
||||
@param[out] BlockPath Pointer to an array of EXT4_MAX_BLOCK_PATH elements, where the
|
||||
indices we'll need to read are inserted.
|
||||
@return The number of path elements that are required (and were inserted in BlockPath)
|
||||
*/
|
||||
UINTN
|
||||
Ext4GetBlockPath (
|
||||
IN CONST EXT4_PARTITION *Partition,
|
||||
IN UINT32 LogicalBlock,
|
||||
OUT EXT2_BLOCK_NR BlockPath[EXT4_MAX_BLOCK_PATH]
|
||||
)
|
||||
{
|
||||
// The logic behind the block map is very much like a page table
|
||||
// Let's think of blocks with 512 entries (exactly like a page table on x64).
|
||||
// On doubly indirect block paths, we subtract the min doubly blocks from the logical block.
|
||||
// The top 9 bits of the result are the index inside the dind block, the bottom 9 bits are the
|
||||
// index inside the ind block. Since Entries is always a power of 2, entries - 1 will give us
|
||||
// a mask of the BlockMapBits.
|
||||
// Note that all this math could be done with ands and shifts (similar implementations exist
|
||||
// in a bunch of other places), but I'm doing it a simplified way with divs and modulus,
|
||||
// since it's not going to be a bottleneck anyway.
|
||||
|
||||
UINT32 Entries;
|
||||
UINT32 EntriesEntries;
|
||||
UINT32 MinSinglyBlock;
|
||||
UINT32 MinDoublyBlock;
|
||||
UINT32 MinTreblyBlock;
|
||||
|
||||
EXT4_LOGICAL_BLOCK_TYPE Type;
|
||||
|
||||
Entries = (Partition->BlockSize / sizeof (UINT32));
|
||||
EntriesEntries = Entries * Entries;
|
||||
|
||||
MinSinglyBlock = EXT4_DBLOCKS;
|
||||
MinDoublyBlock = Entries + MinSinglyBlock;
|
||||
MinTreblyBlock = EntriesEntries + MinDoublyBlock;
|
||||
|
||||
Type = Ext4DetectBlockType (LogicalBlock, Partition);
|
||||
|
||||
switch (Type) {
|
||||
case EXT4_TYPE_DIRECT_BLOCK:
|
||||
BlockPath[0] = LogicalBlock;
|
||||
break;
|
||||
case EXT4_TYPE_SINGLY_BLOCK:
|
||||
BlockPath[0] = EXT4_IND_BLOCK;
|
||||
BlockPath[1] = LogicalBlock - EXT4_DBLOCKS;
|
||||
break;
|
||||
case EXT4_TYPE_DOUBLY_BLOCK:
|
||||
BlockPath[0] = EXT4_DIND_BLOCK;
|
||||
LogicalBlock -= MinDoublyBlock;
|
||||
BlockPath[1] = LogicalBlock / Entries;
|
||||
BlockPath[2] = LogicalBlock % Entries;
|
||||
break;
|
||||
case EXT4_TYPE_TREBLY_BLOCK:
|
||||
BlockPath[0] = EXT4_DIND_BLOCK;
|
||||
LogicalBlock -= MinTreblyBlock;
|
||||
BlockPath[1] = LogicalBlock / EntriesEntries;
|
||||
BlockPath[2] = (LogicalBlock % EntriesEntries) / Entries;
|
||||
BlockPath[3] = (LogicalBlock % EntriesEntries) % Entries;
|
||||
break;
|
||||
default:
|
||||
// EXT4_TYPE_BAD_BLOCK
|
||||
break;
|
||||
}
|
||||
|
||||
return Type + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get an extent from a block map
|
||||
Note: Also parses file holes and creates uninitialized extents from them.
|
||||
|
||||
@param[in] Buffer Buffer of block pointers
|
||||
@param[in] IndEntries Number of entries in this block pointer table
|
||||
@param[in] StartIndex The start index from which we want to find a contiguous extent
|
||||
@param[out] Extent Pointer to the resulting EXT4_EXTENT
|
||||
*/
|
||||
VOID
|
||||
Ext4GetExtentInBlockMap (
|
||||
IN CONST UINT32 *Buffer,
|
||||
IN CONST UINT32 IndEntries,
|
||||
IN UINT32 StartIndex,
|
||||
OUT EXT4_EXTENT *Extent
|
||||
)
|
||||
{
|
||||
UINT32 Index;
|
||||
UINT32 FirstBlock;
|
||||
UINT32 LastBlock;
|
||||
UINT16 Count;
|
||||
|
||||
Count = 1;
|
||||
LastBlock = Buffer[StartIndex];
|
||||
FirstBlock = LastBlock;
|
||||
|
||||
if (FirstBlock == EXT4_BLOCK_FILE_HOLE) {
|
||||
// File hole, let's see how many blocks this hole spans
|
||||
Extent->ee_start_hi = 0;
|
||||
Extent->ee_start_lo = 0;
|
||||
|
||||
for (Index = StartIndex + 1; Index < IndEntries; Index++) {
|
||||
if (Count == EXT4_EXTENT_MAX_INITIALIZED - 1) {
|
||||
// We've reached the max size of an uninit extent, break
|
||||
break;
|
||||
}
|
||||
|
||||
if (Buffer[Index] == EXT4_BLOCK_FILE_HOLE) {
|
||||
Count++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We mark the extent as uninitialized, although there's a difference between uninit
|
||||
// extents and file holes.
|
||||
Extent->ee_len = EXT4_EXTENT_MAX_INITIALIZED + Count;
|
||||
return;
|
||||
}
|
||||
|
||||
for (Index = StartIndex + 1; Index < IndEntries; Index++) {
|
||||
if (Count == EXT4_EXTENT_MAX_INITIALIZED) {
|
||||
// We've reached the max size of an extent, break
|
||||
break;
|
||||
}
|
||||
|
||||
if ((Buffer[Index] == LastBlock + 1) && (Buffer[Index] != EXT4_BLOCK_FILE_HOLE)) {
|
||||
Count++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
LastBlock = Buffer[Index];
|
||||
}
|
||||
|
||||
Extent->ee_start_lo = FirstBlock;
|
||||
Extent->ee_start_hi = 0;
|
||||
Extent->ee_len = Count;
|
||||
}
|
||||
|
||||
/**
|
||||
Retrieves an extent from an EXT2/3 inode (with a blockmap).
|
||||
@param[in] Partition Pointer to the opened EXT4 partition.
|
||||
@param[in] File Pointer to the opened file.
|
||||
@param[in] LogicalBlock Block number which the returned extent must cover.
|
||||
@param[out] Extent Pointer to the output buffer, where the extent will be copied to.
|
||||
|
||||
@retval EFI_SUCCESS Retrieval was successful.
|
||||
@retval EFI_NO_MAPPING Block has no mapping.
|
||||
**/
|
||||
EFI_STATUS
|
||||
Ext4GetBlocks (
|
||||
IN EXT4_PARTITION *Partition,
|
||||
IN EXT4_FILE *File,
|
||||
IN EXT2_BLOCK_NR LogicalBlock,
|
||||
OUT EXT4_EXTENT *Extent
|
||||
)
|
||||
{
|
||||
EXT4_INODE *Inode;
|
||||
EXT2_BLOCK_NR BlockPath[EXT4_MAX_BLOCK_PATH];
|
||||
UINTN BlockPathLength;
|
||||
UINTN Index;
|
||||
UINT32 *Buffer;
|
||||
EFI_STATUS Status;
|
||||
UINT32 Block;
|
||||
UINT32 BlockIndex;
|
||||
|
||||
Inode = File->Inode;
|
||||
|
||||
BlockPathLength = Ext4GetBlockPath (Partition, LogicalBlock, BlockPath);
|
||||
|
||||
if (BlockPathLength - 1 == EXT4_TYPE_BAD_BLOCK) {
|
||||
// Bad logical block (out of range)
|
||||
return EFI_NO_MAPPING;
|
||||
}
|
||||
|
||||
Extent->ee_block = LogicalBlock;
|
||||
|
||||
if (BlockPathLength == 1) {
|
||||
// Fast path for blocks 0 - 12 that skips allocations
|
||||
Ext4GetExtentInBlockMap (Inode->i_data, EXT4_DBLOCKS, BlockPath[0], Extent);
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
Buffer = AllocatePool (Partition->BlockSize);
|
||||
if (Buffer == NULL) {
|
||||
return EFI_OUT_OF_RESOURCES;
|
||||
}
|
||||
|
||||
// Note the BlockPathLength - 1 so we don't end up reading the final block
|
||||
for (Index = 0; Index < BlockPathLength - 1; Index++) {
|
||||
BlockIndex = BlockPath[Index];
|
||||
|
||||
if (Index == 0) {
|
||||
Block = Inode->i_data[BlockIndex];
|
||||
} else {
|
||||
Block = Buffer[BlockIndex];
|
||||
}
|
||||
|
||||
if (Block == EXT4_BLOCK_FILE_HOLE) {
|
||||
FreePool (Buffer);
|
||||
return EFI_NO_MAPPING;
|
||||
}
|
||||
|
||||
Status = Ext4ReadBlocks (Partition, Buffer, 1, Block);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
FreePool (Buffer);
|
||||
return Status;
|
||||
}
|
||||
}
|
||||
|
||||
Ext4GetExtentInBlockMap (
|
||||
Buffer,
|
||||
Partition->BlockSize / sizeof (UINT32),
|
||||
BlockPath[BlockPathLength - 1],
|
||||
Extent
|
||||
);
|
||||
|
||||
FreePool (Buffer);
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
/** @file
|
||||
Unicode collation routines
|
||||
|
||||
Copyright (c) 2021 - 2023 Pedro Falcato All rights reserved.
|
||||
Copyright (c) 2005 - 2017, Intel Corporation. All rights reserved.
|
||||
|
||||
SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
**/
|
||||
|
||||
#include <Uefi.h>
|
||||
|
||||
#include <Library/DebugLib.h>
|
||||
#include <Library/UefiLib.h>
|
||||
#include <Library/UefiBootServicesTableLib.h>
|
||||
#include <Library/MemoryAllocationLib.h>
|
||||
|
||||
#include <Protocol/UnicodeCollation.h>
|
||||
|
||||
STATIC EFI_UNICODE_COLLATION_PROTOCOL *gUnicodeCollationInterface = NULL;
|
||||
|
||||
/*
|
||||
* Note: This code is heavily based on FatPkg's Unicode collation, since they seem to know what
|
||||
* they're doing.
|
||||
* PS: Maybe all this code could be put in a library? It looks heavily shareable.
|
||||
**/
|
||||
|
||||
/**
|
||||
Check if unicode collation is initialized
|
||||
|
||||
@retval TRUE if Ext4InitialiseUnicodeCollation() was already called successfully
|
||||
@retval FALSE if Ext4InitialiseUnicodeCollation() was not yet called successfully
|
||||
**/
|
||||
STATIC
|
||||
BOOLEAN
|
||||
Ext4IsCollationInitialized (
|
||||
VOID
|
||||
)
|
||||
{
|
||||
return gUnicodeCollationInterface != NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
Worker function to initialize Unicode Collation support.
|
||||
|
||||
It tries to locate Unicode Collation (2) protocol and matches it with current
|
||||
platform language code.
|
||||
|
||||
@param[in] DriverHandle The handle used to open Unicode Collation (2) protocol.
|
||||
@param[in] ProtocolGuid The pointer to Unicode Collation (2) protocol GUID.
|
||||
@param[in] VariableName The name of the RFC 4646 or ISO 639-2 language variable.
|
||||
@param[in] DefaultLanguage The default language in case the RFC 4646 or ISO 639-2 language is absent.
|
||||
|
||||
@retval EFI_SUCCESS The Unicode Collation (2) protocol has been successfully located.
|
||||
@retval Others The Unicode Collation (2) protocol has not been located.
|
||||
|
||||
**/
|
||||
STATIC
|
||||
EFI_STATUS
|
||||
Ext4InitialiseUnicodeCollationInternal (
|
||||
IN EFI_HANDLE DriverHandle,
|
||||
IN EFI_GUID *ProtocolGuid,
|
||||
IN CONST CHAR16 *VariableName,
|
||||
IN CONST CHAR8 *DefaultLanguage
|
||||
)
|
||||
{
|
||||
UINTN NumHandles;
|
||||
EFI_HANDLE *Handles;
|
||||
EFI_UNICODE_COLLATION_PROTOCOL *Uci;
|
||||
BOOLEAN Iso639Language;
|
||||
CHAR8 *Language;
|
||||
EFI_STATUS RetStatus;
|
||||
EFI_STATUS Status;
|
||||
UINTN Idx;
|
||||
CHAR8 *BestLanguage;
|
||||
|
||||
Iso639Language = (BOOLEAN)(ProtocolGuid == &gEfiUnicodeCollationProtocolGuid);
|
||||
RetStatus = EFI_UNSUPPORTED;
|
||||
GetEfiGlobalVariable2 (VariableName, (VOID **)&Language, NULL);
|
||||
|
||||
Status = gBS->LocateHandleBuffer (
|
||||
ByProtocol,
|
||||
ProtocolGuid,
|
||||
NULL,
|
||||
&NumHandles,
|
||||
&Handles
|
||||
);
|
||||
if (EFI_ERROR (Status)) {
|
||||
return Status;
|
||||
}
|
||||
|
||||
for (Idx = 0; Idx < NumHandles; Idx++) {
|
||||
Status = gBS->OpenProtocol (
|
||||
Handles[Idx],
|
||||
ProtocolGuid,
|
||||
(VOID **)&Uci,
|
||||
DriverHandle,
|
||||
NULL,
|
||||
EFI_OPEN_PROTOCOL_GET_PROTOCOL
|
||||
);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BestLanguage = GetBestLanguage (
|
||||
Uci->SupportedLanguages,
|
||||
Iso639Language,
|
||||
(Language == NULL) ? "" : Language,
|
||||
DefaultLanguage,
|
||||
NULL
|
||||
);
|
||||
if (BestLanguage != NULL) {
|
||||
FreePool (BestLanguage);
|
||||
gUnicodeCollationInterface = Uci;
|
||||
RetStatus = EFI_SUCCESS;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Language != NULL) {
|
||||
FreePool (Language);
|
||||
}
|
||||
|
||||
FreePool (Handles);
|
||||
return RetStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
Initialises Unicode collation, which is needed for case-insensitive string comparisons
|
||||
within the driver (a good example of an application of this is filename comparison).
|
||||
|
||||
@param[in] DriverHandle Handle to the driver image.
|
||||
|
||||
@retval EFI_SUCCESS Unicode collation was successfully initialised.
|
||||
@retval !EFI_SUCCESS Failure.
|
||||
**/
|
||||
EFI_STATUS
|
||||
Ext4InitialiseUnicodeCollation (
|
||||
EFI_HANDLE DriverHandle
|
||||
)
|
||||
{
|
||||
EFI_STATUS Status;
|
||||
|
||||
Status = EFI_UNSUPPORTED;
|
||||
|
||||
// If already done, just return success.
|
||||
if (Ext4IsCollationInitialized ()) {
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
//
|
||||
// First try to use RFC 4646 Unicode Collation 2 Protocol.
|
||||
//
|
||||
Status = Ext4InitialiseUnicodeCollationInternal (
|
||||
DriverHandle,
|
||||
&gEfiUnicodeCollation2ProtocolGuid,
|
||||
L"PlatformLang",
|
||||
(CONST CHAR8 *)PcdGetPtr (PcdUefiVariableDefaultPlatformLang)
|
||||
);
|
||||
//
|
||||
// If the attempt to use Unicode Collation 2 Protocol fails, then we fall back
|
||||
// on the ISO 639-2 Unicode Collation Protocol.
|
||||
//
|
||||
if (EFI_ERROR (Status)) {
|
||||
Status = Ext4InitialiseUnicodeCollationInternal (
|
||||
DriverHandle,
|
||||
&gEfiUnicodeCollationProtocolGuid,
|
||||
L"Lang",
|
||||
(CONST CHAR8 *)PcdGetPtr (PcdUefiVariableDefaultLang)
|
||||
);
|
||||
}
|
||||
|
||||
return Status;
|
||||
}
|
||||
|
||||
/**
|
||||
Does a case-insensitive string comparison. Refer to EFI_UNICODE_COLLATION_PROTOCOL's StriColl
|
||||
for more details.
|
||||
|
||||
@param[in] Str1 Pointer to a null terminated string.
|
||||
@param[in] Str2 Pointer to a null terminated string.
|
||||
|
||||
@retval 0 Str1 is equivalent to Str2.
|
||||
@retval >0 Str1 is lexically greater than Str2.
|
||||
@retval <0 Str1 is lexically less than Str2.
|
||||
**/
|
||||
INTN
|
||||
Ext4StrCmpInsensitive (
|
||||
IN CHAR16 *Str1,
|
||||
IN CHAR16 *Str2
|
||||
)
|
||||
{
|
||||
ASSERT (gUnicodeCollationInterface != NULL);
|
||||
return gUnicodeCollationInterface->StriColl (gUnicodeCollationInterface, Str1, Str2);
|
||||
}
|
|
@ -0,0 +1,708 @@
|
|||
/** @file
|
||||
Directory related routines
|
||||
|
||||
Copyright (c) 2021 - 2023 Pedro Falcato All rights reserved.
|
||||
|
||||
SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
**/
|
||||
|
||||
#include "Ext4Dxe.h"
|
||||
|
||||
#include <Library/BaseUcs2Utf8Lib.h>
|
||||
|
||||
/**
|
||||
Retrieves the filename of the directory entry and converts it to UTF-16/UCS-2
|
||||
|
||||
@param[in] Entry Pointer to a EXT4_DIR_ENTRY.
|
||||
@param[out] Ucs2FileName Pointer to an array of CHAR16's, of size EXT4_NAME_MAX + 1.
|
||||
|
||||
@retval EFI_SUCCESS The filename was successfully retrieved and converted to UCS2.
|
||||
@retval EFI_INVALID_PARAMETER The filename is not valid UTF-8.
|
||||
@retval !EFI_SUCCESS Failure.
|
||||
**/
|
||||
EFI_STATUS
|
||||
Ext4GetUcs2DirentName (
|
||||
IN EXT4_DIR_ENTRY *Entry,
|
||||
OUT CHAR16 Ucs2FileName[EXT4_NAME_MAX + 1]
|
||||
)
|
||||
{
|
||||
CHAR8 Utf8NameBuf[EXT4_NAME_MAX + 1];
|
||||
UINT16 *Str;
|
||||
UINT8 Index;
|
||||
EFI_STATUS Status;
|
||||
|
||||
for (Index = 0; Index < Entry->name_len; ++Index) {
|
||||
if (Entry->name[Index] == '\0') {
|
||||
return EFI_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
Utf8NameBuf[Index] = Entry->name[Index];
|
||||
}
|
||||
|
||||
Utf8NameBuf[Entry->name_len] = '\0';
|
||||
|
||||
// Unfortunately, BaseUcs2Utf8Lib doesn't have a convert-buffer-to-buffer-like
|
||||
// function. Therefore, we need to allocate from the pool (inside UTF8StrToUCS2),
|
||||
// copy it to our out buffer (Ucs2FileName) and free.
|
||||
|
||||
Status = UTF8StrToUCS2 (Utf8NameBuf, &Str);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
return Status;
|
||||
}
|
||||
|
||||
Status = StrCpyS (Ucs2FileName, EXT4_NAME_MAX + 1, Str);
|
||||
|
||||
FreePool (Str);
|
||||
|
||||
return Status;
|
||||
}
|
||||
|
||||
/**
|
||||
Validates a directory entry.
|
||||
|
||||
@param[in] Dirent Pointer to the directory entry.
|
||||
|
||||
@retval TRUE Valid directory entry.
|
||||
FALSE Invalid directory entry.
|
||||
**/
|
||||
STATIC
|
||||
BOOLEAN
|
||||
Ext4ValidDirent (
|
||||
IN CONST EXT4_DIR_ENTRY *Dirent
|
||||
)
|
||||
{
|
||||
UINTN RequiredSize;
|
||||
|
||||
RequiredSize = Dirent->name_len + EXT4_MIN_DIR_ENTRY_LEN;
|
||||
|
||||
if (Dirent->rec_len < RequiredSize) {
|
||||
DEBUG ((DEBUG_ERROR, "[ext4] dirent size %lu too small (compared to %lu)\n", Dirent->rec_len, RequiredSize));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Dirent sizes need to be 4 byte aligned
|
||||
if ((Dirent->rec_len % 4) != 0) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
Retrieves a directory entry.
|
||||
|
||||
@param[in] Directory Pointer to the opened directory.
|
||||
@param[in] NameUnicode Pointer to the UCS-2 formatted filename.
|
||||
@param[in] Partition Pointer to the ext4 partition.
|
||||
@param[out] Result Pointer to the destination directory entry.
|
||||
|
||||
@return The result of the operation.
|
||||
**/
|
||||
EFI_STATUS
|
||||
Ext4RetrieveDirent (
|
||||
IN EXT4_FILE *Directory,
|
||||
IN CONST CHAR16 *Name,
|
||||
IN EXT4_PARTITION *Partition,
|
||||
OUT EXT4_DIR_ENTRY *Result
|
||||
)
|
||||
{
|
||||
EFI_STATUS Status;
|
||||
CHAR8 *Buf;
|
||||
UINT64 Off;
|
||||
EXT4_INODE *Inode;
|
||||
UINT64 DirInoSize;
|
||||
UINT32 BlockRemainder;
|
||||
UINTN Length;
|
||||
EXT4_DIR_ENTRY *Entry;
|
||||
UINTN RemainingBlock;
|
||||
CHAR16 DirentUcs2Name[EXT4_NAME_MAX + 1];
|
||||
UINTN ToCopy;
|
||||
UINTN BlockOffset;
|
||||
|
||||
Buf = AllocatePool (Partition->BlockSize);
|
||||
|
||||
if (Buf == NULL) {
|
||||
return EFI_OUT_OF_RESOURCES;
|
||||
}
|
||||
|
||||
Off = 0;
|
||||
|
||||
Inode = Directory->Inode;
|
||||
DirInoSize = EXT4_INODE_SIZE (Inode);
|
||||
|
||||
DivU64x32Remainder (DirInoSize, Partition->BlockSize, &BlockRemainder);
|
||||
if (BlockRemainder != 0) {
|
||||
// Directory inodes need to have block aligned sizes
|
||||
Status = EFI_VOLUME_CORRUPTED;
|
||||
goto Out;
|
||||
}
|
||||
|
||||
while (Off < DirInoSize) {
|
||||
Length = Partition->BlockSize;
|
||||
|
||||
Status = Ext4Read (Partition, Directory, Buf, Off, &Length);
|
||||
|
||||
if (Status != EFI_SUCCESS) {
|
||||
goto Out;
|
||||
}
|
||||
|
||||
for (BlockOffset = 0; BlockOffset < Partition->BlockSize; ) {
|
||||
Entry = (EXT4_DIR_ENTRY *)(Buf + BlockOffset);
|
||||
RemainingBlock = Partition->BlockSize - BlockOffset;
|
||||
// Check if the minimum directory entry fits inside [BlockOffset, EndOfBlock]
|
||||
if (RemainingBlock < EXT4_MIN_DIR_ENTRY_LEN) {
|
||||
Status = EFI_VOLUME_CORRUPTED;
|
||||
goto Out;
|
||||
}
|
||||
|
||||
if (!Ext4ValidDirent (Entry)) {
|
||||
Status = EFI_VOLUME_CORRUPTED;
|
||||
goto Out;
|
||||
}
|
||||
|
||||
if ((Entry->name_len > RemainingBlock) || (Entry->rec_len > RemainingBlock)) {
|
||||
// Corrupted filesystem
|
||||
Status = EFI_VOLUME_CORRUPTED;
|
||||
goto Out;
|
||||
}
|
||||
|
||||
// Unused entry
|
||||
if (Entry->inode == 0) {
|
||||
BlockOffset += Entry->rec_len;
|
||||
continue;
|
||||
}
|
||||
|
||||
Status = Ext4GetUcs2DirentName (Entry, DirentUcs2Name);
|
||||
|
||||
/* In theory, this should never fail.
|
||||
* In reality, it's quite possible that it can fail, considering filenames in
|
||||
* Linux (and probably other nixes) are just null-terminated bags of bytes, and don't
|
||||
* need to form valid ASCII/UTF-8 sequences.
|
||||
*/
|
||||
if (EFI_ERROR (Status)) {
|
||||
if (Status == EFI_INVALID_PARAMETER) {
|
||||
// If we error out due to a bad UTF-8 sequence (see Ext4GetUcs2DirentName), skip this entry.
|
||||
// I'm not sure if this is correct behaviour, but I don't think there's a precedent here.
|
||||
BlockOffset += Entry->rec_len;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Other sorts of errors should just error out.
|
||||
FreePool (Buf);
|
||||
return Status;
|
||||
}
|
||||
|
||||
if ((Entry->name_len == StrLen (Name)) &&
|
||||
!Ext4StrCmpInsensitive (DirentUcs2Name, (CHAR16 *)Name))
|
||||
{
|
||||
ToCopy = MIN (Entry->rec_len, sizeof (EXT4_DIR_ENTRY));
|
||||
|
||||
CopyMem (Result, Entry, ToCopy);
|
||||
Status = EFI_SUCCESS;
|
||||
goto Out;
|
||||
}
|
||||
|
||||
BlockOffset += Entry->rec_len;
|
||||
}
|
||||
|
||||
Off += Partition->BlockSize;
|
||||
}
|
||||
|
||||
Status = EFI_NOT_FOUND;
|
||||
|
||||
Out:
|
||||
FreePool (Buf);
|
||||
return Status;
|
||||
}
|
||||
|
||||
/**
|
||||
Opens a file using a directory entry.
|
||||
|
||||
@param[in] Partition Pointer to the ext4 partition.
|
||||
@param[in] OpenMode Mode in which the file is supposed to be open.
|
||||
@param[out] OutFile Pointer to the newly opened file.
|
||||
@param[in] Entry Directory entry to be used.
|
||||
@param[in] Directory Pointer to the opened directory.
|
||||
|
||||
@retval EFI_STATUS Result of the operation
|
||||
**/
|
||||
EFI_STATUS
|
||||
Ext4OpenDirent (
|
||||
IN EXT4_PARTITION *Partition,
|
||||
IN UINT64 OpenMode,
|
||||
OUT EXT4_FILE **OutFile,
|
||||
IN EXT4_DIR_ENTRY *Entry,
|
||||
IN EXT4_FILE *Directory
|
||||
)
|
||||
{
|
||||
EFI_STATUS Status;
|
||||
CHAR16 FileName[EXT4_NAME_MAX + 1];
|
||||
EXT4_FILE *File;
|
||||
|
||||
File = AllocateZeroPool (sizeof (EXT4_FILE));
|
||||
|
||||
if (File == NULL) {
|
||||
Status = EFI_OUT_OF_RESOURCES;
|
||||
goto Error;
|
||||
}
|
||||
|
||||
Status = Ext4GetUcs2DirentName (Entry, FileName);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
goto Error;
|
||||
}
|
||||
|
||||
if (StrCmp (FileName, L".") == 0) {
|
||||
// We're using the parent directory's dentry
|
||||
File->Dentry = Directory->Dentry;
|
||||
|
||||
ASSERT (File->Dentry != NULL);
|
||||
|
||||
Ext4RefDentry (File->Dentry);
|
||||
} else if (StrCmp (FileName, L"..") == 0) {
|
||||
// Using the parent's parent's dentry
|
||||
File->Dentry = Directory->Dentry->Parent;
|
||||
|
||||
if (!File->Dentry) {
|
||||
// Someone tried .. on root, so direct them to /
|
||||
// This is an illegal EFI Open() but is possible to hit from a variety of internal code
|
||||
File->Dentry = Directory->Dentry;
|
||||
}
|
||||
|
||||
Ext4RefDentry (File->Dentry);
|
||||
} else {
|
||||
File->Dentry = Ext4CreateDentry (FileName, Directory->Dentry);
|
||||
|
||||
if (File->Dentry == NULL) {
|
||||
Status = EFI_OUT_OF_RESOURCES;
|
||||
goto Error;
|
||||
}
|
||||
}
|
||||
|
||||
Status = Ext4InitExtentsMap (File);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
goto Error;
|
||||
}
|
||||
|
||||
File->InodeNum = Entry->inode;
|
||||
|
||||
Ext4SetupFile (File, Partition);
|
||||
|
||||
Status = Ext4ReadInode (Partition, Entry->inode, &File->Inode);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
goto Error;
|
||||
}
|
||||
|
||||
*OutFile = File;
|
||||
|
||||
InsertTailList (&Partition->OpenFiles, &File->OpenFilesListNode);
|
||||
|
||||
return EFI_SUCCESS;
|
||||
|
||||
Error:
|
||||
if (File != NULL) {
|
||||
if (File->Dentry != NULL) {
|
||||
Ext4UnrefDentry (File->Dentry);
|
||||
}
|
||||
|
||||
if (File->ExtentsMap != NULL) {
|
||||
OrderedCollectionUninit (File->ExtentsMap);
|
||||
}
|
||||
|
||||
FreePool (File);
|
||||
}
|
||||
|
||||
return Status;
|
||||
}
|
||||
|
||||
/**
|
||||
Opens a file.
|
||||
|
||||
@param[in] Directory Pointer to the opened directory.
|
||||
@param[in] Name Pointer to the UCS-2 formatted filename.
|
||||
@param[in] Partition Pointer to the ext4 partition.
|
||||
@param[in] OpenMode Mode in which the file is supposed to be open.
|
||||
@param[out] OutFile Pointer to the newly opened file.
|
||||
|
||||
@return Result of the operation.
|
||||
**/
|
||||
EFI_STATUS
|
||||
Ext4OpenFile (
|
||||
IN EXT4_FILE *Directory,
|
||||
IN CONST CHAR16 *Name,
|
||||
IN EXT4_PARTITION *Partition,
|
||||
IN UINT64 OpenMode,
|
||||
OUT EXT4_FILE **OutFile
|
||||
)
|
||||
{
|
||||
EXT4_DIR_ENTRY Entry;
|
||||
EFI_STATUS Status;
|
||||
|
||||
Status = Ext4RetrieveDirent (Directory, Name, Partition, &Entry);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
return Status;
|
||||
}
|
||||
|
||||
// EFI requires us to error out on ".." opens for the root directory
|
||||
if (Entry.inode == Directory->InodeNum) {
|
||||
return EFI_NOT_FOUND;
|
||||
}
|
||||
|
||||
return Ext4OpenDirent (Partition, OpenMode, OutFile, &Entry, Directory);
|
||||
}
|
||||
|
||||
/**
|
||||
Open the root directory on a volume.
|
||||
|
||||
@param[in] This A pointer to the volume to open the root directory.
|
||||
@param[out] Root A pointer to the location to return the opened file handle for the
|
||||
root directory.
|
||||
|
||||
@retval EFI_SUCCESS The device was opened.
|
||||
@retval EFI_UNSUPPORTED This volume does not support the requested file system type.
|
||||
@retval EFI_NO_MEDIA The device has no medium.
|
||||
@retval EFI_DEVICE_ERROR The device reported an error.
|
||||
@retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
|
||||
@retval EFI_ACCESS_DENIED The service denied access to the file.
|
||||
@retval EFI_OUT_OF_RESOURCES The volume was not opened due to lack of resources.
|
||||
@retval EFI_MEDIA_CHANGED The device has a different medium in it or the medium is no
|
||||
longer supported. Any existing file handles for this volume are
|
||||
no longer valid. To access the files on the new medium, the
|
||||
volume must be reopened with OpenVolume().
|
||||
|
||||
**/
|
||||
EFI_STATUS
|
||||
EFIAPI
|
||||
Ext4OpenVolume (
|
||||
IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *This,
|
||||
OUT EFI_FILE_PROTOCOL **Root
|
||||
)
|
||||
{
|
||||
EXT4_INODE *RootInode;
|
||||
EFI_STATUS Status;
|
||||
EXT4_FILE *RootDir;
|
||||
EXT4_PARTITION *Partition;
|
||||
|
||||
Partition = (EXT4_PARTITION *)This;
|
||||
|
||||
Status = Ext4ReadInode (Partition, EXT4_ROOT_INODE_NR, &RootInode);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
DEBUG ((DEBUG_ERROR, "[ext4] Could not open root inode - error %r\n", Status));
|
||||
return Status;
|
||||
}
|
||||
|
||||
RootDir = AllocateZeroPool (sizeof (EXT4_FILE));
|
||||
|
||||
if (RootDir == NULL) {
|
||||
FreePool (RootInode);
|
||||
return EFI_OUT_OF_RESOURCES;
|
||||
}
|
||||
|
||||
RootDir->Inode = RootInode;
|
||||
RootDir->InodeNum = EXT4_ROOT_INODE_NR;
|
||||
|
||||
Status = Ext4InitExtentsMap (RootDir);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
FreePool (RootInode);
|
||||
FreePool (RootDir);
|
||||
return EFI_OUT_OF_RESOURCES;
|
||||
}
|
||||
|
||||
Ext4SetupFile (RootDir, Partition);
|
||||
*Root = &RootDir->Protocol;
|
||||
|
||||
InsertTailList (&Partition->OpenFiles, &RootDir->OpenFilesListNode);
|
||||
|
||||
ASSERT (Partition->RootDentry != NULL);
|
||||
RootDir->Dentry = Partition->RootDentry;
|
||||
|
||||
Ext4RefDentry (RootDir->Dentry);
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
Reads a directory entry.
|
||||
|
||||
@param[in] Partition Pointer to the ext4 partition.
|
||||
@param[in] File Pointer to the open directory.
|
||||
@param[out] Buffer Pointer to the output buffer.
|
||||
@param[in] Offset Initial directory position.
|
||||
@param[in out] OutLength Pointer to a UINTN that contains the length of the buffer,
|
||||
and the length of the actual EFI_FILE_INFO after the call.
|
||||
|
||||
@return Result of the operation.
|
||||
**/
|
||||
EFI_STATUS
|
||||
Ext4ReadDir (
|
||||
IN EXT4_PARTITION *Partition,
|
||||
IN EXT4_FILE *File,
|
||||
OUT VOID *Buffer,
|
||||
IN UINT64 Offset,
|
||||
IN OUT UINTN *OutLength
|
||||
)
|
||||
{
|
||||
EXT4_INODE *DirIno;
|
||||
EFI_STATUS Status;
|
||||
UINT64 DirInoSize;
|
||||
UINTN Len;
|
||||
UINT32 BlockRemainder;
|
||||
EXT4_DIR_ENTRY Entry;
|
||||
EXT4_FILE *TempFile;
|
||||
BOOLEAN ShouldSkip;
|
||||
BOOLEAN IsDotOrDotDot;
|
||||
CHAR16 DirentUcs2Name[EXT4_NAME_MAX + 1];
|
||||
|
||||
DirIno = File->Inode;
|
||||
Status = EFI_SUCCESS;
|
||||
DirInoSize = EXT4_INODE_SIZE (DirIno);
|
||||
|
||||
DivU64x32Remainder (DirInoSize, Partition->BlockSize, &BlockRemainder);
|
||||
if (BlockRemainder != 0) {
|
||||
// Directory inodes need to have block aligned sizes
|
||||
return EFI_VOLUME_CORRUPTED;
|
||||
}
|
||||
|
||||
while (TRUE) {
|
||||
TempFile = NULL;
|
||||
|
||||
// We (try to) read the maximum size of a directory entry at a time
|
||||
// Note that we don't need to read any padding that may exist after it.
|
||||
Len = sizeof (Entry);
|
||||
Status = Ext4Read (Partition, File, &Entry, Offset, &Len);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
goto Out;
|
||||
}
|
||||
|
||||
if (Len == 0) {
|
||||
*OutLength = 0;
|
||||
Status = EFI_SUCCESS;
|
||||
goto Out;
|
||||
}
|
||||
|
||||
if (Len < EXT4_MIN_DIR_ENTRY_LEN) {
|
||||
Status = EFI_VOLUME_CORRUPTED;
|
||||
goto Out;
|
||||
}
|
||||
|
||||
// Invalid directory entry length
|
||||
if (!Ext4ValidDirent (&Entry)) {
|
||||
DEBUG ((DEBUG_ERROR, "[ext4] Invalid dirent at offset %lu\n", Offset));
|
||||
Status = EFI_VOLUME_CORRUPTED;
|
||||
goto Out;
|
||||
}
|
||||
|
||||
// Check if the entire dir entry length fits in Len
|
||||
if (Len < (UINTN)(EXT4_MIN_DIR_ENTRY_LEN + Entry.name_len)) {
|
||||
Status = EFI_VOLUME_CORRUPTED;
|
||||
goto Out;
|
||||
}
|
||||
|
||||
// We don't care about passing . or .. entries to the caller of ReadDir(),
|
||||
// since they're generally useless entries *and* may break things if too
|
||||
// many callers assume FAT32.
|
||||
|
||||
// Entry.name_len may be 0 if it's a nameless entry, like an unused entry
|
||||
// or a checksum at the end of the directory block.
|
||||
// memcmp (and CompareMem) return 0 when the passed length is 0.
|
||||
|
||||
// We must bound name_len as > 0 and <= 2 to avoid any out-of-bounds accesses or bad detection of
|
||||
// "." and "..".
|
||||
IsDotOrDotDot = Entry.name_len > 0 && Entry.name_len <= 2 &&
|
||||
CompareMem (Entry.name, "..", Entry.name_len) == 0;
|
||||
|
||||
// When inode = 0, it's unused. When name_len == 0, it's a nameless entry
|
||||
// (which we should not expose to ReadDir).
|
||||
ShouldSkip = Entry.inode == 0 || Entry.name_len == 0 || IsDotOrDotDot;
|
||||
|
||||
if (ShouldSkip) {
|
||||
Offset += Entry.rec_len;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Test if the dirent is valid utf-8. This is already done inside Ext4OpenDirent but EFI_INVALID_PARAMETER
|
||||
// has the danger of its meaning being overloaded in many places, so we can't skip according to that.
|
||||
// So test outside of it, explicitly.
|
||||
Status = Ext4GetUcs2DirentName (&Entry, DirentUcs2Name);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
if (Status == EFI_INVALID_PARAMETER) {
|
||||
// Bad UTF-8, skip.
|
||||
Offset += Entry.rec_len;
|
||||
continue;
|
||||
}
|
||||
|
||||
goto Out;
|
||||
}
|
||||
|
||||
Status = Ext4OpenDirent (Partition, EFI_FILE_MODE_READ, &TempFile, &Entry, File);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
goto Out;
|
||||
}
|
||||
|
||||
Status = Ext4GetFileInfo (TempFile, Buffer, OutLength);
|
||||
if (!EFI_ERROR (Status)) {
|
||||
File->Position = Offset + Entry.rec_len;
|
||||
}
|
||||
|
||||
Ext4CloseInternal (TempFile);
|
||||
|
||||
goto Out;
|
||||
}
|
||||
|
||||
Status = EFI_SUCCESS;
|
||||
Out:
|
||||
return Status;
|
||||
}
|
||||
|
||||
/**
|
||||
Removes a dentry from the other's list.
|
||||
|
||||
@param[in out] Parent Pointer to the parent EXT4_DENTRY.
|
||||
@param[in out] ToBeRemoved Pointer to the child EXT4_DENTRY.
|
||||
**/
|
||||
STATIC
|
||||
VOID
|
||||
Ext4RemoveDentry (
|
||||
IN OUT EXT4_DENTRY *Parent,
|
||||
IN OUT EXT4_DENTRY *ToBeRemoved
|
||||
)
|
||||
{
|
||||
ASSERT (IsNodeInList (&ToBeRemoved->ListNode, &Parent->Children));
|
||||
RemoveEntryList (&ToBeRemoved->ListNode);
|
||||
}
|
||||
|
||||
/**
|
||||
Adds a dentry to the other's list.
|
||||
|
||||
The dentry that is added to the other one's list gets ->Parent set to Parent,
|
||||
and the parent gets its reference count incremented.
|
||||
|
||||
@param[in out] Parent Pointer to the parent EXT4_DENTRY.
|
||||
@param[in out] ToBeAdded Pointer to the child EXT4_DENTRY.
|
||||
**/
|
||||
STATIC
|
||||
VOID
|
||||
Ext4AddDentry (
|
||||
IN OUT EXT4_DENTRY *Parent,
|
||||
IN OUT EXT4_DENTRY *ToBeAdded
|
||||
)
|
||||
{
|
||||
ToBeAdded->Parent = Parent;
|
||||
InsertTailList (&Parent->Children, &ToBeAdded->ListNode);
|
||||
Ext4RefDentry (Parent);
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a new dentry object.
|
||||
|
||||
@param[in] Name Name of the dentry.
|
||||
@param[in out opt] Parent Parent dentry, if it's not NULL.
|
||||
|
||||
@return The new allocated and initialised dentry.
|
||||
The ref count will be set to 1.
|
||||
**/
|
||||
EXT4_DENTRY *
|
||||
Ext4CreateDentry (
|
||||
IN CONST CHAR16 *Name,
|
||||
IN OUT EXT4_DENTRY *Parent OPTIONAL
|
||||
)
|
||||
{
|
||||
EXT4_DENTRY *Dentry;
|
||||
EFI_STATUS Status;
|
||||
|
||||
Dentry = AllocateZeroPool (sizeof (EXT4_DENTRY));
|
||||
|
||||
if (Dentry == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Dentry->RefCount = 1;
|
||||
|
||||
// This StrCpyS should not fail.
|
||||
Status = StrCpyS (Dentry->Name, ARRAY_SIZE (Dentry->Name), Name);
|
||||
|
||||
ASSERT_EFI_ERROR (Status);
|
||||
|
||||
InitializeListHead (&Dentry->Children);
|
||||
|
||||
if (Parent != NULL) {
|
||||
Ext4AddDentry (Parent, Dentry);
|
||||
}
|
||||
|
||||
DEBUG ((DEBUG_FS, "[ext4] Created dentry %s\n", Name));
|
||||
|
||||
return Dentry;
|
||||
}
|
||||
|
||||
/**
|
||||
Increments the ref count of the dentry.
|
||||
|
||||
@param[in out] Dentry Pointer to a valid EXT4_DENTRY.
|
||||
**/
|
||||
VOID
|
||||
Ext4RefDentry (
|
||||
IN OUT EXT4_DENTRY *Dentry
|
||||
)
|
||||
{
|
||||
UINTN OldRef;
|
||||
|
||||
OldRef = Dentry->RefCount;
|
||||
|
||||
Dentry->RefCount++;
|
||||
|
||||
// I'm not sure if this (Refcount overflow) is a valid concern,
|
||||
// but it's better to be safe than sorry.
|
||||
ASSERT (OldRef < Dentry->RefCount);
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the dentry.
|
||||
|
||||
@param[in out] Dentry Pointer to a valid EXT4_DENTRY.
|
||||
**/
|
||||
STATIC
|
||||
VOID
|
||||
Ext4DeleteDentry (
|
||||
IN OUT EXT4_DENTRY *Dentry
|
||||
)
|
||||
{
|
||||
if (Dentry->Parent) {
|
||||
Ext4RemoveDentry (Dentry->Parent, Dentry);
|
||||
Ext4UnrefDentry (Dentry->Parent);
|
||||
}
|
||||
|
||||
DEBUG ((DEBUG_FS, "[ext4] Deleted dentry %s\n", Dentry->Name));
|
||||
FreePool (Dentry);
|
||||
}
|
||||
|
||||
/**
|
||||
Decrements the ref count of the dentry.
|
||||
If the ref count is 0, it's destroyed.
|
||||
|
||||
@param[in out] Dentry Pointer to a valid EXT4_DENTRY.
|
||||
|
||||
@retval True if it was destroyed, false if it's alive.
|
||||
**/
|
||||
BOOLEAN
|
||||
Ext4UnrefDentry (
|
||||
IN OUT EXT4_DENTRY *Dentry
|
||||
)
|
||||
{
|
||||
Dentry->RefCount--;
|
||||
|
||||
if (Dentry->RefCount == 0) {
|
||||
Ext4DeleteDentry (Dentry);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/** @file
|
||||
Disk utilities
|
||||
|
||||
Copyright (c) 2021 Pedro Falcato All rights reserved.
|
||||
SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
**/
|
||||
#include "Ext4Dxe.h"
|
||||
|
||||
/**
|
||||
Reads from the partition's disk using the DISK_IO protocol.
|
||||
|
||||
@param[in] Partition Pointer to the opened ext4 partition.
|
||||
@param[out] Buffer Pointer to a destination buffer.
|
||||
@param[in] Length Length of the destination buffer.
|
||||
@param[in] Offset Offset, in bytes, of the location to read.
|
||||
|
||||
@return Success status of the disk read.
|
||||
**/
|
||||
EFI_STATUS
|
||||
Ext4ReadDiskIo (
|
||||
IN EXT4_PARTITION *Partition,
|
||||
OUT VOID *Buffer,
|
||||
IN UINTN Length,
|
||||
IN UINT64 Offset
|
||||
)
|
||||
{
|
||||
return EXT4_DISK_IO (Partition)->ReadDisk (
|
||||
EXT4_DISK_IO (Partition),
|
||||
EXT4_MEDIA_ID (Partition),
|
||||
Offset,
|
||||
Length,
|
||||
Buffer
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
Reads blocks from the partition's disk using the DISK_IO protocol.
|
||||
|
||||
@param[in] Partition Pointer to the opened ext4 partition.
|
||||
@param[out] Buffer Pointer to a destination buffer.
|
||||
@param[in] NumberBlocks Length of the read, in filesystem blocks.
|
||||
@param[in] BlockNumber Starting block number.
|
||||
|
||||
@return Success status of the read.
|
||||
**/
|
||||
EFI_STATUS
|
||||
Ext4ReadBlocks (
|
||||
IN EXT4_PARTITION *Partition,
|
||||
OUT VOID *Buffer,
|
||||
IN UINTN NumberBlocks,
|
||||
IN EXT4_BLOCK_NR BlockNumber
|
||||
)
|
||||
{
|
||||
UINT64 Offset;
|
||||
UINTN Length;
|
||||
|
||||
ASSERT (NumberBlocks != 0);
|
||||
ASSERT (BlockNumber != EXT4_BLOCK_FILE_HOLE);
|
||||
|
||||
Offset = MultU64x32 (BlockNumber, Partition->BlockSize);
|
||||
Length = NumberBlocks * Partition->BlockSize;
|
||||
|
||||
// Check for overflow on the block -> byte conversions.
|
||||
// Partition->BlockSize is never 0, so we don't need to check for that.
|
||||
|
||||
if (DivU64x64Remainder (Offset, BlockNumber, NULL) != Partition->BlockSize) {
|
||||
return EFI_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (Length / NumberBlocks != Partition->BlockSize) {
|
||||
return EFI_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
return Ext4ReadDiskIo (Partition, Buffer, Length, Offset);
|
||||
}
|
||||
|
||||
/**
|
||||
Allocates a buffer and reads blocks from the partition's disk using the DISK_IO protocol.
|
||||
This function is deprecated and will be removed in the future.
|
||||
|
||||
@param[in] Partition Pointer to the opened ext4 partition.
|
||||
@param[in] NumberBlocks Length of the read, in filesystem blocks.
|
||||
@param[in] BlockNumber Starting block number.
|
||||
|
||||
@return Buffer allocated by AllocatePool, or NULL if some part of the process
|
||||
failed.
|
||||
**/
|
||||
VOID *
|
||||
Ext4AllocAndReadBlocks (
|
||||
IN EXT4_PARTITION *Partition,
|
||||
IN UINTN NumberBlocks,
|
||||
IN EXT4_BLOCK_NR BlockNumber
|
||||
)
|
||||
{
|
||||
VOID *Buf;
|
||||
UINTN Length;
|
||||
|
||||
// Check that number of blocks isn't empty, because
|
||||
// this is incorrect condition for opened partition,
|
||||
// so we just early-exit
|
||||
if ((NumberBlocks == 0) || (BlockNumber == EXT4_BLOCK_FILE_HOLE)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Length = NumberBlocks * Partition->BlockSize;
|
||||
|
||||
// Check for integer overflow
|
||||
if (Length / NumberBlocks != Partition->BlockSize) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Buf = AllocatePool (Length);
|
||||
if (Buf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (Ext4ReadBlocks (Partition, Buf, NumberBlocks, BlockNumber) != EFI_SUCCESS) {
|
||||
FreePool (Buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return Buf;
|
||||
}
|
|
@ -0,0 +1,501 @@
|
|||
/** @file
|
||||
Raw filesystem data structures
|
||||
|
||||
Copyright (c) 2021 - 2023 Pedro Falcato All rights reserved.
|
||||
SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
|
||||
Layout of an EXT2/3/4 filesystem:
|
||||
(note: this driver has been developed using
|
||||
https://www.kernel.org/doc/html/latest/filesystems/ext4/index.html as
|
||||
documentation).
|
||||
|
||||
An ext2/3/4 filesystem (here on out referred to as simply an ext4 filesystem,
|
||||
due to the similarities) is composed of various concepts:
|
||||
|
||||
1) Superblock
|
||||
The superblock is the structure near (1024 bytes offset from the start)
|
||||
the start of the partition, and describes the filesystem in general.
|
||||
Here, we get to know the size of the filesystem's blocks, which features
|
||||
it supports or not, whether it's been cleanly unmounted, how many blocks
|
||||
we have, etc.
|
||||
|
||||
2) Block groups
|
||||
EXT4 filesystems are divided into block groups, and each block group covers
|
||||
s_blocks_per_group(8 * Block Size) blocks. Each block group has an
|
||||
associated block group descriptor; these are present directly after the
|
||||
superblock. Each block group descriptor contains the location of the
|
||||
inode table, and the inode and block bitmaps (note these bitmaps are only
|
||||
a block long, which gets us the 8 * Block Size formula covered previously).
|
||||
|
||||
3) Blocks
|
||||
The ext4 filesystem is divided in blocks, of size s_log_block_size ^ 1024.
|
||||
Blocks can be allocated using individual block groups's bitmaps. Note
|
||||
that block 0 is invalid and its presence on extents/block tables means
|
||||
it's part of a file hole, and that particular location must be read as
|
||||
a block full of zeros.
|
||||
|
||||
4) Inodes
|
||||
The ext4 filesystem divides files/directories into inodes (originally
|
||||
index nodes). Each file/socket/symlink/directory/etc (here on out referred
|
||||
to as a file, since there is no distinction under the ext4 filesystem) is
|
||||
stored as a /nameless/ inode, that is stored in some block group's inode
|
||||
table. Each inode has s_inode_size size (or GOOD_OLD_INODE_SIZE if it's
|
||||
an old filesystem), and holds various metadata about the file. Since the
|
||||
largest inode structure right now is ~160 bytes, the rest of the inode
|
||||
contains inline extended attributes. Inodes' data is stored using either
|
||||
data blocks (under ext2/3) or extents (under ext4).
|
||||
|
||||
5) Extents
|
||||
Ext4 inodes store data in extents. These let N contiguous logical blocks
|
||||
that are represented by N contiguous physical blocks be represented by a
|
||||
single extent structure, which minimizes filesystem metadata bloat and
|
||||
speeds up block mapping (particularly due to the fact that high-quality
|
||||
ext4 implementations like linux's try /really/ hard to make the file
|
||||
contiguous, so it's common to have files with almost 0 fragmentation).
|
||||
Inodes that use extents store them in a tree, and the top of the tree
|
||||
is stored on i_data. The tree's leaves always start with an
|
||||
EXT4_EXTENT_HEADER and contain EXT4_EXTENT_INDEX on eh_depth != 0 and
|
||||
EXT4_EXTENT on eh_depth = 0; these entries are always sorted by logical
|
||||
block.
|
||||
|
||||
6) Directories
|
||||
Ext4 directories are files that store name -> inode mappings for the
|
||||
logical directory; this is where files get their names, which means ext4
|
||||
inodes do not themselves have names, since they can be linked (present)
|
||||
multiple times with different names. Directories can store entries in two
|
||||
different ways:
|
||||
1) Classical linear directories: They store entries as a mostly-linked
|
||||
mostly-list of EXT4_DIR_ENTRY.
|
||||
2) Hash tree directories: These are used for larger directories, with
|
||||
hundreds of entries, and are designed in a backwards compatible way.
|
||||
These are not yet implemented in the Ext4Dxe driver.
|
||||
|
||||
7) Journal
|
||||
Ext3/4 filesystems have a journal to help protect the filesystem against
|
||||
system crashes. This is not yet implemented in Ext4Dxe but is described
|
||||
in detail in the Linux kernel's documentation.
|
||||
**/
|
||||
|
||||
#ifndef EXT4_DISK_H_
|
||||
#define EXT4_DISK_H_
|
||||
|
||||
#include <Uefi.h>
|
||||
|
||||
#define EXT4_SUPERBLOCK_OFFSET 1024U
|
||||
|
||||
#define EXT4_SIGNATURE 0xEF53U
|
||||
|
||||
#define EXT4_FS_STATE_UNMOUNTED 0x1
|
||||
#define EXT4_FS_STATE_ERRORS_DETECTED 0x2
|
||||
#define EXT4_FS_STATE_RECOVERING_ORPHANS 0x4
|
||||
|
||||
#define EXT4_ERRORS_CONTINUE 1
|
||||
#define EXT4_ERRORS_RO 2
|
||||
#define EXT4_ERRORS_PANIC 3
|
||||
|
||||
#define EXT4_LINUX_ID 0
|
||||
#define EXT4_GNU_HURD_ID 1
|
||||
#define EXT4_MASIX_ID 2
|
||||
#define EXT4_FREEBSD_ID 3
|
||||
#define EXT4_LITES_ID 4
|
||||
|
||||
#define EXT4_GOOD_OLD_REV 0
|
||||
#define EXT4_DYNAMIC_REV 1
|
||||
|
||||
#define EXT4_CHECKSUM_CRC32C 0x1
|
||||
|
||||
#define EXT4_FEATURE_COMPAT_DIR_PREALLOC 0x01
|
||||
#define EXT4_FEATURE_COMPAT_IMAGIC_INODES 0x02
|
||||
#define EXT3_FEATURE_COMPAT_HAS_JOURNAL 0x04
|
||||
#define EXT4_FEATURE_COMPAT_EXT_ATTR 0x08
|
||||
#define EXT4_FEATURE_COMPAT_RESIZE_INO 0x10
|
||||
#define EXT4_FEATURE_COMPAT_DIR_INDEX 0x20
|
||||
|
||||
#define EXT4_FEATURE_INCOMPAT_COMPRESSION 0x00001
|
||||
#define EXT4_FEATURE_INCOMPAT_FILETYPE 0x00002
|
||||
#define EXT4_FEATURE_INCOMPAT_RECOVER 0x00004
|
||||
#define EXT4_FEATURE_INCOMPAT_JOURNAL_DEV 0x00008
|
||||
#define EXT4_FEATURE_INCOMPAT_META_BG 0x00010
|
||||
#define EXT4_FEATURE_INCOMPAT_EXTENTS 0x00040
|
||||
#define EXT4_FEATURE_INCOMPAT_64BIT 0x00080
|
||||
#define EXT4_FEATURE_INCOMPAT_MMP 0x00100
|
||||
#define EXT4_FEATURE_INCOMPAT_FLEX_BG 0x00200
|
||||
#define EXT4_FEATURE_INCOMPAT_EA_INODE 0x00400
|
||||
// It's not clear whether or not this feature (below) is used right now
|
||||
#define EXT4_FEATURE_INCOMPAT_DIRDATA 0x01000
|
||||
#define EXT4_FEATURE_INCOMPAT_CSUM_SEED 0x02000
|
||||
#define EXT4_FEATURE_INCOMPAT_LARGEDIR 0x04000
|
||||
#define EXT4_FEATURE_INCOMPAT_INLINE_DATA 0x08000
|
||||
#define EXT4_FEATURE_INCOMPAT_ENCRYPT 0x10000
|
||||
|
||||
#define EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001
|
||||
#define EXT4_FEATURE_RO_COMPAT_LARGE_FILE 0x0002
|
||||
#define EXT4_FEATURE_RO_COMPAT_BTREE_DIR 0x0004// Unused
|
||||
#define EXT4_FEATURE_RO_COMPAT_HUGE_FILE 0x0008
|
||||
#define EXT4_FEATURE_RO_COMPAT_GDT_CSUM 0x0010
|
||||
#define EXT4_FEATURE_RO_COMPAT_DIR_NLINK 0x0020
|
||||
#define EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE 0x0040
|
||||
#define EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT 0x0080// Not implemented in ext4
|
||||
#define EXT4_FEATURE_RO_COMPAT_QUOTA 0x0100
|
||||
#define EXT4_FEATURE_RO_COMPAT_BIGALLOC 0x0200
|
||||
#define EXT4_FEATURE_RO_COMPAT_METADATA_CSUM 0x0400
|
||||
#define EXT4_FEATURE_RO_COMPAT_REPLICA 0x0800// Not used
|
||||
|
||||
// We explicitly don't recognise this, so we get read only.
|
||||
#define EXT4_FEATURE_RO_COMPAT_READONLY 0x1000
|
||||
#define EXT4_FEATURE_RO_COMPAT_PROJECT 0x2000
|
||||
|
||||
/* Important notes about the features
|
||||
* Absolutely needed features:
|
||||
* 1) Every incompat, because we might want to mount root filesystems
|
||||
* 2) Relevant RO_COMPATs(I'm not sure of what to do wrt quota, project)
|
||||
**/
|
||||
|
||||
#define EXT4_INO_TYPE_FIFO 0x1000
|
||||
#define EXT4_INO_TYPE_CHARDEV 0x2000
|
||||
#define EXT4_INO_TYPE_DIR 0x4000
|
||||
#define EXT4_INO_TYPE_BLOCKDEV 0x6000
|
||||
#define EXT4_INO_TYPE_REGFILE 0x8000
|
||||
#define EXT4_INO_TYPE_SYMLINK 0xA000
|
||||
#define EXT4_INO_TYPE_UNIX_SOCK 0xC000
|
||||
|
||||
/* Inode flags */
|
||||
#define EXT4_SECRM_FL 0x00000001
|
||||
#define EXT4_UNRM_FL 0x00000002
|
||||
#define EXT4_COMPR_FL 0x00000004
|
||||
#define EXT4_SYNC_FL 0x00000008
|
||||
#define EXT4_IMMUTABLE_FL 0x00000010
|
||||
#define EXT4_APPEND_FL 0x00000020
|
||||
#define EXT4_NODUMP_FL 0x00000040
|
||||
#define EXT4_NOATIME_FL 0x00000080
|
||||
#define EXT4_DIRTY_FL 0x00000100
|
||||
#define EXT4_COMPRBLK_FL 0x00000200
|
||||
#define EXT4_NOCOMPR_FL 0x00000400
|
||||
#define EXT4_ENCRYPT_FL 0x00000800
|
||||
#define EXT4_BTREE_FL 0x00001000
|
||||
#define EXT4_INDEX_FL 0x00002000
|
||||
#define EXT4_JOURNAL_DATA_FL 0x00004000
|
||||
#define EXT4_NOTAIL_FL 0x00008000
|
||||
#define EXT4_DIRSYNC_FL 0x00010000
|
||||
#define EXT4_TOPDIR_FL 0x00020000
|
||||
#define EXT4_HUGE_FILE_FL 0x00040000
|
||||
#define EXT4_EXTENTS_FL 0x00080000
|
||||
#define EXT4_VERITY_FL 0x00100000
|
||||
#define EXT4_EA_INODE_FL 0x00200000
|
||||
#define EXT4_RESERVED_FL 0x80000000
|
||||
|
||||
/* File type flags that are stored in the directory entries */
|
||||
#define EXT4_FT_UNKNOWN 0
|
||||
#define EXT4_FT_REG_FILE 1
|
||||
#define EXT4_FT_DIR 2
|
||||
#define EXT4_FT_CHRDEV 3
|
||||
#define EXT4_FT_BLKDEV 4
|
||||
#define EXT4_FT_FIFO 5
|
||||
#define EXT4_FT_SOCK 6
|
||||
#define EXT4_FT_SYMLINK 7
|
||||
|
||||
typedef struct {
|
||||
UINT32 s_inodes_count;
|
||||
UINT32 s_blocks_count;
|
||||
UINT32 s_r_blocks_count;
|
||||
UINT32 s_free_blocks_count;
|
||||
UINT32 s_free_inodes_count;
|
||||
UINT32 s_first_data_block;
|
||||
UINT32 s_log_block_size;
|
||||
UINT32 s_log_frag_size;
|
||||
UINT32 s_blocks_per_group;
|
||||
UINT32 s_frags_per_group;
|
||||
UINT32 s_inodes_per_group;
|
||||
UINT32 s_mtime;
|
||||
UINT32 s_wtime;
|
||||
UINT16 s_mnt_count;
|
||||
UINT16 s_max_mnt_count;
|
||||
UINT16 s_magic;
|
||||
UINT16 s_state;
|
||||
UINT16 s_errors;
|
||||
UINT16 s_minor_rev_level;
|
||||
UINT32 s_lastcheck;
|
||||
UINT32 s_check_interval;
|
||||
UINT32 s_creator_os;
|
||||
UINT32 s_rev_level;
|
||||
UINT16 s_def_resuid;
|
||||
UINT16 s_def_resgid;
|
||||
|
||||
/* Every field after this comment is revision >= 1 */
|
||||
|
||||
UINT32 s_first_ino;
|
||||
UINT16 s_inode_size;
|
||||
UINT16 s_block_group_nr;
|
||||
UINT32 s_feature_compat;
|
||||
UINT32 s_feature_incompat;
|
||||
UINT32 s_feature_ro_compat;
|
||||
UINT8 s_uuid[16];
|
||||
UINT8 s_volume_name[16];
|
||||
UINT8 s_last_mounted[64];
|
||||
UINT32 s_algo_bitmap;
|
||||
UINT8 s_prealloc_blocks;
|
||||
UINT8 s_prealloc_dir_blocks;
|
||||
UINT16 unused;
|
||||
UINT8 s_journal_uuid[16];
|
||||
UINT32 s_journal_inum;
|
||||
UINT32 s_journal_dev;
|
||||
UINT32 s_last_orphan;
|
||||
UINT32 s_hash_seed[4];
|
||||
UINT8 s_def_hash_version;
|
||||
UINT8 s_jnl_backup_type;
|
||||
UINT16 s_desc_size;
|
||||
UINT32 s_default_mount_options;
|
||||
UINT32 s_first_meta_bg;
|
||||
UINT32 s_mkfs_time;
|
||||
UINT32 s_jnl_blocks[17];
|
||||
UINT32 s_blocks_count_hi;
|
||||
UINT32 s_r_blocks_count_hi;
|
||||
UINT32 s_free_blocks_count_hi;
|
||||
UINT16 s_min_extra_isize;
|
||||
UINT16 s_want_extra_isize;
|
||||
UINT32 s_flags;
|
||||
UINT16 s_raid_stride;
|
||||
UINT16 s_mmp_interval;
|
||||
UINT64 s_mmp_block;
|
||||
UINT32 s_raid_stride_width;
|
||||
UINT8 s_log_groups_per_flex;
|
||||
UINT8 s_checksum_type; // Only valid value is 1 - CRC32C
|
||||
UINT16 s_reserved_pad;
|
||||
UINT64 s_kbytes_written;
|
||||
|
||||
// Snapshot stuff isn't used in Linux and isn't implemented here
|
||||
UINT32 s_snapshot_inum;
|
||||
UINT32 s_snapshot_id;
|
||||
UINT64 s_snapshot_r_blocks_count;
|
||||
UINT32 s_snapshot_list;
|
||||
UINT32 s_error_count;
|
||||
UINT32 s_first_error_time;
|
||||
UINT32 s_first_error_ino;
|
||||
UINT64 s_first_error_block;
|
||||
UINT8 s_first_error_func[32];
|
||||
UINT32 s_first_error_line;
|
||||
UINT32 s_last_error_time;
|
||||
UINT32 s_last_error_ino;
|
||||
UINT32 s_last_error_line;
|
||||
UINT64 s_last_error_block;
|
||||
UINT8 s_last_error_func[32];
|
||||
UINT8 s_mount_opts[64];
|
||||
UINT32 s_usr_quota_inum;
|
||||
UINT32 s_grp_quota_inum;
|
||||
UINT32 s_overhead_blocks;
|
||||
UINT32 s_backup_bgs[2]; // sparse_super2
|
||||
UINT8 s_encrypt_algos[4];
|
||||
UINT8 s_encrypt_pw_salt[16];
|
||||
UINT32 s_lpf_ino;
|
||||
UINT32 s_prj_quota_inum;
|
||||
UINT32 s_checksum_seed;
|
||||
UINT32 s_reserved[98];
|
||||
UINT32 s_checksum;
|
||||
} EXT4_SUPERBLOCK;
|
||||
|
||||
STATIC_ASSERT (
|
||||
sizeof (EXT4_SUPERBLOCK) == 1024,
|
||||
"ext4 superblock struct has incorrect size"
|
||||
);
|
||||
|
||||
typedef struct {
|
||||
UINT32 bg_block_bitmap_lo;
|
||||
UINT32 bg_inode_bitmap_lo;
|
||||
UINT32 bg_inode_table_lo;
|
||||
UINT16 bg_free_blocks_count_lo;
|
||||
UINT16 bg_free_inodes_count_lo;
|
||||
UINT16 bg_used_dirs_count_lo;
|
||||
UINT16 bg_flags;
|
||||
UINT32 bg_exclude_bitmap_lo;
|
||||
UINT16 bg_block_bitmap_csum_lo;
|
||||
UINT16 bg_inode_bitmap_csum_lo;
|
||||
UINT16 bg_itable_unused_lo;
|
||||
UINT16 bg_checksum;
|
||||
UINT32 bg_block_bitmap_hi;
|
||||
UINT32 bg_inode_bitmap_hi;
|
||||
UINT32 bg_inode_table_hi;
|
||||
UINT16 bg_free_blocks_count_hi;
|
||||
UINT16 bg_free_inodes_count_hi;
|
||||
UINT16 bg_used_dirs_count_hi;
|
||||
UINT16 bg_itable_unused_hi;
|
||||
UINT32 bg_exclude_bitmap_hi;
|
||||
UINT16 bg_block_bitmap_csum_hi;
|
||||
UINT16 bg_inode_bitmap_csum_hi;
|
||||
UINT32 bg_reserved;
|
||||
} EXT4_BLOCK_GROUP_DESC;
|
||||
|
||||
#define EXT4_OLD_BLOCK_DESC_SIZE 32
|
||||
#define EXT4_64BIT_BLOCK_DESC_SIZE 64
|
||||
|
||||
STATIC_ASSERT (
|
||||
sizeof (EXT4_BLOCK_GROUP_DESC) == EXT4_64BIT_BLOCK_DESC_SIZE,
|
||||
"ext4 block group descriptor struct has incorrect size"
|
||||
);
|
||||
|
||||
#define EXT4_DBLOCKS 12
|
||||
#define EXT4_IND_BLOCK 12
|
||||
#define EXT4_DIND_BLOCK 13
|
||||
#define EXT4_TIND_BLOCK 14
|
||||
#define EXT4_NR_BLOCKS 15
|
||||
#define EXT4_FAST_SYMLINK_MAX_SIZE EXT4_NR_BLOCKS * sizeof(UINT32)
|
||||
|
||||
#define EXT4_GOOD_OLD_INODE_SIZE 128U
|
||||
|
||||
typedef struct _Ext4_I_OSD2_Linux {
|
||||
UINT16 l_i_blocks_high;
|
||||
UINT16 l_i_file_acl_high;
|
||||
UINT16 l_i_uid_high;
|
||||
UINT16 l_i_gid_high;
|
||||
UINT16 l_i_checksum_lo;
|
||||
UINT16 l_i_reserved;
|
||||
} EXT4_OSD2_LINUX;
|
||||
|
||||
typedef struct _Ext4_I_OSD2_Hurd {
|
||||
UINT16 h_i_reserved1;
|
||||
UINT16 h_i_mode_high;
|
||||
UINT16 h_i_uid_high;
|
||||
UINT16 h_i_gid_high;
|
||||
UINT32 h_i_author;
|
||||
} EXT4_OSD2_HURD;
|
||||
|
||||
typedef union {
|
||||
// Note: Toolchain-specific defines (such as "linux") stops us from using
|
||||
// simpler names down here.
|
||||
EXT4_OSD2_LINUX data_linux;
|
||||
EXT4_OSD2_HURD data_hurd;
|
||||
} EXT4_OSD2;
|
||||
|
||||
typedef struct _Ext4Inode {
|
||||
UINT16 i_mode;
|
||||
UINT16 i_uid;
|
||||
UINT32 i_size_lo;
|
||||
UINT32 i_atime;
|
||||
UINT32 i_ctime;
|
||||
UINT32 i_mtime;
|
||||
UINT32 i_dtime;
|
||||
UINT16 i_gid;
|
||||
UINT16 i_links;
|
||||
UINT32 i_blocks;
|
||||
UINT32 i_flags;
|
||||
UINT32 i_os_spec;
|
||||
UINT32 i_data[EXT4_NR_BLOCKS];
|
||||
UINT32 i_generation;
|
||||
UINT32 i_file_acl;
|
||||
UINT32 i_size_hi;
|
||||
UINT32 i_faddr;
|
||||
|
||||
EXT4_OSD2 i_osd2;
|
||||
|
||||
UINT16 i_extra_isize;
|
||||
UINT16 i_checksum_hi;
|
||||
UINT32 i_ctime_extra;
|
||||
UINT32 i_mtime_extra;
|
||||
UINT32 i_atime_extra;
|
||||
UINT32 i_crtime;
|
||||
UINT32 i_crtime_extra;
|
||||
UINT32 i_version_hi;
|
||||
UINT32 i_projid;
|
||||
} EXT4_INODE;
|
||||
|
||||
#define EXT4_NAME_MAX 255
|
||||
|
||||
typedef struct {
|
||||
// offset 0x0: inode number (if 0, unused entry, should skip.)
|
||||
UINT32 inode;
|
||||
// offset 0x4: Directory entry's length.
|
||||
// Note: rec_len >= name_len + EXT4_MIN_DIR_ENTRY_LEN and rec_len % 4 == 0.
|
||||
UINT16 rec_len;
|
||||
// offset 0x6: Directory entry's name's length
|
||||
UINT8 name_len;
|
||||
// offset 0x7: Directory entry's file type indicator
|
||||
UINT8 file_type;
|
||||
// offset 0x8: name[name_len]: Variable length character array; not null-terminated.
|
||||
CHAR8 name[EXT4_NAME_MAX];
|
||||
// Further notes on names:
|
||||
// 1) We use EXT4_NAME_MAX here instead of flexible arrays for ease of use around the driver.
|
||||
//
|
||||
// 2) ext4 directories are defined, as the documentation puts it, as:
|
||||
// "a directory is more or less a flat file that maps an arbitrary byte string
|
||||
// (usually ASCII) to an inode number on the filesystem". So, they are not
|
||||
// necessarily encoded with ASCII, UTF-8, or any of the sort. We must treat it
|
||||
// as a bag of bytes. When interacting with EFI interfaces themselves (which expect UCS-2)
|
||||
// we skip any directory entry that is not valid UTF-8.
|
||||
} EXT4_DIR_ENTRY;
|
||||
|
||||
#define EXT4_MIN_DIR_ENTRY_LEN 8
|
||||
|
||||
// This on-disk structure is present at the bottom of the extent tree
|
||||
typedef struct {
|
||||
// First logical block
|
||||
UINT32 ee_block;
|
||||
// Length of the extent, in blocks
|
||||
UINT16 ee_len;
|
||||
// The physical (filesystem-relative) block is split between the high 16 bits
|
||||
// and the low 32 bits - this forms a 48-bit block number
|
||||
UINT16 ee_start_hi;
|
||||
UINT32 ee_start_lo;
|
||||
} EXT4_EXTENT;
|
||||
|
||||
// This on-disk structure is present at all levels except the bottom
|
||||
typedef struct {
|
||||
// This index covers logical blocks from 'ei_block'
|
||||
UINT32 ei_block;
|
||||
// Block of the next level of the extent tree, similarly split in a high and
|
||||
// low portion.
|
||||
UINT32 ei_leaf_lo;
|
||||
UINT16 ei_leaf_hi;
|
||||
|
||||
UINT16 ei_unused;
|
||||
} EXT4_EXTENT_INDEX;
|
||||
|
||||
typedef struct {
|
||||
// Needs to be EXT4_EXTENT_HEADER_MAGIC
|
||||
UINT16 eh_magic;
|
||||
// Number of entries
|
||||
UINT16 eh_entries;
|
||||
// Maximum number of entries that could follow this header
|
||||
UINT16 eh_max;
|
||||
// Depth of this node in the tree - the tree can be at most 5 levels deep
|
||||
UINT16 eh_depth;
|
||||
// Unused by standard ext4
|
||||
UINT32 eh_generation;
|
||||
} EXT4_EXTENT_HEADER;
|
||||
|
||||
#define EXT4_EXTENT_HEADER_MAGIC 0xF30A
|
||||
|
||||
// Specified by ext4 docs and backed by a bunch of math
|
||||
#define EXT4_EXTENT_TREE_MAX_DEPTH 5
|
||||
|
||||
typedef struct {
|
||||
// CRC32C of UUID + inode number + igeneration + extent block
|
||||
UINT32 eb_checksum;
|
||||
} EXT4_EXTENT_TAIL;
|
||||
|
||||
/**
|
||||
* EXT4 has this feature called uninitialized extents:
|
||||
* An extent has a maximum of 32768 blocks (2^15 or 1 << 15).
|
||||
* When we find an extent with > 32768 blocks, this extent is called
|
||||
* uninitialized. Long story short, it's an extent that behaves as a file hole
|
||||
* but has blocks already allocated.
|
||||
*/
|
||||
#define EXT4_EXTENT_MAX_INITIALIZED (1 << 15)
|
||||
|
||||
typedef UINT64 EXT4_BLOCK_NR;
|
||||
typedef UINT32 EXT2_BLOCK_NR;
|
||||
typedef UINT32 EXT4_INO_NR;
|
||||
|
||||
/* Special inode numbers */
|
||||
#define EXT4_ROOT_INODE_NR 2
|
||||
#define EXT4_USR_QUOTA_INODE_NR 3
|
||||
#define EXT4_GRP_QUOTA_INODE_NR 4
|
||||
#define EXT4_BOOT_LOADER_INODE_NR 5
|
||||
#define EXT4_UNDEL_DIR_INODE_NR 6
|
||||
#define EXT4_RESIZE_INODE_NR 7
|
||||
#define EXT4_JOURNAL_INODE_NR 8
|
||||
|
||||
/* First non-reserved inode for old ext4 filesystems */
|
||||
#define EXT4_GOOD_OLD_FIRST_INODE_NR 11
|
||||
|
||||
#define EXT4_BLOCK_FILE_HOLE 0
|
||||
|
||||
#endif
|
|
@ -0,0 +1,847 @@
|
|||
/** @file
|
||||
Driver entry point
|
||||
|
||||
Copyright (c) 2021 - 2023 Pedro Falcato All rights reserved.
|
||||
SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
**/
|
||||
|
||||
#include "Ext4Dxe.h"
|
||||
|
||||
GLOBAL_REMOVE_IF_UNREFERENCED EFI_UNICODE_STRING_TABLE mExt4DriverNameTable[] = {
|
||||
{
|
||||
"eng;en",
|
||||
L"Ext4 File System Driver"
|
||||
},
|
||||
{
|
||||
NULL,
|
||||
NULL
|
||||
}
|
||||
};
|
||||
|
||||
GLOBAL_REMOVE_IF_UNREFERENCED EFI_UNICODE_STRING_TABLE mExt4ControllerNameTable[] = {
|
||||
{
|
||||
"eng;en",
|
||||
L"Ext4 File System"
|
||||
},
|
||||
{
|
||||
NULL,
|
||||
NULL
|
||||
}
|
||||
};
|
||||
|
||||
// Needed by gExt4ComponentName*
|
||||
|
||||
/**
|
||||
Retrieves a Unicode string that is the user-readable name of the EFI Driver.
|
||||
|
||||
@param[in] This A pointer to the EFI_COMPONENT_NAME_PROTOCOL instance.
|
||||
@param[in] Language A pointer to a three-character ISO 639-2 language identifier.
|
||||
This is the language of the driver name that that the caller
|
||||
is requesting, and it must match one of the languages specified
|
||||
in SupportedLanguages. The number of languages supported by a
|
||||
driver is up to the driver writer.
|
||||
@param[out] DriverName A pointer to the Unicode string to return. This Unicode string
|
||||
is the name of the driver specified by This in the language
|
||||
specified by Language.
|
||||
|
||||
@retval EFI_SUCCESS The Unicode string for the Driver specified by This
|
||||
and the language specified by Language was returned
|
||||
in DriverName.
|
||||
@retval EFI_INVALID_PARAMETER Language is NULL.
|
||||
@retval EFI_INVALID_PARAMETER DriverName is NULL.
|
||||
@retval EFI_UNSUPPORTED The driver specified by This does not support the
|
||||
language specified by Language.
|
||||
|
||||
**/
|
||||
EFI_STATUS
|
||||
EFIAPI
|
||||
Ext4ComponentNameGetDriverName (
|
||||
IN EFI_COMPONENT_NAME_PROTOCOL *This,
|
||||
IN CHAR8 *Language,
|
||||
OUT CHAR16 **DriverName
|
||||
);
|
||||
|
||||
/**
|
||||
Retrieves a Unicode string that is the user readable name of the controller
|
||||
that is being managed by an EFI Driver.
|
||||
|
||||
@param[in] This A pointer to the EFI_COMPONENT_NAME_PROTOCOL instance.
|
||||
@param[in] ControllerHandle The handle of a controller that the driver specified by
|
||||
This is managing. This handle specifies the controller
|
||||
whose name is to be returned.
|
||||
@param[in] ChildHandle The handle of the child controller to retrieve the name
|
||||
of. This is an optional parameter that may be NULL. It
|
||||
will be NULL for device drivers. It will also be NULL
|
||||
for a bus drivers that wish to retrieve the name of the
|
||||
bus controller. It will not be NULL for a bus driver
|
||||
that wishes to retrieve the name of a child controller.
|
||||
@param[in] Language A pointer to a three character ISO 639-2 language
|
||||
identifier. This is the language of the controller name
|
||||
that the caller is requesting, and it must match one
|
||||
of the languages specified in SupportedLanguages. The
|
||||
number of languages supported by a driver is up to the
|
||||
driver writer.
|
||||
@param[out] ControllerName A pointer to the Unicode string to return. This Unicode
|
||||
string is the name of the controller specified by
|
||||
ControllerHandle and ChildHandle in the language specified
|
||||
by Language, from the point of view of the driver specified
|
||||
by This.
|
||||
|
||||
@retval EFI_SUCCESS The Unicode string for the user-readable name in the
|
||||
language specified by Language for the driver
|
||||
specified by This was returned in DriverName.
|
||||
@retval EFI_INVALID_PARAMETER ControllerHandle is NULL.
|
||||
@retval EFI_INVALID_PARAMETER ChildHandle is not NULL and it is not a valid EFI_HANDLE.
|
||||
@retval EFI_INVALID_PARAMETER Language is NULL.
|
||||
@retval EFI_INVALID_PARAMETER ControllerName is NULL.
|
||||
@retval EFI_UNSUPPORTED The driver specified by This is not currently managing
|
||||
the controller specified by ControllerHandle and
|
||||
ChildHandle.
|
||||
@retval EFI_UNSUPPORTED The driver specified by This does not support the
|
||||
language specified by Language.
|
||||
|
||||
**/
|
||||
EFI_STATUS
|
||||
EFIAPI
|
||||
Ext4ComponentNameGetControllerName (
|
||||
IN EFI_COMPONENT_NAME_PROTOCOL *This,
|
||||
IN EFI_HANDLE ControllerHandle,
|
||||
IN EFI_HANDLE ChildHandle OPTIONAL,
|
||||
IN CHAR8 *Language,
|
||||
OUT CHAR16 **ControllerName
|
||||
);
|
||||
|
||||
extern EFI_COMPONENT_NAME_PROTOCOL gExt4ComponentName;
|
||||
|
||||
GLOBAL_REMOVE_IF_UNREFERENCED EFI_COMPONENT_NAME_PROTOCOL gExt4ComponentName = {
|
||||
Ext4ComponentNameGetDriverName,
|
||||
Ext4ComponentNameGetControllerName,
|
||||
"eng"
|
||||
};
|
||||
|
||||
//
|
||||
// EFI Component Name 2 Protocol
|
||||
//
|
||||
GLOBAL_REMOVE_IF_UNREFERENCED EFI_COMPONENT_NAME2_PROTOCOL gExt4ComponentName2 = {
|
||||
(EFI_COMPONENT_NAME2_GET_DRIVER_NAME)Ext4ComponentNameGetDriverName,
|
||||
(EFI_COMPONENT_NAME2_GET_CONTROLLER_NAME)Ext4ComponentNameGetControllerName,
|
||||
"en"
|
||||
};
|
||||
|
||||
// Needed by gExt4BindingProtocol
|
||||
|
||||
/**
|
||||
Tests to see if this driver supports a given controller. If a child device is provided,
|
||||
it further tests to see if this driver supports creating a handle for the specified child device.
|
||||
|
||||
This function checks to see if the driver specified by This supports the device specified by
|
||||
ControllerHandle. Drivers will typically use the device path attached to
|
||||
ControllerHandle and/or the services from the bus I/O abstraction attached to
|
||||
ControllerHandle to determine if the driver supports ControllerHandle. This function
|
||||
may be called many times during platform initialization. In order to reduce boot times, the tests
|
||||
performed by this function must be very small, and take as little time as possible to execute. This
|
||||
function must not change the state of any hardware devices, and this function must be aware that the
|
||||
device specified by ControllerHandle may already be managed by the same driver or a
|
||||
different driver. This function must match its calls to AllocatePages() with FreePages(),
|
||||
AllocatePool() with FreePool(), and OpenProtocol() with CloseProtocol().
|
||||
Because ControllerHandle may have been previously started by the same driver, if a protocol is
|
||||
already in the opened state, then it must not be closed with CloseProtocol(). This is required
|
||||
to guarantee the state of ControllerHandle is not modified by this function.
|
||||
|
||||
@param[in] This A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
|
||||
@param[in] ControllerHandle The handle of the controller to test. This handle
|
||||
must support a protocol interface that supplies
|
||||
an I/O abstraction to the driver.
|
||||
@param[in] RemainingDevicePath A pointer to the remaining portion of a device path. This
|
||||
parameter is ignored by device drivers, and is optional for bus
|
||||
drivers. For bus drivers, if this parameter is not NULL, then
|
||||
the bus driver must determine if the bus controller specified
|
||||
by ControllerHandle and the child controller specified
|
||||
by RemainingDevicePath are both supported by this
|
||||
bus driver.
|
||||
|
||||
@retval EFI_SUCCESS The device specified by ControllerHandle and
|
||||
RemainingDevicePath is supported by the driver specified by This.
|
||||
@retval EFI_ALREADY_STARTED The device specified by ControllerHandle and
|
||||
RemainingDevicePath is already being managed by the driver
|
||||
specified by This.
|
||||
@retval EFI_ACCESS_DENIED The device specified by ControllerHandle and
|
||||
RemainingDevicePath is already being managed by a different
|
||||
driver or an application that requires exclusive access.
|
||||
Currently not implemented.
|
||||
@retval EFI_UNSUPPORTED The device specified by ControllerHandle and
|
||||
RemainingDevicePath is not supported by the driver specified by This.
|
||||
**/
|
||||
EFI_STATUS
|
||||
EFIAPI
|
||||
Ext4IsBindingSupported (
|
||||
IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,
|
||||
IN EFI_HANDLE ControllerHandle,
|
||||
IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL
|
||||
);
|
||||
|
||||
/**
|
||||
Starts a device controller or a bus controller.
|
||||
|
||||
The Start() function is designed to be invoked from the EFI boot service ConnectController().
|
||||
As a result, much of the error checking on the parameters to Start() has been moved into this
|
||||
common boot service. It is legal to call Start() from other locations,
|
||||
but the following calling restrictions must be followed, or the system behavior will not be deterministic.
|
||||
1. ControllerHandle must be a valid EFI_HANDLE.
|
||||
2. If RemainingDevicePath is not NULL, then it must be a pointer to a naturally aligned
|
||||
EFI_DEVICE_PATH_PROTOCOL.
|
||||
3. Prior to calling Start(), the Supported() function for the driver specified by This must
|
||||
have been called with the same calling parameters, and Supported() must have returned EFI_SUCCESS.
|
||||
|
||||
@param[in] This A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
|
||||
@param[in] ControllerHandle The handle of the controller to start. This handle
|
||||
must support a protocol interface that supplies
|
||||
an I/O abstraction to the driver.
|
||||
@param[in] RemainingDevicePath A pointer to the remaining portion of a device path. This
|
||||
parameter is ignored by device drivers, and is optional for bus
|
||||
drivers. For a bus driver, if this parameter is NULL, then handles
|
||||
for all the children of Controller are created by this driver.
|
||||
If this parameter is not NULL and the first Device Path Node is
|
||||
not the End of Device Path Node, then only the handle for the
|
||||
child device specified by the first Device Path Node of
|
||||
RemainingDevicePath is created by this driver.
|
||||
If the first Device Path Node of RemainingDevicePath is
|
||||
the End of Device Path Node, no child handle is created by this
|
||||
driver.
|
||||
|
||||
@retval EFI_SUCCESS The device was started.
|
||||
@retval EFI_DEVICE_ERROR The device could not be started due to a device error.Currently not implemented.
|
||||
@retval EFI_OUT_OF_RESOURCES The request could not be completed due to a lack of resources.
|
||||
@retval Others The driver failded to start the device.
|
||||
|
||||
**/
|
||||
EFI_STATUS
|
||||
EFIAPI
|
||||
Ext4Bind (
|
||||
IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,
|
||||
IN EFI_HANDLE ControllerHandle,
|
||||
IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL
|
||||
);
|
||||
|
||||
/**
|
||||
Stops a device controller or a bus controller.
|
||||
|
||||
The Stop() function is designed to be invoked from the EFI boot service DisconnectController().
|
||||
As a result, much of the error checking on the parameters to Stop() has been moved
|
||||
into this common boot service. It is legal to call Stop() from other locations,
|
||||
but the following calling restrictions must be followed, or the system behavior will not be deterministic.
|
||||
1. ControllerHandle must be a valid EFI_HANDLE that was used on a previous call to this
|
||||
same driver's Start() function.
|
||||
2. The first NumberOfChildren handles of ChildHandleBuffer must all be a valid
|
||||
EFI_HANDLE. In addition, all of these handles must have been created in this driver's
|
||||
Start() function, and the Start() function must have called OpenProtocol() on
|
||||
ControllerHandle with an Attribute of EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER.
|
||||
|
||||
@param[in] This A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
|
||||
@param[in] ControllerHandle A handle to the device being stopped. The handle must
|
||||
support a bus specific I/O protocol for the driver
|
||||
to use to stop the device.
|
||||
@param[in] NumberOfChildren The number of child device handles in ChildHandleBuffer.
|
||||
@param[in] ChildHandleBuffer An array of child handles to be freed. May be NULL
|
||||
if NumberOfChildren is 0.
|
||||
|
||||
@retval EFI_SUCCESS The device was stopped.
|
||||
@retval EFI_DEVICE_ERROR The device could not be stopped due to a device error.
|
||||
|
||||
**/
|
||||
EFI_STATUS
|
||||
EFIAPI
|
||||
Ext4Stop (
|
||||
IN EFI_DRIVER_BINDING_PROTOCOL *This,
|
||||
IN EFI_HANDLE ControllerHandle,
|
||||
IN UINTN NumberOfChildren,
|
||||
IN EFI_HANDLE *ChildHandleBuffer OPTIONAL
|
||||
);
|
||||
|
||||
EFI_DRIVER_BINDING_PROTOCOL gExt4BindingProtocol =
|
||||
{
|
||||
.Supported = Ext4IsBindingSupported,
|
||||
.Start = Ext4Bind,
|
||||
.Stop = Ext4Stop,
|
||||
.Version = EXT4_DRIVER_VERSION,
|
||||
.ImageHandle = NULL,
|
||||
.DriverBindingHandle = NULL
|
||||
};
|
||||
|
||||
/**
|
||||
Retrieves a Unicode string that is the user readable name of the controller
|
||||
that is being managed by an EFI Driver.
|
||||
|
||||
@param[in] This A pointer to the EFI_COMPONENT_NAME_PROTOCOL instance.
|
||||
@param[in] ControllerHandle The handle of a controller that the driver specified by
|
||||
This is managing. This handle specifies the controller
|
||||
whose name is to be returned.
|
||||
@param[in] ChildHandle The handle of the child controller to retrieve the name
|
||||
of. This is an optional parameter that may be NULL. It
|
||||
will be NULL for device drivers. It will also be NULL
|
||||
for a bus drivers that wish to retrieve the name of the
|
||||
bus controller. It will not be NULL for a bus driver
|
||||
that wishes to retrieve the name of a child controller.
|
||||
@param[in] Language A pointer to a three character ISO 639-2 language
|
||||
identifier. This is the language of the controller name
|
||||
that the caller is requesting, and it must match one
|
||||
of the languages specified in SupportedLanguages. The
|
||||
number of languages supported by a driver is up to the
|
||||
driver writer.
|
||||
@param[out] ControllerName A pointer to the Unicode string to return. This Unicode
|
||||
string is the name of the controller specified by
|
||||
ControllerHandle and ChildHandle in the language specified
|
||||
by Language, from the point of view of the driver specified
|
||||
by This.
|
||||
|
||||
@retval EFI_SUCCESS The Unicode string for the user-readable name in the
|
||||
language specified by Language for the driver
|
||||
specified by This was returned in DriverName.
|
||||
@retval EFI_INVALID_PARAMETER ControllerHandle is NULL.
|
||||
@retval EFI_INVALID_PARAMETER ChildHandle is not NULL and it is not a valid EFI_HANDLE.
|
||||
@retval EFI_INVALID_PARAMETER Language is NULL.
|
||||
@retval EFI_INVALID_PARAMETER ControllerName is NULL.
|
||||
@retval EFI_UNSUPPORTED The driver specified by This is not currently managing
|
||||
the controller specified by ControllerHandle and
|
||||
ChildHandle.
|
||||
@retval EFI_UNSUPPORTED The driver specified by This does not support the
|
||||
language specified by Language.
|
||||
|
||||
**/
|
||||
EFI_STATUS
|
||||
EFIAPI
|
||||
Ext4ComponentNameGetControllerName (
|
||||
IN EFI_COMPONENT_NAME_PROTOCOL *This,
|
||||
IN EFI_HANDLE ControllerHandle,
|
||||
IN EFI_HANDLE ChildHandle OPTIONAL,
|
||||
IN CHAR8 *Language,
|
||||
OUT CHAR16 **ControllerName
|
||||
)
|
||||
{
|
||||
EFI_STATUS Status;
|
||||
|
||||
if (ChildHandle != NULL) {
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
// Test if the driver manages ControllHandle
|
||||
Status = EfiTestManagedDevice (
|
||||
ControllerHandle,
|
||||
gExt4BindingProtocol.DriverBindingHandle,
|
||||
&gEfiDiskIoProtocolGuid
|
||||
);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
return Status;
|
||||
}
|
||||
|
||||
return LookupUnicodeString2 (
|
||||
Language,
|
||||
This->SupportedLanguages,
|
||||
mExt4ControllerNameTable,
|
||||
ControllerName,
|
||||
(BOOLEAN)(This == &gExt4ComponentName)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
Retrieves a Unicode string that is the user-readable name of the EFI Driver.
|
||||
|
||||
@param[in] This A pointer to the EFI_COMPONENT_NAME_PROTOCOL instance.
|
||||
@param[in] Language A pointer to a three-character ISO 639-2 language identifier.
|
||||
This is the language of the driver name that that the caller
|
||||
is requesting, and it must match one of the languages specified
|
||||
in SupportedLanguages. The number of languages supported by a
|
||||
driver is up to the driver writer.
|
||||
@param[out] DriverName A pointer to the Unicode string to return. This Unicode string
|
||||
is the name of the driver specified by This in the language
|
||||
specified by Language.
|
||||
|
||||
@retval EFI_SUCCESS The Unicode string for the Driver specified by This
|
||||
and the language specified by Language was returned
|
||||
in DriverName.
|
||||
@retval EFI_INVALID_PARAMETER Language is NULL.
|
||||
@retval EFI_INVALID_PARAMETER DriverName is NULL.
|
||||
@retval EFI_UNSUPPORTED The driver specified by This does not support the
|
||||
language specified by Language.
|
||||
|
||||
**/
|
||||
EFI_STATUS
|
||||
EFIAPI
|
||||
Ext4ComponentNameGetDriverName (
|
||||
IN EFI_COMPONENT_NAME_PROTOCOL *This,
|
||||
IN CHAR8 *Language,
|
||||
OUT CHAR16 **DriverName
|
||||
)
|
||||
{
|
||||
return LookupUnicodeString2 (
|
||||
Language,
|
||||
This->SupportedLanguages,
|
||||
mExt4DriverNameTable,
|
||||
DriverName,
|
||||
(BOOLEAN)(This == &gExt4ComponentName)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
Stops a device controller or a bus controller.
|
||||
|
||||
The Stop() function is designed to be invoked from the EFI boot service DisconnectController().
|
||||
As a result, much of the error checking on the parameters to Stop() has been moved
|
||||
into this common boot service. It is legal to call Stop() from other locations,
|
||||
but the following calling restrictions must be followed, or the system behavior will not be deterministic.
|
||||
1. ControllerHandle must be a valid EFI_HANDLE that was used on a previous call to this
|
||||
same driver's Start() function.
|
||||
2. The first NumberOfChildren handles of ChildHandleBuffer must all be a valid
|
||||
EFI_HANDLE. In addition, all of these handles must have been created in this driver's
|
||||
Start() function, and the Start() function must have called OpenProtocol() on
|
||||
ControllerHandle with an Attribute of EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER.
|
||||
|
||||
@param[in] This A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
|
||||
@param[in] ControllerHandle A handle to the device being stopped. The handle must
|
||||
support a bus specific I/O protocol for the driver
|
||||
to use to stop the device.
|
||||
@param[in] NumberOfChildren The number of child device handles in ChildHandleBuffer.
|
||||
@param[in] ChildHandleBuffer An array of child handles to be freed. May be NULL
|
||||
if NumberOfChildren is 0.
|
||||
|
||||
@retval EFI_SUCCESS The device was stopped.
|
||||
@retval EFI_DEVICE_ERROR The device could not be stopped due to a device error.
|
||||
|
||||
**/
|
||||
EFI_STATUS
|
||||
EFIAPI
|
||||
Ext4Stop (
|
||||
IN EFI_DRIVER_BINDING_PROTOCOL *This,
|
||||
IN EFI_HANDLE ControllerHandle,
|
||||
IN UINTN NumberOfChildren,
|
||||
IN EFI_HANDLE *ChildHandleBuffer OPTIONAL
|
||||
)
|
||||
{
|
||||
EFI_STATUS Status;
|
||||
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Sfs;
|
||||
EXT4_PARTITION *Partition;
|
||||
BOOLEAN HasDiskIo2;
|
||||
|
||||
Status = gBS->OpenProtocol (
|
||||
ControllerHandle,
|
||||
&gEfiSimpleFileSystemProtocolGuid,
|
||||
(VOID **)&Sfs,
|
||||
This->DriverBindingHandle,
|
||||
ControllerHandle,
|
||||
EFI_OPEN_PROTOCOL_GET_PROTOCOL
|
||||
);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
return Status;
|
||||
}
|
||||
|
||||
Partition = (EXT4_PARTITION *)Sfs;
|
||||
|
||||
HasDiskIo2 = EXT4_DISK_IO2 (Partition) != NULL;
|
||||
|
||||
Status = Ext4UnmountAndFreePartition (Partition);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
return Status;
|
||||
}
|
||||
|
||||
Status = gBS->UninstallMultipleProtocolInterfaces (
|
||||
ControllerHandle,
|
||||
&gEfiSimpleFileSystemProtocolGuid,
|
||||
&Partition->Interface,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
return Status;
|
||||
}
|
||||
|
||||
// Close all open protocols (DiskIo, DiskIo2, BlockIo)
|
||||
|
||||
Status = gBS->CloseProtocol (
|
||||
ControllerHandle,
|
||||
&gEfiDiskIoProtocolGuid,
|
||||
This->DriverBindingHandle,
|
||||
ControllerHandle
|
||||
);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
return Status;
|
||||
}
|
||||
|
||||
Status = gBS->CloseProtocol (
|
||||
ControllerHandle,
|
||||
&gEfiBlockIoProtocolGuid,
|
||||
This->DriverBindingHandle,
|
||||
ControllerHandle
|
||||
);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
return Status;
|
||||
}
|
||||
|
||||
if (HasDiskIo2) {
|
||||
Status = gBS->CloseProtocol (
|
||||
ControllerHandle,
|
||||
&gEfiDiskIo2ProtocolGuid,
|
||||
This->DriverBindingHandle,
|
||||
ControllerHandle
|
||||
);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
return Status;
|
||||
}
|
||||
}
|
||||
|
||||
return Status;
|
||||
}
|
||||
|
||||
/**
|
||||
Ext4Dxe Driver's entry point.
|
||||
|
||||
Called at load time.
|
||||
|
||||
@param[in] ImageHandle Handle to the image.
|
||||
@param[in] SystemTable Pointer to the EFI_SYSTEM_TABLE.
|
||||
@return Result of the load.
|
||||
**/
|
||||
EFI_STATUS
|
||||
EFIAPI
|
||||
Ext4EntryPoint (
|
||||
IN EFI_HANDLE ImageHandle,
|
||||
IN EFI_SYSTEM_TABLE *SystemTable
|
||||
)
|
||||
{
|
||||
return EfiLibInstallAllDriverProtocols2 (
|
||||
ImageHandle,
|
||||
SystemTable,
|
||||
&gExt4BindingProtocol,
|
||||
ImageHandle,
|
||||
&gExt4ComponentName,
|
||||
&gExt4ComponentName2,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
Ext4Dxe Driver's unload callback.
|
||||
|
||||
Called at unload time.
|
||||
|
||||
@param[in] ImageHandle Handle to the image.
|
||||
@return Result of the unload.
|
||||
**/
|
||||
EFI_STATUS
|
||||
EFIAPI
|
||||
Ext4Unload (
|
||||
IN EFI_HANDLE ImageHandle
|
||||
)
|
||||
{
|
||||
EFI_STATUS Status;
|
||||
EFI_HANDLE *DeviceHandleBuffer;
|
||||
UINTN DeviceHandleCount;
|
||||
UINTN Index;
|
||||
EFI_HANDLE Handle;
|
||||
|
||||
Status = gBS->LocateHandleBuffer (
|
||||
AllHandles,
|
||||
NULL,
|
||||
NULL,
|
||||
&DeviceHandleCount,
|
||||
&DeviceHandleBuffer
|
||||
);
|
||||
if (EFI_ERROR (Status)) {
|
||||
return Status;
|
||||
}
|
||||
|
||||
for (Index = 0; Index < DeviceHandleCount; Index++) {
|
||||
Handle = DeviceHandleBuffer[Index];
|
||||
|
||||
Status = EfiTestManagedDevice (Handle, ImageHandle, &gEfiDiskIoProtocolGuid);
|
||||
|
||||
if (Status == EFI_SUCCESS) {
|
||||
Status = gBS->DisconnectController (Handle, ImageHandle, NULL);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FreePool (DeviceHandleBuffer);
|
||||
|
||||
Status = EfiLibUninstallAllDriverProtocols2 (
|
||||
&gExt4BindingProtocol,
|
||||
&gExt4ComponentName,
|
||||
&gExt4ComponentName2,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
|
||||
return Status;
|
||||
}
|
||||
|
||||
/**
|
||||
Tests to see if this driver supports a given controller. If a child device is provided,
|
||||
it further tests to see if this driver supports creating a handle for the specified child device.
|
||||
|
||||
This function checks to see if the driver specified by This supports the device specified by
|
||||
ControllerHandle. Drivers will typically use the device path attached to
|
||||
ControllerHandle and/or the services from the bus I/O abstraction attached to
|
||||
ControllerHandle to determine if the driver supports ControllerHandle. This function
|
||||
may be called many times during platform initialization. In order to reduce boot times, the tests
|
||||
performed by this function must be very small, and take as little time as possible to execute. This
|
||||
function must not change the state of any hardware devices, and this function must be aware that the
|
||||
device specified by ControllerHandle may already be managed by the same driver or a
|
||||
different driver. This function must match its calls to AllocatePages() with FreePages(),
|
||||
AllocatePool() with FreePool(), and OpenProtocol() with CloseProtocol().
|
||||
Because ControllerHandle may have been previously started by the same driver, if a protocol is
|
||||
already in the opened state, then it must not be closed with CloseProtocol(). This is required
|
||||
to guarantee the state of ControllerHandle is not modified by this function.
|
||||
|
||||
@param[in] This A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
|
||||
@param[in] ControllerHandle The handle of the controller to test. This handle
|
||||
must support a protocol interface that supplies
|
||||
an I/O abstraction to the driver.
|
||||
@param[in] RemainingDevicePath A pointer to the remaining portion of a device path. This
|
||||
parameter is ignored by device drivers, and is optional for bus
|
||||
drivers. For bus drivers, if this parameter is not NULL, then
|
||||
the bus driver must determine if the bus controller specified
|
||||
by ControllerHandle and the child controller specified
|
||||
by RemainingDevicePath are both supported by this
|
||||
bus driver.
|
||||
|
||||
@retval EFI_SUCCESS The device specified by ControllerHandle and
|
||||
RemainingDevicePath is supported by the driver specified by This.
|
||||
@retval EFI_ALREADY_STARTED The device specified by ControllerHandle and
|
||||
RemainingDevicePath is already being managed by the driver
|
||||
specified by This.
|
||||
@retval EFI_ACCESS_DENIED The device specified by ControllerHandle and
|
||||
RemainingDevicePath is already being managed by a different
|
||||
driver or an application that requires exclusive access.
|
||||
@retval EFI_UNSUPPORTED The device specified by ControllerHandle and
|
||||
RemainingDevicePath is not supported by the driver specified by This.
|
||||
**/
|
||||
EFI_STATUS
|
||||
EFIAPI
|
||||
Ext4IsBindingSupported (
|
||||
IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,
|
||||
IN EFI_HANDLE ControllerHandle,
|
||||
IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL
|
||||
)
|
||||
{
|
||||
EFI_STATUS Status;
|
||||
EFI_DISK_IO_PROTOCOL *DiskIo;
|
||||
EFI_BLOCK_IO_PROTOCOL *BlockIo;
|
||||
|
||||
DiskIo = NULL;
|
||||
BlockIo = NULL;
|
||||
|
||||
//
|
||||
// Open the IO Abstraction(s) needed to perform the supported test
|
||||
//
|
||||
Status = gBS->OpenProtocol (
|
||||
ControllerHandle,
|
||||
&gEfiDiskIoProtocolGuid,
|
||||
(VOID **)&DiskIo,
|
||||
BindingProtocol->DriverBindingHandle,
|
||||
ControllerHandle,
|
||||
EFI_OPEN_PROTOCOL_BY_DRIVER
|
||||
);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
return Status;
|
||||
}
|
||||
|
||||
//
|
||||
// Open the IO Abstraction(s) needed to perform the supported test
|
||||
//
|
||||
Status = gBS->OpenProtocol (
|
||||
ControllerHandle,
|
||||
&gEfiBlockIoProtocolGuid,
|
||||
(VOID **)&BlockIo,
|
||||
BindingProtocol->DriverBindingHandle,
|
||||
ControllerHandle,
|
||||
EFI_OPEN_PROTOCOL_GET_PROTOCOL
|
||||
);
|
||||
|
||||
if (!EFI_ERROR (Status)) {
|
||||
if (!Ext4SuperblockCheckMagic (DiskIo, BlockIo)) {
|
||||
Status = EFI_UNSUPPORTED;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Close the I/O Abstraction(s) used to perform the supported test
|
||||
//
|
||||
if (DiskIo != NULL) {
|
||||
gBS->CloseProtocol (
|
||||
ControllerHandle,
|
||||
&gEfiDiskIoProtocolGuid,
|
||||
BindingProtocol->DriverBindingHandle,
|
||||
ControllerHandle
|
||||
);
|
||||
}
|
||||
|
||||
if (BlockIo != NULL) {
|
||||
gBS->CloseProtocol (
|
||||
ControllerHandle,
|
||||
&gEfiBlockIoProtocolGuid,
|
||||
BindingProtocol->DriverBindingHandle,
|
||||
ControllerHandle
|
||||
);
|
||||
}
|
||||
|
||||
return Status;
|
||||
}
|
||||
|
||||
/**
|
||||
Starts a device controller or a bus controller.
|
||||
|
||||
The Start() function is designed to be invoked from the EFI boot service ConnectController().
|
||||
As a result, much of the error checking on the parameters to Start() has been moved into this
|
||||
common boot service. It is legal to call Start() from other locations,
|
||||
but the following calling restrictions must be followed, or the system behavior will not be deterministic.
|
||||
1. ControllerHandle must be a valid EFI_HANDLE.
|
||||
2. If RemainingDevicePath is not NULL, then it must be a pointer to a naturally aligned
|
||||
EFI_DEVICE_PATH_PROTOCOL.
|
||||
3. Prior to calling Start(), the Supported() function for the driver specified by This must
|
||||
have been called with the same calling parameters, and Supported() must have returned EFI_SUCCESS.
|
||||
|
||||
@param[in] This A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
|
||||
@param[in] ControllerHandle The handle of the controller to start. This handle
|
||||
must support a protocol interface that supplies
|
||||
an I/O abstraction to the driver.
|
||||
@param[in] RemainingDevicePath A pointer to the remaining portion of a device path. This
|
||||
parameter is ignored by device drivers, and is optional for bus
|
||||
drivers. For a bus driver, if this parameter is NULL, then handles
|
||||
for all the children of Controller are created by this driver.
|
||||
If this parameter is not NULL and the first Device Path Node is
|
||||
not the End of Device Path Node, then only the handle for the
|
||||
child device specified by the first Device Path Node of
|
||||
RemainingDevicePath is created by this driver.
|
||||
If the first Device Path Node of RemainingDevicePath is
|
||||
the End of Device Path Node, no child handle is created by this
|
||||
driver.
|
||||
|
||||
@retval EFI_SUCCESS The device was started.
|
||||
@retval EFI_DEVICE_ERROR The device could not be started due to a device error.Currently not implemented.
|
||||
@retval EFI_OUT_OF_RESOURCES The request could not be completed due to a lack of resources.
|
||||
@retval Others The driver failded to start the device.
|
||||
|
||||
**/
|
||||
EFI_STATUS
|
||||
EFIAPI
|
||||
Ext4Bind (
|
||||
IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,
|
||||
IN EFI_HANDLE ControllerHandle,
|
||||
IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL
|
||||
)
|
||||
{
|
||||
EFI_DISK_IO_PROTOCOL *DiskIo;
|
||||
EFI_DISK_IO2_PROTOCOL *DiskIo2;
|
||||
EFI_BLOCK_IO_PROTOCOL *BlockIo;
|
||||
EFI_STATUS Status;
|
||||
|
||||
DiskIo2 = NULL;
|
||||
BlockIo = NULL;
|
||||
DiskIo = NULL;
|
||||
|
||||
// Note: We initialize collation here since this is called in BDS, when we are likely
|
||||
// to have the Unicode Collation protocols available.
|
||||
Status = Ext4InitialiseUnicodeCollation (BindingProtocol->ImageHandle);
|
||||
if (EFI_ERROR (Status)) {
|
||||
// Lets throw a loud error into the log
|
||||
// It is very unlikely something like this may fire out of the blue. Chances are either
|
||||
// the platform configuration is wrong, or we are.
|
||||
DEBUG ((DEBUG_ERROR, "[ext4] Error: Unicode Collation not available - failure to Start() - error %r\n", Status));
|
||||
goto Error;
|
||||
}
|
||||
|
||||
Status = gBS->OpenProtocol (
|
||||
ControllerHandle,
|
||||
&gEfiDiskIoProtocolGuid,
|
||||
(VOID **)&DiskIo,
|
||||
BindingProtocol->ImageHandle,
|
||||
ControllerHandle,
|
||||
EFI_OPEN_PROTOCOL_BY_DRIVER
|
||||
);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
goto Error;
|
||||
}
|
||||
|
||||
DEBUG ((DEBUG_INFO, "[ext4] Controller supports DISK_IO\n"));
|
||||
|
||||
Status = gBS->OpenProtocol (
|
||||
ControllerHandle,
|
||||
&gEfiDiskIo2ProtocolGuid,
|
||||
(VOID **)&DiskIo2,
|
||||
BindingProtocol->ImageHandle,
|
||||
ControllerHandle,
|
||||
EFI_OPEN_PROTOCOL_BY_DRIVER
|
||||
);
|
||||
// It's okay to not support DISK_IO2
|
||||
|
||||
if (DiskIo2 != NULL) {
|
||||
DEBUG ((DEBUG_INFO, "[ext4] Controller supports DISK_IO2\n"));
|
||||
}
|
||||
|
||||
Status = gBS->OpenProtocol (
|
||||
ControllerHandle,
|
||||
&gEfiBlockIoProtocolGuid,
|
||||
(VOID **)&BlockIo,
|
||||
BindingProtocol->ImageHandle,
|
||||
ControllerHandle,
|
||||
EFI_OPEN_PROTOCOL_GET_PROTOCOL
|
||||
);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
goto Error;
|
||||
}
|
||||
|
||||
Status = Ext4OpenPartition (ControllerHandle, DiskIo, DiskIo2, BlockIo);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
DEBUG ((DEBUG_ERROR, "[ext4] Error mounting: %r\n", Status));
|
||||
// Falls through to Error
|
||||
} else {
|
||||
return Status;
|
||||
}
|
||||
|
||||
Error:
|
||||
if (DiskIo) {
|
||||
gBS->CloseProtocol (
|
||||
ControllerHandle,
|
||||
&gEfiDiskIoProtocolGuid,
|
||||
BindingProtocol->ImageHandle,
|
||||
ControllerHandle
|
||||
);
|
||||
}
|
||||
|
||||
if (DiskIo2) {
|
||||
gBS->CloseProtocol (
|
||||
ControllerHandle,
|
||||
&gEfiDiskIo2ProtocolGuid,
|
||||
BindingProtocol->ImageHandle,
|
||||
ControllerHandle
|
||||
);
|
||||
}
|
||||
|
||||
if (BlockIo) {
|
||||
gBS->CloseProtocol (
|
||||
ControllerHandle,
|
||||
&gEfiBlockIoProtocolGuid,
|
||||
BindingProtocol->ImageHandle,
|
||||
ControllerHandle
|
||||
);
|
||||
}
|
||||
|
||||
return Status;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,149 @@
|
|||
## @file
|
||||
# Ext4 Package
|
||||
#
|
||||
# UEFI Driver that produces the Simple File System Protocol for a partition that is formatted
|
||||
# with the EXT4 file system.
|
||||
# More details are available at: https://www.kernel.org/doc/html/v5.4/filesystems/ext4/index.html
|
||||
#
|
||||
# Copyright (c) 2021 Pedro Falcato
|
||||
# SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
#
|
||||
# Layout of an EXT2/3/4 filesystem:
|
||||
# (note: this driver has been developed using
|
||||
# https://www.kernel.org/doc/html/latest/filesystems/ext4/index.html as
|
||||
# documentation).
|
||||
#
|
||||
# An ext2/3/4 filesystem (here on out referred to as simply an ext4 filesystem,
|
||||
# due to the similarities) is composed of various concepts:
|
||||
#
|
||||
# 1) Superblock
|
||||
# The superblock is the structure near (1024 bytes offset from the start)
|
||||
# the start of the partition, and describes the filesystem in general.
|
||||
# Here, we get to know the size of the filesystem's blocks, which features
|
||||
# it supports or not, whether it's been cleanly unmounted, how many blocks
|
||||
# we have, etc.
|
||||
#
|
||||
# 2) Block groups
|
||||
# EXT4 filesystems are divided into block groups, and each block group covers
|
||||
# s_blocks_per_group(8 * Block Size) blocks. Each block group has an
|
||||
# associated block group descriptor; these are present directly after the
|
||||
# superblock. Each block group descriptor contains the location of the
|
||||
# inode table, and the inode and block bitmaps (note these bitmaps are only
|
||||
# a block long, which gets us the 8 * Block Size formula covered previously).
|
||||
#
|
||||
# 3) Blocks
|
||||
# The ext4 filesystem is divided in blocks, of size s_log_block_size ^ 1024.
|
||||
# Blocks can be allocated using individual block groups's bitmaps. Note
|
||||
# that block 0 is invalid and its presence on extents/block tables means
|
||||
# it's part of a file hole, and that particular location must be read as
|
||||
# a block full of zeros.
|
||||
#
|
||||
# 4) Inodes
|
||||
# The ext4 filesystem divides files/directories into inodes (originally
|
||||
# index nodes). Each file/socket/symlink/directory/etc (here on out referred
|
||||
# to as a file, since there is no distinction under the ext4 filesystem) is
|
||||
# stored as a /nameless/ inode, that is stored in some block group's inode
|
||||
# table. Each inode has s_inode_size size (or GOOD_OLD_INODE_SIZE if it's
|
||||
# an old filesystem), and holds various metadata about the file. Since the
|
||||
# largest inode structure right now is ~160 bytes, the rest of the inode
|
||||
# contains inline extended attributes. Inodes' data is stored using either
|
||||
# data blocks (under ext2/3) or extents (under ext4).
|
||||
#
|
||||
# 5) Extents
|
||||
# Ext4 inodes store data in extents. These let N contiguous logical blocks
|
||||
# that are represented by N contiguous physical blocks be represented by a
|
||||
# single extent structure, which minimizes filesystem metadata bloat and
|
||||
# speeds up block mapping (particularly due to the fact that high-quality
|
||||
# ext4 implementations like linux's try /really/ hard to make the file
|
||||
# contiguous, so it's common to have files with almost 0 fragmentation).
|
||||
# Inodes that use extents store them in a tree, and the top of the tree
|
||||
# is stored on i_data. The tree's leaves always start with an
|
||||
# EXT4_EXTENT_HEADER and contain EXT4_EXTENT_INDEX on eh_depth != 0 and
|
||||
# EXT4_EXTENT on eh_depth = 0; these entries are always sorted by logical
|
||||
# block.
|
||||
#
|
||||
# 6) Directories
|
||||
# Ext4 directories are files that store name -> inode mappings for the
|
||||
# logical directory; this is where files get their names, which means ext4
|
||||
# inodes do not themselves have names, since they can be linked (present)
|
||||
# multiple times with different names. Directories can store entries in two
|
||||
# different ways:
|
||||
# 1) Classical linear directories: They store entries as a mostly-linked
|
||||
# mostly-list of EXT4_DIR_ENTRY.
|
||||
# 2) Hash tree directories: These are used for larger directories, with
|
||||
# hundreds of entries, and are designed in a backwards compatible way.
|
||||
# These are not yet implemented in the Ext4Dxe driver.
|
||||
#
|
||||
# 7) Journal
|
||||
# Ext3/4 filesystems have a journal to help protect the filesystem against
|
||||
# system crashes. This is not yet implemented in Ext4Dxe but is described
|
||||
# in detail in the Linux kernel's documentation.
|
||||
##
|
||||
|
||||
|
||||
[Defines]
|
||||
INF_VERSION = 0x00010005
|
||||
BASE_NAME = Ext4Dxe
|
||||
MODULE_UNI_FILE = Ext4Dxe.uni
|
||||
FILE_GUID = 75F2B676-D73B-45CB-B7C1-303C7F4E6FD6
|
||||
MODULE_TYPE = UEFI_DRIVER
|
||||
VERSION_STRING = 1.0
|
||||
|
||||
ENTRY_POINT = Ext4EntryPoint
|
||||
UNLOAD_IMAGE = Ext4Unload
|
||||
|
||||
#
|
||||
# The following information is for reference only and not required by the build tools.
|
||||
#
|
||||
# VALID_ARCHITECTURES = IA32 X64 EBC
|
||||
#
|
||||
|
||||
[Sources]
|
||||
Ext4Dxe.c
|
||||
Partition.c
|
||||
DiskUtil.c
|
||||
Superblock.c
|
||||
BlockGroup.c
|
||||
Inode.c
|
||||
Directory.c
|
||||
Extents.c
|
||||
File.c
|
||||
Symlink.c
|
||||
Collation.c
|
||||
Ext4Disk.h
|
||||
Ext4Dxe.h
|
||||
BlockMap.c
|
||||
|
||||
[Packages]
|
||||
MdePkg/MdePkg.dec
|
||||
RedfishPkg/RedfishPkg.dec
|
||||
|
||||
[LibraryClasses]
|
||||
UefiRuntimeServicesTableLib
|
||||
UefiBootServicesTableLib
|
||||
MemoryAllocationLib
|
||||
BaseMemoryLib
|
||||
BaseLib
|
||||
UefiLib
|
||||
UefiDriverEntryPoint
|
||||
DebugLib
|
||||
PcdLib
|
||||
OrderedCollectionLib
|
||||
BaseUcs2Utf8Lib
|
||||
|
||||
[Guids]
|
||||
gEfiFileInfoGuid ## SOMETIMES_CONSUMES ## UNDEFINED
|
||||
gEfiFileSystemInfoGuid ## SOMETIMES_CONSUMES ## UNDEFINED
|
||||
gEfiFileSystemVolumeLabelInfoIdGuid ## SOMETIMES_CONSUMES ## UNDEFINED
|
||||
|
||||
[Protocols]
|
||||
gEfiDiskIoProtocolGuid ## TO_START
|
||||
gEfiDiskIo2ProtocolGuid ## TO_START
|
||||
gEfiBlockIoProtocolGuid ## TO_START
|
||||
gEfiSimpleFileSystemProtocolGuid ## BY_START
|
||||
gEfiUnicodeCollationProtocolGuid ## TO_START
|
||||
gEfiUnicodeCollation2ProtocolGuid ## TO_START
|
||||
|
||||
[Pcd]
|
||||
gEfiMdePkgTokenSpaceGuid.PcdUefiVariableDefaultLang ## SOMETIMES_CONSUMES
|
||||
gEfiMdePkgTokenSpaceGuid.PcdUefiVariableDefaultPlatformLang ## SOMETIMES_CONSUMES
|
|
@ -0,0 +1,15 @@
|
|||
## @file
|
||||
# Ext4 Package
|
||||
#
|
||||
# UEFI Driver that produces the Simple File System Protocol for a partition that is formatted
|
||||
# with the EXT4 file system.
|
||||
# More details are available at: https://www.kernel.org/doc/html/v5.4/filesystems/ext4/index.html
|
||||
#
|
||||
# Copyright (c) 2021 Pedro Falcato
|
||||
# SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
#
|
||||
##
|
||||
|
||||
#string STR_MODULE_ABSTRACT #language en-US "UEFI driver for the EXT4 file system."
|
||||
|
||||
#string STR_MODULE_DESCRIPTION #language en-US "Produces the EFI Simple File System protocol."
|
|
@ -0,0 +1,658 @@
|
|||
/** @file
|
||||
Extent related routines
|
||||
|
||||
Copyright (c) 2021 - 2023 Pedro Falcato All rights reserved.
|
||||
SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
**/
|
||||
|
||||
#include "Ext4Dxe.h"
|
||||
|
||||
/**
|
||||
Checks if the checksum of the extent data block is correct.
|
||||
@param[in] ExtHeader Pointer to the EXT4_EXTENT_HEADER.
|
||||
@param[in] File Pointer to the file.
|
||||
|
||||
@return TRUE if the checksum is correct, FALSE if there is corruption.
|
||||
**/
|
||||
BOOLEAN
|
||||
Ext4CheckExtentChecksum (
|
||||
IN CONST EXT4_EXTENT_HEADER *ExtHeader,
|
||||
IN CONST EXT4_FILE *File
|
||||
);
|
||||
|
||||
/**
|
||||
Calculates the checksum of the extent data block.
|
||||
@param[in] ExtHeader Pointer to the EXT4_EXTENT_HEADER.
|
||||
@param[in] File Pointer to the file.
|
||||
|
||||
@return The checksum.
|
||||
**/
|
||||
UINT32
|
||||
Ext4CalculateExtentChecksum (
|
||||
IN CONST EXT4_EXTENT_HEADER *ExtHeader,
|
||||
IN CONST EXT4_FILE *File
|
||||
);
|
||||
|
||||
/**
|
||||
Caches a range of extents, by allocating pool memory for each extent and adding it to the tree.
|
||||
|
||||
@param[in] File Pointer to the open file.
|
||||
@param[in] Extents Pointer to an array of extents.
|
||||
@param[in] NumberExtents Length of the array.
|
||||
**/
|
||||
VOID
|
||||
Ext4CacheExtents (
|
||||
IN EXT4_FILE *File,
|
||||
IN CONST EXT4_EXTENT *Extents,
|
||||
IN UINT16 NumberExtents
|
||||
);
|
||||
|
||||
/**
|
||||
Gets an extent from the extents cache of the file.
|
||||
|
||||
@param[in] File Pointer to the open file.
|
||||
@param[in] Block Block we want to grab.
|
||||
|
||||
@return Pointer to the extent, or NULL if it was not found.
|
||||
**/
|
||||
EXT4_EXTENT *
|
||||
Ext4GetExtentFromMap (
|
||||
IN EXT4_FILE *File,
|
||||
IN UINT32 Block
|
||||
);
|
||||
|
||||
/**
|
||||
Retrieves the pointer to the top of the extent tree.
|
||||
@param[in] Inode Pointer to the inode structure.
|
||||
|
||||
@return Pointer to an EXT4_EXTENT_HEADER. This pointer is inside
|
||||
the inode and must not be freed.
|
||||
**/
|
||||
STATIC
|
||||
EXT4_EXTENT_HEADER *
|
||||
Ext4GetInoExtentHeader (
|
||||
IN EXT4_INODE *Inode
|
||||
)
|
||||
{
|
||||
return (EXT4_EXTENT_HEADER *)Inode->i_data;
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if an extent header is valid.
|
||||
@param[in] Header Pointer to the EXT4_EXTENT_HEADER structure.
|
||||
@param[in] MaxEntries Maximum number of entries possible for this tree node.
|
||||
|
||||
@return TRUE if valid, FALSE if not.
|
||||
**/
|
||||
STATIC
|
||||
BOOLEAN
|
||||
Ext4ExtentHeaderValid (
|
||||
IN CONST EXT4_EXTENT_HEADER *Header,
|
||||
IN UINT16 MaxEntries
|
||||
)
|
||||
{
|
||||
if (Header->eh_depth > EXT4_EXTENT_TREE_MAX_DEPTH) {
|
||||
DEBUG ((DEBUG_ERROR, "[ext4] Invalid extent header depth %u\n", Header->eh_depth));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (Header->eh_magic != EXT4_EXTENT_HEADER_MAGIC) {
|
||||
DEBUG ((DEBUG_ERROR, "[ext4] Invalid extent header magic %x\n", Header->eh_magic));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Note: We do not need to check eh_entries here, as the next branch makes sure max >= entries
|
||||
if (Header->eh_max > MaxEntries) {
|
||||
DEBUG ((
|
||||
DEBUG_ERROR,
|
||||
"[ext4] Invalid extent header max entries (%u eh_max, "
|
||||
"theoretical max is %u) (larger than permitted)\n",
|
||||
Header->eh_max,
|
||||
MaxEntries
|
||||
));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (Header->eh_max < Header->eh_entries) {
|
||||
DEBUG ((
|
||||
DEBUG_ERROR,
|
||||
"[ext4] Invalid extent header num entries %u max entries %u\n",
|
||||
Header->eh_entries,
|
||||
Header->eh_max
|
||||
));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
Performs a binary search for a EXT4_EXTENT_INDEX that corresponds to a
|
||||
logical block in a given extent tree node.
|
||||
|
||||
@param[in] Header Pointer to the EXT4_EXTENT_HEADER structure.
|
||||
@param[in] LogicalBlock Block that will be searched
|
||||
|
||||
@return Pointer to the found EXT4_EXTENT_INDEX.
|
||||
**/
|
||||
STATIC
|
||||
EXT4_EXTENT_INDEX *
|
||||
Ext4BinsearchExtentIndex (
|
||||
IN EXT4_EXTENT_HEADER *Header,
|
||||
IN EXT4_BLOCK_NR LogicalBlock
|
||||
)
|
||||
{
|
||||
EXT4_EXTENT_INDEX *l;
|
||||
EXT4_EXTENT_INDEX *r;
|
||||
EXT4_EXTENT_INDEX *m;
|
||||
|
||||
l = ((EXT4_EXTENT_INDEX *)(Header + 1)) + 1;
|
||||
r = ((EXT4_EXTENT_INDEX *)(Header + 1)) + Header->eh_entries - 1;
|
||||
|
||||
// Perform a mostly-standard binary search on the array
|
||||
// This works very nicely because the extents arrays are always sorted.
|
||||
|
||||
while (l <= r) {
|
||||
m = l + (r - l) / 2;
|
||||
|
||||
if (LogicalBlock < m->ei_block) {
|
||||
r = m - 1;
|
||||
} else {
|
||||
l = m + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return l - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
Performs a binary search for a EXT4_EXTENT that corresponds to a
|
||||
logical block in a given extent tree node.
|
||||
|
||||
@param[in] Header Pointer to the EXT4_EXTENT_HEADER structure.
|
||||
@param[in] LogicalBlock Block that will be searched
|
||||
|
||||
@return Pointer to the found EXT4_EXTENT_INDEX, else NULL if the array is empty.
|
||||
Note: The caller must check if the logical block
|
||||
is actually mapped under the given extent.
|
||||
**/
|
||||
STATIC
|
||||
EXT4_EXTENT *
|
||||
Ext4BinsearchExtentExt (
|
||||
IN EXT4_EXTENT_HEADER *Header,
|
||||
IN EXT4_BLOCK_NR LogicalBlock
|
||||
)
|
||||
{
|
||||
EXT4_EXTENT *l;
|
||||
EXT4_EXTENT *r;
|
||||
EXT4_EXTENT *m;
|
||||
|
||||
l = ((EXT4_EXTENT *)(Header + 1)) + 1;
|
||||
r = ((EXT4_EXTENT *)(Header + 1)) + Header->eh_entries - 1;
|
||||
// Perform a mostly-standard binary search on the array
|
||||
// This works very nicely because the extents arrays are always sorted.
|
||||
|
||||
// Empty array
|
||||
if (Header->eh_entries == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (l <= r) {
|
||||
m = l + (r - l) / 2;
|
||||
|
||||
if (LogicalBlock < m->ee_block) {
|
||||
r = m - 1;
|
||||
} else {
|
||||
l = m + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return l - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
Retrieves the leaf block from an EXT4_EXTENT_INDEX.
|
||||
|
||||
@param[in] Index Pointer to the EXT4_EXTENT_INDEX structure.
|
||||
|
||||
@return Block number of the leaf node.
|
||||
**/
|
||||
STATIC
|
||||
EXT4_BLOCK_NR
|
||||
Ext4ExtentIdxLeafBlock (
|
||||
IN EXT4_EXTENT_INDEX *Index
|
||||
)
|
||||
{
|
||||
return LShiftU64 (Index->ei_leaf_hi, 32) | Index->ei_leaf_lo;
|
||||
}
|
||||
|
||||
// Results of sizeof(i_data) / sizeof(extent) - 1 = 4
|
||||
#define EXT4_NR_INLINE_EXTENTS 4
|
||||
|
||||
/**
|
||||
Retrieves an extent from an EXT4 inode.
|
||||
@param[in] Partition Pointer to the opened EXT4 partition.
|
||||
@param[in] File Pointer to the opened file.
|
||||
@param[in] LogicalBlock Block number which the returned extent must cover.
|
||||
@param[out] Extent Pointer to the output buffer, where the extent will be copied to.
|
||||
|
||||
@retval EFI_SUCCESS Retrieval was successful.
|
||||
@retval EFI_NO_MAPPING Block has no mapping.
|
||||
**/
|
||||
EFI_STATUS
|
||||
Ext4GetExtent (
|
||||
IN EXT4_PARTITION *Partition,
|
||||
IN EXT4_FILE *File,
|
||||
IN EXT4_BLOCK_NR LogicalBlock,
|
||||
OUT EXT4_EXTENT *Extent
|
||||
)
|
||||
{
|
||||
EXT4_INODE *Inode;
|
||||
VOID *Buffer;
|
||||
EXT4_EXTENT *Ext;
|
||||
UINT32 CurrentDepth;
|
||||
EXT4_EXTENT_HEADER *ExtHeader;
|
||||
EXT4_EXTENT_INDEX *Index;
|
||||
EFI_STATUS Status;
|
||||
UINT32 MaxExtentsPerNode;
|
||||
EXT4_BLOCK_NR BlockNumber;
|
||||
|
||||
Inode = File->Inode;
|
||||
Ext = NULL;
|
||||
Buffer = NULL;
|
||||
|
||||
DEBUG ((DEBUG_FS, "[ext4] Looking up extent for block %lu\n", LogicalBlock));
|
||||
|
||||
// ext4 does not have support for logical block numbers bigger than UINT32_MAX
|
||||
if (LogicalBlock > (UINT32)-1) {
|
||||
return EFI_NO_MAPPING;
|
||||
}
|
||||
|
||||
// Note: Right now, holes are the single biggest reason for cache misses
|
||||
// We should find a way to get (or cache) holes
|
||||
if ((Ext = Ext4GetExtentFromMap (File, (UINT32)LogicalBlock)) != NULL) {
|
||||
*Extent = *Ext;
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
if ((Inode->i_flags & EXT4_EXTENTS_FL) == 0) {
|
||||
// If this is an older ext2/ext3 filesystem, emulate Ext4GetExtent using the block map
|
||||
// By specification files using block maps are limited to 2^32 blocks,
|
||||
// so we can safely cast LogicalBlock to uint32
|
||||
Status = Ext4GetBlocks (Partition, File, (UINT32)LogicalBlock, Extent);
|
||||
|
||||
if (!EFI_ERROR (Status)) {
|
||||
Ext4CacheExtents (File, Extent, 1);
|
||||
}
|
||||
|
||||
return Status;
|
||||
}
|
||||
|
||||
// Slow path, we'll need to read from disk and (try to) cache those extents.
|
||||
|
||||
ExtHeader = Ext4GetInoExtentHeader (Inode);
|
||||
|
||||
if (!Ext4ExtentHeaderValid (ExtHeader, EXT4_NR_INLINE_EXTENTS)) {
|
||||
return EFI_VOLUME_CORRUPTED;
|
||||
}
|
||||
|
||||
CurrentDepth = ExtHeader->eh_depth;
|
||||
|
||||
// A single node fits into a single block, so we can only have (BlockSize / sizeof(EXT4_EXTENT)) - 1
|
||||
// extents in a single node. Note the -1, because both leaf and internal node headers are 12 bytes,
|
||||
// and so are individual entries.
|
||||
MaxExtentsPerNode = (Partition->BlockSize / sizeof (EXT4_EXTENT)) - 1;
|
||||
|
||||
while (ExtHeader->eh_depth != 0) {
|
||||
CurrentDepth--;
|
||||
// While depth != 0, we're traversing the tree itself and not any leaves
|
||||
// As such, every entry is an EXT4_EXTENT_INDEX entry
|
||||
// Note: Entries after the extent header, either index or actual extent, are always sorted.
|
||||
// Therefore, we can use binary search, and it's actually the standard for doing so
|
||||
// (see FreeBSD).
|
||||
|
||||
Index = Ext4BinsearchExtentIndex (ExtHeader, LogicalBlock);
|
||||
BlockNumber = Ext4ExtentIdxLeafBlock (Index);
|
||||
|
||||
// Check that block isn't file hole
|
||||
if (BlockNumber == EXT4_BLOCK_FILE_HOLE) {
|
||||
if (Buffer != NULL) {
|
||||
FreePool (Buffer);
|
||||
}
|
||||
|
||||
return EFI_VOLUME_CORRUPTED;
|
||||
}
|
||||
|
||||
if (Buffer == NULL) {
|
||||
Buffer = AllocatePool (Partition->BlockSize);
|
||||
if (Buffer == NULL) {
|
||||
return EFI_OUT_OF_RESOURCES;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the leaf block onto the previously-allocated buffer.
|
||||
|
||||
Status = Ext4ReadBlocks (Partition, Buffer, 1, BlockNumber);
|
||||
if (EFI_ERROR (Status)) {
|
||||
FreePool (Buffer);
|
||||
return Status;
|
||||
}
|
||||
|
||||
ExtHeader = Buffer;
|
||||
|
||||
if (!Ext4ExtentHeaderValid (ExtHeader, MaxExtentsPerNode)) {
|
||||
FreePool (Buffer);
|
||||
return EFI_VOLUME_CORRUPTED;
|
||||
}
|
||||
|
||||
if (!Ext4CheckExtentChecksum (ExtHeader, File)) {
|
||||
DEBUG ((DEBUG_ERROR, "[ext4] Invalid extent checksum\n"));
|
||||
FreePool (Buffer);
|
||||
return EFI_VOLUME_CORRUPTED;
|
||||
}
|
||||
|
||||
if (ExtHeader->eh_depth != CurrentDepth) {
|
||||
FreePool (Buffer);
|
||||
return EFI_VOLUME_CORRUPTED;
|
||||
}
|
||||
}
|
||||
|
||||
/* We try to cache every extent under a single leaf, since it's quite likely that we
|
||||
* may need to access things sequentially. Furthermore, ext4 block allocation as done
|
||||
* by linux (and possibly other systems) is quite fancy and usually it results in a small number of extents.
|
||||
* Therefore, we shouldn't have any memory issues.
|
||||
**/
|
||||
Ext4CacheExtents (File, (EXT4_EXTENT *)(ExtHeader + 1), ExtHeader->eh_entries);
|
||||
|
||||
Ext = Ext4BinsearchExtentExt (ExtHeader, LogicalBlock);
|
||||
|
||||
if (!Ext) {
|
||||
if (Buffer != NULL) {
|
||||
FreePool (Buffer);
|
||||
}
|
||||
|
||||
return EFI_NO_MAPPING;
|
||||
}
|
||||
|
||||
if (!((LogicalBlock >= Ext->ee_block) && (Ext->ee_block + Ext4GetExtentLength (Ext) > LogicalBlock))) {
|
||||
// This extent does not cover the block
|
||||
if (Buffer != NULL) {
|
||||
FreePool (Buffer);
|
||||
}
|
||||
|
||||
return EFI_NO_MAPPING;
|
||||
}
|
||||
|
||||
*Extent = *Ext;
|
||||
|
||||
if (Buffer != NULL) {
|
||||
FreePool (Buffer);
|
||||
}
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
Compare two EXT4_EXTENT structs.
|
||||
Used in the extent map's ORDERED_COLLECTION.
|
||||
|
||||
@param[in] UserStruct1 Pointer to the first user structure.
|
||||
|
||||
@param[in] UserStruct2 Pointer to the second user structure.
|
||||
|
||||
@retval <0 If UserStruct1 compares less than UserStruct2.
|
||||
|
||||
@retval 0 If UserStruct1 compares equal to UserStruct2.
|
||||
|
||||
@retval >0 If UserStruct1 compares greater than UserStruct2.
|
||||
**/
|
||||
STATIC
|
||||
INTN
|
||||
EFIAPI
|
||||
Ext4ExtentsMapStructCompare (
|
||||
IN CONST VOID *UserStruct1,
|
||||
IN CONST VOID *UserStruct2
|
||||
)
|
||||
{
|
||||
CONST EXT4_EXTENT *Extent1;
|
||||
CONST EXT4_EXTENT *Extent2;
|
||||
|
||||
Extent1 = UserStruct1;
|
||||
Extent2 = UserStruct2;
|
||||
|
||||
return Extent1->ee_block < Extent2->ee_block ? -1 :
|
||||
Extent1->ee_block > Extent2->ee_block ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
Compare a standalone key against a EXT4_EXTENT containing an embedded key.
|
||||
Used in the extent map's ORDERED_COLLECTION.
|
||||
|
||||
@param[in] StandaloneKey Pointer to the bare key.
|
||||
|
||||
@param[in] UserStruct Pointer to the user structure with the embedded
|
||||
key.
|
||||
|
||||
@retval <0 If StandaloneKey compares less than UserStruct's key.
|
||||
|
||||
@retval 0 If StandaloneKey compares equal to UserStruct's key.
|
||||
|
||||
@retval >0 If StandaloneKey compares greater than UserStruct's key.
|
||||
**/
|
||||
STATIC
|
||||
INTN
|
||||
EFIAPI
|
||||
Ext4ExtentsMapKeyCompare (
|
||||
IN CONST VOID *StandaloneKey,
|
||||
IN CONST VOID *UserStruct
|
||||
)
|
||||
{
|
||||
CONST EXT4_EXTENT *Extent;
|
||||
UINT32 Block;
|
||||
|
||||
// Note that logical blocks are 32-bits in size so no truncation can happen here
|
||||
// with regards to 32-bit architectures.
|
||||
Extent = UserStruct;
|
||||
Block = (UINT32)(UINTN)StandaloneKey;
|
||||
|
||||
if ((Block >= Extent->ee_block) && (Block - Extent->ee_block < Ext4GetExtentLength (Extent))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Block < Extent->ee_block ? -1 :
|
||||
Block > Extent->ee_block ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
Initialises the (empty) extents map, that will work as a cache of extents.
|
||||
|
||||
@param[in] File Pointer to the open file.
|
||||
|
||||
@return Result of the operation.
|
||||
**/
|
||||
EFI_STATUS
|
||||
Ext4InitExtentsMap (
|
||||
IN EXT4_FILE *File
|
||||
)
|
||||
{
|
||||
File->ExtentsMap = OrderedCollectionInit (Ext4ExtentsMapStructCompare, Ext4ExtentsMapKeyCompare);
|
||||
if (!File->ExtentsMap) {
|
||||
return EFI_OUT_OF_RESOURCES;
|
||||
}
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
Frees the extents map, deleting every extent stored.
|
||||
|
||||
@param[in] File Pointer to the open file.
|
||||
**/
|
||||
VOID
|
||||
Ext4FreeExtentsMap (
|
||||
IN EXT4_FILE *File
|
||||
)
|
||||
{
|
||||
// Keep calling Min(), so we get an arbitrary node we can delete.
|
||||
// If Min() returns NULL, it's empty.
|
||||
|
||||
ORDERED_COLLECTION_ENTRY *MinEntry;
|
||||
EXT4_EXTENT *Ext;
|
||||
|
||||
MinEntry = NULL;
|
||||
|
||||
while ((MinEntry = OrderedCollectionMin (File->ExtentsMap)) != NULL) {
|
||||
OrderedCollectionDelete (File->ExtentsMap, MinEntry, (VOID **)&Ext);
|
||||
FreePool (Ext);
|
||||
}
|
||||
|
||||
ASSERT (OrderedCollectionIsEmpty (File->ExtentsMap));
|
||||
|
||||
OrderedCollectionUninit (File->ExtentsMap);
|
||||
File->ExtentsMap = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
Caches a range of extents, by allocating pool memory for each extent and adding it to the tree.
|
||||
|
||||
@param[in] File Pointer to the open file.
|
||||
@param[in] Extents Pointer to an array of extents.
|
||||
@param[in] NumberExtents Length of the array.
|
||||
**/
|
||||
VOID
|
||||
Ext4CacheExtents (
|
||||
IN EXT4_FILE *File,
|
||||
IN CONST EXT4_EXTENT *Extents,
|
||||
IN UINT16 NumberExtents
|
||||
)
|
||||
{
|
||||
UINT16 Idx;
|
||||
EXT4_EXTENT *Extent;
|
||||
EFI_STATUS Status;
|
||||
|
||||
/* Note that any out of memory condition might mean we don't get to cache a whole leaf of extents
|
||||
* in which case, future insertions might fail.
|
||||
*/
|
||||
|
||||
for (Idx = 0; Idx < NumberExtents; Idx++, Extents++) {
|
||||
Extent = AllocatePool (sizeof (EXT4_EXTENT));
|
||||
|
||||
if (Extent == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
CopyMem (Extent, Extents, sizeof (EXT4_EXTENT));
|
||||
Status = OrderedCollectionInsert (File->ExtentsMap, NULL, Extent);
|
||||
|
||||
// EFI_ALREADY_STARTED = already exists in the tree.
|
||||
if (EFI_ERROR (Status)) {
|
||||
FreePool (Extent);
|
||||
|
||||
if (Status == EFI_ALREADY_STARTED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Gets an extent from the extents cache of the file.
|
||||
|
||||
@param[in] File Pointer to the open file.
|
||||
@param[in] Block Block we want to grab.
|
||||
|
||||
@return Pointer to the extent, or NULL if it was not found.
|
||||
**/
|
||||
EXT4_EXTENT *
|
||||
Ext4GetExtentFromMap (
|
||||
IN EXT4_FILE *File,
|
||||
IN UINT32 Block
|
||||
)
|
||||
{
|
||||
ORDERED_COLLECTION_ENTRY *Entry;
|
||||
|
||||
Entry = OrderedCollectionFind (File->ExtentsMap, (CONST VOID *)(UINTN)Block);
|
||||
|
||||
if (Entry == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return OrderedCollectionUserStruct (Entry);
|
||||
}
|
||||
|
||||
/**
|
||||
Calculates the checksum of the extent data block.
|
||||
@param[in] ExtHeader Pointer to the EXT4_EXTENT_HEADER.
|
||||
@param[in] File Pointer to the file.
|
||||
|
||||
@return The checksum.
|
||||
**/
|
||||
UINT32
|
||||
Ext4CalculateExtentChecksum (
|
||||
IN CONST EXT4_EXTENT_HEADER *ExtHeader,
|
||||
IN CONST EXT4_FILE *File
|
||||
)
|
||||
{
|
||||
UINT32 Csum;
|
||||
EXT4_PARTITION *Partition;
|
||||
EXT4_INODE *Inode;
|
||||
|
||||
Partition = File->Partition;
|
||||
Inode = File->Inode;
|
||||
|
||||
Csum = Ext4CalculateChecksum (Partition, &File->InodeNum, sizeof (EXT4_INO_NR), Partition->InitialSeed);
|
||||
Csum = Ext4CalculateChecksum (Partition, &Inode->i_generation, sizeof (Inode->i_generation), Csum);
|
||||
Csum = Ext4CalculateChecksum (Partition, ExtHeader, Partition->BlockSize - sizeof (EXT4_EXTENT_TAIL), Csum);
|
||||
|
||||
return Csum;
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if the checksum of the extent data block is correct.
|
||||
@param[in] ExtHeader Pointer to the EXT4_EXTENT_HEADER.
|
||||
@param[in] File Pointer to the file.
|
||||
|
||||
@return TRUE if the checksum is correct, FALSE if there is corruption.
|
||||
**/
|
||||
BOOLEAN
|
||||
Ext4CheckExtentChecksum (
|
||||
IN CONST EXT4_EXTENT_HEADER *ExtHeader,
|
||||
IN CONST EXT4_FILE *File
|
||||
)
|
||||
{
|
||||
EXT4_PARTITION *Partition;
|
||||
EXT4_EXTENT_TAIL *Tail;
|
||||
|
||||
Partition = File->Partition;
|
||||
|
||||
if (!EXT4_HAS_METADATA_CSUM (Partition)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
Tail = (EXT4_EXTENT_TAIL *)((CONST CHAR8 *)ExtHeader + (Partition->BlockSize - 4));
|
||||
|
||||
return Tail->eb_checksum == Ext4CalculateExtentChecksum (ExtHeader, File);
|
||||
}
|
||||
|
||||
/**
|
||||
Retrieves the extent's length, dealing with uninitialized extents in the process.
|
||||
|
||||
@param[in] Extent Pointer to the EXT4_EXTENT
|
||||
|
||||
@returns Extent's length, in filesystem blocks.
|
||||
**/
|
||||
EXT4_BLOCK_NR
|
||||
Ext4GetExtentLength (
|
||||
IN CONST EXT4_EXTENT *Extent
|
||||
)
|
||||
{
|
||||
// If it's an uninitialized extent, the true length is ee_len - 2^15
|
||||
if (EXT4_EXTENT_IS_UNINITIALIZED (Extent)) {
|
||||
return Extent->ee_len - EXT4_EXTENT_MAX_INITIALIZED;
|
||||
}
|
||||
|
||||
return Extent->ee_len;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,490 @@
|
|||
/** @file
|
||||
Inode related routines
|
||||
|
||||
Copyright (c) 2021 - 2022 Pedro Falcato All rights reserved.
|
||||
SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
|
||||
EpochToEfiTime copied from EmbeddedPkg/Library/TimeBaseLib.c
|
||||
Copyright (c) 2016, Hisilicon Limited. All rights reserved.
|
||||
Copyright (c) 2016-2019, Linaro Limited. All rights reserved.
|
||||
Copyright (c) 2021, Ampere Computing LLC. All rights reserved.
|
||||
**/
|
||||
|
||||
#include "Ext4Dxe.h"
|
||||
|
||||
/**
|
||||
Calculates the checksum of the given inode.
|
||||
@param[in] Partition Pointer to the opened EXT4 partition.
|
||||
@param[in] Inode Pointer to the inode.
|
||||
@param[in] InodeNum Inode number.
|
||||
|
||||
@return The checksum.
|
||||
**/
|
||||
UINT32
|
||||
Ext4CalculateInodeChecksum (
|
||||
IN CONST EXT4_PARTITION *Partition,
|
||||
IN CONST EXT4_INODE *Inode,
|
||||
IN EXT4_INO_NR InodeNum
|
||||
)
|
||||
{
|
||||
UINT32 Crc;
|
||||
UINT16 Dummy;
|
||||
BOOLEAN HasSecondChecksumField;
|
||||
CONST VOID *RestOfInode;
|
||||
UINTN RestOfInodeLength;
|
||||
UINTN Length;
|
||||
|
||||
HasSecondChecksumField = EXT4_INODE_HAS_FIELD (Inode, i_checksum_hi);
|
||||
|
||||
Dummy = 0;
|
||||
|
||||
Crc = Ext4CalculateChecksum (Partition, &InodeNum, sizeof (InodeNum), Partition->InitialSeed);
|
||||
Crc = Ext4CalculateChecksum (Partition, &Inode->i_generation, sizeof (Inode->i_generation), Crc);
|
||||
|
||||
Crc = Ext4CalculateChecksum (
|
||||
Partition,
|
||||
Inode,
|
||||
OFFSET_OF (EXT4_INODE, i_osd2.data_linux.l_i_checksum_lo),
|
||||
Crc
|
||||
);
|
||||
|
||||
Crc = Ext4CalculateChecksum (Partition, &Dummy, sizeof (Dummy), Crc);
|
||||
|
||||
RestOfInode = &Inode->i_osd2.data_linux.l_i_reserved;
|
||||
RestOfInodeLength = Partition->InodeSize - OFFSET_OF (EXT4_INODE, i_osd2.data_linux.l_i_reserved);
|
||||
|
||||
if (HasSecondChecksumField) {
|
||||
Length = OFFSET_OF (EXT4_INODE, i_checksum_hi) - OFFSET_OF (EXT4_INODE, i_osd2.data_linux.l_i_reserved);
|
||||
|
||||
Crc = Ext4CalculateChecksum (Partition, &Inode->i_osd2.data_linux.l_i_reserved, Length, Crc);
|
||||
Crc = Ext4CalculateChecksum (Partition, &Dummy, sizeof (Dummy), Crc);
|
||||
|
||||
// 4 is the size of the i_extra_size field + the size of i_checksum_hi
|
||||
RestOfInodeLength = Partition->InodeSize - EXT4_GOOD_OLD_INODE_SIZE - 4;
|
||||
RestOfInode = &Inode->i_ctime_extra;
|
||||
}
|
||||
|
||||
Crc = Ext4CalculateChecksum (Partition, RestOfInode, RestOfInodeLength, Crc);
|
||||
|
||||
return Crc;
|
||||
}
|
||||
|
||||
/**
|
||||
Reads from an EXT4 inode.
|
||||
@param[in] Partition Pointer to the opened EXT4 partition.
|
||||
@param[in] File Pointer to the opened file.
|
||||
@param[out] Buffer Pointer to the buffer.
|
||||
@param[in] Offset Offset of the read.
|
||||
@param[in out] Length Pointer to the length of the buffer, in bytes.
|
||||
After a successful read, it's updated to the number of read bytes.
|
||||
|
||||
@return Status of the read operation.
|
||||
**/
|
||||
EFI_STATUS
|
||||
Ext4Read (
|
||||
IN EXT4_PARTITION *Partition,
|
||||
IN EXT4_FILE *File,
|
||||
OUT VOID *Buffer,
|
||||
IN UINT64 Offset,
|
||||
IN OUT UINTN *Length
|
||||
)
|
||||
{
|
||||
EXT4_INODE *Inode;
|
||||
UINT64 InodeSize;
|
||||
UINT64 CurrentSeek;
|
||||
UINTN RemainingRead;
|
||||
UINTN BeenRead;
|
||||
UINTN WasRead;
|
||||
EXT4_EXTENT Extent;
|
||||
UINT32 BlockOff;
|
||||
EFI_STATUS Status;
|
||||
BOOLEAN HasBackingExtent;
|
||||
UINT32 HoleOff;
|
||||
UINT64 HoleLen;
|
||||
UINT64 ExtentStartBytes;
|
||||
UINT64 ExtentLengthBytes;
|
||||
UINT64 ExtentLogicalBytes;
|
||||
|
||||
// Our extent offset is the difference between CurrentSeek and ExtentLogicalBytes
|
||||
UINT64 ExtentOffset;
|
||||
UINTN ExtentMayRead;
|
||||
|
||||
Inode = File->Inode;
|
||||
InodeSize = EXT4_INODE_SIZE (Inode);
|
||||
CurrentSeek = Offset;
|
||||
RemainingRead = *Length;
|
||||
BeenRead = 0;
|
||||
|
||||
DEBUG ((DEBUG_FS, "[ext4] Ext4Read(%s, Offset %lu, Length %lu)\n", File->Dentry->Name, Offset, *Length));
|
||||
|
||||
if (Offset > InodeSize) {
|
||||
return EFI_DEVICE_ERROR;
|
||||
}
|
||||
|
||||
if (RemainingRead > InodeSize - Offset) {
|
||||
RemainingRead = (UINTN)(InodeSize - Offset);
|
||||
}
|
||||
|
||||
while (RemainingRead != 0) {
|
||||
WasRead = 0;
|
||||
|
||||
// The algorithm here is to get the extent corresponding to the current block
|
||||
// and then read as much as we can from the current extent.
|
||||
|
||||
Status = Ext4GetExtent (
|
||||
Partition,
|
||||
File,
|
||||
DivU64x32Remainder (CurrentSeek, Partition->BlockSize, &BlockOff),
|
||||
&Extent
|
||||
);
|
||||
|
||||
if ((Status != EFI_SUCCESS) && (Status != EFI_NO_MAPPING)) {
|
||||
return Status;
|
||||
}
|
||||
|
||||
HasBackingExtent = Status != EFI_NO_MAPPING;
|
||||
|
||||
if (!HasBackingExtent || EXT4_EXTENT_IS_UNINITIALIZED (&Extent)) {
|
||||
HoleOff = BlockOff;
|
||||
|
||||
if (!HasBackingExtent) {
|
||||
HoleLen = Partition->BlockSize - HoleOff;
|
||||
} else {
|
||||
// Uninitialized extents behave exactly the same as file holes, except they have
|
||||
// blocks already allocated to them.
|
||||
HoleLen = MultU64x32 (Ext4GetExtentLength (&Extent), Partition->BlockSize) - HoleOff;
|
||||
}
|
||||
|
||||
WasRead = HoleLen > RemainingRead ? RemainingRead : (UINTN)HoleLen;
|
||||
// Potential improvement: In the future, we could get the file hole's total
|
||||
// size and memset all that
|
||||
ZeroMem (Buffer, WasRead);
|
||||
} else {
|
||||
ExtentStartBytes = MultU64x32 (
|
||||
LShiftU64 (Extent.ee_start_hi, 32) |
|
||||
Extent.ee_start_lo,
|
||||
Partition->BlockSize
|
||||
);
|
||||
ExtentLengthBytes = Extent.ee_len * Partition->BlockSize;
|
||||
ExtentLogicalBytes = MultU64x32 ((UINT64)Extent.ee_block, Partition->BlockSize);
|
||||
ExtentOffset = CurrentSeek - ExtentLogicalBytes;
|
||||
ExtentMayRead = (UINTN)(ExtentLengthBytes - ExtentOffset);
|
||||
|
||||
WasRead = ExtentMayRead > RemainingRead ? RemainingRead : ExtentMayRead;
|
||||
|
||||
Status = Ext4ReadDiskIo (Partition, Buffer, WasRead, ExtentStartBytes + ExtentOffset);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
DEBUG ((
|
||||
DEBUG_ERROR,
|
||||
"[ext4] Error %r reading [%lu, %lu]\n",
|
||||
Status,
|
||||
ExtentStartBytes + ExtentOffset,
|
||||
ExtentStartBytes + ExtentOffset + WasRead - 1
|
||||
));
|
||||
return Status;
|
||||
}
|
||||
}
|
||||
|
||||
RemainingRead -= WasRead;
|
||||
Buffer = (VOID *)((CHAR8 *)Buffer + WasRead);
|
||||
BeenRead += WasRead;
|
||||
CurrentSeek += WasRead;
|
||||
}
|
||||
|
||||
*Length = BeenRead;
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
Allocates a zeroed inode structure.
|
||||
@param[in] Partition Pointer to the opened EXT4 partition.
|
||||
|
||||
@return Pointer to the allocated structure, from the pool,
|
||||
with size Partition->InodeSize.
|
||||
**/
|
||||
EXT4_INODE *
|
||||
Ext4AllocateInode (
|
||||
IN EXT4_PARTITION *Partition
|
||||
)
|
||||
{
|
||||
BOOLEAN NeedsToZeroRest;
|
||||
UINT32 InodeSize;
|
||||
EXT4_INODE *Inode;
|
||||
|
||||
NeedsToZeroRest = FALSE;
|
||||
InodeSize = Partition->InodeSize;
|
||||
|
||||
// We allocate a structure of at least sizeof(EXT4_INODE), but in the future, when
|
||||
// write support is added and we need to flush inodes to disk, we could have a bit better
|
||||
// distinction between the on-disk inode and a separate, nicer to work with inode struct.
|
||||
// It's important to note that EXT4_INODE includes fields that may not exist in an actual
|
||||
// filesystem (the minimum inode size is 128 byte and at the moment the size of EXT4_INODE
|
||||
// is 160 bytes).
|
||||
|
||||
if (InodeSize < sizeof (EXT4_INODE)) {
|
||||
InodeSize = sizeof (EXT4_INODE);
|
||||
NeedsToZeroRest = TRUE;
|
||||
}
|
||||
|
||||
Inode = AllocateZeroPool (InodeSize);
|
||||
|
||||
if (Inode == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (NeedsToZeroRest) {
|
||||
Inode->i_extra_isize = 0;
|
||||
}
|
||||
|
||||
return Inode;
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if a file is a directory.
|
||||
@param[in] File Pointer to the opened file.
|
||||
|
||||
@return TRUE if file is a directory.
|
||||
**/
|
||||
BOOLEAN
|
||||
Ext4FileIsDir (
|
||||
IN CONST EXT4_FILE *File
|
||||
)
|
||||
{
|
||||
return (File->Inode->i_mode & EXT4_INO_TYPE_DIR) == EXT4_INO_TYPE_DIR;
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if a file is a symlink.
|
||||
|
||||
@param[in] File Pointer to the opened file.
|
||||
|
||||
@return BOOLEAN Whether file is a symlink
|
||||
**/
|
||||
BOOLEAN
|
||||
Ext4FileIsSymlink (
|
||||
IN CONST EXT4_FILE *File
|
||||
)
|
||||
{
|
||||
return (File->Inode->i_mode & EXT4_INO_TYPE_SYMLINK) == EXT4_INO_TYPE_SYMLINK;
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if a file is a regular file.
|
||||
@param[in] File Pointer to the opened file.
|
||||
|
||||
@return BOOLEAN TRUE if file is a regular file.
|
||||
**/
|
||||
BOOLEAN
|
||||
Ext4FileIsReg (
|
||||
IN CONST EXT4_FILE *File
|
||||
)
|
||||
{
|
||||
return (File->Inode->i_mode & EXT4_INO_TYPE_REGFILE) == EXT4_INO_TYPE_REGFILE;
|
||||
}
|
||||
|
||||
/**
|
||||
Calculates the physical space used by a file.
|
||||
@param[in] File Pointer to the opened file.
|
||||
|
||||
@return Physical space used by a file, in bytes.
|
||||
**/
|
||||
UINT64
|
||||
Ext4FilePhysicalSpace (
|
||||
IN EXT4_FILE *File
|
||||
)
|
||||
{
|
||||
BOOLEAN HugeFile;
|
||||
UINT64 Blocks;
|
||||
|
||||
HugeFile = EXT4_HAS_RO_COMPAT (File->Partition, EXT4_FEATURE_RO_COMPAT_HUGE_FILE);
|
||||
Blocks = File->Inode->i_blocks;
|
||||
|
||||
if (HugeFile) {
|
||||
Blocks |= LShiftU64 (File->Inode->i_osd2.data_linux.l_i_blocks_high, 32);
|
||||
|
||||
// If HUGE_FILE is enabled and EXT4_HUGE_FILE_FL is set in the inode's flags, each unit
|
||||
// in i_blocks corresponds to an actual filesystem block
|
||||
if ((File->Inode->i_flags & EXT4_HUGE_FILE_FL) != 0) {
|
||||
return MultU64x32 (Blocks, File->Partition->BlockSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Else, each i_blocks unit corresponds to 512 bytes
|
||||
return MultU64x32 (Blocks, 512);
|
||||
}
|
||||
|
||||
// Copied from EmbeddedPkg at my mentor's request.
|
||||
// The lack of comments and good variable names is frightening...
|
||||
|
||||
/**
|
||||
Converts Epoch seconds (elapsed since 1970 JANUARY 01, 00:00:00 UTC) to EFI_TIME.
|
||||
|
||||
@param[in] EpochSeconds Epoch seconds.
|
||||
@param[out] Time The time converted to UEFI format.
|
||||
|
||||
**/
|
||||
STATIC
|
||||
VOID
|
||||
EFIAPI
|
||||
EpochToEfiTime (
|
||||
IN UINTN EpochSeconds,
|
||||
OUT EFI_TIME *Time
|
||||
)
|
||||
{
|
||||
UINTN a;
|
||||
UINTN b;
|
||||
UINTN c;
|
||||
UINTN d;
|
||||
UINTN g;
|
||||
UINTN j;
|
||||
UINTN m;
|
||||
UINTN y;
|
||||
UINTN da;
|
||||
UINTN db;
|
||||
UINTN dc;
|
||||
UINTN dg;
|
||||
UINTN hh;
|
||||
UINTN mm;
|
||||
UINTN ss;
|
||||
UINTN J;
|
||||
|
||||
J = (EpochSeconds / 86400) + 2440588;
|
||||
j = J + 32044;
|
||||
g = j / 146097;
|
||||
dg = j % 146097;
|
||||
c = (((dg / 36524) + 1) * 3) / 4;
|
||||
dc = dg - (c * 36524);
|
||||
b = dc / 1461;
|
||||
db = dc % 1461;
|
||||
a = (((db / 365) + 1) * 3) / 4;
|
||||
da = db - (a * 365);
|
||||
y = (g * 400) + (c * 100) + (b * 4) + a;
|
||||
m = (((da * 5) + 308) / 153) - 2;
|
||||
d = da - (((m + 4) * 153) / 5) + 122;
|
||||
|
||||
Time->Year = (UINT16)(y - 4800 + ((m + 2) / 12));
|
||||
Time->Month = ((m + 2) % 12) + 1;
|
||||
Time->Day = (UINT8)(d + 1);
|
||||
|
||||
ss = EpochSeconds % 60;
|
||||
a = (EpochSeconds - ss) / 60;
|
||||
mm = a % 60;
|
||||
b = (a - mm) / 60;
|
||||
hh = b % 24;
|
||||
|
||||
Time->Hour = (UINT8)hh;
|
||||
Time->Minute = (UINT8)mm;
|
||||
Time->Second = (UINT8)ss;
|
||||
Time->Nanosecond = 0;
|
||||
}
|
||||
|
||||
// The time format used to (de/en)code timestamp and timestamp_extra is documented on
|
||||
// the ext4 docs page in kernel.org
|
||||
#define EXT4_EXTRA_TIMESTAMP_MASK ((1 << 2) - 1)
|
||||
|
||||
#define EXT4_FILE_GET_TIME_GENERIC(Name, Field) \
|
||||
VOID \
|
||||
Ext4File ## Name (IN EXT4_FILE *File, OUT EFI_TIME *Time) \
|
||||
{ \
|
||||
EXT4_INODE *Inode = File->Inode; \
|
||||
UINT64 SecondsEpoch = Inode->Field; \
|
||||
UINT32 Nanoseconds = 0; \
|
||||
\
|
||||
if (EXT4_INODE_HAS_FIELD (Inode, Field ## _extra)) { \
|
||||
SecondsEpoch |= LShiftU64 ((UINT64)(Inode->Field ## _extra & EXT4_EXTRA_TIMESTAMP_MASK), 32); \
|
||||
Nanoseconds = Inode->Field ## _extra >> 2; \
|
||||
} \
|
||||
EpochToEfiTime ((UINTN)SecondsEpoch, Time); \
|
||||
Time->Nanosecond = Nanoseconds; \
|
||||
}
|
||||
|
||||
// Note: EpochToEfiTime should be adjusted to take in a UINT64 instead of a UINTN, in order to avoid Y2038
|
||||
// on 32-bit systems.
|
||||
|
||||
/**
|
||||
Gets the file's last access time.
|
||||
@param[in] File Pointer to the opened file.
|
||||
@param[out] Time Pointer to an EFI_TIME structure.
|
||||
**/
|
||||
EXT4_FILE_GET_TIME_GENERIC (ATime, i_atime);
|
||||
|
||||
/**
|
||||
Gets the file's last (data) modification time.
|
||||
@param[in] File Pointer to the opened file.
|
||||
@param[out] Time Pointer to an EFI_TIME structure.
|
||||
**/
|
||||
EXT4_FILE_GET_TIME_GENERIC (MTime, i_mtime);
|
||||
|
||||
/**
|
||||
Gets the file's creation time.
|
||||
@param[in] File Pointer to the opened file.
|
||||
@param[out] Time Pointer to an EFI_TIME structure.
|
||||
**/
|
||||
STATIC
|
||||
EXT4_FILE_GET_TIME_GENERIC (
|
||||
CrTime,
|
||||
i_crtime
|
||||
);
|
||||
|
||||
/**
|
||||
Gets the file's creation time, if possible.
|
||||
@param[in] File Pointer to the opened file.
|
||||
@param[out] Time Pointer to an EFI_TIME structure.
|
||||
In the case where the the creation time isn't recorded,
|
||||
Time is zeroed.
|
||||
**/
|
||||
VOID
|
||||
Ext4FileCreateTime (
|
||||
IN EXT4_FILE *File,
|
||||
OUT EFI_TIME *Time
|
||||
)
|
||||
{
|
||||
EXT4_INODE *Inode;
|
||||
|
||||
Inode = File->Inode;
|
||||
|
||||
if (!EXT4_INODE_HAS_FIELD (Inode, i_crtime)) {
|
||||
ZeroMem (Time, sizeof (EFI_TIME));
|
||||
return;
|
||||
}
|
||||
|
||||
Ext4FileCrTime (File, Time);
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if the checksum of the inode is correct.
|
||||
@param[in] Partition Pointer to the opened EXT4 partition.
|
||||
@param[in] Inode Pointer to the inode.
|
||||
@param[in] InodeNum Inode number.
|
||||
|
||||
@return TRUE if checksum is correct, FALSE if there is corruption.
|
||||
**/
|
||||
BOOLEAN
|
||||
Ext4CheckInodeChecksum (
|
||||
IN CONST EXT4_PARTITION *Partition,
|
||||
IN CONST EXT4_INODE *Inode,
|
||||
IN EXT4_INO_NR InodeNum
|
||||
)
|
||||
{
|
||||
UINT32 Csum;
|
||||
UINT32 DiskCsum;
|
||||
|
||||
if (!EXT4_HAS_METADATA_CSUM (Partition)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
Csum = Ext4CalculateInodeChecksum (Partition, Inode, InodeNum);
|
||||
|
||||
DiskCsum = Inode->i_osd2.data_linux.l_i_checksum_lo;
|
||||
|
||||
if (EXT4_INODE_HAS_FIELD (Inode, i_checksum_hi)) {
|
||||
DiskCsum |= ((UINT32)Inode->i_checksum_hi) << 16;
|
||||
} else {
|
||||
// Only keep the lower bits for the comparison if the checksum is 16 bits.
|
||||
Csum &= 0xffff;
|
||||
}
|
||||
|
||||
return Csum == DiskCsum;
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
/** @file
|
||||
Driver entry point
|
||||
|
||||
Copyright (c) 2021 Pedro Falcato All rights reserved.
|
||||
SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
**/
|
||||
|
||||
#include "Ext4Dxe.h"
|
||||
|
||||
/**
|
||||
Opens an ext4 partition and installs the Simple File System protocol.
|
||||
|
||||
@param[in] DeviceHandle Handle to the block device.
|
||||
@param[in] DiskIo Pointer to an EFI_DISK_IO_PROTOCOL.
|
||||
@param[in opt] DiskIo2 Pointer to an EFI_DISK_IO2_PROTOCOL, if supported.
|
||||
@param[in] BlockIo Pointer to an EFI_BLOCK_IO_PROTOCOL.
|
||||
|
||||
@retval EFI_SUCCESS The opening was successful.
|
||||
!EFI_SUCCESS Opening failed.
|
||||
**/
|
||||
EFI_STATUS
|
||||
Ext4OpenPartition (
|
||||
IN EFI_HANDLE DeviceHandle,
|
||||
IN EFI_DISK_IO_PROTOCOL *DiskIo,
|
||||
IN OPTIONAL EFI_DISK_IO2_PROTOCOL *DiskIo2,
|
||||
IN EFI_BLOCK_IO_PROTOCOL *BlockIo
|
||||
)
|
||||
{
|
||||
EXT4_PARTITION *Part;
|
||||
EFI_STATUS Status;
|
||||
|
||||
Part = AllocateZeroPool (sizeof (*Part));
|
||||
|
||||
if (Part == NULL) {
|
||||
return EFI_OUT_OF_RESOURCES;
|
||||
}
|
||||
|
||||
InitializeListHead (&Part->OpenFiles);
|
||||
|
||||
Part->BlockIo = BlockIo;
|
||||
Part->DiskIo = DiskIo;
|
||||
Part->DiskIo2 = DiskIo2;
|
||||
|
||||
Status = Ext4OpenSuperblock (Part);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
FreePool (Part);
|
||||
return Status;
|
||||
}
|
||||
|
||||
Part->Interface.Revision = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION;
|
||||
Part->Interface.OpenVolume = Ext4OpenVolume;
|
||||
Status = gBS->InstallMultipleProtocolInterfaces (
|
||||
&DeviceHandle,
|
||||
&gEfiSimpleFileSystemProtocolGuid,
|
||||
&Part->Interface,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
FreePool (Part);
|
||||
return Status;
|
||||
}
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
Sets up the protocol and metadata of a file that is being opened.
|
||||
|
||||
@param[in out] File Pointer to the file.
|
||||
@param[in] Partition Pointer to the opened partition.
|
||||
**/
|
||||
VOID
|
||||
Ext4SetupFile (
|
||||
IN OUT EXT4_FILE *File,
|
||||
IN EXT4_PARTITION *Partition
|
||||
)
|
||||
{
|
||||
// Note: We don't yet support revision 2 of the file protocol
|
||||
// (needs DISK_IO2 + asynchronous IO)
|
||||
File->Protocol.Revision = EFI_FILE_PROTOCOL_REVISION;
|
||||
File->Protocol.Open = Ext4Open;
|
||||
File->Protocol.Close = Ext4Close;
|
||||
File->Protocol.Delete = Ext4Delete;
|
||||
File->Protocol.Read = Ext4ReadFile;
|
||||
File->Protocol.Write = Ext4WriteFile;
|
||||
File->Protocol.SetPosition = Ext4SetPosition;
|
||||
File->Protocol.GetPosition = Ext4GetPosition;
|
||||
File->Protocol.GetInfo = Ext4GetInfo;
|
||||
File->Protocol.SetInfo = Ext4SetInfo;
|
||||
|
||||
File->Partition = Partition;
|
||||
}
|
||||
|
||||
/**
|
||||
Unmounts and frees an ext4 partition.
|
||||
|
||||
@param[in] Partition Pointer to the opened partition.
|
||||
|
||||
@retval Status of the unmount.
|
||||
**/
|
||||
EFI_STATUS
|
||||
Ext4UnmountAndFreePartition (
|
||||
IN EXT4_PARTITION *Partition
|
||||
)
|
||||
{
|
||||
LIST_ENTRY *Entry;
|
||||
LIST_ENTRY *NextEntry;
|
||||
EXT4_FILE *File;
|
||||
BOOLEAN DeletedRootDentry;
|
||||
|
||||
Partition->Unmounting = TRUE;
|
||||
Ext4CloseInternal (Partition->Root);
|
||||
|
||||
BASE_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Partition->OpenFiles) {
|
||||
File = EXT4_FILE_FROM_OPEN_FILES_NODE (Entry);
|
||||
|
||||
Ext4CloseInternal (File);
|
||||
}
|
||||
|
||||
DeletedRootDentry = Ext4UnrefDentry (Partition->RootDentry);
|
||||
|
||||
if (!DeletedRootDentry) {
|
||||
DEBUG ((DEBUG_ERROR, "[ext4] Failed to delete root dentry - resource leak present.\n"));
|
||||
}
|
||||
|
||||
FreePool (Partition->BlockGroups);
|
||||
FreePool (Partition);
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
|
@ -0,0 +1,363 @@
|
|||
/** @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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,261 @@
|
|||
/** @file
|
||||
Symbolic links routines
|
||||
|
||||
Copyright (c) 2022-2023 Savva Mitrofanov All rights reserved.
|
||||
SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
**/
|
||||
|
||||
#include "Ext4Dxe.h"
|
||||
|
||||
/**
|
||||
Detects if a symlink is a fast symlink.
|
||||
|
||||
@param[in] File Pointer to the opened file.
|
||||
|
||||
@return BOOLEAN Whether symlink is a fast symlink
|
||||
**/
|
||||
STATIC
|
||||
BOOLEAN
|
||||
Ext4SymlinkIsFastSymlink (
|
||||
IN CONST EXT4_FILE *File
|
||||
)
|
||||
{
|
||||
//
|
||||
// Detection logic of the fast-symlink splits into two behaviors - old and new.
|
||||
// The old behavior is based on comparing the extended attribute blocks
|
||||
// with the inode's i_blocks, and if it's zero we know the inode isn't storing
|
||||
// the link in filesystem blocks, so we look to the inode->i_data.
|
||||
// The new behavior is apparently needed only with the large EA inode feature.
|
||||
// In this case we check that inode size less than maximum fast symlink size.
|
||||
// So, we revert to the old behavior if the large EA inode feature is not set.
|
||||
//
|
||||
UINT32 FileAcl;
|
||||
UINT32 ExtAttrBlocks;
|
||||
|
||||
if ((File->Inode->i_flags & EXT4_EA_INODE_FL) == 0) {
|
||||
FileAcl = File->Inode->i_file_acl;
|
||||
if (EXT4_IS_64_BIT (File->Partition)) {
|
||||
//
|
||||
// We don't care about final value, we are just checking for any bit is set
|
||||
// so, thats why we neglect LShiftU64(.., 32)
|
||||
//
|
||||
FileAcl |= File->Inode->i_osd2.data_linux.l_i_file_acl_high;
|
||||
}
|
||||
|
||||
ExtAttrBlocks = FileAcl != 0 ? (File->Partition->BlockSize >> 9) : 0;
|
||||
|
||||
return File->Inode->i_blocks == ExtAttrBlocks;
|
||||
}
|
||||
|
||||
return EXT4_INODE_SIZE (File->Inode) <= EXT4_FAST_SYMLINK_MAX_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
Reads a fast symlink file.
|
||||
|
||||
@param[in] Partition Pointer to the ext4 partition.
|
||||
@param[in] File Pointer to the open symlink file.
|
||||
@param[out] AsciiSymlink Pointer to the output ascii symlink string.
|
||||
@param[out] AsciiSymlinkSize Pointer to the output ascii symlink string length.
|
||||
|
||||
@retval EFI_SUCCESS Fast symlink was read.
|
||||
@retval EFI_OUT_OF_RESOURCES Memory allocation error.
|
||||
**/
|
||||
STATIC
|
||||
EFI_STATUS
|
||||
Ext4ReadFastSymlink (
|
||||
IN EXT4_PARTITION *Partition,
|
||||
IN EXT4_FILE *File,
|
||||
OUT CHAR8 **AsciiSymlink,
|
||||
OUT UINT32 *AsciiSymlinkSize
|
||||
)
|
||||
{
|
||||
UINT32 SymlinkSize;
|
||||
CHAR8 *AsciiSymlinkTmp;
|
||||
|
||||
//
|
||||
// Fast-symlink's EXT4_INODE_SIZE is not necessarily validated when we checked it in
|
||||
// Ext4SymlinkIsFastSymlink(), so truncate if necessary.
|
||||
//
|
||||
SymlinkSize = (UINT32)MIN (EXT4_INODE_SIZE (File->Inode), EXT4_FAST_SYMLINK_MAX_SIZE);
|
||||
|
||||
AsciiSymlinkTmp = AllocatePool (SymlinkSize + 1);
|
||||
if (AsciiSymlinkTmp == NULL) {
|
||||
DEBUG ((DEBUG_ERROR, "[ext4] Failed to allocate symlink ascii string buffer\n"));
|
||||
return EFI_OUT_OF_RESOURCES;
|
||||
}
|
||||
|
||||
CopyMem (AsciiSymlinkTmp, File->Inode->i_data, SymlinkSize);
|
||||
|
||||
//
|
||||
// Add null-terminator
|
||||
//
|
||||
AsciiSymlinkTmp[SymlinkSize] = '\0';
|
||||
|
||||
*AsciiSymlink = AsciiSymlinkTmp;
|
||||
*AsciiSymlinkSize = SymlinkSize + 1;
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
Reads a slow symlink file.
|
||||
|
||||
@param[in] Partition Pointer to the ext4 partition.
|
||||
@param[in] File Pointer to the open symlink file.
|
||||
@param[out] AsciiSymlink Pointer to the output ascii symlink string.
|
||||
@param[out] AsciiSymlinkSize Pointer to the output ascii symlink string length.
|
||||
|
||||
@retval EFI_SUCCESS Slow symlink was read.
|
||||
@retval EFI_OUT_OF_RESOURCES Memory allocation error.
|
||||
@retval EFI_INVALID_PARAMETER Slow symlink path has incorrect length
|
||||
@retval EFI_VOLUME_CORRUPTED Symlink read block size differ from inode value
|
||||
**/
|
||||
STATIC
|
||||
EFI_STATUS
|
||||
Ext4ReadSlowSymlink (
|
||||
IN EXT4_PARTITION *Partition,
|
||||
IN EXT4_FILE *File,
|
||||
OUT CHAR8 **AsciiSymlink,
|
||||
OUT UINT32 *AsciiSymlinkSize
|
||||
)
|
||||
{
|
||||
EFI_STATUS Status;
|
||||
CHAR8 *SymlinkTmp;
|
||||
UINT64 SymlinkSizeTmp;
|
||||
UINT32 SymlinkAllocateSize;
|
||||
UINTN ReadSize;
|
||||
|
||||
SymlinkSizeTmp = EXT4_INODE_SIZE (File->Inode);
|
||||
|
||||
//
|
||||
// Allocate EXT4_INODE_SIZE + 1
|
||||
//
|
||||
if (SymlinkSizeTmp >= EXT4_EFI_PATH_MAX) {
|
||||
DEBUG ((
|
||||
DEBUG_WARN,
|
||||
"[ext4] Warn: symlink path maximum length was hit!\n"
|
||||
));
|
||||
return EFI_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
SymlinkAllocateSize = (UINT32)SymlinkSizeTmp + 1;
|
||||
|
||||
SymlinkTmp = AllocatePool (SymlinkAllocateSize);
|
||||
if (SymlinkTmp == NULL) {
|
||||
DEBUG ((DEBUG_FS, "[ext4] Failed to allocate symlink ascii string buffer\n"));
|
||||
return EFI_OUT_OF_RESOURCES;
|
||||
}
|
||||
|
||||
ReadSize = (UINTN)SymlinkSizeTmp;
|
||||
Status = Ext4Read (Partition, File, SymlinkTmp, File->Position, &ReadSize);
|
||||
if (EFI_ERROR (Status)) {
|
||||
DEBUG ((DEBUG_FS, "[ext4] Failed to read symlink from blocks with status %r\n", Status));
|
||||
FreePool (SymlinkTmp);
|
||||
return Status;
|
||||
}
|
||||
|
||||
if (SymlinkSizeTmp != ReadSize) {
|
||||
DEBUG ((
|
||||
DEBUG_FS,
|
||||
"[ext4] Error! The size of the read block doesn't match the value from the inode!\n"
|
||||
));
|
||||
FreePool (SymlinkTmp);
|
||||
return EFI_VOLUME_CORRUPTED;
|
||||
}
|
||||
|
||||
//
|
||||
// Add null-terminator
|
||||
//
|
||||
SymlinkTmp[ReadSize] = '\0';
|
||||
|
||||
*AsciiSymlinkSize = SymlinkAllocateSize;
|
||||
*AsciiSymlink = SymlinkTmp;
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
Reads a symlink file.
|
||||
|
||||
@param[in] Partition Pointer to the ext4 partition.
|
||||
@param[in] File Pointer to the open symlink file.
|
||||
@param[out] Symlink Pointer to the output unicode symlink string.
|
||||
|
||||
@retval EFI_SUCCESS Symlink was read.
|
||||
@retval EFI_ACCESS_DENIED Symlink is encrypted.
|
||||
@retval EFI_OUT_OF_RESOURCES Memory allocation error.
|
||||
@retval EFI_INVALID_PARAMETER Symlink path has incorrect length
|
||||
@retval EFI_VOLUME_CORRUPTED Symlink read block size differ from inode value
|
||||
**/
|
||||
EFI_STATUS
|
||||
Ext4ReadSymlink (
|
||||
IN EXT4_PARTITION *Partition,
|
||||
IN EXT4_FILE *File,
|
||||
OUT CHAR16 **Symlink
|
||||
)
|
||||
{
|
||||
EFI_STATUS Status;
|
||||
CHAR8 *SymlinkTmp;
|
||||
UINT32 SymlinkSize;
|
||||
CHAR16 *Symlink16Tmp;
|
||||
CHAR16 *Needle;
|
||||
|
||||
//
|
||||
// Assume that we already read Inode via Ext4ReadInode
|
||||
// Skip reading, just check encryption flag
|
||||
//
|
||||
if ((File->Inode->i_flags & EXT4_ENCRYPT_FL) != 0) {
|
||||
DEBUG ((DEBUG_WARN, "[ext4] Warn: symlink is encrypted\n"));
|
||||
return EFI_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
if (Ext4SymlinkIsFastSymlink (File)) {
|
||||
Status = Ext4ReadFastSymlink (Partition, File, &SymlinkTmp, &SymlinkSize);
|
||||
} else {
|
||||
Status = Ext4ReadSlowSymlink (Partition, File, &SymlinkTmp, &SymlinkSize);
|
||||
}
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
DEBUG ((DEBUG_FS, "[ext4] Symlink read error with Status %r\n", Status));
|
||||
return Status;
|
||||
}
|
||||
|
||||
Symlink16Tmp = AllocatePool (SymlinkSize * sizeof (CHAR16));
|
||||
if (Symlink16Tmp == NULL) {
|
||||
DEBUG ((DEBUG_FS, "[ext4] Failed to allocate symlink unicode string buffer\n"));
|
||||
FreePool (SymlinkTmp);
|
||||
return EFI_OUT_OF_RESOURCES;
|
||||
}
|
||||
|
||||
Status = AsciiStrToUnicodeStrS (
|
||||
SymlinkTmp,
|
||||
Symlink16Tmp,
|
||||
SymlinkSize
|
||||
);
|
||||
|
||||
FreePool (SymlinkTmp);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
DEBUG ((
|
||||
DEBUG_FS,
|
||||
"[ext4] Failed to convert ascii symlink to unicode with Status %r\n",
|
||||
Status
|
||||
));
|
||||
FreePool (Symlink16Tmp);
|
||||
return Status;
|
||||
}
|
||||
|
||||
//
|
||||
// Convert to UEFI slashes
|
||||
//
|
||||
for (Needle = Symlink16Tmp; *Needle != L'\0'; Needle++) {
|
||||
if (*Needle == L'/') {
|
||||
*Needle = L'\\';
|
||||
}
|
||||
}
|
||||
|
||||
*Symlink = Symlink16Tmp;
|
||||
|
||||
return Status;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
## @file
|
||||
# Ext4 DSC include file for [LibraryClasses] section of all Architectures.
|
||||
#
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
#
|
||||
##
|
||||
|
||||
!if $(EXT4_ENABLE) == TRUE
|
||||
BaseUcs2Utf8Lib|RedfishPkg/Library/BaseUcs2Utf8Lib/BaseUcs2Utf8Lib.inf
|
||||
!endif
|
|
@ -0,0 +1,17 @@
|
|||
## @file
|
||||
# Ext4 Package
|
||||
#
|
||||
# This package provides libraries and drivers related to the ext4 filesystem implementation.
|
||||
# More details are available at: https://www.kernel.org/doc/html/v5.4/filesystems/ext4/index.html
|
||||
#
|
||||
# Copyright (c) 2021 Pedro Falcato
|
||||
# SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
#
|
||||
##
|
||||
|
||||
[Defines]
|
||||
DEC_SPECIFICATION = 0x00010005
|
||||
PACKAGE_NAME = Ext4Pkg
|
||||
PACKAGE_UNI_FILE = Ext4Pkg.uni
|
||||
PACKAGE_GUID = 6B4BF998-668B-46D3-BCFA-971F99F8708C
|
||||
PACKAGE_VERSION = 0.1
|
|
@ -0,0 +1,75 @@
|
|||
## @file
|
||||
# Ext4 Package
|
||||
#
|
||||
# This package provides libraries and drivers related to the ext4 filesystem implementation.
|
||||
# More details are available at: https://www.kernel.org/doc/html/v5.4/filesystems/ext4/index.html
|
||||
#
|
||||
# Copyright (c) 2021 Pedro Falcato
|
||||
# SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
#
|
||||
##
|
||||
|
||||
|
||||
[Defines]
|
||||
PLATFORM_NAME = Ext4
|
||||
PLATFORM_GUID = 6B4BF998-668B-46D3-BCFA-971F99F8708C
|
||||
PLATFORM_VERSION = 0.1
|
||||
DSC_SPECIFICATION = 0x00010005
|
||||
SUPPORTED_ARCHITECTURES = IA32|X64|EBC|ARM|AARCH64|RISCV64
|
||||
OUTPUT_DIRECTORY = Build/Ext4Pkg
|
||||
BUILD_TARGETS = DEBUG|RELEASE|NOOPT
|
||||
SKUID_IDENTIFIER = DEFAULT
|
||||
|
||||
!include MdePkg/MdeLibs.dsc.inc
|
||||
|
||||
[BuildOptions]
|
||||
*_*_*_CC_FLAGS = -D DISABLE_NEW_DEPRECATED_INTERFACES
|
||||
|
||||
[LibraryClasses]
|
||||
#
|
||||
# Entry Point Libraries
|
||||
#
|
||||
UefiDriverEntryPoint|MdePkg/Library/UefiDriverEntryPoint/UefiDriverEntryPoint.inf
|
||||
#
|
||||
# Common Libraries
|
||||
#
|
||||
BaseLib|MdePkg/Library/BaseLib/BaseLib.inf
|
||||
BaseMemoryLib|MdePkg/Library/BaseMemoryLib/BaseMemoryLib.inf
|
||||
UefiLib|MdePkg/Library/UefiLib/UefiLib.inf
|
||||
PrintLib|MdePkg/Library/BasePrintLib/BasePrintLib.inf
|
||||
PcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.inf
|
||||
MemoryAllocationLib|MdePkg/Library/UefiMemoryAllocationLib/UefiMemoryAllocationLib.inf
|
||||
UefiBootServicesTableLib|MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.inf
|
||||
UefiRuntimeServicesTableLib|MdePkg/Library/UefiRuntimeServicesTableLib/UefiRuntimeServicesTableLib.inf
|
||||
DebugLib|MdePkg/Library/BaseDebugLibNull/BaseDebugLibNull.inf
|
||||
DebugPrintErrorLevelLib|MdePkg/Library/BaseDebugPrintErrorLevelLib/BaseDebugPrintErrorLevelLib.inf
|
||||
DevicePathLib|MdePkg/Library/UefiDevicePathLib/UefiDevicePathLib.inf
|
||||
OrderedCollectionLib|MdePkg/Library/BaseOrderedCollectionRedBlackTreeLib/BaseOrderedCollectionRedBlackTreeLib.inf
|
||||
BaseUcs2Utf8Lib|RedfishPkg/Library/BaseUcs2Utf8Lib/BaseUcs2Utf8Lib.inf
|
||||
|
||||
#
|
||||
# Required for stack protector support
|
||||
#
|
||||
NULL|MdePkg/Library/BaseStackCheckLib/BaseStackCheckLib.inf
|
||||
|
||||
###################################################################################################
|
||||
#
|
||||
# Components Section - list of the modules and components that will be processed by compilation
|
||||
# tools and the EDK II tools to generate PE32/PE32+/Coff image files.
|
||||
#
|
||||
# Note: The EDK II DSC file is not used to specify how compiled binary images get placed
|
||||
# into firmware volume images. This section is just a list of modules to compile from
|
||||
# source into UEFI-compliant binaries.
|
||||
# It is the FDF file that contains information on combining binary files into firmware
|
||||
# volume images, whose concept is beyond UEFI and is described in PI specification.
|
||||
# Binary modules do not need to be listed in this section, as they should be
|
||||
# specified in the FDF file. For example: Shell binary (Shell_Full.efi), FAT binary (Fat.efi),
|
||||
# Logo (Logo.bmp), and etc.
|
||||
# There may also be modules listed in this section that are not required in the FDF file,
|
||||
# When a module listed here is excluded from FDF file, then UEFI-compliant binary will be
|
||||
# generated for it, but the binary will not be put into any firmware volume.
|
||||
#
|
||||
###################################################################################################
|
||||
|
||||
[Components]
|
||||
Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
|
|
@ -0,0 +1,14 @@
|
|||
## @file
|
||||
# Ext4 Package
|
||||
#
|
||||
# This package provides libraries and drivers related to the ext4 filesystem implementation.
|
||||
# More details are available at: https://www.kernel.org/doc/html/v5.4/filesystems/ext4/index.html
|
||||
#
|
||||
# Copyright (c) 2021 Pedro Falcato
|
||||
# SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
#
|
||||
##
|
||||
|
||||
#string STR_PACKAGE_ABSTRACT #language en-US "Module implementations for the EXT4 file system"
|
||||
|
||||
#string STR_PACKAGE_DESCRIPTION #language en-US "This package contains UEFI drivers and libraries for the EXT4 file system."
|
Loading…
Reference in New Issue