readmdir.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. #!/usr/bin/env python3
  2. import struct
  3. import binascii
  4. import sys
  5. import itertools as it
  6. TAG_TYPES = {
  7. 'splice': (0x700, 0x400),
  8. 'create': (0x7ff, 0x401),
  9. 'delete': (0x7ff, 0x4ff),
  10. 'name': (0x700, 0x000),
  11. 'reg': (0x7ff, 0x001),
  12. 'dir': (0x7ff, 0x002),
  13. 'superblock': (0x7ff, 0x0ff),
  14. 'struct': (0x700, 0x200),
  15. 'dirstruct': (0x7ff, 0x200),
  16. 'ctzstruct': (0x7ff, 0x202),
  17. 'inlinestruct': (0x7ff, 0x201),
  18. 'userattr': (0x700, 0x300),
  19. 'tail': (0x700, 0x600),
  20. 'softtail': (0x7ff, 0x600),
  21. 'hardtail': (0x7ff, 0x601),
  22. 'gstate': (0x700, 0x700),
  23. 'movestate': (0x7ff, 0x7ff),
  24. 'crc': (0x700, 0x500),
  25. 'nprogcrc': (0x7ff, 0x5ff),
  26. }
  27. class Tag:
  28. def __init__(self, *args):
  29. if len(args) == 1:
  30. self.tag = args[0]
  31. elif len(args) == 3:
  32. if isinstance(args[0], str):
  33. type = TAG_TYPES[args[0]][1]
  34. else:
  35. type = args[0]
  36. if isinstance(args[1], str):
  37. id = int(args[1], 0) if args[1] not in 'x.' else 0x3ff
  38. else:
  39. id = args[1]
  40. if isinstance(args[2], str):
  41. size = int(args[2], str) if args[2] not in 'x.' else 0x3ff
  42. else:
  43. size = args[2]
  44. self.tag = (type << 20) | (id << 10) | size
  45. else:
  46. assert False
  47. @property
  48. def isvalid(self):
  49. return not bool(self.tag & 0x80000000)
  50. @property
  51. def isattr(self):
  52. return not bool(self.tag & 0x40000000)
  53. @property
  54. def iscompactable(self):
  55. return bool(self.tag & 0x20000000)
  56. @property
  57. def isunique(self):
  58. return not bool(self.tag & 0x10000000)
  59. @property
  60. def type(self):
  61. return (self.tag & 0x7ff00000) >> 20
  62. @property
  63. def type1(self):
  64. return (self.tag & 0x70000000) >> 20
  65. @property
  66. def type3(self):
  67. return (self.tag & 0x7ff00000) >> 20
  68. @property
  69. def id(self):
  70. return (self.tag & 0x000ffc00) >> 10
  71. @property
  72. def size(self):
  73. return (self.tag & 0x000003ff) >> 0
  74. @property
  75. def dsize(self):
  76. return 4 + (self.size if self.size != 0x3ff else 0)
  77. @property
  78. def chunk(self):
  79. return self.type & 0xff
  80. @property
  81. def schunk(self):
  82. return struct.unpack('b', struct.pack('B', self.chunk))[0]
  83. def is_(self, type):
  84. try:
  85. if ' ' in type:
  86. type1, type3 = type.split()
  87. return (self.is_(type1) and
  88. (self.type & ~TAG_TYPES[type1][0]) == int(type3, 0))
  89. return self.type == int(type, 0)
  90. except (ValueError, KeyError):
  91. return (self.type & TAG_TYPES[type][0]) == TAG_TYPES[type][1]
  92. def mkmask(self):
  93. return Tag(
  94. 0x700 if self.isunique else 0x7ff,
  95. 0x3ff if self.isattr else 0,
  96. 0)
  97. def chid(self, nid):
  98. ntag = Tag(self.type, nid, self.size)
  99. if hasattr(self, 'off'): ntag.off = self.off
  100. if hasattr(self, 'data'): ntag.data = self.data
  101. if hasattr(self, 'crc'): ntag.crc = self.crc
  102. if hasattr(self, 'erased'): ntag.erased = self.erased
  103. return ntag
  104. def typerepr(self):
  105. if (self.is_('crc') and not self.is_('nprogcrc') and
  106. getattr(self, 'crc', 0xffffffff) != 0xffffffff):
  107. crc_status = ' (bad)'
  108. elif self.is_('nprogcrc') and getattr(self, 'erased', False):
  109. crc_status = ' (era)'
  110. else:
  111. crc_status = ''
  112. reverse_types = {v: k for k, v in TAG_TYPES.items()}
  113. for prefix in range(12):
  114. mask = 0x7ff & ~((1 << prefix)-1)
  115. if (mask, self.type & mask) in reverse_types:
  116. type = reverse_types[mask, self.type & mask]
  117. if prefix > 0:
  118. return '%s %#x%s' % (
  119. type, self.type & ((1 << prefix)-1), crc_status)
  120. else:
  121. return '%s%s' % (type, crc_status)
  122. else:
  123. return '%02x%s' % (self.type, crc_status)
  124. def idrepr(self):
  125. return repr(self.id) if self.id != 0x3ff else '.'
  126. def sizerepr(self):
  127. return repr(self.size) if self.size != 0x3ff else 'x'
  128. def __repr__(self):
  129. return 'Tag(%r, %d, %d)' % (self.typerepr(), self.id, self.size)
  130. def __lt__(self, other):
  131. return (self.id, self.type) < (other.id, other.type)
  132. def __bool__(self):
  133. return self.isvalid
  134. def __int__(self):
  135. return self.tag
  136. def __index__(self):
  137. return self.tag
  138. class MetadataPair:
  139. def __init__(self, blocks):
  140. if len(blocks) > 1:
  141. self.pair = [MetadataPair([block]) for block in blocks]
  142. self.pair = sorted(self.pair, reverse=True)
  143. self.data = self.pair[0].data
  144. self.rev = self.pair[0].rev
  145. self.tags = self.pair[0].tags
  146. self.ids = self.pair[0].ids
  147. self.log = self.pair[0].log
  148. self.all_ = self.pair[0].all_
  149. return
  150. self.pair = [self]
  151. self.data = blocks[0]
  152. block = self.data
  153. self.rev, = struct.unpack('<I', block[0:4])
  154. crc = binascii.crc32(block[0:4])
  155. etag = None
  156. estate = None
  157. # parse tags
  158. corrupt = False
  159. tag = Tag(0xffffffff)
  160. off = 4
  161. self.log = []
  162. self.all_ = []
  163. while len(block) - off >= 4:
  164. ntag, = struct.unpack('>I', block[off:off+4])
  165. tag = Tag((int(tag) ^ ntag) & 0x7fffffff)
  166. tag.off = off + 4
  167. tag.data = block[off+4:off+tag.dsize]
  168. if tag.is_('crc') and not tag.is_('nprogcrc'):
  169. crc = binascii.crc32(block[off:off+2*4], crc)
  170. else:
  171. crc = binascii.crc32(block[off:off+tag.dsize], crc)
  172. tag.crc = crc
  173. off += tag.dsize
  174. self.all_.append(tag)
  175. if tag.is_('nprogcrc') and len(tag.data) == 8:
  176. etag = tag
  177. estate = struct.unpack('<II', tag.data)
  178. elif tag.is_('crc'):
  179. # is valid commit?
  180. if crc != 0xffffffff:
  181. corrupt = True
  182. if not corrupt:
  183. self.log = self.all_.copy()
  184. # end of commit?
  185. if estate:
  186. esize, ecrc = estate
  187. dcrc = 0xffffffff ^ binascii.crc32(block[off:off+esize])
  188. if ecrc == dcrc:
  189. etag.erased = True
  190. corrupt = True
  191. elif not (tag.is_('crc 0x0') or tag.is_('crc 0x1')):
  192. corrupt = True
  193. # reset tag parsing
  194. crc = 0
  195. etag = None
  196. estate = None
  197. # find active ids
  198. self.ids = list(it.takewhile(
  199. lambda id: Tag('name', id, 0) in self,
  200. it.count()))
  201. # find most recent tags
  202. self.tags = []
  203. for tag in self.log:
  204. if tag.is_('crc') or tag.is_('splice'):
  205. continue
  206. elif tag.id == 0x3ff:
  207. if tag in self and self[tag] is tag:
  208. self.tags.append(tag)
  209. else:
  210. # id could have change, I know this is messy and slow
  211. # but it works
  212. for id in self.ids:
  213. ntag = tag.chid(id)
  214. if ntag in self and self[ntag] is tag:
  215. self.tags.append(ntag)
  216. self.tags = sorted(self.tags)
  217. def __bool__(self):
  218. return bool(self.log)
  219. def __lt__(self, other):
  220. # corrupt blocks don't count
  221. if not self or not other:
  222. return bool(other)
  223. # use sequence arithmetic to avoid overflow
  224. return not ((other.rev - self.rev) & 0x80000000)
  225. def __contains__(self, args):
  226. try:
  227. self[args]
  228. return True
  229. except KeyError:
  230. return False
  231. def __getitem__(self, args):
  232. if isinstance(args, tuple):
  233. gmask, gtag = args
  234. else:
  235. gmask, gtag = args.mkmask(), args
  236. gdiff = 0
  237. for tag in reversed(self.log):
  238. if (gmask.id != 0 and tag.is_('splice') and
  239. tag.id <= gtag.id - gdiff):
  240. if tag.is_('create') and tag.id == gtag.id - gdiff:
  241. # creation point
  242. break
  243. gdiff += tag.schunk
  244. if ((int(gmask) & int(tag)) ==
  245. (int(gmask) & int(gtag.chid(gtag.id - gdiff)))):
  246. if tag.size == 0x3ff:
  247. # deleted
  248. break
  249. return tag
  250. raise KeyError(gmask, gtag)
  251. def _dump_tags(self, tags, f=sys.stdout, truncate=True):
  252. f.write("%-8s %-8s %-13s %4s %4s" % (
  253. 'off', 'tag', 'type', 'id', 'len'))
  254. if truncate:
  255. f.write(' data (truncated)')
  256. f.write('\n')
  257. for tag in tags:
  258. f.write("%08x: %08x %-14s %3s %4s" % (
  259. tag.off, tag,
  260. tag.typerepr(), tag.idrepr(), tag.sizerepr()))
  261. if truncate:
  262. f.write(" %-23s %-8s\n" % (
  263. ' '.join('%02x' % c for c in tag.data[:8]),
  264. ''.join(c if c >= ' ' and c <= '~' else '.'
  265. for c in map(chr, tag.data[:8]))))
  266. else:
  267. f.write("\n")
  268. for i in range(0, len(tag.data), 16):
  269. f.write(" %08x: %-47s %-16s\n" % (
  270. tag.off+i,
  271. ' '.join('%02x' % c for c in tag.data[i:i+16]),
  272. ''.join(c if c >= ' ' and c <= '~' else '.'
  273. for c in map(chr, tag.data[i:i+16]))))
  274. def dump_tags(self, f=sys.stdout, truncate=True):
  275. self._dump_tags(self.tags, f=f, truncate=truncate)
  276. def dump_log(self, f=sys.stdout, truncate=True):
  277. self._dump_tags(self.log, f=f, truncate=truncate)
  278. def dump_all(self, f=sys.stdout, truncate=True):
  279. self._dump_tags(self.all_, f=f, truncate=truncate)
  280. def main(args):
  281. blocks = []
  282. with open(args.disk, 'rb') as f:
  283. for block in [args.block1, args.block2]:
  284. if block is None:
  285. continue
  286. f.seek(block * args.block_size)
  287. blocks.append(f.read(args.block_size)
  288. .ljust(args.block_size, b'\xff'))
  289. # find most recent pair
  290. mdir = MetadataPair(blocks)
  291. try:
  292. mdir.tail = mdir[Tag('tail', 0, 0)]
  293. if mdir.tail.size != 8 or mdir.tail.data == 8*b'\xff':
  294. mdir.tail = None
  295. except KeyError:
  296. mdir.tail = None
  297. print("mdir {%s} rev %d%s%s%s" % (
  298. ', '.join('%#x' % b
  299. for b in [args.block1, args.block2]
  300. if b is not None),
  301. mdir.rev,
  302. ' (was %s)' % ', '.join('%d' % m.rev for m in mdir.pair[1:])
  303. if len(mdir.pair) > 1 else '',
  304. ' (corrupted!)' if not mdir else '',
  305. ' -> {%#x, %#x}' % struct.unpack('<II', mdir.tail.data)
  306. if mdir.tail else ''))
  307. if args.all:
  308. mdir.dump_all(truncate=not args.no_truncate)
  309. elif args.log:
  310. mdir.dump_log(truncate=not args.no_truncate)
  311. else:
  312. mdir.dump_tags(truncate=not args.no_truncate)
  313. return 0 if mdir else 1
  314. if __name__ == "__main__":
  315. import argparse
  316. import sys
  317. parser = argparse.ArgumentParser(
  318. description="Dump useful info about metadata pairs in littlefs.")
  319. parser.add_argument('disk',
  320. help="File representing the block device.")
  321. parser.add_argument('block_size', type=lambda x: int(x, 0),
  322. help="Size of a block in bytes.")
  323. parser.add_argument('block1', type=lambda x: int(x, 0),
  324. help="First block address for finding the metadata pair.")
  325. parser.add_argument('block2', nargs='?', type=lambda x: int(x, 0),
  326. help="Second block address for finding the metadata pair.")
  327. parser.add_argument('-l', '--log', action='store_true',
  328. help="Show tags in log.")
  329. parser.add_argument('-a', '--all', action='store_true',
  330. help="Show all tags in log, included tags in corrupted commits.")
  331. parser.add_argument('-T', '--no-truncate', action='store_true',
  332. help="Don't truncate large amounts of data.")
  333. sys.exit(main(parser.parse_args()))