diff --git a/components/dfs/dfs_v2/filesystems/tmpfs/dfs_tmpfs.c b/components/dfs/dfs_v2/filesystems/tmpfs/dfs_tmpfs.c index 5f9299c11e43..dbed9fb7bc69 100644 --- a/components/dfs/dfs_v2/filesystems/tmpfs/dfs_tmpfs.c +++ b/components/dfs/dfs_v2/filesystems/tmpfs/dfs_tmpfs.c @@ -1022,11 +1022,28 @@ static int dfs_tmpfs_truncate(struct dfs_file *file, off_t offset) superblock = d_file->sb; RT_ASSERT(superblock != RT_NULL); - ptr = rt_realloc(d_file->data, offset); - if (ptr == RT_NULL) + if (offset == 0) { - rt_set_errno(-ENOMEM); - return 0; + /* rt_realloc(ptr, 0) frees the block and returns RT_NULL, which cannot + * be told apart from an allocation failure. Handle the empty case + * explicitly, otherwise d_file->data is left dangling. */ + if (d_file->data != RT_NULL) + { + rt_free(d_file->data); + d_file->data = RT_NULL; + } + ptr = RT_NULL; + } + else + { + ptr = rt_realloc(d_file->data, offset); + if (ptr == RT_NULL) + { + rt_set_errno(-ENOMEM); + return -ENOMEM; + } + + d_file->data = ptr; } rt_spin_lock(&superblock->lock); @@ -1034,7 +1051,6 @@ static int dfs_tmpfs_truncate(struct dfs_file *file, off_t offset) rt_spin_unlock(&superblock->lock); /* update d_file and file size */ - d_file->data = ptr; d_file->size = offset; file->vnode->size = d_file->size; LOG_D("tmpfile ptr:%x, size:%d", ptr, d_file->size); diff --git a/components/dfs/dfs_v2/filesystems/tmpfs/utest/Kconfig b/components/dfs/dfs_v2/filesystems/tmpfs/utest/Kconfig index 96f36468f5f7..ee7951bb22f4 100644 --- a/components/dfs/dfs_v2/filesystems/tmpfs/utest/Kconfig +++ b/components/dfs/dfs_v2/filesystems/tmpfs/utest/Kconfig @@ -1,5 +1,12 @@ menu "Tmpfs Testcase" +config RT_UTEST_TMPFS_TRUNCATE + bool "tmpfs truncate test" + default n + depends on RT_USING_POSIX_FS && RT_USING_DFS_TMPFS + help + Test zero-length truncation, subsequent I/O and allocation failures. + config RT_UTEST_TMPFS_CP bool "tmpfs cp test" default n diff --git a/components/dfs/dfs_v2/filesystems/tmpfs/utest/SConscript b/components/dfs/dfs_v2/filesystems/tmpfs/utest/SConscript index 8449323d7499..85275a37eda3 100644 --- a/components/dfs/dfs_v2/filesystems/tmpfs/utest/SConscript +++ b/components/dfs/dfs_v2/filesystems/tmpfs/utest/SConscript @@ -5,6 +5,9 @@ cwd = GetCurrentDir() src = [] CPPPATH = [cwd] +if GetDepend(['RT_UTEST_TMPFS_TRUNCATE']): + src += ['tc_truncate.c'] + if GetDepend(['RT_USING_SMART','RT_UTEST_TMPFS_CP']): src += ['tmpfs.c'] diff --git a/components/dfs/dfs_v2/filesystems/tmpfs/utest/tc_truncate.c b/components/dfs/dfs_v2/filesystems/tmpfs/utest/tc_truncate.c new file mode 100644 index 000000000000..83ed37823d87 --- /dev/null +++ b/components/dfs/dfs_v2/filesystems/tmpfs/utest/tc_truncate.c @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include "utest.h" + +#define TEST_MOUNT "/tmpfs-truncate" +#define TEST_PATH TEST_MOUNT "/file" + +static const char payload[] = "0123456789abcdef"; + +static int create_file(void) +{ + int fd = open(TEST_PATH, O_CREAT | O_RDWR | O_TRUNC, 0600); + + uassert_true(fd >= 0); + if (fd < 0) + { + return -1; + } + + uassert_int_equal(write(fd, payload, sizeof(payload) - 1), sizeof(payload) - 1); + /* Closing flushes dirty pages before testing the backing buffer. */ + uassert_int_equal(close(fd), 0); + fd = open(TEST_PATH, O_RDWR, 0); + uassert_true(fd >= 0); + return fd; +} + +static void test_zero_length(void) +{ + struct stat st; + char buffer[sizeof(payload)]; + int fd = create_file(); + + if (fd < 0) + { + return; + } + + uassert_int_equal(ftruncate(fd, 0), 0); + uassert_int_equal(fstat(fd, &st), 0); + uassert_int_equal(st.st_size, 0); + uassert_int_equal(lseek(fd, 0, SEEK_SET), 0); + uassert_int_equal(read(fd, buffer, sizeof(buffer)), 0); + + /* Truncating an already empty file must also be safe. */ + uassert_int_equal(ftruncate(fd, 0), 0); + uassert_int_equal(write(fd, payload, sizeof(payload) - 1), sizeof(payload) - 1); + uassert_int_equal(lseek(fd, 0, SEEK_SET), 0); + uassert_int_equal(read(fd, buffer, sizeof(payload) - 1), sizeof(payload) - 1); + uassert_int_equal(rt_memcmp(buffer, payload, sizeof(payload) - 1), 0); + uassert_int_equal(close(fd), 0); + fd = open(TEST_PATH, O_RDONLY, 0); + uassert_true(fd >= 0); + if (fd >= 0) + { + uassert_int_equal(fstat(fd, &st), 0); + uassert_int_equal(st.st_size, sizeof(payload) - 1); + uassert_int_equal(close(fd), 0); + } + uassert_int_equal(unlink(TEST_PATH), 0); +} + +static void test_nonzero_length(void) +{ + struct stat st; + char buffer[4]; + int fd = create_file(); + + if (fd < 0) + { + return; + } + + uassert_int_equal(ftruncate(fd, sizeof(buffer)), 0); + uassert_int_equal(fstat(fd, &st), 0); + uassert_int_equal(st.st_size, sizeof(buffer)); + uassert_int_equal(lseek(fd, 0, SEEK_SET), 0); + uassert_int_equal(read(fd, buffer, sizeof(buffer)), sizeof(buffer)); + uassert_int_equal(rt_memcmp(buffer, payload, sizeof(buffer)), 0); + + uassert_int_equal(ftruncate(fd, 4096), 0); + uassert_int_equal(fstat(fd, &st), 0); + uassert_int_equal(st.st_size, 4096); + uassert_int_equal(lseek(fd, 0, SEEK_SET), 0); + uassert_int_equal(read(fd, buffer, sizeof(buffer)), sizeof(buffer)); + uassert_int_equal(rt_memcmp(buffer, payload, sizeof(buffer)), 0); + uassert_int_equal(close(fd), 0); + uassert_int_equal(unlink(TEST_PATH), 0); +} + +static void test_allocation_failure(void) +{ + rt_size_t total, used, max_used; + struct stat st; + char buffer[sizeof(payload)]; + int fd = create_file(); + + if (fd < 0) + { + return; + } + + /* An existing allocation cannot grow to the size of the entire heap. */ + rt_memory_info(&total, &used, &max_used); + uassert_true(total > 0); + uassert_true((off_t)total > 0); + errno = 0; + uassert_int_equal(ftruncate(fd, (off_t)total), -1); + uassert_int_equal(errno, -ENOMEM); + uassert_int_equal(fstat(fd, &st), 0); + uassert_int_equal(st.st_size, sizeof(payload) - 1); + uassert_int_equal(lseek(fd, 0, SEEK_SET), 0); + uassert_int_equal(read(fd, buffer, sizeof(payload) - 1), sizeof(payload) - 1); + uassert_int_equal(rt_memcmp(buffer, payload, sizeof(payload) - 1), 0); + uassert_int_equal(close(fd), 0); + uassert_int_equal(unlink(TEST_PATH), 0); +} + +static rt_err_t test_init(void) +{ + if (mkdir(TEST_MOUNT, 0700) != 0 && errno != -EEXIST) + { + return -RT_ERROR; + } + + return dfs_mount(RT_NULL, TEST_MOUNT, "tmp", 0, RT_NULL) == 0 ? RT_EOK : -RT_ERROR; +} + +static rt_err_t test_cleanup(void) +{ + int ret = dfs_unmount(TEST_MOUNT); + + rmdir(TEST_MOUNT); + return ret == 0 ? RT_EOK : -RT_ERROR; +} + +static void test_truncate(void) +{ + test_zero_length(); + test_nonzero_length(); + test_allocation_failure(); +} + +static void testcase(void) +{ + UTEST_UNIT_RUN(test_truncate); +} +UTEST_TC_EXPORT(testcase, "components.dfs.tmpfs_truncate", test_init, test_cleanup, 30);