blob: 36a3ee065f2d80d09ceb3ab43053c74ca2aa80ea [file] [log] [blame]
/*
* 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
/*-----------------------------------------------------------*/