public inbox for devel@edk2.groups.io
 help / color / mirror / Atom feed
From: "Pedro Falcato" <pedro.falcato@gmail.com>
To: devel@edk2.groups.io
Cc: Leif Lindholm <leif@nuviainc.com>,
	Michael D Kinney <michael.d.kinney@intel.com>
Subject: [PATCH edk2-platforms 3/3] Ext4Pkg: Add ext2/3 support
Date: Thu,  7 Apr 2022 23:01:46 +0100	[thread overview]
Message-ID: <20220407220146.149580-4-pedro.falcato@gmail.com> (raw)
In-Reply-To: <20220407220146.149580-1-pedro.falcato@gmail.com>

BZ: https://bugzilla.tianocore.org/show_bug.cgi?id=3745

Adds ext2/3 support by supporting (legacy) block maps.
Also fixes a bug regarding uninitialised extents.

Cc: Leif Lindholm <leif@nuviainc.com>
Cc: Michael D Kinney <michael.d.kinney@intel.com>
Signed-off-by: Pedro Falcato <pedro.falcato@gmail.com>
---
 Features/Ext4Pkg/Ext4Dxe/BlockMap.c   | 279 ++++++++++++++++++++++++++
 Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h   |   2 +
 Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h    |  18 ++
 Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf  |   1 +
 Features/Ext4Pkg/Ext4Dxe/Extents.c    |  17 +-
 Features/Ext4Pkg/Ext4Dxe/Inode.c      |   9 +-
 Features/Ext4Pkg/Ext4Dxe/Superblock.c |   7 +-
 7 files changed, 318 insertions(+), 15 deletions(-)
 create mode 100644 Features/Ext4Pkg/Ext4Dxe/BlockMap.c

