From ea711f2ee98315985cdea323489c28df905fbd12 Mon Sep 17 00:00:00 2001 From: Renz Bagaporo Date: Tue, 12 Jun 2018 18:29:05 +0800 Subject: [PATCH] vfs, fatfs: Add file truncate for fatfs --- components/fatfs/src/ff.c | 1 - components/fatfs/src/vfs_fat.c | 73 ++++++++++++++++++ components/fatfs/test/test_fatfs_common.c | 76 +++++++++++++++++++ components/fatfs/test/test_fatfs_common.h | 3 + components/fatfs/test/test_fatfs_sdmmc.c | 8 +- components/fatfs/test/test_fatfs_spiflash.c | 7 ++ .../newlib/platform_include/sys/unistd.h | 30 ++++++++ components/vfs/include/esp_vfs.h | 4 + components/vfs/vfs.c | 14 ++++ 9 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 components/newlib/platform_include/sys/unistd.h diff --git a/components/fatfs/src/ff.c b/components/fatfs/src/ff.c index 992a4ef1a..4225a044c 100644 --- a/components/fatfs/src/ff.c +++ b/components/fatfs/src/ff.c @@ -4694,7 +4694,6 @@ FRESULT f_truncate ( FATFS *fs; DWORD ncl; - res = validate(&fp->obj, &fs); /* Check validity of the file object */ if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); if (!(fp->flag & FA_WRITE)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */ diff --git a/components/fatfs/src/vfs_fat.c b/components/fatfs/src/vfs_fat.c index 9a1450c79..6d7019a1e 100644 --- a/components/fatfs/src/vfs_fat.c +++ b/components/fatfs/src/vfs_fat.c @@ -85,6 +85,7 @@ static int vfs_fat_closedir(void* ctx, DIR* pdir); static int vfs_fat_mkdir(void* ctx, const char* name, mode_t mode); static int vfs_fat_rmdir(void* ctx, const char* name); static int vfs_fat_access(void* ctx, const char *path, int amode); +static int vfs_fat_truncate(void* ctx, const char *path, off_t length); static vfs_fat_ctx_t* s_fat_ctxs[FF_VOLUMES] = { NULL, NULL }; //backwards-compatibility with esp_vfs_fat_unregister() @@ -144,6 +145,7 @@ esp_err_t esp_vfs_fat_register(const char* base_path, const char* fat_drive, siz .mkdir_p = &vfs_fat_mkdir, .rmdir_p = &vfs_fat_rmdir, .access_p = &vfs_fat_access, + .truncate_p = &vfs_fat_truncate, }; size_t ctx_size = sizeof(vfs_fat_ctx_t) + max_files * sizeof(FIL); vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) calloc(1, ctx_size); @@ -751,3 +753,74 @@ static int vfs_fat_access(void* ctx, const char *path, int amode) return ret; } + +static int vfs_fat_truncate(void* ctx, const char *path, off_t length) +{ + FRESULT res; + FIL* file; + + int ret = 0; + + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + + _lock_acquire(&fat_ctx->lock); + prepend_drive_to_path(fat_ctx, &path, NULL); + + file = (FIL*) calloc(1, sizeof(FIL)); + if (file == NULL) { + ESP_LOGD(TAG, "truncate alloc failed"); + errno = ENOMEM; + ret = -1; + goto out; + } + + res = f_open(file, path, FA_WRITE); + + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + ret = -1; + goto out; + } + + res = f_size(file); + + if (res < length) { + ESP_LOGD(TAG, "truncate does not support extending size"); + errno = EPERM; + ret = -1; + goto close; + } + + 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 close; + } + + res = f_truncate(file); + + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + ret = -1; + } + +close: + res = f_close(file); + + if (res != FR_OK) { + ESP_LOGE(TAG, "closing file opened for truncate failed"); + // Overwrite previous errors, since not being able to close + // an opened file is a more critical issue. + errno = fresult_to_errno(res); + ret = -1; + } + +out: + free(file); + _lock_release(&fat_ctx->lock); + return ret; +} diff --git a/components/fatfs/test/test_fatfs_common.c b/components/fatfs/test/test_fatfs_common.c index 7390a7475..0e8479415 100644 --- a/components/fatfs/test/test_fatfs_common.c +++ b/components/fatfs/test/test_fatfs_common.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "unity.h" #include "esp_log.h" #include "esp_system.h" @@ -137,6 +138,81 @@ void test_fatfs_lseek(const char* filename) TEST_ASSERT_EQUAL(0, fclose(f)); } +void test_fatfs_truncate_file(const char* filename) +{ + int read = 0; + int truncated_len = 0; + + const char input[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + char output[sizeof(input)]; + + FILE* f = fopen(filename, "wb"); + + TEST_ASSERT_NOT_NULL(f); + TEST_ASSERT_EQUAL(strlen(input), fprintf(f, input)); + + TEST_ASSERT_EQUAL(0, fclose(f)); + + + // Extending file beyond size is not supported + 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, EPERM); + + + // Truncating should succeed + const char truncated_1[] = "ABCDEFGHIJ"; + truncated_len = strlen(truncated_1); + + TEST_ASSERT_EQUAL(0, truncate(filename, truncated_len)); + + f = fopen(filename, "rb"); + TEST_ASSERT_NOT_NULL(f); + + memset(output, 0, sizeof(output)); + read = fread(output, 1, sizeof(output), f); + + TEST_ASSERT_EQUAL(truncated_len, read); + TEST_ASSERT_EQUAL_STRING_LEN(truncated_1, output, truncated_len); + + TEST_ASSERT_EQUAL(0, fclose(f)); + + + // 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) + 1)); + TEST_ASSERT_EQUAL(EPERM, errno); + + TEST_ASSERT_EQUAL(-1, truncate(filename, -1)); + TEST_ASSERT_EQUAL(EPERM, 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)); + + f = fopen(filename, "rb"); + TEST_ASSERT_NOT_NULL(f); + + memset(output, 0, sizeof(output)); + read = fread(output, 1, sizeof(output), f); + + TEST_ASSERT_EQUAL(truncated_len, read); + TEST_ASSERT_EQUAL_STRING_LEN(truncated_2, output, truncated_len); + + TEST_ASSERT_EQUAL(0, fclose(f)); +} + void test_fatfs_stat(const char* filename, const char* root_dir) { struct tm tm; diff --git a/components/fatfs/test/test_fatfs_common.h b/components/fatfs/test/test_fatfs_common.h index ba7fbfcbb..0e0f8c6d6 100644 --- a/components/fatfs/test/test_fatfs_common.h +++ b/components/fatfs/test/test_fatfs_common.h @@ -46,6 +46,8 @@ 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_stat(const char* filename, const char* root_dir); void test_fatfs_unlink(const char* filename); @@ -63,3 +65,4 @@ void test_fatfs_opendir_readdir_rewinddir(const char* dir_prefix); void test_fatfs_opendir_readdir_rewinddir_utf_8(const char* dir_prefix); void test_fatfs_rw_speed(const char* filename, void* buf, size_t buf_size, size_t file_size, bool write); + diff --git a/components/fatfs/test/test_fatfs_sdmmc.c b/components/fatfs/test/test_fatfs_sdmmc.c index c81302207..c523c0e0e 100644 --- a/components/fatfs/test/test_fatfs_sdmmc.c +++ b/components/fatfs/test/test_fatfs_sdmmc.c @@ -95,7 +95,6 @@ TEST_CASE("(SD) overwrite and append file", "[fatfs][sd][test_env=UT_T1_SDMODE]" test_teardown(); } - TEST_CASE("(SD) can lseek", "[fatfs][sd][test_env=UT_T1_SDMODE]") { test_setup(); @@ -103,6 +102,13 @@ TEST_CASE("(SD) can lseek", "[fatfs][sd][test_env=UT_T1_SDMODE]") test_teardown(); } +TEST_CASE("(SD) can truncate", "[fatfs][sd][test_env=UT_T1_SDMODE]") +{ + test_setup(); + test_fatfs_truncate_file("/sdcard/truncate.txt"); + test_teardown(); +} + TEST_CASE("(SD) stat returns correct values", "[fatfs][test_env=UT_T1_SDMODE]") { test_setup(); diff --git a/components/fatfs/test/test_fatfs_spiflash.c b/components/fatfs/test/test_fatfs_spiflash.c index 70f73f0e5..38ea765dd 100644 --- a/components/fatfs/test/test_fatfs_spiflash.c +++ b/components/fatfs/test/test_fatfs_spiflash.c @@ -96,6 +96,13 @@ TEST_CASE("(WL) can lseek", "[fatfs][wear_levelling]") test_teardown(); } +TEST_CASE("(WL) can truncate", "[fatfs][wear_levelling]") +{ + test_setup(); + test_fatfs_truncate_file("/spiflash/truncate.txt"); + test_teardown(); +} + TEST_CASE("(WL) stat returns correct values", "[fatfs][wear_levelling]") { test_setup(); diff --git a/components/newlib/platform_include/sys/unistd.h b/components/newlib/platform_include/sys/unistd.h new file mode 100644 index 000000000..e76c84b90 --- /dev/null +++ b/components/newlib/platform_include/sys/unistd.h @@ -0,0 +1,30 @@ +// 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. + + +#ifndef _ESP_SYS_UNISTD_H +#define _ESP_SYS_UNISTD_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include_next + +int _EXFUN(truncate, (const char *, off_t __length)); + +#ifdef __cplusplus +} +#endif +#endif /* _SYS_UNISTD_H */ diff --git a/components/vfs/include/esp_vfs.h b/components/vfs/include/esp_vfs.h index 61b01b76f..4d847274b 100644 --- a/components/vfs/include/esp_vfs.h +++ b/components/vfs/include/esp_vfs.h @@ -174,6 +174,10 @@ typedef struct int (*access_p)(void* ctx, const char *path, int amode); int (*access)(const char *path, int amode); }; + union { + int (*truncate_p)(void* ctx, const char *path, off_t length); + int (*truncate)(const char *path, off_t length); + }; /** start_select is called for setting up synchronous I/O multiplexing of the desired file descriptors in the given VFS */ esp_err_t (*start_select)(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, SemaphoreHandle_t *signal_sem); /** socket select function for socket FDs with the functionality of POSIX select(); this should be set only for the socket VFS */ diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c index 778db912a..f0a195992 100644 --- a/components/vfs/vfs.c +++ b/components/vfs/vfs.c @@ -717,6 +717,20 @@ int access(const char *path, int amode) return ret; } +int truncate(const char *path, off_t length) +{ + int ret; + const vfs_entry_t* vfs = get_vfs_for_path(path); + struct _reent* r = __getreent(); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const char* path_within_vfs = translate_path(vfs, path); + CHECK_AND_CALL(ret, r, vfs, truncate, path_within_vfs, length); + return ret; +} + static void call_end_selects(int end_index, const fds_triple_t *vfs_fds_triple) { for (int i = 0; i < end_index; ++i) {