diff --git a/components/fatfs/fatfs_utils/boot_sector.py b/components/fatfs/fatfs_utils/boot_sector.py index 389d27c7a2..2809c03339 100644 --- a/components/fatfs/fatfs_utils/boot_sector.py +++ b/components/fatfs/fatfs_utils/boot_sector.py @@ -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'), diff --git a/components/fatfs/fatfs_utils/fatfs_state.py b/components/fatfs/fatfs_utils/fatfs_state.py index 22af7bfb0d..4ba571324a 100644 --- a/components/fatfs/fatfs_utils/fatfs_state.py +++ b/components/fatfs/fatfs_utils/fatfs_state.py @@ -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 diff --git a/components/fatfs/fatfs_utils/utils.py b/components/fatfs/fatfs_utils/utils.py index d2180c76bd..e85b99d4b2 100644 --- a/components/fatfs/fatfs_utils/utils.py +++ b/components/fatfs/fatfs_utils/utils.py @@ -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 diff --git a/components/fatfs/fatfsgen.py b/components/fatfs/fatfsgen.py index 199916ef38..4ab1302001 100755 --- a/components/fatfs/fatfsgen.py +++ b/components/fatfs/fatfsgen.py @@ -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) diff --git a/components/fatfs/project_include.cmake b/components/fatfs/project_include.cmake index d945c0e96e..aa0edf2a81 100644 --- a/components/fatfs/project_include.cmake +++ b/components/fatfs/project_include.cmake @@ -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() diff --git a/components/fatfs/test_fatfsgen/test_fatfsgen.py b/components/fatfs/test_fatfsgen/test_fatfsgen.py index 5816991c0e..0e73ec2940 100755 --- a/components/fatfs/test_fatfsgen/test_fatfsgen.py +++ b/components/fatfs/test_fatfsgen/test_fatfsgen.py @@ -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']) diff --git a/components/fatfs/test_fatfsgen/test_wl_fatfsgen.py b/components/fatfs/test_fatfsgen/test_wl_fatfsgen.py index 2b3e28c7eb..3179441d12 100755 --- a/components/fatfs/test_fatfsgen/test_wl_fatfsgen.py +++ b/components/fatfs/test_fatfsgen/test_wl_fatfsgen.py @@ -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() diff --git a/components/fatfs/vfs/esp_vfs_fat.h b/components/fatfs/vfs/esp_vfs_fat.h index 983dc8d6b7..70e2392d7c 100644 --- a/components/fatfs/vfs/esp_vfs_fat.h +++ b/components/fatfs/vfs/esp_vfs_fat.h @@ -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; diff --git a/components/fatfs/vfs/vfs_fat_sdmmc.c b/components/fatfs/vfs/vfs_fat_sdmmc.c index f3742a4a6a..418b0880ba 100644 --- a/components/fatfs/vfs/vfs_fat_sdmmc.c +++ b/components/fatfs/vfs/vfs_fat_sdmmc.c @@ -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) { diff --git a/components/fatfs/vfs/vfs_fat_spiflash.c b/components/fatfs/vfs/vfs_fat_spiflash.c index 98e4160faf..f3ea28b8b0 100644 --- a/components/fatfs/vfs/vfs_fat_spiflash.c +++ b/components/fatfs/vfs/vfs_fat_spiflash.c @@ -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; diff --git a/components/fatfs/wl_fatfsgen.py b/components/fatfs/wl_fatfsgen.py index 4a685434a0..104b08ff3d 100755 --- a/components/fatfs/wl_fatfsgen.py +++ b/components/fatfs/wl_fatfsgen.py @@ -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() diff --git a/docs/en/api-reference/storage/fatfs.rst b/docs/en/api-reference/storage/fatfs.rst index 338aac18ed..a5b85c00fd 100644 --- a/docs/en/api-reference/storage/fatfs.rst +++ b/docs/en/api-reference/storage/fatfs.rst @@ -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:: diff --git a/examples/storage/ext_flash_fatfs/main/ext_flash_fatfs_example_main.c b/examples/storage/ext_flash_fatfs/main/ext_flash_fatfs_example_main.c index ea5667e0b5..aa2a2c0990 100644 --- a/examples/storage/ext_flash_fatfs/main/ext_flash_fatfs_example_main.c +++ b/examples/storage/ext_flash_fatfs/main/ext_flash_fatfs_example_main.c @@ -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) { diff --git a/examples/storage/fatfsgen/main/fatfsgen_example_main.c b/examples/storage/fatfsgen/main/fatfsgen_example_main.c index 1c0c94dc3f..37a62a6145 100644 --- a/examples/storage/fatfsgen/main/fatfsgen_example_main.c +++ b/examples/storage/fatfsgen/main/fatfsgen_example_main.c @@ -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){ diff --git a/examples/storage/wear_levelling/main/wear_levelling_example_main.c b/examples/storage/wear_levelling/main/wear_levelling_example_main.c index bea0bb5d8e..94eefb8853 100644 --- a/examples/storage/wear_levelling/main/wear_levelling_example_main.c +++ b/examples/storage/wear_levelling/main/wear_levelling_example_main.c @@ -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) {