summaryrefslogtreecommitdiffstats
path: root/src/vfs
diff options
context:
space:
mode:
Diffstat (limited to 'src/vfs')
-rw-r--r--src/vfs/Makefile.am47
-rw-r--r--src/vfs/Makefile.in875
-rw-r--r--src/vfs/cpio/Makefile.am7
-rw-r--r--src/vfs/cpio/Makefile.in735
-rw-r--r--src/vfs/cpio/cpio.c905
-rw-r--r--src/vfs/cpio/cpio.h18
-rw-r--r--src/vfs/extfs/Makefile.am12
-rw-r--r--src/vfs/extfs/Makefile.in856
-rw-r--r--src/vfs/extfs/extfs.c1722
-rw-r--r--src/vfs/extfs/extfs.h18
-rw-r--r--src/vfs/extfs/helpers/Makefile.am76
-rw-r--r--src/vfs/extfs/helpers/Makefile.in812
-rw-r--r--src/vfs/extfs/helpers/README200
-rw-r--r--src/vfs/extfs/helpers/README.extfs78
-rw-r--r--src/vfs/extfs/helpers/a+.in126
-rw-r--r--src/vfs/extfs/helpers/apt+.in359
-rwxr-xr-xsrc/vfs/extfs/helpers/audio.in53
-rwxr-xr-xsrc/vfs/extfs/helpers/bpp50
-rwxr-xr-xsrc/vfs/extfs/helpers/changesetfs109
-rw-r--r--src/vfs/extfs/helpers/deb.in203
-rw-r--r--src/vfs/extfs/helpers/deba.in107
-rw-r--r--src/vfs/extfs/helpers/debd.in362
-rw-r--r--src/vfs/extfs/helpers/dpkg+.in337
-rwxr-xr-xsrc/vfs/extfs/helpers/gitfs+39
-rw-r--r--src/vfs/extfs/helpers/hp48+.in132
-rw-r--r--src/vfs/extfs/helpers/iso9660.in235
-rw-r--r--src/vfs/extfs/helpers/lslR.in74
-rw-r--r--src/vfs/extfs/helpers/mailfs.in219
-rw-r--r--src/vfs/extfs/helpers/patchfs.in427
-rwxr-xr-xsrc/vfs/extfs/helpers/patchsetfs104
-rwxr-xr-xsrc/vfs/extfs/helpers/rpm349
-rw-r--r--src/vfs/extfs/helpers/rpms+.in66
-rw-r--r--src/vfs/extfs/helpers/s3+.in490
-rwxr-xr-xsrc/vfs/extfs/helpers/trpm176
-rwxr-xr-xsrc/vfs/extfs/helpers/u7z135
-rw-r--r--src/vfs/extfs/helpers/uace.in67
-rw-r--r--src/vfs/extfs/helpers/ualz.in68
-rw-r--r--src/vfs/extfs/helpers/uar.in60
-rw-r--r--src/vfs/extfs/helpers/uarc.in92
-rw-r--r--src/vfs/extfs/helpers/uarj.in75
-rwxr-xr-xsrc/vfs/extfs/helpers/uc1541702
-rw-r--r--src/vfs/extfs/helpers/ucab.in40
-rw-r--r--src/vfs/extfs/helpers/uha.in52
-rw-r--r--src/vfs/extfs/helpers/ulha.in142
-rw-r--r--src/vfs/extfs/helpers/ulib.in146
-rw-r--r--src/vfs/extfs/helpers/unar.in59
-rw-r--r--src/vfs/extfs/helpers/urar.in180
-rw-r--r--src/vfs/extfs/helpers/uwim.in208
-rw-r--r--src/vfs/extfs/helpers/uzip.in483
-rw-r--r--src/vfs/extfs/helpers/uzoo.in69
-rw-r--r--src/vfs/fish/Makefile.am13
-rw-r--r--src/vfs/fish/Makefile.in857
-rw-r--r--src/vfs/fish/fish.c1805
-rw-r--r--src/vfs/fish/fish.h28
-rw-r--r--src/vfs/fish/fishdef.h236
-rw-r--r--src/vfs/fish/helpers/Makefile.am10
-rw-r--r--src/vfs/fish/helpers/Makefile.in642
-rw-r--r--src/vfs/fish/helpers/README.fish217
-rw-r--r--src/vfs/fish/helpers/append16
-rw-r--r--src/vfs/fish/helpers/chmod6
-rw-r--r--src/vfs/fish/helpers/chown6
-rw-r--r--src/vfs/fish/helpers/fexists3
-rw-r--r--src/vfs/fish/helpers/get105
-rw-r--r--src/vfs/fish/helpers/hardlink8
-rw-r--r--src/vfs/fish/helpers/info44
-rw-r--r--src/vfs/fish/helpers/ln8
-rw-r--r--src/vfs/fish/helpers/ls170
-rw-r--r--src/vfs/fish/helpers/mkdir6
-rw-r--r--src/vfs/fish/helpers/mv6
-rw-r--r--src/vfs/fish/helpers/rmdir6
-rw-r--r--src/vfs/fish/helpers/send17
-rw-r--r--src/vfs/fish/helpers/unlink6
-rw-r--r--src/vfs/fish/helpers/utime13
-rw-r--r--src/vfs/ftpfs/Makefile.am8
-rw-r--r--src/vfs/ftpfs/Makefile.in740
-rw-r--r--src/vfs/ftpfs/ftpfs.c2784
-rw-r--r--src/vfs/ftpfs/ftpfs.h46
-rw-r--r--src/vfs/ftpfs/ftpfs_parse_ls.c1236
-rw-r--r--src/vfs/local/Makefile.am7
-rw-r--r--src/vfs/local/Makefile.in735
-rw-r--r--src/vfs/local/local.c523
-rw-r--r--src/vfs/local/local.h32
-rw-r--r--src/vfs/plugins_init.c123
-rw-r--r--src/vfs/plugins_init.h18
-rw-r--r--src/vfs/sfs/Makefile.am16
-rw-r--r--src/vfs/sfs/Makefile.in794
-rw-r--r--src/vfs/sfs/sfs.c604
-rw-r--r--src/vfs/sfs/sfs.h18
-rw-r--r--src/vfs/sfs/sfs.ini34
-rw-r--r--src/vfs/sftpfs/Makefile.am12
-rw-r--r--src/vfs/sftpfs/Makefile.in759
-rw-r--r--src/vfs/sftpfs/config_parser.c427
-rw-r--r--src/vfs/sftpfs/connection.c970
-rw-r--r--src/vfs/sftpfs/dir.c230
-rw-r--r--src/vfs/sftpfs/file.c424
-rw-r--r--src/vfs/sftpfs/internal.c621
-rw-r--r--src/vfs/sftpfs/internal.h114
-rw-r--r--src/vfs/sftpfs/sftpfs.c866
-rw-r--r--src/vfs/sftpfs/sftpfs.h23
-rw-r--r--src/vfs/tar/Makefile.am10
-rw-r--r--src/vfs/tar/Makefile.in750
-rw-r--r--src/vfs/tar/tar-internal.c482
-rw-r--r--src/vfs/tar/tar-internal.h351
-rw-r--r--src/vfs/tar/tar-sparse.c777
-rw-r--r--src/vfs/tar/tar-xheader.c1051
-rw-r--r--src/vfs/tar/tar.c1302
-rw-r--r--src/vfs/tar/tar.h18
-rw-r--r--src/vfs/undelfs/Makefile.am7
-rw-r--r--src/vfs/undelfs/Makefile.in735
-rw-r--r--src/vfs/undelfs/undelfs.c844
-rw-r--r--src/vfs/undelfs/undelfs.h18
111 files changed, 35693 insertions, 0 deletions
diff --git a/src/vfs/Makefile.am b/src/vfs/Makefile.am
new file mode 100644
index 0000000..1441953
--- /dev/null
+++ b/src/vfs/Makefile.am
@@ -0,0 +1,47 @@
+noinst_LTLIBRARIES = libmc-vfs.la
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+libmc_vfs_la_SOURCES = plugins_init.c plugins_init.h
+
+SUBDIRS = local
+libmc_vfs_la_LIBADD = local/libvfs-local.la
+
+if ENABLE_VFS_CPIO
+SUBDIRS += cpio
+libmc_vfs_la_LIBADD += cpio/libvfs-cpio.la
+endif
+
+if ENABLE_VFS_EXTFS
+SUBDIRS += extfs
+libmc_vfs_la_LIBADD += extfs/libvfs-extfs.la
+endif
+
+if ENABLE_VFS_FISH
+SUBDIRS += fish
+libmc_vfs_la_LIBADD += fish/libvfs-fish.la
+endif
+
+if ENABLE_VFS_FTP
+SUBDIRS += ftpfs
+libmc_vfs_la_LIBADD += ftpfs/libvfs-ftpfs.la
+endif
+
+if ENABLE_VFS_SFTP
+SUBDIRS += sftpfs
+libmc_vfs_la_LIBADD += sftpfs/libvfs-sftpfs.la
+endif
+
+if ENABLE_VFS_SFS
+SUBDIRS += sfs
+libmc_vfs_la_LIBADD += sfs/libvfs-sfs.la
+endif
+
+if ENABLE_VFS_TAR
+SUBDIRS += tar
+libmc_vfs_la_LIBADD += tar/libvfs-tar.la
+endif
+
+if ENABLE_VFS_UNDELFS
+SUBDIRS += undelfs
+libmc_vfs_la_LIBADD += undelfs/libvfs-undelfs.la
+endif
diff --git a/src/vfs/Makefile.in b/src/vfs/Makefile.in
new file mode 100644
index 0000000..a245efe
--- /dev/null
+++ b/src/vfs/Makefile.in
@@ -0,0 +1,875 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@ENABLE_VFS_CPIO_TRUE@am__append_1 = cpio
+@ENABLE_VFS_CPIO_TRUE@am__append_2 = cpio/libvfs-cpio.la
+@ENABLE_VFS_EXTFS_TRUE@am__append_3 = extfs
+@ENABLE_VFS_EXTFS_TRUE@am__append_4 = extfs/libvfs-extfs.la
+@ENABLE_VFS_FISH_TRUE@am__append_5 = fish
+@ENABLE_VFS_FISH_TRUE@am__append_6 = fish/libvfs-fish.la
+@ENABLE_VFS_FTP_TRUE@am__append_7 = ftpfs
+@ENABLE_VFS_FTP_TRUE@am__append_8 = ftpfs/libvfs-ftpfs.la
+@ENABLE_VFS_SFTP_TRUE@am__append_9 = sftpfs
+@ENABLE_VFS_SFTP_TRUE@am__append_10 = sftpfs/libvfs-sftpfs.la
+@ENABLE_VFS_SFS_TRUE@am__append_11 = sfs
+@ENABLE_VFS_SFS_TRUE@am__append_12 = sfs/libvfs-sfs.la
+@ENABLE_VFS_TAR_TRUE@am__append_13 = tar
+@ENABLE_VFS_TAR_TRUE@am__append_14 = tar/libvfs-tar.la
+@ENABLE_VFS_UNDELFS_TRUE@am__append_15 = undelfs
+@ENABLE_VFS_UNDELFS_TRUE@am__append_16 = undelfs/libvfs-undelfs.la
+subdir = src/vfs
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libmc_vfs_la_DEPENDENCIES = local/libvfs-local.la $(am__append_2) \
+ $(am__append_4) $(am__append_6) $(am__append_8) \
+ $(am__append_10) $(am__append_12) $(am__append_14) \
+ $(am__append_16)
+am_libmc_vfs_la_OBJECTS = plugins_init.lo
+libmc_vfs_la_OBJECTS = $(am_libmc_vfs_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/plugins_init.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libmc_vfs_la_SOURCES)
+DIST_SOURCES = $(libmc_vfs_la_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+DIST_SUBDIRS = local cpio extfs fish ftpfs sftpfs sfs tar undelfs
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libmc-vfs.la
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+libmc_vfs_la_SOURCES = plugins_init.c plugins_init.h
+SUBDIRS = local $(am__append_1) $(am__append_3) $(am__append_5) \
+ $(am__append_7) $(am__append_9) $(am__append_11) \
+ $(am__append_13) $(am__append_15)
+libmc_vfs_la_LIBADD = local/libvfs-local.la $(am__append_2) \
+ $(am__append_4) $(am__append_6) $(am__append_8) \
+ $(am__append_10) $(am__append_12) $(am__append_14) \
+ $(am__append_16)
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/vfs/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libmc-vfs.la: $(libmc_vfs_la_OBJECTS) $(libmc_vfs_la_DEPENDENCIES) $(EXTRA_libmc_vfs_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libmc_vfs_la_OBJECTS) $(libmc_vfs_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/plugins_init.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(LTLIBRARIES)
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/plugins_init.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/plugins_init.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/vfs/cpio/Makefile.am b/src/vfs/cpio/Makefile.am
new file mode 100644
index 0000000..a7806f8
--- /dev/null
+++ b/src/vfs/cpio/Makefile.am
@@ -0,0 +1,7 @@
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+
+noinst_LTLIBRARIES = libvfs-cpio.la
+
+libvfs_cpio_la_SOURCES = \
+ cpio.c cpio.h
diff --git a/src/vfs/cpio/Makefile.in b/src/vfs/cpio/Makefile.in
new file mode 100644
index 0000000..8534a52
--- /dev/null
+++ b/src/vfs/cpio/Makefile.in
@@ -0,0 +1,735 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/vfs/cpio
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libvfs_cpio_la_LIBADD =
+am_libvfs_cpio_la_OBJECTS = cpio.lo
+libvfs_cpio_la_OBJECTS = $(am_libvfs_cpio_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/cpio.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libvfs_cpio_la_SOURCES)
+DIST_SOURCES = $(libvfs_cpio_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+noinst_LTLIBRARIES = libvfs-cpio.la
+libvfs_cpio_la_SOURCES = \
+ cpio.c cpio.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/vfs/cpio/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/cpio/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libvfs-cpio.la: $(libvfs_cpio_la_OBJECTS) $(libvfs_cpio_la_DEPENDENCIES) $(EXTRA_libvfs_cpio_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libvfs_cpio_la_OBJECTS) $(libvfs_cpio_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cpio.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/cpio.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/cpio.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/vfs/cpio/cpio.c b/src/vfs/cpio/cpio.c
new file mode 100644
index 0000000..447d1f6
--- /dev/null
+++ b/src/vfs/cpio/cpio.c
@@ -0,0 +1,905 @@
+/*
+ Virtual File System: GNU Tar file system.
+
+ Copyright (C) 2000-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Jan Hudec, 2000
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * \brief Source: Virtual File System: GNU Tar file system.
+ * \author Jan Hudec
+ * \date 2000
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "lib/global.h"
+#include "lib/unixcompat.h"
+#include "lib/util.h"
+#include "lib/widget.h" /* message() */
+
+#include "lib/vfs/vfs.h"
+#include "lib/vfs/utilvfs.h"
+#include "lib/vfs/xdirentry.h"
+#include "lib/vfs/gc.h" /* vfs_rmstamp */
+
+#include "cpio.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define CPIO_SUPER(super) ((cpio_super_t *) (super))
+
+#define CPIO_POS(super) cpio_position
+/* If some time reentrancy should be needed change it to */
+/* #define CPIO_POS(super) (super)->u.arch.fd */
+
+#define CPIO_SEEK_SET(super, where) mc_lseek (CPIO_SUPER(super)->fd, CPIO_POS(super) = (where), SEEK_SET)
+#define CPIO_SEEK_CUR(super, where) mc_lseek (CPIO_SUPER(super)->fd, CPIO_POS(super) += (where), SEEK_SET)
+
+#define MAGIC_LENGTH (6) /* How many bytes we have to read ahead */
+#define SEEKBACK CPIO_SEEK_CUR(super, ptr - top)
+#define RETURN(x) return (CPIO_SUPER(super)->type = (x))
+#define TYPEIS(x) ((CPIO_SUPER(super)->type == CPIO_UNKNOWN) || (CPIO_SUPER(super)->type == (x)))
+
+#define HEAD_LENGTH (26)
+
+/*** file scope type declarations ****************************************************************/
+
+enum
+{
+ STATUS_START,
+ STATUS_OK,
+ STATUS_TRAIL,
+ STATUS_FAIL,
+ STATUS_EOF
+};
+
+enum
+{
+ CPIO_UNKNOWN = 0, /* Not determined yet */
+ CPIO_BIN, /* Binary format */
+ CPIO_BINRE, /* Binary format, reverse endianness */
+ CPIO_OLDC, /* Old ASCII format */
+ CPIO_NEWC, /* New ASCII format */
+ CPIO_CRC /* New ASCII format + CRC */
+};
+
+struct old_cpio_header
+{
+ unsigned short c_magic;
+ short c_dev;
+ unsigned short c_ino;
+ unsigned short c_mode;
+ unsigned short c_uid;
+ unsigned short c_gid;
+ unsigned short c_nlink;
+ short c_rdev;
+ unsigned short c_mtimes[2];
+ unsigned short c_namesize;
+ unsigned short c_filesizes[2];
+};
+
+struct new_cpio_header
+{
+ unsigned short c_magic;
+ unsigned long c_ino;
+ unsigned long c_mode;
+ unsigned long c_uid;
+ unsigned long c_gid;
+ unsigned long c_nlink;
+ unsigned long c_mtime;
+ unsigned long c_filesize;
+ long c_dev;
+ long c_devmin;
+ long c_rdev;
+ long c_rdevmin;
+ unsigned long c_namesize;
+ unsigned long c_chksum;
+};
+
+typedef struct
+{
+ unsigned long inumber;
+ dev_t device;
+ struct vfs_s_inode *inode;
+} defer_inode;
+
+typedef struct
+{
+ struct vfs_s_super base; /* base class */
+
+ int fd;
+ struct stat st;
+ int type; /* Type of the archive */
+ GSList *deferred; /* List of inodes for which another entries may appear */
+} cpio_super_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static ssize_t cpio_find_head (struct vfs_class *me, struct vfs_s_super *super);
+static ssize_t cpio_read_bin_head (struct vfs_class *me, struct vfs_s_super *super);
+static ssize_t cpio_read_oldc_head (struct vfs_class *me, struct vfs_s_super *super);
+static ssize_t cpio_read_crc_head (struct vfs_class *me, struct vfs_s_super *super);
+
+/*** file scope variables ************************************************************************/
+
+static struct vfs_s_subclass cpio_subclass;
+static struct vfs_class *vfs_cpiofs_ops = VFS_CLASS (&cpio_subclass);
+
+static off_t cpio_position;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+cpio_defer_find (const void *a, const void *b)
+{
+ const defer_inode *a1 = (const defer_inode *) a;
+ const defer_inode *b1 = (const defer_inode *) b;
+
+ return (a1->inumber == b1->inumber && a1->device == b1->device) ? 0 : 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+cpio_skip_padding (struct vfs_s_super *super)
+{
+ switch (CPIO_SUPER (super)->type)
+ {
+ case CPIO_BIN:
+ case CPIO_BINRE:
+ return CPIO_SEEK_CUR (super, (2 - (CPIO_POS (super) % 2)) % 2);
+ case CPIO_NEWC:
+ case CPIO_CRC:
+ return CPIO_SEEK_CUR (super, (4 - (CPIO_POS (super) % 4)) % 4);
+ case CPIO_OLDC:
+ return CPIO_POS (super);
+ default:
+ g_assert_not_reached ();
+ return 42; /* & the compiler is happy :-) */
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_super *
+cpio_new_archive (struct vfs_class *me)
+{
+ cpio_super_t *arch;
+
+ arch = g_new0 (cpio_super_t, 1);
+ arch->base.me = me;
+ arch->fd = -1; /* for now */
+ arch->type = CPIO_UNKNOWN;
+
+ return VFS_SUPER (arch);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+cpio_free_archive (struct vfs_class *me, struct vfs_s_super *super)
+{
+ cpio_super_t *arch = CPIO_SUPER (super);
+
+ (void) me;
+
+ if (arch->fd != -1)
+ {
+ mc_close (arch->fd);
+ arch->fd = -1;
+ }
+
+ g_clear_slist (&arch->deferred, g_free);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+cpio_open_cpio_file (struct vfs_class *me, struct vfs_s_super *super, const vfs_path_t * vpath)
+{
+ int fd, type;
+ cpio_super_t *arch;
+ mode_t mode;
+ struct vfs_s_inode *root;
+
+ fd = mc_open (vpath, O_RDONLY);
+ if (fd == -1)
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot open cpio archive\n%s"), vfs_path_as_str (vpath));
+ return -1;
+ }
+
+ super->name = g_strdup (vfs_path_as_str (vpath));
+ arch = CPIO_SUPER (super);
+ mc_stat (vpath, &arch->st);
+
+ type = get_compression_type (fd, super->name);
+ if (type == COMPRESSION_NONE)
+ mc_lseek (fd, 0, SEEK_SET);
+ else
+ {
+ char *s;
+ vfs_path_t *tmp_vpath;
+
+ mc_close (fd);
+ s = g_strconcat (super->name, decompress_extension (type), (char *) NULL);
+ tmp_vpath = vfs_path_from_str_flags (s, VPF_NO_CANON);
+ fd = mc_open (tmp_vpath, O_RDONLY);
+ vfs_path_free (tmp_vpath, TRUE);
+ if (fd == -1)
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot open cpio archive\n%s"), s);
+ g_free (s);
+ MC_PTR_FREE (super->name);
+ return -1;
+ }
+ g_free (s);
+ }
+
+ arch->fd = fd;
+ mode = arch->st.st_mode & 07777;
+ mode |= (mode & 0444) >> 2; /* set eXec where Read is */
+ mode |= S_IFDIR;
+
+ root = vfs_s_new_inode (me, super, &arch->st);
+ root->st.st_mode = mode;
+ root->data_offset = -1;
+ root->st.st_nlink++;
+ root->st.st_dev = VFS_SUBCLASS (me)->rdev++;
+
+ super->root = root;
+
+ CPIO_SEEK_SET (super, 0);
+
+ return fd;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+cpio_read_head (struct vfs_class *me, struct vfs_s_super *super)
+{
+ switch (cpio_find_head (me, super))
+ {
+ case CPIO_UNKNOWN:
+ return -1;
+ case CPIO_BIN:
+ case CPIO_BINRE:
+ return cpio_read_bin_head (me, super);
+ case CPIO_OLDC:
+ return cpio_read_oldc_head (me, super);
+ case CPIO_NEWC:
+ case CPIO_CRC:
+ return cpio_read_crc_head (me, super);
+ default:
+ g_assert_not_reached ();
+ return 42; /* & the compiler is happy :-) */
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+cpio_find_head (struct vfs_class *me, struct vfs_s_super *super)
+{
+ cpio_super_t *arch = CPIO_SUPER (super);
+ char buf[BUF_SMALL * 2];
+ ssize_t ptr = 0;
+ ssize_t top;
+ ssize_t tmp;
+
+ top = mc_read (arch->fd, buf, sizeof (buf));
+ if (top > 0)
+ CPIO_POS (super) += top;
+
+ while (TRUE)
+ {
+ if (ptr + MAGIC_LENGTH >= top)
+ {
+ if (top > (ssize_t) (sizeof (buf) / 2))
+ {
+ memmove (buf, buf + top - sizeof (buf) / 2, sizeof (buf) / 2);
+ ptr -= top - sizeof (buf) / 2;
+ top = sizeof (buf) / 2;
+ }
+ tmp = mc_read (arch->fd, buf, top);
+ if (tmp == 0 || tmp == -1)
+ {
+ message (D_ERROR, MSG_ERROR, _("Premature end of cpio archive\n%s"), super->name);
+ cpio_free_archive (me, super);
+ return CPIO_UNKNOWN;
+ }
+ top += tmp;
+ }
+ if (TYPEIS (CPIO_BIN) && ((*(unsigned short *) (buf + ptr)) == 070707))
+ {
+ SEEKBACK;
+ RETURN (CPIO_BIN);
+ }
+ else if (TYPEIS (CPIO_BINRE)
+ && ((*(unsigned short *) (buf + ptr)) == GUINT16_SWAP_LE_BE_CONSTANT (070707)))
+ {
+ SEEKBACK;
+ RETURN (CPIO_BINRE);
+ }
+ else if (TYPEIS (CPIO_OLDC) && (strncmp (buf + ptr, "070707", 6) == 0))
+ {
+ SEEKBACK;
+ RETURN (CPIO_OLDC);
+ }
+ else if (TYPEIS (CPIO_NEWC) && (strncmp (buf + ptr, "070701", 6) == 0))
+ {
+ SEEKBACK;
+ RETURN (CPIO_NEWC);
+ }
+ else if (TYPEIS (CPIO_CRC) && (strncmp (buf + ptr, "070702", 6) == 0))
+ {
+ SEEKBACK;
+ RETURN (CPIO_CRC);
+ };
+ ptr++;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+cpio_create_entry (struct vfs_class *me, struct vfs_s_super *super, struct stat *st, char *name)
+{
+ cpio_super_t *arch = CPIO_SUPER (super);
+ struct vfs_s_inode *inode = NULL;
+ struct vfs_s_inode *root = super->root;
+ struct vfs_s_entry *entry = NULL;
+ char *tn;
+
+ switch (st->st_mode & S_IFMT)
+ { /* For case of HP/UX archives */
+ case S_IFCHR:
+ case S_IFBLK:
+#ifdef S_IFSOCK
+ /* cppcheck-suppress syntaxError */
+ case S_IFSOCK:
+#endif
+#ifdef S_IFIFO
+ /* cppcheck-suppress syntaxError */
+ case S_IFIFO:
+#endif
+#ifdef S_IFNAM
+ /* cppcheck-suppress syntaxError */
+ case S_IFNAM:
+#endif
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ if ((st->st_size != 0) && (st->st_rdev == 0x0001))
+ {
+ /* FIXME: representation of major/minor differs between */
+ /* different operating systems. */
+ st->st_rdev = (unsigned) st->st_size;
+ st->st_size = 0;
+ }
+#endif
+ break;
+ default:
+ break;
+ }
+
+ if ((st->st_nlink > 1) && ((arch->type == CPIO_NEWC) || (arch->type == CPIO_CRC)))
+ { /* For case of hardlinked files */
+ defer_inode i = { st->st_ino, st->st_dev, NULL };
+ GSList *l;
+
+ l = g_slist_find_custom (arch->deferred, &i, cpio_defer_find);
+ if (l != NULL)
+ {
+ inode = ((defer_inode *) l->data)->inode;
+ if (inode->st.st_size != 0 && st->st_size != 0 && (inode->st.st_size != st->st_size))
+ {
+ message (D_ERROR, MSG_ERROR,
+ _("Inconsistent hardlinks of\n%s\nin cpio archive\n%s"),
+ name, super->name);
+ inode = NULL;
+ }
+ else if (inode->st.st_size == 0)
+ inode->st.st_size = st->st_size;
+ }
+ }
+
+ /* remove trailing slashes */
+ for (tn = name + strlen (name) - 1; tn >= name && IS_PATH_SEP (*tn); tn--)
+ *tn = '\0';
+
+ tn = strrchr (name, PATH_SEP);
+ if (tn == NULL)
+ tn = name;
+ else if (tn == name + 1)
+ {
+ /* started with "./" -- directory in the root of archive */
+ tn++;
+ }
+ else
+ {
+ *tn = '\0';
+ root = vfs_s_find_inode (me, super, name, LINK_FOLLOW, FL_MKDIR);
+ *tn = PATH_SEP;
+ tn++;
+ }
+
+ entry = VFS_SUBCLASS (me)->find_entry (me, root, tn, LINK_FOLLOW, FL_NONE); /* In case entry is already there */
+
+ if (entry != NULL)
+ {
+ /* This shouldn't happen! (well, it can happen if there is a record for a
+ file and than a record for a directory it is in; cpio would die with
+ 'No such file or directory' is such case) */
+
+ if (!S_ISDIR (entry->ino->st.st_mode))
+ {
+ /* This can be considered archive inconsistency */
+ message (D_ERROR, MSG_ERROR,
+ _("%s contains duplicate entries! Skipping!"), super->name);
+ }
+ else
+ {
+ entry->ino->st.st_mode = st->st_mode;
+ entry->ino->st.st_uid = st->st_uid;
+ entry->ino->st.st_gid = st->st_gid;
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ entry->ino->st.st_atim = st->st_atim;
+ entry->ino->st.st_mtim = st->st_mtim;
+ entry->ino->st.st_ctim = st->st_ctim;
+#else
+ entry->ino->st.st_atime = st->st_atime;
+ entry->ino->st.st_mtime = st->st_mtime;
+ entry->ino->st.st_ctime = st->st_ctime;
+#endif
+ }
+
+ g_free (name);
+ }
+ else
+ { /* !entry */
+ /* root == NULL can be in the following case:
+ * a/b/c -> d
+ * where 'a/b' is the stale link and therefore root of 'c' cannot be found in the archive
+ */
+ if (root != NULL)
+ {
+ if (inode == NULL)
+ {
+ inode = vfs_s_new_inode (me, super, st);
+ if ((st->st_nlink > 0) && ((arch->type == CPIO_NEWC) || (arch->type == CPIO_CRC)))
+ {
+ /* For case of hardlinked files */
+ defer_inode *i;
+
+ i = g_new (defer_inode, 1);
+ i->inumber = st->st_ino;
+ i->device = st->st_dev;
+ i->inode = inode;
+
+ arch->deferred = g_slist_prepend (arch->deferred, i);
+ }
+ }
+
+ if (st->st_size != 0)
+ inode->data_offset = CPIO_POS (super);
+
+ entry = vfs_s_new_entry (me, tn, inode);
+ vfs_s_insert_entry (me, root, entry);
+ }
+
+ g_free (name);
+
+ if (!S_ISLNK (st->st_mode))
+ CPIO_SEEK_CUR (super, st->st_size);
+ else
+ {
+ if (inode != NULL)
+ {
+ /* FIXME: do we must read from arch->fd in case of inode != NULL only or in any case? */
+
+ inode->linkname = g_malloc (st->st_size + 1);
+
+ if (mc_read (arch->fd, inode->linkname, st->st_size) < st->st_size)
+ {
+ inode->linkname[0] = '\0';
+ return STATUS_EOF;
+ }
+
+ inode->linkname[st->st_size] = '\0'; /* Linkname stored without terminating \0 !!! */
+ }
+
+ CPIO_POS (super) += st->st_size;
+ cpio_skip_padding (super);
+ }
+ } /* !entry */
+
+ return STATUS_OK;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+cpio_read_bin_head (struct vfs_class *me, struct vfs_s_super *super)
+{
+ union
+ {
+ struct old_cpio_header buf;
+ short shorts[HEAD_LENGTH >> 1];
+ } u;
+
+ cpio_super_t *arch = CPIO_SUPER (super);
+ ssize_t len;
+ char *name;
+ struct stat st;
+
+ len = mc_read (arch->fd, (char *) &u.buf, HEAD_LENGTH);
+ if (len < HEAD_LENGTH)
+ return STATUS_EOF;
+ CPIO_POS (super) += len;
+ if (arch->type == CPIO_BINRE)
+ {
+ int i;
+ for (i = 0; i < (HEAD_LENGTH >> 1); i++)
+ u.shorts[i] = GUINT16_SWAP_LE_BE_CONSTANT (u.shorts[i]);
+ }
+
+ if (u.buf.c_magic != 070707 || u.buf.c_namesize == 0 || u.buf.c_namesize > MC_MAXPATHLEN)
+ {
+ message (D_ERROR, MSG_ERROR, _("Corrupted cpio header encountered in\n%s"), super->name);
+ return STATUS_FAIL;
+ }
+ name = g_malloc (u.buf.c_namesize);
+ len = mc_read (arch->fd, name, u.buf.c_namesize);
+ if (len < u.buf.c_namesize)
+ {
+ g_free (name);
+ return STATUS_EOF;
+ }
+ name[u.buf.c_namesize - 1] = '\0';
+ CPIO_POS (super) += len;
+ cpio_skip_padding (super);
+
+ if (!strcmp ("TRAILER!!!", name))
+ { /* We got to the last record */
+ g_free (name);
+ return STATUS_TRAIL;
+ }
+
+ st.st_dev = u.buf.c_dev;
+ st.st_ino = u.buf.c_ino;
+ st.st_mode = u.buf.c_mode;
+ st.st_nlink = u.buf.c_nlink;
+ st.st_uid = u.buf.c_uid;
+ st.st_gid = u.buf.c_gid;
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ st.st_rdev = u.buf.c_rdev;
+#endif
+ st.st_size = ((off_t) u.buf.c_filesizes[0] << 16) | u.buf.c_filesizes[1];
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ st.st_atim.tv_nsec = st.st_mtim.tv_nsec = st.st_ctim.tv_nsec = 0;
+#endif
+ st.st_atime = st.st_mtime = st.st_ctime =
+ ((time_t) u.buf.c_mtimes[0] << 16) | u.buf.c_mtimes[1];
+
+ return cpio_create_entry (me, super, &st, name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#undef HEAD_LENGTH
+#define HEAD_LENGTH (76)
+
+static ssize_t
+cpio_read_oldc_head (struct vfs_class *me, struct vfs_s_super *super)
+{
+ cpio_super_t *arch = CPIO_SUPER (super);
+ struct new_cpio_header hd;
+ union
+ {
+ struct stat st;
+ char buf[HEAD_LENGTH + 1];
+ } u;
+ ssize_t len;
+ char *name;
+
+ if (mc_read (arch->fd, u.buf, HEAD_LENGTH) != HEAD_LENGTH)
+ return STATUS_EOF;
+ CPIO_POS (super) += HEAD_LENGTH;
+ u.buf[HEAD_LENGTH] = 0;
+
+ if (sscanf (u.buf, "070707%6lo%6lo%6lo%6lo%6lo%6lo%6lo%11lo%6lo%11lo",
+ (unsigned long *) &hd.c_dev, &hd.c_ino, &hd.c_mode, &hd.c_uid, &hd.c_gid,
+ &hd.c_nlink, (unsigned long *) &hd.c_rdev, &hd.c_mtime,
+ &hd.c_namesize, &hd.c_filesize) < 10)
+ {
+ message (D_ERROR, MSG_ERROR, _("Corrupted cpio header encountered in\n%s"), super->name);
+ return STATUS_FAIL;
+ }
+
+ if (hd.c_namesize == 0 || hd.c_namesize > MC_MAXPATHLEN)
+ {
+ message (D_ERROR, MSG_ERROR, _("Corrupted cpio header encountered in\n%s"), super->name);
+ return STATUS_FAIL;
+ }
+ name = g_malloc (hd.c_namesize);
+ len = mc_read (arch->fd, name, hd.c_namesize);
+ if ((len == -1) || ((unsigned long) len < hd.c_namesize))
+ {
+ g_free (name);
+ return STATUS_EOF;
+ }
+ name[hd.c_namesize - 1] = '\0';
+ CPIO_POS (super) += len;
+ cpio_skip_padding (super);
+
+ if (!strcmp ("TRAILER!!!", name))
+ { /* We got to the last record */
+ g_free (name);
+ return STATUS_TRAIL;
+ }
+
+ u.st.st_dev = hd.c_dev;
+ u.st.st_ino = hd.c_ino;
+ u.st.st_mode = hd.c_mode;
+ u.st.st_nlink = hd.c_nlink;
+ u.st.st_uid = hd.c_uid;
+ u.st.st_gid = hd.c_gid;
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ u.st.st_rdev = hd.c_rdev;
+#endif
+ u.st.st_size = hd.c_filesize;
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ u.st.st_atim.tv_nsec = u.st.st_mtim.tv_nsec = u.st.st_ctim.tv_nsec = 0;
+#endif
+ u.st.st_atime = u.st.st_mtime = u.st.st_ctime = hd.c_mtime;
+
+ return cpio_create_entry (me, super, &u.st, name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#undef HEAD_LENGTH
+#define HEAD_LENGTH (110)
+
+static ssize_t
+cpio_read_crc_head (struct vfs_class *me, struct vfs_s_super *super)
+{
+ cpio_super_t *arch = CPIO_SUPER (super);
+ struct new_cpio_header hd;
+ union
+ {
+ struct stat st;
+ char buf[HEAD_LENGTH + 1];
+ } u;
+ ssize_t len;
+ char *name;
+
+ if (mc_read (arch->fd, u.buf, HEAD_LENGTH) != HEAD_LENGTH)
+ return STATUS_EOF;
+
+ CPIO_POS (super) += HEAD_LENGTH;
+ u.buf[HEAD_LENGTH] = '\0';
+
+ if (sscanf (u.buf, "%6ho%8lx%8lx%8lx%8lx%8lx%8lx%8lx%8lx%8lx%8lx%8lx%8lx%8lx",
+ &hd.c_magic, &hd.c_ino, &hd.c_mode, &hd.c_uid, &hd.c_gid,
+ &hd.c_nlink, &hd.c_mtime, &hd.c_filesize,
+ (unsigned long *) &hd.c_dev, (unsigned long *) &hd.c_devmin,
+ (unsigned long *) &hd.c_rdev, (unsigned long *) &hd.c_rdevmin,
+ &hd.c_namesize, &hd.c_chksum) < 14)
+ {
+ message (D_ERROR, MSG_ERROR, _("Corrupted cpio header encountered in\n%s"), super->name);
+ return STATUS_FAIL;
+ }
+
+ if ((arch->type == CPIO_NEWC && hd.c_magic != 070701) ||
+ (arch->type == CPIO_CRC && hd.c_magic != 070702))
+ return STATUS_FAIL;
+
+ if (hd.c_namesize == 0 || hd.c_namesize > MC_MAXPATHLEN)
+ {
+ message (D_ERROR, MSG_ERROR, _("Corrupted cpio header encountered in\n%s"), super->name);
+ return STATUS_FAIL;
+ }
+
+ name = g_malloc (hd.c_namesize);
+ len = mc_read (arch->fd, name, hd.c_namesize);
+
+ if ((len == -1) || ((unsigned long) len < hd.c_namesize))
+ {
+ g_free (name);
+ return STATUS_EOF;
+ }
+ name[hd.c_namesize - 1] = '\0';
+ CPIO_POS (super) += len;
+ cpio_skip_padding (super);
+
+ if (strcmp ("TRAILER!!!", name) == 0)
+ { /* We got to the last record */
+ g_free (name);
+ return STATUS_TRAIL;
+ }
+
+ u.st.st_dev = makedev (hd.c_dev, hd.c_devmin);
+ u.st.st_ino = hd.c_ino;
+ u.st.st_mode = hd.c_mode;
+ u.st.st_nlink = hd.c_nlink;
+ u.st.st_uid = hd.c_uid;
+ u.st.st_gid = hd.c_gid;
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ u.st.st_rdev = makedev (hd.c_rdev, hd.c_rdevmin);
+#endif
+ u.st.st_size = hd.c_filesize;
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ u.st.st_atim.tv_nsec = u.st.st_mtim.tv_nsec = u.st.st_ctim.tv_nsec = 0;
+#endif
+ u.st.st_atime = u.st.st_mtime = u.st.st_ctime = hd.c_mtime;
+
+ return cpio_create_entry (me, super, &u.st, name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Need to CPIO_SEEK_CUR to skip the file at the end of add entry!!!! */
+
+static int
+cpio_open_archive (struct vfs_s_super *super, const vfs_path_t * vpath,
+ const vfs_path_element_t * vpath_element)
+{
+ (void) vpath_element;
+
+ if (cpio_open_cpio_file (vpath_element->class, super, vpath) == -1)
+ return -1;
+
+ while (TRUE)
+ {
+ ssize_t status;
+
+ status = cpio_read_head (vpath_element->class, super);
+ if (status < 0)
+ return (-1);
+
+ switch (status)
+ {
+ case STATUS_EOF:
+ {
+ message (D_ERROR, MSG_ERROR, _("Unexpected end of file\n%s"),
+ vfs_path_as_str (vpath));
+ return 0;
+ }
+ case STATUS_OK:
+ continue;
+ case STATUS_TRAIL:
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Remaining functions are exactly same as for tarfs (and were in fact just copied) */
+
+static void *
+cpio_super_check (const vfs_path_t * vpath)
+{
+ static struct stat sb;
+ int stat_result;
+
+ stat_result = mc_stat (vpath, &sb);
+ return (stat_result == 0 ? &sb : NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+cpio_super_same (const vfs_path_element_t * vpath_element, struct vfs_s_super *parc,
+ const vfs_path_t * vpath, void *cookie)
+{
+ struct stat *archive_stat = cookie; /* stat of main archive */
+
+ (void) vpath_element;
+
+ if (strcmp (parc->name, vfs_path_as_str (vpath)))
+ return 0;
+
+ /* Has the cached archive been changed on the disk? */
+ if (parc != NULL && CPIO_SUPER (parc)->st.st_mtime < archive_stat->st_mtime)
+ {
+ /* Yes, reload! */
+ vfs_cpiofs_ops->free ((vfsid) parc);
+ vfs_rmstamp (vfs_cpiofs_ops, (vfsid) parc);
+ return 2;
+ }
+ /* Hasn't been modified, give it a new timeout */
+ vfs_stamp (vfs_cpiofs_ops, (vfsid) parc);
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+cpio_read (void *fh, char *buffer, size_t count)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ struct vfs_class *me = VFS_FILE_HANDLER_SUPER (fh)->me;
+ int fd = CPIO_SUPER (VFS_FILE_HANDLER_SUPER (fh))->fd;
+ off_t begin = file->ino->data_offset;
+ ssize_t res;
+
+ if (mc_lseek (fd, begin + file->pos, SEEK_SET) != begin + file->pos)
+ ERRNOR (EIO, -1);
+
+ count = MIN (count, (size_t) (file->ino->st.st_size - file->pos));
+
+ res = mc_read (fd, buffer, count);
+ if (res == -1)
+ ERRNOR (errno, -1);
+
+ file->pos += res;
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+cpio_fh_open (struct vfs_class *me, vfs_file_handler_t * fh, int flags, mode_t mode)
+{
+ (void) fh;
+ (void) mode;
+
+ if ((flags & O_ACCMODE) != O_RDONLY)
+ ERRNOR (EROFS, -1);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init_cpiofs (void)
+{
+ /* FIXME: cpiofs used own temp files */
+ vfs_init_subclass (&cpio_subclass, "cpiofs", VFSF_READONLY, "ucpio");
+ vfs_cpiofs_ops->read = cpio_read;
+ vfs_cpiofs_ops->setctl = NULL;
+ cpio_subclass.archive_check = cpio_super_check;
+ cpio_subclass.archive_same = cpio_super_same;
+ cpio_subclass.new_archive = cpio_new_archive;
+ cpio_subclass.open_archive = cpio_open_archive;
+ cpio_subclass.free_archive = cpio_free_archive;
+ cpio_subclass.fh_open = cpio_fh_open;
+ vfs_register_class (vfs_cpiofs_ops);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/cpio/cpio.h b/src/vfs/cpio/cpio.h
new file mode 100644
index 0000000..a00756a
--- /dev/null
+++ b/src/vfs/cpio/cpio.h
@@ -0,0 +1,18 @@
+#ifndef MC__VFS_CPIO_H
+#define MC__VFS_CPIO_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void vfs_init_cpiofs (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__VFS_CPIO_H */
diff --git a/src/vfs/extfs/Makefile.am b/src/vfs/extfs/Makefile.am
new file mode 100644
index 0000000..39762a3
--- /dev/null
+++ b/src/vfs/extfs/Makefile.am
@@ -0,0 +1,12 @@
+SUBDIRS = helpers
+DIST_SUBDIRS = helpers
+
+AM_CPPFLAGS = \
+ -DLIBEXECDIR=\""$(libexecdir)/@PACKAGE@/"\" \
+ $(GLIB_CFLAGS) \
+ -I$(top_srcdir)
+
+noinst_LTLIBRARIES = libvfs-extfs.la
+
+libvfs_extfs_la_SOURCES = \
+ extfs.c extfs.h
diff --git a/src/vfs/extfs/Makefile.in b/src/vfs/extfs/Makefile.in
new file mode 100644
index 0000000..317af30
--- /dev/null
+++ b/src/vfs/extfs/Makefile.in
@@ -0,0 +1,856 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/vfs/extfs
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libvfs_extfs_la_LIBADD =
+am_libvfs_extfs_la_OBJECTS = extfs.lo
+libvfs_extfs_la_OBJECTS = $(am_libvfs_extfs_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/extfs.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libvfs_extfs_la_SOURCES)
+DIST_SOURCES = $(libvfs_extfs_la_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = helpers
+DIST_SUBDIRS = helpers
+AM_CPPFLAGS = \
+ -DLIBEXECDIR=\""$(libexecdir)/@PACKAGE@/"\" \
+ $(GLIB_CFLAGS) \
+ -I$(top_srcdir)
+
+noinst_LTLIBRARIES = libvfs-extfs.la
+libvfs_extfs_la_SOURCES = \
+ extfs.c extfs.h
+
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/vfs/extfs/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/extfs/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libvfs-extfs.la: $(libvfs_extfs_la_OBJECTS) $(libvfs_extfs_la_DEPENDENCIES) $(EXTRA_libvfs_extfs_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libvfs_extfs_la_OBJECTS) $(libvfs_extfs_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/extfs.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(LTLIBRARIES)
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/extfs.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/extfs.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/vfs/extfs/extfs.c b/src/vfs/extfs/extfs.c
new file mode 100644
index 0000000..d6ef7af
--- /dev/null
+++ b/src/vfs/extfs/extfs.c
@@ -0,0 +1,1722 @@
+/*
+ Virtual File System: External file system.
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Jakub Jelinek, 1995
+ Pavel Machek, 1998
+ Andrew T. Veliath, 1999
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * \brief Source: Virtual File System: External file system
+ * \author Jakub Jelinek
+ * \author Pavel Machek
+ * \author Andrew T. Veliath
+ * \date 1995, 1998, 1999
+ */
+
+/* Namespace: init_extfs */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/wait.h>
+
+#include "lib/global.h"
+#include "lib/fileloc.h"
+#include "lib/mcconfig.h"
+#include "lib/util.h"
+#include "lib/widget.h" /* message() */
+
+#include "src/execute.h" /* For shell_execute */
+
+#include "lib/vfs/vfs.h"
+#include "lib/vfs/utilvfs.h"
+#include "lib/vfs/xdirentry.h"
+#include "lib/vfs/gc.h" /* vfs_rmstamp */
+
+#include "extfs.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#undef ERRNOR
+#define ERRNOR(x,y) do { my_errno = x; return y; } while(0)
+
+#define RECORDSIZE 512
+
+#define EXTFS_SUPER(a) ((struct extfs_super_t *) (a))
+
+/*** file scope type declarations ****************************************************************/
+
+struct extfs_super_t
+{
+ struct vfs_s_super base; /* base class */
+
+ int fstype;
+ char *local_name;
+ struct stat local_stat;
+ dev_t rdev;
+};
+
+typedef struct
+{
+ char *path;
+ char *prefix;
+ gboolean need_archive;
+} extfs_plugin_info_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static struct vfs_s_entry *extfs_resolve_symlinks_int (struct vfs_s_entry *entry, GSList * list);
+
+/*** file scope variables ************************************************************************/
+
+static GArray *extfs_plugins = NULL;
+
+static gboolean errloop;
+static gboolean notadir;
+
+static struct vfs_s_subclass extfs_subclass;
+static struct vfs_class *vfs_extfs_ops = VFS_CLASS (&extfs_subclass);
+
+static int my_errno = 0;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static struct extfs_super_t *
+extfs_super_new (struct vfs_class *me, const char *name, const vfs_path_t * local_name_vpath,
+ int fstype)
+{
+ struct extfs_super_t *super;
+ struct vfs_s_super *vsuper;
+
+ super = g_new0 (struct extfs_super_t, 1);
+ vsuper = VFS_SUPER (super);
+
+ vsuper->me = me;
+ vsuper->name = g_strdup (name);
+
+ super->fstype = fstype;
+
+ if (local_name_vpath != NULL)
+ {
+ super->local_name = g_strdup (vfs_path_get_last_path_str (local_name_vpath));
+ mc_stat (local_name_vpath, &super->local_stat);
+ }
+
+ VFS_SUBCLASS (me)->supers = g_list_prepend (VFS_SUBCLASS (me)->supers, super);
+
+ return super;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* unlike vfs_s_new_entry(), inode->ent is kept */
+static struct vfs_s_entry *
+extfs_entry_new (struct vfs_class *me, const char *name, struct vfs_s_inode *inode)
+{
+ struct vfs_s_entry *entry;
+
+ (void) me;
+
+ entry = g_new0 (struct vfs_s_entry, 1);
+
+ entry->name = g_strdup (name);
+ entry->ino = inode;
+
+ return entry;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+extfs_fill_name (void *data, void *user_data)
+{
+ struct vfs_s_super *a = VFS_SUPER (data);
+ fill_names_f func = (fill_names_f) user_data;
+ extfs_plugin_info_t *info;
+ char *name;
+
+ info = &g_array_index (extfs_plugins, extfs_plugin_info_t, EXTFS_SUPER (a)->fstype);
+ name =
+ g_strconcat (a->name != NULL ? a->name : "", PATH_SEP_STR, info->prefix,
+ VFS_PATH_URL_DELIMITER, (char *) NULL);
+ func (name);
+ g_free (name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gint
+extfs_cmp_archive (const void *a, const void *b)
+{
+ const struct vfs_s_super *ar = (const struct vfs_s_super *) a;
+ const char *archive_name = (const char *) b;
+
+ return (ar->name != NULL && strcmp (ar->name, archive_name) == 0) ? 0 : 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_entry *
+extfs_generate_entry (struct extfs_super_t *archive, const char *name, struct vfs_s_inode *parent,
+ mode_t mode)
+{
+ struct vfs_class *me = VFS_SUPER (archive)->me;
+ struct stat st;
+ mode_t myumask;
+ struct vfs_s_inode *inode;
+ struct vfs_s_entry *entry;
+
+ memset (&st, 0, sizeof (st));
+ st.st_ino = VFS_SUPER (archive)->ino_usage++;
+ st.st_dev = archive->rdev;
+ myumask = umask (022);
+ umask (myumask);
+ st.st_mode = mode & ~myumask;
+ st.st_uid = getuid ();
+ st.st_gid = getgid ();
+ st.st_mtime = time (NULL);
+ st.st_atime = st.st_mtime;
+ st.st_ctime = st.st_mtime;
+ st.st_nlink = 1;
+
+ inode = vfs_s_new_inode (me, VFS_SUPER (archive), &st);
+ entry = vfs_s_new_entry (me, name, inode);
+ if (parent != NULL)
+ vfs_s_insert_entry (me, parent, entry);
+
+ return entry;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_entry *
+extfs_find_entry_int (struct vfs_s_inode *dir, const char *name, GSList * list, int flags)
+{
+ struct vfs_s_entry *pent, *pdir;
+ const char *p, *name_end;
+ char *q;
+ char c = PATH_SEP;
+ struct extfs_super_t *super;
+
+ if (g_path_is_absolute (name))
+ {
+ /* Handle absolute paths */
+ name = g_path_skip_root (name);
+ dir = dir->super->root;
+ }
+
+ super = EXTFS_SUPER (dir->super);
+ pent = dir->ent;
+ p = name;
+ name_end = name + strlen (name);
+
+ while ((pent != NULL) && (c != '\0') && (*p != '\0'))
+ {
+ q = strchr (p, PATH_SEP);
+ if (q == NULL)
+ q = (char *) name_end;
+
+ c = *q;
+ *q = '\0';
+
+ if (DIR_IS_DOTDOT (p))
+ pent = pent->dir != NULL ? pent->dir->ent : NULL;
+ else
+ {
+ GList *pl;
+
+ pent = extfs_resolve_symlinks_int (pent, list);
+ if (pent == NULL)
+ {
+ *q = c;
+ return NULL;
+ }
+
+ if (!S_ISDIR (pent->ino->st.st_mode))
+ {
+ *q = c;
+ notadir = TRUE;
+ return NULL;
+ }
+
+ pdir = pent;
+ pl = g_queue_find_custom (pent->ino->subdir, p, vfs_s_entry_compare);
+ pent = pl != NULL ? VFS_ENTRY (pl->data) : NULL;
+ if (pent != NULL && q + 1 > name_end)
+ {
+ /* Hack: I keep the original semanthic unless q+1 would break in the strchr */
+ *q = c;
+ notadir = !S_ISDIR (pent->ino->st.st_mode);
+ return pent;
+ }
+
+ /* When we load archive, we create automagically non-existent directories */
+ if (pent == NULL && (flags & FL_MKDIR) != 0)
+ pent = extfs_generate_entry (super, p, pdir->ino, S_IFDIR | 0777);
+ if (pent == NULL && (flags & FL_MKFILE) != 0)
+ pent = extfs_generate_entry (super, p, pdir->ino, S_IFREG | 0666);
+ }
+
+ /* Next iteration */
+ *q = c;
+ if (c != '\0')
+ p = q + 1;
+ }
+ if (pent == NULL)
+ my_errno = ENOENT;
+ return pent;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_entry *
+extfs_find_entry (struct vfs_s_inode *dir, const char *name, int flags)
+{
+ struct vfs_s_entry *res;
+
+ errloop = FALSE;
+ notadir = FALSE;
+
+ res = extfs_find_entry_int (dir, name, NULL, flags);
+ if (res == NULL)
+ {
+ if (errloop)
+ my_errno = ELOOP;
+ else if (notadir)
+ my_errno = ENOTDIR;
+ }
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+extfs_fill_names (struct vfs_class *me, fill_names_f func)
+{
+ g_list_foreach (VFS_SUBCLASS (me)->supers, extfs_fill_name, func);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Create this function because VFSF_USETMP flag is not used in extfs */
+static void
+extfs_free_inode (struct vfs_class *me, struct vfs_s_inode *ino)
+{
+ (void) me;
+
+ if (ino->localname != NULL)
+ {
+ unlink (ino->localname);
+ MC_PTR_FREE (ino->localname);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+extfs_free_archive (struct vfs_class *me, struct vfs_s_super *psup)
+{
+ struct extfs_super_t *archive = EXTFS_SUPER (psup);
+
+ (void) me;
+
+ if (archive->local_name != NULL)
+ {
+ struct stat my;
+ vfs_path_t *local_name_vpath, *name_vpath;
+
+ local_name_vpath = vfs_path_from_str (archive->local_name);
+ name_vpath = vfs_path_from_str (psup->name);
+ mc_stat (local_name_vpath, &my);
+ mc_ungetlocalcopy (name_vpath, local_name_vpath,
+ archive->local_stat.st_mtime != my.st_mtime);
+ vfs_path_free (local_name_vpath, TRUE);
+ vfs_path_free (name_vpath, TRUE);
+ g_free (archive->local_name);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline char *
+extfs_skip_leading_dotslash (char *s)
+{
+ /* Skip leading "./" (if present).
+ * Some programs don't understand it:
+ *
+ * $ zip file.zip ./-file2.txt file1.txt
+ * adding: -file2.txt (stored 0%)
+ * adding: file1.txt (stored 0%)
+ * $ /usr/lib/mc/extfs.d/uzip copyout file.zip ./-file2.txt ./tmp-file2.txt
+ * caution: filename not matched: ./-file2.txt
+ */
+ if (s[0] == '.' && s[1] == PATH_SEP)
+ s += 2;
+
+ return s;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_add_file (struct extfs_super_t *archive, const char *file_name)
+{
+ struct vfs_s_super *super = VFS_SUPER (archive);
+ struct stat hstat;
+ char *current_file_name = NULL, *current_link_name = NULL;
+ int ret = 0;
+
+ if (vfs_parse_ls_lga (file_name, &hstat, &current_file_name, &current_link_name, NULL))
+ {
+ char *cfn = current_file_name;
+
+ if (*cfn != '\0')
+ {
+ struct vfs_s_entry *entry;
+ struct vfs_s_entry *pent = NULL;
+ struct vfs_s_inode *inode;
+ char *p, *q;
+
+ cfn = extfs_skip_leading_dotslash (cfn);
+ if (IS_PATH_SEP (*cfn))
+ cfn++;
+ p = strchr (cfn, '\0');
+ if (p != cfn && IS_PATH_SEP (p[-1]))
+ p[-1] = '\0';
+ p = strrchr (cfn, PATH_SEP);
+ if (p == NULL)
+ {
+ p = cfn;
+ q = strchr (cfn, '\0');
+ }
+ else
+ {
+ *(p++) = '\0';
+ q = cfn;
+ }
+
+ if (*q != '\0')
+ {
+ pent = extfs_find_entry (super->root, q, FL_MKDIR);
+ if (pent == NULL)
+ {
+ ret = -1;
+ goto done;
+ }
+ }
+
+ if (pent != NULL)
+ {
+ entry = extfs_entry_new (super->me, p, pent->ino);
+ entry->dir = pent->ino;
+ g_queue_push_tail (pent->ino->subdir, entry);
+ }
+ else
+ {
+ entry = extfs_entry_new (super->me, p, super->root);
+ entry->dir = super->root;
+ g_queue_push_tail (super->root->subdir, entry);
+ }
+
+ if (!S_ISLNK (hstat.st_mode) && (current_link_name != NULL))
+ {
+ pent = extfs_find_entry (super->root, current_link_name, FL_NONE);
+ if (pent == NULL)
+ {
+ ret = -1;
+ goto done;
+ }
+
+ pent->ino->st.st_nlink++;
+ entry->ino = pent->ino;
+ }
+ else
+ {
+ struct stat st;
+
+ memset (&st, 0, sizeof (st));
+ st.st_ino = super->ino_usage++;
+ st.st_nlink = 1;
+ st.st_dev = archive->rdev;
+ st.st_mode = hstat.st_mode;
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ st.st_rdev = hstat.st_rdev;
+#endif
+ st.st_uid = hstat.st_uid;
+ st.st_gid = hstat.st_gid;
+ st.st_size = hstat.st_size;
+ st.st_mtime = hstat.st_mtime;
+ st.st_atime = hstat.st_atime;
+ st.st_ctime = hstat.st_ctime;
+
+ if (current_link_name == NULL && S_ISLNK (hstat.st_mode))
+ st.st_mode &= ~S_IFLNK; /* You *DON'T* want to do this always */
+
+ inode = vfs_s_new_inode (super->me, super, &st);
+ inode->ent = entry;
+ entry->ino = inode;
+
+ if (current_link_name != NULL && S_ISLNK (hstat.st_mode))
+ {
+ inode->linkname = current_link_name;
+ current_link_name = NULL;
+ }
+ }
+ }
+
+ done:
+ g_free (current_file_name);
+ g_free (current_link_name);
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static mc_pipe_t *
+extfs_open_archive (int fstype, const char *name, struct extfs_super_t **pparc, GError ** error)
+{
+ const extfs_plugin_info_t *info;
+ static dev_t archive_counter = 0;
+ mc_pipe_t *result = NULL;
+ mode_t mode;
+ char *cmd;
+ struct stat mystat;
+ struct extfs_super_t *current_archive;
+ struct vfs_s_entry *root_entry;
+ char *tmp = NULL;
+ vfs_path_t *local_name_vpath = NULL;
+ vfs_path_t *name_vpath;
+
+ memset (&mystat, 0, sizeof (mystat));
+
+ name_vpath = vfs_path_from_str (name);
+ info = &g_array_index (extfs_plugins, extfs_plugin_info_t, fstype);
+
+ if (info->need_archive)
+ {
+ if (mc_stat (name_vpath, &mystat) == -1)
+ goto ret;
+
+ if (!vfs_file_is_local (name_vpath))
+ {
+ local_name_vpath = mc_getlocalcopy (name_vpath);
+ if (local_name_vpath == NULL)
+ goto ret;
+ }
+
+ tmp = name_quote (vfs_path_get_last_path_str (name_vpath), FALSE);
+ }
+
+ cmd = g_strconcat (info->path, info->prefix, " list ",
+ vfs_path_get_last_path_str (local_name_vpath) != NULL ?
+ vfs_path_get_last_path_str (local_name_vpath) : tmp, (char *) NULL);
+ g_free (tmp);
+
+ result = mc_popen (cmd, TRUE, TRUE, error);
+ g_free (cmd);
+
+ if (result == NULL)
+ {
+ if (local_name_vpath != NULL)
+ {
+ mc_ungetlocalcopy (name_vpath, local_name_vpath, FALSE);
+ vfs_path_free (local_name_vpath, TRUE);
+ }
+ goto ret;
+ }
+
+ current_archive = extfs_super_new (vfs_extfs_ops, name, local_name_vpath, fstype);
+ current_archive->rdev = archive_counter++;
+ vfs_path_free (local_name_vpath, TRUE);
+
+ mode = mystat.st_mode & 07777;
+ if (mode & 0400)
+ mode |= 0100;
+ if (mode & 0040)
+ mode |= 0010;
+ if (mode & 0004)
+ mode |= 0001;
+ mode |= S_IFDIR;
+
+ root_entry = extfs_generate_entry (current_archive, PATH_SEP_STR, NULL, mode);
+ root_entry->ino->st.st_uid = mystat.st_uid;
+ root_entry->ino->st.st_gid = mystat.st_gid;
+ root_entry->ino->st.st_atime = mystat.st_atime;
+ root_entry->ino->st.st_ctime = mystat.st_ctime;
+ root_entry->ino->st.st_mtime = mystat.st_mtime;
+ root_entry->ino->ent = root_entry;
+ VFS_SUPER (current_archive)->root = root_entry->ino;
+
+ *pparc = current_archive;
+
+ ret:
+ vfs_path_free (name_vpath, TRUE);
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Main loop for reading an archive.
+ * Return 0 on success, -1 on error.
+ */
+
+static int
+extfs_read_archive (mc_pipe_t * pip, struct extfs_super_t *archive, GError ** error)
+{
+ int ret = 0;
+ GString *buffer;
+ GString *err_msg = NULL;
+ GString *remain_file_name = NULL;
+
+ while (ret != -1)
+ {
+ /* init buffers before call of mc_pread() */
+ pip->out.len = MC_PIPE_BUFSIZE;
+ pip->err.len = MC_PIPE_BUFSIZE;
+
+ mc_pread (pip, error);
+
+ if (*error != NULL)
+ return (-1);
+
+ if (pip->err.len > 0)
+ {
+ /* join errors/warnings */
+ if (err_msg == NULL)
+ err_msg = g_string_new_len (pip->err.buf, pip->err.len);
+ else
+ {
+ if (err_msg->str[err_msg->len - 1] != '\n')
+ g_string_append_c (err_msg, '\n');
+ g_string_append_len (err_msg, pip->err.buf, pip->err.len);
+ }
+ }
+
+ if (pip->out.len == MC_PIPE_STREAM_EOF)
+ break;
+
+ if (pip->out.len == 0)
+ continue;
+
+ if (pip->out.len == MC_PIPE_ERROR_READ)
+ return (-1);
+
+ while (ret != -1 && (buffer = mc_pstream_get_string (&pip->out)) != NULL)
+ {
+ /* handle a \n-separated file list */
+
+ if (buffer->str[buffer->len - 1] == '\n')
+ {
+ /* entire file name or last chunk */
+
+ g_string_truncate (buffer, buffer->len - 1);
+
+ /* join filename chunks */
+ if (remain_file_name != NULL)
+ {
+ g_string_append_len (remain_file_name, buffer->str, buffer->len);
+ g_string_free (buffer, TRUE);
+ buffer = remain_file_name;
+ remain_file_name = NULL;
+ }
+ }
+ else
+ {
+ /* first or middle chunk of file name */
+
+ if (remain_file_name == NULL)
+ remain_file_name = buffer;
+ else
+ {
+ g_string_append_len (remain_file_name, buffer->str, buffer->len);
+ g_string_free (buffer, TRUE);
+ }
+
+ continue;
+ }
+
+ ret = extfs_add_file (archive, buffer->str);
+
+ g_string_free (buffer, TRUE);
+ }
+ }
+
+ if (remain_file_name != NULL)
+ g_string_free (remain_file_name, TRUE);
+
+ if (err_msg != NULL)
+ {
+ if (*error == NULL)
+ mc_propagate_error (error, 0, "%s", err_msg->str);
+
+ g_string_free (err_msg, TRUE);
+ }
+ else if (ret == -1)
+ mc_propagate_error (error, 0, "%s", _("Inconsistent archive"));
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_which (struct vfs_class *me, const char *path)
+{
+ size_t path_len;
+ size_t i;
+
+ (void) me;
+
+ path_len = strlen (path);
+
+ for (i = 0; i < extfs_plugins->len; i++)
+ {
+ extfs_plugin_info_t *info;
+
+ info = &g_array_index (extfs_plugins, extfs_plugin_info_t, i);
+
+ if ((strncmp (path, info->prefix, path_len) == 0)
+ && ((info->prefix[path_len] == '\0') || (info->prefix[path_len] == '+')))
+ return i;
+ }
+ return -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_open_and_read_archive (int fstype, const char *name, struct extfs_super_t **archive)
+{
+ int result = -1;
+ struct extfs_super_t *a;
+ mc_pipe_t *pip;
+ GError *error = NULL;
+
+ pip = extfs_open_archive (fstype, name, archive, &error);
+
+ a = *archive;
+
+ if (pip == NULL)
+ {
+ const extfs_plugin_info_t *info;
+
+ info = &g_array_index (extfs_plugins, extfs_plugin_info_t, fstype);
+ message (D_ERROR, MSG_ERROR, _("Cannot open %s archive\n%s:\n%s"), info->prefix, name,
+ error->message);
+ g_error_free (error);
+ }
+ else
+ {
+ result = extfs_read_archive (pip, a, &error);
+
+ if (result != 0)
+ VFS_SUPER (a)->me->free (VFS_SUPER (a));
+
+ if (error != NULL)
+ {
+ message (D_ERROR, MSG_ERROR, _("EXTFS virtual file system:\n%s"), error->message);
+ g_error_free (error);
+ }
+
+ mc_pclose (pip, NULL);
+ }
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Dissect the path and create corresponding superblock.
+ */
+static const char *
+extfs_get_path (const vfs_path_t * vpath, struct extfs_super_t **archive, int flags)
+{
+ char *archive_name;
+ int result = -1;
+ GList *parc;
+ int fstype;
+ const vfs_path_element_t *path_element;
+ struct extfs_super_t *a = NULL;
+
+ path_element = vfs_path_get_by_index (vpath, -1);
+
+ fstype = extfs_which (path_element->class, path_element->vfs_prefix);
+ if (fstype == -1)
+ return NULL;
+
+ archive_name = vfs_path_to_str_elements_count (vpath, -1);
+
+ /* All filesystems should have some local archive, at least it can be PATH_SEP ('/'). */
+ parc = g_list_find_custom (extfs_subclass.supers, archive_name, extfs_cmp_archive);
+ if (parc != NULL)
+ {
+ a = EXTFS_SUPER (parc->data);
+ vfs_stamp (vfs_extfs_ops, (vfsid) a);
+ g_free (archive_name);
+ }
+ else
+ {
+ if ((flags & FL_NO_OPEN) == 0)
+ result = extfs_open_and_read_archive (fstype, archive_name, &a);
+
+ g_free (archive_name);
+
+ if (result == -1)
+ {
+ path_element->class->verrno = EIO;
+ return NULL;
+ }
+ }
+
+ *archive = a;
+ return path_element->path;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Return allocated path (without leading slash) inside the archive */
+
+static char *
+extfs_get_path_from_entry (const struct vfs_s_entry *entry)
+{
+ const struct vfs_s_entry *e;
+ GString *localpath;
+
+ localpath = g_string_new ("");
+
+ for (e = entry; e->dir != NULL; e = e->dir->ent)
+ {
+ g_string_prepend (localpath, e->name);
+ if (e->dir->ent->dir != NULL)
+ g_string_prepend_c (localpath, PATH_SEP);
+ }
+
+ return g_string_free (localpath, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_entry *
+extfs_resolve_symlinks_int (struct vfs_s_entry *entry, GSList * list)
+{
+ struct vfs_s_entry *pent = NULL;
+
+ if (!S_ISLNK (entry->ino->st.st_mode))
+ return entry;
+
+ if (g_slist_find (list, entry) != NULL)
+ {
+ /* Here we protect us against symlink looping */
+ errloop = TRUE;
+ }
+ else
+ {
+ GSList *looping;
+
+ looping = g_slist_prepend (list, entry);
+ pent = extfs_find_entry_int (entry->dir, entry->ino->linkname, looping, FL_NONE);
+ looping = g_slist_delete_link (looping, looping);
+
+ if (pent == NULL)
+ my_errno = ENOENT;
+ }
+
+ return pent;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_entry *
+extfs_resolve_symlinks (struct vfs_s_entry *entry)
+{
+ struct vfs_s_entry *res;
+
+ errloop = FALSE;
+ notadir = FALSE;
+ res = extfs_resolve_symlinks_int (entry, NULL);
+ if (res == NULL)
+ {
+ if (errloop)
+ my_errno = ELOOP;
+ else if (notadir)
+ my_errno = ENOTDIR;
+ }
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+extfs_get_archive_name (const struct extfs_super_t *archive)
+{
+ const char *archive_name;
+
+ if (archive->local_name != NULL)
+ archive_name = archive->local_name;
+ else
+ archive_name = CONST_VFS_SUPER (archive)->name;
+
+ if (archive_name == NULL || *archive_name == '\0')
+ return g_strdup ("no_archive_name");
+ else
+ {
+ char *ret_str;
+ vfs_path_t *vpath;
+ const char *path;
+
+ vpath = vfs_path_from_str (archive_name);
+ path = vfs_path_get_last_path_str (vpath);
+ ret_str = g_strdup (path);
+ vfs_path_free (vpath, TRUE);
+ return ret_str;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Don't pass localname as NULL */
+
+static int
+extfs_cmd (const char *str_extfs_cmd, const struct extfs_super_t *archive,
+ const struct vfs_s_entry *entry, const char *localname)
+{
+ char *file;
+ char *quoted_file;
+ char *quoted_localname;
+ char *archive_name, *quoted_archive_name;
+ const extfs_plugin_info_t *info;
+ char *cmd;
+ int retval = 0;
+ GError *error = NULL;
+ mc_pipe_t *pip;
+
+ file = extfs_get_path_from_entry (entry);
+ quoted_file = name_quote (file, FALSE);
+ g_free (file);
+
+ /* Skip leading "./" (if present) added in name_quote() */
+ file = extfs_skip_leading_dotslash (quoted_file);
+
+ archive_name = extfs_get_archive_name (archive);
+ quoted_archive_name = name_quote (archive_name, FALSE);
+ g_free (archive_name);
+ quoted_localname = name_quote (localname, FALSE);
+ info = &g_array_index (extfs_plugins, extfs_plugin_info_t, archive->fstype);
+ cmd = g_strconcat (info->path, info->prefix, str_extfs_cmd,
+ quoted_archive_name, " ", file, " ", quoted_localname, (char *) NULL);
+ g_free (quoted_file);
+ g_free (quoted_localname);
+ g_free (quoted_archive_name);
+
+ /* don't read stdout */
+ pip = mc_popen (cmd, FALSE, TRUE, &error);
+ g_free (cmd);
+
+ if (pip == NULL)
+ {
+ message (D_ERROR, MSG_ERROR, _("EXTFS virtual file system:\n%s"), error->message);
+ g_error_free (error);
+ return (-1);
+ }
+
+ pip->err.null_term = TRUE;
+
+ mc_pread (pip, &error);
+ if (error != NULL)
+ {
+ message (D_ERROR, MSG_ERROR, _("EXTFS virtual file system:\n%s"), error->message);
+ g_error_free (error);
+ retval = -1;
+ }
+ else if (pip->err.len > 0)
+ message (D_ERROR, MSG_ERROR, _("EXTFS virtual file system:\n%s"), pip->err.buf);
+
+ mc_pclose (pip, NULL);
+
+ return retval;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+extfs_run (const vfs_path_t * vpath)
+{
+ struct extfs_super_t *archive = NULL;
+ const char *p;
+ char *q, *archive_name, *quoted_archive_name;
+ char *cmd;
+ const extfs_plugin_info_t *info;
+
+ p = extfs_get_path (vpath, &archive, FL_NONE);
+ if (p == NULL)
+ return;
+ q = name_quote (p, FALSE);
+
+ archive_name = extfs_get_archive_name (archive);
+ quoted_archive_name = name_quote (archive_name, FALSE);
+ g_free (archive_name);
+ info = &g_array_index (extfs_plugins, extfs_plugin_info_t, archive->fstype);
+ cmd =
+ g_strconcat (info->path, info->prefix, " run ", quoted_archive_name, " ", q, (char *) NULL);
+ g_free (quoted_archive_name);
+ g_free (q);
+ shell_execute (cmd, 0);
+ g_free (cmd);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+extfs_open (const vfs_path_t * vpath, int flags, mode_t mode)
+{
+ vfs_file_handler_t *extfs_info;
+ struct extfs_super_t *archive = NULL;
+ const char *q;
+ struct vfs_s_entry *entry;
+ int local_handle;
+ gboolean created = FALSE;
+
+ q = extfs_get_path (vpath, &archive, FL_NONE);
+ if (q == NULL)
+ return NULL;
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE);
+ if ((entry == NULL) && ((flags & O_CREAT) != 0))
+ {
+ /* Create new entry */
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_MKFILE);
+ created = (entry != NULL);
+ }
+
+ if (entry == NULL)
+ return NULL;
+ entry = extfs_resolve_symlinks (entry);
+ if (entry == NULL)
+ return NULL;
+
+ if (S_ISDIR (entry->ino->st.st_mode))
+ ERRNOR (EISDIR, NULL);
+
+ if (entry->ino->localname == NULL)
+ {
+ vfs_path_t *local_filename_vpath;
+ const char *local_filename;
+
+ local_handle = vfs_mkstemps (&local_filename_vpath, "extfs", entry->name);
+
+ if (local_handle == -1)
+ return NULL;
+ close (local_handle);
+ local_filename = vfs_path_get_last_path_str (local_filename_vpath);
+
+ if (!created && ((flags & O_TRUNC) == 0)
+ && extfs_cmd (" copyout ", archive, entry, local_filename))
+ {
+ unlink (local_filename);
+ vfs_path_free (local_filename_vpath, TRUE);
+ my_errno = EIO;
+ return NULL;
+ }
+ entry->ino->localname = g_strdup (local_filename);
+ vfs_path_free (local_filename_vpath, TRUE);
+ }
+
+ local_handle = open (entry->ino->localname, NO_LINEAR (flags), mode);
+
+ if (local_handle == -1)
+ {
+ /* file exists(may be). Need to drop O_CREAT flag and truncate file content */
+ flags = ~O_CREAT & (NO_LINEAR (flags) | O_TRUNC);
+ local_handle = open (entry->ino->localname, flags, mode);
+ }
+
+ if (local_handle == -1)
+ ERRNOR (EIO, NULL);
+
+ extfs_info = g_new (vfs_file_handler_t, 1);
+ vfs_s_init_fh (extfs_info, entry->ino, created);
+ extfs_info->handle = local_handle;
+
+ /* i.e. we had no open files and now we have one */
+ vfs_rmstamp (vfs_extfs_ops, (vfsid) archive);
+ VFS_SUPER (archive)->fd_usage++;
+ return extfs_info;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+extfs_read (void *fh, char *buffer, size_t count)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+
+ return read (file->handle, buffer, count);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_close (void *fh)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ int errno_code = 0;
+
+ close (file->handle);
+ file->handle = -1;
+
+ /* Commit the file if it has changed */
+ if (file->changed)
+ {
+ struct stat file_status;
+
+ if (extfs_cmd
+ (" copyin ", EXTFS_SUPER (VFS_FILE_HANDLER_SUPER (fh)), file->ino->ent,
+ file->ino->localname))
+ errno_code = EIO;
+
+ if (stat (file->ino->localname, &file_status) != 0)
+ errno_code = EIO;
+ else
+ file->ino->st.st_size = file_status.st_size;
+
+ file->ino->st.st_mtime = time (NULL);
+ }
+
+ if (--VFS_FILE_HANDLER_SUPER (fh)->fd_usage == 0)
+ vfs_stamp_create (vfs_extfs_ops, VFS_FILE_HANDLER_SUPER (fh));
+
+ g_free (fh);
+ if (errno_code != 0)
+ ERRNOR (EIO, -1);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_errno (struct vfs_class *me)
+{
+ (void) me;
+ return my_errno;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+extfs_opendir (const vfs_path_t * vpath)
+{
+ struct extfs_super_t *archive = NULL;
+ const char *q;
+ struct vfs_s_entry *entry;
+ GList **info;
+
+ q = extfs_get_path (vpath, &archive, FL_NONE);
+ if (q == NULL)
+ return NULL;
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE);
+ if (entry == NULL)
+ return NULL;
+ entry = extfs_resolve_symlinks (entry);
+ if (entry == NULL)
+ return NULL;
+ if (!S_ISDIR (entry->ino->st.st_mode))
+ ERRNOR (ENOTDIR, NULL);
+
+ info = g_new (GList *, 1);
+ *info = g_queue_peek_head_link (entry->ino->subdir);
+
+ return info;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_dirent *
+extfs_readdir (void *data)
+{
+ struct vfs_dirent *dir;
+ GList **info = (GList **) data;
+
+ if (*info == NULL)
+ return NULL;
+
+ dir = vfs_dirent_init (NULL, VFS_ENTRY ((*info)->data)->name, 0); /* FIXME: inode */
+
+ *info = g_list_next (*info);
+
+ return dir;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_closedir (void *data)
+{
+ g_free (data);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+
+static void
+extfs_stat_move (struct stat *buf, const struct vfs_s_inode *inode)
+{
+ *buf = inode->st;
+
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ buf->st_blksize = RECORDSIZE;
+#endif
+ vfs_adjust_stat (buf);
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ buf->st_atim.tv_nsec = buf->st_mtim.tv_nsec = buf->st_ctim.tv_nsec = 0;
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_internal_stat (const vfs_path_t * vpath, struct stat *buf, gboolean resolve)
+{
+ struct extfs_super_t *archive;
+ const char *q;
+ struct vfs_s_entry *entry;
+ int result = -1;
+
+ q = extfs_get_path (vpath, &archive, FL_NONE);
+ if (q == NULL)
+ goto cleanup;
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE);
+ if (entry == NULL)
+ goto cleanup;
+ if (resolve)
+ {
+ entry = extfs_resolve_symlinks (entry);
+ if (entry == NULL)
+ goto cleanup;
+ }
+ extfs_stat_move (buf, entry->ino);
+ result = 0;
+ cleanup:
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_stat (const vfs_path_t * vpath, struct stat *buf)
+{
+ return extfs_internal_stat (vpath, buf, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_lstat (const vfs_path_t * vpath, struct stat *buf)
+{
+ return extfs_internal_stat (vpath, buf, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_fstat (void *fh, struct stat *buf)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+
+ extfs_stat_move (buf, file->ino);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_readlink (const vfs_path_t * vpath, char *buf, size_t size)
+{
+ struct extfs_super_t *archive;
+ const char *q;
+ size_t len;
+ struct vfs_s_entry *entry;
+ int result = -1;
+
+ q = extfs_get_path (vpath, &archive, FL_NONE);
+ if (q == NULL)
+ goto cleanup;
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE);
+ if (entry == NULL)
+ goto cleanup;
+ if (!S_ISLNK (entry->ino->st.st_mode))
+ {
+ VFS_CLASS (vfs_path_get_last_path_vfs (vpath))->verrno = EINVAL;
+ goto cleanup;
+ }
+ len = strlen (entry->ino->linkname);
+ if (size < len)
+ len = size;
+ /* readlink() does not append a NUL character to buf */
+ result = len;
+ memcpy (buf, entry->ino->linkname, result);
+ cleanup:
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_chown (const vfs_path_t * vpath, uid_t owner, gid_t group)
+{
+ (void) vpath;
+ (void) owner;
+ (void) group;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_chmod (const vfs_path_t * vpath, mode_t mode)
+{
+ (void) vpath;
+ (void) mode;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+extfs_write (void *fh, const char *buf, size_t nbyte)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+
+ file->changed = TRUE;
+ return write (file->handle, buf, nbyte);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_unlink (const vfs_path_t * vpath)
+{
+ struct extfs_super_t *archive;
+ const char *q;
+ struct vfs_s_entry *entry;
+ int result = -1;
+
+ q = extfs_get_path (vpath, &archive, FL_NONE);
+ if (q == NULL)
+ goto cleanup;
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE);
+ if (entry == NULL)
+ goto cleanup;
+ entry = extfs_resolve_symlinks (entry);
+ if (entry == NULL)
+ goto cleanup;
+ if (S_ISDIR (entry->ino->st.st_mode))
+ {
+ VFS_CLASS (vfs_path_get_last_path_vfs (vpath))->verrno = EISDIR;
+ goto cleanup;
+ }
+ if (extfs_cmd (" rm ", archive, entry, ""))
+ {
+ my_errno = EIO;
+ goto cleanup;
+ }
+ vfs_s_free_entry (VFS_SUPER (archive)->me, entry);
+ result = 0;
+ cleanup:
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_mkdir (const vfs_path_t * vpath, mode_t mode)
+{
+ struct extfs_super_t *archive;
+ const char *q;
+ struct vfs_s_entry *entry;
+ int result = -1;
+ struct vfs_class *me;
+
+ (void) mode;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+ q = extfs_get_path (vpath, &archive, FL_NONE);
+ if (q == NULL)
+ goto cleanup;
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE);
+ if (entry != NULL)
+ {
+ me->verrno = EEXIST;
+ goto cleanup;
+ }
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_MKDIR);
+ if (entry == NULL)
+ goto cleanup;
+ entry = extfs_resolve_symlinks (entry);
+ if (entry == NULL)
+ goto cleanup;
+ if (!S_ISDIR (entry->ino->st.st_mode))
+ {
+ me->verrno = ENOTDIR;
+ goto cleanup;
+ }
+
+ if (extfs_cmd (" mkdir ", archive, entry, ""))
+ {
+ my_errno = EIO;
+ vfs_s_free_entry (VFS_SUPER (archive)->me, entry);
+ goto cleanup;
+ }
+ result = 0;
+ cleanup:
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_rmdir (const vfs_path_t * vpath)
+{
+ struct extfs_super_t *archive;
+ const char *q;
+ struct vfs_s_entry *entry;
+ int result = -1;
+
+ q = extfs_get_path (vpath, &archive, FL_NONE);
+ if (q == NULL)
+ goto cleanup;
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE);
+ if (entry == NULL)
+ goto cleanup;
+ entry = extfs_resolve_symlinks (entry);
+ if (entry == NULL)
+ goto cleanup;
+ if (!S_ISDIR (entry->ino->st.st_mode))
+ {
+ VFS_CLASS (vfs_path_get_last_path_vfs (vpath))->verrno = ENOTDIR;
+ goto cleanup;
+ }
+
+ if (extfs_cmd (" rmdir ", archive, entry, ""))
+ {
+ my_errno = EIO;
+ goto cleanup;
+ }
+ vfs_s_free_entry (VFS_SUPER (archive)->me, entry);
+ result = 0;
+ cleanup:
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_chdir (const vfs_path_t * vpath)
+{
+ void *data;
+
+ my_errno = ENOTDIR;
+ data = extfs_opendir (vpath);
+ if (data == NULL)
+ return (-1);
+ extfs_closedir (data);
+ my_errno = 0;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+extfs_lseek (void *fh, off_t offset, int whence)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+
+ return lseek (file->handle, offset, whence);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfsid
+extfs_getid (const vfs_path_t * vpath)
+{
+ struct extfs_super_t *archive = NULL;
+ const char *p;
+
+ p = extfs_get_path (vpath, &archive, FL_NO_OPEN);
+ return (p == NULL ? NULL : (vfsid) archive);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_path_t *
+extfs_getlocalcopy (const vfs_path_t * vpath)
+{
+ vfs_file_handler_t *fh;
+ vfs_path_t *p;
+
+ fh = VFS_FILE_HANDLER (extfs_open (vpath, O_RDONLY, 0));
+ if (fh == NULL)
+ return NULL;
+ if (fh->ino->localname == NULL)
+ {
+ extfs_close ((void *) fh);
+ return NULL;
+ }
+ p = vfs_path_from_str (fh->ino->localname);
+ VFS_FILE_HANDLER_SUPER (fh)->fd_usage++;
+ extfs_close ((void *) fh);
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_ungetlocalcopy (const vfs_path_t * vpath, const vfs_path_t * local, gboolean has_changed)
+{
+ vfs_file_handler_t *fh;
+
+ fh = VFS_FILE_HANDLER (extfs_open (vpath, O_RDONLY, 0));
+ if (fh == NULL)
+ return 0;
+
+ if (strcmp (fh->ino->localname, vfs_path_get_last_path_str (local)) == 0)
+ {
+ VFS_FILE_HANDLER_SUPER (fh)->fd_usage--;
+ if (has_changed)
+ fh->changed = TRUE;
+ extfs_close ((void *) fh);
+ return 0;
+ }
+ else
+ {
+ /* Should not happen */
+ extfs_close ((void *) fh);
+ return 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+extfs_get_plugins (const char *where, gboolean silent)
+{
+ char *dirname;
+ GDir *dir;
+ const char *filename;
+
+ dirname = g_build_path (PATH_SEP_STR, where, MC_EXTFS_DIR, (char *) NULL);
+ dir = g_dir_open (dirname, 0, NULL);
+
+ /* We may not use vfs_die() message or message or similar,
+ * UI is not initialized at this time and message would not
+ * appear on screen. */
+ if (dir == NULL)
+ {
+ if (!silent)
+ fprintf (stderr, _("Warning: cannot open %s directory\n"), dirname);
+ g_free (dirname);
+ return FALSE;
+ }
+
+ if (extfs_plugins == NULL)
+ extfs_plugins = g_array_sized_new (FALSE, TRUE, sizeof (extfs_plugin_info_t), 32);
+
+ while ((filename = g_dir_read_name (dir)) != NULL)
+ {
+ char fullname[MC_MAXPATHLEN];
+ struct stat s;
+
+ g_snprintf (fullname, sizeof (fullname), "%s" PATH_SEP_STR "%s", dirname, filename);
+
+ if ((stat (fullname, &s) == 0) && S_ISREG (s.st_mode) && !S_ISDIR (s.st_mode)
+ && is_exe (s.st_mode))
+ {
+ int f;
+
+ f = open (fullname, O_RDONLY);
+
+ if (f >= 0)
+ {
+ size_t len, i;
+ extfs_plugin_info_t info;
+ gboolean found = FALSE;
+
+ close (f);
+
+ /* Handle those with a trailing '+', those flag that the
+ * file system does not require an archive to work
+ */
+ len = strlen (filename);
+ info.need_archive = (filename[len - 1] != '+');
+ info.path = g_strconcat (dirname, PATH_SEP_STR, (char *) NULL);
+ info.prefix = g_strndup (filename, len);
+
+ /* prepare to compare file names without trailing '+' */
+ if (!info.need_archive)
+ info.prefix[len - 1] = '\0';
+
+ /* don't overload already found plugin */
+ for (i = 0; i < extfs_plugins->len && !found; i++)
+ {
+ extfs_plugin_info_t *p;
+
+ p = &g_array_index (extfs_plugins, extfs_plugin_info_t, i);
+
+ /* 2 files with same names cannot be in a directory */
+ found = strcmp (info.path, p->path) != 0
+ && strcmp (info.prefix, p->prefix) == 0;
+ }
+
+ if (found)
+ {
+ g_free (info.path);
+ g_free (info.prefix);
+ }
+ else
+ {
+ /* restore file name */
+ if (!info.need_archive)
+ info.prefix[len - 1] = '+';
+ g_array_append_val (extfs_plugins, info);
+ }
+ }
+ }
+ }
+
+ g_dir_close (dir);
+ g_free (dirname);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_init (struct vfs_class *me)
+{
+ gboolean d1, d2;
+
+ (void) me;
+
+ /* 1st: scan user directory */
+ d1 = extfs_get_plugins (mc_config_get_data_path (), TRUE); /* silent about user dir */
+ /* 2nd: scan system dir */
+ d2 = extfs_get_plugins (LIBEXECDIR, d1);
+
+ return (d1 || d2 ? 1 : 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+extfs_done (struct vfs_class *me)
+{
+ size_t i;
+
+ while (VFS_SUBCLASS (me)->supers != NULL)
+ me->free ((vfsid) VFS_SUBCLASS (me)->supers->data);
+
+ if (extfs_plugins == NULL)
+ return;
+
+ for (i = 0; i < extfs_plugins->len; i++)
+ {
+ extfs_plugin_info_t *info;
+
+ info = &g_array_index (extfs_plugins, extfs_plugin_info_t, i);
+ g_free (info->path);
+ g_free (info->prefix);
+ }
+
+ g_array_free (extfs_plugins, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_setctl (const vfs_path_t * vpath, int ctlop, void *arg)
+{
+ (void) arg;
+
+ if (ctlop == VFS_SETCTL_RUN)
+ {
+ extfs_run (vpath);
+ return 1;
+ }
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init_extfs (void)
+{
+ vfs_init_subclass (&extfs_subclass, "extfs", VFSF_UNKNOWN, NULL);
+ vfs_extfs_ops->init = extfs_init;
+ vfs_extfs_ops->done = extfs_done;
+ vfs_extfs_ops->fill_names = extfs_fill_names;
+ vfs_extfs_ops->which = extfs_which;
+ vfs_extfs_ops->open = extfs_open;
+ vfs_extfs_ops->close = extfs_close;
+ vfs_extfs_ops->read = extfs_read;
+ vfs_extfs_ops->write = extfs_write;
+ vfs_extfs_ops->opendir = extfs_opendir;
+ vfs_extfs_ops->readdir = extfs_readdir;
+ vfs_extfs_ops->closedir = extfs_closedir;
+ vfs_extfs_ops->stat = extfs_stat;
+ vfs_extfs_ops->lstat = extfs_lstat;
+ vfs_extfs_ops->fstat = extfs_fstat;
+ vfs_extfs_ops->chmod = extfs_chmod;
+ vfs_extfs_ops->chown = extfs_chown;
+ vfs_extfs_ops->readlink = extfs_readlink;
+ vfs_extfs_ops->unlink = extfs_unlink;
+ vfs_extfs_ops->chdir = extfs_chdir;
+ vfs_extfs_ops->ferrno = extfs_errno;
+ vfs_extfs_ops->lseek = extfs_lseek;
+ vfs_extfs_ops->getid = extfs_getid;
+ vfs_extfs_ops->getlocalcopy = extfs_getlocalcopy;
+ vfs_extfs_ops->ungetlocalcopy = extfs_ungetlocalcopy;
+ vfs_extfs_ops->mkdir = extfs_mkdir;
+ vfs_extfs_ops->rmdir = extfs_rmdir;
+ vfs_extfs_ops->setctl = extfs_setctl;
+ extfs_subclass.free_inode = extfs_free_inode;
+ extfs_subclass.free_archive = extfs_free_archive;
+ vfs_register_class (vfs_extfs_ops);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/extfs/extfs.h b/src/vfs/extfs/extfs.h
new file mode 100644
index 0000000..c576dc0
--- /dev/null
+++ b/src/vfs/extfs/extfs.h
@@ -0,0 +1,18 @@
+#ifndef MC__VFS_EXTFS_H
+#define MC__VFS_EXTFS_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void vfs_init_extfs (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__VFS_CPIO_H */
diff --git a/src/vfs/extfs/helpers/Makefile.am b/src/vfs/extfs/helpers/Makefile.am
new file mode 100644
index 0000000..f1ea0ac
--- /dev/null
+++ b/src/vfs/extfs/helpers/Makefile.am
@@ -0,0 +1,76 @@
+extfsdir = $(libexecdir)/@PACKAGE@/extfs.d
+
+# Files to install and distribute other than extfs scripts
+EXTFS_MISC = README README.extfs
+
+# Scripts hat don't need adaptation to the local system
+EXTFS_CONST = bpp changesetfs gitfs+ patchsetfs rpm trpm u7z uc1541
+
+# Scripts that need adaptation to the local system - source files
+EXTFS_IN = \
+ a+.in \
+ apt+.in \
+ audio.in \
+ deb.in \
+ deba.in \
+ debd.in \
+ dpkg+.in \
+ iso9660.in \
+ hp48+.in \
+ lslR.in \
+ mailfs.in \
+ patchfs.in \
+ rpms+.in \
+ s3+.in \
+ uace.in \
+ ualz.in \
+ uar.in \
+ uarc.in \
+ uarj.in \
+ ucab.in \
+ uha.in \
+ ulha.in \
+ ulib.in \
+ unar.in \
+ urar.in \
+ uwim.in \
+ uzip.in \
+ uzoo.in
+
+# Scripts that need adaptation to the local system - files to install
+EXTFS_OUT = \
+ a+ \
+ apt+ \
+ audio \
+ deb \
+ deba \
+ debd \
+ dpkg+ \
+ iso9660 \
+ hp48+ \
+ lslR \
+ mailfs \
+ patchfs \
+ rpms+ \
+ s3+ \
+ uace \
+ ualz \
+ uar \
+ uarc \
+ uarj \
+ ucab \
+ uha \
+ ulha \
+ ulib \
+ unar \
+ urar \
+ uwim \
+ uzip \
+ uzoo
+
+if ENABLE_VFS_EXTFS
+extfs_DATA = $(EXTFS_MISC)
+extfs_SCRIPTS = $(EXTFS_CONST) $(EXTFS_OUT)
+endif
+
+EXTRA_DIST = $(EXTFS_MISC) $(EXTFS_CONST)
diff --git a/src/vfs/extfs/helpers/Makefile.in b/src/vfs/extfs/helpers/Makefile.in
new file mode 100644
index 0000000..0a240fb
--- /dev/null
+++ b/src/vfs/extfs/helpers/Makefile.in
@@ -0,0 +1,812 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/vfs/extfs/helpers
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES = a+ apt+ audio deb deba debd dpkg+ iso9660 hp48+ \
+ lslR mailfs patchfs rpms+ s3+ uace ualz uar uarc uarj ucab uha \
+ ulha ulib unar urar uwim uzip uzoo
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(extfsdir)" "$(DESTDIR)$(extfsdir)"
+SCRIPTS = $(extfs_SCRIPTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+DATA = $(extfs_DATA)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/a+.in \
+ $(srcdir)/apt+.in $(srcdir)/audio.in $(srcdir)/deb.in \
+ $(srcdir)/deba.in $(srcdir)/debd.in $(srcdir)/dpkg+.in \
+ $(srcdir)/hp48+.in $(srcdir)/iso9660.in $(srcdir)/lslR.in \
+ $(srcdir)/mailfs.in $(srcdir)/patchfs.in $(srcdir)/rpms+.in \
+ $(srcdir)/s3+.in $(srcdir)/uace.in $(srcdir)/ualz.in \
+ $(srcdir)/uar.in $(srcdir)/uarc.in $(srcdir)/uarj.in \
+ $(srcdir)/ucab.in $(srcdir)/uha.in $(srcdir)/ulha.in \
+ $(srcdir)/ulib.in $(srcdir)/unar.in $(srcdir)/urar.in \
+ $(srcdir)/uwim.in $(srcdir)/uzip.in $(srcdir)/uzoo.in README
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+extfsdir = $(libexecdir)/@PACKAGE@/extfs.d
+
+# Files to install and distribute other than extfs scripts
+EXTFS_MISC = README README.extfs
+
+# Scripts hat don't need adaptation to the local system
+EXTFS_CONST = bpp changesetfs gitfs+ patchsetfs rpm trpm u7z uc1541
+
+# Scripts that need adaptation to the local system - source files
+EXTFS_IN = \
+ a+.in \
+ apt+.in \
+ audio.in \
+ deb.in \
+ deba.in \
+ debd.in \
+ dpkg+.in \
+ iso9660.in \
+ hp48+.in \
+ lslR.in \
+ mailfs.in \
+ patchfs.in \
+ rpms+.in \
+ s3+.in \
+ uace.in \
+ ualz.in \
+ uar.in \
+ uarc.in \
+ uarj.in \
+ ucab.in \
+ uha.in \
+ ulha.in \
+ ulib.in \
+ unar.in \
+ urar.in \
+ uwim.in \
+ uzip.in \
+ uzoo.in
+
+
+# Scripts that need adaptation to the local system - files to install
+EXTFS_OUT = \
+ a+ \
+ apt+ \
+ audio \
+ deb \
+ deba \
+ debd \
+ dpkg+ \
+ iso9660 \
+ hp48+ \
+ lslR \
+ mailfs \
+ patchfs \
+ rpms+ \
+ s3+ \
+ uace \
+ ualz \
+ uar \
+ uarc \
+ uarj \
+ ucab \
+ uha \
+ ulha \
+ ulib \
+ unar \
+ urar \
+ uwim \
+ uzip \
+ uzoo
+
+@ENABLE_VFS_EXTFS_TRUE@extfs_DATA = $(EXTFS_MISC)
+@ENABLE_VFS_EXTFS_TRUE@extfs_SCRIPTS = $(EXTFS_CONST) $(EXTFS_OUT)
+EXTRA_DIST = $(EXTFS_MISC) $(EXTFS_CONST)
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/vfs/extfs/helpers/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/extfs/helpers/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+a+: $(top_builddir)/config.status $(srcdir)/a+.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+apt+: $(top_builddir)/config.status $(srcdir)/apt+.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+audio: $(top_builddir)/config.status $(srcdir)/audio.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+deb: $(top_builddir)/config.status $(srcdir)/deb.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+deba: $(top_builddir)/config.status $(srcdir)/deba.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+debd: $(top_builddir)/config.status $(srcdir)/debd.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+dpkg+: $(top_builddir)/config.status $(srcdir)/dpkg+.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+iso9660: $(top_builddir)/config.status $(srcdir)/iso9660.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+hp48+: $(top_builddir)/config.status $(srcdir)/hp48+.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+lslR: $(top_builddir)/config.status $(srcdir)/lslR.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+mailfs: $(top_builddir)/config.status $(srcdir)/mailfs.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+patchfs: $(top_builddir)/config.status $(srcdir)/patchfs.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+rpms+: $(top_builddir)/config.status $(srcdir)/rpms+.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+s3+: $(top_builddir)/config.status $(srcdir)/s3+.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+uace: $(top_builddir)/config.status $(srcdir)/uace.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+ualz: $(top_builddir)/config.status $(srcdir)/ualz.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+uar: $(top_builddir)/config.status $(srcdir)/uar.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+uarc: $(top_builddir)/config.status $(srcdir)/uarc.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+uarj: $(top_builddir)/config.status $(srcdir)/uarj.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+ucab: $(top_builddir)/config.status $(srcdir)/ucab.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+uha: $(top_builddir)/config.status $(srcdir)/uha.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+ulha: $(top_builddir)/config.status $(srcdir)/ulha.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+ulib: $(top_builddir)/config.status $(srcdir)/ulib.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+unar: $(top_builddir)/config.status $(srcdir)/unar.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+urar: $(top_builddir)/config.status $(srcdir)/urar.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+uwim: $(top_builddir)/config.status $(srcdir)/uwim.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+uzip: $(top_builddir)/config.status $(srcdir)/uzip.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+uzoo: $(top_builddir)/config.status $(srcdir)/uzoo.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+install-extfsSCRIPTS: $(extfs_SCRIPTS)
+ @$(NORMAL_INSTALL)
+ @list='$(extfs_SCRIPTS)'; test -n "$(extfsdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(extfsdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(extfsdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n' \
+ -e 'h;s|.*|.|' \
+ -e 'p;x;s,.*/,,;$(transform)' | sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1; } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) { files[d] = files[d] " " $$1; \
+ if (++n[d] == $(am__install_max)) { \
+ print "f", d, files[d]; n[d] = 0; files[d] = "" } } \
+ else { print "f", d "/" $$4, $$1 } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_SCRIPT) $$files '$(DESTDIR)$(extfsdir)$$dir'"; \
+ $(INSTALL_SCRIPT) $$files "$(DESTDIR)$(extfsdir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-extfsSCRIPTS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(extfs_SCRIPTS)'; test -n "$(extfsdir)" || exit 0; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 's,.*/,,;$(transform)'`; \
+ dir='$(DESTDIR)$(extfsdir)'; $(am__uninstall_files_from_dir)
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-extfsDATA: $(extfs_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(extfs_DATA)'; test -n "$(extfsdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(extfsdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(extfsdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(extfsdir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(extfsdir)" || exit $$?; \
+ done
+
+uninstall-extfsDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(extfs_DATA)'; test -n "$(extfsdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(extfsdir)'; $(am__uninstall_files_from_dir)
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(SCRIPTS) $(DATA)
+installdirs:
+ for dir in "$(DESTDIR)$(extfsdir)" "$(DESTDIR)$(extfsdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-extfsDATA install-extfsSCRIPTS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-extfsDATA uninstall-extfsSCRIPTS
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-generic clean-libtool \
+ cscopelist-am ctags-am distclean distclean-generic \
+ distclean-libtool distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-extfsDATA \
+ install-extfsSCRIPTS install-html install-html-am install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags-am uninstall \
+ uninstall-am uninstall-extfsDATA uninstall-extfsSCRIPTS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/vfs/extfs/helpers/README b/src/vfs/extfs/helpers/README
new file mode 100644
index 0000000..64da3d6
--- /dev/null
+++ b/src/vfs/extfs/helpers/README
@@ -0,0 +1,200 @@
+ Writing scripts for Midnight Commander's external vfs
+
+IMPORTANT NOTE: There may be some bugs left in extfs. Enjoy.
+
+Starting with version 3.1, the Midnight Commander comes with so called
+extfs, which is one of the virtual filesystems. This system makes it
+possible to create new virtual filesystems for the GNU MC very easily.
+
+To handle requests, create a shell/perl/python/etc script/program
+(with executable permissions) in $(libexecdir)/mc/extfs.d
+or in ~/.local/share/mc/extfs.d/.
+
+(Note: $(libexecdir) should be substituted for actual libexecdir path
+stored when configured or compiled, like /usr/local/libexec or /usr/libexec).
+
+Assign a vfs suffix. For example, if you have .zip file, and would like
+to see what's inside it, path will be
+
+/anypath/my.zip/uzip://some_path/...
+
+In this example, .zip is suffix, but I call vfs 'uzip'. Why? Well,
+what this vfs essentially does is UNzip. UN is too long, so I chose
+U. Note that sometime in future filesystem like zip may exist: It will
+take whole tree and create .zip file from it. So /usr/zip:// will be
+zipfile containing whole /usr tree.
+
+If your vfs does not require file to work on, add '+' to the end of name.
+Note, that trailing '+' in file name is not a part of vfs name, it is
+just an vfs attribute. So you have not use it in vfs commands:
+
+cd rpms://
+
+is correct command, and
+
+cd rpms+://
+
+is incorrect command.
+
+
+* Commands that should be implemented by your shell script
+----------------------------------------------------------
+
+Return zero from your script upon completion of the command, otherwise
+nonzero for failure or in case of an unsupported command.
+
+$libdir/extfs/prefix command [arguments]
+
+* Command: list archivename
+
+This command should list the complete archive content in the following format
+(a little modified ls -l listing):
+
+AAAAAAA NNN OOOOOOOO GGGGGGGG SSSSSSSS DATETIME [PATH/]FILENAME [-> [PATH/]FILENAME[/]]]
+
+where (things in [] are optional):
+
+AAAAAAA is the permission string like in ls -l
+NNN is the number of links
+OOOOOOOO is the owner (either UID or name)
+GGGGGGGG is the group (either GID or name)
+SSSSSSSS is the file size
+FILENAME is the filename
+PATH is the path from the archive's root without the leading slash (/)
+DATETIME has one of the following formats:
+ Mon DD hh:mm[:ss], Mon DD YYYY, MM-DD-YYYY hh:mm[:ss]
+
+ where Mon is a three letter English month name, DD is day
+ 01-31 (can be 1-31, if following Mon), MM is month 01-12,
+ YYYY is four digit year, hh is hours, mm is minutes,
+ and ss is optional seconds.
+
+If the -> [PATH/]FILENAME part is present, it means:
+
+If permissions start with an l (ell), then it is the name that symlink
+points to. (If this PATH starts with a MC vfs prefix, then it is a symlink
+somewhere to the other virtual filesystem (if you want to specify path from
+the local root, use local:/path_name instead of /path_name, since /path_name
+means from root of the archive listed).
+
+If permissions do not start with l, but number of links is greater than one,
+then it says that this file should be a hardlinked with the other file.
+
+The result of list command must not contain "." and ".." items.
+
+* Command: copyout archivename storedfilename extractto
+
+This should extract from archive archivename the file called
+storedfilename (possibly with path if not located in archive's root
+[this is wrong. current extfs strips paths! -- pavel@ucw.cz])
+to file extractto.
+
+* Command: copyin archivename storedfilename sourcefile
+
+This should add to the archivename the sourcefile with the name
+storedfilename inside the archive.
+
+Important note: archivename in the above examples may not have the
+extension you are expecting to have, like it may happen that
+archivename will be something like /tmp/f43513254 or just
+anything. Some archivers do not like it, so you'll have to find some
+workaround.
+
+* Command: rm archivename storedfilename
+
+This should remove storedfilename from archivename.
+
+* Command: mkdir archivename dirname
+
+This should create a new directory called dirname inside archivename.
+
+* Command: rmdir archivename dirname
+
+This should remove an existing directory dirname. If the directory is
+not empty, mc will recursively delete it (possibly prompting).
+
+* Command: run
+
+Undocumented :-)
+
+---------------------------------------------------------
+
+Don't forget to mark this file executable (chmod 755 ThisFile, for example)
+
+For skeleton structure of executable, look at some of filesystems
+similar to yours.
+
+---------------------------------------------------------
+
+In constructing these routines, errors will be made, and mc will not display
+a malformed printing line. That can lead the programmer down many false
+trails in search of the bug. Since this routine is an executable shell script
+it can be run from the command line independently of mc, and its output will
+show on the console or can be redirected to a file.
+
+* Putting it to use
+----------------------------------------------------------
+The file .mc.ext in a home directory, and in mc's user directory (commonly
+/etc/mc), contains instructions for operations on files depending
+on filename extensions. It is well documented in other files in this
+distribution, so here are just a few notes specifically on use of the
+Virtual File System you just built.
+
+There are entries in .mc.ext defining a few operations that can be done on a
+file from an mc panel. Typically they are annotated with a hash mark and a
+file extension like this:
+
+# zip
+
+There must be a way to find the file by extension, so the next line does
+that. In essence it says "identify the string ".zip" or (|) ".ZIP" at the
+end ($) of a filename":
+
+regex/\.(zip|ZIP)$
+
+The operations themselves follow that. They must be indented by at least a
+space, and a tab works as well. In particular, the Open operation will
+now use your new virtual file system by cd'ing to it like this:
+
+ Open=%cd zip:%d/%p
+
+This is the line used when a file is highlighted in a panel and the user
+presses <Enter> or <Return>. The contents of the archive should show just
+as if they were in a real directory, and can be manipulated as such.
+The rest of the entry pertains to use of the F3 View key:
+
+ View=%view{ascii} unzip -v %f
+
+And perhaps an optional icon for X:
+
+ Icon=zip.xpm
+
+And perhaps an operation to extract the contents of the file, called from
+a menu selection:
+
+ Extract=unzip %f '*'
+
+This is just an example. The current entry for .zip files has a menu selection
+of 'Unzip' which could be used in place of 'Extract'. What goes here depends
+on what items you have in, or add to, the menu system, and that's another
+subject. The sum of this is the .mc.ext entry:
+
+# zip
+regex/\.(zip|ZIP)$
+ Open=%cd %p/uzip://
+ View=%view{ascii} unzip -v %f
+ Icon=zip.xpm
+ Extract=unzip %f '*'
+
+Add an entry like this to the .mc.ext file in a user's home directory, If you
+want others to have it, add it to the mc.ext file in the mc system directory,
+often /etc/mc/mc.ext. Notice this file is not prepended with a dot.
+
+Once all this is done, and things are in their proper places, exit mc if you
+were using it, and restart it so it picks up the new information.
+
+That's all there is to it. The hardest part is making a listing function
+that sorts the output of a system listing command and turns it into a form
+that mc can use. Currently awk (or gawk) is used because nearly all systems
+have it. If another scripting language is available, like perl, that could
+also be used.
diff --git a/src/vfs/extfs/helpers/README.extfs b/src/vfs/extfs/helpers/README.extfs
new file mode 100644
index 0000000..ce7d086
--- /dev/null
+++ b/src/vfs/extfs/helpers/README.extfs
@@ -0,0 +1,78 @@
+# Each external VFS type must be registered in extfs.d directory if you want to use it.
+# Trailing plus means that the filesystem is not tied to a certain file.
+
+# Popular PC archivers
+uzip
+uzoo
+ulha
+urar
+uha
+u7z
+ualz
+# FIXME: for arj usage you need a special patch to unarj (see unarj.diff)
+uarj
+uarc
+uace
+
+# For cab files
+ucab
+
+# ar is used for static libraries
+uar
+
+# Packages from popular Linux distributions
+rpm
+deb
+
+# a+ - mtools filesystem
+a+
+
+# For browsing lslR listings (found on many ftp sites)
+lslR
+
+# Hewlett Packard calculator
+hp48+
+
+# Commodore 64/128 d64/D64 files
+uc1541
+
+# Break patches into chunks
+patchfs
+
+# Represents a mailbox as a directory
+mailfs
+
+# List all installed RPM packages on the system
+rpms+
+trpm
+
+# dpkg frontend
+dpkg+
+debd
+
+# apt frontend
+apt+
+deba
+
+# Simple filesystem for audio cdroms. Use /dev/cdrom#audio (or /#audio)
+audio
+
+# Package of Bad Penguin (an Italian GNU/Linux distribution)
+bpp
+
+# ISO image
+iso9660
+
+# Amazon S3
+s3+
+
+# git frontend
+gitfs - browse the git repo
+changesetfs - list of versions of current file
+patchsetfs - list of patches of current file
+
+# Gputils lib archives.
+ulib
+
+# PAK Archive
+unar
diff --git a/src/vfs/extfs/helpers/a+.in b/src/vfs/extfs/helpers/a+.in
new file mode 100644
index 0000000..fe446f4
--- /dev/null
+++ b/src/vfs/extfs/helpers/a+.in
@@ -0,0 +1,126 @@
+#! @PERL@
+#
+# External filesystem for mc, using mtools
+# Written Ludek Brukner <lubr@barco.cz>, 1997
+# Much improved by Tom Perkins <968794022@noid.net>, 2000
+#
+# WARNING - This software is ALPHA - Absolutely NO WARRANTY
+#
+
+# These mtools components must be in PATH for this to work
+
+use warnings;
+
+sub quote {
+ $_ = shift(@_);
+ s/([^\w\/.+-])/\\$1/g;
+ return($_);
+}
+
+$mmd = "mmd";
+$mrd = "mrd";
+$mdel = "mdel";
+$mdir = "mdir -a";
+$mcopy = "mcopy -noQ";
+
+$0 =~ s|.*/||;
+$qdisk = quote($0);
+
+$ENV{MTOOLS_DATE_STRING} = "mm-dd-yyyy";
+$ENV{MTOOLS_TWENTY_FOUR_HOUR_CLOCK} = "1";
+
+SWITCH: for ( $ARGV[0] ) {
+ /list/ && do {
+ @dirs = get_dirs("");
+ while ($dir = shift(@dirs)) {
+ push @dirs, get_dirs("$dir/");
+ } exit 0; };
+ /mkdir/ && do {
+ shift; shift;
+ exit 1 if scalar(@ARGV) != 1;
+ $qname = quote($ARGV[0]);
+ system("$mmd $qdisk:/$qname >/dev/null");
+ exit 0; };
+ /rmdir/ && do {
+ shift; shift;
+ exit 1 if scalar(@ARGV) != 1;
+ $qname = quote($ARGV[0]);
+ system("$mrd $qdisk:/$qname >/dev/null");
+ exit 0; };
+ /rm/ && do {
+ shift; shift;
+ exit 1 if scalar(@ARGV) != 1;
+ $qname = quote($ARGV[0]);
+ system("$mdel $qdisk:/$qname >/dev/null");
+ exit 0; };
+ /copyout/ && do {
+ shift; shift;
+ exit 1 if scalar(@ARGV) != 2;
+ ( $qsrc, $qdest ) = @ARGV;
+ $qsrc = quote($qsrc);
+ $qdest = quote($qdest);
+ system("$mcopy $qdisk:/$qsrc $qdest >/dev/null");
+ exit 0; };
+ /copyin/ && do {
+ shift; shift;
+ exit 1 if scalar(@ARGV) != 2;
+ ( $qdest, $qsrc ) = @ARGV;
+ $qsrc = quote($qsrc);
+ $qdest = quote($qdest);
+ system("$mcopy $qsrc $qdisk:/$qdest >/dev/null");
+ exit 0; };
+ /.*/ && do { # an unfamiliar command
+ exit 1; };
+}
+
+sub get_dirs {
+ my ($path, $name, $size, $date, $time, $longname, @lst, @rv);
+ $path = shift(@_);
+ my $qpath = quote($path);
+ @rv = ();
+
+ open(FILE,"$mdir $qdisk:/$qpath |");
+ while ( <FILE> ) {
+ chomp();
+ /^ / && next; # ignore `non-file' lines
+ m{^Directory for $0:/}i && next; # ignore `non-file' lines
+ /^$/ && next; # ignore empty lines
+ /^\.\.?/ && next; # ignore `.' and `..'
+
+ $name = substr($_,0,12);
+ $name =~ s/^([^ ]*) +([^ ]+)[ \t]*$/$1.$2/;
+ $name =~ s/[ .]+$//;
+
+ $_ = substr($_,12);
+ s/^[ ]+//;
+
+ ($size,$date,$time,$longname) = split(/[ \t]+/, $_, 4);
+
+ defined $time || next;
+
+ # process "am" and "pm". Should not be needed if
+ # MTOOLS_TWENTY_FOUR_HOUR_CLOCK is respected.
+ @lst = split(/([:ap])/, $time);
+ $lst[0] += 12 if (defined $lst[3] && $lst[3] eq "p");
+
+ $time = sprintf("%02d:%02d", $lst[0], $lst[2]);
+ @lst = split(/-/, $date);
+ $lst[2] %= 100 if ($lst[2] > 100);
+ $date = sprintf ("%02d-%02d-%02d", @lst);
+
+ $name = $path . lc(($longname) ? $longname : $name);
+
+ if ($size =~ /DIR/) {
+ printf("drwxr-xr-x 1 %-8d %-8d %8d %s %s %s\n",
+ 0, 0, 0, $date, $time, $name);
+ push @rv, $name;
+ } else {
+ printf("-rw-r--r-- 1 %-8d %-8d %8d %s %s %s\n",
+ 0, 0, $size, $date, $time, $name);
+ }
+ }
+ close(FILE);
+ return @rv;
+}
+
+1;
diff --git a/src/vfs/extfs/helpers/apt+.in b/src/vfs/extfs/helpers/apt+.in
new file mode 100644
index 0000000..60011e6
--- /dev/null
+++ b/src/vfs/extfs/helpers/apt+.in
@@ -0,0 +1,359 @@
+#! @PERL@
+#
+# 1999 (c) Piotr Roszatycki <dexter@debian.org>
+# This software is under GNU license
+# last modification: 1999-12-08
+#
+# apt
+
+sub quote {
+ $_ = shift(@_);
+ s/([^\w\/.+-])/\\$1/g;
+ return($_);
+}
+
+sub bt
+{
+ my ($dt) = @_;
+ my (@time);
+ @time = localtime($dt);
+ $bt = sprintf "%02d-%02d-%d %02d:%02d", $time[4] + 1, $time[3],
+ $time[5] + 1900, $time[2], $time[1];
+ return $bt;
+}
+
+
+sub ft
+{
+ my ($f) = @_;
+ return "d" if -d $f;
+ return "l" if -l $f;
+ return "p" if -p $f;
+ return "S" if -S $f;
+ return "b" if -b $f;
+ return "c" if -c $f;
+ return "-";
+}
+
+sub fm
+{
+ my ($n) = @_;
+ my ($m);
+
+ if( $n & 0400 ) {
+ $m .= "r";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 0200 ) {
+ $m .= "w";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 04000 ) {
+ $m .= "s";
+ } elsif( $n & 0100 ) {
+ $m .= "x";
+ } else {
+ $m .= "-";
+ }
+
+ if( $n & 0040 ) {
+ $m .= "r";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 0020 ) {
+ $m .= "w";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 02000 ) {
+ $m .= "s";
+ } elsif( $n & 0010 ) {
+ $m .= "x";
+ } else {
+ $m .= "-";
+ }
+
+ if( $n & 0004 ) {
+ $m .= "r";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 0002 ) {
+ $m .= "w";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 01000 ) {
+ $m .= "t";
+ } elsif( $n & 0001 ) {
+ $m .= "x";
+ } else {
+ $m .= "-";
+ }
+
+ return $m;
+}
+
+sub ls {
+ my ($file,$path,$mode) = @_;
+
+ if (-f $file) {
+ my @stat = stat(_);
+ # mode, nlink, uid, gid, size, mtime, filename
+ printf "%s %d %d %d %d %s %s\n", $mode || ft($file).fm($stat[2] & 07777),
+ $stat[3], $stat[4], $stat[5], $stat[7], bt($stat[9]), $path;
+ }
+}
+
+$DATE=bt(time());
+
+sub list
+{
+ my ($pkg, $fn, $dn, $sz, $bt);
+
+ my($check,$stats,$config);
+ chop($check = `apt-get -q check 2>/dev/null`);
+ chop($available = `apt-cache dumpavail 2>/dev/null`);
+ chop($stats = `apt-cache stats 2>/dev/null`);
+ chop($config = `apt-config dump 2>&1`);
+ $sz = length($check);
+ print "-r--r--r-- 1 root root $sz $DATE CHECK\n";
+ $sz = length($available);
+ print "-r--r--r-- 1 root root $sz $DATE AVAILABLE\n";
+ $sz = length($stats);
+ print "-r--r--r-- 1 root root $sz $DATE STATS\n";
+ $sz = length($config);
+ print "-r--r--r-- 1 root root $sz $DATE CONFIG\n";
+ $sz = length($pressupdate);
+ print "-r-xr--r-- 1 root root $sz $DATE UPDATE\n";
+ $sz = length($pressupgrade);
+ print "-r-xr--r-- 1 root root $sz $DATE UPGRADE\n";
+ print "-r-xr--r-- 1 root root $sz $DATE DIST-UPGRADE\n";
+
+ ls("/etc/apt/sources.list","sources.list");
+ ls('/etc/apt/apt.conf','apt.conf') if (-f '/etc/apt/apt.conf');
+
+ print "drwxr-xr-x 1 root root 0 $DATE all\n";
+
+ if ( open(PIPEIN, "find /var/cache/apt/archives -type f |") ) {
+ while(<PIPEIN>) {
+ chop;
+ next if /\/lock$/;
+ my $file = $_;
+ s%/var/cache/apt/archives/%CACHE/%;
+ ls($file, $_);
+ }
+ close PIPEIN;
+ }
+
+ my %sects = ();
+ my %debd = ();
+ my %deba = ();
+
+ open STAT, "/var/lib/dpkg/status"
+ or exit 1;
+ while( <STAT> ) {
+ chop;
+ if( /^([\w-]*): (.*)/ ) {
+ $pkg = $2 if( lc($1) eq 'package' );
+ $debd{$pkg}{lc($1)} = $2;
+ }
+ }
+ close STAT;
+
+ foreach $pkg (sort keys %debd) {
+ next if $debd{$pkg}{status} =~ /not-installed/;
+ $fn = $debd{$pkg}{package}. "_". $debd{$pkg}{version};
+ $dn = $debd{$pkg}{section};
+ if( ! $dn ) {
+ $dn = "unknown";
+ } elsif( $dn =~ /^(non-us)$/i ) {
+ $dn .= "/main";
+ } elsif( $dn !~ /\// ) {
+ $dn = "main/". $dn;
+ }
+ unless( $sects{$dn} ) {
+ my $sub = $dn;
+ while( $sub =~ s!^(.*)/[^/]*$!$1! ) {
+ unless( $sects{$sub} ) {
+ print "drwxr-xr-x 1 root root 0 $DATE $sub/\n";
+ $sects{$sub} = 1;
+ }
+ }
+ print "drwxr-xr-x 1 root root 0 $DATE $dn/\n";
+ $sects{$dn} = 1;
+ }
+ $sz = $debd{$pkg}{'status'} =~ /config-files/ ? 0 : $debd{$pkg}{'installed-size'} * 1024;
+ @stat = stat("/var/lib/dpkg/info/".$debd{$pkg}{package}.".list");
+ $bt = bt($stat[9]);
+ print "-rw-r--r-- 1 root root $sz $bt $dn/$fn.debd\n";
+ print "lrwxrwxrwx 1 root root $sz $bt all/$fn.debd -> ../$dn/$fn.debd\n";
+ }
+
+ open STAT, "apt-cache dumpavail |"
+ or exit 1;
+ while( <STAT> ) {
+ chop;
+ if( /^([\w-]*): (.*)/ ) {
+ $pkg = $2 if( lc($1) eq 'package' );
+ $deba{$pkg}{lc($1)} = $2;
+ }
+ }
+ close STAT;
+
+ foreach $pkg (sort keys %deba) {
+ next if $deba{$pkg}{version} eq $debd{$pkg}{version};
+ $fn = $deba{$pkg}{package}. "_". $deba{$pkg}{version};
+ $dn = $deba{$pkg}{section};
+ if( ! $dn ) {
+ $dn = "unknown";
+ } elsif( $dn =~ /^(non-us)$/i ) {
+ $dn .= "/main";
+ } elsif( $dn !~ /\// ) {
+ $dn = "main/". $dn;
+ }
+ unless( $sects{$dn} ) {
+ my $sub = $dn;
+ while( $sub =~ s!^(.*)/[^/]*$!$1! ) {
+ unless( $sects{$sub} ) {
+ print "drwxr-xr-x 1 root root 0 $DATE $sub/\n";
+ $sects{$sub} = 1;
+ }
+ }
+ print "drwxr-xr-x 1 root root 0 $DATE $dn/\n";
+ $sects{$dn} = 1;
+ }
+ $sz = $deba{$pkg}{'status'} =~ /config-files/ ? 0 : $deba{$pkg}{'installed-size'} * 1024;
+ print "-rw-r--r-- 1 root root $sz $DATE $dn/$fn.deba\n";
+ print "lrwxrwxrwx 1 root root $sz $DATE all/$fn.deba -> ../$dn/$fn.deba\n";
+ }
+}
+
+sub copyout
+{
+ my($archive,$filename) = @_;
+ my $qarchive = quote($archive);
+ my $qfilename = quote($filename);
+ if( $archive eq 'CHECK' ) {
+ system("apt-get -q check > $qfilename");
+ } elsif( $archive eq 'AVAILABLE' ) {
+ system("apt-cache dumpavail > $qfilename");
+ } elsif( $archive eq 'STATS' ) {
+ system("apt-cache stats > $qfilename");
+ } elsif( $archive eq 'CONFIG' ) {
+ system("(apt-config dump 2>&1) > $qfilename");
+ } elsif( $archive eq 'UPDATE' ) {
+ open O, ">$filename";
+ print O $pressupdate;
+ close O;
+ } elsif( $archive eq 'UPGRADE' || $archive eq 'DIST-UPGRADE' ) {
+ open O, ">$filename";
+ print O $pressupgrade;
+ close O;
+ } elsif( $archive eq 'apt.conf' ) {
+ system("cp /etc/apt/apt.conf $qfilename");
+ } elsif( $archive eq 'sources.list' ) {
+ system("cp /etc/apt/sources.list $qfilename");
+ } elsif( $archive =~ /^CACHE\// ) {
+ $archive =~ s%^CACHE/%/var/cache/apt/archives/%;
+ system("cp $qarchive $qfilename");
+ } else {
+ open O, ">$filename";
+ print O $archive, "\n";
+ close O;
+ }
+}
+
+sub copyin
+{
+ my($archive,$filename) = @_;
+ my $qarchive = quote($archive);
+ my $qfilename = quote($filename);
+ if( $archive =~ /\.deb$/ ) {
+ system("dpkg -i $qfilename>/dev/null");
+ } elsif( $archive eq 'apt.conf' ) {
+ system("cp $qfilename /etc/apt/apt.conf");
+ } elsif( $archive eq 'sources.list' ) {
+ system("cp $qfilename /etc/apt/sources.list");
+ } elsif( $archive =~ /^CACHE\// ) {
+ $qarchive =~ s%^CACHE/%/var/cache/apt/archives/%;
+ system("cp $qfilename $qarchive");
+ } else {
+ die "extfs: cannot create regular file \`$archive\': Permission denied\n";
+ }
+}
+
+sub run
+{
+ my($archive,$filename) = @_;
+ if( $archive eq 'UPDATE' ) {
+ system("apt-get update");
+ } elsif( $archive eq 'UPGRADE' ) {
+ system("apt-get upgrade -u");
+ } elsif( $archive eq 'DIST-UPGRADE' ) {
+ system("apt-get dist-upgrade -u");
+ } else {
+ die "extfs: $archive: command not found\n";
+ }
+}
+
+sub rm
+{
+ my($archive) = @_;
+ my $qarchive = quote($archive);
+ if( $archive =~ /^CACHE\// ) {
+ $qarchive =~ s%^CACHE/%/var/cache/apt/archives/%;
+ system("rm -f $qarchive");
+ } elsif( $archive eq 'apt.conf' ) {
+ system("rm -f /etc/apt/apt.conf");
+ } elsif( $archive eq 'sources.list' ) {
+ system("rm -f /etc/apt/sources.list");
+ } elsif( $archive =~ /\.debd?$/ ) {
+ # uncommented and changed to use dpkg - alpha
+ my $qname = $qarchive;
+ $qname =~ s%.*/%%g;
+ $qname =~ s%_.*%%g;
+ system("dpkg --remove $qname >/dev/null");
+ die("extfs: $archive: Operation not permitted\n") if $? != 0;
+ } else {
+ die "extfs: $archive: Operation not permitted\n";
+ }
+}
+
+
+$pressupdate=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you don't want to retrieve new lists of packages.
+ ==========================================================================
+
+This is not a real file. It is a way to retrieve new lists of packages.
+To update this information go back to the panel and press Enter on this file.
+
+EOInstall
+
+$pressupgrade=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to perform an upgrade.
+ ===================================================================
+
+This is not a real file. It is a way to perform an upgrade.
+To upgrade this information go back to the panel and press Enter on this file.
+
+EOInstall
+
+
+# override any locale for dates
+$ENV{"LC_ALL"}="C";
+
+if ($ARGV[0] eq "list") { list(); exit(0); }
+elsif ($ARGV[0] eq "copyout") { copyout($ARGV[2], $ARGV[3]); exit(0); }
+elsif ($ARGV[0] eq "copyin") { copyin($ARGV[2], $ARGV[3]); exit(0); }
+elsif ($ARGV[0] eq "run") { run($ARGV[2]); exit(0); }
+elsif ($ARGV[0] eq "rm") { rm($ARGV[2]); exit(0); }
+exit(1);
+
diff --git a/src/vfs/extfs/helpers/audio.in b/src/vfs/extfs/helpers/audio.in
new file mode 100755
index 0000000..05c8c65
--- /dev/null
+++ b/src/vfs/extfs/helpers/audio.in
@@ -0,0 +1,53 @@
+#! /bin/sh
+#
+# Written by Pavel Machek
+# CDDB support by Adam Byrtek
+#
+# (C) 2000 The Free Software Foundation.
+#
+
+set -e
+
+CDDB_SERVER="http://freedb.freedb.org"
+CDDB_HANDSHAKE="hello=user+localhost+mc+1.0&proto=1"
+CDDB_TIMEOUT=20 # in seconds
+
+audiofs_list()
+{
+ DATE=`date +"%b %d %H:%M"`
+ echo "-r--r--r-- 1 0 0 0 $DATE CDDB"
+ cdparanoia -Q -d "$1" 2>&1 | grep '^[ 0-9][ 0-9][ 0-9]\.' | while read A B C
+ do
+ A=`echo "$A" | sed -e 's/\.//' -e 's/^\(.\)$/0\1/'`
+ SIZE=`expr 44 + $B \* 2352`
+ echo "-r--r--r-- 1 0 0 $SIZE $DATE track-${A}.wav"
+ done
+}
+
+audiofs_copyout()
+{
+ if [ x"$2" = x"CDDB" ]; then
+ DISCID=`cd-discid "$1" | tr " " "+"`
+ if [ -z "$DISCID" ]; then
+ exit 1
+ fi
+ RESPONSE=`wget -q -T $CDDB_TIMEOUT -O - "$CDDB_SERVER/~cddb/cddb.cgi?cmd=cddb+query+$DISCID&$CDDB_HANDSHAKE" | tee "$3" | @AWK@ '/^200/ { print $2,$3; }'`
+ wget -q -T $CDDB_TIMEOUT -O - "$CDDB_SERVER/~cddb/cddb.cgi?cmd=cddb+read+$RESPONSE&$CDDB_HANDSHAKE" | grep -v "^#" >> "$3"
+ else
+ TRACK=`echo "$2" | sed 's/track-0*//' | sed 's/\.wav//'`
+ cdparanoia -q -d "$1" "$TRACK" "$3" >/dev/null
+ fi
+}
+
+if [ ! -b "$2" ]
+then
+ BASE="/dev/cdrom"
+else
+ BASE="$2"
+fi
+
+case "$1" in
+ list) audiofs_list "$BASE"; exit 0;;
+ copyout) audiofs_copyout "$BASE" "$3" "$4"; exit 0;;
+esac
+exit 1
diff --git a/src/vfs/extfs/helpers/bpp b/src/vfs/extfs/helpers/bpp
new file mode 100755
index 0000000..f71fe7e
--- /dev/null
+++ b/src/vfs/extfs/helpers/bpp
@@ -0,0 +1,50 @@
+#! /bin/sh
+#
+# Written by Marco Ciampa 2000
+# (a simple cut & paste from rpm vfs)
+# (C) 1996 The Free Software Foundation.
+#
+# Package of a new italian distribution: Bad Penguin
+# http://www.badpenguin.org/
+
+# override any locale for dates
+unset LC_ALL
+LC_TIME=C
+export LC_TIME
+
+mcbppfs_list ()
+{
+ FILEPREF="-r--r--r-- 1 root root "
+ FIEXPREF="-r-xr-xr-x 1 root root "
+ DATE=`date +"%b %d %H:%M"`
+ set x `ls -l "$1"`
+ size=$6
+ echo "$FILEPREF $size $DATE CONTENTS.tar.gz"
+ echo "$FIEXPREF 35 $DATE INSTALL"
+ echo "$FIEXPREF 35 $DATE UPGRADE"
+}
+
+mcbppfs_copyout ()
+{
+ case "$2" in
+ CONTENTS.tar.gz) cat "$1" > "$3"; exit 0;;
+ INSTALL) echo "# Run this to install this package" > "$3"; exit 0;;
+ UPGRADE) echo "# Run this to upgrade this package" > "$3"; exit 0;;
+ esac
+}
+
+mcbppfs_run ()
+{
+ case "$2" in
+ INSTALL) echo "Installing \"$1\""; package-setup --install "$1"; exit 0;;
+ UPGRADE) echo "Upgrading \"$1\""; package-setup --update "$1"; exit 0;;
+ esac
+}
+
+umask 077
+case "$1" in
+ list) mcbppfs_list "$2"; exit 0;;
+ copyout) mcbppfs_copyout "$2" "$3" "$4"; exit 0;;
+ run) mcbppfs_run "$2" "$3"; exit 1;;
+esac
+exit 1
diff --git a/src/vfs/extfs/helpers/changesetfs b/src/vfs/extfs/helpers/changesetfs
new file mode 100755
index 0000000..eebdf8c
--- /dev/null
+++ b/src/vfs/extfs/helpers/changesetfs
@@ -0,0 +1,109 @@
+#!/bin/sh
+
+LANG=C
+export LANG
+LC_TIME=C
+export LC_TIME
+
+# --- GIT -----------------------------------------------------------------------
+
+found_git_dir()
+{
+ work_dir=$1
+ while [ -n "$work_dir" -a "$work_dir" != "/" ]; do
+ [ -d "${work_dir}/.git" ] && {
+ echo "${work_dir}/.git/"
+ return
+ }
+ work_dir=`dirname "$work_dir"`
+ done
+ echo ''
+}
+
+changesetfs_list_git()
+{
+ WORK_DIR=$1; shift
+ fname=$1; shift
+ USER=$1; shift
+ DATE=$1; shift
+
+ GIT_DIR=`found_git_dir "$WORK_DIR"`
+ [ -z "$GIT_DIR" ] && GIT_DIR=$WORK_DIR
+ curr_year=`date +"%Y"`
+
+ git --git-dir="$GIT_DIR" log --abbrev=7 --pretty="format:%at %h %an" -- "$fname" | while read TIMESTAMP chset author
+ do
+ year=`date -d @"$TIMESTAMP" +"%Y"`
+ [ "$year" = "$curr_year" ] && {
+ DATE=`date -d @"$TIMESTAMP" +"%b %d %H:%M"`
+ } || {
+ DATE=`date -d @"$TIMESTAMP" +"%b %d %Y"`
+ }
+ NAME="$chset $author"
+ echo "-rw-rw-rw- 1 $USER 0 0 $DATE $NAME `basename $fname`"
+ done
+}
+
+changesetfs_copyout_git()
+{
+ WORK_DIR=$1; shift
+ fname=$1; shift
+ orig_fname=$1;shift
+ output_fname=$1;shift
+
+ chset=`echo "$orig_fname"| cut -f 1 -d " "`
+
+ GIT_DIR=`found_git_dir "$WORK_DIR"`
+ [ -z "$GIT_DIR" ] && GIT_DIR=$WORK_DIR
+
+ filecommit=`git --git-dir="$GIT_DIR" show --raw --pretty=tformat:%h "$chset" -- "$fname"| \
+ tail -n1 | \
+ sed 's@^::[0-9]*\s*[0-9]*\s*[0-9]*\s*@@' | \
+ sed 's@^:[0-9]*\s*[0-9]*\s*@@' | \
+ cut -d'.' -f 1`
+ git --git-dir="$GIT_DIR" show "$filecommit" > "$output_fname"
+}
+
+# --- COMMON --------------------------------------------------------------------
+
+changesetfs_list()
+{
+ VCS_type=$1; shift
+ WORK_DIR=$1; shift
+ fname=$1; shift
+
+ DATE=`date +"%b %d %H:%M"`
+ USER=`whoami`
+
+ case "$VCS_type" in
+ git) changesetfs_list_git "$WORK_DIR" "$fname" "$USER" "$DATE" ;;
+ esac
+}
+
+changesetfs_copyout()
+{
+ VCS_type=$1; shift
+ WORK_DIR=$1; shift
+ fname=$1; shift
+
+ case "$VCS_type" in
+ git) changesetfs_copyout_git "$WORK_DIR" "$fname" "$@" ;;
+ esac
+
+}
+
+# --- MAIN ----------------------------------------------------------------------
+
+command=$1; shift
+tmp_file=$1; shift
+
+WORK_DIR=`head -n1 $tmp_file`
+fname=`tail -n2 $tmp_file | head -n1`
+VCS_type=`tail -n1 $tmp_file`
+
+case "$command" in
+ list) changesetfs_list "$VCS_type" "$WORK_DIR" "$fname" ;;
+ copyout) changesetfs_copyout "$VCS_type" "$WORK_DIR" "$fname" "$@" ;;
+ *) exit 1 ;;
+esac
+exit 0
diff --git a/src/vfs/extfs/helpers/deb.in b/src/vfs/extfs/helpers/deb.in
new file mode 100644
index 0000000..abc98aa
--- /dev/null
+++ b/src/vfs/extfs/helpers/deb.in
@@ -0,0 +1,203 @@
+#! @PERL@
+#
+# Written by Fernando Alegre <alegre@debian.org> 1996
+#
+# Applied patch by Dimitri Maziuk <emaziuk@curtin.edu.au> 1997
+# (to handle new tar format)
+#
+# Modified by Fernando Alegre <alegre@debian.org> 1997
+# (to handle both new and old tar formats)
+#
+# Modified by Patrik Rak <prak@post.cz> 1998
+# (add by Michael Bramer Debian-mc-maintainer <grisu@debian.org>)
+# (to allow access to package control files)
+#
+# Modified by Martin Bialasinski <martinb@debian.org> 1999
+# (deal with change in tar format)
+#
+#
+# Copyright (C) 1997 Free Software Foundation
+#
+
+sub quote {
+ $_ = shift(@_);
+ s/([^\w\/.+-])/\\$1/g;
+ return($_);
+}
+
+sub mcdebfs_list
+{
+#
+# CAVEAT: Hard links are listed as if they were symlinks
+# Empty directories do not appear at all
+#
+ local($archivename)=@_;
+ local $qarchivename = quote($archivename);
+ chop($date=`LC_ALL=C date "+%b %d %H:%M"`);
+ chop($info_size=`dpkg -I $qarchivename | wc -c`);
+ $install_size=length($pressinstall);
+
+ print "dr-xr-xr-x 1 root root 0 $date CONTENTS\n";
+ print "dr-xr-xr-x 1 root root 0 $date DEBIAN\n";
+ print "-r--r--r-- 1 root root $info_size $date INFO\n";
+ print "-r-xr--r-- 1 root root $install_size $date INSTALL\n";
+
+ if ( open(PIPEIN, "LC_ALL=C dpkg-deb -c $qarchivename |") )
+ {
+ while(<PIPEIN>)
+ {
+ @_ = split;
+
+ $perm=$_[0]; $owgr=$_[1]; $size=$_[2];
+ if($_[3] =~ /^\d\d\d\d\-/) { # New tar format
+
+ ($year,$mon,$day) = split(/-/,$_[3]);
+ $month = ("Gee","Jan","Feb","Mar","Apr","May","Jun",
+ "Jul","Aug","Sep","Oct","Nov","Dec")[$mon] || "Gee";
+ $time=$_[4];
+ $pathindex=5;
+ }
+ else {
+ $mstring='GeeJanFebMarAprMayJunJulAugSepOctNovDec';
+ $month=$_[3];
+ $mon=index($mstring,$month) / 3;
+ $day=$_[4];
+ $time=$_[5];
+ $year=$_[6];
+ $pathindex=7;
+ }
+
+ $path=$_[$pathindex++];
+ # remove leading ./
+ $path=~s/^\.\///;
+ next if ($path eq '');
+ $arrow=$_[$pathindex++];
+ $link=$_[$pathindex++];
+ $link2=$_[$pathindex++];
+
+ $owgr=~s!/! !;
+ if($arrow eq 'link')
+ {
+# report hard links as soft links
+ $arrow='->'; $link="/$link2";
+ substr($perm, 0, 1) = "l";
+ }
+ if($arrow ne '')
+ {
+ $arrow=' ' . $arrow;
+ $link= ' ' . $link;
+ }
+ $now=`date "+%Y %m"`;
+ ($thisyear, $thismon) = split(/ /, $now);
+ # show time for files younger than 6 months
+ # but not for files with dates in the future
+ if ($year * 12 + $mon > $thisyear * 12 + $thismon - 6 &&
+ $year * 12 + $mon <= $thisyear * 12 + $thismon) {
+ print "$perm 1 $owgr $size $month $day $time CONTENTS/$path$arrow$link\n";
+ } else {
+ print "$perm 1 $owgr $size $month $day $year CONTENTS/$path$arrow$link\n";
+ }
+ }
+ }
+ if ( open(PIPEIN, "LC_ALL=C dpkg-deb -I $qarchivename |") )
+ {
+ while(<PIPEIN>)
+ {
+ @_ = split;
+ $size=$_[0];
+ last if $size =~ /:/;
+ next if $size !~ /\d+/;
+ if($_[4] eq '*')
+ {
+ $perm='-r-xr-xr-x';
+ $name=$_[5];
+ }
+ else
+ {
+ $perm='-r--r--r--';
+ $name=$_[4];
+ }
+ print "$perm 1 root root $size $date DEBIAN/$name\n";
+ }
+ }
+}
+
+sub mcdebfs_copyout
+{
+ local($archive,$filename,$destfile)=@_;
+ local $qarchive = quote($archive);
+ local $qfilename = quote($filename);
+ local $qdestfile = quote($destfile);
+
+ if($filename eq "INFO")
+ {
+ system("dpkg-deb -I $qarchive > $qdestfile");
+ }
+ elsif($filename =~ /^DEBIAN/)
+ {
+ $qfilename=~s!^DEBIAN/!!;
+ system("dpkg-deb -I $qarchive $qfilename > $qdestfile");
+ }
+ elsif($filename eq "INSTALL")
+ {
+ if ( open(FILEOUT,">$destfile") )
+ {
+ print FILEOUT $pressinstall;
+ close FILEOUT;
+ system("chmod a+x $qdestfile");
+ }
+ }
+ else
+ {
+ # files can be prepended with ./ or not, depending on the version of tar
+ $qfilename=~s!^CONTENTS/!!;
+ system("dpkg-deb --fsys-tarfile $qarchive | tar xOf - $qfilename ./$qfilename > $qdestfile 2>/dev/null");
+ }
+}
+
+sub mcdebfs_run
+{
+ local($archive,$filename)=@_;
+ local $qarchive = quote($archive);
+ if($filename eq "INSTALL")
+ {
+ print "Installing $archive\n";
+ system("dpkg -i $qarchive");
+ }
+ else
+ {
+ use File::Temp qw(mkdtemp);
+ my $template = "/tmp/mcdebfs.run.XXXXXX";
+ $template="$ENV{MC_TMPDIR}/mcdebfs.XXXXXX" if ($ENV{MC_TMPDIR});
+ $tmpdir = mkdtemp($template);
+ $tmpcmd="$tmpdir/run";
+ &mcdebfs_copyout($archive, $filename, $tmpcmd);
+ system("chmod u+x $tmpcmd");
+ system($tmpcmd);
+ unlink($tmpcmd);
+ rmdir($tmpdir);
+ }
+}
+
+$pressinstall=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to reinstall everything...
+
+This is not a real file. It is a way to install the package you are browsing.
+
+To install this package go back to the panel and press Enter on this file.
+
+In Debian systems, a package is automatically upgraded when you install a new
+version of it. There is no special upgrade option. Install always works.
+
+EOInstall
+
+umask 077;
+
+if($ARGV[0] eq "list") { shift; &mcdebfs_list(@ARGV); exit 0; }
+elsif($ARGV[0] eq "copyout") { shift; &mcdebfs_copyout(@ARGV); exit 0; }
+elsif($ARGV[0] eq "run") { shift; &mcdebfs_run(@ARGV); exit 0; }
+
+exit 1;
+
diff --git a/src/vfs/extfs/helpers/deba.in b/src/vfs/extfs/helpers/deba.in
new file mode 100644
index 0000000..3d1a552
--- /dev/null
+++ b/src/vfs/extfs/helpers/deba.in
@@ -0,0 +1,107 @@
+#! @PERL@
+#
+# 1999 (c) Piotr Roszatycki <dexter@debian.org>
+# This software is under GNU license
+# last modification: 1999-12-08
+#
+# deba
+
+sub quote {
+ $_ = shift(@_);
+ s/([^\w\/.+-])/\\$1/g;
+ return($_);
+}
+
+sub list
+{
+ my($qarchive)=@_;
+ $qarchive = quote($qarchive);
+ chop($date=`LC_ALL=C date "+%m-%d-%Y %H:%M"`);
+ chop($info_size=`apt-cache show $qarchive | wc -c`);
+ $install_size=length($pressinstall);
+ $upgrade_size=length($pressupgrade);
+
+ print "-r--r--r-- 1 root root $info_size $date INFO\n";
+
+ chop($debd = `dpkg -s $qarchive | grep -i ^Version | sed 's/^version: //i'`);
+ chop($deba = `apt-cache show $qarchive | grep -i ^Version | sed 's/^version: //i'`);
+ if( ! $debd ) {
+ print "-r-xr--r-- 1 root root $install_size $date INSTALL\n";
+ } elsif( $debd ne $deba ) {
+ print "-r-xr--r-- 1 root root $upgrade_size $date UPGRADE\n";
+ }
+}
+
+sub copyout
+{
+ my($archive,$filename,$destfile)=@_;
+ my $qarchive = quote($archive);
+ my $qdestfile = quote($destfile);
+ if($filename eq "INFO") {
+ system("apt-cache show $qarchive > $qdestfile");
+ } elsif($filename eq "INSTALL") {
+ if ( open(FILEOUT, "> $destfile") ) {
+ print FILEOUT $pressinstall;
+ close FILEOUT;
+ system("chmod a+x $qdestfile");
+ }
+ } elsif($filename eq "UPGRADE") {
+ if ( open(FILEOUT, ">, $destfile") ) {
+ print FILEOUT $pressupgrade;
+ close FILEOUT;
+ system("chmod a+x $qdestfile");
+ }
+ } else {
+ die "extfs: $filename: No such file or directory\n";
+ }
+}
+
+sub run
+{
+ my($archive,$filename)=@_;
+ my $qarchive = quote($archive);
+ if($filename eq "INSTALL") {
+ system("apt-get install $qarchive");
+ } elsif($filename eq "UPGRADE") {
+ system("apt-get install $qarchive");
+ } else {
+ die "extfs: $filename: Permission denied\n";
+ }
+}
+
+$pressinstall=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to install this package...
+
+This is not a real file. It is a way to install the package you are browsing.
+
+To install this package go back to the panel and press Enter on this file.
+
+EOInstall
+
+$pressupgrade=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to upgrade this package...
+
+This is not a real file. It is a way to upgrade the package you are browsing.
+
+To upgrade this package go back to the panel and press Enter on this file.
+
+EOInstall
+
+
+umask 077;
+
+chop($name = `if [ -f "$ARGV[1]" ]; then cat $ARGV[1]; else echo $ARGV[1]; fi`);
+$name =~ s%.*/([0-9a-z.-]*)_.*%$1%;
+
+exit 1 unless $name;
+
+if($ARGV[0] eq "list") { &list($name); exit 0; }
+elsif($ARGV[0] eq "copyout") { &copyout($name,$ARGV[2],$ARGV[3]); exit 0; }
+elsif($ARGV[0] eq "run") { &run($name,$ARGV[2]); exit 0; }
+
+exit 1;
+
diff --git a/src/vfs/extfs/helpers/debd.in b/src/vfs/extfs/helpers/debd.in
new file mode 100644
index 0000000..858dadd
--- /dev/null
+++ b/src/vfs/extfs/helpers/debd.in
@@ -0,0 +1,362 @@
+#! @PERL@
+#
+# 1999 (c) Piotr Roszatycki <dexter@debian.org>
+# This software is under GNU license
+# last modification: 1999-12-08
+#
+# debd
+
+sub quote {
+ $_ = shift(@_);
+ s/([^\w\/.+-])/\\$1/g;
+ return($_);
+}
+
+sub bt
+{
+ my ($dt) = @_;
+ my (@time);
+ @time = localtime($dt);
+ $bt = sprintf "%02d-%02d-%d %02d:%02d", $time[4] + 1, $time[3],
+ $time[5] + 1900, $time[2], $time[1];
+ return $bt;
+}
+
+
+sub ft
+{
+ my ($f) = @_;
+ return "d" if -d $f;
+ return "l" if -l $f;
+ return "p" if -p $f;
+ return "S" if -S $f;
+ return "b" if -b $f;
+ return "c" if -c $f;
+ return "-";
+}
+
+sub fm
+{
+ my ($n) = @_;
+ my ($m);
+
+ if( $n & 0400 ) {
+ $m .= "r";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 0200 ) {
+ $m .= "w";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 04000 ) {
+ $m .= "s";
+ } elsif( $n & 0100 ) {
+ $m .= "x";
+ } else {
+ $m .= "-";
+ }
+
+ if( $n & 0040 ) {
+ $m .= "r";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 0020 ) {
+ $m .= "w";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 02000 ) {
+ $m .= "s";
+ } elsif( $n & 0010 ) {
+ $m .= "x";
+ } else {
+ $m .= "-";
+ }
+
+ if( $n & 0004 ) {
+ $m .= "r";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 0002 ) {
+ $m .= "w";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 01000 ) {
+ $m .= "t";
+ } elsif( $n & 0001 ) {
+ $m .= "x";
+ } else {
+ $m .= "-";
+ }
+
+ return $m;
+}
+
+sub ls {
+ my ($file) = @_;
+ my @stat = stat($file);
+ # mode, nlink, uid, gid, size, mtime, filename
+ printf "%s%s %d %d %d %d %s CONTENTS%s\n", ft($file), fm($stat[2] & 07777),
+ $stat[3], $stat[4], $stat[5], $stat[7], bt($stat[9]), $file;
+}
+
+sub list
+{
+ my($archive)=@_;
+ my $qarchive = quote($archive);
+ chop($date=`LC_ALL=C date "+%m-%d-%Y %H:%M"`);
+ chop($info_size=`dpkg -s $qarchive | wc -c`);
+ $repack_size=length($pressrepack);
+ $reinstall_size=length($pressreinstall);
+ $remove_size=length($pressremove);
+ $purge_size=length($presspurge);
+ $reconfigure_size=length($pressreconfigure);
+ $reinstall_size=length($pressreinstall);
+ $select_size=length($pressselect);
+ $unselect_size=length($pressunselect);
+
+ print "dr-xr-xr-x 1 root root 0 $date CONTENTS\n";
+ print "dr-xr-xr-x 1 root root 0 $date DEBIAN\n";
+ print "-r--r--r-- 1 root root $info_size $date INFO\n";
+ print "-r-xr--r-- 1 root root $purge_size $date DPKG-PURGE\n";
+
+ chop($status = `dpkg -s $qarchive | grep ^Status`);
+ if( $status =~ /deinstall/ ) {
+ print "-r-xr--r-- 1 root root $select_size $date DPKG-SELECT\n";
+ } elsif( $status =~ /install/ ) {
+ print "-r-xr--r-- 1 root root $unselect_size $date DPKG-UNSELECT\n";
+ }
+ if( $status !~ /config-files/ ) {
+ if ( -x "/usr/bin/dpkg-repack" ) {
+ print "-r-xr--r-- 1 root root $repack_size $date DPKG-REPACK\n";
+ }
+ print "-r-xr--r-- 1 root root $remove_size $date DPKG-REMOVE\n";
+ if ( -x "/usr/bin/apt-get" ) {
+ print "-r-xr--r-- 1 root root $remove_size $date APT-REMOVE\n";
+ print "-r-xr--r-- 1 root root $reinstall_size $date APT-REINSTALL\n";
+ print "-r-xr--r-- 1 root root $purge_size $date APT-PURGE\n";
+ }
+ }
+ if( -x "/usr/bin/dpkg-reconfigure" && -x "/var/lib/dpkg/info/$archive.config" ) {
+ print "-r-xr--r-- 1 root root $reconfigure_size $date DPKG-RECONFIGURE\n";
+ }
+
+
+
+ if ( open(PIPEIN, "LC_TIME=C LANG=C ls -l /var/lib/dpkg/info/$qarchive.* |") ) {
+ while(<PIPEIN>) {
+ chop;
+ next if /\.list$/;
+ s%/var/lib/dpkg/info/$archive.%DEBIAN/%;
+ print $_, "\n";
+ }
+ close PIPEIN;
+ }
+
+ if ( open(LIST, "/var/lib/dpkg/info/$archive.list") ) {
+ while(<LIST>) {
+ chop;
+ ls($_);
+ }
+ close LIST;
+ }
+}
+
+sub copyout
+{
+ my($archive,$filename,$destfile)=@_;
+ my $qarchive = quote($archive);
+ my $qfilename = quote($filename);
+ my $qdestfile = quote($destfile);
+
+ if($filename eq "INFO") {
+ system("dpkg -s $qarchive > $qdestfile");
+ } elsif($filename eq "DPKG-REPACK") {
+ if ( open(FILEOUT,">$destfile") ) {
+ print FILEOUT $pressrepack;
+ close FILEOUT;
+ system("chmod a+x $qdestfile");
+ }
+ } elsif($filename =~ /^DEBIAN/) {
+ $qfilename=~s!^DEBIAN/!!;
+ system("cat /var/lib/dpkg/info/$qarchive.$qfilename > $qdestfile");
+ } elsif($filename eq "DPKG-REMOVE" || $filename eq "APT-REMOVE") {
+ if ( open(FILEOUT,">$destfile") ) {
+ print FILEOUT $pressremove;
+ close FILEOUT;
+ system("chmod a+x $qdestfile");
+ }
+ } elsif($filename eq "DPKG-PURGE" || $filename eq "APT-PURGE") {
+ if ( open(FILEOUT,">$destfile") ) {
+ print FILEOUT $presspurge;
+ close FILEOUT;
+ system("chmod a+x $qdestfile");
+ }
+ } elsif($filename eq "DPKG-RECONFIGURE") {
+ if ( open(FILEOUT,">$destfile") ) {
+ print FILEOUT $pressreconfigure;
+ close FILEOUT;
+ system("chmod a+x $qdestfile");
+ }
+ } elsif($filename eq "APT-REINSTALL") {
+ if ( open(FILEOUT,">$destfile") ) {
+ print FILEOUT $pressreinstall;
+ close FILEOUT;
+ system("chmod a+x $destfile");
+ }
+ } elsif($filename eq "DPKG-SELECT") {
+ if ( open(FILEOUT,">$destfile") ) {
+ print FILEOUT $pressselect;
+ close FILEOUT;
+ system("chmod a+x $destfile");
+ }
+ } elsif($filename eq "DPKG-UNSELECT") {
+ if ( open(FILEOUT,">$destfile") ) {
+ print FILEOUT $pressunselect;
+ close FILEOUT;
+ system("chmod a+x $qdestfile");
+ }
+ } else {
+ $qfilename=~s!^CONTENTS!!;
+ system("cat $qfilename > $qdestfile");
+ }
+}
+
+sub run
+{
+ my($archive,$filename)=@_;
+ my $qarchive = quote($archive);
+ my $qfilename = quote($filename);
+ if($filename eq "DPKG-REMOVE") {
+ system("dpkg --remove $qarchive");
+ } elsif($filename eq "APT-REMOVE") {
+ system("apt-get remove $qarchive");
+ } elsif($filename eq "DPKG-PURGE") {
+ system("dpkg --purge $qarchive");
+ } elsif($filename eq "APT-PURGE") {
+ system("apt-get --purge remove $qarchive");
+ } elsif($filename eq "DPKG-REPACK") {
+ system("dpkg-repack $qarchive");
+ } elsif($filename eq "DPKG-SELECT") {
+ system("echo $aqrchive install | dpkg --set-selections");
+ } elsif($filename eq "DPKG-UNSELECT") {
+ system("echo $qarchive deinstall | dpkg --set-selections");
+ } elsif($filename eq "APT-REINSTALL") {
+ system("apt-get -u --reinstall install $qarchive");
+ } elsif($filename eq "DPKG-RECONFIGURE") {
+ system("dpkg-reconfigure $qarchive");
+ } elsif($filename=~/^DEBIAN/) {
+ $qfilename=~s!^DEBIAN!!;
+ system("/var/lib/dpkg/info/$qarchive.$qfilename");
+ } else {
+ $qfilename=~s!^CONTENTS!!;
+ system($qfilename);
+ }
+}
+
+$pressrepack=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to repack this package...
+
+This is not a real file. It is a way to repack the package you are browsing.
+
+To repack this package go back to the panel and press Enter on this file.
+
+EOInstall
+
+$pressreinstall=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to reinstall this package...
+
+This is not a real file. It is a way to reinstall the package you are browsing.
+
+To reinstall this package go back to the panel and press Enter on this file.
+
+EOInstall
+
+$pressremove=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to remove this package...
+
+This is not a real file. It is a way to remove the package you are browsing.
+
+To remove this package go back to the panel and press Enter on this file.
+
+EOInstall
+
+$presspurge=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to purge this package...
+
+This is not a real file. It is a way to purge the package you are browsing.
+
+To purge this package go back to the panel and press Enter on this file.
+
+EOInstall
+
+$pressreconfigure=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to reconfigure this package...
+
+This is not a real file. It is a way to reconfigure the package you are browsing.
+
+To reconfigure this package go back to the panel and press Enter on this file.
+
+EOInstall
+
+$pressreinstall=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to reinstall this package...
+
+This is not a real file. It is a way to reinstall the package you are browsing.
+
+To reinstall this package go back to the panel and press Enter on this file.
+
+EOInstall
+
+$pressselect=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to select this package...
+
+This is not a real file. It is a way to select the package you are browsing.
+
+To select this package go back to the panel and press Enter on this file.
+
+EOInstall
+
+$pressunselect=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to unselect this package...
+
+This is not a real file. It is a way to unselect the package you are browsing.
+
+To unselect this package go back to the panel and press Enter on this file.
+
+EOInstall
+
+umask 077;
+
+chop($name = `if [ -f "$ARGV[1]" ]; then cat $ARGV[1]; else echo $ARGV[1]; fi`);
+$name =~ s%.*/([0-9a-z.-]*)_.*%$1%;
+
+exit 1 unless $name;
+
+if($ARGV[0] eq "list") { &list($name); exit 0; }
+elsif($ARGV[0] eq "copyout") { &copyout($name,$ARGV[2],$ARGV[3]); exit 0; }
+elsif($ARGV[0] eq "run") { &run($name,$ARGV[2]); exit 0; }
+
+exit 1;
+
diff --git a/src/vfs/extfs/helpers/dpkg+.in b/src/vfs/extfs/helpers/dpkg+.in
new file mode 100644
index 0000000..048862e
--- /dev/null
+++ b/src/vfs/extfs/helpers/dpkg+.in
@@ -0,0 +1,337 @@
+#! @PERL@
+#
+# 1999 (c) Piotr Roszatycki <dexter@debian.org>
+# This software is under GNU license
+# last modification: 1999-12-08
+#
+# dpkg
+
+sub quote {
+ $_ = shift(@_);
+ s/([^\w\/.+-])/\\$1/g;
+ return($_);
+}
+
+sub bt
+{
+ my ($dt) = @_;
+ my (@time);
+ @time = localtime($dt);
+ $bt = sprintf "%02d-%02d-%d %02d:%02d", $time[4] + 1, $time[3],
+ $time[5] + 1900, $time[2], $time[1];
+ return $bt;
+}
+
+
+sub ft
+{
+ my ($f) = @_;
+ return "d" if -d $f;
+ return "l" if -l $f;
+ return "p" if -p $f;
+ return "S" if -S $f;
+ return "b" if -b $f;
+ return "c" if -c $f;
+ return "-";
+}
+
+sub fm
+{
+ my ($n) = @_;
+ my ($m);
+
+ if( $n & 0400 ) {
+ $m .= "r";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 0200 ) {
+ $m .= "w";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 04000 ) {
+ $m .= "s";
+ } elsif( $n & 0100 ) {
+ $m .= "x";
+ } else {
+ $m .= "-";
+ }
+
+ if( $n & 0040 ) {
+ $m .= "r";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 0020 ) {
+ $m .= "w";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 02000 ) {
+ $m .= "s";
+ } elsif( $n & 0010 ) {
+ $m .= "x";
+ } else {
+ $m .= "-";
+ }
+
+ if( $n & 0004 ) {
+ $m .= "r";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 0002 ) {
+ $m .= "w";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 01000 ) {
+ $m .= "t";
+ } elsif( $n & 0001 ) {
+ $m .= "x";
+ } else {
+ $m .= "-";
+ }
+
+ return $m;
+}
+
+sub ls {
+ my ($file,$path,$mode) = @_;
+
+ if (-f $file) {
+ my @stat = stat(_);
+ # mode, nlink, uid, gid, size, mtime, filename
+ printf "%s %d %d %d %d %s %s\n", $mode || ft($file).fm($stat[2] & 07777),
+ $stat[3], $stat[4], $stat[5], $stat[7], bt($stat[9]), $path;
+ }
+}
+
+$DATE=bt(time());
+
+sub list
+{
+ my ($pkg, $fn, $dn, $sz, $bt);
+ my %debs = ();
+ my %sects = ();
+
+ my($diversions,$architecture);
+ chop($diversions = `dpkg-divert --list 2>/dev/null`);
+ chop($architecture = `dpkg-architecture 2>/dev/null`);
+ chop($list = `dpkg -l '*' 2>/dev/null`);
+ chop($getselections = `dpkg --get-selections 2>/dev/null`);
+ chop($audit = `dpkg --audit 2>/dev/null`);
+ $sz = length($diversions);
+ print "-r--r--r-- 1 root root $sz $DATE DIVERSIONS\n";
+ $sz = length($architecture);
+ print "-r--r--r-- 1 root root $sz $DATE ARCHITECTURE\n";
+ $sz = length($list);
+ print "-r--r--r-- 1 root root $sz $DATE LIST\n";
+ $sz = length($getselections);
+ print "-r--r--r-- 1 root root $sz $DATE GET-SELECTIONS\n";
+ $sz = length($audit);
+ print "-r--r--r-- 1 root root $sz $DATE AUDIT\n";
+ $sz = length($pressconfigure);
+ print "-r-xr--r-- 1 root root $sz $DATE CONFIGURE\n";
+ $sz = length($pressremove);
+ print "-r-xr--r-- 1 root root $sz $DATE REMOVE\n";
+ $sz = length($pressclearavail);
+ print "-r-xr--r-- 1 root root $sz $DATE CLEAR-AVAIL\n";
+ $sz = length($pressforgetoldunavail);
+ print "-r-xr--r-- 1 root root $sz $DATE FORGET-OLD-UNAVAIL\n";
+ ls("/var/lib/dpkg/status","STATUS","-r--r--r--");
+ # ls("/var/lib/dpkg/available","AVAILABLE","-r--r--r--");
+
+ print "drwxr-xr-x 1 root root 0 $DATE all\n";
+
+ open STAT, "/var/lib/dpkg/status"
+ or exit 1;
+ while( <STAT> ) {
+ chop;
+ if( /^([\w-]*): (.*)/ ) {
+ $pkg = $2 if( lc($1) eq 'package' );
+ $debs{$pkg}{lc($1)} = $2;
+ }
+ }
+ close STAT;
+
+ foreach $pkg (sort keys %debs) {
+ next if $debs{$pkg}{status} =~ /not-installed/;
+ $fn = $debs{$pkg}{package}. "_". $debs{$pkg}{version};
+ $dn = $debs{$pkg}{section};
+ if( ! $dn ) {
+ $dn = "unknown";
+ } elsif( $dn =~ /^(non-us)$/i ) {
+ $dn .= "/main";
+ } elsif( $dn !~ /\// ) {
+ $dn = "main/". $dn;
+ }
+ unless( $sects{$dn} ) {
+ my $sub = $dn;
+ while( $sub =~ s!^(.*)/[^/]*$!$1! ) {
+ unless( $sects{$sub} ) {
+ print "drwxr-xr-x 1 root root 0 $DATE $sub/\n";
+ $sects{$sub} = 1;
+ }
+ }
+ print "drwxr-xr-x 1 root root 0 $DATE $dn/\n";
+ $sects{$dn} = 1;
+ }
+ $sz = $debs{$pkg}{'status'} =~ /config-files/ ? 0 : $debs{$pkg}{'installed-size'} * 1024;
+ @stat = stat("/var/lib/dpkg/info/".$debs{$pkg}{package}.".list");
+ $bt = bt($stat[9]);
+ print "-rw-r--r-- 1 root root $sz $bt $dn/$fn.debd\n";
+ print "lrwxrwxrwx 1 root root $sz $bt all/$fn.debd -> ../$dn/$fn.debd\n";
+ }
+}
+
+sub copyout
+{
+ my($archive,$filename) = @_;
+ my $qfilename = quote($filename);
+ if( $archive eq 'DIVERSIONS' ) {
+ system("dpkg-divert --list > $qfilename 2>/dev/null");
+ } elsif( $archive eq 'ARCHITECTURE' ) {
+ system("dpkg-architecture > $qfilename 2>/dev/null");
+ } elsif( $archive eq 'LIST' ) {
+ system("dpkg -l '*' > $qfilename 2>/dev/null");
+ } elsif( $archive eq 'AUDIT' ) {
+ system("dpkg --audit > $qfilename 2>/dev/null");
+ } elsif( $archive eq 'GET-SELECTIONS' ) {
+ system("dpkg --get-selections > $qfilename 2>/dev/null");
+ } elsif( $archive eq 'STATUS' ) {
+ system("cp /var/lib/dpkg/status $qfilename");
+ } elsif( $archive eq 'AVAILABLE' ) {
+ system("cp /var/lib/dpkg/available $qfilename");
+ } elsif( $archive eq 'CONFIGURE' ) {
+ open O, ">$filename";
+ print O $pressconfigure;
+ close O;
+ } elsif( $archive eq 'REMOVE' ) {
+ open O, ">$filename";
+ print O $pressremove;
+ close O;
+ } elsif( $archive eq 'CLEAR-AVAIL' ) {
+ open O, ">$filename";
+ print O $pressclearavail;
+ close O;
+ } elsif( $archive eq 'FORGET-OLD-UNAVAIL' ) {
+ open O, ">$filename";
+ print O $pressforgetoldunavail;
+ close O;
+ } else {
+ open O, ">$filename";
+ print O $archive, "\n";
+ close O;
+ }
+}
+
+# too noisy but less dangerouse
+sub copyin
+{
+ my($archive,$filename) = @_;
+ my $qfilename = quote($filename);
+ if( $archive =~ /\.deb$/ ) {
+ system("dpkg -i $qfilename>/dev/null");
+ } else {
+ die "extfs: cannot create regular file \`$archive\': Permission denied\n";
+ }
+}
+
+sub run
+{
+ my($archive,$filename) = @_;
+ if( $archive eq 'CONFIGURE' ) {
+ system("dpkg --pending --configure");
+ } elsif( $archive eq 'REMOVE' ) {
+ system("dpkg --pending --remove");
+ } elsif( $archive eq 'CLEAR-AVAIL' ) {
+ system("dpkg --clear-avail");
+ } elsif( $archive eq 'FORGET-OLD-UNAVAIL' ) {
+ system("dpkg --forget-old-unavail");
+ } else {
+ die "extfs: $filename: command not found\n";
+ }
+}
+
+# Disabled - too dangerous and too noisy
+sub rm_disabled
+{
+ my($archive) = @_;
+ if( $archive =~ /\.debd?$/ ) {
+ my $qname = quote($archive);
+ $qname =~ s%.*/%%g;
+ $qname =~ s%_.*%%g;
+ system("if dpkg -s $qname | grep ^Status | grep -qs config-files; \
+ then dpkg --purge $qname>/dev/null; \
+ else dpkg --remove $qname>/dev/null; fi");
+ die("extfs: $archive: Operation not permitted\n") if $? != 0;
+ } else {
+ die "extfs: $archive: Operation not permitted\n";
+ }
+}
+
+
+$pressconfigure=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to configure all
+ non configured packages.
+
+This is not a real file. It is a way to configure all non configured packages.
+
+To configure packages go back to the panel and press Enter on this file.
+
+EOInstall
+
+$pressremove=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to remove all
+ unselected packages.
+
+This is not a real file. It is a way to remove all unselected packages.
+
+To remove packages go back to the panel and press Enter on this file.
+
+EOInstall
+
+$pressforgetoldunavail=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to forget about
+ uninstalled unavailable packages.
+
+This is not a real file. It is a way to forget about uninstalled
+unavailable packages.
+
+To forget this information go back to the panel and press Enter on this file.
+
+EOInstall
+
+$pressclearavail=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to erase the existing
+ information about what packages are available.
+
+This is not a real file. It is a way to erase the existing information
+about what packages are available.
+
+To clear this information go back to the panel and press Enter on this file.
+
+EOInstall
+
+
+
+# override any locale for dates
+$ENV{"LC_ALL"}="C";
+
+if ($ARGV[0] eq "list") { list(); exit(0); }
+elsif ($ARGV[0] eq "copyout") { copyout($ARGV[2], $ARGV[3]); exit(0); }
+elsif ($ARGV[0] eq "copyin") { copyin($ARGV[2], $ARGV[3]); exit(0); }
+elsif ($ARGV[0] eq "run") { run($ARGV[2],$ARGV[3]); exit(0); }
+#elsif ($ARGV[0] eq "rm") { rm($ARGV[2]); exit(0); }
+exit(1);
+
diff --git a/src/vfs/extfs/helpers/gitfs+ b/src/vfs/extfs/helpers/gitfs+
new file mode 100755
index 0000000..66861fb
--- /dev/null
+++ b/src/vfs/extfs/helpers/gitfs+
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+LANG=C
+export LANG
+LC_TIME=C
+export LC_TIME
+
+umask 077
+prefix='[git]'
+
+gitfs_list()
+{
+ DATE=`date +"%b %d %H:%M"`
+ GIT_DIR="$2/.git"
+ user=`whoami`
+ git ls-files -v -c -m -d | sort -k 2 | uniq -f 1 | while read status fname
+ do
+ [ "$status" = "H" ] && status=" "
+ [ "$status" = "C" ] && status="*"
+ echo "-r--r--r-- 1 $user 0 0 $DATE `dirname $fname`/$prefix$status`basename $fname`"
+ done
+}
+
+gitfs_copyout()
+{
+ printf "%s\n" "$2" > "$4"
+ b=`echo "$prefix"| wc -c`
+ b=`expr "$b" + 1`
+ # remove prefix from file name
+ echo "`dirname "$3"`/`basename "$3" | tail -c+"$b"`" >> "$4"
+ echo "git" >> "$4"
+}
+
+case "$1" in
+ list) gitfs_list "$@" ;;
+ copyout) gitfs_copyout "$@" ;;
+ *) exit 1 ;;
+esac
+exit 0
diff --git a/src/vfs/extfs/helpers/hp48+.in b/src/vfs/extfs/helpers/hp48+.in
new file mode 100644
index 0000000..17c03ab
--- /dev/null
+++ b/src/vfs/extfs/helpers/hp48+.in
@@ -0,0 +1,132 @@
+#!/bin/sh
+#
+# Written by Christofer Edvardsen <ce@earthling.net>, Feb 1998
+#
+# This script makes it possible to view and copy files to/from a hp48
+# (tested with a HP48G and the emulator x48)
+#
+# To use the hp48 external filesystem:
+# - read the relevant parts of your HP48 manual
+# - install kermit
+# - connect the HP48 to your computer or start x48
+# - below change the line which reflects the serial device you use
+# - configure your HP48 (<left shift> - i/o - iopar):
+# port: wire
+# baud: 9600
+# transfer format: binary (fast transfers) or
+# ascii (editable on the pc)
+# - start the server on the HP48: <left shift> - i/o - srvr - serve
+# or the shortcut <right shift> - <right arrow>
+# - on MC's commandline enter "cd hp48://"
+#
+# Make sure you have kermit installed and that it's using the right serial
+# device by changing /dev/ttyXX on the next line
+AWK=@AWK@
+KERMIT=${MC_TEST_EXTFS_LIST_CMD:-"kermit -l /dev/ttyS1 -b 9600"}
+
+NOW=`date +"%m-%d-%Y %H:%M"`
+
+hp48_cmd()
+{
+$KERMIT -C "SET EXIT WARNING OFF,REMOTE $1,QUIT"
+}
+
+hp48_cd()
+{
+(echo SET EXIT WARNING OFF;echo REMOTE HOST HOME
+for HP48_DIR in `echo "$1" | tr '/' ' '`;do
+ if [ "x$HP48_DIR" != "x." ];then echo REMOTE HOST "$HP48_DIR"; fi
+done
+echo QUIT)| $KERMIT -B >/dev/null
+}
+
+#
+# Parses the reply to the DIRECTORY command.
+#
+# Here's an example reply (taken from [1][2]):
+#
+# { HOME } 105617
+# STRAY 185.5 Directory 29225
+# YEN 30.5 Program 53391
+# JYTLIGHT 21848.5 String 62692
+# IOPAR 37.5 List 61074
+#
+# The meaning of the fields (according to [3][4]):
+#
+# { Current_directory } Free_space
+# Object_name Object_size_bytes Object_type Object_CRC
+# ...
+#
+# [1] http://newarea48.tripod.com/kermit.html
+# [2] http://www.hpmuseum.org/forum/thread-4684.html
+# [3] https://groups.google.com/d/msg/comp.sys.hp48/bYTCu9K3k20/YWQfF--W3EEJ
+# [4] http://www.columbia.edu/kermit/hp48.html (also has a link to the HP's user manual).
+#
+hp48_parser()
+{
+HP48_DIRS=
+
+read -r INPUT
+while [ "x$INPUT" != "xEOF" ]
+do
+ set -- $INPUT
+
+ obj_name=$1
+ obj_size=$2
+ obj_type=$3
+
+ obj_size=`echo $obj_size | $AWK '{ print int($0) }'` # Truncates floats to ints; anything else to "0".
+
+ if [ "$obj_size" != "0" ]; then # Skips the 1st reply line (purportedly there aren't zero-size files b/c, according to resource [4], the size is "including name").
+ case "$obj_type" in
+ Directory)
+ HP48_DIRS="$HP48_DIRS $obj_name"
+ printf "%crwxr-xr-x 1 %-8d %-8d %8d %s %s\n" 'd' \
+ 0 0 $obj_size "$NOW" "$HP48_CDIR/$obj_name"
+ ;;
+ *)
+ printf "%crw-r--r-- 1 %-8d %-8d %8d %s %s\n" '-' \
+ 0 0 $obj_size "$NOW" "$HP48_CDIR/$obj_name"
+ ;;
+ esac
+ fi
+
+ read -r INPUT
+done
+
+for HP48_DIR in $HP48_DIRS;
+do
+ HP48_PDIR="$HP48_CDIR"
+ HP48_CDIR="$HP48_CDIR/$HP48_DIR"; hp48_cmd "HOST $HP48_DIR" >/dev/null
+ hp48_list
+ HP48_CDIR="$HP48_PDIR"; hp48_cmd "HOST UPDIR" >/dev/null
+done
+}
+
+hp48_list()
+{
+# It's hard to see why this "EOF" thing is needed. The loop above can be changed to "while read -r obj_name ...". @TODO.
+{ hp48_cmd "DIRECTORY"; echo; echo EOF; } | hp48_parser
+}
+
+# override any locale for dates
+LC_ALL=C
+export LC_ALL
+
+case "$1" in
+list) HP48_CDIR=
+ hp48_cmd "HOST HOME" >/dev/null
+ hp48_list
+ exit 0;;
+copyout)
+ cd "`dirname "$4"`"
+ hp48_cd "`dirname "$3"`"
+ $KERMIT -B -g "`basename "$3"`" -a "$4" >/dev/null
+ exit 0;;
+copyin)
+ cd "`dirname "$4"`"
+ hp48_cd "`dirname "$3"`"
+ $KERMIT -B -s "$4" -a "`basename "$3"`" >/dev/null
+ exit 0;;
+esac
+exit 1
diff --git a/src/vfs/extfs/helpers/iso9660.in b/src/vfs/extfs/helpers/iso9660.in
new file mode 100644
index 0000000..5a6f1d5
--- /dev/null
+++ b/src/vfs/extfs/helpers/iso9660.in
@@ -0,0 +1,235 @@
+#! /bin/sh
+# Midnight Commander - ISO9660 VFS for MC
+# based on lslR by Tomas Novak <tnovak@ipex.cz> April 2000
+#
+# Copyright (C) 2000, 2003
+# The Free Software Foundation, Inc.
+#
+# Written by:
+# Michael Shigorin <mike@altlinux.org>,
+# Grigory Milev <week@altlinux.org>,
+# Kachalov Anton <mouse@linux.ru.net>, 2003
+# Victor Ananjevsky <ananasik@gmail.com>, 2013
+# slava zanko <slavazanko@gmail.com>, 2013
+#
+# This file is part of the Midnight Commander.
+#
+# The Midnight Commander is free software: you can redistribute it
+# and/or modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the License,
+# or (at your option) any later version.
+#
+# The Midnight Commander is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#*** include section (source functions, for example) *******************
+
+#*** file scope functions **********************************************
+
+XORRISO=$(which xorriso 2>/dev/null)
+
+xorriso_list() {
+ if test -z "$XORRISO"; then
+ return 1
+ fi
+ local dir attr ln usr gr sz dt1 dt2 dt3 nm len name lsl r
+ dir="${2:-/}"
+ lsl=$( $XORRISO -abort_on FATAL -dev stdio:"$1" -cd "$dir" -lsl 2> /dev/null )
+ r=$?
+ test $r -gt 0 && return $r
+
+ echo "$lsl" | grep "^[-d]" | \
+ while read attr ln usr gr sz dt1 dt2 dt3 nm ; do
+ len=$((${#nm} - 1))
+ name=$(printf -- "$nm" | cut -c2-$len) # remove quotes
+
+ if test $(printf -- "$attr" | cut -c1-1) != "d"; then
+ printf -- "%s %s %s %s %s %s %s %s %s/%s\n" "$attr" "$ln" "$usr" "$gr" "$sz" "$dt1" "$dt2" "$dt3" "$dir" "$name"
+ else
+ xorriso_list "$1" "$dir/$name"
+ fi
+ done
+}
+
+xorriso_copyout() {
+ if test -z "$XORRISO"; then
+ return 1
+ fi
+ $XORRISO -dev stdio:"$1" -osirrox on -extract "$2" "$3" >/dev/null 2>&1
+}
+
+xorriso_copyin() {
+ if test -z "$XORRISO"; then
+ return 1
+ fi
+ $XORRISO -dev stdio:"$1" -cpr "$3" "$2" >/dev/null 2>&1
+}
+
+xorriso_mkdir() {
+ if test -z "$XORRISO"; then
+ return 1
+ fi
+ $XORRISO -dev stdio:"$1" -mkdir "$2" >/dev/null 2>&1
+}
+
+xorriso_rmdir() {
+ if test -z "$XORRISO"; then
+ return 1
+ fi
+ $XORRISO -dev stdio:"$1" -rmdir "$2" >/dev/null 2>&1
+}
+
+xorriso_rm() {
+ if test -z "$XORRISO"; then
+ return 1
+ fi
+ $XORRISO -dev stdio:"$1" -rm "$2" >/dev/null 2>&1
+}
+
+# tested to comply with isoinfo 2.0's output
+test_iso () {
+ ISOINFO=$(which isoinfo 2>/dev/null)
+ if test -z "$ISOINFO"; then
+ echo "isoinfo not found" >&2
+ return 1
+ fi
+
+ CHARSET=$(locale charmap 2>/dev/null)
+ if test -z "$CHARSET"; then
+ CHARSET=$(locale 2>/dev/null | grep LC_CTYPE | sed -n -e 's/.*\.\(.*\)"$/\1/p')
+ fi
+ if test -n "$CHARSET"; then
+ CHARSET=$(echo "$CHARSET" | tr '[A-Z]' '[a-z]' | sed -e 's/^iso-/iso/')
+ $ISOINFO -j $CHARSET -i /dev/null 2>&1 | grep "Iconv not yet supported\|Unknown charset" >/dev/null && CHARSET=
+ fi
+ if test -n "$CHARSET"; then
+ JOLIET_OPT="-j $CHARSET -J"
+ else
+ JOLIET_OPT="-J"
+ fi
+
+ ISOINFO_D_I="$($ISOINFO -d -i "$1" 2>/dev/null)"
+ ISOINFO="$ISOINFO -R"
+
+ echo "$ISOINFO_D_I" | grep "UCS level 1\|NO Joliet" > /dev/null || ISOINFO="$ISOINFO $JOLIET_OPT"
+
+ if [ $(echo "$ISOINFO_D_I" | grep "Joliet with UCS level 3 found" | wc -l) = 1 \
+ -a $(echo "$ISOINFO_D_I" | grep "NO Rock Ridge" | wc -l) = 1 ] ; then
+ SEMICOLON="YES"
+ fi
+}
+
+mcisofs_list () {
+ local lsl r
+
+ # left as a reminder to implement compressed image support =)
+ case "$1" in
+ *.lz) MYCAT="lzip -dc";;
+ *.lz4) MYCAT="lz4 -dc";;
+ *.lzma) MYCAT="lzma -dc";;
+ *.xz) MYCAT="xz -dc";;
+ *.zst) MYCAT="zstd -dc";;
+ *.bz2) MYCAT="bzip2 -dc";;
+ *.gz) MYCAT="gzip -dc";;
+ *.z) MYCAT="gzip -dc";;
+ *.Z) MYCAT="gzip -dc";;
+ *) MYCAT="cat";;
+ esac
+
+ lsl=$($ISOINFO -l -i "$1" 2>/dev/null)
+ r=$?
+ test $r -gt 0 && return $r
+
+ echo "$lsl" | @AWK@ -v SEMICOLON=$SEMICOLON '
+BEGIN {
+ dir="";
+ # Pattern to match 8 first fields.
+ rx = "[^ ]+[ ]+";
+ rx = "^" rx rx rx rx rx rx rx rx;
+ irx = "^\\[ *-?[0-9]* *[0-9]+\\] +";
+}
+/^$/ { next }
+/^d---------/ { next }
+/^Directory listing of [^ ].*$/ {
+ dir=substr($0, 23);
+ next;
+}
+{ $11 != "" } {
+ name=$0
+ sub(rx, "", name)
+ attr=substr($0, 1, length($0)-length(name))
+ # strip inodes and extra dir entries; fix perms
+ sub(irx, "", name)
+ sub("^---------- 0 0 0", "-r--r--r-- 1 root root", attr)
+ sub(" $", "", name)
+ # for Joliet UCS level 3
+ if (SEMICOLON == "YES") sub(";1$", "", name);
+ ## sub(";[0-9]+$", "", name) ## would break copyout
+ # skip . and ..
+ if (name == ".") next;
+ if (name == "..") next;
+ printf "%s%s%s\n", attr, dir, name
+}'
+}
+
+mcisofs_copyout () {
+ if [ "x$SEMICOLON" = "xYES" ]; then
+ $ISOINFO -i "$1" -x "/$2;1" 2>/dev/null > "$3"
+ else
+ $ISOINFO -i "$1" -x "/$2" 2>/dev/null > "$3"
+ fi
+}
+
+#*** main code *********************************************************
+
+LC_ALL=C
+
+cmd="$1"
+shift
+
+case "$cmd" in
+ list)
+ xorriso_list "$@" || {
+ test_iso "$@" || exit 1
+ mcisofs_list "$@" || exit 1
+ }
+ exit 0
+ ;;
+ rm)
+ xorriso_rm "$@" || {
+ exit 1
+ }
+ exit 0
+ ;;
+ rmdir)
+ xorriso_rmdir "$@" || {
+ exit 1
+ }
+ exit 0
+ ;;
+ mkdir)
+ xorriso_mkdir "$@" || {
+ exit 1
+ }
+ exit 0
+ ;;
+ copyin)
+ xorriso_copyin "$@" || {
+ exit 1
+ }
+ exit 0
+ ;;
+ copyout)
+ xorriso_copyout "$@" || {
+ test_iso "$@" || exit 1
+ mcisofs_copyout "$@" || exit 1
+ }
+ exit 0
+ ;;
+esac
+exit 1
diff --git a/src/vfs/extfs/helpers/lslR.in b/src/vfs/extfs/helpers/lslR.in
new file mode 100644
index 0000000..69b663b
--- /dev/null
+++ b/src/vfs/extfs/helpers/lslR.in
@@ -0,0 +1,74 @@
+#! /bin/sh
+
+# Based on previous version of lslR
+# Modified by Tomas Novak <tnovak@ipex.cz> April 2000
+# (to allow spaces in filenames)
+#
+# It's assumed that lslR was generated in C locale.
+LC_ALL=C
+export LC_ALL=C
+
+AWK=@AWK@
+
+mclslRfs_list () {
+case "$1" in
+ *.lz) MYCAT="lzip -dc";;
+ *.lz4) MYCAT="lz4 -dc";;
+ *.lzma) MYCAT="lzma -dc";;
+ *.xz) MYCAT="xz -dc";;
+ *.zst) MYCAT="zstd -dc";;
+ *.bz2) MYCAT="bzip2 -dc";;
+ *.gz) MYCAT="gzip -dc";;
+ *.z) MYCAT="gzip -dc";;
+ *.Z) MYCAT="gzip -dc";;
+ *) MYCAT="cat";;
+esac
+
+MYCAT=${MC_TEST_EXTFS_LIST_CMD:-$MYCAT} # Let the test framework hook in.
+
+$MYCAT "$1" | $AWK '
+BEGIN {
+ dir="";
+ empty=1;
+ rx = "[^ ]+[ ]+";
+ # Pattern to match 7 first fields.
+ rx7 = "^" rx rx rx rx rx rx "[^ ]+[ ]";
+ # Pattern to match 8 first fields.
+ rx8 = "^" rx rx rx rx rx rx rx "[^ ]+[ ]";
+}
+/^total\ [0-9]*$/ { next }
+/^$/ { empty=1; next }
+empty==1 && /:$/ {
+ empty=0
+ if ($0 ~ /^\//) dir=substr($0, 2);
+ else dir=$0;
+ if (dir ~ /\/:$/) sub(/:$/, "", dir);
+ else sub(/:$/, "/", dir);
+ if (dir ~ /^[ ]/) dir="./"dir;
+ next;
+}
+( NF > 7 ) {
+ empty=0
+ # gensub() is not portable.
+ name=$0
+ i=index($6, "-")
+ if (i) {
+ sub(rx7, "", name)
+ NF = 7
+ $6=substr($6,i+1)"-"substr($6,1,i-1)
+ }
+ else {
+ sub(rx8, "", name)
+ NF = 8
+ }
+ printf "%s %s%s\n", $0, dir, name
+}
+ {
+ empty=0
+}'
+}
+
+case "$1" in
+ list) mclslRfs_list "$2"; exit 0;;
+esac
+exit 1
diff --git a/src/vfs/extfs/helpers/mailfs.in b/src/vfs/extfs/helpers/mailfs.in
new file mode 100644
index 0000000..5bb373b
--- /dev/null
+++ b/src/vfs/extfs/helpers/mailfs.in
@@ -0,0 +1,219 @@
+#! @PERL@
+
+use bytes;
+use warnings;
+
+# MC extfs for (possibly compressed) Berkeley style mailbox files
+# Peter Daum <gator@cs.tu-berlin.de> (Jan 1998, mc-4.1.24)
+
+$zcat="zcat"; # gunzip to stdout
+$bzcat="bzip2 -dc"; # bunzip2 to stdout
+$lzipcat="lzip -dc"; # unlzip to stdout
+$lz4cat="lz4 -dc"; # unlz4 to stdout
+$lzcat="lzma -dc"; # unlzma to stdout
+$xzcat="xz -dc"; # unxz to stdout
+$zstdcat="zstd -dc"; # unzstd to stdout
+$file="file"; # "file" command
+$TZ='GMT'; # default timezone (for Date module)
+
+if (eval "require Date::Parse") {
+ import Date::Parse;
+ $parse_date=
+ sub {
+ local $ftime = str2time($_[0],$TZ);
+ $_ = localtime($ftime);
+ /^(...) (...) ([ \d]\d) (\d\d:\d\d):\d\d (\d\d\d\d)$/;
+ if ($ftime + 6 * 30 * 24 * 60 * 60 < $now ||
+ $ftime + 60 * 60 > $now) {
+ return "$2 $3 $5";
+ } else {
+ return "$2 $3 $4";
+ }
+ }
+} elsif (eval "require Date::Manip") {
+ import Date::Manip;
+ $parse_date=
+ sub {
+ return UnixDate($_[0], "%l"); # "ls -l" format
+ }
+} else { # use "light" version
+ $parse_date= sub {
+ local $mstring='GeeJanFebMarAprMayJunJulAugSepOctNovDec';
+ # assumes something like: Mon, 5 Jan 1998 16:08:19 +0200 (GMT+0200)
+ # if you have mails with another date format, add it here
+ if (/(\d\d?) ([A-Z][a-z][a-z]) (\d\d\d\d) (\d\d?):(\d\d)/) {
+ $day = $1;
+ $month = $2;
+ $mon = index($mstring,$month) / 3;
+ $year = $3;
+ $hour = $4;
+ $min = $5;
+ # pass time not year for files younger than roughly 6 months
+ # but not for files with dates more than 1-2 hours in the future
+ if ($year * 12 + $mon > $thisyear * 12 + $thismon - 7 &&
+ $year * 12 + $mon <= $thisyear * 12 + $thismon &&
+ ! (($year * 12 + $mon) * 31 + $day ==
+ ($thisyear * 12 + $thismon) * 31 + $thisday &&
+ $hour > $thishour + 2)) {
+ return "$month $day $hour:$min";
+ } else {
+ return "$month $day $year";
+ }
+ }
+ # Y2K bug.
+ # Date: Mon, 27 Mar 100 16:30:47 +0000 (GMT)
+ if (/(\d\d?) ([A-Z][a-z][a-z]) (1?\d\d) (\d\d?):(\d\d)/) {
+ $day = $1;
+ $month = $2;
+ $mon = index($mstring,$month) / 3;
+ $year = 1900 + $3;
+ $hour = $4;
+ $min = $5;
+ if ($year < 1970) {
+ $year += 100;
+ }
+ if ($year * 12 + $mon > $thisyear * 12 + $thismon - 7 &&
+ $year * 12 + $mon <= $thisyear * 12 + $thismon &&
+ ! (($year * 12 + $mon) * 31 + $day ==
+ ($thisyear * 12 + $thismon) * 31 + $thisday &&
+ $hour > $thishour + 2)) {
+ return "$month $day $hour:$min";
+ } else {
+ return "$month $day $year";
+ }
+ }
+ # AOLMail(SM).
+ # Date: Sat Jul 01 10:06:06 2000
+ if (/([A-Z][a-z][a-z]) (\d\d?) (\d\d?):(\d\d)(:\d\d)? (\d\d\d\d)/) {
+ $month = $1;
+ $mon = index($mstring,$month) / 3;
+ $day = $2;
+ $hour = $3;
+ $min = $4;
+ $year = $6;
+ if ($year * 12 + $mon > $thisyear * 12 + $thismon - 7 &&
+ $year * 12 + $mon <= $thisyear * 12 + $thismon &&
+ ! (($year * 12 + $mon) * 31 + $day ==
+ ($thisyear * 12 + $thismon) * 31 + $thisday &&
+ $hour > $thishour + 2)) {
+ return "$month $day $hour:$min";
+ } else {
+ return "$month $day $year";
+ }
+ }
+ # Fallback
+ return $fallback;
+ }
+}
+
+sub process_header {
+ while (<IN>) {
+ $size+=length;
+ s/\r$//;
+ last if /^$/;
+ die "unexpected EOF\n" if eof;
+ if (/^date:\s(.*)$/i) {
+ $date=&$parse_date($1);
+ } elsif (/^subject:\s(.*)$/i) {
+ $subj=lc($1);
+ $subj=~ s/^(re:\s?)+//gi; # no leading Re:
+ $subj=~ tr/a-zA-Z0-9//cd; # strip all "special" characters
+ } elsif (/^from:\s.*?(\w+)\@/i) {
+ $from=$1;
+ } elsif (/^to:\s.*?(\w+)\@/i) {
+ $to=lc($1);
+ }
+ }
+}
+
+sub print_dir_line {
+ $from=$to if ($from eq $user); # otherwise, it would look pretty boring
+ $date=localtime(time) if (!defined $date);
+ printf "-r-------- 1 $< $< %d %s %3.3d_%.25s\n",
+ $size, $date, $msg_nr, "${from}_${subj}";
+
+}
+
+sub mailfs_list {
+ my $blank = 1;
+ $user=$ENV{USER}||getlogin||getpwuid($<) || "nobody";
+
+ while(<IN>) {
+ s/\r$//;
+ if($blank && /^from\s+\w+(\.\w+)*@/i) { # Start of header
+ print_dir_line unless (!$msg_nr);
+ $size=length;
+ $msg_nr++;
+ ($from,$to,$subj,$date)=("none","none","none", "01-01-80");
+ process_header;
+ $line=$blank=0;
+ } else {
+ $size+=length;
+ $line++;
+ $blank= /^$/;
+ }
+ }
+ print_dir_line unless (!$msg_nr);
+ exit 0;
+}
+
+sub mailfs_copyout {
+ my($source,$dest)=@_;
+ exit 1 unless (open STDOUT, ">$dest");
+ ($nr)= ($source =~ /^(\d+)/); # extract message number from "filename"
+
+ my $blank = 1;
+ while(<IN>) {
+ s/\r$//;
+ if($blank && /^from\s+\w+(\.\w+)*@/i) {
+ $msg_nr++;
+ exit(0) if ($msg_nr > $nr);
+ $blank= 0;
+ } else {
+ $blank= /^$/;
+ }
+ print if ($msg_nr == $nr);
+ }
+}
+
+# main {
+exit 1 unless ($#ARGV >= 1);
+$msg_nr=0;
+$cmd=shift;
+$mbox_name=shift;
+my $mbox_qname = quotemeta ($mbox_name);
+$_=`$file $mbox_qname`;
+
+if (/gzip/) {
+ exit 1 unless (open IN, "$zcat $mbox_qname|");
+} elsif (/bzip/) {
+ exit 1 unless (open IN, "$bzcat $mbox_qname|");
+} elsif (/lzip/) {
+ exit 1 unless (open IN, "$lzipcat $mbox_qname|");
+} elsif (/lz4/) {
+ exit 1 unless (open IN, "$lz4cat $mbox_qname|");
+} elsif (/lzma/) {
+ exit 1 unless (open IN, "$lzcat $mbox_qname|");
+} elsif (/xz/) {
+ exit 1 unless (open IN, "$xzcat $mbox_qname|");
+} elsif (/zst/) {
+ exit 1 unless (open IN, "$zstdcat $mbox_qname|");
+} else {
+ exit 1 unless (open IN, "<$mbox_name");
+}
+
+umask 077;
+
+if($cmd eq "list") {
+ $now = time;
+ $_ = localtime($now);
+ /^... (... [ \d]\d \d\d:\d\d):\d\d \d\d\d\d$/;
+ $fallback = $1;
+ $nowstring=`date "+%Y %m %d %H"`;
+ ($thisyear, $thismon, $thisday, $thishour) = split(/ /, $nowstring);
+ &mailfs_list;
+ exit 0;
+}
+elsif($cmd eq "copyout") { &mailfs_copyout(@ARGV); exit 0; }
+
+exit 1;
diff --git a/src/vfs/extfs/helpers/patchfs.in b/src/vfs/extfs/helpers/patchfs.in
new file mode 100644
index 0000000..ee1e651
--- /dev/null
+++ b/src/vfs/extfs/helpers/patchfs.in
@@ -0,0 +1,427 @@
+#! @PERL@
+#
+# Written by Adam Byrtek <alpha@debian.org>, 2002
+# Rewritten by David Sterba <dave@jikos.cz>, 2009
+#
+# Extfs to handle patches in context and unified diff format.
+# Known issues: When name of file to patch is modified during editing,
+# hunk is duplicated on copyin. It is unavoidable.
+
+use bytes;
+use strict;
+use warnings;
+use POSIX;
+use File::Temp 'tempfile';
+
+# standard binaries
+my $lzip = 'lzip';
+my $lz4 = 'lz4';
+my $lzma = 'lzma';
+my $xz = 'xz';
+my $zstd = 'zstd';
+my $bzip = 'bzip2';
+my $gzip = 'gzip';
+my $fileutil = 'file -b';
+
+# date parsing requires Date::Parse from TimeDate module
+my $parsedates = eval 'require Date::Parse';
+
+# regular expressions
+my $unified_header=qr/^--- .*\t.*\n\+\+\+ .*\t.*\n$/;
+my $unified_extract=qr/^--- ([^\t]+).*\n\+\+\+ ([^\t]+)\s*(.*)\n/;
+my $unified_header2=qr/^--- .*\n\+\+\+ .*\n$/;
+my $unified_extract2=qr/^--- ([^\s]+).*\n\+\+\+ ([^\s]+)\s*(.*)\n/;
+my $unified_contents=qr/^([+\-\\ \n]|@@ .* @@)/;
+my $unified_hunk=qr/@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@.*\n/;
+
+my $context_header=qr/^\*\*\* .*\t.*\n--- .*\t.*\n$/;
+my $context_extract=qr/^\*\*\* ([^\t]+).*\n--- ([^\t]+)\s*(.*)\n/;
+my $context_header2=qr/^\*\*\* .*\n--- .*\n$/;
+my $context_extract2=qr/^\*\*\* ([^\s]+).*\n--- ([^\s]+)\s*(.*)\n/;
+my $context_contents=qr/^([!+\-\\ \n]|-{3} .* -{4}|\*{3} .* \*{4}|\*{15})/;
+
+my $ls_extract_id=qr/^[^\s]+\s+[^\s]+\s+([^\s]+)\s+([^\s]+)/;
+my $basename=qr|^(.*/)*([^/]+)$|;
+
+sub patchfs_canonicalize_path ($) {
+ my ($fname) = @_;
+ $fname =~ s,/+,/,g;
+ $fname =~ s,(^|/)(?:\.?\./)+,$1,;
+ return $fname;
+}
+
+# output unix date in a mc-readable format
+sub timef
+{
+ my @time=localtime($_[0]);
+ return sprintf '%02d-%02d-%02d %02d:%02d', $time[4]+1, $time[3],
+ $time[5]+1900, $time[2], $time[1];
+}
+
+# parse given string as a date and return unix time
+sub datetime
+{
+ # in case of problems fall back to 0 in unix time
+ # note: str2time interprets some wrong values (eg. " ") as 'today'
+ if ($parsedates && defined (my $t=str2time($_[0]))) {
+ return timef($t);
+ }
+ return timef(time);
+}
+
+# print message on stderr and exit
+sub error
+{
+ print STDERR $_[0], "\n";
+ exit 1;
+}
+
+# (compressed) input
+sub myin
+{
+ my ($qfname)=(quotemeta $_[0]);
+
+ $_=`$fileutil $qfname`;
+ if (/^'*lz4/) {
+ return "$lz4 -dc $qfname";
+ } elsif (/^'*lzip/) {
+ return "$lzip -dc $qfname";
+ } elsif (/^'*lzma/) {
+ return "$lzma -dc $qfname";
+ } elsif (/^'*xz/) {
+ return "$xz -dc $qfname";
+ } elsif (/^'*zst/) {
+ return "$zstd -dc $qfname";
+ } elsif (/^'*bzip/) {
+ return "$bzip -dc $qfname";
+ } elsif (/^'*gzip/) {
+ return "$gzip -dc $qfname";
+ } else {
+ return "cat $qfname";
+ }
+}
+
+# (compressed) output
+sub myout
+{
+ my ($qfname,$append)=(quotemeta $_[0],$_[1]);
+ my ($sep) = $append ? '>>' : '>';
+
+ $_=`$fileutil $qfname`;
+ if (/^'*lz4/) {
+ return "$lz4 -c $sep $qfname";
+ } elsif (/^'*lzip/) {
+ return "$lzip -c $sep $qfname";
+ } elsif (/^'*lzma/) {
+ return "$lzma -c $sep $qfname";
+ } elsif (/^'*xz/) {
+ return "$xz -c $sep $qfname";
+ } elsif (/^'*zst/) {
+ return "$zstd -c $sep $qfname";
+ } elsif (/^'*bzip/) {
+ return "$bzip -c $sep $qfname";
+ } elsif (/^'*gzip/) {
+ return "$gzip -c $sep $qfname";
+ } else {
+ return "cat $sep $qfname";
+ }
+}
+
+# select diff filename conforming with rules found in diff.info
+sub diff_filename
+{
+ my ($fsrc,$fdst)= @_;
+ # TODO: can remove these two calls later
+ $fsrc = patchfs_canonicalize_path ($fsrc);
+ $fdst = patchfs_canonicalize_path ($fdst);
+ if (!$fdst && !$fsrc) {
+ error 'Index: not yet implemented';
+ } elsif (!$fsrc || $fsrc eq '/dev/null') {
+ return ($fdst,'PATCH-CREATE/');
+ } elsif (!$fdst || $fdst eq '/dev/null') {
+ return ($fsrc,'PATCH-REMOVE/');
+ } elsif (($fdst eq '/dev/null') && ($fsrc eq '/dev/null')) {
+ error 'Malformed diff, missing a sane filename';
+ } else {
+ # fewest path name components
+ if ($fdst=~s|/|/|g < $fsrc=~s|/|/|g) {
+ return ($fdst,'');
+ } elsif ($fdst=~s|/|/|g > $fsrc=~s|/|/|g) {
+ return ($fsrc,'');
+ } else {
+ # shorter base name
+ if (($fdst=~/$basename/o,length $2) < ($fsrc=~/$basename/o,length $2)) {
+ return ($fdst,'');
+ } elsif (($fdst=~/$basename/o,length $2) > ($fsrc=~/$basename/o,length $2)) {
+ return ($fsrc,'');
+ } else {
+ # shortest names
+ if (length $fdst < length $fsrc) {
+ return ($fdst,'');
+ } else {
+ return ($fsrc,'');
+ }
+ }
+ }
+ }
+}
+
+# IN: diff "archive" name
+# IN: file handle for output; STDIN for list, tempfile else
+# IN: filename to watch (for: copyout, rm), '' for: list
+# IN: remove the file?
+# true - ... and print out the rest
+# false - ie. copyout mode, print just the file
+sub parse($$$$)
+{
+ my $archive=shift;
+ my $fh=shift;
+ my $file=shift;
+ my $rmmod=shift;
+ my ($state,$fsize,$time);
+ my ($f,$fsrc,$fdst,$prefix);
+ my ($unified,$context);
+ my ($skipread, $filetoprint, $filefound);
+ my ($h_add,$h_del,$h_ctx); # hunk line counts
+ my ($h_r1,$h_r2); # hunk ranges
+ my @outsrc; # if desired ...
+ my @outdst;
+ my $line;
+ my %fmap_size=();
+ my %fmap_time=();
+
+ import Date::Parse if ($parsedates && $file eq '');
+
+ $line=1;
+ $state=0; $fsize=0; $f='';
+ $filefound=0;
+ while ($skipread || ($line++,$_=<I>)) {
+ $skipread=0;
+ if($state == 0) { # expecting comments
+ $unified=$context=0;
+ $unified=1 if (/^--- /);
+ $context=1 if (/^\*\*\* /);
+ if (!$unified && !$context) {
+ $filefound=0 if($file ne '' && $filetoprint);
+ # shortcut for rmmod xor filefound
+ # - in rmmod we print if not found
+ # - in copyout (!rmmod) we print if found
+ print $fh $_ if($rmmod != $filefound);
+ next;
+ }
+
+ if($file eq '' && $filetoprint) {
+ $fmap_size{"$prefix$f"}+=$fsize;
+ $fmap_time{"$prefix$f"}=$time;
+ }
+
+ # start of new file
+ $_ .=<I>; # steal next line, both formats
+ $line++;
+ if($unified) {
+ if(/$unified_header/o) {
+ ($fsrc,$fdst,$time) = /$unified_extract/o;
+ } elsif(/$unified_header2/o) {
+ ($fsrc,$fdst,$time) = /$unified_extract2/o;
+ } else {
+ error "Can't parse unified diff header";
+ }
+ } elsif($context) {
+ if(/$context_header/o) {
+ ($fsrc,$fdst,$time) = /$context_extract/o;
+ } elsif(/$context_header2/o) {
+ ($fsrc,$fdst,$time) = /$context_extract2/o;
+ } else {
+ error "Can't parse context diff header";
+ }
+ } else {
+ error "Unrecognized diff header";
+ }
+ $fsrc=patchfs_canonicalize_path($fsrc);
+ $fdst=patchfs_canonicalize_path($fdst);
+ if(wantarray) {
+ push @outsrc,$fsrc;
+ push @outdst,$fdst;
+ }
+ ($f,$prefix)=diff_filename($fsrc,$fdst);
+ $filefound=($f eq $file);
+
+ $f="$f.diff";
+ $filetoprint=1;
+ $fsize=length;
+ print $fh $_ if($rmmod != $filefound);
+
+ $state=1;
+ } elsif($state == 1) { # expecting diff hunk headers, end of file or comments
+ if($unified) {
+ my ($a,$b,$c,$d);
+ ($a,$b,$h_r1,$c,$d,$h_r2)=/$unified_hunk/o;
+ if(!defined($a) || !defined($c)) {
+ # hunk header does not come, a comment inside
+ # or maybe a new file, state 0 will decide
+ $skipread=1;
+ $state=0;
+ next;
+ }
+ $fsize+=length;
+ print $fh $_ if($rmmod != $filefound);
+ $h_r1=1 if(!defined($b));
+ $h_r2=1 if(!defined($d));
+ $h_add=$h_del=$h_ctx=0;
+ $state=2;
+ } elsif($context) {
+ if(!/$context_contents/o) {
+ $skipread=1;
+ $state=0;
+ next;
+ }
+ print $fh $_ if($rmmod != $filefound);
+ $fsize+=length;
+ }
+ } elsif($state == 2) { # expecting hunk contents
+ if($h_del + $h_ctx == $h_r1 && $h_add + $h_ctx == $h_r2) {
+ # hooray, end of hunk
+ # we optimistically ended with a hunk before but
+ # the line has been read already
+ $skipread=1;
+ $state=1;
+ next;
+ }
+ print $fh $_ if($rmmod != $filefound);
+ $fsize+=length;
+ my ($first)= /^(.)/;
+ if(ord($first) == ord('+')) { $h_add++; }
+ elsif(ord($first) == ord('-')) { $h_del++; }
+ elsif(ord($first) == ord(' ')) { $h_ctx++; }
+ elsif(ord($first) == ord('\\')) { 0; }
+ elsif(ord($first) == ord('@')) { error "Malformed hunk, header came too early"; }
+ else { error "$archive:$line: Unrecognized character '$first' in hunk"; }
+ }
+ }
+ if($file eq '' && $filetoprint) {
+ $fmap_size{"$prefix$f"}+=$fsize;
+ $fmap_time{"$prefix$f"}=$time;
+ }
+
+ # use uid and gid from file
+ my $qarchive = quotemeta $archive;
+ my ($uid,$gid)=(`ls -l $qarchive`=~/$ls_extract_id/o);
+
+ # flush all file names with cumulative file size
+ while(my ($fn, $fs) = each %fmap_size) {
+ printf $fh "-rw-r--r-- 1 %s %s %d %s %s\n", $uid, $gid, $fs, datetime($fmap_time{$fn}), $fn;
+ }
+
+ close($fh) if($file ne '');
+ return \(@outsrc, @outdst) if wantarray;
+}
+
+# list files affected by patch
+sub list($) {
+ parse($_[0], *STDOUT, '', 0);
+ close(I);
+}
+
+# extract diff from patch
+# IN: diff file to find
+# IN: output file name
+sub copyout($$) {
+ my ($file,$out)=@_;
+
+ $file=~s/^(PATCH-(CREATE|REMOVE)\/)?(.*)\.diff$/$3/;
+ $file = patchfs_canonicalize_path ($file);
+
+ open(FH, ">$out") or error("Cannot open output file");
+ parse('', *FH, $file, 0);
+}
+
+# remove diff(s) from patch
+# IN: archive
+# IN: file to delete
+sub rm($$) {
+ my $archive=shift;
+ my ($tmp,$tmpname)=tempfile();
+
+ @_=map {scalar(s/^(PATCH-(CREATE|REMOVE)\/)?(.*)\.diff$/$3/,$_)} @_;
+
+ # just the first file for now
+ parse($archive, $tmp, $_[0], 1);
+ close I;
+
+ # replace archive
+ system("cat \Q$tmpname\E | " . myout($archive,0))==0
+ or error "Can't write to archive";
+ system("rm -f -- \Q$tmpname\E");
+}
+
+# append diff to archive
+# IN: diff archive name
+# IN: newly created file name in archive
+# IN: the real source file
+sub copyin($$$) {
+ # TODO: seems to be tricky. what to do?
+ # copyin of file which is already there may:
+ # * delete the original and copy only the new
+ # * just append the new hunks to the same file
+ # problems: may not be a valid diff, unmerged hunks
+ # * try to merge the two together
+ # ... but we do not want write patchutils again, right?
+ error "Copying files into diff not supported";
+ return;
+
+ my ($archive,$name,$src)=@_;
+
+ # in case we are appending another diff, we have
+ # to delete/merge all the files
+ open(DEVNULL, ">/dev/null");
+ open I, myin($src).'|';
+ my ($srclist,$dstlist)=parse($archive, *DEVNULL, '', 0);
+ close(I);
+ close(DEVNULL);
+ foreach(@$srclist) {
+ print("SRC: del $_\n");
+ }
+ foreach(@$dstlist) {
+ print("DST: del $_\n");
+ }
+ return;
+
+ # remove overwritten file
+ open I, myin($archive).'|';
+ rm ($archive, $name);
+ close I;
+
+ my $cmd1=myin("$src.diff");
+ my $cmd2=myout($archive,1);
+ system("$cmd1 | $cmd2")==0
+ or error "Can't write to archive";
+}
+
+my $fin = $ARGV[1];
+
+# resolve symlink
+while (-l $fin) {
+ $fin = readlink $fin;
+}
+
+if ($ARGV[0] eq 'list') {
+ open I, myin($fin).'|';
+ list ($fin);
+ exit 0;
+} elsif ($ARGV[0] eq 'copyout') {
+ open I, myin($fin)."|";
+ copyout ($ARGV[2], $ARGV[3]);
+ exit 0;
+} elsif ($ARGV[0] eq 'rm') {
+ open I, myin($fin)."|";
+ rm ($fin, $ARGV[2]);
+ exit 0;
+} elsif ($ARGV[0] eq 'rmdir') {
+ exit 0;
+} elsif ($ARGV[0] eq 'mkdir') {
+ exit 0;
+} elsif ($ARGV[0] eq 'copyin') {
+ copyin ($fin, $ARGV[2], $ARGV[3]);
+ exit 0;
+}
+exit 1;
diff --git a/src/vfs/extfs/helpers/patchsetfs b/src/vfs/extfs/helpers/patchsetfs
new file mode 100755
index 0000000..9bbe9f9
--- /dev/null
+++ b/src/vfs/extfs/helpers/patchsetfs
@@ -0,0 +1,104 @@
+#!/bin/sh
+
+LANG=C
+export LANG
+LC_TIME=C
+export LC_TIME
+
+# --- GIT -----------------------------------------------------------------------
+
+found_git_dir()
+{
+ work_dir=$1
+ while [ -n "$work_dir" -a "$work_dir" != "/" ]; do
+ [ -d "${work_dir}/.git" ] && {
+ echo "${work_dir}/.git/"
+ return
+ }
+ work_dir=`dirname "$work_dir"`
+ done
+ echo ''
+}
+
+patchsetfs_list_git()
+{
+ WORK_DIR=$1; shift
+ fname=$1; shift
+ USER=$1; shift
+ DATE=$1; shift
+
+ GIT_DIR=`found_git_dir "$WORK_DIR"`
+ [ -z "$GIT_DIR" ] && GIT_DIR=$WORK_DIR
+ curr_year=`date +"%Y"`
+
+ git --git-dir="$GIT_DIR" log --abbrev=7 --pretty="format:%at %h %an" -- "$fname" | while read TIMESTAMP chset author
+ do
+ year=`date -d @"$TIMESTAMP" +"%Y"`
+ [ "$year" = "$curr_year" ] && {
+ DATE=`date -d @"$TIMESTAMP" +"%b %d %H:%M"`
+ } || {
+ DATE=`date -d @"$TIMESTAMP" +"%b %d %Y"`
+ }
+ NAME="$chset $author"
+ echo "-rw-rw-rw- 1 $USER 0 0 $DATE $NAME.diff"
+ done
+}
+
+patchsetfs_copyout_git()
+{
+ WORK_DIR=$1; shift
+ fname=$1; shift
+ orig_fname=$1;shift
+ output_fname=$1;shift
+
+ chset=`echo "$orig_fname"| cut -f 1 -d " "`
+
+ GIT_DIR=`found_git_dir "$WORK_DIR"`
+ [ -z "$GIT_DIR" ] && GIT_DIR=$WORK_DIR
+
+ git --git-dir="$GIT_DIR" show "$chset" -- "$fname" > "$output_fname"
+}
+
+# --- COMMON --------------------------------------------------------------------
+
+patchsetfs_list()
+{
+ VCS_type=$1; shift
+ WORK_DIR=$1; shift
+ fname=$1; shift
+
+ DATE=`date +"%b %d %H:%M"`
+ USER=`whoami`
+
+ case "$VCS_type" in
+ git) patchsetfs_list_git "$WORK_DIR" "$fname" "$USER" "$DATE" ;;
+ esac
+}
+
+patchsetfs_copyout()
+{
+ VCS_type=$1; shift
+ WORK_DIR=$1; shift
+ fname=$1; shift
+
+ case "$VCS_type" in
+ git) patchsetfs_copyout_git "$WORK_DIR" "$fname" "$@" ;;
+ esac
+
+}
+
+# --- MAIN ----------------------------------------------------------------------
+
+command=$1; shift
+tmp_file=$1; shift
+
+WORK_DIR=`head -n1 $tmp_file`
+fname=`tail -n2 $tmp_file | head -n1`
+VCS_type=`tail -n1 $tmp_file`
+
+case "$command" in
+ list) patchsetfs_list "$VCS_type" "$WORK_DIR" "$fname" ;;
+ copyout) patchsetfs_copyout "$VCS_type" "$WORK_DIR" "$fname" "$@" ;;
+ *) exit 1 ;;
+esac
+exit 0
diff --git a/src/vfs/extfs/helpers/rpm b/src/vfs/extfs/helpers/rpm
new file mode 100755
index 0000000..8fa9188
--- /dev/null
+++ b/src/vfs/extfs/helpers/rpm
@@ -0,0 +1,349 @@
+#! /bin/sh
+# VFS-wrapper for RPM (and src.rpm) files
+#
+# Copyright (C) 1996-2004,2009
+# Free Software Foundation, Inc.
+#
+# Written by
+# Erik Troan <ewt@redhat.com> 1996
+# Jakub Jelinek <jj@sunsite.mff.cuni.cz> 1996, 2004
+# Tomasz KÅ‚oczko <kloczek@rudy.mif.pg.gda.pl> 1997
+# Wojtek Pilorz <wpilorz@bdk.lublin.pl>
+# 1997: minor changes
+# Michele Marziani <marziani@fe.infn.it>
+# 1997: minor changes
+# Marc Merlin <marcsoft@merlins.org> 1998
+# 1998: bug files
+# Michal Svec <rebel@penguin.cz> 2000
+# 2000: locale bugfix
+# Andrew V. Samoilov <sav@bcs.zp.ua>
+# 2004: Whitespace(s) & single quote(s) in filename workaround
+# https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=64007
+# Slava Zanko <slavazanko@gmail.com>
+# 2009: Totally rewritten.
+# Alexander Chumachenko <ledest@gmail.com>
+# 2013: add dependency version output
+# Denis Silakov <denis.silakov@rosalab.ru>
+# 2013: tar payload support.
+# Arkadiusz Miśkiewicz <arekm@maven.pl>
+# 2013: improve support for EPOCH
+# add support for PREINPROG/POSTINPROG/PREUNPROG/POSTUNPROG
+# add support for VERIFYSCRIPTPROG
+# add support for TRIGGERSCRIPTS/TRIGGERSCRIPTPROG
+# Jiri Tyr <jiri.tyr@gmail.com>
+# 2016: add support for PRETRANS/PRETRANSPROG/POSTTRANS/POSTTRANSPROG
+#
+# This file is part of the Midnight Commander.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+# override any locale for dates
+unset LC_ALL
+LC_TIME=C
+export LC_TIME
+
+if rpmbuild --version >/dev/null 2>&1; then
+ RPMBUILD="rpmbuild"
+else
+ RPMBUILD="rpm"
+fi
+
+if rpm --nosignature --version >/dev/null 2>&1; then
+ RPM="rpm --nosignature"
+ RPMBUILD="$RPMBUILD --nosignature"
+else
+ RPM="rpm"
+fi
+RPM_QUERY_FMT="$RPM -qp --qf"
+RPM2CPIO="rpm2cpio"
+
+SED="sed"
+
+param=$1; shift
+rpm_filename=$1; shift
+
+FILEPREF="-r--r--r-- 1 root root "
+
+mcrpmfs_getDesription()
+{
+ $RPM -qip "${rpm_filename}"
+}
+
+mcrpmfs_getAllNeededTags()
+{
+ $RPM_QUERY_FMT \
+"|NAME=%{NAME}"\
+"|VERSION=%{VERSION}"\
+"|RELEASE=%{RELEASE}"\
+"|DISTRIBUTION=%{DISTRIBUTION}"\
+"|VENDOR=%{VENDOR}"\
+"|DESCRIPTION=%{DESCRIPTION}"\
+"|SUMMARY=%{SUMMARY}"\
+"|URL=%{URL}"\
+"|EPOCH=%{EPOCH}"\
+"|LICENSE=%{LICENSE}"\
+"|REQUIRES=%{REQUIRENAME} %{REQUIREFLAGS:depflags} %{REQUIREVERSION}"\
+"|OBSOLETES=%{OBSOLETES}"\
+"|PROVIDES=%{PROVIDES} %{PROVIDEFLAGS:depflags} %{PROVIDEVERSION}"\
+"|CONFLICTS=%{CONFLICTS}"\
+"|PACKAGER=%{PACKAGER}" \
+ "${rpm_filename}" \
+ | tr '\n' ' ' # The newlines in DESCRIPTION mess with the sed script in mcrpmfs_getOneTag().
+}
+
+mcrpmfs_getRawOneTag()
+{
+ $RPM_QUERY_FMT "$1" "${rpm_filename}"
+}
+
+mcrpmfs_getOneTag()
+{
+ # 'echo' can't be used for arbitrary data (see commit message).
+ printf "%s" "$AllTAGS" | $SED "s/.*|${1}=//" | cut -d '|' -f 1
+}
+
+mcrpmfs_printOneMetaInfo()
+{
+ if test "$3" = "raw"; then
+ metaInfo=`mcrpmfs_getRawOneTag "%{$2}"`
+ else
+ metaInfo=`mcrpmfs_getOneTag "$2"`
+ fi
+
+ if test -n "${metaInfo}" -a "${metaInfo}" != "(none)"; then
+ echo "${FILEPREF} 0 ${DATE} ${1}"
+ return 0
+ fi
+ return 1
+}
+
+mcrpmfs_list_fastRPM ()
+{
+ echo "$FILEPREF 0 $DATE INFO/DISTRIBUTION"
+ echo "$FILEPREF 0 $DATE INFO/VENDOR"
+ echo "$FILEPREF 0 $DATE INFO/DESCRIPTION"
+ echo "$FILEPREF 0 $DATE INFO/SUMMARY"
+ echo "dr-xr-xr-x 1 root root 0 $DATE INFO/SCRIPTS"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/PRETRANS"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/POSTTRANS"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/PREIN"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/POSTIN"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/PREUN"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/POSTUN"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/VERIFYSCRIPT"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/TRIGGERSCRIPTS"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/ALL"
+ echo "$FILEPREF 0 $DATE INFO/PACKAGER"
+ echo "$FILEPREF 0 $DATE INFO/URL"
+ echo "$FILEPREF 0 $DATE INFO/EPOCH"
+ echo "$FILEPREF 0 $DATE INFO/LICENSE"
+ echo "$FILEPREF 0 $DATE INFO/REQUIRES"
+ echo "$FILEPREF 0 $DATE INFO/OBSOLETES"
+ echo "$FILEPREF 0 $DATE INFO/PROVIDES"
+ echo "$FILEPREF 0 $DATE INFO/ENHANCES"
+ echo "$FILEPREF 0 $DATE INFO/SUGGESTS"
+ echo "$FILEPREF 0 $DATE INFO/RECOMMENDS"
+ echo "$FILEPREF 0 $DATE INFO/SUPPLEMENTS"
+ echo "$FILEPREF 0 $DATE INFO/CONFLICTS"
+ echo "$FILEPREF 0 $DATE INFO/CHANGELOG"
+}
+
+mcrpmfs_list_fullRPM ()
+{
+ mcrpmfs_printOneMetaInfo "INFO/DISTRIBUTION" "DISTRIBUTION"
+ mcrpmfs_printOneMetaInfo "INFO/VENDOR" "VENDOR"
+ mcrpmfs_printOneMetaInfo "INFO/DESCRIPTION" "DESCRIPTION"
+ mcrpmfs_printOneMetaInfo "INFO/SUMMARY" "SUMMARY"
+
+ if test "`mcrpmfs_getRawOneTag \"%{RPMTAG_PRETRANS}%{RPMTAG_POSTTRANS}%{RPMTAG_PREIN}%{RPMTAG_POSTIN}%{RPMTAG_PREUN}%{RPMTAG_POSTUN}%{VERIFYSCRIPT}%{TRIGGERSCRIPTS}\"`" != "(none)(none)(none)(none)(none)(none)(none)(none)"; then
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/PRETRANS" "RPMTAG_PRETRANS" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/POSTTRANS" "RPMTAG_POSTTRANS" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/PREIN" "RPMTAG_PREIN" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/POSTIN" "RPMTAG_POSTIN" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/PREUN" "RPMTAG_PREUN" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/POSTUN" "RPMTAG_POSTUN" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/VERIFYSCRIPT" "VERIFYSCRIPT" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/TRIGGERSCRIPTS" "TRIGGERSCRIPTS" "raw"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/ALL"
+ fi
+
+ if test "`mcrpmfs_getRawOneTag \"%{RPMTAG_PRETRANSPROG}%{RPMTAG_POSTTRANSPROG}%{RPMTAG_PREINPROG}%{RPMTAG_POSTINPROG}%{RPMTAG_PREUNPROG}%{RPMTAG_POSTUNPROG}%{VERIFYSCRIPTPROG}%{TRIGGERSCRIPTPROG}\"`" != "(none)(none)(none)(none)(none)(none)(none)(none)"; then
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/PRETRANSPROG" "RPMTAG_PRETRANSPROG" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/POSTTRANSPROG" "RPMTAG_POSTTRANSPROG" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/PREINPROG" "RPMTAG_PREINPROG" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/POSTINPROG" "RPMTAG_POSTINPROG" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/PREUNPROG" "RPMTAG_PREUNPROG" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/POSTUNPROG" "RPMTAG_POSTUNPROG" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/VERIFYSCRIPTPROG" "VERIFYSCRIPTPROG" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/TRIGGERSCRIPTPROG" "TRIGGERSCRIPTPROG" "raw"
+ fi
+
+ mcrpmfs_printOneMetaInfo "INFO/PACKAGER" "PACKAGER"
+ mcrpmfs_printOneMetaInfo "INFO/URL" "URL"
+ mcrpmfs_printOneMetaInfo "INFO/EPOCH" "EPOCH"
+ mcrpmfs_printOneMetaInfo "INFO/LICENSE" "LICENSE"
+
+ mcrpmfs_printOneMetaInfo "INFO/REQUIRES" "REQUIRES"
+ mcrpmfs_printOneMetaInfo "INFO/OBSOLETES" "OBSOLETES"
+ mcrpmfs_printOneMetaInfo "INFO/PROVIDES" "PROVIDES"
+ mcrpmfs_printOneMetaInfo "INFO/CONFLICTS" "CONFLICTS"
+ mcrpmfs_printOneMetaInfo "INFO/CHANGELOG" "CHANGELOGTEXT" "raw"
+}
+
+mcrpmfs_list ()
+{
+ # set MCFASTRPM_DFLT to 1 for faster rpm files handling by default, to 0 for
+ # slower handling
+ MCFASTRPM_DFLT=0
+ if test -z "$MCFASTRPM"; then
+ MCFASTRPM=$MCFASTRPM_DFLT
+ fi
+
+ DESC=`mcrpmfs_getDesription 2>/dev/null` || {
+ echo "$FILEPREF 0 "`date +"%b %d %H:%M"`" ERROR"
+ exit 1
+ }
+ DATE=`mcrpmfs_getRawOneTag "%{BUILDTIME:date}\n" | cut -c 5-11,21-24`
+ PAYLOAD=`mcrpmfs_getRawOneTag "%{PAYLOADFORMAT}\n" | sed s/ustar/tar/`
+
+ HEADERSIZE=`printf '%s\n' "$DESC" | wc -c` # 'echo' can't be used for arbitrary data (see commit message).
+ printf '%s %s %s HEADER\n' "${FILEPREF}" "${HEADERSIZE}" "${DATE}"
+ echo "-r-xr-xr-x 1 root root 0 $DATE INSTALL"
+ case "${rpm_filename}" in
+ *.src.rpm)
+ echo "-r-xr-xr-x 1 root root 0 $DATE REBUILD"
+ ;;
+ *)
+ echo "-r-xr-xr-x 1 root root 0 $DATE UPGRADE"
+ ;;
+ esac
+
+ echo "dr-xr-xr-x 3 root root 0 $DATE INFO"
+ if [ `mcrpmfs_getRawOneTag "%{EPOCH}"` = "(none)" ]; then
+ echo "$FILEPREF 0 $DATE INFO/NAME-VERSION-RELEASE"
+ else
+ echo "$FILEPREF 0 $DATE INFO/NAME-EPOCH:VERSION-RELEASE"
+ fi
+ echo "$FILEPREF 0 $DATE INFO/GROUP"
+ echo "$FILEPREF 0 $DATE INFO/BUILDHOST"
+ echo "$FILEPREF 0 $DATE INFO/SOURCERPM"
+ echo "$FILEPREF 0 $DATE INFO/BUILDTIME"
+ echo "$FILEPREF 0 $DATE INFO/RPMVERSION"
+ echo "$FILEPREF 0 $DATE INFO/OS"
+ echo "$FILEPREF 0 $DATE INFO/SIZE"
+
+ if test "$MCFASTRPM" = 0 ; then
+ mcrpmfs_list_fullRPM
+ else
+ mcrpmfs_list_fastRPM
+ fi
+
+ echo "$FILEPREF 0 $DATE CONTENTS.$PAYLOAD"
+}
+
+mcrpmfs_copyout ()
+{
+ case "$1" in
+ HEADER) mcrpmfs_getDesription > "$2"; exit 0;;
+ INSTALL)
+ echo "# Run this to install this RPM package" > "$2"
+ exit 0
+ ;;
+ UPGRADE)
+ echo "# Run this to upgrade this RPM package" > "$2"
+ exit 0
+ ;;
+ REBUILD)
+ echo "# Run this to rebuild this RPM package" > "$2"
+ exit 0
+ ;;
+ ERROR) mcrpmfs_getDesription > /dev/null 2> "$2"; exit 0;;
+ INFO/NAME-VERSION-RELEASE)
+ echo `mcrpmfs_getOneTag "NAME"`-`mcrpmfs_getOneTag "VERSION"`-`mcrpmfs_getOneTag "RELEASE"` > "$2"
+ exit 0
+ ;;
+ INFO/NAME-EPOCH:VERSION-RELEASE)
+ echo `mcrpmfs_getOneTag "NAME"`-`mcrpmfs_getOneTag "EPOCH"`:`mcrpmfs_getOneTag "VERSION"`-`mcrpmfs_getOneTag "RELEASE"` > "$2"
+ exit 0
+ ;;
+ INFO/RELEASE) mcrpmfs_getOneTag "RELEASE" > "$2"; exit 0;;
+ INFO/GROUP) mcrpmfs_getRawOneTag "%{GROUP}\n" > "$2"; exit 0;;
+ INFO/DISTRIBUTION) mcrpmfs_getOneTag "DISTRIBUTION" > "$2"; exit 0;;
+ INFO/VENDOR) mcrpmfs_getOneTag "VENDOR" > "$2"; exit 0;;
+ INFO/BUILDHOST) mcrpmfs_getRawOneTag "%{BUILDHOST}\n" > "$2"; exit 0;;
+ INFO/SOURCERPM) mcrpmfs_getRawOneTag "%{SOURCERPM}\n" > "$2"; exit 0;;
+ INFO/DESCRIPTION) mcrpmfs_getRawOneTag "%{DESCRIPTION}\n" > "$2"; exit 0;;
+ INFO/PACKAGER) mcrpmfs_getOneTag "PACKAGER" > "$2"; exit 0;;
+ INFO/URL) mcrpmfs_getOneTag "URL" >"$2"; exit 0;;
+ INFO/BUILDTIME) mcrpmfs_getRawOneTag "%{BUILDTIME:date}\n" >"$2"; exit 0;;
+ INFO/EPOCH) mcrpmfs_getOneTag "EPOCH" >"$2"; exit 0;;
+ INFO/LICENSE) mcrpmfs_getOneTag "LICENSE" >"$2"; exit 0;;
+ INFO/RPMVERSION) mcrpmfs_getRawOneTag "%{RPMVERSION}\n" >"$2"; exit 0;;
+ INFO/REQUIRES) mcrpmfs_getRawOneTag "[%{REQUIRENAME} %{REQUIREFLAGS:depflags} %{REQUIREVERSION}\n]" >"$2"; exit 0;;
+ INFO/ENHANCES) mcrpmfs_getRawOneTag "[%|ENHANCESFLAGS:depflag_strong?{}:{%{ENHANCESNAME} %{ENHANCESFLAGS:depflags} %{ENHANCESVERSION}\n}|]" "$f" >"$3"; exit 0;;
+ INFO/SUGGESTS) mcrpmfs_getRawOneTag "[%|SUGGESTSFLAGS:depflag_strong?{}:{%{SUGGESTSNAME} %{SUGGESTSFLAGS:depflags} %{SUGGESTSVERSION}\n}|]" "$f" >"$3"; exit 0;;
+ INFO/RECOMMENDS) mcrpmfs_getRawOneTag "[%|SUGGESTSFLAGS:depflag_strong?{%{SUGGESTSNAME} %{SUGGESTSFLAGS:depflags} %{SUGGESTSVERSION}\n}|]" "$f" >"$3"; exit 0;;
+ INFO/SUPPLEMENTS) mcrpmfs_getRawOneTag "[%|ENHANCESFLAGS:depflag_strong?{%{ENHANCESNAME} %{ENHANCESFLAGS:depflags} %{ENHANCESVERSION}\n}|]" "$f" >"$3"; exit 0;;
+ INFO/PROVIDES) mcrpmfs_getRawOneTag "[%{PROVIDES} %{PROVIDEFLAGS:depflags} %{PROVIDEVERSION}\n]" >"$2"; exit 0;;
+ INFO/SCRIPTS/PRETRANS) mcrpmfs_getRawOneTag "%{RPMTAG_PRETRANS}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/PRETRANSPROG) mcrpmfs_getRawOneTag "%{RPMTAG_PRETRANSPROG}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/POSTTRANS) mcrpmfs_getRawOneTag "%{RPMTAG_POSTTRANS}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/POSTTRANSPROG) mcrpmfs_getRawOneTag "%{RPMTAG_POSTTRANSPROG}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/PREIN) mcrpmfs_getRawOneTag "%{RPMTAG_PREIN}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/PREINPROG) mcrpmfs_getRawOneTag "%{RPMTAG_PREINPROG}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/POSTIN) mcrpmfs_getRawOneTag "%{RPMTAG_POSTIN}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/POSTINPROG) mcrpmfs_getRawOneTag "%{RPMTAG_POSTINPROG}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/PREUN) mcrpmfs_getRawOneTag "%{RPMTAG_PREUN}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/PREUNPROG) mcrpmfs_getRawOneTag "%{RPMTAG_PREUNPROG}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/POSTUN) mcrpmfs_getRawOneTag "%{RPMTAG_POSTUN}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/POSTUNPROG) mcrpmfs_getRawOneTag "%{RPMTAG_POSTUNPROG}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/VERIFYSCRIPT) mcrpmfs_getRawOneTag "%{VERIFYSCRIPT}\n" > "$2"; exit 0;;
+ INFO/SCRIPTS/VERIFYSCRIPTPROG) mcrpmfs_getRawOneTag "%{VERIFYSCRIPTPROG}\n" > "$2"; exit 0;;
+ INFO/SCRIPTS/TRIGGERSCRIPTS) $RPM -qp --triggers "${rpm_filename}" > "$2"; exit 0;;
+ INFO/SCRIPTS/TRIGGERSCRIPTPROG) mcrpmfs_getRawOneTag "%{TRIGGERSCRIPTPROG}\n" > "$2"; exit 0;;
+ INFO/SCRIPTS/ALL) $RPM -qp --scripts "${rpm_filename}" > "$2"; exit 0;;
+ INFO/SUMMARY) mcrpmfs_getRawOneTag "%{SUMMARY}\n" > "$2"; exit 0;;
+ INFO/OS) mcrpmfs_getRawOneTag "%{OS}\n" > "$2"; exit 0;;
+ INFO/CHANGELOG) mcrpmfs_getRawOneTag "[* %{CHANGELOGTIME:date} %{CHANGELOGNAME}\n%{CHANGELOGTEXT}\n\n]\n" > "$2"; exit 0;;
+ INFO/SIZE) mcrpmfs_getRawOneTag "%{SIZE} bytes\n" > "$2"; exit 0;;
+ INFO/OBSOLETES) mcrpmfs_getRawOneTag "[%{OBSOLETENAME} %|OBSOLETEFLAGS?{%{OBSOLETEFLAGS:depflags} %{OBSOLETEVERSION}}:{}|\n]" > "$2"; exit 0;;
+ INFO/CONFLICTS) mcrpmfs_getRawOneTag "[%{CONFLICTNAME} %{CONFLICTFLAGS:depflags} %{CONFLICTVERSION}\n]" >"$2"; exit 0;;
+ CONTENTS.*) $RPM2CPIO "${rpm_filename}" > "$2"; exit 0;;
+ *)
+ ;;
+ esac
+}
+
+mcrpmfs_run ()
+{
+ case "$1" in
+ INSTALL) echo "Installing \"${rpm_filename}\""; $RPM -ivh "${rpm_filename}"; exit 0;;
+ UPGRADE) echo "Upgrading \"${rpm_filename}\""; $RPM -Uvh "${rpm_filename}"; exit 0;;
+ REBUILD) echo "Rebuilding \"${rpm_filename}\""; $RPMBUILD --rebuild "${rpm_filename}"; exit 0;;
+ esac
+}
+
+# Let the test framework override functions and variables.
+[ -n "$MC_TEST_RPM_REWRITE" ] && . "$MC_TEST_RPM_REWRITE"
+
+AllTAGS=`mcrpmfs_getAllNeededTags "$1"`
+
+umask 077
+case "${param}" in
+ list) mcrpmfs_list; exit 0;;
+ copyout) mcrpmfs_copyout "$1" "$2"; exit 0;;
+ run) mcrpmfs_run "$1"; exit 1;;
+esac
+exit 1
diff --git a/src/vfs/extfs/helpers/rpms+.in b/src/vfs/extfs/helpers/rpms+.in
new file mode 100644
index 0000000..9a8e7de
--- /dev/null
+++ b/src/vfs/extfs/helpers/rpms+.in
@@ -0,0 +1,66 @@
+#! @PERL@
+#
+# Written by Balazs Nagy (julian7@kva.hu) 1998
+# locale bugfix by Michal Svec (rebel@penguin.cz) 2000
+# (C) 1998 The Free Software Foundation.
+#
+#
+
+# override any locale for dates
+delete $ENV{"LC_ALL"};
+$ENV{"LC_TIME"}="C";
+
+#print $ENV{"LC_ALL"};
+#exit 0;
+
+sub gd
+{
+ my ($dt) = @_;
+ $dt =~ tr/ //s;
+ $dt =~ s/^\w+ (\w+) (\d+) (\d+:\d+):\d+ .+\n?$/$1 $2 $3/;
+ return $dt;
+}
+
+$DATE=gd(`date`);
+
+sub list
+{
+ my (@rpms, %files, $i, $fn, $dn, $sz, $bt);
+# @rpms = `rpm -qa --qf "\%{NAME}-\%{VERSION}-\%{RELEASE}:\%{GROUP}:\%{SIZE}:\%{BUILDTIME:date}\n"`;
+ @rpms = `rpm -qa --qf "\%{NAME}-\%{VERSION}:\%{GROUP}:\%{SIZE}:\%{BUILDTIME:date}\n"`;
+ print @trpms;
+ %files = ();
+ %sizes = ();
+ %dates = ();
+ for $i (@rpms) {
+ if ($i =~ /^([^:]+):([^:]+):([^:]+):(.+)$/) {
+ ($fn, $dn, $sz, $bt) = ($1, $2, $3, $4);
+ $dn =~ s/ /_/g;
+ if (defined $files{$dn}) {
+ push(@{$files{$dn}}, $fn);
+ } else {
+ @{$files{$dn}} = ($fn);
+ }
+ $sizes{$fn} = $sz;
+ $dates{$fn} = gd($bt);
+ }
+ }
+ for $i (sort keys %files) {
+ print "dr-xr-xr-x 1 root root 0 $DATE $i/\n";
+ for $fn (sort @{$files{$i}}) {
+ print "-r--r--r-- 1 root root $sizes{$fn} $dates{$fn} $i/$fn.trpm\n";
+ }
+ }
+}
+
+#open O, ">>/tmp/tt";
+#print O "RPMS: ";
+#for $i (@ARGV) {
+# print O "$i ";
+#}
+#print O "\n";
+#close O;
+
+if ($ARGV[0] eq "list") { list(); exit(0); }
+elsif ($ARGV[0] eq "copyout") { open O, ">$ARGV[3]"; print O $ARGV[2], "\n"; close O; exit(0); }
+exit(1);
diff --git a/src/vfs/extfs/helpers/s3+.in b/src/vfs/extfs/helpers/s3+.in
new file mode 100644
index 0000000..f5e4b90
--- /dev/null
+++ b/src/vfs/extfs/helpers/s3+.in
@@ -0,0 +1,490 @@
+#! @PYTHON@
+# -*- coding: utf-8 -*-
+
+#
+# Midnight Commander compatible EXTFS for accessing Amazon Web Services S3.
+# Written by Jakob Kemi <jakob.kemi@gmail.com> 2009
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+# Notes:
+# This EXTFS exposes buckets as directories and keys as files
+# Due to EXTFS limitations all buckets & keys have to be read initially which might
+# take quite some time.
+# Tested on Debian with Python 2.4-2.6 and boto 1.4c and 1.6b
+# (Python 2.6 might need -W ignore::DeprecationWarning due to boto using
+# deprecated module Popen2)
+#
+#
+# Installation:
+# Make sure that boto <http://code.google.com/p/boto> (python-boto in Debian) is installed.
+# Preferably pytz (package python-tz in Debian) should be installed as well.
+#
+# Save as executable file /usr/libexec/mc/extfs/s3 (or wherever your mc expects to find extfs modules)
+#
+# Settings: (should be set via environment)
+# Required:
+# AWS_ACCESS_KEY_ID : Amazon AWS access key (required)
+# AWS_SECRET_ACCESS_KEY : Amazon AWS secret access key (required)
+# Optional:
+# MCVFS_EXTFS_S3_LOCATION : where to create new buckets: "EU" - default, "USWest", "APNortheast" etc.
+# MCVFS_EXTFS_S3_DEBUGFILE : write debug info to this file (no info by default)
+# MCVFS_EXTFS_S3_DEBUGLEVEL : debug messages level ("WARNING" - default, "DEBUG" - verbose)
+#
+#
+# Usage:
+# Open dialog "Quick cd" (<alt-c>) and type: s3:// <enter> (or simply type `cd s3://' in shell line)
+#
+#
+# History:
+#
+# 2015-07-22 Dmitry Koterov <dmitry.koterov@gmail.com>
+# - Support for non-ASCII characters in filenames (system encoding detection).
+#
+# 2015-05-21 Dmitry Koterov <dmitry.koterov@gmail.com>
+# - Resolve "Please use AWS4-HMAC-SHA256" error: enforce the new V4 authentication method.
+# It is required in many (if not all) locations nowadays.
+# - Now s3+ works with buckets in different regions: locations are auto-detected.
+# - Debug level specification support (MCVFS_EXTFS_S3_DEBUGLEVEL).
+#
+# 2009-02-07 Jakob Kemi <jakob.kemi@gmail.com>
+# - Updated instructions.
+# - Improved error reporting.
+#
+# 2009-02-06 Jakob Kemi <jakob.kemi@gmail.com>
+# - Threaded list command.
+# - Handle rm of empty "subdirectories" (as seen in mc).
+# - List most recent datetime and total size of keys as directory properties.
+# - List modification time in local time.
+#
+# 2009-02-05 Jakob Kemi <jakob.kemi@gmail.com>
+# - Initial version.
+#
+
+import sys
+import os
+import time
+import re
+import datetime
+
+
+import boto
+from boto.s3.connection import S3Connection
+from boto.exception import BotoServerError
+
+
+# Get settings from environment
+USER=os.getenv('USER','0')
+AWS_ACCESS_KEY_ID=os.getenv('AWS_ACCESS_KEY_ID')
+AWS_SECRET_ACCESS_KEY=os.getenv('AWS_SECRET_ACCESS_KEY')
+S3LOCATION=os.getenv('MCVFS_EXTFS_S3_LOCATION', 'EU')
+DEBUGFILE=os.getenv('MCVFS_EXTFS_S3_DEBUGFILE')
+DEBUGLEVEL=os.getenv('MCVFS_EXTFS_S3_DEBUGLEVEL', 'WARNING')
+
+if not AWS_ACCESS_KEY_ID or not AWS_SECRET_ACCESS_KEY:
+ sys.stderr.write('Missing AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY environment variables.\n')
+ sys.exit(1)
+
+# Setup logging
+if DEBUGFILE:
+ import logging
+ logging.basicConfig(
+ filename=DEBUGFILE,
+ level=logging.DEBUG,
+ format='%(asctime)s %(levelname)s %(message)s')
+ logging.getLogger('boto').setLevel(getattr(logging, DEBUGLEVEL))
+else:
+ class Void(object):
+ def __getattr__(self, attr):
+ return self
+ def __call__(self, *args, **kw):
+ return self
+ logging = Void()
+
+logger = logging.getLogger('s3extfs')
+
+
+def __fix_io_encoding(last_resort_default='UTF-8'):
+ """
+ The following code is needed to work with non-ASCII characters in filenames.
+ We're trying hard to detect the system encoding.
+ """
+ import codecs
+ import locale
+ for var in ('stdin', 'stdout', 'stderr'):
+ if getattr(sys, var).encoding is None:
+ enc = None
+ if enc is None:
+ try:
+ enc = locale.getpreferredencoding()
+ except:
+ pass
+ if enc is None:
+ try:
+ enc = sys.getfilesystemencoding()
+ except:
+ pass
+ if enc is None:
+ try:
+ enc = sys.stdout.encoding
+ except:
+ pass
+ if enc is None:
+ enc = last_resort_default
+ setattr(sys, var, codecs.getwriter(enc)(getattr(sys, var), 'strict'))
+__fix_io_encoding()
+
+
+def threadmap(fun, iterable, maxthreads=16):
+ """
+ Quick and dirty threaded version of builtin method map.
+ Propagates exception safely.
+ """
+ from threading import Thread
+ import Queue
+
+ items = list(iterable)
+ nitems = len(items)
+ if nitems < 2:
+ return map(fun, items)
+
+ # Create and fill input queue
+ input = Queue.Queue()
+ output = Queue.Queue()
+
+ for i,item in enumerate(items):
+ input.put( (i,item) )
+
+ class WorkThread(Thread):
+ """
+ Takes one item from input queue (thread terminates when input queue is empty),
+ performs fun, puts result in output queue
+ """
+ def run(self):
+ while True:
+ try:
+ (i,item) = input.get_nowait()
+ try:
+ result = fun(item)
+ output.put( (i,result) )
+ except:
+ output.put( (None,sys.exc_info()) )
+ except Queue.Empty:
+ return
+
+ # Start threads
+ for i in range( min(len(items), maxthreads) ):
+ t = WorkThread()
+ t.setDaemon(True)
+ t.start()
+
+ # Wait for all threads to finish & collate results
+ ret = []
+ for i in range(nitems):
+ try:
+ i,res = output.get()
+ if i == None:
+ raise res[0],res[1],res[2]
+ except Queue.Empty:
+ break
+ ret.append(res)
+
+ return ret
+
+logger.debug('started')
+
+if S3LOCATION.upper() == "EU":
+ S3LOCATION = "eu-central-1"
+if S3LOCATION.upper() == "US":
+ S3LOCATION = "us-east-1"
+for att in dir(boto.s3.connection.Location):
+ v = getattr(boto.s3.connection.Location, att)
+ if type(v) is str and att.lower() == S3LOCATION.lower():
+ S3LOCATION = v
+ break
+logger.debug('Using location %s for new buckets', S3LOCATION)
+
+
+def get_connection(location):
+ """
+ Creates a connection to the specified region.
+ """
+ os.environ['S3_USE_SIGV4'] = 'True' # only V4 method is supported in all locations.
+ return boto.s3.connect_to_region(
+ location,
+ aws_access_key_id=AWS_ACCESS_KEY_ID,
+ aws_secret_access_key=AWS_SECRET_ACCESS_KEY
+ )
+
+
+# Global S3 default connection.
+s3 = get_connection('us-east-1')
+
+
+def get_bucket(name):
+ """
+ Returns a bucket by its name, no matter what region is it in.
+ """
+ try:
+ b = s3.get_bucket(name, validate=False)
+ b.get_location() # just to raise an exception on error
+ return b
+ except boto.exception.S3ResponseError, e:
+ # Seems this is the only proper way to switch to the bucket's region.
+ # Requesting of the default region for "?location" does not work unfortunately.
+ m = re.search(r'<Region>(.*?)</Region>', e.body)
+ if m:
+ return get_connection(m.group(1)).get_bucket(name)
+ raise
+
+
+logger.debug('argv: ' + str(sys.argv))
+try:
+ cmd = sys.argv[1]
+ args = sys.argv[2:]
+except:
+ sys.stderr.write('This program should be called from within MC\n')
+ sys.exit(1)
+
+
+def handleServerError(msg):
+ e = sys.exc_info()
+ msg += ', reason: ' + e[1].reason
+ logger.error(msg, exc_info=e)
+ sys.stderr.write(msg+'\n')
+ sys.exit(1)
+
+#
+# Lists all S3 contents
+#
+if cmd == 'list':
+ if len(args) > 0:
+ path = args[0]
+ else:
+ path = ''
+
+ logger.info('list')
+
+ rs = s3.get_all_buckets()
+
+ # Import python timezones (pytz)
+ try:
+ import pytz
+ except:
+ logger.warning('Missing pytz module, timestamps will be off')
+ # A fallback UTC tz stub
+ class pytzutc(datetime.tzinfo):
+ def __init__(self):
+ datetime.tzinfo.__init__(self)
+ self.utc = self
+ self.zone = 'UTC'
+ def utcoffset(self, dt):
+ return datetime.timedelta(0)
+ def tzname(self, dt):
+ return "UTC"
+ def dst(self, dt):
+ return datetime.timedelta(0)
+ pytz = pytzutc()
+
+
+ # Find timezone
+ # (yes, timeZONE as in _geographic zone_ not EST/CEST or whatever crap we get from time.tzname)
+ # http://regebro.wordpress.com/2008/05/10/python-and-time-zones-part-2-the-beast-returns/
+ def getGuessedTimezone():
+ # 1. check TZ env. var
+ try:
+ tz = os.getenv('TZ', '')
+ return pytz.timezone(tz)
+ except:
+ pass
+ # 2. check if /etc/timezone exists (Debian at least)
+ try:
+ if os.path.isfile('/etc/timezone'):
+ tz = open('/etc/timezone', 'r').readline().strip()
+ return pytz.timezone(tz)
+ except:
+ pass
+ # 3. check if /etc/localtime is a _link_ to something useful
+ try:
+ if os.path.islink('/etc/localtime'):
+ link = os.readlink('/etc/localtime')
+ tz = '/'.join(link.split(os.path.sep)[-2:])
+ return pytz.timezone(tz)
+ except:
+ pass
+ # 4. use time.tzname which will probably be wrong by an hour 50% of the time.
+ try:
+ return pytz.timezone(time.tzname[0])
+ except:
+ pass
+ # 5. use plain UTC ...
+ return pytz.utc
+
+ tz=getGuessedTimezone()
+ logger.debug('Using timezone: ' + tz.zone)
+
+ # AWS time is on format: 2009-01-07T16:43:39.000Z
+ # we "want" MM-DD-YYYY hh:mm (in localtime)
+ expr = re.compile(r'^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.\d{3}Z$')
+ def convDate(awsdatetime):
+ m = expr.match(awsdatetime)
+ ye,mo,da,ho,mi,se = map(int,m.groups())
+
+ dt = datetime.datetime(ye,mo,da,ho,mi,se, tzinfo=pytz.utc)
+ return dt.astimezone(tz).strftime('%m-%d-%Y %H:%M')
+
+
+ def bucketList(b):
+ b = get_bucket(b.name) # get the bucket at its own region
+ totsz = 0
+ mostrecent = '1970-01-01T00:00:00.000Z'
+ ret = []
+ for k in b.list():
+ if k.name.endswith('/'):
+ # Sometimes someone create S3 keys which are ended with "/".
+ # Extfs cannot work with them as with files, and such keys may
+ # hide same-name directories, so we skip them.
+ continue
+ mostrecent = max(mostrecent, k.last_modified)
+ datetime = convDate(k.last_modified)
+ ret.append('%10s %3d %-8s %-8s %d %s %s\n' % (
+ '-rw-r--r--', 1, USER, USER, k.size, datetime, b.name+'/'+k.name)
+ )
+ totsz += k.size
+
+ datetime=convDate(mostrecent)
+ sys.stdout.write('%10s %3d %-8s %-8s %d %s %s\n' % (
+ 'drwxr-xr-x', 1, USER, USER, totsz, datetime, b.name)
+ )
+ for line in ret:
+ sys.stdout.write(line)
+
+ threadmap(bucketList, rs)
+
+#
+# Fetch file from S3
+#
+elif cmd == 'copyout':
+ archivename = args[0]
+ storedfilename = args[1]
+ extractto = args[2]
+
+ bucket,key = storedfilename.split('/', 1)
+ logger.info('copyout bucket: %s, key: %s'%(bucket, key))
+
+ try:
+ b = get_bucket(bucket)
+ k = b.get_key(key)
+
+ out = open(extractto, 'w')
+
+ k.open(mode='r')
+ for buf in k:
+ out.write(buf)
+ k.close()
+ out.close()
+ except BotoServerError:
+ handleServerError('Unable to fetch key "%s"'%(key))
+
+#
+# Upload file to S3
+#
+elif cmd == 'copyin':
+ archivename = args[0]
+ storedfilename = args[1]
+ sourcefile = args[2]
+
+ bucket,key = storedfilename.split('/', 1)
+ logger.info('copyin bucket: %s, key: %s'%(bucket, key))
+
+ try:
+ b = get_bucket(bucket)
+ k = b.new_key(key)
+ k.set_contents_from_file(fp=open(sourcefile,'r'))
+ except BotoServerError:
+ handleServerError('Unable to upload key "%s"' % (key))
+
+#
+# Remove file from S3
+#
+elif cmd == 'rm':
+ archivename = args[0]
+ storedfilename = args[1]
+
+ bucket,key = storedfilename.split('/', 1)
+ logger.info('rm bucket: %s, key: %s'%(bucket, key))
+
+ try:
+ b = get_bucket(bucket)
+ b.delete_key(key)
+ except BotoServerError:
+ handleServerError('Unable to remove key "%s"' % (key))
+
+#
+# Create directory
+#
+elif cmd == 'mkdir':
+ archivename = args[0]
+ dirname = args[1]
+
+ logger.info('mkdir dir: %s' %(dirname))
+ if '/' in dirname:
+ logger.warning('skipping mkdir')
+ pass
+ else:
+ bucket = dirname
+ try:
+ get_connection(S3LOCATION).create_bucket(bucket, location=S3LOCATION)
+ except BotoServerError:
+ handleServerError('Unable to create bucket "%s"' % (bucket))
+
+#
+# Remove directory
+#
+elif cmd == 'rmdir':
+ archivename = args[0]
+ dirname = args[1]
+
+ logger.info('rmdir dir: %s' %(dirname))
+ if '/' in dirname:
+ logger.warning('skipping rmdir')
+ pass
+ else:
+ bucket = dirname
+ try:
+ b = get_bucket(bucket)
+ b.connection.delete_bucket(b)
+ except BotoServerError:
+ handleServerError('Unable to delete bucket "%s"' % (bucket))
+
+#
+# Run from S3
+#
+elif cmd == 'run':
+ archivename = args[0]
+ storedfilename = args[1]
+ arguments = args[2:]
+
+ bucket,key = storedfilename.split('/', 1)
+ logger.info('run bucket: %s, key: %s'%(bucket, key))
+
+ os.execv(storedfilename, arguments)
+else:
+ logger.error('unhandled, bye')
+ sys.exit(1)
+
+logger.debug('command handled')
+sys.exit(0)
+
diff --git a/src/vfs/extfs/helpers/trpm b/src/vfs/extfs/helpers/trpm
new file mode 100755
index 0000000..d9a7930
--- /dev/null
+++ b/src/vfs/extfs/helpers/trpm
@@ -0,0 +1,176 @@
+#! /bin/sh
+#
+# Browse contents of an installed RPM package.
+# This filesystem works on the entries of the "rpms" filesystem.
+#
+# Written by Erik Troan (ewt@redhat.com) 1996
+# Jakub Jelinek (jj@sunsite.mff.cuni.cz) 1996
+# Tomasz K³oczko (kloczek@rudy.mif.pg.gda.pl) 1997
+# minor changes by Wojtek Pilorz (wpilorz@bdk.lublin.pl) 1997
+# minor changes by Michele Marziani (marziani@fe.infn.it) 1997
+# slight changes to put rpm to Trpm by Balazs Nagy (julian7@kva.hu) 1998
+# locale bugfix by Michal Svec (rebel@penguin.cz) 2000
+# (C) 1996 The Free Software Foundation.
+#
+#
+
+# override any locale for dates
+unset LC_ALL
+LC_TIME=C
+export LC_TIME
+
+if rpm --nosignature --version >/dev/null 2>&1; then
+ RPM="rpm --nosignature"
+else
+ RPM="rpm"
+fi
+
+mcrpmfs_list ()
+{
+ # set MCFASTRPM_DFLT to 1 for faster rpm files handling by default, to 0 for
+ # slower handling
+ MCFASTRPM_DFLT=0
+ if test -z "$MCFASTRPM"; then
+ MCFASTRPM=$MCFASTRPM_DFLT
+ fi
+ FILEPREF="-r--r--r-- 1 root root "
+ DESC=`$RPM -qi -- "$1"`
+ DATE=`$RPM -q --qf "%{BUILDTIME:date}" -- "$1" | cut -c 5-11,21-24`
+ HEADERSIZE=`echo "$DESC" | wc -c`
+ echo "-r--r--r-- 1 root root $HEADERSIZE $DATE HEADER"
+ echo "-r-xr-xr-x 1 root root 40 $DATE UNINSTALL"
+ echo "dr-xr-xr-x 3 root root 0 $DATE INFO"
+ echo "$FILEPREF 0 $DATE INFO/NAME-VERSION-RELEASE"
+ echo "$FILEPREF 0 $DATE INFO/GROUP"
+ echo "$FILEPREF 0 $DATE INFO/BUILDHOST"
+ echo "$FILEPREF 0 $DATE INFO/SOURCERPM"
+ if test "$MCFASTRPM" = 0 ; then
+ test "`$RPM -q --qf \"%{DISTRIBUTION}\" -- "$1"`" = "(none)" ||
+ echo "$FILEPREF 0 $DATE INFO/DISTRIBUTION"
+ test "`$RPM -q --qf \"%{VENDOR}\" -- "$1"`" = "(none)" ||
+ echo "$FILEPREF 0 $DATE INFO/VENDOR"
+ test "`$RPM -q --qf \"%{DESCRIPTION}\" -- "$1"`" = "(none)" ||
+ echo "$FILEPREF 0 $DATE INFO/DESCRIPTION"
+ test "`$RPM -q --qf \"%{SUMMARY}\" -- "$1"`" = "(none)" ||
+ echo "$FILEPREF 0 $DATE INFO/SUMMARY"
+ if test "`$RPM -q --qf \"%{RPMTAG_PREIN}%{RPMTAG_POSTIN}%{RPMTAG_PREUN}%{RPMTAG_POSTUN}%{VERIFYSCRIPT}\" -- "$1"`" != "(none)(none)(none)(none)(none)"; then
+ echo "dr-xr-xr-x 1 root root 0 $DATE INFO/SCRIPTS"
+ test "`$RPM -q --qf \"%{RPMTAG_PREIN}\" -- "$1"`" = '(none)' ||
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/PREIN"
+ test "`$RPM -q --qf \"%{RPMTAG_POSTIN}\" -- "$1"`" = '(none)' ||
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/POSTIN"
+ test "`$RPM -q --qf \"%{RPMTAG_PREUN}\" -- "$1"`" = '(none)' ||
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/PREUN"
+ test "`$RPM -q --qf \"%{RPMTAG_POSTUN}\" -- "$1"`" = '(none)' ||
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/POSTUN"
+ test "`$RPM -q --qf \"%{VERIFYSCRIPT}\" -- "$1"`" = '(none)' ||
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/VERIFYSCRIPT"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/ALL"
+ fi
+ else
+ echo "$FILEPREF 0 $DATE INFO/DISTRIBUTION"
+ echo "$FILEPREF 0 $DATE INFO/VENDOR"
+ echo "$FILEPREF 0 $DATE INFO/DESCRIPTION"
+ echo "$FILEPREF 0 $DATE INFO/SUMMARY"
+ echo "dr-xr-xr-x 1 root root 0 $DATE INFO/SCRIPTS"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/PREIN"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/POSTIN"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/PREUN"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/POSTUN"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/VERIFYSCRIPT"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/ALL"
+ fi
+ if test "$MCFASTRPM" = 0 ; then
+ test "`$RPM -q --qf \"%{PACKAGER}\" -- "$1"`" = "(none)" ||
+ echo "$FILEPREF 0 $DATE INFO/PACKAGER"
+ test "`$RPM -q --qf \"%{URL}\" -- "$1"`" = "(none)" ||
+ echo "$FILEPREF 0 $DATE INFO/URL"
+ test "`$RPM -q --qf \"%{EPOCH}\" -- "$1"`" = "(none)" ||
+ echo "$FILEPREF 0 $DATE INFO/EPOCH"
+ test "`$RPM -q --qf \"%{LICENSE}\" -- "$1"`" = "(none)" ||
+ echo "$FILEPREF 0 $DATE INFO/LICENSE"
+ else
+ echo "$FILEPREF 0 $DATE INFO/PACKAGER"
+ echo "$FILEPREF 0 $DATE INFO/URL"
+ echo "$FILEPREF 0 $DATE INFO/EPOCH"
+ echo "$FILEPREF 0 $DATE INFO/LICENSE"
+ fi
+ echo "$FILEPREF 0 $DATE INFO/BUILDTIME"
+ echo "$FILEPREF 0 $DATE INFO/RPMVERSION"
+ echo "$FILEPREF 0 $DATE INFO/OS"
+ echo "$FILEPREF 0 $DATE INFO/SIZE"
+ if test "$MCFASTRPM" != 0 ; then
+ $RPM -q --qf "[%{REQUIRENAME}\n]" -- "$1" | grep "(none)" > /dev/null ||
+ echo "$FILEPREF 0 $DATE INFO/REQUIRENAME"
+ $RPM -q --qf "[%{OBSOLETES}\n]" -- "$1" | grep "(none)" > /dev/null ||
+ echo "$FILEPREF 0 $DATE INFO/OBSOLETES"
+ $RPM -q --qf "[%{PROVIDES}\n]" -- "$1" | grep "(none)" > /dev/null ||
+ echo "$FILEPREF 0 $DATE INFO/PROVIDES"
+ $RPM -q --qf "[%{CONFLICTS}\n]" -- "$1" | grep "(none)" > /dev/null ||
+ echo "$FILEPREF 0 $DATE INFO/CONFLICTS"
+ test "`$RPM -q --qf \"%{CHANGELOGTEXT}\" -- "$1"`" = "(none)" ||
+ echo "$FILEPREF 0 $DATE INFO/CHANGELOG"
+ else
+ echo "$FILEPREF 0 $DATE INFO/REQUIRENAME"
+ echo "$FILEPREF 0 $DATE INFO/OBSOLETES"
+ echo "$FILEPREF 0 $DATE INFO/PROVIDES"
+ echo "$FILEPREF 0 $DATE INFO/CONFLICTS"
+ echo "$FILEPREF 0 $DATE INFO/CHANGELOG"
+ fi
+
+ $RPM -qlv -- "$1" | grep '^[A-Za-z0-9-]'
+}
+
+mcrpmfs_copyout ()
+{
+ case "$2" in
+ HEADER) $RPM -qi -- "$1" > "$3"; exit 0;;
+ UNINSTALL) echo "# Run this to uninstall this RPM package" > "$3"; exit 0;;
+ INFO/NAME-VERSION-RELEASE) $RPM -q --qf "%{NAME}-%{VERSION}-%{RELEASE}\n" -- "$1" > "$3"; exit 0;;
+ INFO/RELEASE) $RPM -q --qf "%{RELEASE}\n" -- "$1" > "$3"; exit 0;;
+ INFO/GROUP) $RPM -q --qf "%{GROUP}\n" -- "$1" > "$3"; exit 0;;
+ INFO/DISTRIBUTION) $RPM -q --qf "%{DISTRIBUTION}\n" -- "$1" > "$3"; exit 0;;
+ INFO/VENDOR) $RPM -q --qf "%{VENDOR}\n" -- "$1" > "$3"; exit 0;;
+ INFO/BUILDHOST) $RPM -q --qf "%{BUILDHOST}\n" -- "$1" > "$3"; exit 0;;
+ INFO/SOURCERPM) $RPM -q --qf "%{SOURCERPM}\n" -- "$1" > "$3"; exit 0;;
+ INFO/DESCRIPTION) $RPM -q --qf "%{DESCRIPTION}\n" -- "$1" > "$3"; exit 0;;
+ INFO/PACKAGER) $RPM -q --qf "%{PACKAGER}\n" -- "$1" > "$3"; exit 0;;
+ INFO/URL) $RPM -q --qf "%{URL}\n" -- "$1" > "$3"; exit 0;;
+ INFO/BUILDTIME) $RPM -q --qf "%{BUILDTIME:date}\n" -- "$1" > "$3"; exit 0;;
+ INFO/EPOCH) $RPM -q --qf "%{EPOCH}\n" -- "$1" > "$3"; exit 0;;
+ INFO/LICENSE) $RPM -q --qf "%{LICENSE}\n" -- "$1" > "$3"; exit 0;;
+ INFO/RPMVERSION) $RPM -q --qf "%{RPMVERSION}\n" -- "$1" > "$3"; exit 0;;
+ INFO/REQUIRENAME) $RPM -q --qf "[%{REQUIRENAME} %{REQUIREFLAGS:depflags} %{REQUIREVERSION}\n]" -- "$1" > "$3"; exit 0;;
+ INFO/OBSOLETES) $RPM -q --qf "[%{OBSOLETENAME} %|OBSOLETEFLAGS?{%{OBSOLETEFLAGS:depflags} %{OBSOLETEVERSION}}:{}|\n]" -- "$1" > "$3"; exit 0;;
+ INFO/PROVIDES) $RPM -q --qf "[%{PROVIDES}\n]" -- "$1" > "$3"; exit 0;;
+ INFO/CONFLICTS) $RPM -q --qf "[%{CONFLICTS}\n]" -- "$1" > "$3"; exit 0;;
+ INFO/SCRIPTS/PREIN) $RPM -q --qf "%{RPMTAG_PREIN}\n" -- "$1" > "$3"; exit 0;;
+ INFO/SCRIPTS/POSTIN) $RPM -q --qf "%{RPMTAG_POSTIN}\n" -- "$1" > "$3"; exit 0;;
+ INFO/SCRIPTS/PREUN) $RPM -q --qf "%{RPMTAG_PREUN}\n" -- "$1" > "$3"; exit 0;;
+ INFO/SCRIPTS/POSTUN) $RPM -q --qf "%{RPMTAG_POSTUN}\n" -- "$1" > "$3"; exit 0;;
+ INFO/SCRIPTS/VERIFYSCRIPT) $RPM -q --qf "%{VERIFYSCRIPT}\n" -- "$1" > "$3"; exit 0;;
+ INFO/SCRIPTS/ALL) $RPM -q --scripts -- "$1" > "$3"; exit 0;;
+ INFO/SUMMARY) $RPM -q --qf "%{SUMMARY}\n" -- "$1" > "$3"; exit 0;;
+ INFO/OS) $RPM -q --qf "%{OS}\n" -- "$1" > "$3"; exit 0;;
+ INFO/CHANGELOG) $RPM -q --qf "[* %{CHANGELOGTIME:date} %{CHANGELOGNAME}\n%{CHANGELOGTEXT}\n\n]\n" -- "$1" > "$3"; exit 0;;
+ INFO/SIZE) $RPM -q --qf "%{SIZE} bytes\n" -- "$1" > "$3"; exit 0;;
+ *)
+ cp "/$2" "$3"
+ esac
+}
+
+mcrpmfs_run ()
+{
+ case "$2" in
+ UNINSTALL) echo "Uninstalling $1"; rpm -e -- "$1"; exit 0;;
+ esac
+}
+
+name=`sed 's/.*\///;s/\.trpm$//' "$2"`
+
+case "$1" in
+ list) mcrpmfs_list "$name"; exit 0;;
+ copyout) mcrpmfs_copyout "$name" "$3" "$4"; exit 0;;
+ run) mcrpmfs_run "$name" "$3"; exit 1;;
+esac
+exit 1
diff --git a/src/vfs/extfs/helpers/u7z b/src/vfs/extfs/helpers/u7z
new file mode 100755
index 0000000..91301c3
--- /dev/null
+++ b/src/vfs/extfs/helpers/u7z
@@ -0,0 +1,135 @@
+#! /bin/sh
+#
+# extfs support for p7zip
+# Written by Pavel Roskin <proski@gnu.org>
+# Some Bugfixes/workarounds by Sergiy Niskorodov <sgh@mail.zp.ua>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+P7ZIP=`which 7z 2>/dev/null` \
+ || P7ZIP=`which 7zz 2>/dev/null` \
+ || P7ZIP=`which 7za 2>/dev/null` \
+ || P7ZIP=`which 7zr 2>/dev/null` \
+ || P7ZIP=""
+
+# Let the test framework hook in:
+P7ZIP=${MC_TEST_EXTFS_LIST_CMD:-$P7ZIP}
+STAT=${MC_TEST_EXTFS_U7Z_STAT:-stat}
+
+mcu7zip_list ()
+{
+ # Symlinks are not shown - no idea how to distinguish them
+ # Read-only files are not shown as such - it's rarely useful
+
+ ugid="`id -nu` `id -ng`"
+
+ date_re='^\(....\)-\(..\)-\(..\) \(..:..:..\)' # 19 chars.
+ date_mc='\2-\3-\1 \4'
+ empty_date_re='^ \{19\}'
+
+ size_re='............' # 12 chars.
+ empty_size_re=' \{12\}'
+ zero_size=' 0'
+
+ # archive entries can have no datetime info, 7z will use archive file datetime
+ date_archive=`$STAT -c %y "$1" 2>/dev/null | sed -n "s/${date_re}.*/${date_mc}/p" 2>/dev/null`
+ [ "${date_archive}"x = x ] && date_archive=`ls -lan "$1" 2>/dev/null | awk '{print $6, $7, $8}' 2>/dev/null`
+ [ "${date_archive}"x = x ] && date_archive="01-01-1970 00:00:00"
+
+ $P7ZIP l "$1" | sed -n "
+
+ # If the uncompressed size is missing, we copy the compressed size onto it.
+ #
+ # But first, if the compressed size is missing too, set it to zero:
+ s/^\(.\{19\} [D.]....\) $empty_size_re $empty_size_re/\1 $zero_size $zero_size/
+ # Next, do the copy:
+ s/^\(.\{19\} [D.]....\) $empty_size_re \($size_re\)/\1 \2 \2/
+ #
+ # (We use '.\{19\}' as the date may be missing. It may give false positives
+ # but we don't mind: the printing commands that follow use strict patterns.).
+
+ # Handle directories.
+ s/$date_re D.... $size_re $size_re\(.*\)/drwxr-xr-x 1 $ugid 0 $date_mc \5/p
+ s/$empty_date_re D.... $size_re $size_re\(.*\)/drwxr-xr-x 1 $ugid 0 $date_archive \1/p
+
+ # Handle normal files.
+ s/$date_re \..... \($size_re\) $size_re\(.*\)/-rw-r--r-- 1 $ugid \5 $date_mc \6/p
+ s/$empty_date_re \..... \($size_re\) $size_re\(.*\)/-rw-r--r-- 1 $ugid \1 $date_archive \2/p
+ "
+}
+
+mcu7zip_copyout ()
+{
+ #first we check if we have old p7zip archive with prefix ./ in filename
+ $P7ZIP l "$1" "$2" | grep -q "0 files, 0 folders" && \
+ EXFNAME='*./'"$2" || EXFNAME="$2"
+ $P7ZIP e -so "$1" "$EXFNAME" > "$3" 2>/dev/null
+}
+
+mcu7zip_copyin ()
+{
+ $P7ZIP a -si"$2" "$1" <"$3" >/dev/null 2>&1
+}
+
+mcu7zip_mkdir ()
+{
+ dir=`mktemp -d "${MC_TMPDIR:-/tmp}/mctmpdir-u7z.XXXXXX"` || exit 1
+ mkdir -p "$dir"/"$2"
+ $P7ZIP a -w"$dir" "$1" "$dir"/"$2" >/dev/null 2>&1
+ rm -rf "$dir"
+}
+
+mcu7zip_rm ()
+{
+ # NOTE: Version 4.20 fails to delete files in subdirectories
+ #first we check if we have old p7zip archive with prefix ./ in filename
+ $P7ZIP l "$1" "$2" | grep -q "0 files, 0 folders" && \
+ EXFNAME='*./'"$2" || EXFNAME="$2"
+ $P7ZIP d "$1" "$EXFNAME" 2>&1 | grep -q E_NOTIMPL > /dev/null 2>&1 && \
+ { printf "Function not implemented...\n7z cannot delete from solid archive." >&2 ; exit 1 ; }
+}
+
+mcu7zip_rmdir ()
+{
+ #first we check if we have old p7zip archive with prefix ./ in filename
+ $P7ZIP l "$1" "$2" | grep -q "0 files, 0 folders" && \
+ EXFNAME='*./'"$2" || EXFNAME="$2"
+ $P7ZIP d "$1" "$EXFNAME"/ 2>&1 | grep -q E_NOTIMPL > /dev/null 2>&1 && \
+ { printf "Function not implemented...\n7z cannot delete from solid archive." >&2 ; exit 1 ; }
+}
+
+# override any locale for dates
+LC_DATE=C
+export LC_DATE
+
+umask 077
+
+if [ -z "$P7ZIP" ]; then
+ echo "Error: could not find p7zip (looked for 7z, 7za and 7zr)" >&2
+ exit 1
+fi
+
+cmd="$1"
+shift
+
+case "$cmd" in
+ list) mcu7zip_list "$@" | sort -k 8 ;;
+ copyout) mcu7zip_copyout "$@" ;;
+ copyin) mcu7zip_copyin "$@" ;;
+ mkdir) mcu7zip_mkdir "$@" ;;
+ rm) mcu7zip_rm "$@" ;;
+ rmdir) mcu7zip_rmdir "$@" ;;
+ *) exit 1 ;;
+esac
+exit 0
diff --git a/src/vfs/extfs/helpers/uace.in b/src/vfs/extfs/helpers/uace.in
new file mode 100644
index 0000000..22eae30
--- /dev/null
+++ b/src/vfs/extfs/helpers/uace.in
@@ -0,0 +1,67 @@
+#! /bin/sh
+
+#
+# ACE Virtual filesystem executive v0.1
+# Works with unace v2.5
+
+# Note: There are two packages for Debian: 'unace' (v1.2b) and
+# 'unace-nonfree' (v2.x). This script supports 'unace-nonfree' only.
+# 'unace', which supports only old versions of ACE archives (and is
+# therefore of little use), uses the pipe character to separate columns
+# in its listing format.
+
+# Copyright (C) 2008 Jacques Pelletier
+# May be distributed under the terms of the GNU Public License
+# <jpelletier@ieee.org>
+#
+
+# Define which archiver you are using with appropriate options
+ACE_LIST=${MC_TEST_EXTFS_LIST_CMD:-"unace l"}
+ACE_GET="unace x"
+# ACE_PUT="unace ?" not available
+
+# The 'list' command executive
+
+# Unace: DD.MM.YY HH:MM packed size ratio file
+# ls:
+mc_ace_fs_list()
+{
+ if [ "x$UID" = "x" ]; then
+ UID=`id -ru 2>/dev/null`
+ if [ "x$UID" = "x" ]; then
+ UID=0
+ fi
+ fi
+ $ACE_LIST "$1" | @AWK@ -v uid=$UID '
+/%/ {
+ split($1,date,".")
+
+ if (date[3] > 50)
+ date[3]=date[3] + 1900
+ else
+ date[3]=date[3] + 2000
+
+ printf "-rw-r--r-- 1 %-8d %-8d %8d %02d-%02d-%04d %s %s\n", uid, 0, $4, date[2], date[1], date[3], $2, $6
+}' 2>/dev/null
+ exit 0
+}
+
+# Command: copyout archivename storedfilename extractto
+mc_ace_fs_copyout()
+{
+ $ACE_GET "$1" "$2" > /dev/null 2>&1
+ mv "$2" "$3"
+}
+
+# The main routine
+umask 077
+
+cmd="$1"
+shift
+
+case "$cmd" in
+ list) mc_ace_fs_list "$@" ;;
+ copyout) mc_ace_fs_copyout "$@" ;;
+ *) exit 1 ;;
+esac
+exit 0
diff --git a/src/vfs/extfs/helpers/ualz.in b/src/vfs/extfs/helpers/ualz.in
new file mode 100644
index 0000000..d054553
--- /dev/null
+++ b/src/vfs/extfs/helpers/ualz.in
@@ -0,0 +1,68 @@
+#!/bin/sh
+#
+# Written by Pavel Roskin <proski@gnu.org>
+# (C) 2005 The Free Software Foundation.
+#
+#
+
+UNALZ=unalz
+
+mcualz_list ()
+{
+ $UNALZ -l "$1" | @AWK@ -v uid=`id -nu` -v gid=`id -ng` '
+{
+ if ($1 ~ /[0-9][0-9][:/][0-9][0-9][:/][0-9][0-9]$/)
+ {
+ # Kludge for non-POSIX date format in unalz 0.50
+ split($1, date, "[/:]")
+ if (length(date[1]) == 4) {
+ pdate = date[2] "/" date[3] "/" date[1]
+ } else {
+ pdate = date[1] "/" date[2] "/" date[3]
+ }
+
+ time=$2
+ perm=$3
+ size=$4
+ sub(/^ *[^ ]* *[^ ]* *[^ ]* *[^ ]* *[^ ]* */, "")
+ file=$0
+ gsub(/\\/, "/", file)
+ if (perm ~ /.D../)
+ perm = "drwxr-xr-x"
+ else
+ perm = "-rw-r--r--"
+ printf "%s 1 %s %s %d %s %s %s\n", perm, uid, gid, size, pdate, time, file
+ }
+}
+'
+}
+
+mcualz_copyout ()
+{
+ TMPDIR=`mktemp -d ${MC_TMPDIR:-/tmp}/mctmpdir-ualz.XXXXXX` || exit 1
+
+ # This is a workaround for a bug in unalz 0.50 - it crashes if the
+ # output directory is an absolute path.
+ dir=`dirname "$TMPDIR/$2"`
+ mkdir -p "$dir"
+
+ $UNALZ -d "$TMPDIR" "$1" "$2" >/dev/null
+ cat "$TMPDIR/$2" > "$3"
+ rm -rf "$TMPDIR"
+}
+
+# override any locale for dates
+LC_ALL=C
+export LC_ALL
+umask 077
+
+cmd="$1"
+shift
+
+case "$cmd" in
+ list) mcualz_list "$@" ;;
+ copyout) mcualz_copyout "$@" ;;
+ *) exit 1 ;;
+esac
+
+exit 0
diff --git a/src/vfs/extfs/helpers/uar.in b/src/vfs/extfs/helpers/uar.in
new file mode 100644
index 0000000..269bdb6
--- /dev/null
+++ b/src/vfs/extfs/helpers/uar.in
@@ -0,0 +1,60 @@
+#!/bin/sh
+#
+# Written by Alex Kuchma <ask@bcs.zp.ua>
+# Alex Tkachenko <alex@bcs.zp.ua>
+# Updated by Vitezslav Samel <xsamel00@dcse.fee.vutbr.cz>
+#
+# (C) 1997, 1998 The Free Software Foundation.
+#
+#
+
+XAR=ar
+
+mcarfs_list ()
+{
+ # If $temp_replace string is part of the filename that part might get lost
+ temp_replace='Unique Separator String'
+ thisyear="`date +%Y`"
+ $XAR tv "$1" | sed 's,^,-,;s, , 1 ,;s,/, ,' |
+ sed -e "s/\( [0-2][0-9]\:[0-5][0-9]\)\( $thisyear \)\(.*\)/\1$temp_replace\3/" |
+ sed -e "s/\( [0-2][0-9]\:[0-5][0-9] \)\([12][0-9][0-9][0-9] \)\(.*\)/ \2\3/" |
+ sed -e "s/$temp_replace/ /"
+}
+
+mcarfs_copyout ()
+{
+ $XAR p "$1" "$2" > "$3"
+}
+
+mcarfs_copyin ()
+{
+ TMPDIR=`mktemp -d "${MC_TMPDIR:-/tmp}/mctmpdir-uar.XXXXXX"` || exit 1
+ name=`basename "$2"`
+ (cd "$TMPDIR" && cp -fp "$3" "$name" && $XAR r "$1" "$name")
+ rm -rf "$TMPDIR"
+}
+
+mcarfs_rm ()
+{
+ $XAR d "$1" "$2"
+}
+
+# override any locale for dates
+LC_ALL=C
+export LC_ALL
+
+umask 077
+case "$1" in
+ list) mcarfs_list "$2" ;;
+ copyout) shift; mcarfs_copyout "$@" ;;
+ copyin) shift; mcarfs_copyin "$@" ;;
+ rm) shift; mcarfs_rm "$@" ;;
+ mkdir|rmdir)
+ echo "mcarfs: ar archives cannot contain directories." 1>&2
+ exit 1;;
+ *)
+ echo "mcarfs: unknown command: \"$1\"." 1>&2
+ exit 1;;
+esac
+
+exit 0
diff --git a/src/vfs/extfs/helpers/uarc.in b/src/vfs/extfs/helpers/uarc.in
new file mode 100644
index 0000000..a81839a
--- /dev/null
+++ b/src/vfs/extfs/helpers/uarc.in
@@ -0,0 +1,92 @@
+#! /bin/sh
+
+#
+# ARC Virtual filesystem executive
+# Copyright (C) 2008 Jacques Pelletier
+# May be distributed under the terms of the GNU Public License
+# <jpelletier@ieee.org>
+#
+
+# Define which archiver you are using with appropriate options
+ARC_LIST=${MC_TEST_EXTFS_LIST_CMD:-"arc v"}
+ARC_GET="arc x"
+ARC_PUT="arc a"
+ARC_DEL="arc d"
+
+# The 'list' command executive
+
+mc_arc_fs_list()
+{
+ if [ "x$UID" = "x" ]; then
+ UID=`id -ru 2>/dev/null`
+ if [ "x$UID" = "x" ]; then
+ UID=0
+ fi
+ fi
+ $ARC_LIST "$1" | @AWK@ -v uid=$UID '
+BEGIN {
+ # Copied from uzoo.in.
+ split("Jan:Feb:Mar:Apr:May:Jun:Jul:Aug:Sep:Oct:Nov:Dec", month_list, ":")
+ for (i=1; i<=12; i++) {
+ month[month_list[i]] = i
+ }
+}
+/^Name/ { next }
+/===/ { next }
+/^Total/ { next }
+{
+ if ($8 > 50)
+ $8=$8 + 1900
+ else
+ $8=$8 + 2000
+
+ split($9, a, ":")
+
+ # convert AM/PM to 00-23
+ if (a[2] ~ /a$|p$/)
+ {
+ if (a[2] ~ /p$/)
+ a[1] = a[1]+12
+
+ a[2]=substr(a[2],1,2)
+ }
+
+ printf "-rw-r--r-- 1 %-8d %-8d %8d %02d-%02d-%04d %02d:%02d %s\n", uid, 0, $2, month[$7], $6, $8, a[1], a[2], $1
+}' 2>/dev/null
+ exit 0
+}
+
+# Command: copyout archivename storedfilename extractto
+mc_arc_fs_copyout()
+{
+ $ARC_GET "$1" "$2" 2> /dev/null
+ mv "$2" "$3"
+}
+
+# Command: copyin archivename storedfilename sourcefile
+mc_arc_fs_copyin()
+{
+ mv "$3" "$2"
+ $ARC_PUT "$1" "$2" 2> /dev/null
+}
+
+# Command: rm archivename storedfilename
+mc_arc_fs_rm()
+{
+ $ARC_DEL "$1" "$2" 2> /dev/null
+}
+
+# The main routine
+umask 077
+
+cmd="$1"
+shift
+
+case "$cmd" in
+ list) mc_arc_fs_list "$@" ;;
+ copyout) mc_arc_fs_copyout "$@" ;;
+ copyin) mc_arc_fs_copyin "$@" ;;
+ rm) mc_arc_fs_rm "$@" ;;
+ *) exit 1 ;;
+esac
+exit 0
diff --git a/src/vfs/extfs/helpers/uarj.in b/src/vfs/extfs/helpers/uarj.in
new file mode 100644
index 0000000..15549a0
--- /dev/null
+++ b/src/vfs/extfs/helpers/uarj.in
@@ -0,0 +1,75 @@
+#! /bin/sh
+#
+# Written by Viatcheslav Odintsov (2:5020/181)
+# (C) 2002 ARJ Software Russia.
+#
+# This is an updated parser for ARJ archives in Midnight Commander. You need
+# full ARJ rather than UNARJ. Open-source ARJ v 3.10 for Unix platforms can
+# be obtained here:
+#
+# - http://www.sourceforge.net/projects/arj/
+# - http://arj.sourceforge.net/
+
+
+ARJ="arj -+ -ja1"
+
+
+mcarjfs_list ()
+{
+ $ARJ v "$1" | @AWK@ -v uuid=$(id -ru) '
+ {
+ if (($0 ~ /^[0-9]+\) .*/)||($0 ~ /^------------ ---------- ---------- -----/)){
+ if (filestr ~ /^[0-9]+\) .*/) {
+ printf "%s 1 %-8d %-8d %8d %02d-%02d-%02d %02d:%02d %s%s\n", perm, uid, gid, size, date[2], date[3], date[1], time[1], time[2], file, symfile
+ perm=""
+ file=""
+ symfile=""
+ filestr=""
+ }
+ }
+
+ if ($0 ~ /^[0-9]+\) .*/) {
+ filestr=$0
+ sub(/^[0-9]*\) /, "")
+ file=$0
+ uid=uuid
+ gid=0
+ }
+
+ if ($0 ~ /^.* [0-9]+[\t ]+[0-9]+ [0-9]\.[0-9][0-9][0-9] [0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].*/) {
+ size=$3
+ split($6, date, "-")
+ split($7, time, ":")
+ if ($8 ~ /^[rwx-]/) {perm=$8;}
+ else {perm="-rw-r--r--"}
+ }
+
+ if ($0 ~ /^[\t ]+SymLink -> .*/) {
+ symfile = " -> "$3
+ perm="l"substr(perm, 2)
+ }
+
+ if ($0 ~ /^[\t ]+Owner: UID [0-9]+\, GID [0-9]+/) {
+ uid=$3
+ gid=$5
+ owner=1
+ }
+ }'
+}
+
+
+mcarjfs_copyout ()
+{
+ $ARJ e -y "$1" "$2" -jw"$3" >/dev/null 2>/dev/null
+}
+
+
+umask 077
+cmd="$1"
+shift
+case "$cmd" in
+ list) mcarjfs_list "$@" ;;
+ copyout) mcarjfs_copyout "$@" ;;
+ *) exit 1 ;;
+esac
+exit 0
diff --git a/src/vfs/extfs/helpers/uc1541 b/src/vfs/extfs/helpers/uc1541
new file mode 100755
index 0000000..dc15b42
--- /dev/null
+++ b/src/vfs/extfs/helpers/uc1541
@@ -0,0 +1,702 @@
+#!/usr/bin/env python
+"""
+UC1541 Virtual filesystem
+
+Author: Roman 'gryf' Dobosz <gryf73@gmail.com>
+Date: 2019-09-20
+Version: 3.3
+Licence: BSD
+source: https://bitbucket.org/gryf/uc1541
+mirror: https://github.com/gryf/uc1541
+"""
+
+import sys
+import re
+import os
+import gzip
+from subprocess import Popen, PIPE
+
+if os.getenv('UC1541_DEBUG'):
+ import logging
+ LOG = logging.getLogger('UC1541')
+ LOG.setLevel(logging.DEBUG)
+ FILE_HANDLER = logging.FileHandler("/tmp/uc1541.log")
+ FILE_FORMATTER = logging.Formatter("%(asctime)s %(levelname)-8s "
+ "%(lineno)s %(funcName)s - %(message)s")
+ FILE_HANDLER.setFormatter(FILE_FORMATTER)
+ FILE_HANDLER.setLevel(logging.DEBUG)
+ LOG.addHandler(FILE_HANDLER)
+else:
+ class LOG(object):
+ """
+ Dummy logger object. Does nothing.
+ """
+ @classmethod
+ def debug(*args, **kwargs):
+ pass
+
+ @classmethod
+ def info(*args, **kwargs):
+ pass
+
+ @classmethod
+ def warning(*args, **kwargs):
+ pass
+
+ @classmethod
+ def error(*args, **kwargs):
+ pass
+
+ @classmethod
+ def critical(*args, **kwargs):
+ pass
+
+
+SECLEN = 256
+
+
+def _ord(string_or_int):
+ """
+ Return an int value for the (possible) string passed in argument. This
+ function is for compatibility between python2 and python3, where single
+ element in byte string array is a string or an int respectively.
+ """
+ try:
+ return ord(string_or_int)
+ except TypeError:
+ return string_or_int
+
+
+def _get_raw(dimage):
+ """
+ Try to get contents of the D64 image either it's gzip compressed or not.
+ """
+ raw = None
+ with gzip.open(dimage, 'rb') as fobj:
+ # Although the common approach with gzipped files is to check the
+ # magic number, in this case there is no guarantee that first track
+ # does not contain exactly the same byte sequence as the magic number.
+ # So the only way left is to actually try to uncompress the file.
+ try:
+ raw = fobj.read()
+ except (IOError, OSError):
+ pass
+ if not raw:
+ with open(dimage, 'rb') as fobj:
+ raw = fobj.read()
+
+ return raw
+
+
+def _get_implementation(disk):
+ """
+ Check the file under fname and return right class for creating an object
+ corresponding for the file
+ """
+ len_map = {822400: D81, # 80 tracks
+ 819200: D81, # 80 tracks, 3200 error bytes
+ 349696: D71, # 70 tracks
+ 351062: D71, # 70 tracks, 1366 error bytes
+ 174848: D64, # usual d64 disc image, 35 tracks, no errors
+ 175531: D64, # 35 track, 683 error bytes
+ 196608: D64, # 40 track, no errors
+ 197376: D64} # 40 track, 768 error bytes
+
+ if disk[:32].startswith(b'C64'):
+ return # T64
+
+ return len_map.get(len(disk))(disk)
+
+
+class Disk(object):
+ """
+ Represent common disk interface
+ """
+ CHAR_MAP = {32: ' ', 33: '!', 34: '"', 35: '#', 37: '%', 38: '&', 39: "'",
+ 40: '(', 41: ')', 42: '*', 43: '+', 44: ',', 45: '-', 46: '.',
+ 47: '/', 48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5',
+ 54: '6', 55: '7', 56: '8', 57: '9', 59: ';', 60: '<', 61: '=',
+ 62: '>', 63: '?', 64: '@', 65: 'a', 66: 'b', 67: 'c', 68: 'd',
+ 69: 'e', 70: 'f', 71: 'g', 72: 'h', 73: 'i', 74: 'j', 75: 'k',
+ 76: 'l', 77: 'm', 78: 'n', 79: 'o', 80: 'p', 81: 'q', 82: 'r',
+ 83: 's', 84: 't', 85: 'u', 86: 'v', 87: 'w', 88: 'x', 89: 'y',
+ 90: 'z', 91: '[', 93: ']', 97: 'A', 98: 'B', 99: 'C',
+ 100: 'D', 101: 'E', 102: 'F', 103: 'G', 104: 'H', 105: 'I',
+ 106: 'J', 107: 'K', 108: 'L', 109: 'M', 110: 'N', 111: 'O',
+ 112: 'P', 113: 'Q', 114: 'R', 115: 'S', 116: 'T', 117: 'U',
+ 118: 'V', 119: 'W', 120: 'X', 121: 'Y', 122: 'Z', 193: 'A',
+ 194: 'B', 195: 'C', 196: 'D', 197: 'E', 198: 'F', 199: 'G',
+ 200: 'H', 201: 'I', 202: 'J', 203: 'K', 204: 'L', 205: 'M',
+ 206: 'N', 207: 'O', 208: 'P', 209: 'Q', 210: 'R', 211: 'S',
+ 212: 'T', 213: 'U', 214: 'V', 215: 'W', 216: 'X', 217: 'Y',
+ 218: 'Z'}
+
+ FILE_TYPES = {0b000: 'del',
+ 0b001: 'seq',
+ 0b010: 'prg',
+ 0b011: 'usr',
+ 0b100: 'rel'}
+
+ DIR_TRACK = 18
+ DIR_SECTOR = 1
+
+ def __init__(self, raw):
+ """
+ Init
+ """
+ self.raw = raw
+ self.current_sector_data = None
+ self.next_sector = 0
+ self.next_track = None
+ self._dir_contents = []
+ self._already_done = []
+
+ def _map_filename(self, string):
+ """
+ Transcode filename to ASCII compatible. Replace not supported
+ characters with jokers.
+ """
+
+ filename = list()
+
+ for chr_ in string:
+ if _ord(chr_) == 160: # shift+space character; $a0
+ break
+
+ character = D64.CHAR_MAP.get(_ord(chr_), '?')
+ filename.append(character)
+
+ # special cases
+ if filename[0] == "-":
+ filename[0] = "?"
+
+ LOG.debug("string: ``%s'' mapped to: ``%s''", string,
+ "".join(filename))
+ return "".join(filename)
+
+ def _go_to_next_sector(self):
+ """
+ Fetch (if exist) next sector from a directory chain
+ Return False if the chain ends, True otherwise
+ """
+
+ # Well, self.next_sector _should_ have value $FF, but apparently there
+ # are the cases where it is not, therefore checking for that will not
+ # be performed and value of $00 on the next track will end the
+ # directory
+ if self.next_track == 0:
+ LOG.debug("End of directory")
+ return False
+
+ if self.next_track is None:
+ LOG.debug("Going to the track: %s, %s", self.DIR_TRACK,
+ self.DIR_SECTOR)
+ offset = self._get_offset(self.DIR_TRACK, self.DIR_SECTOR)
+ else:
+ offset = self._get_offset(self.next_track, self.next_sector)
+ LOG.debug("Going to the track: %s,%s", self.next_track,
+ self.next_sector)
+
+ self.current_sector_data = self.raw[offset:offset + SECLEN]
+
+ # Guard for reading data out of bound - that happened for discs which
+ # store only raw data, even on directory track
+ if not self.current_sector_data:
+ return False
+
+ self.next_track = _ord(self.current_sector_data[0])
+ self.next_sector = _ord(self.current_sector_data[1])
+
+ if (self.next_track, self.next_sector) in self._already_done:
+ # Just a failsafe. Endless loop is not what is expected.
+ LOG.debug("Loop in track/sector pointer at %d,%d",
+ self.next_track, self.next_sector)
+ self._already_done = []
+ return False
+
+ self._already_done.append((self.next_track, self.next_sector))
+ LOG.debug("Next track: %s,%s", self.next_track, self.next_sector)
+ return True
+
+ def _get_ftype(self, num):
+ """
+ Get filetype as a string
+ """
+ return D64.FILE_TYPES.get(int("%d%d%d" % (num & 4 and 1,
+ num & 2 and 1,
+ num & 1), 2), '???')
+
+ def _get_offset(self, track, sector):
+ """
+ Return offset (in bytes) for specified track and sector.
+ """
+ return 0
+
+ def _harvest_entries(self):
+ """
+ Traverse through sectors and store entries in _dir_contents
+ """
+ sector = self.current_sector_data
+ for dummy in range(8):
+ entry = sector[:32]
+ ftype = _ord(entry[2])
+
+ if ftype == 0: # deleted
+ sector = sector[32:]
+ continue
+
+ type_verbose = self._get_ftype(ftype)
+
+ protect = _ord(entry[2]) & 64 and "<" or " "
+ fname = entry[5:21]
+ if ftype == 'rel':
+ size = _ord(entry[23])
+ else:
+ size = _ord(entry[30]) + _ord(entry[31]) * 226
+
+ self._dir_contents.append({'fname': self._map_filename(fname),
+ 'ftype': type_verbose,
+ 'size': size,
+ 'protect': protect})
+ sector = sector[32:]
+
+ def list_dir(self):
+ """
+ Return directory list as list of dict with keys:
+ fname, ftype, protect and size
+ """
+ while self._go_to_next_sector():
+ self._harvest_entries()
+
+ return self._dir_contents
+
+
+class D64(Disk):
+ """
+ Implement d64 directory reader
+ """
+
+ def _get_offset(self, track, sector):
+ """
+ Return offset (in bytes) for specified track and sector.
+
+ Track Sectors/track # Tracks
+ ----- ------------- ---------
+ 1-17 21 17
+ 18-24 19 7
+ 25-30 18 6
+ 31-40 17 10
+ """
+ offset = 0
+ truncate_track = 0
+
+ if track > 17:
+ offset = 17 * 21 * SECLEN
+ truncate_track = 17
+
+ if track > 24:
+ offset += 7 * 19 * SECLEN
+ truncate_track = 24
+
+ if track > 30:
+ offset += 6 * 18 * SECLEN
+ truncate_track = 30
+
+ track = track - truncate_track
+ offset += track * sector * SECLEN
+
+ return offset
+
+
+class D71(Disk):
+ """
+ Implement d71 directory reader
+ """
+
+ def _get_offset(self, track, sector):
+ """
+ Return offset (in bytes) for specified track and sector.
+
+ Track Sec/trk # Tracks
+ -------------- ------- ---------
+ 1-17 (side 0) 21 17
+ 18-24 (side 0) 19 7
+ 25-30 (side 0) 18 6
+ 31-35 (side 0) 17 5
+ 36-52 (side 1) 21 17
+ 53-59 (side 1) 19 7
+ 60-65 (side 1) 18 6
+ 66-70 (side 1) 17 5
+ """
+ offset = 0
+ truncate_track = 0
+
+ if track > 17:
+ offset = 17 * 21 * SECLEN
+ truncate_track = 17
+
+ if track > 24:
+ offset += 7 * 19 * SECLEN
+ truncate_track = 24
+
+ if track > 30:
+ offset += 6 * 18 * SECLEN
+ truncate_track = 30
+
+ if track > 35:
+ offset += 5 * 17 * SECLEN
+ truncate_track = 35
+
+ if track > 52:
+ offset = 17 * 21 * SECLEN
+ truncate_track = 17
+
+ if track > 59:
+ offset += 7 * 19 * SECLEN
+ truncate_track = 24
+
+ if track > 65:
+ offset += 6 * 18 * SECLEN
+ truncate_track = 30
+
+ track = track - truncate_track
+ offset += track * sector * SECLEN
+
+ return offset
+
+
+class D81(Disk):
+ """
+ Implement d81 directory reader
+ """
+ DIR_TRACK = 40
+ DIR_SECTOR = 3
+ FILE_TYPES = {0b000: 'del',
+ 0b001: 'seq',
+ 0b010: 'prg',
+ 0b011: 'usr',
+ 0b100: 'rel',
+ 0b101: 'cbm'}
+
+ def _get_offset(self, track, sector):
+ """
+ Return offset (in bytes) for specified track and sector. In d81 is
+ easy, since we have 80 tracks with 40 sectors for 256 bytes each.
+ """
+ # we wan to go to the beginning (first sector) of the track, not it's
+ # max, so that we need to extract its amount.
+ return (track * 40 - 40) * SECLEN + sector * SECLEN
+
+
+class Uc1541(object):
+ """
+ Class for interact with c1541 program and MC
+ """
+ PRG = re.compile(r'(\d+)\s+"([^"]*)".+?\s(del|prg|rel|seq|usr)([\s<])')
+
+ def __init__(self, archname):
+ self.arch = archname
+ self.out = ''
+ self.err = ''
+ self._verbose = os.getenv("UC1541_VERBOSE", False)
+ self._hide_del = os.getenv("UC1541_HIDE_DEL", False)
+
+ self.dirlist = _get_implementation(_get_raw(archname)).list_dir()
+ self.file_map = {}
+ self.directory = []
+
+ def list(self):
+ """
+ Output list contents of D64 image.
+ Convert filenames to be Unix filesystem friendly
+ Add suffix to show user what kind of file do he dealing with.
+ """
+ LOG.info("List contents of %s", self.arch)
+ directory = self._get_dir()
+
+ # If there is an error reading directory, show the reason to the user
+ if self.out.startswith("Error"):
+ sys.stderr.write(self.out.split("\n")[0] + "\n")
+ return 2
+
+ for entry in directory:
+ sys.stdout.write("%(perms)s 1 %(uid)-8d %(gid)-8d %(size)8d "
+ "Jan 01 1980 %(display_name)s\n" % entry)
+ return 0
+
+ def rm(self, dst):
+ """
+ Remove file from D64 image
+ """
+ LOG.info("Removing file %s", dst)
+ dst = self._get_masked_fname(dst)
+
+ if not self._call_command('delete', dst=dst):
+ return self._show_error()
+
+ return 0
+
+ def copyin(self, dst, src):
+ """
+ Copy file to the D64 image. Destination filename has to be corrected.
+ """
+ LOG.info("Copy into D64 %s as %s", src, dst)
+ dst = self._correct_fname(dst)
+
+ if not self._call_command('write', src=src, dst=dst):
+ return self._show_error()
+
+ return 0
+
+ def copyout(self, src, dst):
+ """
+ Copy file form the D64 image. Source filename has to be corrected,
+ since it's representation differ from the real one inside D64 image.
+ """
+ LOG.info("Copy form D64 %s as %s", src, dst)
+ if not src.endswith(".prg"):
+ return "cannot read"
+
+ src = self._get_masked_fname(src)
+
+ if not self._call_command('read', src=src, dst=dst):
+ return self._show_error()
+
+ return 0
+
+ def mkdir(self, dirname):
+ """Not supported"""
+ self.err = "D64 format doesn't support directories"
+ return self._show_error()
+
+ def run(self, fname):
+ """Not supported"""
+ self.err = "Not supported, unless you are using MC on real C64 ;)"
+ return self._show_error()
+
+ def _correct_fname(self, fname):
+ """
+ Return filename with mapped characters, without .prg extension.
+ Characters like $, *, + in filenames are perfectly legal, but c1541
+ program seem to have issues with it while writing, so it will also be
+ replaced.
+ """
+ char_map = {'|': "/",
+ "\\": "/",
+ "~": " ",
+ "$": "?",
+ "*": "?"}
+
+ if fname.lower().endswith(".prg"):
+ fname = fname[:-4]
+
+ new_fname = []
+ for char in fname:
+ trans = char_map.get(char)
+ new_fname.append(trans if trans else char)
+
+ return "".join(new_fname)
+
+ def _get_masked_fname(self, fname):
+ """
+ Return masked filename with '?' jokers instead of non ASCII
+ characters, useful for copying or deleting files with c1541. In case
+ of several files with same name exists in directory, only first one
+ will be operative (first as appeared in directory).
+
+ Warning! If there are two different names but the only difference is in
+ non-ASCII characters (some PET ASCII or control characters) there is
+ a risk that one can remove both files.
+ """
+ directory = self._get_dir()
+
+ for entry in directory:
+ if entry['display_name'] == fname:
+ return entry['pattern_name']
+
+ def _get_dir(self):
+ """
+ Retrieve directory via c1541 program
+ """
+ directory = []
+
+ uid = os.getuid()
+ gid = os.getgid()
+
+ if not self._call_command('list'):
+ return self._show_error()
+
+ idx = 0
+ for line in self.out.split("\n"):
+ if Uc1541.PRG.match(line):
+ blocks, fname, ext, rw = Uc1541.PRG.match(line).groups()
+
+ if ext == 'del' and self._hide_del:
+ continue
+
+ display_name = ".".join([fname, ext])
+ pattern_name = self.dirlist[idx]['fname']
+
+ if '/' in display_name:
+ display_name = display_name.replace('/', '|')
+
+ # workaround for space and dash at the beginning of the
+ # filename
+ char_map = {' ': '~',
+ '-': '_'}
+ display_name = "".join([char_map.get(display_name[0],
+ display_name[0]),
+ display_name[1:]])
+
+ if ext == 'del':
+ perms = "----------"
+ else:
+ perms = "-r%s-r--r--" % (rw.strip() and "-" or "w")
+
+ directory.append({'pattern_name': pattern_name,
+ 'display_name': display_name,
+ 'uid': uid,
+ 'gid': gid,
+ 'size': int(blocks) * SECLEN,
+ 'perms': perms})
+ idx += 1
+ return directory
+
+ def _show_error(self):
+ """
+ Pass out error output from c1541 execution
+ """
+ if self._verbose:
+ return self.err
+ else:
+ return 1
+
+ def _call_command(self, cmd, src=None, dst=None):
+ """
+ Return status of the provided command, which can be one of:
+ write
+ read
+ delete
+ dir/list
+ """
+ command = ['c1541', '-attach', self.arch, '-%s' % cmd]
+ if src:
+ command.append(src)
+ if dst:
+ command.append(dst)
+
+ LOG.debug('executing command: %s', ' '.join(command))
+ # For some reason using write and delete commands and reading output
+ # confuses Python3 beneath MC and as a consequence MC report an
+ # error...therefore for those commands let's not use
+ # universal_newlines...
+ universal_newlines = True
+ if cmd in ['delete', 'write']:
+ universal_newlines = False
+ self.out, self.err = Popen(command,
+ universal_newlines=universal_newlines,
+ stdout=PIPE, stderr=PIPE).communicate()
+
+ if self.err:
+ LOG.debug('an err: %s', self.err)
+ return not self.err
+
+
+CALL_MAP = {'list': lambda a: Uc1541(a.arch).list(),
+ 'copyin': lambda a: Uc1541(a.arch).copyin(a.src, a.dst),
+ 'copyout': lambda a: Uc1541(a.arch).copyout(a.src, a.dst),
+ 'mkdir': lambda a: Uc1541(a.arch).mkdir(a.dst),
+ 'rm': lambda a: Uc1541(a.arch).rm(a.dst),
+ 'run': lambda a: Uc1541(a.arch).run(a.dst)}
+
+
+def parse_args():
+ """Use ArgumentParser to check for script arguments and execute."""
+ parser = ArgumentParser()
+ subparsers = parser.add_subparsers(help='supported commands',
+ dest='subcommand')
+ subparsers.required = True
+ parser_list = subparsers.add_parser('list', help="List contents of D64 "
+ "image")
+ parser_copyin = subparsers.add_parser('copyin', help="Copy file into D64 "
+ "image")
+ parser_copyout = subparsers.add_parser('copyout', help="Copy file out of "
+ "D64 image")
+ parser_rm = subparsers.add_parser('rm', help="Delete file from D64 image")
+ parser_mkdir = subparsers.add_parser('mkdir', help="Create directory in "
+ "archive")
+ parser_run = subparsers.add_parser('run', help="Execute archived file")
+
+ parser_list.add_argument('arch', help="D64 Image filename")
+ parser_list.set_defaults(func=CALL_MAP['list'])
+
+ parser_copyin.add_argument('arch', help="D64 Image filename")
+ parser_copyin.add_argument('src', help="Source filename")
+ parser_copyin.add_argument('dst', help="Destination filename (to be "
+ "written into D64 image)")
+ parser_copyin.set_defaults(func=CALL_MAP['copyin'])
+
+ parser_copyout.add_argument('arch', help="D64 Image filename")
+ parser_copyout.add_argument('src', help="Source filename (to be read from"
+ " D64 image")
+ parser_copyout.add_argument('dst', help="Destination filename")
+ parser_copyout.set_defaults(func=CALL_MAP['copyout'])
+
+ parser_rm.add_argument('arch', help="D64 Image filename")
+ parser_rm.add_argument('dst', help="File inside D64 image to be deleted")
+ parser_rm.set_defaults(func=CALL_MAP['rm'])
+
+ parser_mkdir.add_argument('arch', help="archive filename")
+ parser_mkdir.add_argument('dst', help="Directory name inside archive to "
+ "be created")
+ parser_mkdir.set_defaults(func=CALL_MAP['mkdir'])
+
+ parser_run.add_argument('arch', help="archive filename")
+ parser_run.add_argument('dst', help="File to be executed")
+ parser_run.set_defaults(func=CALL_MAP['run'])
+
+ args = parser.parse_args()
+ return args.func(args)
+
+
+def no_parse():
+ """Failsafe argument "parsing". Note, that it blindly takes positional
+ arguments without checking them. In case of wrong arguments it will
+ silently exit"""
+ try:
+ if sys.argv[1] not in ('list', 'copyin', 'copyout', 'rm', 'mkdir',
+ "run"):
+ sys.exit(2)
+ except IndexError:
+ sys.exit(2)
+
+ class Arg(object):
+ """Mimic argparse object"""
+ dst = None
+ src = None
+ arch = None
+
+ arg = Arg()
+
+ try:
+ arg.arch = sys.argv[2]
+ if sys.argv[1] in ('copyin', 'copyout'):
+ arg.src = sys.argv[3]
+ arg.dst = sys.argv[4]
+ elif sys.argv[1] in ('rm', 'run', 'mkdir'):
+ arg.dst = sys.argv[3]
+ except IndexError:
+ sys.exit(2)
+
+ return CALL_MAP[sys.argv[1]](arg)
+
+
+if __name__ == "__main__":
+ LOG.debug("Script params: %s", str(sys.argv))
+ try:
+ from argparse import ArgumentParser
+ PARSE_FUNC = parse_args
+ except ImportError:
+ PARSE_FUNC = no_parse
+
+ sys.exit(PARSE_FUNC())
diff --git a/src/vfs/extfs/helpers/ucab.in b/src/vfs/extfs/helpers/ucab.in
new file mode 100644
index 0000000..252c8ca
--- /dev/null
+++ b/src/vfs/extfs/helpers/ucab.in
@@ -0,0 +1,40 @@
+#! /bin/sh
+
+CAB=cabextract
+
+mccabfs_list ()
+{
+ $CAB -l "$1" | @AWK@ -v uid=`id -un` -v gid=`id -gn` '
+BEGIN { flag=0 }
+/^-------/ { flag++; if (flag > 1) exit 0; next }
+{
+if (flag == 0) next
+if (length($6) == 0) next
+pr="-rw-r--r--"
+split($3, a, ".")
+split($4, b, ":")
+printf "%s 1 %s %s %d %02d/%02d/%02d %02d:%02d %s\n", pr, uid, gid, $1, a[2], a[1], a[3], b[1], b[2], $6
+}'
+
+}
+
+mccabfs_copyout ()
+{
+ $CAB -F "$2" -p "$1" > "$3"
+}
+
+LC_ALL=C
+export LC_ALL
+
+umask 077
+
+cmd="$1"
+
+case "$cmd" in
+ # Workaround for a bug in mc - directories must precede files to
+ # avoid duplicate entries, so we sort output by filenames
+ list) mccabfs_list "$2" ;;
+ copyout) mccabfs_copyout "$2" "$3" "$4" ;;
+ *) exit 1 ;;
+esac
+exit 0
diff --git a/src/vfs/extfs/helpers/uha.in b/src/vfs/extfs/helpers/uha.in
new file mode 100644
index 0000000..9dd0016
--- /dev/null
+++ b/src/vfs/extfs/helpers/uha.in
@@ -0,0 +1,52 @@
+#!/bin/sh
+#
+# It is the uhafs Valery Kornienkov vlk@st.simbirsk.su 2:5051/30@fidonet
+# ver 0.1 Thu Apr 6 12:05:08 2000
+#
+# Tested with HA 0.999. Source of ha can be found at
+# ftp://ftp.ibiblio.org/pub/Linux/utils/compress/
+
+HA=ha
+
+mchafs_list ()
+{
+ $HA lf "$1" 2>/dev/null | @AWK@ -v uid=$(id -ru) '
+/^===========/ {next}
+{
+ if ($5="%" && $8~/DIR|ASC|HSC|CPY/) {
+ split($6, a, "-")
+ split($7, t, ":")
+ filename=$1
+ filesize=$2
+ getline
+ if ($2=="(none)") $2=""
+ path=$2
+ getline
+ if ($1~/^d.*/) next
+ printf "%s %s %-8d %-8d %8d %s-%s-%s %s:%s %s%s\n",\
+ $1,1,0,0,filesize,a[3],a[2],a[1],t[1],t[2],path,filename
+ }
+}'
+}
+
+mchafs_copyout ()
+{
+ TMPDIR=`mktemp -d "${MC_TMPDIR:-/tmp}/mctmpdir-uha.XXXXXX"` || exit 1
+ cd "$TMPDIR"
+
+ $HA xyq "$1" "$2" >/dev/null
+ cat "$2" > "$3"
+
+ cd /
+ rm -rf "$TMPDIR"
+}
+
+cmd="$1"
+shift
+
+case "$cmd" in
+ list) mchafs_list "$@" ;;
+ copyout) mchafs_copyout "$@" ;;
+ *) exit 1 ;;
+esac
+exit 0
diff --git a/src/vfs/extfs/helpers/ulha.in b/src/vfs/extfs/helpers/ulha.in
new file mode 100644
index 0000000..0b5735c
--- /dev/null
+++ b/src/vfs/extfs/helpers/ulha.in
@@ -0,0 +1,142 @@
+#! /bin/sh
+
+#
+# LHa Virtual filesystem executive v0.1
+# Copyright (C) 1996, 1997 Joseph M. Hinkle
+# May be distributed under the terms of the GNU Public License
+# <jhinkle@rockisland.com>
+#
+
+# Code for mc_lha_fs_run() suggested by:
+# Jan 97 Zdenek Kabelac <kabi@informatics.muni.cz>
+
+# Tested with mc 3.5.18 and gawk 3.0.0 on Linux 2.0.0
+# Tested with lha v1.01 and lharc v1.02
+# Information and sources for other forms of lha/lzh appreciated
+
+# Nota bene:
+# There are several compression utilities which produce *.lha files.
+# LHArc and LHa in exist several versions, and their listing output varies.
+# Another variable is the architecture on which the compressed file was made.
+# This program attempts to sort out the variables known to me, but it is likely
+# to display an empty panel if it encounters a mystery.
+# In that case it will be useful to execute this file from the command line:
+# ./lha list Mystery.lha
+# to examine the output directly on the console. The output string must be
+# precisely in the format described in the README in this directory.
+# Caveat emptor.
+# Learn Latin.
+
+# Define your awk
+AWK=@AWK@
+
+# Define which archiver you are using with appropriate options
+LHA_LIST="lha lq"
+LHA_GET="lha pq"
+LHA_PUT="lha aq"
+
+# The 'list' command executive
+
+mc_lha_fs_list()
+{
+ # List the contents of the archive and sort it out
+ $LHA_LIST "$1" | $AWK -v uid=`id -nu` -v gid=`id -ng` '
+ # Strip a leading '/' if present in a filepath
+ $(NF) ~ /^\// { $(NF) = substr($NF,2) }
+ # Print the line this way if there is no permission string
+ $1 ~ /^\[.*\]/ {
+ # Invent a generic permission
+ $1 = ($NF ~ /\/$/) ? "drwxr-xr-x":"-rwxr--r--";
+ # Print it
+ printf "%s 1 %-8s %-8s %-8d %s %s %s %s\n",
+ $1, uid, gid, $2, $4, $5, $6, $7;
+ # Get the next line of the list
+ next;
+ }
+ # Do it this way for a defined permission
+ $1 !~ /^\[.*\]/ {
+ # If the permissions and UID run together
+ if ($1 ~ /\//) {
+ $8 = $7;
+ $7 = $6;
+ $6 = $5;
+ $5 = $4;
+ $3 = $2;
+ $2 = substr($1,10);
+ $1 = substr($1,1,9);
+ }
+ # If the permission string is missing a type
+ if (length($1) == 9) {
+ if ($NF ~ /\/$/)
+ $1 = ("d" $1);
+ else
+ $1 = ("-" $1);
+ }
+ # UID:GID might not be the same as on your system so print numbers
+ # Well, that is the intent. At the moment mc is translating them.
+ split($2, id, "/");
+ printf "%s 1 %-8d %-8d %-8d %s %s %s %s\n",
+ $1, id[1], id[2], $3, $5, $6, $7, $8;
+ # Get the next line of the list
+ next;
+ }
+
+ '
+}
+
+# The 'copyout' command executive to copy displayed files to a destination
+
+mc_lha_fs_copyout()
+{
+ $LHA_GET "$1" "$2" > "$3"
+}
+
+# The 'copyin' command executive to add something to the archive
+
+mc_lha_fs_copyin ()
+{
+ NAME2=`basename "$2"`; DIR2=${2%$NAME2}
+ NAME3=`basename "$3"`; DIR3=${3%$NAME3}
+
+ cd "${DIR3}"
+
+ ONE2=${2%%/*}
+ [ -n "${ONE2}" ] || exit 1
+ [ -e "${ONE2}" ] && exit 1
+
+ [ -e "${DIR2}" ] || mkdir -p "${DIR2}"
+ ln "$3" "$2" || exit 1
+
+ $LHA_PUT "$1" "$2"
+ rm -r "${ONE2}"
+}
+
+# The 'run' command executive to run a command from within an archive
+
+mc_lha_fs_run()
+{
+ TMPDIR=`mktemp -d "${MC_TMPDIR:-/tmp}/mctmpdir-ulha.XXXXXX"` || exit 1
+ trap "rm -rf \"$TMPDIR\"; exit 0" 1 2 3 4 15
+ TMPCMD=$TMPDIR/run
+ $LHA_GET "$1" "$2" > $TMPCMD
+ chmod a+x "$TMPCMD"
+ "$TMPCMD"
+ rm -rf "$TMPDIR"
+}
+
+
+# The main routine
+umask 077
+
+cmd="$1"
+shift
+
+case "$cmd" in
+ list) mc_lha_fs_list "$@" ;;
+ copyout) mc_lha_fs_copyout "$@" ;;
+ copyin) mc_lha_fs_copyin "$@" ;;
+ run) mc_lha_fs_run "$@" ;;
+ *) exit 1 ;;
+esac
+
+exit 0
diff --git a/src/vfs/extfs/helpers/ulib.in b/src/vfs/extfs/helpers/ulib.in
new file mode 100644
index 0000000..82c7ccf
--- /dev/null
+++ b/src/vfs/extfs/helpers/ulib.in
@@ -0,0 +1,146 @@
+#! @PERL@
+#
+# VFS to manage the gputils archives.
+# Written by Molnár Károly (proton7@freemail.hu) 2012
+#
+
+use warnings;
+
+my %month = ('jan' => '01', 'feb' => '02', 'mar' => '03',
+ 'apr' => '04', 'may' => '05', 'jun' => '06',
+ 'jul' => '07', 'aug' => '08', 'sep' => '09',
+ 'oct' => '10', 'nov' => '11', 'dec' => '12');
+
+my @PATHS = ('/usr/bin/gplib', '/usr/local/bin/gplib');
+
+my $gplib = '';
+
+foreach my $i (@PATHS)
+{
+ if (-x $i)
+ {
+ $gplib = $i;
+ last;
+ }
+}
+
+if ($gplib eq '')
+{
+ print STDERR "\a\t$0 : Gplib not found!\n";
+ exit(1);
+}
+
+my $cmd = shift;
+my $archive = shift;
+
+#-------------------------------------------------------------------------------
+
+sub mc_ulib_fs_list
+{
+ open(PIPE, "$gplib -tq $archive |") || die("Error in $gplib -tq");
+
+ my($dev, $inode, $mode, $nlink, $uid, $gid) = stat($archive);
+
+ while (<PIPE>)
+ {
+ chomp;
+ my @w = split(/\s+/o);
+ my $fname = $w[0];
+
+ $fname =~ s|\\|/|g;
+
+ printf("-rw-r--r-- 1 %s %s %d %s-%02u-%s %s %s\n",
+ $uid, $gid, int($w[1]), $month{lc($w[4])}, $w[5], $w[7], substr($w[6], 0, 5), $fname);
+ }
+ close (PIPE);
+}
+
+#-------------------------------------------------------------------------------
+
+sub mc_ulib_fs_copyin
+{
+ system("$gplib -r $archive $_[0]");
+ my $ret = $?;
+
+ if ($ret)
+ {
+ die("Error in: $gplib -r");
+ }
+}
+
+#-------------------------------------------------------------------------------
+
+sub mc_ulib_fs_copyout
+{
+ my($module, $fname) = @_;
+ my $tmpdir = $ENV{'TMPDIR'};
+
+ $tmpdir = '/tmp' if (! defined $tmpdir or $tmpdir eq '');
+
+ open(PIPE, "$gplib -tq $archive |") || die("Error in: $gplib -tq");
+
+ while (<PIPE>)
+ {
+ chomp;
+ my @w = split(/\s+/o);
+ my $module_orig = $w[0];
+ my $count = () = ($module_orig =~ /(\\)/g);
+ my $md = $module_orig;
+
+ $md =~ s|\\|/|g;
+
+ if ($module eq $md)
+ {
+ return if ($count);
+ }
+ }
+
+ close (PIPE);
+
+ chdir($tmpdir);
+ system("$gplib -x $archive $module");
+ my $ret = $?;
+
+ if ($ret)
+ {
+ die("Error in: $gplib -x");
+ }
+
+ rename($module, $fname) || die("Error in: rename($module, $fname)");
+}
+
+#-------------------------------------------------------------------------------
+
+sub mc_ulib_fs_rm
+{
+ system("$gplib -d $archive $_[0]");
+ my $ret = $?;
+
+ if ($ret)
+ {
+ die("Error in: $gplib -d");
+ }
+}
+
+################################################################################
+
+if ($cmd eq 'list')
+{
+ mc_ulib_fs_list(@ARGV);
+}
+elsif ($cmd eq 'copyin')
+{
+ mc_ulib_fs_copyin(@ARGV);
+}
+elsif ($cmd eq 'copyout')
+{
+ mc_ulib_fs_copyout(@ARGV);
+}
+elsif ($cmd eq 'rm')
+{
+ mc_ulib_fs_rm(@ARGV);
+}
+else
+{
+ exit(1);
+}
diff --git a/src/vfs/extfs/helpers/unar.in b/src/vfs/extfs/helpers/unar.in
new file mode 100644
index 0000000..e810307
--- /dev/null
+++ b/src/vfs/extfs/helpers/unar.in
@@ -0,0 +1,59 @@
+#! /bin/sh
+
+# Written by Ilia Maslakov <il.smind@gmail.com>
+#
+# (C) 2020 The Free Software Foundation.
+
+# Define awk
+AWK=@AWK@
+
+# Define which archiver you are using with appropriate options
+UNAR_LIST="lsar "
+UNAR_GET="unar "
+
+# The 'list' command executive
+mc_unar_fs_list()
+{
+ # List the contents of the archive and sort it out
+ $UNAR_LIST -l "$1" | $AWK -v uid=`id -nu` -v gid=`id -ng` '
+ BEGIN { flag = 0 }
+ /^\(Flags/ {next}
+ /^\(Mode/ {next}
+ {
+ flag++;
+ if (flag < 4)
+ next
+ pr="-r--r--r--"
+ if (index($2, "D") != 0)
+ pr="dr-xr-xr-x"
+ split($6, a, "-")
+ split($7, b, ":")
+ printf "%s 1 %s %s %d %02d/%02d/%02d %02d:%02d %s\n", pr, uid, gid, $3, a[3], a[2], a[1], b[1], b[2], $8
+ }'
+}
+
+# The 'copyout' command executive to copy displayed files to a destination
+mc_unar_fs_copyout ()
+{
+ TMPDIR=`mktemp -d "${MC_TMPDIR:-/tmp}/mctmpdir-uha.XXXXXX"` || exit 1
+
+ $UNAR_GET "$1" "$2" -o "$TMPDIR" >/dev/null
+ we=`basename "$1" | sed -E 's|^(.*?)\.\w+$|\1|'`
+ cat "$TMPDIR/$we/$2" > "$3"
+ cd /
+ rm -rf "$TMPDIR"
+}
+
+# The main routine
+umask 077
+
+cmd="$1"
+shift
+
+case "$cmd" in
+ list) mc_unar_fs_list "$@" ;;
+ copyout) mc_unar_fs_copyout "$@" ;;
+ *) exit 1 ;;
+esac
+
+exit 0
diff --git a/src/vfs/extfs/helpers/urar.in b/src/vfs/extfs/helpers/urar.in
new file mode 100644
index 0000000..684234c
--- /dev/null
+++ b/src/vfs/extfs/helpers/urar.in
@@ -0,0 +1,180 @@
+#! /bin/sh
+#
+# Written by andrey joukov
+# (C) 1996 2:5020/337.13@fidonet.org
+# Updated by christian.gennerat@alcatel.fr 1999
+# Andrew V. Samoilov <sav@bcs.zp.ua> 2000
+#
+# Andrew Borodin <aborodin@vmail.ru>
+# David Haller <dnh@opensuse.org>
+# 2013: support unrar5
+#
+# beta version 2.0
+#
+# rar and unrar can be found on http://www.rarlabs.com/
+
+RAR=rar
+
+# Prefer unrar (freeware).
+UNRAR=`which unrar 2>/dev/null`
+
+[ -z $UNRAR ] && UNRAR=$RAR
+[ ! -x $UNRAR -a -x $RAR ] && UNRAR=$RAR
+
+# Let the test framework hook in:
+UNRAR=${MC_TEST_EXTFS_LIST_CMD:-$UNRAR}
+
+# Determine the $UNRAR version
+if [ -n "$MC_TEST_EXTFS_UNRAR_VERSION" ]; then
+ # Let the test framework fool us:
+ UNRAR_VERSION=$MC_TEST_EXTFS_UNRAR_VERSION
+else
+ # Figure it out from rar itself:
+ UNRAR_VERSION=`$UNRAR -cfg- -? | grep "Copyright" | sed -e 's/.*\([0-9]\)\..*/\1/'`
+fi
+
+mcrar4fs_list ()
+{
+ $UNRAR v -c- -cfg- "$1" | @AWK@ -v uid=`id -u` -v gid=`id -g` '
+BEGIN { flag=0 }
+/^-------/ { flag++; if (flag > 1) exit 0; next }
+flag==1 {
+ str = substr($0, 2)
+ getline
+ split($4, a, "-")
+ if (index($6, "D") != 0)
+ $6="drwxr-xr-x"
+ else
+ if (index($6, ".") != 0)
+ $6="-rw-r--r--"
+ printf "%s 1 %s %s %d %02d/%02d/%02d %s ./%s\n", $6, uid, gid, $1, a[2], a[1], a[3], $5, str
+}'
+}
+
+mcrar5fs_list ()
+{
+ $UNRAR vt -c- -cfg- "$1" | @AWK@ -F ':' -v uid=`id -u` -v gid=`id -g` '
+ {
+ ### remove space after the ":" of the field name
+ sub ("^ ", "", $2);
+ }
+
+ $1 ~ /^ *Name$/ {
+ ### next file
+ name = mtime = size = attrs = "";
+ delete date;
+ name = $2;
+ ### if the name contains ":", append the rest of the fields
+ if (NF > 2) {
+ for (i = 3; i <= NF; i++) {
+ name = name ":" $i;
+ }
+ }
+ }
+ $1 ~ /^ *mtime$/ {
+ mtime = $2 ":" $3;
+ }
+ $1 ~ /^ *Size$/ {
+ size = $2;
+ }
+ $1 ~ /^ *Attributes$/ {
+ attrs = $2;
+ }
+
+ $1 ~ /^ *Compression$/ {
+ ### file done, using /^$/ is not so good you
+ ### would have to skip the version stuff first
+
+ ### get date and time
+ split (mtime, date, " ");
+ time = date[2];
+ ### cut off seconds from the time
+ sub (",[0-9]*$", "", time);
+ ### split for reordering of the date in the printf below
+ split (date[1], date, "-");
+ ### mc seems to be able to parse 4 digit years too, so remove if tested
+ # sub ("^..", "", date[1]); ### cut year to 2 digits only
+
+ ### check/adjust rights
+ if (index (attrs, "D") != 0) {
+ attrs = "drwxr-xr-x";
+ } else {
+ if (index (attrs, ".") != 0) {
+ attrs = "-rw-r--r--";
+ }
+ }
+
+ ### and finally
+ printf ("%s 1 %s %s %d %02d/%02d/%02d %s ./%s\n",
+ attrs, uid, gid, size, date[2], date[3], date[1], time, name);
+ }
+'
+}
+
+mcrarfs_list ()
+{
+ [ x$UNRAR_VERSION = x6 -o x$UNRAR_VERSION = x5 ] && mcrar5fs_list "$@" || mcrar4fs_list "$@"
+}
+
+mcrarfs_copyin ()
+{
+# copyin by christian.gennerat@alcatel.fr
+# preserve pwd. It is clean, but is it necessary?
+ pwd=`pwd`
+# Create a directory and copy in it the tmp file with the good name
+ mkdir "$3.dir"
+ cd "$3.dir"
+ di="${2%/*}"
+# if file is to be written upper in the archive tree, make fake dir
+ if test x"$di" != x"${2##*/}" ; then
+ mkdir -p "$di"
+ fi
+ cp -fp "$3" "$3.dir/$2"
+ $RAR a "$1" "$2" >/dev/null
+ cd "$pwd"
+ rm -rf "$3.dir"
+}
+
+mcrarfs_copyout ()
+{
+ $UNRAR p -p- -c- -cfg- -inul "$1" "$2" > "$3"
+}
+
+mcrarfs_mkdir ()
+{
+# preserve pwd. It is clean, but is it necessary?
+ pwd=`pwd`
+# Create a directory and create in it a tmp directory with the good name
+ dir=`mktemp -d "${MC_TMPDIR:-/tmp}/mctmpdir-urar.XXXXXX"` || exit 1
+ cd "$dir"
+ mkdir -p "$2"
+# rar cannot create an empty directory
+ touch "$2"/.rarfs
+ $RAR a -r "$1" "$2" >/dev/null
+ $RAR d "$1" "$2/.rarfs" >/dev/null
+ cd "$pwd"
+ rm -rf "$dir"
+}
+
+mcrarfs_rm ()
+{
+ $RAR d "$1" "$2" >/dev/null
+}
+
+umask 077
+
+cmd="$1"
+shift
+
+case "$cmd" in
+ # Workaround for a bug in mc - directories must precede files to
+ # avoid duplicate entries, so we sort output by filenames
+ list) mcrarfs_list "$@" | sort -k 8 ;;
+ rm) mcrarfs_rm "$@" ;;
+ rmdir) mcrarfs_rm "$@" ;;
+ mkdir) mcrarfs_mkdir "$@" ;;
+ copyin) mcrarfs_copyin "$@" ;;
+ copyout) mcrarfs_copyout "$@" ;;
+ *) exit 1 ;;
+esac
+exit 0
diff --git a/src/vfs/extfs/helpers/uwim.in b/src/vfs/extfs/helpers/uwim.in
new file mode 100644
index 0000000..76197f7
--- /dev/null
+++ b/src/vfs/extfs/helpers/uwim.in
@@ -0,0 +1,208 @@
+#! /bin/sh
+# Midnight Commander - WIM support
+#
+# Written by:
+# Vadim Kalinnikov <moose@ylsoftware.com>,
+#
+# This file is part of the Midnight Commander.
+#
+# It required wimtools: https://wimlib.net/
+# On Debian/Ubuntu wimtools can be installed via:
+# apt install wimtools
+
+which wimlib-imagex 2>/dev/null > /dev/null || exit 1
+WIM=`which wimlib-imagex`
+AWK=@AWK@
+
+[ -n "$2" ] || exit 1
+
+ACTION=$1
+WIMFILENAME=$2
+
+mcwim_list() {
+ # Here we can use "Image count" from output,
+ # but on some broken images we can get garbage, instead of number
+ IMAGECOUNT=`${WIM} info ${WIMFILENAME} | grep Index: | grep -v Boot | wc -l`
+ IMGNUM=1
+ VUID=`id -nu`
+ VGID=`id -ng`
+ while [ ${IMGNUM} -le ${IMAGECOUNT} ]; do
+ ${WIM} dir ${WIMFILENAME} ${IMGNUM} --detailed | \
+ ${AWK} -v uid=${VUID} -v gid=${VGID} -v imgnum=${IMGNUM} '
+ /^----------------------------------------------------------------------------/,/^$/ {
+ if (match($0, /^Full Path/)) {
+ split($0, namesrc, "\"");
+ name=namesrc[2];
+ }
+ if (match($0, /FILE_ATTRIBUTE_DIRECTORY is set/)) {
+ attr="drwxr-xr-x"
+ }
+ if (match($0, /^Uncompressed size/)) {
+ size=$4;
+ }
+ if (match($0, /^Last Write Time/)) {
+ months["Jan"] = "01";
+ months["Feb"] = "02";
+ months["Mar"] = "03";
+ months["Apr"] = "04";
+ months["May"] = "05";
+ months["Jun"] = "06";
+ months["Jul"] = "07";
+ months["Aug"] = "08";
+ months["Sep"] = "09";
+ months["Oct"] = "10";
+ months["Nov"] = "11";
+ months["Dec"] = "12";
+ split($0, mtimesrc, " ");
+ mtime=sprintf("%s/%s/%s %s", months[mtimesrc[6]], mtimesrc[7], mtimesrc[9], mtimesrc[8]);
+ }
+
+ if (match($0, /^$/)) {
+ printf("%s 1 %s %s % 20s % 24s IMAGE%s%s\n",
+ attr, uid, gid, size, mtime, imgnum, name);
+ name = size = mtime = "";
+ attr="-rw-r--r--";
+ }
+ }
+ '
+
+ IMGNUM=$((IMGNUM+1))
+ done
+
+ # Virtual files
+ echo "-r-xr-xr-x 1 ${VUID} ${VGID} 1 01/01/2020 00:00:00 OPTIMIZE"
+ echo "-r-xr-xr-x 1 ${VUID} ${VGID} 1 01/01/2020 00:00:00 VERIFY"
+}
+
+mcwim_copyout() {
+ # Virtual files
+ if [ "${FILENAMESRC}" = "OPTIMIZE" ]; then
+ echo "#/bin/sh" > ${FILENAMEDST}
+ echo "# Run this to optimize archive" >> ${FILENAMEDST}
+ echo "${WIM} optimize \"${WIMFILENAME}\"" >> ${FILENAMEDST}
+ chmod a+x ${FILENAMEDST}
+ exit 0;
+ elif [ "${FILENAMESRC}" = "VERIFY" ]; then
+ echo "#/bin/sh" > ${FILENAMEDST}
+ echo "# Run this to verify archive" >> ${FILENAMEDST}
+ echo "${WIM} verify \"${WIMFILENAME}\"" >> ${FILENAMEDST}
+ chmod a+x ${FILENAMEDST}
+ exit 0;
+ fi
+
+ # Filename must contain imgnum
+ echo ${FILENAMESRC} | grep -E '^IMAGE[0-9]+/' || exit 1
+
+ IMGNUM=`echo ${FILENAMESRC} | cut -d '/' -f1 | sed "s/IMAGE//"`
+ REALFILENAME=`echo ${FILENAMESRC} | sed "s/IMAGE${IMGNUM}//"`
+ ${WIM} extract ${WIMFILENAME} ${IMGNUM} ${REALFILENAME} --to-stdout > ${FILENAMEDST}
+}
+
+mcwim_copyin() {
+ # Skip virtual files
+ [ "${FILENAMEDST}" = "OPTIMIZE" ] && exit 1;
+ [ "${FILENAMEDST}" = "VERIFY" ] && exit 1;
+
+ # Filename must contain imgnum
+ echo ${FILENAMEDST} | grep -E '^IMAGE[0-9]+/' || exit 1
+
+ IMGNUM=`echo ${FILENAMEDST} | cut -d '/' -f1 | sed "s/IMAGE//"`
+ REALFILENAME=`echo ${FILENAMEDST} | sed "s/IMAGE${IMGNUM}//"`
+ echo "add \"${FILENAMESRC}\" \"${REALFILENAME}\"" | ${WIM} update ${WIMFILENAME} ${IMGNUM} > /dev/null
+}
+
+
+mcwim_rm() {
+ # Skip virtual files
+ [ "${FILENAMESRC}" = "OPTIMIZE" ] && exit 0;
+ [ "${FILENAMESRC}" = "VERIFY" ] && exit 0;
+
+ # Filename must contain imgnum
+ echo ${FILENAMESRC} | grep -E '^IMAGE[0-9]+/' || exit 1
+
+ IMGNUM=`echo ${FILENAMESRC} | cut -d '/' -f1 | sed "s/IMAGE//"`
+ REALFILENAME=`echo ${FILENAMESRC} | sed "s/IMAGE${IMGNUM}//"`
+
+ if [ -z "${REALFILENAME}" ]; then
+ # If user want to remove image
+ ${WIM} delete ${WIMFILENAME} ${IMGNUM}
+ else
+ # remove regular file or directory
+ echo "delete \"${REALFILENAME}\"" | ${WIM} update ${WIMFILENAME} ${IMGNUM} --force --recursive > /dev/null
+ fi
+}
+
+mcwim_run() {
+ case ${RUNFILENAME} in
+ OPTIMIZE)
+ ${WIM} optimize ${WIMFILENAME}
+ exit 0;
+ ;;
+ VERIFY)
+ ${WIM} verify ${WIMFILENAME}
+ exit 0;
+ ;;
+ esac
+ exit 1;
+}
+
+
+mcwim_mkdir() {
+ # New dirname must contain imgnum
+ echo ${NEWDIRNAME} | grep -E '^IMAGE[0-9]+/' || exit 1
+ IMGNUM=`echo ${NEWDIRNAME} | cut -d '/' -f1 | sed "s/IMAGE//"`
+ REALDIRNAME=`echo ${NEWDIRNAME} | sed "s/IMAGE${IMGNUM}//"`
+ [ -z "${REALDIRNAME}" ] && exit 1
+
+ TMPDIR=`mktemp -d`
+ DSTBASENAME=`basename ${REALDIRNAME}`
+ SRCDIRNAME="${TMPDIR}/${DSTBASENAME}"
+ mkdir -p ${SRCDIRNAME}
+ echo "add \"${SRCDIRNAME}\" \"${REALDIRNAME}\"" | ${WIM} update ${WIMFILENAME} ${IMGNUM} > /dev/null
+ rm -rf ${TMPDIR}
+}
+
+#echo "'$1' '$2' '$3' '$4' '$5'" >> /tmp/mcdebug
+
+case "$ACTION" in
+ list)
+ mcwim_list
+ ;;
+
+ copyout)
+ [ -n "$4" ] || exit 1
+ FILENAMESRC="$3"
+ FILENAMEDST="$4"
+ mcwim_copyout
+ ;;
+
+ copyin)
+ [ -n "$4" ] || exit 1
+ FILENAMEDST="$3"
+ FILENAMESRC="$4"
+ mcwim_copyin
+ ;;
+
+ rm|rmdir)
+ [ -n "$3" ] || exit 1
+ FILENAMESRC="$3"
+ mcwim_rm
+ ;;
+
+ run)
+ [ -n "$3" ] || exit 1
+ RUNFILENAME="$3"
+ mcwim_run
+ ;;
+
+ mkdir)
+ [ -n "$3" ] || exit 1
+ NEWDIRNAME="$3"
+ mcwim_mkdir
+ ;;
+
+
+ *)
+ exit 1
+ ;;
+esac
diff --git a/src/vfs/extfs/helpers/uzip.in b/src/vfs/extfs/helpers/uzip.in
new file mode 100644
index 0000000..ceffb53
--- /dev/null
+++ b/src/vfs/extfs/helpers/uzip.in
@@ -0,0 +1,483 @@
+#! @PERL@
+#
+# zip file archive Virtual File System for Midnight Commander
+# Version 1.4.0 (2001-08-07).
+#
+# (C) 2000-2001 Oskar Liljeblad <osk@hem.passagen.se>.
+#
+
+use POSIX;
+use File::Basename;
+use strict;
+use warnings;
+
+#
+# Configuration options
+#
+
+# Location of the zip program
+my $app_zip = "@ZIP@";
+# Location of the unzip program
+my $app_unzip = $ENV{MC_TEST_EXTFS_LIST_CMD} || "@UNZIP@";
+# Set this to 1 if zipinfo (unzip -Z) is to be used (recommended), otherwise 0.
+my $op_has_zipinfo = exists($ENV{MC_TEST_EXTFS_HAVE_ZIPINFO}) ? $ENV{MC_TEST_EXTFS_HAVE_ZIPINFO} : @HAVE_ZIPINFO@;
+
+# Command used to list archives (zipinfo mode)
+my $cmd_list_zi = "$app_unzip -Z -l -T";
+# Command used to list archives (non-zipinfo mode)
+my $cmd_list_nzi = "$app_unzip -qq -v";
+# Command used to add a file to the archive
+my $cmd_add = "$app_zip -g";
+# Command used to add a link file to the archive (unused)
+my $cmd_addlink = "$app_zip -g -y";
+# Command used to delete a file from the archive
+my $cmd_delete = "$app_zip -d";
+# Command used to extract a file to standard out
+my $cmd_extract = "$app_unzip -p";
+
+# -rw-r--r-- 2.2 unx 2891 tx 1435 defN 20000330.211927 ./edit.html
+# (perm) (?) (?) (size) (?) (zippedsize) (method) (yyyy)(mm)(dd).(HH)(MM)(SS) (fname)
+my $regex_zipinfo_line = qr"^(\S{7,10})\s+(\d+\.\d+)\s+(\S+)\s+(\d+)\s+(\S\S)\s+(\d+)\s+(\S{4})\s+(\d{4})(\d\d)(\d\d)\.(\d\d)(\d\d)(\d\d)\s(.*)$";
+
+# 2891 Defl:N 1435 50% 03-30-00 21:19 50cbaaf8 ./edit.html
+# (size) (method) (zippedsize) (zipratio) (mm)-(dd)-(yy|yyyy) (HH):(MM) (cksum) (fname)
+# or: (yyyy)-(mm)-(dd)
+my $regex_nonzipinfo_line = qr"^\s*(\d+)\s+(\S+)\s+(\d+)\s+(-?\d+\%)\s+(\d+)-(\d?\d)-(\d+)\s+(\d?\d):(\d\d)\s+([0-9a-f]+)\s\s(.*)$";
+
+#
+# Main code
+#
+
+die "uzip: missing command and/or archive arguments\n" if ($#ARGV < 1);
+
+# Initialization of some global variables
+my $cmd = shift;
+my %known = ( './' => 1 );
+my %pending = ();
+my $oldpwd = POSIX::getcwd();
+my $archive = shift;
+my $aarchive = absolutize($archive, $oldpwd);
+my $cmd_list = ($op_has_zipinfo ? $cmd_list_zi : $cmd_list_nzi);
+my ($qarchive, $aqarchive) = map (quotemeta, $archive, $aarchive);
+
+# Strip all "." and ".." path components from a pathname.
+sub zipfs_canonicalize_pathname($) {
+ my ($fname) = @_;
+ $fname =~ s,/+,/,g;
+ $fname =~ s,(^|/)(?:\.?\./)+,$1,;
+ return $fname;
+}
+
+# The Midnight Commander never calls this script with archive pathnames
+# starting with either "./" or "../". Some ZIP files contain such names,
+# so we need to build a translation table for them.
+my $zipfs_realpathname_table = undef;
+sub zipfs_realpathname($) {
+ my ($fname) = @_;
+
+ if (!defined($zipfs_realpathname_table)) {
+ $zipfs_realpathname_table = {};
+ if (!open(ZIP, "$cmd_list $qarchive |")) {
+ return $fname;
+ }
+ foreach my $line (<ZIP>) {
+ $line =~ s/\r*\n*$//;
+ if ($op_has_zipinfo) {
+ if ($line =~ $regex_zipinfo_line) {
+ my ($fname) = ($14);
+ $zipfs_realpathname_table->{zipfs_canonicalize_pathname($fname)} = $fname;
+ }
+ } else {
+ if ($line =~ $regex_nonzipinfo_line) {
+ my ($fname) = ($11);
+ $zipfs_realpathname_table->{zipfs_canonicalize_pathname($fname)} = $fname;
+ }
+ }
+ }
+ if (!close(ZIP)) {
+ return $fname;
+ }
+ }
+ if (exists($zipfs_realpathname_table->{$fname})) {
+ return $zipfs_realpathname_table->{$fname};
+ }
+ return $fname;
+}
+
+if ($cmd eq 'list') { &mczipfs_list(@ARGV); }
+if ($cmd eq 'rm') { &mczipfs_rm(@ARGV); }
+if ($cmd eq 'rmdir') { &mczipfs_rmdir(@ARGV); }
+if ($cmd eq 'mkdir') { &mczipfs_mkdir(@ARGV); }
+if ($cmd eq 'copyin') { &mczipfs_copyin(@ARGV); }
+if ($cmd eq 'copyout') { &mczipfs_copyout(@ARGV); }
+if ($cmd eq 'run') { &mczipfs_run(@ARGV); }
+#if ($cmd eq 'mklink') { &mczipfs_mklink(@ARGV); } # Not supported by MC extfs
+#if ($cmd eq 'linkout') { &mczipfs_linkout(@ARGV); } # Not supported by MC extfs
+exit 1;
+
+# Remove a file from the archive.
+sub mczipfs_rm {
+ my ($qfile) = map { &zipquotemeta(zipfs_realpathname($_)) } @_;
+
+ # "./" at the beginning of pathnames is stripped by Info-ZIP,
+ # so convert it to "[.]/" to prevent stripping.
+ $qfile =~ s/^\\\./[.]/;
+
+ &checkargs(1, 'archive file', @_);
+ &safesystem("$cmd_delete $qarchive $qfile >/dev/null");
+ exit;
+}
+
+# Remove an empty directory from the archive.
+# The only difference from mczipfs_rm is that we append an
+# additional slash to the directory name to remove. I am not
+# sure this is absolutely necessary, but it doesn't hurt.
+sub mczipfs_rmdir {
+ my ($qfile) = map { &zipquotemeta(zipfs_realpathname($_)) } @_;
+ &checkargs(1, 'archive directory', @_);
+ &safesystem("$cmd_delete $qarchive $qfile/ >/dev/null", 12);
+ exit;
+}
+
+# Extract a file from the archive.
+# Note that we don't need to check if the file is a link,
+# because mc apparently doesn't call copyout for symbolic links.
+sub mczipfs_copyout {
+ my ($qafile, $qfsfile) = map { &zipquotemeta(zipfs_realpathname($_)) } @_;
+ &checkargs(1, 'archive file', @_);
+ &checkargs(2, 'local file', @_);
+ &safesystem("$cmd_extract $qarchive $qafile > $qfsfile", 11);
+ exit;
+}
+
+# Add a file to the archive.
+# This is done by making a temporary directory, in which
+# we create a symlink the original file (with a new name).
+# Zip is then run to include the real file in the archive,
+# with the name of the symbolic link.
+# Here we also doesn't need to check for symbolic links,
+# because the mc extfs doesn't allow adding of symbolic
+# links.
+sub mczipfs_copyin {
+ my ($afile, $fsfile) = @_;
+ &checkargs(1, 'archive file', @_);
+ &checkargs(2, 'local file', @_);
+ my ($qafile) = quotemeta $afile;
+ $fsfile = &absolutize($fsfile, $oldpwd);
+ my $adir = File::Basename::dirname($afile);
+
+ my $tmpdir = &mktmpdir();
+ chdir $tmpdir || &croak("chdir $tmpdir failed");
+ &mkdirs($adir, 0700);
+ symlink ($fsfile, $afile) || &croak("link $afile failed");
+ &safesystem("$cmd_add $aqarchive $qafile >/dev/null");
+ unlink $afile || &croak("unlink $afile failed");
+ &rmdirs($adir);
+ chdir $oldpwd || &croak("chdir $oldpwd failed");
+ rmdir $tmpdir || &croak("rmdir $tmpdir failed");
+ exit;
+}
+
+# Add an empty directory the the archive.
+# This is similar to mczipfs_copyin, except that we don't need
+# to use symlinks.
+sub mczipfs_mkdir {
+ my ($dir) = @_;
+ &checkargs(1, 'directory', @_);
+ my ($qdir) = quotemeta $dir;
+
+ my $tmpdir = &mktmpdir();
+ chdir $tmpdir || &croak("chdir $tmpdir failed");
+ &mkdirs($dir, 0700);
+ &safesystem("$cmd_add $aqarchive $qdir >/dev/null");
+ &rmdirs($dir);
+ chdir $oldpwd || &croak("chdir $oldpwd failed");
+ rmdir $tmpdir || &croak("rmdir $tmpdir failed");
+ exit;
+}
+
+# Add a link to the archive. This operation is not used yet,
+# because it is not supported by the MC extfs.
+sub mczipfs_mklink {
+ my ($linkdest, $afile) = @_;
+ &checkargs(1, 'link destination', @_);
+ &checkargs(2, 'archive file', @_);
+ my ($qafile) = quotemeta $afile;
+ my $adir = File::Basename::dirname($afile);
+
+ my $tmpdir = &mktmpdir();
+ chdir $tmpdir || &croak("chdir $tmpdir failed");
+ &mkdirs($adir, 0700);
+ symlink ($linkdest, $afile) || &croak("link $afile failed");
+ &safesystem("$cmd_addlink $aqarchive $qafile >/dev/null");
+ unlink $afile || &croak("unlink $afile failed");
+ &rmdirs($adir);
+ chdir $oldpwd || &croak("chdir $oldpwd failed");
+ rmdir $tmpdir || &croak("rmdir $tmpdir failed");
+ exit;
+}
+
+# This operation is not used yet, because it is not
+# supported by the MC extfs.
+sub mczipfs_linkout {
+ my ($afile, $fsfile) = @_;
+ &checkargs(1, 'archive file', @_);
+ &checkargs(2, 'local file', @_);
+ my ($qafile) = map { &zipquotemeta($_) } $afile;
+
+ my $linkdest = &get_link_destination($afile);
+ symlink ($linkdest, $fsfile) || &croak("link $fsfile failed");
+ exit;
+}
+
+# Use unzip to find the link destination of a certain file in the
+# archive.
+sub get_link_destination {
+ my ($afile) = @_;
+ my ($qafile) = map { &zipquotemeta($_) } $afile;
+ my $linkdest = safeticks("$cmd_extract $qarchive $qafile");
+ &croak ("extract failed", "link destination of $afile not found")
+ if (!defined $linkdest || $linkdest eq '');
+ return $linkdest;
+}
+
+# List files in the archive.
+# Because mc currently doesn't allow a file's parent directory
+# to be listed after the file itself, we need to do some
+# rearranging of the output. Most of this is done in
+# checked_print_file.
+sub mczipfs_list {
+ open (PIPE, "$cmd_list $qarchive |") || &croak("$app_unzip failed");
+ if ($op_has_zipinfo) {
+ while (<PIPE>) {
+ chomp;
+ next if /^Archive:/;
+ next if /^\d+ file/;
+ next if /^Empty zipfile\.$/;
+ my @match = /$regex_zipinfo_line/;
+ next if ($#match != 13);
+ &checked_print_file(@match);
+ }
+ } else {
+ while (<PIPE>) {
+ chomp;
+ my @match = /$regex_nonzipinfo_line/;
+ next if ($#match != 10);
+
+ # Massage the date.
+ my ($year, $month, $day) = $match[4] > 12
+ ? ($match[4], $match[5], $match[6]) # 4,5,6 = Y,M,D
+ : ($match[6], $match[4], $match[5]); # 4,5,6 = M,D,Y
+ $year += ($year < 70 ? 2000 : 1900) if $year < 100; # Fix 2-digit year.
+
+ my @rmatch = ('', '', 'unknown', $match[0], '', $match[2], $match[1],
+ $year, $month, $day, $match[7], $match[8], "00", $match[10]);
+ &checked_print_file(@rmatch);
+ }
+ }
+ if (!close (PIPE)) {
+ &croak("$app_unzip failed") if ($! != 0);
+ &croak("$app_unzip failed", 'non-zero exit status ('.($? >> 8).')')
+ }
+
+ foreach my $key (sort keys %pending) {
+ foreach my $file (@{ $pending{$key} }) {
+ &print_file(@{ $file });
+ }
+ }
+
+ exit;
+}
+
+# Execute a file in the archive, by first extracting it to a
+# temporary directory. The name of the extracted file will be
+# the same as the name of it in the archive.
+sub mczipfs_run {
+ my ($afile) = @_;
+ &checkargs(1, 'archive file', @_);
+ my $qafile = &zipquotemeta(zipfs_realpathname($afile));
+ my $tmpdir = &mktmpdir();
+ my $tmpfile = File::Basename::basename($afile);
+
+ chdir $tmpdir || &croak("chdir $tmpdir failed");
+ &safesystem("$cmd_extract $aqarchive $qafile > $tmpfile");
+ chmod 0700, $tmpfile;
+ &safesystem("./$tmpfile");
+ unlink $tmpfile || &croak("rm $tmpfile failed");
+ chdir $oldpwd || &croak("chdir $oldpwd failed");
+ rmdir $tmpdir || &croak("rmdir $tmpdir failed");
+ exit;
+}
+
+# This is called prior to printing the listing of a file.
+# A check is done to see if the parent directory of the file has already
+# been printed or not. If it hasn't, we must cache it (in %pending) and
+# print it later once the parent directory has been listed. When all
+# files have been processed, there may still be some that haven't been
+# printed because their parent directories weren't listed at all. These
+# files are dealt with in mczipfs_list.
+sub checked_print_file {
+ my @waiting = ([ @_ ]);
+
+ while ($#waiting != -1) {
+ my $item = shift @waiting;
+ my $filename = ${$item}[13];
+ my $dirname = File::Basename::dirname($filename) . '/';
+
+ if (exists $known{$dirname}) {
+ &print_file(@{$item});
+ if ($filename =~ /\/$/) {
+ $known{$filename} = 1;
+ if (exists $pending{$filename}) {
+ push @waiting, @{ $pending{$filename} };
+ delete $pending{$filename};
+ }
+ }
+ } else {
+ push @{$pending{$dirname}}, $item;
+ }
+ }
+}
+
+# Print the mc extfs listing of a file from a set of parsed fields.
+# If the file is a link, we extract it from the zip archive and
+# include the output as the link destination. Because this output
+# is not newline terminated, we must execute unzip once for each
+# link file encountered.
+sub print_file {
+ my ($perms,$zipver,$platform,$realsize,$format,$cmpsize,$comp,$year,$mon,$day,$hours,$mins,$secs,$filename) = @_;
+ if ($platform ne 'unx') {
+ $perms = ($filename =~ /\/$/ ? 'drwxr-xr-x' : '-rw-r--r--');
+ }
+ # adjust abnormal perms on directory
+ if ($platform eq 'unx' && $filename =~ /\/$/ && $perms =~ /^\?(.*)$/) {
+ $perms = 'd'.$1;
+ }
+ printf "%-10s 1 %-8d %-8d %8s %s/%s/%s %s:%s:%s ./%s", $perms, $<,
+ $(, $realsize, $mon, $day, $year, $hours, $mins, $secs, $filename;
+ if ($platform eq 'unx' && $perms =~ /^l/) {
+ my $linkdest = &get_link_destination($filename);
+ print " -> $linkdest";
+ }
+ print "\n";
+}
+
+# Die with a reasonable error message.
+sub croak {
+ my ($command, $desc) = @_;
+ die "uzip ($cmd): $command - $desc\n" if (defined $desc);
+ die "uzip ($cmd): $command - $!\n";
+}
+
+# Make a set of directories, like the command `mkdir -p'.
+# This subroutine has been tailored for this script, and
+# because of that, it ignored the directory name '.'.
+sub mkdirs {
+ my ($dirs, $mode) = @_;
+ $dirs = &cleandirs($dirs);
+ return if ($dirs eq '.');
+
+ my $newpos = -1;
+ while (($newpos = index($dirs, '/', $newpos+1)) != -1) {
+ my $dir = substr($dirs, 0, $newpos);
+ mkdir ($dir, $mode) || &croak("mkdir $dir failed");
+ }
+ mkdir ($dirs, $mode) || &croak("mkdir $dirs failed");
+}
+
+# Remove a set of directories, failing if the directories
+# contain other files.
+# This subroutine has been tailored for this script, and
+# because of that, it ignored the directory name '.'.
+sub rmdirs {
+ my ($dirs) = @_;
+ $dirs = &cleandirs($dirs);
+ return if ($dirs eq '.');
+
+ rmdir $dirs || &croak("rmdir $dirs failed");
+ my $newpos = length($dirs);
+ while (($newpos = rindex($dirs, '/', $newpos-1)) != -1) {
+ my $dir = substr($dirs, 0, $newpos);
+ rmdir $dir || &croak("rmdir $dir failed");
+ }
+}
+
+# Return a semi-canonical directory name.
+sub cleandirs {
+ my ($dir) = @_;
+ $dir =~ s:/+:/:g;
+ $dir =~ s:/*$::;
+ return $dir;
+}
+
+# Make a temporary directory with mode 0700.
+sub mktmpdir {
+ use File::Temp qw(mkdtemp);
+ my $template = "/tmp/mcuzipfs.XXXXXX";
+ $template="$ENV{MC_TMPDIR}/mcuzipfs.XXXXXX" if ($ENV{MC_TMPDIR});
+ return mkdtemp($template);
+}
+
+# Make a filename absolute and return it.
+sub absolutize {
+ my ($file, $pwd) = @_;
+ return "$pwd/$file" if ($file !~ /^\//);
+ return $file;
+}
+
+# Like the system built-in function, but with error checking.
+# The other argument is an exit status to allow.
+sub safesystem {
+ my ($command, @allowrc) = @_;
+ my ($desc) = ($command =~ /^([^ ]*) */);
+ $desc = File::Basename::basename($desc);
+ system $command;
+ my $rc = $?;
+ &croak("`$desc' failed") if (($rc & 0xFF) != 0);
+ if ($rc != 0) {
+ $rc = $rc >> 8;
+ foreach my $arc (@allowrc) {
+ return if ($rc == $arc);
+ }
+ &croak("`$desc' failed", "non-zero exit status ($rc)");
+ }
+}
+
+# Like backticks built-in, but with error checking.
+sub safeticks {
+ my ($command, @allowrc) = @_;
+ my ($desc) = ($command =~ /^([^ ]*) /);
+ $desc = File::Basename::basename($desc);
+ my $out = `$command`;
+ my $rc = $?;
+ &croak("`$desc' failed") if (($rc & 0xFF) != 0);
+ if ($rc != 0) {
+ $rc = $rc >> 8;
+ foreach my $arc (@allowrc) {
+ return if ($rc == $arc);
+ }
+ &croak("`$desc' failed", "non-zero exit status ($rc)");
+ }
+ return $out;
+}
+
+# Make sure enough arguments are supplied, or die.
+sub checkargs {
+ my $count = shift;
+ my $desc = shift;
+ &croak('missing argument', $desc) if ($count-1 > $#_);
+}
+
+# Quote zip wildcard metacharacters. Unfortunately Info-ZIP zip and unzip
+# on unix interpret some wildcards in filenames, despite the fact that
+# the shell already does this. Thus this function.
+sub zipquotemeta {
+ my ($name) = @_;
+ my $out = '';
+ for (my $c = 0; $c < length $name; $c++) {
+ my $ch = substr($name, $c, 1);
+ $out .= '\\' if (index('*?[]\\', $ch) != -1);
+ $out .= $ch;
+ }
+ return quotemeta($out);
+}
diff --git a/src/vfs/extfs/helpers/uzoo.in b/src/vfs/extfs/helpers/uzoo.in
new file mode 100644
index 0000000..fb079a5
--- /dev/null
+++ b/src/vfs/extfs/helpers/uzoo.in
@@ -0,0 +1,69 @@
+#! /bin/sh
+#
+# Zoo file system
+#
+# Source of zoo can be found at
+# ftp://ftp.ibiblio.org/pub/Linux/utils/compress/
+
+ZOO=${MC_TEST_EXTFS_LIST_CMD:-zoo}
+
+# Stupid zoo won't work if the archive name has no .zoo extension, so we
+# have to make a symlink with a "better" name. Also, zoo can create
+# directories even if printing files to stdout, so it's safer to confine
+# it to a temporary directory.
+mklink ()
+{
+ TMPDIR=`mktemp -d ${MC_TMPDIR:-/tmp}/mctmpdir-uzoo.XXXXXX` || exit 1
+ trap 'cd /; rm -rf "$TMPDIR"' 0 1 2 3 5 13 15
+ ARCHIVE="$TMPDIR/tmp.zoo"
+ ln -sf "$1" "$ARCHIVE"
+ cd "$TMPDIR" || exit 1
+}
+
+mczoofs_list ()
+{
+ mklink "$1"
+ $ZOO lq "$ARCHIVE" | @AWK@ -v uid=$(id -ru) '
+/^[^\ ]/ { next }
+{
+if (NF < 8)
+ next
+if ($8 ~ /^\^/)
+ $8=substr($8, 2)
+if ($6 > 50)
+ $6=$6 + 1900
+else
+ $6=$6 + 2000
+split($7, a, ":")
+split("Jan:Feb:Mar:Apr:May:Jun:Jul:Aug:Sep:Oct:Nov:Dec", month_list, ":")
+for (i=1; i<=12; i++) {
+ month[month_list[i]] = i
+}
+if ($8 ~ /\/$/)
+ printf "drwxr-xr-x 1 %-8d %-8d %8d %02d-%02d-%4d %02d:%02d %s\n", uid, 0, $1, month[$5], $4, $6, a[1], a[2], $8
+else
+ printf "-rw-r--r-- 1 %-8d %-8d %8d %02d-%02d-%4d %02d:%02d %s\n", uid, 0, $1, month[$5], $4, $6, a[1], a[2], $8
+}' 2>/dev/null
+ exit 0
+}
+
+mczoofs_copyout ()
+{
+ mklink "$1"
+ # zoo only accepts name without directory as file to extract
+ base=`echo "$2" | sed 's,.*/,,'`
+ $ZOO xpq: "$ARCHIVE" "$base" > "$3"
+ cd /
+ exit 0
+}
+
+umask 077
+
+cmd="$1"
+shift
+case "$cmd" in
+ list) mczoofs_list "$@" ;;
+ copyout) mczoofs_copyout "$@" ;;
+ *) exit 1 ;;
+esac
+exit 0
diff --git a/src/vfs/fish/Makefile.am b/src/vfs/fish/Makefile.am
new file mode 100644
index 0000000..4f3ca87
--- /dev/null
+++ b/src/vfs/fish/Makefile.am
@@ -0,0 +1,13 @@
+SUBDIRS = helpers
+DIST_SUBDIRS = helpers
+
+AM_CPPFLAGS = \
+ -DLIBEXECDIR=\""$(libexecdir)/@PACKAGE@/"\" \
+ $(GLIB_CFLAGS) \
+ -I$(top_srcdir)
+
+noinst_LTLIBRARIES = libvfs-fish.la
+
+libvfs_fish_la_SOURCES = \
+ fish.c fish.h \
+ fishdef.h
diff --git a/src/vfs/fish/Makefile.in b/src/vfs/fish/Makefile.in
new file mode 100644
index 0000000..cd952a8
--- /dev/null
+++ b/src/vfs/fish/Makefile.in
@@ -0,0 +1,857 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/vfs/fish
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libvfs_fish_la_LIBADD =
+am_libvfs_fish_la_OBJECTS = fish.lo
+libvfs_fish_la_OBJECTS = $(am_libvfs_fish_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/fish.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libvfs_fish_la_SOURCES)
+DIST_SOURCES = $(libvfs_fish_la_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = helpers
+DIST_SUBDIRS = helpers
+AM_CPPFLAGS = \
+ -DLIBEXECDIR=\""$(libexecdir)/@PACKAGE@/"\" \
+ $(GLIB_CFLAGS) \
+ -I$(top_srcdir)
+
+noinst_LTLIBRARIES = libvfs-fish.la
+libvfs_fish_la_SOURCES = \
+ fish.c fish.h \
+ fishdef.h
+
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/vfs/fish/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/fish/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libvfs-fish.la: $(libvfs_fish_la_OBJECTS) $(libvfs_fish_la_DEPENDENCIES) $(EXTRA_libvfs_fish_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libvfs_fish_la_OBJECTS) $(libvfs_fish_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fish.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(LTLIBRARIES)
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/fish.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/fish.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/vfs/fish/fish.c b/src/vfs/fish/fish.c
new file mode 100644
index 0000000..ec71a41
--- /dev/null
+++ b/src/vfs/fish/fish.c
@@ -0,0 +1,1805 @@
+/*
+ Virtual File System: FISH implementation for transferring files over
+ shell connections.
+
+ Copyright (C) 1998-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Pavel Machek, 1998
+ Michal Svec, 2000
+ Andrew Borodin <aborodin@vmail.ru>, 2010-2022
+ Slava Zanko <slavazanko@gmail.com>, 2010, 2013
+ Ilia Maslakov <il.smind@gmail.com>, 2010
+
+ Derived from ftpfs.c.
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * \brief Source: Virtual File System: FISH implementation for transferring files over
+ * shell connections
+ * \author Pavel Machek
+ * \author Michal Svec
+ * \date 1998, 2000
+ *
+ * Derived from ftpfs.c
+ * Read README.fish for protocol specification.
+ *
+ * Syntax of path is: \verbatim sh://user@host[:Cr]/path \endverbatim
+ * where C means you want compressed connection,
+ * and r means you want to use rsh
+ *
+ * Namespace: fish_vfs_ops exported.
+ */
+
+/* Define this if your ssh can take -I option */
+
+#include <config.h>
+#include <errno.h>
+#include <pwd.h>
+#include <grp.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h> /* uintmax_t */
+
+#include "lib/global.h"
+#include "lib/tty/tty.h" /* enable/disable interrupt key */
+#include "lib/strescape.h"
+#include "lib/unixcompat.h"
+#include "lib/fileloc.h"
+#include "lib/util.h" /* my_exit() */
+#include "lib/mcconfig.h"
+
+#include "src/execute.h" /* pre_exec, post_exec */
+
+#include "lib/vfs/vfs.h"
+#include "lib/vfs/utilvfs.h"
+#include "lib/vfs/netutil.h"
+#include "lib/vfs/xdirentry.h"
+#include "lib/vfs/gc.h" /* vfs_stamp_create */
+
+#include "fish.h"
+#include "fishdef.h"
+
+/*** global variables ****************************************************************************/
+
+int fish_directory_timeout = 900;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define DO_RESOLVE_SYMLINK 1
+#define DO_OPEN 2
+#define DO_FREE_RESOURCE 4
+
+#define FISH_FLAG_COMPRESSED 1
+#define FISH_FLAG_RSH 2
+
+#define OPT_FLUSH 1
+#define OPT_IGNORE_ERROR 2
+
+/*
+ * Reply codes.
+ */
+#define PRELIM 1 /* positive preliminary */
+#define COMPLETE 2 /* positive completion */
+#define CONTINUE 3 /* positive intermediate */
+#define TRANSIENT 4 /* transient negative completion */
+#define ERROR 5 /* permanent negative completion */
+
+/* command wait_flag: */
+#define NONE 0x00
+#define WAIT_REPLY 0x01
+#define WANT_STRING 0x02
+
+/* environment flags */
+#define FISH_HAVE_HEAD 1
+#define FISH_HAVE_SED 2
+#define FISH_HAVE_AWK 4
+#define FISH_HAVE_PERL 8
+#define FISH_HAVE_LSQ 16
+#define FISH_HAVE_DATE_MDYT 32
+#define FISH_HAVE_TAIL 64
+
+#define FISH_SUPER(super) ((fish_super_t *) (super))
+#define FISH_FILE_HANDLER(fh) ((fish_file_handler_t *) fh)
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ struct vfs_s_super base; /* base class */
+
+ int sockr;
+ int sockw;
+ char *scr_ls;
+ char *scr_chmod;
+ char *scr_utime;
+ char *scr_exists;
+ char *scr_mkdir;
+ char *scr_unlink;
+ char *scr_chown;
+ char *scr_rmdir;
+ char *scr_ln;
+ char *scr_mv;
+ char *scr_hardlink;
+ char *scr_get;
+ char *scr_send;
+ char *scr_append;
+ char *scr_info;
+ int host_flags;
+ GString *scr_env;
+} fish_super_t;
+
+typedef struct
+{
+ vfs_file_handler_t base; /* base class */
+
+ off_t got;
+ off_t total;
+ gboolean append;
+} fish_file_handler_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static char reply_str[80];
+
+static struct vfs_s_subclass fish_subclass;
+static struct vfs_class *vfs_fish_ops = VFS_CLASS (&fish_subclass);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fish_set_blksize (struct stat *s)
+{
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ /* redefine block size */
+ s->st_blksize = 64 * 1024; /* FIXME */
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct stat *
+fish_default_stat (struct vfs_class *me)
+{
+ struct stat *s;
+
+ s = vfs_s_default_stat (me, S_IFDIR | 0755);
+ fish_set_blksize (s);
+ vfs_adjust_stat (s);
+
+ return s;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+fish_load_script_from_file (const char *hostname, const char *script_name, const char *def_content)
+{
+ char *scr_filename = NULL;
+ char *scr_content;
+ gsize scr_len = 0;
+
+ /* 1st: scan user directory */
+ scr_filename = g_build_path (PATH_SEP_STR, mc_config_get_data_path (), FISH_PREFIX, hostname,
+ script_name, (char *) NULL);
+ /* silent about user dir */
+ g_file_get_contents (scr_filename, &scr_content, &scr_len, NULL);
+ g_free (scr_filename);
+ /* 2nd: scan system dir */
+ if (scr_content == NULL)
+ {
+ scr_filename =
+ g_build_path (PATH_SEP_STR, LIBEXECDIR, FISH_PREFIX, script_name, (char *) NULL);
+ g_file_get_contents (scr_filename, &scr_content, &scr_len, NULL);
+ g_free (scr_filename);
+ }
+
+ if (scr_content != NULL)
+ return scr_content;
+
+ return g_strdup (def_content);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_decode_reply (char *s, gboolean was_garbage)
+{
+ int code;
+
+ /* cppcheck-suppress invalidscanf */
+ if (sscanf (s, "%d", &code) == 0)
+ {
+ code = 500;
+ return 5;
+ }
+ if (code < 100)
+ return was_garbage ? ERROR : (code == 0 ? COMPLETE : PRELIM);
+ return code / 100;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Returns a reply code, check /usr/include/arpa/ftp.h for possible values */
+
+static int
+fish_get_reply (struct vfs_class *me, int sock, char *string_buf, int string_len)
+{
+ char answer[BUF_1K];
+ gboolean was_garbage = FALSE;
+
+ while (TRUE)
+ {
+ if (!vfs_s_get_line (me, sock, answer, sizeof (answer), '\n'))
+ {
+ if (string_buf != NULL)
+ *string_buf = '\0';
+ return 4;
+ }
+
+ if (strncmp (answer, "### ", 4) == 0)
+ return fish_decode_reply (answer + 4, was_garbage ? 1 : 0);
+
+ was_garbage = TRUE;
+ if (string_buf != NULL)
+ g_strlcpy (string_buf, answer, string_len);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_command (struct vfs_class *me, struct vfs_s_super *super, int wait_reply, const char *cmd,
+ size_t cmd_len)
+{
+ ssize_t status;
+ FILE *logfile = me->logfile;
+
+ if (cmd_len == (size_t) (-1))
+ cmd_len = strlen (cmd);
+
+ if (logfile != NULL)
+ {
+ size_t ret;
+
+ ret = fwrite (cmd, cmd_len, 1, logfile);
+ ret = fflush (logfile);
+ (void) ret;
+ }
+
+ tty_enable_interrupt_key ();
+ status = write (FISH_SUPER (super)->sockw, cmd, cmd_len);
+ tty_disable_interrupt_key ();
+
+ if (status < 0)
+ return TRANSIENT;
+
+ if (wait_reply)
+ return fish_get_reply (me, FISH_SUPER (super)->sockr,
+ (wait_reply & WANT_STRING) != 0 ? reply_str :
+ NULL, sizeof (reply_str) - 1);
+ return COMPLETE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+G_GNUC_PRINTF (5, 0)
+fish_command_va (struct vfs_class *me, struct vfs_s_super *super, int wait_reply, const char *scr,
+ const char *vars, va_list ap)
+{
+ int r;
+ GString *command;
+
+ command = mc_g_string_dup (FISH_SUPER (super)->scr_env);
+ g_string_append_vprintf (command, vars, ap);
+ g_string_append (command, scr);
+ r = fish_command (me, super, wait_reply, command->str, command->len);
+ g_string_free (command, TRUE);
+
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+G_GNUC_PRINTF (5, 6)
+fish_command_v (struct vfs_class *me, struct vfs_s_super *super, int wait_reply, const char *scr,
+ const char *vars, ...)
+{
+ int r;
+ va_list ap;
+
+ va_start (ap, vars);
+ r = fish_command_va (me, super, wait_reply, scr, vars, ap);
+ va_end (ap);
+
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+G_GNUC_PRINTF (5, 6)
+fish_send_command (struct vfs_class *me, struct vfs_s_super *super, int flags, const char *scr,
+ const char *vars, ...)
+{
+ int r;
+ va_list ap;
+
+ va_start (ap, vars);
+ r = fish_command_va (me, super, WAIT_REPLY, scr, vars, ap);
+ va_end (ap);
+ vfs_stamp_create (vfs_fish_ops, super);
+
+ if (r != COMPLETE)
+ ERRNOR (E_REMOTE, -1);
+ if ((flags & OPT_FLUSH) != 0)
+ vfs_s_invalidate (me, super);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_super *
+fish_new_archive (struct vfs_class *me)
+{
+ fish_super_t *arch;
+
+ arch = g_new0 (fish_super_t, 1);
+ arch->base.me = me;
+
+ return VFS_SUPER (arch);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fish_free_archive (struct vfs_class *me, struct vfs_s_super *super)
+{
+ fish_super_t *fish_super = FISH_SUPER (super);
+
+ if ((fish_super->sockw != -1) || (fish_super->sockr != -1))
+ vfs_print_message (_("fish: Disconnecting from %s"), super->name ? super->name : "???");
+
+ if (fish_super->sockw != -1)
+ {
+ fish_command (me, super, NONE, "#BYE\nexit\n", -1);
+ close (fish_super->sockw);
+ fish_super->sockw = -1;
+ }
+
+ if (fish_super->sockr != -1)
+ {
+ close (fish_super->sockr);
+ fish_super->sockr = -1;
+ }
+
+ g_free (fish_super->scr_ls);
+ g_free (fish_super->scr_exists);
+ g_free (fish_super->scr_mkdir);
+ g_free (fish_super->scr_unlink);
+ g_free (fish_super->scr_chown);
+ g_free (fish_super->scr_chmod);
+ g_free (fish_super->scr_utime);
+ g_free (fish_super->scr_rmdir);
+ g_free (fish_super->scr_ln);
+ g_free (fish_super->scr_mv);
+ g_free (fish_super->scr_hardlink);
+ g_free (fish_super->scr_get);
+ g_free (fish_super->scr_send);
+ g_free (fish_super->scr_append);
+ g_free (fish_super->scr_info);
+ g_string_free (fish_super->scr_env, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fish_pipeopen (struct vfs_s_super *super, const char *path, const char *argv[])
+{
+ int fileset1[2], fileset2[2];
+ int res;
+
+ if ((pipe (fileset1) < 0) || (pipe (fileset2) < 0))
+ vfs_die ("Cannot pipe(): %m.");
+
+ res = fork ();
+
+ if (res != 0)
+ {
+ if (res < 0)
+ vfs_die ("Cannot fork(): %m.");
+ /* We are the parent */
+ close (fileset1[0]);
+ FISH_SUPER (super)->sockw = fileset1[1];
+ close (fileset2[1]);
+ FISH_SUPER (super)->sockr = fileset2[0];
+ }
+ else
+ {
+ res = dup2 (fileset1[0], STDIN_FILENO);
+ close (fileset1[0]);
+ close (fileset1[1]);
+ res = dup2 (fileset2[1], STDOUT_FILENO);
+ close (STDERR_FILENO);
+ /* stderr to /dev/null */
+ res = open ("/dev/null", O_WRONLY);
+ close (fileset2[0]);
+ close (fileset2[1]);
+ execvp (path, (char **) argv);
+ my_exit (3);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GString *
+fish_set_env (int flags)
+{
+ GString *ret;
+
+ ret = g_string_sized_new (256);
+
+ if ((flags & FISH_HAVE_HEAD) != 0)
+ g_string_append (ret, "FISH_HAVE_HEAD=1 export FISH_HAVE_HEAD; ");
+
+ if ((flags & FISH_HAVE_SED) != 0)
+ g_string_append (ret, "FISH_HAVE_SED=1 export FISH_HAVE_SED; ");
+
+ if ((flags & FISH_HAVE_AWK) != 0)
+ g_string_append (ret, "FISH_HAVE_AWK=1 export FISH_HAVE_AWK; ");
+
+ if ((flags & FISH_HAVE_PERL) != 0)
+ g_string_append (ret, "FISH_HAVE_PERL=1 export FISH_HAVE_PERL; ");
+
+ if ((flags & FISH_HAVE_LSQ) != 0)
+ g_string_append (ret, "FISH_HAVE_LSQ=1 export FISH_HAVE_LSQ; ");
+
+ if ((flags & FISH_HAVE_DATE_MDYT) != 0)
+ g_string_append (ret, "FISH_HAVE_DATE_MDYT=1 export FISH_HAVE_DATE_MDYT; ");
+
+ if ((flags & FISH_HAVE_TAIL) != 0)
+ g_string_append (ret, "FISH_HAVE_TAIL=1 export FISH_HAVE_TAIL; ");
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+fish_info (struct vfs_class *me, struct vfs_s_super *super)
+{
+ fish_super_t *fish_super = FISH_SUPER (super);
+
+ if (fish_command (me, super, NONE, fish_super->scr_info, -1) == COMPLETE)
+ {
+ while (TRUE)
+ {
+ int res;
+ char buffer[BUF_8K] = "";
+
+ res = vfs_s_get_line_interruptible (me, buffer, sizeof (buffer), fish_super->sockr);
+ if ((res == 0) || (res == EINTR))
+ ERRNOR (ECONNRESET, FALSE);
+ if (strncmp (buffer, "### ", 4) == 0)
+ break;
+ fish_super->host_flags = atol (buffer);
+ }
+ return TRUE;
+ }
+ ERRNOR (E_PROTO, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fish_open_archive_pipeopen (struct vfs_s_super *super)
+{
+ char gbuf[10];
+ const char *argv[10]; /* All of 10 is used now */
+ const char *xsh = (super->path_element->port == FISH_FLAG_RSH ? "rsh" : "ssh");
+ int i = 0;
+
+ argv[i++] = xsh;
+ if (super->path_element->port == FISH_FLAG_COMPRESSED)
+ argv[i++] = "-C";
+
+ if (super->path_element->port > FISH_FLAG_RSH)
+ {
+ argv[i++] = "-p";
+ g_snprintf (gbuf, sizeof (gbuf), "%d", super->path_element->port);
+ argv[i++] = gbuf;
+ }
+
+ /*
+ * Add the user name to the ssh command line only if it was explicitly
+ * set in vfs URL. rsh/ssh will get current user by default
+ * plus we can set convenient overrides in ~/.ssh/config (explicit -l
+ * option breaks it for some)
+ */
+
+ if (super->path_element->user != NULL)
+ {
+ argv[i++] = "-l";
+ argv[i++] = super->path_element->user;
+ }
+ else
+ {
+ /* The rest of the code assumes it to be a valid username */
+ super->path_element->user = vfs_get_local_username ();
+ }
+
+ argv[i++] = super->path_element->host;
+ argv[i++] = "echo FISH:; /bin/sh";
+ argv[i++] = NULL;
+
+ fish_pipeopen (super, xsh, argv);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+fish_open_archive_talk (struct vfs_class *me, struct vfs_s_super *super)
+{
+ fish_super_t *fish_super = FISH_SUPER (super);
+ char answer[2048];
+
+ printf ("\n%s\n", _("fish: Waiting for initial line..."));
+
+ if (vfs_s_get_line (me, fish_super->sockr, answer, sizeof (answer), ':') == 0)
+ return FALSE;
+
+ if (strstr (answer, "assword") != NULL)
+ {
+ /* Currently, this does not work. ssh reads passwords from
+ /dev/tty, not from stdin :-(. */
+
+ printf ("\n%s\n", _("Sorry, we cannot do password authenticated connections for now."));
+
+ return FALSE;
+#if 0
+ if (super->path_element->password == NULL)
+ {
+ char *p, *op;
+
+ p = g_strdup_printf (_("fish: Password is required for %s"), super->path_element->user);
+ op = vfs_get_password (p);
+ g_free (p);
+ if (op == NULL)
+ return FALSE;
+ super->path_element->password = op;
+ }
+
+ printf ("\n%s\n", _("fish: Sending password..."));
+
+ {
+ size_t str_len;
+
+ str_len = strlen (super->path_element->password);
+ if ((write (fish_super.sockw, super->path_element->password, str_len) !=
+ (ssize_t) str_len) || (write (fish_super->sockw, "\n", 1) != 1))
+ return FALSE;
+ }
+#endif
+ }
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_open_archive_int (struct vfs_class *me, struct vfs_s_super *super)
+{
+ gboolean ftalk;
+
+ /* hide panels */
+ pre_exec ();
+
+ /* open pipe */
+ fish_open_archive_pipeopen (super);
+
+ /* Start talk with ssh-server (password prompt, etc ) */
+ ftalk = fish_open_archive_talk (me, super);
+
+ /* show panels */
+ post_exec ();
+
+ if (!ftalk)
+ ERRNOR (E_PROTO, -1);
+
+ vfs_print_message ("%s", _("fish: Sending initial line..."));
+ /*
+ * Run 'start_fish_server'. If it doesn't exist - no problem,
+ * we'll talk directly to the shell.
+ */
+
+ if (fish_command
+ (me, super, WAIT_REPLY, "#FISH\necho; start_fish_server 2>&1; echo '### 200'\n",
+ -1) != COMPLETE)
+ ERRNOR (E_PROTO, -1);
+
+ vfs_print_message ("%s", _("fish: Handshaking version..."));
+ if (fish_command (me, super, WAIT_REPLY, "#VER 0.0.3\necho '### 000'\n", -1) != COMPLETE)
+ ERRNOR (E_PROTO, -1);
+
+ /* Set up remote locale to C, otherwise dates cannot be recognized */
+ if (fish_command
+ (me, super, WAIT_REPLY,
+ "LANG=C LC_ALL=C LC_TIME=C; export LANG LC_ALL LC_TIME;\n" "echo '### 200'\n",
+ -1) != COMPLETE)
+ ERRNOR (E_PROTO, -1);
+
+ vfs_print_message ("%s", _("fish: Getting host info..."));
+ if (fish_info (me, super))
+ FISH_SUPER (super)->scr_env = fish_set_env (FISH_SUPER (super)->host_flags);
+
+#if 0
+ super->name =
+ g_strconcat ("sh://", super->path_element->user, "@", super->path_element->host,
+ PATH_SEP_STR, (char *) NULL);
+#else
+ super->name = g_strdup (PATH_SEP_STR);
+#endif
+
+ super->root = vfs_s_new_inode (me, super, fish_default_stat (me));
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_open_archive (struct vfs_s_super *super,
+ const vfs_path_t * vpath, const vfs_path_element_t * vpath_element)
+{
+ fish_super_t *fish_super = FISH_SUPER (super);
+
+ (void) vpath;
+
+ super->path_element = vfs_path_element_clone (vpath_element);
+
+ if (strncmp (vpath_element->vfs_prefix, "rsh", 3) == 0)
+ super->path_element->port = FISH_FLAG_RSH;
+
+ fish_super->scr_ls =
+ fish_load_script_from_file (super->path_element->host, FISH_LS_FILE, FISH_LS_DEF_CONTENT);
+ fish_super->scr_exists =
+ fish_load_script_from_file (super->path_element->host, FISH_EXISTS_FILE,
+ FISH_EXISTS_DEF_CONTENT);
+ fish_super->scr_mkdir =
+ fish_load_script_from_file (super->path_element->host, FISH_MKDIR_FILE,
+ FISH_MKDIR_DEF_CONTENT);
+ fish_super->scr_unlink =
+ fish_load_script_from_file (super->path_element->host, FISH_UNLINK_FILE,
+ FISH_UNLINK_DEF_CONTENT);
+ fish_super->scr_chown =
+ fish_load_script_from_file (super->path_element->host, FISH_CHOWN_FILE,
+ FISH_CHOWN_DEF_CONTENT);
+ fish_super->scr_chmod =
+ fish_load_script_from_file (super->path_element->host, FISH_CHMOD_FILE,
+ FISH_CHMOD_DEF_CONTENT);
+ fish_super->scr_utime =
+ fish_load_script_from_file (super->path_element->host, FISH_UTIME_FILE,
+ FISH_UTIME_DEF_CONTENT);
+ fish_super->scr_rmdir =
+ fish_load_script_from_file (super->path_element->host, FISH_RMDIR_FILE,
+ FISH_RMDIR_DEF_CONTENT);
+ fish_super->scr_ln =
+ fish_load_script_from_file (super->path_element->host, FISH_LN_FILE, FISH_LN_DEF_CONTENT);
+ fish_super->scr_mv =
+ fish_load_script_from_file (super->path_element->host, FISH_MV_FILE, FISH_MV_DEF_CONTENT);
+ fish_super->scr_hardlink =
+ fish_load_script_from_file (super->path_element->host, FISH_HARDLINK_FILE,
+ FISH_HARDLINK_DEF_CONTENT);
+ fish_super->scr_get =
+ fish_load_script_from_file (super->path_element->host, FISH_GET_FILE, FISH_GET_DEF_CONTENT);
+ fish_super->scr_send =
+ fish_load_script_from_file (super->path_element->host, FISH_SEND_FILE,
+ FISH_SEND_DEF_CONTENT);
+ fish_super->scr_append =
+ fish_load_script_from_file (super->path_element->host, FISH_APPEND_FILE,
+ FISH_APPEND_DEF_CONTENT);
+ fish_super->scr_info =
+ fish_load_script_from_file (super->path_element->host, FISH_INFO_FILE,
+ FISH_INFO_DEF_CONTENT);
+
+ return fish_open_archive_int (vpath_element->class, super);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_archive_same (const vfs_path_element_t * vpath_element, struct vfs_s_super *super,
+ const vfs_path_t * vpath, void *cookie)
+{
+ vfs_path_element_t *path_element;
+ int result;
+
+ (void) vpath;
+ (void) cookie;
+
+ path_element = vfs_path_element_clone (vpath_element);
+
+ if (path_element->user == NULL)
+ path_element->user = vfs_get_local_username ();
+
+ result = ((strcmp (path_element->host, super->path_element->host) == 0)
+ && (strcmp (path_element->user, super->path_element->user) == 0)
+ && (path_element->port == super->path_element->port)) ? 1 : 0;
+
+ vfs_path_element_free (path_element);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fish_parse_ls (char *buffer, struct vfs_s_entry *ent)
+{
+#define ST ent->ino->st
+
+ buffer++;
+
+ switch (buffer[-1])
+ {
+ case ':':
+ {
+ char *filename;
+ char *filename_bound;
+ char *temp;
+
+ filename = buffer;
+
+ if (strcmp (filename, "\".\"") == 0 || strcmp (filename, "\"..\"") == 0)
+ break; /* We'll do "." and ".." ourselves */
+
+ filename_bound = filename + strlen (filename);
+
+ if (S_ISLNK (ST.st_mode))
+ {
+ char *linkname;
+ char *linkname_bound;
+ /* we expect: "escaped-name" -> "escaped-name"
+ // -> cannot occur in filenames,
+ // because it will be escaped to -\> */
+
+
+ linkname_bound = filename_bound;
+
+ if (*filename == '"')
+ ++filename;
+
+ linkname = strstr (filename, "\" -> \"");
+ if (linkname == NULL)
+ {
+ /* broken client, or smth goes wrong */
+ linkname = filename_bound;
+ if (filename_bound > filename && *(filename_bound - 1) == '"')
+ --filename_bound; /* skip trailing " */
+ }
+ else
+ {
+ filename_bound = linkname;
+ linkname += 6; /* strlen ("\" -> \"") */
+ if (*(linkname_bound - 1) == '"')
+ --linkname_bound; /* skip trailing " */
+ }
+
+ ent->name = g_strndup (filename, filename_bound - filename);
+ temp = ent->name;
+ ent->name = strutils_shell_unescape (ent->name);
+ g_free (temp);
+
+ ent->ino->linkname = g_strndup (linkname, linkname_bound - linkname);
+ temp = ent->ino->linkname;
+ ent->ino->linkname = strutils_shell_unescape (ent->ino->linkname);
+ g_free (temp);
+ }
+ else
+ {
+ /* we expect: "escaped-name" */
+ if (filename_bound - filename > 2)
+ {
+ /*
+ there is at least 2 "
+ and we skip them
+ */
+ if (*filename == '"')
+ ++filename;
+ if (*(filename_bound - 1) == '"')
+ --filename_bound;
+ }
+
+ ent->name = g_strndup (filename, filename_bound - filename);
+ temp = ent->name;
+ ent->name = strutils_shell_unescape (ent->name);
+ g_free (temp);
+ }
+ break;
+ }
+
+ case 'S':
+ ST.st_size = (off_t) g_ascii_strtoll (buffer, NULL, 10);
+ break;
+
+ case 'P':
+ {
+ size_t skipped;
+
+ vfs_parse_filemode (buffer, &skipped, &ST.st_mode);
+ break;
+ }
+
+ case 'R':
+ {
+ /*
+ raw filemode:
+ we expect: Roctal-filemode octal-filetype uid.gid
+ */
+ size_t skipped;
+
+ vfs_parse_raw_filemode (buffer, &skipped, &ST.st_mode);
+ break;
+ }
+
+ case 'd':
+ vfs_split_text (buffer);
+ if (vfs_parse_filedate (0, &ST.st_ctime) == 0)
+ break;
+ ST.st_atime = ST.st_mtime = ST.st_ctime;
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ ST.st_atim.tv_nsec = ST.st_mtim.tv_nsec = ST.st_ctim.tv_nsec = 0;
+#endif
+ break;
+
+ case 'D':
+ {
+ struct tm tim;
+
+ memset (&tim, 0, sizeof (tim));
+ /* cppcheck-suppress invalidscanf */
+ if (sscanf (buffer, "%d %d %d %d %d %d", &tim.tm_year, &tim.tm_mon,
+ &tim.tm_mday, &tim.tm_hour, &tim.tm_min, &tim.tm_sec) != 6)
+ break;
+ ST.st_atime = ST.st_mtime = ST.st_ctime = mktime (&tim);
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ ST.st_atim.tv_nsec = ST.st_mtim.tv_nsec = ST.st_ctim.tv_nsec = 0;
+#endif
+ }
+ break;
+
+ case 'E':
+ {
+ int maj, min;
+
+ /* cppcheck-suppress invalidscanf */
+ if (sscanf (buffer, "%d,%d", &maj, &min) != 2)
+ break;
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ ST.st_rdev = makedev (maj, min);
+#endif
+ }
+ break;
+
+ default:
+ break;
+ }
+
+#undef ST
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_dir_load (struct vfs_class *me, struct vfs_s_inode *dir, const char *remote_path)
+{
+ struct vfs_s_super *super = dir->super;
+ char buffer[BUF_8K] = "\0";
+ struct vfs_s_entry *ent = NULL;
+ char *quoted_path;
+ int reply_code;
+
+ /*
+ * Simple FISH debug interface :]
+ */
+#if 0
+ if (me->logfile == NULL)
+ me->logfile = fopen ("/tmp/mc-FISH.sh", "w");
+#endif
+
+ vfs_print_message (_("fish: Reading directory %s..."), remote_path);
+
+ dir->timestamp = g_get_monotonic_time () + fish_directory_timeout * G_USEC_PER_SEC;
+
+ quoted_path = strutils_shell_escape (remote_path);
+ (void) fish_command_v (me, super, NONE, FISH_SUPER (super)->scr_ls, "FISH_FILENAME=%s;\n",
+ quoted_path);
+ g_free (quoted_path);
+
+ ent = vfs_s_generate_entry (me, NULL, dir, 0);
+
+ while (TRUE)
+ {
+ int res;
+
+ res = vfs_s_get_line_interruptible (me, buffer, sizeof (buffer), FISH_SUPER (super)->sockr);
+
+ if ((res == 0) || (res == EINTR))
+ {
+ vfs_s_free_entry (me, ent);
+ me->verrno = ECONNRESET;
+ goto error;
+ }
+ if (me->logfile != NULL)
+ {
+ fputs (buffer, me->logfile);
+ fputs ("\n", me->logfile);
+ fflush (me->logfile);
+ }
+ if (strncmp (buffer, "### ", 4) == 0)
+ break;
+
+ if (buffer[0] != '\0')
+ fish_parse_ls (buffer, ent);
+ else if (ent->name != NULL)
+ {
+ vfs_s_insert_entry (me, dir, ent);
+ ent = vfs_s_generate_entry (me, NULL, dir, 0);
+ }
+ }
+
+ vfs_s_free_entry (me, ent);
+ reply_code = fish_decode_reply (buffer + 4, 0);
+ if (reply_code == COMPLETE)
+ {
+ vfs_print_message (_("%s: done."), me->name);
+ return 0;
+ }
+
+ me->verrno = reply_code == ERROR ? EACCES : E_REMOTE;
+
+ error:
+ vfs_print_message (_("%s: failure"), me->name);
+ return -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_file_store (struct vfs_class *me, vfs_file_handler_t * fh, char *name, char *localname)
+{
+ fish_file_handler_t *fish = FISH_FILE_HANDLER (fh);
+ struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh);
+ fish_super_t *fish_super = FISH_SUPER (super);
+ int code;
+ off_t total = 0;
+ char buffer[BUF_8K];
+ struct stat s;
+ int h;
+ char *quoted_name;
+
+ h = open (localname, O_RDONLY);
+ if (h == -1)
+ ERRNOR (EIO, -1);
+ if (fstat (h, &s) < 0)
+ {
+ close (h);
+ ERRNOR (EIO, -1);
+ }
+
+ /* First, try this as stor:
+ *
+ * ( head -c number ) | ( cat > file; cat >/dev/null )
+ *
+ * If 'head' is not present on the remote system, 'dd' will be used.
+ * Unfortunately, we cannot trust most non-GNU 'head' implementations
+ * even if '-c' options is supported. Therefore, we separate GNU head
+ * (and other modern heads?) using '-q' and '-' . This causes another
+ * implementations to fail (because of "incorrect options").
+ *
+ * Fallback is:
+ *
+ * rest=<number>
+ * while [ $rest -gt 0 ]
+ * do
+ * cnt=`expr \( $rest + 255 \) / 256`
+ * n=`dd bs=256 count=$cnt | tee -a <target_file> | wc -c`
+ * rest=`expr $rest - $n`
+ * done
+ *
+ * 'dd' was not designed for full filling of input buffers,
+ * and does not report exact number of bytes (not blocks).
+ * Therefore a more complex shell script is needed.
+ *
+ * On some systems non-GNU head writes "Usage:" error report to stdout
+ * instead of stderr. It makes impossible the use of "head || dd"
+ * algorithm for file appending case, therefore just "dd" is used for it.
+ */
+
+ quoted_name = strutils_shell_escape (name);
+ vfs_print_message (_("fish: store %s: sending command..."), quoted_name);
+
+ /* FIXME: File size is limited to ULONG_MAX */
+ code =
+ fish_command_v (me, super, WAIT_REPLY,
+ fish->append ? fish_super->scr_append : fish_super->scr_send,
+ "FISH_FILENAME=%s FISH_FILESIZE=%" PRIuMAX ";\n", quoted_name,
+ (uintmax_t) s.st_size);
+ g_free (quoted_name);
+
+ if (code != PRELIM)
+ {
+ close (h);
+ ERRNOR (E_REMOTE, -1);
+ }
+
+ while (TRUE)
+ {
+ ssize_t n, t;
+
+ while ((n = read (h, buffer, sizeof (buffer))) < 0)
+ {
+ if ((errno == EINTR) && tty_got_interrupt ())
+ continue;
+ vfs_print_message ("%s", _("fish: Local read failed, sending zeros"));
+ close (h);
+ h = open ("/dev/zero", O_RDONLY);
+ }
+
+ if (n == 0)
+ break;
+
+ t = write (fish_super->sockw, buffer, n);
+ if (t != n)
+ {
+ if (t == -1)
+ me->verrno = errno;
+ else
+ me->verrno = EIO;
+ goto error_return;
+ }
+ tty_disable_interrupt_key ();
+ total += n;
+ vfs_print_message ("%s: %" PRIuMAX "/%" PRIuMAX, _("fish: storing file"),
+ (uintmax_t) total, (uintmax_t) s.st_size);
+ }
+ close (h);
+
+ if (fish_get_reply (me, fish_super->sockr, NULL, 0) != COMPLETE)
+ ERRNOR (E_REMOTE, -1);
+ return 0;
+
+ error_return:
+ close (h);
+ fish_get_reply (me, fish_super->sockr, NULL, 0);
+ return -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_linear_start (struct vfs_class *me, vfs_file_handler_t * fh, off_t offset)
+{
+ fish_file_handler_t *fish = FISH_FILE_HANDLER (fh);
+ struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh);
+ char *name;
+ char *quoted_name;
+
+ name = vfs_s_fullpath (me, fh->ino);
+ if (name == NULL)
+ return 0;
+ quoted_name = strutils_shell_escape (name);
+ g_free (name);
+ fish->append = FALSE;
+
+ /*
+ * Check whether the remote file is readable by using 'dd' to copy
+ * a single byte from the remote file to /dev/null. If 'dd' completes
+ * with exit status of 0 use 'cat' to send the file contents to the
+ * standard output (i.e. over the network).
+ */
+
+ offset =
+ fish_command_v (me, super, WANT_STRING, FISH_SUPER (super)->scr_get,
+ "FISH_FILENAME=%s FISH_START_OFFSET=%" PRIuMAX ";\n", quoted_name,
+ (uintmax_t) offset);
+ g_free (quoted_name);
+
+ if (offset != PRELIM)
+ ERRNOR (E_REMOTE, 0);
+ fh->linear = LS_LINEAR_OPEN;
+ fish->got = 0;
+ errno = 0;
+#if SIZEOF_OFF_T == SIZEOF_LONG
+ fish->total = (off_t) strtol (reply_str, NULL, 10);
+#else
+ fish->total = (off_t) g_ascii_strtoll (reply_str, NULL, 10);
+#endif
+ if (errno != 0)
+ ERRNOR (E_REMOTE, 0);
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fish_linear_abort (struct vfs_class *me, vfs_file_handler_t * fh)
+{
+ fish_file_handler_t *fish = FISH_FILE_HANDLER (fh);
+ struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh);
+ char buffer[BUF_8K];
+ ssize_t n;
+
+ vfs_print_message ("%s", _("Aborting transfer..."));
+
+ do
+ {
+ n = MIN ((off_t) sizeof (buffer), (fish->total - fish->got));
+ if (n != 0)
+ {
+ n = read (FISH_SUPER (super)->sockr, buffer, n);
+ if (n < 0)
+ return;
+ fish->got += n;
+ }
+ }
+ while (n != 0);
+
+ if (fish_get_reply (me, FISH_SUPER (super)->sockr, NULL, 0) != COMPLETE)
+ vfs_print_message ("%s", _("Error reported after abort."));
+ else
+ vfs_print_message ("%s", _("Aborted transfer would be successful."));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+fish_linear_read (struct vfs_class *me, vfs_file_handler_t * fh, void *buf, size_t len)
+{
+ fish_file_handler_t *fish = FISH_FILE_HANDLER (fh);
+ struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh);
+ ssize_t n = 0;
+
+ len = MIN ((size_t) (fish->total - fish->got), len);
+ tty_disable_interrupt_key ();
+ while (len != 0 && ((n = read (FISH_SUPER (super)->sockr, buf, len)) < 0))
+ {
+ if ((errno == EINTR) && !tty_got_interrupt ())
+ continue;
+ break;
+ }
+ tty_enable_interrupt_key ();
+
+ if (n > 0)
+ fish->got += n;
+ else if (n < 0)
+ fish_linear_abort (me, fh);
+ else if (fish_get_reply (me, FISH_SUPER (super)->sockr, NULL, 0) != COMPLETE)
+ ERRNOR (E_REMOTE, -1);
+ ERRNOR (errno, n);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fish_linear_close (struct vfs_class *me, vfs_file_handler_t * fh)
+{
+ fish_file_handler_t *fish = FISH_FILE_HANDLER (fh);
+
+ if (fish->total != fish->got)
+ fish_linear_abort (me, fh);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_ctl (void *fh, int ctlop, void *arg)
+{
+ (void) arg;
+ (void) fh;
+ (void) ctlop;
+
+ return 0;
+
+#if 0
+ switch (ctlop)
+ {
+ case VFS_CTL_IS_NOTREADY:
+ {
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ int v;
+
+ if (file->linear == LS_NOT_LINEAR)
+ vfs_die ("You may not do this");
+ if (file->linear == LS_LINEAR_CLOSED || file->linear == LS_LINEAR_PREOPEN)
+ return 0;
+
+ v = vfs_s_select_on_two (VFS_FILE_HANDLER_SUPER (fh)->u.fish.sockr, 0);
+
+ return (((v < 0) && (errno == EINTR)) || v == 0) ? 1 : 0;
+ }
+ default:
+ return 0;
+ }
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_rename (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ const char *crpath1, *crpath2;
+ char *rpath1, *rpath2;
+ struct vfs_s_super *super, *super2;
+ struct vfs_class *me;
+ int ret;
+
+ crpath1 = vfs_s_get_path (vpath1, &super, 0);
+ if (crpath1 == NULL)
+ return -1;
+
+ crpath2 = vfs_s_get_path (vpath2, &super2, 0);
+ if (crpath2 == NULL)
+ return -1;
+
+ rpath1 = strutils_shell_escape (crpath1);
+ rpath2 = strutils_shell_escape (crpath2);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath1));
+
+ ret =
+ fish_send_command (me, super2, OPT_FLUSH, FISH_SUPER (super)->scr_mv,
+ "FISH_FILEFROM=%s FISH_FILETO=%s;\n", rpath1, rpath2);
+
+ g_free (rpath1);
+ g_free (rpath2);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_link (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ const char *crpath1, *crpath2;
+ char *rpath1, *rpath2;
+ struct vfs_s_super *super, *super2;
+ struct vfs_class *me;
+ int ret;
+
+ crpath1 = vfs_s_get_path (vpath1, &super, 0);
+ if (crpath1 == NULL)
+ return -1;
+
+ crpath2 = vfs_s_get_path (vpath2, &super2, 0);
+ if (crpath2 == NULL)
+ return -1;
+
+ rpath1 = strutils_shell_escape (crpath1);
+ rpath2 = strutils_shell_escape (crpath2);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath1));
+
+ ret =
+ fish_send_command (me, super2, OPT_FLUSH, FISH_SUPER (super)->scr_hardlink,
+ "FISH_FILEFROM=%s FISH_FILETO=%s;\n", rpath1, rpath2);
+
+ g_free (rpath1);
+ g_free (rpath2);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_symlink (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ char *qsetto;
+ const char *crpath;
+ char *rpath;
+ struct vfs_s_super *super;
+ struct vfs_class *me;
+ int ret;
+
+ crpath = vfs_s_get_path (vpath2, &super, 0);
+ if (crpath == NULL)
+ return -1;
+
+ rpath = strutils_shell_escape (crpath);
+ qsetto = strutils_shell_escape (vfs_path_get_last_path_str (vpath1));
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath2));
+
+ ret =
+ fish_send_command (me, super, OPT_FLUSH, FISH_SUPER (super)->scr_ln,
+ "FISH_FILEFROM=%s FISH_FILETO=%s;\n", qsetto, rpath);
+
+ g_free (qsetto);
+ g_free (rpath);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_stat (const vfs_path_t * vpath, struct stat *buf)
+{
+ int ret;
+
+ ret = vfs_s_stat (vpath, buf);
+ fish_set_blksize (buf);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_lstat (const vfs_path_t * vpath, struct stat *buf)
+{
+ int ret;
+
+ ret = vfs_s_lstat (vpath, buf);
+ fish_set_blksize (buf);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_fstat (void *vfs_info, struct stat *buf)
+{
+ int ret;
+
+ ret = vfs_s_fstat (vfs_info, buf);
+ fish_set_blksize (buf);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_chmod (const vfs_path_t * vpath, mode_t mode)
+{
+ const char *crpath;
+ char *rpath;
+ struct vfs_s_super *super;
+ struct vfs_class *me;
+ int ret;
+
+ crpath = vfs_s_get_path (vpath, &super, 0);
+ if (crpath == NULL)
+ return -1;
+
+ rpath = strutils_shell_escape (crpath);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ ret =
+ fish_send_command (me, super, OPT_FLUSH, FISH_SUPER (super)->scr_chmod,
+ "FISH_FILENAME=%s FISH_FILEMODE=%4.4o;\n", rpath,
+ (unsigned int) (mode & 07777));
+
+ g_free (rpath);
+
+ return ret;;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_chown (const vfs_path_t * vpath, uid_t owner, gid_t group)
+{
+ char *sowner, *sgroup;
+ struct passwd *pw;
+ struct group *gr;
+ const char *crpath;
+ char *rpath;
+ struct vfs_s_super *super;
+ struct vfs_class *me;
+ int ret;
+
+ pw = getpwuid (owner);
+ if (pw == NULL)
+ return 0;
+
+ gr = getgrgid (group);
+ if (gr == NULL)
+ return 0;
+
+ sowner = pw->pw_name;
+ sgroup = gr->gr_name;
+
+ crpath = vfs_s_get_path (vpath, &super, 0);
+ if (crpath == NULL)
+ return -1;
+
+ rpath = strutils_shell_escape (crpath);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ /* FIXME: what should we report if chgrp succeeds but chown fails? */
+ ret =
+ fish_send_command (me, super, OPT_FLUSH, FISH_SUPER (super)->scr_chown,
+ "FISH_FILENAME=%s FISH_FILEOWNER=%s FISH_FILEGROUP=%s;\n", rpath, sowner,
+ sgroup);
+
+ g_free (rpath);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fish_get_atime (mc_timesbuf_t * times, time_t * sec, long *nsec)
+{
+#ifdef HAVE_UTIMENSAT
+ *sec = (*times)[0].tv_sec;
+ *nsec = (*times)[0].tv_nsec;
+#else
+ *sec = times->actime;
+ *nsec = 0;
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fish_get_mtime (mc_timesbuf_t * times, time_t * sec, long *nsec)
+{
+#ifdef HAVE_UTIMENSAT
+ *sec = (*times)[1].tv_sec;
+ *nsec = (*times)[1].tv_nsec;
+#else
+ *sec = times->modtime;
+ *nsec = 0;
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_utime (const vfs_path_t * vpath, mc_timesbuf_t * times)
+{
+ char utcatime[16], utcmtime[16];
+ char utcatime_w_nsec[30], utcmtime_w_nsec[30];
+ time_t atime, mtime;
+ long atime_nsec, mtime_nsec;
+ struct tm *gmt;
+ const char *crpath;
+ char *rpath;
+ struct vfs_s_super *super;
+ struct vfs_class *me;
+ int ret;
+
+ crpath = vfs_s_get_path (vpath, &super, 0);
+ if (crpath == NULL)
+ return -1;
+
+ rpath = strutils_shell_escape (crpath);
+
+ fish_get_atime (times, &atime, &atime_nsec);
+ gmt = gmtime (&atime);
+ g_snprintf (utcatime, sizeof (utcatime), "%04d%02d%02d%02d%02d.%02d",
+ gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday,
+ gmt->tm_hour, gmt->tm_min, gmt->tm_sec);
+ g_snprintf (utcatime_w_nsec, sizeof (utcatime_w_nsec), "%04d-%02d-%02d %02d:%02d:%02d.%09ld",
+ gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday,
+ gmt->tm_hour, gmt->tm_min, gmt->tm_sec, atime_nsec);
+
+ fish_get_mtime (times, &mtime, &mtime_nsec);
+ gmt = gmtime (&mtime);
+ g_snprintf (utcmtime, sizeof (utcmtime), "%04d%02d%02d%02d%02d.%02d",
+ gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday,
+ gmt->tm_hour, gmt->tm_min, gmt->tm_sec);
+ g_snprintf (utcmtime_w_nsec, sizeof (utcmtime_w_nsec), "%04d-%02d-%02d %02d:%02d:%02d.%09ld",
+ gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday,
+ gmt->tm_hour, gmt->tm_min, gmt->tm_sec, mtime_nsec);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ ret = fish_send_command (me, super, OPT_FLUSH, FISH_SUPER (super)->scr_utime,
+ "FISH_FILENAME=%s FISH_FILEATIME=%ld FISH_FILEMTIME=%ld "
+ "FISH_TOUCHATIME=%s FISH_TOUCHMTIME=%s FISH_TOUCHATIME_W_NSEC=\"%s\" "
+ "FISH_TOUCHMTIME_W_NSEC=\"%s\";\n", rpath, (long) atime, (long) mtime,
+ utcatime, utcmtime, utcatime_w_nsec, utcmtime_w_nsec);
+
+ g_free (rpath);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_unlink (const vfs_path_t * vpath)
+{
+ const char *crpath;
+ char *rpath;
+ struct vfs_s_super *super;
+ struct vfs_class *me;
+ int ret;
+
+ crpath = vfs_s_get_path (vpath, &super, 0);
+ if (crpath == NULL)
+ return -1;
+
+ rpath = strutils_shell_escape (crpath);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ ret =
+ fish_send_command (me, super, OPT_FLUSH, FISH_SUPER (super)->scr_unlink,
+ "FISH_FILENAME=%s;\n", rpath);
+
+ g_free (rpath);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_exists (const vfs_path_t * vpath)
+{
+ const char *crpath;
+ char *rpath;
+ struct vfs_s_super *super;
+ struct vfs_class *me;
+ int ret;
+
+ crpath = vfs_s_get_path (vpath, &super, 0);
+ if (crpath == NULL)
+ return -1;
+
+ rpath = strutils_shell_escape (crpath);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ ret =
+ fish_send_command (me, super, OPT_FLUSH, FISH_SUPER (super)->scr_exists,
+ "FISH_FILENAME=%s;\n", rpath);
+
+ g_free (rpath);
+
+ return (ret == 0 ? 1 : 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_mkdir (const vfs_path_t * vpath, mode_t mode)
+{
+ const char *crpath;
+ char *rpath;
+ struct vfs_s_super *super;
+ struct vfs_class *me;
+ int ret;
+
+ (void) mode;
+
+ crpath = vfs_s_get_path (vpath, &super, 0);
+ if (crpath == NULL)
+ return -1;
+
+ rpath = strutils_shell_escape (crpath);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ ret =
+ fish_send_command (me, super, OPT_FLUSH, FISH_SUPER (super)->scr_mkdir,
+ "FISH_FILENAME=%s;\n", rpath);
+ g_free (rpath);
+
+ if (ret != 0)
+ return ret;
+
+ if (fish_exists (vpath) == 0)
+ {
+ me->verrno = EACCES;
+ return -1;
+ }
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_rmdir (const vfs_path_t * vpath)
+{
+ const char *crpath;
+ char *rpath;
+ struct vfs_s_super *super;
+ struct vfs_class *me;
+ int ret;
+
+ crpath = vfs_s_get_path (vpath, &super, 0);
+ if (crpath == NULL)
+ return -1;
+
+ rpath = strutils_shell_escape (crpath);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ ret =
+ fish_send_command (me, super, OPT_FLUSH, FISH_SUPER (super)->scr_rmdir,
+ "FISH_FILENAME=%s;\n", rpath);
+
+ g_free (rpath);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_file_handler_t *
+fish_fh_new (struct vfs_s_inode *ino, gboolean changed)
+{
+ fish_file_handler_t *fh;
+
+ fh = g_new0 (fish_file_handler_t, 1);
+ vfs_s_init_fh (VFS_FILE_HANDLER (fh), ino, changed);
+
+ return VFS_FILE_HANDLER (fh);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_fh_open (struct vfs_class *me, vfs_file_handler_t * fh, int flags, mode_t mode)
+{
+ fish_file_handler_t *fish = FISH_FILE_HANDLER (fh);
+
+ (void) mode;
+
+ /* File will be written only, so no need to retrieve it */
+ if (((flags & O_WRONLY) == O_WRONLY) && ((flags & (O_RDONLY | O_RDWR)) == 0))
+ {
+ /* user pressed the button [ Append ] in the "Copy" dialog */
+ if ((flags & O_APPEND) != 0)
+ fish->append = TRUE;
+
+ if (fh->ino->localname == NULL)
+ {
+ vfs_path_t *vpath = NULL;
+ int tmp_handle;
+
+ tmp_handle = vfs_mkstemps (&vpath, me->name, fh->ino->ent->name);
+ if (tmp_handle == -1)
+ return (-1);
+
+ fh->ino->localname = vfs_path_free (vpath, FALSE);
+ close (tmp_handle);
+ }
+ return 0;
+ }
+
+ if (fh->ino->localname == NULL && vfs_s_retrieve_file (me, fh->ino) == -1)
+ return (-1);
+
+ if (fh->ino->localname == NULL)
+ vfs_die ("retrieve_file failed to fill in localname");
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fish_fill_names (struct vfs_class *me, fill_names_f func)
+{
+ GList *iter;
+
+ for (iter = VFS_SUBCLASS (me)->supers; iter != NULL; iter = g_list_next (iter))
+ {
+ const struct vfs_s_super *super = (const struct vfs_s_super *) iter->data;
+
+ char *name;
+ char gbuf[10];
+ const char *flags = "";
+
+ switch (super->path_element->port)
+ {
+ case FISH_FLAG_RSH:
+ flags = ":r";
+ break;
+ case FISH_FLAG_COMPRESSED:
+ flags = ":C";
+ break;
+ default:
+ if (super->path_element->port > FISH_FLAG_RSH)
+ {
+ g_snprintf (gbuf, sizeof (gbuf), ":%d", super->path_element->port);
+ flags = gbuf;
+ }
+ break;
+ }
+
+ name =
+ g_strconcat (vfs_fish_ops->prefix, VFS_PATH_URL_DELIMITER,
+ super->path_element->user, "@", super->path_element->host, flags,
+ PATH_SEP_STR, super->path_element->path, (char *) NULL);
+ func (name);
+ g_free (name);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+fish_open (const vfs_path_t * vpath, int flags, mode_t mode)
+{
+ /*
+ sorry, i've places hack here
+ cause fish don't able to open files with O_EXCL flag
+ */
+ flags &= ~O_EXCL;
+ return vfs_s_open (vpath, flags, mode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init_fish (void)
+{
+ tcp_init ();
+
+ vfs_init_subclass (&fish_subclass, "fish", VFSF_REMOTE | VFSF_USETMP, "sh");
+ vfs_fish_ops->fill_names = fish_fill_names;
+ vfs_fish_ops->stat = fish_stat;
+ vfs_fish_ops->lstat = fish_lstat;
+ vfs_fish_ops->fstat = fish_fstat;
+ vfs_fish_ops->chmod = fish_chmod;
+ vfs_fish_ops->chown = fish_chown;
+ vfs_fish_ops->utime = fish_utime;
+ vfs_fish_ops->open = fish_open;
+ vfs_fish_ops->symlink = fish_symlink;
+ vfs_fish_ops->link = fish_link;
+ vfs_fish_ops->unlink = fish_unlink;
+ vfs_fish_ops->rename = fish_rename;
+ vfs_fish_ops->mkdir = fish_mkdir;
+ vfs_fish_ops->rmdir = fish_rmdir;
+ vfs_fish_ops->ctl = fish_ctl;
+ fish_subclass.archive_same = fish_archive_same;
+ fish_subclass.new_archive = fish_new_archive;
+ fish_subclass.open_archive = fish_open_archive;
+ fish_subclass.free_archive = fish_free_archive;
+ fish_subclass.fh_new = fish_fh_new;
+ fish_subclass.fh_open = fish_fh_open;
+ fish_subclass.dir_load = fish_dir_load;
+ fish_subclass.file_store = fish_file_store;
+ fish_subclass.linear_start = fish_linear_start;
+ fish_subclass.linear_read = fish_linear_read;
+ fish_subclass.linear_close = fish_linear_close;
+ vfs_register_class (vfs_fish_ops);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/fish/fish.h b/src/vfs/fish/fish.h
new file mode 100644
index 0000000..3c1fa06
--- /dev/null
+++ b/src/vfs/fish/fish.h
@@ -0,0 +1,28 @@
+
+/**
+ * \file
+ * \brief Header: Virtual File System: FISH implementation for transferring files over
+ * shell connections
+ */
+
+
+#ifndef MC__VFS_FISH_H
+#define MC__VFS_FISH_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+extern int fish_directory_timeout;
+
+/*** declarations of public functions ************************************************************/
+
+void vfs_init_fish (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif
diff --git a/src/vfs/fish/fishdef.h b/src/vfs/fish/fishdef.h
new file mode 100644
index 0000000..129d2b9
--- /dev/null
+++ b/src/vfs/fish/fishdef.h
@@ -0,0 +1,236 @@
+
+/**
+ * \file
+ * \brief Header: FISH script defaults
+ */
+
+#ifndef MC__FISH_DEF_H
+#define MC__FISH_DEF_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/* default 'ls' script */
+#define FISH_LS_DEF_CONTENT "" \
+"#LIST /${FISH_FILENAME}\n" \
+"export LC_TIME=C\n" \
+"ls -Qlan \"/${FISH_FILENAME}\" 2>/dev/null | grep '^[^cbt]' | (\n" \
+"while read p l u g s m d y n; do\n" \
+" echo \"P$p $u.$g\"\n" \
+" echo \"S$s\"\n" \
+" echo \"d$m $d $y\"\n" \
+" echo \":$n\"\n" \
+" echo\n" \
+"done\n" \
+")\n" \
+"ls -Qlan \"/${FISH_FILENAME}\" 2>/dev/null | grep '^[cb]' | (\n" \
+"while read p l u g a i m d y n; do\n" \
+" echo \"P$p $u.$g\"\n" \
+" echo \"E$a$i\"\n" \
+" echo \"d$m $d $y\"\n" \
+" echo \":$n\"\n" \
+" echo\n" \
+"done\n" \
+")\n" \
+"echo \"### 200\"\n"
+
+/* default file exists script */
+#define FISH_EXISTS_DEF_CONTENT "" \
+"#ISEXISTS $FISH_FILENAME\n" \
+"ls -l \"/${FISH_FILENAME}\" >/dev/null 2>/dev/null\n" \
+"echo '### '$?\n"
+
+/* default 'mkdir' script */
+#define FISH_MKDIR_DEF_CONTENT "" \
+"#MKD $FISH_FILENAME\n" \
+"if mkdir \"/${FISH_FILENAME}\" 2>/dev/null; then\n" \
+" echo \"### 000\"\n" \
+"else\n" \
+" echo \"### 500\"\n" \
+"fi\n"
+
+/* default 'unlink' script */
+#define FISH_UNLINK_DEF_CONTENT "" \
+"#DELE $FISH_FILENAME\n" \
+"if rm -f \"/${FISH_FILENAME}\" 2>/dev/null; then\n" \
+" echo \"### 000\"\n" \
+"else\n" \
+" echo \"### 500\"\n" \
+"fi\n"
+/* default 'chown' script */
+#define FISH_CHOWN_DEF_CONTENT "" \
+"#CHOWN $FISH_FILEOWNER:$FISH_FILEGROUP $FISH_FILENAME\n" \
+"if chown ${FISH_FILEOWNER}:${FISH_FILEGROUP} \"/${FISH_FILENAME}\"; then\n"\
+" echo \"### 000\"\n" \
+"else\n" \
+" echo \"### 500\"\n" \
+"fi\n"
+
+/* default 'chmod' script */
+#define FISH_CHMOD_DEF_CONTENT "" \
+"#CHMOD $FISH_FILEMODE $FISH_FILENAME\n" \
+"if chmod ${FISH_FILEMODE} \"/${FISH_FILENAME}\" 2>/dev/null; then\n" \
+" echo \"### 000\"\n" \
+"else\n" \
+" echo \"### 500\"\n" \
+"fi\n"
+
+/* default 'utime' script */
+#define FISH_UTIME_DEF_CONTENT "" \
+"#UTIME \"$FISH_TOUCHATIME_W_NSEC\" \"$FISH_TOUCHMTIME_W_NSEC\" $FISH_FILENAME\n" \
+"if TZ=UTC touch -h -m -d \"$FISH_TOUCHMTIME_W_NSEC\" \"/${FISH_FILENAME}\" 2>/dev/null && \\\n" \
+" TZ=UTC touch -h -a -d \"$FISH_TOUCHATIME_W_NSEC\" \"/${FISH_FILENAME}\" 2>/dev/null; then\n" \
+" echo \"### 000\"\n" \
+"elif TZ=UTC touch -h -m -t $FISH_TOUCHMTIME \"/${FISH_FILENAME}\" 2>/dev/null && \\\n" \
+" TZ=UTC touch -h -a -t $FISH_TOUCHATIME \"/${FISH_FILENAME}\" 2>/dev/null; then\n" \
+" echo \"### 000\"\n" \
+"elif [ -n \"$FISH_HAVE_PERL\" ] && \\\n" \
+" perl -e 'utime '$FISH_FILEATIME','$FISH_FILEMTIME',@ARGV;' \"/${FISH_FILENAME}\" 2>/dev/null; then\n" \
+" echo \"### 000\"\n" \
+"else\n" \
+" echo \"### 500\"\n" \
+"fi\n"
+
+
+/* default 'rmdir' script */
+#define FISH_RMDIR_DEF_CONTENT "" \
+"#RMD $FISH_FILENAME\n" \
+"if rmdir \"/${FISH_FILENAME}\" 2>/dev/null; then\n" \
+" echo \"### 000\"\n" \
+"else\n" \
+" echo \"### 500\"\n" \
+"fi\n"
+
+/* default 'ln -s' symlink script */
+#define FISH_LN_DEF_CONTENT "" \
+"#SYMLINK $FISH_FILEFROM $FISH_FILETO\n" \
+"if ln -s \"/${FISH_FILEFROM}\" \"/${FISH_FILETO}\" 2>/dev/null; then\n" \
+" echo \"### 000\"\n" \
+"else\n" \
+" echo \"### 500\"\n" \
+"fi\n"
+
+/* default 'mv' script */
+#define FISH_MV_DEF_CONTENT "" \
+"#RENAME $FISH_FILEFROM $FISH_FILETO\n" \
+"if mv \"/${FISH_FILEFROM}\" \"/${FISH_FILETO}\" 2>/dev/null; then\n" \
+" echo \"### 000\"\n" \
+"else\n" \
+" echo \"### 500\"\n" \
+"fi\n"
+
+/* default 'ln' hardlink script */
+#define FISH_HARDLINK_DEF_CONTENT "" \
+"#LINK $FISH_FILEFROM $FISH_FILETO\n" \
+"if ln \"/${FISH_FILEFROM}\" \"/${FISH_FILETO}\" 2>/dev/null; then\n" \
+" echo \"### 000\"\n" \
+"else\n" \
+" echo \"### 500\"\n" \
+"fi\n"
+
+/* default 'retr' script */
+#define FISH_GET_DEF_CONTENT "" \
+"export LC_TIME=C\n" \
+"#RETR $FISH_FILENAME\n" \
+"if dd if=\"/${FISH_FILENAME}\" of=/dev/null bs=1 count=1 2>/dev/null ; then\n" \
+" ls -ln \"/${FISH_FILENAME}\" 2>/dev/null | (\n" \
+" read p l u g s r\n" \
+" echo $s\n" \
+" )\n" \
+" echo \"### 100\"\n" \
+" cat \"/${FISH_FILENAME}\"\n" \
+" echo \"### 200\"\n" \
+"else\n" \
+" echo \"### 500\"\n" \
+"fi\n"
+
+/* default 'stor' script */
+#define FISH_SEND_DEF_CONTENT "" \
+"FILENAME=\"/${FISH_FILENAME}\"\n" \
+"FILESIZE=${FISH_FILESIZE}\n" \
+"#STOR $FILESIZE $FILENAME\n" \
+"echo \"### 001\"\n" \
+"{\n" \
+" while [ $FILESIZE -gt 0 ]; do\n" \
+" cnt=`expr \\( $FILESIZE + 255 \\) / 256`\n" \
+" n=`dd bs=256 count=$cnt | tee -a \"${FILENAME}\" | wc -c`\n" \
+" FILESIZE=`expr $FILESIZE - $n`\n" \
+" done\n" \
+"}; echo \"### 200\"\n"
+
+/* default 'appe' script */
+#define FISH_APPEND_DEF_CONTENT "" \
+"FILENAME=\"/${FISH_FILENAME}\"\n" \
+"FILESIZE=${FISH_FILESIZE}\n" \
+"#APPE $FILESIZE $FILENAME\n" \
+"echo \"### 001\"\n" \
+"res=`exec 3>&1\n" \
+"(\n" \
+" head -c $FILESIZE -q - || echo DD >&3\n" \
+") 2>/dev/null | (\n" \
+" cat > \"${FILENAME}\"\n" \
+" cat > /dev/null\n" \
+")`; [ \"$res\" = DD ] && {\n" \
+" > \"${FILENAME}\"\n" \
+" while [ $FILESIZE -gt 0 ]\n" \
+" do\n" \
+" cnt=`expr \\( $FILESIZE + 255 \\) / 256`\n" \
+" n=`dd bs=256 count=$cnt | tee -a \"${FILENAME}\" | wc -c`\n" \
+" FILESIZE=`expr $FILESIZE - $n`\n" \
+" done\n" \
+"}; echo \"### 200\"\n"
+
+/* default 'info' script */
+#define FISH_INFO_DEF_CONTENT "" \
+"export LC_TIME=C\n" \
+"#FISH_HAVE_HEAD 1\n" \
+"#FISH_HAVE_SED 2\n" \
+"#FISH_HAVE_AWK 4\n" \
+"#FISH_HAVE_PERL 8\n" \
+"#FISH_HAVE_LSQ 16\n" \
+"#FISH_HAVE_DATE_MDYT 32\n" \
+"#FISH_HAVE_TAIL 64\n" \
+"res=0\n" \
+"if `echo yes| head -c 1 > /dev/null 2>&1` ; then\n" \
+" res=`expr $res + 1`\n" \
+"fi\n" \
+"if `sed --version >/dev/null 2>&1` ; then\n" \
+" res=`expr $res + 2`\n" \
+"fi\n" \
+"if `awk --version > /dev/null 2>&1` ; then\n" \
+" res=`expr $res + 4`\n" \
+"fi\n" \
+"if `perl -v > /dev/null 2>&1` ; then\n" \
+" res=`expr $res + 8`\n" \
+"fi\n" \
+"if `ls -Q / >/dev/null 2>&1` ; then\n" \
+" res=`expr $res + 16`\n" \
+"fi\n" \
+"dat=`ls -lan / 2>/dev/null | head -n 3 | tail -n 1 | (\n" \
+" while read p l u g s rec; do\n" \
+" if [ -n \"$g\" ]; then\n" \
+" if [ -n \"$l\" ]; then\n" \
+" echo \"$rec\"\n" \
+" fi\n" \
+" fi\n" \
+" done\n" \
+") | cut -c1 2>/dev/null`\n" \
+"r=`echo \"0123456789\"| grep \"$dat\"`\n" \
+"if [ -z \"$r\" ]; then\n" \
+" res=`expr $res + 32`\n" \
+"fi\n" \
+"if `echo yes| tail -c +1 - > /dev/null 2>&1` ; then\n" \
+" res=`expr $res + 64`\n" \
+"fi\n" \
+"echo $res\n" \
+"echo \"### 200\"\n"
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/*** inline functions ****************************************************************************/
+#endif
diff --git a/src/vfs/fish/helpers/Makefile.am b/src/vfs/fish/helpers/Makefile.am
new file mode 100644
index 0000000..e3ba15d
--- /dev/null
+++ b/src/vfs/fish/helpers/Makefile.am
@@ -0,0 +1,10 @@
+fishdir = $(libexecdir)/@PACKAGE@/fish
+
+# Files to install and distribute other than fish scripts
+FISH_MISC = README.fish
+
+# Install and distribute FISH helper scripts w/o shebang & executable bit as data
+fish_DATA = $(FISH_MISC) ls mkdir fexists unlink chown chmod rmdir ln mv hardlink get send append info utime
+fishconfdir = $(sysconfdir)/@PACKAGE@
+
+EXTRA_DIST = $(fish_DATA)
diff --git a/src/vfs/fish/helpers/Makefile.in b/src/vfs/fish/helpers/Makefile.in
new file mode 100644
index 0000000..c17efbb
--- /dev/null
+++ b/src/vfs/fish/helpers/Makefile.in
@@ -0,0 +1,642 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/vfs/fish/helpers
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(fishdir)"
+DATA = $(fish_DATA)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+am__DIST_COMMON = $(srcdir)/Makefile.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+fishdir = $(libexecdir)/@PACKAGE@/fish
+
+# Files to install and distribute other than fish scripts
+FISH_MISC = README.fish
+
+# Install and distribute FISH helper scripts w/o shebang & executable bit as data
+fish_DATA = $(FISH_MISC) ls mkdir fexists unlink chown chmod rmdir ln mv hardlink get send append info utime
+fishconfdir = $(sysconfdir)/@PACKAGE@
+EXTRA_DIST = $(fish_DATA)
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/vfs/fish/helpers/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/fish/helpers/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-fishDATA: $(fish_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(fish_DATA)'; test -n "$(fishdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(fishdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(fishdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(fishdir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(fishdir)" || exit $$?; \
+ done
+
+uninstall-fishDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(fish_DATA)'; test -n "$(fishdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(fishdir)'; $(am__uninstall_files_from_dir)
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(DATA)
+installdirs:
+ for dir in "$(DESTDIR)$(fishdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-fishDATA
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-fishDATA
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-generic clean-libtool \
+ cscopelist-am ctags-am distclean distclean-generic \
+ distclean-libtool distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-fishDATA \
+ install-html install-html-am install-info install-info-am \
+ install-man install-pdf install-pdf-am install-ps \
+ install-ps-am install-strip installcheck installcheck-am \
+ installdirs maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \
+ ps ps-am tags-am uninstall uninstall-am uninstall-fishDATA
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/vfs/fish/helpers/README.fish b/src/vfs/fish/helpers/README.fish
new file mode 100644
index 0000000..ac319c8
--- /dev/null
+++ b/src/vfs/fish/helpers/README.fish
@@ -0,0 +1,217 @@
+
+ FIles transferred over SHell protocol (V 0.0.3)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This protocol was designed for transferring files over a remote shell
+connection (rsh and compatibles). It can be as well used for transfers over
+rsh, and there may be other uses.
+
+Client sends requests of following form:
+
+#FISH_COMMAND
+equivalent shell commands,
+which may be multiline
+
+Only fish commands are defined here, shell equivalents are for your
+information only and will probably vary from implementation to
+implementation. Fish commands always have priority: server is
+expected to execute fish command if it understands it. If it does not,
+however, it can try the luck and execute shell command.
+
+Since version 4.7.3, the scripts that FISH sends to host machines after
+a command is transmitted are no longer hardwired in the Midnight
+Commander source code.
+
+First, mc looks for system-wide set of scripts, then it checks whether
+current user has host-specific overrides in his per-user mc
+configuration directory. User-defined overrides take priority over
+sytem-wide scripts if they exist. The order in which the directories are
+traversed is as follows:
+
+ /usr/libexec/mc/fish
+ ~/.local/share/mc/fish/<hostname>/
+
+Server's reply is multiline, but always ends with
+
+### 000<optional text>
+
+line. ### is prefix to mark this line, 000 is return code. Return
+codes are superset to those used in ftp.
+
+There are few new exit codes defined:
+
+000 don't know; if there were no previous lines, this marks COMPLETE
+success, if they were, it marks failure.
+
+001 don't know; if there were no previous lines, this marks
+PRELIMinary success, if they were, it marks failure
+
+ Connecting
+ ~~~~~~~~~~
+Client uses "echo FISH:;/bin/sh" as command executed on remote
+machine. This should make it possible for server to distinguish FISH
+connections from normal rsh/ssh.
+
+ Commands
+ ~~~~~~~~
+#FISH
+echo; start_fish_server; echo '### 200'
+
+This command is sent at the beginning. It marks that client wishes to
+talk via FISH protocol. #VER command must follow. If server
+understands FISH protocol, it has option to put FISH server somewhere
+on system path and name it start_fish_server.
+
+#VER 0.0.2 <feature1> <feature2> <...>
+echo '### 000'
+
+This command is the second one. It sends client version and extensions
+to the server. Server should reply with protocol version to be used,
+and list of extensions accepted.
+
+VER 0.0.0 <feature2>
+### 200
+
+#PWD
+pwd; echo '### 200'
+
+Server should reply with current directory (in form /abc/def/ghi)
+followed by line indicating success.
+
+#LIST /directory
+ls -lLa $1 | grep '^[^cbt]' | ( while read p x u g s m d y n; do echo "P$p $u.$g
+S$s
+d$m $d $y
+:$n
+"; done )
+ls -lLa $1 | grep '^[cb]' | ( while read p x u g a i m d y n; do echo "P$p $u.$g
+E$a$i
+dD$m $d $y
+:$n
+"; done )
+echo '### 200'
+
+This allows client to list directory or get status information about
+single file. Output is in following form (any line except :<filename>
+may be omitted):
+
+P<unix permissions> <owner>.<group>
+S<size>
+d<3-letters month name> <day> <year or HH:MM>
+D<year> <month> <day> <hour> <minute> <second>[.1234]
+E<major-of-device>,<minor>
+:<filename>
+L<filename symlink points to>
+<blank line to separate items>
+
+Unix permissions are of form X--------- where X is type of
+file. Currently, '-' means regular file, 'd' means directory, 'c', 'b'
+means character and block device, 'l' means symbolic link, 'p' means
+FIFO and 's' means socket.
+
+'d' has three fields: month (one of strings Jan Feb Mar Apr May Jun
+Jul Aug Sep Oct Nov Dec), day of month, and third is either single
+number indicating year, or HH:MM field (assume current year in such
+case). As you've probably noticed, this is pretty broken; it is for
+compatibility with ls listing.
+
+#RETR /some/name
+ls -l /some/name | ( read a b c d x e; echo $x ); echo '### 100'; cat /some/name; echo '### 200'
+
+Server sends line with filesize on it, followed by line with ### 100
+indicating partial success, then it sends binary data (exactly
+filesize bytes) and follows them with (with no preceding newline) ###
+200.
+
+Note that there's no way to abort running RETR command - except
+closing the connection.
+
+#STOR <size> /file/name
+> /file/name; echo '### 001'; ( dd bs=4096 count=<size/4096>; dd bs=<size%4096> count=1 ) 2>/dev/null | ( cat > %s; cat > /dev/null ); echo '### 200'
+
+This command is for storing /file/name, which is exactly size bytes
+big. You probably think I went crazy. Well, I did not: that strange
+cat > /dev/null has purpose to discard any extra data which was not
+written to disk (due to for example out of space condition).
+
+[Why? Imagine uploading file with "rm -rf /" line in it.]
+
+#CWD /somewhere
+cd /somewhere; echo '### 000'
+
+It is specified here, but I'm not sure how wise idea is to use this
+one: it breaks stateless-ness of the protocol.
+
+Following commands should be rather self-explanatory:
+
+#CHMOD 1234 file
+chmod 1234 file; echo '### 000'
+
+#DELE /some/path
+rm -f /some/path; echo '### 000'
+
+#MKD /some/path
+mkdir /some/path; echo '### 000'
+
+#RMD /some/path
+rmdir /some/path; echo '### 000'
+
+#RENAME /path/a /path/b
+mv /path/a /path/b; echo '### 000'
+
+#LINK /path/a /path/b
+ln /path/a /path/b; echo '### 000'
+
+#SYMLINK /path/a /path/b
+ln -s /path/a /path/b; echo '### 000'
+
+#CHOWN user /file/name
+chown user /file/name; echo '### 000'
+
+#CHGRP group /file/name
+chgrp group /file/name; echo '### 000'
+
+#INFO
+...collect info about host into $result ...
+echo $result
+echo '### 200'
+
+#READ <offset> <size> /path/and/filename
+cat /path/and/filename | ( dd bs=4096 count=<offset/4096> > /dev/null;
+dd bs=<offset%4096> count=1 > /dev/null;
+dd bs=4096 count=<offset/4096>;
+dd bs=<offset%4096> count=1; )
+
+Returns ### 200 on successful exit, ### 291 on successful exit when
+reading ended at eof, ### 292 on successful exit when reading did not
+end at eof.
+
+#WRITE <offset> <size> /path/and/filename
+
+Hmm, shall we define these ones if we know our client is not going to
+use them?
+
+you can use follow parameters:
+FISH_FILESIZE
+FISH_FILENAME
+FISH_FILEMODE
+FISH_FILEOWNER
+FISH_FILEGROUPE
+FISH_FILEFROM
+FISH_FILETO
+
+NB:
+'FISH_FILESIZE' used if we operate with single file name in 'unlink', 'rmdir', 'chmod', etc...
+'FISH_FILEFROM','FISH_FILETO' used if we operate with two files in 'ln', 'hardlink', 'mv' etc...
+'FISH_FILEOWNER', 'FISH_FILEGROUPE' is a new user/group in chown
+
+also flags:
+FISH_HAVE_HEAD
+FISH_HAVE_SED
+FISH_HAVE_AWK
+FISH_HAVE_PERL
+FISH_HAVE_LSQ
+FISH_HAVE_DATE_MDYT
+
+That's all, folks!
+ pavel@ucw.cz
diff --git a/src/vfs/fish/helpers/append b/src/vfs/fish/helpers/append
new file mode 100644
index 0000000..81ded44
--- /dev/null
+++ b/src/vfs/fish/helpers/append
@@ -0,0 +1,16 @@
+#APPE $FISH_FILESIZE $FISH_FILENAME
+FILENAME="/${FISH_FILENAME}"
+echo "### 001"
+{
+ bss=4096
+ bsl=4095
+ if [ $FISH_FILESIZE -lt $bss ]; then
+ bss=1;
+ bsl=0;
+ fi
+ while [ $FISH_FILESIZE -gt 0 ]; do
+ cnt=`expr \\( $FISH_FILESIZE + $bsl \\) / $bss`
+ n=`dd bs=$bss count=$cnt | tee -a "${FILENAME}" | wc -c`
+ FISH_FILESIZE=`expr $FISH_FILESIZE - $n`
+ done
+}; echo "### 200"
diff --git a/src/vfs/fish/helpers/chmod b/src/vfs/fish/helpers/chmod
new file mode 100644
index 0000000..a5a88b4
--- /dev/null
+++ b/src/vfs/fish/helpers/chmod
@@ -0,0 +1,6 @@
+#CHMOD $FISH_FILEMODE $FISH_FILENAME
+if chmod ${FISH_FILEMODE} "/${FISH_FILENAME}" 2>/dev/null; then
+ echo "### 000"
+else
+ echo "### 500"
+fi
diff --git a/src/vfs/fish/helpers/chown b/src/vfs/fish/helpers/chown
new file mode 100644
index 0000000..469fdc1
--- /dev/null
+++ b/src/vfs/fish/helpers/chown
@@ -0,0 +1,6 @@
+#CHOWN $FISH_FILEOWNER:$FISH_FILEGROUP $FISH_FILENAME
+if chown ${FISH_FILEOWNER}:${FISH_FILEGROUP} "/${FISH_FILENAME}" ; then
+ echo "### 000"
+else
+ echo "### 500"
+fi
diff --git a/src/vfs/fish/helpers/fexists b/src/vfs/fish/helpers/fexists
new file mode 100644
index 0000000..cf03b15
--- /dev/null
+++ b/src/vfs/fish/helpers/fexists
@@ -0,0 +1,3 @@
+#ISEXISTS $FISH_FILENAME
+ls -l "/${FISH_FILENAME}" >/dev/null 2>/dev/null
+echo '### '$?
diff --git a/src/vfs/fish/helpers/get b/src/vfs/fish/helpers/get
new file mode 100644
index 0000000..762267a
--- /dev/null
+++ b/src/vfs/fish/helpers/get
@@ -0,0 +1,105 @@
+#RETR $FISH_FILENAME $FISH_START_OFFSET
+LC_TIME=C
+export LC_TIME
+fish_get_perl ()
+{
+FILENAME=$1
+OFFSET=$2
+perl -e '
+use strict;
+use POSIX;
+use Fcntl;
+my $filename = $ARGV[0];
+my $pos = $ARGV[1];
+my $content;
+my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = lstat("$filename");
+my $n;
+if (open IFILE,$filename) {
+ if ($size<$pos) {
+ printf("0\n");
+ } else {
+ $size-=$pos;
+ printf("$size\n");
+ }
+ printf("### 100\n");
+ seek (IFILE, $pos, 0);
+ while ($n = read(IFILE,$content,$blksize)!= 0) {
+ print $content;
+ }
+ close IFILE;
+ printf("### 200\n");
+} else {
+ printf("### 500\n");
+}
+exit 0
+' "${FILENAME}" $OFFSET
+}
+
+fish_get_tail ()
+{
+FILENAME=$1
+OFFSET=$2
+LC_TIME=C
+export LC_TIME
+if dd if="${FILENAME}" of=/dev/null bs=1 count=1 2>/dev/null ; then
+ file_size=`ls -ln "${FILENAME}" 2>/dev/null | (
+ read p l u g s r
+ echo $s
+ )`
+ if [ $OFFSET -gt 0 ]; then
+ file_size=`expr $file_size - $OFFSET`
+ OFFSET=`expr $OFFSET + 1`
+ fi
+ if [ $file_size -gt 0 ]; then
+ echo $file_size
+ else
+ echo 0
+ fi
+ echo "### 100"
+ if [ $OFFSET -gt 0 ]; then
+ tail -c +${OFFSET} "${FILENAME}"
+ else
+ cat "${FILENAME}"
+ fi
+ echo "### 200"
+else
+ echo "### 500"
+fi
+}
+
+fish_get_dd ()
+{
+FILENAME=$1
+OFFSET=$2
+LC_TIME=C
+export LC_TIME
+if dd if="${FILENAME}" of=/dev/null bs=1 count=1 2>/dev/null ; then
+ file_size=`ls -ln "${FILENAME}" 2>/dev/null | (
+ read p l u g s r
+ echo $s
+ )`
+ file_size=`expr $file_size - $OFFSET`
+ if [ $file_size -gt 0 ]; then
+ echo $file_size
+ else
+ echo 0
+ fi
+ echo "### 100"
+ if [ $OFFSET -gt 0 ]; then
+ dd skip=$OFFSET ibs=1 if="${FILENAME}" 2>/dev/null
+ else
+ cat "${FILENAME}"
+ fi
+ echo "### 200"
+else
+ echo "### 500"
+fi
+}
+
+if [ -n "${FISH_HAVE_PERL}" ]; then
+ fish_get_perl "/${FISH_FILENAME}" ${FISH_START_OFFSET}
+elif [ -n "${FISH_HAVE_TAIL}" ]; then
+ fish_get_tail "/${FISH_FILENAME}" ${FISH_START_OFFSET}
+else
+ fish_get_dd "/${FISH_FILENAME}" ${FISH_START_OFFSET}
+fi
diff --git a/src/vfs/fish/helpers/hardlink b/src/vfs/fish/helpers/hardlink
new file mode 100644
index 0000000..4f36b3f
--- /dev/null
+++ b/src/vfs/fish/helpers/hardlink
@@ -0,0 +1,8 @@
+#LINK $FISH_FILEFROM $FISH_FILETO
+FILEFROM="/${FISH_FILEFROM}"
+FILETO="/${FISH_FILETO}"
+if ln "${FILEFROM}" "${FILETO}" 2>/dev/null; then
+ echo "### 000"
+else
+ echo "### 500"
+fi
diff --git a/src/vfs/fish/helpers/info b/src/vfs/fish/helpers/info
new file mode 100644
index 0000000..b85b0a7
--- /dev/null
+++ b/src/vfs/fish/helpers/info
@@ -0,0 +1,44 @@
+LC_TIME=C
+export LC_TIME
+#FISH_HAVE_HEAD 1
+#FISH_HAVE_SED 2
+#FISH_HAVE_AWK 4
+#FISH_HAVE_PERL 8
+#FISH_HAVE_LSQ 16
+#FISH_HAVE_DATE_MDYT 32
+#FISH_HAVE_TAIL 64
+res=0
+if `echo yes| head -c 1 > /dev/null 2>&1` ; then
+ res=`expr $res + 1`
+fi
+if `echo 1 | sed 's/1/2/' >/dev/null 2>&1` ; then
+ res=`expr $res + 2`
+fi
+if `echo 1| awk '{print}' > /dev/null 2>&1` ; then
+ res=`expr $res + 4`
+fi
+if `perl -v > /dev/null 2>&1` ; then
+ res=`expr $res + 8`
+fi
+if `ls -Q / >/dev/null 2>&1` ; then
+ res=`expr $res + 16`
+fi
+dat=`ls -lan / 2>/dev/null | head -n 3 | (
+ while read p l u g s rec; do
+ if [ -n "$g" ]; then
+ if [ -n "$l" ]; then
+ echo "$rec"
+ fi
+ fi
+ done
+)`
+dat=`echo $dat | cut -c1 2>/dev/null`
+r=`echo "0123456789"| grep "$dat"`
+if [ -z "$r" ]; then
+ res=`expr $res + 32`
+fi
+if `echo yes| tail -c +1 - > /dev/null 2>&1` ; then
+ res=`expr $res + 64`
+fi
+echo $res
+echo "### 200"
diff --git a/src/vfs/fish/helpers/ln b/src/vfs/fish/helpers/ln
new file mode 100644
index 0000000..a8445d8
--- /dev/null
+++ b/src/vfs/fish/helpers/ln
@@ -0,0 +1,8 @@
+#SYMLINK $FISH_FILEFROM $FISH_FILETO
+FILEFROM="${FISH_FILEFROM}"
+FILETO="/${FISH_FILETO}"
+if ln -s "${FILEFROM}" "${FILETO}" 2>/dev/null; then
+ echo "### 000"
+else
+ echo "### 500"
+fi
diff --git a/src/vfs/fish/helpers/ls b/src/vfs/fish/helpers/ls
new file mode 100644
index 0000000..7165b51
--- /dev/null
+++ b/src/vfs/fish/helpers/ls
@@ -0,0 +1,170 @@
+#LIST /${FISH_DIR}
+LC_TIME=C
+export LC_TIME
+perl_res="1"
+fish_list_lsq ()
+{
+FISH_DIR="$1"
+ls -Qlan "${FISH_DIR}" 2>/dev/null | grep '^[^cbt]' | (
+while read p l u g s m d y n; do
+ echo "P$p $u.$g"
+ echo "S$s"
+ echo "d$m $d $y"
+ echo ":$n"
+ echo
+done
+)
+
+ls -Qlan "${FISH_DIR}" 2>/dev/null | grep '^[cb]' | (
+while read p l u g a i m d y n; do
+ echo "P$p $u.$g"
+ echo "E$a$i"
+ echo "d$m $d $y"
+ echo ":$n"
+ echo
+done
+)
+echo '### 200'
+}
+
+fish_list_sed ()
+{
+FISH_DIR="$1"
+ls -lan "${FISH_DIR}" 2>/dev/null | grep '^[^cbt]' | (
+while read p l u g s rec; do
+ if [ -n "$g" ]; then
+ if [ -n "$FISH_HAVE_DATE_MDYT" ]; then
+ filename=`echo "$rec"| sed 's/[^[:space:]]\+ \+[^[:space:]]\+ \+[^[:space:]]\+ //'`
+ filedate=`echo "$rec"| sed 's/\([^[:space:]]\+ \+[^[:space:]]\+ \+[^[:space:]]\+\) .*/\1/'`
+ else
+ filename=`echo "$rec"| sed 's/[^[:space:]]\+ \+[^[:space:]]\+ //'`
+ filedate=`echo "$rec"| sed 's/\([^[:space:]]\+ \+[^[:space:]]\+\) .*/\1/'`
+ fi
+ pfile=\"`echo "$filename" | sed -e 's#^\(.*\) -> \(.*\)#\1" -> "\2#'`\"
+ echo "P$p $u.$g"
+ echo "S$s"
+ if [ -n "$FISH_HAVE_DATE_MDYT" ]; then
+ echo "d$filedate"
+ else
+ echo "D$filedate"
+ fi
+ echo ":$pfile"
+ echo
+ fi
+done
+)
+ls -lan "${FISH_DIR}" 2>/dev/null | grep '^[cb]' | (
+while read p l u g a i rec; do
+ if [ -n "$g" ]; then
+ if [ -n "$FISH_HAVE_DATE_MDYT" ]; then
+ filename=`echo "$rec"| sed 's/[^[:space:]]\+ \+[^[:space:]]\+ \+[^[:space:]]\+ //'`
+ filedate=`echo "$rec"| sed 's/\([^[:space:]]\+ \+[^[:space:]]\+ \+[^[:space:]]\+\) .*/\1/'`
+ else
+ filename=`echo "$rec"| sed 's/[^[:space:]]\+ \+[^[:space:]]\+ //'`
+ filedate=`echo "$rec"| sed 's/\([^[:space:]]\+ \+[^[:space:]]\+\) .*/\1/'`
+ fi
+ pfile=\"`echo "$filename" | sed -e 's#^\(.*\) -> \(.*\)#\1" -> "\2#'`\"
+ echo "P$p $u.$g"
+ echo "E$a$i"
+ if [ -n "$FISH_HAVE_DATE_MDYT" ]; then
+ echo "d$filedate"
+ else
+ echo "D$filedate"
+ fi
+ echo ":$pfile"
+ echo
+ fi
+done
+)
+echo '### 200'
+}
+
+fish_list_poor_ls ()
+{
+FISH_DIR="$1"
+ls -lan "${FISH_DIR}" 2>/dev/null | grep '^[^cbt]' | (
+while read p l u g s m d y n n2 n3; do
+ if [ -n "$g" ]; then
+ if [ "$m" = "0" ]; then
+ s=$d; m=$y; d=$n; y=$n2; n=$n3
+ else
+ n=$n" "$n2" "$n3
+ fi
+ echo "P$p $u $g"
+ echo "S$s"
+ echo "d$m $d $y"
+ echo ":"$n
+ echo
+ fi
+done
+)
+ls -lan "${FISH_DIR}" 2>/dev/null | grep '^[cb]' | (
+while read p l u g a i m d y n n2 n3; do
+ if [ -n "$g" ]; then
+ if [ "$a" = "0" ]; then
+ a=$m; i=$d; m=$y; d=$n; y=$n2; n=$n3
+ else
+ n=$n" "$n2" "$n3
+ fi
+ echo "P$p $u $g"
+ echo "E$a$i"
+ echo "d$m $d $y"
+ echo ":"$n
+ echo
+ fi
+done
+)
+echo '### 200'
+}
+
+fish_list_perl ()
+{
+FISH_DIR=$1
+perl -e '
+use strict;
+use POSIX;
+use Fcntl;
+use POSIX ":fcntl_h"; #S_ISLNK was here until 5.6
+import Fcntl ":mode" unless defined &S_ISLNK; #and is now here
+my $dirname = $ARGV[0];
+if (opendir (DIR, $dirname)) {
+while((my $filename = readdir (DIR))){
+ my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = lstat("$dirname/$filename");
+ my $mloctime= strftime("%m-%d-%Y %H:%M", localtime $mtime);
+ my $strutils_shell_escape_regex = s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'\''"\ \\])/\\$1/g;
+ my $e_filename = $filename;
+ $e_filename =~ $strutils_shell_escape_regex;
+ if (S_ISLNK ($mode)) {
+ my $linkname = readlink ("$dirname/$filename");
+ $linkname =~ $strutils_shell_escape_regex;
+ printf("R%o %o $uid.$gid\nS$size\nd$mloctime\n:\"%s\" -> \"%s\"\n\n", S_IMODE($mode), S_IFMT($mode), $e_filename, $linkname);
+ } elsif (S_ISCHR ($mode) || S_ISBLK ($mode)) {
+ my $minor = $rdev % 256;
+ my $major = int( $rdev / 256 );
+ printf("R%o %o $uid.$gid\nE$major,$minor\nd$mloctime\n:\"%s\"\n\n", S_IMODE($mode), S_IFMT($mode), $e_filename);
+ } else {
+ printf("R%o %o $uid.$gid\nS$size\nd$mloctime\n:\"%s\"\n\n", S_IMODE($mode), S_IFMT($mode), $e_filename);
+ }
+}
+ printf("### 200\n");
+ closedir(DIR);
+} else {
+ printf("### 500\n");
+}
+exit 0
+' "/${FISH_DIR}"
+perl_res=$?
+}
+
+if [ -n "${FISH_HAVE_PERL}" ]; then
+ fish_list_perl "/${FISH_FILENAME}"
+fi
+if [ "${perl_res}" != "0" ]; then
+ if [ -n "${FISH_HAVE_LSQ}" ]; then
+ fish_list_lsq "/${FISH_FILENAME}"
+ elif [ -n "${FISH_HAVE_SED}" ]; then
+ fish_list_sed "/${FISH_FILENAME}"
+ else
+ fish_list_poor_ls "/${FISH_FILENAME}"
+ fi
+fi
diff --git a/src/vfs/fish/helpers/mkdir b/src/vfs/fish/helpers/mkdir
new file mode 100644
index 0000000..b32e995
--- /dev/null
+++ b/src/vfs/fish/helpers/mkdir
@@ -0,0 +1,6 @@
+#MKD $FISH_FILENAME
+if mkdir "/$FISH_FILENAME" 2>/dev/null; then
+ echo "### 000"
+else
+ echo "### 500"
+fi
diff --git a/src/vfs/fish/helpers/mv b/src/vfs/fish/helpers/mv
new file mode 100644
index 0000000..c8cf70c
--- /dev/null
+++ b/src/vfs/fish/helpers/mv
@@ -0,0 +1,6 @@
+#RENAME $FISH_FILEFROM $FISH_FILETO
+if mv "/${FISH_FILEFROM}" "/${FISH_FILETO}" 2>/dev/null; then
+ echo "### 000"
+else
+ echo "### 500"
+fi
diff --git a/src/vfs/fish/helpers/rmdir b/src/vfs/fish/helpers/rmdir
new file mode 100644
index 0000000..0f99bf6
--- /dev/null
+++ b/src/vfs/fish/helpers/rmdir
@@ -0,0 +1,6 @@
+#RMD $FISH_FILENAME
+if rmdir "/${FISH_FILENAME}" 2>/dev/null; then
+ echo "### 000"
+else
+ echo "### 500"
+fi
diff --git a/src/vfs/fish/helpers/send b/src/vfs/fish/helpers/send
new file mode 100644
index 0000000..80dd22b
--- /dev/null
+++ b/src/vfs/fish/helpers/send
@@ -0,0 +1,17 @@
+#STOR $FISH_FILESIZE $FISH_FILENAME
+FILENAME="/${FISH_FILENAME}"
+echo "### 001"
+{
+ > "${FILENAME}"
+ bss=4096
+ bsl=4095
+ if [ $FISH_FILESIZE -lt $bss ]; then
+ bss=1;
+ bsl=0;
+ fi
+ while [ $FISH_FILESIZE -gt 0 ]; do
+ cnt=`expr \\( $FISH_FILESIZE + $bsl \\) / $bss`
+ n=`dd bs=$bss count=$cnt | tee -a "${FILENAME}" | wc -c`
+ FISH_FILESIZE=`expr $FISH_FILESIZE - $n`
+ done
+}; echo "### 200"
diff --git a/src/vfs/fish/helpers/unlink b/src/vfs/fish/helpers/unlink
new file mode 100644
index 0000000..79b9ad0
--- /dev/null
+++ b/src/vfs/fish/helpers/unlink
@@ -0,0 +1,6 @@
+#DELE $FISH_FILENAME
+if rm -f "/${FISH_FILENAME}" 2>/dev/null; then
+ echo "### 000"
+else
+ echo "### 500"
+fi
diff --git a/src/vfs/fish/helpers/utime b/src/vfs/fish/helpers/utime
new file mode 100644
index 0000000..94395b4
--- /dev/null
+++ b/src/vfs/fish/helpers/utime
@@ -0,0 +1,13 @@
+#UTIME "$FISH_TOUCHATIME_W_NSEC" "$FISH_TOUCHMTIME_W_NSEC" "$FISH_FILENAME"
+if TZ=UTC touch -h -m -d "$FISH_TOUCHMTIME_W_NSEC" "/${FISH_FILENAME}" 2>/dev/null && \
+ TZ=UTC touch -h -a -d "$FISH_TOUCHATIME_W_NSEC" "/${FISH_FILENAME}" 2>/dev/null; then
+ echo "### 000"
+elif TZ=UTC touch -h -m -t $FISH_TOUCHMTIME "/${FISH_FILENAME}" 2>/dev/null && \
+ TZ=UTC touch -h -a -t $FISH_TOUCHATIME "/${FISH_FILENAME}" 2>/dev/null; then
+ echo "### 000"
+elif [ -n "$FISH_HAVE_PERL" ] &&
+ perl -e 'utime '$FISH_FILEATIME','$FISH_FILEMTIME',@ARGV;' "/${FISH_FILENAME}" 2>/dev/null; then
+ echo "### 000"
+else
+ echo "### 500"
+fi
diff --git a/src/vfs/ftpfs/Makefile.am b/src/vfs/ftpfs/Makefile.am
new file mode 100644
index 0000000..b581563
--- /dev/null
+++ b/src/vfs/ftpfs/Makefile.am
@@ -0,0 +1,8 @@
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+
+noinst_LTLIBRARIES = libvfs-ftpfs.la
+
+libvfs_ftpfs_la_SOURCES = \
+ ftpfs.c ftpfs.h \
+ ftpfs_parse_ls.c \ No newline at end of file
diff --git a/src/vfs/ftpfs/Makefile.in b/src/vfs/ftpfs/Makefile.in
new file mode 100644
index 0000000..e6e561f
--- /dev/null
+++ b/src/vfs/ftpfs/Makefile.in
@@ -0,0 +1,740 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/vfs/ftpfs
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libvfs_ftpfs_la_LIBADD =
+am_libvfs_ftpfs_la_OBJECTS = ftpfs.lo ftpfs_parse_ls.lo
+libvfs_ftpfs_la_OBJECTS = $(am_libvfs_ftpfs_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/ftpfs.Plo \
+ ./$(DEPDIR)/ftpfs_parse_ls.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libvfs_ftpfs_la_SOURCES)
+DIST_SOURCES = $(libvfs_ftpfs_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+noinst_LTLIBRARIES = libvfs-ftpfs.la
+libvfs_ftpfs_la_SOURCES = \
+ ftpfs.c ftpfs.h \
+ ftpfs_parse_ls.c
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/vfs/ftpfs/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/ftpfs/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libvfs-ftpfs.la: $(libvfs_ftpfs_la_OBJECTS) $(libvfs_ftpfs_la_DEPENDENCIES) $(EXTRA_libvfs_ftpfs_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libvfs_ftpfs_la_OBJECTS) $(libvfs_ftpfs_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ftpfs.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ftpfs_parse_ls.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/ftpfs.Plo
+ -rm -f ./$(DEPDIR)/ftpfs_parse_ls.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/ftpfs.Plo
+ -rm -f ./$(DEPDIR)/ftpfs_parse_ls.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/vfs/ftpfs/ftpfs.c b/src/vfs/ftpfs/ftpfs.c
new file mode 100644
index 0000000..549ba32
--- /dev/null
+++ b/src/vfs/ftpfs/ftpfs.c
@@ -0,0 +1,2784 @@
+/*
+ Virtual File System: FTP file system.
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Ching Hui, 1995
+ Jakub Jelinek, 1995
+ Miguel de Icaza, 1995, 1996, 1997
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Yury V. Zaytsev, 2010
+ Slava Zanko <slavazanko@gmail.com>, 2010, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2010-2022
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * \brief Source: Virtual File System: FTP file system
+ * \author Ching Hui
+ * \author Jakub Jelinek
+ * \author Miguel de Icaza
+ * \author Norbert Warmuth
+ * \author Pavel Machek
+ * \date 1995, 1997, 1998
+ *
+ * \todo
+- make it more robust - all the connects etc. should handle EADDRINUSE and
+ ERETRY (have I spelled these names correctly?)
+- make the user able to flush a connection - all the caches will get empty
+ etc., (tarfs as well), we should give there a user selectable timeout
+ and assign a key sequence.
+- use hash table instead of linklist to cache ftpfs directory.
+
+What to do with this?
+
+
+ * NOTE: Usage of tildes is deprecated, consider:
+ * \verbatim
+ cd ftp//:pavel@hobit
+ cd ~
+ \endverbatim
+ * And now: what do I want to do? Do I want to go to /home/pavel or to
+ * ftp://hobit/home/pavel? I think first has better sense...
+ *
+ \verbatim
+ {
+ int f = !strcmp( remote_path, "/~" );
+ if (f || !strncmp( remote_path, "/~/", 3 )) {
+ char *s;
+ s = mc_build_filename ( qhome (*bucket), remote_path +3-f, (char *) NULL );
+ g_free (remote_path);
+ remote_path = s;
+ }
+ }
+ \endverbatim
+ */
+
+/* \todo Fix: Namespace pollution: horrible */
+
+#include <config.h>
+#include <stdio.h> /* sscanf() */
+#include <stdlib.h> /* atoi() */
+#include <sys/types.h> /* POSIX-required by sys/socket.h and netdb.h */
+#include <netdb.h> /* struct hostent */
+#include <sys/socket.h> /* AF_INET */
+#include <netinet/in.h> /* struct in_addr */
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+#include <arpa/ftp.h>
+#include <arpa/telnet.h>
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <inttypes.h> /* uintmax_t */
+
+#include "lib/global.h"
+#include "lib/file-entry.h"
+#include "lib/util.h"
+#include "lib/strutil.h" /* str_move() */
+#include "lib/mcconfig.h"
+
+#include "lib/tty/tty.h" /* enable/disable interrupt key */
+#include "lib/widget.h" /* message() */
+
+#include "src/history.h"
+#include "src/setup.h" /* for load_anon_passwd */
+
+#include "lib/vfs/vfs.h"
+#include "lib/vfs/utilvfs.h"
+#include "lib/vfs/netutil.h"
+#include "lib/vfs/xdirentry.h"
+#include "lib/vfs/gc.h" /* vfs_stamp_create */
+
+#include "ftpfs.h"
+
+/*** global variables ****************************************************************************/
+
+/* Delay to retry a connection */
+int ftpfs_retry_seconds = 30;
+
+/* Method to use to connect to ftp sites */
+gboolean ftpfs_use_passive_connections = TRUE;
+gboolean ftpfs_use_passive_connections_over_proxy = FALSE;
+
+/* Method used to get directory listings:
+ * 1: try 'LIST -la <path>', if it fails
+ * fall back to CWD <path>; LIST
+ * 0: always use CWD <path>; LIST
+ */
+gboolean ftpfs_use_unix_list_options = TRUE;
+
+/* First "CWD <path>", then "LIST -la ." */
+gboolean ftpfs_first_cd_then_ls = TRUE;
+
+/* Use the ~/.netrc */
+gboolean ftpfs_use_netrc = TRUE;
+
+/* Anonymous setup */
+char *ftpfs_anonymous_passwd = NULL;
+int ftpfs_directory_timeout = 900;
+
+/* Proxy host */
+char *ftpfs_proxy_host = NULL;
+
+/* whether we have to use proxy by default? */
+gboolean ftpfs_always_use_proxy = FALSE;
+
+gboolean ftpfs_ignore_chattr_errors = TRUE;
+
+/*** file scope macro definitions ****************************************************************/
+
+#ifndef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 64
+#endif
+
+#define FTP_SUPER(super) ((ftp_super_t *) (super))
+#define FTP_FILE_HANDLER(fh) ((ftp_file_handler_t *) (fh))
+#define FH_SOCK FTP_FILE_HANDLER(fh)->sock
+
+#ifndef INADDR_NONE
+#define INADDR_NONE 0xffffffff
+#endif
+
+#define RFC_AUTODETECT 0
+#define RFC_DARING 1
+#define RFC_STRICT 2
+
+/* ftpfs_command wait_flag: */
+#define NONE 0x00
+#define WAIT_REPLY 0x01
+#define WANT_STRING 0x02
+
+#define FTP_COMMAND_PORT 21
+
+/* some defines only used by ftpfs_changetype */
+/* These two are valid values for the second parameter */
+#define TYPE_ASCII 0
+#define TYPE_BINARY 1
+
+/* This one is only used to initialize bucket->isbinary, don't use it as
+ second parameter to ftpfs_changetype. */
+#define TYPE_UNKNOWN -1
+
+#define ABORT_TIMEOUT (5 * G_USEC_PER_SEC)
+/*** file scope type declarations ****************************************************************/
+
+#ifndef HAVE_SOCKLEN_T
+typedef int socklen_t;
+#endif
+
+/* This should match the keywords[] array below */
+typedef enum
+{
+ NETRC_NONE = 0,
+ NETRC_DEFAULT,
+ NETRC_MACHINE,
+ NETRC_LOGIN,
+ NETRC_PASSWORD,
+ NETRC_PASSWD,
+ NETRC_ACCOUNT,
+ NETRC_MACDEF,
+ NETRC_UNKNOWN
+} keyword_t;
+
+typedef struct
+{
+ struct vfs_s_super base; /* base class */
+
+ int sock;
+
+ char *proxy; /* proxy server, NULL if no proxy */
+ gboolean failed_on_login; /* used to pass the failure reason to upper levels */
+ gboolean use_passive_connection;
+ gboolean remote_is_amiga; /* No leading slash allowed for AmiTCP (Amiga) */
+ int isbinary;
+ gboolean cwd_deferred; /* current_directory was changed but CWD command hasn't
+ been sent yet */
+ int strict; /* ftp server doesn't understand
+ * "LIST -la <path>"; use "CWD <path>"/
+ * "LIST" instead
+ */
+ gboolean ctl_connection_busy;
+ char *current_dir;
+} ftp_super_t;
+
+typedef struct
+{
+ vfs_file_handler_t base; /* base class */
+
+ int sock;
+ gboolean append;
+} ftp_file_handler_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static char *ftpfs_get_current_directory (struct vfs_class *me, struct vfs_s_super *super);
+static int ftpfs_chdir_internal (struct vfs_class *me, struct vfs_s_super *super,
+ const char *remote_path);
+static int ftpfs_open_socket (struct vfs_class *me, struct vfs_s_super *super);
+static gboolean ftpfs_login_server (struct vfs_class *me, struct vfs_s_super *super,
+ const char *netrcpass);
+static gboolean ftpfs_netrc_lookup (const char *host, char **login, char **pass);
+
+/*** file scope variables ************************************************************************/
+
+static int code;
+
+static char reply_str[80];
+
+static struct vfs_s_subclass ftpfs_subclass;
+static struct vfs_class *vfs_ftpfs_ops = VFS_CLASS (&ftpfs_subclass);
+
+static GSList *no_proxy = NULL;
+
+static char buffer[BUF_MEDIUM];
+static char *netrc = NULL;
+static const char *netrcp;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+ftpfs_set_blksize (struct stat *s)
+{
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ /* redefine block size */
+ s->st_blksize = 64 * 1024; /* FIXME */
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct stat *
+ftpfs_default_stat (struct vfs_class *me)
+{
+ struct stat *s;
+
+ s = vfs_s_default_stat (me, S_IFDIR | 0755);
+ ftpfs_set_blksize (s);
+ vfs_adjust_stat (s);
+
+ return s;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Translate a Unix path, i.e. MC's internal path representation (e.g.
+ /somedir/somefile) to a path valid for the remote server. Every path
+ transferred to the remote server has to be mangled by this function
+ right prior to sending it.
+ Currently only Amiga ftp servers are handled in a special manner.
+
+ When the remote server is an amiga:
+ a) strip leading slash if necessary
+ b) replace first occurrence of ":/" with ":"
+ c) strip trailing "/."
+ */
+static char *
+ftpfs_translate_path (struct vfs_class *me, struct vfs_s_super *super, const char *remote_path)
+{
+ char *ret, *p;
+
+ if (!FTP_SUPER (super)->remote_is_amiga)
+ return g_strdup (remote_path);
+
+ if (me->logfile != NULL)
+ {
+ fprintf (me->logfile, "MC -- ftpfs_translate_path: %s\n", remote_path);
+ fflush (me->logfile);
+ }
+
+ /* strip leading slash(es) */
+ while (IS_PATH_SEP (*remote_path))
+ remote_path++;
+
+ /* Don't change "/" into "", e.g. "CWD " would be invalid. */
+ if (*remote_path == '\0')
+ return g_strdup (".");
+
+ ret = g_strdup (remote_path);
+
+ /* replace first occurrence of ":/" with ":" */
+ p = strchr (ret, ':');
+ if (p != NULL && IS_PATH_SEP (p[1]))
+ str_move (p + 1, p + 2);
+
+ /* strip trailing "/." */
+ p = strrchr (ret, PATH_SEP);
+ if ((p != NULL) && (*(p + 1) == '.') && (*(p + 2) == '\0'))
+ *p = '\0';
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Extract the hostname and username from the path */
+/*
+ * path is in the form: [user@]hostname:port/remote-dir, e.g.:
+ * ftp://sunsite.unc.edu/pub/linux
+ * ftp://miguel@sphinx.nuclecu.unam.mx/c/nc
+ * ftp://tsx-11.mit.edu:8192/
+ * ftp://joe@foo.edu:11321/private
+ * If the user is empty, e.g. ftp://@roxanne/private, then your login name
+ * is supplied.
+ */
+
+static vfs_path_element_t *
+ftpfs_correct_url_parameters (const vfs_path_element_t * velement)
+{
+ vfs_path_element_t *path_element = vfs_path_element_clone (velement);
+
+ if (path_element->port == 0)
+ path_element->port = FTP_COMMAND_PORT;
+
+ if (path_element->user == NULL)
+ {
+ /* Look up user and password in netrc */
+ if (ftpfs_use_netrc)
+ ftpfs_netrc_lookup (path_element->host, &path_element->user, &path_element->password);
+ }
+ if (path_element->user == NULL)
+ path_element->user = g_strdup ("anonymous");
+
+ /* Look up password in netrc for known user */
+ if (ftpfs_use_netrc && path_element->password == NULL)
+ {
+ char *new_user = NULL;
+ char *new_passwd = NULL;
+
+ ftpfs_netrc_lookup (path_element->host, &new_user, &new_passwd);
+
+ /* If user is different, remove password */
+ if (new_user != NULL && strcmp (path_element->user, new_user) != 0)
+ MC_PTR_FREE (path_element->password);
+
+ g_free (new_user);
+ g_free (new_passwd);
+ }
+
+ return path_element;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Returns a reply code, check /usr/include/arpa/ftp.h for possible values */
+
+static int
+ftpfs_get_reply (struct vfs_class *me, int sock, char *string_buf, int string_len)
+{
+ while (TRUE)
+ {
+ char answer[BUF_1K];
+
+ if (vfs_s_get_line (me, sock, answer, sizeof (answer), '\n') == 0)
+ {
+ if (string_buf != NULL)
+ *string_buf = '\0';
+ code = 421;
+ return 4;
+ }
+
+ /* cppcheck-suppress invalidscanf */
+ switch (sscanf (answer, "%d", &code))
+ {
+ case 0:
+ if (string_buf != NULL)
+ g_strlcpy (string_buf, answer, string_len);
+ code = 500;
+ return 5;
+ case 1:
+ if (answer[3] == '-')
+ {
+ while (TRUE)
+ {
+ int i;
+
+ if (vfs_s_get_line (me, sock, answer, sizeof (answer), '\n') == 0)
+ {
+ if (string_buf != NULL)
+ *string_buf = '\0';
+ code = 421;
+ return 4;
+ }
+ /* cppcheck-suppress invalidscanf */
+ if ((sscanf (answer, "%d", &i) > 0) && (code == i) && (answer[3] == ' '))
+ break;
+ }
+ }
+ if (string_buf != NULL)
+ g_strlcpy (string_buf, answer, string_len);
+ return code / 100;
+ default:
+ break;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+ftpfs_reconnect (struct vfs_class *me, struct vfs_s_super *super)
+{
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+ int sock;
+
+ sock = ftpfs_open_socket (me, super);
+ if (sock != -1)
+ {
+ char *cwdir = ftp_super->current_dir;
+
+ close (ftp_super->sock);
+ ftp_super->sock = sock;
+ ftp_super->current_dir = NULL;
+
+ if (ftpfs_login_server (me, super, super->path_element->password))
+ {
+ if (cwdir == NULL)
+ return TRUE;
+
+ sock = ftpfs_chdir_internal (me, super, cwdir);
+ g_free (cwdir);
+ return (sock == COMPLETE);
+ }
+
+ ftp_super->current_dir = cwdir;
+ }
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+G_GNUC_PRINTF (4, 5)
+ftpfs_command (struct vfs_class *me, struct vfs_s_super *super, int wait_reply, const char *fmt,
+ ...)
+{
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+ va_list ap;
+ GString *cmdstr;
+ int status;
+ static gboolean retry = FALSE;
+ static int level = 0; /* ftpfs_login_server() use ftpfs_command() */
+
+ cmdstr = g_string_sized_new (32);
+ va_start (ap, fmt);
+ g_string_vprintf (cmdstr, fmt, ap);
+ va_end (ap);
+ g_string_append (cmdstr, "\r\n");
+
+ if (me->logfile != NULL)
+ {
+ if (strncmp (cmdstr->str, "PASS ", 5) == 0)
+ fputs ("PASS <Password not logged>\r\n", me->logfile);
+ else
+ {
+ size_t ret;
+
+ ret = fwrite (cmdstr->str, cmdstr->len, 1, me->logfile);
+ (void) ret;
+ }
+
+ fflush (me->logfile);
+ }
+
+ got_sigpipe = 0;
+ tty_enable_interrupt_key ();
+ status = write (ftp_super->sock, cmdstr->str, cmdstr->len);
+
+ if (status < 0)
+ {
+ code = 421;
+
+ if (errno == EPIPE)
+ { /* Remote server has closed connection */
+ if (level == 0)
+ {
+ level = 1;
+ status = ftpfs_reconnect (me, super) ? 1 : 0;
+ level = 0;
+ if (status != 0 && (write (ftp_super->sock, cmdstr->str, cmdstr->len) > 0))
+ goto ok;
+
+ }
+ got_sigpipe = 1;
+ }
+ g_string_free (cmdstr, TRUE);
+ tty_disable_interrupt_key ();
+ return TRANSIENT;
+ }
+
+ retry = FALSE;
+
+ ok:
+ tty_disable_interrupt_key ();
+
+ if (wait_reply != NONE)
+ {
+ status = ftpfs_get_reply (me, ftp_super->sock,
+ (wait_reply & WANT_STRING) != 0 ? reply_str : NULL,
+ sizeof (reply_str) - 1);
+ if ((wait_reply & WANT_STRING) != 0 && !retry && level == 0 && code == 421)
+ {
+ retry = TRUE;
+ level = 1;
+ status = ftpfs_reconnect (me, super) ? 1 : 0;
+ level = 0;
+ if (status != 0 && (write (ftp_super->sock, cmdstr->str, cmdstr->len) > 0))
+ goto ok;
+ }
+ retry = FALSE;
+ g_string_free (cmdstr, TRUE);
+ return status;
+ }
+
+ g_string_free (cmdstr, TRUE);
+ return COMPLETE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_super *
+ftpfs_new_archive (struct vfs_class *me)
+{
+ ftp_super_t *arch;
+
+ arch = g_new0 (ftp_super_t, 1);
+ arch->base.me = me;
+ arch->base.name = g_strdup (PATH_SEP_STR);
+ arch->sock = -1;
+ arch->use_passive_connection = ftpfs_use_passive_connections;
+ arch->strict = ftpfs_use_unix_list_options ? RFC_AUTODETECT : RFC_STRICT;
+ arch->isbinary = TYPE_UNKNOWN;
+
+ return VFS_SUPER (arch);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+ftpfs_free_archive (struct vfs_class *me, struct vfs_s_super *super)
+{
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+
+ if (ftp_super->sock != -1)
+ {
+ vfs_print_message (_("ftpfs: Disconnecting from %s"), super->path_element->host);
+ ftpfs_command (me, super, NONE, "%s", "QUIT");
+ close (ftp_super->sock);
+ }
+ g_free (ftp_super->current_dir);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_changetype (struct vfs_class *me, struct vfs_s_super *super, int binary)
+{
+ if (binary != FTP_SUPER (super)->isbinary)
+ {
+ if (ftpfs_command (me, super, WAIT_REPLY, "TYPE %c", binary ? 'I' : 'A') != COMPLETE)
+ ERRNOR (EIO, -1);
+ FTP_SUPER (super)->isbinary = binary;
+ }
+ return binary;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* This routine logs the user in */
+
+static int
+ftpfs_login_server (struct vfs_class *me, struct vfs_s_super *super, const char *netrcpass)
+{
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+ char *pass;
+ char *op;
+ char *name; /* login user name */
+ gboolean anon = FALSE;
+ char reply_string[BUF_MEDIUM];
+
+ ftp_super->isbinary = TYPE_UNKNOWN;
+
+ if (super->path_element->password != NULL) /* explicit password */
+ op = g_strdup (super->path_element->password);
+ else if (netrcpass != NULL) /* password from netrc */
+ op = g_strdup (netrcpass);
+ else if (strcmp (super->path_element->user, "anonymous") == 0
+ || strcmp (super->path_element->user, "ftp") == 0)
+ {
+ if (ftpfs_anonymous_passwd == NULL) /* default anonymous password */
+ ftpfs_init_passwd ();
+ op = g_strdup (ftpfs_anonymous_passwd);
+ anon = TRUE;
+ }
+ else
+ { /* ask user */
+ char *p;
+
+ p = g_strdup_printf (_("FTP: Password required for %s"), super->path_element->user);
+ op = vfs_get_password (p);
+ g_free (p);
+ if (op == NULL)
+ ERRNOR (EPERM, 0);
+ super->path_element->password = g_strdup (op);
+ }
+
+ if (!anon || me->logfile != NULL)
+ pass = op;
+ else
+ {
+ pass = g_strconcat ("-", op, (char *) NULL);
+ wipe_password (op);
+ }
+
+ /* Proxy server accepts: username@host-we-want-to-connect */
+ if (ftp_super->proxy != NULL)
+ name =
+ g_strconcat (super->path_element->user, "@",
+ super->path_element->host[0] ==
+ '!' ? super->path_element->host + 1 : super->path_element->host,
+ (char *) NULL);
+ else
+ name = g_strdup (super->path_element->user);
+
+ if (ftpfs_get_reply (me, ftp_super->sock, reply_string, sizeof (reply_string) - 1) == COMPLETE)
+ {
+ char *reply_up;
+
+ reply_up = g_ascii_strup (reply_string, -1);
+ ftp_super->remote_is_amiga = strstr (reply_up, "AMIGA") != NULL;
+ if (strstr (reply_up, " SPFTP/1.0.0000 SERVER ") != NULL) /* handles `LIST -la` in a weird way */
+ ftp_super->strict = RFC_STRICT;
+ g_free (reply_up);
+
+ if (me->logfile != NULL)
+ {
+ fprintf (me->logfile, "MC -- remote_is_amiga = %s\n",
+ ftp_super->remote_is_amiga ? "yes" : "no");
+ fflush (me->logfile);
+ }
+
+ vfs_print_message ("%s", _("ftpfs: sending login name"));
+
+ switch (ftpfs_command (me, super, WAIT_REPLY, "USER %s", name))
+ {
+ case CONTINUE:
+ vfs_print_message ("%s", _("ftpfs: sending user password"));
+ code = ftpfs_command (me, super, WAIT_REPLY, "PASS %s", pass);
+ if (code == CONTINUE)
+ {
+ char *p;
+
+ p = g_strdup_printf (_("FTP: Account required for user %s"),
+ super->path_element->user);
+ op = input_dialog (p, _("Account:"), MC_HISTORY_FTPFS_ACCOUNT, "",
+ INPUT_COMPLETE_USERNAMES);
+ g_free (p);
+ if (op == NULL)
+ ERRNOR (EPERM, 0);
+ vfs_print_message ("%s", _("ftpfs: sending user account"));
+ code = ftpfs_command (me, super, WAIT_REPLY, "ACCT %s", op);
+ g_free (op);
+ }
+ if (code != COMPLETE)
+ break;
+
+ MC_FALLTHROUGH;
+
+ case COMPLETE:
+ vfs_print_message ("%s", _("ftpfs: logged in"));
+ wipe_password (pass);
+ g_free (name);
+ return TRUE;
+
+ default:
+ ftp_super->failed_on_login = TRUE;
+ wipe_password (super->path_element->password);
+ super->path_element->password = NULL;
+ goto login_fail;
+ }
+ }
+
+ message (D_ERROR, MSG_ERROR, _("ftpfs: Login incorrect for user %s "),
+ super->path_element->user);
+
+ login_fail:
+ wipe_password (pass);
+ g_free (name);
+ ERRNOR (EPERM, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+ftpfs_load_no_proxy_list (void)
+{
+ /* FixMe: shouldn't be hardcoded!!! */
+ char *mc_file;
+
+ mc_file = g_build_filename (mc_global.sysconfig_dir, "mc.no_proxy", (char *) NULL);
+ if (exist_file (mc_file))
+ {
+ FILE *npf;
+
+ npf = fopen (mc_file, "r");
+ if (npf != NULL)
+ {
+ char s[BUF_LARGE]; /* provide for BUF_LARGE characters */
+
+ while (fgets (s, sizeof (s), npf) != NULL)
+ {
+ char *p;
+
+ p = strchr (s, '\n');
+ if (p == NULL) /* skip bogus entries */
+ {
+ int c;
+
+ while ((c = fgetc (npf)) != EOF && c != '\n')
+ ;
+ }
+ else if (p != s)
+ {
+ *p = '\0';
+ no_proxy = g_slist_prepend (no_proxy, g_strdup (s));
+ }
+ }
+
+ fclose (npf);
+ }
+ }
+
+ g_free (mc_file);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Return TRUE if FTP proxy should be used for this host, FALSE otherwise */
+
+static gboolean
+ftpfs_check_proxy (const char *host)
+{
+
+ if (ftpfs_proxy_host == NULL || *ftpfs_proxy_host == '\0' || host == NULL || *host == '\0')
+ return FALSE; /* sanity check */
+
+ if (*host == '!')
+ return TRUE;
+
+ if (!ftpfs_always_use_proxy)
+ return FALSE;
+
+ if (strchr (host, '.') == NULL)
+ return FALSE;
+
+ if (no_proxy == NULL)
+ {
+ GSList *npe;
+
+ ftpfs_load_no_proxy_list ();
+
+ for (npe = no_proxy; npe != NULL; npe = g_slist_next (npe))
+ {
+ const char *domain = (const char *) npe->data;
+
+ if (domain[0] == '.')
+ {
+ size_t ld, lh;
+
+ ld = strlen (domain);
+ lh = strlen (host);
+
+ while (ld != 0 && lh != 0 && host[lh - 1] == domain[ld - 1])
+ {
+ ld--;
+ lh--;
+ }
+
+ if (ld == 0)
+ return FALSE;
+ }
+ else if (g_ascii_strcasecmp (host, domain) == 0)
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+ftpfs_get_proxy_host_and_port (const char *proxy, char **host, int *port)
+{
+ vfs_path_element_t *path_element;
+
+ path_element = vfs_url_split (proxy, FTP_COMMAND_PORT, URL_USE_ANONYMOUS);
+ *host = path_element->host;
+ path_element->host = NULL;
+ *port = path_element->port;
+ vfs_path_element_free (path_element);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_open_socket (struct vfs_class *me, struct vfs_s_super *super)
+{
+ struct addrinfo hints, *res, *curr_res;
+ int my_socket = 0;
+ char *host = NULL;
+ char port[8];
+ int tmp_port = 0;
+ int e;
+
+ (void) me;
+
+ if (super->path_element->host == NULL || *super->path_element->host == '\0')
+ {
+ vfs_print_message ("%s", _("ftpfs: Invalid host name."));
+ me->verrno = EINVAL;
+ return (-1);
+ }
+
+ /* Use a proxy host? */
+ /* Hosts to connect to that start with a ! should use proxy */
+ if (FTP_SUPER (super)->proxy != NULL)
+ ftpfs_get_proxy_host_and_port (ftpfs_proxy_host, &host, &tmp_port);
+ else
+ {
+ host = g_strdup (super->path_element->host);
+ tmp_port = super->path_element->port;
+ }
+
+ g_snprintf (port, sizeof (port), "%hu", (unsigned short) tmp_port);
+
+ tty_enable_interrupt_key (); /* clear the interrupt flag */
+
+ memset (&hints, 0, sizeof (hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+
+#ifdef AI_ADDRCONFIG
+ /* By default, only look up addresses using address types for
+ * which a local interface is configured (i.e. no IPv6 if no IPv6
+ * interfaces, likewise for IPv4 (see RFC 3493 for details). */
+ hints.ai_flags = AI_ADDRCONFIG;
+#endif
+
+ e = getaddrinfo (host, port, &hints, &res);
+
+#ifdef AI_ADDRCONFIG
+ if (e == EAI_BADFLAGS)
+ {
+ /* Retry with no flags if AI_ADDRCONFIG was rejected. */
+ hints.ai_flags = 0;
+ e = getaddrinfo (host, port, &hints, &res);
+ }
+#endif
+
+ *port = '\0';
+
+ if (e != 0)
+ {
+ tty_disable_interrupt_key ();
+ vfs_print_message (_("ftpfs: %s"), gai_strerror (e));
+ g_free (host);
+ me->verrno = EINVAL;
+ return (-1);
+ }
+
+ for (curr_res = res; curr_res != NULL; curr_res = curr_res->ai_next)
+ {
+ my_socket = socket (curr_res->ai_family, curr_res->ai_socktype, curr_res->ai_protocol);
+
+ if (my_socket < 0)
+ {
+ if (curr_res->ai_next != NULL)
+ continue;
+
+ tty_disable_interrupt_key ();
+ vfs_print_message (_("ftpfs: %s"), unix_error_string (errno));
+ g_free (host);
+ freeaddrinfo (res);
+ me->verrno = errno;
+ return (-1);
+ }
+
+ vfs_print_message (_("ftpfs: making connection to %s"), host);
+ MC_PTR_FREE (host);
+
+ if (connect (my_socket, curr_res->ai_addr, curr_res->ai_addrlen) >= 0)
+ break;
+
+ me->verrno = errno;
+ close (my_socket);
+
+ if (errno == EINTR && tty_got_interrupt ())
+ vfs_print_message ("%s", _("ftpfs: connection interrupted by user"));
+ else if (res->ai_next == NULL)
+ vfs_print_message (_("ftpfs: connection to server failed: %s"),
+ unix_error_string (errno));
+ else
+ continue;
+
+ freeaddrinfo (res);
+ tty_disable_interrupt_key ();
+ return (-1);
+ }
+
+ freeaddrinfo (res);
+ tty_disable_interrupt_key ();
+ return my_socket;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_open_archive_int (struct vfs_class *me, struct vfs_s_super *super)
+{
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+ int retry_seconds = 0;
+
+ /* We do not want to use the passive if we are using proxies */
+ if (ftp_super->proxy != NULL)
+ ftp_super->use_passive_connection = ftpfs_use_passive_connections_over_proxy;
+
+ do
+ {
+ ftp_super->failed_on_login = FALSE;
+
+ ftp_super->sock = ftpfs_open_socket (me, super);
+ if (ftp_super->sock == -1)
+ return (-1);
+
+ if (ftpfs_login_server (me, super, NULL))
+ {
+ /* Logged in, no need to retry the connection */
+ break;
+ }
+
+ if (!ftp_super->failed_on_login)
+ return (-1);
+
+ /* Close only the socket descriptor */
+ close (ftp_super->sock);
+
+ if (ftpfs_retry_seconds != 0)
+ {
+ int count_down;
+
+ retry_seconds = ftpfs_retry_seconds;
+ tty_enable_interrupt_key ();
+ for (count_down = retry_seconds; count_down != 0; count_down--)
+ {
+ vfs_print_message (_("Waiting to retry... %d (Control-G to cancel)"), count_down);
+ sleep (1);
+ if (tty_got_interrupt ())
+ {
+ /* me->verrno = E; */
+ tty_disable_interrupt_key ();
+ return 0;
+ }
+ }
+ tty_disable_interrupt_key ();
+ }
+ }
+ while (retry_seconds != 0);
+
+ ftp_super->current_dir = ftpfs_get_current_directory (me, super);
+ if (ftp_super->current_dir == NULL)
+ ftp_super->current_dir = g_strdup (PATH_SEP_STR);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_open_archive (struct vfs_s_super *super,
+ const vfs_path_t * vpath, const vfs_path_element_t * vpath_element)
+{
+ (void) vpath;
+
+ super->path_element = ftpfs_correct_url_parameters (vpath_element);
+ if (ftpfs_check_proxy (super->path_element->host))
+ FTP_SUPER (super)->proxy = ftpfs_proxy_host;
+ super->root =
+ vfs_s_new_inode (vpath_element->class, super, ftpfs_default_stat (vpath_element->class));
+
+ return ftpfs_open_archive_int (vpath_element->class, super);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_archive_same (const vfs_path_element_t * vpath_element, struct vfs_s_super *super,
+ const vfs_path_t * vpath, void *cookie)
+{
+ vfs_path_element_t *path_element;
+ int result;
+
+ (void) vpath;
+ (void) cookie;
+
+ path_element = ftpfs_correct_url_parameters (vpath_element);
+
+ result = ((strcmp (path_element->host, super->path_element->host) == 0)
+ && (strcmp (path_element->user, super->path_element->user) == 0)
+ && (path_element->port == super->path_element->port)) ? 1 : 0;
+
+ vfs_path_element_free (path_element);
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* The returned directory should always contain a trailing slash */
+
+static char *
+ftpfs_get_current_directory (struct vfs_class *me, struct vfs_s_super *super)
+{
+ char buf[MC_MAXPATHLEN + 1];
+
+ if (ftpfs_command (me, super, NONE, "%s", "PWD") == COMPLETE &&
+ ftpfs_get_reply (me, FTP_SUPER (super)->sock, buf, sizeof (buf)) == COMPLETE)
+ {
+ char *bufp = NULL;
+ char *bufq;
+
+ for (bufq = buf; *bufq != '\0'; bufq++)
+ if (*bufq == '"')
+ {
+ if (bufp == NULL)
+ bufp = bufq + 1;
+ else
+ {
+ *bufq = '\0';
+
+ if (*bufp != '\0')
+ {
+ if (!IS_PATH_SEP (bufq[-1]))
+ {
+ *bufq++ = PATH_SEP;
+ *bufq = '\0';
+ }
+
+ if (IS_PATH_SEP (*bufp))
+ return g_strdup (bufp);
+
+ /* If the remote server is an Amiga a leading slash
+ might be missing. MC needs it because it is used
+ as separator between hostname and path internally. */
+ return g_strconcat (PATH_SEP_STR, bufp, (char *) NULL);
+ }
+
+ break;
+ }
+ }
+ }
+
+ me->verrno = EIO;
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Setup Passive PASV FTP connection */
+
+static gboolean
+ftpfs_setup_passive_pasv (struct vfs_class *me, struct vfs_s_super *super,
+ int my_socket, struct sockaddr_storage *sa, socklen_t * salen)
+{
+ char *c;
+ char n[6];
+ int xa, xb, xc, xd, xe, xf;
+
+ if (ftpfs_command (me, super, WAIT_REPLY | WANT_STRING, "%s", "PASV") != COMPLETE)
+ return FALSE;
+
+ /* Parse remote parameters */
+ for (c = reply_str + 4; *c != '\0' && !isdigit ((unsigned char) *c); c++)
+ ;
+
+ if (*c == '\0' || !isdigit ((unsigned char) *c))
+ return FALSE;
+
+ /* cppcheck-suppress invalidscanf */
+ if (sscanf (c, "%d,%d,%d,%d,%d,%d", &xa, &xb, &xc, &xd, &xe, &xf) != 6)
+ return FALSE;
+
+ n[0] = (unsigned char) xa;
+ n[1] = (unsigned char) xb;
+ n[2] = (unsigned char) xc;
+ n[3] = (unsigned char) xd;
+ n[4] = (unsigned char) xe;
+ n[5] = (unsigned char) xf;
+
+ memcpy (&(((struct sockaddr_in *) sa)->sin_addr.s_addr), (void *) n, 4);
+ memcpy (&(((struct sockaddr_in *) sa)->sin_port), (void *) &n[4], 2);
+
+ return (connect (my_socket, (struct sockaddr *) sa, *salen) >= 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Setup Passive EPSV FTP connection */
+
+static gboolean
+ftpfs_setup_passive_epsv (struct vfs_class *me, struct vfs_s_super *super,
+ int my_socket, struct sockaddr_storage *sa, socklen_t * salen)
+{
+ char *c;
+ int port;
+
+ if (ftpfs_command (me, super, WAIT_REPLY | WANT_STRING, "%s", "EPSV") != COMPLETE)
+ return FALSE;
+
+ /* (|||<port>|) */
+ c = strchr (reply_str, '|');
+ if (c == NULL || strlen (c) <= 3)
+ return FALSE;
+
+ c += 3;
+ port = atoi (c);
+ if (port < 0 || port > 65535)
+ return FALSE;
+
+ port = htons (port);
+
+ switch (sa->ss_family)
+ {
+ case AF_INET:
+ ((struct sockaddr_in *) sa)->sin_port = port;
+ break;
+ case AF_INET6:
+ ((struct sockaddr_in6 *) sa)->sin6_port = port;
+ break;
+ default:
+ break;
+ }
+
+ return (connect (my_socket, (struct sockaddr *) sa, *salen) >= 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Setup Passive ftp connection, we use it for source routed connections */
+
+static gboolean
+ftpfs_setup_passive (struct vfs_class *me, struct vfs_s_super *super,
+ int my_socket, struct sockaddr_storage *sa, socklen_t * salen)
+{
+ /* It's IPV4, so try PASV first, some servers and ALGs get confused by EPSV */
+ if (sa->ss_family == AF_INET)
+ {
+ if (!ftpfs_setup_passive_pasv (me, super, my_socket, sa, salen))
+ /* An IPV4 FTP server might support EPSV, so if PASV fails we can try EPSV anyway */
+ if (!ftpfs_setup_passive_epsv (me, super, my_socket, sa, salen))
+ return FALSE;
+ }
+ /* It's IPV6, so EPSV is our only hope */
+ else if (!ftpfs_setup_passive_epsv (me, super, my_socket, sa, salen))
+ return FALSE;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Setup Active PORT or EPRT FTP connection */
+
+static int
+ftpfs_setup_active (struct vfs_class *me, struct vfs_s_super *super,
+ struct sockaddr_storage data_addr, socklen_t data_addrlen)
+{
+ unsigned short int port;
+ char *addr;
+ unsigned int af;
+ int res;
+
+ switch (data_addr.ss_family)
+ {
+ case AF_INET:
+ af = FTP_INET;
+ port = ((struct sockaddr_in *) &data_addr)->sin_port;
+ break;
+ case AF_INET6:
+ af = FTP_INET6;
+ port = ((struct sockaddr_in6 *) &data_addr)->sin6_port;
+ break;
+ default:
+ /* Not implemented */
+ return 0;
+ }
+
+ addr = g_try_malloc (NI_MAXHOST);
+ if (addr == NULL)
+ ERRNOR (ENOMEM, -1);
+
+ res =
+ getnameinfo ((struct sockaddr *) &data_addr, data_addrlen, addr, NI_MAXHOST, NULL, 0,
+ NI_NUMERICHOST);
+ if (res != 0)
+ {
+ const char *err_str;
+
+ g_free (addr);
+
+ if (res == EAI_SYSTEM)
+ {
+ me->verrno = errno;
+ err_str = unix_error_string (me->verrno);
+ }
+ else
+ {
+ me->verrno = EIO;
+ err_str = gai_strerror (res);
+ }
+
+ vfs_print_message (_("ftpfs: could not make address-to-name translation: %s"), err_str);
+
+ return (-1);
+ }
+
+ /* If we are talking to an IPV4 server, try PORT, and, only if it fails, go for EPRT */
+ if (af == FTP_INET)
+ {
+ unsigned char *a = (unsigned char *) &((struct sockaddr_in *) &data_addr)->sin_addr;
+ unsigned char *p = (unsigned char *) &port;
+
+ if (ftpfs_command (me, super, WAIT_REPLY,
+ "PORT %u,%u,%u,%u,%u,%u", a[0], a[1], a[2], a[3],
+ p[0], p[1]) == COMPLETE)
+ {
+ g_free (addr);
+ return 1;
+ }
+ }
+
+ /*
+ * Converts network MSB first order to host byte order (LSB
+ * first on i386). If we do it earlier, we will run into an
+ * endianness issue, because the server actually expects to see
+ * "PORT A,D,D,R,MSB,LSB" in the PORT command.
+ */
+ port = ntohs (port);
+
+ /* We are talking to an IPV6 server or PORT failed, so we can try EPRT anyway */
+ res =
+ (ftpfs_command (me, super, WAIT_REPLY, "EPRT |%u|%s|%hu|", af, addr, port) ==
+ COMPLETE) ? 1 : 0;
+ g_free (addr);
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Initialize a socket for FTP DATA connection */
+
+static int
+ftpfs_init_data_socket (struct vfs_class *me, struct vfs_s_super *super,
+ struct sockaddr_storage *data_addr, socklen_t * data_addrlen)
+{
+ const unsigned int attempts = 10;
+ unsigned int i;
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+ int result;
+
+ for (i = 0; i < attempts; i++)
+ {
+ memset (data_addr, 0, sizeof (*data_addr));
+ *data_addrlen = sizeof (*data_addr);
+
+ if (ftp_super->use_passive_connection)
+ {
+ result = getpeername (ftp_super->sock, (struct sockaddr *) data_addr, data_addrlen);
+ if (result == 0)
+ break;
+
+ me->verrno = errno;
+
+ if (me->verrno == ENOTCONN)
+ {
+ vfs_print_message (_("ftpfs: try reconnect to server, attempt %u"), i);
+ if (ftpfs_reconnect (me, super))
+ continue; /* get name of new socket */
+ }
+ else
+ {
+ /* error -- stop loop */
+ vfs_print_message (_("ftpfs: could not get socket name: %s"),
+ unix_error_string (me->verrno));
+ }
+ }
+ else
+ {
+ result = getsockname (ftp_super->sock, (struct sockaddr *) data_addr, data_addrlen);
+ if (result == 0)
+ break;
+
+ me->verrno = errno;
+
+ vfs_print_message (_("ftpfs: try reconnect to server, attempt %u"), i);
+ if (ftpfs_reconnect (me, super))
+ continue; /* get name of new socket */
+
+ /* error -- stop loop */
+ vfs_print_message ("%s", _("ftpfs: could not reconnect to server"));
+ }
+
+ i = attempts;
+ }
+
+ if (i >= attempts)
+ return (-1);
+
+ switch (data_addr->ss_family)
+ {
+ case AF_INET:
+ ((struct sockaddr_in *) data_addr)->sin_port = 0;
+ break;
+ case AF_INET6:
+ ((struct sockaddr_in6 *) data_addr)->sin6_port = 0;
+ break;
+ default:
+ vfs_print_message ("%s", _("ftpfs: invalid address family"));
+ ERRNOR (EINVAL, -1);
+ }
+
+ result = socket (data_addr->ss_family, SOCK_STREAM, IPPROTO_TCP);
+ if (result < 0)
+ {
+ me->verrno = errno;
+ vfs_print_message (_("ftpfs: could not create socket: %s"), unix_error_string (me->verrno));
+ result = -1;
+ }
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Initialize FTP DATA connection */
+
+static int
+ftpfs_initconn (struct vfs_class *me, struct vfs_s_super *super)
+{
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+ struct sockaddr_storage data_addr;
+ socklen_t data_addrlen;
+
+ /*
+ * Don't factor socket initialization out of these conditionals,
+ * because ftpfs_init_data_socket initializes it in different way
+ * depending on use_passive_connection flag.
+ */
+
+ /* Try to establish a passive connection first (if requested) */
+ if (ftp_super->use_passive_connection)
+ {
+ int data_sock;
+
+ data_sock = ftpfs_init_data_socket (me, super, &data_addr, &data_addrlen);
+ if (data_sock < 0)
+ return (-1);
+
+ if (ftpfs_setup_passive (me, super, data_sock, &data_addr, &data_addrlen))
+ return data_sock;
+
+ vfs_print_message ("%s", _("ftpfs: could not setup passive mode"));
+ ftp_super->use_passive_connection = FALSE;
+
+ close (data_sock);
+ }
+
+ /* If passive setup is disabled or failed, fallback to active connections */
+ if (!ftp_super->use_passive_connection)
+ {
+ int data_sock;
+
+ data_sock = ftpfs_init_data_socket (me, super, &data_addr, &data_addrlen);
+ if (data_sock < 0)
+ return (-1);
+
+ if ((bind (data_sock, (struct sockaddr *) &data_addr, data_addrlen) != 0) ||
+ (getsockname (data_sock, (struct sockaddr *) &data_addr, &data_addrlen) != 0) ||
+ (listen (data_sock, 1) != 0))
+ {
+ close (data_sock);
+ ERRNOR (errno, -1);
+ }
+
+ if (ftpfs_setup_active (me, super, data_addr, data_addrlen) != 0)
+ return data_sock;
+
+ close (data_sock);
+ }
+
+ /* Restore the initial value of use_passive_connection (for subsequent retries) */
+ ftp_super->use_passive_connection =
+ ftp_super->proxy !=
+ NULL ? ftpfs_use_passive_connections_over_proxy : ftpfs_use_passive_connections;
+
+ me->verrno = EIO;
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_open_data_connection (struct vfs_class *me, struct vfs_s_super *super, const char *cmd,
+ const char *remote, int isbinary, int reget)
+{
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+ int s, j, data;
+
+ /* FTP doesn't allow to open more than one file at a time */
+ if (ftp_super->ctl_connection_busy)
+ return (-1);
+
+ s = ftpfs_initconn (me, super);
+ if (s == -1)
+ return (-1);
+
+ if (ftpfs_changetype (me, super, isbinary) == -1)
+ {
+ close (s);
+ return (-1);
+ }
+
+ if (reget > 0)
+ {
+ j = ftpfs_command (me, super, WAIT_REPLY, "REST %d", reget);
+ if (j != CONTINUE)
+ {
+ close (s);
+ ERRNOR (EIO, -1);
+ }
+ }
+
+ if (remote == NULL)
+ j = ftpfs_command (me, super, WAIT_REPLY, "%s", cmd);
+ else
+ {
+ char *remote_path;
+
+ remote_path = ftpfs_translate_path (me, super, remote);
+ j = ftpfs_command (me, super, WAIT_REPLY, "%s /%s", cmd,
+ /* WarFtpD can't STORE //filename */
+ IS_PATH_SEP (*remote_path) ? remote_path + 1 : remote_path);
+ g_free (remote_path);
+ }
+
+ if (j != PRELIM)
+ {
+ close (s);
+ ERRNOR (EPERM, -1);
+ }
+
+ if (ftp_super->use_passive_connection)
+ data = s;
+ else
+ {
+ struct sockaddr_storage from;
+ socklen_t fromlen = sizeof (from);
+
+ tty_enable_interrupt_key ();
+ data = accept (s, (struct sockaddr *) &from, &fromlen);
+ if (data < 0)
+ me->verrno = errno;
+ tty_disable_interrupt_key ();
+ close (s);
+ if (data < 0)
+ return (-1);
+ }
+
+ ftp_super->ctl_connection_busy = TRUE;
+ return data;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+ftpfs_linear_abort (struct vfs_class *me, vfs_file_handler_t * fh)
+{
+ struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh);
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+ static unsigned char const ipbuf[3] = { IAC, IP, IAC };
+ fd_set mask;
+ int dsock = FH_SOCK;
+
+ FH_SOCK = -1;
+ ftp_super->ctl_connection_busy = FALSE;
+
+ vfs_print_message ("%s", _("ftpfs: aborting transfer."));
+
+ if (send (ftp_super->sock, ipbuf, sizeof (ipbuf), MSG_OOB) != sizeof (ipbuf))
+ {
+ vfs_print_message (_("ftpfs: abort error: %s"), unix_error_string (errno));
+ if (dsock != -1)
+ close (dsock);
+ return;
+ }
+
+ if (ftpfs_command (me, super, NONE, "%cABOR", DM) != COMPLETE)
+ {
+ vfs_print_message ("%s", _("ftpfs: abort failed"));
+ if (dsock != -1)
+ close (dsock);
+ return;
+ }
+
+ if (dsock != -1)
+ {
+ FD_ZERO (&mask);
+ FD_SET (dsock, &mask);
+
+ if (select (dsock + 1, &mask, NULL, NULL, NULL) > 0)
+ {
+ gint64 start_tim;
+ char buf[BUF_8K];
+
+ start_tim = g_get_monotonic_time ();
+
+ /* flush the remaining data */
+ while (read (dsock, buf, sizeof (buf)) > 0)
+ {
+ gint64 tim;
+
+ tim = g_get_monotonic_time ();
+
+ if (tim > start_tim + ABORT_TIMEOUT)
+ {
+ /* server keeps sending, drop the connection and ftpfs_reconnect */
+ close (dsock);
+ ftpfs_reconnect (me, super);
+ return;
+ }
+ }
+ }
+ close (dsock);
+ }
+
+ if ((ftpfs_get_reply (me, ftp_super->sock, NULL, 0) == TRANSIENT) && (code == 426))
+ ftpfs_get_reply (me, ftp_super->sock, NULL, 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if 0
+static void
+resolve_symlink_without_ls_options (struct vfs_class *me, struct vfs_s_super *super,
+ struct vfs_s_inode *dir)
+{
+ struct linklist *flist;
+ struct direntry *fe, *fel;
+ char tmp[MC_MAXPATHLEN];
+
+ dir->symlink_status = FTPFS_RESOLVING_SYMLINKS;
+ for (flist = dir->file_list->next; flist != dir->file_list; flist = flist->next)
+ {
+ /* flist->data->l_stat is already initialized with 0 */
+ fel = flist->data;
+ if (S_ISLNK (fel->s.st_mode) && fel->linkname != NULL)
+ {
+ int depth;
+
+ if (IS_PATH_SEP (fel->linkname[0]))
+ {
+ if (strlen (fel->linkname) >= MC_MAXPATHLEN)
+ continue;
+ strcpy (tmp, fel->linkname);
+ }
+ else
+ {
+ if ((strlen (dir->remote_path) + strlen (fel->linkname)) >= MC_MAXPATHLEN)
+ continue;
+ strcpy (tmp, dir->remote_path);
+ if (tmp[1] != '\0')
+ strcat (tmp, PATH_SEP_STR);
+ strcat (tmp + 1, fel->linkname);
+ }
+
+ for (depth = 0; depth < 100; depth++)
+ { /* depth protects against recursive symbolic links */
+ canonicalize_pathname (tmp);
+ fe = _get_file_entry_t (bucket, tmp, 0, 0);
+ if (fe != NULL)
+ {
+ if (S_ISLNK (fe->s.st_mode) && fe->l_stat == 0)
+ {
+ /* Symlink points to link which isn't resolved, yet. */
+ if (IS_PATH_SEP (fe->linkname[0]))
+ {
+ if (strlen (fe->linkname) >= MC_MAXPATHLEN)
+ break;
+ strcpy (tmp, fe->linkname);
+ }
+ else
+ {
+ /* at this point tmp looks always like this
+ /directory/filename, i.e. no need to check
+ strrchr's return value */
+ *(strrchr (tmp, PATH_SEP) + 1) = '\0'; /* dirname */
+ if ((strlen (tmp) + strlen (fe->linkname)) >= MC_MAXPATHLEN)
+ break;
+ strcat (tmp, fe->linkname);
+ }
+ continue;
+ }
+ else
+ {
+ fel->l_stat = g_new (struct stat, 1);
+ if (S_ISLNK (fe->s.st_mode))
+ *fel->l_stat = *fe->l_stat;
+ else
+ *fel->l_stat = fe->s;
+ (*fel->l_stat).st_ino = bucket->__inode_counter++;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ dir->symlink_status = FTPFS_RESOLVED_SYMLINKS;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+resolve_symlink_with_ls_options (struct vfs_class *me, struct vfs_s_super *super,
+ struct vfs_s_inode *dir)
+{
+ char buffer[2048] = "", *filename;
+ int sock;
+ FILE *fp;
+ struct stat s;
+ struct linklist *flist;
+ struct direntry *fe;
+ int switch_method = 0;
+
+ dir->symlink_status = FTPFS_RESOLVED_SYMLINKS;
+ if (strchr (dir->remote_path, ' ') == NULL)
+ sock = ftpfs_open_data_connection (bucket, "LIST -lLa", dir->remote_path, TYPE_ASCII, 0);
+ else
+ {
+ if (ftpfs_chdir_internal (bucket, dir->remote_path) != COMPLETE)
+ {
+ vfs_print_message ("%s", _("ftpfs: CWD failed."));
+ return;
+ }
+
+ sock = ftpfs_open_data_connection (bucket, "LIST -lLa", ".", TYPE_ASCII, 0);
+ }
+
+ if (sock == -1)
+ {
+ vfs_print_message ("%s", _("ftpfs: couldn't resolve symlink"));
+ return;
+ }
+
+ fp = fdopen (sock, "r");
+ if (fp == NULL)
+ {
+ close (sock);
+ vfs_print_message ("%s", _("ftpfs: couldn't resolve symlink"));
+ return;
+ }
+ tty_enable_interrupt_key ();
+ flist = dir->file_list->next;
+
+ while (TRUE)
+ {
+ do
+ {
+ if (flist == dir->file_list)
+ goto done;
+
+ fe = flist->data;
+ flist = flist->next;
+ }
+ while (!S_ISLNK (fe->s.st_mode));
+
+ while (TRUE)
+ {
+ if (fgets (buffer, sizeof (buffer), fp) == NULL)
+ goto done;
+
+ if (me->logfile != NULL)
+ {
+ fputs (buffer, me->logfile);
+ fflush (me->logfile);
+ }
+
+ vfs_die ("This code should be commented out\n");
+
+ if (vfs_parse_ls_lga (buffer, &s, &filename, NULL))
+ {
+ int r;
+
+ r = strcmp (fe->name, filename);
+ g_free (filename);
+ if (r == 0)
+ {
+ if (S_ISLNK (s.st_mode))
+ {
+ /* This server doesn't understand LIST -lLa */
+ switch_method = 1;
+ goto done;
+ }
+
+ fe->l_stat = g_try_new (struct stat, 1);
+ if (fe->l_stat == NULL)
+ goto done;
+
+ *fe->l_stat = s;
+ (*fe->l_stat).st_ino = bucket->__inode_counter++;
+ break;
+ }
+
+ if (r < 0)
+ break;
+ }
+ }
+ }
+
+ done:
+ while (fgets (buffer, sizeof (buffer), fp) != NULL)
+ ;
+ tty_disable_interrupt_key ();
+ fclose (fp);
+ ftpfs_get_reply (me, FTP_SUPER (super)->sock, NULL, 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+resolve_symlink (struct vfs_class *me, struct vfs_s_super *super, struct vfs_s_inode *dir)
+{
+ vfs_print_message ("%s", _("Resolving symlink..."));
+
+ if (FTP_SUPER (super)->strict_rfc959_list_cmd)
+ resolve_symlink_without_ls_options (me, super, dir);
+ else
+ resolve_symlink_with_ls_options (me, super, dir);
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_dir_load (struct vfs_class *me, struct vfs_s_inode *dir, const char *remote_path)
+{
+ struct vfs_s_super *super = dir->super;
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+ int sock;
+ char lc_buffer[BUF_8K];
+ int res;
+ gboolean cd_first;
+ GSList *dirlist = NULL;
+ GSList *entlist;
+ GSList *iter;
+ int err_count = 0;
+
+ cd_first = ftpfs_first_cd_then_ls || (ftp_super->strict == RFC_STRICT)
+ || (strchr (remote_path, ' ') != NULL);
+
+ again:
+ vfs_print_message (_("ftpfs: Reading FTP directory %s... %s%s"),
+ remote_path,
+ ftp_super->strict ==
+ RFC_STRICT ? _("(strict rfc959)") : "", cd_first ? _("(chdir first)") : "");
+
+ if (cd_first && ftpfs_chdir_internal (me, super, remote_path) != COMPLETE)
+ {
+ me->verrno = ENOENT;
+ vfs_print_message ("%s", _("ftpfs: CWD failed."));
+ return (-1);
+ }
+
+ dir->timestamp = g_get_monotonic_time () + ftpfs_directory_timeout * G_USEC_PER_SEC;
+
+ if (ftp_super->strict == RFC_STRICT)
+ sock = ftpfs_open_data_connection (me, super, "LIST", 0, TYPE_ASCII, 0);
+ else if (cd_first)
+ /* Dirty hack to avoid autoprepending / to . */
+ /* Wu-ftpd produces strange output for '/' if 'LIST -la .' used */
+ sock = ftpfs_open_data_connection (me, super, "LIST -la", 0, TYPE_ASCII, 0);
+ else
+ {
+ char *path;
+
+ /* Trailing "/." is necessary if remote_path is a symlink */
+ path = g_strconcat (remote_path, PATH_SEP_STR ".", (char *) NULL);
+ sock = ftpfs_open_data_connection (me, super, "LIST -la", path, TYPE_ASCII, 0);
+ g_free (path);
+ }
+
+ if (sock == -1)
+ {
+ fallback:
+ if (ftp_super->strict == RFC_AUTODETECT)
+ {
+ /* It's our first attempt to get a directory listing from this
+ server (UNIX style LIST command) */
+ ftp_super->strict = RFC_STRICT;
+ /* I hate goto, but recursive call needs another 8K on stack */
+ /* return ftpfs_dir_load (me, dir, remote_path); */
+ cd_first = TRUE;
+ goto again;
+ }
+
+ vfs_print_message ("%s", _("ftpfs: failed; nowhere to fallback to"));
+ ERRNOR (EACCES, -1);
+ }
+
+ /* read full directory list, then parse it */
+ while ((res = vfs_s_get_line_interruptible (me, lc_buffer, sizeof (lc_buffer), sock)) != 0)
+ {
+ if (res == EINTR)
+ {
+ me->verrno = ECONNRESET;
+ close (sock);
+ ftp_super->ctl_connection_busy = FALSE;
+ ftpfs_get_reply (me, ftp_super->sock, NULL, 0);
+ g_slist_free_full (dirlist, g_free);
+ vfs_print_message (_("%s: failure"), me->name);
+ return (-1);
+ }
+
+ if (me->logfile != NULL)
+ {
+ fputs (lc_buffer, me->logfile);
+ fputs ("\n", me->logfile);
+ fflush (me->logfile);
+ }
+
+ dirlist = g_slist_prepend (dirlist, g_strdup (lc_buffer));
+ }
+
+ close (sock);
+ ftp_super->ctl_connection_busy = FALSE;
+ me->verrno = E_REMOTE;
+ if ((ftpfs_get_reply (me, ftp_super->sock, NULL, 0) != COMPLETE))
+ {
+ g_slist_free_full (dirlist, g_free);
+ goto fallback;
+ }
+
+ if (dirlist == NULL && !cd_first)
+ {
+ /* The LIST command may produce an empty output. In such scenario
+ it is not clear whether this is caused by 'remote_path' being
+ a non-existent path or for some other reason (listing empty
+ directory without the -a option, non-readable directory, etc.).
+
+ Since 'dir_load' is a crucial method, when it comes to determine
+ whether a given path is a _directory_, the code must try its best
+ to determine the type of 'remote_path'. The only reliable way to
+ achieve this is through issuing a CWD command. */
+
+ cd_first = TRUE;
+ goto again;
+ }
+
+ /* parse server's reply */
+ dirlist = g_slist_reverse (dirlist); /* restore order */
+ entlist = ftpfs_parse_long_list (me, dir, dirlist, &err_count);
+ g_slist_free_full (dirlist, g_free);
+
+ for (iter = entlist; iter != NULL; iter = g_slist_next (iter))
+ vfs_s_insert_entry (me, dir, VFS_ENTRY (iter->data));
+
+ g_slist_free (entlist);
+
+ if (ftp_super->strict == RFC_AUTODETECT)
+ ftp_super->strict = RFC_DARING;
+
+ vfs_print_message (_("%s: done."), me->name);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_file_store (struct vfs_class *me, vfs_file_handler_t * fh, char *name, char *localname)
+{
+ struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh);
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+ ftp_file_handler_t *ftp = FTP_FILE_HANDLER (fh);
+
+ int h, sock;
+ off_t n_stored = 0;
+#ifdef HAVE_STRUCT_LINGER_L_LINGER
+ struct linger li;
+#else
+ int flag_one = 1;
+#endif
+ char lc_buffer[BUF_8K];
+ struct stat s;
+ char *w_buf;
+
+ h = open (localname, O_RDONLY);
+ if (h == -1)
+ ERRNOR (EIO, -1);
+
+ if (fstat (h, &s) == -1)
+ {
+ me->verrno = errno;
+ close (h);
+ return (-1);
+ }
+
+ sock =
+ ftpfs_open_data_connection (me, super, ftp->append ? "APPE" : "STOR", name, TYPE_BINARY, 0);
+ if (sock < 0)
+ {
+ close (h);
+ return (-1);
+ }
+#ifdef HAVE_STRUCT_LINGER_L_LINGER
+ li.l_onoff = 1;
+ li.l_linger = 120;
+ setsockopt (sock, SOL_SOCKET, SO_LINGER, (char *) &li, sizeof (li));
+#else
+ setsockopt (sock, SOL_SOCKET, SO_LINGER, &flag_one, sizeof (flag_one));
+#endif
+
+ tty_enable_interrupt_key ();
+ while (TRUE)
+ {
+ ssize_t n_read, n_written;
+
+ while ((n_read = read (h, lc_buffer, sizeof (lc_buffer))) == -1)
+ {
+ if (errno != EINTR)
+ {
+ me->verrno = errno;
+ goto error_return;
+ }
+ if (tty_got_interrupt ())
+ {
+ me->verrno = EINTR;
+ goto error_return;
+ }
+ }
+ if (n_read == 0)
+ break;
+
+ n_stored += n_read;
+ w_buf = lc_buffer;
+
+ while ((n_written = write (sock, w_buf, n_read)) != n_read)
+ {
+ if (n_written == -1)
+ {
+ if (errno == EINTR && !tty_got_interrupt ())
+ continue;
+
+ me->verrno = errno;
+ goto error_return;
+ }
+
+ w_buf += n_written;
+ n_read -= n_written;
+ }
+
+ vfs_print_message ("%s: %" PRIuMAX "/%" PRIuMAX,
+ _("ftpfs: storing file"), (uintmax_t) n_stored, (uintmax_t) s.st_size);
+ }
+ tty_disable_interrupt_key ();
+
+ close (sock);
+ ftp_super->ctl_connection_busy = FALSE;
+ close (h);
+
+ if (ftpfs_get_reply (me, ftp_super->sock, NULL, 0) != COMPLETE)
+ ERRNOR (EIO, -1);
+ return 0;
+
+ error_return:
+ tty_disable_interrupt_key ();
+ close (sock);
+ ftp_super->ctl_connection_busy = FALSE;
+ close (h);
+
+ ftpfs_get_reply (me, ftp_super->sock, NULL, 0);
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_linear_start (struct vfs_class *me, vfs_file_handler_t * fh, off_t offset)
+{
+ char *name;
+
+ name = vfs_s_fullpath (me, fh->ino);
+ if (name == NULL)
+ return 0;
+
+ FH_SOCK =
+ ftpfs_open_data_connection (me, VFS_FILE_HANDLER_SUPER (fh), "RETR", name, TYPE_BINARY,
+ offset);
+ g_free (name);
+ if (FH_SOCK == -1)
+ ERRNOR (EACCES, 0);
+
+ fh->linear = LS_LINEAR_OPEN;
+ FTP_FILE_HANDLER (fh)->append = FALSE;
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+ftpfs_linear_read (struct vfs_class *me, vfs_file_handler_t * fh, void *buf, size_t len)
+{
+ ssize_t n;
+ struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh);
+
+ while ((n = read (FH_SOCK, buf, len)) < 0)
+ {
+ if ((errno == EINTR) && !tty_got_interrupt ())
+ continue;
+ break;
+ }
+
+ if (n < 0)
+ ftpfs_linear_abort (me, fh);
+ else if (n == 0)
+ {
+ FTP_SUPER (super)->ctl_connection_busy = FALSE;
+ close (FH_SOCK);
+ FH_SOCK = -1;
+ if ((ftpfs_get_reply (me, FTP_SUPER (super)->sock, NULL, 0) != COMPLETE))
+ ERRNOR (E_REMOTE, -1);
+ return 0;
+ }
+
+ ERRNOR (errno, n);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+ftpfs_linear_close (struct vfs_class *me, vfs_file_handler_t * fh)
+{
+ if (FH_SOCK != -1)
+ ftpfs_linear_abort (me, fh);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_ctl (void *fh, int ctlop, void *arg)
+{
+ (void) arg;
+
+ switch (ctlop)
+ {
+ case VFS_CTL_IS_NOTREADY:
+ {
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ int v;
+
+ if (file->linear == LS_NOT_LINEAR)
+ vfs_die ("You may not do this");
+ if (file->linear == LS_LINEAR_CLOSED || file->linear == LS_LINEAR_PREOPEN)
+ return 0;
+
+ v = vfs_s_select_on_two (FH_SOCK, 0);
+ return (((v < 0) && (errno == EINTR)) || v == 0) ? 1 : 0;
+ }
+ default:
+ return 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_send_command (const vfs_path_t * vpath, const char *cmd, int flags)
+{
+ const char *rpath;
+ char *p;
+ struct vfs_s_super *super;
+ int r;
+ struct vfs_class *me;
+ gboolean flush_directory_cache = (flags & OPT_FLUSH) != 0;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ rpath = vfs_s_get_path (vpath, &super, 0);
+ if (rpath == NULL)
+ return (-1);
+
+ p = ftpfs_translate_path (me, super, rpath);
+ r = ftpfs_command (me, super, WAIT_REPLY, cmd, p);
+ g_free (p);
+ vfs_stamp_create (vfs_ftpfs_ops, super);
+ if ((flags & OPT_IGNORE_ERROR) != 0)
+ r = COMPLETE;
+ if (r != COMPLETE)
+ {
+ me->verrno = EPERM;
+ return (-1);
+ }
+ if (flush_directory_cache)
+ vfs_s_invalidate (me, super);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_stat (const vfs_path_t * vpath, struct stat *buf)
+{
+ int ret;
+
+ ret = vfs_s_stat (vpath, buf);
+ ftpfs_set_blksize (buf);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_lstat (const vfs_path_t * vpath, struct stat *buf)
+{
+ int ret;
+
+ ret = vfs_s_lstat (vpath, buf);
+ ftpfs_set_blksize (buf);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_fstat (void *vfs_info, struct stat *buf)
+{
+ int ret;
+
+ ret = vfs_s_fstat (vfs_info, buf);
+ ftpfs_set_blksize (buf);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_chmod (const vfs_path_t * vpath, mode_t mode)
+{
+ char buf[BUF_SMALL];
+ int ret;
+
+ g_snprintf (buf, sizeof (buf), "SITE CHMOD %4.4o /%%s", (unsigned int) (mode & 07777));
+ ret = ftpfs_send_command (vpath, buf, OPT_FLUSH);
+ return ftpfs_ignore_chattr_errors ? 0 : ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_chown (const vfs_path_t * vpath, uid_t owner, gid_t group)
+{
+#if 0
+ (void) vpath;
+ (void) owner;
+ (void) group;
+
+ me->verrno = EPERM;
+ return (-1);
+#else
+ /* Everyone knows it is not possible to chown remotely, so why bother them.
+ If someone's root, then copy/move will always try to chown it... */
+ (void) vpath;
+ (void) owner;
+ (void) group;
+ return 0;
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_unlink (const vfs_path_t * vpath)
+{
+ return ftpfs_send_command (vpath, "DELE /%s", OPT_FLUSH);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Return TRUE if path is the same directory as the one we are in now */
+static gboolean
+ftpfs_is_same_dir (struct vfs_class *me, struct vfs_s_super *super, const char *path)
+{
+ (void) me;
+
+ return (FTP_SUPER (super)->current_dir != NULL
+ && strcmp (path, FTP_SUPER (super)->current_dir) == 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_chdir_internal (struct vfs_class *me, struct vfs_s_super *super, const char *remote_path)
+{
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+ int r;
+ char *p;
+
+ if (!ftp_super->cwd_deferred && ftpfs_is_same_dir (me, super, remote_path))
+ return COMPLETE;
+
+ p = ftpfs_translate_path (me, super, remote_path);
+ r = ftpfs_command (me, super, WAIT_REPLY, "CWD /%s", p);
+ g_free (p);
+
+ if (r != COMPLETE)
+ me->verrno = EIO;
+ else
+ {
+ g_free (ftp_super->current_dir);
+ ftp_super->current_dir = g_strdup (remote_path);
+ ftp_super->cwd_deferred = FALSE;
+ }
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_rename (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ ftpfs_send_command (vpath1, "RNFR /%s", OPT_FLUSH);
+ return ftpfs_send_command (vpath2, "RNTO /%s", OPT_FLUSH);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_mkdir (const vfs_path_t * vpath, mode_t mode)
+{
+ (void) mode; /* FIXME: should be used */
+
+ return ftpfs_send_command (vpath, "MKD /%s", OPT_FLUSH);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_rmdir (const vfs_path_t * vpath)
+{
+ return ftpfs_send_command (vpath, "RMD /%s", OPT_FLUSH);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_file_handler_t *
+ftpfs_fh_new (struct vfs_s_inode *ino, gboolean changed)
+{
+ ftp_file_handler_t *fh;
+
+ fh = g_new0 (ftp_file_handler_t, 1);
+ vfs_s_init_fh (VFS_FILE_HANDLER (fh), ino, changed);
+ fh->sock = -1;
+
+ return VFS_FILE_HANDLER (fh);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_fh_open (struct vfs_class *me, vfs_file_handler_t * fh, int flags, mode_t mode)
+{
+ ftp_file_handler_t *ftp = FTP_FILE_HANDLER (fh);
+
+ (void) mode;
+
+ /* File will be written only, so no need to retrieve it from ftp server */
+ if (((flags & O_WRONLY) == O_WRONLY) && ((flags & (O_RDONLY | O_RDWR)) == 0))
+ {
+#ifdef HAVE_STRUCT_LINGER_L_LINGER
+ struct linger li;
+#else
+ int li = 1;
+#endif
+ char *name;
+
+ /* ftpfs_linear_start() called, so data will be written
+ * to local temporary file and stored to ftp server
+ * by vfs_s_close later
+ */
+ if (FTP_SUPER (VFS_FILE_HANDLER_SUPER (fh))->ctl_connection_busy)
+ {
+ if (fh->ino->localname == NULL)
+ {
+ vfs_path_t *vpath;
+ int handle;
+
+ handle = vfs_mkstemps (&vpath, me->name, fh->ino->ent->name);
+ if (handle == -1)
+ return (-1);
+
+ close (handle);
+ fh->ino->localname = vfs_path_free (vpath, FALSE);
+ ftp->append = (flags & O_APPEND) != 0;
+ }
+ return 0;
+ }
+ name = vfs_s_fullpath (me, fh->ino);
+ if (name == NULL)
+ return (-1);
+
+ fh->handle =
+ ftpfs_open_data_connection (me, VFS_FILE_HANDLER_SUPER (fh),
+ (flags & O_APPEND) != 0 ? "APPE" : "STOR", name,
+ TYPE_BINARY, 0);
+ g_free (name);
+
+ if (fh->handle < 0)
+ return (-1);
+
+#ifdef HAVE_STRUCT_LINGER_L_LINGER
+ li.l_onoff = 1;
+ li.l_linger = 120;
+#endif
+ setsockopt (fh->handle, SOL_SOCKET, SO_LINGER, &li, sizeof (li));
+
+ if (fh->ino->localname != NULL)
+ {
+ unlink (fh->ino->localname);
+ MC_PTR_FREE (fh->ino->localname);
+ }
+ return 0;
+ }
+
+ if (fh->ino->localname == NULL && vfs_s_retrieve_file (me, fh->ino) == -1)
+ return (-1);
+
+ if (fh->ino->localname == NULL)
+ vfs_die ("retrieve_file failed to fill in localname");
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_fh_close (struct vfs_class *me, vfs_file_handler_t * fh)
+{
+ if (fh->handle != -1 && fh->ino->localname == NULL)
+ {
+ ftp_super_t *ftp = FTP_SUPER (VFS_FILE_HANDLER_SUPER (fh));
+
+ close (fh->handle);
+ fh->handle = -1;
+ ftp->ctl_connection_busy = FALSE;
+ /* File is stored to destination already, so
+ * we prevent VFS_SUBCLASS (me)->ftpfs_file_store() call from vfs_s_close ()
+ */
+ fh->changed = FALSE;
+ if (ftpfs_get_reply (me, ftp->sock, NULL, 0) != COMPLETE)
+ ERRNOR (EIO, -1);
+ vfs_s_invalidate (me, VFS_FILE_HANDLER_SUPER (fh));
+ }
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+ftpfs_done (struct vfs_class *me)
+{
+ (void) me;
+
+ g_slist_free_full (no_proxy, g_free);
+
+ g_free (ftpfs_anonymous_passwd);
+ g_free (ftpfs_proxy_host);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+ftpfs_fill_names (struct vfs_class *me, fill_names_f func)
+{
+ GList *iter;
+
+ for (iter = VFS_SUBCLASS (me)->supers; iter != NULL; iter = g_list_next (iter))
+ {
+ const struct vfs_s_super *super = (const struct vfs_s_super *) iter->data;
+ GString *name;
+
+ name = vfs_path_element_build_pretty_path_str (super->path_element);
+
+ func (name->str);
+ g_string_free (name, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static keyword_t
+ftpfs_netrc_next (void)
+{
+ char *p;
+ keyword_t i;
+ static const char *const keywords[] = { "default", "machine",
+ "login", "password", "passwd", "account", "macdef", NULL
+ };
+
+ while (TRUE)
+ {
+ netrcp = skip_separators (netrcp);
+ if (*netrcp != '\n')
+ break;
+ netrcp++;
+ }
+ if (*netrcp == '\0')
+ return NETRC_NONE;
+
+ p = buffer;
+ if (*netrcp == '"')
+ {
+ for (netrcp++; *netrcp != '"' && *netrcp != '\0'; netrcp++)
+ {
+ if (*netrcp == '\\')
+ netrcp++;
+ *p++ = *netrcp;
+ }
+ }
+ else
+ {
+ for (; *netrcp != '\0' && !whiteness (*netrcp) && *netrcp != ','; netrcp++)
+ {
+ if (*netrcp == '\\')
+ netrcp++;
+ *p++ = *netrcp;
+ }
+ }
+
+ *p = '\0';
+ if (*buffer == '\0')
+ return NETRC_NONE;
+
+ for (i = NETRC_DEFAULT; keywords[i - 1] != NULL; i++)
+ if (strcmp (keywords[i - 1], buffer) == 0)
+ return i;
+
+ return NETRC_UNKNOWN;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+ftpfs_netrc_bad_mode (const char *netrcname)
+{
+ struct stat mystat;
+
+ if (stat (netrcname, &mystat) >= 0 && (mystat.st_mode & 077) != 0)
+ {
+ static gboolean be_angry = TRUE;
+
+ if (be_angry)
+ {
+ message (D_ERROR, MSG_ERROR,
+ _("~/.netrc file has incorrect mode\nRemove password or correct mode"));
+ be_angry = FALSE;
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Scan .netrc until we find matching "machine" or "default"
+ * domain is used for additional matching
+ * No search is done after "default" in compliance with "man netrc"
+ * Return TRUE if found, FALSE otherwise */
+
+static gboolean
+ftpfs_find_machine (const char *host, const char *domain)
+{
+ keyword_t keyword;
+
+ if (host == NULL)
+ host = "";
+ if (domain == NULL)
+ domain = "";
+
+ while ((keyword = ftpfs_netrc_next ()) != NETRC_NONE)
+ {
+ if (keyword == NETRC_DEFAULT)
+ return TRUE;
+
+ if (keyword == NETRC_MACDEF)
+ {
+ /* Scan for an empty line, which concludes "macdef" */
+ do
+ {
+ while (*netrcp != '\0' && *netrcp != '\n')
+ netrcp++;
+ if (*netrcp != '\n')
+ break;
+ netrcp++;
+ }
+ while (*netrcp != '\0' && *netrcp != '\n');
+
+ continue;
+ }
+
+ if (keyword != NETRC_MACHINE)
+ continue;
+
+ /* Take machine name */
+ if (ftpfs_netrc_next () == NETRC_NONE)
+ break;
+
+ if (g_ascii_strcasecmp (host, buffer) != 0)
+ {
+ const char *host_domain;
+
+ /* Try adding our domain to short names in .netrc */
+ host_domain = strchr (host, '.');
+ if (host_domain == NULL)
+ continue;
+
+ /* Compare domain part */
+ if (g_ascii_strcasecmp (host_domain, domain) != 0)
+ continue;
+
+ /* Compare local part */
+ if (g_ascii_strncasecmp (host, buffer, host_domain - host) != 0)
+ continue;
+ }
+
+ return TRUE;
+ }
+
+ /* end of .netrc */
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Extract login and password from .netrc for the host.
+ * pass may be NULL.
+ * Returns TRUE for success, FALSE for error */
+
+static gboolean
+ftpfs_netrc_lookup (const char *host, char **login, char **pass)
+{
+ char *netrcname;
+ char *tmp_pass = NULL;
+ char hostname[MAXHOSTNAMELEN];
+ const char *domain;
+ static struct rupcache
+ {
+ struct rupcache *next;
+ char *host;
+ char *login;
+ char *pass;
+ } *rup_cache = NULL, *rupp;
+
+ /* Initialize *login and *pass */
+ MC_PTR_FREE (*login);
+ MC_PTR_FREE (*pass);
+
+ /* Look up in the cache first */
+ for (rupp = rup_cache; rupp != NULL; rupp = rupp->next)
+ if (strcmp (host, rupp->host) == 0)
+ {
+ *login = g_strdup (rupp->login);
+ *pass = g_strdup (rupp->pass);
+ return TRUE;
+ }
+
+ /* Load current .netrc */
+ netrcname = g_build_filename (mc_config_get_home_dir (), ".netrc", (char *) NULL);
+ if (!g_file_get_contents (netrcname, &netrc, NULL, NULL))
+ {
+ g_free (netrcname);
+ return TRUE;
+ }
+
+ netrcp = netrc;
+
+ /* Find our own domain name */
+ if (gethostname (hostname, sizeof (hostname)) < 0)
+ *hostname = '\0';
+
+ domain = strchr (hostname, '.');
+ if (domain == NULL)
+ domain = "";
+
+ /* Scan for "default" and matching "machine" keywords */
+ ftpfs_find_machine (host, domain);
+
+ /* Scan for keywords following "default" and "machine" */
+ while (TRUE)
+ {
+ keyword_t keyword;
+
+ gboolean need_break = FALSE;
+ keyword = ftpfs_netrc_next ();
+
+ switch (keyword)
+ {
+ case NETRC_LOGIN:
+ if (ftpfs_netrc_next () == NETRC_NONE)
+ {
+ need_break = TRUE;
+ break;
+ }
+
+ /* We have another name already - should not happen */
+ if (*login != NULL)
+ {
+ need_break = TRUE;
+ break;
+ }
+
+ /* We have login name now */
+ *login = g_strdup (buffer);
+ break;
+
+ case NETRC_PASSWORD:
+ case NETRC_PASSWD:
+ if (ftpfs_netrc_next () == NETRC_NONE)
+ {
+ need_break = TRUE;
+ break;
+ }
+
+ /* Ignore unsafe passwords */
+ if (*login != NULL &&
+ strcmp (*login, "anonymous") != 0 && strcmp (*login, "ftp") != 0
+ && ftpfs_netrc_bad_mode (netrcname))
+ {
+ need_break = TRUE;
+ break;
+ }
+
+ /* Remember password. pass may be NULL, so use tmp_pass */
+ if (tmp_pass == NULL)
+ tmp_pass = g_strdup (buffer);
+ break;
+
+ case NETRC_ACCOUNT:
+ /* "account" is followed by a token which we ignore */
+ if (ftpfs_netrc_next () == NETRC_NONE)
+ {
+ need_break = TRUE;
+ break;
+ }
+
+ /* Ignore account, but warn user anyways */
+ ftpfs_netrc_bad_mode (netrcname);
+ break;
+
+ default:
+ /* Unexpected keyword or end of file */
+ need_break = TRUE;
+ break;
+ }
+
+ if (need_break)
+ break;
+ }
+
+ MC_PTR_FREE (netrc);
+ g_free (netrcname);
+
+ rupp = g_new (struct rupcache, 1);
+ rupp->host = g_strdup (host);
+ rupp->login = g_strdup (*login);
+ rupp->pass = g_strdup (tmp_pass);
+
+ rupp->next = rup_cache;
+ rup_cache = rupp;
+
+ *pass = tmp_pass;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** This routine is called as the last step in load_setup */
+void
+ftpfs_init_passwd (void)
+{
+ ftpfs_anonymous_passwd = load_anon_passwd ();
+
+ if (ftpfs_anonymous_passwd == NULL)
+ {
+ /* If there is no anonymous ftp password specified
+ * then we'll just use anonymous@
+ * We don't send any other thing because:
+ * - We want to remain anonymous
+ * - We want to stop SPAM
+ * - We don't want to let ftp sites to discriminate by the user,
+ * host or country.
+ */
+ ftpfs_anonymous_passwd = g_strdup ("anonymous@");
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init_ftpfs (void)
+{
+ tcp_init ();
+
+ vfs_init_subclass (&ftpfs_subclass, "ftpfs", VFSF_NOLINKS | VFSF_REMOTE | VFSF_USETMP, "ftp");
+ vfs_ftpfs_ops->done = ftpfs_done;
+ vfs_ftpfs_ops->fill_names = ftpfs_fill_names;
+ vfs_ftpfs_ops->stat = ftpfs_stat;
+ vfs_ftpfs_ops->lstat = ftpfs_lstat;
+ vfs_ftpfs_ops->fstat = ftpfs_fstat;
+ vfs_ftpfs_ops->chmod = ftpfs_chmod;
+ vfs_ftpfs_ops->chown = ftpfs_chown;
+ vfs_ftpfs_ops->unlink = ftpfs_unlink;
+ vfs_ftpfs_ops->rename = ftpfs_rename;
+ vfs_ftpfs_ops->mkdir = ftpfs_mkdir;
+ vfs_ftpfs_ops->rmdir = ftpfs_rmdir;
+ vfs_ftpfs_ops->ctl = ftpfs_ctl;
+ ftpfs_subclass.archive_same = ftpfs_archive_same;
+ ftpfs_subclass.new_archive = ftpfs_new_archive;
+ ftpfs_subclass.open_archive = ftpfs_open_archive;
+ ftpfs_subclass.free_archive = ftpfs_free_archive;
+ ftpfs_subclass.fh_new = ftpfs_fh_new;
+ ftpfs_subclass.fh_open = ftpfs_fh_open;
+ ftpfs_subclass.fh_close = ftpfs_fh_close;
+ ftpfs_subclass.dir_load = ftpfs_dir_load;
+ ftpfs_subclass.file_store = ftpfs_file_store;
+ ftpfs_subclass.linear_start = ftpfs_linear_start;
+ ftpfs_subclass.linear_read = ftpfs_linear_read;
+ ftpfs_subclass.linear_close = ftpfs_linear_close;
+ vfs_register_class (vfs_ftpfs_ops);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/ftpfs/ftpfs.h b/src/vfs/ftpfs/ftpfs.h
new file mode 100644
index 0000000..d00c0b5
--- /dev/null
+++ b/src/vfs/ftpfs/ftpfs.h
@@ -0,0 +1,46 @@
+/**
+ * \file
+ * \brief Header: Virtual File System: FTP file system
+ */
+
+#ifndef MC__VFS_FTPFS_H
+#define MC__VFS_FTPFS_H
+
+#include "lib/vfs/xdirentry.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define FTP_INET 1
+#define FTP_INET6 2
+
+#define OPT_FLUSH 1
+#define OPT_IGNORE_ERROR 2
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+extern gboolean ftpfs_use_netrc;
+extern char *ftpfs_anonymous_passwd;
+extern char *ftpfs_proxy_host;
+extern int ftpfs_directory_timeout;
+extern gboolean ftpfs_always_use_proxy;
+extern gboolean ftpfs_ignore_chattr_errors;
+
+extern int ftpfs_retry_seconds;
+extern gboolean ftpfs_use_passive_connections;
+extern gboolean ftpfs_use_passive_connections_over_proxy;
+extern gboolean ftpfs_use_unix_list_options;
+extern gboolean ftpfs_first_cd_then_ls;
+
+/*** declarations of public functions ************************************************************/
+
+void ftpfs_init_passwd (void);
+void vfs_init_ftpfs (void);
+GSList *ftpfs_parse_long_list (struct vfs_class *me, struct vfs_s_inode *dir, GSList * buf,
+ int *err_ret);
+
+/*** inline functions ****************************************************************************/
+#endif
diff --git a/src/vfs/ftpfs/ftpfs_parse_ls.c b/src/vfs/ftpfs/ftpfs_parse_ls.c
new file mode 100644
index 0000000..5db79e0
--- /dev/null
+++ b/src/vfs/ftpfs/ftpfs_parse_ls.c
@@ -0,0 +1,1236 @@
+/*
+ Virtual File System: FTP file system
+
+ Copyright (C) 2015-2023
+ The Free Software Foundation, Inc.
+
+ Written by: Andrew Borodin <aborodin@vmail.ru>, 2013
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * \brief Source: Virtual File System: FTP file system
+ * \author Andrew Borodin
+ * \date 2015
+ *
+ * Parser of ftp long file list (reply to "LIST -la" command).
+ * Borrowed from lftp project (http://http://lftp.yar.ru/).
+ * Author of original lftp code: Alexander V. Lukyanov (lav@yars.free.net)
+ */
+
+#include <config.h>
+
+#include <ctype.h> /* isdigit() */
+#include <stdio.h> /* sscanf() */
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h> /* mode_t */
+#include <time.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "lib/global.h"
+
+#include "lib/vfs/vfs.h"
+#include "lib/vfs/utilvfs.h"
+
+#include "ftpfs.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define number_of_parsers 7
+
+#define NO_SIZE ((off_t) (-1L))
+#define NO_DATE ((time_t) (-1L))
+
+#define FIRST_TOKEN strtok (line, " \t")
+#define NEXT_TOKEN strtok (NULL, " \t")
+#define FIRST_TOKEN_R strtok_r (line, " \t", &next)
+#define NEXT_TOKEN_R strtok_r (NULL, " \t", &next)
+
+#define ERR2 do { (*err)++; return FALSE; } while (FALSE)
+
+/*** file scope type declarations ****************************************************************/
+
+typedef enum
+{
+ UNKNOWN = 0,
+ DIRECTORY,
+ SYMLINK,
+ NORMAL
+} filetype;
+
+typedef gboolean (*ftpfs_line_parser) (char *line, struct stat * s, char **filename,
+ char **linkname, int *err);
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static gboolean ftpfs_parse_long_list_UNIX (char *line, struct stat *s, char **filename,
+ char **linkname, int *err);
+static gboolean ftpfs_parse_long_list_NT (char *line, struct stat *s, char **filename,
+ char **linkname, int *err);
+static gboolean ftpfs_parse_long_list_EPLF (char *line, struct stat *s, char **filename,
+ char **linkname, int *err);
+static gboolean ftpfs_parse_long_list_MLSD (char *line, struct stat *s, char **filename,
+ char **linkname, int *err);
+static gboolean ftpfs_parse_long_list_AS400 (char *line, struct stat *s, char **filename,
+ char **linkname, int *err);
+static gboolean ftpfs_parse_long_list_OS2 (char *line, struct stat *s, char **filename,
+ char **linkname, int *err);
+static gboolean ftpfs_parse_long_list_MacWebStar (char *line, struct stat *s, char **filename,
+ char **linkname, int *err);
+
+/*** file scope variables ************************************************************************/
+
+static time_t rawnow;
+static struct tm now;
+
+static ftpfs_line_parser line_parsers[number_of_parsers] = {
+ ftpfs_parse_long_list_UNIX,
+ ftpfs_parse_long_list_NT,
+ ftpfs_parse_long_list_EPLF,
+ ftpfs_parse_long_list_MLSD,
+ ftpfs_parse_long_list_AS400,
+ ftpfs_parse_long_list_OS2,
+ ftpfs_parse_long_list_MacWebStar
+};
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static inline uid_t
+ftpfs_get_uid (const char *s)
+{
+ uid_t u;
+
+ if (*s < '0' || *s > '9')
+ u = vfs_finduid (s);
+ else
+ u = (uid_t) atol (s);
+
+ return u;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline gid_t
+ftpfs_get_gid (const char *s)
+{
+ gid_t g;
+
+ if (*s < '0' || *s > '9')
+ g = vfs_findgid (s);
+ else
+ g = (gid_t) atol (s);
+
+ return g;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+ftpfs_init_time (void)
+{
+ time (&rawnow);
+ now = *localtime (&rawnow);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+guess_year (int month, int day, int hour, int minute)
+{
+ int year;
+
+ (void) hour;
+ (void) minute;
+
+ year = now.tm_year + 1900;
+
+ if (month * 32 + day > now.tm_mon * 32 + now.tm_mday + 6)
+ year--;
+
+ return year;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+parse_year_or_time (const char *year_or_time, int *year, int *hour, int *minute)
+{
+ if (year_or_time[2] == ':')
+ {
+ if (sscanf (year_or_time, "%2d:%2d", hour, minute) != 2)
+ return FALSE;
+
+ *year = -1;
+ }
+ else
+ {
+ if (sscanf (year_or_time, "%d", year) != 1)
+ return FALSE;
+
+ *hour = *minute = 0;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Converts struct tm to time_t, assuming the data in tm is UTC rather
+ than local timezone (mktime assumes the latter).
+
+ Contributed by Roger Beeman <beeman@cisco.com>, with the help of
+ Mark Baushke <mdb@cisco.com> and the rest of the Gurus at CISCO. */
+static time_t
+mktime_from_utc (const struct tm *t)
+{
+ struct tm tc;
+ time_t tl, tb;
+
+ memcpy (&tc, t, sizeof (struct tm));
+
+ /* UTC times are never DST; if we say -1, we'll introduce odd localtime-
+ * dependent errors. */
+
+ tc.tm_isdst = 0;
+
+ tl = mktime (&tc);
+ if (tl == -1)
+ return (-1);
+
+ tb = mktime (gmtime (&tl));
+
+ return (tl <= tb ? (tl + (tl - tb)) : (tl - (tb - tl)));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static time_t
+ftpfs_convert_date (const char *s)
+{
+ struct tm tm;
+ int year, month, day, hour, minute, second;
+ int skip = 0;
+ int n;
+
+ memset (&tm, 0, sizeof (tm));
+
+ n = sscanf (s, "%4d%n", &year, &skip);
+
+ /* try to workaround server's y2k bug *
+ * I hope in the next 300 years the y2k bug will be finally fixed :) */
+ if (n == 1 && year >= 1910 && year <= 1930)
+ {
+ n = sscanf (s, "%5d%n", &year, &skip);
+ year = year - 19100 + 2000;
+ }
+
+ if (n != 1)
+ return NO_DATE;
+
+ n = sscanf (s + skip, "%2d%2d%2d%2d%2d", &month, &day, &hour, &minute, &second);
+
+ if (n != 5)
+ return NO_DATE;
+
+ tm.tm_year = year - 1900;
+ tm.tm_mon = month - 1;
+ tm.tm_mday = day;
+ tm.tm_hour = hour;
+ tm.tm_min = minute;
+ tm.tm_sec = second;
+
+ return mktime_from_utc (&tm);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/*
+ -rwxr-xr-x 1 lav root 4771 Sep 12 1996 install-sh
+ -rw-r--r-- 1 lav root 1349 Feb 2 14:10 lftp.lsm
+ drwxr-xr-x 4 lav root 1024 Feb 22 15:32 lib
+ lrwxrwxrwx 1 lav root 33 Feb 14 17:45 ltconfig -> /usr/share/libtool/ltconfig
+
+ NOTE: group may be missing.
+ */
+
+static gboolean
+parse_ls_line (char *line, struct stat *s, char **filename, char **linkname)
+{
+ char *next = NULL;
+ char *t;
+ mode_t type, mode = 0;
+ char *group_or_size;
+ struct tm date;
+ const char *day_of_month;
+ gboolean year_anomaly = FALSE;
+ char *name;
+
+ /* parse perms */
+ t = FIRST_TOKEN_R;
+ if (t == NULL)
+ return FALSE;
+
+ if (!vfs_parse_filetype (t, NULL, &type))
+ return FALSE;
+
+ if (vfs_parse_fileperms (t + 1, NULL, &mode))
+ mode |= type;
+
+ s->st_mode = mode;
+
+ /* link count */
+ t = NEXT_TOKEN_R;
+ if (t == NULL)
+ return FALSE;
+ s->st_nlink = atol (t);
+
+ /* user */
+ t = NEXT_TOKEN_R;
+ if (t == NULL)
+ return FALSE;
+
+ s->st_uid = ftpfs_get_uid (t);
+
+ /* group or size */
+ group_or_size = NEXT_TOKEN_R;
+
+ /* size or month */
+ t = NEXT_TOKEN_R;
+ if (t == NULL)
+ return FALSE;
+ if (isdigit ((unsigned char) *t))
+ {
+ /* it's size, so the previous was group: */
+ long long size;
+ int n;
+
+ s->st_gid = ftpfs_get_gid (group_or_size);
+
+ if (sscanf (t, "%lld%n", &size, &n) == 1 && t[n] == '\0')
+ s->st_size = (off_t) size;
+ t = NEXT_TOKEN_R;
+ if (t == NULL)
+ return FALSE;
+ }
+ else
+ {
+ /* it was month, so the previous was size: */
+ long long size;
+ int n;
+
+ if (sscanf (group_or_size, "%lld%n", &size, &n) == 1 && group_or_size[n] == '\0')
+ s->st_size = (off_t) size;
+ }
+
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ s->st_blksize = 512;
+#endif
+#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
+ s->st_blocks = (s->st_size + 511) / 512;
+#endif
+
+ memset (&date, 0, sizeof (date));
+
+ if (!vfs_parse_month (t, &date))
+ date.tm_mon = 0;
+
+ day_of_month = NEXT_TOKEN_R;
+ if (day_of_month == NULL)
+ return FALSE;
+ date.tm_mday = atoi (day_of_month);
+
+ /* time or year */
+ t = NEXT_TOKEN_R;
+ if (t == NULL)
+ return FALSE;
+ date.tm_isdst = -1;
+ date.tm_hour = date.tm_min = 0;
+ date.tm_sec = 30;
+
+ if (sscanf (t, "%2d:%2d", &date.tm_hour, &date.tm_min) == 2)
+ date.tm_year = guess_year (date.tm_mon, date.tm_mday, date.tm_hour, date.tm_min) - 1900;
+ else
+ {
+ if (day_of_month + strlen (day_of_month) + 1 == t)
+ year_anomaly = TRUE;
+ date.tm_year = atoi (t) - 1900;
+ /* We don't know the hour. Set it to something other than 0, or
+ * DST -1 will end up changing the date. */
+ date.tm_hour = 12;
+ date.tm_min = 0;
+ date.tm_sec = 0;
+ }
+
+ s->st_mtime = mktime (&date);
+ /* Use resulting time value */
+ s->st_atime = s->st_ctime = s->st_mtime;
+
+ name = strtok_r (NULL, "", &next);
+ if (name == NULL)
+ return FALSE;
+
+ /* there are ls which output extra space after year. */
+ if (year_anomaly && *name == ' ')
+ name++;
+
+ if (!S_ISLNK (s->st_mode))
+ *linkname = NULL;
+ else
+ {
+ char *arrow;
+
+ for (arrow = name; (arrow = strstr (arrow, " -> ")) != NULL; arrow++)
+ if (arrow != name && arrow[4] != '\0')
+ {
+ *arrow = '\0';
+ *linkname = g_strdup (arrow + 4);
+ break;
+ }
+ }
+
+ *filename = g_strdup (name);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+ftpfs_parse_long_list_UNIX (char *line, struct stat *s, char **filename, char **linkname, int *err)
+{
+ int tmp;
+ gboolean ret;
+
+ if (sscanf (line, "total %d", &tmp) == 1)
+ return FALSE;
+
+ if (strncasecmp (line, "Status of ", 10) == 0)
+ return FALSE; /* STAT output. */
+
+ ret = parse_ls_line (line, s, filename, linkname);
+ if (!ret)
+ (*err)++;
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/*
+ 07-13-98 09:06PM <DIR> aix
+ 07-13-98 09:06PM <DIR> hpux
+ 07-13-98 09:06PM <DIR> linux
+ 07-13-98 09:06PM <DIR> ncr
+ 07-13-98 09:06PM <DIR> solaris
+ 03-18-98 06:01AM 2109440 nlxb318e.tar
+ 07-02-98 11:17AM 13844 Whatsnew.txt
+ */
+
+static gboolean
+ftpfs_parse_long_list_NT (char *line, struct stat *s, char **filename, char **linkname, int *err)
+{
+ char *t;
+ int month, day, year, hour, minute;
+ char am;
+ struct tm tms;
+ long long size;
+
+ t = FIRST_TOKEN;
+ if (t == NULL)
+ ERR2;
+ if (sscanf (t, "%2d-%2d-%2d", &month, &day, &year) != 3)
+ ERR2;
+ if (year >= 70)
+ year += 1900;
+ else
+ year += 2000;
+
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+ am = 'A'; /* AM/PM is optional */
+ if (sscanf (t, "%2d:%2d%c", &hour, &minute, &am) < 2)
+ ERR2;
+
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+
+ if (am == 'P') /* PM - after noon */
+ {
+ hour += 12;
+ if (hour == 24)
+ hour = 0;
+ }
+
+ tms.tm_sec = 30; /* seconds after the minute [0, 61] */
+ tms.tm_min = minute; /* minutes after the hour [0, 59] */
+ tms.tm_hour = hour; /* hour since midnight [0, 23] */
+ tms.tm_mday = day; /* day of the month [1, 31] */
+ tms.tm_mon = month - 1; /* months since January [0, 11] */
+ tms.tm_year = year - 1900; /* years since 1900 */
+ tms.tm_isdst = -1;
+
+
+ s->st_mtime = mktime (&tms);
+ /* Use resulting time value */
+ s->st_atime = s->st_ctime = s->st_mtime;
+
+ if (strcmp (t, "<DIR>") == 0)
+ s->st_mode = S_IFDIR;
+ else
+ {
+ s->st_mode = S_IFREG;
+ if (sscanf (t, "%lld", &size) != 1)
+ ERR2;
+ s->st_size = (off_t) size;
+ }
+
+ t = strtok (NULL, "");
+ if (t == NULL)
+ ERR2;
+ while (*t == ' ')
+ t++;
+ if (*t == '\0')
+ ERR2;
+
+ *filename = g_strdup (t);
+ *linkname = NULL;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/*
+ +i774.71425,m951188401,/, users
+ +i774.49602,m917883130,r,s79126, jgr_www2.exe
+
+ starts with +
+ comma separated
+ first character of field is type:
+ i - ?
+ m - modification time
+ / - means directory
+ r - means plain file
+ s - size
+ up - permissions in octal
+ \t - file name follows.
+ */
+
+static gboolean
+ftpfs_parse_long_list_EPLF (char *line, struct stat *s, char **filename, char **linkname, int *err)
+{
+ size_t len;
+ const char *b;
+ const char *name = NULL;
+ size_t name_len = 0;
+ off_t size = NO_SIZE;
+ time_t date = NO_DATE;
+ long date_l;
+ long long size_ll;
+ gboolean dir = FALSE;
+ gboolean type_known = FALSE;
+ int perms = -1;
+ const char *scan;
+ ssize_t scan_len;
+
+ len = strlen (line);
+ b = line;
+
+ if (len < 2 || b[0] != '+')
+ ERR2;
+
+ scan = b + 1;
+ scan_len = len - 1;
+
+ while (scan != NULL && scan_len > 0)
+ {
+ const char *comma;
+
+ switch (*scan)
+ {
+ case '\t': /* the rest is file name. */
+ name = scan + 1;
+ name_len = scan_len - 1;
+ scan = NULL;
+ break;
+ case 's':
+ if (sscanf (scan + 1, "%lld", &size_ll) != 1)
+ break;
+ size = size_ll;
+ break;
+ case 'm':
+ if (sscanf (scan + 1, "%ld", &date_l) != 1)
+ break;
+ date = date_l;
+ break;
+ case '/':
+ dir = TRUE;
+ type_known = TRUE;
+ break;
+ case 'r':
+ dir = FALSE;
+ type_known = TRUE;
+ break;
+ case 'i':
+ break;
+ case 'u':
+ if (scan[1] == 'p') /* permissions. */
+ if (sscanf (scan + 2, "%o", (unsigned int *) &perms) != 1)
+ perms = -1;
+ break;
+ default:
+ name = NULL;
+ scan = NULL;
+ break;
+ }
+ if (scan == NULL || scan_len == 0)
+ break;
+
+ comma = (const char *) memchr (scan, ',', scan_len);
+ if (comma == NULL)
+ break;
+
+ scan_len -= comma + 1 - scan;
+ scan = comma + 1;
+ }
+
+ if (name == NULL || !type_known)
+ ERR2;
+
+ *filename = g_strndup (name, name_len);
+ *linkname = NULL;
+
+ if (size != NO_SIZE)
+ s->st_size = size;
+ if (date != NO_DATE)
+ {
+ s->st_mtime = date;
+ /* Use resulting time value */
+ s->st_atime = s->st_ctime = s->st_mtime;
+ }
+ if (type_known)
+ s->st_mode = dir ? S_IFDIR : S_IFREG;
+ if (perms != -1)
+ s->st_mode |= perms; /* FIXME */
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ Type=cdir;Modify=20021029173810;Perm=el;Unique=BP8AAjJufAA; /
+ Type=pdir;Modify=20021029173810;Perm=el;Unique=BP8AAjJufAA; ..
+ Type=dir;Modify=20010118144705;Perm=e;Unique=BP8AAjNufAA; bin
+ Type=dir;Modify=19981021003019;Perm=el;Unique=BP8AAlhufAA; pub
+ Type=file;Size=12303;Modify=19970124132601;Perm=r;Unique=BP8AAo9ufAA; mailserv.FAQ
+ modify=20161215062118;perm=flcdmpe;type=dir;UNIX.group=503;UNIX.mode=0700; directory-name
+ modify=20161213121618;perm=adfrw;size=6369064;type=file;UNIX.group=503;UNIX.mode=0644; file-name
+ modify=20120103123744;perm=adfrw;size=11;type=OS.unix=symlink;UNIX.group=0;UNIX.mode=0777; www
+ */
+
+static gboolean
+ftpfs_parse_long_list_MLSD (char *line, struct stat *s, char **filename, char **linkname, int *err)
+{
+ const char *name = NULL;
+ off_t size = NO_SIZE;
+ time_t date = NO_DATE;
+ const char *owner = NULL;
+ const char *group = NULL;
+ filetype type = UNKNOWN;
+ int perms = -1;
+ char *space;
+ char *tok;
+
+ space = strstr (line, "; ");
+ if (space != NULL)
+ {
+ name = space + 2;
+ *space = '\0';
+ }
+ else
+ {
+ /* NcFTPd does not put a semicolon after last fact, workaround it. */
+ space = strchr (line, ' ');
+ if (space == NULL)
+ ERR2;
+ name = space + 1;
+ *space = '\0';
+ }
+
+ for (tok = strtok (line, ";"); tok != NULL; tok = strtok (NULL, ";"))
+ {
+ if (strcasecmp (tok, "Type=cdir") == 0
+ || strcasecmp (tok, "Type=pdir") == 0 || strcasecmp (tok, "Type=dir") == 0)
+ {
+ type = DIRECTORY;
+ continue;
+ }
+ if (strcasecmp (tok, "Type=file") == 0)
+ {
+ type = NORMAL;
+ continue;
+ }
+ if (strcasecmp (tok, "Type=OS.unix=symlink") == 0)
+ {
+ type = SYMLINK;
+ continue;
+ }
+ if (strncasecmp (tok, "Modify=", 7) == 0)
+ {
+ date = ftpfs_convert_date (tok + 7);
+ continue;
+ }
+ if (strncasecmp (tok, "Size=", 5) == 0)
+ {
+ long long size_ll;
+
+ if (sscanf (tok + 5, "%lld", &size_ll) == 1)
+ size = size_ll;
+ continue;
+ }
+ if (strncasecmp (tok, "Perm=", 5) == 0)
+ {
+ perms = 0;
+ for (tok += 5; *tok != '\0'; tok++)
+ {
+ switch (g_ascii_tolower (*tok))
+ {
+ case 'e':
+ perms |= 0111;
+ break;
+ case 'l':
+ perms |= 0444;
+ break;
+ case 'r':
+ perms |= 0444;
+ break;
+ case 'c':
+ perms |= 0200;
+ break;
+ case 'w':
+ perms |= 0200;
+ break;
+ default:
+ break;
+ }
+ }
+ continue;
+ }
+ if (strncasecmp (tok, "UNIX.mode=", 10) == 0)
+ {
+ if (sscanf (tok + 10, "%o", (unsigned int *) &perms) != 1)
+ perms = -1;
+ continue;
+ }
+ if (strncasecmp (tok, "UNIX.owner=", 11) == 0)
+ {
+ owner = tok + 11;
+ continue;
+ }
+ if (strncasecmp (tok, "UNIX.group=", 11) == 0)
+ {
+ group = tok + 11;
+ continue;
+ }
+ if (strncasecmp (tok, "UNIX.uid=", 9) == 0)
+ {
+ if (owner == NULL)
+ owner = tok + 9;
+ continue;
+ }
+ if (strncasecmp (tok, "UNIX.gid=", 9) == 0)
+ {
+ if (group == NULL)
+ group = tok + 9;
+ continue;
+ }
+ }
+ if (name == NULL || name[0] == '\0' || type == UNKNOWN)
+ ERR2;
+
+ *filename = g_strdup (name);
+ *linkname = NULL;
+
+ if (size != NO_SIZE)
+ s->st_size = size;
+ if (date != NO_DATE)
+ {
+ s->st_mtime = date;
+ /* Use resulting time value */
+ s->st_atime = s->st_ctime = s->st_mtime;
+ }
+ switch (type)
+ {
+ case DIRECTORY:
+ s->st_mode = S_IFDIR;
+ break;
+ case SYMLINK:
+ s->st_mode = S_IFLNK;
+ break;
+ case NORMAL:
+ s->st_mode = S_IFREG;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ if (perms != -1)
+ s->st_mode |= perms; /* FIXME */
+ if (owner != NULL)
+ s->st_uid = ftpfs_get_uid (owner);
+ if (group != NULL)
+ s->st_gid = ftpfs_get_gid (group);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/*
+ ASUSER 8192 04/26/05 13:54:16 *DIR dir/
+ ASUSER 8192 04/26/05 13:57:34 *DIR dir1/
+ ASUSER 365255 02/28/01 15:41:40 *STMF readme.txt
+ ASUSER 8489625 03/18/03 09:37:00 *STMF saved.zip
+ ASUSER 365255 02/28/01 15:41:40 *STMF unist.old
+ */
+
+static gboolean
+ftpfs_parse_long_list_AS400 (char *line, struct stat *s, char **filename, char **linkname, int *err)
+{
+ char *t;
+ char *user;
+ long long size;
+ int month, day, year, hour, minute, second;
+ struct tm tms;
+ time_t mtime;
+ mode_t type;
+ char *slash;
+
+ t = FIRST_TOKEN;
+ if (t == NULL)
+ ERR2;
+ user = t;
+
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+ if (sscanf (t, "%lld", &size) != 1)
+ ERR2;
+
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+ if (sscanf (t, "%2d/%2d/%2d", &month, &day, &year) != 3)
+ ERR2;
+ if (year >= 70)
+ year += 1900;
+ else
+ year += 2000;
+
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+ if (sscanf (t, "%2d:%2d:%2d", &hour, &minute, &second) != 3)
+ ERR2;
+
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+
+ tms.tm_sec = second; /* seconds after the minute [0, 61] */
+ tms.tm_min = minute; /* minutes after the hour [0, 59] */
+ tms.tm_hour = hour; /* hour since midnight [0, 23] */
+ tms.tm_mday = day; /* day of the month [1, 31] */
+ tms.tm_mon = month - 1; /* months since January [0, 11] */
+ tms.tm_year = year - 1900; /* years since 1900 */
+ tms.tm_isdst = -1;
+ mtime = mktime (&tms);
+
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+ if (strcmp (t, "*DIR") == 0)
+ type = S_IFDIR;
+ else
+ type = S_IFREG;
+
+ t = strtok (NULL, "");
+ if (t == NULL)
+ ERR2;
+ while (*t == ' ')
+ t++;
+ if (*t == '\0')
+ ERR2;
+
+ *linkname = NULL;
+
+ slash = strchr (t, '/');
+ if (slash != NULL)
+ {
+ if (slash == t)
+ return FALSE;
+
+ *slash = '\0';
+ type = S_IFDIR;
+ if (slash[1] != '\0')
+ {
+ *filename = g_strdup (t);
+ s->st_mode = type; /* FIXME */
+ return TRUE;
+ }
+ }
+
+ *filename = g_strdup (t);
+ s->st_mode = type;
+ s->st_size = (off_t) size;
+ s->st_mtime = mtime;
+ /* Use resulting time value */
+ s->st_atime = s->st_ctime = s->st_mtime;
+ s->st_uid = ftpfs_get_uid (user);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/*
+ 0 DIR 06-27-96 11:57 PROTOCOL
+ 169 11-29-94 09:20 SYSLEVEL.MPT
+ */
+
+static gboolean
+ftpfs_parse_long_list_OS2 (char *line, struct stat *s, char **filename, char **linkname, int *err)
+{
+ char *t;
+ long long size;
+ int month, day, year, hour, minute;
+ struct tm tms;
+
+ t = FIRST_TOKEN;
+ if (t == NULL)
+ ERR2;
+
+ if (sscanf (t, "%lld", &size) != 1)
+ ERR2;
+ s->st_size = (off_t) size;
+
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+ s->st_mode = S_IFREG;
+ if (strcmp (t, "DIR") == 0)
+ {
+ s->st_mode = S_IFDIR;
+ t = NEXT_TOKEN;
+
+ if (t == NULL)
+ ERR2;
+ }
+
+ if (sscanf (t, "%2d-%2d-%2d", &month, &day, &year) != 3)
+ ERR2;
+ if (year >= 70)
+ year += 1900;
+ else
+ year += 2000;
+
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+ if (sscanf (t, "%2d:%2d", &hour, &minute) != 3)
+ ERR2;
+
+ tms.tm_sec = 30; /* seconds after the minute [0, 61] */
+ tms.tm_min = minute; /* minutes after the hour [0, 59] */
+ tms.tm_hour = hour; /* hour since midnight [0, 23] */
+ tms.tm_mday = day; /* day of the month [1, 31] */
+ tms.tm_mon = month - 1; /* months since January [0, 11] */
+ tms.tm_year = year - 1900; /* years since 1900 */
+ tms.tm_isdst = -1;
+ s->st_mtime = mktime (&tms);
+ /* Use resulting time value */
+ s->st_atime = s->st_ctime = s->st_mtime;
+
+ t = strtok (NULL, "");
+ if (t == NULL)
+ ERR2;
+ while (*t == ' ')
+ t++;
+ if (*t == '\0')
+ ERR2;
+ *filename = g_strdup (t);
+ *linkname = NULL;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+ftpfs_parse_long_list_MacWebStar (char *line, struct stat *s, char **filename,
+ char **linkname, int *err)
+{
+ char *t;
+ mode_t type, mode;
+ struct tm date;
+ const char *day_of_month;
+ char *name;
+
+ t = FIRST_TOKEN;
+ if (t == NULL)
+ ERR2;
+
+ if (!vfs_parse_filetype (t, NULL, &type))
+ ERR2;
+
+ s->st_mode = type;
+
+ if (!vfs_parse_fileperms (t + 1, NULL, &mode))
+ ERR2;
+ /* permissions are meaningless here. */
+
+ /* "folder" or 0 */
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+
+ if (strcmp (t, "folder") != 0)
+ {
+ long long size;
+
+ /* size? */
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+ /* size */
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+ if (!isdigit ((unsigned char) *t))
+ ERR2;
+
+ if (sscanf (t, "%lld", &size) == 1)
+ s->st_size = (off_t) size;
+ }
+ else
+ {
+ /* ?? */
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+ }
+
+ /* month */
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+
+ memset (&date, 0, sizeof (date));
+
+ if (!vfs_parse_month (t, &date))
+ ERR2;
+
+ day_of_month = NEXT_TOKEN;
+ if (day_of_month == NULL)
+ ERR2;
+
+ date.tm_mday = atoi (day_of_month);
+
+ /* time or year */
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+ if (!parse_year_or_time (t, &date.tm_year, &date.tm_hour, &date.tm_min))
+ ERR2;
+
+ date.tm_isdst = -1;
+ date.tm_sec = 30;
+ if (date.tm_year == -1)
+ date.tm_year = guess_year (date.tm_mon, date.tm_mday, date.tm_hour, date.tm_min) - 1900;
+ else
+ date.tm_hour = 12;
+
+ s->st_mtime = mktime (&date);
+ /* Use resulting time value */
+ s->st_atime = s->st_ctime = s->st_mtime;
+
+ name = strtok (NULL, "");
+ if (name == NULL)
+ ERR2;
+
+ /* no symlinks on Mac, but anyway. */
+ if (!S_ISLNK (s->st_mode))
+ *linkname = NULL;
+ else
+ {
+ char *arrow;
+
+ for (arrow = name; (arrow = strstr (arrow, " -> ")) != NULL; arrow++)
+ if (arrow != name && arrow[4] != '\0')
+ {
+ *arrow = '\0';
+ *linkname = g_strdup (arrow + 4);
+ break;
+ }
+ }
+
+ *filename = g_strdup (name);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+GSList *
+ftpfs_parse_long_list (struct vfs_class * me, struct vfs_s_inode * dir, GSList * buf, int *err_ret)
+{
+ int err[number_of_parsers];
+ GSList *set[number_of_parsers]; /* arrays of struct vfs_s_entry */
+ size_t i;
+ GSList *bufp;
+ ftpfs_line_parser guessed_parser = NULL;
+ GSList **the_set = NULL;
+ int *the_err = NULL;
+ int *best_err1 = &err[0];
+ int *best_err2 = &err[1];
+
+ ftpfs_init_time ();
+
+ if (err_ret != NULL)
+ *err_ret = 0;
+
+ memset (&err, 0, sizeof (err));
+ memset (&set, 0, sizeof (set));
+
+ for (bufp = buf; bufp != NULL; bufp = g_slist_next (bufp))
+ {
+ char *b = (char *) bufp->data;
+ size_t blen;
+
+ blen = strlen (b);
+
+ if (b[blen - 1] == '\r')
+ {
+ b[blen - 1] = '\0';
+ blen--;
+ }
+
+ if (blen == 0)
+ continue;
+
+ if (guessed_parser == NULL)
+ {
+ for (i = 0; i < number_of_parsers; i++)
+ {
+ struct vfs_s_entry *info;
+ gboolean ok;
+ char *tmp_line;
+ int nlink;
+
+ /* parser can clobber the line - work on a copy */
+ tmp_line = g_strndup (b, blen);
+
+ info = vfs_s_generate_entry (me, NULL, dir, 0);
+ nlink = info->ino->st.st_nlink;
+ ok = (*line_parsers[i]) (tmp_line, &info->ino->st, &info->name,
+ &info->ino->linkname, &err[i]);
+ if (ok && strchr (info->name, '/') == NULL)
+ {
+ info->ino->st.st_nlink = nlink; /* Ouch, we need to preserve our counts :-( */
+ set[i] = g_slist_prepend (set[i], info);
+ }
+ else
+ vfs_s_free_entry (me, info);
+
+ g_free (tmp_line);
+
+ if (*best_err1 > err[i])
+ best_err1 = &err[i];
+ if (*best_err2 > err[i] && best_err1 != &err[i])
+ best_err2 = &err[i];
+
+ if (*best_err1 > 16)
+ goto leave; /* too many errors with best parser. */
+ }
+
+ if (*best_err2 > (*best_err1 + 1) * 16)
+ {
+ i = (size_t) (best_err1 - err);
+ guessed_parser = line_parsers[i];
+ the_set = &set[i];
+ the_err = &err[i];
+ }
+ }
+ else
+ {
+ struct vfs_s_entry *info;
+ gboolean ok;
+ char *tmp_line;
+ int nlink;
+
+ /* parser can clobber the line - work on a copy */
+ tmp_line = g_strndup (b, blen);
+
+ info = vfs_s_generate_entry (me, NULL, dir, 0);
+ nlink = info->ino->st.st_nlink;
+ ok = guessed_parser (tmp_line, &info->ino->st, &info->name, &info->ino->linkname,
+ the_err);
+ if (ok && strchr (info->name, '/') == NULL)
+ {
+ info->ino->st.st_nlink = nlink; /* Ouch, we need to preserve our counts :-( */
+ *the_set = g_slist_prepend (*the_set, info);
+ }
+ else
+ vfs_s_free_entry (me, info);
+
+ g_free (tmp_line);
+ }
+ }
+
+ if (the_set == NULL)
+ {
+ i = best_err1 - err;
+ the_set = &set[i];
+ the_err = &err[i];
+ }
+
+ leave:
+ for (i = 0; i < number_of_parsers; i++)
+ if (&set[i] != the_set)
+ {
+ for (bufp = set[i]; bufp != NULL; bufp = g_slist_next (bufp))
+ vfs_s_free_entry (me, VFS_ENTRY (bufp->data));
+
+ g_slist_free (set[i]);
+ }
+
+ if (err_ret != NULL && the_err != NULL)
+ *err_ret = *the_err;
+
+ return the_set != NULL ? g_slist_reverse (*the_set) : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/local/Makefile.am b/src/vfs/local/Makefile.am
new file mode 100644
index 0000000..0176d46
--- /dev/null
+++ b/src/vfs/local/Makefile.am
@@ -0,0 +1,7 @@
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+
+noinst_LTLIBRARIES = libvfs-local.la
+
+libvfs_local_la_SOURCES = \
+ local.c local.h
diff --git a/src/vfs/local/Makefile.in b/src/vfs/local/Makefile.in
new file mode 100644
index 0000000..6d79948
--- /dev/null
+++ b/src/vfs/local/Makefile.in
@@ -0,0 +1,735 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/vfs/local
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libvfs_local_la_LIBADD =
+am_libvfs_local_la_OBJECTS = local.lo
+libvfs_local_la_OBJECTS = $(am_libvfs_local_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/local.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libvfs_local_la_SOURCES)
+DIST_SOURCES = $(libvfs_local_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+noinst_LTLIBRARIES = libvfs-local.la
+libvfs_local_la_SOURCES = \
+ local.c local.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/vfs/local/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/local/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libvfs-local.la: $(libvfs_local_la_OBJECTS) $(libvfs_local_la_DEPENDENCIES) $(EXTRA_libvfs_local_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libvfs_local_la_OBJECTS) $(libvfs_local_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/local.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/local.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/local.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/vfs/local/local.c b/src/vfs/local/local.c
new file mode 100644
index 0000000..a777c84
--- /dev/null
+++ b/src/vfs/local/local.c
@@ -0,0 +1,523 @@
+/*
+ Virtual File System: local file system.
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * \brief Source: local FS
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#ifdef ENABLE_EXT2FS_ATTR
+#include <e2p/e2p.h> /* fgetflags(), fsetflags() */
+#endif
+
+#include "lib/global.h"
+
+#include "lib/vfs/xdirentry.h" /* vfs_s_subclass */
+#include "lib/vfs/utilvfs.h"
+
+#include "local.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static struct vfs_s_subclass local_subclass;
+static struct vfs_class *vfs_local_ops = VFS_CLASS (&local_subclass);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+local_open (const vfs_path_t * vpath, int flags, mode_t mode)
+{
+ int *local_info;
+ int fd;
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ fd = open (path, NO_LINEAR (flags), mode);
+ if (fd == -1)
+ return 0;
+
+ local_info = g_new (int, 1);
+ *local_info = fd;
+
+ return local_info;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+local_opendir (const vfs_path_t * vpath)
+{
+ DIR **local_info;
+ DIR *dir = NULL;
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+
+ /* On Linux >= 5.1, MC sometimes shows empty directories on mounted CIFS shares.
+ * Rereading directory restores the directory content.
+ *
+ * Reopen directory, if first readdir() returns NULL and errno == EINTR.
+ */
+ while (dir == NULL)
+ {
+ dir = opendir (path);
+ if (dir == NULL)
+ return NULL;
+
+ if (readdir (dir) == NULL && errno == EINTR)
+ {
+ closedir (dir);
+ dir = NULL;
+ }
+ else
+ rewinddir (dir);
+ }
+
+ local_info = (DIR **) g_new (DIR *, 1);
+ *local_info = dir;
+
+ return local_info;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_dirent *
+local_readdir (void *data)
+{
+ struct dirent *d;
+
+ d = readdir (*(DIR **) data);
+
+ return (d != NULL ? vfs_dirent_init (NULL, d->d_name, d->d_ino) : NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_closedir (void *data)
+{
+ int i;
+
+ i = closedir (*(DIR **) data);
+ g_free (data);
+ return i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_stat (const vfs_path_t * vpath, struct stat *buf)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ return stat (path, buf);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_lstat (const vfs_path_t * vpath, struct stat *buf)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+#ifndef HAVE_STATLSTAT
+ return lstat (path, buf);
+#else
+ return statlstat (path, buf);
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_chmod (const vfs_path_t * vpath, mode_t mode)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ return chmod (path, mode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_chown (const vfs_path_t * vpath, uid_t owner, gid_t group)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ return chown (path, owner, group);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_EXT2FS_ATTR
+
+static int
+local_fgetflags (const vfs_path_t * vpath, unsigned long *flags)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ return fgetflags (path, flags);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_fsetflags (const vfs_path_t * vpath, unsigned long flags)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ return fsetflags (path, flags);
+}
+
+#endif /* ENABLE_EXT2FS_ATTR */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_utime (const vfs_path_t * vpath, mc_timesbuf_t * times)
+{
+ int ret;
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+#ifdef HAVE_UTIMENSAT
+ ret = utimensat (AT_FDCWD, path, *times, AT_SYMLINK_NOFOLLOW);
+#else
+ ret = utime (path, times);
+#endif
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_readlink (const vfs_path_t * vpath, char *buf, size_t size)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ return readlink (path, buf, size);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_unlink (const vfs_path_t * vpath)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ return unlink (path);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_symlink (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ const char *path1, *path2;
+
+ path1 = vfs_path_get_last_path_str (vpath1);
+ path2 = vfs_path_get_last_path_str (vpath2);
+ return symlink (path1, path2);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+local_write (void *data, const char *buf, size_t nbyte)
+{
+ int fd;
+ int n;
+
+ if (data == NULL)
+ return (-1);
+
+ fd = *(int *) data;
+
+ while ((n = write (fd, buf, nbyte)) == -1)
+ {
+#ifdef EAGAIN
+ if (errno == EAGAIN)
+ continue;
+#endif
+#ifdef EINTR
+ if (errno == EINTR)
+ continue;
+#endif
+ break;
+ }
+
+ return n;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_rename (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ const char *path1, *path2;
+
+ path1 = vfs_path_get_last_path_str (vpath1);
+ path2 = vfs_path_get_last_path_str (vpath2);
+ return rename (path1, path2);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_chdir (const vfs_path_t * vpath)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ return chdir (path);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_mknod (const vfs_path_t * vpath, mode_t mode, dev_t dev)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ return mknod (path, mode, dev);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_link (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ const char *path1, *path2;
+
+ path1 = vfs_path_get_last_path_str (vpath1);
+ path2 = vfs_path_get_last_path_str (vpath2);
+ return link (path1, path2);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_mkdir (const vfs_path_t * vpath, mode_t mode)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ return mkdir (path, mode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_rmdir (const vfs_path_t * vpath)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ return rmdir (path);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_path_t *
+local_getlocalcopy (const vfs_path_t * vpath)
+{
+ return vfs_path_clone (vpath);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_ungetlocalcopy (const vfs_path_t * vpath, const vfs_path_t * local, gboolean has_changed)
+{
+ (void) vpath;
+ (void) local;
+ (void) has_changed;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_which (struct vfs_class *me, const char *path)
+{
+ (void) me;
+ (void) path;
+
+ return 0; /* Every path which other systems do not like is expected to be ours */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+ssize_t
+local_read (void *data, char *buffer, size_t count)
+{
+ int n;
+ int fd;
+
+ if (data == NULL)
+ return (-1);
+
+ fd = *(int *) data;
+
+ while ((n = read (fd, buffer, count)) == -1)
+ {
+#ifdef EAGAIN
+ if (errno == EAGAIN)
+ continue;
+#endif
+#ifdef EINTR
+ if (errno == EINTR)
+ continue;
+#endif
+ return (-1);
+ }
+
+ return n;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+local_close (void *data)
+{
+ int fd;
+
+ if (data == NULL)
+ return (-1);
+
+ fd = *(int *) data;
+ g_free (data);
+ return close (fd);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+local_errno (struct vfs_class *me)
+{
+ (void) me;
+ return errno;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+local_fstat (void *data, struct stat *buf)
+{
+ int fd = *(int *) data;
+
+ return fstat (fd, buf);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+off_t
+local_lseek (void *data, off_t offset, int whence)
+{
+ int fd = *(int *) data;
+
+ return lseek (fd, offset, whence);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+local_nothingisopen (vfsid id)
+{
+ (void) id;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init_localfs (void)
+{
+ /* NULLize vfs_s_subclass members */
+ memset (&local_subclass, 0, sizeof (local_subclass));
+
+ vfs_init_class (vfs_local_ops, "localfs", VFSF_LOCAL, NULL);
+ vfs_local_ops->which = local_which;
+ vfs_local_ops->open = local_open;
+ vfs_local_ops->close = local_close;
+ vfs_local_ops->read = local_read;
+ vfs_local_ops->write = local_write;
+ vfs_local_ops->opendir = local_opendir;
+ vfs_local_ops->readdir = local_readdir;
+ vfs_local_ops->closedir = local_closedir;
+ vfs_local_ops->stat = local_stat;
+ vfs_local_ops->lstat = local_lstat;
+ vfs_local_ops->fstat = local_fstat;
+ vfs_local_ops->chmod = local_chmod;
+ vfs_local_ops->chown = local_chown;
+#ifdef ENABLE_EXT2FS_ATTR
+ vfs_local_ops->fgetflags = local_fgetflags;
+ vfs_local_ops->fsetflags = local_fsetflags;
+#endif
+ vfs_local_ops->utime = local_utime;
+ vfs_local_ops->readlink = local_readlink;
+ vfs_local_ops->symlink = local_symlink;
+ vfs_local_ops->link = local_link;
+ vfs_local_ops->unlink = local_unlink;
+ vfs_local_ops->rename = local_rename;
+ vfs_local_ops->chdir = local_chdir;
+ vfs_local_ops->ferrno = local_errno;
+ vfs_local_ops->lseek = local_lseek;
+ vfs_local_ops->mknod = local_mknod;
+ vfs_local_ops->getlocalcopy = local_getlocalcopy;
+ vfs_local_ops->ungetlocalcopy = local_ungetlocalcopy;
+ vfs_local_ops->mkdir = local_mkdir;
+ vfs_local_ops->rmdir = local_rmdir;
+ vfs_local_ops->nothingisopen = local_nothingisopen;
+ vfs_register_class (vfs_local_ops);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/local/local.h b/src/vfs/local/local.h
new file mode 100644
index 0000000..8929d10
--- /dev/null
+++ b/src/vfs/local/local.h
@@ -0,0 +1,32 @@
+/**
+ * \file
+ * \brief Header: local FS
+ */
+
+#ifndef MC__VFS_LOCAL_H
+#define MC__VFS_LOCAL_H
+
+#include "lib/vfs/vfs.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+extern void vfs_init_localfs (void);
+
+/* these functions are used by other filesystems, so they are
+ * published here. */
+extern int local_close (void *data);
+extern ssize_t local_read (void *data, char *buffer, size_t count);
+extern int local_fstat (void *data, struct stat *buf);
+extern int local_errno (struct vfs_class *me);
+extern off_t local_lseek (void *data, off_t offset, int whence);
+
+/*** inline functions ****************************************************************************/
+#endif
diff --git a/src/vfs/plugins_init.c b/src/vfs/plugins_init.c
new file mode 100644
index 0000000..767e284
--- /dev/null
+++ b/src/vfs/plugins_init.c
@@ -0,0 +1,123 @@
+/*
+ Init VFS plugins.
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2011.
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * \brief This is a template file (here goes brief description).
+ * \author Author1
+ * \author Author2
+ * \date 20xx
+ *
+ * Detailed description.
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+
+#include "local/local.h"
+
+#ifdef ENABLE_VFS_CPIO
+#include "cpio/cpio.h"
+#endif
+
+#ifdef ENABLE_VFS_EXTFS
+#include "extfs/extfs.h"
+#endif
+
+#ifdef ENABLE_VFS_FISH
+#include "fish/fish.h"
+#endif
+
+#ifdef ENABLE_VFS_FTP
+#include "ftpfs/ftpfs.h"
+#endif
+
+#ifdef ENABLE_VFS_SFTP
+#include "sftpfs/sftpfs.h"
+#endif
+
+#ifdef ENABLE_VFS_SFS
+#include "sfs/sfs.h"
+#endif
+
+#ifdef ENABLE_VFS_TAR
+#include "tar/tar.h"
+#endif
+
+#ifdef ENABLE_VFS_UNDELFS
+#include "undelfs/undelfs.h"
+#endif
+
+#include "plugins_init.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_plugins_init (void)
+{
+ /* localfs needs to be the first one */
+ vfs_init_localfs ();
+
+#ifdef ENABLE_VFS_CPIO
+ vfs_init_cpiofs ();
+#endif /* ENABLE_VFS_CPIO */
+#ifdef ENABLE_VFS_TAR
+ vfs_init_tarfs ();
+#endif /* ENABLE_VFS_TAR */
+#ifdef ENABLE_VFS_SFS
+ vfs_init_sfs ();
+#endif /* ENABLE_VFS_SFS */
+#ifdef ENABLE_VFS_EXTFS
+ vfs_init_extfs ();
+#endif /* ENABLE_VFS_EXTFS */
+#ifdef ENABLE_VFS_UNDELFS
+ vfs_init_undelfs ();
+#endif /* ENABLE_VFS_UNDELFS */
+
+#ifdef ENABLE_VFS_FTP
+ vfs_init_ftpfs ();
+#endif /* ENABLE_VFS_FTP */
+#ifdef ENABLE_VFS_SFTP
+ vfs_init_sftpfs ();
+#endif /* ENABLE_VFS_SFTP */
+#ifdef ENABLE_VFS_FISH
+ vfs_init_fish ();
+#endif /* ENABLE_VFS_FISH */
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/plugins_init.h b/src/vfs/plugins_init.h
new file mode 100644
index 0000000..9a36f18
--- /dev/null
+++ b/src/vfs/plugins_init.h
@@ -0,0 +1,18 @@
+#ifndef MC__VFS_PLUINS_INIT_H
+#define MC__VFS_PLUINS_INIT_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void vfs_plugins_init (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__VFS_PLUINS_INIT_H */
diff --git a/src/vfs/sfs/Makefile.am b/src/vfs/sfs/Makefile.am
new file mode 100644
index 0000000..7de97d0
--- /dev/null
+++ b/src/vfs/sfs/Makefile.am
@@ -0,0 +1,16 @@
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+
+noinst_LTLIBRARIES = libvfs-sfs.la
+
+libvfs_sfs_la_SOURCES = \
+ sfs.c sfs.h
+
+SFSCONFFILES = sfs.ini
+
+if ENABLE_VFS_SFS
+sfsconfdir = $(sysconfdir)/@PACKAGE@
+sfsconf_DATA = $(SFSCONFFILES)
+endif
+
+EXTRA_DIST = $(SFSCONFFILES)
diff --git a/src/vfs/sfs/Makefile.in b/src/vfs/sfs/Makefile.in
new file mode 100644
index 0000000..f9893eb
--- /dev/null
+++ b/src/vfs/sfs/Makefile.in
@@ -0,0 +1,794 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/vfs/sfs
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libvfs_sfs_la_LIBADD =
+am_libvfs_sfs_la_OBJECTS = sfs.lo
+libvfs_sfs_la_OBJECTS = $(am_libvfs_sfs_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/sfs.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libvfs_sfs_la_SOURCES)
+DIST_SOURCES = $(libvfs_sfs_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(sfsconfdir)"
+DATA = $(sfsconf_DATA)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+noinst_LTLIBRARIES = libvfs-sfs.la
+libvfs_sfs_la_SOURCES = \
+ sfs.c sfs.h
+
+SFSCONFFILES = sfs.ini
+@ENABLE_VFS_SFS_TRUE@sfsconfdir = $(sysconfdir)/@PACKAGE@
+@ENABLE_VFS_SFS_TRUE@sfsconf_DATA = $(SFSCONFFILES)
+EXTRA_DIST = $(SFSCONFFILES)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/vfs/sfs/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/sfs/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libvfs-sfs.la: $(libvfs_sfs_la_OBJECTS) $(libvfs_sfs_la_DEPENDENCIES) $(EXTRA_libvfs_sfs_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libvfs_sfs_la_OBJECTS) $(libvfs_sfs_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sfs.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-sfsconfDATA: $(sfsconf_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(sfsconf_DATA)'; test -n "$(sfsconfdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(sfsconfdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(sfsconfdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(sfsconfdir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(sfsconfdir)" || exit $$?; \
+ done
+
+uninstall-sfsconfDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(sfsconf_DATA)'; test -n "$(sfsconfdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(sfsconfdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(DATA)
+installdirs:
+ for dir in "$(DESTDIR)$(sfsconfdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/sfs.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-sfsconfDATA
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/sfs.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-sfsconfDATA
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-sfsconfDATA \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-sfsconfDATA
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/vfs/sfs/sfs.c b/src/vfs/sfs/sfs.c
new file mode 100644
index 0000000..fdcc823
--- /dev/null
+++ b/src/vfs/sfs/sfs.c
@@ -0,0 +1,604 @@
+/*
+ Single File fileSystem
+
+ Copyright (C) 1998-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * \brief Source: Single File fileSystem
+ *
+ * This defines whole class of filesystems which contain single file
+ * inside. It is somehow similar to extfs, except that extfs makes
+ * whole virtual trees and we do only single virtual files.
+ *
+ * If you want to gunzip something, you should open it with \verbatim #ugz \endverbatim
+ * suffix, DON'T try to gunzip it yourself.
+ *
+ * Namespace: exports vfs_sfs_ops
+ */
+
+#include <config.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/util.h"
+#include "lib/widget.h" /* D_ERROR, D_NORMAL */
+
+#include "src/execute.h" /* EXECUTE_AS_SHELL */
+
+#include "lib/vfs/vfs.h"
+#include "lib/vfs/utilvfs.h"
+#include "lib/vfs/xdirentry.h"
+#include "src/vfs/local/local.h"
+#include "lib/vfs/gc.h" /* vfs_stamp_create */
+
+#include "sfs.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define MAXFS 32
+
+typedef enum
+{
+ F_NONE = 0x0,
+ F_1 = 0x1,
+ F_2 = 0x2,
+ F_NOLOCALCOPY = 0x4,
+ F_FULLMATCH = 0x8
+} sfs_flags_t;
+
+#define COPY_CHAR \
+ if ((size_t) (t - pad) > sizeof (pad)) \
+ { \
+ g_free (pqname); \
+ return (-1); \
+ } \
+ else \
+ *t++ = *s_iter;
+
+#define COPY_STRING(a) \
+ if ((t - pad) + strlen (a) > sizeof (pad)) \
+ { \
+ g_free (pqname); \
+ return (-1); \
+ } \
+ else \
+ { \
+ strcpy (t, a); \
+ t += strlen (a); \
+ }
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct cachedfile
+{
+ char *name;
+ char *cache;
+} cachedfile;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static GSList *head = NULL;
+
+static struct vfs_s_subclass sfs_subclass;
+static struct vfs_class *vfs_sfs_ops = VFS_CLASS (&sfs_subclass);
+
+static int sfs_no = 0;
+static struct
+{
+ char *prefix;
+ char *command;
+ sfs_flags_t flags;
+} sfs_info[MAXFS];
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+cachedfile_compare (const void *a, const void *b)
+{
+ const cachedfile *cf = (const cachedfile *) a;
+ const char *name = (const char *) b;
+
+ return strcmp (name, cf->name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sfs_vfmake (const vfs_path_t * vpath, vfs_path_t * cache_vpath)
+{
+ int w;
+ char pad[10240];
+ char *s_iter, *t = pad;
+ gboolean was_percent = FALSE;
+ vfs_path_t *pname; /* name of parent archive */
+ char *pqname; /* name of parent archive, quoted */
+ const vfs_path_element_t *path_element;
+ mc_pipe_t *pip;
+ GError *error = NULL;
+
+ path_element = vfs_path_get_by_index (vpath, -1);
+ pname = vfs_path_clone (vpath);
+ vfs_path_remove_element_by_index (pname, -1);
+
+ w = path_element->class->which (path_element->class, path_element->vfs_prefix);
+ if (w == -1)
+ vfs_die ("This cannot happen... Hopefully.\n");
+
+ if ((sfs_info[w].flags & F_1) == 0
+ && strcmp (vfs_path_get_last_path_str (pname), PATH_SEP_STR) != 0)
+ {
+ vfs_path_free (pname, TRUE);
+ return (-1);
+ }
+
+ /* if ((sfs_info[w].flags & F_2) || (!inpath) || (!*inpath)); else return -1; */
+ if ((sfs_info[w].flags & F_NOLOCALCOPY) != 0)
+ pqname = name_quote (vfs_path_as_str (pname), FALSE);
+ else
+ {
+ vfs_path_t *s;
+
+ s = mc_getlocalcopy (pname);
+ if (s == NULL)
+ {
+ vfs_path_free (pname, TRUE);
+ return (-1);
+ }
+
+ pqname = name_quote (vfs_path_get_last_path_str (s), FALSE);
+ vfs_path_free (s, TRUE);
+ }
+
+ vfs_path_free (pname, TRUE);
+
+ for (s_iter = sfs_info[w].command; *s_iter != '\0'; s_iter++)
+ {
+ if (was_percent)
+ {
+ const char *ptr = NULL;
+
+ was_percent = FALSE;
+
+ switch (*s_iter)
+ {
+ case '1':
+ ptr = pqname;
+ break;
+ case '2':
+ ptr = path_element->path;
+ break;
+ case '3':
+ ptr = vfs_path_get_last_path_str (cache_vpath);
+ break;
+ case '%':
+ COPY_CHAR;
+ continue;
+ default:
+ break;
+ }
+
+ if (ptr != NULL)
+ {
+ COPY_STRING (ptr);
+ }
+ }
+ else if (*s_iter == '%')
+ was_percent = TRUE;
+ else
+ {
+ COPY_CHAR;
+ }
+ }
+
+ g_free (pqname);
+
+ /* don't read stdout */
+ pip = mc_popen (pad, FALSE, TRUE, &error);
+ if (pip == NULL)
+ {
+ message (D_ERROR, MSG_ERROR, _("SFS virtual file system:\n%s"), error->message);
+ g_error_free (error);
+ return (-1);
+ }
+
+ pip->err.null_term = TRUE;
+
+ mc_pread (pip, &error);
+ if (error != NULL)
+ {
+ message (D_ERROR, MSG_ERROR, _("SFS virtual file system:\n%s"), error->message);
+ g_error_free (error);
+ mc_pclose (pip, NULL);
+ return (-1);
+ }
+
+ if (pip->err.len > 0)
+ message (D_ERROR, MSG_ERROR, _("SFS virtual file system:\n%s"), pip->err.buf);
+
+ mc_pclose (pip, NULL);
+ return 0; /* OK */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+sfs_redirect (const vfs_path_t * vpath)
+{
+ GSList *cur;
+ cachedfile *cf;
+ vfs_path_t *cache_vpath;
+ int handle;
+
+ cur = g_slist_find_custom (head, vfs_path_as_str (vpath), cachedfile_compare);
+
+ if (cur != NULL)
+ {
+ cf = (cachedfile *) cur->data;
+ vfs_stamp (vfs_sfs_ops, cf);
+ return cf->cache;
+ }
+
+ handle = vfs_mkstemps (&cache_vpath, "sfs", vfs_path_get_last_path_str (vpath));
+
+ if (handle == -1)
+ return "/SOMEONE_PLAYING_DIRTY_TMP_TRICKS_ON_US";
+
+ close (handle);
+
+ if (sfs_vfmake (vpath, cache_vpath) == 0)
+ {
+ cf = g_new (cachedfile, 1);
+ cf->name = g_strdup (vfs_path_as_str (vpath));
+ cf->cache = vfs_path_free (cache_vpath, FALSE);
+ head = g_slist_prepend (head, cf);
+
+ vfs_stamp_create (vfs_sfs_ops, (cachedfile *) head->data);
+ return cf->cache;
+ }
+
+ mc_unlink (cache_vpath);
+ vfs_path_free (cache_vpath, TRUE);
+ return "/I_MUST_NOT_EXIST";
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+sfs_open (const vfs_path_t * vpath /*struct vfs_class *me, const char *path */ , int flags,
+ mode_t mode)
+{
+ int *info;
+ int fd;
+
+ fd = open (sfs_redirect (vpath), NO_LINEAR (flags), mode);
+ if (fd == -1)
+ return NULL;
+
+ info = g_new (int, 1);
+ *info = fd;
+
+ return info;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sfs_stat (const vfs_path_t * vpath, struct stat *buf)
+{
+ return stat (sfs_redirect (vpath), buf);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sfs_lstat (const vfs_path_t * vpath, struct stat *buf)
+{
+#ifndef HAVE_STATLSTAT
+ return lstat (sfs_redirect (vpath), buf);
+#else
+ return statlstat (sfs_redirect (vpath), buf);
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sfs_chmod (const vfs_path_t * vpath, mode_t mode)
+{
+ return chmod (sfs_redirect (vpath), mode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sfs_chown (const vfs_path_t * vpath, uid_t owner, gid_t group)
+{
+ return chown (sfs_redirect (vpath), owner, group);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sfs_utime (const vfs_path_t * vpath, mc_timesbuf_t * times)
+{
+#ifdef HAVE_UTIMENSAT
+ return utimensat (AT_FDCWD, sfs_redirect (vpath), *times, 0);
+#else
+ return utime (sfs_redirect (vpath), times);
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sfs_readlink (const vfs_path_t * vpath, char *buf, size_t size)
+{
+ return readlink (sfs_redirect (vpath), buf, size);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfsid
+sfs_getid (const vfs_path_t * vpath)
+{
+ GSList *cur;
+
+ cur = g_slist_find_custom (head, vfs_path_as_str (vpath), cachedfile_compare);
+
+ return (vfsid) (cur != NULL ? cur->data : NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+sfs_free (vfsid id)
+{
+ struct cachedfile *which;
+ GSList *cur;
+
+ which = (struct cachedfile *) id;
+ cur = g_slist_find (head, which);
+ if (cur == NULL)
+ vfs_die ("Free of thing which is unknown to me\n");
+
+ which = (struct cachedfile *) cur->data;
+ unlink (which->cache);
+ g_free (which->cache);
+ g_free (which->name);
+ g_free (which);
+
+ head = g_slist_delete_link (head, cur);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+sfs_fill_names (struct vfs_class *me, fill_names_f func)
+{
+ GSList *cur;
+
+ (void) me;
+
+ for (cur = head; cur != NULL; cur = g_slist_next (cur))
+ func (((cachedfile *) cur->data)->name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sfs_nothingisopen (vfsid id)
+{
+ /* FIXME: Investigate whether have to guard this like in
+ the other VFSs (see fd_usage in extfs) -- Norbert */
+ (void) id;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_path_t *
+sfs_getlocalcopy (const vfs_path_t * vpath)
+{
+ return vfs_path_from_str (sfs_redirect (vpath));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sfs_ungetlocalcopy (const vfs_path_t * vpath, const vfs_path_t * local, gboolean has_changed)
+{
+ (void) vpath;
+ (void) local;
+ (void) has_changed;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sfs_init (struct vfs_class *me)
+{
+ char *mc_sfsini;
+ FILE *cfg;
+ char key[256];
+
+ (void) me;
+
+ mc_sfsini = g_build_filename (mc_global.sysconfig_dir, "sfs.ini", (char *) NULL);
+ cfg = fopen (mc_sfsini, "r");
+
+ if (cfg == NULL)
+ {
+ fprintf (stderr, _("%s: Warning: file %s not found\n"), "sfs_init()", mc_sfsini);
+ g_free (mc_sfsini);
+ return 0;
+ }
+ g_free (mc_sfsini);
+
+ sfs_no = 0;
+ while (sfs_no < MAXFS && fgets (key, sizeof (key), cfg) != NULL)
+ {
+ char *c, *semi = NULL;
+ sfs_flags_t flags = F_NONE;
+
+ if (*key == '#' || *key == '\n')
+ continue;
+
+ for (c = key; *c != '\0'; c++)
+ if (*c == ':' || IS_PATH_SEP (*c))
+ {
+ semi = c;
+ if (IS_PATH_SEP (*c))
+ {
+ *c = '\0';
+ flags |= F_FULLMATCH;
+ }
+ break;
+ }
+
+ if (semi == NULL)
+ {
+ invalid_line:
+ fprintf (stderr, _("Warning: Invalid line in %s:\n%s\n"), "sfs.ini", key);
+ continue;
+ }
+
+ for (c = semi + 1; *c != '\0' && !whitespace (*c); c++)
+ switch (*c)
+ {
+ case '1':
+ flags |= F_1;
+ break;
+ case '2':
+ flags |= F_2;
+ break;
+ case 'R':
+ flags |= F_NOLOCALCOPY;
+ break;
+ default:
+ fprintf (stderr, _("Warning: Invalid flag %c in %s:\n%s\n"), *c, "sfs.ini", key);
+ }
+
+ if (*c == '\0')
+ goto invalid_line;
+
+ c++;
+ *(semi + 1) = '\0';
+ semi = strchr (c, '\n');
+ if (semi != NULL)
+ *semi = '\0';
+
+ sfs_info[sfs_no].prefix = g_strdup (key);
+ sfs_info[sfs_no].command = g_strdup (c);
+ sfs_info[sfs_no].flags = flags;
+ sfs_no++;
+ }
+ fclose (cfg);
+
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+sfs_done (struct vfs_class *me)
+{
+ int i;
+
+ (void) me;
+
+ for (i = 0; i < sfs_no; i++)
+ {
+ MC_PTR_FREE (sfs_info[i].prefix);
+ MC_PTR_FREE (sfs_info[i].command);
+ }
+ sfs_no = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sfs_which (struct vfs_class *me, const char *path)
+{
+ int i;
+
+ (void) me;
+
+ for (i = 0; i < sfs_no; i++)
+ if ((sfs_info[i].flags & F_FULLMATCH) != 0)
+ {
+ if (strcmp (path, sfs_info[i].prefix) == 0)
+ return i;
+ }
+ else if (strncmp (path, sfs_info[i].prefix, strlen (sfs_info[i].prefix)) == 0)
+ return i;
+
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init_sfs (void)
+{
+ /* NULLize vfs_s_subclass members */
+ memset (&sfs_subclass, 0, sizeof (sfs_subclass));
+
+ vfs_init_class (vfs_sfs_ops, "sfs", VFSF_UNKNOWN, NULL);
+ vfs_sfs_ops->init = sfs_init;
+ vfs_sfs_ops->done = sfs_done;
+ vfs_sfs_ops->fill_names = sfs_fill_names;
+ vfs_sfs_ops->which = sfs_which;
+ vfs_sfs_ops->open = sfs_open;
+ vfs_sfs_ops->close = local_close;
+ vfs_sfs_ops->read = local_read;
+ vfs_sfs_ops->stat = sfs_stat;
+ vfs_sfs_ops->lstat = sfs_lstat;
+ vfs_sfs_ops->fstat = local_fstat;
+ vfs_sfs_ops->chmod = sfs_chmod;
+ vfs_sfs_ops->chown = sfs_chown;
+ vfs_sfs_ops->utime = sfs_utime;
+ vfs_sfs_ops->readlink = sfs_readlink;
+ vfs_sfs_ops->ferrno = local_errno;
+ vfs_sfs_ops->lseek = local_lseek;
+ vfs_sfs_ops->getid = sfs_getid;
+ vfs_sfs_ops->nothingisopen = sfs_nothingisopen;
+ vfs_sfs_ops->free = sfs_free;
+ vfs_sfs_ops->getlocalcopy = sfs_getlocalcopy;
+ vfs_sfs_ops->ungetlocalcopy = sfs_ungetlocalcopy;
+ vfs_register_class (vfs_sfs_ops);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/sfs/sfs.h b/src/vfs/sfs/sfs.h
new file mode 100644
index 0000000..c846c93
--- /dev/null
+++ b/src/vfs/sfs/sfs.h
@@ -0,0 +1,18 @@
+#ifndef MC__VFS_SFS_H
+#define MC__VFS_SFS_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void vfs_init_sfs (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__VFS_SFS_H */
diff --git a/src/vfs/sfs/sfs.ini b/src/vfs/sfs/sfs.ini
new file mode 100644
index 0000000..d817dc9
--- /dev/null
+++ b/src/vfs/sfs/sfs.ini
@@ -0,0 +1,34 @@
+#
+# This is config for Single File fileSystem
+#
+# Notice that output files (%3) are pre-created atomically in /tmp
+# with 0600 rights, so it is safe to > %3
+#
+gz/1 gzip < %1 > %3
+ugz/1 gzip -cdf < %1 > %3
+bz/1 bzip < %1 > %3
+ubz/1 bzip -d < %1 > %3
+bz2/1 bzip2 < %1 > %3
+ubz2/1 bzip2 -d < %1 > %3
+lz/1 lzip < %1 > %3
+ulz/1 lzip -d < %1 > %3
+lz4/1 lz4 < %1 > %3
+ulz4/1 lz4 -d < %1 > %3
+lzma/1 lzma < %1 > %3
+ulzma/1 lzma -d < %1 > %3
+xz/1 xz < %1 > %3
+uxz/1 xz -d < %1 > %3
+zst/1 zstd < %1 > %3
+uzst/1 zstd -d < %1 > %3
+tar/1 tar cf %3 %1
+tgz/1 tar czf %3 %1
+uhtml/1 lynx -force_html -dump %1 > %3
+uman/1 groff -Tascii -man %1 > %3
+uue/1 uuenpipe < %1 > %3
+uude/1 uudepipe < %1 > %3
+crlf/1 todos < %1 > %3
+cr/1 fromdos < %1 > %3
+# Fixme: we need it to fail whenever it should
+url:2 lynx -source `echo "%2" | sed 's-|-/-g'` > %3
+nop/1 cat %1 > %3
+strings/1 strings %1 > %3
diff --git a/src/vfs/sftpfs/Makefile.am b/src/vfs/sftpfs/Makefile.am
new file mode 100644
index 0000000..12905d1
--- /dev/null
+++ b/src/vfs/sftpfs/Makefile.am
@@ -0,0 +1,12 @@
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir) $(LIBSSH_CFLAGS)
+
+noinst_LTLIBRARIES = libvfs-sftpfs.la
+
+libvfs_sftpfs_la_SOURCES = \
+ config_parser.c \
+ connection.c \
+ dir.c \
+ file.c \
+ internal.c internal.h \
+ sftpfs.c sftpfs.h
diff --git a/src/vfs/sftpfs/Makefile.in b/src/vfs/sftpfs/Makefile.in
new file mode 100644
index 0000000..e59e875
--- /dev/null
+++ b/src/vfs/sftpfs/Makefile.in
@@ -0,0 +1,759 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/vfs/sftpfs
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libvfs_sftpfs_la_LIBADD =
+am_libvfs_sftpfs_la_OBJECTS = config_parser.lo connection.lo dir.lo \
+ file.lo internal.lo sftpfs.lo
+libvfs_sftpfs_la_OBJECTS = $(am_libvfs_sftpfs_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/config_parser.Plo \
+ ./$(DEPDIR)/connection.Plo ./$(DEPDIR)/dir.Plo \
+ ./$(DEPDIR)/file.Plo ./$(DEPDIR)/internal.Plo \
+ ./$(DEPDIR)/sftpfs.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libvfs_sftpfs_la_SOURCES)
+DIST_SOURCES = $(libvfs_sftpfs_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir) $(LIBSSH_CFLAGS)
+noinst_LTLIBRARIES = libvfs-sftpfs.la
+libvfs_sftpfs_la_SOURCES = \
+ config_parser.c \
+ connection.c \
+ dir.c \
+ file.c \
+ internal.c internal.h \
+ sftpfs.c sftpfs.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/vfs/sftpfs/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/sftpfs/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libvfs-sftpfs.la: $(libvfs_sftpfs_la_OBJECTS) $(libvfs_sftpfs_la_DEPENDENCIES) $(EXTRA_libvfs_sftpfs_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libvfs_sftpfs_la_OBJECTS) $(libvfs_sftpfs_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/config_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/connection.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dir.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/internal.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sftpfs.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/config_parser.Plo
+ -rm -f ./$(DEPDIR)/connection.Plo
+ -rm -f ./$(DEPDIR)/dir.Plo
+ -rm -f ./$(DEPDIR)/file.Plo
+ -rm -f ./$(DEPDIR)/internal.Plo
+ -rm -f ./$(DEPDIR)/sftpfs.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/config_parser.Plo
+ -rm -f ./$(DEPDIR)/connection.Plo
+ -rm -f ./$(DEPDIR)/dir.Plo
+ -rm -f ./$(DEPDIR)/file.Plo
+ -rm -f ./$(DEPDIR)/internal.Plo
+ -rm -f ./$(DEPDIR)/sftpfs.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/vfs/sftpfs/config_parser.c b/src/vfs/sftpfs/config_parser.c
new file mode 100644
index 0000000..d3e2287
--- /dev/null
+++ b/src/vfs/sftpfs/config_parser.c
@@ -0,0 +1,427 @@
+/* Virtual File System: SFTP file system.
+ The SSH config parser
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Ilia Maslakov <il.smind@gmail.com>, 2011
+ Slava Zanko <slavazanko@gmail.com>, 2011, 2012, 2013
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdlib.h> /* atoi() */
+
+#include "lib/global.h"
+
+#include "lib/search.h"
+#include "lib/util.h" /* tilde_expand() */
+#include "lib/vfs/utilvfs.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define SFTP_DEFAULT_PORT 22
+
+#ifndef SFTPFS_SSH_CONFIG
+#define SFTPFS_SSH_CONFIG "~/.ssh/config"
+#endif
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ char *real_host; /* host DNS name or ip address */
+ int port; /* port for connect to host */
+ char *user; /* the user to log in as */
+ gboolean password_auth; /* FALSE - no passwords allowed (default TRUE) */
+ gboolean identities_only; /* TRUE - no ssh agent (default FALSE) */
+ gboolean pubkey_auth; /* FALSE - disable public key authentication (default TRUE) */
+ char *identity_file; /* A file from which the user's DSA, ECDSA or DSA authentication identity is read. */
+} sftpfs_ssh_config_entity_t;
+
+enum config_var_type
+{
+ STRING,
+ INTEGER,
+ BOOLEAN,
+ FILENAME
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* *INDENT-OFF* */
+static struct
+{
+ const char *pattern;
+ mc_search_t *pattern_regexp;
+ enum config_var_type type;
+ size_t offset;
+} config_variables[] =
+{
+ {"^\\s*User\\s+(.*)$", NULL, STRING, offsetof (sftpfs_ssh_config_entity_t, user)},
+ {"^\\s*HostName\\s+(.*)$", NULL, STRING, offsetof (sftpfs_ssh_config_entity_t, real_host)},
+ {"^\\s*IdentitiesOnly\\s+(.*)$", NULL, BOOLEAN, offsetof (sftpfs_ssh_config_entity_t, identities_only)},
+ {"^\\s*IdentityFile\\s+(.*)$", NULL, FILENAME, offsetof (sftpfs_ssh_config_entity_t, identity_file)},
+ {"^\\s*Port\\s+(.*)$", NULL, INTEGER, offsetof (sftpfs_ssh_config_entity_t, port)},
+ {"^\\s*PasswordAuthentication\\s+(.*)$", NULL, BOOLEAN, offsetof (sftpfs_ssh_config_entity_t, password_auth)},
+ {"^\\s*PubkeyAuthentication\\s+(.*)$", NULL, STRING, offsetof (sftpfs_ssh_config_entity_t, pubkey_auth)},
+ {NULL, NULL, 0, 0}
+};
+/* *INDENT-ON* */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Free one config entity.
+ *
+ * @param config_entity config entity structure
+ */
+
+static void
+sftpfs_ssh_config_entity_free (sftpfs_ssh_config_entity_t * config_entity)
+{
+ g_free (config_entity->real_host);
+ g_free (config_entity->user);
+ g_free (config_entity->identity_file);
+ g_free (config_entity);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Transform tilda (~) to full home dirname.
+ *
+ * @param filename file name with tilda
+ * @return newly allocated file name with full home dirname
+ */
+
+static char *
+sftpfs_correct_file_name (const char *filename)
+{
+ vfs_path_t *vpath;
+ char *fn;
+
+ fn = tilde_expand (filename);
+ vpath = vfs_path_from_str (fn);
+ g_free (fn);
+ return vfs_path_free (vpath, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#define POINTER_TO_STRUCTURE_MEMBER(type) \
+ ((type) ((char *) config_entity + (size_t) config_variables[i].offset))
+
+/**
+ * Parse string and filling one config entity by parsed data.
+ *
+ * @param config_entity config entity structure
+ * @param buffer string for parce
+ */
+
+static void
+sftpfs_fill_config_entity_from_string (sftpfs_ssh_config_entity_t * config_entity, char *buffer)
+{
+ int i;
+
+ for (i = 0; config_variables[i].pattern != NULL; i++)
+ {
+ if (mc_search_run (config_variables[i].pattern_regexp, buffer, 0, strlen (buffer), NULL))
+ {
+ int value_offset;
+ char *value;
+
+ int *pointer_int;
+ char **pointer_str;
+ gboolean *pointer_bool;
+
+ /* Calculate start of value in string */
+ value_offset = mc_search_getstart_result_by_num (config_variables[i].pattern_regexp, 1);
+ value = &buffer[value_offset];
+
+ switch (config_variables[i].type)
+ {
+ case STRING:
+ pointer_str = POINTER_TO_STRUCTURE_MEMBER (char **);
+ *pointer_str = g_strdup (value);
+ break;
+ case FILENAME:
+ pointer_str = POINTER_TO_STRUCTURE_MEMBER (char **);
+ *pointer_str = sftpfs_correct_file_name (value);
+ break;
+ case INTEGER:
+ pointer_int = POINTER_TO_STRUCTURE_MEMBER (int *);
+ *pointer_int = atoi (value);
+ break;
+ case BOOLEAN:
+ pointer_bool = POINTER_TO_STRUCTURE_MEMBER (gboolean *);
+ *pointer_bool = strcasecmp (value, "True") == 0;
+ break;
+ default:
+ continue;
+ }
+ return;
+ }
+ }
+}
+
+#undef POINTER_TO_STRUCTURE_MEMBER
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Fill one config entity from config file.
+ *
+ * @param ssh_config_handler file descriptor for the ssh config file
+ * @param config_entity config entity structure
+ * @param vpath_element path element with host data (hostname, port)
+ * @param mcerror pointer to the error handler
+ * @return TRUE if config entity was filled successfully, FALSE otherwise
+ */
+
+static gboolean
+sftpfs_fill_config_entity_from_config (FILE * ssh_config_handler,
+ sftpfs_ssh_config_entity_t * config_entity,
+ const vfs_path_element_t * vpath_element, GError ** mcerror)
+{
+ char buffer[BUF_MEDIUM];
+ gboolean host_block_hit = FALSE;
+ gboolean pattern_block_hit = FALSE;
+ mc_search_t *host_regexp;
+ gboolean ok = TRUE;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ host_regexp = mc_search_new ("^\\s*host\\s+(.*)$", DEFAULT_CHARSET);
+ host_regexp->search_type = MC_SEARCH_T_REGEX;
+ host_regexp->is_case_sensitive = FALSE;
+
+ while (TRUE)
+ {
+ char *cr;
+
+ if (fgets (buffer, sizeof (buffer), ssh_config_handler) == NULL)
+ {
+ int e;
+
+ e = errno;
+
+ if (!feof (ssh_config_handler))
+ {
+ mc_propagate_error (mcerror, e,
+ _("sftp: an error occurred while reading %s: %s"),
+ SFTPFS_SSH_CONFIG, strerror (e));
+ ok = FALSE;
+ goto done;
+ }
+
+ break;
+ }
+
+ cr = strrchr (buffer, '\n');
+ if (cr != NULL)
+ *cr = '\0';
+
+ if (mc_search_run (host_regexp, buffer, 0, strlen (buffer), NULL))
+ {
+ const char *host_pattern;
+ int host_pattern_offset;
+
+ /* if previous host block exactly describe our connection */
+ if (host_block_hit)
+ goto done;
+
+ host_pattern_offset = mc_search_getstart_result_by_num (host_regexp, 1);
+ host_pattern = &buffer[host_pattern_offset];
+ if (strcmp (host_pattern, vpath_element->host) == 0)
+ {
+ /* current host block describe our connection */
+ host_block_hit = TRUE;
+ }
+ else
+ {
+ mc_search_t *pattern_regexp;
+
+ pattern_regexp = mc_search_new (host_pattern, DEFAULT_CHARSET);
+ pattern_regexp->search_type = MC_SEARCH_T_GLOB;
+ pattern_regexp->is_case_sensitive = FALSE;
+ pattern_regexp->is_entire_line = TRUE;
+ pattern_block_hit =
+ mc_search_run (pattern_regexp, vpath_element->host, 0,
+ strlen (vpath_element->host), NULL);
+ mc_search_free (pattern_regexp);
+ }
+ }
+ else if (pattern_block_hit || host_block_hit)
+ {
+ sftpfs_fill_config_entity_from_string (config_entity, buffer);
+ }
+ }
+
+ done:
+ mc_search_free (host_regexp);
+ return ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Open the ssh config file and fill config entity.
+ *
+ * @param vpath_element path element with host data (hostname, port)
+ * @param mcerror pointer to the error handler
+ * @return newly allocated config entity structure
+ */
+
+static sftpfs_ssh_config_entity_t *
+sftpfs_get_config_entity (const vfs_path_element_t * vpath_element, GError ** mcerror)
+{
+ sftpfs_ssh_config_entity_t *config_entity;
+ FILE *ssh_config_handler;
+ char *config_filename;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ config_entity = g_new0 (sftpfs_ssh_config_entity_t, 1);
+ config_entity->password_auth = TRUE;
+ config_entity->identities_only = FALSE;
+ config_entity->pubkey_auth = TRUE;
+ config_entity->port = SFTP_DEFAULT_PORT;
+
+ config_filename = sftpfs_correct_file_name (SFTPFS_SSH_CONFIG);
+ ssh_config_handler = fopen (config_filename, "r");
+ g_free (config_filename);
+
+ if (ssh_config_handler != NULL)
+ {
+ gboolean ok;
+
+ ok = sftpfs_fill_config_entity_from_config
+ (ssh_config_handler, config_entity, vpath_element, mcerror);
+ fclose (ssh_config_handler);
+
+ if (!ok)
+ {
+ sftpfs_ssh_config_entity_free (config_entity);
+ return NULL;
+ }
+ }
+
+ if (config_entity->user == NULL)
+ {
+ config_entity->user = vfs_get_local_username ();
+ if (config_entity->user == NULL)
+ {
+ sftpfs_ssh_config_entity_free (config_entity);
+ config_entity = NULL;
+ mc_propagate_error (mcerror, EPERM, "%s", _("sftp: Unable to get current user name."));
+ }
+ }
+ return config_entity;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Reads data from the ssh config file related to connection.
+ *
+ * @param super connection data
+ * @param error pointer to the error handler
+ */
+
+void
+sftpfs_fill_connection_data_from_config (struct vfs_s_super *super, GError ** mcerror)
+{
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+ sftpfs_ssh_config_entity_t *config_entity;
+
+ mc_return_if_error (mcerror);
+
+ config_entity = sftpfs_get_config_entity (super->path_element, mcerror);
+ if (config_entity == NULL)
+ return;
+
+ sftpfs_super->config_auth_type = (config_entity->pubkey_auth) ? PUBKEY : 0;
+ sftpfs_super->config_auth_type |= (config_entity->identities_only) ? 0 : AGENT;
+ sftpfs_super->config_auth_type |= (config_entity->password_auth) ? PASSWORD : 0;
+
+ if (super->path_element->port == 0)
+ super->path_element->port = config_entity->port;
+
+ if (super->path_element->user == NULL)
+ super->path_element->user = g_strdup (config_entity->user);
+
+ if (config_entity->real_host != NULL)
+ {
+ g_free (super->path_element->host);
+ super->path_element->host = g_strdup (config_entity->real_host);
+ }
+
+ if (config_entity->identity_file != NULL)
+ {
+ sftpfs_super->privkey = g_strdup (config_entity->identity_file);
+ sftpfs_super->pubkey = g_strdup_printf ("%s.pub", config_entity->identity_file);
+ }
+
+ sftpfs_ssh_config_entity_free (config_entity);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Initialize the SSH config parser.
+ */
+
+void
+sftpfs_init_config_variables_patterns (void)
+{
+ int i;
+
+ for (i = 0; config_variables[i].pattern != NULL; i++)
+ {
+ config_variables[i].pattern_regexp =
+ mc_search_new (config_variables[i].pattern, DEFAULT_CHARSET);
+ config_variables[i].pattern_regexp->search_type = MC_SEARCH_T_REGEX;
+ config_variables[i].pattern_regexp->is_case_sensitive = FALSE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Deinitialize the SSH config parser.
+ */
+
+void
+sftpfs_deinit_config_variables_patterns (void)
+{
+ int i;
+
+ for (i = 0; config_variables[i].pattern != NULL; i++)
+ {
+ mc_search_free (config_variables[i].pattern_regexp);
+ config_variables[i].pattern_regexp = NULL;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/sftpfs/connection.c b/src/vfs/sftpfs/connection.c
new file mode 100644
index 0000000..d2466de
--- /dev/null
+++ b/src/vfs/sftpfs/connection.c
@@ -0,0 +1,970 @@
+/* Virtual File System: SFTP file system.
+ The internal functions: connections
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Ilia Maslakov <il.smind@gmail.com>, 2011
+ Slava Zanko <slavazanko@gmail.com>, 2011, 2012, 2013
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <errno.h>
+
+#include <netdb.h> /* struct hostent */
+#include <sys/socket.h> /* AF_INET */
+#include <netinet/in.h> /* struct in_addr */
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+#include <libssh2.h>
+#include <libssh2_sftp.h>
+
+#include "lib/global.h"
+
+#include "lib/util.h"
+#include "lib/tty/tty.h" /* tty_enable_interrupt_key () */
+#include "lib/vfs/utilvfs.h"
+#include "lib/mcconfig.h" /* mc_config_get_home_dir () */
+#include "lib/widget.h" /* query_dialog () */
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define SHA1_DIGEST_LENGTH 20
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+#ifdef LIBSSH2_KNOWNHOST_KEY_ED25519
+static const char *const hostkey_method_ssh_ed25519 = "ssh-ed25519";
+#endif
+#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_521
+static const char *const hostkey_method_ssh_ecdsa_521 = "ecdsa-sha2-nistp521";
+#endif
+#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_384
+static const char *const hostkey_method_ssh_ecdsa_384 = "ecdsa-sha2-nistp384";
+#endif
+#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_256
+static const char *const hostkey_method_ssh_ecdsa_256 = "ecdsa-sha2-nistp256";
+#endif
+static const char *const hostkey_method_ssh_rsa = "ssh-rsa";
+static const char *const hostkey_method_ssh_dss = "ssh-dss";
+
+/**
+ *
+ * The current implementation of know host key checking has following limitations:
+ *
+ * - Only plain-text entries are supported (`HashKnownHosts no` OpenSSH option)
+ * - Only HEX-encoded SHA1 fingerprint display is supported (`FingerprintHash` OpenSSH option)
+ * - Resolved IP addresses are *not* saved/validated along with the hostnames
+ *
+ */
+
+static const char *kbi_passwd = NULL;
+static const struct vfs_s_super *kbi_super = NULL;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create socket to host.
+ *
+ * @param super connection data
+ * @param mcerror pointer to the error handler
+ * @return socket descriptor number, -1 if any error was occurred
+ */
+
+static int
+sftpfs_open_socket (struct vfs_s_super *super, GError ** mcerror)
+{
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+ struct addrinfo hints, *res = NULL, *curr_res;
+ int my_socket = 0;
+ char port[BUF_TINY];
+ static char address_ipv4[INET_ADDRSTRLEN];
+ static char address_ipv6[INET6_ADDRSTRLEN];
+ int e;
+
+ mc_return_val_if_error (mcerror, LIBSSH2_INVALID_SOCKET);
+
+ if (super->path_element->host == NULL || *super->path_element->host == '\0')
+ {
+ mc_propagate_error (mcerror, 0, "%s", _("sftp: Invalid host name."));
+ return LIBSSH2_INVALID_SOCKET;
+ }
+
+ sprintf (port, "%hu", (unsigned short) super->path_element->port);
+
+ tty_enable_interrupt_key (); /* clear the interrupt flag */
+
+ memset (&hints, 0, sizeof (hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+
+#ifdef AI_ADDRCONFIG
+ /* By default, only look up addresses using address types for
+ * which a local interface is configured (i.e. no IPv6 if no IPv6
+ * interfaces, likewise for IPv4 (see RFC 3493 for details). */
+ hints.ai_flags = AI_ADDRCONFIG;
+#endif
+
+ e = getaddrinfo (super->path_element->host, port, &hints, &res);
+
+#ifdef AI_ADDRCONFIG
+ if (e == EAI_BADFLAGS)
+ {
+ /* Retry with no flags if AI_ADDRCONFIG was rejected. */
+ hints.ai_flags = 0;
+ e = getaddrinfo (super->path_element->host, port, &hints, &res);
+ }
+#endif
+
+ if (e != 0)
+ {
+ mc_propagate_error (mcerror, e, _("sftp: %s"), gai_strerror (e));
+ my_socket = LIBSSH2_INVALID_SOCKET;
+ goto ret;
+ }
+
+ for (curr_res = res; curr_res != NULL; curr_res = curr_res->ai_next)
+ {
+ int save_errno;
+
+ switch (curr_res->ai_addr->sa_family)
+ {
+ case AF_INET:
+ sftpfs_super->ip_address =
+ inet_ntop (AF_INET, &((struct sockaddr_in *) curr_res->ai_addr)->sin_addr,
+ address_ipv4, INET_ADDRSTRLEN);
+ break;
+ case AF_INET6:
+ sftpfs_super->ip_address =
+ inet_ntop (AF_INET6, &((struct sockaddr_in6 *) curr_res->ai_addr)->sin6_addr,
+ address_ipv6, INET6_ADDRSTRLEN);
+ break;
+ default:
+ sftpfs_super->ip_address = NULL;
+ }
+
+ if (sftpfs_super->ip_address == NULL)
+ {
+ mc_propagate_error (mcerror, 0, "%s",
+ _("sftp: failed to convert remote host IP address into text form"));
+ my_socket = LIBSSH2_INVALID_SOCKET;
+ goto ret;
+ }
+
+ my_socket = socket (curr_res->ai_family, curr_res->ai_socktype, curr_res->ai_protocol);
+
+ if (my_socket < 0)
+ {
+ if (curr_res->ai_next != NULL)
+ continue;
+
+ vfs_print_message (_("sftp: %s"), unix_error_string (errno));
+ my_socket = LIBSSH2_INVALID_SOCKET;
+ goto ret;
+ }
+
+ vfs_print_message (_("sftp: making connection to %s"), super->path_element->host);
+
+ if (connect (my_socket, curr_res->ai_addr, curr_res->ai_addrlen) >= 0)
+ break;
+
+ save_errno = errno;
+
+ close (my_socket);
+
+ if (save_errno == EINTR && tty_got_interrupt ())
+ mc_propagate_error (mcerror, 0, "%s", _("sftp: connection interrupted by user"));
+ else if (res->ai_next == NULL)
+ mc_propagate_error (mcerror, save_errno, _("sftp: connection to server failed: %s"),
+ unix_error_string (save_errno));
+ else
+ continue;
+
+ my_socket = LIBSSH2_INVALID_SOCKET;
+ break;
+ }
+
+ ret:
+ if (res != NULL)
+ freeaddrinfo (res);
+ tty_disable_interrupt_key ();
+ return my_socket;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Read ~/.ssh/known_hosts file.
+ *
+ * @param super connection data
+ * @param mcerror pointer to the error handler
+ * @return TRUE on success, FALSE otherwise
+ *
+ * Thanks the Curl project for the code used in this function.
+ */
+static gboolean
+sftpfs_read_known_hosts (struct vfs_s_super *super, GError ** mcerror)
+{
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+ struct libssh2_knownhost *store = NULL;
+ int rc;
+ gboolean found = FALSE;
+
+ sftpfs_super->known_hosts = libssh2_knownhost_init (sftpfs_super->session);
+ if (sftpfs_super->known_hosts == NULL)
+ goto err;
+
+ sftpfs_super->known_hosts_file =
+ mc_build_filename (mc_config_get_home_dir (), ".ssh", "known_hosts", (char *) NULL);
+ rc = libssh2_knownhost_readfile (sftpfs_super->known_hosts, sftpfs_super->known_hosts_file,
+ LIBSSH2_KNOWNHOST_FILE_OPENSSH);
+ if (rc > 0)
+ {
+ const char *kh_name_end = NULL;
+
+ while (!found && libssh2_knownhost_get (sftpfs_super->known_hosts, &store, store) == 0)
+ {
+ /* For non-standard ports, the name will be enclosed in
+ * square brackets, followed by a colon and the port */
+ if (store == NULL)
+ continue;
+
+ if (store->name == NULL)
+ found = TRUE;
+ else if (store->name[0] != '[')
+ found = strcmp (store->name, super->path_element->host) == 0;
+ else
+ {
+ int port;
+
+ kh_name_end = strstr (store->name, "]:");
+ if (kh_name_end == NULL)
+ /* Invalid host pattern */
+ continue;
+
+ port = (int) g_ascii_strtoll (kh_name_end + 2, NULL, 10);
+ if (port == super->path_element->port)
+ {
+ size_t kh_name_size;
+
+ kh_name_size = strlen (store->name) - 1 - strlen (kh_name_end);
+ found = strncmp (store->name + 1, super->path_element->host, kh_name_size) == 0;
+ }
+ }
+ }
+ }
+
+ if (found)
+ {
+ int mask;
+ const char *hostkey_method = NULL;
+
+ mask = store->typemask & LIBSSH2_KNOWNHOST_KEY_MASK;
+
+ switch (mask)
+ {
+#ifdef LIBSSH2_KNOWNHOST_KEY_ED25519
+ case LIBSSH2_KNOWNHOST_KEY_ED25519:
+ hostkey_method = hostkey_method_ssh_ed25519;
+ break;
+#endif
+#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_521
+ case LIBSSH2_KNOWNHOST_KEY_ECDSA_521:
+ hostkey_method = hostkey_method_ssh_ecdsa_521;
+ break;
+#endif
+#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_384
+ case LIBSSH2_KNOWNHOST_KEY_ECDSA_384:
+ hostkey_method = hostkey_method_ssh_ecdsa_384;
+ break;
+#endif
+#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_256
+ case LIBSSH2_KNOWNHOST_KEY_ECDSA_256:
+ hostkey_method = hostkey_method_ssh_ecdsa_256;
+ break;
+#endif
+ case LIBSSH2_KNOWNHOST_KEY_SSHRSA:
+ hostkey_method = hostkey_method_ssh_rsa;
+ break;
+ case LIBSSH2_KNOWNHOST_KEY_SSHDSS:
+ hostkey_method = hostkey_method_ssh_dss;
+ break;
+ case LIBSSH2_KNOWNHOST_KEY_RSA1:
+ mc_propagate_error (mcerror, 0, "%s",
+ _("sftp: found host key of unsupported type: RSA1"));
+ return FALSE;
+ default:
+ mc_propagate_error (mcerror, 0, "%s 0x%x", _("sftp: unknown host key type:"),
+ (unsigned int) mask);
+ return FALSE;
+ }
+
+ rc = libssh2_session_method_pref (sftpfs_super->session, LIBSSH2_METHOD_HOSTKEY,
+ hostkey_method);
+ if (rc < 0)
+ goto err;
+ }
+
+ return TRUE;
+
+ err:
+ {
+ int sftp_errno;
+
+ sftp_errno = libssh2_session_last_errno (sftpfs_super->session);
+ sftpfs_ssherror_to_gliberror (sftpfs_super, sftp_errno, mcerror);
+ }
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Write new host + key pair to the ~/.ssh/known_hosts file.
+ *
+ * @param super connection data
+ * @param remote_key he key for the remote host
+ * @param remote_key_len length of @remote_key
+ * @param type_mask info about format of host name, key and key type
+ * @return 0 on success, regular libssh2 error code otherwise
+ *
+ * Thanks the Curl project for the code used in this function.
+ */
+static int
+sftpfs_update_known_hosts (struct vfs_s_super *super, const char *remote_key, size_t remote_key_len,
+ int type_mask)
+{
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+ int rc;
+
+ /* add this host + key pair */
+ rc = libssh2_knownhost_addc (sftpfs_super->known_hosts, super->path_element->host, NULL,
+ remote_key, remote_key_len, NULL, 0, type_mask, NULL);
+ if (rc < 0)
+ return rc;
+
+ /* write the entire in-memory list of known hosts to the known_hosts file */
+ rc = libssh2_knownhost_writefile (sftpfs_super->known_hosts, sftpfs_super->known_hosts_file,
+ LIBSSH2_KNOWNHOST_FILE_OPENSSH);
+
+ if (rc < 0)
+ return rc;
+
+ (void) message (D_NORMAL, _("Information"),
+ _("Permanently added\n%s (%s)\nto the list of known hosts."),
+ super->path_element->host, sftpfs_super->ip_address);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Compute and return readable host key fingerprint hash.
+ *
+ * @param session libssh2 session handle
+ * @return pointer to static buffer on success, NULL otherwise
+ */
+static const char *
+sftpfs_compute_fingerprint_hash (LIBSSH2_SESSION * session)
+{
+ static char result[SHA1_DIGEST_LENGTH * 3 + 1]; /* "XX:" for each byte, and EOL */
+ const char *fingerprint;
+ size_t i;
+
+ /* The fingerprint points to static storage (!), don't free() it. */
+ fingerprint = libssh2_hostkey_hash (session, LIBSSH2_HOSTKEY_HASH_SHA1);
+ if (fingerprint == NULL)
+ return NULL;
+
+ for (i = 0; i < SHA1_DIGEST_LENGTH && i * 3 < sizeof (result) - 1; i++)
+ g_snprintf ((gchar *) (result + i * 3), 4, "%02x:", (guint8) fingerprint[i]);
+
+ /* remove last ":" */
+ result[i * 3 - 1] = '\0';
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Process host info found in ~/.ssh/known_hosts file.
+ *
+ * @param super connection data
+ * @param mcerror pointer to the error handler
+ * @return TRUE on success, FALSE otherwise
+ *
+ * Thanks the Curl project for the code used in this function.
+ */
+static gboolean
+sftpfs_process_known_host (struct vfs_s_super *super, GError ** mcerror)
+{
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+ const char *remote_key;
+ const char *key_type;
+ const char *fingerprint_hash;
+ size_t remote_key_len = 0;
+ int remote_key_type = LIBSSH2_HOSTKEY_TYPE_UNKNOWN;
+ int keybit = 0;
+ struct libssh2_knownhost *host = NULL;
+ int rc;
+ char *msg = NULL;
+ gboolean handle_query = FALSE;
+
+ remote_key = libssh2_session_hostkey (sftpfs_super->session, &remote_key_len, &remote_key_type);
+ if (remote_key == NULL || remote_key_len == 0
+ || remote_key_type == LIBSSH2_HOSTKEY_TYPE_UNKNOWN)
+ {
+ mc_propagate_error (mcerror, 0, "%s", _("sftp: cannot get the remote host key"));
+ return FALSE;
+ }
+
+ switch (remote_key_type)
+ {
+ case LIBSSH2_HOSTKEY_TYPE_RSA:
+ keybit = LIBSSH2_KNOWNHOST_KEY_SSHRSA;
+ key_type = "RSA";
+ break;
+ case LIBSSH2_HOSTKEY_TYPE_DSS:
+ keybit = LIBSSH2_KNOWNHOST_KEY_SSHDSS;
+ key_type = "DSS";
+ break;
+#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256
+ case LIBSSH2_HOSTKEY_TYPE_ECDSA_256:
+ keybit = LIBSSH2_KNOWNHOST_KEY_ECDSA_256;
+ key_type = "ECDSA";
+ break;
+#endif
+#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_384
+ case LIBSSH2_HOSTKEY_TYPE_ECDSA_384:
+ keybit = LIBSSH2_KNOWNHOST_KEY_ECDSA_384;
+ key_type = "ECDSA";
+ break;
+#endif
+#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_521
+ case LIBSSH2_HOSTKEY_TYPE_ECDSA_521:
+ keybit = LIBSSH2_KNOWNHOST_KEY_ECDSA_521;
+ key_type = "ECDSA";
+ break;
+#endif
+#ifdef LIBSSH2_HOSTKEY_TYPE_ED25519
+ case LIBSSH2_HOSTKEY_TYPE_ED25519:
+ keybit = LIBSSH2_KNOWNHOST_KEY_ED25519;
+ key_type = "ED25519";
+ break;
+#endif
+ default:
+ mc_propagate_error (mcerror, 0, "%s",
+ _("sftp: unsupported key type, can't check remote host key"));
+ return FALSE;
+ }
+
+ fingerprint_hash = sftpfs_compute_fingerprint_hash (sftpfs_super->session);
+ if (fingerprint_hash == NULL)
+ {
+ mc_propagate_error (mcerror, 0, "%s", _("sftp: can't compute host key fingerprint hash"));
+ return FALSE;
+ }
+
+ rc = libssh2_knownhost_checkp (sftpfs_super->known_hosts, super->path_element->host,
+ super->path_element->port, remote_key, remote_key_len,
+ LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW |
+ keybit, &host);
+
+ switch (rc)
+ {
+ default:
+ case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
+ /* something prevented the check to be made */
+ goto err;
+
+ case LIBSSH2_KNOWNHOST_CHECK_MATCH:
+ /* host + key pair matched -- OK */
+ break;
+
+ case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
+ /* no host match was found -- add it to the known_hosts file */
+ msg = g_strdup_printf (_("The authenticity of host\n%s (%s)\ncan't be established!\n"
+ "%s key fingerprint hash is\nSHA1:%s.\n"
+ "Do you want to add it to the list of known hosts and continue connecting?"),
+ super->path_element->host, sftpfs_super->ip_address,
+ key_type, fingerprint_hash);
+ /* Select "No" initially */
+ query_set_sel (2);
+ rc = query_dialog (_("Warning"), msg, D_NORMAL, 3, _("&Yes"), _("&Ignore"), _("&No"));
+ g_free (msg);
+ handle_query = TRUE;
+ break;
+
+ case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
+ msg = g_strdup_printf (_("%s (%s)\nis found in the list of known hosts but\n"
+ "KEYS DO NOT MATCH! THIS COULD BE A MITM ATTACK!\n"
+ "Are you sure you want to add it to the list of known hosts and continue connecting?"),
+ super->path_element->host, sftpfs_super->ip_address);
+ /* Select "No" initially */
+ query_set_sel (2);
+ rc = query_dialog (MSG_ERROR, msg, D_ERROR, 3, _("&Yes"), _("&Ignore"), _("&No"));
+ g_free (msg);
+ handle_query = TRUE;
+ break;
+ }
+
+ if (handle_query)
+ switch (rc)
+ {
+ case 0:
+ /* Yes: add this host + key pair, continue connecting */
+ if (sftpfs_update_known_hosts (super, remote_key, remote_key_len,
+ LIBSSH2_KNOWNHOST_TYPE_PLAIN
+ | LIBSSH2_KNOWNHOST_KEYENC_RAW | keybit) < 0)
+ goto err;
+ break;
+ case 1:
+ /* Ignore: do not add this host + key pair, continue connecting anyway */
+ break;
+ case 2:
+ default:
+ mc_propagate_error (mcerror, 0, "%s", _("sftp: host key verification failed"));
+ /* No: abort connection */
+ goto err;
+ }
+
+ return TRUE;
+
+ err:
+ {
+ int sftp_errno;
+
+ sftp_errno = libssh2_session_last_errno (sftpfs_super->session);
+ sftpfs_ssherror_to_gliberror (sftpfs_super, sftp_errno, mcerror);
+ }
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Recognize authentication types supported by remote side and filling internal 'super' structure by
+ * proper enum's values.
+ *
+ * @param super connection data
+ * @return TRUE if some of authentication methods is available, FALSE otherwise
+ */
+static gboolean
+sftpfs_recognize_auth_types (struct vfs_s_super *super)
+{
+ char *userauthlist;
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+
+ /* check what authentication methods are available */
+ /* userauthlist is internally managed by libssh2 and freed by libssh2_session_free() */
+ userauthlist = libssh2_userauth_list (sftpfs_super->session, super->path_element->user,
+ strlen (super->path_element->user));
+
+ if (userauthlist == NULL)
+ return FALSE;
+
+ if ((strstr (userauthlist, "password") != NULL
+ || strstr (userauthlist, "keyboard-interactive") != NULL)
+ && (sftpfs_super->config_auth_type & PASSWORD) != 0)
+ sftpfs_super->auth_type |= PASSWORD;
+
+ if (strstr (userauthlist, "publickey") != NULL
+ && (sftpfs_super->config_auth_type & PUBKEY) != 0)
+ sftpfs_super->auth_type |= PUBKEY;
+
+ if ((sftpfs_super->config_auth_type & AGENT) != 0)
+ sftpfs_super->auth_type |= AGENT;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Open connection to host using SSH-agent helper.
+ *
+ * @param super connection data
+ * @param mcerror pointer to the error handler
+ * @return TRUE if connection was successfully opened, FALSE otherwise
+ */
+
+static gboolean
+sftpfs_open_connection_ssh_agent (struct vfs_s_super *super, GError ** mcerror)
+{
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+ struct libssh2_agent_publickey *identity, *prev_identity = NULL;
+ int rc;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ sftpfs_super->agent = NULL;
+
+ if ((sftpfs_super->auth_type & AGENT) == 0)
+ return FALSE;
+
+ /* Connect to the ssh-agent */
+ sftpfs_super->agent = libssh2_agent_init (sftpfs_super->session);
+ if (sftpfs_super->agent == NULL)
+ return FALSE;
+
+ if (libssh2_agent_connect (sftpfs_super->agent) != 0)
+ return FALSE;
+
+ if (libssh2_agent_list_identities (sftpfs_super->agent) != 0)
+ return FALSE;
+
+ while (TRUE)
+ {
+ rc = libssh2_agent_get_identity (sftpfs_super->agent, &identity, prev_identity);
+ if (rc == 1)
+ break;
+
+ if (rc < 0)
+ return FALSE;
+
+ if (libssh2_agent_userauth (sftpfs_super->agent, super->path_element->user, identity) == 0)
+ break;
+
+ prev_identity = identity;
+ }
+
+ return (rc == 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Open connection to host using SSH-keypair.
+ *
+ * @param super connection data
+ * @param mcerror pointer to the error handler
+ * @return TRUE if connection was successfully opened, FALSE otherwise
+ */
+
+static gboolean
+sftpfs_open_connection_ssh_key (struct vfs_s_super *super, GError ** mcerror)
+{
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+ char *p, *passwd;
+ gboolean ret_value = FALSE;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ if ((sftpfs_super->auth_type & PUBKEY) == 0)
+ return FALSE;
+
+ if (sftpfs_super->privkey == NULL)
+ return FALSE;
+
+ if (libssh2_userauth_publickey_fromfile (sftpfs_super->session, super->path_element->user,
+ sftpfs_super->pubkey, sftpfs_super->privkey,
+ super->path_element->password) == 0)
+ return TRUE;
+
+ p = g_strdup_printf (_("sftp: Enter passphrase for %s "), super->path_element->user);
+ passwd = vfs_get_password (p);
+ g_free (p);
+
+ if (passwd == NULL)
+ mc_propagate_error (mcerror, 0, "%s", _("sftp: Passphrase is empty."));
+ else
+ {
+ ret_value = (libssh2_userauth_publickey_fromfile (sftpfs_super->session,
+ super->path_element->user,
+ sftpfs_super->pubkey,
+ sftpfs_super->privkey, passwd) == 0);
+ g_free (passwd);
+ }
+
+ return ret_value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Keyboard-interactive password helper for opening connection to host by
+ * sftpfs_open_connection_ssh_password
+ *
+ * Uses global kbi_super (data with existing connection) and kbi_passwd (password)
+ *
+ * @param name username
+ * @param name_len length of @name
+ * @param instruction unused
+ * @param instruction_len unused
+ * @param num_prompts number of possible problems to process
+ * @param prompts array of prompts to process
+ * @param responses array of responses, one per prompt
+ * @param abstract unused
+ */
+
+static
+LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC (sftpfs_keyboard_interactive_helper)
+{
+ int i;
+ size_t len;
+
+ (void) instruction;
+ (void) instruction_len;
+ (void) abstract;
+
+ if (kbi_super == NULL || kbi_passwd == NULL)
+ return;
+
+ if (strncmp (name, kbi_super->path_element->user, name_len) != 0)
+ return;
+
+ /* assume these are password prompts */
+ len = strlen (kbi_passwd);
+
+ for (i = 0; i < num_prompts; ++i)
+ if (strncmp (prompts[i].text, "Password: ", prompts[i].length) == 0)
+ {
+ responses[i].text = strdup (kbi_passwd);
+ responses[i].length = len;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Open connection to host using password.
+ *
+ * @param super connection data
+ * @param mcerror pointer to the error handler
+ * @return TRUE if connection was successfully opened, FALSE otherwise
+ */
+
+static gboolean
+sftpfs_open_connection_ssh_password (struct vfs_s_super *super, GError ** mcerror)
+{
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+ char *p, *passwd;
+ gboolean ret_value = FALSE;
+ int rc;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ if ((sftpfs_super->auth_type & PASSWORD) == 0)
+ return FALSE;
+
+ if (super->path_element->password != NULL)
+ {
+ while ((rc = libssh2_userauth_password (sftpfs_super->session, super->path_element->user,
+ super->path_element->password)) ==
+ LIBSSH2_ERROR_EAGAIN);
+ if (rc == 0)
+ return TRUE;
+
+ kbi_super = super;
+ kbi_passwd = super->path_element->password;
+
+ while ((rc =
+ libssh2_userauth_keyboard_interactive (sftpfs_super->session,
+ super->path_element->user,
+ sftpfs_keyboard_interactive_helper)) ==
+ LIBSSH2_ERROR_EAGAIN)
+ ;
+
+ kbi_super = NULL;
+ kbi_passwd = NULL;
+
+ if (rc == 0)
+ return TRUE;
+ }
+
+ p = g_strdup_printf (_("sftp: Enter password for %s "), super->path_element->user);
+ passwd = vfs_get_password (p);
+ g_free (p);
+
+ if (passwd == NULL)
+ mc_propagate_error (mcerror, 0, "%s", _("sftp: Password is empty."));
+ else
+ {
+ while ((rc = libssh2_userauth_password (sftpfs_super->session, super->path_element->user,
+ passwd)) == LIBSSH2_ERROR_EAGAIN)
+ ;
+
+ if (rc != 0)
+ {
+ kbi_super = super;
+ kbi_passwd = passwd;
+
+ while ((rc =
+ libssh2_userauth_keyboard_interactive (sftpfs_super->session,
+ super->path_element->user,
+ sftpfs_keyboard_interactive_helper)) ==
+ LIBSSH2_ERROR_EAGAIN)
+ ;
+
+ kbi_super = NULL;
+ kbi_passwd = NULL;
+ }
+
+ if (rc == 0)
+ {
+ ret_value = TRUE;
+ g_free (super->path_element->password);
+ super->path_element->password = passwd;
+ }
+ else
+ g_free (passwd);
+ }
+
+ return ret_value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Open new connection.
+ *
+ * @param super connection data
+ * @param mcerror pointer to the error handler
+ * @return 0 if success, -1 otherwise
+ */
+
+int
+sftpfs_open_connection (struct vfs_s_super *super, GError ** mcerror)
+{
+ int rc;
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+
+ mc_return_val_if_error (mcerror, -1);
+
+ /*
+ * The application code is responsible for creating the socket
+ * and establishing the connection
+ */
+ sftpfs_super->socket_handle = sftpfs_open_socket (super, mcerror);
+ if (sftpfs_super->socket_handle == LIBSSH2_INVALID_SOCKET)
+ return (-1);
+
+ /* Create a session instance */
+ sftpfs_super->session = libssh2_session_init ();
+ if (sftpfs_super->session == NULL)
+ return (-1);
+
+ if (!sftpfs_read_known_hosts (super, mcerror))
+ return (-1);
+
+ /* ... start it up. This will trade welcome banners, exchange keys,
+ * and setup crypto, compression, and MAC layers
+ */
+ while ((rc =
+ libssh2_session_handshake (sftpfs_super->session,
+ (libssh2_socket_t) sftpfs_super->socket_handle)) ==
+ LIBSSH2_ERROR_EAGAIN)
+ ;
+ if (rc != 0)
+ {
+ mc_propagate_error (mcerror, rc, "%s", _("sftp: failure establishing SSH session"));
+ return (-1);
+ }
+
+ if (!sftpfs_process_known_host (super, mcerror))
+ return (-1);
+
+ if (!sftpfs_recognize_auth_types (super))
+ {
+ int sftp_errno;
+
+ sftp_errno = libssh2_session_last_errno (sftpfs_super->session);
+ sftpfs_ssherror_to_gliberror (sftpfs_super, sftp_errno, mcerror);
+ return (-1);
+ }
+
+ if (!sftpfs_open_connection_ssh_agent (super, mcerror)
+ && !sftpfs_open_connection_ssh_key (super, mcerror)
+ && !sftpfs_open_connection_ssh_password (super, mcerror))
+ return (-1);
+
+ sftpfs_super->sftp_session = libssh2_sftp_init (sftpfs_super->session);
+
+ if (sftpfs_super->sftp_session == NULL)
+ return (-1);
+
+ /* Since we have not set non-blocking, tell libssh2 we are blocking */
+ libssh2_session_set_blocking (sftpfs_super->session, 1);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Close connection.
+ *
+ * @param super connection data
+ * @param shutdown_message message for shutdown functions
+ * @param mcerror pointer to the error handler
+ */
+
+void
+sftpfs_close_connection (struct vfs_s_super *super, const char *shutdown_message, GError ** mcerror)
+{
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+
+ /* no mc_return_*_if_error() here because of abort open_connection handling too */
+ (void) mcerror;
+
+ if (sftpfs_super->sftp_session != NULL)
+ {
+ libssh2_sftp_shutdown (sftpfs_super->sftp_session);
+ sftpfs_super->sftp_session = NULL;
+ }
+
+ if (sftpfs_super->agent != NULL)
+ {
+ libssh2_agent_disconnect (sftpfs_super->agent);
+ libssh2_agent_free (sftpfs_super->agent);
+ sftpfs_super->agent = NULL;
+ }
+
+ if (sftpfs_super->known_hosts != NULL)
+ {
+ libssh2_knownhost_free (sftpfs_super->known_hosts);
+ sftpfs_super->known_hosts = NULL;
+ }
+
+ MC_PTR_FREE (sftpfs_super->known_hosts_file);
+
+ if (sftpfs_super->session != NULL)
+ {
+ libssh2_session_disconnect (sftpfs_super->session, shutdown_message);
+ libssh2_session_free (sftpfs_super->session);
+ sftpfs_super->session = NULL;
+ }
+
+ if (sftpfs_super->socket_handle != LIBSSH2_INVALID_SOCKET)
+ {
+ close (sftpfs_super->socket_handle);
+ sftpfs_super->socket_handle = LIBSSH2_INVALID_SOCKET;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/sftpfs/dir.c b/src/vfs/sftpfs/dir.c
new file mode 100644
index 0000000..a19a31f
--- /dev/null
+++ b/src/vfs/sftpfs/dir.c
@@ -0,0 +1,230 @@
+/* Virtual File System: SFTP file system.
+ The internal functions: dirs
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Ilia Maslakov <il.smind@gmail.com>, 2011
+ Slava Zanko <slavazanko@gmail.com>, 2011, 2012
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <libssh2.h>
+#include <libssh2_sftp.h>
+
+#include "lib/global.h"
+#include "lib/util.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ LIBSSH2_SFTP_HANDLE *handle;
+ sftpfs_super_t *super;
+} sftpfs_dir_data_t;
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Open a directory stream corresponding to the directory name.
+ *
+ * @param vpath path to directory
+ * @param mcerror pointer to the error handler
+ * @return directory data handler if success, NULL otherwise
+ */
+
+void *
+sftpfs_opendir (const vfs_path_t * vpath, GError ** mcerror)
+{
+ sftpfs_dir_data_t *sftpfs_dir;
+ sftpfs_super_t *sftpfs_super;
+ const vfs_path_element_t *path_element;
+ LIBSSH2_SFTP_HANDLE *handle;
+ const GString *fixfname;
+
+ if (!sftpfs_op_init (&sftpfs_super, &path_element, vpath, mcerror))
+ return NULL;
+
+ fixfname = sftpfs_fix_filename (path_element->path);
+
+ while (TRUE)
+ {
+ int libssh_errno;
+
+ handle =
+ libssh2_sftp_open_ex (sftpfs_super->sftp_session, fixfname->str, fixfname->len, 0, 0,
+ LIBSSH2_SFTP_OPENDIR);
+ if (handle != NULL)
+ break;
+
+ libssh_errno = libssh2_session_last_errno (sftpfs_super->session);
+ if (!sftpfs_waitsocket (sftpfs_super, libssh_errno, mcerror))
+ return NULL;
+ }
+
+ sftpfs_dir = g_new0 (sftpfs_dir_data_t, 1);
+ sftpfs_dir->handle = handle;
+ sftpfs_dir->super = sftpfs_super;
+
+ return (void *) sftpfs_dir;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get a pointer to a structure representing the next directory entry.
+ *
+ * @param data directory data handler
+ * @param mcerror pointer to the error handler
+ * @return information about direntry if success, NULL otherwise
+ */
+
+struct vfs_dirent *
+sftpfs_readdir (void *data, GError ** mcerror)
+{
+ char mem[BUF_MEDIUM];
+ LIBSSH2_SFTP_ATTRIBUTES attrs;
+ sftpfs_dir_data_t *sftpfs_dir = (sftpfs_dir_data_t *) data;
+ int rc;
+
+ mc_return_val_if_error (mcerror, NULL);
+
+ do
+ {
+ rc = libssh2_sftp_readdir (sftpfs_dir->handle, mem, sizeof (mem), &attrs);
+ if (rc >= 0)
+ break;
+
+ if (!sftpfs_waitsocket (sftpfs_dir->super, rc, mcerror))
+ return NULL;
+ }
+ while (rc == LIBSSH2_ERROR_EAGAIN);
+
+ return (rc != 0 ? vfs_dirent_init (NULL, mem, 0) : NULL); /* FIXME: inode */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Close the directory stream.
+ *
+ * @param data directory data handler
+ * @param mcerror pointer to the error handler
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_closedir (void *data, GError ** mcerror)
+{
+ int rc;
+ sftpfs_dir_data_t *sftpfs_dir = (sftpfs_dir_data_t *) data;
+
+ mc_return_val_if_error (mcerror, -1);
+
+ rc = libssh2_sftp_closedir (sftpfs_dir->handle);
+ g_free (sftpfs_dir);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create a new directory.
+ *
+ * @param vpath path directory
+ * @param mode mode (see man 2 open)
+ * @param mcerror pointer to the error handler
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_mkdir (const vfs_path_t * vpath, mode_t mode, GError ** mcerror)
+{
+ int res;
+ sftpfs_super_t *sftpfs_super;
+ const vfs_path_element_t *path_element;
+ const GString *fixfname;
+
+ if (!sftpfs_op_init (&sftpfs_super, &path_element, vpath, mcerror))
+ return -1;
+
+ fixfname = sftpfs_fix_filename (path_element->path);
+
+ do
+ {
+ res =
+ libssh2_sftp_mkdir_ex (sftpfs_super->sftp_session, fixfname->str, fixfname->len, mode);
+ if (res >= 0)
+ break;
+
+ if (!sftpfs_waitsocket (sftpfs_super, res, mcerror))
+ return -1;
+ }
+ while (res == LIBSSH2_ERROR_EAGAIN);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Remove a directory.
+ *
+ * @param vpath path directory
+ * @param mcerror pointer to the error handler
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_rmdir (const vfs_path_t * vpath, GError ** mcerror)
+{
+ int res;
+ sftpfs_super_t *sftpfs_super;
+ const vfs_path_element_t *path_element;
+ const GString *fixfname;
+
+ if (!sftpfs_op_init (&sftpfs_super, &path_element, vpath, mcerror))
+ return -1;
+
+ fixfname = sftpfs_fix_filename (path_element->path);
+
+ do
+ {
+ res = libssh2_sftp_rmdir_ex (sftpfs_super->sftp_session, fixfname->str, fixfname->len);
+ if (res >= 0)
+ break;
+
+ if (!sftpfs_waitsocket (sftpfs_super, res, mcerror))
+ return -1;
+ }
+ while (res == LIBSSH2_ERROR_EAGAIN);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/sftpfs/file.c b/src/vfs/sftpfs/file.c
new file mode 100644
index 0000000..4146239
--- /dev/null
+++ b/src/vfs/sftpfs/file.c
@@ -0,0 +1,424 @@
+/* Virtual File System: SFTP file system.
+ The internal functions: files
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Ilia Maslakov <il.smind@gmail.com>, 2011
+ Slava Zanko <slavazanko@gmail.com>, 2011, 2012
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <errno.h> /* ENOENT, EACCES */
+
+#include <libssh2.h>
+#include <libssh2_sftp.h>
+
+#include "lib/global.h"
+#include "lib/util.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define SFTP_FILE_HANDLER(a) ((sftpfs_file_handler_t *) a)
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ vfs_file_handler_t base; /* base class */
+
+ LIBSSH2_SFTP_HANDLE *handle;
+ int flags;
+ mode_t mode;
+} sftpfs_file_handler_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Reopen file by file handle.
+ *
+ * @param fh the file handler
+ * @param mcerror pointer to the error handler
+ */
+static void
+sftpfs_reopen (vfs_file_handler_t * fh, GError ** mcerror)
+{
+ sftpfs_file_handler_t *file = SFTP_FILE_HANDLER (fh);
+ int flags;
+ mode_t mode;
+
+ g_return_if_fail (mcerror == NULL || *mcerror == NULL);
+
+ flags = file->flags;
+ mode = file->mode;
+
+ sftpfs_close_file (fh, mcerror);
+ sftpfs_open_file (fh, flags, mode, mcerror);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sftpfs_file__handle_error (sftpfs_super_t * super, int sftp_res, GError ** mcerror)
+{
+ if (sftpfs_is_sftp_error (super->sftp_session, sftp_res, LIBSSH2_FX_PERMISSION_DENIED))
+ return -EACCES;
+
+ if (sftpfs_is_sftp_error (super->sftp_session, sftp_res, LIBSSH2_FX_NO_SUCH_FILE))
+ return -ENOENT;
+
+ if (!sftpfs_waitsocket (super, sftp_res, mcerror))
+ return -1;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+vfs_file_handler_t *
+sftpfs_fh_new (struct vfs_s_inode * ino, gboolean changed)
+{
+ sftpfs_file_handler_t *fh;
+
+ fh = g_new0 (sftpfs_file_handler_t, 1);
+ vfs_s_init_fh (VFS_FILE_HANDLER (fh), ino, changed);
+
+ return VFS_FILE_HANDLER (fh);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Open new SFTP file.
+ *
+ * @param fh the file handler
+ * @param flags flags (see man 2 open)
+ * @param mode mode (see man 2 open)
+ * @param mcerror pointer to the error handler
+ * @return TRUE if connection was created successfully, FALSE otherwise
+ */
+
+gboolean
+sftpfs_open_file (vfs_file_handler_t * fh, int flags, mode_t mode, GError ** mcerror)
+{
+ unsigned long sftp_open_flags = 0;
+ int sftp_open_mode = 0;
+ gboolean do_append = FALSE;
+ sftpfs_file_handler_t *file = SFTP_FILE_HANDLER (fh);
+ sftpfs_super_t *super = SFTP_SUPER (fh->ino->super);
+ char *name;
+ const GString *fixfname;
+
+ (void) mode;
+ mc_return_val_if_error (mcerror, FALSE);
+
+ name = vfs_s_fullpath (vfs_sftpfs_ops, fh->ino);
+ if (name == NULL)
+ return FALSE;
+
+ if ((flags & O_CREAT) != 0 || (flags & O_WRONLY) != 0)
+ {
+ sftp_open_flags = (flags & O_WRONLY) != 0 ? LIBSSH2_FXF_WRITE : 0;
+ sftp_open_flags |= (flags & O_CREAT) != 0 ? LIBSSH2_FXF_CREAT : 0;
+ if ((flags & O_APPEND) != 0)
+ {
+ sftp_open_flags |= LIBSSH2_FXF_APPEND;
+ do_append = TRUE;
+ }
+ sftp_open_flags |= (flags & O_TRUNC) != 0 ? LIBSSH2_FXF_TRUNC : 0;
+
+ sftp_open_mode = LIBSSH2_SFTP_S_IRUSR |
+ LIBSSH2_SFTP_S_IWUSR | LIBSSH2_SFTP_S_IRGRP | LIBSSH2_SFTP_S_IROTH;
+ }
+ else
+ sftp_open_flags = LIBSSH2_FXF_READ;
+
+ fixfname = sftpfs_fix_filename (name);
+
+ while (TRUE)
+ {
+ int libssh_errno;
+
+ file->handle =
+ libssh2_sftp_open_ex (super->sftp_session, fixfname->str, fixfname->len,
+ sftp_open_flags, sftp_open_mode, LIBSSH2_SFTP_OPENFILE);
+ if (file->handle != NULL)
+ break;
+
+ libssh_errno = libssh2_session_last_errno (super->session);
+ if (libssh_errno != LIBSSH2_ERROR_EAGAIN)
+ {
+ sftpfs_ssherror_to_gliberror (super, libssh_errno, mcerror);
+ g_free (name);
+ return FALSE;
+ }
+ }
+
+ g_free (name);
+
+ file->flags = flags;
+ file->mode = mode;
+
+ if (do_append)
+ {
+ struct stat file_info = {
+ .st_dev = 0
+ };
+ /* In case of
+
+ struct stat file_info = { 0 };
+
+ gcc < 4.7 [1] generates the following:
+
+ error: missing initializer [-Werror=missing-field-initializers]
+ error: (near initialization for 'file_info.st_dev') [-Werror=missing-field-initializers]
+
+ [1] http://stackoverflow.com/questions/13373695/how-to-remove-the-warning-in-gcc-4-6-missing-initializer-wmissing-field-initi/27461062#27461062
+ */
+
+ if (sftpfs_fstat (fh, &file_info, mcerror) == 0)
+ libssh2_sftp_seek64 (file->handle, file_info.st_size);
+ }
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Stats the file specified by the file descriptor.
+ *
+ * @param data file handler
+ * @param buf buffer for store stat-info
+ * @param mcerror pointer to the error handler
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_fstat (void *data, struct stat *buf, GError ** mcerror)
+{
+ int res;
+ LIBSSH2_SFTP_ATTRIBUTES attrs;
+ vfs_file_handler_t *fh = VFS_FILE_HANDLER (data);
+ sftpfs_file_handler_t *sftpfs_fh = (sftpfs_file_handler_t *) data;
+ struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh);
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+
+ mc_return_val_if_error (mcerror, -1);
+
+ if (sftpfs_fh->handle == NULL)
+ return -1;
+
+ do
+ {
+ int err;
+
+ res = libssh2_sftp_fstat_ex (sftpfs_fh->handle, &attrs, 0);
+ if (res >= 0)
+ break;
+
+ err = sftpfs_file__handle_error (sftpfs_super, res, mcerror);
+ if (err < 0)
+ return err;
+ }
+ while (res == LIBSSH2_ERROR_EAGAIN);
+
+ sftpfs_attr_to_stat (&attrs, buf);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Read up to 'count' bytes from the file descriptor 'fh' to the buffer starting at 'buffer'.
+ *
+ * @param fh file handler
+ * @param buffer buffer for data
+ * @param count data size
+ * @param mcerror pointer to the error handler
+ *
+ * @return 0 on success, negative value otherwise
+ */
+
+ssize_t
+sftpfs_read_file (vfs_file_handler_t * fh, char *buffer, size_t count, GError ** mcerror)
+{
+ ssize_t rc;
+ sftpfs_file_handler_t *file = SFTP_FILE_HANDLER (fh);
+ sftpfs_super_t *super;
+
+ mc_return_val_if_error (mcerror, -1);
+
+ if (fh == NULL)
+ {
+ mc_propagate_error (mcerror, 0, "%s",
+ _("sftp: No file handler data present for reading file"));
+ return -1;
+ }
+
+ super = SFTP_SUPER (VFS_FILE_HANDLER_SUPER (fh));
+
+ do
+ {
+ int err;
+
+ rc = libssh2_sftp_read (file->handle, buffer, count);
+ if (rc >= 0)
+ break;
+
+ err = sftpfs_file__handle_error (super, (int) rc, mcerror);
+ if (err < 0)
+ return err;
+ }
+ while (rc == LIBSSH2_ERROR_EAGAIN);
+
+ fh->pos = (off_t) libssh2_sftp_tell64 (file->handle);
+
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Write up to 'count' bytes from the buffer starting at 'buffer' to the descriptor 'fh'.
+ *
+ * @param fh file handler
+ * @param buffer buffer for data
+ * @param count data size
+ * @param mcerror pointer to the error handler
+ *
+ * @return 0 on success, negative value otherwise
+ */
+
+ssize_t
+sftpfs_write_file (vfs_file_handler_t * fh, const char *buffer, size_t count, GError ** mcerror)
+{
+ ssize_t rc;
+ sftpfs_file_handler_t *file = SFTP_FILE_HANDLER (fh);
+ sftpfs_super_t *super = SFTP_SUPER (VFS_FILE_HANDLER_SUPER (fh));
+
+ mc_return_val_if_error (mcerror, -1);
+
+ fh->pos = (off_t) libssh2_sftp_tell64 (file->handle);
+
+ do
+ {
+ int err;
+
+ rc = libssh2_sftp_write (file->handle, buffer, count);
+ if (rc >= 0)
+ break;
+
+ err = sftpfs_file__handle_error (super, (int) rc, mcerror);
+ if (err < 0)
+ return err;
+ }
+ while (rc == LIBSSH2_ERROR_EAGAIN);
+
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Close a file descriptor.
+ *
+ * @param fh file handler
+ * @param mcerror pointer to the error handler
+ *
+ * @return 0 on success, negative value otherwise
+ */
+
+int
+sftpfs_close_file (vfs_file_handler_t * fh, GError ** mcerror)
+{
+ int ret;
+
+ mc_return_val_if_error (mcerror, -1);
+
+ ret = libssh2_sftp_close (SFTP_FILE_HANDLER (fh)->handle);
+
+ return ret == 0 ? 0 : -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Reposition the offset of the open file associated with the file descriptor.
+ *
+ * @param fh file handler
+ * @param offset file offset
+ * @param whence method of seek (at begin, at current, at end)
+ * @param mcerror pointer to the error handler
+ *
+ * @return 0 on success, negative value otherwise
+ */
+
+off_t
+sftpfs_lseek (vfs_file_handler_t * fh, off_t offset, int whence, GError ** mcerror)
+{
+ sftpfs_file_handler_t *file = SFTP_FILE_HANDLER (fh);
+
+ mc_return_val_if_error (mcerror, 0);
+
+ switch (whence)
+ {
+ case SEEK_SET:
+ /* Need reopen file because:
+ "You MUST NOT seek during writing or reading a file with SFTP, as the internals use
+ outstanding packets and changing the "file position" during transit will results in
+ badness." */
+ if (fh->pos > offset || offset == 0)
+ {
+ sftpfs_reopen (fh, mcerror);
+ mc_return_val_if_error (mcerror, 0);
+ }
+ fh->pos = offset;
+ break;
+ case SEEK_CUR:
+ fh->pos += offset;
+ break;
+ case SEEK_END:
+ if (fh->pos > fh->ino->st.st_size - offset)
+ {
+ sftpfs_reopen (fh, mcerror);
+ mc_return_val_if_error (mcerror, 0);
+ }
+ fh->pos = fh->ino->st.st_size - offset;
+ break;
+ default:
+ break;
+ }
+
+ libssh2_sftp_seek64 (file->handle, fh->pos);
+ fh->pos = (off_t) libssh2_sftp_tell64 (file->handle);
+
+ return fh->pos;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/sftpfs/internal.c b/src/vfs/sftpfs/internal.c
new file mode 100644
index 0000000..9faa76c
--- /dev/null
+++ b/src/vfs/sftpfs/internal.c
@@ -0,0 +1,621 @@
+/* Virtual File System: SFTP file system.
+ The internal functions
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Ilia Maslakov <il.smind@gmail.com>, 2011
+ Slava Zanko <slavazanko@gmail.com>, 2011, 2012
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <errno.h>
+
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#else
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+#include "lib/global.h"
+#include "lib/util.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+GString *sftpfs_filename_buffer = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* Adjust block size and number of blocks */
+
+static void
+sftpfs_blksize (struct stat *s)
+{
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ s->st_blksize = LIBSSH2_CHANNEL_WINDOW_DEFAULT; /* FIXME */
+#endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */
+ vfs_adjust_stat (s);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Awaiting for any activity on socket.
+ *
+ * @param super extra data for SFTP connection
+ * @param mcerror pointer to the error object
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_internal_waitsocket (sftpfs_super_t * super, GError ** mcerror)
+{
+ struct timeval timeout = { 10, 0 };
+ fd_set fd;
+ fd_set *writefd = NULL;
+ fd_set *readfd = NULL;
+ int dir, ret;
+
+ mc_return_val_if_error (mcerror, -1);
+
+ FD_ZERO (&fd);
+ FD_SET (super->socket_handle, &fd);
+
+ /* now make sure we wait in the correct direction */
+ dir = libssh2_session_block_directions (super->session);
+
+ if ((dir & LIBSSH2_SESSION_BLOCK_INBOUND) != 0)
+ readfd = &fd;
+
+ if ((dir & LIBSSH2_SESSION_BLOCK_OUTBOUND) != 0)
+ writefd = &fd;
+
+ ret = select (super->socket_handle + 1, readfd, writefd, NULL, &timeout);
+ if (ret < 0)
+ {
+ int my_errno = errno;
+
+ mc_propagate_error (mcerror, my_errno, _("sftp: socket error: %s"),
+ unix_error_string (my_errno));
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sftpfs_stat_init (sftpfs_super_t ** super, const vfs_path_element_t ** path_element,
+ const vfs_path_t * vpath, GError ** mcerror, int stat_type,
+ LIBSSH2_SFTP_ATTRIBUTES * attrs)
+{
+ const GString *fixfname;
+ int res;
+
+ if (!sftpfs_op_init (super, path_element, vpath, mcerror))
+ return -1;
+
+ fixfname = sftpfs_fix_filename ((*path_element)->path);
+
+ do
+ {
+ res = libssh2_sftp_stat_ex ((*super)->sftp_session, fixfname->str, fixfname->len,
+ stat_type, attrs);
+ if (res >= 0)
+ break;
+
+ if (sftpfs_is_sftp_error ((*super)->sftp_session, res, LIBSSH2_FX_PERMISSION_DENIED))
+ return -EACCES;
+
+ if (sftpfs_is_sftp_error ((*super)->sftp_session, res, LIBSSH2_FX_NO_SUCH_FILE))
+ return -ENOENT;
+
+ if (!sftpfs_waitsocket (*super, res, mcerror))
+ return -1;
+ }
+ while (res == LIBSSH2_ERROR_EAGAIN);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+sftpfs_waitsocket (sftpfs_super_t * super, int sftp_res, GError ** mcerror)
+{
+ if (sftp_res != LIBSSH2_ERROR_EAGAIN)
+ {
+ sftpfs_ssherror_to_gliberror (super, sftp_res, mcerror);
+ return FALSE;
+ }
+
+ sftpfs_internal_waitsocket (super, mcerror);
+
+ return (mcerror == NULL || *mcerror == NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+sftpfs_is_sftp_error (LIBSSH2_SFTP * sftp_session, int sftp_res, int sftp_error)
+{
+ return (sftp_res == LIBSSH2_ERROR_SFTP_PROTOCOL &&
+ libssh2_sftp_last_error (sftp_session) == (unsigned long) sftp_error);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Convert libssh error to GError object.
+ *
+ * @param super extra data for SFTP connection
+ * @param libssh_errno errno from libssh
+ * @param mcerror pointer to the error object
+ */
+
+void
+sftpfs_ssherror_to_gliberror (sftpfs_super_t * super, int libssh_errno, GError ** mcerror)
+{
+ char *err = NULL;
+ int err_len;
+
+ mc_return_if_error (mcerror);
+
+ libssh2_session_last_error (super->session, &err, &err_len, 1);
+ if (libssh_errno == LIBSSH2_ERROR_SFTP_PROTOCOL && super->sftp_session != NULL)
+ mc_propagate_error (mcerror, libssh_errno, "%s %lu", err,
+ libssh2_sftp_last_error (super->sftp_session));
+ else
+ mc_propagate_error (mcerror, libssh_errno, "%s", err);
+ g_free (err);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Fix filename for SFTP operations: add leading slash to file name.
+ *
+ * @param file_name file name
+ * @param length length of returned string
+ *
+ * @return pointer to string that contains the file name with leading slash
+ */
+
+const GString *
+sftpfs_fix_filename (const char *file_name)
+{
+ g_string_printf (sftpfs_filename_buffer, "%c%s", PATH_SEP, file_name);
+ return sftpfs_filename_buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+sftpfs_op_init (sftpfs_super_t ** super, const vfs_path_element_t ** path_element,
+ const vfs_path_t * vpath, GError ** mcerror)
+{
+ struct vfs_s_super *lc_super = NULL;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ if (vfs_s_get_path (vpath, &lc_super, 0) == NULL)
+ return FALSE;
+
+ if (lc_super == NULL)
+ return FALSE;
+
+ *super = SFTP_SUPER (lc_super);
+ if ((*super)->sftp_session == NULL)
+ return FALSE;
+
+ *path_element = vfs_path_get_by_index (vpath, -1);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+sftpfs_attr_to_stat (const LIBSSH2_SFTP_ATTRIBUTES * attrs, struct stat *s)
+{
+ if ((attrs->flags & LIBSSH2_SFTP_ATTR_UIDGID) != 0)
+ {
+ s->st_uid = attrs->uid;
+ s->st_gid = attrs->gid;
+ }
+
+ if ((attrs->flags & LIBSSH2_SFTP_ATTR_ACMODTIME) != 0)
+ {
+ s->st_atime = attrs->atime;
+ s->st_mtime = attrs->mtime;
+ s->st_ctime = attrs->mtime;
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ s->st_atim.tv_nsec = s->st_mtim.tv_nsec = s->st_ctim.tv_nsec = 0;
+#endif
+ }
+
+ if ((attrs->flags & LIBSSH2_SFTP_ATTR_SIZE) != 0)
+ {
+ s->st_size = attrs->filesize;
+ sftpfs_blksize (s);
+ }
+
+ if ((attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) != 0)
+ s->st_mode = attrs->permissions;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Getting information about a symbolic link.
+ *
+ * @param vpath path to file, directory or symbolic link
+ * @param buf buffer for store stat-info
+ * @param mcerror pointer to error object
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_lstat (const vfs_path_t * vpath, struct stat *buf, GError ** mcerror)
+{
+ sftpfs_super_t *super = NULL;
+ const vfs_path_element_t *path_element = NULL;
+ LIBSSH2_SFTP_ATTRIBUTES attrs;
+ int res;
+
+ res = sftpfs_stat_init (&super, &path_element, vpath, mcerror, LIBSSH2_SFTP_LSTAT, &attrs);
+ if (res >= 0)
+ {
+ sftpfs_attr_to_stat (&attrs, buf);
+ res = 0;
+ }
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Getting information about a file or directory.
+ *
+ * @param vpath path to file or directory
+ * @param buf buffer for store stat-info
+ * @param mcerror pointer to error object
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_stat (const vfs_path_t * vpath, struct stat *buf, GError ** mcerror)
+{
+ sftpfs_super_t *super = NULL;
+ const vfs_path_element_t *path_element = NULL;
+ LIBSSH2_SFTP_ATTRIBUTES attrs;
+ int res;
+
+ res = sftpfs_stat_init (&super, &path_element, vpath, mcerror, LIBSSH2_SFTP_STAT, &attrs);
+ if (res >= 0)
+ {
+ buf->st_nlink = 1;
+ sftpfs_attr_to_stat (&attrs, buf);
+ res = 0;
+ }
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Read value of a symbolic link.
+ *
+ * @param vpath path to file or directory
+ * @param buf buffer for store stat-info
+ * @param size buffer size
+ * @param mcerror pointer to error object
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_readlink (const vfs_path_t * vpath, char *buf, size_t size, GError ** mcerror)
+{
+ sftpfs_super_t *super = NULL;
+ const vfs_path_element_t *path_element = NULL;
+ const GString *fixfname;
+ int res;
+
+ if (!sftpfs_op_init (&super, &path_element, vpath, mcerror))
+ return -1;
+
+ fixfname = sftpfs_fix_filename (path_element->path);
+
+ do
+ {
+ res =
+ libssh2_sftp_symlink_ex (super->sftp_session, fixfname->str, fixfname->len, buf, size,
+ LIBSSH2_SFTP_READLINK);
+ if (res >= 0)
+ break;
+
+ if (!sftpfs_waitsocket (super, res, mcerror))
+ return -1;
+ }
+ while (res == LIBSSH2_ERROR_EAGAIN);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create symlink to file or directory
+ *
+ * @param vpath1 path to file or directory
+ * @param vpath2 path to symlink
+ * @param mcerror pointer to error object
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_symlink (const vfs_path_t * vpath1, const vfs_path_t * vpath2, GError ** mcerror)
+{
+ sftpfs_super_t *super = NULL;
+ const vfs_path_element_t *path_element2 = NULL;
+ const char *path1;
+ size_t path1_len;
+ const GString *ctmp_path;
+ char *tmp_path;
+ unsigned int tmp_path_len;
+ int res;
+
+ if (!sftpfs_op_init (&super, &path_element2, vpath2, mcerror))
+ return -1;
+
+ ctmp_path = sftpfs_fix_filename (path_element2->path);
+ tmp_path = g_strndup (ctmp_path->str, ctmp_path->len);
+ tmp_path_len = ctmp_path->len;
+
+ path1 = vfs_path_get_last_path_str (vpath1);
+ path1_len = strlen (path1);
+
+ do
+ {
+ res =
+ libssh2_sftp_symlink_ex (super->sftp_session, path1, path1_len, tmp_path, tmp_path_len,
+ LIBSSH2_SFTP_SYMLINK);
+ if (res >= 0)
+ break;
+
+ if (!sftpfs_waitsocket (super, res, mcerror))
+ {
+ g_free (tmp_path);
+ return -1;
+ }
+ }
+ while (res == LIBSSH2_ERROR_EAGAIN);
+ g_free (tmp_path);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Changes the times of the file.
+ *
+ * @param vpath path to file or directory
+ * @param atime access time
+ * @param mtime modification time
+ * @param mcerror pointer to error object
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_utime (const vfs_path_t * vpath, time_t atime, time_t mtime, GError ** mcerror)
+{
+ sftpfs_super_t *super = NULL;
+ const vfs_path_element_t *path_element = NULL;
+ LIBSSH2_SFTP_ATTRIBUTES attrs;
+ const GString *fixfname;
+ int res;
+
+ res = sftpfs_stat_init (&super, &path_element, vpath, mcerror, LIBSSH2_SFTP_LSTAT, &attrs);
+ if (res < 0)
+ return res;
+
+ attrs.flags = LIBSSH2_SFTP_ATTR_ACMODTIME;
+ attrs.atime = atime;
+ attrs.mtime = mtime;
+
+ fixfname = sftpfs_fix_filename (path_element->path);
+
+ do
+ {
+ res =
+ libssh2_sftp_stat_ex (super->sftp_session, fixfname->str, fixfname->len,
+ LIBSSH2_SFTP_SETSTAT, &attrs);
+ if (res >= 0)
+ break;
+
+ if (sftpfs_is_sftp_error (super->sftp_session, res, LIBSSH2_FX_NO_SUCH_FILE))
+ return -ENOENT;
+
+ if (sftpfs_is_sftp_error (super->sftp_session, res, LIBSSH2_FX_FAILURE))
+ {
+ res = 0; /* need something like ftpfs_ignore_chattr_errors */
+ break;
+ }
+
+ if (!sftpfs_waitsocket (super, res, mcerror))
+ return -1;
+ }
+ while (res == LIBSSH2_ERROR_EAGAIN);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Changes the permissions of the file.
+ *
+ * @param vpath path to file or directory
+ * @param mode mode (see man 2 open)
+ * @param mcerror pointer to error object
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_chmod (const vfs_path_t * vpath, mode_t mode, GError ** mcerror)
+{
+ sftpfs_super_t *super = NULL;
+ const vfs_path_element_t *path_element = NULL;
+ LIBSSH2_SFTP_ATTRIBUTES attrs;
+ const GString *fixfname;
+ int res;
+
+ res = sftpfs_stat_init (&super, &path_element, vpath, mcerror, LIBSSH2_SFTP_LSTAT, &attrs);
+ if (res < 0)
+ return res;
+
+ attrs.flags = LIBSSH2_SFTP_ATTR_PERMISSIONS;
+ attrs.permissions = mode;
+
+ fixfname = sftpfs_fix_filename (path_element->path);
+
+ do
+ {
+ res =
+ libssh2_sftp_stat_ex (super->sftp_session, fixfname->str, fixfname->len,
+ LIBSSH2_SFTP_SETSTAT, &attrs);
+ if (res >= 0)
+ break;
+
+ if (sftpfs_is_sftp_error (super->sftp_session, res, LIBSSH2_FX_NO_SUCH_FILE))
+ return -ENOENT;
+
+ if (sftpfs_is_sftp_error (super->sftp_session, res, LIBSSH2_FX_FAILURE))
+ {
+ res = 0; /* need something like ftpfs_ignore_chattr_errors */
+ break;
+ }
+
+ if (!sftpfs_waitsocket (super, res, mcerror))
+ return -1;
+ }
+ while (res == LIBSSH2_ERROR_EAGAIN);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Delete a name from the file system.
+ *
+ * @param vpath path to file or directory
+ * @param mcerror pointer to error object
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_unlink (const vfs_path_t * vpath, GError ** mcerror)
+{
+ sftpfs_super_t *super = NULL;
+ const vfs_path_element_t *path_element = NULL;
+ const GString *fixfname;
+ int res;
+
+ if (!sftpfs_op_init (&super, &path_element, vpath, mcerror))
+ return -1;
+
+ fixfname = sftpfs_fix_filename (path_element->path);
+
+ do
+ {
+ res = libssh2_sftp_unlink_ex (super->sftp_session, fixfname->str, fixfname->len);
+ if (res >= 0)
+ break;
+
+ if (!sftpfs_waitsocket (super, res, mcerror))
+ return -1;
+ }
+ while (res == LIBSSH2_ERROR_EAGAIN);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Rename a file, moving it between directories if required.
+ *
+ * @param vpath1 path to source file or directory
+ * @param vpath2 path to destination file or directory
+ * @param mcerror pointer to error object
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_rename (const vfs_path_t * vpath1, const vfs_path_t * vpath2, GError ** mcerror)
+{
+ sftpfs_super_t *super = NULL;
+ const char *path1;
+ const vfs_path_element_t *path_element2 = NULL;
+ const GString *ctmp_path;
+ char *tmp_path;
+ unsigned int tmp_path_len;
+ const GString *fixfname;
+ int res;
+
+ if (!sftpfs_op_init (&super, &path_element2, vpath2, mcerror))
+ return -1;
+
+ ctmp_path = sftpfs_fix_filename (path_element2->path);
+ tmp_path = g_strndup (ctmp_path->str, ctmp_path->len);
+ tmp_path_len = ctmp_path->len;
+
+ path1 = vfs_path_get_last_path_str (vpath1);
+ fixfname = sftpfs_fix_filename (path1);
+
+ do
+ {
+ res =
+ libssh2_sftp_rename_ex (super->sftp_session, fixfname->str, fixfname->len, tmp_path,
+ tmp_path_len, LIBSSH2_SFTP_SYMLINK);
+ if (res >= 0)
+ break;
+
+ if (!sftpfs_waitsocket (super, res, mcerror))
+ {
+ g_free (tmp_path);
+ return -1;
+ }
+ }
+ while (res == LIBSSH2_ERROR_EAGAIN);
+ g_free (tmp_path);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/sftpfs/internal.h b/src/vfs/sftpfs/internal.h
new file mode 100644
index 0000000..15c547d
--- /dev/null
+++ b/src/vfs/sftpfs/internal.h
@@ -0,0 +1,114 @@
+/**
+ * \file
+ * \brief Header: SFTP FS
+ */
+
+#ifndef MC__VFS_SFTPFS_INTERNAL_H
+#define MC__VFS_SFTPFS_INTERNAL_H
+
+#include <libssh2.h>
+#include <libssh2_sftp.h>
+
+#include "lib/vfs/vfs.h"
+#include "lib/vfs/xdirentry.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define SFTP_DEFAULT_PORT 22
+
+/* LIBSSH2_INVALID_SOCKET is defined in libssh2 >= 1.4.1 */
+#ifndef LIBSSH2_INVALID_SOCKET
+#define LIBSSH2_INVALID_SOCKET -1
+#endif
+
+#define SFTP_SUPER(super) ((sftpfs_super_t *) (super))
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ NONE = 0,
+ PUBKEY = (1 << 0),
+ PASSWORD = (1 << 1),
+ AGENT = (1 << 2)
+} sftpfs_auth_type_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ struct vfs_s_super base;
+
+ sftpfs_auth_type_t auth_type;
+ sftpfs_auth_type_t config_auth_type;
+
+ LIBSSH2_KNOWNHOSTS *known_hosts;
+ char *known_hosts_file;
+
+ LIBSSH2_SESSION *session;
+ LIBSSH2_SFTP *sftp_session;
+
+ LIBSSH2_AGENT *agent;
+
+ char *pubkey;
+ char *privkey;
+
+ int socket_handle;
+ const char *ip_address;
+ vfs_path_element_t *original_connection_info;
+} sftpfs_super_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern GString *sftpfs_filename_buffer;
+extern struct vfs_s_subclass sftpfs_subclass;
+extern struct vfs_class *vfs_sftpfs_ops;
+
+/*** declarations of public functions ************************************************************/
+
+void sftpfs_init_config_variables_patterns (void);
+void sftpfs_deinit_config_variables_patterns (void);
+
+gboolean sftpfs_is_sftp_error (LIBSSH2_SFTP * sftp_session, int sftp_res, int sftp_error);
+void sftpfs_ssherror_to_gliberror (sftpfs_super_t * super, int libssh_errno, GError ** mcerror);
+gboolean sftpfs_waitsocket (sftpfs_super_t * super, int sftp_res, GError ** mcerror);
+
+const GString *sftpfs_fix_filename (const char *file_name);
+
+gboolean sftpfs_op_init (sftpfs_super_t ** super, const vfs_path_element_t ** path_element,
+ const vfs_path_t * vpath, GError ** mcerror);
+
+void sftpfs_attr_to_stat (const LIBSSH2_SFTP_ATTRIBUTES * attrs, struct stat *s);
+int sftpfs_lstat (const vfs_path_t * vpath, struct stat *buf, GError ** mcerror);
+int sftpfs_stat (const vfs_path_t * vpath, struct stat *buf, GError ** mcerror);
+int sftpfs_readlink (const vfs_path_t * vpath, char *buf, size_t size, GError ** mcerror);
+int sftpfs_symlink (const vfs_path_t * vpath1, const vfs_path_t * vpath2, GError ** mcerror);
+int sftpfs_utime (const vfs_path_t * vpath, time_t atime, time_t mtime, GError ** mcerror);
+int sftpfs_chmod (const vfs_path_t * vpath, mode_t mode, GError ** mcerror);
+int sftpfs_unlink (const vfs_path_t * vpath, GError ** mcerror);
+int sftpfs_rename (const vfs_path_t * vpath1, const vfs_path_t * vpath2, GError ** mcerror);
+
+void sftpfs_fill_connection_data_from_config (struct vfs_s_super *super, GError ** mcerror);
+int sftpfs_open_connection (struct vfs_s_super *super, GError ** mcerror);
+void sftpfs_close_connection (struct vfs_s_super *super, const char *shutdown_message,
+ GError ** mcerror);
+
+vfs_file_handler_t *sftpfs_fh_new (struct vfs_s_inode *ino, gboolean changed);
+
+void *sftpfs_opendir (const vfs_path_t * vpath, GError ** mcerror);
+struct vfs_dirent *sftpfs_readdir (void *data, GError ** mcerror);
+int sftpfs_closedir (void *data, GError ** mcerror);
+int sftpfs_mkdir (const vfs_path_t * vpath, mode_t mode, GError ** mcerror);
+int sftpfs_rmdir (const vfs_path_t * vpath, GError ** mcerror);
+
+gboolean sftpfs_open_file (vfs_file_handler_t * fh, int flags, mode_t mode, GError ** mcerror);
+ssize_t sftpfs_read_file (vfs_file_handler_t * fh, char *buffer, size_t count, GError ** mcerror);
+ssize_t sftpfs_write_file (vfs_file_handler_t * fh, const char *buffer, size_t count,
+ GError ** mcerror);
+int sftpfs_close_file (vfs_file_handler_t * fh, GError ** mcerror);
+int sftpfs_fstat (void *data, struct stat *buf, GError ** mcerror);
+off_t sftpfs_lseek (vfs_file_handler_t * fh, off_t offset, int whence, GError ** mcerror);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__VFS_SFTPFS_INTERNAL_H */
diff --git a/src/vfs/sftpfs/sftpfs.c b/src/vfs/sftpfs/sftpfs.c
new file mode 100644
index 0000000..f2cc592
--- /dev/null
+++ b/src/vfs/sftpfs/sftpfs.c
@@ -0,0 +1,866 @@
+/* Virtual File System: SFTP file system.
+ The interface function
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Ilia Maslakov <il.smind@gmail.com>, 2011
+ Slava Zanko <slavazanko@gmail.com>, 2011-2013
+ Andrew Borodin <aborodin@vmail.ru>, 2021-2022
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h> /* memset() */
+
+#include "lib/global.h"
+#include "lib/vfs/netutil.h"
+#include "lib/vfs/utilvfs.h"
+#include "lib/vfs/gc.h"
+#include "lib/widget.h"
+#include "lib/tty/tty.h" /* tty_enable_interrupt_key () */
+
+#include "internal.h"
+
+#include "sftpfs.h"
+
+/*** global variables ****************************************************************************/
+
+struct vfs_s_subclass sftpfs_subclass;
+struct vfs_class *vfs_sftpfs_ops = VFS_CLASS (&sftpfs_subclass); /* used in file.c */
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for VFS-class init action.
+ *
+ * @param me structure of VFS class
+ */
+
+static int
+sftpfs_cb_init (struct vfs_class *me)
+{
+ (void) me;
+
+ if (libssh2_init (0) != 0)
+ return 0;
+
+ sftpfs_filename_buffer = g_string_new ("");
+ sftpfs_init_config_variables_patterns ();
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for VFS-class deinit action.
+ *
+ * @param me structure of VFS class
+ */
+
+static void
+sftpfs_cb_done (struct vfs_class *me)
+{
+ (void) me;
+
+ sftpfs_deinit_config_variables_patterns ();
+ g_string_free (sftpfs_filename_buffer, TRUE);
+ libssh2_exit ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for opening file.
+ *
+ * @param vpath path to file
+ * @param flags flags (see man 2 open)
+ * @param mode mode (see man 2 open)
+ * @return file data handler if success, NULL otherwise
+ */
+
+static void *
+sftpfs_cb_open (const vfs_path_t * vpath, int flags, mode_t mode)
+{
+ vfs_file_handler_t *fh;
+ struct vfs_class *me;
+ struct vfs_s_super *super;
+ const char *path_super;
+ struct vfs_s_inode *path_inode;
+ GError *mcerror = NULL;
+ gboolean is_changed = FALSE;
+
+ path_super = vfs_s_get_path (vpath, &super, 0);
+ if (path_super == NULL)
+ return NULL;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ path_inode = vfs_s_find_inode (me, super, path_super, LINK_FOLLOW, FL_NONE);
+ if (path_inode != NULL && ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)))
+ {
+ me->verrno = EEXIST;
+ return NULL;
+ }
+
+ if (path_inode == NULL)
+ {
+ char *name;
+ struct vfs_s_entry *ent;
+ struct vfs_s_inode *dir;
+
+ name = g_path_get_dirname (path_super);
+ dir = vfs_s_find_inode (me, super, name, LINK_FOLLOW, FL_DIR);
+ g_free (name);
+ if (dir == NULL)
+ return NULL;
+
+ name = g_path_get_basename (path_super);
+ ent = vfs_s_generate_entry (me, name, dir, 0755);
+ g_free (name);
+ path_inode = ent->ino;
+ vfs_s_insert_entry (me, dir, ent);
+ is_changed = TRUE;
+ }
+
+ if (S_ISDIR (path_inode->st.st_mode))
+ {
+ me->verrno = EISDIR;
+ return NULL;
+ }
+
+ fh = sftpfs_fh_new (path_inode, is_changed);
+
+ if (!sftpfs_open_file (fh, flags, mode, &mcerror))
+ {
+ mc_error_message (&mcerror, NULL);
+ g_free (fh);
+ return NULL;
+ }
+
+ vfs_rmstamp (me, (vfsid) super);
+ super->fd_usage++;
+ fh->ino->st.st_nlink++;
+ return fh;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for opening directory.
+ *
+ * @param vpath path to directory
+ * @return directory data handler if success, NULL otherwise
+ */
+
+static void *
+sftpfs_cb_opendir (const vfs_path_t * vpath)
+{
+ GError *mcerror = NULL;
+ void *ret_value;
+
+ /* reset interrupt flag */
+ tty_got_interrupt ();
+
+ ret_value = sftpfs_opendir (vpath, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return ret_value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for reading directory entry.
+ *
+ * @param data directory data handler
+ * @return information about direntry if success, NULL otherwise
+ */
+
+static struct vfs_dirent *
+sftpfs_cb_readdir (void *data)
+{
+ GError *mcerror = NULL;
+ struct vfs_dirent *sftpfs_dirent;
+
+ if (tty_got_interrupt ())
+ {
+ tty_disable_interrupt_key ();
+ return NULL;
+ }
+
+ sftpfs_dirent = sftpfs_readdir (data, &mcerror);
+ if (!mc_error_message (&mcerror, NULL))
+ {
+ if (sftpfs_dirent != NULL)
+ vfs_print_message (_("sftp: (Ctrl-G break) Listing... %s"), sftpfs_dirent->d_name);
+ else
+ vfs_print_message ("%s", _("sftp: Listing done."));
+ }
+
+ return sftpfs_dirent;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for closing directory.
+ *
+ * @param data directory data handler
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_closedir (void *data)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+ rc = sftpfs_closedir (data, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for lstat VFS-function.
+ *
+ * @param vpath path to file or directory
+ * @param buf buffer for store stat-info
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_lstat (const vfs_path_t * vpath, struct stat *buf)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+ rc = sftpfs_lstat (vpath, buf, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for stat VFS-function.
+ *
+ * @param vpath path to file or directory
+ * @param buf buffer for store stat-info
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_stat (const vfs_path_t * vpath, struct stat *buf)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+ rc = sftpfs_stat (vpath, buf, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for fstat VFS-function.
+ *
+ * @param data file data handler
+ * @param buf buffer for store stat-info
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_fstat (void *data, struct stat *buf)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+ rc = sftpfs_fstat (data, buf, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for readlink VFS-function.
+ *
+ * @param vpath path to file or directory
+ * @param buf buffer for store stat-info
+ * @param size buffer size
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_readlink (const vfs_path_t * vpath, char *buf, size_t size)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+ rc = sftpfs_readlink (vpath, buf, size, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for utime VFS-function.
+ *
+ * @param vpath path to file or directory
+ * @param times access and modification time to set
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_utime (const vfs_path_t * vpath, mc_timesbuf_t * times)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+#ifdef HAVE_UTIMENSAT
+ time_t atime = (*times)[0].tv_sec;
+ time_t mtime = (*times)[1].tv_sec;
+#else
+ time_t atime = times->actime;
+ time_t mtime = times->modtime;
+#endif
+
+ rc = sftpfs_utime (vpath, atime, mtime, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for symlink VFS-function.
+ *
+ * @param vpath1 path to file or directory
+ * @param vpath2 path to symlink
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_symlink (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+ rc = sftpfs_symlink (vpath1, vpath2, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for symlink VFS-function.
+ *
+ * @param vpath unused
+ * @param mode unused
+ * @param dev unused
+ * @return always 0
+ */
+
+static int
+sftpfs_cb_mknod (const vfs_path_t * vpath, mode_t mode, dev_t dev)
+{
+ (void) vpath;
+ (void) mode;
+ (void) dev;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for link VFS-function.
+ *
+ * @param vpath1 unused
+ * @param vpath2 unused
+ * @return always 0
+ */
+
+static int
+sftpfs_cb_link (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ (void) vpath1;
+ (void) vpath2;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for chown VFS-function.
+ *
+ * @param vpath unused
+ * @param owner unused
+ * @param group unused
+ * @return always 0
+ */
+
+static int
+sftpfs_cb_chown (const vfs_path_t * vpath, uid_t owner, gid_t group)
+{
+ (void) vpath;
+ (void) owner;
+ (void) group;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for reading file content.
+ *
+ * @param data file data handler
+ * @param buffer buffer for data
+ * @param count data size
+ * @return 0 if success, negative value otherwise
+ */
+
+static ssize_t
+sftpfs_cb_read (void *data, char *buffer, size_t count)
+{
+ int rc;
+ GError *mcerror = NULL;
+ vfs_file_handler_t *fh = VFS_FILE_HANDLER (data);
+
+ if (tty_got_interrupt ())
+ {
+ tty_disable_interrupt_key ();
+ return 0;
+ }
+
+ rc = sftpfs_read_file (fh, buffer, count, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for writing file content.
+ *
+ * @param data file data handler
+ * @param buf buffer for data
+ * @param count data size
+ * @return 0 if success, negative value otherwise
+ */
+
+static ssize_t
+sftpfs_cb_write (void *data, const char *buf, size_t nbyte)
+{
+ int rc;
+ GError *mcerror = NULL;
+ vfs_file_handler_t *fh = VFS_FILE_HANDLER (data);
+
+ rc = sftpfs_write_file (fh, buf, nbyte, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for close file.
+ *
+ * @param data file data handler
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_close (void *data)
+{
+ int rc;
+ GError *mcerror = NULL;
+ struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (data);
+ vfs_file_handler_t *fh = VFS_FILE_HANDLER (data);
+
+ super->fd_usage--;
+ if (super->fd_usage == 0)
+ vfs_stamp_create (vfs_sftpfs_ops, super);
+
+ rc = sftpfs_close_file (fh, &mcerror);
+ mc_error_message (&mcerror, NULL);
+
+ if (fh->handle != -1)
+ close (fh->handle);
+
+ vfs_s_free_inode (vfs_sftpfs_ops, fh->ino);
+
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for chmod VFS-function.
+ *
+ * @param vpath path to file or directory
+ * @param mode mode (see man 2 open)
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_chmod (const vfs_path_t * vpath, mode_t mode)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+ rc = sftpfs_chmod (vpath, mode, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for mkdir VFS-function.
+ *
+ * @param vpath path directory
+ * @param mode mode (see man 2 open)
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_mkdir (const vfs_path_t * vpath, mode_t mode)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+ rc = sftpfs_mkdir (vpath, mode, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for rmdir VFS-function.
+ *
+ * @param vpath path directory
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_rmdir (const vfs_path_t * vpath)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+ rc = sftpfs_rmdir (vpath, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for lseek VFS-function.
+ *
+ * @param data file data handler
+ * @param offset file offset
+ * @param whence method of seek (at begin, at current, at end)
+ * @return 0 if success, negative value otherwise
+ */
+
+static off_t
+sftpfs_cb_lseek (void *data, off_t offset, int whence)
+{
+ off_t ret_offset;
+ vfs_file_handler_t *fh = VFS_FILE_HANDLER (data);
+ GError *mcerror = NULL;
+
+ ret_offset = sftpfs_lseek (fh, offset, whence, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return ret_offset;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for unlink VFS-function.
+ *
+ * @param vpath path to file or directory
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_unlink (const vfs_path_t * vpath)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+ rc = sftpfs_unlink (vpath, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for rename VFS-function.
+ *
+ * @param vpath1 path to source file or directory
+ * @param vpath2 path to destination file or directory
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_rename (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+ rc = sftpfs_rename (vpath1, vpath2, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for errno VFS-function.
+ *
+ * @param me unused
+ * @return value of errno global variable
+ */
+
+static int
+sftpfs_cb_errno (struct vfs_class *me)
+{
+ (void) me;
+
+ return errno;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for fill_names VFS function.
+ * Add SFTP connections to the 'Active VFS connections' list
+ *
+ * @param me unused
+ * @param func callback function for adding SFTP-connection to list of active connections
+ */
+
+static void
+sftpfs_cb_fill_names (struct vfs_class *me, fill_names_f func)
+{
+ GList *iter;
+
+ (void) me;
+
+ for (iter = sftpfs_subclass.supers; iter != NULL; iter = g_list_next (iter))
+ {
+ const struct vfs_s_super *super = (const struct vfs_s_super *) iter->data;
+ GString *name;
+
+ name = vfs_path_element_build_pretty_path_str (super->path_element);
+
+ func (name->str);
+ g_string_free (name, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for checking if connection is equal to existing connection.
+ *
+ * @param vpath_element path element with connection data
+ * @param super data with exists connection
+ * @param vpath unused
+ * @param cookie unused
+ * @return TRUE if connections is equal, FALSE otherwise
+ */
+
+static gboolean
+sftpfs_archive_same (const vfs_path_element_t * vpath_element, struct vfs_s_super *super,
+ const vfs_path_t * vpath, void *cookie)
+{
+ int result;
+ vfs_path_element_t *orig_connect_info;
+
+ (void) vpath;
+ (void) cookie;
+
+ orig_connect_info = SFTP_SUPER (super)->original_connection_info;
+
+ result = ((g_strcmp0 (vpath_element->host, orig_connect_info->host) == 0)
+ && (g_strcmp0 (vpath_element->user, orig_connect_info->user) == 0)
+ && (vpath_element->port == orig_connect_info->port));
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_super *
+sftpfs_new_archive (struct vfs_class *me)
+{
+ sftpfs_super_t *arch;
+
+ arch = g_new0 (sftpfs_super_t, 1);
+ arch->base.me = me;
+ arch->base.name = g_strdup (PATH_SEP_STR);
+ arch->auth_type = NONE;
+ arch->config_auth_type = NONE;
+ arch->socket_handle = LIBSSH2_INVALID_SOCKET;
+
+ return VFS_SUPER (arch);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for opening new connection.
+ *
+ * @param super connection data
+ * @param vpath unused
+ * @param vpath_element path element with connection data
+ * @return 0 if success, -1 otherwise
+ */
+
+static int
+sftpfs_open_archive (struct vfs_s_super *super, const vfs_path_t * vpath,
+ const vfs_path_element_t * vpath_element)
+{
+ GError *mcerror = NULL;
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+ int ret_value;
+
+ (void) vpath;
+
+ if (vpath_element->host == NULL || *vpath_element->host == '\0')
+ {
+ vfs_print_message ("%s", _("sftp: Invalid host name."));
+ vpath_element->class->verrno = EPERM;
+ return -1;
+ }
+
+ sftpfs_super->original_connection_info = vfs_path_element_clone (vpath_element);
+ super->path_element = vfs_path_element_clone (vpath_element);
+
+ sftpfs_fill_connection_data_from_config (super, &mcerror);
+ if (mc_error_message (&mcerror, &ret_value))
+ {
+ vpath_element->class->verrno = ret_value;
+ return -1;
+ }
+
+ super->root =
+ vfs_s_new_inode (vpath_element->class, super,
+ vfs_s_default_stat (vpath_element->class, S_IFDIR | 0755));
+
+ ret_value = sftpfs_open_connection (super, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return ret_value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for closing connection.
+ *
+ * @param me unused
+ * @param super connection data
+ */
+
+static void
+sftpfs_free_archive (struct vfs_class *me, struct vfs_s_super *super)
+{
+ GError *mcerror = NULL;
+
+ (void) me;
+
+ sftpfs_close_connection (super, "Normal Shutdown", &mcerror);
+
+ vfs_path_element_free (SFTP_SUPER (super)->original_connection_info);
+
+ mc_error_message (&mcerror, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for getting directory content.
+ *
+ * @param me unused
+ * @param dir unused
+ * @param remote_path unused
+ * @return always 0
+ */
+
+static int
+sftpfs_cb_dir_load (struct vfs_class *me, struct vfs_s_inode *dir, const char *remote_path)
+{
+ (void) me;
+ (void) dir;
+ (void) remote_path;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Initialization of SFTP Virtual File Sysytem.
+ */
+
+void
+vfs_init_sftpfs (void)
+{
+ tcp_init ();
+
+ vfs_init_subclass (&sftpfs_subclass, "sftpfs", VFSF_NOLINKS | VFSF_REMOTE, "sftp");
+
+ vfs_sftpfs_ops->init = sftpfs_cb_init;
+ vfs_sftpfs_ops->done = sftpfs_cb_done;
+
+ vfs_sftpfs_ops->fill_names = sftpfs_cb_fill_names;
+
+ vfs_sftpfs_ops->opendir = sftpfs_cb_opendir;
+ vfs_sftpfs_ops->readdir = sftpfs_cb_readdir;
+ vfs_sftpfs_ops->closedir = sftpfs_cb_closedir;
+ vfs_sftpfs_ops->mkdir = sftpfs_cb_mkdir;
+ vfs_sftpfs_ops->rmdir = sftpfs_cb_rmdir;
+
+ vfs_sftpfs_ops->stat = sftpfs_cb_stat;
+ vfs_sftpfs_ops->lstat = sftpfs_cb_lstat;
+ vfs_sftpfs_ops->fstat = sftpfs_cb_fstat;
+ vfs_sftpfs_ops->readlink = sftpfs_cb_readlink;
+ vfs_sftpfs_ops->symlink = sftpfs_cb_symlink;
+ vfs_sftpfs_ops->link = sftpfs_cb_link;
+ vfs_sftpfs_ops->utime = sftpfs_cb_utime;
+ vfs_sftpfs_ops->mknod = sftpfs_cb_mknod;
+ vfs_sftpfs_ops->chown = sftpfs_cb_chown;
+ vfs_sftpfs_ops->chmod = sftpfs_cb_chmod;
+
+ vfs_sftpfs_ops->open = sftpfs_cb_open;
+ vfs_sftpfs_ops->read = sftpfs_cb_read;
+ vfs_sftpfs_ops->write = sftpfs_cb_write;
+ vfs_sftpfs_ops->close = sftpfs_cb_close;
+ vfs_sftpfs_ops->lseek = sftpfs_cb_lseek;
+ vfs_sftpfs_ops->unlink = sftpfs_cb_unlink;
+ vfs_sftpfs_ops->rename = sftpfs_cb_rename;
+ vfs_sftpfs_ops->ferrno = sftpfs_cb_errno;
+
+ sftpfs_subclass.archive_same = sftpfs_archive_same;
+ sftpfs_subclass.new_archive = sftpfs_new_archive;
+ sftpfs_subclass.open_archive = sftpfs_open_archive;
+ sftpfs_subclass.free_archive = sftpfs_free_archive;
+ sftpfs_subclass.fh_new = sftpfs_fh_new;
+ sftpfs_subclass.dir_load = sftpfs_cb_dir_load;
+
+ vfs_register_class (vfs_sftpfs_ops);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/sftpfs/sftpfs.h b/src/vfs/sftpfs/sftpfs.h
new file mode 100644
index 0000000..d3a1935
--- /dev/null
+++ b/src/vfs/sftpfs/sftpfs.h
@@ -0,0 +1,23 @@
+/**
+ * \file
+ * \brief Header: SFTP FS
+ */
+
+#ifndef MC__VFS_SFTPFS_H
+#define MC__VFS_SFTPFS_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void vfs_init_sftpfs (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__VFS_SFTPFS_H */
diff --git a/src/vfs/tar/Makefile.am b/src/vfs/tar/Makefile.am
new file mode 100644
index 0000000..16642f0
--- /dev/null
+++ b/src/vfs/tar/Makefile.am
@@ -0,0 +1,10 @@
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+
+noinst_LTLIBRARIES = libvfs-tar.la
+
+libvfs_tar_la_SOURCES = \
+ tar-internal.c tar-internal.h \
+ tar-sparse.c \
+ tar-xheader.c \
+ tar.c tar.h
diff --git a/src/vfs/tar/Makefile.in b/src/vfs/tar/Makefile.in
new file mode 100644
index 0000000..c89786e
--- /dev/null
+++ b/src/vfs/tar/Makefile.in
@@ -0,0 +1,750 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/vfs/tar
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libvfs_tar_la_LIBADD =
+am_libvfs_tar_la_OBJECTS = tar-internal.lo tar-sparse.lo \
+ tar-xheader.lo tar.lo
+libvfs_tar_la_OBJECTS = $(am_libvfs_tar_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/tar-internal.Plo \
+ ./$(DEPDIR)/tar-sparse.Plo ./$(DEPDIR)/tar-xheader.Plo \
+ ./$(DEPDIR)/tar.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libvfs_tar_la_SOURCES)
+DIST_SOURCES = $(libvfs_tar_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+noinst_LTLIBRARIES = libvfs-tar.la
+libvfs_tar_la_SOURCES = \
+ tar-internal.c tar-internal.h \
+ tar-sparse.c \
+ tar-xheader.c \
+ tar.c tar.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/vfs/tar/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/tar/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libvfs-tar.la: $(libvfs_tar_la_OBJECTS) $(libvfs_tar_la_DEPENDENCIES) $(EXTRA_libvfs_tar_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libvfs_tar_la_OBJECTS) $(libvfs_tar_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tar-internal.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tar-sparse.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tar-xheader.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tar.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/tar-internal.Plo
+ -rm -f ./$(DEPDIR)/tar-sparse.Plo
+ -rm -f ./$(DEPDIR)/tar-xheader.Plo
+ -rm -f ./$(DEPDIR)/tar.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/tar-internal.Plo
+ -rm -f ./$(DEPDIR)/tar-sparse.Plo
+ -rm -f ./$(DEPDIR)/tar-xheader.Plo
+ -rm -f ./$(DEPDIR)/tar.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/vfs/tar/tar-internal.c b/src/vfs/tar/tar-internal.c
new file mode 100644
index 0000000..f77b1b3
--- /dev/null
+++ b/src/vfs/tar/tar-internal.c
@@ -0,0 +1,482 @@
+/*
+ Virtual File System: GNU Tar file system.
+
+ Copyright (C) 2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2023
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * \brief Source: Virtual File System: GNU Tar file system
+ * \author Andrew Borodin
+ * \date 2022
+ */
+
+#include <config.h>
+
+#include <ctype.h> /* isspace() */
+#include <inttypes.h> /* uintmax_t */
+#include <stdint.h> /* UINTMAX_MAX, etc */
+
+#include "lib/global.h"
+#include "lib/widget.h" /* message() */
+#include "lib/vfs/vfs.h" /* mc_read() */
+
+#include "tar-internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/* Log base 2 of common values. */
+#define LG_8 3
+#define LG_64 6
+#define LG_256 8
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* Base 64 digits; see RFC 2045 Table 1. */
+static char const base_64_digits[64] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
+};
+
+/* Table of base 64 digit values indexed by unsigned chars.
+ The value is 64 for unsigned chars that are not base 64 digits. */
+static char base64_map[1 + (unsigned char) (-1)];
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+tar_short_read (size_t status, tar_super_t * archive)
+{
+ size_t left; /* bytes left */
+ char *more; /* pointer to next byte to read */
+
+ more = archive->record_start->buffer + status;
+ left = record_size - status;
+
+ while (left % BLOCKSIZE != 0 || (left != 0 && status != 0))
+ {
+ if (status != 0)
+ {
+ ssize_t r;
+
+ r = mc_read (archive->fd, more, left);
+ if (r == -1)
+ return FALSE;
+
+ status = (size_t) r;
+ }
+
+ if (status == 0)
+ break;
+
+ left -= status;
+ more += status;
+ }
+
+ record_end = archive->record_start + (record_size - left) / BLOCKSIZE;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+tar_flush_read (tar_super_t * archive)
+{
+ size_t status;
+
+ status = mc_read (archive->fd, archive->record_start->buffer, record_size);
+ if (status == record_size)
+ return TRUE;
+
+ return tar_short_read (status, archive);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Flush the current buffer from the archive.
+ */
+static gboolean
+tar_flush_archive (tar_super_t * archive)
+{
+ record_start_block += record_end - archive->record_start;
+ current_block = archive->record_start;
+ record_end = archive->record_start + blocking_factor;
+
+ return tar_flush_read (archive);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+tar_seek_archive (tar_super_t * archive, off_t size)
+{
+ off_t start, offset;
+ off_t nrec, nblk;
+ off_t skipped;
+
+ skipped = (blocking_factor - (current_block - archive->record_start)) * BLOCKSIZE;
+ if (size <= skipped)
+ return 0;
+
+ /* Compute number of records to skip */
+ nrec = (size - skipped) / record_size;
+ if (nrec == 0)
+ return 0;
+
+ start = tar_current_block_ordinal (archive);
+
+ offset = mc_lseek (archive->fd, nrec * record_size, SEEK_CUR);
+ if (offset < 0)
+ return offset;
+
+#if 0
+ if ((offset % record_size) != 0)
+ {
+ message (D_ERROR, MSG_ERROR, _("tar: mc_lseek not stopped at a record boundary"));
+ return -1;
+ }
+#endif
+
+ /* Convert to number of records */
+ offset /= BLOCKSIZE;
+ /* Compute number of skipped blocks */
+ nblk = offset - start;
+
+ /* Update buffering info */
+ record_start_block = offset - blocking_factor;
+ current_block = record_end;
+
+ return nblk;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tar_base64_init (void)
+{
+ size_t i;
+
+ memset (base64_map, 64, sizeof base64_map);
+ for (i = 0; i < 64; i++)
+ base64_map[(int) base_64_digits[i]] = i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tar_assign_string (char **string, char *value)
+{
+ g_free (*string);
+ *string = value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tar_assign_string_dup (char **string, const char *value)
+{
+ g_free (*string);
+ *string = g_strdup (value);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tar_assign_string_dup_n (char **string, const char *value, size_t n)
+{
+ g_free (*string);
+ *string = g_strndup (value, n);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Convert buffer at @where0 of size @digs from external format to intmax_t.
+ * @digs must be positive.
+ * If @type is non-NULL, data are of type @type.
+ * The buffer must represent a value in the range -@minval through @maxval;
+ * if the mathematically correct result V would be greater than INTMAX_MAX,
+ * return a negative integer V such that (uintmax_t) V yields the correct result.
+ * If @octal_only, allow only octal numbers instead of the other GNU extensions.
+ *
+ * Result is -1 if the field is invalid.
+ */
+#if !(INTMAX_MAX <= UINTMAX_MAX && - (INTMAX_MIN + 1) <= UINTMAX_MAX)
+#error "tar_from_header() internally represents intmax_t as uintmax_t + sign"
+#endif
+#if !(UINTMAX_MAX / 2 <= INTMAX_MAX)
+#error "tar_from_header() returns intmax_t to represent uintmax_t"
+#endif
+intmax_t
+tar_from_header (const char *where0, size_t digs, char const *type, intmax_t minval,
+ uintmax_t maxval, gboolean octal_only)
+{
+ uintmax_t value = 0;
+ uintmax_t uminval = minval;
+ uintmax_t minus_minval = -uminval;
+ const char *where = where0;
+ char const *lim = where + digs;
+ gboolean negative = FALSE;
+
+ /* Accommodate buggy tar of unknown vintage, which outputs leading
+ NUL if the previous field overflows. */
+ if (*where == '\0')
+ where++;
+
+ /* Accommodate older tars, which output leading spaces. */
+ while (TRUE)
+ {
+ if (where == lim)
+ return (-1);
+
+ if (!isspace ((unsigned char) *where))
+ break;
+
+ where++;
+ }
+
+ if (isodigit (*where))
+ {
+ char const *where1 = where;
+ gboolean overflow = FALSE;
+
+ while (TRUE)
+ {
+ value += *where++ - '0';
+ if (where == lim || !isodigit (*where))
+ break;
+ overflow |= value != (value << LG_8 >> LG_8);
+ value <<= LG_8;
+ }
+
+ /* Parse the output of older, unportable tars, which generate
+ negative values in two's complement octal. If the leading
+ nonzero digit is 1, we can't recover the original value
+ reliably; so do this only if the digit is 2 or more. This
+ catches the common case of 32-bit negative time stamps. */
+ if ((overflow || maxval < value) && *where1 >= 2 && type != NULL)
+ {
+ /* Compute the negative of the input value, assuming two's complement. */
+ int digit;
+
+ digit = (*where1 - '0') | 4;
+ overflow = FALSE;
+ value = 0;
+ where = where1;
+
+ while (TRUE)
+ {
+ value += 7 - digit;
+ where++;
+ if (where == lim || !isodigit (*where))
+ break;
+ digit = *where - '0';
+ overflow |= value != (value << LG_8 >> LG_8);
+ value <<= LG_8;
+ }
+
+ value++;
+ overflow |= value == 0;
+
+ if (!overflow && value <= minus_minval)
+ negative = TRUE;
+ }
+
+ if (overflow)
+ return (-1);
+ }
+ else if (octal_only)
+ {
+ /* Suppress the following extensions. */
+ }
+ else if (*where == '-' || *where == '+')
+ {
+ /* Parse base-64 output produced only by tar test versions
+ 1.13.6 (1999-08-11) through 1.13.11 (1999-08-23).
+ Support for this will be withdrawn in future tar releases. */
+ int dig;
+
+ negative = *where++ == '-';
+
+ while (where != lim && (dig = base64_map[(unsigned char) *where]) < 64)
+ {
+ if (value << LG_64 >> LG_64 != value)
+ return (-1);
+ value = (value << LG_64) | dig;
+ where++;
+ }
+ }
+ else if (where <= lim - 2 && (*where == '\200' /* positive base-256 */
+ || *where == '\377' /* negative base-256 */ ))
+ {
+ /* Parse base-256 output. A nonnegative number N is
+ represented as (256**DIGS)/2 + N; a negative number -N is
+ represented as (256**DIGS) - N, i.e. as two's complement.
+ The representation guarantees that the leading bit is
+ always on, so that we don't confuse this format with the
+ others (assuming ASCII bytes of 8 bits or more). */
+
+ int signbit;
+ uintmax_t topbits;
+
+ signbit = *where & (1 << (LG_256 - 2));
+ topbits =
+ (((uintmax_t) - signbit) << (CHAR_BIT * sizeof (uintmax_t) - LG_256 - (LG_256 - 2)));
+
+ value = (*where++ & ((1 << (LG_256 - 2)) - 1)) - signbit;
+
+ while (TRUE)
+ {
+ value = (value << LG_256) + (unsigned char) *where++;
+ if (where == lim)
+ break;
+
+ if (((value << LG_256 >> LG_256) | topbits) != value)
+ return (-1);
+ }
+
+ negative = signbit != 0;
+ if (negative)
+ value = -value;
+ }
+
+ if (where != lim && *where != '\0' && !isspace ((unsigned char) *where))
+ return (-1);
+
+ if (value <= (negative ? minus_minval : maxval))
+ return tar_represent_uintmax (negative ? -value : value);
+
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+off_t
+off_from_header (const char *p, size_t s)
+{
+ /* Negative offsets are not allowed in tar files, so invoke
+ from_header with minimum value 0, not TYPE_MINIMUM (off_t). */
+ return tar_from_header (p, s, "off_t", 0, TYPE_MAXIMUM (off_t), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Return the location of the next available input or output block.
+ * Return NULL for EOF.
+ */
+union block *
+tar_find_next_block (tar_super_t * archive)
+{
+ if (current_block == record_end)
+ {
+ if (hit_eof)
+ return NULL;
+
+ if (!tar_flush_archive (archive))
+ {
+ message (D_ERROR, MSG_ERROR, _("Inconsistent tar archive"));
+ return NULL;
+ }
+
+ if (current_block == record_end)
+ {
+ hit_eof = TRUE;
+ return NULL;
+ }
+ }
+
+ return current_block;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Indicate that we have used all blocks up thru @block.
+ */
+gboolean
+tar_set_next_block_after (union block * block)
+{
+ while (block >= current_block)
+ current_block++;
+
+ /* Do *not* flush the archive here. If we do, the same argument to tar_set_next_block_after()
+ could mean the next block (if the input record is exactly one block long), which is not
+ what is intended. */
+
+ return !(current_block > record_end);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Compute and return the block ordinal at current_block.
+ */
+off_t
+tar_current_block_ordinal (const tar_super_t * archive)
+{
+ return record_start_block + (current_block - archive->record_start);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Skip over @size bytes of data in blocks in the archive.
+ */
+gboolean
+tar_skip_file (tar_super_t * archive, off_t size)
+{
+ union block *x;
+ off_t nblk;
+
+ nblk = tar_seek_archive (archive, size);
+ if (nblk >= 0)
+ size -= nblk * BLOCKSIZE;
+
+ while (size > 0)
+ {
+ x = tar_find_next_block (archive);
+ if (x == NULL)
+ return FALSE;
+
+ tar_set_next_block_after (x);
+ size -= BLOCKSIZE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/tar/tar-internal.h b/src/vfs/tar/tar-internal.h
new file mode 100644
index 0000000..7b3bb53
--- /dev/null
+++ b/src/vfs/tar/tar-internal.h
@@ -0,0 +1,351 @@
+
+#ifndef MC__VFS_TAR_INTERNAL_H
+#define MC__VFS_TAR_INTERNAL_H
+
+#include <inttypes.h> /* (u)intmax_t */
+#include <limits.h> /* CHAR_BIT, INT_MAX, etc */
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "lib/vfs/xdirentry.h" /* vfs_s_super */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/* tar files are made in basic blocks of this size. */
+#define BLOCKSIZE 512
+
+#define DEFAULT_BLOCKING 20
+
+/* Sparse files are not supported in POSIX ustar format. For sparse files
+ with a POSIX header, a GNU extra header is provided which holds overall
+ sparse information and a few sparse descriptors. When an old GNU header
+ replaces both the POSIX header and the GNU extra header, it holds some
+ sparse descriptors too. Whether POSIX or not, if more sparse descriptors
+ are still needed, they are put into as many successive sparse headers as
+ necessary. The following constants tell how many sparse descriptors fit
+ in each kind of header able to hold them. */
+
+#define SPARSES_IN_EXTRA_HEADER 16
+#define SPARSES_IN_OLDGNU_HEADER 4
+#define SPARSES_IN_SPARSE_HEADER 21
+
+#define SPARSES_IN_STAR_HEADER 4
+#define SPARSES_IN_STAR_EXT_HEADER 21
+
+/* *BEWARE* *BEWARE* *BEWARE* that the following information is still
+ boiling, and may change. Even if the OLDGNU format description should be
+ accurate, the so-called GNU format is not yet fully decided. It is
+ surely meant to use only extensions allowed by POSIX, but the sketch
+ below repeats some ugliness from the OLDGNU format, which should rather
+ go away. Sparse files should be saved in such a way that they do *not*
+ require two passes at archive creation time. Huge files get some POSIX
+ fields to overflow, alternate solutions have to be sought for this. */
+
+/* This is a dir entry that contains the names of files that were in the
+ dir at the time the dump was made. */
+#define GNUTYPE_DUMPDIR 'D'
+
+/* Identifies the *next* file on the tape as having a long linkname. */
+#define GNUTYPE_LONGLINK 'K'
+
+/* Identifies the *next* file on the tape as having a long name. */
+#define GNUTYPE_LONGNAME 'L'
+
+/* Solaris extended header */
+#define SOLARIS_XHDTYPE 'X'
+
+#define GNUTYPE_SPARSE 'S'
+
+
+/* These macros work even on ones'-complement hosts (!).
+ The extra casts work around some compiler bugs. */
+#define TYPE_SIGNED(t) (!((t) 0 < (t) (-1)))
+#define TYPE_MINIMUM(t) (TYPE_SIGNED (t) ? ~(t) 0 << (sizeof (t) * CHAR_BIT - 1) : (t) 0)
+#define TYPE_MAXIMUM(t) (~(t) 0 - TYPE_MINIMUM (t))
+
+#define OFF_FROM_HEADER(where) off_from_header (where, sizeof (where))
+
+#define isodigit(c) ( ((c) >= '0') && ((c) <= '7') )
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* *INDENT-OFF* */
+
+/* POSIX header */
+struct posix_header
+{ /* byte offset */
+ char name[100]; /* 0 */
+ char mode[8]; /* 100 */
+ char uid[8]; /* 108 */
+ char gid[8]; /* 116 */
+ char size[12]; /* 124 */
+ char mtime[12]; /* 136 */
+ char chksum[8]; /* 148 */
+ char typeflag; /* 156 */
+ char linkname[100]; /* 157 */
+ char magic[6]; /* 257 */
+ char version[2]; /* 263 */
+ char uname[32]; /* 265 */
+ char gname[32]; /* 297 */
+ char devmajor[8]; /* 329 */
+ char devminor[8]; /* 337 */
+ char prefix[155]; /* 345 */
+ /* 500 */
+};
+
+/* Descriptor for a single file hole */
+struct sparse
+{ /* byte offset */
+ /* cppcheck-suppress unusedStructMember */
+ char offset[12]; /* 0 */
+ /* cppcheck-suppress unusedStructMember */
+ char numbytes[12]; /* 12 */
+ /* 24 */
+};
+
+/* Extension header for sparse files, used immediately after the GNU extra
+ header, and used only if all sparse information cannot fit into that
+ extra header. There might even be many such extension headers, one after
+ the other, until all sparse information has been recorded. */
+struct sparse_header
+{ /* byte offset */
+ struct sparse sp[SPARSES_IN_SPARSE_HEADER];
+ /* 0 */
+ char isextended; /* 504 */
+ /* 505 */
+};
+
+/* The old GNU format header conflicts with POSIX format in such a way that
+ POSIX archives may fool old GNU tar's, and POSIX tar's might well be
+ fooled by old GNU tar archives. An old GNU format header uses the space
+ used by the prefix field in a POSIX header, and cumulates information
+ normally found in a GNU extra header. With an old GNU tar header, we
+ never see any POSIX header nor GNU extra header. Supplementary sparse
+ headers are allowed, however. */
+struct oldgnu_header
+{ /* byte offset */
+ char unused_pad1[345]; /* 0 */
+ char atime[12]; /* 345 Incr. archive: atime of the file */
+ char ctime[12]; /* 357 Incr. archive: ctime of the file */
+ char offset[12]; /* 369 Multivolume archive: the offset of start of this volume */
+ char longnames[4]; /* 381 Not used */
+ char unused_pad2; /* 385 */
+ struct sparse sp[SPARSES_IN_OLDGNU_HEADER];
+ /* 386 */
+ char isextended; /* 482 Sparse file: Extension sparse header follows */
+ char realsize[12]; /* 483 Sparse file: Real size */
+ /* 495 */
+};
+
+/* J@"org Schilling star header */
+struct star_header
+{ /* byte offset */
+ char name[100]; /* 0 */
+ char mode[8]; /* 100 */
+ char uid[8]; /* 108 */
+ char gid[8]; /* 116 */
+ char size[12]; /* 124 */
+ char mtime[12]; /* 136 */
+ char chksum[8]; /* 148 */
+ char typeflag; /* 156 */
+ char linkname[100]; /* 157 */
+ char magic[6]; /* 257 */
+ char version[2]; /* 263 */
+ char uname[32]; /* 265 */
+ char gname[32]; /* 297 */
+ char devmajor[8]; /* 329 */
+ char devminor[8]; /* 337 */
+ char prefix[131]; /* 345 */
+ char atime[12]; /* 476 */
+ char ctime[12]; /* 488 */
+ /* 500 */
+};
+
+struct star_in_header
+{
+ char fill[345]; /* 0 Everything that is before t_prefix */
+ char prefix[1]; /* 345 t_name prefix */
+ char fill2; /* 346 */
+ char fill3[8]; /* 347 */
+ char isextended; /* 355 */
+ struct sparse sp[SPARSES_IN_STAR_HEADER]; /* 356 */
+ char realsize[12]; /* 452 Actual size of the file */
+ char offset[12]; /* 464 Offset of multivolume contents */
+ char atime[12]; /* 476 */
+ char ctime[12]; /* 488 */
+ char mfill[8]; /* 500 */
+ char xmagic[4]; /* 508 "tar" */
+};
+
+struct star_ext_header
+{
+ struct sparse sp[SPARSES_IN_STAR_EXT_HEADER];
+ char isextended;
+};
+
+/* *INDENT-ON* */
+
+/* tar Header Block, overall structure */
+union block
+{
+ char buffer[BLOCKSIZE];
+ struct posix_header header;
+ struct star_header star_header;
+ struct oldgnu_header oldgnu_header;
+ struct sparse_header sparse_header;
+ struct star_in_header star_in_header;
+ struct star_ext_header star_ext_header;
+};
+
+/* Information about a sparse file */
+struct sp_array
+{
+ off_t offset; /* chunk offset in file */
+ off_t numbytes; /* length of chunk */
+ off_t arch_offset; /* chunk offset in archive */
+};
+
+enum dump_status
+{
+ dump_status_ok,
+ dump_status_short,
+ dump_status_fail,
+ dump_status_not_implemented
+};
+
+enum archive_format
+{
+ TAR_UNKNOWN = 0, /**< format to be decided later */
+ TAR_V7, /**< old V7 tar format */
+ TAR_OLDGNU, /**< GNU format as per before tar 1.12 */
+ TAR_USTAR, /**< POSIX.1-1988 (ustar) format */
+ TAR_POSIX, /**< POSIX.1-2001 format */
+ TAR_STAR, /**< star format defined in 1994 */
+ TAR_GNU /**< almost same as OLDGNU_FORMAT */
+};
+
+typedef struct
+{
+ struct vfs_s_super base; /* base class */
+
+ int fd;
+ struct stat st;
+ enum archive_format type; /**< type of the archive */
+ union block *record_start; /**< start of record of archive */
+} tar_super_t;
+
+struct xheader
+{
+ size_t size;
+ char *buffer;
+};
+
+struct tar_stat_info
+{
+ char *orig_file_name; /**< name of file read from the archive header */
+ char *file_name; /**< name of file for the current archive entry after being normalized */
+ char *link_name; /**< name of link for the current archive entry */
+#if 0
+ char *uname; /**< user name of owner */
+ char *gname; /**< group name of owner */
+#endif
+ struct stat stat; /**< regular filesystem stat */
+
+ /* stat() doesn't always have access, data modification, and status
+ change times in a convenient form, so store them separately. */
+ struct timespec atime;
+ struct timespec mtime;
+ struct timespec ctime;
+
+ off_t archive_file_size; /**< size of file as stored in the archive.
+ Equals stat.st_size for non-sparse files */
+ gboolean is_sparse; /**< is the file sparse */
+
+ /* For sparse files */
+ unsigned int sparse_major;
+ unsigned int sparse_minor;
+ GArray *sparse_map; /**< array of struct sp_array */
+
+ off_t real_size; /**< real size of sparse file */
+ gboolean real_size_set; /**< TRUE when GNU.sparse.realsize is set in archived file */
+
+ gboolean sparse_name_done; /**< TRUE if 'GNU.sparse.name' header was processed pax header parsing.
+ Following 'path' header (lower priority) will be ignored. */
+
+ /* Extended headers */
+ struct xheader xhdr;
+
+ /* For dumpdirs */
+ gboolean is_dumpdir; /**< is the member a dumpdir? */
+ gboolean skipped; /**< the member contents is already read (for GNUTYPE_DUMPDIR) */
+ char *dumpdir; /**< contents of the dump directory */
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+extern const int blocking_factor;
+extern const size_t record_size;
+
+extern union block *record_end; /* last+1 block of archive record */
+extern union block *current_block; /* current block of archive */
+extern off_t record_start_block; /* block ordinal at record_start */
+
+extern union block *current_header;
+
+/* Have we hit EOF yet? */
+extern gboolean hit_eof;
+
+extern struct tar_stat_info current_stat_info;
+
+/*** declarations of public functions ************************************************************/
+
+/* tar-internal.c */
+void tar_base64_init (void);
+void tar_assign_string (char **string, char *value);
+void tar_assign_string_dup (char **string, const char *value);
+void tar_assign_string_dup_n (char **string, const char *value, size_t n);
+intmax_t tar_from_header (const char *where0, size_t digs, char const *type, intmax_t minval,
+ uintmax_t maxval, gboolean octal_only);
+off_t off_from_header (const char *p, size_t s);
+union block *tar_find_next_block (tar_super_t * archive);
+gboolean tar_set_next_block_after (union block *block);
+off_t tar_current_block_ordinal (const tar_super_t * archive);
+gboolean tar_skip_file (tar_super_t * archive, off_t size);
+
+/* tar-sparse.c */
+gboolean tar_sparse_member_p (tar_super_t * archive, struct tar_stat_info *st);
+gboolean tar_sparse_fixup_header (tar_super_t * archive, struct tar_stat_info *st);
+enum dump_status tar_sparse_skip_file (tar_super_t * archive, struct tar_stat_info *st);
+
+/* tar-xheader.c */
+gboolean tar_xheader_decode (struct tar_stat_info *st);
+gboolean tar_xheader_read (tar_super_t * archive, struct xheader *xhdr, union block *header,
+ off_t size);
+gboolean tar_xheader_decode_global (struct xheader *xhdr);
+void tar_xheader_destroy (struct xheader *xhdr);
+
+/*** inline functions ****************************************************************************/
+
+/**
+ * Represent @n using a signed integer I such that (uintmax_t) I == @n.
+ With a good optimizing compiler, this is equivalent to (intmax_t) i
+ and requires zero machine instructions. */
+#if !(UINTMAX_MAX / 2 <= INTMAX_MAX)
+#error "tar_represent_uintmax() returns intmax_t to represent uintmax_t"
+#endif
+static inline intmax_t
+tar_represent_uintmax (uintmax_t n)
+{
+ intmax_t nd;
+
+ if (n <= INTMAX_MAX)
+ return n;
+
+ /* Avoid signed integer overflow on picky platforms. */
+ nd = n - INTMAX_MIN;
+ return nd + INTMAX_MIN;
+}
+
+#endif /* MC__VFS_TAR_INTERNAL_H */
diff --git a/src/vfs/tar/tar-sparse.c b/src/vfs/tar/tar-sparse.c
new file mode 100644
index 0000000..0bc169b
--- /dev/null
+++ b/src/vfs/tar/tar-sparse.c
@@ -0,0 +1,777 @@
+/*
+ Virtual File System: GNU Tar file system.
+
+ Copyright (C) 2003-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2023
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * \brief Source: Virtual File System: GNU Tar file system
+ */
+
+/*
+ * Avoid following error:
+ * comparison of unsigned expression < 0 is always false [-Werror=type-limits]
+ *
+ * https://www.boost.org/doc/libs/1_55_0/libs/integer/test/cstdint_test.cpp
+ * We can't suppress this warning on the command line as not all GCC versions support -Wno-type-limits
+ */
+#if defined(__GNUC__) && (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 4))
+#pragma GCC diagnostic ignored "-Wtype-limits"
+#endif
+
+#include <config.h>
+
+#include <ctype.h> /* isdigit() */
+#include <errno.h>
+#include <inttypes.h> /* uintmax_t */
+
+#include "lib/global.h"
+
+#include "tar-internal.h"
+
+/* Old GNU Format.
+ The sparse file information is stored in the oldgnu_header in the following manner:
+
+ The header is marked with type 'S'. Its 'size' field contains the cumulative size
+ of all non-empty blocks of the file. The actual file size is stored in `realsize'
+ member of oldgnu_header.
+
+ The map of the file is stored in a list of 'struct sparse'. Each struct contains
+ offset to the block of data and its size (both as octal numbers). The first file
+ header contains at most 4 such structs (SPARSES_IN_OLDGNU_HEADER). If the map
+ contains more structs, then the field 'isextended' of the main header is set to
+ 1 (binary) and the 'struct sparse_header' header follows, containing at most
+ 21 following structs (SPARSES_IN_SPARSE_HEADER). If more structs follow, 'isextended'
+ field of the extended header is set and next next extension header follows, etc...
+ */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/* The width in bits of the integer type or expression T.
+ Do not evaluate T. T must not be a bit-field expression.
+ Padding bits are not supported; this is checked at compile-time below. */
+#define TYPE_WIDTH(t) (sizeof (t) * CHAR_BIT)
+
+/* Bound on length of the string representing an unsigned integer
+ value representable in B bits. log10 (2.0) < 146/485. The
+ smallest value of B where this bound is not tight is 2621. */
+#define INT_BITS_STRLEN_BOUND(b) (((b) * 146 + 484) / 485)
+
+/* Does the __typeof__ keyword work? This could be done by
+ 'configure', but for now it's easier to do it by hand. */
+#if (2 <= __GNUC__ \
+ || (4 <= __clang_major__) \
+ || (1210 <= __IBMC__ && defined __IBM__TYPEOF__) \
+ || (0x5110 <= __SUNPRO_C && !__STDC__))
+#define _GL_HAVE___TYPEOF__ 1
+#else
+#define _GL_HAVE___TYPEOF__ 0
+#endif
+
+/* Return 1 if the integer type or expression T might be signed. Return 0
+ if it is definitely unsigned. T must not be a bit-field expression.
+ This macro does not evaluate its argument, and expands to an
+ integer constant expression. */
+#if _GL_HAVE___TYPEOF__
+#define _GL_SIGNED_TYPE_OR_EXPR(t) TYPE_SIGNED (__typeof__ (t))
+#else
+#define _GL_SIGNED_TYPE_OR_EXPR(t) 1
+#endif
+
+/* Return a value with the common real type of E and V and the value of V.
+ Do not evaluate E. */
+#define _GL_INT_CONVERT(e, v) ((1 ? 0 : (e)) + (v))
+
+/* Act like _GL_INT_CONVERT (E, -V) but work around a bug in IRIX 6.5 cc; see
+ <https://lists.gnu.org/r/bug-gnulib/2011-05/msg00406.html>. */
+#define _GL_INT_NEGATE_CONVERT(e, v) ((1 ? 0 : (e)) - (v))
+
+/* Return 1 if the real expression E, after promotion, has a
+ signed or floating type. Do not evaluate E. */
+#define EXPR_SIGNED(e) (_GL_INT_NEGATE_CONVERT (e, 1) < 0)
+
+#define _GL_SIGNED_INT_MAXIMUM(e) \
+ (((_GL_INT_CONVERT (e, 1) << (TYPE_WIDTH (+ (e)) - 2)) - 1) * 2 + 1)
+
+/* The maximum and minimum values for the type of the expression E,
+ after integer promotion. E is not evaluated. */
+#define _GL_INT_MINIMUM(e) \
+ (EXPR_SIGNED (e) \
+ ? ~_GL_SIGNED_INT_MAXIMUM (e) \
+ : _GL_INT_CONVERT (e, 0))
+#define _GL_INT_MAXIMUM(e) \
+ (EXPR_SIGNED (e) \
+ ? _GL_SIGNED_INT_MAXIMUM (e) \
+ : _GL_INT_NEGATE_CONVERT (e, 1))
+
+/* Return 1 if the expression A <op> B would overflow,
+ where OP_RESULT_OVERFLOW (A, B, MIN, MAX) does the actual test,
+ assuming MIN and MAX are the minimum and maximum for the result type.
+ Arguments should be free of side effects. */
+#define _GL_BINARY_OP_OVERFLOW(a, b, op_result_overflow) \
+ op_result_overflow (a, b, \
+ _GL_INT_MINIMUM (_GL_INT_CONVERT (a, b)), \
+ _GL_INT_MAXIMUM (_GL_INT_CONVERT (a, b)))
+
+#define INT_ADD_RANGE_OVERFLOW(a, b, min, max) \
+ ((b) < 0 \
+ ? (a) < (min) - (b) \
+ : (max) - (b) < (a))
+
+
+/* True if __builtin_add_overflow_p (A, B, C) works, and similarly for
+ __builtin_sub_overflow_p and __builtin_mul_overflow_p. */
+#if defined __clang__ || defined __ICC
+/* Clang 11 lacks __builtin_mul_overflow_p, and even if it did it
+ would presumably run afoul of Clang bug 16404. ICC 2021.1's
+ __builtin_add_overflow_p etc. are not treated as integral constant
+ expressions even when all arguments are. */
+#define _GL_HAS_BUILTIN_OVERFLOW_P 0
+#elif defined __has_builtin
+#define _GL_HAS_BUILTIN_OVERFLOW_P __has_builtin (__builtin_mul_overflow_p)
+#else
+#define _GL_HAS_BUILTIN_OVERFLOW_P (7 <= __GNUC__)
+#endif
+
+/* The _GL*_OVERFLOW macros have the same restrictions as the
+ *_RANGE_OVERFLOW macros, except that they do not assume that operands
+ (e.g., A and B) have the same type as MIN and MAX. Instead, they assume
+ that the result (e.g., A + B) has that type. */
+#if _GL_HAS_BUILTIN_OVERFLOW_P
+#define _GL_ADD_OVERFLOW(a, b, min, max) \
+ __builtin_add_overflow_p (a, b, (__typeof__ ((a) + (b))) 0)
+#else
+#define _GL_ADD_OVERFLOW(a, b, min, max) \
+ ((min) < 0 ? INT_ADD_RANGE_OVERFLOW (a, b, min, max) \
+ : (a) < 0 ? (b) <= (a) + (b) \
+ : (b) < 0 ? (a) <= (a) + (b) \
+ : (a) + (b) < (b))
+#endif
+
+/* Bound on length of the string representing an integer type or expression T.
+ T must not be a bit-field expression.
+
+ Subtract 1 for the sign bit if T is signed, and then add 1 more for
+ a minus sign if needed.
+
+ Because _GL_SIGNED_TYPE_OR_EXPR sometimes returns 1 when its argument is
+ unsigned, this macro may overestimate the true bound by one byte when
+ applied to unsigned types of size 2, 4, 16, ... bytes. */
+#define INT_STRLEN_BOUND(t) \
+ (INT_BITS_STRLEN_BOUND (TYPE_WIDTH (t) - _GL_SIGNED_TYPE_OR_EXPR (t)) \
+ + _GL_SIGNED_TYPE_OR_EXPR (t))
+
+/* Bound on buffer size needed to represent an integer type or expression T,
+ including the terminating null. T must not be a bit-field expression. */
+#define INT_BUFSIZE_BOUND(t) (INT_STRLEN_BOUND (t) + 1)
+
+#define UINTMAX_STRSIZE_BOUND INT_BUFSIZE_BOUND (uintmax_t)
+
+#define INT_ADD_OVERFLOW(a, b) \
+ _GL_BINARY_OP_OVERFLOW (a, b, _GL_ADD_OVERFLOW)
+
+#define SPARSES_INIT_COUNT SPARSES_IN_SPARSE_HEADER
+
+#define COPY_BUF(arch,b,buf,src) \
+do \
+{ \
+ char *endp = b->buffer + BLOCKSIZE; \
+ char *dst = buf; \
+ do \
+ { \
+ if (dst == buf + UINTMAX_STRSIZE_BOUND - 1) \
+ /* numeric overflow in sparse archive member */ \
+ return FALSE; \
+ if (src == endp) \
+ { \
+ tar_set_next_block_after (b); \
+ b = tar_find_next_block (arch); \
+ if (b == NULL) \
+ /* unexpected EOF in archive */ \
+ return FALSE; \
+ src = b->buffer; \
+ endp = b->buffer + BLOCKSIZE; \
+ } \
+ *dst = *src++; \
+ } \
+ while (*dst++ != '\n'); \
+ dst[-1] = '\0'; \
+} \
+while (FALSE)
+
+/*** file scope type declarations ****************************************************************/
+
+struct tar_sparse_file;
+
+struct tar_sparse_optab
+{
+ gboolean (*init) (struct tar_sparse_file * file);
+ gboolean (*done) (struct tar_sparse_file * file);
+ gboolean (*sparse_member_p) (struct tar_sparse_file * file);
+ gboolean (*fixup_header) (struct tar_sparse_file * file);
+ gboolean (*decode_header) (tar_super_t * archive, struct tar_sparse_file * file);
+};
+
+struct tar_sparse_file
+{
+ int fd; /**< File descriptor */
+ off_t dumped_size; /**< Number of bytes actually written to the archive */
+ struct tar_stat_info *stat_info; /**< Information about the file */
+ struct tar_sparse_optab const *optab;
+ void *closure; /**< Any additional data optab calls might reqiure */
+};
+
+enum oldgnu_add_status
+{
+ add_ok,
+ add_finish,
+ add_fail
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static gboolean oldgnu_sparse_member_p (struct tar_sparse_file *file);
+static gboolean oldgnu_fixup_header (struct tar_sparse_file *file);
+static gboolean oldgnu_get_sparse_info (tar_super_t * archive, struct tar_sparse_file *file);
+
+static gboolean star_sparse_member_p (struct tar_sparse_file *file);
+static gboolean star_fixup_header (struct tar_sparse_file *file);
+static gboolean star_get_sparse_info (tar_super_t * archive, struct tar_sparse_file *file);
+
+static gboolean pax_sparse_member_p (struct tar_sparse_file *file);
+static gboolean pax_decode_header (tar_super_t * archive, struct tar_sparse_file *file);
+
+/*** file scope variables ************************************************************************/
+
+/* *INDENT-OFF* */
+static struct tar_sparse_optab const oldgnu_optab =
+{
+ .init = NULL, /* No init function */
+ .done = NULL, /* No done function */
+ .sparse_member_p = oldgnu_sparse_member_p,
+ .fixup_header = oldgnu_fixup_header,
+ .decode_header = oldgnu_get_sparse_info
+};
+/* *INDENT-ON* */
+
+/* *INDENT-OFF* */
+static struct tar_sparse_optab const star_optab =
+{
+ .init = NULL, /* No init function */
+ .done = NULL, /* No done function */
+ .sparse_member_p = star_sparse_member_p,
+ .fixup_header = star_fixup_header,
+ .decode_header = star_get_sparse_info
+};
+/* *INDENT-ON* */
+
+/* GNU PAX sparse file format. There are several versions:
+ * 0.0
+
+ The initial version of sparse format used by tar 1.14-1.15.1.
+ The sparse file map is stored in x header:
+
+ GNU.sparse.size Real size of the stored file
+ GNU.sparse.numblocks Number of blocks in the sparse map repeat numblocks time
+ GNU.sparse.offset Offset of the next data block
+ GNU.sparse.numbytes Size of the next data block end repeat
+
+ This has been reported as conflicting with the POSIX specs. The reason is
+ that offsets and sizes of non-zero data blocks were stored in multiple instances
+ of GNU.sparse.offset/GNU.sparse.numbytes variables, whereas POSIX requires the
+ latest occurrence of the variable to override all previous occurrences.
+
+ To avoid this incompatibility two following versions were introduced.
+
+ * 0.1
+
+ Used by tar 1.15.2 -- 1.15.91 (alpha releases).
+
+ The sparse file map is stored in x header:
+
+ GNU.sparse.size Real size of the stored file
+ GNU.sparse.numblocks Number of blocks in the sparse map
+ GNU.sparse.map Map of non-null data chunks. A string consisting of comma-separated
+ values "offset,size[,offset,size]..."
+
+ The resulting GNU.sparse.map string can be *very* long. While POSIX does not impose
+ any limit on the length of a x header variable, this can confuse some tars.
+
+ * 1.0
+
+ Starting from this version, the exact sparse format version is specified explicitly
+ in the header using the following variables:
+
+ GNU.sparse.major Major version
+ GNU.sparse.minor Minor version
+
+ X header keeps the following variables:
+
+ GNU.sparse.name Real file name of the sparse file
+ GNU.sparse.realsize Real size of the stored file (corresponds to the old GNU.sparse.size
+ variable)
+
+ The name field of the ustar header is constructed using the pattern "%d/GNUSparseFile.%p/%f".
+
+ The sparse map itself is stored in the file data block, preceding the actual file data.
+ It consists of a series of octal numbers of arbitrary length, delimited by newlines.
+ The map is padded with nulls to the nearest block boundary.
+
+ The first number gives the number of entries in the map. Following are map entries, each one
+ consisting of two numbers giving the offset and size of the data block it describes.
+
+ The format is designed in such a way that non-posix aware tars and tars not supporting
+ GNU.sparse.* keywords will extract each sparse file in its condensed form with the file map
+ attached and will place it into a separate directory. Then, using a simple program it would be
+ possible to expand the file to its original form even without GNU tar.
+
+ Bu default, v.1.0 archives are created. To use other formats, --sparse-version option is provided.
+ Additionally, v.0.0 can be obtained by deleting GNU.sparse.map from 0.1 format:
+ --sparse-version 0.1 --pax-option delete=GNU.sparse.map
+ */
+
+static struct tar_sparse_optab const pax_optab = {
+ .init = NULL, /* No init function */
+ .done = NULL, /* No done function */
+ .sparse_member_p = pax_sparse_member_p,
+ .fixup_header = NULL, /* No fixup_header function */
+ .decode_header = pax_decode_header
+};
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decode_num (uintmax_t * num, const char *arg, uintmax_t maxval)
+{
+ uintmax_t u;
+ char *arg_lim;
+
+ if (!isdigit (*arg))
+ return FALSE;
+
+ errno = 0;
+ u = (uintmax_t) g_ascii_strtoll (arg, &arg_lim, 10);
+
+ if (!(u <= maxval && errno != ERANGE) || *arg_lim != '\0')
+ return FALSE;
+
+ *num = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_select_optab (const tar_super_t * archive, struct tar_sparse_file *file)
+{
+ switch (archive->type)
+ {
+ case TAR_V7:
+ case TAR_USTAR:
+ return FALSE;
+
+ case TAR_OLDGNU:
+ case TAR_GNU: /* FIXME: This one should disappear? */
+ file->optab = &oldgnu_optab;
+ break;
+
+ case TAR_POSIX:
+ file->optab = &pax_optab;
+ break;
+
+ case TAR_STAR:
+ file->optab = &star_optab;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_init (tar_super_t * archive, struct tar_sparse_file *file)
+{
+ memset (file, 0, sizeof (*file));
+
+ if (!sparse_select_optab (archive, file))
+ return FALSE;
+
+ if (file->optab->init != NULL)
+ return file->optab->init (file);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_done (struct tar_sparse_file *file)
+{
+ if (file->optab->done != NULL)
+ return file->optab->done (file);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_member_p (struct tar_sparse_file *file)
+{
+ if (file->optab->sparse_member_p != NULL)
+ return file->optab->sparse_member_p (file);
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_fixup_header (struct tar_sparse_file *file)
+{
+ if (file->optab->fixup_header != NULL)
+ return file->optab->fixup_header (file);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_decode_header (tar_super_t * archive, struct tar_sparse_file *file)
+{
+ if (file->optab->decode_header != NULL)
+ return file->optab->decode_header (archive, file);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+sparse_add_map (struct tar_stat_info *st, struct sp_array *sp)
+{
+ if (st->sparse_map == NULL)
+ st->sparse_map = g_array_sized_new (FALSE, FALSE, sizeof (struct sp_array), 1);
+ g_array_append_val (st->sparse_map, *sp);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Add a sparse item to the sparse file
+ */
+static enum oldgnu_add_status
+oldgnu_add_sparse (struct tar_sparse_file *file, struct sparse *s)
+{
+ struct sp_array sp;
+
+ if (s->numbytes[0] == '\0')
+ return add_finish;
+
+ sp.offset = OFF_FROM_HEADER (s->offset);
+ sp.numbytes = OFF_FROM_HEADER (s->numbytes);
+
+ if (sp.offset < 0 || sp.numbytes < 0
+ || INT_ADD_OVERFLOW (sp.offset, sp.numbytes)
+ || file->stat_info->stat.st_size < sp.offset + sp.numbytes
+ || file->stat_info->archive_file_size < 0)
+ return add_fail;
+
+ sparse_add_map (file->stat_info, &sp);
+
+ return add_ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+oldgnu_sparse_member_p (struct tar_sparse_file *file)
+{
+ (void) file;
+
+ return current_header->header.typeflag == GNUTYPE_SPARSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+oldgnu_fixup_header (struct tar_sparse_file *file)
+{
+ /* NOTE! st_size was initialized from the header which actually contains archived size.
+ The following fixes it */
+ off_t realsize;
+
+ realsize = OFF_FROM_HEADER (current_header->oldgnu_header.realsize);
+ file->stat_info->archive_file_size = file->stat_info->stat.st_size;
+ file->stat_info->stat.st_size = MAX (0, realsize);
+
+ return (realsize >= 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Convert old GNU format sparse data to internal representation.
+ */
+static gboolean
+oldgnu_get_sparse_info (tar_super_t * archive, struct tar_sparse_file *file)
+{
+ size_t i;
+ union block *h = current_header;
+ int ext_p;
+ enum oldgnu_add_status rc;
+
+ if (file->stat_info->sparse_map != NULL)
+ g_array_set_size (file->stat_info->sparse_map, 0);
+
+ for (i = 0; i < SPARSES_IN_OLDGNU_HEADER; i++)
+ {
+ rc = oldgnu_add_sparse (file, &h->oldgnu_header.sp[i]);
+ if (rc != add_ok)
+ break;
+ }
+
+ for (ext_p = h->oldgnu_header.isextended ? 1 : 0; rc == add_ok && ext_p != 0;
+ ext_p = h->sparse_header.isextended ? 1 : 0)
+ {
+ h = tar_find_next_block (archive);
+ if (h == NULL)
+ return FALSE;
+
+ tar_set_next_block_after (h);
+
+ for (i = 0; i < SPARSES_IN_SPARSE_HEADER && rc == add_ok; i++)
+ rc = oldgnu_add_sparse (file, &h->sparse_header.sp[i]);
+ }
+
+ return (rc != add_fail);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+star_sparse_member_p (struct tar_sparse_file *file)
+{
+ (void) file;
+
+ return current_header->header.typeflag == GNUTYPE_SPARSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+star_fixup_header (struct tar_sparse_file *file)
+{
+ /* NOTE! st_size was initialized from the header which actually contains archived size.
+ The following fixes it */
+ off_t realsize;
+
+ realsize = OFF_FROM_HEADER (current_header->star_in_header.realsize);
+ file->stat_info->archive_file_size = file->stat_info->stat.st_size;
+ file->stat_info->stat.st_size = MAX (0, realsize);
+
+ return (realsize >= 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Convert STAR format sparse data to internal representation
+ */
+static gboolean
+star_get_sparse_info (tar_super_t * archive, struct tar_sparse_file *file)
+{
+ size_t i;
+ union block *h = current_header;
+ int ext_p = 1;
+ enum oldgnu_add_status rc = add_ok;
+
+ if (file->stat_info->sparse_map != NULL)
+ g_array_set_size (file->stat_info->sparse_map, 0);
+
+ if (h->star_in_header.prefix[0] == '\0' && h->star_in_header.sp[0].offset[10] != '\0')
+ {
+ /* Old star format */
+ for (i = 0; i < SPARSES_IN_STAR_HEADER; i++)
+ {
+ rc = oldgnu_add_sparse (file, &h->star_in_header.sp[i]);
+ if (rc != add_ok)
+ break;
+ }
+
+ ext_p = h->star_in_header.isextended ? 1 : 0;
+ }
+
+ for (; rc == add_ok && ext_p != 0; ext_p = h->star_ext_header.isextended ? 1 : 0)
+ {
+ h = tar_find_next_block (archive);
+ if (h == NULL)
+ return FALSE;
+
+ tar_set_next_block_after (h);
+
+ for (i = 0; i < SPARSES_IN_STAR_EXT_HEADER && rc == add_ok; i++)
+ rc = oldgnu_add_sparse (file, &h->star_ext_header.sp[i]);
+
+ file->dumped_size += BLOCKSIZE;
+ }
+
+ return (rc != add_fail);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+pax_sparse_member_p (struct tar_sparse_file *file)
+{
+ return file->stat_info->sparse_map != NULL && file->stat_info->sparse_map->len > 0
+ && file->stat_info->sparse_major > 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+pax_decode_header (tar_super_t * archive, struct tar_sparse_file *file)
+{
+ if (file->stat_info->sparse_major > 0)
+ {
+ uintmax_t u;
+ char nbuf[UINTMAX_STRSIZE_BOUND];
+ union block *blk;
+ char *p;
+ size_t sparse_map_len;
+ size_t i;
+ off_t start;
+
+ start = tar_current_block_ordinal (archive);
+ tar_set_next_block_after (current_header);
+ blk = tar_find_next_block (archive);
+ if (blk == NULL)
+ /* unexpected EOF in archive */
+ return FALSE;
+ p = blk->buffer;
+ COPY_BUF (archive, blk, nbuf, p);
+
+ if (!decode_num (&u, nbuf, TYPE_MAXIMUM (size_t)))
+ {
+ /* malformed sparse archive member */
+ return FALSE;
+ }
+
+ if (file->stat_info->sparse_map == NULL)
+ file->stat_info->sparse_map =
+ g_array_sized_new (FALSE, FALSE, sizeof (struct sp_array), u);
+ else
+ g_array_set_size (file->stat_info->sparse_map, u);
+
+ sparse_map_len = u;
+
+ for (i = 0; i < sparse_map_len; i++)
+ {
+ struct sp_array sp;
+
+ COPY_BUF (archive, blk, nbuf, p);
+ if (!decode_num (&u, nbuf, TYPE_MAXIMUM (off_t)))
+ {
+ /* malformed sparse archive member */
+ return FALSE;
+ }
+ sp.offset = u;
+ COPY_BUF (archive, blk, nbuf, p);
+ if (!decode_num (&u, nbuf, TYPE_MAXIMUM (size_t)) || INT_ADD_OVERFLOW (sp.offset, u)
+ || (uintmax_t) file->stat_info->stat.st_size < sp.offset + u)
+ {
+ /* malformed sparse archive member */
+ return FALSE;
+ }
+ sp.numbytes = u;
+ sparse_add_map (file->stat_info, &sp);
+ }
+
+ tar_set_next_block_after (blk);
+
+ file->dumped_size += BLOCKSIZE * (tar_current_block_ordinal (archive) - start);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+tar_sparse_member_p (tar_super_t * archive, struct tar_stat_info * st)
+{
+ struct tar_sparse_file file;
+
+ if (!sparse_init (archive, &file))
+ return FALSE;
+
+ file.stat_info = st;
+ return sparse_member_p (&file);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+tar_sparse_fixup_header (tar_super_t * archive, struct tar_stat_info * st)
+{
+ struct tar_sparse_file file;
+
+ if (!sparse_init (archive, &file))
+ return FALSE;
+
+ file.stat_info = st;
+ return sparse_fixup_header (&file);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+enum dump_status
+tar_sparse_skip_file (tar_super_t * archive, struct tar_stat_info *st)
+{
+ gboolean rc = TRUE;
+ struct tar_sparse_file file;
+
+ if (!sparse_init (archive, &file))
+ return dump_status_not_implemented;
+
+ file.stat_info = st;
+ file.fd = -1;
+
+ rc = sparse_decode_header (archive, &file);
+ (void) tar_skip_file (archive, file.stat_info->archive_file_size - file.dumped_size);
+ return (sparse_done (&file) && rc) ? dump_status_ok : dump_status_short;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/tar/tar-xheader.c b/src/vfs/tar/tar-xheader.c
new file mode 100644
index 0000000..5062ed1
--- /dev/null
+++ b/src/vfs/tar/tar-xheader.c
@@ -0,0 +1,1051 @@
+/*
+ Virtual File System: GNU Tar file system.
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2023
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * \brief Source: Virtual File System: GNU Tar file system
+ */
+
+#include <config.h>
+
+#include <ctype.h> /* isdigit() */
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/util.h" /* MC_PTR_FREE */
+
+#include "tar-internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define XHDR_PROTECTED 0x01
+#define XHDR_GLOBAL 0x02
+
+/*** file scope type declarations ****************************************************************/
+
+/* General Interface */
+
+/* Since tar VFS is read-only, inplement decodes only */
+/* *INDENT-OFF* */
+struct xhdr_tab
+{
+ const char *keyword;
+ gboolean (*decoder) (struct tar_stat_info * st, const char *keyword, const char *arg, size_t size);
+ int flags;
+};
+/* *INDENT-ON* */
+
+/* Keyword options */
+struct keyword_item
+{
+ char *pattern;
+ char *value;
+};
+
+enum decode_record_status
+{
+ decode_record_ok,
+ decode_record_finish,
+ decode_record_fail
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static gboolean dummy_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean atime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean gid_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+#if 0
+static gboolean gname_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+#endif
+static gboolean linkpath_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean mtime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean ctime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean path_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean size_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean uid_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+#if 0
+static gboolean uname_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+#endif
+static gboolean sparse_path_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean sparse_major_decoder (struct tar_stat_info *st, const char *keyword,
+ const char *arg, size_t size);
+static gboolean sparse_minor_decoder (struct tar_stat_info *st, const char *keyword,
+ const char *arg, size_t size);
+static gboolean sparse_size_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean sparse_numblocks_decoder (struct tar_stat_info *st, const char *keyword,
+ const char *arg, size_t size);
+static gboolean sparse_offset_decoder (struct tar_stat_info *st, const char *keyword,
+ const char *arg, size_t size);
+static gboolean sparse_numbytes_decoder (struct tar_stat_info *st, const char *keyword,
+ const char *arg, size_t size);
+static gboolean sparse_map_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean dumpdir_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+
+/*** file scope variables ************************************************************************/
+
+enum
+{
+ BILLION = 1000000000,
+ LOG10_BILLION = 9
+};
+
+/* *INDENT-OFF* */
+static struct xhdr_tab xhdr_tab[] =
+{
+ { "atime", atime_decoder, 0 },
+ { "comment", dummy_decoder, 0 },
+ { "charset", dummy_decoder, 0 },
+ { "ctime", ctime_decoder, 0 },
+ { "gid", gid_decoder, 0 },
+#if 0
+ { "gname", gname_decoder, 0 },
+#endif
+ { "linkpath", linkpath_decoder, 0 },
+ { "mtime", mtime_decoder, 0 },
+ { "path", path_decoder, 0 },
+ { "size", size_decoder, 0 },
+ { "uid", uid_decoder, 0 },
+#if 0
+ { "uname", uname_decoder, 0 },
+#endif
+
+ /* Sparse file handling */
+ { "GNU.sparse.name", sparse_path_decoder, XHDR_PROTECTED },
+ { "GNU.sparse.major", sparse_major_decoder, XHDR_PROTECTED },
+ { "GNU.sparse.minor", sparse_minor_decoder, XHDR_PROTECTED },
+ { "GNU.sparse.realsize", sparse_size_decoder, XHDR_PROTECTED },
+ { "GNU.sparse.numblocks", sparse_numblocks_decoder, XHDR_PROTECTED },
+
+ { "GNU.sparse.size", sparse_size_decoder, XHDR_PROTECTED },
+ /* tar 1.14 - 1.15.1 keywords. Multiple instances of these appeared in 'x'
+ headers, and each of them was meaningful. It confilcted with POSIX specs,
+ which requires that "when extended header records conflict, the last one
+ given in the header shall take precedence." */
+ { "GNU.sparse.offset", sparse_offset_decoder, XHDR_PROTECTED },
+ { "GNU.sparse.numbytes", sparse_numbytes_decoder, XHDR_PROTECTED },
+ /* tar 1.15.90 keyword, introduced to remove the above-mentioned conflict. */
+ { "GNU.sparse.map", sparse_map_decoder, 0 },
+
+ { "GNU.dumpdir", dumpdir_decoder, XHDR_PROTECTED },
+
+ { NULL, NULL, 0 }
+};
+/* *INDENT-ON* */
+
+/* List of keyword/value pairs decoded from the last 'g' type header */
+static GSList *global_header_override_list = NULL;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* Convert a prefix of the string @arg to a system integer type whose minimum value is @minval
+ and maximum @maxval. If @minval is negative, negative integers @minval .. -1 are assumed
+ to be represented using leading '-' in the usual way. If the represented value exceeds INTMAX_MAX,
+ return a negative integer V such that (uintmax_t) V yields the represented value. If @arglim is
+ nonnull, store into *@arglim a pointer to the first character after the prefix.
+
+ This is the inverse of sysinttostr.
+
+ On a normal return, set errno = 0.
+ On conversion error, return 0 and set errno = EINVAL.
+ On overflow, return an extreme value and set errno = ERANGE.
+ */
+#if ! (INTMAX_MAX <= UINTMAX_MAX)
+#error "strtosysint: nonnegative intmax_t does not fit in uintmax_t"
+#endif
+static intmax_t
+strtosysint (const char *arg, char **arglim, intmax_t minval, uintmax_t maxval)
+{
+ errno = 0;
+
+ if (maxval <= INTMAX_MAX)
+ {
+ if (isdigit (arg[*arg == '-' ? 1 : 0]))
+ {
+ gint64 i;
+
+ i = g_ascii_strtoll (arg, arglim, 10);
+ if ((gint64) minval <= i && i <= (gint64) maxval)
+ return (intmax_t) i;
+
+ errno = ERANGE;
+ return i < (gint64) minval ? minval : (intmax_t) maxval;
+ }
+ }
+ else
+ {
+ if (isdigit (*arg))
+ {
+ guint64 i;
+
+ i = g_ascii_strtoull (arg, arglim, 10);
+ if (i <= (guint64) maxval)
+ return tar_represent_uintmax ((uintmax_t) i);
+
+ errno = ERANGE;
+ return maxval;
+ }
+ }
+
+ errno = EINVAL;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct xhdr_tab *
+locate_handler (const char *keyword)
+{
+ struct xhdr_tab *p;
+
+ for (p = xhdr_tab; p->keyword != NULL; p++)
+ if (strcmp (p->keyword, keyword) == 0)
+ return p;
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+keyword_item_run (gpointer data, gpointer user_data)
+{
+ struct keyword_item *kp = (struct keyword_item *) data;
+ struct tar_stat_info *st = (struct tar_stat_info *) user_data;
+ struct xhdr_tab const *t;
+
+ t = locate_handler (kp->pattern);
+ if (t != NULL)
+ return t->decoder (st, t->keyword, kp->value, strlen (kp->value));
+
+ return TRUE; /* FIXME */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+keyword_item_free (gpointer data)
+{
+ struct keyword_item *kp = (struct keyword_item *) data;
+
+ g_free (kp->pattern);
+ g_free (kp->value);
+ g_free (kp);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+xheader_list_append (GSList ** root, const char *kw, const char *value)
+{
+ struct keyword_item *kp;
+
+ kp = g_new (struct keyword_item, 1);
+ kp->pattern = g_strdup (kw);
+ kp->value = g_strdup (value);
+ *root = g_slist_prepend (*root, kp);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+xheader_list_destroy (GSList ** root)
+{
+ g_slist_free_full (*root, keyword_item_free);
+ *root = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+run_override_list (GSList * kp, struct tar_stat_info *st)
+{
+ g_slist_foreach (kp, (GFunc) keyword_item_run, st);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct timespec
+decode_timespec (const char *arg, char **arg_lim, gboolean parse_fraction)
+{
+ time_t s = TYPE_MINIMUM (time_t);
+ int ns = -1;
+ const char *p = arg;
+ gboolean negative = *arg == '-';
+ struct timespec r;
+
+ if (!isdigit (arg[negative]))
+ errno = EINVAL;
+ else
+ {
+ errno = 0;
+
+ if (negative)
+ {
+ gint64 i;
+
+ i = g_ascii_strtoll (arg, arg_lim, 10);
+ if (TYPE_SIGNED (time_t) ? TYPE_MINIMUM (time_t) <= i : 0 <= i)
+ s = (intmax_t) i;
+ else
+ errno = ERANGE;
+ }
+ else
+ {
+ guint64 i;
+
+ i = g_ascii_strtoull (arg, arg_lim, 10);
+ if (i <= TYPE_MAXIMUM (time_t))
+ s = (uintmax_t) i;
+ else
+ errno = ERANGE;
+ }
+
+ p = *arg_lim;
+ ns = 0;
+
+ if (parse_fraction && *p == '.')
+ {
+ int digits = 0;
+ gboolean trailing_nonzero = FALSE;
+
+ while (isdigit (*++p))
+ if (digits < LOG10_BILLION)
+ {
+ digits++;
+ ns = 10 * ns + (*p - '0');
+ }
+ else if (*p != '0')
+ trailing_nonzero = TRUE;
+
+ while (digits < LOG10_BILLION)
+ {
+ digits++;
+ ns *= 10;
+ }
+
+ if (negative)
+ {
+ /* Convert "-1.10000000000001" to s == -2, ns == 89999999.
+ I.e., truncate time stamps towards minus infinity while
+ converting them to internal form. */
+ if (trailing_nonzero)
+ ns++;
+ if (ns != 0)
+ {
+ if (s == TYPE_MINIMUM (time_t))
+ ns = -1;
+ else
+ {
+ s--;
+ ns = BILLION - ns;
+ }
+ }
+ }
+ }
+
+ if (errno == ERANGE)
+ ns = -1;
+ }
+
+ *arg_lim = (char *) p;
+ r.tv_sec = s;
+ r.tv_nsec = ns;
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decode_time (struct timespec *ts, const char *arg, const char *keyword)
+{
+ char *arg_lim;
+ struct timespec t;
+
+ (void) keyword;
+
+ t = decode_timespec (arg, &arg_lim, TRUE);
+
+ if (t.tv_nsec < 0)
+ /* Malformed extended header */
+ return FALSE;
+
+ if (*arg_lim != '\0')
+ /* Malformed extended header */
+ return FALSE;
+
+ *ts = t;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decode_signed_num (intmax_t * num, const char *arg, intmax_t minval, uintmax_t maxval,
+ const char *keyword)
+{
+ char *arg_lim;
+ intmax_t u;
+
+ (void) keyword;
+
+ if (!isdigit (*arg))
+ return FALSE; /* malformed extended header */
+
+ u = strtosysint (arg, &arg_lim, minval, maxval);
+
+ if (errno == EINVAL || *arg_lim != '\0')
+ return FALSE; /* malformed extended header */
+
+ if (errno == ERANGE)
+ return FALSE; /* out of range */
+
+ *num = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decode_num (uintmax_t * num, const char *arg, uintmax_t maxval, const char *keyword)
+{
+ intmax_t i;
+
+ if (!decode_signed_num (&i, arg, 0, maxval, keyword))
+ return FALSE;
+
+ *num = i;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+raw_path_decoder (struct tar_stat_info *st, const char *arg)
+{
+ if (*arg != '\0')
+ {
+ tar_assign_string_dup (&st->orig_file_name, arg);
+ tar_assign_string_dup (&st->file_name, arg);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+dummy_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) st;
+ (void) keyword;
+ (void) arg;
+ (void) size;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+atime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ struct timespec ts;
+
+ (void) size;
+
+ if (!decode_time (&ts, arg, keyword))
+ return FALSE;
+
+ st->atime = ts;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+gid_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ intmax_t u;
+
+ (void) size;
+
+ if (!decode_signed_num (&u, arg, TYPE_MINIMUM (gid_t), TYPE_MINIMUM (gid_t), keyword))
+ return FALSE;
+
+ st->stat.st_gid = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if 0
+static gboolean
+gname_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+ (void) size;
+
+ tar_assign_string_dup (&st->gname, arg);
+ return TRUE;
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+linkpath_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+ (void) size;
+
+ tar_assign_string_dup (&st->link_name, arg);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+ctime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ struct timespec ts;
+
+ (void) size;
+
+ if (!decode_time (&ts, arg, keyword))
+ return FALSE;
+
+ st->ctime = ts;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mtime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ struct timespec ts;
+
+ (void) size;
+
+ if (!decode_time (&ts, arg, keyword))
+ return FALSE;
+
+ st->mtime = ts;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+path_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+ (void) size;
+
+ if (!st->sparse_name_done)
+ return raw_path_decoder (st, arg);
+
+ return TRUE; /* FIXME */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+size_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ uintmax_t u;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, TYPE_MAXIMUM (off_t), keyword))
+ return FALSE;
+
+ st->stat.st_size = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+uid_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ intmax_t u;
+
+ (void) size;
+
+ if (!decode_signed_num (&u, arg, TYPE_MINIMUM (uid_t), TYPE_MAXIMUM (uid_t), keyword))
+ return FALSE;
+
+ st->stat.st_uid = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if 0
+static gboolean
+uname_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+ (void) size;
+
+ tar_assign_string_dup (&st->uname, arg);
+ return TRUE;
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+dumpdir_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+
+#if GLIB_CHECK_VERSION (2, 68, 0)
+ st->dumpdir = g_memdup2 (arg, size);
+#else
+ st->dumpdir = g_memdup (arg, size);
+#endif
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Decodes a single extended header record, advancing @ptr to the next record.
+ *
+ * @param p pointer to extended header record
+ * @param st stat info
+ *
+ * @return decode_record_ok or decode_record_finish on success, decode_record_fail otherwize
+ */
+static enum decode_record_status
+decode_record (struct xheader *xhdr, char **ptr,
+ gboolean (*handler) (void *data, const char *keyword, const char *value,
+ size_t size), void *data)
+{
+ char *start = *ptr;
+ char *p = start;
+ size_t len;
+ char *len_lim;
+ const char *keyword;
+ char *nextp;
+ size_t len_max;
+ gboolean ret;
+
+ len_max = xhdr->buffer + xhdr->size - start;
+
+ while (*p == ' ' || *p == '\t')
+ p++;
+
+ if (!isdigit (*p))
+ return (*p != '\0' ? decode_record_fail : decode_record_finish);
+
+ len = (uintmax_t) g_ascii_strtoull (p, &len_lim, 10);
+ if (len_max < len)
+ return decode_record_fail;
+
+ nextp = start + len;
+
+ for (p = len_lim; *p == ' ' || *p == '\t'; p++)
+ ;
+
+ if (p == len_lim)
+ return decode_record_fail;
+
+ keyword = p;
+ p = strchr (p, '=');
+ if (!(p != NULL && p < nextp))
+ return decode_record_fail;
+
+ if (nextp[-1] != '\n')
+ return decode_record_fail;
+
+ *p = nextp[-1] = '\0';
+ ret = handler (data, keyword, p + 1, nextp - p - 2); /* '=' + trailing '\n' */
+ *p = '=';
+ nextp[-1] = '\n';
+ *ptr = nextp;
+
+ return (ret ? decode_record_ok : decode_record_fail);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decg (void *data, const char *keyword, const char *value, size_t size)
+{
+ GSList **kwl = (GSList **) data;
+ struct xhdr_tab const *tab;
+
+ (void) size;
+
+ tab = locate_handler (keyword);
+ if (tab != NULL && (tab->flags & XHDR_GLOBAL) != 0)
+ {
+ if (!tab->decoder (data, keyword, value, size))
+ return FALSE;
+ }
+ else
+ xheader_list_append (kwl, keyword, value);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decx (void *data, const char *keyword, const char *value, size_t size)
+{
+ struct keyword_item kp = {
+ .pattern = (char *) keyword,
+ .value = (char *) value
+ };
+
+ (void) size;
+
+ return keyword_item_run (&kp, data);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_path_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+ (void) size;
+
+ st->sparse_name_done = TRUE;
+ return raw_path_decoder (st, arg);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_major_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ uintmax_t u;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, TYPE_MAXIMUM (unsigned), keyword))
+ return FALSE;
+
+ st->sparse_major = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_minor_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ uintmax_t u;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, TYPE_MAXIMUM (unsigned), keyword))
+ return FALSE;
+
+ st->sparse_minor = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_size_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ uintmax_t u;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, TYPE_MAXIMUM (off_t), keyword))
+ return FALSE;
+
+ st->real_size_set = TRUE;
+ st->real_size = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_numblocks_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size)
+{
+ uintmax_t u;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, SIZE_MAX, keyword))
+ return FALSE;
+
+ if (st->sparse_map == NULL)
+ st->sparse_map = g_array_sized_new (FALSE, FALSE, sizeof (struct sp_array), u);
+ else
+ g_array_set_size (st->sparse_map, u);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_offset_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ uintmax_t u;
+ struct sp_array *s;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, TYPE_MAXIMUM (off_t), keyword))
+ return FALSE;
+
+ s = &g_array_index (st->sparse_map, struct sp_array, st->sparse_map->len - 1);
+ s->offset = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_numbytes_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size)
+{
+ uintmax_t u;
+ struct sp_array s;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, SIZE_MAX, keyword))
+ return FALSE;
+
+ s.offset = 0;
+ s.numbytes = u;
+ g_array_append_val (st->sparse_map, s);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_map_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ gboolean offset = TRUE;
+ struct sp_array e;
+
+ (void) keyword;
+ (void) size;
+
+ if (st->sparse_map != NULL)
+ g_array_set_size (st->sparse_map, 0);
+
+ while (TRUE)
+ {
+ gint64 u;
+ char *delim;
+
+ if (!isdigit (*arg))
+ {
+ /* malformed extended header */
+ return FALSE;
+ }
+
+ errno = 0;
+ u = g_ascii_strtoll (arg, &delim, 10);
+ if (TYPE_MAXIMUM (off_t) < u)
+ {
+ u = TYPE_MAXIMUM (off_t);
+ errno = ERANGE;
+ }
+ if (offset)
+ {
+ e.offset = u;
+ if (errno == ERANGE)
+ {
+ /* out of range */
+ return FALSE;
+ }
+ }
+ else
+ {
+ e.numbytes = u;
+ if (errno == ERANGE)
+ {
+ /* out of range */
+ return FALSE;
+ }
+
+ g_array_append_val (st->sparse_map, e);
+ }
+
+ offset = !offset;
+
+ if (*delim == '\0')
+ break;
+ if (*delim != ',')
+ {
+ /* malformed extended header */
+ return FALSE;
+ }
+
+ arg = delim + 1;
+ }
+
+ if (!offset)
+ {
+ /* malformed extended header */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Decodes an extended header.
+ *
+ * @param st stat info
+ *
+ * @return TRUE on success, FALSE otherwize
+ */
+gboolean
+tar_xheader_decode (struct tar_stat_info * st)
+{
+ char *p;
+ enum decode_record_status status;
+
+ run_override_list (global_header_override_list, st);
+
+ p = st->xhdr.buffer + BLOCKSIZE;
+
+ while ((status = decode_record (&st->xhdr, &p, decx, st)) == decode_record_ok)
+ ;
+
+ if (status == decode_record_fail)
+ return FALSE;
+
+ /* The archived (effective) file size is always set directly in tar header
+ field, possibly overridden by "size" extended header - in both cases,
+ result is now decoded in st->stat.st_size */
+ st->archive_file_size = st->stat.st_size;
+
+ /* The real file size (given by stat()) may be redefined for sparse
+ files in "GNU.sparse.realsize" extended header */
+ if (st->real_size_set)
+ st->stat.st_size = st->real_size;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+tar_xheader_read (tar_super_t * archive, struct xheader * xhdr, union block * p, off_t size)
+{
+ size_t j = 0;
+
+ size = MAX (0, size);
+ size += BLOCKSIZE;
+
+ xhdr->size = size;
+ xhdr->buffer = g_malloc (size + 1);
+ xhdr->buffer[size] = '\0';
+
+ do
+ {
+ size_t len;
+
+ if (p == NULL)
+ return FALSE; /* Unexpected EOF in archive */
+
+ len = MIN (size, BLOCKSIZE);
+
+ memcpy (xhdr->buffer + j, p->buffer, len);
+ tar_set_next_block_after (p);
+ p = tar_find_next_block (archive);
+
+ j += len;
+ size -= len;
+ }
+ while (size > 0);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+tar_xheader_decode_global (struct xheader * xhdr)
+{
+ char *p;
+ gboolean ret;
+
+ p = xhdr->buffer + BLOCKSIZE;
+
+ xheader_list_destroy (&global_header_override_list);
+
+ while ((ret = decode_record (xhdr, &p, decg, &global_header_override_list)) == decode_record_ok)
+ ;
+
+ return (ret == decode_record_finish);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tar_xheader_destroy (struct xheader *xhdr)
+{
+ MC_PTR_FREE (xhdr->buffer);
+ xhdr->size = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/tar/tar.c b/src/vfs/tar/tar.c
new file mode 100644
index 0000000..2d32111
--- /dev/null
+++ b/src/vfs/tar/tar.c
@@ -0,0 +1,1302 @@
+/*
+ Virtual File System: GNU Tar file system.
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Jakub Jelinek, 1995
+ Pavel Machek, 1998
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * \brief Source: Virtual File System: GNU Tar file system
+ * \author Jakub Jelinek
+ * \author Pavel Machek
+ * \date 1995, 1998
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <string.h> /* memset() */
+
+#ifdef hpux
+/* major() and minor() macros (among other things) defined here for hpux */
+#include <sys/mknod.h>
+#endif
+
+#include "lib/global.h"
+#include "lib/util.h"
+#include "lib/unixcompat.h" /* makedev() */
+#include "lib/widget.h" /* message() */
+
+#include "lib/vfs/vfs.h"
+#include "lib/vfs/utilvfs.h"
+#include "lib/vfs/gc.h" /* vfs_rmstamp */
+
+#include "tar-internal.h"
+#include "tar.h"
+
+/*** global variables ****************************************************************************/
+
+/* Size of each record, once in blocks, once in bytes. Those two variables are always related,
+ the second being BLOCKSIZE times the first. */
+const int blocking_factor = DEFAULT_BLOCKING;
+const size_t record_size = DEFAULT_BLOCKING * BLOCKSIZE;
+
+/* As we open one archive at a time, it is safe to have these static */
+union block *record_end; /* last+1 block of archive record */
+union block *current_block; /* current block of archive */
+off_t record_start_block; /* block ordinal at record_start */
+
+union block *current_header;
+
+/* Have we hit EOF yet? */
+gboolean hit_eof;
+
+struct tar_stat_info current_stat_info;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define TAR_SUPER(super) ((tar_super_t *) (super))
+
+/* tar Header Block, from POSIX 1003.1-1990. */
+
+/* The magic field is filled with this if uname and gname are valid. */
+#define TMAGIC "ustar" /* ustar and a null */
+
+#define XHDTYPE 'x' /* Extended header referring to the next file in the archive */
+#define XGLTYPE 'g' /* Global extended header */
+
+/* Values used in typeflag field. */
+#define LNKTYPE '1' /* link */
+#define SYMTYPE '2' /* symbolic link */
+#define CHRTYPE '3' /* character special */
+#define BLKTYPE '4' /* block special */
+#define DIRTYPE '5' /* directory */
+#define FIFOTYPE '6' /* FIFO special */
+
+
+/* OLDGNU_MAGIC uses both magic and version fields, which are contiguous.
+ Found in an archive, it indicates an old GNU header format, which will be
+ hopefully become obsolescent. With OLDGNU_MAGIC, uname and gname are
+ valid, though the header is not truly POSIX conforming. */
+#define OLDGNU_MAGIC "ustar " /* 7 chars and a null */
+
+
+/* Bits used in the mode field, values in octal. */
+#define TSUID 04000 /* set UID on execution */
+#define TSGID 02000 /* set GID on execution */
+#define TSVTX 01000 /* reserved */
+ /* file permissions */
+#define TUREAD 00400 /* read by owner */
+#define TUWRITE 00200 /* write by owner */
+#define TUEXEC 00100 /* execute/search by owner */
+#define TGREAD 00040 /* read by group */
+#define TGWRITE 00020 /* write by group */
+#define TGEXEC 00010 /* execute/search by group */
+#define TOREAD 00004 /* read by other */
+#define TOWRITE 00002 /* write by other */
+#define TOEXEC 00001 /* execute/search by other */
+
+#define GID_FROM_HEADER(where) gid_from_header (where, sizeof (where))
+#define MAJOR_FROM_HEADER(where) major_from_header (where, sizeof (where))
+#define MINOR_FROM_HEADER(where) minor_from_header (where, sizeof (where))
+#define MODE_FROM_HEADER(where,hbits) mode_from_header (where, sizeof (where), hbits)
+#define TIME_FROM_HEADER(where) time_from_header (where, sizeof (where))
+#define UID_FROM_HEADER(where) uid_from_header (where, sizeof (where))
+#define UINTMAX_FROM_HEADER(where) uintmax_from_header (where, sizeof (where))
+
+/*** file scope type declarations ****************************************************************/
+
+typedef enum
+{
+ HEADER_STILL_UNREAD, /* for when read_header has not been called */
+ HEADER_SUCCESS, /* header successfully read and checksummed */
+ HEADER_ZERO_BLOCK, /* zero block where header expected */
+ HEADER_END_OF_FILE, /* true end of file while header expected */
+ HEADER_FAILURE /* ill-formed header, or bad checksum */
+} read_header;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static struct vfs_s_subclass tarfs_subclass;
+static struct vfs_class *vfs_tarfs_ops = VFS_CLASS (&tarfs_subclass);
+
+static struct timespec start_time;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tar_stat_destroy (struct tar_stat_info *st)
+{
+ g_free (st->orig_file_name);
+ g_free (st->file_name);
+ g_free (st->link_name);
+#if 0
+ g_free (st->uname);
+ g_free (st->gname);
+#endif
+ if (st->sparse_map != NULL)
+ {
+ g_array_free (st->sparse_map, TRUE);
+ st->sparse_map = NULL;
+ }
+ tar_xheader_destroy (&st->xhdr);
+ memset (st, 0, sizeof (*st));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline gid_t
+gid_from_header (const char *p, size_t s)
+{
+ return tar_from_header (p, s, "gid_t", TYPE_MINIMUM (gid_t), TYPE_MAXIMUM (gid_t), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline major_t
+major_from_header (const char *p, size_t s)
+{
+ return tar_from_header (p, s, "major_t", TYPE_MINIMUM (major_t), TYPE_MAXIMUM (major_t), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline minor_t
+minor_from_header (const char *p, size_t s)
+{
+ return tar_from_header (p, s, "minor_t", TYPE_MINIMUM (minor_t), TYPE_MAXIMUM (minor_t), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Convert @p to the file mode, as understood by tar.
+ * Store unrecognized mode bits (from 10th up) in @hbits.
+ * Set *hbits if there are any unrecognized bits.
+ * */
+static inline mode_t
+mode_from_header (const char *p, size_t s, gboolean * hbits)
+{
+ unsigned int u;
+ mode_t mode;
+
+ /* Do not complain about unrecognized mode bits. */
+ u = tar_from_header (p, s, "mode_t", INTMAX_MIN, UINTMAX_MAX, FALSE);
+
+ /* *INDENT-OFF* */
+ mode = ((u & TSUID ? S_ISUID : 0)
+ | (u & TSGID ? S_ISGID : 0)
+ | (u & TSVTX ? S_ISVTX : 0)
+ | (u & TUREAD ? S_IRUSR : 0)
+ | (u & TUWRITE ? S_IWUSR : 0)
+ | (u & TUEXEC ? S_IXUSR : 0)
+ | (u & TGREAD ? S_IRGRP : 0)
+ | (u & TGWRITE ? S_IWGRP : 0)
+ | (u & TGEXEC ? S_IXGRP : 0)
+ | (u & TOREAD ? S_IROTH : 0)
+ | (u & TOWRITE ? S_IWOTH : 0)
+ | (u & TOEXEC ? S_IXOTH : 0));
+ /* *INDENT-ON* */
+
+ if (hbits != NULL)
+ *hbits = (u & ~07777) != 0;
+
+ return mode;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline time_t
+time_from_header (const char *p, size_t s)
+{
+ return tar_from_header (p, s, "time_t", TYPE_MINIMUM (time_t), TYPE_MAXIMUM (time_t), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline uid_t
+uid_from_header (const char *p, size_t s)
+{
+ return tar_from_header (p, s, "uid_t", TYPE_MINIMUM (uid_t), TYPE_MAXIMUM (uid_t), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline uintmax_t
+uintmax_from_header (const char *p, size_t s)
+{
+ return tar_from_header (p, s, "uintmax_t", 0, UINTMAX_MAX, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tar_calc_sparse_offsets (struct vfs_s_inode *inode)
+{
+ off_t begin = inode->data_offset;
+ GArray *sm = (GArray *) inode->user_data;
+ size_t i;
+
+ for (i = 0; i < sm->len; i++)
+ {
+ struct sp_array *sp;
+
+ sp = &g_array_index (sm, struct sp_array, i);
+ sp->arch_offset = begin;
+ begin += BLOCKSIZE * (sp->numbytes / BLOCKSIZE + sp->numbytes % BLOCKSIZE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+tar_skip_member (tar_super_t * archive, struct vfs_s_inode *inode)
+{
+ char save_typeflag;
+
+ if (current_stat_info.skipped)
+ return TRUE;
+
+ save_typeflag = current_header->header.typeflag;
+
+ tar_set_next_block_after (current_header);
+
+ if (current_stat_info.is_sparse)
+ {
+ if (inode != NULL)
+ inode->data_offset = BLOCKSIZE * tar_current_block_ordinal (archive);
+
+ (void) tar_sparse_skip_file (archive, &current_stat_info);
+
+ if (inode != NULL)
+ {
+ /* use vfs_s_inode::user_data to keep the sparse map */
+ inode->user_data = current_stat_info.sparse_map;
+ current_stat_info.sparse_map = NULL;
+
+ tar_calc_sparse_offsets (inode);
+ }
+ }
+ else if (save_typeflag != DIRTYPE)
+ {
+ if (inode != NULL)
+ inode->data_offset = BLOCKSIZE * tar_current_block_ordinal (archive);
+
+ return tar_skip_file (archive, current_stat_info.stat.st_size);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Return the number of bytes comprising the space between @pointer through the end
+ * of the current buffer of blocks. This space is available for filling with data,
+ * or taking data from. @pointer is usually (but not always) the result previous
+ * tar_find_next_block() call.
+ */
+static inline size_t
+tar_available_space_after (const union block *pointer)
+{
+ return record_end->buffer - pointer->buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Check header checksum.
+ */
+static read_header
+tar_checksum (const union block *header)
+{
+ size_t i;
+ int unsigned_sum = 0; /* the POSIX one :-) */
+ int signed_sum = 0; /* the Sun one :-( */
+ int recorded_sum;
+ int parsed_sum;
+ const char *p = header->buffer;
+
+ for (i = sizeof (*header); i-- != 0;)
+ {
+ unsigned_sum += (unsigned char) *p;
+ signed_sum += (signed char) (*p++);
+ }
+
+ if (unsigned_sum == 0)
+ return HEADER_ZERO_BLOCK;
+
+ /* Adjust checksum to count the "chksum" field as blanks. */
+ for (i = sizeof (header->header.chksum); i-- != 0;)
+ {
+ unsigned_sum -= (unsigned char) header->header.chksum[i];
+ signed_sum -= (signed char) (header->header.chksum[i]);
+ }
+
+ unsigned_sum += ' ' * sizeof (header->header.chksum);
+ signed_sum += ' ' * sizeof (header->header.chksum);
+
+ parsed_sum =
+ tar_from_header (header->header.chksum, sizeof (header->header.chksum), NULL, 0,
+ INT_MAX, TRUE);
+ if (parsed_sum < 0)
+ return HEADER_FAILURE;
+
+ recorded_sum = parsed_sum;
+
+ if (unsigned_sum != recorded_sum && signed_sum != recorded_sum)
+ return HEADER_FAILURE;
+
+ return HEADER_SUCCESS;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tar_decode_header (union block *header, tar_super_t * arch)
+{
+ gboolean hbits = FALSE;
+
+ current_stat_info.stat.st_mode = MODE_FROM_HEADER (header->header.mode, &hbits);
+
+ /*
+ * Try to determine the archive format.
+ */
+ if (arch->type == TAR_UNKNOWN)
+ {
+ if (strcmp (header->header.magic, TMAGIC) == 0)
+ {
+ if (header->star_header.prefix[130] == 0 && isodigit (header->star_header.atime[0])
+ && header->star_header.atime[11] == ' ' && isodigit (header->star_header.ctime[0])
+ && header->star_header.ctime[11] == ' ')
+ arch->type = TAR_STAR;
+ else if (current_stat_info.xhdr.buffer != NULL)
+ arch->type = TAR_POSIX;
+ else
+ arch->type = TAR_USTAR;
+ }
+ else if (strcmp (header->buffer + offsetof (struct posix_header, magic), OLDGNU_MAGIC) == 0)
+ arch->type = hbits ? TAR_OLDGNU : TAR_GNU;
+ else
+ arch->type = TAR_V7;
+ }
+
+ /*
+ * typeflag on BSDI tar (pax) always '\000'
+ */
+ if (header->header.typeflag == '\000')
+ {
+ size_t len;
+
+ if (header->header.name[sizeof (header->header.name) - 1] != '\0')
+ len = sizeof (header->header.name);
+ else
+ len = strlen (header->header.name);
+
+ if (len != 0 && IS_PATH_SEP (header->header.name[len - 1]))
+ header->header.typeflag = DIRTYPE;
+ }
+
+ if (header->header.typeflag == GNUTYPE_DUMPDIR)
+ if (arch->type == TAR_UNKNOWN)
+ arch->type = TAR_GNU;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tar_fill_stat (struct vfs_s_super *archive, union block *header)
+{
+ tar_super_t *arch = TAR_SUPER (archive);
+
+ /* Adjust current_stat_info.stat.st_mode because there are tar-files with
+ * typeflag==SYMTYPE and S_ISLNK(mod)==0. I don't
+ * know about the other modes but I think I cause no new
+ * problem when I adjust them, too. -- Norbert.
+ */
+ if (header->header.typeflag == DIRTYPE || header->header.typeflag == GNUTYPE_DUMPDIR)
+ current_stat_info.stat.st_mode |= S_IFDIR;
+ else if (header->header.typeflag == SYMTYPE)
+ current_stat_info.stat.st_mode |= S_IFLNK;
+ else if (header->header.typeflag == CHRTYPE)
+ current_stat_info.stat.st_mode |= S_IFCHR;
+ else if (header->header.typeflag == BLKTYPE)
+ current_stat_info.stat.st_mode |= S_IFBLK;
+ else if (header->header.typeflag == FIFOTYPE)
+ current_stat_info.stat.st_mode |= S_IFIFO;
+ else
+ current_stat_info.stat.st_mode |= S_IFREG;
+
+ current_stat_info.stat.st_dev = 0;
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ current_stat_info.stat.st_rdev = 0;
+#endif
+
+ switch (arch->type)
+ {
+ case TAR_USTAR:
+ case TAR_POSIX:
+ case TAR_GNU:
+ case TAR_OLDGNU:
+ /* *INDENT-OFF* */
+ current_stat_info.stat.st_uid = *header->header.uname != '\0'
+ ? (uid_t) vfs_finduid (header->header.uname)
+ : UID_FROM_HEADER (header->header.uid);
+ current_stat_info.stat.st_gid = *header->header.gname != '\0'
+ ? (gid_t) vfs_findgid (header->header.gname)
+ : GID_FROM_HEADER (header->header.gid);
+ /* *INDENT-ON* */
+
+ switch (header->header.typeflag)
+ {
+ case BLKTYPE:
+ case CHRTYPE:
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ current_stat_info.stat.st_rdev =
+ makedev (MAJOR_FROM_HEADER (header->header.devmajor),
+ MINOR_FROM_HEADER (header->header.devminor));
+#endif
+ break;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ current_stat_info.stat.st_uid = UID_FROM_HEADER (header->header.uid);
+ current_stat_info.stat.st_gid = GID_FROM_HEADER (header->header.gid);
+ break;
+ }
+
+ current_stat_info.atime.tv_nsec = 0;
+ current_stat_info.mtime.tv_nsec = 0;
+ current_stat_info.ctime.tv_nsec = 0;
+
+ current_stat_info.mtime.tv_sec = TIME_FROM_HEADER (header->header.mtime);
+ if (arch->type == TAR_GNU || arch->type == TAR_OLDGNU)
+ {
+ current_stat_info.atime.tv_sec = TIME_FROM_HEADER (header->oldgnu_header.atime);
+ current_stat_info.ctime.tv_sec = TIME_FROM_HEADER (header->oldgnu_header.ctime);
+ }
+ else if (arch->type == TAR_STAR)
+ {
+ current_stat_info.atime.tv_sec = TIME_FROM_HEADER (header->star_header.atime);
+ current_stat_info.ctime.tv_sec = TIME_FROM_HEADER (header->star_header.ctime);
+ }
+ else
+ current_stat_info.atime = current_stat_info.ctime = start_time;
+
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ current_stat_info.stat.st_blksize = 8 * 1024; /* FIXME */
+#endif
+ vfs_adjust_stat (&current_stat_info.stat);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tar_free_inode (struct vfs_class *me, struct vfs_s_inode *ino)
+{
+ (void) me;
+
+ /* free sparse_map */
+ if (ino->user_data != NULL)
+ g_array_free ((GArray *) ino->user_data, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static read_header
+tar_insert_entry (struct vfs_class *me, struct vfs_s_super *archive, union block *header,
+ struct vfs_s_inode **inode)
+{
+ char *p, *q;
+ char *file_name = current_stat_info.file_name;
+ char *link_name = current_stat_info.link_name;
+ size_t len;
+ struct vfs_s_inode *parent;
+ struct vfs_s_entry *entry;
+
+ p = strrchr (file_name, PATH_SEP);
+ if (p == NULL)
+ {
+ len = strlen (file_name);
+ p = file_name;
+ q = file_name + len; /* "" */
+ }
+ else
+ {
+ *(p++) = '\0';
+ q = file_name;
+ }
+
+ parent = vfs_s_find_inode (me, archive, q, LINK_NO_FOLLOW, FL_MKDIR);
+ if (parent == NULL)
+ return HEADER_FAILURE;
+
+ *inode = NULL;
+
+ if (header->header.typeflag == LNKTYPE)
+ {
+ if (*link_name != '\0')
+ {
+ len = strlen (link_name);
+ if (IS_PATH_SEP (link_name[len - 1]))
+ link_name[len - 1] = '\0';
+
+ *inode = vfs_s_find_inode (me, archive, link_name, LINK_NO_FOLLOW, FL_NONE);
+ }
+
+ if (*inode == NULL)
+ return HEADER_FAILURE;
+ }
+ else
+ {
+ if (S_ISDIR (current_stat_info.stat.st_mode))
+ {
+ entry = VFS_SUBCLASS (me)->find_entry (me, parent, p, LINK_NO_FOLLOW, FL_NONE);
+ if (entry != NULL)
+ return HEADER_SUCCESS;
+ }
+
+ *inode = vfs_s_new_inode (me, archive, &current_stat_info.stat);
+ /* assgin timestamps after decoding of extended headers */
+ (*inode)->st.st_mtime = current_stat_info.mtime.tv_sec;
+ (*inode)->st.st_atime = current_stat_info.atime.tv_sec;
+ (*inode)->st.st_ctime = current_stat_info.ctime.tv_sec;
+ (*inode)->data_offset = BLOCKSIZE * tar_current_block_ordinal (TAR_SUPER (archive));
+
+ if (link_name != NULL && *link_name != '\0')
+ (*inode)->linkname = g_strdup (link_name);
+ }
+
+ entry = vfs_s_new_entry (me, p, *inode);
+ vfs_s_insert_entry (me, parent, entry);
+
+ return HEADER_SUCCESS;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static read_header
+tar_read_header (struct vfs_class *me, struct vfs_s_super *archive)
+{
+ tar_super_t *arch = TAR_SUPER (archive);
+ union block *header;
+ union block *next_long_name = NULL, *next_long_link = NULL;
+ read_header status = HEADER_SUCCESS;
+
+ while (TRUE)
+ {
+ header = tar_find_next_block (arch);
+ current_header = header;
+ if (header == NULL)
+ {
+ status = HEADER_END_OF_FILE;
+ goto ret;
+ }
+
+ status = tar_checksum (header);
+ if (status != HEADER_SUCCESS)
+ goto ret;
+
+ if (header->header.typeflag == LNKTYPE || header->header.typeflag == DIRTYPE)
+ current_stat_info.stat.st_size = 0; /* Links 0 size on tape */
+ else
+ {
+ current_stat_info.stat.st_size = OFF_FROM_HEADER (header->header.size);
+ if (current_stat_info.stat.st_size < 0)
+ {
+ status = HEADER_FAILURE;
+ goto ret;
+ }
+ }
+
+ tar_decode_header (header, arch);
+ tar_fill_stat (archive, header);
+
+ if (header->header.typeflag == GNUTYPE_LONGNAME
+ || header->header.typeflag == GNUTYPE_LONGLINK)
+ {
+ size_t name_size = current_stat_info.stat.st_size;
+ size_t n;
+ off_t size;
+ union block *header_copy;
+ char *bp;
+ size_t written;
+
+ if (arch->type == TAR_UNKNOWN)
+ arch->type = TAR_GNU;
+
+ n = name_size % BLOCKSIZE;
+ size = name_size + BLOCKSIZE;
+ if (n != 0)
+ size += BLOCKSIZE - n;
+ if ((off_t) name_size != current_stat_info.stat.st_size || size < (off_t) name_size)
+ {
+ message (D_ERROR, MSG_ERROR, _("Inconsistent tar archive"));
+ status = HEADER_FAILURE;
+ goto ret;
+ }
+
+ header_copy = g_malloc (size + 1);
+
+ if (header->header.typeflag == GNUTYPE_LONGNAME)
+ {
+ g_free (next_long_name);
+ next_long_name = header_copy;
+ }
+ else
+ {
+ g_free (next_long_link);
+ next_long_link = header_copy;
+ }
+
+ tar_set_next_block_after (header);
+ *header_copy = *header;
+ bp = header_copy->buffer + BLOCKSIZE;
+
+ for (size -= BLOCKSIZE; size > 0; size -= written)
+ {
+ union block *data_block;
+
+ data_block = tar_find_next_block (arch);
+ if (data_block == NULL)
+ {
+ g_free (header_copy);
+ message (D_ERROR, MSG_ERROR, _("Unexpected EOF on archive file"));
+ status = HEADER_FAILURE;
+ goto ret;
+ }
+
+ written = tar_available_space_after (data_block);
+ if ((off_t) written > size)
+ written = (size_t) size;
+
+ memcpy (bp, data_block->buffer, written);
+ bp += written;
+ tar_set_next_block_after ((union block *) (data_block->buffer + written - 1));
+ }
+
+ *bp = '\0';
+ }
+ else if (header->header.typeflag == XHDTYPE || header->header.typeflag == SOLARIS_XHDTYPE)
+ {
+ if (arch->type == TAR_UNKNOWN)
+ arch->type = TAR_POSIX;
+ if (!tar_xheader_read
+ (arch, &current_stat_info.xhdr, header, OFF_FROM_HEADER (header->header.size)))
+ {
+ message (D_ERROR, MSG_ERROR, _("Unexpected EOF on archive file"));
+ status = HEADER_FAILURE;
+ goto ret;
+ }
+ }
+ else if (header->header.typeflag == XGLTYPE)
+ {
+ struct xheader xhdr;
+ gboolean ok;
+
+ if (arch->type == TAR_UNKNOWN)
+ arch->type = TAR_POSIX;
+
+ memset (&xhdr, 0, sizeof (xhdr));
+ tar_xheader_read (arch, &xhdr, header, OFF_FROM_HEADER (header->header.size));
+ ok = tar_xheader_decode_global (&xhdr);
+ tar_xheader_destroy (&xhdr);
+
+ if (!ok)
+ {
+ message (D_ERROR, MSG_ERROR, _("Inconsistent tar archive"));
+ status = HEADER_FAILURE;
+ goto ret;
+ }
+ }
+ else
+ break;
+ }
+
+ {
+ static union block *recent_long_name = NULL, *recent_long_link = NULL;
+ struct posix_header const *h = &header->header;
+ char *file_name = NULL;
+ char *link_name;
+ struct vfs_s_inode *inode = NULL;
+
+ g_free (recent_long_name);
+
+ if (next_long_name != NULL)
+ {
+ file_name = g_strdup (next_long_name->buffer + BLOCKSIZE);
+ recent_long_name = next_long_name;
+ }
+ else
+ {
+ /* Accept file names as specified by POSIX.1-1996 section 10.1.1. */
+ char *s1 = NULL;
+ char *s2;
+
+ /* Don't parse TAR_OLDGNU incremental headers as POSIX prefixes. */
+ if (h->prefix[0] != '\0' && strcmp (h->magic, TMAGIC) == 0)
+ s1 = g_strndup (h->prefix, sizeof (h->prefix));
+
+ s2 = g_strndup (h->name, sizeof (h->name));
+
+ if (s1 == NULL)
+ file_name = s2;
+ else
+ {
+ file_name = g_strconcat (s1, PATH_SEP_STR, s2, (char *) NULL);
+ g_free (s1);
+ g_free (s2);
+ }
+
+ recent_long_name = NULL;
+ }
+
+ tar_assign_string_dup (&current_stat_info.orig_file_name, file_name);
+ canonicalize_pathname (file_name);
+ tar_assign_string (&current_stat_info.file_name, file_name);
+
+ g_free (recent_long_link);
+
+ if (next_long_link != NULL)
+ {
+ link_name = g_strdup (next_long_link->buffer + BLOCKSIZE);
+ recent_long_link = next_long_link;
+ }
+ else
+ {
+ link_name = g_strndup (h->linkname, sizeof (h->linkname));
+ recent_long_link = NULL;
+ }
+
+ tar_assign_string (&current_stat_info.link_name, link_name);
+
+ if (current_stat_info.xhdr.buffer != NULL && !tar_xheader_decode (&current_stat_info))
+ {
+ status = HEADER_FAILURE;
+ goto ret;
+ }
+
+ if (tar_sparse_member_p (arch, &current_stat_info))
+ {
+ if (!tar_sparse_fixup_header (arch, &current_stat_info))
+ {
+ status = HEADER_FAILURE;
+ goto ret;
+ }
+
+ current_stat_info.is_sparse = TRUE;
+ }
+ else
+ {
+ current_stat_info.is_sparse = FALSE;
+
+ if (((arch->type == TAR_GNU || arch->type == TAR_OLDGNU)
+ && current_header->header.typeflag == GNUTYPE_DUMPDIR)
+ || current_stat_info.dumpdir != NULL)
+ current_stat_info.is_dumpdir = TRUE;
+ }
+
+ status = tar_insert_entry (me, archive, header, &inode);
+ if (status != HEADER_SUCCESS)
+ {
+ message (D_ERROR, MSG_ERROR, _("Inconsistent tar archive"));
+ goto ret;
+ }
+
+ if (recent_long_name == next_long_name)
+ recent_long_name = NULL;
+
+ if (recent_long_link == next_long_link)
+ recent_long_link = NULL;
+
+ if (tar_skip_member (arch, inode))
+ status = HEADER_SUCCESS;
+ else if (hit_eof)
+ status = HEADER_END_OF_FILE;
+ else
+ status = HEADER_FAILURE;
+ }
+
+ ret:
+ g_free (next_long_name);
+ g_free (next_long_link);
+
+ return status;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_super *
+tar_new_archive (struct vfs_class *me)
+{
+ tar_super_t *arch;
+ gint64 usec;
+
+ arch = g_new0 (tar_super_t, 1);
+ arch->base.me = me;
+ arch->fd = -1;
+ arch->type = TAR_UNKNOWN;
+
+ /* Prepare global data needed for tar_find_next_block: */
+ record_start_block = 0;
+ arch->record_start = g_malloc (record_size);
+ record_end = arch->record_start; /* set up for 1st record = # 0 */
+ current_block = arch->record_start;
+ hit_eof = FALSE;
+
+ /* time in microseconds */
+ usec = g_get_real_time ();
+ /* time in seconds and nanoseconds */
+ start_time.tv_sec = usec / G_USEC_PER_SEC;
+ start_time.tv_nsec = (usec % G_USEC_PER_SEC) * 1000;
+
+ return VFS_SUPER (arch);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tar_free_archive (struct vfs_class *me, struct vfs_s_super *archive)
+{
+ tar_super_t *arch = TAR_SUPER (archive);
+
+ (void) me;
+
+ if (arch->fd != -1)
+ {
+ mc_close (arch->fd);
+ arch->fd = -1;
+ }
+
+ g_free (arch->record_start);
+ tar_stat_destroy (&current_stat_info);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Returns status of the tar archive open */
+static gboolean
+tar_open_archive_int (struct vfs_class *me, const vfs_path_t * vpath, struct vfs_s_super *archive)
+{
+ tar_super_t *arch = TAR_SUPER (archive);
+ int result, type;
+ mode_t mode;
+ struct vfs_s_inode *root;
+
+ result = mc_open (vpath, O_RDONLY);
+ if (result == -1)
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot open tar archive\n%s"), vfs_path_as_str (vpath));
+ ERRNOR (ENOENT, FALSE);
+ }
+
+ archive->name = g_strdup (vfs_path_as_str (vpath));
+ mc_stat (vpath, &arch->st);
+
+ /* Find out the method to handle this tar file */
+ type = get_compression_type (result, archive->name);
+ if (type == COMPRESSION_NONE)
+ mc_lseek (result, 0, SEEK_SET);
+ else
+ {
+ char *s;
+ vfs_path_t *tmp_vpath;
+
+ mc_close (result);
+ s = g_strconcat (archive->name, decompress_extension (type), (char *) NULL);
+ tmp_vpath = vfs_path_from_str_flags (s, VPF_NO_CANON);
+ result = mc_open (tmp_vpath, O_RDONLY);
+ vfs_path_free (tmp_vpath, TRUE);
+ if (result == -1)
+ message (D_ERROR, MSG_ERROR, _("Cannot open tar archive\n%s"), s);
+ g_free (s);
+ if (result == -1)
+ {
+ MC_PTR_FREE (archive->name);
+ ERRNOR (ENOENT, FALSE);
+ }
+ }
+
+ arch->fd = result;
+ mode = arch->st.st_mode & 07777;
+ if (mode & 0400)
+ mode |= 0100;
+ if (mode & 0040)
+ mode |= 0010;
+ if (mode & 0004)
+ mode |= 0001;
+ mode |= S_IFDIR;
+
+ root = vfs_s_new_inode (me, archive, &arch->st);
+ root->st.st_mode = mode;
+ root->data_offset = -1;
+ root->st.st_nlink++;
+ root->st.st_dev = VFS_SUBCLASS (me)->rdev++;
+
+ archive->root = root;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Main loop for reading an archive.
+ * Returns 0 on success, -1 on error.
+ */
+static int
+tar_open_archive (struct vfs_s_super *archive, const vfs_path_t * vpath,
+ const vfs_path_element_t * vpath_element)
+{
+ tar_super_t *arch = TAR_SUPER (archive);
+ /* Initial status at start of archive */
+ read_header status = HEADER_STILL_UNREAD;
+
+ /* Open for reading */
+ if (!tar_open_archive_int (vpath_element->class, vpath, archive))
+ return -1;
+
+ tar_find_next_block (arch);
+
+ while (TRUE)
+ {
+ read_header prev_status;
+
+ prev_status = status;
+ tar_stat_destroy (&current_stat_info);
+ status = tar_read_header (vpath_element->class, archive);
+
+ switch (status)
+ {
+ case HEADER_STILL_UNREAD:
+ message (D_ERROR, MSG_ERROR, _("%s\ndoesn't look like a tar archive"),
+ vfs_path_as_str (vpath));
+ return -1;
+
+ case HEADER_SUCCESS:
+ continue;
+
+ /* Record of zeroes */
+ case HEADER_ZERO_BLOCK:
+ tar_set_next_block_after (current_header);
+ (void) tar_read_header (vpath_element->class, archive);
+ status = prev_status;
+ continue;
+
+ case HEADER_END_OF_FILE:
+ break;
+
+ /* Invalid header:
+ * If the previous header was good, tell them that we are skipping bad ones. */
+ case HEADER_FAILURE:
+ tar_set_next_block_after (current_header);
+
+ switch (prev_status)
+ {
+ case HEADER_STILL_UNREAD:
+ message (D_ERROR, MSG_ERROR, _("%s\ndoesn't look like a tar archive"),
+ vfs_path_as_str (vpath));
+ return -1;
+
+ case HEADER_ZERO_BLOCK:
+ case HEADER_SUCCESS:
+ /* Skipping to next header. */
+ break; /* AB: FIXME */
+
+ case HEADER_END_OF_FILE:
+ case HEADER_FAILURE:
+ /* We are in the middle of a cascade of errors. */
+ /* AB: FIXME: TODO: show an error message here */
+ return -1;
+
+ default:
+ break;
+ }
+ continue;
+
+ default:
+ break;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+tar_super_check (const vfs_path_t * vpath)
+{
+ static struct stat stat_buf;
+ int stat_result;
+
+ stat_result = mc_stat (vpath, &stat_buf);
+
+ return (stat_result != 0) ? NULL : &stat_buf;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+tar_super_same (const vfs_path_element_t * vpath_element, struct vfs_s_super *parc,
+ const vfs_path_t * vpath, void *cookie)
+{
+ struct stat *archive_stat = cookie; /* stat of main archive */
+
+ (void) vpath_element;
+
+ if (strcmp (parc->name, vfs_path_as_str (vpath)) != 0)
+ return 0;
+
+ /* Has the cached archive been changed on the disk? */
+ if (parc != NULL && TAR_SUPER (parc)->st.st_mtime < archive_stat->st_mtime)
+ {
+ /* Yes, reload! */
+ vfs_tarfs_ops->free ((vfsid) parc);
+ vfs_rmstamp (vfs_tarfs_ops, (vfsid) parc);
+ return 2;
+ }
+ /* Hasn't been modified, give it a new timeout */
+ vfs_stamp (vfs_tarfs_ops, (vfsid) parc);
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Get indes of current data chunk in a sparse file.
+ *
+ * @param sparse_map map of the sparse file
+ * @param offset offset in the sparse file
+ *
+ * @return an index of ahole or a data chunk
+ * positive: pointer to the data chunk;
+ * negative: pointer to the hole before data chunk;
+ * zero: pointer to the hole after last data chunk
+ *
+ * +--------+--------+-------+--------+-----+-------+--------+---------+
+ * | hole1 | chunk1 | hole2 | chunk2 | ... | holeN | chunkN | holeN+1 |
+ * +--------+--------+-------+--------+-----+-------+--------+---------+
+ * -1 1 -2 2 -N N 0
+ */
+
+static ssize_t
+tar_get_sparse_chunk_idx (const GArray * sparse_map, off_t offset)
+{
+ size_t k;
+
+ for (k = 1; k <= sparse_map->len; k++)
+ {
+ const struct sp_array *chunk;
+
+ chunk = &g_array_index (sparse_map, struct sp_array, k - 1);
+
+ /* are we in the current chunk? */
+ if (offset >= chunk->offset && offset < chunk->offset + chunk->numbytes)
+ return k;
+
+ /* are we before the current chunk? */
+ if (offset < chunk->offset)
+ return -k;
+ }
+
+ /* after the last chunk */
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+tar_read_sparse (vfs_file_handler_t * fh, char *buffer, size_t count)
+{
+ int fd = TAR_SUPER (fh->ino->super)->fd;
+ const GArray *sm = (const GArray *) fh->ino->user_data;
+ ssize_t chunk_idx;
+ const struct sp_array *chunk;
+ off_t remain;
+ ssize_t res;
+
+ chunk_idx = tar_get_sparse_chunk_idx (sm, fh->pos);
+ if (chunk_idx > 0)
+ {
+ /* we are in the chunk -- read data until chunk end */
+ chunk = &g_array_index (sm, struct sp_array, chunk_idx - 1);
+ remain = MIN ((off_t) count, chunk->offset + chunk->numbytes - fh->pos);
+ res = mc_read (fd, buffer, (size_t) remain);
+ }
+ else
+ {
+ if (chunk_idx == 0)
+ {
+ /* we are in the hole after last chunk -- return zeros until file end */
+ remain = MIN ((off_t) count, fh->ino->st.st_size - fh->pos);
+ /* FIXME: can remain be negative? */
+ remain = MAX (remain, 0);
+ }
+ else /* chunk_idx < 0 */
+ {
+ /* we are in the hole -- return zeros until next chunk start */
+ chunk = &g_array_index (sm, struct sp_array, -chunk_idx - 1);
+ remain = MIN ((off_t) count, chunk->offset - fh->pos);
+ }
+
+ memset (buffer, 0, (size_t) remain);
+ res = (ssize_t) remain;
+ }
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+tar_lseek_sparse (vfs_file_handler_t * fh, off_t offset)
+{
+ off_t saved_offset = offset;
+ int fd = TAR_SUPER (fh->ino->super)->fd;
+ const GArray *sm = (const GArray *) fh->ino->user_data;
+ ssize_t chunk_idx;
+ const struct sp_array *chunk;
+ off_t res;
+
+ chunk_idx = tar_get_sparse_chunk_idx (sm, offset);
+ if (chunk_idx > 0)
+ {
+ /* we are in the chunk */
+
+ chunk = &g_array_index (sm, struct sp_array, chunk_idx - 1);
+ /* offset in the chunk */
+ offset -= chunk->offset;
+ /* offset in the archive */
+ offset += chunk->arch_offset;
+ }
+ else
+ {
+ /* we are in the hole */
+
+ /* we cannot lseek in hole so seek to the hole begin or end */
+ switch (chunk_idx)
+ {
+ case -1:
+ offset = fh->ino->data_offset;
+ break;
+
+ case 0:
+ chunk = &g_array_index (sm, struct sp_array, sm->len - 1);
+ /* FIXME: can we seek beyond tar archive EOF here? */
+ offset = chunk->arch_offset + chunk->numbytes;
+ break;
+
+ default:
+ chunk = &g_array_index (sm, struct sp_array, -chunk_idx - 1);
+ offset = chunk->arch_offset + chunk->numbytes;
+ break;
+ }
+ }
+
+ res = mc_lseek (fd, offset, SEEK_SET);
+ /* return requested offset in success */
+ if (res == offset)
+ res = saved_offset;
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+tar_read (void *fh, char *buffer, size_t count)
+{
+ struct vfs_class *me = VFS_FILE_HANDLER_SUPER (fh)->me;
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ int fd = TAR_SUPER (VFS_FILE_HANDLER_SUPER (fh))->fd;
+ off_t begin = file->pos;
+ ssize_t res;
+
+ if (file->ino->user_data != NULL)
+ {
+ if (tar_lseek_sparse (file, begin) != begin)
+ ERRNOR (EIO, -1);
+
+ res = tar_read_sparse (file, buffer, count);
+ }
+ else
+ {
+ begin += file->ino->data_offset;
+
+ if (mc_lseek (fd, begin, SEEK_SET) != begin)
+ ERRNOR (EIO, -1);
+
+ count = (size_t) MIN ((off_t) count, file->ino->st.st_size - file->pos);
+ res = mc_read (fd, buffer, count);
+ }
+
+ if (res == -1)
+ ERRNOR (errno, -1);
+
+ file->pos += res;
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+tar_fh_open (struct vfs_class *me, vfs_file_handler_t * fh, int flags, mode_t mode)
+{
+ (void) fh;
+ (void) mode;
+
+ if ((flags & O_ACCMODE) != O_RDONLY)
+ ERRNOR (EROFS, -1);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init_tarfs (void)
+{
+ /* FIXME: tarfs used own temp files */
+ vfs_init_subclass (&tarfs_subclass, "tarfs", VFSF_READONLY, "utar");
+ vfs_tarfs_ops->read = tar_read;
+ vfs_tarfs_ops->setctl = NULL;
+ tarfs_subclass.archive_check = tar_super_check;
+ tarfs_subclass.archive_same = tar_super_same;
+ tarfs_subclass.new_archive = tar_new_archive;
+ tarfs_subclass.open_archive = tar_open_archive;
+ tarfs_subclass.free_archive = tar_free_archive;
+ tarfs_subclass.free_inode = tar_free_inode;
+ tarfs_subclass.fh_open = tar_fh_open;
+ vfs_register_class (vfs_tarfs_ops);
+
+ tar_base64_init ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/tar/tar.h b/src/vfs/tar/tar.h
new file mode 100644
index 0000000..5ad11b5
--- /dev/null
+++ b/src/vfs/tar/tar.h
@@ -0,0 +1,18 @@
+#ifndef MC__VFS_TAR_H
+#define MC__VFS_TAR_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void vfs_init_tarfs (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__VFS_TAR_H */
diff --git a/src/vfs/undelfs/Makefile.am b/src/vfs/undelfs/Makefile.am
new file mode 100644
index 0000000..4e7a77d
--- /dev/null
+++ b/src/vfs/undelfs/Makefile.am
@@ -0,0 +1,7 @@
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+
+noinst_LTLIBRARIES = libvfs-undelfs.la
+
+libvfs_undelfs_la_SOURCES = \
+ undelfs.c undelfs.h
diff --git a/src/vfs/undelfs/Makefile.in b/src/vfs/undelfs/Makefile.in
new file mode 100644
index 0000000..4f258d7
--- /dev/null
+++ b/src/vfs/undelfs/Makefile.in
@@ -0,0 +1,735 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/vfs/undelfs
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libvfs_undelfs_la_LIBADD =
+am_libvfs_undelfs_la_OBJECTS = undelfs.lo
+libvfs_undelfs_la_OBJECTS = $(am_libvfs_undelfs_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/undelfs.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libvfs_undelfs_la_SOURCES)
+DIST_SOURCES = $(libvfs_undelfs_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+noinst_LTLIBRARIES = libvfs-undelfs.la
+libvfs_undelfs_la_SOURCES = \
+ undelfs.c undelfs.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/vfs/undelfs/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/undelfs/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libvfs-undelfs.la: $(libvfs_undelfs_la_OBJECTS) $(libvfs_undelfs_la_DEPENDENCIES) $(EXTRA_libvfs_undelfs_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libvfs_undelfs_la_OBJECTS) $(libvfs_undelfs_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/undelfs.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/undelfs.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/undelfs.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/vfs/undelfs/undelfs.c b/src/vfs/undelfs/undelfs.c
new file mode 100644
index 0000000..de54440
--- /dev/null
+++ b/src/vfs/undelfs/undelfs.c
@@ -0,0 +1,844 @@
+/*
+ UnDel File System: Midnight Commander file system.
+
+ This file system is intended to be used together with the
+ ext2fs library to recover files from ext2fs file systems.
+
+ Parts of this program were taken from the lsdel.c and dump.c files
+ written by Ted Ts'o (tytso@mit.edu) for the ext2fs package.
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1995
+ Norbert Warmuth, 1997
+ Pavel Machek, 2000
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * \brief Source: UnDel File System
+ *
+ * Assumptions:
+ *
+ * 1. We don't handle directories (thus undelfs_get_path is easy to write).
+ * 2. Files are on the local file system (we do not support vfs files
+ * because we would have to provide an io_manager for the ext2fs tools,
+ * and I don't think it would be too useful to undelete files
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h> /* memset() */
+#include <ext2fs/ext2_fs.h>
+#include <ext2fs/ext2fs.h>
+#include <ctype.h>
+
+#include "lib/global.h"
+
+#include "lib/util.h"
+#include "lib/widget.h" /* message() */
+#include "lib/vfs/xdirentry.h"
+#include "lib/vfs/utilvfs.h"
+#include "lib/vfs/vfs.h"
+
+#include "undelfs.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/* To generate the . and .. entries use -2 */
+#define READDIR_PTR_INIT 0
+
+#define undelfs_stat undelfs_lstat
+
+/*** file scope type declarations ****************************************************************/
+
+struct deleted_info
+{
+ ext2_ino_t ino;
+ unsigned short mode;
+ unsigned short uid;
+ unsigned short gid;
+ unsigned long size;
+ time_t dtime;
+ int num_blocks;
+ int free_blocks;
+};
+
+struct lsdel_struct
+{
+ ext2_ino_t inode;
+ int num_blocks;
+ int free_blocks;
+ int bad_blocks;
+};
+
+typedef struct
+{
+ int f_index; /* file index into delarray */
+ char *buf;
+ int error_code; /* */
+ off_t pos; /* file position */
+ off_t current; /* used to determine current position in itereate */
+ gboolean finished;
+ ext2_ino_t inode;
+ int bytes_read;
+ off_t size;
+
+ /* Used by undelfs_read: */
+ char *dest_buffer; /* destination buffer */
+ size_t count; /* bytes to read */
+} undelfs_file;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* We only allow one opened ext2fs */
+static char *ext2_fname;
+static ext2_filsys fs = NULL;
+static struct lsdel_struct lsd;
+static struct deleted_info *delarray;
+static int num_delarray, max_delarray;
+static char *block_buf;
+static const char *undelfserr = N_("undelfs: error");
+static int readdir_ptr;
+static int undelfs_usage;
+
+static struct vfs_s_subclass undelfs_subclass;
+static struct vfs_class *vfs_undelfs_ops = VFS_CLASS (&undelfs_subclass);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+undelfs_shutdown (void)
+{
+ if (fs)
+ ext2fs_close (fs);
+ fs = NULL;
+ MC_PTR_FREE (ext2_fname);
+ MC_PTR_FREE (delarray);
+ MC_PTR_FREE (block_buf);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+undelfs_get_path (const vfs_path_t * vpath, char **fsname, char **file)
+{
+ const char *p, *dirname;
+
+ dirname = vfs_path_get_last_path_str (vpath);
+
+ /* To look like filesystem, we have virtual directories
+ undel://XXX, which have no subdirectories. XXX is replaced with
+ hda5, sdb8 etc, which is assumed to live under /dev.
+ -- pavel@ucw.cz */
+
+ *fsname = NULL;
+
+ if (strncmp (dirname, "undel://", 8) != 0)
+ return;
+
+ dirname += 8;
+
+ /* Since we don't allow subdirectories, it's easy to get a filename,
+ * just scan backwards for a slash */
+ if (*dirname == '\0')
+ return;
+
+ p = dirname + strlen (dirname);
+#if 0
+ /* Strip trailing ./
+ */
+ if (p - dirname > 2 && IS_PATH_SEP (p[-1]) && p[-2] == '.')
+ *(p = p - 2) = 0;
+#endif
+
+ while (p > dirname)
+ {
+ if (IS_PATH_SEP (*p))
+ {
+ char *tmp;
+
+ *file = g_strdup (p + 1);
+ tmp = g_strndup (dirname, p - dirname);
+ *fsname = g_strconcat ("/dev/", tmp, (char *) NULL);
+ g_free (tmp);
+ return;
+ }
+ p--;
+ }
+ *file = g_strdup ("");
+ *fsname = g_strconcat ("/dev/", dirname, (char *) NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+undelfs_lsdel_proc (ext2_filsys _fs, blk_t * block_nr, int blockcnt, void *private)
+{
+ struct lsdel_struct *_lsd = (struct lsdel_struct *) private;
+ (void) blockcnt;
+ _lsd->num_blocks++;
+
+ if (*block_nr < _fs->super->s_first_data_block || *block_nr >= _fs->super->s_blocks_count)
+ {
+ _lsd->bad_blocks++;
+ return BLOCK_ABORT;
+ }
+
+ if (!ext2fs_test_block_bitmap (_fs->block_map, *block_nr))
+ _lsd->free_blocks++;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Load information about deleted files.
+ * Don't abort if there is not enough memory - load as much as we can.
+ */
+
+static int
+undelfs_loaddel (void)
+{
+ int retval, count;
+ ext2_ino_t ino;
+ struct ext2_inode inode;
+ ext2_inode_scan scan;
+
+ max_delarray = 100;
+ num_delarray = 0;
+ delarray = g_try_malloc (sizeof (struct deleted_info) * max_delarray);
+ if (!delarray)
+ {
+ message (D_ERROR, undelfserr, "%s", _("not enough memory"));
+ return 0;
+ }
+ block_buf = g_try_malloc (fs->blocksize * 3);
+ if (!block_buf)
+ {
+ message (D_ERROR, undelfserr, "%s", _("while allocating block buffer"));
+ goto free_delarray;
+ }
+ retval = ext2fs_open_inode_scan (fs, 0, &scan);
+ if (retval != 0)
+ {
+ message (D_ERROR, undelfserr, _("open_inode_scan: %d"), retval);
+ goto free_block_buf;
+ }
+ retval = ext2fs_get_next_inode (scan, &ino, &inode);
+ if (retval != 0)
+ {
+ message (D_ERROR, undelfserr, _("while starting inode scan %d"), retval);
+ goto error_out;
+ }
+ count = 0;
+ while (ino)
+ {
+ if ((count++ % 1024) == 0)
+ vfs_print_message (_("undelfs: loading deleted files information %d inodes"), count);
+ if (inode.i_dtime == 0)
+ goto next;
+
+ if (S_ISDIR (inode.i_mode))
+ goto next;
+
+ lsd.inode = ino;
+ lsd.num_blocks = 0;
+ lsd.free_blocks = 0;
+ lsd.bad_blocks = 0;
+
+ retval = ext2fs_block_iterate (fs, ino, 0, block_buf, undelfs_lsdel_proc, &lsd);
+ if (retval)
+ {
+ message (D_ERROR, undelfserr, _("while calling ext2_block_iterate %d"), retval);
+ goto next;
+ }
+ if (lsd.free_blocks && !lsd.bad_blocks)
+ {
+ if (num_delarray >= max_delarray)
+ {
+ struct deleted_info *delarray_new = g_try_realloc (delarray,
+ sizeof (struct deleted_info) *
+ (max_delarray + 50));
+ if (!delarray_new)
+ {
+ message (D_ERROR, undelfserr, "%s",
+ _("no more memory while reallocating array"));
+ goto error_out;
+ }
+ delarray = delarray_new;
+ max_delarray += 50;
+ }
+
+ delarray[num_delarray].ino = ino;
+ delarray[num_delarray].mode = inode.i_mode;
+ delarray[num_delarray].uid = inode.i_uid;
+ delarray[num_delarray].gid = inode.i_gid;
+ delarray[num_delarray].size = inode.i_size;
+ delarray[num_delarray].dtime = inode.i_dtime;
+ delarray[num_delarray].num_blocks = lsd.num_blocks;
+ delarray[num_delarray].free_blocks = lsd.free_blocks;
+ num_delarray++;
+ }
+
+ next:
+ retval = ext2fs_get_next_inode (scan, &ino, &inode);
+ if (retval)
+ {
+ message (D_ERROR, undelfserr, _("while doing inode scan %d"), retval);
+ goto error_out;
+ }
+ }
+ readdir_ptr = READDIR_PTR_INIT;
+ ext2fs_close_inode_scan (scan);
+ return 1;
+
+ error_out:
+ ext2fs_close_inode_scan (scan);
+ free_block_buf:
+ MC_PTR_FREE (block_buf);
+ free_delarray:
+ MC_PTR_FREE (delarray);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+undelfs_opendir (const vfs_path_t * vpath)
+{
+ char *file, *f = NULL;
+ const char *class_name;
+
+ class_name = vfs_path_get_last_path_vfs (vpath)->name;
+ undelfs_get_path (vpath, &file, &f);
+ if (file == NULL)
+ {
+ g_free (f);
+ return 0;
+ }
+
+ /* We don't use the file name */
+ g_free (f);
+
+ if (!ext2_fname || strcmp (ext2_fname, file))
+ {
+ undelfs_shutdown ();
+ ext2_fname = file;
+ }
+ else
+ {
+ /* To avoid expensive re-scannings */
+ readdir_ptr = READDIR_PTR_INIT;
+ g_free (file);
+ return fs;
+ }
+
+ if (ext2fs_open (ext2_fname, 0, 0, 0, unix_io_manager, &fs))
+ {
+ message (D_ERROR, undelfserr, _("Cannot open file %s"), ext2_fname);
+ return 0;
+ }
+ vfs_print_message ("%s", _("undelfs: reading inode bitmap..."));
+ if (ext2fs_read_inode_bitmap (fs))
+ {
+ message (D_ERROR, undelfserr, _("Cannot load inode bitmap from:\n%s"), ext2_fname);
+ goto quit_opendir;
+ }
+ vfs_print_message ("%s", _("undelfs: reading block bitmap..."));
+ if (ext2fs_read_block_bitmap (fs))
+ {
+ message (D_ERROR, undelfserr, _("Cannot load block bitmap from:\n%s"), ext2_fname);
+ goto quit_opendir;
+ }
+ /* Now load the deleted information */
+ if (!undelfs_loaddel ())
+ goto quit_opendir;
+ vfs_print_message (_("%s: done."), class_name);
+ return fs;
+ quit_opendir:
+ vfs_print_message (_("%s: failure"), class_name);
+ ext2fs_close (fs);
+ fs = NULL;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_dirent *
+undelfs_readdir (void *vfs_info)
+{
+ struct vfs_dirent *dirent;
+
+ if (vfs_info != fs)
+ {
+ message (D_ERROR, undelfserr, "%s", _("vfs_info is not fs!"));
+ return NULL;
+ }
+ if (readdir_ptr == num_delarray)
+ return NULL;
+ if (readdir_ptr < 0)
+ dirent = vfs_dirent_init (NULL, readdir_ptr == -2 ? "." : "..", 0); /* FIXME: inode */
+ else
+ {
+ char dirent_dest[MC_MAXPATHLEN];
+
+ g_snprintf (dirent_dest, MC_MAXPATHLEN, "%ld:%d",
+ (long) delarray[readdir_ptr].ino, delarray[readdir_ptr].num_blocks);
+ dirent = vfs_dirent_init (NULL, dirent_dest, 0); /* FIXME: inode */
+ }
+ readdir_ptr++;
+
+ return dirent;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+undelfs_closedir (void *vfs_info)
+{
+ (void) vfs_info;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* We do not support lseek */
+
+static void *
+undelfs_open (const vfs_path_t * vpath, int flags, mode_t mode)
+{
+ char *file, *f = NULL;
+ ext2_ino_t inode, i;
+ undelfs_file *p = NULL;
+ (void) flags;
+ (void) mode;
+
+ /* Only allow reads on this file system */
+ undelfs_get_path (vpath, &file, &f);
+ if (file == NULL)
+ {
+ g_free (f);
+ return 0;
+ }
+
+ if (!ext2_fname || strcmp (ext2_fname, file))
+ {
+ message (D_ERROR, undelfserr, "%s", _("You have to chdir to extract files first"));
+ g_free (file);
+ g_free (f);
+ return 0;
+ }
+ inode = atol (f);
+
+ /* Search the file into delarray */
+ for (i = 0; i < (ext2_ino_t) num_delarray; i++)
+ {
+ if (inode != delarray[i].ino)
+ continue;
+
+ /* Found: setup all the structures needed by read */
+ p = (undelfs_file *) g_try_malloc (((gsize) sizeof (undelfs_file)));
+ if (!p)
+ {
+ g_free (file);
+ g_free (f);
+ return 0;
+ }
+ p->buf = g_try_malloc (fs->blocksize);
+ if (!p->buf)
+ {
+ g_free (p);
+ g_free (file);
+ g_free (f);
+ return 0;
+ }
+ p->inode = inode;
+ p->finished = FALSE;
+ p->f_index = i;
+ p->error_code = 0;
+ p->pos = 0;
+ p->size = delarray[i].size;
+ }
+ g_free (file);
+ g_free (f);
+ undelfs_usage++;
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+undelfs_close (void *vfs_info)
+{
+ undelfs_file *p = vfs_info;
+ g_free (p->buf);
+ g_free (p);
+ undelfs_usage--;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+undelfs_dump_read (ext2_filsys param_fs, blk_t * blocknr, int blockcnt, void *private)
+{
+ int copy_count;
+ undelfs_file *p = (undelfs_file *) private;
+
+ if (blockcnt < 0)
+ return 0;
+
+ if (*blocknr)
+ {
+ p->error_code = io_channel_read_blk (param_fs->io, *blocknr, 1, p->buf);
+ if (p->error_code)
+ return BLOCK_ABORT;
+ }
+ else
+ memset (p->buf, 0, param_fs->blocksize);
+
+ if (p->pos + (off_t) p->count < p->current)
+ {
+ p->finished = TRUE;
+ return BLOCK_ABORT;
+ }
+ if (p->pos > p->current + param_fs->blocksize)
+ {
+ p->current += param_fs->blocksize;
+ return 0; /* we have not arrived yet */
+ }
+
+ /* Now, we know we have to extract some data */
+ if (p->pos >= p->current)
+ {
+
+ /* First case: starting pointer inside this block */
+ if (p->pos + (off_t) p->count <= p->current + param_fs->blocksize)
+ {
+ /* Fully contained */
+ copy_count = p->count;
+ p->finished = (p->count != 0);
+ }
+ else
+ {
+ /* Still some more data */
+ copy_count = param_fs->blocksize - (p->pos - p->current);
+ }
+ memcpy (p->dest_buffer, p->buf + (p->pos - p->current), copy_count);
+ }
+ else
+ {
+ /* Second case: we already have passed p->pos */
+ if (p->pos + (off_t) p->count < p->current + param_fs->blocksize)
+ {
+ copy_count = (p->pos + p->count) - p->current;
+ p->finished = (p->count != 0);
+ }
+ else
+ {
+ copy_count = param_fs->blocksize;
+ }
+ memcpy (p->dest_buffer, p->buf, copy_count);
+ }
+ p->dest_buffer += copy_count;
+ p->current += param_fs->blocksize;
+ if (p->finished)
+ {
+ return BLOCK_ABORT;
+ }
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+undelfs_read (void *vfs_info, char *buffer, size_t count)
+{
+ undelfs_file *p = vfs_info;
+ int retval;
+
+ p->dest_buffer = buffer;
+ p->current = 0;
+ p->finished = FALSE;
+ p->count = count;
+
+ if (p->pos + (off_t) p->count > p->size)
+ {
+ p->count = p->size - p->pos;
+ }
+ retval = ext2fs_block_iterate (fs, p->inode, 0, NULL, undelfs_dump_read, p);
+ if (retval)
+ {
+ message (D_ERROR, undelfserr, "%s", _("while iterating over blocks"));
+ return -1;
+ }
+ if (p->error_code && !p->finished)
+ return 0;
+ p->pos = p->pos + (p->dest_buffer - buffer);
+ return p->dest_buffer - buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static long
+undelfs_getindex (char *path)
+{
+ ext2_ino_t inode = atol (path);
+ int i;
+
+ for (i = 0; i < num_delarray; i++)
+ {
+ if (delarray[i].ino == inode)
+ return i;
+ }
+ return -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+undelfs_stat_int (int inode_index, struct stat *buf)
+{
+ buf->st_dev = 0;
+ buf->st_ino = delarray[inode_index].ino;
+ buf->st_mode = delarray[inode_index].mode;
+ buf->st_nlink = 1;
+ buf->st_uid = delarray[inode_index].uid;
+ buf->st_gid = delarray[inode_index].gid;
+ buf->st_size = delarray[inode_index].size;
+ buf->st_atime = delarray[inode_index].dtime;
+ buf->st_ctime = delarray[inode_index].dtime;
+ buf->st_mtime = delarray[inode_index].dtime;
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ buf->st_atim.tv_nsec = buf->st_mtim.tv_nsec = buf->st_ctim.tv_nsec = 0;
+#endif
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+undelfs_lstat (const vfs_path_t * vpath, struct stat *buf)
+{
+ int inode_index;
+ char *file, *f = NULL;
+
+ undelfs_get_path (vpath, &file, &f);
+ if (file == NULL)
+ {
+ g_free (f);
+ return 0;
+ }
+
+ /* When called from save_cwd_stats we get an incorrect file and f here:
+ e.g. incorrect correct
+ path = "undel:/dev/sda1" path="undel:/dev/sda1/401:1"
+ file = "/dev" file="/dev/sda1"
+ f = "sda1" f ="401:1"
+ If the first char in f is no digit -> return error */
+ if (!isdigit (*f))
+ {
+ g_free (file);
+ g_free (f);
+ return -1;
+ }
+
+ if (!ext2_fname || strcmp (ext2_fname, file))
+ {
+ g_free (file);
+ g_free (f);
+ message (D_ERROR, undelfserr, "%s", _("You have to chdir to extract files first"));
+ return 0;
+ }
+ inode_index = undelfs_getindex (f);
+ g_free (file);
+ g_free (f);
+
+ if (inode_index == -1)
+ return -1;
+
+ return undelfs_stat_int (inode_index, buf);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+undelfs_fstat (void *vfs_info, struct stat *buf)
+{
+ undelfs_file *p = vfs_info;
+
+ return undelfs_stat_int (p->f_index, buf);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+undelfs_chdir (const vfs_path_t * vpath)
+{
+ char *file, *f = NULL;
+ int fd;
+
+ undelfs_get_path (vpath, &file, &f);
+ if (file == NULL)
+ {
+ g_free (f);
+ return (-1);
+ }
+
+ /* We may use access because ext2 file systems are local */
+ /* this could be fixed by making an ext2fs io manager to use */
+ /* our vfs, but that is left as an exercise for the reader */
+ fd = open (file, O_RDONLY);
+ if (fd == -1)
+ {
+ message (D_ERROR, undelfserr, _("Cannot open file \"%s\""), file);
+ g_free (f);
+ g_free (file);
+ return -1;
+ }
+ close (fd);
+ g_free (f);
+ g_free (file);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* this has to stay here for now: vfs layer does not know how to emulate it */
+static off_t
+undelfs_lseek (void *vfs_info, off_t offset, int whence)
+{
+ (void) vfs_info;
+ (void) offset;
+ (void) whence;
+
+ return -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfsid
+undelfs_getid (const vfs_path_t * vpath)
+{
+ char *fname = NULL, *fsname;
+ gboolean ok;
+
+ undelfs_get_path (vpath, &fsname, &fname);
+ ok = fsname != NULL;
+
+ g_free (fname);
+ g_free (fsname);
+
+ return ok ? (vfsid) fs : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+undelfs_nothingisopen (vfsid id)
+{
+ (void) id;
+
+ return (undelfs_usage == 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+undelfs_free (vfsid id)
+{
+ (void) id;
+
+ undelfs_shutdown ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_NLS
+static int
+undelfs_init (struct vfs_class *me)
+{
+ (void) me;
+
+ undelfserr = _(undelfserr);
+ return 1;
+}
+#else
+#define undelfs_init NULL
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * This function overrides com_err() from libcom_err library.
+ * It is used in libext2fs to report errors.
+ */
+
+void
+com_err (const char *whoami, long err_code, const char *fmt, ...)
+{
+ va_list ap;
+ char *str;
+
+ va_start (ap, fmt);
+ str = g_strdup_vprintf (fmt, ap);
+ va_end (ap);
+
+ message (D_ERROR, _("Ext2lib error"), "%s (%s: %ld)", str, whoami, err_code);
+ g_free (str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init_undelfs (void)
+{
+ /* NULLize vfs_s_subclass members */
+ memset (&undelfs_subclass, 0, sizeof (undelfs_subclass));
+
+ vfs_init_class (vfs_undelfs_ops, "undelfs", VFSF_UNKNOWN, "undel");
+ vfs_undelfs_ops->init = undelfs_init;
+ vfs_undelfs_ops->open = undelfs_open;
+ vfs_undelfs_ops->close = undelfs_close;
+ vfs_undelfs_ops->read = undelfs_read;
+ vfs_undelfs_ops->opendir = undelfs_opendir;
+ vfs_undelfs_ops->readdir = undelfs_readdir;
+ vfs_undelfs_ops->closedir = undelfs_closedir;
+ vfs_undelfs_ops->stat = undelfs_stat;
+ vfs_undelfs_ops->lstat = undelfs_lstat;
+ vfs_undelfs_ops->fstat = undelfs_fstat;
+ vfs_undelfs_ops->chdir = undelfs_chdir;
+ vfs_undelfs_ops->lseek = undelfs_lseek;
+ vfs_undelfs_ops->getid = undelfs_getid;
+ vfs_undelfs_ops->nothingisopen = undelfs_nothingisopen;
+ vfs_undelfs_ops->free = undelfs_free;
+ vfs_register_class (vfs_undelfs_ops);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/undelfs/undelfs.h b/src/vfs/undelfs/undelfs.h
new file mode 100644
index 0000000..9e32458
--- /dev/null
+++ b/src/vfs/undelfs/undelfs.h
@@ -0,0 +1,18 @@
+#ifndef MC__VFS_UNDELFS_H
+#define MC__VFS_UNDELFS_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void vfs_init_undelfs (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__VFS_UNDELFS_H */