From ac76f1928581ed64109634210d0f95e6542b53e8 Mon Sep 17 00:00:00 2001 From: Martin Vychodil Date: Wed, 19 Aug 2026 11:41:24 +0200 Subject: [PATCH] fix(fatfs): preserve access mode in VFS F_SETFL F_SETFL was replacing the whole flags word, so fcntl(fd, F_SETFL, O_APPEND) made F_GETFL report O_RDONLY|O_APPEND. Keep O_ACCMODE and apply only POSIX status flags. Co-authored-by: Cursor --- .../flash_wl/main/test_fatfs_flash_wl.c | 7 +++++ .../test_fatfs_common/test_fatfs_common.c | 31 +++++++++++++++++++ .../test_fatfs_common/test_fatfs_common.h | 2 ++ components/fatfs/vfs/vfs_fat.c | 9 ++++-- 4 files changed, 47 insertions(+), 2 deletions(-) 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 da182301781..ca9aa172076 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 @@ -195,6 +195,13 @@ TEST_CASE("(WL) overwrite and append file", "[fatfs][wear_levelling]") test_teardown(); } +TEST_CASE("(WL) fcntl F_SETFL preserves access mode", "[fatfs][wear_levelling]") +{ + test_setup(); + test_fatfs_fcntl_setfl("/spiflash/fcntl.txt"); + test_teardown(); +} + TEST_CASE("(WL) can lseek", "[fatfs][wear_levelling]") { test_setup(); 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 71b34494b86..f24e5acddd7 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 @@ -94,6 +94,37 @@ void test_fatfs_overwrite_append(const char* filename) TEST_ASSERT_EQUAL(0, fclose(f_r)); } +void test_fatfs_fcntl_setfl(const char* filename) +{ + unlink(filename); + int fd = open(filename, O_RDWR | O_CREAT | O_TRUNC); + TEST_ASSERT_NOT_EQUAL(-1, fd); + + int flags = fcntl(fd, F_GETFL); + TEST_ASSERT_NOT_EQUAL(-1, flags); + TEST_ASSERT_EQUAL(O_RDWR, flags & O_ACCMODE); + TEST_ASSERT_EQUAL(0, flags & O_APPEND); + + /* F_SETFL must not clobber O_ACCMODE when arg is only a status flag. */ + TEST_ASSERT_EQUAL(0, fcntl(fd, F_SETFL, O_APPEND)); + flags = fcntl(fd, F_GETFL); + TEST_ASSERT_EQUAL(O_RDWR, flags & O_ACCMODE); + TEST_ASSERT_NOT_EQUAL(0, flags & O_APPEND); + + TEST_ASSERT_EQUAL(4, write(fd, "AAAA", 4)); + TEST_ASSERT_NOT_EQUAL(-1, lseek(fd, 0, SEEK_SET)); + TEST_ASSERT_EQUAL(4, write(fd, "BBBB", 4)); + TEST_ASSERT_EQUAL(0, close(fd)); + + char buf[10] = { 0 }; + FILE *f_r = fopen(filename, "r"); + TEST_ASSERT_NOT_NULL(f_r); + TEST_ASSERT_EQUAL(8, fread(buf, 1, 8, f_r)); + TEST_ASSERT_EQUAL_STRING_LEN("AAAABBBB", buf, 8); + TEST_ASSERT_EQUAL(0, fclose(f_r)); + unlink(filename); +} + void test_fatfs_read_file(const char* filename) { FILE* f = fopen(filename, "r"); 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 9504fd863b9..1253ec338be 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 @@ -33,6 +33,8 @@ void test_fatfs_open_file_with_o_creat_flag(const char* filename); void test_fatfs_overwrite_append(const char* filename); +void test_fatfs_fcntl_setfl(const char* filename); + void test_fatfs_read_file(const char* filename); void test_fatfs_read_file_utf_8(const char* filename); diff --git a/components/fatfs/vfs/vfs_fat.c b/components/fatfs/vfs/vfs_fat.c index 4595f91c00e..939c4215ac9 100644 --- a/components/fatfs/vfs/vfs_fat.c +++ b/components/fatfs/vfs/vfs_fat.c @@ -719,10 +719,15 @@ static int vfs_fat_fcntl(void* ctx, int fd, int cmd, int arg) case F_GETFL: result = fat_ctx->flags[fd]; break; - case F_SETFL: - fat_ctx->flags[fd] = arg; + case F_SETFL: { + /* POSIX: F_SETFL changes status flags only; access mode is immutable. + * FatFS honors O_APPEND on write(). O_NONBLOCK is stored for F_GETFL + * round-trip but does not make I/O non-blocking. */ + const uint32_t setfl_mask = (uint32_t)(O_APPEND | O_NONBLOCK); + fat_ctx->flags[fd] = (fat_ctx->flags[fd] & ~setfl_mask) | ((uint32_t)arg & setfl_mask); result = 0; break; + } // no-ops: case F_SETLK: case F_SETLKW: