Skip to content

Commit 843b8ae

Browse files
committed
Add deep extent tree support (depth >= 2) in EXT4 formatter
1 parent 5691645 commit 843b8ae

1 file changed

Lines changed: 166 additions & 56 deletions

File tree

Sources/ContainerizationEXT4/EXT4+Formatter.swift

Lines changed: 166 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,6 +1039,130 @@ extension EXT4 {
10391039
}
10401040
}
10411041

1042+
/// Recursively writes extent tree nodes to disk and returns the
1043+
/// ExtentIndex entries that the parent should store.
1044+
///
1045+
/// At depth 1, this writes leaf blocks (containing ExtentLeaf entries).
1046+
/// At depth > 1, this recurses to build child subtrees first, then
1047+
/// writes index blocks pointing to those children.
1048+
private func writeExtentSubtree(
1049+
depth: UInt16,
1050+
numExtents: UInt32,
1051+
numBlocks: UInt32,
1052+
start: UInt32,
1053+
entriesPerBlock: UInt32,
1054+
extentBlockCount: inout UInt32
1055+
) throws -> [ExtentIndex] {
1056+
var indices: [ExtentIndex] = []
1057+
let childDepth = depth - 1
1058+
1059+
// How many child blocks do we need at this level?
1060+
// Each child block can ultimately cover entriesPerBlock^childDepth leaf extents.
1061+
var leafCapacityPerChild: UInt32 = entriesPerBlock
1062+
for _ in 1..<depth {
1063+
leafCapacityPerChild *= entriesPerBlock
1064+
}
1065+
let numChildren = (numExtents + leafCapacityPerChild - 1) / leafCapacityPerChild
1066+
var extentsWritten: UInt32 = 0
1067+
1068+
for _ in 0..<numChildren {
1069+
let extentsForChild = min(numExtents - extentsWritten, leafCapacityPerChild)
1070+
let logicalOffset = extentsWritten * EXT4.MaxBlocksPerExtent
1071+
1072+
if childDepth == 0 {
1073+
// Write a leaf block containing ExtentLeaf entries.
1074+
if self.pos % self.blockSize != 0 {
1075+
try self.seek(block: self.currentBlock + 1)
1076+
}
1077+
let leafBlockAddr = self.currentBlock
1078+
extentBlockCount += 1
1079+
1080+
let leafHeader = ExtentHeader(
1081+
magic: EXT4.ExtentHeaderMagic,
1082+
entries: UInt16(extentsForChild),
1083+
max: UInt16(entriesPerBlock),
1084+
depth: 0,
1085+
generation: 0
1086+
)
1087+
var leafNode = ExtentLeafNode(header: leafHeader, leaves: [])
1088+
fillExtents(
1089+
node: &leafNode, numExtents: extentsForChild, numBlocks: numBlocks,
1090+
start: start,
1091+
offset: logicalOffset)
1092+
try withUnsafeLittleEndianBytes(of: leafNode.header) { bytes in
1093+
try self.handle.write(contentsOf: bytes)
1094+
}
1095+
for leaf in leafNode.leaves {
1096+
try withUnsafeLittleEndianBytes(of: leaf) { bytes in
1097+
try self.handle.write(contentsOf: bytes)
1098+
}
1099+
}
1100+
let checksum = leafNode.leaves.last?.block ?? 0
1101+
let extentTail = ExtentTail(checksum: checksum)
1102+
try withUnsafeLittleEndianBytes(of: extentTail) { bytes in
1103+
try self.handle.write(contentsOf: bytes)
1104+
}
1105+
1106+
indices.append(
1107+
ExtentIndex(
1108+
block: logicalOffset,
1109+
leafLow: leafBlockAddr,
1110+
leafHigh: 0,
1111+
unused: 0
1112+
))
1113+
} else {
1114+
// Recurse to build the child subtree, then write an index
1115+
// block pointing to the returned child indices.
1116+
let childIndices = try writeExtentSubtree(
1117+
depth: childDepth,
1118+
numExtents: extentsForChild,
1119+
numBlocks: numBlocks,
1120+
start: start,
1121+
entriesPerBlock: entriesPerBlock,
1122+
extentBlockCount: &extentBlockCount
1123+
)
1124+
1125+
// Write the index block for this subtree.
1126+
if self.pos % self.blockSize != 0 {
1127+
try self.seek(block: self.currentBlock + 1)
1128+
}
1129+
let indexBlockAddr = self.currentBlock
1130+
extentBlockCount += 1
1131+
1132+
let indexHeader = ExtentHeader(
1133+
magic: EXT4.ExtentHeaderMagic,
1134+
entries: UInt16(childIndices.count),
1135+
max: UInt16(entriesPerBlock),
1136+
depth: childDepth,
1137+
generation: 0
1138+
)
1139+
try withUnsafeLittleEndianBytes(of: indexHeader) { bytes in
1140+
try self.handle.write(contentsOf: bytes)
1141+
}
1142+
for childIdx in childIndices {
1143+
try withUnsafeLittleEndianBytes(of: childIdx) { bytes in
1144+
try self.handle.write(contentsOf: bytes)
1145+
}
1146+
}
1147+
let checksum = childIndices.last?.block ?? 0
1148+
let extentTail = ExtentTail(checksum: checksum)
1149+
try withUnsafeLittleEndianBytes(of: extentTail) { bytes in
1150+
try self.handle.write(contentsOf: bytes)
1151+
}
1152+
1153+
indices.append(
1154+
ExtentIndex(
1155+
block: logicalOffset,
1156+
leafLow: indexBlockAddr,
1157+
leafHigh: 0,
1158+
unused: 0
1159+
))
1160+
}
1161+
extentsWritten += extentsForChild
1162+
}
1163+
return indices
1164+
}
1165+
10421166
private func writeExtents(_ inode: Inode, _ blocks: (start: UInt32, end: UInt32)) throws -> Inode {
10431167
var inode = inode
10441168
// rest of code assumes that extents MUST go into a new block
@@ -1048,14 +1172,31 @@ extension EXT4 {
10481172
let dataBlocks = blocks.end - blocks.start
10491173
let numExtents = (dataBlocks + EXT4.MaxBlocksPerExtent - 1) / EXT4.MaxBlocksPerExtent
10501174
var usedBlocks = dataBlocks
1051-
let extentNodeSize = 12
1052-
let extentsPerBlock = self.blockSize / extentNodeSize - 1
1175+
let extentNodeSize: UInt32 = 12
1176+
let entriesPerBlock = self.blockSize / extentNodeSize - 1
10531177
var blockData: [UInt8] = .init(repeating: 0, count: 60)
10541178
var blockIndex: Int = 0
1055-
switch numExtents {
1056-
case 0:
1179+
1180+
guard numExtents > 0 else {
10571181
return inode // noop
1058-
case 1..<5:
1182+
}
1183+
1184+
// Determine the required tree depth.
1185+
// Depth 0: up to 4 extents fit inline in the inode.
1186+
// Depth N (N >= 1): each level multiplies capacity by entriesPerBlock,
1187+
// with up to 4 entries at the root.
1188+
var depth: UInt16 = 0
1189+
var capacity: UInt32 = 4
1190+
while capacity < numExtents {
1191+
depth += 1
1192+
capacity = 4
1193+
for _ in 0..<depth {
1194+
capacity *= entriesPerBlock
1195+
}
1196+
}
1197+
1198+
if depth == 0 {
1199+
// All extents fit inline in the inode's 60-byte block field.
10591200
let extentHeader = ExtentHeader(
10601201
magic: EXT4.ExtentHeaderMagic,
10611202
entries: UInt16(numExtents),
@@ -1079,73 +1220,42 @@ extension EXT4 {
10791220
}
10801221
}
10811222
}
1082-
case 5..<4 * UInt32(extentsPerBlock) + 1:
1083-
let extentBlocks = (numExtents + extentsPerBlock - 1) / extentsPerBlock
1084-
usedBlocks += extentBlocks
1223+
} else {
1224+
// Build the extent tree bottom-up. writeExtentSubtree writes
1225+
// child blocks to disk and returns the index entries for the
1226+
// parent level.
1227+
var extentBlockCount: UInt32 = 0
1228+
let rootIndices = try writeExtentSubtree(
1229+
depth: depth,
1230+
numExtents: numExtents,
1231+
numBlocks: dataBlocks,
1232+
start: blocks.start,
1233+
entriesPerBlock: entriesPerBlock,
1234+
extentBlockCount: &extentBlockCount
1235+
)
1236+
usedBlocks += extentBlockCount
1237+
10851238
let extentHeader = ExtentHeader(
10861239
magic: EXT4.ExtentHeaderMagic,
1087-
entries: UInt16(extentBlocks),
1240+
entries: UInt16(rootIndices.count),
10881241
max: 4,
1089-
depth: 1,
1242+
depth: depth,
10901243
generation: 0
10911244
)
1092-
var root = ExtentIndexNode(header: extentHeader, indices: [])
1093-
for i in 0..<extentBlocks {
1094-
if self.pos % self.blockSize != 0 {
1095-
try self.seek(block: self.currentBlock + 1)
1096-
}
1097-
let extentIdx = ExtentIndex(
1098-
block: i * extentsPerBlock * EXT4.MaxBlocksPerExtent,
1099-
leafLow: self.currentBlock,
1100-
leafHigh: 0,
1101-
unused: 0)
1102-
var extentsInBlock = numExtents - i * extentsPerBlock
1103-
if extentsInBlock > extentsPerBlock {
1104-
extentsInBlock = extentsPerBlock
1105-
}
1106-
let leafHeader = ExtentHeader(
1107-
magic: EXT4.ExtentHeaderMagic,
1108-
entries: UInt16(extentsInBlock),
1109-
max: UInt16(extentsPerBlock),
1110-
depth: 0,
1111-
generation: 0
1112-
)
1113-
var leafNode = ExtentLeafNode(header: leafHeader, leaves: [])
1114-
let offset = i * extentsPerBlock * EXT4.MaxBlocksPerExtent
1115-
fillExtents(
1116-
node: &leafNode, numExtents: extentsInBlock, numBlocks: dataBlocks,
1117-
start: blocks.start,
1118-
offset: offset)
1119-
try withUnsafeLittleEndianBytes(of: leafNode.header) { bytes in
1120-
try self.handle.write(contentsOf: bytes)
1121-
}
1122-
for leaf in leafNode.leaves {
1123-
try withUnsafeLittleEndianBytes(of: leaf) { bytes in
1124-
try self.handle.write(contentsOf: bytes)
1125-
}
1126-
}
1127-
let extentTail = ExtentTail(checksum: leafNode.leaves.last!.block)
1128-
try withUnsafeLittleEndianBytes(of: extentTail) { bytes in
1129-
try self.handle.write(contentsOf: bytes)
1130-
}
1131-
root.indices.append(extentIdx)
1132-
}
1133-
withUnsafeLittleEndianBytes(of: root.header) { bytes in
1245+
withUnsafeLittleEndianBytes(of: extentHeader) { bytes in
11341246
for b in bytes {
11351247
blockData[blockIndex] = b
11361248
blockIndex = blockIndex + 1
11371249
}
11381250
}
1139-
for leaf in root.indices {
1140-
withUnsafeLittleEndianBytes(of: leaf) { bytes in
1251+
for idx in rootIndices {
1252+
withUnsafeLittleEndianBytes(of: idx) { bytes in
11411253
for b in bytes {
11421254
blockData[blockIndex] = b
11431255
blockIndex = blockIndex + 1
11441256
}
11451257
}
11461258
}
1147-
default:
1148-
throw Error.fileTooBig(UInt64(dataBlocks) * self.blockSize)
11491259
}
11501260
inode.block = (
11511261
blockData[0], blockData[1], blockData[2], blockData[3], blockData[4], blockData[5], blockData[6],

0 commit comments

Comments
 (0)