summaryrefslogtreecommitdiff
path: root/FreeRTOS-Labs/Demo/Common/Demo_IP_Protocols/FTP/FreeRTOS_FTP_server.c
diff options
context:
space:
mode:
Diffstat (limited to 'FreeRTOS-Labs/Demo/Common/Demo_IP_Protocols/FTP/FreeRTOS_FTP_server.c')
-rw-r--r--FreeRTOS-Labs/Demo/Common/Demo_IP_Protocols/FTP/FreeRTOS_FTP_server.c2637
1 files changed, 2637 insertions, 0 deletions
diff --git a/FreeRTOS-Labs/Demo/Common/Demo_IP_Protocols/FTP/FreeRTOS_FTP_server.c b/FreeRTOS-Labs/Demo/Common/Demo_IP_Protocols/FTP/FreeRTOS_FTP_server.c
new file mode 100644
index 000000000..a2a6ba155
--- /dev/null
+++ b/FreeRTOS-Labs/Demo/Common/Demo_IP_Protocols/FTP/FreeRTOS_FTP_server.c
@@ -0,0 +1,2637 @@
+/*
+ * FreeRTOS+TCP V2.0.3
+ * Copyright (C) 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * http://aws.amazon.com/freertos
+ * http://www.FreeRTOS.org
+ */
+
+/* Standard includes. */
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+/* FreeRTOS includes. */
+#include "FreeRTOS.h"
+#include "task.h"
+#include "portmacro.h"
+
+/* FreeRTOS+TCP includes. */
+#include "FreeRTOS_IP.h"
+#include "FreeRTOS_TCP_IP.h"
+#include "FreeRTOS_Sockets.h"
+#include "FreeRTOS_Stream_Buffer.h"
+
+/* FreeRTOS Protocol includes. */
+#include "FreeRTOS_FTP_commands.h"
+#include "FreeRTOS_TCP_server.h"
+#include "FreeRTOS_server_private.h"
+
+/* Remove the whole file if FTP is not supported. */
+#if( ipconfigUSE_FTP == 1 )
+
+#ifndef HTTP_SERVER_BACKLOG
+ #define HTTP_SERVER_BACKLOG ( 12 )
+#endif
+
+#if !defined( ARRAY_SIZE )
+ #define ARRAY_SIZE( x ) ( BaseType_t ) (sizeof( x ) / sizeof( x )[ 0 ] )
+#endif
+
+#if defined(__WIN32__) && !defined(ipconfigFTP_FS_USES_BACKSLAH)
+ #define ipconfigFTP_FS_USES_BACKSLAH 1
+#endif
+
+/* Some defines to make the code more readbale */
+#define pcCOMMAND_BUFFER pxClient->pxParent->pcCommandBuffer
+#define pcNEW_DIR pxClient->pxParent->pcNewDir
+#define pcFILE_BUFFER pxClient->pxParent->pcFileBuffer
+
+/* This FTP server will only do binary transfers */
+#define TMODE_BINARY 1
+#define TMODE_ASCII 2
+#define TMODE_7BITS 3
+#define TMODE_8BITS 4
+
+/* Ascii character definitions. */
+#define ftpASCII_CR 13
+#define ftpASCII_LF 10
+
+#if defined( FTP_WRITES_ALIGNED ) || defined( ipconfigFTP_WRITES_ALIGNED )
+ #error Name change : please rename the define to the new name 'ipconfigFTP_ZERO_COPY_ALIGNED_WRITES'
+#endif
+
+/*
+ * ipconfigFTP_ZERO_COPY_ALIGNED_WRITES : experimental optimisation option.
+ * If non-zero, receiving data will be done with the zero-copy method and also
+ * writes to disk will be done with sector-alignment as much as possible.
+ */
+#ifndef ipconfigFTP_ZERO_COPY_ALIGNED_WRITES
+ #define ipconfigFTP_ZERO_COPY_ALIGNED_WRITES 0
+#endif
+
+/*
+ * This module only has 2 public functions:
+ */
+BaseType_t xFTPClientWork( TCPClient_t *pxClient );
+void vFTPClientDelete( TCPClient_t *pxClient );
+
+/*
+ * Process a single command.
+ */
+static BaseType_t prvProcessCommand( FTPClient_t *pxClient, BaseType_t xIndex, char *pcRestCommand );
+
+/*
+ * Create a socket for a data connection to the FTP client.
+ */
+static BaseType_t prvTransferConnect( FTPClient_t *pxClient, BaseType_t xDoListen );
+
+/*
+ * Either call listen() or connect() to start the transfer connection.
+ */
+static BaseType_t prvTransferStart( FTPClient_t *pxClient );
+
+/*
+ * See if the socket has got connected or disconnected. Close the socket if
+ * necessary.
+ */
+static void prvTransferCheck( FTPClient_t *pxClient );
+
+/*
+ * Close the data socket and issue some informative logging.
+ */
+static void prvTransferCloseSocket( FTPClient_t *pxClient );
+
+/*
+ * Close the file handle (pxReadHandle or pxWriteHandle).
+ */
+static void prvTransferCloseFile( FTPClient_t *pxClient );
+
+/*
+ * Close a directory (-handle).
+ */
+static void prvTransferCloseDir( FTPClient_t *pxClient );
+
+/*
+ * Translate a string (indicating a transfer type) to a number.
+ */
+static BaseType_t prvGetTransferType( const char *pcType );
+
+#if( ipconfigHAS_PRINTF != 0 )
+ /*
+ * For nice logging: write an amount (number of bytes), e.g. 3512200 as
+ * "3.45 MB"
+ */
+ static const char *pcMkSize( uint32_t ulAmount, char *pcBuffer, BaseType_t xBufferSize );
+#endif
+
+#if( ipconfigHAS_PRINTF != 0 )
+ /*
+ * Calculate the average as bytes-per-second, when amount and milliseconds
+ * are known.
+ */
+ static uint32_t ulGetAverage( uint32_t ulAmount, TickType_t xDeltaMs );
+#endif
+
+/*
+ * A port command looks like: PORT h1,h2,h3,h4,p1,p2. Parse it and translate it
+ * to an IP-address and a port number.
+ */
+static UBaseType_t prvParsePortData( const char *pcCommand, uint32_t *pulIPAddress );
+
+/*
+ * CWD: Change current working directory.
+ */
+
+static BaseType_t prvChangeDir( FTPClient_t *pxClient, char *pcDirectory );
+
+/*
+ * RNFR: Rename from ...
+ */
+static BaseType_t prvRenameFrom( FTPClient_t *pxClient, const char *pcFileName );
+
+/*
+ * RNTO: Rename to ...
+ */
+static BaseType_t prvRenameTo( FTPClient_t *pxClient, const char *pcFileName );
+
+/*
+ * SITE: Change file permissions.
+ */
+static BaseType_t prvSiteCmd( FTPClient_t *pxClient, char *pcRestCommand );
+
+/*
+ * DELE: Delete a file.
+ */
+static BaseType_t prvDeleteFile( FTPClient_t *pxClient, char *pcFileName );
+
+/*
+ * SIZE: get the size of a file (xSendDate = 0).
+ * MDTM: get data and time properties (xSendDate = 1).
+ */
+static BaseType_t prvSizeDateFile( FTPClient_t *pxClient, char *pcFileName, BaseType_t xSendDate );
+
+/*
+ * MKD: Make / create a directory (xDoRemove = 0).
+ * RMD: Remove a directory (xDoRemove = 1).
+ */
+static BaseType_t prvMakeRemoveDir( FTPClient_t *pxClient, const char *pcDirectory, BaseType_t xDoRemove );
+
+/*
+ * The next three commands: LIST, RETR and STOR all require a data socket.
+ * The data connection is either started with a 'PORT' or a 'PASV' command.
+ * Each of the commands has a prepare- (Prep) and a working- (Work) function.
+ * The Work function should be called as long as the data socket is open, and
+ * there is data to be transmitted.
+ */
+
+/*
+ * LIST: Send a directory listing in Unix style.
+ */
+static BaseType_t prvListSendPrep( FTPClient_t *pxClient );
+static BaseType_t prvListSendWork( FTPClient_t *pxClient );
+
+/*
+ * RETR: Send a file to the FTP client.
+ */
+static BaseType_t prvRetrieveFilePrep( FTPClient_t *pxClient, char *pcFileName );
+static BaseType_t prvRetrieveFileWork( FTPClient_t *pxClient );
+
+/*
+ * STOR: Receive a file from the FTP client and store it.
+ */
+static BaseType_t prvStoreFilePrep( FTPClient_t *pxClient, char *pcFileName );
+static BaseType_t prvStoreFileWork( FTPClient_t *pxClient );
+
+/*
+ * Print/format a single directory entry in Unix style.
+ */
+static BaseType_t prvGetFileInfoStat( FF_DirEnt_t *pxEntry, char *pcLine, BaseType_t xMaxLength );
+
+/*
+ * Send a reply to a socket, either the command- or the data-socket.
+ */
+static BaseType_t prvSendReply( Socket_t xSocket, const char *pcBuffer, BaseType_t xLength );
+
+/*
+ * Prepend the root directory (if any), plus the current working directory
+ * (always), to get an absolute path.
+ */
+BaseType_t xMakeAbsolute( FTPClient_t *pxClient, char *pcBuffer, BaseType_t xBufferLength, const char *pcPath );
+
+/*
+
+####### ##### ###### # # ##
+ # ## # # # # # # # #
+ # # # # # # #
+ # # # # # # # #### ### ## # #
+ ##### # ##### # # # # # # # # # #
+ # # # # # # # # # ## # ####
+ # # # ## ## # # # # #
+ # # # ## ## # # # # #
+#### #### #### ## ## #### #### ## ##
+
+ * xFTPClientWork()
+ * will be called by FreeRTOS_TCPServerWork(), after select has expired().
+ * FD_ISSET will not be used. This work function will always be called at
+ * regular intervals, and also after a select() event has occurred.
+ */
+BaseType_t xFTPClientWork( TCPClient_t *pxTCPClient )
+{
+FTPClient_t *pxClient = ( FTPClient_t * ) pxTCPClient;
+BaseType_t xRc;
+
+ if( pxClient->bits.bHelloSent == pdFALSE_UNSIGNED )
+ {
+ BaseType_t xLength;
+
+ pxClient->bits.bHelloSent = pdTRUE_UNSIGNED;
+
+ xLength = snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ),
+ "220 Welcome to the FreeRTOS+TCP FTP server\r\n" );
+ prvSendReply( pxClient->xSocket, pcCOMMAND_BUFFER, xLength );
+ }
+
+ /* Call recv() in a non-blocking way, to see if there is an FTP command
+ sent to this server. */
+ xRc = FreeRTOS_recv( pxClient->xSocket, ( void * )pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ), 0 );
+
+ if( xRc > 0 )
+ {
+ BaseType_t xIndex;
+ const FTPCommand_t *pxCommand;
+ char *pcRestCommand;
+
+ if( xRc < ( BaseType_t ) sizeof( pcCOMMAND_BUFFER ) )
+ {
+ pcCOMMAND_BUFFER[ xRc ] = '\0';
+ }
+
+ while( xRc && ( ( pcCOMMAND_BUFFER[ xRc - 1 ] == ftpASCII_CR ) || ( pcCOMMAND_BUFFER[ xRc - 1 ] == ftpASCII_LF ) ) )
+ {
+ pcCOMMAND_BUFFER[ --xRc ] = '\0';
+ }
+
+ /* Now iterate through a list of FTP commands, and look for a match. */
+ pxCommand = xFTPCommands;
+ pcRestCommand = pcCOMMAND_BUFFER;
+ for( xIndex = 0; xIndex < FTP_CMD_COUNT - 1; xIndex++, pxCommand++ )
+ {
+ BaseType_t xLength;
+
+ /* The length of each command is stored as well, just to be a bit
+ quicker here. */
+ xLength = pxCommand->xCommandLength;
+
+ if( ( xRc >= xLength ) && ( memcmp( ( const void * ) pxCommand->pcCommandName, ( const void * ) pcCOMMAND_BUFFER, xLength ) == 0 ) )
+ {
+ /* A match with an existing command is found. Skip any
+ whitespace to get the first parameter. */
+ pcRestCommand += xLength;
+ while( ( *pcRestCommand == ' ' ) || ( *pcRestCommand == '\t' ) )
+ {
+ pcRestCommand++;
+ }
+ break;
+ }
+ }
+
+ /* If the command received was not recognised, xIndex will point to a
+ fake entry called 'ECMD_UNKNOWN'. */
+ prvProcessCommand( pxClient, xIndex, pcRestCommand );
+ }
+ else if( xRc < 0 )
+ {
+ /* The connection will be closed and the client will be deleted. */
+ FreeRTOS_printf( ( "xFTPClientWork: xRc = %ld\n", xRc ) );
+ }
+
+ /* Does it have an open data connection? */
+ if( pxClient->xTransferSocket != FREERTOS_NO_SOCKET )
+ {
+ /* See if the connection has changed. */
+ prvTransferCheck( pxClient );
+
+ /* "pcConnectionAck" contains a string like:
+ "Response: 150 Accepted data connection from 192.168.2.3:6789"
+ The socket can only be used once this acknowledgement has been sent. */
+ if( ( pxClient->xTransferSocket != FREERTOS_NO_SOCKET ) && ( pxClient->pcConnectionAck[ 0 ] == '\0' ) )
+ {
+ BaseType_t xClientRc = 0;
+
+ if( pxClient->bits1.bDirHasEntry )
+ {
+ /* Still listing a directory. */
+ xClientRc = prvListSendWork( pxClient );
+ }
+ else if( pxClient->pxReadHandle != NULL )
+ {
+ /* Sending a file. */
+ xClientRc = prvRetrieveFileWork( pxClient );
+ }
+ else if( pxClient->pxWriteHandle != NULL )
+ {
+ /* Receiving a file. */
+ xClientRc = prvStoreFileWork( pxClient );
+ }
+
+ if( xClientRc < 0 )
+ {
+ prvTransferCloseSocket( pxClient );
+ prvTransferCloseFile( pxClient );
+ }
+ }
+ }
+
+ return xRc;
+}
+/*-----------------------------------------------------------*/
+
+static void prvTransferCloseDir( FTPClient_t *pxClient )
+{
+ /* Nothing to close for +FAT. */
+ ( void ) pxClient;
+}
+/*-----------------------------------------------------------*/
+
+void vFTPClientDelete( TCPClient_t *pxTCPClient )
+{
+FTPClient_t *pxClient = ( FTPClient_t * ) pxTCPClient;
+
+ /* Close any directory-listing-handles (not used by +FAT ). */
+ prvTransferCloseDir( pxClient );
+ /* Close the data-socket. */
+ prvTransferCloseSocket( pxClient );
+ /* Close any open file handle. */
+ prvTransferCloseFile( pxClient );
+
+ /* Close the FTP command socket */
+ if( pxClient->xSocket != FREERTOS_NO_SOCKET )
+ {
+ FreeRTOS_FD_CLR( pxClient->xSocket, pxClient->pxParent->xSocketSet, eSELECT_ALL );
+ FreeRTOS_closesocket( pxClient->xSocket );
+ pxClient->xSocket = FREERTOS_NO_SOCKET;
+ }
+}
+/*-----------------------------------------------------------*/
+
+static BaseType_t prvProcessCommand( FTPClient_t *pxClient, BaseType_t xIndex, char *pcRestCommand )
+{
+const FTPCommand_t *pxFTPCommand = &( xFTPCommands[ xIndex ] );
+const char *pcMyReply = NULL;
+BaseType_t xResult = 0;
+
+ if( ( pxFTPCommand->ucCommandType != ECMD_PASS ) && ( pxFTPCommand->ucCommandType != ECMD_PORT ) )
+ {
+ FreeRTOS_printf( ( " %s %s\n", pxFTPCommand->pcCommandName, pcRestCommand ) );
+ }
+
+ if( ( pxFTPCommand->checkLogin != pdFALSE ) && ( pxClient->bits.bLoggedIn == pdFALSE_UNSIGNED ) )
+ {
+ pcMyReply = REPL_530; /* Please first log in. */
+ }
+ else if( ( pxFTPCommand->checkNullArg != pdFALSE ) && ( ( pcRestCommand == NULL ) || ( pcRestCommand[ 0 ] == '\0' ) ) )
+ {
+ pcMyReply = REPL_501; /* Command needs a parameter. */
+ }
+
+ if( pcMyReply == NULL )
+ {
+ switch( pxFTPCommand->ucCommandType )
+ {
+ case ECMD_USER: /* User. */
+ /* User name has been entered, expect password. */
+ pxClient->bits.bStatusUser = pdTRUE_UNSIGNED;
+
+ #if( ipconfigFTP_HAS_USER_PASSWORD_HOOK != 0 )/*_RB_ Needs defaulting and adding to the web documentation. */
+ {
+ /* Save the user name in 'pcFileName'. */
+ snprintf( pxClient->pcFileName, sizeof( pxClient->pcFileName ), "%s", pcRestCommand );
+
+ /* The USER name is presented to the application. The function
+ may return a const string like "331 Please enter your
+ password\r\n". */
+ pcMyReply = pcApplicationFTPUserHook( pxClient->pcFileName );
+ if( pcMyReply == NULL )
+ {
+ pcMyReply = REPL_331_ANON;
+ }
+ }
+ #else
+ {
+ /* No password checks, any password will be accepted. */
+ pcMyReply = REPL_331_ANON;
+ }
+ #endif /* ipconfigFTP_HAS_USER_PASSWORD_HOOK != 0 */
+
+ #if( ipconfigFTP_HAS_USER_PROPERTIES_HOOK != 0 )/*_RB_ Needs defaulting and adding to the web documentation. */
+ {
+ FTPUserProperties_t xProperties;
+
+ xProperties.pcRootDir = pxClient->pcRootDir;
+ xProperties.xReadOnly = pdFALSE;
+ xProperties.usPortNumber = pxClient->usClientPort;
+ vApplicationFTPUserPropertiesHook( pxClient->pcFileName, &( xProperties ) );
+
+ if( xProperties.pcRootDir != NULL )
+ {
+ pxClient->pcRootDir = xProperties.pcRootDir;
+ }
+ pxClient->bits.bReadOnly = ( xProperties.xReadOnly != pdFALSE_UNSIGNED );
+ }
+ #endif /* ipconfigFTP_HAS_USER_PROPERTIES_HOOK */
+ break;
+
+ case ECMD_PASS: /* Password. */
+ pxClient->ulRestartOffset = 0;
+ if( pxClient->bits.bStatusUser == pdFALSE_UNSIGNED )
+ {
+ pcMyReply = REPL_503; /* "503 Bad sequence of commands.\r\n". */
+ }
+ else
+ {
+ BaseType_t xAllow;
+
+ pxClient->bits.bStatusUser = pdFALSE_UNSIGNED;
+ #if( ipconfigFTP_HAS_USER_PASSWORD_HOOK != 0 )
+ {
+ xAllow = xApplicationFTPPasswordHook( pxClient->pcFileName, pcRestCommand );
+ }
+ #else
+ {
+ xAllow = 1;
+ }
+ #endif /* ipconfigFTP_HAS_USER_PASSWORD_HOOK */
+
+ if( xAllow > 0 )
+ {
+ pxClient->bits.bLoggedIn = pdTRUE_UNSIGNED; /* Client has now logged in. */
+ pcMyReply = "230 OK. Current directory is /\r\n";
+ }
+ else
+ {
+ pcMyReply = "530 Login incorrect\r\n"; /* 530 Login incorrect. */
+ }
+
+ strcpy( pxClient->pcCurrentDir, ( const char * ) "/" );
+ }
+ break;
+
+ case ECMD_SYST: /* System. */
+ snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ), "215 UNIX Type: L8\r\n" );
+ pcMyReply = pcCOMMAND_BUFFER;
+ break;
+
+ case ECMD_PWD: /* Get working directory. */
+ xMakeRelative( pxClient, pcFILE_BUFFER, sizeof( pcFILE_BUFFER ), pxClient->pcCurrentDir );
+ snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ), REPL_257_PWD, pcFILE_BUFFER );
+ pcMyReply = pcCOMMAND_BUFFER;
+ break;
+
+ case ECMD_REST:
+ if( pxClient->bits.bReadOnly != pdFALSE_UNSIGNED )
+ {
+ pcMyReply = REPL_553_READ_ONLY;
+ }
+ else
+ {
+ const char *pcPtr = pcRestCommand;
+
+ while( *pcPtr == ' ' )
+ {
+ pcPtr++;
+ }
+
+ if( ( *pcPtr >= '0' ) && ( *pcPtr <= '9' ) )
+ {
+ sscanf( pcPtr, "%lu", &pxClient->ulRestartOffset );
+ snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ),
+ "350 Restarting at %lu. Send STORE or RETRIEVE\r\n", pxClient->ulRestartOffset );
+ pcMyReply = pcCOMMAND_BUFFER;
+ }
+ else
+ {
+ pcMyReply = REPL_500; /* 500 Syntax error, command unrecognised. */
+ }
+ }
+ break;
+
+ case ECMD_NOOP: /* NOP operation */
+ if( pxClient->xTransferSocket != FREERTOS_NO_SOCKET )
+ {
+ pcMyReply = REPL_200_PROGRESS;
+ }
+ else
+ {
+ pcMyReply = REPL_200;
+ }
+ break;
+
+ case ECMD_TYPE: /* Ask or set transfer type. */
+ {
+ /* e.g. "TYPE I" for Images (binary). */
+ BaseType_t xType = prvGetTransferType( pcRestCommand );
+
+ if( xType < 0 )
+ {
+ /* TYPE not recognised. */
+ pcMyReply = REPL_500;
+ }
+ else
+ {
+ pxClient->xTransType = xType;
+ pcMyReply = REPL_200;
+ }
+ }
+ break;
+
+ case ECMD_PASV: /* Enter passive mode. */
+ /* Connect passive: Server will listen() and wait for a connection.
+ Start up a new data connection with 'xDoListen' set to true. */
+ if( prvTransferConnect( pxClient, pdTRUE ) == pdFALSE )
+ {
+ pcMyReply = REPL_502;
+ }
+ else
+ {
+ uint32_t ulIP;
+ uint16_t ulPort;
+ struct freertos_sockaddr xLocalAddress;
+ struct freertos_sockaddr xRemoteAddress;
+
+ FreeRTOS_GetLocalAddress( pxClient->xTransferSocket, &xLocalAddress );
+ FreeRTOS_GetRemoteAddress( pxClient->xSocket, &xRemoteAddress );
+
+ ulIP = FreeRTOS_ntohl( xLocalAddress.sin_addr );
+ pxClient->ulClientIP = FreeRTOS_ntohl( xRemoteAddress.sin_addr );
+ ulPort = FreeRTOS_ntohs( xLocalAddress.sin_port );
+
+ pxClient->usClientPort = FreeRTOS_ntohs( xRemoteAddress.sin_port );
+
+ /* REPL_227_D "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d). */
+ snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ), REPL_227_D,
+ ( unsigned )ulIP >> 24,
+ ( unsigned )( ulIP >> 16 ) & 0xFF,
+ ( unsigned )( ulIP >> 8 ) & 0xFF,
+ ( unsigned )ulIP & 0xFF,
+ ( unsigned )ulPort >> 8,
+ ( unsigned )ulPort & 0xFF );
+
+ pcMyReply = pcCOMMAND_BUFFER;
+ }
+ break;
+
+ case ECMD_PORT: /* Active connection to the client. */
+ /* The client uses this command to tell the server to what
+ client-side port the server should contact; use of this command
+ indicates an active data transfer. e.g. PORT 192,168,1,2,4,19. */
+ {
+ uint32_t ulIPAddress = 0;
+ UBaseType_t uxPort;
+
+ uxPort = prvParsePortData( pcRestCommand, &ulIPAddress );
+ FreeRTOS_printf( (" PORT %lxip:%ld\n", ulIPAddress, uxPort ) );
+
+ if( uxPort == 0u )
+ {
+ pcMyReply = REPL_501;
+ }
+ else if( prvTransferConnect( pxClient, pdFALSE ) == pdFALSE )
+ {
+ /* Call prvTransferConnect() with 'xDoListen' = false for an
+ active connect(). */
+ pcMyReply = REPL_501;
+ }
+ else
+ {
+ pxClient->usClientPort = ( uint16_t ) uxPort;
+ pxClient->ulClientIP = ulIPAddress;
+ FreeRTOS_printf( ("Client address %lxip:%lu\n", ulIPAddress, uxPort ) );
+ pcMyReply = REPL_200;
+ }
+ }
+ break;
+
+ case ECMD_CWD: /* Change current working directory. */
+ prvChangeDir( pxClient, pcRestCommand );
+ break;
+
+ case ECMD_RNFR:
+ if( pxClient->bits.bReadOnly != pdFALSE_UNSIGNED )
+ {
+ pcMyReply = REPL_553_READ_ONLY;
+ }
+ else
+ {
+ prvRenameFrom( pxClient, pcRestCommand );
+ }
+ break;
+
+ case ECMD_RNTO:
+ if( pxClient->bits.bInRename == pdFALSE_UNSIGNED )
+ {
+ pcMyReply = REPL_503; /* "503 Bad sequence of commands. */
+ }
+ else
+ {
+ prvRenameTo( pxClient, pcRestCommand );
+ }
+ break;
+
+ case ECMD_SITE: /* Set file permissions */
+ if( pxClient->bits.bReadOnly != pdFALSE_UNSIGNED )
+ {
+ pcMyReply = REPL_553_READ_ONLY;
+ }
+ else if( prvSiteCmd( pxClient, pcRestCommand ) == pdFALSE )
+ {
+ pcMyReply = REPL_202;
+ }
+ break;
+
+ case ECMD_DELE:
+ if( pxClient->bits.bReadOnly != pdFALSE_UNSIGNED )
+ {
+ pcMyReply = REPL_553_READ_ONLY;
+ }
+ else
+ {
+ prvDeleteFile( pxClient, pcRestCommand );
+ }
+ break;
+
+ case ECMD_MDTM:
+ prvSizeDateFile( pxClient, pcRestCommand, pdTRUE );
+ break;
+
+ case ECMD_SIZE:
+ if( pxClient->pxWriteHandle != NULL )
+ {
+ /* This SIZE query is probably about a file which is now being
+ received. If so, return the value of pxClient->ulRecvBytes,
+ pcRestCommand points to 'pcCommandBuffer', make it free by
+ copying it to pcNewDir. */
+
+ xMakeAbsolute( pxClient, pcNEW_DIR, sizeof( pcNEW_DIR ), pcRestCommand );
+
+ if( strcmp( pcNEW_DIR, pcRestCommand ) == 0 )
+ {
+ BaseType_t xCount;
+ for( xCount = 0; xCount < 3 && pxClient->pxWriteHandle; xCount++ )
+ {
+ prvStoreFileWork( pxClient );
+ }
+ if( pxClient->pxWriteHandle != NULL )
+ {
+ /* File being queried is still open, return number of
+ bytes received until now. */
+ snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ), "213 %lu\r\n", pxClient->ulRecvBytes );
+ pcMyReply = pcCOMMAND_BUFFER;
+ } /* otherwise, do a normal stat(). */
+ }
+ strcpy( pcRestCommand, pcNEW_DIR );
+ }
+ if( pcMyReply == NULL )
+ {
+ prvSizeDateFile( pxClient, pcRestCommand, pdFALSE );
+ }
+ break;
+ case ECMD_MKD:
+ case ECMD_RMD:
+ if( pxClient->bits.bReadOnly != pdFALSE_UNSIGNED )
+ {
+ pcMyReply = REPL_553_READ_ONLY;
+ }
+ else
+ {
+ prvMakeRemoveDir( pxClient, pcRestCommand, pxFTPCommand->ucCommandType == ECMD_RMD );
+ }
+ break;
+ case ECMD_CDUP:
+ prvChangeDir( pxClient, ".." );
+ break;
+
+ case ECMD_QUIT:
+ prvSendReply( pxClient->xSocket, REPL_221, 0 );
+ pxClient->bits.bLoggedIn = pdFALSE_UNSIGNED;
+ break;
+ case ECMD_LIST:
+ case ECMD_RETR:
+ case ECMD_STOR:
+ if( ( pxClient->xTransferSocket == FREERTOS_NO_SOCKET ) &&
+ ( ( pxFTPCommand->ucCommandType != ECMD_STOR ) ||
+ ( pxClient->bits1.bEmptyFile == pdFALSE_UNSIGNED ) ) )
+ {
+ /* Sending "425 Can't open data connection." :
+ Before receiving any of these commands, there must have been a
+ PORT or PASV command, which causes the creation of a data socket. */
+ /* There is one exception: a STOR command is received while the
+ data connection has already been closed. This is tested with the
+ 'bEmptyFile' flag. */
+ pcMyReply = REPL_425;
+ }
+ else
+ {
+ /* In case an empty file was received ( bits1.bEmptyFile ), the
+ transfer socket never delivered any data. Check if the transfer
+ socket is still open: */
+ if( pxClient->xTransferSocket != FREERTOS_NO_SOCKET )
+ {
+ prvTransferCheck( pxClient );
+ }
+ switch( pxFTPCommand->ucCommandType )
+ {
+ case ECMD_LIST:
+ prvListSendPrep( pxClient );
+ break;
+ case ECMD_RETR:
+ prvRetrieveFilePrep( pxClient, pcRestCommand );
+ break;
+ case ECMD_STOR:
+ if( pxClient->bits.bReadOnly != pdFALSE_UNSIGNED )
+ {
+ pcMyReply = REPL_553_READ_ONLY;
+ }
+ else
+ {
+ prvStoreFilePrep( pxClient, pcRestCommand );
+ if( pxClient->bits1.bEmptyFile != pdFALSE_UNSIGNED )
+ {
+ /* Although the 'xTransferSocket' is closed already,
+ call this function just for the logging. */
+ prvTransferCloseSocket( pxClient );
+
+ /* Close an empty file. */
+ prvTransferCloseFile( pxClient );
+ }
+ }
+ break;
+ }
+ }
+ break;
+
+ case ECMD_FEAT:
+ {
+ static const char pcFeatAnswer[] =
+ "211-Features:\x0a"
+ /* The MDTM command is only allowed when
+ there is support for date&time. */
+ #if( ffconfigTIME_SUPPORT != 0 )
+ " MDTM\x0a"
+ #endif
+ " REST STREAM\x0a"
+ " SIZE\x0d\x0a"
+ "211 End\x0d\x0a";
+ pcMyReply = pcFeatAnswer;
+ }
+ break;
+
+ case ECMD_UNKNOWN:
+ FreeRTOS_printf( ("ftp::processCmd: Cmd %s unknown\n", pcRestCommand ) );
+ pcMyReply = REPL_500;
+ break;
+ }
+ }
+ if( pxFTPCommand->ucCommandType != ECMD_RNFR )
+ {
+ pxClient->bits.bInRename = pdFALSE_UNSIGNED;
+ }
+
+ if( pcMyReply != NULL )
+ {
+ xResult = prvSendReply( pxClient->xSocket, pcMyReply, strlen( pcMyReply ) );
+ }
+
+ return xResult;
+}
+/*-----------------------------------------------------------*/
+
+static BaseType_t prvTransferConnect( FTPClient_t *pxClient, BaseType_t xDoListen )
+{
+Socket_t xSocket;
+BaseType_t xResult;
+
+ /* Open a socket for a data connection with the FTP client.
+ Happens after a PORT or a PASV command. */
+
+ /* Make sure the previous socket is deleted and flags reset */
+ prvTransferCloseSocket( pxClient );
+
+ pxClient->bits1.bEmptyFile = pdFALSE_UNSIGNED;
+
+ xSocket = FreeRTOS_socket( FREERTOS_AF_INET, FREERTOS_SOCK_STREAM, FREERTOS_IPPROTO_TCP );
+
+ if( ( xSocket != FREERTOS_NO_SOCKET ) && ( xSocket != FREERTOS_INVALID_SOCKET ) )
+ {
+ BaseType_t xSmallTimeout = pdMS_TO_TICKS( 100 );
+ struct freertos_sockaddr xAddress;
+
+ #if( ipconfigFTP_TX_BUFSIZE > 0 )
+ WinProperties_t xWinProps;
+ #endif
+ xAddress.sin_addr = FreeRTOS_GetIPAddress( ); /* Single NIC, currently not used */
+ xAddress.sin_port = FreeRTOS_htons( 0 ); /* Bind to any available port number */
+
+ FreeRTOS_bind( xSocket, &xAddress, sizeof( xAddress ) );
+
+ #if( ipconfigFTP_TX_BUFSIZE > 0 )
+ {
+ /* Fill in the buffer and window sizes that will be used by the
+ socket. */
+ xWinProps.lTxBufSize = ipconfigFTP_TX_BUFSIZE;
+ xWinProps.lTxWinSize = ipconfigFTP_TX_WINSIZE;
+ xWinProps.lRxBufSize = ipconfigFTP_RX_BUFSIZE;
+ xWinProps.lRxWinSize = ipconfigFTP_RX_WINSIZE;
+
+ /* Set the window and buffer sizes. */
+ FreeRTOS_setsockopt( xSocket, 0, FREERTOS_SO_WIN_PROPERTIES, ( void * ) &xWinProps, sizeof( xWinProps ) );
+ }
+ #endif
+
+ FreeRTOS_setsockopt( xSocket, 0, FREERTOS_SO_RCVTIMEO, ( void * ) &xSmallTimeout, sizeof( BaseType_t ) );
+ FreeRTOS_setsockopt( xSocket, 0, FREERTOS_SO_SNDTIMEO, ( void * ) &xSmallTimeout, sizeof( BaseType_t ) );
+
+ /* The same instance of the socket will be used for the connection and
+ data transport. */
+ if( xDoListen != pdFALSE )
+ {
+ BaseType_t xTrueValue = pdTRUE;
+ FreeRTOS_setsockopt( xSocket, 0, FREERTOS_SO_REUSE_LISTEN_SOCKET, ( void * ) &xTrueValue, sizeof( xTrueValue ) );
+ }
+ pxClient->bits1.bIsListen = xDoListen;
+ pxClient->xTransferSocket = xSocket;
+
+ if( xDoListen != pdFALSE )
+ {
+ FreeRTOS_FD_SET( xSocket, pxClient->pxParent->xSocketSet, eSELECT_EXCEPT | eSELECT_READ );
+ /* Calling FreeRTOS_listen( ) */
+ xResult = prvTransferStart( pxClient );
+ if( xResult >= 0 )
+ {
+ xResult = pdTRUE;
+ }
+ }
+ else
+ {
+ FreeRTOS_FD_SET( xSocket, pxClient->pxParent->xSocketSet, eSELECT_EXCEPT | eSELECT_READ | eSELECT_WRITE );
+ xResult = pdTRUE;
+ }
+ }
+ else
+ {
+ FreeRTOS_printf( ( "FreeRTOS_socket() failed\n" ) );
+ xResult = -pdFREERTOS_ERRNO_ENOMEM;
+ }
+
+ /* An active socket (PORT) should connect() later. */
+ return xResult;
+}
+/*-----------------------------------------------------------*/
+
+static BaseType_t prvTransferStart( FTPClient_t *pxClient )
+{
+BaseType_t xResult;
+
+ /* A transfer socket has been opened, now either call listen() for 'PASV'
+ or connect() for the 'PORT' command. */
+ if( pxClient->bits1.bIsListen != pdFALSE_UNSIGNED )
+ {
+ xResult = FreeRTOS_listen( pxClient->xTransferSocket, 1 );
+ }
+ else
+ {
+ struct freertos_sockaddr xAddress;
+
+ xAddress.sin_addr = FreeRTOS_htonl( pxClient->ulClientIP );
+ xAddress.sin_port = FreeRTOS_htons( pxClient->usClientPort );
+ /* Start an active connection for this data socket */
+ xResult = FreeRTOS_connect( pxClient->xTransferSocket, &xAddress, sizeof( xAddress ) );
+ }
+
+ return xResult;
+}
+/*-----------------------------------------------------------*/
+
+static void prvTransferCheck( FTPClient_t *pxClient )
+{
+BaseType_t xRxSize;
+
+ /* A data transfer is busy. Check if there are changes in connectedness. */
+ xRxSize = FreeRTOS_rx_size( pxClient->xTransferSocket );
+
+ if( pxClient->bits1.bClientConnected == pdFALSE_UNSIGNED )
+ {
+ /* The time to receive a small file can be so short, that we don't even
+ see that the socket gets connected and disconnected. Therefore, check
+ the sizeof of the RX buffer. */
+ {
+ struct freertos_sockaddr xAddress;
+ Socket_t xNexSocket;
+ socklen_t xSocketLength = sizeof( xAddress );
+
+ if( pxClient->bits1.bIsListen != pdFALSE_UNSIGNED )
+ {
+ xNexSocket = FreeRTOS_accept( pxClient->xTransferSocket, &xAddress, &xSocketLength);
+ if( ( ( xNexSocket != FREERTOS_NO_SOCKET ) && ( xNexSocket != FREERTOS_INVALID_SOCKET ) ) ||
+ xRxSize > 0 )
+ {
+ pxClient->bits1.bClientConnected = pdTRUE_UNSIGNED;
+ }
+ }
+ else
+ {
+ if( FreeRTOS_issocketconnected( pxClient->xTransferSocket ) > 0 ||
+ xRxSize > 0 )
+ {
+ pxClient->bits1.bClientConnected = pdTRUE_UNSIGNED;
+ }
+ }
+ if( pxClient->bits1.bClientConnected != pdFALSE_UNSIGNED )
+ {
+ pxClient->bits1.bEmptyFile = pdFALSE_UNSIGNED;
+ #if( ipconfigHAS_PRINTF != 0 )
+ {
+ struct freertos_sockaddr xRemoteAddress, xLocalAddress;
+ FreeRTOS_GetRemoteAddress( pxClient->xTransferSocket, &xRemoteAddress );
+ FreeRTOS_GetLocalAddress( pxClient->xTransferSocket, &xLocalAddress );
+ FreeRTOS_printf( ( "%s Connected from %u to %u\n",
+ pxClient->bits1.bIsListen != pdFALSE_UNSIGNED ? "PASV" : "PORT",
+ ( unsigned ) FreeRTOS_ntohs( xLocalAddress.sin_port ),
+ ( unsigned ) FreeRTOS_ntohs( xRemoteAddress.sin_port ) ) );
+ }
+ #endif /* ipconfigHAS_PRINTF */
+ FreeRTOS_FD_CLR( pxClient->xTransferSocket, pxClient->pxParent->xSocketSet, eSELECT_WRITE );
+ FreeRTOS_FD_SET( pxClient->xTransferSocket, pxClient->pxParent->xSocketSet, eSELECT_READ|eSELECT_EXCEPT );
+ }
+ }
+ }
+
+ if ( pxClient->bits1.bClientConnected != pdFALSE_UNSIGNED )
+ {
+ if( pxClient->pcConnectionAck[ 0 ] != '\0' )
+ {
+ BaseType_t xLength;
+ BaseType_t xRemotePort;
+ struct freertos_sockaddr xRemoteAddress;
+
+ FreeRTOS_GetRemoteAddress( pxClient->xTransferSocket, &xRemoteAddress );
+ xRemotePort = FreeRTOS_ntohs( xRemoteAddress.sin_port );
+
+ /* Tell on the command port 21 we have a data connection */
+ xLength = snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ),
+ pxClient->pcConnectionAck, pxClient->ulClientIP, xRemotePort );
+
+ prvSendReply( pxClient->xSocket, pcCOMMAND_BUFFER, xLength );
+ pxClient->pcConnectionAck[ 0 ] = '\0';
+ }
+
+ if( ( FreeRTOS_issocketconnected( pxClient->xTransferSocket ) == pdFALSE ) && FreeRTOS_rx_size( pxClient->xTransferSocket ) == 0 )
+ {
+ prvTransferCloseSocket( pxClient );
+ prvTransferCloseFile( pxClient );
+ }
+ }
+}
+/*-----------------------------------------------------------*/
+
+static void prvTransferCloseSocket( FTPClient_t *pxClient )
+{
+ if( pxClient->xTransferSocket != FREERTOS_NO_SOCKET )
+ {
+ /* DEBUGGING ONLY */
+ BaseType_t xRxSize = FreeRTOS_rx_size( pxClient->xTransferSocket );
+ if( xRxSize > 0 )
+ {
+ BaseType_t xRxSize2;
+ BaseType_t xStatus;
+ prvStoreFileWork( pxClient );
+ xStatus = FreeRTOS_connstatus( pxClient->xTransferSocket );
+ xRxSize2 = FreeRTOS_rx_size( pxClient->xTransferSocket );
+ FreeRTOS_printf( ( "FTP: WARNING: %s: RX size = %ld -> %ld (%s)\n",
+ FreeRTOS_GetTCPStateName( xStatus ),
+ xRxSize, xRxSize2, pxClient->pcFileName ) );
+ if( xRxSize2 > 1 )
+ {
+ return;
+ }
+
+ /* Remove compiler warnings in case FreeRTOS_printf() is not
+ defined. */
+ ( void ) xStatus;
+ }
+ }
+
+ if( ( pxClient->pxWriteHandle != NULL ) || ( pxClient->pxReadHandle != NULL ) )
+ {
+ BaseType_t xLength;
+ char pcStrBuf[ 32 ];
+
+ if( pxClient->bits1.bHadError == pdFALSE_UNSIGNED )
+ {
+ xLength = snprintf( pxClient->pcClientAck, sizeof( pxClient->pcClientAck ),
+ "226 Closing connection %d bytes transmitted\r\n", ( int ) pxClient->ulRecvBytes );
+ }
+ else
+ {
+ xLength = snprintf( pxClient->pcClientAck, sizeof( pxClient->pcClientAck ),
+ "451 Requested action aborted after %d bytes\r\n", ( int ) pxClient->ulRecvBytes );
+ }
+
+ /* Tell on the command socket the data connection is now closed. */
+ prvSendReply( pxClient->xSocket, pxClient->pcClientAck, xLength );
+
+ #if( ipconfigHAS_PRINTF != 0 )
+ {
+ TickType_t xDelta;
+ uint32_t ulAverage;
+ xDelta = xTaskGetTickCount( ) - pxClient->xStartTime;
+ ulAverage = ulGetAverage( pxClient->ulRecvBytes, xDelta );
+
+ FreeRTOS_printf( ("FTP: %s: '%s' %lu Bytes (%s/sec)\n",
+ pxClient->pxReadHandle ? "sent" : "recv",
+ pxClient->pcFileName,
+ pxClient->ulRecvBytes,
+ pcMkSize( ulAverage, pcStrBuf, sizeof( pcStrBuf ) ) ) );
+ }
+ #endif
+ }
+
+ if( pxClient->xTransferSocket != FREERTOS_NO_SOCKET )
+ {
+ FreeRTOS_FD_CLR( pxClient->xTransferSocket, pxClient->pxParent->xSocketSet, eSELECT_ALL );
+ FreeRTOS_closesocket( pxClient->xTransferSocket );
+ pxClient->xTransferSocket = FREERTOS_NO_SOCKET;
+ if( pxClient->ulRecvBytes == 0ul )
+ {
+ /* Received zero bytes: an empty file */
+ pxClient->bits1.bEmptyFile = pdTRUE_UNSIGNED;
+ }
+ else
+ {
+ pxClient->bits1.bEmptyFile = pdFALSE_UNSIGNED;
+ }
+ }
+ pxClient->bits1.bIsListen = pdFALSE_UNSIGNED;
+ pxClient->bits1.bDirHasEntry = pdFALSE_UNSIGNED;
+ pxClient->bits1.bClientConnected = pdFALSE_UNSIGNED;
+ pxClient->bits1.bHadError = pdFALSE_UNSIGNED;
+}
+/*-----------------------------------------------------------*/
+
+static void prvTransferCloseFile( FTPClient_t *pxClient )
+{
+ if( pxClient->pxWriteHandle != NULL )
+ {
+ ff_fclose( pxClient->pxWriteHandle );
+ pxClient->pxWriteHandle = NULL;
+ #if( ipconfigFTP_HAS_RECEIVED_HOOK != 0 )
+ {
+ vApplicationFTPReceivedHook( pxClient->pcFileName, pxClient->ulRecvBytes, pxClient );
+ }
+ #endif
+
+ }
+ if( pxClient->pxReadHandle != NULL )
+ {
+ ff_fclose( pxClient->pxReadHandle );
+ pxClient->pxReadHandle = NULL;
+ }
+ /* These two field are only used for logging / file-statistics */
+ pxClient->ulRecvBytes = 0ul;
+ pxClient->xStartTime = 0ul;
+}
+/*-----------------------------------------------------------*/
+
+/**
+ * Guess the transfer type, given the client requested type.
+ * Actually in unix there is no difference between binary and
+ * ascii mode when we work with file descriptors.
+ * If #type is not recognized as a valid client request, -1 is returned.
+ */
+static BaseType_t prvGetTransferType( const char *pcType )
+{
+BaseType_t xResult = -1;
+
+ if( pcType != NULL )
+ {
+ BaseType_t xLength = strlen( pcType );
+ if( xLength == 0 )
+ {
+ return -1;
+ }
+ switch( pcType[ 0 ] ) {
+ case 'I':
+ xResult = TMODE_BINARY;
+ break;
+ case 'A':
+ xResult = TMODE_ASCII;
+ break;
+ case 'L':
+ if( xLength >= 3 )
+ {
+ if( pcType[ 2 ] == '7' )
+ {
+ xResult = TMODE_7BITS;
+ }
+ else if( pcType[ 2 ] == '8' )
+ {
+ xResult = TMODE_7BITS;
+ }
+ }
+ break;
+ }
+ }
+ return xResult;
+}
+/*-----------------------------------------------------------*/
+
+#if( ipconfigHAS_PRINTF != 0 )
+ #define SIZE_1_GB ( 1024ul * 1024ul * 1024ul )
+ #define SIZE_1_MB ( 1024ul * 1024ul )
+ #define SIZE_1_KB ( 1024ul )
+
+ static const char *pcMkSize( uint32_t ulAmount, char *pcBuffer, BaseType_t xBufferSize )
+ {
+ uint32_t ulGB, ulMB, ulKB, ulByte;
+
+ ulGB = ( ulAmount / SIZE_1_GB );
+ ulAmount -= ( ulGB * SIZE_1_GB );
+ ulMB = ( ulAmount / SIZE_1_MB );
+ ulAmount -= ( ulMB * SIZE_1_MB );
+ ulKB = ( ulAmount / SIZE_1_KB );
+ ulAmount -= ( ulKB * SIZE_1_KB );
+ ulByte = ( ulAmount );
+
+ if (ulGB != 0ul )
+ {
+ snprintf( pcBuffer, xBufferSize, "%lu.%02lu GB", ulGB, (100 * ulMB) / SIZE_1_KB );
+ }
+ else if( ulMB != 0ul )
+ {
+ snprintf( pcBuffer, xBufferSize, "%lu.%02lu MB", ulMB, (100 * ulKB) / SIZE_1_KB );
+ }
+ else if( ulKB != 0ul )
+ {
+ snprintf(pcBuffer, xBufferSize, "%lu.%02lu KB", ulKB, (100 * ulByte) / SIZE_1_KB );
+ }
+ else
+ {
+ snprintf( pcBuffer, xBufferSize, "%lu bytes", ulByte );
+ }
+
+ return pcBuffer;
+ }
+ /*-----------------------------------------------------------*/
+#endif /* ipconfigHAS_PRINTF != 0 */
+
+#if( ipconfigHAS_PRINTF != 0 )
+ static uint32_t ulGetAverage( uint32_t ulAmount, TickType_t xDeltaMs )
+ {
+ uint32_t ulAverage;
+
+ /* Get the average amount of bytes per seconds. Ideally this is
+ calculated by Multiplying with 1000 and dividing by milliseconds:
+ ulAverage = ( 1000ul * ulAmount ) / xDeltaMs;
+ Now get a maximum precision, while avoiding an arithmetic overflow:
+ */
+ if( xDeltaMs == 0ul )
+ {
+ /* Time is zero, there is no average */
+ ulAverage = 0ul;
+ }
+ else if( ulAmount >= ( ~0ul / 10ul ) )
+ {
+ /* More than 409 MB has been transferred, do not multiply. */
+ ulAverage = ( ulAmount / ( xDeltaMs / 1000ul ) );
+ }
+ else if( ulAmount >= ( ~0ul / 100ul ) )
+ {
+ /* Between 409 and 41 MB has been transferred, can multiply by 10. */
+ ulAverage = ( ( ulAmount * 10ul ) / ( xDeltaMs / 100ul ) );
+ }
+ else if( ulAmount >= ( ~0ul / 1000ul ) )
+ {
+ /* Between 4.1 MB and 41 has been transferred, can multiply by 100. */
+ ulAverage = ( ( ulAmount * 100ul ) / ( xDeltaMs / 10ul ) );
+ }
+ else
+ {
+ /* Less than 4.1 MB: can multiply by 1000. */
+ ulAverage = ( ( ulAmount * 1000ul ) / xDeltaMs );
+ }
+
+ return ulAverage;
+ }
+ /*-----------------------------------------------------------*/
+#endif /* ipconfigHAS_PRINTF != 0 */
+
+static UBaseType_t prvParsePortData( const char *pcCommand, uint32_t *pulIPAddress )
+{
+/*_HT_ Using 'unsigned' here because when sscanf() sees '%u', it expects a pointer to 'unsigned'.
+Not sure about the sscanf() format for UBaseType_t ? */
+unsigned h1, h2, h3, h4, p1, p2;
+char sep;
+UBaseType_t uxResult;
+
+ /* Expect PORT h1,h2,h3,h4,p1,p2 */
+ if (sscanf (pcCommand, "%u%c%u%c%u%c%u%c%u%c%u", &h1, &sep, &h2, &sep, &h3, &sep, &h4, &sep, &p1, &sep, &p2) != 11)
+ {
+ uxResult= 0u;
+ }
+ else
+ {
+ /* Put in network byte order. */
+ *pulIPAddress =
+ ( ( uint32_t ) h1 << 24 ) |
+ ( ( uint32_t ) h2 << 16 ) |
+ ( ( uint32_t ) h3 << 8 ) |
+ ( ( uint32_t ) h4 );
+ uxResult = ( p1 << 8 ) | p2;
+ }
+ return uxResult;
+}
+/*-----------------------------------------------------------*/
+
+/*
+
+ #### ####### # ###
+# # # # ## # #
+# # # # # #
+# ###### #### ### ## #### # # ### # ####
+ ## # # # # # # # # ##### # # # #
+ ## # # # ## # ###### # # # # ######
+# # # # # # # # # # #
+# # # ## # # # # ## # # # # ##
+ #### ## #### #### #### #### ##### ##### ####
+
+*/
+
+static BaseType_t prvStoreFilePrep( FTPClient_t *pxClient, char *pcFileName )
+{
+BaseType_t xResult;
+FF_FILE *pxNewHandle;
+size_t uxFileSize = 0ul;
+int iErrorNo;
+
+ /* Close previous handle (if any) and reset file transfer parameters. */
+ prvTransferCloseFile( pxClient );
+
+ xMakeAbsolute( pxClient, pxClient->pcFileName, sizeof( pxClient->pcFileName ), pcFileName );
+
+ pxNewHandle = NULL;
+
+ if( pxClient->ulRestartOffset != 0 )
+ {
+ size_t uxOffset = pxClient->ulRestartOffset;
+ int32_t lRc;
+
+ pxClient->ulRestartOffset = 0ul; /* Only use 1 time. */
+ pxNewHandle = ff_fopen( pxClient->pcFileName, "ab" );
+
+ if( pxNewHandle != NULL )
+ {
+ uxFileSize = pxNewHandle->ulFileSize;
+
+ if( uxOffset <= uxFileSize )
+ {
+ lRc = ff_fseek( pxNewHandle, uxOffset, FF_SEEK_SET );
+ }
+ else
+ {
+ /* Won't even try to seek after EOF */
+ lRc = -pdFREERTOS_ERRNO_EINVAL;
+ }
+ if( lRc != 0 )
+ {
+ BaseType_t xLength;
+
+ xLength = snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ),
+ "450 Seek invalid %u length %u\r\n",
+ ( unsigned ) uxOffset, ( unsigned ) uxFileSize );
+
+ /* "Requested file action not taken". */
+ prvSendReply( pxClient->xSocket, pcCOMMAND_BUFFER, xLength );
+
+ FreeRTOS_printf( ( "ftp::storeFile: create %s: Seek %u length %u\n",
+ pxClient->pcFileName, ( unsigned ) uxOffset, ( unsigned ) uxFileSize ) );
+
+ ff_fclose( pxNewHandle );
+ pxNewHandle = NULL;
+ }
+ }
+ }
+ else
+ {
+ pxNewHandle = ff_fopen( pxClient->pcFileName, "wb" );
+ }
+
+ if( pxNewHandle == NULL )
+ {
+ iErrorNo = stdioGET_ERRNO();
+ if( iErrorNo == pdFREERTOS_ERRNO_ENOSPC )
+ {
+ prvSendReply( pxClient->xSocket, REPL_552, 0 );
+ }
+ else
+ {
+ /* "Requested file action not taken". */
+ prvSendReply( pxClient->xSocket, REPL_450, 0 );
+ }
+ FreeRTOS_printf( ( "ftp::storeFile: create %s: %s (errno %d)\n",
+ pxClient->pcFileName,
+ ( const char* ) strerror( iErrorNo ), iErrorNo ) );
+
+ xResult = pdFALSE;
+ }
+ else
+ {
+ if( pxClient->bits1.bIsListen )
+ {
+ /* True if PASV is used. */
+ snprintf( pxClient->pcConnectionAck, sizeof( pxClient->pcConnectionAck ),
+ "150 Accepted data connection from %%xip:%%u\r\n" );
+ prvTransferCheck( pxClient );
+ }
+ else
+ {
+ BaseType_t xLength;
+
+ xLength = snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ), "150 Opening BIN connection to store file\r\n" );
+ prvSendReply( pxClient->xSocket, pcCOMMAND_BUFFER, xLength );
+ pxClient->pcConnectionAck[ 0 ] = '\0';
+ prvTransferStart( pxClient ); /* Now active connect. */
+ }
+
+ pxClient->pxWriteHandle = pxNewHandle;
+
+ /* To get some statistics about the performance. */
+ pxClient->xStartTime = xTaskGetTickCount( );
+
+ xResult = pdTRUE;
+ }
+
+ return xResult;
+}
+/*-----------------------------------------------------------*/
+
+#if( ipconfigFTP_ZERO_COPY_ALIGNED_WRITES == 0 )
+
+ static BaseType_t prvStoreFileWork( FTPClient_t *pxClient )
+ {
+ BaseType_t xRc, xWritten;
+
+ /* Read from the data socket until all has been read or until a negative value
+ is returned. */
+ for( ; ; )
+ {
+ char *pcBuffer;
+
+ /* The "zero-copy" method: */
+ xRc = FreeRTOS_recv( pxClient->xTransferSocket, ( void * ) &pcBuffer,
+ 0x20000u, FREERTOS_ZERO_COPY | FREERTOS_MSG_DONTWAIT );
+ if( xRc <= 0 )
+ {
+ break;
+ }
+ pxClient->ulRecvBytes += xRc;
+ xWritten = ff_fwrite( pcBuffer, 1, xRc, pxClient->pxWriteHandle );
+ FreeRTOS_recv( pxClient->xTransferSocket, ( void * ) NULL, xRc, 0 );
+ if( xWritten != xRc )
+ {
+ xRc = -1;
+ /* bHadError: a transfer got aborted because of an error. */
+ pxClient->bits1.bHadError = pdTRUE_UNSIGNED;
+ break;
+ }
+ }
+ return xRc;
+ }
+
+#else /* ipconfigFTP_ZERO_COPY_ALIGNED_WRITES != 0 */
+
+ #if !defined( ipconfigFTP_PREFERRED_WRITE_SIZE )
+ /* If you store data on flash, it may be profitable to give 'ipconfigFTP_PREFERRED_WRITE_SIZE'
+ the same size as the size of the flash' erase blocks, e.g. 4KB */
+ #define ipconfigFTP_PREFERRED_WRITE_SIZE 512ul
+ #endif
+
+ static BaseType_t prvStoreFileWork( FTPClient_t *pxClient )
+ {
+ BaseType_t xRc, xWritten;
+
+ /* Read from the data socket until all has been read or until a negative
+ value is returned. */
+ for( ; ; )
+ {
+ char *pcBuffer;
+ UBaseType_t xStatus;
+
+ /* The "zero-copy" method: */
+ xRc = FreeRTOS_recv( pxClient->xTransferSocket, ( void * ) &pcBuffer,
+ 0x20000u, FREERTOS_ZERO_COPY | FREERTOS_MSG_DONTWAIT );
+
+ if( xRc <= 0 )
+ {
+ /* There are no data or the connection is closed. */
+ break;
+ }
+ xStatus = FreeRTOS_connstatus( pxClient->xTransferSocket );
+ if( xStatus != eESTABLISHED )
+ {
+ /* The connection is not established (any more), therefore
+ accept any amount of bytes, probably the last few bytes. */
+ }
+ else
+ {
+ if( xRc >= ipconfigFTP_PREFERRED_WRITE_SIZE )
+ {
+ /* More than a sector to write, round down to a multiple of
+ PREFERRED_WRITE_SIZE bytes. */
+ xRc = ( xRc / ipconfigFTP_PREFERRED_WRITE_SIZE ) * ipconfigFTP_PREFERRED_WRITE_SIZE;
+ }
+ else
+ {
+ const StreamBuffer_t *pxBuffer = FreeRTOS_get_rx_buf( pxClient->xTransferSocket );
+ size_t uxSpace = pxBuffer->LENGTH - pxBuffer->uxTail;
+
+ if( uxSpace >= ipconfigFTP_PREFERRED_WRITE_SIZE )
+ {
+ /* At this moment there are les than PREFERRED_WRITE_SIZE bytes in the RX
+ buffer, but there is space for more. Just return and
+ wait for more. */
+ xRc = 0;
+ }
+ else
+ {
+ /* Now reading beyond the end of the circular buffer,
+ use a normal read. */
+ pcBuffer = pcFILE_BUFFER;
+ xRc = FreeRTOS_recvcount( pxClient->xTransferSocket );
+ xRc = ( xRc / ipconfigFTP_PREFERRED_WRITE_SIZE ) * ipconfigFTP_PREFERRED_WRITE_SIZE;
+ if( xRc > 0 )
+ {
+ xRc = FreeRTOS_recv( pxClient->xTransferSocket, ( void * ) pcBuffer,
+ sizeof( pcFILE_BUFFER ), FREERTOS_MSG_DONTWAIT );
+ }
+ }
+ }
+ }
+ if( xRc == 0 )
+ {
+ break;
+ }
+ pxClient->ulRecvBytes += xRc;
+
+ xWritten = ff_fwrite( pcBuffer, 1, xRc, pxClient->pxWriteHandle );
+ if( pcBuffer != pcFILE_BUFFER )
+ {
+ FreeRTOS_recv( pxClient->xTransferSocket, ( void * ) NULL, xRc, 0 );
+ }
+ if( xWritten != xRc )
+ {
+ xRc = -1;
+ /* bHadError: a transfer got aborted because of an error. */
+ pxClient->bits1.bHadError = pdTRUE_UNSIGNED;
+ break;
+ }
+ }
+ return xRc;
+ }
+
+#endif /* ipconfigFTP_ZERO_COPY_ALIGNED_WRITES */
+/*-----------------------------------------------------------*/
+
+/*
+###### # ####### # ###
+ # # # # # ## # #
+ # # # # # #
+ # # #### ###### ### ## ### #### # # #### # # ### # ####
+ ###### # # # # # # # # # # # # # ##### # # # #
+ # ## ###### # ## # # ###### # # ###### # # # # ######
+ # # # # # # # # # # # # # #
+ # # # ## # ## # # # ## # # # ## # # # # ##
+### ## #### ## #### ##### #### ## #### #### ##### ##### ####
+*/
+static BaseType_t prvRetrieveFilePrep( FTPClient_t *pxClient, char *pcFileName )
+{
+BaseType_t xResult = pdTRUE;
+size_t uxFileSize;
+
+ /* Close previous handle (if any) and reset file transfer parameters */
+ prvTransferCloseFile( pxClient );
+
+ xMakeAbsolute( pxClient, pxClient->pcFileName, sizeof( pxClient->pcFileName ), pcFileName );
+
+ pxClient->pxReadHandle = ff_fopen( pxClient->pcFileName, "rb" );
+ if( pxClient->pxReadHandle == NULL )
+ {
+ int iErrno = stdioGET_ERRNO();
+ /* "Requested file action not taken". */
+ prvSendReply( pxClient->xSocket, REPL_450, 0 );
+ FreeRTOS_printf( ("prvRetrieveFilePrep: open '%s': errno %d: %s\n",
+ pxClient->pcFileName, iErrno, ( const char * ) strerror( iErrno ) ) );
+ uxFileSize = 0ul;
+ xResult = pdFALSE;
+ }
+ else
+ {
+ uxFileSize = pxClient->pxReadHandle->ulFileSize;
+ pxClient->uxBytesLeft = uxFileSize;
+ if( pxClient->ulRestartOffset != 0ul )
+ {
+ size_t uxOffset = pxClient->ulRestartOffset;
+ int32_t iRc;
+
+ /* Only use 1 time. */
+ pxClient->ulRestartOffset = 0;
+
+ if( uxOffset < uxFileSize )
+ {
+ iRc = ff_fseek( pxClient->pxReadHandle, uxOffset, FF_SEEK_SET );
+ }
+ else
+ {
+ iRc = -pdFREERTOS_ERRNO_EINVAL;
+ }
+ if( iRc != 0 )
+ {
+ BaseType_t xLength;
+
+ xLength = snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ),
+ "450 Seek invalid %u length %u\r\n", ( unsigned ) uxOffset, ( unsigned ) uxFileSize );
+
+ /* "Requested file action not taken". */
+ prvSendReply( pxClient->xSocket, pcCOMMAND_BUFFER, xLength );
+
+ FreeRTOS_printf( ( "prvRetrieveFilePrep: create %s: Seek %u length %u\n",
+ pxClient->pcFileName, ( unsigned ) uxOffset, ( unsigned ) uxFileSize ) );
+
+ ff_fclose( pxClient->pxReadHandle );
+ pxClient->pxReadHandle = NULL;
+ xResult = pdFALSE;
+ }
+ else
+ {
+ pxClient->uxBytesLeft = uxFileSize - pxClient->ulRestartOffset;
+ }
+ }
+ }
+ if( xResult != pdFALSE )
+ {
+ if( pxClient->bits1.bIsListen != pdFALSE_UNSIGNED )
+ {
+ /* True if PASV is used. */
+ snprintf( pxClient->pcConnectionAck, sizeof( pxClient->pcConnectionAck ),
+ "150%cAccepted data connection from %%xip:%%u\r\n%s",
+ pxClient->xTransType == TMODE_ASCII ? '-' : ' ',
+ pxClient->xTransType == TMODE_ASCII ? "150 NOTE: ASCII mode requested, but binary mode used\r\n" : "" );
+ } else {
+ BaseType_t xLength;
+
+ xLength = snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ), "150%cOpening data connection to %lxip:%u\r\n%s",
+ pxClient->xTransType == TMODE_ASCII ? '-' : ' ',
+ pxClient->ulClientIP,
+ pxClient->usClientPort,
+ pxClient->xTransType == TMODE_ASCII ? "150 NOTE: ASCII mode requested, but binary mode used\r\n" : "" );
+ prvSendReply( pxClient->xSocket, pcCOMMAND_BUFFER, xLength );
+ pxClient->pcConnectionAck[ 0 ] = '\0';
+ prvTransferStart( pxClient );
+ }
+
+ /* Prepare the ACK which will be sent when all data has been sent. */
+ snprintf( pxClient->pcClientAck, sizeof( pxClient->pcClientAck ), "%s", REPL_226 );
+
+ /* To get some statistics about the performance. */
+ pxClient->xStartTime = xTaskGetTickCount( );
+ if( uxFileSize == 0ul )
+ {
+ FreeRTOS_shutdown( pxClient->xTransferSocket, FREERTOS_SHUT_RDWR );
+ }
+ }
+
+ return xResult;
+}
+/*-----------------------------------------------------------*/
+
+static BaseType_t prvRetrieveFileWork( FTPClient_t *pxClient )
+{
+size_t uxSpace;
+size_t uxCount, uxItemsRead;
+BaseType_t xRc = 0;
+BaseType_t xSetEvent = pdFALSE;
+
+ do
+ {
+ #if( ipconfigFTP_TX_ZERO_COPY != 0 )
+ char *pcBuffer;
+ BaseType_t xBufferLength;
+ #endif /* ipconfigFTP_TX_ZERO_COPY */
+
+ /* Take the lesser of the two: tx_space (number of bytes that can be
+ queued for transmission) and uxBytesLeft (the number of bytes left to
+ read from the file) */
+ uxSpace = FreeRTOS_tx_space( pxClient->xTransferSocket );
+
+ if( uxSpace == 0 )
+ {
+ FreeRTOS_FD_SET( pxClient->xTransferSocket, pxClient->pxParent->xSocketSet, eSELECT_WRITE | eSELECT_EXCEPT );
+ xRc = FreeRTOS_select( pxClient->pxParent->xSocketSet, 200 );
+ uxSpace = FreeRTOS_tx_space( pxClient->xTransferSocket );
+ }
+
+ uxCount = FreeRTOS_min_uint32( pxClient->uxBytesLeft, uxSpace );
+
+ if( uxCount == 0 )
+ {
+ break;
+ }
+
+ #if( ipconfigFTP_TX_ZERO_COPY == 0 )
+ {
+ if( uxCount > sizeof( pcFILE_BUFFER ) )
+ {
+ uxCount = sizeof( pcFILE_BUFFER );
+ }
+ uxItemsRead = ff_fread( pcFILE_BUFFER, 1, uxCount, pxClient->pxReadHandle );
+ if( uxItemsRead != uxCount )
+ {
+ FreeRTOS_printf( ( "prvRetrieveFileWork: Got %u Expected %u\n", ( unsigned )uxItemsRead, ( unsigned ) uxCount ) );
+ xRc = FreeRTOS_shutdown( pxClient->xTransferSocket, FREERTOS_SHUT_RDWR );
+ pxClient->uxBytesLeft = 0u;
+ break;
+ }
+ pxClient->uxBytesLeft -= uxCount;
+
+ if( pxClient->uxBytesLeft == 0u )
+ {
+ BaseType_t xTrueValue = 1;
+
+ FreeRTOS_setsockopt( pxClient->xTransferSocket, 0, FREERTOS_SO_CLOSE_AFTER_SEND, ( void * ) &xTrueValue, sizeof( xTrueValue ) );
+ }
+
+ xRc = FreeRTOS_send( pxClient->xTransferSocket, pcFILE_BUFFER, uxCount, 0 );
+ }
+ #else /* ipconfigFTP_TX_ZERO_COPY != 0 */
+ {
+ /* Use zero-copy transmission:
+ FreeRTOS_get_tx_head() returns a direct pointer to the TX stream and
+ set xBufferLength to know how much space there is left. */
+ pcBuffer = ( char * )FreeRTOS_get_tx_head( pxClient->xTransferSocket, &xBufferLength );
+ if( ( pcBuffer != NULL ) && ( xBufferLength >= 512 ) )
+ {
+ /* Will read disk data directly to the TX stream of the socket. */
+ uxCount = FreeRTOS_min_uint32( uxCount, ( uint32_t )xBufferLength );
+ if( uxCount > ( size_t ) 0x40000u )
+ {
+ uxCount = ( size_t ) 0x40000u;
+ }
+ }
+ else
+ {
+ /* Use the normal file i/o buffer. */
+ pcBuffer = pcFILE_BUFFER;
+ if( uxCount > sizeof( pcFILE_BUFFER ) )
+ {
+ uxCount = sizeof( pcFILE_BUFFER );
+ }
+ }
+
+ if ( pxClient->uxBytesLeft >= 1024u )
+ {
+ uxCount &= ~( ( size_t ) 512u - 1u );
+ }
+
+ if( uxCount <= 0u )
+ {
+ /* Nothing to send after rounding down to a multiple of a sector size. */
+ break;
+ }
+
+ uxItemsRead = ff_fread( pcBuffer, 1, uxCount, pxClient->pxReadHandle );
+
+ if( uxCount != uxItemsRead )
+ {
+ FreeRTOS_printf( ( "prvRetrieveFileWork: Got %u Expected %u\n", ( unsigned )uxItemsRead, ( unsigned )uxCount ) );
+ xRc = FreeRTOS_shutdown( pxClient->xTransferSocket, FREERTOS_SHUT_RDWR );
+ pxClient->uxBytesLeft = 0u;
+ break;
+ }
+ pxClient->uxBytesLeft -= uxCount;
+
+ if( pxClient->uxBytesLeft == 0u )
+ {
+ BaseType_t xTrueValue = 1;
+
+ FreeRTOS_setsockopt( pxClient->xTransferSocket, 0, FREERTOS_SO_CLOSE_AFTER_SEND, ( void * ) &xTrueValue, sizeof( xTrueValue ) );
+ }
+ if( pcBuffer != pcFILE_BUFFER )
+ {
+ pcBuffer = NULL;
+ }
+ xRc = FreeRTOS_send( pxClient->xTransferSocket, pcBuffer, uxCount, 0 );
+ }
+ #endif /* ipconfigFTP_TX_ZERO_COPY */
+
+ if( xRc < 0 )
+ {
+ break;
+ }
+
+ pxClient->ulRecvBytes += xRc;
+ if( pxClient->uxBytesLeft == 0u )
+ {
+ break;
+ }
+ } while( uxCount > 0u );
+
+ if( xRc < 0 )
+ {
+ FreeRTOS_printf( ( "prvRetrieveFileWork: already disconnected\n" ) );
+ }
+ else if( pxClient->uxBytesLeft <= 0u )
+ {
+ BaseType_t x;
+
+ for( x = 0; x < 5; x++ )
+ {
+ xRc = FreeRTOS_recv( pxClient->xTransferSocket, pcFILE_BUFFER, sizeof( pcFILE_BUFFER ), 0 );
+ if( xRc < 0 )
+ {
+ break;
+ }
+ }
+// FreeRTOS_printf( ( "prvRetrieveFileWork: %s all sent: xRc %ld\n", pxClient->pcFileName, xRc ) );
+ }
+ else
+ {
+ FreeRTOS_FD_SET( pxClient->xTransferSocket, pxClient->pxParent->xSocketSet, eSELECT_WRITE );
+ xSetEvent = pdTRUE;
+ }
+ if( xSetEvent == pdFALSE )
+ {
+ FreeRTOS_FD_CLR( pxClient->xTransferSocket, pxClient->pxParent->xSocketSet, eSELECT_WRITE );
+ }
+ return xRc;
+}
+/*-----------------------------------------------------------*/
+
+/*
+### ##### #### #####
+ # # # # # # #
+ # # # # #
+ # # # #
+ # # ## #
+ # # # ## #
+ # # # # # #
+ # # # # # #
+####### ##### #### ####
+*/
+/* Prepare sending a directory LIST */
+static BaseType_t prvListSendPrep( FTPClient_t *pxClient )
+{
+BaseType_t xFindResult;
+int iErrorNo;
+
+ if( pxClient->bits1.bIsListen != pdFALSE_UNSIGNED )
+ {
+ /* True if PASV is used */
+ snprintf( pxClient->pcConnectionAck, sizeof( pxClient->pcConnectionAck ),
+ "150 Accepted data connection from %%xip:%%u\r\n" );
+ }
+ else
+ {
+ BaseType_t xLength;
+
+ /* Here the FTP server is supposed to connect() */
+ xLength = snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ),
+ "150 Opening ASCII mode data connection to for /bin/ls \r\n" );
+
+ prvSendReply( pxClient->xSocket, pcCOMMAND_BUFFER, xLength );
+ /* Clear the current connection acknowledge message */
+ pxClient->pcConnectionAck[ 0 ] = '\0';
+ prvTransferStart( pxClient );
+ }
+
+ pxClient->xDirCount = 0;
+ xMakeAbsolute( pxClient, pcNEW_DIR, sizeof( pcNEW_DIR ), pxClient->pcCurrentDir );
+
+ xFindResult = ff_findfirst( pcNEW_DIR, &pxClient->xFindData );
+
+ pxClient->bits1.bDirHasEntry = ( xFindResult >= 0 );
+
+ iErrorNo = stdioGET_ERRNO();
+ if( ( xFindResult < 0 ) && ( iErrorNo == pdFREERTOS_ERRNO_ENMFILE ) )
+ {
+ FreeRTOS_printf( ("prvListSendPrep: Empty directory? (%s)\n", pxClient->pcCurrentDir ) );
+ prvSendReply( pxClient->xTransferSocket, "total 0\r\n", 0 );
+ pxClient->xDirCount++;
+ }
+ else if( xFindResult < 0 )
+ {
+ FreeRTOS_printf( ( "prvListSendPrep: rc = %ld iErrorNo = %d\n", xFindResult, iErrorNo ) );
+ prvSendReply( pxClient->xSocket, REPL_451, 0 );
+ }
+ pxClient->pcClientAck[ 0 ] = '\0';
+
+ return pxClient->xDirCount;
+}
+/*-----------------------------------------------------------*/
+
+#define MAX_DIR_LIST_ENTRY_SIZE 256
+
+static BaseType_t prvListSendWork( FTPClient_t *pxClient )
+{
+BaseType_t xTxSpace;
+
+ while( pxClient->bits1.bClientConnected != pdFALSE_UNSIGNED )
+ {
+ char *pcWritePtr = pcCOMMAND_BUFFER;
+ BaseType_t xWriteLength;
+
+ xTxSpace = FreeRTOS_tx_space( pxClient->xTransferSocket );
+
+ if( xTxSpace > ( BaseType_t ) sizeof( pcCOMMAND_BUFFER ) )
+ {
+ xTxSpace = sizeof( pcCOMMAND_BUFFER );
+ }
+
+ while( ( xTxSpace >= MAX_DIR_LIST_ENTRY_SIZE ) && ( pxClient->bits1.bDirHasEntry != pdFALSE_UNSIGNED ) )
+ {
+ BaseType_t xLength, xEndOfDir;
+ int32_t iRc;
+ int iErrorNo;
+
+ xLength = prvGetFileInfoStat( &( pxClient->xFindData.xDirectoryEntry ), pcWritePtr, xTxSpace );
+
+ pxClient->xDirCount++;
+ pcWritePtr += xLength;
+ xTxSpace -= xLength;
+
+ iRc = ff_findnext( &pxClient->xFindData );
+ iErrorNo = stdioGET_ERRNO();
+
+ xEndOfDir = ( iRc < 0 ) && ( iErrorNo == pdFREERTOS_ERRNO_ENMFILE );
+
+ pxClient->bits1.bDirHasEntry = ( xEndOfDir == pdFALSE ) && ( iRc >= 0 );
+
+ if( ( iRc < 0 ) && ( xEndOfDir == pdFALSE ) )
+ {
+ FreeRTOS_printf( ("prvListSendWork: %s (rc %08x)\n",
+ ( const char * ) strerror( iErrorNo ),
+ ( unsigned )iRc ) );
+ }
+ }
+ xWriteLength = ( BaseType_t ) ( pcWritePtr - pcCOMMAND_BUFFER );
+
+ if( xWriteLength == 0 )
+ {
+ break;
+ }
+
+ if( pxClient->bits1.bDirHasEntry == pdFALSE_UNSIGNED )
+ {
+ uint32_t ulTotalCount;
+ uint32_t ulFreeCount;
+ uint32_t ulPercentage;
+
+ ulTotalCount = 1;
+ ulFreeCount = ff_diskfree( pxClient->pcCurrentDir, &ulTotalCount );
+ ulPercentage = ( uint32_t ) ( ( 100ULL * ulFreeCount + ulTotalCount / 2 ) / ulTotalCount );
+
+ /* Prepare the ACK which will be sent when all data has been sent. */
+ snprintf( pxClient->pcClientAck, sizeof( pxClient->pcClientAck ),
+ "226-Options: -l\r\n"
+ "226-%ld matches total\r\n"
+ "226 Total %lu KB (%lu %% free)\r\n",
+ pxClient->xDirCount, ulTotalCount /1024, ulPercentage );
+ }
+
+ if( xWriteLength )
+ {
+ if( pxClient->bits1.bDirHasEntry == pdFALSE_UNSIGNED )
+ {
+ BaseType_t xTrueValue = 1;
+
+ FreeRTOS_setsockopt( pxClient->xTransferSocket, 0, FREERTOS_SO_CLOSE_AFTER_SEND, ( void * ) &xTrueValue, sizeof( xTrueValue ) );
+ }
+
+ prvSendReply( pxClient->xTransferSocket, pcCOMMAND_BUFFER, xWriteLength );
+ }
+
+ if( pxClient->bits1.bDirHasEntry == pdFALSE_UNSIGNED )
+ {
+ prvSendReply( pxClient->xSocket, pxClient->pcClientAck, 0 );
+ break;
+ }
+
+ } /* while( pxClient->bits1.bClientConnected ) */
+
+ return 0;
+}
+/*-----------------------------------------------------------*/
+
+static const char *pcMonthAbbrev( BaseType_t xMonth )
+{
+static const char pcMonthList[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
+
+ if( xMonth < 1 || xMonth > 12 )
+ xMonth = 12;
+
+ return pcMonthList + 3 * ( xMonth - 1 );
+};
+/*-----------------------------------------------------------*/
+
+static BaseType_t prvGetFileInfoStat( FF_DirEnt_t *pxEntry, char *pcLine, BaseType_t xMaxLength )
+{
+ char date[ 16 ];
+ char mode[ 11 ] = "----------";
+ BaseType_t st_nlink = 1;
+ const char user[ 9 ] = "freertos";
+ const char group[ 8 ] = "plusfat";
+
+/*
+ * Creates a unix-style listing, understood by most FTP clients:
+ *
+ * -rw-rw-r-- 1 freertos FreeRTOS+FAT 10564588 Sep 01 00:17 03. Metaharmoniks - Star (Instrumental).mp3
+ * -rw-rw-r-- 1 freertos FreeRTOS+FAT 19087839 Sep 01 00:17 04. Simon Le Grec - Dimitri (Wherever U Are) (Cosmos Mix).mp3
+ * -rw-rw-r-- 1 freertos FreeRTOS+FAT 11100621 Sep 01 00:16 05. D-Chill - Mistake (feat. Katy Blue).mp3
+ */
+
+ #if ( ffconfigTIME_SUPPORT == 1 )
+ const FF_SystemTime_t *pxCreateTime = &( pxEntry->xCreateTime );
+ #else
+ #warning Do not use this.
+ FF_SystemTime_t xCreateTime;
+ const FF_SystemTime_t *pxCreateTime = &xCreateTime;
+ #endif
+ size_t ulSize = ( size_t )pxEntry->ulFileSize;
+ const char *pcFileName = pxEntry->pcFileName;
+
+ mode[ 0 ] = ( ( pxEntry->ucAttrib & FF_FAT_ATTR_DIR ) != 0 ) ? 'd' : '-';
+ #if( ffconfigDEV_SUPPORT != 0 )
+ {
+ if( ( pxEntry->ucAttrib & FF_FAT_ATTR_DIR ) == 0 )
+ {
+ switch( pxEntry->ucIsDeviceDir )
+ {
+ case FF_DEV_CHAR_DEV:
+ mode[ 0 ] = 'c';
+ break;
+ case FF_DEV_BLOCK_DEV:
+ mode[ 0 ] = 'b';
+ break;
+ }
+ }
+ }
+ #endif /* ffconfigDEV_SUPPORT != 0 */
+
+ mode[ 1 ] = 'r'; /* Owner. */
+ mode[ 2 ] = ( ( pxEntry->ucAttrib & FF_FAT_ATTR_READONLY ) != 0 ) ? '-' : 'w';
+ mode[ 3 ] = '-'; /* x for executable. */
+
+ mode[ 4 ] = 'r'; /* group. */
+ mode[ 5 ] = ( ( pxEntry->ucAttrib & FF_FAT_ATTR_READONLY ) != 0 ) ? '-' : 'w';
+ mode[ 6 ] = '-'; /* x for executable. */
+
+ mode[ 7 ] = 'r'; /* world. */
+ mode[ 8 ] = '-';
+ mode[ 9 ] = '-'; /* x for executable. */
+
+ if( pxCreateTime->Month && pxCreateTime->Day )
+ {
+ snprintf( date, sizeof( date ), "%-3.3s %02d %02d:%02d",
+ pcMonthAbbrev( pxCreateTime->Month ),
+ pxCreateTime->Day,
+ pxCreateTime->Hour,
+ pxCreateTime->Minute );
+ }
+ else
+ {
+ snprintf (date, sizeof( date ), "Jan 01 1970");
+ }
+ return snprintf( pcLine, xMaxLength, "%s %3ld %-4s %-4s %8d %12s %s\r\n",
+ mode, st_nlink, user, group, ( int ) ulSize, date, pcFileName );
+}
+/*-----------------------------------------------------------*/
+
+/*
+ #### # # #####
+ # # # # # #
+# # # # # #
+# # # # # #
+# # # # # #
+# # # # # #
+# # ## ## # #
+ # # ## ## # #
+ #### ## ## #####
+*/
+static BaseType_t prvChangeDir( FTPClient_t *pxClient, char *pcDirectory )
+{
+BaseType_t xResult;
+BaseType_t xIsRootDir, xLength, xValid;
+BaseType_t xIsDotDir = 0;
+
+ if( pcDirectory[ 0 ] == '.' )
+ {
+ if( ( pcDirectory[ 1 ] == '.' ) &&
+ ( pcDirectory[ 2 ] == '\0' ) )
+ {
+ xIsDotDir = 2;
+ }
+ else if( pcDirectory[ 1 ] == '\0' )
+ {
+ xIsDotDir = 1;
+ }
+ }
+
+ if( xIsDotDir != 0 )
+ {
+ strcpy( pcFILE_BUFFER, pxClient->pcCurrentDir );
+
+ if( pcDirectory[ 1 ] == '.' )
+ {
+ char *p = strrchr( pcFILE_BUFFER, '/' );
+ if( p != NULL )
+ {
+ if( p == pcFILE_BUFFER )
+ {
+ p[ 1 ] = '\0';
+ }
+ else
+ {
+ p[ 0 ] = '\0';
+ }
+ }
+ }
+ }
+ else
+ {
+ if(pcDirectory[ 0 ] != '/' )
+ {
+ BaseType_t xCurLength;
+
+ xCurLength = strlen( pxClient->pcCurrentDir );
+ snprintf( pcFILE_BUFFER, sizeof( pcFILE_BUFFER ), "%s%s%s",
+ pxClient->pcCurrentDir,
+ pxClient->pcCurrentDir[ xCurLength - 1 ] == '/' ? "" : "/",
+ pcDirectory );
+ }
+ else
+ {
+ snprintf( pcFILE_BUFFER, sizeof( pcFILE_BUFFER ), "%s", pcDirectory );
+ }
+ }
+
+ xIsRootDir = ( pcFILE_BUFFER[ 0 ] == '/' ) && ( pcFILE_BUFFER[ 1 ] == '\0' );
+ xMakeAbsolute( pxClient, pcNEW_DIR, sizeof( pcNEW_DIR ), pcFILE_BUFFER );
+
+ if( ( ( xIsRootDir == pdFALSE ) || ( FF_FS_Count() == 0 ) ) && ( ff_finddir( pcNEW_DIR ) == pdFALSE ) )
+ {
+ xValid = pdFALSE;
+ }
+ else
+ {
+ xValid = pdTRUE;
+ }
+
+ if( xValid == pdFALSE )
+ {
+ /* Get the directory cluster, if it exists. */
+ FreeRTOS_printf( ("FTP: chdir \"%s\": No such dir\n", pcNEW_DIR ) );
+ //#define REPL_550 "550 Requested action not taken.\r\n"
+ //550 /home/hein/arch/h8300: No such file or directory
+ xLength = snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ),
+ "550 %s: No such file or directory\r\n",
+ pcNEW_DIR );
+ prvSendReply( pxClient->xSocket, pcCOMMAND_BUFFER, xLength );
+ xResult = pdFALSE;
+ }
+ else
+ {
+ memcpy( pxClient->pcCurrentDir, pcNEW_DIR, sizeof( pxClient->pcCurrentDir ) );
+
+ xLength = snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ), "250 Changed to %s\r\n", pcNEW_DIR );
+ prvSendReply( pxClient->xSocket, pcCOMMAND_BUFFER, xLength );
+ xResult = pdTRUE;
+ }
+
+ return xResult;
+}
+/*-----------------------------------------------------------*/
+
+/*
+###### ## # ####### ######
+ # # ## # # ## # #
+ # # ## # # # # #
+ # # ### # # # # #
+ ###### # ## # ##### ######
+ # ## # ## # # # # ##
+ # # # ### # # #
+ # # # ## # # #
+### ## # ## #### ### ##
+*/
+static BaseType_t prvRenameFrom( FTPClient_t *pxClient, const char *pcFileName )
+{
+const char *myReply;
+FF_FILE *fh;
+
+ xMakeAbsolute( pxClient, pxClient->pcFileName, sizeof( pxClient->pcFileName ), pcFileName );
+
+ myReply = NULL;
+
+ fh = ff_fopen( pxClient->pcFileName, "rb" );
+
+ if( fh != NULL )
+ {
+ ff_fclose( fh );
+ /* REPL_350; "350 Requested file action pending further information." */
+ snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ),
+ "350 Rename '%s' ...\r\n", pxClient->pcFileName );
+ myReply = pcCOMMAND_BUFFER;
+ pxClient->bits.bInRename = pdTRUE_UNSIGNED;
+ }
+ else if( stdioGET_ERRNO() == pdFREERTOS_ERRNO_EISDIR )
+ {
+ snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ),
+ "350 Rename directory '%s' ...\r\n", pxClient->pcFileName );
+ myReply = pcCOMMAND_BUFFER;
+ pxClient->bits.bInRename = pdTRUE_UNSIGNED;
+ }
+ else
+ {
+ FreeRTOS_printf( ("ftp::renameFrom[%s]\n%s\n", pxClient->pcFileName, strerror( stdioGET_ERRNO() ) ) );
+ myReply = REPL_451; /* "451 Requested action aborted. Local error in processing." */
+ }
+ if( myReply )
+ {
+ prvSendReply( pxClient->xSocket, myReply, 0 );
+ }
+
+ return pdTRUE;
+}
+/*-----------------------------------------------------------*/
+
+/*
+###### ## # ##### ###
+ # # ## # # # # ## ##
+ # # ## # # ## ##
+ # # ### # # # #
+ ###### # ## # # # #
+ # ## # ## # # # #
+ # # # ### # ## ##
+ # # # ## # ## ##
+### ## # ## #### ###
+*/
+static BaseType_t prvRenameTo( FTPClient_t *pxClient, const char *pcFileName )
+{
+const char *myReply = NULL;
+int iResult;
+
+ xMakeAbsolute( pxClient, pcNEW_DIR, sizeof( pcNEW_DIR ), pcFileName );
+
+ /* FreeRTOS+FAT rename has an extra parameter: "remove target if already
+ exists". */
+ iResult = ff_rename( pxClient->pcFileName, pcNEW_DIR, pdFALSE );
+
+ if( iResult < 0 )
+ {
+ iResult = stdioGET_ERRNO();
+ }
+ else
+ {
+ iResult = 0;
+ }
+
+ switch( iResult )
+ {
+ case 0:
+ FreeRTOS_printf( ( "ftp::renameTo[%s,%s]: Ok\n", pxClient->pcFileName, pcNEW_DIR ) );
+ snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ),
+ "250 Rename successful to '%s'\r\n", pcNEW_DIR );
+ myReply = pcCOMMAND_BUFFER;
+ break;
+ case pdFREERTOS_ERRNO_EEXIST:
+ /* the destination file already exists.
+ "450 Requested file action not taken.\r\n"*/
+ snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ),
+ "450 Already exists '%s'\r\n", pcNEW_DIR );
+ myReply = pcCOMMAND_BUFFER;
+ break;
+ case pdFREERTOS_ERRNO_EIO: /* FF_ERR_FILE_COULD_NOT_CREATE_DIRENT */
+ /* if dirent creation failed (fatal error!).
+ "553 Requested action not taken.\r\n" */
+ FreeRTOS_printf( ("ftp::renameTo[%s,%s]: Error creating DirEnt\n",
+ pxClient->pcFileName, pcNEW_DIR ) );
+ myReply = REPL_553;
+ break;
+ case pdFREERTOS_ERRNO_ENXIO:
+ case pdFREERTOS_ERRNO_ENOENT:
+ /* if the source file was not found.
+ "450 Requested file action not taken.\r\n" */
+ snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ),
+ "450 No such file '%s'\r\n", pxClient->pcFileName );
+ myReply = pcCOMMAND_BUFFER;
+ break;
+ default:
+ FreeRTOS_printf( ("ftp::renameTo[%s,%s]: %s\n", pxClient->pcFileName, pcNEW_DIR,
+ (const char*)strerror( stdioGET_ERRNO() ) ) );
+ myReply = REPL_451; /* "451 Requested action aborted. Local error in processing." */
+ break;
+ }
+ prvSendReply( pxClient->xSocket, myReply, 0 );
+
+ return pdTRUE;
+}
+/*-----------------------------------------------------------*/
+
+/*
+ #### #
+# # # #
+# # #
+# ### ###### ####
+ ## # # # #
+ ## # # ######
+# # # # #
+# # # # ## # ##
+ #### ##### ## ####
+*/
+static BaseType_t prvSiteCmd( FTPClient_t *pxClient, char *pcRestCommand )
+{
+ ( void ) pxClient;
+ ( void ) pcRestCommand;
+
+ return 0;
+}
+/*-----------------------------------------------------------*/
+
+/*
+##### ###
+ # # # #
+ # # # #
+ # # #### # #### ###### ####
+ # # # # # # # # # #
+ # # ###### # ###### # ######
+ # # # # # # #
+ # # # ## # # ## # ## # ##
+##### #### ##### #### ## ####
+*/
+static BaseType_t prvDeleteFile( FTPClient_t *pxClient, char *pcFileName )
+{
+BaseType_t xResult, xLength;
+int32_t iRc;
+int iErrorNo;
+
+ /* DELE: Delete a file. */
+ xMakeAbsolute( pxClient, pxClient->pcFileName, sizeof( pxClient->pcFileName ), pcFileName );
+
+ iRc = ff_remove( pxClient->pcFileName );
+
+ if (iRc >= 0 )
+ {
+ xLength = snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ),
+ "250 File \"%s\" removed\r\n", pxClient->pcFileName );
+ xResult = pdTRUE;
+ }
+ else
+ {
+ const char *errMsg = "other error";
+
+ iErrorNo = stdioGET_ERRNO();
+ switch( iErrorNo )
+ { /*_RB_ What do these negative numbers relate to? */
+ case pdFREERTOS_ERRNO_ENOENT: errMsg = "No such file"; break; /* -31 File was not found. */
+ case pdFREERTOS_ERRNO_EALREADY: errMsg = "File still open"; break; /* -30 File is in use. */
+ case pdFREERTOS_ERRNO_EISDIR: errMsg = "Is a dir"; break; /* -32 Tried to FF_Open() a Directory. */
+ case pdFREERTOS_ERRNO_EROFS: errMsg = "Read-only"; break; /* -33 Tried to FF_Open() a file marked read only. */
+ case pdFREERTOS_ERRNO_ENOTDIR: errMsg = "Invalid path"; break; /* -34 The path of the file was not found. */
+ }
+ FreeRTOS_printf( ( "ftp::delFile: '%s' because %s\n",
+ pxClient->pcFileName, strerror( iErrorNo ) ) );
+
+ xLength = snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ),
+ "521-\"%s\" %s;\r\n"
+ "521 taking no action\r\n",
+ pxClient->pcFileName, errMsg );
+
+ xResult = pdFALSE;
+ }
+
+ prvSendReply( pxClient->xSocket, pcCOMMAND_BUFFER, xLength );
+
+ return xResult;
+}
+/*-----------------------------------------------------------*/
+
+/*
+ #### # #####
+# # # # # #
+# # # # #
+# ### ###### #### # # #### ###### ####
+ ## # # # # # # # # # # #
+ ## # # ###### # # ##### # ######
+# # # # # # # # # # #
+# # # # # ## # # # # # ## # ##
+ #### ##### ###### #### ##### ### ## ## ####
+*/
+static BaseType_t prvSizeDateFile( FTPClient_t *pxClient, char *pcFileName, BaseType_t xSendDate )
+{
+BaseType_t xResult = pdFALSE;
+char *pcPtr;
+
+ /* SIZE: get the size of a file (xSendDate = 0)
+ MDTM: get data and time properties (xSendDate = 1) */
+ xMakeAbsolute( pxClient, pxClient->pcFileName, sizeof( pxClient->pcFileName ), pcFileName );
+
+ pcPtr = strrchr( pxClient->pcFileName, '/' );
+
+ if( ( pcPtr != NULL ) && ( pcPtr[ 1 ] != '\0' ) )
+ {
+ FF_Stat_t xStatBuf;
+ int32_t iRc = ff_stat( pxClient->pcFileName, &xStatBuf );
+ if (iRc < 0 )
+ FreeRTOS_printf( ("In %s: %s\n", pxClient->pcFileName,
+ ( const char* )strerror( stdioGET_ERRNO() ) ) );
+
+ if( iRc == 0 )
+ {
+ BaseType_t xLength;
+ /* "YYYYMMDDhhmmss" */
+ if( xSendDate != pdFALSE )
+ {
+ #if( ffconfigTIME_SUPPORT != 0 )
+ {
+ FF_TimeStruct_t tmStruct;
+ time_t secs = xStatBuf.st_mtime;
+ FreeRTOS_gmtime_r( &secs, &tmStruct );
+
+ xLength = snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ), "213 %04u%02u%02u%02u%02u%02u\r\n",
+ tmStruct.tm_year + 1900,
+ tmStruct.tm_mon+1,
+ tmStruct.tm_mday,
+ tmStruct.tm_hour,
+ tmStruct.tm_min,
+ tmStruct.tm_sec );
+ }
+ #else
+ {
+ xLength = snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ), "213 19700101000000\r\n",
+ }
+ #endif
+ }
+ else
+ {
+ xLength = snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ), "213 %lu\r\n", xStatBuf.st_size );
+ }
+ prvSendReply( pxClient->xSocket, pcCOMMAND_BUFFER, xLength );
+ xResult = pdTRUE;
+ }
+ else
+ {
+ FreeRTOS_printf( ("ftp::sizeDateFile: No such file %s\n", pxClient->pcFileName ) );
+ }
+ } else {
+ FreeRTOS_printf( ("ftp::sizeDateFile: Invalid file name: %s ?\n", pxClient->pcFileName ) );
+ }
+ if( xResult == pdFALSE )
+ {
+ prvSendReply( pxClient->xSocket, REPL_450, 0 ); /* "Requested file action not taken". */
+ }
+
+ return xResult;
+}
+/*-----------------------------------------------------------*/
+
+/*
+## ## ## ## ##### ###### ## ## #####
+### ### # # # # # # ### ### # #
+# ### # # # # # # # # ### # # #
+# # # # # # # # # # # # # #
+# # # #### # # ###### # # # # #
+# # # # # # # ## # # # #
+# # # # # # # # # # # #
+# # # # # # # # # # # #
+# # ### ## ##### ### ## # # #####
+*/
+static BaseType_t prvMakeRemoveDir( FTPClient_t *pxClient, const char *pcDirectory, BaseType_t xDoRemove )
+{
+BaseType_t xResult;
+BaseType_t xLength;
+int32_t iRc;
+int iErrorNo;
+
+ /* MKD: Make / create a directory (xDoRemove = 0)
+ RMD: Remove a directory (xDoRemove = 1) */
+ xMakeAbsolute( pxClient, pxClient->pcFileName, sizeof( pxClient->pcFileName ), pcDirectory );
+
+ if( xDoRemove )
+ {
+ iRc = ff_rmdir( pxClient->pcFileName );
+ }
+ else
+ {
+ #if( ffconfigMKDIR_RECURSIVE != 0 )
+ {
+ iRc = ff_mkdir( pxClient->pcFileName, pdFALSE );
+ }
+ #else
+ {
+ iRc = ff_mkdir( pxClient->pcFileName );
+ }
+ #endif /* ffconfigMKDIR_RECURSIVE */
+ }
+ xResult = pdTRUE;
+
+ if( iRc >= 0 )
+ {
+ xLength = snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ), "257 \"%s\" directory %s\r\n",
+ pxClient->pcFileName, xDoRemove ? "removed" : "created" );
+ }
+ else
+ {
+ const char *errMsg = "other error";
+ BaseType_t xFTPCode = 521;
+
+ xResult = pdFALSE;
+ iErrorNo = stdioGET_ERRNO();
+ switch( iErrorNo )
+ {
+ case pdFREERTOS_ERRNO_EEXIST: errMsg = "Directory already exists"; break;
+ case pdFREERTOS_ERRNO_ENOTDIR: errMsg = "Invalid path"; break; /* -34 The path of the file was not found. *//*_RB_ As before, what do these negative numbers relate to? */
+ case pdFREERTOS_ERRNO_ENOTEMPTY:errMsg = "Dir not empty"; break;
+ case pdFREERTOS_ERRNO_EROFS: errMsg = "Read-only"; break; /* -33 Tried to FF_Open() a file marked read only. */
+ default: errMsg = strerror( iErrorNo ); break;
+ }
+ if( iErrorNo == pdFREERTOS_ERRNO_ENOSPC )
+ {
+ xFTPCode = 552;
+ }
+ xLength = snprintf( pcCOMMAND_BUFFER, sizeof( pcCOMMAND_BUFFER ),
+ "%ld-\"%s\" %s;\r\n"
+ "%ld taking no action\r\n",
+ xFTPCode, pxClient->pcFileName, errMsg, xFTPCode );
+ FreeRTOS_printf( ( "%sdir '%s': %s\n", xDoRemove ? "rm" : "mk", pxClient->pcFileName, errMsg ) );
+ }
+ prvSendReply( pxClient->xSocket, pcCOMMAND_BUFFER, xLength );
+
+ return xResult;
+}
+/*-----------------------------------------------------------*/
+
+static portINLINE BaseType_t IsDigit( char cChar )
+{
+BaseType_t xResult;
+
+ if( cChar >= '0' && cChar <= '9' )
+ {
+ xResult = pdTRUE;
+ }
+ else
+ {
+ xResult = pdFALSE;
+ }
+ return xResult;
+}
+
+static BaseType_t prvSendReply( Socket_t xSocket, const char *pcBuffer, BaseType_t xLength )
+{
+BaseType_t xResult;
+
+ if( xLength == 0 )
+ {
+ xLength = strlen( pcBuffer );
+ }
+ xResult = FreeRTOS_send( xSocket, ( const void * )pcBuffer, ( size_t ) xLength, 0 );
+ if( IsDigit( ( int ) pcBuffer[ 0 ] ) &&
+ IsDigit( ( int ) pcBuffer[ 1 ] ) &&
+ IsDigit( ( int ) pcBuffer[ 2 ] ) &&
+ IsDigit( ( int ) pcBuffer[ 3 ] ) )
+ {
+ const char *last = pcBuffer + strlen( pcBuffer );
+ int iLength;
+ while( ( last > pcBuffer ) && ( ( last[ -1 ] == ftpASCII_CR ) || ( last[ -1 ] == ftpASCII_LF ) ) )
+ {
+ last--;
+ }
+ iLength = ( int )( last - pcBuffer );
+ FF_PRINTF( " %-*.*s", iLength, iLength, pcBuffer );
+ }
+ return xResult;
+}
+/*-----------------------------------------------------------*/
+
+#if( ipconfigFTP_HAS_RECEIVED_HOOK != 0 )
+
+ /*
+ * The following function is called for every file received:
+ * void vApplicationFTPReceivedHook( pcFileName, ulSize, pxFTPClient );
+ * This callback function may do a callback to vFTPReplyMessage() to send messages
+ * to the FTP client like:
+ * 200-Please wait: Received new firmware
+ * 200-Please wait: Please wait a few seconds for reboot
+ */
+ void vFTPReplyMessage( struct xFTP_CLIENT *pxFTPClient, const char *pcMessage )
+ {
+ if( ( pxFTPClient != NULL ) && ( pxFTPClient->xSocket != NULL ) )
+ {
+ prvSendReply( pxFTPClient->xSocket, pcMessage, 0 );
+ }
+ }
+ /*-----------------------------------------------------------*/
+
+#endif /* ipconfigFTP_HAS_RECEIVED_HOOK != 0 */
+
+/*
+ * Some explanation:
+ * The FTP client may send: "DELE readme.txt"
+ * Here the complete path is constructed consisting of 3 parts:
+ *
+ * pxClient->pcRootDir + pxClient->pcCurrentDir + pcFileName
+ *
+ * 'pcCurrentDir' will not be applied for an absolute path like in "DELE /.htaccess"
+ */
+BaseType_t xMakeAbsolute( FTPClient_t *pxClient, char *pcBuffer, BaseType_t xBufferLength, const char *pcFileName )
+{
+BaseType_t xLength = strlen( pxClient->pcRootDir );
+
+ if( pcFileName[ 0 ] != '/' )
+ {
+ char *pcNewDirBuffer = pcNEW_DIR;
+ BaseType_t xCurLength;
+
+ xCurLength = strlen( pxClient->pcCurrentDir );
+ if( pcBuffer == pcNEW_DIR )
+ {
+ /* In one call, the result already goes into pcNEW_DIR.
+ Use pcFILE_BUFFER in that case */
+ pcNewDirBuffer = pcFILE_BUFFER;
+ }
+ snprintf( pcNewDirBuffer, sizeof( pcNEW_DIR ), "%s%s%s",
+ pxClient->pcCurrentDir,
+ pxClient->pcCurrentDir[ xCurLength - 1 ] == '/' ? "" : "/",
+ pcFileName );
+ pcFileName = pcNewDirBuffer;
+ }
+ if( strncasecmp( pxClient->pcRootDir, pcFileName, xLength ) == 0 )
+ {
+ xLength = snprintf( pcBuffer, xBufferLength, "%s", pcFileName );
+ }
+ else
+ {
+ xLength = snprintf( pcBuffer, xBufferLength, "%s/%s",
+ pxClient->pcRootDir,
+ pcFileName[ 0 ] == '/' ? ( pcFileName + 1 ) : pcFileName );
+ }
+
+ #if( ipconfigFTP_FS_USES_BACKSLAH == 1 )
+ for( pcPtr = pcBuffer; *pcPtr; pcPtr++ )
+ {
+ if( pcPtr[ 0 ] == '/' )
+ {
+ pcPtr[ 0 ] = '\\';
+ }
+ }
+ #endif
+
+ return xLength;
+}
+/*-----------------------------------------------------------*/
+
+BaseType_t xMakeRelative( FTPClient_t *pxClient, char *pcBuffer, BaseType_t xBufferLength, const char *pcFileName )
+{
+BaseType_t xLength = strlen( pxClient->pcRootDir );
+
+ if( strncasecmp ( pxClient->pcRootDir, pcFileName, xLength ) == 0 )
+ {
+ xLength = snprintf( pcBuffer, xBufferLength, "%s", pcFileName + xLength );
+ }
+ else
+ {
+ xLength = snprintf( pcBuffer, xBufferLength, "%s", pcFileName );
+ }
+
+ return xLength;
+}
+/*-----------------------------------------------------------*/
+
+#endif /* ipconfigUSE_FTP */
+
+
+