From 41062bea99a2289dd674b4bcbb2ebe97f1e94c6c Mon Sep 17 00:00:00 2001 From: Roland Dobai Date: Mon, 1 Jul 2019 11:08:57 +0200 Subject: [PATCH] VFS: Implement pread() and pwrite() Closes https://github.com/espressif/esp-idf/issues/3515 --- components/fatfs/test/test_fatfs_common.c | 84 +++++++++++++++++++++ components/fatfs/test/test_fatfs_common.h | 4 + components/fatfs/test/test_fatfs_sdmmc.c | 14 ++++ components/fatfs/test/test_fatfs_spiflash.c | 15 ++++ components/fatfs/vfs/vfs_fat.c | 82 ++++++++++++++++++++ components/newlib/CMakeLists.txt | 2 + components/newlib/pread.c | 21 ++++++ components/newlib/pwrite.c | 21 ++++++ components/vfs/include/esp_vfs.h | 37 +++++++++ components/vfs/vfs.c | 27 +++++++ 10 files changed, 307 insertions(+) create mode 100644 components/newlib/pread.c create mode 100644 components/newlib/pwrite.c diff --git a/components/fatfs/test/test_fatfs_common.c b/components/fatfs/test/test_fatfs_common.c index 77e53707c..09e293826 100644 --- a/components/fatfs/test/test_fatfs_common.c +++ b/components/fatfs/test/test_fatfs_common.c @@ -16,8 +16,10 @@ #include #include #include +#include #include #include +#include #include #include #include "unity.h" @@ -98,6 +100,88 @@ void test_fatfs_read_file_utf_8(const char* filename) TEST_ASSERT_EQUAL(0, fclose(f)); } +void test_fatfs_pread_file(const char* filename) +{ + char buf[32] = { 0 }; + const int fd = open(filename, O_RDONLY); + TEST_ASSERT_NOT_EQUAL(-1, fd); + + int r = pread(fd, buf, sizeof(buf), 0); // it is a regular read() with offset==0 + TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str, buf)); + TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str), r); + + memset(buf, 0, sizeof(buf)); + r = pread(fd, buf, sizeof(buf), 1); // offset==1 + TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str + 1, buf)); + TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str) - 1, r); + + memset(buf, 0, sizeof(buf)); + r = pread(fd, buf, sizeof(buf), 5); // offset==5 + TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str + 5, buf)); + TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str) - 5, r); + + // regular read() should work now because pread() should not affect the current position in file + + memset(buf, 0, sizeof(buf)); + r = read(fd, buf, sizeof(buf)); // note that this is read() and not pread() + TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str, buf)); + TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str), r); + + memset(buf, 0, sizeof(buf)); + r = pread(fd, buf, sizeof(buf), 10); // offset==10 + TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str + 10, buf)); + TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str) - 10, r); + + memset(buf, 0, sizeof(buf)); + r = pread(fd, buf, sizeof(buf), strlen(fatfs_test_hello_str) + 1); // offset to EOF + TEST_ASSERT_EQUAL(0, r); + + TEST_ASSERT_EQUAL(0, close(fd)); +} + +static void test_pwrite(const char *filename, off_t offset, const char *msg) +{ + const int fd = open(filename, O_WRONLY); + TEST_ASSERT_NOT_EQUAL(-1, fd); + + const off_t current_pos = lseek(fd, 0, SEEK_END); // O_APPEND is not the same - jumps to the end only before write() + + const int r = pwrite(fd, msg, strlen(msg), offset); + TEST_ASSERT_EQUAL(strlen(msg), r); + + TEST_ASSERT_EQUAL(current_pos, lseek(fd, 0, SEEK_CUR)); // pwrite should not move the pointer + + TEST_ASSERT_EQUAL(0, close(fd)); +} + +static void test_file_content(const char *filename, const char *msg) +{ + char buf[32] = { 0 }; + const int fd = open(filename, O_RDONLY); + TEST_ASSERT_NOT_EQUAL(-1, fd); + + int r = read(fd, buf, sizeof(buf)); + TEST_ASSERT_NOT_EQUAL(-1, r); + TEST_ASSERT_EQUAL(0, strcmp(msg, buf)); + + TEST_ASSERT_EQUAL(0, close(fd)); +} + +void test_fatfs_pwrite_file(const char *filename) +{ + int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC); + TEST_ASSERT_NOT_EQUAL(-1, fd); + TEST_ASSERT_EQUAL(0, close(fd)); + + test_pwrite(filename, 0, "Hello"); + test_file_content(filename, "Hello"); + + test_pwrite(filename, strlen("Hello"), ", world!"); + test_file_content(filename, "Hello, world!"); + test_pwrite(filename, strlen("Hello, "), "Dolly"); + test_file_content(filename, "Hello, Dolly!"); +} + void test_fatfs_open_max_files(const char* filename_prefix, size_t files_count) { FILE** files = calloc(files_count, sizeof(FILE*)); diff --git a/components/fatfs/test/test_fatfs_common.h b/components/fatfs/test/test_fatfs_common.h index ba330b6b6..0a792434b 100644 --- a/components/fatfs/test/test_fatfs_common.h +++ b/components/fatfs/test/test_fatfs_common.h @@ -41,6 +41,10 @@ void test_fatfs_read_file(const char* filename); void test_fatfs_read_file_utf_8(const char* filename); +void test_fatfs_pread_file(const char* filename); + +void test_fatfs_pwrite_file(const char* filename); + void test_fatfs_open_max_files(const char* filename_prefix, size_t files_count); void test_fatfs_lseek(const char* filename); diff --git a/components/fatfs/test/test_fatfs_sdmmc.c b/components/fatfs/test/test_fatfs_sdmmc.c index b2df8f010..50e1eb5a0 100644 --- a/components/fatfs/test/test_fatfs_sdmmc.c +++ b/components/fatfs/test/test_fatfs_sdmmc.c @@ -86,6 +86,20 @@ TEST_CASE("(SD) can read file", "[fatfs][test_env=UT_T1_SDMODE]") test_teardown(); } +TEST_CASE("(SD) can read file with pread()", "[fatfs][test_env=UT_T1_SDMODE]") +{ + test_setup(); + test_fatfs_create_file_with_text(test_filename, fatfs_test_hello_str); + test_fatfs_pread_file(test_filename); + test_teardown(); +} + +TEST_CASE("(SD) pwrite() works well", "[fatfs][test_env=UT_T1_SDMODE]") +{ + test_setup(); + test_fatfs_pwrite_file(test_filename); + test_teardown(); +} TEST_CASE("(SD) overwrite and append file", "[fatfs][sd][test_env=UT_T1_SDMODE]") { diff --git a/components/fatfs/test/test_fatfs_spiflash.c b/components/fatfs/test/test_fatfs_spiflash.c index 6c3355939..f848c31ca 100644 --- a/components/fatfs/test/test_fatfs_spiflash.c +++ b/components/fatfs/test/test_fatfs_spiflash.c @@ -70,6 +70,21 @@ TEST_CASE("(WL) can read file", "[fatfs][wear_levelling]") test_teardown(); } +TEST_CASE("(WL) can read file with pread", "[fatfs][wear_levelling]") +{ + test_setup(); + 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) pwrite() works well", "[fatfs][wear_levelling]") +{ + test_setup(); + test_fatfs_pwrite_file("/spiflash/hello.txt"); + test_teardown(); +} + TEST_CASE("(WL) can open maximum number of files", "[fatfs][wear_levelling]") { size_t max_files = FOPEN_MAX - 3; /* account for stdin, stdout, stderr */ diff --git a/components/fatfs/vfs/vfs_fat.c b/components/fatfs/vfs/vfs_fat.c index 1ee269590..511be8c56 100644 --- a/components/fatfs/vfs/vfs_fat.c +++ b/components/fatfs/vfs/vfs_fat.c @@ -68,6 +68,8 @@ static const char* TAG = "vfs_fat"; static ssize_t vfs_fat_write(void* p, int fd, const void * data, size_t size); static off_t vfs_fat_lseek(void* p, int fd, off_t size, int mode); static ssize_t vfs_fat_read(void* ctx, int fd, void * dst, size_t size); +static ssize_t vfs_fat_pread(void *ctx, int fd, void *dst, size_t size, off_t offset); +static ssize_t vfs_fat_pwrite(void *ctx, int fd, const void *src, size_t size, off_t offset); 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); @@ -129,6 +131,8 @@ esp_err_t esp_vfs_fat_register(const char* base_path, const char* fat_drive, siz .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, @@ -373,6 +377,84 @@ static ssize_t vfs_fat_read(void* ctx, int fd, void * dst, size_t size) return read; } +static ssize_t vfs_fat_pread(void *ctx, int fd, void *dst, size_t size, off_t offset) +{ + ssize_t ret = -1; + vfs_fat_ctx_t *fat_ctx = (vfs_fat_ctx_t *) ctx; + _lock_acquire(&fat_ctx->lock); + FIL *file = &fat_ctx->files[fd]; + const off_t prev_pos = f_tell(file); + + FRESULT f_res = f_lseek(file, offset); + if (f_res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, f_res); + errno = fresult_to_errno(f_res); + goto pread_release; + } + + unsigned read = 0; + f_res = f_read(file, dst, size, &read); + if (f_res == FR_OK) { + ret = read; + } else { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, f_res); + errno = fresult_to_errno(f_res); + // No return yet - need to restore previous position + } + + f_res = f_lseek(file, prev_pos); + if (f_res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, f_res); + if (ret >= 0) { + errno = fresult_to_errno(f_res); + } // else f_read failed so errno shouldn't be overwritten + ret = -1; // in case the read was successful but the seek wasn't + } + +pread_release: + _lock_release(&fat_ctx->lock); + return ret; +} + +static ssize_t vfs_fat_pwrite(void *ctx, int fd, const void *src, size_t size, off_t offset) +{ + ssize_t ret = -1; + vfs_fat_ctx_t *fat_ctx = (vfs_fat_ctx_t *) ctx; + _lock_acquire(&fat_ctx->lock); + FIL *file = &fat_ctx->files[fd]; + const off_t prev_pos = f_tell(file); + + FRESULT f_res = f_lseek(file, offset); + if (f_res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, f_res); + errno = fresult_to_errno(f_res); + goto pwrite_release; + } + + unsigned wr = 0; + f_res = f_write(file, src, size, &wr); + if (f_res == FR_OK) { + ret = wr; + } else { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, f_res); + errno = fresult_to_errno(f_res); + // No return yet - need to restore previous position + } + + f_res = f_lseek(file, prev_pos); + if (f_res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, f_res); + if (ret >= 0) { + errno = fresult_to_errno(f_res); + } // else f_write failed so errno shouldn't be overwritten + ret = -1; // in case the write was successful but the seek wasn't + } + +pwrite_release: + _lock_release(&fat_ctx->lock); + return ret; +} + static int vfs_fat_fsync(void* ctx, int fd) { vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; diff --git a/components/newlib/CMakeLists.txt b/components/newlib/CMakeLists.txt index 47b7c368b..ccd5a7669 100644 --- a/components/newlib/CMakeLists.txt +++ b/components/newlib/CMakeLists.txt @@ -2,6 +2,8 @@ set(srcs "heap.c" "locks.c" "poll.c" + "pread.c" + "pwrite.c" "pthread.c" "random.c" "reent_init.c" diff --git a/components/newlib/pread.c b/components/newlib/pread.c new file mode 100644 index 000000000..d14bd7257 --- /dev/null +++ b/components/newlib/pread.c @@ -0,0 +1,21 @@ +// Copyright 2019 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. + +#include +#include "esp_vfs.h" + +ssize_t pread(int fd, void *dst, size_t size, off_t offset) +{ + return esp_vfs_pread(fd, dst, size, offset); +} diff --git a/components/newlib/pwrite.c b/components/newlib/pwrite.c new file mode 100644 index 000000000..78fd0b85c --- /dev/null +++ b/components/newlib/pwrite.c @@ -0,0 +1,21 @@ +// Copyright 2019 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. + +#include +#include "esp_vfs.h" + +ssize_t pwrite(int fd, const void *src, size_t size, off_t offset) +{ + return esp_vfs_pwrite(fd, src, size, offset); +} diff --git a/components/vfs/include/esp_vfs.h b/components/vfs/include/esp_vfs.h index 3e711f254..4d9396c1c 100644 --- a/components/vfs/include/esp_vfs.h +++ b/components/vfs/include/esp_vfs.h @@ -112,6 +112,14 @@ typedef struct ssize_t (*read_p)(void* ctx, int fd, void * dst, size_t size); ssize_t (*read)(int fd, void * dst, size_t size); }; + union { + ssize_t (*pread_p)(void *ctx, int fd, void * dst, size_t size, off_t offset); + ssize_t (*pread)(int fd, void * dst, size_t size, off_t offset); + }; + union { + ssize_t (*pwrite_p)(void *ctx, int fd, const void *src, size_t size, off_t offset); + ssize_t (*pwrite)(int fd, const void *src, size_t size, off_t offset); + }; union { int (*open_p)(void* ctx, const char * path, int flags, int mode); int (*open)(const char * path, int flags, int mode); @@ -414,6 +422,35 @@ void esp_vfs_select_triggered_isr(esp_vfs_select_sem_t sem, BaseType_t *woken); */ int esp_vfs_poll(struct pollfd *fds, nfds_t nfds, int timeout); + +/** + * + * @brief Implements the VFS layer of POSIX pread() + * + * @param fd File descriptor used for read + * @param dst Pointer to the buffer where the output will be written + * @param size Number of bytes to be read + * @param offset Starting offset of the read + * + * @return A positive return value indicates the number of bytes read. -1 is return on failure and errno is + * set accordingly. + */ +ssize_t esp_vfs_pread(int fd, void *dst, size_t size, off_t offset); + +/** + * + * @brief Implements the VFS layer of POSIX pwrite() + * + * @param fd File descriptor used for write + * @param src Pointer to the buffer from where the output will be read + * @param size Number of bytes to write + * @param offset Starting offset of the write + * + * @return A positive return value indicates the number of bytes written. -1 is return on failure and errno is + * set accordingly. + */ +ssize_t esp_vfs_pwrite(int fd, const void *src, size_t size, off_t offset); + #ifdef __cplusplus } // extern "C" #endif diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c index ecbaf6acf..0fc5aead3 100644 --- a/components/vfs/vfs.c +++ b/components/vfs/vfs.c @@ -447,6 +447,33 @@ ssize_t esp_vfs_read(struct _reent *r, int fd, void * dst, size_t size) return ret; } +ssize_t esp_vfs_pread(int fd, void *dst, size_t size, off_t offset) +{ + struct _reent *r = __getreent(); + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + const int local_fd = get_local_fd(vfs, fd); + if (vfs == NULL || local_fd < 0) { + __errno_r(r) = EBADF; + return -1; + } + ssize_t ret; + CHECK_AND_CALL(ret, r, vfs, pread, local_fd, dst, size, offset); + return ret; +} + +ssize_t esp_vfs_pwrite(int fd, const void *src, size_t size, off_t offset) +{ + struct _reent *r = __getreent(); + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + const int local_fd = get_local_fd(vfs, fd); + if (vfs == NULL || local_fd < 0) { + __errno_r(r) = EBADF; + return -1; + } + ssize_t ret; + CHECK_AND_CALL(ret, r, vfs, pwrite, local_fd, src, size, offset); + return ret; +} int esp_vfs_close(struct _reent *r, int fd) {