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