/* * FreeRTOS+FAT V2.3.3 * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * https://www.FreeRTOS.org * https://github.com/FreeRTOS * */ /** * @file ff_file.c * @ingroup FILEIO * * @defgroup FILEIO FILE I/O Access * @brief Provides an interface to allow File I/O on a mounted IOMAN. * * Provides file-system interfaces for the FAT file-system. **/ #include "ff_headers.h" #if ( ffconfigUNICODE_UTF16_SUPPORT != 0 ) #include #endif static FF_Error_t FF_Truncate( FF_FILE * pxFile, BaseType_t bClosing ); static int32_t FF_ReadPartial( FF_FILE * pxFile, uint32_t ulItemLBA, uint32_t ulRelBlockPos, uint32_t ulCount, uint8_t * pucBuffer, FF_Error_t * pxError ); static int32_t FF_WritePartial( FF_FILE * pxFile, uint32_t ulItemLBA, uint32_t ulRelBlockPos, uint32_t ulCount, const uint8_t * pucBuffer, FF_Error_t * pxError ); static uint32_t FF_SetCluster( FF_FILE * pxFile, FF_Error_t * pxError ); static uint32_t FF_FileLBA( FF_FILE * pxFile ); static FF_Error_t FF_ExtendFile( FF_FILE * pxFile, uint32_t ulSize ); /*-----------------------------------------------------------*/ /** * @public * @brief Converts STDIO mode strings into the equivalent FreeRTOS+FAT mode. * * @param Mode The mode string e.g. "rb" "rb+" "w" "a" "r" "w+" "a+" etc * * @return Returns the mode bits that should be passed to the FF_Open function. **/ uint8_t FF_GetModeBits( const char * pcMode ) { uint8_t ucModeBits = 0x00; while( *pcMode != '\0' ) { switch( *pcMode ) { case 'r': /* Allow Read. */ case 'R': ucModeBits |= FF_MODE_READ; break; case 'w': /* Allow Write. */ case 'W': ucModeBits |= FF_MODE_WRITE; ucModeBits |= FF_MODE_CREATE; /* Create if not exist. */ ucModeBits |= FF_MODE_TRUNCATE; break; case 'a': /* Append new writes to the end of the file. */ case 'A': ucModeBits |= FF_MODE_WRITE; ucModeBits |= FF_MODE_APPEND; ucModeBits |= FF_MODE_CREATE; /* Create if not exist. */ break; case '+': /* Update the file, don't Append! */ ucModeBits |= FF_MODE_READ; ucModeBits |= FF_MODE_WRITE; /* RW Mode. */ break; case 'D': /* Internal use only! */ ucModeBits |= FF_MODE_DIR; break; case 'b': case 'B': /* b|B flags not supported (Binary mode is native anyway). */ break; default: break; } pcMode++; } return ucModeBits; } /* FF_GetModeBits() */ /*-----------------------------------------------------------*/ static FF_FILE * prvAllocFileHandle( FF_IOManager_t * pxIOManager, FF_Error_t * pxError ) { FF_FILE * pxFile; pxFile = ffconfigMALLOC( sizeof( FF_FILE ) ); if( pxFile == NULL ) { *pxError = ( FF_Error_t ) ( FF_ERR_NOT_ENOUGH_MEMORY | FF_OPEN ); } else { memset( pxFile, 0, sizeof( *pxFile ) ); #if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 ) { pxFile->pucBuffer = ( uint8_t * ) ffconfigMALLOC( pxIOManager->usSectorSize ); if( pxFile->pucBuffer != NULL ) { memset( pxFile->pucBuffer, 0, pxIOManager->usSectorSize ); } else { *pxError = ( FF_Error_t ) ( FF_ERR_NOT_ENOUGH_MEMORY | FF_OPEN ); ffconfigFREE( pxFile ); /* Make sure that NULL will be returned. */ pxFile = NULL; } } #else /* if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 ) */ { /* Remove compiler warnings. */ ( void ) pxIOManager; } #endif /* if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 ) */ } return pxFile; } /* prvAllocFileHandle() */ /*-----------------------------------------------------------*/ /** * FF_Open() Mode Information * - FF_MODE_WRITE * - Allows WRITE access to the file. * . * - FF_MODE_READ * - Allows READ access to the file. * . * - FF_MODE_CREATE * - Create file if it doesn't exist. * . * - FF_MODE_TRUNCATE * - Erase the file if it already exists and overwrite. * * * - FF_MODE_APPEND * - Causes all writes to occur at the end of the file. (Its impossible to overwrite other data in a file with this flag set). * . * . * * Some sample modes: * - (FF_MODE_WRITE | FF_MODE_CREATE | FF_MODE_TRUNCATE) * - Write access to the file. (Equivalent to "w"). * . * - (FF_MODE_WRITE | FF_MODE_READ) * - Read and Write access to the file. (Equivalent to "rb+"). * . * - (FF_MODE_WRITE | FF_MODE_READ | FF_MODE_APPEND | FF_MODE_CREATE) * - Read and Write append mode access to the file. (Equivalent to "a+"). * . * . * Be careful when choosing modes. For those using FF_Open() at the application layer * its best to use the provided FF_GetModeBits() function, as this complies to the same * behaviour as the stdio.h fopen() function. * **/ /** * @public * @brief Opens a File for Access * * @param pxIOManager FF_IOManager_t object that was created by FF_CreateIOManger(). * @param pcPath Path to the File or object. * @param ucMode Access Mode required. Modes are a little complicated, the function FF_GetModeBits() * @param ucMode will convert a stdio Mode string into the equivalent Mode bits for this parameter. * @param pxError Pointer to a signed byte for error checking. Can be NULL if not required. * @param pxError To be checked when a NULL pointer is returned. * * @return NULL pointer on error, in which case pxError should be checked for more information. * @return pxError can be: **/ #if ( ffconfigUNICODE_UTF16_SUPPORT != 0 ) FF_FILE * FF_Open( FF_IOManager_t * pxIOManager, const FF_T_WCHAR * pcPath, uint8_t ucMode, FF_Error_t * pxError ) #else FF_FILE * FF_Open( FF_IOManager_t * pxIOManager, const char * pcPath, uint8_t ucMode, FF_Error_t * pxError ) #endif { FF_FILE * pxFile = NULL; FF_FILE * pxFileChain; FF_DirEnt_t xDirEntry; uint32_t ulFileCluster; FF_Error_t xError; BaseType_t xIndex; FF_FindParams_t xFindParams; #if ( ffconfigUNICODE_UTF16_SUPPORT != 0 ) FF_T_WCHAR pcFileName[ ffconfigMAX_FILENAME ]; #else char pcFileName[ ffconfigMAX_FILENAME ]; #endif memset( &xFindParams, '\0', sizeof( xFindParams ) ); /* Inform the functions that the entry will be created if not found. */ if( ( ucMode & FF_MODE_CREATE ) != 0 ) { xFindParams.ulFlags |= FIND_FLAG_CREATE_FLAG; } if( pxIOManager == NULL ) { /* Use the error function code 'FF_OPEN' as this static * function is only called from that function. */ xError = ( FF_Error_t ) ( FF_ERR_NULL_POINTER | FF_OPEN ); } #if ( ffconfigREMOVABLE_MEDIA != 0 ) else if( ( pxIOManager->ucFlags & FF_IOMAN_DEVICE_IS_EXTRACTED ) != 0 ) { xError = ( FF_Error_t ) ( FF_ERR_IOMAN_DRIVER_NOMEDIUM | FF_OPEN ); } #endif /* ffconfigREMOVABLE_MEDIA */ else { xError = FF_ERR_NONE; /* Let xIndex point to the last occurrence of '/' or '\', * to separate the path from the file name. */ xIndex = ( BaseType_t ) STRLEN( pcPath ); while( xIndex != 0 ) { if( ( pcPath[ xIndex ] == '\\' ) || ( pcPath[ xIndex ] == '/' ) ) { break; } xIndex--; } /* Copy the file name, i.e. the string that comes after the last separator. */ STRNCPY( pcFileName, pcPath + xIndex + 1, ffconfigMAX_FILENAME ); if( xIndex == 0 ) { /* Only for the root, the slash is part of the directory name. * 'xIndex' now equals to the length of the path name. */ xIndex = 1; } /* FF_CreateShortName() might set flags FIND_FLAG_FITS_SHORT and FIND_FLAG_SIZE_OK. */ FF_CreateShortName( &xFindParams, pcFileName ); /* Lookup the path and find the cluster pointing to the directory: */ xFindParams.ulDirCluster = FF_FindDir( pxIOManager, pcPath, xIndex, &xError ); if( xFindParams.ulDirCluster == 0ul ) { if( ( ucMode & FF_MODE_WRITE ) != 0 ) { FF_PRINTF( "FF_Open[%s]: Path not found\n", pcPath ); } /* The user tries to open a file but the path leading to the file does not exist. */ } else if( FF_isERR( xError ) == pdFALSE ) { /* Allocate an empty file handle and buffer space for 'unaligned access'. */ pxFile = prvAllocFileHandle( pxIOManager, &xError ); } } if( FF_isERR( xError ) == pdFALSE ) { /* Copy the Mode Bits. */ pxFile->ucMode = ucMode; /* See if the file does exist within the given directory. */ ulFileCluster = FF_FindEntryInDir( pxIOManager, &xFindParams, pcFileName, 0x00, &xDirEntry, &xError ); if( ulFileCluster == 0ul ) { /* If cluster 0 was returned, it might be because the file has no allocated cluster, * i.e. only a directory entry and no stored data. */ if( STRLEN( pcFileName ) == STRLEN( xDirEntry.pcFileName ) ) { if( ( xDirEntry.ulFileSize == 0 ) && ( FF_strmatch( pcFileName, xDirEntry.pcFileName, ( BaseType_t ) STRLEN( pcFileName ) ) == pdTRUE ) ) { /* It is the file, give it a pseudo cluster number '1'. */ ulFileCluster = 1; /* And reset any error. */ xError = FF_ERR_NONE; } } } /* Test 'ulFileCluster' again, it might have been changed. */ if( ulFileCluster == 0ul ) { /* The path is found, but it does not contain the file name yet. * Maybe the user wants to create it? */ if( ( ucMode & FF_MODE_CREATE ) == 0 ) { xError = ( FF_Error_t ) ( FF_ERR_FILE_NOT_FOUND | FF_OPEN ); } else { ulFileCluster = FF_CreateFile( pxIOManager, &xFindParams, pcFileName, &xDirEntry, &xError ); if( FF_isERR( xError ) == pdFALSE ) { xDirEntry.usCurrentItem += 1; } } } } if( FF_isERR( xError ) == pdFALSE ) { /* Now the file exists, or it has been created. * Check if the Mode flags are allowed: */ if( ( xDirEntry.ucAttrib == FF_FAT_ATTR_DIR ) && ( ( ucMode & FF_MODE_DIR ) == 0 ) ) { /* Not the object, File Not Found! */ xError = ( FF_Error_t ) ( FF_ERR_FILE_OBJECT_IS_A_DIR | FF_OPEN ); } /*---------- Ensure Read-Only files don't get opened for Writing. */ else if( ( ( ucMode & ( FF_MODE_WRITE | FF_MODE_APPEND ) ) != 0 ) && ( ( xDirEntry.ucAttrib & FF_FAT_ATTR_READONLY ) != 0 ) ) { xError = ( FF_Error_t ) ( FF_ERR_FILE_IS_READ_ONLY | FF_OPEN ); } } if( FF_isERR( xError ) == pdFALSE ) { pxFile->pxIOManager = pxIOManager; pxFile->ulFilePointer = 0; /* Despite the warning output by MSVC - it is not possible to get here * if xDirEntry has not been initialised. */ pxFile->ulObjectCluster = xDirEntry.ulObjectCluster; pxFile->ulFileSize = xDirEntry.ulFileSize; pxFile->ulCurrentCluster = 0; pxFile->ulAddrCurrentCluster = pxFile->ulObjectCluster; pxFile->pxNext = NULL; pxFile->ulDirCluster = xFindParams.ulDirCluster; pxFile->usDirEntry = xDirEntry.usCurrentItem - 1; pxFile->ulChainLength = 0; pxFile->ulEndOfChain = 0; pxFile->ulValidFlags &= ~( FF_VALID_FLAG_DELETED ); /* Add pxFile onto the end of our linked list of FF_FILE objects. * But first make sure that there are not 2 handles with write access * to the same object. */ FF_PendSemaphore( pxIOManager->pvSemaphore ); { pxFileChain = ( FF_FILE * ) pxIOManager->FirstFile; if( pxFileChain == NULL ) { pxIOManager->FirstFile = pxFile; } else { for( ; ; ) { /* See if two file handles point to the same object. */ if( ( pxFileChain->ulObjectCluster == pxFile->ulObjectCluster ) && ( pxFileChain->ulDirCluster == pxFile->ulDirCluster ) && ( pxFileChain->usDirEntry == pxFile->usDirEntry ) ) { /* Fail if any of the two has write access to the object. */ if( ( ( pxFileChain->ucMode | pxFile->ucMode ) & ( FF_MODE_WRITE | FF_MODE_APPEND ) ) != 0 ) { /* File is already open! DON'T ALLOW IT! */ xError = ( FF_Error_t ) ( FF_ERR_FILE_ALREADY_OPEN | FF_OPEN ); break; } } if( pxFileChain->pxNext == NULL ) { pxFileChain->pxNext = pxFile; break; } pxFileChain = ( FF_FILE * ) pxFileChain->pxNext; } } } FF_ReleaseSemaphore( pxIOManager->pvSemaphore ); } if( FF_isERR( xError ) == pdFALSE ) { /* If the file is opened with the truncate flag, truncate its contents. */ if( ( ucMode & FF_MODE_TRUNCATE ) != 0 ) { /* Set the current size and position to zero. */ pxFile->ulFileSize = 0; pxFile->ulFilePointer = 0; } } if( FF_isERR( xError ) != pdFALSE ) { if( pxFile != NULL ) { #if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 ) { ffconfigFREE( pxFile->pucBuffer ); } #endif ffconfigFREE( pxFile ); } pxFile = NULL; } if( pxError != NULL ) { *pxError = xError; } return pxFile; } /* FF_Open() */ /*-----------------------------------------------------------*/ /** * @public * @brief Tests if a Directory contains any other files or folders. * * @param pxIOManager FF_IOManager_t object returned from the FF_CreateIOManger() function. * **/ #if ( ffconfigUNICODE_UTF16_SUPPORT != 0 ) BaseType_t FF_isDirEmpty( FF_IOManager_t * pxIOManager, const FF_T_WCHAR * pcPath ) #else BaseType_t FF_isDirEmpty( FF_IOManager_t * pxIOManager, const char * pcPath ) #endif { FF_DirEnt_t xDirEntry; FF_Error_t xError = FF_ERR_NONE; BaseType_t xReturn; if( pxIOManager == NULL ) { xReturn = pdFALSE; } else { xError = FF_FindFirst( pxIOManager, &xDirEntry, pcPath ); /* Assume the directory is empty until a file is * encountered with a name other than ".." or "." */ xReturn = pdTRUE; while( xError == 0 ) { /* As we can't be sure the first 2 entries contain * "." and "..", check it, not just count them */ #if ( ffconfigUNICODE_UTF16_SUPPORT != 0 ) if( ( wcscmp( xDirEntry.pcFileName, L".." ) != 0 ) && ( wcscmp( xDirEntry.pcFileName, L"." ) != 0 ) ) #else if( ( strcmp( xDirEntry.pcFileName, ".." ) != 0 ) && ( strcmp( xDirEntry.pcFileName, "." ) != 0 ) ) #endif { xReturn = pdFALSE; break; } xError = FF_FindNext( pxIOManager, &xDirEntry ); } } return xReturn; } /* FF_isDirEmpty() */ /*-----------------------------------------------------------*/ #if ( ffconfigPATH_CACHE != 0 ) /* _HT_ After a directory has been renamed, the path cache becomes out-of-date */ #if ( ffconfigUNICODE_UTF16_SUPPORT != 0 ) static void FF_RmPathCache( FF_IOManager_t * pxIOManager, const FF_T_WCHAR * pcPath ) #else static void FF_RmPathCache( FF_IOManager_t * pxIOManager, const char * pcPath ) #endif { /* * The directory 'path' will be removed or renamed * now clear all entries starting with 'path' in the path cache */ BaseType_t xIndex; BaseType_t pathLen = STRLEN( pcPath ); FF_PendSemaphore( pxIOManager->pvSemaphore ); { for( xIndex = 0; xIndex < ffconfigPATH_CACHE_DEPTH; xIndex++ ) { BaseType_t len2 = STRLEN( pxIOManager->xPartition.pxPathCache[ xIndex ].pcPath ); if( ( len2 >= pathLen ) && FF_strmatch( pxIOManager->xPartition.pxPathCache[ xIndex ].pcPath, pcPath, pathLen ) ) { pxIOManager->xPartition.pxPathCache[ xIndex ].pcPath[ 0 ] = '\0'; pxIOManager->xPartition.pxPathCache[ xIndex ].ulDirCluster = 0; } } } FF_ReleaseSemaphore( pxIOManager->pvSemaphore ); } #endif /* ffconfigPATH_CACHE */ /*-----------------------------------------------------------*/ #if ( ffconfigUNICODE_UTF16_SUPPORT != 0 ) FF_Error_t FF_RmDir( FF_IOManager_t * pxIOManager, const FF_T_WCHAR * pcPath ) #else FF_Error_t FF_RmDir( FF_IOManager_t * pxIOManager, const char * pcPath ) #endif { FF_FILE * pxFile; uint8_t ucEntryBuffer[ 32 ]; FF_FetchContext_t xFetchContext; FF_Error_t xError = FF_ERR_NONE; if( pxIOManager == NULL ) { xError = ( FF_Error_t ) ( FF_ERR_NULL_POINTER | FF_RMDIR ); } #if ( ffconfigREMOVABLE_MEDIA != 0 ) else if( ( pxIOManager->ucFlags & FF_IOMAN_DEVICE_IS_EXTRACTED ) != 0 ) { xError = ( FF_Error_t ) ( FF_ERR_IOMAN_DRIVER_NOMEDIUM | FF_RMDIR ); } #endif /* ffconfigREMOVABLE_MEDIA */ else { pxFile = FF_Open( pxIOManager, pcPath, FF_MODE_DIR, &xError ); if( pxFile != NULL ) { pxFile->ulValidFlags |= FF_VALID_FLAG_DELETED; /* Clear the struct to allow a call to FF_CleanupEntryFetch() in any * state. */ memset( &xFetchContext, '\0', sizeof( xFetchContext ) ); /* This task will get the unique right to change directories. */ FF_LockDirectory( pxIOManager ); do /* while( pdFALSE ) */ { /* This while loop is only introduced to be able to use break * statements. */ if( FF_isDirEmpty( pxIOManager, pcPath ) == pdFALSE ) { xError = ( FF_ERR_DIR_NOT_EMPTY | FF_RMDIR ); break; } FF_LockFAT( pxIOManager ); #if ( ffconfigHASH_CACHE != 0 ) { /* A directory is removed so invalidate any hash table * referring to this directory. */ FF_UnHashDir( pxIOManager, pxFile->ulObjectCluster ); } #endif /* ffconfigHASH_CACHE */ { /* Add parameter 0 to delete the entire chain! * The actual directory entries on disk will be freed. */ xError = FF_UnlinkClusterChain( pxIOManager, pxFile->ulObjectCluster, 0 ); } FF_UnlockFAT( pxIOManager ); if( FF_isERR( xError ) ) { break; } /* Now remove this directory from its parent directory. * Initialise the dirent Fetch Context object for faster removal of * dirents. */ xError = FF_InitEntryFetch( pxIOManager, pxFile->ulDirCluster, &xFetchContext ); if( FF_isERR( xError ) ) { break; } #if ( ffconfigHASH_CACHE != 0 ) { /* Invalidate any hash table of the parent directory * as well. */ FF_UnHashDir( pxIOManager, pxFile->ulDirCluster ); } #endif /* ffconfigHASH_CACHE */ /* Edit the Directory Entry, so it will show as deleted. * First remove the LFN entries: */ xError = FF_RmLFNs( pxIOManager, pxFile->usDirEntry, &xFetchContext ); if( FF_isERR( xError ) ) { break; } /* And remove the Short file name entry: */ xError = FF_FetchEntryWithContext( pxIOManager, pxFile->usDirEntry, &xFetchContext, ucEntryBuffer ); if( FF_isERR( xError ) == pdFALSE ) { ucEntryBuffer[ 0 ] = FF_FAT_DELETED; FF_putShort( ucEntryBuffer, FF_FAT_DIRENT_CLUS_HIGH, ( uint32_t ) 0ul ); FF_putShort( ucEntryBuffer, FF_FAT_DIRENT_CLUS_LOW, ( uint32_t ) 0ul ); xError = FF_PushEntryWithContext( pxIOManager, pxFile->usDirEntry, &xFetchContext, ucEntryBuffer ); } if( FF_isERR( xError ) ) { break; } #if ( ffconfigPATH_CACHE != 0 ) { /* We're removing a directory which might contain * subdirectories. Instead of iterating through all * subdirectories, just clear the path cache. */ FF_RmPathCache( pxIOManager, pcPath ); } #endif } while( pdFALSE ); { FF_Error_t xTempError; xTempError = FF_CleanupEntryFetch( pxIOManager, &xFetchContext ); if( FF_isERR( xError ) == pdFALSE ) { xError = xTempError; } FF_UnlockDirectory( pxIOManager ); /* Free the file pointer resources. */ xTempError = FF_Close( pxFile ); if( FF_isERR( xError ) == pdFALSE ) { xError = xTempError; } xTempError = FF_FlushCache( pxIOManager ); if( FF_isERR( xError ) == pdFALSE ) { xError = xTempError; } } } /* if( pxFile != NULL ) */ } /* else if( pxIOManager != NULL ) */ return xError; } /* FF_RmDir() */ /*-----------------------------------------------------------*/ #if ( ffconfigUNICODE_UTF16_SUPPORT != 0 ) FF_Error_t FF_RmFile( FF_IOManager_t * pxIOManager, const FF_T_WCHAR * pcPath ) #else FF_Error_t FF_RmFile( FF_IOManager_t * pxIOManager, const char * pcPath ) #endif { FF_FILE * pxFile; FF_Error_t xError = FF_ERR_NONE; uint8_t ucEntryBuffer[ 32 ]; FF_FetchContext_t xFetchContext; /* Opening the file-to-be-deleted in WR mode has two advantages: * 1. The file handle gives all necessary information to delete it such * as the data clusters and directory entries. * 2. The file is now locked, it can not be opened by another task. */ pxFile = FF_Open( pxIOManager, pcPath, FF_MODE_WRITE, &xError ); if( pxFile != NULL ) { /* FF_Close() will see this flag and won't do any disc access. */ pxFile->ulValidFlags |= FF_VALID_FLAG_DELETED; /* Ensure there is actually a cluster chain to delete! */ if( pxFile->ulObjectCluster != 0 ) { /* Lock the FAT so its thread-safe. */ FF_LockFAT( pxIOManager ); { /* 0 to delete the entire chain! */ xError = FF_UnlinkClusterChain( pxIOManager, pxFile->ulObjectCluster, 0 ); } FF_UnlockFAT( pxIOManager ); } if( FF_isERR( xError ) == pdFALSE ) { /* Clear the struct to allow a call to FF_CleanupEntryFetch() in any * state. */ memset( &xFetchContext, '\0', sizeof( xFetchContext ) ); /* Get sole access to "directory changes" */ FF_LockDirectory( pxIOManager ); /* Edit the Directory Entry! (So it appears as deleted); */ do { xError = FF_InitEntryFetch( pxIOManager, pxFile->ulDirCluster, &xFetchContext ); if( FF_isERR( xError ) ) { break; } #if ( ffconfigHASH_CACHE != 0 ) { FF_UnHashDir( pxIOManager, pxFile->ulDirCluster ); } #endif /* ffconfigHASH_CACHE */ /* Remove LFN entries, if any. */ xError = FF_RmLFNs( pxIOManager, ( uint16_t ) pxFile->usDirEntry, &xFetchContext ); if( FF_isERR( xError ) ) { break; } /* Remove the Short file name entry. */ xError = FF_FetchEntryWithContext( pxIOManager, pxFile->usDirEntry, &xFetchContext, ucEntryBuffer ); if( FF_isERR( xError ) == pdFALSE ) { ucEntryBuffer[ 0 ] = FF_FAT_DELETED; FF_putShort( ucEntryBuffer, FF_FAT_DIRENT_CLUS_HIGH, ( uint32_t ) 0ul ); FF_putShort( ucEntryBuffer, FF_FAT_DIRENT_CLUS_LOW, ( uint32_t ) 0ul ); xError = FF_PushEntryWithContext( pxIOManager, pxFile->usDirEntry, &xFetchContext, ucEntryBuffer ); } } while( pdFALSE ); { FF_Error_t xTempError; xTempError = FF_CleanupEntryFetch( pxIOManager, &xFetchContext ); if( FF_isERR( xError ) == pdFALSE ) { xError = xTempError; } FF_UnlockDirectory( pxIOManager ); /* Free the file pointer resources. */ xTempError = FF_Close( pxFile ); if( FF_isERR( xError ) == pdFALSE ) { xError = xTempError; } xTempError = FF_FlushCache( pxIOManager ); if( FF_isERR( xError ) == pdFALSE ) { xError = xTempError; } } } } /* if( pxFile != NULL ) */ return xError; } /* FF_RmFile() */ /*-----------------------------------------------------------*/ /** * @public * @brief Moves a file or directory from source to destination. * * @param pxIOManager The FF_IOManager_t object pointer. * @param szSourceFile String of the source file to be moved or renamed. * @param szDestinationFile String of the destination file to where the source should be moved or renamed. * * @return FF_ERR_NONE on success. * @return FF_ERR_FILE_DESTINATION_EXISTS if the destination file exists. * @return FF_ERR_FILE_COULD_NOT_CREATE_DIRENT if dirent creation failed (fatal error!). * @return FF_ERR_FILE_DIR_NOT_FOUND if destination directory was not found. * @return FF_ERR_FILE_SOURCE_NOT_FOUND if the source file was not found. * **/ #if ( ffconfigUNICODE_UTF16_SUPPORT != 0 ) FF_Error_t FF_Move( FF_IOManager_t * pxIOManager, const FF_T_WCHAR * szSourceFile, const FF_T_WCHAR * szDestinationFile, BaseType_t xDeleteIfExists ) #else FF_Error_t FF_Move( FF_IOManager_t * pxIOManager, const char * szSourceFile, const char * szDestinationFile, BaseType_t xDeleteIfExists ) #endif { FF_Error_t xError; FF_FILE * pSrcFile, * pxDestFile; FF_DirEnt_t xMyFile; uint8_t ucEntryBuffer[ 32 ]; BaseType_t xIndex; uint32_t ulDirCluster = 0ul; FF_FetchContext_t xFetchContext; #if ( ffconfigPATH_CACHE != 0 ) BaseType_t xIsDirectory = pdFALSE; #endif memset( &xFetchContext, '\0', sizeof( xFetchContext ) ); if( pxIOManager == NULL ) { xError = ( FF_Error_t ) ( FF_ERR_NULL_POINTER | FF_MOVE ); } #if ( ffconfigREMOVABLE_MEDIA != 0 ) else if( ( pxIOManager->ucFlags & FF_IOMAN_DEVICE_IS_EXTRACTED ) != 0 ) { xError = ( FF_Error_t ) ( FF_ERR_IOMAN_DRIVER_NOMEDIUM | FF_MOVE ); } #endif /* ffconfigREMOVABLE_MEDIA */ else { /* Check destination file doesn't exist! */ pxDestFile = FF_Open( pxIOManager, szDestinationFile, FF_MODE_READ, &xError ); if( ( pxDestFile != NULL ) || ( FF_GETERROR( xError ) == FF_ERR_FILE_OBJECT_IS_A_DIR ) ) { xError = ( FF_Error_t ) ( FF_ERR_FILE_DESTINATION_EXISTS | FF_MOVE ); if( pxDestFile != NULL ) { FF_Close( pxDestFile ); if( xDeleteIfExists != pdFALSE ) { xError = FF_RmFile( pxIOManager, szDestinationFile ); } } } else { /* Discard the error set by FF_Open(). * The target file (or directory) is not found: continue renaming. */ xError = FF_ERR_NONE; } } if( FF_isERR( xError ) == pdFALSE ) { /* About to move/rename 'szSourceFile'. When opening it with 'FF_MODE_WRITE' * only succeeds if it has no other open handle to it. */ pSrcFile = FF_Open( pxIOManager, szSourceFile, FF_MODE_WRITE, &xError ); if( FF_GETERROR( xError ) == FF_ERR_FILE_OBJECT_IS_A_DIR ) { /* Open a directory for moving! */ pSrcFile = FF_Open( pxIOManager, szSourceFile, FF_MODE_DIR, &xError ); #if ( ffconfigPATH_CACHE != 0 ) xIsDirectory = pdTRUE; #endif } if( pSrcFile != NULL ) { /* Collect information about the current directory entry. */ xError = FF_InitEntryFetch( pxIOManager, pSrcFile->ulDirCluster, &xFetchContext ); if( FF_isERR( xError ) == pdFALSE ) { xError = FF_FetchEntryWithContext( pxIOManager, pSrcFile->usDirEntry, &xFetchContext, ucEntryBuffer ); if( FF_isERR( xError ) == pdFALSE ) { xMyFile.ucAttrib = FF_getChar( ucEntryBuffer, ( uint16_t ) ( FF_FAT_DIRENT_ATTRIB ) ); xMyFile.ulFileSize = pSrcFile->ulFileSize; xMyFile.ulObjectCluster = pSrcFile->ulObjectCluster; xMyFile.usCurrentItem = 0; xIndex = ( BaseType_t ) STRLEN( szDestinationFile ); while( xIndex != 0 ) { if( ( szDestinationFile[ xIndex ] == '\\' ) || ( szDestinationFile[ xIndex ] == '/' ) ) { break; } xIndex--; } /* Copy the base name of the destination file. */ STRNCPY( xMyFile.pcFileName, ( szDestinationFile + xIndex + 1 ), ffconfigMAX_FILENAME ); if( xIndex == 0 ) { xIndex = 1; } /* Find the (cluster of the) directory in which the target file will be located. * It must exist before calling FF_Move(). */ ulDirCluster = FF_FindDir( pxIOManager, szDestinationFile, xIndex, &xError ); } } } if( FF_isERR( xError ) == pdFALSE ) { if( ulDirCluster != 0ul ) { FF_FindParams_t xFindParams; memset( &xFindParams, '\0', sizeof( xFindParams ) ); /* Clean up because FF_CreateDirent might want to write to the same sector. */ xError = FF_CleanupEntryFetch( pxIOManager, &xFetchContext ); if( FF_isERR( xError ) == pdFALSE ) { /* Destination directory was found, we can now create the new entry. */ xFindParams.ulDirCluster = ulDirCluster; xError = FF_CreateDirent( pxIOManager, &xFindParams, &xMyFile ); } if( FF_isERR( xError ) == pdFALSE ) { /* Edit the Directory Entry! (So it appears as deleted); */ FF_LockDirectory( pxIOManager ); { xError = FF_RmLFNs( pxIOManager, pSrcFile->usDirEntry, &xFetchContext ); if( FF_isERR( xError ) == pdFALSE ) { xError = FF_FetchEntryWithContext( pxIOManager, pSrcFile->usDirEntry, &xFetchContext, ucEntryBuffer ); if( FF_isERR( xError ) == pdFALSE ) { FF_Error_t xTempError; ucEntryBuffer[ 0 ] = FF_FAT_DELETED; FF_putShort( ucEntryBuffer, FF_FAT_DIRENT_CLUS_HIGH, ( uint32_t ) 0ul ); FF_putShort( ucEntryBuffer, FF_FAT_DIRENT_CLUS_LOW, ( uint32_t ) 0ul ); xError = FF_PushEntryWithContext( pxIOManager, pSrcFile->usDirEntry, &xFetchContext, ucEntryBuffer ); /* The contents of 'xFetchContext' has changed, flush it to disk now. */ xTempError = FF_CleanupEntryFetch( pxIOManager, &xFetchContext ); if( FF_isERR( xError ) == pdFALSE ) { xError = xTempError; } } } } FF_UnlockDirectory( pxIOManager ); } #if ( ffconfigPATH_CACHE != 0 ) { if( xIsDirectory != 0 ) { /* We've renamed a directory which might contain * subdirectories. To avoid having false entries, clear * the path cache. */ FF_RmPathCache( pxIOManager, szSourceFile ); } } #endif } else /* ulDirCluster == 0ul */ { xError = ( FF_Error_t ) ( FF_ERR_FILE_DIR_NOT_FOUND | FF_MOVE ); } } if( pSrcFile != NULL ) { /* The source file was opened in WRITE mode just to lock it. * Now clear the write flags to avoid writing back any changes. */ pSrcFile->ucMode &= ~( FF_MODE_WRITE | FF_MODE_APPEND | FF_MODE_CREATE ); FF_Close( pSrcFile ); } } { FF_Error_t xTempError; xTempError = FF_FlushCache( pxIOManager ); if( FF_isERR( xError ) == pdFALSE ) { xError = xTempError; } xTempError = FF_CleanupEntryFetch( pxIOManager, &xFetchContext ); if( FF_isERR( xError ) == pdFALSE ) { xError = xTempError; } } return xError; } /* FF_Move() */ /*-----------------------------------------------------------*/ /** * @public * @brief Get's the next Entry based on the data recorded in the FF_DirEnt_t object. * * @param pxFile FF_FILE object that was created by FF_Open(). * * @return pdTRUE if End of File was reached. pdFALSE if not. * @return pdFALSE if a null pointer was provided. * **/ BaseType_t FF_isEOF( FF_FILE * pxFile ) { BaseType_t xReturn; if( ( pxFile != NULL ) && ( pxFile->ulFilePointer >= pxFile->ulFileSize ) ) { xReturn = pdTRUE; } else { xReturn = pdFALSE; } return xReturn; } /* FF_isEOF() */ /*-----------------------------------------------------------*/ /** * @public * @brief Checks the number of bytes left on a read handle * * @param pxFile An open file handle * * @return Less than zero: an error code * @return Number of bytes left to read from handle **/ int32_t FF_BytesLeft( FF_FILE * pxFile ) { BaseType_t xReturn; if( pxFile == NULL ) { xReturn = FF_ERR_NULL_POINTER | FF_BYTESLEFT; } else if( ( pxFile->ucMode & FF_MODE_READ ) == 0 ) { xReturn = FF_ERR_FILE_NOT_OPENED_IN_READ_MODE | FF_BYTESLEFT; } else if( pxFile->ulFilePointer >= pxFile->ulFileSize ) { xReturn = 0; } else { xReturn = pxFile->ulFileSize - pxFile->ulFilePointer; } return xReturn; } /* FF_BytesLeft() */ /*-----------------------------------------------------------*/ /** * @public * @brief Returns the file size of a read handle * * @param pxFile An open file handle * * @return Less than zero: an error code * @return Number of bytes left to read from handle **/ FF_Error_t FF_GetFileSize( FF_FILE * pxFile, uint32_t * pulSize ) /* Writes # of bytes in a file to the parameter. */ { BaseType_t xReturn; if( pxFile == NULL ) { xReturn = ( FF_Error_t ) ( FF_ERR_NULL_POINTER | FF_BYTESLEFT ); *( pulSize ) = ( uint32_t ) 0u; } else if( FF_isERR( FF_CheckValid( pxFile ) ) ) { xReturn = ( FF_Error_t ) ( FF_ERR_FILE_BAD_HANDLE | FF_BYTESLEFT ); *( pulSize ) = ( uint32_t ) 0u; } else { xReturn = 0; *( pulSize ) = pxFile->ulFileSize; } return xReturn; } /* FF_GetFileSize */ int32_t FF_FileSize( FF_FILE * pxFile ) { uint32_t ulLength; FF_Error_t xResult; /* Function is deprecated. Please use FF_GetFileSize(). */ xResult = FF_GetFileSize( pxFile, &( ulLength ) ); if( FF_isERR( xResult ) == 0 ) { xResult = ( int32_t ) ulLength; } return ( int32_t ) xResult; } /* FF_FileSize() */ /*-----------------------------------------------------------*/ static uint32_t FF_GetSequentialClusters( FF_IOManager_t * pxIOManager, uint32_t ulStartCluster, uint32_t ulLimit, FF_Error_t * pxError ) { uint32_t ulCurrentCluster; uint32_t ulNextCluster = ulStartCluster; uint32_t ulIndex = 0; FF_FATBuffers_t xFATBuffers; FF_InitFATBuffers( &xFATBuffers, FF_MODE_READ ); *pxError = FF_ERR_NONE; FF_LockFAT( pxIOManager ); do { ulCurrentCluster = ulNextCluster; ulNextCluster = FF_getFATEntry( pxIOManager, ulCurrentCluster, pxError, &xFATBuffers ); if( FF_isERR( *pxError ) ) { ulIndex = 0; break; } if( ulNextCluster == ( ulCurrentCluster + 1 ) ) { ulIndex++; } else { break; } if( ( ulLimit != 0 ) && ( ulIndex == ulLimit ) ) { break; } } while( ulNextCluster == ( ulCurrentCluster + 1 ) ); FF_UnlockFAT( pxIOManager ); *pxError = FF_ReleaseFATBuffers( pxIOManager, &xFATBuffers ); return ulIndex; } /* FF_GetSequentialClusters() */ /*-----------------------------------------------------------*/ static FF_Error_t FF_ReadClusters( FF_FILE * pxFile, uint32_t ulCount, uint8_t * buffer ) { uint32_t ulSectors; uint32_t ulSequentialClusters = 0; uint32_t ulItemLBA; FF_Error_t xError = FF_ERR_NONE; while( ulCount != 0 ) { if( ( ulCount - 1 ) > 0 ) { ulSequentialClusters = FF_GetSequentialClusters( pxFile->pxIOManager, pxFile->ulAddrCurrentCluster, ulCount - 1, &xError ); if( FF_isERR( xError ) ) { break; } } ulSectors = ( ulSequentialClusters + 1 ) * pxFile->pxIOManager->xPartition.ulSectorsPerCluster; ulItemLBA = FF_Cluster2LBA( pxFile->pxIOManager, pxFile->ulAddrCurrentCluster ); ulItemLBA = FF_getRealLBA( pxFile->pxIOManager, ulItemLBA ); xError = FF_BlockRead( pxFile->pxIOManager, ulItemLBA, ulSectors, buffer, pdFALSE ); if( FF_isERR( xError ) ) { break; } ulCount -= ( ulSequentialClusters + 1 ); FF_LockFAT( pxFile->pxIOManager ); { pxFile->ulAddrCurrentCluster = FF_TraverseFAT( pxFile->pxIOManager, pxFile->ulAddrCurrentCluster, ulSequentialClusters + 1, &xError ); } FF_UnlockFAT( pxFile->pxIOManager ); if( FF_isERR( xError ) ) { break; } pxFile->ulCurrentCluster += ( ulSequentialClusters + 1 ); buffer += ulSectors * pxFile->pxIOManager->usSectorSize; ulSequentialClusters = 0; } return xError; } /* FF_ReadClusters ()*/ /*-----------------------------------------------------------*/ static FF_Error_t FF_ExtendFile( FF_FILE * pxFile, uint32_t ulSize ) { FF_IOManager_t * pxIOManager = pxFile->pxIOManager; uint32_t ulBytesPerCluster = pxIOManager->xPartition.usBlkSize * pxIOManager->xPartition.ulSectorsPerCluster; uint32_t ulTotalClustersNeeded = ( ulSize + ulBytesPerCluster - 1 ) / ulBytesPerCluster; uint32_t ulClusterToExtend; /* Initialise xIndex just for the compiler. */ BaseType_t xIndex = 0; FF_DirEnt_t xOriginalEntry; FF_Error_t xError = FF_ERR_NONE; FF_FATBuffers_t xFATBuffers; if( ( pxFile->ucMode & FF_MODE_WRITE ) != FF_MODE_WRITE ) { xError = ( FF_Error_t ) ( FF_ERR_FILE_NOT_OPENED_IN_WRITE_MODE | FF_EXTENDFILE ); } else { if( ( pxFile->ulFileSize == 0 ) && ( pxFile->ulObjectCluster == 0 ) ) { /* If there is no object cluster yet, create it.*/ pxFile->ulAddrCurrentCluster = FF_CreateClusterChain( pxFile->pxIOManager, &xError ); if( FF_isERR( xError ) == pdFALSE ) { /* The directory denotes the address of the first data cluster of every file. * Now change it to 'ulAddrCurrentCluster': */ xError = FF_GetEntry( pxIOManager, pxFile->usDirEntry, pxFile->ulDirCluster, &xOriginalEntry ); if( FF_isERR( xError ) == pdFALSE ) { xOriginalEntry.ulObjectCluster = pxFile->ulAddrCurrentCluster; xError = FF_PutEntry( pxIOManager, pxFile->usDirEntry, pxFile->ulDirCluster, &xOriginalEntry, NULL ); if( FF_isERR( xError ) == pdFALSE ) { pxFile->ulObjectCluster = pxFile->ulAddrCurrentCluster; pxFile->ulChainLength = 1; pxFile->ulCurrentCluster = 0; pxFile->ulEndOfChain = pxFile->ulAddrCurrentCluster; } } } } else { /* This file already has at least one cluster. */ } } if( FF_isERR( xError ) == pdFALSE ) { if( pxFile->ulChainLength == 0 ) { /* This is the first extension requiring the chain length. * Calculate it now: */ pxFile->ulChainLength = FF_GetChainLength( pxIOManager, pxFile->ulObjectCluster, &pxFile->ulEndOfChain, &xError ); } } if( ( FF_isERR( xError ) == pdFALSE ) && ( ulTotalClustersNeeded > pxFile->ulChainLength ) ) { uint32_t ulCurrentCluster, ulNextCluster; ulClusterToExtend = ( ulTotalClustersNeeded - pxFile->ulChainLength ); /* Now the file has at least 1 cluster, but it needs more clusters. */ ulNextCluster = pxFile->ulAddrCurrentCluster; FF_LockFAT( pxIOManager ); ulCurrentCluster = FF_FindEndOfChain( pxIOManager, ulNextCluster, &xError ); if( FF_isERR( xError ) == pdFALSE ) { for( xIndex = 0; xIndex < ( BaseType_t ) ulClusterToExtend; xIndex++ ) { /* In FF_ExtendFile() */ ulNextCluster = FF_FindFreeCluster( pxIOManager, &xError, pdTRUE ); if( ( FF_isERR( xError ) == pdFALSE ) && ( ulNextCluster == 0UL ) ) { xError = ( FF_Error_t ) ( FF_ERR_FAT_NO_FREE_CLUSTERS | FF_EXTENDFILE ); } if( FF_isERR( xError ) ) { break; } /* Can not use this buffer earlier because of FF_FindEndOfChain/FF_FindFreeCluster */ FF_InitFATBuffers( &xFATBuffers, FF_MODE_WRITE ); xError = FF_putFATEntry( pxIOManager, ulCurrentCluster, ulNextCluster, &xFATBuffers ); if( FF_isERR( xError ) ) { break; } xError = FF_ReleaseFATBuffers( pxIOManager, &xFATBuffers ); if( FF_isERR( xError ) ) { break; } ulCurrentCluster = ulNextCluster; } if( FF_isERR( xError ) == pdFALSE ) { pxFile->ulEndOfChain = ulCurrentCluster; } pxFile->ulChainLength += xIndex; } FF_UnlockFAT( pxIOManager ); { FF_Error_t xTempError; xTempError = FF_DecreaseFreeClusters( pxIOManager, ( uint32_t ) xIndex ); /* Keep Tab of Numbers for fast FreeSize() */ if( FF_isERR( xError ) == pdFALSE ) { xError = xTempError; } } /* We must ensure that the ulAddrCurrentCluster is not out-of-sync with the CurrentCluster number. * This could have occurred in append mode, where the file was opened with a filesize % clustersize == 0 * because of a seek, where the ulAddrCurrentCluster was not updated after extending. This caused the data to * be written to the previous cluster(s). */ if( ( pxFile->ulCurrentCluster == pxFile->ulChainLength - 1 ) && ( pxFile->ulAddrCurrentCluster != pxFile->ulEndOfChain ) ) { pxFile->ulAddrCurrentCluster = pxFile->ulEndOfChain; } /* By default, 'ffconfigFILE_EXTEND_FLUSHES_BUFFERS' is * defined as 1. * Users may set it to zero in order to increase the * speed of writing to disk. */ #if ( ffconfigFILE_EXTEND_FLUSHES_BUFFERS != 0 ) { FF_Error_t xTempError; xTempError = FF_FlushCache( pxIOManager ); if( FF_isERR( xError ) == pdFALSE ) { xError = xTempError; } } #endif /* ffconfigFILE_EXTEND_FLUSHES_BUFFERS */ if( pxFile->ulFilePointer == pxFile->ulFileSize ) { /* Writing at the end of a file, while new clusters have just been added. * Make sure that the fields 'ulCurrentCluster' and 'ulAddrCurrentCluster' are * set correctly. */ FF_Error_t xTempError = FF_ERR_NONE; uint32_t ulNewCluster = FF_getClusterChainNumber( pxIOManager, pxFile->ulFilePointer, 1 ); FF_LockFAT( pxIOManager ); { pxFile->ulAddrCurrentCluster = FF_TraverseFAT( pxIOManager, pxFile->ulObjectCluster, ulNewCluster, &( xTempError ) ); pxFile->ulCurrentCluster = ulNewCluster; } FF_UnlockFAT( pxIOManager ); if( FF_isERR( xError ) == pdFALSE ) { xError = xTempError; } } } /* if( ulTotalClustersNeeded > pxFile->ulChainLength ) */ return xError; } /* FF_ExtendFile() */ /*-----------------------------------------------------------*/ static FF_Error_t FF_WriteClusters( FF_FILE * pxFile, uint32_t ulCount, uint8_t * buffer ) { uint32_t ulSectors; uint32_t ulSequentialClusters = 0; uint32_t ulItemLBA; FF_Error_t xError = FF_ERR_NONE; while( ulCount != 0 ) { if( ulCount > 1U ) { ulSequentialClusters = FF_GetSequentialClusters( pxFile->pxIOManager, pxFile->ulAddrCurrentCluster, ulCount - 1, &xError ); if( FF_isERR( xError ) ) { break; } } else { /* Handling the last cluster which is a single one. */ } ulSectors = ( ulSequentialClusters + 1 ) * pxFile->pxIOManager->xPartition.ulSectorsPerCluster; ulItemLBA = FF_Cluster2LBA( pxFile->pxIOManager, pxFile->ulAddrCurrentCluster ); ulItemLBA = FF_getRealLBA( pxFile->pxIOManager, ulItemLBA ); xError = FF_BlockWrite( pxFile->pxIOManager, ulItemLBA, ulSectors, buffer, pdFALSE ); if( FF_isERR( xError ) ) { break; } ulCount -= ulSequentialClusters + 1; FF_LockFAT( pxFile->pxIOManager ); { pxFile->ulAddrCurrentCluster = FF_TraverseFAT( pxFile->pxIOManager, pxFile->ulAddrCurrentCluster, ulSequentialClusters + 1, &xError ); } FF_UnlockFAT( pxFile->pxIOManager ); if( FF_isERR( xError ) ) { break; } pxFile->ulCurrentCluster += ( ulSequentialClusters + 1 ); buffer += ulSectors * pxFile->pxIOManager->usSectorSize; ulSequentialClusters = 0; } return xError; } /* FF_WriteClusters */ /*-----------------------------------------------------------*/ /** * @private * @brief Calculate the Logical Block Address (LBA) * * @param pxFile The file handle * * @return LBA * * Must be set: * - pxFile->ulFilePointer : byte offset in file * - pxFile->ulAddrCurrentCluster : physical cluster on the partition **/ static uint32_t FF_FileLBA( FF_FILE * pxFile ) { uint32_t ulItemLBA; ulItemLBA = FF_Cluster2LBA( pxFile->pxIOManager, pxFile->ulAddrCurrentCluster ); ulItemLBA += FF_getMajorBlockNumber( pxFile->pxIOManager, pxFile->ulFilePointer, 1 ); ulItemLBA = FF_getRealLBA( pxFile->pxIOManager, ulItemLBA ); ulItemLBA += FF_getMinorBlockNumber( pxFile->pxIOManager, pxFile->ulFilePointer, 1 ); return ulItemLBA; } /* FF_FileLBA() */ /*-----------------------------------------------------------*/ /** * @private * @brief Depending on FilePointer, calculate CurrentCluster * @brief and traverse the FAT to find the right ulAddrCurrentCluster * * @param pxFile The file handle * * @return FF_ERR_NONE on success * @return Possible error returned by FF_TraverseFAT() or END_OF_DIR * * Side effects: * - pxFile->ulCurrentCluster : relative cluster number (0 <= Num < ulChainLength) * - pxFile->ulAddrCurrentCluster : fysical cluster on the partition **/ static uint32_t FF_SetCluster( FF_FILE * pxFile, FF_Error_t * pxError ) { FF_IOManager_t * pxIOManager = pxFile->pxIOManager; uint32_t ulNewCluster = FF_getClusterChainNumber( pxIOManager, pxFile->ulFilePointer, 1 ); FF_Error_t xResult = FF_ERR_NONE; uint32_t ulReturn; if( ulNewCluster > pxFile->ulCurrentCluster ) { FF_LockFAT( pxIOManager ); { pxFile->ulAddrCurrentCluster = FF_TraverseFAT( pxIOManager, pxFile->ulAddrCurrentCluster, ulNewCluster - pxFile->ulCurrentCluster, &xResult ); } FF_UnlockFAT( pxIOManager ); } else if( ulNewCluster < pxFile->ulCurrentCluster ) { FF_LockFAT( pxIOManager ); { pxFile->ulAddrCurrentCluster = FF_TraverseFAT( pxIOManager, pxFile->ulObjectCluster, ulNewCluster, &xResult ); } FF_UnlockFAT( pxIOManager ); } else { /* Well positioned. */ } if( FF_isERR( xResult ) == pdFALSE ) { pxFile->ulCurrentCluster = ulNewCluster; ulReturn = FF_FileLBA( pxFile ); } else { ulReturn = 0; } *pxError = xResult; return ulReturn; } /* FF_SetCluster() */ /*-----------------------------------------------------------*/ static int32_t FF_ReadPartial( FF_FILE * pxFile, uint32_t ulItemLBA, uint32_t ulRelBlockPos, uint32_t ulCount, uint8_t * pucBuffer, FF_Error_t * pxError ) { FF_Error_t xError = FF_ERR_NONE; uint32_t ulBytesRead; /* Bytes to read are within a block and less than a block size. */ #if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 ) { BaseType_t xLastRead; /* Optimised method: each file handle holds one data block * in cache: 'pxFile->pucBuffer'. */ /* See if the current block will be accessed after this read: */ if( ( ulRelBlockPos + ulCount ) >= ( uint32_t ) pxFile->pxIOManager->usSectorSize ) { /* After this read, ulFilePointer will point to the next block/sector. */ xLastRead = pdTRUE; } else { /* It is not the last read within this block/sector. */ xLastRead = pdFALSE; } if( ( pxFile->ucState & FF_BUFSTATE_VALID ) == 0 ) { xError = FF_BlockRead( pxFile->pxIOManager, ulItemLBA, 1, pxFile->pucBuffer, pdFALSE ); if( FF_isERR( xError ) == pdFALSE ) { pxFile->ucState = FF_BUFSTATE_VALID; } } if( ( pxFile->ucState & FF_BUFSTATE_VALID ) != 0 ) { memcpy( pucBuffer, pxFile->pucBuffer + ulRelBlockPos, ulCount ); pxFile->ulFilePointer += ulCount; ulBytesRead = ulCount; if( ( xLastRead == pdTRUE ) && ( ( pxFile->ucState & FF_BUFSTATE_WRITTEN ) != 0 ) ) { /* If the data was changed (file in 'update' mode), store the changes: */ xError = FF_BlockWrite( pxFile->pxIOManager, ulItemLBA, 1, pxFile->pucBuffer, pdFALSE ); } } else { ulBytesRead = 0ul; } if( xLastRead == pdTRUE ) { /* As the next FF_Read() will go passed the current block, invalidate the buffer now. */ pxFile->ucState = FF_BUFSTATE_INVALID; } } #else /* if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 ) */ { FF_Buffer_t * pxBuffer; /* Reading in the standard way, using FF_Buffer_t. */ pxBuffer = FF_GetBuffer( pxFile->pxIOManager, ulItemLBA, FF_MODE_READ ); if( pxBuffer == NULL ) { xError = ( FF_Error_t ) ( FF_ERR_DEVICE_DRIVER_FAILED | FF_READ ); ulBytesRead = 0ul; } else { memcpy( pucBuffer, pxBuffer->pucBuffer + ulRelBlockPos, ulCount ); /* Releasing a buffer in FF_MODE_READ mode will not lead to an error, * because no disk access is needed. */ xError = FF_ReleaseBuffer( pxFile->pxIOManager, pxBuffer ); pxFile->ulFilePointer += ulCount; ulBytesRead = ulCount; } } #endif /* if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 ) */ *pxError = xError; return ulBytesRead; } /* FF_ReadPartial() */ /*-----------------------------------------------------------*/ /** * @public * @brief Equivalent to fread() * * @param pxFile FF_FILE object that was created by FF_Open(). * @param ulElementSize The size of an element to read. * @param ulCount The number of elements to read. * @param buffer A pointer to a buffer of adequate size to be filled with the requested data. * * @return Number of bytes read. * * FF_Read() and FF_Write() work very similar. They both complete their task in 5 steps: * 1. Read bytes up to a sector border: FF_ReadPartial() * 2. Read sectors up to cluster border: FF_BlockRead() * 3. Read complete clusters: FF_ReadClusters() * 4. Read remaining sectors: FF_BlockRead() * 5. Read remaining bytes: FF_ReadPartial() **/ int32_t FF_Read( FF_FILE * pxFile, uint32_t ulElementSize, uint32_t ulCount, uint8_t * pucBuffer ) { uint32_t ulBytesLeft = ulElementSize * ulCount; uint32_t ulBytesRead = 0; uint32_t ulBytesToRead; FF_IOManager_t * pxIOManager; uint32_t ulRelBlockPos; uint32_t ulItemLBA; int32_t lResult; uint32_t ulSectors; uint32_t ulRelClusterPos; uint32_t ulBytesPerCluster; FF_Error_t xError; if( pxFile == NULL ) { xError = ( FF_Error_t ) ( FF_ERR_NULL_POINTER | FF_READ ); } else { /* Check validity of the handle and the current position within the file. */ xError = FF_CheckValid( pxFile ); if( FF_isERR( xError ) == pdFALSE ) { if( ( pxFile->ucMode & FF_MODE_READ ) == 0 ) { /* File was not opened with READ mode access. */ xError = ( FF_Error_t ) ( FF_ERR_FILE_NOT_OPENED_IN_READ_MODE | FF_READ ); } else if( pxFile->ulFilePointer >= pxFile->ulFileSize ) { /* The end-of-file is reached. The error READ_ZERO will not be * returned, it is just used to avoid further processing. */ xError = ( FF_Error_t ) ( FF_ERR_FILE_READ_ZERO | FF_READ ); } else if( ( pxFile->ulFilePointer + ulBytesLeft ) > pxFile->ulFileSize ) { /* Note that many bytes can be read. */ ulBytesLeft = pxFile->ulFileSize - pxFile->ulFilePointer; } } else { /* The file handle is not valid. */ } } /* else pxFile != NULL */ if( FF_isERR( xError ) == pdFALSE ) { pxIOManager = pxFile->pxIOManager; /* And calculate the Logical Block Address. */ ulItemLBA = FF_SetCluster( pxFile, &xError ); /* Get the position within a block. */ ulRelBlockPos = FF_getMinorBlockEntry( pxIOManager, pxFile->ulFilePointer, 1 ); /* Open a do {} while( 0 ) loop to allow easy breaks: */ do { if( ( ulRelBlockPos + ulBytesLeft ) <= ( uint32_t ) pxIOManager->usSectorSize ) { /*---------- A small read within the current block only. */ ulBytesRead = FF_ReadPartial( pxFile, ulItemLBA, ulRelBlockPos, ulBytesLeft, pucBuffer, &xError ); break; } /*---------- Read (memcpy) to a Sector Boundary. */ if( ulRelBlockPos != 0 ) { /* Not on a sector boundary, at this point the LBA is known. */ ulBytesToRead = pxIOManager->usSectorSize - ulRelBlockPos; ulBytesRead = FF_ReadPartial( pxFile, ulItemLBA, ulRelBlockPos, ulBytesToRead, pucBuffer, &xError ); if( FF_isERR( xError ) ) { break; } ulBytesLeft -= ulBytesRead; pucBuffer += ulBytesRead; } /*---------- Read sectors, up to a Cluster Boundary. */ ulBytesPerCluster = ( pxIOManager->xPartition.ulSectorsPerCluster * pxIOManager->usSectorSize ); ulRelClusterPos = pxFile->ulFilePointer % ( ulBytesPerCluster * pxIOManager->xPartition.ucBlkFactor ); if( ( ulRelClusterPos != 0 ) && ( ( ulRelClusterPos + ulBytesLeft ) >= ulBytesPerCluster ) ) { /* Need to get to cluster boundary. */ ulItemLBA = FF_SetCluster( pxFile, &xError ); if( FF_isERR( xError ) ) { break; } ulSectors = pxIOManager->xPartition.ulSectorsPerCluster - ( ulRelClusterPos / pxIOManager->usSectorSize ); xError = FF_BlockRead( pxIOManager, ulItemLBA, ulSectors, pucBuffer, pdFALSE ); if( FF_isERR( xError ) ) { break; } ulBytesToRead = ulSectors * pxIOManager->usSectorSize; ulBytesLeft -= ulBytesToRead; pucBuffer += ulBytesToRead; ulBytesRead += ulBytesToRead; pxFile->ulFilePointer += ulBytesToRead; } /*---------- Read entire clusters. */ if( ulBytesLeft >= ulBytesPerCluster ) { uint32_t ulClusters; FF_SetCluster( pxFile, &xError ); if( FF_isERR( xError ) ) { break; } ulClusters = ulBytesLeft / ulBytesPerCluster; xError = FF_ReadClusters( pxFile, ulClusters, pucBuffer ); if( FF_isERR( xError ) ) { break; } ulBytesToRead = ulBytesPerCluster * ulClusters; pxFile->ulFilePointer += ulBytesToRead; ulBytesLeft -= ulBytesToRead; pucBuffer += ulBytesToRead; ulBytesRead += ulBytesToRead; } /*---------- Read Remaining Blocks. */ while( ulBytesLeft >= ( uint32_t ) pxIOManager->usSectorSize ) { ulSectors = ulBytesLeft / pxIOManager->usSectorSize; { /* HT: I'd leave these pPart/ulOffset for readability */ /* and shorter code lines */ FF_Partition_t * pPart = &( pxIOManager->xPartition ); uint32_t ulOffset = ( pxFile->ulFilePointer / pxIOManager->usSectorSize ) % pPart->ulSectorsPerCluster; uint32_t ulRemain = pPart->ulSectorsPerCluster - ulOffset; if( ulSectors > ulRemain ) { ulSectors = ulRemain; } } ulItemLBA = FF_SetCluster( pxFile, &xError ); if( FF_isERR( xError ) ) { break; } xError = FF_BlockRead( pxIOManager, ulItemLBA, ulSectors, pucBuffer, pdFALSE ); if( FF_isERR( xError ) ) { break; } ulBytesToRead = ulSectors * pxIOManager->usSectorSize; pxFile->ulFilePointer += ulBytesToRead; ulBytesLeft -= ulBytesToRead; pucBuffer += ulBytesToRead; ulBytesRead += ulBytesToRead; } /*---------- Read (memcpy) Remaining Bytes */ if( ulBytesLeft == 0 ) { break; } ulItemLBA = FF_SetCluster( pxFile, &xError ); if( FF_isERR( xError ) ) { break; } /* Bytes to read are within a block and less than a block size. */ FF_ReadPartial( pxFile, ulItemLBA, 0, ulBytesLeft, pucBuffer, &xError ); if( FF_isERR( xError ) == pdFALSE ) { ulBytesRead += ulBytesLeft; } } while( pdFALSE ); } /* if( FF_isERR( xError ) == pdFALSE ) */ if( FF_GETERROR( xError ) == FF_ERR_FILE_READ_ZERO ) { lResult = 0; } else if( FF_isERR( xError ) ) { lResult = xError; } else { lResult = ( int32_t ) ( ulBytesRead / ulElementSize ); } return lResult; } /* FF_Read() */ /*-----------------------------------------------------------*/ /** * @public * @brief Equivalent to fgetc() * * @param pxFile FF_FILE object that was created by FF_Open(). * * @return The character that was read (cast as a 32-bit interger). -1 on EOF. * @return FF_Error_t code. (Check with if(FF_isERR(xRetVal)) {}). * @return -1 EOF (end of file). * **/ int32_t FF_GetC( FF_FILE * pxFile ) { uint32_t ulItemLBA; uint8_t ucReturnedChar; uint32_t ulRelBlockPos; FF_Error_t xResult; if( pxFile == NULL ) { xResult = FF_ERR_NULL_POINTER | FF_GETC; /* Ensure this is a signed error. */ } else if( ( pxFile->ucMode & FF_MODE_READ ) == 0 ) { xResult = FF_ERR_FILE_NOT_OPENED_IN_READ_MODE | FF_GETC; } else if( pxFile->ulFilePointer >= pxFile->ulFileSize ) { /* The end-of-file is reached. The error READ_ZERO will not be * returned, it is just used to avoid further processing. */ xResult = FF_ERR_FILE_READ_ZERO | FF_READ; } else { ulRelBlockPos = FF_getMinorBlockEntry( pxFile->pxIOManager, pxFile->ulFilePointer, 1 ); ulItemLBA = FF_SetCluster( pxFile, &xResult ); if( FF_isERR( xResult ) == pdFALSE ) { FF_ReadPartial( pxFile, ulItemLBA, ulRelBlockPos, 1, &ucReturnedChar, &xResult ); if( FF_isERR( xResult ) == pdFALSE ) { xResult = ( int32_t ) ( ( uint32_t ) ucReturnedChar ); } } } return ( int32_t ) xResult; } /* FF_GetC() */ /*-----------------------------------------------------------*/ /** * @public * @brief Gets a Line from a Text File, but no more than ulLimit characters. The line will be NULL terminated. * * The behaviour of this function is undefined when called on a binary file. * It should just read in ulLimit bytes of binary, and ZERO terminate the line. * * This function works for both UNIX line feeds, and Windows CRLF type files. * * @param pxFile The FF_FILE object pointer. * @param szLine The character buffer where the line should be stored. * @param ulLimit This should be the max number of characters that szLine can hold. * * @return The number of characters read from the line, on success. * @return 0 when no more lines are available, or when ulLimit is 0. * @return FF_ERR_NULL_POINTER if pxFile or szLine are NULL; * **/ int32_t FF_GetLine( FF_FILE * pxFile, char * pcLine, uint32_t ulLimit ) { int32_t iChar = 0; BaseType_t xIndex; FF_Error_t xResult = FF_ERR_NONE; if( ( pxFile == NULL ) || ( pcLine == NULL ) ) { xResult = FF_ERR_NULL_POINTER | FF_GETLINE; } else { for( xIndex = 0; xIndex < ( BaseType_t ) ( ulLimit - 1 ); ++xIndex ) { iChar = FF_GetC( pxFile ); if( FF_isERR( iChar ) == pdFALSE ) { pcLine[ xIndex ] = ( char ) iChar; if( iChar == '\n' ) { /* Read until the first linefeed. Move xIndex forward so the * null terminator does not overwrite the \n. xIndex must be less * thank ( ulLimit - 1 ), so incrementing it here cannot make it * greater than ulLimit - 1, so the NULL can be inserted without * overflowing the buffer. */ xIndex++; break; } } else { if( ( FF_GETERROR( iChar ) == FF_ERR_FILE_READ_ZERO ) && ( xIndex > 0 ) ) { /* Although FF_GetC() returns an End Of File, * the last few characters will be returned first. */ iChar = xIndex; } break; } } /* Make sure that the resulting string always ends with a zero: */ pcLine[ xIndex ] = '\0'; /*_RB_ In some paths this will be the second time FF_isERR() is called * on the same value. */ if( FF_isERR( iChar ) == pdFALSE ) { /* Return the number of bytes read. */ xResult = xIndex; } else { /* Return iChar as an error code (see FF_GetC()). */ xResult = iChar; } } return xResult; } /* FF_GetLine() */ /*-----------------------------------------------------------*/ static int32_t FF_WritePartial( FF_FILE * pxFile, uint32_t ulItemLBA, uint32_t ulRelBlockPos, uint32_t ulCount, const uint8_t * pucBuffer, FF_Error_t * pxError ) { FF_Error_t xError; uint32_t ulBytesWritten; #if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 ) { BaseType_t xLastRead; if( ( ulRelBlockPos + ulCount ) >= ( uint32_t ) pxFile->pxIOManager->usSectorSize ) { /* After this read, ulFilePointer will point to the next block/sector. */ xLastRead = pdTRUE; } else { /* It is not the last read within this block/sector. */ xLastRead = pdFALSE; } if( ( ( pxFile->ucState & FF_BUFSTATE_VALID ) == 0 ) && ( ( ulRelBlockPos != 0 ) || ( pxFile->ulFilePointer < pxFile->ulFileSize ) ) ) { xError = FF_BlockRead( pxFile->pxIOManager, ulItemLBA, 1, pxFile->pucBuffer, pdFALSE ); /* pxFile->ucState will be set later on. */ } else { xError = FF_ERR_NONE; /* the buffer is valid or a whole block/sector will be written, so it is * not necessary to read the contents first. */ } if( FF_isERR( xError ) == pdFALSE ) { memcpy( pxFile->pucBuffer + ulRelBlockPos, pucBuffer, ulCount ); if( xLastRead == pdTRUE ) { xError = FF_BlockWrite( pxFile->pxIOManager, ulItemLBA, 1, pxFile->pucBuffer, pdFALSE ); pxFile->ucState = FF_BUFSTATE_INVALID; } else { pxFile->ucState |= FF_BUFSTATE_WRITTEN | FF_BUFSTATE_VALID; } } else { pxFile->ucState = FF_BUFSTATE_INVALID; } } #else /* if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 ) */ { FF_Buffer_t * pxBuffer; if( ( ulRelBlockPos == 0 ) && ( pxFile->ulFilePointer >= pxFile->ulFileSize ) ) { /* An entire sector will be written. */ pxBuffer = FF_GetBuffer( pxFile->pxIOManager, ulItemLBA, FF_MODE_WR_ONLY ); } else { /* A partial write will be done, make sure to read the contents before * changing anything. */ pxBuffer = FF_GetBuffer( pxFile->pxIOManager, ulItemLBA, FF_MODE_WRITE ); } if( pxBuffer == NULL ) { xError = ( FF_Error_t ) ( FF_ERR_DEVICE_DRIVER_FAILED | FF_WRITE ); } else { /* Here we copy to the sector boundary. */ memcpy( ( pxBuffer->pucBuffer + ulRelBlockPos ), pucBuffer, ulCount ); xError = FF_ReleaseBuffer( pxFile->pxIOManager, pxBuffer ); } } #endif /* if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 ) */ if( FF_isERR( xError ) == pdFALSE ) { pxFile->ulFilePointer += ulCount; ulBytesWritten = ulCount; if( pxFile->ulFilePointer > pxFile->ulFileSize ) { pxFile->ulFileSize = pxFile->ulFilePointer; } } else { ulBytesWritten = 0ul; } *pxError = xError; return ulBytesWritten; } /* FF_WritePartial() */ /*-----------------------------------------------------------*/ /** * @public * @brief Writes data to a File. * * @param pxFile FILE Pointer. * @param ulElementSize Size of an Element of Data to be copied. (in bytes). * @param ulCount Number of Elements of Data to be copied. (ulElementSize * ulCount must not exceed ((2^31)-1) bytes. (2GB). For best performance, multiples of 512 bytes or Cluster sizes are best. * @param pucBuffer Byte-wise pucBuffer containing the data to be written. * * FF_Read() and FF_Write() work very similar. They both complete their task in 5 steps: * 1. Write bytes up to a sector border: FF_WritePartial() * 2. Write sectors up to cluster border: FF_BlockWrite() * 3. Write complete clusters: FF_WriteClusters() * 4. Write remaining sectors: FF_BlockWrite() * 5. Write remaining bytes: FF_WritePartial() * @return **/ int32_t FF_Write( FF_FILE * pxFile, uint32_t ulElementSize, uint32_t ulCount, uint8_t * pucBuffer ) { uint32_t ulBytesLeft = ulElementSize * ulCount; uint32_t nBytesWritten = 0; uint32_t nBytesToWrite; FF_IOManager_t * pxIOManager; uint32_t ulRelBlockPos; uint32_t ulItemLBA; int32_t lResult; uint32_t ulSectors; uint32_t ulRelClusterPos; uint32_t ulBytesPerCluster; FF_Error_t xError; if( pxFile == NULL ) { xError = ( FF_Error_t ) ( FF_ERR_NULL_POINTER | FF_READ ); } else { /* Check validity of the handle and the current position within the file. */ xError = FF_CheckValid( pxFile ); if( FF_isERR( xError ) == pdFALSE ) { if( ( pxFile->ucMode & FF_MODE_WRITE ) == 0 ) { xError = ( FF_Error_t ) ( FF_ERR_FILE_NOT_OPENED_IN_WRITE_MODE | FF_WRITE ); } /* Make sure a write is after the append point. */ else if( ( pxFile->ucMode & FF_MODE_APPEND ) != 0 ) { if( pxFile->ulFilePointer < pxFile->ulFileSize ) { xError = FF_Seek( pxFile, 0, FF_SEEK_END ); } } } } if( FF_isERR( xError ) == pdFALSE ) { pxIOManager = pxFile->pxIOManager; /* Open a do{} while( 0 ) loop to allow the use of breaks */ do { /* Extend File for at least ulBytesLeft! * Handle file-space allocation + 1 byte because the code assumes there is always a next cluster */ xError = FF_ExtendFile( pxFile, pxFile->ulFilePointer + ulBytesLeft + 1 ); if( FF_isERR( xError ) ) { /* On every error, break from the while( 0 ) loop. */ break; } ulRelBlockPos = FF_getMinorBlockEntry( pxIOManager, pxFile->ulFilePointer, 1 ); /* Get the position within a block. */ ulItemLBA = FF_SetCluster( pxFile, &xError ); if( FF_isERR( xError ) ) { break; } if( ( ulRelBlockPos + ulBytesLeft ) <= ( uint32_t ) pxIOManager->usSectorSize ) { /* Bytes to write are within a block and and do not go passed the current block. */ nBytesWritten = FF_WritePartial( pxFile, ulItemLBA, ulRelBlockPos, ulBytesLeft, pucBuffer, &xError ); break; } /*---------- Write (memcpy) to a Sector Boundary. */ if( ulRelBlockPos != 0 ) { /* Not writing on a sector boundary, at this point the LBA is known. */ nBytesToWrite = pxIOManager->usSectorSize - ulRelBlockPos; nBytesWritten = FF_WritePartial( pxFile, ulItemLBA, ulRelBlockPos, nBytesToWrite, pucBuffer, &xError ); if( FF_isERR( xError ) ) { break; } ulBytesLeft -= nBytesWritten; pucBuffer += nBytesWritten; } /*---------- Write sectors, up to a Cluster Boundary. */ ulBytesPerCluster = ( pxIOManager->xPartition.ulSectorsPerCluster * pxIOManager->usSectorSize ); ulRelClusterPos = FF_getClusterPosition( pxIOManager, pxFile->ulFilePointer, 1 ); if( ( ulRelClusterPos != 0 ) && ( ( ulRelClusterPos + ulBytesLeft ) >= ulBytesPerCluster ) ) { /* Need to get to cluster boundary */ ulItemLBA = FF_SetCluster( pxFile, &xError ); if( FF_isERR( xError ) ) { break; } ulSectors = pxIOManager->xPartition.ulSectorsPerCluster - ( ulRelClusterPos / pxIOManager->usSectorSize ); xError = FF_BlockWrite( pxIOManager, ulItemLBA, ulSectors, pucBuffer, pdFALSE ); if( FF_isERR( xError ) ) { break; } nBytesToWrite = ulSectors * pxIOManager->usSectorSize; ulBytesLeft -= nBytesToWrite; pucBuffer += nBytesToWrite; nBytesWritten += nBytesToWrite; pxFile->ulFilePointer += nBytesToWrite; if( pxFile->ulFilePointer > pxFile->ulFileSize ) { pxFile->ulFileSize = pxFile->ulFilePointer; } } /*---------- Write entire Clusters. */ if( ulBytesLeft >= ulBytesPerCluster ) { uint32_t ulClusters; FF_SetCluster( pxFile, &xError ); if( FF_isERR( xError ) ) { break; } ulClusters = ( ulBytesLeft / ulBytesPerCluster ); xError = FF_WriteClusters( pxFile, ulClusters, pucBuffer ); if( FF_isERR( xError ) ) { break; } nBytesToWrite = ulBytesPerCluster * ulClusters; ulBytesLeft -= nBytesToWrite; pucBuffer += nBytesToWrite; nBytesWritten += nBytesToWrite; pxFile->ulFilePointer += nBytesToWrite; if( pxFile->ulFilePointer > pxFile->ulFileSize ) { pxFile->ulFileSize = pxFile->ulFilePointer; } } /*---------- Write Remaining Blocks */ while( ulBytesLeft >= ( uint32_t ) pxIOManager->usSectorSize ) { ulSectors = ulBytesLeft / pxIOManager->usSectorSize; { /* HT: I'd leave these pPart/ulOffset for readability... */ FF_Partition_t * pPart = &( pxIOManager->xPartition ); uint32_t ulOffset = ( pxFile->ulFilePointer / pxIOManager->usSectorSize ) % pPart->ulSectorsPerCluster; uint32_t ulRemain = pPart->ulSectorsPerCluster - ulOffset; if( ulSectors > ulRemain ) { ulSectors = ulRemain; } } ulItemLBA = FF_SetCluster( pxFile, &xError ); if( FF_isERR( xError ) ) { break; } xError = FF_BlockWrite( pxIOManager, ulItemLBA, ulSectors, pucBuffer, pdFALSE ); if( FF_isERR( xError ) ) { break; } nBytesToWrite = ulSectors * pxIOManager->usSectorSize; ulBytesLeft -= nBytesToWrite; pucBuffer += nBytesToWrite; nBytesWritten += nBytesToWrite; pxFile->ulFilePointer += nBytesToWrite; if( pxFile->ulFilePointer > pxFile->ulFileSize ) { pxFile->ulFileSize = pxFile->ulFilePointer; } } /*---------- Write (memcpy) Remaining Bytes */ if( ulBytesLeft == 0 ) { break; } ulItemLBA = FF_SetCluster( pxFile, &xError ); if( FF_isERR( xError ) ) { break; } FF_WritePartial( pxFile, ulItemLBA, 0, ulBytesLeft, pucBuffer, &xError ); nBytesWritten += ulBytesLeft; } while( pdFALSE ); } if( FF_isERR( xError ) ) { lResult = xError; } else { lResult = ( int32_t ) ( nBytesWritten / ulElementSize ); } return lResult; } /* FF_Write() */ /*-----------------------------------------------------------*/ /** * @public * @brief Writes a char to a FILE. * * @param pxFile FILE Pointer. * @param ucValue Char to be placed in the file. * * @return Returns the value written to the file, or a value less than 0. * **/ int32_t FF_PutC( FF_FILE * pxFile, uint8_t ucValue ) { uint32_t ulItemLBA; uint32_t ulRelBlockPos; FF_Error_t xResult; if( pxFile == NULL ) { /* Ensure we don't have a Null file pointer on a Public interface. */ xResult = FF_ERR_NULL_POINTER | FF_PUTC; } else if( ( pxFile->ucMode & FF_MODE_WRITE ) == 0 ) { xResult = FF_ERR_FILE_NOT_OPENED_IN_WRITE_MODE | FF_PUTC; } else { xResult = FF_ERR_NONE; do { /* Make sure a write is after the append point. */ if( ( pxFile->ucMode & FF_MODE_APPEND ) != 0 ) { if( pxFile->ulFilePointer < pxFile->ulFileSize ) { xResult = FF_Seek( pxFile, 0, FF_SEEK_END ); if( FF_isERR( xResult ) ) { break; } } } ulRelBlockPos = FF_getMinorBlockEntry( pxFile->pxIOManager, pxFile->ulFilePointer, 1 ); /* Handle File Space Allocation. */ /* We'll write 1 byte and always have a next cluster reserved. */ xResult = FF_ExtendFile( pxFile, pxFile->ulFilePointer + 2 ); if( FF_isERR( xResult ) ) { break; } ulItemLBA = FF_SetCluster( pxFile, &xResult ); if( FF_isERR( xResult ) ) { break; } FF_WritePartial( pxFile, ulItemLBA, ulRelBlockPos, 1, &ucValue, &xResult ); if( FF_isERR( xResult ) == pdFALSE ) { xResult = ( FF_Error_t ) ucValue; } } while( pdFALSE ); } return xResult; } /* FF_PutC() */ /*-----------------------------------------------------------*/ /** * @public * @brief Equivalent to fseek() * * @param pxFile FF_FILE object that was created by FF_Open(). * @param ulOffset An integer (+/-) to seek to, from the specified origin. * @param xOrigin Where to seek from. (FF_SEEK_SET seek from start, FF_SEEK_CUR seek from current position, or FF_SEEK_END seek from end of file). * * @return 0 on Sucess, * @return -2 if offset results in an invalid position in the file. * @return FF_ERR_NULL_POINTER if a FF_FILE pointer was not received. * @return -3 if an invalid origin was provided. * **/ FF_Error_t FF_Seek( FF_FILE * pxFile, int32_t lOffset, BaseType_t xOrigin ) { FF_Error_t xError; uint32_t ulPosition = 0ul; xError = FF_CheckValid( pxFile ); if( FF_isERR( xError ) == pdFALSE ) { xError = FF_FlushCache( pxFile->pxIOManager ); #if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 ) { if( FF_isERR( xError ) == pdFALSE ) { /* Here we must ensure that if the user tries to seek, and we had data in the file's * write buffer that this is written to disk. */ if( ( pxFile->ucState & FF_BUFSTATE_WRITTEN ) != 0 ) { xError = FF_BlockWrite( pxFile->pxIOManager, FF_FileLBA( pxFile ), 1, pxFile->pucBuffer, pdFALSE ); } pxFile->ucState = FF_BUFSTATE_INVALID; } } #endif /* ffconfigOPTIMISE_UNALIGNED_ACCESS */ if( FF_isERR( xError ) == pdFALSE ) { if( xOrigin == FF_SEEK_SET ) { ulPosition = ( uint32_t ) lOffset; } else if( xOrigin == FF_SEEK_CUR ) { if( lOffset >= ( int32_t ) 0 ) { ulPosition = pxFile->ulFilePointer + ( ( uint32_t ) lOffset ); } else { ulPosition = pxFile->ulFilePointer - ( ( uint32_t ) ( -lOffset ) ); } } else if( xOrigin == FF_SEEK_END ) { /* 'FF_SEEK_END' only allows zero or negative values. */ if( lOffset <= ( int32_t ) 0 ) { ulPosition = pxFile->ulFileSize - ( ( uint32_t ) ( -lOffset ) ); } } else { xError = ( FF_Error_t ) ( FF_SEEK | FF_ERR_FILE_SEEK_INVALID_ORIGIN ); /* To supress a compiler warning. */ ulPosition = ( uint32_t ) 0u; } if( FF_isERR( xError ) == pdFALSE ) { if( ulPosition <= ( uint32_t ) pxFile->ulFileSize ) { if( ulPosition != ( uint32_t ) pxFile->ulFilePointer ) { pxFile->ulFilePointer = ulPosition; FF_SetCluster( pxFile, &xError ); } } else { xError = ( FF_Error_t ) ( FF_SEEK | FF_ERR_FILE_SEEK_INVALID_POSITION ); } } } } return xError; } /* FF_Seek() */ /*-----------------------------------------------------------*/ #if ( ffconfigREMOVABLE_MEDIA != 0 ) /** * @public * @brief Invalidate all file handles belonging to pxIOManager * * @param pIoMan FF_IOManager_t object that was created by FF_CreateIOManger(). * * @return 0 if no handles were open * @return >0 the amount of handles that were invalidated * @return <0 probably an invalid FF_IOManager_t pointer * **/ int32_t FF_Invalidate( FF_IOManager_t * pxIOManager ) { int32_t xResult; FF_FILE * pxFileChain; if( pxIOManager == NULL ) { xResult = FF_ERR_NULL_POINTER | FF_INVALIDATE; } else { xResult = 0; FF_PendSemaphore( pxIOManager->pvSemaphore ); { pxIOManager->ucFlags |= FF_IOMAN_DEVICE_IS_EXTRACTED; /* Semaphore is required, or linked list might change */ pxFileChain = ( FF_FILE * ) pxIOManager->FirstFile; if( pxFileChain != NULL ) { /* Count elements in FirstFile */ do { pxFileChain->ulValidFlags |= FF_VALID_FLAG_INVALID; xResult++; pxFileChain = pxFileChain->pxNext; } while( pxFileChain != NULL ); } } FF_ReleaseSemaphore( pxIOManager->pvSemaphore ); } return xResult; } /* FF_Invalidate() */ #endif /* ffconfigREMOVABLE_MEDIA */ /*-----------------------------------------------------------*/ /** * @public * @brief Check validity of file handle * * @param pxFile FF_FILE object that was created by FF_Open(). * * @return 0 on sucess. * @return FF_ERR_NULL_POINTER if a null pointer was provided. * @return FF_ERR_FILE_BAD_HANDLE if handle is not recognized * @return FF_ERR_FILE_MEDIA_REMOVED please call FF_Close * **/ FF_Error_t FF_CheckValid( FF_FILE * pxFile ) { FF_FILE * pxFileChain; FF_Error_t xError; if( ( pxFile == NULL ) || ( pxFile->pxIOManager == NULL ) ) { xError = ( FF_Error_t ) ( FF_ERR_NULL_POINTER | FF_CHECKVALID ); } else { FF_PendSemaphore( pxFile->pxIOManager->pvSemaphore ); { pxFileChain = ( FF_FILE * ) pxFile->pxIOManager->FirstFile; xError = ( FF_Error_t ) ( FF_ERR_FILE_BAD_HANDLE | FF_CHECKVALID ); while( pxFileChain != NULL ) { if( pxFileChain == pxFile ) { #if ( ffconfigREMOVABLE_MEDIA != 0 ) if( ( pxFileChain->ulValidFlags & FF_VALID_FLAG_INVALID ) != 0 ) { /* The medium has been removed while this file handle was open. */ xError = ( FF_Error_t ) ( FF_ERR_FILE_MEDIA_REMOVED | FF_CHECKVALID ); } else #endif { /* Found the handle, so it is a valid / existing handle. */ xError = FF_ERR_NONE; } break; } pxFileChain = pxFileChain->pxNext; } } FF_ReleaseSemaphore( pxFile->pxIOManager->pvSemaphore ); } return xError; } /* FF_CheckValid() */ /*-----------------------------------------------------------*/ #if ( ffconfigTIME_SUPPORT != 0 ) /** * @public * @brief Set the time-stamp(s) of a file entry * * @param pxFile FF_FILE object that was created by FF_Open(). * @param pxTime FF_SystemTime_t the time stamp * @param uxWhat UBaseType_t a combination of enum ETimeMask * * @return 0 or FF_Error_t * **/ FF_Error_t FF_SetFileTime( FF_FILE * pxFile, FF_SystemTime_t * pxTime, UBaseType_t uxWhat ) { FF_DirEnt_t xOriginalEntry; FF_Error_t xError; xError = FF_CheckValid( pxFile ); if( FF_isERR( xError ) == pdFALSE ) { if( pxFile->ulValidFlags & FF_VALID_FLAG_DELETED ) { /*if (pxFile->FileDeleted) */ xError = ( FF_Error_t ) ( FF_ERR_FILE_NOT_FOUND | FF_SETFILETIME ); } else if( ( pxFile->ucMode & ( FF_MODE_WRITE | FF_MODE_APPEND ) ) == 0 ) { xError = ( FF_Error_t ) ( FF_ERR_FILE_NOT_OPENED_IN_WRITE_MODE | FF_SETFILETIME ); } else { /* Update the Dirent! */ xError = FF_GetEntry( pxFile->pxIOManager, pxFile->usDirEntry, pxFile->ulDirCluster, &xOriginalEntry ); if( FF_isERR( xError ) == pdFALSE ) { if( uxWhat & ETimeCreate ) { xOriginalEntry.xCreateTime = *pxTime; /*/< Date and Time Created. */ } if( uxWhat & ETimeMod ) { xOriginalEntry.xModifiedTime = *pxTime; /*/< Date and Time Modified. */ } if( uxWhat & ETimeAccess ) { xOriginalEntry.xAccessedTime = *pxTime; /*/< Date of Last Access. */ } xError = FF_PutEntry( pxFile->pxIOManager, pxFile->usDirEntry, pxFile->ulDirCluster, &xOriginalEntry, NULL ); } if( FF_isERR( xError ) == pdFALSE ) { xError = FF_FlushCache( pxFile->pxIOManager ); /* Ensure all modfied blocks are flushed to disk! */ } } } return xError; } /* FF_SetFileTime() */ #endif /* ffconfigTIME_SUPPORT */ /*-----------------------------------------------------------*/ #if ( ffconfigTIME_SUPPORT != 0 ) /** * @public * @brief Set the time-stamp(s) of a file entry (by name) * * @param pxIOManager FF_IOManager_t device handle * @param pcPath int8_t/FF_T_WCHAR name of the file * @param pxTime FF_SystemTime_t the time stamp * @param uxWhat UBaseType_t a combination of enum ETimeMask * * @return 0 or FF_Error_t * **/ #if ( ffconfigUNICODE_UTF16_SUPPORT != 0 ) FF_Error_t FF_SetTime( FF_IOManager_t * pxIOManager, const FF_T_WCHAR * pcPath, FF_SystemTime_t * pxTime, UBaseType_t uxWhat ) #else FF_Error_t FF_SetTime( FF_IOManager_t * pxIOManager, const char * pcPath, FF_SystemTime_t * pxTime, UBaseType_t uxWhat ) #endif /* ffconfigUNICODE_UTF16_SUPPORT */ { FF_DirEnt_t xOriginalEntry; FF_Error_t xError; uint32_t ulFileCluster; BaseType_t xIndex; FF_FindParams_t xFindParams; #if ( ffconfigUNICODE_UTF16_SUPPORT != 0 ) FF_T_WCHAR pcFileName[ ffconfigMAX_FILENAME ]; #else char pcFileName[ ffconfigMAX_FILENAME ]; #endif /* ffconfigUNICODE_UTF16_SUPPORT */ xIndex = ( BaseType_t ) STRLEN( pcPath ); memset( &xFindParams, '\0', sizeof( xFindParams ) ); while( xIndex != 0 ) { if( ( pcPath[ xIndex ] == '\\' ) || ( pcPath[ xIndex ] == '/' ) ) { break; } xIndex--; } STRNCPY( pcFileName, ( pcPath + xIndex + 1 ), ffconfigMAX_FILENAME ); if( xIndex == 0 ) { xIndex = 1; } xFindParams.ulDirCluster = FF_FindDir( pxIOManager, pcPath, ( uint16_t ) xIndex, &xError ); if( FF_isERR( xError ) == pdFALSE ) { if( xFindParams.ulDirCluster == 0 ) { xError = ( FF_Error_t ) ( FF_ERR_FILE_NOT_FOUND | FF_SETTIME ); } else { ulFileCluster = FF_FindEntryInDir( pxIOManager, &xFindParams, pcFileName, 0, &xOriginalEntry, &xError ); if( ( FF_isERR( xError ) == pdFALSE ) || ( FF_GETERROR( xError ) == FF_ERR_DIR_END_OF_DIR ) ) { if( ulFileCluster == 0ul ) { /*FF_PRINTF ("FF_SetTime: Can not find '%s'\n", pcFileName); */ xError = ( FF_Error_t ) ( FF_ERR_FILE_NOT_FOUND | FF_SETTIME ); } } } } if( FF_isERR( xError ) == pdFALSE ) { /* Update the Dirent! */ if( uxWhat & ETimeCreate ) { xOriginalEntry.xCreateTime = *pxTime; /*/< Date and Time Created. */ } if( uxWhat & ETimeMod ) { xOriginalEntry.xModifiedTime = *pxTime; /*/< Date and Time Modified. */ } if( uxWhat & ETimeAccess ) { xOriginalEntry.xAccessedTime = *pxTime; /*/< Date of Last Access. */ } xError = FF_PutEntry( pxIOManager, xOriginalEntry.usCurrentItem - 1, xFindParams.ulDirCluster, &xOriginalEntry, NULL ); if( FF_isERR( xError ) == pdFALSE ) { xError = FF_FlushCache( pxIOManager ); /* Ensure all modified blocks are flushed to disk! */ } } return xError; } /* FF_SetTime() */ #endif /* ffconfigTIME_SUPPORT */ /*-----------------------------------------------------------*/ #if ( ffconfigUNICODE_UTF16_SUPPORT != 0 ) FF_Error_t FF_SetPerm( FF_IOManager_t * pxIOManager, const FF_T_WCHAR * pcPath, UBaseType_t aPerm ) #else FF_Error_t FF_SetPerm( FF_IOManager_t * pxIOManager, const char * pcPath, UBaseType_t aPerm ) #endif { FF_DirEnt_t xOriginalEntry; FF_Error_t xError; uint32_t ulFileCluster; BaseType_t xIndex; #if ( ffconfigUNICODE_UTF16_SUPPORT != 0 ) FF_T_WCHAR pcFileName[ ffconfigMAX_FILENAME ]; #else char pcFileName[ ffconfigMAX_FILENAME ]; #endif FF_FindParams_t xFindParams; xIndex = ( BaseType_t ) STRLEN( pcPath ); memset( &xFindParams, '\0', sizeof( xFindParams ) ); while( xIndex != 0 ) { if( ( pcPath[ xIndex ] == '\\' ) || ( pcPath[ xIndex ] == '/' ) ) { break; } xIndex--; } STRNCPY( pcFileName, ( pcPath + xIndex + 1 ), ffconfigMAX_FILENAME ); if( xIndex == 0 ) { xIndex = 1; } /* Open a do {} while( pdFALSE ) loop to allow the use of break statements. */ do { xFindParams.ulDirCluster = FF_FindDir( pxIOManager, pcPath, ( uint16_t ) xIndex, &xError ); if( xError ) { break; } if( !xFindParams.ulDirCluster ) { xError = ( FF_Error_t ) ( FF_ERR_FILE_NOT_FOUND | FF_SETTIME ); break; } ulFileCluster = FF_FindEntryInDir( pxIOManager, &xFindParams, pcFileName, 0, &xOriginalEntry, &xError ); if( FF_isERR( xError ) ) { break; } if( ulFileCluster == 0ul ) { /*FF_PRINTF ("FF_SetTime: Can not find '%s'\n", pcFileName); */ xError = ( FF_Error_t ) ( FF_ERR_FILE_NOT_FOUND | FF_SETTIME ); break; } /* #define FF_FAT_ATTR_READONLY 0x01 */ /* #define FF_FAT_ATTR_HIDDEN 0x02 */ /* #define FF_FAT_ATTR_SYSTEM 0x04 */ /* #define FF_FAT_ATTR_VOLID 0x08 */ /* #define FF_FAT_ATTR_DIR 0x10 */ /* #define FF_FAT_ATTR_ARCHIVE 0x20 */ /* #define FF_FAT_ATTR_LFN 0x0F */ #define FF_FAT_ATTR_USER ( ( uint8_t ) FF_FAT_ATTR_READONLY | FF_FAT_ATTR_HIDDEN | FF_FAT_ATTR_SYSTEM | FF_FAT_ATTR_ARCHIVE ) /* Update the Dirent! */ xOriginalEntry.ucAttrib &= ~FF_FAT_ATTR_USER; xOriginalEntry.ucAttrib |= ( aPerm & FF_FAT_ATTR_USER ); xError = FF_PutEntry( pxIOManager, xOriginalEntry.usCurrentItem - 1, xFindParams.ulDirCluster, &xOriginalEntry, NULL ); if( FF_isERR( xError ) == pdFALSE ) { xError = FF_FlushCache( pxIOManager ); /* Ensure all modfied blocks are flushed to disk! */ } } while( pdFALSE ); return xError; } /* FF_SetPerm() */ /*-----------------------------------------------------------*/ /** * @public * @brief Equivalent to fclose() * * @param pxFile FF_FILE object that was created by FF_Open(). * * @return 0 on sucess. * @return -1 if a null pointer was provided. * **/ FF_Error_t FF_Close( FF_FILE * pxFile ) { FF_FILE * pxFileChain; FF_DirEnt_t xOriginalEntry; FF_Error_t xError; /* Opening a do {} while( 0 ) loop to allow the use of the break statement. */ do { if( pxFile == NULL ) { xError = ( FF_Error_t ) ( FF_ERR_NULL_POINTER | FF_CLOSE ); break; } /* It is important to check that user doesn't supply invalid * handle or a handle invalid because of "media removed" */ xError = FF_CheckValid( pxFile ); #if ( ffconfigREMOVABLE_MEDIA != 0 ) { if( FF_GETERROR( xError ) == FF_ERR_FILE_MEDIA_REMOVED ) { FF_PendSemaphore( pxFile->pxIOManager->pvSemaphore ); { pxFileChain = ( FF_FILE * ) pxFile->pxIOManager->FirstFile; if( pxFileChain == pxFile ) { pxFile->pxIOManager->FirstFile = pxFile->pxNext; } else { while( pxFileChain ) { if( pxFileChain->pxNext == pxFile ) { pxFileChain->pxNext = pxFile->pxNext; break; } pxFileChain = pxFileChain->pxNext; /* Forgot this one */ } } } /* Semaphore released, linked list was shortened! */ FF_ReleaseSemaphore( pxFile->pxIOManager->pvSemaphore ); #if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 ) { ffconfigFREE( pxFile->pucBuffer ); } #endif /* ffconfigOPTIMISE_UNALIGNED_ACCESS */ ffconfigFREE( pxFile ); /* So at least we have freed the pointer. */ xError = FF_ERR_NONE; break; } } #endif /* ffconfigREMOVABLE_MEDIA */ if( FF_isERR( xError ) ) { /* FF_ERR_FILE_BAD_HANDLE or FF_ERR_NULL_POINTER */ break; } /* So here we have a normal valid file handle. */ /* Sometimes FreeRTOS+FAT will leave a trailing cluster on the end of a cluster chain. * To ensure we're compliant we shall now check for this condition and truncate it. */ if( ( ( pxFile->ulValidFlags & FF_VALID_FLAG_DELETED ) == 0 ) && ( ( pxFile->ucMode & ( FF_MODE_WRITE | FF_MODE_APPEND | FF_MODE_CREATE ) ) != 0 ) ) { uint32_t ulClusterSize; /* File is not deleted and it was opened for writing or updating */ ulClusterSize = pxFile->pxIOManager->xPartition.usBlkSize * pxFile->pxIOManager->xPartition.ulSectorsPerCluster; if( ( ( pxFile->ulFileSize % ulClusterSize ) == 0 ) && ( pxFile->ulObjectCluster != 0ul ) ) { /* The file's length is a multiple of cluster size. This means * that an extra cluster has been reserved, which wasn't necessary. */ xError = FF_Truncate( pxFile, pdTRUE ); } /* Get the directory entry and update it to show the new file size */ if( FF_isERR( xError ) == pdFALSE ) { 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( pxFile->ulFileSize == 0UL ) { xOriginalEntry.ulObjectCluster = 0; } xOriginalEntry.ulFileSize = pxFile->ulFileSize; xError = FF_PutEntry( pxFile->pxIOManager, pxFile->usDirEntry, pxFile->ulDirCluster, &xOriginalEntry, NULL ); } } } /* Handle Linked list! */ FF_PendSemaphore( pxFile->pxIOManager->pvSemaphore ); { /* Semaphore is required, or linked list could become corrupted. */ pxFileChain = ( FF_FILE * ) pxFile->pxIOManager->FirstFile; if( pxFileChain == pxFile ) { pxFile->pxIOManager->FirstFile = pxFile->pxNext; } else { while( pxFileChain ) { if( pxFileChain->pxNext == pxFile ) { /* Found it, remove it from the list. */ pxFileChain->pxNext = pxFile->pxNext; break; } pxFileChain = pxFileChain->pxNext; } } } /* Semaphore released, linked list was shortened! */ FF_ReleaseSemaphore( pxFile->pxIOManager->pvSemaphore ); #if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 ) { if( pxFile->pucBuffer != NULL ) { /* Ensure any unaligned points are pushed to the disk! */ if( pxFile->ucState & FF_BUFSTATE_WRITTEN ) { FF_Error_t xTempError; xTempError = FF_BlockWrite( pxFile->pxIOManager, FF_FileLBA( pxFile ), 1, pxFile->pucBuffer, pdFALSE ); if( FF_isERR( xError ) == pdFALSE ) { xError = xTempError; } } ffconfigFREE( pxFile->pucBuffer ); } } #endif /* if ( ffconfigOPTIMISE_UNALIGNED_ACCESS != 0 ) */ if( FF_isERR( xError ) == pdFALSE ) { xError = FF_FlushCache( pxFile->pxIOManager ); /* Ensure all modified blocks are flushed to disk! */ } ffconfigFREE( pxFile ); } while( pdFALSE ); return xError; } /* FF_Close() */ /*-----------------------------------------------------------*/ /** * @public * @brief Make Filesize equal to the FilePointer and truncates the file to this position * * @param pxFile FF_FILE object that was created by FF_Open(). * * @return 0 on sucess. * @return negative if some error occurred * **/ FF_Error_t FF_SetEof( FF_FILE * pxFile ) { FF_Error_t xError; /* Check if the file was not deleted and if it was opened with write permissions: */ if( ( ( pxFile->ulValidFlags & FF_VALID_FLAG_DELETED ) == 0 ) && ( ( pxFile->ucMode & ( FF_MODE_WRITE | FF_MODE_APPEND | FF_MODE_CREATE ) ) != 0 ) ) { pxFile->ulFileSize = pxFile->ulFilePointer; if( pxFile->ulObjectCluster != 0ul ) { xError = FF_Truncate( pxFile, pdFALSE ); } else { xError = FF_ERR_NONE; } } else { xError = ( FF_Error_t ) ( FF_ERR_FILE_NOT_OPENED_IN_WRITE_MODE | FF_SETEOF ); } return xError; } /* FF_SetEof() */ /*-----------------------------------------------------------*/ /** * @public * @brief Truncate a file to 'pxFile->ulFileSize' * * @param pxFile FF_FILE object that was created by FF_Open(). * * @return 0 on sucess. * @return negative if some error occurred * **/ static FF_Error_t FF_Truncate( FF_FILE * pxFile, BaseType_t bClosing ) { FF_Error_t xError; FF_IOManager_t * pxIOManager = pxFile->pxIOManager; uint32_t ulClusterSize; uint32_t ulClusterCount; uint32_t ulClustersNeeded; /* The number of bytes contained in a cluster. */ ulClusterSize = pxIOManager->xPartition.usBlkSize * pxIOManager->xPartition.ulSectorsPerCluster; /* See how many clusters have been allocated. */ ulClusterCount = FF_GetChainLength( pxIOManager, pxFile->ulObjectCluster, NULL, &xError ); /* Calculate the actual number of clusters needed, rounding up */ ulClustersNeeded = ( pxFile->ulFileSize + ulClusterSize - 1 ) / ulClusterSize; if( bClosing != pdFALSE ) { /* The handle will be closed after truncating. This function is called * because Filesize is an exact multiple of ulClusterSize. */ ulClustersNeeded = pxFile->ulFileSize / ulClusterSize; } else { /* This function is called to make the file size equal to the current * position within the file. Always keep an extra cluster to write to. */ ulClustersNeeded = ( pxFile->ulFileSize + ulClusterSize ) / ulClusterSize; } /* First change the FAT chain. */ if( ( FF_isERR( xError ) == pdFALSE ) && ( ulClusterCount > ulClustersNeeded ) ) { if( ulClustersNeeded == 0ul ) { FF_LockFAT( pxIOManager ); { /* In FF_Truncate() */ xError = FF_UnlinkClusterChain( pxIOManager, pxFile->ulObjectCluster, 0 ); } FF_UnlockFAT( pxIOManager ); if( FF_isERR( xError ) == pdFALSE ) { FF_DirEnt_t xOriginalEntry; /* The directory denotes the address of the first data cluster of every file. * Now change it to 'ulAddrCurrentCluster': */ xError = FF_GetEntry( pxIOManager, pxFile->usDirEntry, pxFile->ulDirCluster, &xOriginalEntry ); if( FF_isERR( xError ) == pdFALSE ) { xOriginalEntry.ulObjectCluster = 0ul; xError = FF_PutEntry( pxIOManager, pxFile->usDirEntry, pxFile->ulDirCluster, &xOriginalEntry, NULL ); if( FF_isERR( xError ) == pdFALSE ) { pxFile->ulObjectCluster = 0ul; pxFile->ulChainLength = 0ul; pxFile->ulCurrentCluster = 0ul; pxFile->ulEndOfChain = 0ul; } } } } else { FF_LockFAT( pxIOManager ); { uint32_t ulTruncateCluster = FF_TraverseFAT( pxIOManager, pxFile->ulObjectCluster, ulClustersNeeded - 1, &xError ); if( FF_isERR( xError ) == pdFALSE ) { xError = FF_UnlinkClusterChain( pxIOManager, ulTruncateCluster, 1 ); } } FF_UnlockFAT( pxIOManager ); } } return xError; } /* FF_Truncate() */ /*-----------------------------------------------------------*/