From c55e1b7bda8e166ad6978dc27364fe616391afb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Wed, 30 Sep 2026 16:26:58 -0400 Subject: [PATCH] Fixed stale first clusters in already-open FileX readers Fixes #97 When a writer allocates a zero-length file's first cluster, FileX updates other open handles but leaves their first cluster at zero. A later seek uses that stale value and reads the wrong sector. Copy the first cluster when initializing other handles opened before the allocation. Add a RAM-disk regression for ordinary and fast readers, and make CTest recognize failures reported by its standalone test harness. GCC 14 CMake/Ninja: the regression failed on dev and passed in four build configurations with the fix. Three existing read/write and seek regressions passed. The new assignment was exercised. No hardware test. Assisted-by: Codex (GPT-6-Sol) --- common/src/fx_file_write.c | 3 + test/cmake/regression/CMakeLists.txt | 5 + .../filex_file_open_empty_reader_test.c | 202 ++++++++++++++++++ 3 files changed, 210 insertions(+) create mode 100644 test/regression_test/filex_file_open_empty_reader_test.c diff --git a/common/src/fx_file_write.c b/common/src/fx_file_write.c index 2133b13..8e80c54 100644 --- a/common/src/fx_file_write.c +++ b/common/src/fx_file_write.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +/* Portions of this file were generated with AI assistance. */ + /**************************************************************************/ /**************************************************************************/ @@ -1321,6 +1323,7 @@ ULONG replace_clusters = 0; /* The number of clusters to /* Setup initial parameters. */ search_ptr -> fx_file_total_clusters = file_ptr -> fx_file_total_clusters; + search_ptr -> fx_file_first_physical_cluster = file_ptr -> fx_file_first_physical_cluster; search_ptr -> fx_file_current_physical_cluster = file_ptr -> fx_file_first_physical_cluster; search_ptr -> fx_file_current_relative_cluster = 0; search_ptr -> fx_file_current_logical_sector = ((ULONG)media_ptr -> fx_media_data_sector_start) + diff --git a/test/cmake/regression/CMakeLists.txt b/test/cmake/regression/CMakeLists.txt index e72989f..9d0e0d7 100644 --- a/test/cmake/regression/CMakeLists.txt +++ b/test/cmake/regression/CMakeLists.txt @@ -108,6 +108,7 @@ set(regression_test_cases ${SOURCE_DIR}/filex_file_date_time_set_test.c ${SOURCE_DIR}/filex_file_naming_test.c ${SOURCE_DIR}/filex_file_read_write_test.c + ${SOURCE_DIR}/filex_file_open_empty_reader_test.c ${SOURCE_DIR}/filex_file_rename_test.c ${SOURCE_DIR}/filex_file_seek_test.c ${SOURCE_DIR}/filex_file_name_test.c @@ -172,6 +173,10 @@ foreach(test_case ${regression_test_cases}) add_test(NAME ${CMAKE_BUILD_TYPE}::${test_name} COMMAND ${test_target}) endforeach() +set_tests_properties( + ${CMAKE_BUILD_TYPE}::filex_file_open_empty_reader_test + PROPERTIES FAIL_REGULAR_EXPRESSION "ERROR CODE") + # These two tests write or corrupt large amounts of data on a 900 MB RAM disk and are # inherently slow (≈95 s and ≈330 s on Windows). Override the default 60-second ctest # timeout so that they are not mis-reported as failures. diff --git a/test/regression_test/filex_file_open_empty_reader_test.c b/test/regression_test/filex_file_open_empty_reader_test.c new file mode 100644 index 0000000..b7fab2c --- /dev/null +++ b/test/regression_test/filex_file_open_empty_reader_test.c @@ -0,0 +1,202 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Codex (GPT-6-Sol). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +#ifndef FX_STANDALONE_ENABLE +#include "tx_api.h" +#endif +#include "fx_api.h" +#include "fx_ram_driver_test.h" + +#define TEST_STACK_SIZE 4096u +#define TEST_CACHE_SIZE 4096u +#define TEST_SECTOR_SIZE 512u + +#ifndef FX_STANDALONE_ENABLE +static TX_THREAD test_thread; +static UCHAR *test_cache; +#else +static UCHAR test_cache[TEST_CACHE_SIZE]; +#endif + +static UCHAR *test_disk_memory; +static FX_MEDIA test_media; +static FX_FILE write_file; +static FX_FILE read_file; +static FX_FILE fast_read_file; +static UCHAR write_data[2u * TEST_SECTOR_SIZE]; +static UCHAR read_data[TEST_SECTOR_SIZE]; + +VOID _fx_ram_driver(FX_MEDIA *media_ptr); +void test_control_return(UINT status); + +/* Check that both readers can seek after the first cluster is allocated. */ +static UINT check_read_handle(FX_FILE *file_ptr) +{ + ULONG actual_size; + UINT index; + UINT status; + + if ((file_ptr -> fx_file_first_physical_cluster == 0u) || + (file_ptr -> fx_file_first_physical_cluster != write_file.fx_file_first_physical_cluster)) + { + return(1u); + } + + status = fx_file_seek(file_ptr, TEST_SECTOR_SIZE); + if (status != FX_SUCCESS) + { + return(2u); + } + + status = fx_file_read(file_ptr, read_data, sizeof(read_data), &actual_size); + if ((status != FX_SUCCESS) || (actual_size != sizeof(read_data))) + { + return(3u); + } + + for (index = 0u; index < sizeof(read_data); index++) + { + if (read_data[index] != (UCHAR)'a') + { + return(4u); + } + } + + return(FX_SUCCESS); +} + +/* Exercise a writer and two readers of the same initially empty file. */ +static UINT run_case(VOID) +{ + UINT index; + UINT status; + + status = fx_media_format(&test_media, _fx_ram_driver, test_disk_memory, + test_cache, TEST_CACHE_SIZE, "RAM DISK", 1u, 32u, + 0u, 512u, TEST_SECTOR_SIZE, 8u, 1u, 1u); + if (status != FX_SUCCESS) + { + return(5u); + } + + status = fx_media_open(&test_media, "RAM DISK", _fx_ram_driver, + test_disk_memory, test_cache, TEST_CACHE_SIZE); + if (status != FX_SUCCESS) + { + return(6u); + } + + status = fx_file_create(&test_media, "TEST.TXT"); + if (status != FX_SUCCESS) + { + return(7u); + } + + status = fx_file_open(&test_media, &write_file, "TEST.TXT", FX_OPEN_FOR_WRITE); + if (status != FX_SUCCESS) + { + return(8u); + } + + status = fx_file_open(&test_media, &read_file, "TEST.TXT", FX_OPEN_FOR_READ); + if (status != FX_SUCCESS) + { + return(9u); + } + + status = fx_file_open(&test_media, &fast_read_file, "TEST.TXT", FX_OPEN_FOR_READ_FAST); + if (status != FX_SUCCESS) + { + return(10u); + } + + for (index = 0u; index < sizeof(write_data); index++) + { + write_data[index] = (UCHAR)'a'; + } + + status = fx_file_write(&write_file, write_data, sizeof(write_data)); + if (status != FX_SUCCESS) + { + return(11u); + } + + status = check_read_handle(&read_file); + if (status == FX_SUCCESS) + { + status = check_read_handle(&fast_read_file); + } + if (status != FX_SUCCESS) + { + return(status); + } + + status = fx_file_close(&fast_read_file); + if (status != FX_SUCCESS) + { + return(12u); + } + + status = fx_file_close(&read_file); + if (status != FX_SUCCESS) + { + return(13u); + } + + status = fx_file_close(&write_file); + if (status != FX_SUCCESS) + { + return(14u); + } + + status = fx_media_close(&test_media); + if (status != FX_SUCCESS) + { + return(15u); + } + + return(FX_SUCCESS); +} + +/* Run the regression from a ThreadX thread or standalone test context. */ +static VOID test_thread_entry(ULONG input) +{ + FX_PARAMETER_NOT_USED(input); + test_control_return(run_case()); +} + +/* Supply the application entry point used by the regression runner. */ +void test_application_define(void *first_unused_memory) +{ +#ifndef FX_STANDALONE_ENABLE + UCHAR *pointer; + + pointer = (UCHAR *)first_unused_memory; + pointer += TEST_STACK_SIZE; + test_cache = pointer; + pointer += TEST_CACHE_SIZE; + test_disk_memory = pointer; + + fx_system_initialize(); + (VOID)tx_thread_create(&test_thread, "empty-file reader", test_thread_entry, + 0u, first_unused_memory, TEST_STACK_SIZE, + 4u, 4u, TX_NO_TIME_SLICE, TX_AUTO_START); +#else + FX_PARAMETER_NOT_USED(first_unused_memory); + test_disk_memory = ram_disk_memory; + fx_system_initialize(); + test_thread_entry(0u); +#endif +}