boot_sector.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. # SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
  2. # SPDX-License-Identifier: Apache-2.0
  3. from inspect import getmembers, isroutine
  4. from typing import Optional
  5. from construct import Const, Int8ul, Int16ul, Int32ul, PaddedString, Struct, core
  6. from .exceptions import InconsistentFATAttributes, NotInitialized
  7. from .fatfs_state import BootSectorState
  8. from .utils import (ALLOWED_SECTOR_SIZES, ALLOWED_SECTORS_PER_CLUSTER, EMPTY_BYTE, FAT32, FULL_BYTE,
  9. SHORT_NAMES_ENCODING, FATDefaults, generate_4bytes_random, pad_string)
  10. class BootSector:
  11. """
  12. This class describes the first sector of the volume in the Reserved Region.
  13. It contains data from BPB (BIOS Parameter Block) and BS (Boot sector). The fields of the BPB and BS are mixed in
  14. the header of the physical boot sector. Fields with prefix BPB belongs to BPB block and with prefix BS
  15. belongs to the actual boot sector.
  16. Please beware, that the name of class BootSector refer to data both from the boot sector and BPB.
  17. ESP32 ignores fields with prefix "BS_"! Fields with prefix BPB_ are essential to read the filesystem.
  18. """
  19. MAX_VOL_LAB_SIZE = 11
  20. MAX_OEM_NAME_SIZE = 8
  21. MAX_FS_TYPE_SIZE = 8
  22. # the FAT specification defines 512 bytes for the boot sector header
  23. BOOT_HEADER_SIZE = 512
  24. BOOT_SECTOR_HEADER = Struct(
  25. # this value reflects BS_jmpBoot used for ESP32 boot sector (any other accepted)
  26. 'BS_jmpBoot' / Const(b'\xeb\xfe\x90'),
  27. 'BS_OEMName' / PaddedString(MAX_OEM_NAME_SIZE, SHORT_NAMES_ENCODING),
  28. 'BPB_BytsPerSec' / Int16ul,
  29. 'BPB_SecPerClus' / Int8ul,
  30. 'BPB_RsvdSecCnt' / Int16ul,
  31. 'BPB_NumFATs' / Int8ul,
  32. 'BPB_RootEntCnt' / Int16ul,
  33. 'BPB_TotSec16' / Int16ul, # zero if the FAT type is 32, otherwise number of sectors
  34. 'BPB_Media' / Int8ul,
  35. 'BPB_FATSz16' / Int16ul, # for FAT32 always zero, for FAT12/FAT16 number of sectors per FAT
  36. 'BPB_SecPerTrk' / Int16ul,
  37. 'BPB_NumHeads' / Int16ul,
  38. 'BPB_HiddSec' / Int32ul,
  39. 'BPB_TotSec32' / Int32ul, # zero if the FAT type is 12/16, otherwise number of sectors
  40. 'BS_DrvNum' / Const(b'\x80'),
  41. 'BS_Reserved1' / Const(EMPTY_BYTE),
  42. 'BS_BootSig' / Const(b'\x29'),
  43. 'BS_VolID' / Int32ul,
  44. 'BS_VolLab' / PaddedString(MAX_VOL_LAB_SIZE, SHORT_NAMES_ENCODING),
  45. 'BS_FilSysType' / PaddedString(MAX_FS_TYPE_SIZE, SHORT_NAMES_ENCODING),
  46. 'BS_EMPTY' / Const(448 * EMPTY_BYTE),
  47. 'Signature_word' / Const(FATDefaults.SIGNATURE_WORD)
  48. )
  49. assert BOOT_SECTOR_HEADER.sizeof() == BOOT_HEADER_SIZE
  50. def __init__(self, boot_sector_state: Optional[BootSectorState] = None) -> None:
  51. self._parsed_header: dict = {}
  52. self.boot_sector_state: BootSectorState = boot_sector_state
  53. def generate_boot_sector(self) -> None:
  54. boot_sector_state: BootSectorState = self.boot_sector_state
  55. if boot_sector_state is None:
  56. raise NotInitialized('The BootSectorState instance is not initialized!')
  57. volume_uuid = generate_4bytes_random()
  58. pad_header: bytes = (boot_sector_state.sector_size - BootSector.BOOT_HEADER_SIZE) * EMPTY_BYTE
  59. data_content: bytes = boot_sector_state.data_sectors * boot_sector_state.sector_size * FULL_BYTE
  60. root_dir_content: bytes = boot_sector_state.root_dir_sectors_cnt * boot_sector_state.sector_size * EMPTY_BYTE
  61. fat_tables_content: bytes = (boot_sector_state.sectors_per_fat_cnt
  62. * boot_sector_state.fat_tables_cnt
  63. * boot_sector_state.sector_size
  64. * EMPTY_BYTE)
  65. self.boot_sector_state.binary_image = (
  66. BootSector.BOOT_SECTOR_HEADER.build(
  67. dict(BS_OEMName=pad_string(boot_sector_state.oem_name, size=BootSector.MAX_OEM_NAME_SIZE),
  68. BPB_BytsPerSec=boot_sector_state.sector_size,
  69. BPB_SecPerClus=boot_sector_state.sectors_per_cluster,
  70. BPB_RsvdSecCnt=boot_sector_state.reserved_sectors_cnt,
  71. BPB_NumFATs=boot_sector_state.fat_tables_cnt,
  72. BPB_RootEntCnt=boot_sector_state.entries_root_count,
  73. # if fat type is 12 or 16 BPB_TotSec16 is filled and BPB_TotSec32 is 0x00 and vice versa
  74. BPB_TotSec16=0x00 if boot_sector_state.fatfs_type == FAT32 else boot_sector_state.sectors_count,
  75. BPB_Media=boot_sector_state.media_type,
  76. BPB_FATSz16=boot_sector_state.sectors_per_fat_cnt,
  77. BPB_SecPerTrk=boot_sector_state.sec_per_track,
  78. BPB_NumHeads=boot_sector_state.num_heads,
  79. BPB_HiddSec=boot_sector_state.hidden_sectors,
  80. BPB_TotSec32=boot_sector_state.sectors_count if boot_sector_state.fatfs_type == FAT32 else 0x00,
  81. BS_VolID=volume_uuid,
  82. BS_VolLab=pad_string(boot_sector_state.volume_label,
  83. size=BootSector.MAX_VOL_LAB_SIZE),
  84. BS_FilSysType=pad_string(boot_sector_state.file_sys_type,
  85. size=BootSector.MAX_FS_TYPE_SIZE)
  86. )
  87. ) + pad_header + fat_tables_content + root_dir_content + data_content
  88. )
  89. def parse_boot_sector(self, binary_data: bytes) -> None:
  90. """
  91. Checks the validity of the boot sector and derives the metadata from boot sector to the structured shape.
  92. """
  93. try:
  94. self._parsed_header = BootSector.BOOT_SECTOR_HEADER.parse(binary_data)
  95. except core.StreamError:
  96. raise NotInitialized('The boot sector header is not parsed successfully!')
  97. if self._parsed_header['BPB_TotSec16'] != 0x00:
  98. sectors_count_: int = self._parsed_header['BPB_TotSec16']
  99. elif self._parsed_header['BPB_TotSec32'] != 0x00:
  100. # uncomment for FAT32 implementation
  101. # sectors_count_ = self._parsed_header['BPB_TotSec32']
  102. # possible_fat_types = [FAT32]
  103. assert self._parsed_header['BPB_TotSec16'] == 0
  104. raise NotImplementedError('FAT32 not implemented!')
  105. else:
  106. raise InconsistentFATAttributes('The number of FS sectors cannot be zero!')
  107. if self._parsed_header['BPB_BytsPerSec'] not in ALLOWED_SECTOR_SIZES:
  108. raise InconsistentFATAttributes(f'The number of bytes '
  109. f"per sector is {self._parsed_header['BPB_BytsPerSec']}! "
  110. f'The accepted values are {ALLOWED_SECTOR_SIZES}')
  111. if self._parsed_header['BPB_SecPerClus'] not in ALLOWED_SECTORS_PER_CLUSTER:
  112. raise InconsistentFATAttributes(f'The number of sectors per cluster '
  113. f"is {self._parsed_header['BPB_SecPerClus']}"
  114. f'The accepted values are {ALLOWED_SECTORS_PER_CLUSTER}')
  115. total_root_bytes: int = self._parsed_header['BPB_RootEntCnt'] * FATDefaults.ENTRY_SIZE
  116. root_dir_sectors_cnt_: int = total_root_bytes // self._parsed_header['BPB_BytsPerSec']
  117. self.boot_sector_state = BootSectorState(oem_name=self._parsed_header['BS_OEMName'],
  118. sector_size=self._parsed_header['BPB_BytsPerSec'],
  119. sectors_per_cluster=self._parsed_header['BPB_SecPerClus'],
  120. reserved_sectors_cnt=self._parsed_header['BPB_RsvdSecCnt'],
  121. fat_tables_cnt=self._parsed_header['BPB_NumFATs'],
  122. root_dir_sectors_cnt=root_dir_sectors_cnt_,
  123. sectors_count=sectors_count_,
  124. media_type=self._parsed_header['BPB_Media'],
  125. sec_per_track=self._parsed_header['BPB_SecPerTrk'],
  126. num_heads=self._parsed_header['BPB_NumHeads'],
  127. hidden_sectors=self._parsed_header['BPB_HiddSec'],
  128. volume_label=self._parsed_header['BS_VolLab'],
  129. file_sys_type=self._parsed_header['BS_FilSysType'],
  130. volume_uuid=self._parsed_header['BS_VolID'])
  131. self.boot_sector_state.binary_image = binary_data
  132. assert self.boot_sector_state.file_sys_type in (f'FAT{self.boot_sector_state.fatfs_type} ', 'FAT ')
  133. def __str__(self) -> str:
  134. """
  135. FATFS properties parser (internal helper tool for fatfsgen.py/fatfsparse.py)
  136. Provides all the properties of given FATFS instance by parsing its boot sector (returns formatted string)
  137. """
  138. if self._parsed_header == {}:
  139. return 'Boot sector is not initialized!'
  140. res: str = 'FATFS properties:\n'
  141. for member in getmembers(self.boot_sector_state, lambda a: not (isroutine(a))):
  142. prop_ = getattr(self.boot_sector_state, member[0])
  143. if isinstance(prop_, int) or isinstance(prop_, str) and not member[0].startswith('_'):
  144. res += f'{member[0]}: {prop_}\n'
  145. return res
  146. @property
  147. def binary_image(self) -> bytes:
  148. # when BootSector is not instantiated, self.boot_sector_state might be None
  149. if self.boot_sector_state is None or len(self.boot_sector_state.binary_image) == 0:
  150. raise NotInitialized('Boot sector is not initialized!')
  151. bin_image_: bytes = self.boot_sector_state.binary_image
  152. return bin_image_