source: rtems/cpukit/libnetworking/lib/ftpfs.c @ da154e14

4.11
Last change on this file since da154e14 was da154e14, checked in by Sebastian Huber <sebastian.huber@…>, on May 14, 2012 at 2:55:41 PM

Filesystem: Move operations to mount table entry

The scope of the file system operations is the file system instance.
The scope of the file system node handlers is the file location. The
benefit of moving the operations to the mount table entry is a size
reduction of the file location (rtems_filesystem_location_info_t). The
code size is slightly increased due to additional load instructions.

Restructure rtems_filesystem_mount_table_entry_t to improve cache
efficiency.

  • Property mode set to 100644
File size: 29.2 KB
Line 
1/**
2 * @file
3 *
4 * File Transfer Protocol file system (FTP client).
5 */
6
7/*
8 * Copyright (c) 2009-2012
9 * embedded brains GmbH
10 * Obere Lagerstr. 30
11 * D-82178 Puchheim
12 * Germany
13 * <rtems@embedded-brains.de>
14 *
15 * (c) Copyright 2002
16 * Thomas Doerfler
17 * IMD Ingenieurbuero fuer Microcomputertechnik
18 * Herbststr. 8
19 * 82178 Puchheim, Germany
20 * <Thomas.Doerfler@imd-systems.de>
21 *
22 * Modified by Sebastian Huber <sebastian.huber@embedded-brains.de>.
23 *
24 * This code has been created after closly inspecting "tftpdriver.c" from Eric
25 * Norum.
26 *
27 * The license and distribution terms for this file may be
28 * found in the file LICENSE in this distribution or at
29 * http://www.rtems.com/license/LICENSE.
30 */
31
32#ifdef HAVE_CONFIG_H
33#include "config.h"
34#endif
35
36#include <ctype.h>
37#include <errno.h>
38#include <fcntl.h>
39#include <malloc.h>
40#include <netdb.h>
41#include <stdarg.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <string.h>
45#include <unistd.h>
46#include <arpa/inet.h>
47#include <netinet/in.h>
48#include <sys/select.h>
49#include <sys/socket.h>
50#include <sys/types.h>
51
52#include <rtems.h>
53#include <rtems/ftpfs.h>
54#include <rtems/imfs.h>
55#include <rtems/libio_.h>
56#include <rtems/rtems_bsdnet.h>
57#include <rtems/seterr.h>
58
59#ifdef DEBUG
60  #define DEBUG_PRINTF(...) printf(__VA_ARGS__)
61#else
62  #define DEBUG_PRINTF(...)
63#endif
64
65/**
66 * Connection entry for each open file stream.
67 */
68typedef struct {
69  /**
70   * Control connection socket.
71   */
72  int ctrl_socket;
73
74  /**
75   * Data transfer socket.
76   */
77  int data_socket;
78
79  /**
80   * Current index into the reply buffer.
81   */
82  size_t reply_current;
83
84  /**
85   * End index of the reply buffer.
86   */
87  size_t reply_end;
88
89  /**
90   * Buffer for relpy data.
91   */
92  char reply_buffer [128];
93
94  /**
95   * End of file flag.
96   */
97  bool eof;
98} rtems_ftpfs_entry;
99
100/**
101 * Mount entry for each file system instance.
102 */
103typedef struct {
104  /**
105   * Verbose mode enabled or disabled.
106   */
107  bool verbose;
108
109  /**
110   * Timeout value
111   */
112  struct timeval timeout;
113} rtems_ftpfs_mount_entry;
114
115static const rtems_filesystem_operations_table rtems_ftpfs_ops;
116
117static const rtems_filesystem_file_handlers_r rtems_ftpfs_handlers;
118
119static const rtems_filesystem_file_handlers_r rtems_ftpfs_root_handlers;
120
121static bool rtems_ftpfs_use_timeout(const struct timeval *to)
122{
123  return to->tv_sec != 0 || to->tv_usec != 0;
124}
125
126static int rtems_ftpfs_set_connection_timeout(
127  int socket,
128  const struct timeval *to
129)
130{
131  if (rtems_ftpfs_use_timeout(to)) {
132    int rv = 0;
133   
134    rv = setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, to, sizeof(*to));
135    if (rv != 0) {
136      return EIO;
137    }
138
139    rv = setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, to, sizeof(*to));
140    if (rv != 0) {
141      return EIO;
142    }
143  }
144
145  return 0;
146}
147
148static rtems_status_code rtems_ftpfs_do_ioctl(
149  const char *mount_point,
150  ioctl_command_t req,
151  ...
152)
153{
154  rtems_status_code sc = RTEMS_SUCCESSFUL;
155  int rv = 0;
156  int fd = 0;
157  va_list ap;
158
159  if (mount_point == NULL) {
160    mount_point = RTEMS_FTPFS_MOUNT_POINT_DEFAULT;
161  }
162
163  fd = open(mount_point, O_RDWR);
164  if (fd < 0) {
165    return RTEMS_INVALID_NAME;
166  }
167 
168  va_start(ap, req);
169  rv = ioctl(fd, req, va_arg(ap, void *));
170  va_end(ap);
171  if (rv != 0) {
172    sc = RTEMS_INVALID_NUMBER;
173  }
174
175  rv = close(fd);
176  if (rv != 0 && sc == RTEMS_SUCCESSFUL) {
177    sc = RTEMS_IO_ERROR;
178  }
179 
180  return sc;
181}
182
183rtems_status_code rtems_ftpfs_get_verbose(const char *mount_point, bool *verbose)
184{
185  return rtems_ftpfs_do_ioctl(
186    mount_point,
187    RTEMS_FTPFS_IOCTL_GET_VERBOSE,
188    verbose
189  );
190}
191
192rtems_status_code rtems_ftpfs_set_verbose(const char *mount_point, bool verbose)
193{
194  return rtems_ftpfs_do_ioctl(
195    mount_point,
196    RTEMS_FTPFS_IOCTL_SET_VERBOSE,
197    &verbose
198  );
199}
200
201rtems_status_code rtems_ftpfs_get_timeout(
202  const char *mount_point,
203  struct timeval *timeout
204)
205{
206  return rtems_ftpfs_do_ioctl(
207    mount_point,
208    RTEMS_FTPFS_IOCTL_GET_TIMEOUT,
209    timeout
210  );
211}
212
213rtems_status_code rtems_ftpfs_set_timeout(
214  const char *mount_point,
215  const struct timeval *timeout
216)
217{
218  return rtems_ftpfs_do_ioctl(
219    mount_point,
220    RTEMS_FTPFS_IOCTL_SET_TIMEOUT,
221    timeout
222  );
223}
224
225typedef void (*rtems_ftpfs_reply_parser)(
226  const char * /* reply fragment */,
227  size_t /* reply fragment length */,
228  void * /* parser argument */
229);
230
231typedef enum {
232  RTEMS_FTPFS_REPLY_START,
233  RTEMS_FTPFS_REPLY_SINGLE_LINE,
234  RTEMS_FTPFS_REPLY_DONE,
235  RTEMS_FTPFS_REPLY_MULTI_LINE,
236  RTEMS_FTPFS_REPLY_MULTI_LINE_START
237} rtems_ftpfs_reply_state;
238
239typedef enum {
240  RTEMS_FTPFS_REPLY_ERROR = 0,
241  RTEMS_FTPFS_REPLY_1 = '1',
242  RTEMS_FTPFS_REPLY_2 = '2',
243  RTEMS_FTPFS_REPLY_3 = '3',
244  RTEMS_FTPFS_REPLY_4 = '4',
245  RTEMS_FTPFS_REPLY_5 = '5'
246} rtems_ftpfs_reply;
247
248#define RTEMS_FTPFS_REPLY_SIZE 3
249
250static bool rtems_ftpfs_is_reply_code_valid(unsigned char *reply)
251{
252  return isdigit(reply [0]) && isdigit(reply [1]) && isdigit(reply [2]);
253}
254
255static rtems_ftpfs_reply rtems_ftpfs_get_reply(
256  rtems_ftpfs_entry *e,
257  rtems_ftpfs_reply_parser parser,
258  void *parser_arg,
259  bool verbose
260)
261{
262  rtems_ftpfs_reply_state state = RTEMS_FTPFS_REPLY_START;
263  unsigned char reply_code [RTEMS_FTPFS_REPLY_SIZE] = { 'a', 'a', 'a' };
264  size_t reply_code_index = 0;
265
266  while (state != RTEMS_FTPFS_REPLY_DONE) {
267    char *buf = NULL;
268    size_t i = 0;
269    size_t n = 0;
270
271    /* Receive reply fragment from socket */
272    if (e->reply_current == e->reply_end) {
273      ssize_t rv = 0;
274
275      e->reply_current = 0;
276      e->reply_end = 0;
277
278      rv = recv(
279        e->ctrl_socket,
280        &e->reply_buffer [0],
281        sizeof(e->reply_buffer),
282        0
283      );
284
285      if (rv > 0) {
286        e->reply_end = (size_t) rv;
287      } else {
288        return RTEMS_FTPFS_REPLY_ERROR;
289      }
290    }
291
292    buf = &e->reply_buffer [e->reply_current];
293    n = e->reply_end - e->reply_current;
294
295    /* Invoke parser if necessary */
296    if (parser != NULL) {
297      parser(buf, n, parser_arg);
298    }
299
300    /* Parse reply fragment */
301    for (i = 0; i < n && state != RTEMS_FTPFS_REPLY_DONE; ++i) {
302      char c = buf [i];
303
304      switch (state) {
305        case RTEMS_FTPFS_REPLY_START:
306          if (reply_code_index < RTEMS_FTPFS_REPLY_SIZE) {
307            reply_code [reply_code_index] = c;
308            ++reply_code_index;
309          } else if (rtems_ftpfs_is_reply_code_valid(reply_code)) {
310            if (c == '-') {
311              state = RTEMS_FTPFS_REPLY_MULTI_LINE;
312            } else {
313              state = RTEMS_FTPFS_REPLY_SINGLE_LINE;
314            }
315          } else {
316            return RTEMS_FTPFS_REPLY_ERROR;
317          }
318          break;
319        case RTEMS_FTPFS_REPLY_SINGLE_LINE:
320          if (c == '\n') {
321            state = RTEMS_FTPFS_REPLY_DONE;
322          }
323          break;
324        case RTEMS_FTPFS_REPLY_MULTI_LINE:
325          if (c == '\n') {
326            state = RTEMS_FTPFS_REPLY_MULTI_LINE_START;
327            reply_code_index = 0;
328          }
329          break;
330        case RTEMS_FTPFS_REPLY_MULTI_LINE_START:
331          if (reply_code_index < RTEMS_FTPFS_REPLY_SIZE) {
332            if (reply_code [reply_code_index] == c) {
333              ++reply_code_index;
334            } else {
335              state = RTEMS_FTPFS_REPLY_MULTI_LINE;
336            }
337          } else {
338            if (c == ' ') {
339              state = RTEMS_FTPFS_REPLY_SINGLE_LINE;
340            } else {
341              state = RTEMS_FTPFS_REPLY_MULTI_LINE;
342            }
343          }
344          break;
345        default:
346          return RTEMS_FTPFS_REPLY_ERROR;
347      }
348    }
349
350    /* Be verbose if necessary */
351    if (verbose) {
352      write(STDERR_FILENO, buf, i);
353    }
354
355    /* Update reply index */
356    e->reply_current += i;
357  }
358
359  return reply_code [0];
360}
361
362static rtems_ftpfs_reply rtems_ftpfs_send_command_with_parser(
363  rtems_ftpfs_entry *e,
364  const char *cmd,
365  const char *arg,
366  rtems_ftpfs_reply_parser parser,
367  void *parser_arg,
368  bool verbose
369)
370{
371  rtems_ftpfs_reply reply = RTEMS_FTPFS_REPLY_ERROR;
372  size_t cmd_len = strlen(cmd);
373  size_t arg_len = arg != NULL ? strlen(arg) : 0;
374  size_t len = cmd_len + arg_len + 2;
375  char *buf = malloc(len);
376
377  if (buf != NULL) {
378    ssize_t n = 0;
379    char *buf_arg = buf + cmd_len;
380    char *buf_eol = buf_arg + arg_len;
381
382    memcpy(buf, cmd, cmd_len);
383    memcpy(buf_arg, arg, arg_len);
384    buf_eol [0] = '\r';
385    buf_eol [1] = '\n';
386
387    /* Send */
388    n = send(e->ctrl_socket, buf, len, 0);
389    if (n == (ssize_t) len) {
390      if (verbose) {
391        write(STDERR_FILENO, buf, len);
392      }
393
394      /* Reply */
395      reply = rtems_ftpfs_get_reply(e, parser, parser_arg, verbose);
396    }
397
398    free(buf);
399  }
400
401  return reply;
402}
403
404static rtems_ftpfs_reply rtems_ftpfs_send_command(
405  rtems_ftpfs_entry *e,
406  const char *cmd,
407  const char *arg,
408  bool verbose
409)
410{
411  return rtems_ftpfs_send_command_with_parser(
412    e,
413    cmd,
414    arg,
415    NULL,
416    NULL,
417    verbose
418  );
419}
420
421typedef enum {
422  STATE_USER_NAME,
423  STATE_START_PASSWORD,
424  STATE_START_HOST_NAME,
425  STATE_START_HOST_NAME_OR_PATH,
426  STATE_START_PATH,
427  STATE_PASSWORD,
428  STATE_HOST_NAME,
429  STATE_DONE,
430  STATE_INVALID
431} split_state;
432
433static bool rtems_ftpfs_split_names (
434  char *s,
435  const char **user,
436  const char **password,
437  const char **hostname,
438  const char **path
439)
440{
441  split_state state = STATE_USER_NAME;
442  size_t len = strlen(s);
443  size_t i = 0;
444
445  *user = s;
446  *password = NULL;
447  *hostname = NULL;
448  *path = NULL;
449
450  for (i = 0; i < len; ++i) {
451    char c = s [i];
452
453    switch (state) {
454      case STATE_USER_NAME:
455        if (c == ':') {
456          state = STATE_START_PASSWORD;
457          s [i] = '\0';
458        } else if (c == '@') {
459          state = STATE_START_HOST_NAME;
460          s [i] = '\0';
461        } else if (c == '/') {
462          state = STATE_START_HOST_NAME_OR_PATH;
463          s [i] = '\0';
464        }
465        break;
466      case STATE_START_PASSWORD:
467        state = STATE_PASSWORD;
468        *password = &s [i];
469        --i;
470        break;
471      case STATE_START_HOST_NAME:
472        state = STATE_HOST_NAME;
473        *hostname = &s [i];
474        --i;
475        break;
476      case STATE_START_HOST_NAME_OR_PATH:
477        if (c == '@') {
478          state = STATE_START_HOST_NAME;
479        } else {
480          state = STATE_DONE;
481          *path = &s [i];
482          goto done;
483        }
484        break;
485      case STATE_START_PATH:
486        state = STATE_DONE;
487        *path = &s [i];
488        goto done;
489      case STATE_PASSWORD:
490        if (c == '@') {
491          state = STATE_START_HOST_NAME;
492          s [i] = '\0';
493        } else if (c == '/') {
494          state = STATE_START_HOST_NAME_OR_PATH;
495          s [i] = '\0';
496        }
497        break;
498      case STATE_HOST_NAME:
499        if (c == '/') {
500          state = STATE_START_PATH;
501          s [i] = '\0';
502        }
503        break;
504      default:
505        state = STATE_INVALID;
506        goto done;
507    }
508  }
509
510done:
511
512  /* This is a special case with no username and password */
513  if (*hostname == NULL) {
514    *hostname = &s [0];
515    *user = "anonymous";
516    *password = *user;
517  }
518
519  /* If we have no password use the user name */
520  if (*password == NULL) {
521    *password = *user;
522  }
523
524  return state == STATE_DONE;
525}
526
527static socklen_t rtems_ftpfs_create_address(
528  struct sockaddr_in *sa,
529  unsigned long address,
530  unsigned short port
531)
532{
533  memset(sa, sizeof(*sa), 0);
534
535  sa->sin_family = AF_INET;
536  sa->sin_addr.s_addr = address;
537  sa->sin_port = port;
538  sa->sin_len = sizeof(*sa);
539
540  return sizeof(*sa);
541}
542
543static int rtems_ftpfs_terminate(rtems_libio_t *iop, bool error)
544{
545  int eno = 0;
546  int rv = 0;
547  rtems_ftpfs_entry *e = iop->data1;
548  rtems_ftpfs_mount_entry *me = iop->pathinfo.mt_entry->fs_info;
549  bool verbose = me->verbose;
550  rtems_ftpfs_reply reply = RTEMS_FTPFS_REPLY_ERROR;
551
552  if (e != NULL) {
553    /* Close data connection if necessary */
554    if (e->data_socket >= 0) {
555      rv = close(e->data_socket);
556      if (rv != 0) {
557        eno = EIO;
558      }
559
560      /* For write connections we have to obtain the transfer reply  */
561      if (
562        e->ctrl_socket >= 0
563          && (iop->flags & LIBIO_FLAGS_WRITE) != 0
564          && !error
565      ) {
566        reply = rtems_ftpfs_get_reply(e, NULL, NULL, verbose);
567        if (reply != RTEMS_FTPFS_REPLY_2) {
568          eno = EIO;
569        }
570      }
571    }
572
573    /* Close control connection if necessary */
574    if (e->ctrl_socket >= 0) {
575      reply = rtems_ftpfs_send_command(e, "QUIT", NULL, verbose);
576      if (reply != RTEMS_FTPFS_REPLY_2) {
577        eno = EIO;
578      }
579
580      rv = close(e->ctrl_socket);
581      if (rv != 0) {
582        eno = EIO;
583      }
584    }
585
586    /* Free connection entry */
587    free(e);
588  }
589
590  /* Invalidate IO entry */
591  iop->data1 = NULL;
592
593  return eno;
594}
595
596static int rtems_ftpfs_open_ctrl_connection(
597  rtems_ftpfs_entry *e,
598  const char *user,
599  const char *password,
600  const char *hostname,
601  uint32_t *client_address,
602  bool verbose,
603  const struct timeval *timeout
604)
605{
606  int rv = 0;
607  int eno = 0;
608  rtems_ftpfs_reply reply = RTEMS_FTPFS_REPLY_ERROR;
609  struct in_addr address = { .s_addr = 0 };
610  struct sockaddr_in sa;
611  socklen_t size = 0;
612
613  /* Create the socket for the control connection */
614  e->ctrl_socket = socket(AF_INET, SOCK_STREAM, 0);
615  if (e->ctrl_socket < 0) {
616    return ENOMEM;
617  }
618
619  /* Set up the server address from the hostname */
620  if (inet_aton(hostname, &address) == 0) {
621    /* Try to get the address by name */
622    struct hostent *he = gethostbyname(hostname);
623
624    if (he != NULL) {
625      memcpy(&address, he->h_addr, sizeof(address));
626    } else {
627      return ENOENT;
628    }
629  }
630  rtems_ftpfs_create_address(&sa, address.s_addr, htons(RTEMS_FTPFS_CTRL_PORT));
631  DEBUG_PRINTF("server = %s\n", inet_ntoa(sa.sin_addr));
632
633  /* Open control connection */
634  rv = connect(
635    e->ctrl_socket,
636    (struct sockaddr *) &sa,
637    sizeof(sa)
638  );
639  if (rv != 0) {
640    return ENOENT;
641  }
642
643  /* Set control connection timeout */
644  eno = rtems_ftpfs_set_connection_timeout(e->ctrl_socket, timeout);
645  if (eno != 0) {
646    return eno;
647  }
648
649  /* Get client address */
650  size = rtems_ftpfs_create_address(&sa, INADDR_ANY, 0);
651  rv = getsockname(
652    e->ctrl_socket,
653    (struct sockaddr *) &sa,
654    &size
655  );
656  if (rv != 0) {
657    return ENOMEM;
658  }
659  *client_address = ntohl(sa.sin_addr.s_addr);
660  DEBUG_PRINTF("client = %s\n", inet_ntoa(sa.sin_addr));
661
662  /* Now we should get a welcome message from the server */
663  reply = rtems_ftpfs_get_reply(e, NULL, NULL, verbose);
664  if (reply != RTEMS_FTPFS_REPLY_2) {
665    return ENOENT;
666  }
667
668  /* Send USER command */
669  reply = rtems_ftpfs_send_command(e, "USER ", user, verbose);
670  if (reply == RTEMS_FTPFS_REPLY_3) {
671    /* Send PASS command */
672    reply = rtems_ftpfs_send_command(e, "PASS ", password, verbose);
673    if (reply != RTEMS_FTPFS_REPLY_2) {
674      return EACCES;
675    }
676
677    /* TODO: Some server may require an account */
678  } else if (reply != RTEMS_FTPFS_REPLY_2) {
679    return EACCES;
680  }
681
682  /* Send TYPE command to set binary mode for all data transfers */
683  reply = rtems_ftpfs_send_command(e, "TYPE I", NULL, verbose);
684  if (reply != RTEMS_FTPFS_REPLY_2) {
685    return EIO;
686  }
687
688  return 0;
689}
690
691static int rtems_ftpfs_open_data_connection_active(
692  rtems_ftpfs_entry *e,
693  uint32_t client_address,
694  const char *file_command,
695  const char *filename,
696  bool verbose,
697  const struct timeval *timeout
698)
699{
700  int rv = 0;
701  int eno = 0;
702  rtems_ftpfs_reply reply = RTEMS_FTPFS_REPLY_ERROR;
703  struct sockaddr_in sa;
704  socklen_t size = 0;
705  int port_socket = -1;
706  char port_command [] = "PORT 000,000,000,000,000,000";
707  uint16_t data_port = 0;
708
709  /* Create port socket to establish a data data connection */
710  port_socket = socket(AF_INET, SOCK_STREAM, 0);
711  if (port_socket < 0) {
712    eno = ENOMEM;
713    goto cleanup;
714  }
715
716  /* Bind port socket */
717  rtems_ftpfs_create_address(&sa, INADDR_ANY, 0);
718  rv = bind(
719    port_socket,
720    (struct sockaddr *) &sa,
721    sizeof(sa)
722  );
723  if (rv != 0) {
724    eno = EBUSY;
725    goto cleanup;
726  }
727
728  /* Get port number for data socket */
729  size = rtems_ftpfs_create_address(&sa, INADDR_ANY, 0);
730  rv = getsockname(
731    port_socket,
732    (struct sockaddr *) &sa,
733    &size
734  );
735  if (rv != 0) {
736    eno = ENOMEM;
737    goto cleanup;
738  }
739  data_port = ntohs(sa.sin_port);
740
741  /* Send PORT command to set data connection port for server */
742  snprintf(
743    port_command,
744    sizeof(port_command),
745    "PORT %lu,%lu,%lu,%lu,%lu,%lu",
746    (client_address >> 24) & 0xffUL,
747    (client_address >> 16) & 0xffUL,
748    (client_address >> 8) & 0xffUL,
749    (client_address >> 0) & 0xffUL,
750    (data_port >> 8) & 0xffUL,
751    (data_port >> 0) & 0xffUL
752  );
753  reply = rtems_ftpfs_send_command(e, port_command, NULL, verbose);
754  if (reply != RTEMS_FTPFS_REPLY_2) {
755    eno = ENOTSUP;
756    goto cleanup;
757  }
758
759  /* Listen on port socket for incoming data connections */
760  rv = listen(port_socket, 1);
761  if (rv != 0) {
762    eno = EBUSY;
763    goto cleanup;
764  }
765
766  /* Send RETR or STOR command with filename */
767  reply = rtems_ftpfs_send_command(e, file_command, filename, verbose);
768  if (reply != RTEMS_FTPFS_REPLY_1) {
769    eno = EIO;
770    goto cleanup;
771  }
772
773  /* Wait for connect on data connection if necessary */
774  if (rtems_ftpfs_use_timeout(timeout)) {
775    struct timeval to = *timeout;
776    fd_set fds;
777
778    FD_ZERO(&fds);
779    FD_SET(port_socket, &fds);
780
781    rv = select(port_socket + 1, &fds, NULL, NULL, &to);
782    if (rv <= 0) {
783      eno = EIO;
784      goto cleanup;
785    }
786  }
787
788  /* Accept data connection  */
789  size = sizeof(sa);
790  e->data_socket = accept(
791    port_socket,
792    (struct sockaddr *) &sa,
793    &size
794  );
795  if (e->data_socket < 0) {
796    eno = EIO;
797    goto cleanup;
798  }
799
800cleanup:
801
802  /* Close port socket if necessary */
803  if (port_socket >= 0) {
804    rv = close(port_socket);
805    if (rv != 0) {
806      eno = EIO;
807    }
808  }
809
810  return eno;
811}
812
813typedef enum {
814  RTEMS_FTPFS_PASV_START = 0,
815  RTEMS_FTPFS_PASV_JUNK,
816  RTEMS_FTPFS_PASV_DATA,
817  RTEMS_FTPFS_PASV_DONE
818} rtems_ftpfs_pasv_state;
819
820typedef struct {
821  rtems_ftpfs_pasv_state state;
822  uint8_t data [6];
823  size_t index;
824} rtems_ftpfs_pasv_entry;
825
826static void rtems_ftpfs_pasv_parser(
827  const char* buf,
828  size_t len,
829  void *arg
830)
831{
832  rtems_ftpfs_pasv_entry *e = arg;
833  size_t i = 0;
834
835  for (i = 0; i < len; ++i) {
836    int c = buf [i];
837
838    switch (e->state) {
839      case RTEMS_FTPFS_PASV_START:
840        if (!isdigit(c)) {
841          e->state = RTEMS_FTPFS_PASV_JUNK;
842          e->index = 0;
843        }
844        break;
845      case RTEMS_FTPFS_PASV_JUNK:
846        if (isdigit(c)) {
847          e->state = RTEMS_FTPFS_PASV_DATA;
848          e->data [e->index] = (uint8_t) (c - '0');
849        }
850        break;
851      case RTEMS_FTPFS_PASV_DATA:
852        if (isdigit(c)) {
853          e->data [e->index] = (uint8_t) (e->data [e->index] * 10 + c - '0');
854        } else if (c == ',') {
855          ++e->index;
856          if (e->index < sizeof(e->data)) {
857            e->data [e->index] = 0;
858          } else {
859            e->state = RTEMS_FTPFS_PASV_DONE;
860          }
861        } else {
862          e->state = RTEMS_FTPFS_PASV_DONE;
863        }
864        break;
865      default:
866        return;
867    }
868  }
869}
870
871static int rtems_ftpfs_open_data_connection_passive(
872  rtems_ftpfs_entry *e,
873  uint32_t client_address,
874  const char *file_command,
875  const char *filename,
876  bool verbose,
877  const struct timeval *timeout
878)
879{
880  int rv = 0;
881  rtems_ftpfs_reply reply = RTEMS_FTPFS_REPLY_ERROR;
882  struct sockaddr_in sa;
883  uint32_t data_address = 0;
884  uint16_t data_port = 0;
885
886  rtems_ftpfs_pasv_entry pe = {
887    .state = RTEMS_FTPFS_PASV_START
888  };
889
890  /* Send PASV command */
891  reply = rtems_ftpfs_send_command_with_parser(
892    e,
893    "PASV",
894    NULL,
895    rtems_ftpfs_pasv_parser,
896    &pe,
897    verbose
898  );
899  if (reply != RTEMS_FTPFS_REPLY_2) {
900    return ENOTSUP;
901  }
902  data_address = ((uint32_t)(pe.data [0]) << 24) + ((uint32_t)(pe.data [1]) << 16)
903    + ((uint32_t)(pe.data [2]) << 8) + ((uint32_t)(pe.data [3]));
904  data_port = (uint16_t) ((pe.data [4] << 8) + pe.data [5]);
905  rtems_ftpfs_create_address(&sa, htonl(data_address), htons(data_port));
906  DEBUG_PRINTF(
907    "server data = %s:%u\n",
908    inet_ntoa(sa.sin_addr),
909    (unsigned) ntohs(sa.sin_port)
910  );
911
912  /* Create data socket */
913  e->data_socket = socket(AF_INET, SOCK_STREAM, 0);
914  if (e->data_socket < 0) {
915    return ENOMEM;
916  }
917
918  /* Open data connection */
919  rv = connect(
920    e->data_socket,
921    (struct sockaddr *) &sa,
922    sizeof(sa)
923  );
924  if (rv != 0) {
925    return EIO;
926  }
927
928  /* Send RETR or STOR command with filename */
929  reply = rtems_ftpfs_send_command(e, file_command, filename, verbose);
930  if (reply != RTEMS_FTPFS_REPLY_1) {
931    return EIO;
932  }
933
934  return 0;
935}
936
937static int rtems_ftpfs_open(
938  rtems_libio_t *iop,
939  const char *path,
940  int oflag,
941  mode_t mode
942)
943{
944  int eno = 0;
945  bool ok = false;
946  rtems_ftpfs_entry *e = NULL;
947  rtems_ftpfs_mount_entry *me = iop->pathinfo.mt_entry->fs_info;
948  bool verbose = me->verbose;
949  const struct timeval *timeout = &me->timeout;
950  const char *user = NULL;
951  const char *password = NULL;
952  const char *hostname = NULL;
953  const char *filename = NULL;
954  const char *file_command = (iop->flags & LIBIO_FLAGS_WRITE) != 0
955    ? "STOR "
956    : "RETR ";
957  uint32_t client_address = 0;
958  char *location = iop->pathinfo.node_access;
959
960  /* Invalidate data handle */
961  iop->data1 = NULL;
962
963  /* Split location into parts */
964  ok = rtems_ftpfs_split_names(
965      location,
966      &user,
967      &password,
968      &hostname,
969      &filename
970  );
971  if (!ok) {
972    rtems_set_errno_and_return_minus_one(ENOENT);
973  }
974  DEBUG_PRINTF(
975    "user = '%s', password = '%s', filename = '%s'\n",
976    user,
977    password,
978    filename
979  );
980
981  /* Check for either read-only or write-only flags */
982  if (
983    (iop->flags & LIBIO_FLAGS_WRITE) != 0
984      && (iop->flags & LIBIO_FLAGS_READ) != 0
985  ) {
986    rtems_set_errno_and_return_minus_one(ENOTSUP);
987  }
988
989  /* Allocate connection entry */
990  e = calloc(1, sizeof(*e));
991  if (e == NULL) {
992    rtems_set_errno_and_return_minus_one(ENOMEM);
993  }
994
995  /* Initialize connection entry */
996  e->ctrl_socket = -1;
997  e->data_socket = -1;
998
999  /* Save connection state */
1000  iop->data1 = e;
1001
1002  /* Open control connection */
1003  eno = rtems_ftpfs_open_ctrl_connection(
1004    e,
1005    user,
1006    password,
1007    hostname,
1008    &client_address,
1009    verbose,
1010    timeout
1011  );
1012  if (eno != 0) {
1013    goto cleanup;
1014  }
1015
1016  /* Open passive data connection */
1017  eno = rtems_ftpfs_open_data_connection_passive(
1018    e,
1019    client_address,
1020    file_command,
1021    filename,
1022    verbose,
1023    timeout
1024  );
1025  if (eno == ENOTSUP) {
1026    /* Open active data connection */
1027    eno = rtems_ftpfs_open_data_connection_active(
1028      e,
1029      client_address,
1030      file_command,
1031      filename,
1032      verbose,
1033      timeout
1034    );
1035  }
1036  if (eno != 0) {
1037    goto cleanup;
1038  }
1039
1040  /* Set data connection timeout */
1041  eno = rtems_ftpfs_set_connection_timeout(e->data_socket, timeout);
1042
1043cleanup:
1044
1045  if (eno == 0) {
1046    return 0;
1047  } else {
1048    /* Free all resources if an error occured */
1049    rtems_ftpfs_terminate(iop, true);
1050
1051    rtems_set_errno_and_return_minus_one(eno);
1052  }
1053}
1054
1055static ssize_t rtems_ftpfs_read(
1056  rtems_libio_t *iop,
1057  void *buffer,
1058  size_t count
1059)
1060{
1061  rtems_ftpfs_entry *e = iop->data1;
1062  rtems_ftpfs_mount_entry *me = iop->pathinfo.mt_entry->fs_info;
1063  bool verbose = me->verbose;
1064  char *in = buffer;
1065  size_t todo = count;
1066
1067  if (e->eof) {
1068    return 0;
1069  }
1070
1071  while (todo > 0) {
1072    ssize_t rv = recv(e->data_socket, in, todo, 0);
1073
1074    if (rv <= 0) {
1075      if (rv == 0) {
1076        rtems_ftpfs_reply reply =
1077          rtems_ftpfs_get_reply(e, NULL, NULL, verbose);
1078
1079        if (reply == RTEMS_FTPFS_REPLY_2) {
1080          e->eof = true;
1081          break;
1082        }
1083      }
1084
1085      rtems_set_errno_and_return_minus_one(EIO);
1086    }
1087
1088    in += rv;
1089    todo -= (size_t) rv;
1090  }
1091
1092  return (ssize_t) (count - todo);
1093}
1094
1095static ssize_t rtems_ftpfs_write(
1096  rtems_libio_t *iop,
1097  const void *buffer,
1098  size_t count
1099)
1100{
1101  rtems_ftpfs_entry *e = iop->data1;
1102  const char *out = buffer;
1103  size_t todo = count;
1104
1105  while (todo > 0) {
1106    ssize_t rv = send(e->data_socket, out, todo, 0);
1107
1108    if (rv <= 0) {
1109      if (rv == 0) {
1110        break;
1111      } else {
1112        rtems_set_errno_and_return_minus_one(EIO);
1113      }
1114    }
1115
1116    out += rv;
1117    todo -= (size_t) rv;
1118  }
1119
1120  return (ssize_t) (count - todo);
1121}
1122
1123static int rtems_ftpfs_close(rtems_libio_t *iop)
1124{
1125  int eno = rtems_ftpfs_terminate(iop, false);
1126
1127  if (eno == 0) {
1128    return 0;
1129  } else {
1130    rtems_set_errno_and_return_minus_one(eno);
1131  }
1132}
1133
1134/* Dummy version to let fopen(*,"w") work properly */
1135static int rtems_ftpfs_ftruncate(rtems_libio_t *iop, off_t count)
1136{
1137  return 0;
1138}
1139
1140static void rtems_ftpfs_eval_path(
1141  rtems_filesystem_eval_path_context_t *self
1142)
1143{
1144  rtems_filesystem_eval_path_eat_delimiter(self);
1145
1146  if (rtems_filesystem_eval_path_has_path(self)) {
1147    const char *path = rtems_filesystem_eval_path_get_path(self);
1148    size_t pathlen = rtems_filesystem_eval_path_get_pathlen(self);
1149    char *pathdup = malloc(pathlen + 1);
1150
1151    rtems_filesystem_eval_path_clear_path(self);
1152
1153    if (pathdup != NULL) {
1154      rtems_filesystem_location_info_t *currentloc =
1155        rtems_filesystem_eval_path_get_currentloc(self);
1156
1157      memcpy(pathdup, path, pathlen);
1158      pathdup [pathlen] = '\0';
1159      currentloc->node_access = pathdup;
1160      currentloc->handlers = &rtems_ftpfs_handlers;
1161    } else {
1162      rtems_filesystem_eval_path_error(self, ENOMEM);
1163    }
1164  }
1165}
1166
1167static void rtems_ftpfs_free_node(const rtems_filesystem_location_info_t *loc)
1168{
1169  free(loc->node_access);
1170}
1171
1172static rtems_filesystem_node_types_t rtems_ftpfs_node_type(
1173  const rtems_filesystem_location_info_t *loc
1174)
1175{
1176  return RTEMS_FILESYSTEM_MEMORY_FILE;
1177}
1178
1179int rtems_ftpfs_initialize(
1180  rtems_filesystem_mount_table_entry_t *e,
1181  const void                           *d
1182)
1183{
1184  rtems_ftpfs_mount_entry *me = malloc(sizeof(rtems_ftpfs_mount_entry));
1185
1186  /* Mount entry for FTP file system instance */
1187  e->fs_info = me;
1188  if (e->fs_info == NULL) {
1189    rtems_set_errno_and_return_minus_one(ENOMEM);
1190  }
1191  me->verbose = false;
1192  me->timeout.tv_sec = 0;
1193  me->timeout.tv_usec = 0;
1194
1195  /* Set handler and oparations table */
1196  e->mt_fs_root->location.handlers = &rtems_ftpfs_root_handlers;
1197  e->ops = &rtems_ftpfs_ops;
1198
1199  /* We maintain no real file system nodes, so there is no real root */
1200  e->mt_fs_root->location.node_access = NULL;
1201
1202  /* Just use the limits from IMFS */
1203  e->pathconf_limits_and_options = IMFS_LIMITS_AND_OPTIONS;
1204
1205  return 0;
1206}
1207
1208static void rtems_ftpfs_unmount_me(
1209  rtems_filesystem_mount_table_entry_t *e
1210)
1211{
1212  free(e->fs_info);
1213}
1214
1215static int rtems_ftpfs_ioctl(
1216  rtems_libio_t *iop,
1217  uint32_t command,
1218  void *arg
1219)
1220{
1221  rtems_ftpfs_mount_entry *me = iop->pathinfo.mt_entry->fs_info;
1222  bool *verbose = arg;
1223  struct timeval *timeout = arg;
1224
1225  if (arg == NULL) {
1226    rtems_set_errno_and_return_minus_one(EINVAL);
1227  }
1228
1229  switch (command) {
1230    case RTEMS_FTPFS_IOCTL_GET_VERBOSE:
1231      *verbose = me->verbose;
1232      break;
1233    case RTEMS_FTPFS_IOCTL_SET_VERBOSE:
1234      me->verbose = *verbose;
1235      break;
1236    case RTEMS_FTPFS_IOCTL_GET_TIMEOUT:
1237      *timeout = me->timeout;
1238      break;
1239    case RTEMS_FTPFS_IOCTL_SET_TIMEOUT:
1240      me->timeout = *timeout;
1241      break;
1242    default:
1243      rtems_set_errno_and_return_minus_one(EINVAL);
1244  }
1245
1246  return 0;
1247}
1248
1249/*
1250 * The stat() support is intended only for the cp shell command.  Each request
1251 * will return that we have a regular file with read, write and execute
1252 * permissions for every one.  The node index uses a global counter to support
1253 * a remote to remote copy.  This is not a very sophisticated method.
1254 */
1255static int rtems_ftpfs_fstat(
1256  const rtems_filesystem_location_info_t *loc,
1257  struct stat *st
1258)
1259{
1260  static unsigned ino = 0;
1261
1262  /* FIXME */
1263  st->st_ino = ++ino;
1264  st->st_dev = rtems_filesystem_make_dev_t(0xcc494cd6U, 0x1d970b4dU);
1265
1266  st->st_mode = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO;
1267
1268  return 0;
1269}
1270
1271static const rtems_filesystem_operations_table rtems_ftpfs_ops = {
1272  .lock_h = rtems_filesystem_default_lock,
1273  .unlock_h = rtems_filesystem_default_unlock,
1274  .eval_path_h = rtems_ftpfs_eval_path,
1275  .link_h = rtems_filesystem_default_link,
1276  .are_nodes_equal_h = rtems_filesystem_default_are_nodes_equal,
1277  .node_type_h = rtems_ftpfs_node_type,
1278  .mknod_h = rtems_filesystem_default_mknod,
1279  .rmnod_h = rtems_filesystem_default_rmnod,
1280  .fchmod_h = rtems_filesystem_default_fchmod,
1281  .chown_h = rtems_filesystem_default_chown,
1282  .clonenod_h = rtems_filesystem_default_clonenode,
1283  .freenod_h = rtems_ftpfs_free_node,
1284  .mount_h = rtems_filesystem_default_mount,
1285  .fsmount_me_h = rtems_ftpfs_initialize,
1286  .unmount_h = rtems_filesystem_default_unmount,
1287  .fsunmount_me_h = rtems_ftpfs_unmount_me,
1288  .utime_h = rtems_filesystem_default_utime,
1289  .symlink_h = rtems_filesystem_default_symlink,
1290  .readlink_h = rtems_filesystem_default_readlink,
1291  .rename_h = rtems_filesystem_default_rename,
1292  .statvfs_h = rtems_filesystem_default_statvfs
1293};
1294
1295static const rtems_filesystem_file_handlers_r rtems_ftpfs_handlers = {
1296  .open_h = rtems_ftpfs_open,
1297  .close_h = rtems_ftpfs_close,
1298  .read_h = rtems_ftpfs_read,
1299  .write_h = rtems_ftpfs_write,
1300  .ioctl_h = rtems_filesystem_default_ioctl,
1301  .lseek_h = rtems_filesystem_default_lseek,
1302  .fstat_h = rtems_ftpfs_fstat,
1303  .ftruncate_h = rtems_ftpfs_ftruncate,
1304  .fsync_h = rtems_filesystem_default_fsync_or_fdatasync,
1305  .fdatasync_h = rtems_filesystem_default_fsync_or_fdatasync,
1306  .fcntl_h = rtems_filesystem_default_fcntl
1307};
1308
1309static const rtems_filesystem_file_handlers_r rtems_ftpfs_root_handlers = {
1310  .open_h = rtems_filesystem_default_open,
1311  .close_h = rtems_filesystem_default_close,
1312  .read_h = rtems_filesystem_default_read,
1313  .write_h = rtems_filesystem_default_write,
1314  .ioctl_h = rtems_ftpfs_ioctl,
1315  .lseek_h = rtems_filesystem_default_lseek,
1316  .fstat_h = rtems_filesystem_default_fstat,
1317  .ftruncate_h = rtems_filesystem_default_ftruncate,
1318  .fsync_h = rtems_filesystem_default_fsync_or_fdatasync,
1319  .fdatasync_h = rtems_filesystem_default_fsync_or_fdatasync,
1320  .fcntl_h = rtems_filesystem_default_fcntl
1321};
Note: See TracBrowser for help on using the repository browser.