summaryrefslogtreecommitdiffstats
path: root/cpukit/libfs
diff options
context:
space:
mode:
authorVijay Kumar Banerjee <vijay@rtems.org>2021-02-24 12:27:29 -0700
committerVijay Kumar Banerjee <vijay@rtems.org>2021-04-07 16:15:38 -0600
commita28eafb2a4d7418ad1efb3f8b72e9cbb014c1c0b (patch)
treed5a5703fd9f0fb924a2f2de14dc58fc5fe44dc4a /cpukit/libfs
parentspec: Remove RTEMS_NETWORKING options (diff)
downloadrtems-a28eafb2a4d7418ad1efb3f8b72e9cbb014c1c0b.tar.bz2
cpukit: Move ftpfs from libnetworking to libfs
Update #3850
Diffstat (limited to 'cpukit/libfs')
-rw-r--r--cpukit/libfs/src/ftpfs/ftpfs.c1430
-rw-r--r--cpukit/libfs/src/ftpfs/tftpDriver.c1066
2 files changed, 2496 insertions, 0 deletions
diff --git a/cpukit/libfs/src/ftpfs/ftpfs.c b/cpukit/libfs/src/ftpfs/ftpfs.c
new file mode 100644
index 0000000000..5e0cb95dd3
--- /dev/null
+++ b/cpukit/libfs/src/ftpfs/ftpfs.c
@@ -0,0 +1,1430 @@
+/**
+ * @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@embedded-brains.de>
+ *
+ * (c) Copyright 2002
+ * Thomas Doerfler
+ * IMD Ingenieurbuero fuer Microcomputertechnik
+ * Herbststr. 8
+ * 82178 Puchheim, Germany
+ * <Thomas.Doerfler@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 = rtems_libio_iop_is_writeable(iop);
+
+ /* Check for either read-only or write-only flags */
+ if (
+ rtems_libio_iop_is_writeable(iop)
+ && rtems_libio_iop_is_readable(iop)
+ ) {
+ 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,
+ ioctl_command_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,
+ .mmap_h = rtems_filesystem_default_mmap,
+ .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,
+ .mmap_h = rtems_filesystem_default_mmap,
+ .poll_h = rtems_filesystem_default_poll,
+ .readv_h = rtems_filesystem_default_readv,
+ .writev_h = rtems_filesystem_default_writev
+};
diff --git a/cpukit/libfs/src/ftpfs/tftpDriver.c b/cpukit/libfs/src/ftpfs/tftpDriver.c
new file mode 100644
index 0000000000..7cbb402b63
--- /dev/null
+++ b/cpukit/libfs/src/ftpfs/tftpDriver.c
@@ -0,0 +1,1066 @@
+/*
+ * Trivial File Transfer Protocol (RFC 1350)
+ *
+ * Transfer file to/from remote host
+ *
+ * W. Eric Norum
+ * Saskatchewan Accelerator Laboratory
+ * University of Saskatchewan
+ * Saskatoon, Saskatchewan, CANADA
+ * eric@skatter.usask.ca
+ *
+ * Modifications to support reference counting in the file system are
+ * Copyright (c) 2012 embedded brains GmbH.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <malloc.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <rtems.h>
+#include <rtems/libio_.h>
+#include <rtems/seterr.h>
+#include <rtems/tftp.h>
+#include <rtems/thread.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#ifdef RTEMS_NETWORKING
+#include <rtems/rtems_bsdnet.h>
+#endif
+
+#ifdef RTEMS_TFTP_DRIVER_DEBUG
+int rtems_tftp_driver_debug = 1;
+#endif
+
+/*
+ * Range of UDP ports to try
+ */
+#define UDP_PORT_BASE 3180
+
+/*
+ * Default limits
+ */
+#define PACKET_FIRST_TIMEOUT_MILLISECONDS 400L
+#define PACKET_TIMEOUT_MILLISECONDS 6000L
+#define OPEN_RETRY_LIMIT 10
+#define IO_RETRY_LIMIT 10
+
+/*
+ * TFTP opcodes
+ */
+#define TFTP_OPCODE_RRQ 1
+#define TFTP_OPCODE_WRQ 2
+#define TFTP_OPCODE_DATA 3
+#define TFTP_OPCODE_ACK 4
+#define TFTP_OPCODE_ERROR 5
+
+/*
+ * Largest data transfer
+ */
+#define TFTP_BUFSIZE 512
+
+/*
+ * Packets transferred between machines
+ */
+union tftpPacket {
+ /*
+ * RRQ/WRQ packet
+ */
+ struct tftpRWRQ {
+ uint16_t opcode;
+ char filename_mode[TFTP_BUFSIZE];
+ } tftpRWRQ;
+
+ /*
+ * DATA packet
+ */
+ struct tftpDATA {
+ uint16_t opcode;
+ uint16_t blocknum;
+ uint8_t data[TFTP_BUFSIZE];
+ } tftpDATA;
+
+ /*
+ * ACK packet
+ */
+ struct tftpACK {
+ uint16_t opcode;
+ uint16_t blocknum;
+ } tftpACK;
+
+ /*
+ * ERROR packet
+ */
+ struct tftpERROR {
+ uint16_t opcode;
+ uint16_t errorCode;
+ char errorMessage[TFTP_BUFSIZE];
+ } tftpERROR;
+};
+
+/*
+ * State of each TFTP stream
+ */
+struct tftpStream {
+ /*
+ * Buffer for storing most recently-received packet
+ */
+ union tftpPacket pkbuf;
+
+ /*
+ * Last block number transferred
+ */
+ uint16_t blocknum;
+
+ /*
+ * Data transfer socket
+ */
+ int socket;
+ struct sockaddr_in myAddress;
+ struct sockaddr_in farAddress;
+
+ /*
+ * Indices into buffer
+ */
+ int nleft;
+ int nused;
+
+ /*
+ * Flags
+ */
+ int firstReply;
+ int eof;
+ int writing;
+};
+
+/*
+ * Flags for filesystem info.
+ */
+#define TFTPFS_VERBOSE (1 << 0)
+
+/*
+ * TFTP File system info.
+ */
+typedef struct tftpfs_info_s {
+ uint32_t flags;
+ rtems_mutex tftp_mutex;
+ int nStreams;
+ struct tftpStream ** volatile tftpStreams;
+} tftpfs_info_t;
+
+#define tftpfs_info_mount_table(_mt) ((tftpfs_info_t*) ((_mt)->fs_info))
+#define tftpfs_info_pathloc(_pl) ((tftpfs_info_t*) ((_pl)->mt_entry->fs_info))
+#define tftpfs_info_iop(_iop) (tftpfs_info_pathloc (&((_iop)->pathinfo)))
+
+/*
+ * Number of streams open at the same time
+ */
+
+static const rtems_filesystem_operations_table rtems_tftp_ops;
+static const rtems_filesystem_file_handlers_r rtems_tftp_handlers;
+
+static bool rtems_tftp_is_directory(
+ const char *path,
+ size_t pathlen
+)
+{
+ return path [pathlen - 1] == '/';
+}
+
+int rtems_tftpfs_initialize(
+ rtems_filesystem_mount_table_entry_t *mt_entry,
+ const void *data
+)
+{
+ const char *device = mt_entry->dev;
+ size_t devicelen = strlen (device);
+ tftpfs_info_t *fs = NULL;
+ char *root_path;
+
+ if (devicelen == 0) {
+ root_path = malloc (1);
+ if (root_path == NULL)
+ goto error;
+ root_path [0] = '\0';
+ }
+ else {
+ root_path = malloc (devicelen + 2);
+ if (root_path == NULL)
+ goto error;
+
+ root_path = memcpy (root_path, device, devicelen);
+ root_path [devicelen] = '/';
+ root_path [devicelen + 1] = '\0';
+ }
+
+ fs = malloc (sizeof (*fs));
+ if (fs == NULL)
+ goto error;
+ fs->flags = 0;
+ fs->nStreams = 0;
+ fs->tftpStreams = 0;
+
+ mt_entry->fs_info = fs;
+ mt_entry->mt_fs_root->location.node_access = root_path;
+ mt_entry->mt_fs_root->location.handlers = &rtems_tftp_handlers;
+ mt_entry->ops = &rtems_tftp_ops;
+
+ /*
+ * Now allocate a semaphore for mutual exclusion.
+ *
+ * NOTE: This could be in an fsinfo for this filesystem type.
+ */
+
+ rtems_mutex_init (&fs->tftp_mutex, "TFTPFS");
+
+ if (data) {
+ char* config = (char*) data;
+ char* token;
+ char* saveptr;
+ token = strtok_r (config, " ", &saveptr);
+ while (token) {
+ if (strcmp (token, "verbose") == 0)
+ fs->flags |= TFTPFS_VERBOSE;
+ token = strtok_r (NULL, " ", &saveptr);
+ }
+ }
+
+ return 0;
+
+error:
+
+ free (fs);
+ free (root_path);
+
+ rtems_set_errno_and_return_minus_one (ENOMEM);
+}
+
+/*
+ * Release a stream and clear the pointer to it
+ */
+static void
+releaseStream (tftpfs_info_t *fs, int s)
+{
+ if (fs->tftpStreams[s] && (fs->tftpStreams[s]->socket >= 0))
+ close (fs->tftpStreams[s]->socket);
+ rtems_mutex_lock (&fs->tftp_mutex);
+ free (fs->tftpStreams[s]);
+ fs->tftpStreams[s] = NULL;
+ rtems_mutex_unlock (&fs->tftp_mutex);
+}
+
+static void
+rtems_tftpfs_shutdown (rtems_filesystem_mount_table_entry_t* mt_entry)
+{
+ tftpfs_info_t *fs = tftpfs_info_mount_table (mt_entry);
+ int s;
+ for (s = 0; s < fs->nStreams; s++)
+ releaseStream (fs, s);
+ rtems_mutex_destroy (&fs->tftp_mutex);
+ free (fs);
+ free (mt_entry->mt_fs_root->location.node_access);
+}
+
+/*
+ * Map error message
+ */
+static int
+tftpErrno (struct tftpStream *tp)
+{
+ unsigned int tftpError;
+ static const int errorMap[] = {
+ EINVAL,
+ ENOENT,
+ EPERM,
+ ENOSPC,
+ EINVAL,
+ ENXIO,
+ EEXIST,
+ ESRCH,
+ };
+
+ tftpError = ntohs (tp->pkbuf.tftpERROR.errorCode);
+ if (tftpError < (sizeof errorMap / sizeof errorMap[0]))
+ return errorMap[tftpError];
+ else
+ return 1000 + tftpError;
+}
+
+/*
+ * Send a message to make the other end shut up
+ */
+static void
+sendStifle (struct tftpStream *tp, struct sockaddr_in *to)
+{
+ int len;
+ struct {
+ uint16_t opcode;
+ uint16_t errorCode;
+ char errorMessage[12];
+ } msg;
+
+ /*
+ * Create the error packet (Unknown transfer ID).
+ */
+ msg.opcode = htons (TFTP_OPCODE_ERROR);
+ msg.errorCode = htons (5);
+ len = sizeof msg.opcode + sizeof msg.errorCode + 1;
+ len += sprintf (msg.errorMessage, "GO AWAY");
+
+ /*
+ * Send it
+ */
+ sendto (tp->socket, (char *)&msg, len, 0, (struct sockaddr *)to, sizeof *to);
+}
+
+/*
+ * Wait for a data packet
+ */
+static int
+getPacket (struct tftpStream *tp, int retryCount)
+{
+ int len;
+ struct timeval tv;
+
+ if (retryCount == 0) {
+ tv.tv_sec = PACKET_FIRST_TIMEOUT_MILLISECONDS / 1000L;
+ tv.tv_usec = (PACKET_FIRST_TIMEOUT_MILLISECONDS % 1000L) * 1000L;
+ }
+ else {
+ tv.tv_sec = PACKET_TIMEOUT_MILLISECONDS / 1000L;
+ tv.tv_usec = (PACKET_TIMEOUT_MILLISECONDS % 1000L) * 1000L;
+ }
+ setsockopt (tp->socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
+ for (;;) {
+ union {
+ struct sockaddr s;
+ struct sockaddr_in i;
+ } from;
+ socklen_t fromlen = sizeof from;
+ len = recvfrom (tp->socket, &tp->pkbuf,
+ sizeof tp->pkbuf, 0,
+ &from.s, &fromlen);
+ if (len < 0)
+ break;
+ if (from.i.sin_addr.s_addr == tp->farAddress.sin_addr.s_addr) {
+ if (tp->firstReply) {
+ tp->firstReply = 0;
+ tp->farAddress.sin_port = from.i.sin_port;
+ }
+ if (tp->farAddress.sin_port == from.i.sin_port)
+ break;
+ }
+
+ /*
+ * Packet is from someone with whom we are
+ * not interested. Tell them to go away.
+ */
+ sendStifle (tp, &from.i);
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ setsockopt (tp->socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
+#ifdef RTEMS_TFTP_DRIVER_DEBUG
+ if (rtems_tftp_driver_debug) {
+ if (len >= (int) sizeof tp->pkbuf.tftpACK) {
+ int opcode = ntohs (tp->pkbuf.tftpDATA.opcode);
+ switch (opcode) {
+ default:
+ printf ("TFTP: OPCODE %d\n", opcode);
+ break;
+
+ case TFTP_OPCODE_DATA:
+ printf ("TFTP: RECV %d\n", ntohs (tp->pkbuf.tftpDATA.blocknum));
+ break;
+
+ case TFTP_OPCODE_ACK:
+ printf ("TFTP: GOT ACK %d\n", ntohs (tp->pkbuf.tftpACK.blocknum));
+ break;
+ }
+ }
+ else {
+ printf ("TFTP: %d-byte packet\n", len);
+ }
+ }
+#endif
+ return len;
+}
+
+/*
+ * Send an acknowledgement
+ */
+static int
+sendAck (struct tftpStream *tp)
+{
+#ifdef RTEMS_TFTP_DRIVER_DEBUG
+ if (rtems_tftp_driver_debug)
+ printf ("TFTP: ACK %d\n", tp->blocknum);
+#endif
+
+ /*
+ * Create the acknowledgement
+ */
+ tp->pkbuf.tftpACK.opcode = htons (TFTP_OPCODE_ACK);
+ tp->pkbuf.tftpACK.blocknum = htons (tp->blocknum);
+
+ /*
+ * Send it
+ */
+ if (sendto (tp->socket, (char *)&tp->pkbuf, sizeof tp->pkbuf.tftpACK, 0,
+ (struct sockaddr *)&tp->farAddress,
+ sizeof tp->farAddress) < 0)
+ return errno;
+ return 0;
+}
+
+/*
+ * Convert a path to canonical form
+ */
+static void
+fixPath (char *path)
+{
+ char *inp, *outp, *base;
+
+ outp = inp = path;
+ base = NULL;
+ for (;;) {
+ if (inp[0] == '.') {
+ if (inp[1] == '\0')
+ break;
+ if (inp[1] == '/') {
+ inp += 2;
+ continue;
+ }
+ if (inp[1] == '.') {
+ if (inp[2] == '\0') {
+ if ((base != NULL) && (outp > base)) {
+ outp--;
+ while ((outp > base) && (outp[-1] != '/'))
+ outp--;
+ }
+ break;
+ }
+ if (inp[2] == '/') {
+ inp += 3;
+ if (base == NULL)
+ continue;
+ if (outp > base) {
+ outp--;
+ while ((outp > base) && (outp[-1] != '/'))
+ outp--;
+ }
+ continue;
+ }
+ }
+ }
+ if (base == NULL)
+ base = inp;
+ while (inp[0] != '/') {
+ if ((*outp++ = *inp++) == '\0')
+ return;
+ }
+ *outp++ = '/';
+ while (inp[0] == '/')
+ inp++;
+ }
+ *outp = '\0';
+ return;
+}
+
+static void rtems_tftp_eval_path(rtems_filesystem_eval_path_context_t *self)
+{
+ int eval_flags = rtems_filesystem_eval_path_get_flags (self);
+
+ if ((eval_flags & RTEMS_FS_MAKE) == 0) {
+ int rw = RTEMS_FS_PERMS_READ | RTEMS_FS_PERMS_WRITE;
+
+ if ((eval_flags & rw) != rw) {
+ rtems_filesystem_location_info_t *currentloc =
+ rtems_filesystem_eval_path_get_currentloc (self);
+ char *current = currentloc->node_access;
+ size_t currentlen = strlen (current);
+ const char *path = rtems_filesystem_eval_path_get_path (self);
+ size_t pathlen = rtems_filesystem_eval_path_get_pathlen (self);
+ size_t len = currentlen + pathlen;
+
+ rtems_filesystem_eval_path_clear_path (self);
+
+ current = realloc (current, len + 1);
+ if (current != NULL) {
+ memcpy (current + currentlen, path, pathlen);
+ current [len] = '\0';
+ if (!rtems_tftp_is_directory (current, len)) {
+ fixPath (current);
+ }
+ currentloc->node_access = current;
+ } else {
+ rtems_filesystem_eval_path_error (self, ENOMEM);
+ }
+ } else {
+ rtems_filesystem_eval_path_error (self, EINVAL);
+ }
+ } else {
+ rtems_filesystem_eval_path_error (self, EIO);
+ }
+}
+
+/*
+ * The routine which does most of the work for the IMFS open handler
+ */
+static int rtems_tftp_open_worker(
+ rtems_libio_t *iop,
+ char *full_path_name,
+ int oflag
+)
+{
+ tftpfs_info_t *fs;
+ struct tftpStream *tp;
+ int retryCount;
+ struct in_addr farAddress;
+ int s;
+ int len;
+ char *cp1;
+ char *cp2;
+ char *remoteFilename;
+ rtems_interval now;
+ char *hostname;
+
+ /*
+ * Get the file system info.
+ */
+ fs = tftpfs_info_iop (iop);
+
+ /*
+ * Extract the host name component
+ */
+ if (*full_path_name == '/')
+ full_path_name++;
+
+ hostname = full_path_name;
+ cp1 = strchr (full_path_name, ':');
+ if (!cp1) {
+#ifdef RTEMS_NETWORKING
+ hostname = "BOOTP_HOST";
+#endif
+ } else {
+ *cp1 = '\0';
+ ++cp1;
+ }
+
+ /*
+ * Convert hostname to Internet address
+ */
+#ifdef RTEMS_NETWORKING
+ if (strcmp (hostname, "BOOTP_HOST") == 0)
+ farAddress = rtems_bsdnet_bootp_server_address;
+ else
+#endif
+ if (inet_aton (hostname, &farAddress) == 0) {
+ struct hostent *he = gethostbyname(hostname);
+ if (he == NULL)
+ return ENOENT;
+ memcpy (&farAddress, he->h_addr, sizeof (farAddress));
+ }
+
+ /*
+ * Extract file pathname component
+ */
+#ifdef RTEMS_NETWORKING
+ if (strcmp (cp1, "BOOTP_FILE") == 0) {
+ cp1 = rtems_bsdnet_bootp_boot_file_name;
+ }
+#endif
+ if (*cp1 == '\0')
+ return ENOENT;
+ remoteFilename = cp1;
+ if (strlen (remoteFilename) > (TFTP_BUFSIZE - 10))
+ return ENOENT;
+
+ /*
+ * Find a free stream
+ */
+ rtems_mutex_lock (&fs->tftp_mutex);
+ for (s = 0 ; s < fs->nStreams ; s++) {
+ if (fs->tftpStreams[s] == NULL)
+ break;
+ }
+ if (s == fs->nStreams) {
+ /*
+ * Reallocate stream pointers
+ * Guard against the case where realloc() returns NULL.
+ */
+ struct tftpStream **np;
+
+ np = realloc (fs->tftpStreams, ++fs->nStreams * sizeof *fs->tftpStreams);
+ if (np == NULL) {
+ rtems_mutex_unlock (&fs->tftp_mutex);
+ return ENOMEM;
+ }
+ fs->tftpStreams = np;
+ }
+ tp = fs->tftpStreams[s] = malloc (sizeof (struct tftpStream));
+ rtems_mutex_unlock (&fs->tftp_mutex);
+ if (tp == NULL)
+ return ENOMEM;
+ iop->data0 = s;
+ iop->data1 = tp;
+
+ /*
+ * Create the socket
+ */
+ if ((tp->socket = socket (AF_INET, SOCK_DGRAM, 0)) < 0) {
+ releaseStream (fs, s);
+ return ENOMEM;
+ }
+
+ /*
+ * Bind the socket to a local address
+ */
+ retryCount = 0;
+ now = rtems_clock_get_ticks_since_boot();
+ for (;;) {
+ int try = (now + retryCount) % 10;
+
+ tp->myAddress.sin_family = AF_INET;
+ tp->myAddress.sin_port = htons (UDP_PORT_BASE + fs->nStreams * try + s);
+ tp->myAddress.sin_addr.s_addr = htonl (INADDR_ANY);
+ if (bind (tp->socket, (struct sockaddr *)&tp->myAddress, sizeof tp->myAddress) >= 0)
+ break;
+ if (++retryCount == 10) {
+ releaseStream (fs, s);
+ return EBUSY;
+ }
+ }
+
+ /*
+ * Set the UDP destination to the TFTP server
+ * port on the remote machine.
+ */
+ tp->farAddress.sin_family = AF_INET;
+ tp->farAddress.sin_addr = farAddress;
+ tp->farAddress.sin_port = htons (69);
+
+ /*
+ * Start the transfer
+ */
+ tp->firstReply = 1;
+ retryCount = 0;
+ for (;;) {
+ /*
+ * Create the request
+ */
+ if ((oflag & O_ACCMODE) == O_RDONLY) {
+ tp->writing = 0;
+ tp->pkbuf.tftpRWRQ.opcode = htons (TFTP_OPCODE_RRQ);
+ }
+ else {
+ tp->writing = 1;
+ tp->pkbuf.tftpRWRQ.opcode = htons (TFTP_OPCODE_WRQ);
+ }
+ cp1 = (char *) tp->pkbuf.tftpRWRQ.filename_mode;
+ cp2 = (char *) remoteFilename;
+ while ((*cp1++ = *cp2++) != '\0')
+ continue;
+ cp2 = "octet";
+ while ((*cp1++ = *cp2++) != '\0')
+ continue;
+ len = cp1 - (char *)&tp->pkbuf.tftpRWRQ;
+
+ /*
+ * Send the request
+ */
+ if (sendto (tp->socket, (char *)&tp->pkbuf, len, 0,
+ (struct sockaddr *)&tp->farAddress,
+ sizeof tp->farAddress) < 0) {
+ releaseStream (fs, s);
+ return EIO;
+ }
+
+ /*
+ * Get reply
+ */
+ len = getPacket (tp, retryCount);
+ if (len >= (int) sizeof tp->pkbuf.tftpACK) {
+ int opcode = ntohs (tp->pkbuf.tftpDATA.opcode);
+ if (!tp->writing
+ && (opcode == TFTP_OPCODE_DATA)
+ && (ntohs (tp->pkbuf.tftpDATA.blocknum) == 1)) {
+ tp->nused = 0;
+ tp->blocknum = 1;
+ tp->nleft = len - 2 * sizeof (uint16_t );
+ tp->eof = (tp->nleft < TFTP_BUFSIZE);
+ if (sendAck (tp) != 0) {
+ releaseStream (fs, s);
+ return EIO;
+ }
+ break;
+ }
+ if (tp->writing
+ && (opcode == TFTP_OPCODE_ACK)
+ && (ntohs (tp->pkbuf.tftpACK.blocknum) == 0)) {
+ tp->nused = 0;
+ tp->blocknum = 1;
+ break;
+ }
+ if (opcode == TFTP_OPCODE_ERROR) {
+ int e = tftpErrno (tp);
+ releaseStream (fs, s);
+ return e;
+ }
+ }
+
+ /*
+ * Keep trying
+ */
+ if (++retryCount >= OPEN_RETRY_LIMIT) {
+ releaseStream (fs, s);
+ return EIO;
+ }
+ }
+ return 0;
+}
+
+static int rtems_tftp_open(
+ rtems_libio_t *iop,
+ const char *new_name,
+ int oflag,
+ mode_t mode
+)
+{
+ tftpfs_info_t *fs;
+ char *full_path_name;
+ int err;
+
+ full_path_name = iop->pathinfo.node_access;
+
+ if (rtems_tftp_is_directory (full_path_name, strlen (full_path_name))) {
+ rtems_set_errno_and_return_minus_one (ENOTSUP);
+ }
+
+ /*
+ * Get the file system info.
+ */
+ fs = tftpfs_info_iop (iop);
+
+ if (fs->flags & TFTPFS_VERBOSE)
+ printf ("TFTPFS: %s\n", full_path_name);
+
+ err = rtems_tftp_open_worker (iop, full_path_name, oflag);
+ if (err != 0) {
+ rtems_set_errno_and_return_minus_one (err);
+ }
+
+ return 0;
+}
+
+/*
+ * Read from a TFTP stream
+ */
+static ssize_t rtems_tftp_read(
+ rtems_libio_t *iop,
+ void *buffer,
+ size_t count
+)
+{
+ char *bp;
+ struct tftpStream *tp = iop->data1;
+ int retryCount;
+ int nwant;
+
+ if (!tp)
+ rtems_set_errno_and_return_minus_one( EIO );
+
+ /*
+ * Read till user request is satisfied or EOF is reached
+ */
+ bp = buffer;
+ nwant = count;
+ while (nwant) {
+ if (tp->nleft) {
+ int ncopy;
+ if (nwant < tp->nleft)
+ ncopy = nwant;
+ else
+ ncopy = tp->nleft;
+ memcpy (bp, &tp->pkbuf.tftpDATA.data[tp->nused], ncopy);
+ tp->nused += ncopy;
+ tp->nleft -= ncopy;
+ bp += ncopy;
+ nwant -= ncopy;
+ if (nwant == 0)
+ break;
+ }
+ if (tp->eof)
+ break;
+
+ /*
+ * Wait for the next packet
+ */
+ retryCount = 0;
+ for (;;) {
+ int len = getPacket (tp, retryCount);
+ if (len >= (int)sizeof tp->pkbuf.tftpACK) {
+ int opcode = ntohs (tp->pkbuf.tftpDATA.opcode);
+ uint16_t nextBlock = tp->blocknum + 1;
+ if ((opcode == TFTP_OPCODE_DATA)
+ && (ntohs (tp->pkbuf.tftpDATA.blocknum) == nextBlock)) {
+ tp->nused = 0;
+ tp->nleft = len - 2 * sizeof (uint16_t);
+ tp->eof = (tp->nleft < TFTP_BUFSIZE);
+ tp->blocknum++;
+ if (sendAck (tp) != 0)
+ rtems_set_errno_and_return_minus_one (EIO);
+ break;
+ }
+ if (opcode == TFTP_OPCODE_ERROR)
+ rtems_set_errno_and_return_minus_one (tftpErrno (tp));
+ }
+
+ /*
+ * Keep trying?
+ */
+ if (++retryCount == IO_RETRY_LIMIT)
+ rtems_set_errno_and_return_minus_one (EIO);
+ if (sendAck (tp) != 0)
+ rtems_set_errno_and_return_minus_one (EIO);
+ }
+ }
+ return count - nwant;
+}
+
+/*
+ * Flush a write buffer and wait for acknowledgement
+ */
+static int rtems_tftp_flush (struct tftpStream *tp)
+{
+ int wlen, rlen;
+ int retryCount = 0;
+
+ wlen = tp->nused + 2 * sizeof (uint16_t );
+ for (;;) {
+ tp->pkbuf.tftpDATA.opcode = htons (TFTP_OPCODE_DATA);
+ tp->pkbuf.tftpDATA.blocknum = htons (tp->blocknum);
+#ifdef RTEMS_TFTP_DRIVER_DEBUG
+ if (rtems_tftp_driver_debug)
+ printf ("TFTP: SEND %d (%d)\n", tp->blocknum, tp->nused);
+#endif
+ if (sendto (tp->socket, (char *)&tp->pkbuf, wlen, 0,
+ (struct sockaddr *)&tp->farAddress,
+ sizeof tp->farAddress) < 0)
+ return EIO;
+ rlen = getPacket (tp, retryCount);
+ /*
+ * Our last packet won't necessarily be acknowledged!
+ */
+ if ((rlen < 0) && (tp->nused < sizeof tp->pkbuf.tftpDATA.data))
+ return 0;
+ if (rlen >= (int)sizeof tp->pkbuf.tftpACK) {
+ int opcode = ntohs (tp->pkbuf.tftpACK.opcode);
+ if ((opcode == TFTP_OPCODE_ACK)
+ && (ntohs (tp->pkbuf.tftpACK.blocknum) == tp->blocknum)) {
+ tp->nused = 0;
+ tp->blocknum++;
+ return 0;
+ }
+ if (opcode == TFTP_OPCODE_ERROR)
+ return tftpErrno (tp);
+ }
+
+ /*
+ * Keep trying?
+ */
+ if (++retryCount == IO_RETRY_LIMIT)
+ return EIO;
+ }
+}
+
+/*
+ * Close a TFTP stream
+ */
+static int rtems_tftp_close(
+ rtems_libio_t *iop
+)
+{
+ tftpfs_info_t *fs;
+ struct tftpStream *tp = iop->data1;
+ int e = 0;
+
+ /*
+ * Get the file system info.
+ */
+ fs = tftpfs_info_iop (iop);
+
+ if (!tp)
+ rtems_set_errno_and_return_minus_one (EIO);
+
+ if (tp->writing)
+ e = rtems_tftp_flush (tp);
+ if (!tp->eof && !tp->firstReply) {
+ /*
+ * Tell the other end to stop
+ */
+ rtems_interval ticksPerSecond;
+ sendStifle (tp, &tp->farAddress);
+ ticksPerSecond = rtems_clock_get_ticks_per_second();
+ rtems_task_wake_after (1 + ticksPerSecond / 10);
+ }
+ releaseStream (fs, iop->data0);
+ if (e)
+ rtems_set_errno_and_return_minus_one (e);
+ return 0;
+}
+
+static ssize_t rtems_tftp_write(
+ rtems_libio_t *iop,
+ const void *buffer,
+ size_t count
+)
+{
+ const char *bp;
+ struct tftpStream *tp = iop->data1;
+ int nleft, nfree, ncopy;
+
+ /*
+ * Bail out if an error has occurred
+ */
+ if (!tp->writing)
+ rtems_set_errno_and_return_minus_one (EIO);
+
+ /*
+ * Write till user request is satisfied
+ * Notice that the buffer is flushed as soon as it is filled rather
+ * than waiting for the next write or a close. This ensures that
+ * the flush in close writes a less than full buffer so the far
+ * end can detect the end-of-file condition.
+ */
+ bp = buffer;
+ nleft = count;
+ while (nleft) {
+ nfree = sizeof tp->pkbuf.tftpDATA.data - tp->nused;
+ if (nleft < nfree)
+ ncopy = nleft;
+ else
+ ncopy = nfree;
+ memcpy (&tp->pkbuf.tftpDATA.data[tp->nused], bp, ncopy);
+ tp->nused += ncopy;
+ nleft -= ncopy;
+ bp += ncopy;
+ if (tp->nused == sizeof tp->pkbuf.tftpDATA.data) {
+ int e = rtems_tftp_flush (tp);
+ if (e) {
+ tp->writing = 0;
+ rtems_set_errno_and_return_minus_one (e);
+ }
+ }
+ }
+ return count;
+}
+
+/*
+ * Dummy version to let fopen(xxxx,"w") work properly.
+ */
+static int rtems_tftp_ftruncate(
+ rtems_libio_t *iop RTEMS_UNUSED,
+ off_t count RTEMS_UNUSED
+)
+{
+ return 0;
+}
+
+static int rtems_tftp_fstat(
+ const rtems_filesystem_location_info_t *loc,
+ struct stat *buf
+)
+{
+ const char *path = loc->node_access;
+ size_t pathlen = strlen (path);
+
+ buf->st_mode = S_IRWXU | S_IRWXG | S_IRWXO
+ | (rtems_tftp_is_directory (path, pathlen) ? S_IFDIR : S_IFREG);
+
+ return 0;
+}
+
+static int rtems_tftp_clone(
+ rtems_filesystem_location_info_t *loc
+)
+{
+ int rv = 0;
+
+ loc->node_access = strdup (loc->node_access);
+
+ if (loc->node_access == NULL) {
+ errno = ENOMEM;
+ rv = -1;
+ }
+
+ return rv;
+}
+
+static void rtems_tftp_free_node_info(
+ const rtems_filesystem_location_info_t *loc
+)
+{
+ free (loc->node_access);
+}
+
+static bool rtems_tftp_are_nodes_equal(
+ const rtems_filesystem_location_info_t *a,
+ const rtems_filesystem_location_info_t *b
+)
+{
+ return strcmp (a->node_access, b->node_access) == 0;
+}
+
+static const rtems_filesystem_operations_table rtems_tftp_ops = {
+ .lock_h = rtems_filesystem_default_lock,
+ .unlock_h = rtems_filesystem_default_unlock,
+ .eval_path_h = rtems_tftp_eval_path,
+ .link_h = rtems_filesystem_default_link,
+ .are_nodes_equal_h = rtems_tftp_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_tftp_clone,
+ .freenod_h = rtems_tftp_free_node_info,
+ .mount_h = rtems_filesystem_default_mount,
+ .unmount_h = rtems_filesystem_default_unmount,
+ .fsunmount_me_h = rtems_tftpfs_shutdown,
+ .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_tftp_handlers = {
+ .open_h = rtems_tftp_open,
+ .close_h = rtems_tftp_close,
+ .read_h = rtems_tftp_read,
+ .write_h = rtems_tftp_write,
+ .ioctl_h = rtems_filesystem_default_ioctl,
+ .lseek_h = rtems_filesystem_default_lseek,
+ .fstat_h = rtems_tftp_fstat,
+ .ftruncate_h = rtems_tftp_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,
+ .mmap_h = rtems_filesystem_default_mmap,
+ .poll_h = rtems_filesystem_default_poll,
+ .readv_h = rtems_filesystem_default_readv,
+ .writev_h = rtems_filesystem_default_writev
+};