from structs import * """ Probably buggy HAX, only works on case SENSITIVE """ class BTree(object): def __init__(self, file, keyStruct, dataStruct): self.file = file self.keyStruct = keyStruct self.dataStruct = dataStruct block0 = self.file.readBlock(0) btnode = BTNodeDescriptor.parse(block0) assert btnode.kind == kBTHeaderNode self.header = BTHeaderRec.parse(block0[BTNodeDescriptor.sizeof():]) assert self.header.keyCompareType == 0 or self.header.keyCompareType == 0 or kHFSBinaryCompare #TODO: do more testing when nodeSize != blockSize self.nodeSize = self.header.nodeSize self.nodesInBlock = file.blockSize / self.header.nodeSize self.blocksForNode = self.header.nodeSize / file.blockSize #print file.blockSize , self.header.nodeSize self.lastRecordNumber = 0 type, (hdr, maprec) = self.readBtreeNode(0) assert len(maprec) == self.nodeSize - 256 if self.header.totalNodes / 8 > len(maprec): pass #TODO: handle map records self.maprec = maprec def isNodeInUse(self, nodeNumber): thisByte = ord(self.maprec[nodeNumber / 8]) return (thisByte & (1 << (7 - (nodeNumber % 8)))) != 0 def readEmptySpace(self): res = "" z = 0 for i in xrange(self.header.totalNodes): if not self.isNodeInUse(i): z += 1 res += self.readNode(i) assert z == self.header.freeNodes return res #convert construct structure to tuple def getComparableKey(self, k): raise Exception("implement in subclass") def compareKeys(self, k1, k2): k2 = self.getComparableKey(k2) if k1 == k2: return 0 return -1 if k1 < k2 else 1 def printLeaf(self, key, data): print key, data def readNode(self, nodeNumber): node = "" for i in xrange(self.blocksForNode): node += self.file.readBlock(nodeNumber * self.blocksForNode + i) return node def readBtreeNode(self, nodeNumber): self.lastnodeNumber = nodeNumber node = self.readNode(nodeNumber) self.lastbtnode = btnode = BTNodeDescriptor.parse(node) if btnode.kind == kBTHeaderNode: assert btnode.numRecords == 3 end = self.nodeSize - 8 #2*4 offsets = Array(btnode.numRecords+1, UBInt16("off")).parse(node[end:]) assert offsets[-4] == end hdr = BTHeaderRec.parse(node[BTNodeDescriptor.sizeof():]) maprec = node[offsets[-3]:end] return kBTHeaderNode, [hdr, maprec] elif btnode.kind == kBTIndexNode: recs = [] offsets = Array(btnode.numRecords, UBInt16("off")).parse(node[-2*btnode.numRecords:]) for i in xrange(btnode.numRecords): off = offsets[btnode.numRecords-i-1] k = self.keyStruct.parse(node[off:]) off += 2 + k.keyLength k.childNode = UBInt32("nodeNumber").parse(node[off:off+4]) recs.append(k) return kBTIndexNode, recs elif btnode.kind == kBTLeafNode: recs = [] offsets = Array(btnode.numRecords, UBInt16("off")).parse(node[-2*btnode.numRecords:]) for i in xrange(btnode.numRecords): off = offsets[btnode.numRecords-i-1] k = self.keyStruct.parse(node[off:]) off += 2 + k.keyLength d = self.dataStruct.parse(node[off:]) recs.append((k,d)) return kBTLeafNode, recs else: raise Exception("Invalid node type " + str(btnode)) def search(self, searchKey, node=None): if node == None: node = self.header.rootNode type, stuff = self.readBtreeNode(node) if len(stuff) == 0: return None, None if type == kBTIndexNode: for i in xrange(len(stuff)): if self.compareKeys(searchKey, stuff[i]) < 0: if i > 0: i = i - 1 return self.search(searchKey, stuff[i].childNode) return self.search(searchKey, stuff[len(stuff)-1].childNode) elif type == kBTLeafNode: self.lastRecordNumber = 0 for k,v in stuff: res = self.compareKeys(searchKey, k) if res == 0: return k, v if res < 0: return None, None self.lastRecordNumber += 1 return None, None def traverse(self, node=None, count=0, callback=None): if node == None: node = self.header.rootNode type, stuff = self.readBtreeNode(node) if type == kBTIndexNode: for i in xrange(len(stuff)): count += self.traverse(stuff[i].childNode, callback=callback) elif type == kBTLeafNode: for k,v in stuff: if callback: callback(k,v) else: self.printLeaf(k, v) count += 1 return count def traverseLeafNodes(self, callback=None): nodeNumber = self.header.firstLeafNode count = 0 while nodeNumber != 0: _, stuff = self.readBtreeNode(nodeNumber) count += len(stuff) for k,v in stuff: if callback: callback(k,v) else: self.printLeaf(k, v) nodeNumber = self.lastbtnode.fLink return count #XXX def searchMultiple(self, searchKey, filterKeyFunction=lambda x:False): self.search(searchKey) nodeNumber = self.lastnodeNumber recordNumber = self.lastRecordNumber kv = [] while nodeNumber != 0: _, stuff = self.readBtreeNode(nodeNumber) for k,v in stuff[recordNumber:]: if filterKeyFunction(k): kv.append((k,v)) else: return kv nodeNumber = self.lastbtnode.fLink recordNumber = 0 return kv def getLBAsHax(self): nodes = [self.lastnodeNumber] n = self.lastbtnode for i in xrange(2): nodes.append(self.lastbtnode.bLink) self.readBtreeNode(self.lastbtnode.bLink) self.lastbtnode = n for i in xrange(2): nodes.append(self.lastbtnode.fLink) self.readBtreeNode(self.lastbtnode.fLink) res = [] for n in nodes: res.append(self.file.getLBAforBlock(n * self.blocksForNode)) return res class CatalogTree(BTree): def __init__(self, file, volume): super(CatalogTree,self).__init__(file, HFSPlusCatalogKey, HFSPlusCatalogData) self.volume = volume def printLeaf(self, k, d): if d.recordType == kHFSPlusFolderRecord or d.recordType == kHFSPlusFileRecord: print getString(k) def getComparableKey(self, k2): #XXX http://dubeiko.com/development/FileSystems/HFSPLUS/tn1150.html#StringComparisonAlgorithm return (k2.parentID, getString(k2)) def searchByCNID(self, cnid): threadk, threadd = self.search((cnid, "")) return self.search((threadd.data.parentID, getString(threadd.data))) if threadd else (None, None) def getFolderContents(self, cnid): return self.searchMultiple((cnid, ""), lambda k:k.parentID == cnid) def getRecordFromPath(self, path): if not path.startswith("/"): return None, None if path == "/": return self.searchByCNID(kHFSRootFolderID) parentId=kHFSRootFolderID i = 1 k, v = None, None for p in path.split("/")[1:]: if p == "": break k,v = self.search((parentId, p)) if (k,v) == (None, None): return None, None if v.recordType == kHFSPlusFolderRecord: parentId = v.data.folderID elif v.recordType == kHFSPlusFileRecord and is_symlink(v.data): linkdata = self.volume.readFileByRecord(v) print "symlink %s => %s" % (p, linkdata) if not linkdata: return None, None t = path.split("/") t[i] = linkdata newpath = "/".join(t) return self.getRecordFromPath(newpath) else: break i += 1 return k,v class ExtentsOverflowTree(BTree): def __init__(self, file): super(ExtentsOverflowTree,self).__init__(file, HFSPlusExtentKey, HFSPlusExtentRecord) def getComparableKey(self, k2): return (k2.fileID, k2.forkType, k2.startBlock) def searchExtents(self, fileID, forkType, startBlock): return self.search((fileID, forkType, startBlock)) class AttributesTree(BTree): def __init__(self, file): super(AttributesTree,self).__init__(file, HFSPlusAttrKey, HFSPlusAttrData) def printLeaf(self, k, d): print k.fileID, getString(k), d.data.encode("hex") def getComparableKey(self, k2): return (k2.fileID, getString(k2)) def searchXattr(self, fileID, name): k,v = self.search((fileID, name)) return v.data if v else None def getAllXattrs(self, fileID): res = {} for k,v in self.searchMultiple((fileID, ""), lambda k:k.fileID == fileID): res[getString(k)] = v.data return res