mirror of
https://github.com/CloverHackyColor/CloverBootloader.git
synced 2024-12-01 12:53:27 +01:00
686 lines
18 KiB
C
686 lines
18 KiB
C
|
/** @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.
|
||
|
|
||
|
@return Result of the caching
|
||
|
**/
|
||
|
STATIC
|
||
|
EFI_STATUS
|
||
|
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;
|
||
|
UINT16 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)) {
|
||
|
Status = Ext4CacheExtents (File, Extent, 1);
|
||
|
|
||
|
if (EFI_ERROR (Status) && (Status != EFI_OUT_OF_RESOURCES)) {
|
||
|
return Status;
|
||
|
}
|
||
|
|
||
|
Status = EFI_SUCCESS;
|
||
|
}
|
||
|
|
||
|
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 = (UINT16)((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.
|
||
|
**/
|
||
|
Status = Ext4CacheExtents (File, (EXT4_EXTENT *)(ExtHeader + 1), ExtHeader->eh_entries);
|
||
|
|
||
|
if (EFI_ERROR (Status) && (Status != EFI_OUT_OF_RESOURCES)) {
|
||
|
if (Buffer != NULL) {
|
||
|
FreePool (Buffer);
|
||
|
}
|
||
|
|
||
|
return Status;
|
||
|
}
|
||
|
|
||
|
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.
|
||
|
|
||
|
@return Result of the caching
|
||
|
**/
|
||
|
STATIC
|
||
|
EFI_STATUS
|
||
|
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++) {
|
||
|
if (Extents->ee_len == 0) {
|
||
|
// 0-sized extent, must be corruption
|
||
|
return EFI_VOLUME_CORRUPTED;
|
||
|
}
|
||
|
|
||
|
Extent = AllocatePool (sizeof (EXT4_EXTENT));
|
||
|
|
||
|
if (Extent == NULL) {
|
||
|
return EFI_OUT_OF_RESOURCES;
|
||
|
}
|
||
|
|
||
|
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 EFI_SUCCESS;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return EFI_SUCCESS;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
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;
|
||
|
}
|