diff --git a/components/fatfs/src/ffconf.h b/components/fatfs/src/ffconf.h index 8d579002b6..62cbeb9423 100644 --- a/components/fatfs/src/ffconf.h +++ b/components/fatfs/src/ffconf.h @@ -40,7 +40,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) */ diff --git a/components/fatfs/test_apps/flash_wl/main/test_fatfs_flash_wl.c b/components/fatfs/test_apps/flash_wl/main/test_fatfs_flash_wl.c index 5374b88de3..66c9f6930d 100644 --- a/components/fatfs/test_apps/flash_wl/main/test_fatfs_flash_wl.c +++ b/components/fatfs/test_apps/flash_wl/main/test_fatfs_flash_wl.c @@ -190,10 +190,26 @@ 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]") { test_setup(); diff --git a/components/fatfs/test_apps/sdcard/main/test_fatfs_sdmmc.c b/components/fatfs/test_apps/sdcard/main/test_fatfs_sdmmc.c index bfda90cf45..3f709f0a38 100644 --- a/components/fatfs/test_apps/sdcard/main/test_fatfs_sdmmc.c +++ b/components/fatfs/test_apps/sdcard/main/test_fatfs_sdmmc.c @@ -186,7 +186,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); } @@ -194,10 +194,20 @@ 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]") { sdmmc_card_t *card = NULL; diff --git a/components/fatfs/test_apps/test_fatfs_common/test_fatfs_common.c b/components/fatfs/test_apps/test_fatfs_common/test_fatfs_common.c index f1a4ba5068..2d2d5c1e34 100644 --- a/components/fatfs/test_apps/test_fatfs_common/test_fatfs_common.c +++ b/components/fatfs/test_apps/test_fatfs_common/test_fatfs_common.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 */ @@ -21,6 +21,7 @@ #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"; @@ -253,7 +254,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; @@ -268,14 +269,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"; @@ -283,6 +314,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); @@ -294,28 +329,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); @@ -328,29 +369,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); + struct stat st; + size_t size; - TEST_ASSERT_EQUAL(-1, ftruncate(fd, -1)); - TEST_ASSERT_EQUAL(errno, EINVAL); + 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 @@ -368,25 +443,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 @@ -1012,3 +1097,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/components/fatfs/test_apps/test_fatfs_common/test_fatfs_common.h b/components/fatfs/test_apps/test_fatfs_common/test_fatfs_common.h index dad352e2c4..4ae67937dd 100644 --- a/components/fatfs/test_apps/test_fatfs_common/test_fatfs_common.h +++ b/components/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,7 @@ 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 diff --git a/components/fatfs/vfs/esp_vfs_fat.h b/components/fatfs/vfs/esp_vfs_fat.h index 8bfb3d61f0..7b87818fa8 100644 --- a/components/fatfs/vfs/esp_vfs_fat.h +++ b/components/fatfs/vfs/esp_vfs_fat.h @@ -401,6 +401,38 @@ 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 diff --git a/components/fatfs/vfs/vfs_fat.c b/components/fatfs/vfs/vfs_fat.c index 7354cdf17f..5fff8b573d 100644 --- a/components/fatfs/vfs/vfs_fat.c +++ b/components/fatfs/vfs/vfs_fat.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -17,6 +18,8 @@ #include "ff.h" #include "diskio_impl.h" +#define F_WRITE_MALLOC_ZEROING_BUF_SIZE_LIMIT 512 + typedef struct { char fat_drive[8]; /* FAT drive name */ char base_path[ESP_VFS_PATH_MAX]; /* base path in VFS where partition is registered */ @@ -993,6 +996,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; @@ -1031,31 +1077,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 @@ -1083,6 +1153,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) @@ -1109,29 +1186,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 @@ -1146,6 +1244,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) @@ -1201,3 +1305,138 @@ 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); + + return 0; +fail: + _lock_release(&fat_ctx->lock); + 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); + + return 0; +fail: + _lock_release(&fat_ctx->lock); + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return -1; +}