Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 48 additions & 11 deletions ff_file.c
Original file line number Diff line number Diff line change
Expand Up @@ -1512,13 +1512,16 @@ static FF_Error_t FF_ExtendFile( FF_FILE * pxFile,

static FF_Error_t FF_WriteClusters( FF_FILE * pxFile,
uint32_t ulCount,
uint8_t * buffer )
uint8_t * buffer,
uint32_t * pulClustersWritten )
{
uint32_t ulSectors;
uint32_t ulSequentialClusters = 0;
uint32_t ulItemLBA;
FF_Error_t xError = FF_ERR_NONE;

*pulClustersWritten = 0U;

while( ulCount != 0 )
{
if( ulCount > 1U )
Expand Down Expand Up @@ -1547,6 +1550,7 @@ static FF_Error_t FF_WriteClusters( FF_FILE * pxFile,
break;
}

*pulClustersWritten += ulSequentialClusters + 1U;
ulCount -= ulSequentialClusters + 1;

FF_LockFAT( pxFile->pxIOManager );
Expand Down Expand Up @@ -2376,6 +2380,7 @@ int32_t FF_Write( FF_FILE * pxFile,
if( ulBytesLeft >= ulBytesPerCluster )
{
uint32_t ulClusters;
uint32_t ulClustersWritten;

FF_SetCluster( pxFile, &xError );

Expand All @@ -2386,7 +2391,8 @@ int32_t FF_Write( FF_FILE * pxFile,

ulClusters = ( ulBytesLeft / ulBytesPerCluster );

xError = FF_WriteClusters( pxFile, ulClusters, pucBuffer );
xError = FF_WriteClusters( pxFile, ulClusters, pucBuffer, &ulClustersWritten );
nBytesWritten += ulBytesPerCluster * ulClustersWritten;

if( FF_isERR( xError ) )
{
Expand All @@ -2396,7 +2402,6 @@ int32_t FF_Write( FF_FILE * pxFile,
nBytesToWrite = ulBytesPerCluster * ulClusters;
ulBytesLeft -= nBytesToWrite;
pucBuffer += nBytesToWrite;
nBytesWritten += nBytesToWrite;
pxFile->ulFilePointer += nBytesToWrite;

if( pxFile->ulFilePointer > pxFile->ulFileSize )
Expand Down Expand Up @@ -2465,12 +2470,16 @@ int32_t FF_Write( FF_FILE * pxFile,
break;
}

FF_WritePartial( pxFile, ulItemLBA, 0, ulBytesLeft, pucBuffer, &xError );
nBytesWritten += ulBytesLeft;
nBytesWritten += FF_WritePartial( pxFile, ulItemLBA, 0, ulBytesLeft, pucBuffer, &xError );
}
while( pdFALSE );
}

if( nBytesWritten > 0U )
{
pxFile->ulValidFlags |= FF_VALID_FLAG_MODIFIED;
}

if( FF_isERR( xError ) )
{
lResult = xError;
Expand Down Expand Up @@ -2550,6 +2559,7 @@ int32_t FF_PutC( FF_FILE * pxFile,

if( FF_isERR( xResult ) == pdFALSE )
{
pxFile->ulValidFlags |= FF_VALID_FLAG_MODIFIED;
xResult = ( FF_Error_t ) ucValue;
}
} while( pdFALSE );
Expand Down Expand Up @@ -3143,16 +3153,37 @@ FF_Error_t FF_Close( FF_FILE * pxFile )
xError = FF_GetEntry( pxFile->pxIOManager, pxFile->usDirEntry, pxFile->ulDirCluster, &xOriginalEntry );

/* Now update the directory entry */
if( ( FF_isERR( xError ) == pdFALSE ) &&
( ( pxFile->ulFileSize != xOriginalEntry.ulFileSize ) || ( pxFile->ulFileSize == 0UL ) ) )
if( FF_isERR( xError ) == pdFALSE )
{
if( pxFile->ulFileSize == 0UL )
BaseType_t xUpdateDirectoryEntry = pdFALSE;

if( ( pxFile->ulFileSize != xOriginalEntry.ulFileSize ) || ( pxFile->ulFileSize == 0UL ) )
{
xOriginalEntry.ulObjectCluster = 0;
if( pxFile->ulFileSize == 0UL )
{
xOriginalEntry.ulObjectCluster = 0;
}

xOriginalEntry.ulFileSize = pxFile->ulFileSize;
xUpdateDirectoryEntry = pdTRUE;
}

xOriginalEntry.ulFileSize = pxFile->ulFileSize;
xError = FF_PutEntry( pxFile->pxIOManager, pxFile->usDirEntry, pxFile->ulDirCluster, &xOriginalEntry, NULL );
#if ( ffconfigTIME_SUPPORT != 0 ) && ( ffconfigUPDATE_FILE_MODIFIED_TIME_ON_CLOSE != 0 )
{
if( ( pxFile->ulValidFlags & FF_VALID_FLAG_MODIFIED ) != 0U )
{
/* Reuse this directory-entry write so timestamp
* maintenance does not add another disk update. */
( void ) FF_GetSystemTime( &xOriginalEntry.xModifiedTime );
xUpdateDirectoryEntry = pdTRUE;
}
}
#endif /* ffconfigTIME_SUPPORT && ffconfigUPDATE_FILE_MODIFIED_TIME_ON_CLOSE */

if( xUpdateDirectoryEntry != pdFALSE )
{
xError = FF_PutEntry( pxFile->pxIOManager, pxFile->usDirEntry, pxFile->ulDirCluster, &xOriginalEntry, NULL );
}
}
}
}
Expand Down Expand Up @@ -3230,6 +3261,7 @@ FF_Error_t FF_Close( FF_FILE * pxFile )
FF_Error_t FF_SetEof( FF_FILE * pxFile )
{
FF_Error_t xError;
uint32_t ulOriginalFileSize = pxFile->ulFileSize;

/* Check if the file was not deleted and if it was opened with write permissions: */
if( ( ( pxFile->ulValidFlags & FF_VALID_FLAG_DELETED ) == 0 ) &&
Expand All @@ -3245,6 +3277,11 @@ FF_Error_t FF_SetEof( FF_FILE * pxFile )
{
xError = FF_ERR_NONE;
}

if( ( FF_isERR( xError ) == pdFALSE ) && ( pxFile->ulFileSize != ulOriginalFileSize ) )
{
pxFile->ulValidFlags |= FF_VALID_FLAG_MODIFIED;
}
}
else
{
Expand Down
10 changes: 10 additions & 0 deletions include/FreeRTOSFATConfigDefaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,16 @@
#define ffconfigTIME_SUPPORT 0
#endif

#if !defined( ffconfigUPDATE_FILE_MODIFIED_TIME_ON_CLOSE )

/* Set to 1 to update a file's modified timestamp when closing a file that was
* changed through FF_Write(), FF_PutC(), or FF_SetEof().
*
* Set to 0 to preserve the historical behaviour and avoid extra directory
* entry writes on close. */
#define ffconfigUPDATE_FILE_MODIFIED_TIME_ON_CLOSE 0
#endif

#if !defined( ffconfigREMOVABLE_MEDIA )

/* Set to 1 if the media is removable (such as a memory card).
Expand Down
1 change: 1 addition & 0 deletions include/ff_file.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ typedef struct _FF_FILE
#define FF_VALID_FLAG_INVALID 0x00000001U
#define FF_VALID_FLAG_DELETED 0x00000002U
#define FF_VALID_FLAG_EXTENDED 0x00000004U
#define FF_VALID_FLAG_MODIFIED 0x00000008U

/*---------- PROTOTYPES */
/* PUBLIC (Interfaces): */
Expand Down
65 changes: 64 additions & 1 deletion test/unit-test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,76 @@ create_test( ${utest_name}
"${utest_link_list}"
"${utest_dep_list}"
"${test_include_directories}" )
set( coverage_dep_list ${utest_name} )

set( project_name "ff_file" )

# ===================== Mocks ================================================
# Keep the ff_file unit focused on metadata updates from FF_Write() and
# FF_Close(). The module is built with function sections so the linker can
# discard unrelated file APIs and their dependencies.
set( mock_list "" )
list( APPEND mock_list
${MODULE_ROOT_DIR}/include/ff_locking.h
${UNIT_TEST_DIR}/include/ff_dir_mock_subset.h
${MODULE_ROOT_DIR}/include/ff_fat.h
${UNIT_TEST_DIR}/include/ff_ioman_mock_subset.h
${MODULE_ROOT_DIR}/include/ff_time.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_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.
# ------------------------------------------------------------------------------
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" )
5 changes: 4 additions & 1 deletion test/unit-test/config/FreeRTOSFATConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@
* helpers (FF_GetFreeSize / FF_GetVolumeSize) it generates the 64-bit variant.
* Select the matching 64-bit configuration here so the real declarations agree
* with the generated mocks. */
#define ffconfig64_NUM_SUPPORT ( 1 )
#define ffconfig64_NUM_SUPPORT ( 1 )

#define ffconfigTIME_SUPPORT ( 1 )
#define ffconfigUPDATE_FILE_MODIFIED_TIME_ON_CLOSE ( 1 )

/* All other ffconfig values fall back to FreeRTOSFATConfigDefaults.h. */

Expand Down
Loading
Loading