| /* | |
| * FreeRTOS+FAT build 191128 - Note: FreeRTOS+FAT is still in the lab! | |
| * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. | |
| * Authors include James Walmsley, Hein Tibosch and Richard Barry | |
| * | |
| * 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 | |
| * | |
| */ | |
| /* 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 <time.h> | |
| #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 | |
| /*-----------------------------------------------------------*/ | |
| #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; | |
| /* | |
| * 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 ); | |
| /* | |
| * 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 | |
| /* | |
| * 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 | |
| /*-----------------------------------------------------------*/ | |
| 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 | |
| /*-----------------------------------------------------------*/ | |
| /*_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 | |
| /*-----------------------------------------------------------*/ | |