import struct import time def hexdump(str): count = 0 gra = '' for c in str: print "%02X" % (ord(c)), if ord(c)>=32: gra = gra + c else: gra = gra + '.' count += 1 if (count == 16): print '\t', gra gra = '' count = 0 print ' ' * (16-count), '\t', gra def chl32to64(h,l): return h*(2.0**32)+l def c64tohl32(v): c32m = (2.0**32) return (int(v/c32m), v % c32m) def Appletime2PyTime(apple_time): return time.localtime(apple_time+long(time.mktime((1904, 01, 01, 00, 00, 00, 0, 0, 0)))-time.timezone) def PyTime2Appletime(py_time): return long(time.mktime(py_time))-long(time.mktime((1904, 01, 01, 00, 00, 00, 0, 0, 0)))-time.timezone class HFSPlusForkData: def __init__(self, volume): self.format = '>IIII' self.size = struct.calcsize(self.format)+64 self.volume = volume self.logicalSize = 0 self.clumpSize = 0 self.totalBlocks = 0 def from_string(self, str): self.data = str (logicalSizeH, logicalSizeL, self.clumpSize, self.totalBlocks) = struct.unpack(self.format, str[0:16]) self.logicalSize = chl32to64(logicalSizeH,logicalSizeL) self.extents = [] for i in range(0,8): self.extents.append(struct.unpack( '>II', str[16+8*i:24+8*i])) def read_block(self, block_idx, nb_block=1): tt_bk = 0 for e in self.extents: tt_bk += e[1] if tt_bk > block_idx: break if tt_bk <= block_idx: raise '%d not found in fork.' % (block_idx) start = e[1]-(tt_bk-block_idx) if (nb_block+start)>tt_bk: raise 'FIXME: Crossing extents is bad for the health now' return self.volume.read_block(e[0]+start, nb_block) def write_block(self, block_idx, data): nb_block = len(data)/self.volume.blockSize tt_bk = 0 for e in self.extents: tt_bk += e[1] if tt_bk > block_idx: break if tt_bk <= block_idx: raise '%d not found in fork.' % (block_idx) start = e[1]-(tt_bk-block_idx) if (nb_block+start)>tt_bk: raise 'FIXME: Crossing extents is bad for the health now' return self.volume.write_block(e[0]+start, data) def dump(self): hexdump(self.data) print "logicalSize %d, clumpSize %d, totalBlocks %d" % (self.logicalSize, self.clumpSize, self.totalBlocks) for i in range(0,8): print self.extents[i] def to_string(self): (logicalSizeH, logicalSizeL) = c64tohl32(self.logicalSize) self.data = struct.pack(self.format, logicalSizeH, logicalSizeL, self.clumpSize, self.totalBlocks) for i in range(0,8): self.data += struct.pack( '>II', self.extents[i][0], self.extents[i][1]) return self.data class HFSVolume: def __init__(self): # get only what wee need. self.format = '>2sIIhhhhhIIh94x2sHH' self.size = struct.calcsize(self.format) def read(self, device, offset): device.seek(offset) (self.drSigWord, self.drCrDate, self.drLsMod, self.drAtrb, self.drNmFls, self.drVBMSt, self.drAllocPtr, self.drNmAlBlks, self.drAlBlkSiz, self.drClpSiz, self.drAlBlSt, self.drEmbedSigWord, self.drEmbedStartBlock, self.drEmbedBlockCount) = struct.unpack(self.format, device.read(self.size)) def unwrap(self): if self.drSigWord != 'BD': raise "%s is not a valid signature for HFS." % (self.drSigWord) if self.drEmbedSigWord != 'H+': raise "%s is not a valid signature for HFS+ wrapper." % (self.drEmbedSigWord) self.hfsplus_start_offset = self.drAlBlSt*512 + (self.drEmbedStartBlock * self.drAlBlkSiz) self.hfsplus_len = self.drAlBlkSiz * self.drEmbedBlockCount return (self.hfsplus_start_offset, self.hfsplus_len) class HFSPlusVolume: def __init__(self, wrap_offset, len): self.volume_len = len self.wrap_offset = wrap_offset self.my_offset = 2*512 self.format = '>2sHI4sIIIIIIIIIIIIIIIII32x80s80s80s80s80s' self.size = struct.calcsize(self.format) def write(self): self.device.seek(self.wrap_offset+self.my_offset) self.device.write(struct.pack(self.format, self.signature, self.version, self.attributes, self.lastMountedVersion, self.reserved, self.createDate, self.modifyDate, self.backupDate, self.checkedDate, self.fileCount, self.folderCount, self.blockSize, self.totalBlocks, self.freeBlocks, self.nextAllocation, self.rsrcClumpSize, self.dataClumpSize, self.nextCatalogID, self.writeCount, self.encodingsBitmapH, self.encodingsBitmapL, self.allocationFile, self.extentsFile, self.catalogFile, self.attributesFile, self.startupFile)) def read(self, device): self.device = device device.seek(self.wrap_offset + self.my_offset) (self.signature, self.version, self.attributes, self.lastMountedVersion, self.reserved, self.createDate, self.modifyDate, self.backupDate, self.checkedDate, self.fileCount, self.folderCount, self.blockSize, self.totalBlocks, self.freeBlocks, self.nextAllocation, self.rsrcClumpSize, self.dataClumpSize, self.nextCatalogID, self.writeCount, self.encodingsBitmapH, self.encodingsBitmapL, self.allocationFile, self.extentsFile, self.catalogFile, self.attributesFile, self.startupFile) = struct.unpack(self.format, device.read(self.size)) if (self.signature != 'H+'): raise "%s is not a valid signature for HFS+ Volume Header." % (self.signature) # # # Stupid. Slow. Dumb. Easy. # # self.allocation_file_fork = HFSPlusForkData(self) self.allocation_file_fork.from_string(self.allocationFile) self.alloc_bmap = self.allocation_file_fork.read_block(0, self.allocation_file_fork.totalBlocks) def update(self): # # Write alloc_bmap # self.allocation_file_fork.write_block(0, self.alloc_bmap) # # Write header # self.write() def info(self): print "Version %d Last is %s" % (self.version, self.lastMountedVersion) print "fileCount %d, folderCount %d" % (self.fileCount, self.folderCount) print "blockSize %d, totalBlocks %d, freeBlocks %d" % (self.blockSize, self.totalBlocks, self.freeBlocks) print "nextAllocation %d, rsrcClumpSize %d, dataClumpSize %d" % (self.nextAllocation, self.rsrcClumpSize, self.dataClumpSize) def read_block(self, idx, nb): self.device.seek(idx*self.blockSize+self.wrap_offset) return self.device.read(self.blockSize*nb) def write_block(self, idx, data): self.device.seek(idx*self.blockSize+self.wrap_offset) return self.device.write(data) # # get fileId and forkData for a new file of given size. # # updates volume info # def alloc_file(self, size): nb_blocks = int(size/self.blockSize) if (size % self.blockSize) != 0: nb_blocks+=1 nb_chunks = int(nb_blocks/8) nb_remain = nb_blocks%8 if (nb_remain == 0): nb_chunks -=1 nb_remain = 8 if nb_chunks<1: raise "Doh! I'm dumb, and dunno how to alloc %d chunks and %d remain. This file is too small!!!" % (nb_chunks, nb_remain) # Do it like a pork. nb_chunks += 1 idx = self.alloc_bmap.find('\x00' * nb_chunks, (self.nextAllocation/8)-1) if (idx==-1): raise 'unable to alloc blocks' first_block = idx*8 nb_blocks_alloc = nb_chunks*8 print "I'm going to alloc %d blocks (for %d blocks, %d chunks, %d remain), starting at %d. Header told me %d" % (nb_blocks_alloc, nb_blocks, nb_chunks, nb_remain, first_block, self.nextAllocation) alloc = '\xFF' * nb_chunks self.alloc_bmap = self.alloc_bmap[0:idx] + alloc + self.alloc_bmap[idx+len(alloc):] # create an empty fork fork = HFSPlusForkData(self) fork.from_string('\x00' * fork.size) fork.logicalSize = size fork.clumpSize = self.dataClumpSize fork.totalBlocks = nb_blocks_alloc fork.extents[0] = (first_block, nb_blocks_alloc) # Get ID, and update volume fileID = self.nextCatalogID self.nextCatalogID += 1 self.fileCount += 1 self.freeBlocks -= nb_blocks_alloc # crea/modif date self.modifyDate = PyTime2Appletime(time.gmtime()) return (fileID, fork, self.modifyDate )