feat(fatfs): Add an option to use 2 FATs (file allocation tables) in FATFS volumes

Should help with redundancy and data corruption when enabled but uses more space.
This commit is contained in:
Adam Múdry 2024-01-15 01:34:02 +01:00
parent 813dbe5526
commit 8e50d63fea
15 changed files with 697 additions and 104 deletions

View File

@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
from inspect import getmembers, isroutine
from typing import Optional
@ -64,12 +64,13 @@ class BootSector:
raise NotInitialized('The BootSectorState instance is not initialized!')
volume_uuid = generate_4bytes_random()
pad_header: bytes = (boot_sector_state.sector_size - BootSector.BOOT_HEADER_SIZE) * EMPTY_BYTE
data_content: bytes = boot_sector_state.data_sectors * boot_sector_state.sector_size * FULL_BYTE
root_dir_content: bytes = boot_sector_state.root_dir_sectors_cnt * boot_sector_state.sector_size * EMPTY_BYTE
fat_tables_content: bytes = (boot_sector_state.sectors_per_fat_cnt
* boot_sector_state.fat_tables_cnt
* boot_sector_state.sector_size
* EMPTY_BYTE)
root_dir_content: bytes = boot_sector_state.root_dir_sectors_cnt * boot_sector_state.sector_size * EMPTY_BYTE
data_content: bytes = boot_sector_state.data_sectors * boot_sector_state.sector_size * FULL_BYTE
self.boot_sector_state.binary_image = (
BootSector.BOOT_SECTOR_HEADER.build(
dict(BS_jmpBoot=(b'\xeb\xfe\x90'),

View File

@ -152,6 +152,7 @@ class BootSectorState:
def non_data_sectors(self) -> int:
non_data_sectors_: int = get_non_data_sectors_cnt(self.reserved_sectors_cnt,
self.sectors_per_fat_cnt,
self.fat_tables_cnt,
self.root_dir_sectors_cnt)
return non_data_sectors_
@ -166,5 +167,5 @@ class BootSectorState:
@property
def root_directory_start(self) -> int:
root_dir_start: int = (self.reserved_sectors_cnt + self.sectors_per_fat_cnt) * self.sector_size
root_dir_start: int = (self.reserved_sectors_cnt + self.sectors_per_fat_cnt * self.fat_tables_cnt) * self.sector_size
return root_dir_start

View File

@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import argparse
@ -68,8 +68,8 @@ def number_of_clusters(number_of_sectors: int, sectors_per_cluster: int) -> int:
return number_of_sectors // sectors_per_cluster
def get_non_data_sectors_cnt(reserved_sectors_cnt: int, sectors_per_fat_cnt: int, root_dir_sectors_cnt: int) -> int:
return reserved_sectors_cnt + sectors_per_fat_cnt + root_dir_sectors_cnt
def get_non_data_sectors_cnt(reserved_sectors_cnt: int, sectors_per_fat_cnt: int, fat_tables_cnt: int, root_dir_sectors_cnt: int) -> int:
return reserved_sectors_cnt + sectors_per_fat_cnt * fat_tables_cnt + root_dir_sectors_cnt
def get_fatfs_type(clusters_count: int) -> int:
@ -203,9 +203,14 @@ def get_args_for_partition_generator(desc: str, wl: bool) -> argparse.Namespace:
type=int,
choices=[FAT12, FAT16, 0],
help="""
Type of fat. Select 12 for fat12, 16 for fat16. Don't set, or set to 0 for automatic
calculation using cluster size and partition size.
Type of the FAT file-system. Select '12' for FAT12, '16' for FAT16.
Leave unset or select 0 for automatic file-system type detection.
""")
parser.add_argument('--fat_count',
default=FATDefaults.FAT_TABLES_COUNT,
type=int,
choices=[1, 2],
help='Number of file allocation tables (FATs) in the filesystem.')
args = parser.parse_args()
if args.fat_type == 0:
@ -276,7 +281,7 @@ class FATDefaults:
# FATFS defaults
SIZE: int = 1024 * 1024
RESERVED_SECTORS_COUNT: int = 1
FAT_TABLES_COUNT: int = 1
FAT_TABLES_COUNT: int = 2
SECTORS_PER_CLUSTER: int = 1
SECTOR_SIZE: int = 0x1000
HIDDEN_SECTORS: int = 0

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import os
@ -18,6 +18,14 @@ from fatfs_utils.utils import (BYTES_PER_DIRECTORY_ENTRY, FATFS_INCEPTION, FATFS
required_clusters_count)
def duplicate_fat_decorator(func): # type: ignore
def wrapper(self, *args, **kwargs) -> None: # type: ignore
func(self, *args, **kwargs)
if isinstance(self, FATFS):
self.duplicate_fat()
return wrapper
class FATFS:
"""
The class FATFS provides API for generating FAT file system.
@ -79,6 +87,7 @@ class FATFS:
fatfs_state=self.state)
self.root_directory.init_directory()
@duplicate_fat_decorator
def create_file(self, name: str,
extension: str = '',
path_from_root: Optional[List[str]] = None,
@ -102,6 +111,7 @@ class FATFS:
object_timestamp_=object_timestamp_,
is_empty=is_empty)
@duplicate_fat_decorator
def create_directory(self, name: str,
path_from_root: Optional[List[str]] = None,
object_timestamp_: datetime = FATFS_INCEPTION) -> None:
@ -126,6 +136,7 @@ class FATFS:
path_from_root=path_from_root,
object_timestamp_=object_timestamp_)
@duplicate_fat_decorator
def write_content(self, path_from_root: List[str], content: bytes) -> None:
"""
fat fs invokes root directory to recursively find the required file and writes the content
@ -137,10 +148,24 @@ class FATFS:
boot_sector_.generate_boot_sector()
return boot_sector_.binary_image
def duplicate_fat(self) -> None:
"""
Duplicate FAT table if 2 FAT tables are required
"""
boot_sec_st = self.state.boot_sector_state
if boot_sec_st.fat_tables_cnt == 2:
fat_start = boot_sec_st.reserved_sectors_cnt * boot_sec_st.sector_size
fat_end = fat_start + boot_sec_st.sectors_per_fat_cnt * boot_sec_st.sector_size
second_fat_shift = boot_sec_st.sectors_per_fat_cnt * boot_sec_st.sector_size
self.state.binary_image[fat_start + second_fat_shift: fat_end + second_fat_shift] = (
self.state.binary_image[fat_start: fat_end]
)
def write_filesystem(self, output_path: str) -> None:
with open(output_path, 'wb') as output:
output.write(bytearray(self.state.binary_image))
@duplicate_fat_decorator
def _generate_partition_from_folder(self,
folder_relative_path: str,
folder_path: str = '',
@ -225,17 +250,19 @@ def main() -> None:
args.partition_size = max(FATFS_MIN_ALLOC_UNIT * args.sector_size,
(clusters + fats + get_non_data_sectors_cnt(RESERVED_CLUSTERS_COUNT,
fats,
args.fat_count,
root_dir_sectors)
) * args.sector_size
)
fatfs = FATFS(sector_size=args.sector_size,
fatfs = FATFS(size=args.partition_size,
fat_tables_cnt=args.fat_count,
sectors_per_cluster=args.sectors_per_cluster,
size=args.partition_size,
root_entry_count=args.root_entry_count,
explicit_fat_type=args.fat_type,
sector_size=args.sector_size,
long_names_enabled=args.long_name_support,
use_default_datetime=args.use_default_datetime)
use_default_datetime=args.use_default_datetime,
root_entry_count=args.root_entry_count,
explicit_fat_type=args.fat_type)
fatfs.generate(args.input_directory)
fatfs.write_filesystem(args.output_file)

View File

@ -3,7 +3,7 @@
# Create a fatfs image of the specified directory on the host during build and optionally
# have the created image flashed using `idf.py flash`
function(fatfs_create_partition_image partition base_dir)
set(options FLASH_IN_PROJECT WL_INIT PRESERVE_TIME)
set(options FLASH_IN_PROJECT WL_INIT PRESERVE_TIME ONE_FAT)
cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}")
@ -22,6 +22,12 @@ function(fatfs_create_partition_image partition base_dir)
set(default_datetime_option --use_default_datetime)
endif()
if(arg_ONE_FAT)
set(fatfsgen_fat_count --fat_count=1)
else()
set(fatfsgen_fat_count)
endif()
if("${CONFIG_FATFS_SECTOR_512}")
set(fatfs_sector_size 512)
elseif("${CONFIG_FATFS_SECTOR_1024}")
@ -52,6 +58,7 @@ function(fatfs_create_partition_image partition base_dir)
COMMAND ${fatfsgen_py} ${base_dir_full_path}
${fatfs_long_names_option}
${default_datetime_option}
${fatfsgen_fat_count}
--partition_size ${size}
--output_file ${image_file}
--sector_size "${fatfs_sector_size}"
@ -81,39 +88,39 @@ endfunction()
function(fatfs_create_rawflash_image partition base_dir)
set(options FLASH_IN_PROJECT PRESERVE_TIME)
set(options FLASH_IN_PROJECT PRESERVE_TIME ONE_FAT)
cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}")
set(argument_list)
if(arg_FLASH_IN_PROJECT)
if(arg_PRESERVE_TIME)
fatfs_create_partition_image(${partition} ${base_dir} FLASH_IN_PROJECT PRESERVE_TIME)
else()
fatfs_create_partition_image(${partition} ${base_dir} FLASH_IN_PROJECT)
endif()
else()
if(arg_PRESERVE_TIME)
fatfs_create_partition_image(${partition} ${base_dir} PRESERVE_TIME)
else()
fatfs_create_partition_image(${partition} ${base_dir})
endif()
list(APPEND argument_list FLASH_IN_PROJECT)
endif()
if(arg_PRESERVE_TIME)
list(APPEND argument_list PRESERVE_TIME)
endif()
if(arg_ONE_FAT)
list(APPEND argument_list ONE_FAT)
endif()
fatfs_create_partition_image(${partition} ${base_dir} ${argument_list})
endfunction()
function(fatfs_create_spiflash_image partition base_dir)
set(options FLASH_IN_PROJECT PRESERVE_TIME)
set(options FLASH_IN_PROJECT PRESERVE_TIME ONE_FAT)
cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}")
set(argument_list WL_INIT)
if(arg_FLASH_IN_PROJECT)
if(arg_PRESERVE_TIME)
fatfs_create_partition_image(${partition} ${base_dir} FLASH_IN_PROJECT WL_INIT PRESERVE_TIME)
else()
fatfs_create_partition_image(${partition} ${base_dir} FLASH_IN_PROJECT WL_INIT)
endif()
else()
if(arg_PRESERVE_TIME)
fatfs_create_partition_image(${partition} ${base_dir} WL_INIT PRESERVE_TIME)
else()
fatfs_create_partition_image(${partition} ${base_dir} WL_INIT)
endif()
list(APPEND argument_list FLASH_IN_PROJECT)
endif()
if(arg_PRESERVE_TIME)
list(APPEND argument_list PRESERVE_TIME)
endif()
if(arg_ONE_FAT)
list(APPEND argument_list ONE_FAT)
endif()
fatfs_create_partition_image(${partition} ${base_dir} ${argument_list})
endfunction()

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import os
@ -39,7 +39,17 @@ class FatFSGen(unittest.TestCase):
os.remove('fatfs_image.img')
def test_empty_file_sn_fat12(self) -> None:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
fatfs.create_file('TESTFILE')
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
self.assertEqual(file_system[0x3000:0x300c], b'TESTFILE \x20') # check entry name and type
self.assertEqual(file_system[0x1000:0x1006], b'\xf8\xff\xff\xff\x0f\x00') # check fat
def test_empty_file_sn_fat12_one_fat(self) -> None:
fatfs = fatfsgen.FATFS(fat_tables_cnt=1)
fatfs.create_file('TESTFILE')
fatfs.write_filesystem(CFG['output_file'])
@ -49,7 +59,18 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x1000:0x1006], b'\xf8\xff\xff\xff\x0f\x00') # check fat
def test_directory_sn_fat12(self) -> None:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
fatfs.create_directory('TESTFOLD')
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
self.assertEqual(file_system[0x3000:0x300c], b'TESTFOLD \x10') # check entry name and type
self.assertEqual(file_system[0x1000:0x1006], b'\xf8\xff\xff\xff\x0f\x00') # check fat
self.assertEqual(file_system[0x7000:0x700c], b'. \x10') # reference to itself
self.assertEqual(file_system[0x7020:0x702c], b'.. \x10') # reference to parent
def test_directory_sn_fat12_one_fat(self) -> None:
fatfs = fatfsgen.FATFS(fat_tables_cnt=1)
fatfs.create_directory('TESTFOLD')
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
@ -60,7 +81,15 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x6020:0x602c], b'.. \x10') # reference to parent
def test_empty_file_with_extension_sn_fat12(self) -> None:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
fatfs.create_file('TESTF', extension='TXT')
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
self.assertEqual(file_system[0x3000:0x300c], b'TESTF TXT\x20') # check entry name and type
self.assertEqual(file_system[0x1000:0x1006], b'\xf8\xff\xff\xff\x0f\x00') # check fat
def test_empty_file_with_extension_sn_fat12_one_fat(self) -> None:
fatfs = fatfsgen.FATFS(fat_tables_cnt=1)
fatfs.create_file('TESTF', extension='TXT')
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
@ -68,7 +97,19 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x1000:0x1006], b'\xf8\xff\xff\xff\x0f\x00') # check fat
def test_write_to_file_with_extension_sn_fat12(self) -> None:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
fatfs.create_file('WRITEF', extension='TXT')
fatfs.write_content(path_from_root=['WRITEF.TXT'], content=b'testcontent')
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
self.assertEqual(file_system[0x3000:0x300c], b'WRITEF TXT\x20') # check entry name and type
self.assertEqual(file_system[0x301a:0x3020], b'\x02\x00\x0b\x00\x00\x00') # check size and cluster ref
self.assertEqual(file_system[0x1000:0x1006], b'\xf8\xff\xff\xff\x0f\x00') # check fat
self.assertEqual(file_system[0x7000:0x700f], b'testcontent\x00\x00\x00\x00') # check file content
def test_write_to_file_with_extension_sn_fat12_one_fat(self) -> None:
fatfs = fatfsgen.FATFS(fat_tables_cnt=1)
fatfs.create_file('WRITEF', extension='TXT')
fatfs.write_content(path_from_root=['WRITEF.TXT'], content=b'testcontent')
fatfs.write_filesystem(CFG['output_file'])
@ -80,7 +121,23 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x6000:0x600f], b'testcontent\x00\x00\x00\x00') # check file content
def test_write_to_file_in_folder_sn_fat12(self) -> None:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
fatfs.create_directory('TESTFOLD')
fatfs.create_file('WRITEF', extension='TXT', path_from_root=['TESTFOLD'])
fatfs.write_content(path_from_root=['TESTFOLD', 'WRITEF.TXT'], content=b'testcontent')
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
self.assertEqual(file_system[0x3000:0x300c], b'TESTFOLD \x10')
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[0x7040:0x7050], b'WRITEF TXT\x20\x00\x00\x00\x00')
self.assertEqual(file_system[0x705a:0x7060], b'\x03\x00\x0b\x00\x00\x00')
self.assertEqual(file_system[0x8000:0x800b], b'testcontent') # check file content
def test_write_to_file_in_folder_sn_fat12_one_fat(self) -> None:
fatfs = fatfsgen.FATFS(fat_tables_cnt=1)
fatfs.create_directory('TESTFOLD')
fatfs.create_file('WRITEF', extension='TXT', path_from_root=['TESTFOLD'])
fatfs.write_content(path_from_root=['TESTFOLD', 'WRITEF.TXT'], content=b'testcontent')
@ -96,7 +153,7 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x7000:0x700b], b'testcontent') # check file content
def test_cluster_setting_values(self) -> None:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
fatfs.create_file('TESTFIL1')
fatfs.create_file('TESTFIL2')
fatfs.create_file('TESTFIL3')
@ -112,7 +169,16 @@ class FatFSGen(unittest.TestCase):
b'\xf8\xff\xff\xe8\x43\x00\x05\xf0\xff\xff\x0f\x00\x00\x00\x00\x00')
def test_full_sector_file(self) -> None:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
fatfs.create_file('WRITEF', extension='TXT')
fatfs.write_content(path_from_root=['WRITEF.TXT'], content=CFG['sector_size'] * b'a')
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
self.assertEqual(file_system[0x1000: 0x100e], b'\xf8\xff\xff\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x7000: 0x8000], CFG['sector_size'] * b'a')
def test_full_sector_file_one_fat(self) -> None:
fatfs = fatfsgen.FATFS(fat_tables_cnt=1)
fatfs.create_file('WRITEF', extension='TXT')
fatfs.write_content(path_from_root=['WRITEF.TXT'], content=CFG['sector_size'] * b'a')
fatfs.write_filesystem(CFG['output_file'])
@ -121,7 +187,16 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x6000: 0x7000], CFG['sector_size'] * b'a')
def test_file_chaining(self) -> None:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
fatfs.create_file('WRITEF', extension='TXT')
fatfs.write_content(path_from_root=['WRITEF.TXT'], content=CFG['sector_size'] * b'a' + b'a')
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
self.assertEqual(file_system[0x1000: 0x100e], b'\xf8\xff\xff\x03\xf0\xff\x00\x00\x00\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x8000: 0x9000], b'a' + (CFG['sector_size'] - 1) * b'\x00')
def test_file_chaining_one_fat(self) -> None:
fatfs = fatfsgen.FATFS(fat_tables_cnt=1)
fatfs.create_file('WRITEF', extension='TXT')
fatfs.write_content(path_from_root=['WRITEF.TXT'], content=CFG['sector_size'] * b'a' + b'a')
fatfs.write_filesystem(CFG['output_file'])
@ -130,7 +205,22 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x7000: 0x8000], b'a' + (CFG['sector_size'] - 1) * b'\x00')
def test_full_sector_folder(self) -> None:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
fatfs.create_directory('TESTFOLD')
fill_sector(fatfs)
fatfs.write_content(path_from_root=['TESTFOLD', 'A0'], content=b'first')
fatfs.write_content(path_from_root=['TESTFOLD', 'A126'], content=b'later')
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
self.assertEqual(file_system[0x1000: 0x10d0],
b'\xf8\xff\xff\x82\xf0\xff' + 192 * b'\xff' + 10 * b'\x00')
self.assertEqual(file_system[0x86000:0x86005], b'later')
self.assertEqual(file_system[0x87000:0x87010], b'A126 \x00\x00\x00\x00')
self.assertEqual(file_system[0x87020:0x87030], b'A127 \x00\x00\x00\x00')
def test_full_sector_folder_one_fat(self) -> None:
fatfs = fatfsgen.FATFS(fat_tables_cnt=1)
fatfs.create_directory('TESTFOLD')
fill_sector(fatfs)
@ -145,21 +235,21 @@ class FatFSGen(unittest.TestCase):
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()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
fatfs.create_directory('TESTFOLD')
fatfs.create_directory('TESTFOLL', path_from_root=['TESTFOLD'])
self.assertRaises(WriteDirectoryException, fatfs.write_content, path_from_root=['TESTFOLD', 'TESTFOLL'],
content=b'testcontent')
def test_write_non_existing_file_in_folder_sn_fat12(self) -> None:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
fatfs.create_directory('TESTFOLD')
self.assertRaises(FileNotFoundError, fatfs.write_content, path_from_root=['TESTFOLD', 'AHOJ'],
content=b'testcontent')
@staticmethod
def create_too_many_files() -> None:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
fatfs.create_directory('TESTFOLD')
for i in range(2 * CFG['sector_size'] // CFG['entry_size']):
fatfs.create_file(f'A{str(i).upper()}', path_from_root=['TESTFOLD'])
@ -168,7 +258,20 @@ class FatFSGen(unittest.TestCase):
self.assertRaises(NoFreeClusterException, self.create_too_many_files)
def test_full_two_sectors_folder(self) -> None:
fatfs = fatfsgen.FATFS(size=2 * 1024 * 1024)
fatfs = fatfsgen.FATFS(fat_tables_cnt=2, size=2 * 1024 * 1024)
fatfs.create_directory('TESTFOLD')
for i in range(2 * CFG['sector_size'] // CFG['entry_size']):
fatfs.create_file(f'A{str(i).upper()}', path_from_root=['TESTFOLD'])
fatfs.write_content(path_from_root=['TESTFOLD', 'A253'], content=b'later')
fatfs.write_content(path_from_root=['TESTFOLD', 'A255'], content=b'last')
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
self.assertEqual(file_system[0x106000:0x106010], b'later\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x109000:0x109010], b'last\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
def test_full_two_sectors_folder_one_fat(self) -> None:
fatfs = fatfsgen.FATFS(fat_tables_cnt=1, size=2 * 1024 * 1024)
fatfs.create_directory('TESTFOLD')
for i in range(2 * CFG['sector_size'] // CFG['entry_size']):
@ -181,26 +284,38 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x108000:0x108010], b'last\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
def test_lower_case_dir_short_names(self) -> None:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
self.assertRaises(LowerCaseException, fatfs.create_directory, 'testfold')
def test_lower_case_file_short_names(self) -> None:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
self.assertRaises(LowerCaseException, fatfs.create_file, 'newfile')
def test_too_long_name_dir_short_names(self) -> None:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
self.assertRaises(TooLongNameException, fatfs.create_directory, 'TOOLONGNAME')
def test_fatfs16_detection(self) -> None:
fatfs = fatfsgen.FATFS(size=16 * 1024 * 1024)
fatfs = fatfsgen.FATFS(fat_tables_cnt=2, size=16 * 1024 * 1024)
self.assertEqual(fatfs.state.boot_sector_state.fatfs_type, 16)
def test_fatfs32_detection(self) -> None:
self.assertRaises(NotImplementedError, fatfsgen.FATFS, size=256 * 1024 * 1024)
def test_deep_structure(self) -> None:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
fatfs.create_directory('TESTFOLD')
fatfs.create_directory('TESTFOLL', path_from_root=['TESTFOLD'])
fatfs.create_directory('TESTFOLO', path_from_root=['TESTFOLD', 'TESTFOLL'])
fatfs.create_file('WRITEF', extension='TXT', path_from_root=['TESTFOLD', 'TESTFOLL', 'TESTFOLO'])
fatfs.write_content(path_from_root=['TESTFOLD', 'TESTFOLL', 'TESTFOLO', 'WRITEF.TXT'], content=b'later')
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
self.assertEqual(file_system[0xa000:0xa010], b'later\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
def test_deep_structure_one_fat(self) -> None:
fatfs = fatfsgen.FATFS(fat_tables_cnt=1)
fatfs.create_directory('TESTFOLD')
fatfs.create_directory('TESTFOLL', path_from_root=['TESTFOLD'])
fatfs.create_directory('TESTFOLO', path_from_root=['TESTFOLD', 'TESTFOLL'])
@ -212,7 +327,26 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x9000:0x9010], b'later\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
def test_same_name_deep_structure(self) -> None:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
fatfs.create_directory('TESTFOLD')
fatfs.create_directory('TESTFOLD', path_from_root=['TESTFOLD'])
fatfs.create_directory('TESTFOLD', path_from_root=['TESTFOLD', 'TESTFOLD'])
fatfs.create_file('WRITEF', extension='TXT', path_from_root=['TESTFOLD', 'TESTFOLD', 'TESTFOLD'])
fatfs.write_content(path_from_root=['TESTFOLD', 'TESTFOLD', 'TESTFOLD', 'WRITEF.TXT'], content=b'later')
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
self.assertEqual(file_system[0x3000:0x3010], b'TESTFOLD \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x3010:0x3020], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x7040:0x7050], b'TESTFOLD \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x7040:0x7050], b'TESTFOLD \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x8040:0x8050], b'TESTFOLD \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x9040:0x9050], b'WRITEF TXT \x00\x00\x00\x00')
self.assertEqual(file_system[0xa000:0xa010], b'later\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
def test_same_name_deep_structure_one_fat(self) -> None:
fatfs = fatfsgen.FATFS(fat_tables_cnt=1)
fatfs.create_directory('TESTFOLD')
fatfs.create_directory('TESTFOLD', path_from_root=['TESTFOLD'])
fatfs.create_directory('TESTFOLD', path_from_root=['TESTFOLD', 'TESTFOLD'])
@ -231,7 +365,19 @@ class FatFSGen(unittest.TestCase):
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:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
fatfs.generate(CFG['test_dir'])
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
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\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')
def test_e2e_deep_folder_into_image_one_fat(self) -> None:
fatfs = fatfsgen.FATFS(fat_tables_cnt=1)
fatfs.generate(CFG['test_dir'])
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
@ -243,7 +389,22 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0xa000:0xa010], b'ahoj\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
def test_e2e_deep_folder_into_image_ext(self) -> None:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
fatfs.generate(CFG['test_dir2'])
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
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')
self.assertEqual(file_system[0xc000:0xc009], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff')
def test_e2e_deep_folder_into_image_ext_one_fat(self) -> None:
fatfs = fatfsgen.FATFS(fat_tables_cnt=1)
fatfs.generate(CFG['test_dir2'])
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
@ -258,20 +419,29 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0xb000:0xb009], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff')
def test_empty_fat16(self) -> None:
fatfs = fatfsgen.FATFS(size=17 * 1024 * 1024)
fatfs = fatfsgen.FATFS(fat_tables_cnt=2, size=17 * 1024 * 1024)
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
self.assertEqual(file_system[0x1000:0x1007], b'\xf8\xff\xff\xff\x00\x00\x00')
def test_simple_fat16(self) -> None:
fatfs = fatfsgen.FATFS(size=17 * 1024 * 1024)
fatfs = fatfsgen.FATFS(fat_tables_cnt=2, size=17 * 1024 * 1024)
fatfs.create_directory('TESTFOLD')
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
self.assertEqual(file_system[0x1000:0x1007], b'\xf8\xff\xff\xff\xff\xff\x00')
def test_chaining_fat16(self) -> None:
fatfs = fatfsgen.FATFS(size=17 * 1024 * 1024)
fatfs = fatfsgen.FATFS(fat_tables_cnt=2, size=17 * 1024 * 1024)
fatfs.create_file('WRITEF', extension='TXT')
fatfs.write_content(path_from_root=['WRITEF.TXT'], content=CFG['sector_size'] * b'a' + b'a')
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
self.assertEqual(file_system[0x1000: 0x100e], b'\xf8\xff\xff\xff\x03\x00\xff\xff\x00\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0xc000: 0xd000], b'a' + (CFG['sector_size'] - 1) * b'\x00')
def test_chaining_fat16_one_fat(self) -> None:
fatfs = fatfsgen.FATFS(fat_tables_cnt=1, size=17 * 1024 * 1024)
fatfs.create_file('WRITEF', extension='TXT')
fatfs.write_content(path_from_root=['WRITEF.TXT'], content=CFG['sector_size'] * b'a' + b'a')
fatfs.write_filesystem(CFG['output_file'])
@ -280,7 +450,22 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x9000: 0xa000], b'a' + (CFG['sector_size'] - 1) * b'\x00')
def test_full_sector_folder_fat16(self) -> None:
fatfs = fatfsgen.FATFS(size=17 * 1024 * 1024)
fatfs = fatfsgen.FATFS(fat_tables_cnt=2, size=17 * 1024 * 1024)
fatfs.create_directory('TESTFOLD')
fill_sector(fatfs)
fatfs.write_content(path_from_root=['TESTFOLD', 'A0'], content=b'first')
fatfs.write_content(path_from_root=['TESTFOLD', 'A126'], content=b'later')
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
self.assertEqual(file_system[0x1000: 0x1110],
b'\xf8\xff\xff\xff\x82\x00' + 258 * b'\xff' + 8 * b'\x00')
self.assertEqual(file_system[0x8a000:0x8a005], b'later')
self.assertEqual(file_system[0x8b000:0x8b010], b'A126 \x00\x00\x00\x00')
self.assertEqual(file_system[0x8b020:0x8b030], b'A127 \x00\x00\x00\x00')
def test_full_sector_folder_fat16_one_fat(self) -> None:
fatfs = fatfsgen.FATFS(fat_tables_cnt=1, size=17 * 1024 * 1024)
fatfs.create_directory('TESTFOLD')
fill_sector(fatfs)
@ -295,14 +480,31 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x88020:0x88030], b'A127 \x00\x00\x00\x00')
def test_empty_lfn_short_name(self) -> None:
fatfs = fatfsgen.FATFS(long_names_enabled=True)
fatfs = fatfsgen.FATFS(fat_tables_cnt=2, 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[0x3000: 0x3019], b'HELLO TXT \x18\x00\x00\x00!\x00!\x00\x00\x00\x00\x00!')
def test_empty_lfn_short_name_one_fat(self) -> None:
fatfs = fatfsgen.FATFS(fat_tables_cnt=1, 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 \x18\x00\x00\x00!\x00!\x00\x00\x00\x00\x00!')
def test_lfn_short_name(self) -> None:
fatfs = fatfsgen.FATFS(long_names_enabled=True)
fatfs = fatfsgen.FATFS(fat_tables_cnt=2, 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[0x3000: 0x3010], b'HELLO TXT \x18\x00\x00\x00')
self.assertEqual(file_system[0x3010: 0x3020], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x0e\x00\x00\x00')
self.assertEqual(file_system[0x7000: 0x7010], b'this is a test\x00\x00')
def test_lfn_short_name_one_fat(self) -> None:
fatfs = fatfsgen.FATFS(fat_tables_cnt=1, 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'])
@ -312,7 +514,19 @@ class FatFSGen(unittest.TestCase):
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 = fatfsgen.FATFS(fat_tables_cnt=2, 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[0x3000: 0x3010], b'Bl\x00o\x00.\x00t\x00x\x00\x0f\x00\xadt\x00')
self.assertEqual(file_system[0x3012: 0x3020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
self.assertEqual(file_system[0x3020: 0x3030], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00\xadh\x00')
self.assertEqual(file_system[0x3030: 0x3040], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00')
self.assertEqual(file_system[0x3040: 0x3050], b'HELLOH~\x01TXT \x00\x00\x00\x00')
self.assertEqual(file_system[0x3050: 0x3060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
def test_lfn_empty_name_one_fat(self) -> None:
fatfs = fatfsgen.FATFS(fat_tables_cnt=1, long_names_enabled=True)
fatfs.create_file('HELLOHELLOHELLO', extension='TXT')
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
@ -324,7 +538,21 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x2050: 0x2060], b'!\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 = fatfsgen.FATFS(fat_tables_cnt=2, 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[0x3000: 0x3010], b'Bl\x00o\x00.\x00t\x00x\x00\x0f\x00\xadt\x00')
self.assertEqual(file_system[0x3012: 0x3020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
self.assertEqual(file_system[0x3020: 0x3030], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00\xadh\x00')
self.assertEqual(file_system[0x3030: 0x3040], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00')
self.assertEqual(file_system[0x3040: 0x3050], b'HELLOH~\x01TXT \x00\x00\x00\x00')
self.assertEqual(file_system[0x3050: 0x3060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x0e\x00\x00\x00')
self.assertEqual(file_system[0x7000: 0x7010], b'this is a test\x00\x00')
def test_lfn_plain_name_one_fat(self) -> None:
fatfs = fatfsgen.FATFS(fat_tables_cnt=1, 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'])
@ -338,7 +566,21 @@ class FatFSGen(unittest.TestCase):
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 = fatfsgen.FATFS(fat_tables_cnt=2, 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[0x3000: 0x3010], b'Bl\x00o\x00\x00\x00\xff\xff\xff\xff\x0f\x00P\xff\xff')
self.assertEqual(file_system[0x3012: 0x3020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
self.assertEqual(file_system[0x3020: 0x3030], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00Ph\x00')
self.assertEqual(file_system[0x3030: 0x3040], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00')
self.assertEqual(file_system[0x3040: 0x3050], b'HELLOH~\x01 \x00\x00\x00\x00')
self.assertEqual(file_system[0x3050: 0x3060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x0e\x00\x00\x00')
self.assertEqual(file_system[0x7000: 0x7010], b'this is a test\x00\x00')
def test_lfn_plain_name_no_ext_one_fat(self) -> None:
fatfs = fatfsgen.FATFS(fat_tables_cnt=1, 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'])
@ -352,11 +594,31 @@ class FatFSGen(unittest.TestCase):
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)
fatfs = fatfsgen.FATFS(fat_tables_cnt=2, 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 = fatfsgen.FATFS(fat_tables_cnt=2, 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[0x3000: 0x3010], b'Bo\x00l\x00d\x00\x00\x00\xff\xff\x0f\x00\xa0\xff\xff')
self.assertEqual(file_system[0x3012: 0x3020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
self.assertEqual(file_system[0x3020: 0x3030], b'\x01v\x00e\x00r\x00y\x00l\x00\x0f\x00\xa0o\x00')
self.assertEqual(file_system[0x3030: 0x3040], b'n\x00g\x00t\x00e\x00s\x00\x00\x00t\x00f\x00')
self.assertEqual(file_system[0x3040: 0x3050], b'VERYLO~\x01 \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x3050: 0x3060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x7000: 0x7010], b'. \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x7012: 0x7020], b'!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x7020: 0x7030], b'.. \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x7030: 0x7040], b'!\x00!\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x7040: 0x7050], b'HELLO TXT \x18\x00\x00\x00')
self.assertEqual(file_system[0x7050: 0x7060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x03\x00\x00\x00\x00\x00')
def test_lfn_nested_empty_one_fat(self) -> None:
fatfs = fatfsgen.FATFS(fat_tables_cnt=1, long_names_enabled=True)
fatfs.create_directory('VERYLONGTESTFOLD')
fatfs.create_file('HELLO', extension='TXT', path_from_root=['VERYLONGTESTFOLD'])
fatfs.write_filesystem(CFG['output_file'])
@ -376,7 +638,29 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x6050: 0x6060], b'!\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: fatfsgen.fatfs = fatfsgen.FATFS(fat_tables_cnt=2, 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[0x3000: 0x3010], b'Bo\x00l\x00d\x00\x00\x00\xff\xff\x0f\x00\n\xff\xff')
self.assertEqual(file_system[0x3012: 0x3020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
self.assertEqual(file_system[0x3020: 0x3030], b'\x01v\x00e\x00r\x00y\x00l\x00\x0f\x00\no\x00')
self.assertEqual(file_system[0x3030: 0x3040], b'n\x00g\x00t\x00e\x00s\x00\x00\x00t\x00f\x00')
self.assertEqual(file_system[0x3040: 0x3050], b'verylo~\x01 \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x3050: 0x3060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x7000: 0x7010], b'. \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x7012: 0x7020], b'!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x7020: 0x7030], b'.. \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x7030: 0x7040], b'!\x00!\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x7040: 0x7050], b'Bl\x00o\x00.\x00t\x00x\x00\x0f\x00\xcft\x00')
self.assertEqual(file_system[0x7050: 0x7060],
b'\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
def test_lfn_nested_long_empty_one_fat(self) -> None:
fatfs: fatfsgen.FATFS = fatfsgen.FATFS(fat_tables_cnt=1, long_names_enabled=True)
fatfs.create_directory('verylongtestfold')
fatfs.create_file('hellohellohello', extension='txt', path_from_root=['verylongtestfold'])
fatfs.write_filesystem(CFG['output_file'])
@ -398,7 +682,30 @@ class FatFSGen(unittest.TestCase):
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: fatfsgen.fatfs = fatfsgen.FATFS(fat_tables_cnt=2, 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[0x3000: 0x3010], b'Bo\x00l\x00d\x00\x00\x00\xff\xff\x0f\x00\xa0\xff\xff')
self.assertEqual(file_system[0x3012: 0x3020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
self.assertEqual(file_system[0x3020: 0x3030], b'\x01v\x00e\x00r\x00y\x00l\x00\x0f\x00\xa0o\x00')
self.assertEqual(file_system[0x3030: 0x3040], b'n\x00g\x00t\x00e\x00s\x00\x00\x00t\x00f\x00')
self.assertEqual(file_system[0x3040: 0x3050], b'VERYLO~\x01 \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x3050: 0x3060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x7000: 0x7010], b'. \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x7012: 0x7020], b'!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x7020: 0x7030], b'.. \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x7030: 0x7040], b'!\x00!\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x7040: 0x7050], b'HELLO TXT \x18\x00\x00\x00')
self.assertEqual(file_system[0x7050: 0x7060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x03\x00\x0e\x00\x00\x00')
self.assertEqual(file_system[0x8000: 0x8010], b'this is a test\x00\x00')
def test_lfn_nested_text_one_fat(self) -> None:
fatfs: fatfsgen.FATFS = fatfsgen.FATFS(fat_tables_cnt=1, 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')
@ -441,7 +748,45 @@ class FatFSGen(unittest.TestCase):
self.assertRaises(InconsistentFATAttributes, fatfsgen.FATFS, size=20480000, explicit_fat_type=FAT12)
def test_lfn_increasing(self) -> None:
fatfs: fatfsgen.FATFS = fatfsgen.FATFS(long_names_enabled=True)
fatfs: fatfsgen.fatfs = fatfsgen.FATFS(fat_tables_cnt=2, 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[0x3000: 0x3010], b'Bo\x00l\x00d\x00\x00\x00\xff\xff\x0f\x00\xa0\xff\xff')
self.assertEqual(file_system[0x3011: 0x3020], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
self.assertEqual(file_system[0x3020: 0x3030], b'\x01v\x00e\x00r\x00y\x00l\x00\x0f\x00\xa0o\x00')
self.assertEqual(file_system[0x3030: 0x3040], b'n\x00g\x00t\x00e\x00s\x00\x00\x00t\x00f\x00')
self.assertEqual(file_system[0x3040: 0x3050], b'VERYLO~\x01 \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x3050: 0x3060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x7000: 0x7010], b'. \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x7011: 0x7020], b'\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x7020: 0x7030], b'.. \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x7030: 0x7040], b'!\x00!\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x7040: 0x7050], b'Bl\x00o\x00o\x00o\x00o\x00\x0f\x00\xado\x00')
self.assertEqual(file_system[0x7050: 0x7060], b'o\x00o\x00.\x00t\x00x\x00\x00\x00t\x00\x00\x00')
self.assertEqual(file_system[0x7050: 0x7060], b'o\x00o\x00.\x00t\x00x\x00\x00\x00t\x00\x00\x00')
self.assertEqual(file_system[0x7060: 0x7070], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00\xadh\x00')
self.assertEqual(file_system[0x7070: 0x7080], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00')
self.assertEqual(file_system[0x7080: 0x7090], b'HELLOH~\x01TXT \x00\x00\x00\x00')
self.assertEqual(file_system[0x7090: 0x70a0], b'!\x00!\x00\x00\x00\x00\x00!\x00\x03\x00\x10\x00\x00\x00')
self.assertEqual(file_system[0x70a0: 0x70b0], b'Bl\x00o\x00o\x00o\x00o\x00\x0f\x00\x8do\x00')
self.assertEqual(file_system[0x70b0: 0x70c0], b'o\x00b\x00.\x00t\x00x\x00\x00\x00t\x00\x00\x00')
self.assertEqual(file_system[0x70c0: 0x70d0], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00\x8dh\x00')
self.assertEqual(file_system[0x70d0: 0x70e0], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00')
self.assertEqual(file_system[0x70e0: 0x70f0], b'HELLOH~\x02TXT \x00\x00\x00\x00')
def test_lfn_increasing_one_fat(self) -> None:
fatfs: fatfsgen.FATFS = fatfsgen.FATFS(fat_tables_cnt=1, 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'])
@ -484,7 +829,21 @@ class FatFSGen(unittest.TestCase):
self.assertRaises(NotInitialized, lambda: BootSector().binary_image) # encapsulate property to callable
def test_bs_str(self) -> None:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
bs = BootSector(fatfs.state.boot_sector_state)
bs.generate_boot_sector()
bs.parse_boot_sector(bs.binary_image)
x = 'FATFS properties:,clusters: 251,data_region_start: 28672,data_sectors: ' \
'249,entries_root_count: 512,fat_table_start_address: 4096,fat_tables_cnt: 2,' \
'fatfs_type: 12,file_sys_type: FAT ,hidden_sectors: 0,media_type: 248,' \
'non_data_sectors: 7,num_heads: 255,oem_name: MSDOS5.0,reserved_sectors_cnt: 1,' \
'root_dir_sectors_cnt: 4,root_directory_start: 12288,sec_per_track: 63,sector_size: 4096,' \
'sectors_count: 256,sectors_per_cluster: 1,sectors_per_fat_cnt: 1,size: 1048576,' \
'volume_label: Espressif ,volume_uuid: 1144419653,'
self.assertEqual(x.split(',')[:-2], str(bs).split('\n')[:-2]) # except for volume id
def test_bs_str_one_fat(self) -> None:
fatfs = fatfsgen.FATFS(fat_tables_cnt=1)
bs = BootSector(fatfs.state.boot_sector_state)
bs.generate_boot_sector()
bs.parse_boot_sector(bs.binary_image)
@ -508,19 +867,19 @@ class FatFSGen(unittest.TestCase):
2)
def test_get_cluster_value_from_fat(self) -> None:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
self.assertEqual(fatfs.fat.get_cluster_value(1), 0xFFF)
def test_is_cluster_last(self) -> None:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
self.assertEqual(fatfs.fat.is_cluster_last(2), False)
def test_chain_in_fat(self) -> None:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
self.assertEqual(fatfs.fat.get_chained_content(1), b'\x00' * 0x1000)
def test_retrieve_file_chaining(self) -> None:
fatfs = fatfsgen.FATFS()
fatfs = fatfsgen.FATFS(fat_tables_cnt=2)
fatfs.create_file('WRITEF', extension='TXT')
fatfs.write_content(path_from_root=['WRITEF.TXT'], content=CFG['sector_size'] * b'a' + b'a')
fatfs.write_filesystem(CFG['output_file'])

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import os
@ -7,7 +7,7 @@ import shutil
import sys
import unittest
from test_utils import CFG, generate_test_dir_1, generate_test_dir_2
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 wl_fatfsgen # noqa E402 # pylint: disable=C0413
@ -24,7 +24,18 @@ class WLFatFSGen(unittest.TestCase):
shutil.rmtree('output_data')
def test_empty_file_sn_fat12(self) -> None:
fatfs = wl_fatfsgen.WLFATFS()
fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=2)
fatfs.plain_fatfs.create_file('TESTFILE')
fatfs.init_wl()
fatfs.wl_write_filesystem(CFG['output_file'])
with open(CFG['output_file'], 'rb') as fs_file:
file_system = fs_file.read()
self.assertEqual(file_system[0x4000:0x400c], b'TESTFILE \x20') # check entry name and type
self.assertEqual(file_system[0x3000:0x3006], b'\xf8\xff\xff\xff\x0f\x00') # check fat
def test_empty_file_sn_fat12_one_fat(self) -> None:
fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=1)
fatfs.plain_fatfs.create_file('TESTFILE')
fatfs.init_wl()
fatfs.wl_write_filesystem(CFG['output_file'])
@ -35,7 +46,7 @@ class WLFatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x2000:0x2006], b'\xf8\xff\xff\xff\x0f\x00') # check fat
def test_directory_sn_fat12(self) -> None:
fatfs = wl_fatfsgen.WLFATFS(device_id=3750448905)
fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=2, device_id=3750448905)
fatfs.plain_fatfs.create_directory('TESTFOLD')
fatfs.init_wl()
@ -45,7 +56,44 @@ class WLFatFSGen(unittest.TestCase):
# boot sector
self.assertEqual(file_system[0x1000:0x1010], b'\xeb\xfe\x90MSDOS5.0\x00\x10\x01\x01\x00')
self.assertEqual(file_system[0x1010:0x1020], b'\x01\x00\x02\xfa\x00\xf8\x01\x00?\x00\xff\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x1010:0x1020], b'\x02\x00\x02\xfa\x00\xf8\x01\x00?\x00\xff\x00\x00\x00\x00\x00') # two fats b'\x02...'
self.assertEqual(file_system[0x102b:0x1034], b'Espressif')
self.assertEqual(file_system[0x4000:0x400c], b'TESTFOLD \x10') # check entry name and type
self.assertEqual(file_system[0x2000:0x2006], b'\xf8\xff\xff\xff\x0f\x00') # check fat
self.assertEqual(file_system[0x8000:0x800c], b'. \x10') # reference to itself
self.assertEqual(file_system[0x8020:0x802c], b'.. \x10') # reference to parent
# check state1
self.assertEqual(file_system[0xfb000:0xfb00f], b'\x00\x00\x00\x00\xfb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0xfb010:0xfb020], b'\x10\x00\x00\x00\x00\x10\x00\x00\x02\x00\x00\x00\tO\x8b\xdf')
self.assertEqual(file_system[0xfb020:0xfb02f], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0xfb031:0xfb040], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd4\xa1\x94i')
# check state2
self.assertEqual(file_system[0xfd000:0xfd00f], b'\x00\x00\x00\x00\xfb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0xfd010:0xfd020], b'\x10\x00\x00\x00\x00\x10\x00\x00\x02\x00\x00\x00\tO\x8b\xdf')
self.assertEqual(file_system[0xfd020:0xfd02f], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0xfd031:0xfd040], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd4\xa1\x94i')
# check config
self.assertEqual(file_system[0xff001:0xff010], b'\x00\x00\x00\x00\x00\x10\x00\x00\x10\x00\x00\x00\x10\x00\x00')
self.assertEqual(file_system[0xff010:0xff01f], b'\x10\x00\x00\x00\x10\x00\x00\x00\x02\x00\x00\x00 \x00\x00')
self.assertEqual(file_system[0xff020:0xff030], b'\xe0b\xb5O\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0xff030:0xff03f], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff')
def test_directory_sn_fat12_one_fat(self) -> None:
fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=1, device_id=3750448905)
fatfs.plain_fatfs.create_directory('TESTFOLD')
fatfs.init_wl()
fatfs.wl_write_filesystem(CFG['output_file'])
with open(CFG['output_file'], 'rb') as fs_file:
file_system = fs_file.read()
# boot sector
self.assertEqual(file_system[0x1000:0x1010], b'\xeb\xfe\x90MSDOS5.0\x00\x10\x01\x01\x00')
self.assertEqual(file_system[0x1010:0x1020], b'\x01\x00\x02\xfa\x00\xf8\x01\x00?\x00\xff\x00\x00\x00\x00\x00') # one fat b'\x01...'
self.assertEqual(file_system[0x102b:0x1034], b'Espressif')
self.assertEqual(file_system[0x3000:0x300c], b'TESTFOLD \x10') # check entry name and type
@ -72,7 +120,36 @@ class WLFatFSGen(unittest.TestCase):
self.assertEqual(file_system[0xff030:0xff03f], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff')
def test_directory_sn_fat122mb(self) -> None:
fatfs = wl_fatfsgen.WLFATFS(device_id=3750448905, size=2 * 1024 * 1024)
fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=2, device_id=3750448905, size=2 * 1024 * 1024)
fatfs.plain_fatfs.create_directory('TESTFOLD')
fatfs.init_wl()
fatfs.wl_write_filesystem(CFG['output_file'])
with open(CFG['output_file'], 'rb') as fs_file:
file_system = fs_file.read()
# check state1
self.assertEqual(file_system[0x1f9000:0x1f900e], b'\x00\x00\x00\x00\xf9\x01\x00\x00\x00\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x1f9010:0x1f9020],
b'\x10\x00\x00\x00\x00\x10\x00\x00\x02\x00\x00\x00\tO\x8b\xdf')
self.assertEqual(file_system[0x1f9020:0x1f902e], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x1f9030:0x1f9040], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00j5\xbdp')
# check state2
self.assertEqual(file_system[0x1fc000:0x1fc00e], b'\x00\x00\x00\x00\xf9\x01\x00\x00\x00\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x1fc010:0x1fc020],
b'\x10\x00\x00\x00\x00\x10\x00\x00\x02\x00\x00\x00\tO\x8b\xdf')
self.assertEqual(file_system[0x1fc020:0x1fc02e], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x1fc030:0x1fc040], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00j5\xbdp')
# check config
self.assertEqual(file_system[0x1ff000:0x1ff00f], b'\x00\x00\x00\x00\x00\x00 \x00\x00\x10\x00\x00\x00\x10\x00')
self.assertEqual(file_system[0x1ff010:0x1ff01f], b'\x10\x00\x00\x00\x10\x00\x00\x00\x02\x00\x00\x00 \x00\x00')
self.assertEqual(file_system[0x1ff020:0x1ff030], b')\x892j\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x1ff030:0x1ff03e], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff')
def test_directory_sn_fat122mb_one_fat(self) -> None:
fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=1, device_id=3750448905, size=2 * 1024 * 1024)
fatfs.plain_fatfs.create_directory('TESTFOLD')
fatfs.init_wl()
@ -101,12 +178,29 @@ class WLFatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x1ff030:0x1ff03e], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff')
def test_write_not_initialized_wlfatfs(self) -> None:
fatfs = wl_fatfsgen.WLFATFS()
fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=2)
fatfs.plain_fatfs.create_directory('TESTFOLD')
self.assertRaises(WLNotInitialized, fatfs.wl_write_filesystem, CFG['output_file'])
def test_e2e_deep_folder_into_image_ext(self) -> None:
fatfs = wl_fatfsgen.WLFATFS()
fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=2)
fatfs.plain_fatfs.generate(CFG['test_dir2'])
fatfs.init_wl()
fatfs.wl_write_filesystem(CFG['output_file'])
with open(CFG['output_file'], 'rb') as fs_file:
file_system = bytearray(fs_file.read())
self.assertEqual(file_system[0x4020:0x4030], b'TESTFILE \x00\x00\x00\x00')
self.assertEqual(file_system[0x8060:0x8070], b'TESTFIL2 \x00\x00\x00\x00')
self.assertEqual(file_system[0x9000:0x9010], b'. \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x9040:0x9050], b'LASTFILETXT \x00\x00\x00\x00')
self.assertEqual(file_system[0xa000:0xa010], b'deeptest\n\x00\x00\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0xb000:0xb010], b'thisistest\n\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0xc000:0xc010], b'ahoj\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0xd000:0xd009], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff')
def test_e2e_deep_folder_into_image_ext_one_fat(self) -> None:
fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=1)
fatfs.plain_fatfs.generate(CFG['test_dir2'])
fatfs.init_wl()
fatfs.wl_write_filesystem(CFG['output_file'])
@ -123,7 +217,22 @@ class WLFatFSGen(unittest.TestCase):
self.assertEqual(file_system[0xc000:0xc009], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff')
def test_e2e_deep_folder_into_image(self) -> None:
fatfs = wl_fatfsgen.WLFATFS()
fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=2)
fatfs.plain_fatfs.generate(CFG['test_dir'])
fatfs.init_wl()
fatfs.wl_write_filesystem(CFG['output_file'])
with open(CFG['output_file'], 'rb') as fs_file:
file_system = bytearray(fs_file.read())
self.assertEqual(file_system[0x8060:0x8070], b'TESTFIL2 \x00\x00\x00\x00')
self.assertEqual(file_system[0x8070:0x8080], b'!\x00!\x00\x00\x00\x00\x00!\x00\x05\x00\x0b\x00\x00\x00')
self.assertEqual(file_system[0x9040:0x9050], b'LASTFILE \x00\x00\x00\x00')
self.assertEqual(file_system[0xa000:0xa010], b'deeptest\n\x00\x00\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0xb000:0xb010], b'thisistest\n\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0xc000:0xc010], b'ahoj\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
def test_e2e_deep_folder_into_image_one_fat(self) -> None:
fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=1)
fatfs.plain_fatfs.generate(CFG['test_dir'])
fatfs.init_wl()
fatfs.wl_write_filesystem(CFG['output_file'])
@ -137,6 +246,66 @@ class WLFatFSGen(unittest.TestCase):
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')
def test_chaining_wl_fat16(self) -> None:
fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=2, size=17 * 1024 * 1024)
fatfs.plain_fatfs.create_file('WRITEF', extension='TXT')
fatfs.plain_fatfs.write_content(path_from_root=['WRITEF.TXT'], content=CFG['sector_size'] * b'a' + b'a')
fatfs.init_wl()
fatfs.wl_write_filesystem(CFG['output_file'])
with open(CFG['output_file'], 'rb') as fs_file:
file_system = bytearray(fs_file.read())
self.assertEqual(file_system[0x2000: 0x200e], b'\xf8\xff\xff\xff\x03\x00\xff\xff\x00\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0xd000: 0xe000], b'a' + (CFG['sector_size'] - 1) * b'\x00')
def test_chaining_wl_fat16_one_fat(self) -> None:
fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=1, size=17 * 1024 * 1024)
fatfs.plain_fatfs.create_file('WRITEF', extension='TXT')
fatfs.plain_fatfs.write_content(path_from_root=['WRITEF.TXT'], content=CFG['sector_size'] * b'a' + b'a')
fatfs.init_wl()
fatfs.wl_write_filesystem(CFG['output_file'])
with open(CFG['output_file'], 'rb') as fs_file:
file_system = bytearray(fs_file.read())
self.assertEqual(file_system[0x2000: 0x200e], b'\xf8\xff\xff\xff\x03\x00\xff\xff\x00\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0xa000: 0xb000], b'a' + (CFG['sector_size'] - 1) * b'\x00')
def test_full_sector_folder_wl_fat16(self) -> None:
fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=2, size=17 * 1024 * 1024)
fatfs.plain_fatfs.create_directory('TESTFOLD')
fill_sector(fatfs.plain_fatfs)
fatfs.plain_fatfs.write_content(path_from_root=['TESTFOLD', 'A0'], content=b'first')
fatfs.plain_fatfs.write_content(path_from_root=['TESTFOLD', 'A126'], content=b'later')
fatfs.init_wl()
fatfs.wl_write_filesystem(CFG['output_file'])
with open(CFG['output_file'], 'rb') as fs_file:
file_system = bytearray(fs_file.read())
self.assertEqual(file_system[0x2000: 0x2110],
b'\xf8\xff\xff\xff\x82\x00' + 258 * b'\xff' + 8 * b'\x00')
self.assertEqual(file_system[0x8b000:0x8b005], b'later')
self.assertEqual(file_system[0x8c000:0x8c010], b'A126 \x00\x00\x00\x00')
self.assertEqual(file_system[0x8c020:0x8c030], b'A127 \x00\x00\x00\x00')
def test_full_sector_folder_wl_fat16_one_fat(self) -> None:
fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=1, size=17 * 1024 * 1024)
fatfs.plain_fatfs.create_directory('TESTFOLD')
fill_sector(fatfs.plain_fatfs)
fatfs.plain_fatfs.write_content(path_from_root=['TESTFOLD', 'A0'], content=b'first')
fatfs.plain_fatfs.write_content(path_from_root=['TESTFOLD', 'A126'], content=b'later')
fatfs.init_wl()
fatfs.wl_write_filesystem(CFG['output_file'])
with open(CFG['output_file'], 'rb') as fs_file:
file_system = bytearray(fs_file.read())
self.assertEqual(file_system[0x2000: 0x2110],
b'\xf8\xff\xff\xff\x82\x00' + 258 * b'\xff' + 8 * b'\x00')
self.assertEqual(file_system[0x88000:0x88005], b'later')
self.assertEqual(file_system[0x89000:0x89010], b'A126 \x00\x00\x00\x00')
self.assertEqual(file_system[0x89020:0x89030], b'A127 \x00\x00\x00\x00')
if __name__ == '__main__':
unittest.main()

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -95,8 +95,26 @@ typedef struct {
* Doesn't do anything for other memory storage media.
*/
bool disk_status_check_enable;
/**
* Use 1 FAT (File Allocation Tables) instead of 2.
* This decreases reliability, but makes more space available
* (usually only one sector).
* Note that this option has effect only when the filesystem is formatted.
* When mounting an already-formatted partition, the actual number of FATs
* may be different.
*/
bool use_one_fat;
} esp_vfs_fat_mount_config_t;
#define VFS_FAT_MOUNT_DEFAULT_CONFIG() \
{ \
.format_if_mount_failed = false, \
.max_files = 5, \
.allocation_unit_size = 0, \
.disk_status_check_enable = false, \
.use_one_fat = false, \
}
// Compatibility definition
typedef esp_vfs_fat_mount_config_t esp_vfs_fat_sdmmc_mount_config_t;

View File

@ -207,7 +207,7 @@ static esp_err_t partition_card(const esp_vfs_fat_mount_config_t *mount_config,
card->csd.sector_size,
mount_config->allocation_unit_size);
ESP_LOGW(TAG, "formatting card, allocation unit size=%d", alloc_unit_size);
const MKFS_PARM opt = {(BYTE)FM_ANY, 0, 0, 0, alloc_unit_size};
const MKFS_PARM opt = {(BYTE)FM_ANY, (mount_config->use_one_fat ? 1 : 2), 0, 0, alloc_unit_size};
res = f_mkfs(drv, &opt, workbuf, workbuf_size);
if (res != FR_OK) {
err = ESP_FAIL;
@ -486,7 +486,7 @@ esp_err_t esp_vfs_fat_sdcard_format(const char *base_path, sdmmc_card_t *card)
card->csd.sector_size,
s_ctx[id]->mount_config.allocation_unit_size);
ESP_LOGI(TAG, "Formatting card, allocation unit size=%d", alloc_unit_size);
const MKFS_PARM opt = {(BYTE)FM_ANY, 0, 0, 0, alloc_unit_size};
const MKFS_PARM opt = {(BYTE)FM_ANY, (s_ctx[id]->mount_config.use_one_fat ? 1 : 2), 0, 0, alloc_unit_size};
res = f_mkfs(drv, &opt, workbuf, workbuf_size);
free(workbuf);
if (res != FR_OK) {

View File

@ -93,7 +93,7 @@ static esp_err_t s_f_mount_rw(FATFS *fs, const char *drv, const esp_vfs_fat_moun
size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size(CONFIG_WL_SECTOR_SIZE, mount_config->allocation_unit_size);
ESP_LOGI(TAG, "Formatting FATFS partition, allocation unit size=%d", alloc_unit_size);
const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), 0, 0, 0, alloc_unit_size};
const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), (mount_config->use_one_fat ? 1 : 2), 0, 0, alloc_unit_size};
fresult = f_mkfs(drv, &opt, workbuf, workbuf_size);
free(workbuf);
workbuf = NULL;
@ -236,7 +236,7 @@ esp_err_t esp_vfs_fat_spiflash_format_rw_wl(const char* base_path, const char* p
}
size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size(CONFIG_WL_SECTOR_SIZE, s_ctx[id]->mount_config.allocation_unit_size);
ESP_LOGI(TAG, "Formatting FATFS partition, allocation unit size=%d", alloc_unit_size);
const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), 0, 0, 0, alloc_unit_size};
const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), (s_ctx[id]->mount_config.use_one_fat ? 1 : 2), 0, 0, alloc_unit_size};
fresult = f_mkfs(drv, &opt, workbuf, workbuf_size);
free(workbuf);
workbuf = NULL;

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
from construct import Const, Int32ul, Struct
@ -202,13 +202,14 @@ class WLFATFS:
if __name__ == '__main__':
desc = 'Create a FAT filesystem with support for wear levelling and populate it with directory content'
args = get_args_for_partition_generator(desc, wl=True)
wl_fatfs = WLFATFS(sectors_per_cluster=args.sectors_per_cluster,
size=args.partition_size,
wl_fatfs = WLFATFS(size=args.partition_size,
sector_size=args.sector_size,
root_entry_count=args.root_entry_count,
fat_tables_cnt=args.fat_count,
sectors_per_cluster=args.sectors_per_cluster,
explicit_fat_type=args.fat_type,
long_names_enabled=args.long_name_support,
use_default_datetime=args.use_default_datetime)
use_default_datetime=args.use_default_datetime,
root_entry_count=args.root_entry_count)
wl_fatfs.plain_fatfs.generate(args.input_directory)
wl_fatfs.init_wl()

View File

@ -126,6 +126,8 @@ The arguments of the function are as follows:
#. flag ``PRESERVE_TIME`` - optionally, users can force preserving the timestamps from the source folder to the target image. Without preserving the time, every timestamp will be set to the FATFS default initial time (1st January 1980).
#. flag ``ONE_FAT`` - optionally, users can still choose to generate a FATFS volume with a single FAT (file allocation table) instead of two. This makes the free space in the FATFS volume a little bit larger (by ``number of sectors used by FAT * sector size``) but also more prone to corruption.
For example::

View File

@ -195,7 +195,8 @@ static bool example_mount_fatfs(const char* partition_label)
const esp_vfs_fat_mount_config_t mount_config = {
.max_files = 4,
.format_if_mount_failed = true,
.allocation_unit_size = CONFIG_WL_SECTOR_SIZE
.allocation_unit_size = CONFIG_WL_SECTOR_SIZE,
.use_one_fat = false,
};
esp_err_t err = esp_vfs_fat_spiflash_mount_rw_wl(base_path, partition_label, &mount_config, &s_wl_handle);
if (err != ESP_OK) {

View File

@ -40,7 +40,8 @@ void app_main(void)
const esp_vfs_fat_mount_config_t mount_config = {
.max_files = 4,
.format_if_mount_failed = false,
.allocation_unit_size = CONFIG_WL_SECTOR_SIZE
.allocation_unit_size = CONFIG_WL_SECTOR_SIZE,
.use_one_fat = false,
};
esp_err_t err;
if (EXAMPLE_FATFS_MODE_READ_ONLY){

View File

@ -71,7 +71,8 @@ void app_main(void)
const esp_vfs_fat_mount_config_t mount_config = {
.max_files = 4,
.format_if_mount_failed = true,
.allocation_unit_size = CONFIG_WL_SECTOR_SIZE
.allocation_unit_size = CONFIG_WL_SECTOR_SIZE,
.use_one_fat = false,
};
esp_err_t err = esp_vfs_fat_spiflash_mount_rw_wl(base_path, "storage", &mount_config, &s_wl_handle);
if (err != ESP_OK) {