diff --git a/Features/Ext4Pkg/Ext4Dxe/BlockMap.c b/Features/Ext4Pkg/Ext4Dxe/BlockMap.c
new file mode 100644
index 000000000000..6e8ccaa82437
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/BlockMap.c
@@ -0,0 +1,279 @@
+/** @file
+  Implementation of routines that deal with ext2/3 block maps.
+
+  Copyright (c) 2022 Pedro Falcato All rights reserved.
+  SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
+
+#include <Ext4Dxe.h>
+
+// Note: The largest path we can take uses up 4 indices
+#define EXT4_MAX_BLOCK_PATH  4
+
+typedef enum ext4_logical_block_type {
+  EXT4_TYPE_DIRECT_BLOCK = 0,
+  EXT4_TYPE_SINGLY_BLOCK,
+  EXT4_TYPE_DOUBLY_BLOCK,
+  EXT4_TYPE_TREBLY_BLOCK,
+  EXT4_TYPE_BAD_BLOCK
+} EXT4_LOGICAL_BLOCK_TYPE;
+
+/**
+   @brief Detect the type of path the logical block will follow
+
+   @param[in] LogicalBlock The logical block
+   @param[in] Partition    Pointer to an EXT4_PARTITION
+   @return The type of path the logical block will need to follow
+ */
+STATIC
+EXT4_LOGICAL_BLOCK_TYPE
+Ext4DetectBlockType (
+  IN UINT32                LogicalBlock,
+  IN CONST EXT4_PARTITION  *Partition
+  )
+{
+  UINT32  Entries;
+  UINT32  MinSinglyBlock;
+  UINT32  MinDoublyBlock;
+  UINT32  MinTreblyBlock;
+  UINT32  MinQuadBlock;
+
+  Entries        = (Partition->BlockSize / sizeof (UINT32));
+  MinSinglyBlock = EXT4_DBLOCKS;
+  MinDoublyBlock = Entries + MinSinglyBlock;
+  MinTreblyBlock = Entries * Entries + MinDoublyBlock;
+  MinQuadBlock   = Entries * Entries * Entries + MinTreblyBlock; // Doesn't actually exist
+
+  if (LogicalBlock < MinSinglyBlock) {
+    return EXT4_TYPE_DIRECT_BLOCK;
+  } else if ((LogicalBlock >= MinSinglyBlock) && (LogicalBlock < MinDoublyBlock)) {
+    return EXT4_TYPE_SINGLY_BLOCK;
+  } else if ((LogicalBlock >= MinDoublyBlock) && (LogicalBlock < MinTreblyBlock)) {
+    return EXT4_TYPE_DOUBLY_BLOCK;
+  } else if (((LogicalBlock >= MinTreblyBlock) && (LogicalBlock < MinQuadBlock))) {
+    return EXT4_TYPE_TREBLY_BLOCK;
+  } else {
+    return EXT4_TYPE_BAD_BLOCK;
+  }
+}
+
+/**
+   @brief Get a block's path in indices
+
+   @param[in]  Partition       Pointer to an EXT4_PARTITION
+   @param[in]  LogicalBlock    Logical block
+   @param[out] BlockPath       Pointer to an array of EXT4_MAX_BLOCK_PATH elements, where the
+                               indices we'll need to read are inserted.
+   @return The number of path elements that are required (and were inserted in BlockPath)
+ */
+UINTN
+Ext4GetBlockPath (
+  IN  CONST EXT4_PARTITION  *Partition,
+  IN  UINT32                LogicalBlock,
+  OUT EXT4_BLOCK_NR         BlockPath[EXT4_MAX_BLOCK_PATH]
+  )
+{
+  // The logic behind the block map is very much like a page table
+  // Let's think of blocks with 512 entries (exactly like a page table on x64).
+  // On doubly indirect block paths, we subtract the min doubly blocks from the logical block.
+  // The top 9 bits of the result are the index inside the dind block, the bottom 9 bits are the
+  // index inside the ind block. Since Entries is always a power of 2, entries - 1 will give us
+  // a mask of the BlockMapBits.
+  // Note that all this math could be done with ands and shifts (similar implementations exist
+  // in a bunch of other places), but I'm doing it a simplified way with divs and modulus,
+  // since it's not going to be a bottleneck anyway.
+
+  UINT32  Entries;
+  UINT32  EntriesEntries;
+  UINT32  MinSinglyBlock;
+  UINT32  MinDoublyBlock;
+  UINT32  MinTreblyBlock;
+
+  EXT4_LOGICAL_BLOCK_TYPE  Type;
+
+  Entries        = (Partition->BlockSize / sizeof (UINT32));
+  EntriesEntries = Entries * Entries;
+
+  MinSinglyBlock = EXT4_DBLOCKS;
+  MinDoublyBlock = Entries + MinSinglyBlock;
+  MinTreblyBlock = EntriesEntries + MinDoublyBlock;
+
+  Type = Ext4DetectBlockType (LogicalBlock, Partition);
+
+  switch (Type) {
+    case EXT4_TYPE_DIRECT_BLOCK:
+      BlockPath[0] = LogicalBlock;
+      break;
+    case EXT4_TYPE_SINGLY_BLOCK:
+      BlockPath[0] = EXT4_IND_BLOCK;
+      BlockPath[1] = LogicalBlock - EXT4_DBLOCKS;
+      break;
+    case EXT4_TYPE_DOUBLY_BLOCK:
+      BlockPath[0]  = EXT4_DIND_BLOCK;
+      LogicalBlock -= MinDoublyBlock;
+      BlockPath[1]  = LogicalBlock / Entries;
+      BlockPath[2]  = LogicalBlock % Entries;
+      break;
+    case EXT4_TYPE_TREBLY_BLOCK:
+      BlockPath[0]  = EXT4_DIND_BLOCK;
+      LogicalBlock -= MinTreblyBlock;
+      BlockPath[1]  = LogicalBlock / EntriesEntries;
+      BlockPath[2]  = (LogicalBlock % EntriesEntries) / Entries;
+      BlockPath[3]  = (LogicalBlock % EntriesEntries) % Entries;
+      break;
+    default:
+      // EXT4_TYPE_BAD_BLOCK
+      return -1;
+  }
+
+  return Type + 1;
+}
+
+/**
+   @brief Get an extent from a block map
+   Note: Also parses file holes and creates uninitialised extents from them.
+
+   @param[in]  Buffer          Buffer of block pointers
+   @param[in]  IndEntries      Number of entries in this block pointer table
+   @param[in]  StartIndex      The start index from which we want to find a contiguous extent
+   @param[out] Extent          Pointer to the resulting EXT4_EXTENT
+ */
+VOID
+Ext4GetExtentInBlockMap (
+  IN CONST UINT32  *Buffer,
+  IN CONST UINT32  IndEntries,
+  IN UINT32        StartIndex,
+  OUT EXT4_EXTENT  *Extent
+  )
+{
+  UINT32  Index;
+  UINT32  FirstBlock;
+  UINT32  LastBlock;
+  UINT16  Count;
+
+  Count      = 1;
+  LastBlock  = Buffer[StartIndex];
+  FirstBlock = LastBlock;
+
+  if (FirstBlock == EXT4_BLOCK_FILE_HOLE) {
+    // File hole, let's see how many blocks this hole spans
+    Extent->ee_start_hi = 0;
+    Extent->ee_start_lo = 0;
+
+    for (Index = StartIndex + 1; Index < IndEntries; Index++) {
+      if (Count == EXT4_EXTENT_MAX_INITIALIZED - 1) {
+        // We've reached the max size of an uninit extent, break
+        break;
+      }
+
+      if (Buffer[Index] == EXT4_BLOCK_FILE_HOLE) {
+        Count++;
+      } else {
+        break;
+      }
+    }
+
+    // We mark the extent as uninitialised, although there's a difference between uninit
+    // extents and file holes.
+    Extent->ee_len = EXT4_EXTENT_MAX_INITIALIZED + Count;
+    return;
+  }
+
+  for (Index = StartIndex + 1; Index < IndEntries; Index++) {
+    if (Count == EXT4_EXTENT_MAX_INITIALIZED) {
+      // We've reached the max size of an extent, break
+      break;
+    }
+
+    if ((Buffer[Index] == LastBlock + 1) && (Buffer[Index] != EXT4_BLOCK_FILE_HOLE)) {
+      Count++;
+    } else {
+      break;
+    }
+
+    LastBlock = Buffer[Index];
+  }
+
+  Extent->ee_start_lo = FirstBlock;
+  Extent->ee_start_hi = 0;
+  Extent->ee_len      = Count;
+}
+
+/**
+   Retrieves an extent from an EXT2/3 inode (with a blockmap).
+   @param[in]      Partition     Pointer to the opened EXT4 partition.
+   @param[in]      File          Pointer to the opened file.
+   @param[in]      LogicalBlock  Block number which the returned extent must cover.
+   @param[out]     Extent        Pointer to the output buffer, where the extent will be copied to.
+
+   @retval EFI_SUCCESS        Retrieval was succesful.
+   @retval EFI_NO_MAPPING     Block has no mapping.
+**/
+EFI_STATUS
+Ext4GetBlocks (
+  IN  EXT4_PARTITION  *Partition,
+  IN  EXT4_FILE       *File,
+  IN  EXT4_BLOCK_NR   LogicalBlock,
+  OUT EXT4_EXTENT     *Extent
+  )
+{
+  EXT4_INODE     *Inode;
+  EXT4_BLOCK_NR  BlockPath[EXT4_MAX_BLOCK_PATH];
+  UINTN          BlockPathLength;
+  UINTN          Index;
+  UINT32         *Buffer;
+  EFI_STATUS     Status;
+  UINT32         Block;
+  UINT32         BlockIndex;
+
+  Inode = File->Inode;
+
+  BlockPathLength = Ext4GetBlockPath (Partition, LogicalBlock, BlockPath);
+
+  if (BlockPathLength == (UINTN)-1) {
+    // Bad logical block (out of range)
+    return EFI_NO_MAPPING;
+  }
+
+  Extent->ee_block = LogicalBlock;
+
+  if (BlockPathLength == 1) {
+    // Fast path for blocks 0 - 12 that skips allocations
+    Ext4GetExtentInBlockMap (Inode->i_data, EXT4_DBLOCKS, BlockPath[0], Extent);
+
+    return EFI_SUCCESS;
+  }
+
+  Buffer = AllocatePool (Partition->BlockSize);
+  if (Buffer == NULL) {
+    return EFI_OUT_OF_RESOURCES;
+  }
+
+  // Note the BlockPathLength - 1 so we don't end up reading the final block
+  for (Index = 0; Index < BlockPathLength - 1; Index++) {
+    BlockIndex = BlockPath[Index];
+
+    if (Index == 0) {
+      Block = Inode->i_data[BlockIndex];
+    } else {
+      Block = Buffer[BlockIndex];
+    }
+
+    if (Block == EXT4_BLOCK_FILE_HOLE) {
+      FreePool (Buffer);
+      return EFI_NO_MAPPING;
+    }
+
+    Status = Ext4ReadBlocks (Partition, Buffer, 1, Block);
+
+    if (EFI_ERROR (Status)) {
+      FreePool (Buffer);
+      return Status;
+    }
+  }
+
+  Ext4GetExtentInBlockMap (Buffer, Partition->BlockSize / sizeof (UINT32), BlockPath[BlockPathLength - 1], Extent);
+  FreePool (Buffer);
+
+  return EFI_SUCCESS;
+}
diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h b/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
index 5f812215fbb8..a55cd2fa68ad 100644
--- a/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
+++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
@@ -468,4 +468,6 @@ typedef UINT32  EXT4_INO_NR;
 // 2 is always the root inode number in ext4
 #define EXT4_ROOT_INODE_NR  2
 
