cluster.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. # SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
  2. # SPDX-License-Identifier: Apache-2.0
  3. from typing import Dict, Optional
  4. from construct import Int16ul
  5. from .fatfs_state import BootSectorState
  6. from .utils import (EMPTY_BYTE, FAT12, FAT16, build_byte, merge_by_half_byte_12_bit_little_endian,
  7. split_by_half_byte_12_bit_little_endian)
  8. def get_dir_size(is_root: bool, boot_sector: BootSectorState) -> int:
  9. dir_size_: int = boot_sector.root_dir_sectors_cnt * boot_sector.sector_size if is_root else boot_sector.sector_size
  10. return dir_size_
  11. class Cluster:
  12. """
  13. class Cluster handles values in FAT table and allocates sectors in data region.
  14. """
  15. RESERVED_BLOCK_ID: int = 0
  16. ROOT_BLOCK_ID: int = 1
  17. ALLOCATED_BLOCK_FAT12: int = 0xFFF
  18. ALLOCATED_BLOCK_FAT16: int = 0xFFFF
  19. ALLOCATED_BLOCK_SWITCH = {FAT12: ALLOCATED_BLOCK_FAT12, FAT16: ALLOCATED_BLOCK_FAT16}
  20. INITIAL_BLOCK_SWITCH: Dict[int, int] = {FAT12: 0xFF8, FAT16: 0xFFF8}
  21. def __init__(self,
  22. cluster_id: int,
  23. boot_sector_state: BootSectorState,
  24. init_: bool) -> None:
  25. self.id: int = cluster_id
  26. self.boot_sector_state: BootSectorState = boot_sector_state
  27. self._next_cluster = None # type: Optional[Cluster]
  28. # First cluster in FAT is reserved, low 8 bits contains BPB_Media and the rest is filled with 1
  29. # e.g. the esp32 media type is 0xF8 thus the FAT[0] = 0xFF8 for FAT12, 0xFFF8 for FAT16
  30. if self.id == Cluster.RESERVED_BLOCK_ID and init_:
  31. self.set_in_fat(self.INITIAL_BLOCK_SWITCH[self.boot_sector_state.fatfs_type])
  32. return
  33. self.cluster_data_address: int = self._compute_cluster_data_address()
  34. assert self.cluster_data_address
  35. @property
  36. def next_cluster(self): # type: () -> Optional[Cluster]
  37. return self._next_cluster
  38. @next_cluster.setter
  39. def next_cluster(self, value): # type: (Optional[Cluster]) -> None
  40. self._next_cluster = value
  41. def _cluster_id_to_logical_position_in_bits(self, _id: int) -> int:
  42. # computes address of the cluster in fat table
  43. logical_position_: int = self.boot_sector_state.fatfs_type * _id
  44. return logical_position_
  45. @staticmethod
  46. def compute_cluster_data_address(boot_sector_state: BootSectorState, id_: int) -> int:
  47. """
  48. This method translates the id of the cluster to the address in data region.
  49. :param boot_sector_state: the class with FS shared data
  50. :param id_: id of the cluster
  51. :returns: integer denoting the address of the cluster in the data region
  52. """
  53. data_address_: int = boot_sector_state.root_directory_start
  54. if not id_ == Cluster.ROOT_BLOCK_ID:
  55. # the first data cluster id is 2 (we have to subtract reserved cluster and cluster for root)
  56. data_address_ = boot_sector_state.sector_size * (id_ - 2) + boot_sector_state.data_region_start
  57. return data_address_
  58. def _compute_cluster_data_address(self) -> int:
  59. return self.compute_cluster_data_address(self.boot_sector_state, self.id)
  60. def _set_left_half_byte(self, address: int, value: int) -> None:
  61. self.boot_sector_state.binary_image[address] &= 0x0f
  62. self.boot_sector_state.binary_image[address] |= value << 4
  63. def _set_right_half_byte(self, address: int, value: int) -> None:
  64. self.boot_sector_state.binary_image[address] &= 0xf0
  65. self.boot_sector_state.binary_image[address] |= value
  66. @property
  67. def fat_cluster_address(self) -> int:
  68. """Determines how many bits precede the first bit of the cluster in FAT"""
  69. return self._cluster_id_to_logical_position_in_bits(self.id)
  70. @property
  71. def real_cluster_address(self) -> int:
  72. """
  73. The property method computes the real address of the cluster in the FAT region. Result is simply
  74. address of the cluster in fat + fat table address.
  75. """
  76. cluster_address: int = self.boot_sector_state.fat_table_start_address + self.fat_cluster_address // 8
  77. return cluster_address
  78. def get_from_fat(self) -> int:
  79. """
  80. Calculating the value in the FAT block, that denotes if the block is full, empty, or chained to other block.
  81. For FAT12 is the block stored in one and half byte. If the order of the block is even the first byte and second
  82. half of the second byte belongs to the block. First half of the second byte and the third byte belongs to
  83. the second block.
  84. e.g. b'\xff\x0f\x00' stores two blocks. First of them is evenly ordered (index 0) and is set to 0xfff,
  85. that means full block that is final in chain of blocks
  86. and second block is set to 0x000 that means empty block.
  87. three bytes - AB XC YZ - stores two blocks - CAB YZX
  88. """
  89. address_: int = self.real_cluster_address
  90. bin_img_: bytearray = self.boot_sector_state.binary_image
  91. if self.boot_sector_state.fatfs_type == FAT12:
  92. if self.fat_cluster_address % 8 == 0:
  93. # even block
  94. return bin_img_[self.real_cluster_address] | ((bin_img_[self.real_cluster_address + 1] & 0x0F) << 8)
  95. # odd block
  96. return ((bin_img_[self.real_cluster_address] & 0xF0) >> 4) | (bin_img_[self.real_cluster_address + 1] << 4)
  97. if self.boot_sector_state.fatfs_type == FAT16:
  98. return int.from_bytes(bin_img_[address_:address_ + 2], byteorder='little')
  99. raise NotImplementedError('Only valid fatfs types are FAT12 and FAT16.')
  100. @property
  101. def is_empty(self) -> bool:
  102. """
  103. The property method takes a look into the binary array and checks if the bytes ordered by little endian
  104. and relates to the current cluster are all zeros (which denotes they are empty).
  105. """
  106. return self.get_from_fat() == 0x00
  107. def set_in_fat(self, value: int) -> None:
  108. """
  109. Sets cluster in FAT to certain value.
  110. Firstly, we split the target value into 3 half bytes (max value is 0xfff).
  111. Then we could encounter two situations:
  112. 1. if the cluster index (indexed from zero) is even, we set the full byte computed by
  113. self.cluster_id_to_logical_position_in_bits and the second half of the consequent byte.
  114. Order of half bytes is 2, 1, 3.
  115. 2. if the cluster index is odd, we set the first half of the computed byte and the full consequent byte.
  116. Order of half bytes is 1, 3, 2.
  117. """
  118. # value must fit into number of bits of the fat (12, 16 or 32)
  119. assert value <= (1 << self.boot_sector_state.fatfs_type) - 1
  120. half_bytes = split_by_half_byte_12_bit_little_endian(value)
  121. bin_img_: bytearray = self.boot_sector_state.binary_image
  122. if self.boot_sector_state.fatfs_type == FAT12:
  123. assert merge_by_half_byte_12_bit_little_endian(*half_bytes) == value
  124. if self.fat_cluster_address % 8 == 0:
  125. # even block
  126. bin_img_[self.real_cluster_address] = build_byte(half_bytes[1], half_bytes[0])
  127. self._set_right_half_byte(self.real_cluster_address + 1, half_bytes[2])
  128. elif self.fat_cluster_address % 8 != 0:
  129. # odd block
  130. self._set_left_half_byte(self.real_cluster_address, half_bytes[0])
  131. bin_img_[self.real_cluster_address + 1] = build_byte(half_bytes[2], half_bytes[1])
  132. elif self.boot_sector_state.fatfs_type == FAT16:
  133. bin_img_[self.real_cluster_address:self.real_cluster_address + 2] = Int16ul.build(value)
  134. assert self.get_from_fat() == value
  135. @property
  136. def is_root(self) -> bool:
  137. return self.id == Cluster.ROOT_BLOCK_ID
  138. def allocate_cluster(self) -> None:
  139. """
  140. This method sets bits in FAT table to `allocated` and clean the corresponding sector(s)
  141. """
  142. self.set_in_fat(self.ALLOCATED_BLOCK_SWITCH[self.boot_sector_state.fatfs_type])
  143. cluster_start = self.cluster_data_address
  144. dir_size = get_dir_size(self.is_root, self.boot_sector_state)
  145. cluster_end = cluster_start + dir_size
  146. self.boot_sector_state.binary_image[cluster_start:cluster_end] = dir_size * EMPTY_BYTE