diff --git a/manifest.py b/components/micropython/manifest.py similarity index 100% rename from manifest.py rename to components/micropython/manifest.py diff --git a/third_party/fatfsgen/README.md b/third_party/fatfsgen/README.md deleted file mode 100644 index 70ed6b1db6ab673f1798fcc668a40c7cc4a0e95f..0000000000000000000000000000000000000000 --- a/third_party/fatfsgen/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Vendored from esp-idf/components/fastfs at 213504238f77e01073f668e5e8f87e3b3cc02a8f . - -Can be removed once we update to ESP-IDF 5.x. diff --git a/third_party/fatfsgen/fatfs_utils/__init__.py b/third_party/fatfsgen/fatfs_utils/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/third_party/fatfsgen/fatfs_utils/boot_sector.py b/third_party/fatfsgen/fatfs_utils/boot_sector.py deleted file mode 100644 index 615dd065112d1b9ceaad1ec14df68e45ea7c9d6d..0000000000000000000000000000000000000000 --- a/third_party/fatfsgen/fatfs_utils/boot_sector.py +++ /dev/null @@ -1,168 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: Apache-2.0 -from inspect import getmembers, isroutine -from typing import Optional - -from construct import Const, Int8ul, Int16ul, Int32ul, PaddedString, Struct, core - -from .exceptions import InconsistentFATAttributes, NotInitialized -from .fatfs_state import BootSectorState -from .utils import (ALLOWED_SECTOR_SIZES, ALLOWED_SECTORS_PER_CLUSTER, EMPTY_BYTE, FAT32, FULL_BYTE, - SHORT_NAMES_ENCODING, FATDefaults, generate_4bytes_random, pad_string) - - -class BootSector: - """ - This class describes the first sector of the volume in the Reserved Region. - It contains data from BPB (BIOS Parameter Block) and BS (Boot sector). The fields of the BPB and BS are mixed in - the header of the physical boot sector. Fields with prefix BPB belongs to BPB block and with prefix BS - belongs to the actual boot sector. - - Please beware, that the name of class BootSector refer to data both from the boot sector and BPB. - ESP32 ignores fields with prefix "BS_"! Fields with prefix BPB_ are essential to read the filesystem. - """ - MAX_VOL_LAB_SIZE = 11 - MAX_OEM_NAME_SIZE = 8 - MAX_FS_TYPE_SIZE = 8 - - # the FAT specification defines 512 bytes for the boot sector header - BOOT_HEADER_SIZE = 512 - - BOOT_SECTOR_HEADER = Struct( - # this value reflects BS_jmpBoot used for ESP32 boot sector (any other accepted) - 'BS_jmpBoot' / Const(b'\xeb\xfe\x90'), - 'BS_OEMName' / PaddedString(MAX_OEM_NAME_SIZE, SHORT_NAMES_ENCODING), - 'BPB_BytsPerSec' / Int16ul, - 'BPB_SecPerClus' / Int8ul, - 'BPB_RsvdSecCnt' / Int16ul, - 'BPB_NumFATs' / Int8ul, - 'BPB_RootEntCnt' / Int16ul, - 'BPB_TotSec16' / Int16ul, # zero if the FAT type is 32, otherwise number of sectors - 'BPB_Media' / Int8ul, - 'BPB_FATSz16' / Int16ul, # for FAT32 always zero, for FAT12/FAT16 number of sectors per FAT - 'BPB_SecPerTrk' / Int16ul, - 'BPB_NumHeads' / Int16ul, - 'BPB_HiddSec' / Int32ul, - 'BPB_TotSec32' / Int32ul, # zero if the FAT type is 12/16, otherwise number of sectors - 'BS_DrvNum' / Const(b'\x80'), - 'BS_Reserved1' / Const(EMPTY_BYTE), - 'BS_BootSig' / Const(b'\x29'), - 'BS_VolID' / Int32ul, - 'BS_VolLab' / PaddedString(MAX_VOL_LAB_SIZE, SHORT_NAMES_ENCODING), - 'BS_FilSysType' / PaddedString(MAX_FS_TYPE_SIZE, SHORT_NAMES_ENCODING), - 'BS_EMPTY' / Const(448 * EMPTY_BYTE), - 'Signature_word' / Const(FATDefaults.SIGNATURE_WORD) - ) - assert BOOT_SECTOR_HEADER.sizeof() == BOOT_HEADER_SIZE - - def __init__(self, boot_sector_state: Optional[BootSectorState] = None) -> None: - self._parsed_header: dict = {} - self.boot_sector_state: BootSectorState = boot_sector_state - - def generate_boot_sector(self) -> None: - boot_sector_state: BootSectorState = self.boot_sector_state - if boot_sector_state is None: - raise NotInitialized('The BootSectorState instance is not initialized!') - volume_uuid = generate_4bytes_random() - pad_header: bytes = (boot_sector_state.sector_size - BootSector.BOOT_HEADER_SIZE) * EMPTY_BYTE - data_content: bytes = boot_sector_state.data_sectors * boot_sector_state.sector_size * FULL_BYTE - root_dir_content: bytes = boot_sector_state.root_dir_sectors_cnt * boot_sector_state.sector_size * EMPTY_BYTE - fat_tables_content: bytes = (boot_sector_state.sectors_per_fat_cnt - * boot_sector_state.fat_tables_cnt - * boot_sector_state.sector_size - * EMPTY_BYTE) - self.boot_sector_state.binary_image = ( - BootSector.BOOT_SECTOR_HEADER.build( - dict(BS_OEMName=pad_string(boot_sector_state.oem_name, size=BootSector.MAX_OEM_NAME_SIZE), - BPB_BytsPerSec=boot_sector_state.sector_size, - BPB_SecPerClus=boot_sector_state.sectors_per_cluster, - BPB_RsvdSecCnt=boot_sector_state.reserved_sectors_cnt, - BPB_NumFATs=boot_sector_state.fat_tables_cnt, - BPB_RootEntCnt=boot_sector_state.entries_root_count, - # if fat type is 12 or 16 BPB_TotSec16 is filled and BPB_TotSec32 is 0x00 and vice versa - BPB_TotSec16=0x00 if boot_sector_state.fatfs_type == FAT32 else boot_sector_state.sectors_count, - BPB_Media=boot_sector_state.media_type, - BPB_FATSz16=boot_sector_state.sectors_per_fat_cnt, - BPB_SecPerTrk=boot_sector_state.sec_per_track, - BPB_NumHeads=boot_sector_state.num_heads, - BPB_HiddSec=boot_sector_state.hidden_sectors, - BPB_TotSec32=boot_sector_state.sectors_count if boot_sector_state.fatfs_type == FAT32 else 0x00, - BS_VolID=volume_uuid, - BS_VolLab=pad_string(boot_sector_state.volume_label, - size=BootSector.MAX_VOL_LAB_SIZE), - BS_FilSysType=pad_string(boot_sector_state.file_sys_type, - size=BootSector.MAX_FS_TYPE_SIZE) - ) - ) + pad_header + fat_tables_content + root_dir_content + data_content - ) - - def parse_boot_sector(self, binary_data: bytes) -> None: - """ - Checks the validity of the boot sector and derives the metadata from boot sector to the structured shape. - """ - try: - self._parsed_header = BootSector.BOOT_SECTOR_HEADER.parse(binary_data) - except core.StreamError: - raise NotInitialized('The boot sector header is not parsed successfully!') - - if self._parsed_header['BPB_TotSec16'] != 0x00: - sectors_count_: int = self._parsed_header['BPB_TotSec16'] - elif self._parsed_header['BPB_TotSec32'] != 0x00: - # uncomment for FAT32 implementation - # sectors_count_ = self._parsed_header['BPB_TotSec32'] - # possible_fat_types = [FAT32] - assert self._parsed_header['BPB_TotSec16'] == 0 - raise NotImplementedError('FAT32 not implemented!') - else: - raise InconsistentFATAttributes('The number of FS sectors cannot be zero!') - - if self._parsed_header['BPB_BytsPerSec'] not in ALLOWED_SECTOR_SIZES: - raise InconsistentFATAttributes(f'The number of bytes ' - f"per sector is {self._parsed_header['BPB_BytsPerSec']}! " - f'The accepted values are {ALLOWED_SECTOR_SIZES}') - if self._parsed_header['BPB_SecPerClus'] not in ALLOWED_SECTORS_PER_CLUSTER: - raise InconsistentFATAttributes(f'The number of sectors per cluster ' - f"is {self._parsed_header['BPB_SecPerClus']}" - f'The accepted values are {ALLOWED_SECTORS_PER_CLUSTER}') - - total_root_bytes: int = self._parsed_header['BPB_RootEntCnt'] * FATDefaults.ENTRY_SIZE - root_dir_sectors_cnt_: int = total_root_bytes // self._parsed_header['BPB_BytsPerSec'] - self.boot_sector_state = BootSectorState(oem_name=self._parsed_header['BS_OEMName'], - sector_size=self._parsed_header['BPB_BytsPerSec'], - sectors_per_cluster=self._parsed_header['BPB_SecPerClus'], - reserved_sectors_cnt=self._parsed_header['BPB_RsvdSecCnt'], - fat_tables_cnt=self._parsed_header['BPB_NumFATs'], - root_dir_sectors_cnt=root_dir_sectors_cnt_, - sectors_count=sectors_count_, - media_type=self._parsed_header['BPB_Media'], - sec_per_track=self._parsed_header['BPB_SecPerTrk'], - num_heads=self._parsed_header['BPB_NumHeads'], - hidden_sectors=self._parsed_header['BPB_HiddSec'], - volume_label=self._parsed_header['BS_VolLab'], - file_sys_type=self._parsed_header['BS_FilSysType'], - volume_uuid=self._parsed_header['BS_VolID']) - self.boot_sector_state.binary_image = binary_data - assert self.boot_sector_state.file_sys_type in (f'FAT{self.boot_sector_state.fatfs_type} ', 'FAT ') - - def __str__(self) -> str: - """ - FATFS properties parser (internal helper tool for fatfsgen.py/fatfsparse.py) - Provides all the properties of given FATFS instance by parsing its boot sector (returns formatted string) - """ - - if self._parsed_header == {}: - return 'Boot sector is not initialized!' - res: str = 'FATFS properties:\n' - for member in getmembers(self.boot_sector_state, lambda a: not (isroutine(a))): - prop_ = getattr(self.boot_sector_state, member[0]) - if isinstance(prop_, int) or isinstance(prop_, str) and not member[0].startswith('_'): - res += f'{member[0]}: {prop_}\n' - return res - - @property - def binary_image(self) -> bytes: - # when BootSector is not instantiated, self.boot_sector_state might be None - if self.boot_sector_state is None or len(self.boot_sector_state.binary_image) == 0: - raise NotInitialized('Boot sector is not initialized!') - bin_image_: bytes = self.boot_sector_state.binary_image - return bin_image_ diff --git a/third_party/fatfsgen/fatfs_utils/cluster.py b/third_party/fatfsgen/fatfs_utils/cluster.py deleted file mode 100644 index ced9b1f5c3b1b5c9a2d70358d7895e700eab73cc..0000000000000000000000000000000000000000 --- a/third_party/fatfsgen/fatfs_utils/cluster.py +++ /dev/null @@ -1,213 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: Apache-2.0 - -from typing import Dict, Optional - -from construct import Int16ul - -from .fatfs_state import BootSectorState -from .utils import (EMPTY_BYTE, FAT12, FAT16, build_byte, merge_by_half_byte_12_bit_little_endian, - split_by_half_byte_12_bit_little_endian) - - -def get_dir_size(is_root: bool, boot_sector: BootSectorState) -> int: - dir_size_: int = boot_sector.root_dir_sectors_cnt * boot_sector.sector_size if is_root else boot_sector.sector_size - return dir_size_ - - -class Cluster: - """ - class Cluster handles values in FAT table and allocates sectors in data region. - """ - RESERVED_BLOCK_ID: int = 0 - ROOT_BLOCK_ID: int = 1 - ALLOCATED_BLOCK_FAT12: int = 0xFFF - ALLOCATED_BLOCK_FAT16: int = 0xFFFF - ALLOCATED_BLOCK_SWITCH = {FAT12: ALLOCATED_BLOCK_FAT12, FAT16: ALLOCATED_BLOCK_FAT16} - INITIAL_BLOCK_SWITCH: Dict[int, int] = {FAT12: 0xFF8, FAT16: 0xFFF8} - - def __init__(self, - cluster_id: int, - boot_sector_state: BootSectorState, - init_: bool) -> None: - """ - Initially, if init_ is False, the cluster is virtual and is not allocated (doesn't do changes in the FAT). - :param cluster_id: the cluster ID - a key value linking the file's cluster, - the corresponding physical cluster (data region) and the FAT table cluster. - :param boot_sector_state: auxiliary structure holding the file-system's metadata - :param init_: True for allocation the cluster on instantiation, otherwise False. - :returns: None - """ - self.id: int = cluster_id - self.boot_sector_state: BootSectorState = boot_sector_state - - self._next_cluster = None # type: Optional[Cluster] - # First cluster in FAT is reserved, low 8 bits contains BPB_Media and the rest is filled with 1 - # e.g. the esp32 media type is 0xF8 thus the FAT[0] = 0xFF8 for FAT12, 0xFFF8 for FAT16 - if self.id == Cluster.RESERVED_BLOCK_ID and init_: - self.set_in_fat(self.INITIAL_BLOCK_SWITCH[self.boot_sector_state.fatfs_type]) - return - self.cluster_data_address: int = self._compute_cluster_data_address() - assert self.cluster_data_address - - @property - def next_cluster(self): # type: () -> Optional[Cluster] - return self._next_cluster - - @next_cluster.setter - def next_cluster(self, value): # type: (Optional[Cluster]) -> None - self._next_cluster = value - - def _cluster_id_to_fat_position_in_bits(self, _id: int) -> int: - """ - This private method calculates the position of the memory block (cluster) in the FAT table. - - :param _id: the cluster ID - a key value linking the file's cluster, - the corresponding physical cluster (data region) and the FAT table cluster. - :returns: bit offset of the cluster in FAT - e.g.: - 00003000: 42 65 00 2E 00 74 00 78 00 74 00 0F 00 43 FF FF - - For FAT12 the third cluster has value = 0x02E and ID = 2. - Its bit-address is 24 (24 bits preceding, 0-indexed), because 0x2E starts at the bit-offset 24. - """ - logical_position_: int = self.boot_sector_state.fatfs_type * _id - return logical_position_ - - @staticmethod - def compute_cluster_data_address(boot_sector_state: BootSectorState, id_: int) -> int: - """ - This method translates the id of the cluster to the address in data region. - - :param boot_sector_state: the class with FS shared data - :param id_: id of the cluster - :returns: integer denoting the address of the cluster in the data region - """ - data_address_: int = boot_sector_state.root_directory_start - if not id_ == Cluster.ROOT_BLOCK_ID: - # the first data cluster id is 2 (we have to subtract reserved cluster and cluster for root) - data_address_ = boot_sector_state.sector_size * (id_ - 2) + boot_sector_state.data_region_start - return data_address_ - - def _compute_cluster_data_address(self) -> int: - return self.compute_cluster_data_address(self.boot_sector_state, self.id) - - @property - def fat_cluster_address(self) -> int: - """Determines how many bits precede the first bit of the cluster in FAT""" - return self._cluster_id_to_fat_position_in_bits(self.id) - - @property - def real_cluster_address(self) -> int: - """ - The property method computes the real address of the cluster in the FAT region. Result is simply - address of the cluster in fat + fat table address. - """ - cluster_address: int = self.boot_sector_state.fat_table_start_address + self.fat_cluster_address // 8 - return cluster_address - - def get_from_fat(self) -> int: - """ - Calculating the value in the FAT block, that denotes if the block is full, empty, or chained to other block. - - For FAT12 is the block stored in one and half byte. If the order of the block is even the first byte and second - half of the second byte belongs to the block. First half of the second byte and the third byte belongs to - the second block. - - e.g. b'\xff\x0f\x00' stores two blocks. First of them is evenly ordered (index 0) and is set to 0xfff, - that means full block that is final in chain of blocks - and second block is set to 0x000 that means empty block. - - three bytes - AB XC YZ - stores two blocks - CAB YZX - """ - address_: int = self.real_cluster_address - bin_img_: bytearray = self.boot_sector_state.binary_image - if self.boot_sector_state.fatfs_type == FAT12: - if self.fat_cluster_address % 8 == 0: - # even block - return bin_img_[self.real_cluster_address] | ((bin_img_[self.real_cluster_address + 1] & 0x0F) << 8) - # odd block - return ((bin_img_[self.real_cluster_address] & 0xF0) >> 4) | (bin_img_[self.real_cluster_address + 1] << 4) - if self.boot_sector_state.fatfs_type == FAT16: - return int.from_bytes(bin_img_[address_:address_ + 2], byteorder='little') - raise NotImplementedError('Only valid fatfs types are FAT12 and FAT16.') - - @property - def is_empty(self) -> bool: - """ - The property method takes a look into the binary array and checks if the bytes ordered by little endian - and relates to the current cluster are all zeros (which denotes they are empty). - """ - return self.get_from_fat() == 0x00 - - def set_in_fat(self, value: int) -> None: - """ - Sets cluster in FAT to certain value. - Firstly, we split the target value into 3 half bytes (max value is 0xfff). - Then we could encounter two situations: - 1. if the cluster index (indexed from zero) is even, we set the full byte computed by - self.cluster_id_to_logical_position_in_bits and the second half of the consequent byte. - Order of half bytes is 2, 1, 3. - - 2. if the cluster index is odd, we set the first half of the computed byte and the full consequent byte. - Order of half bytes is 1, 3, 2. - """ - - def _set_msb_half_byte(address: int, value_: int) -> None: - """ - Sets 4 most significant bits (msb half-byte) of 'boot_sector_state.binary_image' at given - 'address' to 'value_' (size of variable 'value_' is half byte) - - If a byte contents is 0b11110000, the msb half-byte would be 0b1111 - """ - self.boot_sector_state.binary_image[address] &= 0x0f - self.boot_sector_state.binary_image[address] |= value_ << 4 - - def _set_lsb_half_byte(address: int, value_: int) -> None: - """ - Sets 4 least significant bits (lsb half-byte) of 'boot_sector_state.binary_image' at given - 'address' to 'value_' (size of variable 'value_' is half byte) - - If a byte contents is 0b11110000, the lsb half-byte would be 0b0000 - """ - self.boot_sector_state.binary_image[address] &= 0xf0 - self.boot_sector_state.binary_image[address] |= value_ - - # value must fit into number of bits of the fat (12, 16 or 32) - assert value <= (1 << self.boot_sector_state.fatfs_type) - 1 - half_bytes = split_by_half_byte_12_bit_little_endian(value) - bin_img_: bytearray = self.boot_sector_state.binary_image - - if self.boot_sector_state.fatfs_type == FAT12: - assert merge_by_half_byte_12_bit_little_endian(*half_bytes) == value - if self.fat_cluster_address % 8 == 0: - # even block - bin_img_[self.real_cluster_address] = build_byte(half_bytes[1], half_bytes[0]) - _set_lsb_half_byte(self.real_cluster_address + 1, half_bytes[2]) - elif self.fat_cluster_address % 8 != 0: - # odd block - _set_msb_half_byte(self.real_cluster_address, half_bytes[0]) - bin_img_[self.real_cluster_address + 1] = build_byte(half_bytes[2], half_bytes[1]) - elif self.boot_sector_state.fatfs_type == FAT16: - bin_img_[self.real_cluster_address:self.real_cluster_address + 2] = Int16ul.build(value) - assert self.get_from_fat() == value - - @property - def is_root(self) -> bool: - """ - The FAT12/FAT16 contains only one root directory, - the root directory allocates the first cluster with the ID `ROOT_BLOCK_ID`. - The method checks if the cluster belongs to the root directory. - """ - return self.id == Cluster.ROOT_BLOCK_ID - - def allocate_cluster(self) -> None: - """ - This method sets bits in FAT table to `allocated` and clean the corresponding sector(s) - """ - self.set_in_fat(self.ALLOCATED_BLOCK_SWITCH[self.boot_sector_state.fatfs_type]) - - cluster_start = self.cluster_data_address - dir_size = get_dir_size(self.is_root, self.boot_sector_state) - cluster_end = cluster_start + dir_size - self.boot_sector_state.binary_image[cluster_start:cluster_end] = dir_size * EMPTY_BYTE diff --git a/third_party/fatfsgen/fatfs_utils/entry.py b/third_party/fatfsgen/fatfs_utils/entry.py deleted file mode 100644 index adc5e3ea7f67688cc96adbad5755734a23d523b8..0000000000000000000000000000000000000000 --- a/third_party/fatfsgen/fatfs_utils/entry.py +++ /dev/null @@ -1,253 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: Apache-2.0 - -from typing import List, Optional, Union - -from construct import Const, Int8ul, Int16ul, Int32ul, PaddedString, Struct - -from .exceptions import LowerCaseException, TooLongNameException -from .fatfs_state import FATFSState -from .utils import (DATETIME, EMPTY_BYTE, FATFS_INCEPTION, MAX_EXT_SIZE, MAX_NAME_SIZE, SHORT_NAMES_ENCODING, - FATDefaults, build_date_entry, build_time_entry, is_valid_fatfs_name, pad_string) - - -class Entry: - """ - The class Entry represents entry of the directory. - """ - ATTR_READ_ONLY: int = 0x01 - ATTR_HIDDEN: int = 0x02 - ATTR_SYSTEM: int = 0x04 - ATTR_VOLUME_ID: int = 0x08 - ATTR_DIRECTORY: int = 0x10 # directory - ATTR_ARCHIVE: int = 0x20 # file - ATTR_LONG_NAME: int = ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID - - # indexes in the entry structure and sizes in bytes, not in characters (encoded using 2 bytes for lfn) - LDIR_Name1_IDX: int = 1 - LDIR_Name1_SIZE: int = 5 - LDIR_Name2_IDX: int = 14 - LDIR_Name2_SIZE: int = 6 - LDIR_Name3_IDX: int = 28 - LDIR_Name3_SIZE: int = 2 - - # short entry in long file names - LDIR_DIR_NTRES: int = 0x18 - # one entry can hold 13 characters with size 2 bytes distributed in three regions of the 32 bytes entry - CHARS_PER_ENTRY: int = LDIR_Name1_SIZE + LDIR_Name2_SIZE + LDIR_Name3_SIZE - - # the last 16 bytes record in the LFN entry has first byte masked with the following value - LAST_RECORD_LFN_ENTRY: int = 0x40 - SHORT_ENTRY: int = -1 - # this value is used for short-like entry but with accepted lower case - SHORT_ENTRY_LN: int = 0 - - # The 1st January 1980 00:00:00 - DEFAULT_DATE: DATETIME = (FATFS_INCEPTION.year, FATFS_INCEPTION.month, FATFS_INCEPTION.day) - DEFAULT_TIME: DATETIME = (FATFS_INCEPTION.hour, FATFS_INCEPTION.minute, FATFS_INCEPTION.second) - - ENTRY_FORMAT_SHORT_NAME = Struct( - 'DIR_Name' / PaddedString(MAX_NAME_SIZE, SHORT_NAMES_ENCODING), - 'DIR_Name_ext' / PaddedString(MAX_EXT_SIZE, SHORT_NAMES_ENCODING), - 'DIR_Attr' / Int8ul, - 'DIR_NTRes' / Int8ul, # this tagged for lfn (0x00 for short entry in lfn, 0x18 for short name) - 'DIR_CrtTimeTenth' / Const(EMPTY_BYTE), # ignored by esp-idf fatfs library - 'DIR_CrtTime' / Int16ul, # ignored by esp-idf fatfs library - 'DIR_CrtDate' / Int16ul, # ignored by esp-idf fatfs library - 'DIR_LstAccDate' / Int16ul, # must be same as DIR_WrtDate - 'DIR_FstClusHI' / Const(2 * EMPTY_BYTE), - 'DIR_WrtTime' / Int16ul, - 'DIR_WrtDate' / Int16ul, - 'DIR_FstClusLO' / Int16ul, - 'DIR_FileSize' / Int32ul, - ) - - def __init__(self, - entry_id: int, - parent_dir_entries_address: int, - fatfs_state: FATFSState) -> None: - self.fatfs_state: FATFSState = fatfs_state - self.id: int = entry_id - self.entry_address: int = parent_dir_entries_address + self.id * FATDefaults.ENTRY_SIZE - self._is_alias: bool = False - self._is_empty: bool = True - - @staticmethod - def get_cluster_id(obj_: dict) -> int: - cluster_id_: int = obj_['DIR_FstClusLO'] - return cluster_id_ - - @property - def is_empty(self) -> bool: - return self._is_empty - - @staticmethod - def _parse_entry(entry_bytearray: Union[bytearray, bytes]) -> dict: - entry_: dict = Entry.ENTRY_FORMAT_SHORT_NAME.parse(entry_bytearray) - return entry_ - - @staticmethod - def _build_entry(**kwargs) -> bytes: # type: ignore - entry_: bytes = Entry.ENTRY_FORMAT_SHORT_NAME.build(dict(**kwargs)) - return entry_ - - @staticmethod - def _build_entry_long(names: List[bytes], checksum: int, order: int, is_last: bool) -> bytes: - """ - Long entry starts with 1 bytes of the order, if the entry is the last in the chain it is or-masked with 0x40, - otherwise is without change (or masked with 0x00). The following example shows 3 entries: - first two (0x2000-0x2040) are long in the reverse order and the last one (0x2040-0x2060) is short. - The entries define file name "thisisverylongfilenama.txt". - - 00002000: 42 67 00 66 00 69 00 6C 00 65 00 0F 00 43 6E 00 Bg.f.i.l.e...Cn. - 00002010: 61 00 6D 00 61 00 2E 00 74 00 00 00 78 00 74 00 a.m.a...t...x.t. - 00002020: 01 74 00 68 00 69 00 73 00 69 00 0F 00 43 73 00 .t.h.i.s.i...Cs. - 00002030: 76 00 65 00 72 00 79 00 6C 00 00 00 6F 00 6E 00 v.e.r.y.l...o.n. - 00002040: 54 48 49 53 49 53 7E 31 54 58 54 20 00 00 00 00 THISIS~1TXT..... - 00002050: 21 00 00 00 00 00 00 00 21 00 02 00 15 00 00 00 !.......!....... - """ - order |= (Entry.LAST_RECORD_LFN_ENTRY if is_last else 0x00) - long_entry: bytes = (Int8ul.build(order) + # order of the long name entry (possibly masked with 0x40) - names[0] + # first 5 characters (10 bytes) of the name part - Int8ul.build(Entry.ATTR_LONG_NAME) + # one byte entity type ATTR_LONG_NAME - Int8ul.build(0) + # one byte of zeros - Int8ul.build(checksum) + # lfn_checksum defined in utils.py - names[1] + # next 6 characters (12 bytes) of the name part - Int16ul.build(0) + # 2 bytes of zeros - names[2]) # last 2 characters (4 bytes) of the name part - return long_entry - - @staticmethod - def parse_entry_long(entry_bytes_: bytes, my_check: int) -> dict: - order_ = Int8ul.parse(entry_bytes_[0:1]) - names0 = entry_bytes_[1:11] - if Int8ul.parse(entry_bytes_[12:13]) != 0 or Int16ul.parse(entry_bytes_[26:28]) != 0 or Int8ul.parse(entry_bytes_[11:12]) != 15: - return {} - if Int8ul.parse(entry_bytes_[13:14]) != my_check: - return {} - names1 = entry_bytes_[14:26] - names2 = entry_bytes_[28:32] - return { - 'order': order_, - 'name1': names0, - 'name2': names1, - 'name3': names2, - 'is_last': bool(order_ & Entry.LAST_RECORD_LFN_ENTRY == Entry.LAST_RECORD_LFN_ENTRY) - } - - @property - def entry_bytes(self) -> bytes: - """ - :returns: Bytes defining the entry belonging to the given instance. - """ - start_: int = self.entry_address - entry_: bytes = self.fatfs_state.binary_image[start_: start_ + FATDefaults.ENTRY_SIZE] - return entry_ - - @entry_bytes.setter - def entry_bytes(self, value: bytes) -> None: - """ - :param value: new content of the entry - :returns: None - - The setter sets the content of the entry in bytes. - """ - self.fatfs_state.binary_image[self.entry_address: self.entry_address + FATDefaults.ENTRY_SIZE] = value - - def _clean_entry(self) -> None: - self.entry_bytes: bytes = FATDefaults.ENTRY_SIZE * EMPTY_BYTE - - def allocate_entry(self, - first_cluster_id: int, - entity_name: str, - entity_type: int, - entity_extension: str = '', - size: int = 0, - date: DATETIME = DEFAULT_DATE, - time: DATETIME = DEFAULT_TIME, - lfn_order: int = SHORT_ENTRY, - lfn_names: Optional[List[bytes]] = None, - lfn_checksum_: int = 0, - fits_short: bool = False, - lfn_is_last: bool = False) -> None: - """ - :param first_cluster_id: id of the first data cluster for given entry - :param entity_name: name recorded in the entry - :param entity_extension: extension recorded in the entry - :param size: size of the content of the file - :param date: denotes year (actual year minus 1980), month number day of the month (minimal valid is (0, 1, 1)) - :param time: denotes hour, minute and second with granularity 2 seconds (sec // 2) - :param entity_type: type of the entity (file [0x20] or directory [0x10]) - :param lfn_order: if long names support is enabled, defines order in long names entries sequence (-1 for short) - :param lfn_names: if the entry is dedicated for long names the lfn_names contains - LDIR_Name1, LDIR_Name2 and LDIR_Name3 in this order - :param lfn_checksum_: use only for long file names, checksum calculated lfn_checksum function - :param fits_short: determines if the name fits in 8.3 filename - :param lfn_is_last: determines if the long file name entry is holds last part of the name, - thus its address is first in the physical order - :returns: None - - :raises LowerCaseException: In case when long_names_enabled is set to False and filename exceeds 8 chars - for name or 3 chars for extension the exception is raised - :raises TooLongNameException: When long_names_enabled is set to False and name doesn't fit to 8.3 filename - an exception is raised - """ - valid_full_name: bool = is_valid_fatfs_name(entity_name) and is_valid_fatfs_name(entity_extension) - if not (valid_full_name or lfn_order >= 0): - raise LowerCaseException('Lower case is not supported in short name entry, use upper case.') - - if self.fatfs_state.use_default_datetime: - date = self.DEFAULT_DATE - time = self.DEFAULT_TIME - - # clean entry before allocation - self._clean_entry() - self._is_empty = False - - object_name = entity_name.upper() if not self.fatfs_state.long_names_enabled else entity_name - object_extension = entity_extension.upper() if not self.fatfs_state.long_names_enabled else entity_extension - - exceeds_short_name: bool = len(object_name) > MAX_NAME_SIZE or len(object_extension) > MAX_EXT_SIZE - if not self.fatfs_state.long_names_enabled and exceeds_short_name: - raise TooLongNameException( - 'Maximal length of the object name ({}) is {} characters and {} characters for extension!'.format( - object_name, MAX_NAME_SIZE, MAX_EXT_SIZE - ) - ) - - start_address = self.entry_address - end_address = start_address + FATDefaults.ENTRY_SIZE - if lfn_order in (self.SHORT_ENTRY, self.SHORT_ENTRY_LN): - date_entry_: int = build_date_entry(*date) - time_entry: int = build_time_entry(*time) - self.fatfs_state.binary_image[start_address: end_address] = self._build_entry( - DIR_Name=pad_string(object_name, size=MAX_NAME_SIZE), - DIR_Name_ext=pad_string(object_extension, size=MAX_EXT_SIZE), - DIR_Attr=entity_type, - DIR_NTRes=0x00 if (not self.fatfs_state.long_names_enabled) or (not fits_short) else 0x18, - DIR_FstClusLO=first_cluster_id, - DIR_FileSize=size, - DIR_CrtDate=date_entry_, # ignored by esp-idf fatfs library - DIR_LstAccDate=date_entry_, # must be same as DIR_WrtDate - DIR_WrtDate=date_entry_, - DIR_CrtTime=time_entry, # ignored by esp-idf fatfs library - DIR_WrtTime=time_entry - ) - else: - assert lfn_names is not None - self.fatfs_state.binary_image[start_address: end_address] = self._build_entry_long(lfn_names, - lfn_checksum_, - lfn_order, - lfn_is_last) - - def update_content_size(self, content_size: int) -> None: - """ - :param content_size: the new size of the file content in bytes - :returns: None - - This method parses the binary entry to the construct structure, updates the content size of the file - and builds new binary entry. - """ - parsed_entry = self._parse_entry(self.entry_bytes) - parsed_entry.DIR_FileSize = content_size # type: ignore - self.entry_bytes = Entry.ENTRY_FORMAT_SHORT_NAME.build(parsed_entry) diff --git a/third_party/fatfsgen/fatfs_utils/exceptions.py b/third_party/fatfsgen/fatfs_utils/exceptions.py deleted file mode 100644 index a3a27df5d6eeb60b3034dfdea8a822c165db252f..0000000000000000000000000000000000000000 --- a/third_party/fatfsgen/fatfs_utils/exceptions.py +++ /dev/null @@ -1,54 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: Apache-2.0 - -class WriteDirectoryException(Exception): - """ - Exception is raised when the user tries to write the content into the directory instead of file - """ - pass - - -class NoFreeClusterException(Exception): - """ - Exception is raised when the user tries allocate cluster but no free one is available - """ - pass - - -class LowerCaseException(Exception): - """ - Exception is raised when the user tries to write file or directory with lower case - """ - pass - - -class TooLongNameException(Exception): - """ - Exception is raised when long name support is not enabled and user tries to write file longer then allowed - """ - pass - - -class NotInitialized(Exception): - """ - Exception is raised when the user tries to access not initialized property - """ - pass - - -class WLNotInitialized(Exception): - """ - Exception is raised when the user tries to write fatfs not initialized with wear levelling - """ - pass - - -class FatalError(Exception): - pass - - -class InconsistentFATAttributes(Exception): - """ - Caused by e.g. wrong number of clusters for given FAT type - """ - pass diff --git a/third_party/fatfsgen/fatfs_utils/fat.py b/third_party/fatfsgen/fatfs_utils/fat.py deleted file mode 100644 index 396075dd08773c1b3271f9bee344803a59678307..0000000000000000000000000000000000000000 --- a/third_party/fatfsgen/fatfs_utils/fat.py +++ /dev/null @@ -1,100 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: Apache-2.0 - -from typing import List, Optional - -from .cluster import Cluster -from .exceptions import NoFreeClusterException -from .fatfs_state import BootSectorState - - -class FAT: - """ - The FAT represents the FAT region in file system. It is responsible for storing clusters - and chaining them in case we need to extend file or directory to more clusters. - """ - - def allocate_root_dir(self) -> None: - """ - The root directory is implicitly created with the FatFS, - its block is on the index 1 (second index) and is allocated implicitly. - """ - self.clusters[Cluster.ROOT_BLOCK_ID].allocate_cluster() - - def __init__(self, boot_sector_state: BootSectorState, init_: bool) -> None: - self._first_free_cluster_id = 1 - self.boot_sector_state = boot_sector_state - self.clusters: List[Cluster] = [Cluster(cluster_id=i, - boot_sector_state=self.boot_sector_state, - init_=init_) for i in range(self.boot_sector_state.clusters)] - if init_: - self.allocate_root_dir() - - def get_cluster_value(self, cluster_id_: int) -> int: - """ - The method retrieves the values of the FAT memory block. - E.g. in case of FAT12: - 00000000: F8 FF FF 55 05 00 00 00 00 00 00 00 00 00 00 00 - - The reserved value is 0xFF8, the value of first cluster if 0xFFF, thus is last in chain, - and the value of the second cluster is 0x555, so refers to the cluster number 0x555. - """ - fat_cluster_value_: int = self.clusters[cluster_id_].get_from_fat() - return fat_cluster_value_ - - def is_cluster_last(self, cluster_id_: int) -> bool: - """ - Checks if the cluster is last in its cluster chain. If the value of the cluster is - 0xFFF for FAT12, 0xFFFF for FAT16 or 0xFFFFFFFF for FAT32, the cluster is the last. - """ - value_ = self.get_cluster_value(cluster_id_) - is_cluster_last_: bool = value_ == (1 << self.boot_sector_state.fatfs_type) - 1 - return is_cluster_last_ - - def get_chained_content(self, cluster_id_: int, size: Optional[int] = None) -> bytearray: - """ - The purpose of the method is retrieving the content from chain of clusters when the FAT FS partition - is analyzed. The file entry provides the reference to the first cluster, this method - traverses linked list of clusters and append partial results to the content. - """ - binary_image: bytearray = self.boot_sector_state.binary_image - - data_address_ = Cluster.compute_cluster_data_address(self.boot_sector_state, cluster_id_) - content_ = binary_image[data_address_: data_address_ + self.boot_sector_state.sector_size] - - while not self.is_cluster_last(cluster_id_): - cluster_id_ = self.get_cluster_value(cluster_id_) - data_address_ = Cluster.compute_cluster_data_address(self.boot_sector_state, cluster_id_) - content_ += binary_image[data_address_: data_address_ + self.boot_sector_state.sector_size] - # the size is None if the object is directory - if size is None: - return content_ - return content_[:size] - - def find_free_cluster(self) -> Cluster: - """ - Returns the first free cluster and increments value of `self._first_free_cluster_id`. - The method works only in context of creating a partition from scratch. - In situations where the clusters are allocated and freed during the run of the program, - might the method cause `Out of space` error despite there would be free clusters. - """ - - if self._first_free_cluster_id + 1 >= len(self.clusters): - raise NoFreeClusterException('No free cluster available!') - cluster = self.clusters[self._first_free_cluster_id + 1] - if not cluster.is_empty: - raise NoFreeClusterException('No free cluster available!') - cluster.allocate_cluster() - self._first_free_cluster_id += 1 - return cluster - - def allocate_chain(self, first_cluster: Cluster, size: int) -> None: - """ - Allocates the linked list of clusters needed for the given file or directory. - """ - current = first_cluster - for _ in range(size - 1): - free_cluster = self.find_free_cluster() - current.next_cluster = free_cluster - current.set_in_fat(free_cluster.id) - current = free_cluster diff --git a/third_party/fatfsgen/fatfs_utils/fatfs_parser.py b/third_party/fatfsgen/fatfs_utils/fatfs_parser.py deleted file mode 100644 index 66aea11dd3788190b2a3aac22712cdb9518a014f..0000000000000000000000000000000000000000 --- a/third_party/fatfsgen/fatfs_utils/fatfs_parser.py +++ /dev/null @@ -1,17 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: Apache-2.0 - -from .boot_sector import BootSector -from .utils import read_filesystem - - -class FATFSParser: - - def __init__(self, image_file_path: str, wl_support: bool = False) -> None: - if wl_support: - raise NotImplementedError('Parser is not implemented for WL yet.') - self.fatfs = read_filesystem(image_file_path) - - # when wl is not supported we expect boot sector to be the first - self.parsed_header = BootSector.BOOT_SECTOR_HEADER.parse(self.fatfs[:BootSector.BOOT_HEADER_SIZE]) - print(BootSector) diff --git a/third_party/fatfsgen/fatfs_utils/fatfs_state.py b/third_party/fatfsgen/fatfs_utils/fatfs_state.py deleted file mode 100644 index 22af7bfb0de2f352549ed8338a8f15129a9eafa5..0000000000000000000000000000000000000000 --- a/third_party/fatfsgen/fatfs_utils/fatfs_state.py +++ /dev/null @@ -1,170 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: Apache-2.0 - -from textwrap import dedent -from typing import Optional - -from .exceptions import InconsistentFATAttributes -from .utils import (ALLOWED_SECTOR_SIZES, FAT12, FAT12_MAX_CLUSTERS, FAT16, FAT16_MAX_CLUSTERS, - RESERVED_CLUSTERS_COUNT, FATDefaults, get_fat_sectors_count, get_fatfs_type, - get_non_data_sectors_cnt, number_of_clusters) - - -class FATFSState: - """ - The class represents the state and the configuration of the FATFS. - """ - - def __init__(self, - sector_size: int, - reserved_sectors_cnt: int, - root_dir_sectors_cnt: int, - size: int, - media_type: int, - sectors_per_cluster: int, - volume_label: str, - oem_name: str, - fat_tables_cnt: int, - sec_per_track: int, - num_heads: int, - hidden_sectors: int, - file_sys_type: str, - use_default_datetime: bool, - explicit_fat_type: Optional[int] = None, - long_names_enabled: bool = False): - self.boot_sector_state = BootSectorState(oem_name=oem_name, - sector_size=sector_size, - sectors_per_cluster=sectors_per_cluster, - reserved_sectors_cnt=reserved_sectors_cnt, - fat_tables_cnt=fat_tables_cnt, - root_dir_sectors_cnt=root_dir_sectors_cnt, - sectors_count=size // sector_size, - media_type=media_type, - sec_per_track=sec_per_track, - num_heads=num_heads, - hidden_sectors=hidden_sectors, - volume_label=volume_label, - file_sys_type=file_sys_type, - volume_uuid=-1) - - self._explicit_fat_type: Optional[int] = explicit_fat_type - self.long_names_enabled: bool = long_names_enabled - self.use_default_datetime: bool = use_default_datetime - - if (size // sector_size) * sectors_per_cluster in (FAT12_MAX_CLUSTERS, FAT16_MAX_CLUSTERS): - print('WARNING: It is not recommended to create FATFS with bounding ' - f'count of clusters: {FAT12_MAX_CLUSTERS} or {FAT16_MAX_CLUSTERS}') - self.check_fat_type() - - @property - def binary_image(self) -> bytearray: - return self.boot_sector_state.binary_image - - @binary_image.setter - def binary_image(self, value: bytearray) -> None: - self.boot_sector_state.binary_image = value - - def check_fat_type(self) -> None: - _type = self.boot_sector_state.fatfs_type - if self._explicit_fat_type is not None and self._explicit_fat_type != _type: - raise InconsistentFATAttributes(dedent( - f"""FAT type you specified is inconsistent with other attributes of the system. - The specified FATFS type: FAT{self._explicit_fat_type} - The actual FATFS type: FAT{_type}""")) - if _type not in (FAT12, FAT16): - raise NotImplementedError('FAT32 is currently not supported.') - - -class BootSectorState: - # pylint: disable=too-many-instance-attributes - def __init__(self, - oem_name: str, - sector_size: int, - sectors_per_cluster: int, - reserved_sectors_cnt: int, - fat_tables_cnt: int, - root_dir_sectors_cnt: int, - sectors_count: int, - media_type: int, - sec_per_track: int, - num_heads: int, - hidden_sectors: int, - volume_label: str, - file_sys_type: str, - volume_uuid: int = -1) -> None: - self.oem_name: str = oem_name - self.sector_size: int = sector_size - assert self.sector_size in ALLOWED_SECTOR_SIZES - self.sectors_per_cluster: int = sectors_per_cluster - self.reserved_sectors_cnt: int = reserved_sectors_cnt - self.fat_tables_cnt: int = fat_tables_cnt - self.root_dir_sectors_cnt: int = root_dir_sectors_cnt - self.sectors_count: int = sectors_count - self.media_type: int = media_type - self.sectors_per_fat_cnt = get_fat_sectors_count(self.size // self.sector_size, self.sector_size) - self.sec_per_track: int = sec_per_track - self.num_heads: int = num_heads - self.hidden_sectors: int = hidden_sectors - self.volume_label: str = volume_label - self.file_sys_type: str = file_sys_type - self.volume_uuid: int = volume_uuid - self._binary_image: bytearray = bytearray(b'') - - @property - def binary_image(self) -> bytearray: - return self._binary_image - - @binary_image.setter - def binary_image(self, value: bytearray) -> None: - self._binary_image = value - - @property - def size(self) -> int: - return self.sector_size * self.sectors_count - - @property - def data_region_start(self) -> int: - return self.non_data_sectors * self.sector_size - - @property - def fatfs_type(self) -> int: - # variable typed_fatfs_type must be explicitly typed to avoid mypy error - typed_fatfs_type: int = get_fatfs_type(self.clusters) - return typed_fatfs_type - - @property - def clusters(self) -> int: - """ - The actual number of clusters is calculated by `number_of_clusters`, - however, the initial two blocks of FAT are reserved (device type and root directory), - despite they don't refer to the data region. - Since that, two clusters are added to use the full potential of the FAT file system partition. - """ - clusters_cnt_: int = number_of_clusters(self.data_sectors, self.sectors_per_cluster) + RESERVED_CLUSTERS_COUNT - return clusters_cnt_ - - @property - def data_sectors(self) -> int: - # self.sector_size is checked in constructor if has one of allowed values (ALLOWED_SECTOR_SIZES) - return (self.size // self.sector_size) - self.non_data_sectors - - @property - def non_data_sectors(self) -> int: - non_data_sectors_: int = get_non_data_sectors_cnt(self.reserved_sectors_cnt, - self.sectors_per_fat_cnt, - self.root_dir_sectors_cnt) - return non_data_sectors_ - - @property - def fat_table_start_address(self) -> int: - return self.sector_size * self.reserved_sectors_cnt - - @property - def entries_root_count(self) -> int: - entries_root_count_: int = (self.root_dir_sectors_cnt * self.sector_size) // FATDefaults.ENTRY_SIZE - return entries_root_count_ - - @property - def root_directory_start(self) -> int: - root_dir_start: int = (self.reserved_sectors_cnt + self.sectors_per_fat_cnt) * self.sector_size - return root_dir_start diff --git a/third_party/fatfsgen/fatfs_utils/fs_object.py b/third_party/fatfsgen/fatfs_utils/fs_object.py deleted file mode 100644 index 307087cfb3d8dc04fb3f509a3c73fc57e03ee534..0000000000000000000000000000000000000000 --- a/third_party/fatfsgen/fatfs_utils/fs_object.py +++ /dev/null @@ -1,343 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: Apache-2.0 - -import os -from datetime import datetime -from typing import List, Optional, Tuple, Union - -from .entry import Entry -from .exceptions import FatalError, WriteDirectoryException -from .fat import FAT, Cluster -from .fatfs_state import FATFSState -from .long_filename_utils import (build_lfn_full_name, build_lfn_unique_entry_name_order, - get_required_lfn_entries_count, split_name_to_lfn_entries, - split_name_to_lfn_entry_blocks) -from .utils import (DATETIME, INVALID_SFN_CHARS_PATTERN, MAX_EXT_SIZE, MAX_NAME_SIZE, FATDefaults, - build_lfn_short_entry_name, build_name, lfn_checksum, required_clusters_count, - split_content_into_sectors, split_to_name_and_extension) - - -class File: - """ - The class File provides API to write into the files. It represents file in the FS. - """ - ATTR_ARCHIVE: int = 0x20 - ENTITY_TYPE: int = ATTR_ARCHIVE - - def __init__(self, name: str, fat: FAT, fatfs_state: FATFSState, entry: Entry, extension: str = '') -> None: - self.name: str = name - self.extension: str = extension - self.fatfs_state: FATFSState = fatfs_state - self.fat: FAT = fat - self.size: int = 0 - self._first_cluster: Optional[Cluster] = None - self._entry: Entry = entry - - @property - def entry(self) -> Entry: - return self._entry - - @property - def first_cluster(self) -> Optional[Cluster]: - return self._first_cluster - - @first_cluster.setter - def first_cluster(self, value: Cluster) -> None: - self._first_cluster = value - - def name_equals(self, name: str, extension: str) -> bool: - equals_: bool = build_name(name, extension) == build_name(self.name, self.extension) - return equals_ - - def write(self, content: bytes) -> None: - self.entry.update_content_size(len(content)) - # we assume that the correct amount of clusters is allocated - current_cluster = self._first_cluster - for content_part in split_content_into_sectors(content, self.fatfs_state.boot_sector_state.sector_size): - content_as_list = content_part - if current_cluster is None: - raise FatalError('No free space left!') - - address: int = current_cluster.cluster_data_address - self.fatfs_state.binary_image[address: address + len(content_part)] = content_as_list - current_cluster = current_cluster.next_cluster - - -class Directory: - """ - The Directory class provides API to add files and directories into the directory - and to find the file according to path and write it. - """ - ATTR_DIRECTORY: int = 0x10 - ATTR_ARCHIVE: int = 0x20 - ENTITY_TYPE: int = ATTR_DIRECTORY - - CURRENT_DIRECTORY = '.' - PARENT_DIRECTORY = '..' - - def __init__(self, - name, - fat, - fatfs_state, - entry=None, - cluster=None, - size=None, - extension='', - parent=None): - # type: (str, FAT, FATFSState, Optional[Entry], Cluster, Optional[int], str, Directory) -> None - self.name: str = name - self.fatfs_state: FATFSState = fatfs_state - self.extension: str = extension - - self.fat: FAT = fat - self.size: int = size or self.fatfs_state.boot_sector_state.sector_size - - # if directory is root its parent is itself - self.parent: Directory = parent or self - self._first_cluster: Cluster = cluster - - # entries will be initialized after the cluster allocation - self.entries: List[Entry] = [] - self.entities: List[Union[File, Directory]] = [] # type: ignore - self._entry = entry # currently not in use (will use later for e.g. modification time, etc.) - - @property - def is_root(self) -> bool: - return self.parent is self - - @property - def first_cluster(self) -> Cluster: - return self._first_cluster - - @first_cluster.setter - def first_cluster(self, value: Cluster) -> None: - self._first_cluster = value - - def name_equals(self, name: str, extension: str) -> bool: - equals_: bool = build_name(name, extension) == build_name(self.name, self.extension) - return equals_ - - @property - def entries_count(self) -> int: - entries_count_: int = self.size // FATDefaults.ENTRY_SIZE - return entries_count_ - - def create_entries(self, cluster: Cluster) -> List[Entry]: - return [Entry(entry_id=i, - parent_dir_entries_address=cluster.cluster_data_address, - fatfs_state=self.fatfs_state) - for i in range(self.entries_count)] - - def init_directory(self) -> None: - self.entries = self.create_entries(self._first_cluster) - - # the root directory doesn't contain link to itself nor the parent - if self.is_root: - return - # if the directory is not root we initialize the reference to itself and to the parent directory - for dir_id, name_ in ((self, self.CURRENT_DIRECTORY), (self.parent, self.PARENT_DIRECTORY)): - new_dir_: Entry = self.find_free_entry() or self.chain_directory() - new_dir_.allocate_entry(first_cluster_id=dir_id.first_cluster.id, - entity_name=name_, - entity_extension='', - entity_type=dir_id.ENTITY_TYPE) - - def lookup_entity(self, object_name: str, extension: str): # type: ignore - for entity in self.entities: - if build_name(entity.name, entity.extension) == build_name(object_name, extension): - return entity - return None - - @staticmethod - def _is_end_of_path(path_as_list: List[str]) -> bool: - """ - :param path_as_list: path split into the list - - :returns: True if the file is the leaf of the directory tree, False otherwise - The method is part of the base of recursion, - determines if the path is target file or directory in the tree folder structure. - """ - return len(path_as_list) == 1 - - def recursive_search(self, path_as_list, current_dir): # type: ignore - name, extension = split_to_name_and_extension(path_as_list[0]) - next_obj = current_dir.lookup_entity(name, extension) - if next_obj is None: - raise FileNotFoundError('No such file or directory!') - if self._is_end_of_path(path_as_list) and next_obj.name_equals(name, extension): - return next_obj - return self.recursive_search(path_as_list[1:], next_obj) - - def find_free_entry(self) -> Optional[Entry]: - for entry in self.entries: - if entry.is_empty: - return entry - return None - - def _extend_directory(self) -> None: - current: Cluster = self.first_cluster - while current.next_cluster is not None: - current = current.next_cluster - new_cluster: Cluster = self.fat.find_free_cluster() - current.set_in_fat(new_cluster.id) - assert current is not new_cluster - current.next_cluster = new_cluster - self.entries += self.create_entries(new_cluster) - - def chain_directory(self) -> Entry: - """ - :returns: First free entry - - The method adds new Cluster to the Directory and returns first free entry. - """ - self._extend_directory() - free_entry: Entry = self.find_free_entry() - if free_entry is None: - raise FatalError('No more space left!') - return free_entry - - @staticmethod - def allocate_long_name_object(free_entry, - name, - extension, - target_dir, - free_cluster_id, - entity_type, - date, - time): - # type: (Entry, str, str, Directory, int, int, DATETIME, DATETIME) -> Entry - lfn_full_name: str = build_lfn_full_name(name, extension) - lfn_unique_entry_order: int = build_lfn_unique_entry_name_order(target_dir.entities, name) - lfn_short_entry_name: str = build_lfn_short_entry_name(name, extension, lfn_unique_entry_order) - checksum: int = lfn_checksum(lfn_short_entry_name) - entries_count: int = get_required_lfn_entries_count(lfn_full_name) - - # entries in long file name entries chain starts with the last entry - split_names_reversed = list(reversed(list(enumerate(split_name_to_lfn_entries(lfn_full_name, entries_count))))) - for i, name_split_to_entry in split_names_reversed: - order: int = i + 1 - blocks_: List[bytes] = split_name_to_lfn_entry_blocks(name_split_to_entry) - lfn_names: List[bytes] = list(map(lambda x: x.lower(), blocks_)) - free_entry.allocate_entry(first_cluster_id=free_cluster_id, - entity_name=name, - entity_extension=extension, - entity_type=entity_type, - lfn_order=order, - lfn_names=lfn_names, - lfn_checksum_=checksum, - lfn_is_last=order == entries_count) - free_entry = target_dir.find_free_entry() or target_dir.chain_directory() - free_entry.allocate_entry(first_cluster_id=free_cluster_id, - entity_name=lfn_short_entry_name[:MAX_NAME_SIZE], - entity_extension=lfn_short_entry_name[MAX_NAME_SIZE:], - entity_type=entity_type, - lfn_order=Entry.SHORT_ENTRY_LN, - date=date, - time=time) - return free_entry - - @staticmethod - def _is_valid_sfn(name: str, extension: str) -> bool: - if INVALID_SFN_CHARS_PATTERN.search(name) or INVALID_SFN_CHARS_PATTERN.search(name): - return False - ret: bool = len(name) <= MAX_NAME_SIZE and len(extension) <= MAX_EXT_SIZE - return ret - - def allocate_object(self, - name, - entity_type, - object_timestamp_, - path_from_root=None, - extension='', - is_empty=False): - # type: (str, int, datetime, Optional[List[str]], str, bool) -> Tuple[Cluster, Entry, Directory] - """ - Method finds the target directory in the path - and allocates cluster (both the record in FAT and cluster in the data region) - and entry in the specified directory - """ - - free_cluster: Optional[Cluster] = None - free_cluster_id = 0x00 - if not is_empty: - free_cluster = self.fat.find_free_cluster() - free_cluster_id = free_cluster.id - - target_dir: Directory = self if not path_from_root else self.recursive_search(path_from_root, self) - free_entry: Entry = target_dir.find_free_entry() or target_dir.chain_directory() - - fatfs_date_ = (object_timestamp_.year, object_timestamp_.month, object_timestamp_.day) - fatfs_time_ = (object_timestamp_.hour, object_timestamp_.minute, object_timestamp_.second) - - if not self.fatfs_state.long_names_enabled or self._is_valid_sfn(name, extension): - free_entry.allocate_entry(first_cluster_id=free_cluster_id, - entity_name=name, - entity_extension=extension, - date=fatfs_date_, - time=fatfs_time_, - fits_short=True, - entity_type=entity_type) - return free_cluster, free_entry, target_dir - return free_cluster, self.allocate_long_name_object(free_entry=free_entry, - name=name, - extension=extension, - target_dir=target_dir, - free_cluster_id=free_cluster_id, - entity_type=entity_type, - date=fatfs_date_, - time=fatfs_time_), target_dir - - def new_file(self, - name: str, - extension: str, - path_from_root: Optional[List[str]], - object_timestamp_: datetime, - is_empty: bool) -> None: - free_cluster, free_entry, target_dir = self.allocate_object(name=name, - extension=extension, - entity_type=Directory.ATTR_ARCHIVE, - path_from_root=path_from_root, - object_timestamp_=object_timestamp_, - is_empty=is_empty) - - file: File = File(name=name, - fat=self.fat, - extension=extension, - fatfs_state=self.fatfs_state, - entry=free_entry) - file.first_cluster = free_cluster - target_dir.entities.append(file) - - def new_directory(self, name, parent, path_from_root, object_timestamp_): - # type: (str, Directory, Optional[List[str]], datetime) -> None - free_cluster, free_entry, target_dir = self.allocate_object(name=name, - entity_type=Directory.ATTR_DIRECTORY, - path_from_root=path_from_root, - object_timestamp_=object_timestamp_) - - directory: Directory = Directory(name=name, - fat=self.fat, - parent=parent, - fatfs_state=self.fatfs_state, - entry=free_entry) - directory.first_cluster = free_cluster - directory.init_directory() - target_dir.entities.append(directory) - - def write_to_file(self, path: List[str], content: bytes) -> None: - """ - Writes to file existing in the directory structure. - - :param path: path split into the list - :param content: content as a string to be written into a file - :returns: None - :raises WriteDirectoryException: raised is the target object for writing is a directory - """ - entity_to_write: Entry = self.recursive_search(path, self) - if isinstance(entity_to_write, File): - clusters_cnt: int = required_clusters_count(cluster_size=self.fatfs_state.boot_sector_state.sector_size, - content=content) - self.fat.allocate_chain(entity_to_write.first_cluster, clusters_cnt) - entity_to_write.write(content) - else: - raise WriteDirectoryException(f'`{os.path.join(*path)}` is a directory!') diff --git a/third_party/fatfsgen/fatfs_utils/long_filename_utils.py b/third_party/fatfsgen/fatfs_utils/long_filename_utils.py deleted file mode 100644 index 649312aeada0b17bf54ea2c7c8bcd1370ba29e40..0000000000000000000000000000000000000000 --- a/third_party/fatfsgen/fatfs_utils/long_filename_utils.py +++ /dev/null @@ -1,98 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: Apache-2.0 -from typing import List - -from .entry import Entry -from .exceptions import NoFreeClusterException -from .utils import build_name, convert_to_utf16_and_pad - -# File name with long filenames support can be as long as memory allows. It is split into entries -# holding 13 characters of the filename, thus the number of required entries is ceil(len(long_name) / 13). -# This is computed using `get_required_lfn_entries_count`. -# For creating long name entries we need to split the name by 13 characters using `split_name_to_lfn_entries` -# and in every entry into three blocks with sizes 5, 6 and 2 characters using `split_name_to_lfn_entry`. - -MAXIMAL_FILES_SAME_PREFIX: int = 127 - - -def get_required_lfn_entries_count(lfn_full_name: str) -> int: - """ - Compute the number of entries required to store the long name. - One long filename entry can hold 13 characters with size 2 bytes. - - E.g. "thisisverylongfilenama.txt" with length of 26 needs 2 lfn entries, - but "thisisverylongfilenamax.txt" with 27 characters needs 3 lfn entries. - """ - entries_count: int = (len(lfn_full_name) + Entry.CHARS_PER_ENTRY - 1) // Entry.CHARS_PER_ENTRY - return entries_count - - -def split_name_to_lfn_entries(name: str, entries: int) -> List[str]: - """ - If the filename is longer than 8 (name) + 3 (extension) characters, - generator uses long name structure and splits the name into suitable amount of blocks. - - E.g. 'thisisverylongfilenama.txt' would be split to ['THISISVERYLON', 'GFILENAMA.TXT'], - in case of 'thisisverylongfilenamax.txt' - ['THISISVERYLON', 'GFILENAMAX.TX', 'T'] - """ - return [name[i * Entry.CHARS_PER_ENTRY:(i + 1) * Entry.CHARS_PER_ENTRY] for i in range(entries)] - - -def split_name_to_lfn_entry_blocks(name: str) -> List[bytes]: - """ - Filename is divided into three blocks in every long file name entry. Sizes of the blocks are defined - by LDIR_Name1_SIZE, LDIR_Name2_SIZE and LDIR_Name3_SIZE, thus every block contains LDIR_Name{X}_SIZE * 2 bytes. - - If the filename ends in one of the blocks, it is terminated by zero encoded to two bytes (0x0000). Other unused - characters are set to 0xFFFF. - E.g.: - 'GFILENAMA.TXT' -> [b'G\x00F\x00I\x00L\x00E\x00', b'N\x00A\x00M\x00A\x00.\x00T\x00', b'X\x00T\x00']; - 'T' -> [b'T\x00\x00\x00\xff\xff\xff\xff\xff\xff', b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff', - b'\xff\xff\xff\xff'] - - Notice that since every character is coded using 2 bytes be must add 0x00 to ASCII symbols ('G' -> 'G\x00', etc.), - since character 'T' ends in the first block, we must add '\x00\x00' after 'T\x00'. - """ - max_entry_size: int = Entry.LDIR_Name1_SIZE + Entry.LDIR_Name2_SIZE + Entry.LDIR_Name2_SIZE - assert len(name) <= max_entry_size - blocks_: List[bytes] = [ - convert_to_utf16_and_pad(content=name[:Entry.LDIR_Name1_SIZE], - expected_size=Entry.LDIR_Name1_SIZE), - convert_to_utf16_and_pad(content=name[Entry.LDIR_Name1_SIZE:Entry.LDIR_Name1_SIZE + Entry.LDIR_Name2_SIZE], - expected_size=Entry.LDIR_Name2_SIZE), - convert_to_utf16_and_pad(content=name[Entry.LDIR_Name1_SIZE + Entry.LDIR_Name2_SIZE:], - expected_size=Entry.LDIR_Name3_SIZE) - ] - return blocks_ - - -def build_lfn_unique_entry_name_order(entities: list, lfn_entry_name: str) -> int: - """ - The short entry contains only the first 6 characters of the file name, - and we have to distinguish it from other names within the directory starting with the same 6 characters. - To make it unique, we add its order in relation to other names such that lfn_entry_name[:6] == other[:6]. - The order is specified by the character, starting with chr(1). - - E.g. the file in directory 'thisisverylongfilenama.txt' will be named 'THISIS~1TXT' in its short entry. - If we add another file 'thisisverylongfilenamax.txt' its name in the short entry will be 'THISIS~2TXT'. - """ - preceding_entries: int = 1 - for entity in entities: - if entity.name[:6] == lfn_entry_name[:6]: - preceding_entries += 1 - if preceding_entries > MAXIMAL_FILES_SAME_PREFIX: - raise NoFreeClusterException('Maximal number of files with the same prefix is 127') - return preceding_entries - - -def build_lfn_full_name(name: str, extension: str) -> str: - """ - The extension is optional, and the long filename entry explicitly specifies it, - on the opposite as for short file names. - """ - lfn_record: str = build_name(name, extension) - # the name must be terminated with NULL terminator - # if it doesn't fit into the set of long name directory entries - if len(lfn_record) % Entry.CHARS_PER_ENTRY != 0: - return lfn_record + chr(0) - return lfn_record diff --git a/third_party/fatfsgen/fatfs_utils/utils.py b/third_party/fatfsgen/fatfs_utils/utils.py deleted file mode 100644 index d2180c76bdaa986e05e12bc719975d24f705b4ec..0000000000000000000000000000000000000000 --- a/third_party/fatfsgen/fatfs_utils/utils.py +++ /dev/null @@ -1,299 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: Apache-2.0 - -import argparse -import binascii -import os -import re -import uuid -from datetime import datetime -from typing import List, Optional, Tuple - -from construct import BitsInteger, BitStruct, Int16ul - -# the regex pattern defines symbols that are allowed by long file names but not by short file names -INVALID_SFN_CHARS_PATTERN = re.compile(r'[.+,;=\[\]]') - -FATFS_MIN_ALLOC_UNIT: int = 128 -FAT12_MAX_CLUSTERS: int = 4085 -FAT16_MAX_CLUSTERS: int = 65525 -RESERVED_CLUSTERS_COUNT: int = 2 -PAD_CHAR: int = 0x20 -FAT12: int = 12 -FAT16: int = 16 -FAT32: int = 32 -FULL_BYTE: bytes = b'\xff' -EMPTY_BYTE: bytes = b'\x00' -# redundant -BYTES_PER_DIRECTORY_ENTRY: int = 32 -UINT32_MAX: int = (1 << 32) - 1 -MAX_NAME_SIZE: int = 8 -MAX_EXT_SIZE: int = 3 -DATETIME = Tuple[int, int, int] -FATFS_INCEPTION_YEAR: int = 1980 - -FATFS_INCEPTION: datetime = datetime(FATFS_INCEPTION_YEAR, 1, 1, 0, 0, 0, 0) - -FATFS_MAX_HOURS = 24 -FATFS_MAX_MINUTES = 60 -FATFS_MAX_SECONDS = 60 - -FATFS_MAX_DAYS = 31 -FATFS_MAX_MONTHS = 12 -FATFS_MAX_YEARS = 127 - -FATFS_SECONDS_GRANULARITY: int = 2 - -# long names are encoded to two bytes in utf-16 -LONG_NAMES_ENCODING: str = 'utf-16' -SHORT_NAMES_ENCODING: str = 'utf-8' - -# compatible with WL_SECTOR_SIZE -# choices for WL are WL_SECTOR_SIZE_512 and WL_SECTOR_SIZE_4096 -ALLOWED_WL_SECTOR_SIZES: List[int] = [512, 4096] -ALLOWED_SECTOR_SIZES: List[int] = [512, 1024, 2048, 4096] - -ALLOWED_SECTORS_PER_CLUSTER: List[int] = [1, 2, 4, 8, 16, 32, 64, 128] - - -def crc32(input_values: List[int], crc: int) -> int: - """ - Name Polynomial Reversed? Init-value XOR-out - crc32 0x104C11DB7 True 4294967295 (UINT32_MAX) 0xFFFFFFFF - """ - return binascii.crc32(bytearray(input_values), crc) - - -def number_of_clusters(number_of_sectors: int, sectors_per_cluster: int) -> int: - return number_of_sectors // sectors_per_cluster - - -def get_non_data_sectors_cnt(reserved_sectors_cnt: int, sectors_per_fat_cnt: int, root_dir_sectors_cnt: int) -> int: - return reserved_sectors_cnt + sectors_per_fat_cnt + root_dir_sectors_cnt - - -def get_fatfs_type(clusters_count: int) -> int: - if clusters_count < FAT12_MAX_CLUSTERS: - return FAT12 - if clusters_count <= FAT16_MAX_CLUSTERS: - return FAT16 - return FAT32 - - -def get_fat_sectors_count(clusters_count: int, sector_size: int) -> int: - fatfs_type_ = get_fatfs_type(clusters_count) - if fatfs_type_ == FAT32: - raise NotImplementedError('FAT32 is not supported!') - # number of byte halves - cluster_s: int = fatfs_type_ // 4 - fat_size_bytes: int = ( - clusters_count * 2 + cluster_s) if fatfs_type_ == FAT16 else (clusters_count * 3 + 1) // 2 + cluster_s - return (fat_size_bytes + sector_size - 1) // sector_size - - -def required_clusters_count(cluster_size: int, content: bytes) -> int: - # compute number of required clusters for file text - return (len(content) + cluster_size - 1) // cluster_size - - -def generate_4bytes_random() -> int: - return uuid.uuid4().int & 0xFFFFFFFF - - -def pad_string(content: str, size: Optional[int] = None, pad: int = PAD_CHAR) -> str: - # cut string if longer and fill with pad character if shorter than size - return content.ljust(size or len(content), chr(pad))[:size] - - -def right_strip_string(content: str, pad: int = PAD_CHAR) -> str: - return content.rstrip(chr(pad)) - - -def build_lfn_short_entry_name(name: str, extension: str, order: int) -> str: - return '{}{}'.format(pad_string(content=name[:MAX_NAME_SIZE - 2] + '~' + chr(order), size=MAX_NAME_SIZE), - pad_string(extension[:MAX_EXT_SIZE], size=MAX_EXT_SIZE)) - - -def lfn_checksum(short_entry_name: str) -> int: - """ - Function defined by FAT specification. Computes checksum out of name in the short file name entry. - """ - checksum_result = 0 - for i in range(MAX_NAME_SIZE + MAX_EXT_SIZE): - # operation is a right rotation on 8 bits (Python equivalent for unsigned char in C) - checksum_result = (0x80 if checksum_result & 1 else 0x00) + (checksum_result >> 1) + ord(short_entry_name[i]) - checksum_result &= 0xff - return checksum_result - - -def convert_to_utf16_and_pad(content: str, - expected_size: int, - pad: bytes = FULL_BYTE) -> bytes: - # we need to get rid of the Byte order mark 0xfeff or 0xfffe, fatfs does not use it - bom_utf16: bytes = b'\xfe\xff' - encoded_content_utf16: bytes = content.encode(LONG_NAMES_ENCODING)[len(bom_utf16):] - return encoded_content_utf16.ljust(2 * expected_size, pad) - - -def split_to_name_and_extension(full_name: str) -> Tuple[str, str]: - name, extension = os.path.splitext(full_name) - return name, extension.replace('.', '') - - -def is_valid_fatfs_name(string: str) -> bool: - return string == string.upper() - - -def split_by_half_byte_12_bit_little_endian(value: int) -> Tuple[int, int, int]: - value_as_bytes: bytes = Int16ul.build(value) - return value_as_bytes[0] & 0x0f, value_as_bytes[0] >> 4, value_as_bytes[1] & 0x0f - - -def merge_by_half_byte_12_bit_little_endian(v1: int, v2: int, v3: int) -> int: - return v1 | v2 << 4 | v3 << 8 - - -def build_byte(first_half: int, second_half: int) -> int: - return (first_half << 4) | second_half - - -def split_content_into_sectors(content: bytes, sector_size: int) -> List[bytes]: - result = [] - clusters_cnt: int = required_clusters_count(cluster_size=sector_size, content=content) - - for i in range(clusters_cnt): - result.append(content[sector_size * i:(i + 1) * sector_size]) - return result - - -def get_args_for_partition_generator(desc: str, wl: bool) -> argparse.Namespace: - parser: argparse.ArgumentParser = argparse.ArgumentParser(description=desc) - parser.add_argument('input_directory', - help='Path to the directory that will be encoded into fatfs image') - parser.add_argument('--output_file', - default='fatfs_image.img', - help='Filename of the generated fatfs image') - parser.add_argument('--partition_size', - default=FATDefaults.SIZE, - help='Size of the partition in bytes.' + - ('' if wl else ' Use `--partition_size detect` for detecting the minimal partition size.') - ) - parser.add_argument('--sector_size', - default=FATDefaults.SECTOR_SIZE, - type=int, - choices=ALLOWED_WL_SECTOR_SIZES if wl else ALLOWED_SECTOR_SIZES, - help='Size of the partition in bytes') - parser.add_argument('--sectors_per_cluster', - default=1, - type=int, - choices=ALLOWED_SECTORS_PER_CLUSTER, - help='Number of sectors per cluster') - parser.add_argument('--root_entry_count', - default=FATDefaults.ROOT_ENTRIES_COUNT, - help='Number of entries in the root directory') - parser.add_argument('--long_name_support', - action='store_true', - help='Set flag to enable long names support.') - parser.add_argument('--use_default_datetime', - action='store_true', - help='For test purposes. If the flag is set the files are created with ' - 'the default timestamp that is the 1st of January 1980') - parser.add_argument('--fat_type', - default=0, - type=int, - choices=[FAT12, FAT16, 0], - help=""" - Type of fat. Select 12 for fat12, 16 for fat16. Don't set, or set to 0 for automatic - calculation using cluster size and partition size. - """) - - args = parser.parse_args() - if args.fat_type == 0: - args.fat_type = None - if args.partition_size == 'detect' and not wl: - args.partition_size = -1 - args.partition_size = int(str(args.partition_size), 0) - if not os.path.isdir(args.input_directory): - raise NotADirectoryError(f'The target directory `{args.input_directory}` does not exist!') - return args - - -def read_filesystem(path: str) -> bytearray: - with open(path, 'rb') as fs_file: - return bytearray(fs_file.read()) - - -DATE_ENTRY = BitStruct( - 'year' / BitsInteger(7), - 'month' / BitsInteger(4), - 'day' / BitsInteger(5)) - -TIME_ENTRY = BitStruct( - 'hour' / BitsInteger(5), - 'minute' / BitsInteger(6), - 'second' / BitsInteger(5), -) - - -def build_name(name: str, extension: str) -> str: - return f'{name}.{extension}' if len(extension) > 0 else name - - -def build_date_entry(year: int, mon: int, mday: int) -> int: - """ - :param year: denotes year starting from 1980 (0 ~ 1980, 1 ~ 1981, etc), valid values are 1980 + 0..127 inclusive - thus theoretically 1980 - 2107 - :param mon: denotes number of month of year in common order (1 ~ January, 2 ~ February, etc.), - valid values: 1..12 inclusive - :param mday: denotes number of day in month, valid values are 1..31 inclusive - - :returns: 16 bit integer number (7 bits for year, 4 bits for month and 5 bits for day of the month) - """ - assert year in range(FATFS_INCEPTION_YEAR, FATFS_INCEPTION_YEAR + FATFS_MAX_YEARS) - assert mon in range(1, FATFS_MAX_MONTHS + 1) - assert mday in range(1, FATFS_MAX_DAYS + 1) - return int.from_bytes(DATE_ENTRY.build(dict(year=year - FATFS_INCEPTION_YEAR, month=mon, day=mday)), 'big') - - -def build_time_entry(hour: int, minute: int, sec: int) -> int: - """ - :param hour: denotes number of hour, valid values are 0..23 inclusive - :param minute: denotes minutes, valid range 0..59 inclusive - :param sec: denotes seconds with granularity 2 seconds (e.g. 1 ~ 2, 29 ~ 58), valid range 0..29 inclusive - - :returns: 16 bit integer number (5 bits for hour, 6 bits for minute and 5 bits for second) - """ - assert hour in range(FATFS_MAX_HOURS) - assert minute in range(FATFS_MAX_MINUTES) - assert sec in range(FATFS_MAX_SECONDS) - return int.from_bytes(TIME_ENTRY.build( - dict(hour=hour, minute=minute, second=sec // FATFS_SECONDS_GRANULARITY)), - byteorder='big' - ) - - -class FATDefaults: - # FATFS defaults - SIZE: int = 1024 * 1024 - RESERVED_SECTORS_COUNT: int = 1 - FAT_TABLES_COUNT: int = 1 - SECTORS_PER_CLUSTER: int = 1 - SECTOR_SIZE: int = 0x1000 - HIDDEN_SECTORS: int = 0 - ENTRY_SIZE: int = 32 - NUM_HEADS: int = 0xff - OEM_NAME: str = 'MSDOS5.0' - SEC_PER_TRACK: int = 0x3f - VOLUME_LABEL: str = 'Espressif' - FILE_SYS_TYPE: str = 'FAT' - ROOT_ENTRIES_COUNT: int = 512 # number of entries in the root directory, recommended 512 - MEDIA_TYPE: int = 0xf8 - SIGNATURE_WORD: bytes = b'\x55\xAA' - - # wear levelling defaults - VERSION: int = 2 - TEMP_BUFFER_SIZE: int = 32 - UPDATE_RATE: int = 16 - WR_SIZE: int = 16 - # wear leveling metadata (config sector) contains always sector size 4096 - WL_SECTOR_SIZE: int = 4096 diff --git a/third_party/fatfsgen/fatfsgen.py b/third_party/fatfsgen/fatfsgen.py deleted file mode 100755 index d854749632bb329b083ae06621cf933155a25bf3..0000000000000000000000000000000000000000 --- a/third_party/fatfsgen/fatfsgen.py +++ /dev/null @@ -1,251 +0,0 @@ -#!/usr/bin/env python -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: Apache-2.0 - -import os -from datetime import datetime -from typing import Any, List, Optional - -from fatfs_utils.boot_sector import BootSector -from fatfs_utils.exceptions import NoFreeClusterException -from fatfs_utils.fat import FAT -from fatfs_utils.fatfs_state import FATFSState -from fatfs_utils.fs_object import Directory -from fatfs_utils.long_filename_utils import get_required_lfn_entries_count -from fatfs_utils.utils import (BYTES_PER_DIRECTORY_ENTRY, FATFS_INCEPTION, FATFS_MIN_ALLOC_UNIT, - RESERVED_CLUSTERS_COUNT, FATDefaults, get_args_for_partition_generator, - get_fat_sectors_count, get_non_data_sectors_cnt, read_filesystem, - required_clusters_count) - - -class FATFS: - """ - The class FATFS provides API for generating FAT file system. - It contains reference to the FAT table and to the root directory. - """ - - def __init__(self, - binary_image_path: Optional[str] = None, - size: int = FATDefaults.SIZE, - reserved_sectors_cnt: int = FATDefaults.RESERVED_SECTORS_COUNT, - fat_tables_cnt: int = FATDefaults.FAT_TABLES_COUNT, - sectors_per_cluster: int = FATDefaults.SECTORS_PER_CLUSTER, - sector_size: int = FATDefaults.SECTOR_SIZE, - hidden_sectors: int = FATDefaults.HIDDEN_SECTORS, - long_names_enabled: bool = False, - use_default_datetime: bool = True, - num_heads: int = FATDefaults.NUM_HEADS, - oem_name: str = FATDefaults.OEM_NAME, - sec_per_track: int = FATDefaults.SEC_PER_TRACK, - volume_label: str = FATDefaults.VOLUME_LABEL, - file_sys_type: str = FATDefaults.FILE_SYS_TYPE, - root_entry_count: int = FATDefaults.ROOT_ENTRIES_COUNT, - explicit_fat_type: int = None, - media_type: int = FATDefaults.MEDIA_TYPE) -> None: - # root directory bytes should be aligned by sector size - assert (root_entry_count * BYTES_PER_DIRECTORY_ENTRY) % sector_size == 0 - # number of bytes in the root dir must be even multiple of BPB_BytsPerSec - assert ((root_entry_count * BYTES_PER_DIRECTORY_ENTRY) // sector_size) % 2 == 0 - - root_dir_sectors_cnt: int = (root_entry_count * BYTES_PER_DIRECTORY_ENTRY) // sector_size - - self.state: FATFSState = FATFSState(sector_size=sector_size, - explicit_fat_type=explicit_fat_type, - reserved_sectors_cnt=reserved_sectors_cnt, - root_dir_sectors_cnt=root_dir_sectors_cnt, - size=size, - file_sys_type=file_sys_type, - num_heads=num_heads, - fat_tables_cnt=fat_tables_cnt, - sectors_per_cluster=sectors_per_cluster, - media_type=media_type, - hidden_sectors=hidden_sectors, - sec_per_track=sec_per_track, - long_names_enabled=long_names_enabled, - volume_label=volume_label, - oem_name=oem_name, - use_default_datetime=use_default_datetime) - binary_image: bytes = bytearray( - read_filesystem(binary_image_path) if binary_image_path else self.create_empty_fatfs()) - self.state.binary_image = binary_image - - self.fat: FAT = FAT(boot_sector_state=self.state.boot_sector_state, init_=True) - - root_dir_size = self.state.boot_sector_state.root_dir_sectors_cnt * self.state.boot_sector_state.sector_size - self.root_directory: Directory = Directory(name='A', # the name is not important, must be string - size=root_dir_size, - fat=self.fat, - cluster=self.fat.clusters[1], - fatfs_state=self.state) - self.root_directory.init_directory() - - def create_file(self, name: str, - extension: str = '', - path_from_root: Optional[List[str]] = None, - object_timestamp_: datetime = FATFS_INCEPTION, - is_empty: bool = False) -> None: - """ - This method allocates necessary clusters and creates a new file record in the directory required. - The directory must exists. - - When path_from_root is None the dir is root. - - :param name: The name of the file. - :param extension: The extension of the file. - :param path_from_root: List of strings containing names of the ancestor directories in the given order. - :param object_timestamp_: is not None, this will be propagated to the file's entry - :param is_empty: True if there is no need to allocate any cluster, otherwise False - """ - self.root_directory.new_file(name=name, - extension=extension, - path_from_root=path_from_root, - object_timestamp_=object_timestamp_, - is_empty=is_empty) - - def create_directory(self, name: str, - path_from_root: Optional[List[str]] = None, - object_timestamp_: datetime = FATFS_INCEPTION) -> None: - """ - Initially recursively finds a parent of the new directory - and then create a new directory inside the parent. - - When path_from_root is None the parent dir is root. - - :param name: The full name of the directory (excluding its path) - :param path_from_root: List of strings containing names of the ancestor directories in the given order. - :param object_timestamp_: in case the user preserves the timestamps, this will be propagated to the - metadata of the directory (to the corresponding entry) - :returns: None - """ - parent_dir = self.root_directory - if path_from_root: - parent_dir = self.root_directory.recursive_search(path_from_root, self.root_directory) - - self.root_directory.new_directory(name=name, - parent=parent_dir, - path_from_root=path_from_root, - object_timestamp_=object_timestamp_) - - def write_content(self, path_from_root: List[str], content: bytes) -> None: - """ - fat fs invokes root directory to recursively find the required file and writes the content - """ - self.root_directory.write_to_file(path_from_root, content) - - def create_empty_fatfs(self) -> Any: - boot_sector_ = BootSector(boot_sector_state=self.state.boot_sector_state) - boot_sector_.generate_boot_sector() - return boot_sector_.binary_image - - def write_filesystem(self, output_path: str) -> None: - with open(output_path, 'wb') as output: - output.write(bytearray(self.state.binary_image)) - - def _generate_partition_from_folder(self, - folder_relative_path: str, - folder_path: str = '', - is_dir: bool = False) -> None: - """ - Given path to folder and folder name recursively encodes folder into binary image. - Used by method generate. - """ - real_path: str = os.path.join(folder_path, folder_relative_path) - lower_path: str = folder_relative_path - - folder_relative_path = folder_relative_path.upper() - - normal_path = os.path.normpath(folder_relative_path) - split_path = normal_path.split(os.sep) - object_timestamp = datetime.fromtimestamp(os.path.getctime(real_path)) - - if '__pycache__' in real_path: - return - - if '.mypy_cache' in real_path: - return - - if os.path.isfile(real_path): - with open(real_path, 'rb') as file: - content = file.read() - file_name, extension = os.path.splitext(split_path[-1]) - extension = extension[1:] # remove the dot from the extension - self.create_file(name=file_name, - extension=extension, - path_from_root=split_path[1:-1] or None, - object_timestamp_=object_timestamp, - is_empty=len(content) == 0) - self.write_content(split_path[1:], content) - elif os.path.isdir(real_path): - if not is_dir: - self.create_directory(name=split_path[-1], - path_from_root=split_path[1:-1], - object_timestamp_=object_timestamp) - - # sorting files for better testability - dir_content = list(sorted(os.listdir(real_path))) - for path in dir_content: - self._generate_partition_from_folder(os.path.join(lower_path, path), folder_path=folder_path) - - def generate(self, input_directory: str) -> None: - """ - Normalize path to folder and recursively encode folder to binary image - """ - path_to_folder, folder_name = os.path.split(input_directory) - self._generate_partition_from_folder(folder_name, folder_path=path_to_folder, is_dir=True) - - -def calculate_min_space(path: List[str], - fs_entity: str, - sector_size: int = 0x1000, - long_file_names: bool = False, - is_root: bool = False) -> int: - if os.path.isfile(os.path.join(*path, fs_entity)): - with open(os.path.join(*path, fs_entity), 'rb') as file_: - content = file_.read() - res: int = required_clusters_count(sector_size, content) - return res - buff: int = 0 - dir_size = 2 * FATDefaults.ENTRY_SIZE # record for symlinks "." and ".." - for file in sorted(os.listdir(os.path.join(*path, fs_entity))): - if long_file_names and True: - # LFN entries + one short entry - dir_size += (get_required_lfn_entries_count(fs_entity) + 1) * FATDefaults.ENTRY_SIZE - else: - dir_size += FATDefaults.ENTRY_SIZE - buff += calculate_min_space(path + [fs_entity], file, sector_size, long_file_names, is_root=False) - if is_root and dir_size // FATDefaults.ENTRY_SIZE > FATDefaults.ROOT_ENTRIES_COUNT: - raise NoFreeClusterException('Not enough space in root!') - - # roundup sectors, at least one is required - buff += (dir_size + sector_size - 1) // sector_size - return buff - - -def main() -> None: - args = get_args_for_partition_generator('Create a FAT filesystem and populate it with directory content', wl=False) - - if args.partition_size == -1: - clusters = calculate_min_space([], args.input_directory, args.sector_size, long_file_names=True, is_root=True) - fats = get_fat_sectors_count(clusters, args.sector_size) - root_dir_sectors = (FATDefaults.ROOT_ENTRIES_COUNT * FATDefaults.ENTRY_SIZE) // args.sector_size - args.partition_size = max(FATFS_MIN_ALLOC_UNIT * args.sector_size, - (clusters + fats + get_non_data_sectors_cnt(RESERVED_CLUSTERS_COUNT, - fats, - root_dir_sectors) - ) * args.sector_size - ) - - fatfs = FATFS(sector_size=args.sector_size, - sectors_per_cluster=args.sectors_per_cluster, - size=args.partition_size, - root_entry_count=args.root_entry_count, - explicit_fat_type=args.fat_type, - long_names_enabled=args.long_name_support, - use_default_datetime=args.use_default_datetime) - - fatfs.generate(args.input_directory) - fatfs.write_filesystem(args.output_file) - - -if __name__ == '__main__': - main() diff --git a/third_party/fatfsgen/project_include.cmake b/third_party/fatfsgen/project_include.cmake deleted file mode 100644 index c7bbbc7e7602ba3433c69184fe77aea9a7e64f11..0000000000000000000000000000000000000000 --- a/third_party/fatfsgen/project_include.cmake +++ /dev/null @@ -1,113 +0,0 @@ -# fatfs_create_partition_image -# -# Create a fatfs image of the specified directory on the host during build and optionally -# have the created image flashed using `idf.py flash` -function(fatfs_create_partition_image partition base_dir) - set(options FLASH_IN_PROJECT WL_INIT PRESERVE_TIME) - cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}") - - - idf_build_get_property(idf_path IDF_PATH) - idf_build_get_property(python PYTHON) - - if(arg_WL_INIT) - set(fatfsgen_py ${python} ${PROJECT_DIR}/third_party/fatfsgen/wl_fatfsgen.py) - else() - set(fatfsgen_py ${python} ${PROJECT_DIR}/third_party/fatfsgen/fatfsgen.py) - endif() - - if(arg_PRESERVE_TIME) - set(default_datetime_option) - else() - set(default_datetime_option --use_default_datetime) - endif() - - if("${CONFIG_FATFS_SECTOR_512}") - set(fatfs_sector_size 512) - elseif("${CONFIG_FATFS_SECTOR_1024}") - set(fatfs_sector_size 1024) - elseif("${CONFIG_FATFS_SECTOR_2048}") - set(fatfs_sector_size 2048) - else() - set(fatfs_sector_size 4096) - endif() - - set(fatfs_long_names_option --long_name_support) - - get_filename_component(base_dir_full_path ${base_dir} ABSOLUTE) - partition_table_get_partition_info(size "--partition-name ${partition}" "size") - partition_table_get_partition_info(offset "--partition-name ${partition}" "offset") - - if("${size}" AND "${offset}") - set(image_file ${CMAKE_BINARY_DIR}/${partition}.bin) - # Execute FATFS image generation; this always executes as there is no way to specify for CMake to watch for - # contents of the base dir changing. - add_custom_target(fatfs_${partition}_bin ALL - COMMAND ${fatfsgen_py} ${base_dir_full_path} - ${fatfs_long_names_option} - ${default_datetime_option} - --partition_size ${size} - --output_file ${image_file} - --sector_size "${fatfs_sector_size}" - ) - set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY - ADDITIONAL_CLEAN_FILES - ${image_file}) - - idf_component_get_property(main_args esptool_py FLASH_ARGS) - idf_component_get_property(sub_args esptool_py FLASH_SUB_ARGS) - # Last (optional) parameter is the encryption for the target. In our - # case, fatfs is not encrypt so pass FALSE to the function. - esptool_py_flash_target(${partition}-flash "${main_args}" "${sub_args}" ALWAYS_PLAINTEXT) - esptool_py_flash_to_partition(${partition}-flash "${partition}" "${image_file}") - - add_dependencies(${partition}-flash fatfs_${partition}_bin) - if(arg_FLASH_IN_PROJECT) - esptool_py_flash_to_partition(flash "${partition}" "${image_file}") - add_dependencies(flash fatfs_${partition}_bin) - endif() - else() - set(message "Failed to create FATFS image for partition '${partition}'. " - "Check project configuration if using the correct partition table file.") - fail_at_build_time(fatfs_${partition}_bin "${message}") - endif() -endfunction() - - -function(fatfs_create_rawflash_image partition base_dir) - set(options FLASH_IN_PROJECT PRESERVE_TIME) - cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}") - - if(arg_FLASH_IN_PROJECT) - if(arg_PRESERVE_TIME) - fatfs_create_partition_image(${partition} ${base_dir} FLASH_IN_PROJECT PRESERVE_TIME) - else() - fatfs_create_partition_image(${partition} ${base_dir} FLASH_IN_PROJECT) - endif() - else() - if(arg_PRESERVE_TIME) - fatfs_create_partition_image(${partition} ${base_dir} PRESERVE_TIME) - else() - fatfs_create_partition_image(${partition} ${base_dir}) - endif() - endif() -endfunction() - -function(fatfs_create_spiflash_image partition base_dir) - set(options FLASH_IN_PROJECT PRESERVE_TIME) - cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}") - - if(arg_FLASH_IN_PROJECT) - if(arg_PRESERVE_TIME) - fatfs_create_partition_image(${partition} ${base_dir} FLASH_IN_PROJECT WL_INIT PRESERVE_TIME) - else() - fatfs_create_partition_image(${partition} ${base_dir} FLASH_IN_PROJECT WL_INIT) - endif() - else() - if(arg_PRESERVE_TIME) - fatfs_create_partition_image(${partition} ${base_dir} WL_INIT PRESERVE_TIME) - else() - fatfs_create_partition_image(${partition} ${base_dir} WL_INIT) - endif() - endif() -endfunction() diff --git a/third_party/fatfsgen/wl_fatfsgen.py b/third_party/fatfsgen/wl_fatfsgen.py deleted file mode 100755 index 4a685434a0752413da22256e701dd68c6ecbd1fc..0000000000000000000000000000000000000000 --- a/third_party/fatfsgen/wl_fatfsgen.py +++ /dev/null @@ -1,215 +0,0 @@ -#!/usr/bin/env python -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: Apache-2.0 - -from construct import Const, Int32ul, Struct -from fatfs_utils.exceptions import WLNotInitialized -from fatfs_utils.utils import (FULL_BYTE, UINT32_MAX, FATDefaults, crc32, generate_4bytes_random, - get_args_for_partition_generator) -from fatfsgen import FATFS - - -def remove_wl(binary_image: bytes) -> bytes: - partition_size: int = len(binary_image) - total_sectors: int = partition_size // FATDefaults.WL_SECTOR_SIZE - wl_state_size: int = WLFATFS.WL_STATE_HEADER_SIZE + WLFATFS.WL_STATE_RECORD_SIZE * total_sectors - wl_state_sectors_cnt: int = (wl_state_size + FATDefaults.WL_SECTOR_SIZE - 1) // FATDefaults.WL_SECTOR_SIZE - wl_state_total_size: int = wl_state_sectors_cnt * FATDefaults.WL_SECTOR_SIZE - wl_sectors_size: int = (wl_state_sectors_cnt - * FATDefaults.WL_SECTOR_SIZE - * WLFATFS.WL_STATE_COPY_COUNT - + FATDefaults.WL_SECTOR_SIZE) - - correct_wl_configuration = binary_image[-wl_sectors_size:] - - data_ = WLFATFS.WL_STATE_T_DATA.parse(correct_wl_configuration[:WLFATFS.WL_STATE_HEADER_SIZE]) - - total_records = 0 - # iterating over records field of the first copy of the state sector - for i in range(WLFATFS.WL_STATE_HEADER_SIZE, wl_state_total_size, WLFATFS.WL_STATE_RECORD_SIZE): - if correct_wl_configuration[i:i + WLFATFS.WL_STATE_RECORD_SIZE] != WLFATFS.WL_STATE_RECORD_SIZE * b'\xff': - total_records += 1 - else: - break - before_dummy = binary_image[:total_records * FATDefaults.WL_SECTOR_SIZE] - after_dummy = binary_image[total_records * FATDefaults.WL_SECTOR_SIZE + FATDefaults.WL_SECTOR_SIZE:] - new_image: bytes = before_dummy + after_dummy - - # remove wl sectors - new_image = new_image[:len(new_image) - (FATDefaults.WL_SECTOR_SIZE + 2 * wl_state_total_size)] - - # reorder to preserve original order - new_image = (new_image[-data_['move_count'] * FATDefaults.WL_SECTOR_SIZE:] - + new_image[:-data_['move_count'] * FATDefaults.WL_SECTOR_SIZE]) - return new_image - - -class WLFATFS: - # pylint: disable=too-many-instance-attributes - WL_CFG_SECTORS_COUNT = 1 - WL_DUMMY_SECTORS_COUNT = 1 - WL_CONFIG_HEADER_SIZE = 48 - WL_STATE_RECORD_SIZE = 16 - WL_STATE_HEADER_SIZE = 64 - WL_STATE_COPY_COUNT = 2 # always 2 copies for power failure safety - WL_SECTOR_SIZE = 0x1000 - - WL_STATE_T_DATA = Struct( - 'pos' / Int32ul, - 'max_pos' / Int32ul, - 'move_count' / Int32ul, - 'access_count' / Int32ul, - 'max_count' / Int32ul, - 'block_size' / Int32ul, - 'version' / Int32ul, - 'device_id' / Int32ul, - 'reserved' / Const(28 * b'\x00') - ) - - WL_CONFIG_T_DATA = Struct( - 'start_addr' / Int32ul, - 'full_mem_size' / Int32ul, - 'page_size' / Int32ul, - 'sector_size' / Int32ul, # always 4096 for the types of NOR flash supported by ESP-IDF! - 'updaterate' / Int32ul, - 'wr_size' / Int32ul, - 'version' / Int32ul, - 'temp_buff_size' / Int32ul - ) - WL_CONFIG_T_HEADER_SIZE = 48 - - def __init__(self, - size: int = FATDefaults.SIZE, - sector_size: int = FATDefaults.SECTOR_SIZE, - reserved_sectors_cnt: int = FATDefaults.RESERVED_SECTORS_COUNT, - fat_tables_cnt: int = FATDefaults.FAT_TABLES_COUNT, - sectors_per_cluster: int = FATDefaults.SECTORS_PER_CLUSTER, - explicit_fat_type: int = None, - hidden_sectors: int = FATDefaults.HIDDEN_SECTORS, - long_names_enabled: bool = False, - num_heads: int = FATDefaults.NUM_HEADS, - oem_name: str = FATDefaults.OEM_NAME, - sec_per_track: int = FATDefaults.SEC_PER_TRACK, - volume_label: str = FATDefaults.VOLUME_LABEL, - file_sys_type: str = FATDefaults.FILE_SYS_TYPE, - use_default_datetime: bool = True, - version: int = FATDefaults.VERSION, - temp_buff_size: int = FATDefaults.TEMP_BUFFER_SIZE, - device_id: int = None, - root_entry_count: int = FATDefaults.ROOT_ENTRIES_COUNT, - media_type: int = FATDefaults.MEDIA_TYPE) -> None: - self._initialized = False - self._version = version - self._temp_buff_size = temp_buff_size - self._device_id = device_id - self.partition_size = size - self.total_sectors = self.partition_size // FATDefaults.WL_SECTOR_SIZE - self.wl_state_size = WLFATFS.WL_STATE_HEADER_SIZE + WLFATFS.WL_STATE_RECORD_SIZE * self.total_sectors - - # determine the number of required sectors (roundup to sector size) - self.wl_state_sectors = (self.wl_state_size + FATDefaults.WL_SECTOR_SIZE - 1) // FATDefaults.WL_SECTOR_SIZE - - self.boot_sector_start = FATDefaults.WL_SECTOR_SIZE # shift by one "dummy" sector - self.fat_table_start = self.boot_sector_start + reserved_sectors_cnt * FATDefaults.WL_SECTOR_SIZE - - wl_sectors = (WLFATFS.WL_DUMMY_SECTORS_COUNT + WLFATFS.WL_CFG_SECTORS_COUNT + - self.wl_state_sectors * WLFATFS.WL_STATE_COPY_COUNT) - self.plain_fat_sectors = self.total_sectors - wl_sectors - self.plain_fatfs = FATFS( - explicit_fat_type=explicit_fat_type, - size=self.plain_fat_sectors * FATDefaults.WL_SECTOR_SIZE, - reserved_sectors_cnt=reserved_sectors_cnt, - fat_tables_cnt=fat_tables_cnt, - sectors_per_cluster=sectors_per_cluster, - sector_size=sector_size, - root_entry_count=root_entry_count, - hidden_sectors=hidden_sectors, - long_names_enabled=long_names_enabled, - num_heads=num_heads, - use_default_datetime=use_default_datetime, - oem_name=oem_name, - sec_per_track=sec_per_track, - volume_label=volume_label, - file_sys_type=file_sys_type, - media_type=media_type - ) - - self.fatfs_binary_image = self.plain_fatfs.state.binary_image - - def init_wl(self) -> None: - self.fatfs_binary_image = self.plain_fatfs.state.binary_image - self._add_dummy_sector() - # config must be added after state, do not change the order of these two calls! - self._add_state_sectors() - self._add_config_sector() - self._initialized = True - - def _add_dummy_sector(self) -> None: - self.fatfs_binary_image = FATDefaults.WL_SECTOR_SIZE * FULL_BYTE + self.fatfs_binary_image - - def _add_config_sector(self) -> None: - wl_config_data = WLFATFS.WL_CONFIG_T_DATA.build( - dict( - start_addr=0, - full_mem_size=self.partition_size, - page_size=FATDefaults.WL_SECTOR_SIZE, # equal to sector size (always 4096) - sector_size=FATDefaults.WL_SECTOR_SIZE, - updaterate=FATDefaults.UPDATE_RATE, - wr_size=FATDefaults.WR_SIZE, - version=self._version, - temp_buff_size=self._temp_buff_size - ) - ) - - crc = crc32(list(wl_config_data), UINT32_MAX) - wl_config_crc = Int32ul.build(crc) - - # adding three 4 byte zeros to align the structure - wl_config = wl_config_data + wl_config_crc + Int32ul.build(0) + Int32ul.build(0) + Int32ul.build(0) - - self.fatfs_binary_image += ( - wl_config + (FATDefaults.WL_SECTOR_SIZE - WLFATFS.WL_CONFIG_HEADER_SIZE) * FULL_BYTE) - - def _add_state_sectors(self) -> None: - wl_state_data = WLFATFS.WL_STATE_T_DATA.build( - dict( - pos=0, - max_pos=self.plain_fat_sectors + WLFATFS.WL_DUMMY_SECTORS_COUNT, - move_count=0, - access_count=0, - max_count=FATDefaults.UPDATE_RATE, - block_size=FATDefaults.WL_SECTOR_SIZE, # equal to page size, thus equal to wl sector size (4096) - version=self._version, - device_id=self._device_id or generate_4bytes_random(), - ) - ) - crc = crc32(list(wl_state_data), UINT32_MAX) - wl_state_crc = Int32ul.build(crc) - wl_state = wl_state_data + wl_state_crc - wl_state_sector_padding: bytes = (FATDefaults.WL_SECTOR_SIZE - WLFATFS.WL_STATE_HEADER_SIZE) * FULL_BYTE - wl_state_sector: bytes = ( - wl_state + wl_state_sector_padding + (self.wl_state_sectors - 1) * FATDefaults.WL_SECTOR_SIZE * FULL_BYTE - ) - self.fatfs_binary_image += (WLFATFS.WL_STATE_COPY_COUNT * wl_state_sector) - - def wl_write_filesystem(self, output_path: str) -> None: - if not self._initialized: - raise WLNotInitialized('FATFS is not initialized with WL. First call method WLFATFS.init_wl!') - with open(output_path, 'wb') as output: - output.write(bytearray(self.fatfs_binary_image)) - - -if __name__ == '__main__': - desc = 'Create a FAT filesystem with support for wear levelling and populate it with directory content' - args = get_args_for_partition_generator(desc, wl=True) - wl_fatfs = WLFATFS(sectors_per_cluster=args.sectors_per_cluster, - size=args.partition_size, - sector_size=args.sector_size, - root_entry_count=args.root_entry_count, - explicit_fat_type=args.fat_type, - long_names_enabled=args.long_name_support, - use_default_datetime=args.use_default_datetime) - - wl_fatfs.plain_fatfs.generate(args.input_directory) - wl_fatfs.init_wl() - wl_fatfs.wl_write_filesystem(args.output_file)