+#define EXT4_BLOCK_FILE_HOLE  0
+
 #endif
diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
index 03e0586cbb05..b1508482b0a7 100644
--- a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
+++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
@@ -1151,4 +1151,22 @@ Ext4GetExtentLength (
   IN CONST EXT4_EXTENT  *Extent
   );
 
+/**
+   Retrieves an extent from an EXT2/3 inode (with a blockmap).
+   @param[in]      Partition     Pointer to the opened EXT4 partition.
+   @param[in]      File          Pointer to the opened file.
+   @param[in]      LogicalBlock  Block number which the returned extent must cover.
+   @param[out]     Extent        Pointer to the output buffer, where the extent will be copied to.
+
+   @retval EFI_SUCCESS        Retrieval was succesful.
+   @retval EFI_NO_MAPPING     Block has no mapping.
+**/
+EFI_STATUS
+Ext4GetBlocks (
+  IN  EXT4_PARTITION  *Partition,
+  IN  EXT4_FILE       *File,
+  IN  EXT4_BLOCK_NR   LogicalBlock,
+  OUT EXT4_EXTENT     *Extent
+  );
+
 #endif
diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
index 12e89bf1fdfc..deaf89fb3743 100644
--- a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
+++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
@@ -111,6 +111,7 @@
   Collation.c
   Ext4Disk.h
   Ext4Dxe.h
