Skip to content

Instantly share code, notes, and snippets.

@mountainstorm
Created September 9, 2014 18:20
Show Gist options
  • Select an option

  • Save mountainstorm/e2488899bc3edfad8d83 to your computer and use it in GitHub Desktop.

Select an option

Save mountainstorm/e2488899bc3edfad8d83 to your computer and use it in GitHub Desktop.
hfsplus structures, allows parsing the hfsplus file structure and changing it.
#!/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))
#!/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