You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2029 lines
58 KiB
C
2029 lines
58 KiB
C
/*
|
|
* 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
|
|
/*-----------------------------------------------------------*/
|
|
|
|
|