[edk2-devel] [Patch 2/3] Ext4Pkg: Add Ext4Dxe driver.

Pedro Falcato pedro.falcato at gmail.com
Mon Aug 2 15:56:27 UTC 2021


Hi Marvin,

Thanks for the quick feedback! Most of your criticism seems valid.
I'll take it into consideration for v2 of the patch-set.

As for the nitpick in Collation.c, while I do understand what you mean
by that, large parts of that file were mostly borrowed from FatPkg
(such that I kept the original Intel copyright header); I think
separating that code into a library of some sorts would be a good
idea, I was completely lost when
trying to figure out all the tiny Unicode/language details that don't
seem to be documented anywhere (I think!). As for the
EFI_OPEN_PROTOCOL_GET_PROTOCOL,
I should have RTFM'd harder :)

Best regards,

Pedro

On Fri, 30 Jul 2021 at 21:28, Marvin Häuser <mhaeuser at posteo.de> wrote:
>
> Hey Pedro,
>
> Sorry, I went through this in a great rush, but I wanted to get some
> feedback out asap to have enough time for discussion till end of GSoC.
> Some comments are inline.
>
> Best regards,
> Marvin
>
> On 30.07.21 18:16, Pedro Falcato wrote:
> > Adds a UEFI EXT4 filesystem driver that implements the EFI_FILE_PROTOCOL
> > and EFI_SIMPLE_FILE_SYSTEM_PROTOCOL.
> >
> > Cc: Leif Lindholm <leif at nuviainc.com>
> > Cc: Michael D Kinney <michael.d.kinney at intel.com>
> > Cc: Bret Barkelew <Bret.Barkelew at microsoft.com>
> >
> > Signed-off-by: Pedro Falcato <pedro.falcato at gmail.com>
> > ---
> >   Features/Ext4Pkg/Ext4Dxe/BlockGroup.c | 208 ++++++
> >   Features/Ext4Pkg/Ext4Dxe/Collation.c  | 157 +++++
> >   Features/Ext4Pkg/Ext4Dxe/Crc16.c      |  75 ++
> >   Features/Ext4Pkg/Ext4Dxe/Crc32c.c     |  84 +++
> >   Features/Ext4Pkg/Ext4Dxe/Directory.c  | 492 ++++++++++++++
> >   Features/Ext4Pkg/Ext4Dxe/DiskUtil.c   |  83 +++
> >   Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h   | 450 ++++++++++++
> >   Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.c    | 454 +++++++++++++
> >   Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h    | 942 ++++++++++++++++++++++++++
> >   Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf  | 147 ++++
> >   Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni  |  15 +
> >   Features/Ext4Pkg/Ext4Dxe/Extents.c    | 616 +++++++++++++++++
> >   Features/Ext4Pkg/Ext4Dxe/File.c       | 583 ++++++++++++++++
> >   Features/Ext4Pkg/Ext4Dxe/Inode.c      | 468 +++++++++++++
> >   Features/Ext4Pkg/Ext4Dxe/Partition.c  | 120 ++++
> >   Features/Ext4Pkg/Ext4Dxe/Superblock.c | 257 +++++++
> >   16 files changed, 5151 insertions(+)
> >   create mode 100644 Features/Ext4Pkg/Ext4Dxe/BlockGroup.c
> >   create mode 100644 Features/Ext4Pkg/Ext4Dxe/Collation.c
> >   create mode 100644 Features/Ext4Pkg/Ext4Dxe/Crc16.c
> >   create mode 100644 Features/Ext4Pkg/Ext4Dxe/Crc32c.c
> >   create mode 100644 Features/Ext4Pkg/Ext4Dxe/Directory.c
> >   create mode 100644 Features/Ext4Pkg/Ext4Dxe/DiskUtil.c
> >   create mode 100644 Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
> >   create mode 100644 Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.c
> >   create mode 100644 Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
> >   create mode 100644 Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
> >   create mode 100644 Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni
> >   create mode 100644 Features/Ext4Pkg/Ext4Dxe/Extents.c
> >   create mode 100644 Features/Ext4Pkg/Ext4Dxe/File.c
> >   create mode 100644 Features/Ext4Pkg/Ext4Dxe/Inode.c
> >   create mode 100644 Features/Ext4Pkg/Ext4Dxe/Partition.c
> >   create mode 100644 Features/Ext4Pkg/Ext4Dxe/Superblock.c
> >
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/BlockGroup.c b/Features/Ext4Pkg/Ext4Dxe/BlockGroup.c
> > new file mode 100644
> > index 0000000000..10a82d40a0
> > --- /dev/null
> > +++ b/Features/Ext4Pkg/Ext4Dxe/BlockGroup.c
> > @@ -0,0 +1,208 @@
> > +/**
> > +  @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"
> > +
> > +/**
> > +   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;
> > +
> > +  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 we add write support
> > +
> > +  InodeTableStart = Ext4MakeBlockNumberFromHalfs (
> > +                      Partition,
> > +                      BlockGroup->bg_inode_table_lo,
> > +                      BlockGroup->bg_inode_table_hi
> > +                      );
> > +
> > +  Status = Ext4ReadDiskIo (
> > +             Partition,
> > +             Inode,
> > +             Partition->InodeSize,
> > +             Ext4BlockToByteOffset (Partition, InodeTableStart) + InodeOffset * Partition->InodeSize
> > +             );
> > +
> > +  if (EFI_ERROR (Status)) {
> > +    DEBUG ((
> > +      EFI_D_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 ((
> > +      EFI_D_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;
> > +  UINT16  Dummy;
> > +
> > +  Dummy = 0;
> > +
> > +  Csum = CalculateCrc16 (Partition->SuperBlock.s_uuid, 16, 0);
> > +  Csum = CalculateCrc16 (&BlockGroupNum, sizeof (BlockGroupNum), Csum);
> > +  Csum = CalculateCrc16 (BlockGroupDesc, OFFSET_OF (EXT4_BLOCK_GROUP_DESC, bg_checksum), Csum);
> > +  Csum = CalculateCrc16 (&Dummy, sizeof (Dummy), Csum);
> > +  Csum =
> > +    CalculateCrc16 (
> > +      &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(!Ext4HasMetadataCsum (Partition) && !Ext4HasGdtCsum (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(Partition->FeaturesRoCompat & EXT4_FEATURE_RO_COMPAT_METADATA_CSUM) {
> > +    return Ext4CalculateBlockGroupDescChecksumMetadataCsum (Partition, BlockGroupDesc, BlockGroupNum);
> > +  } else if(Partition->FeaturesRoCompat & EXT4_FEATURE_RO_COMPAT_GDT_CSUM) {
> > +    return Ext4CalculateBlockGroupDescChecksumGdtCsum (Partition, BlockGroupDesc, BlockGroupNum);
> > +  }
> > +
> > +  return 0;
> > +}
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/Collation.c b/Features/Ext4Pkg/Ext4Dxe/Collation.c
> > new file mode 100644
> > index 0000000000..92d6a9184b
> > --- /dev/null
> > +++ b/Features/Ext4Pkg/Ext4Dxe/Collation.c
> > @@ -0,0 +1,157 @@
> > +/**
> > +  @file Unicode collation routines
> > +
> > +  Copyright (c) 2021 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/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.
> > + */
> > +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;
> > +
> > +  Iso639Language = (BOOLEAN)(ProtocolGuid == &gEfiUnicodeCollationProtocolGuid);
> > +  RetStatus = EFI_UNSUPPORTED;
> > +  GetEfiGlobalVariable2 (VariableName, (VOID **)&Language, NULL);
>
> I know that this is done across EDK II drivers, however please do not
> discard return results and rely on the function initialising all
> parameters. C code traditionally is driven by returned results, i.e. the
> function succeeded if and only if the returned status indicates success.
> There are many reasons not to rely on this behaviour, most importantly:
> 1) A future library class instance may not unconditionally initialise
> the argument pointers. The function documentation has no mention of this
> behaviour, and it is awkward.
> 2) The callee may pass the parameters down to other functions and
> accidentally initialise them with non-trivial values, making it look
> like the function has succeeded when not considering the returned status.
> 3) This is basically 2.1, the functions must all follow this pattern
> down the call tree (or workarounds need to be implemented), which wastes
> resources.
>
> > +
> > +  Status = gBS->LocateHandleBuffer (
> > +                  ByProtocol,
> > +                  ProtocolGuid,
> > +                  NULL,
> > +                  &NumHandles,
> > +                  &Handles
> > +                  );
> > +  if (EFI_ERROR (Status)) {
> > +    return Status;
> > +  }
> > +
> > +  // Note: FatPkg also doesn't close unneeded protocols.
> > +  // This looks like a leak but I'm likely wrong.
>
> I'm not 100 % sure what you mean here, but in case you refer to below's
> code, protocols retrieved by EFI_OPEN_PROTOCOL_GET_PROTOCOL do not need
> to be closed as per UEFI 2.9, page 194.
> > +  for(UINTN i = 0; i < NumHandles; i++) {
> > +    Status = gBS->OpenProtocol (
> > +                    Handles[i],
> > +                    ProtocolGuid,
> > +                    (VOID **)&Uci,
> > +                    DriverHandle,
> > +                    NULL,
> > +                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
> > +                    );
> > +
> > +    if(EFI_ERROR (Status)) {
> > +      continue;
> > +    }
> > +
> > +    CHAR8  *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;
> > +
> > +  //
> > +  // 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
> > +  )
> > +{
> > +  return gUnicodeCollationInterface->StriColl (gUnicodeCollationInterface, Str1, Str2);
> > +}
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/Crc16.c b/Features/Ext4Pkg/Ext4Dxe/Crc16.c
> > new file mode 100644
> > index 0000000000..25a11cfde3
> > --- /dev/null
> > +++ b/Features/Ext4Pkg/Ext4Dxe/Crc16.c
> > @@ -0,0 +1,75 @@
> > +/**
> > +  @file CRC16 calculation routines.
> > +
> > +  Copyright (c) 2021 Pedro Falcato All rights reserved.
> > +
> > +  SPDX-License-Identifier: BSD-2-Clause-Patent
> > + */
> > +
> > +#include <Uefi.h>
> > +
> > +STATIC CONST UINT16  gCrc16LookupTable[256] =
> > +{
> > +  0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
> > +  0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
> > +  0x0919, 0x1890, 0x2a0b, 0x3b82, 0x4f3d, 0x5eb4, 0x6c2f, 0x7da6,
> > +  0x8551, 0x94d8, 0xa643, 0xb7ca, 0xc375, 0xd2fc, 0xe067, 0xf1ee,
> > +  0x1232, 0x03bb, 0x3120, 0x20a9, 0x5416, 0x459f, 0x7704, 0x668d,
> > +  0x9e7a, 0x8ff3, 0xbd68, 0xace1, 0xd85e, 0xc9d7, 0xfb4c, 0xeac5,
> > +  0x1b2b, 0x0aa2, 0x3839, 0x29b0, 0x5d0f, 0x4c86, 0x7e1d, 0x6f94,
> > +  0x9763, 0x86ea, 0xb471, 0xa5f8, 0xd147, 0xc0ce, 0xf255, 0xe3dc,
> > +  0x2464, 0x35ed, 0x0776, 0x16ff, 0x6240, 0x73c9, 0x4152, 0x50db,
> > +  0xa82c, 0xb9a5, 0x8b3e, 0x9ab7, 0xee08, 0xff81, 0xcd1a, 0xdc93,
> > +  0x2d7d, 0x3cf4, 0x0e6f, 0x1fe6, 0x6b59, 0x7ad0, 0x484b, 0x59c2,
> > +  0xa135, 0xb0bc, 0x8227, 0x93ae, 0xe711, 0xf698, 0xc403, 0xd58a,
> > +  0x3656, 0x27df, 0x1544, 0x04cd, 0x7072, 0x61fb, 0x5360, 0x42e9,
> > +  0xba1e, 0xab97, 0x990c, 0x8885, 0xfc3a, 0xedb3, 0xdf28, 0xcea1,
> > +  0x3f4f, 0x2ec6, 0x1c5d, 0x0dd4, 0x796b, 0x68e2, 0x5a79, 0x4bf0,
> > +  0xb307, 0xa28e, 0x9015, 0x819c, 0xf523, 0xe4aa, 0xd631, 0xc7b8,
> > +  0x48c8, 0x5941, 0x6bda, 0x7a53, 0x0eec, 0x1f65, 0x2dfe, 0x3c77,
> > +  0xc480, 0xd509, 0xe792, 0xf61b, 0x82a4, 0x932d, 0xa1b6, 0xb03f,
> > +  0x41d1, 0x5058, 0x62c3, 0x734a, 0x07f5, 0x167c, 0x24e7, 0x356e,
> > +  0xcd99, 0xdc10, 0xee8b, 0xff02, 0x8bbd, 0x9a34, 0xa8af, 0xb926,
> > +  0x5afa, 0x4b73, 0x79e8, 0x6861, 0x1cde, 0x0d57, 0x3fcc, 0x2e45,
> > +  0xd6b2, 0xc73b, 0xf5a0, 0xe429, 0x9096, 0x811f, 0xb384, 0xa20d,
> > +  0x53e3, 0x426a, 0x70f1, 0x6178, 0x15c7, 0x044e, 0x36d5, 0x275c,
> > +  0xdfab, 0xce22, 0xfcb9, 0xed30, 0x998f, 0x8806, 0xba9d, 0xab14,
> > +  0x6cac, 0x7d25, 0x4fbe, 0x5e37, 0x2a88, 0x3b01, 0x099a, 0x1813,
> > +  0xe0e4, 0xf16d, 0xc3f6, 0xd27f, 0xa6c0, 0xb749, 0x85d2, 0x945b,
> > +  0x65b5, 0x743c, 0x46a7, 0x572e, 0x2391, 0x3218, 0x0083, 0x110a,
> > +  0xe9fd, 0xf874, 0xcaef, 0xdb66, 0xafd9, 0xbe50, 0x8ccb, 0x9d42,
> > +  0x7e9e, 0x6f17, 0x5d8c, 0x4c05, 0x38ba, 0x2933, 0x1ba8, 0x0a21,
> > +  0xf2d6, 0xe35f, 0xd1c4, 0xc04d, 0xb4f2, 0xa57b, 0x97e0, 0x8669,
> > +  0x7787, 0x660e, 0x5495, 0x451c, 0x31a3, 0x202a, 0x12b1, 0x0338,
> > +  0xfbcf, 0xea46, 0xd8dd, 0xc954, 0xbdeb, 0xac62, 0x9ef9, 0x8f70
> > +};
> > +
> > +/**
> > +   Calculates the CRC16 checksum of the given buffer.
> > +
> > +   @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 CRC16 checksum.
> > +*/
> > +UINT16
> > +CalculateCrc16 (
> > +  IN CONST VOID *Buffer,
> > +  IN UINTN Length,
> > +  IN UINT16 InitialValue
> > +  )
> > +{
> > +  CONST UINT8  *Buf;
> > +  UINT16       Crc;
> > +
> > +  Buf = Buffer;
> > +
> > +  Crc = ~InitialValue;
> > +
> > +  while(Length-- != 0) {
> > +    Crc = gCrc16LookupTable[(Crc & 0xFF) ^ *(Buf++)] ^ (Crc >> 8);
> > +  }
> > +
> > +  return ~Crc;
> > +}
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/Crc32c.c b/Features/Ext4Pkg/Ext4Dxe/Crc32c.c
> > new file mode 100644
> > index 0000000000..3986c9b10f
> > --- /dev/null
> > +++ b/Features/Ext4Pkg/Ext4Dxe/Crc32c.c
> > @@ -0,0 +1,84 @@
> > +/**
> > +  @file CRC32c calculation routines.
> > +
> > +  Copyright (c) 2021 Pedro Falcato All rights reserved.
> > +
> > +  SPDX-License-Identifier: BSD-2-Clause-Patent
> > + */
> > +
> > +#include <Uefi.h>
> > +
> > +STATIC CONST UINT32  gCrc32cLookupTable[256] = {
> > +  0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4, 0xc79a971f, 0x35f1141c,
> > +  0x26a1e7e8, 0xd4ca64eb, 0x8ad958cf, 0x78b2dbcc, 0x6be22838, 0x9989ab3b,
> > +  0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24, 0x105ec76f, 0xe235446c,
> > +  0xf165b798, 0x030e349b, 0xd7c45070, 0x25afd373, 0x36ff2087, 0xc494a384,
> > +  0x9a879fa0, 0x68ec1ca3, 0x7bbcef57, 0x89d76c54, 0x5d1d08bf, 0xaf768bbc,
> > +  0xbc267848, 0x4e4dfb4b, 0x20bd8ede, 0xd2d60ddd, 0xc186fe29, 0x33ed7d2a,
> > +  0xe72719c1, 0x154c9ac2, 0x061c6936, 0xf477ea35, 0xaa64d611, 0x580f5512,
> > +  0x4b5fa6e6, 0xb93425e5, 0x6dfe410e, 0x9f95c20d, 0x8cc531f9, 0x7eaeb2fa,
> > +  0x30e349b1, 0xc288cab2, 0xd1d83946, 0x23b3ba45, 0xf779deae, 0x05125dad,
> > +  0x1642ae59, 0xe4292d5a, 0xba3a117e, 0x4851927d, 0x5b016189, 0xa96ae28a,
> > +  0x7da08661, 0x8fcb0562, 0x9c9bf696, 0x6ef07595, 0x417b1dbc, 0xb3109ebf,
> > +  0xa0406d4b, 0x522bee48, 0x86e18aa3, 0x748a09a0, 0x67dafa54, 0x95b17957,
> > +  0xcba24573, 0x39c9c670, 0x2a993584, 0xd8f2b687, 0x0c38d26c, 0xfe53516f,
> > +  0xed03a29b, 0x1f682198, 0x5125dad3, 0xa34e59d0, 0xb01eaa24, 0x42752927,
> > +  0x96bf4dcc, 0x64d4cecf, 0x77843d3b, 0x85efbe38, 0xdbfc821c, 0x2997011f,
> > +  0x3ac7f2eb, 0xc8ac71e8, 0x1c661503, 0xee0d9600, 0xfd5d65f4, 0x0f36e6f7,
> > +  0x61c69362, 0x93ad1061, 0x80fde395, 0x72966096, 0xa65c047d, 0x5437877e,
> > +  0x4767748a, 0xb50cf789, 0xeb1fcbad, 0x197448ae, 0x0a24bb5a, 0xf84f3859,
> > +  0x2c855cb2, 0xdeeedfb1, 0xcdbe2c45, 0x3fd5af46, 0x7198540d, 0x83f3d70e,
> > +  0x90a324fa, 0x62c8a7f9, 0xb602c312, 0x44694011, 0x5739b3e5, 0xa55230e6,
> > +  0xfb410cc2, 0x092a8fc1, 0x1a7a7c35, 0xe811ff36, 0x3cdb9bdd, 0xceb018de,
> > +  0xdde0eb2a, 0x2f8b6829, 0x82f63b78, 0x709db87b, 0x63cd4b8f, 0x91a6c88c,
> > +  0x456cac67, 0xb7072f64, 0xa457dc90, 0x563c5f93, 0x082f63b7, 0xfa44e0b4,
> > +  0xe9141340, 0x1b7f9043, 0xcfb5f4a8, 0x3dde77ab, 0x2e8e845f, 0xdce5075c,
> > +  0x92a8fc17, 0x60c37f14, 0x73938ce0, 0x81f80fe3, 0x55326b08, 0xa759e80b,
> > +  0xb4091bff, 0x466298fc, 0x1871a4d8, 0xea1a27db, 0xf94ad42f, 0x0b21572c,
> > +  0xdfeb33c7, 0x2d80b0c4, 0x3ed04330, 0xccbbc033, 0xa24bb5a6, 0x502036a5,
> > +  0x4370c551, 0xb11b4652, 0x65d122b9, 0x97baa1ba, 0x84ea524e, 0x7681d14d,
> > +  0x2892ed69, 0xdaf96e6a, 0xc9a99d9e, 0x3bc21e9d, 0xef087a76, 0x1d63f975,
> > +  0x0e330a81, 0xfc588982, 0xb21572c9, 0x407ef1ca, 0x532e023e, 0xa145813d,
> > +  0x758fe5d6, 0x87e466d5, 0x94b49521, 0x66df1622, 0x38cc2a06, 0xcaa7a905,
> > +  0xd9f75af1, 0x2b9cd9f2, 0xff56bd19, 0x0d3d3e1a, 0x1e6dcdee, 0xec064eed,
> > +  0xc38d26c4, 0x31e6a5c7, 0x22b65633, 0xd0ddd530, 0x0417b1db, 0xf67c32d8,
> > +  0xe52cc12c, 0x1747422f, 0x49547e0b, 0xbb3ffd08, 0xa86f0efc, 0x5a048dff,
> > +  0x8ecee914, 0x7ca56a17, 0x6ff599e3, 0x9d9e1ae0, 0xd3d3e1ab, 0x21b862a8,
> > +  0x32e8915c, 0xc083125f, 0x144976b4, 0xe622f5b7, 0xf5720643, 0x07198540,
> > +  0x590ab964, 0xab613a67, 0xb831c993, 0x4a5a4a90, 0x9e902e7b, 0x6cfbad78,
> > +  0x7fab5e8c, 0x8dc0dd8f, 0xe330a81a, 0x115b2b19, 0x020bd8ed, 0xf0605bee,
> > +  0x24aa3f05, 0xd6c1bc06, 0xc5914ff2, 0x37faccf1, 0x69e9f0d5, 0x9b8273d6,
> > +  0x88d28022, 0x7ab90321, 0xae7367ca, 0x5c18e4c9, 0x4f48173d, 0xbd23943e,
> > +  0xf36e6f75, 0x0105ec76, 0x12551f82, 0xe03e9c81, 0x34f4f86a, 0xc69f7b69,
> > +  0xd5cf889d, 0x27a40b9e, 0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e,
> > +  0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351
> > +};
> > +
> > +/**
> > +   Calculates the CRC32c checksum of the given buffer.
> > +
> > +   @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 CRC32c checksum.
> > +*/
> > +UINT32
> > +CalculateCrc32c (
> > +  IN CONST VOID *Buffer,
> > +  IN UINTN Length,
> > +  IN UINT32 InitialValue
> > +  )
> > +{
> > +  CONST UINT8  *Buf;
> > +  UINT32       Crc;
> > +
> > +  Buf = Buffer;
> > +  Crc = ~InitialValue;
> > +
> > +  while(Length-- != 0) {
> > +    Crc = gCrc32cLookupTable[(Crc & 0xFF) ^ *(Buf++)] ^ (Crc >> 8);
> > +  }
> > +
> > +  return ~Crc;
> > +}
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/Directory.c b/Features/Ext4Pkg/Ext4Dxe/Directory.c
> > new file mode 100644
> > index 0000000000..caa97cf9f1
> > --- /dev/null
> > +++ b/Features/Ext4Pkg/Ext4Dxe/Directory.c
> > @@ -0,0 +1,492 @@
> > +/**
> > +  @file Directory related routines
> > +
> > +  Copyright (c) 2021 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 succesfully retrieved and converted to UCS2.
> > +   @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;
> > +  EFI_STATUS  Status;
> > +
> > +  CopyMem (Utf8NameBuf, Entry->name, Entry->name_len);
> > +
> > +  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.
>
> Maybe that's a good idea to propose then? Dynamic memory allocations are
> never nice. :)
>
> > +  Status = UTF8StrToUCS2 (Utf8NameBuf, &Str);
> > +
> > +  if (EFI_ERROR (Status)) {
> > +    return Status;
> > +  }
> > +
> > +  Status = StrCpyS (Ucs2FileName, EXT4_NAME_MAX + 1, Str);
> > +
> > +  FreePool (Str);
> > +
> > +  return Status;
> > +}
> > +
> > +/**
> > +   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 *File,
> > +  IN CONST CHAR16 *Name,
> > +  IN EXT4_PARTITION *Partition,
> > +  OUT EXT4_DIR_ENTRY *res
> > +  )
> > +{
> > +  EFI_STATUS  Status;
> > +  CHAR8       *Buf;
> > +  UINT64      Off;
> > +  EXT4_INODE  *Inode;
> > +  UINT64      DirInoSize;
> > +  UINT32      BlockRemainder;
> > +
> > +  Status = EFI_NOT_FOUND;
> > +  Buf    = AllocatePool (Partition->BlockSize);
> > +
> > +  if(Buf == NULL) {
> > +    return EFI_OUT_OF_RESOURCES;
> > +  }
> > +
> > +  Off = 0;
> > +
> > +  Inode = File->Inode;
> > +  DirInoSize = EXT4_INODE_SIZE (Inode);
> > +
> > +  DivU64x32Remainder (DirInoSize, Partition->BlockSize, &BlockRemainder);
> > +  if(BlockRemainder != 0) {
> > +    // Directory inodes need to have block aligned sizes
> > +    return EFI_VOLUME_CORRUPTED;
> > +  }
> > +
> > +  while(Off < DirInoSize) {
> > +    UINTN  Length;
>
> Variable not declared at function entry.
>
> > +
> > +    Length = Partition->BlockSize;
> > +
> > +    Status = Ext4Read (Partition, File, Buf, Off, &Length);
> > +
> > +    if (Status != EFI_SUCCESS) {
> > +      FreePool (Buf);
> > +      return Status;
> > +    }
> > +
> > +    for(CHAR8 *b = Buf; b < Buf + Partition->BlockSize; ) {
> > +      EXT4_DIR_ENTRY  *Entry;
> > +      UINTN           RemainingBlock;
> > +
> > +      Entry = (EXT4_DIR_ENTRY *)b;
> > +      ASSERT (Entry->rec_len != 0);
> > +
> > +      RemainingBlock = Partition->BlockSize - (b - Buf);
>
> I think this would be easier to read and verify if the loop variant as
> RemainingBlock.
>
> > +
> > +      if(Entry->name_len > RemainingBlock || Entry->rec_len > RemainingBlock) {
> > +        // Corrupted filesystem
> > +        // TODO: Do the proper ext4 corruption detection thing and dirty the filesystem.
> > +        FreePool (Buf);
> > +        return EFI_VOLUME_CORRUPTED;
> > +      }
> > +
> > +      // Ignore names bigger than our limit.
> > +
> > +      /* Note: I think having a limit is sane because:
> > +        1) It's nicer to work with.
>
> When in doubt, say it's for security. :) An upper limit gives you a
> definite guarantee you need to (and can) test your code against, you can
> avoid dynamic memory allocations and their classes of potential
> exploits, etc...
> Also, I'm not sure C++ comments are permitted in function bodies, at
> least I have not seen them.
>
> > +        2) Linux and a number of BSDs also have a filename limit of 255.
> > +      */
> > +      if(Entry->name_len > EXT4_NAME_MAX) {
> > +        continue;
> > +      }
> > +
> > +      // Unused entry
> > +      if(Entry->inode == 0) {
> > +        b += Entry->rec_len;
> > +        continue;
> > +      }
> > +
> > +      CHAR16  Ucs2FileName[EXT4_NAME_MAX + 1];
>
> I have seen this type a lot now, maybe it'd be a good idea to typedef it?
>
> > +
> > +      Status = Ext4GetUcs2DirentName (Entry, Ucs2FileName);
> > +
> > +      /* 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 we error out, skip this entry
> > +        // I'm not sure if this is correct behaviour, but I don't think there's a precedent here.
> > +        b += Entry->rec_len;
> > +        continue;
> > +      }
> > +
> > +      if (Entry->name_len == StrLen (Name) &&
> > +          !Ext4StrCmpInsensitive (Ucs2FileName, (CHAR16 *)Name)) {
> > +        UINTN  ToCopy;
> > +
> > +        ToCopy = Entry->rec_len > sizeof (EXT4_DIR_ENTRY) ?
> > +                 sizeof (EXT4_DIR_ENTRY) :
> > +                 Entry->rec_len;
>
> Please use MIN().
>
> > +
> > +        CopyMem (res, Entry, ToCopy);
> > +        FreePool (Buf);
> > +        return EFI_SUCCESS;
> > +      }
> > +
> > +      b += Entry->rec_len;
> > +    }
> > +
> > +    Off += Partition->BlockSize;
> > +  }
> > +
> > +  FreePool (Buf);
> > +  return EFI_NOT_FOUND;
> > +}
> > +
> > +/**
> > +   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.
> > +
> > +   @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
> > +  )
> > +{
> > +  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;
> > +  }
> > +
> > +  File->FileName = AllocateZeroPool (StrSize (FileName));
> > +
> > +  if (!File->FileName) {
> > +    Status = EFI_OUT_OF_RESOURCES;
> > +    goto Error;
> > +  }
> > +
> > +  Status = Ext4InitExtentsMap (File);
> > +
> > +  if (EFI_ERROR (Status)) {
> > +    goto Error;
> > +  }
> > +
> > +  // This should not fail.
> > +  StrCpyS (File->FileName, EXT4_NAME_MAX + 1, FileName);
>
> Maybe use ARRAY_SIZE to avoid size constants?
>
> > +
> > +  File->InodeNum = Entry->inode;
> > +
> > +  Ext4SetupFile (File, (EXT4_PARTITION *)Partition);
>
> Is Partition not already this type?
>
> > +
> > +  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->FileName != NULL) {
> > +      FreePool (File->FileName);
> > +    }
> > +
> > +    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);
> > +}
> > +
> > +EFI_STATUS EFIAPI
> > +Ext4OpenVolume (
> > +  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Partition, EFI_FILE_PROTOCOL **Root
> > +  )
> > +{
> > +  EXT4_INODE  *RootInode;
> > +  EFI_STATUS  Status;
> > +
> > +  Status = Ext4ReadInode ((EXT4_PARTITION *)Partition, 2, &RootInode);
> > +
> > +  if(EFI_ERROR (Status)) {
> > +    DEBUG ((EFI_D_ERROR, "[ext4] Could not open root inode - status %x\n", Status));
> > +    return Status;
> > +  }
> > +
> > +  EXT4_FILE  *RootDir = AllocateZeroPool (sizeof (EXT4_FILE));
> > +
> > +  if(!RootDir) {
> > +    FreePool (RootInode);
> > +    return EFI_OUT_OF_RESOURCES;
> > +  }
> > +
> > +  // The filename will be "\"(null terminated of course)
> > +  RootDir->FileName = AllocateZeroPool (2 * sizeof (CHAR16));
> > +
> > +  if (!RootDir->FileName) {
> > +    FreePool (RootDir);
> > +    FreePool (RootInode);
> > +    return EFI_OUT_OF_RESOURCES;
> > +  }
> > +
> > +  RootDir->FileName[0] = L'\\';
> > +
> > +  RootDir->Inode    = RootInode;
> > +  RootDir->InodeNum = 2;
> > +
> > +  if (EFI_ERROR (Ext4InitExtentsMap (RootDir))) {
>
> I'm not sure it's allowed to call functions in such expressions, but
> if-clauses with side-effects are generally error-prone and hard to read
> in my opinion.
>
> > +    FreePool (RootDir->FileName);
> > +    FreePool (RootInode);
> > +    FreePool (RootDir);
> > +    return EFI_OUT_OF_RESOURCES;
> > +  }
> > +
> > +  Ext4SetupFile (RootDir, (EXT4_PARTITION *)Partition);
> > +  *Root = &RootDir->Protocol;
> > +
> > +  InsertTailList (&((EXT4_PARTITION *)Partition)->OpenFiles, &RootDir->OpenFilesListNode);
>
> Casting like this is unusual in EDK II, usually the private part of your
> structure would start with a Signature and you would use CR() to get a
> pointer to the private part.
>
> > +
> > +  return EFI_SUCCESS;
> > +}
> > +
> > +/**
> > +   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 = Dirent->name_len + EXT4_MIN_DIR_ENTRY_LEN;
> > +
> > +  if (Dirent->rec_len < RequiredSize) {
> > +    DEBUG ((EFI_D_ERROR, "[ext4] dirent size %lu too small (compared to %lu)\n", Dirent->rec_len, RequiredSize));
>
> I *think* "EFI_D_" is considered legacy in favour of "DEBUG_", but not
> 100 % sure.
>
> > +    return FALSE;
> > +  }
> > +
> > +  // Dirent sizes need to be 4 byte aligned
> > +  if (Dirent->rec_len % 4) {
>
> The offset is used for casts to EXT4_DIR_ENTRY, maybe add a
> STATIC_ASSERT the alignment is sufficient? There is no macro for type
> alignment yet, but I will submit it soon, so maybe add a TODO if you agree.
>
> > +    return FALSE;
> > +  }
> > +
> > +  return TRUE;
> > +}
> > +
> > +/**
> > +   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
> > +  )
> > +{
> > +  DEBUG ((EFI_D_INFO, "[ext4] Ext4ReadDir offset %lu\n", Offset));
> > +  EXT4_INODE      *DirIno;
> > +  EFI_STATUS      Status;
> > +  UINT64          DirInoSize;
> > +  UINTN           Len;
> > +  UINT32          BlockRemainder;
> > +  EXT4_DIR_ENTRY  Entry;
> > +
> > +  DirIno     = File->Inode;
> > +  Status     = EFI_SUCCESS;
> > +  DirInoSize = Ext4InodeSize (DirIno);
> > +
> > +  DivU64x32Remainder (DirInoSize, Partition->BlockSize, &BlockRemainder);
> > +  if(BlockRemainder != 0) {
> > +    // Directory inodes need to have block aligned sizes
> > +    return EFI_VOLUME_CORRUPTED;
> > +  }
> > +
> > +  while(TRUE) {
> > +    EXT4_FILE  *TempFile;
> > +
> > +    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 0
> > +      DEBUG ((EFI_D_INFO, "[ext4] Length read %lu, offset %lu\n", Len, Offset));
> > + #endif
> > +
> > +    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 ((EFI_D_ERROR, "[ext4] Invalid dirent at offset %lu\n", Offset));
> > +      Status = EFI_VOLUME_CORRUPTED;
> > +      goto Out;
> > +    }
> > +
> > +    DEBUG ((EFI_D_INFO, "[ext4] dirent size %lu\n", Entry.rec_len));
> > +
> > +    if (Entry.inode == 0) {
> > +      // When inode = 0, it's unused
> > +      Offset += Entry.rec_len;
> > +      continue;
> > +    }
> > +
> > +    Status = Ext4OpenDirent (Partition, EFI_FILE_MODE_READ, &TempFile, &Entry);
> > +
> > +    if (EFI_ERROR (Status)) {
> > +      goto Out;
> > +    }
> > +
> > +    // TODO: Is this needed?
> > +    if (!StrCmp (TempFile->FileName, L".") || !StrCmp (TempFile->FileName, L"..")) {
>
> Integer comparisons with boolean operators are disallowed, please
> compare against int literals (-> "!= 0").
>
> > +      Offset += Entry.rec_len;
> > +      Ext4CloseInternal (TempFile);
> > +      continue;
> > +    }
> > +
> > + #if 0
> > +      DEBUG ((EFI_D_INFO, "[ext4] Listing file %s\n", TempFile->FileName));
> > + #endif
> > +
> > +    Status = Ext4GetFileInfo (TempFile, Buffer, OutLength);
> > +    if (!EFI_ERROR (Status)) {
> > +      File->Position = Offset + Entry.rec_len;
> > +    }
> > +
> > +    Ext4CloseInternal (TempFile);
> > +
> > +    break;
> > +  }
> > +
> > +  Status = EFI_SUCCESS;
> > +Out:
> > +  return Status;
> > +}
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/DiskUtil.c b/Features/Ext4Pkg/Ext4Dxe/DiskUtil.c
> > new file mode 100644
> > index 0000000000..1cafdd64cd
> > --- /dev/null
> > +++ b/Features/Ext4Pkg/Ext4Dxe/DiskUtil.c
> > @@ -0,0 +1,83 @@
> > +/**
> > +  @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 Ext4DiskIo (Partition)->ReadDisk (Ext4DiskIo (Partition), Ext4MediaId (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,
>
> Nitpicking, but I'd say any "IN" pointer should be CONST.
>
> > +  OUT VOID *Buffer,
> > +  IN UINTN NumberBlocks,
> > +  IN EXT4_BLOCK_NR BlockNumber
> > +  )
> > +{
> > +  return Ext4ReadDiskIo (Partition, Buffer, NumberBlocks * Partition->BlockSize, BlockNumber * Partition->BlockSize);
>
> I've seen a few multiplications, how do we know they do not wrap around?
>
> > +}
> > +
> > +/**
> > +   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;
> > +
> > +  Buf = AllocatePool (NumberBlocks * Partition->BlockSize);
> > +
> > +  if(Buf == NULL) {
> > +    return NULL;
> > +  }
> > +
> > +  if(Ext4ReadBlocks (Partition, Buf, NumberBlocks, BlockNumber) != EFI_SUCCESS) {
> > +    FreePool (Buf);
> > +    return NULL;
> > +  }
> > +
> > +  return Buf;
> > +}
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h b/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
> > new file mode 100644
> > index 0000000000..d790e70be1
> > --- /dev/null
> > +++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
> > @@ -0,0 +1,450 @@
> > +/**
> > +  @file Raw filesystem data structures
> > +
> > +  Copyright (c) 2021 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_ECOMPR_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_GOOD_OLD_INODE_SIZE  128
> > +
> > +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;
> > +  union {
> > +    // Note: Toolchain-specific defines (such as "linux") stops us from using simpler names down here.
> > +    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;
> > +    } data_linux;
> > +
> > +    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;
> > +    } data_hurd;
> > +  } 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;
> > +
> > +typedef struct {
> > +  UINT32    inode;
> > +  UINT16    rec_len;
> > +  UINT8     name_len;
> > +  UINT8     file_type;
> > +  CHAR8     name[255];
> > +} 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;
> > +
> > +typedef UINT64  EXT4_BLOCK_NR;
> > +typedef UINT32  EXT4_INO_NR;
> > +
> > +#define EXT4_INODE_SIZE(ino)  (((UINT64)ino->i_size_hi << 32) | ino->i_size_lo)
> > +
> > +#endif
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.c b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.c
> > new file mode 100644
> > index 0000000000..d1e289f8fa
> > --- /dev/null
> > +++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.c
> > @@ -0,0 +1,454 @@
> > +/**
> > +  @file Driver entry point
> > +
> > +  Copyright (c) 2021 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*
> > +
> > +EFI_STATUS
> > +EFIAPI
> > +Ext4ComponentNameGetDriverName (
> > +  IN  EFI_COMPONENT_NAME_PROTOCOL  *This,
> > +  IN  CHAR8                        *Language,
> > +  OUT CHAR16                       **DriverName
> > +  );
> > +
> > +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
> > +
> > +EFI_STATUS EFIAPI
> > +Ext4IsBindingSupported (
> > +  IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,
> > +  IN EFI_HANDLE ControllerHandle,
> > +  IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL
> > +  );
> > +
> > +EFI_STATUS EFIAPI
> > +Ext4Bind (
> > +  IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,
> > +  IN EFI_HANDLE ControllerHandle,
> > +  IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL
> > +  );
> > +
> > +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 =
> > +{
> > +  Ext4IsBindingSupported,
> > +  Ext4Bind,
> > +  Ext4Stop,
> > +  EXT4_DRIVER_VERSION
> > +};
> > +
> > +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
> > +  )
> > +{
> > +  // TODO: Do we need to test whether we're managing the handle, like FAT does?
> > +  return LookupUnicodeString2 (
> > +           Language,
> > +           This->SupportedLanguages,
> > +           mExt4ControllerNameTable,
> > +           ControllerName,
> > +           (BOOLEAN)(This == &gExt4ComponentName)
> > +           );
> > +}
> > +
> > +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)
> > +           );
> > +}
> > +
> > +EFI_STATUS EFIAPI
> > +Ext4IsBindingSupported (
> > +  IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,
> > +  IN EFI_HANDLE ControllerHandle,
> > +  IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL
> > +  );
> > +
> > +EFI_STATUS EFIAPI
> > +Ext4Bind (
> > +  IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,
> > +  IN EFI_HANDLE ControllerHandle,
> > +  IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL
> > +  );
> > +
> > +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 = Ext4DiskIo2 (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;
> > +}
> > +
> > +EFI_STATUS
> > +EFIAPI
> > +Ext4EntryPoint (
> > +  IN EFI_HANDLE         ImageHandle,
> > +  IN EFI_SYSTEM_TABLE   *SystemTable
> > +  )
> > +{
> > +  EFI_STATUS  Status;
> > +
> > +  Status = EfiLibInstallAllDriverProtocols2 (
> > +             ImageHandle,
> > +             SystemTable,
> > +             &gExt4BindingProtocol,
> > +             ImageHandle,
> > +             &gExt4ComponentName,
> > +             &gExt4ComponentName2,
> > +             NULL,
> > +             NULL,
> > +             NULL,
> > +             NULL
> > +             );
> > +
> > +  if(EFI_ERROR (Status)) {
> > +    return Status;
> > +  }
> > +
> > +  return Ext4InitialiseUnicodeCollation (ImageHandle);
> > +}
> > +
> > +EFI_STATUS
> > +EFIAPI
> > +Ext4Unload (
> > +  IN EFI_HANDLE ImageHandle
> > +  )
> > +{
> > +  EFI_STATUS  Status;
> > +  EFI_HANDLE  *DeviceHandleBuffer;
> > +  UINTN       DeviceHandleCount;
> > +  UINTN       Index;
> > +
> > +  Status = gBS->LocateHandleBuffer (
> > +                  AllHandles,
> > +                  NULL,
> > +                  NULL,
> > +                  &DeviceHandleCount,
> > +                  &DeviceHandleBuffer
> > +                  );
> > +  if (EFI_ERROR (Status)) {
> > +    return Status;
> > +  }
> > +
> > +  for(Index = 0; Index < DeviceHandleCount; Index++) {
> > +    EFI_HANDLE  Handle;
> > +
> > +    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;
> > +}
> > +
> > +EFI_STATUS EFIAPI
> > +Ext4IsBindingSupported (
> > +  IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,
> > +  IN EFI_HANDLE ControllerHandle,
> > +  IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL
> > +  )
> > +{
> > +  // Note to self: EFI_OPEN_PROTOCOL_TEST_PROTOCOL lets us not close the
> > +  // protocol and ignore the output argument entirely
> > +
> > +  EFI_STATUS  Status;
> > +
> > +  Status = gBS->OpenProtocol (
> > +                  ControllerHandle,
> > +                  &gEfiDiskIoProtocolGuid,
> > +                  NULL,
> > +                  BindingProtocol->ImageHandle,
> > +                  ControllerHandle,
> > +                  EFI_OPEN_PROTOCOL_TEST_PROTOCOL
> > +                  );
> > +
> > +  if(EFI_ERROR (Status)) {
> > +    return Status;
> > +  }
> > +
> > +  Status = gBS->OpenProtocol (
> > +                  ControllerHandle,
> > +                  &gEfiBlockIoProtocolGuid,
> > +                  NULL,
> > +                  BindingProtocol->ImageHandle,
> > +                  ControllerHandle,
> > +                  EFI_OPEN_PROTOCOL_TEST_PROTOCOL
> > +                  );
> > +  return Status;
> > +}
> > +
> > +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;
> > +
> > +  DEBUG ((EFI_D_INFO, "[Ext4] Binding to controller\n"));
> > +
> > +  Status = gBS->OpenProtocol (
> > +                  ControllerHandle,
> > +                  &gEfiDiskIoProtocolGuid,
> > +                  (VOID **)&DiskIo,
> > +                  BindingProtocol->ImageHandle,
> > +                  ControllerHandle,
> > +                  EFI_OPEN_PROTOCOL_BY_DRIVER
> > +                  );
> > +
> > +  if(EFI_ERROR (Status)) {
> > +    return Status;
>
> Why does this return when the others jump to Error?
>
> > +  }
> > +
> > +  DEBUG ((EFI_D_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 ((EFI_D_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;
> > +  }
> > +
> > +  DEBUG ((EFI_D_INFO, "Opening partition\n"));
> > +
> > +  Status = Ext4OpenPartition (ControllerHandle, DiskIo, DiskIo2, blockIo);
> > +
> > +  if(!EFI_ERROR (Status)) {
> > +    return Status;
> > +  }
> > +
> > +  DEBUG ((EFI_D_INFO, "[ext4] Error mounting %x\n", Status));
>
> You can print EFI_STATUS with "%r" to get a name string. I would move
> this into an error clause for Ext4OpenPartition() and jump to Error,
> with the outside branch returning success, to upkeep consistency and
> make it obvious to what the error message refers.
>
> > +
> > +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;
> > +}
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
> > new file mode 100644
> > index 0000000000..f6875c919e
> > --- /dev/null
> > +++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
> > @@ -0,0 +1,942 @@
> > +/**
> > +  @file Common header for the driver
> > +
> > +  Copyright (c) 2021 Pedro Falcato All rights reserved.
> > +  SPDX-License-Identifier: BSD-2-Clause-Patent
> > + */
> > +
> > +#ifndef _EXT4_H
> > +#define _EXT4_H
> > +
> > +#include <Uefi.h>
> > +
> > +#include <Guid/FileInfo.h>
> > +#include <Guid/FileSystemInfo.h>
> > +#include <Guid/FileSystemVolumeLabelInfo.h>
> > +#include <Protocol/BlockIo.h>
> > +#include <Protocol/DiskIo.h>
> > +#include <Protocol/DiskIo2.h>
> > +#include <Protocol/SimpleFileSystem.h>
> > +
> > +#include <Library/PcdLib.h>
> > +#include <Library/DebugLib.h>
> > +#include <Library/UefiLib.h>
> > +#include <Library/BaseLib.h>
> > +#include <Library/BaseMemoryLib.h>
> > +#include <Library/MemoryAllocationLib.h>
> > +#include <Library/UefiDriverEntryPoint.h>
> > +#include <Library/UefiBootServicesTableLib.h>
> > +#include <Library/UefiRuntimeServicesTableLib.h>
> > +#include <Library/OrderedCollectionLib.h>
> > +
> > +#include "Ext4Disk.h"
> > +
> > +#define EXT4_NAME_MAX  255
> > +
> > +#define EXT4_DRIVER_VERSION  0x0000
> > +
> > +/**
> > +   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
> > +  );
> > +
> > +typedef struct _Ext4File EXT4_FILE;
> > +
> > +typedef struct _Ext4_PARTITION {
> > +  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL    Interface;
> > +  EFI_DISK_IO_PROTOCOL               *DiskIo;
> > +  EFI_DISK_IO2_PROTOCOL              *DiskIo2;
> > +  EFI_BLOCK_IO_PROTOCOL              *BlockIo;
> > +
> > +  EXT4_SUPERBLOCK                    SuperBlock;
> > +  BOOLEAN                            Unmounting;
> > +
> > +  UINT32                             FeaturesIncompat;
> > +  UINT32                             FeaturesCompat;
> > +  UINT32                             FeaturesRoCompat;
> > +  UINT32                             InodeSize;
> > +  UINT32                             BlockSize;
> > +  BOOLEAN                            ReadOnly;
> > +  UINT64                             NumberBlockGroups;
> > +  EXT4_BLOCK_NR                      NumberBlocks;
> > +
> > +  EXT4_BLOCK_GROUP_DESC              *BlockGroups;
> > +  UINT32                             DescSize;
> > +  EXT4_FILE                          *Root;
> > +
> > +  UINT32                             InitialSeed;
> > +
> > +  LIST_ENTRY                         OpenFiles;
> > +} EXT4_PARTITION;
> > +
> > +/**
> > +   Opens and parses the superblock.
> > +
> > +   @param[out]     Partition Partition structure to fill with filesystem details.
> > +   @retval EFI_SUCCESS       Parsing was succesful and the partition is a
> > +                             valid ext4 partition.
> > + */
> > +EFI_STATUS
> > +Ext4OpenSuperblock (
> > +  OUT EXT4_PARTITION *Partition
> > +  );
> > +
> > +/**
> > +   Retrieves the EFI_BLOCK_IO_PROTOCOL of the partition.
> > +
> > +   @param[in]     Partition  Pointer to the opened ext4 partition.
> > +   @return The Block IO protocol associated with the partition.
> > + */
> > +STATIC inline
> > +EFI_BLOCK_IO_PROTOCOL *
> > +Ext4BlockIo (
> > +  EXT4_PARTITION *Partition
> > +  )
> > +{
> > +  return Partition->BlockIo;
> > +}
> > +
> > +/**
> > +   Retrieves the EFI_DISK_IO_PROTOCOL of the partition.
> > +
> > +   @param[in]     Partition  Pointer to the opened ext4 partition.
> > +   @return The Disk IO protocol associated with the partition.
> > + */
> > +STATIC inline
> > +EFI_DISK_IO_PROTOCOL *
> > +Ext4DiskIo (
> > +  EXT4_PARTITION *Partition
> > +  )
> > +{
> > +  return Partition->DiskIo;
> > +}
> > +
> > +/**
> > +   Retrieves the EFI_DISK_IO2_PROTOCOL of the partition.
> > +
> > +   @param[in]     Partition  Pointer to the opened ext4 partition.
> > +   @return The Disk IO 2 protocol associated with the partition, or NULL if
> > +           not supported.
> > + */
> > +STATIC inline
> > +EFI_DISK_IO2_PROTOCOL *
> > +Ext4DiskIo2 (
> > +  EXT4_PARTITION *Partition
> > +  )
> > +{
> > +  return Partition->DiskIo2;
> > +}
> > +
> > +/**
> > +   Retrieves the media ID of the partition.
> > +
> > +   @param[in]     Partition  Pointer to the opened ext4 partition.
> > +   @return The media ID associated with the partition.
> > + */
> > +STATIC inline
> > +UINT32
> > +Ext4MediaId (
> > +  EXT4_PARTITION *Partition
> > +  )
> > +{
> > +  return Partition->BlockIo->Media->MediaId;
> > +}
> > +
> > +/**
> > +   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
> > +  );
> > +
> > +/**
> > +   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
> > +  );
> > +
> > +/**
> > +   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
> > +  );
> > +
> > +/**
> > +   Checks if the opened partition has the 64-bit feature (see EXT4_FEATURE_INCOMPAT_64BIT).
> > +
> > +   @param[in]  Partition      Pointer to the opened ext4 partition.
> > +
> > +   @return TRUE if EXT4_FEATURE_INCOMPAT_64BIT is enabled, else FALSE.
> > + */
> > +STATIC inline
> > +BOOLEAN
> > +Ext4Is64Bit (
> > +  IN CONST EXT4_PARTITION *Partition
> > +  )
> > +{
> > +  return Partition->FeaturesIncompat & EXT4_FEATURE_INCOMPAT_64BIT;
> > +}
> > +
> > +/**
> > +   Composes an EXT4_BLOCK_NR safely, from two halfs.
> > +
> > +   @param[in]  Partition      Pointer to the opened ext4 partition.
> > +   @param[in]  Low            Low half of the block number.
> > +   @param[in]  High           High half of the block number.
> > +
> > +   @return The block number formed by Low, and if 64 bit is enabled, High.
> > + */
> > +STATIC inline
> > +EXT4_BLOCK_NR
> > +Ext4MakeBlockNumberFromHalfs (
> > +  IN CONST EXT4_PARTITION *Partition,
> > +  IN UINT32 Low,
> > +  IN UINT32 High
> > +  )
> > +{
> > +  // High might have garbage if it's not a 64 bit filesystem
> > +  return Ext4Is64Bit (Partition) ? Low | ((UINT64)High << 32) : Low;
> > +}
> > +
> > +/**
> > +   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.
> > + */
> > +STATIC inline
> > +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
> > +  );
> > +
> > +/**
> > +   Converts blocks to bytes.
> > +
> > +   @param[in]    Partition  Pointer to the opened partition.
> > +   @param[in]    Block      Block number/number of blocks.
> > +
> > +   @return The number of bytes.
> > + */
> > +STATIC inline
> > +UINT64
> > +Ext4BlockToByteOffset (
> > +  IN CONST EXT4_PARTITION *Partition,
> > +  IN EXT4_BLOCK_NR Block
> > +  )
> > +{
> > +  return Partition->BlockSize * Block;
> > +}
> > +
> > +/**
> > +   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 succesful 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
> > +  );
> > +
> > +/**
> > +   Retrieves the size of the inode.
> > +
> > +   @param[in]    Inode      Pointer to the ext4 inode.
> > +
> > +   @return The size of the inode, in bytes.
> > + */
> > +STATIC inline
> > +UINT64
> > +Ext4InodeSize (
> > +  CONST EXT4_INODE *Inode
> > +  )
> > +{
> > +  return ((UINT64)Inode->i_size_hi << 32) | Inode->i_size_lo;
> > +}
> > +
> > +/**
> > +   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 succesful.
> > +   @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
> > +  );
> > +
> > +struct _Ext4File {
> > +  EFI_FILE_PROTOCOL     Protocol;
> > +  EXT4_INODE            *Inode;
> > +  EXT4_INO_NR           InodeNum;
> > +
> > +  UINT64                OpenMode;
> > +  UINT64                Position;
> > +
> > +  EXT4_PARTITION        *Partition;
> > +  CHAR16                *FileName;
> > +
> > +  ORDERED_COLLECTION    *ExtentsMap;
> > +
> > +  LIST_ENTRY            OpenFilesListNode;
> > +};
> > +
> > +#define Ext4FileFromOpenFileNode(Node)  BASE_CR (Node, EXT4_FILE, OpenFilesListNode)
> > +
> > +/**
> > +   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 *NameUnicode,
> > +  IN  EXT4_PARTITION *Partition,
> > +  OUT EXT4_DIR_ENTRY *Result
> > +  );
> > +
> > +/**
> > +   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
> > +  );
> > +
> > +/**
> > +   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.
> > +
> > +   @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
> > +  );
> > +
> > +/**
> > +   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
> > +  );
> > +
> > +// Part of the EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
> > +
> > +EFI_STATUS EFIAPI
> > +Ext4OpenVolume (
> > +  IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Partition,
> > +  IN EFI_FILE_PROTOCOL **Root
> > +  );
> > +
> > +// End of EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
> > +
> > +/**
> > +   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
> > +  );
> > +
> > +/**
> > +   Closes a file.
> > +
> > +   @param[in]        File        Pointer to the file.
> > +
> > +   @return Status of the closing of the file.
> > + */
> > +EFI_STATUS
> > +Ext4CloseInternal (
> > +  IN EXT4_FILE *File
> > +  );
> > +
> > +// Part of the EFI_FILE_PROTOCOL
> > +
> > +EFI_STATUS EFIAPI
> > +Ext4Open (
> > +  IN EFI_FILE_PROTOCOL        *This,
> > +  OUT EFI_FILE_PROTOCOL       **NewHandle,
> > +  IN CHAR16                   *FileName,
> > +  IN UINT64                   OpenMode,
> > +  IN UINT64                   Attributes
> > +  );
> > +
> > +EFI_STATUS EFIAPI
> > +Ext4Close (
> > +  IN EFI_FILE_PROTOCOL *This
> > +  );
> > +
> > +EFI_STATUS EFIAPI
> > +Ext4Delete (
> > +  IN EFI_FILE_PROTOCOL  *This
> > +  );
> > +
> > +EFI_STATUS
> > +EFIAPI
> > +Ext4ReadFile (
> > +  IN EFI_FILE_PROTOCOL        *This,
> > +  IN OUT UINTN                *BufferSize,
> > +  OUT VOID                    *Buffer
> > +  );
> > +
> > +EFI_STATUS
> > +EFIAPI
> > +Ext4WriteFile (
> > +  IN EFI_FILE_PROTOCOL        *This,
> > +  IN OUT UINTN                *BufferSize,
> > +  IN VOID                    *Buffer
> > +  );
> > +
> > +EFI_STATUS
> > +EFIAPI
> > +Ext4GetPosition (
> > +  IN EFI_FILE_PROTOCOL        *This,
> > +  OUT UINT64                  *Position
> > +  );
> > +
> > +EFI_STATUS
> > +EFIAPI
> > +Ext4SetPosition (
> > +  IN EFI_FILE_PROTOCOL        *This,
> > +  IN UINT64                   Position
> > +  );
> > +
> > +EFI_STATUS
> > +EFIAPI
> > +Ext4GetInfo (
> > +  IN EFI_FILE_PROTOCOL        *This,
> > +  IN EFI_GUID                 *InformationType,
> > +  IN OUT UINTN                *BufferSize,
> > +  OUT VOID                    *Buffer
> > +  );
> > +
> > +// EFI_FILE_PROTOCOL implementation ends here.
> > +
> > +/**
> > +   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
> > +  );
> > +
> > +/**
> > +   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
> > +  );
> > +
> > +// In EFI we can't open FIFO pipes, UNIX sockets, character/block devices since these concepts are
> > +// at the kernel level and are OS dependent.
> > +
> > +/**
> > +   Checks if a file is openable.
> > +   @param[in]      File    Pointer to the file trying to be opened.
> > +
> > +
> > +   @return TRUE if file is openable. A file is considered openable if
> > +           it's a regular file or a directory, since most other file types
> > +           don't make sense under UEFI.
> > +*/
> > +STATIC inline
> > +BOOLEAN
> > +Ext4FileIsOpenable (
> > +  IN CONST EXT4_FILE *File
> > +  )
> > +{
> > +  return Ext4FileIsReg (File) || Ext4FileIsDir (File);
> > +}
> > +
> > +#define Ext4InodeHasField(Inode, \
> > +                          Field)  (Inode->i_extra_isize + EXT4_GOOD_OLD_INODE_SIZE >= OFFSET_OF (EXT4_INODE, Field) + \
> > +                                   sizeof (((EXT4_INODE *)NULL)->Field))
> > +
> > +/**
> > +   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 (
> > +  EXT4_FILE *File
> > +  );
> > +
> > +/**
> > +   Gets the file's last access time.
> > +   @param[in]      File   Pointer to the opened file.
> > +   @param[out]     Time   Pointer to an EFI_TIME structure.
> > +*/
> > +VOID
> > +Ext4FileATime (
> > +  IN EXT4_FILE *File,
> > +  OUT EFI_TIME *Time
> > +  );
> > +
> > +/**
> > +   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.
> > +*/
> > +VOID
> > +Ext4FileMTime (
> > +  IN EXT4_FILE *File,
> > +  OUT EFI_TIME *Time
> > +  );
> > +
> > +/**
> > +   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
> > +  );
> > +
> > +/**
> > +   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
> > +  );
> > +
> > +/**
> > +   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
>
> Why are they not CONST? The caller needs to cast this way. You can cast
> in the callee in my opinion.
>
> > +  );
> > +
> > +/**
> > +   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   Unicode collation was successfully initialised.
> > +   @retval !EFI_SUCCESS  Failure.
> > +*/
> > +EFI_STATUS
> > +Ext4GetUcs2DirentName (
> > +  IN EXT4_DIR_ENTRY *Entry,
> > +  OUT CHAR16 Ucs2FileName[EXT4_NAME_MAX + 1]
> > +  );
> > +
> > +/**
> > +   Retrieves information about the file and stores it in the EFI_FILE_INFO format.
> > +
> > +   @param[in]      File           Pointer to an opened file.
> > +   @param[out]     Info           Pointer to a EFI_FILE_INFO.
> > +   @param[in out]  BufferSize     Pointer to the buffer size
> > +
> > +   @return Status of the file information request.
> > +*/
> > +EFI_STATUS
> > +Ext4GetFileInfo (
> > +  IN     EXT4_FILE *File,
> > +  OUT    EFI_FILE_INFO *Info,
> > +  IN OUT UINTN *BufferSize
> > +  );
> > +
> > +/**
> > +   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
> > +  );
> > +
> > +/**
> > +   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
> > +  );
> > +
> > +/**
> > +   Frees the extents map, deleting every extent stored.
> > +
> > +   @param[in]      File        Pointer to the open file.
> > +*/
> > +VOID
> > +Ext4FreeExtentsMap (
> > +  IN EXT4_FILE *File
> > +  );
> > +
> > +/**
> > +   Calculates the CRC32c checksum of the given buffer.
> > +
> > +   @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 CRC32c checksum.
> > +*/
> > +UINT32
> > +CalculateCrc32c (
> > +  IN CONST VOID *Buffer,
> > +  IN UINTN Length,
> > +  IN UINT32 InitialValue
> > +  );
> > +
> > +/**
> > +   Calculates the CRC16 checksum of the given buffer.
> > +
> > +   @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 CRC16 checksum.
> > +*/
> > +UINT16
> > +CalculateCrc16 (
> > +  IN CONST VOID *Buffer,
> > +  IN UINTN Length,
> > +  IN UINT16 InitialValue
> > +  );
> > +
> > +/**
> > +   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
> > +  );
> > +
> > +/**
> > +   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
> > +  );
> > +
> > +/**
> > +   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
> > +  );
> > +
> > +/**
> > +   Unmounts and frees an ext4 partition.
> > +
> > +   @param[in]        Partition        Pointer to the opened partition.
> > +
> > +   @return Status of the unmount.
> > + */
> > +EFI_STATUS
> > +Ext4UnmountAndFreePartition (
> > +  IN EXT4_PARTITION *Partition
> > +  );
> > +
> > +/**
> > +   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
> > +  );
> > +
> > +/**
> > +   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
> > +  );
> > +
> > +/**
> > +   Verifies the existance of a particular RO compat feature set.
> > +   @param[in]      Partition           Pointer to the opened EXT4 partition.
> > +   @param[in]      RoCompatFeatureSet  Feature set to test.
> > +
> > +   @return TRUE if all features are supported, else FALSE.
> > +*/
> > +STATIC inline
> > +BOOLEAN
> > +Ext4HasRoCompat (
> > +  IN CONST EXT4_PARTITION *Partition,
> > +  IN UINT32 RoCompatFeatureSet
> > +  )
> > +{
> > +  return (Partition->FeaturesRoCompat & RoCompatFeatureSet) == RoCompatFeatureSet;
> > +}
> > +
> > +/**
> > +   Verifies the existance of a particular compat feature set.
> > +   @param[in]      Partition           Pointer to the opened EXT4 partition.
> > +   @param[in]      RoCompatFeatureSet  Feature set to test.
> > +
> > +   @return TRUE if all features are supported, else FALSE.
> > +*/
> > +STATIC inline
> > +BOOLEAN
> > +Ext4HasCompat (
> > +  IN CONST EXT4_PARTITION *Partition,
> > +  IN UINT32 CompatFeatureSet
> > +  )
> > +{
> > +  return (Partition->FeaturesCompat & CompatFeatureSet) == CompatFeatureSet;
> > +}
> > +
> > +/**
> > +   Verifies the existance of a particular compat feature set.
> > +   @param[in]      Partition           Pointer to the opened EXT4 partition.
> > +   @param[in]      RoCompatFeatureSet  Feature set to test.
> > +
> > +   @return TRUE if all features are supported, else FALSE.
> > +*/
> > +STATIC inline
> > +BOOLEAN
> > +Ext4HasIncompat (
> > +  IN CONST EXT4_PARTITION *Partition,
> > +  IN UINT32 IncompatFeatureSet
> > +  )
> > +{
> > +  return (Partition->FeaturesIncompat & IncompatFeatureSet) == IncompatFeatureSet;
> > +}
> > +
> > +// Note: Might be a good idea to provide generic Ext4Has$feature() through macros.
> > +
> > +/**
> > +   Checks if metadata_csum is enabled on the partition.
> > +   @param[in]      Partition           Pointer to the opened EXT4 partition.
> > +   @param[in]      RoCompatFeatureSet  Feature set to test.
> > +
> > +   @return TRUE if the feature is supported, else FALSE.
> > +*/
> > +STATIC inline
> > +BOOLEAN
> > +Ext4HasMetadataCsum (
> > +  IN CONST EXT4_PARTITION *Partition
> > +  )
> > +{
> > +  return Ext4HasRoCompat (Partition, EXT4_FEATURE_RO_COMPAT_METADATA_CSUM);
> > +}
> > +
> > +/**
> > +   Checks if gdt_csum is enabled on the partition.
> > +   @param[in]      Partition           Pointer to the opened EXT4 partition.
> > +   @param[in]      RoCompatFeatureSet  Feature set to test.
> > +
> > +   @return TRUE if the feature is supported, else FALSE.
> > +*/
> > +STATIC inline
> > +BOOLEAN
> > +Ext4HasGdtCsum (
> > +  IN CONST EXT4_PARTITION *Partition
> > +  )
> > +{
> > +  return Ext4HasRoCompat (Partition, EXT4_FEATURE_RO_COMPAT_METADATA_CSUM);
> > +}
> > +
> > +#endif
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
> > new file mode 100644
> > index 0000000000..102b12d613
> > --- /dev/null
> > +++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
> > @@ -0,0 +1,147 @@
> > +## @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                      = Ext4
> > +  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
> > +  Collation.c
> > +  Crc32c.c
> > +  Crc16.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
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni
> > new file mode 100644
> > index 0000000000..7476fbf9bd
> > --- /dev/null
> > +++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni
> > @@ -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."
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/Extents.c b/Features/Ext4Pkg/Ext4Dxe/Extents.c
> > new file mode 100644
> > index 0000000000..db4bf5aa3f
> > --- /dev/null
> > +++ b/Features/Ext4Pkg/Ext4Dxe/Extents.c
> > @@ -0,0 +1,616 @@
> > +/**
> > +  @file Extent related routines
> > +
> > +  Copyright (c) 2021 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.
> > +
> > +   @return TRUE if valid, FALSE if not.
> > +*/
> > +STATIC
> > +BOOLEAN
> > +Ext4ExtentHeaderValid (
> > +  IN CONST EXT4_EXTENT_HEADER *Header
> > +  )
> > +{
> > +  if(Header->eh_depth > EXT4_EXTENT_TREE_MAX_DEPTH) {
> > +    DEBUG ((EFI_D_ERROR, "[ext4] Invalid extent header depth %u\n", Header->eh_depth));
> > +    return FALSE;
> > +  }
> > +
> > +  if(Header->eh_magic != EXT4_EXTENT_HEADER_MAGIC) {
> > +    DEBUG ((EFI_D_ERROR, "[ext4] Invalid extent header magic %x\n", Header->eh_magic));
> > +    return FALSE;
> > +  }
> > +
> > +  if(Header->eh_max < Header->eh_entries) {
> > +    DEBUG ((
> > +      EFI_D_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 ((UINT64)Index->ei_leaf_hi << 32) | Index->ei_leaf_lo;
> > +}
> > +
> > +STATIC UINTN  GetExtentRequests  = 0;
> > +STATIC UINTN  GetExtentCacheHits = 0;
>
> Private globals are defined with "m" ("module") and public ones with "g".
>
> > +
> > +/**
> > +   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 succesful.
> > +   @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;
> > +
> > +  Inode  = File->Inode;
> > +  Ext    = NULL;
> > +  Buffer = NULL;
> > +
> > +  DEBUG ((EFI_D_INFO, "[ext4] Looking up extent for block %lu\n", LogicalBlock));
> > +
> > +  if (!(Inode->i_flags & EXT4_EXTENTS_FL)) {
> > +    return EFI_UNSUPPORTED;
> > +  }
> > +
> > +  // ext4 does not have support for logical block numbers bigger than UINT32_MAX
> > +  if (LogicalBlock > (UINT32)- 1) {
> > +    return EFI_NO_MAPPING;
> > +  }
> > +
> > +  GetExtentRequests++;
> > + #if DEBUG_EXTENT_CACHE
> > +    DEBUG ((
> > +      EFI_D_INFO,
> > +      "[ext4] Requests %lu, hits %lu, misses %lu\n",
> > +      GetExtentRequests,
> > +      GetExtentCacheHits,
> > +      GetExtentRequests - GetExtentCacheHits
> > +      ));
> > + #endif
> > +
> > +  // 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;
> > +    GetExtentCacheHits++;
> > +
> > +    return EFI_SUCCESS;
> > +  }
> > +
> > +  // Slow path, we'll need to read from disk and (try to) cache those extents.
> > +
> > +  ExtHeader = Ext4GetInoExtentHeader (Inode);
> > +
> > +  if(!Ext4ExtentHeaderValid (ExtHeader)) {
> > +    return EFI_VOLUME_CORRUPTED;
> > +  }
> > +
> > +  CurrentDepth = ExtHeader->eh_depth;
> > +
> > +  while(ExtHeader->eh_depth != 0) {
> > +    EXT4_EXTENT_INDEX  *Index;
> > +    EFI_STATUS         Status;
> > +
> > +    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);
> > +
> > +    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, Ext4ExtentIdxLeafBlock (Index));
> > +    if(EFI_ERROR (Status)) {
> > +      FreePool (Buffer);
> > +      return Status;
> > +    }
> > +
> > +    ExtHeader = Buffer;
> > +
> > +    if(!Ext4ExtentHeaderValid (ExtHeader)) {
> > +      FreePool (Buffer);
> > +      return EFI_VOLUME_CORRUPTED;
> > +    }
> > +
> > +    if(!Ext4CheckExtentChecksum (ExtHeader, File)) {
> > +      DEBUG ((EFI_D_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 + Ext->ee_len > 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 = UserStruct1;
> > +  CONST EXT4_EXTENT  *Extent2 = UserStruct2;
> > +
> > +  // TODO: Detect extent overlaps? in case of corruption.
> > +
> > +  /* DEBUG((EFI_D_INFO, "[ext4] extent 1 %u extent 2 %u = %ld\n", Extent1->ee_block,
> > +   Extent2->ee_block, Extent1->ee_block - Extent2->ee_block)); */
> > +  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 = UserStruct;
> > +
> > +  // Note that logical blocks are 32-bits in size so no truncation can happen here
> > +  // with regards to 32-bit architectures.
> > +  UINT32  Block = (UINT32)(UINTN)StandaloneKey;
> > +
> > +  // DEBUG((EFI_D_INFO, "[ext4] comparing %u %u\n", Block, Extent->ee_block));
> > +  if(Block >= Extent->ee_block && Block < Extent->ee_block + Extent->ee_len) {
> > +    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 = NULL;
> > +
> > +  while ((MinEntry = OrderedCollectionMin (File->ExtentsMap)) != NULL) {
> > +    EXT4_EXTENT  *Ext;
> > +    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  i;
> > +
> > +  /* 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(i = 0; i < NumberExtents; i++, Extents++) {
> > +    EXT4_EXTENT  *Extent;
> > +    EFI_STATUS   Status;
> > +
> > +    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;
> > +    }
> > +
> > + #if DEBUG_EXTENT_CACHE
> > +      DEBUG ((
> > +        EFI_D_INFO,
> > +        "[ext4] Cached extent [%lu, %lu]\n",
> > +        Extent->ee_block,
> > +        Extent->ee_block + Extent->ee_len - 1
> > +        ));
> > + #endif
> > +  }
> > +}
> > +
> > +/**
> > +   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(!Ext4HasMetadataCsum (Partition)) {
> > +    return TRUE;
> > +  }
> > +
> > +  Tail = (EXT4_EXTENT_TAIL *)((CONST CHAR8 *)ExtHeader + (Partition->BlockSize - 4));
> > +
> > +  return Tail->eb_checksum == Ext4CalculateExtentChecksum (ExtHeader, File);
> > +}
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/File.c b/Features/Ext4Pkg/Ext4Dxe/File.c
> > new file mode 100644
> > index 0000000000..10dda64b16
> > --- /dev/null
> > +++ b/Features/Ext4Pkg/Ext4Dxe/File.c
> > @@ -0,0 +1,583 @@
> > +/**
> > +  @file EFI_FILE_PROTOCOL implementation for EXT4
> > +
> > +  Copyright (c) 2021 Pedro Falcato All rights reserved.
> > +  SPDX-License-Identifier: BSD-2-Clause-Patent
> > + */
> > +
> > +#include "Ext4Dxe.h"
> > +
> > +#include <Library/BaseUcs2Utf8Lib.h>
> > +
> > +/**
> > +   Duplicates a file structure.
> > +
> > +   @param[in]        Original    Pointer to the original file.
> > +
> > +   @return Pointer to the new file structure.
> > + */
> > +STATIC
> > +EXT4_FILE *
> > +Ext4DuplicateFile (
> > +  IN CONST EXT4_FILE *Original
> > +  );
> > +
> > +/**
> > +   Gets the next path segment.
> > +
> > +   @param[in]        Path        Pointer to the rest of the path.
> > +   @param[out]       PathSegment Pointer to the buffer that will hold the path segment.
> > +                                 Note: It's necessarily EXT4_NAME_MAX +1 long.
> > +   @param[out]       Length      Pointer to the UINTN that will hold the length of the path segment.
> > +
> > +   @retval !EFI_SUCCESS          The path segment is too large(> EXT4_NAME_MAX).
> > + */
> > +STATIC
> > +EFI_STATUS
> > +GetPathSegment (
> > +  IN CONST CHAR16 *Path, OUT CHAR16 *PathSegment, OUT UINTN *Length
> > +  )
> > +{
> > +  CONST CHAR16  *Start = Path;
> > +  CONST CHAR16  *End   = Path;
> > +
> > +  // The path segment ends on a backslash or a null terminator
> > +  for( ; *End != L'\0' && *End != L'\\'; End++) {
> > +  }
> > +
> > +  *Length = End - Start;
> > +
> > +  return StrnCpyS (PathSegment, EXT4_NAME_MAX, Start, End - Start);
>
> Why not EXT4_NAME_MAX + 1?
>
> > +}
> > +
> > +/**
> > +   Detects if we have more path segments on the path.
> > +
> > +   @param[in] Path   Pointer to the rest of the path.
> > +   @return           True if we're on the last segment, false if there are more
> > +                     segments.
> > + */
> > +STATIC
> > +BOOLEAN
> > +Ext4IsLastPathSegment (
> > +  IN CONST CHAR16 *Path
> > +  )
> > +{
> > +  while(Path[0] == L'\\') {
> > +    Path++;
> > +  }
> > +
> > +  return Path[0] == '\0';
> > +}
> > +
> > +#define EXT4_INO_PERM_READ_OWNER   0400
> > +#define EXT4_INO_PERM_WRITE_OWNER  0200
> > +#define EXT4_INO_PERM_EXEC_OWNER   0100
> > +
> > +/**
> > +   Detects if we have permissions to open the file on the desired mode.
> > +
> > +   @param[in out] File         Pointer to the file we're opening.
> > +   @param[in]     OpenMode     Mode in which to open the file.
> > +
> > +   @return           True if the open was succesful, false if we don't have
> > +                     enough permissions.
> > + */
> > +STATIC
> > +BOOLEAN
> > +Ext4ApplyPermissions (
> > +  IN OUT EXT4_FILE *File, IN UINT64 OpenMode
> > +  )
> > +{
> > +  UINT16  NeededPerms = 0;
> > +
> > +  if((OpenMode & EFI_FILE_MODE_READ) != 0) {
> > +    NeededPerms |= EXT4_INO_PERM_READ_OWNER;
> > +  }
> > +
> > +  if((OpenMode & EFI_FILE_MODE_WRITE) != 0) {
> > +    NeededPerms |= EXT4_INO_PERM_WRITE_OWNER;
> > +  }
> > +
> > +  if((File->Inode->i_mode & NeededPerms) != NeededPerms) {
> > +    return FALSE;
> > +  }
> > +
> > +  File->OpenMode = OpenMode;
> > +
> > +  return TRUE;
> > +}
> > +
> > +/**
> > +   Detects if we have permissions to search on the directory.
> > +
> > +   @param[in out] File         Pointer to the open directory.
> > +
> > +   @return           True if we have permission to search, else false.
> > + */
> > +STATIC
> > +BOOLEAN
> > +Ext4DirCanLookup (
> > +  IN CONST EXT4_FILE *File
> > +  )
> > +{
> > +  // In UNIX, executable permission on directories means that we have permission to look up
> > +  // files in a directory.
> > +  return (File->Inode->i_mode & EXT4_INO_PERM_EXEC_OWNER) == EXT4_INO_PERM_EXEC_OWNER;
> > +}
> > +
> > +EFI_STATUS EFIAPI
> > +Ext4Open (
> > +  IN EFI_FILE_PROTOCOL        *This,
> > +  OUT EFI_FILE_PROTOCOL       **NewHandle,
> > +  IN CHAR16                   *FileName,
> > +  IN UINT64                   OpenMode,
> > +  IN UINT64                   Attributes
> > +  )
> > +{
> > +  EXT4_FILE       *Current;
> > +  EXT4_PARTITION  *Partition;
> > +  UINTN           Level;
> > +
> > +  Current   = (EXT4_FILE *)This;
> > +  Partition = Current->Partition;
> > +  Level     = 0;
> > +
> > +  DEBUG ((EFI_D_INFO, "[ext4] Ext4Open %s\n", FileName));
> > +  // If the path starts with a backslash, we treat the root directory as the base directory
> > +  if(FileName[0] == L'\\') {
> > +    FileName++;
> > +    Current = Partition->Root;
> > +  }
> > +
> > +  while(FileName[0] != L'\0') {
> > +    CHAR16      PathSegment[EXT4_NAME_MAX + 1];
> > +    UINTN       Length;
> > +    EXT4_FILE   *File;
> > +    EFI_STATUS  Status;
> > +
> > +    // Discard leading path separators
> > +    while(FileName[0] == L'\\') {
> > +      FileName++;
> > +    }
> > +
> > +    if(GetPathSegment (FileName, PathSegment, &Length) != EFI_SUCCESS) {
> > +      return EFI_BUFFER_TOO_SMALL;
> > +    }
> > +
> > +    // Reached the end of the path
> > +    if(Length == 0) {
> > +      break;
> > +    }
> > +
> > +    FileName += Length;
> > +
> > +    DEBUG ((EFI_D_INFO, "[ext4] Opening %s\n", PathSegment));
> > +
> > +    // TODO: What to do with symlinks? They're nonsense when absolute but may
> > +    // be useful when they're relative.
> > +
> > +    if (!Ext4FileIsDir (Current)) {
> > +      return EFI_INVALID_PARAMETER;
> > +    }
> > +
> > +    if (!Ext4IsLastPathSegment (FileName)) {
> > +      if (!Ext4DirCanLookup (Current)) {
> > +        return EFI_ACCESS_DENIED;
> > +      }
> > +    }
> > +
> > +    Status = Ext4OpenFile (Current, PathSegment, Partition, EFI_FILE_MODE_READ, &File);
> > +
> > +    if(EFI_ERROR (Status) && Status != EFI_NOT_FOUND) {
> > +      return Status;
> > +    } else if(Status == EFI_NOT_FOUND) {
> > +      // WRITE SUPPORT: Handle file creation when write support is added
> > +      return Status;
> > +    }
> > +
> > +    // Check if this is a valid file to open in EFI
> > +    if(!Ext4FileIsOpenable (File)) {
> > +      Ext4CloseInternal (File);
> > +      // This looks like an /okay/ status to return.
> > +      return EFI_ACCESS_DENIED;
> > +    }
> > +
> > +    if(Level != 0) {
> > +      // Careful not to close the base directory
> > +      Ext4CloseInternal (Current);
> > +    }
> > +
> > +    Level++;
> > +
> > +    Current = File;
> > +  }
> > +
> > +  if (Level == 0) {
> > +    // We opened the base directory again, so we need to duplicate the file structure
> > +    Current = Ext4DuplicateFile (Current);
> > +    if (Current == NULL) {
> > +      return EFI_OUT_OF_RESOURCES;
> > +    }
> > +  }
> > +
> > +  if(!Ext4ApplyPermissions (Current, OpenMode)) {
> > +    Ext4CloseInternal (Current);
> > +    return EFI_ACCESS_DENIED;
> > +  }
> > +
> > +  *NewHandle = &Current->Protocol;
> > +
> > +  DEBUG ((EFI_D_INFO, "Open successful\n"));
> > +  DEBUG ((EFI_D_INFO, "Opened filename %s\n", Current->FileName));
> > +  return EFI_SUCCESS;
> > +}
> > +
> > +EFI_STATUS EFIAPI
> > +Ext4Close (
> > +  IN EFI_FILE_PROTOCOL *This
> > +  )
> > +{
> > +  return Ext4CloseInternal ((EXT4_FILE *)This);
> > +}
> > +
> > +/**
> > +   Closes a file.
> > +
> > +   @param[in]        File        Pointer to the file.
> > +
> > +   @return Status of the closing of the file.
> > + */
> > +EFI_STATUS
> > +Ext4CloseInternal (
> > +  IN EXT4_FILE *File
> > +  )
> > +{
> > +  if(File == File->Partition->Root && !File->Partition->Unmounting) {
> > +    return EFI_SUCCESS;
> > +  }
> > +
> > +  DEBUG ((EFI_D_INFO, "[ext4] Closed file %p (inode %lu)\n", File, File->InodeNum));
> > +  RemoveEntryList (&File->OpenFilesListNode);
> > +  FreePool (File->FileName);
> > +  FreePool (File->Inode);
> > +  Ext4FreeExtentsMap (File);
> > +  FreePool (File);
> > +  return EFI_SUCCESS;
>
> As this function can only ever return success, and all direct uses
> discard the status anyway, in my opinion make it VOID and return success
> in Ext3Close().
>
> > +}
> > +
> > +EFI_STATUS EFIAPI
> > +Ext4Delete (
> > +  IN EFI_FILE_PROTOCOL  *This
> > +  )
> > +{
> > +  // Write support
> > +  Ext4Close (This);
> > +  return EFI_WARN_DELETE_FAILURE;
> > +}
> > +
> > +EFI_STATUS
> > +EFIAPI
> > +Ext4ReadFile (
> > +  IN EFI_FILE_PROTOCOL        *This,
> > +  IN OUT UINTN                *BufferSize,
> > +  OUT VOID                    *Buffer
> > +  )
> > +{
> > +  EXT4_FILE       *File;
> > +  EXT4_PARTITION  *Partition;
> > +  EFI_STATUS      Status;
> > +
> > +  File = (EXT4_FILE *)This;
> > +  Partition = File->Partition;
> > +
> > +  ASSERT (Ext4FileIsOpenable (File));
> > +
> > +  if(Ext4FileIsReg (File)) {
> > +    Status = Ext4Read (Partition, File, Buffer, File->Position, BufferSize);
> > +    if(Status == EFI_SUCCESS) {
> > +      File->Position += *BufferSize;
> > +    }
> > +
> > +    return Status;
> > +  } else if(Ext4FileIsDir (File)) {
> > +    Status = Ext4ReadDir (Partition, File, Buffer, File->Position, BufferSize);
> > +    DEBUG ((EFI_D_INFO, "[ext4] ReadDir status %lx\n", Status));
> > +
> > +    if(Status == EFI_SUCCESS) {
> > +      DEBUG ((EFI_D_INFO, "[ext4] ReadDir retlen %lu\n", *BufferSize));
> > +    }
> > +
> > +    return Status;
> > +  }
> > +
> > +  return EFI_SUCCESS;
> > +}
> > +
> > +EFI_STATUS
> > +EFIAPI
> > +Ext4WriteFile (
> > +  IN EFI_FILE_PROTOCOL        *This,
> > +  IN OUT UINTN                *BufferSize,
> > +  IN VOID                    *Buffer
> > +  )
> > +{
> > +  EXT4_FILE  *File = (EXT4_FILE *)This;
> > +
> > +  if(!(File->OpenMode & EFI_FILE_MODE_WRITE)) {
> > +    return EFI_ACCESS_DENIED;
> > +  }
> > +
> > +  // TODO: Add write support
> > +  return EFI_WRITE_PROTECTED;
> > +}
> > +
> > +EFI_STATUS
> > +EFIAPI
> > +Ext4GetPosition (
> > +  IN EFI_FILE_PROTOCOL        *This,
> > +  OUT UINT64                  *Position
> > +  )
> > +{
> > +  EXT4_FILE  *File = (EXT4_FILE *)This;
> > +
> > +  if(Ext4FileIsDir (File)) {
> > +    return EFI_UNSUPPORTED;
> > +  }
> > +
> > +  *Position = File->Position;
> > +
> > +  return EFI_SUCCESS;
> > +}
> > +
> > +EFI_STATUS
> > +EFIAPI
> > +Ext4SetPosition (
> > +  IN EFI_FILE_PROTOCOL        *This,
> > +  IN UINT64                   Position
> > +  )
> > +{
> > +  EXT4_FILE  *File = (EXT4_FILE *)This;
> > +
> > +  // Only seeks to 0 (so it resets the ReadDir operation) are allowed
> > +  if(Ext4FileIsDir (File) && Position != 0) {
> > +    return EFI_UNSUPPORTED;
> > +  }
> > +
> > +  // -1 (0xffffff.......) seeks to the end of the file
> > +  if(Position == (UINT64)- 1) {
>
> MAX_UINT64?
>
> > +    Position = EXT4_INODE_SIZE (File->Inode);
> > +  }
> > +
> > +  File->Position = Position;
> > +
> > +  return EFI_SUCCESS;
> > +}
> > +
> > +/**
> > +   Retrieves information about the file and stores it in the EFI_FILE_INFO format.
> > +
> > +   @param[in]      File           Pointer to an opened file.
> > +   @param[out]     Info           Pointer to a EFI_FILE_INFO.
> > +   @param[in out]  BufferSize     Pointer to the buffer size
> > +
> > +   @return Status of the file information request.
> > +*/
> > +EFI_STATUS
> > +Ext4GetFileInfo (
> > +  IN EXT4_FILE *File, OUT EFI_FILE_INFO *Info, IN OUT UINTN *BufferSize
> > +  )
> > +{
> > +  // TODO: Get a way to set the directory entry for SetFileInfo
> > +  UINTN  FileNameLen  = StrLen (File->FileName);
> > +  UINTN  FileNameSize = StrSize (File->FileName);
> > +
> > +  UINTN  NeededLength = SIZE_OF_EFI_FILE_INFO + FileNameSize;
> > +
> > +  if(*BufferSize < NeededLength) {
> > +    *BufferSize = NeededLength;
> > +    return EFI_BUFFER_TOO_SMALL;
> > +  }
> > +
> > +  Info->FileSize     = EXT4_INODE_SIZE (File->Inode);
> > +  Info->PhysicalSize = Ext4FilePhysicalSpace (File);
> > +  Ext4FileATime (File, &Info->LastAccessTime);
> > +  Ext4FileMTime (File, &Info->ModificationTime);
> > +  Ext4FileCreateTime (File, &Info->LastAccessTime);
> > +  Info->Attribute = 0;
> > +  Info->Size = NeededLength;
> > +
> > +  if(Ext4FileIsDir (File)) {
> > +    Info->Attribute |= EFI_FILE_DIRECTORY;
> > +  }
> > +
> > +  *BufferSize = NeededLength;
> > +
> > +  return StrCpyS (Info->FileName, FileNameLen + 1, File->FileName);
> > +}
> > +
> > +/**
> > +   Retrieves information about the filesystem and stores it in the EFI_FILE_SYSTEM_INFO format.
> > +
> > +   @param[in]      File           Pointer to an opened file.
> > +   @param[out]     Info           Pointer to a EFI_FILE_SYSTEM_INFO.
> > +   @param[in out]  BufferSize     Pointer to the buffer size
> > +
> > +   @return Status of the file information request.
> > +*/
> > +STATIC
> > +EFI_STATUS
> > +Ext4GetFilesystemInfo (
> > +  IN EXT4_PARTITION *Part, OUT EFI_FILE_SYSTEM_INFO *Info, IN OUT UINTN *BufferSize
> > +  )
> > +{
> > +  // Length of s_volume_name + null terminator
> > +  CHAR8          TempVolName[16 + 1];
> > +  CHAR16         *VolumeName;
> > +  UINTN          VolNameLength;
> > +  EFI_STATUS     Status;
> > +  UINTN          NeededLength;
> > +  EXT4_BLOCK_NR  TotalBlocks;
> > +  EXT4_BLOCK_NR  FreeBlocks;
> > +
> > +  VolNameLength = 0;
> > +  VolumeName    = NULL;
> > +
> > +  // s_volume_name is only valid on dynamic revision; old filesystems don't support this
> > +  if(Part->SuperBlock.s_rev_level == EXT4_DYNAMIC_REV) {
> > +    CopyMem (TempVolName, (CONST CHAR8 *)Part->SuperBlock.s_volume_name, 16);
> > +    TempVolName[16] = '\0';
> > +
> > +    Status = UTF8StrToUCS2 (TempVolName, &VolumeName);
> > +
> > +    if(EFI_ERROR (Status)) {
> > +      return Status;
> > +    }
> > +
> > +    VolNameLength = StrLen (VolumeName);
> > +  }
> > +
> > +  NeededLength = SIZE_OF_EFI_FILE_SYSTEM_INFO;
> > +
> > +  if (VolumeName != NULL) {
> > +    NeededLength += StrSize (VolumeName);
> > +  } else {
> > +    // If we don't have a volume name, we set VolumeLabel to a single null terminator
> > +    NeededLength += sizeof (CHAR16);
> > +  }
> > +
> > +  if(*BufferSize < NeededLength) {
> > +    *BufferSize = NeededLength;
> > +
> > +    if (VolumeName != NULL) {
> > +      FreePool (VolumeName);
> > +    }
> > +
> > +    return EFI_BUFFER_TOO_SMALL;
> > +  }
> > +
> > +  TotalBlocks = Part->NumberBlocks;
> > +
> > +  FreeBlocks = Ext4MakeBlockNumberFromHalfs (
> > +                 Part,
> > +                 Part->SuperBlock.s_free_blocks_count,
> > +                 Part->SuperBlock.s_free_blocks_count_hi
> > +                 );
> > +
> > +  Info->BlockSize = Part->BlockSize;
> > +  Info->Size       = NeededLength;
> > +  Info->ReadOnly   = Part->ReadOnly;
> > +  Info->VolumeSize = TotalBlocks * Part->BlockSize;
> > +  Info->FreeSpace  = FreeBlocks * Part->BlockSize;
> > +
> > +  if (VolumeName != NULL) {
> > +    StrCpyS (Info->VolumeLabel, VolNameLength + 1, VolumeName);
> > +  } else {
> > +    Info->VolumeLabel[0] = L'\0';
> > +  }
> > +
> > +  if (VolumeName != NULL) {
> > +    FreePool (VolumeName);
> > +  }
> > +
> > +  *BufferSize = NeededLength;
> > +
> > +  return EFI_SUCCESS;
> > +}
> > +
> > +EFI_STATUS
> > +EFIAPI
> > +Ext4GetInfo (
> > +  IN EFI_FILE_PROTOCOL        *This,
> > +  IN EFI_GUID                 *InformationType,
> > +  IN OUT UINTN                *BufferSize,
> > +  OUT VOID                    *Buffer
> > +  )
> > +{
> > +  if (CompareGuid (InformationType, &gEfiFileInfoGuid)) {
> > +    return Ext4GetFileInfo ((EXT4_FILE *)This, Buffer, BufferSize);
> > +  }
> > +
> > +  if (CompareGuid (InformationType, &gEfiFileSystemInfoGuid)) {
> > +    return Ext4GetFilesystemInfo (((EXT4_FILE *)This)->Partition, Buffer, BufferSize);
> > +  }
> > +
> > +  return EFI_UNSUPPORTED;
> > +}
> > +
> > +/**
> > +   Duplicates a file structure.
> > +
> > +   @param[in]        Original    Pointer to the original file.
> > +
> > +   @return Pointer to the new file structure.
> > + */
> > +STATIC
> > +EXT4_FILE *
> > +Ext4DuplicateFile (
> > +  IN CONST EXT4_FILE *Original
> > +  )
> > +{
> > +  EXT4_PARTITION  *Partition;
> > +  EXT4_FILE       *File;
> > +
> > +  Partition = Original->Partition;
> > +  File = AllocateZeroPool (sizeof (EXT4_FILE));
> > +
> > +  if (File == NULL) {
> > +    return NULL;
> > +  }
> > +
> > +  File->Inode = Ext4AllocateInode (Partition);
> > +  if (File->Inode == NULL) {
> > +    FreePool (File);
> > +    return NULL;
> > +  }
> > +
> > +  CopyMem (File->Inode, Original->Inode, Partition->InodeSize);
> > +
> > +  File->FileName = AllocateZeroPool (StrSize (Original->FileName));
> > +  if (File->FileName == NULL) {
> > +    FreePool (File->Inode);
> > +    FreePool (File);
> > +    return NULL;
> > +  }
> > +
> > +  StrCpyS (File->FileName, StrLen (Original->FileName) + 1, Original->FileName);
> > +
> > +  File->Position = 0;
> > +  Ext4SetupFile (File, Partition);
> > +  File->InodeNum = Original->InodeNum;
> > +  File->OpenMode = 0; // Will be filled by other code
> > +
> > +  if (!Ext4InitExtentsMap (File)) {
> > +    FreePool (File->FileName);
> > +    FreePool (File->Inode);
> > +    FreePool (File);
> > +    return NULL;
> > +  }
> > +
> > +  InsertTailList (&Partition->OpenFiles, &File->OpenFilesListNode);
> > +
> > +  return File;
> > +}
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/Inode.c b/Features/Ext4Pkg/Ext4Dxe/Inode.c
> > new file mode 100644
> > index 0000000000..304bf0c4a9
> > --- /dev/null
> > +++ b/Features/Ext4Pkg/Ext4Dxe/Inode.c
> > @@ -0,0 +1,468 @@
> > +/**
> > +  @file Inode related routines
> > +
> > +  Copyright (c) 2021 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"
> > +
> > +#include <Uefi.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;
> > +
> > +  HasSecondChecksumField = Ext4InodeHasField (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) {
> > +    UINTN  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 succesful 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
> > +  )
> > +{
> > +  // DEBUG((EFI_D_INFO, "Ext4Read[Offset %lu, Length %lu]\n", Offset, *Length));
> > +  EXT4_INODE  *Inode;
> > +  UINT64      InodeSize;
> > +  UINT64      CurrentSeek;
> > +  UINTN       RemainingRead;
> > +  UINTN       BeenRead;
> > +
> > +  Inode         = File->Inode;
> > +  InodeSize     = Ext4InodeSize (Inode);
> > +  CurrentSeek   = Offset;
> > +  RemainingRead = *Length;
> > +  BeenRead      = 0;
> > +
> > +  if(Offset > InodeSize) {
> > +    return EFI_DEVICE_ERROR;
> > +  }
> > +
> > +  if (RemainingRead > InodeSize - Offset) {
> > +    RemainingRead = (UINTN)(InodeSize - Offset);
> > +  }
> > +
> > +  while(RemainingRead != 0) {
> > +    UINTN        WasRead;
> > +    EXT4_EXTENT  Extent;
> > +    UINT32       BlockOff;
> > +    EFI_STATUS   Status;
> > +    BOOLEAN      HasBackingExtent;
> > +
> > +    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) {
> > +      UINT32  HoleOff;
> > +      UINTN   HoleLen;
> > +
> > +      HoleOff = BlockOff;
> > +      HoleLen = Partition->BlockSize - HoleOff;
> > +      WasRead = HoleLen > RemainingRead ? RemainingRead : HoleLen;
> > +      // TODO: Get the hole size and memset all that
> > +      SetMem (Buffer, WasRead, 0);
> > +    } else {
> > +      UINT64  ExtentStartBytes;
> > +      UINT64  ExtentLengthBytes;
> > +      UINT64  ExtentLogicalBytes;
> > +
> > +      // Our extent offset is the difference between CurrentSeek and ExtentLogicalBytes
> > +      UINT64  ExtentOffset;
> > +      UINTN   ExtentMayRead;
> > +
> > +      ExtentStartBytes   = (((UINT64)Extent.ee_start_hi << 32) | Extent.ee_start_lo) * Partition->BlockSize;
> > +      ExtentLengthBytes  = Extent.ee_len * Partition->BlockSize;
> > +      ExtentLogicalBytes = (UINT64)Extent.ee_block * Partition->BlockSize;
> > +      ExtentOffset  = CurrentSeek - ExtentLogicalBytes;
> > +      ExtentMayRead = (UINTN)(ExtentLengthBytes - ExtentOffset);
> > +
> > +      WasRead = ExtentMayRead > RemainingRead ? RemainingRead : ExtentMayRead;
> > +
> > +      // DEBUG((EFI_D_INFO, "[ext4] may read %lu, remaining %lu\n", ExtentMayRead, RemainingRead));
> > +      // DEBUG((EFI_D_INFO, "[ext4] Reading block %lu\n", (ExtentStartBytes + ExtentOffset) / Partition->BlockSize));
> > +      Status = Ext4ReadDiskIo (Partition, Buffer, WasRead, ExtentStartBytes + ExtentOffset);
> > +
> > +      if(EFI_ERROR (Status)) {
> > +        DEBUG ((
> > +          EFI_D_ERROR,
> > +          "[ext4] Error %x 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;
> > +
> > +  // DEBUG((EFI_D_INFO, "File length %lu crc %x\n", BeenRead, CalculateCrc32(original, 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;
> > +
> > +  // HACK!: 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.
> > +  if (InodeSize < sizeof (EXT4_INODE)) {
> > +    InodeSize = sizeof (EXT4_INODE);
> > +    NeedsToZeroRest = TRUE;
> > +  }
> > +
> > +  Inode = AllocateZeroPool (InodeSize);
> > +
> > +  if (!Inode) {
> > +    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 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 (
> > +  EXT4_FILE *File
> > +  )
> > +{
> > +  BOOLEAN  HugeFile;
> > +  UINT64   Blocks;
> > +
> > +  HugeFile = Ext4HasRoCompat (File->Partition, EXT4_FEATURE_RO_COMPAT_HUGE_FILE);
> > +  Blocks   = File->Inode->i_blocks;
> > +
> > +  if(HugeFile) {
> > +    Blocks |= ((UINT64)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) {
> > +      return Blocks * File->Partition->BlockSize;
> > +    }
> > +  }
> > +
> > +  // Else, each i_blocks unit corresponds to 512 bytes
> > +  return 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  EpochSeconds   Epoch seconds.
> > +  @param  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 (Ext4InodeHasField (Inode, Field ## _extra)) {          \
> > +      SecondsEpoch |= ((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 (!Ext4InodeHasField (Inode, i_crtime)) {
> > +    SetMem (Time, sizeof (EFI_TIME), 0);
> > +    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(!Ext4HasMetadataCsum (Partition)) {
> > +    return TRUE;
> > +  }
> > +
> > +  Csum = Ext4CalculateInodeChecksum (Partition, Inode, InodeNum);
> > +
> > +  DiskCsum = Inode->i_osd2.data_linux.l_i_checksum_lo;
> > +
> > +  if (Ext4InodeHasField (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;
> > +  }
> > +
> > + #if 0
> > +    DEBUG ((EFI_D_INFO, "[ext4] Inode %d csum %x vs %x\n", InodeNum, Csum, DiskCsum));
> > + #endif
> > +
> > +  return Csum == DiskCsum;
> > +}
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/Partition.c b/Features/Ext4Pkg/Ext4Dxe/Partition.c
> > new file mode 100644
> > index 0000000000..a2c4c57e78
> > --- /dev/null
> > +++ b/Features/Ext4Pkg/Ext4Dxe/Partition.c
> > @@ -0,0 +1,120 @@
> > +/**
> > +  @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, EXT4_PARTITION *Partition
> > +  )
> > +{
> > +  // TODO: Support revision 2 (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->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;
> > +  Partition->Unmounting = TRUE;
> > +  Ext4CloseInternal (Partition->Root);
> > +
> > +  BASE_LIST_FOR_EACH_SAFE(Entry, NextEntry, &Partition->OpenFiles) {
> > +    EXT4_FILE *File = Ext4FileFromOpenFileNode(Entry);
> > +
> > +    Ext4CloseInternal(File);
> > +  }
> > +
> > +  FreePool (Partition->BlockGroups);
> > +  FreePool (Partition);
> > +
> > +  return EFI_SUCCESS;
> > +}
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/Superblock.c b/Features/Ext4Pkg/Ext4Dxe/Superblock.c
> > new file mode 100644
> > index 0000000000..18d8295a1f
> > --- /dev/null
> > +++ b/Features/Ext4Pkg/Ext4Dxe/Superblock.c
> > @@ -0,0 +1,257 @@
> > +/**
> > +  @file Superblock managing routines
> > +
> > +  Copyright (c) 2021 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;
> > +// TODO: Add btree support
> > +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;
> > +
> > +// TODO: Add meta_bg support
> > +
> > +// 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).
> > +
> > +BOOLEAN
> > +Ext4SuperblockValidate (
> > +  EXT4_SUPERBLOCK *sb
> > +  )
> > +{
> > +  if(sb->s_magic != EXT4_SIGNATURE) {
> > +    return FALSE;
> > +  }
> > +
> > +  // TODO: We should try to support EXT2/3 partitions too
> > +  if(sb->s_rev_level != EXT4_DYNAMIC_REV && sb->s_rev_level != EXT4_GOOD_OLD_REV) {
> > +    return FALSE;
> > +  }
> > +
> > +  // TODO: Is this correct behaviour? Imagine the power cuts out, should the system fail to boot because
> > +  // we're scared of touching something corrupt?
> > +  if((sb->s_state & EXT4_FS_STATE_UNMOUNTED) == 0) {
> > +    return FALSE;
> > +  }
> > +
> > +  return TRUE;
> > +}
> > +
> > +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 = Ext4CalculateChecksum (
> > +                       Partition,
> > +                       sb,
> > +                       OFFSET_OF (EXT4_SUPERBLOCK, s_checksum),
> > +                       ~0U
> > +                       );
> > +
> > +  return Checksum;
> > +}
> > +
> > +STATIC BOOLEAN
> > +Ext4VerifySuperblockChecksum (
> > +  EXT4_PARTITION *Partition, CONST EXT4_SUPERBLOCK *sb
> > +  )
> > +{
> > +  if(!Ext4HasMetadataCsum (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 succesful 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;
> > +
> > +  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;
> > +  } 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 disasterous.
> > +
> > +  if (Partition->FeaturesIncompat & ~gSupportedIncompatFeat) {
> > +    DEBUG ((EFI_D_INFO, "[Ext4] Unsupported %lx\n", Partition->FeaturesIncompat & ~gSupportedIncompatFeat));
> > +    return EFI_UNSUPPORTED;
> > +  }
> > +
> > +  // This should be removed once we add ext2/3 support in the future.
> > +  if ((Partition->FeaturesIncompat & EXT4_FEATURE_INCOMPAT_EXTENTS) == 0) {
> > +    return EFI_UNSUPPORTED;
> > +  }
> > +
> > +  // At the time of writing, it's the only supported checksum.
> > +  if (Partition->FeaturesCompat & EXT4_FEATURE_RO_COMPAT_METADATA_CSUM &&
> > +      Sb->s_checksum_type != EXT4_CHECKSUM_CRC32C) {
> > +    return EFI_UNSUPPORTED;
> > +  }
> > +
> > +  if (Partition->FeaturesIncompat & 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 ((EFI_D_INFO, "[Ext4] Unsupported ro compat %x\n", UnsupportedRoCompat));
> > +    Partition->ReadOnly = TRUE;
> > +  }
> > +
> > +  (VOID)gSupportedCompatFeat;
> > +
> > +  DEBUG ((EFI_D_INFO, "Read only = %u\n", Partition->ReadOnly));
> > +
> > +  Partition->BlockSize = 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 = Ext4MakeBlockNumberFromHalfs (Partition, Sb->s_blocks_count, Sb->s_blocks_count_hi);
> > +  Partition->NumberBlockGroups = DivU64x32 (Partition->NumberBlocks, Sb->s_blocks_per_group);
> > +
> > +  DEBUG ((
> > +    EFI_D_INFO,
> > +    "[ext4] Number of blocks = %lu\n[ext4] Number of block groups: %lu\n",
> > +    Partition->NumberBlocks,
> > +    Partition->NumberBlockGroups
> > +    ));
> > +
> > +  if (Ext4Is64Bit (Partition)) {
> > +    Partition->DescSize = Sb->s_desc_size;
> > +  } else {
> > +    Partition->DescSize = EXT4_OLD_BLOCK_DESC_SIZE;
> > +  }
> > +
> > +  if (Partition->DescSize < EXT4_64BIT_BLOCK_DESC_SIZE && Ext4Is64Bit (Partition)) {
> > +    // 64 bit filesystems need DescSize to be 64 bytes
> > +    return EFI_VOLUME_CORRUPTED;
> > +  }
> > +
> > +  if (!Ext4VerifySuperblockChecksum (Partition, Sb)) {
> > +    DEBUG ((EFI_D_ERROR, "[ext4] Bad superblock checksum %lx\n", Ext4CalculateSuperblockChecksum (Partition, Sb)));
> > +    return EFI_VOLUME_CORRUPTED;
> > +  }
> > +
> > +  NrBlocks = (UINTN)DivU64x32Remainder (
> > +                              Partition->NumberBlockGroups * Partition->DescSize,
> > +                              Partition->BlockSize,
> > +                              &NrBlocksRem
> > +                              );
> > +
> > +  if(NrBlocksRem != 0) {
> > +    NrBlocks++;
> > +  }
> > +
> > +  Partition->BlockGroups = Ext4AllocAndReadBlocks (Partition, NrBlocks, Partition->BlockSize == 1024 ? 2 : 1);
> > +
> > +  if (!Partition->BlockGroups) {
> > +    return EFI_OUT_OF_RESOURCES;
> > +  }
> > +
> > +  for (Index = 0; Index < Partition->NumberBlockGroups; Index++) {
> > +    EXT4_BLOCK_GROUP_DESC  *Desc;
> > +
> > +    Desc = Ext4GetBlockGroupDesc (Partition, Index);
> > +    if (!Ext4VerifyBlockGroupDescChecksum (Partition, Desc, Index)) {
> > +      DEBUG ((EFI_D_INFO, "[ext4] Block group descriptor %u has an invalid checksum\n", Index));
> > +      return EFI_VOLUME_CORRUPTED;
> > +    }
> > +  }
> > +
> > +  // Note that the cast below is completely safe, because EXT4_FILE is a specialisation of EFI_FILE_PROTOCOL
> > +  Status = Ext4OpenVolume (&Partition->Interface, (EFI_FILE_PROTOCOL **)&Partition->Root);
> > +
> > +  DEBUG ((EFI_D_INFO, "[ext4] Root File %p\n", Partition->Root));
> > +  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(!Ext4HasMetadataCsum (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:
> > +      UNREACHABLE ();
>
> UNREACHABLE allows the compiler to optimise the control flow with this
> assumption, e.g. remove the redundant "return 0" below. Basically you
> make a case distinction here (switch), and then tell the compiler only
> one of those cases is reachable. In my opinion, remove the switch
> altogether and add an ASSERT on the equality constraint. This would
> avoid awkward code generation and potentially reachable control flow
> interruptions (in case the constraint does not hold and the compiler
> decided to generate the branch, but just terminate it immediately or
> whatever), add debug feedback, increase readability, and so on. Also
> nitpicking, but I try to document *why* ASSERTs hold.
>
> > +      return 0;
> > +  }
> > +}
>


-- 
Pedro Falcato


-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#78525): https://edk2.groups.io/g/devel/message/78525
Mute This Topic: https://groups.io/mt/84553678/1813853
Group Owner: devel+owner at edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub [edk2-devel-archive at redhat.com]
-=-=-=-=-=-=-=-=-=-=-=-






More information about the edk2-devel-archive mailing list