123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- #!/usr/bin/env python3
- import struct
- import sys
- import json
- import io
- import itertools as it
- from readmdir import Tag, MetadataPair
- def main(args):
- superblock = None
- gstate = b'\0\0\0\0\0\0\0\0\0\0\0\0'
- dirs = []
- mdirs = []
- corrupted = []
- cycle = False
- with open(args.disk, 'rb') as f:
- tail = (args.block1, args.block2)
- hard = False
- while True:
- for m in it.chain((m for d in dirs for m in d), mdirs):
- if set(m.blocks) == set(tail):
- # cycle detected
- cycle = m.blocks
- if cycle:
- 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
- # 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 directories
- mdirs.append(mdir)
- if mdir.tail is None or not mdir.tail.is_('hardtail'):
- dirs.append(mdirs)
- mdirs = []
- if mdir.tail is None:
- break
- tail = struct.unpack('<II', mdir.tail.data)
- hard = mdir.tail.is_('hardtail')
- # find paths
- dirtable = {}
- for dir in dirs:
- dirtable[frozenset(dir[0].blocks)] = dir
- pending = [("/", dirs[0])]
- while pending:
- path, dir = pending.pop(0)
- for mdir in dir:
- for tag in mdir.tags:
- if tag.is_('dir'):
- try:
- npath = tag.data.decode('utf8')
- dirstruct = mdir[Tag('dirstruct', tag.id, 0)]
- nblocks = struct.unpack('<II', dirstruct.data)
- nmdir = dirtable[frozenset(nblocks)]
- pending.append(((path + '/' + npath), nmdir))
- except KeyError:
- pass
- dir[0].path = path.replace('//', '/')
- # print littlefs + version info
- version = ('?', '?')
- if superblock:
- version = tuple(reversed(
- struct.unpack('<HH', superblock[1].data[0:4].ljust(4, b'\xff'))))
- print("%-47s%s" % ("littlefs v%s.%s" % version,
- "data (truncated, if it fits)"
- if not any([args.no_truncate, args.tags, args.log, args.all]) else ""))
- # print gstate
- print("gstate 0x%s" % ''.join('%02x' % c for c in gstate))
- tag = Tag(struct.unpack('<I', gstate[0:4].ljust(4, b'\xff'))[0])
- blocks = struct.unpack('<II', gstate[4:4+8].ljust(8, b'\xff'))
- if tag.size or not tag.isvalid:
- print(" orphans >=%d" % max(tag.size, 1))
- if tag.type:
- print(" move dir {%#x, %#x} id %d" % (
- blocks[0], blocks[1], tag.id))
- # print mdir info
- for i, dir in enumerate(dirs):
- print("dir %s" % (json.dumps(dir[0].path)
- if hasattr(dir[0], 'path') else '(orphan)'))
- 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('<II', mdir.tail.data)
- if mdir.tail else ''))
- f = io.StringIO()
- if args.log:
- mdir.dump_log(f, truncate=not args.no_truncate)
- elif args.all:
- mdir.dump_all(f, truncate=not args.no_truncate)
- else:
- mdir.dump_tags(f, truncate=not args.no_truncate)
- lines = list(filter(None, f.getvalue().split('\n')))
- for k, line in enumerate(lines):
- print("%s %s" % (
- ' ' if j == len(dir)-1 else
- 'v' if k == len(lines)-1 else
- '|',
- line))
- errcode = 0
- for mdir in corrupted:
- errcode = errcode or 1
- print("*** corrupted mdir {%#x, %#x}! ***" % (
- mdir.blocks[0], mdir.blocks[1]))
- if cycle:
- errcode = errcode or 2
- print("*** cycle detected {%#x, %#x}! ***" % (
- cycle[0], cycle[1]))
- return errcode
- if __name__ == "__main__":
- import argparse
- import sys
- parser = argparse.ArgumentParser(
- description="Dump semantic info about the metadata tree in littlefs")
- parser.add_argument('disk',
- help="File representing the block device.")
- parser.add_argument('block_size', type=lambda x: int(x, 0),
- help="Size of a block in bytes.")
- parser.add_argument('block1', nargs='?', default=0,
- type=lambda x: int(x, 0),
- help="Optional first block address for finding the superblock.")
- parser.add_argument('block2', nargs='?', default=1,
- type=lambda x: int(x, 0),
- help="Optional second block address for finding the superblock.")
- parser.add_argument('-l', '--log', action='store_true',
- help="Show tags in log.")
- parser.add_argument('-a', '--all', action='store_true',
- help="Show all tags in log, included tags in corrupted commits.")
- parser.add_argument('-T', '--no-truncate', action='store_true',
- help="Show the full contents of files/attrs/tags.")
- sys.exit(main(parser.parse_args()))
|