CloverBootloader/Ext4Pkg/Ext4Dxe/Inode.c

491 lines
14 KiB
C

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