CloverBootloader/Ext4Pkg/Ext4Dxe/BlockMap.c

286 lines
8.5 KiB
C

/** @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;
}