diff --git a/Package.swift b/Package.swift index 04d841190..462a55e44 100644 --- a/Package.swift +++ b/Package.swift @@ -106,6 +106,7 @@ let package = Package( name: "ContainerizationEXT4", dependencies: [ "ContainerizationArchive", + .product(name: "OrderedCollections", package: "swift-collections"), .product(name: "SystemPackage", package: "swift-system"), "ContainerizationOS", ], diff --git a/Sources/ContainerizationEXT4/EXT4+FileTree.swift b/Sources/ContainerizationEXT4/EXT4+FileTree.swift index db69c9950..8d8069afe 100644 --- a/Sources/ContainerizationEXT4/EXT4+FileTree.swift +++ b/Sources/ContainerizationEXT4/EXT4+FileTree.swift @@ -15,6 +15,7 @@ //===----------------------------------------------------------------------===// import Foundation +import OrderedCollections import SystemPackage extension EXT4 { @@ -22,7 +23,11 @@ extension EXT4 { class FileTreeNode { let inode: InodeNumber let name: String - var children: [Ptr] = [] + // Children keyed by name for O(1) lookup, preserving insertion order. + private(set) var childrenByName: OrderedDictionary> = [:] + var children: OrderedDictionary>.Values { + childrenByName.values + } var blocks: (start: UInt32, end: UInt32)? var additionalBlocks: [(start: UInt32, end: UInt32)]? var link: InodeNumber? @@ -39,16 +44,17 @@ extension EXT4 { ) { self.inode = inode self.name = name - self.children = children self.blocks = blocks self.additionalBlocks = additionalBlocks self.link = link self.parent = parent + for child in children { + self.addChild(child) + } } deinit { - self.children.removeAll() - self.children = [] + self.childrenByName.removeAll() self.blocks = nil self.additionalBlocks = nil self.link = nil @@ -64,6 +70,14 @@ extension EXT4 { let path = components.reversed().joined(separator: "/") return FilePath(path).lexicallyNormalized() } + + func addChild(_ child: Ptr) { + childrenByName[child.pointee.name] = child + } + + func removeChild(named name: String) { + childrenByName.removeValue(forKey: name) + } } var root: Ptr @@ -82,18 +96,10 @@ extension EXT4 { return node } for component in components { - var found = false - for childPtr in node.pointee.children { - let child = childPtr.pointee - if child.name == component { - node = childPtr - found = true - break - } - } - guard found else { + guard let childPtr = node.pointee.childrenByName[component] else { return nil } + node = childPtr } return node } diff --git a/Sources/ContainerizationEXT4/EXT4+Formatter.swift b/Sources/ContainerizationEXT4/EXT4+Formatter.swift index 5f7ff44aa..6cca38859 100644 --- a/Sources/ContainerizationEXT4/EXT4+Formatter.swift +++ b/Sources/ContainerizationEXT4/EXT4+Formatter.swift @@ -186,7 +186,7 @@ extension EXT4 { blocks: nil, link: targetNode.inode )) - parentTreeNode.children.append(linkTreeNodePtr) + parentTreeNode.addChild(linkTreeNodePtr) parentTreeNodePtr.pointee = parentTreeNode } @@ -254,9 +254,7 @@ extension EXT4 { } } parentInodePtr.pointee = parentInode - parentNode.children.removeAll { childPtr in - childPtr.pointee.name == pathComponent - } + parentNode.removeChild(named: pathComponent) parentNodePtr.pointee = parentNode if let hardlink = pathNode.link { @@ -412,7 +410,7 @@ extension EXT4 { children: [], blocks: (startBlock, endBlock) )) - parentTreeNode.children.append(childTreeNodePtr) + parentTreeNode.addChild(childTreeNodePtr) parentTreeNodePtr.pointee = parentTreeNode } childInode.mode = mode diff --git a/Sources/ContainerizationEXT4/EXT4+Reader.swift b/Sources/ContainerizationEXT4/EXT4+Reader.swift index 640b16ff4..5b2fa90c4 100644 --- a/Sources/ContainerizationEXT4/EXT4+Reader.swift +++ b/Sources/ContainerizationEXT4/EXT4+Reader.swift @@ -102,7 +102,7 @@ extension EXT4 { itemTreeNode.blocks = blocks.first } let itemTreeNodePtr = Ptr(itemTreeNode) - root.children.append(itemTreeNodePtr) + root.addChild(itemTreeNodePtr) itemPtr.pointee = root let itemInode = try self.getInode(number: itemInodeNum) if itemInode.mode.isDir() { diff --git a/Sources/ContainerizationEXT4/EXT4Reader+Export.swift b/Sources/ContainerizationEXT4/EXT4Reader+Export.swift index 2fd4109fc..762a7af4b 100644 --- a/Sources/ContainerizationEXT4/EXT4Reader+Export.swift +++ b/Sources/ContainerizationEXT4/EXT4Reader+Export.swift @@ -24,7 +24,7 @@ extension EXT4.EXT4Reader { format: .paxRestricted, filter: .none, options: [Options.xattrformat(.schily)]) let writer = try ArchiveWriter(configuration: config) try writer.open(file: archive.url) - var items = self.tree.root.pointee.children + var items = Array(self.tree.root.pointee.children) let hardlinkedInodes = Set(self.hardlinks.values) var hardlinkTargets: [EXT4.InodeNumber: FilePath] = [:] diff --git a/Tests/ContainerizationEXT4Tests/TestEXT4Reader+IO.swift b/Tests/ContainerizationEXT4Tests/TestEXT4Reader+IO.swift index 40d241d27..37b775669 100644 --- a/Tests/ContainerizationEXT4Tests/TestEXT4Reader+IO.swift +++ b/Tests/ContainerizationEXT4Tests/TestEXT4Reader+IO.swift @@ -617,10 +617,10 @@ struct EXT4PathIOTests { let tree = EXT4.FileTree(EXT4.RootInode, "/") let dirPtr = EXT4.Ptr(EXT4.FileTree.FileTreeNode(inode: 3, name: "dir", parent: tree.root)) - tree.root.pointee.children.append(dirPtr) + tree.root.pointee.addChild(dirPtr) let filePtr = EXT4.Ptr(EXT4.FileTree.FileTreeNode(inode: 4, name: "file", parent: dirPtr)) - dirPtr.pointee.children.append(filePtr) + dirPtr.pointee.addChild(filePtr) #expect(dirPtr.pointee.path == FilePath("/dir")) #expect(filePtr.pointee.path == FilePath("/dir/file")) @@ -631,10 +631,10 @@ struct EXT4PathIOTests { let tree = EXT4.FileTree(EXT4.RootInode, ".") let dirPtr = EXT4.Ptr(EXT4.FileTree.FileTreeNode(inode: 3, name: "dir", parent: tree.root)) - tree.root.pointee.children.append(dirPtr) + tree.root.pointee.addChild(dirPtr) let filePtr = EXT4.Ptr(EXT4.FileTree.FileTreeNode(inode: 4, name: "file", parent: dirPtr)) - dirPtr.pointee.children.append(filePtr) + dirPtr.pointee.addChild(filePtr) #expect(dirPtr.pointee.path == FilePath("dir")) #expect(filePtr.pointee.path == FilePath("dir/file")) @@ -645,7 +645,7 @@ struct EXT4PathIOTests { let tree = EXT4.FileTree(EXT4.RootInode, "dir") let filePtr = EXT4.Ptr(EXT4.FileTree.FileTreeNode(inode: 3, name: "file", parent: tree.root)) - tree.root.pointee.children.append(filePtr) + tree.root.pointee.addChild(filePtr) #expect(filePtr.pointee.path == FilePath("dir/file")) }