source: rtems-libbsd/rtemsbsd/ftpfs/ftpfs.c @ 60b1d40

55-freebsd-126-freebsd-12freebsd-9.3
Last change on this file since 60b1d40 was 68d406b, checked in by Sebastian Huber <sebastian.huber@…>, on 06/09/16 at 09:19:09

ftpfs: Import from RTEMS

RTEMS Git commit 251c94d3d3d27e0039f01b718e5c2eb06f39fdf7.

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