fat.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. # SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
  2. # SPDX-License-Identifier: Apache-2.0
  3. from typing import List, Optional
  4. from .cluster import Cluster
  5. from .exceptions import NoFreeClusterException
  6. from .fatfs_state import BootSectorState
  7. class FAT:
  8. """
  9. The FAT represents the FAT region in file system. It is responsible for storing clusters
  10. and chaining them in case we need to extend file or directory to more clusters.
  11. """
  12. def allocate_root_dir(self) -> None:
  13. """
  14. The root directory is implicitly created with the FatFS,
  15. its block is on the index 1 (second index) and is allocated implicitly.
  16. """
  17. self.clusters[Cluster.ROOT_BLOCK_ID].allocate_cluster()
  18. def __init__(self, boot_sector_state: BootSectorState, init_: bool) -> None:
  19. self._first_free_cluster_id = 1
  20. self.boot_sector_state = boot_sector_state
  21. self.clusters: List[Cluster] = [Cluster(cluster_id=i,
  22. boot_sector_state=self.boot_sector_state,
  23. init_=init_) for i in range(self.boot_sector_state.clusters)]
  24. if init_:
  25. self.allocate_root_dir()
  26. def get_cluster_value(self, cluster_id_: int) -> int:
  27. """
  28. The method retrieves the values of the FAT memory block.
  29. E.g. in case of FAT12:
  30. 00000000: F8 FF FF 55 05 00 00 00 00 00 00 00 00 00 00 00
  31. The reserved value is 0xFF8, the value of first cluster if 0xFFF, thus is last in chain,
  32. and the value of the second cluster is 0x555, so refers to the cluster number 0x555.
  33. """
  34. fat_cluster_value_: int = self.clusters[cluster_id_].get_from_fat()
  35. return fat_cluster_value_
  36. def is_cluster_last(self, cluster_id_: int) -> bool:
  37. """
  38. Checks if the cluster is last in its cluster chain. If the value of the cluster is
  39. 0xFFF for FAT12, 0xFFFF for FAT16 or 0xFFFFFFFF for FAT32, the cluster is the last.
  40. """
  41. value_ = self.get_cluster_value(cluster_id_)
  42. is_cluster_last_: bool = value_ == (1 << self.boot_sector_state.fatfs_type) - 1
  43. return is_cluster_last_
  44. def get_chained_content(self, cluster_id_: int, size: Optional[int] = None) -> bytearray:
  45. """
  46. The purpose of the method is retrieving the content from chain of clusters when the FAT FS partition
  47. is analyzed. The file entry provides the reference to the first cluster, this method
  48. traverses linked list of clusters and append partial results to the content.
  49. """
  50. binary_image: bytearray = self.boot_sector_state.binary_image
  51. data_address_ = Cluster.compute_cluster_data_address(self.boot_sector_state, cluster_id_)
  52. content_ = binary_image[data_address_: data_address_ + self.boot_sector_state.sector_size]
  53. while not self.is_cluster_last(cluster_id_):
  54. cluster_id_ = self.get_cluster_value(cluster_id_)
  55. data_address_ = Cluster.compute_cluster_data_address(self.boot_sector_state, cluster_id_)
  56. content_ += binary_image[data_address_: data_address_ + self.boot_sector_state.sector_size]
  57. # the size is None if the object is directory
  58. if size is None:
  59. return content_
  60. return content_[:size]
  61. def find_free_cluster(self) -> Cluster:
  62. """
  63. Returns the first free cluster and increments value of `self._first_free_cluster_id`.
  64. The method works only in context of creating a partition from scratch.
  65. In situations where the clusters are allocated and freed during the run of the program,
  66. might the method cause `Out of space` error despite there would be free clusters.
  67. """
  68. if self._first_free_cluster_id + 1 >= len(self.clusters):
  69. raise NoFreeClusterException('No free cluster available!')
  70. cluster = self.clusters[self._first_free_cluster_id + 1]
  71. if not cluster.is_empty:
  72. raise NoFreeClusterException('No free cluster available!')
  73. cluster.allocate_cluster()
  74. self._first_free_cluster_id += 1
  75. return cluster
  76. def allocate_chain(self, first_cluster: Cluster, size: int) -> None:
  77. """
  78. Allocates the linked list of clusters needed for the given file or directory.
  79. """
  80. current = first_cluster
  81. for _ in range(size - 1):
  82. free_cluster = self.find_free_cluster()
  83. current.next_cluster = free_cluster
  84. current.set_in_fat(free_cluster.id)
  85. current = free_cluster