MAX_CARLINK_A270S/MXC_A27-PCB4.5-270S/FreeRTOS-Plus/FreeRTOS-FAT/ff_stdio.c

2175 lines
71 KiB
C

/*
* 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 <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
/*-----------------------------------------------------------*/
/*
* 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 ) */
/*-----------------------------------------------------------*/