[rtems-libbsd commit] ftpfs: Import from RTEMS

Sebastian Huber sebh at rtems.org
Fri Jun 10 12:15:35 UTC 2016


Module:    rtems-libbsd
Branch:    master
Commit:    68d406b3b8572dba875464f78ef36563df82ebbd
Changeset: http://git.rtems.org/rtems-libbsd/commit/?id=68d406b3b8572dba875464f78ef36563df82ebbd

Author:    Sebastian Huber <sebastian.huber at embedded-brains.de>
Date:      Thu Jun  9 11:19:09 2016 +0200

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(+)

diff --git a/libbsd.py b/libbsd.py
index 3d45229..1a4e808 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 5cdcd5e..16ff863 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 0000000..e016cae
--- /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
+ *  <rtems at embedded-brains.de>
+ *
+ * (c) Copyright 2002
+ * Thomas Doerfler
+ * IMD Ingenieurbuero fuer Microcomputertechnik
+ * Herbststr. 8
+ * 82178 Puchheim, Germany
+ * <Thomas.Doerfler at imd-systems.de>
+ *
+ * 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 <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <malloc.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <rtems.h>
+#include <rtems/ftpfs.h>
+#include <rtems/libio_.h>
+#include <rtems/seterr.h>
+
+#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 0000000..c1f615b
--- /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
+ * <rtems at embedded-brains.de>
+ *
+ * (c) Copyright 2002
+ * Thomas Doerfler
+ * IMD Ingenieurbuero fuer Microcomputertechnik
+ * Herbststr. 8
+ * 82178 Puchheim, Germany
+ * <Thomas.Doerfler at imd-systems.de>
+ *
+ * Modified by Sebastian Huber <sebastian.huber at embedded-brains.de>.
+ *
+ * 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 <sys/time.h>
+#include <sys/ioctl.h>
+
+#include <rtems/libio.h>
+
+#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:
+ * <tt>/FTP/user:pw@@host/dir/file.txt</tt>.
+ *
+ * If the server is the default server specified in BOOTP, it can be ommitted:
+ * <tt>/FTP/user:pw/dir/file.txt</tt>.
+ *
+ * The user name will be used for the password if it is ommitted:
+ * <tt>/FTP/user@@host/dir/file.txt</tt>.
+ *
+ * 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 0000000..457192b
--- /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
+ *  <rtems at embedded-brains.de>
+ *
+ * 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 <sys/stat.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stdlib.h>
+
+#include <rtems.h>
+#include <rtems/ftpd.h>
+#include <rtems/ftpfs.h>
+#include <rtems/shell.h>
+#include <rtems/console.h>
+
+#include <machine/rtems-bsd-commands.h>
+
+#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 <rtems/bsd/test/default-init.h>



More information about the vc mailing list