1# PROJECT:     Python tools for traversing BTRFS structures
2# LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
3# PURPOSE:     Classes and structures for BTRFS on-disk layout
4# COPYRIGHT:   Copyright 2018 Victor Perevertkin (victor@perevertkin.ru)
5
6# some code was taken from https://github.com/knorrie/python-btrfs
7
8from btrfs_constants import *
9import struct
10from collections import namedtuple, OrderedDict
11import collections.abc
12import copy
13import datetime
14import os
15import uuid
16import crc32c
17
18ULLONG_MAX = (1 << 64) - 1
19ULONG_MAX = (1 << 32) - 1
20
21
22def ULL(n):
23  return n & ULLONG_MAX
24
25
26ROOT_TREE_OBJECTID = 1
27EXTENT_TREE_OBJECTID = 2
28CHUNK_TREE_OBJECTID = 3
29DEV_TREE_OBJECTID = 4
30FS_TREE_OBJECTID = 5
31ROOT_TREE_DIR_OBJECTID = 6
32CSUM_TREE_OBJECTID = 7
33QUOTA_TREE_OBJECTID = 8
34UUID_TREE_OBJECTID = 9
35FREE_SPACE_TREE_OBJECTID = 10
36
37DEV_STATS_OBJECTID = 0
38BALANCE_OBJECTID = ULL(-4)
39ORPHAN_OBJECTID = ULL(-5)
40TREE_LOG_OBJECTID = ULL(-6)
41TREE_LOG_FIXUP_OBJECTID = ULL(-7)
42TREE_RELOC_OBJECTID = ULL(-8)
43DATA_RELOC_TREE_OBJECTID = ULL(-9)
44EXTENT_CSUM_OBJECTID = ULL(-10)
45FREE_SPACE_OBJECTID = ULL(-11)
46FREE_INO_OBJECTID = ULL(-12)
47MULTIPLE_OBJECTIDS = ULL(-255)
48
49FIRST_FREE_OBJECTID = 256
50LAST_FREE_OBJECTID = ULL(-256)
51FIRST_CHUNK_TREE_OBJECTID = 256
52
53DEV_ITEMS_OBJECTID = 1
54
55BTRFS_SYSTEM_CHUNK_ARRAY_SIZE = 2048
56
57
58INODE_ITEM_KEY = 1
59INODE_REF_KEY = 12
60INODE_EXTREF_KEY = 13
61XATTR_ITEM_KEY = 24
62ORPHAN_ITEM_KEY = 48
63DIR_LOG_ITEM_KEY = 60
64DIR_LOG_INDEX_KEY = 72
65DIR_ITEM_KEY = 84
66DIR_INDEX_KEY = 96
67EXTENT_DATA_KEY = 108
68EXTENT_CSUM_KEY = 128
69ROOT_ITEM_KEY = 132
70ROOT_BACKREF_KEY = 144
71ROOT_REF_KEY = 156
72EXTENT_ITEM_KEY = 168
73METADATA_ITEM_KEY = 169
74TREE_BLOCK_REF_KEY = 176
75EXTENT_DATA_REF_KEY = 178
76SHARED_BLOCK_REF_KEY = 182
77SHARED_DATA_REF_KEY = 184
78BLOCK_GROUP_ITEM_KEY = 192
79FREE_SPACE_INFO_KEY = 198
80FREE_SPACE_EXTENT_KEY = 199
81FREE_SPACE_BITMAP_KEY = 200
82DEV_EXTENT_KEY = 204
83DEV_ITEM_KEY = 216
84CHUNK_ITEM_KEY = 228
85QGROUP_STATUS_KEY = 240
86QGROUP_INFO_KEY = 242
87QGROUP_LIMIT_KEY = 244
88QGROUP_RELATION_KEY = 246
89BALANCE_ITEM_KEY = 248
90DEV_STATS_KEY = 249
91DEV_REPLACE_KEY = 250
92UUID_KEY_SUBVOL = 251
93UUID_KEY_RECEIVED_SUBVOL = 252
94STRING_ITEM_KEY = 253
95
96BLOCK_GROUP_SINGLE = 0
97BLOCK_GROUP_DATA = 1 << 0
98BLOCK_GROUP_SYSTEM = 1 << 1
99BLOCK_GROUP_METADATA = 1 << 2
100BLOCK_GROUP_RAID0 = 1 << 3
101BLOCK_GROUP_RAID1 = 1 << 4
102BLOCK_GROUP_DUP = 1 << 5
103BLOCK_GROUP_RAID10 = 1 << 6
104BLOCK_GROUP_RAID5 = 1 << 7
105BLOCK_GROUP_RAID6 = 1 << 8
106
107BLOCK_GROUP_TYPE_MASK = (
108  BLOCK_GROUP_DATA |
109  BLOCK_GROUP_SYSTEM |
110  BLOCK_GROUP_METADATA
111)
112
113BLOCK_GROUP_PROFILE_MASK = (
114  BLOCK_GROUP_RAID0 |
115  BLOCK_GROUP_RAID1 |
116  BLOCK_GROUP_RAID5 |
117  BLOCK_GROUP_RAID6 |
118  BLOCK_GROUP_DUP |
119  BLOCK_GROUP_RAID10
120)
121
122AVAIL_ALLOC_BIT_SINGLE = 1 << 48 # used in balance_args
123SPACE_INFO_GLOBAL_RSV = 1 << 49
124
125
126_block_group_flags_str_map = {
127  BLOCK_GROUP_DATA: 'DATA',
128  BLOCK_GROUP_METADATA: 'METADATA',
129  BLOCK_GROUP_SYSTEM: 'SYSTEM',
130  BLOCK_GROUP_RAID0: 'RAID0',
131  BLOCK_GROUP_RAID1: 'RAID1',
132  BLOCK_GROUP_DUP: 'DUP',
133  BLOCK_GROUP_RAID10: 'RAID10',
134  BLOCK_GROUP_RAID5: 'RAID5',
135  BLOCK_GROUP_RAID6: 'RAID6',
136}
137
138_balance_args_profiles_str_map = {
139  BLOCK_GROUP_RAID0: 'RAID0',
140  BLOCK_GROUP_RAID1: 'RAID1',
141  BLOCK_GROUP_DUP: 'DUP',
142  BLOCK_GROUP_RAID10: 'RAID10',
143  BLOCK_GROUP_RAID5: 'RAID5',
144  BLOCK_GROUP_RAID6: 'RAID6',
145  AVAIL_ALLOC_BIT_SINGLE: 'SINGLE',
146}
147
148QGROUP_LEVEL_SHIFT = 48
149
150EXTENT_FLAG_DATA = 1 << 0
151EXTENT_FLAG_TREE_BLOCK = 1 << 1
152BLOCK_FLAG_FULL_BACKREF = 1 << 8
153
154_extent_flags_str_map = {
155  EXTENT_FLAG_DATA: 'DATA',
156  EXTENT_FLAG_TREE_BLOCK: 'TREE_BLOCK',
157  BLOCK_FLAG_FULL_BACKREF: 'FULL_BACKREF',
158}
159
160INODE_NODATASUM = 1 << 0
161INODE_NODATACOW = 1 << 1
162INODE_READONLY = 1 << 2
163INODE_NOCOMPRESS = 1 << 3
164INODE_PREALLOC = 1 << 4
165INODE_SYNC = 1 << 5
166INODE_IMMUTABLE = 1 << 6
167INODE_APPEND = 1 << 7
168INODE_NODUMP = 1 << 8
169INODE_NOATIME = 1 << 9
170INODE_DIRSYNC = 1 << 10
171INODE_COMPRESS = 1 << 11
172
173_inode_flags_str_map = {
174  INODE_NODATASUM: 'NODATASUM',
175  INODE_READONLY: 'READONLY',
176  INODE_NOCOMPRESS: 'NOCOMPRESS',
177  INODE_PREALLOC: 'PREALLOC',
178  INODE_SYNC: 'SYNC',
179  INODE_IMMUTABLE: 'IMMUTABLE',
180  INODE_APPEND: 'APPEND',
181  INODE_NODUMP: 'NODUMP',
182  INODE_NOATIME: 'NOATIME',
183  INODE_DIRSYNC: 'DIRSYNC',
184  INODE_COMPRESS: 'COMPRESS',
185}
186
187ROOT_SUBVOL_RDONLY = 1 << 0
188
189_root_flags_str_map = {
190  ROOT_SUBVOL_RDONLY: 'RDONLY',
191}
192
193FT_UNKNOWN = 0
194FT_REG_FILE = 1
195FT_DIR = 2
196FT_CHRDEV = 3
197FT_BLKDEV = 4
198FT_FIFO = 5
199FT_SOCK = 6
200FT_SYMLINK = 7
201FT_XATTR = 8
202FT_MAX = 9
203
204_dir_item_type_str_map = {
205  FT_UNKNOWN: 'UNKNOWN',
206  FT_REG_FILE: 'FILE',
207  FT_DIR: 'DIR',
208  FT_CHRDEV: 'CHRDEV',
209  FT_BLKDEV: 'BLKDEV',
210  FT_FIFO: 'FIFO',
211  FT_SOCK: 'SOCK',
212  FT_SYMLINK: 'SYMLINK',
213  FT_XATTR: 'XATTR',
214}
215
216COMPRESS_NONE = 0
217COMPRESS_ZLIB = 1
218COMPRESS_LZO = 2
219COMPRESS_ZSTD = 3
220
221_compress_type_str_map = {
222  COMPRESS_NONE: 'none',
223  COMPRESS_ZLIB: 'zlib',
224  COMPRESS_LZO: 'lzo',
225  COMPRESS_ZSTD: 'zstd',
226}
227
228FILE_EXTENT_INLINE = 0
229FILE_EXTENT_REG = 1
230FILE_EXTENT_PREALLOC = 2
231
232_file_extent_type_str_map = {
233  FILE_EXTENT_INLINE: 'inline',
234  FILE_EXTENT_REG: 'regular',
235  FILE_EXTENT_PREALLOC: 'prealloc',
236}
237
238
239def qgroup_level(objectid):
240  return objectid >> QGROUP_LEVEL_SHIFT
241
242
243def qgroup_subvid(objectid):
244  return objectid & ((1 << QGROUP_LEVEL_SHIFT) - 1)
245
246
247_key_objectid_str_map = {
248  ROOT_TREE_OBJECTID: 'ROOT_TREE',
249  EXTENT_TREE_OBJECTID: 'EXTENT_TREE',
250  CHUNK_TREE_OBJECTID: 'CHUNK_TREE',
251  DEV_TREE_OBJECTID: 'DEV_TREE',
252  FS_TREE_OBJECTID: 'FS_TREE',
253  ROOT_TREE_DIR_OBJECTID: 'ROOT_TREE_DIR',
254  CSUM_TREE_OBJECTID: 'CSUM_TREE',
255  QUOTA_TREE_OBJECTID: 'QUOTA_TREE',
256  UUID_TREE_OBJECTID: 'UUID_TREE',
257  FREE_SPACE_TREE_OBJECTID: 'FREE_SPACE_TREE',
258  BALANCE_OBJECTID: 'BALANCE',
259  ORPHAN_OBJECTID: 'ORPHAN',
260  TREE_LOG_OBJECTID: 'TREE_LOG',
261  TREE_LOG_FIXUP_OBJECTID: 'TREE_LOG_FIXUP',
262  TREE_RELOC_OBJECTID: 'TREE_RELOC',
263  DATA_RELOC_TREE_OBJECTID: 'DATA_RELOC_TREE',
264  EXTENT_CSUM_OBJECTID: 'EXTENT_CSUM',
265  FREE_SPACE_OBJECTID: 'FREE_SPACE',
266  FREE_INO_OBJECTID: 'FREE_INO',
267  MULTIPLE_OBJECTIDS: 'MULTIPLE',
268}
269
270
271def key_objectid_str(objectid, _type):
272  if _type == DEV_EXTENT_KEY:
273    return str(objectid)
274  if _type == QGROUP_RELATION_KEY:
275    return "{}/{}".format(qgroup_level(objectid), qgroup_subvid(objectid))
276  if _type == UUID_KEY_SUBVOL or _type == UUID_KEY_RECEIVED_SUBVOL:
277    return "0x{:0>16x}".format(objectid)
278
279  if objectid == ROOT_TREE_OBJECTID and _type == DEV_ITEM_KEY:
280    return 'DEV_ITEMS'
281  if objectid == DEV_STATS_OBJECTID and _type == DEV_STATS_KEY:
282    return 'DEV_STATS'
283  if objectid == FIRST_CHUNK_TREE_OBJECTID and _type == CHUNK_ITEM_KEY:
284    return 'FIRST_CHUNK_TREE'
285  if objectid == ULLONG_MAX:
286    return '-1'
287
288  return _key_objectid_str_map.get(objectid, str(objectid))
289
290
291_key_type_str_map = {
292  INODE_ITEM_KEY: 'INODE_ITEM',
293  INODE_REF_KEY: 'INODE_REF',
294  INODE_EXTREF_KEY: 'INODE_EXTREF',
295  XATTR_ITEM_KEY: 'XATTR_ITEM',
296  ORPHAN_ITEM_KEY: 'ORPHAN_ITEM',
297  DIR_LOG_ITEM_KEY: 'DIR_LOG_ITEM',
298  DIR_LOG_INDEX_KEY: 'DIR_LOG_INDEX',
299  DIR_ITEM_KEY: 'DIR_ITEM',
300  DIR_INDEX_KEY: 'DIR_INDEX',
301  EXTENT_DATA_KEY: 'EXTENT_DATA',
302  EXTENT_CSUM_KEY: 'EXTENT_CSUM',
303  ROOT_ITEM_KEY: 'ROOT_ITEM',
304  ROOT_BACKREF_KEY: 'ROOT_BACKREF',
305  ROOT_REF_KEY: 'ROOT_REF',
306  EXTENT_ITEM_KEY: 'EXTENT_ITEM',
307  METADATA_ITEM_KEY: 'METADATA_ITEM',
308  TREE_BLOCK_REF_KEY: 'TREE_BLOCK_REF',
309  EXTENT_DATA_REF_KEY: 'EXTENT_DATA_REF',
310  SHARED_BLOCK_REF_KEY: 'SHARED_BLOCK_REF',
311  SHARED_DATA_REF_KEY: 'SHARED_DATA_REF',
312  BLOCK_GROUP_ITEM_KEY: 'BLOCK_GROUP_ITEM',
313  FREE_SPACE_INFO_KEY: 'FREE_SPACE_INFO',
314  FREE_SPACE_EXTENT_KEY: 'FREE_SPACE_EXTENT',
315  FREE_SPACE_BITMAP_KEY: 'FREE_SPACE_BITMAP',
316  DEV_EXTENT_KEY: 'DEV_EXTENT',
317  DEV_ITEM_KEY: 'DEV_ITEM',
318  CHUNK_ITEM_KEY: 'CHUNK_ITEM',
319  QGROUP_STATUS_KEY: 'QGROUP_STATUS',
320  QGROUP_INFO_KEY: 'QGROUP_INFO',
321  QGROUP_LIMIT_KEY: 'QGROUP_LIMIT',
322  QGROUP_RELATION_KEY: 'QGROUP_RELATION',
323  BALANCE_ITEM_KEY: 'BALANCE_ITEM',
324  DEV_STATS_KEY: 'DEV_STATS',
325  DEV_REPLACE_KEY: 'DEV_REPLACE',
326  UUID_KEY_SUBVOL: 'UUID_SUBVOL',
327  UUID_KEY_RECEIVED_SUBVOL: 'RECEIVED_SUBVOL',
328  STRING_ITEM_KEY: 'STRING_ITEM',
329}
330
331# === Helper functions
332
333def key_type_str(_type):
334  return _key_type_str_map.get(_type, str(_type))
335
336
337def key_offset_str(offset, _type):
338  if _type == QGROUP_RELATION_KEY or _type == QGROUP_INFO_KEY or _type == QGROUP_LIMIT_KEY:
339    return "{}/{}".format(qgroup_level(offset), qgroup_subvid(offset))
340  if _type == UUID_KEY_SUBVOL or _type == UUID_KEY_RECEIVED_SUBVOL:
341    return "0x{:0>16x}".format(offset)
342  if _type == ROOT_ITEM_KEY:
343    return _key_objectid_str_map.get(offset, str(offset))
344  if offset == ULLONG_MAX:
345    return '-1'
346
347  return str(offset)
348
349
350def flags_str(flags, flags_str_map):
351  ret = []
352  for flag in sorted(flags_str_map.keys()):
353    if flags & flag:
354      ret.append(flags_str_map[flag])
355  if len(ret) == 0:
356    ret.append("none")
357    return '|'.join(ret)
358
359
360def embedded_text_for_str(text):
361  try:
362    return "utf-8 {}".format(text.decode('utf-8'))
363  except UnicodeDecodeError:
364   return "raw {}".format(repr(text))
365
366
367# === Basic structures
368
369
370class TimeSpec(object):
371  sstruct = struct.Struct('<QL')
372
373  @staticmethod
374  def from_values(sec, nsec):
375    t = TimeSpec.__new__(TimeSpec)
376    t.sec = sec
377    t.nsec = nsec
378    return t
379
380  def __init__(self, data):
381    self.sec, self.nsec = TimeSpec.sstruct.unpack_from(data)
382
383  @property
384  def iso8601(self):
385    return datetime.datetime.utcfromtimestamp(
386      float("{self.sec}.{self.nsec}".format(self=self))
387    ).isoformat()
388
389  def __str__(self):
390   return "{self.sec}.{self.nsec} ({self.iso8601})".format(self=self)
391
392
393class Key(object):
394 def __init__(self, objectid, _type, offset):
395  self._objectid = objectid
396  self._type = _type
397  self._offset = offset
398  self._pack()
399
400 @property
401 def objectid(self):
402  return self._objectid
403
404 @objectid.setter
405 def objectid(self, _objectid):
406  self._objectid = _objectid
407  self._pack()
408
409 @property
410 def type(self):
411  return self._type
412
413 @type.setter
414 def type(self, _type):
415  self._type = _type
416  self._pack()
417
418 @property
419 def offset(self):
420  return self._offset
421
422 @offset.setter
423 def offset(self, _offset):
424  self._offset = _offset
425  self._pack()
426
427 @property
428 def key(self):
429  return self._key
430
431 @key.setter
432 def key(self, _key):
433  self._key = _key
434  self._unpack()
435
436 def _pack(self):
437  self._key = (self.objectid << 72) + (self._type << 64) + self.offset
438
439 def _unpack(self):
440  self._objectid = self._key >> 72
441  self._type = (self._key & ((1 << 72) - 1)) >> 64
442  self._offset = (self._key & ((1 << 64) - 1))
443
444 def __lt__(self, other):
445  if isinstance(other, Key):
446   return self._key < other._key
447  return self._key < other
448
449 def __le__(self, other):
450  if isinstance(other, Key):
451   return self._key <= other._key
452  return self._key <= other
453
454 def __eq__(self, other):
455  if isinstance(other, Key):
456   return self._key == other._key
457  return self._key == other
458
459 def __ge__(self, other):
460  if isinstance(other, Key):
461   return self._key >= other._key
462  return self._key >= other
463
464 def __gt__(self, other):
465  if isinstance(other, Key):
466   return self._key > other._key
467  return self._key > other
468
469 def __str__(self):
470  return "({} {} {})".format(
471   key_objectid_str(self._objectid, self._type),
472   key_type_str(self._type),
473   key_offset_str(self._offset, self._type),
474  )
475
476 def __add__(self, amount):
477  new_key = copy.copy(self)
478  new_key.key += amount
479  return new_key
480
481 def __sub__(self, amount):
482  new_key = copy.copy(self)
483  new_key.key -= amount
484  return new_key
485
486class DiskKey(Key):
487  sstruct = struct.Struct('<QBQ')
488
489  def __init__(self, data):
490   super(DiskKey, self).__init__(*DiskKey.sstruct.unpack_from(data))
491
492class InnerKey(Key):
493  sstruct = struct.Struct('<QBQQQ')
494
495  def __init__(self, data):
496   unpacked_data = InnerKey.sstruct.unpack_from(data)
497   super().__init__(*unpacked_data[:3])
498   self.block_num = unpacked_data[3]
499   self.generation = unpacked_data[4]
500
501  def __str__(self):
502   return "(inner_key {} {} {} block_num {}, generation {})".format(
503    key_objectid_str(self._objectid, self._type),
504    key_type_str(self._type),
505    key_offset_str(self._offset, self._type),
506    self.block_num,
507    self.generation,
508   )
509
510class LeafKey(Key):
511  sstruct = struct.Struct('<QBQLL')
512
513  def __init__(self, data):
514   unpacked_data = LeafKey.sstruct.unpack_from(data)
515   super().__init__(*unpacked_data[:3])
516   self.data_offset = unpacked_data[3]
517   self.data_size = unpacked_data[4]
518
519  def __str__(self):
520   return "(leaf_key {} {} {} data_offset {:#x} data_size {})".format(
521    key_objectid_str(self._objectid, self._type),
522    key_type_str(self._type),
523    key_offset_str(self._offset, self._type),
524    self.data_offset,
525    self.data_size,
526   )
527
528class ItemData(object):
529  def __init__(self, key):
530   self.key = key
531
532  def setattr_from_key(self, objectid_attr=None, type_attr=None, offset_attr=None):
533    if objectid_attr is not None:
534      setattr(self, objectid_attr, self.key.objectid)
535    if type_attr is not None:
536      setattr(self, type_attr, self.key.type)
537    if offset_attr is not None:
538      setattr(self, offset_attr, self.key.offset)
539    self._key_attrs = objectid_attr, type_attr, offset_attr
540
541  @property
542  def key_attrs(self):
543    try:
544      return self._key_attrs
545    except AttributeError:
546      return None, None, None
547
548  def __lt__(self, other):
549   return self.key < other.key
550
551
552superblock = struct.Struct('<32x16s2Q8s9Q5L4QH2B611x2048s')
553# NOTE: the structure is not complete
554# FS UUID
555# Physical block address
556# Flags
557# Signature (_BHRfS_M)
558# generation
559# Log. address of root of tree roots
560# Log. address of chunk tree root
561# Log. address of log tree root
562# log_root_transid
563# total_bytes
564# bytes_used
565# root_dir_objectid (usually 6)
566# num_devices
567# sectorsize
568# nodesize
569# __unused_leafsize
570# stripesize
571# sys_chunk_array_size
572# chunk_root_generation
573# compat_flags
574# compat_ro_flags
575# incompat_flags
576# csum_type
577# root_level 23
578# chunk_root_level 24
579# ---
580# sys_chunk_array
581
582
583_node_header_struct = struct.Struct('<32x16sQQ16sQQLB')
584NodeHeader = namedtuple('NodeHeader', 'FS_UUID node_addr flags chunk_tree_uuid generation tree_id items_num level')
585
586# === Items
587
588class InodeItem(ItemData):
589  _inode_item = [
590    struct.Struct('<5Q4L3Q32x'),
591    TimeSpec.sstruct,
592    TimeSpec.sstruct,
593    TimeSpec.sstruct,
594    TimeSpec.sstruct,
595  ]
596  sstruct = struct.Struct('<' + ''.join([s.format[1:].decode() for s in _inode_item]))
597
598  def __init__(self, key, data):
599    super().__init__(key)
600    self.generation, self.transid, self.size, self.nbytes, self.block_group, \
601      self.nlink, self.uid, self.gid, self.mode, self.rdev, self.flags, self.sequence = \
602      InodeItem._inode_item[0].unpack_from(data)
603    pos = InodeItem._inode_item[0].size
604    next_pos = pos + TimeSpec.sstruct.size
605    self.atime = TimeSpec(data[pos:next_pos])
606    pos, next_pos = next_pos, next_pos + TimeSpec.sstruct.size
607    self.ctime = TimeSpec(data[pos:next_pos])
608    pos, next_pos = next_pos, next_pos + TimeSpec.sstruct.size
609    self.mtime = TimeSpec(data[pos:next_pos])
610    pos, next_pos = next_pos, next_pos + TimeSpec.sstruct.size
611    self.otime = TimeSpec(data[pos:next_pos])
612
613  @property
614  def flags_str(self):
615    return flags_str(self.flags, _inode_flags_str_map)
616
617  def __str__(self):
618    return "inode generation {self.generation} transid {self.transid} size {self.size} " \
619      "nbytes {self.nbytes} block_group {self.block_group} mode {self.mode:05o} " \
620      "nlink {self.nlink} uid {self.uid} gid {self.gid} rdev {self.rdev} " \
621      "flags {self.flags:#x}({self.flags_str})".format(self=self)
622
623
624class RootItem(ItemData):
625  _root_item = [
626    InodeItem.sstruct,
627    struct.Struct('<7QL'),
628    DiskKey.sstruct,
629    struct.Struct('<BBQ16s16s16s4Q'),
630    TimeSpec.sstruct,
631    TimeSpec.sstruct,
632    TimeSpec.sstruct,
633    TimeSpec.sstruct,
634  ]
635  sstruct = struct.Struct('<' + ''.join([s.format[1:].decode() for s in _root_item]))
636
637  def __init__(self, key, data):
638    super().__init__(key)
639    self.inode = InodeItem(None, data[:InodeItem.sstruct.size])
640    pos = InodeItem.sstruct.size
641    self.generation, self.dirid, self.bytenr, self.byte_limit, self.bytes_used, \
642      self.last_snapshot, self.flags, self.refs = \
643      RootItem._root_item[1].unpack_from(data, pos)
644    pos += RootItem._root_item[1].size
645    self.drop_progress = DiskKey(data[pos:pos+DiskKey.sstruct.size])
646    pos += DiskKey.sstruct.size
647    self.drop_level, self.level, self.generation_v2, uuid_bytes, parent_uuid_bytes, \
648      received_uuid_bytes, self.ctransid, self.otransid, self.stransid, self.rtransid = \
649      RootItem._root_item[3].unpack_from(data, pos)
650    self.uuid = uuid.UUID(bytes=uuid_bytes)
651    self.parent_uuid = uuid.UUID(bytes=parent_uuid_bytes)
652    self.received_uuid = uuid.UUID(bytes=received_uuid_bytes)
653    pos += RootItem._root_item[3].size
654    next_pos = pos + TimeSpec.sstruct.size
655    self.ctime = TimeSpec(data[pos:next_pos])
656    pos, next_pos = next_pos, next_pos + TimeSpec.sstruct.size
657    self.otime = TimeSpec(data[pos:next_pos])
658    pos, next_pos = next_pos, next_pos + TimeSpec.sstruct.size
659    self.stime = TimeSpec(data[pos:next_pos])
660    pos, next_pos = next_pos, next_pos + TimeSpec.sstruct.size
661    self.rtime = TimeSpec(data[pos:next_pos])
662
663  @property
664  def flags_str(self):
665    return flags_str(self.flags, _root_flags_str_map)
666
667  def __str__(self):
668    return "root {self.key.objectid} uuid {self.uuid} " \
669      "generation {self.generation} last_snapshot {self.last_snapshot} " \
670      "bytenr {self.bytenr:#x} level {self.level} " \
671      "flags {self.flags:#x}({self.flags_str})".format(self=self)
672
673
674class Chunk(ItemData):
675  sstruct = struct.Struct('<4Q3L2H')
676
677  def __init__(self, key, data):
678    super().__init__(key)
679    self.setattr_from_key(offset_attr='vaddr')
680    self.length, self.owner, self.stripe_len, self.type, self.io_align, \
681      self.io_width, self.sector_size, self.num_stripes, self.sub_stripes = \
682      Chunk.sstruct.unpack_from(data)
683    self.stripes = []
684    pos = Chunk.sstruct.size
685    for i in range(self.num_stripes):
686      next_pos = pos + Stripe.sstruct.size
687      self.stripes.append(Stripe(data[pos:next_pos]))
688      pos = next_pos
689
690  @property
691  def size(self):
692   return Chunk.sstruct.size + self.num_stripes * Stripe.sstruct.size
693
694  @property
695  def type_str(self):
696    return flags_str(self.type, _block_group_flags_str_map)
697
698  def __str__(self):
699    return "chunk vaddr {self.vaddr:#x} type {self.type_str} length {self.length} " \
700      "num_stripes {self.num_stripes}".format(self=self)
701
702
703class Stripe(object):
704  sstruct = struct.Struct('<2Q16s')
705
706  def __init__(self, data):
707    self.devid, self.offset, uuid_bytes = Stripe.sstruct.unpack(data)
708    self.uuid = uuid.UUID(bytes=uuid_bytes)
709
710  def __str__(self):
711    return "stripe devid {self.devid} offset {self.offset:#x}".format(self=self)
712
713
714class InodeRefList(ItemData, collections.abc.MutableSequence):
715  def __init__(self, header, data):
716    super().__init__(header)
717    self._list = []
718    pos = 0
719    while pos < header.len:
720      inode_ref = InodeRef(data, pos)
721      self._list.append(inode_ref)
722      pos += len(inode_ref)
723
724  def __getitem__(self, index):
725    return self._list[index]
726
727  def __setitem__(self, index, value):
728    self._list[index] = value
729
730  def __delitem__(self, index):
731    del self._list[index]
732
733  def __len__(self):
734    return len(self._list)
735
736  def insert(self, index, value):
737    self._list.insert(index, value)
738
739  def __str__(self):
740    return "inode ref list size {}".format(len(self))
741
742
743class InodeRef(ItemData):
744  sstruct = struct.Struct('<QH')
745
746  def __init__(self, key, data):
747   super().__init__(key)
748   self.index, self.name_len = InodeRef.sstruct.unpack_from(data)
749   self.name, = struct.Struct('<{}s'.format(self.name_len)).unpack_from(data, InodeRef.sstruct.size)
750   self._len = InodeRef.sstruct.size + self.name_len
751
752  @property
753  def name_str(self):
754    return embedded_text_for_str(self.name)
755
756  def __len__(self):
757    return self._len
758
759  def __str__(self):
760   return "inode ref index {self.index} name {self.name_str}".format(self=self)
761
762
763class DirItemList(ItemData, collections.abc.MutableSequence):
764  def __init__(self, key, data):
765    super().__init__(key)
766    self._list = []
767    pos = 0
768    while pos < key.data_size:
769      cls = {DIR_ITEM_KEY: DirItem, XATTR_ITEM_KEY: XAttrItem}
770      dir_item = cls[self.key.type](data, pos)
771      self._list.append(dir_item)
772      pos += len(dir_item)
773
774  def __getitem__(self, index):
775    return self._list[index]
776
777  def __setitem__(self, index, value):
778    self._list[index] = value
779
780  def __delitem__(self, index):
781    del self._list[index]
782
783  def __len__(self):
784    return len(self._list)
785
786  def insert(self, index, value):
787    self._list.insert(index, value)
788
789  def __str__(self):
790   return "dir item list hash {self.key.offset} size {}".format(len(self), self=self)
791
792
793class XAttrItemList(DirItemList):
794  def __str__(self):
795    return "xattr item list hash {self.key.offset} size {}".format(len(self), self=self)
796
797
798class DirItem(object):
799  _dir_item = [
800    DiskKey.sstruct,
801    struct.Struct('<QHHB')
802  ]
803  sstruct = struct.Struct('<' + ''.join([s.format[1:].decode() for s in _dir_item]))
804
805  def __init__(self, data, pos):
806   next_pos = pos + DiskKey.sstruct.size
807   self.location = DiskKey(data[pos:next_pos])
808   pos = next_pos
809   self.transid, self.data_len, self.name_len, self.type = \
810     DirItem._dir_item[1].unpack_from(data, pos)
811   pos += DirItem._dir_item[1].size
812   self.name, = struct.Struct('<{}s'.format(self.name_len)).unpack_from(data, pos)
813   pos += self.name_len
814   self.data, = struct.Struct('<{}s'.format(self.data_len)).unpack_from(data, pos)
815   pos += self.data_len
816   self._len = DirItem.sstruct.size + self.name_len + self.data_len
817
818  @property
819  def type_str(self):
820    return _dir_item_type_str_map[self.type]
821
822  @property
823  def name_str(self):
824    return embedded_text_for_str(self.name)
825
826  @property
827  def data_str(self):
828    return embedded_text_for_str(self.data)
829
830  def __len__(self):
831    return self._len
832
833  def __str__(self):
834    return "dir item location {self.location} type {self.type_str} " \
835      "name {self.name_str}".format(self=self)
836
837
838class XAttrItem(DirItem):
839  def __str__(self):
840    return "xattr item name {self.name_str} data {self.data_str}".format(self=self)
841
842
843class DirIndex(ItemData):
844  def __init__(self, header, data):
845    super().__init__(header)
846    self.location = DiskKey(data[:DiskKey.sstruct.size])
847    pos = DiskKey.sstruct.size
848    self.transid, self.data_len, self.name_len, self.type = \
849      DirItem._dir_item[1].unpack_from(data, pos)
850    pos += DirItem._dir_item[1].size
851    self.name, = struct.Struct('<{}s'.format(self.name_len)).unpack_from(data, pos)
852
853  @property
854  def type_str(self):
855    return _dir_item_type_str_map[self.type]
856
857  @property
858  def name_str(self):
859    return embedded_text_for_str(self.name)
860
861  def __str__(self):
862    return "dir index {self.key.offset} location {self.location} type {self.type_str} " \
863     "name {self.name_str}".format(self=self)
864
865
866class FileExtentItem(ItemData):
867  _file_extent_item = [
868    struct.Struct('<QQBB2xB'),
869    struct.Struct('<4Q'),
870  ]
871  sstruct = struct.Struct('<' + ''.join([s.format[1:].decode()
872                          for s in _file_extent_item]))
873
874  def __init__(self, key, data):
875    super().__init__(key)
876    self.logical_offset = key.offset
877    self.generation, self.ram_bytes, self.compression, self.encryption, self.type = \
878      FileExtentItem._file_extent_item[0].unpack_from(data)
879    if self.type != FILE_EXTENT_INLINE:
880      # These are confusing, so they deserve a comment in the code:
881      # (disk_bytenr EXTENT_ITEM disk_num_bytes) is the tree key of
882      # the extent item storing the actual data.
883      #
884      # The third one, offset is the offset inside that extent where the
885      # data we need starts. num_bytes is the amount of bytes to be used
886      # from that offset onwards.
887      #
888      # Remember that these numbers always be multiples of disk block
889      # sizes, because that's how it gets cowed. We don't just use 1 or 2
890      # bytes from another extent.
891      pos = FileExtentItem._file_extent_item[0].size
892      self.disk_bytenr, self.disk_num_bytes, self.offset, self.num_bytes = \
893        FileExtentItem._file_extent_item[1].unpack_from(data, pos)
894    else:
895      self._inline_encoded_nbytes = key.data_size - FileExtentItem._file_extent_item[0].size
896
897  @property
898  def compression_str(self):
899    return _compress_type_str_map.get(self.compression, 'unknown')
900
901  @property
902  def type_str(self):
903    return _file_extent_type_str_map.get(self.type, 'unknown')
904
905  def __str__(self):
906    ret = ["extent data at {self.logical_offset} generation {self.generation} "
907        "ram_bytes {self.ram_bytes} "
908        "compression {self.compression_str} type {self.type_str}".format(self=self)]
909    if self.type != FILE_EXTENT_INLINE:
910      ret.append("disk_bytenr {self.disk_bytenr} disk_num_bytes {self.disk_num_bytes} "
911            "offset {self.offset} num_bytes {self.num_bytes}".format(self=self))
912    else:
913      ret.append("inline_encoded_nbytes {self._inline_encoded_nbytes}".format(self=self))
914    return ' '.join(ret)
915
916
917class ExtentItem(ItemData):
918  sstruct = struct.Struct('<3Q')
919  extent_inline_ref = struct.Struct('<BQ')
920
921  def __init__(self, header, data, load_data_refs=True, load_metadata_refs=True):
922    super().__init__(header)
923    self.setattr_from_key(objectid_attr='vaddr', offset_attr='length')
924    pos = 0
925    self.refs, self.generation, self.flags = ExtentItem.sstruct.unpack_from(data, pos)
926    pos += ExtentItem.sstruct.size
927    if self.flags == EXTENT_FLAG_DATA and load_data_refs:
928      self.extent_data_refs = []
929      self.shared_data_refs = []
930      while pos < len(data):
931        inline_ref_type, inline_ref_offset = \
932          ExtentItem.extent_inline_ref.unpack_from(data, pos)
933        if inline_ref_type == EXTENT_DATA_REF_KEY:
934          pos += 1
935          next_pos = pos + InlineExtentDataRef.sstruct.size
936          self.extent_data_refs.append(InlineExtentDataRef(data[pos:next_pos]))
937          pos = next_pos
938        elif inline_ref_type == SHARED_DATA_REF_KEY:
939          pos += 1
940          next_pos = pos + InlineSharedDataRef.inline_shared_data_ref.size
941          self.shared_data_refs.append(InlineSharedDataRef(data[pos:next_pos]))
942          pos = next_pos
943    elif self.flags & EXTENT_FLAG_TREE_BLOCK and load_metadata_refs:
944      next_pos = pos + TreeBlockInfo.tree_block_info.size
945      self.tree_block_info = TreeBlockInfo(data[pos:next_pos])
946      pos = next_pos
947      self.tree_block_refs = []
948      self.shared_block_refs = []
949      while pos < len(data):
950        inline_ref_type, inline_ref_offset = \
951          ExtentItem.extent_inline_ref.unpack_from(data, pos)
952        if inline_ref_type == TREE_BLOCK_REF_KEY:
953          self.tree_block_refs.append(InlineTreeBlockRef(inline_ref_offset))
954        elif inline_ref_type == SHARED_BLOCK_REF_KEY:
955          self.shared_block_refs.append(InlineSharedBlockRef(inline_ref_offset))
956        else:
957          raise Exception("BUG: expected inline TREE_BLOCK_REF or SHARED_BLOCK_REF_KEY "
958                  "but got inline_ref_type {}".format(inline_ref_type))
959        pos += ExtentItem.extent_inline_ref.size
960
961  def append_extent_data_ref(self, ref):
962    self.extent_data_refs.append(ref)
963
964  def append_shared_data_ref(self, ref):
965    self.shared_data_refs.append(ref)
966
967  def append_tree_block_ref(self, ref):
968    self.tree_block_refs.append(ref)
969
970  def append_shared_block_ref(self, ref):
971    self.shared_block_refs.append(ref)
972
973  @property
974  def flags_str(self):
975    return flags_str(self.flags, _extent_flags_str_map)
976
977  def __str__(self):
978    return "extent vaddr {self.vaddr} length {self.length} refs {self.refs} " \
979      "gen {self.generation} flags {self.flags_str}".format(self=self)
980
981
982class ExtentDataRef(ItemData):
983  sstruct = struct.Struct('<3QL')
984
985  def __init__(self, header, data):
986    super().__init__(header)
987    self.root, self.objectid, self.offset, self.count = \
988      ExtentDataRef.sstruct.unpack(data)
989
990  def __str__(self):
991    return "extent data backref root {self.root} objectid {self.objectid} " \
992      "offset {self.offset} count {self.count}".format(self=self)
993
994
995class InlineExtentDataRef(ExtentDataRef):
996  sstruct = ExtentDataRef.sstruct
997
998  def __init__(self, data):
999    self.root, self.objectid, self.offset, self.count = \
1000      InlineExtentDataRef.sstruct.unpack(data)
1001
1002  def __str__(self):
1003    return "inline extent data backref root {self.root} objectid {self.objectid} " \
1004      "offset {self.offset} count {self.count}".format(self=self)
1005
1006
1007class SharedDataRef(ItemData):
1008  sstruct = struct.Struct('<L')
1009
1010  def __init__(self, header, data):
1011    super().__init__(header)
1012    self.setattr_from_key(offset_attr='parent')
1013    self.count, = SharedDataRef.sstruct.unpack(data)
1014
1015  def __str__(self):
1016    return "shared data backref parent {self.parent} count {self.count}".format(self=self)
1017
1018
1019class InlineSharedDataRef(SharedDataRef):
1020  sstruct = struct.Struct('<QL')
1021
1022  def __init__(self, data):
1023    self.parent, self.count = InlineSharedDataRef.sstruct.unpack(data)
1024
1025  def __str__(self):
1026    return "inline shared data backref parent {self.parent} " \
1027     "count {self.count}".format(self=self)
1028
1029
1030class TreeBlockInfo(object):
1031  sstruct = struct.Struct('<QBQB')
1032
1033  def __init__(self, data):
1034    tb_objectid, tb_type, tb_offset, self.level = \
1035      TreeBlockInfo.sstruct.unpack(data)
1036    self.key = Key(tb_objectid, tb_type, tb_offset)
1037
1038  def __str__(self):
1039   return "tree block key {self.key} level {self.level}".format(self=self)
1040
1041class TreeBlockRef(ItemData):
1042  def __init__(self, header):
1043    super().__init__(header)
1044    self.setattr_from_key(offset_attr='root')
1045
1046  def __str__(self):
1047    return "tree block backref root {}".format(key_objectid_str(self.root, None))
1048
1049
1050class InlineTreeBlockRef(TreeBlockRef):
1051  def __init__(self, root):
1052    self.root = root
1053
1054  def __str__(self):
1055    return "inline tree block backref root {}".format(key_objectid_str(self.root, None))
1056
1057
1058class SharedBlockRef(ItemData):
1059  def __init__(self, header):
1060    super().__init__(header)
1061    self.setattr_from_key(offset_attr='parent')
1062
1063  def __str__(self):
1064    return "shared block backref parent {}".format(self.parent)
1065
1066
1067class InlineSharedBlockRef(SharedBlockRef):
1068  def __init__(self, parent):
1069    self.parent = parent
1070
1071  def __str__(self):
1072   return "inline shared block backref parent {}".format(self.parent)
1073
1074# === Main FileSystem class
1075
1076def key_bin_search(fd, base_offset, item_size, cmp_item, min, max):
1077  low = min
1078  high = max
1079
1080  while low < high:
1081    mid = (low + high) // 2
1082    offset = base_offset + mid * item_size
1083
1084    fd.seek(offset)
1085    key1 = DiskKey(fd.read(item_size))
1086    if key1 > cmp_item:
1087      high = mid
1088    elif key1 < cmp_item:
1089      low = mid + 1
1090    else:
1091      return True, mid
1092
1093  return False, low
1094
1095
1096chunk_map_item = namedtuple('chunk_map_item', 'logical physical length devid')
1097
1098
1099class FileSystem(object):
1100  def __init__(self, path, part_offset):
1101    self._chunk_map = OrderedDict()
1102    self.path = path
1103    self.part_offset = part_offset
1104    self.fd = open(path, 'rb')
1105    self.fd.seek(part_offset + 0x10000) # going to superblock
1106
1107    sb_bytes = self.fd.read(superblock.size)
1108    sb_tuple = superblock.unpack(sb_bytes)
1109
1110    if sb_tuple[3] != b'_BHRfS_M':
1111      raise "No signature found"
1112
1113    # setting base FS information
1114    self.fsid = sb_tuple[0]
1115    self.nodesize = sb_tuple[14]
1116    self.sectorsize = sb_tuple[13]
1117
1118    self._chunk_root = sb_tuple[6]
1119    self._chunk_root_level = sb_tuple[24]
1120    self._tree_roots_root = sb_tuple[5]
1121    self._tree_roots_root_level = sb_tuple[23]
1122
1123    # setting chunk map
1124    sys_chunk_array_size = sb_tuple[17]
1125    sys_chunk = sb_tuple[25][:sys_chunk_array_size]
1126
1127    pos = 0
1128    while pos < sys_chunk_array_size:
1129      key = DiskKey(sys_chunk[pos:])
1130      pos += DiskKey.sstruct.size
1131      chunk = Chunk(key, sys_chunk[pos:])
1132      for st in chunk.stripes:
1133        self._insert_chunk(chunk_map_item(chunk.vaddr, st.offset, chunk.length, st.devid))
1134      pos += chunk.size
1135
1136    # setting tree roots
1137    _, fs_tree_root_item = self.search_tree(self._tree_roots_root_level, self._tree_roots_root, Key(FS_TREE_OBJECTID, ROOT_ITEM_KEY, 0))
1138    _, extent_tree_root_item = self.search_tree(self._tree_roots_root_level, self._tree_roots_root, Key(EXTENT_TREE_OBJECTID, ROOT_ITEM_KEY, 0))
1139    self._fs_root_level = fs_tree_root_item.level
1140    self._fs_root = fs_tree_root_item.bytenr
1141    self._extent_root_level = extent_tree_root_item.level
1142    self._extent_root = extent_tree_root_item.bytenr
1143
1144  @property
1145  def chunk_root(self):
1146    return self._chunk_root_level, self._chunk_root
1147
1148  @property
1149  def tree_roots_root(self):
1150    return self._tree_roots_root_level, self._tree_roots_root
1151
1152  @property
1153  def fs_root(self):
1154    return self._fs_root_level, self._fs_root
1155
1156  @property
1157  def extent_root(self):
1158    return self._extent_root_level, self._extent_root
1159
1160  def logical_to_physical(self, log):
1161    cur_logical = next(iter(self._chunk_map)) # first item
1162
1163    for logical, cmi in self._chunk_map.items():
1164      if logical > log:
1165        break
1166      cur_logical = logical
1167
1168    # if there is no address in chunk_map, searching in chunk_tree
1169    if cur_logical + self._chunk_map[cur_logical].length < log:
1170      def process_func(header, offset):
1171        for i in range(header.items_num):
1172          self.fd.seek(offset + i * LeafKey.sstruct.size)
1173          k = LeafKey(self.fd.read(LeafKey.sstruct.size))
1174          self.fd.seek(offset + k.data_offset)
1175          if k.type == CHUNK_ITEM_KEY:
1176            item = _key_type_class_map[k.type](k, self.fd.read(k.data_size))
1177            for st in item.stripes:
1178              self._insert_chunk(chunk_map_item(item.vaddr, st.offset, item.length, st.devid))
1179
1180      self.search_tree(self._chunk_root_level, self._chunk_root, Key(FIRST_CHUNK_TREE_OBJECTID, CHUNK_ITEM_KEY, log), process_func)
1181
1182      cur_logical = next(iter(self._chunk_map)) # first item
1183      if cur_logical > log:
1184        raise Exception(f'Cannot translate address {log:#x}')
1185
1186      for logical, cmi in self._chunk_map.items():
1187        if logical > log:
1188          break
1189        cur_logical = logical
1190
1191      if cur_logical + self._chunk_map[cur_logical].length < log:
1192        raise Exception(f'Cannot translate address {log:#x}')
1193
1194    print('address translation: {:#x} -> {:#x}'.format(log, self._chunk_map[cur_logical].physical + log - cur_logical))
1195    return self.part_offset + self._chunk_map[cur_logical].physical + log - cur_logical
1196
1197  def search_tree(self, level, root_offset, key, process_node_func = None):
1198    for lvl in range(level, 0, -1):
1199      # inner node
1200      root_offset = self.logical_to_physical(root_offset)
1201      self.fd.seek(root_offset)
1202      header = NodeHeader._make(_node_header_struct.unpack(self.fd.read(_node_header_struct.size)))
1203      if header.level != lvl:
1204        raise Exception('Invalid inner node level')
1205
1206      found, itemnr = key_bin_search(
1207        self.fd,
1208        root_offset + _node_header_struct.size,
1209        InnerKey.sstruct.size,
1210        key,
1211        0,
1212        header.items_num
1213        )
1214
1215      # TODO: better understand this
1216      if not found and itemnr > 0:
1217        itemnr -= 1
1218
1219      self.fd.seek(root_offset + _node_header_struct.size + itemnr * InnerKey.sstruct.size)
1220      k = InnerKey(self.fd.read(InnerKey.sstruct.size))
1221      root_offset = k.block_num
1222    else:
1223      # we are in leaf node
1224      root_offset = self.logical_to_physical(root_offset)
1225      self.fd.seek(root_offset)
1226      header = NodeHeader._make(_node_header_struct.unpack(self.fd.read(_node_header_struct.size)))
1227      if header.level != 0:
1228        raise Exception('Invalid leaf level')
1229
1230      if process_node_func:
1231        process_node_func(header, root_offset + _node_header_struct.size)
1232        return root_offset
1233
1234      found, itemnr = key_bin_search(
1235        self.fd,
1236        root_offset + _node_header_struct.size,
1237        LeafKey.sstruct.size,
1238        key,
1239        0,
1240        header.items_num
1241        )
1242
1243      self.fd.seek(root_offset + _node_header_struct.size + itemnr * LeafKey.sstruct.size)
1244      k = LeafKey(self.fd.read(LeafKey.sstruct.size))
1245      self.fd.seek(root_offset + _node_header_struct.size + k.data_offset)
1246
1247      if k.type in _key_type_class_map:
1248        return k, _key_type_class_map[k.type](k, self.fd.read(k.data_size))
1249      else:
1250        return k, False
1251
1252  def print_node(self, header, offset):
1253    print(header)
1254    root_paddr = self.logical_to_physical(header.node_addr)
1255    key_size = InnerKey.sstruct.size if header.level > 0 else LeafKey.sstruct.size
1256    key_struct = InnerKey if header.level > 0 else LeafKey
1257    for i in range(header.items_num):
1258      self.fd.seek(root_paddr + _node_header_struct.size + i * key_size)
1259      k = key_struct(self.fd.read(key_size))
1260      print(k)
1261      self.fd.seek(root_paddr + _node_header_struct.size + k.data_offset)
1262      if k.type in _key_type_class_map and header.level == 0:
1263        item = _key_type_class_map[k.type](k, self.fd.read(k.data_size))
1264        print(item)
1265        if k.type == DIR_ITEM_KEY:
1266          for it in item:
1267            print(it)
1268      print('============================')
1269
1270  def print_chunk_map(self):
1271    print('=== chunk map ===')
1272    for logical, cmi in self._chunk_map.items():
1273      print(f'{cmi.logical:#x}..{cmi.logical+cmi.length:#x} -> {cmi.physical:#x}..{cmi.physical+cmi.length:#x}')
1274    print('=================')
1275
1276  def _insert_chunk(self, chunk):
1277    if not chunk.logical in self._chunk_map:
1278      cm = dict(self._chunk_map)
1279      cm[chunk.logical] = chunk
1280      self._chunk_map = OrderedDict(sorted(cm.items()))
1281
1282
1283_key_type_class_map = {
1284  INODE_ITEM_KEY: InodeItem,
1285  INODE_REF_KEY: InodeRef,
1286  DIR_ITEM_KEY: DirItemList,
1287  DIR_INDEX_KEY: DirIndex,
1288  EXTENT_DATA_KEY: FileExtentItem,
1289  ROOT_ITEM_KEY: RootItem,
1290  EXTENT_ITEM_KEY: ExtentItem,
1291  CHUNK_ITEM_KEY: Chunk,
1292}
1293