Created
September 9, 2014 18:20
-
-
Save mountainstorm/e2488899bc3edfad8d83 to your computer and use it in GitHub Desktop.
hfsplus structures, allows parsing the hfsplus file structure and changing it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/python | |
| # coding: latin-1 | |
| # Copyright (c) 2013 Mountainstorm | |
| # | |
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |
| # of this software and associated documentation files (the "Software"), to deal | |
| # in the Software without restriction, including without limitation the rights | |
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| # copies of the Software, and to permit persons to whom the Software is | |
| # furnished to do so, subject to the following conditions: | |
| # | |
| # The above copyright notice and this permission notice shall be included in all | |
| # copies or substantial portions of the Software. | |
| # | |
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| # SOFTWARE. | |
| """Python Enumerations, with ctypes support. | |
| Typically all you need to do is produce a subclass of the 'Enumeration' class | |
| and add a _values_ class member. | |
| The only member you need in your class is '_values_'. This should be an array | |
| of 2-tuples; the first value of the tuple should be a string name for the | |
| enumeration value, the second its integer value. If you want, you can use a | |
| string in place of the tuple; in this case the value is sequential from the last | |
| one with a value. | |
| By default the ctype of an enumeration in c_long, and values are given integer | |
| values starting from zero e.g. just like in C | |
| Other class member you might want experiment with are: | |
| * _ctype_ ; if present provides the ctype for the enumeration | |
| * _start_value_ ; if present numbering starts from this rather than zero | |
| For example: | |
| class MH_FILETYPE(Enumeration): | |
| _ctype_ = c_uint32 | |
| _values_ = [ | |
| 'MH_UNKNOWN', # value is 0 | |
| ('MH_EXECUTE', 2), # value is 2 | |
| 'MH_FVMLIB' # value is 3 | |
| ] | |
| To use as a defined value: | |
| >>> MH_FILETYPE.MH_EXECUTE | |
| 2 | |
| To lookup a value's name: | |
| >>> MH_FILETYPE[2] | |
| MH_EXECUTE | |
| To create an instance: | |
| >>> a = MH_FILETYPE(MH_FILETYPE.MH_EXECUTE) | |
| To get the name and value of a instance: | |
| >>> a.name | |
| MH_EXECUTE | |
| >>> a.value | |
| 2 | |
| To test if a value is valid for the enumeration: | |
| >>> 2 in MH_FILETYPE | |
| True | |
| You can also write/read them from disk (and use them within ctype structures): | |
| >>> f = open("test", "wb+") | |
| >>> f.write(a) | |
| >>> f.tell() | |
| 4 | |
| >>> f.seek(0) | |
| >>> b = MH_FILETYPE() | |
| >>> f.readinto(b) | |
| >>> f.tell() | |
| 4 | |
| >>> b | |
| <value MH_EXECUTE=2 of <Enumeration MH_FILETYPE>> | |
| Finally, you can interate over the class to see all the avaliable names: | |
| >>> for k, v in MH_FILETYPE: | |
| ... print k, v | |
| MH_UNKNOWN 0 | |
| MH_EXECUTE 2 | |
| MH_FVMLIB 3 | |
| """ | |
| from ctypes import * | |
| class EnumerationType(type(c_long)): | |
| def __new__(metacls, name, bases, dict): | |
| cls = None | |
| if bases[0].__name__ != "Enumeration": | |
| # creating a base class - just do the default construction | |
| cls = type(c_long).__new__(metacls, name, bases, dict) | |
| else: | |
| # creating an derivation of Enumeration - make it inherit from | |
| # both actual ctype we want to be, and the original base | |
| # (Enumeration). We make sure the ctype is the first parent | |
| # so that it overrides the parent of Enumeration (c_long) | |
| ctype = c_long | |
| if "_ctype_" in dict and dict["_ctype_"] is not c_long: | |
| ctype = dict["_ctype_"] | |
| bases = (ctype,) + bases | |
| cls = type(ctype).__new__(metacls, name, bases, dict) | |
| return cls | |
| def __init__(cls, name, bases, dict): | |
| if bases[0].__name__ == "Enumeration": | |
| # on init of a subclass of Enumeration fill its lookup tables | |
| if cls._values_ is not None: | |
| cls._namesByValue = {} | |
| cls._valuesByName = {} | |
| # setup start value | |
| start_value = 0 | |
| if cls._start_value_ is not None: | |
| start_value = cls._start_value_ | |
| # process _values_ and create the lookup dicts | |
| curName = None | |
| curValue = start_value | |
| for v in cls._values_: | |
| if isinstance(v, tuple): | |
| curName = v[0] | |
| if len(v) == 2: | |
| curValue = v[1] | |
| else: | |
| curName = v | |
| if curValue in cls._namesByValue: | |
| val = cls._namesByValue[curValue] | |
| if isinstance(val, str): | |
| # convert from string to tuple of strings | |
| cls._namesByValue[curValue] = (val, curName) | |
| else: | |
| # append additional elements to the tuple | |
| cls._namesByValue[curValue] = val + (curName,) | |
| else: | |
| cls._namesByValue[curValue] = curName | |
| cls._valuesByName[curName] = curValue | |
| curValue += 1 | |
| def __contains__(cls, value): | |
| retVal = None | |
| if not (isinstance(value, str) and value.startswith("_")): | |
| if value in cls._namesByValue: | |
| retVal = cls._namesByValue[value] | |
| return retVal | |
| def __iter__(cls): | |
| for value in sorted(cls._namesByValue.keys()): | |
| name = cls._namesByValue[value] | |
| if isinstance(name, str) or isinstance(name, unicode): | |
| yield (name, value) | |
| else: | |
| for n in name: | |
| yield (n, value) | |
| def __getattr__(cls, name): | |
| retVal = None | |
| if not name.startswith("_") and "_values_" in cls.__dict__: | |
| if name in cls._valuesByName: | |
| retVal = cls._valuesByName[name] | |
| return retVal | |
| def __getitem__(cls, values): | |
| retVal = None | |
| if values in cls._namesByValue: | |
| retVal = cls._namesByValue[values] | |
| return retVal | |
| def __repr__(cls): | |
| return "<Enumeration %s>" % cls.__name__ | |
| # We derive from c_long as we need something with a _type_ member | |
| # to keep the metaclass (derrived from c_long's type happy. We'll override this | |
| # in the meta class when subclasses of Enumeration are created | |
| class Enumeration(c_long): | |
| kBitValue = 0x0 | |
| kBitShift = 0x1 | |
| __metaclass__ = EnumerationType | |
| def __getattr__(self, key): | |
| retVal = None | |
| if key == "name": | |
| retVal = self.__class__._namesByValue[self.value] | |
| else: | |
| retVal = dict.__getattr__(self, key) | |
| return retVal | |
| def __repr__(self): | |
| return "<value %s=%d of %r>" % (self.name, self.value, self.__class__) | |
| @classmethod | |
| def from_param(cls, param): | |
| retVal = None | |
| if isinstance(param, Enumeration): | |
| if param.__class__ == cls: | |
| retVal = param | |
| else: | |
| raise ValueError("Can't mix enumeration values") | |
| else: | |
| retVal = cls(param) | |
| return retVal | |
| @classmethod | |
| def to_bitfield(cls, values): | |
| retval = 0 | |
| if '_bitfield_' in dir(cls): | |
| if cls._bitfield_ == Enumeration.kBitShift: | |
| if type(values) == list: | |
| for value in values: | |
| retval |= 1 << value | |
| else: | |
| retval = 1 << values | |
| else: | |
| if type(values) == list: | |
| for value in values: | |
| retval |= vaue | |
| else: | |
| retval = value | |
| else: | |
| raise ValueError(u'Enumeration does not have _bitfield_ member') | |
| return retval | |
| @classmethod | |
| def from_bitfield(cls, value, asstr=False): | |
| retval = [] | |
| if '_bitfield_' in dir(cls): | |
| for i in range(0, 64): | |
| sh = 1 << i | |
| if value & sh: | |
| #print '%s shift: %d' % (cls.__name__, i) | |
| if cls._bitfield_ == Enumeration.kBitShift: | |
| if i in cls: | |
| if asstr: | |
| retval.append(cls[i]) | |
| else: | |
| retval.append(i) | |
| elif asstr == True: | |
| retval.append('%d?' % i) | |
| else: | |
| v = value & sh | |
| if v in cls: | |
| if asstr: | |
| retval.append(cls[v]) | |
| else: | |
| retval.append(v) | |
| elif asstr == True: | |
| retval.append('%d?' % i) | |
| else: | |
| raise ValueError(u'Enumeration does not have _bitfield_ member') | |
| return retval | |
| if __name__ == "__main__": | |
| class MH_FILETYPE(Enumeration): | |
| _ctype_ = c_uint32 | |
| _values_ = [ | |
| 'MH_UNKNOWN', # value is 0 | |
| ('MH_EXECUTE', 2), # value is 2 | |
| 'MH_FVMLIB' # value is 3 | |
| ] | |
| print((MH_FILETYPE.MH_EXECUTE)) | |
| print((MH_FILETYPE[2])) | |
| a = MH_FILETYPE(MH_FILETYPE.MH_EXECUTE) | |
| print((a.name)) | |
| print((a.value)) | |
| print((2 in MH_FILETYPE)) | |
| f = open("test", "wb+") | |
| f.write(a) | |
| print((f.tell())) | |
| f.seek(0) | |
| b = MH_FILETYPE() | |
| f.readinto(b) | |
| print((f.tell())) | |
| print(b) | |
| for k, v in MH_FILETYPE: | |
| print((k, v)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/python | |
| # coding: utf-8 | |
| # Copyright (c) 2014 Mountainstorm | |
| # | |
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |
| # of this software and associated documentation files (the "Software"), to deal | |
| # in the Software without restriction, including without limitation the rights | |
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| # copies of the Software, and to permit persons to whom the Software is | |
| # furnished to do so, subject to the following conditions: | |
| # | |
| # The above copyright notice and this permission notice shall be included in all | |
| # copies or substantial portions of the Software. | |
| # | |
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| # SOFTWARE. | |
| from ctypes import * | |
| from enumeration import * | |
| from copy import deepcopy | |
| def indent(value, istr): | |
| parts = value.__str__().split(u'\n') | |
| retval = u'' | |
| for p in parts: | |
| retval += istr + p + u'\n' | |
| return retval[:-1] | |
| class HFSUniStr255(BigEndianStructure): | |
| _pack_ = 1 | |
| _fields_ = [ | |
| (u'length', c_uint16), | |
| (u'unicode', c_uint16 * 255) | |
| ] | |
| def __str__(self): | |
| unistr = u'' | |
| for i in range(0, self.length): | |
| unitstr += c_wchar(self.unicode[i].value) | |
| return u'''HFSUniStr255: | |
| length: %x | |
| unicode: '%s' | |
| ''' % (self.length, unistr) | |
| class TextEncoding(Enumeration): | |
| _ctype_ = c_uint32 | |
| _bitfield_ = Enumeration.kBitShift | |
| _values_ = [ | |
| u'MacRoman', | |
| u'MacJapanese', | |
| u'MacChineseTrad', | |
| u'MacKorean', | |
| u'MacArabic', | |
| u'MacHebrew', | |
| u'MacGreek', | |
| u'MacCyrillic', | |
| u'MacDevanagari', | |
| u'MacGurmukhi', | |
| u'MacGujarati', | |
| u'MacOriya', | |
| u'MacBengali', | |
| u'MacTamil', | |
| u'MacTelugu', | |
| u'MacKannada', | |
| u'MacMalayalam', | |
| u'MacSinhalese', | |
| u'MacBurmese', | |
| u'MacKhmer', | |
| u'MacThai', | |
| u'MacLaotian', | |
| u'MacGeorgian', | |
| u'MacArmenian', | |
| u'MacChineseSimp', | |
| u'MacTibetan', | |
| u'MacMongolian', | |
| u'MacEthiopic', | |
| u'MacCentralEurRoman', | |
| u'MacVietnamese', | |
| u'MacExtArabic', | |
| u'MacSymbol', | |
| u'MacDingbats', | |
| u'MacTurkish', | |
| u'MacCroatian', | |
| u'MacIcelandic', | |
| u'MacRomanian', | |
| (u'MacFarsiBit', 49), # dont use directly | |
| (u'MacUkrainianBit', 48), # dont use directly | |
| (u'MacFarsi', 140), | |
| (u'MacUkrainian', 152) | |
| ] | |
| @classmethod | |
| def to_bitfield(cls, values): | |
| newvalues = [] | |
| if type(values) != list: | |
| values = [values] | |
| for value in values: | |
| bitname = value + 'Bit' | |
| if bitname in cls: | |
| newvalues.append(bitname) | |
| else: | |
| newvalues.append(value) | |
| return Enumeration.to_bitfield(cls, newvalues) | |
| @classmethod | |
| def from_bitfield(cls, value, asstr=False): | |
| retval = [] | |
| res = super(TextEncoding, cls).from_bitfield(value, True) | |
| for value in res: | |
| if value.endswith('Bit'): | |
| retval.append(value[:-3]) | |
| else: | |
| retval.append(value) | |
| if asstr == False: | |
| valret = [] | |
| # convert to values | |
| for value in retval: | |
| valret.append(cls._valuesByName[value]) | |
| retval = valret | |
| return retval | |
| class AdminFlags(Enumeration): | |
| _ctype_ = c_uint32 | |
| _values_ = [ | |
| (u'SF_ARCHIVED', 1 << 0), | |
| (u'SF_IMMUTABLE', 1 << 1), | |
| (u'SF_APPEND', 1 << 2) | |
| ] | |
| class OwnerFlags(Enumeration): | |
| _ctype_ = c_uint32 | |
| _values_ = [ | |
| (u'UF_NODUMP', 1 << 0), | |
| (u'SF_IMMUTABLE', 1 << 1), | |
| (u'SF_APPEND', 1 << 2), | |
| (u'UF_OPAQUE', 1 << 3) | |
| ] | |
| class FileModeFlags(Enumeration): | |
| _ctype_ = c_uint32 | |
| _bitfield_ = Enumeration.kBitValue | |
| _values_ = [ | |
| (u'S_ISUID', 0004000), | |
| (u'S_ISGID', 0002000), | |
| (u'S_ISTXT', 0001000), | |
| (u'S_IRWXU', 0000700), | |
| (u'S_IRUSR', 0000400), | |
| (u'S_IWUSR', 0000200), | |
| (u'S_IXUSR', 0000100), | |
| (u'S_IRWXG', 0000070), | |
| (u'S_IRGRP', 0000040), | |
| (u'S_IWGRP', 0000020), | |
| (u'S_IXGRP', 0000010), | |
| (u'S_IRWXO', 0000007), | |
| (u'S_IROTH', 0000004), | |
| (u'S_IWOTH', 0000002), | |
| (u'S_IXOTH', 0000001), | |
| (u'S_IFMT', 0170000), | |
| (u'S_IFIFO', 0010000), | |
| (u'S_IFCHR', 0020000), | |
| (u'S_IFDIR', 0040000), | |
| (u'S_IFBLK', 0060000), | |
| (u'S_IFREG', 0100000), | |
| (u'S_IFLNK', 0120000), | |
| (u'S_IFSOCK', 0140000), | |
| (u'S_IFWHT', 0160000), | |
| ] | |
| @classmethod | |
| def to_unixstyle(cls, value): | |
| # convert into unix style - not quite the same as ls -l | |
| type = u'?' | |
| t = value & FileModeFlags.S_IFMT | |
| ttos = { | |
| FileModeFlags.S_IFIFO: u'p', | |
| FileModeFlags.S_IFCHR: u'c', | |
| FileModeFlags.S_IFDIR: u'd', | |
| FileModeFlags.S_IFBLK: u'b', | |
| FileModeFlags.S_IFREG: u'-', | |
| FileModeFlags.S_IFLNK: u'l', | |
| FileModeFlags.S_IFSOCK: u's', | |
| FileModeFlags.S_IFWHT: u'w' | |
| } | |
| if t in ttos: | |
| type = ttos[t] | |
| retval = u'%c' % type | |
| retval += u'r' if value & FileModeFlags.S_IRUSR == FileModeFlags.S_IRUSR else u'-' | |
| retval += u'w' if value & FileModeFlags.S_IWUSR == FileModeFlags.S_IWUSR else u'-' | |
| retval += u'x' if value & FileModeFlags.S_IXUSR == FileModeFlags.S_IXUSR else u'-' | |
| retval += u'r' if value & FileModeFlags.S_IRGRP == FileModeFlags.S_IRGRP else u'-' | |
| retval += u'w' if value & FileModeFlags.S_IWGRP == FileModeFlags.S_IWGRP else u'-' | |
| retval += u'x' if value & FileModeFlags.S_IXGRP == FileModeFlags.S_IXGRP else u'-' | |
| retval += u'r' if value & FileModeFlags.S_IROTH == FileModeFlags.S_IROTH else u'-' | |
| retval += u'w' if value & FileModeFlags.S_IWOTH == FileModeFlags.S_IWOTH else u'-' | |
| retval += u'x' if value & FileModeFlags.S_IXOTH == FileModeFlags.S_IXOTH else u'-' | |
| retval +=u' (' | |
| if value & FileModeFlags.S_ISUID: | |
| retval += u'suid' | |
| if value & FileModeFlags.S_ISGID: | |
| retval += u'guid' | |
| if value & FileModeFlags.S_ISTXT: | |
| retval += u'sticky' | |
| retval += u')' | |
| return retval | |
| class HFSPlusBSDInfo(BigEndianStructure): | |
| _pack_ = 1 | |
| _fields_ = [ | |
| (u'ownerID', c_uint32), | |
| (u'groupID', c_uint32), | |
| (u'adminFlags', c_uint8), | |
| (u'ownerFlags', c_uint8), | |
| (u'fileMode', c_uint16), | |
| (u'special', c_uint32) | |
| ] | |
| # simple, big endian union | |
| def __getattr__(cls, name): | |
| retval = None | |
| altnames = [u'iNodeNum', u'linkCount', u'rawDevice'] | |
| if name in altnames: | |
| retval = cls.special | |
| return retval | |
| def __str__(self): | |
| aflags = AdminFlags.from_bitfield(self.adminFlags, True) | |
| oflags = OwnerFlags.from_bitfield(self.ownerFlags, True) | |
| fmode = FileModeFlags.to_unixstyle(self.ownerFlags) | |
| return u'''HFSPlusBSDInfo: | |
| ownerID: %d | |
| groupID: %d | |
| adminFlags: 0x%08x %s | |
| ownerFlags: 0x%08x %s | |
| fileMode: 0%06o %s | |
| special: %d (iNodeNum, linkCount, rawDevice) | |
| ''' % (self.ownerID, | |
| self.groupID, | |
| self.adminFlags, | |
| aflags, | |
| self.ownerFlags, | |
| oflags, | |
| self.fileMode, | |
| fmode, | |
| self.special) | |
| class HFSPlusExtentDescriptor(BigEndianStructure): | |
| _pack_ = 1 | |
| _fields_ = [ | |
| (u'startBlock', c_uint32), | |
| (u'blockCount', c_uint32) | |
| ] | |
| def __str__(self): | |
| return u'startBlock: %d, blockCount: %d\n' % (self.startBlock, self.blockCount) | |
| HFSPlusExtentRecord = HFSPlusExtentDescriptor * 8 | |
| class HFSPlusForkData(BigEndianStructure): | |
| _pack_ = 1 | |
| _fields_ = [ | |
| (u'logicalSize', c_uint64), | |
| (u'clumpSize', c_uint32), | |
| (u'totalBlocks', c_uint32), | |
| (u'extents', HFSPlusExtentRecord) | |
| ] | |
| def __str__(self): | |
| extents = u'' | |
| for i in range(0, 8): | |
| extents += u' ' + self.extents[i].__str__() | |
| return u'''HFSPlusForkData: | |
| logicalSize: %d | |
| clumpSize: %d | |
| totalBlocks: %d | |
| extents: | |
| %s''' % (self.logicalSize, self.clumpSize, self.totalBlocks, extents) | |
| HFSCatalogNodeID = c_uint32 | |
| class VolumeAttributes(Enumeration): | |
| _ctype_ = c_uint32 | |
| _bitfield_ = Enumeration.kBitShift | |
| _values_ = [ | |
| (u'kHFSVolumeHardwareLockBit', 7), | |
| (u'kHFSVolumeUnmountedBit', 8), | |
| u'kHFSVolumeSparedBlocksBit', | |
| u'kHFSVolumeNoCacheRequiredBit', | |
| u'kHFSBootVolumeInconsistentBit', | |
| u'kHFSCatalogNodeIDsReusedBit', | |
| u'kHFSVolumeJournaledBit', | |
| (u'kHFSVolumeSoftwareLockBit', 15), | |
| (u'kHFSContentProtectionBit', 30), | |
| u'kHFSUnusedNodeFixBit' | |
| ] | |
| class HFSPlusDate(BigEndianStructure): | |
| _pack_ = 1 | |
| _fields_ = [ | |
| (u'value', c_uint32) | |
| ] | |
| class FinderInfo(BigEndianStructure): | |
| _pack_ = 1 | |
| _fields_ = [ | |
| (u'value', c_uint32 * 8) | |
| ] | |
| kHFSPlusSigWord = ord('H') << 8 | ord('+') | |
| kHFSXSigWord = ord('H') << 8 | ord('X') | |
| class HFSPlusVolumeHeader(BigEndianStructure): | |
| _pack_ = 1 | |
| _fields_ = [ | |
| (u'signature', c_uint16), | |
| (u'version', c_uint16), | |
| (u'attributes', c_uint32), | |
| (u'lastMountedVersion', c_uint32), | |
| (u'journalInfoBlock', c_uint32), | |
| (u'createDate', HFSPlusDate), | |
| (u'modifyDate', HFSPlusDate), | |
| (u'backupDate', HFSPlusDate), | |
| (u'checkedDate', HFSPlusDate), | |
| (u'fileCount', c_uint32), | |
| (u'folderCount', c_uint32), | |
| (u'blockSize', c_uint32), | |
| (u'totalBlocks', c_uint32), | |
| (u'freeBlocks', c_uint32), | |
| (u'nextAllocation', c_uint32), | |
| (u'rsrcClumpSize', c_uint32), | |
| (u'dataClumpSize', c_uint32), | |
| (u'nextCatalogID', HFSCatalogNodeID), | |
| (u'writeCount', c_uint32), | |
| (u'encodingsBitmap', c_uint64), | |
| (u'finderInfo', FinderInfo), | |
| (u'allocationFile', HFSPlusForkData), | |
| (u'extentsFile', HFSPlusForkData), | |
| (u'catalogFile', HFSPlusForkData), | |
| (u'attributesFile', HFSPlusForkData), | |
| (u'startupFile', HFSPlusForkData), | |
| ] | |
| def __str__(self): | |
| sig = u'?' | |
| if self.signature == kHFSPlusSigWord: | |
| sig = u'kHFSPlusSigWord' | |
| elif self.signature == kHFSXSigWord: | |
| sig = u'kHFSXSigWord' | |
| lastMount = u'' | |
| for i in range(24, -8, -8): | |
| try: | |
| lastMount += chr((self.lastMountedVersion >> i) & 0xff) | |
| except: | |
| lastMount += u'?' | |
| return u'''HFSPlusVolumeHeader: | |
| signature: 0x%04x %s | |
| version: %d | |
| attributes: 0x%08x %s | |
| lastMountedVersion: 0x%08x %s | |
| journalInfoBlock: 0x%08x | |
| createDate: %s | |
| modifyDate: %s | |
| backupDate: %s | |
| checkedDate: %s | |
| fileCount: %d | |
| folderCount: %d | |
| blockSize: 0x%08x | |
| totalBlocks: 0x%08x | |
| freeBlocks: 0x%08x | |
| nextAllocation: 0x%08x | |
| rsrcClumpSize: 0x%08x | |
| dataClumpSize: 0x%08x | |
| nextCatalogID: 0x%08x | |
| writeCount: %d | |
| encodingsBitmap: 0x%016x %s | |
| finderInfo: | |
| %s | |
| allocationFile: | |
| %s | |
| extentsFile: | |
| %s | |
| catalogFile: | |
| %s | |
| attributesFile: | |
| %s | |
| startupFile: | |
| %s | |
| ''' % ( | |
| self.signature, sig, | |
| self.version, | |
| self.attributes, VolumeAttributes.from_bitfield(self.attributes, True), | |
| self.lastMountedVersion, lastMount, | |
| self.journalInfoBlock, | |
| 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.encodingsBitmap, TextEncoding.from_bitfield(self.encodingsBitmap, True), | |
| self.finderInfo, | |
| indent(self.allocationFile, u' '), | |
| indent(self.extentsFile, u' '), | |
| indent(self.catalogFile, u' '), | |
| indent(self.attributesFile, u' '), | |
| indent(self.startupFile, u' ') | |
| ) | |
| class JournalInfoFlags(Enumeration): | |
| _ctype_ = c_uint32 | |
| _bitfield_ = Enumeration.kBitValue | |
| _values_ = [ | |
| (u'kJIJournalInFSMask', 0x1), | |
| (u'kJIJournalOnOtherDeviceMask', 0x2), | |
| (u'kJIJournalNeedInitMask', 0x4) | |
| ] | |
| class JournalInfoBlock(BigEndianStructure): | |
| _pack_ = 1 | |
| _fields_ = [ | |
| (u'flags', c_uint32), | |
| (u'device_signature', c_uint32 * 8), | |
| (u'offset', c_uint64), | |
| (u'size', c_uint64), | |
| (u'reserved', c_uint32 * 32) | |
| ] | |
| def __str__(self): | |
| devsig = u'' | |
| for i in range(0, 8): | |
| devsig += '0x%08x ' % self.device_signature[i] | |
| res = u'' | |
| for i in range(0, 32): | |
| if i != 0 and not i % 4: | |
| res += u'\n ' | |
| res += '0x%08x ' % self.reserved[i] | |
| return u'''JournalInfoBlock: | |
| flags: 0x%08x %s | |
| device_signature: %s | |
| offset: 0x%016x | |
| size: 0x%016x | |
| reserved: %s | |
| ''' % ( | |
| self.flags, JournalInfoFlags.from_bitfield(self.flags, True), | |
| devsig, | |
| self.offset, | |
| self.size, | |
| res | |
| ) | |
| JOURNAL_HEADER_MAGIC = 0x4a4e4c78 | |
| ENDIAN_MAGIC = 0x12345678 | |
| def intwrap32(value): | |
| return (value & 0xffffffff) + (value >> 32) | |
| def journal_checksum(struct): | |
| s = deepcopy(struct) | |
| s.checksum = 0 | |
| cksum = 0 | |
| ba = bytearray(s) | |
| for b in ba: | |
| cksum = (((cksum << 8) & 0xffffffff) ^ intwrap32(cksum + b)) | |
| return c_uint32(~cksum).value | |
| class journal_header(LittleEndianStructure): | |
| # XXX: technically this structure can be in either endian | |
| _pack_ = 1 | |
| _fields_ = [ | |
| (u'magic', c_uint32), | |
| (u'endian', c_uint32), | |
| (u'start', c_uint64), | |
| (u'end', c_uint64), | |
| (u'size', c_uint64), | |
| (u'blhdr_size', c_uint32), | |
| (u'checksum', c_uint32), | |
| (u'jhdr_size', c_uint32) | |
| ] | |
| def __str__(self): | |
| mg = u'JOURNAL_HEADER_MAGIC' if self.magic == JOURNAL_HEADER_MAGIC else u'?' | |
| end = u'ENDIAN_MAGIC' if self.endian == ENDIAN_MAGIC else u'?' | |
| ck = u'' if journal_checksum(self) == self.checksum else u'?' | |
| return u'''journal_header: | |
| magic: 0x%08x %s | |
| endian: 0x%08x %s | |
| start: 0x%016x | |
| end: 0x%016x | |
| size: 0x%016x | |
| blhdr_size: 0x%08x | |
| checksum: 0x%08x %s | |
| jhdr_size: 0x%08x | |
| ''' % ( | |
| self.magic, mg, | |
| self.endian, end, | |
| self.start, | |
| self.end, | |
| self.size, | |
| self.blhdr_size, | |
| self.checksum, ck, | |
| self.jhdr_size | |
| ) | |
| class block_info(LittleEndianStructure): | |
| _pack_ = 1 | |
| _fields_ = [ | |
| (u'bnum', c_uint64), | |
| (u'bsize', c_uint32), | |
| (u'next', c_uint32) | |
| ] | |
| def __str__(self): | |
| return u'''block_info: | |
| bnum: 0x%016x | |
| bsize: 0x%08x | |
| next: 0x%08x''' % ( | |
| self.bnum, | |
| self.bsize, | |
| self.next | |
| ) | |
| class block_list_flags(Enumeration): | |
| _ctype_ = c_uint32 | |
| _bitfield_ = Enumeration.kBitValue | |
| _values_ = [ | |
| (u'BLHDR_CHECK_CHECKSUMS', 1), | |
| (u'BLHDR_FIRST_HEADER', 2), | |
| ] | |
| class block_list_header(LittleEndianStructure): | |
| _pack_ = 1 | |
| _fields_ = [ | |
| (u'max_blocks', c_uint16), | |
| (u'num_blocks', c_uint16), | |
| (u'bytes_used', c_uint32), | |
| (u'checksum', c_uint32), | |
| (u'flags', c_uint32), | |
| (u'block_info', block_info) | |
| ] | |
| def __str__(self): | |
| ck = u'' if journal_checksum(self) == self.checksum else u'?' | |
| return u'''block_list_header: | |
| max_blocks: 0x%04x | |
| num_blocks: 0x%04x | |
| bytes_used: 0x%08x | |
| checksum: 0x%08x %s | |
| flags: 0x%08x %s | |
| %s''' % ( | |
| self.max_blocks, | |
| self.num_blocks, | |
| self.bytes_used, | |
| self.checksum, ck, | |
| self.flags, block_list_flags.from_bitfield(self.flags, True), | |
| indent(self.block_info.__str__(), u' ') | |
| ) | |
| # XXX sort time decodig | |
| # XXX decode finderInfo | |
| # +4 == textencoding? | |
| # +6 == VolumeUUID | |
| # different for files/folder - see ATTR_CMN_FNDRINFO in man getattrlist | |
| if __name__ == u'__main__': | |
| import sys | |
| vhdr = HFSPlusVolumeHeader() | |
| jb = JournalInfoBlock() | |
| jh = journal_header() | |
| f = file(sys.argv[1], 'rb') | |
| f.seek(1024, 0) | |
| f.readinto(vhdr) | |
| print vhdr | |
| f.seek(vhdr.blockSize * vhdr.journalInfoBlock, 0) | |
| f.readinto(jb) | |
| print jb | |
| f.seek(jb.offset, 0) | |
| f.readinto(jh) | |
| print jh | |
| if jh.start != jh.end: | |
| # transactions in journal | |
| joff = jh.start | |
| while joff != jh.end: | |
| f.seek(jb.offset + joff, 0) | |
| blh = block_list_header() | |
| f.readinto(blh) | |
| print blh | |
| for i in range(1, blh.num_blocks): | |
| bi = block_info() | |
| f.readinto(bi) | |
| print indent(bi, u' ') | |
| joff += blh.bytes_used | |
| f.close() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment