fatfsparse.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
  2. # SPDX-License-Identifier: Apache-2.0
  3. import argparse
  4. import os
  5. import construct
  6. from fatfs_utils.boot_sector import BootSector
  7. from fatfs_utils.entry import Entry
  8. from fatfs_utils.fat import FAT
  9. from fatfs_utils.fatfs_state import BootSectorState
  10. from fatfs_utils.utils import FULL_BYTE, LONG_NAMES_ENCODING, PAD_CHAR, FATDefaults, lfn_checksum, read_filesystem
  11. def build_file_name(name1: bytes, name2: bytes, name3: bytes) -> str:
  12. full_name_ = name1 + name2 + name3
  13. # need to strip empty bytes and null-terminating char ('\x00')
  14. return full_name_.rstrip(FULL_BYTE).decode(LONG_NAMES_ENCODING).rstrip('\x00')
  15. def get_obj_name(obj_: dict, directory_bytes_: bytes, entry_position_: int, lfn_checksum_: int) -> str:
  16. obj_ext_ = obj_['DIR_Name_ext'].rstrip(chr(PAD_CHAR))
  17. ext_ = f'.{obj_ext_}' if len(obj_ext_) > 0 else ''
  18. obj_name_: str = obj_['DIR_Name'].rstrip(chr(PAD_CHAR)) + ext_ # short entry name
  19. if not args.long_name_support:
  20. return obj_name_
  21. full_name = {}
  22. for pos in range(entry_position_ - 1, -1, -1): # loop from the current entry back to the start
  23. obj_address_: int = FATDefaults.ENTRY_SIZE * pos
  24. entry_bytes_: bytes = directory_bytes_[obj_address_: obj_address_ + FATDefaults.ENTRY_SIZE]
  25. struct_ = Entry.parse_entry_long(entry_bytes_, lfn_checksum_)
  26. if len(struct_.items()) > 0:
  27. full_name[struct_['order']] = build_file_name(struct_['name1'], struct_['name2'], struct_['name3'])
  28. if struct_['is_last']:
  29. break
  30. return ''.join(map(lambda x: x[1], sorted(full_name.items()))) or obj_name_
  31. def traverse_folder_tree(directory_bytes_: bytes,
  32. name: str,
  33. state_: BootSectorState,
  34. fat_: FAT,
  35. binary_array_: bytearray) -> None:
  36. os.makedirs(name)
  37. assert len(directory_bytes_) % FATDefaults.ENTRY_SIZE == 0
  38. entries_count_: int = len(directory_bytes_) // FATDefaults.ENTRY_SIZE
  39. for i in range(entries_count_):
  40. obj_address_: int = FATDefaults.ENTRY_SIZE * i
  41. try:
  42. obj_: dict = Entry.ENTRY_FORMAT_SHORT_NAME.parse(
  43. directory_bytes_[obj_address_: obj_address_ + FATDefaults.ENTRY_SIZE])
  44. except (construct.core.ConstError, UnicodeDecodeError) as e:
  45. if not args.long_name_support:
  46. raise e
  47. continue
  48. if obj_['DIR_Attr'] == 0: # empty entry
  49. continue
  50. obj_name_: str = get_obj_name(obj_,
  51. directory_bytes_,
  52. entry_position_=i,
  53. lfn_checksum_=lfn_checksum(obj_['DIR_Name'] + obj_['DIR_Name_ext']))
  54. if obj_['DIR_Attr'] == Entry.ATTR_ARCHIVE:
  55. content_ = fat_.chain_content(cluster_id_=Entry.get_cluster_id(obj_)).rstrip(chr(0x00).encode())
  56. with open(os.path.join(name, obj_name_), 'wb') as new_file:
  57. new_file.write(content_)
  58. elif obj_['DIR_Attr'] == Entry.ATTR_DIRECTORY:
  59. # avoid creating symlinks to itself and parent folder
  60. if obj_name_ in ('.', '..'):
  61. continue
  62. child_directory_bytes_ = fat_.chain_content(cluster_id_=obj_['DIR_FstClusLO'])
  63. traverse_folder_tree(directory_bytes_=child_directory_bytes_,
  64. name=os.path.join(name, obj_name_),
  65. state_=state_,
  66. fat_=fat_,
  67. binary_array_=binary_array_)
  68. if __name__ == '__main__':
  69. desc = 'Tool for parsing fatfs image and extracting directory structure on host.'
  70. argument_parser: argparse.ArgumentParser = argparse.ArgumentParser(description=desc)
  71. argument_parser.add_argument('input_image',
  72. help='Path to the image that will be parsed and extracted.')
  73. argument_parser.add_argument('--long-name-support',
  74. action='store_true',
  75. help='Set flag to enable long names support.')
  76. args = argument_parser.parse_args()
  77. fs = read_filesystem(args.input_image)
  78. boot_sector_ = BootSector()
  79. boot_sector_.parse_boot_sector(fs)
  80. fat = FAT(boot_sector_.boot_sector_state, init_=False)
  81. boot_dir_start_ = boot_sector_.boot_sector_state.root_directory_start
  82. boot_dir_sectors = boot_sector_.boot_sector_state.root_dir_sectors_cnt
  83. full_ = fs[boot_dir_start_: boot_dir_start_ + boot_dir_sectors * boot_sector_.boot_sector_state.sector_size]
  84. traverse_folder_tree(full_,
  85. boot_sector_.boot_sector_state.volume_label.rstrip(chr(PAD_CHAR)),
  86. boot_sector_.boot_sector_state, fat, fs)