text: /*
   * FreeRTOS+TCP V2.0.1
   * 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://www.FreeRTOS.org
   * http://aws.amazon.com/freertos
   *
   * 1 tab == 4 spaces!
   */
  
  /* 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 */
  
  
  
  