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