readmdir.py 9.9 KB

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