fatfsgen.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. #!/usr/bin/env python
  2. # SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
  3. # SPDX-License-Identifier: Apache-2.0
  4. import os
  5. from datetime import datetime
  6. from typing import Any, List, Optional
  7. from fatfs_utils.boot_sector import BootSector
  8. from fatfs_utils.fat import FAT
  9. from fatfs_utils.fatfs_state import FATFSState
  10. from fatfs_utils.fs_object import Directory
  11. from fatfs_utils.utils import (BYTES_PER_DIRECTORY_ENTRY, FATFS_INCEPTION, FATDefaults,
  12. get_args_for_partition_generator, read_filesystem)
  13. class FATFS:
  14. """
  15. The class FATFS provides API for generating FAT file system.
  16. It contains reference to the FAT table and to the root directory.
  17. """
  18. def __init__(self,
  19. binary_image_path: Optional[str] = None,
  20. size: int = FATDefaults.SIZE,
  21. reserved_sectors_cnt: int = FATDefaults.RESERVED_SECTORS_COUNT,
  22. fat_tables_cnt: int = FATDefaults.FAT_TABLES_COUNT,
  23. sectors_per_cluster: int = FATDefaults.SECTORS_PER_CLUSTER,
  24. sector_size: int = FATDefaults.SECTOR_SIZE,
  25. hidden_sectors: int = FATDefaults.HIDDEN_SECTORS,
  26. long_names_enabled: bool = False,
  27. use_default_datetime: bool = True,
  28. num_heads: int = FATDefaults.NUM_HEADS,
  29. oem_name: str = FATDefaults.OEM_NAME,
  30. sec_per_track: int = FATDefaults.SEC_PER_TRACK,
  31. volume_label: str = FATDefaults.VOLUME_LABEL,
  32. file_sys_type: str = FATDefaults.FILE_SYS_TYPE,
  33. root_entry_count: int = FATDefaults.ROOT_ENTRIES_COUNT,
  34. explicit_fat_type: int = None,
  35. media_type: int = FATDefaults.MEDIA_TYPE) -> None:
  36. # root directory bytes should be aligned by sector size
  37. assert (root_entry_count * BYTES_PER_DIRECTORY_ENTRY) % sector_size == 0
  38. # number of bytes in the root dir must be even multiple of BPB_BytsPerSec
  39. assert ((root_entry_count * BYTES_PER_DIRECTORY_ENTRY) // sector_size) % 2 == 0
  40. root_dir_sectors_cnt: int = (root_entry_count * BYTES_PER_DIRECTORY_ENTRY) // sector_size
  41. self.state: FATFSState = FATFSState(sector_size=sector_size,
  42. explicit_fat_type=explicit_fat_type,
  43. reserved_sectors_cnt=reserved_sectors_cnt,
  44. root_dir_sectors_cnt=root_dir_sectors_cnt,
  45. size=size,
  46. file_sys_type=file_sys_type,
  47. num_heads=num_heads,
  48. fat_tables_cnt=fat_tables_cnt,
  49. sectors_per_cluster=sectors_per_cluster,
  50. media_type=media_type,
  51. hidden_sectors=hidden_sectors,
  52. sec_per_track=sec_per_track,
  53. long_names_enabled=long_names_enabled,
  54. volume_label=volume_label,
  55. oem_name=oem_name,
  56. use_default_datetime=use_default_datetime)
  57. binary_image: bytes = bytearray(
  58. read_filesystem(binary_image_path) if binary_image_path else self.create_empty_fatfs())
  59. self.state.binary_image = binary_image
  60. self.fat: FAT = FAT(boot_sector_state=self.state.boot_sector_state, init_=True)
  61. root_dir_size = self.state.boot_sector_state.root_dir_sectors_cnt * self.state.boot_sector_state.sector_size
  62. self.root_directory: Directory = Directory(name='A', # the name is not important, must be string
  63. size=root_dir_size,
  64. fat=self.fat,
  65. cluster=self.fat.clusters[1],
  66. fatfs_state=self.state)
  67. self.root_directory.init_directory()
  68. def create_file(self, name: str,
  69. extension: str = '',
  70. path_from_root: Optional[List[str]] = None,
  71. object_timestamp_: datetime = FATFS_INCEPTION) -> None:
  72. # when path_from_root is None the dir is root
  73. self.root_directory.new_file(name=name,
  74. extension=extension,
  75. path_from_root=path_from_root,
  76. object_timestamp_=object_timestamp_)
  77. def create_directory(self, name: str,
  78. path_from_root: Optional[List[str]] = None,
  79. object_timestamp_: datetime = FATFS_INCEPTION) -> None:
  80. # when path_from_root is None the dir is root
  81. parent_dir = self.root_directory
  82. if path_from_root:
  83. parent_dir = self.root_directory.recursive_search(path_from_root, self.root_directory)
  84. self.root_directory.new_directory(name=name,
  85. parent=parent_dir,
  86. path_from_root=path_from_root,
  87. object_timestamp_=object_timestamp_)
  88. def write_content(self, path_from_root: List[str], content: bytes) -> None:
  89. """
  90. fat fs invokes root directory to recursively find the required file and writes the content
  91. """
  92. self.root_directory.write_to_file(path_from_root, content)
  93. def create_empty_fatfs(self) -> Any:
  94. boot_sector_ = BootSector(boot_sector_state=self.state.boot_sector_state)
  95. boot_sector_.generate_boot_sector()
  96. return boot_sector_.binary_image
  97. def write_filesystem(self, output_path: str) -> None:
  98. with open(output_path, 'wb') as output:
  99. output.write(bytearray(self.state.binary_image))
  100. def _generate_partition_from_folder(self,
  101. folder_relative_path: str,
  102. folder_path: str = '',
  103. is_dir: bool = False) -> None:
  104. """
  105. Given path to folder and folder name recursively encodes folder into binary image.
  106. Used by method generate.
  107. """
  108. real_path: str = os.path.join(folder_path, folder_relative_path)
  109. lower_path: str = folder_relative_path
  110. folder_relative_path = folder_relative_path.upper()
  111. normal_path = os.path.normpath(folder_relative_path)
  112. split_path = normal_path.split(os.sep)
  113. object_timestamp = datetime.fromtimestamp(os.path.getctime(real_path))
  114. if os.path.isfile(real_path):
  115. with open(real_path, 'rb') as file:
  116. content = file.read()
  117. file_name, extension = os.path.splitext(split_path[-1])
  118. extension = extension[1:] # remove the dot from the extension
  119. self.create_file(name=file_name,
  120. extension=extension,
  121. path_from_root=split_path[1:-1] or None,
  122. object_timestamp_=object_timestamp)
  123. self.write_content(split_path[1:], content)
  124. elif os.path.isdir(real_path):
  125. if not is_dir:
  126. self.create_directory(name=split_path[-1],
  127. path_from_root=split_path[1:-1],
  128. object_timestamp_=object_timestamp)
  129. # sorting files for better testability
  130. dir_content = list(sorted(os.listdir(real_path)))
  131. for path in dir_content:
  132. self._generate_partition_from_folder(os.path.join(lower_path, path), folder_path=folder_path)
  133. def generate(self, input_directory: str) -> None:
  134. """
  135. Normalize path to folder and recursively encode folder to binary image
  136. """
  137. path_to_folder, folder_name = os.path.split(input_directory)
  138. self._generate_partition_from_folder(folder_name, folder_path=path_to_folder, is_dir=True)
  139. def main() -> None:
  140. args = get_args_for_partition_generator('Create a FAT filesystem and populate it with directory content')
  141. fatfs = FATFS(sector_size=args.sector_size,
  142. sectors_per_cluster=args.sectors_per_cluster,
  143. size=args.partition_size,
  144. root_entry_count=args.root_entry_count,
  145. explicit_fat_type=args.fat_type,
  146. long_names_enabled=args.long_name_support,
  147. use_default_datetime=args.use_default_datetime)
  148. fatfs.generate(args.input_directory)
  149. fatfs.write_filesystem(args.output_file)
  150. if __name__ == '__main__':
  151. main()