/* * 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 * */ /* FreeRTOS includes. */ #include "FreeRTOS.h" #include "task.h" #include "portable.h" /* FreeRTOS+FAT includes. */ #include "ff_headers.h" #include "ff_stdio.h" #if ( ffconfigTIME_SUPPORT != 0 ) #include #endif #ifndef SIZE_MAX #define SIZE_MAX ( ( size_t ) -1 ) #endif /* The number of bytes to write at a time when extending the length of a file * in a call to ff_truncate(). */ #define stdioTRUNCATE_WRITE_LENGTH 512 /* Bits set to indicate whether ".." should be included as well as ".". */ #define stdioDIR_ENTRY_DOT_1 ( 1U & 0x03U ) #define stdioDIR_ENTRY_DOT_2 ( 2U & 0x03U ) /* The directory entries '.' and '..' will show a file size of 1 KB. */ #define stdioDOT_ENTRY_FILE_SIZE 1024 /*-----------------------------------------------------------*/ /* * Add the CWD to the beginning of a relative path, and copy the resultant * absolute path into a thread local non const buffer. */ /*static*/ const char * prvABSPath( const char * pcPath ); /* * Translate a +FAT error to a value compatible with errno.h * If the value represents an error, it is negative * The return value of this function will always be positive */ int prvFFErrorToErrno( FF_Error_t xError ); /* * Generate a time stamp for the file. */ #if ( ffconfigTIME_SUPPORT == 1 ) static uint32_t prvFileTime( FF_SystemTime_t * pxTime ); #endif #if ( ffconfigHAS_CWD == 1 ) /* FreeRTOS+FAT requires two thread local storage pointers. One for errno * and one for the CWD structure. */ #if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS < 2 ) #error FreeRTOS+FAT requires two thread local storage pointers so configNUM_THREAD_LOCAL_STORAGE_POINTERS must be at least 2 in FreeRTOSConfig.h #endif /* Each task has its own Current Working Directory (CWD). The CWD is used * to extend relative paths to absolute paths. */ typedef struct WORKING_DIR { char pcCWD[ ffconfigMAX_FILENAME ]; /* The current working directory. */ char pcFileName[ ffconfigMAX_FILENAME ]; /* The created absolute path. */ } WorkingDirectory_t; /* * Lookup the CWD of the current task. */ static WorkingDirectory_t * pxFindCWD( void ); /* * Convert a string which may contain a relative path into a string that * will only contain an absolute path. */ static const char * prvProcessRelativePaths( const char * pcPath ); #else /* ffconfigHAS_CWD */ /* FreeRTOS+FAT requires one thread local storage pointers for errno. */ #if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS < 2 ) #error FreeRTOS+FAT requires one thread local storage pointers so configNUM_THREAD_LOCAL_STORAGE_POINTERS must be at least 1 in FreeRTOSConfig.h #endif /* Only absolute paths are supported so define away the prvABSPath() * function. */ /*static*/ const char * prvABSPath( const char * pcPath ) { return pcPath; } #endif /* ffconfigHAS_CWD */ #if ( ffconfigUSE_DELTREE != 0 ) /* * Remove all files and directories starting from a certain path. * This function uses recursion - which breaches the coding standard. USE * WITH CARE. */ static int ff_deltree_recurse( char * pcPath ); #endif /*-----------------------------------------------------------*/ FF_FILE * ff_fopen( const char * pcFile, const char * pcMode ) { FF_FILE * pxStream = NULL; FF_DirHandler_t xHandler; FF_Error_t xError; uint8_t ucMode; /* Insert the current working directory in front of relative paths. */ pcFile = prvABSPath( pcFile ); /* Look-up the I/O manager for the file system. */ if( FF_FS_Find( pcFile, &xHandler ) == pdFALSE ) { stdioSET_ERRNO( pdFREERTOS_ERRNO_ENXIO ); /* No such device or address. */ } else { /* Now 'xHandler.pcPath' contains an absolute path within the file system. * Translate a type string "r|w|a[+]" to +FAT's mode bits. */ ucMode = FF_GetModeBits( pcMode ); pxStream = FF_Open( xHandler.pxManager, xHandler.pcPath, ucMode, &xError ); stdioSET_ERRNO( prvFFErrorToErrno( xError ) ); #if ( ffconfigUSE_NOTIFY != 0 ) { if( ( pxStream != NULL ) && ( ( ucMode & ( FF_MODE_WRITE | FF_MODE_APPEND ) ) != 0 ) ) { /*_RB_ Function name needs updating. */ callFileEvents( pcFile, eFileCreate ); } } #endif /* ffconfigUSE_NOTIFY */ #if ( ffconfigDEV_SUPPORT != 0 ) { if( pxStream != NULL ) { FF_Device_Open( pcFile, pxStream ); } } #endif /* ffconfigDEV_SUPPORT */ } return pxStream; } /*-----------------------------------------------------------*/ int ff_fclose( FF_FILE * pxStream ) { FF_Error_t xError; int iReturn, ff_errno; #if ( ffconfigDEV_SUPPORT != 0 ) { /* Currently device support is in an experimental state. It will allow * to create virtual files. The I/O data to those files will be redirected * to their connected "drivers". */ if( pxStream != NULL ) { FF_Device_Close( pxStream ); } } #endif xError = FF_Close( pxStream ); ff_errno = prvFFErrorToErrno( xError ); if( ff_errno == 0 ) { iReturn = 0; } else { /* Return -1 for error as per normal fclose() semantics. */ iReturn = -1; } /* Store the errno to thread local storage. */ stdioSET_ERRNO( ff_errno ); return iReturn; } /*-----------------------------------------------------------*/ int ff_fseek( FF_FILE * pxStream, long lOffset, int iWhence ) { FF_Error_t xError; int iReturn, ff_errno; #if ( ffconfigDEV_SUPPORT != 0 ) if( pxStream->pxDevNode != NULL ) { xError = FF_Device_Seek( pxStream, lOffset, iWhence ); } else #endif { xError = FF_Seek( pxStream, lOffset, iWhence ); } ff_errno = prvFFErrorToErrno( xError ); if( ff_errno == 0 ) { iReturn = 0; } else { if( xError == FF_ERR_FILE_SEEK_INVALID_POSITION ) { /* Illegal position, outside the file's space */ ff_errno = pdFREERTOS_ERRNO_ESPIPE; } else if( xError == FF_ERR_FILE_SEEK_INVALID_ORIGIN ) { /* Illegal parameter value for iWhence: SET,CUR,END. */ ff_errno = pdFREERTOS_ERRNO_EINVAL; } /* Return -1 for error as per normal fseek() semantics. */ iReturn = -1; } /* Store the errno to thread local storage. */ stdioSET_ERRNO( ff_errno ); return iReturn; } /*-----------------------------------------------------------*/ void ff_rewind( FF_FILE * pxStream ) { ff_fseek( pxStream, 0, FF_SEEK_SET ); /* Rewind is supposed to reset errno unconditionally. Store the errno to * thread local storage. */ stdioSET_ERRNO( 0 ); } /*-----------------------------------------------------------*/ long ff_ftell( FF_FILE * pxStream ) { long lResult; if( pxStream == NULL ) { /* Store the errno to thread local storage. */ stdioSET_ERRNO( pdFREERTOS_ERRNO_EBADF ); /* Return -1 for error as per normal ftell() semantics. */ lResult = -1; } else { lResult = ( long ) pxStream->ulFilePointer; } return lResult; } /*-----------------------------------------------------------*/ int ff_feof( FF_FILE * pxStream ) { int iResult; FF_Error_t xError; xError = FF_CheckValid( pxStream ); if( FF_isERR( xError ) == pdFALSE ) { /* Store the errno to thread local storage. */ stdioSET_ERRNO( 0 ); if( pxStream->ulFilePointer >= pxStream->ulFileSize ) { iResult = pdTRUE; } else { iResult = pdFALSE; } } else { /* Store the errno to thread local storage. */ stdioSET_ERRNO( prvFFErrorToErrno( xError ) ); /* The file was invalid so a non-zero value cannot be returned. */ iResult = pdFALSE; } return iResult; } /*-----------------------------------------------------------*/ size_t ff_fread( void * pvBuffer, size_t xSize, size_t xItems, FF_FILE * pxStream ) { int32_t iReturned; size_t xReturn; int ff_errno; #if ( ffconfigDEV_SUPPORT != 0 ) if( pxStream->pxDevNode != NULL ) { iReturned = FF_Device_Read( pvBuffer, xSize, xItems, pxStream ); } else #endif { iReturned = FF_Read( pxStream, xSize, xItems, ( uint8_t * ) pvBuffer ); } ff_errno = prvFFErrorToErrno( iReturned ); if( ff_errno == pdFREERTOS_ERRNO_NONE ) { /* As per the standard fread() semantics, the return value is the number * of complete items read, which will only equal the number of bytes * transferred when the item size is 1. */ xReturn = ( size_t ) iReturned; } else { xReturn = 0; } /* Store the errno to thread local storage. */ stdioSET_ERRNO( ff_errno ); return xReturn; } /*-----------------------------------------------------------*/ size_t ff_fwrite( const void * pvBuffer, size_t xSize, size_t xItems, FF_FILE * pxStream ) { int32_t iReturned; size_t xReturn; int ff_errno; #if ( ffconfigDEV_SUPPORT != 0 ) if( pxStream->pxDevNode != NULL ) { iReturned = FF_Device_Write( pvBuffer, xSize, xItems, pxStream ); } else #endif { iReturned = FF_Write( pxStream, xSize, xItems, ( uint8_t * ) pvBuffer ); } ff_errno = prvFFErrorToErrno( iReturned ); if( ff_errno == pdFREERTOS_ERRNO_NONE ) { /* As per the standard fwrite() semantics, the return value is the * number of complete items read, which will only equal the number of bytes * transferred when the item size is 1. */ xReturn = ( size_t ) iReturned; } else { xReturn = 0; } /* Store the errno to thread local storage. */ stdioSET_ERRNO( ff_errno ); return xReturn; } /*-----------------------------------------------------------*/ int ff_fgetc( FF_FILE * pxStream ) { int32_t iResult; int ff_errno; iResult = FF_GetC( pxStream ); ff_errno = prvFFErrorToErrno( iResult ); if( ff_errno != 0 ) { iResult = FF_EOF; } /* Store the errno to thread local storage. */ stdioSET_ERRNO( ff_errno ); return iResult; } /*-----------------------------------------------------------*/ int ff_fputc( int iChar, FF_FILE * pxStream ) { int iResult, ff_errno; iResult = FF_PutC( pxStream, ( uint8_t ) iChar ); ff_errno = prvFFErrorToErrno( iResult ); if( ff_errno != 0 ) { iResult = FF_EOF; } /* Store the errno to thread local storage. */ stdioSET_ERRNO( ff_errno ); return iResult; } /*-----------------------------------------------------------*/ #if ( ffconfigFPRINTF_SUPPORT == 1 ) int ff_fprintf( FF_FILE * pxStream, const char * pcFormat, ... ) { int iCount; size_t xResult; char * pcBuffer; va_list xArgs; pcBuffer = ( char * ) ffconfigMALLOC( ffconfigFPRINTF_BUFFER_LENGTH ); if( pcBuffer == NULL ) { /* Store the errno to thread local storage. */ stdioSET_ERRNO( pdFREERTOS_ERRNO_ENOMEM ); iCount = -1; } else { va_start( xArgs, pcFormat ); iCount = vsnprintf( pcBuffer, ffconfigFPRINTF_BUFFER_LENGTH, pcFormat, xArgs ); va_end( xArgs ); /* ff_fwrite() will set ff_errno. */ if( iCount > 0 ) { xResult = ff_fwrite( pcBuffer, ( size_t ) 1, ( size_t ) iCount, pxStream ); if( xResult < ( size_t ) iCount ) { iCount = -1; } } ffconfigFREE( pcBuffer ); } return iCount; } #endif /* if ( ffconfigFPRINTF_SUPPORT == 1 ) */ /*-----------------------------------------------------------*/ /*_RB_ to comply with the norm, the second parameter should be an int, but size_t * is more appropriate. */ char * ff_fgets( char * pcBuffer, size_t xCount, FF_FILE * pxStream ) { int32_t xResult; int ff_errno; xResult = FF_GetLine( pxStream, ( char * ) pcBuffer, xCount ); /* This call seems to result in errno being incorrectly set to * FF_ERR_IOMAN_NO_MOUNTABLE_PARTITION when an EOF is encountered. */ ff_errno = prvFFErrorToErrno( xResult ); if( ff_errno != 0 ) { pcBuffer = NULL; } /* Store the errno to thread local storage. */ stdioSET_ERRNO( ff_errno ); return pcBuffer; } /*-----------------------------------------------------------*/ int ff_seteof( FF_FILE * pxStream ) { FF_Error_t iResult; int iReturn, ff_errno; iResult = FF_SetEof( pxStream ); ff_errno = prvFFErrorToErrno( iResult ); if( ff_errno == 0 ) { iReturn = 0; } else { iReturn = FF_EOF; } /* Store the errno to thread local storage. */ stdioSET_ERRNO( ff_errno ); return iReturn; } /*-----------------------------------------------------------*/ /*_RB_ The norm would be to return an int, but in either case it is not clear * what state the file is left in (open/closed). */ FF_FILE * ff_truncate( const char * pcFileName, long lTruncateSize ) { FF_Error_t xResult = 0; FF_FILE * pxStream; size_t xReturned; uint32_t ulLength, ulBytesLeftToAdd, ulBytesToWrite; char * pcBufferToWrite; pxStream = ff_fopen( pcFileName, "a+" ); if( pxStream != NULL ) { ulLength = pxStream->ulFileSize; } else { ulLength = 0; } if( pxStream == NULL ) { /* Store the errno to thread local storage. */ stdioSET_ERRNO( prvFFErrorToErrno( xResult ) ); } else if( ulLength > ( uint32_t ) lTruncateSize ) { /* Seek the desired position */ xResult = FF_Seek( pxStream, lTruncateSize, FF_SEEK_SET ); /* Make the current position equal to its length */ if( FF_isERR( xResult ) == pdFALSE ) { xResult = FF_SetEof( pxStream ); } if( FF_isERR( xResult ) != pdFALSE ) { ff_fclose( pxStream ); pxStream = NULL; } /* Store the errno to thread local storage. */ stdioSET_ERRNO( prvFFErrorToErrno( xResult ) ); } else if( ulLength == ( uint32_t ) lTruncateSize ) { /* Nothing to do, the file has the desired size * and the open handle will be returned. */ } else { /* lTruncateSize > ulLength. The user wants to open this file with a * larger size than it currently has. Fill it with zeros. */ pcBufferToWrite = ( char * ) ffconfigMALLOC( stdioTRUNCATE_WRITE_LENGTH ); if( pcBufferToWrite == NULL ) { ff_fclose( pxStream ); pxStream = NULL; /* Store the errno to thread local storage. */ stdioSET_ERRNO( pdFREERTOS_ERRNO_ENOMEM ); } else { /* File has to grow */ ulBytesLeftToAdd = ( ( uint32_t ) lTruncateSize ) - ulLength; /* Zeros must be written. */ memset( pcBufferToWrite, '\0', stdioTRUNCATE_WRITE_LENGTH ); while( ulBytesLeftToAdd > 0UL ) { if( ( pxStream->ulFileSize % stdioTRUNCATE_WRITE_LENGTH ) != 0 ) { /* Although +FAT's FF_Write() can handle any size at any * offset, the driver puts data more efficiently if blocks are * written at block boundaries. */ ulBytesToWrite = stdioTRUNCATE_WRITE_LENGTH - ( pxStream->ulFileSize % stdioTRUNCATE_WRITE_LENGTH ); if( ulBytesToWrite > ulBytesLeftToAdd ) { ulBytesToWrite = ulBytesLeftToAdd; } } else { ulBytesToWrite = ulBytesLeftToAdd; if( ulBytesToWrite > stdioTRUNCATE_WRITE_LENGTH ) { ulBytesToWrite = stdioTRUNCATE_WRITE_LENGTH; } } xReturned = ff_fwrite( pcBufferToWrite, sizeof( char ), ulBytesToWrite, pxStream ); if( xReturned != ( size_t ) ulBytesToWrite ) { /* Write error. Close the stream and set the proper . * errno. */ ff_fclose( pxStream ); pxStream = NULL; /* Not setting ff_errno because it has been set by other * functions from this ff_stdio. */ break; } ulBytesLeftToAdd -= ulBytesToWrite; } ffconfigFREE( pcBufferToWrite ); } } return pxStream; } /*-----------------------------------------------------------*/ #if ( ffconfigMKDIR_RECURSIVE == 0 ) /* The normal mkdir() : if assumes that the directories leading to the last * element of pcDirectory already exists. For instance: mkdir( "/a/b/c" ) will * succeed if the path "/a/b" already exists. */ int ff_mkdir( const char * pcDirectory ) { int iResult, ff_errno; FF_DirHandler_t xHandler; /* In case a CWD is used, get the absolute path. */ pcDirectory = prvABSPath( pcDirectory ); /* Find the i/o manager for this path */ if( FF_FS_Find( pcDirectory, &xHandler ) == pdFALSE ) { /* No such device or address. */ stdioSET_ERRNO( pdFREERTOS_ERRNO_ENXIO ); /* Return -1 for error as per normal mkdir() semantics. */ iResult = -1; } else { /* A simple non-recursive make of a directory. */ iResult = FF_MkDir( xHandler.pxManager, xHandler.pcPath ); if( FF_GETERROR( iResult ) == FF_ERR_DIR_OBJECT_EXISTS ) { /* No error if the target directory already exists. */ iResult = FF_ERR_NONE; } ff_errno = prvFFErrorToErrno( iResult ); /* Store the errno to thread local storage. */ stdioSET_ERRNO( ff_errno ); if( ff_errno == pdFREERTOS_ERRNO_NONE ) { iResult = 0; } else { /* Return -1 for error as per normal mkdir() semantics. */ iResult = -1; } } return iResult; } #else /* ffconfigMKDIR_RECURSIVE */ #warning This path is not yet included in the regression tests. /* The 'recursive mkdir() : if the parameter 'xRecursive' is non-zero, * the function will try to create the complete path. */ int ff_mkdir( const char * pcDirectory, int xRecursive ) { int32_t lResult; FF_DirHandler_t xHandler; /* In case a CWD is used, get the absolute path. */ pcDirectory = prvABSPath( pcDirectory ); /* Find the i/o manager for this path */ if( FF_FS_Find( pcDirectory, &xHandler ) == pdFALSE ) { /* No such device or address. Store the errno to thread local * storage. */ stdioSET_ERRNO( pdFREERTOS_ERRNO_ENXIO ); /* Return -1 for error as per normal mkdir() semantics. */ lResult = -1; } else { if( xRecursive == pdFALSE ) { /* A simple non-recursive make of a directory. */ lResult = FF_MkDir( xHandler.pxManager, xHandler.pcPath ); if( FF_GETERROR( lResult ) == FF_ERR_DIR_OBJECT_EXISTS ) { /* No error if the target directory already exists. */ lResult = 0; } } else { /* The recursive option is used. */ char pcTempPath[ ffconfigMAX_FILENAME ]; FF_Error_t errCode; int iLength = snprintf( pcTempPath, sizeof( pcTempPath ), "%s", xHandler.pcPath ); char * pcPtr = pcTempPath + 1, * pcPrev; const char * pcLast = pcTempPath + iLength; lResult = FF_ERR_NONE; for( ; ; ) { for( pcPrev = pcPtr; pcPtr < pcLast; pcPtr++ ) { if( *pcPtr == '/' ) { *pcPtr = '\0'; break; } } if( pcPrev == pcPtr ) { break; } errCode = FF_MkDir( xHandler.pxManager, pcTempPath ); if( FF_isERR( errCode ) && ( FF_GETERROR( errCode ) != FF_ERR_DIR_OBJECT_EXISTS ) ) { lResult = errCode; break; } if( pcPtr >= ( pcLast - 1 ) ) { break; } *( pcPtr++ ) = '/'; } } /* Store the errno to thread local storage. */ stdioSET_ERRNO( prvFFErrorToErrno( lResult ) ); } return lResult; } #endif /* ffconfigMKDIR_RECURSIVE */ /*-----------------------------------------------------------*/ int ff_rmdir( const char * pcDirectory ) { int32_t lResult; int iReturn, ff_errno; FF_DirHandler_t xHandler; /* In case a CWD is used, get the absolute path */ pcDirectory = prvABSPath( pcDirectory ); /* Find the i/o manager which can handle this path. */ if( FF_FS_Find( pcDirectory, &xHandler ) == pdFALSE ) { ff_errno = pdFREERTOS_ERRNO_ENXIO; /* No such device or address */ /* Return -1 for error as per normal rmdir() semantics. */ iReturn = -1; } else { lResult = FF_RmDir( xHandler.pxManager, xHandler.pcPath ); ff_errno = prvFFErrorToErrno( lResult ); if( ff_errno == 0 ) { iReturn = 0; } else { /* Return -1 for error as per normal rmdir() semantics. */ iReturn = -1; } } /* Store the errno to thread local storage. */ stdioSET_ERRNO( ff_errno ); return iReturn; } /*-----------------------------------------------------------*/ int ff_remove( const char * pcPath ) { FF_DirHandler_t xHandler; FF_Error_t xError; int iReturn, ff_errno; /* In case a CWD is used, get the absolute path */ pcPath = prvABSPath( pcPath ); /* Find the i/o manager which can handle this path. */ if( FF_FS_Find( pcPath, &xHandler ) == pdFALSE ) { /* No such device or address */ ff_errno = pdFREERTOS_ERRNO_ENXIO; /* Return -1 for error as per normal remove() semantics. */ iReturn = -1; } else { xError = FF_RmFile( xHandler.pxManager, xHandler.pcPath ); ff_errno = prvFFErrorToErrno( xError ); #if ffconfigUSE_NOTIFY { if( FF_isERR( xError ) == pdFALSE ) { callFileEvents( pcPath, eFileRemove ); } } #endif if( ff_errno == 0 ) { iReturn = 0; } else { /* Return -1 for error as per normal remove() semantics. */ iReturn = -1; } } /* Store the errno to thread local storage. */ stdioSET_ERRNO( ff_errno ); return iReturn; } /*-----------------------------------------------------------*/ /*_RB_ Last parameter not documented. */ int ff_rename( const char * pcOldName, const char * pcNewName, int bDeleteIfExists ) { FF_DirHandler_t xHandlers[ 2 ]; FF_Error_t xError = FF_ERR_NONE; int ff_errno = 0, iReturn; #if ( ffconfigHAS_CWD != 0 ) char * pcOldCopy; size_t xSize; #endif /* In case a CWD is used, get the absolute path */ pcOldName = prvABSPath( pcOldName ); /* Find the i/o manager which can handle this path */ if( FF_FS_Find( pcOldName, &xHandlers[ 0 ] ) == pdFALSE ) { xError = ( int32_t ) ( FF_ERR_NULL_POINTER | FF_MOVE ); ff_errno = pdFREERTOS_ERRNO_ENXIO; /* No such device or address */ } else { #if ( ffconfigHAS_CWD != 0 ) { xSize = strlen( xHandlers[ 0 ].pcPath ) + 1; pcOldCopy = ( char * ) ffconfigMALLOC( xSize ); if( pcOldCopy == NULL ) { /* Could not allocate space to store a file name. */ ff_errno = pdFREERTOS_ERRNO_ENOMEM; xError = ( int32_t ) ( FF_ERR_NOT_ENOUGH_MEMORY | FF_MOVE ); } else { /* The function prvABSPath() returns a pointer to the task * storage space. Rename needs to call it twice and therefore the * path must be stored before it gets overwritten. */ memcpy( pcOldCopy, xHandlers[ 0 ].pcPath, xSize ); xHandlers[ 0 ].pcPath = pcOldCopy; } } #endif /* ffconfigHAS_CWD != 0 */ #if ( ffconfigHAS_CWD != 0 ) if( pcOldCopy != NULL ) #endif /* ffconfigHAS_CWD != 0 */ { pcNewName = prvABSPath( pcNewName ); /* Find the i/o manager which can handle this path */ if( FF_FS_Find( pcNewName, &( xHandlers[ 1 ] ) ) == pdFALSE ) { xError = ( int32_t ) ( FF_ERR_NULL_POINTER | FF_MOVE ); ff_errno = pdFREERTOS_ERRNO_ENXIO; /* No such device or address */ } else if( xHandlers[ 0 ].pxManager != xHandlers[ 1 ].pxManager ) { xError = ( int32_t ) ( FF_ERR_NULL_POINTER | FF_MOVE ); /* Cross-device link, which can not be done. */ ff_errno = pdFREERTOS_ERRNO_EXDEV; } else { xError = FF_Move( xHandlers[ 0 ].pxManager, xHandlers[ 0 ].pcPath, xHandlers[ 1 ].pcPath, bDeleteIfExists ); ff_errno = prvFFErrorToErrno( xError ); #if ffconfigUSE_NOTIFY { if( FF_isERR( xError ) == pdFALSE ) { callFileEvents( pcNewName, eFileChange ); } } #endif } #if ( ffconfigHAS_CWD != 0 ) { ffconfigFREE( pcOldCopy ); } #endif } } /* Store the errno to thread local storage. */ stdioSET_ERRNO( ff_errno ); if( ff_errno == 0 ) { iReturn = 0; } else { /* Return -1 for error as per normal rmdir() semantics. */ iReturn = -1; } return iReturn; } /*-----------------------------------------------------------*/ int ff_stat( const char * pcName, FF_Stat_t * pxStatBuffer ) { FF_DirEnt_t xDirEntry; uint32_t ulFileCluster; FF_Error_t xError; int iResult; FF_DirHandler_t xHandler; BaseType_t xIndex; FF_FindParams_t xFindParams; #if ( ffconfigUNICODE_UTF16_SUPPORT != 0 ) const FF_T_WCHAR * pcFileName = NULL; #else /* Initialised to prevent MSVC incorrectly claiming the variable is used * without being initialised. */ const char * pcFileName = NULL; #endif memset( &xFindParams, '\0', sizeof( xFindParams ) ); /* Insert the current working directory in front of relative paths. */ pcName = prvABSPath( pcName ); /* Look-up the I/O manager for the file system. */ if( FF_FS_Find( pcName, &xHandler ) == pdFALSE ) { /* No such device or address. */ xError = ( FF_Error_t ) ( pdFREERTOS_ERRNO_ENXIO | FF_STAT_FUNC ); } else { xError = FF_ERR_NONE; pcName = xHandler.pcPath; /* Let xIndex point to the last occurrence of '/' or '\', to separate * the path from the file name. */ xIndex = ( BaseType_t ) STRLEN( pcName ); while( xIndex != 0 ) { if( ( pcName[ xIndex ] == '\\' ) || ( pcName[ xIndex ] == '/' ) ) { break; } xIndex--; } /* Copy the file name, i.e. the string that comes after the last * separator. */ pcFileName = pcName + xIndex + 1; 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( xHandler.pxManager, pcName, xIndex, &xError ); } if( FF_isERR( xError ) == pdFALSE ) { /* See if the file does exist within the given directory. */ ulFileCluster = FF_FindEntryInDir( xHandler.pxManager, &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 ) { xError = FF_ERR_FILE_NOT_FOUND | FF_STAT_FUNC; } } if( ( pxStatBuffer != NULL ) && ( FF_isERR( xError ) == pdFALSE ) ) { if( ( xDirEntry.ucAttrib & FF_FAT_ATTR_DIR ) != 0 ) { pxStatBuffer->st_mode = ( unsigned short ) FF_IFDIR; } else { pxStatBuffer->st_mode = ( unsigned short ) FF_IFREG; } #if ( ffconfigDEV_SUPPORT != 0 ) { BaseType_t bIsDeviceDir = xCheckDevicePath( pcFileName ); if( bIsDeviceDir != pdFALSE ) { FF_Device_GetDirEnt( xHandler.pcPath, &( xDirEntry ) ); } } #endif /* Despite the warning output by MSVC - it is not possible to get here * if xDirEntry has not been initialised. */ pxStatBuffer->st_size = xDirEntry.ulFileSize; pxStatBuffer->st_ino = xDirEntry.ulObjectCluster; pxStatBuffer->st_dev = ( short ) xHandler.xFSIndex; #if ( ffconfigTIME_SUPPORT == 1 ) { pxStatBuffer->st_atime = ( unsigned long ) prvFileTime( &( xDirEntry.xAccessedTime ) ); pxStatBuffer->st_mtime = ( unsigned long ) prvFileTime( &( xDirEntry.xModifiedTime ) ); pxStatBuffer->st_ctime = ( unsigned long ) prvFileTime( &( xDirEntry.xCreateTime ) ); } #endif } stdioSET_ERRNO( prvFFErrorToErrno( xError ) ); if( FF_isERR( xError ) == pdFALSE ) { iResult = 0; } else { iResult = -1; } return iResult; } /* ff_stat() */ /*-----------------------------------------------------------*/ #if ( ffconfigHAS_CWD == 1 ) int ff_chdir( const char * pcDirectoryName ) { int iResult, iLength, iValid = pdFALSE; WorkingDirectory_t * pxDir = NULL; /* Not all paths set an errno. */ stdioSET_ERRNO( 0 ); /* Is there a file system mounted? */ if( FF_FS_Count() != 0 ) { /* In case a CWD is used, get the absolute path. */ pcDirectoryName = prvABSPath( pcDirectoryName ); pxDir = pxFindCWD(); if( pxDir == NULL ) { /* Store the errno to thread local storage. */ stdioSET_ERRNO( pdFREERTOS_ERRNO_ENOMEM ); /* Return -1 for error as per normal chdir() semantics. */ iResult = -1; } else { /* The CWD will be stored without a trailing '/'. If "/" * happens to be the CWD, it will be stored as an empty string. */ iLength = strlen( pcDirectoryName ); /* Knock off the trailing / if one exits - being careful not to * remove the trailing slash if this is the root directory. */ if( ( iLength > 1 ) && ( pxDir->pcFileName[ iLength - 1 ] == '/' ) ) { pxDir->pcFileName[ iLength - 1 ] = '\0'; } stdioSET_ERRNO( pdFREERTOS_ERRNO_ENOENT ); /* Does the directory exist? */ if( strcmp( pcDirectoryName, "/" ) == 0 ) { /* Moving to the root - which exists. */ iValid = pdTRUE; } else if( ff_finddir( pxDir->pcFileName ) != pdFALSE ) { iValid = pdTRUE; } } } if( iValid == pdTRUE ) { /* The generated name becomes the CWD. No need to test for overflow * as pcPath and pcFileName are the same size. */ strcpy( pxDir->pcCWD, pxDir->pcFileName ); /* chdir returns 0 for success. */ iResult = FF_ERR_NONE; } else { /* Return -1 for error as per normal chdir() semantics. */ iResult = -1; } return iResult; } #endif /* ffconfigHAS_CWD == 1 */ /*-----------------------------------------------------------*/ #if ( ffconfigHAS_CWD == 1 ) char * ff_getcwd( char * pcBuffer, size_t xBufferLength ) { WorkingDirectory_t * pxDir = pxFindCWD(); stdioSET_ERRNO( 0 ); if( ( pxDir == NULL ) || ( pxDir->pcCWD[ 0 ] == '\0' ) ) { if( xBufferLength > strlen( "/" ) ) { strncpy( pcBuffer, "/", xBufferLength ); } else { pcBuffer = NULL; } } else { if( strlen( pxDir->pcCWD ) < xBufferLength ) { strncpy( pcBuffer, pxDir->pcCWD, xBufferLength ); } else { pcBuffer = NULL; } } return pcBuffer; } #endif /* ffconfigHAS_CWD */ /*-----------------------------------------------------------*/ int ff_findfirst( const char * pcPath, FF_FindData_t * pxFindData ) { int iIsRootDir, iReturn; const char * pcDirectory; iReturn = 0; memset( pxFindData, '\0', sizeof( *pxFindData ) ); pxFindData->pcFileName = pxFindData->xDirectoryEntry.pcFileName; /* In case a CWD is used, get the absolute path. */ pcDirectory = prvABSPath( pcPath ); if( ( pcDirectory[ 0 ] == '/' ) && ( pcDirectory[ 1 ] == 0x00 ) ) { iIsRootDir = pdTRUE; } else { iIsRootDir = pdFALSE; } /* Find the i/o manager that can handle this path. */ if( FF_FS_Find( pcDirectory, &( pxFindData->xDirectoryHandler ) ) == pdFALSE ) { if( ( iIsRootDir == pdFALSE ) || ( FF_FS_Count() == 0 ) ) { stdioSET_ERRNO( prvFFErrorToErrno( ( FF_Error_t ) ( FF_ERR_NULL_POINTER | FF_FINDFIRST ) ) ); iReturn = -1; } } /* Check no errors before continuing. */ if( iReturn == 0 ) { #if ( ffconfigDEV_SUPPORT != 0 ) { pxFindData->bIsDeviceDir = xCheckDevicePath( pcDirectory ); } #endif if( iIsRootDir != pdFALSE ) { /* A listing of the root directory will include pseudo entries * such as /ram /nand. */ pxFindData->xDirectoryHandler.xFSIndex = FF_FS_Count(); /* Only add '.' */ pxFindData->xDirectoryHandler.u.bits.bAddDotEntries = stdioDIR_ENTRY_DOT_1; } else { /* This is the root of a sub file system, add "." and ".." */ pxFindData->xDirectoryHandler.u.bits.bAddDotEntries = stdioDIR_ENTRY_DOT_1 | stdioDIR_ENTRY_DOT_2; } pxFindData->xDirectoryHandler.u.bits.bIsValid = pdTRUE; iReturn = ff_findnext( pxFindData ); } else { /* errno has already been set. */ } return iReturn; } /*-----------------------------------------------------------*/ int ff_findnext( FF_FindData_t * pxFindData ) { FF_Error_t xError; #if ( ffconfigTIME_SUPPORT != 0 ) BaseType_t xSetTime = 0; #endif /* ffconfigTIME_SUPPORT */ if( pxFindData->xDirectoryHandler.u.bits.bIsValid == pdFALSE ) { xError = ( FF_Error_t ) ( FF_ERR_DIR_INVALID_PARAMETER | FF_FINDNEXT ); FF_PRINTF( "ff_findnext: xDirectoryHandler not valid\n" ); } else { xError = ( FF_Error_t ) ( FF_ERR_DIR_END_OF_DIR | FF_FINDNEXT ); if( pxFindData->xDirectoryHandler.pxManager != NULL ) { if( pxFindData->xDirectoryHandler.u.bits.bFirstCalled == pdFALSE ) { pxFindData->xDirectoryHandler.u.bits.bFirstCalled = pdTRUE; xError = FF_FindFirst( pxFindData->xDirectoryHandler.pxManager, &( pxFindData->xDirectoryEntry ), pxFindData->xDirectoryHandler.pcPath ); } else if( pxFindData->xDirectoryHandler.u.bits.bEndOfDir == pdFALSE ) { xError = FF_FindNext( pxFindData->xDirectoryHandler.pxManager, &( pxFindData->xDirectoryEntry ) ); } if( FF_GETERROR( xError ) == FF_ERR_DIR_END_OF_DIR ) { /* Stop further calls to FF_FindNext(). */ pxFindData->xDirectoryHandler.u.bits.bEndOfDir = pdTRUE; } #if ( ffconfigDEV_SUPPORT != 0 ) { if( pxFindData->bIsDeviceDir != pdFALSE ) { FF_Device_GetDirEnt( pxFindData->xDirectoryHandler.pcPath, &( pxFindData->xDirectoryEntry ) ); } } #endif } if( FF_isERR( xError ) == pdFALSE ) { /* If an entry is found, see if it is a dot-entry. Dot-entries * ("." and "..") need a time-stamp. */ if( pxFindData->xDirectoryEntry.pcFileName[ 0 ] == '.' ) { if( ( pxFindData->xDirectoryEntry.pcFileName[ 1 ] == '.' ) && ( pxFindData->xDirectoryEntry.pcFileName[ 2 ] == '\0' ) ) { /* This is a directory "..". Clear the flag for DOT_2. */ pxFindData->xDirectoryHandler.u.bits.bAddDotEntries &= stdioDIR_ENTRY_DOT_1; #if ( ffconfigTIME_SUPPORT != 0 ) { /* The dot-entries do not have a proper time stamp, add * it here. */ xSetTime = pdTRUE; } #endif /* ffconfigTIME_SUPPORT */ } else if( pxFindData->xDirectoryEntry.pcFileName[ 1 ] == '\0' ) { /* This is a directory ".". Clear the flag for DOT_1. */ pxFindData->xDirectoryHandler.u.bits.bAddDotEntries &= stdioDIR_ENTRY_DOT_2; #if ( ffconfigTIME_SUPPORT != 0 ) { xSetTime = pdTRUE; } #endif /* ffconfigTIME_SUPPORT */ } } } if( FF_GETERROR( xError ) == FF_ERR_DIR_END_OF_DIR ) { /* No more physical entries were found. Now see if there are FS * entries or dot-entries to be added: */ while( ( pxFindData->xDirectoryHandler.xFSIndex > 0 ) || ( pxFindData->xDirectoryHandler.u.bits.bAddDotEntries != 0 ) ) { if( pxFindData->xDirectoryHandler.xFSIndex > 0 ) { FF_SubSystem_t xSubSystem; int found; pxFindData->xDirectoryHandler.xFSIndex--; found = FF_FS_Get( pxFindData->xDirectoryHandler.xFSIndex, &xSubSystem ); if( ( found == pdFALSE ) || ( xSubSystem.pcPath[ 1 ] == '\0' ) ) { continue; } snprintf( pxFindData->xDirectoryEntry.pcFileName, sizeof( pxFindData->xDirectoryEntry.pcFileName ), "%s", xSubSystem.pcPath + 1 ); if( xSubSystem.pxManager != NULL ) { pxFindData->xDirectoryEntry.ulObjectCluster = xSubSystem.pxManager->xPartition.ulRootDirCluster; } else { pxFindData->xDirectoryEntry.ulObjectCluster = 0; } } else if( ( pxFindData->xDirectoryHandler.u.bits.bAddDotEntries & stdioDIR_ENTRY_DOT_2 ) != 0 ) { strcpy( pxFindData->xDirectoryEntry.pcFileName, ".." ); /* Clear DOT_2 (keep DOT_1). */ pxFindData->xDirectoryHandler.u.bits.bAddDotEntries &= stdioDIR_ENTRY_DOT_1; } else { strcpy( pxFindData->xDirectoryEntry.pcFileName, "." ); pxFindData->xDirectoryHandler.u.bits.bAddDotEntries = 0; } pxFindData->xDirectoryEntry.ucAttrib = FF_FAT_ATTR_READONLY | FF_FAT_ATTR_DIR; pxFindData->xDirectoryEntry.ulFileSize = stdioDOT_ENTRY_FILE_SIZE; #if ( ffconfigTIME_SUPPORT != 0 ) { xSetTime = pdTRUE; } #endif /* ffconfigTIME_SUPPORT */ xError = FF_ERR_NONE; break; } } #if ( ffconfigTIME_SUPPORT != 0 ) { if( xSetTime != pdFALSE ) { FF_TimeStruct_t xTimeStruct; time_t xSeconds; xSeconds = FreeRTOS_time( NULL ); FreeRTOS_gmtime_r( &xSeconds, &xTimeStruct ); pxFindData->xDirectoryEntry.xCreateTime.Year = ( uint16_t ) ( xTimeStruct.tm_year + 1900 ); /* Year (e.g. 2009). */ pxFindData->xDirectoryEntry.xCreateTime.Month = ( uint16_t ) ( xTimeStruct.tm_mon + 1 ); /* Month (e.g. 1 = Jan, 12 = Dec). */ pxFindData->xDirectoryEntry.xCreateTime.Day = ( uint16_t ) xTimeStruct.tm_mday; /* Day (1 - 31). */ pxFindData->xDirectoryEntry.xCreateTime.Hour = ( uint16_t ) xTimeStruct.tm_hour; /* Hour (0 - 23). */ pxFindData->xDirectoryEntry.xCreateTime.Minute = ( uint16_t ) xTimeStruct.tm_min; /* Min (0 - 59). */ pxFindData->xDirectoryEntry.xCreateTime.Second = ( uint16_t ) xTimeStruct.tm_sec; /* Second (0 - 59). */ pxFindData->xDirectoryEntry.xModifiedTime = pxFindData->xDirectoryEntry.xCreateTime; /* Date and Time Modified. */ pxFindData->xDirectoryEntry.xAccessedTime = pxFindData->xDirectoryEntry.xCreateTime; /* Date of Last Access. */ } } #endif /* ffconfigTIME_SUPPORT */ if( FF_GETERROR( xError ) == FF_ERR_DIR_END_OF_DIR ) { /* FF_ERR_DIR_END_OF_DIR will be returned. */ pxFindData->xDirectoryHandler.u.bits.bIsValid = 0; } pxFindData->ucAttributes = pxFindData->xDirectoryEntry.ucAttrib; pxFindData->ulFileSize = pxFindData->xDirectoryEntry.ulFileSize; } stdioSET_ERRNO( prvFFErrorToErrno( xError ) ); return xError; } /*-----------------------------------------------------------*/ /*----------------------------------------------------------- * ff_isdirempty() returns 1 if a given directory is empty * (has no entries) *-----------------------------------------------------------*/ int ff_isdirempty( const char * pcPath ) { FF_DirHandler_t xHandler; int iResult; /* In case a CWD is used, get the absolute path */ pcPath = prvABSPath( pcPath ); /* Find the i/o manager which can handle this path */ if( FF_FS_Find( pcPath, &xHandler ) == pdFALSE ) { iResult = ( int ) ( FF_ERR_NULL_POINTER | FF_ISDIREMPTY ); } else { iResult = FF_isDirEmpty( xHandler.pxManager, xHandler.pcPath ); } /* Store the errno to thread local storage. */ stdioSET_ERRNO( prvFFErrorToErrno( iResult ) ); return iResult; } /*-----------------------------------------------------------*/ #if ( ffconfig64_NUM_SUPPORT != 0 ) int64_t ff_diskfree( const char * pcPath, uint32_t * pxSectorCount ) #else int32_t ff_diskfree( const char * pcPath, uint32_t * pxSectorCount ) #endif { FF_DirHandler_t xHandler; FF_Error_t xError; #if ( ffconfig64_NUM_SUPPORT != 0 ) #define DISKFREE_RETURN_TYPE int64_t int64_t lReturn; #else #define DISKFREE_RETURN_TYPE int32_t int32_t lReturn; #endif if( FF_FS_Find( pcPath, &xHandler ) == pdFALSE ) { /* Return cluster 0 for error. */ lReturn = 0ul; /* Store the errno to thread local storage. */ stdioSET_ERRNO( pdFREERTOS_ERRNO_ENXIO ); /* No such device or address */ } else { if( pxSectorCount != NULL ) { *pxSectorCount = xHandler.pxManager->xPartition.ulDataSectors; } lReturn = ( DISKFREE_RETURN_TYPE ) FF_GetFreeSize( xHandler.pxManager, &xError ) / 512; /* Store the errno to thread local storage. */ stdioSET_ERRNO( prvFFErrorToErrno( xError ) ); } return lReturn; } /*-----------------------------------------------------------*/ int ff_finddir( const char * pcPath ) { int iResult; FF_DirHandler_t xHandler; FF_Error_t errCode; if( FF_FS_Find( pcPath, &xHandler ) == pdFALSE ) { /* Return cluster 0 for error. */ iResult = 0; } else { iResult = ( int ) FF_FindDir( xHandler.pxManager, xHandler.pcPath, ( uint16_t ) strlen( xHandler.pcPath ), &errCode ); } return iResult; } /*-----------------------------------------------------------*/ size_t ff_filelength( FF_FILE * pxStream ) { FF_Error_t xReturned; uint32_t ulLength; xReturned = FF_GetFileSize( pxStream, &( ulLength ) ); if( FF_isERR( xReturned ) != pdFALSE ) { /* An error. */ ulLength = ( uint32_t ) 0u; stdioSET_ERRNO( prvFFErrorToErrno( xReturned ) ); } else { stdioSET_ERRNO( pdFREERTOS_ERRNO_NONE ); } return ( size_t ) ulLength; } /*-----------------------------------------------------------*/ /*----------------------------------------------------------- * Delete a directory and, recursively, all of its contents *-----------------------------------------------------------*/ #if ( ffconfigUSE_DELTREE != 0 ) int ff_deltree( const char * pcDirectory ) { int iResult; char * pcPath; pcPath = ( char * ) ffconfigMALLOC( ffconfigMAX_FILENAME ); if( pcPath != NULL ) { /* In case a CWD is used, get the absolute path */ pcDirectory = prvABSPath( pcDirectory ); snprintf( pcPath, ffconfigMAX_FILENAME, "%s", pcDirectory ); /* This recursive function will do all the work */ iResult = ff_deltree_recurse( pcPath ); if( iResult >= 0 ) { iResult = ff_rmdir( pcPath ); if( iResult ) { FF_PRINTF( "ff_deltree(%s): %s\n", pcPath, strerror( stdioGET_ERRNO() ) ); } } ffconfigFREE( pcPath ); } else { iResult = -1; stdioSET_ERRNO( pdFREERTOS_ERRNO_ENOMEM ); } return iResult; } #endif /* ffconfigUSE_DELTREE */ /*-----------------------------------------------------------*/ #if ( ffconfigUSE_DELTREE != 0 ) static int ff_deltree_recurse( char * pcPath ) { FF_FindData_t * pxFindData; BaseType_t xIsDir, xIsDotDir; FF_Error_t xError; int iResult, iNext, iNameLength, pass, iCount = 0; pxFindData = ( FF_FindData_t * ) ffconfigMALLOC( sizeof( *pxFindData ) ); if( pxFindData != NULL ) { iNameLength = ( int ) strlen( pcPath ); /* The directory will be scanned 2 times. First the sub-directories will be * entered and their contents deleted. In the second pass the files in the * current directory will be removed. In this way 'pcPath' can be constantly * used and reused recursively which is cheaper than allocating 'ffconfigMAX_FILENAME' * bytes within each recursion. */ for( pass = 0; pass < 2; pass++ ) { for( iResult = ff_findfirst( pcPath, pxFindData ); iResult == 0; iResult = iNext ) { xIsDir = ( pxFindData->xDirectoryEntry.ucAttrib & FF_FAT_ATTR_DIR ) != 0; if( ( pass == 0 ) && ( xIsDir != pdFALSE ) ) { /* This entry is a directory. Don't traverse '.' or '..' */ xIsDotDir = 0; if( pxFindData->pcFileName[ 0 ] == '.' ) { if( ( pxFindData->pcFileName[ 1 ] == '.' ) && ( pxFindData->pcFileName[ 2 ] == '\0' ) ) { xIsDotDir = 2; } else if( pxFindData->pcFileName[ 1 ] == '\0' ) { xIsDotDir = 1; } } if( xIsDotDir == 0 ) { snprintf( pcPath + iNameLength, ( size_t ) ( ffconfigMAX_FILENAME - iNameLength ), "%s%s", pcPath[ iNameLength - 1 ] == '/' ? "" : "/", pxFindData->pcFileName ); /* Let pxFindData point to the next element before * the current will get removed. */ iNext = ff_findnext( pxFindData ); /* Remove the contents of this directory. */ iResult = ff_deltree_recurse( pcPath ); if( iResult < 0 ) { iCount = -1; break; } iCount += iResult; /* remove the directory itself */ xError = ff_rmdir( pcPath ); if( xError != 0 ) { FF_PRINTF( "ff_rmdir( %s ): errno %d\n", pcPath, stdioGET_ERRNO() ); } else { iCount++; } } else { iNext = ff_findnext( pxFindData ); } } else if( ( pass == 1 ) && ( xIsDir == pdFALSE ) ) { snprintf( pcPath + iNameLength, ( size_t ) ( ffconfigMAX_FILENAME - iNameLength ), "%s%s", pcPath[ iNameLength - 1 ] == '/' ? "" : "/", pxFindData->pcFileName ); /* Let pxFindData point to the next element before * the current will get removed. */ iNext = ff_findnext( pxFindData ); /* Remove a plain file. */ xError = ff_remove( pcPath ); if( xError != 0 ) { FF_PRINTF( "ff_remove( %s ): errno %d\n", pcPath, stdioGET_ERRNO() ); } else { iCount++; } } else { iNext = ff_findnext( pxFindData ); } pcPath[ iNameLength ] = '\0'; } if( FF_GETERROR( iResult ) == FF_ERR_DIR_INVALID_PATH ) { break; } if( ( FF_GETERROR( iResult ) != FF_ERR_DIR_END_OF_DIR ) && ( FF_GETERROR( iResult ) != FF_ERR_FILE_INVALID_PATH ) ) { FF_PRINTF( "ff_deltree_recurse[%s]: %s\n", pcPath, ( const char * ) FF_GetErrMessage( iResult ) ); } } ffconfigFREE( pxFindData ); } else { iCount = -1; stdioSET_ERRNO( pdFREERTOS_ERRNO_ENOMEM ); } return iCount; } #endif /* ffconfigUSE_DELTREE */ /*-----------------------------------------------------------*/ int prvFFErrorToErrno( FF_Error_t xError ) { if( FF_isERR( xError ) == pdFALSE ) { return 0; } /* Store the last +FAT error code received. */ stdioSET_FF_ERROR( xError ); switch( FF_GETERROR( xError ) ) { /* Global Error Codes. */ case FF_ERR_NONE: return 0; /* No Error. */ case FF_ERR_NULL_POINTER: return pdFREERTOS_ERRNO_EBADF; /* pxIOManager was NULL. */ case FF_ERR_NOT_ENOUGH_MEMORY: return pdFREERTOS_ERRNO_ENOMEM; /* malloc() failed! - Could not allocate handle memory. */ case FF_ERR_DEVICE_DRIVER_FAILED: return pdFREERTOS_ERRNO_EIO; /* The Block Device driver reported a FATAL error, cannot continue. */ /* User return codes for Rd/Wr functions:. */ case FF_ERR_IOMAN_DRIVER_BUSY: return pdFREERTOS_ERRNO_EBUSY; /* 10. */ case FF_ERR_IOMAN_DRIVER_FATAL_ERROR: return pdFREERTOS_ERRNO_EUNATCH; /* Protocol driver not attached. */ /* IOMAN Error Codes. */ case FF_ERR_IOMAN_BAD_BLKSIZE: return pdFREERTOS_ERRNO_EINVAL; /* The provided blocksize was not a multiple of 512. */ case FF_ERR_IOMAN_BAD_MEMSIZE: return pdFREERTOS_ERRNO_EINVAL; /* The memory size was not a multiple of the blocksize. */ case FF_ERR_IOMAN_DEV_ALREADY_REGD: return pdFREERTOS_ERRNO_EADDRINUSE; /* Device was already registered. Use FF_UnRegister() to re-use this IOMAN with another device. */ case FF_ERR_IOMAN_NO_MOUNTABLE_PARTITION: return pdFREERTOS_ERRNO_ENOMEDIUM; /* A mountable partition could not be found on the device. */ case FF_ERR_IOMAN_INVALID_FORMAT: return pdFREERTOS_ERRNO_EFTYPE; /* The. */ case FF_ERR_IOMAN_INVALID_PARTITION_NUM: return pdFREERTOS_ERRNO_EINVAL; /* The partition number provided was out of range. */ case FF_ERR_IOMAN_NOT_FAT_FORMATTED: return pdFREERTOS_ERRNO_EFTYPE; /* The partition did not look like a FAT partition. */ case FF_ERR_IOMAN_DEV_INVALID_BLKSIZE: return pdFREERTOS_ERRNO_EINVAL; /* IOMAN object BlkSize is not compatible with the blocksize of this device driver. */ case FF_ERR_IOMAN_PARTITION_MOUNTED: return pdFREERTOS_ERRNO_EADDRINUSE; /* Device is in use by an actively mounted partition. Unmount the partition first. */ case FF_ERR_IOMAN_ACTIVE_HANDLES: return pdFREERTOS_ERRNO_EBUSY; /* The partition cannot be unmounted until all active file handles are closed. (There may also be active handles on the cache). */ case FF_ERR_IOMAN_GPT_HEADER_CORRUPT: return pdFREERTOS_ERRNO_EBADE; /* The GPT partition table appears to be corrupt, refusing to mount. */ case FF_ERR_IOMAN_NOT_ENOUGH_FREE_SPACE: return pdFREERTOS_ERRNO_ENOSPC; /* 22. */ case FF_ERR_IOMAN_OUT_OF_BOUNDS_READ: return pdFREERTOS_ERRNO_ESPIPE; /* 23, return 'Illegal seek'. */ case FF_ERR_IOMAN_OUT_OF_BOUNDS_WRITE: return pdFREERTOS_ERRNO_ESPIPE; /* 24. */ case FF_ERR_IOMAN_DRIVER_NOMEDIUM: return pdFREERTOS_ERRNO_ENOMEDIUM; /* The medium (e.g. SD-card) has been extracted. */ /* File Error Codes 30 +. */ case FF_ERR_FILE_ALREADY_OPEN: return pdFREERTOS_ERRNO_EALREADY; /* File is in use. */ case FF_ERR_FILE_NOT_FOUND: return pdFREERTOS_ERRNO_ENOENT; /* File was not found. */ case FF_ERR_FILE_OBJECT_IS_A_DIR: return pdFREERTOS_ERRNO_EISDIR; /* Tried to FF_Open() a Directory. */ case FF_ERR_FILE_IS_READ_ONLY: return pdFREERTOS_ERRNO_EROFS; /* Tried to FF_Open() a file marked read only. */ case FF_ERR_FILE_INVALID_PATH: return pdFREERTOS_ERRNO_ENOTDIR; /* The path of the file was not found. */ case FF_ERR_FILE_NOT_OPENED_IN_WRITE_MODE: return pdFREERTOS_ERRNO_EACCES; /* 35. */ case FF_ERR_FILE_NOT_OPENED_IN_READ_MODE: return pdFREERTOS_ERRNO_EACCES; /* 36. */ case FF_ERR_FILE_EXTEND_FAILED: return pdFREERTOS_ERRNO_ENOSPC; /* Could not extend the file. */ case FF_ERR_FILE_DESTINATION_EXISTS: return pdFREERTOS_ERRNO_EEXIST; /* 38. */ case FF_ERR_FILE_SOURCE_NOT_FOUND: return pdFREERTOS_ERRNO_ENOENT; /* 39. */ case FF_ERR_FILE_DIR_NOT_FOUND: return pdFREERTOS_ERRNO_ENOENT; /* 40. */ case FF_ERR_FILE_COULD_NOT_CREATE_DIRENT: return pdFREERTOS_ERRNO_EIO; /* 41. */ case FF_ERR_FILE_BAD_HANDLE: return pdFREERTOS_ERRNO_EBADF; /* A file handle was invalid. */ case FF_ERR_FILE_MEDIA_REMOVED: return pdFREERTOS_ERRNO_ENODEV; /* File handle got invalid because media was removed. */ case FF_ERR_FILE_SEEK_INVALID_POSITION: return pdFREERTOS_ERRNO_ESPIPE; /* Illegal position, outside the file's space */ case FF_ERR_FILE_SEEK_INVALID_ORIGIN: return pdFREERTOS_ERRNO_EINVAL; /* Seeking beyond end of file. */ /* Directory Error Codes 50 +. */ case FF_ERR_DIR_OBJECT_EXISTS: return pdFREERTOS_ERRNO_EEXIST; /* A file or folder of the same name already exists in the current directory. */ case FF_ERR_DIR_DIRECTORY_FULL: return pdFREERTOS_ERRNO_ENOSPC; /* No more items could be added to the directory. */ case FF_ERR_DIR_END_OF_DIR: return pdFREERTOS_ERRNO_ENMFILE; /*/. */ case FF_ERR_DIR_NOT_EMPTY: return pdFREERTOS_ERRNO_ENOTEMPTY; /* Cannot delete a directory that contains files or folders. */ case FF_ERR_DIR_INVALID_PATH: return pdFREERTOS_ERRNO_EINVAL; /* Could not find the directory specified by the path. */ case FF_ERR_DIR_CANT_EXTEND_ROOT_DIR: return pdFREERTOS_ERRNO_ENOSPC; /* Can't extend the root dir. */ case FF_ERR_DIR_EXTEND_FAILED: return pdFREERTOS_ERRNO_ENOSPC; /* Not enough space to extend the directory. */ case FF_ERR_DIR_NAME_TOO_LONG: return pdFREERTOS_ERRNO_ENAMETOOLONG; /* Name exceeds the number of allowed characters for a filename. */ /* Fat Error Codes 70 +. */ case FF_ERR_FAT_NO_FREE_CLUSTERS: return pdFREERTOS_ERRNO_ENOSPC; /* No more free space is available on the disk. */ /* UNICODE Error Codes 100 +. */ case FF_ERR_UNICODE_INVALID_CODE: return pdFREERTOS_ERRNO_EBADE; /* An invalid Unicode character was provided!. */ case FF_ERR_UNICODE_DEST_TOO_SMALL: return pdFREERTOS_ERRNO_ENOBUFS; /* Not enough space in the UTF-16 buffer to encode the entire sequence as UTF-16. */ case FF_ERR_UNICODE_INVALID_SEQUENCE: return pdFREERTOS_ERRNO_EILSEQ; /* An invalid UTF-16 sequence was encountered. */ case FF_ERR_UNICODE_CONVERSION_EXCEEDED: return pdFREERTOS_ERRNO_ENAMETOOLONG; /* Filename exceeds MAX long-filename length when converted to UTF-16. */ } return pdFREERTOS_ERRNO_EFAULT; } /*-----------------------------------------------------------*/ #if ( ffconfigHAS_CWD == 1 ) void ff_free_CWD_space( void ) { WorkingDirectory_t * pxSpace; /* Obtain the CWD used by the current task. */ pxSpace = ( WorkingDirectory_t * ) pvTaskGetThreadLocalStoragePointer( NULL, stdioCWD_THREAD_LOCAL_OFFSET ); if( pxSpace != NULL ) { vTaskSetThreadLocalStoragePointer( NULL, stdioCWD_THREAD_LOCAL_OFFSET, ( void * ) NULL ); ffconfigFREE( pxSpace ); } } #endif /* ffconfigHAS_CWD */ /*-----------------------------------------------------------*/ #if ( ffconfigHAS_CWD == 1 ) static WorkingDirectory_t * pxFindCWD( void ) { WorkingDirectory_t * pxReturn; /* Obtain the CWD used by the current task. */ pxReturn = ( WorkingDirectory_t * ) pvTaskGetThreadLocalStoragePointer( NULL, stdioCWD_THREAD_LOCAL_OFFSET ); if( pxReturn == NULL ) { /* This task does not yet have a WorkingDirectory_t structure - create and * initialise one now. */ pxReturn = ( WorkingDirectory_t * ) ffconfigMALLOC( sizeof( WorkingDirectory_t ) ); if( pxReturn != NULL ) { pxReturn->pcCWD[ 0 ] = '\0'; vTaskSetThreadLocalStoragePointer( NULL, stdioCWD_THREAD_LOCAL_OFFSET, ( void * ) pxReturn ); } } return pxReturn; } #endif /* ffconfigHAS_CWD */ /*-----------------------------------------------------------*/ #if ( ffconfigHAS_CWD == 1 ) static const char * prvProcessRelativePaths( const char * pcPath ) { const char * pcReturn; char * pcChar, * pcTokenStart = NULL, * pcFollowingToken, cPreviousChar = 0x00; BaseType_t xByte; /* Scan the string looking for a relative path. */ pcReturn = pcPath; pcChar = ( char * ) pcReturn; configASSERT( pcPath ); while( *pcChar != 0x00 ) { if( *pcChar == '.' ) { /* A potential relative path was found. Is this a "." or a "..". */ if( *( pcChar + 1 ) == '.' ) { /* Nothing can be done if this is at the start of the string. */ if( pcTokenStart != NULL ) { /* A ".." was found. Where does the next token start? */ pcFollowingToken = pcChar + 2; if( *pcFollowingToken == '/' ) { /* The next token starts after the "../" */ pcFollowingToken += sizeof( char ); } /* Remove the ".." and the previous token. */ xByte = 0; while( pcFollowingToken[ xByte ] != 0x00 ) { pcTokenStart[ xByte ] = pcFollowingToken[ xByte ]; xByte++; } /* Terminate. */ pcTokenStart[ xByte ] = 0x00; /* The pointer to the previous token will now be wrong if * there are multiple if "../.." appears in the string. So * reset the variables to continue scanning. */ pcChar = ( char * ) pcReturn; cPreviousChar = 0x00; pcTokenStart = NULL; continue; } } else { /* A "." was found. Remove it. */ } } if( cPreviousChar == '/' ) { /* This is the start of a new token. */ pcTokenStart = pcChar; } cPreviousChar = *pcChar; pcChar++; } /* Make sure there is no / on the end of the string, being careful not to * remove the / at the beginning of the string. */ if( *( pcChar - 1 ) == '/' ) { if( ( pcChar - 1 ) != pcReturn ) { *( pcChar - 1 ) = 0x00; } } return pcReturn; } #endif /* ffconfigHAS_CWD */ /*-----------------------------------------------------------*/ #if ( ffconfigHAS_CWD == 1 ) /*static*/ const char * prvABSPath( const char * pcPath ) { char * pcReturn; WorkingDirectory_t * pxWorkingDirectory = pxFindCWD(); configASSERT( pxWorkingDirectory ); if( ( pcPath[ 0 ] ) == '/' ) { /* If the path starts with a slash it does not start with a relative * path. Copy the string into a thread local buffer so it can be * manipulated without risk of attempting to write to read only * memory. */ snprintf( pxWorkingDirectory->pcFileName, sizeof( pxWorkingDirectory->pcFileName ), "%s", pcPath ); pcReturn = pxWorkingDirectory->pcFileName; } else { /* Insert the working directory into the front of the path. */ if( pxWorkingDirectory->pcCWD[ 1 ] == 0x00 ) { /* In the root, so don't add a '/' between the CWD and the * file name. */ snprintf( pxWorkingDirectory->pcFileName, sizeof( pxWorkingDirectory->pcFileName ), "/%s", pcPath ); } else { snprintf( pxWorkingDirectory->pcFileName, sizeof( pxWorkingDirectory->pcFileName ), "%s/%s", pxWorkingDirectory->pcCWD, pcPath ); } pcReturn = pxWorkingDirectory->pcFileName; } /* Make any adjustments necessitated by relative paths. */ prvProcessRelativePaths( pcReturn ); return pcReturn; } #endif /* ffconfigHAS_CWD */ #if ( ffconfigTIME_SUPPORT == 1 ) static uint32_t prvFileTime( FF_SystemTime_t * pxTime ) { FF_TimeStruct_t xTime; time_t xReturn; xTime.tm_sec = pxTime->Second; xTime.tm_min = pxTime->Minute; xTime.tm_hour = pxTime->Hour; xTime.tm_mday = pxTime->Day; xTime.tm_mon = pxTime->Month - 1; xTime.tm_year = pxTime->Year - 1900; xReturn = FreeRTOS_mktime( &xTime ); return xReturn; } #endif /* if ( ffconfigTIME_SUPPORT == 1 ) */ /*-----------------------------------------------------------*/