mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
fatfsgen.py: enabled long names support
Closes https://github.com/espressif/esp-idf/issues/8171
This commit is contained in:
parent
b02ac9cab9
commit
fea2b5b64e
@ -42,37 +42,36 @@ class FATFS:
|
||||
assert (root_entry_count * BYTES_PER_DIRECTORY_ENTRY) % sector_size == 0
|
||||
assert ((root_entry_count * BYTES_PER_DIRECTORY_ENTRY) // sector_size) % 2 == 0
|
||||
|
||||
root_dir_sectors_cnt = (root_entry_count * BYTES_PER_DIRECTORY_ENTRY) // sector_size
|
||||
root_dir_sectors_cnt: int = (root_entry_count * BYTES_PER_DIRECTORY_ENTRY) // sector_size
|
||||
|
||||
self.state = FATFSState(entry_size=entry_size,
|
||||
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_fat=sectors_per_fat,
|
||||
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)
|
||||
binary_image = bytearray(
|
||||
self.state: FATFSState = FATFSState(entry_size=entry_size,
|
||||
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_fat=sectors_per_fat,
|
||||
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)
|
||||
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(fatfs_state=self.state,
|
||||
reserved_sectors_cnt=self.state.reserved_sectors_cnt)
|
||||
self.fat: FAT = FAT(fatfs_state=self.state, reserved_sectors_cnt=self.state.reserved_sectors_cnt)
|
||||
|
||||
self.root_directory = Directory(name='A', # the name is not important, must be string
|
||||
size=self.state.root_dir_sectors_cnt * self.state.sector_size,
|
||||
fat=self.fat,
|
||||
cluster=self.fat.clusters[1],
|
||||
fatfs_state=self.state)
|
||||
self.root_directory: Directory = Directory(name='A', # the name is not important, must be string
|
||||
size=self.state.root_dir_sectors_cnt * self.state.sector_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) -> None:
|
||||
@ -171,7 +170,8 @@ def main() -> None:
|
||||
sectors_per_cluster=args.sectors_per_cluster,
|
||||
size=args.partition_size,
|
||||
root_entry_count=args.root_entry_count,
|
||||
explicit_fat_type=args.fat_type)
|
||||
explicit_fat_type=args.fat_type,
|
||||
long_names_enabled=args.long_name_support)
|
||||
|
||||
fatfs.generate(args.input_directory)
|
||||
fatfs.write_filesystem(args.output_file)
|
||||
|
@ -1,7 +1,7 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from typing import Optional
|
||||
from typing import Dict, Optional
|
||||
|
||||
from construct import Int16ul
|
||||
|
||||
@ -14,20 +14,20 @@ class Cluster:
|
||||
"""
|
||||
class Cluster handles values in FAT table and allocates sectors in data region.
|
||||
"""
|
||||
RESERVED_BLOCK_ID = 0
|
||||
ROOT_BLOCK_ID = 1
|
||||
ALLOCATED_BLOCK_FAT12 = 0xFFF
|
||||
ALLOCATED_BLOCK_FAT16 = 0xFFFF
|
||||
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 = {FAT12: 0xFF8, FAT16: 0xFFF8}
|
||||
INITIAL_BLOCK_SWITCH: Dict[int, int] = {FAT12: 0xFF8, FAT16: 0xFFF8}
|
||||
|
||||
def __init__(self,
|
||||
cluster_id: int,
|
||||
fatfs_state: FATFSState,
|
||||
is_empty: bool = True) -> None:
|
||||
|
||||
self.id = cluster_id
|
||||
self.fatfs_state = fatfs_state
|
||||
self.id: int = cluster_id
|
||||
self.fatfs_state: FATFSState = fatfs_state
|
||||
|
||||
self._next_cluster = None # type: Optional[Cluster]
|
||||
if self.id == Cluster.RESERVED_BLOCK_ID:
|
||||
@ -35,7 +35,7 @@ class Cluster:
|
||||
self.set_in_fat(self.INITIAL_BLOCK_SWITCH[self.fatfs_state.fatfs_type])
|
||||
return
|
||||
|
||||
self.cluster_data_address = self._compute_cluster_data_address()
|
||||
self.cluster_data_address: int = self._compute_cluster_data_address()
|
||||
self.is_empty = is_empty
|
||||
|
||||
assert self.cluster_data_address or self.is_empty
|
||||
|
@ -1,71 +1,105 @@
|
||||
# SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from typing import Any, Optional
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from construct import Const, Int8ul, Int16ul, Int32ul, PaddedString, Struct
|
||||
|
||||
from .exceptions import LowerCaseException, TooLongNameException
|
||||
from .fatfs_state import FATFSState
|
||||
from .utils import is_valid_fatfs_name, pad_string
|
||||
from .utils import MAX_EXT_SIZE, MAX_NAME_SIZE, is_valid_fatfs_name, pad_string
|
||||
|
||||
|
||||
class Entry:
|
||||
"""
|
||||
The class Entry represents entry of the directory.
|
||||
"""
|
||||
ATTR_READ_ONLY = 0x01
|
||||
ATTR_HIDDEN = 0x02
|
||||
ATTR_SYSTEM = 0x04
|
||||
ATTR_VOLUME_ID = 0x08
|
||||
ATTR_DIRECTORY = 0x10
|
||||
ATTR_ARCHIVE = 0x20
|
||||
MAX_NAME_SIZE_S = 8
|
||||
MAX_EXT_SIZE_S = 3
|
||||
ATTR_READ_ONLY: int = 0x01
|
||||
ATTR_HIDDEN: int = 0x02
|
||||
ATTR_SYSTEM: int = 0x04
|
||||
ATTR_VOLUME_ID: int = 0x08
|
||||
ATTR_DIRECTORY: int = 0x10
|
||||
ATTR_ARCHIVE: int = 0x20
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
SHORT_ENTRY: int = -1
|
||||
SHORT_ENTRY_LN: int = 0
|
||||
|
||||
ENTRY_FORMAT_SHORT_NAME = Struct(
|
||||
'DIR_Name' / PaddedString(MAX_NAME_SIZE_S, 'utf-8'),
|
||||
'DIR_Name_ext' / PaddedString(MAX_EXT_SIZE_S, 'utf-8'),
|
||||
'DIR_Name' / PaddedString(MAX_NAME_SIZE, 'utf-8'),
|
||||
'DIR_Name_ext' / PaddedString(MAX_EXT_SIZE, 'utf-8'),
|
||||
'DIR_Attr' / Int8ul,
|
||||
'DIR_NTRes' / Const(b'\x00'),
|
||||
'DIR_CrtTimeTenth' / Const(b'\x00'),
|
||||
'DIR_CrtTime' / Const(b'\x01\x00'),
|
||||
'DIR_CrtTime' / Const(b'\x00\x00'),
|
||||
'DIR_CrtDate' / Const(b'\x21\x00'),
|
||||
'DIR_LstAccDate' / Const(b'\x00\x00'),
|
||||
'DIR_FstClusHI' / Const(b'\x00\x00'),
|
||||
'DIR_WrtTime' / Const(b'\x01\x00'),
|
||||
'DIR_WrtDate' / Const(b'\x01\x00'),
|
||||
'DIR_WrtTime' / Const(b'\x00\x00'),
|
||||
'DIR_WrtDate' / Const(b'\x21\x00'),
|
||||
'DIR_FstClusLO' / Int16ul,
|
||||
'DIR_FileSize' / Int32ul,
|
||||
)
|
||||
|
||||
# IDF-4044
|
||||
ENTRY_FORMAT_LONG_NAME = Struct()
|
||||
|
||||
def __init__(self,
|
||||
entry_id: int,
|
||||
parent_dir_entries_address: int,
|
||||
fatfs_state: FATFSState) -> None:
|
||||
self.fatfs_state = fatfs_state
|
||||
self.id = entry_id
|
||||
self.entry_address = parent_dir_entries_address + self.id * self.fatfs_state.entry_size
|
||||
self._is_alias = False
|
||||
self._is_empty = True
|
||||
self.fatfs_state: FATFSState = fatfs_state
|
||||
self.id: int = entry_id
|
||||
self.entry_address: int = parent_dir_entries_address + self.id * self.fatfs_state.entry_size
|
||||
self._is_alias: bool = False
|
||||
self._is_empty: bool = True
|
||||
|
||||
@property
|
||||
def is_empty(self) -> bool:
|
||||
return self._is_empty
|
||||
|
||||
def _parse_entry(self, entry_bytearray: Optional[bytearray]) -> dict:
|
||||
if self.fatfs_state.long_names_enabled:
|
||||
return Entry.ENTRY_FORMAT_LONG_NAME.parse(entry_bytearray) # type: ignore
|
||||
@staticmethod
|
||||
def _parse_entry(entry_bytearray: Optional[bytearray]) -> dict:
|
||||
return Entry.ENTRY_FORMAT_SHORT_NAME.parse(entry_bytearray) # type: ignore
|
||||
|
||||
def _build_entry(self, **kwargs) -> Any: # type: ignore
|
||||
if self.fatfs_state.long_names_enabled:
|
||||
return Entry.ENTRY_FORMAT_LONG_NAME.build(dict(**kwargs))
|
||||
@staticmethod
|
||||
def _build_entry(**kwargs) -> Any: # type: ignore
|
||||
return Entry.ENTRY_FORMAT_SHORT_NAME.build(dict(**kwargs))
|
||||
|
||||
@staticmethod
|
||||
def _build_entry_long(names: List[bytes], checksum: int, order: int, is_last: bool, entity_type: int) -> 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 |= (0x40 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(entity_type) + # 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
|
||||
|
||||
@property
|
||||
def entry_bytes(self) -> Any:
|
||||
return self.fatfs_state.binary_image[self.entry_address: self.entry_address + self.fatfs_state.entry_size]
|
||||
@ -75,51 +109,70 @@ class Entry:
|
||||
self.fatfs_state.binary_image[self.entry_address: self.entry_address + self.fatfs_state.entry_size] = value
|
||||
|
||||
def _clean_entry(self) -> None:
|
||||
self.entry_bytes = self.fatfs_state.entry_size * b'\x00'
|
||||
self.entry_bytes: bytes = self.fatfs_state.entry_size * b'\x00'
|
||||
|
||||
def allocate_entry(self,
|
||||
first_cluster_id: int,
|
||||
entity_name: str,
|
||||
entity_type: int,
|
||||
entity_extension: str = '',
|
||||
size: int = 0) -> None:
|
||||
size: int = 0,
|
||||
lfn_order: int = SHORT_ENTRY,
|
||||
lfn_names: Optional[List[bytes]] = None,
|
||||
lfn_checksum_: int = 0,
|
||||
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 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 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
|
||||
"""
|
||||
if not ((is_valid_fatfs_name(entity_name) and
|
||||
is_valid_fatfs_name(entity_extension)) or
|
||||
self.fatfs_state.long_names_enabled):
|
||||
raise LowerCaseException('Lower case is not supported because long name support is not enabled!')
|
||||
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.')
|
||||
|
||||
# clean entry before allocation
|
||||
self._clean_entry()
|
||||
self._is_empty = False
|
||||
object_name = entity_name.upper()
|
||||
object_extension = entity_extension.upper()
|
||||
|
||||
# implementation of long names support will be part of IDF-4044
|
||||
exceeds_short_name = len(object_name) > Entry.MAX_NAME_SIZE_S or len(object_extension) > Entry.MAX_EXT_SIZE_S
|
||||
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 8 characters and 3 characters for extension!')
|
||||
'Maximal length of the object name is {} characters and {} characters for extension!'.format(
|
||||
MAX_NAME_SIZE, MAX_EXT_SIZE
|
||||
))
|
||||
|
||||
start_address = self.entry_address
|
||||
end_address = start_address + self.fatfs_state.entry_size
|
||||
self.fatfs_state.binary_image[start_address: end_address] = self._build_entry(
|
||||
DIR_Name=pad_string(object_name, size=Entry.MAX_NAME_SIZE_S),
|
||||
DIR_Name_ext=pad_string(object_extension, size=Entry.MAX_EXT_SIZE_S),
|
||||
DIR_Attr=entity_type,
|
||||
DIR_FstClusLO=first_cluster_id,
|
||||
DIR_FileSize=size
|
||||
)
|
||||
if lfn_order in (self.SHORT_ENTRY, self.SHORT_ENTRY_LN):
|
||||
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_FstClusLO=first_cluster_id,
|
||||
DIR_FileSize=size
|
||||
)
|
||||
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,
|
||||
self.ATTR_LONG_NAME)
|
||||
|
||||
def update_content_size(self, content_size: int) -> None:
|
||||
parsed_entry = self._parse_entry(self.entry_bytes)
|
||||
|
@ -2,30 +2,34 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import os
|
||||
from typing import List, Optional, Tuple
|
||||
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 .utils import required_clusters_count, split_content_into_sectors, split_to_name_and_extension
|
||||
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 (MAX_EXT_SIZE, MAX_NAME_SIZE, build_lfn_short_entry_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 = 0x20
|
||||
ENTITY_TYPE = ATTR_ARCHIVE
|
||||
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 = name
|
||||
self.extension = extension
|
||||
self.fatfs_state = fatfs_state
|
||||
self.fat = fat
|
||||
self.size = 0
|
||||
self._first_cluster = None
|
||||
self._entry = entry
|
||||
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:
|
||||
@ -51,7 +55,7 @@ class File:
|
||||
if current_cluster is None:
|
||||
raise FatalError('No free space left!')
|
||||
|
||||
address = current_cluster.cluster_data_address
|
||||
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
|
||||
|
||||
@ -61,9 +65,12 @@ 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 = 0x10
|
||||
ATTR_ARCHIVE = 0x20
|
||||
ENTITY_TYPE = ATTR_DIRECTORY
|
||||
ATTR_DIRECTORY: int = 0x10
|
||||
ATTR_ARCHIVE: int = 0x20
|
||||
ENTITY_TYPE: int = ATTR_DIRECTORY
|
||||
|
||||
CURRENT_DIRECTORY = '.'
|
||||
PARENT_DIRECTORY = '..'
|
||||
|
||||
def __init__(self,
|
||||
name,
|
||||
@ -75,20 +82,20 @@ class Directory:
|
||||
extension='',
|
||||
parent=None):
|
||||
# type: (str, FAT, FATFSState, Optional[Entry], Cluster, Optional[int], str, Directory) -> None
|
||||
self.name = name
|
||||
self.fatfs_state = fatfs_state
|
||||
self.extension = extension
|
||||
self.name: str = name
|
||||
self.fatfs_state: FATFSState = fatfs_state
|
||||
self.extension: str = extension
|
||||
|
||||
self.fat = fat
|
||||
self.size = size or self.fatfs_state.sector_size
|
||||
self.fat: FAT = fat
|
||||
self.size: int = size or self.fatfs_state.sector_size
|
||||
|
||||
# if directory is root its parent is itself
|
||||
self.parent: Directory = parent or self
|
||||
self._first_cluster = cluster
|
||||
self._first_cluster: Cluster = cluster
|
||||
|
||||
# entries will be initialized after the cluster allocation
|
||||
self.entries: List[Entry] = []
|
||||
self.entities = [] # type: ignore
|
||||
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
|
||||
@ -106,7 +113,7 @@ class Directory:
|
||||
def name_equals(self, name: str, extension: str) -> bool:
|
||||
return self.name == name and self.extension == extension
|
||||
|
||||
def create_entries(self, cluster: Cluster) -> list:
|
||||
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)
|
||||
@ -114,19 +121,20 @@ class Directory:
|
||||
|
||||
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 not self.is_root:
|
||||
# the root directory doesn't contain link to itself nor the parent
|
||||
free_entry1 = self.find_free_entry() or self.chain_directory()
|
||||
free_entry1.allocate_entry(first_cluster_id=self.first_cluster.id,
|
||||
entity_name='.',
|
||||
entity_extension='',
|
||||
entity_type=self.ENTITY_TYPE)
|
||||
self_dir_reference: Entry = self.find_free_entry() or self.chain_directory()
|
||||
self_dir_reference.allocate_entry(first_cluster_id=self.first_cluster.id,
|
||||
entity_name=self.CURRENT_DIRECTORY,
|
||||
entity_extension='',
|
||||
entity_type=self.ENTITY_TYPE)
|
||||
self.first_cluster = self._first_cluster
|
||||
free_entry2 = self.find_free_entry() or self.chain_directory()
|
||||
free_entry2.allocate_entry(first_cluster_id=self.parent.first_cluster.id,
|
||||
entity_name='..',
|
||||
entity_extension='',
|
||||
entity_type=self.parent.ENTITY_TYPE)
|
||||
parent_dir_reference: Entry = self.find_free_entry() or self.chain_directory()
|
||||
parent_dir_reference.allocate_entry(first_cluster_id=self.parent.first_cluster.id,
|
||||
entity_name=self.PARENT_DIRECTORY,
|
||||
entity_extension='',
|
||||
entity_type=self.parent.ENTITY_TYPE)
|
||||
self.parent.first_cluster = self.parent.first_cluster
|
||||
|
||||
def lookup_entity(self, object_name: str, extension: str): # type: ignore
|
||||
@ -151,21 +159,57 @@ class Directory:
|
||||
return None
|
||||
|
||||
def _extend_directory(self) -> None:
|
||||
current = self.first_cluster
|
||||
current: Cluster = self.first_cluster
|
||||
while current.next_cluster is not None:
|
||||
current = current.next_cluster
|
||||
new_cluster = self.fat.find_free_cluster()
|
||||
new_cluster: Cluster = self.fat.find_free_cluster()
|
||||
current.set_in_fat(new_cluster.id)
|
||||
current.next_cluster = new_cluster
|
||||
self.entries += self.create_entries(new_cluster)
|
||||
|
||||
def chain_directory(self) -> Entry:
|
||||
self._extend_directory()
|
||||
free_entry = self.find_free_entry()
|
||||
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,
|
||||
entity_type):
|
||||
# type: (Entry, str, str, Directory, Cluster, int) -> Tuple[Cluster, Entry, Directory]
|
||||
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 = 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
|
||||
lfn_names: List[bytes] = list(
|
||||
map(lambda x: x.lower(), split_name_to_lfn_entry_blocks(name_split_to_entry))) # type: ignore
|
||||
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)
|
||||
return free_cluster, free_entry, target_dir
|
||||
|
||||
def allocate_object(self,
|
||||
name,
|
||||
entity_type,
|
||||
@ -177,14 +221,23 @@ class Directory:
|
||||
and allocates cluster (both the record in FAT and cluster in the data region)
|
||||
and entry in the specified directory
|
||||
"""
|
||||
free_cluster = self.fat.find_free_cluster()
|
||||
target_dir = self if not path_from_root else self.recursive_search(path_from_root, self)
|
||||
free_entry = target_dir.find_free_entry() or target_dir.chain_directory()
|
||||
free_entry.allocate_entry(first_cluster_id=free_cluster.id,
|
||||
entity_name=name,
|
||||
entity_extension=extension,
|
||||
entity_type=entity_type)
|
||||
return free_cluster, free_entry, target_dir
|
||||
free_cluster: Cluster = self.fat.find_free_cluster()
|
||||
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()
|
||||
|
||||
name_fits_short_struct: bool = len(name) <= MAX_NAME_SIZE and len(extension) <= MAX_EXT_SIZE
|
||||
if not self.fatfs_state.long_names_enabled or name_fits_short_struct:
|
||||
free_entry.allocate_entry(first_cluster_id=free_cluster.id,
|
||||
entity_name=name,
|
||||
entity_extension=extension,
|
||||
entity_type=entity_type)
|
||||
return free_cluster, free_entry, target_dir
|
||||
return self.allocate_long_name_object(free_entry=free_entry,
|
||||
name=name,
|
||||
extension=extension,
|
||||
target_dir=target_dir,
|
||||
free_cluster=free_cluster,
|
||||
entity_type=entity_type)
|
||||
|
||||
def new_file(self, name: str, extension: str, path_from_root: Optional[List[str]]) -> None:
|
||||
free_cluster, free_entry, target_dir = self.allocate_object(name=name,
|
||||
@ -192,7 +245,11 @@ class Directory:
|
||||
entity_type=Directory.ATTR_ARCHIVE,
|
||||
path_from_root=path_from_root)
|
||||
|
||||
file = File(name, fat=self.fat, extension=extension, fatfs_state=self.fatfs_state, entry=free_entry)
|
||||
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)
|
||||
|
||||
@ -202,7 +259,11 @@ class Directory:
|
||||
entity_type=Directory.ATTR_DIRECTORY,
|
||||
path_from_root=path_from_root)
|
||||
|
||||
directory = Directory(name=name, fat=self.fat, parent=parent, fatfs_state=self.fatfs_state, entry=free_entry)
|
||||
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)
|
||||
@ -216,9 +277,9 @@ class Directory:
|
||||
:returns: None
|
||||
:raises WriteDirectoryException: raised is the target object for writing is a directory
|
||||
"""
|
||||
entity_to_write = self.recursive_search(path, self)
|
||||
entity_to_write: Entry = self.recursive_search(path, self)
|
||||
if isinstance(entity_to_write, File):
|
||||
clusters_cnt = required_clusters_count(cluster_size=self.fatfs_state.sector_size, content=content)
|
||||
clusters_cnt: int = required_clusters_count(cluster_size=self.fatfs_state.sector_size, content=content)
|
||||
self.fat.allocate_chain(entity_to_write.first_cluster, clusters_cnt)
|
||||
entity_to_write.write(content)
|
||||
else:
|
||||
|
86
components/fatfs/fatfsgen_utils/long_filename_utils.py
Normal file
86
components/fatfs/fatfsgen_utils/long_filename_utils.py
Normal file
@ -0,0 +1,86 @@
|
||||
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
from typing import List
|
||||
|
||||
from .entry import Entry
|
||||
from .utils import 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`.
|
||||
|
||||
|
||||
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
|
||||
return [
|
||||
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)
|
||||
]
|
||||
|
||||
|
||||
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 '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 = 0
|
||||
for entity in entities:
|
||||
if entity.name[:6] == lfn_entry_name[:6]:
|
||||
preceding_entries += 1
|
||||
return preceding_entries + ord('1')
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
return f'{name}.{extension}' if len(extension) > 0 else name
|
@ -14,7 +14,13 @@ FAT16_MAX_CLUSTERS: int = 65525
|
||||
FAT12: int = 12
|
||||
FAT16: int = 16
|
||||
FAT32: int = 32
|
||||
BYTES_PER_DIRECTORY_ENTRY = 32
|
||||
BYTES_PER_DIRECTORY_ENTRY: int = 32
|
||||
UINT32_MAX: int = (1 << 32) - 1
|
||||
MAX_NAME_SIZE: int = 8
|
||||
MAX_EXT_SIZE: int = 3
|
||||
|
||||
# long names are encoded to two bytes in utf-16
|
||||
LONG_NAMES_ENCODING: str = 'utf-16'
|
||||
|
||||
|
||||
def crc32(input_values: List[int], crc: int) -> int:
|
||||
@ -36,7 +42,7 @@ def get_non_data_sectors_cnt(reserved_sectors_cnt: int, sectors_per_fat_cnt: int
|
||||
def get_fatfs_type(clusters_count: int) -> int:
|
||||
if clusters_count < FAT12_MAX_CLUSTERS:
|
||||
return FAT12
|
||||
if clusters_count < FAT16_MAX_CLUSTERS:
|
||||
if clusters_count <= FAT16_MAX_CLUSTERS:
|
||||
return FAT16
|
||||
return FAT32
|
||||
|
||||
@ -55,6 +61,35 @@ def pad_string(content: str, size: Optional[int] = None, pad: int = 0x20) -> str
|
||||
return content.ljust(size or len(content), chr(pad))[:size]
|
||||
|
||||
|
||||
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 = b'\xff',
|
||||
terminator: bytes = b'\x00\x00') -> 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):]
|
||||
terminated_encoded_content_utf16: bytes = (encoded_content_utf16 + terminator) if (2 * expected_size > len(
|
||||
encoded_content_utf16) > 0) else encoded_content_utf16
|
||||
return terminated_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('.', '')
|
||||
@ -65,7 +100,7 @@ def is_valid_fatfs_name(string: str) -> bool:
|
||||
|
||||
|
||||
def split_by_half_byte_12_bit_little_endian(value: int) -> Tuple[int, int, int]:
|
||||
value_as_bytes = Int16ul.build(value)
|
||||
value_as_bytes: bytes = Int16ul.build(value)
|
||||
return value_as_bytes[0] & 0x0f, value_as_bytes[0] >> 4, value_as_bytes[1] & 0x0f
|
||||
|
||||
|
||||
@ -91,7 +126,7 @@ def clean_second_half_byte(bytes_array: bytearray, address: int) -> None:
|
||||
|
||||
def split_content_into_sectors(content: bytes, sector_size: int) -> List[bytes]:
|
||||
result = []
|
||||
clusters_cnt = required_clusters_count(cluster_size=sector_size, content=content)
|
||||
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])
|
||||
@ -99,8 +134,7 @@ def split_content_into_sectors(content: bytes, sector_size: int) -> List[bytes]:
|
||||
|
||||
|
||||
def get_args_for_partition_generator(desc: str) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description=desc)
|
||||
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',
|
||||
@ -122,6 +156,9 @@ def get_args_for_partition_generator(desc: str) -> argparse.Namespace:
|
||||
parser.add_argument('--root_entry_count',
|
||||
default=512,
|
||||
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('--fat_type',
|
||||
default=0,
|
||||
type=int,
|
||||
|
@ -44,6 +44,13 @@ function(fatfs_create_partition_image partition base_dir)
|
||||
set(fatfs_sector_size 4096)
|
||||
endif()
|
||||
|
||||
if("${CONFIG_FATFS_LFN_NONE}")
|
||||
set(fatfs_long_names_option)
|
||||
elseif("${CONFIG_FATFS_LFN_HEAP}")
|
||||
set(fatfs_long_names_option --long_name_support)
|
||||
elseif("${CONFIG_FATFS_LFN_STACK}")
|
||||
set(fatfs_long_names_option --long_name_support)
|
||||
endif()
|
||||
|
||||
if("${CONFIG_FATFS_AUTO_TYPE}")
|
||||
set(fatfs_explicit_type 0)
|
||||
@ -63,13 +70,13 @@ function(fatfs_create_partition_image partition base_dir)
|
||||
# contents of the base dir changing.
|
||||
add_custom_target(fatfs_${partition}_bin ALL
|
||||
COMMAND ${fatfsgen_py} ${base_dir_full_path}
|
||||
${fatfs_long_names_option}
|
||||
--partition_size ${size}
|
||||
--output_file ${image_file}
|
||||
--sector_size "${fatfs_sector_size}"
|
||||
--sectors_per_cluster "${sectors_per_cluster}"
|
||||
--fat_type "${fatfs_explicit_type}"
|
||||
)
|
||||
|
||||
set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY
|
||||
ADDITIONAL_MAKE_CLEAN_FILES
|
||||
${image_file})
|
||||
|
@ -6,25 +6,32 @@ import os
|
||||
import shutil
|
||||
import sys
|
||||
import unittest
|
||||
from subprocess import STDOUT, check_output
|
||||
|
||||
from test_utils import CFG, fill_sector, generate_test_dir_1, generate_test_dir_2
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||
import fatfsgen # noqa E402 # pylint: disable=C0413
|
||||
from fatfsgen_utils.exceptions import InconsistentFATAttributes # noqa E402 # pylint: disable=C0413
|
||||
from fatfsgen_utils.exceptions import TooLongNameException # noqa E402 # pylint: disable=C0413
|
||||
from fatfsgen_utils.exceptions import WriteDirectoryException # noqa E402 # pylint: disable=C0413
|
||||
from fatfsgen_utils.exceptions import LowerCaseException, NoFreeClusterException # noqa E402 # pylint: disable=C0413
|
||||
from fatfsgen_utils.utils import read_filesystem # noqa E402 # pylint: disable=C0413
|
||||
from fatfsgen_utils.utils import FAT12, read_filesystem # noqa E402 # pylint: disable=C0413
|
||||
|
||||
|
||||
class FatFSGen(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
os.makedirs('output_data')
|
||||
os.makedirs('test_dir')
|
||||
generate_test_dir_1()
|
||||
generate_test_dir_2()
|
||||
|
||||
def tearDown(self) -> None:
|
||||
shutil.rmtree('output_data')
|
||||
shutil.rmtree('test_dir')
|
||||
|
||||
if os.path.exists('fatfs_image.img'):
|
||||
os.remove('fatfs_image.img')
|
||||
|
||||
def test_empty_file_sn_fat12(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
@ -79,7 +86,7 @@ class FatFSGen(unittest.TestCase):
|
||||
self.assertEqual(
|
||||
file_system[0x1000:0x1010],
|
||||
b'\xf8\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6040:0x6050], b'WRITEF TXT\x20\x00\x00\x01\x00')
|
||||
self.assertEqual(file_system[0x6040:0x6050], b'WRITEF TXT\x20\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x605a:0x6060], b'\x03\x00\x0b\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x7000:0x700b], b'testcontent') # check file content
|
||||
|
||||
@ -129,8 +136,8 @@ class FatFSGen(unittest.TestCase):
|
||||
self.assertEqual(file_system[0x1000: 0x10d0],
|
||||
b'\xf8\xff\xff\x82\xf0\xff' + 192 * b'\xff' + 10 * b'\x00')
|
||||
self.assertEqual(file_system[0x85000:0x85005], b'later')
|
||||
self.assertEqual(file_system[0x86000:0x86010], b'A126 \x00\x00\x01\x00')
|
||||
self.assertEqual(file_system[0x86020:0x86030], b'A127 \x00\x00\x01\x00')
|
||||
self.assertEqual(file_system[0x86000:0x86010], b'A126 \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x86020:0x86030], b'A127 \x00\x00\x00\x00')
|
||||
|
||||
def test_write_to_folder_in_folder_sn_fat12(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
@ -209,13 +216,13 @@ class FatFSGen(unittest.TestCase):
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
|
||||
self.assertEqual(file_system[0x2000:0x2010], b'TESTFOLD \x10\x00\x00\x01\x00')
|
||||
self.assertEqual(file_system[0x2010:0x2020], b'!\x00\x00\x00\x00\x00\x01\x00\x01\x00\x02\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6040:0x6050], b'TESTFOLD \x10\x00\x00\x01\x00')
|
||||
self.assertEqual(file_system[0x6040:0x6050], b'TESTFOLD \x10\x00\x00\x01\x00')
|
||||
self.assertEqual(file_system[0x2000:0x2010], b'TESTFOLD \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x2010:0x2020], b'!\x00\x00\x00\x00\x00\x00\x00\x21\x00\x02\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6040:0x6050], b'TESTFOLD \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6040:0x6050], b'TESTFOLD \x10\x00\x00\x00\x00')
|
||||
|
||||
self.assertEqual(file_system[0x7040:0x7050], b'TESTFOLD \x10\x00\x00\x01\x00')
|
||||
self.assertEqual(file_system[0x8040:0x8050], b'WRITEF TXT \x00\x00\x01\x00')
|
||||
self.assertEqual(file_system[0x7040:0x7050], b'TESTFOLD \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x8040:0x8050], b'WRITEF TXT \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x9000:0x9010], b'later\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
|
||||
def test_e2e_deep_folder_into_image(self) -> None:
|
||||
@ -223,9 +230,9 @@ class FatFSGen(unittest.TestCase):
|
||||
fatfs.generate(CFG['test_dir'])
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x6060:0x6070], b'TESTFIL2 \x00\x00\x01\x00')
|
||||
self.assertEqual(file_system[0x6070:0x6080], b'!\x00\x00\x00\x00\x00\x01\x00\x01\x00\x05\x00\x0b\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x7040:0x7050], b'LASTFILE \x00\x00\x01\x00')
|
||||
self.assertEqual(file_system[0x6060:0x6070], b'TESTFIL2 \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6070:0x6080], b'!\x00\x00\x00\x00\x00\x00\x00\x21\x00\x05\x00\x0b\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x7040:0x7050], b'LASTFILE \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x8000:0x8010], b'deeptest\n\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x9000:0x9010], b'thisistest\n\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0xa000:0xa010], b'ahoj\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
@ -236,10 +243,10 @@ class FatFSGen(unittest.TestCase):
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
|
||||
self.assertEqual(file_system[0x2020:0x2030], b'TESTFILE \x00\x00\x01\x00')
|
||||
self.assertEqual(file_system[0x6060:0x6070], b'TESTFIL2 \x00\x00\x01\x00')
|
||||
self.assertEqual(file_system[0x7000:0x7010], b'. \x10\x00\x00\x01\x00')
|
||||
self.assertEqual(file_system[0x7040:0x7050], b'LASTFILETXT \x00\x00\x01\x00')
|
||||
self.assertEqual(file_system[0x2020:0x2030], b'TESTFILE \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6060:0x6070], b'TESTFIL2 \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x7000:0x7010], b'. \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x7040:0x7050], b'LASTFILETXT \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x8000:0x8010], b'deeptest\n\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x9000:0x9010], b'thisistest\n\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0xa000:0xa010], b'ahoj\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
@ -279,8 +286,192 @@ class FatFSGen(unittest.TestCase):
|
||||
self.assertEqual(file_system[0x1000: 0x1110],
|
||||
b'\xf8\xff\xff\xff\x82\x00' + 258 * b'\xff' + 8 * b'\x00')
|
||||
self.assertEqual(file_system[0x85000:0x85005], b'later')
|
||||
self.assertEqual(file_system[0x86000:0x86010], b'A126 \x00\x00\x01\x00')
|
||||
self.assertEqual(file_system[0x86020:0x86030], b'A127 \x00\x00\x01\x00')
|
||||
self.assertEqual(file_system[0x86000:0x86010], b'A126 \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x86020:0x86030], b'A127 \x00\x00\x00\x00')
|
||||
|
||||
def test_empty_lfn_short_name(self) -> None:
|
||||
fatfs = fatfsgen.FATFS(long_names_enabled=True)
|
||||
fatfs.create_file('HELLO', extension='TXT')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x2000: 0x2019], b'HELLO TXT \x00\x00\x00\x00!\x00\x00\x00\x00\x00\x00\x00!')
|
||||
|
||||
def test_lfn_short_name(self) -> None:
|
||||
fatfs = fatfsgen.FATFS(long_names_enabled=True)
|
||||
fatfs.create_file('HELLO', extension='TXT')
|
||||
fatfs.write_content(path_from_root=['HELLO.TXT'], content=b'this is a test')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x2000: 0x2010], b'HELLO TXT \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x2010: 0x2020], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x0e\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6000: 0x6010], b'this is a test\x00\x00')
|
||||
|
||||
def test_lfn_empty_name(self) -> None:
|
||||
fatfs = fatfsgen.FATFS(long_names_enabled=True)
|
||||
fatfs.create_file('HELLOHELLOHELLO', extension='TXT')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x2000: 0x2010], b'Bl\x00o\x00.\x00t\x00x\x00\x0f\x00\xb3t\x00')
|
||||
self.assertEqual(file_system[0x2012: 0x2020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
|
||||
self.assertEqual(file_system[0x2020: 0x2030], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00\xb3h\x00')
|
||||
self.assertEqual(file_system[0x2030: 0x2040], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00')
|
||||
self.assertEqual(file_system[0x2040: 0x2050], b'HELLOH~1TXT \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
|
||||
|
||||
def test_lfn_plain_name(self) -> None:
|
||||
fatfs = fatfsgen.FATFS(long_names_enabled=True)
|
||||
fatfs.create_file('HELLOHELLOHELLO', extension='TXT')
|
||||
fatfs.write_content(path_from_root=['HELLOHELLOHELLO.TXT'], content=b'this is a test')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x2000: 0x2010], b'Bl\x00o\x00.\x00t\x00x\x00\x0f\x00\xb3t\x00')
|
||||
self.assertEqual(file_system[0x2012: 0x2020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
|
||||
self.assertEqual(file_system[0x2020: 0x2030], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00\xb3h\x00')
|
||||
self.assertEqual(file_system[0x2030: 0x2040], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00')
|
||||
self.assertEqual(file_system[0x2040: 0x2050], b'HELLOH~1TXT \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x0e\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6000: 0x6010], b'this is a test\x00\x00')
|
||||
|
||||
def test_lfn_plain_name_no_ext(self) -> None:
|
||||
fatfs = fatfsgen.FATFS(long_names_enabled=True)
|
||||
fatfs.create_file('HELLOHELLOHELLO')
|
||||
fatfs.write_content(path_from_root=['HELLOHELLOHELLO'], content=b'this is a test')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x2000: 0x2010], b'Bl\x00o\x00\x00\x00\xff\xff\xff\xff\x0f\x00V\xff\xff')
|
||||
self.assertEqual(file_system[0x2012: 0x2020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
|
||||
self.assertEqual(file_system[0x2020: 0x2030], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00Vh\x00')
|
||||
self.assertEqual(file_system[0x2030: 0x2040], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00')
|
||||
self.assertEqual(file_system[0x2040: 0x2050], b'HELLOH~1 \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x0e\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6000: 0x6010], b'this is a test\x00\x00')
|
||||
|
||||
def test_lfn_short_entry_exception(self) -> None:
|
||||
fatfs = fatfsgen.FATFS(long_names_enabled=True)
|
||||
self.assertRaises(LowerCaseException, fatfs.create_file, 'hello', extension='txt')
|
||||
|
||||
def test_lfn_nested_empty(self) -> None:
|
||||
fatfs = fatfsgen.FATFS(long_names_enabled=True)
|
||||
fatfs.create_directory('VERYLONGTESTFOLD')
|
||||
fatfs.create_file('HELLO', extension='TXT', path_from_root=['VERYLONGTESTFOLD'])
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x2000: 0x2010], b'Bo\x00l\x00d\x00\x00\x00\xff\xff\x0f\x00\xa6\xff\xff')
|
||||
self.assertEqual(file_system[0x2012: 0x2020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
|
||||
self.assertEqual(file_system[0x2020: 0x2030], b'\x01v\x00e\x00r\x00y\x00l\x00\x0f\x00\xa6o\x00')
|
||||
self.assertEqual(file_system[0x2030: 0x2040], b'n\x00g\x00t\x00e\x00s\x00\x00\x00t\x00f\x00')
|
||||
self.assertEqual(file_system[0x2040: 0x2050], b'VERYLO~1 \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
|
||||
|
||||
self.assertEqual(file_system[0x6000: 0x6010], b'. \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6012: 0x6020], b'\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6020: 0x6030], b'.. \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6030: 0x6040], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6040: 0x6050], b'HELLO TXT \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6050: 0x6060], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x03\x00\x00\x00\x00\x00')
|
||||
|
||||
def test_lfn_nested_long_empty(self) -> None:
|
||||
fatfs: fatfsgen.FATFS = fatfsgen.FATFS(long_names_enabled=True)
|
||||
fatfs.create_directory('verylongtestfold')
|
||||
fatfs.create_file('hellohellohello', extension='txt', path_from_root=['verylongtestfold'])
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x2000: 0x2010], b'Bo\x00l\x00d\x00\x00\x00\xff\xff\x0f\x00\x10\xff\xff')
|
||||
self.assertEqual(file_system[0x2012: 0x2020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
|
||||
self.assertEqual(file_system[0x2020: 0x2030], b'\x01v\x00e\x00r\x00y\x00l\x00\x0f\x00\x10o\x00')
|
||||
self.assertEqual(file_system[0x2030: 0x2040], b'n\x00g\x00t\x00e\x00s\x00\x00\x00t\x00f\x00')
|
||||
self.assertEqual(file_system[0x2040: 0x2050], b'verylo~1 \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
|
||||
|
||||
self.assertEqual(file_system[0x6000: 0x6010], b'. \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6012: 0x6020], b'\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6020: 0x6030], b'.. \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6030: 0x6040], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6040: 0x6050], b'Bl\x00o\x00.\x00t\x00x\x00\x0f\x00\xd5t\x00')
|
||||
self.assertEqual(file_system[0x6050: 0x6060],
|
||||
b'\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
|
||||
|
||||
def test_lfn_nested_text(self) -> None:
|
||||
fatfs: fatfsgen.FATFS = fatfsgen.FATFS(long_names_enabled=True)
|
||||
fatfs.create_directory('VERYLONGTESTFOLD')
|
||||
fatfs.create_file('HELLO', extension='TXT', path_from_root=['VERYLONGTESTFOLD'])
|
||||
fatfs.write_content(path_from_root=['VERYLONGTESTFOLD', 'HELLO.TXT'], content=b'this is a test')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x2000: 0x2010], b'Bo\x00l\x00d\x00\x00\x00\xff\xff\x0f\x00\xa6\xff\xff')
|
||||
self.assertEqual(file_system[0x2012: 0x2020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
|
||||
self.assertEqual(file_system[0x2020: 0x2030], b'\x01v\x00e\x00r\x00y\x00l\x00\x0f\x00\xa6o\x00')
|
||||
self.assertEqual(file_system[0x2030: 0x2040], b'n\x00g\x00t\x00e\x00s\x00\x00\x00t\x00f\x00')
|
||||
self.assertEqual(file_system[0x2040: 0x2050], b'VERYLO~1 \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
|
||||
|
||||
self.assertEqual(file_system[0x6000: 0x6010], b'. \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6012: 0x6020], b'\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6020: 0x6030], b'.. \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6030: 0x6040], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6040: 0x6050], b'HELLO TXT \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6050: 0x6060], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x03\x00\x0e\x00\x00\x00')
|
||||
|
||||
self.assertEqual(file_system[0x7000: 0x7010], b'this is a test\x00\x00')
|
||||
|
||||
def test_boundary_clusters12(self) -> None:
|
||||
output: bytes = check_output(['python', '../fatfsgen.py', '--partition_size', '16756736', 'test_dir'],
|
||||
stderr=STDOUT)
|
||||
self.assertEqual(
|
||||
output,
|
||||
b'WARNING: It is not recommended to create FATFS with bounding count of clusters: 4085 or 65525\n')
|
||||
|
||||
def test_boundary_clusters16(self) -> None:
|
||||
output: bytes = check_output(['python', '../fatfsgen.py', '--partition_size', '268415097', 'test_dir'],
|
||||
stderr=STDOUT)
|
||||
self.assertEqual(
|
||||
output,
|
||||
b'WARNING: It is not recommended to create FATFS with bounding count of clusters: 4085 or 65525\n')
|
||||
|
||||
def test_boundary_clusters_fat32(self) -> None:
|
||||
self.assertRaises(NotImplementedError, fatfsgen.FATFS, size=268419193)
|
||||
|
||||
def test_inconsistent_fat12(self) -> None:
|
||||
self.assertRaises(InconsistentFATAttributes, fatfsgen.FATFS, size=268411001, explicit_fat_type=FAT12)
|
||||
|
||||
def test_lfn_increasing(self) -> None:
|
||||
fatfs: fatfsgen.FATFS = fatfsgen.FATFS(long_names_enabled=True)
|
||||
fatfs.create_directory('VERYLONGTESTFOLD')
|
||||
fatfs.create_file('HELLOHELLOHELLOOOOOOO', extension='TXT', path_from_root=['VERYLONGTESTFOLD'])
|
||||
fatfs.create_file('HELLOHELLOHELLOOOOOOB', extension='TXT', path_from_root=['VERYLONGTESTFOLD'])
|
||||
fatfs.write_content(path_from_root=['VERYLONGTESTFOLD', 'HELLOHELLOHELLOOOOOOO.TXT'],
|
||||
content=b'this is a test A')
|
||||
fatfs.write_content(path_from_root=['VERYLONGTESTFOLD', 'HELLOHELLOHELLOOOOOOB.TXT'],
|
||||
content=b'this is a test B')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
|
||||
self.assertEqual(file_system[0x2000: 0x2010], b'Bo\x00l\x00d\x00\x00\x00\xff\xff\x0f\x00\xa6\xff\xff')
|
||||
self.assertEqual(file_system[0x2011: 0x2020], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
|
||||
self.assertEqual(file_system[0x2020: 0x2030], b'\x01v\x00e\x00r\x00y\x00l\x00\x0f\x00\xa6o\x00')
|
||||
self.assertEqual(file_system[0x2030: 0x2040], b'n\x00g\x00t\x00e\x00s\x00\x00\x00t\x00f\x00')
|
||||
self.assertEqual(file_system[0x2040: 0x2050], b'VERYLO~1 \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
|
||||
|
||||
self.assertEqual(file_system[0x6000: 0x6010], b'. \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6011: 0x6020], b'\x00\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6020: 0x6030], b'.. \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6030: 0x6040], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6040: 0x6050], b'Bl\x00o\x00o\x00o\x00o\x00\x0f\x00\xb3o\x00')
|
||||
self.assertEqual(file_system[0x6050: 0x6060], b'o\x00o\x00.\x00t\x00x\x00\x00\x00t\x00\x00\x00')
|
||||
|
||||
self.assertEqual(file_system[0x6050: 0x6060], b'o\x00o\x00.\x00t\x00x\x00\x00\x00t\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6060: 0x6070], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00\xb3h\x00')
|
||||
self.assertEqual(file_system[0x6070: 0x6080], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00')
|
||||
self.assertEqual(file_system[0x6080: 0x6090], b'HELLOH~1TXT \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6090: 0x60a0], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x03\x00\x10\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x60a0: 0x60b0], b'Bl\x00o\x00o\x00o\x00o\x00\x0f\x00\x93o\x00')
|
||||
|
||||
self.assertEqual(file_system[0x60b0: 0x60c0], b'o\x00b\x00.\x00t\x00x\x00\x00\x00t\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x60c0: 0x60d0], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00\x93h\x00')
|
||||
self.assertEqual(file_system[0x60d0: 0x60e0], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00')
|
||||
self.assertEqual(file_system[0x60e0: 0x60f0], b'HELLOH~2TXT \x00\x00\x00\x00')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -39,6 +39,6 @@ def generate_test_dir_2() -> None:
|
||||
file.write('ahoj\n')
|
||||
|
||||
|
||||
def fill_sector(fatfs: fatfsgen.FATFS) -> None:
|
||||
def fill_sector(fatfs: fatfsgen.FATFS, file_prefix: str = 'A') -> None:
|
||||
for i in range(CFG['sector_size'] // CFG['entry_size']):
|
||||
fatfs.create_file(f'A{str(i).upper()}', path_from_root=['TESTFOLD'])
|
||||
fatfs.create_file(f'{file_prefix}{str(i).upper()}', path_from_root=['TESTFOLD'])
|
||||
|
@ -116,10 +116,10 @@ class WLFatFSGen(unittest.TestCase):
|
||||
with open(CFG['output_file'], 'rb') as fs_file:
|
||||
file_system = bytearray(fs_file.read())
|
||||
|
||||
self.assertEqual(file_system[0x3020:0x3030], b'TESTFILE \x00\x00\x01\x00')
|
||||
self.assertEqual(file_system[0x7060:0x7070], b'TESTFIL2 \x00\x00\x01\x00')
|
||||
self.assertEqual(file_system[0x8000:0x8010], b'. \x10\x00\x00\x01\x00')
|
||||
self.assertEqual(file_system[0x8040:0x8050], b'LASTFILETXT \x00\x00\x01\x00')
|
||||
self.assertEqual(file_system[0x3020:0x3030], b'TESTFILE \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x7060:0x7070], b'TESTFIL2 \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x8000:0x8010], b'. \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x8040:0x8050], b'LASTFILETXT \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x9000:0x9010], b'deeptest\n\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0xa000:0xa010], b'thisistest\n\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0xb000:0xb010], b'ahoj\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
@ -133,9 +133,9 @@ class WLFatFSGen(unittest.TestCase):
|
||||
with open(CFG['output_file'], 'rb') as fs_file:
|
||||
file_system = bytearray(fs_file.read())
|
||||
|
||||
self.assertEqual(file_system[0x7060:0x7070], b'TESTFIL2 \x00\x00\x01\x00')
|
||||
self.assertEqual(file_system[0x7070:0x7080], b'!\x00\x00\x00\x00\x00\x01\x00\x01\x00\x05\x00\x0b\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x8040:0x8050], b'LASTFILE \x00\x00\x01\x00')
|
||||
self.assertEqual(file_system[0x7060:0x7070], b'TESTFIL2 \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x7070:0x7080], b'!\x00\x00\x00\x00\x00\x00\x00\x21\x00\x05\x00\x0b\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x8040:0x8050], b'LASTFILE \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x9000:0x9010], b'deeptest\n\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0xa000:0xa010], b'thisistest\n\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0xb000:0xb010], b'ahoj\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
|
@ -7,7 +7,7 @@ from typing import List, Optional
|
||||
from construct import Const, Int32ul, Struct
|
||||
from fatfsgen import FATFS
|
||||
from fatfsgen_utils.exceptions import WLNotInitialized
|
||||
from fatfsgen_utils.utils import crc32, generate_4bytes_random, get_args_for_partition_generator
|
||||
from fatfsgen_utils.utils import UINT32_MAX, crc32, generate_4bytes_random, get_args_for_partition_generator
|
||||
|
||||
|
||||
class WLFATFS:
|
||||
@ -18,7 +18,6 @@ class WLFATFS:
|
||||
WL_STATE_RECORD_SIZE = 16
|
||||
WL_STATE_HEADER_SIZE = 64
|
||||
WL_STATE_COPY_COUNT = 2
|
||||
UINT32_MAX = 4294967295
|
||||
WL_SECTOR_SIZE = 0x1000
|
||||
|
||||
WL_STATE_T_DATA = Struct(
|
||||
@ -136,7 +135,7 @@ class WLFATFS:
|
||||
)
|
||||
)
|
||||
|
||||
crc = crc32(list(wl_config_data), WLFATFS.UINT32_MAX)
|
||||
crc = crc32(list(wl_config_data), UINT32_MAX)
|
||||
wl_config_crc = Int32ul.build(crc)
|
||||
|
||||
# adding three 4 byte zeros to align the structure
|
||||
@ -157,7 +156,7 @@ class WLFATFS:
|
||||
device_id=self._device_id or generate_4bytes_random(),
|
||||
)
|
||||
)
|
||||
crc = crc32(list(wl_state_data), WLFATFS.UINT32_MAX)
|
||||
crc = crc32(list(wl_state_data), UINT32_MAX)
|
||||
wl_state_crc = Int32ul.build(crc)
|
||||
wl_state = wl_state_data + wl_state_crc
|
||||
self.fatfs_binary_image += WLFATFS.WL_STATE_COPY_COUNT * (
|
||||
@ -194,7 +193,8 @@ if __name__ == '__main__':
|
||||
sectors_per_cluster=args.sectors_per_cluster,
|
||||
size=args.partition_size,
|
||||
root_entry_count=args.root_entry_count,
|
||||
explicit_fat_type=args.fat_type)
|
||||
explicit_fat_type=args.fat_type,
|
||||
long_names_enabled=args.long_name_support)
|
||||
|
||||
wl_fatfs.wl_generate(args.input_directory)
|
||||
wl_fatfs.init_wl()
|
||||
|
@ -95,8 +95,7 @@ The tool is used to create filesystem images on a host and populate it with cont
|
||||
|
||||
The script is based on the partition generator (:component_file:`fatfsgen.py<fatfs/fatfsgen.py>`) and except for generating partition also initializes wear levelling.
|
||||
|
||||
Current implementation supports short file names, FAT12 and FAT16. Long file names support is the subject of the future work.
|
||||
|
||||
The latest version supports both short and long file names, FAT12 and FAT16. The long file names are limited to 255 characters, and can contain multiple period (".") characters within the filename and additional characters "+", ",", ";", "=", "[" and also "]". The long files name characters are encoded using utf-16, on the contrary to short file names encoded using utf-8.
|
||||
|
||||
Build system integration with FATFS partition generator
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -95,7 +95,7 @@ FatFs 分区生成器
|
||||
|
||||
该脚本是建立在分区生成器的基础上 (:component_file:`fatfsgen.py<fatfs/fatfsgen.py>`),目前除了可以生成分区外,也可以初始化磨损均衡。
|
||||
|
||||
当前支持短文件名、FAT12 和 FAT16。未来计划实现对长文件名的支持。
|
||||
目前最新版本支持短文件名、长文件名、FAT12 和 FAT16。长文件名的上线是 255 个字符,文件名中可以包含多个 "." 字符以及其他字符如 "+"、","、";"、"="、"[" and also "]" 等。长文件名字符采用 utf-16 编码而短文件名采用 utf-8 编码。
|
||||
|
||||
|
||||
构建系统中使用 FatFs 分区生成器
|
||||
|
@ -0,0 +1 @@
|
||||
This is generated on the host; long name it has
|
@ -0,0 +1 @@
|
||||
this is test; long name it has
|
@ -1,5 +1,5 @@
|
||||
# SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
from typing import Optional
|
||||
|
||||
import ttfw_idf
|
||||
@ -31,6 +31,30 @@ def test_examples_fatfsgen(env: ttfw_idf.TinyFW.Env, _: Optional[list]) -> None:
|
||||
timeout=20)
|
||||
env.close_dut(dut.name)
|
||||
|
||||
dut = env.get_dut('fatfsgen', 'examples/storage/fatfsgen', app_config_name='test_read_write_partition_gen_ln')
|
||||
dut.start_app()
|
||||
dut.expect_all('example: Mounting FAT filesystem',
|
||||
'example: Opening file',
|
||||
'example: File written',
|
||||
'example: Reading file',
|
||||
'example: Read from file: \'This is written by the device\'',
|
||||
'example: Reading file',
|
||||
'example: Read from file: \'This is generated on the host; long name it has\'',
|
||||
'example: Unmounting FAT filesystem',
|
||||
'example: Done',
|
||||
timeout=20)
|
||||
env.close_dut(dut.name)
|
||||
|
||||
dut = env.get_dut('fatfsgen', 'examples/storage/fatfsgen', app_config_name='test_read_only_partition_gen_ln')
|
||||
dut.start_app()
|
||||
dut.expect_all('example: Mounting FAT filesystem',
|
||||
'example: Reading file',
|
||||
'example: Read from file: \'this is test; long name it has\'',
|
||||
'example: Unmounting FAT filesystem',
|
||||
'example: Done',
|
||||
timeout=20)
|
||||
env.close_dut(dut.name)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_fatfsgen()
|
||||
|
@ -9,8 +9,14 @@ idf_component_register(SRCS "fatfsgen_example_main.c"
|
||||
# the generated image will be raw without wear levelling support.
|
||||
# Otherwise it will support wear levelling and thus enable read-write mounting of the image in the device.
|
||||
|
||||
if(CONFIG_EXAMPLE_FATFS_MODE_READ_ONLY)
|
||||
fatfs_create_rawflash_image(storage ../fatfs_image FLASH_IN_PROJECT)
|
||||
if(CONFIG_FATFS_LFN_NONE)
|
||||
set(image ../fatfs_image)
|
||||
else()
|
||||
fatfs_create_spiflash_image(storage ../fatfs_image FLASH_IN_PROJECT)
|
||||
set(image ../fatfs_long_name_image)
|
||||
endif()
|
||||
|
||||
if(CONFIG_EXAMPLE_FATFS_MODE_READ_ONLY)
|
||||
fatfs_create_rawflash_image(storage ${image} FLASH_IN_PROJECT)
|
||||
else()
|
||||
fatfs_create_spiflash_image(storage ${image} FLASH_IN_PROJECT)
|
||||
endif()
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
@ -18,6 +18,12 @@
|
||||
#define EXAMPLE_FATFS_MODE_READ_ONLY false
|
||||
#endif
|
||||
|
||||
#if CONFIG_FATFS_LFN_NONE
|
||||
#define EXAMPLE_FATFS_LONG_NAMES false
|
||||
#else
|
||||
#define EXAMPLE_FATFS_LONG_NAMES true
|
||||
#endif
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
|
||||
@ -50,10 +56,17 @@ void app_main(void)
|
||||
}
|
||||
|
||||
char line[128];
|
||||
char *device_filename;
|
||||
if (EXAMPLE_FATFS_LONG_NAMES){
|
||||
device_filename = "/spiflash/innerbutverylongname.txt";
|
||||
} else {
|
||||
device_filename = "/spiflash/inner.txt";
|
||||
}
|
||||
|
||||
if (!EXAMPLE_FATFS_MODE_READ_ONLY){
|
||||
// Open file for reading
|
||||
ESP_LOGI(TAG, "Opening file");
|
||||
FILE *f = fopen("/spiflash/inner.txt", "wb");
|
||||
FILE *f = fopen(device_filename, "wb");
|
||||
if (f == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to open file for writing");
|
||||
return;
|
||||
@ -64,7 +77,7 @@ void app_main(void)
|
||||
|
||||
// Open file for reading
|
||||
ESP_LOGI(TAG, "Reading file");
|
||||
f = fopen("/spiflash/inner.txt", "rb");
|
||||
f = fopen(device_filename, "rb");
|
||||
if (f == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to open file for reading");
|
||||
return;
|
||||
@ -82,10 +95,21 @@ void app_main(void)
|
||||
FILE *f;
|
||||
char *pos;
|
||||
ESP_LOGI(TAG, "Reading file");
|
||||
char *host_filename1;
|
||||
char *host_filename2;
|
||||
|
||||
if (EXAMPLE_FATFS_LONG_NAMES){
|
||||
host_filename1 = "/spiflash/sublongnames/testlongfilenames.txt";
|
||||
host_filename2 = "/spiflash/hellolongname.txt";
|
||||
} else{
|
||||
host_filename1 = "/spiflash/sub/test.txt";
|
||||
host_filename2 = "/spiflash/hello.txt";
|
||||
}
|
||||
|
||||
if (EXAMPLE_FATFS_MODE_READ_ONLY){
|
||||
f = fopen("/spiflash/sub/test.txt", "rb");
|
||||
f = fopen(host_filename1, "rb");
|
||||
} else {
|
||||
f = fopen("/spiflash/hello.txt", "rb");
|
||||
f = fopen(host_filename2, "rb");
|
||||
}
|
||||
if (f == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to open file for reading");
|
||||
|
@ -1 +1,4 @@
|
||||
CONFIG_EXAMPLE_FATFS_MODE_READ_ONLY=y
|
||||
CONFIG_FATFS_LFN_HEAP=n
|
||||
CONFIG_FATFS_LFN_STACK=n
|
||||
CONFIG_FATFS_LFN_NONE=y
|
||||
|
@ -0,0 +1,4 @@
|
||||
CONFIG_EXAMPLE_FATFS_MODE_READ_ONLY=y
|
||||
CONFIG_FATFS_LFN_HEAP=y
|
||||
CONFIG_FATFS_LFN_NONE=n
|
||||
CONFIG_FATFS_LFN_STACK=n
|
@ -1 +1,4 @@
|
||||
CONFIG_EXAMPLE_FATFS_MODE_READ_ONLY=n
|
||||
CONFIG_FATFS_LFN_HEAP=n
|
||||
CONFIG_FATFS_LFN_STACK=n
|
||||
CONFIG_FATFS_LFN_NONE=y
|
||||
|
@ -0,0 +1,4 @@
|
||||
CONFIG_EXAMPLE_FATFS_MODE_READ_ONLY=n
|
||||
CONFIG_FATFS_LFN_HEAP=y
|
||||
CONFIG_FATFS_LFN_NONE=n
|
||||
CONFIG_FATFS_LFN_STACK=n
|
Loading…
x
Reference in New Issue
Block a user