+  BlockMap.c
 
 [Packages]
   MdePkg/MdePkg.dec
diff --git a/Features/Ext4Pkg/Ext4Dxe/Extents.c b/Features/Ext4Pkg/Ext4Dxe/Extents.c
index e920eed090fd..c3874df71751 100644
--- a/Features/Ext4Pkg/Ext4Dxe/Extents.c
+++ b/Features/Ext4Pkg/Ext4Dxe/Extents.c
@@ -1,7 +1,7 @@
 /** @file
   Extent related routines
 
-  Copyright (c) 2021 Pedro Falcato All rights reserved.
+  Copyright (c) 2021 - 2022 Pedro Falcato All rights reserved.
   SPDX-License-Identifier: BSD-2-Clause-Patent
 **/
 
@@ -244,10 +244,6 @@ Ext4GetExtent (
 
   DEBUG ((DEBUG_FS, "[ext4] Looking up extent for block %lu\n", LogicalBlock));
 
-  if (!(Inode->i_flags & EXT4_EXTENTS_FL)) {
-    return EFI_UNSUPPORTED;
-  }
-
   // ext4 does not have support for logical block numbers bigger than UINT32_MAX
   if (LogicalBlock > (UINT32)-1) {
     return EFI_NO_MAPPING;
@@ -261,6 +257,17 @@ Ext4GetExtent (
     return EFI_SUCCESS;
   }
 
+  if (!(Inode->i_flags & EXT4_EXTENTS_FL)) {
+    // If this is an older ext2/ext3 filesystem, emulate Ext4GetExtent using the block map
+    Status = Ext4GetBlocks (Partition, File, LogicalBlock, Extent);
+
+    if (!EFI_ERROR (Status)) {
+      Ext4CacheExtents (File, Extent, 1);
+    }
+
+    return Status;
+  }
+
   // Slow path, we'll need to read from disk and (try to) cache those extents.
 
   ExtHeader = Ext4GetInoExtentHeader (Inode);
diff --git a/Features/Ext4Pkg/Ext4Dxe/Inode.c b/Features/Ext4Pkg/Ext4Dxe/Inode.c
index f692909edf78..831f5946e870 100644
--- a/Features/Ext4Pkg/Ext4Dxe/Inode.c
+++ b/Features/Ext4Pkg/Ext4Dxe/Inode.c
@@ -1,7 +1,7 @@
 /** @file
   Inode related routines
 
-  Copyright (c) 2021 Pedro Falcato All rights reserved.
+  Copyright (c) 2021 - 2022 Pedro Falcato All rights reserved.
   SPDX-License-Identifier: BSD-2-Clause-Patent
 
   EpochToEfiTime copied from EmbeddedPkg/Library/TimeBaseLib.c
@@ -150,8 +150,9 @@ Ext4Read (
       if (!HasBackingExtent) {
         HoleLen = Partition->BlockSize - HoleOff;
       } else {
-        // Uninitialized extents behave exactly the same as file holes.
-        HoleLen = Ext4GetExtentLength (&Extent) - HoleOff;
+        // Uninitialized extents behave exactly the same as file holes, except they have
+        // blocks already allocated to them.
+        HoleLen = (Ext4GetExtentLength (&Extent) * Partition->BlockSize) - HoleOff;
       }
 
       WasRead = HoleLen > RemainingRead ? RemainingRead : HoleLen;
@@ -176,7 +177,7 @@ Ext4Read (
       if (EFI_ERROR (Status)) {
         DEBUG ((
           DEBUG_ERROR,
-          "[ext4] Error %x reading [%lu, %lu]\n",
+          "[ext4] Error %r reading [%lu, %lu]\n",
           Status,
           ExtentStartBytes + ExtentOffset,
           ExtentStartBytes + ExtentOffset + WasRead - 1
diff --git a/Features/Ext4Pkg/Ext4Dxe/Superblock.c b/Features/Ext4Pkg/Ext4Dxe/Superblock.c
index a7dbe9bf0fec..47fc3a65507a 100644
--- a/Features/Ext4Pkg/Ext4Dxe/Superblock.c
+++ b/Features/Ext4Pkg/Ext4Dxe/Superblock.c
@@ -1,7 +1,7 @@
 /** @file
   Superblock managing routines
 
-  Copyright (c) 2021 Pedro Falcato All rights reserved.
+  Copyright (c) 2021 - 2022 Pedro Falcato All rights reserved.
   SPDX-License-Identifier: BSD-2-Clause-Patent
 **/
 
@@ -208,11 +208,6 @@ Ext4OpenSuperblock (
     return EFI_UNSUPPORTED;
   }
 
-  // This should be removed once we add ext2/3 support in the future.
-  if ((Partition->FeaturesIncompat & EXT4_FEATURE_INCOMPAT_EXTENTS) == 0) {
-    return EFI_UNSUPPORTED;
-  }
-
   if (EXT4_HAS_INCOMPAT (Partition, EXT4_FEATURE_INCOMPAT_RECOVER)) {
     DEBUG ((DEBUG_WARN, "[ext4] Needs journal recovery, mounting read-only\n"));
     Partition->ReadOnly = TRUE;
-- 
2.35.1


  parent reply	other threads:[~2022-04-07 22:01 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-04-07 22:01 [PATCH edk2-platforms 0/3] Ext4Pkg: Add ext2/3 support and move crc16/32c to BaseLib Pedro Falcato
2022-04-07 22:01 ` [PATCH edk2-platforms 1/3] Ext4Pkg: Replace the CRC implementations with BaseLib Pedro Falcato
2022-04-07 22:01 ` [PATCH edk2-platforms 2/3] Ext4Pkg: Format using uncrustify Pedro Falcato
2022-04-07 22:01 ` Pedro Falcato [this message]
2022-04-25 17:14 ` [PATCH edk2-platforms 0/3] Ext4Pkg: Add ext2/3 support and move crc16/32c to BaseLib Pedro Falcato
     [not found] ` <16E9330A7A87074F.18109@groups.io>
2022-05-11 17:41   ` [edk2-devel] " Pedro Falcato
     [not found]   ` <16EE1DD8FA9A45F3.9448@groups.io>
2022-05-31 21:32     ` Pedro Falcato
2022-06-02  3:04       ` 回复: " gaoliming
2022-06-13 14:45         ` Pedro Falcato
2022-06-14  1:11           ` 回复: " gaoliming
2022-06-14 15:58             ` Pedro Falcato

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-list from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20220407220146.149580-4-pedro.falcato@gmail.com \
    --to=devel@edk2.groups.io \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox