diff --git a/Sources/ContainerizationEXT4/EXT4+FileTree.swift b/Sources/ContainerizationEXT4/EXT4+FileTree.swift index db69c995..aa26c247 100644 --- a/Sources/ContainerizationEXT4/EXT4+FileTree.swift +++ b/Sources/ContainerizationEXT4/EXT4+FileTree.swift @@ -22,7 +22,9 @@ extension EXT4 { class FileTreeNode { let inode: InodeNumber let name: String - var children: [Ptr] = [] + private(set) var children: [Ptr] = [] + // Name index of `children` for O(1) lookup, maintained by addChild/removeChild. + private(set) var childIndex: [String: Ptr] = [:] var blocks: (start: UInt32, end: UInt32)? var additionalBlocks: [(start: UInt32, end: UInt32)]? var link: InodeNumber? @@ -39,16 +41,19 @@ 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.childIndex.removeAll() self.blocks = nil self.additionalBlocks = nil self.link = nil @@ -64,6 +69,16 @@ extension EXT4 { let path = components.reversed().joined(separator: "/") return FilePath(path).lexicallyNormalized() } + + func addChild(_ child: Ptr) { + children.append(child) + childIndex[child.pointee.name] = child + } + + func removeChild(named name: String) { + children.removeAll { $0.pointee.name == name } + childIndex[name] = nil + } } var root: Ptr @@ -82,18 +97,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.childIndex[component] else { return nil } + node = childPtr } return node } diff --git a/Sources/ContainerizationEXT4/EXT4+Formatter.swift b/Sources/ContainerizationEXT4/EXT4+Formatter.swift index 5f7ff44a..6cca3885 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 640b16ff..5b2fa90c 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/Tests/ContainerizationEXT4Tests/TestEXT4Reader+IO.swift b/Tests/ContainerizationEXT4Tests/TestEXT4Reader+IO.swift index 40d241d2..37b77566 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")) }