From c327e6926f39291bb78b37b07ffecc9b8b303662 Mon Sep 17 00:00:00 2001 From: aineoae86-sys Date: Wed, 1 Jul 2026 16:21:15 +0800 Subject: [PATCH 1/2] Fix empty directory checks with wildcard find API --- ff_file.c | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/ff_file.c b/ff_file.c index fbae1da..bfbade5 100644 --- a/ff_file.c +++ b/ff_file.c @@ -512,6 +512,8 @@ static FF_FILE * prvAllocFileHandle( FF_IOManager_t * pxIOManager, FF_DirEnt_t xDirEntry; FF_Error_t xError = FF_ERR_NONE; BaseType_t xReturn; + uint32_t ulDirCluster; + uint16_t usPathLength; if( pxIOManager == NULL ) { @@ -519,7 +521,46 @@ static FF_FILE * prvAllocFileHandle( FF_IOManager_t * pxIOManager, } else { - xError = FF_FindFirst( pxIOManager, &xDirEntry, pcPath ); + memset( &xDirEntry, 0, sizeof( xDirEntry ) ); + + #if ( ffconfigUNICODE_UTF16_SUPPORT != 0 ) + usPathLength = ( uint16_t ) wcslen( pcPath ); + #else + usPathLength = ( uint16_t ) strlen( pcPath ); + #endif + + #if ( ffconfigREMOVABLE_MEDIA != 0 ) + if( ( pxIOManager->ucFlags & FF_IOMAN_DEVICE_IS_EXTRACTED ) != 0 ) + { + xError = FF_createERR( FF_ERR_IOMAN_DRIVER_NOMEDIUM, FF_ISDIREMPTY ); + } + else + #endif /* ffconfigREMOVABLE_MEDIA */ + { + /* FF_isDirEmpty() must enumerate the directory contents. Calling + * FF_FindFirst() would let wildcard mode reinterpret a path without + * a trailing separator as the directory entry itself. */ + ulDirCluster = FF_FindDir( pxIOManager, pcPath, usPathLength, &xError ); + + if( FF_isERR( xError ) == pdFALSE ) + { + if( ulDirCluster == 0UL ) + { + xError = FF_createERR( FF_ERR_DIR_INVALID_PATH, FF_ISDIREMPTY ); + } + else + { + xDirEntry.ulDirCluster = ulDirCluster; + xError = FF_InitEntryFetch( pxIOManager, ulDirCluster, &( xDirEntry.xFetchContext ) ); + + if( FF_isERR( xError ) == pdFALSE ) + { + xDirEntry.usCurrentItem = 0; + xError = FF_FindNext( pxIOManager, &xDirEntry ); + } + } + } + } /* Assume the directory is empty until a file is * encountered with a name other than ".." or "." */ From 54641ba952ee5dd106eff20d64087697ef30fa52 Mon Sep 17 00:00:00 2001 From: Old-Ding <35417409+Old-Ding@users.noreply.github.com> Date: Sat, 11 Jul 2026 00:10:35 +0800 Subject: [PATCH 2/2] test: cover empty directory path handling Document that FAT12/FAT16 roots are normalized to cluster 1 and exercise root directory and trailing separator behavior. Signed-off-by: Old-Ding <35417409+Old-Ding@users.noreply.github.com> --- ff_file.c | 3 + test/unit-test/CMakeLists.txt | 59 +++++++++- test/unit-test/ff_file_dir_empty_utest.c | 107 ++++++++++++++++++ .../include/ff_dir_is_empty_mock_subset.h | 20 ++++ 4 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 test/unit-test/ff_file_dir_empty_utest.c create mode 100644 test/unit-test/include/ff_dir_is_empty_mock_subset.h diff --git a/ff_file.c b/ff_file.c index bfbade5..e07e605 100644 --- a/ff_file.c +++ b/ff_file.c @@ -546,6 +546,9 @@ static FF_FILE * prvAllocFileHandle( FF_IOManager_t * pxIOManager, { if( ulDirCluster == 0UL ) { + /* FAT12/FAT16 mounts represent their fixed root directory + * with cluster 1, so zero remains the lookup-failure + * sentinel for every FAT type. */ xError = FF_createERR( FF_ERR_DIR_INVALID_PATH, FF_ISDIREMPTY ); } else diff --git a/test/unit-test/CMakeLists.txt b/test/unit-test/CMakeLists.txt index 46eb7e8..c764c33 100644 --- a/test/unit-test/CMakeLists.txt +++ b/test/unit-test/CMakeLists.txt @@ -108,6 +108,63 @@ create_test( ${utest_name} "${utest_link_list}" "${utest_dep_list}" "${test_include_directories}" ) +set( coverage_dep_list ${utest_name} ) + +set( project_name "ff_file_dir_empty" ) + +# ===================== Mocks ================================================ +# Keep this unit focused on FF_isDirEmpty() and discard unrelated ff_file APIs. +set( mock_list "" ) +list( APPEND mock_list + ${UNIT_TEST_DIR}/include/ff_dir_is_empty_mock_subset.h ) + +set( mock_include_list ${FAT_TEST_INCLUDE_DIRS} ) +set( mock_define_list "" ) + +# ================== Module under test ======================================= +set( real_source_files "" ) +list( APPEND real_source_files + ${MODULE_ROOT_DIR}/ff_file.c ) + +set( real_include_directories ${FAT_TEST_INCLUDE_DIRS} ) + +# ===================== Test code ============================================ +set( test_include_directories ${FAT_TEST_INCLUDE_DIRS} ) + +# ============================================================================== +set( mock_name "${project_name}_mock" ) +set( real_name "${project_name}_real" ) + +create_mock_list( ${mock_name} + "${mock_list}" + "${MODULE_ROOT_DIR}/tools/cmock/project.yml" + "${mock_include_list}" + "${mock_define_list}" ) + +create_real_library( ${real_name} + "${real_source_files}" + "${real_include_directories}" + "${mock_name}" ) +target_compile_options( ${real_name} PRIVATE -ffunction-sections -fdata-sections ) + +set( utest_dep_list "" ) +list( APPEND utest_dep_list ${real_name} ) + +set( utest_link_list "" ) +list( APPEND utest_link_list + lib${real_name}.a + -l${mock_name} + -Wl,--gc-sections ) + +set( utest_name "${project_name}_utest" ) +set( utest_source "${UNIT_TEST_DIR}/ff_file_dir_empty_utest.c" ) + +create_test( ${utest_name} + ${utest_source} + "${utest_link_list}" + "${utest_dep_list}" + "${test_include_directories}" ) +list( APPEND coverage_dep_list ${utest_name} ) # ------------------------------------------------------------------------------ # `coverage` target: run the tests and collect lcov data into coverage.info. @@ -115,6 +172,6 @@ create_test( ${utest_name} add_custom_target( coverage COMMAND ${CMAKE_COMMAND} -DCMAKE_BINARY_DIR=${CMAKE_BINARY_DIR} -P ${MODULE_ROOT_DIR}/tools/cmock/coverage.cmake - DEPENDS ${utest_name} + DEPENDS ${coverage_dep_list} WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMENT "Running unit tests and collecting coverage" ) diff --git a/test/unit-test/ff_file_dir_empty_utest.c b/test/unit-test/ff_file_dir_empty_utest.c new file mode 100644 index 0000000..36f8010 --- /dev/null +++ b/test/unit-test/ff_file_dir_empty_utest.c @@ -0,0 +1,107 @@ +/* + * Unit tests for directory-empty checks in ff_file.c. + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +#include "unity.h" + +#include "mock_ff_dir_is_empty_mock_subset.h" + +#include "ff_headers.h" + +#define TEST_ROOT_DIR_CLUSTER ( 1U ) +#define TEST_SUBDIR_CLUSTER ( 7U ) + +static FF_IOManager_t xIOManager; + +static BaseType_t prvCheckEmptyDirectory( const char * pcPath, + uint32_t ulDirCluster ) +{ + FF_Error_t xFindError = FF_ERR_NONE; + + FF_FindDir_ExpectAndReturn( &xIOManager, + pcPath, + ( uint16_t ) strlen( pcPath ), + NULL, + ulDirCluster ); + FF_FindDir_IgnoreArg_pxError(); + FF_FindDir_ReturnThruPtr_pxError( &xFindError ); + FF_InitEntryFetch_ExpectAndReturn( &xIOManager, + ulDirCluster, + NULL, + FF_ERR_NONE ); + FF_InitEntryFetch_IgnoreArg_pxContext(); + FF_FindNext_ExpectAndReturn( &xIOManager, + NULL, + FF_createERR( FF_ERR_DIR_END_OF_DIR, FF_FINDNEXT ) ); + FF_FindNext_IgnoreArg_pxDirEntry(); + + return FF_isDirEmpty( &xIOManager, pcPath ); +} + +void setUp( void ) +{ + memset( &xIOManager, 0, sizeof( xIOManager ) ); +} + +void tearDown( void ) +{ +} + +void test_FF_isDirEmpty_accepts_FAT12_root_directory_cluster( void ) +{ + xIOManager.xPartition.ucType = FF_T_FAT12; + xIOManager.xPartition.ulRootDirCluster = TEST_ROOT_DIR_CLUSTER; + + TEST_ASSERT_EQUAL( pdTRUE, + prvCheckEmptyDirectory( "/", TEST_ROOT_DIR_CLUSTER ) ); +} + +void test_FF_isDirEmpty_accepts_FAT16_root_directory_cluster( void ) +{ + xIOManager.xPartition.ucType = FF_T_FAT16; + xIOManager.xPartition.ulRootDirCluster = TEST_ROOT_DIR_CLUSTER; + + TEST_ASSERT_EQUAL( pdTRUE, + prvCheckEmptyDirectory( "/", TEST_ROOT_DIR_CLUSTER ) ); +} + +void test_FF_isDirEmpty_handles_directory_with_or_without_trailing_separator( void ) +{ + TEST_ASSERT_EQUAL( pdTRUE, + prvCheckEmptyDirectory( "/dir", TEST_SUBDIR_CLUSTER ) ); + TEST_ASSERT_EQUAL( pdTRUE, + prvCheckEmptyDirectory( "/dir/", TEST_SUBDIR_CLUSTER ) ); +} + +void test_FF_isDirEmpty_reports_directory_entry_as_non_empty( void ) +{ + FF_DirEnt_t xDirEntry; + FF_Error_t xFindError = FF_ERR_NONE; + const char * pcPath = "/dir"; + + memset( &xDirEntry, 0, sizeof( xDirEntry ) ); + ( void ) strcpy( xDirEntry.pcFileName, "file.txt" ); + + FF_FindDir_ExpectAndReturn( &xIOManager, + pcPath, + ( uint16_t ) strlen( pcPath ), + NULL, + TEST_SUBDIR_CLUSTER ); + FF_FindDir_IgnoreArg_pxError(); + FF_FindDir_ReturnThruPtr_pxError( &xFindError ); + FF_InitEntryFetch_ExpectAndReturn( &xIOManager, + TEST_SUBDIR_CLUSTER, + NULL, + FF_ERR_NONE ); + FF_InitEntryFetch_IgnoreArg_pxContext(); + FF_FindNext_ExpectAndReturn( &xIOManager, NULL, FF_ERR_NONE ); + FF_FindNext_IgnoreArg_pxDirEntry(); + FF_FindNext_ReturnThruPtr_pxDirEntry( &xDirEntry ); + + TEST_ASSERT_EQUAL( pdFALSE, FF_isDirEmpty( &xIOManager, pcPath ) ); +} diff --git a/test/unit-test/include/ff_dir_is_empty_mock_subset.h b/test/unit-test/include/ff_dir_is_empty_mock_subset.h new file mode 100644 index 0000000..130d947 --- /dev/null +++ b/test/unit-test/include/ff_dir_is_empty_mock_subset.h @@ -0,0 +1,20 @@ +/* + * Minimal dependency subset for FF_isDirEmpty() unit tests. + * + * SPDX-License-Identifier: MIT + */ + +#ifndef FF_DIR_IS_EMPTY_MOCK_SUBSET_H +#define FF_DIR_IS_EMPTY_MOCK_SUBSET_H + +uint32_t FF_FindDir( FF_IOManager_t * pxIOManager, + const char * pcPath, + uint16_t usPathLength, + FF_Error_t * pxError ); +FF_Error_t FF_InitEntryFetch( FF_IOManager_t * pxIOManager, + uint32_t ulDirCluster, + FF_FetchContext_t * pxContext ); +FF_Error_t FF_FindNext( FF_IOManager_t * pxIOManager, + FF_DirEnt_t * pxDirEntry ); + +#endif /* FF_DIR_IS_EMPTY_MOCK_SUBSET_H */