summaryrefslogtreecommitdiff
path: root/Features/Ext4Pkg/Ext4Dxe/BlockMap.c
blob: 8fee4476a5ac6c9146c0904921d1feb3e64a7633 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
/** @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 EXT2_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
      break;
  }

  return Type + 1;
}

/**
   @brief Get an extent from a block map
   Note: Also parses file holes and creates uninitialized 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 uninitialized, 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 successful.
   @retval EFI_NO_MAPPING     Block has no mapping.
**/
EFI_STATUS
Ext4GetBlocks (
  IN  EXT4_PARTITION  *Partition,
  IN  EXT4_FILE       *File,
  IN  EXT2_BLOCK_NR   LogicalBlock,
  OUT EXT4_EXTENT     *Extent
  )
{
  EXT4_INODE     *Inode;
  EXT2_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 - 1 == EXT4_TYPE_BAD_BLOCK) {
    // 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;
}