2 Virtual File System: FTP file system.
4 Copyright (C) 1995-2024
5 Free Software Foundation, Inc.
10 Miguel de Icaza, 1995, 1996, 1997
14 Slava Zanko <slavazanko@gmail.com>, 2010, 2013
15 Andrew Borodin <aborodin@vmail.ru>, 2010-2022
17 This file is part of the Midnight Commander.
19 The Midnight Commander is free software: you can redistribute it
20 and/or modify it under the terms of the GNU General Public License as
21 published by the Free Software Foundation, either version 3 of the License,
22 or (at your option) any later version.
24 The Midnight Commander is distributed in the hope that it will be useful,
25 but WITHOUT ANY WARRANTY; without even the implied warranty of
26 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 GNU General Public License for more details.
29 You should have received a copy of the GNU General Public License
30 along with this program. If not, see <http://www.gnu.org/licenses/>.
35 * \brief Source: Virtual File System: FTP file system
37 * \author Jakub Jelinek
38 * \author Miguel de Icaza
39 * \author Norbert Warmuth
40 * \author Pavel Machek
41 * \date 1995, 1997, 1998
44 - make it more robust - all the connects etc. should handle EADDRINUSE and
45 ERETRY (have I spelled these names correctly?)
46 - make the user able to flush a connection - all the caches will get empty
47 etc., (tarfs as well), we should give there a user selectable timeout
48 and assign a key sequence.
49 - use hash table instead of linklist to cache ftpfs directory.
54 * NOTE: Usage of tildes is deprecated, consider:
59 * And now: what do I want to do? Do I want to go to /home/pavel or to
60 * ftp://hobit/home/pavel? I think first has better sense...
64 int f = !strcmp( remote_path, "/~" );
65 if (f || !strncmp( remote_path, "/~/", 3 )) {
67 s = mc_build_filename ( qhome (*bucket), remote_path +3-f, (char *) NULL );
75 /* \todo Fix: Namespace pollution: horrible */
78 #include <stdio.h> /* sscanf() */
79 #include <stdlib.h> /* atoi() */
80 #include <sys/types.h> /* POSIX-required by sys/socket.h and netdb.h */
81 #include <netdb.h> /* struct hostent */
82 #include <sys/socket.h> /* AF_INET */
83 #include <netinet/in.h> /* struct in_addr */
84 #ifdef HAVE_ARPA_INET_H
85 #include <arpa/inet.h>
88 #include <arpa/telnet.h>
89 #ifdef HAVE_SYS_PARAM_H
90 #include <sys/param.h>
95 #include <inttypes.h> /* uintmax_t */
97 #include "lib/global.h"
98 #include "lib/file-entry.h"
100 #include "lib/strutil.h" /* str_move() */
101 #include "lib/mcconfig.h"
103 #include "lib/tty/tty.h" /* enable/disable interrupt key */
104 #include "lib/widget.h" /* message() */
106 #include "src/history.h"
107 #include "src/setup.h" /* for load_anon_passwd */
109 #include "lib/vfs/vfs.h"
110 #include "lib/vfs/utilvfs.h"
111 #include "lib/vfs/netutil.h"
112 #include "lib/vfs/xdirentry.h"
113 #include "lib/vfs/gc.h" /* vfs_stamp_create */
117 /*** global variables ****************************************************************************/
119 /* Delay to retry a connection */
120 int ftpfs_retry_seconds
= 30;
122 /* Method to use to connect to ftp sites */
123 gboolean ftpfs_use_passive_connections
= TRUE
;
124 gboolean ftpfs_use_passive_connections_over_proxy
= FALSE
;
126 /* Method used to get directory listings:
127 * 1: try 'LIST -la <path>', if it fails
128 * fall back to CWD <path>; LIST
129 * 0: always use CWD <path>; LIST
131 gboolean ftpfs_use_unix_list_options
= TRUE
;
133 /* First "CWD <path>", then "LIST -la ." */
134 gboolean ftpfs_first_cd_then_ls
= TRUE
;
136 /* Use the ~/.netrc */
137 gboolean ftpfs_use_netrc
= TRUE
;
139 /* Anonymous setup */
140 char *ftpfs_anonymous_passwd
= NULL
;
141 int ftpfs_directory_timeout
= 900;
144 char *ftpfs_proxy_host
= NULL
;
146 /* whether we have to use proxy by default? */
147 gboolean ftpfs_always_use_proxy
= FALSE
;
149 gboolean ftpfs_ignore_chattr_errors
= TRUE
;
151 /*** file scope macro definitions ****************************************************************/
153 #ifndef MAXHOSTNAMELEN
154 #define MAXHOSTNAMELEN 64
157 #define FTP_SUPER(super) ((ftp_super_t *) (super))
158 #define FTP_FILE_HANDLER(fh) ((ftp_file_handler_t *) (fh))
159 #define FH_SOCK FTP_FILE_HANDLER(fh)->sock
162 #define INADDR_NONE 0xffffffff
165 #define RFC_AUTODETECT 0
169 /* ftpfs_command wait_flag: */
171 #define WAIT_REPLY 0x01
172 #define WANT_STRING 0x02
174 #define FTP_COMMAND_PORT 21
176 /* some defines only used by ftpfs_changetype */
177 /* These two are valid values for the second parameter */
179 #define TYPE_BINARY 1
181 /* This one is only used to initialize bucket->isbinary, don't use it as
182 second parameter to ftpfs_changetype. */
183 #define TYPE_UNKNOWN -1
185 #define ABORT_TIMEOUT (5 * G_USEC_PER_SEC)
186 /*** file scope type declarations ****************************************************************/
188 #ifndef HAVE_SOCKLEN_T
189 typedef int socklen_t
;
192 /* This should match the keywords[] array below */
208 struct vfs_s_super base
; /* base class */
212 char *proxy
; /* proxy server, NULL if no proxy */
213 gboolean failed_on_login
; /* used to pass the failure reason to upper levels */
214 gboolean use_passive_connection
;
215 gboolean remote_is_amiga
; /* No leading slash allowed for AmiTCP (Amiga) */
217 gboolean cwd_deferred
; /* current_directory was changed but CWD command hasn't
219 int strict
; /* ftp server doesn't understand
220 * "LIST -la <path>"; use "CWD <path>"/
223 gboolean ctl_connection_busy
;
229 vfs_file_handler_t base
; /* base class */
233 } ftp_file_handler_t
;
235 /*** forward declarations (file scope functions) *************************************************/
237 static char *ftpfs_get_current_directory (struct vfs_class
*me
, struct vfs_s_super
*super
);
238 static int ftpfs_chdir_internal (struct vfs_class
*me
, struct vfs_s_super
*super
,
239 const char *remote_path
);
240 static int ftpfs_open_socket (struct vfs_class
*me
, struct vfs_s_super
*super
);
241 static gboolean
ftpfs_login_server (struct vfs_class
*me
, struct vfs_s_super
*super
,
242 const char *netrcpass
);
243 static gboolean
ftpfs_netrc_lookup (const char *host
, char **login
, char **pass
);
245 /*** file scope variables ************************************************************************/
249 static char reply_str
[80];
251 static struct vfs_s_subclass ftpfs_subclass
;
252 static struct vfs_class
*vfs_ftpfs_ops
= VFS_CLASS (&ftpfs_subclass
);
254 static GSList
*no_proxy
= NULL
;
256 static char buffer
[BUF_MEDIUM
];
257 static char *netrc
= NULL
;
258 static const char *netrcp
;
260 /* --------------------------------------------------------------------------------------------- */
261 /*** file scope functions ************************************************************************/
262 /* --------------------------------------------------------------------------------------------- */
265 ftpfs_set_blksize (struct stat
*s
)
267 #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
268 /* redefine block size */
269 s
->st_blksize
= 64 * 1024; /* FIXME */
273 /* --------------------------------------------------------------------------------------------- */
276 ftpfs_default_stat (struct vfs_class
*me
)
280 s
= vfs_s_default_stat (me
, S_IFDIR
| 0755);
281 ftpfs_set_blksize (s
);
287 /* --------------------------------------------------------------------------------------------- */
289 /* Translate a Unix path, i.e. MC's internal path representation (e.g.
290 /somedir/somefile) to a path valid for the remote server. Every path
291 transferred to the remote server has to be mangled by this function
292 right prior to sending it.
293 Currently only Amiga ftp servers are handled in a special manner.
295 When the remote server is an amiga:
296 a) strip leading slash if necessary
297 b) replace first occurrence of ":/" with ":"
298 c) strip trailing "/."
301 ftpfs_translate_path (struct vfs_class
*me
, struct vfs_s_super
*super
, const char *remote_path
)
305 if (!FTP_SUPER (super
)->remote_is_amiga
)
306 return g_strdup (remote_path
);
308 if (me
->logfile
!= NULL
)
310 fprintf (me
->logfile
, "MC -- ftpfs_translate_path: %s\n", remote_path
);
311 fflush (me
->logfile
);
314 /* strip leading slash(es) */
315 while (IS_PATH_SEP (*remote_path
))
318 /* Don't change "/" into "", e.g. "CWD " would be invalid. */
319 if (*remote_path
== '\0')
320 return g_strdup (".");
322 ret
= g_strdup (remote_path
);
324 /* replace first occurrence of ":/" with ":" */
325 p
= strchr (ret
, ':');
326 if (p
!= NULL
&& IS_PATH_SEP (p
[1]))
327 str_move (p
+ 1, p
+ 2);
329 /* strip trailing "/." */
330 p
= strrchr (ret
, PATH_SEP
);
331 if ((p
!= NULL
) && (*(p
+ 1) == '.') && (*(p
+ 2) == '\0'))
337 /* --------------------------------------------------------------------------------------------- */
338 /** Extract the hostname and username from the path */
340 * path is in the form: [user@]hostname:port/remote-dir, e.g.:
341 * ftp://sunsite.unc.edu/pub/linux
342 * ftp://miguel@sphinx.nuclecu.unam.mx/c/nc
343 * ftp://tsx-11.mit.edu:8192/
344 * ftp://joe@foo.edu:11321/private
345 * If the user is empty, e.g. ftp://@roxanne/private, then your login name
349 static vfs_path_element_t
*
350 ftpfs_correct_url_parameters (const vfs_path_element_t
*velement
)
352 vfs_path_element_t
*path_element
= vfs_path_element_clone (velement
);
354 if (path_element
->port
== 0)
355 path_element
->port
= FTP_COMMAND_PORT
;
357 if (path_element
->user
== NULL
)
359 /* Look up user and password in netrc */
361 ftpfs_netrc_lookup (path_element
->host
, &path_element
->user
, &path_element
->password
);
363 if (path_element
->user
== NULL
)
364 path_element
->user
= g_strdup ("anonymous");
366 /* Look up password in netrc for known user */
367 if (ftpfs_use_netrc
&& path_element
->password
== NULL
)
369 char *new_user
= NULL
;
370 char *new_passwd
= NULL
;
372 ftpfs_netrc_lookup (path_element
->host
, &new_user
, &new_passwd
);
374 /* If user is different, remove password */
375 if (new_user
!= NULL
&& strcmp (path_element
->user
, new_user
) != 0)
376 MC_PTR_FREE (path_element
->password
);
385 /* --------------------------------------------------------------------------------------------- */
386 /* Returns a reply code, check /usr/include/arpa/ftp.h for possible values */
389 ftpfs_get_reply (struct vfs_class
*me
, int sock
, char *string_buf
, int string_len
)
395 if (vfs_s_get_line (me
, sock
, answer
, sizeof (answer
), '\n') == 0)
397 if (string_buf
!= NULL
)
403 /* cppcheck-suppress invalidscanf */
404 switch (sscanf (answer
, "%d", &code
))
407 if (string_buf
!= NULL
)
408 g_strlcpy (string_buf
, answer
, string_len
);
412 if (answer
[3] == '-')
418 if (vfs_s_get_line (me
, sock
, answer
, sizeof (answer
), '\n') == 0)
420 if (string_buf
!= NULL
)
425 /* cppcheck-suppress invalidscanf */
426 if ((sscanf (answer
, "%d", &i
) > 0) && (code
== i
) && (answer
[3] == ' '))
430 if (string_buf
!= NULL
)
431 g_strlcpy (string_buf
, answer
, string_len
);
439 /* --------------------------------------------------------------------------------------------- */
442 ftpfs_reconnect (struct vfs_class
*me
, struct vfs_s_super
*super
)
444 ftp_super_t
*ftp_super
= FTP_SUPER (super
);
447 sock
= ftpfs_open_socket (me
, super
);
450 char *cwdir
= ftp_super
->current_dir
;
452 close (ftp_super
->sock
);
453 ftp_super
->sock
= sock
;
454 ftp_super
->current_dir
= NULL
;
456 if (ftpfs_login_server (me
, super
, super
->path_element
->password
))
461 sock
= ftpfs_chdir_internal (me
, super
, cwdir
);
463 return (sock
== COMPLETE
);
466 ftp_super
->current_dir
= cwdir
;
472 /* --------------------------------------------------------------------------------------------- */
476 ftpfs_command (struct vfs_class
*me
, struct vfs_s_super
*super
, int wait_reply
, const char *fmt
,
479 ftp_super_t
*ftp_super
= FTP_SUPER (super
);
483 static gboolean retry
= FALSE
;
484 static int level
= 0; /* ftpfs_login_server() use ftpfs_command() */
486 cmdstr
= g_string_sized_new (32);
488 g_string_vprintf (cmdstr
, fmt
, ap
);
490 g_string_append (cmdstr
, "\r\n");
492 if (me
->logfile
!= NULL
)
494 if (strncmp (cmdstr
->str
, "PASS ", 5) == 0)
495 fputs ("PASS <Password not logged>\r\n", me
->logfile
);
500 ret
= fwrite (cmdstr
->str
, cmdstr
->len
, 1, me
->logfile
);
504 fflush (me
->logfile
);
508 tty_enable_interrupt_key ();
509 status
= write (ftp_super
->sock
, cmdstr
->str
, cmdstr
->len
);
516 { /* Remote server has closed connection */
520 status
= ftpfs_reconnect (me
, super
) ? 1 : 0;
522 if (status
!= 0 && (write (ftp_super
->sock
, cmdstr
->str
, cmdstr
->len
) > 0))
528 g_string_free (cmdstr
, TRUE
);
529 tty_disable_interrupt_key ();
536 tty_disable_interrupt_key ();
538 if (wait_reply
!= NONE
)
540 status
= ftpfs_get_reply (me
, ftp_super
->sock
,
541 (wait_reply
& WANT_STRING
) != 0 ? reply_str
: NULL
,
542 sizeof (reply_str
) - 1);
543 if ((wait_reply
& WANT_STRING
) != 0 && !retry
&& level
== 0 && code
== 421)
547 status
= ftpfs_reconnect (me
, super
) ? 1 : 0;
549 if (status
!= 0 && (write (ftp_super
->sock
, cmdstr
->str
, cmdstr
->len
) > 0))
553 g_string_free (cmdstr
, TRUE
);
557 g_string_free (cmdstr
, TRUE
);
561 /* --------------------------------------------------------------------------------------------- */
563 static struct vfs_s_super
*
564 ftpfs_new_archive (struct vfs_class
*me
)
568 arch
= g_new0 (ftp_super_t
, 1);
570 arch
->base
.name
= g_strdup (PATH_SEP_STR
);
572 arch
->use_passive_connection
= ftpfs_use_passive_connections
;
573 arch
->strict
= ftpfs_use_unix_list_options
? RFC_AUTODETECT
: RFC_STRICT
;
574 arch
->isbinary
= TYPE_UNKNOWN
;
576 return VFS_SUPER (arch
);
579 /* --------------------------------------------------------------------------------------------- */
582 ftpfs_free_archive (struct vfs_class
*me
, struct vfs_s_super
*super
)
584 ftp_super_t
*ftp_super
= FTP_SUPER (super
);
586 if (ftp_super
->sock
!= -1)
588 vfs_print_message (_("ftpfs: Disconnecting from %s"), super
->path_element
->host
);
589 ftpfs_command (me
, super
, NONE
, "%s", "QUIT");
590 close (ftp_super
->sock
);
592 g_free (ftp_super
->current_dir
);
595 /* --------------------------------------------------------------------------------------------- */
598 ftpfs_changetype (struct vfs_class
*me
, struct vfs_s_super
*super
, int binary
)
600 if (binary
!= FTP_SUPER (super
)->isbinary
)
602 if (ftpfs_command (me
, super
, WAIT_REPLY
, "TYPE %c", binary
? 'I' : 'A') != COMPLETE
)
604 FTP_SUPER (super
)->isbinary
= binary
;
609 /* --------------------------------------------------------------------------------------------- */
610 /* This routine logs the user in */
613 ftpfs_login_server (struct vfs_class
*me
, struct vfs_s_super
*super
, const char *netrcpass
)
615 ftp_super_t
*ftp_super
= FTP_SUPER (super
);
618 char *name
; /* login user name */
619 gboolean anon
= FALSE
;
620 char reply_string
[BUF_MEDIUM
];
622 ftp_super
->isbinary
= TYPE_UNKNOWN
;
624 if (super
->path_element
->password
!= NULL
) /* explicit password */
625 op
= g_strdup (super
->path_element
->password
);
626 else if (netrcpass
!= NULL
) /* password from netrc */
627 op
= g_strdup (netrcpass
);
628 else if (strcmp (super
->path_element
->user
, "anonymous") == 0
629 || strcmp (super
->path_element
->user
, "ftp") == 0)
631 if (ftpfs_anonymous_passwd
== NULL
) /* default anonymous password */
632 ftpfs_init_passwd ();
633 op
= g_strdup (ftpfs_anonymous_passwd
);
640 p
= g_strdup_printf (_("FTP: Password required for %s"), super
->path_element
->user
);
641 op
= vfs_get_password (p
);
645 super
->path_element
->password
= g_strdup (op
);
648 if (!anon
|| me
->logfile
!= NULL
)
652 pass
= g_strconcat ("-", op
, (char *) NULL
);
656 /* Proxy server accepts: username@host-we-want-to-connect */
657 if (ftp_super
->proxy
!= NULL
)
659 g_strconcat (super
->path_element
->user
, "@",
660 super
->path_element
->host
[0] ==
661 '!' ? super
->path_element
->host
+ 1 : super
->path_element
->host
,
664 name
= g_strdup (super
->path_element
->user
);
666 if (ftpfs_get_reply (me
, ftp_super
->sock
, reply_string
, sizeof (reply_string
) - 1) == COMPLETE
)
670 reply_up
= g_ascii_strup (reply_string
, -1);
671 ftp_super
->remote_is_amiga
= strstr (reply_up
, "AMIGA") != NULL
;
672 if (strstr (reply_up
, " SPFTP/1.0.0000 SERVER ") != NULL
) /* handles `LIST -la` in a weird way */
673 ftp_super
->strict
= RFC_STRICT
;
676 if (me
->logfile
!= NULL
)
678 fprintf (me
->logfile
, "MC -- remote_is_amiga = %s\n",
679 ftp_super
->remote_is_amiga
? "yes" : "no");
680 fflush (me
->logfile
);
683 vfs_print_message ("%s", _("ftpfs: sending login name"));
685 switch (ftpfs_command (me
, super
, WAIT_REPLY
, "USER %s", name
))
688 vfs_print_message ("%s", _("ftpfs: sending user password"));
689 code
= ftpfs_command (me
, super
, WAIT_REPLY
, "PASS %s", pass
);
690 if (code
== CONTINUE
)
694 p
= g_strdup_printf (_("FTP: Account required for user %s"),
695 super
->path_element
->user
);
696 op
= input_dialog (p
, _("Account:"), MC_HISTORY_FTPFS_ACCOUNT
, "",
697 INPUT_COMPLETE_USERNAMES
);
701 vfs_print_message ("%s", _("ftpfs: sending user account"));
702 code
= ftpfs_command (me
, super
, WAIT_REPLY
, "ACCT %s", op
);
705 if (code
!= COMPLETE
)
711 vfs_print_message ("%s", _("ftpfs: logged in"));
712 wipe_password (pass
);
717 ftp_super
->failed_on_login
= TRUE
;
718 wipe_password (super
->path_element
->password
);
719 super
->path_element
->password
= NULL
;
724 message (D_ERROR
, MSG_ERROR
, _("ftpfs: Login incorrect for user %s "),
725 super
->path_element
->user
);
728 wipe_password (pass
);
730 ERRNOR (EPERM
, FALSE
);
733 /* --------------------------------------------------------------------------------------------- */
736 ftpfs_load_no_proxy_list (void)
738 /* FixMe: shouldn't be hardcoded!!! */
741 mc_file
= g_build_filename (mc_global
.sysconfig_dir
, "mc.no_proxy", (char *) NULL
);
742 if (exist_file (mc_file
))
746 npf
= fopen (mc_file
, "r");
749 char s
[BUF_LARGE
]; /* provide for BUF_LARGE characters */
751 while (fgets (s
, sizeof (s
), npf
) != NULL
)
755 p
= strchr (s
, '\n');
756 if (p
== NULL
) /* skip bogus entries */
760 while ((c
= fgetc (npf
)) != EOF
&& c
!= '\n')
766 no_proxy
= g_slist_prepend (no_proxy
, g_strdup (s
));
777 /* --------------------------------------------------------------------------------------------- */
778 /* Return TRUE if FTP proxy should be used for this host, FALSE otherwise */
781 ftpfs_check_proxy (const char *host
)
784 if (ftpfs_proxy_host
== NULL
|| *ftpfs_proxy_host
== '\0' || host
== NULL
|| *host
== '\0')
785 return FALSE
; /* sanity check */
790 if (!ftpfs_always_use_proxy
)
793 if (strchr (host
, '.') == NULL
)
796 if (no_proxy
== NULL
)
800 ftpfs_load_no_proxy_list ();
802 for (npe
= no_proxy
; npe
!= NULL
; npe
= g_slist_next (npe
))
804 const char *domain
= (const char *) npe
->data
;
806 if (domain
[0] == '.')
810 ld
= strlen (domain
);
813 while (ld
!= 0 && lh
!= 0 && host
[lh
- 1] == domain
[ld
- 1])
822 else if (g_ascii_strcasecmp (host
, domain
) == 0)
830 /* --------------------------------------------------------------------------------------------- */
833 ftpfs_get_proxy_host_and_port (const char *proxy
, char **host
, int *port
)
835 vfs_path_element_t
*path_element
;
837 path_element
= vfs_url_split (proxy
, FTP_COMMAND_PORT
, URL_USE_ANONYMOUS
);
838 *host
= path_element
->host
;
839 path_element
->host
= NULL
;
840 *port
= path_element
->port
;
841 vfs_path_element_free (path_element
);
844 /* --------------------------------------------------------------------------------------------- */
847 ftpfs_open_socket (struct vfs_class
*me
, struct vfs_s_super
*super
)
849 struct addrinfo hints
, *res
, *curr_res
;
858 if (super
->path_element
->host
== NULL
|| *super
->path_element
->host
== '\0')
860 vfs_print_message ("%s", _("ftpfs: Invalid host name."));
865 /* Use a proxy host? */
866 /* Hosts to connect to that start with a ! should use proxy */
867 if (FTP_SUPER (super
)->proxy
!= NULL
)
868 ftpfs_get_proxy_host_and_port (ftpfs_proxy_host
, &host
, &tmp_port
);
871 host
= g_strdup (super
->path_element
->host
);
872 tmp_port
= super
->path_element
->port
;
875 g_snprintf (port
, sizeof (port
), "%hu", (unsigned short) tmp_port
);
877 tty_enable_interrupt_key (); /* clear the interrupt flag */
879 memset (&hints
, 0, sizeof (hints
));
880 hints
.ai_family
= AF_UNSPEC
;
881 hints
.ai_socktype
= SOCK_STREAM
;
884 /* By default, only look up addresses using address types for
885 * which a local interface is configured (i.e. no IPv6 if no IPv6
886 * interfaces, likewise for IPv4 (see RFC 3493 for details). */
887 hints
.ai_flags
= AI_ADDRCONFIG
;
890 e
= getaddrinfo (host
, port
, &hints
, &res
);
893 if (e
== EAI_BADFLAGS
)
895 /* Retry with no flags if AI_ADDRCONFIG was rejected. */
897 e
= getaddrinfo (host
, port
, &hints
, &res
);
905 tty_disable_interrupt_key ();
906 vfs_print_message (_("ftpfs: %s"), gai_strerror (e
));
912 for (curr_res
= res
; curr_res
!= NULL
; curr_res
= curr_res
->ai_next
)
914 my_socket
= socket (curr_res
->ai_family
, curr_res
->ai_socktype
, curr_res
->ai_protocol
);
918 if (curr_res
->ai_next
!= NULL
)
921 tty_disable_interrupt_key ();
922 vfs_print_message (_("ftpfs: %s"), unix_error_string (errno
));
929 vfs_print_message (_("ftpfs: making connection to %s"), host
);
932 if (connect (my_socket
, curr_res
->ai_addr
, curr_res
->ai_addrlen
) >= 0)
938 if (errno
== EINTR
&& tty_got_interrupt ())
939 vfs_print_message ("%s", _("ftpfs: connection interrupted by user"));
940 else if (res
->ai_next
== NULL
)
941 vfs_print_message (_("ftpfs: connection to server failed: %s"),
942 unix_error_string (errno
));
947 tty_disable_interrupt_key ();
952 tty_disable_interrupt_key ();
956 /* --------------------------------------------------------------------------------------------- */
959 ftpfs_open_archive_int (struct vfs_class
*me
, struct vfs_s_super
*super
)
961 ftp_super_t
*ftp_super
= FTP_SUPER (super
);
962 int retry_seconds
= 0;
964 /* We do not want to use the passive if we are using proxies */
965 if (ftp_super
->proxy
!= NULL
)
966 ftp_super
->use_passive_connection
= ftpfs_use_passive_connections_over_proxy
;
970 ftp_super
->failed_on_login
= FALSE
;
972 ftp_super
->sock
= ftpfs_open_socket (me
, super
);
973 if (ftp_super
->sock
== -1)
976 if (ftpfs_login_server (me
, super
, NULL
))
978 /* Logged in, no need to retry the connection */
982 if (!ftp_super
->failed_on_login
)
985 /* Close only the socket descriptor */
986 close (ftp_super
->sock
);
988 if (ftpfs_retry_seconds
!= 0)
992 retry_seconds
= ftpfs_retry_seconds
;
993 tty_enable_interrupt_key ();
994 for (count_down
= retry_seconds
; count_down
!= 0; count_down
--)
996 vfs_print_message (_("Waiting to retry... %d (Control-G to cancel)"), count_down
);
998 if (tty_got_interrupt ())
1000 /* me->verrno = E; */
1001 tty_disable_interrupt_key ();
1005 tty_disable_interrupt_key ();
1008 while (retry_seconds
!= 0);
1010 ftp_super
->current_dir
= ftpfs_get_current_directory (me
, super
);
1011 if (ftp_super
->current_dir
== NULL
)
1012 ftp_super
->current_dir
= g_strdup (PATH_SEP_STR
);
1017 /* --------------------------------------------------------------------------------------------- */
1020 ftpfs_open_archive (struct vfs_s_super
*super
,
1021 const vfs_path_t
*vpath
, const vfs_path_element_t
*vpath_element
)
1025 super
->path_element
= ftpfs_correct_url_parameters (vpath_element
);
1026 if (ftpfs_check_proxy (super
->path_element
->host
))
1027 FTP_SUPER (super
)->proxy
= ftpfs_proxy_host
;
1029 vfs_s_new_inode (vpath_element
->class, super
, ftpfs_default_stat (vpath_element
->class));
1031 return ftpfs_open_archive_int (vpath_element
->class, super
);
1034 /* --------------------------------------------------------------------------------------------- */
1037 ftpfs_archive_same (const vfs_path_element_t
*vpath_element
, struct vfs_s_super
*super
,
1038 const vfs_path_t
*vpath
, void *cookie
)
1040 vfs_path_element_t
*path_element
;
1046 path_element
= ftpfs_correct_url_parameters (vpath_element
);
1048 result
= ((strcmp (path_element
->host
, super
->path_element
->host
) == 0)
1049 && (strcmp (path_element
->user
, super
->path_element
->user
) == 0)
1050 && (path_element
->port
== super
->path_element
->port
)) ? 1 : 0;
1052 vfs_path_element_free (path_element
);
1056 /* --------------------------------------------------------------------------------------------- */
1057 /* The returned directory should always contain a trailing slash */
1060 ftpfs_get_current_directory (struct vfs_class
*me
, struct vfs_s_super
*super
)
1062 char buf
[MC_MAXPATHLEN
+ 1];
1064 if (ftpfs_command (me
, super
, NONE
, "%s", "PWD") == COMPLETE
&&
1065 ftpfs_get_reply (me
, FTP_SUPER (super
)->sock
, buf
, sizeof (buf
)) == COMPLETE
)
1070 for (bufq
= buf
; *bufq
!= '\0'; bufq
++)
1081 if (!IS_PATH_SEP (bufq
[-1]))
1087 if (IS_PATH_SEP (*bufp
))
1088 return g_strdup (bufp
);
1090 /* If the remote server is an Amiga a leading slash
1091 might be missing. MC needs it because it is used
1092 as separator between hostname and path internally. */
1093 return g_strconcat (PATH_SEP_STR
, bufp
, (char *) NULL
);
1105 /* --------------------------------------------------------------------------------------------- */
1106 /* Setup Passive PASV FTP connection */
1109 ftpfs_setup_passive_pasv (struct vfs_class
*me
, struct vfs_s_super
*super
,
1110 int my_socket
, struct sockaddr_storage
*sa
, socklen_t
*salen
)
1114 int xa
, xb
, xc
, xd
, xe
, xf
;
1116 if (ftpfs_command (me
, super
, WAIT_REPLY
| WANT_STRING
, "%s", "PASV") != COMPLETE
)
1119 /* Parse remote parameters */
1120 for (c
= reply_str
+ 4; *c
!= '\0' && !isdigit ((unsigned char) *c
); c
++)
1123 if (*c
== '\0' || !isdigit ((unsigned char) *c
))
1126 /* cppcheck-suppress invalidscanf */
1127 if (sscanf (c
, "%d,%d,%d,%d,%d,%d", &xa
, &xb
, &xc
, &xd
, &xe
, &xf
) != 6)
1130 n
[0] = (unsigned char) xa
;
1131 n
[1] = (unsigned char) xb
;
1132 n
[2] = (unsigned char) xc
;
1133 n
[3] = (unsigned char) xd
;
1134 n
[4] = (unsigned char) xe
;
1135 n
[5] = (unsigned char) xf
;
1137 memcpy (&(((struct sockaddr_in
*) sa
)->sin_addr
.s_addr
), (void *) n
, 4);
1138 memcpy (&(((struct sockaddr_in
*) sa
)->sin_port
), (void *) &n
[4], 2);
1140 return (connect (my_socket
, (struct sockaddr
*) sa
, *salen
) >= 0);
1143 /* --------------------------------------------------------------------------------------------- */
1144 /* Setup Passive EPSV FTP connection */
1147 ftpfs_setup_passive_epsv (struct vfs_class
*me
, struct vfs_s_super
*super
,
1148 int my_socket
, struct sockaddr_storage
*sa
, socklen_t
*salen
)
1153 if (ftpfs_command (me
, super
, WAIT_REPLY
| WANT_STRING
, "%s", "EPSV") != COMPLETE
)
1157 c
= strchr (reply_str
, '|');
1158 if (c
== NULL
|| strlen (c
) <= 3)
1163 if (port
< 0 || port
> 65535)
1166 port
= htons (port
);
1168 switch (sa
->ss_family
)
1171 ((struct sockaddr_in
*) sa
)->sin_port
= port
;
1174 ((struct sockaddr_in6
*) sa
)->sin6_port
= port
;
1180 return (connect (my_socket
, (struct sockaddr
*) sa
, *salen
) >= 0);
1183 /* --------------------------------------------------------------------------------------------- */
1184 /* Setup Passive ftp connection, we use it for source routed connections */
1187 ftpfs_setup_passive (struct vfs_class
*me
, struct vfs_s_super
*super
,
1188 int my_socket
, struct sockaddr_storage
*sa
, socklen_t
*salen
)
1190 /* It's IPV4, so try PASV first, some servers and ALGs get confused by EPSV */
1191 if (sa
->ss_family
== AF_INET
)
1193 if (!ftpfs_setup_passive_pasv (me
, super
, my_socket
, sa
, salen
))
1194 /* An IPV4 FTP server might support EPSV, so if PASV fails we can try EPSV anyway */
1195 if (!ftpfs_setup_passive_epsv (me
, super
, my_socket
, sa
, salen
))
1198 /* It's IPV6, so EPSV is our only hope */
1199 else if (!ftpfs_setup_passive_epsv (me
, super
, my_socket
, sa
, salen
))
1205 /* --------------------------------------------------------------------------------------------- */
1206 /* Setup Active PORT or EPRT FTP connection */
1209 ftpfs_setup_active (struct vfs_class
*me
, struct vfs_s_super
*super
,
1210 struct sockaddr_storage data_addr
, socklen_t data_addrlen
)
1212 unsigned short int port
;
1217 switch (data_addr
.ss_family
)
1221 port
= ((struct sockaddr_in
*) &data_addr
)->sin_port
;
1225 port
= ((struct sockaddr_in6
*) &data_addr
)->sin6_port
;
1228 /* Not implemented */
1232 addr
= g_try_malloc (NI_MAXHOST
);
1234 ERRNOR (ENOMEM
, -1);
1237 getnameinfo ((struct sockaddr
*) &data_addr
, data_addrlen
, addr
, NI_MAXHOST
, NULL
, 0,
1241 const char *err_str
;
1245 if (res
== EAI_SYSTEM
)
1248 err_str
= unix_error_string (me
->verrno
);
1253 err_str
= gai_strerror (res
);
1256 vfs_print_message (_("ftpfs: could not make address-to-name translation: %s"), err_str
);
1261 /* If we are talking to an IPV4 server, try PORT, and, only if it fails, go for EPRT */
1264 unsigned char *a
= (unsigned char *) &((struct sockaddr_in
*) &data_addr
)->sin_addr
;
1265 unsigned char *p
= (unsigned char *) &port
;
1267 if (ftpfs_command (me
, super
, WAIT_REPLY
,
1268 "PORT %u,%u,%u,%u,%u,%u", a
[0], a
[1], a
[2], a
[3],
1269 p
[0], p
[1]) == COMPLETE
)
1277 * Converts network MSB first order to host byte order (LSB
1278 * first on i386). If we do it earlier, we will run into an
1279 * endianness issue, because the server actually expects to see
1280 * "PORT A,D,D,R,MSB,LSB" in the PORT command.
1282 port
= ntohs (port
);
1284 /* We are talking to an IPV6 server or PORT failed, so we can try EPRT anyway */
1286 (ftpfs_command (me
, super
, WAIT_REPLY
, "EPRT |%u|%s|%hu|", af
, addr
, port
) ==
1292 /* --------------------------------------------------------------------------------------------- */
1293 /* Initialize a socket for FTP DATA connection */
1296 ftpfs_init_data_socket (struct vfs_class
*me
, struct vfs_s_super
*super
,
1297 struct sockaddr_storage
*data_addr
, socklen_t
*data_addrlen
)
1299 const unsigned int attempts
= 10;
1301 ftp_super_t
*ftp_super
= FTP_SUPER (super
);
1304 for (i
= 0; i
< attempts
; i
++)
1306 memset (data_addr
, 0, sizeof (*data_addr
));
1307 *data_addrlen
= sizeof (*data_addr
);
1309 if (ftp_super
->use_passive_connection
)
1311 result
= getpeername (ftp_super
->sock
, (struct sockaddr
*) data_addr
, data_addrlen
);
1317 if (me
->verrno
== ENOTCONN
)
1319 vfs_print_message (_("ftpfs: try reconnect to server, attempt %u"), i
);
1320 if (ftpfs_reconnect (me
, super
))
1321 continue; /* get name of new socket */
1325 /* error -- stop loop */
1326 vfs_print_message (_("ftpfs: could not get socket name: %s"),
1327 unix_error_string (me
->verrno
));
1332 result
= getsockname (ftp_super
->sock
, (struct sockaddr
*) data_addr
, data_addrlen
);
1338 vfs_print_message (_("ftpfs: try reconnect to server, attempt %u"), i
);
1339 if (ftpfs_reconnect (me
, super
))
1340 continue; /* get name of new socket */
1342 /* error -- stop loop */
1343 vfs_print_message ("%s", _("ftpfs: could not reconnect to server"));
1352 switch (data_addr
->ss_family
)
1355 ((struct sockaddr_in
*) data_addr
)->sin_port
= 0;
1358 ((struct sockaddr_in6
*) data_addr
)->sin6_port
= 0;
1361 vfs_print_message ("%s", _("ftpfs: invalid address family"));
1362 ERRNOR (EINVAL
, -1);
1365 result
= socket (data_addr
->ss_family
, SOCK_STREAM
, IPPROTO_TCP
);
1369 vfs_print_message (_("ftpfs: could not create socket: %s"), unix_error_string (me
->verrno
));
1376 /* --------------------------------------------------------------------------------------------- */
1377 /* Initialize FTP DATA connection */
1380 ftpfs_initconn (struct vfs_class
*me
, struct vfs_s_super
*super
)
1382 ftp_super_t
*ftp_super
= FTP_SUPER (super
);
1383 struct sockaddr_storage data_addr
;
1384 socklen_t data_addrlen
;
1387 * Don't factor socket initialization out of these conditionals,
1388 * because ftpfs_init_data_socket initializes it in different way
1389 * depending on use_passive_connection flag.
1392 /* Try to establish a passive connection first (if requested) */
1393 if (ftp_super
->use_passive_connection
)
1397 data_sock
= ftpfs_init_data_socket (me
, super
, &data_addr
, &data_addrlen
);
1401 if (ftpfs_setup_passive (me
, super
, data_sock
, &data_addr
, &data_addrlen
))
1404 vfs_print_message ("%s", _("ftpfs: could not setup passive mode"));
1405 ftp_super
->use_passive_connection
= FALSE
;
1410 /* If passive setup is disabled or failed, fallback to active connections */
1411 if (!ftp_super
->use_passive_connection
)
1415 data_sock
= ftpfs_init_data_socket (me
, super
, &data_addr
, &data_addrlen
);
1419 if ((bind (data_sock
, (struct sockaddr
*) &data_addr
, data_addrlen
) != 0) ||
1420 (getsockname (data_sock
, (struct sockaddr
*) &data_addr
, &data_addrlen
) != 0) ||
1421 (listen (data_sock
, 1) != 0))
1427 if (ftpfs_setup_active (me
, super
, data_addr
, data_addrlen
) != 0)
1433 /* Restore the initial value of use_passive_connection (for subsequent retries) */
1434 ftp_super
->use_passive_connection
=
1436 NULL
? ftpfs_use_passive_connections_over_proxy
: ftpfs_use_passive_connections
;
1442 /* --------------------------------------------------------------------------------------------- */
1445 ftpfs_open_data_connection (struct vfs_class
*me
, struct vfs_s_super
*super
, const char *cmd
,
1446 const char *remote
, int isbinary
, int reget
)
1448 ftp_super_t
*ftp_super
= FTP_SUPER (super
);
1451 /* FTP doesn't allow to open more than one file at a time */
1452 if (ftp_super
->ctl_connection_busy
)
1455 s
= ftpfs_initconn (me
, super
);
1459 if (ftpfs_changetype (me
, super
, isbinary
) == -1)
1467 j
= ftpfs_command (me
, super
, WAIT_REPLY
, "REST %d", reget
);
1476 j
= ftpfs_command (me
, super
, WAIT_REPLY
, "%s", cmd
);
1481 remote_path
= ftpfs_translate_path (me
, super
, remote
);
1482 j
= ftpfs_command (me
, super
, WAIT_REPLY
, "%s /%s", cmd
,
1483 /* WarFtpD can't STORE //filename */
1484 IS_PATH_SEP (*remote_path
) ? remote_path
+ 1 : remote_path
);
1485 g_free (remote_path
);
1494 if (ftp_super
->use_passive_connection
)
1498 struct sockaddr_storage from
;
1499 socklen_t fromlen
= sizeof (from
);
1501 tty_enable_interrupt_key ();
1502 data
= accept (s
, (struct sockaddr
*) &from
, &fromlen
);
1505 tty_disable_interrupt_key ();
1511 ftp_super
->ctl_connection_busy
= TRUE
;
1515 /* --------------------------------------------------------------------------------------------- */
1518 ftpfs_linear_abort (struct vfs_class
*me
, vfs_file_handler_t
*fh
)
1520 struct vfs_s_super
*super
= VFS_FILE_HANDLER_SUPER (fh
);
1521 ftp_super_t
*ftp_super
= FTP_SUPER (super
);
1522 static unsigned char const ipbuf
[3] = { IAC
, IP
, IAC
};
1524 int dsock
= FH_SOCK
;
1527 ftp_super
->ctl_connection_busy
= FALSE
;
1529 vfs_print_message ("%s", _("ftpfs: aborting transfer."));
1531 if (send (ftp_super
->sock
, ipbuf
, sizeof (ipbuf
), MSG_OOB
) != sizeof (ipbuf
))
1533 vfs_print_message (_("ftpfs: abort error: %s"), unix_error_string (errno
));
1539 if (ftpfs_command (me
, super
, NONE
, "%cABOR", DM
) != COMPLETE
)
1541 vfs_print_message ("%s", _("ftpfs: abort failed"));
1550 FD_SET (dsock
, &mask
);
1552 if (select (dsock
+ 1, &mask
, NULL
, NULL
, NULL
) > 0)
1557 start_tim
= g_get_monotonic_time ();
1559 /* flush the remaining data */
1560 while (read (dsock
, buf
, sizeof (buf
)) > 0)
1564 tim
= g_get_monotonic_time ();
1566 if (tim
> start_tim
+ ABORT_TIMEOUT
)
1568 /* server keeps sending, drop the connection and ftpfs_reconnect */
1570 ftpfs_reconnect (me
, super
);
1578 if ((ftpfs_get_reply (me
, ftp_super
->sock
, NULL
, 0) == TRANSIENT
) && (code
== 426))
1579 ftpfs_get_reply (me
, ftp_super
->sock
, NULL
, 0);
1582 /* --------------------------------------------------------------------------------------------- */
1586 resolve_symlink_without_ls_options (struct vfs_class
*me
, struct vfs_s_super
*super
,
1587 struct vfs_s_inode
*dir
)
1589 struct linklist
*flist
;
1590 struct direntry
*fe
, *fel
;
1591 char tmp
[MC_MAXPATHLEN
];
1593 dir
->symlink_status
= FTPFS_RESOLVING_SYMLINKS
;
1594 for (flist
= dir
->file_list
->next
; flist
!= dir
->file_list
; flist
= flist
->next
)
1596 /* flist->data->l_stat is already initialized with 0 */
1598 if (S_ISLNK (fel
->s
.st_mode
) && fel
->linkname
!= NULL
)
1602 if (IS_PATH_SEP (fel
->linkname
[0]))
1604 if (strlen (fel
->linkname
) >= MC_MAXPATHLEN
)
1606 strcpy (tmp
, fel
->linkname
);
1610 if ((strlen (dir
->remote_path
) + strlen (fel
->linkname
)) >= MC_MAXPATHLEN
)
1612 strcpy (tmp
, dir
->remote_path
);
1614 strcat (tmp
, PATH_SEP_STR
);
1615 strcat (tmp
+ 1, fel
->linkname
);
1618 for (depth
= 0; depth
< 100; depth
++)
1619 { /* depth protects against recursive symbolic links */
1620 canonicalize_pathname (tmp
);
1621 fe
= _get_file_entry_t (bucket
, tmp
, 0, 0);
1624 if (S_ISLNK (fe
->s
.st_mode
) && fe
->l_stat
== 0)
1626 /* Symlink points to link which isn't resolved, yet. */
1627 if (IS_PATH_SEP (fe
->linkname
[0]))
1629 if (strlen (fe
->linkname
) >= MC_MAXPATHLEN
)
1631 strcpy (tmp
, fe
->linkname
);
1635 /* at this point tmp looks always like this
1636 /directory/filename, i.e. no need to check
1637 strrchr's return value */
1638 *(strrchr (tmp
, PATH_SEP
) + 1) = '\0'; /* dirname */
1639 if ((strlen (tmp
) + strlen (fe
->linkname
)) >= MC_MAXPATHLEN
)
1641 strcat (tmp
, fe
->linkname
);
1647 fel
->l_stat
= g_new (struct stat
, 1);
1648 if (S_ISLNK (fe
->s
.st_mode
))
1649 *fel
->l_stat
= *fe
->l_stat
;
1651 *fel
->l_stat
= fe
->s
;
1652 (*fel
->l_stat
).st_ino
= bucket
->__inode_counter
++;
1660 dir
->symlink_status
= FTPFS_RESOLVED_SYMLINKS
;
1663 /* --------------------------------------------------------------------------------------------- */
1666 resolve_symlink_with_ls_options (struct vfs_class
*me
, struct vfs_s_super
*super
,
1667 struct vfs_s_inode
*dir
)
1669 char buffer
[2048] = "", *filename
;
1673 struct linklist
*flist
;
1674 struct direntry
*fe
;
1675 int switch_method
= 0;
1677 dir
->symlink_status
= FTPFS_RESOLVED_SYMLINKS
;
1678 if (strchr (dir
->remote_path
, ' ') == NULL
)
1679 sock
= ftpfs_open_data_connection (bucket
, "LIST -lLa", dir
->remote_path
, TYPE_ASCII
, 0);
1682 if (ftpfs_chdir_internal (bucket
, dir
->remote_path
) != COMPLETE
)
1684 vfs_print_message ("%s", _("ftpfs: CWD failed."));
1688 sock
= ftpfs_open_data_connection (bucket
, "LIST -lLa", ".", TYPE_ASCII
, 0);
1693 vfs_print_message ("%s", _("ftpfs: couldn't resolve symlink"));
1697 fp
= fdopen (sock
, "r");
1701 vfs_print_message ("%s", _("ftpfs: couldn't resolve symlink"));
1704 tty_enable_interrupt_key ();
1705 flist
= dir
->file_list
->next
;
1711 if (flist
== dir
->file_list
)
1715 flist
= flist
->next
;
1717 while (!S_ISLNK (fe
->s
.st_mode
));
1721 if (fgets (buffer
, sizeof (buffer
), fp
) == NULL
)
1724 if (me
->logfile
!= NULL
)
1726 fputs (buffer
, me
->logfile
);
1727 fflush (me
->logfile
);
1730 vfs_die ("This code should be commented out\n");
1732 if (vfs_parse_ls_lga (buffer
, &s
, &filename
, NULL
))
1736 r
= strcmp (fe
->name
, filename
);
1740 if (S_ISLNK (s
.st_mode
))
1742 /* This server doesn't understand LIST -lLa */
1747 fe
->l_stat
= g_try_new (struct stat
, 1);
1748 if (fe
->l_stat
== NULL
)
1752 (*fe
->l_stat
).st_ino
= bucket
->__inode_counter
++;
1763 while (fgets (buffer
, sizeof (buffer
), fp
) != NULL
)
1765 tty_disable_interrupt_key ();
1767 ftpfs_get_reply (me
, FTP_SUPER (super
)->sock
, NULL
, 0);
1770 /* --------------------------------------------------------------------------------------------- */
1773 resolve_symlink (struct vfs_class
*me
, struct vfs_s_super
*super
, struct vfs_s_inode
*dir
)
1775 vfs_print_message ("%s", _("Resolving symlink..."));
1777 if (FTP_SUPER (super
)->strict_rfc959_list_cmd
)
1778 resolve_symlink_without_ls_options (me
, super
, dir
);
1780 resolve_symlink_with_ls_options (me
, super
, dir
);
1784 /* --------------------------------------------------------------------------------------------- */
1787 ftpfs_dir_load (struct vfs_class
*me
, struct vfs_s_inode
*dir
, const char *remote_path
)
1789 struct vfs_s_super
*super
= dir
->super
;
1790 ftp_super_t
*ftp_super
= FTP_SUPER (super
);
1792 char lc_buffer
[BUF_8K
];
1795 GSList
*dirlist
= NULL
;
1800 cd_first
= ftpfs_first_cd_then_ls
|| (ftp_super
->strict
== RFC_STRICT
)
1801 || (strchr (remote_path
, ' ') != NULL
);
1804 vfs_print_message (_("ftpfs: Reading FTP directory %s... %s%s"),
1806 ftp_super
->strict
==
1807 RFC_STRICT
? _("(strict rfc959)") : "", cd_first
? _("(chdir first)") : "");
1809 if (cd_first
&& ftpfs_chdir_internal (me
, super
, remote_path
) != COMPLETE
)
1811 me
->verrno
= ENOENT
;
1812 vfs_print_message ("%s", _("ftpfs: CWD failed."));
1816 dir
->timestamp
= g_get_monotonic_time () + ftpfs_directory_timeout
* G_USEC_PER_SEC
;
1818 if (ftp_super
->strict
== RFC_STRICT
)
1819 sock
= ftpfs_open_data_connection (me
, super
, "LIST", 0, TYPE_ASCII
, 0);
1821 /* Dirty hack to avoid autoprepending / to . */
1822 /* Wu-ftpd produces strange output for '/' if 'LIST -la .' used */
1823 sock
= ftpfs_open_data_connection (me
, super
, "LIST -la", 0, TYPE_ASCII
, 0);
1828 /* Trailing "/." is necessary if remote_path is a symlink */
1829 path
= g_strconcat (remote_path
, PATH_SEP_STR
".", (char *) NULL
);
1830 sock
= ftpfs_open_data_connection (me
, super
, "LIST -la", path
, TYPE_ASCII
, 0);
1837 if (ftp_super
->strict
== RFC_AUTODETECT
)
1839 /* It's our first attempt to get a directory listing from this
1840 server (UNIX style LIST command) */
1841 ftp_super
->strict
= RFC_STRICT
;
1842 /* I hate goto, but recursive call needs another 8K on stack */
1843 /* return ftpfs_dir_load (me, dir, remote_path); */
1848 vfs_print_message ("%s", _("ftpfs: failed; nowhere to fallback to"));
1849 ERRNOR (EACCES
, -1);
1852 /* read full directory list, then parse it */
1853 while ((res
= vfs_s_get_line_interruptible (me
, lc_buffer
, sizeof (lc_buffer
), sock
)) != 0)
1857 me
->verrno
= ECONNRESET
;
1859 ftp_super
->ctl_connection_busy
= FALSE
;
1860 ftpfs_get_reply (me
, ftp_super
->sock
, NULL
, 0);
1861 g_slist_free_full (dirlist
, g_free
);
1862 vfs_print_message (_("%s: failure"), me
->name
);
1866 if (me
->logfile
!= NULL
)
1868 fputs (lc_buffer
, me
->logfile
);
1869 fputs ("\n", me
->logfile
);
1870 fflush (me
->logfile
);
1873 dirlist
= g_slist_prepend (dirlist
, g_strdup (lc_buffer
));
1877 ftp_super
->ctl_connection_busy
= FALSE
;
1878 me
->verrno
= E_REMOTE
;
1879 if ((ftpfs_get_reply (me
, ftp_super
->sock
, NULL
, 0) != COMPLETE
))
1881 g_slist_free_full (dirlist
, g_free
);
1885 if (dirlist
== NULL
&& !cd_first
)
1887 /* The LIST command may produce an empty output. In such scenario
1888 it is not clear whether this is caused by 'remote_path' being
1889 a non-existent path or for some other reason (listing empty
1890 directory without the -a option, non-readable directory, etc.).
1892 Since 'dir_load' is a crucial method, when it comes to determine
1893 whether a given path is a _directory_, the code must try its best
1894 to determine the type of 'remote_path'. The only reliable way to
1895 achieve this is through issuing a CWD command. */
1901 /* parse server's reply */
1902 dirlist
= g_slist_reverse (dirlist
); /* restore order */
1903 entlist
= ftpfs_parse_long_list (me
, dir
, dirlist
, &err_count
);
1904 g_slist_free_full (dirlist
, g_free
);
1906 for (iter
= entlist
; iter
!= NULL
; iter
= g_slist_next (iter
))
1907 vfs_s_insert_entry (me
, dir
, VFS_ENTRY (iter
->data
));
1909 g_slist_free (entlist
);
1911 if (ftp_super
->strict
== RFC_AUTODETECT
)
1912 ftp_super
->strict
= RFC_DARING
;
1914 vfs_print_message (_("%s: done."), me
->name
);
1918 /* --------------------------------------------------------------------------------------------- */
1921 ftpfs_file_store (struct vfs_class
*me
, vfs_file_handler_t
*fh
, char *name
, char *localname
)
1923 struct vfs_s_super
*super
= VFS_FILE_HANDLER_SUPER (fh
);
1924 ftp_super_t
*ftp_super
= FTP_SUPER (super
);
1925 ftp_file_handler_t
*ftp
= FTP_FILE_HANDLER (fh
);
1929 #ifdef HAVE_STRUCT_LINGER_L_LINGER
1934 char lc_buffer
[BUF_8K
];
1938 h
= open (localname
, O_RDONLY
);
1942 if (fstat (h
, &s
) == -1)
1950 ftpfs_open_data_connection (me
, super
, ftp
->append
? "APPE" : "STOR", name
, TYPE_BINARY
, 0);
1956 #ifdef HAVE_STRUCT_LINGER_L_LINGER
1959 setsockopt (sock
, SOL_SOCKET
, SO_LINGER
, (char *) &li
, sizeof (li
));
1961 setsockopt (sock
, SOL_SOCKET
, SO_LINGER
, &flag_one
, sizeof (flag_one
));
1964 tty_enable_interrupt_key ();
1967 ssize_t n_read
, n_written
;
1969 while ((n_read
= read (h
, lc_buffer
, sizeof (lc_buffer
))) == -1)
1976 if (tty_got_interrupt ())
1988 while ((n_written
= write (sock
, w_buf
, n_read
)) != n_read
)
1990 if (n_written
== -1)
1992 if (errno
== EINTR
&& !tty_got_interrupt ())
2000 n_read
-= n_written
;
2003 vfs_print_message ("%s: %" PRIuMAX
"/%" PRIuMAX
,
2004 _("ftpfs: storing file"), (uintmax_t) n_stored
, (uintmax_t) s
.st_size
);
2006 tty_disable_interrupt_key ();
2009 ftp_super
->ctl_connection_busy
= FALSE
;
2012 if (ftpfs_get_reply (me
, ftp_super
->sock
, NULL
, 0) != COMPLETE
)
2017 tty_disable_interrupt_key ();
2019 ftp_super
->ctl_connection_busy
= FALSE
;
2022 ftpfs_get_reply (me
, ftp_super
->sock
, NULL
, 0);
2026 /* --------------------------------------------------------------------------------------------- */
2029 ftpfs_linear_start (struct vfs_class
*me
, vfs_file_handler_t
*fh
, off_t offset
)
2033 name
= vfs_s_fullpath (me
, fh
->ino
);
2038 ftpfs_open_data_connection (me
, VFS_FILE_HANDLER_SUPER (fh
), "RETR", name
, TYPE_BINARY
,
2044 fh
->linear
= LS_LINEAR_OPEN
;
2045 FTP_FILE_HANDLER (fh
)->append
= FALSE
;
2049 /* --------------------------------------------------------------------------------------------- */
2052 ftpfs_linear_read (struct vfs_class
*me
, vfs_file_handler_t
*fh
, void *buf
, size_t len
)
2055 struct vfs_s_super
*super
= VFS_FILE_HANDLER_SUPER (fh
);
2057 while ((n
= read (FH_SOCK
, buf
, len
)) < 0)
2059 if ((errno
== EINTR
) && !tty_got_interrupt ())
2065 ftpfs_linear_abort (me
, fh
);
2068 FTP_SUPER (super
)->ctl_connection_busy
= FALSE
;
2071 if ((ftpfs_get_reply (me
, FTP_SUPER (super
)->sock
, NULL
, 0) != COMPLETE
))
2072 ERRNOR (E_REMOTE
, -1);
2079 /* --------------------------------------------------------------------------------------------- */
2082 ftpfs_linear_close (struct vfs_class
*me
, vfs_file_handler_t
*fh
)
2085 ftpfs_linear_abort (me
, fh
);
2088 /* --------------------------------------------------------------------------------------------- */
2091 ftpfs_ctl (void *fh
, int ctlop
, void *arg
)
2097 case VFS_CTL_IS_NOTREADY
:
2099 vfs_file_handler_t
*file
= VFS_FILE_HANDLER (fh
);
2102 if (file
->linear
== LS_NOT_LINEAR
)
2103 vfs_die ("You may not do this");
2104 if (file
->linear
== LS_LINEAR_CLOSED
|| file
->linear
== LS_LINEAR_PREOPEN
)
2107 v
= vfs_s_select_on_two (FH_SOCK
, 0);
2108 return (((v
< 0) && (errno
== EINTR
)) || v
== 0) ? 1 : 0;
2115 /* --------------------------------------------------------------------------------------------- */
2118 ftpfs_send_command (const vfs_path_t
*vpath
, const char *cmd
, int flags
)
2122 struct vfs_s_super
*super
;
2124 struct vfs_class
*me
;
2125 gboolean flush_directory_cache
= (flags
& OPT_FLUSH
) != 0;
2127 me
= VFS_CLASS (vfs_path_get_last_path_vfs (vpath
));
2129 rpath
= vfs_s_get_path (vpath
, &super
, 0);
2133 p
= ftpfs_translate_path (me
, super
, rpath
);
2134 r
= ftpfs_command (me
, super
, WAIT_REPLY
, cmd
, p
);
2136 vfs_stamp_create (vfs_ftpfs_ops
, super
);
2137 if ((flags
& OPT_IGNORE_ERROR
) != 0)
2144 if (flush_directory_cache
)
2145 vfs_s_invalidate (me
, super
);
2149 /* --------------------------------------------------------------------------------------------- */
2152 ftpfs_stat (const vfs_path_t
*vpath
, struct stat
*buf
)
2156 ret
= vfs_s_stat (vpath
, buf
);
2157 ftpfs_set_blksize (buf
);
2161 /* --------------------------------------------------------------------------------------------- */
2164 ftpfs_lstat (const vfs_path_t
*vpath
, struct stat
*buf
)
2168 ret
= vfs_s_lstat (vpath
, buf
);
2169 ftpfs_set_blksize (buf
);
2173 /* --------------------------------------------------------------------------------------------- */
2176 ftpfs_fstat (void *vfs_info
, struct stat
*buf
)
2180 ret
= vfs_s_fstat (vfs_info
, buf
);
2181 ftpfs_set_blksize (buf
);
2185 /* --------------------------------------------------------------------------------------------- */
2188 ftpfs_chmod (const vfs_path_t
*vpath
, mode_t mode
)
2190 char buf
[BUF_SMALL
];
2193 g_snprintf (buf
, sizeof (buf
), "SITE CHMOD %4.4o /%%s", (unsigned int) (mode
& 07777));
2194 ret
= ftpfs_send_command (vpath
, buf
, OPT_FLUSH
);
2195 return ftpfs_ignore_chattr_errors
? 0 : ret
;
2198 /* --------------------------------------------------------------------------------------------- */
2201 ftpfs_chown (const vfs_path_t
*vpath
, uid_t owner
, gid_t group
)
2211 /* Everyone knows it is not possible to chown remotely, so why bother them.
2212 If someone's root, then copy/move will always try to chown it... */
2220 /* --------------------------------------------------------------------------------------------- */
2223 ftpfs_unlink (const vfs_path_t
*vpath
)
2225 return ftpfs_send_command (vpath
, "DELE /%s", OPT_FLUSH
);
2228 /* --------------------------------------------------------------------------------------------- */
2230 /* Return TRUE if path is the same directory as the one we are in now */
2232 ftpfs_is_same_dir (struct vfs_class
*me
, struct vfs_s_super
*super
, const char *path
)
2236 return (FTP_SUPER (super
)->current_dir
!= NULL
2237 && strcmp (path
, FTP_SUPER (super
)->current_dir
) == 0);
2240 /* --------------------------------------------------------------------------------------------- */
2243 ftpfs_chdir_internal (struct vfs_class
*me
, struct vfs_s_super
*super
, const char *remote_path
)
2245 ftp_super_t
*ftp_super
= FTP_SUPER (super
);
2249 if (!ftp_super
->cwd_deferred
&& ftpfs_is_same_dir (me
, super
, remote_path
))
2252 p
= ftpfs_translate_path (me
, super
, remote_path
);
2253 r
= ftpfs_command (me
, super
, WAIT_REPLY
, "CWD /%s", p
);
2260 g_free (ftp_super
->current_dir
);
2261 ftp_super
->current_dir
= g_strdup (remote_path
);
2262 ftp_super
->cwd_deferred
= FALSE
;
2267 /* --------------------------------------------------------------------------------------------- */
2270 ftpfs_rename (const vfs_path_t
*vpath1
, const vfs_path_t
*vpath2
)
2272 ftpfs_send_command (vpath1
, "RNFR /%s", OPT_FLUSH
);
2273 return ftpfs_send_command (vpath2
, "RNTO /%s", OPT_FLUSH
);
2276 /* --------------------------------------------------------------------------------------------- */
2279 ftpfs_mkdir (const vfs_path_t
*vpath
, mode_t mode
)
2281 (void) mode
; /* FIXME: should be used */
2283 return ftpfs_send_command (vpath
, "MKD /%s", OPT_FLUSH
);
2286 /* --------------------------------------------------------------------------------------------- */
2289 ftpfs_rmdir (const vfs_path_t
*vpath
)
2291 return ftpfs_send_command (vpath
, "RMD /%s", OPT_FLUSH
);
2294 /* --------------------------------------------------------------------------------------------- */
2296 static vfs_file_handler_t
*
2297 ftpfs_fh_new (struct vfs_s_inode
*ino
, gboolean changed
)
2299 ftp_file_handler_t
*fh
;
2301 fh
= g_new0 (ftp_file_handler_t
, 1);
2302 vfs_s_init_fh (VFS_FILE_HANDLER (fh
), ino
, changed
);
2305 return VFS_FILE_HANDLER (fh
);
2308 /* --------------------------------------------------------------------------------------------- */
2311 ftpfs_fh_open (struct vfs_class
*me
, vfs_file_handler_t
*fh
, int flags
, mode_t mode
)
2313 ftp_file_handler_t
*ftp
= FTP_FILE_HANDLER (fh
);
2317 /* File will be written only, so no need to retrieve it from ftp server */
2318 if (((flags
& O_WRONLY
) == O_WRONLY
) && ((flags
& (O_RDONLY
| O_RDWR
)) == 0))
2320 #ifdef HAVE_STRUCT_LINGER_L_LINGER
2327 /* ftpfs_linear_start() called, so data will be written
2328 * to local temporary file and stored to ftp server
2329 * by vfs_s_close later
2331 if (FTP_SUPER (VFS_FILE_HANDLER_SUPER (fh
))->ctl_connection_busy
)
2333 if (fh
->ino
->localname
== NULL
)
2338 handle
= vfs_mkstemps (&vpath
, me
->name
, fh
->ino
->ent
->name
);
2343 fh
->ino
->localname
= vfs_path_free (vpath
, FALSE
);
2344 ftp
->append
= (flags
& O_APPEND
) != 0;
2348 name
= vfs_s_fullpath (me
, fh
->ino
);
2353 ftpfs_open_data_connection (me
, VFS_FILE_HANDLER_SUPER (fh
),
2354 (flags
& O_APPEND
) != 0 ? "APPE" : "STOR", name
,
2361 #ifdef HAVE_STRUCT_LINGER_L_LINGER
2365 setsockopt (fh
->handle
, SOL_SOCKET
, SO_LINGER
, &li
, sizeof (li
));
2367 if (fh
->ino
->localname
!= NULL
)
2369 unlink (fh
->ino
->localname
);
2370 MC_PTR_FREE (fh
->ino
->localname
);
2375 if (fh
->ino
->localname
== NULL
&& vfs_s_retrieve_file (me
, fh
->ino
) == -1)
2378 if (fh
->ino
->localname
== NULL
)
2379 vfs_die ("retrieve_file failed to fill in localname");
2383 /* --------------------------------------------------------------------------------------------- */
2386 ftpfs_fh_close (struct vfs_class
*me
, vfs_file_handler_t
*fh
)
2388 if (fh
->handle
!= -1 && fh
->ino
->localname
== NULL
)
2390 ftp_super_t
*ftp
= FTP_SUPER (VFS_FILE_HANDLER_SUPER (fh
));
2394 ftp
->ctl_connection_busy
= FALSE
;
2395 /* File is stored to destination already, so
2396 * we prevent VFS_SUBCLASS (me)->ftpfs_file_store() call from vfs_s_close ()
2398 fh
->changed
= FALSE
;
2399 if (ftpfs_get_reply (me
, ftp
->sock
, NULL
, 0) != COMPLETE
)
2401 vfs_s_invalidate (me
, VFS_FILE_HANDLER_SUPER (fh
));
2407 /* --------------------------------------------------------------------------------------------- */
2410 ftpfs_done (struct vfs_class
*me
)
2414 g_slist_free_full (no_proxy
, g_free
);
2416 g_free (ftpfs_anonymous_passwd
);
2417 g_free (ftpfs_proxy_host
);
2420 /* --------------------------------------------------------------------------------------------- */
2423 ftpfs_fill_names (struct vfs_class
*me
, fill_names_f func
)
2427 for (iter
= VFS_SUBCLASS (me
)->supers
; iter
!= NULL
; iter
= g_list_next (iter
))
2429 const struct vfs_s_super
*super
= (const struct vfs_s_super
*) iter
->data
;
2432 name
= vfs_path_element_build_pretty_path_str (super
->path_element
);
2435 g_string_free (name
, TRUE
);
2439 /* --------------------------------------------------------------------------------------------- */
2442 ftpfs_netrc_next (void)
2446 static const char *const keywords
[] = { "default", "machine",
2447 "login", "password", "passwd", "account", "macdef", NULL
2452 netrcp
= skip_separators (netrcp
);
2453 if (*netrcp
!= '\n')
2457 if (*netrcp
== '\0')
2463 for (netrcp
++; *netrcp
!= '"' && *netrcp
!= '\0'; netrcp
++)
2465 if (*netrcp
== '\\')
2472 for (; *netrcp
!= '\0' && !whiteness (*netrcp
) && *netrcp
!= ','; netrcp
++)
2474 if (*netrcp
== '\\')
2481 if (*buffer
== '\0')
2484 for (i
= NETRC_DEFAULT
; keywords
[i
- 1] != NULL
; i
++)
2485 if (strcmp (keywords
[i
- 1], buffer
) == 0)
2488 return NETRC_UNKNOWN
;
2491 /* --------------------------------------------------------------------------------------------- */
2494 ftpfs_netrc_bad_mode (const char *netrcname
)
2498 if (stat (netrcname
, &mystat
) >= 0 && (mystat
.st_mode
& 077) != 0)
2500 static gboolean be_angry
= TRUE
;
2504 message (D_ERROR
, MSG_ERROR
,
2505 _("~/.netrc file has incorrect mode\nRemove password or correct mode"));
2514 /* --------------------------------------------------------------------------------------------- */
2515 /* Scan .netrc until we find matching "machine" or "default"
2516 * domain is used for additional matching
2517 * No search is done after "default" in compliance with "man netrc"
2518 * Return TRUE if found, FALSE otherwise */
2521 ftpfs_find_machine (const char *host
, const char *domain
)
2530 while ((keyword
= ftpfs_netrc_next ()) != NETRC_NONE
)
2532 if (keyword
== NETRC_DEFAULT
)
2535 if (keyword
== NETRC_MACDEF
)
2537 /* Scan for an empty line, which concludes "macdef" */
2540 while (*netrcp
!= '\0' && *netrcp
!= '\n')
2542 if (*netrcp
!= '\n')
2546 while (*netrcp
!= '\0' && *netrcp
!= '\n');
2551 if (keyword
!= NETRC_MACHINE
)
2554 /* Take machine name */
2555 if (ftpfs_netrc_next () == NETRC_NONE
)
2558 if (g_ascii_strcasecmp (host
, buffer
) != 0)
2560 const char *host_domain
;
2562 /* Try adding our domain to short names in .netrc */
2563 host_domain
= strchr (host
, '.');
2564 if (host_domain
== NULL
)
2567 /* Compare domain part */
2568 if (g_ascii_strcasecmp (host_domain
, domain
) != 0)
2571 /* Compare local part */
2572 if (g_ascii_strncasecmp (host
, buffer
, host_domain
- host
) != 0)
2583 /* --------------------------------------------------------------------------------------------- */
2584 /* Extract login and password from .netrc for the host.
2586 * Returns TRUE for success, FALSE for error */
2589 ftpfs_netrc_lookup (const char *host
, char **login
, char **pass
)
2592 char *tmp_pass
= NULL
;
2593 char hostname
[MAXHOSTNAMELEN
];
2595 static struct rupcache
2597 struct rupcache
*next
;
2601 } *rup_cache
= NULL
, *rupp
;
2603 /* Initialize *login and *pass */
2604 MC_PTR_FREE (*login
);
2605 MC_PTR_FREE (*pass
);
2607 /* Look up in the cache first */
2608 for (rupp
= rup_cache
; rupp
!= NULL
; rupp
= rupp
->next
)
2609 if (strcmp (host
, rupp
->host
) == 0)
2611 *login
= g_strdup (rupp
->login
);
2612 *pass
= g_strdup (rupp
->pass
);
2616 /* Load current .netrc */
2617 netrcname
= g_build_filename (mc_config_get_home_dir (), ".netrc", (char *) NULL
);
2618 if (!g_file_get_contents (netrcname
, &netrc
, NULL
, NULL
))
2626 /* Find our own domain name */
2627 if (gethostname (hostname
, sizeof (hostname
)) < 0)
2630 domain
= strchr (hostname
, '.');
2634 /* Scan for "default" and matching "machine" keywords */
2635 ftpfs_find_machine (host
, domain
);
2637 /* Scan for keywords following "default" and "machine" */
2642 gboolean need_break
= FALSE
;
2643 keyword
= ftpfs_netrc_next ();
2648 if (ftpfs_netrc_next () == NETRC_NONE
)
2654 /* We have another name already - should not happen */
2661 /* We have login name now */
2662 *login
= g_strdup (buffer
);
2665 case NETRC_PASSWORD
:
2667 if (ftpfs_netrc_next () == NETRC_NONE
)
2673 /* Ignore unsafe passwords */
2674 if (*login
!= NULL
&&
2675 strcmp (*login
, "anonymous") != 0 && strcmp (*login
, "ftp") != 0
2676 && ftpfs_netrc_bad_mode (netrcname
))
2682 /* Remember password. pass may be NULL, so use tmp_pass */
2683 if (tmp_pass
== NULL
)
2684 tmp_pass
= g_strdup (buffer
);
2688 /* "account" is followed by a token which we ignore */
2689 if (ftpfs_netrc_next () == NETRC_NONE
)
2695 /* Ignore account, but warn user anyways */
2696 ftpfs_netrc_bad_mode (netrcname
);
2700 /* Unexpected keyword or end of file */
2709 MC_PTR_FREE (netrc
);
2712 rupp
= g_new (struct rupcache
, 1);
2713 rupp
->host
= g_strdup (host
);
2714 rupp
->login
= g_strdup (*login
);
2715 rupp
->pass
= g_strdup (tmp_pass
);
2717 rupp
->next
= rup_cache
;
2725 /* --------------------------------------------------------------------------------------------- */
2726 /*** public functions ****************************************************************************/
2727 /* --------------------------------------------------------------------------------------------- */
2729 /** This routine is called as the last step in load_setup */
2731 ftpfs_init_passwd (void)
2733 ftpfs_anonymous_passwd
= load_anon_passwd ();
2735 if (ftpfs_anonymous_passwd
== NULL
)
2737 /* If there is no anonymous ftp password specified
2738 * then we'll just use anonymous@
2739 * We don't send any other thing because:
2740 * - We want to remain anonymous
2741 * - We want to stop SPAM
2742 * - We don't want to let ftp sites to discriminate by the user,
2745 ftpfs_anonymous_passwd
= g_strdup ("anonymous@");
2749 /* --------------------------------------------------------------------------------------------- */
2752 vfs_init_ftpfs (void)
2756 vfs_init_subclass (&ftpfs_subclass
, "ftpfs", VFSF_NOLINKS
| VFSF_REMOTE
| VFSF_USETMP
, "ftp");
2757 vfs_ftpfs_ops
->done
= ftpfs_done
;
2758 vfs_ftpfs_ops
->fill_names
= ftpfs_fill_names
;
2759 vfs_ftpfs_ops
->stat
= ftpfs_stat
;
2760 vfs_ftpfs_ops
->lstat
= ftpfs_lstat
;
2761 vfs_ftpfs_ops
->fstat
= ftpfs_fstat
;
2762 vfs_ftpfs_ops
->chmod
= ftpfs_chmod
;
2763 vfs_ftpfs_ops
->chown
= ftpfs_chown
;
2764 vfs_ftpfs_ops
->unlink
= ftpfs_unlink
;
2765 vfs_ftpfs_ops
->rename
= ftpfs_rename
;
2766 vfs_ftpfs_ops
->mkdir
= ftpfs_mkdir
;
2767 vfs_ftpfs_ops
->rmdir
= ftpfs_rmdir
;
2768 vfs_ftpfs_ops
->ctl
= ftpfs_ctl
;
2769 ftpfs_subclass
.archive_same
= ftpfs_archive_same
;
2770 ftpfs_subclass
.new_archive
= ftpfs_new_archive
;
2771 ftpfs_subclass
.open_archive
= ftpfs_open_archive
;
2772 ftpfs_subclass
.free_archive
= ftpfs_free_archive
;
2773 ftpfs_subclass
.fh_new
= ftpfs_fh_new
;
2774 ftpfs_subclass
.fh_open
= ftpfs_fh_open
;
2775 ftpfs_subclass
.fh_close
= ftpfs_fh_close
;
2776 ftpfs_subclass
.dir_load
= ftpfs_dir_load
;
2777 ftpfs_subclass
.file_store
= ftpfs_file_store
;
2778 ftpfs_subclass
.linear_start
= ftpfs_linear_start
;
2779 ftpfs_subclass
.linear_read
= ftpfs_linear_read
;
2780 ftpfs_subclass
.linear_close
= ftpfs_linear_close
;
2781 vfs_register_class (vfs_ftpfs_ops
);
2784 /* --------------------------------------------------------------------------------------------- */