summaryrefslogtreecommitdiff
path: root/lib/fatfs
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2025-07-25 13:33:07 +1000
committerjacqueline <me@jacqueline.id.au>2025-07-25 13:33:07 +1000
commitc8e79a926620e48830778714cfe4b2ea2453fcaf (patch)
tree8c756e08e01b8e147cf72bec128026f46bd854c5 /lib/fatfs
parent237136f3e93cb6b5be24670d7520adb17cc0fa36 (diff)
downloadtangara-fw-c8e79a926620e48830778714cfe4b2ea2453fcaf.tar.gz
Update forked idf components
Diffstat (limited to 'lib/fatfs')
-rw-r--r--lib/fatfs/.build-test-rules.yml7
-rw-r--r--lib/fatfs/Kconfig111
-rw-r--r--lib/fatfs/diskio/diskio.c6
-rw-r--r--lib/fatfs/diskio/diskio_rawflash.c16
-rw-r--r--lib/fatfs/diskio/diskio_sdmmc.c18
-rw-r--r--lib/fatfs/diskio/diskio_sdmmc.h2
-rw-r--r--lib/fatfs/diskio/diskio_wl.c24
-rw-r--r--lib/fatfs/fatfs_utils/boot_sector.py22
-rw-r--r--lib/fatfs/fatfs_utils/fatfs_state.py3
-rw-r--r--lib/fatfs/fatfs_utils/utils.py34
-rwxr-xr-xlib/fatfs/fatfsgen.py67
-rwxr-xr-xlib/fatfs/fatfsparse.py11
-rw-r--r--lib/fatfs/host_test/.build-test-rules.yml7
-rw-r--r--lib/fatfs/host_test/CMakeLists.txt4
-rw-r--r--lib/fatfs/host_test/main/CMakeLists.txt8
-rw-r--r--lib/fatfs/host_test/main/idf_component.yml2
-rw-r--r--lib/fatfs/host_test/main/main.cpp7
-rw-r--r--lib/fatfs/host_test/main/test_fatfs.cpp22
-rw-r--r--lib/fatfs/host_test/partition_table.csv4
-rw-r--r--lib/fatfs/host_test/pytest_fatfs_linux.py5
-rw-r--r--lib/fatfs/host_test/sdkconfig.defaults1
-rw-r--r--lib/fatfs/port/freertos/ffsystem.c2
-rw-r--r--lib/fatfs/project_include.cmake57
-rw-r--r--lib/fatfs/src/ff.c59
-rw-r--r--lib/fatfs/src/ff.h12
-rw-r--r--lib/fatfs/src/ffconf.h43
-rw-r--r--lib/fatfs/test_apps/.build-test-rules.yml48
-rw-r--r--lib/fatfs/test_apps/README.md4
-rw-r--r--lib/fatfs/test_apps/dyn_buffers/CMakeLists.txt7
-rw-r--r--lib/fatfs/test_apps/dyn_buffers/README.md8
-rw-r--r--lib/fatfs/test_apps/dyn_buffers/main/CMakeLists.txt3
-rw-r--r--lib/fatfs/test_apps/dyn_buffers/main/test_fatfs_dyn_buffers.c101
-rw-r--r--lib/fatfs/test_apps/dyn_buffers/partitions.csv5
-rw-r--r--lib/fatfs/test_apps/dyn_buffers/pytest_fatfs_dyn_buffers.py22
-rw-r--r--lib/fatfs/test_apps/dyn_buffers/sdkconfig.ci.dyn_buffers1
-rw-r--r--lib/fatfs/test_apps/dyn_buffers/sdkconfig.ci.no_dyn_buffers1
-rw-r--r--lib/fatfs/test_apps/dyn_buffers/sdkconfig.defaults14
-rw-r--r--lib/fatfs/test_apps/flash_ro/README.md4
-rw-r--r--lib/fatfs/test_apps/flash_ro/main/test_fatfs_flash_ro.c3
-rw-r--r--lib/fatfs/test_apps/flash_ro/pytest_fatfs_flash_ro.py12
-rw-r--r--lib/fatfs/test_apps/flash_wl/README.md4
-rw-r--r--lib/fatfs/test_apps/flash_wl/main/CMakeLists.txt2
-rw-r--r--lib/fatfs/test_apps/flash_wl/main/Kconfig.projbuild10
-rw-r--r--lib/fatfs/test_apps/flash_wl/main/test_fatfs_flash_wl.c92
-rw-r--r--lib/fatfs/test_apps/flash_wl/main/test_fatfs_small_partition.c76
-rw-r--r--lib/fatfs/test_apps/flash_wl/partitions.csv1
-rw-r--r--lib/fatfs/test_apps/flash_wl/pytest_fatfs_flash_wl.py27
-rw-r--r--lib/fatfs/test_apps/flash_wl/sdkconfig.ci.auto_fsync2
-rw-r--r--lib/fatfs/test_apps/flash_wl/sdkconfig.ci.dyn_buffers1
-rw-r--r--lib/fatfs/test_apps/flash_wl/sdkconfig.ci.psram.esp32 (renamed from lib/fatfs/test_apps/flash_wl/sdkconfig.ci.psram)1
-rw-r--r--lib/fatfs/test_apps/sdcard/README.md4
-rw-r--r--lib/fatfs/test_apps/sdcard/main/CMakeLists.txt2
-rw-r--r--lib/fatfs/test_apps/sdcard/main/test_fatfs_sdmmc.c275
-rw-r--r--lib/fatfs/test_apps/sdcard/main/test_fatfs_sdspi.c61
-rw-r--r--lib/fatfs/test_apps/sdcard/pytest_fatfs_sdcard.py48
-rw-r--r--lib/fatfs/test_apps/sdcard/sdkconfig.ci.psram.esp32 (renamed from lib/fatfs/test_apps/sdcard/sdkconfig.ci.psram)1
-rw-r--r--lib/fatfs/test_apps/test_fatfs_common/CMakeLists.txt2
-rw-r--r--lib/fatfs/test_apps/test_fatfs_common/test_fatfs_common.c245
-rw-r--r--lib/fatfs/test_apps/test_fatfs_common/test_fatfs_common.h10
-rw-r--r--lib/fatfs/test_fatfs_host/Makefile106
-rw-r--r--lib/fatfs/test_fatfs_host/Makefile.files44
-rw-r--r--lib/fatfs/test_fatfs_host/component.mk17
-rw-r--r--lib/fatfs/test_fatfs_host/main.cpp2
-rw-r--r--lib/fatfs/test_fatfs_host/partition_table.csv6
-rw-r--r--lib/fatfs/test_fatfs_host/test_fatfs.cpp94
-rwxr-xr-xlib/fatfs/test_fatfsgen/test_fatfsgen.py441
-rwxr-xr-xlib/fatfs/test_fatfsgen/test_fatfsparse.py50
-rwxr-xr-xlib/fatfs/test_fatfsgen/test_wl_fatfsgen.py187
-rw-r--r--lib/fatfs/vfs/esp_vfs_fat.h118
-rw-r--r--lib/fatfs/vfs/vfs_fat.c625
-rw-r--r--lib/fatfs/vfs/vfs_fat_internal.h47
-rw-r--r--lib/fatfs/vfs/vfs_fat_sdmmc.c85
-rw-r--r--lib/fatfs/vfs/vfs_fat_spiflash.c168
-rwxr-xr-xlib/fatfs/wl_fatfsgen.py33
74 files changed, 2821 insertions, 853 deletions
diff --git a/lib/fatfs/.build-test-rules.yml b/lib/fatfs/.build-test-rules.yml
deleted file mode 100644
index 737e82b7..00000000
--- a/lib/fatfs/.build-test-rules.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
-
-components/fatfs/test_apps/sdcard:
- disable_test:
- - if: IDF_TARGET in ["esp32s3", "esp32c2", "esp32c6", "esp32h2"]
- temporary: true
- reason: No sdspi runners for these targets
diff --git a/lib/fatfs/Kconfig b/lib/fatfs/Kconfig
index f2c737a6..1a35dc4e 100644
--- a/lib/fatfs/Kconfig
+++ b/lib/fatfs/Kconfig
@@ -64,7 +64,7 @@ menu "FAT Filesystem support"
config FATFS_CODEPAGE_857
bool "Turkish (CP857)"
config FATFS_CODEPAGE_860
- bool "Portugese (CP860)"
+ bool "Portuguese (CP860)"
config FATFS_CODEPAGE_861
bool "Icelandic (CP861)"
config FATFS_CODEPAGE_862
@@ -161,7 +161,7 @@ menu "FAT Filesystem support"
help
This option sets FATFS configuration value _FS_TIMEOUT, scaled to milliseconds.
Sets the number of milliseconds FATFS will wait to acquire a mutex when
- operating on an open file. For example, if one task is performing a lenghty
+ operating on an open file. For example, if one task is performing a lengthy
operation, another task will wait for the first task to release the lock,
and time out after amount of time set by this option.
@@ -184,7 +184,7 @@ menu "FAT Filesystem support"
config FATFS_ALLOC_PREFER_EXTRAM
- bool "Perfer external RAM when allocating FATFS buffers"
+ bool "Prefer external RAM when allocating FATFS buffers"
default y
depends on SPIRAM_USE_CAPS_ALLOC || SPIRAM_USE_MALLOC
help
@@ -205,6 +205,63 @@ menu "FAT Filesystem support"
file is opened in write-mode, the seek mechanism will automatically fallback
to the default implementation.
+ choice FATFS_USE_STRFUNC_CHOICE
+ prompt "Enable string functions, f_gets(), f_putc(), f_puts() and f_printf()"
+ default FATFS_USE_STRFUNC_NONE
+ help
+ These are specialized alternatives to stdio functions for working
+ directly with FATFS without VFS. Legacy code may need functions,
+ but for new development, it is advised to use stdio under VFS.
+
+ 0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect.
+ 1: Enable without LF-CRLF conversion.
+ 2: Enable with LF-CRLF conversion.
+
+ config FATFS_USE_STRFUNC_NONE
+ bool "0:Disable"
+
+ config FATFS_USE_STRFUNC_WITHOUT_CRLF_CONV
+ bool "1:Enable without LF-CRLF conversion"
+
+ config FATFS_USE_STRFUNC_WITH_CRLF_CONV
+ bool "2:Enable with LF-CRLF conversion"
+ endchoice
+
+ config FATFS_PRINT_LLI
+ depends on !FATFS_USE_STRFUNC_NONE
+ bool "Make fatfs f_printf() support long long argument"
+ default 0
+
+ config FATFS_PRINT_FLOAT
+ depends on !FATFS_USE_STRFUNC_NONE
+ bool "Make fatfs f_printf() support floating point argument"
+ default 0
+
+ choice FATFS_STRF_ENCODE_CHOICE
+ prompt "FatFS string functions: convert character encoding"
+ depends on !FATFS_LFN_NONE && !FATFS_USE_STRFUNC_NONE
+ default FATFS_STRF_ENCODE_UTF8
+ help
+ When FF_LFN_UNICODE >= 1 with LFN enabled, string functions convert the character
+ encoding in it. FF_STRF_ENCODE selects assumption of character encoding ON THE FILE
+ to be read/written via those functions.
+ 0: ANSI/OEM in current CP
+ 1: Unicode in UTF-16LE
+ 2: Unicode in UTF-16BE
+ 3: Unicode in UTF-8
+
+ config FATFS_STRF_ENCODE_ANSI
+ bool "0:ANSI/OEM in current CP"
+
+ config FATFS_STRF_ENCODE_UTF16LE
+ bool "1:Unicode in UTF-16LE"
+
+ config FATFS_STRF_ENCODE_UTF16BE
+ bool "2:Unicode in UTF-16BE"
+
+ config FATFS_STRF_ENCODE_UTF8
+ bool "3:Unicode in UTF-8"
+ endchoice
config FATFS_FAST_SEEK_BUFFER_SIZE
int "Fast seek CLMT buffer size"
@@ -239,4 +296,52 @@ menu "FAT Filesystem support"
vfs_fat_pwrite(), vfs_fat_link(), vfs_fat_truncate() and vfs_fat_ftruncate() functions.
This feature improves file-consistency and size reporting accuracy for the FatFS,
at a price on decreased performance due to frequent disk operations
+
+ config FATFS_USE_LABEL
+ bool "Use FATFS volume label"
+ default n
+ help
+ Allows FATFS volume label to be specified using f_setlabel
+
+ config FATFS_LINK_LOCK
+ bool "Perform the whole link operation under lock"
+ default y
+ help
+ If enabled, the whole link operation (including file copying) is performed under lock.
+ This ensures that the link operation is atomic, but may cause performance for large files.
+ It may create less fragmented file copy.
+
+ config FATFS_USE_DYN_BUFFERS
+ bool "Use dynamic buffers"
+ default n
+ help
+ If enabled, the buffers used by FATFS will be allocated separately from the rest of the structure.
+ This option is useful when using multiple FATFS instances with different
+ sector sizes, as the buffers will be allocated according to the sector size.
+ If disabled, the greatest sector size will be used for all FATFS instances.
+ (In most cases, this would be the sector size of Wear Levelling library)
+ This might cause more memory to be used than necessary.
+
+ menu "File system free space calculation behavior"
+ help
+ Controls if the file system does or does not trust cached data like free cluster count and allocated
+ cluster number. Setting these to do not trust the data may result of more accurate output from
+ `f_getfree()` function but increased overhead (forces a full FAT scan, etc.).
+
+ config FATFS_DONT_TRUST_FREE_CLUSTER_CNT
+ int "Don't trust free cluster count"
+ default 0
+ range 0 1
+ help
+ If 1, the file system will not trust the free cluster count in the FSINFO (in FATFS struct).
+ This may result in more accurate output from `f_getfree()` function but increased overhead.
+
+ config FATFS_DONT_TRUST_LAST_ALLOC
+ int "Don't trust allocated cluster number"
+ default 0
+ range 0 1
+ help
+ If 1, the file system will not trust the last allocated cluster number in the FSINFO (in FATFS struct).
+ This may result in more accurate output from `f_getfree()` function but increased overhead.
+ endmenu
endmenu
diff --git a/lib/fatfs/diskio/diskio.c b/lib/fatfs/diskio/diskio.c
index 4bafa7ea..7d39835f 100644
--- a/lib/fatfs/diskio/diskio.c
+++ b/lib/fatfs/diskio/diskio.c
@@ -1,10 +1,10 @@
/*-----------------------------------------------------------------------*/
/* Low level disk I/O module skeleton for FatFs (C)ChaN, 2016 */
-/* ESP-IDF port Copyright 2016 Espressif Systems (Shanghai) PTE LTD */
+/* ESP-IDF port Copyright 2016-2025 Espressif Systems (Shanghai) PTE LTD */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be */
/* attached to the FatFs via a glue function rather than modifying it. */
-/* This is an example of glue functions to attach various exsisting */
+/* This is an example of glue functions to attach various existing */
/* storage control modules to the FatFs module with a defined API. */
/*-----------------------------------------------------------------------*/
@@ -19,7 +19,7 @@
static ff_diskio_impl_t * s_impls[FF_VOLUMES] = { NULL };
#if FF_MULTI_PARTITION /* Multiple partition configuration */
-const PARTITION VolToPart[FF_VOLUMES] = {
+PARTITION VolToPart[FF_VOLUMES] = {
{0, 0}, /* Logical drive 0 ==> Physical drive 0, auto detection */
#if FF_VOLUMES > 1
{1, 0}, /* Logical drive 1 ==> Physical drive 1, auto detection */
diff --git a/lib/fatfs/diskio/diskio_rawflash.c b/lib/fatfs/diskio/diskio_rawflash.c
index 313aacec..4bc3eb8e 100644
--- a/lib/fatfs/diskio/diskio_rawflash.c
+++ b/lib/fatfs/diskio/diskio_rawflash.c
@@ -1,5 +1,5 @@
/*
- * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
+ * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -25,7 +25,7 @@ static uint8_t s_initialized[FF_VOLUMES];
#define BPB_TotSec32 32
-DSTATUS ff_raw_initialize (BYTE pdrv)
+static DSTATUS ff_raw_initialize (BYTE pdrv)
{
uint16_t sector_size_tmp;
@@ -61,7 +61,7 @@ DSTATUS ff_raw_initialize (BYTE pdrv)
return STA_PROTECT;
}
-DSTATUS ff_raw_status (BYTE pdrv)
+static DSTATUS ff_raw_status (BYTE pdrv)
{
DSTATUS status = STA_PROTECT;
if (!s_initialized[pdrv]) {
@@ -70,7 +70,7 @@ DSTATUS ff_raw_status (BYTE pdrv)
return status;
}
-DRESULT ff_raw_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count)
+static DRESULT ff_raw_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count)
{
ESP_LOGV(TAG, "ff_raw_read - pdrv=%i, sector=%i, count=%in", (unsigned int)pdrv, (unsigned int)sector, (unsigned int)count);
const esp_partition_t* part = s_ff_raw_handles[pdrv];
@@ -84,16 +84,16 @@ DRESULT ff_raw_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count)
}
-DRESULT ff_raw_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count)
+static DRESULT ff_raw_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count)
{
return RES_WRPRT;
}
-DRESULT ff_raw_ioctl (BYTE pdrv, BYTE cmd, void *buff)
+static DRESULT ff_raw_ioctl (BYTE pdrv, BYTE cmd, void *buff)
{
- const esp_partition_t* part = s_ff_raw_handles[pdrv];
ESP_LOGV(TAG, "ff_raw_ioctl: cmd=%in", cmd);
- assert(part);
+ assert(s_ff_raw_handles[pdrv]);
+
switch (cmd) {
case CTRL_SYNC:
return RES_OK;
diff --git a/lib/fatfs/diskio/diskio_sdmmc.c b/lib/fatfs/diskio/diskio_sdmmc.c
index 3e61eb00..689a07bc 100644
--- a/lib/fatfs/diskio/diskio_sdmmc.c
+++ b/lib/fatfs/diskio/diskio_sdmmc.c
@@ -1,5 +1,5 @@
/*
- * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+ * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -34,12 +34,12 @@ static DSTATUS ff_sdmmc_card_available(BYTE pdrv)
* fails. This error value is checked throughout the FATFS code.
* Both functions return 0 on success.
*/
-DSTATUS ff_sdmmc_initialize (BYTE pdrv)
+static DSTATUS ff_sdmmc_initialize (BYTE pdrv)
{
return ff_sdmmc_card_available(pdrv);
}
-DSTATUS ff_sdmmc_status(BYTE pdrv)
+static DSTATUS ff_sdmmc_status(BYTE pdrv)
{
if (s_disk_status_check_en[pdrv]) {
return ff_sdmmc_card_available(pdrv);
@@ -47,32 +47,32 @@ DSTATUS ff_sdmmc_status(BYTE pdrv)
return 0;
}
-DRESULT ff_sdmmc_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count)
+static DRESULT ff_sdmmc_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count)
{
sdmmc_card_t* card = s_cards[pdrv];
assert(card);
esp_err_t err = sdmmc_read_sectors(card, buff, sector, count);
if (unlikely(err != ESP_OK)) {
- ESP_LOGE(TAG, "sdmmc_read_blocks failed (%d)", err);
+ ESP_LOGE(TAG, "sdmmc_read_blocks failed (0x%x)", err);
return RES_ERROR;
}
return RES_OK;
}
-DRESULT ff_sdmmc_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count)
+static DRESULT ff_sdmmc_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count)
{
sdmmc_card_t* card = s_cards[pdrv];
assert(card);
esp_err_t err = sdmmc_write_sectors(card, buff, sector, count);
if (unlikely(err != ESP_OK)) {
- ESP_LOGE(TAG, "sdmmc_write_blocks failed (%d)", err);
+ ESP_LOGE(TAG, "sdmmc_write_blocks failed (0x%x)", err);
return RES_ERROR;
}
return RES_OK;
}
#if FF_USE_TRIM
-DRESULT ff_sdmmc_trim (BYTE pdrv, DWORD start_sector, DWORD sector_count)
+static DRESULT ff_sdmmc_trim (BYTE pdrv, DWORD start_sector, DWORD sector_count)
{
sdmmc_card_t* card = s_cards[pdrv];
assert(card);
@@ -88,7 +88,7 @@ DRESULT ff_sdmmc_trim (BYTE pdrv, DWORD start_sector, DWORD sector_count)
}
#endif //FF_USE_TRIM
-DRESULT ff_sdmmc_ioctl (BYTE pdrv, BYTE cmd, void* buff)
+static DRESULT ff_sdmmc_ioctl (BYTE pdrv, BYTE cmd, void* buff)
{
sdmmc_card_t* card = s_cards[pdrv];
assert(card);
diff --git a/lib/fatfs/diskio/diskio_sdmmc.h b/lib/fatfs/diskio/diskio_sdmmc.h
index 2ba4c2d9..d6c881ae 100644
--- a/lib/fatfs/diskio/diskio_sdmmc.h
+++ b/lib/fatfs/diskio/diskio_sdmmc.h
@@ -7,7 +7,7 @@
#pragma once
#include "sdmmc_cmd.h"
-#include "driver/sdmmc_defs.h"
+#include "sd_protocol_defs.h"
#ifdef __cplusplus
extern "C" {
diff --git a/lib/fatfs/diskio/diskio_wl.c b/lib/fatfs/diskio/diskio_wl.c
index 8e20290e..4852ca7a 100644
--- a/lib/fatfs/diskio/diskio_wl.c
+++ b/lib/fatfs/diskio/diskio_wl.c
@@ -1,5 +1,5 @@
/*
- * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
+ * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -19,52 +19,52 @@ wl_handle_t ff_wl_handles[FF_VOLUMES] = {
[0 ... FF_VOLUMES - 1] = WL_INVALID_HANDLE
};
-DSTATUS ff_wl_initialize (BYTE pdrv)
+static DSTATUS ff_wl_initialize (BYTE pdrv)
{
return 0;
}
-DSTATUS ff_wl_status (BYTE pdrv)
+static DSTATUS ff_wl_status (BYTE pdrv)
{
return 0;
}
-DRESULT ff_wl_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count)
+static DRESULT ff_wl_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count)
{
ESP_LOGV(TAG, "ff_wl_read - pdrv=%i, sector=%i, count=%i", (unsigned int)pdrv, (unsigned int)sector, (unsigned int)count);
wl_handle_t wl_handle = ff_wl_handles[pdrv];
- assert(wl_handle + 1);
+ assert(wl_handle != WL_INVALID_HANDLE);
esp_err_t err = wl_read(wl_handle, sector * wl_sector_size(wl_handle), buff, count * wl_sector_size(wl_handle));
if (unlikely(err != ESP_OK)) {
- ESP_LOGE(TAG, "wl_read failed (%d)", err);
+ ESP_LOGE(TAG, "wl_read failed (0x%x)", err);
return RES_ERROR;
}
return RES_OK;
}
-DRESULT ff_wl_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count)
+static DRESULT ff_wl_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count)
{
ESP_LOGV(TAG, "ff_wl_write - pdrv=%i, sector=%i, count=%i", (unsigned int)pdrv, (unsigned int)sector, (unsigned int)count);
wl_handle_t wl_handle = ff_wl_handles[pdrv];
- assert(wl_handle + 1);
+ assert(wl_handle != WL_INVALID_HANDLE);
esp_err_t err = wl_erase_range(wl_handle, sector * wl_sector_size(wl_handle), count * wl_sector_size(wl_handle));
if (unlikely(err != ESP_OK)) {
- ESP_LOGE(TAG, "wl_erase_range failed (%d)", err);
+ ESP_LOGE(TAG, "wl_erase_range failed (0x%x)", err);
return RES_ERROR;
}
err = wl_write(wl_handle, sector * wl_sector_size(wl_handle), buff, count * wl_sector_size(wl_handle));
if (unlikely(err != ESP_OK)) {
- ESP_LOGE(TAG, "wl_write failed (%d)", err);
+ ESP_LOGE(TAG, "wl_write failed (0x%x)", err);
return RES_ERROR;
}
return RES_OK;
}
-DRESULT ff_wl_ioctl (BYTE pdrv, BYTE cmd, void *buff)
+static DRESULT ff_wl_ioctl (BYTE pdrv, BYTE cmd, void *buff)
{
wl_handle_t wl_handle = ff_wl_handles[pdrv];
ESP_LOGV(TAG, "ff_wl_ioctl: cmd=%i", cmd);
- assert(wl_handle + 1);
+ assert(wl_handle != WL_INVALID_HANDLE);
switch (cmd) {
case CTRL_SYNC:
return RES_OK;
diff --git a/lib/fatfs/fatfs_utils/boot_sector.py b/lib/fatfs/fatfs_utils/boot_sector.py
index 615dd065..2809c033 100644
--- a/lib/fatfs/fatfs_utils/boot_sector.py
+++ b/lib/fatfs/fatfs_utils/boot_sector.py
@@ -1,9 +1,9 @@
-# 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 inspect import getmembers, isroutine
from typing import Optional
-from construct import Const, Int8ul, Int16ul, Int32ul, PaddedString, Struct, core
+from construct import Bytes, Const, Int8ul, Int16ul, Int32ul, PaddedString, Padding, Struct, core
from .exceptions import InconsistentFATAttributes, NotInitialized
from .fatfs_state import BootSectorState
@@ -29,8 +29,7 @@ class BootSector:
BOOT_HEADER_SIZE = 512
BOOT_SECTOR_HEADER = Struct(
- # this value reflects BS_jmpBoot used for ESP32 boot sector (any other accepted)
- 'BS_jmpBoot' / Const(b'\xeb\xfe\x90'),
+ 'BS_jmpBoot' / Bytes(3),
'BS_OEMName' / PaddedString(MAX_OEM_NAME_SIZE, SHORT_NAMES_ENCODING),
'BPB_BytsPerSec' / Int16ul,
'BPB_SecPerClus' / Int8ul,
@@ -45,12 +44,12 @@ class BootSector:
'BPB_HiddSec' / Int32ul,
'BPB_TotSec32' / Int32ul, # zero if the FAT type is 12/16, otherwise number of sectors
'BS_DrvNum' / Const(b'\x80'),
- 'BS_Reserved1' / Const(EMPTY_BYTE),
+ 'BS_Reserved1' / Padding(1),
'BS_BootSig' / Const(b'\x29'),
'BS_VolID' / Int32ul,
'BS_VolLab' / PaddedString(MAX_VOL_LAB_SIZE, SHORT_NAMES_ENCODING),
'BS_FilSysType' / PaddedString(MAX_FS_TYPE_SIZE, SHORT_NAMES_ENCODING),
- 'BS_EMPTY' / Const(448 * EMPTY_BYTE),
+ 'BS_EMPTY' / Padding(448),
'Signature_word' / Const(FATDefaults.SIGNATURE_WORD)
)
assert BOOT_SECTOR_HEADER.sizeof() == BOOT_HEADER_SIZE
@@ -65,15 +64,17 @@ 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_OEMName=pad_string(boot_sector_state.oem_name, size=BootSector.MAX_OEM_NAME_SIZE),
+ dict(BS_jmpBoot=(b'\xeb\xfe\x90'),
+ BS_OEMName=pad_string(boot_sector_state.oem_name, size=BootSector.MAX_OEM_NAME_SIZE),
BPB_BytsPerSec=boot_sector_state.sector_size,
BPB_SecPerClus=boot_sector_state.sectors_per_cluster,
BPB_RsvdSecCnt=boot_sector_state.reserved_sectors_cnt,
@@ -91,8 +92,7 @@ class BootSector:
BS_VolLab=pad_string(boot_sector_state.volume_label,
size=BootSector.MAX_VOL_LAB_SIZE),
BS_FilSysType=pad_string(boot_sector_state.file_sys_type,
- size=BootSector.MAX_FS_TYPE_SIZE)
- )
+ size=BootSector.MAX_FS_TYPE_SIZE))
) + pad_header + fat_tables_content + root_dir_content + data_content
)
diff --git a/lib/fatfs/fatfs_utils/fatfs_state.py b/lib/fatfs/fatfs_utils/fatfs_state.py
index 22af7bfb..4ba57132 100644
--- a/lib/fatfs/fatfs_utils/fatfs_state.py
+++ b/lib/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/lib/fatfs/fatfs_utils/utils.py b/lib/fatfs/fatfs_utils/utils.py
index d2180c76..662586a5 100644
--- a/lib/fatfs/fatfs_utils/utils.py
+++ b/lib/fatfs/fatfs_utils/utils.py
@@ -1,15 +1,18 @@
-# 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
import binascii
import os
import re
import uuid
from datetime import datetime
-from typing import List, Optional, Tuple
+from typing import List
+from typing import Optional
+from typing import Tuple
-from construct import BitsInteger, BitStruct, Int16ul
+from construct import BitsInteger
+from construct import BitStruct
+from construct import Int16ul
# the regex pattern defines symbols that are allowed by long file names but not by short file names
INVALID_SFN_CHARS_PATTERN = re.compile(r'[.+,;=\[\]]')
@@ -68,8 +71,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 +206,19 @@ 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.')
+ parser.add_argument('--wl_mode',
+ default=None,
+ type=str,
+ choices=['safe', 'perf'],
+ help='Wear levelling mode to use. Safe or performance. Only for sector size of 512')
args = parser.parse_args()
if args.fat_type == 0:
@@ -215,6 +228,9 @@ def get_args_for_partition_generator(desc: str, wl: bool) -> argparse.Namespace:
args.partition_size = int(str(args.partition_size), 0)
if not os.path.isdir(args.input_directory):
raise NotADirectoryError(f'The target directory `{args.input_directory}` does not exist!')
+ if args.wl_mode is not None:
+ if args.sector_size != 512:
+ raise ValueError('Wear levelling mode can be set only for sector size 512')
return args
@@ -276,7 +292,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/lib/fatfs/fatfsgen.py b/lib/fatfs/fatfsgen.py
index 199916ef..30d274f5 100755
--- a/lib/fatfs/fatfsgen.py
+++ b/lib/fatfs/fatfsgen.py
@@ -1,10 +1,11 @@
#!/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
from datetime import datetime
-from typing import Any, List, Optional
+from typing import Any
+from typing import List
+from typing import Optional
from fatfs_utils.boot_sector import BootSector
from fatfs_utils.exceptions import NoFreeClusterException
@@ -12,10 +13,24 @@ from fatfs_utils.fat import FAT
from fatfs_utils.fatfs_state import FATFSState
from fatfs_utils.fs_object import Directory
from fatfs_utils.long_filename_utils import get_required_lfn_entries_count
-from fatfs_utils.utils import (BYTES_PER_DIRECTORY_ENTRY, FATFS_INCEPTION, FATFS_MIN_ALLOC_UNIT,
- RESERVED_CLUSTERS_COUNT, FATDefaults, get_args_for_partition_generator,
- get_fat_sectors_count, get_non_data_sectors_cnt, read_filesystem,
- required_clusters_count)
+from fatfs_utils.utils import BYTES_PER_DIRECTORY_ENTRY
+from fatfs_utils.utils import FATDefaults
+from fatfs_utils.utils import FATFS_INCEPTION
+from fatfs_utils.utils import FATFS_MIN_ALLOC_UNIT
+from fatfs_utils.utils import get_args_for_partition_generator
+from fatfs_utils.utils import get_fat_sectors_count
+from fatfs_utils.utils import get_non_data_sectors_cnt
+from fatfs_utils.utils import read_filesystem
+from fatfs_utils.utils import required_clusters_count
+from fatfs_utils.utils import RESERVED_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:
@@ -40,14 +55,15 @@ class FATFS:
volume_label: str = FATDefaults.VOLUME_LABEL,
file_sys_type: str = FATDefaults.FILE_SYS_TYPE,
root_entry_count: int = FATDefaults.ROOT_ENTRIES_COUNT,
- explicit_fat_type: int = None,
+ explicit_fat_type: Optional[int] = None,
media_type: int = FATDefaults.MEDIA_TYPE) -> None:
# root directory bytes should be aligned by sector size
- assert (root_entry_count * BYTES_PER_DIRECTORY_ENTRY) % sector_size == 0
+ assert (int(root_entry_count) * BYTES_PER_DIRECTORY_ENTRY) % sector_size == 0
# number of bytes in the root dir must be even multiple of BPB_BytsPerSec
- assert ((root_entry_count * BYTES_PER_DIRECTORY_ENTRY) // sector_size) % 2 == 0
+ if (int(root_entry_count) > 128):
+ assert ((int(root_entry_count) * BYTES_PER_DIRECTORY_ENTRY) // sector_size) % 2 == 0
- root_dir_sectors_cnt: int = (root_entry_count * BYTES_PER_DIRECTORY_ENTRY) // sector_size
+ root_dir_sectors_cnt: int = (int(root_entry_count) * BYTES_PER_DIRECTORY_ENTRY) // sector_size
self.state: FATFSState = FATFSState(sector_size=sector_size,
explicit_fat_type=explicit_fat_type,
@@ -79,6 +95,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 +119,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 +144,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 +156,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 +258,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/lib/fatfs/fatfsparse.py b/lib/fatfs/fatfsparse.py
index de50f2fe..13c9a056 100755
--- a/lib/fatfs/fatfsparse.py
+++ b/lib/fatfs/fatfsparse.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import argparse
import os
@@ -100,7 +100,7 @@ def remove_wear_levelling_if_exists(fs_: bytes) -> bytes:
boot_sector__.parse_boot_sector(fs_)
if boot_sector__.boot_sector_state.size == len(fs_):
return fs_
- except construct.core.ConstError:
+ except (UnicodeDecodeError, construct.core.StringError):
pass
plain_fs: bytes = remove_wl(fs_)
return plain_fs
@@ -124,6 +124,9 @@ if __name__ == '__main__':
default=None,
help="If detection doesn't work correctly, "
'you can force analyzer to or not to assume WL.')
+ argument_parser.add_argument('--verbose',
+ action='store_true',
+ help='Prints details about FAT image.')
args = argument_parser.parse_args()
@@ -157,6 +160,10 @@ if __name__ == '__main__':
boot_sector_ = BootSector()
boot_sector_.parse_boot_sector(fs)
+
+ if args.verbose:
+ print(str(boot_sector_))
+
fat = FAT(boot_sector_.boot_sector_state, init_=False)
boot_dir_start_ = boot_sector_.boot_sector_state.root_directory_start
diff --git a/lib/fatfs/host_test/.build-test-rules.yml b/lib/fatfs/host_test/.build-test-rules.yml
new file mode 100644
index 00000000..17714e9c
--- /dev/null
+++ b/lib/fatfs/host_test/.build-test-rules.yml
@@ -0,0 +1,7 @@
+components/fatfs/host_test:
+ enable:
+ - if: IDF_TARGET == "linux"
+ disable_test:
+ - if: IDF_TARGET == "esp32p4"
+ temporary: true
+ reason: test not pass, should be re-enable # TODO: IDF-8980
diff --git a/lib/fatfs/host_test/CMakeLists.txt b/lib/fatfs/host_test/CMakeLists.txt
index 48267da8..7bf861b1 100644
--- a/lib/fatfs/host_test/CMakeLists.txt
+++ b/lib/fatfs/host_test/CMakeLists.txt
@@ -2,9 +2,7 @@ cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)
-# Freertos is included via common components. However, CATCH isn't compatible with the FreeRTOS component yet, hence
-# using the FreeRTOS mock component.
-# target.
+# This test doesn't require FreeRTOS, uses a mock instead
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/freertos/")
project(fatfs_host_test)
diff --git a/lib/fatfs/host_test/main/CMakeLists.txt b/lib/fatfs/host_test/main/CMakeLists.txt
index ef8aeb43..046433c1 100644
--- a/lib/fatfs/host_test/main/CMakeLists.txt
+++ b/lib/fatfs/host_test/main/CMakeLists.txt
@@ -1,6 +1,8 @@
-idf_component_register(SRCS "main.cpp"
- "test_fatfs.cpp"
- INCLUDE_DIRS "$ENV{IDF_PATH}/tools/catch"
+idf_component_register(SRCS "test_fatfs.cpp"
REQUIRES fatfs
WHOLE_ARCHIVE
)
+
+# Currently 'main' for IDF_TARGET=linux is defined in freertos component.
+# Since we are using a freertos mock here, need to let Catch2 provide 'main'.
+target_link_libraries(${COMPONENT_LIB} PRIVATE Catch2WithMain)
diff --git a/lib/fatfs/host_test/main/idf_component.yml b/lib/fatfs/host_test/main/idf_component.yml
new file mode 100644
index 00000000..f7982136
--- /dev/null
+++ b/lib/fatfs/host_test/main/idf_component.yml
@@ -0,0 +1,2 @@
+dependencies:
+ espressif/catch2: "^3.4.0"
diff --git a/lib/fatfs/host_test/main/main.cpp b/lib/fatfs/host_test/main/main.cpp
deleted file mode 100644
index cd66dc30..00000000
--- a/lib/fatfs/host_test/main/main.cpp
+++ /dev/null
@@ -1,7 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
- *
- * SPDX-License-Identifier: Apache-2.0
- */
-#define CATCH_CONFIG_MAIN
-#include "catch.hpp"
diff --git a/lib/fatfs/host_test/main/test_fatfs.cpp b/lib/fatfs/host_test/main/test_fatfs.cpp
index 77af2e70..88aa16e1 100644
--- a/lib/fatfs/host_test/main/test_fatfs.cpp
+++ b/lib/fatfs/host_test/main/test_fatfs.cpp
@@ -1,5 +1,5 @@
/*
- * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+ * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -12,7 +12,7 @@
#include "diskio_impl.h"
#include "diskio_wl.h"
-#include "catch.hpp"
+#include <catch2/catch_test_macros.hpp>
TEST_CASE("Create volume, open file, write and read back data", "[fatfs]")
{
@@ -44,7 +44,10 @@ TEST_CASE("Create volume, open file, write and read back data", "[fatfs]")
fr_result = f_fdisk(pdrv, part_list, work_area);
REQUIRE(fr_result == FR_OK);
- const MKFS_PARM opt = {(BYTE)FM_ANY, 0, 0, 0, 0};
+
+ // For host tests, include FM_SFD flag when formatting partitions smaller than 128KB.
+ // if n_root field of MKFS_PARM is set to 128 => 1 root directory sec and if set to 0(default 512) => 4 root directory sectors.
+ const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), 0, 0, 128, 0};
fr_result = f_mkfs("", &opt, work_area, sizeof(work_area)); // Use default volume
// Mount the volume
@@ -56,7 +59,7 @@ TEST_CASE("Create volume, open file, write and read back data", "[fatfs]")
REQUIRE(fr_result == FR_OK);
// Generate data
- uint32_t data_size = 100000;
+ uint32_t data_size = 1000;
char *data = (char*) malloc(data_size);
char *read = (char*) malloc(data_size);
@@ -130,7 +133,7 @@ static void prepare_fatfs(const char* partition_label, const esp_partition_t** p
fr_result = f_fdisk(_pdrv, part_list, work_area);
REQUIRE(fr_result == FR_OK);
- const MKFS_PARM opt = {(BYTE)FM_ANY, 0, 0, 0, 0};
+ const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), 0, 0, 128, 0};
fr_result = f_mkfs(drv, &opt, work_area, sizeof(work_area)); // Use default volume
REQUIRE(fr_result == FR_OK);
}
@@ -141,7 +144,7 @@ static void prepare_fatfs(const char* partition_label, const esp_partition_t** p
* at the time of writing this - therefore there also is a device test_apps test in
* `components/fatfs/test_apps/flash_wl/main/test_fatfs_flash_wl.c` which tests our VFS FATFS SPIFLASH API.
*/
-TEST_CASE("Test mounting 2 volumes, writing data and formating the 2nd one, reading data", "[fatfs]")
+TEST_CASE("Test mounting 2 volumes, writing data and formatting the 2nd one, reading data", "[fatfs]")
{
FRESULT fr_result;
esp_err_t esp_result;
@@ -158,7 +161,6 @@ TEST_CASE("Test mounting 2 volumes, writing data and formating the 2nd one, read
FATFS fs1;
wl_handle_t wl_handle1 = WL_INVALID_HANDLE;
- size_t allocation_unit_size = CONFIG_WL_SECTOR_SIZE;
size_t data_size = 10;
@@ -223,7 +225,7 @@ TEST_CASE("Test mounting 2 volumes, writing data and formating the 2nd one, read
const size_t workbuf_size = 4096;
void *workbuf = ff_memalloc(workbuf_size);
REQUIRE(workbuf != NULL);
- const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), 0, 0, 0, CONFIG_WL_SECTOR_SIZE};
+ const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), 0, 0, 128, CONFIG_WL_SECTOR_SIZE};
fr_result = f_mkfs(drv1, &opt, workbuf, workbuf_size);
free(workbuf);
workbuf = NULL;
@@ -241,7 +243,7 @@ TEST_CASE("Test mounting 2 volumes, writing data and formating the 2nd one, read
fr_result = f_read(&file1, read1, data_size, &bw1);
REQUIRE(fr_result == FR_OK);
REQUIRE(bw1 != data_size);
- // Comapre data
+ // Compare data
printf("data1=%s, read1=%s\n", data1, read1);
REQUIRE(strncmp(data1, read1, data_size-1) != 0); // 987654321 should be ersead due to formatting
// Close file from file1
@@ -259,7 +261,7 @@ TEST_CASE("Test mounting 2 volumes, writing data and formating the 2nd one, read
fr_result = f_read(&file0, read0, data_size, &bw0);
REQUIRE(fr_result == FR_OK);
REQUIRE(bw0 == data_size);
- // Comapre data
+ // Compare data
printf("data0=%s, read0=%s\n", data0, read0);
REQUIRE(strncmp(data0, read0, data_size-1) == 0); // should match since the partition was not formatted
// Close file from file0
diff --git a/lib/fatfs/host_test/partition_table.csv b/lib/fatfs/host_test/partition_table.csv
index 30d2d909..777a3e67 100644
--- a/lib/fatfs/host_test/partition_table.csv
+++ b/lib/fatfs/host_test/partition_table.csv
@@ -3,5 +3,5 @@
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,
-storage, data, fat, , 1M,
-storage2, data, fat, , 1M,
+storage, data, fat, , 32k,
+storage2, data, fat, , 32k,
diff --git a/lib/fatfs/host_test/pytest_fatfs_linux.py b/lib/fatfs/host_test/pytest_fatfs_linux.py
index 7b12c361..e14e927a 100644
--- a/lib/fatfs/host_test/pytest_fatfs_linux.py
+++ b/lib/fatfs/host_test/pytest_fatfs_linux.py
@@ -1,10 +1,11 @@
-# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+# SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import pytest
from pytest_embedded import Dut
+from pytest_embedded_idf.utils import idf_parametrize
-@pytest.mark.linux
@pytest.mark.host_test
+@idf_parametrize('target', ['linux'], indirect=['target'])
def test_fatfs_linux(dut: Dut) -> None:
dut.expect_exact('All tests passed', timeout=120)
diff --git a/lib/fatfs/host_test/sdkconfig.defaults b/lib/fatfs/host_test/sdkconfig.defaults
index e0d9a692..53e7687b 100644
--- a/lib/fatfs/host_test/sdkconfig.defaults
+++ b/lib/fatfs/host_test/sdkconfig.defaults
@@ -6,7 +6,6 @@ CONFIG_LOG_DEFAULT_LEVEL=3
CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partition_table.csv"
-CONFIG_ESPTOOLPY_FLASHSIZE="8MB"
CONFIG_MMU_PAGE_SIZE=0X10000
CONFIG_ESP_PARTITION_ENABLE_STATS=y
CONFIG_FATFS_VOLUME_COUNT=3
diff --git a/lib/fatfs/port/freertos/ffsystem.c b/lib/fatfs/port/freertos/ffsystem.c
index 7ca7ecc5..37c31de3 100644
--- a/lib/fatfs/port/freertos/ffsystem.c
+++ b/lib/fatfs/port/freertos/ffsystem.c
@@ -58,7 +58,7 @@ static mtxid Mutex[FF_VOLUMES + 1]; /* Table of mutex ID */
#elif OS_TYPE == 2 /* uc/OS-II */
#include "includes.h"
-static OS_EVENT *Mutex[FF_VOLUMES + 1]; /* Table of mutex pinter */
+static OS_EVENT *Mutex[FF_VOLUMES + 1]; /* Table of mutex pointers */
#elif OS_TYPE == 3 /* FreeRTOS */
#include "freertos/FreeRTOS.h"
diff --git a/lib/fatfs/project_include.cmake b/lib/fatfs/project_include.cmake
index d945c0e9..aa0edf2a 100644
--- a/lib/fatfs/project_include.cmake
+++ b/lib/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/lib/fatfs/src/ff.c b/lib/fatfs/src/ff.c
index 52adc2ab..3c3f1ace 100644
--- a/lib/fatfs/src/ff.c
+++ b/lib/fatfs/src/ff.c
@@ -34,7 +34,6 @@
#error Wrong include file (ff.h).
#endif
-
/* Limits and boundaries */
#define MAX_DIR 0x200000 /* Max size of FAT directory */
#define MAX_DIR_EX 0x10000000 /* Max size of exFAT directory */
@@ -43,6 +42,10 @@
#define MAX_FAT32 0x0FFFFFF5 /* Max FAT32 clusters (not specified, practical limit) */
#define MAX_EXFAT 0x7FFFFFFD /* Max exFAT clusters (differs from specs, implementation limit) */
+#define MIN_FAT12_SEC_VOL 4 /* Min size of the FAT sector volume
+ 1 FAT, 1 root dir, 1 reserved, 1 data sector */
+#define MIN_FAT12_DATA_SEC 1 /* Min FAT data sectors */
+
/* Character code support macros */
#define IsUpper(c) ((c) >= 'A' && (c) <= 'Z')
@@ -1118,7 +1121,7 @@ static FRESULT sync_fs ( /* Returns FR_OK or FR_DISK_ERR */
if (res == FR_OK) {
if (fs->fs_type == FS_FAT32 && fs->fsi_flag == 1) { /* FAT32: Update FSInfo sector if needed */
/* Create FSInfo structure */
- memset(fs->win, 0, sizeof fs->win);
+ memset(fs->win, 0, SS(fs));
st_word(fs->win + BS_55AA, 0xAA55); /* Boot signature */
st_dword(fs->win + FSI_LeadSig, 0x41615252); /* Leading signature */
st_dword(fs->win + FSI_StrucSig, 0x61417272); /* Structure signature */
@@ -1670,7 +1673,7 @@ static FRESULT dir_clear ( /* Returns FR_OK or FR_DISK_ERR */
if (sync_window(fs) != FR_OK) return FR_DISK_ERR; /* Flush disk access window */
sect = clst2sect(fs, clst); /* Top of the cluster */
fs->winsect = sect; /* Set window to top of the cluster */
- memset(fs->win, 0, sizeof fs->win); /* Clear window buffer */
+ memset(fs->win, 0, SS(fs)); /* Clear window buffer */
#if FF_USE_LFN == 3 /* Quick table clear by using multi-secter write */
/* Allocate a temporary buffer */
for (szb = ((DWORD)fs->csize * SS(fs) >= MAX_MALLOC) ? MAX_MALLOC : fs->csize * SS(fs), ibuf = 0; szb > SS(fs) && (ibuf = ff_memalloc(szb)) == 0; szb /= 2) ;
@@ -3318,7 +3321,7 @@ static UINT check_fs ( /* 0:FAT/FAT32 VBR, 1:exFAT VBR, 2:Not FAT and valid BS,
&& ld_word(fs->win + BPB_RsvdSecCnt) != 0 /* Properness of reserved sectors (MNBZ) */
&& (UINT)fs->win[BPB_NumFATs] - 1 <= 1 /* Properness of FATs (1 or 2) */
&& ld_word(fs->win + BPB_RootEntCnt) != 0 /* Properness of root dir entries (MNBZ) */
- && (ld_word(fs->win + BPB_TotSec16) >= 128 || ld_dword(fs->win + BPB_TotSec32) >= 0x10000) /* Properness of volume sectors (>=128) */
+ && (ld_word(fs->win + BPB_TotSec16) >= MIN_FAT12_SEC_VOL || ld_dword(fs->win + BPB_TotSec32) >= 0x10000) /* Properness of volume sectors (>=MIN_FAT12_SEC_VOL) */
&& ld_word(fs->win + BPB_FATSz16) != 0) { /* Properness of FAT size (MNBZ) */
return 0; /* It can be presumed an FAT VBR */
}
@@ -3438,6 +3441,10 @@ static FRESULT mount_volume ( /* FR_OK(0): successful, !=0: an error occurred */
if (disk_ioctl(fs->pdrv, GET_SECTOR_SIZE, &SS(fs)) != RES_OK) return FR_DISK_ERR;
if (SS(fs) > FF_MAX_SS || SS(fs) < FF_MIN_SS || (SS(fs) & (SS(fs) - 1))) return FR_DISK_ERR;
#endif
+#if FF_USE_DYN_BUFFER
+ fs->win = ff_memalloc(SS(fs)); /* Allocate memory for sector buffer */
+ if (!fs->win) return FR_NOT_ENOUGH_CORE;
+#endif
/* Find an FAT volume on the hosting drive */
fmt = find_volume(fs, LD2PT(vol));
@@ -3681,6 +3688,10 @@ FRESULT f_mount (
#if FF_FS_REENTRANT /* Discard mutex of the current volume */
ff_mutex_delete(vol);
#endif
+#if FF_USE_DYN_BUFFER
+ if (cfs->fs_type) /* Check if the buffer was ever allocated */
+ ff_memfree(cfs->win); /* Deallocate buffer allocated for the filesystem object */
+#endif
cfs->fs_type = 0; /* Invalidate the filesystem object to be unregistered */
}
@@ -3868,7 +3879,19 @@ FRESULT f_open (
fp->fptr = 0; /* Set file pointer top of the file */
#if !FF_FS_READONLY
#if !FF_FS_TINY
- memset(fp->buf, 0, sizeof fp->buf); /* Clear sector buffer */
+#if FF_USE_DYN_BUFFER
+ fp->buf = NULL;
+ if (res == FR_OK) {
+ fp->buf = ff_memalloc(SS(fs));
+ if (!fp->buf) {
+ res = FR_NOT_ENOUGH_CORE; /* Not enough memory */
+ goto fail;
+ }
+ memset(fp->buf, 0, SS(fs)); /* Clear sector buffer */
+ }
+#else
+ memset(fp->buf, 0, SS(fs)); /* Clear sector buffer */
+#endif
#endif
if ((mode & FA_SEEKEND) && fp->obj.objsize > 0) { /* Seek to end of file if FA_OPEN_APPEND is specified */
fp->fptr = fp->obj.objsize; /* Offset to seek */
@@ -3901,7 +3924,19 @@ FRESULT f_open (
FREE_NAMBUF();
}
- if (res != FR_OK) fp->obj.fs = 0; /* Invalidate file object on error */
+ if (res != FR_OK) {
+ fp->obj.fs = 0; /* Invalidate file object on error */
+#if !FF_FS_TINY && FF_USE_DYN_BUFFER
+ if (fp->buf) {
+ ff_memfree(fp->buf);
+ fp->buf = NULL;
+ }
+#endif
+ }
+
+#if FF_USE_DYN_BUFFER
+fail:
+#endif
LEAVE_FF(fs, res);
}
@@ -4235,6 +4270,10 @@ FRESULT f_close (
#else
fp->obj.fs = 0; /* Invalidate file object */
#endif
+#if !FF_FS_TINY && FF_USE_DYN_BUFFER
+ ff_memfree(fp->buf);
+ fp->buf = NULL;
+#endif
#if FF_FS_REENTRANT
unlock_volume(fs, FR_OK); /* Unlock volume */
#endif
@@ -5998,7 +6037,11 @@ FRESULT f_mkfs (
}
}
}
- if (sz_vol < 128) LEAVE_MKFS(FR_MKFS_ABORTED); /* Check if volume size is >=128s */
+ if (n_fat == 1) {
+ if (sz_vol < MIN_FAT12_SEC_VOL) LEAVE_MKFS(FR_MKFS_ABORTED); /* Check if volume size is >= MIN_FAT12_SEC_VOLs */
+ } else {
+ if (sz_vol < (MIN_FAT12_SEC_VOL + 1)) LEAVE_MKFS(FR_MKFS_ABORTED); /* Check if volume size is >= (MIN_FAT12_SEC_VOL+1)s */
+ }
/* Now start to create an FAT volume at b_vol and sz_vol */
@@ -6229,7 +6272,7 @@ FRESULT f_mkfs (
}
/* Determine number of clusters and final check of validity of the FAT sub-type */
- if (sz_vol < b_data + pau * 16 - b_vol) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too small volume? */
+ if (sz_vol < b_data + pau * MIN_FAT12_DATA_SEC - b_vol) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too small volume? */
n_clst = ((DWORD)sz_vol - sz_rsv - sz_fat * n_fat - sz_dir) / pau;
if (fsty == FS_FAT32) {
if (n_clst <= MAX_FAT16) { /* Too few clusters for FAT32? */
diff --git a/lib/fatfs/src/ff.h b/lib/fatfs/src/ff.h
index 19854705..f4493e03 100644
--- a/lib/fatfs/src/ff.h
+++ b/lib/fatfs/src/ff.h
@@ -116,7 +116,7 @@ typedef struct {
BYTE pd; /* Physical drive number */
BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */
} PARTITION;
-extern const PARTITION VolToPart[]; /* Volume - Partition mapping table */
+extern PARTITION VolToPart[]; /* Volume - Partition mapping table */
#endif
#if FF_STR_VOLUME_ID
@@ -170,7 +170,11 @@ typedef struct {
LBA_t bitbase; /* Allocation bitmap base sector */
#endif
LBA_t winsect; /* Current sector appearing in the win[] */
+#if FF_USE_DYN_BUFFER
+ BYTE* win; /* Disk access window for Directory, FAT (and file data at tiny cfg) */
+#else
BYTE win[FF_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */
+#endif
} FATFS;
@@ -215,8 +219,12 @@ typedef struct {
DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */
#endif
#if !FF_FS_TINY
+#if FF_USE_DYN_BUFFER
+ BYTE* buf; /* File private data read/write window */
+#else
BYTE buf[FF_MAX_SS]; /* File private data read/write window */
#endif
+#endif
} FIL;
@@ -289,7 +297,7 @@ typedef enum {
FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */
FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */
FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */
- FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */
+ FR_NOT_ENOUGH_CORE, /* (17) Buffer could not be allocated */
FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > FF_FS_LOCK */
FR_INVALID_PARAMETER /* (19) Given parameter is invalid */
} FRESULT;
diff --git a/lib/fatfs/src/ffconf.h b/lib/fatfs/src/ffconf.h
index a5a4ae17..d378ee6d 100644
--- a/lib/fatfs/src/ffconf.h
+++ b/lib/fatfs/src/ffconf.h
@@ -1,3 +1,6 @@
+#ifndef _FFCONF_DEFINED
+#define _FFCONF_DEFINED
+
#include "sdkconfig.h"
/*---------------------------------------------------------------------------/
@@ -40,7 +43,7 @@
/* This option switches fast seek function. (0:Disable or 1:Enable) */
-#define FF_USE_EXPAND 0
+#define FF_USE_EXPAND 1
/* This option switches f_expand function. (0:Disable or 1:Enable) */
@@ -57,11 +60,36 @@
#define FF_USE_FORWARD 1
/* This option switches f_forward() function. (0:Disable or 1:Enable) */
+#if defined(CONFIG_FATFS_USE_STRFUNC_WITHOUT_CRLF_CONV)
+#define FF_USE_STRFUNC 1
+#elif defined(CONFIG_FATFS_USE_STRFUNC_WITH_CRLF_CONV)
+#define FF_USE_STRFUNC 2
+#else /* CONFIG_FATFS_USE_STRFUNC_NONE */
+#define FF_USE_STRFUNC 0
+#endif
-#define FF_USE_STRFUNC 1
+#ifdef CONFIG_FATFS_PRINT_LLI
+#define FF_PRINT_LLI 1
+#else
#define FF_PRINT_LLI 0
+#endif
+
+#ifdef CONFIG_FATFS_PRINT_FLOAT
+#define FF_PRINT_FLOAT 1
+#else
#define FF_PRINT_FLOAT 0
+#endif
+
+#if defined(CONFIG_FATFS_STRF_ENCODE_ANSI)
+#define FF_STRF_ENCODE 0
+#elif defined(CONFIG_FATFS_STRF_ENCODE_UTF16LE)
+#define FF_STRF_ENCODE 1
+#elif defined(CONFIG_FATFS_STRF_ENCODE_UTF16BE)
+#define FF_STRF_ENCODE 2
+#else /* CONFIG_FATFS_STRF_ENCODE_UTF8 */
#define FF_STRF_ENCODE 3
+#endif
+
/* FF_USE_STRFUNC switches string functions, f_gets(), f_putc(), f_puts() and
/ f_printf().
/
@@ -271,7 +299,7 @@
/ These options have no effect in read-only configuration (FF_FS_READONLY = 1). */
-#define FF_FS_NOFSINFO 0
+#define FF_FS_NOFSINFO (CONFIG_FATFS_DONT_TRUST_FREE_CLUSTER_CNT << 0 | CONFIG_FATFS_DONT_TRUST_LAST_ALLOC << 1)
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
/ option, and f_getfree() function at the first time after volume mount will force
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
@@ -311,6 +339,13 @@
/ The FF_FS_TIMEOUT defines timeout period in unit of O/S time tick.
*/
+#define FF_USE_DYN_BUFFER CONFIG_FATFS_USE_DYN_BUFFERS
+/* The option FF_USE_DYN_BUFFER controls source of size used for buffers in the FS and FIL objects.
+/
+/ 0: Disable dynamic buffer size and use static size buffers defined by FF_MAX_SS.
+/ 1: Enable dynamic buffer size and use ff_memmalloc() to allocate buffers.
+*/
+
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
@@ -330,3 +365,5 @@ void ff_memfree(void*);
#define disk_read ff_disk_read
#define disk_write ff_disk_write
#define disk_ioctl ff_disk_ioctl
+
+#endif /* _FFCONF_DEFINED */
diff --git a/lib/fatfs/test_apps/.build-test-rules.yml b/lib/fatfs/test_apps/.build-test-rules.yml
index d8277804..86cfac0d 100644
--- a/lib/fatfs/test_apps/.build-test-rules.yml
+++ b/lib/fatfs/test_apps/.build-test-rules.yml
@@ -1,11 +1,47 @@
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
-components/fatfs/test_apps/sdcard:
+components/fatfs/test_apps/dyn_buffers:
disable_test:
- - if: IDF_TARGET in ["esp32s3", "esp32c2", "esp32c6", "esp32h2"]
- temporary: true
- reason: No sdspi runners for these targets
+ - if: IDF_TARGET != "esp32"
+ reason: only one target required
+
+ depends_components:
+ - fatfs
+
+components/fatfs/test_apps/flash_ro:
+ disable_test:
+ - if: IDF_TARGET not in ["esp32", "esp32c3"]
+ reason: only one target per arch needed
+
+ depends_components:
+ - esp_partition
+ - spi_flash
+ - fatfs
+ - vfs
+
+components/fatfs/test_apps/flash_wl:
+ disable_test:
+ - if: IDF_TARGET not in ["esp32", "esp32c3", "linux"]
+ reason: only one target per arch needed
+ depends_components:
+ - esp_partition
+ - spi_flash
+ - fatfs
+ - vfs
+ - wear_leveling
+
+components/fatfs/test_apps/sdcard:
disable:
- - if: IDF_TARGET == "esp32p4"
+ - if: IDF_TARGET in ["esp32h21", "esp32h4"]
+ temporary: true
+ reason: not supported yet # TODO: [esp32h21] IDF-11593 [ESP32H4] IDF-12372
+ disable_test:
+ - if: IDF_TARGET not in ["esp32", "esp32c3"]
temporary: true
- reason: target esp32p4 is not supported yet # TODO: IDF-7501
+ reason: lack of runners
+ depends_components:
+ - esp_driver_sdmmc
+ - esp_driver_spi
+ - sdmmc
+ - fatfs
+ - vfs
diff --git a/lib/fatfs/test_apps/README.md b/lib/fatfs/test_apps/README.md
index 2140e8c4..78a9dcd9 100644
--- a/lib/fatfs/test_apps/README.md
+++ b/lib/fatfs/test_apps/README.md
@@ -1,3 +1,6 @@
+| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
+| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- | -------- |
+
# fatfs component target tests
This directory contains tests for `fatfs` component which are run on chip targets.
@@ -9,6 +12,7 @@ Fatfs tests can be executed with different `diskio` backends: `diskio_sdmmc` (SD
- [sdcard](sdcard/) — runs fatfs tests with an SD card over SDMMC or SDSPI interface
- [flash_wl](flash_wl/) - runs fatfs test in a wear_levelling partition in SPI flash
- [flash_ro](flash_ro/) - runs fatfs test in a read-only (no wear levelling) partition in SPI flash
+- [dyn_buffers](dyn_buffers/) - check if enabling dynamic buffers in FATFS has an effect
These test apps define:
- test functions
diff --git a/lib/fatfs/test_apps/dyn_buffers/CMakeLists.txt b/lib/fatfs/test_apps/dyn_buffers/CMakeLists.txt
new file mode 100644
index 00000000..4e6f01d2
--- /dev/null
+++ b/lib/fatfs/test_apps/dyn_buffers/CMakeLists.txt
@@ -0,0 +1,7 @@
+# The following five lines of boilerplate have to be in your project's
+# CMakeLists in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.16)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+idf_build_set_property(MINIMAL_BUILD ON)
+project(dyn_buffers)
diff --git a/lib/fatfs/test_apps/dyn_buffers/README.md b/lib/fatfs/test_apps/dyn_buffers/README.md
new file mode 100644
index 00000000..261aeee2
--- /dev/null
+++ b/lib/fatfs/test_apps/dyn_buffers/README.md
@@ -0,0 +1,8 @@
+| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
+| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- |
+
+This test app checks if `CONFIG_FATFS_USE_DYN_BUFFERS` has any effect.
+
+These tests should be possible to run on any ESP development board, not extra hardware is necessary.
+
+See [../README.md](../README.md) for more information about FATFS test apps.
diff --git a/lib/fatfs/test_apps/dyn_buffers/main/CMakeLists.txt b/lib/fatfs/test_apps/dyn_buffers/main/CMakeLists.txt
new file mode 100644
index 00000000..c3cd1aa7
--- /dev/null
+++ b/lib/fatfs/test_apps/dyn_buffers/main/CMakeLists.txt
@@ -0,0 +1,3 @@
+idf_component_register(SRCS "test_fatfs_dyn_buffers.c"
+ INCLUDE_DIRS "."
+ REQUIRES wear_levelling fatfs vfs)
diff --git a/lib/fatfs/test_apps/dyn_buffers/main/test_fatfs_dyn_buffers.c b/lib/fatfs/test_apps/dyn_buffers/main/test_fatfs_dyn_buffers.c
new file mode 100644
index 00000000..c945d97b
--- /dev/null
+++ b/lib/fatfs/test_apps/dyn_buffers/main/test_fatfs_dyn_buffers.c
@@ -0,0 +1,101 @@
+/*
+ * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Unlicense OR CC0-1.0
+ */
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdint.h>
+#include "wear_levelling.h"
+#include "esp_partition.h"
+#include "esp_vfs.h"
+#include "esp_vfs_fat.h"
+#include "esp_heap_caps.h"
+#include "sdkconfig.h"
+#include "ff.h"
+#include "esp_debug_helpers.h"
+
+static const char* TAG = "Test dynamic buffers";
+
+static volatile bool g_alloc_count_enable = false;
+static volatile int g_buffer_alloc_count = 0;
+
+static esp_vfs_fat_mount_config_t g_mount_config = {
+ .format_if_mount_failed = true,
+ .max_files = 5,
+};
+
+void esp_heap_trace_alloc_hook(void* ptr, size_t size, uint32_t caps)
+{
+ (void) ptr;
+ (void) caps;
+
+ if (!g_alloc_count_enable) {
+ return;
+ }
+
+ // This will work only on SPI flash
+ // Different flash types might break this check
+ if (size == FF_MAX_SS) {
+ g_buffer_alloc_count++;
+ }
+}
+
+void app_main(void)
+{
+ esp_err_t err = ESP_OK;
+
+ wl_handle_t wl_handle;
+
+ err = esp_vfs_fat_spiflash_format_cfg_rw_wl("/spiflash", NULL, &g_mount_config);
+
+ ESP_LOGI(TAG, "Mounting FATFS");
+
+ g_mount_config.format_if_mount_failed = false,
+
+ g_alloc_count_enable = true;
+
+ err = esp_vfs_fat_spiflash_mount_rw_wl("/spiflash", NULL, &g_mount_config, &wl_handle);
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "FATFS mount failed with error: %d", err);
+ return;
+ }
+
+ ESP_LOGI(TAG, "Mounted");
+
+ int fd = open("/spiflash/test.txt", O_RDWR|O_CREAT);
+ if (fd < 0) {
+ ESP_LOGE(TAG, "Failed opening file");
+ }
+
+ close(fd);
+
+ g_alloc_count_enable = false;
+
+ ESP_LOGI(TAG, "Unmounting FATFS");
+
+ esp_vfs_fat_spiflash_unmount_rw_wl("/spiflash", wl_handle);
+
+ ESP_LOGI(TAG, "Unmounted");
+
+ ESP_LOGI(TAG, "Allocs called:\n\tBuffer: %d"
+ , g_buffer_alloc_count);
+
+#if CONFIG_FATFS_USE_DYN_BUFFERS
+
+ if (g_buffer_alloc_count != 2) {
+ ESP_LOGE(TAG, "FATFS buffer should have been allocated once for each context (file and fatfs)");
+ return;
+ }
+#else
+
+ if (g_buffer_alloc_count != 0) {
+ ESP_LOGE(TAG, "FATFS buffer should not have been allocated");
+ return;
+ }
+
+#endif
+
+ ESP_LOGI(TAG, "Done");
+}
diff --git a/lib/fatfs/test_apps/dyn_buffers/partitions.csv b/lib/fatfs/test_apps/dyn_buffers/partitions.csv
new file mode 100644
index 00000000..d68a9de0
--- /dev/null
+++ b/lib/fatfs/test_apps/dyn_buffers/partitions.csv
@@ -0,0 +1,5 @@
+# Name, Type, SubType, Offset, Size, Flags
+factory, app, factory, 0x10000, 768k,
+storage, data, fat, , 528k,
+storage2, data, fat, , 528k,
+storage1, data, fat, , 32k,
diff --git a/lib/fatfs/test_apps/dyn_buffers/pytest_fatfs_dyn_buffers.py b/lib/fatfs/test_apps/dyn_buffers/pytest_fatfs_dyn_buffers.py
new file mode 100644
index 00000000..a60ac87e
--- /dev/null
+++ b/lib/fatfs/test_apps/dyn_buffers/pytest_fatfs_dyn_buffers.py
@@ -0,0 +1,22 @@
+# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
+# SPDX-License-Identifier: CC0-1.0
+import pytest
+from pytest_embedded import Dut
+from pytest_embedded_idf.utils import idf_parametrize
+
+
+@pytest.mark.generic
+@pytest.mark.parametrize(
+ 'config',
+ [
+ 'dyn_buffers',
+ 'no_dyn_buffers',
+ ],
+)
+@idf_parametrize('target', ['esp32'], indirect=['target'])
+def test_fatfs_flash_dyn_buffers(config: str, dut: Dut) -> None:
+ dut.expect('Mounting FATFS')
+ dut.expect('Mounted')
+ dut.expect('Unmounting FATFS')
+ dut.expect('Unmounted')
+ dut.expect('Done')
diff --git a/lib/fatfs/test_apps/dyn_buffers/sdkconfig.ci.dyn_buffers b/lib/fatfs/test_apps/dyn_buffers/sdkconfig.ci.dyn_buffers
new file mode 100644
index 00000000..380dafbf
--- /dev/null
+++ b/lib/fatfs/test_apps/dyn_buffers/sdkconfig.ci.dyn_buffers
@@ -0,0 +1 @@
+CONFIG_FATFS_USE_DYN_BUFFERS=y
diff --git a/lib/fatfs/test_apps/dyn_buffers/sdkconfig.ci.no_dyn_buffers b/lib/fatfs/test_apps/dyn_buffers/sdkconfig.ci.no_dyn_buffers
new file mode 100644
index 00000000..bc9b68f3
--- /dev/null
+++ b/lib/fatfs/test_apps/dyn_buffers/sdkconfig.ci.no_dyn_buffers
@@ -0,0 +1 @@
+CONFIG_FATFS_USE_DYN_BUFFERS=n
diff --git a/lib/fatfs/test_apps/dyn_buffers/sdkconfig.defaults b/lib/fatfs/test_apps/dyn_buffers/sdkconfig.defaults
new file mode 100644
index 00000000..8b57aacd
--- /dev/null
+++ b/lib/fatfs/test_apps/dyn_buffers/sdkconfig.defaults
@@ -0,0 +1,14 @@
+# General options for additional checks
+CONFIG_HEAP_POISONING_COMPREHENSIVE=y
+CONFIG_COMPILER_WARN_WRITE_STRINGS=y
+CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y
+CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y
+CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
+CONFIG_COMPILER_STACK_CHECK=y
+
+# use custom partition table
+CONFIG_PARTITION_TABLE_CUSTOM=y
+CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
+
+# to measure allocations
+CONFIG_HEAP_USE_HOOKS=y
diff --git a/lib/fatfs/test_apps/flash_ro/README.md b/lib/fatfs/test_apps/flash_ro/README.md
index 4d341fbc..90297186 100644
--- a/lib/fatfs/test_apps/flash_ro/README.md
+++ b/lib/fatfs/test_apps/flash_ro/README.md
@@ -1,5 +1,5 @@
-| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
-| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
+| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
+| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- |
This test app runs a few FATFS test cases in a read-only FAT partition.
diff --git a/lib/fatfs/test_apps/flash_ro/main/test_fatfs_flash_ro.c b/lib/fatfs/test_apps/flash_ro/main/test_fatfs_flash_ro.c
index 61786bfe..cca3dc0d 100644
--- a/lib/fatfs/test_apps/flash_ro/main/test_fatfs_flash_ro.c
+++ b/lib/fatfs/test_apps/flash_ro/main/test_fatfs_flash_ro.c
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
+#include "sdkconfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -263,7 +264,7 @@ TEST_CASE("(raw) multiple tasks can use same volume", "[fatfs]")
read_test_arg_t args4 = READ_TEST_ARG_INIT(names[3], 0x34343434);
const int cpuid_0 = 0;
- const int cpuid_1 = portNUM_PROCESSORS - 1;
+ const int cpuid_1 = CONFIG_FREERTOS_NUMBER_OF_CORES - 1;
const int stack_size = 4096;
printf("reading files 1.txt 2.txt 3.txt 4.txt \n");
diff --git a/lib/fatfs/test_apps/flash_ro/pytest_fatfs_flash_ro.py b/lib/fatfs/test_apps/flash_ro/pytest_fatfs_flash_ro.py
index eda296ef..0339f8b6 100644
--- a/lib/fatfs/test_apps/flash_ro/pytest_fatfs_flash_ro.py
+++ b/lib/fatfs/test_apps/flash_ro/pytest_fatfs_flash_ro.py
@@ -1,15 +1,11 @@
-# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
-
import pytest
from pytest_embedded import Dut
+from pytest_embedded_idf.utils import idf_parametrize
-@pytest.mark.supported_targets
@pytest.mark.generic
+@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target'])
def test_fatfs_flash_ro(dut: Dut) -> None:
- dut.expect_exact('Press ENTER to see the list of tests')
- dut.write('')
- dut.expect_exact('Enter test for running.')
- dut.write('*')
- dut.expect_unity_test_output()
+ dut.run_all_single_board_cases()
diff --git a/lib/fatfs/test_apps/flash_wl/README.md b/lib/fatfs/test_apps/flash_wl/README.md
index 9112db39..a90777a3 100644
--- a/lib/fatfs/test_apps/flash_wl/README.md
+++ b/lib/fatfs/test_apps/flash_wl/README.md
@@ -1,5 +1,5 @@
-| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
-| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
+| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
+| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- |
This test app runs a few FATFS test cases in a wear levelling FAT partition.
diff --git a/lib/fatfs/test_apps/flash_wl/main/CMakeLists.txt b/lib/fatfs/test_apps/flash_wl/main/CMakeLists.txt
index 20b600fc..f6501bda 100644
--- a/lib/fatfs/test_apps/flash_wl/main/CMakeLists.txt
+++ b/lib/fatfs/test_apps/flash_wl/main/CMakeLists.txt
@@ -1,4 +1,4 @@
-idf_component_register(SRCS "test_fatfs_flash_wl.c"
+idf_component_register(SRCS "test_fatfs_flash_wl.c" "test_fatfs_small_partition.c"
INCLUDE_DIRS "."
PRIV_REQUIRES unity spi_flash fatfs vfs test_fatfs_common
WHOLE_ARCHIVE)
diff --git a/lib/fatfs/test_apps/flash_wl/main/Kconfig.projbuild b/lib/fatfs/test_apps/flash_wl/main/Kconfig.projbuild
new file mode 100644
index 00000000..c7292513
--- /dev/null
+++ b/lib/fatfs/test_apps/flash_wl/main/Kconfig.projbuild
@@ -0,0 +1,10 @@
+menu "Test configuration"
+ config SPI_WL_TEST_ERASE_PARTITION
+ bool "Erase partition"
+ default y if IDF_TARGET_LINUX
+ help
+ Erase the partition before each format operation.
+ This will destroy the flash fairly quickly in CI, but is necessary to
+ ensure that the test is not affected by previous test runs.
+ Run with caution.
+endmenu
diff --git a/lib/fatfs/test_apps/flash_wl/main/test_fatfs_flash_wl.c b/lib/fatfs/test_apps/flash_wl/main/test_fatfs_flash_wl.c
index 1af50fe6..fa383803 100644
--- a/lib/fatfs/test_apps/flash_wl/main/test_fatfs_flash_wl.c
+++ b/lib/fatfs/test_apps/flash_wl/main/test_fatfs_flash_wl.c
@@ -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
*/
@@ -23,6 +23,7 @@
#include "wear_levelling.h"
#include "esp_partition.h"
#include "esp_memory_utils.h"
+#include "vfs_fat_internal.h"
void app_main(void)
{
@@ -32,7 +33,7 @@ void app_main(void)
static wl_handle_t s_test_wl_handle;
static void test_setup(void)
{
- esp_vfs_fat_sdmmc_mount_config_t mount_config = {
+ esp_vfs_fat_mount_config_t mount_config = {
.format_if_mount_failed = true,
.max_files = 5,
};
@@ -45,14 +46,47 @@ static void test_teardown(void)
TEST_ESP_OK(esp_vfs_fat_spiflash_unmount_rw_wl("/spiflash", s_test_wl_handle));
}
-TEST_CASE("(WL) can format partition", "[fatfs][wear_levelling][timeout=180]")
+#ifdef CONFIG_SPI_WL_TEST_ERASE_PARTITION
+static void corrupt_wl_data(void)
{
+ const esp_partition_t* part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, NULL);
+ TEST_ASSERT_NOT_NULL(part);
+ TEST_ESP_OK(esp_partition_erase_range(part, 0, part->size));
+}
+#endif
+
+TEST_CASE("(WL) can format partition", "[fatfs][wear_levelling][timeout=120]")
+{
+#ifdef CONFIG_SPI_WL_TEST_ERASE_PARTITION
+ corrupt_wl_data();
+#endif
TEST_ESP_OK(esp_vfs_fat_spiflash_format_rw_wl("/spiflash", NULL));
test_setup();
+ vfs_fat_spiflash_ctx_t* ctx = get_vfs_fat_spiflash_ctx(s_test_wl_handle);
+ TEST_ASSERT_NOT_NULL(ctx);
+ TEST_ASSERT_TRUE(ctx->fs->n_fats == 2); // 2 FATs are created by default
+ test_teardown();
+}
+
+TEST_CASE("(WL) can format partition with config", "[fatfs][wear_levelling][timeout=120]")
+{
+#ifdef CONFIG_SPI_WL_TEST_ERASE_PARTITION
+ corrupt_wl_data();
+#endif
+ esp_vfs_fat_mount_config_t format_config = {
+ .format_if_mount_failed = true,
+ .max_files = 5,
+ .use_one_fat = true,
+ };
+ TEST_ESP_OK(esp_vfs_fat_spiflash_format_cfg_rw_wl("/spiflash", NULL, &format_config));
+ test_setup();
+ vfs_fat_spiflash_ctx_t* ctx = get_vfs_fat_spiflash_ctx(s_test_wl_handle);
+ TEST_ASSERT_NOT_NULL(ctx);
+ TEST_ASSERT_TRUE(ctx->fs->n_fats == 1);
test_teardown();
}
-TEST_CASE("(WL) can format when the FAT is mounted already", "[fatfs][wear_levelling][timeout=180]")
+TEST_CASE("(WL) can format when the FAT is mounted already", "[fatfs][wear_levelling][timeout=120]")
{
test_setup();
TEST_ESP_OK(esp_vfs_fat_spiflash_format_rw_wl("/spiflash", NULL));
@@ -61,9 +95,28 @@ TEST_CASE("(WL) can format when the FAT is mounted already", "[fatfs][wear_level
test_teardown();
}
-TEST_CASE("(WL) can format specified FAT when more are mounted", "[fatfs][wear_levelling][timeout=180]")
+TEST_CASE("(WL) can format when the FAT is mounted already with config", "[fatfs][wear_levelling][timeout=120]")
+{
+ TEST_ESP_OK(esp_vfs_fat_spiflash_format_rw_wl("/spiflash", NULL)); // To reset the FAT number to 2
+ test_setup();
+ vfs_fat_spiflash_ctx_t* ctx = get_vfs_fat_spiflash_ctx(s_test_wl_handle);
+ TEST_ASSERT_NOT_NULL(ctx);
+ TEST_ASSERT_TRUE(ctx->fs->n_fats == 2);
+ esp_vfs_fat_mount_config_t format_config = {
+ .format_if_mount_failed = true,
+ .max_files = 5,
+ .use_one_fat = true,
+ };
+ TEST_ESP_OK(esp_vfs_fat_spiflash_format_cfg_rw_wl("/spiflash", NULL, &format_config));
+ TEST_ASSERT_TRUE(ctx->fs->n_fats == 1);
+ test_fatfs_create_file_with_text("/spiflash/hello.txt", fatfs_test_hello_str);
+ test_fatfs_pread_file("/spiflash/hello.txt");
+ test_teardown();
+}
+
+TEST_CASE("(WL) can format specified FAT when more are mounted", "[fatfs][wear_levelling][timeout=120]")
{
- esp_vfs_fat_sdmmc_mount_config_t mount_config = {
+ esp_vfs_fat_mount_config_t mount_config = {
.format_if_mount_failed = true,
.max_files = 5,
};
@@ -126,7 +179,7 @@ TEST_CASE("(WL) pwrite() works well", "[fatfs][wear_levelling]")
TEST_CASE("(WL) can open maximum number of files", "[fatfs][wear_levelling]")
{
size_t max_files = FOPEN_MAX - 3; /* account for stdin, stdout, stderr */
- esp_vfs_fat_sdmmc_mount_config_t mount_config = {
+ esp_vfs_fat_mount_config_t mount_config = {
.format_if_mount_failed = true,
.max_files = max_files
};
@@ -152,9 +205,25 @@ TEST_CASE("(WL) can lseek", "[fatfs][wear_levelling]")
TEST_CASE("(WL) can truncate", "[fatfs][wear_levelling]")
{
test_setup();
- test_fatfs_truncate_file("/spiflash/truncate.txt");
+ test_fatfs_truncate_file("/spiflash/truncate.txt", true);
+ test_teardown();
+}
+
+TEST_CASE("(WL) can ftruncate", "[fatfs][wear_levelling]")
+{
+ test_setup();
+ test_fatfs_ftruncate_file("/spiflash/ftrunc.txt", true);
+ test_teardown();
+}
+
+#if FF_USE_EXPAND
+TEST_CASE("(WL) can esp_vfs_fat_create_contiguous_file", "[fatfs][wear_levelling]")
+{
+ test_setup();
+ test_fatfs_create_contiguous_file("/spiflash", "/spiflash/expand.txt");
test_teardown();
}
+#endif
TEST_CASE("(WL) stat returns correct values", "[fatfs][wear_levelling]")
{
@@ -205,6 +274,13 @@ TEST_CASE("(WL) can opendir root directory of FS", "[fatfs][wear_levelling]")
test_teardown();
}
+TEST_CASE("(WL) readdir, stat work as expected", "[fatfs][wear_levelling]")
+{
+ test_setup();
+ test_fatfs_readdir_stat("/spiflash/dir");
+ test_teardown();
+}
+
TEST_CASE("(WL) opendir, readdir, rewinddir, seekdir work as expected", "[fatfs][wear_levelling]")
{
test_setup();
diff --git a/lib/fatfs/test_apps/flash_wl/main/test_fatfs_small_partition.c b/lib/fatfs/test_apps/flash_wl/main/test_fatfs_small_partition.c
new file mode 100644
index 00000000..fd65a4db
--- /dev/null
+++ b/lib/fatfs/test_apps/flash_wl/main/test_fatfs_small_partition.c
@@ -0,0 +1,76 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/unistd.h>
+#include "unity.h"
+#include "esp_vfs_fat.h"
+
+static wl_handle_t s_test_wl_handle;
+static void test_setup(void)
+{
+ // With this configuration, for 32k partition size,
+ // 4 sectors will be used for WL and 4 sectors for FATFS
+ // (1 FAT, 1 root directory, 1 reserved and 1 data sector)
+ esp_vfs_fat_mount_config_t mount_config = {
+ .format_if_mount_failed = true,
+ .max_files = 5,
+ .use_one_fat = true,
+ };
+
+ TEST_ESP_OK(esp_vfs_fat_spiflash_format_cfg_rw_wl("/spiflash", "storage1", &mount_config));
+ TEST_ESP_OK(esp_vfs_fat_spiflash_mount_rw_wl("/spiflash", "storage1", &mount_config, &s_test_wl_handle));
+}
+
+static void test_teardown(void)
+{
+ TEST_ESP_OK(esp_vfs_fat_spiflash_unmount_rw_wl("/spiflash", s_test_wl_handle));
+}
+
+static void test_write_data_sec(int num_data_sec)
+{
+ int fd = open("/spiflash/test.txt", O_CREAT | O_WRONLY);
+ TEST_ASSERT_NOT_EQUAL(-1, fd);
+
+ // Generate data
+ uint32_t data_size = 4096*num_data_sec;
+
+ char *data = (char*) malloc(data_size);
+ char *read_data = (char*) malloc(data_size);
+
+ for(uint32_t i = 0; i < (data_size); i += sizeof(i))
+ {
+ *((uint32_t*)(data + i)) = i;
+ }
+ ssize_t wr = write(fd, data, data_size);
+ if (num_data_sec == 1) {
+ TEST_ASSERT_EQUAL(data_size, wr);
+ } else {
+ TEST_ASSERT_NOT_EQUAL(data_size, wr);
+ }
+ TEST_ASSERT_EQUAL(0, close(fd));
+
+ fd = open("/spiflash/test.txt", O_RDONLY);
+ int r = read(fd, read_data, data_size);
+ if (num_data_sec == 1) {
+ TEST_ASSERT_EQUAL(data_size, r);
+ } else {
+ TEST_ASSERT_NOT_EQUAL(data_size, r);
+ }
+ TEST_ASSERT_EQUAL(0, strcmp(data, read_data));
+ TEST_ASSERT_EQUAL(0, close(fd));
+}
+
+TEST_CASE("(WL) can format small partition and read-write data", "[fatfs][wear_levelling][timeout=120]")
+{
+ test_setup();
+ test_write_data_sec(1); //for 1 data sectors, write and read func should work
+ test_write_data_sec(2); //for 2 data sectors, write and read func should fail
+ test_teardown();
+}
diff --git a/lib/fatfs/test_apps/flash_wl/partitions.csv b/lib/fatfs/test_apps/flash_wl/partitions.csv
index d1dcbae6..d68a9de0 100644
--- a/lib/fatfs/test_apps/flash_wl/partitions.csv
+++ b/lib/fatfs/test_apps/flash_wl/partitions.csv
@@ -2,3 +2,4 @@
factory, app, factory, 0x10000, 768k,
storage, data, fat, , 528k,
storage2, data, fat, , 528k,
+storage1, data, fat, , 32k,
diff --git a/lib/fatfs/test_apps/flash_wl/pytest_fatfs_flash_wl.py b/lib/fatfs/test_apps/flash_wl/pytest_fatfs_flash_wl.py
index af8c5db6..a9863635 100644
--- a/lib/fatfs/test_apps/flash_wl/pytest_fatfs_flash_wl.py
+++ b/lib/fatfs/test_apps/flash_wl/pytest_fatfs_flash_wl.py
@@ -1,11 +1,10 @@
-# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
+# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
-
import pytest
from pytest_embedded import Dut
+from pytest_embedded_idf.utils import idf_parametrize
-@pytest.mark.supported_targets
@pytest.mark.generic
@pytest.mark.parametrize(
'config',
@@ -13,27 +12,23 @@ from pytest_embedded import Dut
'default',
'release',
'fastseek',
- ]
+ 'auto_fsync',
+ 'dyn_buffers',
+ ],
)
+@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target'])
def test_fatfs_flash_wl_generic(dut: Dut) -> None:
- dut.expect_exact('Press ENTER to see the list of tests')
- dut.write('')
- dut.expect_exact('Enter test for running.')
- dut.write('*')
- dut.expect_unity_test_output(timeout=180)
+ dut.run_all_single_board_cases(timeout=240)
-@pytest.mark.supported_targets
+@pytest.mark.generic
@pytest.mark.psram
@pytest.mark.parametrize(
'config',
[
'psram',
- ]
+ ],
)
+@idf_parametrize('target', ['esp32'], indirect=['target'])
def test_fatfs_flash_wl_psram(dut: Dut) -> None:
- dut.expect_exact('Press ENTER to see the list of tests')
- dut.write('')
- dut.expect_exact('Enter test for running.')
- dut.write('*')
- dut.expect_unity_test_output(timeout=180)
+ dut.run_all_single_board_cases(timeout=180)
diff --git a/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.auto_fsync b/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.auto_fsync
index b74d5124..9ab32baa 100644
--- a/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.auto_fsync
+++ b/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.auto_fsync
@@ -1 +1,3 @@
CONFIG_FATFS_IMMEDIATE_FSYNC=y
+CONFIG_ESP_MAIN_TASK_STACK_SIZE=4096
+CONFIG_FATFS_VFS_FSTAT_BLKSIZE=2048
diff --git a/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.dyn_buffers b/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.dyn_buffers
new file mode 100644
index 00000000..380dafbf
--- /dev/null
+++ b/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.dyn_buffers
@@ -0,0 +1 @@
+CONFIG_FATFS_USE_DYN_BUFFERS=y
diff --git a/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.psram b/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.psram.esp32
index b3b45db9..5becfb94 100644
--- a/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.psram
+++ b/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.psram.esp32
@@ -1,3 +1,4 @@
+CONFIG_IDF_TARGET="esp32"
CONFIG_SPIRAM=y
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0
CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y
diff --git a/lib/fatfs/test_apps/sdcard/README.md b/lib/fatfs/test_apps/sdcard/README.md
index 639f1d38..381b38a9 100644
--- a/lib/fatfs/test_apps/sdcard/README.md
+++ b/lib/fatfs/test_apps/sdcard/README.md
@@ -1,5 +1,5 @@
-| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
-| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |
+| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
+| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- | -------- |
This test app runs a few FATFS test cases in a FAT-formatted SD card.
diff --git a/lib/fatfs/test_apps/sdcard/main/CMakeLists.txt b/lib/fatfs/test_apps/sdcard/main/CMakeLists.txt
index be2a969a..e4a519cb 100644
--- a/lib/fatfs/test_apps/sdcard/main/CMakeLists.txt
+++ b/lib/fatfs/test_apps/sdcard/main/CMakeLists.txt
@@ -1,6 +1,6 @@
idf_component_register(SRCS "test_fatfs_sdcard_main.c" "test_fatfs_sdspi.c"
INCLUDE_DIRS "."
- PRIV_REQUIRES unity fatfs vfs sdmmc driver test_fatfs_common
+ PRIV_REQUIRES unity fatfs vfs sdmmc driver test_fatfs_common esp_timer
WHOLE_ARCHIVE)
if(CONFIG_SOC_SDMMC_HOST_SUPPORTED)
diff --git a/lib/fatfs/test_apps/sdcard/main/test_fatfs_sdmmc.c b/lib/fatfs/test_apps/sdcard/main/test_fatfs_sdmmc.c
index 4504a5d6..e6478ad9 100644
--- a/lib/fatfs/test_apps/sdcard/main/test_fatfs_sdmmc.c
+++ b/lib/fatfs/test_apps/sdcard/main/test_fatfs_sdmmc.c
@@ -1,5 +1,5 @@
/*
- * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
+ * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -13,6 +13,7 @@
#include "unity.h"
#include "esp_log.h"
#include "esp_random.h"
+#include "esp_timer.h"
#include "esp_vfs.h"
#include "esp_vfs_fat.h"
#include "freertos/FreeRTOS.h"
@@ -22,6 +23,7 @@
#include "ff.h"
#include "test_fatfs_common.h"
#include "soc/soc_caps.h"
+#include "vfs_fat_internal.h"
#if CONFIG_IDF_TARGET_ESP32
#define SDSPI_MISO_PIN 2
@@ -50,6 +52,7 @@
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S3)
// No runner
#include "driver/sdmmc_host.h"
+const char* base_path = "/sdcard";
static void test_setup_sdmmc(sdmmc_card_t **out_card)
{
@@ -61,7 +64,7 @@ static void test_setup_sdmmc(sdmmc_card_t **out_card)
.max_files = 5,
.allocation_unit_size = 16 * 1024
};
- TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card));
+ TEST_ESP_OK(esp_vfs_fat_sdmmc_mount(base_path, &host, &slot_config, &mount_config, &card));
*out_card = card;
}
@@ -102,6 +105,35 @@ TEST_CASE("(SD) can format partition", "[fatfs][sdmmc][timeout=180]")
test_teardown_sdmmc(card);
}
+TEST_CASE("(SD) can format partition with config", "[fatfs][sdmmc][timeout=180]")
+{
+ sdmmc_card_t *card = NULL;
+ test_setup_sdmmc(&card);
+ vfs_fat_sd_ctx_t* ctx = get_vfs_fat_get_sd_ctx(card);
+ TEST_ASSERT_NOT_NULL(ctx);
+
+ esp_vfs_fat_mount_config_t format_config = {
+ .format_if_mount_failed = true,
+ .max_files = 5,
+ .allocation_unit_size = 16 * 1024,
+ .use_one_fat = true,
+ };
+ TEST_ESP_OK(esp_vfs_fat_sdcard_format_cfg("/sdcard", card, &format_config));
+ TEST_ASSERT_TRUE(ctx->fs->n_fats == 1);
+
+ test_fatfs_create_file_with_text(test_filename, fatfs_test_hello_str);
+ test_fatfs_read_file(test_filename);
+
+ format_config.use_one_fat = false;
+ TEST_ESP_OK(esp_vfs_fat_sdcard_format_cfg("/sdcard", card, &format_config));
+ TEST_ASSERT_TRUE(ctx->fs->n_fats == 2);
+
+ test_fatfs_create_file_with_text(test_filename, fatfs_test_hello_str);
+ test_fatfs_read_file(test_filename);
+
+ test_teardown_sdmmc(card);
+}
+
TEST_CASE("(SD) can create and write file", "[fatfs][sdmmc]")
{
sdmmc_card_t *card = NULL;
@@ -156,7 +188,7 @@ TEST_CASE("(SD) can truncate", "[fatfs][sdmmc]")
{
sdmmc_card_t *card = NULL;
test_setup_sdmmc(&card);
- test_fatfs_truncate_file("/sdcard/truncate.txt");
+ test_fatfs_truncate_file("/sdcard/truncate.txt", true);
test_teardown_sdmmc(card);
}
@@ -164,9 +196,19 @@ TEST_CASE("(SD) can ftruncate", "[fatfs][sdmmc]")
{
sdmmc_card_t *card = NULL;
test_setup_sdmmc(&card);
- test_fatfs_ftruncate_file("/sdcard/ftrunc.txt");
+ test_fatfs_ftruncate_file("/sdcard/ftrunc.txt", true);
+ test_teardown_sdmmc(card);
+}
+
+#if FF_USE_EXPAND
+TEST_CASE("(SD) can esp_vfs_fat_create_contiguous_file", "[fatfs][sdmmc]")
+{
+ sdmmc_card_t *card = NULL;
+ test_setup_sdmmc(&card);
+ test_fatfs_create_contiguous_file("/sdcard", "/sdcard/expand.txt");
test_teardown_sdmmc(card);
}
+#endif
TEST_CASE("(SD) stat returns correct values", "[fatfs][sdmmc]")
{
@@ -275,6 +317,231 @@ static void sdmmc_speed_test(void *buf, size_t buf_size, size_t file_size, bool
TEST_ESP_OK(esp_vfs_fat_sdcard_unmount("/sdcard", card));
}
+TEST_CASE("(SD) mount FAT partitions and readdir to get stat structure", "[fatfs][sdmmc]")
+{
+ char name_dir_file[64];
+ char name_dir_stat[64] = {0};
+ const char* dir_prefix = "/sdcard";
+ int dir_prefix_len = strlen(dir_prefix);
+ int file_num = 300;
+
+ /* Mount FATFS in SD can WL at the same time. Create a file on each FS */
+ sdmmc_card_t* card = NULL;
+ test_setup_sdmmc(&card);
+ TEST_ESP_OK(esp_vfs_fat_sdcard_format("/sdcard", card));
+
+ //Create multiple files with text on sdcard. Each file size is 14 bytes
+ //Total files created are file_num (300 in this case)
+ //So directory size will be 300*14 bytes
+ for(int i=0;i<file_num;i++) {
+ snprintf(name_dir_file, sizeof(name_dir_file), "%s/boo_%d.bin", dir_prefix, i);
+ test_fatfs_create_file_with_text(name_dir_file, fatfs_test_hello_str);
+ }
+
+ //Start the timer to get time needed to calculate the directory size
+ int64_t start = esp_timer_get_time();
+ DIR* dir = opendir(dir_prefix);
+ TEST_ASSERT_NOT_NULL(dir);
+ struct stat st;
+ struct dirent* de;
+ uint32_t dir_size = 0;
+
+ // Call readdir before stat function and record the time needed to calculate the directory size
+ while(1) {
+ de = readdir(dir);
+ if (!de) {
+ break;
+ }
+ snprintf(name_dir_stat, dir_prefix_len+sizeof(de->d_name)+1, "%s/%s", dir_prefix, de->d_name);
+ TEST_ASSERT_EQUAL(0, stat(name_dir_stat, &st));
+ dir_size += st.st_size;
+ }
+ TEST_ASSERT_EQUAL(0, closedir(dir));
+ int64_t end = esp_timer_get_time();
+ int64_t total_time_readdir = end-start;
+ printf("Time in us for calculating directory size by calling readdir first and then stat func: %lld \n",total_time_readdir);
+ printf("Size of the directory %s is %"PRIu32"Kb\n", dir_prefix, (dir_size/1000));
+ TEST_ASSERT_EQUAL(file_num*strlen(fatfs_test_hello_str), dir_size); //each file size is 14 bytes
+
+ // Call stat function directly without calling readdir and record the time needed to calculate the directory size
+ dir_size = 0;
+ start = esp_timer_get_time();
+ for(int i=0;i<file_num;i++) {
+ snprintf(name_dir_file, sizeof(name_dir_file), "%s/boo_%d.bin", dir_prefix, i);
+ TEST_ASSERT_EQUAL(0, stat(name_dir_file, &st));
+ dir_size += st.st_size;
+ }
+ end = esp_timer_get_time();
+ int64_t total_time_stat = end-start;
+ printf("Time in us for calculating directory size by calling stat func: %lld \n",total_time_stat);
+ printf("Size of the directory %s is %"PRIu32"Kb\n", dir_prefix, (dir_size/1000));
+ printf("%d\n", strlen(fatfs_test_hello_str));
+ TEST_ASSERT_EQUAL(file_num*strlen(fatfs_test_hello_str), dir_size); //each file size is 14 bytes
+
+ for(int i=0;i<file_num;i++) {
+ snprintf(name_dir_file, sizeof(name_dir_file), "%s/boo_%d.bin", dir_prefix,i);
+ unlink(name_dir_file);
+ }
+
+ test_teardown_sdmmc(card);
+}
+
+typedef struct {
+ const char *dir;
+ const char *filename;
+ const char *str;
+ SemaphoreHandle_t sem;
+} test_task_param_t;
+
+static void test_task(void *param)
+{
+ DIR* dir = NULL;
+ struct dirent* de = NULL;
+ struct stat st;
+ char name_dir_stat[64] = {0};
+ const test_task_param_t *test_task_param = param;
+
+ dir = opendir(test_task_param->dir);
+ TEST_ASSERT_NOT_NULL(dir);
+ while(1) {
+ de = readdir(dir);
+ if (!de) {
+ break;
+ }
+ //Intentionally introduced a delay to ensure that the second task is triggered simultaneously.
+ vTaskDelay(10 / portTICK_PERIOD_MS);
+ snprintf(name_dir_stat, sizeof(test_task_param->dir)+sizeof(de->d_name), "%s/%s", test_task_param->dir, de->d_name);
+ TEST_ASSERT_EQUAL(0, stat(name_dir_stat, &st));
+ if (strcasecmp(de->d_name, test_task_param->filename) == 0) {
+ TEST_ASSERT_FALSE(st.st_mode & S_IFDIR);
+ TEST_ASSERT_EQUAL(strlen(test_task_param->str), st.st_size);
+ } else {
+ TEST_FAIL_MESSAGE("unexpected directory entry");
+ }
+ }
+
+ if (test_task_param->sem) {
+ xSemaphoreGive(test_task_param->sem);
+ }
+ vTaskDelete(NULL);
+}
+
+TEST_CASE("(SD) mount two FAT partitions, SDMMC and WL, at the same time and readdir to get stat structure", "[fatfs][sdmmc]")
+{
+ esp_vfs_fat_sdmmc_mount_config_t mount_config = {
+ .format_if_mount_failed = true,
+ .max_files = 5
+ };
+
+ const char *dir_prefix[FF_VOLUMES] = {"/sdcard", "/spiflash"};
+ const char *dir_filename[FF_VOLUMES] = {"sd.txt", "wl.txt"};
+ const char* str[FF_VOLUMES] = {"this is sd\n", "this is spiflash\n"};
+ const char* filename_sd = "/sdcard/sd.txt";
+ const char* filename_wl = "/spiflash/wl.txt";
+
+ /* Erase flash before the first use */
+ const esp_partition_t *test_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, NULL);
+ TEST_ASSERT_NOT_NULL(test_partition);
+ esp_partition_erase_range(test_partition, 0, test_partition->size);
+
+ /* Mount FATFS in SD can WL at the same time. Create a file on each FS */
+ wl_handle_t wl_handle = WL_INVALID_HANDLE;
+ sdmmc_card_t *card = NULL;
+ test_setup_sdmmc(&card);
+ TEST_ESP_OK(esp_vfs_fat_spiflash_mount_rw_wl("/spiflash", NULL, &mount_config, &wl_handle));
+ unlink(filename_sd);
+ unlink(filename_wl);
+ test_fatfs_create_file_with_text(filename_sd, str[0]);
+ test_fatfs_create_file_with_text(filename_wl, str[1]);
+
+ test_task_param_t test_task_param_sd = {
+ .dir = dir_prefix[0],
+ .filename = dir_filename[0],
+ .str = str[0],
+ .sem = xSemaphoreCreateBinary(),
+ };
+
+ test_task_param_t test_task_param_spiflash = {
+ .dir = dir_prefix[1],
+ .filename = dir_filename[1],
+ .str = str[1],
+ .sem = xSemaphoreCreateBinary(),
+ };
+
+ //Create two tasks with same priority to check file size on two different FAT partitions at the same time
+ xTaskCreate(test_task, "test_task_1", 8*1024, (void *) &test_task_param_sd, 5, NULL);
+ xTaskCreate(test_task, "test_task_2", 8*1024, (void *) &test_task_param_spiflash, 5, NULL);
+
+ TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(test_task_param_sd.sem, 1000 / portTICK_PERIOD_MS));
+ vSemaphoreDelete(test_task_param_sd.sem);
+ TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(test_task_param_spiflash.sem, 1000 / portTICK_PERIOD_MS));
+ vSemaphoreDelete(test_task_param_spiflash.sem);
+ TEST_ESP_OK(esp_vfs_fat_spiflash_unmount_rw_wl("/spiflash", wl_handle));
+ test_teardown_sdmmc(card);
+}
+
+TEST_CASE("(SD) read two directories and get stat structure for respective file at the same time", "[fatfs][sdmmc]")
+{
+ char name_dir_file[64];
+ char name_dir1_stat[64] = {0};
+ char name_dir2_stat[64] = {0};
+ const char* dir1_prefix = "/sdcard/dir1";
+ const char* dir2_prefix = "/sdcard/dir2";
+ int dir1_prefix_len = strlen(dir1_prefix);
+ int dir2_prefix_len = strlen(dir2_prefix);
+ const char* test_str1 = "Hello, World!\n";
+ const char* test_str2 = "Hello, ESP Community\n";
+
+ /* Mount FATFS in SD can WL at the same time. Create a file on each FS */
+ sdmmc_card_t* card = NULL;
+ test_setup_sdmmc(&card);
+
+ TEST_ASSERT_EQUAL(0, mkdir(dir1_prefix, 0755));
+ TEST_ASSERT_EQUAL(0, mkdir(dir2_prefix, 0755));
+
+ snprintf(name_dir_file, sizeof(name_dir_file), "%s/boo_1.bin", dir1_prefix);
+ test_fatfs_create_file_with_text(name_dir_file, test_str1);
+ snprintf(name_dir_file, sizeof(name_dir_file), "%s/boo_1.bin", dir2_prefix);
+ test_fatfs_create_file_with_text(name_dir_file, test_str2);
+
+ DIR* dir1 = opendir(dir1_prefix);
+ TEST_ASSERT_NOT_NULL(dir1);
+ DIR* dir2 = opendir(dir2_prefix);
+ TEST_ASSERT_NOT_NULL(dir2);
+ struct dirent* de1;
+ struct dirent* de2;
+ struct stat st1;
+ struct stat st2;
+
+ while(1) {
+ de1 = readdir(dir1);
+ if (!de1) {
+ break;
+ }
+ de2 = readdir(dir2);
+ if (!de2) {
+ break;
+ }
+ snprintf(name_dir1_stat, dir1_prefix_len+sizeof(de1->d_name)+1, "%s/%s", dir1_prefix, de1->d_name);
+ snprintf(name_dir2_stat, dir2_prefix_len+sizeof(de2->d_name)+1, "%s/%s", dir2_prefix, de2->d_name);
+ TEST_ASSERT_EQUAL(0, stat(name_dir1_stat, &st1));
+ TEST_ASSERT_EQUAL(0, stat(name_dir2_stat, &st2));
+ TEST_ASSERT_EQUAL(strlen(test_str1), st1.st_size); //size of dir1/boo_1.bin is 14
+ TEST_ASSERT_EQUAL(strlen(test_str2), st2.st_size); //size of dir2/boo_1.bin is 21
+ }
+ TEST_ASSERT_EQUAL(0, closedir(dir1));
+ TEST_ASSERT_EQUAL(0, closedir(dir2));
+
+ snprintf(name_dir_file, sizeof(name_dir_file), "%s/boo_1.bin", dir1_prefix);
+ unlink(name_dir_file);
+ snprintf(name_dir_file, sizeof(name_dir_file), "%s/boo_1.bin", dir2_prefix);
+ unlink(name_dir_file);
+ rmdir(dir1_prefix);
+ rmdir(dir2_prefix);
+
+ test_teardown_sdmmc(card);
+}
+
TEST_CASE("(SD) mount two FAT partitions, SDMMC and WL, at the same time", "[fatfs][sdmmc]")
{
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
diff --git a/lib/fatfs/test_apps/sdcard/main/test_fatfs_sdspi.c b/lib/fatfs/test_apps/sdcard/main/test_fatfs_sdspi.c
index c1d567c0..a3247bd2 100644
--- a/lib/fatfs/test_apps/sdcard/main/test_fatfs_sdspi.c
+++ b/lib/fatfs/test_apps/sdcard/main/test_fatfs_sdspi.c
@@ -1,5 +1,5 @@
/*
- * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
+ * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -22,6 +22,7 @@
#include "ff.h"
#include "test_fatfs_common.h"
#include "soc/soc_caps.h"
+#include "vfs_fat_internal.h"
#if CONFIG_IDF_TARGET_ESP32
#define SDSPI_MISO_PIN 2
@@ -34,13 +35,13 @@
#define SDSPI_MOSI_PIN 35
#define SDSPI_CLK_PIN 36
#define SDSPI_CS_PIN 34
-#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C6
+#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61
#define SDSPI_MISO_PIN 6
#define SDSPI_MOSI_PIN 4
#define SDSPI_CLK_PIN 5
#define SDSPI_CS_PIN 1
#define SPI_DMA_CHAN SPI_DMA_CH_AUTO
-#elif CONFIG_IDF_TARGET_ESP32H2
+#elif CONFIG_IDF_TARGET_ESP32H2 || CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32P4
#define SDSPI_MISO_PIN 0
#define SDSPI_MOSI_PIN 5
#define SDSPI_CLK_PIN 4
@@ -89,7 +90,7 @@ static void test_teardown_sdspi(sdspi_mem_t* mem)
HEAP_SIZE_CHECK(mem->heap_size, 0);
}
-TEST_CASE("(SDSPI) write/read speed test", "[fatfs][sdspi]")
+TEST_CASE("(SDSPI) write/read speed test", "[fatfs][sdspi][timeout=120]")
{
sdspi_mem_t mem;
size_t file_size = 1 * 1024 * 1024;
@@ -197,3 +198,55 @@ TEST_CASE("(SDSPI) can format card", "[fatfs][sdspi][timeout=180]")
TEST_ESP_OK(esp_vfs_fat_sdcard_unmount(path, card));
test_teardown_sdspi(&mem);
}
+
+
+TEST_CASE("(SDSPI) can format card with config", "[fatfs][sdspi][timeout=180]")
+{
+ sdspi_mem_t mem;
+ test_setup_sdspi(&mem);
+
+ const char path[] = "/sdcard";
+ sdmmc_card_t *card;
+ card = NULL;
+ sdspi_device_config_t device_cfg = {
+ .gpio_cs = SDSPI_CS_PIN,
+ .host_id = SDSPI_HOST_ID,
+ .gpio_cd = SDSPI_SLOT_NO_CD,
+ .gpio_wp = SDSPI_SLOT_NO_WP,
+ .gpio_int = SDSPI_SLOT_NO_INT,
+ };
+
+ sdmmc_host_t host = SDSPI_HOST_DEFAULT();
+ host.slot = SDSPI_HOST_ID;
+ esp_vfs_fat_sdmmc_mount_config_t mount_config = {
+ .format_if_mount_failed = true,
+ .max_files = 5,
+ .allocation_unit_size = 64 * 1024,
+ };
+ TEST_ESP_OK(esp_vfs_fat_sdspi_mount(path, &host, &device_cfg, &mount_config, &card));
+
+ vfs_fat_sd_ctx_t* ctx = get_vfs_fat_get_sd_ctx(card);
+ TEST_ASSERT_NOT_NULL(ctx);
+
+ esp_vfs_fat_mount_config_t format_config = {
+ .format_if_mount_failed = true,
+ .max_files = 5,
+ .allocation_unit_size = 64 * 1024,
+ .use_one_fat = true,
+ };
+ TEST_ESP_OK(esp_vfs_fat_sdcard_format_cfg("/sdcard", card, &format_config));
+ TEST_ASSERT_TRUE(ctx->fs->n_fats == 1);
+
+ test_fatfs_create_file_with_text(s_test_filename, fatfs_test_hello_str);
+ test_fatfs_read_file(s_test_filename);
+
+ format_config.use_one_fat = false;
+ TEST_ESP_OK(esp_vfs_fat_sdcard_format_cfg("/sdcard", card, &format_config));
+ TEST_ASSERT_TRUE(ctx->fs->n_fats == 2);
+
+ test_fatfs_create_file_with_text(s_test_filename, fatfs_test_hello_str);
+ test_fatfs_read_file(s_test_filename);
+
+ TEST_ESP_OK(esp_vfs_fat_sdcard_unmount(path, card));
+ test_teardown_sdspi(&mem);
+}
diff --git a/lib/fatfs/test_apps/sdcard/pytest_fatfs_sdcard.py b/lib/fatfs/test_apps/sdcard/pytest_fatfs_sdcard.py
index fe720a02..b0657186 100644
--- a/lib/fatfs/test_apps/sdcard/pytest_fatfs_sdcard.py
+++ b/lib/fatfs/test_apps/sdcard/pytest_fatfs_sdcard.py
@@ -1,75 +1,59 @@
-# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
+# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
-
import pytest
from pytest_embedded import Dut
+from pytest_embedded_idf.utils import idf_parametrize
-@pytest.mark.esp32
@pytest.mark.sdcard_sdmode
@pytest.mark.parametrize(
'config',
[
'default',
'release',
- ]
+ ],
)
+@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target'])
def test_fatfs_sdcard_generic_sdmmc(dut: Dut) -> None:
- dut.expect_exact('Press ENTER to see the list of tests')
- dut.write('')
- dut.expect_exact('Enter test for running.')
- dut.write('[sdmmc]')
- dut.expect_unity_test_output(timeout=180)
+ dut.run_all_single_board_cases(group='sdmmc', timeout=180)
-@pytest.mark.esp32
-@pytest.mark.esp32s2
-@pytest.mark.esp32c3
+@pytest.mark.temp_skip_ci(targets=['esp32'], reason='IDFCI-2058, temporary lack runner')
@pytest.mark.sdcard_spimode
@pytest.mark.parametrize(
'config',
[
'default',
'release',
- ]
+ ],
)
+@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target'])
def test_fatfs_sdcard_generic_sdspi(dut: Dut) -> None:
- dut.expect_exact('Press ENTER to see the list of tests')
- dut.write('')
- dut.expect_exact('Enter test for running.')
- dut.write('[sdspi]')
- dut.expect_unity_test_output(timeout=180)
+ dut.run_all_single_board_cases(group='sdspi', timeout=180)
-@pytest.mark.esp32
@pytest.mark.sdcard_sdmode
@pytest.mark.psram
@pytest.mark.parametrize(
'config',
[
'psram',
- ]
+ ],
)
+@idf_parametrize('target', ['esp32'], indirect=['target'])
def test_fatfs_sdcard_psram_sdmmc(dut: Dut) -> None:
- dut.expect_exact('Press ENTER to see the list of tests')
- dut.write('')
- dut.expect_exact('Enter test for running.')
- dut.write('[sdmmc]')
- dut.expect_unity_test_output(timeout=180)
+ dut.run_all_single_board_cases(group='sdmmc', timeout=180)
-@pytest.mark.esp32
+@pytest.mark.temp_skip_ci(targets=['esp32'], reason='IDFCI-2058, temporary lack runner')
@pytest.mark.sdcard_spimode
@pytest.mark.psram
@pytest.mark.parametrize(
'config',
[
'psram',
- ]
+ ],
)
+@idf_parametrize('target', ['esp32'], indirect=['target'])
def test_fatfs_sdcard_psram_sdspi(dut: Dut) -> None:
- dut.expect_exact('Press ENTER to see the list of tests')
- dut.write('')
- dut.expect_exact('Enter test for running.')
- dut.write('[sdspi]')
- dut.expect_unity_test_output(timeout=180)
+ dut.run_all_single_board_cases(group='sdspi', timeout=180)
diff --git a/lib/fatfs/test_apps/sdcard/sdkconfig.ci.psram b/lib/fatfs/test_apps/sdcard/sdkconfig.ci.psram.esp32
index b3b45db9..5becfb94 100644
--- a/lib/fatfs/test_apps/sdcard/sdkconfig.ci.psram
+++ b/lib/fatfs/test_apps/sdcard/sdkconfig.ci.psram.esp32
@@ -1,3 +1,4 @@
+CONFIG_IDF_TARGET="esp32"
CONFIG_SPIRAM=y
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0
CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y
diff --git a/lib/fatfs/test_apps/test_fatfs_common/CMakeLists.txt b/lib/fatfs/test_apps/test_fatfs_common/CMakeLists.txt
index 8f06878d..74ec85e7 100644
--- a/lib/fatfs/test_apps/test_fatfs_common/CMakeLists.txt
+++ b/lib/fatfs/test_apps/test_fatfs_common/CMakeLists.txt
@@ -1,3 +1,3 @@
idf_component_register(SRCS "test_fatfs_common.c"
INCLUDE_DIRS "."
- PRIV_REQUIRES unity fatfs vfs unity)
+ PRIV_REQUIRES unity fatfs vfs unity esp_timer)
diff --git a/lib/fatfs/test_apps/test_fatfs_common/test_fatfs_common.c b/lib/fatfs/test_apps/test_fatfs_common/test_fatfs_common.c
index 77372fbb..887123cb 100644
--- a/lib/fatfs/test_apps/test_fatfs_common/test_fatfs_common.c
+++ b/lib/fatfs/test_apps/test_fatfs_common/test_fatfs_common.c
@@ -1,9 +1,10 @@
/*
- * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
+ * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
+#include "sdkconfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -16,10 +17,12 @@
#include <utime.h>
#include "unity.h"
#include "esp_vfs.h"
+#include "esp_timer.h"
#include "esp_vfs_fat.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "test_fatfs_common.h"
+#include "ff.h"
const char* fatfs_test_hello_str = "Hello, World!\n";
const char* fatfs_test_hello_str_utf = "世界,你好!\n";
@@ -252,7 +255,7 @@ void test_fatfs_lseek(const char* filename)
}
-void test_fatfs_truncate_file(const char* filename)
+void test_fatfs_truncate_file(const char* filename, bool allow_expanding_files)
{
int read = 0;
int truncated_len = 0;
@@ -267,14 +270,44 @@ void test_fatfs_truncate_file(const char* filename)
TEST_ASSERT_EQUAL(0, fclose(f));
+ struct stat st;
+ size_t size;
- // Extending file beyond size is not supported
- TEST_ASSERT_EQUAL(-1, truncate(filename, strlen(input) + 1));
- TEST_ASSERT_EQUAL(errno, EPERM);
+ stat(filename, &st);
+ size = st.st_size;
+ TEST_ASSERT_EQUAL(strlen(input), size);
- TEST_ASSERT_EQUAL(-1, truncate(filename, -1));
- TEST_ASSERT_EQUAL(errno, EINVAL);
+ if (allow_expanding_files) {
+ size_t trunc_add = 2;
+ size_t new_size = strlen(input) + trunc_add;
+ TEST_ASSERT_EQUAL(0, truncate(filename, new_size));
+
+ stat(filename, &st);
+ size = st.st_size;
+ TEST_ASSERT_EQUAL(new_size, size);
+
+ f = fopen(filename, "rb");
+ TEST_ASSERT_NOT_NULL(f);
+
+ char expanded_output[sizeof(input) + trunc_add];
+ memset(expanded_output, 42, sizeof(expanded_output)); // set to something else than 0 (42)
+
+ read = fread(expanded_output, 1, sizeof(input) + trunc_add, f);
+ TEST_ASSERT_EQUAL(new_size, read);
+
+ TEST_ASSERT_EQUAL('Z', expanded_output[strlen(input) - 1]); // 'Z' character
+ TEST_ASSERT_EQUAL('\0', expanded_output[sizeof(input) + trunc_add - 3]); // zeroed expanded space
+ TEST_ASSERT_EQUAL('\0', expanded_output[sizeof(input) + trunc_add - 2]); // zeroed expanded space
+ TEST_ASSERT_EQUAL(42, expanded_output[sizeof(input) + trunc_add - 1]); // 42 set with memset, end of the array
+ TEST_ASSERT_EQUAL(0, fclose(f));
+ } else {
+ TEST_ASSERT_EQUAL(-1, truncate(filename, strlen(input) + 1));
+ TEST_ASSERT_EQUAL(errno, EPERM);
+
+ TEST_ASSERT_EQUAL(-1, truncate(filename, -1));
+ TEST_ASSERT_EQUAL(errno, EINVAL);
+ }
// Truncating should succeed
const char truncated_1[] = "ABCDEFGHIJ";
@@ -282,6 +315,10 @@ void test_fatfs_truncate_file(const char* filename)
TEST_ASSERT_EQUAL(0, truncate(filename, truncated_len));
+ stat(filename, &st);
+ size = st.st_size;
+ TEST_ASSERT_EQUAL(strlen(truncated_1), size);
+
f = fopen(filename, "rb");
TEST_ASSERT_NOT_NULL(f);
@@ -293,28 +330,34 @@ void test_fatfs_truncate_file(const char* filename)
TEST_ASSERT_EQUAL(0, fclose(f));
+ if (allow_expanding_files) {
+ TEST_ASSERT_EQUAL(0, truncate(filename, truncated_len + 1));
+ } else {
+ // Once truncated, the new file size should be the basis
+ // whether truncation should succeed or not when `allow_expanding_files == false`
+ TEST_ASSERT_EQUAL(-1, truncate(filename, truncated_len + 1));
+ TEST_ASSERT_EQUAL(EPERM, errno);
- // Once truncated, the new file size should be the basis
- // whether truncation should succeed or not
- TEST_ASSERT_EQUAL(-1, truncate(filename, truncated_len + 1));
- TEST_ASSERT_EQUAL(EPERM, errno);
-
- TEST_ASSERT_EQUAL(-1, truncate(filename, strlen(input)));
- TEST_ASSERT_EQUAL(EPERM, errno);
+ TEST_ASSERT_EQUAL(-1, truncate(filename, strlen(input)));
+ TEST_ASSERT_EQUAL(EPERM, errno);
- TEST_ASSERT_EQUAL(-1, truncate(filename, strlen(input) + 1));
- TEST_ASSERT_EQUAL(EPERM, errno);
+ TEST_ASSERT_EQUAL(-1, truncate(filename, strlen(input) + 1));
+ TEST_ASSERT_EQUAL(EPERM, errno);
+ }
TEST_ASSERT_EQUAL(-1, truncate(filename, -1));
TEST_ASSERT_EQUAL(EINVAL, errno);
-
// Truncating a truncated file should succeed
const char truncated_2[] = "ABCDE";
truncated_len = strlen(truncated_2);
TEST_ASSERT_EQUAL(0, truncate(filename, truncated_len));
+ stat(filename, &st);
+ size = st.st_size;
+ TEST_ASSERT_EQUAL(strlen(truncated_2), size);
+
f = fopen(filename, "rb");
TEST_ASSERT_NOT_NULL(f);
@@ -327,29 +370,63 @@ void test_fatfs_truncate_file(const char* filename)
TEST_ASSERT_EQUAL(0, fclose(f));
}
-void test_fatfs_ftruncate_file(const char* filename)
+void test_fatfs_ftruncate_file(const char* filename, bool allow_expanding_files)
{
int truncated_len = 0;
const char input[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char output[sizeof(input)];
- int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC);
+ int fd = open(filename, O_RDWR | O_CREAT | O_TRUNC);
TEST_ASSERT_NOT_EQUAL(-1, fd);
TEST_ASSERT_EQUAL(strlen(input), write(fd, input, strlen(input)));
- // Extending file beyond size is not supported
- TEST_ASSERT_EQUAL(-1, ftruncate(fd, strlen(input) + 1));
- TEST_ASSERT_EQUAL(errno, EPERM);
-
- TEST_ASSERT_EQUAL(-1, ftruncate(fd, -1));
- TEST_ASSERT_EQUAL(errno, EINVAL);
+ struct stat st;
+ size_t size;
+
+ fstat(fd, &st);
+ size = st.st_size;
+ TEST_ASSERT_EQUAL(strlen(input), size);
+
+ if (allow_expanding_files) {
+ size_t trunc_add = 2;
+ size_t new_size = strlen(input) + trunc_add;
+ TEST_ASSERT_EQUAL(0, ftruncate(fd, new_size));
+
+ fstat(fd, &st);
+ size = st.st_size;
+ TEST_ASSERT_EQUAL(new_size, size);
+
+ char expanded_output[sizeof(input) + trunc_add];
+ memset(expanded_output, 42, sizeof(expanded_output)); // set to something else than 0 (42)
+
+ lseek(fd, 0, SEEK_SET);
+ int r = read(fd, expanded_output, sizeof(input) + trunc_add);
+ TEST_ASSERT_NOT_EQUAL(-1, r);
+ TEST_ASSERT_EQUAL(new_size, r);
+
+ TEST_ASSERT_EQUAL('Z', expanded_output[strlen(input) - 1]); // 'Z' character
+ TEST_ASSERT_EQUAL('\0', expanded_output[sizeof(input) + trunc_add - 3]); // zeroed expanded space
+ TEST_ASSERT_EQUAL('\0', expanded_output[sizeof(input) + trunc_add - 2]); // zeroed expanded space
+ TEST_ASSERT_EQUAL(42, expanded_output[sizeof(input) + trunc_add - 1]); // 42 set with memset, end of the array
+ } else {
+ TEST_ASSERT_EQUAL(-1, ftruncate(fd, strlen(input) + 1));
+ TEST_ASSERT_EQUAL(errno, EPERM);
+
+ TEST_ASSERT_EQUAL(-1, ftruncate(fd, -1));
+ TEST_ASSERT_EQUAL(errno, EINVAL);
+ }
// Truncating should succeed
const char truncated_1[] = "ABCDEFGHIJ";
truncated_len = strlen(truncated_1);
TEST_ASSERT_EQUAL(0, ftruncate(fd, truncated_len));
+
+ fstat(fd, &st);
+ size = st.st_size;
+ TEST_ASSERT_EQUAL(truncated_len, size);
+
TEST_ASSERT_EQUAL(0, close(fd));
// open file for reading and validate the content
@@ -367,25 +444,35 @@ void test_fatfs_ftruncate_file(const char* filename)
// further truncate the file
fd = open(filename, O_WRONLY);
TEST_ASSERT_NOT_EQUAL(-1, fd);
- // Once truncated, the new file size should be the basis
- // whether truncation should succeed or not
- TEST_ASSERT_EQUAL(-1, ftruncate(fd, truncated_len + 1));
- TEST_ASSERT_EQUAL(EPERM, errno);
- TEST_ASSERT_EQUAL(-1, ftruncate(fd, strlen(input)));
- TEST_ASSERT_EQUAL(EPERM, errno);
+ if (allow_expanding_files) {
+ TEST_ASSERT_EQUAL(0, ftruncate(fd, truncated_len + 1));
+ } else {
+ // Once truncated, the new file size should be the basis
+ // whether truncation should succeed or not when `allow_expanding_files == false`
+ TEST_ASSERT_EQUAL(-1, ftruncate(fd, truncated_len + 1));
+ TEST_ASSERT_EQUAL(EPERM, errno);
- TEST_ASSERT_EQUAL(-1, ftruncate(fd, strlen(input) + 1));
- TEST_ASSERT_EQUAL(EPERM, errno);
+ TEST_ASSERT_EQUAL(-1, ftruncate(fd, strlen(input)));
+ TEST_ASSERT_EQUAL(EPERM, errno);
- TEST_ASSERT_EQUAL(-1, ftruncate(fd, -1));
- TEST_ASSERT_EQUAL(EINVAL, errno);
+ TEST_ASSERT_EQUAL(-1, ftruncate(fd, strlen(input) + 1));
+ TEST_ASSERT_EQUAL(EPERM, errno);
+
+ TEST_ASSERT_EQUAL(-1, ftruncate(fd, -1));
+ TEST_ASSERT_EQUAL(EINVAL, errno);
+ }
// Truncating a truncated file should succeed
const char truncated_2[] = "ABCDE";
truncated_len = strlen(truncated_2);
TEST_ASSERT_EQUAL(0, ftruncate(fd, truncated_len));
+
+ fstat(fd, &st);
+ size = st.st_size;
+ TEST_ASSERT_EQUAL(truncated_len, size);
+
TEST_ASSERT_EQUAL(0, close(fd));
// open file for reading and validate the content
@@ -556,7 +643,7 @@ void test_fatfs_utime(const char* filename, const char* root_dir)
TEST_ASSERT_EQUAL(0, stat(filename, &achieved_stat));
TEST_ASSERT_EQUAL_UINT32(desired_time.modtime, achieved_stat.st_mtime);
- //WARNING: it has the Unix Millenium bug (Y2K38)
+ //WARNING: it has the Unix Millennium bug (Y2K38)
// 00:00:00. January 1st, 1970 - FATFS cannot handle years before 1980
desired_tm.tm_mon = 1 - 1;
@@ -670,6 +757,65 @@ void test_fatfs_can_opendir(const char* path)
unlink(name_dir_file);
}
+void test_fatfs_readdir_stat(const char* dir_prefix)
+{
+ char name_dir_file[64];
+ char name_dir_stat[64];
+ int file_num = 25;
+
+ rmdir(dir_prefix);
+ TEST_ASSERT_EQUAL(0, mkdir(dir_prefix, 0755));
+
+ for(int i=0;i<file_num;i++) {
+ snprintf(name_dir_file, sizeof(name_dir_file), "%s/boo_%d.bin", dir_prefix,i);
+ test_fatfs_create_file_with_text(name_dir_file, fatfs_test_hello_str);
+ }
+
+ printf("Start counting\n");
+ int64_t start = esp_timer_get_time();
+ DIR* dir = opendir(dir_prefix);
+ TEST_ASSERT_NOT_NULL(dir);
+ struct stat st;
+ struct dirent* de;
+ uint32_t dir_size = 0;
+
+ // Call readdir before stat function and record the time needed to calculate the directory size
+ while(1) {
+ de = readdir(dir);
+ if (!de) {
+ break;
+ }
+ snprintf(name_dir_stat, sizeof(dir_prefix)+sizeof(de->d_name), "%s/%s", dir_prefix, de->d_name);
+ TEST_ASSERT_EQUAL(0, stat(name_dir_stat, &st));
+ dir_size += st.st_size;
+ }
+ TEST_ASSERT_EQUAL(0, closedir(dir));
+ int64_t end = esp_timer_get_time();
+ int64_t total_time_readdir = end-start;
+ printf("Time in us for calculating directory size by calling readdir first and then stat func: %lld \n",total_time_readdir);
+ printf("Size of the directory %s is %"PRIu32"bytes\n", dir_prefix, dir_size);
+ TEST_ASSERT_EQUAL(file_num*14, dir_size); //each file size is 14 bytes
+
+ // Call stat function directly and record the time needed to calculate the directory size
+ dir_size = 0;
+ start = esp_timer_get_time();
+ for(int i=0;i<file_num;i++) {
+ snprintf(name_dir_file, sizeof(name_dir_file), "%s/boo_%d.bin", dir_prefix, i);
+ TEST_ASSERT_EQUAL(0, stat(name_dir_file, &st));
+ dir_size += st.st_size;
+ }
+ end = esp_timer_get_time();
+ int64_t total_time_stat = end-start;
+ printf("Time in us for calculating directory size by calling stat func: %lld \n",total_time_stat);
+ printf("Size of the directory %s is %"PRIu32"bytes\n", dir_prefix, dir_size);
+ TEST_ASSERT_EQUAL(file_num*14, dir_size); //each file size is 14 bytes
+
+ for(int i=0;i<file_num;i++) {
+ snprintf(name_dir_file, sizeof(name_dir_file), "%s/boo_%d.bin", dir_prefix,i);
+ unlink(name_dir_file);
+ }
+}
+
void test_fatfs_opendir_readdir_rewinddir(const char* dir_prefix)
{
char name_dir_inner_file[64];
@@ -902,7 +1048,7 @@ void test_fatfs_concurrent(const char* filename_prefix)
printf("writing f1 and f2\n");
const int cpuid_0 = 0;
- const int cpuid_1 = portNUM_PROCESSORS - 1;
+ const int cpuid_1 = CONFIG_FREERTOS_NUMBER_OF_CORES - 1;
const int stack_size = 4096;
xTaskCreatePinnedToCore(&read_write_task, "rw1", stack_size, &args1, 3, NULL, cpuid_0);
xTaskCreatePinnedToCore(&read_write_task, "rw2", stack_size, &args2, 3, NULL, cpuid_1);
@@ -1011,3 +1157,28 @@ void test_fatfs_info(const char* base_path, const char* filepath)
ESP_LOGD("fatfs info", "total_bytes=%llu, free_bytes_after_delete=%llu", total_bytes, free_bytes_new);
TEST_ASSERT_EQUAL(free_bytes, free_bytes_new);
}
+
+#if FF_USE_EXPAND
+void test_fatfs_create_contiguous_file(const char* base_path, const char* full_path)
+{
+ size_t desired_file_size = 64;
+
+ // Don't check for errors, file may not exist at first
+ remove(full_path); // esp_vfs_fat_create_contiguous_file will fail if the file already exists
+
+ esp_err_t err = esp_vfs_fat_create_contiguous_file(base_path, full_path, desired_file_size, true);
+ TEST_ASSERT_EQUAL(ESP_OK, err);
+
+ struct stat st;
+ size_t size;
+
+ stat(full_path, &st);
+ size = st.st_size;
+ TEST_ASSERT_EQUAL(desired_file_size, size);
+
+ bool is_contiguous = false;
+ err = esp_vfs_fat_test_contiguous_file(base_path, full_path, &is_contiguous);
+ TEST_ASSERT_EQUAL(ESP_OK, err);
+ TEST_ASSERT_TRUE(is_contiguous);
+}
+#endif
diff --git a/lib/fatfs/test_apps/test_fatfs_common/test_fatfs_common.h b/lib/fatfs/test_apps/test_fatfs_common/test_fatfs_common.h
index dad352e2..1a8d56af 100644
--- a/lib/fatfs/test_apps/test_fatfs_common/test_fatfs_common.h
+++ b/lib/fatfs/test_apps/test_fatfs_common/test_fatfs_common.h
@@ -45,9 +45,9 @@ void test_fatfs_open_max_files(const char* filename_prefix, size_t files_count);
void test_fatfs_lseek(const char* filename);
-void test_fatfs_truncate_file(const char* path);
+void test_fatfs_truncate_file(const char* path, bool allow_expanding_files);
-void test_fatfs_ftruncate_file(const char* path);
+void test_fatfs_ftruncate_file(const char* path, bool allow_expanding_files);
void test_fatfs_stat(const char* filename, const char* root_dir);
@@ -76,3 +76,9 @@ void test_leading_spaces(void);
void test_fatfs_rw_speed(const char* filename, void* buf, size_t buf_size, size_t file_size, bool write);
void test_fatfs_info(const char* base_path, const char* filepath);
+
+#if FF_USE_EXPAND
+void test_fatfs_create_contiguous_file(const char* base_path, const char* full_path);
+#endif
+
+void test_fatfs_readdir_stat(const char* path);
diff --git a/lib/fatfs/test_fatfs_host/Makefile b/lib/fatfs/test_fatfs_host/Makefile
deleted file mode 100644
index 2e63322c..00000000
--- a/lib/fatfs/test_fatfs_host/Makefile
+++ /dev/null
@@ -1,106 +0,0 @@
-ifndef COMPONENT
-COMPONENT := fatfs
-endif
-
-COMPONENT_LIB := lib$(COMPONENT).a
-TEST_PROGRAM := test_$(COMPONENT)
-
-STUBS_LIB_DIR := ../../../components/spi_flash/sim/stubs
-STUBS_LIB_BUILD_DIR := $(STUBS_LIB_DIR)/build
-STUBS_LIB := libstubs.a
-
-SPI_FLASH_SIM_DIR := ../../../components/spi_flash/sim
-SPI_FLASH_SIM_BUILD_DIR := $(SPI_FLASH_SIM_DIR)/build
-SPI_FLASH_SIM_LIB := libspi_flash.a
-
-WEAR_LEVELLING_DIR := ../../../components/wear_levelling/test_wl_host
-WEAR_LEVELLING_BUILD_DIR := $(WEAR_LEVELLING_DIR)/build
-WEAR_LEVELLING_LIB := libwl.a
-
-include Makefile.files
-
-all: test
-
-ifndef SDKCONFIG
-SDKCONFIG_DIR := $(dir $(realpath sdkconfig/sdkconfig.h))
-SDKCONFIG := $(SDKCONFIG_DIR)sdkconfig.h
-else
-SDKCONFIG_DIR := $(dir $(realpath $(SDKCONFIG)))
-endif
-
-INCLUDE_FLAGS := $(addprefix -I, $(INCLUDE_DIRS) $(SDKCONFIG_DIR) ../../../tools/catch)
-
-CPPFLAGS += $(INCLUDE_FLAGS) -g -m32
-CXXFLAGS += $(INCLUDE_FLAGS) -std=c++11 -g -m32
-
-# Build libraries that this component is dependent on
-$(STUBS_LIB_BUILD_DIR)/$(STUBS_LIB): force
- $(MAKE) -C $(STUBS_LIB_DIR) lib SDKCONFIG=$(SDKCONFIG)
-
-$(SPI_FLASH_SIM_BUILD_DIR)/$(SPI_FLASH_SIM_LIB): force
- $(MAKE) -C $(SPI_FLASH_SIM_DIR) lib SDKCONFIG=$(SDKCONFIG)
-
-$(WEAR_LEVELLING_BUILD_DIR)/$(WEAR_LEVELLING_LIB): force
- $(MAKE) -C $(WEAR_LEVELLING_DIR) lib SDKCONFIG=$(SDKCONFIG)
-
-# Create target for building this component as a library
-CFILES := $(filter %.c, $(SOURCE_FILES))
-CPPFILES := $(filter %.cpp, $(SOURCE_FILES))
-
-CTARGET = ${2}/$(patsubst %.c,%.o,$(notdir ${1}))
-CPPTARGET = ${2}/$(patsubst %.cpp,%.o,$(notdir ${1}))
-
-ifndef BUILD_DIR
-BUILD_DIR := build
-endif
-
-OBJ_FILES := $(addprefix $(BUILD_DIR)/, $(filter %.o, $(notdir $(SOURCE_FILES:.cpp=.o) $(SOURCE_FILES:.c=.o))))
-
-define COMPILE_C
-$(call CTARGET, ${1}, $(BUILD_DIR)) : ${1} $(SDKCONFIG)
- mkdir -p $(BUILD_DIR)
- $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $(call CTARGET, ${1}, $(BUILD_DIR)) ${1}
-endef
-
-define COMPILE_CPP
-$(call CPPTARGET, ${1}, $(BUILD_DIR)) : ${1} $(SDKCONFIG)
- mkdir -p $(BUILD_DIR)
- $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $(call CPPTARGET, ${1}, $(BUILD_DIR)) ${1}
-endef
-
-$(BUILD_DIR)/$(COMPONENT_LIB): $(OBJ_FILES) $(SDKCONFIG)
- mkdir -p $(BUILD_DIR)
- $(AR) rcs $@ $^
-
-lib: $(BUILD_DIR)/$(COMPONENT_LIB)
-
-$(foreach cfile, $(CFILES), $(eval $(call COMPILE_C, $(cfile))))
-$(foreach cxxfile, $(CPPFILES), $(eval $(call COMPILE_CPP, $(cxxfile))))
-
-# Create target for building this component as a test
-TEST_SOURCE_FILES = \
- test_fatfs.cpp \
- main.cpp \
-
-TEST_OBJ_FILES = $(filter %.o, $(TEST_SOURCE_FILES:.cpp=.o) $(TEST_SOURCE_FILES:.c=.o))
-
-$(TEST_PROGRAM): lib $(TEST_OBJ_FILES) $(WEAR_LEVELLING_BUILD_DIR)/$(WEAR_LEVELLING_LIB) $(SPI_FLASH_SIM_BUILD_DIR)/$(SPI_FLASH_SIM_LIB) $(STUBS_LIB_BUILD_DIR)/$(STUBS_LIB) partition_table.bin $(SDKCONFIG)
- g++ $(LDFLAGS) $(CXXFLAGS) -o $@ $(TEST_OBJ_FILES) -L$(BUILD_DIR) -l:$(COMPONENT_LIB) -L$(WEAR_LEVELLING_BUILD_DIR) -l:$(WEAR_LEVELLING_LIB) -L$(SPI_FLASH_SIM_BUILD_DIR) -l:$(SPI_FLASH_SIM_LIB) -L$(STUBS_LIB_BUILD_DIR) -l:$(STUBS_LIB)
-
-test: $(TEST_PROGRAM)
- ./$(TEST_PROGRAM)
-
-# Create other necessary targets
-partition_table.bin: partition_table.csv
- python ../../../components/partition_table/gen_esp32part.py --verify $< $@
-
-force:
-
-# Create target to cleanup files
-clean:
- $(MAKE) -C $(STUBS_LIB_DIR) clean
- $(MAKE) -C $(SPI_FLASH_SIM_DIR) clean
- $(MAKE) -C $(WEAR_LEVELLING_DIR) clean
- rm -f $(OBJ_FILES) $(TEST_OBJ_FILES) $(TEST_PROGRAM) $(COMPONENT_LIB) partition_table.bin
-
-.PHONY: all lib test clean force
diff --git a/lib/fatfs/test_fatfs_host/Makefile.files b/lib/fatfs/test_fatfs_host/Makefile.files
deleted file mode 100644
index f0c7bbf7..00000000
--- a/lib/fatfs/test_fatfs_host/Makefile.files
+++ /dev/null
@@ -1,44 +0,0 @@
-SOURCE_FILES := \
- $(addprefix ../src/, \
- ff.c \
- ffunicode.c \
- ) \
- $(addprefix ../diskio/,\
- diskio.c \
- diskio_wl.c \
- ) \
- ../port/linux/ffsystem.c
-
-INCLUDE_DIRS := \
- . \
- ../diskio \
- ../src \
- $(addprefix ../../spi_flash/sim/stubs/, \
- app_update/include \
- driver/include \
- freertos/include \
- newlib/include \
- sdmmc/include \
- vfs/include \
- ) \
- $(addprefix ../../../components/, \
- esp_rom/include \
- esp_hw_support/include \
- esp_hw_support/include/soc \
- esp_system/include \
- log/include \
- xtensa/include \
- xtensa/esp32/include \
- soc/esp32/include \
- heap/include \
- soc/include \
- esp32/include \
- esp_common/include \
- bootloader_support/include \
- bootloader_support/bootloader_flash/include \
- app_update/include \
- hal/include \
- spi_flash/include \
- wear_levelling/include \
- esp_partition/include \
- )
diff --git a/lib/fatfs/test_fatfs_host/component.mk b/lib/fatfs/test_fatfs_host/component.mk
deleted file mode 100644
index 928f5343..00000000
--- a/lib/fatfs/test_fatfs_host/component.mk
+++ /dev/null
@@ -1,17 +0,0 @@
-include $(COMPONENT_PATH)/Makefile.files
-
-COMPONENT_OWNBUILDTARGET := 1
-COMPONENT_OWNCLEANTARGET := 1
-
-COMPONENT_ADD_INCLUDEDIRS := $(INCLUDE_DIRS)
-
-.PHONY: build
-build: $(SDKCONFIG_HEADER)
- $(MAKE) -C $(COMPONENT_PATH) lib SDKCONFIG=$(SDKCONFIG_HEADER) BUILD_DIR=$(COMPONENT_BUILD_DIR) COMPONENT=$(COMPONENT_NAME)
-
-CLEAN_FILES := component_project_vars.mk
-.PHONY: clean
-clean:
- $(summary) RM $(CLEAN_FILES)
- rm -f $(CLEAN_FILES)
- $(MAKE) -C $(COMPONENT_PATH) clean SDKCONFIG=$(SDKCONFIG_HEADER) BUILD_DIR=$(COMPONENT_BUILD_DIR) COMPONENT=$(COMPONENT_NAME)
diff --git a/lib/fatfs/test_fatfs_host/main.cpp b/lib/fatfs/test_fatfs_host/main.cpp
deleted file mode 100644
index 0c7c351f..00000000
--- a/lib/fatfs/test_fatfs_host/main.cpp
+++ /dev/null
@@ -1,2 +0,0 @@
-#define CATCH_CONFIG_MAIN
-#include "catch.hpp"
diff --git a/lib/fatfs/test_fatfs_host/partition_table.csv b/lib/fatfs/test_fatfs_host/partition_table.csv
deleted file mode 100644
index 1c79321a..00000000
--- a/lib/fatfs/test_fatfs_host/partition_table.csv
+++ /dev/null
@@ -1,6 +0,0 @@
-# Name, Type, SubType, Offset, Size, Flags
-# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
-nvs, data, nvs, 0x9000, 0x6000,
-phy_init, data, phy, 0xf000, 0x1000,
-factory, app, factory, 0x10000, 1M,
-storage, data, fat, , 1M,
diff --git a/lib/fatfs/test_fatfs_host/test_fatfs.cpp b/lib/fatfs/test_fatfs_host/test_fatfs.cpp
deleted file mode 100644
index 2dae2471..00000000
--- a/lib/fatfs/test_fatfs_host/test_fatfs.cpp
+++ /dev/null
@@ -1,94 +0,0 @@
-#include <stdio.h>
-#include <string.h>
-
-#include "ff.h"
-#include "esp_partition.h"
-#include "wear_levelling.h"
-#include "diskio_impl.h"
-#include "diskio_wl.h"
-
-#include "catch.hpp"
-
-extern "C" void _spi_flash_init(const char* chip_size, size_t block_size, size_t sector_size, size_t page_size, const char* partition_bin);
-
-TEST_CASE("create volume, open file, write and read back data", "[fatfs]")
-{
- _spi_flash_init(CONFIG_ESPTOOLPY_FLASHSIZE, CONFIG_WL_SECTOR_SIZE * 16, CONFIG_WL_SECTOR_SIZE, CONFIG_WL_SECTOR_SIZE, "partition_table.bin");
-
- FRESULT fr_result;
- BYTE pdrv;
- FATFS fs;
- FIL file;
- UINT bw;
-
- esp_err_t esp_result;
-
- const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, "storage");
-
- // Mount wear-levelled partition
- wl_handle_t wl_handle;
- esp_result = wl_mount(partition, &wl_handle);
- REQUIRE(esp_result == ESP_OK);
-
- // Get a physical drive
- esp_result = ff_diskio_get_drive(&pdrv);
- REQUIRE(esp_result == ESP_OK);
-
- // Register physical drive as wear-levelled partition
- esp_result = ff_diskio_register_wl_partition(pdrv, wl_handle);
-
- // Create FAT volume on the entire disk
- LBA_t part_list[] = {100, 0, 0, 0};
- BYTE work_area[FF_MAX_SS];
-
- fr_result = f_fdisk(pdrv, part_list, work_area);
- REQUIRE(fr_result == FR_OK);
- const MKFS_PARM opt = {(BYTE)FM_ANY, 0, 0, 0, 0};
- fr_result = f_mkfs("", &opt, work_area, sizeof(work_area)); // Use default volume
-
- // Mount the volume
- fr_result = f_mount(&fs, "", 0);
- REQUIRE(fr_result == FR_OK);
-
- // Open, write and read data
- fr_result = f_open(&file, "test.txt", FA_OPEN_ALWAYS | FA_READ | FA_WRITE);
- REQUIRE(fr_result == FR_OK);
-
- // Generate data
- uint32_t data_size = 100000;
-
- char *data = (char*) malloc(data_size);
- char *read = (char*) malloc(data_size);
-
- for(uint32_t i = 0; i < data_size; i += sizeof(i))
- {
- *((uint32_t*)(data + i)) = i;
- }
-
- // Write generated data
- fr_result = f_write(&file, data, data_size, &bw);
- REQUIRE(fr_result == FR_OK);
- REQUIRE(bw == data_size);
-
- // Move to beginning of file
- fr_result = f_lseek(&file, 0);
- REQUIRE(fr_result == FR_OK);
-
- // Read written data
- fr_result = f_read(&file, read, data_size, &bw);
- REQUIRE(fr_result == FR_OK);
- REQUIRE(bw == data_size);
-
- REQUIRE(memcmp(data, read, data_size) == 0);
-
- // Close file
- fr_result = f_close(&file);
- REQUIRE(fr_result == FR_OK);
-
- // Unmount default volume
- fr_result = f_mount(0, "", 0);
- REQUIRE(fr_result == FR_OK);
-
- free(read);
- free(data);
-}
diff --git a/lib/fatfs/test_fatfsgen/test_fatfsgen.py b/lib/fatfs/test_fatfsgen/test_fatfsgen.py
index 5816991c..0e73ec29 100755
--- a/lib/fatfs/test_fatfsgen/test_fatfsgen.py
+++ b/lib/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/lib/fatfs/test_fatfsgen/test_fatfsparse.py b/lib/fatfs/test_fatfsgen/test_fatfsparse.py
index 15a7f3d8..43d62eb5 100755
--- a/lib/fatfs/test_fatfsgen/test_fatfsparse.py
+++ b/lib/fatfs/test_fatfsgen/test_fatfsparse.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
@@ -32,13 +32,13 @@ class FatFSGen(unittest.TestCase):
@staticmethod
def test_gen_parse() -> None:
run([
- 'python',
+ sys.executable,
f'{os.path.join(os.path.dirname(__file__), "..", "fatfsgen.py")}',
'output_data/tst_str'
], stderr=STDOUT)
- run(['python', '../fatfsgen.py', 'output_data/tst_str'], stderr=STDOUT)
- run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
+ run([sys.executable, '../fatfsgen.py', 'output_data/tst_str'], stderr=STDOUT)
+ run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
assert set(os.listdir('Espressif')) == {'TEST', 'TESTFILE'}
with open('Espressif/TESTFILE', 'rb') as in_:
@@ -59,7 +59,7 @@ class FatFSGen(unittest.TestCase):
fatfs.write_content(path_from_root=['WRITEF.TXT'], content=4096 * b'a' + b'a')
fatfs.write_filesystem('fatfs_image.img')
- run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
+ run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
with open('Espressif/WRITEF.TXT', 'rb') as in_:
assert in_.read() == 4097 * b'a'
@@ -74,7 +74,7 @@ class FatFSGen(unittest.TestCase):
fatfs.write_content(path_from_root=['TESTFOLD', 'A255'], content=b'last')
fatfs.write_filesystem('fatfs_image.img')
- run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
+ run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
assert set(os.listdir('Espressif')) == {'TESTFOLD'}
assert set(os.listdir('Espressif/TESTFOLD')) == {f'A{str(i).upper()}' for i in range(256)}
@@ -88,7 +88,7 @@ class FatFSGen(unittest.TestCase):
def test_empty_fat16() -> None:
fatfs = fatfsgen.FATFS(size=17 * 1024 * 1024)
fatfs.write_filesystem('fatfs_image.img')
- run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
+ run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
@staticmethod
def test_chaining_fat16() -> None:
@@ -96,7 +96,7 @@ class FatFSGen(unittest.TestCase):
fatfs.create_file('WRITEF', extension='TXT')
fatfs.write_content(path_from_root=['WRITEF.TXT'], content=4096 * b'a' + b'a')
fatfs.write_filesystem('fatfs_image.img')
- run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
+ run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
with open('Espressif/WRITEF.TXT', 'rb') as in_:
assert in_.read() == 4097 * b'a'
@@ -109,7 +109,7 @@ class FatFSGen(unittest.TestCase):
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('fatfs_image.img')
- run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
+ run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
assert set(os.listdir('Espressif')) == {'TESTFOLD'}
assert set(os.listdir('Espressif/TESTFOLD')) == {f'A{str(i).upper()}' for i in range(128)}
with open('Espressif/TESTFOLD/A0', 'rb') as in_:
@@ -134,20 +134,20 @@ class FatFSGen(unittest.TestCase):
}
generate_local_folder_structure(struct_, path_='.')
run([
- 'python',
+ sys.executable,
f'{os.path.join(os.path.dirname(__file__), "..", "fatfsgen.py")}',
'testf'
], stderr=STDOUT)
- run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
+ run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
assert compare_folders('testf', 'Espressif')
shutil.rmtree('Espressif', ignore_errors=True)
run([
- 'python',
+ sys.executable,
f'{os.path.join(os.path.dirname(__file__), "..", "wl_fatfsgen.py")}',
'testf'
], stderr=STDOUT)
- run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
+ run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
assert compare_folders('testf', 'Espressif')
def test_e2e_deeper(self) -> None:
@@ -173,20 +173,20 @@ class FatFSGen(unittest.TestCase):
generate_local_folder_structure(struct_, path_='.')
run([
- 'python',
+ sys.executable,
f'{os.path.join(os.path.dirname(__file__), "..", "fatfsgen.py")}',
'testf'
], stderr=STDOUT)
- run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
+ run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
assert compare_folders('testf', 'Espressif')
shutil.rmtree('Espressif', ignore_errors=True)
run([
- 'python',
+ sys.executable,
f'{os.path.join(os.path.dirname(__file__), "..", "wl_fatfsgen.py")}',
'testf'
], stderr=STDOUT)
- run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
+ run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
assert compare_folders('testf', 'Espressif')
def test_e2e_deeper_large(self) -> None:
@@ -229,20 +229,20 @@ class FatFSGen(unittest.TestCase):
}
generate_local_folder_structure(struct_, path_='.')
run([
- 'python',
+ sys.executable,
f'{os.path.join(os.path.dirname(__file__), "..", "fatfsgen.py")}',
'testf'
], stderr=STDOUT)
- run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
+ run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
assert compare_folders('testf', 'Espressif')
shutil.rmtree('Espressif', ignore_errors=True)
run([
- 'python',
+ sys.executable,
f'{os.path.join(os.path.dirname(__file__), "..", "wl_fatfsgen.py")}',
'testf'
], stderr=STDOUT)
- run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
+ run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
assert compare_folders('testf', 'Espressif')
def test_e2e_very_deep(self) -> None:
@@ -287,11 +287,11 @@ class FatFSGen(unittest.TestCase):
}
generate_local_folder_structure(struct_, path_='.')
run([
- 'python',
+ sys.executable,
f'{os.path.join(os.path.dirname(__file__), "..", "fatfsgen.py")}',
'testf'
], stderr=STDOUT)
- run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
+ run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
assert compare_folders('testf', 'Espressif')
def test_e2e_very_deep_long(self) -> None:
@@ -317,11 +317,11 @@ class FatFSGen(unittest.TestCase):
}
generate_local_folder_structure(struct_, path_='.')
run([
- 'python',
+ sys.executable,
f'{os.path.join(os.path.dirname(__file__), "..", "fatfsgen.py")}',
'testf', '--long_name_support'
], stderr=STDOUT)
- run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
+ run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
assert compare_folders('testf', 'Espressif')
def test_parse_long_name(self) -> None:
diff --git a/lib/fatfs/test_fatfsgen/test_wl_fatfsgen.py b/lib/fatfs/test_fatfsgen/test_wl_fatfsgen.py
index 2b3e28c7..3179441d 100755
--- a/lib/fatfs/test_fatfsgen/test_wl_fatfsgen.py
+++ b/lib/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,44 @@ 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()
+
+ 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'\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()
@@ -45,7 +93,7 @@ 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'\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/lib/fatfs/vfs/esp_vfs_fat.h b/lib/fatfs/vfs/esp_vfs_fat.h
index 3e7b165b..7b87818f 100644
--- a/lib/fatfs/vfs/esp_vfs_fat.h
+++ b/lib/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
*/
@@ -7,8 +7,7 @@
#pragma once
#include <stddef.h>
#include "esp_err.h"
-#include "driver/gpio.h"
-#include "driver/sdmmc_types.h"
+#include "sd_protocol_types.h"
#include "driver/sdspi_host.h"
#include "ff.h"
#include "wear_levelling.h"
@@ -18,9 +17,19 @@ extern "C" {
#endif
/**
+ * @brief Configuration structure for esp_vfs_fat_register
+ */
+typedef struct {
+ const char* base_path; /*!< Path prefix where FATFS should be registered, */
+ const char* fat_drive; /*!< FATFS drive specification; if only one drive is used, can be an empty string. */
+ size_t max_files; /*!< Maximum number of files which can be open at the same time. */
+} esp_vfs_fat_conf_t;
+
+/**
* @brief Register FATFS with VFS component
*
* This function registers given FAT drive in VFS, at the specified base path.
+ * Input arguments are held in esp_vfs_fat_conf_t structure.
* If only one drive is used, fat_drive argument can be an empty string.
* Refer to FATFS library documentation on how to specify FAT drive.
* This function also allocates FATFS structure which should be used for f_mount
@@ -30,17 +39,14 @@ extern "C" {
* POSIX and C standard library IO function with FATFS. You need to mount
* desired drive into FATFS separately.
*
- * @param base_path path prefix where FATFS should be registered
- * @param fat_drive FATFS drive specification; if only one drive is used, can be an empty string
- * @param max_files maximum number of files which can be open at the same time
+ * @param conf pointer to esp_vfs_fat_conf_t configuration structure
* @param[out] out_fs pointer to FATFS structure which can be used for FATFS f_mount call is returned via this argument.
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE if esp_vfs_fat_register was already called
* - ESP_ERR_NO_MEM if not enough memory or too many VFSes already registered
*/
-esp_err_t esp_vfs_fat_register(const char* base_path, const char* fat_drive,
- size_t max_files, FATFS** out_fs);
+esp_err_t esp_vfs_fat_register_cfg(const esp_vfs_fat_conf_t* conf, FATFS** out_fs);
/**
* @brief Un-register FATFS from VFS
@@ -96,8 +102,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;
@@ -208,6 +232,25 @@ esp_err_t esp_vfs_fat_sdmmc_unmount(void) __attribute__((deprecated("Please use
esp_err_t esp_vfs_fat_sdcard_unmount(const char* base_path, sdmmc_card_t *card);
/**
+ * @brief Format FAT filesystem with given configuration
+ *
+ * @note
+ * This API should be only called when the FAT is already mounted.
+ *
+ * @param base_path Path where partition should be registered (e.g. "/sdcard")
+ * @param card Pointer to the card handle, which should be initialised by calling `esp_vfs_fat_sdspi_mount` first
+ * @param cfg Pointer to structure with extra parameters for formatting FATFS (only relevant fields are used).
+ * If NULL, the previous configuration will be used.
+ *
+ * @return
+ * - ESP_OK
+ * - ESP_ERR_INVALID_STATE: FAT partition isn't mounted, call esp_vfs_fat_sdmmc_mount or esp_vfs_fat_sdspi_mount first
+ * - ESP_ERR_NO_MEM: if memory can not be allocated
+ * - ESP_FAIL: fail to format it, or fail to mount back
+ */
+esp_err_t esp_vfs_fat_sdcard_format_cfg(const char *base_path, sdmmc_card_t *card, esp_vfs_fat_mount_config_t *cfg);
+
+/**
* @brief Format FAT filesystem
*
* @note
@@ -268,6 +311,27 @@ esp_err_t esp_vfs_fat_spiflash_mount_rw_wl(const char* base_path,
esp_err_t esp_vfs_fat_spiflash_unmount_rw_wl(const char* base_path, wl_handle_t wl_handle);
/**
+ * @brief Format FAT filesystem with given configuration
+ *
+ * @note
+ * This API can be called when the FAT is mounted / not mounted.
+ * If this API is called when the FAT isn't mounted (by calling esp_vfs_fat_spiflash_mount_rw_wl),
+ * this API will first mount the FAT then format it, then restore back to the original state.
+ *
+ * @param base_path Path where partition should be registered (e.g. "/spiflash")
+ * @param partition_label Label of the partition which should be used
+ * @param cfg Pointer to structure with extra parameters for formatting FATFS (only relevant fields are used).
+ * If NULL and mounted the previous configuration will be used.
+ * If NULL and unmounted the default configuration will be used.
+ *
+ * @return
+ * - ESP_OK
+ * - ESP_ERR_NO_MEM: if memory can not be allocated
+ * - Other errors from esp_vfs_fat_spiflash_mount_rw_wl
+ */
+esp_err_t esp_vfs_fat_spiflash_format_cfg_rw_wl(const char* base_path, const char* partition_label, esp_vfs_fat_mount_config_t *cfg);
+
+/**
* @brief Format FAT filesystem
*
* @note
@@ -337,8 +401,46 @@ esp_err_t esp_vfs_fat_spiflash_unmount_ro(const char* base_path, const char* par
*/
esp_err_t esp_vfs_fat_info(const char* base_path, uint64_t* out_total_bytes, uint64_t* out_free_bytes);
+/**
+ * @brief Create a file with contiguous space at given path
+ *
+ * @note The file cannot exist before calling this function (or the file size has to be 0)
+ * For more information see documentation for `f_expand` from FATFS library
+ *
+ * @param base_path Base path of the partition examined (e.g. "/spiflash")
+ * @param full_path Full path of the file (e.g. "/spiflash/ABC.TXT")
+ * @param size File size expanded to, number of bytes in size to prepare or allocate for the file
+ * @param alloc_now True == allocate space now, false == prepare to allocate -- see `f_expand` from FATFS
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_INVALID_ARG if invalid arguments (e.g. any of arguments are NULL or size lower or equal to 0)
+ * - ESP_ERR_INVALID_STATE if partition not found
+ * - ESP_FAIL if another FRESULT error (saved in errno)
+ */
+esp_err_t esp_vfs_fat_create_contiguous_file(const char* base_path, const char* full_path, uint64_t size, bool alloc_now);
+
+/**
+ * @brief Test if a file is contiguous in the FAT filesystem
+ *
+ * @param base_path Base path of the partition examined (e.g. "/spiflash")
+ * @param full_path Full path of the file (e.g. "/spiflash/ABC.TXT")
+ * @param[out] is_contiguous True == allocate space now, false == prepare to allocate -- see `f_expand` from FATFS
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_INVALID_ARG if invalid arguments (e.g. any of arguments are NULL)
+ * - ESP_ERR_INVALID_STATE if partition not found
+ * - ESP_FAIL if another FRESULT error (saved in errno)
+ */
+esp_err_t esp_vfs_fat_test_contiguous_file(const char* base_path, const char* full_path, bool* is_contiguous);
+
/** @cond */
/**
+ * @deprecated Please use `esp_vfs_fat_register_cfg` instead
+ */
+esp_err_t esp_vfs_fat_register(const char* base_path, const char* fat_drive,
+ size_t max_files, FATFS** out_fs);
+
+/**
* @deprecated Please use `esp_vfs_fat_spiflash_mount_rw_wl` instead
*/
esp_err_t esp_vfs_fat_spiflash_mount(const char* base_path,
diff --git a/lib/fatfs/vfs/vfs_fat.c b/lib/fatfs/vfs/vfs_fat.c
index 4ae04c3d..b0b32fa9 100644
--- a/lib/fatfs/vfs/vfs_fat.c
+++ b/lib/fatfs/vfs/vfs_fat.c
@@ -1,21 +1,40 @@
/*
- * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
+ * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <stdlib.h>
+#include <limits.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/errno.h>
#include <sys/fcntl.h>
#include <sys/lock.h>
+#include "esp_vfs_fat.h"
#include "esp_vfs.h"
#include "esp_log.h"
#include "ff.h"
#include "diskio_impl.h"
+#define F_WRITE_MALLOC_ZEROING_BUF_SIZE_LIMIT 512
+
+#ifdef CONFIG_VFS_SUPPORT_DIR
+struct cached_data{
+#if FF_USE_LFN
+ char file_path[FILENAME_MAX+1+FF_LFN_BUF+1]; //FILENAME_MAX+1: for dir_path, FF_LFN_BUF+1: for file name
+#else
+ char file_path[FILENAME_MAX+1+FF_SFN_BUF+1]; //FILENAME_MAX+1: for dir_path, FF_LFN_BUF+1: for file name
+#endif
+ FILINFO fileinfo;
+};
+#endif // CONFIG_VFS_SUPPORT_DIR
+
+#if !defined(FILENAME_MAX)
+#define FILENAME_MAX 255
+#endif
+
typedef struct {
char fat_drive[8]; /* FAT drive name */
char base_path[ESP_VFS_PATH_MAX]; /* base path in VFS where partition is registered */
@@ -24,8 +43,12 @@ typedef struct {
FATFS fs; /* fatfs library FS structure */
char tmp_path_buf[FILENAME_MAX+3]; /* temporary buffer used to prepend drive name to the path */
char tmp_path_buf2[FILENAME_MAX+3]; /* as above; used in functions which take two path arguments */
- bool *o_append; /* O_APPEND is stored here for each max_files entries (because O_APPEND is not compatible with FA_OPEN_APPEND) */
- FIL files[0]; /* array with max_files entries; must be the final member of the structure */
+ uint32_t *flags; /* file descriptor flags, array of max_files size */
+#ifdef CONFIG_VFS_SUPPORT_DIR
+ char dir_path[FILENAME_MAX]; /* variable to store path of opened directory*/
+ struct cached_data cached_fileinfo;
+#endif
+ FIL files[]; /* array with max_files entries; must be the final member of the structure */
} vfs_fat_ctx_t;
typedef struct {
@@ -66,6 +89,7 @@ static int vfs_fat_open(void* ctx, const char * path, int flags, int mode);
static int vfs_fat_close(void* ctx, int fd);
static int vfs_fat_fstat(void* ctx, int fd, struct stat * st);
static int vfs_fat_fsync(void* ctx, int fd);
+static int vfs_fat_fcntl(void* ctx, int fd, int cmd, int arg);
#ifdef CONFIG_VFS_SUPPORT_DIR
static int vfs_fat_stat(void* ctx, const char * path, struct stat * st);
static int vfs_fat_link(void* ctx, const char* n1, const char* n2);
@@ -90,6 +114,7 @@ static vfs_fat_ctx_t* s_fat_ctxs[FF_VOLUMES] = { NULL };
//backwards-compatibility with esp_vfs_fat_unregister()
static vfs_fat_ctx_t* s_fat_ctx = NULL;
+
static size_t find_context_index_by_path(const char* base_path)
{
for(size_t i=0; i<FF_VOLUMES; i++) {
@@ -112,7 +137,54 @@ static size_t find_unused_context_index(void)
esp_err_t esp_vfs_fat_register(const char* base_path, const char* fat_drive, size_t max_files, FATFS** out_fs)
{
- size_t ctx = find_context_index_by_path(base_path);
+ esp_vfs_fat_conf_t conf = {
+ .base_path = base_path,
+ .fat_drive = fat_drive,
+ .max_files = max_files,
+ };
+ return esp_vfs_fat_register_cfg(&conf, out_fs);
+}
+
+#ifdef CONFIG_VFS_SUPPORT_DIR
+static const esp_vfs_dir_ops_t s_vfs_fat_dir = {
+ .stat_p = &vfs_fat_stat,
+ .link_p = &vfs_fat_link,
+ .unlink_p = &vfs_fat_unlink,
+ .rename_p = &vfs_fat_rename,
+ .opendir_p = &vfs_fat_opendir,
+ .closedir_p = &vfs_fat_closedir,
+ .readdir_p = &vfs_fat_readdir,
+ .readdir_r_p = &vfs_fat_readdir_r,
+ .seekdir_p = &vfs_fat_seekdir,
+ .telldir_p = &vfs_fat_telldir,
+ .mkdir_p = &vfs_fat_mkdir,
+ .rmdir_p = &vfs_fat_rmdir,
+ .access_p = &vfs_fat_access,
+ .truncate_p = &vfs_fat_truncate,
+ .ftruncate_p = &vfs_fat_ftruncate,
+ .utime_p = &vfs_fat_utime,
+};
+#endif // CONFIG_VFS_SUPPORT_DIR
+
+static const esp_vfs_fs_ops_t s_vfs_fat = {
+ .write_p = &vfs_fat_write,
+ .lseek_p = &vfs_fat_lseek,
+ .read_p = &vfs_fat_read,
+ .pread_p = &vfs_fat_pread,
+ .pwrite_p = &vfs_fat_pwrite,
+ .open_p = &vfs_fat_open,
+ .close_p = &vfs_fat_close,
+ .fstat_p = &vfs_fat_fstat,
+ .fcntl_p = &vfs_fat_fcntl,
+ .fsync_p = &vfs_fat_fsync,
+#ifdef CONFIG_VFS_SUPPORT_DIR
+ .dir = &s_vfs_fat_dir,
+#endif // CONFIG_VFS_SUPPORT_DIR
+};
+
+esp_err_t esp_vfs_fat_register_cfg(const esp_vfs_fat_conf_t* conf, FATFS** out_fs)
+{
+ size_t ctx = find_context_index_by_path(conf->base_path);
if (ctx < FF_VOLUMES) {
return ESP_ERR_INVALID_STATE;
}
@@ -122,55 +194,30 @@ esp_err_t esp_vfs_fat_register(const char* base_path, const char* fat_drive, siz
return ESP_ERR_NO_MEM;
}
- const esp_vfs_t vfs = {
- .flags = ESP_VFS_FLAG_CONTEXT_PTR,
- .write_p = &vfs_fat_write,
- .lseek_p = &vfs_fat_lseek,
- .read_p = &vfs_fat_read,
- .pread_p = &vfs_fat_pread,
- .pwrite_p = &vfs_fat_pwrite,
- .open_p = &vfs_fat_open,
- .close_p = &vfs_fat_close,
- .fstat_p = &vfs_fat_fstat,
- .fsync_p = &vfs_fat_fsync,
-#ifdef CONFIG_VFS_SUPPORT_DIR
- .stat_p = &vfs_fat_stat,
- .link_p = &vfs_fat_link,
- .unlink_p = &vfs_fat_unlink,
- .rename_p = &vfs_fat_rename,
- .opendir_p = &vfs_fat_opendir,
- .closedir_p = &vfs_fat_closedir,
- .readdir_p = &vfs_fat_readdir,
- .readdir_r_p = &vfs_fat_readdir_r,
- .seekdir_p = &vfs_fat_seekdir,
- .telldir_p = &vfs_fat_telldir,
- .mkdir_p = &vfs_fat_mkdir,
- .rmdir_p = &vfs_fat_rmdir,
- .access_p = &vfs_fat_access,
- .truncate_p = &vfs_fat_truncate,
- .ftruncate_p = &vfs_fat_ftruncate,
- .utime_p = &vfs_fat_utime,
-#endif // CONFIG_VFS_SUPPORT_DIR
- };
+ size_t max_files = conf->max_files;
+ if (max_files < 1) {
+ max_files = 1; // ff_memalloc(max_files * sizeof(bool)) below will fail if max_files == 0
+ }
+
size_t ctx_size = sizeof(vfs_fat_ctx_t) + max_files * sizeof(FIL);
vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ff_memalloc(ctx_size);
if (fat_ctx == NULL) {
return ESP_ERR_NO_MEM;
}
memset(fat_ctx, 0, ctx_size);
- fat_ctx->o_append = ff_memalloc(max_files * sizeof(bool));
- if (fat_ctx->o_append == NULL) {
+ fat_ctx->flags = ff_memalloc(max_files * sizeof(*fat_ctx->flags));
+ if (fat_ctx->flags == NULL) {
free(fat_ctx);
return ESP_ERR_NO_MEM;
}
- memset(fat_ctx->o_append, 0, max_files * sizeof(bool));
+ memset(fat_ctx->flags, 0, max_files * sizeof(*fat_ctx->flags));
fat_ctx->max_files = max_files;
- strlcpy(fat_ctx->fat_drive, fat_drive, sizeof(fat_ctx->fat_drive) - 1);
- strlcpy(fat_ctx->base_path, base_path, sizeof(fat_ctx->base_path) - 1);
+ strlcpy(fat_ctx->fat_drive, conf->fat_drive, sizeof(fat_ctx->fat_drive) - 1);
+ strlcpy(fat_ctx->base_path, conf->base_path, sizeof(fat_ctx->base_path) - 1);
- esp_err_t err = esp_vfs_register(base_path, &vfs, fat_ctx);
+ esp_err_t err = esp_vfs_register_fs(conf->base_path, &s_vfs_fat, ESP_VFS_FLAG_CONTEXT_PTR | ESP_VFS_FLAG_STATIC, fat_ctx);
if (err != ESP_OK) {
- free(fat_ctx->o_append);
+ free(fat_ctx->flags);
free(fat_ctx);
return err;
}
@@ -198,7 +245,7 @@ esp_err_t esp_vfs_fat_unregister_path(const char* base_path)
return err;
}
_lock_close(&fat_ctx->lock);
- free(fat_ctx->o_append);
+ free(fat_ctx->flags);
free(fat_ctx);
s_fat_ctxs[ctx] = NULL;
return ESP_OK;
@@ -386,7 +433,7 @@ static int vfs_fat_open(void* ctx, const char * path, int flags, int mode)
// Other VFS drivers handles O_APPEND well (to the best of my knowledge),
// therefore this flag is stored here (at this VFS level) in order to save
// memory.
- fat_ctx->o_append[fd] = (flags & O_APPEND) == O_APPEND;
+ fat_ctx->flags[fd] = (flags & (O_APPEND | O_ACCMODE));
_lock_release(&fat_ctx->lock);
return fd;
}
@@ -396,10 +443,12 @@ static ssize_t vfs_fat_write(void* ctx, int fd, const void * data, size_t size)
vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
FIL* file = &fat_ctx->files[fd];
FRESULT res;
- if (fat_ctx->o_append[fd]) {
+ _lock_acquire(&fat_ctx->lock);
+ if (fat_ctx->flags[fd] & O_APPEND) {
if ((res = f_lseek(file, f_size(file))) != FR_OK) {
ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
errno = fresult_to_errno(res);
+ _lock_release(&fat_ctx->lock);
return -1;
}
}
@@ -407,18 +456,19 @@ static ssize_t vfs_fat_write(void* ctx, int fd, const void * data, size_t size)
res = f_write(file, data, size, &written);
if (((written == 0) && (size != 0)) && (res == 0)) {
errno = ENOSPC;
+ _lock_release(&fat_ctx->lock);
return -1;
}
if (res != FR_OK) {
ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
errno = fresult_to_errno(res);
if (written == 0) {
+ _lock_release(&fat_ctx->lock);
return -1;
}
}
#if CONFIG_FATFS_IMMEDIATE_FSYNC
- _lock_acquire(&fat_ctx->lock);
if (written > 0) {
res = f_sync(file);
if (res != FR_OK) {
@@ -428,9 +478,8 @@ static ssize_t vfs_fat_write(void* ctx, int fd, const void * data, size_t size)
return -1;
}
}
- _lock_release(&fat_ctx->lock);
#endif
-
+ _lock_release(&fat_ctx->lock);
return written;
}
@@ -549,10 +598,8 @@ pwrite_release:
static int vfs_fat_fsync(void* ctx, int fd)
{
vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
- _lock_acquire(&fat_ctx->lock);
FIL* file = &fat_ctx->files[fd];
FRESULT res = f_sync(file);
- _lock_release(&fat_ctx->lock);
int rc = 0;
if (res != FR_OK) {
ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
@@ -631,6 +678,26 @@ static int vfs_fat_fstat(void* ctx, int fd, struct stat * st)
return 0;
}
+static int vfs_fat_fcntl(void* ctx, int fd, int cmd, int arg)
+{
+ vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
+ switch (cmd) {
+ case F_GETFL:
+ return fat_ctx->flags[fd];
+ case F_SETFL:
+ fat_ctx->flags[fd] = arg;
+ return 0;
+ // no-ops:
+ case F_SETLK:
+ case F_SETLKW:
+ case F_GETLK:
+ return 0;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+}
+
#ifdef CONFIG_VFS_SUPPORT_DIR
static inline mode_t get_stat_mode(bool is_dir)
@@ -639,6 +706,32 @@ static inline mode_t get_stat_mode(bool is_dir)
((is_dir) ? S_IFDIR : S_IFREG);
}
+static void update_stat_struct(struct stat *st, FILINFO *info)
+{
+ memset(st, 0, sizeof(*st));
+ st->st_size = info->fsize;
+ st->st_mode = get_stat_mode((info->fattrib & AM_DIR) != 0);
+ fat_date_t fdate = { .as_int = info->fdate };
+ fat_time_t ftime = { .as_int = info->ftime };
+ struct tm tm = {
+ .tm_mday = fdate.mday,
+ .tm_mon = fdate.mon - 1, /* unlike tm_mday, tm_mon is zero-based */
+ .tm_year = fdate.year + 80,
+ .tm_sec = ftime.sec * 2,
+ .tm_min = ftime.min,
+ .tm_hour = ftime.hour,
+ /* FAT doesn't keep track if the time was DST or not, ask the C library
+ * to try to figure this out. Note that this may yield incorrect result
+ * in the hour before the DST comes in effect, when the local time can't
+ * be converted to UTC uniquely.
+ */
+ .tm_isdst = -1
+ };
+ st->st_mtime = mktime(&tm);
+ st->st_atime = 0;
+ st->st_ctime = 0;
+}
+
static int vfs_fat_stat(void* ctx, const char * path, struct stat * st)
{
if (strcmp(path, "/") == 0) {
@@ -651,6 +744,16 @@ static int vfs_fat_stat(void* ctx, const char * path, struct stat * st)
}
vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
+
+ //If fileinfo is already cached by readdir for requested filename,
+ //then return the same info else obtain fileinfo with f_stat function
+ if (strcmp(path, fat_ctx->cached_fileinfo.file_path) == 0) {
+ update_stat_struct(st, &fat_ctx->cached_fileinfo.fileinfo);
+ memset(&fat_ctx->cached_fileinfo, 0 ,sizeof(FILINFO));
+ return 0;
+ }
+
+ memset(&fat_ctx->cached_fileinfo, 0 ,sizeof(fat_ctx->cached_fileinfo));
_lock_acquire(&fat_ctx->lock);
prepend_drive_to_path(fat_ctx, &path, NULL);
FILINFO info;
@@ -662,28 +765,7 @@ static int vfs_fat_stat(void* ctx, const char * path, struct stat * st)
return -1;
}
- memset(st, 0, sizeof(*st));
- st->st_size = info.fsize;
- st->st_mode = get_stat_mode((info.fattrib & AM_DIR) != 0);
- fat_date_t fdate = { .as_int = info.fdate };
- fat_time_t ftime = { .as_int = info.ftime };
- struct tm tm = {
- .tm_mday = fdate.mday,
- .tm_mon = fdate.mon - 1, /* unlike tm_mday, tm_mon is zero-based */
- .tm_year = fdate.year + 80,
- .tm_sec = ftime.sec * 2,
- .tm_min = ftime.min,
- .tm_hour = ftime.hour,
- /* FAT doesn't keep track if the time was DST or not, ask the C library
- * to try to figure this out. Note that this may yield incorrect result
- * in the hour before the DST comes in effect, when the local time can't
- * be converted to UTC uniquely.
- */
- .tm_isdst = -1
- };
- st->st_mtime = mktime(&tm);
- st->st_atime = 0;
- st->st_ctime = 0;
+ update_stat_struct(st, &info);
return 0;
}
@@ -707,74 +789,85 @@ static int vfs_fat_link(void* ctx, const char* n1, const char* n2)
vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
_lock_acquire(&fat_ctx->lock);
prepend_drive_to_path(fat_ctx, &n1, &n2);
- const size_t copy_buf_size = fat_ctx->fs.csize;
- FRESULT res;
+
+ FRESULT res = FR_OK;
+ int ret = 0;
+
FIL* pf1 = (FIL*) ff_memalloc(sizeof(FIL));
FIL* pf2 = (FIL*) ff_memalloc(sizeof(FIL));
+
+ const size_t copy_buf_size = fat_ctx->fs.csize;
void* buf = ff_memalloc(copy_buf_size);
if (buf == NULL || pf1 == NULL || pf2 == NULL) {
- _lock_release(&fat_ctx->lock);
ESP_LOGD(TAG, "alloc failed, pf1=%p, pf2=%p, buf=%p", pf1, pf2, buf);
- free(pf1);
- free(pf2);
- free(buf);
+ _lock_release(&fat_ctx->lock);
errno = ENOMEM;
- return -1;
+ ret = -1;
+ goto cleanup;
}
+
memset(pf1, 0, sizeof(*pf1));
memset(pf2, 0, sizeof(*pf2));
+
res = f_open(pf1, n1, FA_READ | FA_OPEN_EXISTING);
if (res != FR_OK) {
_lock_release(&fat_ctx->lock);
- goto fail1;
+ goto cleanup;
}
+
res = f_open(pf2, n2, FA_WRITE | FA_CREATE_NEW);
+
+#if !CONFIG_FATFS_LINK_LOCK
_lock_release(&fat_ctx->lock);
+#endif
+
if (res != FR_OK) {
- goto fail2;
+ goto close_old;
}
+
size_t size_left = f_size(pf1);
while (size_left > 0) {
size_t will_copy = (size_left < copy_buf_size) ? size_left : copy_buf_size;
size_t read;
res = f_read(pf1, buf, will_copy, &read);
if (res != FR_OK) {
- goto fail3;
+ goto close_both;
} else if (read != will_copy) {
res = FR_DISK_ERR;
- goto fail3;
+ goto close_both;
}
size_t written;
res = f_write(pf2, buf, will_copy, &written);
if (res != FR_OK) {
- goto fail3;
+ goto close_both;
} else if (written != will_copy) {
res = FR_DISK_ERR;
- goto fail3;
+ goto close_both;
}
size_left -= will_copy;
}
-#if CONFIG_FATFS_IMMEDIATE_FSYNC
- _lock_acquire(&fat_ctx->lock);
- res = f_sync(pf2);
+close_both:
+ f_close(pf2);
+
+close_old:
+ f_close(pf1);
+
+#if CONFIG_FATFS_LINK_LOCK
_lock_release(&fat_ctx->lock);
#endif
-fail3:
- f_close(pf2);
-fail2:
- f_close(pf1);
-fail1:
+cleanup:
free(buf);
free(pf2);
free(pf1);
- if (res != FR_OK) {
+ if (ret == 0 && res != FR_OK) {
ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
errno = fresult_to_errno(res);
return -1;
}
- return 0;
+
+ return ret;
}
static int vfs_fat_rename(void* ctx, const char *src, const char *dst)
@@ -795,6 +888,7 @@ static int vfs_fat_rename(void* ctx, const char *src, const char *dst)
static DIR* vfs_fat_opendir(void* ctx, const char* name)
{
vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
+ strlcpy(fat_ctx->dir_path, name, sizeof(fat_ctx->dir_path));
_lock_acquire(&fat_ctx->lock);
prepend_drive_to_path(fat_ctx, &name, NULL);
vfs_fat_dir_t* fat_dir = ff_memalloc(sizeof(vfs_fat_dir_t));
@@ -832,6 +926,9 @@ static int vfs_fat_closedir(void* ctx, DIR* pdir)
static struct dirent* vfs_fat_readdir(void* ctx, DIR* pdir)
{
+ assert(ctx);
+ assert(pdir);
+ vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir;
struct dirent* out_dirent;
int err = vfs_fat_readdir_r(ctx, pdir, &fat_dir->cur_dirent, &out_dirent);
@@ -839,6 +936,25 @@ static struct dirent* vfs_fat_readdir(void* ctx, DIR* pdir)
errno = err;
return NULL;
}
+
+ //Store the FILEINFO in the cached_fileinfo. If the stat function is invoked immediately afterward,
+ //the cached_fileinfo will provide the FILEINFO directly, as it was already obtained during the readdir operation.
+ //During directory size calculation, this optimization can reduce the computation time.
+ memset(&fat_ctx->cached_fileinfo, 0 ,sizeof(fat_ctx->cached_fileinfo));
+ if (strcmp(fat_ctx->dir_path, "/") == 0) {
+ snprintf(fat_ctx->cached_fileinfo.file_path, sizeof(fat_ctx->cached_fileinfo.file_path),
+ "/%s", fat_dir->filinfo.fname);
+ } else {
+ char *temp_file_path = (char*) ff_memalloc(sizeof(fat_ctx->cached_fileinfo.file_path));
+ if (temp_file_path == NULL) {
+ return out_dirent;
+ }
+ snprintf(temp_file_path, sizeof(fat_ctx->cached_fileinfo.file_path),
+ "%s/%s", fat_ctx->dir_path, fat_dir->filinfo.fname);
+ memcpy(fat_ctx->cached_fileinfo.file_path, temp_file_path, sizeof(fat_ctx->cached_fileinfo.file_path));
+ ff_memfree(temp_file_path);
+ }
+ fat_ctx->cached_fileinfo.fileinfo = fat_dir->filinfo;
return out_dirent;
}
@@ -881,8 +997,10 @@ static long vfs_fat_telldir(void* ctx, DIR* pdir)
static void vfs_fat_seekdir(void* ctx, DIR* pdir, long offset)
{
assert(pdir);
+ vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir;
FRESULT res;
+ _lock_acquire(&fat_ctx->lock);
if (offset < fat_dir->offset) {
res = f_rewinddir(&fat_dir->ffdir);
if (res != FR_OK) {
@@ -901,6 +1019,7 @@ static void vfs_fat_seekdir(void* ctx, DIR* pdir, long offset)
}
fat_dir->offset++;
}
+ _lock_release(&fat_ctx->lock);
}
static int vfs_fat_mkdir(void* ctx, const char* name, mode_t mode)
@@ -962,6 +1081,49 @@ static int vfs_fat_access(void* ctx, const char *path, int amode)
return ret;
}
+static FRESULT f_write_zero_mem(FIL* fp, FSIZE_t data_size, FSIZE_t buf_size, UINT* bytes_written)
+{
+ if (fp == NULL || data_size <= 0 || buf_size <= 0) {
+ return FR_INVALID_PARAMETER;
+ }
+
+ void* buf = ff_memalloc(buf_size);
+ if (buf == NULL) {
+ return FR_DISK_ERR;
+ }
+ memset(buf, 0, buf_size);
+
+ FRESULT res = FR_OK;
+ UINT bw = 0;
+ FSIZE_t i = 0;
+ if (bytes_written != NULL) {
+ *bytes_written = 0;
+ }
+
+ if (data_size > buf_size) { // prevent unsigned underflow
+ for (; i < (data_size - buf_size); i += buf_size) { // write chunks of buf_size
+ res = f_write(fp, buf, (UINT) buf_size, &bw);
+ if (res != FR_OK) {
+ goto out;
+ }
+ if (bytes_written != NULL) {
+ *bytes_written += bw;
+ }
+ }
+ }
+
+ if (i < data_size) { // write the remaining data
+ res = f_write(fp, buf, (UINT) (data_size - i), &bw);
+ if (res == FR_OK && bytes_written != NULL) {
+ *bytes_written += bw;
+ }
+ }
+
+out:
+ ff_memfree(buf);
+ return res;
+}
+
static int vfs_fat_truncate(void* ctx, const char *path, off_t length)
{
FRESULT res;
@@ -1000,31 +1162,55 @@ static int vfs_fat_truncate(void* ctx, const char *path, off_t length)
goto out;
}
- long sz = f_size(file);
- if (sz < length) {
- _lock_release(&fat_ctx->lock);
- ESP_LOGD(TAG, "truncate does not support extending size");
- errno = EPERM;
- ret = -1;
- goto close;
- }
+ FSIZE_t seek_ptr_pos = (FSIZE_t) f_tell(file); // current seek pointer position
+ FSIZE_t sz = (FSIZE_t) f_size(file); // current file size (end of file position)
res = f_lseek(file, length);
- if (res != FR_OK) {
- _lock_release(&fat_ctx->lock);
- ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
- errno = fresult_to_errno(res);
- ret = -1;
- goto close;
+ if (res != FR_OK || f_tell(file) != length) {
+ goto lseek_or_write_fail;
}
- res = f_truncate(file);
+ if (sz < length) {
+ res = f_lseek(file, sz); // go to the previous end of file
+ if (res != FR_OK) {
+ goto lseek_or_write_fail;
+ }
- if (res != FR_OK) {
- ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
- errno = fresult_to_errno(res);
- ret = -1;
- goto close;
+ FSIZE_t new_free_space = ((FSIZE_t) length) - sz;
+ UINT written;
+
+ if (new_free_space > UINT32_MAX) {
+ _lock_release(&fat_ctx->lock);
+ ESP_LOGE(TAG, "%s: Cannot extend the file more than 4GB at once", __func__);
+ ret = -1;
+ goto close;
+ }
+
+ FSIZE_t buf_size_limit = F_WRITE_MALLOC_ZEROING_BUF_SIZE_LIMIT;
+ FSIZE_t buf_size = new_free_space < buf_size_limit ? new_free_space : buf_size_limit;
+ res = f_write_zero_mem(file, new_free_space, buf_size, &written);
+
+ if (res != FR_OK) {
+ goto lseek_or_write_fail;
+ } else if (written != (UINT) new_free_space) {
+ res = FR_DISK_ERR;
+ goto lseek_or_write_fail;
+ }
+
+ res = f_lseek(file, seek_ptr_pos); // return to the original position
+ if (res != FR_OK) {
+ goto lseek_or_write_fail;
+ }
+ } else {
+ res = f_truncate(file);
+
+ if (res != FR_OK) {
+ _lock_release(&fat_ctx->lock);
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ ret = -1;
+ goto close;
+ }
}
#if CONFIG_FATFS_IMMEDIATE_FSYNC
@@ -1052,6 +1238,13 @@ close:
out:
free(file);
return ret;
+
+lseek_or_write_fail:
+ _lock_release(&fat_ctx->lock);
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ ret = -1;
+ goto close;
}
static int vfs_fat_ftruncate(void* ctx, int fd, off_t length)
@@ -1078,29 +1271,50 @@ static int vfs_fat_ftruncate(void* ctx, int fd, off_t length)
goto out;
}
- long sz = f_size(file);
- if (sz < length) {
- ESP_LOGD(TAG, "ftruncate does not support extending size");
- errno = EPERM;
- ret = -1;
- goto out;
- }
+ FSIZE_t seek_ptr_pos = (FSIZE_t) f_tell(file); // current seek pointer position
+ FSIZE_t sz = (FSIZE_t) f_size(file); // current file size (end of file position)
res = f_lseek(file, length);
- if (res != FR_OK) {
- ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
- errno = fresult_to_errno(res);
- ret = -1;
- goto out;
+ if (res != FR_OK || f_tell(file) != length) {
+ goto fail;
}
- res = f_truncate(file);
+ if (sz < length) {
+ res = f_lseek(file, sz); // go to the previous end of file
+ if (res != FR_OK) {
+ goto fail;
+ }
- if (res != FR_OK) {
- ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
- errno = fresult_to_errno(res);
- ret = -1;
- goto out;
+ FSIZE_t new_free_space = ((FSIZE_t) length) - sz;
+ UINT written;
+
+ if (new_free_space > UINT32_MAX) {
+ ESP_LOGE(TAG, "%s: Cannot extend the file more than 4GB at once", __func__);
+ ret = -1;
+ goto out;
+ }
+
+ FSIZE_t buf_size_limit = F_WRITE_MALLOC_ZEROING_BUF_SIZE_LIMIT;
+ FSIZE_t buf_size = new_free_space < buf_size_limit ? new_free_space : buf_size_limit;
+ res = f_write_zero_mem(file, new_free_space, buf_size, &written);
+
+ if (res != FR_OK) {
+ goto fail;
+ } else if (written != (UINT) new_free_space) {
+ res = FR_DISK_ERR;
+ goto fail;
+ }
+
+ res = f_lseek(file, seek_ptr_pos); // return to the original position
+ if (res != FR_OK) {
+ goto fail;
+ }
+ } else {
+ res = f_truncate(file);
+
+ if (res != FR_OK) {
+ goto fail;
+ }
}
#if CONFIG_FATFS_IMMEDIATE_FSYNC
@@ -1115,6 +1329,12 @@ static int vfs_fat_ftruncate(void* ctx, int fd, off_t length)
out:
_lock_release(&fat_ctx->lock);
return ret;
+
+fail:
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ ret = -1;
+ goto out;
}
static int vfs_fat_utime(void *ctx, const char *path, const struct utimbuf *times)
@@ -1142,7 +1362,7 @@ static int vfs_fat_utime(void *ctx, const char *path, const struct utimbuf *time
fat_date_t fdate;
fat_time_t ftime;
- // this time transformation is esentially the reverse of the one in vfs_fat_stat()
+ // this time transformation is essentially the reverse of the one in vfs_fat_stat()
fdate.mday = tm_time.tm_mday;
fdate.mon = tm_time.tm_mon + 1; // January in fdate.mon is 1, and 0 in tm_time.tm_mon
fdate.year = tm_time.tm_year - 80; // tm_time.tm_year=0 is 1900, tm_time.tm_year=0 is 1980
@@ -1170,3 +1390,142 @@ static int vfs_fat_utime(void *ctx, const char *path, const struct utimbuf *time
}
#endif // CONFIG_VFS_SUPPORT_DIR
+
+esp_err_t esp_vfs_fat_create_contiguous_file(const char* base_path, const char* full_path, uint64_t size, bool alloc_now)
+{
+ if (base_path == NULL || full_path == NULL || size <= 0) {
+ return ESP_ERR_INVALID_ARG;
+ }
+
+ size_t ctx = find_context_index_by_path(base_path);
+ if (ctx == FF_VOLUMES) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ vfs_fat_ctx_t* fat_ctx = s_fat_ctxs[ctx];
+
+ _lock_acquire(&fat_ctx->lock);
+ const char* file_path = full_path + strlen(base_path); // shift the pointer and omit the base_path
+ prepend_drive_to_path(fat_ctx, &file_path, NULL);
+
+ FIL* file = (FIL*) ff_memalloc(sizeof(FIL));
+ if (file == NULL) {
+ _lock_release(&fat_ctx->lock);
+ ESP_LOGD(TAG, "esp_vfs_fat_create_contiguous_file alloc failed");
+ errno = ENOMEM;
+ return -1;
+ }
+ memset(file, 0, sizeof(*file));
+
+ FRESULT res = f_open(file, file_path, FA_WRITE | FA_OPEN_ALWAYS);
+ if (res != FR_OK) {
+ goto fail;
+ }
+
+ res = f_expand(file, size, alloc_now ? 1 : 0);
+ if (res != FR_OK) {
+ f_close(file);
+ goto fail;
+ }
+
+ res = f_close(file);
+ if (res != FR_OK) {
+ goto fail;
+ }
+
+ _lock_release(&fat_ctx->lock);
+ free(file);
+
+ return 0;
+fail:
+ _lock_release(&fat_ctx->lock);
+ free(file);
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ return -1;
+}
+
+static FRESULT test_contiguous_file( // From FATFS examples
+ FIL* fp, /* [IN] Open file object to be checked */
+ int* cont /* [OUT] 1:Contiguous, 0:Fragmented or zero-length */
+) {
+ DWORD clst, clsz, step;
+ FSIZE_t fsz;
+ FRESULT fr;
+
+ *cont = 0;
+ fr = f_rewind(fp); /* Validates and prepares the file */
+ if (fr != FR_OK) return fr;
+
+#if FF_MAX_SS == FF_MIN_SS
+ clsz = (DWORD)fp->obj.fs->csize * FF_MAX_SS; /* Cluster size */
+#else
+ clsz = (DWORD)fp->obj.fs->csize * fp->obj.fs->ssize;
+#endif
+ fsz = f_size(fp);
+ if (fsz > 0) {
+ clst = fp->obj.sclust - 1; /* A cluster leading the first cluster for first test */
+ while (fsz) {
+ step = (fsz >= clsz) ? clsz : (DWORD)fsz;
+ fr = f_lseek(fp, f_tell(fp) + step); /* Advances file pointer a cluster */
+ if (fr != FR_OK) return fr;
+ if (clst + 1 != fp->clust) break; /* Is not the cluster next to previous one? */
+ clst = fp->clust; fsz -= step; /* Get current cluster for next test */
+ }
+ if (fsz == 0) *cont = 1; /* All done without fail? */
+ }
+
+ return FR_OK;
+}
+
+esp_err_t esp_vfs_fat_test_contiguous_file(const char* base_path, const char* full_path, bool* is_contiguous)
+{
+ if (base_path == NULL || full_path == NULL || is_contiguous == NULL) {
+ return ESP_ERR_INVALID_ARG;
+ }
+
+ size_t ctx = find_context_index_by_path(base_path);
+ if (ctx == FF_VOLUMES) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ vfs_fat_ctx_t* fat_ctx = s_fat_ctxs[ctx];
+
+ _lock_acquire(&fat_ctx->lock);
+ const char* file_path = full_path + strlen(base_path); // shift the pointer and omit the base_path
+ prepend_drive_to_path(fat_ctx, &file_path, NULL);
+
+ FIL* file = (FIL*) ff_memalloc(sizeof(FIL));
+ if (file == NULL) {
+ _lock_release(&fat_ctx->lock);
+ ESP_LOGD(TAG, "esp_vfs_fat_test_contiguous_file alloc failed");
+ errno = ENOMEM;
+ return -1;
+ }
+ memset(file, 0, sizeof(*file));
+
+ FRESULT res = f_open(file, file_path, FA_WRITE | FA_OPEN_ALWAYS);
+ if (res != FR_OK) {
+ goto fail;
+ }
+
+ res = test_contiguous_file(file, (int*) is_contiguous);
+ if (res != FR_OK) {
+ f_close(file);
+ goto fail;
+ }
+
+ res = f_close(file);
+ if (res != FR_OK) {
+ goto fail;
+ }
+
+ _lock_release(&fat_ctx->lock);
+ free(file);
+
+ return 0;
+fail:
+ _lock_release(&fat_ctx->lock);
+ free(file);
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ return -1;
+}
diff --git a/lib/fatfs/vfs/vfs_fat_internal.h b/lib/fatfs/vfs/vfs_fat_internal.h
index dc3bae27..5b894604 100644
--- a/lib/fatfs/vfs/vfs_fat_internal.h
+++ b/lib/fatfs/vfs/vfs_fat_internal.h
@@ -1,23 +1,41 @@
-// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+/*
+ * SPDX-FileCopyrightText: 2018-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
#pragma once
#include "esp_vfs_fat.h"
+#include "diskio_impl.h"
+#include "esp_partition.h"
+#include "sdmmc_cmd.h"
#include <sys/param.h>
#include <stddef.h>
+typedef enum {
+ FORMATTED_DURING_LAST_MOUNT = 1 << 0, // The FATFS partition was formatted during the last mount
+} vfs_fat_x_ctx_flags_t;
+
+typedef struct vfs_fat_spiflash_ctx_t {
+ const esp_partition_t *partition; //The partition where the FAT is located
+ bool by_label; //If the partition is mounted by label or not
+ BYTE pdrv; //Drive number that is mounted
+ FATFS *fs; //FAT structure pointer that is registered
+ wl_handle_t wlhandle; //WL handle
+ esp_vfs_fat_mount_config_t mount_config; //Mount configuration
+ vfs_fat_x_ctx_flags_t flags; //Flags
+} vfs_fat_spiflash_ctx_t;
+
+typedef struct vfs_fat_sd_ctx_t {
+ BYTE pdrv; //Drive number that is mounted
+ esp_vfs_fat_mount_config_t mount_config; //Mount configuration
+ FATFS *fs; //FAT structure pointer that is registered
+ sdmmc_card_t *card; //Card info
+ char *base_path; //Path where partition is registered
+ vfs_fat_x_ctx_flags_t flags; //Flags
+} vfs_fat_sd_ctx_t;
+
static inline size_t esp_vfs_fat_get_allocation_unit_size(
size_t sector_size, size_t requested_size)
{
@@ -28,3 +46,6 @@ static inline size_t esp_vfs_fat_get_allocation_unit_size(
alloc_unit_size = MIN(alloc_unit_size, max_size);
return alloc_unit_size;
}
+
+vfs_fat_spiflash_ctx_t* get_vfs_fat_spiflash_ctx(wl_handle_t wlhandle);
+vfs_fat_sd_ctx_t* get_vfs_fat_get_sd_ctx(const sdmmc_card_t *card);
diff --git a/lib/fatfs/vfs/vfs_fat_sdmmc.c b/lib/fatfs/vfs/vfs_fat_sdmmc.c
index f62d6e7c..cd012d4d 100644
--- a/lib/fatfs/vfs/vfs_fat_sdmmc.c
+++ b/lib/fatfs/vfs/vfs_fat_sdmmc.c
@@ -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
*/
@@ -16,7 +16,7 @@
#include "diskio_impl.h"
#include "diskio_sdmmc.h"
#include "soc/soc_caps.h"
-#include "driver/sdmmc_defs.h"
+#include "sd_protocol_defs.h"
#if SOC_SDMMC_HOST_SUPPORTED
#include "driver/sdmmc_host.h"
@@ -31,14 +31,6 @@ static const char* TAG = "vfs_fat_sdmmc";
} \
} while(0)
-typedef struct vfs_fat_sd_ctx_t {
- BYTE pdrv; //Drive number that is mounted
- esp_vfs_fat_mount_config_t mount_config; //Mount configuration
- FATFS *fs; //FAT structure pointer that is registered
- sdmmc_card_t *card; //Card info
- char *base_path; //Path where partition is registered
-} vfs_fat_sd_ctx_t;
-
static vfs_fat_sd_ctx_t *s_ctx[FF_VOLUMES] = {};
/**
* This `s_saved_ctx_id` is only used by `esp_vfs_fat_sdmmc_unmount`, which is deprecated.
@@ -75,6 +67,15 @@ static uint32_t s_get_unused_context_id(void)
return FF_VOLUMES;
}
+vfs_fat_sd_ctx_t* get_vfs_fat_get_sd_ctx(const sdmmc_card_t *card)
+{
+ uint32_t id = FF_VOLUMES;
+ if (s_get_context_id_by_card(card, &id)) {
+ return s_ctx[id];
+ }
+ return NULL;
+}
+
static esp_err_t mount_prepare_mem(const char *base_path,
BYTE *out_pdrv,
char **out_dup_path,
@@ -116,7 +117,7 @@ cleanup:
return err;
}
-static esp_err_t s_f_mount(sdmmc_card_t *card, FATFS *fs, const char *drv, uint8_t pdrv, const esp_vfs_fat_mount_config_t *mount_config)
+static esp_err_t s_f_mount(sdmmc_card_t *card, FATFS *fs, const char *drv, uint8_t pdrv, const esp_vfs_fat_mount_config_t *mount_config, vfs_fat_x_ctx_flags_t *out_flags)
{
esp_err_t err = ESP_OK;
FRESULT res = f_mount(fs, drv, 1);
@@ -134,20 +135,28 @@ static esp_err_t s_f_mount(sdmmc_card_t *card, FATFS *fs, const char *drv, uint8
return err;
}
+ if (out_flags) {
+ *out_flags |= FORMATTED_DURING_LAST_MOUNT; // set flag
+ }
+
ESP_LOGW(TAG, "mounting again");
- res = f_mount(fs, drv, 0);
+ res = f_mount(fs, drv, 1);
if (res != FR_OK) {
err = ESP_FAIL;
ESP_LOGD(TAG, "f_mount failed after formatting (%d)", res);
return err;
}
+ } else {
+ if (out_flags) {
+ *out_flags &= ~FORMATTED_DURING_LAST_MOUNT; // reset flag
+ }
}
return ESP_OK;
}
static esp_err_t mount_to_vfs_fat(const esp_vfs_fat_mount_config_t *mount_config, sdmmc_card_t *card, uint8_t pdrv,
- const char *base_path, FATFS **out_fs)
+ const char *base_path, FATFS **out_fs, vfs_fat_x_ctx_flags_t *out_flags)
{
FATFS *fs = NULL;
esp_err_t err;
@@ -157,17 +166,22 @@ static esp_err_t mount_to_vfs_fat(const esp_vfs_fat_mount_config_t *mount_config
char drv[3] = {(char)('0' + pdrv), ':', 0};
// connect FATFS to VFS
- err = esp_vfs_fat_register(base_path, drv, mount_config->max_files, &fs);
+ esp_vfs_fat_conf_t conf = {
+ .base_path = base_path,
+ .fat_drive = drv,
+ .max_files = mount_config->max_files,
+ };
+ err = esp_vfs_fat_register_cfg(&conf, &fs);
*out_fs = fs;
if (err == ESP_ERR_INVALID_STATE) {
// it's okay, already registered with VFS
} else if (err != ESP_OK) {
- ESP_LOGD(TAG, "esp_vfs_fat_register failed 0x(%x)", err);
+ ESP_LOGD(TAG, "esp_vfs_fat_register_cfg failed 0x(%x)", err);
goto fail;
}
// Try to mount partition
- err = s_f_mount(card, fs, drv, pdrv, mount_config);
+ err = s_f_mount(card, fs, drv, pdrv, mount_config, out_flags);
if (err != ESP_OK) {
goto fail;
}
@@ -207,7 +221,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;
@@ -265,7 +279,9 @@ esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path,
err = sdmmc_card_init(host_config, card);
CHECK_EXECUTE_RESULT(err, "sdmmc_card_init failed");
- err = mount_to_vfs_fat(mount_config, card, pdrv, dup_path, &fs);
+ vfs_fat_x_ctx_flags_t flags = 0;
+
+ err = mount_to_vfs_fat(mount_config, card, pdrv, dup_path, &fs, &flags);
CHECK_EXECUTE_RESULT(err, "mount_to_vfs failed");
if (out_card != NULL) {
@@ -276,7 +292,7 @@ esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path,
s_saved_ctx_id = 0;
}
- ctx = calloc(sizeof(vfs_fat_sd_ctx_t), 1);
+ ctx = calloc(1, sizeof(vfs_fat_sd_ctx_t));
if (!ctx) {
CHECK_EXECUTE_RESULT(ESP_ERR_NO_MEM, "no mem");
}
@@ -285,6 +301,7 @@ esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path,
ctx->card = card;
ctx->base_path = dup_path;
ctx->fs = fs;
+ ctx->flags = flags;
ctx_id = s_get_unused_context_id();
assert(ctx_id != FF_VOLUMES);
s_ctx[ctx_id] = ctx;
@@ -360,7 +377,9 @@ esp_err_t esp_vfs_fat_sdspi_mount(const char* base_path,
err = sdmmc_card_init(host_config, card);
CHECK_EXECUTE_RESULT(err, "sdmmc_card_init failed");
- err = mount_to_vfs_fat(mount_config, card, pdrv, dup_path, &fs);
+ vfs_fat_x_ctx_flags_t flags = 0;
+
+ err = mount_to_vfs_fat(mount_config, card, pdrv, dup_path, &fs, &flags);
CHECK_EXECUTE_RESULT(err, "mount_to_vfs failed");
if (out_card != NULL) {
@@ -371,7 +390,7 @@ esp_err_t esp_vfs_fat_sdspi_mount(const char* base_path,
s_saved_ctx_id = 0;
}
- ctx = calloc(sizeof(vfs_fat_sd_ctx_t), 1);
+ ctx = calloc(1, sizeof(vfs_fat_sd_ctx_t));
if (!ctx) {
CHECK_EXECUTE_RESULT(ESP_ERR_NO_MEM, "no mem");
}
@@ -380,6 +399,7 @@ esp_err_t esp_vfs_fat_sdspi_mount(const char* base_path,
ctx->card = card;
ctx->base_path = dup_path;
ctx->fs = fs;
+ ctx->flags = flags;
ctx_id = s_get_unused_context_id();
assert(ctx_id != FF_VOLUMES);
s_ctx[ctx_id] = ctx;
@@ -450,7 +470,7 @@ esp_err_t esp_vfs_fat_sdcard_unmount(const char *base_path, sdmmc_card_t *card)
return err;
}
-esp_err_t esp_vfs_fat_sdcard_format(const char *base_path, sdmmc_card_t *card)
+esp_err_t esp_vfs_fat_sdcard_format_cfg(const char *base_path, sdmmc_card_t *card, esp_vfs_fat_mount_config_t *cfg)
{
esp_err_t ret = ESP_OK;
if (!card) {
@@ -480,13 +500,22 @@ esp_err_t esp_vfs_fat_sdcard_format(const char *base_path, sdmmc_card_t *card)
//format
uint32_t id = FF_VOLUMES;
- bool found = s_get_context_id_by_card(card, &id);
- assert(found);
+
+ {
+ const bool found = s_get_context_id_by_card(card, &id);
+ (void)found;
+ assert(found);
+ }
+
+ if (cfg) {
+ s_ctx[id]->mount_config = *cfg;
+ }
+
size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size(
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) {
@@ -495,7 +524,7 @@ esp_err_t esp_vfs_fat_sdcard_format(const char *base_path, sdmmc_card_t *card)
}
//mount back
- esp_err_t err = s_f_mount(card, s_ctx[id]->fs, drv, pdrv, &s_ctx[id]->mount_config);
+ esp_err_t err = s_f_mount(card, s_ctx[id]->fs, drv, pdrv, &s_ctx[id]->mount_config, NULL);
if (err != ESP_OK) {
unmount_card_core(base_path, card);
ESP_LOGE(TAG, "failed to format, resources recycled, please mount again");
@@ -503,3 +532,7 @@ esp_err_t esp_vfs_fat_sdcard_format(const char *base_path, sdmmc_card_t *card)
return ret;
}
+
+esp_err_t esp_vfs_fat_sdcard_format(const char *base_path, sdmmc_card_t *card) {
+ return esp_vfs_fat_sdcard_format_cfg(base_path, card, NULL);
+}
diff --git a/lib/fatfs/vfs/vfs_fat_spiflash.c b/lib/fatfs/vfs/vfs_fat_spiflash.c
index 98e4160f..0ee00e25 100644
--- a/lib/fatfs/vfs/vfs_fat_spiflash.c
+++ b/lib/fatfs/vfs/vfs_fat_spiflash.c
@@ -1,5 +1,5 @@
/*
- * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
+ * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -15,16 +15,11 @@
#include "wear_levelling.h"
#include "diskio_wl.h"
-static const char* TAG = "vfs_fat_spiflash";
+// If the available sectors based on partition size are less than 128,
+// the root directory sector should be set to 1.
+#define MIN_REQ_SEC 128
-typedef struct vfs_fat_spiflash_ctx_t {
- const esp_partition_t *partition; //The partition where the FAT is located
- bool by_label; //If the partition is mounted by lable or not
- BYTE pdrv; //Drive number that is mounted
- FATFS *fs; //FAT structure pointer that is registered
- wl_handle_t wlhandle; //WL handle
- esp_vfs_fat_mount_config_t mount_config; //Mount configuration
-} vfs_fat_spiflash_ctx_t;
+static const char* TAG = "vfs_fat_spiflash";
static vfs_fat_spiflash_ctx_t *s_ctx[FF_VOLUMES] = {};
@@ -74,35 +69,78 @@ static uint32_t s_get_unused_context_id(void)
return FF_VOLUMES;
}
-static esp_err_t s_f_mount_rw(FATFS *fs, const char *drv, const esp_vfs_fat_mount_config_t *mount_config)
+vfs_fat_spiflash_ctx_t* get_vfs_fat_spiflash_ctx(wl_handle_t wlhandle)
{
- FRESULT fresult = f_mount(fs, drv, 1);
- if (fresult != FR_OK) {
- ESP_LOGW(TAG, "f_mount failed (%d)", fresult);
+ uint32_t id = FF_VOLUMES;
+ if (s_get_context_id_by_wl_handle(wlhandle, &id)) {
+ return s_ctx[id];
+ }
+ return NULL;
+}
- bool need_mount_again = (fresult == FR_NO_FILESYSTEM || fresult == FR_INT_ERR) && mount_config->format_if_mount_failed;
- if (!need_mount_again) {
- return ESP_FAIL;
+static esp_err_t s_f_mount_rw(FATFS *fs, const char *drv, const esp_vfs_fat_mount_config_t *mount_config, vfs_fat_x_ctx_flags_t *out_flags, size_t sec_num)
+{
+ FRESULT fresult = f_mount(fs, drv, 1);
+ if (fresult == FR_OK) {
+ if (out_flags) {
+ *out_flags &= ~FORMATTED_DURING_LAST_MOUNT; // reset flag
}
+ return ESP_OK;
+ }
- const size_t workbuf_size = 4096;
- void *workbuf = ff_memalloc(workbuf_size);
- if (workbuf == NULL) {
- return ESP_ERR_NO_MEM;
- }
+ const char *msg = "Unknown";
+ const char *note = "";
+ bool recoverable = false;
+
+ switch (fresult) {
+ case FR_NO_FILESYSTEM:
+ msg = "No filesystem detected";
+ note = "(This may indicate corrupt FS, or attempt to mount read-only fatfsgen image for write)";
+ recoverable = true;
+ break;
+ case FR_INT_ERR:
+ msg = "Assertion failed";
+ recoverable = true;
+ break;
+ default:
+ break;
+ }
+
+ if (!recoverable || !mount_config->format_if_mount_failed) {
+ ESP_LOGE(TAG, "f_mount failed with error: \"%s\" [%d]. %s", msg, fresult, note);
+ return ESP_FAIL;
+ }
+
+ ESP_LOGW(TAG, "FatFS mount (f_mount) failed with error: \"%s\" [%d]. Retrying after format...", msg, fresult);
- 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};
- fresult = f_mkfs(drv, &opt, workbuf, workbuf_size);
- free(workbuf);
- workbuf = NULL;
- ESP_RETURN_ON_FALSE(fresult == FR_OK, ESP_FAIL, TAG, "f_mkfs failed (%d)", fresult);
-
- ESP_LOGI(TAG, "Mounting again");
- fresult = f_mount(fs, drv, 0);
- ESP_RETURN_ON_FALSE(fresult == FR_OK, ESP_FAIL, TAG, "f_mount failed after formatting (%d)", fresult);
+ const size_t workbuf_size = 4096;
+ void *workbuf = ff_memalloc(workbuf_size);
+ if (workbuf == NULL) {
+ return ESP_ERR_NO_MEM;
}
+
+ 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);
+ UINT root_dir_entries;
+ if (CONFIG_WL_SECTOR_SIZE == 512) {
+ root_dir_entries = 16;
+ } else {
+ root_dir_entries = 128;
+ }
+ const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), (mount_config->use_one_fat ? 1 : 2), 0, (sec_num <= MIN_REQ_SEC ? root_dir_entries : 0), alloc_unit_size};
+ fresult = f_mkfs(drv, &opt, workbuf, workbuf_size);
+ free(workbuf);
+ workbuf = NULL;
+ ESP_RETURN_ON_FALSE(fresult == FR_OK, ESP_FAIL, TAG, "f_mkfs failed (%d)", fresult);
+
+ if (out_flags) {
+ *out_flags |= FORMATTED_DURING_LAST_MOUNT; // set flag
+ }
+
+ ESP_LOGI(TAG, "Mounting again");
+ fresult = f_mount(fs, drv, 1);
+ ESP_RETURN_ON_FALSE(fresult == FR_OK, ESP_FAIL, TAG, "f_mount failed after formatting (%d)", fresult);
+
return ESP_OK;
}
@@ -134,27 +172,36 @@ esp_err_t esp_vfs_fat_spiflash_mount_rw_wl(const char* base_path,
ESP_GOTO_ON_ERROR(ff_diskio_register_wl_partition(pdrv, *wl_handle), fail, TAG, "ff_diskio_register_wl_partition failed pdrv=%i, error - 0x(%x)", pdrv, ret);
FATFS *fs;
- ret = esp_vfs_fat_register(base_path, drv, mount_config->max_files, &fs);
+ esp_vfs_fat_conf_t conf = {
+ .base_path = base_path,
+ .fat_drive = drv,
+ .max_files = mount_config->max_files,
+ };
+ ret = esp_vfs_fat_register_cfg(&conf, &fs);
if (ret == ESP_ERR_INVALID_STATE) {
// it's okay, already registered with VFS
} else if (ret != ESP_OK) {
- ESP_LOGD(TAG, "esp_vfs_fat_register failed 0x(%x)", ret);
+ ESP_LOGD(TAG, "esp_vfs_fat_register_cfg failed 0x(%x)", ret);
goto fail;
}
+ vfs_fat_x_ctx_flags_t flags = 0;
+
+ size_t sec_num = wl_size(*wl_handle) / wl_sector_size(*wl_handle);
// Try to mount partition
- ret = s_f_mount_rw(fs, drv, mount_config);
+ ret = s_f_mount_rw(fs, drv, mount_config, &flags, sec_num);
if (ret != ESP_OK) {
goto fail;
}
- ctx = calloc(sizeof(vfs_fat_spiflash_ctx_t), 1);
+ ctx = calloc(1, sizeof(vfs_fat_spiflash_ctx_t));
ESP_GOTO_ON_FALSE(ctx, ESP_ERR_NO_MEM, fail, TAG, "no mem");
ctx->partition = data_partition;
ctx->by_label = (partition_label != NULL);
ctx->pdrv = pdrv;
ctx->fs = fs;
ctx->wlhandle = *wl_handle;
+ ctx->flags = flags;
memcpy(&ctx->mount_config, mount_config, sizeof(esp_vfs_fat_mount_config_t));
ctx_id = s_get_unused_context_id();
//At this stage, we should always get a free context, otherwise program should return already
@@ -201,25 +248,43 @@ esp_err_t esp_vfs_fat_spiflash_unmount_rw_wl(const char* base_path, wl_handle_t
return err;
}
-esp_err_t esp_vfs_fat_spiflash_format_rw_wl(const char* base_path, const char* partition_label)
+esp_err_t esp_vfs_fat_spiflash_format_cfg_rw_wl(const char* base_path, const char* partition_label, esp_vfs_fat_mount_config_t *cfg)
{
esp_err_t ret = ESP_OK;
bool partition_was_mounted = false;
wl_handle_t temp_handle = WL_INVALID_HANDLE;
uint32_t id = FF_VOLUMES;
+ size_t sec_num = 0;
bool found = s_get_context_id_by_label(partition_label, &id);
if (!found) {
- const esp_vfs_fat_mount_config_t mount_config = {
+ esp_vfs_fat_mount_config_t default_mount_config = {
.max_files = 1,
.format_if_mount_failed = true,
};
- ESP_RETURN_ON_ERROR(esp_vfs_fat_spiflash_mount_rw_wl(base_path, partition_label, &mount_config, &temp_handle), TAG, "Failed to mount");
+ esp_vfs_fat_mount_config_t *mount_cfg = NULL;
+ if (cfg) {
+ mount_cfg = cfg;
+ } else {
+ mount_cfg = &default_mount_config;
+ }
+ ESP_RETURN_ON_ERROR(esp_vfs_fat_spiflash_mount_rw_wl(base_path, partition_label, mount_cfg, &temp_handle), TAG, "Failed to mount");
found = s_get_context_id_by_label(partition_label, &id);
+ sec_num = wl_size(temp_handle) / wl_sector_size(temp_handle);
assert(found);
+ if (s_ctx[id]->flags & FORMATTED_DURING_LAST_MOUNT) {
+ ESP_LOGD(TAG, "partition was formatted during mounting, skipping another format");
+ ret = ESP_OK;
+ goto mount_back;
+ }
} else {
partition_was_mounted = true;
+ if (cfg) {
+ s_ctx[id]->mount_config = *cfg;
+ }
+ temp_handle = s_ctx[id]->wlhandle;
+ sec_num = wl_size(temp_handle) / wl_sector_size(temp_handle);
}
//unmount
@@ -236,7 +301,13 @@ 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};
+ UINT root_dir_entries;
+ if (CONFIG_WL_SECTOR_SIZE == 512) {
+ root_dir_entries = 16;
+ } else {
+ root_dir_entries = 128;
+ }
+ const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), (s_ctx[id]->mount_config.use_one_fat ? 1 : 2), 0, (sec_num <= MIN_REQ_SEC ? root_dir_entries : 0), alloc_unit_size};
fresult = f_mkfs(drv, &opt, workbuf, workbuf_size);
free(workbuf);
workbuf = NULL;
@@ -244,7 +315,7 @@ esp_err_t esp_vfs_fat_spiflash_format_rw_wl(const char* base_path, const char* p
mount_back:
if (partition_was_mounted) {
- esp_err_t err = s_f_mount_rw(s_ctx[id]->fs, drv, &s_ctx[id]->mount_config);
+ esp_err_t err = s_f_mount_rw(s_ctx[id]->fs, drv, &s_ctx[id]->mount_config, NULL, sec_num);
if (err != ESP_OK) {
ESP_LOGE(TAG, "failed to mount back, go to recycle");
goto recycle;
@@ -265,6 +336,10 @@ recycle:
return ret;
}
+esp_err_t esp_vfs_fat_spiflash_format_rw_wl(const char* base_path, const char* partition_label)
+{
+ return esp_vfs_fat_spiflash_format_cfg_rw_wl(base_path, partition_label, NULL);
+}
esp_err_t esp_vfs_fat_spiflash_mount_ro(const char* base_path,
const char* partition_label,
@@ -287,11 +362,16 @@ esp_err_t esp_vfs_fat_spiflash_mount_ro(const char* base_path,
ESP_GOTO_ON_ERROR(ff_diskio_register_raw_partition(pdrv, data_partition), fail, TAG, "ff_diskio_register_raw_partition failed pdrv=%i, error - 0x(%x)", pdrv, ret);
FATFS *fs;
- ret = esp_vfs_fat_register(base_path, drv, mount_config->max_files, &fs);
+ esp_vfs_fat_conf_t conf = {
+ .base_path = base_path,
+ .fat_drive = drv,
+ .max_files = mount_config->max_files,
+ };
+ ret = esp_vfs_fat_register_cfg(&conf, &fs);
if (ret == ESP_ERR_INVALID_STATE) {
// it's okay, already registered with VFS
} else if (ret != ESP_OK) {
- ESP_LOGD(TAG, "esp_vfs_fat_register failed 0x(%x)", ret);
+ ESP_LOGD(TAG, "esp_vfs_fat_register_cfg failed 0x(%x)", ret);
goto fail;
}
diff --git a/lib/fatfs/wl_fatfsgen.py b/lib/fatfs/wl_fatfsgen.py
index 4a685434..13ef5168 100755
--- a/lib/fatfs/wl_fatfsgen.py
+++ b/lib/fatfs/wl_fatfsgen.py
@@ -1,11 +1,18 @@
#!/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 typing import Optional
-from construct import Const, Int32ul, Struct
+from construct import Const
+from construct import Int32ul
+from construct import Struct
from fatfs_utils.exceptions import WLNotInitialized
-from fatfs_utils.utils import (FULL_BYTE, UINT32_MAX, FATDefaults, crc32, generate_4bytes_random,
- get_args_for_partition_generator)
+from fatfs_utils.utils import crc32
+from fatfs_utils.utils import FATDefaults
+from fatfs_utils.utils import FULL_BYTE
+from fatfs_utils.utils import generate_4bytes_random
+from fatfs_utils.utils import get_args_for_partition_generator
+from fatfs_utils.utils import UINT32_MAX
from fatfsgen import FATFS
@@ -53,6 +60,7 @@ class WLFATFS:
WL_STATE_HEADER_SIZE = 64
WL_STATE_COPY_COUNT = 2 # always 2 copies for power failure safety
WL_SECTOR_SIZE = 0x1000
+ WL_SAFE_MODE_DUMP_SECTORS = 2
WL_STATE_T_DATA = Struct(
'pos' / Int32ul,
@@ -97,7 +105,8 @@ class WLFATFS:
temp_buff_size: int = FATDefaults.TEMP_BUFFER_SIZE,
device_id: int = None,
root_entry_count: int = FATDefaults.ROOT_ENTRIES_COUNT,
- media_type: int = FATDefaults.MEDIA_TYPE) -> None:
+ media_type: int = FATDefaults.MEDIA_TYPE,
+ wl_mode: Optional[str] = None) -> None:
self._initialized = False
self._version = version
self._temp_buff_size = temp_buff_size
@@ -105,6 +114,7 @@ class WLFATFS:
self.partition_size = size
self.total_sectors = self.partition_size // FATDefaults.WL_SECTOR_SIZE
self.wl_state_size = WLFATFS.WL_STATE_HEADER_SIZE + WLFATFS.WL_STATE_RECORD_SIZE * self.total_sectors
+ self.wl_mode = wl_mode
# determine the number of required sectors (roundup to sector size)
self.wl_state_sectors = (self.wl_state_size + FATDefaults.WL_SECTOR_SIZE - 1) // FATDefaults.WL_SECTOR_SIZE
@@ -114,6 +124,9 @@ class WLFATFS:
wl_sectors = (WLFATFS.WL_DUMMY_SECTORS_COUNT + WLFATFS.WL_CFG_SECTORS_COUNT +
self.wl_state_sectors * WLFATFS.WL_STATE_COPY_COUNT)
+ if self.wl_mode is not None and self.wl_mode == 'safe':
+ wl_sectors += WLFATFS.WL_SAFE_MODE_DUMP_SECTORS
+
self.plain_fat_sectors = self.total_sectors - wl_sectors
self.plain_fatfs = FATFS(
explicit_fat_type=explicit_fat_type,
@@ -202,13 +215,15 @@ 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_mode=args.wl_mode)
wl_fatfs.plain_fatfs.generate(args.input_directory)
wl_fatfs.init_wl()