fatfsgen.py: enabled long names support

Closes https://github.com/espressif/esp-idf/issues/8171
This commit is contained in:
Martin Gaňo 2022-02-09 16:09:09 +01:00
parent b02ac9cab9
commit fea2b5b64e
22 changed files with 691 additions and 187 deletions

View File

@ -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)

View 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

View File

@ -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)

View File

@ -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:

View 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

View File

@ -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,

View File

@ -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})

View 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__':

View File

@ -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'])

View File

@ -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')

View File

@ -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()

View File

@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -95,7 +95,7 @@ FatFs 分区生成器
该脚本是建立在分区生成器的基础上 (:component_file:`fatfsgen.py<fatfs/fatfsgen.py>`),目前除了可以生成分区外,也可以初始化磨损均衡。
当前支持短文件名、FAT12 和 FAT16。未来计划实现对长文件名的支持
目前最新版本支持短文件名、长文件名、FAT12 和 FAT16。长文件名的上线是 255 个字符,文件名中可以包含多个 "." 字符以及其他字符如 "+"、","、";"、"="、"[" and also "]" 等。长文件名字符采用 utf-16 编码而短文件名采用 utf-8 编码
构建系统中使用 FatFs 分区生成器

View File

@ -0,0 +1 @@
This is generated on the host; long name it has

View File

@ -0,0 +1 @@
this is test; long name it has

View File

@ -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()

View File

@ -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()

View File

@ -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");

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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