mirror of
https://git.rtems.org/rtems-libbsd/
synced 2025-05-13 12:39:22 +08:00
1429 lines
31 KiB
C
1429 lines
31 KiB
C
/**
|
|
* @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 = (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
|
|
};
|