From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wm1-f43.google.com (mail-wm1-f43.google.com [209.85.128.43]) by mx.groups.io with SMTP id smtpd.web10.56.1627661944774678262 for ; Fri, 30 Jul 2021 09:19:05 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20161025 header.b=IjlHj3+l; spf=pass (domain: gmail.com, ip: 209.85.128.43, mailfrom: pedro.falcato@gmail.com) Received: by mail-wm1-f43.google.com with SMTP id o5-20020a1c4d050000b02901fc3a62af78so9540226wmh.3 for ; Fri, 30 Jul 2021 09:19:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=WpKXEXgBPqI4cIi+hL7i68m5/sT62lurh+5fiYO3G+k=; b=IjlHj3+l3+8HoCegrvAPKv0pSFomDv04T7jcrXvyJU/wCd1NONlU9vapQIwz0tmMKl dUFgm+gsOmHdniUeknV6lR3re27f9U53pZtHGeByEqWMgGYund9ya1okTURHu+geXcr+ ickflbKdA3I3rTNaORObtJIgAQWd+nzbMM6jtB9vLKqJnmKfHcwh7loX9aoZRnUFlXR2 J35eTO+SwaafvDW1J65mSfVBDpgX4mfnZ4Dlsk6krqU6MaOw2W4KQwxDrZU8rOCVmMN8 sKuq8hb5gadWNjreu8XCTNbDjfFciBL4ZygFeW0gRK2ZlhsssmwmmRFoJQJL9VEhA4b4 zV4g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=WpKXEXgBPqI4cIi+hL7i68m5/sT62lurh+5fiYO3G+k=; b=WuGzyxanO5rteUcrjFCATKxRPVF/R27rtwoeDhOkCAaSRDzsAYjPhRSCPbSs8kF0/z f3hdRTcH4FKEZebiLq1Jp5Ouv3YFxYxlEbXC6ZSYu2yeL55oNW1qng1BKEJkQJjUX4Ag yHOHFDpK+Mssj2Mbcy92kaETdU00+Sz7qFdL4r9zvi+djrodlizQND0kUJhkfxgwZOxH /5Nb0tB2rvFTYiUf1jexlMqbcVHAAqPsQ5L3BS28IN24uKgTKqNJY26uapoUyINTgi38 VatJHQf9xiD6ug/z+6hDA2z69OlsU//PmFwoAQxSlfqGfQvOyCCQ6acrsWzRXoQNvzpF jnww== X-Gm-Message-State: AOAM5301/+ynb4OyJDQXaZGx91QeALA1U0Xj7w83Ehngq+EN0rcGXM9h Ti+qJfpDDUwUcN9g3jtodnoI//r2XxH7QsuX X-Google-Smtp-Source: ABdhPJzZ6Oi7uTe2xSKVIbSUoSWbN2zLyiOJh4L/L5T1e/FcGjwxQBbok7rUD3VoWIwggM83HKm8nw== X-Received: by 2002:a7b:c1d8:: with SMTP id a24mr3827075wmj.155.1627661942564; Fri, 30 Jul 2021 09:19:02 -0700 (PDT) Return-Path: Received: from PC-PEDRO.lan (bl8-253-151.dsl.telepac.pt. [85.241.253.151]) by smtp.gmail.com with ESMTPSA id u15sm2011984wmn.6.2021.07.30.09.19.01 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 30 Jul 2021 09:19:01 -0700 (PDT) From: "Pedro Falcato" To: devel@edk2.groups.io Cc: Pedro Falcato , Leif Lindholm , Michael D Kinney , Bret Barkelew Subject: [Patch 2/3] Ext4Pkg: Add Ext4Dxe driver. Date: Fri, 30 Jul 2021 17:16:41 +0100 Message-Id: <20210730161641.7245-3-pedro.falcato@gmail.com> X-Mailer: git-send-email 2.32.0 In-Reply-To: <20210730161641.7245-1-pedro.falcato@gmail.com> References: <20210730161641.7245-1-pedro.falcato@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Adds a UEFI EXT4 filesystem driver that implements the EFI_FILE_PROTOCOL and EFI_SIMPLE_FILE_SYSTEM_PROTOCOL. Cc: Leif Lindholm Cc: Michael D Kinney Cc: Bret Barkelew Signed-off-by: Pedro Falcato --- 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/Ext4D= xe/BlockGroup.c new file mode 100644 index 0000000000..10a82d40a0 --- /dev/null +++ b/Features/Ext4Pkg/Ext4Dxe/BlockGroup.c @@ -0,0 +1,208 @@ +/**=0D + @file Block group related routines=0D +=0D + Copyright (c) 2021 Pedro Falcato All rights reserved.=0D + Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.
=0D +=0D + SPDX-License-Identifier: BSD-2-Clause-Patent=0D + */=0D +=0D +#include "Ext4Dxe.h"=0D +=0D +/**=0D + Reads an inode from disk.=0D +=0D + @param[in] Partition Pointer to the opened partition.=0D + @param[in] InodeNum Number of the desired Inode=0D + @param[out] OutIno Pointer to where it will be stored a pointer t= o the read inode.=0D +=0D + @return Status of the inode read.=0D + */=0D +EFI_STATUS=0D +Ext4ReadInode (=0D + IN EXT4_PARTITION *Partition, IN EXT4_INO_NR InodeNum, OUT EXT4_INODE **= OutIno=0D + )=0D +{=0D + UINT64 InodeOffset;=0D + UINT32 BlockGroupNumber;=0D + EXT4_INODE *Inode;=0D + EXT4_BLOCK_GROUP_DESC *BlockGroup;=0D + EXT4_BLOCK_NR InodeTableStart;=0D + EFI_STATUS Status;=0D +=0D + BlockGroupNumber =3D (UINT32)DivU64x64Remainder (=0D + InodeNum - 1,=0D + Partition->SuperBlock.s_inodes_per_group,=0D + &InodeOffset=0D + );=0D +=0D + // Check for the block group number's correctness=0D + if (BlockGroupNumber >=3D Partition->NumberBlockGroups) {=0D + return EFI_VOLUME_CORRUPTED;=0D + }=0D +=0D + Inode =3D Ext4AllocateInode (Partition);=0D +=0D + if (Inode =3D=3D NULL) {=0D + return EFI_OUT_OF_RESOURCES;=0D + }=0D +=0D + BlockGroup =3D Ext4GetBlockGroupDesc (Partition, BlockGroupNumber);=0D +=0D + // Note: We'll need to check INODE_UNINIT and friends when we add write = support=0D +=0D + InodeTableStart =3D Ext4MakeBlockNumberFromHalfs (=0D + Partition,=0D + BlockGroup->bg_inode_table_lo,=0D + BlockGroup->bg_inode_table_hi=0D + );=0D +=0D + Status =3D Ext4ReadDiskIo (=0D + Partition,=0D + Inode,=0D + Partition->InodeSize,=0D + Ext4BlockToByteOffset (Partition, InodeTableStart) + InodeOff= set * Partition->InodeSize=0D + );=0D +=0D + if (EFI_ERROR (Status)) {=0D + DEBUG ((=0D + EFI_D_ERROR,=0D + "[ext4] Error reading inode: status %x; inode offset %lx"=0D + " inode table start %lu block group %lu\n",=0D + Status,=0D + InodeOffset,=0D + InodeTableStart,=0D + BlockGroupNumber=0D + ));=0D + FreePool (Inode);=0D + return Status;=0D + }=0D +=0D + if (!Ext4CheckInodeChecksum (Partition, Inode, InodeNum)) {=0D + DEBUG ((=0D + EFI_D_ERROR,=0D + "[ext4] Inode %llu has invalid checksum (calculated %x)\n",=0D + InodeNum,=0D + Ext4CalculateInodeChecksum (Partition, Inode, InodeNum)=0D + ));=0D + FreePool (Inode);=0D + return EFI_VOLUME_CORRUPTED;=0D + }=0D +=0D + *OutIno =3D Inode;=0D + return EFI_SUCCESS;=0D +}=0D +=0D +/**=0D + Calculates the checksum of the block group descriptor for METADATA_CSUM= enabled filesystems.=0D + @param[in] Partition Pointer to the opened EXT4 partition.=0D + @param[in] BlockGroupDesc Pointer to the block group descriptor.= =0D + @param[in] BlockGroupNum Number of the block group.=0D +=0D + @return The checksum.=0D +*/=0D +STATIC=0D +UINT16=0D +Ext4CalculateBlockGroupDescChecksumMetadataCsum (=0D + IN CONST EXT4_PARTITION *Partition,=0D + IN CONST EXT4_BLOCK_GROUP_DESC *BlockGroupDesc,=0D + IN UINT32 BlockGroupNum=0D + )=0D +{=0D + UINT32 Csum;=0D + UINT16 Dummy;=0D +=0D + Dummy =3D 0;=0D +=0D + Csum =3D Ext4CalculateChecksum (Partition, &BlockGroupNum, sizeof (Block= GroupNum), Partition->InitialSeed);=0D + Csum =3D Ext4CalculateChecksum (Partition, BlockGroupDesc, OFFSET_OF (EX= T4_BLOCK_GROUP_DESC, bg_checksum), Csum);=0D + Csum =3D Ext4CalculateChecksum (Partition, &Dummy, sizeof (Dummy), Csum)= ;=0D + Csum =3D=0D + Ext4CalculateChecksum (=0D + Partition,=0D + &BlockGroupDesc->bg_block_bitmap_hi,=0D + Partition->DescSize - OFFSET_OF (EXT4_BLOCK_GROUP_DESC, bg_block_bit= map_hi),=0D + Csum=0D + );=0D + return (UINT16)Csum;=0D +}=0D +=0D +/**=0D + Calculates the checksum of the block group descriptor for GDT_CSUM enab= led filesystems.=0D + @param[in] Partition Pointer to the opened EXT4 partition.=0D + @param[in] BlockGroupDesc Pointer to the block group descriptor.= =0D + @param[in] BlockGroupNum Number of the block group.=0D +=0D + @return The checksum.=0D +*/=0D +STATIC=0D +UINT16=0D +Ext4CalculateBlockGroupDescChecksumGdtCsum (=0D + IN CONST EXT4_PARTITION *Partition,=0D + IN CONST EXT4_BLOCK_GROUP_DESC *BlockGroupDesc,=0D + IN UINT32 BlockGroupNum=0D + )=0D +{=0D + UINT16 Csum;=0D + UINT16 Dummy;=0D +=0D + Dummy =3D 0;=0D +=0D + Csum =3D CalculateCrc16 (Partition->SuperBlock.s_uuid, 16, 0);=0D + Csum =3D CalculateCrc16 (&BlockGroupNum, sizeof (BlockGroupNum), Csum);= =0D + Csum =3D CalculateCrc16 (BlockGroupDesc, OFFSET_OF (EXT4_BLOCK_GROUP_DES= C, bg_checksum), Csum);=0D + Csum =3D CalculateCrc16 (&Dummy, sizeof (Dummy), Csum);=0D + Csum =3D=0D + CalculateCrc16 (=0D + &BlockGroupDesc->bg_block_bitmap_hi,=0D + Partition->DescSize - OFFSET_OF (EXT4_BLOCK_GROUP_DESC, bg_block_bit= map_hi),=0D + Csum=0D + );=0D + return Csum;=0D +}=0D +=0D +/**=0D + Checks if the checksum of the block group descriptor is correct.=0D + @param[in] Partition Pointer to the opened EXT4 partition.=0D + @param[in] BlockGroupDesc Pointer to the block group descriptor.= =0D + @param[in] BlockGroupNum Number of the block group.=0D +=0D + @return TRUE if checksum is correct, FALSE if there is corruption.=0D +*/=0D +BOOLEAN=0D +Ext4VerifyBlockGroupDescChecksum (=0D + IN CONST EXT4_PARTITION *Partition,=0D + IN CONST EXT4_BLOCK_GROUP_DESC *BlockGroupDesc,=0D + IN UINT32 BlockGroupNum=0D + )=0D +{=0D + if(!Ext4HasMetadataCsum (Partition) && !Ext4HasGdtCsum (Partition)) {=0D + return TRUE;=0D + }=0D +=0D + return Ext4CalculateBlockGroupDescChecksum (Partition, BlockGroupDesc, B= lockGroupNum) =3D=3D BlockGroupDesc->bg_checksum;=0D +}=0D +=0D +/**=0D + Calculates the checksum of the block group descriptor.=0D + @param[in] Partition Pointer to the opened EXT4 partition.=0D + @param[in] BlockGroupDesc Pointer to the block group descriptor.= =0D + @param[in] BlockGroupNum Number of the block group.=0D +=0D + @return The checksum.=0D +*/=0D +UINT16=0D +Ext4CalculateBlockGroupDescChecksum (=0D + IN CONST EXT4_PARTITION *Partition,=0D + IN CONST EXT4_BLOCK_GROUP_DESC *BlockGroupDesc,=0D + IN UINT32 BlockGroupNum=0D + )=0D +{=0D + if(Partition->FeaturesRoCompat & EXT4_FEATURE_RO_COMPAT_METADATA_CSUM) {= =0D + return Ext4CalculateBlockGroupDescChecksumMetadataCsum (Partition, Blo= ckGroupDesc, BlockGroupNum);=0D + } else if(Partition->FeaturesRoCompat & EXT4_FEATURE_RO_COMPAT_GDT_CSUM)= {=0D + return Ext4CalculateBlockGroupDescChecksumGdtCsum (Partition, BlockGro= upDesc, BlockGroupNum);=0D + }=0D +=0D + return 0;=0D +}=0D diff --git a/Features/Ext4Pkg/Ext4Dxe/Collation.c b/Features/Ext4Pkg/Ext4Dx= e/Collation.c new file mode 100644 index 0000000000..92d6a9184b --- /dev/null +++ b/Features/Ext4Pkg/Ext4Dxe/Collation.c @@ -0,0 +1,157 @@ +/**=0D + @file Unicode collation routines=0D +=0D + Copyright (c) 2021 Pedro Falcato All rights reserved.=0D + Copyright (c) 2005 - 2017, Intel Corporation. All rights reserved.=0D +=0D + SPDX-License-Identifier: BSD-2-Clause-Patent=0D + */=0D +=0D +#include =0D +=0D +#include =0D +#include =0D +#include =0D +=0D +#include =0D +=0D +STATIC EFI_UNICODE_COLLATION_PROTOCOL *gUnicodeCollationInterface =3D NUL= L;=0D +=0D +/*=0D + * Note: This code is heavily based on FatPkg's Unicode collation, since t= hey seem to know what=0D + * they're doing.=0D + * PS: Maybe all this code could be put in a library? It looks heavily sha= reable.=0D + */=0D +STATIC=0D +EFI_STATUS=0D +Ext4InitialiseUnicodeCollationInternal (=0D + IN EFI_HANDLE DriverHandle,=0D + IN EFI_GUID *ProtocolGuid,=0D + IN CONST CHAR16 *VariableName,=0D + IN CONST CHAR8 *DefaultLanguage=0D + )=0D +{=0D + UINTN NumHandles;=0D + EFI_HANDLE *Handles;=0D + EFI_UNICODE_COLLATION_PROTOCOL *Uci;=0D + BOOLEAN Iso639Language;=0D + CHAR8 *Language;=0D + EFI_STATUS RetStatus;=0D + EFI_STATUS Status;=0D +=0D + Iso639Language =3D (BOOLEAN)(ProtocolGuid =3D=3D &gEfiUnicodeCollationPr= otocolGuid);=0D + RetStatus =3D EFI_UNSUPPORTED;=0D + GetEfiGlobalVariable2 (VariableName, (VOID **)&Language, NULL);=0D +=0D + Status =3D gBS->LocateHandleBuffer (=0D + ByProtocol,=0D + ProtocolGuid,=0D + NULL,=0D + &NumHandles,=0D + &Handles=0D + );=0D + if (EFI_ERROR (Status)) {=0D + return Status;=0D + }=0D +=0D + // Note: FatPkg also doesn't close unneeded protocols.=0D + // This looks like a leak but I'm likely wrong.=0D + for(UINTN i =3D 0; i < NumHandles; i++) {=0D + Status =3D gBS->OpenProtocol (=0D + Handles[i],=0D + ProtocolGuid,=0D + (VOID **)&Uci,=0D + DriverHandle,=0D + NULL,=0D + EFI_OPEN_PROTOCOL_GET_PROTOCOL=0D + );=0D +=0D + if(EFI_ERROR (Status)) {=0D + continue;=0D + }=0D +=0D + CHAR8 *BestLanguage =3D GetBestLanguage (=0D + Uci->SupportedLanguages,=0D + Iso639Language,=0D + (Language =3D=3D NULL) ? "" : Language,=0D + DefaultLanguage,=0D + NULL=0D + );=0D + if (BestLanguage !=3D NULL) {=0D + FreePool (BestLanguage);=0D + gUnicodeCollationInterface =3D Uci;=0D + RetStatus =3D EFI_SUCCESS;=0D + break;=0D + }=0D + }=0D +=0D + if (Language !=3D NULL) {=0D + FreePool (Language);=0D + }=0D +=0D + FreePool (Handles);=0D + return RetStatus;=0D +}=0D +=0D +/**=0D + Initialises Unicode collation, which is needed for case-insensitive str= ing comparisons=0D + within the driver (a good example of an application of this is filename= comparison).=0D +=0D + @param[in] DriverHandle Handle to the driver image.=0D +=0D + @retval EFI_SUCCESS Unicode collation was successfully initialised.=0D + @retval !EFI_SUCCESS Failure.=0D +*/=0D +EFI_STATUS=0D +Ext4InitialiseUnicodeCollation (=0D + EFI_HANDLE DriverHandle=0D + )=0D +{=0D + EFI_STATUS Status;=0D +=0D + Status =3D EFI_UNSUPPORTED;=0D +=0D + //=0D + // First try to use RFC 4646 Unicode Collation 2 Protocol.=0D + //=0D + Status =3D Ext4InitialiseUnicodeCollationInternal (=0D + DriverHandle,=0D + &gEfiUnicodeCollation2ProtocolGuid,=0D + L"PlatformLang",=0D + (CONST CHAR8 *)PcdGetPtr (PcdUefiVariableDefaultPlatformLang)= =0D + );=0D + //=0D + // If the attempt to use Unicode Collation 2 Protocol fails, then we fal= l back=0D + // on the ISO 639-2 Unicode Collation Protocol.=0D + //=0D + if (EFI_ERROR (Status)) {=0D + Status =3D Ext4InitialiseUnicodeCollationInternal (=0D + DriverHandle,=0D + &gEfiUnicodeCollationProtocolGuid,=0D + L"Lang",=0D + (CONST CHAR8 *)PcdGetPtr (PcdUefiVariableDefaultLang)=0D + );=0D + }=0D +=0D + return Status;=0D +}=0D +=0D +/**=0D + Does a case-insensitive string comparison. Refer to EFI_UNICODE_COLLATI= ON_PROTOCOL's StriColl=0D + for more details.=0D +=0D + @param[in] Str1 Pointer to a null terminated string.=0D + @param[in] Str2 Pointer to a null terminated string.=0D +=0D + @retval 0 Str1 is equivalent to Str2.=0D + @retval >0 Str1 is lexically greater than Str2.=0D + @retval <0 Str1 is lexically less than Str2.=0D +*/=0D +INTN=0D +Ext4StrCmpInsensitive (=0D + IN CHAR16 *Str1,=0D + IN CHAR16 *Str2=0D + )=0D +{=0D + return gUnicodeCollationInterface->StriColl (gUnicodeCollationInterface,= Str1, Str2);=0D +}=0D diff --git a/Features/Ext4Pkg/Ext4Dxe/Crc16.c b/Features/Ext4Pkg/Ext4Dxe/Cr= c16.c new file mode 100644 index 0000000000..25a11cfde3 --- /dev/null +++ b/Features/Ext4Pkg/Ext4Dxe/Crc16.c @@ -0,0 +1,75 @@ +/**=0D + @file CRC16 calculation routines.=0D +=0D + Copyright (c) 2021 Pedro Falcato All rights reserved.=0D +=0D + SPDX-License-Identifier: BSD-2-Clause-Patent=0D + */=0D +=0D +#include =0D +=0D +STATIC CONST UINT16 gCrc16LookupTable[256] =3D=0D +{=0D + 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,=0D + 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,=0D + 0x0919, 0x1890, 0x2a0b, 0x3b82, 0x4f3d, 0x5eb4, 0x6c2f, 0x7da6,=0D + 0x8551, 0x94d8, 0xa643, 0xb7ca, 0xc375, 0xd2fc, 0xe067, 0xf1ee,=0D + 0x1232, 0x03bb, 0x3120, 0x20a9, 0x5416, 0x459f, 0x7704, 0x668d,=0D + 0x9e7a, 0x8ff3, 0xbd68, 0xace1, 0xd85e, 0xc9d7, 0xfb4c, 0xeac5,=0D + 0x1b2b, 0x0aa2, 0x3839, 0x29b0, 0x5d0f, 0x4c86, 0x7e1d, 0x6f94,=0D + 0x9763, 0x86ea, 0xb471, 0xa5f8, 0xd147, 0xc0ce, 0xf255, 0xe3dc,=0D + 0x2464, 0x35ed, 0x0776, 0x16ff, 0x6240, 0x73c9, 0x4152, 0x50db,=0D + 0xa82c, 0xb9a5, 0x8b3e, 0x9ab7, 0xee08, 0xff81, 0xcd1a, 0xdc93,=0D + 0x2d7d, 0x3cf4, 0x0e6f, 0x1fe6, 0x6b59, 0x7ad0, 0x484b, 0x59c2,=0D + 0xa135, 0xb0bc, 0x8227, 0x93ae, 0xe711, 0xf698, 0xc403, 0xd58a,=0D + 0x3656, 0x27df, 0x1544, 0x04cd, 0x7072, 0x61fb, 0x5360, 0x42e9,=0D + 0xba1e, 0xab97, 0x990c, 0x8885, 0xfc3a, 0xedb3, 0xdf28, 0xcea1,=0D + 0x3f4f, 0x2ec6, 0x1c5d, 0x0dd4, 0x796b, 0x68e2, 0x5a79, 0x4bf0,=0D + 0xb307, 0xa28e, 0x9015, 0x819c, 0xf523, 0xe4aa, 0xd631, 0xc7b8,=0D + 0x48c8, 0x5941, 0x6bda, 0x7a53, 0x0eec, 0x1f65, 0x2dfe, 0x3c77,=0D + 0xc480, 0xd509, 0xe792, 0xf61b, 0x82a4, 0x932d, 0xa1b6, 0xb03f,=0D + 0x41d1, 0x5058, 0x62c3, 0x734a, 0x07f5, 0x167c, 0x24e7, 0x356e,=0D + 0xcd99, 0xdc10, 0xee8b, 0xff02, 0x8bbd, 0x9a34, 0xa8af, 0xb926,=0D + 0x5afa, 0x4b73, 0x79e8, 0x6861, 0x1cde, 0x0d57, 0x3fcc, 0x2e45,=0D + 0xd6b2, 0xc73b, 0xf5a0, 0xe429, 0x9096, 0x811f, 0xb384, 0xa20d,=0D + 0x53e3, 0x426a, 0x70f1, 0x6178, 0x15c7, 0x044e, 0x36d5, 0x275c,=0D + 0xdfab, 0xce22, 0xfcb9, 0xed30, 0x998f, 0x8806, 0xba9d, 0xab14,=0D + 0x6cac, 0x7d25, 0x4fbe, 0x5e37, 0x2a88, 0x3b01, 0x099a, 0x1813,=0D + 0xe0e4, 0xf16d, 0xc3f6, 0xd27f, 0xa6c0, 0xb749, 0x85d2, 0x945b,=0D + 0x65b5, 0x743c, 0x46a7, 0x572e, 0x2391, 0x3218, 0x0083, 0x110a,=0D + 0xe9fd, 0xf874, 0xcaef, 0xdb66, 0xafd9, 0xbe50, 0x8ccb, 0x9d42,=0D + 0x7e9e, 0x6f17, 0x5d8c, 0x4c05, 0x38ba, 0x2933, 0x1ba8, 0x0a21,=0D + 0xf2d6, 0xe35f, 0xd1c4, 0xc04d, 0xb4f2, 0xa57b, 0x97e0, 0x8669,=0D + 0x7787, 0x660e, 0x5495, 0x451c, 0x31a3, 0x202a, 0x12b1, 0x0338,=0D + 0xfbcf, 0xea46, 0xd8dd, 0xc954, 0xbdeb, 0xac62, 0x9ef9, 0x8f70=0D +};=0D +=0D +/**=0D + Calculates the CRC16 checksum of the given buffer.=0D +=0D + @param[in] Buffer Pointer to the buffer.=0D + @param[in] Length Length of the buffer, in bytes.=0D + @param[in] InitialValue Initial value of the CRC.=0D +=0D + @return The CRC16 checksum.=0D +*/=0D +UINT16=0D +CalculateCrc16 (=0D + IN CONST VOID *Buffer,=0D + IN UINTN Length,=0D + IN UINT16 InitialValue=0D + )=0D +{=0D + CONST UINT8 *Buf;=0D + UINT16 Crc;=0D +=0D + Buf =3D Buffer;=0D +=0D + Crc =3D ~InitialValue;=0D +=0D + while(Length-- !=3D 0) {=0D + Crc =3D gCrc16LookupTable[(Crc & 0xFF) ^ *(Buf++)] ^ (Crc >> 8);=0D + }=0D +=0D + return ~Crc;=0D +}=0D diff --git a/Features/Ext4Pkg/Ext4Dxe/Crc32c.c b/Features/Ext4Pkg/Ext4Dxe/C= rc32c.c new file mode 100644 index 0000000000..3986c9b10f --- /dev/null +++ b/Features/Ext4Pkg/Ext4Dxe/Crc32c.c @@ -0,0 +1,84 @@ +/**=0D + @file CRC32c calculation routines.=0D +=0D + Copyright (c) 2021 Pedro Falcato All rights reserved.=0D +=0D + SPDX-License-Identifier: BSD-2-Clause-Patent=0D + */=0D +=0D +#include =0D +=0D +STATIC CONST UINT32 gCrc32cLookupTable[256] =3D {=0D + 0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4, 0xc79a971f, 0x35f1141c,= =0D + 0x26a1e7e8, 0xd4ca64eb, 0x8ad958cf, 0x78b2dbcc, 0x6be22838, 0x9989ab3b,= =0D + 0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24, 0x105ec76f, 0xe235446c,= =0D + 0xf165b798, 0x030e349b, 0xd7c45070, 0x25afd373, 0x36ff2087, 0xc494a384,= =0D + 0x9a879fa0, 0x68ec1ca3, 0x7bbcef57, 0x89d76c54, 0x5d1d08bf, 0xaf768bbc,= =0D + 0xbc267848, 0x4e4dfb4b, 0x20bd8ede, 0xd2d60ddd, 0xc186fe29, 0x33ed7d2a,= =0D + 0xe72719c1, 0x154c9ac2, 0x061c6936, 0xf477ea35, 0xaa64d611, 0x580f5512,= =0D + 0x4b5fa6e6, 0xb93425e5, 0x6dfe410e, 0x9f95c20d, 0x8cc531f9, 0x7eaeb2fa,= =0D + 0x30e349b1, 0xc288cab2, 0xd1d83946, 0x23b3ba45, 0xf779deae, 0x05125dad,= =0D + 0x1642ae59, 0xe4292d5a, 0xba3a117e, 0x4851927d, 0x5b016189, 0xa96ae28a,= =0D + 0x7da08661, 0x8fcb0562, 0x9c9bf696, 0x6ef07595, 0x417b1dbc, 0xb3109ebf,= =0D + 0xa0406d4b, 0x522bee48, 0x86e18aa3, 0x748a09a0, 0x67dafa54, 0x95b17957,= =0D + 0xcba24573, 0x39c9c670, 0x2a993584, 0xd8f2b687, 0x0c38d26c, 0xfe53516f,= =0D + 0xed03a29b, 0x1f682198, 0x5125dad3, 0xa34e59d0, 0xb01eaa24, 0x42752927,= =0D + 0x96bf4dcc, 0x64d4cecf, 0x77843d3b, 0x85efbe38, 0xdbfc821c, 0x2997011f,= =0D + 0x3ac7f2eb, 0xc8ac71e8, 0x1c661503, 0xee0d9600, 0xfd5d65f4, 0x0f36e6f7,= =0D + 0x61c69362, 0x93ad1061, 0x80fde395, 0x72966096, 0xa65c047d, 0x5437877e,= =0D + 0x4767748a, 0xb50cf789, 0xeb1fcbad, 0x197448ae, 0x0a24bb5a, 0xf84f3859,= =0D + 0x2c855cb2, 0xdeeedfb1, 0xcdbe2c45, 0x3fd5af46, 0x7198540d, 0x83f3d70e,= =0D + 0x90a324fa, 0x62c8a7f9, 0xb602c312, 0x44694011, 0x5739b3e5, 0xa55230e6,= =0D + 0xfb410cc2, 0x092a8fc1, 0x1a7a7c35, 0xe811ff36, 0x3cdb9bdd, 0xceb018de,= =0D + 0xdde0eb2a, 0x2f8b6829, 0x82f63b78, 0x709db87b, 0x63cd4b8f, 0x91a6c88c,= =0D + 0x456cac67, 0xb7072f64, 0xa457dc90, 0x563c5f93, 0x082f63b7, 0xfa44e0b4,= =0D + 0xe9141340, 0x1b7f9043, 0xcfb5f4a8, 0x3dde77ab, 0x2e8e845f, 0xdce5075c,= =0D + 0x92a8fc17, 0x60c37f14, 0x73938ce0, 0x81f80fe3, 0x55326b08, 0xa759e80b,= =0D + 0xb4091bff, 0x466298fc, 0x1871a4d8, 0xea1a27db, 0xf94ad42f, 0x0b21572c,= =0D + 0xdfeb33c7, 0x2d80b0c4, 0x3ed04330, 0xccbbc033, 0xa24bb5a6, 0x502036a5,= =0D + 0x4370c551, 0xb11b4652, 0x65d122b9, 0x97baa1ba, 0x84ea524e, 0x7681d14d,= =0D + 0x2892ed69, 0xdaf96e6a, 0xc9a99d9e, 0x3bc21e9d, 0xef087a76, 0x1d63f975,= =0D + 0x0e330a81, 0xfc588982, 0xb21572c9, 0x407ef1ca, 0x532e023e, 0xa145813d,= =0D + 0x758fe5d6, 0x87e466d5, 0x94b49521, 0x66df1622, 0x38cc2a06, 0xcaa7a905,= =0D + 0xd9f75af1, 0x2b9cd9f2, 0xff56bd19, 0x0d3d3e1a, 0x1e6dcdee, 0xec064eed,= =0D + 0xc38d26c4, 0x31e6a5c7, 0x22b65633, 0xd0ddd530, 0x0417b1db, 0xf67c32d8,= =0D + 0xe52cc12c, 0x1747422f, 0x49547e0b, 0xbb3ffd08, 0xa86f0efc, 0x5a048dff,= =0D + 0x8ecee914, 0x7ca56a17, 0x6ff599e3, 0x9d9e1ae0, 0xd3d3e1ab, 0x21b862a8,= =0D + 0x32e8915c, 0xc083125f, 0x144976b4, 0xe622f5b7, 0xf5720643, 0x07198540,= =0D + 0x590ab964, 0xab613a67, 0xb831c993, 0x4a5a4a90, 0x9e902e7b, 0x6cfbad78,= =0D + 0x7fab5e8c, 0x8dc0dd8f, 0xe330a81a, 0x115b2b19, 0x020bd8ed, 0xf0605bee,= =0D + 0x24aa3f05, 0xd6c1bc06, 0xc5914ff2, 0x37faccf1, 0x69e9f0d5, 0x9b8273d6,= =0D + 0x88d28022, 0x7ab90321, 0xae7367ca, 0x5c18e4c9, 0x4f48173d, 0xbd23943e,= =0D + 0xf36e6f75, 0x0105ec76, 0x12551f82, 0xe03e9c81, 0x34f4f86a, 0xc69f7b69,= =0D + 0xd5cf889d, 0x27a40b9e, 0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e,= =0D + 0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351=0D +};=0D +=0D +/**=0D + Calculates the CRC32c checksum of the given buffer.=0D +=0D + @param[in] Buffer Pointer to the buffer.=0D + @param[in] Length Length of the buffer, in bytes.=0D + @param[in] InitialValue Initial value of the CRC.=0D +=0D + @return The CRC32c checksum.=0D +*/=0D +UINT32=0D +CalculateCrc32c (=0D + IN CONST VOID *Buffer,=0D + IN UINTN Length,=0D + IN UINT32 InitialValue=0D + )=0D +{=0D + CONST UINT8 *Buf;=0D + UINT32 Crc;=0D +=0D + Buf =3D Buffer;=0D + Crc =3D ~InitialValue;=0D +=0D + while(Length-- !=3D 0) {=0D + Crc =3D gCrc32cLookupTable[(Crc & 0xFF) ^ *(Buf++)] ^ (Crc >> 8);=0D + }=0D +=0D + return ~Crc;=0D +}=0D diff --git a/Features/Ext4Pkg/Ext4Dxe/Directory.c b/Features/Ext4Pkg/Ext4Dx= e/Directory.c new file mode 100644 index 0000000000..caa97cf9f1 --- /dev/null +++ b/Features/Ext4Pkg/Ext4Dxe/Directory.c @@ -0,0 +1,492 @@ +/**=0D + @file Directory related routines=0D +=0D + Copyright (c) 2021 Pedro Falcato All rights reserved.=0D +=0D + SPDX-License-Identifier: BSD-2-Clause-Patent=0D + */=0D +=0D +#include "Ext4Dxe.h"=0D +=0D +#include =0D +=0D +/**=0D + Retrieves the filename of the directory entry and converts it to UTF-16= /UCS-2=0D +=0D + @param[in] Entry Pointer to a EXT4_DIR_ENTRY.=0D + @param[out] Ucs2FileName Pointer to an array of CHAR16's, of siz= e EXT4_NAME_MAX + 1.=0D +=0D + @retval EFI_SUCCESS The filename was succesfully retrieved and conver= ted to UCS2.=0D + @retval !EFI_SUCCESS Failure.=0D +*/=0D +EFI_STATUS=0D +Ext4GetUcs2DirentName (=0D + IN EXT4_DIR_ENTRY *Entry,=0D + OUT CHAR16 Ucs2FileName[EXT4_NAME_MAX + 1]=0D + )=0D +{=0D + CHAR8 Utf8NameBuf[EXT4_NAME_MAX + 1];=0D + UINT16 *Str;=0D + EFI_STATUS Status;=0D +=0D + CopyMem (Utf8NameBuf, Entry->name, Entry->name_len);=0D +=0D + Utf8NameBuf[Entry->name_len] =3D '\0';=0D +=0D + // Unfortunately, BaseUcs2Utf8Lib doesn't have a convert-buffer-to-buffe= r-like=0D + // function. Therefore, we need to allocate from the pool (inside UTF8St= rToUCS2),=0D + // copy it to our out buffer (Ucs2FileName) and free.=0D +=0D + Status =3D UTF8StrToUCS2 (Utf8NameBuf, &Str);=0D +=0D + if (EFI_ERROR (Status)) {=0D + return Status;=0D + }=0D +=0D + Status =3D StrCpyS (Ucs2FileName, EXT4_NAME_MAX + 1, Str);=0D +=0D + FreePool (Str);=0D +=0D + return Status;=0D +}=0D +=0D +/**=0D + Retrieves a directory entry.=0D +=0D + @param[in] Directory Pointer to the opened directory.=0D + @param[in] NameUnicode Pointer to the UCS-2 formatted filename.=0D + @param[in] Partition Pointer to the ext4 partition.=0D + @param[out] Result Pointer to the destination directory entry.= =0D +=0D + @return The result of the operation.=0D +*/=0D +EFI_STATUS=0D +Ext4RetrieveDirent (=0D + IN EXT4_FILE *File,=0D + IN CONST CHAR16 *Name,=0D + IN EXT4_PARTITION *Partition,=0D + OUT EXT4_DIR_ENTRY *res=0D + )=0D +{=0D + EFI_STATUS Status;=0D + CHAR8 *Buf;=0D + UINT64 Off;=0D + EXT4_INODE *Inode;=0D + UINT64 DirInoSize;=0D + UINT32 BlockRemainder;=0D +=0D + Status =3D EFI_NOT_FOUND;=0D + Buf =3D AllocatePool (Partition->BlockSize);=0D +=0D + if(Buf =3D=3D NULL) {=0D + return EFI_OUT_OF_RESOURCES;=0D + }=0D +=0D + Off =3D 0;=0D +=0D + Inode =3D File->Inode;=0D + DirInoSize =3D EXT4_INODE_SIZE (Inode);=0D +=0D + DivU64x32Remainder (DirInoSize, Partition->BlockSize, &BlockRemainder);= =0D + if(BlockRemainder !=3D 0) {=0D + // Directory inodes need to have block aligned sizes=0D + return EFI_VOLUME_CORRUPTED;=0D + }=0D +=0D + while(Off < DirInoSize) {=0D + UINTN Length;=0D +=0D + Length =3D Partition->BlockSize;=0D +=0D + Status =3D Ext4Read (Partition, File, Buf, Off, &Length);=0D +=0D + if (Status !=3D EFI_SUCCESS) {=0D + FreePool (Buf);=0D + return Status;=0D + }=0D +=0D + for(CHAR8 *b =3D Buf; b < Buf + Partition->BlockSize; ) {=0D + EXT4_DIR_ENTRY *Entry;=0D + UINTN RemainingBlock;=0D +=0D + Entry =3D (EXT4_DIR_ENTRY *)b;=0D + ASSERT (Entry->rec_len !=3D 0);=0D +=0D + RemainingBlock =3D Partition->BlockSize - (b - Buf);=0D +=0D + if(Entry->name_len > RemainingBlock || Entry->rec_len > RemainingBlo= ck) {=0D + // Corrupted filesystem=0D + // TODO: Do the proper ext4 corruption detection thing and dirty t= he filesystem.=0D + FreePool (Buf);=0D + return EFI_VOLUME_CORRUPTED;=0D + }=0D +=0D + // Ignore names bigger than our limit.=0D +=0D + /* Note: I think having a limit is sane because:=0D + 1) It's nicer to work with.=0D + 2) Linux and a number of BSDs also have a filename limit of 255.=0D + */=0D + if(Entry->name_len > EXT4_NAME_MAX) {=0D + continue;=0D + }=0D +=0D + // Unused entry=0D + if(Entry->inode =3D=3D 0) {=0D + b +=3D Entry->rec_len;=0D + continue;=0D + }=0D +=0D + CHAR16 Ucs2FileName[EXT4_NAME_MAX + 1];=0D +=0D + Status =3D Ext4GetUcs2DirentName (Entry, Ucs2FileName);=0D +=0D + /* In theory, this should never fail.=0D + * In reality, it's quite possible that it can fail, considering fil= enames in=0D + * Linux (and probably other nixes) are just null-terminated bags of= bytes, and don't=0D + * need to form valid ASCII/UTF-8 sequences.=0D + */=0D + if (EFI_ERROR (Status)) {=0D + // If we error out, skip this entry=0D + // I'm not sure if this is correct behaviour, but I don't think th= ere's a precedent here.=0D + b +=3D Entry->rec_len;=0D + continue;=0D + }=0D +=0D + if (Entry->name_len =3D=3D StrLen (Name) &&=0D + !Ext4StrCmpInsensitive (Ucs2FileName, (CHAR16 *)Name)) {=0D + UINTN ToCopy;=0D +=0D + ToCopy =3D Entry->rec_len > sizeof (EXT4_DIR_ENTRY) ?=0D + sizeof (EXT4_DIR_ENTRY) :=0D + Entry->rec_len;=0D +=0D + CopyMem (res, Entry, ToCopy);=0D + FreePool (Buf);=0D + return EFI_SUCCESS;=0D + }=0D +=0D + b +=3D Entry->rec_len;=0D + }=0D +=0D + Off +=3D Partition->BlockSize;=0D + }=0D +=0D + FreePool (Buf);=0D + return EFI_NOT_FOUND;=0D +}=0D +=0D +/**=0D + Opens a file using a directory entry.=0D +=0D + @param[in] Partition Pointer to the ext4 partition.=0D + @param[in] OpenMode Mode in which the file is supposed to be op= en.=0D + @param[out] OutFile Pointer to the newly opened file.=0D + @param[in] Entry Directory entry to be used.=0D +=0D + @retval EFI_STATUS Result of the operation=0D +*/=0D +EFI_STATUS=0D +Ext4OpenDirent (=0D + IN EXT4_PARTITION *Partition,=0D + IN UINT64 OpenMode,=0D + OUT EXT4_FILE **OutFile,=0D + IN EXT4_DIR_ENTRY *Entry=0D + )=0D +{=0D + EFI_STATUS Status;=0D + CHAR16 FileName[EXT4_NAME_MAX + 1];=0D + EXT4_FILE *File;=0D +=0D + File =3D AllocateZeroPool (sizeof (EXT4_FILE));=0D +=0D + if (File =3D=3D NULL) {=0D + Status =3D EFI_OUT_OF_RESOURCES;=0D + goto Error;=0D + }=0D +=0D + Status =3D Ext4GetUcs2DirentName (Entry, FileName);=0D +=0D + if (EFI_ERROR (Status)) {=0D + goto Error;=0D + }=0D +=0D + File->FileName =3D AllocateZeroPool (StrSize (FileName));=0D +=0D + if (!File->FileName) {=0D + Status =3D EFI_OUT_OF_RESOURCES;=0D + goto Error;=0D + }=0D +=0D + Status =3D Ext4InitExtentsMap (File);=0D +=0D + if (EFI_ERROR (Status)) {=0D + goto Error;=0D + }=0D +=0D + // This should not fail.=0D + StrCpyS (File->FileName, EXT4_NAME_MAX + 1, FileName);=0D +=0D + File->InodeNum =3D Entry->inode;=0D +=0D + Ext4SetupFile (File, (EXT4_PARTITION *)Partition);=0D +=0D + Status =3D Ext4ReadInode (Partition, Entry->inode, &File->Inode);=0D +=0D + if (EFI_ERROR (Status)) {=0D + goto Error;=0D + }=0D +=0D + *OutFile =3D File;=0D +=0D + InsertTailList (&Partition->OpenFiles, &File->OpenFilesListNode);=0D +=0D + return EFI_SUCCESS;=0D +=0D +Error:=0D + if (File !=3D NULL) {=0D + if (File->FileName !=3D NULL) {=0D + FreePool (File->FileName);=0D + }=0D +=0D + if (File->ExtentsMap !=3D NULL) {=0D + OrderedCollectionUninit (File->ExtentsMap);=0D + }=0D +=0D + FreePool (File);=0D + }=0D +=0D + return Status;=0D +}=0D +=0D +/**=0D + Opens a file.=0D +=0D + @param[in] Directory Pointer to the opened directory.=0D + @param[in] Name Pointer to the UCS-2 formatted filename.=0D + @param[in] Partition Pointer to the ext4 partition.=0D + @param[in] OpenMode Mode in which the file is supposed to be op= en.=0D + @param[out] OutFile Pointer to the newly opened file.=0D +=0D + @return Result of the operation.=0D +*/=0D +EFI_STATUS=0D +Ext4OpenFile (=0D + IN EXT4_FILE *Directory,=0D + IN CONST CHAR16 *Name,=0D + IN EXT4_PARTITION *Partition,=0D + IN UINT64 OpenMode,=0D + OUT EXT4_FILE **OutFile=0D + )=0D +{=0D + EXT4_DIR_ENTRY Entry;=0D + EFI_STATUS Status;=0D +=0D + Status =3D Ext4RetrieveDirent (Directory, Name, Partition, &Entry);=0D +=0D + if (EFI_ERROR (Status)) {=0D + return Status;=0D + }=0D +=0D + // EFI requires us to error out on ".." opens for the root directory=0D + if (Entry.inode =3D=3D Directory->InodeNum) {=0D + return EFI_NOT_FOUND;=0D + }=0D +=0D + return Ext4OpenDirent (Partition, OpenMode, OutFile, &Entry);=0D +}=0D +=0D +EFI_STATUS EFIAPI=0D +Ext4OpenVolume (=0D + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Partition, EFI_FILE_PROTOCOL **Root=0D + )=0D +{=0D + EXT4_INODE *RootInode;=0D + EFI_STATUS Status;=0D +=0D + Status =3D Ext4ReadInode ((EXT4_PARTITION *)Partition, 2, &RootInode);=0D +=0D + if(EFI_ERROR (Status)) {=0D + DEBUG ((EFI_D_ERROR, "[ext4] Could not open root inode - status %x\n",= Status));=0D + return Status;=0D + }=0D +=0D + EXT4_FILE *RootDir =3D AllocateZeroPool (sizeof (EXT4_FILE));=0D +=0D + if(!RootDir) {=0D + FreePool (RootInode);=0D + return EFI_OUT_OF_RESOURCES;=0D + }=0D +=0D + // The filename will be "\"(null terminated of course)=0D + RootDir->FileName =3D AllocateZeroPool (2 * sizeof (CHAR16));=0D +=0D + if (!RootDir->FileName) {=0D + FreePool (RootDir);=0D + FreePool (RootInode);=0D + return EFI_OUT_OF_RESOURCES;=0D + }=0D +=0D + RootDir->FileName[0] =3D L'\\';=0D +=0D + RootDir->Inode =3D RootInode;=0D + RootDir->InodeNum =3D 2;=0D +=0D + if (EFI_ERROR (Ext4InitExtentsMap (RootDir))) {=0D + FreePool (RootDir->FileName);=0D + FreePool (RootInode);=0D + FreePool (RootDir);=0D + return EFI_OUT_OF_RESOURCES;=0D + }=0D +=0D + Ext4SetupFile (RootDir, (EXT4_PARTITION *)Partition);=0D + *Root =3D &RootDir->Protocol;=0D +=0D + InsertTailList (&((EXT4_PARTITION *)Partition)->OpenFiles, &RootDir->Ope= nFilesListNode);=0D +=0D + return EFI_SUCCESS;=0D +}=0D +=0D +/**=0D + Validates a directory entry.=0D +=0D + @param[in] Dirent Pointer to the directory entry.=0D +=0D + @retval TRUE Valid directory entry.=0D + FALSE Invalid directory entry.=0D +*/=0D +STATIC=0D +BOOLEAN=0D +Ext4ValidDirent (=0D + IN CONST EXT4_DIR_ENTRY *Dirent=0D + )=0D +{=0D + UINTN RequiredSize =3D Dirent->name_len + EXT4_MIN_DIR_ENTRY_LEN;=0D +=0D + if (Dirent->rec_len < RequiredSize) {=0D + DEBUG ((EFI_D_ERROR, "[ext4] dirent size %lu too small (compared to %l= u)\n", Dirent->rec_len, RequiredSize));=0D + return FALSE;=0D + }=0D +=0D + // Dirent sizes need to be 4 byte aligned=0D + if (Dirent->rec_len % 4) {=0D + return FALSE;=0D + }=0D +=0D + return TRUE;=0D +}=0D +=0D +/**=0D + Reads a directory entry.=0D +=0D + @param[in] Partition Pointer to the ext4 partition.=0D + @param[in] File Pointer to the open directory.=0D + @param[out] Buffer Pointer to the output buffer.=0D + @param[in] Offset Initial directory position.=0D + @param[in out] OutLength Pointer to a UINTN that contains the length= of the buffer,=0D + and the length of the actual EFI_FILE_INFO = after the call.=0D +=0D + @return Result of the operation.=0D +*/=0D +EFI_STATUS=0D +Ext4ReadDir (=0D + IN EXT4_PARTITION *Partition,=0D + IN EXT4_FILE *File,=0D + OUT VOID *Buffer,=0D + IN UINT64 Offset,=0D + IN OUT UINTN *OutLength=0D + )=0D +{=0D + DEBUG ((EFI_D_INFO, "[ext4] Ext4ReadDir offset %lu\n", Offset));=0D + EXT4_INODE *DirIno;=0D + EFI_STATUS Status;=0D + UINT64 DirInoSize;=0D + UINTN Len;=0D + UINT32 BlockRemainder;=0D + EXT4_DIR_ENTRY Entry;=0D +=0D + DirIno =3D File->Inode;=0D + Status =3D EFI_SUCCESS;=0D + DirInoSize =3D Ext4InodeSize (DirIno);=0D +=0D + DivU64x32Remainder (DirInoSize, Partition->BlockSize, &BlockRemainder);= =0D + if(BlockRemainder !=3D 0) {=0D + // Directory inodes need to have block aligned sizes=0D + return EFI_VOLUME_CORRUPTED;=0D + }=0D +=0D + while(TRUE) {=0D + EXT4_FILE *TempFile;=0D +=0D + TempFile =3D NULL;=0D +=0D + // We (try to) read the maximum size of a directory entry at a time=0D + // Note that we don't need to read any padding that may exist after it= .=0D + Len =3D sizeof (Entry);=0D + Status =3D Ext4Read (Partition, File, &Entry, Offset, &Len);=0D +=0D + if (EFI_ERROR (Status)) {=0D + goto Out;=0D + }=0D +=0D + #if 0=0D + DEBUG ((EFI_D_INFO, "[ext4] Length read %lu, offset %lu\n", Len, Off= set));=0D + #endif=0D +=0D + if (Len =3D=3D 0) {=0D + *OutLength =3D 0;=0D + Status =3D EFI_SUCCESS;=0D + goto Out;=0D + }=0D +=0D + if (Len < EXT4_MIN_DIR_ENTRY_LEN) {=0D + Status =3D EFI_VOLUME_CORRUPTED;=0D + goto Out;=0D + }=0D +=0D + // Invalid directory entry length=0D + if (!Ext4ValidDirent (&Entry)) {=0D + DEBUG ((EFI_D_ERROR, "[ext4] Invalid dirent at offset %lu\n", Offset= ));=0D + Status =3D EFI_VOLUME_CORRUPTED;=0D + goto Out;=0D + }=0D +=0D + DEBUG ((EFI_D_INFO, "[ext4] dirent size %lu\n", Entry.rec_len));=0D +=0D + if (Entry.inode =3D=3D 0) {=0D + // When inode =3D 0, it's unused=0D + Offset +=3D Entry.rec_len;=0D + continue;=0D + }=0D +=0D + Status =3D Ext4OpenDirent (Partition, EFI_FILE_MODE_READ, &TempFile, &= Entry);=0D +=0D + if (EFI_ERROR (Status)) {=0D + goto Out;=0D + }=0D +=0D + // TODO: Is this needed?=0D + if (!StrCmp (TempFile->FileName, L".") || !StrCmp (TempFile->FileName,= L"..")) {=0D + Offset +=3D Entry.rec_len;=0D + Ext4CloseInternal (TempFile);=0D + continue;=0D + }=0D +=0D + #if 0=0D + DEBUG ((EFI_D_INFO, "[ext4] Listing file %s\n", TempFile->FileName))= ;=0D + #endif=0D +=0D + Status =3D Ext4GetFileInfo (TempFile, Buffer, OutLength);=0D + if (!EFI_ERROR (Status)) {=0D + File->Position =3D Offset + Entry.rec_len;=0D + }=0D +=0D + Ext4CloseInternal (TempFile);=0D +=0D + break;=0D + }=0D +=0D + Status =3D EFI_SUCCESS;=0D +Out:=0D + return Status;=0D +}=0D 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 @@ +/**=0D + @file Disk utilities=0D +=0D + Copyright (c) 2021 Pedro Falcato All rights reserved.=0D + SPDX-License-Identifier: BSD-2-Clause-Patent=0D + */=0D +#include "Ext4Dxe.h"=0D +=0D +/**=0D + Reads from the partition's disk using the DISK_IO protocol.=0D +=0D + @param[in] Partition Pointer to the opened ext4 partition.=0D + @param[out] Buffer Pointer to a destination buffer.=0D + @param[in] Length Length of the destination buffer.=0D + @param[in] Offset Offset, in bytes, of the location to read.=0D +=0D + @return Success status of the disk read.=0D + */=0D +EFI_STATUS=0D +Ext4ReadDiskIo (=0D + IN EXT4_PARTITION *Partition,=0D + OUT VOID *Buffer,=0D + IN UINTN Length,=0D + IN UINT64 Offset=0D + )=0D +{=0D + return Ext4DiskIo (Partition)->ReadDisk (Ext4DiskIo (Partition), Ext4Med= iaId (Partition), Offset, Length, Buffer);=0D +}=0D +=0D +/**=0D + Reads blocks from the partition's disk using the DISK_IO protocol.=0D +=0D + @param[in] Partition Pointer to the opened ext4 partition.=0D + @param[out] Buffer Pointer to a destination buffer.=0D + @param[in] NumberBlocks Length of the read, in filesystem blocks.=0D + @param[in] BlockNumber Starting block number.=0D +=0D + @return Success status of the read.=0D + */=0D +EFI_STATUS=0D +Ext4ReadBlocks (=0D + IN EXT4_PARTITION *Partition,=0D + OUT VOID *Buffer,=0D + IN UINTN NumberBlocks,=0D + IN EXT4_BLOCK_NR BlockNumber=0D + )=0D +{=0D + return Ext4ReadDiskIo (Partition, Buffer, NumberBlocks * Partition->Bloc= kSize, BlockNumber * Partition->BlockSize);=0D +}=0D +=0D +/**=0D + Allocates a buffer and reads blocks from the partition's disk using the= DISK_IO protocol.=0D + This function is deprecated and will be removed in the future.=0D +=0D + @param[in] Partition Pointer to the opened ext4 partition.=0D + @param[in] NumberBlocks Length of the read, in filesystem blocks.=0D + @param[in] BlockNumber Starting block number.=0D +=0D + @return Buffer allocated by AllocatePool, or NULL if some part of the p= rocess=0D + failed.=0D + */=0D +VOID *=0D +Ext4AllocAndReadBlocks (=0D + IN EXT4_PARTITION *Partition,=0D + IN UINTN NumberBlocks,=0D + IN EXT4_BLOCK_NR BlockNumber=0D + )=0D +{=0D + VOID *Buf;=0D +=0D + Buf =3D AllocatePool (NumberBlocks * Partition->BlockSize);=0D +=0D + if(Buf =3D=3D NULL) {=0D + return NULL;=0D + }=0D +=0D + if(Ext4ReadBlocks (Partition, Buf, NumberBlocks, BlockNumber) !=3D EFI_S= UCCESS) {=0D + FreePool (Buf);=0D + return NULL;=0D + }=0D +=0D + return Buf;=0D +}=0D 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 @@ +/**=0D + @file Raw filesystem data structures=0D +=0D + Copyright (c) 2021 Pedro Falcato All rights reserved.=0D + SPDX-License-Identifier: BSD-2-Clause-Patent=0D + =0D + Layout of an EXT2/3/4 filesystem:=0D + (note: this driver has been developed using=0D + https://www.kernel.org/doc/html/latest/filesystems/ext4/index.html as=0D + documentation).=0D + =0D + An ext2/3/4 filesystem (here on out referred to as simply an ext4 filesy= stem,=0D + due to the similarities) is composed of various concepts:=0D + =0D + 1) Superblock=0D + The superblock is the structure near (1024 bytes offset from the star= t)=0D + the start of the partition, and describes the filesystem in general.= =0D + Here, we get to know the size of the filesystem's blocks, which featu= res=0D + it supports or not, whether it's been cleanly unmounted, how many blo= cks=0D + we have, etc.=0D + =0D + 2) Block groups=0D + EXT4 filesystems are divided into block groups, and each block group = covers=0D + s_blocks_per_group(8 * Block Size) blocks. Each block group has an=0D + associated block group descriptor; these are present directly after t= he=0D + superblock. Each block group descriptor contains the location of the= =0D + inode table, and the inode and block bitmaps (note these bitmaps are = only=0D + a block long, which gets us the 8 * Block Size formula covered previo= usly).=0D + =0D + 3) Blocks=0D + The ext4 filesystem is divided in blocks, of size s_log_block_size ^ = 1024.=0D + Blocks can be allocated using individual block groups's bitmaps. Note= =0D + that block 0 is invalid and its presence on extents/block tables mean= s=0D + it's part of a file hole, and that particular location must be read a= s=0D + a block full of zeros.=0D + =0D + 4) Inodes=0D + The ext4 filesystem divides files/directories into inodes (originally= =0D + index nodes). Each file/socket/symlink/directory/etc (here on out ref= erred=0D + to as a file, since there is no distinction under the ext4 filesystem= ) is=0D + stored as a /nameless/ inode, that is stored in some block group's in= ode=0D + table. Each inode has s_inode_size size (or GOOD_OLD_INODE_SIZE if it= 's=0D + an old filesystem), and holds various metadata about the file. Since = the=0D + largest inode structure right now is ~160 bytes, the rest of the inod= e=0D + contains inline extended attributes. Inodes' data is stored using eit= her=0D + data blocks (under ext2/3) or extents (under ext4).=0D + =0D + 5) Extents=0D + Ext4 inodes store data in extents. These let N contiguous logical blo= cks=0D + that are represented by N contiguous physical blocks be represented b= y a=0D + single extent structure, which minimizes filesystem metadata bloat an= d=0D + speeds up block mapping (particularly due to the fact that high-quali= ty=0D + ext4 implementations like linux's try /really/ hard to make the file= =0D + contiguous, so it's common to have files with almost 0 fragmentation)= .=0D + Inodes that use extents store them in a tree, and the top of the tree= =0D + is stored on i_data. The tree's leaves always start with an=0D + EXT4_EXTENT_HEADER and contain EXT4_EXTENT_INDEX on eh_depth !=3D 0 a= nd=0D + EXT4_EXTENT on eh_depth =3D 0; these entries are always sorted by log= ical=0D + block.=0D + =0D + 6) Directories=0D + Ext4 directories are files that store name -> inode mappings for the= =0D + logical directory; this is where files get their names, which means e= xt4=0D + inodes do not themselves have names, since they can be linked (presen= t)=0D + multiple times with different names. Directories can store entries in= two=0D + different ways:=0D + 1) Classical linear directories: They store entries as a mostly-lin= ked=0D + mostly-list of EXT4_DIR_ENTRY.=0D + 2) Hash tree directories: These are used for larger directories, wi= th=0D + hundreds of entries, and are designed in a backwards compatible = way.=0D + These are not yet implemented in the Ext4Dxe driver.=0D + =0D + 7) Journal=0D + Ext3/4 filesystems have a journal to help protect the filesystem agai= nst=0D + system crashes. This is not yet implemented in Ext4Dxe but is describ= ed=0D + in detail in the Linux kernel's documentation.=0D + */=0D +=0D +#ifndef _EXT4_DISK_H=0D +#define _EXT4_DISK_H=0D +=0D +#include =0D +=0D +#define EXT4_SUPERBLOCK_OFFSET 1024U=0D +=0D +#define EXT4_SIGNATURE 0xEF53U=0D +=0D +#define EXT4_FS_STATE_UNMOUNTED 0x1=0D +#define EXT4_FS_STATE_ERRORS_DETECTED 0x2=0D +#define EXT4_FS_STATE_RECOVERING_ORPHANS 0x4=0D +=0D +#define EXT4_ERRORS_CONTINUE 1=0D +#define EXT4_ERRORS_RO 2=0D +#define EXT4_ERRORS_PANIC 3=0D +=0D +#define EXT4_LINUX_ID 0=0D +#define EXT4_GNU_HURD_ID 1=0D +#define EXT4_MASIX_ID 2=0D +#define EXT4_FREEBSD_ID 3=0D +#define EXT4_LITES_ID 4=0D +=0D +#define EXT4_GOOD_OLD_REV 0=0D +#define EXT4_DYNAMIC_REV 1=0D +=0D +#define EXT4_CHECKSUM_CRC32C 0x1=0D +=0D +#define EXT4_FEATURE_COMPAT_DIR_PREALLOC 0x01=0D +#define EXT4_FEATURE_COMPAT_IMAGIC_INODES 0x02=0D +#define EXT3_FEATURE_COMPAT_HAS_JOURNAL 0x04=0D +#define EXT4_FEATURE_COMPAT_EXT_ATTR 0x08=0D +#define EXT4_FEATURE_COMPAT_RESIZE_INO 0x10=0D +#define EXT4_FEATURE_COMPAT_DIR_INDEX 0x20=0D +=0D +#define EXT4_FEATURE_INCOMPAT_COMPRESSION 0x00001=0D +#define EXT4_FEATURE_INCOMPAT_FILETYPE 0x00002=0D +#define EXT4_FEATURE_INCOMPAT_RECOVER 0x00004=0D +#define EXT4_FEATURE_INCOMPAT_JOURNAL_DEV 0x00008=0D +#define EXT4_FEATURE_INCOMPAT_META_BG 0x00010=0D +#define EXT4_FEATURE_INCOMPAT_EXTENTS 0x00040=0D +#define EXT4_FEATURE_INCOMPAT_64BIT 0x00080=0D +#define EXT4_FEATURE_INCOMPAT_MMP 0x00100=0D +#define EXT4_FEATURE_INCOMPAT_FLEX_BG 0x00200=0D +#define EXT4_FEATURE_INCOMPAT_EA_INODE 0x00400=0D +// It's not clear whether or not this feature (below) is used right now=0D +#define EXT4_FEATURE_INCOMPAT_DIRDATA 0x01000=0D +#define EXT4_FEATURE_INCOMPAT_CSUM_SEED 0x02000=0D +#define EXT4_FEATURE_INCOMPAT_LARGEDIR 0x04000=0D +#define EXT4_FEATURE_INCOMPAT_INLINE_DATA 0x08000=0D +#define EXT4_FEATURE_INCOMPAT_ENCRYPT 0x10000=0D +=0D +#define EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001=0D +#define EXT4_FEATURE_RO_COMPAT_LARGE_FILE 0x0002=0D +#define EXT4_FEATURE_RO_COMPAT_BTREE_DIR 0x0004 // Unused=0D +#define EXT4_FEATURE_RO_COMPAT_HUGE_FILE 0x0008=0D +#define EXT4_FEATURE_RO_COMPAT_GDT_CSUM 0x0010=0D +#define EXT4_FEATURE_RO_COMPAT_DIR_NLINK 0x0020=0D +#define EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE 0x0040=0D +#define EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT 0x0080 // Not implemente= d in ext4=0D +#define EXT4_FEATURE_RO_COMPAT_QUOTA 0x0100=0D +#define EXT4_FEATURE_RO_COMPAT_BIGALLOC 0x0200=0D +#define EXT4_FEATURE_RO_COMPAT_METADATA_CSUM 0x0400=0D +#define EXT4_FEATURE_RO_COMPAT_REPLICA 0x0800 // Not used=0D +=0D +// We explicitly don't recognise this, so we get read only.=0D +#define EXT4_FEATURE_RO_COMPAT_READONLY 0x1000=0D +#define EXT4_FEATURE_RO_COMPAT_PROJECT 0x2000=0D +=0D +/* Important notes about the features=0D + * Absolutely needed features:=0D + * 1) Every incompat, because we might want to mount root filesystems=0D + * 2) Relevant RO_COMPATs(I'm not sure of what to do wrt quota, project= )=0D + */=0D +=0D +#define EXT4_INO_TYPE_FIFO 0x1000=0D +#define EXT4_INO_TYPE_CHARDEV 0x2000=0D +#define EXT4_INO_TYPE_DIR 0x4000=0D +#define EXT4_INO_TYPE_BLOCKDEV 0x6000=0D +#define EXT4_INO_TYPE_REGFILE 0x8000=0D +#define EXT4_INO_TYPE_SYMLINK 0xA000=0D +#define EXT4_INO_TYPE_UNIX_SOCK 0xC000=0D +=0D +/* Inode flags */=0D +#define EXT4_SECRM_FL 0x00000001=0D +#define EXT4_UNRM_FL 0x00000002=0D +#define EXT4_COMPR_FL 0x00000004=0D +#define EXT4_SYNC_FL 0x00000008=0D +#define EXT4_IMMUTABLE_FL 0x00000010=0D +#define EXT4_APPEND_FL 0x00000020=0D +#define EXT4_NODUMP_FL 0x00000040=0D +#define EXT4_NOATIME_FL 0x00000080=0D +#define EXT4_DIRTY_FL 0x00000100=0D +#define EXT4_COMPRBLK_FL 0x00000200=0D +#define EXT4_NOCOMPR_FL 0x00000400=0D +#define EXT4_ECOMPR_FL 0x00000800=0D +#define EXT4_BTREE_FL 0x00001000=0D +#define EXT4_INDEX_FL 0x00002000=0D +#define EXT4_JOURNAL_DATA_FL 0x00004000=0D +#define EXT4_NOTAIL_FL 0x00008000=0D +#define EXT4_DIRSYNC_FL 0x00010000=0D +#define EXT4_TOPDIR_FL 0x00020000=0D +#define EXT4_HUGE_FILE_FL 0x00040000=0D +#define EXT4_EXTENTS_FL 0x00080000=0D +#define EXT4_VERITY_FL 0x00100000=0D +#define EXT4_EA_INODE_FL 0x00200000=0D +#define EXT4_RESERVED_FL 0x80000000=0D +=0D +/* File type flags that are stored in the directory entries */=0D +#define EXT4_FT_UNKNOWN 0=0D +#define EXT4_FT_REG_FILE 1=0D +#define EXT4_FT_DIR 2=0D +#define EXT4_FT_CHRDEV 3=0D +#define EXT4_FT_BLKDEV 4=0D +#define EXT4_FT_FIFO 5=0D +#define EXT4_FT_SOCK 6=0D +#define EXT4_FT_SYMLINK 7=0D +=0D +typedef struct {=0D + UINT32 s_inodes_count;=0D + UINT32 s_blocks_count;=0D + UINT32 s_r_blocks_count;=0D + UINT32 s_free_blocks_count;=0D + UINT32 s_free_inodes_count;=0D + UINT32 s_first_data_block;=0D + UINT32 s_log_block_size;=0D + UINT32 s_log_frag_size;=0D + UINT32 s_blocks_per_group;=0D + UINT32 s_frags_per_group;=0D + UINT32 s_inodes_per_group;=0D + UINT32 s_mtime;=0D + UINT32 s_wtime;=0D + UINT16 s_mnt_count;=0D + UINT16 s_max_mnt_count;=0D + UINT16 s_magic;=0D + UINT16 s_state;=0D + UINT16 s_errors;=0D + UINT16 s_minor_rev_level;=0D + UINT32 s_lastcheck;=0D + UINT32 s_check_interval;=0D + UINT32 s_creator_os;=0D + UINT32 s_rev_level;=0D + UINT16 s_def_resuid;=0D + UINT16 s_def_resgid;=0D +=0D + /* Every field after this comment is revision >=3D 1 */=0D +=0D + UINT32 s_first_ino;=0D + UINT16 s_inode_size;=0D + UINT16 s_block_group_nr;=0D + UINT32 s_feature_compat;=0D + UINT32 s_feature_incompat;=0D + UINT32 s_feature_ro_compat;=0D + UINT8 s_uuid[16];=0D + UINT8 s_volume_name[16];=0D + UINT8 s_last_mounted[64];=0D + UINT32 s_algo_bitmap;=0D + UINT8 s_prealloc_blocks;=0D + UINT8 s_prealloc_dir_blocks;=0D + UINT16 unused;=0D + UINT8 s_journal_uuid[16];=0D + UINT32 s_journal_inum;=0D + UINT32 s_journal_dev;=0D + UINT32 s_last_orphan;=0D + UINT32 s_hash_seed[4];=0D + UINT8 s_def_hash_version;=0D + UINT8 s_jnl_backup_type;=0D + UINT16 s_desc_size;=0D + UINT32 s_default_mount_options;=0D + UINT32 s_first_meta_bg;=0D + UINT32 s_mkfs_time;=0D + UINT32 s_jnl_blocks[17];=0D + UINT32 s_blocks_count_hi;=0D + UINT32 s_r_blocks_count_hi;=0D + UINT32 s_free_blocks_count_hi;=0D + UINT16 s_min_extra_isize;=0D + UINT16 s_want_extra_isize;=0D + UINT32 s_flags;=0D + UINT16 s_raid_stride;=0D + UINT16 s_mmp_interval;=0D + UINT64 s_mmp_block;=0D + UINT32 s_raid_stride_width;=0D + UINT8 s_log_groups_per_flex;=0D + UINT8 s_checksum_type; // Only valid value is 1 - CRC32C=0D + UINT16 s_reserved_pad;=0D + UINT64 s_kbytes_written;=0D +=0D + // Snapshot stuff isn't used in Linux and isn't implemented here=0D + UINT32 s_snapshot_inum;=0D + UINT32 s_snapshot_id;=0D + UINT64 s_snapshot_r_blocks_count;=0D + UINT32 s_snapshot_list;=0D + UINT32 s_error_count;=0D + UINT32 s_first_error_time;=0D + UINT32 s_first_error_ino;=0D + UINT64 s_first_error_block;=0D + UINT8 s_first_error_func[32];=0D + UINT32 s_first_error_line;=0D + UINT32 s_last_error_time;=0D + UINT32 s_last_error_ino;=0D + UINT32 s_last_error_line;=0D + UINT64 s_last_error_block;=0D + UINT8 s_last_error_func[32];=0D + UINT8 s_mount_opts[64];=0D + UINT32 s_usr_quota_inum;=0D + UINT32 s_grp_quota_inum;=0D + UINT32 s_overhead_blocks;=0D + UINT32 s_backup_bgs[2]; // sparse_super2=0D + UINT8 s_encrypt_algos[4];=0D + UINT8 s_encrypt_pw_salt[16];=0D + UINT32 s_lpf_ino;=0D + UINT32 s_prj_quota_inum;=0D + UINT32 s_checksum_seed;=0D + UINT32 s_reserved[98];=0D + UINT32 s_checksum;=0D +} EXT4_SUPERBLOCK;=0D +=0D +STATIC_ASSERT (sizeof (EXT4_SUPERBLOCK) =3D=3D 1024, "ext4 superblock stru= ct has incorrect size");=0D +=0D +typedef struct {=0D + UINT32 bg_block_bitmap_lo;=0D + UINT32 bg_inode_bitmap_lo;=0D + UINT32 bg_inode_table_lo;=0D + UINT16 bg_free_blocks_count_lo;=0D + UINT16 bg_free_inodes_count_lo;=0D + UINT16 bg_used_dirs_count_lo;=0D + UINT16 bg_flags;=0D + UINT32 bg_exclude_bitmap_lo;=0D + UINT16 bg_block_bitmap_csum_lo;=0D + UINT16 bg_inode_bitmap_csum_lo;=0D + UINT16 bg_itable_unused_lo;=0D + UINT16 bg_checksum;=0D + UINT32 bg_block_bitmap_hi;=0D + UINT32 bg_inode_bitmap_hi;=0D + UINT32 bg_inode_table_hi;=0D + UINT16 bg_free_blocks_count_hi;=0D + UINT16 bg_free_inodes_count_hi;=0D + UINT16 bg_used_dirs_count_hi;=0D + UINT16 bg_itable_unused_hi;=0D + UINT32 bg_exclude_bitmap_hi;=0D + UINT16 bg_block_bitmap_csum_hi;=0D + UINT16 bg_inode_bitmap_csum_hi;=0D + UINT32 bg_reserved;=0D +} EXT4_BLOCK_GROUP_DESC;=0D +=0D +#define EXT4_OLD_BLOCK_DESC_SIZE 32=0D +#define EXT4_64BIT_BLOCK_DESC_SIZE 64=0D +=0D +STATIC_ASSERT (=0D + sizeof (EXT4_BLOCK_GROUP_DESC) =3D=3D EXT4_64BIT_BLOCK_DESC_SIZE,=0D + "ext4 block group descriptor struct has incorrect size"=0D + );=0D +=0D +#define EXT4_DBLOCKS 12=0D +#define EXT4_IND_BLOCK 12=0D +#define EXT4_DIND_BLOCK 13=0D +#define EXT4_TIND_BLOCK 14=0D +#define EXT4_NR_BLOCKS 15=0D +=0D +#define EXT4_GOOD_OLD_INODE_SIZE 128=0D +=0D +typedef struct _Ext4Inode {=0D + UINT16 i_mode;=0D + UINT16 i_uid;=0D + UINT32 i_size_lo;=0D + UINT32 i_atime;=0D + UINT32 i_ctime;=0D + UINT32 i_mtime;=0D + UINT32 i_dtime;=0D + UINT16 i_gid;=0D + UINT16 i_links;=0D + UINT32 i_blocks;=0D + UINT32 i_flags;=0D + UINT32 i_os_spec;=0D + UINT32 i_data[EXT4_NR_BLOCKS];=0D + UINT32 i_generation;=0D + UINT32 i_file_acl;=0D + UINT32 i_size_hi;=0D + UINT32 i_faddr;=0D + union {=0D + // Note: Toolchain-specific defines (such as "linux") stops us from us= ing simpler names down here.=0D + struct _Ext4_I_OSD2_Linux {=0D + UINT16 l_i_blocks_high;=0D + UINT16 l_i_file_acl_high;=0D + UINT16 l_i_uid_high;=0D + UINT16 l_i_gid_high;=0D + UINT16 l_i_checksum_lo;=0D + UINT16 l_i_reserved;=0D + } data_linux;=0D +=0D + struct _Ext4_I_OSD2_Hurd {=0D + UINT16 h_i_reserved1;=0D + UINT16 h_i_mode_high;=0D + UINT16 h_i_uid_high;=0D + UINT16 h_i_gid_high;=0D + UINT32 h_i_author;=0D + } data_hurd;=0D + } i_osd2;=0D +=0D + UINT16 i_extra_isize;=0D + UINT16 i_checksum_hi;=0D + UINT32 i_ctime_extra;=0D + UINT32 i_mtime_extra;=0D + UINT32 i_atime_extra;=0D + UINT32 i_crtime;=0D + UINT32 i_crtime_extra;=0D + UINT32 i_version_hi;=0D + UINT32 i_projid;=0D +} EXT4_INODE;=0D +=0D +typedef struct {=0D + UINT32 inode;=0D + UINT16 rec_len;=0D + UINT8 name_len;=0D + UINT8 file_type;=0D + CHAR8 name[255];=0D +} EXT4_DIR_ENTRY;=0D +=0D +#define EXT4_MIN_DIR_ENTRY_LEN 8=0D +=0D +// This on-disk structure is present at the bottom of the extent tree=0D +typedef struct {=0D + // First logical block=0D + UINT32 ee_block;=0D + // Length of the extent, in blocks=0D + UINT16 ee_len;=0D + // The physical (filesystem-relative) block is split between the high 16= bits=0D + // and the low 32 bits - this forms a 48-bit block number=0D + UINT16 ee_start_hi;=0D + UINT32 ee_start_lo;=0D +} EXT4_EXTENT;=0D +=0D +// This on-disk structure is present at all levels except the bottom=0D +typedef struct {=0D + // This index covers logical blocks from 'ei_block'=0D + UINT32 ei_block;=0D + // Block of the next level of the extent tree, similarly split in a high= and low portion.=0D + UINT32 ei_leaf_lo;=0D + UINT16 ei_leaf_hi;=0D +=0D + UINT16 ei_unused;=0D +} EXT4_EXTENT_INDEX;=0D +=0D +typedef struct {=0D + // Needs to be EXT4_EXTENT_HEADER_MAGIC=0D + UINT16 eh_magic;=0D + // Number of entries=0D + UINT16 eh_entries;=0D + // Maximum number of entries that could follow this header=0D + UINT16 eh_max;=0D + // Depth of this node in the tree - the tree can be at most 5 levels dee= p=0D + UINT16 eh_depth;=0D + // Unused by standard ext4=0D + UINT32 eh_generation;=0D +} EXT4_EXTENT_HEADER;=0D +=0D +#define EXT4_EXTENT_HEADER_MAGIC 0xF30A=0D +=0D +// Specified by ext4 docs and backed by a bunch of math=0D +#define EXT4_EXTENT_TREE_MAX_DEPTH 5=0D +=0D +typedef struct {=0D + // CRC32C of UUID + inode number + igeneration + extent block=0D + UINT32 eb_checksum;=0D +} EXT4_EXTENT_TAIL;=0D +=0D +typedef UINT64 EXT4_BLOCK_NR;=0D +typedef UINT32 EXT4_INO_NR;=0D +=0D +#define EXT4_INODE_SIZE(ino) (((UINT64)ino->i_size_hi << 32) | ino->i_siz= e_lo)=0D +=0D +#endif=0D 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 @@ +/**=0D + @file Driver entry point=0D +=0D + Copyright (c) 2021 Pedro Falcato All rights reserved.=0D + SPDX-License-Identifier: BSD-2-Clause-Patent=0D + */=0D +=0D +#include "Ext4Dxe.h"=0D +=0D +GLOBAL_REMOVE_IF_UNREFERENCED EFI_UNICODE_STRING_TABLE mExt4DriverNameTab= le[] =3D {=0D + {=0D + "eng;en",=0D + L"Ext4 File System Driver"=0D + },=0D + {=0D + NULL,=0D + NULL=0D + }=0D +};=0D +=0D +GLOBAL_REMOVE_IF_UNREFERENCED EFI_UNICODE_STRING_TABLE mExt4ControllerNam= eTable[] =3D {=0D + {=0D + "eng;en",=0D + L"Ext4 File System"=0D + },=0D + {=0D + NULL,=0D + NULL=0D + }=0D +};=0D +=0D +// Needed by gExt4ComponentName*=0D +=0D +EFI_STATUS=0D +EFIAPI=0D +Ext4ComponentNameGetDriverName (=0D + IN EFI_COMPONENT_NAME_PROTOCOL *This,=0D + IN CHAR8 *Language,=0D + OUT CHAR16 **DriverName=0D + );=0D +=0D +EFI_STATUS=0D +EFIAPI=0D +Ext4ComponentNameGetControllerName (=0D + IN EFI_COMPONENT_NAME_PROTOCOL *This,=0D + IN EFI_HANDLE ControllerHandle,=0D + IN EFI_HANDLE ChildHandle O= PTIONAL,=0D + IN CHAR8 *Language,=0D + OUT CHAR16 **ControllerName=0D + );=0D +=0D +extern EFI_COMPONENT_NAME_PROTOCOL gExt4ComponentName;=0D +=0D +GLOBAL_REMOVE_IF_UNREFERENCED EFI_COMPONENT_NAME_PROTOCOL gExt4ComponentN= ame =3D {=0D + Ext4ComponentNameGetDriverName,=0D + Ext4ComponentNameGetControllerName,=0D + "eng"=0D +};=0D +=0D +//=0D +// EFI Component Name 2 Protocol=0D +//=0D +GLOBAL_REMOVE_IF_UNREFERENCED EFI_COMPONENT_NAME2_PROTOCOL gExt4Component= Name2 =3D {=0D + (EFI_COMPONENT_NAME2_GET_DRIVER_NAME)Ext4ComponentNameGetDriverName,=0D + (EFI_COMPONENT_NAME2_GET_CONTROLLER_NAME)Ext4ComponentNameGetControllerN= ame,=0D + "en"=0D +};=0D +=0D +// Needed by gExt4BindingProtocol=0D +=0D +EFI_STATUS EFIAPI=0D +Ext4IsBindingSupported (=0D + IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,=0D + IN EFI_HANDLE ControllerHandle,=0D + IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL=0D + );=0D +=0D +EFI_STATUS EFIAPI=0D +Ext4Bind (=0D + IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,=0D + IN EFI_HANDLE ControllerHandle,=0D + IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL=0D + );=0D +=0D +EFI_STATUS EFIAPI=0D +Ext4Stop (=0D + IN EFI_DRIVER_BINDING_PROTOCOL *This,=0D + IN EFI_HANDLE ControllerHandle,=0D + IN UINTN NumberOfChildren,=0D + IN EFI_HANDLE *ChildHandleBuffer OPTIONAL=0D + );=0D +=0D +EFI_DRIVER_BINDING_PROTOCOL gExt4BindingProtocol =3D=0D +{=0D + Ext4IsBindingSupported,=0D + Ext4Bind,=0D + Ext4Stop,=0D + EXT4_DRIVER_VERSION=0D +};=0D +=0D +EFI_STATUS=0D +EFIAPI=0D +Ext4ComponentNameGetControllerName (=0D + IN EFI_COMPONENT_NAME_PROTOCOL *This,=0D + IN EFI_HANDLE ControllerHandle,=0D + IN EFI_HANDLE ChildHandle O= PTIONAL,=0D + IN CHAR8 *Language,=0D + OUT CHAR16 **ControllerName=0D + )=0D +{=0D + // TODO: Do we need to test whether we're managing the handle, like FAT = does?=0D + return LookupUnicodeString2 (=0D + Language,=0D + This->SupportedLanguages,=0D + mExt4ControllerNameTable,=0D + ControllerName,=0D + (BOOLEAN)(This =3D=3D &gExt4ComponentName)=0D + );=0D +}=0D +=0D +EFI_STATUS=0D +EFIAPI=0D +Ext4ComponentNameGetDriverName (=0D + IN EFI_COMPONENT_NAME_PROTOCOL *This,=0D + IN CHAR8 *Language,=0D + OUT CHAR16 **DriverName=0D + )=0D +{=0D + return LookupUnicodeString2 (=0D + Language,=0D + This->SupportedLanguages,=0D + mExt4DriverNameTable,=0D + DriverName,=0D + (BOOLEAN)(This =3D=3D &gExt4ComponentName)=0D + );=0D +}=0D +=0D +EFI_STATUS EFIAPI=0D +Ext4IsBindingSupported (=0D + IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,=0D + IN EFI_HANDLE ControllerHandle,=0D + IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL=0D + );=0D +=0D +EFI_STATUS EFIAPI=0D +Ext4Bind (=0D + IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,=0D + IN EFI_HANDLE ControllerHandle,=0D + IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL=0D + );=0D +=0D +EFI_STATUS EFIAPI=0D +Ext4Stop (=0D + IN EFI_DRIVER_BINDING_PROTOCOL *This,=0D + IN EFI_HANDLE ControllerHandle,=0D + IN UINTN NumberOfChildren,=0D + IN EFI_HANDLE *ChildHandleBuffer OPTIONAL=0D + )=0D +{=0D + EFI_STATUS Status;=0D + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Sfs;=0D + EXT4_PARTITION *Partition;=0D + BOOLEAN HasDiskIo2;=0D +=0D + Status =3D gBS->OpenProtocol (=0D + ControllerHandle,=0D + &gEfiSimpleFileSystemProtocolGuid,=0D + (VOID **)&Sfs,=0D + This->DriverBindingHandle,=0D + ControllerHandle,=0D + EFI_OPEN_PROTOCOL_GET_PROTOCOL=0D + );=0D +=0D + if (EFI_ERROR (Status)) {=0D + return Status;=0D + }=0D +=0D + Partition =3D (EXT4_PARTITION *)Sfs;=0D +=0D + HasDiskIo2 =3D Ext4DiskIo2 (Partition) !=3D NULL;=0D +=0D + Status =3D Ext4UnmountAndFreePartition (Partition);=0D +=0D + if (EFI_ERROR (Status)) {=0D + return Status;=0D + }=0D +=0D + Status =3D gBS->UninstallMultipleProtocolInterfaces (=0D + ControllerHandle,=0D + &gEfiSimpleFileSystemProtocolGuid,=0D + &Partition->Interface,=0D + NULL=0D + );=0D +=0D + if (EFI_ERROR (Status)) {=0D + return Status;=0D + }=0D +=0D + // Close all open protocols (DiskIo, DiskIo2, BlockIo)=0D +=0D + Status =3D gBS->CloseProtocol (=0D + ControllerHandle,=0D + &gEfiDiskIoProtocolGuid,=0D + This->DriverBindingHandle,=0D + ControllerHandle=0D + );=0D +=0D + if (EFI_ERROR (Status)) {=0D + return Status;=0D + }=0D +=0D + Status =3D gBS->CloseProtocol (=0D + ControllerHandle,=0D + &gEfiBlockIoProtocolGuid,=0D + This->DriverBindingHandle,=0D + ControllerHandle=0D + );=0D +=0D + if (EFI_ERROR (Status)) {=0D + return Status;=0D + }=0D +=0D + if(HasDiskIo2) {=0D + Status =3D gBS->CloseProtocol (=0D + ControllerHandle,=0D + &gEfiDiskIo2ProtocolGuid,=0D + This->DriverBindingHandle,=0D + ControllerHandle=0D + );=0D +=0D + if (EFI_ERROR (Status)) {=0D + return Status;=0D + }=0D + }=0D +=0D + return Status;=0D +}=0D +=0D +EFI_STATUS=0D +EFIAPI=0D +Ext4EntryPoint (=0D + IN EFI_HANDLE ImageHandle,=0D + IN EFI_SYSTEM_TABLE *SystemTable=0D + )=0D +{=0D + EFI_STATUS Status;=0D +=0D + Status =3D EfiLibInstallAllDriverProtocols2 (=0D + ImageHandle,=0D + SystemTable,=0D + &gExt4BindingProtocol,=0D + ImageHandle,=0D + &gExt4ComponentName,=0D + &gExt4ComponentName2,=0D + NULL,=0D + NULL,=0D + NULL,=0D + NULL=0D + );=0D +=0D + if(EFI_ERROR (Status)) {=0D + return Status;=0D + }=0D +=0D + return Ext4InitialiseUnicodeCollation (ImageHandle);=0D +}=0D +=0D +EFI_STATUS=0D +EFIAPI=0D +Ext4Unload (=0D + IN EFI_HANDLE ImageHandle=0D + )=0D +{=0D + EFI_STATUS Status;=0D + EFI_HANDLE *DeviceHandleBuffer;=0D + UINTN DeviceHandleCount;=0D + UINTN Index;=0D +=0D + Status =3D gBS->LocateHandleBuffer (=0D + AllHandles,=0D + NULL,=0D + NULL,=0D + &DeviceHandleCount,=0D + &DeviceHandleBuffer=0D + );=0D + if (EFI_ERROR (Status)) {=0D + return Status;=0D + }=0D +=0D + for(Index =3D 0; Index < DeviceHandleCount; Index++) {=0D + EFI_HANDLE Handle;=0D +=0D + Handle =3D DeviceHandleBuffer[Index];=0D +=0D + Status =3D EfiTestManagedDevice (Handle, ImageHandle, &gEfiDiskIoProto= colGuid);=0D +=0D + if(Status =3D=3D EFI_SUCCESS) {=0D + Status =3D gBS->DisconnectController (Handle, ImageHandle, NULL);=0D +=0D + if (EFI_ERROR (Status)) {=0D + break;=0D + }=0D + }=0D + }=0D +=0D + FreePool (DeviceHandleBuffer);=0D +=0D + Status =3D EfiLibUninstallAllDriverProtocols2 (=0D + &gExt4BindingProtocol,=0D + &gExt4ComponentName,=0D + &gExt4ComponentName2,=0D + NULL,=0D + NULL,=0D + NULL,=0D + NULL=0D + );=0D +=0D + return Status;=0D +}=0D +=0D +EFI_STATUS EFIAPI=0D +Ext4IsBindingSupported (=0D + IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,=0D + IN EFI_HANDLE ControllerHandle,=0D + IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL=0D + )=0D +{=0D + // Note to self: EFI_OPEN_PROTOCOL_TEST_PROTOCOL lets us not close the=0D + // protocol and ignore the output argument entirely=0D +=0D + EFI_STATUS Status;=0D +=0D + Status =3D gBS->OpenProtocol (=0D + ControllerHandle,=0D + &gEfiDiskIoProtocolGuid,=0D + NULL,=0D + BindingProtocol->ImageHandle,=0D + ControllerHandle,=0D + EFI_OPEN_PROTOCOL_TEST_PROTOCOL=0D + );=0D +=0D + if(EFI_ERROR (Status)) {=0D + return Status;=0D + }=0D +=0D + Status =3D gBS->OpenProtocol (=0D + ControllerHandle,=0D + &gEfiBlockIoProtocolGuid,=0D + NULL,=0D + BindingProtocol->ImageHandle,=0D + ControllerHandle,=0D + EFI_OPEN_PROTOCOL_TEST_PROTOCOL=0D + );=0D + return Status;=0D +}=0D +=0D +EFI_STATUS EFIAPI=0D +Ext4Bind (=0D + IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,=0D + IN EFI_HANDLE ControllerHandle,=0D + IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL=0D + )=0D +{=0D + EFI_DISK_IO_PROTOCOL *DiskIo;=0D + EFI_DISK_IO2_PROTOCOL *DiskIo2;=0D + EFI_BLOCK_IO_PROTOCOL *blockIo;=0D + EFI_STATUS Status;=0D +=0D + DiskIo2 =3D NULL;=0D +=0D + DEBUG ((EFI_D_INFO, "[Ext4] Binding to controller\n"));=0D +=0D + Status =3D gBS->OpenProtocol (=0D + ControllerHandle,=0D + &gEfiDiskIoProtocolGuid,=0D + (VOID **)&DiskIo,=0D + BindingProtocol->ImageHandle,=0D + ControllerHandle,=0D + EFI_OPEN_PROTOCOL_BY_DRIVER=0D + );=0D +=0D + if(EFI_ERROR (Status)) {=0D + return Status;=0D + }=0D +=0D + DEBUG ((EFI_D_INFO, "[Ext4] Controller supports DISK_IO\n"));=0D +=0D + Status =3D gBS->OpenProtocol (=0D + ControllerHandle,=0D + &gEfiDiskIo2ProtocolGuid,=0D + (VOID **)&DiskIo2,=0D + BindingProtocol->ImageHandle,=0D + ControllerHandle,=0D + EFI_OPEN_PROTOCOL_BY_DRIVER=0D + );=0D + // It's okay to not support DISK_IO2=0D +=0D + if(DiskIo2 !=3D NULL) {=0D + DEBUG ((EFI_D_INFO, "[Ext4] Controller supports DISK_IO2\n"));=0D + }=0D +=0D + Status =3D gBS->OpenProtocol (=0D + ControllerHandle,=0D + &gEfiBlockIoProtocolGuid,=0D + (VOID **)&blockIo,=0D + BindingProtocol->ImageHandle,=0D + ControllerHandle,=0D + EFI_OPEN_PROTOCOL_GET_PROTOCOL=0D + );=0D +=0D + if(EFI_ERROR (Status)) {=0D + goto Error;=0D + }=0D +=0D + DEBUG ((EFI_D_INFO, "Opening partition\n"));=0D +=0D + Status =3D Ext4OpenPartition (ControllerHandle, DiskIo, DiskIo2, blockIo= );=0D +=0D + if(!EFI_ERROR (Status)) {=0D + return Status;=0D + }=0D +=0D + DEBUG ((EFI_D_INFO, "[ext4] Error mounting %x\n", Status));=0D +=0D +Error:=0D + if(DiskIo) {=0D + gBS->CloseProtocol (=0D + ControllerHandle,=0D + &gEfiDiskIoProtocolGuid,=0D + BindingProtocol->ImageHandle,=0D + ControllerHandle=0D + );=0D + }=0D +=0D + if(DiskIo2) {=0D + gBS->CloseProtocol (=0D + ControllerHandle,=0D + &gEfiDiskIo2ProtocolGuid,=0D + BindingProtocol->ImageHandle,=0D + ControllerHandle=0D + );=0D + }=0D +=0D + if(blockIo) {=0D + gBS->CloseProtocol (=0D + ControllerHandle,=0D + &gEfiBlockIoProtocolGuid,=0D + BindingProtocol->ImageHandle,=0D + ControllerHandle=0D + );=0D + }=0D +=0D + return Status;=0D +}=0D 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 @@ +/**=0D + @file Common header for the driver=0D +=0D + Copyright (c) 2021 Pedro Falcato All rights reserved.=0D + SPDX-License-Identifier: BSD-2-Clause-Patent=0D + */=0D +=0D +#ifndef _EXT4_H=0D +#define _EXT4_H=0D +=0D +#include =0D +=0D +#include =0D +#include =0D +#include =0D +#include =0D +#include =0D +#include =0D +#include =0D +=0D +#include =0D +#include =0D +#include =0D +#include =0D +#include =0D +#include =0D +#include =0D +#include =0D +#include =0D +#include =0D +=0D +#include "Ext4Disk.h"=0D +=0D +#define EXT4_NAME_MAX 255=0D +=0D +#define EXT4_DRIVER_VERSION 0x0000=0D +=0D +/**=0D + Opens an ext4 partition and installs the Simple File System protocol.=0D +=0D + @param[in] DeviceHandle Handle to the block device.=0D + @param[in] DiskIo Pointer to an EFI_DISK_IO_PROTOCOL.= =0D + @param[in opt] DiskIo2 Pointer to an EFI_DISK_IO2_PROTOCOL,= if supported.=0D + @param[in] BlockIo Pointer to an EFI_BLOCK_IO_PROTOCOL.= =0D +=0D + @retval EFI_SUCCESS The opening was successful.=0D + !EFI_SUCCESS Opening failed.=0D + */=0D +EFI_STATUS=0D +Ext4OpenPartition (=0D + IN EFI_HANDLE DeviceHandle,=0D + IN EFI_DISK_IO_PROTOCOL *DiskIo,=0D + IN OPTIONAL EFI_DISK_IO2_PROTOCOL *DiskIo2,=0D + IN EFI_BLOCK_IO_PROTOCOL *BlockIo=0D + );=0D +=0D +typedef struct _Ext4File EXT4_FILE;=0D +=0D +typedef struct _Ext4_PARTITION {=0D + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL Interface;=0D + EFI_DISK_IO_PROTOCOL *DiskIo;=0D + EFI_DISK_IO2_PROTOCOL *DiskIo2;=0D + EFI_BLOCK_IO_PROTOCOL *BlockIo;=0D +=0D + EXT4_SUPERBLOCK SuperBlock;=0D + BOOLEAN Unmounting;=0D +=0D + UINT32 FeaturesIncompat;=0D + UINT32 FeaturesCompat;=0D + UINT32 FeaturesRoCompat;=0D + UINT32 InodeSize;=0D + UINT32 BlockSize;=0D + BOOLEAN ReadOnly;=0D + UINT64 NumberBlockGroups;=0D + EXT4_BLOCK_NR NumberBlocks;=0D +=0D + EXT4_BLOCK_GROUP_DESC *BlockGroups;=0D + UINT32 DescSize;=0D + EXT4_FILE *Root;=0D +=0D + UINT32 InitialSeed;=0D +=0D + LIST_ENTRY OpenFiles;=0D +} EXT4_PARTITION;=0D +=0D +/**=0D + Opens and parses the superblock.=0D +=0D + @param[out] Partition Partition structure to fill with filesystem d= etails.=0D + @retval EFI_SUCCESS Parsing was succesful and the partition is a= =0D + valid ext4 partition.=0D + */=0D +EFI_STATUS=0D +Ext4OpenSuperblock (=0D + OUT EXT4_PARTITION *Partition=0D + );=0D +=0D +/**=0D + Retrieves the EFI_BLOCK_IO_PROTOCOL of the partition.=0D +=0D + @param[in] Partition Pointer to the opened ext4 partition.=0D + @return The Block IO protocol associated with the partition.=0D + */=0D +STATIC inline=0D +EFI_BLOCK_IO_PROTOCOL *=0D +Ext4BlockIo (=0D + EXT4_PARTITION *Partition=0D + )=0D +{=0D + return Partition->BlockIo;=0D +}=0D +=0D +/**=0D + Retrieves the EFI_DISK_IO_PROTOCOL of the partition.=0D +=0D + @param[in] Partition Pointer to the opened ext4 partition.=0D + @return The Disk IO protocol associated with the partition.=0D + */=0D +STATIC inline=0D +EFI_DISK_IO_PROTOCOL *=0D +Ext4DiskIo (=0D + EXT4_PARTITION *Partition=0D + )=0D +{=0D + return Partition->DiskIo;=0D +}=0D +=0D +/**=0D + Retrieves the EFI_DISK_IO2_PROTOCOL of the partition.=0D +=0D + @param[in] Partition Pointer to the opened ext4 partition.=0D + @return The Disk IO 2 protocol associated with the partition, or NULL i= f=0D + not supported.=0D + */=0D +STATIC inline=0D +EFI_DISK_IO2_PROTOCOL *=0D +Ext4DiskIo2 (=0D + EXT4_PARTITION *Partition=0D + )=0D +{=0D + return Partition->DiskIo2;=0D +}=0D +=0D +/**=0D + Retrieves the media ID of the partition.=0D +=0D + @param[in] Partition Pointer to the opened ext4 partition.=0D + @return The media ID associated with the partition.=0D + */=0D +STATIC inline=0D +UINT32=0D +Ext4MediaId (=0D + EXT4_PARTITION *Partition=0D + )=0D +{=0D + return Partition->BlockIo->Media->MediaId;=0D +}=0D +=0D +/**=0D + Reads from the partition's disk using the DISK_IO protocol.=0D +=0D + @param[in] Partition Pointer to the opened ext4 partition.=0D + @param[out] Buffer Pointer to a destination buffer.=0D + @param[in] Length Length of the destination buffer.=0D + @param[in] Offset Offset, in bytes, of the location to read.=0D +=0D + @return Success status of the disk read.=0D + */=0D +EFI_STATUS=0D +Ext4ReadDiskIo (=0D + IN EXT4_PARTITION *Partition,=0D + OUT VOID *Buffer,=0D + IN UINTN Length,=0D + IN UINT64 Offset=0D + );=0D +=0D +/**=0D + Reads blocks from the partition's disk using the DISK_IO protocol.=0D +=0D + @param[in] Partition Pointer to the opened ext4 partition.=0D + @param[out] Buffer Pointer to a destination buffer.=0D + @param[in] NumberBlocks Length of the read, in filesystem blocks.=0D + @param[in] BlockNumber Starting block number.=0D +=0D + @return Success status of the read.=0D + */=0D +EFI_STATUS=0D +Ext4ReadBlocks (=0D + IN EXT4_PARTITION *Partition,=0D + OUT VOID *Buffer,=0D + IN UINTN NumberBlocks,=0D + IN EXT4_BLOCK_NR BlockNumber=0D + );=0D +=0D +/**=0D + Allocates a buffer and reads blocks from the partition's disk using the= DISK_IO protocol.=0D + This function is deprecated and will be removed in the future.=0D +=0D + @param[in] Partition Pointer to the opened ext4 partition.=0D + @param[in] NumberBlocks Length of the read, in filesystem blocks.=0D + @param[in] BlockNumber Starting block number.=0D +=0D + @return Buffer allocated by AllocatePool, or NULL if some part of the p= rocess=0D + failed.=0D + */=0D +VOID *=0D +Ext4AllocAndReadBlocks (=0D + IN EXT4_PARTITION *Partition,=0D + IN UINTN NumberBlocks,=0D + IN EXT4_BLOCK_NR BlockNumber=0D + );=0D +=0D +/**=0D + Checks if the opened partition has the 64-bit feature (see EXT4_FEATURE= _INCOMPAT_64BIT).=0D +=0D + @param[in] Partition Pointer to the opened ext4 partition.=0D +=0D + @return TRUE if EXT4_FEATURE_INCOMPAT_64BIT is enabled, else FALSE.=0D + */=0D +STATIC inline=0D +BOOLEAN=0D +Ext4Is64Bit (=0D + IN CONST EXT4_PARTITION *Partition=0D + )=0D +{=0D + return Partition->FeaturesIncompat & EXT4_FEATURE_INCOMPAT_64BIT;=0D +}=0D +=0D +/**=0D + Composes an EXT4_BLOCK_NR safely, from two halfs.=0D +=0D + @param[in] Partition Pointer to the opened ext4 partition.=0D + @param[in] Low Low half of the block number.=0D + @param[in] High High half of the block number.=0D +=0D + @return The block number formed by Low, and if 64 bit is enabled, High.= =0D + */=0D +STATIC inline=0D +EXT4_BLOCK_NR=0D +Ext4MakeBlockNumberFromHalfs (=0D + IN CONST EXT4_PARTITION *Partition,=0D + IN UINT32 Low,=0D + IN UINT32 High=0D + )=0D +{=0D + // High might have garbage if it's not a 64 bit filesystem=0D + return Ext4Is64Bit (Partition) ? Low | ((UINT64)High << 32) : Low;=0D +}=0D +=0D +/**=0D + Retrieves a block group descriptor of the ext4 filesystem.=0D +=0D + @param[in] Partition Pointer to the opened ext4 partition.=0D + @param[in] BlockGroup Block group number.=0D +=0D + @return A pointer to the block group descriptor.=0D + */=0D +STATIC inline=0D +EXT4_BLOCK_GROUP_DESC *=0D +Ext4GetBlockGroupDesc (=0D + IN EXT4_PARTITION *Partition,=0D + IN UINT32 BlockGroup=0D + )=0D +{=0D + // Maybe assert that the block group nr isn't a nonsense number?=0D + return (EXT4_BLOCK_GROUP_DESC *)((CHAR8 *)Partition->BlockGroups + Block= Group * Partition->DescSize);=0D +}=0D +=0D +/**=0D + Reads an inode from disk.=0D +=0D + @param[in] Partition Pointer to the opened partition.=0D + @param[in] InodeNum Number of the desired Inode=0D + @param[out] OutIno Pointer to where it will be stored a pointer t= o the read inode.=0D +=0D + @return Status of the inode read.=0D + */=0D +EFI_STATUS=0D +Ext4ReadInode (=0D + IN EXT4_PARTITION *Partition,=0D + IN EXT4_INO_NR InodeNum,=0D + OUT EXT4_INODE **OutIno=0D + );=0D +=0D +/**=0D + Converts blocks to bytes.=0D +=0D + @param[in] Partition Pointer to the opened partition.=0D + @param[in] Block Block number/number of blocks.=0D +=0D + @return The number of bytes.=0D + */=0D +STATIC inline=0D +UINT64=0D +Ext4BlockToByteOffset (=0D + IN CONST EXT4_PARTITION *Partition,=0D + IN EXT4_BLOCK_NR Block=0D + )=0D +{=0D + return Partition->BlockSize * Block;=0D +}=0D +=0D +/**=0D + Reads from an EXT4 inode.=0D + @param[in] Partition Pointer to the opened EXT4 partition.=0D + @param[in] File Pointer to the opened file.=0D + @param[out] Buffer Pointer to the buffer.=0D + @param[in] Offset Offset of the read.=0D + @param[in out] Length Pointer to the length of the buffer, in b= ytes.=0D + After a succesful read, it's updated to t= he number of read bytes.=0D +=0D + @return Status of the read operation.=0D +*/=0D +EFI_STATUS=0D +Ext4Read (=0D + IN EXT4_PARTITION *Partition,=0D + IN EXT4_FILE *File,=0D + OUT VOID *Buffer,=0D + IN UINT64 Offset,=0D + IN OUT UINTN *Length=0D + );=0D +=0D +/**=0D + Retrieves the size of the inode.=0D +=0D + @param[in] Inode Pointer to the ext4 inode.=0D +=0D + @return The size of the inode, in bytes.=0D + */=0D +STATIC inline=0D +UINT64=0D +Ext4InodeSize (=0D + CONST EXT4_INODE *Inode=0D + )=0D +{=0D + return ((UINT64)Inode->i_size_hi << 32) | Inode->i_size_lo;=0D +}=0D +=0D +/**=0D + Retrieves an extent from an EXT4 inode.=0D + @param[in] Partition Pointer to the opened EXT4 partition.=0D + @param[in] File Pointer to the opened file.=0D + @param[in] LogicalBlock Block number which the returned extent mu= st cover.=0D + @param[out] Extent Pointer to the output buffer, where the e= xtent will be copied to.=0D +=0D + @retval EFI_SUCCESS Retrieval was succesful.=0D + @retval EFI_NO_MAPPING Block has no mapping.=0D +*/=0D +EFI_STATUS=0D +Ext4GetExtent (=0D + IN EXT4_PARTITION *Partition,=0D + IN EXT4_FILE *File,=0D + IN EXT4_BLOCK_NR LogicalBlock,=0D + OUT EXT4_EXTENT *Extent=0D + );=0D +=0D +struct _Ext4File {=0D + EFI_FILE_PROTOCOL Protocol;=0D + EXT4_INODE *Inode;=0D + EXT4_INO_NR InodeNum;=0D +=0D + UINT64 OpenMode;=0D + UINT64 Position;=0D +=0D + EXT4_PARTITION *Partition;=0D + CHAR16 *FileName;=0D +=0D + ORDERED_COLLECTION *ExtentsMap;=0D +=0D + LIST_ENTRY OpenFilesListNode;=0D +};=0D +=0D +#define Ext4FileFromOpenFileNode(Node) BASE_CR (Node, EXT4_FILE, OpenFile= sListNode)=0D +=0D +/**=0D + Retrieves a directory entry.=0D +=0D + @param[in] Directory Pointer to the opened directory.=0D + @param[in] NameUnicode Pointer to the UCS-2 formatted filename.=0D + @param[in] Partition Pointer to the ext4 partition.=0D + @param[out] Result Pointer to the destination directory entry.= =0D +=0D + @return The result of the operation.=0D +*/=0D +EFI_STATUS=0D +Ext4RetrieveDirent (=0D + IN EXT4_FILE *Directory,=0D + IN CONST CHAR16 *NameUnicode,=0D + IN EXT4_PARTITION *Partition,=0D + OUT EXT4_DIR_ENTRY *Result=0D + );=0D +=0D +/**=0D + Opens a file.=0D +=0D + @param[in] Directory Pointer to the opened directory.=0D + @param[in] Name Pointer to the UCS-2 formatted filename.=0D + @param[in] Partition Pointer to the ext4 partition.=0D + @param[in] OpenMode Mode in which the file is supposed to be op= en.=0D + @param[out] OutFile Pointer to the newly opened file.=0D +=0D + @return Result of the operation.=0D +*/=0D +EFI_STATUS=0D +Ext4OpenFile (=0D + IN EXT4_FILE *Directory,=0D + IN CONST CHAR16 *Name,=0D + IN EXT4_PARTITION *Partition,=0D + IN UINT64 OpenMode,=0D + OUT EXT4_FILE **OutFile=0D + );=0D +=0D +/**=0D + Opens a file using a directory entry.=0D +=0D + @param[in] Partition Pointer to the ext4 partition.=0D + @param[in] OpenMode Mode in which the file is supposed to be op= en.=0D + @param[out] OutFile Pointer to the newly opened file.=0D + @param[in] Entry Directory entry to be used.=0D +=0D + @retval EFI_STATUS Result of the operation=0D +*/=0D +EFI_STATUS=0D +Ext4OpenDirent (=0D + IN EXT4_PARTITION *Partition,=0D + IN UINT64 OpenMode,=0D + OUT EXT4_FILE **OutFile,=0D + IN EXT4_DIR_ENTRY *Entry=0D + );=0D +=0D +/**=0D + Allocates a zeroed inode structure.=0D + @param[in] Partition Pointer to the opened EXT4 partition.=0D +=0D + @return Pointer to the allocated structure, from the pool,=0D + with size Partition->InodeSize.=0D +*/=0D +EXT4_INODE *=0D +Ext4AllocateInode (=0D + IN EXT4_PARTITION *Partition=0D + );=0D +=0D +// Part of the EFI_SIMPLE_FILE_SYSTEM_PROTOCOL=0D +=0D +EFI_STATUS EFIAPI=0D +Ext4OpenVolume (=0D + IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Partition,=0D + IN EFI_FILE_PROTOCOL **Root=0D + );=0D +=0D +// End of EFI_SIMPLE_FILE_SYSTEM_PROTOCOL=0D +=0D +/**=0D + Sets up the protocol and metadata of a file that is being opened.=0D +=0D + @param[in out] File Pointer to the file.=0D + @param[in] Partition Pointer to the opened partition.=0D + */=0D +VOID=0D +Ext4SetupFile (=0D + IN OUT EXT4_FILE *File,=0D + IN EXT4_PARTITION *Partition=0D + );=0D +=0D +/**=0D + Closes a file.=0D +=0D + @param[in] File Pointer to the file.=0D +=0D + @return Status of the closing of the file.=0D + */=0D +EFI_STATUS=0D +Ext4CloseInternal (=0D + IN EXT4_FILE *File=0D + );=0D +=0D +// Part of the EFI_FILE_PROTOCOL=0D +=0D +EFI_STATUS EFIAPI=0D +Ext4Open (=0D + IN EFI_FILE_PROTOCOL *This,=0D + OUT EFI_FILE_PROTOCOL **NewHandle,=0D + IN CHAR16 *FileName,=0D + IN UINT64 OpenMode,=0D + IN UINT64 Attributes=0D + );=0D +=0D +EFI_STATUS EFIAPI=0D +Ext4Close (=0D + IN EFI_FILE_PROTOCOL *This=0D + );=0D +=0D +EFI_STATUS EFIAPI=0D +Ext4Delete (=0D + IN EFI_FILE_PROTOCOL *This=0D + );=0D +=0D +EFI_STATUS=0D +EFIAPI=0D +Ext4ReadFile (=0D + IN EFI_FILE_PROTOCOL *This,=0D + IN OUT UINTN *BufferSize,=0D + OUT VOID *Buffer=0D + );=0D +=0D +EFI_STATUS=0D +EFIAPI=0D +Ext4WriteFile (=0D + IN EFI_FILE_PROTOCOL *This,=0D + IN OUT UINTN *BufferSize,=0D + IN VOID *Buffer=0D + );=0D +=0D +EFI_STATUS=0D +EFIAPI=0D +Ext4GetPosition (=0D + IN EFI_FILE_PROTOCOL *This,=0D + OUT UINT64 *Position=0D + );=0D +=0D +EFI_STATUS=0D +EFIAPI=0D +Ext4SetPosition (=0D + IN EFI_FILE_PROTOCOL *This,=0D + IN UINT64 Position=0D + );=0D +=0D +EFI_STATUS=0D +EFIAPI=0D +Ext4GetInfo (=0D + IN EFI_FILE_PROTOCOL *This,=0D + IN EFI_GUID *InformationType,=0D + IN OUT UINTN *BufferSize,=0D + OUT VOID *Buffer=0D + );=0D +=0D +// EFI_FILE_PROTOCOL implementation ends here.=0D +=0D +/**=0D + Checks if a file is a directory.=0D + @param[in] File Pointer to the opened file.=0D +=0D + @return TRUE if file is a directory.=0D +*/=0D +BOOLEAN=0D +Ext4FileIsDir (=0D + IN CONST EXT4_FILE *File=0D + );=0D +=0D +/**=0D + Checks if a file is a regular file.=0D + @param[in] File Pointer to the opened file.=0D +=0D + @return BOOLEAN TRUE if file is a regular file.=0D +*/=0D +BOOLEAN=0D +Ext4FileIsReg (=0D + IN CONST EXT4_FILE *File=0D + );=0D +=0D +// In EFI we can't open FIFO pipes, UNIX sockets, character/block devices = since these concepts are=0D +// at the kernel level and are OS dependent.=0D +=0D +/**=0D + Checks if a file is openable.=0D + @param[in] File Pointer to the file trying to be opened.=0D +=0D +=0D + @return TRUE if file is openable. A file is considered openable if=0D + it's a regular file or a directory, since most other file types= =0D + don't make sense under UEFI.=0D +*/=0D +STATIC inline=0D +BOOLEAN=0D +Ext4FileIsOpenable (=0D + IN CONST EXT4_FILE *File=0D + )=0D +{=0D + return Ext4FileIsReg (File) || Ext4FileIsDir (File);=0D +}=0D +=0D +#define Ext4InodeHasField(Inode, \=0D + Field) (Inode->i_extra_isize + EXT4_GOOD_OLD_IN= ODE_SIZE >=3D OFFSET_OF (EXT4_INODE, Field) + \=0D + sizeof (((EXT4_INODE *)NULL)->Field))=0D +=0D +/**=0D + Calculates the physical space used by a file.=0D + @param[in] File Pointer to the opened file.=0D +=0D + @return Physical space used by a file, in bytes.=0D +*/=0D +UINT64=0D +Ext4FilePhysicalSpace (=0D + EXT4_FILE *File=0D + );=0D +=0D +/**=0D + Gets the file's last access time.=0D + @param[in] File Pointer to the opened file.=0D + @param[out] Time Pointer to an EFI_TIME structure.=0D +*/=0D +VOID=0D +Ext4FileATime (=0D + IN EXT4_FILE *File,=0D + OUT EFI_TIME *Time=0D + );=0D +=0D +/**=0D + Gets the file's last (data) modification time.=0D + @param[in] File Pointer to the opened file.=0D + @param[out] Time Pointer to an EFI_TIME structure.=0D +*/=0D +VOID=0D +Ext4FileMTime (=0D + IN EXT4_FILE *File,=0D + OUT EFI_TIME *Time=0D + );=0D +=0D +/**=0D + Gets the file's creation time, if possible.=0D + @param[in] File Pointer to the opened file.=0D + @param[out] Time Pointer to an EFI_TIME structure.=0D + In the case where the the creation time isn't re= corded,=0D + Time is zeroed.=0D +*/=0D +VOID=0D +Ext4FileCreateTime (=0D + IN EXT4_FILE *File, OUT EFI_TIME *Time=0D + );=0D +=0D +/**=0D + Initialises Unicode collation, which is needed for case-insensitive str= ing comparisons=0D + within the driver (a good example of an application of this is filename= comparison).=0D +=0D + @param[in] DriverHandle Handle to the driver image.=0D +=0D + @retval EFI_SUCCESS Unicode collation was successfully initialised.=0D + @retval !EFI_SUCCESS Failure.=0D +*/=0D +EFI_STATUS=0D +Ext4InitialiseUnicodeCollation (=0D + EFI_HANDLE DriverHandle=0D + );=0D +=0D +/**=0D + Does a case-insensitive string comparison. Refer to EFI_UNICODE_COLLATI= ON_PROTOCOL's StriColl=0D + for more details.=0D +=0D + @param[in] Str1 Pointer to a null terminated string.=0D + @param[in] Str2 Pointer to a null terminated string.=0D +=0D + @retval 0 Str1 is equivalent to Str2.=0D + @retval >0 Str1 is lexically greater than Str2.=0D + @retval <0 Str1 is lexically less than Str2.=0D +*/=0D +INTN=0D +Ext4StrCmpInsensitive (=0D + IN CHAR16 *Str1,=0D + IN CHAR16 *Str2=0D + );=0D +=0D +/**=0D + Retrieves the filename of the directory entry and converts it to UTF-16= /UCS-2=0D +=0D + @param[in] Entry Pointer to a EXT4_DIR_ENTRY.=0D + @param[out] Ucs2FileName Pointer to an array of CHAR16's, of siz= e EXT4_NAME_MAX + 1.=0D +=0D + @retval EFI_SUCCESS Unicode collation was successfully initialised.=0D + @retval !EFI_SUCCESS Failure.=0D +*/=0D +EFI_STATUS=0D +Ext4GetUcs2DirentName (=0D + IN EXT4_DIR_ENTRY *Entry,=0D + OUT CHAR16 Ucs2FileName[EXT4_NAME_MAX + 1]=0D + );=0D +=0D +/**=0D + Retrieves information about the file and stores it in the EFI_FILE_INFO= format.=0D +=0D + @param[in] File Pointer to an opened file.=0D + @param[out] Info Pointer to a EFI_FILE_INFO.=0D + @param[in out] BufferSize Pointer to the buffer size=0D +=0D + @return Status of the file information request.=0D +*/=0D +EFI_STATUS=0D +Ext4GetFileInfo (=0D + IN EXT4_FILE *File,=0D + OUT EFI_FILE_INFO *Info,=0D + IN OUT UINTN *BufferSize=0D + );=0D +=0D +/**=0D + Reads a directory entry.=0D +=0D + @param[in] Partition Pointer to the ext4 partition.=0D + @param[in] File Pointer to the open directory.=0D + @param[out] Buffer Pointer to the output buffer.=0D + @param[in] Offset Initial directory position.=0D + @param[in out] OutLength Pointer to a UINTN that contains the length= of the buffer,=0D + and the length of the actual EFI_FILE_INFO = after the call.=0D +=0D + @return Result of the operation.=0D +*/=0D +EFI_STATUS=0D +Ext4ReadDir (=0D + IN EXT4_PARTITION *Partition,=0D + IN EXT4_FILE *File,=0D + OUT VOID *Buffer,=0D + IN UINT64 Offset,=0D + IN OUT UINTN *OutLength=0D + );=0D +=0D +/**=0D + Initialises the (empty) extents map, that will work as a cache of exten= ts.=0D +=0D + @param[in] File Pointer to the open file.=0D +=0D + @return Result of the operation.=0D +*/=0D +EFI_STATUS=0D +Ext4InitExtentsMap (=0D + IN EXT4_FILE *File=0D + );=0D +=0D +/**=0D + Frees the extents map, deleting every extent stored.=0D +=0D + @param[in] File Pointer to the open file.=0D +*/=0D +VOID=0D +Ext4FreeExtentsMap (=0D + IN EXT4_FILE *File=0D + );=0D +=0D +/**=0D + Calculates the CRC32c checksum of the given buffer.=0D +=0D + @param[in] Buffer Pointer to the buffer.=0D + @param[in] Length Length of the buffer, in bytes.=0D + @param[in] InitialValue Initial value of the CRC.=0D +=0D + @return The CRC32c checksum.=0D +*/=0D +UINT32=0D +CalculateCrc32c (=0D + IN CONST VOID *Buffer,=0D + IN UINTN Length,=0D + IN UINT32 InitialValue=0D + );=0D +=0D +/**=0D + Calculates the CRC16 checksum of the given buffer.=0D +=0D + @param[in] Buffer Pointer to the buffer.=0D + @param[in] Length Length of the buffer, in bytes.=0D + @param[in] InitialValue Initial value of the CRC.=0D +=0D + @return The CRC16 checksum.=0D +*/=0D +UINT16=0D +CalculateCrc16 (=0D + IN CONST VOID *Buffer,=0D + IN UINTN Length,=0D + IN UINT16 InitialValue=0D + );=0D +=0D +/**=0D + Calculates the checksum of the given buffer.=0D + @param[in] Partition Pointer to the opened EXT4 partition.=0D + @param[in] Buffer Pointer to the buffer.=0D + @param[in] Length Length of the buffer, in bytes.=0D + @param[in] InitialValue Initial value of the CRC.=0D +=0D + @return The checksum.=0D +*/=0D +UINT32=0D +Ext4CalculateChecksum (=0D + IN CONST EXT4_PARTITION *Partition,=0D + IN CONST VOID *Buffer,=0D + IN UINTN Length,=0D + IN UINT32 InitialValue=0D + );=0D +=0D +/**=0D + Calculates the checksum of the given inode.=0D + @param[in] Partition Pointer to the opened EXT4 partition.=0D + @param[in] Inode Pointer to the inode.=0D + @param[in] InodeNum Inode number.=0D +=0D + @return The checksum.=0D +*/=0D +UINT32=0D +Ext4CalculateInodeChecksum (=0D + IN CONST EXT4_PARTITION *Partition,=0D + IN CONST EXT4_INODE *Inode,=0D + IN EXT4_INO_NR InodeNum=0D + );=0D +=0D +/**=0D + Checks if the checksum of the inode is correct.=0D + @param[in] Partition Pointer to the opened EXT4 partition.=0D + @param[in] Inode Pointer to the inode.=0D + @param[in] InodeNum Inode number.=0D +=0D + @return TRUE if checksum is correct, FALSE if there is corruption.=0D +*/=0D +BOOLEAN=0D +Ext4CheckInodeChecksum (=0D + IN CONST EXT4_PARTITION *Partition,=0D + IN CONST EXT4_INODE *Inode,=0D + IN EXT4_INO_NR InodeNum=0D + );=0D +=0D +/**=0D + Unmounts and frees an ext4 partition.=0D +=0D + @param[in] Partition Pointer to the opened partition.=0D +=0D + @return Status of the unmount.=0D + */=0D +EFI_STATUS=0D +Ext4UnmountAndFreePartition (=0D + IN EXT4_PARTITION *Partition=0D + );=0D +=0D +/**=0D + Checks if the checksum of the block group descriptor is correct.=0D + @param[in] Partition Pointer to the opened EXT4 partition.=0D + @param[in] BlockGroupDesc Pointer to the block group descriptor.= =0D + @param[in] BlockGroupNum Number of the block group.=0D +=0D + @return TRUE if checksum is correct, FALSE if there is corruption.=0D +*/=0D +BOOLEAN=0D +Ext4VerifyBlockGroupDescChecksum (=0D + IN CONST EXT4_PARTITION *Partition,=0D + IN CONST EXT4_BLOCK_GROUP_DESC *BlockGroupDesc,=0D + IN UINT32 BlockGroupNum=0D + );=0D +=0D +/**=0D + Calculates the checksum of the block group descriptor.=0D + @param[in] Partition Pointer to the opened EXT4 partition.=0D + @param[in] BlockGroupDesc Pointer to the block group descriptor.= =0D + @param[in] BlockGroupNum Number of the block group.=0D +=0D + @return The checksum.=0D +*/=0D +UINT16=0D +Ext4CalculateBlockGroupDescChecksum (=0D + IN CONST EXT4_PARTITION *Partition,=0D + IN CONST EXT4_BLOCK_GROUP_DESC *BlockGroupDesc,=0D + IN UINT32 BlockGroupNum=0D + );=0D +=0D +/**=0D + Verifies the existance of a particular RO compat feature set.=0D + @param[in] Partition Pointer to the opened EXT4 partitio= n.=0D + @param[in] RoCompatFeatureSet Feature set to test.=0D +=0D + @return TRUE if all features are supported, else FALSE.=0D +*/=0D +STATIC inline=0D +BOOLEAN=0D +Ext4HasRoCompat (=0D + IN CONST EXT4_PARTITION *Partition,=0D + IN UINT32 RoCompatFeatureSet=0D + )=0D +{=0D + return (Partition->FeaturesRoCompat & RoCompatFeatureSet) =3D=3D RoCompa= tFeatureSet;=0D +}=0D +=0D +/**=0D + Verifies the existance of a particular compat feature set.=0D + @param[in] Partition Pointer to the opened EXT4 partitio= n.=0D + @param[in] RoCompatFeatureSet Feature set to test.=0D +=0D + @return TRUE if all features are supported, else FALSE.=0D +*/=0D +STATIC inline=0D +BOOLEAN=0D +Ext4HasCompat (=0D + IN CONST EXT4_PARTITION *Partition,=0D + IN UINT32 CompatFeatureSet=0D + )=0D +{=0D + return (Partition->FeaturesCompat & CompatFeatureSet) =3D=3D CompatFeatu= reSet;=0D +}=0D +=0D +/**=0D + Verifies the existance of a particular compat feature set.=0D + @param[in] Partition Pointer to the opened EXT4 partitio= n.=0D + @param[in] RoCompatFeatureSet Feature set to test.=0D +=0D + @return TRUE if all features are supported, else FALSE.=0D +*/=0D +STATIC inline=0D +BOOLEAN=0D +Ext4HasIncompat (=0D + IN CONST EXT4_PARTITION *Partition,=0D + IN UINT32 IncompatFeatureSet=0D + )=0D +{=0D + return (Partition->FeaturesIncompat & IncompatFeatureSet) =3D=3D Incompa= tFeatureSet;=0D +}=0D +=0D +// Note: Might be a good idea to provide generic Ext4Has$feature() through= macros.=0D +=0D +/**=0D + Checks if metadata_csum is enabled on the partition.=0D + @param[in] Partition Pointer to the opened EXT4 partitio= n.=0D + @param[in] RoCompatFeatureSet Feature set to test.=0D +=0D + @return TRUE if the feature is supported, else FALSE.=0D +*/=0D +STATIC inline=0D +BOOLEAN=0D +Ext4HasMetadataCsum (=0D + IN CONST EXT4_PARTITION *Partition=0D + )=0D +{=0D + return Ext4HasRoCompat (Partition, EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)= ;=0D +}=0D +=0D +/**=0D + Checks if gdt_csum is enabled on the partition.=0D + @param[in] Partition Pointer to the opened EXT4 partitio= n.=0D + @param[in] RoCompatFeatureSet Feature set to test.=0D +=0D + @return TRUE if the feature is supported, else FALSE.=0D +*/=0D +STATIC inline=0D +BOOLEAN=0D +Ext4HasGdtCsum (=0D + IN CONST EXT4_PARTITION *Partition=0D + )=0D +{=0D + return Ext4HasRoCompat (Partition, EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)= ;=0D +}=0D +=0D +#endif=0D diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf b/Features/Ext4Pkg/Ext4Dx= e/Ext4Dxe.inf new file mode 100644 index 0000000000..102b12d613 --- /dev/null +++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf @@ -0,0 +1,147 @@ +## @file=0D +# Ext4 Package=0D +#=0D +# UEFI Driver that produces the Simple File System Protocol for a partiti= on that is formatted=0D +# with the EXT4 file system.=0D +# More details are available at: https://www.kernel.org/doc/html/v5.4/fil= esystems/ext4/index.html=0D +#=0D +# Copyright (c) 2021 Pedro Falcato=0D +# SPDX-License-Identifier: BSD-2-Clause-Patent=0D +#=0D +# Layout of an EXT2/3/4 filesystem:=0D +# (note: this driver has been developed using=0D +# https://www.kernel.org/doc/html/latest/filesystems/ext4/index.html as= =0D +# documentation).=0D +# =0D +# An ext2/3/4 filesystem (here on out referred to as simply an ext4 file= system,=0D +# due to the similarities) is composed of various concepts:=0D +# =0D +# 1) Superblock=0D +# The superblock is the structure near (1024 bytes offset from the st= art)=0D +# the start of the partition, and describes the filesystem in general= .=0D +# Here, we get to know the size of the filesystem's blocks, which fea= tures=0D +# it supports or not, whether it's been cleanly unmounted, how many b= locks=0D +# we have, etc.=0D +# =0D +# 2) Block groups=0D +# EXT4 filesystems are divided into block groups, and each block grou= p covers=0D +# s_blocks_per_group(8 * Block Size) blocks. Each block group has an= =0D +# associated block group descriptor; these are present directly after= the=0D +# superblock. Each block group descriptor contains the location of th= e=0D +# inode table, and the inode and block bitmaps (note these bitmaps ar= e only=0D +# a block long, which gets us the 8 * Block Size formula covered prev= iously).=0D +# =0D +# 3) Blocks=0D +# The ext4 filesystem is divided in blocks, of size s_log_block_size = ^ 1024.=0D +# Blocks can be allocated using individual block groups's bitmaps. No= te=0D +# that block 0 is invalid and its presence on extents/block tables me= ans=0D +# it's part of a file hole, and that particular location must be read= as=0D +# a block full of zeros.=0D +# =0D +# 4) Inodes=0D +# The ext4 filesystem divides files/directories into inodes (original= ly=0D +# index nodes). Each file/socket/symlink/directory/etc (here on out r= eferred=0D +# to as a file, since there is no distinction under the ext4 filesyst= em) is=0D +# stored as a /nameless/ inode, that is stored in some block group's = inode=0D +# table. Each inode has s_inode_size size (or GOOD_OLD_INODE_SIZE if = it's=0D +# an old filesystem), and holds various metadata about the file. Sinc= e the=0D +# largest inode structure right now is ~160 bytes, the rest of the in= ode=0D +# contains inline extended attributes. Inodes' data is stored using e= ither=0D +# data blocks (under ext2/3) or extents (under ext4).=0D +# =0D +# 5) Extents=0D +# Ext4 inodes store data in extents. These let N contiguous logical b= locks=0D +# that are represented by N contiguous physical blocks be represented= by a=0D +# single extent structure, which minimizes filesystem metadata bloat = and=0D +# speeds up block mapping (particularly due to the fact that high-qua= lity=0D +# ext4 implementations like linux's try /really/ hard to make the fil= e=0D +# contiguous, so it's common to have files with almost 0 fragmentatio= n).=0D +# Inodes that use extents store them in a tree, and the top of the tr= ee=0D +# is stored on i_data. The tree's leaves always start with an=0D +# EXT4_EXTENT_HEADER and contain EXT4_EXTENT_INDEX on eh_depth !=3D 0= and=0D +# EXT4_EXTENT on eh_depth =3D 0; these entries are always sorted by l= ogical=0D +# block.=0D +# =0D +# 6) Directories=0D +# Ext4 directories are files that store name -> inode mappings for th= e=0D +# logical directory; this is where files get their names, which means= ext4=0D +# inodes do not themselves have names, since they can be linked (pres= ent)=0D +# multiple times with different names. Directories can store entries = in two=0D +# different ways:=0D +# 1) Classical linear directories: They store entries as a mostly-l= inked=0D +# mostly-list of EXT4_DIR_ENTRY.=0D +# 2) Hash tree directories: These are used for larger directories, = with=0D +# hundreds of entries, and are designed in a backwards compatibl= e way.=0D +# These are not yet implemented in the Ext4Dxe driver.=0D +# =0D +# 7) Journal=0D +# Ext3/4 filesystems have a journal to help protect the filesystem ag= ainst=0D +# system crashes. This is not yet implemented in Ext4Dxe but is descr= ibed=0D +# in detail in the Linux kernel's documentation.=0D +##=0D +=0D +=0D +[Defines]=0D + INF_VERSION =3D 0x00010005=0D + BASE_NAME =3D Ext4=0D + MODULE_UNI_FILE =3D Ext4Dxe.uni=0D + FILE_GUID =3D 75F2B676-D73B-45CB-B7C1-303C7F4E6FD6= =0D + MODULE_TYPE =3D UEFI_DRIVER=0D + VERSION_STRING =3D 1.0=0D +=0D + ENTRY_POINT =3D Ext4EntryPoint=0D + UNLOAD_IMAGE =3D Ext4Unload=0D +=0D +#=0D +# The following information is for reference only and not required by the = build tools.=0D +#=0D +# VALID_ARCHITECTURES =3D IA32 X64 EBC=0D +#=0D +=0D +[Sources]=0D + Ext4Dxe.c=0D + Partition.c=0D + DiskUtil.c=0D + Superblock.c=0D + BlockGroup.c=0D + Inode.c=0D + Directory.c=0D + Extents.c=0D + File.c=0D + Collation.c=0D + Crc32c.c=0D + Crc16.c=0D +=0D +[Packages]=0D + MdePkg/MdePkg.dec=0D + RedfishPkg/RedfishPkg.dec=0D +=0D +[LibraryClasses]=0D + UefiRuntimeServicesTableLib=0D + UefiBootServicesTableLib=0D + MemoryAllocationLib=0D + BaseMemoryLib=0D + BaseLib=0D + UefiLib=0D + UefiDriverEntryPoint=0D + DebugLib=0D + PcdLib=0D + OrderedCollectionLib=0D + BaseUcs2Utf8Lib=0D +=0D +[Guids]=0D + gEfiFileInfoGuid ## SOMETIMES_CONSUMES ## UNDEFIN= ED=0D + gEfiFileSystemInfoGuid ## SOMETIMES_CONSUMES ## UNDEFIN= ED=0D + gEfiFileSystemVolumeLabelInfoIdGuid ## SOMETIMES_CONSUMES ## UNDEFIN= ED=0D +=0D +[Protocols]=0D + gEfiDiskIoProtocolGuid ## TO_START=0D + gEfiDiskIo2ProtocolGuid ## TO_START=0D + gEfiBlockIoProtocolGuid ## TO_START=0D + gEfiSimpleFileSystemProtocolGuid ## BY_START=0D + gEfiUnicodeCollationProtocolGuid ## TO_START=0D + gEfiUnicodeCollation2ProtocolGuid ## TO_START=0D +=0D +[Pcd]=0D + gEfiMdePkgTokenSpaceGuid.PcdUefiVariableDefaultLang ## SOMETIM= ES_CONSUMES=0D + gEfiMdePkgTokenSpaceGuid.PcdUefiVariableDefaultPlatformLang ## SOMETIM= ES_CONSUMES=0D diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni b/Features/Ext4Pkg/Ext4Dx= e/Ext4Dxe.uni new file mode 100644 index 0000000000..7476fbf9bd --- /dev/null +++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni @@ -0,0 +1,15 @@ +## @file=0D +# Ext4 Package=0D +#=0D +# UEFI Driver that produces the Simple File System Protocol for a partiti= on that is formatted=0D +# with the EXT4 file system.=0D +# More details are available at: https://www.kernel.org/doc/html/v5.4/fil= esystems/ext4/index.html=0D +#=0D +# Copyright (c) 2021 Pedro Falcato=0D +# SPDX-License-Identifier: BSD-2-Clause-Patent=0D +#=0D +##=0D +=0D +#string STR_MODULE_ABSTRACT #language en-US "UEFI driver for th= e EXT4 file system."=0D +=0D +#string STR_MODULE_DESCRIPTION #language en-US "Produces the EFI S= imple File System protocol."=0D 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 @@ +/**=0D + @file Extent related routines=0D +=0D + Copyright (c) 2021 Pedro Falcato All rights reserved.=0D +=0D + SPDX-License-Identifier: BSD-2-Clause-Patent=0D + */=0D +=0D +#include "Ext4Dxe.h"=0D +=0D +/**=0D + Checks if the checksum of the extent data block is correct.=0D + @param[in] ExtHeader Pointer to the EXT4_EXTENT_HEADER.=0D + @param[in] File Pointer to the file.=0D +=0D + @return TRUE if the checksum is correct, FALSE if there is corruption.= =0D +*/=0D +BOOLEAN=0D +Ext4CheckExtentChecksum (=0D + IN CONST EXT4_EXTENT_HEADER *ExtHeader,=0D + IN CONST EXT4_FILE *File=0D + );=0D +=0D +/**=0D + Calculates the checksum of the extent data block.=0D + @param[in] ExtHeader Pointer to the EXT4_EXTENT_HEADER.=0D + @param[in] File Pointer to the file.=0D +=0D + @return The checksum.=0D +*/=0D +UINT32=0D +Ext4CalculateExtentChecksum (=0D + IN CONST EXT4_EXTENT_HEADER *ExtHeader,=0D + IN CONST EXT4_FILE *File=0D + );=0D +=0D +/**=0D + Caches a range of extents, by allocating pool memory for each extent an= d adding it to the tree.=0D +=0D + @param[in] File Pointer to the open file.=0D + @param[in] Extents Pointer to an array of extents.=0D + @param[in] NumberExtents Length of the array.=0D +*/=0D +VOID=0D +Ext4CacheExtents (=0D + IN EXT4_FILE *File,=0D + IN CONST EXT4_EXTENT *Extents,=0D + IN UINT16 NumberExtents=0D + );=0D +=0D +/**=0D + Gets an extent from the extents cache of the file.=0D +=0D + @param[in] File Pointer to the open file.=0D + @param[in] Block Block we want to grab.=0D +=0D + @return Pointer to the extent, or NULL if it was not found.=0D +*/=0D +EXT4_EXTENT *=0D +Ext4GetExtentFromMap (=0D + IN EXT4_FILE *File,=0D + IN UINT32 Block=0D + );=0D +=0D +/**=0D + Retrieves the pointer to the top of the extent tree.=0D + @param[in] Inode Pointer to the inode structure.=0D +=0D + @return Pointer to an EXT4_EXTENT_HEADER. This pointer is inside=0D + the inode and must not be freed.=0D +*/=0D +STATIC=0D +EXT4_EXTENT_HEADER *=0D +Ext4GetInoExtentHeader (=0D + IN EXT4_INODE *Inode=0D + )=0D +{=0D + return (EXT4_EXTENT_HEADER *)Inode->i_data;=0D +}=0D +=0D +/**=0D + Checks if an extent header is valid.=0D + @param[in] Header Pointer to the EXT4_EXTENT_HEADER struct= ure.=0D +=0D + @return TRUE if valid, FALSE if not.=0D +*/=0D +STATIC=0D +BOOLEAN=0D +Ext4ExtentHeaderValid (=0D + IN CONST EXT4_EXTENT_HEADER *Header=0D + )=0D +{=0D + if(Header->eh_depth > EXT4_EXTENT_TREE_MAX_DEPTH) {=0D + DEBUG ((EFI_D_ERROR, "[ext4] Invalid extent header depth %u\n", Header= ->eh_depth));=0D + return FALSE;=0D + }=0D +=0D + if(Header->eh_magic !=3D EXT4_EXTENT_HEADER_MAGIC) {=0D + DEBUG ((EFI_D_ERROR, "[ext4] Invalid extent header magic %x\n", Header= ->eh_magic));=0D + return FALSE;=0D + }=0D +=0D + if(Header->eh_max < Header->eh_entries) {=0D + DEBUG ((=0D + EFI_D_ERROR,=0D + "[ext4] Invalid extent header num entries %u max entries %u\n",=0D + Header->eh_entries,=0D + Header->eh_max=0D + ));=0D + return FALSE;=0D + }=0D +=0D + return TRUE;=0D +}=0D +=0D +/**=0D + Performs a binary search for a EXT4_EXTENT_INDEX that corresponds to a= =0D + logical block in a given extent tree node.=0D +=0D + @param[in] Header Pointer to the EXT4_EXTENT_HEADER struct= ure.=0D + @param[in] LogicalBlock Block that will be searched=0D +=0D + @return Pointer to the found EXT4_EXTENT_INDEX.=0D +*/=0D +STATIC=0D +EXT4_EXTENT_INDEX *=0D +Ext4BinsearchExtentIndex (=0D + IN EXT4_EXTENT_HEADER *Header,=0D + IN EXT4_BLOCK_NR LogicalBlock=0D + )=0D +{=0D + EXT4_EXTENT_INDEX *l;=0D + EXT4_EXTENT_INDEX *r;=0D + EXT4_EXTENT_INDEX *m;=0D +=0D + l =3D ((EXT4_EXTENT_INDEX *)(Header + 1)) + 1;=0D + r =3D ((EXT4_EXTENT_INDEX *)(Header + 1)) + Header->eh_entries - 1;=0D +=0D + // Perform a mostly-standard binary search on the array=0D + // This works very nicely because the extents arrays are always sorted.= =0D +=0D + while(l <=3D r) {=0D + m =3D l + (r - l) / 2;=0D +=0D + if(LogicalBlock < m->ei_block) {=0D + r =3D m - 1;=0D + } else {=0D + l =3D m + 1;=0D + }=0D + }=0D +=0D + return l - 1;=0D +}=0D +=0D +/**=0D + Performs a binary search for a EXT4_EXTENT that corresponds to a=0D + logical block in a given extent tree node.=0D +=0D + @param[in] Header Pointer to the EXT4_EXTENT_HEADER struct= ure.=0D + @param[in] LogicalBlock Block that will be searched=0D +=0D + @return Pointer to the found EXT4_EXTENT_INDEX, else NULL if the array = is empty.=0D + Note: The caller must check if the logical block=0D + is actually mapped under the given extent.=0D +*/=0D +STATIC=0D +EXT4_EXTENT *=0D +Ext4BinsearchExtentExt (=0D + IN EXT4_EXTENT_HEADER *Header,=0D + IN EXT4_BLOCK_NR LogicalBlock=0D + )=0D +{=0D + EXT4_EXTENT *l;=0D + EXT4_EXTENT *r;=0D + EXT4_EXTENT *m;=0D +=0D + l =3D ((EXT4_EXTENT *)(Header + 1)) + 1;=0D + r =3D ((EXT4_EXTENT *)(Header + 1)) + Header->eh_entries - 1;=0D + // Perform a mostly-standard binary search on the array=0D + // This works very nicely because the extents arrays are always sorted.= =0D +=0D + // Empty array=0D + if(Header->eh_entries =3D=3D 0) {=0D + return NULL;=0D + }=0D +=0D + while(l <=3D r) {=0D + m =3D l + (r - l) / 2;=0D +=0D + if(LogicalBlock < m->ee_block) {=0D + r =3D m - 1;=0D + } else {=0D + l =3D m + 1;=0D + }=0D + }=0D +=0D + return l - 1;=0D +}=0D +=0D +/**=0D + Retrieves the leaf block from an EXT4_EXTENT_INDEX.=0D +=0D + @param[in] Index Pointer to the EXT4_EXTENT_INDEX structu= re.=0D +=0D + @return Block number of the leaf node.=0D +*/=0D +STATIC=0D +EXT4_BLOCK_NR=0D +Ext4ExtentIdxLeafBlock (=0D + IN EXT4_EXTENT_INDEX *Index=0D + )=0D +{=0D + return ((UINT64)Index->ei_leaf_hi << 32) | Index->ei_leaf_lo;=0D +}=0D +=0D +STATIC UINTN GetExtentRequests =3D 0;=0D +STATIC UINTN GetExtentCacheHits =3D 0;=0D +=0D +/**=0D + Retrieves an extent from an EXT4 inode.=0D + @param[in] Partition Pointer to the opened EXT4 partition.=0D + @param[in] File Pointer to the opened file.=0D + @param[in] LogicalBlock Block number which the returned extent mu= st cover.=0D + @param[out] Extent Pointer to the output buffer, where the e= xtent will be copied to.=0D +=0D + @retval EFI_SUCCESS Retrieval was succesful.=0D + @retval EFI_NO_MAPPING Block has no mapping.=0D +*/=0D +EFI_STATUS=0D +Ext4GetExtent (=0D + IN EXT4_PARTITION *Partition,=0D + IN EXT4_FILE *File,=0D + IN EXT4_BLOCK_NR LogicalBlock,=0D + OUT EXT4_EXTENT *Extent=0D + )=0D +{=0D + EXT4_INODE *Inode;=0D + VOID *Buffer;=0D + EXT4_EXTENT *Ext;=0D + UINT32 CurrentDepth;=0D + EXT4_EXTENT_HEADER *ExtHeader;=0D +=0D + Inode =3D File->Inode;=0D + Ext =3D NULL;=0D + Buffer =3D NULL;=0D +=0D + DEBUG ((EFI_D_INFO, "[ext4] Looking up extent for block %lu\n", LogicalB= lock));=0D +=0D + if (!(Inode->i_flags & EXT4_EXTENTS_FL)) {=0D + return EFI_UNSUPPORTED;=0D + }=0D +=0D + // ext4 does not have support for logical block numbers bigger than UINT= 32_MAX=0D + if (LogicalBlock > (UINT32)- 1) {=0D + return EFI_NO_MAPPING;=0D + }=0D +=0D + GetExtentRequests++;=0D + #if DEBUG_EXTENT_CACHE=0D + DEBUG ((=0D + EFI_D_INFO,=0D + "[ext4] Requests %lu, hits %lu, misses %lu\n",=0D + GetExtentRequests,=0D + GetExtentCacheHits,=0D + GetExtentRequests - GetExtentCacheHits=0D + ));=0D + #endif=0D +=0D + // Note: Right now, holes are the single biggest reason for cache misses= =0D + // We should find a way to get (or cache) holes=0D + if ((Ext =3D Ext4GetExtentFromMap (File, (UINT32)LogicalBlock)) !=3D NUL= L) {=0D + *Extent =3D *Ext;=0D + GetExtentCacheHits++;=0D +=0D + return EFI_SUCCESS;=0D + }=0D +=0D + // Slow path, we'll need to read from disk and (try to) cache those exte= nts.=0D +=0D + ExtHeader =3D Ext4GetInoExtentHeader (Inode);=0D +=0D + if(!Ext4ExtentHeaderValid (ExtHeader)) {=0D + return EFI_VOLUME_CORRUPTED;=0D + }=0D +=0D + CurrentDepth =3D ExtHeader->eh_depth;=0D +=0D + while(ExtHeader->eh_depth !=3D 0) {=0D + EXT4_EXTENT_INDEX *Index;=0D + EFI_STATUS Status;=0D +=0D + CurrentDepth--;=0D + // While depth !=3D 0, we're traversing the tree itself and not any le= aves=0D + // As such, every entry is an EXT4_EXTENT_INDEX entry=0D + // Note: Entries after the extent header, either index or actual exten= t, are always sorted.=0D + // Therefore, we can use binary search, and it's actually the standard= for doing so=0D + // (see FreeBSD).=0D +=0D + Index =3D Ext4BinsearchExtentIndex (ExtHeader, LogicalBlock);=0D +=0D + if(Buffer =3D=3D NULL) {=0D + Buffer =3D AllocatePool (Partition->BlockSize);=0D + if(Buffer =3D=3D NULL) {=0D + return EFI_OUT_OF_RESOURCES;=0D + }=0D + }=0D +=0D + // Read the leaf block onto the previously-allocated buffer.=0D +=0D + Status =3D Ext4ReadBlocks (Partition, Buffer, 1, Ext4ExtentIdxLeafBloc= k (Index));=0D + if(EFI_ERROR (Status)) {=0D + FreePool (Buffer);=0D + return Status;=0D + }=0D +=0D + ExtHeader =3D Buffer;=0D +=0D + if(!Ext4ExtentHeaderValid (ExtHeader)) {=0D + FreePool (Buffer);=0D + return EFI_VOLUME_CORRUPTED;=0D + }=0D +=0D + if(!Ext4CheckExtentChecksum (ExtHeader, File)) {=0D + DEBUG ((EFI_D_ERROR, "[ext4] Invalid extent checksum\n"));=0D + FreePool (Buffer);=0D + return EFI_VOLUME_CORRUPTED;=0D + }=0D +=0D + if(ExtHeader->eh_depth !=3D CurrentDepth) {=0D + FreePool (Buffer);=0D + return EFI_VOLUME_CORRUPTED;=0D + }=0D + }=0D +=0D + /* We try to cache every extent under a single leaf, since it's quite li= kely that we=0D + * may need to access things sequentially. Furthermore, ext4 block alloc= ation as done=0D + * by linux (and possibly other systems) is quite fancy and usually it r= esults in a small number of extents.=0D + * Therefore, we shouldn't have any memory issues.=0D + */=0D + Ext4CacheExtents (File, (EXT4_EXTENT *)(ExtHeader + 1), ExtHeader->eh_en= tries);=0D +=0D + Ext =3D Ext4BinsearchExtentExt (ExtHeader, LogicalBlock);=0D +=0D + if(!Ext) {=0D + if(Buffer !=3D NULL) {=0D + FreePool (Buffer);=0D + }=0D +=0D + return EFI_NO_MAPPING;=0D + }=0D +=0D + if(!(LogicalBlock >=3D Ext->ee_block && Ext->ee_block + Ext->ee_len > Lo= gicalBlock)) {=0D + // This extent does not cover the block=0D + if(Buffer !=3D NULL) {=0D + FreePool (Buffer);=0D + }=0D +=0D + return EFI_NO_MAPPING;=0D + }=0D +=0D + *Extent =3D *Ext;=0D +=0D + if(Buffer !=3D NULL) {=0D + FreePool (Buffer);=0D + }=0D +=0D + return EFI_SUCCESS;=0D +}=0D +=0D +/**=0D + Compare two EXT4_EXTENT structs.=0D + Used in the extent map's ORDERED_COLLECTION.=0D +=0D + @param[in] UserStruct1 Pointer to the first user structure.=0D +=0D + @param[in] UserStruct2 Pointer to the second user structure.=0D +=0D + @retval <0 If UserStruct1 compares less than UserStruct2.=0D +=0D + @retval 0 If UserStruct1 compares equal to UserStruct2.=0D +=0D + @retval >0 If UserStruct1 compares greater than UserStruct2.=0D +**/=0D +STATIC=0D +INTN EFIAPI=0D +Ext4ExtentsMapStructCompare (=0D + IN CONST VOID *UserStruct1,=0D + IN CONST VOID *UserStruct2=0D + )=0D +{=0D + CONST EXT4_EXTENT *Extent1 =3D UserStruct1;=0D + CONST EXT4_EXTENT *Extent2 =3D UserStruct2;=0D +=0D + // TODO: Detect extent overlaps? in case of corruption.=0D +=0D + /* DEBUG((EFI_D_INFO, "[ext4] extent 1 %u extent 2 %u =3D %ld\n", Extent= 1->ee_block,=0D + Extent2->ee_block, Extent1->ee_block - Extent2->ee_block)); */=0D + return Extent1->ee_block < Extent2->ee_block ? - 1 :=0D + Extent1->ee_block > Extent2->ee_block ? 1 : 0;=0D +}=0D +=0D +/**=0D + Compare a standalone key against a EXT4_EXTENT containing an embedded ke= y.=0D + Used in the extent map's ORDERED_COLLECTION.=0D +=0D + @param[in] StandaloneKey Pointer to the bare key.=0D +=0D + @param[in] UserStruct Pointer to the user structure with the embedde= d=0D + key.=0D +=0D + @retval <0 If StandaloneKey compares less than UserStruct's key.=0D +=0D + @retval 0 If StandaloneKey compares equal to UserStruct's key.=0D +=0D + @retval >0 If StandaloneKey compares greater than UserStruct's key.=0D +**/=0D +STATIC=0D +INTN EFIAPI=0D +Ext4ExtentsMapKeyCompare (=0D + IN CONST VOID *StandaloneKey,=0D + IN CONST VOID *UserStruct=0D + )=0D +{=0D + CONST EXT4_EXTENT *Extent =3D UserStruct;=0D +=0D + // Note that logical blocks are 32-bits in size so no truncation can hap= pen here=0D + // with regards to 32-bit architectures.=0D + UINT32 Block =3D (UINT32)(UINTN)StandaloneKey;=0D +=0D + // DEBUG((EFI_D_INFO, "[ext4] comparing %u %u\n", Block, Extent->ee_bloc= k));=0D + if(Block >=3D Extent->ee_block && Block < Extent->ee_block + Extent->ee_= len) {=0D + return 0;=0D + }=0D +=0D + return Block < Extent->ee_block ? - 1 :=0D + Block > Extent->ee_block ? 1 : 0;=0D +}=0D +=0D +/**=0D + Initialises the (empty) extents map, that will work as a cache of exten= ts.=0D +=0D + @param[in] File Pointer to the open file.=0D +=0D + @return Result of the operation.=0D +*/=0D +EFI_STATUS=0D +Ext4InitExtentsMap (=0D + IN EXT4_FILE *File=0D + )=0D +{=0D + File->ExtentsMap =3D OrderedCollectionInit (Ext4ExtentsMapStructCompare,= Ext4ExtentsMapKeyCompare);=0D + if (!File->ExtentsMap) {=0D + return EFI_OUT_OF_RESOURCES;=0D + }=0D +=0D + return EFI_SUCCESS;=0D +}=0D +=0D +/**=0D + Frees the extents map, deleting every extent stored.=0D +=0D + @param[in] File Pointer to the open file.=0D +*/=0D +VOID=0D +Ext4FreeExtentsMap (=0D + IN EXT4_FILE *File=0D + )=0D +{=0D + // Keep calling Min(), so we get an arbitrary node we can delete.=0D + // If Min() returns NULL, it's empty.=0D +=0D + ORDERED_COLLECTION_ENTRY *MinEntry =3D NULL;=0D +=0D + while ((MinEntry =3D OrderedCollectionMin (File->ExtentsMap)) !=3D NULL)= {=0D + EXT4_EXTENT *Ext;=0D + OrderedCollectionDelete (File->ExtentsMap, MinEntry, (VOID **)&Ext);=0D + FreePool (Ext);=0D + }=0D +=0D + ASSERT (OrderedCollectionIsEmpty (File->ExtentsMap));=0D +=0D + OrderedCollectionUninit (File->ExtentsMap);=0D + File->ExtentsMap =3D NULL;=0D +}=0D +=0D +/**=0D + Caches a range of extents, by allocating pool memory for each extent an= d adding it to the tree.=0D +=0D + @param[in] File Pointer to the open file.=0D + @param[in] Extents Pointer to an array of extents.=0D + @param[in] NumberExtents Length of the array.=0D +*/=0D +VOID=0D +Ext4CacheExtents (=0D + IN EXT4_FILE *File, IN CONST EXT4_EXTENT *Extents, IN UINT16 NumberExten= ts=0D + )=0D +{=0D + UINT16 i;=0D +=0D + /* Note that any out of memory condition might mean we don't get to cach= e a whole leaf of extents=0D + * in which case, future insertions might fail.=0D + */=0D +=0D + for(i =3D 0; i < NumberExtents; i++, Extents++) {=0D + EXT4_EXTENT *Extent;=0D + EFI_STATUS Status;=0D +=0D + Extent =3D AllocatePool (sizeof (EXT4_EXTENT));=0D +=0D + if (Extent =3D=3D NULL) {=0D + return;=0D + }=0D +=0D + CopyMem (Extent, Extents, sizeof (EXT4_EXTENT));=0D + Status =3D OrderedCollectionInsert (File->ExtentsMap, NULL, Extent);=0D +=0D + // EFI_ALREADY_STARTED =3D already exists in the tree.=0D + if (EFI_ERROR (Status)) {=0D + FreePool (Extent);=0D +=0D + if(Status =3D=3D EFI_ALREADY_STARTED) {=0D + continue;=0D + }=0D +=0D + return;=0D + }=0D +=0D + #if DEBUG_EXTENT_CACHE=0D + DEBUG ((=0D + EFI_D_INFO,=0D + "[ext4] Cached extent [%lu, %lu]\n",=0D + Extent->ee_block,=0D + Extent->ee_block + Extent->ee_len - 1=0D + ));=0D + #endif=0D + }=0D +}=0D +=0D +/**=0D + Gets an extent from the extents cache of the file.=0D +=0D + @param[in] File Pointer to the open file.=0D + @param[in] Block Block we want to grab.=0D +=0D + @return Pointer to the extent, or NULL if it was not found.=0D +*/=0D +EXT4_EXTENT *=0D +Ext4GetExtentFromMap (=0D + IN EXT4_FILE *File,=0D + IN UINT32 Block=0D + )=0D +{=0D + ORDERED_COLLECTION_ENTRY *Entry;=0D +=0D + Entry =3D OrderedCollectionFind (File->ExtentsMap, (CONST VOID *)(UINTN)= Block);=0D +=0D + if (Entry =3D=3D NULL) {=0D + return NULL;=0D + }=0D +=0D + return OrderedCollectionUserStruct (Entry);=0D +}=0D +=0D +/**=0D + Calculates the checksum of the extent data block.=0D + @param[in] ExtHeader Pointer to the EXT4_EXTENT_HEADER.=0D + @param[in] File Pointer to the file.=0D +=0D + @return The checksum.=0D +*/=0D +UINT32=0D +Ext4CalculateExtentChecksum (=0D + IN CONST EXT4_EXTENT_HEADER *ExtHeader,=0D + IN CONST EXT4_FILE *File=0D + )=0D +{=0D + UINT32 Csum;=0D + EXT4_PARTITION *Partition;=0D + EXT4_INODE *Inode;=0D +=0D + Partition =3D File->Partition;=0D + Inode =3D File->Inode;=0D +=0D + Csum =3D Ext4CalculateChecksum (Partition, &File->InodeNum, sizeof (EXT4= _INO_NR), Partition->InitialSeed);=0D + Csum =3D Ext4CalculateChecksum (Partition, &Inode->i_generation, sizeof = (Inode->i_generation), Csum);=0D + Csum =3D Ext4CalculateChecksum (Partition, ExtHeader, Partition->BlockSi= ze - sizeof (EXT4_EXTENT_TAIL), Csum);=0D +=0D + return Csum;=0D +}=0D +=0D +/**=0D + Checks if the checksum of the extent data block is correct.=0D + @param[in] ExtHeader Pointer to the EXT4_EXTENT_HEADER.=0D + @param[in] File Pointer to the file.=0D +=0D + @return TRUE if the checksum is correct, FALSE if there is corruption.= =0D +*/=0D +BOOLEAN=0D +Ext4CheckExtentChecksum (=0D + IN CONST EXT4_EXTENT_HEADER *ExtHeader,=0D + IN CONST EXT4_FILE *File=0D + )=0D +{=0D + EXT4_PARTITION *Partition;=0D + EXT4_EXTENT_TAIL *Tail;=0D +=0D + Partition =3D File->Partition;=0D +=0D + if(!Ext4HasMetadataCsum (Partition)) {=0D + return TRUE;=0D + }=0D +=0D + Tail =3D (EXT4_EXTENT_TAIL *)((CONST CHAR8 *)ExtHeader + (Partition->Blo= ckSize - 4));=0D +=0D + return Tail->eb_checksum =3D=3D Ext4CalculateExtentChecksum (ExtHeader, = File);=0D +}=0D diff --git a/Features/Ext4Pkg/Ext4Dxe/File.c b/Features/Ext4Pkg/Ext4Dxe/Fil= e.c new file mode 100644 index 0000000000..10dda64b16 --- /dev/null +++ b/Features/Ext4Pkg/Ext4Dxe/File.c @@ -0,0 +1,583 @@ +/**=0D + @file EFI_FILE_PROTOCOL implementation for EXT4=0D +=0D + Copyright (c) 2021 Pedro Falcato All rights reserved.=0D + SPDX-License-Identifier: BSD-2-Clause-Patent=0D + */=0D +=0D +#include "Ext4Dxe.h"=0D +=0D +#include =0D +=0D +/**=0D + Duplicates a file structure.=0D +=0D + @param[in] Original Pointer to the original file.=0D +=0D + @return Pointer to the new file structure.=0D + */=0D +STATIC=0D +EXT4_FILE *=0D +Ext4DuplicateFile (=0D + IN CONST EXT4_FILE *Original=0D + );=0D +=0D +/**=0D + Gets the next path segment.=0D +=0D + @param[in] Path Pointer to the rest of the path.=0D + @param[out] PathSegment Pointer to the buffer that will hold the = path segment.=0D + Note: It's necessarily EXT4_NAME_MAX +1 l= ong.=0D + @param[out] Length Pointer to the UINTN that will hold the l= ength of the path segment.=0D +=0D + @retval !EFI_SUCCESS The path segment is too large(> EXT4_NAME= _MAX).=0D + */=0D +STATIC=0D +EFI_STATUS=0D +GetPathSegment (=0D + IN CONST CHAR16 *Path, OUT CHAR16 *PathSegment, OUT UINTN *Length=0D + )=0D +{=0D + CONST CHAR16 *Start =3D Path;=0D + CONST CHAR16 *End =3D Path;=0D +=0D + // The path segment ends on a backslash or a null terminator=0D + for( ; *End !=3D L'\0' && *End !=3D L'\\'; End++) {=0D + }=0D +=0D + *Length =3D End - Start;=0D +=0D + return StrnCpyS (PathSegment, EXT4_NAME_MAX, Start, End - Start);=0D +}=0D +=0D +/**=0D + Detects if we have more path segments on the path.=0D +=0D + @param[in] Path Pointer to the rest of the path.=0D + @return True if we're on the last segment, false if there are= more=0D + segments.=0D + */=0D +STATIC=0D +BOOLEAN=0D +Ext4IsLastPathSegment (=0D + IN CONST CHAR16 *Path=0D + )=0D +{=0D + while(Path[0] =3D=3D L'\\') {=0D + Path++;=0D + }=0D +=0D + return Path[0] =3D=3D '\0';=0D +}=0D +=0D +#define EXT4_INO_PERM_READ_OWNER 0400=0D +#define EXT4_INO_PERM_WRITE_OWNER 0200=0D +#define EXT4_INO_PERM_EXEC_OWNER 0100=0D +=0D +/**=0D + Detects if we have permissions to open the file on the desired mode.=0D +=0D + @param[in out] File Pointer to the file we're opening.=0D + @param[in] OpenMode Mode in which to open the file.=0D +=0D + @return True if the open was succesful, false if we don't hav= e=0D + enough permissions.=0D + */=0D +STATIC=0D +BOOLEAN=0D +Ext4ApplyPermissions (=0D + IN OUT EXT4_FILE *File, IN UINT64 OpenMode=0D + )=0D +{=0D + UINT16 NeededPerms =3D 0;=0D +=0D + if((OpenMode & EFI_FILE_MODE_READ) !=3D 0) {=0D + NeededPerms |=3D EXT4_INO_PERM_READ_OWNER;=0D + }=0D +=0D + if((OpenMode & EFI_FILE_MODE_WRITE) !=3D 0) {=0D + NeededPerms |=3D EXT4_INO_PERM_WRITE_OWNER;=0D + }=0D +=0D + if((File->Inode->i_mode & NeededPerms) !=3D NeededPerms) {=0D + return FALSE;=0D + }=0D +=0D + File->OpenMode =3D OpenMode;=0D +=0D + return TRUE;=0D +}=0D +=0D +/**=0D + Detects if we have permissions to search on the directory.=0D +=0D + @param[in out] File Pointer to the open directory.=0D +=0D + @return True if we have permission to search, else false.=0D + */=0D +STATIC=0D +BOOLEAN=0D +Ext4DirCanLookup (=0D + IN CONST EXT4_FILE *File=0D + )=0D +{=0D + // In UNIX, executable permission on directories means that we have perm= ission to look up=0D + // files in a directory.=0D + return (File->Inode->i_mode & EXT4_INO_PERM_EXEC_OWNER) =3D=3D EXT4_INO_= PERM_EXEC_OWNER;=0D +}=0D +=0D +EFI_STATUS EFIAPI=0D +Ext4Open (=0D + IN EFI_FILE_PROTOCOL *This,=0D + OUT EFI_FILE_PROTOCOL **NewHandle,=0D + IN CHAR16 *FileName,=0D + IN UINT64 OpenMode,=0D + IN UINT64 Attributes=0D + )=0D +{=0D + EXT4_FILE *Current;=0D + EXT4_PARTITION *Partition;=0D + UINTN Level;=0D +=0D + Current =3D (EXT4_FILE *)This;=0D + Partition =3D Current->Partition;=0D + Level =3D 0;=0D +=0D + DEBUG ((EFI_D_INFO, "[ext4] Ext4Open %s\n", FileName));=0D + // If the path starts with a backslash, we treat the root directory as t= he base directory=0D + if(FileName[0] =3D=3D L'\\') {=0D + FileName++;=0D + Current =3D Partition->Root;=0D + }=0D +=0D + while(FileName[0] !=3D L'\0') {=0D + CHAR16 PathSegment[EXT4_NAME_MAX + 1];=0D + UINTN Length;=0D + EXT4_FILE *File;=0D + EFI_STATUS Status;=0D +=0D + // Discard leading path separators=0D + while(FileName[0] =3D=3D L'\\') {=0D + FileName++;=0D + }=0D +=0D + if(GetPathSegment (FileName, PathSegment, &Length) !=3D EFI_SUCCESS) {= =0D + return EFI_BUFFER_TOO_SMALL;=0D + }=0D +=0D + // Reached the end of the path=0D + if(Length =3D=3D 0) {=0D + break;=0D + }=0D +=0D + FileName +=3D Length;=0D +=0D + DEBUG ((EFI_D_INFO, "[ext4] Opening %s\n", PathSegment));=0D +=0D + // TODO: What to do with symlinks? They're nonsense when absolute but = may=0D + // be useful when they're relative.=0D +=0D + if (!Ext4FileIsDir (Current)) {=0D + return EFI_INVALID_PARAMETER;=0D + }=0D +=0D + if (!Ext4IsLastPathSegment (FileName)) {=0D + if (!Ext4DirCanLookup (Current)) {=0D + return EFI_ACCESS_DENIED;=0D + }=0D + }=0D +=0D + Status =3D Ext4OpenFile (Current, PathSegment, Partition, EFI_FILE_MOD= E_READ, &File);=0D +=0D + if(EFI_ERROR (Status) && Status !=3D EFI_NOT_FOUND) {=0D + return Status;=0D + } else if(Status =3D=3D EFI_NOT_FOUND) {=0D + // WRITE SUPPORT: Handle file creation when write support is added=0D + return Status;=0D + }=0D +=0D + // Check if this is a valid file to open in EFI=0D + if(!Ext4FileIsOpenable (File)) {=0D + Ext4CloseInternal (File);=0D + // This looks like an /okay/ status to return.=0D + return EFI_ACCESS_DENIED;=0D + }=0D +=0D + if(Level !=3D 0) {=0D + // Careful not to close the base directory=0D + Ext4CloseInternal (Current);=0D + }=0D +=0D + Level++;=0D +=0D + Current =3D File;=0D + }=0D +=0D + if (Level =3D=3D 0) {=0D + // We opened the base directory again, so we need to duplicate the fil= e structure=0D + Current =3D Ext4DuplicateFile (Current);=0D + if (Current =3D=3D NULL) {=0D + return EFI_OUT_OF_RESOURCES;=0D + }=0D + }=0D +=0D + if(!Ext4ApplyPermissions (Current, OpenMode)) {=0D + Ext4CloseInternal (Current);=0D + return EFI_ACCESS_DENIED;=0D + }=0D +=0D + *NewHandle =3D &Current->Protocol;=0D +=0D + DEBUG ((EFI_D_INFO, "Open successful\n"));=0D + DEBUG ((EFI_D_INFO, "Opened filename %s\n", Current->FileName));=0D + return EFI_SUCCESS;=0D +}=0D +=0D +EFI_STATUS EFIAPI=0D +Ext4Close (=0D + IN EFI_FILE_PROTOCOL *This=0D + )=0D +{=0D + return Ext4CloseInternal ((EXT4_FILE *)This);=0D +}=0D +=0D +/**=0D + Closes a file.=0D +=0D + @param[in] File Pointer to the file.=0D +=0D + @return Status of the closing of the file.=0D + */=0D +EFI_STATUS=0D +Ext4CloseInternal (=0D + IN EXT4_FILE *File=0D + )=0D +{=0D + if(File =3D=3D File->Partition->Root && !File->Partition->Unmounting) {= =0D + return EFI_SUCCESS;=0D + }=0D +=0D + DEBUG ((EFI_D_INFO, "[ext4] Closed file %p (inode %lu)\n", File, File->I= nodeNum));=0D + RemoveEntryList (&File->OpenFilesListNode);=0D + FreePool (File->FileName);=0D + FreePool (File->Inode);=0D + Ext4FreeExtentsMap (File);=0D + FreePool (File);=0D + return EFI_SUCCESS;=0D +}=0D +=0D +EFI_STATUS EFIAPI=0D +Ext4Delete (=0D + IN EFI_FILE_PROTOCOL *This=0D + )=0D +{=0D + // Write support=0D + Ext4Close (This);=0D + return EFI_WARN_DELETE_FAILURE;=0D +}=0D +=0D +EFI_STATUS=0D +EFIAPI=0D +Ext4ReadFile (=0D + IN EFI_FILE_PROTOCOL *This,=0D + IN OUT UINTN *BufferSize,=0D + OUT VOID *Buffer=0D + )=0D +{=0D + EXT4_FILE *File;=0D + EXT4_PARTITION *Partition;=0D + EFI_STATUS Status;=0D +=0D + File =3D (EXT4_FILE *)This;=0D + Partition =3D File->Partition;=0D +=0D + ASSERT (Ext4FileIsOpenable (File));=0D +=0D + if(Ext4FileIsReg (File)) {=0D + Status =3D Ext4Read (Partition, File, Buffer, File->Position, BufferSi= ze);=0D + if(Status =3D=3D EFI_SUCCESS) {=0D + File->Position +=3D *BufferSize;=0D + }=0D +=0D + return Status;=0D + } else if(Ext4FileIsDir (File)) {=0D + Status =3D Ext4ReadDir (Partition, File, Buffer, File->Position, Buffe= rSize);=0D + DEBUG ((EFI_D_INFO, "[ext4] ReadDir status %lx\n", Status));=0D +=0D + if(Status =3D=3D EFI_SUCCESS) {=0D + DEBUG ((EFI_D_INFO, "[ext4] ReadDir retlen %lu\n", *BufferSize));=0D + }=0D +=0D + return Status;=0D + }=0D +=0D + return EFI_SUCCESS;=0D +}=0D +=0D +EFI_STATUS=0D +EFIAPI=0D +Ext4WriteFile (=0D + IN EFI_FILE_PROTOCOL *This,=0D + IN OUT UINTN *BufferSize,=0D + IN VOID *Buffer=0D + )=0D +{=0D + EXT4_FILE *File =3D (EXT4_FILE *)This;=0D +=0D + if(!(File->OpenMode & EFI_FILE_MODE_WRITE)) {=0D + return EFI_ACCESS_DENIED;=0D + }=0D +=0D + // TODO: Add write support=0D + return EFI_WRITE_PROTECTED;=0D +}=0D +=0D +EFI_STATUS=0D +EFIAPI=0D +Ext4GetPosition (=0D + IN EFI_FILE_PROTOCOL *This,=0D + OUT UINT64 *Position=0D + )=0D +{=0D + EXT4_FILE *File =3D (EXT4_FILE *)This;=0D +=0D + if(Ext4FileIsDir (File)) {=0D + return EFI_UNSUPPORTED;=0D + }=0D +=0D + *Position =3D File->Position;=0D +=0D + return EFI_SUCCESS;=0D +}=0D +=0D +EFI_STATUS=0D +EFIAPI=0D +Ext4SetPosition (=0D + IN EFI_FILE_PROTOCOL *This,=0D + IN UINT64 Position=0D + )=0D +{=0D + EXT4_FILE *File =3D (EXT4_FILE *)This;=0D +=0D + // Only seeks to 0 (so it resets the ReadDir operation) are allowed=0D + if(Ext4FileIsDir (File) && Position !=3D 0) {=0D + return EFI_UNSUPPORTED;=0D + }=0D +=0D + // -1 (0xffffff.......) seeks to the end of the file=0D + if(Position =3D=3D (UINT64)- 1) {=0D + Position =3D EXT4_INODE_SIZE (File->Inode);=0D + }=0D +=0D + File->Position =3D Position;=0D +=0D + return EFI_SUCCESS;=0D +}=0D +=0D +/**=0D + Retrieves information about the file and stores it in the EFI_FILE_INFO= format.=0D +=0D + @param[in] File Pointer to an opened file.=0D + @param[out] Info Pointer to a EFI_FILE_INFO.=0D + @param[in out] BufferSize Pointer to the buffer size=0D +=0D + @return Status of the file information request.=0D +*/=0D +EFI_STATUS=0D +Ext4GetFileInfo (=0D + IN EXT4_FILE *File, OUT EFI_FILE_INFO *Info, IN OUT UINTN *BufferSize=0D + )=0D +{=0D + // TODO: Get a way to set the directory entry for SetFileInfo=0D + UINTN FileNameLen =3D StrLen (File->FileName);=0D + UINTN FileNameSize =3D StrSize (File->FileName);=0D +=0D + UINTN NeededLength =3D SIZE_OF_EFI_FILE_INFO + FileNameSize;=0D +=0D + if(*BufferSize < NeededLength) {=0D + *BufferSize =3D NeededLength;=0D + return EFI_BUFFER_TOO_SMALL;=0D + }=0D +=0D + Info->FileSize =3D EXT4_INODE_SIZE (File->Inode);=0D + Info->PhysicalSize =3D Ext4FilePhysicalSpace (File);=0D + Ext4FileATime (File, &Info->LastAccessTime);=0D + Ext4FileMTime (File, &Info->ModificationTime);=0D + Ext4FileCreateTime (File, &Info->LastAccessTime);=0D + Info->Attribute =3D 0;=0D + Info->Size =3D NeededLength;=0D +=0D + if(Ext4FileIsDir (File)) {=0D + Info->Attribute |=3D EFI_FILE_DIRECTORY;=0D + }=0D +=0D + *BufferSize =3D NeededLength;=0D +=0D + return StrCpyS (Info->FileName, FileNameLen + 1, File->FileName);=0D +}=0D +=0D +/**=0D + Retrieves information about the filesystem and stores it in the EFI_FIL= E_SYSTEM_INFO format.=0D +=0D + @param[in] File Pointer to an opened file.=0D + @param[out] Info Pointer to a EFI_FILE_SYSTEM_INFO.=0D + @param[in out] BufferSize Pointer to the buffer size=0D +=0D + @return Status of the file information request.=0D +*/=0D +STATIC=0D +EFI_STATUS=0D +Ext4GetFilesystemInfo (=0D + IN EXT4_PARTITION *Part, OUT EFI_FILE_SYSTEM_INFO *Info, IN OUT UINTN *B= ufferSize=0D + )=0D +{=0D + // Length of s_volume_name + null terminator=0D + CHAR8 TempVolName[16 + 1];=0D + CHAR16 *VolumeName;=0D + UINTN VolNameLength;=0D + EFI_STATUS Status;=0D + UINTN NeededLength;=0D + EXT4_BLOCK_NR TotalBlocks;=0D + EXT4_BLOCK_NR FreeBlocks;=0D +=0D + VolNameLength =3D 0;=0D + VolumeName =3D NULL;=0D +=0D + // s_volume_name is only valid on dynamic revision; old filesystems don'= t support this=0D + if(Part->SuperBlock.s_rev_level =3D=3D EXT4_DYNAMIC_REV) {=0D + CopyMem (TempVolName, (CONST CHAR8 *)Part->SuperBlock.s_volume_name, 1= 6);=0D + TempVolName[16] =3D '\0';=0D +=0D + Status =3D UTF8StrToUCS2 (TempVolName, &VolumeName);=0D +=0D + if(EFI_ERROR (Status)) {=0D + return Status;=0D + }=0D +=0D + VolNameLength =3D StrLen (VolumeName);=0D + }=0D +=0D + NeededLength =3D SIZE_OF_EFI_FILE_SYSTEM_INFO;=0D +=0D + if (VolumeName !=3D NULL) {=0D + NeededLength +=3D StrSize (VolumeName);=0D + } else {=0D + // If we don't have a volume name, we set VolumeLabel to a single null= terminator=0D + NeededLength +=3D sizeof (CHAR16);=0D + }=0D +=0D + if(*BufferSize < NeededLength) {=0D + *BufferSize =3D NeededLength;=0D +=0D + if (VolumeName !=3D NULL) {=0D + FreePool (VolumeName);=0D + }=0D +=0D + return EFI_BUFFER_TOO_SMALL;=0D + }=0D +=0D + TotalBlocks =3D Part->NumberBlocks;=0D +=0D + FreeBlocks =3D Ext4MakeBlockNumberFromHalfs (=0D + Part,=0D + Part->SuperBlock.s_free_blocks_count,=0D + Part->SuperBlock.s_free_blocks_count_hi=0D + );=0D +=0D + Info->BlockSize =3D Part->BlockSize;=0D + Info->Size =3D NeededLength;=0D + Info->ReadOnly =3D Part->ReadOnly;=0D + Info->VolumeSize =3D TotalBlocks * Part->BlockSize;=0D + Info->FreeSpace =3D FreeBlocks * Part->BlockSize;=0D +=0D + if (VolumeName !=3D NULL) {=0D + StrCpyS (Info->VolumeLabel, VolNameLength + 1, VolumeName);=0D + } else {=0D + Info->VolumeLabel[0] =3D L'\0';=0D + }=0D +=0D + if (VolumeName !=3D NULL) {=0D + FreePool (VolumeName);=0D + }=0D +=0D + *BufferSize =3D NeededLength;=0D +=0D + return EFI_SUCCESS;=0D +}=0D +=0D +EFI_STATUS=0D +EFIAPI=0D +Ext4GetInfo (=0D + IN EFI_FILE_PROTOCOL *This,=0D + IN EFI_GUID *InformationType,=0D + IN OUT UINTN *BufferSize,=0D + OUT VOID *Buffer=0D + )=0D +{=0D + if (CompareGuid (InformationType, &gEfiFileInfoGuid)) {=0D + return Ext4GetFileInfo ((EXT4_FILE *)This, Buffer, BufferSize);=0D + }=0D +=0D + if (CompareGuid (InformationType, &gEfiFileSystemInfoGuid)) {=0D + return Ext4GetFilesystemInfo (((EXT4_FILE *)This)->Partition, Buffer, = BufferSize);=0D + }=0D +=0D + return EFI_UNSUPPORTED;=0D +}=0D +=0D +/**=0D + Duplicates a file structure.=0D +=0D + @param[in] Original Pointer to the original file.=0D +=0D + @return Pointer to the new file structure.=0D + */=0D +STATIC=0D +EXT4_FILE *=0D +Ext4DuplicateFile (=0D + IN CONST EXT4_FILE *Original=0D + )=0D +{=0D + EXT4_PARTITION *Partition;=0D + EXT4_FILE *File;=0D +=0D + Partition =3D Original->Partition;=0D + File =3D AllocateZeroPool (sizeof (EXT4_FILE));=0D +=0D + if (File =3D=3D NULL) {=0D + return NULL;=0D + }=0D +=0D + File->Inode =3D Ext4AllocateInode (Partition);=0D + if (File->Inode =3D=3D NULL) {=0D + FreePool (File);=0D + return NULL;=0D + }=0D +=0D + CopyMem (File->Inode, Original->Inode, Partition->InodeSize);=0D +=0D + File->FileName =3D AllocateZeroPool (StrSize (Original->FileName));=0D + if (File->FileName =3D=3D NULL) {=0D + FreePool (File->Inode);=0D + FreePool (File);=0D + return NULL;=0D + }=0D +=0D + StrCpyS (File->FileName, StrLen (Original->FileName) + 1, Original->File= Name);=0D +=0D + File->Position =3D 0;=0D + Ext4SetupFile (File, Partition);=0D + File->InodeNum =3D Original->InodeNum;=0D + File->OpenMode =3D 0; // Will be filled by other code=0D +=0D + if (!Ext4InitExtentsMap (File)) {=0D + FreePool (File->FileName);=0D + FreePool (File->Inode);=0D + FreePool (File);=0D + return NULL;=0D + }=0D +=0D + InsertTailList (&Partition->OpenFiles, &File->OpenFilesListNode);=0D +=0D + return File;=0D +}=0D diff --git a/Features/Ext4Pkg/Ext4Dxe/Inode.c b/Features/Ext4Pkg/Ext4Dxe/In= ode.c new file mode 100644 index 0000000000..304bf0c4a9 --- /dev/null +++ b/Features/Ext4Pkg/Ext4Dxe/Inode.c @@ -0,0 +1,468 @@ +/**=0D + @file Inode related routines=0D +=0D + Copyright (c) 2021 Pedro Falcato All rights reserved.=0D + SPDX-License-Identifier: BSD-2-Clause-Patent=0D +=0D + EpochToEfiTime copied from EmbeddedPkg/Library/TimeBaseLib.c=0D + Copyright (c) 2016, Hisilicon Limited. All rights reserved.=0D + Copyright (c) 2016-2019, Linaro Limited. All rights reserved.=0D + Copyright (c) 2021, Ampere Computing LLC. All rights reserved.=0D + */=0D +=0D +#include "Ext4Dxe.h"=0D +=0D +#include =0D +=0D +/**=0D + Calculates the checksum of the given inode.=0D + @param[in] Partition Pointer to the opened EXT4 partition.=0D + @param[in] Inode Pointer to the inode.=0D + @param[in] InodeNum Inode number.=0D +=0D + @return The checksum.=0D +*/=0D +UINT32=0D +Ext4CalculateInodeChecksum (=0D + IN CONST EXT4_PARTITION *Partition,=0D + IN CONST EXT4_INODE *Inode,=0D + IN EXT4_INO_NR InodeNum=0D + )=0D +{=0D + UINT32 Crc;=0D + UINT16 Dummy;=0D + BOOLEAN HasSecondChecksumField;=0D + CONST VOID *RestOfInode;=0D + UINTN RestOfInodeLength;=0D +=0D + HasSecondChecksumField =3D Ext4InodeHasField (Inode, i_checksum_hi);=0D +=0D + Dummy =3D 0;=0D +=0D + Crc =3D Ext4CalculateChecksum (Partition, &InodeNum, sizeof (InodeNum), = Partition->InitialSeed);=0D + Crc =3D Ext4CalculateChecksum (Partition, &Inode->i_generation, sizeof (= Inode->i_generation), Crc);=0D +=0D + Crc =3D Ext4CalculateChecksum (=0D + Partition,=0D + Inode,=0D + OFFSET_OF (EXT4_INODE, i_osd2.data_linux.l_i_checksum_lo),=0D + Crc=0D + );=0D +=0D + Crc =3D Ext4CalculateChecksum (Partition, &Dummy, sizeof (Dummy), Crc);= =0D +=0D + RestOfInode =3D &Inode->i_osd2.data_linux.l_i_reserved;=0D + RestOfInodeLength =3D Partition->InodeSize - OFFSET_OF (EXT4_INODE, i_os= d2.data_linux.l_i_reserved);=0D +=0D + if (HasSecondChecksumField) {=0D + UINTN Length =3D OFFSET_OF (EXT4_INODE, i_checksum_hi) - OFFSET_OF (E= XT4_INODE, i_osd2.data_linux.l_i_reserved);=0D +=0D + Crc =3D Ext4CalculateChecksum (Partition, &Inode->i_osd2.data_linux.l_= i_reserved, Length, Crc);=0D + Crc =3D Ext4CalculateChecksum (Partition, &Dummy, sizeof (Dummy), Crc)= ;=0D +=0D + // 4 is the size of the i_extra_size field + the size of i_checksum_hi= =0D + RestOfInodeLength =3D Partition->InodeSize - EXT4_GOOD_OLD_INODE_SIZE = - 4;=0D + RestOfInode =3D &Inode->i_ctime_extra;=0D + }=0D +=0D + Crc =3D Ext4CalculateChecksum (Partition, RestOfInode, RestOfInodeLength= , Crc);=0D +=0D + return Crc;=0D +}=0D +=0D +/**=0D + Reads from an EXT4 inode.=0D + @param[in] Partition Pointer to the opened EXT4 partition.=0D + @param[in] File Pointer to the opened file.=0D + @param[out] Buffer Pointer to the buffer.=0D + @param[in] Offset Offset of the read.=0D + @param[in out] Length Pointer to the length of the buffer, in b= ytes.=0D + After a succesful read, it's updated to t= he number of read bytes.=0D +=0D + @return Status of the read operation.=0D +*/=0D +EFI_STATUS=0D +Ext4Read (=0D + IN EXT4_PARTITION *Partition,=0D + IN EXT4_FILE *File,=0D + OUT VOID *Buffer,=0D + IN UINT64 Offset,=0D + IN OUT UINTN *Length=0D + )=0D +{=0D + // DEBUG((EFI_D_INFO, "Ext4Read[Offset %lu, Length %lu]\n", Offset, *Len= gth));=0D + EXT4_INODE *Inode;=0D + UINT64 InodeSize;=0D + UINT64 CurrentSeek;=0D + UINTN RemainingRead;=0D + UINTN BeenRead;=0D +=0D + Inode =3D File->Inode;=0D + InodeSize =3D Ext4InodeSize (Inode);=0D + CurrentSeek =3D Offset;=0D + RemainingRead =3D *Length;=0D + BeenRead =3D 0;=0D +=0D + if(Offset > InodeSize) {=0D + return EFI_DEVICE_ERROR;=0D + }=0D +=0D + if (RemainingRead > InodeSize - Offset) {=0D + RemainingRead =3D (UINTN)(InodeSize - Offset);=0D + }=0D +=0D + while(RemainingRead !=3D 0) {=0D + UINTN WasRead;=0D + EXT4_EXTENT Extent;=0D + UINT32 BlockOff;=0D + EFI_STATUS Status;=0D + BOOLEAN HasBackingExtent;=0D +=0D + WasRead =3D 0;=0D +=0D + // The algorithm here is to get the extent corresponding to the curren= t block=0D + // and then read as much as we can from the current extent.=0D +=0D + Status =3D Ext4GetExtent (=0D + Partition,=0D + File,=0D + DivU64x32Remainder (CurrentSeek, Partition->BlockSize, &Blo= ckOff),=0D + &Extent=0D + );=0D +=0D + if(Status !=3D EFI_SUCCESS && Status !=3D EFI_NO_MAPPING) {=0D + return Status;=0D + }=0D +=0D + HasBackingExtent =3D Status !=3D EFI_NO_MAPPING;=0D +=0D + if(!HasBackingExtent) {=0D + UINT32 HoleOff;=0D + UINTN HoleLen;=0D +=0D + HoleOff =3D BlockOff;=0D + HoleLen =3D Partition->BlockSize - HoleOff;=0D + WasRead =3D HoleLen > RemainingRead ? RemainingRead : HoleLen;=0D + // TODO: Get the hole size and memset all that=0D + SetMem (Buffer, WasRead, 0);=0D + } else {=0D + UINT64 ExtentStartBytes;=0D + UINT64 ExtentLengthBytes;=0D + UINT64 ExtentLogicalBytes;=0D +=0D + // Our extent offset is the difference between CurrentSeek and Exten= tLogicalBytes=0D + UINT64 ExtentOffset;=0D + UINTN ExtentMayRead;=0D +=0D + ExtentStartBytes =3D (((UINT64)Extent.ee_start_hi << 32) | Extent.= ee_start_lo) * Partition->BlockSize;=0D + ExtentLengthBytes =3D Extent.ee_len * Partition->BlockSize;=0D + ExtentLogicalBytes =3D (UINT64)Extent.ee_block * Partition->BlockSiz= e;=0D + ExtentOffset =3D CurrentSeek - ExtentLogicalBytes;=0D + ExtentMayRead =3D (UINTN)(ExtentLengthBytes - ExtentOffset);=0D +=0D + WasRead =3D ExtentMayRead > RemainingRead ? RemainingRead : ExtentMa= yRead;=0D +=0D + // DEBUG((EFI_D_INFO, "[ext4] may read %lu, remaining %lu\n", Extent= MayRead, RemainingRead));=0D + // DEBUG((EFI_D_INFO, "[ext4] Reading block %lu\n", (ExtentStartByte= s + ExtentOffset) / Partition->BlockSize));=0D + Status =3D Ext4ReadDiskIo (Partition, Buffer, WasRead, ExtentStartBy= tes + ExtentOffset);=0D +=0D + if(EFI_ERROR (Status)) {=0D + DEBUG ((=0D + EFI_D_ERROR,=0D + "[ext4] Error %x reading [%lu, %lu]\n",=0D + Status,=0D + ExtentStartBytes + ExtentOffset,=0D + ExtentStartBytes + ExtentOffset + WasRead - 1=0D + ));=0D + return Status;=0D + }=0D + }=0D +=0D + RemainingRead -=3D WasRead;=0D + Buffer =3D (VOID *)((CHAR8 *)Buffer + WasRead);=0D + BeenRead +=3D WasRead;=0D + CurrentSeek +=3D WasRead;=0D + }=0D +=0D + *Length =3D BeenRead;=0D +=0D + // DEBUG((EFI_D_INFO, "File length %lu crc %x\n", BeenRead, CalculateCrc= 32(original, BeenRead)));=0D + return EFI_SUCCESS;=0D +}=0D +=0D +/**=0D + Allocates a zeroed inode structure.=0D + @param[in] Partition Pointer to the opened EXT4 partition.=0D +=0D + @return Pointer to the allocated structure, from the pool,=0D + with size Partition->InodeSize.=0D +*/=0D +EXT4_INODE *=0D +Ext4AllocateInode (=0D + IN EXT4_PARTITION *Partition=0D + )=0D +{=0D + BOOLEAN NeedsToZeroRest;=0D + UINT32 InodeSize;=0D + EXT4_INODE *Inode;=0D +=0D + NeedsToZeroRest =3D FALSE;=0D + InodeSize =3D Partition->InodeSize;=0D +=0D + // HACK!: We allocate a structure of at least sizeof(EXT4_INODE), but in= the future, when=0D + // write support is added and we need to flush inodes to disk, we could = have a bit better=0D + // distinction between the on-disk inode and a separate, nicer to work w= ith inode struct.=0D + if (InodeSize < sizeof (EXT4_INODE)) {=0D + InodeSize =3D sizeof (EXT4_INODE);=0D + NeedsToZeroRest =3D TRUE;=0D + }=0D +=0D + Inode =3D AllocateZeroPool (InodeSize);=0D +=0D + if (!Inode) {=0D + return NULL;=0D + }=0D +=0D + if (NeedsToZeroRest) {=0D + Inode->i_extra_isize =3D 0;=0D + }=0D +=0D + return Inode;=0D +}=0D +=0D +/**=0D + Checks if a file is a directory.=0D + @param[in] File Pointer to the opened file.=0D +=0D + @return TRUE if file is a directory.=0D +*/=0D +BOOLEAN=0D +Ext4FileIsDir (=0D + IN CONST EXT4_FILE *File=0D + )=0D +{=0D + return (File->Inode->i_mode & EXT4_INO_TYPE_DIR) =3D=3D EXT4_INO_TYPE_DI= R;=0D +}=0D +=0D +/**=0D + Checks if a file is a regular file.=0D + @param[in] File Pointer to the opened file.=0D +=0D + @return BOOLEAN TRUE if file is a regular file.=0D +*/=0D +BOOLEAN=0D +Ext4FileIsReg (=0D + IN CONST EXT4_FILE *File=0D + )=0D +{=0D + return (File->Inode->i_mode & EXT4_INO_TYPE_REGFILE) =3D=3D EXT4_INO_TYP= E_REGFILE;=0D +}=0D +=0D +/**=0D + Calculates the physical space used by a file.=0D + @param[in] File Pointer to the opened file.=0D +=0D + @return Physical space used by a file, in bytes.=0D +*/=0D +UINT64=0D +Ext4FilePhysicalSpace (=0D + EXT4_FILE *File=0D + )=0D +{=0D + BOOLEAN HugeFile;=0D + UINT64 Blocks;=0D +=0D + HugeFile =3D Ext4HasRoCompat (File->Partition, EXT4_FEATURE_RO_COMPAT_HU= GE_FILE);=0D + Blocks =3D File->Inode->i_blocks;=0D +=0D + if(HugeFile) {=0D + Blocks |=3D ((UINT64)File->Inode->i_osd2.data_linux.l_i_blocks_high) <= < 32;=0D +=0D + // If HUGE_FILE is enabled and EXT4_HUGE_FILE_FL is set in the inode's= flags, each unit=0D + // in i_blocks corresponds to an actual filesystem block=0D + if(File->Inode->i_flags & EXT4_HUGE_FILE_FL) {=0D + return Blocks * File->Partition->BlockSize;=0D + }=0D + }=0D +=0D + // Else, each i_blocks unit corresponds to 512 bytes=0D + return Blocks * 512;=0D +}=0D +=0D +// Copied from EmbeddedPkg at my mentor's request.=0D +// The lack of comments and good variable names is frightening...=0D +=0D +/**=0D + Converts Epoch seconds (elapsed since 1970 JANUARY 01, 00:00:00 UTC) to = EFI_TIME.=0D +=0D + @param EpochSeconds Epoch seconds.=0D + @param Time The time converted to UEFI format.=0D +=0D +**/=0D +STATIC=0D +VOID=0D +EFIAPI=0D +EpochToEfiTime (=0D + IN UINTN EpochSeconds,=0D + OUT EFI_TIME *Time=0D + )=0D +{=0D + UINTN a;=0D + UINTN b;=0D + UINTN c;=0D + UINTN d;=0D + UINTN g;=0D + UINTN j;=0D + UINTN m;=0D + UINTN y;=0D + UINTN da;=0D + UINTN db;=0D + UINTN dc;=0D + UINTN dg;=0D + UINTN hh;=0D + UINTN mm;=0D + UINTN ss;=0D + UINTN J;=0D +=0D + J =3D (EpochSeconds / 86400) + 2440588;=0D + j =3D J + 32044;=0D + g =3D j / 146097;=0D + dg =3D j % 146097;=0D + c =3D (((dg / 36524) + 1) * 3) / 4;=0D + dc =3D dg - (c * 36524);=0D + b =3D dc / 1461;=0D + db =3D dc % 1461;=0D + a =3D (((db / 365) + 1) * 3) / 4;=0D + da =3D db - (a * 365);=0D + y =3D (g * 400) + (c * 100) + (b * 4) + a;=0D + m =3D (((da * 5) + 308) / 153) - 2;=0D + d =3D da - (((m + 4) * 153) / 5) + 122;=0D +=0D + Time->Year =3D (UINT16)(y - 4800 + ((m + 2) / 12));=0D + Time->Month =3D ((m + 2) % 12) + 1;=0D + Time->Day =3D (UINT8)(d + 1);=0D +=0D + ss =3D EpochSeconds % 60;=0D + a =3D (EpochSeconds - ss) / 60;=0D + mm =3D a % 60;=0D + b =3D (a - mm) / 60;=0D + hh =3D b % 24;=0D +=0D + Time->Hour =3D (UINT8)hh;=0D + Time->Minute =3D (UINT8)mm;=0D + Time->Second =3D (UINT8)ss;=0D + Time->Nanosecond =3D 0;=0D +=0D +}=0D +=0D +// The time format used to (de/en)code timestamp and timestamp_extra is do= cumented on=0D +// the ext4 docs page in kernel.org=0D +#define EXT4_EXTRA_TIMESTAMP_MASK ((1 << 2) - 1)=0D +=0D +#define EXT4_FILE_GET_TIME_GENERIC(Name, Field) \=0D + VOID \=0D + Ext4File ## Name (IN EXT4_FILE *File, OUT EFI_TIME *Time) \=0D + { \=0D + EXT4_INODE *Inode =3D File->Inode; \=0D + UINT64 SecondsEpoch =3D Inode->Field; \=0D + UINT32 Nanoseconds =3D 0; \=0D + \=0D + if (Ext4InodeHasField (Inode, Field ## _extra)) { \=0D + SecondsEpoch |=3D ((UINT64)(Inode->Field ## _extra & EXT4_EXTRA_TIME= STAMP_MASK)) << 32; \=0D + Nanoseconds =3D Inode->Field ## _extra >> 2; = \=0D + } = \=0D + EpochToEfiTime ((UINTN)SecondsEpoch, Time); = \=0D + Time->Nanosecond =3D Nanoseconds; = \=0D + }=0D +=0D +// Note: EpochToEfiTime should be adjusted to take in a UINT64 instead of = a UINTN, in order to avoid Y2038=0D +// on 32-bit systems.=0D +=0D +/**=0D + Gets the file's last access time.=0D + @param[in] File Pointer to the opened file.=0D + @param[out] Time Pointer to an EFI_TIME structure.=0D +*/=0D +EXT4_FILE_GET_TIME_GENERIC (ATime, i_atime);=0D +=0D +/**=0D + Gets the file's last (data) modification time.=0D + @param[in] File Pointer to the opened file.=0D + @param[out] Time Pointer to an EFI_TIME structure.=0D +*/=0D +EXT4_FILE_GET_TIME_GENERIC (MTime, i_mtime);=0D +=0D +/**=0D + Gets the file's creation time.=0D + @param[in] File Pointer to the opened file.=0D + @param[out] Time Pointer to an EFI_TIME structure.=0D +*/=0D +STATIC=0D +EXT4_FILE_GET_TIME_GENERIC (=0D + CrTime, i_crtime=0D + );=0D +=0D +/**=0D + Gets the file's creation time, if possible.=0D + @param[in] File Pointer to the opened file.=0D + @param[out] Time Pointer to an EFI_TIME structure.=0D + In the case where the the creation time isn't re= corded,=0D + Time is zeroed.=0D +*/=0D +VOID=0D +Ext4FileCreateTime (=0D + IN EXT4_FILE *File,=0D + OUT EFI_TIME *Time=0D + )=0D +{=0D + EXT4_INODE *Inode;=0D +=0D + Inode =3D File->Inode;=0D +=0D + if (!Ext4InodeHasField (Inode, i_crtime)) {=0D + SetMem (Time, sizeof (EFI_TIME), 0);=0D + return;=0D + }=0D +=0D + Ext4FileCrTime (File, Time);=0D +}=0D +=0D +/**=0D + Checks if the checksum of the inode is correct.=0D + @param[in] Partition Pointer to the opened EXT4 partition.=0D + @param[in] Inode Pointer to the inode.=0D + @param[in] InodeNum Inode number.=0D +=0D + @return TRUE if checksum is correct, FALSE if there is corruption.=0D +*/=0D +BOOLEAN=0D +Ext4CheckInodeChecksum (=0D + IN CONST EXT4_PARTITION *Partition,=0D + IN CONST EXT4_INODE *Inode,=0D + IN EXT4_INO_NR InodeNum=0D + )=0D +{=0D + UINT32 Csum;=0D + UINT32 DiskCsum;=0D +=0D + if(!Ext4HasMetadataCsum (Partition)) {=0D + return TRUE;=0D + }=0D +=0D + Csum =3D Ext4CalculateInodeChecksum (Partition, Inode, InodeNum);=0D +=0D + DiskCsum =3D Inode->i_osd2.data_linux.l_i_checksum_lo;=0D +=0D + if (Ext4InodeHasField (Inode, i_checksum_hi)) {=0D + DiskCsum |=3D ((UINT32)Inode->i_checksum_hi) << 16;=0D + } else {=0D + // Only keep the lower bits for the comparison if the checksum is 16 b= its.=0D + Csum &=3D 0xffff;=0D + }=0D +=0D + #if 0=0D + DEBUG ((EFI_D_INFO, "[ext4] Inode %d csum %x vs %x\n", InodeNum, Csum,= DiskCsum));=0D + #endif=0D +=0D + return Csum =3D=3D DiskCsum;=0D +}=0D diff --git a/Features/Ext4Pkg/Ext4Dxe/Partition.c b/Features/Ext4Pkg/Ext4Dx= e/Partition.c new file mode 100644 index 0000000000..a2c4c57e78 --- /dev/null +++ b/Features/Ext4Pkg/Ext4Dxe/Partition.c @@ -0,0 +1,120 @@ +/**=0D + @file Driver entry point=0D +=0D + Copyright (c) 2021 Pedro Falcato All rights reserved.=0D + SPDX-License-Identifier: BSD-2-Clause-Patent=0D + */=0D +=0D +#include "Ext4Dxe.h"=0D +=0D +/**=0D + Opens an ext4 partition and installs the Simple File System protocol.=0D +=0D + @param[in] DeviceHandle Handle to the block device.=0D + @param[in] DiskIo Pointer to an EFI_DISK_IO_PROTOCOL.= =0D + @param[in opt] DiskIo2 Pointer to an EFI_DISK_IO2_PROTOCOL,= if supported.=0D + @param[in] BlockIo Pointer to an EFI_BLOCK_IO_PROTOCOL.= =0D +=0D + @retval EFI_SUCCESS The opening was successful.=0D + !EFI_SUCCESS Opening failed.=0D + */=0D +EFI_STATUS=0D +Ext4OpenPartition (=0D + IN EFI_HANDLE DeviceHandle,=0D + IN EFI_DISK_IO_PROTOCOL *DiskIo,=0D + IN OPTIONAL EFI_DISK_IO2_PROTOCOL *DiskIo2,=0D + IN EFI_BLOCK_IO_PROTOCOL *BlockIo=0D + )=0D +{=0D + EXT4_PARTITION *Part;=0D + EFI_STATUS Status;=0D +=0D + Part =3D AllocateZeroPool (sizeof (*Part));=0D +=0D + if(Part =3D=3D NULL) {=0D + return EFI_OUT_OF_RESOURCES;=0D + }=0D +=0D + InitializeListHead(&Part->OpenFiles);=0D +=0D + Part->BlockIo =3D BlockIo;=0D + Part->DiskIo =3D DiskIo;=0D + Part->DiskIo2 =3D DiskIo2;=0D +=0D + Status =3D Ext4OpenSuperblock (Part);=0D +=0D + if(EFI_ERROR (Status)) {=0D + FreePool (Part);=0D + return Status;=0D + }=0D +=0D + Part->Interface.Revision =3D EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION;= =0D + Part->Interface.OpenVolume =3D Ext4OpenVolume;=0D + Status =3D gBS->InstallMultipleProtocolInterfaces (=0D + &DeviceHandle,=0D + &gEfiSimpleFileSystemProtocolGuid,=0D + &Part->Interface,=0D + NULL=0D + );=0D +=0D + if(EFI_ERROR (Status)) {=0D + FreePool (Part);=0D + return Status;=0D + }=0D +=0D + return EFI_SUCCESS;=0D +}=0D +=0D +/**=0D + Sets up the protocol and metadata of a file that is being opened.=0D +=0D + @param[in out] File Pointer to the file.=0D + @param[in] Partition Pointer to the opened partition.=0D + */=0D +VOID=0D +Ext4SetupFile (=0D + IN OUT EXT4_FILE *File, EXT4_PARTITION *Partition=0D + )=0D +{=0D + // TODO: Support revision 2 (needs DISK_IO2 + asynchronous IO)=0D + File->Protocol.Revision =3D EFI_FILE_PROTOCOL_REVISION;=0D + File->Protocol.Open =3D Ext4Open;=0D + File->Protocol.Close =3D Ext4Close;=0D + File->Protocol.Delete =3D Ext4Delete;=0D + File->Protocol.Read =3D Ext4ReadFile;=0D + File->Protocol.Write =3D Ext4WriteFile;=0D + File->Protocol.SetPosition =3D Ext4SetPosition;=0D + File->Protocol.GetPosition =3D Ext4GetPosition;=0D + File->Protocol.GetInfo =3D Ext4GetInfo;=0D +=0D + File->Partition =3D Partition;=0D +}=0D +=0D +/**=0D + Unmounts and frees an ext4 partition.=0D +=0D + @param[in] Partition Pointer to the opened partition.=0D +=0D + @retval Status of the unmount.=0D + */=0D +EFI_STATUS=0D +Ext4UnmountAndFreePartition (=0D + IN EXT4_PARTITION *Partition=0D + )=0D +{=0D + LIST_ENTRY *Entry;=0D + LIST_ENTRY *NextEntry;=0D + Partition->Unmounting =3D TRUE;=0D + Ext4CloseInternal (Partition->Root);=0D +=0D + BASE_LIST_FOR_EACH_SAFE(Entry, NextEntry, &Partition->OpenFiles) {=0D + EXT4_FILE *File =3D Ext4FileFromOpenFileNode(Entry);=0D +=0D + Ext4CloseInternal(File);=0D + }=0D +=0D + FreePool (Partition->BlockGroups);=0D + FreePool (Partition);=0D +=0D + return EFI_SUCCESS;=0D +}=0D diff --git a/Features/Ext4Pkg/Ext4Dxe/Superblock.c b/Features/Ext4Pkg/Ext4D= xe/Superblock.c new file mode 100644 index 0000000000..18d8295a1f --- /dev/null +++ b/Features/Ext4Pkg/Ext4Dxe/Superblock.c @@ -0,0 +1,257 @@ +/**=0D + @file Superblock managing routines=0D +=0D + Copyright (c) 2021 Pedro Falcato All rights reserved.=0D + SPDX-License-Identifier: BSD-2-Clause-Patent=0D + */=0D +=0D +#include "Ext4Dxe.h"=0D +=0D +STATIC CONST UINT32 gSupportedCompatFeat =3D EXT4_FEATURE_COMPAT_EXT_ATTR= ;=0D +=0D +STATIC CONST UINT32 gSupportedRoCompatFeat =3D=0D + EXT4_FEATURE_RO_COMPAT_DIR_NLINK | EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE |= =0D + EXT4_FEATURE_RO_COMPAT_HUGE_FILE | EXT4_FEATURE_RO_COMPAT_LARGE_FILE |=0D + EXT4_FEATURE_RO_COMPAT_GDT_CSUM | EXT4_FEATURE_RO_COMPAT_METADATA_CSUM |= EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER;=0D +// TODO: Add btree support=0D +STATIC CONST UINT32 gSupportedIncompatFeat =3D=0D + EXT4_FEATURE_INCOMPAT_64BIT | EXT4_FEATURE_INCOMPAT_DIRDATA |=0D + EXT4_FEATURE_INCOMPAT_FLEX_BG | EXT4_FEATURE_INCOMPAT_FILETYPE |=0D + EXT4_FEATURE_INCOMPAT_EXTENTS | EXT4_FEATURE_INCOMPAT_LARGEDIR |=0D + EXT4_FEATURE_INCOMPAT_MMP;=0D +=0D +// TODO: Add meta_bg support=0D +=0D +// Note: We ignore MMP because it's impossible that it's mapped elsewhere,= =0D +// I think (unless there's some sort of network setup where we're accessin= g a remote partition).=0D +=0D +BOOLEAN=0D +Ext4SuperblockValidate (=0D + EXT4_SUPERBLOCK *sb=0D + )=0D +{=0D + if(sb->s_magic !=3D EXT4_SIGNATURE) {=0D + return FALSE;=0D + }=0D +=0D + // TODO: We should try to support EXT2/3 partitions too=0D + if(sb->s_rev_level !=3D EXT4_DYNAMIC_REV && sb->s_rev_level !=3D EXT4_GO= OD_OLD_REV) {=0D + return FALSE;=0D + }=0D +=0D + // TODO: Is this correct behaviour? Imagine the power cuts out, should t= he system fail to boot because=0D + // we're scared of touching something corrupt?=0D + if((sb->s_state & EXT4_FS_STATE_UNMOUNTED) =3D=3D 0) {=0D + return FALSE;=0D + }=0D +=0D + return TRUE;=0D +}=0D +=0D +STATIC UINT32=0D +Ext4CalculateSuperblockChecksum (=0D + EXT4_PARTITION *Partition, CONST EXT4_SUPERBLOCK *sb=0D + )=0D +{=0D + // Most checksums require us to go through a dummy 0 as part of the requ= irement=0D + // that the checksum is done over a structure with its checksum field = =3D 0.=0D + UINT32 Checksum =3D Ext4CalculateChecksum (=0D + Partition,=0D + sb,=0D + OFFSET_OF (EXT4_SUPERBLOCK, s_checksum),=0D + ~0U=0D + );=0D +=0D + return Checksum;=0D +}=0D +=0D +STATIC BOOLEAN=0D +Ext4VerifySuperblockChecksum (=0D + EXT4_PARTITION *Partition, CONST EXT4_SUPERBLOCK *sb=0D + )=0D +{=0D + if(!Ext4HasMetadataCsum (Partition)) {=0D + return TRUE;=0D + }=0D +=0D + return sb->s_checksum =3D=3D Ext4CalculateSuperblockChecksum (Partition,= sb);=0D +}=0D +=0D +/**=0D + Opens and parses the superblock.=0D +=0D + @param[out] Partition Partition structure to fill with filesystem d= etails.=0D + @retval EFI_SUCCESS Parsing was succesful and the partition is a= =0D + valid ext4 partition.=0D + */=0D +EFI_STATUS=0D +Ext4OpenSuperblock (=0D + OUT EXT4_PARTITION *Partition=0D + )=0D +{=0D + UINT32 Index;=0D + EFI_STATUS Status;=0D + EXT4_SUPERBLOCK *Sb;=0D + UINT32 NrBlocksRem;=0D + UINTN NrBlocks;=0D + UINT32 UnsupportedRoCompat;=0D +=0D + Status =3D Ext4ReadDiskIo (=0D + Partition,=0D + &Partition->SuperBlock,=0D + sizeof (EXT4_SUPERBLOCK),=0D + EXT4_SUPERBLOCK_OFFSET=0D + );=0D +=0D + if (EFI_ERROR (Status)) {=0D + return Status;=0D + }=0D +=0D + Sb =3D &Partition->SuperBlock;=0D +=0D + if (!Ext4SuperblockValidate (Sb)) {=0D + return EFI_VOLUME_CORRUPTED;=0D + }=0D +=0D + if (Sb->s_rev_level =3D=3D EXT4_DYNAMIC_REV) {=0D + Partition->FeaturesCompat =3D Sb->s_feature_compat;=0D + Partition->FeaturesIncompat =3D Sb->s_feature_incompat;=0D + Partition->FeaturesRoCompat =3D Sb->s_feature_ro_compat;=0D + Partition->InodeSize =3D Sb->s_inode_size;=0D + } else {=0D + // GOOD_OLD_REV=0D + Partition->FeaturesCompat =3D Partition->FeaturesIncompat =3D Partitio= n->FeaturesRoCompat =3D 0;=0D + Partition->InodeSize =3D EXT4_GOOD_OLD_INODE_SIZE;=0D + }=0D +=0D + // Now, check for the feature set of the filesystem=0D + // It's essential to check for this to avoid filesystem corruption and t= o avoid=0D + // accidentally opening an ext2/3/4 filesystem we don't understand, whic= h would be disasterous.=0D +=0D + if (Partition->FeaturesIncompat & ~gSupportedIncompatFeat) {=0D + DEBUG ((EFI_D_INFO, "[Ext4] Unsupported %lx\n", Partition->FeaturesInc= ompat & ~gSupportedIncompatFeat));=0D + return EFI_UNSUPPORTED;=0D + }=0D +=0D + // This should be removed once we add ext2/3 support in the future.=0D + if ((Partition->FeaturesIncompat & EXT4_FEATURE_INCOMPAT_EXTENTS) =3D=3D= 0) {=0D + return EFI_UNSUPPORTED;=0D + }=0D +=0D + // At the time of writing, it's the only supported checksum.=0D + if (Partition->FeaturesCompat & EXT4_FEATURE_RO_COMPAT_METADATA_CSUM &&= =0D + Sb->s_checksum_type !=3D EXT4_CHECKSUM_CRC32C) {=0D + return EFI_UNSUPPORTED;=0D + }=0D +=0D + if (Partition->FeaturesIncompat & EXT4_FEATURE_INCOMPAT_CSUM_SEED) {=0D + Partition->InitialSeed =3D Sb->s_checksum_seed;=0D + } else {=0D + Partition->InitialSeed =3D Ext4CalculateChecksum (Partition, Sb->s_uui= d, 16, ~0U);=0D + }=0D +=0D + UnsupportedRoCompat =3D Partition->FeaturesRoCompat & ~gSupportedRoCompa= tFeat;=0D +=0D + if (UnsupportedRoCompat !=3D 0) {=0D + DEBUG ((EFI_D_INFO, "[Ext4] Unsupported ro compat %x\n", UnsupportedRo= Compat));=0D + Partition->ReadOnly =3D TRUE;=0D + }=0D +=0D + (VOID)gSupportedCompatFeat;=0D +=0D + DEBUG ((EFI_D_INFO, "Read only =3D %u\n", Partition->ReadOnly));=0D +=0D + Partition->BlockSize =3D 1024 << Sb->s_log_block_size;=0D +=0D + // The size of a block group can also be calculated as 8 * Partition->Bl= ockSize=0D + if(Sb->s_blocks_per_group !=3D 8 * Partition->BlockSize) {=0D + return EFI_UNSUPPORTED;=0D + }=0D +=0D + Partition->NumberBlocks =3D Ext4MakeBlockNumberFromHalfs (Partition, Sb-= >s_blocks_count, Sb->s_blocks_count_hi);=0D + Partition->NumberBlockGroups =3D DivU64x32 (Partition->NumberBlocks, Sb-= >s_blocks_per_group);=0D +=0D + DEBUG ((=0D + EFI_D_INFO,=0D + "[ext4] Number of blocks =3D %lu\n[ext4] Number of block groups: %lu\n= ",=0D + Partition->NumberBlocks,=0D + Partition->NumberBlockGroups=0D + ));=0D +=0D + if (Ext4Is64Bit (Partition)) {=0D + Partition->DescSize =3D Sb->s_desc_size;=0D + } else {=0D + Partition->DescSize =3D EXT4_OLD_BLOCK_DESC_SIZE;=0D + }=0D +=0D + if (Partition->DescSize < EXT4_64BIT_BLOCK_DESC_SIZE && Ext4Is64Bit (Par= tition)) {=0D + // 64 bit filesystems need DescSize to be 64 bytes=0D + return EFI_VOLUME_CORRUPTED;=0D + }=0D +=0D + if (!Ext4VerifySuperblockChecksum (Partition, Sb)) {=0D + DEBUG ((EFI_D_ERROR, "[ext4] Bad superblock checksum %lx\n", Ext4Calcu= lateSuperblockChecksum (Partition, Sb)));=0D + return EFI_VOLUME_CORRUPTED;=0D + }=0D +=0D + NrBlocks =3D (UINTN)DivU64x32Remainder (=0D + Partition->NumberBlockGroups * Partition->De= scSize,=0D + Partition->BlockSize,=0D + &NrBlocksRem=0D + );=0D +=0D + if(NrBlocksRem !=3D 0) {=0D + NrBlocks++;=0D + }=0D +=0D + Partition->BlockGroups =3D Ext4AllocAndReadBlocks (Partition, NrBlocks, = Partition->BlockSize =3D=3D 1024 ? 2 : 1);=0D +=0D + if (!Partition->BlockGroups) {=0D + return EFI_OUT_OF_RESOURCES;=0D + }=0D +=0D + for (Index =3D 0; Index < Partition->NumberBlockGroups; Index++) {=0D + EXT4_BLOCK_GROUP_DESC *Desc;=0D + =0D + Desc =3D Ext4GetBlockGroupDesc (Partition, Index);=0D + if (!Ext4VerifyBlockGroupDescChecksum (Partition, Desc, Index)) {=0D + DEBUG ((EFI_D_INFO, "[ext4] Block group descriptor %u has an invalid= checksum\n", Index));=0D + return EFI_VOLUME_CORRUPTED;=0D + }=0D + }=0D +=0D + // Note that the cast below is completely safe, because EXT4_FILE is a s= pecialisation of EFI_FILE_PROTOCOL=0D + Status =3D Ext4OpenVolume (&Partition->Interface, (EFI_FILE_PROTOCOL **)= &Partition->Root);=0D +=0D + DEBUG ((EFI_D_INFO, "[ext4] Root File %p\n", Partition->Root));=0D + return Status;=0D +}=0D +=0D +/**=0D + Calculates the checksum of the given buffer.=0D + @param[in] Partition Pointer to the opened EXT4 partition.=0D + @param[in] Buffer Pointer to the buffer.=0D + @param[in] Length Length of the buffer, in bytes.=0D + @param[in] InitialValue Initial value of the CRC.=0D +=0D + @return The checksum.=0D +*/=0D +UINT32=0D +Ext4CalculateChecksum (=0D + IN CONST EXT4_PARTITION *Partition, IN CONST VOID *Buffer, IN UINTN Leng= th,=0D + IN UINT32 InitialValue=0D + )=0D +{=0D + if(!Ext4HasMetadataCsum (Partition)) {=0D + return 0;=0D + }=0D +=0D + switch(Partition->SuperBlock.s_checksum_type) {=0D + case EXT4_CHECKSUM_CRC32C:=0D + // For some reason, EXT4 really likes non-inverted CRC32C checksums,= so we stick to that here.=0D + return ~CalculateCrc32c(Buffer, Length, ~InitialValue);=0D + default:=0D + UNREACHABLE ();=0D + return 0;=0D + }=0D +}=0D --=20 2.32.0