From 68d406b3b8572dba875464f78ef36563df82ebbd Mon Sep 17 00:00:00 2001 From: Sebastian Huber Date: Thu, 9 Jun 2016 11:19:09 +0200 Subject: ftpfs: Import from RTEMS RTEMS Git commit 251c94d3d3d27e0039f01b718e5c2eb06f39fdf7. --- libbsd.py | 2 + libbsd_waf.py | 11 + rtemsbsd/ftpfs/ftpfs.c | 1428 ++++++++++++++++++++++++++++++++++++++++ rtemsbsd/include/rtems/ftpfs.h | 159 +++++ testsuite/ftpd02/test_main.c | 265 ++++++++ 5 files changed, 1865 insertions(+) create mode 100644 rtemsbsd/ftpfs/ftpfs.c create mode 100644 rtemsbsd/include/rtems/ftpfs.h create mode 100644 testsuite/ftpd02/test_main.c diff --git a/libbsd.py b/libbsd.py index 3d452290..1a4e8089 100755 --- a/libbsd.py +++ b/libbsd.py @@ -107,6 +107,7 @@ def rtems(mm): 'rtems/rtems-kvm.c', 'rtems/syslog.c', 'ftpd/ftpd.c', + 'ftpfs/ftpfs.c', 'mdns/mdns.c', 'mdns/mdns-hostname-default.c', 'pppd/auth.c', @@ -2428,6 +2429,7 @@ def tests(mm): runTest = False, netTest = True)) mod.addTest(mm.generator['test']('unix01', ['test_main'])) mod.addTest(mm.generator['test']('ftpd01', ['test_main'], netTest = True)) + mod.addTest(mm.generator['test']('ftpd02', ['test_main'])) mod.addTest(mm.generator['test']('ping01', ['test_main'], netTest = True)) mod.addTest(mm.generator['test']('selectpollkqueue01', ['test_main'])) mod.addTest(mm.generator['test']('rwlock01', ['test_main'])) diff --git a/libbsd_waf.py b/libbsd_waf.py index 5cdcd5ea..16ff863a 100644 --- a/libbsd_waf.py +++ b/libbsd_waf.py @@ -943,6 +943,7 @@ def build(bld): 'mDNSResponder/mDNSShared/dnssd_clientshim.c', 'mDNSResponder/mDNSShared/mDNSDebug.c', 'rtemsbsd/ftpd/ftpd.c', + 'rtemsbsd/ftpfs/ftpfs.c', 'rtemsbsd/local/bus_if.c', 'rtemsbsd/local/cryptodev_if.c', 'rtemsbsd/local/device_if.c', @@ -1192,6 +1193,16 @@ def build(bld): lib = ["m", "z"], install_path = None) + test_ftpd02 = ['testsuite/ftpd02/test_main.c'] + bld.program(target = "ftpd02.exe", + features = "cprogram", + cflags = cflags, + includes = includes, + source = test_ftpd02, + use = ["bsd"], + lib = ["m", "z"], + install_path = None) + test_init01 = ['testsuite/init01/test_main.c'] bld.program(target = "init01.exe", features = "cprogram", diff --git a/rtemsbsd/ftpfs/ftpfs.c b/rtemsbsd/ftpfs/ftpfs.c new file mode 100644 index 00000000..e016caeb --- /dev/null +++ b/rtemsbsd/ftpfs/ftpfs.c @@ -0,0 +1,1428 @@ +/** + * @file + * + * File Transfer Protocol file system (FTP client). + */ + +/* + * Copyright (c) 2009-2012 embedded brains GmbH. + * + * embedded brains GmbH + * Obere Lagerstr. 30 + * 82178 Puchheim + * Germany + * + * + * (c) Copyright 2002 + * Thomas Doerfler + * IMD Ingenieurbuero fuer Microcomputertechnik + * Herbststr. 8 + * 82178 Puchheim, Germany + * + * + * 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.org/license/LICENSE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifdef DEBUG + #define DEBUG_PRINTF(...) printf(__VA_ARGS__) +#else + #define DEBUG_PRINTF(...) +#endif + +/** + * Connection entry for each open file stream. + */ +typedef struct { + off_t file_size; + + /** + * Control connection socket. + */ + int ctrl_socket; + + uint32_t client_address; + + /** + * Data transfer socket. + */ + int data_socket; + + /** + * Current index into the reply buffer. + */ + size_t reply_current; + + /** + * End index of the reply buffer. + */ + size_t reply_end; + + /** + * Buffer for relpy data. + */ + char reply_buffer [128]; + + /** + * End of file flag. + */ + bool eof; + + bool write; + + /** + * Indicates if we should do a SIZE command. + * + * The first call to the rtems_ftpfs_fstat() handler is issued by the path + * evaluation to check for access permission. For this case we avoid the + * SIZE command. + */ + bool do_size_command; + + ino_t ino; + + const char *user; + + const char *password; + + const char *hostname; + + const char *filename; + + char buffer []; +} rtems_ftpfs_entry; + +/** + * Mount entry for each file system instance. + */ +typedef struct { + /** + * Verbose mode enabled or disabled. + */ + bool verbose; + + /** + * Timeout value + */ + struct timeval timeout; + + /** + * Inode counter. + */ + ino_t ino; +} rtems_ftpfs_mount_entry; + +static const rtems_filesystem_operations_table rtems_ftpfs_ops; + +static const rtems_filesystem_file_handlers_r rtems_ftpfs_handlers; + +static const rtems_filesystem_file_handlers_r rtems_ftpfs_root_handlers; + +static bool rtems_ftpfs_use_timeout(const struct timeval *to) +{ + return to->tv_sec != 0 || to->tv_usec != 0; +} + +static int rtems_ftpfs_set_connection_timeout( + int socket, + const struct timeval *to +) +{ + if (rtems_ftpfs_use_timeout(to)) { + int rv = 0; + + rv = setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, to, sizeof(*to)); + if (rv != 0) { + return EIO; + } + + rv = setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, to, sizeof(*to)); + if (rv != 0) { + return EIO; + } + } + + return 0; +} + +static rtems_status_code rtems_ftpfs_do_ioctl( + const char *mount_point, + ioctl_command_t req, + ... +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + int rv = 0; + int fd = 0; + va_list ap; + + if (mount_point == NULL) { + mount_point = RTEMS_FTPFS_MOUNT_POINT_DEFAULT; + } + + fd = open(mount_point, O_RDWR); + if (fd < 0) { + return RTEMS_INVALID_NAME; + } + + va_start(ap, req); + rv = ioctl(fd, req, va_arg(ap, void *)); + va_end(ap); + if (rv != 0) { + sc = RTEMS_INVALID_NUMBER; + } + + rv = close(fd); + if (rv != 0 && sc == RTEMS_SUCCESSFUL) { + sc = RTEMS_IO_ERROR; + } + + return sc; +} + +rtems_status_code rtems_ftpfs_get_verbose(const char *mount_point, bool *verbose) +{ + return rtems_ftpfs_do_ioctl( + mount_point, + RTEMS_FTPFS_IOCTL_GET_VERBOSE, + verbose + ); +} + +rtems_status_code rtems_ftpfs_set_verbose(const char *mount_point, bool verbose) +{ + return rtems_ftpfs_do_ioctl( + mount_point, + RTEMS_FTPFS_IOCTL_SET_VERBOSE, + &verbose + ); +} + +rtems_status_code rtems_ftpfs_get_timeout( + const char *mount_point, + struct timeval *timeout +) +{ + return rtems_ftpfs_do_ioctl( + mount_point, + RTEMS_FTPFS_IOCTL_GET_TIMEOUT, + timeout + ); +} + +rtems_status_code rtems_ftpfs_set_timeout( + const char *mount_point, + const struct timeval *timeout +) +{ + return rtems_ftpfs_do_ioctl( + mount_point, + RTEMS_FTPFS_IOCTL_SET_TIMEOUT, + timeout + ); +} + +typedef void (*rtems_ftpfs_reply_parser)( + const char * /* reply fragment */, + size_t /* reply fragment length */, + void * /* parser argument */ +); + +typedef enum { + RTEMS_FTPFS_REPLY_START, + RTEMS_FTPFS_REPLY_SINGLE_LINE, + RTEMS_FTPFS_REPLY_DONE, + RTEMS_FTPFS_REPLY_MULTI_LINE, + RTEMS_FTPFS_REPLY_MULTI_LINE_START +} rtems_ftpfs_reply_state; + +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; + +#define RTEMS_FTPFS_REPLY_SIZE 3 + +static bool rtems_ftpfs_is_reply_code_valid(unsigned char *reply) +{ + return isdigit(reply [0]) && isdigit(reply [1]) && isdigit(reply [2]); +} + +static rtems_ftpfs_reply rtems_ftpfs_get_reply( + rtems_ftpfs_entry *e, + rtems_ftpfs_reply_parser parser, + void *parser_arg, + bool verbose +) +{ + rtems_ftpfs_reply_state state = RTEMS_FTPFS_REPLY_START; + unsigned char reply_code [RTEMS_FTPFS_REPLY_SIZE] = { 'a', 'a', 'a' }; + size_t reply_code_index = 0; + + while (state != RTEMS_FTPFS_REPLY_DONE) { + char *buf = NULL; + size_t i = 0; + size_t n = 0; + + /* Receive reply fragment from socket */ + if (e->reply_current == e->reply_end) { + ssize_t rv = 0; + + e->reply_current = 0; + e->reply_end = 0; + + rv = recv( + e->ctrl_socket, + &e->reply_buffer [0], + sizeof(e->reply_buffer), + 0 + ); + + if (rv > 0) { + e->reply_end = (size_t) rv; + } else { + return RTEMS_FTPFS_REPLY_ERROR; + } + } + + buf = &e->reply_buffer [e->reply_current]; + n = e->reply_end - e->reply_current; + + /* Invoke parser if necessary */ + if (parser != NULL) { + parser(buf, n, parser_arg); + } + + /* Parse reply fragment */ + for (i = 0; i < n && state != RTEMS_FTPFS_REPLY_DONE; ++i) { + char c = buf [i]; + + switch (state) { + case RTEMS_FTPFS_REPLY_START: + if (reply_code_index < RTEMS_FTPFS_REPLY_SIZE) { + reply_code [reply_code_index] = c; + ++reply_code_index; + } else if (rtems_ftpfs_is_reply_code_valid(reply_code)) { + if (c == '-') { + state = RTEMS_FTPFS_REPLY_MULTI_LINE; + } else { + state = RTEMS_FTPFS_REPLY_SINGLE_LINE; + } + } else { + return RTEMS_FTPFS_REPLY_ERROR; + } + break; + case RTEMS_FTPFS_REPLY_SINGLE_LINE: + if (c == '\n') { + state = RTEMS_FTPFS_REPLY_DONE; + } + break; + case RTEMS_FTPFS_REPLY_MULTI_LINE: + if (c == '\n') { + state = RTEMS_FTPFS_REPLY_MULTI_LINE_START; + reply_code_index = 0; + } + break; + case RTEMS_FTPFS_REPLY_MULTI_LINE_START: + if (reply_code_index < RTEMS_FTPFS_REPLY_SIZE) { + if (reply_code [reply_code_index] == c) { + ++reply_code_index; + } else { + state = RTEMS_FTPFS_REPLY_MULTI_LINE; + } + } else { + if (c == ' ') { + state = RTEMS_FTPFS_REPLY_SINGLE_LINE; + } else { + state = RTEMS_FTPFS_REPLY_MULTI_LINE; + } + } + break; + default: + return RTEMS_FTPFS_REPLY_ERROR; + } + } + + /* Be verbose if necessary */ + if (verbose) { + write(STDERR_FILENO, buf, i); + } + + /* Update reply index */ + e->reply_current += i; + } + + return reply_code [0]; +} + +static rtems_ftpfs_reply rtems_ftpfs_send_command_with_parser( + rtems_ftpfs_entry *e, + const char *cmd, + const char *arg, + rtems_ftpfs_reply_parser parser, + void *parser_arg, + bool verbose +) +{ + rtems_ftpfs_reply reply = RTEMS_FTPFS_REPLY_ERROR; + size_t cmd_len = strlen(cmd); + size_t arg_len = arg != NULL ? strlen(arg) : 0; + size_t len = cmd_len + arg_len + 2; + char *buf = malloc(len); + + if (buf != NULL) { + ssize_t n = 0; + char *buf_arg = buf + cmd_len; + char *buf_eol = buf_arg + arg_len; + + memcpy(buf, cmd, cmd_len); + memcpy(buf_arg, arg, arg_len); + buf_eol [0] = '\r'; + buf_eol [1] = '\n'; + + /* Send */ + n = send(e->ctrl_socket, buf, len, 0); + if (n == (ssize_t) len) { + if (verbose) { + write(STDERR_FILENO, buf, len); + } + + /* Reply */ + reply = rtems_ftpfs_get_reply(e, parser, parser_arg, verbose); + } + + free(buf); + } + + return reply; +} + +static rtems_ftpfs_reply rtems_ftpfs_send_command( + rtems_ftpfs_entry *e, + const char *cmd, + const char *arg, + bool verbose +) +{ + return rtems_ftpfs_send_command_with_parser( + e, + cmd, + arg, + NULL, + NULL, + verbose + ); +} + +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; + +static int 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; + + *user = s; + + 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; + } + } + +done: + + /* This is a special case with no username and password */ + if (*hostname == NULL) { + *hostname = &s [0]; + *user = "anonymous"; + *password = *user; + } + + /* If we have no password use the user name */ + if (*password == NULL) { + *password = *user; + } + + return state == STATE_DONE ? 0 : ENOENT; +} + +static socklen_t rtems_ftpfs_create_address( + struct sockaddr_in *sa, + unsigned long address, + unsigned short port +) +{ + memset(sa, 0, sizeof(*sa)); + + sa->sin_family = AF_INET; + sa->sin_addr.s_addr = address; + sa->sin_port = port; + sa->sin_len = sizeof(*sa); + + return sizeof(*sa); +} + +static int rtems_ftpfs_close_data_connection( + rtems_ftpfs_entry *e, + bool verbose, + bool error +) +{ + int eno = 0; + + /* Close data connection if necessary */ + if (e->data_socket >= 0) { + int rv = close(e->data_socket); + + e->data_socket = -1; + if (rv != 0) { + eno = EIO; + } + + /* For write connections we have to obtain the transfer reply */ + if (e->write && !error) { + rtems_ftpfs_reply reply = + rtems_ftpfs_get_reply(e, NULL, NULL, verbose); + + if (reply != RTEMS_FTPFS_REPLY_2) { + eno = EIO; + } + } + } + + return eno; +} + +static int rtems_ftpfs_open_ctrl_connection( + rtems_ftpfs_entry *e, + bool verbose, + const struct timeval *timeout +) +{ + 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; + + /* Create the socket for the control connection */ + e->ctrl_socket = socket(AF_INET, SOCK_STREAM, 0); + if (e->ctrl_socket < 0) { + return ENOMEM; + } + + /* Set up the server address from the hostname */ + if (inet_aton(e->hostname, &address) == 0) { + /* Try to get the address by name */ + struct hostent *he = gethostbyname(e->hostname); + + if (he != NULL) { + memcpy(&address, he->h_addr, sizeof(address)); + } else { + return ENOENT; + } + } + 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; + } + + /* Set control connection timeout */ + eno = rtems_ftpfs_set_connection_timeout(e->ctrl_socket, timeout); + if (eno != 0) { + return eno; + } + + /* 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; + } + e->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, NULL, NULL, verbose); + if (reply != RTEMS_FTPFS_REPLY_2) { + return ENOENT; + } + + /* Send USER command */ + reply = rtems_ftpfs_send_command(e, "USER ", e->user, verbose); + if (reply == RTEMS_FTPFS_REPLY_3) { + /* Send PASS command */ + reply = rtems_ftpfs_send_command(e, "PASS ", e->password, verbose); + if (reply != RTEMS_FTPFS_REPLY_2) { + return EACCES; + } + + /* TODO: Some server may require an account */ + } else if (reply != RTEMS_FTPFS_REPLY_2) { + return EACCES; + } + + /* Send TYPE command to set binary mode for all data transfers */ + reply = rtems_ftpfs_send_command(e, "TYPE I", NULL, verbose); + if (reply != RTEMS_FTPFS_REPLY_2) { + return EIO; + } + + return 0; +} + +static int rtems_ftpfs_open_data_connection_active( + rtems_ftpfs_entry *e, + const char *file_command, + bool verbose, + const struct timeval *timeout +) +{ + int rv = 0; + int eno = 0; + rtems_ftpfs_reply reply = RTEMS_FTPFS_REPLY_ERROR; + 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; + } + + /* 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; + } + + /* 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; + } + data_port = ntohs(sa.sin_port); + + /* Send PORT command to set data connection port for server */ + snprintf( + port_command, + sizeof(port_command), + "PORT %lu,%lu,%lu,%lu,%lu,%lu", + (e->client_address >> 24) & 0xffUL, + (e->client_address >> 16) & 0xffUL, + (e->client_address >> 8) & 0xffUL, + (e->client_address >> 0) & 0xffUL, + (data_port >> 8) & 0xffUL, + (data_port >> 0) & 0xffUL + ); + reply = rtems_ftpfs_send_command(e, port_command, NULL, verbose); + if (reply != RTEMS_FTPFS_REPLY_2) { + eno = ENOTSUP; + goto cleanup; + } + + /* Listen on port socket for incoming data connections */ + rv = listen(port_socket, 1); + if (rv != 0) { + eno = EBUSY; + goto cleanup; + } + + /* Send RETR or STOR command with filename */ + reply = rtems_ftpfs_send_command(e, file_command, e->filename, verbose); + if (reply != RTEMS_FTPFS_REPLY_1) { + eno = EIO; + goto cleanup; + } + + /* Wait for connect on data connection if necessary */ + if (rtems_ftpfs_use_timeout(timeout)) { + struct timeval to = *timeout; + fd_set fds; + + FD_ZERO(&fds); + FD_SET(port_socket, &fds); + + rv = select(port_socket + 1, &fds, NULL, NULL, &to); + if (rv <= 0) { + eno = EIO; + goto cleanup; + } + } + + /* Accept data connection */ + size = sizeof(sa); + e->data_socket = accept( + port_socket, + (struct sockaddr *) &sa, + &size + ); + if (e->data_socket < 0) { + eno = EIO; + goto cleanup; + } + +cleanup: + + /* Close port socket if necessary */ + if (port_socket >= 0) { + rv = close(port_socket); + if (rv != 0) { + eno = EIO; + } + } + + 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; + size_t index; + uint8_t data [6]; +} rtems_ftpfs_pasv_entry; + +static void rtems_ftpfs_pasv_parser( + const char* buf, + size_t len, + void *arg +) +{ + rtems_ftpfs_pasv_entry *pe = arg; + size_t i = 0; + + for (i = 0; i < len; ++i) { + int c = buf [i]; + + switch (pe->state) { + case RTEMS_FTPFS_PASV_START: + if (!isdigit(c)) { + pe->state = RTEMS_FTPFS_PASV_JUNK; + pe->index = 0; + } + break; + case RTEMS_FTPFS_PASV_JUNK: + if (isdigit(c)) { + pe->state = RTEMS_FTPFS_PASV_DATA; + pe->data [pe->index] = (uint8_t) (c - '0'); + } + break; + case RTEMS_FTPFS_PASV_DATA: + if (isdigit(c)) { + pe->data [pe->index] = + (uint8_t) (pe->data [pe->index] * 10 + c - '0'); + } else if (c == ',') { + ++pe->index; + if (pe->index < sizeof(pe->data)) { + pe->data [pe->index] = 0; + } else { + pe->state = RTEMS_FTPFS_PASV_DONE; + } + } else { + pe->state = RTEMS_FTPFS_PASV_DONE; + } + break; + default: + return; + } + } +} + +static int rtems_ftpfs_open_data_connection_passive( + rtems_ftpfs_entry *e, + const char *file_command, + bool verbose, + const struct timeval *timeout +) +{ + int rv = 0; + rtems_ftpfs_reply reply = RTEMS_FTPFS_REPLY_ERROR; + struct sockaddr_in sa; + uint32_t data_address = 0; + uint16_t data_port = 0; + rtems_ftpfs_pasv_entry pe; + + memset(&pe, 0, sizeof(pe)); + + /* Send PASV command */ + reply = rtems_ftpfs_send_command_with_parser( + e, + "PASV", + NULL, + rtems_ftpfs_pasv_parser, + &pe, + verbose + ); + if (reply != RTEMS_FTPFS_REPLY_2) { + return ENOTSUP; + } + data_address = ((uint32_t)(pe.data [0]) << 24) + + ((uint32_t)(pe.data [1]) << 16) + + ((uint32_t)(pe.data [2]) << 8) + + ((uint32_t)(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; + } + + /* Open data connection */ + rv = connect( + e->data_socket, + (struct sockaddr *) &sa, + sizeof(sa) + ); + if (rv != 0) { + return EIO; + } + + /* Send RETR or STOR command with filename */ + reply = rtems_ftpfs_send_command(e, file_command, e->filename, verbose); + if (reply != RTEMS_FTPFS_REPLY_1) { + return EIO; + } + + return 0; +} + +typedef enum { + RTEMS_FTPFS_SIZE_START = 0, + RTEMS_FTPFS_SIZE_SPACE, + RTEMS_FTPFS_SIZE_NUMBER, + RTEMS_FTPFS_SIZE_NL +} rtems_ftpfs_size_state; + +typedef struct { + rtems_ftpfs_size_state state; + size_t index; + off_t size; +} rtems_ftpfs_size_entry; + +static void rtems_ftpfs_size_parser( + const char* buf, + size_t len, + void *arg +) +{ + rtems_ftpfs_size_entry *se = arg; + size_t i = 0; + + for (i = 0; se->size >= 0 && i < len; ++i, ++se->index) { + int c = buf [i]; + + switch (se->state) { + case RTEMS_FTPFS_SIZE_START: + if (se->index == 2) { + se->state = RTEMS_FTPFS_SIZE_SPACE; + } + break; + case RTEMS_FTPFS_SIZE_SPACE: + if (c == ' ') { + se->state = RTEMS_FTPFS_SIZE_NUMBER; + } else { + se->size = -1; + } + break; + case RTEMS_FTPFS_SIZE_NUMBER: + if (isdigit(c)) { + se->size = 10 * se->size + c - '0'; + } else if (c == '\r') { + se->state = RTEMS_FTPFS_SIZE_NL; + } else { + se->size = -1; + } + break; + case RTEMS_FTPFS_SIZE_NL: + if (c != '\n') { + se->size = -1; + } + break; + default: + se->size = -1; + break; + } + } +} + +static void rtems_ftpfs_get_file_size(rtems_ftpfs_entry *e, bool verbose) +{ + if (e->file_size < 0) { + if (e->write) { + e->file_size = 0; + } else { + rtems_ftpfs_size_entry se; + rtems_ftpfs_reply reply = RTEMS_FTPFS_REPLY_ERROR; + + memset(&se, 0, sizeof(se)); + + reply = rtems_ftpfs_send_command_with_parser( + e, + "SIZE ", + e->filename, + rtems_ftpfs_size_parser, + &se, + verbose + ); + if (reply == RTEMS_FTPFS_REPLY_2 && se.size >= 0) { + e->file_size = se.size; + } else { + e->file_size = 0; + } + } + } +} + +static int rtems_ftpfs_open( + rtems_libio_t *iop, + const char *path, + int oflag, + mode_t mode +) +{ + int eno = 0; + rtems_ftpfs_entry *e = iop->pathinfo.node_access; + rtems_ftpfs_mount_entry *me = iop->pathinfo.mt_entry->fs_info; + bool verbose = me->verbose; + const struct timeval *timeout = &me->timeout; + + e->write = (iop->flags & LIBIO_FLAGS_WRITE) != 0; + + /* Check for either read-only or write-only flags */ + if ( + (iop->flags & LIBIO_FLAGS_WRITE) != 0 + && (iop->flags & LIBIO_FLAGS_READ) != 0 + ) { + eno = ENOTSUP; + } + + if (eno == 0) { + rtems_ftpfs_get_file_size(e, verbose); + } + + if (eno == 0) { + const char *file_command = e->write ? "STOR " : "RETR "; + + /* Open passive data connection */ + eno = rtems_ftpfs_open_data_connection_passive( + e, + file_command, + verbose, + timeout + ); + if (eno == ENOTSUP) { + /* Open active data connection */ + eno = rtems_ftpfs_open_data_connection_active( + e, + file_command, + verbose, + timeout + ); + } + } + + /* Set data connection timeout */ + if (eno == 0) { + eno = rtems_ftpfs_set_connection_timeout(e->data_socket, timeout); + } + + if (eno == 0) { + return 0; + } else { + rtems_ftpfs_close_data_connection(e, verbose, true); + + rtems_set_errno_and_return_minus_one(eno); + } +} + +static ssize_t rtems_ftpfs_read( + rtems_libio_t *iop, + void *buffer, + size_t count +) +{ + rtems_ftpfs_entry *e = iop->pathinfo.node_access; + const rtems_ftpfs_mount_entry *me = iop->pathinfo.mt_entry->fs_info; + bool verbose = me->verbose; + char *in = buffer; + size_t todo = count; + + 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, NULL, NULL, verbose); + + if (reply == RTEMS_FTPFS_REPLY_2) { + e->eof = true; + break; + } + } + + rtems_set_errno_and_return_minus_one(EIO); + } + + in += rv; + todo -= (size_t) rv; + } + + return (ssize_t) (count - todo); +} + +static ssize_t rtems_ftpfs_write( + rtems_libio_t *iop, + const void *buffer, + size_t count +) +{ + rtems_ftpfs_entry *e = iop->pathinfo.node_access; + const char *out = buffer; + size_t todo = count; + + while (todo > 0) { + ssize_t rv = send(e->data_socket, out, todo, 0); + + if (rv <= 0) { + if (rv == 0) { + break; + } else { + rtems_set_errno_and_return_minus_one(EIO); + } + } + + out += rv; + todo -= (size_t) rv; + + e->file_size += rv; + } + + return (ssize_t) (count - todo); +} + +static int rtems_ftpfs_close(rtems_libio_t *iop) +{ + rtems_ftpfs_entry *e = iop->pathinfo.node_access; + const rtems_ftpfs_mount_entry *me = iop->pathinfo.mt_entry->fs_info; + int eno = rtems_ftpfs_close_data_connection(e, me->verbose, false); + + if (eno == 0) { + return 0; + } else { + rtems_set_errno_and_return_minus_one(eno); + } +} + +/* Dummy version to let fopen(*,"w") work properly */ +static int rtems_ftpfs_ftruncate(rtems_libio_t *iop, off_t count) +{ + return 0; +} + +static void rtems_ftpfs_eval_path( + rtems_filesystem_eval_path_context_t *self +) +{ + int eno = 0; + + rtems_filesystem_eval_path_eat_delimiter(self); + + if (rtems_filesystem_eval_path_has_path(self)) { + const char *path = rtems_filesystem_eval_path_get_path(self); + size_t pathlen = rtems_filesystem_eval_path_get_pathlen(self); + rtems_ftpfs_entry *e = calloc(1, sizeof(*e) + pathlen + 1); + + rtems_filesystem_eval_path_clear_path(self); + + if (e != NULL) { + memcpy(e->buffer, path, pathlen); + + eno = rtems_ftpfs_split_names( + e->buffer, + &e->user, + &e->password, + &e->hostname, + &e->filename + ); + + DEBUG_PRINTF( + "user = '%s', password = '%s', filename = '%s'\n", + e->user, + e->password, + e->filename + ); + + if (eno == 0) { + rtems_filesystem_location_info_t *currentloc = + rtems_filesystem_eval_path_get_currentloc(self); + rtems_ftpfs_mount_entry *me = currentloc->mt_entry->fs_info; + + rtems_libio_lock(); + ++me->ino; + e->ino = me->ino; + rtems_libio_unlock(); + + e->file_size = -1; + e->ctrl_socket = -1; + + eno = rtems_ftpfs_open_ctrl_connection( + e, + me->verbose, + &me->timeout + ); + if (eno == 0) { + currentloc->node_access = e; + currentloc->handlers = &rtems_ftpfs_handlers; + } + } + + if (eno != 0) { + free(e); + } + } else { + eno = ENOMEM; + } + } + + if (eno != 0) { + rtems_filesystem_eval_path_error(self, eno); + } +} + +static void rtems_ftpfs_free_node(const rtems_filesystem_location_info_t *loc) +{ + rtems_ftpfs_entry *e = loc->node_access; + + /* The root node handler has no entry */ + if (e != NULL) { + const rtems_ftpfs_mount_entry *me = loc->mt_entry->fs_info; + + /* Close control connection if necessary */ + if (e->ctrl_socket >= 0) { + rtems_ftpfs_send_command(e, "QUIT", NULL, me->verbose); + + close(e->ctrl_socket); + } + + free(e); + } +} + +int rtems_ftpfs_initialize( + rtems_filesystem_mount_table_entry_t *e, + const void *d +) +{ + rtems_ftpfs_mount_entry *me = calloc(1, sizeof(*me)); + + /* Mount entry for FTP file system instance */ + e->fs_info = me; + if (e->fs_info == NULL) { + rtems_set_errno_and_return_minus_one(ENOMEM); + } + me->verbose = false; + me->timeout.tv_sec = 0; + me->timeout.tv_usec = 0; + + /* Set handler and oparations table */ + e->mt_fs_root->location.handlers = &rtems_ftpfs_root_handlers; + e->ops = &rtems_ftpfs_ops; + + /* We maintain no real file system nodes, so there is no real root */ + e->mt_fs_root->location.node_access = NULL; + + return 0; +} + +static void rtems_ftpfs_unmount_me( + rtems_filesystem_mount_table_entry_t *e +) +{ + free(e->fs_info); +} + +static int rtems_ftpfs_ioctl( + rtems_libio_t *iop, + uint32_t command, + void *arg +) +{ + rtems_ftpfs_mount_entry *me = iop->pathinfo.mt_entry->fs_info; + bool *verbose = arg; + struct timeval *timeout = arg; + + if (arg == NULL) { + rtems_set_errno_and_return_minus_one(EINVAL); + } + + switch (command) { + case RTEMS_FTPFS_IOCTL_GET_VERBOSE: + *verbose = me->verbose; + break; + case RTEMS_FTPFS_IOCTL_SET_VERBOSE: + me->verbose = *verbose; + break; + case RTEMS_FTPFS_IOCTL_GET_TIMEOUT: + *timeout = me->timeout; + break; + case RTEMS_FTPFS_IOCTL_SET_TIMEOUT: + me->timeout = *timeout; + break; + default: + rtems_set_errno_and_return_minus_one(EINVAL); + } + + return 0; +} + +/* + * 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. This is not a very sophisticated method. + */ +static int rtems_ftpfs_fstat( + const rtems_filesystem_location_info_t *loc, + struct stat *st +) +{ + int eno = 0; + rtems_ftpfs_entry *e = loc->node_access; + + /* FIXME */ + st->st_ino = e->ino; + st->st_dev = rtems_filesystem_make_dev_t(0xcc494cd6U, 0x1d970b4dU); + + st->st_mode = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO; + + if (e->do_size_command) { + const rtems_ftpfs_mount_entry *me = loc->mt_entry->fs_info; + + rtems_ftpfs_get_file_size(e, me->verbose); + st->st_size = e->file_size; + } else { + e->do_size_command = true; + } + + if (eno == 0) { + return 0; + } else { + rtems_set_errno_and_return_minus_one(eno); + } +} + +static void rtems_ftpfs_lock_or_unlock( + const rtems_filesystem_mount_table_entry_t *mt_entry +) +{ + /* Do nothing */ +} + +static const rtems_filesystem_operations_table rtems_ftpfs_ops = { + .lock_h = rtems_ftpfs_lock_or_unlock, + .unlock_h = rtems_ftpfs_lock_or_unlock, + .eval_path_h = rtems_ftpfs_eval_path, + .link_h = rtems_filesystem_default_link, + .are_nodes_equal_h = rtems_filesystem_default_are_nodes_equal, + .mknod_h = rtems_filesystem_default_mknod, + .rmnod_h = rtems_filesystem_default_rmnod, + .fchmod_h = rtems_filesystem_default_fchmod, + .chown_h = rtems_filesystem_default_chown, + .clonenod_h = rtems_filesystem_default_clonenode, + .freenod_h = rtems_ftpfs_free_node, + .mount_h = rtems_filesystem_default_mount, + .unmount_h = rtems_filesystem_default_unmount, + .fsunmount_me_h = rtems_ftpfs_unmount_me, + .utime_h = rtems_filesystem_default_utime, + .symlink_h = rtems_filesystem_default_symlink, + .readlink_h = rtems_filesystem_default_readlink, + .rename_h = rtems_filesystem_default_rename, + .statvfs_h = rtems_filesystem_default_statvfs +}; + +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 = rtems_filesystem_default_ioctl, + .lseek_h = rtems_filesystem_default_lseek, + .fstat_h = rtems_ftpfs_fstat, + .ftruncate_h = rtems_ftpfs_ftruncate, + .fsync_h = rtems_filesystem_default_fsync_or_fdatasync, + .fdatasync_h = rtems_filesystem_default_fsync_or_fdatasync, + .fcntl_h = rtems_filesystem_default_fcntl, + .kqfilter_h = rtems_filesystem_default_kqfilter, + .poll_h = rtems_filesystem_default_poll, + .readv_h = rtems_filesystem_default_readv, + .writev_h = rtems_filesystem_default_writev +}; + +static const rtems_filesystem_file_handlers_r rtems_ftpfs_root_handlers = { + .open_h = rtems_filesystem_default_open, + .close_h = rtems_filesystem_default_close, + .read_h = rtems_filesystem_default_read, + .write_h = rtems_filesystem_default_write, + .ioctl_h = rtems_ftpfs_ioctl, + .lseek_h = rtems_filesystem_default_lseek, + .fstat_h = rtems_filesystem_default_fstat, + .ftruncate_h = rtems_filesystem_default_ftruncate, + .fsync_h = rtems_filesystem_default_fsync_or_fdatasync, + .fdatasync_h = rtems_filesystem_default_fsync_or_fdatasync, + .fcntl_h = rtems_filesystem_default_fcntl, + .kqfilter_h = rtems_filesystem_default_kqfilter, + .poll_h = rtems_filesystem_default_poll, + .readv_h = rtems_filesystem_default_readv, + .writev_h = rtems_filesystem_default_writev +}; diff --git a/rtemsbsd/include/rtems/ftpfs.h b/rtemsbsd/include/rtems/ftpfs.h new file mode 100644 index 00000000..c1f615b8 --- /dev/null +++ b/rtemsbsd/include/rtems/ftpfs.h @@ -0,0 +1,159 @@ +/** + * @file + * + * @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 + * + * + * 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.org/license/LICENSE. + */ + +#ifndef _RTEMS_FTPFS_H +#define _RTEMS_FTPFS_H + +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup rtems_ftpfs File Transfer Protocol File System + * + * @brief 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 mount() or + * mount_and_make_target_path() with the @ref RTEMS_FILESYSTEM_TYPE_FTPFS file + * system type. + * + * You have to add @ref CONFIGURE_FILESYSTEM_FTPFS to your application + * configuration. + * + * 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: + * /FTP/user:pw@@host/dir/file.txt. + * + * If the server is the default server specified in BOOTP, it can be ommitted: + * /FTP/user:pw/dir/file.txt. + * + * The user name will be used for the password if it is ommitted: + * /FTP/user@@host/dir/file.txt. + * + * For the data transfer passive (= default) and active (= fallback) mode are + * supported. + */ +/**@{**/ + +/** + * @brief Well-known port number for FTP control connection. + */ +#define RTEMS_FTPFS_CTRL_PORT 21 + +/** + * @brief Default mount point for FTP file system. + */ +#define RTEMS_FTPFS_MOUNT_POINT_DEFAULT "/FTP" + +/** + * @brief FTP file system IO control requests. + */ +typedef enum { + RTEMS_FTPFS_IOCTL_GET_VERBOSE = _IOR( 'd', 1, bool *), + RTEMS_FTPFS_IOCTL_SET_VERBOSE = _IOW( 'd', 1, bool *), + RTEMS_FTPFS_IOCTL_GET_TIMEOUT = _IOR( 'd', 2, struct timeval *), + RTEMS_FTPFS_IOCTL_SET_TIMEOUT = _IOW( 'd', 2, struct timeval *) +} rtems_ftpfs_ioctl_numbers; + +/** + * @brief Returns in @a verbose if the verbose mode is enabled or disabled for + * the file system at @a mount_point. + * + * If @a mount_point is @c NULL the default mount point + * @ref RTEMS_FTPFS_MOUNT_POINT_DEFAULT will be used. + */ +rtems_status_code rtems_ftpfs_get_verbose( const char *mount_point, bool *verbose); + +/** + * @brief Enables or disables the verbose mode if @a verbose is @c true or + * @c false respectively for the file system at @a mount_point. + * + * In the enabled verbose mode the commands and replies of the FTP control + * connections will be printed to standard error. + * + * If @a mount_point is @c NULL the default mount point + * @ref RTEMS_FTPFS_MOUNT_POINT_DEFAULT will be used. + */ +rtems_status_code rtems_ftpfs_set_verbose( const char *mount_point, bool verbose); + +/** + * @brief Returns the current timeout value in @a timeout for the file system + * at @a mount_point. + * + * If @a mount_point is @c NULL the default mount point + * @ref RTEMS_FTPFS_MOUNT_POINT_DEFAULT will be used. + */ +rtems_status_code rtems_ftpfs_get_timeout( + const char *mount_point, + struct timeval *timeout +); + +/** + * @brief Sets the timeout value to @a timeout for the file system at + * @a mount_point. + * + * The timeout value will be used during connection establishment of active + * data connections. It will be also used for send and receive operations on + * data and control connections. + * + * If @a mount_point is @c NULL the default mount point + * @ref RTEMS_FTPFS_MOUNT_POINT_DEFAULT will be used. + */ +rtems_status_code rtems_ftpfs_set_timeout( + const char *mount_point, + const struct timeval *timeout +); + +/** @} */ + +/** + * @brief Do not call directly, use mount(). + */ +int rtems_ftpfs_initialize( + rtems_filesystem_mount_table_entry_t *mt_entry, + const void *data +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/testsuite/ftpd02/test_main.c b/testsuite/ftpd02/test_main.c new file mode 100644 index 00000000..457192b4 --- /dev/null +++ b/testsuite/ftpd02/test_main.c @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2011, 2016 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Dornierstr. 4 + * 82178 Puchheim + * Germany + * + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#define TEST_NAME "LIBBSD FTPD 2" + +#define FTP_WORKER_TASK_COUNT 2 + +#define FTP_WORKER_TASK_EXTRA_STACK (FTP_WORKER_TASK_COUNT * FTPD_STACKSIZE) + +struct rtems_ftpd_configuration rtems_ftpd_configuration = { + /* FTPD task priority */ + .priority = 100, + + /* Maximum buffersize for hooks */ + .max_hook_filesize = 0, + + /* Well-known port */ + .port = 21, + + /* List of hooks */ + .hooks = NULL, + + /* Root for FTPD or NULL for "/" */ + .root = NULL, + + /* Max. connections */ + .tasks_count = 4, + + /* Idle timeout in seconds or 0 for no (infinite) timeout */ + .idle = 5 * 60, + + /* Access: 0 - r/w, 1 - read-only, 2 - write-only, 3 - browse-only */ + .access = 0 +}; + +static const char content[] = +" LICENSE INFORMATION\n" +"\n" +"RTEMS is free software; you can redistribute it and/or modify it under\n" +"terms of the GNU General Public License as published by the\n" +"Free Software Foundation; either version 2, or (at your option) any\n" +"later version. RTEMS is distributed in the hope that it will be useful,\n" +"but WITHOUT ANY WARRANTY; without even the implied warranty of\n" +"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" +"General Public License for more details. You should have received\n" +"a copy of the GNU General Public License along with RTEMS; see\n" +"file COPYING. If not, write to the Free Software Foundation, 675\n" +"Mass Ave, Cambridge, MA 02139, USA.\n" +"\n" +"As a special exception, including RTEMS header files in a file,\n" +"instantiating RTEMS generics or templates, or linking other files\n" +"with RTEMS objects to produce an executable application, does not\n" +"by itself cause the resulting executable application to be covered\n" +"by the GNU General Public License. This exception does not\n" +"however invalidate any other reasons why the executable file might be\n" +"covered by the GNU Public License.\n"; + +static void +initialize_ftpfs(void) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + int rv = 0; + struct timeval to = { + .tv_sec = 10, + .tv_usec = 0 + }; + const char *target = RTEMS_FTPFS_MOUNT_POINT_DEFAULT; + + rv = mount_and_make_target_path( + NULL, + target, + RTEMS_FILESYSTEM_TYPE_FTPFS, + RTEMS_FILESYSTEM_READ_WRITE, + NULL + ); + assert(rv == 0); + + sc = rtems_ftpfs_set_verbose(target, true); + assert(sc == RTEMS_SUCCESSFUL); + + sc = rtems_ftpfs_set_timeout(target, &to); + assert(sc == RTEMS_SUCCESSFUL); +} + +static void +change_self_priority(void) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_task_priority cur = 0; + + sc = rtems_task_set_priority(RTEMS_SUCCESSFUL, 110, &cur); + assert(sc == RTEMS_SUCCESSFUL); +} + +static void +create_file(const char *path, const void *begin, size_t size) +{ + int rv = 0; + int fd = open(path, O_WRONLY); + ssize_t n = 0; + + assert(fd >= 0); + + n = write(fd, begin, size); + assert(n == (ssize_t) size); + + rv = close(fd); + assert(rv == 0); +} + +static void +copy_file(const char *src_path, const char *dest_path) +{ + int rv = 0; + int in = open(src_path, O_RDONLY); + int out = open(dest_path, O_WRONLY); + ssize_t n_in = 0; + char buf [64]; + struct stat st_in; + struct stat st_out; + + memset(&st_in, 0xff, sizeof(st_in)); + memset(&st_out, 0xff, sizeof(st_out)); + + assert(in >= 0); + assert(out >= 0); + + rv = fstat(out, &st_out); + assert(rv == 0); + + assert(st_out.st_size == 0); + + while ((n_in = read(in, buf, sizeof(buf))) > 0) { + ssize_t n_out = write(out, buf, (size_t) n_in); + assert(n_out == n_in); + } + + rv = fstat(out, &st_out); + assert(rv == 0); + + rv = fstat(in, &st_in); + assert(rv == 0); + + assert(st_in.st_size == st_out.st_size); + + rv = close(out); + assert(rv == 0); + + rv = close(in); + assert(rv == 0); +} + +static void +check_file_size(const char *path, size_t size) +{ + struct stat st; + int rv = lstat(path, &st); + + assert(rv == 0); + assert(st.st_size == (off_t) size); +} + +static void +check_file(const char *path) +{ + int rv = 0; + int fd = open(path, O_RDONLY); + ssize_t n = 0; + char buf [64]; + const char *current = &content [0]; + size_t done = 0; + + assert(fd >= 0); + + while ((n = read(fd, buf, sizeof(buf))) > 0) { + done += (size_t) n; + assert(done <= sizeof(content)); + assert(memcmp(current, buf, (size_t) n) == 0); + current += (size_t) n; + } + + assert(done == sizeof(content)); + + rv = close(fd); + assert(rv == 0); +} + +static void +test_main(void) +{ + const char file_a[] = "/FTP/127.0.0.1/a.txt"; + const char file_b[] = "/FTP/127.0.0.1/b.txt"; + char *lo0[] = { + "ifconfig", + "lo0", + "inet", + "127.0.0.1", + "netmask", + "255.255.255.0", + NULL + }; + int rv; + int exit_code; + + exit_code = rtems_bsd_command_ifconfig(RTEMS_BSD_ARGC(lo0), lo0); + assert(exit_code == EXIT_SUCCESS); + + rv = rtems_initialize_ftpd(); + assert(rv == 0); + + initialize_ftpfs(); + change_self_priority(); + create_file(file_a, &content [0], sizeof(content)); + copy_file(file_a, file_b); + check_file(file_b); + check_file_size(file_a, sizeof(content)); + check_file_size(file_b, sizeof(content)); + + exit(0); +} + +#define CONFIGURE_FILESYSTEM_FTPFS + +#include -- cgit v1.2.3