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

4.115
Last change on this file since c625a641 was c625a641, checked in by Sebastian Huber <sebastian.huber@…>, on 12/21/14 at 19:12:28

Filesystem: Delete node type operation

Use the fstat handler instead.

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