@@ -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