/** * @file * * File Transfer Protocol file system (FTP client). */ /* * Copyright (c) 2009-2012 embedded brains GmbH. * * embedded brains GmbH * Obere Lagerstr. 30 * 82178 Puchheim * Germany * * * (c) Copyright 2002 * Thomas Doerfler * IMD Ingenieurbuero fuer Microcomputertechnik * Herbststr. 8 * 82178 Puchheim, Germany * * * This code has been created after closly inspecting "tftpdriver.c" from Eric * Norum. * * The license and distribution terms for this file may be * found in the file LICENSE in this distribution or at * http://www.rtems.org/license/LICENSE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DEBUG #define DEBUG_PRINTF(...) printf(__VA_ARGS__) #else #define DEBUG_PRINTF(...) #endif /** * Connection entry for each open file stream. */ typedef struct { off_t file_size; /** * Control connection socket. */ int ctrl_socket; uint32_t client_address; /** * Data transfer socket. */ int data_socket; /** * Current index into the reply buffer. */ size_t reply_current; /** * End index of the reply buffer. */ size_t reply_end; /** * Buffer for relpy data. */ char reply_buffer [128]; /** * End of file flag. */ bool eof; bool write; /** * Indicates if we should do a SIZE command. * * The first call to the rtems_ftpfs_fstat() handler is issued by the path * evaluation to check for access permission. For this case we avoid the * SIZE command. */ bool do_size_command; ino_t ino; const char *user; const char *password; const char *hostname; const char *filename; char buffer []; } rtems_ftpfs_entry; /** * Mount entry for each file system instance. */ typedef struct { /** * Verbose mode enabled or disabled. */ bool verbose; /** * Timeout value */ struct timeval timeout; /** * Inode counter. */ ino_t ino; } rtems_ftpfs_mount_entry; static const rtems_filesystem_operations_table rtems_ftpfs_ops; static const rtems_filesystem_file_handlers_r rtems_ftpfs_handlers; static const rtems_filesystem_file_handlers_r rtems_ftpfs_root_handlers; static bool rtems_ftpfs_use_timeout(const struct timeval *to) { return to->tv_sec != 0 || to->tv_usec != 0; } static int rtems_ftpfs_set_connection_timeout( int socket, const struct timeval *to ) { if (rtems_ftpfs_use_timeout(to)) { int rv = 0; rv = setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, to, sizeof(*to)); if (rv != 0) { return EIO; } rv = setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, to, sizeof(*to)); if (rv != 0) { return EIO; } } return 0; } static rtems_status_code rtems_ftpfs_do_ioctl( const char *mount_point, ioctl_command_t req, ... ) { rtems_status_code sc = RTEMS_SUCCESSFUL; int rv = 0; int fd = 0; va_list ap; if (mount_point == NULL) { mount_point = RTEMS_FTPFS_MOUNT_POINT_DEFAULT; } fd = open(mount_point, O_RDWR); if (fd < 0) { return RTEMS_INVALID_NAME; } va_start(ap, req); rv = ioctl(fd, req, va_arg(ap, void *)); va_end(ap); if (rv != 0) { sc = RTEMS_INVALID_NUMBER; } rv = close(fd); if (rv != 0 && sc == RTEMS_SUCCESSFUL) { sc = RTEMS_IO_ERROR; } return sc; } rtems_status_code rtems_ftpfs_get_verbose(const char *mount_point, bool *verbose) { return rtems_ftpfs_do_ioctl( mount_point, RTEMS_FTPFS_IOCTL_GET_VERBOSE, verbose ); } rtems_status_code rtems_ftpfs_set_verbose(const char *mount_point, bool verbose) { return rtems_ftpfs_do_ioctl( mount_point, RTEMS_FTPFS_IOCTL_SET_VERBOSE, &verbose ); } rtems_status_code rtems_ftpfs_get_timeout( const char *mount_point, struct timeval *timeout ) { return rtems_ftpfs_do_ioctl( mount_point, RTEMS_FTPFS_IOCTL_GET_TIMEOUT, timeout ); } rtems_status_code rtems_ftpfs_set_timeout( const char *mount_point, const struct timeval *timeout ) { return rtems_ftpfs_do_ioctl( mount_point, RTEMS_FTPFS_IOCTL_SET_TIMEOUT, timeout ); } typedef void (*rtems_ftpfs_reply_parser)( const char * /* reply fragment */, size_t /* reply fragment length */, void * /* parser argument */ ); typedef enum { RTEMS_FTPFS_REPLY_START, RTEMS_FTPFS_REPLY_SINGLE_LINE, RTEMS_FTPFS_REPLY_DONE, RTEMS_FTPFS_REPLY_MULTI_LINE, RTEMS_FTPFS_REPLY_MULTI_LINE_START } rtems_ftpfs_reply_state; typedef enum { RTEMS_FTPFS_REPLY_ERROR = 0, RTEMS_FTPFS_REPLY_1 = '1', RTEMS_FTPFS_REPLY_2 = '2', RTEMS_FTPFS_REPLY_3 = '3', RTEMS_FTPFS_REPLY_4 = '4', RTEMS_FTPFS_REPLY_5 = '5' } rtems_ftpfs_reply; #define RTEMS_FTPFS_REPLY_SIZE 3 static bool rtems_ftpfs_is_reply_code_valid(unsigned char *reply) { return isdigit(reply [0]) && isdigit(reply [1]) && isdigit(reply [2]); } static rtems_ftpfs_reply rtems_ftpfs_get_reply( rtems_ftpfs_entry *e, rtems_ftpfs_reply_parser parser, void *parser_arg, bool verbose ) { rtems_ftpfs_reply_state state = RTEMS_FTPFS_REPLY_START; unsigned char reply_code [RTEMS_FTPFS_REPLY_SIZE] = { 'a', 'a', 'a' }; size_t reply_code_index = 0; while (state != RTEMS_FTPFS_REPLY_DONE) { char *buf = NULL; size_t i = 0; size_t n = 0; /* Receive reply fragment from socket */ if (e->reply_current == e->reply_end) { ssize_t rv = 0; e->reply_current = 0; e->reply_end = 0; rv = recv( e->ctrl_socket, &e->reply_buffer [0], sizeof(e->reply_buffer), 0 ); if (rv > 0) { e->reply_end = (size_t) rv; } else { return RTEMS_FTPFS_REPLY_ERROR; } } buf = &e->reply_buffer [e->reply_current]; n = e->reply_end - e->reply_current; /* Invoke parser if necessary */ if (parser != NULL) { parser(buf, n, parser_arg); } /* Parse reply fragment */ for (i = 0; i < n && state != RTEMS_FTPFS_REPLY_DONE; ++i) { char c = buf [i]; switch (state) { case RTEMS_FTPFS_REPLY_START: if (reply_code_index < RTEMS_FTPFS_REPLY_SIZE) { reply_code [reply_code_index] = c; ++reply_code_index; } else if (rtems_ftpfs_is_reply_code_valid(reply_code)) { if (c == '-') { state = RTEMS_FTPFS_REPLY_MULTI_LINE; } else { state = RTEMS_FTPFS_REPLY_SINGLE_LINE; } } else { return RTEMS_FTPFS_REPLY_ERROR; } break; case RTEMS_FTPFS_REPLY_SINGLE_LINE: if (c == '\n') { state = RTEMS_FTPFS_REPLY_DONE; } break; case RTEMS_FTPFS_REPLY_MULTI_LINE: if (c == '\n') { state = RTEMS_FTPFS_REPLY_MULTI_LINE_START; reply_code_index = 0; } break; case RTEMS_FTPFS_REPLY_MULTI_LINE_START: if (reply_code_index < RTEMS_FTPFS_REPLY_SIZE) { if (reply_code [reply_code_index] == c) { ++reply_code_index; } else { state = RTEMS_FTPFS_REPLY_MULTI_LINE; } } else { if (c == ' ') { state = RTEMS_FTPFS_REPLY_SINGLE_LINE; } else { state = RTEMS_FTPFS_REPLY_MULTI_LINE; } } break; default: return RTEMS_FTPFS_REPLY_ERROR; } } /* Be verbose if necessary */ if (verbose) { write(STDERR_FILENO, buf, i); } /* Update reply index */ e->reply_current += i; } return reply_code [0]; } static rtems_ftpfs_reply rtems_ftpfs_send_command_with_parser( rtems_ftpfs_entry *e, const char *cmd, const char *arg, rtems_ftpfs_reply_parser parser, void *parser_arg, bool verbose ) { rtems_ftpfs_reply reply = RTEMS_FTPFS_REPLY_ERROR; size_t cmd_len = strlen(cmd); size_t arg_len = arg != NULL ? strlen(arg) : 0; size_t len = cmd_len + arg_len + 2; char *buf = malloc(len); if (buf != NULL) { ssize_t n = 0; char *buf_arg = buf + cmd_len; char *buf_eol = buf_arg + arg_len; memcpy(buf, cmd, cmd_len); memcpy(buf_arg, arg, arg_len); buf_eol [0] = '\r'; buf_eol [1] = '\n'; /* Send */ n = send(e->ctrl_socket, buf, len, 0); if (n == (ssize_t) len) { if (verbose) { write(STDERR_FILENO, buf, len); } /* Reply */ reply = rtems_ftpfs_get_reply(e, parser, parser_arg, verbose); } free(buf); } return reply; } static rtems_ftpfs_reply rtems_ftpfs_send_command( rtems_ftpfs_entry *e, const char *cmd, const char *arg, bool verbose ) { return rtems_ftpfs_send_command_with_parser( e, cmd, arg, NULL, NULL, verbose ); } typedef enum { STATE_USER_NAME, STATE_START_PASSWORD, STATE_START_HOST_NAME, STATE_START_HOST_NAME_OR_PATH, STATE_START_PATH, STATE_PASSWORD, STATE_HOST_NAME, STATE_DONE, STATE_INVALID } split_state; static int rtems_ftpfs_split_names ( char *s, const char **user, const char **password, const char **hostname, const char **path ) { split_state state = STATE_USER_NAME; size_t len = strlen(s); size_t i = 0; *user = s; for (i = 0; i < len; ++i) { char c = s [i]; switch (state) { case STATE_USER_NAME: if (c == ':') { state = STATE_START_PASSWORD; s [i] = '\0'; } else if (c == '@') { state = STATE_START_HOST_NAME; s [i] = '\0'; } else if (c == '/') { state = STATE_START_HOST_NAME_OR_PATH; s [i] = '\0'; } break; case STATE_START_PASSWORD: state = STATE_PASSWORD; *password = &s [i]; --i; break; case STATE_START_HOST_NAME: state = STATE_HOST_NAME; *hostname = &s [i]; --i; break; case STATE_START_HOST_NAME_OR_PATH: if (c == '@') { state = STATE_START_HOST_NAME; } else { state = STATE_DONE; *path = &s [i]; goto done; } break; case STATE_START_PATH: state = STATE_DONE; *path = &s [i]; goto done; case STATE_PASSWORD: if (c == '@') { state = STATE_START_HOST_NAME; s [i] = '\0'; } else if (c == '/') { state = STATE_START_HOST_NAME_OR_PATH; s [i] = '\0'; } break; case STATE_HOST_NAME: if (c == '/') { state = STATE_START_PATH; s [i] = '\0'; } break; default: state = STATE_INVALID; goto done; } } done: /* This is a special case with no username and password */ if (*hostname == NULL) { *hostname = &s [0]; *user = "anonymous"; *password = *user; } /* If we have no password use the user name */ if (*password == NULL) { *password = *user; } return state == STATE_DONE ? 0 : ENOENT; } static socklen_t rtems_ftpfs_create_address( struct sockaddr_in *sa, unsigned long address, unsigned short port ) { memset(sa, 0, sizeof(*sa)); sa->sin_family = AF_INET; sa->sin_addr.s_addr = address; sa->sin_port = port; sa->sin_len = sizeof(*sa); return sizeof(*sa); } static int rtems_ftpfs_close_data_connection( rtems_ftpfs_entry *e, bool verbose, bool error ) { int eno = 0; /* Close data connection if necessary */ if (e->data_socket >= 0) { int rv = close(e->data_socket); e->data_socket = -1; if (rv != 0) { eno = EIO; } /* For write connections we have to obtain the transfer reply */ if (e->write && !error) { rtems_ftpfs_reply reply = rtems_ftpfs_get_reply(e, NULL, NULL, verbose); if (reply != RTEMS_FTPFS_REPLY_2) { eno = EIO; } } } return eno; } static int rtems_ftpfs_open_ctrl_connection( rtems_ftpfs_entry *e, bool verbose, const struct timeval *timeout ) { int rv = 0; int eno = 0; rtems_ftpfs_reply reply = RTEMS_FTPFS_REPLY_ERROR; struct in_addr address = { .s_addr = 0 }; struct sockaddr_in sa; socklen_t size = 0; /* Create the socket for the control connection */ e->ctrl_socket = socket(AF_INET, SOCK_STREAM, 0); if (e->ctrl_socket < 0) { return ENOMEM; } /* Set up the server address from the hostname */ if (inet_aton(e->hostname, &address) == 0) { /* Try to get the address by name */ struct hostent *he = gethostbyname(e->hostname); if (he != NULL) { memcpy(&address, he->h_addr, sizeof(address)); } else { return ENOENT; } } rtems_ftpfs_create_address(&sa, address.s_addr, htons(RTEMS_FTPFS_CTRL_PORT)); DEBUG_PRINTF("server = %s\n", inet_ntoa(sa.sin_addr)); /* Open control connection */ rv = connect( e->ctrl_socket, (struct sockaddr *) &sa, sizeof(sa) ); if (rv != 0) { return ENOENT; } /* Set control connection timeout */ eno = rtems_ftpfs_set_connection_timeout(e->ctrl_socket, timeout); if (eno != 0) { return eno; } /* Get client address */ size = rtems_ftpfs_create_address(&sa, INADDR_ANY, 0); rv = getsockname( e->ctrl_socket, (struct sockaddr *) &sa, &size ); if (rv != 0) { return ENOMEM; } e->client_address = ntohl(sa.sin_addr.s_addr); DEBUG_PRINTF("client = %s\n", inet_ntoa(sa.sin_addr)); /* Now we should get a welcome message from the server */ reply = rtems_ftpfs_get_reply(e, NULL, NULL, verbose); if (reply != RTEMS_FTPFS_REPLY_2) { return ENOENT; } /* Send USER command */ reply = rtems_ftpfs_send_command(e, "USER ", e->user, verbose); if (reply == RTEMS_FTPFS_REPLY_3) { /* Send PASS command */ reply = rtems_ftpfs_send_command(e, "PASS ", e->password, verbose); if (reply != RTEMS_FTPFS_REPLY_2) { return EACCES; } /* TODO: Some server may require an account */ } else if (reply != RTEMS_FTPFS_REPLY_2) { return EACCES; } /* Send TYPE command to set binary mode for all data transfers */ reply = rtems_ftpfs_send_command(e, "TYPE I", NULL, verbose); if (reply != RTEMS_FTPFS_REPLY_2) { return EIO; } return 0; } static int rtems_ftpfs_open_data_connection_active( rtems_ftpfs_entry *e, const char *file_command, bool verbose, const struct timeval *timeout ) { int rv = 0; int eno = 0; rtems_ftpfs_reply reply = RTEMS_FTPFS_REPLY_ERROR; struct sockaddr_in sa; socklen_t size = 0; int port_socket = -1; char port_command [] = "PORT 000,000,000,000,000,000"; uint16_t data_port = 0; /* Create port socket to establish a data data connection */ port_socket = socket(AF_INET, SOCK_STREAM, 0); if (port_socket < 0) { eno = ENOMEM; goto cleanup; } /* Bind port socket */ rtems_ftpfs_create_address(&sa, INADDR_ANY, 0); rv = bind( port_socket, (struct sockaddr *) &sa, sizeof(sa) ); if (rv != 0) { eno = EBUSY; goto cleanup; } /* Get port number for data socket */ size = rtems_ftpfs_create_address(&sa, INADDR_ANY, 0); rv = getsockname( port_socket, (struct sockaddr *) &sa, &size ); if (rv != 0) { eno = ENOMEM; goto cleanup; } data_port = ntohs(sa.sin_port); /* Send PORT command to set data connection port for server */ snprintf( port_command, sizeof(port_command), "PORT %lu,%lu,%lu,%lu,%lu,%lu", (e->client_address >> 24) & 0xffUL, (e->client_address >> 16) & 0xffUL, (e->client_address >> 8) & 0xffUL, (e->client_address >> 0) & 0xffUL, (data_port >> 8) & 0xffUL, (data_port >> 0) & 0xffUL ); reply = rtems_ftpfs_send_command(e, port_command, NULL, verbose); if (reply != RTEMS_FTPFS_REPLY_2) { eno = ENOTSUP; goto cleanup; } /* Listen on port socket for incoming data connections */ rv = listen(port_socket, 1); if (rv != 0) { eno = EBUSY; goto cleanup; } /* Send RETR or STOR command with filename */ reply = rtems_ftpfs_send_command(e, file_command, e->filename, verbose); if (reply != RTEMS_FTPFS_REPLY_1) { eno = EIO; goto cleanup; } /* Wait for connect on data connection if necessary */ if (rtems_ftpfs_use_timeout(timeout)) { struct timeval to = *timeout; fd_set fds; FD_ZERO(&fds); FD_SET(port_socket, &fds); rv = select(port_socket + 1, &fds, NULL, NULL, &to); if (rv <= 0) { eno = EIO; goto cleanup; } } /* Accept data connection */ size = sizeof(sa); e->data_socket = accept( port_socket, (struct sockaddr *) &sa, &size ); if (e->data_socket < 0) { eno = EIO; goto cleanup; } cleanup: /* Close port socket if necessary */ if (port_socket >= 0) { rv = close(port_socket); if (rv != 0) { eno = EIO; } } return eno; } typedef enum { RTEMS_FTPFS_PASV_START = 0, RTEMS_FTPFS_PASV_JUNK, RTEMS_FTPFS_PASV_DATA, RTEMS_FTPFS_PASV_DONE } rtems_ftpfs_pasv_state; typedef struct { rtems_ftpfs_pasv_state state; size_t index; uint8_t data [6]; } rtems_ftpfs_pasv_entry; static void rtems_ftpfs_pasv_parser( const char* buf, size_t len, void *arg ) { rtems_ftpfs_pasv_entry *pe = arg; size_t i = 0; for (i = 0; i < len; ++i) { int c = buf [i]; switch (pe->state) { case RTEMS_FTPFS_PASV_START: if (!isdigit(c)) { pe->state = RTEMS_FTPFS_PASV_JUNK; pe->index = 0; } break; case RTEMS_FTPFS_PASV_JUNK: if (isdigit(c)) { pe->state = RTEMS_FTPFS_PASV_DATA; pe->data [pe->index] = (uint8_t) (c - '0'); } break; case RTEMS_FTPFS_PASV_DATA: if (isdigit(c)) { pe->data [pe->index] = (uint8_t) (pe->data [pe->index] * 10 + c - '0'); } else if (c == ',') { ++pe->index; if (pe->index < sizeof(pe->data)) { pe->data [pe->index] = 0; } else { pe->state = RTEMS_FTPFS_PASV_DONE; } } else { pe->state = RTEMS_FTPFS_PASV_DONE; } break; default: return; } } } static int rtems_ftpfs_open_data_connection_passive( rtems_ftpfs_entry *e, const char *file_command, bool verbose, const struct timeval *timeout ) { int rv = 0; rtems_ftpfs_reply reply = RTEMS_FTPFS_REPLY_ERROR; struct sockaddr_in sa; uint32_t data_address = 0; uint16_t data_port = 0; rtems_ftpfs_pasv_entry pe; memset(&pe, 0, sizeof(pe)); /* Send PASV command */ reply = rtems_ftpfs_send_command_with_parser( e, "PASV", NULL, rtems_ftpfs_pasv_parser, &pe, verbose ); if (reply != RTEMS_FTPFS_REPLY_2) { return ENOTSUP; } data_address = ((uint32_t)(pe.data [0]) << 24) + ((uint32_t)(pe.data [1]) << 16) + ((uint32_t)(pe.data [2]) << 8) + ((uint32_t)(pe.data [3])); data_port = (uint16_t) ((pe.data [4] << 8) + pe.data [5]); rtems_ftpfs_create_address(&sa, htonl(data_address), htons(data_port)); DEBUG_PRINTF( "server data = %s:%u\n", inet_ntoa(sa.sin_addr), (unsigned) ntohs(sa.sin_port) ); /* Create data socket */ e->data_socket = socket(AF_INET, SOCK_STREAM, 0); if (e->data_socket < 0) { return ENOMEM; } /* Open data connection */ rv = connect( e->data_socket, (struct sockaddr *) &sa, sizeof(sa) ); if (rv != 0) { return EIO; } /* Send RETR or STOR command with filename */ reply = rtems_ftpfs_send_command(e, file_command, e->filename, verbose); if (reply != RTEMS_FTPFS_REPLY_1) { return EIO; } return 0; } typedef enum { RTEMS_FTPFS_SIZE_START = 0, RTEMS_FTPFS_SIZE_SPACE, RTEMS_FTPFS_SIZE_NUMBER, RTEMS_FTPFS_SIZE_NL } rtems_ftpfs_size_state; typedef struct { rtems_ftpfs_size_state state; size_t index; off_t size; } rtems_ftpfs_size_entry; static void rtems_ftpfs_size_parser( const char* buf, size_t len, void *arg ) { rtems_ftpfs_size_entry *se = arg; size_t i = 0; for (i = 0; se->size >= 0 && i < len; ++i, ++se->index) { int c = buf [i]; switch (se->state) { case RTEMS_FTPFS_SIZE_START: if (se->index == 2) { se->state = RTEMS_FTPFS_SIZE_SPACE; } break; case RTEMS_FTPFS_SIZE_SPACE: if (c == ' ') { se->state = RTEMS_FTPFS_SIZE_NUMBER; } else { se->size = -1; } break; case RTEMS_FTPFS_SIZE_NUMBER: if (isdigit(c)) { se->size = 10 * se->size + c - '0'; } else if (c == '\r') { se->state = RTEMS_FTPFS_SIZE_NL; } else { se->size = -1; } break; case RTEMS_FTPFS_SIZE_NL: if (c != '\n') { se->size = -1; } break; default: se->size = -1; break; } } } static void rtems_ftpfs_get_file_size(rtems_ftpfs_entry *e, bool verbose) { if (e->file_size < 0) { if (e->write) { e->file_size = 0; } else { rtems_ftpfs_size_entry se; rtems_ftpfs_reply reply = RTEMS_FTPFS_REPLY_ERROR; memset(&se, 0, sizeof(se)); reply = rtems_ftpfs_send_command_with_parser( e, "SIZE ", e->filename, rtems_ftpfs_size_parser, &se, verbose ); if (reply == RTEMS_FTPFS_REPLY_2 && se.size >= 0) { e->file_size = se.size; } else { e->file_size = 0; } } } } static int rtems_ftpfs_open( rtems_libio_t *iop, const char *path, int oflag, mode_t mode ) { int eno = 0; rtems_ftpfs_entry *e = iop->pathinfo.node_access; rtems_ftpfs_mount_entry *me = iop->pathinfo.mt_entry->fs_info; bool verbose = me->verbose; const struct timeval *timeout = &me->timeout; e->write = (iop->flags & LIBIO_FLAGS_WRITE) != 0; /* Check for either read-only or write-only flags */ if ( (iop->flags & LIBIO_FLAGS_WRITE) != 0 && (iop->flags & LIBIO_FLAGS_READ) != 0 ) { eno = ENOTSUP; } if (eno == 0) { rtems_ftpfs_get_file_size(e, verbose); } if (eno == 0) { const char *file_command = e->write ? "STOR " : "RETR "; /* Open passive data connection */ eno = rtems_ftpfs_open_data_connection_passive( e, file_command, verbose, timeout ); if (eno == ENOTSUP) { /* Open active data connection */ eno = rtems_ftpfs_open_data_connection_active( e, file_command, verbose, timeout ); } } /* Set data connection timeout */ if (eno == 0) { eno = rtems_ftpfs_set_connection_timeout(e->data_socket, timeout); } if (eno == 0) { return 0; } else { rtems_ftpfs_close_data_connection(e, verbose, true); rtems_set_errno_and_return_minus_one(eno); } } static ssize_t rtems_ftpfs_read( rtems_libio_t *iop, void *buffer, size_t count ) { rtems_ftpfs_entry *e = iop->pathinfo.node_access; const rtems_ftpfs_mount_entry *me = iop->pathinfo.mt_entry->fs_info; bool verbose = me->verbose; char *in = buffer; size_t todo = count; if (e->eof) { return 0; } while (todo > 0) { ssize_t rv = recv(e->data_socket, in, todo, 0); if (rv <= 0) { if (rv == 0) { rtems_ftpfs_reply reply = rtems_ftpfs_get_reply(e, NULL, NULL, verbose); if (reply == RTEMS_FTPFS_REPLY_2) { e->eof = true; break; } } rtems_set_errno_and_return_minus_one(EIO); } in += rv; todo -= (size_t) rv; } return (ssize_t) (count - todo); } static ssize_t rtems_ftpfs_write( rtems_libio_t *iop, const void *buffer, size_t count ) { rtems_ftpfs_entry *e = iop->pathinfo.node_access; const char *out = buffer; size_t todo = count; while (todo > 0) { ssize_t rv = send(e->data_socket, out, todo, 0); if (rv <= 0) { if (rv == 0) { break; } else { rtems_set_errno_and_return_minus_one(EIO); } } out += rv; todo -= (size_t) rv; e->file_size += rv; } return (ssize_t) (count - todo); } static int rtems_ftpfs_close(rtems_libio_t *iop) { rtems_ftpfs_entry *e = iop->pathinfo.node_access; const rtems_ftpfs_mount_entry *me = iop->pathinfo.mt_entry->fs_info; int eno = rtems_ftpfs_close_data_connection(e, me->verbose, false); if (eno == 0) { return 0; } else { rtems_set_errno_and_return_minus_one(eno); } } /* Dummy version to let fopen(*,"w") work properly */ static int rtems_ftpfs_ftruncate(rtems_libio_t *iop, off_t count) { return 0; } static void rtems_ftpfs_eval_path( rtems_filesystem_eval_path_context_t *self ) { int eno = 0; rtems_filesystem_eval_path_eat_delimiter(self); if (rtems_filesystem_eval_path_has_path(self)) { const char *path = rtems_filesystem_eval_path_get_path(self); size_t pathlen = rtems_filesystem_eval_path_get_pathlen(self); rtems_ftpfs_entry *e = calloc(1, sizeof(*e) + pathlen + 1); rtems_filesystem_eval_path_clear_path(self); if (e != NULL) { memcpy(e->buffer, path, pathlen); eno = rtems_ftpfs_split_names( e->buffer, &e->user, &e->password, &e->hostname, &e->filename ); DEBUG_PRINTF( "user = '%s', password = '%s', filename = '%s'\n", e->user, e->password, e->filename ); if (eno == 0) { rtems_filesystem_location_info_t *currentloc = rtems_filesystem_eval_path_get_currentloc(self); rtems_ftpfs_mount_entry *me = currentloc->mt_entry->fs_info; rtems_libio_lock(); ++me->ino; e->ino = me->ino; rtems_libio_unlock(); e->file_size = -1; e->ctrl_socket = -1; eno = rtems_ftpfs_open_ctrl_connection( e, me->verbose, &me->timeout ); if (eno == 0) { currentloc->node_access = e; currentloc->handlers = &rtems_ftpfs_handlers; } } if (eno != 0) { free(e); } } else { eno = ENOMEM; } } if (eno != 0) { rtems_filesystem_eval_path_error(self, eno); } } static void rtems_ftpfs_free_node(const rtems_filesystem_location_info_t *loc) { rtems_ftpfs_entry *e = loc->node_access; /* The root node handler has no entry */ if (e != NULL) { const rtems_ftpfs_mount_entry *me = loc->mt_entry->fs_info; /* Close control connection if necessary */ if (e->ctrl_socket >= 0) { rtems_ftpfs_send_command(e, "QUIT", NULL, me->verbose); close(e->ctrl_socket); } free(e); } } int rtems_ftpfs_initialize( rtems_filesystem_mount_table_entry_t *e, const void *d ) { rtems_ftpfs_mount_entry *me = calloc(1, sizeof(*me)); /* Mount entry for FTP file system instance */ e->fs_info = me; if (e->fs_info == NULL) { rtems_set_errno_and_return_minus_one(ENOMEM); } me->verbose = false; me->timeout.tv_sec = 0; me->timeout.tv_usec = 0; /* Set handler and oparations table */ e->mt_fs_root->location.handlers = &rtems_ftpfs_root_handlers; e->ops = &rtems_ftpfs_ops; /* We maintain no real file system nodes, so there is no real root */ e->mt_fs_root->location.node_access = NULL; return 0; } static void rtems_ftpfs_unmount_me( rtems_filesystem_mount_table_entry_t *e ) { free(e->fs_info); } static int rtems_ftpfs_ioctl( rtems_libio_t *iop, uint32_t command, void *arg ) { rtems_ftpfs_mount_entry *me = iop->pathinfo.mt_entry->fs_info; bool *verbose = arg; struct timeval *timeout = arg; if (arg == NULL) { rtems_set_errno_and_return_minus_one(EINVAL); } switch (command) { case RTEMS_FTPFS_IOCTL_GET_VERBOSE: *verbose = me->verbose; break; case RTEMS_FTPFS_IOCTL_SET_VERBOSE: me->verbose = *verbose; break; case RTEMS_FTPFS_IOCTL_GET_TIMEOUT: *timeout = me->timeout; break; case RTEMS_FTPFS_IOCTL_SET_TIMEOUT: me->timeout = *timeout; break; default: rtems_set_errno_and_return_minus_one(EINVAL); } return 0; } /* * The stat() support is intended only for the cp shell command. Each request * will return that we have a regular file with read, write and execute * permissions for every one. The node index uses a global counter to support * a remote to remote copy. This is not a very sophisticated method. */ static int rtems_ftpfs_fstat( const rtems_filesystem_location_info_t *loc, struct stat *st ) { int eno = 0; rtems_ftpfs_entry *e = loc->node_access; /* FIXME */ st->st_ino = e->ino; st->st_dev = rtems_filesystem_make_dev_t(0xcc494cd6U, 0x1d970b4dU); st->st_mode = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO; if (e->do_size_command) { const rtems_ftpfs_mount_entry *me = loc->mt_entry->fs_info; rtems_ftpfs_get_file_size(e, me->verbose); st->st_size = e->file_size; } else { e->do_size_command = true; } if (eno == 0) { return 0; } else { rtems_set_errno_and_return_minus_one(eno); } } static void rtems_ftpfs_lock_or_unlock( const rtems_filesystem_mount_table_entry_t *mt_entry ) { /* Do nothing */ } static const rtems_filesystem_operations_table rtems_ftpfs_ops = { .lock_h = rtems_ftpfs_lock_or_unlock, .unlock_h = rtems_ftpfs_lock_or_unlock, .eval_path_h = rtems_ftpfs_eval_path, .link_h = rtems_filesystem_default_link, .are_nodes_equal_h = rtems_filesystem_default_are_nodes_equal, .mknod_h = rtems_filesystem_default_mknod, .rmnod_h = rtems_filesystem_default_rmnod, .fchmod_h = rtems_filesystem_default_fchmod, .chown_h = rtems_filesystem_default_chown, .clonenod_h = rtems_filesystem_default_clonenode, .freenod_h = rtems_ftpfs_free_node, .mount_h = rtems_filesystem_default_mount, .unmount_h = rtems_filesystem_default_unmount, .fsunmount_me_h = rtems_ftpfs_unmount_me, .utime_h = rtems_filesystem_default_utime, .symlink_h = rtems_filesystem_default_symlink, .readlink_h = rtems_filesystem_default_readlink, .rename_h = rtems_filesystem_default_rename, .statvfs_h = rtems_filesystem_default_statvfs }; static const rtems_filesystem_file_handlers_r rtems_ftpfs_handlers = { .open_h = rtems_ftpfs_open, .close_h = rtems_ftpfs_close, .read_h = rtems_ftpfs_read, .write_h = rtems_ftpfs_write, .ioctl_h = rtems_filesystem_default_ioctl, .lseek_h = rtems_filesystem_default_lseek, .fstat_h = rtems_ftpfs_fstat, .ftruncate_h = rtems_ftpfs_ftruncate, .fsync_h = rtems_filesystem_default_fsync_or_fdatasync, .fdatasync_h = rtems_filesystem_default_fsync_or_fdatasync, .fcntl_h = rtems_filesystem_default_fcntl, .kqfilter_h = rtems_filesystem_default_kqfilter, .poll_h = rtems_filesystem_default_poll, .readv_h = rtems_filesystem_default_readv, .writev_h = rtems_filesystem_default_writev }; static const rtems_filesystem_file_handlers_r rtems_ftpfs_root_handlers = { .open_h = rtems_filesystem_default_open, .close_h = rtems_filesystem_default_close, .read_h = rtems_filesystem_default_read, .write_h = rtems_filesystem_default_write, .ioctl_h = rtems_ftpfs_ioctl, .lseek_h = rtems_filesystem_default_lseek, .fstat_h = rtems_filesystem_default_fstat, .ftruncate_h = rtems_filesystem_default_ftruncate, .fsync_h = rtems_filesystem_default_fsync_or_fdatasync, .fdatasync_h = rtems_filesystem_default_fsync_or_fdatasync, .fcntl_h = rtems_filesystem_default_fcntl, .kqfilter_h = rtems_filesystem_default_kqfilter, .poll_h = rtems_filesystem_default_poll, .readv_h = rtems_filesystem_default_readv, .writev_h = rtems_filesystem_default_writev };