#!/usr/bin/env python3 import struct import sys import json import io import itertools as it import collections as c from readmdir import Tag, MetadataPair def popc(x): return bin(x).count('1') def ctz(x): return len(bin(x)) - len(bin(x).rstrip('0')) def dumpentries(args, mdir, mdirs, f): for k, id_ in enumerate(mdir.ids): name = mdir[Tag('name', id_, 0)] struct_ = mdir[Tag('struct', id_, 0)] desc = "id %d %s %s" % ( id_, name.typerepr(), json.dumps(name.data.decode('utf8'))) if struct_.is_('dirstruct'): pair = struct.unpack('= 0: f2.seek(block * args.block_size) dat = f2.read(args.block_size) data.append(dat[4*(ctz(i)+1) if i != 0 else 0:]) block, = struct.unpack('= ' ' and c <= '~' else '.' for c in map(chr, data[:8]))) if not args.no_truncate and len(desc) < 45 and data is not None else "")) if name.is_('superblock') and struct_.is_('inlinestruct'): f.write( " block_size %d\n" " block_count %d\n" " name_max %d\n" " file_max %d\n" " attr_max %d\n" % struct.unpack( '= ' ' and c <= '~' else '.' for c in map(chr, tag.data[:8]))) if not args.no_truncate and len(desc) < 43 else "")) if args.no_truncate: for i in range(0, len(tag.data), 16): f.write(" %08x: %-47s %-16s\n" % ( i, ' '.join('%02x' % c for c in tag.data[i:i+16]), ''.join(c if c >= ' ' and c <= '~' else '.' for c in map(chr, tag.data[i:i+16])))) if args.no_truncate and data is not None: for i in range(0, len(data), 16): f.write(" %08x: %-47s %-16s\n" % ( i, ' '.join('%02x' % c for c in data[i:i+16]), ''.join(c if c >= ' ' and c <= '~' else '.' for c in map(chr, data[i:i+16])))) def main(args): superblock = None gstate = b'\0\0\0\0\0\0\0\0\0\0\0\0' mdirs = c.OrderedDict() corrupted = [] cycle = False with open(args.disk, 'rb') as f: tail = (args.block1, args.block2) while tail: if frozenset(tail) in mdirs: # cycle detected cycle = tail break # load mdir data = [] blocks = {} for block in tail: f.seek(block * args.block_size) data.append(f.read(args.block_size) .ljust(args.block_size, b'\xff')) blocks[id(data[-1])] = block mdir = MetadataPair(data) mdir.blocks = tuple(blocks[id(p.data)] for p in mdir.pair) # fetch some key metadata as a we scan try: mdir.tail = mdir[Tag('tail', 0, 0)] if mdir.tail.size != 8 or mdir.tail.data == 8*b'\xff': mdir.tail = None except KeyError: mdir.tail = None try: mdir.branch = mdir[Tag('branch', 0, 0)] if mdir.branch.size != 8 or mdir.branch.data == 8*b'\xff': mdir.branch = None except KeyError: mdir.branch = None # have superblock? try: nsuperblock = mdir[ Tag(0x7ff, 0x3ff, 0), Tag('superblock', 0, 0)] superblock = nsuperblock, mdir[Tag('inlinestruct', 0, 0)] except KeyError: pass # have gstate? try: ngstate = mdir[Tag('movestate', 0, 0)] gstate = bytes((a or 0) ^ (b or 0) for a,b in it.zip_longest(gstate, ngstate.data)) except KeyError: pass # corrupted? if not mdir: corrupted.append(mdir) # add to metadata-pairs mdirs[frozenset(mdir.blocks)] = mdir tail = (struct.unpack(' 0 and (pair == frozenset( struct.unpack('=%d" % max(tag.size, 1)) if tag.type: if frozenset(blocks) not in mdirs: badgstate = gstate print(" move dir {%#x, %#x}%s id %d" % ( blocks[0], blocks[1], '?' if frozenset(blocks) not in mdirs else '', tag.id)) # print dir info for path, dir in it.chain( sorted(dirs.items()), zip(it.repeat(None), orphans)): print("dir %s" % json.dumps(path) if path else "orphaned") for j, mdir in enumerate(dir): print("mdir {%#x, %#x} rev %d (was %d)%s%s" % ( mdir.blocks[0], mdir.blocks[1], mdir.rev, mdir.pair[1].rev, ' (corrupted!)' if not mdir else '', ' -> {%#x, %#x}' % struct.unpack('