diff --git a/Sources/ContainerizationEXT4/EXT4+Formatter.swift b/Sources/ContainerizationEXT4/EXT4+Formatter.swift index e961744b..8a505465 100644 --- a/Sources/ContainerizationEXT4/EXT4+Formatter.swift +++ b/Sources/ContainerizationEXT4/EXT4+Formatter.swift @@ -1039,6 +1039,130 @@ extension EXT4 { } } + /// Recursively writes extent tree nodes to disk and returns the + /// ExtentIndex entries that the parent should store. + /// + /// At depth 1, this writes leaf blocks (containing ExtentLeaf entries). + /// At depth > 1, this recurses to build child subtrees first, then + /// writes index blocks pointing to those children. + private func writeExtentSubtree( + depth: UInt16, + numExtents: UInt32, + numBlocks: UInt32, + start: UInt32, + entriesPerBlock: UInt32, + extentBlockCount: inout UInt32 + ) throws -> [ExtentIndex] { + var indices: [ExtentIndex] = [] + let childDepth = depth - 1 + + // How many child blocks do we need at this level? + // Each child block can ultimately cover entriesPerBlock^childDepth leaf extents. + var leafCapacityPerChild: UInt32 = entriesPerBlock + for _ in 1.. Inode { var inode = inode // rest of code assumes that extents MUST go into a new block @@ -1048,14 +1172,31 @@ extension EXT4 { let dataBlocks = blocks.end - blocks.start let numExtents = (dataBlocks + EXT4.MaxBlocksPerExtent - 1) / EXT4.MaxBlocksPerExtent var usedBlocks = dataBlocks - let extentNodeSize = 12 - let extentsPerBlock = self.blockSize / extentNodeSize - 1 + let extentNodeSize: UInt32 = 12 + let entriesPerBlock = self.blockSize / extentNodeSize - 1 var blockData: [UInt8] = .init(repeating: 0, count: 60) var blockIndex: Int = 0 - switch numExtents { - case 0: + + guard numExtents > 0 else { return inode // noop - case 1..<5: + } + + // Determine the required tree depth. + // Depth 0: up to 4 extents fit inline in the inode. + // Depth N (N >= 1): each level multiplies capacity by entriesPerBlock, + // with up to 4 entries at the root. + var depth: UInt16 = 0 + var capacity: UInt32 = 4 + while capacity < numExtents { + depth += 1 + capacity = 4 + for _ in 0.. extentsPerBlock { - extentsInBlock = extentsPerBlock - } - let leafHeader = ExtentHeader( - magic: EXT4.ExtentHeaderMagic, - entries: UInt16(extentsInBlock), - max: UInt16(extentsPerBlock), - depth: 0, - generation: 0 - ) - var leafNode = ExtentLeafNode(header: leafHeader, leaves: []) - let offset = i * extentsPerBlock * EXT4.MaxBlocksPerExtent - fillExtents( - node: &leafNode, numExtents: extentsInBlock, numBlocks: dataBlocks, - start: blocks.start, - offset: offset) - try withUnsafeLittleEndianBytes(of: leafNode.header) { bytes in - try self.handle.write(contentsOf: bytes) - } - for leaf in leafNode.leaves { - try withUnsafeLittleEndianBytes(of: leaf) { bytes in - try self.handle.write(contentsOf: bytes) - } - } - let extentTail = ExtentTail(checksum: leafNode.leaves.last!.block) - try withUnsafeLittleEndianBytes(of: extentTail) { bytes in - try self.handle.write(contentsOf: bytes) - } - root.indices.append(extentIdx) - } - withUnsafeLittleEndianBytes(of: root.header) { bytes in + withUnsafeLittleEndianBytes(of: extentHeader) { bytes in for b in bytes { blockData[blockIndex] = b blockIndex = blockIndex + 1 } } - for leaf in root.indices { - withUnsafeLittleEndianBytes(of: leaf) { bytes in + for idx in rootIndices { + withUnsafeLittleEndianBytes(of: idx) { bytes in for b in bytes { blockData[blockIndex] = b blockIndex = blockIndex + 1 } } } - default: - throw Error.fileTooBig(UInt64(dataBlocks) * self.blockSize) } inode.block = ( blockData[0], blockData[1], blockData[2], blockData[3], blockData[4], blockData[5], blockData[6], diff --git a/Sources/ContainerizationEXT4/EXT4+Reader.swift b/Sources/ContainerizationEXT4/EXT4+Reader.swift index 54274aa7..c32a8563 100644 --- a/Sources/ContainerizationEXT4/EXT4+Reader.swift +++ b/Sources/ContainerizationEXT4/EXT4+Reader.swift @@ -196,61 +196,61 @@ extension EXT4 { func getExtents(inode: InodeNumber) throws -> [(start: UInt32, end: UInt32)]? { let inode = try self.getInode(number: inode) let inodeBlock = Data(tupleToArray(inode.block)) - var offset = 0 - var extents: [(start: UInt32, end: UInt32)] = [] let extentHeaderSize = MemoryLayout.size - let extentIndexSize = MemoryLayout.size - let extentLeafSize = MemoryLayout.size - // read extent header - let header = inodeBlock.subdata(in: offset.. 0), follows each + /// index entry to a child block and recurses. + private func readExtentNode( + data: Data, + header: ExtentHeader, + into extents: inout [(start: UInt32, end: UInt32)] + ) throws { + let extentHeaderSize = MemoryLayout.size + let extentIndexSize = MemoryLayout.size + let extentLeafSize = MemoryLayout.size + var offset = extentHeaderSize + + if header.depth == 0 { + // Leaf node: entries are ExtentLeaf mappings for _ in 0..