From 8916bdc71bfcc3bb0a7c84808e8d2391bd7d44b0 Mon Sep 17 00:00:00 2001 From: Thomas Doerfler Date: Thu, 26 Mar 2009 14:11:53 +0000 Subject: * libmisc/shell/shell.h: Pointer to oparations table for mount command is now const. * libnetworking/lib/ftpfs.c, libnetworking/rtems/ftpfs.h: Rewrite of the FTP file system which implements now the trivial command state machines of RFC 959. For the data transfer passive (= default) and active (= fallback) modes are now supported. * libmisc/shell/main_mount_ftp.c: Update for FTP file system changes. --- cpukit/ChangeLog | 10 + cpukit/libmisc/shell/main_mount_ftp.c | 2 +- cpukit/libmisc/shell/shell.h | 8 +- cpukit/libnetworking/lib/ftpfs.c | 1946 ++++++++++++++++----------------- cpukit/libnetworking/rtems/ftpfs.h | 118 +- 5 files changed, 1064 insertions(+), 1020 deletions(-) diff --git a/cpukit/ChangeLog b/cpukit/ChangeLog index 0f819b4a41..49666f77db 100644 --- a/cpukit/ChangeLog +++ b/cpukit/ChangeLog @@ -1,3 +1,13 @@ +2009-03-26 Sebastian Huber + + * libmisc/shell/shell.h: Pointer to + oparations table for mount command is now const. + * libnetworking/lib/ftpfs.c, libnetworking/rtems/ftpfs.h: Rewrite of + the FTP file system which implements now the trivial command state + machines of RFC 959. For the data transfer passive (= default) and + active (= fallback) modes are now supported. + * libmisc/shell/main_mount_ftp.c: Update for FTP file system changes. + 2009-03-12 Santosh G Vattam PR 1378/filesystem diff --git a/cpukit/libmisc/shell/main_mount_ftp.c b/cpukit/libmisc/shell/main_mount_ftp.c index a30e9bd18b..2712812fc0 100644 --- a/cpukit/libmisc/shell/main_mount_ftp.c +++ b/cpukit/libmisc/shell/main_mount_ftp.c @@ -32,6 +32,6 @@ rtems_shell_filesystems_t rtems_shell_Mount_FTP = { name: "ftp", driver_needed: 1, - fs_ops: &rtems_ftp_ops, + fs_ops: &rtems_ftpfs_ops, mounter: rtems_shell_libc_mounter }; diff --git a/cpukit/libmisc/shell/shell.h b/cpukit/libmisc/shell/shell.h index b1d589031c..9e5baccf42 100644 --- a/cpukit/libmisc/shell/shell.h +++ b/cpukit/libmisc/shell/shell.h @@ -222,10 +222,10 @@ typedef int (*rtems_shell_filesystems_mounter_t)( ); struct rtems_shell_filesystems_tt { - const char* name; - int driver_needed; - rtems_filesystem_operations_table* fs_ops; - rtems_shell_filesystems_mounter_t mounter; + const char *name; + int driver_needed; + const rtems_filesystem_operations_table *fs_ops; + rtems_shell_filesystems_mounter_t mounter; }; /** diff --git a/cpukit/libnetworking/lib/ftpfs.c b/cpukit/libnetworking/lib/ftpfs.c index bff0546489..dc32823038 100644 --- a/cpukit/libnetworking/lib/ftpfs.c +++ b/cpukit/libnetworking/lib/ftpfs.c @@ -1,1159 +1,1099 @@ -/* - * File Transfer Protocol client - * - * Transfer file to/from remote host - * - * This driver can be added to the RTEMS file system with a call to - * "rtems_bsdnet_initialize_ftp_filesystem () ". - * From then on, you can open, read and close files on a remote FTP server - * using the following syntax: - * To open a file "myfile.txt" in the directory "mydir" (relative to home - * directory) on a server named "myserver" using the user id - * "myuserid" and the password "my_very_secret_password" you must - * specify the following path: - * - * /FTP/myuserid:my_very_secret_password/@myserver/mydirectory/myfile.txt - * - * If the server is the default server specified in BOOTP, it can be ommitted: - * - * /FTP/myuserid:my_very_secret_password/mydirectory/myfile.txt +/** + * @file * - * WARNING: write accesses have not yet been tested. - * + * @brief File Transfer Protocol file system (FTP client). + */ + +/* + * Copyright (c) 2009 + * embedded brains GmbH + * Obere Lagerstr. 30 + * D-82178 Puchheim + * Germany + * * * (c) Copyright 2002 * Thomas Doerfler * IMD Ingenieurbuero fuer Microcomputertechnik * Herbststr. 8 - * 82178 Puchheim, Germany + * 82178 Puchheim, Germany * * - * This code has been created after closly inspecting - * "tftpdriver.c" from Eric Norum. - * - * $Id$ + * Modified by Sebastian Huber . + * + * This code has been created after closly inspecting "tftpdriver.c" from Eric + * Norum. + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.com/license/LICENSE. + * + * $Id$ */ -#include +#include #include +#include +#include #include -#include +#include +#include +#include #include -#include +#include #include -#include -#include +#include +#include +#include +#include + #include +#include +#include #include #include -#include -#include -#include -#include -#include -#include +#include - -#ifndef set_errno_and_return_minus_one -#define set_errno_and_return_minus_one( _error ) \ - do { errno = (_error); return -1; } while(0) +#ifdef DEBUG + #define DEBUG_PRINTF( ...) printf( __VA_ARGS__) +#else + #define DEBUG_PRINTF( ...) #endif -/* #define DEBUG_OUT */ - -/* - * Well-known port for FTP - */ -#define FTP_PORT_NUM 21 - -/* - * Pathname prefix +/** + * @brief Connection entry for each open file stream. */ -#define FTP_PATHNAME_PREFIX "/FTP/" -/* - * reply codes - */ -#define FTP_REPLY_CONNECT 220 /* Connection established */ -#define FTP_REPLY_PASSREQ 331 /* user ok, password required */ -#define FTP_REPLY_LOGIN 230 /* login finished */ -#define FTP_REPLY_SUCCESS 200 /* xxx successful */ -#define FTP_REPLY_OPENCONN 150 /* opening connection for tfer */ -#define FTP_REPLY_TFERCMPL 226 /* transfer complete */ - -extern rtems_filesystem_operations_table rtems_ftp_ops; -extern rtems_filesystem_file_handlers_r rtems_ftp_handlers; - -/* - * FTP command strings - */ -#define FTP_USER_CMD "USER " -#define FTP_PASS_CMD "PASS " -#define FTP_BINARY_CMD "TYPE I" -#define FTP_PORT_CMD "PORT " -#define FTP_STOR_CMD "STOR " -#define FTP_RETR_CMD "RETR " -#define FTP_QUIT_CMD "QUIT" - -/* - * State of each FTP stream - */ -struct ftpStream { - /* - * Control connection socket +typedef struct { + /** + * @brief Control connection socket. */ int ctrl_socket; - struct sockaddr_in myCtrlAddress; - struct sockaddr_in farCtrlAddress; - /* - * Data transfer socket + + /** + * @brief Data transfer socket. */ - int port_socket; int data_socket; - struct sockaddr_in myDataAddress; - struct sockaddr_in farDataAddress; - /* - * other stuff to remember + + /** + * @brief End of file flag. */ - bool eof_reached; -}; + bool eof; +} rtems_ftpfs_entry; -/* - * Number of streams open at the same time - */ -static rtems_id ftp_mutex; -static int nStreams; -static struct ftpStream ** volatile ftpStreams; +static bool rtems_ftpfs_verbose = false; -extern rtems_filesystem_operations_table rtems_tftp_ops; -extern rtems_filesystem_file_handlers_r rtems_tftp_handlers; +rtems_status_code rtems_ftpfs_mount( const char *mount_point) +{ + int rv = 0; -/* - * Direct copy from the IMFS/TFTP. Look at this. - */ + if (mount_point == NULL) { + mount_point = RTEMS_FTPFS_MOUNT_POINT_DEFAULT; + } -rtems_filesystem_limits_and_options_t rtems_ftp_limits_and_options = { - 5, /* link_max */ - 6, /* max_canon */ - 7, /* max_input */ - 255, /* name_max */ - 255, /* path_max */ - 2, /* pipe_buf */ - 1, /* posix_async_io */ - 2, /* posix_chown_restrictions */ - 3, /* posix_no_trunc */ - 4, /* posix_prio_io */ - 5, /* posix_sync_io */ - 6 /* posix_vdisable */ -}; + rv = mkdir( mount_point, S_IRWXU | S_IRWXG | S_IRWXO); + if (rv != 0) { + return RTEMS_IO_ERROR; + } -int rtems_ftp_mount_me( - rtems_filesystem_mount_table_entry_t *temp_mt_entry -) -{ - rtems_status_code sc; + rv = mount( + NULL, + &rtems_ftpfs_ops, + RTEMS_FILESYSTEM_READ_WRITE, + NULL, + mount_point + ); + if (rv != 0) { + return RTEMS_IO_ERROR; + } - temp_mt_entry->mt_fs_root.handlers = &rtems_ftp_handlers; - temp_mt_entry->mt_fs_root.ops = &rtems_ftp_ops; + return RTEMS_SUCCESSFUL; +} - /* - * We have no ftp filesystem specific data to maintain. This - * filesystem may only be mounted ONCE. - * - * And we maintain no real filesystem nodes, so there is no real root. - */ +rtems_status_code rtems_ftpfs_set_verbose( bool verbose) +{ + rtems_ftpfs_verbose = verbose; - temp_mt_entry->fs_info = NULL; - temp_mt_entry->mt_fs_root.node_access = NULL; + return RTEMS_SUCCESSFUL; +} - /* - * These need to be looked at for full POSIX semantics. - */ +rtems_status_code rtems_ftpfs_get_verbose( bool *verbose) +{ + if (verbose == NULL) { + return RTEMS_INVALID_ADDRESS; + } - temp_mt_entry->pathconf_limits_and_options = rtems_ftp_limits_and_options; + *verbose = rtems_ftpfs_verbose; + return RTEMS_SUCCESSFUL; +} - /* - * Now allocate a semaphore for mutual exclusion. - * - * NOTE: This could be in an fsinfo for this filesystem type. - */ - - sc = rtems_semaphore_create (rtems_build_name('F','T','P',' '), - 1, - RTEMS_FIFO | - RTEMS_BINARY_SEMAPHORE | - RTEMS_NO_INHERIT_PRIORITY | - RTEMS_NO_PRIORITY_CEILING | - RTEMS_LOCAL, - 0, - &ftp_mutex); - - if (sc != RTEMS_SUCCESSFUL) - set_errno_and_return_minus_one( ENOMEM ); +int rtems_bsdnet_initialize_ftp_filesystem( void) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; - return 0; + sc = rtems_ftpfs_mount( NULL); + + if (sc == RTEMS_SUCCESSFUL) { + return 0; + } else { + return -1; + } } -/* - * Initialize the FTP driver - */ +typedef void (*rtems_ftpfs_reply_parser)( + const char * /* reply fragment */, + size_t /* reply fragment length */, + void * /* parser argument */ +); -int rtems_bsdnet_initialize_ftp_filesystem (void) -{ - int status; - rtems_filesystem_mount_table_entry_t *entry; - - status = mkdir( FTP_PATHNAME_PREFIX, S_IRWXU | S_IRWXG | S_IRWXO ); - if ( status == -1 ) - return status; - - status = mount( - &entry, - &rtems_ftp_ops, - RTEMS_FILESYSTEM_READ_WRITE, - NULL, - FTP_PATHNAME_PREFIX - ); +typedef enum { + RTEMS_FTPFS_REPLY_START, + RTEMS_FTPFS_REPLY_SINGLE_LINE, + RTEMS_FTPFS_REPLY_SINGLE_LINE_DONE, + RTEMS_FTPFS_REPLY_MULTI_LINE, + RTEMS_FTPFS_REPLY_NEW_LINE, + RTEMS_FTPFS_REPLY_NEW_LINE_START +} rtems_ftpfs_reply_state; - if ( status ) - perror( "FTP mount failed" ); +typedef enum { + RTEMS_FTPFS_REPLY_ERROR = 0, + RTEMS_FTPFS_REPLY_1 = '1', + RTEMS_FTPFS_REPLY_2 = '2', + RTEMS_FTPFS_REPLY_3 = '3', + RTEMS_FTPFS_REPLY_4 = '4', + RTEMS_FTPFS_REPLY_5 = '5' +} rtems_ftpfs_reply; - return status; -} +#define RTEMS_FTPFS_REPLY_SIZE 3 -/* - * read and return message code from ftp control connection - */ -int rtems_ftp_get_message -( - const struct ftpStream *fsp, /* ptr to ftp control structure */ - int *msg_code /* ptr to return message code */ +static rtems_ftpfs_reply rtems_ftpfs_get_reply( + int socket, + rtems_ftpfs_reply_parser parser, + void *parser_arg ) { - char rd_buffer[4]; - size_t rd_size; - ssize_t tmp_size; - int eno = 0; - bool finished = false; - do { - /* - * fetch (at least) 4 characters from control connection - * FIXME: how about a timeout? - */ - rd_size = 0; - while ((eno == 0) && - (rd_size < sizeof(rd_buffer))) { - tmp_size = read(fsp->ctrl_socket, - (&rd_buffer)+rd_size, - sizeof(rd_buffer)-rd_size); - if (tmp_size < 0) { - eno = EIO; - } - else { -#ifdef DEBUG_OUT - write(1,(&rd_buffer)+rd_size,tmp_size); -#endif - rd_size += tmp_size; - } - } - /* - * check for 3 digits and space, otherwise not finished - */ - if ((eno == 0) && - (isdigit((unsigned int)rd_buffer[0])) && - (isdigit((unsigned int)rd_buffer[1])) && - (isdigit((unsigned int)rd_buffer[2])) && - (rd_buffer[3] == ' ')) { - finished = true; - rd_buffer[3] = '\0'; - *msg_code = atol(rd_buffer); - } - /* - * skip rest until end-of-line - */ - do { - tmp_size = read(fsp->ctrl_socket, - &rd_buffer, - 1); - if (tmp_size < 0) { - eno = EIO; - } -#ifdef DEBUG_OUT - else { - write(1,(&rd_buffer),tmp_size); - } -#endif - } while ((eno == 0) && - (rd_buffer[0] != '\n')); - } while ((eno == 0) && !finished); - return eno; -} + rtems_ftpfs_reply_state state = RTEMS_FTPFS_REPLY_START; + bool verbose = rtems_ftpfs_verbose; + char reply_first [RTEMS_FTPFS_REPLY_SIZE] = { 'a', 'a', 'a' }; + char reply_last [RTEMS_FTPFS_REPLY_SIZE] = { 'b', 'b', 'b' }; + size_t reply_first_index = 0; + size_t reply_last_index = 0; + char buf [128]; -/* - * split a pseudo file name into host, user, password, filename - * NOTE: this function will allocate space for these strings, - * the calling function should free the space, when no longer needed - * exception: when we return any error, we will also cleanup - * the strings - * valid forms: - * /FTP/user:pass/filepath - * /FTP/user:pass@hostname/filepath - - * /FTP/user:pass/filepath - * /FTP/user:pass/@hostname/filepath - * NOTE: /FTP is already stripped from the name - */ -int rtems_ftp_split_names -( const char *pathname, /* total path name (including prefix) */ - char **usernamep, /* ptr to ptr to user name */ - char **passwordp, /* ptr to ptr to password */ - char **hostnamep, /* ptr to ptr to host name */ - char **filenamep) /* ptr to ptr to hostremaining file name */ -{ - const char *chunk_start; - const char *chunk_end; - size_t chunk_len; - int rc = 0; + while (true) { + /* Receive reply fragment from socket */ + ssize_t i = 0; + ssize_t rv = recv( socket, buf, sizeof( buf), 0); - /* - * ensure, that result pointers are NULL... - */ - *usernamep = NULL; - *passwordp = NULL; - *hostnamep = NULL; - *filenamep = NULL; - -#if 1 - chunk_start = pathname; -#else /* no longer needed with IMFS */ - /* - * check, that total path is long enough, skip prefix - */ - if (rc == 0) { - if (strlen (pathname) <= strlen (FTP_PATHNAME_PREFIX)) { - rc = ENOENT; + if (rv <= 0) { + return RTEMS_FTPFS_REPLY_ERROR; } - else { - chunk_start = pathname + strlen (FTP_PATHNAME_PREFIX); + + /* Be verbose if necessary */ + if (verbose) { + write( STDERR_FILENO, buf, (size_t) rv); } - } -#endif - /* - * fetch user name: terminated with ":" - */ - if (rc == 0) { - chunk_end = strchr(chunk_start,':'); - if ((chunk_end == NULL) || /* No ':' found or */ - (chunk_end == chunk_start)) { /* ':' is first character-> no name */ - rc = ENOENT; + + /* Invoke parser if necessary */ + if (parser != NULL) { + parser( buf, (size_t) rv, parser_arg); } - else { - chunk_len = chunk_end-chunk_start; - *usernamep = malloc(chunk_len+1); - if (*usernamep == NULL) { - rc = ENOMEM; - } - else { - memcpy(*usernamep,chunk_start,chunk_len); - (*usernamep)[chunk_len] = '\0'; + + /* Parse reply fragment */ + for (i = 0; i < rv; ++i) { + char c = buf [i]; + + switch (state) { + case RTEMS_FTPFS_REPLY_START: + if (reply_first_index < RTEMS_FTPFS_REPLY_SIZE) { + reply_first [reply_first_index] = c; + ++reply_first_index; + } else if (c == '-') { + state = RTEMS_FTPFS_REPLY_MULTI_LINE; + } else { + state = RTEMS_FTPFS_REPLY_SINGLE_LINE; + } + break; + case RTEMS_FTPFS_REPLY_SINGLE_LINE: + if (c == '\n') { + state = RTEMS_FTPFS_REPLY_SINGLE_LINE_DONE; + } + break; + case RTEMS_FTPFS_REPLY_MULTI_LINE: + if (c == '\n') { + state = RTEMS_FTPFS_REPLY_NEW_LINE_START; + reply_last_index = 0; + } + break; + case RTEMS_FTPFS_REPLY_NEW_LINE: + case RTEMS_FTPFS_REPLY_NEW_LINE_START: + if (reply_last_index < RTEMS_FTPFS_REPLY_SIZE) { + state = RTEMS_FTPFS_REPLY_NEW_LINE; + reply_last [reply_last_index] = c; + ++reply_last_index; + } else { + state = RTEMS_FTPFS_REPLY_MULTI_LINE; + } + break; + default: + return RTEMS_FTPFS_REPLY_ERROR; } } - } - /* - * fetch password: terminated with "/" or "@" - */ - if (rc == 0) { - chunk_start = chunk_end + 1; /* skip ":" after user name */ - chunk_end = strchr(chunk_start,'/'); - if ((chunk_end == NULL) || /* No '/' found or */ - (chunk_end == chunk_start)) { /* '/' is first character-> no pwd */ - rc = ENOENT; - } - else { - /* - * we have found a proper '/' - * this is the end of the password - */ - chunk_len = chunk_end-chunk_start; - *passwordp = malloc(chunk_len+1); - if (*passwordp == NULL) { - rc = ENOMEM; - } - else { - memcpy(*passwordp,chunk_start,chunk_len); - (*passwordp)[chunk_len] = '\0'; + + /* Check reply */ + if (state == RTEMS_FTPFS_REPLY_SINGLE_LINE_DONE) { + if ( + isdigit( reply_first [0]) + && isdigit( reply_first [1]) + && isdigit( reply_first [2]) + ) { + break; + } else { + return RTEMS_FTPFS_REPLY_ERROR; } - } - } - /* - * if first char after '/' is '@', then this is the hostname - * fetch hostname terminated with "/" - * if exists at all. otherwise take default server from bootp - */ - if (rc == 0) { - chunk_start = chunk_end+1; - if (*chunk_start == '@') { - /* - * hostname follows - */ - chunk_start = chunk_start + 1; /* skip "@" after password */ - chunk_end = strchr(chunk_start,'/'); - if ((chunk_end == NULL) || /* No '/' found or */ - (chunk_end == chunk_start)) { /* '/' is first character-> no host */ - rc = ENOENT; + } else if (state == RTEMS_FTPFS_REPLY_NEW_LINE_START) { + bool ok = true; + + for (i = 0; i < RTEMS_FTPFS_REPLY_SIZE; ++i) { + ok = ok + && reply_first [i] == reply_last [i] + && isdigit( reply_first [i]); } - else { - /* - * we have found a proper '/' - */ - chunk_len = chunk_end-chunk_start; - *hostnamep = malloc(chunk_len+1); - if (*hostnamep == NULL) { - rc = ENOMEM; - } - else { - memcpy(*hostnamep,chunk_start,chunk_len); - (*hostnamep)[chunk_len] = '\0'; - } + + if (ok) { + break; } } - else { /* chunk_start != '@' */ - /* - * no host name given, keep string empty - */ - *hostnamep = malloc(1); - if (*hostnamep == NULL) { - rc = ENOMEM; - } - else { - (*hostnamep)[0] = '\0'; - } - } } - /* - * fetch filename. This is all the rest... - */ - if (rc == 0) { - chunk_start = chunk_end+1; - if (*chunk_start == '\0') { /* nothing left for filename */ - rc = ENOENT; - } - else { - chunk_len = strlen(chunk_start); - *filenamep = malloc(chunk_len+1); - if (*filenamep == NULL) { - rc = ENOMEM; - } - else { - memcpy(*filenamep,chunk_start,chunk_len); - (*filenamep)[chunk_len] = '\0'; - } - } + + return reply_first [0]; +} + +static rtems_ftpfs_reply rtems_ftpfs_send_command_with_parser( + int socket, + const char *cmd, + const char *arg, + rtems_ftpfs_reply_parser parser, + void *parser_arg +) +{ + const char *const eol = "\r\n"; + bool verbose = rtems_ftpfs_verbose; + int rv = 0; + + /* Send command */ + rv = send( socket, cmd, strlen( cmd), 0); + if (rv < 0) { + return RTEMS_FTPFS_REPLY_ERROR; } - - /* - * cleanup anything, if error occured - */ - if (rc != 0) { - if (*hostnamep != NULL) { - free(*hostnamep); - *hostnamep = NULL; - } - if (*usernamep != NULL) { - free(*usernamep); - *usernamep = NULL; - } - if (*passwordp != NULL) { - free(*passwordp); - *passwordp = NULL; - } - if (*filenamep != NULL) { - free(*filenamep); - *filenamep = NULL; + if (verbose) { + write( STDERR_FILENO, cmd, strlen( cmd)); + } + + /* Send command argument if necessary */ + if (arg != NULL) { + rv = send( socket, arg, strlen( arg), 0); + if (rv < 0) { + return RTEMS_FTPFS_REPLY_ERROR; + } + if (verbose) { + write( STDERR_FILENO, arg, strlen( arg)); } } - return rc; + + /* Send end of line */ + rv = send( socket, eol, 2, 0); + if (rv < 0) { + return RTEMS_FTPFS_REPLY_ERROR; + } + if (verbose) { + write( STDERR_FILENO, &eol [1], 1); + } + + /* Return reply */ + return rtems_ftpfs_get_reply( socket, parser, parser_arg); } - -int rtems_ftp_evaluate_for_make( - const char *path, /* IN */ - rtems_filesystem_location_info_t *pathloc, /* IN/OUT */ - const char **name /* OUT */ + +static rtems_ftpfs_reply rtems_ftpfs_send_command( + int socket, + const char *cmd, + const char *arg ) -{ - set_errno_and_return_minus_one( EIO ); +{ + return rtems_ftpfs_send_command_with_parser( socket, cmd, arg, NULL, NULL); } -/* - * XXX - Fix return values. - */ +typedef enum { + STATE_USER_NAME, + STATE_START_PASSWORD, + STATE_START_HOST_NAME, + STATE_START_HOST_NAME_OR_PATH, + STATE_START_PATH, + STATE_PASSWORD, + STATE_HOST_NAME, + STATE_DONE, + STATE_INVALID +} split_state; -int rtems_ftp_eval_path( - const char *pathname, /* IN */ - int flags, /* IN */ - rtems_filesystem_location_info_t *pathloc /* IN/OUT */ +static bool rtems_ftpfs_split_names ( + char *s, + const char **user, + const char **password, + const char **hostname, + const char **path ) { + split_state state = STATE_USER_NAME; + size_t len = strlen( s); + size_t i = 0; - /* - * Read-only for now - */ - - if ( ((flags & O_RDONLY) != O_RDONLY ) && - ((flags & O_WRONLY) != O_WRONLY )) { - set_errno_and_return_minus_one( ENOENT ); + *user = s; + *password = NULL; + *hostname = NULL; + *path = NULL; + + for (i = 0; i < len; ++i) { + char c = s [i]; + + switch (state) { + case STATE_USER_NAME: + if (c == ':') { + state = STATE_START_PASSWORD; + s [i] = '\0'; + } else if (c == '@') { + state = STATE_START_HOST_NAME; + s [i] = '\0'; + } else if (c == '/') { + state = STATE_START_HOST_NAME_OR_PATH; + s [i] = '\0'; + } + break; + case STATE_START_PASSWORD: + state = STATE_PASSWORD; + *password = &s [i]; + --i; + break; + case STATE_START_HOST_NAME: + state = STATE_HOST_NAME; + *hostname = &s [i]; + --i; + break; + case STATE_START_HOST_NAME_OR_PATH: + if (c == '@') { + state = STATE_START_HOST_NAME; + } else { + state = STATE_DONE; + *path = &s [i]; + goto done; + } + break; + case STATE_START_PATH: + state = STATE_DONE; + *path = &s [i]; + goto done; + case STATE_PASSWORD: + if (c == '@') { + state = STATE_START_HOST_NAME; + s [i] = '\0'; + } else if (c == '/') { + state = STATE_START_HOST_NAME_OR_PATH; + s [i] = '\0'; + } + break; + case STATE_HOST_NAME: + if (c == '/') { + state = STATE_START_PATH; + s [i] = '\0'; + } + break; + default: + state = STATE_INVALID; + goto done; + } } - /* - * The File system is mounted at FTP_PATHNAME_PREFIX - * the caller of this routine has striped off this part of the - * name. Save the remainder of the name for use by the open routine. - */ - pathloc->node_access = (void * ) pathname; - pathloc->handlers = &rtems_ftp_handlers; +done: - return 0; + /* If we have no password use the user name */ + if (*password == NULL) { + *password = *user; + } + + return state == STATE_DONE; } -/* - * Open a FTP stream - */ -int rtems_ftp_open( - rtems_libio_t *iop, - const char *new_name, - uint32_t flag, - uint32_t mode +static socklen_t rtems_ftpfs_create_address( + struct sockaddr_in *sa, + unsigned long address, + unsigned short port ) { - int s = 0; - char *filename = NULL; - char *uname = NULL; - char *upass = NULL; - char *hostname = NULL; - char port_buffer[sizeof(FTP_PORT_CMD)+6*4+1+1]; - uint32_t my_ip; - uint16_t my_port; - int eno = 0; - rtems_status_code rc; - bool is_write = false; - bool sema_obtained = false; - struct ftpStream *fsp = NULL; - int msg_tmp = 0; - socklen_t sockaddr_size; - /* - * check for R/O or W/O flags - */ - if (eno == 0) { - if ((0 != (iop->flags & LIBIO_FLAGS_WRITE)) && - (0 != (iop->flags & LIBIO_FLAGS_READ))) { - eno = ENOTSUP; - } - else { - is_write = (0 != (iop->flags & LIBIO_FLAGS_WRITE)); - } - } - /* - * split pathname into parts - */ - if (eno == 0) { - eno = rtems_ftp_split_names(iop->file_info, - &uname, - &upass, - &hostname, - &filename); - } - - /* - * Find a free stream - */ - if (eno == 0) { - rc = rtems_semaphore_obtain (ftp_mutex, RTEMS_WAIT, RTEMS_NO_TIMEOUT); - if (rc == RTEMS_SUCCESSFUL) { - sema_obtained = true; + memset( sa, sizeof( *sa), 0); + + sa->sin_family = AF_INET; + sa->sin_addr.s_addr = address; + sa->sin_port = port; + sa->sin_len = sizeof( *sa); + + return sizeof( *sa); +} + +static void rtems_ftpfs_terminate( rtems_ftpfs_entry *e, rtems_libio_t *iop) +{ + if (e != NULL) { + /* Close data connection if necessary */ + if (e->data_socket >= 0) { + close( e->data_socket); } - else { - eno = EBUSY; + + /* Close control connection if necessary */ + if (e->ctrl_socket >= 0) { + close( e->ctrl_socket); } + + /* Free connection entry */ + free( e); } - if (eno == 0) { - for (s = 0 ; s < nStreams ; s++) { - if (ftpStreams[s] == NULL) - break; - } - if (s == nStreams) { - /* - * Reallocate stream pointers - * Guard against the case where realloc() returns NULL. - */ - struct ftpStream **np; - - np = realloc (ftpStreams, ++nStreams * sizeof *ftpStreams); - if (np == NULL) { - eno = ENOMEM; - } - else { - ftpStreams = np; - } - } + + /* Invalidate IO entry */ + iop->data1 = NULL; +} + +static int rtems_ftpfs_open_ctrl_connection( + rtems_ftpfs_entry *e, + const char *user, + const char *password, + const char *hostname, + uint32_t *client_address +) +{ + int rv = 0; + rtems_ftpfs_reply reply = RTEMS_FTPFS_REPLY_ERROR; + struct in_addr address = { .s_addr = 0 }; + struct sockaddr_in sa; + socklen_t size = 0; + + /* Create the socket for the control connection */ + e->ctrl_socket = socket( AF_INET, SOCK_STREAM, 0); + if (e->ctrl_socket < 0) { + return ENOMEM; } - if (eno == 0) { - fsp = ftpStreams[s] = malloc (sizeof (struct ftpStream)); - rtems_semaphore_release (ftp_mutex); - sema_obtained = false; - if (fsp == NULL) { - eno = ENOMEM; - } - else { - iop->data0 = s; - iop->data1 = fsp; - fsp->ctrl_socket = -1; /* mark, that sockets not yet created */ - fsp->port_socket = -1; - fsp->data_socket = -1; - fsp->eof_reached = false; + + /* Set up the server address from the hostname */ + if (hostname == NULL || strlen( hostname) == 0) { + /* Default to BOOTP server address */ + address = rtems_bsdnet_bootp_server_address; + } else if (inet_aton( hostname, &address) == 0) { + /* Try to get the address by name */ + struct hostent *he = gethostbyname( hostname); + + if (he != NULL) { + memcpy( &address, he->h_addr, sizeof( address)); + } else { + return ENOENT; } } - if (eno == 0) { - /* - * Create the socket for control connection - */ - if ((fsp->ctrl_socket = socket (AF_INET, SOCK_STREAM, 0)) < 0) { - eno = ENOMEM; - } + rtems_ftpfs_create_address( &sa, address.s_addr, htons( RTEMS_FTPFS_CTRL_PORT)); + DEBUG_PRINTF( "server = %s\n", inet_ntoa( sa.sin_addr)); + + /* Open control connection */ + rv = connect( + e->ctrl_socket, + (struct sockaddr *) &sa, + sizeof( sa) + ); + if (rv != 0) { + return ENOENT; } - if (eno == 0) { - /* - * Set the destination to the FTP server - * port on the remote machine. - */ - memset(&(fsp->farCtrlAddress),sizeof(fsp->farCtrlAddress),0); - fsp->farCtrlAddress.sin_family = AF_INET; - if (*hostname == '\0') { - fsp->farCtrlAddress.sin_addr.s_addr = rtems_bsdnet_bootp_server_address.s_addr; - } - else if (1 != inet_aton(hostname,&(fsp->farCtrlAddress.sin_addr))) { - struct hostent *hent; - - hent = gethostbyname(hostname); - if (hent != NULL) { - memcpy(&fsp->farCtrlAddress.sin_addr, - hent->h_addr, - sizeof(fsp->farCtrlAddress.sin_addr)); - } - else { - eno = ENOENT; - } - } - if (eno == 0) { - fsp->farCtrlAddress.sin_port = htons (FTP_PORT_NUM); - fsp->farCtrlAddress.sin_len = sizeof(fsp->farCtrlAddress); - if (0 > connect(fsp->ctrl_socket, - (struct sockaddr *)&(fsp->farCtrlAddress), - sizeof(fsp->farCtrlAddress))) { - eno = ENOENT; - } - } - if (eno == 0) { - /* - * fetch IP address of interface used - */ - memset(&(fsp->myCtrlAddress),sizeof(fsp->myCtrlAddress),0); - fsp->myCtrlAddress.sin_family = AF_INET; - fsp->myCtrlAddress.sin_addr.s_addr = INADDR_ANY; - fsp->myCtrlAddress.sin_port = 0; - fsp->myCtrlAddress.sin_len = sizeof(fsp->myDataAddress); - sockaddr_size = sizeof(fsp->myCtrlAddress); - if (0 > getsockname(fsp->ctrl_socket, - (struct sockaddr *)&(fsp->myCtrlAddress), - &sockaddr_size)) { - eno = ENOMEM; - } - } + /* Get client address */ + size = rtems_ftpfs_create_address( &sa, INADDR_ANY, 0); + rv = getsockname( + e->ctrl_socket, + (struct sockaddr *) &sa, + &size + ); + if (rv != 0) { + return ENOMEM; } - if (eno == 0) { - /* - * now we should get a connect message from the FTP server - */ - eno = rtems_ftp_get_message(fsp,&msg_tmp); - if ((eno == 0) && - (msg_tmp != FTP_REPLY_CONNECT)) { - eno = ENOENT; - } + *client_address = ntohl( sa.sin_addr.s_addr); + DEBUG_PRINTF( "client = %s\n", inet_ntoa( sa.sin_addr)); + + /* Now we should get a welcome message from the server */ + reply = rtems_ftpfs_get_reply( e->ctrl_socket, NULL, NULL); + if (reply != RTEMS_FTPFS_REPLY_2) { + return ENOENT; } - if (eno == 0) { - /* - * send user ID to server - * NOTE: the following lines will be executed in order - * and will be aborted whenever an error occures... (see your ANSI C book) - */ - if ((0 > send(fsp->ctrl_socket,FTP_USER_CMD,strlen(FTP_USER_CMD),0)) || - (0 > send(fsp->ctrl_socket,uname, strlen(uname), 0)) || - (0 > send(fsp->ctrl_socket,"\n", 1, 0))) { - eno = EIO; + + /* Send USER command */ + reply = rtems_ftpfs_send_command( e->ctrl_socket, "USER ", user); + if (reply == RTEMS_FTPFS_REPLY_3) { + /* Send PASS command */ + reply = rtems_ftpfs_send_command( e->ctrl_socket, "PASS ", password); + if (reply != RTEMS_FTPFS_REPLY_2) { + return EACCES; } + + /* TODO: Some server may require an account */ + } else if (reply != RTEMS_FTPFS_REPLY_2) { + return EACCES; } - if (eno == 0) { - /* - * now we should get a request for the password or a login... - */ - eno = rtems_ftp_get_message(fsp,&msg_tmp); - if (eno == 0) { - if (msg_tmp == FTP_REPLY_PASSREQ) { - /* - * send password to server - */ -#ifdef DEBUG_OUT - write(1,FTP_PASS_CMD,strlen(FTP_PASS_CMD)); - write(1,upass, strlen(upass) ); - write(1,"\n", 1 ); -#endif - if ((0 > send(fsp->ctrl_socket,FTP_PASS_CMD,strlen(FTP_PASS_CMD),0)) || - (0 > send(fsp->ctrl_socket,upass, strlen(upass), 0)) || - (0 > send(fsp->ctrl_socket,"\n", 1, 0))) { - eno = EIO; - } - /* - * at least now a login reply should come up... - * this is checked some lines downwards the code - */ - if (eno == 0) { - eno = rtems_ftp_get_message(fsp,&msg_tmp); - } - } - } + + /* Send TYPE command to set binary mode for all data transfers */ + reply = rtems_ftpfs_send_command( e->ctrl_socket, "TYPE I", NULL); + if (reply != RTEMS_FTPFS_REPLY_2) { + return EIO; } - if (eno == 0) { - /* - * check for a login reply. this should be present now... - */ - if (msg_tmp != FTP_REPLY_LOGIN) { - eno = EACCES; /* pseudo for wrong user/pass */ - } + + return 0; +} + +static int rtems_ftpfs_open_data_connection_active( + rtems_ftpfs_entry *e, + uint32_t client_address, + const char *file_command, + const char *filename +) +{ + int rv = 0; + int eno = 0; + rtems_ftpfs_reply reply = RTEMS_FTPFS_REPLY_ERROR; + struct in_addr address = { .s_addr = 0 }; + struct sockaddr_in sa; + socklen_t size = 0; + int port_socket = -1; + char port_command [] = "PORT 000,000,000,000,000,000"; + uint16_t data_port = 0; + + /* Create port socket to establish a data data connection */ + port_socket = socket( AF_INET, SOCK_STREAM, 0); + if (port_socket < 0) { + eno = ENOMEM; + goto cleanup; } - if (eno == 0) { - /* - * set binary mode for all transfers - */ -#ifdef DEBUG_OUT - write(1,FTP_BINARY_CMD,strlen(FTP_BINARY_CMD)); - write(1,"\n", 1 ); -#endif - if ((0 > send(fsp->ctrl_socket,FTP_BINARY_CMD,strlen(FTP_BINARY_CMD),0)) || - (0 > send(fsp->ctrl_socket,"\n", 1, 0))) { - eno = EIO; - } - else { - eno = rtems_ftp_get_message(fsp,&msg_tmp); - } + + /* Bind port socket */ + rtems_ftpfs_create_address( &sa, INADDR_ANY, 0); + rv = bind( + port_socket, + (struct sockaddr *) &sa, + sizeof( sa) + ); + if (rv != 0) { + eno = EBUSY; + goto cleanup; } - if (eno == 0) { - /* - * check for a "BINARY TYPE command successful" reply - */ - if (msg_tmp != FTP_REPLY_SUCCESS) { - eno = EIO; - } + + /* Get port number for data socket */ + size = rtems_ftpfs_create_address( &sa, INADDR_ANY, 0); + rv = getsockname( + port_socket, + (struct sockaddr *) &sa, + &size + ); + if (rv != 0) { + eno = ENOMEM; + goto cleanup; } - if (eno == 0) { - /* - * create and bind socket for data connection - */ - if ((fsp->port_socket = socket (AF_INET, SOCK_STREAM, 0)) < 0) { - eno = ENOMEM; - } - else { - memset(&(fsp->myDataAddress),sizeof(fsp->myDataAddress),0); - fsp->myDataAddress.sin_family = AF_INET; - fsp->myDataAddress.sin_addr.s_addr = INADDR_ANY; - fsp->myDataAddress.sin_port = 0; /* unique port will be assigned */ - fsp->myDataAddress.sin_len = sizeof(fsp->myDataAddress); - if (0 > bind(fsp->port_socket, - (struct sockaddr *)&(fsp->myDataAddress), - sizeof(fsp->myDataAddress))) { - eno = EBUSY; - } - else { - /* - * fetch port number of data socket - */ - memset(&(fsp->myDataAddress),sizeof(fsp->myDataAddress),0); - fsp->myDataAddress.sin_family = AF_INET; - fsp->myDataAddress.sin_addr.s_addr = INADDR_ANY; - fsp->myDataAddress.sin_port = 0; - fsp->myDataAddress.sin_len = sizeof(fsp->myDataAddress); - sockaddr_size = sizeof(fsp->myDataAddress); - if (0 > getsockname(fsp->port_socket, - (struct sockaddr *)&(fsp->myDataAddress), - &sockaddr_size)) { - eno = ENOMEM; - } - } - } + data_port = ntohs( sa.sin_port); + + /* Send PORT command to set data connection port for server */ + snprintf( + port_command, + sizeof( port_command), + "PORT %" PRIu32 ",%" PRIu32 ",%" PRIu32 ",%" PRIu32 ",%" PRIu16 ",%" PRIu16, + (client_address >> 24) & 0xff, + (client_address >> 16) & 0xff, + (client_address >> 8) & 0xff, + (client_address >> 0) & 0xff, + (data_port >> 8) & 0xff, + (data_port >> 0) & 0xff + ); + reply = rtems_ftpfs_send_command( e->ctrl_socket, port_command, NULL); + if (reply != RTEMS_FTPFS_REPLY_2) { + eno = ENOTSUP; + goto cleanup; } - if (eno == 0) { - /* - * propagate data connection port to server - */ - my_ip = ntohl(fsp->myCtrlAddress.sin_addr.s_addr); - my_port = ntohs(fsp->myDataAddress.sin_port); - sprintf(port_buffer,"%s%" PRIu32 ",%" PRIu32 ",%" PRIu32 ",%" PRIu32 ",%" PRIu16 ",%" PRIu16 "\n", - FTP_PORT_CMD, - (my_ip >> 24) & 0x00ff, - (my_ip >> 16) & 0x00ff, - (my_ip >> 8) & 0x00ff, - (my_ip >> 0) & 0x00ff, - (my_port>> 8) & 0x00ff, - (my_port>> 0) & 0x00ff); -#ifdef DEBUG_OUT - write(1,port_buffer,strlen(port_buffer)); -#endif - if (0 > send(fsp->ctrl_socket,port_buffer,strlen(port_buffer),0)) { - eno = EIO; - } - else { - eno = rtems_ftp_get_message(fsp,&msg_tmp); - } + + /* Listen on port socket for incoming data connections */ + rv = listen( port_socket, 1); + if (rv != 0) { + eno = EBUSY; + goto cleanup; } - if (eno == 0) { - /* - * check for a "PORT command successful" reply - */ - if (msg_tmp != FTP_REPLY_SUCCESS) { - eno = EBUSY; - } + + /* Send RETR or STOR command with filename */ + reply = rtems_ftpfs_send_command( e->ctrl_socket, file_command, filename); + if (reply != RTEMS_FTPFS_REPLY_1) { + eno = EIO; + goto cleanup; } + /* - * prepare port socket to listen for incoming connections + * Wait for connect on data connection. + * + * FIXME: This should become a select instead with a timeout. */ - if (eno == 0) { - if (0 > listen(fsp->port_socket,1)) { - eno = EBUSY; - } + size = sizeof( sa); + e->data_socket = accept( + port_socket, + (struct sockaddr *) &sa, + &size + ); + if (e->data_socket < 0) { + eno = EIO; + goto cleanup; } - if (eno == 0) { - /* - * send retrive/store command with filename - */ - if (is_write) { -#ifdef DEBUG_OUT - write(1,FTP_STOR_CMD,strlen(FTP_STOR_CMD)); - write(1,filename ,strlen(filename) ); - write(1,"\n",1); -#endif - if ((0 > send(fsp->ctrl_socket,FTP_STOR_CMD,strlen(FTP_STOR_CMD),0)) || - (0 > send(fsp->ctrl_socket,filename, strlen(filename), 0)) || - (0 > send(fsp->ctrl_socket,"\n", 1, 0))) { - eno = EIO; - } + + /* FIXME: Check, that server data address is really from our server */ + +cleanup: + + /* Close port socket if necessary */ + if (port_socket >= 0) { + close( port_socket); + } + + return eno; +} + +typedef enum { + RTEMS_FTPFS_PASV_START = 0, + RTEMS_FTPFS_PASV_JUNK, + RTEMS_FTPFS_PASV_DATA, + RTEMS_FTPFS_PASV_DONE +} rtems_ftpfs_pasv_state; + +typedef struct { + rtems_ftpfs_pasv_state state; + uint8_t data [6]; + size_t index; +} rtems_ftpfs_pasv_entry; + +static void rtems_ftpfs_pasv_parser( + const char* buf, + size_t len, + void *arg +) +{ + rtems_ftpfs_pasv_entry *e = (rtems_ftpfs_pasv_entry *) arg; + size_t i = 0; + + for (i = 0; i < len; ++i) { + char c = buf [i]; + + switch (e->state) { + case RTEMS_FTPFS_PASV_START: + if (!isdigit( c)) { + e->state = RTEMS_FTPFS_PASV_JUNK; + e->index = 0; + } + break; + case RTEMS_FTPFS_PASV_JUNK: + if (isdigit( c)) { + e->state = RTEMS_FTPFS_PASV_DATA; + e->data [e->index] = (uint8_t) (c - '0'); + } + break; + case RTEMS_FTPFS_PASV_DATA: + if (isdigit( c)) { + e->data [e->index] = (uint8_t) (e->data [e->index] * 10 + c - '0'); + } else if (c == ',') { + ++e->index; + if (e->index < sizeof( e->data)) { + e->data [e->index] = 0; + } else { + e->state = RTEMS_FTPFS_PASV_DONE; + } + } else { + e->state = RTEMS_FTPFS_PASV_DONE; + } + break; + default: + return; } - else { -#ifdef DEBUG_OUT - write(1,FTP_RETR_CMD,strlen(FTP_RETR_CMD)); - write(1,filename ,strlen(filename) ); - write(1,"\n",1); -#endif - if ((0 > send(fsp->ctrl_socket,FTP_RETR_CMD,strlen(FTP_RETR_CMD),0)) || - (0 > send(fsp->ctrl_socket,filename, strlen(filename), 0)) || - (0 > send(fsp->ctrl_socket,"\n", 1, 0))) { - eno = EIO; - } - } } -#if 1 - if (eno == 0) { - eno = rtems_ftp_get_message(fsp,&msg_tmp); +} + +static int rtems_ftpfs_open_data_connection_passive( + rtems_ftpfs_entry *e, + uint32_t client_address, + const char *file_command, + const char *filename +) +{ + int rv = 0; + rtems_ftpfs_reply reply = RTEMS_FTPFS_REPLY_ERROR; + struct sockaddr_in sa; + socklen_t size = 0; + uint32_t data_address = 0; + uint16_t data_port = 0; + + rtems_ftpfs_pasv_entry pe = { + .state = RTEMS_FTPFS_PASV_START + }; + + /* Send PASV command */ + reply = rtems_ftpfs_send_command_with_parser( + e->ctrl_socket, + "PASV", + NULL, + rtems_ftpfs_pasv_parser, + &pe + ); + if (reply != RTEMS_FTPFS_REPLY_2) { + return ENOTSUP; } - if (eno == 0) { - /* - * check for a "OPENING binary connection" reply - */ - if (msg_tmp != FTP_REPLY_OPENCONN) { - eno = EACCES; - } + data_address = (uint32_t) ((pe.data [0] << 24) + (pe.data [1] << 16) + + (pe.data [2] << 8) + pe.data [3]); + data_port = (uint16_t) ((pe.data [4] << 8) + pe.data [5]); + rtems_ftpfs_create_address( &sa, htonl( data_address), htons( data_port)); + DEBUG_PRINTF( + "server data = %s:%u\n", + inet_ntoa( sa.sin_addr), + (unsigned) ntohs( sa.sin_port) + ); + + /* Create data socket */ + e->data_socket = socket( AF_INET, SOCK_STREAM, 0); + if (e->data_socket < 0) { + return ENOMEM; } -#endif - /* - * wait for connect on data connection - * FIXME: this should become a select instead with a timeout - */ - if (eno == 0) { - sockaddr_size = sizeof(fsp->farDataAddress); - fsp->data_socket = accept(fsp->port_socket, - (struct sockaddr *)&(fsp->farDataAddress), - &sockaddr_size); - if (0 > fsp->data_socket) { - eno = EIO; - } + + /* Open data connection */ + rv = connect( + e->data_socket, + (struct sockaddr *) &sa, + sizeof( sa) + ); + if (rv != 0) { + return EIO; } - /* - * FIXME: check, that fardataAddr is really from our ftp server - */ - - /* - * clean up temp strings... - */ - if (uname != NULL) { - free(uname); - uname = NULL; + + /* Send RETR or STOR command with filename */ + reply = rtems_ftpfs_send_command( e->ctrl_socket, file_command, filename); + if (reply != RTEMS_FTPFS_REPLY_1) { + return EIO; } - if (upass != NULL) { - free(upass); - upass = NULL; + + return 0; +} + +static int rtems_ftpfs_open( + rtems_libio_t *iop, + const char *path, + uint32_t flags, + uint32_t mode +) +{ + int rv = 0; + int eno = 0; + bool ok = false; + rtems_ftpfs_entry *e = NULL; + const char *user = NULL; + const char *password = NULL; + const char *hostname = NULL; + const char *filename = NULL; + const char *file_command = (iop->flags & LIBIO_FLAGS_WRITE) != 0 + ? "STOR " + : "RETR "; + uint32_t client_address = 0; + char *location = strdup( (const char *) iop->file_info); + + /* Check allocation */ + if (location == NULL) { + return ENOMEM; } - if (hostname != NULL) { - free(hostname); - hostname = NULL; + + /* Check for either read-only or write-only flags */ + if ( + (iop->flags & LIBIO_FLAGS_WRITE) != 0 + && (iop->flags & LIBIO_FLAGS_READ) != 0 + ) { + eno = ENOTSUP; + goto cleanup; } - if (filename != NULL) { - free(filename); - filename = NULL; + + /* Split location into parts */ + ok = rtems_ftpfs_split_names( + location, + &user, + &password, + &hostname, + &filename + ); + if (!ok) { + eno = ENOENT; + goto cleanup; } - /* - * close part socket, no longer needed - */ - if (fsp->port_socket != -1) { - close(fsp->port_socket); - fsp->port_socket = -1; + DEBUG_PRINTF( + "user = '%s', password = '%s', filename = '%s'\n", + user, + password, + filename + ); + + /* Allocate connection entry */ + e = malloc( sizeof( *e)); + if (e == NULL) { + eno = ENOMEM; + goto cleanup; } - /* - * if error, clean up everything - */ + + /* Initialize connection entry */ + e->ctrl_socket = -1; + e->data_socket = -1; + e->eof = false; + + /* Open control connection */ + eno = rtems_ftpfs_open_ctrl_connection( + e, + user, + password, + hostname, + &client_address + ); if (eno != 0) { - if (fsp != NULL) { - /* - * check and close ctrl/data connection - */ - if (fsp->data_socket != -1) { - close(fsp->data_socket); - fsp->data_socket = -1; - } - if (fsp->ctrl_socket != -1) { - close(fsp->ctrl_socket); - fsp->ctrl_socket = -1; - } - /* - * free ftpStream structure - */ - ftpStreams[s] = NULL; - free(fsp); - fsp = NULL; - } + goto cleanup; } - /* - * return sema, if still occupied - */ - if (sema_obtained) { - rtems_semaphore_release (ftp_mutex); - sema_obtained = false; + + /* Open passive data connection */ + eno = rtems_ftpfs_open_data_connection_passive( + e, + client_address, + file_command, + filename + ); + if (eno == ENOTSUP) { + /* Open active data connection */ + eno = rtems_ftpfs_open_data_connection_active( + e, + client_address, + file_command, + filename + ); } -#if 0 - if (eno != 0) { - set_errno_and_return_minus_one(eno); + +cleanup: + + /* Free location parts buffer */ + free( location); + + if (eno == 0) { + /* Save connection state */ + iop->data1 = e; + } else { + /* Free all resources if an error occured */ + rtems_ftpfs_terminate( e, iop); } - return 0; -#else + return eno; -#endif } -/* - * Read from a FTP stream - */ -ssize_t rtems_ftp_read( +static ssize_t rtems_ftpfs_read( rtems_libio_t *iop, - void *buffer, - size_t count + void *buffer, + size_t count ) { - int eno = 0; - struct ftpStream *fsp; - size_t want_cnt; - ssize_t rd_cnt; - int msg_tmp = 0; + rtems_ftpfs_entry *e = (rtems_ftpfs_entry *) iop->data1; + char *in = (char *) buffer; + size_t todo = count; - fsp = iop->data1; - want_cnt = count; - /* - * check, that data connection present - */ - if (eno == 0) { - if ((fsp == NULL) || - (fsp->data_socket < 0)) { - eno = EBADF; - } - } - - /* - * perform read from data socket - * read multiple junks, if smaller than wanted - */ - while ((eno == 0) && - (want_cnt > 0) && - !(fsp->eof_reached) ) { - rd_cnt = read(fsp->data_socket,buffer,want_cnt); - if (rd_cnt > 0) { - buffer += rd_cnt; - want_cnt -= rd_cnt; - } - else { - eno = rtems_ftp_get_message(fsp,&msg_tmp); - fsp->eof_reached = true; - if ((eno == 0) && - (msg_tmp != FTP_REPLY_TFERCMPL)) { - eno = EIO; - } - if (rd_cnt < 0) { - eno = EIO; + if (e->eof) { + return 0; + } + + while (todo > 0) { + ssize_t rv = recv( e->data_socket, in, todo, 0); + + if (rv <= 0) { + if (rv == 0) { + rtems_ftpfs_reply reply = + rtems_ftpfs_get_reply( e->ctrl_socket, NULL, NULL); + + if (reply == RTEMS_FTPFS_REPLY_2) { + e->eof = true; + break; + } } + + rtems_set_errno_and_return_minus_one( EIO); } + + in += rv; + todo -= (size_t) rv; } - if (eno != 0) { - set_errno_and_return_minus_one(eno); - } - return count - want_cnt; + + return (ssize_t) (count - todo); } -ssize_t rtems_ftp_write( +static ssize_t rtems_ftpfs_write( rtems_libio_t *iop, - const void *buffer, - size_t count + const void *buffer, + size_t count ) { - int eno = 0; - struct ftpStream *fsp; - size_t want_cnt; - ssize_t wr_cnt; - int msg_tmp = 0; + rtems_ftpfs_entry *e = (rtems_ftpfs_entry *) iop->data1; + const char *out = (const char *) buffer; + size_t todo = count; - fsp = iop->data1; - want_cnt = count; - /* - * check, that data connection present - */ - if (eno == 0) { - if ((fsp == NULL) || - (fsp->data_socket < 0)) { - eno = EBADF; - } - } - - /* - * perform write to data socket - */ - if (eno == 0) { - wr_cnt = write(fsp->data_socket,buffer,want_cnt); - if (wr_cnt > 0) { - buffer += wr_cnt; - want_cnt -= wr_cnt; - } - else { - eno = rtems_ftp_get_message(fsp,&msg_tmp); - if ((eno == 0) && - (msg_tmp != FTP_REPLY_TFERCMPL)) { - eno = EIO; - } - if (wr_cnt < 0) { - eno = EIO; + if (e->eof) { + return 0; + } + + while (todo > 0) { + ssize_t rv = send( e->data_socket, out, todo, 0); + + if (rv <= 0) { + if (rv == 0) { + rtems_ftpfs_reply reply = + rtems_ftpfs_get_reply( e->ctrl_socket, NULL, NULL); + + if (reply == RTEMS_FTPFS_REPLY_2) { + e->eof = true; + break; + } } + + rtems_set_errno_and_return_minus_one( EIO); } + + out += rv; + todo -= (size_t) rv; } - if (eno != 0) { - set_errno_and_return_minus_one(eno); - } - return count - want_cnt; + + return (ssize_t) (count - todo); } -/* - * Close a FTP stream - */ -int rtems_ftp_close( - rtems_libio_t *iop -) +static int rtems_ftpfs_close( rtems_libio_t *iop) { - int s = iop->data0; - struct ftpStream *fsp = iop->data1; + rtems_ftpfs_entry *e = (rtems_ftpfs_entry *) iop->data1; + rtems_ftpfs_terminate( e, iop); + + return 0; +} + +/* Dummy version to let fopen( *,"w") work properly */ +static int rtems_ftpfs_ftruncate( rtems_libio_t *iop, off_t count) +{ + return 0; +} + +static int rtems_ftpfs_eval_path( + const char *pathname, + int flags, + rtems_filesystem_location_info_t *pathloc +) +{ /* - * close ctrl/data connection, if needed - */ - if (fsp->data_socket >= 0) { - close(fsp->data_socket); - fsp->data_socket = -1; - } - if (fsp->ctrl_socket >= 0) { - close(fsp->ctrl_socket); - fsp->ctrl_socket = -1; - } - /* - * free any used space... + * The caller of this routine has striped off the mount prefix from the path. + * We need to store this path here or otherwise we would have to do this job + * again. It is not possible to allocate resources here since there is no + * way to free them later in every case. The path is used in + * rtems_ftpfs_open() via iop->file_info. + * + * FIXME: Avoid to discard the const qualifier. */ - rtems_semaphore_obtain (ftp_mutex, RTEMS_WAIT, RTEMS_NO_TIMEOUT); - free (ftpStreams[s]); - ftpStreams[s] = NULL; - rtems_semaphore_release (ftp_mutex); + pathloc->node_access = (void *) pathname; return 0; } -rtems_device_driver rtems_ftp_control( - rtems_device_major_number major, - rtems_device_minor_number minor, - void *pargp +static int rtems_ftpfs_eval_for_make( + const char *pathname, + rtems_filesystem_location_info_t *pathloc, + const char **name ) { - return RTEMS_NOT_CONFIGURED; + rtems_set_errno_and_return_minus_one( EIO); } -/* - * Dummy version to let fopen(xxxx,"w") work properly. - */ -static int rtems_ftp_ftruncate( - rtems_libio_t *iop, - off_t count +static rtems_filesystem_node_types_t rtems_ftpfs_node_type( + rtems_filesystem_location_info_t *pathloc ) { - return 0; + return RTEMS_FILESYSTEM_MEMORY_FILE; +} + +static const rtems_filesystem_file_handlers_r rtems_ftpfs_handlers; + +static int rtems_ftpfs_mount_me( + rtems_filesystem_mount_table_entry_t *e +) +{ + /* Set handler and oparations table */ + e->mt_fs_root.handlers = &rtems_ftpfs_handlers; + e->mt_fs_root.ops = &rtems_ftpfs_ops; + + /* We have no FTP file system specific data to maintain */ + e->fs_info = NULL; + + /* We maintain no real file system nodes, so there is no real root */ + e->mt_fs_root.node_access = NULL; + + /* Just use the limits from IMFS */ + e->pathconf_limits_and_options = IMFS_LIMITS_AND_OPTIONS; + + return 0; } -rtems_filesystem_node_types_t rtems_ftp_node_type( - rtems_filesystem_location_info_t *pathloc /* IN */ +/* + * The stat() support is intended only for the cp shell command. Each request + * will return that we have a regular file with read, write and execute + * permissions for every one. The node index uses a global counter to support + * a remote to remote copy. Is not a very sophisticated method. + */ +static int rtems_ftpfs_fstat( + rtems_filesystem_location_info_t *loc, + struct stat *st ) { - return RTEMS_FILESYSTEM_MEMORY_FILE; + static unsigned ino = 0; + + memset( st, 0, sizeof( *st)); + + /* FIXME */ + st->st_ino = ++ino; + + st->st_mode = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO; + + return 0; } -rtems_filesystem_operations_table rtems_ftp_ops = { - rtems_ftp_eval_path, /* eval_path */ - rtems_ftp_evaluate_for_make, /* evaluate_for_make */ - NULL, /* link */ - NULL, /* unlink */ - rtems_ftp_node_type, /* node_type */ - NULL, /* mknod */ - NULL, /* chown */ - NULL, /* freenodinfo */ - NULL, /* mount */ - rtems_ftp_mount_me, /* initialize */ - NULL, /* unmount */ - NULL, /* fsunmount */ - NULL, /* utime, */ - NULL, /* evaluate_link */ - NULL, /* symlink */ - NULL, /* readlin */ +const rtems_filesystem_operations_table rtems_ftpfs_ops = { + .evalpath_h = rtems_ftpfs_eval_path, + .evalformake_h = rtems_ftpfs_eval_for_make, + .link_h = NULL, + .unlink_h = NULL, + .node_type_h = rtems_ftpfs_node_type, + .mknod_h = NULL, + .chown_h = NULL, + .freenod_h = NULL, + .mount_h = NULL, + .fsmount_me_h = rtems_ftpfs_mount_me, + .unmount_h = NULL, + .fsunmount_me_h = NULL, + .utime_h = NULL, + .eval_link_h = NULL, + .symlink_h = NULL, + .readlink_h = NULL }; - -rtems_filesystem_file_handlers_r rtems_ftp_handlers = { - rtems_ftp_open, /* open */ - rtems_ftp_close, /* close */ - rtems_ftp_read, /* read */ - rtems_ftp_write, /* write */ - NULL, /* ioctl */ - NULL, /* lseek */ - NULL, /* fstat */ - NULL, /* fchmod */ - rtems_ftp_ftruncate, /* ftruncate */ - NULL, /* fpathconf */ - NULL, /* fsync */ - NULL, /* fdatasync */ - NULL, /* fcntl */ - NULL /* rmnod */ + +static const rtems_filesystem_file_handlers_r rtems_ftpfs_handlers = { + .open_h = rtems_ftpfs_open, + .close_h = rtems_ftpfs_close, + .read_h = rtems_ftpfs_read, + .write_h = rtems_ftpfs_write, + .ioctl_h = NULL, + .lseek_h = NULL, + .fstat_h = rtems_ftpfs_fstat, + .fchmod_h = NULL, + .ftruncate_h = rtems_ftpfs_ftruncate, + .fpathconf_h = NULL, + .fsync_h = NULL, + .fdatasync_h = NULL, + .fcntl_h = NULL, + .rmnod_h = NULL }; diff --git a/cpukit/libnetworking/rtems/ftpfs.h b/cpukit/libnetworking/rtems/ftpfs.h index e0592394f7..d3275ad4c1 100644 --- a/cpukit/libnetworking/rtems/ftpfs.h +++ b/cpukit/libnetworking/rtems/ftpfs.h @@ -1,12 +1,17 @@ /** - * @file rtems/ftpfs.h + * @file * - * File Transfer Protocol client declarations - * - * Transfer file to/from remote host + * @brief File Transfer Protocol file system (FTP client). */ /* + * Copyright (c) 2009 + * embedded brains GmbH + * Obere Lagerstr. 30 + * D-82178 Puchheim + * Germany + * + * * (c) Copyright 2002 * Thomas Doerfler * IMD Ingenieurbuero fuer Microcomputertechnik @@ -14,26 +19,115 @@ * 82178 Puchheim, Germany * * - * This code has been created after closly inspecting - * "tftpdriver.c" from Eric Norum. + * Modified by Sebastian Huber . + * + * This code has been created after closly inspecting "tftpdriver.c" from Eric + * Norum. * - * $Id$ + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.com/license/LICENSE. + * + * $Id$ */ #ifndef _RTEMS_FTPFS_H #define _RTEMS_FTPFS_H +#include + #ifdef __cplusplus extern "C" { #endif -#include +/** + * @defgroup rtems_ftpfs File Transfer Protocol File System + * + * The FTP file system (FTP client) can be used to transfer files from or to + * remote hosts. + * + * You can mount the FTP file system with a call to rtems_ftpfs_mount(). + * Alternatively you can use mount() with the @ref rtems_ftpfs_ops operations + * table. + * + * You can open files either read-only or write-only. A seek is not allowed. + * A close terminates the control and data connections. + * + * To open a file @c file.txt in the directory @c dir (relative to home + * directory of the server) on a server named @c host using the user name + * @c user and the password @c pw you must specify the following path: + * + * @c /FTP/user:pw@host/dir/file.txt + * + * If the server is the default server specified in BOOTP, it can be ommitted: + * + * @c /FTP/user:pw/dir/file.txt + * + * The user name will be used for the password if it is ommitted: + * + * @c /FTP/user@host/dir/file.txt + * + * For the data transfer passive (= default) and active (= fallback) mode are + * supported. + * + * @{ + */ - /* create mount point and mount ftp file system */ -extern int rtems_bsdnet_initialize_ftp_filesystem (void); +/** + * @brief Well-known port number for FTP control connection. + */ +#define RTEMS_FTPFS_CTRL_PORT 21 - /* FTP File sysem operations table. */ -extern rtems_filesystem_operations_table rtems_ftp_ops; +/** + * @brief Default mount point for FTP file system. + */ +#define RTEMS_FTPFS_MOUNT_POINT_DEFAULT "/FTP" + +/** + * @brief FTP file system operations table. + */ +extern const rtems_filesystem_operations_table rtems_ftpfs_ops; + +/** + * @brief Creates the mount point @a mount_point and mounts the FTP file + * system. + * + * If @a mount_point is @c NULL the default mount point + * @ref RTEMS_FTPFS_MOUNT_POINT_DEFAULT will be used. + * + * It is mounted with read and write access. + * + * @note The parent directories of the mount point have to exist. + */ +rtems_status_code rtems_ftpfs_mount( const char *mount_point); + +/** + * @brief Enables or disables the verbose mode if @a verbose is @c true or + * @c false respectively. + * + * In the enabled verbose mode the commands and replies of the FTP control + * connections will be printed to standard error. + */ +rtems_status_code rtems_ftpfs_set_verbose( bool verbose); + +/** + * @brief Returns in @a verbose if the verbose mode is enabled or disabled. + */ +rtems_status_code rtems_ftpfs_get_verbose( bool *verbose); + +/** @} */ + +/** + * @brief Creates the default mount point @ref RTEMS_FTPFS_MOUNT_POINT_DEFAULT + * and mounts the FTP file system. + * + * It is mounted with read and write access. + * + * On success, zero is returned. On error, -1 is returned. + * + * @deprecated Use rtems_ftpfs_mount() instead. + */ +int rtems_bsdnet_initialize_ftp_filesystem( void); #ifdef __cplusplus } -- cgit v1.2.3