summaryrefslogtreecommitdiffstats
path: root/lib/vfs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/vfs/HACKING104
-rw-r--r--lib/vfs/Makefile.am19
-rw-r--r--lib/vfs/Makefile.in767
-rw-r--r--lib/vfs/README70
-rw-r--r--lib/vfs/direntry.c1740
-rw-r--r--lib/vfs/gc.c335
-rw-r--r--lib/vfs/gc.h27
-rw-r--r--lib/vfs/interface.c875
-rw-r--r--lib/vfs/netutil.c83
-rw-r--r--lib/vfs/netutil.h26
-rw-r--r--lib/vfs/parse_ls_vga.c886
-rw-r--r--lib/vfs/path.c1683
-rw-r--r--lib/vfs/path.h149
-rw-r--r--lib/vfs/utilvfs.c374
-rw-r--r--lib/vfs/utilvfs.h64
-rw-r--r--lib/vfs/vfs.c775
-rw-r--r--lib/vfs/vfs.h343
-rw-r--r--lib/vfs/xdirentry.h205
18 files changed, 8525 insertions, 0 deletions
diff --git a/lib/vfs/HACKING b/lib/vfs/HACKING
new file mode 100644
index 0000000..c02e23d
--- /dev/null
+++ b/lib/vfs/HACKING
@@ -0,0 +1,104 @@
+Intended audience
+=================
+
+This document is intended for everybody who wants to understand VFS
+code. Knowledge of programming is a must.
+
+
+Preface
+=======
+
+While VFS should be considered an excellent idea, which came ahead of
+its time, the implementation used in GNU Midnight Commander is now
+showing its age.
+
+The VFS code was left us without any decent documentation. Most
+functions don't have comments explaining what they do. Most comments
+describe quirks and implementation details, rather than the intended
+functionality of the code. This document is an attempt to reconstruct
+understanding of the VFS code and help its future developers.
+
+Being the part of GNU Midnight Commander most exposed to potential
+security threats, the VFS code needs to be kept is a good shape.
+Understanding the code is the key to making and keeping it secure.
+
+
+Basics of code organization
+===========================
+
+VFS code it to a certain extent object oriented. The code dealing with
+a certain type of data (e.g. tar archives) can be thought
+of as a class in the terms of object oriented programming. They may
+reuse some code from their parent classes. For instance, tar and cpio
+archives have a common parent class direntry, which contains some common
+code for archives.
+
+Individual archives or connections can be considered as instances of
+those classes. They provide POSIX like interface to their structure,
+but don't expose that structure directly to the common VFS layer.
+
+Each VFS object has a directory tree associated with it. The tree
+consists of entries for files and directories. In some VFS classes, the
+entries have names and a are associated with nameless inodes, which
+contain information such as size, timestamps and other data normally
+contained in POSIX "struct stat".
+
+File vfs.c serves as a multiplexor. It exports functions similar to
+POSIX but with "mc_" prepended to them. For example, mc_open() will act
+like open(), but will treat VFS names in a special way.
+
+Common utility functions not intended to be used outside the VFS code
+should go to utilvfs.c and possibly to other files. Presently, there is
+a lot of such code in vfs.c.
+
+
+Hierarchy of classes
+====================
+
+vfs ---- direntry ---- cpio } archives
+ | | ---- tar }
+ | |
+ | | ---- fish } remote systems
+ | | ---- ftpfs }
+ |
+ |---- extfs ---- extfs archives
+ |---- localfs ---- sfs ---- sfs archives
+ |---- undelfs
+
+
+Properties of classes
+=====================
+
+ read only inode->entry local cache full tree
+ mapping loaded
+
+cpio yes* yes* no yes
+tar yes* yes* no yes
+fish no yes yes no
+ftpfs no yes yes no
+extfs no no yes yes
+localfs no no N/A N/A
+sfs no yes yes N/A
+undelfs no yes no yes
+
+
+"*" means that this property should change during further development.
+Mapping from inode to entry prevents implementing hard links. It is
+permissible for directories, which cannot be hardlinked. Not loading
+the full tree speeds up access to large archives and conserves memory.
+
+
+Stamping
+========
+
+Stamping is the VFS equivalent of garbage collection. It's purpose is
+to destroy unreferenced VFS objects, in other words close archives or
+connections once they are unused for some time. There is a tree of
+items representing VFS objects. The common layer doesn't know the
+structure of the pointers, but it knows the class that should handle the
+pointer. Every item has a timestamp. Once the timestamp becomes too
+old, the object is freed.
+
+There are ways to keep objects alive if they are used. Also, objects
+can have parent objects, which are freed together with there original
+object if they are otherwise unreferenced.
diff --git a/lib/vfs/Makefile.am b/lib/vfs/Makefile.am
new file mode 100644
index 0000000..87a51c6
--- /dev/null
+++ b/lib/vfs/Makefile.am
@@ -0,0 +1,19 @@
+noinst_LTLIBRARIES = libmcvfs.la
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+
+libmcvfs_la_SOURCES = \
+ direntry.c \
+ gc.c gc.h \
+ interface.c \
+ parse_ls_vga.c \
+ path.c path.h \
+ vfs.c vfs.h \
+ utilvfs.c utilvfs.h \
+ xdirentry.h
+
+if ENABLE_VFS_NET
+libmcvfs_la_SOURCES += netutil.c netutil.h
+endif
+
+EXTRA_DIST = HACKING README
diff --git a/lib/vfs/Makefile.in b/lib/vfs/Makefile.in
new file mode 100644
index 0000000..bf588f9
--- /dev/null
+++ b/lib/vfs/Makefile.in
@@ -0,0 +1,767 @@
+# 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_NET_TRUE@am__append_1 = netutil.c netutil.h
+subdir = lib/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)
+libmcvfs_la_LIBADD =
+am__libmcvfs_la_SOURCES_DIST = direntry.c gc.c gc.h interface.c \
+ parse_ls_vga.c path.c path.h vfs.c vfs.h utilvfs.c utilvfs.h \
+ xdirentry.h netutil.c netutil.h
+@ENABLE_VFS_NET_TRUE@am__objects_1 = netutil.lo
+am_libmcvfs_la_OBJECTS = direntry.lo gc.lo interface.lo \
+ parse_ls_vga.lo path.lo vfs.lo utilvfs.lo $(am__objects_1)
+libmcvfs_la_OBJECTS = $(am_libmcvfs_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)/direntry.Plo ./$(DEPDIR)/gc.Plo \
+ ./$(DEPDIR)/interface.Plo ./$(DEPDIR)/netutil.Plo \
+ ./$(DEPDIR)/parse_ls_vga.Plo ./$(DEPDIR)/path.Plo \
+ ./$(DEPDIR)/utilvfs.Plo ./$(DEPDIR)/vfs.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 = $(libmcvfs_la_SOURCES)
+DIST_SOURCES = $(am__libmcvfs_la_SOURCES_DIST)
+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 \
+ 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@
+noinst_LTLIBRARIES = libmcvfs.la
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+libmcvfs_la_SOURCES = direntry.c gc.c gc.h interface.c parse_ls_vga.c \
+ path.c path.h vfs.c vfs.h utilvfs.c utilvfs.h xdirentry.h \
+ $(am__append_1)
+EXTRA_DIST = HACKING README
+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 lib/vfs/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu lib/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}; \
+ }
+
+libmcvfs.la: $(libmcvfs_la_OBJECTS) $(libmcvfs_la_DEPENDENCIES) $(EXTRA_libmcvfs_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libmcvfs_la_OBJECTS) $(libmcvfs_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/direntry.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gc.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/interface.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/netutil.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parse_ls_vga.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/path.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utilvfs.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/vfs.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)/direntry.Plo
+ -rm -f ./$(DEPDIR)/gc.Plo
+ -rm -f ./$(DEPDIR)/interface.Plo
+ -rm -f ./$(DEPDIR)/netutil.Plo
+ -rm -f ./$(DEPDIR)/parse_ls_vga.Plo
+ -rm -f ./$(DEPDIR)/path.Plo
+ -rm -f ./$(DEPDIR)/utilvfs.Plo
+ -rm -f ./$(DEPDIR)/vfs.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)/direntry.Plo
+ -rm -f ./$(DEPDIR)/gc.Plo
+ -rm -f ./$(DEPDIR)/interface.Plo
+ -rm -f ./$(DEPDIR)/netutil.Plo
+ -rm -f ./$(DEPDIR)/parse_ls_vga.Plo
+ -rm -f ./$(DEPDIR)/path.Plo
+ -rm -f ./$(DEPDIR)/utilvfs.Plo
+ -rm -f ./$(DEPDIR)/vfs.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/lib/vfs/README b/lib/vfs/README
new file mode 100644
index 0000000..14d4397
--- /dev/null
+++ b/lib/vfs/README
@@ -0,0 +1,70 @@
+NOTE: Although vfs has been meant to be implemented as a separate
+entity redistributable under the LGPL in its current implementation it
+uses GPLed code from src/. So there are two possibilities if you want
+to use vfs:
+
+1. Distribute your copy of vfs under the GPL. Then you can freely
+include the GPLed functions from the rest of the mc source code.
+
+2. Distribute your copy of vfs under the LGPL. Then you cannot include
+the functions outside the vfs subdirectory. You must then either
+rewrite them or work around them in other ways.
+
+========================================================================
+
+Hi!
+
+I'm midnight commander's vfs layer. Before you start hacking me,
+please read this file. I'm integral part of midnight commander, but I
+try to go out and live my life myself as a shared library, too. That
+means that I should try to use as little functions from midnight as
+possible (so I'm tiny, nice and people like me), that I should not
+pollute name space by unnecessary symbols (so I do not crash fellow
+programs) and that I should have a clean interface between myself and
+midnight.
+
+Because I'm rather close to midnight, try to:
+
+* Keep the indentation as the rest of the code. Following could help
+you with your friend emacs:
+
+(defun mc-c-mode ()
+ "C mode with adjusted defaults for use with the Midnight commander."
+ (interactive)
+ (c-mode)
+ (c-set-style "K&R")
+ (setq c-indent-level 4
+ c-continued-statement-offset 4
+ c-brace-offset 0
+ c-argdecl-indent 4
+ c-label-offset -4
+ c-brace-imaginary-offset 0
+ c-continued-brace-offset 0
+ c-tab-always-indent nil
+ c-basic-offset 4
+ tab-width 8
+ comment-column 60))
+
+(setq auto-mode-alist (cons '(".*/mc/.*\\.[ch]$" . mc-c-mode)
+ auto-mode-alist))
+
+And because I'm trying to live life on my own as libvfs.so, try to:
+
+* Make sure all exported symbols are defined in vfs.h and begin with
+'vfs_'.
+
+* Do not make any references from midnight into modules like tar. It
+would probably pollute name space and midnight would depend on concrete
+configuration of libvfs. mc_setctl() and mc_ctl() are your
+friends. (And mine too :-).
+
+ Pavel Machek
+ pavel@ucw.cz
+
+PS: If you'd like to use my features in whole operating system, you
+might want to link me to rpc.nfsd. On
+http://atrey.karlin.mff.cuni.cz/~pavel/podfuk/podfuk.html you'll find
+how to do it.
+
+PPS: I have a friend, shared library called avfs, which is LD_PRELOAD
+capable. You can reach her at http://www.inf.bme.hu/~mszeredi/avfs.
diff --git a/lib/vfs/direntry.c b/lib/vfs/direntry.c
new file mode 100644
index 0000000..32b8594
--- /dev/null
+++ b/lib/vfs/direntry.c
@@ -0,0 +1,1740 @@
+/*
+ Directory cache support
+
+ Copyright (C) 1998-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Pavel Machek <pavel@ucw.cz>, 1998
+ 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/>.
+
+ \warning Paths here do _not_ begin with '/', so root directory of
+ archive/site is simply "".
+ */
+
+/** \file
+ * \brief Source: directory cache support
+ *
+ * So that you do not have copy of this in each and every filesystem.
+ *
+ * Very loosely based on tar.c from midnight and archives.[ch] from
+ * avfs by Miklos Szeredi (mszeredi@inf.bme.hu)
+ *
+ * Unfortunately, I was unable to keep all filesystems
+ * uniform. tar-like filesystems use tree structure where each
+ * directory has pointers to its subdirectories. We can do this
+ * because we have full information about our archive.
+ *
+ * At ftp-like filesystems, situation is a little bit different. When
+ * you cd /usr/src/linux/drivers/char, you do _not_ want /usr,
+ * /usr/src, /usr/src/linux and /usr/src/linux/drivers to be
+ * listed. That means that we do not have complete information, and if
+ * /usr is symlink to /4, we will not know. Also we have to time out
+ * entries and things would get messy with tree-like approach. So we
+ * do different trick: root directory is completely special and
+ * completely fake, it contains entries such as 'usr', 'usr/src', ...,
+ * and we'll try to use custom find_entry function.
+ *
+ * \author Pavel Machek <pavel@ucw.cz>
+ * \date 1998
+ *
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <inttypes.h> /* uintmax_t */
+#include <stdarg.h>
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h" /* enable/disable interrupt key */
+#include "lib/util.h" /* canonicalize_pathname_custom() */
+#if 0
+#include "lib/widget.h" /* message() */
+#endif
+
+#include "vfs.h"
+#include "utilvfs.h"
+#include "xdirentry.h"
+#include "gc.h" /* vfs_rmstamp */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define CALL(x) \
+ if (VFS_SUBCLASS (me)->x != NULL) \
+ VFS_SUBCLASS (me)->x
+
+/*** file scope type declarations ****************************************************************/
+
+struct dirhandle
+{
+ GList *cur;
+ struct vfs_s_inode *dir;
+};
+
+/*** file scope variables ************************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* We were asked to create entries automagically */
+
+static struct vfs_s_entry *
+vfs_s_automake (struct vfs_class *me, struct vfs_s_inode *dir, char *path, int flags)
+{
+ struct vfs_s_entry *res;
+ char *sep;
+
+ sep = strchr (path, PATH_SEP);
+ if (sep != NULL)
+ *sep = '\0';
+
+ res = vfs_s_generate_entry (me, path, dir, (flags & FL_MKDIR) != 0 ? (0777 | S_IFDIR) : 0777);
+ vfs_s_insert_entry (me, dir, res);
+
+ if (sep != NULL)
+ *sep = PATH_SEP;
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* If the entry is a symlink, find the entry for its target */
+
+static struct vfs_s_entry *
+vfs_s_resolve_symlink (struct vfs_class *me, struct vfs_s_entry *entry, int follow)
+{
+ char *linkname;
+ char *fullname = NULL;
+ struct vfs_s_entry *target;
+
+ if (follow == LINK_NO_FOLLOW)
+ return entry;
+ if (follow == 0)
+ ERRNOR (ELOOP, NULL);
+ if (entry == NULL)
+ ERRNOR (ENOENT, NULL);
+ if (!S_ISLNK (entry->ino->st.st_mode))
+ return entry;
+
+ linkname = entry->ino->linkname;
+ if (linkname == NULL)
+ ERRNOR (EFAULT, NULL);
+
+ /* make full path from relative */
+ if (!IS_PATH_SEP (*linkname))
+ {
+ char *fullpath;
+
+ fullpath = vfs_s_fullpath (me, entry->dir);
+ if (fullpath != NULL)
+ {
+ fullname = g_strconcat (fullpath, PATH_SEP_STR, linkname, (char *) NULL);
+ linkname = fullname;
+ g_free (fullpath);
+ }
+ }
+
+ target =
+ VFS_SUBCLASS (me)->find_entry (me, entry->dir->super->root, linkname, follow - 1, FL_NONE);
+ g_free (fullname);
+ return target;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Follow > 0: follow links, serves as loop protect,
+ * == -1: do not follow links
+ */
+
+static struct vfs_s_entry *
+vfs_s_find_entry_tree (struct vfs_class *me, struct vfs_s_inode *root,
+ const char *a_path, int follow, int flags)
+{
+ size_t pseg;
+ struct vfs_s_entry *ent = NULL;
+ char *const pathref = g_strdup (a_path);
+ char *path = pathref;
+
+ /* canonicalize as well, but don't remove '../' from path */
+ canonicalize_pathname_custom (path, CANON_PATH_ALL & (~CANON_PATH_REMDOUBLEDOTS));
+
+ while (root != NULL)
+ {
+ GList *iter;
+
+ while (IS_PATH_SEP (*path)) /* Strip leading '/' */
+ path++;
+
+ if (path[0] == '\0')
+ {
+ g_free (pathref);
+ return ent;
+ }
+
+ for (pseg = 0; path[pseg] != '\0' && !IS_PATH_SEP (path[pseg]); pseg++)
+ ;
+
+ for (iter = g_queue_peek_head_link (root->subdir); iter != NULL; iter = g_list_next (iter))
+ {
+ ent = VFS_ENTRY (iter->data);
+ if (strlen (ent->name) == pseg && strncmp (ent->name, path, pseg) == 0)
+ /* FOUND! */
+ break;
+ }
+
+ ent = iter != NULL ? VFS_ENTRY (iter->data) : NULL;
+
+ if (ent == NULL && (flags & (FL_MKFILE | FL_MKDIR)) != 0)
+ ent = vfs_s_automake (me, root, path, flags);
+ if (ent == NULL)
+ {
+ me->verrno = ENOENT;
+ goto cleanup;
+ }
+
+ path += pseg;
+ /* here we must follow leading directories always;
+ only the actual file is optional */
+ ent = vfs_s_resolve_symlink (me, ent,
+ strchr (path, PATH_SEP) != NULL ? LINK_FOLLOW : follow);
+ if (ent == NULL)
+ goto cleanup;
+ root = ent->ino;
+ }
+ cleanup:
+ g_free (pathref);
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_entry *
+vfs_s_find_entry_linear (struct vfs_class *me, struct vfs_s_inode *root,
+ const char *a_path, int follow, int flags)
+{
+ struct vfs_s_entry *ent = NULL;
+ char *const path = g_strdup (a_path);
+ GList *iter;
+
+ if (root->super->root != root)
+ vfs_die ("We have to use _real_ root. Always. Sorry.");
+
+ /* canonicalize as well, but don't remove '../' from path */
+ canonicalize_pathname_custom (path, CANON_PATH_ALL & (~CANON_PATH_REMDOUBLEDOTS));
+
+ if ((flags & FL_DIR) == 0)
+ {
+ char *dirname, *name;
+ struct vfs_s_inode *ino;
+
+ dirname = g_path_get_dirname (path);
+ name = g_path_get_basename (path);
+ ino = vfs_s_find_inode (me, root->super, dirname, follow, flags | FL_DIR);
+ ent = vfs_s_find_entry_tree (me, ino, name, follow, flags);
+ g_free (dirname);
+ g_free (name);
+ g_free (path);
+ return ent;
+ }
+
+ iter = g_queue_find_custom (root->subdir, path, (GCompareFunc) vfs_s_entry_compare);
+ ent = iter != NULL ? VFS_ENTRY (iter->data) : NULL;
+
+ if (ent != NULL && !VFS_SUBCLASS (me)->dir_uptodate (me, ent->ino))
+ {
+#if 1
+ vfs_print_message (_("Directory cache expired for %s"), path);
+#endif
+ vfs_s_free_entry (me, ent);
+ ent = NULL;
+ }
+
+ if (ent == NULL)
+ {
+ struct vfs_s_inode *ino;
+
+ ino = vfs_s_new_inode (me, root->super, vfs_s_default_stat (me, S_IFDIR | 0755));
+ ent = vfs_s_new_entry (me, path, ino);
+ if (VFS_SUBCLASS (me)->dir_load (me, ino, path) == -1)
+ {
+ vfs_s_free_entry (me, ent);
+ g_free (path);
+ return NULL;
+ }
+
+ vfs_s_insert_entry (me, root, ent);
+
+ iter = g_queue_find_custom (root->subdir, path, (GCompareFunc) vfs_s_entry_compare);
+ ent = iter != NULL ? VFS_ENTRY (iter->data) : NULL;
+ }
+ if (ent == NULL)
+ vfs_die ("find_linear: success but directory is not there\n");
+
+#if 0
+ if (vfs_s_resolve_symlink (me, ent, follow) == NULL)
+ {
+ g_free (path);
+ return NULL;
+ }
+#endif
+ g_free (path);
+ return ent;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Ook, these were functions around directory entries / inodes */
+/* -------------------------------- superblock games -------------------------- */
+
+static struct vfs_s_super *
+vfs_s_new_super (struct vfs_class *me)
+{
+ struct vfs_s_super *super;
+
+ super = g_new0 (struct vfs_s_super, 1);
+ super->me = me;
+ return super;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+vfs_s_insert_super (struct vfs_class *me, struct vfs_s_super *super)
+{
+ VFS_SUBCLASS (me)->supers = g_list_prepend (VFS_SUBCLASS (me)->supers, super);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+vfs_s_free_super (struct vfs_class *me, struct vfs_s_super *super)
+{
+ if (super->root != NULL)
+ {
+ vfs_s_free_inode (me, super->root);
+ super->root = NULL;
+ }
+
+#if 0
+ /* FIXME: We currently leak small amount of memory, sometimes. Fix it if you can. */
+ if (super->ino_usage != 0)
+ message (D_ERROR, "Direntry warning",
+ "Super ino_usage is %d, memory leak", super->ino_usage);
+
+ if (super->want_stale)
+ message (D_ERROR, "Direntry warning", "%s", "Super has want_stale set");
+#endif
+
+ VFS_SUBCLASS (me)->supers = g_list_remove (VFS_SUBCLASS (me)->supers, super);
+
+ CALL (free_archive) (me, super);
+#ifdef ENABLE_VFS_NET
+ vfs_path_element_free (super->path_element);
+#endif
+ g_free (super->name);
+ g_free (super);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_file_handler_t *
+vfs_s_new_fh (struct vfs_s_inode *ino, gboolean changed)
+{
+ vfs_file_handler_t *fh;
+
+ fh = g_new0 (vfs_file_handler_t, 1);
+ vfs_s_init_fh (fh, ino, changed);
+
+ return fh;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+vfs_s_free_fh (struct vfs_s_subclass *s, vfs_file_handler_t * fh)
+{
+ if (s->fh_free != NULL)
+ s->fh_free (fh);
+
+ g_free (fh);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Support of archives */
+/* ------------------------ readdir & friends ----------------------------- */
+
+static struct vfs_s_inode *
+vfs_s_inode_from_path (const vfs_path_t * vpath, int flags)
+{
+ struct vfs_s_super *super;
+ struct vfs_s_inode *ino;
+ const char *q;
+ struct vfs_class *me;
+
+ q = vfs_s_get_path (vpath, &super, 0);
+ if (q == NULL)
+ return NULL;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ ino =
+ vfs_s_find_inode (me, super, q,
+ (flags & FL_FOLLOW) != 0 ? LINK_FOLLOW : LINK_NO_FOLLOW,
+ flags & ~FL_FOLLOW);
+ if (ino == NULL && *q == '\0')
+ /* We are asking about / directory of ftp server: assume it exists */
+ ino =
+ vfs_s_find_inode (me, super, q,
+ (flags & FL_FOLLOW) != 0 ? LINK_FOLLOW : LINK_NO_FOLLOW,
+ FL_DIR | (flags & ~FL_FOLLOW));
+ return ino;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+vfs_s_opendir (const vfs_path_t * vpath)
+{
+ struct vfs_s_inode *dir;
+ struct dirhandle *info;
+ struct vfs_class *me;
+
+ dir = vfs_s_inode_from_path (vpath, FL_DIR | FL_FOLLOW);
+ if (dir == NULL)
+ return NULL;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ if (!S_ISDIR (dir->st.st_mode))
+ {
+ me->verrno = ENOTDIR;
+ return NULL;
+ }
+
+ dir->st.st_nlink++;
+#if 0
+ if (dir->subdir == NULL) /* This can actually happen if we allow empty directories */
+ {
+ me->verrno = EAGAIN;
+ return NULL;
+ }
+#endif
+ info = g_new (struct dirhandle, 1);
+ info->cur = g_queue_peek_head_link (dir->subdir);
+ info->dir = dir;
+
+ return info;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_dirent *
+vfs_s_readdir (void *data)
+{
+ struct vfs_dirent *dir = NULL;
+ struct dirhandle *info = (struct dirhandle *) data;
+ const char *name;
+
+ if (info->cur == NULL || info->cur->data == NULL)
+ return NULL;
+
+ name = VFS_ENTRY (info->cur->data)->name;
+ if (name != NULL)
+ dir = vfs_dirent_init (NULL, name, 0);
+ else
+ vfs_die ("Null in structure-cannot happen");
+
+ info->cur = g_list_next (info->cur);
+
+ return dir;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+vfs_s_closedir (void *data)
+{
+ struct dirhandle *info = (struct dirhandle *) data;
+ struct vfs_s_inode *dir = info->dir;
+
+ vfs_s_free_inode (dir->super->me, dir);
+ g_free (data);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+vfs_s_chdir (const vfs_path_t * vpath)
+{
+ void *data;
+
+ data = vfs_s_opendir (vpath);
+ if (data == NULL)
+ return (-1);
+ vfs_s_closedir (data);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* --------------------------- stat and friends ---------------------------- */
+
+static int
+vfs_s_internal_stat (const vfs_path_t * vpath, struct stat *buf, int flag)
+{
+ struct vfs_s_inode *ino;
+
+ ino = vfs_s_inode_from_path (vpath, flag);
+ if (ino == NULL)
+ return (-1);
+ *buf = ino->st;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+vfs_s_readlink (const vfs_path_t * vpath, char *buf, size_t size)
+{
+ struct vfs_s_inode *ino;
+ size_t len;
+ struct vfs_class *me;
+
+ ino = vfs_s_inode_from_path (vpath, 0);
+ if (ino == NULL)
+ return (-1);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ if (!S_ISLNK (ino->st.st_mode))
+ {
+ me->verrno = EINVAL;
+ return (-1);
+ }
+
+ if (ino->linkname == NULL)
+ {
+ me->verrno = EFAULT;
+ return (-1);
+ }
+
+ len = strlen (ino->linkname);
+ if (size < len)
+ len = size;
+ /* readlink() does not append a NUL character to buf */
+ memcpy (buf, ino->linkname, len);
+ return len;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+vfs_s_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;
+
+ if (file->linear == LS_LINEAR_PREOPEN)
+ {
+ if (VFS_SUBCLASS (me)->linear_start (me, file, file->pos) == 0)
+ return (-1);
+ }
+
+ if (file->linear == LS_LINEAR_CLOSED)
+ vfs_die ("linear_start() did not set linear_state!");
+
+ if (file->linear == LS_LINEAR_OPEN)
+ return VFS_SUBCLASS (me)->linear_read (me, file, buffer, count);
+
+ if (file->handle != -1)
+ {
+ ssize_t n;
+
+ n = read (file->handle, buffer, count);
+ if (n < 0)
+ me->verrno = errno;
+ return n;
+ }
+ vfs_die ("vfs_s_read: This should not happen\n");
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+vfs_s_write (void *fh, const char *buffer, size_t count)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ struct vfs_class *me = VFS_FILE_HANDLER_SUPER (fh)->me;
+
+ if (file->linear != LS_NOT_LINEAR)
+ vfs_die ("no writing to linear files, please");
+
+ file->changed = TRUE;
+ if (file->handle != -1)
+ {
+ ssize_t n;
+
+ n = write (file->handle, buffer, count);
+ if (n < 0)
+ me->verrno = errno;
+ return n;
+ }
+ vfs_die ("vfs_s_write: This should not happen\n");
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+vfs_s_lseek (void *fh, off_t offset, int whence)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ off_t size = file->ino->st.st_size;
+
+ if (file->linear == LS_LINEAR_OPEN)
+ vfs_die ("cannot lseek() after linear_read!");
+
+ if (file->handle != -1)
+ { /* If we have local file opened, we want to work with it */
+ off_t retval;
+
+ retval = lseek (file->handle, offset, whence);
+ if (retval == -1)
+ VFS_FILE_HANDLER_SUPER (fh)->me->verrno = errno;
+ return retval;
+ }
+
+ switch (whence)
+ {
+ case SEEK_CUR:
+ offset += file->pos;
+ break;
+ case SEEK_END:
+ offset += size;
+ break;
+ default:
+ break;
+ }
+ if (offset < 0)
+ file->pos = 0;
+ else if (offset < size)
+ file->pos = offset;
+ else
+ file->pos = size;
+ return file->pos;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+vfs_s_close (void *fh)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh);
+ struct vfs_class *me = super->me;
+ struct vfs_s_subclass *sub = VFS_SUBCLASS (me);
+ int res = 0;
+
+ if (me == NULL)
+ return (-1);
+
+ super->fd_usage--;
+ if (super->fd_usage == 0)
+ vfs_stamp_create (me, VFS_FILE_HANDLER_SUPER (fh));
+
+ if (file->linear == LS_LINEAR_OPEN)
+ sub->linear_close (me, fh);
+ if (sub->fh_close != NULL)
+ res = sub->fh_close (me, fh);
+ if ((me->flags & VFSF_USETMP) != 0 && file->changed && sub->file_store != NULL)
+ {
+ char *s;
+
+ s = vfs_s_fullpath (me, file->ino);
+
+ if (s == NULL)
+ res = -1;
+ else
+ {
+ res = sub->file_store (me, fh, s, file->ino->localname);
+ g_free (s);
+ }
+ vfs_s_invalidate (me, super);
+ }
+
+ if (file->handle != -1)
+ {
+ close (file->handle);
+ file->handle = -1;
+ }
+
+ vfs_s_free_inode (me, file->ino);
+ vfs_s_free_fh (sub, fh);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+vfs_s_print_stats (const char *fs_name, const char *action,
+ const char *file_name, off_t have, off_t need)
+{
+ if (need != 0)
+ vfs_print_message (_("%s: %s: %s %3d%% (%lld) bytes transferred"), fs_name, action,
+ file_name, (int) ((double) have * 100 / need), (long long) have);
+ else
+ vfs_print_message (_("%s: %s: %s %lld bytes transferred"), fs_name, action, file_name,
+ (long long) have);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* ------------------------------- mc support ---------------------------- */
+
+static void
+vfs_s_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;
+
+ name = g_strconcat (super->name, PATH_SEP_STR, me->prefix, VFS_PATH_URL_DELIMITER,
+ /* super->current_dir->name, */ (char *) NULL);
+ func (name);
+ g_free (name);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+vfs_s_ferrno (struct vfs_class *me)
+{
+ return me->verrno;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get local copy of the given file. We reuse the existing file cache
+ * for remote filesystems. Archives use standard VFS facilities.
+ */
+
+static vfs_path_t *
+vfs_s_getlocalcopy (const vfs_path_t * vpath)
+{
+ vfs_file_handler_t *fh;
+ vfs_path_t *local = NULL;
+
+ if (vpath == NULL)
+ return NULL;
+
+ fh = vfs_s_open (vpath, O_RDONLY, 0);
+
+ if (fh != NULL)
+ {
+ const struct vfs_class *me;
+
+ me = vfs_path_get_last_path_vfs (vpath);
+ if ((me->flags & VFSF_USETMP) != 0 && fh->ino != NULL)
+ local = vfs_path_from_str_flags (fh->ino->localname, VPF_NO_CANON);
+
+ vfs_s_close (fh);
+ }
+
+ return local;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Return the local copy. Since we are using our cache, we do nothing -
+ * the cache will be removed when the archive is closed.
+ */
+
+static int
+vfs_s_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
+vfs_s_setctl (const vfs_path_t * vpath, int ctlop, void *arg)
+{
+ struct vfs_class *me;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ switch (ctlop)
+ {
+ case VFS_SETCTL_STALE_DATA:
+ {
+ struct vfs_s_inode *ino;
+
+ ino = vfs_s_inode_from_path (vpath, 0);
+ if (ino == NULL)
+ return 0;
+ if (arg != NULL)
+ ino->super->want_stale = TRUE;
+ else
+ {
+ ino->super->want_stale = FALSE;
+ vfs_s_invalidate (me, ino->super);
+ }
+ return 1;
+ }
+ case VFS_SETCTL_LOGFILE:
+ me->logfile = fopen ((char *) arg, "w");
+ return 1;
+ case VFS_SETCTL_FLUSH:
+ me->flush = TRUE;
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* ----------------------------- Stamping support -------------------------- */
+
+static vfsid
+vfs_s_getid (const vfs_path_t * vpath)
+{
+ struct vfs_s_super *archive = NULL;
+ const char *p;
+
+ p = vfs_s_get_path (vpath, &archive, FL_NO_OPEN);
+ if (p == NULL)
+ return NULL;
+
+ return (vfsid) archive;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+vfs_s_nothingisopen (vfsid id)
+{
+ return (VFS_SUPER (id)->fd_usage <= 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+vfs_s_free (vfsid id)
+{
+ vfs_s_free_super (VFS_SUPER (id)->me, VFS_SUPER (id));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+vfs_s_dir_uptodate (struct vfs_class *me, struct vfs_s_inode *ino)
+{
+ gint64 tim;
+
+ if (me->flush)
+ {
+ me->flush = FALSE;
+ return 0;
+ }
+
+ tim = g_get_monotonic_time ();
+
+ return (tim < ino->timestamp);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+struct vfs_s_inode *
+vfs_s_new_inode (struct vfs_class *me, struct vfs_s_super *super, struct stat *initstat)
+{
+ struct vfs_s_inode *ino;
+
+ ino = g_try_new0 (struct vfs_s_inode, 1);
+ if (ino == NULL)
+ return NULL;
+
+ if (initstat != NULL)
+ ino->st = *initstat;
+ ino->super = super;
+ ino->subdir = g_queue_new ();
+ ino->st.st_nlink = 0;
+ ino->st.st_ino = VFS_SUBCLASS (me)->inode_counter++;
+ ino->st.st_dev = VFS_SUBCLASS (me)->rdev;
+
+ super->ino_usage++;
+
+ CALL (init_inode) (me, ino);
+
+ return ino;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_s_free_inode (struct vfs_class *me, struct vfs_s_inode *ino)
+{
+ if (ino == NULL)
+ vfs_die ("Don't pass NULL to me");
+
+ /* ==0 can happen if freshly created entry is deleted */
+ if (ino->st.st_nlink > 1)
+ {
+ ino->st.st_nlink--;
+ return;
+ }
+
+ while (g_queue_get_length (ino->subdir) != 0)
+ {
+ struct vfs_s_entry *entry;
+
+ entry = VFS_ENTRY (g_queue_peek_head (ino->subdir));
+ vfs_s_free_entry (me, entry);
+ }
+
+ g_queue_free (ino->subdir);
+ ino->subdir = NULL;
+
+ CALL (free_inode) (me, ino);
+ g_free (ino->linkname);
+ if ((me->flags & VFSF_USETMP) != 0 && ino->localname != NULL)
+ {
+ unlink (ino->localname);
+ g_free (ino->localname);
+ }
+ ino->super->ino_usage--;
+ g_free (ino);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+struct vfs_s_entry *
+vfs_s_new_entry (struct vfs_class *me, const char *name, struct vfs_s_inode *inode)
+{
+ struct vfs_s_entry *entry;
+
+ entry = g_new0 (struct vfs_s_entry, 1);
+
+ entry->name = g_strdup (name);
+ entry->ino = inode;
+ entry->ino->ent = entry;
+ CALL (init_entry) (me, entry);
+
+ return entry;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_s_free_entry (struct vfs_class *me, struct vfs_s_entry *ent)
+{
+ if (ent->dir != NULL)
+ g_queue_remove (ent->dir->subdir, ent);
+
+ MC_PTR_FREE (ent->name);
+
+ if (ent->ino != NULL)
+ {
+ ent->ino->ent = NULL;
+ vfs_s_free_inode (me, ent->ino);
+ }
+
+ g_free (ent);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_s_insert_entry (struct vfs_class *me, struct vfs_s_inode *dir, struct vfs_s_entry *ent)
+{
+ (void) me;
+
+ ent->dir = dir;
+
+ ent->ino->st.st_nlink++;
+ g_queue_push_tail (dir->subdir, ent);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_s_entry_compare (const void *a, const void *b)
+{
+ const struct vfs_s_entry *e = (const struct vfs_s_entry *) a;
+ const char *name = (const char *) b;
+
+ return strcmp (e->name, name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+struct stat *
+vfs_s_default_stat (struct vfs_class *me, mode_t mode)
+{
+ static struct stat st;
+ mode_t myumask;
+
+ (void) me;
+
+ myumask = umask (022);
+ umask (myumask);
+ mode &= ~myumask;
+
+ st.st_mode = mode;
+ st.st_ino = 0;
+ st.st_dev = 0;
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ st.st_rdev = 0;
+#endif
+ st.st_uid = getuid ();
+ st.st_gid = getgid ();
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ st.st_blksize = 512;
+#endif
+ st.st_size = 0;
+
+ st.st_mtime = st.st_atime = st.st_ctime = time (NULL);
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ st.st_atim.tv_nsec = st.st_mtim.tv_nsec = st.st_ctim.tv_nsec = 0;
+#endif
+
+ vfs_adjust_stat (&st);
+
+ return &st;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculate number of st_blocks using st_size and st_blksize.
+ * In according to stat(2), st_blocks is the size in 512-byte units.
+ *
+ * @param s stat info
+ */
+
+void
+vfs_adjust_stat (struct stat *s)
+{
+#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
+ if (s->st_size == 0)
+ s->st_blocks = 0;
+ else
+ {
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ blkcnt_t ioblocks;
+ blksize_t ioblock_size;
+
+ /* 1. Calculate how many IO blocks are occupied */
+ ioblocks = 1 + (s->st_size - 1) / s->st_blksize;
+ /* 2. Calculate size of st_blksize in 512-byte units */
+ ioblock_size = 1 + (s->st_blksize - 1) / 512;
+ /* 3. Calculate number of blocks */
+ s->st_blocks = ioblocks * ioblock_size;
+#else
+ /* Let IO block size is 512 bytes */
+ s->st_blocks = 1 + (s->st_size - 1) / 512;
+#endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */
+ }
+#endif /* HAVE_STRUCT_STAT_ST_BLOCKS */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+struct vfs_s_entry *
+vfs_s_generate_entry (struct vfs_class *me, const char *name, struct vfs_s_inode *parent,
+ mode_t mode)
+{
+ struct vfs_s_inode *inode;
+ struct stat *st;
+
+ st = vfs_s_default_stat (me, mode);
+ inode = vfs_s_new_inode (me, parent->super, st);
+
+ return vfs_s_new_entry (me, name, inode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+struct vfs_s_inode *
+vfs_s_find_inode (struct vfs_class *me, const struct vfs_s_super *super,
+ const char *path, int follow, int flags)
+{
+ struct vfs_s_entry *ent;
+
+ if (((me->flags & VFSF_REMOTE) == 0) && (*path == '\0'))
+ return super->root;
+
+ ent = VFS_SUBCLASS (me)->find_entry (me, super->root, path, follow, flags);
+ return (ent != NULL ? ent->ino : NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Ook, these were functions around directory entries / inodes */
+/* -------------------------------- superblock games -------------------------- */
+/**
+ * get superlock object by vpath
+ *
+ * @param vpath path
+ * @return superlock object or NULL if not found
+ */
+
+struct vfs_s_super *
+vfs_get_super_by_vpath (const vfs_path_t * vpath)
+{
+ GList *iter;
+ void *cookie = NULL;
+ const vfs_path_element_t *path_element;
+ struct vfs_s_subclass *subclass;
+ struct vfs_s_super *super = NULL;
+ vfs_path_t *vpath_archive;
+
+ path_element = vfs_path_get_by_index (vpath, -1);
+ subclass = VFS_SUBCLASS (path_element->class);
+
+ vpath_archive = vfs_path_clone (vpath);
+ vfs_path_remove_element_by_index (vpath_archive, -1);
+
+ if (subclass->archive_check != NULL)
+ {
+ cookie = subclass->archive_check (vpath_archive);
+ if (cookie == NULL)
+ goto ret;
+ }
+
+ if (subclass->archive_same == NULL)
+ goto ret;
+
+ for (iter = subclass->supers; iter != NULL; iter = g_list_next (iter))
+ {
+ int i;
+
+ super = VFS_SUPER (iter->data);
+
+ /* 0 == other, 1 == same, return it, 2 == other but stop scanning */
+ i = subclass->archive_same (path_element, super, vpath_archive, cookie);
+ if (i == 1)
+ goto ret;
+ if (i != 0)
+ break;
+
+ super = NULL;
+ }
+
+ ret:
+ vfs_path_free (vpath_archive, TRUE);
+ return super;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * get path from last VFS-element and create corresponding superblock
+ *
+ * @param vpath source path object
+ * @param archive pointer to object for store newly created superblock
+ * @param flags flags
+ *
+ * @return path from last VFS-element
+ */
+const char *
+vfs_s_get_path (const vfs_path_t * vpath, struct vfs_s_super **archive, int flags)
+{
+ const char *retval = "";
+ int result = -1;
+ struct vfs_s_super *super;
+ const vfs_path_element_t *path_element;
+ struct vfs_s_subclass *subclass;
+
+ path_element = vfs_path_get_by_index (vpath, -1);
+
+ if (path_element->path != NULL)
+ retval = path_element->path;
+
+ super = vfs_get_super_by_vpath (vpath);
+ if (super != NULL)
+ goto return_success;
+
+ if ((flags & FL_NO_OPEN) != 0)
+ {
+ path_element->class->verrno = EIO;
+ return NULL;
+ }
+
+ subclass = VFS_SUBCLASS (path_element->class);
+
+ super = subclass->new_archive != NULL ?
+ subclass->new_archive (path_element->class) : vfs_s_new_super (path_element->class);
+
+ if (subclass->open_archive != NULL)
+ {
+ vfs_path_t *vpath_archive;
+
+ vpath_archive = vfs_path_clone (vpath);
+ vfs_path_remove_element_by_index (vpath_archive, -1);
+
+ result = subclass->open_archive (super, vpath_archive, path_element);
+ vfs_path_free (vpath_archive, TRUE);
+ }
+ if (result == -1)
+ {
+ vfs_s_free_super (path_element->class, super);
+ path_element->class->verrno = EIO;
+ return NULL;
+ }
+ if (super->name == NULL)
+ vfs_die ("You have to fill name\n");
+ if (super->root == NULL)
+ vfs_die ("You have to fill root inode\n");
+
+ vfs_s_insert_super (path_element->class, super);
+ vfs_stamp_create (path_element->class, super);
+
+ return_success:
+ *archive = super;
+ return retval;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_s_invalidate (struct vfs_class *me, struct vfs_s_super *super)
+{
+ if (!super->want_stale)
+ {
+ vfs_s_free_inode (me, super->root);
+ super->root = vfs_s_new_inode (me, super, vfs_s_default_stat (me, S_IFDIR | 0755));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+vfs_s_fullpath (struct vfs_class *me, struct vfs_s_inode *ino)
+{
+ if (ino->ent == NULL)
+ ERRNOR (EAGAIN, NULL);
+
+ if ((me->flags & VFSF_USETMP) == 0)
+ {
+ /* archives */
+ char *path;
+
+ path = g_strdup (ino->ent->name);
+
+ while (TRUE)
+ {
+ char *newpath;
+
+ ino = ino->ent->dir;
+ if (ino == ino->super->root)
+ break;
+
+ newpath = g_strconcat (ino->ent->name, PATH_SEP_STR, path, (char *) NULL);
+ g_free (path);
+ path = newpath;
+ }
+ return path;
+ }
+
+ /* remote systems */
+ if (ino->ent->dir == NULL || ino->ent->dir->ent == NULL)
+ return g_strdup (ino->ent->name);
+
+ return g_strconcat (ino->ent->dir->ent->name, PATH_SEP_STR, ino->ent->name, (char *) NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_s_init_fh (vfs_file_handler_t * fh, struct vfs_s_inode *ino, gboolean changed)
+{
+ fh->ino = ino;
+ fh->handle = -1;
+ fh->changed = changed;
+ fh->linear = LS_NOT_LINEAR;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* --------------------------- stat and friends ---------------------------- */
+
+void *
+vfs_s_open (const vfs_path_t * vpath, int flags, mode_t mode)
+{
+ gboolean was_changed = FALSE;
+ vfs_file_handler_t *fh;
+ struct vfs_s_super *super;
+ const char *q;
+ struct vfs_s_inode *ino;
+ struct vfs_class *me;
+ struct vfs_s_subclass *s;
+
+ q = vfs_s_get_path (vpath, &super, 0);
+ if (q == NULL)
+ return NULL;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ ino = vfs_s_find_inode (me, super, q, LINK_FOLLOW, FL_NONE);
+ if (ino != NULL && (flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
+ {
+ me->verrno = EEXIST;
+ return NULL;
+ }
+
+ s = VFS_SUBCLASS (me);
+
+ if (ino == NULL)
+ {
+ char *name;
+ struct vfs_s_entry *ent;
+ struct vfs_s_inode *dir;
+
+ /* If the filesystem is read-only, disable file creation */
+ if ((flags & O_CREAT) == 0 || me->write == NULL)
+ return NULL;
+
+ name = g_path_get_dirname (q);
+ 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 (q);
+ ent = vfs_s_generate_entry (me, name, dir, 0755);
+ ino = ent->ino;
+ vfs_s_insert_entry (me, dir, ent);
+ if ((VFS_CLASS (s)->flags & VFSF_USETMP) != 0)
+ {
+ int tmp_handle;
+ vfs_path_t *tmp_vpath;
+
+ tmp_handle = vfs_mkstemps (&tmp_vpath, me->name, name);
+ ino->localname = vfs_path_free (tmp_vpath, FALSE);
+ if (tmp_handle == -1)
+ {
+ g_free (name);
+ return NULL;
+ }
+
+ close (tmp_handle);
+ }
+
+ g_free (name);
+ was_changed = TRUE;
+ }
+
+ if (S_ISDIR (ino->st.st_mode))
+ {
+ me->verrno = EISDIR;
+ return NULL;
+ }
+
+ fh = s->fh_new != NULL ? s->fh_new (ino, was_changed) : vfs_s_new_fh (ino, was_changed);
+
+ if (IS_LINEAR (flags))
+ {
+ if (s->linear_start != NULL)
+ {
+ vfs_print_message ("%s", _("Starting linear transfer..."));
+ fh->linear = LS_LINEAR_PREOPEN;
+ }
+ }
+ else
+ {
+ if (s->fh_open != NULL && s->fh_open (me, fh, flags, mode) != 0)
+ {
+ vfs_s_free_fh (s, fh);
+ return NULL;
+ }
+ }
+
+ if ((VFS_CLASS (s)->flags & VFSF_USETMP) != 0 && fh->ino->localname != NULL)
+ {
+ fh->handle = open (fh->ino->localname, NO_LINEAR (flags), mode);
+ if (fh->handle == -1)
+ {
+ vfs_s_free_fh (s, fh);
+ me->verrno = errno;
+ return NULL;
+ }
+ }
+
+ /* i.e. we had no open files and now we have one */
+ vfs_rmstamp (me, (vfsid) super);
+ super->fd_usage++;
+ fh->ino->st.st_nlink++;
+ return fh;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_s_stat (const vfs_path_t * vpath, struct stat *buf)
+{
+ return vfs_s_internal_stat (vpath, buf, FL_FOLLOW);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_s_lstat (const vfs_path_t * vpath, struct stat *buf)
+{
+ return vfs_s_internal_stat (vpath, buf, FL_NONE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_s_fstat (void *fh, struct stat *buf)
+{
+ *buf = VFS_FILE_HANDLER (fh)->ino->st;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_s_retrieve_file (struct vfs_class *me, struct vfs_s_inode *ino)
+{
+ /* If you want reget, you'll have to open file with O_LINEAR */
+ off_t total = 0;
+ char buffer[BUF_8K];
+ int handle;
+ ssize_t n;
+ off_t stat_size = ino->st.st_size;
+ vfs_file_handler_t *fh = NULL;
+ vfs_path_t *tmp_vpath;
+ struct vfs_s_subclass *s = VFS_SUBCLASS (me);
+
+ if ((me->flags & VFSF_USETMP) == 0)
+ return (-1);
+
+ handle = vfs_mkstemps (&tmp_vpath, me->name, ino->ent->name);
+ ino->localname = vfs_path_free (tmp_vpath, FALSE);
+ if (handle == -1)
+ {
+ me->verrno = errno;
+ goto error_4;
+ }
+
+ fh = s->fh_new != NULL ? s->fh_new (ino, FALSE) : vfs_s_new_fh (ino, FALSE);
+
+ if (s->linear_start (me, fh, 0) == 0)
+ goto error_3;
+
+ /* Clear the interrupt status */
+ tty_got_interrupt ();
+ tty_enable_interrupt_key ();
+
+ while ((n = s->linear_read (me, fh, buffer, sizeof (buffer))) != 0)
+ {
+ int t;
+
+ if (n < 0)
+ goto error_1;
+
+ total += n;
+ vfs_s_print_stats (me->name, _("Getting file"), ino->ent->name, total, stat_size);
+
+ if (tty_got_interrupt ())
+ goto error_1;
+
+ t = write (handle, buffer, n);
+ if (t != n)
+ {
+ if (t == -1)
+ me->verrno = errno;
+ goto error_1;
+ }
+ }
+ s->linear_close (me, fh);
+ close (handle);
+
+ tty_disable_interrupt_key ();
+ vfs_s_free_fh (s, fh);
+ return 0;
+
+ error_1:
+ s->linear_close (me, fh);
+ error_3:
+ tty_disable_interrupt_key ();
+ close (handle);
+ unlink (ino->localname);
+ error_4:
+ MC_PTR_FREE (ino->localname);
+ if (fh != NULL)
+ vfs_s_free_fh (s, fh);
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* ----------------------------- Stamping support -------------------------- */
+
+/* Initialize one of our subclasses - fill common functions */
+void
+vfs_init_class (struct vfs_class *vclass, const char *name, vfs_flags_t flags, const char *prefix)
+{
+ memset (vclass, 0, sizeof (struct vfs_class));
+
+ vclass->name = name;
+ vclass->flags = flags;
+ vclass->prefix = prefix;
+
+ vclass->fill_names = vfs_s_fill_names;
+ vclass->open = vfs_s_open;
+ vclass->close = vfs_s_close;
+ vclass->read = vfs_s_read;
+ if ((vclass->flags & VFSF_READONLY) == 0)
+ vclass->write = vfs_s_write;
+ vclass->opendir = vfs_s_opendir;
+ vclass->readdir = vfs_s_readdir;
+ vclass->closedir = vfs_s_closedir;
+ vclass->stat = vfs_s_stat;
+ vclass->lstat = vfs_s_lstat;
+ vclass->fstat = vfs_s_fstat;
+ vclass->readlink = vfs_s_readlink;
+ vclass->chdir = vfs_s_chdir;
+ vclass->ferrno = vfs_s_ferrno;
+ vclass->lseek = vfs_s_lseek;
+ vclass->getid = vfs_s_getid;
+ vclass->nothingisopen = vfs_s_nothingisopen;
+ vclass->free = vfs_s_free;
+ vclass->setctl = vfs_s_setctl;
+ if ((vclass->flags & VFSF_USETMP) != 0)
+ {
+ vclass->getlocalcopy = vfs_s_getlocalcopy;
+ vclass->ungetlocalcopy = vfs_s_ungetlocalcopy;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init_subclass (struct vfs_s_subclass *sub, const char *name, vfs_flags_t flags,
+ const char *prefix)
+{
+ struct vfs_class *vclass = VFS_CLASS (sub);
+ size_t len;
+ char *start;
+
+ vfs_init_class (vclass, name, flags, prefix);
+
+ len = sizeof (struct vfs_s_subclass) - sizeof (struct vfs_class);
+ start = (char *) sub + sizeof (struct vfs_class);
+ memset (start, 0, len);
+
+ if ((vclass->flags & VFSF_USETMP) != 0)
+ sub->find_entry = vfs_s_find_entry_linear;
+ else if ((vclass->flags & VFSF_REMOTE) != 0)
+ sub->find_entry = vfs_s_find_entry_linear;
+ else
+ sub->find_entry = vfs_s_find_entry_tree;
+ sub->dir_uptodate = vfs_s_dir_uptodate;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Find VFS id for given directory name */
+
+vfsid
+vfs_getid (const vfs_path_t * vpath)
+{
+ const struct vfs_class *me;
+
+ me = vfs_path_get_last_path_vfs (vpath);
+ if (me == NULL || me->getid == NULL)
+ return NULL;
+
+ return me->getid (vpath);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* ----------- Utility functions for networked filesystems -------------- */
+
+#ifdef ENABLE_VFS_NET
+int
+vfs_s_select_on_two (int fd1, int fd2)
+{
+ fd_set set;
+ struct timeval time_out;
+ int v;
+ int maxfd = MAX (fd1, fd2) + 1;
+
+ time_out.tv_sec = 1;
+ time_out.tv_usec = 0;
+ FD_ZERO (&set);
+ FD_SET (fd1, &set);
+ FD_SET (fd2, &set);
+
+ v = select (maxfd, &set, 0, 0, &time_out);
+ if (v <= 0)
+ return v;
+ if (FD_ISSET (fd1, &set))
+ return 1;
+ if (FD_ISSET (fd2, &set))
+ return 2;
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_s_get_line (struct vfs_class *me, int sock, char *buf, int buf_len, char term)
+{
+ FILE *logfile = me->logfile;
+ int i;
+ char c;
+
+ for (i = 0; i < buf_len - 1; i++, buf++)
+ {
+ if (read (sock, buf, sizeof (char)) <= 0)
+ return 0;
+
+ if (logfile != NULL)
+ {
+ size_t ret1;
+ int ret2;
+
+ ret1 = fwrite (buf, 1, 1, logfile);
+ ret2 = fflush (logfile);
+ (void) ret1;
+ (void) ret2;
+ }
+
+ if (*buf == term)
+ {
+ *buf = '\0';
+ return 1;
+ }
+ }
+
+ /* Line is too long - terminate buffer and discard the rest of line */
+ *buf = '\0';
+ while (read (sock, &c, sizeof (c)) > 0)
+ {
+ if (logfile != NULL)
+ {
+ size_t ret1;
+ int ret2;
+
+ ret1 = fwrite (&c, 1, 1, logfile);
+ ret2 = fflush (logfile);
+ (void) ret1;
+ (void) ret2;
+ }
+ if (c == '\n')
+ return 1;
+ }
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_s_get_line_interruptible (struct vfs_class *me, char *buffer, int size, int fd)
+{
+ int i;
+ int res = 0;
+
+ (void) me;
+
+ tty_enable_interrupt_key ();
+
+ for (i = 0; i < size - 1; i++)
+ {
+ ssize_t n;
+
+ n = read (fd, &buffer[i], 1);
+ if (n == -1 && errno == EINTR)
+ {
+ buffer[i] = '\0';
+ res = EINTR;
+ goto ret;
+ }
+ if (n == 0)
+ {
+ buffer[i] = '\0';
+ goto ret;
+ }
+ if (buffer[i] == '\n')
+ {
+ buffer[i] = '\0';
+ res = 1;
+ goto ret;
+ }
+ }
+
+ buffer[size - 1] = '\0';
+
+ ret:
+ tty_disable_interrupt_key ();
+
+ return res;
+}
+#endif /* ENABLE_VFS_NET */
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Normalize filenames start position
+ */
+
+void
+vfs_s_normalize_filename_leading_spaces (struct vfs_s_inode *root_inode, size_t final_num_spaces)
+{
+ GList *iter;
+
+ for (iter = g_queue_peek_head_link (root_inode->subdir); iter != NULL;
+ iter = g_list_next (iter))
+ {
+ struct vfs_s_entry *entry = VFS_ENTRY (iter->data);
+
+ if ((size_t) entry->leading_spaces > final_num_spaces)
+ {
+ char *source_name, *spacer;
+
+ source_name = entry->name;
+ spacer = g_strnfill ((size_t) entry->leading_spaces - final_num_spaces, ' ');
+ entry->name = g_strconcat (spacer, source_name, (char *) NULL);
+ g_free (spacer);
+ g_free (source_name);
+ }
+
+ entry->leading_spaces = -1;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/vfs/gc.c b/lib/vfs/gc.c
new file mode 100644
index 0000000..0914b75
--- /dev/null
+++ b/lib/vfs/gc.c
@@ -0,0 +1,335 @@
+/*
+ Virtual File System garbage collection code
+
+ Copyright (C) 2003-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1995
+ Jakub Jelinek, 1995
+ Pavel Machek, 1998
+ Pavel Roskin, 2003
+
+ 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: garbage collection code
+ * \author Miguel de Icaza
+ * \author Jakub Jelinek
+ * \author Pavel Machek
+ * \author Pavel Roskin
+ * \date 1995, 1998, 2003
+ */
+
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/event.h"
+#include "lib/util.h" /* MC_PTR_FREE */
+
+#include "vfs.h"
+#include "utilvfs.h"
+
+#include "gc.h"
+
+/*
+ * The garbage collection mechanism is based on "stamps".
+ *
+ * A stamp is a record that says "I'm a filesystem which is no longer in
+ * use. Free me when you get a chance."
+ *
+ * This file contains a set of functions used for managing this stamp. You
+ * should use them when you write your own filesystem. Here are some rules
+ * of thumb:
+ *
+ * (1) When the last open file in your filesystem gets closed, conditionally
+ * create a stamp. You do this with vfs_stamp_create(). (The meaning
+ * of "conditionally" is explained below.)
+ *
+ * (2) When a file in your filesystem is opened, delete the stamp. You do
+ * this with vfs_rmstamp().
+ *
+ * (3) When a path inside your filesystem is invoked, call vfs_stamp() to
+ * postpone the free'ing of your filesystem a bit. (This simply updates
+ * a timestamp variable inside the stamp.)
+ *
+ * Additionally, when a user navigates to a new directory in a panel (or a
+ * programmer uses mc_chdir()), a stamp is conditionally created for the
+ * previous directory's filesystem. This ensures that that filesystem is
+ * free'ed. (see: _do_panel_cd() -> vfs_release_path(); mc_chdir()).
+ *
+ * We've spoken here of "conditionally creating" a stamp. What we mean is
+ * that vfs_stamp_create() is to be used: this function creates a stamp
+ * only if no directories are open (aka "active") in your filesystem. (If
+ * there _are_ directories open, it means that the filesystem is in use, in
+ * which case we don't want to free it.)
+ */
+
+/*** global variables ****************************************************************************/
+
+int vfs_timeout = 60; /* VFS timeout in seconds */
+
+/*** file scope macro definitions ****************************************************************/
+
+#define VFS_STAMPING(a) ((struct vfs_stamping *)(a))
+
+/*** file scope type declarations ****************************************************************/
+
+struct vfs_stamping
+{
+ struct vfs_class *v;
+ vfsid id;
+ gint64 time;
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static GSList *stamps = NULL;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gint
+vfs_stamp_compare (gconstpointer a, gconstpointer b)
+{
+ const struct vfs_stamping *vsa = (const struct vfs_stamping *) a;
+ const struct vfs_stamping *vsb = (const struct vfs_stamping *) b;
+
+ return (vsa == NULL || vsb == NULL || (vsa->v == vsb->v && vsa->id == vsb->id)) ? 0 : 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+vfs_addstamp (struct vfs_class *v, vfsid id)
+{
+ if ((v->flags & VFSF_LOCAL) == 0 && id != NULL && !vfs_stamp (v, id))
+ {
+ struct vfs_stamping *stamp;
+
+ stamp = g_new (struct vfs_stamping, 1);
+ stamp->v = v;
+ stamp->id = id;
+ stamp->time = g_get_monotonic_time ();
+
+ stamps = g_slist_append (stamps, stamp);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+vfs_stamp (struct vfs_class *v, vfsid id)
+{
+ struct vfs_stamping what = {
+ .v = v,
+ .id = id
+ };
+ GSList *stamp;
+ gboolean ret = FALSE;
+
+ stamp = g_slist_find_custom (stamps, &what, vfs_stamp_compare);
+ if (stamp != NULL && stamp->data != NULL)
+ {
+ VFS_STAMPING (stamp->data)->time = g_get_monotonic_time ();
+ ret = TRUE;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_rmstamp (struct vfs_class *v, vfsid id)
+{
+ struct vfs_stamping what = {
+ .v = v,
+ .id = id
+ };
+ GSList *stamp;
+
+ stamp = g_slist_find_custom (stamps, &what, vfs_stamp_compare);
+ if (stamp != NULL)
+ {
+ g_free (stamp->data);
+ stamps = g_slist_delete_link (stamps, stamp);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_stamp_path (const vfs_path_t * vpath)
+{
+ vfsid id;
+ struct vfs_class *me;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+ id = vfs_getid (vpath);
+ vfs_addstamp (me, id);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create a new timestamp item by VFS class and VFS id.
+ */
+
+void
+vfs_stamp_create (struct vfs_class *vclass, vfsid id)
+{
+ vfsid nvfsid;
+
+ ev_vfs_stamp_create_t event_data = { vclass, id, FALSE };
+ const vfs_path_t *vpath;
+ struct vfs_class *me;
+
+ /* There are three directories we have to take care of: current_dir,
+ current_panel->cwd and other_panel->cwd. Although most of the time either
+ current_dir and current_panel->cwd or current_dir and other_panel->cwd are the
+ same, it's possible that all three are different -- Norbert */
+
+ if (!mc_event_present (MCEVENT_GROUP_CORE, "vfs_timestamp"))
+ return;
+
+ vpath = vfs_get_raw_current_dir ();
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ nvfsid = vfs_getid (vpath);
+ vfs_rmstamp (me, nvfsid);
+
+ if (!(id == NULL || (me == vclass && nvfsid == id)))
+ {
+ mc_event_raise (MCEVENT_GROUP_CORE, "vfs_timestamp", (gpointer) & event_data);
+
+ if (!event_data.ret && vclass != NULL && vclass->nothingisopen != NULL
+ && vclass->nothingisopen (id))
+ vfs_addstamp (vclass, id);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** This is called from timeout handler with now = FALSE,
+ or can be called with now = TRUE to force freeing all filesystems */
+
+void
+vfs_expire (gboolean now)
+{
+ static gboolean locked = FALSE;
+ gint64 curr_time, exp_time;
+ GSList *stamp;
+
+ /* Avoid recursive invocation, e.g. when one of the free functions
+ calls message */
+ if (locked)
+ return;
+ locked = TRUE;
+
+ curr_time = g_get_monotonic_time ();
+ exp_time = curr_time - vfs_timeout * G_USEC_PER_SEC;
+
+ if (now)
+ {
+ /* reverse list to free nested VFSes at first */
+ stamps = g_slist_reverse (stamps);
+ }
+
+ /* NULLize stamps that point to expired VFS */
+ for (stamp = stamps; stamp != NULL; stamp = g_slist_next (stamp))
+ {
+ struct vfs_stamping *stamping = VFS_STAMPING (stamp->data);
+
+ if (now)
+ {
+ /* free VFS forced */
+ if (stamping->v->free != NULL)
+ stamping->v->free (stamping->id);
+ MC_PTR_FREE (stamp->data);
+ }
+ else if (stamping->time <= exp_time)
+ {
+ /* update timestamp of VFS that is in use, or free unused VFS */
+ if (stamping->v->nothingisopen != NULL && !stamping->v->nothingisopen (stamping->id))
+ stamping->time = curr_time;
+ else
+ {
+ if (stamping->v->free != NULL)
+ stamping->v->free (stamping->id);
+ MC_PTR_FREE (stamp->data);
+ }
+ }
+ }
+
+ /* then remove NULLized stamps */
+ stamps = g_slist_remove_all (stamps, NULL);
+
+ locked = FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Return the number of seconds remaining to the vfs timeout.
+ * FIXME: The code should be improved to actually return the number of
+ * seconds until the next item times out.
+ */
+
+int
+vfs_timeouts (void)
+{
+ return stamps != NULL ? 10 : 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_timeout_handler (void)
+{
+ vfs_expire (FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_release_path (const vfs_path_t * vpath)
+{
+ vfsid id;
+ struct vfs_class *me;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+ id = vfs_getid (vpath);
+ vfs_stamp_create (me, id);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Free all data */
+
+void
+vfs_gc_done (void)
+{
+ vfs_expire (TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/vfs/gc.h b/lib/vfs/gc.h
new file mode 100644
index 0000000..59fa5ec
--- /dev/null
+++ b/lib/vfs/gc.h
@@ -0,0 +1,27 @@
+/**
+ * \file
+ * \brief Header: Virtual File System: garbage collection code
+ */
+
+#ifndef MC__VFS_GC_H
+#define MC__VFS_GC_H
+
+#include "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 ************************************************************/
+
+gboolean vfs_stamp (struct vfs_class *vclass, vfsid id);
+void vfs_rmstamp (struct vfs_class *vclass, vfsid id);
+void vfs_stamp_create (struct vfs_class *vclass, vfsid id);
+void vfs_gc_done (void);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC_VFS_GC_H */
diff --git a/lib/vfs/interface.c b/lib/vfs/interface.c
new file mode 100644
index 0000000..1b2de26
--- /dev/null
+++ b/lib/vfs/interface.c
@@ -0,0 +1,875 @@
+/*
+ Virtual File System: interface functions
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2011, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2011-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: path handlers
+ * \author Slava Zanko
+ * \date 2011
+ */
+
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h> /* For atol() */
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <ctype.h> /* is_digit() */
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include "lib/global.h"
+
+#include "lib/widget.h" /* message() */
+#include "lib/strutil.h" /* str_crt_conv_from() */
+#include "lib/util.h"
+
+#include "vfs.h"
+#include "utilvfs.h"
+#include "path.h"
+#include "gc.h"
+#include "xdirentry.h"
+
+/* TODO: move it to separate private .h */
+extern GString *vfs_str_buffer;
+extern vfs_class *current_vfs;
+extern struct vfs_dirent *mc_readdir_result;
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_path_t *
+mc_def_getlocalcopy (const vfs_path_t * filename_vpath)
+{
+ vfs_path_t *tmp_vpath = NULL;
+ int fdin, fdout = -1;
+ ssize_t i;
+ char buffer[BUF_1K * 8];
+ struct stat mystat;
+
+ fdin = mc_open (filename_vpath, O_RDONLY | O_LINEAR);
+ if (fdin == -1)
+ goto fail;
+
+ fdout = vfs_mkstemps (&tmp_vpath, "vfs", vfs_path_get_last_path_str (filename_vpath));
+ if (fdout == -1)
+ goto fail;
+
+ while ((i = mc_read (fdin, buffer, sizeof (buffer))) > 0)
+ {
+ if (write (fdout, buffer, i) != i)
+ goto fail;
+ }
+ if (i == -1)
+ goto fail;
+ i = mc_close (fdin);
+ fdin = -1;
+ if (i == -1)
+ goto fail;
+
+ i = close (fdout);
+ fdout = -1;
+ if (i == -1)
+ goto fail;
+
+ if (mc_stat (filename_vpath, &mystat) != -1)
+ mc_chmod (tmp_vpath, mystat.st_mode);
+
+ return tmp_vpath;
+
+ fail:
+ vfs_path_free (tmp_vpath, TRUE);
+ if (fdout != -1)
+ close (fdout);
+ if (fdin != -1)
+ mc_close (fdin);
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+mc_def_ungetlocalcopy (const vfs_path_t * filename_vpath,
+ const vfs_path_t * local_vpath, gboolean has_changed)
+{
+ int fdin = -1, fdout = -1;
+ const char *local;
+
+ local = vfs_path_get_last_path_str (local_vpath);
+
+ if (has_changed)
+ {
+ char buffer[BUF_1K * 8];
+ ssize_t i;
+
+ if (vfs_path_get_last_path_vfs (filename_vpath)->write == NULL)
+ goto failed;
+
+ fdin = open (local, O_RDONLY);
+ if (fdin == -1)
+ goto failed;
+ fdout = mc_open (filename_vpath, O_WRONLY | O_TRUNC);
+ if (fdout == -1)
+ goto failed;
+ while ((i = read (fdin, buffer, sizeof (buffer))) > 0)
+ if (mc_write (fdout, buffer, (size_t) i) != i)
+ goto failed;
+ if (i == -1)
+ goto failed;
+
+ if (close (fdin) == -1)
+ {
+ fdin = -1;
+ goto failed;
+ }
+ fdin = -1;
+ if (mc_close (fdout) == -1)
+ {
+ fdout = -1;
+ goto failed;
+ }
+ }
+ unlink (local);
+ return 0;
+
+ failed:
+ message (D_ERROR, _("Changes to file lost"), "%s", vfs_path_get_last_path_str (filename_vpath));
+ if (fdout != -1)
+ mc_close (fdout);
+ if (fdin != -1)
+ close (fdin);
+ unlink (local);
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mc_open (const vfs_path_t * vpath, int flags, ...)
+{
+ int result = -1;
+ mode_t mode = 0;
+ struct vfs_class *me;
+
+ if (vpath == NULL)
+ return (-1);
+
+ /* Get the mode flag */
+ if ((flags & O_CREAT) != 0)
+ {
+ va_list ap;
+
+ va_start (ap, flags);
+ /* We have to use PROMOTED_MODE_T instead of mode_t. Doing 'va_arg (ap, mode_t)'
+ * fails on systems where 'mode_t' is smaller than 'int' because of C's "default
+ * argument promotions". */
+ mode = va_arg (ap, PROMOTED_MODE_T);
+ va_end (ap);
+ }
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+ if (me != NULL && me->open != NULL)
+ {
+ void *info;
+
+ /* open must be supported */
+ info = me->open (vpath, flags, mode);
+ if (info == NULL)
+ errno = vfs_ferrno (me);
+ else
+ result = vfs_new_handle (me, info);
+ }
+ else
+ errno = ENOTSUP;
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* *INDENT-OFF* */
+
+#define MC_NAMEOP(name, inarg, callarg) \
+int mc_##name inarg \
+{ \
+ int result; \
+ struct vfs_class *me; \
+\
+ if (vpath == NULL) \
+ return (-1); \
+\
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath)); \
+ if (me == NULL) \
+ return (-1); \
+\
+ result = me->name != NULL ? me->name callarg : -1; \
+ if (result == -1) \
+ errno = me->name != NULL ? vfs_ferrno (me) : ENOTSUP; \
+ return result; \
+}
+
+MC_NAMEOP (chmod, (const vfs_path_t *vpath, mode_t mode), (vpath, mode))
+MC_NAMEOP (chown, (const vfs_path_t *vpath, uid_t owner, gid_t group), (vpath, owner, group))
+MC_NAMEOP (fgetflags, (const vfs_path_t *vpath, unsigned long *flags), (vpath, flags))
+MC_NAMEOP (fsetflags, (const vfs_path_t *vpath, unsigned long flags), (vpath, flags))
+MC_NAMEOP (utime, (const vfs_path_t *vpath, mc_timesbuf_t * times), (vpath, times))
+MC_NAMEOP (readlink, (const vfs_path_t *vpath, char *buf, size_t bufsiz), (vpath, buf, bufsiz))
+MC_NAMEOP (unlink, (const vfs_path_t *vpath), (vpath))
+MC_NAMEOP (mkdir, (const vfs_path_t *vpath, mode_t mode), (vpath, mode))
+MC_NAMEOP (rmdir, (const vfs_path_t *vpath), (vpath))
+MC_NAMEOP (mknod, (const vfs_path_t *vpath, mode_t mode, dev_t dev), (vpath, mode, dev))
+
+/* *INDENT-ON* */
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mc_symlink (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ int result = -1;
+
+ if (vpath1 != NULL && vpath2 != NULL)
+ {
+ struct vfs_class *me;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath2));
+ if (me != NULL)
+ {
+ result = me->symlink != NULL ? me->symlink (vpath1, vpath2) : -1;
+ if (result == -1)
+ errno = me->symlink != NULL ? vfs_ferrno (me) : ENOTSUP;
+ }
+ }
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* *INDENT-OFF* */
+
+#define MC_HANDLEOP(rettype, name, inarg, callarg) \
+rettype mc_##name inarg \
+{ \
+ struct vfs_class *vfs; \
+ void *fsinfo = NULL; \
+ rettype result; \
+\
+ if (handle == -1) \
+ return (-1); \
+\
+ vfs = vfs_class_find_by_handle (handle, &fsinfo); \
+ if (vfs == NULL) \
+ return (-1); \
+\
+ result = vfs->name != NULL ? vfs->name callarg : -1; \
+ if (result == -1) \
+ errno = vfs->name != NULL ? vfs_ferrno (vfs) : ENOTSUP; \
+ return result; \
+}
+
+MC_HANDLEOP (ssize_t, read, (int handle, void *buf, size_t count), (fsinfo, buf, count))
+MC_HANDLEOP (ssize_t, write, (int handle, const void *buf, size_t count), (fsinfo, buf, count))
+MC_HANDLEOP (int, fstat, (int handle, struct stat *buf), (fsinfo, buf))
+
+/* --------------------------------------------------------------------------------------------- */
+
+#define MC_RENAMEOP(name) \
+int mc_##name (const vfs_path_t *vpath1, const vfs_path_t *vpath2) \
+{ \
+ int result; \
+ struct vfs_class *me1, *me2; \
+\
+ if (vpath1 == NULL || vpath2 == NULL) \
+ return (-1); \
+\
+ me1 = VFS_CLASS (vfs_path_get_last_path_vfs (vpath1)); \
+ me2 = VFS_CLASS (vfs_path_get_last_path_vfs (vpath2)); \
+\
+ if (me1 == NULL || me2 == NULL || me1 != me2) \
+ { \
+ errno = EXDEV; \
+ return (-1); \
+ } \
+\
+ result = me1->name != NULL ? me1->name (vpath1, vpath2) : -1; \
+ if (result == -1) \
+ errno = me1->name != NULL ? vfs_ferrno (me1) : ENOTSUP; \
+ return result; \
+}
+
+MC_RENAMEOP (link)
+MC_RENAMEOP (rename)
+
+/* *INDENT-ON* */
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mc_ctl (int handle, int ctlop, void *arg)
+{
+ struct vfs_class *vfs;
+ void *fsinfo = NULL;
+
+ vfs = vfs_class_find_by_handle (handle, &fsinfo);
+
+ return (vfs == NULL || vfs->ctl == NULL) ? 0 : vfs->ctl (fsinfo, ctlop, arg);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mc_setctl (const vfs_path_t * vpath, int ctlop, void *arg)
+{
+ int result = -1;
+ struct vfs_class *me;
+
+ if (vpath == NULL)
+ vfs_die ("You don't want to pass NULL to mc_setctl.");
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+ if (me != NULL)
+ result = me->setctl != NULL ? me->setctl (vpath, ctlop, arg) : 0;
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mc_close (int handle)
+{
+ struct vfs_class *vfs;
+ void *fsinfo = NULL;
+ int result;
+
+ if (handle == -1)
+ return (-1);
+
+ vfs = vfs_class_find_by_handle (handle, &fsinfo);
+ if (vfs == NULL || fsinfo == NULL)
+ return (-1);
+
+ if (handle < 3)
+ return close (handle);
+
+ if (vfs->close == NULL)
+ vfs_die ("VFS must support close.\n");
+ result = vfs->close (fsinfo);
+ vfs_free_handle (handle);
+ if (result == -1)
+ errno = vfs_ferrno (vfs);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+DIR *
+mc_opendir (const vfs_path_t * vpath)
+{
+ int handle, *handlep;
+ void *info;
+ vfs_path_element_t *path_element;
+
+ if (vpath == NULL)
+ return NULL;
+
+ path_element = (vfs_path_element_t *) vfs_path_get_by_index (vpath, -1);
+ if (!vfs_path_element_valid (path_element))
+ {
+ errno = ENOTSUP;
+ return NULL;
+ }
+
+ info = path_element->class->opendir ? path_element->class->opendir (vpath) : NULL;
+ if (info == NULL)
+ {
+ errno = path_element->class->opendir ? vfs_ferrno (path_element->class) : ENOTSUP;
+ return NULL;
+ }
+
+ path_element->dir.info = info;
+
+#ifdef HAVE_CHARSET
+ path_element->dir.converter = (path_element->encoding != NULL) ?
+ str_crt_conv_from (path_element->encoding) : str_cnv_from_term;
+ if (path_element->dir.converter == INVALID_CONV)
+ path_element->dir.converter = str_cnv_from_term;
+#endif
+
+ handle = vfs_new_handle (path_element->class, vfs_path_element_clone (path_element));
+
+ handlep = g_new (int, 1);
+ *handlep = handle;
+ return (DIR *) handlep;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+struct vfs_dirent *
+mc_readdir (DIR * dirp)
+{
+ int handle;
+ struct vfs_class *vfs;
+ void *fsinfo = NULL;
+ struct vfs_dirent *entry = NULL;
+ vfs_path_element_t *vfs_path_element;
+
+ if (dirp == NULL)
+ {
+ errno = EFAULT;
+ return NULL;
+ }
+
+ handle = *(int *) dirp;
+
+ vfs = vfs_class_find_by_handle (handle, &fsinfo);
+ if (vfs == NULL || fsinfo == NULL)
+ return NULL;
+
+ vfs_path_element = (vfs_path_element_t *) fsinfo;
+ if (vfs->readdir != NULL)
+ {
+ entry = vfs->readdir (vfs_path_element->dir.info);
+ if (entry == NULL)
+ return NULL;
+
+ g_string_set_size (vfs_str_buffer, 0);
+#ifdef HAVE_CHARSET
+ str_vfs_convert_from (vfs_path_element->dir.converter, entry->d_name, vfs_str_buffer);
+#else
+ g_string_assign (vfs_str_buffer, entry->d_name);
+#endif
+ vfs_dirent_assign (mc_readdir_result, vfs_str_buffer->str, entry->d_ino);
+ vfs_dirent_free (entry);
+ }
+ if (entry == NULL)
+ errno = vfs->readdir ? vfs_ferrno (vfs) : ENOTSUP;
+ return (entry != NULL) ? mc_readdir_result : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mc_closedir (DIR * dirp)
+{
+ int handle;
+ struct vfs_class *vfs;
+ void *fsinfo = NULL;
+ int result = -1;
+
+ if (dirp == NULL)
+ return result;
+
+ handle = *(int *) dirp;
+
+ vfs = vfs_class_find_by_handle (handle, &fsinfo);
+ if (vfs != NULL && fsinfo != NULL)
+ {
+ vfs_path_element_t *vfs_path_element = (vfs_path_element_t *) fsinfo;
+
+#ifdef HAVE_CHARSET
+ if (vfs_path_element->dir.converter != str_cnv_from_term)
+ {
+ str_close_conv (vfs_path_element->dir.converter);
+ vfs_path_element->dir.converter = INVALID_CONV;
+ }
+#endif
+
+ result = vfs->closedir ? (*vfs->closedir) (vfs_path_element->dir.info) : -1;
+ vfs_free_handle (handle);
+ vfs_path_element_free (vfs_path_element);
+ }
+ g_free (dirp);
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* *INDENT-OFF* */
+
+#define MC_STATOP(name) \
+int mc_##name (const vfs_path_t *vpath, struct stat *buf) \
+{ \
+ int result = -1; \
+ struct vfs_class *me; \
+\
+ if (vpath == NULL) \
+ return (-1); \
+\
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath)); \
+ if (me != NULL) \
+ { \
+ result = me->name ? me->name (vpath, buf) : -1; \
+ if (result == -1) \
+ errno = me->name ? vfs_ferrno (me) : ENOTSUP; \
+ } \
+\
+ return result; \
+}
+
+MC_STATOP (stat)
+MC_STATOP (lstat)
+
+/* --------------------------------------------------------------------------------------------- */
+
+vfs_path_t *
+mc_getlocalcopy (const vfs_path_t * pathname_vpath)
+{
+ vfs_path_t *result = NULL;
+ struct vfs_class *me;
+
+ if (pathname_vpath == NULL)
+ return NULL;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (pathname_vpath));
+ if (me != NULL)
+ {
+ result = me->getlocalcopy != NULL ?
+ me->getlocalcopy (pathname_vpath) : mc_def_getlocalcopy (pathname_vpath);
+ if (result == NULL)
+ errno = vfs_ferrno (me);
+ }
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mc_ungetlocalcopy (const vfs_path_t * pathname_vpath, const vfs_path_t * local_vpath,
+ gboolean has_changed)
+{
+ int result = -1;
+ const struct vfs_class *me;
+
+ if (pathname_vpath == NULL)
+ return (-1);
+
+ me = vfs_path_get_last_path_vfs (pathname_vpath);
+ if (me != NULL)
+ result = me->ungetlocalcopy != NULL ?
+ me->ungetlocalcopy (pathname_vpath, local_vpath, has_changed) :
+ mc_def_ungetlocalcopy (pathname_vpath, local_vpath, has_changed);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * VFS chdir.
+ *
+ * @param vpath VFS path.
+ * May be NULL. In this case NULL is returned and errno set to 0.
+ *
+ * @return 0 on success, -1 on failure.
+ */
+
+int
+mc_chdir (const vfs_path_t * vpath)
+{
+ struct vfs_class *old_vfs;
+ vfsid old_vfsid;
+ int result;
+ struct vfs_class *me;
+ const vfs_path_element_t *path_element;
+ vfs_path_t *cd_vpath;
+
+ if (vpath == NULL)
+ {
+ errno = 0;
+ return (-1);
+ }
+
+ if (vpath->relative)
+ cd_vpath = vfs_path_to_absolute (vpath);
+ else
+ cd_vpath = vfs_path_clone (vpath);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (cd_vpath));
+ if (me == NULL)
+ {
+ errno = EINVAL;
+ goto error_end;
+ }
+
+ if (me->chdir == NULL)
+ {
+ errno = ENOTSUP;
+ goto error_end;
+ }
+
+ result = me->chdir (cd_vpath);
+ if (result == -1)
+ {
+ errno = vfs_ferrno (me);
+ goto error_end;
+ }
+
+ old_vfsid = vfs_getid (vfs_get_raw_current_dir ());
+ old_vfs = current_vfs;
+
+ /* Actually change directory */
+ vfs_set_raw_current_dir (cd_vpath);
+ current_vfs = me;
+
+ /* This function uses the new current_dir implicitly */
+ vfs_stamp_create (old_vfs, old_vfsid);
+
+ /* Sometimes we assume no trailing slash on cwd */
+ path_element = vfs_path_get_by_index (vfs_get_raw_current_dir (), -1);
+ if (vfs_path_element_valid (path_element))
+ {
+ if (*path_element->path != '\0')
+ {
+ char *p;
+
+ p = strchr (path_element->path, 0) - 1;
+ if (IS_PATH_SEP (*p) && p > path_element->path)
+ *p = '\0';
+ }
+
+#ifdef ENABLE_VFS_NET
+ {
+ struct vfs_s_super *super;
+
+ super = vfs_get_super_by_vpath (vpath);
+ if (super != NULL && super->path_element != NULL)
+ {
+ g_free (super->path_element->path);
+ super->path_element->path = g_strdup (path_element->path);
+ }
+ }
+#endif /* ENABLE_VFS_NET */
+ }
+
+ return 0;
+
+ error_end:
+ vfs_path_free (cd_vpath, TRUE);
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+off_t
+mc_lseek (int fd, off_t offset, int whence)
+{
+ struct vfs_class *vfs;
+ void *fsinfo = NULL;
+ off_t result;
+
+ if (fd == -1)
+ return (-1);
+
+ vfs = vfs_class_find_by_handle (fd, &fsinfo);
+ if (vfs == NULL)
+ return (-1);
+
+ result = vfs->lseek ? vfs->lseek (fsinfo, offset, whence) : -1;
+ if (result == -1)
+ errno = vfs->lseek ? vfs_ferrno (vfs) : ENOTSUP;
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Following code heavily borrows from libiberty, mkstemps.c */
+/*
+ * Arguments:
+ * pname (output) - pointer to the name of the temp file (needs g_free).
+ * NULL if the function fails.
+ * prefix - part of the filename before the random part.
+ * Prepend $TMPDIR or /tmp if there are no path separators.
+ * suffix - if not NULL, part of the filename after the random part.
+ *
+ * Result:
+ * handle of the open file or -1 if couldn't open any.
+ */
+
+int
+mc_mkstemps (vfs_path_t ** pname_vpath, const char *prefix, const char *suffix)
+{
+ char *p1, *p2;
+ int fd;
+
+ if (strchr (prefix, PATH_SEP) != NULL)
+ p1 = g_strdup (prefix);
+ else
+ {
+ /* Add prefix first to find the position of XXXXXX */
+ p1 = g_build_filename (mc_tmpdir (), prefix, (char *) NULL);
+ }
+
+ p2 = g_strconcat (p1, "XXXXXX", suffix, (char *) NULL);
+ g_free (p1);
+
+ fd = g_mkstemp (p2);
+ if (fd >= 0)
+ *pname_vpath = vfs_path_from_str (p2);
+ else
+ {
+ *pname_vpath = NULL;
+ fd = -1;
+ }
+
+ g_free (p2);
+
+ return fd;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Return the directory where mc should keep its temporary files.
+ * This directory is (in Bourne shell terms) "${TMPDIR=/tmp}/mc-$USER"
+ * When called the first time, the directory is created if needed.
+ * The first call should be done early, since we are using fprintf()
+ * and not message() to report possible problems.
+ */
+
+const char *
+mc_tmpdir (void)
+{
+ static char buffer[PATH_MAX];
+ static const char *tmpdir = NULL;
+ const char *sys_tmp;
+ struct passwd *pwd;
+ struct stat st;
+ const char *error = NULL;
+
+ /* Check if already correctly initialized */
+ if (tmpdir != NULL && lstat (tmpdir, &st) == 0 && S_ISDIR (st.st_mode) &&
+ st.st_uid == getuid () && (st.st_mode & 0777) == 0700)
+ return tmpdir;
+
+ sys_tmp = getenv ("MC_TMPDIR");
+ if (sys_tmp == NULL || !IS_PATH_SEP (sys_tmp[0]))
+ {
+ sys_tmp = getenv ("TMPDIR");
+ if (sys_tmp == NULL || !IS_PATH_SEP (sys_tmp[0]))
+ sys_tmp = TMPDIR_DEFAULT;
+ }
+
+ pwd = getpwuid (getuid ());
+ if (pwd != NULL)
+ g_snprintf (buffer, sizeof (buffer), "%s/mc-%s", sys_tmp, pwd->pw_name);
+ else
+ g_snprintf (buffer, sizeof (buffer), "%s/mc-%lu", sys_tmp, (unsigned long) getuid ());
+
+ canonicalize_pathname (buffer);
+
+ /* Try to create directory */
+ if (mkdir (buffer, S_IRWXU) != 0)
+ {
+ if (errno == EEXIST && lstat (buffer, &st) == 0)
+ {
+ /* Sanity check for existing directory */
+ if (!S_ISDIR (st.st_mode))
+ error = _("%s is not a directory\n");
+ else if (st.st_uid != getuid ())
+ error = _("Directory %s is not owned by you\n");
+ else if (((st.st_mode & 0777) != 0700) && (chmod (buffer, 0700) != 0))
+ error = _("Cannot set correct permissions for directory %s\n");
+ }
+ else
+ {
+ fprintf (stderr,
+ _("Cannot create temporary directory %s: %s\n"),
+ buffer, unix_error_string (errno));
+ error = "";
+ }
+ }
+
+ if (error != NULL)
+ {
+ int test_fd;
+ char *fallback_prefix;
+ gboolean fallback_ok = FALSE;
+ vfs_path_t *test_vpath;
+
+ if (*error != '\0')
+ fprintf (stderr, error, buffer);
+
+ /* Test if sys_tmp is suitable for temporary files */
+ fallback_prefix = g_strdup_printf ("%s/mctest", sys_tmp);
+ test_fd = mc_mkstemps (&test_vpath, fallback_prefix, NULL);
+ g_free (fallback_prefix);
+ if (test_fd != -1)
+ {
+ close (test_fd);
+ test_fd = open (vfs_path_as_str (test_vpath), O_RDONLY);
+ if (test_fd != -1)
+ {
+ close (test_fd);
+ unlink (vfs_path_as_str (test_vpath));
+ fallback_ok = TRUE;
+ }
+ }
+
+ if (fallback_ok)
+ {
+ fprintf (stderr, _("Temporary files will be created in %s\n"), sys_tmp);
+ g_snprintf (buffer, sizeof (buffer), "%s", sys_tmp);
+ error = NULL;
+ }
+ else
+ {
+ fprintf (stderr, _("Temporary files will not be created\n"));
+ g_snprintf (buffer, sizeof (buffer), "%s", "/dev/null/");
+ }
+
+ vfs_path_free (test_vpath, TRUE);
+ fprintf (stderr, "%s\n", _("Press any key to continue..."));
+ getc (stdin);
+ }
+
+ tmpdir = buffer;
+
+ if (error == NULL)
+ g_setenv ("MC_TMPDIR", tmpdir, TRUE);
+
+ return tmpdir;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/vfs/netutil.c b/lib/vfs/netutil.c
new file mode 100644
index 0000000..1306879
--- /dev/null
+++ b/lib/vfs/netutil.c
@@ -0,0 +1,83 @@
+/*
+ Network utilities for the Midnight Commander Virtual 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: Virtual File System: Network utilities
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h> /* memset() */
+
+#include "lib/global.h"
+
+#include "netutil.h"
+
+/*** global variables ****************************************************************************/
+
+SIG_ATOMIC_VOLATILE_T got_sigpipe = 0;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+sig_pipe (int unused)
+{
+ (void) unused;
+ got_sigpipe = 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tcp_init (void)
+{
+ static gboolean initialized = FALSE;
+ struct sigaction sa;
+
+ if (initialized)
+ return;
+
+ got_sigpipe = 0;
+ memset (&sa, 0, sizeof (sa));
+ sa.sa_handler = sig_pipe;
+ sigemptyset (&sa.sa_mask);
+ sigaction (SIGPIPE, &sa, NULL);
+
+ initialized = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/vfs/netutil.h b/lib/vfs/netutil.h
new file mode 100644
index 0000000..9a12745
--- /dev/null
+++ b/lib/vfs/netutil.h
@@ -0,0 +1,26 @@
+
+/**
+ * \file
+ * \brief Header: Virtual File System: Network utilities
+ */
+
+#ifndef MC__VFS_NETUTIL_H
+#define MC__VFS_NETUTIL_H
+
+#include <signal.h>
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+extern SIG_ATOMIC_VOLATILE_T got_sigpipe;
+
+/*** declarations of public functions ************************************************************/
+
+void tcp_init (void);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC_VFS_NETUTIL_H */
diff --git a/lib/vfs/parse_ls_vga.c b/lib/vfs/parse_ls_vga.c
new file mode 100644
index 0000000..779792f
--- /dev/null
+++ b/lib/vfs/parse_ls_vga.c
@@ -0,0 +1,886 @@
+/*
+ Routines for parsing output from the 'ls' command.
+
+ Copyright (C) 1988-2023
+ Free Software Foundation, Inc.
+
+ Copyright (C) 1995, 1996 Miguel de Icaza
+
+ Written by:
+ Miguel de Icaza, 1995, 1996
+ 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 Source: Utilities for VFS modules
+ * \author Miguel de Icaza
+ * \date 1995, 1996
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/unixcompat.h" /* makedev */
+#include "lib/widget.h" /* message() */
+
+#include "utilvfs.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/* Parsing code is used by ftpfs, fish and extfs */
+#define MAXCOLS 30
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static char *columns[MAXCOLS]; /* Points to the string in column n */
+static int column_ptr[MAXCOLS]; /* Index from 0 to the starting positions of the columns */
+static size_t vfs_parce_ls_final_num_spaces = 0;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_num (int idx)
+{
+ char *column = columns[idx];
+
+ return (column != NULL && isdigit (column[0]));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Return TRUE for MM-DD-YY and MM-DD-YYYY */
+
+static gboolean
+is_dos_date (const char *str)
+{
+ size_t len;
+
+ if (str == NULL)
+ return FALSE;
+
+ len = strlen (str);
+ if (len != 8 && len != 10)
+ return FALSE;
+
+ if (str[2] != str[5])
+ return FALSE;
+
+ return (strchr ("\\-/", (int) str[2]) != NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_week (const char *str, struct tm *tim)
+{
+ static const char *week = "SunMonTueWedThuFriSat";
+ const char *pos;
+
+ if (str == NULL)
+ return FALSE;
+
+ pos = strstr (week, str);
+ if (pos == NULL)
+ return FALSE;
+
+ if (tim != NULL)
+ tim->tm_wday = (pos - week) / 3;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check for possible locale's abbreviated month name (Jan..Dec).
+ * Any 3 bytes long string without digit, control and punctuation characters.
+ * isalpha() is locale specific, so it cannot be used if current
+ * locale is "C" and ftp server use Cyrillic.
+ * NB: It is assumed there are no whitespaces in month.
+ */
+static gboolean
+is_localized_month (const char *month)
+{
+ int i;
+
+ if (month == NULL)
+ return FALSE;
+
+ for (i = 0;
+ i < 3 && *month != '\0' && !isdigit ((unsigned char) *month)
+ && !iscntrl ((unsigned char) *month) && !ispunct ((unsigned char) *month); i++, month++)
+ ;
+
+ return (i == 3 && *month == '\0');
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_time (const char *str, struct tm *tim)
+{
+ const char *p, *p2;
+
+ if (str == NULL)
+ return FALSE;
+
+ p = strchr (str, ':');
+ if (p == NULL)
+ return FALSE;
+
+ p2 = strrchr (str, ':');
+ if (p2 == NULL)
+ return FALSE;
+
+ if (p != p2)
+ {
+ if (sscanf (str, "%2d:%2d:%2d", &tim->tm_hour, &tim->tm_min, &tim->tm_sec) != 3)
+ return FALSE;
+ }
+ else
+ {
+ if (sscanf (str, "%2d:%2d", &tim->tm_hour, &tim->tm_min) != 2)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_year (char *str, struct tm *tim)
+{
+ long year;
+
+ if (str == NULL)
+ return FALSE;
+
+ if (strchr (str, ':') != NULL)
+ return FALSE;
+
+ if (strlen (str) != 4)
+ return FALSE;
+
+ /* cppcheck-suppress invalidscanf */
+ if (sscanf (str, "%ld", &year) != 1)
+ return FALSE;
+
+ if (year < 1900 || year > 3000)
+ return FALSE;
+
+ tim->tm_year = (int) (year - 1900);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+vfs_parse_filetype (const char *s, size_t * ret_skipped, mode_t * ret_type)
+{
+ mode_t type;
+
+ switch (*s)
+ {
+ case 'd':
+ type = S_IFDIR;
+ break;
+ case 'b':
+ type = S_IFBLK;
+ break;
+ case 'c':
+ type = S_IFCHR;
+ break;
+ case 'l':
+ type = S_IFLNK;
+ break;
+#ifdef S_IFSOCK
+ case 's':
+ type = S_IFSOCK;
+ break;
+#else
+ case 's':
+ type = S_IFIFO;
+ break;
+#endif
+#ifdef S_IFDOOR /* Solaris door */
+ case 'D':
+ type = S_IFDOOR;
+ break;
+#else
+ case 'D':
+ type = S_IFIFO;
+ break;
+#endif
+ case 'p':
+ type = S_IFIFO;
+ break;
+#ifdef S_IFNAM /* Special named files */
+ case 'n':
+ type = S_IFNAM;
+ break;
+#else
+ case 'n':
+ type = S_IFREG;
+ break;
+#endif
+ case 'm': /* Don't know what these are :-) */
+ case '-':
+ case '?':
+ type = S_IFREG;
+ break;
+ default:
+ return FALSE;
+ }
+
+ *ret_type = type;
+
+ if (ret_skipped != NULL)
+ *ret_skipped = 1;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+vfs_parse_fileperms (const char *s, size_t * ret_skipped, mode_t * ret_perms)
+{
+ const char *p = s;
+ mode_t perms = 0;
+
+ switch (*p++)
+ {
+ case '-':
+ break;
+ case 'r':
+ perms |= S_IRUSR;
+ break;
+ default:
+ return FALSE;
+ }
+
+ switch (*p++)
+ {
+ case '-':
+ break;
+ case 'w':
+ perms |= S_IWUSR;
+ break;
+ default:
+ return FALSE;
+ }
+
+ switch (*p++)
+ {
+ case '-':
+ break;
+ case 'S':
+ perms |= S_ISUID;
+ break;
+ case 's':
+ perms |= S_IXUSR | S_ISUID;
+ break;
+ case 'x':
+ perms |= S_IXUSR;
+ break;
+ default:
+ return FALSE;
+ }
+
+ switch (*p++)
+ {
+ case '-':
+ break;
+ case 'r':
+ perms |= S_IRGRP;
+ break;
+ default:
+ return FALSE;
+ }
+
+ switch (*p++)
+ {
+ case '-':
+ break;
+ case 'w':
+ perms |= S_IWGRP;
+ break;
+ default:
+ return FALSE;
+ }
+
+ switch (*p++)
+ {
+ case '-':
+ break;
+ case 'S':
+ perms |= S_ISGID;
+ break;
+ case 'l':
+ perms |= S_ISGID;
+ break; /* found on Solaris */
+ case 's':
+ perms |= S_IXGRP | S_ISGID;
+ break;
+ case 'x':
+ perms |= S_IXGRP;
+ break;
+ default:
+ return FALSE;
+ }
+
+ switch (*p++)
+ {
+ case '-':
+ break;
+ case 'r':
+ perms |= S_IROTH;
+ break;
+ default:
+ return FALSE;
+ }
+
+ switch (*p++)
+ {
+ case '-':
+ break;
+ case 'w':
+ perms |= S_IWOTH;
+ break;
+ default:
+ return FALSE;
+ }
+
+ switch (*p++)
+ {
+ case '-':
+ break;
+ case 'T':
+ perms |= S_ISVTX;
+ break;
+ case 't':
+ perms |= S_IXOTH | S_ISVTX;
+ break;
+ case 'x':
+ perms |= S_IXOTH;
+ break;
+ default:
+ return FALSE;
+ }
+
+ if (*p == '+')
+ /* ACLs on Solaris, HP-UX and others */
+ p++;
+
+ if (ret_skipped != NULL)
+ *ret_skipped = p - s;
+ *ret_perms = perms;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+vfs_parse_filemode (const char *s, size_t * ret_skipped, mode_t * ret_mode)
+{
+ const char *p = s;
+ mode_t type, perms;
+ size_t skipped;
+
+ if (!vfs_parse_filetype (p, &skipped, &type))
+ return FALSE;
+
+ p += skipped;
+ if (!vfs_parse_fileperms (p, &skipped, &perms))
+ return FALSE;
+
+ p += skipped;
+ *ret_skipped = p - s;
+ *ret_mode = type | perms;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+vfs_parse_raw_filemode (const char *s, size_t * ret_skipped, mode_t * ret_mode)
+{
+ const char *p = s;
+ mode_t remote_type = 0, local_type, perms = 0;
+
+ /* isoctal */
+ for (; *p >= '0' && *p <= '7'; p++)
+ {
+ perms *= 010;
+ perms += (*p - '0');
+ }
+
+ if (*p++ != ' ')
+ return FALSE;
+
+ for (; *p >= '0' && *p <= '7'; p++)
+ {
+ remote_type *= 010;
+ remote_type += (*p - '0');
+ }
+
+ if (*p++ != ' ')
+ return FALSE;
+
+ /* generated with:
+ $ perl -e 'use Fcntl ":mode";
+ my @modes = (S_IFDIR, S_IFBLK, S_IFCHR, S_IFLNK, S_IFREG);
+ foreach $t (@modes) { printf ("%o\n", $t); };'
+ TODO: S_IFDOOR, S_IFIFO, S_IFSOCK (if supported by os)
+ (see vfs_parse_filetype)
+ */
+
+ switch (remote_type)
+ {
+ case 020000:
+ local_type = S_IFCHR;
+ break;
+ case 040000:
+ local_type = S_IFDIR;
+ break;
+ case 060000:
+ local_type = S_IFBLK;
+ break;
+ case 0120000:
+ local_type = S_IFLNK;
+ break;
+ case 0100000:
+ default: /* don't know what is it */
+ local_type = S_IFREG;
+ break;
+ }
+
+ *ret_skipped = p - s;
+ *ret_mode = local_type | perms;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+vfs_parse_month (const char *str, struct tm * tim)
+{
+ static const char *month = "JanFebMarAprMayJunJulAugSepOctNovDec";
+ const char *pos;
+
+ if (str == NULL)
+ return FALSE;
+
+ pos = strstr (month, str);
+ if (pos == NULL)
+ return FALSE;
+
+ if (tim != NULL)
+ tim->tm_mon = (pos - month) / 3;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** This function parses from idx in the columns[] array */
+
+int
+vfs_parse_filedate (int idx, time_t * t)
+{
+ char *p;
+ struct tm tim;
+ int d[3];
+ gboolean got_year = FALSE;
+ gboolean l10n = FALSE; /* Locale's abbreviated month name */
+ time_t current_time;
+ struct tm *local_time;
+
+ /* Let's setup default time values */
+ current_time = time (NULL);
+ local_time = localtime (&current_time);
+ tim.tm_mday = local_time->tm_mday;
+ tim.tm_mon = local_time->tm_mon;
+ tim.tm_year = local_time->tm_year;
+
+ tim.tm_hour = 0;
+ tim.tm_min = 0;
+ tim.tm_sec = 0;
+ tim.tm_isdst = -1; /* Let mktime() try to guess correct dst offset */
+
+ p = columns[idx++];
+
+ /* We eat weekday name in case of extfs */
+ if (is_week (p, &tim))
+ p = columns[idx++];
+
+ /*
+ ALLOWED DATE FORMATS
+
+ We expect 3 fields max or we'll see oddities with certain file names.
+
+ Formats that contain either year or time (the default 'ls' formats):
+
+ * Mon DD hh:mm[:ss]
+ * Mon DD YYYY
+
+ Formats that contain both year and time, to make it easier to write
+ extfs scripts:
+
+ * MM-DD-YYYY hh:mm[:ss]
+ * MM-DD-YY hh:mm[:ss]
+
+ ('/' and '\' can be used instead of '-'.)
+
+ where Mon is Jan-Dec, DD, MM, YY two digit day, month, year,
+ YYYY four digit year, hh, mm, ss two digit hour, minute or second.
+
+ (As for the "3 fields max" restriction: this prevents, for example, a
+ file name "13:48" from being considered part of a "Sep 19 2016" date
+ string preceding it.)
+ */
+
+ /* Month name */
+ if (vfs_parse_month (p, &tim))
+ {
+ /* And we expect, it followed by day number */
+ if (!is_num (idx))
+ return 0; /* No day */
+
+ tim.tm_mday = (int) atol (columns[idx++]);
+
+ }
+ else if (is_dos_date (p))
+ {
+ /* Case with MM-DD-YY or MM-DD-YYYY */
+ p[2] = p[5] = '-';
+
+ /* cppcheck-suppress invalidscanf */
+ if (sscanf (p, "%2d-%2d-%d", &d[0], &d[1], &d[2]) != 3)
+ return 0; /* sscanf failed */
+
+ /* Months are zero based */
+ if (d[0] > 0)
+ d[0]--;
+
+ if (d[2] > 1900)
+ d[2] -= 1900;
+ else if (d[2] < 70)
+ /* Y2K madness */
+ d[2] += 100;
+
+ tim.tm_mon = d[0];
+ tim.tm_mday = d[1];
+ tim.tm_year = d[2];
+ got_year = TRUE;
+ }
+ else if (is_localized_month (p) && is_num (idx++))
+ /* Locale's abbreviated month name followed by day number */
+ l10n = TRUE;
+ else
+ return 0; /* unsupported format */
+
+ /* Here we expect to find time or year */
+ if (!is_num (idx)
+ || !(is_time (columns[idx], &tim) || (got_year = is_year (columns[idx], &tim))))
+ return 0; /* Neither time nor date */
+
+ idx++;
+
+ /*
+ * If the date is less than 6 months in the past, it is shown without year
+ * other dates in the past or future are shown with year but without time
+ * This does not check for years before 1900 ... I don't know, how
+ * to represent them at all
+ */
+ if (!got_year && local_time->tm_mon < 6 && local_time->tm_mon < tim.tm_mon
+ && tim.tm_mon - local_time->tm_mon >= 6)
+ tim.tm_year--;
+
+ *t = mktime (&tim);
+ if (l10n || (*t < 0))
+ *t = 0;
+
+ return idx;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_split_text (char *p)
+{
+ char *original = p;
+ int numcols;
+
+ memset (columns, 0, sizeof (columns));
+
+ for (numcols = 0; *p != '\0' && numcols < MAXCOLS; numcols++)
+ {
+ for (; *p == ' ' || *p == '\r' || *p == '\n'; p++)
+ *p = '\0';
+
+ columns[numcols] = p;
+ column_ptr[numcols] = p - original;
+
+ for (; *p != '\0' && *p != ' ' && *p != '\r' && *p != '\n'; p++)
+ ;
+ }
+
+ return numcols;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_parse_ls_lga_init (void)
+{
+ vfs_parce_ls_final_num_spaces = 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+size_t
+vfs_parse_ls_lga_get_final_spaces (void)
+{
+ return vfs_parce_ls_final_num_spaces;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+vfs_parse_ls_lga (const char *p, struct stat * s, char **filename, char **linkname,
+ size_t * num_spaces)
+{
+ int idx, idx2, num_cols;
+ int i;
+ char *p_copy = NULL;
+ char *t = NULL;
+ const char *line = p;
+ size_t skipped;
+
+ if (strncmp (p, "total", 5) == 0)
+ return FALSE;
+
+ if (!vfs_parse_filetype (p, &skipped, &s->st_mode))
+ goto error;
+
+ p += skipped;
+ if (*p == ' ') /* Notwell 4 */
+ p++;
+ if (*p == '[')
+ {
+ if (strlen (p) <= 8 || p[8] != ']')
+ goto error;
+
+ /* Should parse here the Notwell permissions :) */
+ if (S_ISDIR (s->st_mode))
+ s->st_mode |= (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IXUSR | S_IXGRP | S_IXOTH);
+ else
+ s->st_mode |= (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR);
+ p += 9;
+ }
+ else
+ {
+ size_t lc_skipped;
+ mode_t perms;
+
+ if (!vfs_parse_fileperms (p, &lc_skipped, &perms))
+ goto error;
+
+ p += lc_skipped;
+ s->st_mode |= perms;
+ }
+
+ p_copy = g_strdup (p);
+ num_cols = vfs_split_text (p_copy);
+
+ s->st_nlink = atol (columns[0]);
+ if (s->st_nlink <= 0)
+ goto error;
+
+ if (!is_num (1))
+ s->st_uid = vfs_finduid (columns[1]);
+ else
+ s->st_uid = (uid_t) atol (columns[1]);
+
+ /* Mhm, the ls -lg did not produce a group field */
+ for (idx = 3; idx <= 5; idx++)
+ if (vfs_parse_month (columns[idx], NULL) || is_week (columns[idx], NULL)
+ || is_dos_date (columns[idx]) || is_localized_month (columns[idx]))
+ break;
+
+ if (idx == 6 || (idx == 5 && !S_ISCHR (s->st_mode) && !S_ISBLK (s->st_mode)))
+ goto error;
+
+ /* We don't have gid */
+ if (idx == 3 || (idx == 4 && (S_ISCHR (s->st_mode) || S_ISBLK (s->st_mode))))
+ idx2 = 2;
+ else
+ {
+ /* We have gid field */
+ if (is_num (2))
+ s->st_gid = (gid_t) atol (columns[2]);
+ else
+ s->st_gid = vfs_findgid (columns[2]);
+ idx2 = 3;
+ }
+
+ /* This is device */
+ if (S_ISCHR (s->st_mode) || S_ISBLK (s->st_mode))
+ {
+ int maj, min;
+
+ /* Corner case: there is no whitespace(s) between maj & min */
+ if (!is_num (idx2) && idx2 == 2)
+ {
+ /* cppcheck-suppress invalidscanf */
+ if (!is_num (++idx2) || sscanf (columns[idx2], " %d,%d", &maj, &min) != 2)
+ goto error;
+ }
+ else
+ {
+ /* cppcheck-suppress invalidscanf */
+ if (!is_num (idx2) || sscanf (columns[idx2], " %d,", &maj) != 1)
+ goto error;
+
+ /* cppcheck-suppress invalidscanf */
+ if (!is_num (++idx2) || sscanf (columns[idx2], " %d", &min) != 1)
+ goto error;
+ }
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ s->st_rdev = makedev (maj, min);
+#endif
+ s->st_size = 0;
+
+ }
+ else
+ {
+ /* Common file size */
+ if (!is_num (idx2))
+ goto error;
+
+ s->st_size = (off_t) g_ascii_strtoll (columns[idx2], NULL, 10);
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ s->st_rdev = 0;
+#endif
+ }
+
+ idx = vfs_parse_filedate (idx, &s->st_mtime);
+ if (idx == 0)
+ goto error;
+
+ /* Use resulting time value */
+ s->st_atime = s->st_ctime = s->st_mtime;
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ s->st_atim.tv_nsec = s->st_mtim.tv_nsec = s->st_ctim.tv_nsec = 0;
+#endif
+
+ /* s->st_dev and s->st_ino must be initialized by vfs_s_new_inode () */
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ s->st_blksize = 512;
+#endif
+ vfs_adjust_stat (s);
+
+ if (num_spaces != NULL)
+ {
+ *num_spaces = column_ptr[idx] - column_ptr[idx - 1] - strlen (columns[idx - 1]);
+ if (DIR_IS_DOTDOT (columns[idx]))
+ vfs_parce_ls_final_num_spaces = *num_spaces;
+ }
+
+ for (i = idx + 1, idx2 = 0; i < num_cols; i++)
+ if (strcmp (columns[i], "->") == 0)
+ {
+ idx2 = i;
+ break;
+ }
+
+ if (((S_ISLNK (s->st_mode) || (num_cols == idx + 3 && s->st_nlink > 1))) /* Maybe a hardlink? (in extfs) */
+ && idx2 != 0)
+ {
+ if (filename != NULL)
+ *filename = g_strndup (p + column_ptr[idx], column_ptr[idx2] - column_ptr[idx] - 1);
+
+ if (linkname != NULL)
+ {
+ t = g_strdup (p + column_ptr[idx2 + 1]);
+ *linkname = t;
+ }
+ }
+ else
+ {
+ /* Extract the filename from the string copy, not from the columns
+ * this way we have a chance of entering hidden directories like ". ."
+ */
+ if (filename != NULL)
+ {
+ /* filename = g_strdup (columns [idx++]); */
+ t = g_strdup (p + column_ptr[idx]);
+ *filename = t;
+ }
+
+ if (linkname != NULL)
+ *linkname = NULL;
+ }
+
+ if (t != NULL)
+ {
+ size_t p2;
+
+ p2 = strlen (t);
+ if (--p2 > 0 && (t[p2] == '\r' || t[p2] == '\n'))
+ t[p2] = '\0';
+ if (--p2 > 0 && (t[p2] == '\r' || t[p2] == '\n'))
+ t[p2] = '\0';
+ }
+
+ g_free (p_copy);
+ return TRUE;
+
+ error:
+ {
+ static int errorcount = 0;
+
+ if (++errorcount < 5)
+ message (D_ERROR, _("Cannot parse:"), "%s",
+ (p_copy != NULL && *p_copy != '\0') ? p_copy : line);
+ else if (errorcount == 5)
+ message (D_ERROR, MSG_ERROR, _("More parsing errors will be ignored."));
+ }
+
+ g_free (p_copy);
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/vfs/path.c b/lib/vfs/path.c
new file mode 100644
index 0000000..c599e25
--- /dev/null
+++ b/lib/vfs/path.c
@@ -0,0 +1,1683 @@
+/*
+ Virtual File System path handlers
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2011, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2013-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: path handlers
+ * \author Slava Zanko
+ * \date 2011
+ */
+
+
+#include <config.h>
+
+#include <errno.h>
+
+#include "lib/global.h"
+#include "lib/strutil.h"
+#include "lib/util.h" /* mc_build_filename() */
+#include "lib/serialize.h"
+
+#include "vfs.h"
+#include "utilvfs.h"
+#include "xdirentry.h"
+#include "path.h"
+
+extern GPtrArray *vfs__classes_list;
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+path_magic (const char *path)
+{
+ struct stat buf;
+
+ return (stat (path, &buf) != 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Splits path extracting vfs part.
+ *
+ * Splits path
+ * \verbatim /p1#op/inpath \endverbatim
+ * into
+ * \verbatim inpath,op; \endverbatim
+ * returns which vfs it is.
+ * What is left in path is p1. You still want to g_free(path), you DON'T
+ * want to free neither *inpath nor *op
+ */
+
+static struct vfs_class *
+_vfs_split_with_semi_skip_count (char *path, const char **inpath, const char **op,
+ size_t skip_count)
+{
+ char *semi;
+ char *slash;
+ struct vfs_class *ret;
+
+ if (path == NULL)
+ vfs_die ("Cannot split NULL");
+
+ semi = strrstr_skip_count (path, "#", skip_count);
+
+ if ((semi == NULL) || (!path_magic (path)))
+ return NULL;
+
+ slash = strchr (semi, PATH_SEP);
+ *semi = '\0';
+
+ if (op != NULL)
+ *op = NULL;
+
+ if (inpath != NULL)
+ *inpath = NULL;
+
+ if (slash != NULL)
+ *slash = '\0';
+
+ ret = vfs_prefix_to_class (semi + 1);
+ if (ret != NULL)
+ {
+ if (op != NULL)
+ *op = semi + 1;
+ if (inpath != NULL)
+ *inpath = slash != NULL ? slash + 1 : NULL;
+ return ret;
+ }
+
+ if (slash != NULL)
+ *slash = PATH_SEP;
+
+ *semi = '#';
+ ret = _vfs_split_with_semi_skip_count (path, inpath, op, skip_count + 1);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * remove //, /./ and /../
+ *
+ * @return newly allocated string
+ */
+
+static char *
+vfs_canon (const char *path)
+{
+ char *result;
+
+ if (path == NULL)
+ vfs_die ("Cannot canonicalize NULL");
+
+ if (!IS_PATH_SEP (*path))
+ {
+ /* Relative to current directory */
+
+ char *local;
+
+ if (g_str_has_prefix (path, VFS_ENCODING_PREFIX))
+ {
+ /*
+ encoding prefix placed at start of string without the leading slash
+ should be autofixed by adding the leading slash
+ */
+ local = mc_build_filename (PATH_SEP_STR, path, (char *) NULL);
+ }
+ else
+ {
+ const char *curr_dir;
+
+ curr_dir = vfs_get_current_dir ();
+ local = mc_build_filename (curr_dir, path, (char *) NULL);
+ }
+ result = vfs_canon (local);
+ g_free (local);
+ }
+ else
+ {
+ /* Absolute path */
+
+ result = g_strdup (path);
+ canonicalize_pathname (result);
+ }
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_CHARSET
+/** get encoding after last #enc: or NULL, if part does not contain #enc:
+ *
+ * @param path null-terminated string
+ * @param len the maximum length of path, where #enc: should be searched
+ *
+ * @return newly allocated string.
+ */
+
+static char *
+vfs_get_encoding (const char *path, ssize_t len)
+{
+ char *semi;
+
+ /* try found #enc: */
+ semi = g_strrstr_len (path, len, VFS_ENCODING_PREFIX);
+ if (semi == NULL)
+ return NULL;
+
+ if (semi == path || IS_PATH_SEP (semi[-1]))
+ {
+ char *slash;
+
+ semi += strlen (VFS_ENCODING_PREFIX); /* skip "#enc:" */
+ slash = strchr (semi, PATH_SEP);
+ if (slash != NULL)
+ return g_strndup (semi, slash - semi);
+ return g_strdup (semi);
+ }
+
+ return vfs_get_encoding (path, semi - path);
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+/** Extract the hostname and username from the path
+ *
+ * Format of the path is [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
+ * ftp://joe:password@foo.se
+ *
+ * @param path_element is an input string to be parsed
+ * @param path is an input string to be parsed
+ *
+ * @return g_malloc()ed url info.
+ * If the user is empty, e.g. ftp://@roxanne/private, and URL_USE_ANONYMOUS
+ * is not set, then the current login name is supplied.
+ * Return value is a g_malloc()ed structure with the pathname relative to the
+ * host.
+ */
+
+static void
+vfs_path_url_split (vfs_path_element_t * path_element, const char *path)
+{
+ char *pcopy;
+ char *colon, *at, *rest;
+
+ path_element->port = 0;
+
+ pcopy = g_strdup (path);
+
+ /* search for any possible user */
+ at = strrchr (pcopy, '@');
+
+ /* We have a username */
+ if (at == NULL)
+ rest = pcopy;
+ else
+ {
+ const char *pend;
+ char *inner_colon;
+
+ pend = strchr (at, '\0');
+ *at = '\0';
+
+ inner_colon = strchr (pcopy, ':');
+ if (inner_colon != NULL)
+ {
+ *inner_colon = '\0';
+ inner_colon++;
+ path_element->password = g_strdup (inner_colon);
+ }
+
+ if (*pcopy != '\0')
+ path_element->user = g_strdup (pcopy);
+
+ if (pend == at + 1)
+ rest = at;
+ else
+ rest = at + 1;
+ }
+
+ /* Check if the host comes with a port spec, if so, chop it */
+ if (*rest != '[')
+ colon = strchr (rest, ':');
+ else
+ {
+ colon = strchr (++rest, ']');
+ if (colon != NULL)
+ {
+ *colon = '\0';
+ colon++;
+ *colon = '\0';
+ path_element->ipv6 = TRUE;
+ }
+ }
+
+ if (colon != NULL)
+ {
+ *colon = '\0';
+ /* cppcheck-suppress invalidscanf */
+ if (sscanf (colon + 1, "%d", &path_element->port) == 1)
+ {
+ if (path_element->port <= 0 || path_element->port >= 65536)
+ path_element->port = 0;
+ }
+ else
+ while (*(++colon) != '\0')
+ {
+ switch (*colon)
+ {
+ case 'C':
+ path_element->port = 1;
+ break;
+ case 'r':
+ path_element->port = 2;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ path_element->host = g_strdup (rest);
+ g_free (pcopy);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * get VFS class for the given name
+ *
+ * @param class_name name of class
+ *
+ * @return pointer to class structure or NULL if class not found
+ */
+
+static struct vfs_class *
+vfs_get_class_by_name (const char *class_name)
+{
+ guint i;
+
+ if (class_name == NULL)
+ return NULL;
+
+ for (i = 0; i < vfs__classes_list->len; i++)
+ {
+ struct vfs_class *vfs = VFS_CLASS (g_ptr_array_index (vfs__classes_list, i));
+ if ((vfs->name != NULL) && (strcmp (vfs->name, class_name) == 0))
+ return vfs;
+ }
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check if path string contain URL-like elements
+ *
+ * @param path_str path
+ *
+ * @return TRUE if path is deprecated or FALSE otherwise
+ */
+
+static gboolean
+vfs_path_is_str_path_deprecated (const char *path_str)
+{
+ return strstr (path_str, VFS_PATH_URL_DELIMITER) == NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Split path string to path elements by deprecated algorithm.
+ *
+ * @param path_str VFS-path
+ *
+ * @return pointer to newly created vfs_path_t object with filled path elements array.
+*/
+
+static vfs_path_t *
+vfs_path_from_str_deprecated_parser (char *path)
+{
+ vfs_path_t *vpath;
+ vfs_path_element_t *element;
+ struct vfs_class *class;
+ const char *local, *op;
+
+ vpath = vfs_path_new (FALSE);
+
+ while ((class = _vfs_split_with_semi_skip_count (path, &local, &op, 0)) != NULL)
+ {
+ char *url_params;
+ element = g_new0 (vfs_path_element_t, 1);
+ element->class = class;
+ if (local == NULL)
+ local = "";
+ element->path = vfs_translate_path_n (local);
+
+#ifdef HAVE_CHARSET
+ element->encoding = vfs_get_encoding (local, -1);
+ element->dir.converter =
+ (element->encoding != NULL) ? str_crt_conv_from (element->encoding) : INVALID_CONV;
+#endif
+
+ url_params = strchr (op, ':'); /* skip VFS prefix */
+ if (url_params != NULL)
+ {
+ *url_params = '\0';
+ url_params++;
+ vfs_path_url_split (element, url_params);
+ }
+
+ if (*op != '\0')
+ element->vfs_prefix = g_strdup (op);
+
+ g_array_prepend_val (vpath->path, element);
+ }
+ if (path[0] != '\0')
+ {
+ element = g_new0 (vfs_path_element_t, 1);
+ element->class = g_ptr_array_index (vfs__classes_list, 0);
+ element->path = vfs_translate_path_n (path);
+
+#ifdef HAVE_CHARSET
+ element->encoding = vfs_get_encoding (path, -1);
+ element->dir.converter =
+ (element->encoding != NULL) ? str_crt_conv_from (element->encoding) : INVALID_CONV;
+#endif
+ g_array_prepend_val (vpath->path, element);
+ }
+
+ return vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Split path string to path elements by URL algorithm.
+ *
+ * @param path_str VFS-path
+ * @param flags flags for converter
+ *
+ * @return pointer to newly created vfs_path_t object with filled path elements array.
+*/
+
+static vfs_path_t *
+vfs_path_from_str_uri_parser (char *path)
+{
+ vfs_path_t *vpath;
+ vfs_path_element_t *element;
+ char *url_delimiter;
+
+ vpath = vfs_path_new (path != NULL && !IS_PATH_SEP (*path));
+
+ while ((url_delimiter = g_strrstr (path, VFS_PATH_URL_DELIMITER)) != NULL)
+ {
+ char *vfs_prefix_start;
+ char *real_vfs_prefix_start = url_delimiter;
+
+ while (real_vfs_prefix_start > path && !IS_PATH_SEP (*real_vfs_prefix_start))
+ real_vfs_prefix_start--;
+ vfs_prefix_start = real_vfs_prefix_start;
+
+ if (IS_PATH_SEP (*vfs_prefix_start))
+ vfs_prefix_start += 1;
+
+ *url_delimiter = '\0';
+
+ element = g_new0 (vfs_path_element_t, 1);
+ element->class = vfs_prefix_to_class (vfs_prefix_start);
+ element->vfs_prefix = g_strdup (vfs_prefix_start);
+
+ url_delimiter += strlen (VFS_PATH_URL_DELIMITER);
+
+ if (element->class != NULL && (element->class->flags & VFSF_REMOTE) != 0)
+ {
+ char *slash_pointer;
+
+ slash_pointer = strchr (url_delimiter, PATH_SEP);
+ if (slash_pointer == NULL)
+ {
+ element->path = g_strdup ("");
+ }
+ else
+ {
+ element->path = vfs_translate_path_n (slash_pointer + 1);
+#ifdef HAVE_CHARSET
+ element->encoding = vfs_get_encoding (slash_pointer, -1);
+#endif
+ *slash_pointer = '\0';
+ }
+ vfs_path_url_split (element, url_delimiter);
+ }
+ else
+ {
+ element->path = vfs_translate_path_n (url_delimiter);
+#ifdef HAVE_CHARSET
+ element->encoding = vfs_get_encoding (url_delimiter, -1);
+#endif
+ }
+#ifdef HAVE_CHARSET
+ element->dir.converter =
+ (element->encoding != NULL) ? str_crt_conv_from (element->encoding) : INVALID_CONV;
+#endif
+ g_array_prepend_val (vpath->path, element);
+
+ if ((real_vfs_prefix_start > path && IS_PATH_SEP (*real_vfs_prefix_start)) ||
+ (real_vfs_prefix_start == path && !IS_PATH_SEP (*real_vfs_prefix_start)))
+ *real_vfs_prefix_start = '\0';
+ else
+ *(real_vfs_prefix_start + 1) = '\0';
+ }
+
+ if (path[0] != '\0')
+ {
+ element = g_new0 (vfs_path_element_t, 1);
+ element->class = g_ptr_array_index (vfs__classes_list, 0);
+ element->path = vfs_translate_path_n (path);
+#ifdef HAVE_CHARSET
+ element->encoding = vfs_get_encoding (path, -1);
+ element->dir.converter =
+ (element->encoding != NULL) ? str_crt_conv_from (element->encoding) : INVALID_CONV;
+#endif
+ g_array_prepend_val (vpath->path, element);
+ }
+
+ return vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Add element's class info to result string (such as VFS name, host, encoding etc)
+ * This function used as helper only in vfs_path_tokens_get() function
+ *
+ * @param element current path element
+ * @param ret_tokens total tikens for return
+ * @param element_tokens accumulated element-only tokens
+ */
+
+static void
+vfs_path_tokens_add_class_info (const vfs_path_element_t * element, GString * ret_tokens,
+ GString * element_tokens)
+{
+ if (((element->class->flags & VFSF_LOCAL) == 0 || ret_tokens->len > 0)
+ && element_tokens->len > 0)
+ {
+ GString *url_str;
+
+ if (ret_tokens->len > 0 && !IS_PATH_SEP (ret_tokens->str[ret_tokens->len - 1]))
+ g_string_append_c (ret_tokens, PATH_SEP);
+
+ g_string_append (ret_tokens, element->vfs_prefix);
+ g_string_append (ret_tokens, VFS_PATH_URL_DELIMITER);
+
+ url_str = vfs_path_build_url_params_str (element, TRUE);
+ if (url_str->len != 0)
+ {
+ g_string_append_len (ret_tokens, url_str->str, url_str->len);
+ g_string_append_c (ret_tokens, PATH_SEP);
+ }
+
+ g_string_free (url_str, TRUE);
+ }
+
+#ifdef HAVE_CHARSET
+ if (element->encoding != NULL)
+ {
+ if (ret_tokens->len > 0 && !IS_PATH_SEP (ret_tokens->str[ret_tokens->len - 1]))
+ g_string_append (ret_tokens, PATH_SEP_STR);
+ g_string_append (ret_tokens, VFS_ENCODING_PREFIX);
+ g_string_append (ret_tokens, element->encoding);
+ g_string_append (ret_tokens, PATH_SEP_STR);
+ }
+#endif
+
+ g_string_append (ret_tokens, element_tokens->str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Strip path to home dir.
+ * @param dir pointer to string contains full path
+ */
+
+static char *
+vfs_path_strip_home (const char *dir)
+{
+ const char *home_dir = mc_config_get_home_dir ();
+
+ if (home_dir != NULL)
+ {
+ size_t len;
+
+ len = strlen (home_dir);
+
+ if (strncmp (dir, home_dir, len) == 0 && (IS_PATH_SEP (dir[len]) || dir[len] == '\0'))
+ return g_strdup_printf ("~%s", dir + len);
+ }
+
+ return g_strdup (dir);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+#define vfs_append_from_path(appendfrom, is_relative) \
+{ \
+ if ((flags & VPF_STRIP_HOME) && element_index == 0 && \
+ (element->class->flags & VFSF_LOCAL) != 0) \
+ { \
+ char *stripped_home_str; \
+ stripped_home_str = vfs_path_strip_home (appendfrom); \
+ g_string_append (buffer, stripped_home_str); \
+ g_free (stripped_home_str); \
+ } \
+ else \
+ { \
+ if (!is_relative && !IS_PATH_SEP (*appendfrom) && *appendfrom != '\0' \
+ && (buffer->len == 0 || !IS_PATH_SEP (buffer->str[buffer->len - 1]))) \
+ g_string_append_c (buffer, PATH_SEP); \
+ g_string_append (buffer, appendfrom); \
+ } \
+}
+
+/**
+ * Convert first elements_count elements from vfs_path_t to string representation with flags.
+ *
+ * @param vpath pointer to vfs_path_t object
+ * @param elements_count count of first elements for convert
+ * @param flags for converter
+ *
+ * @return pointer to newly created string.
+ */
+
+char *
+vfs_path_to_str_flags (const vfs_path_t * vpath, int elements_count, vfs_path_flag_t flags)
+{
+ int element_index;
+ GString *buffer;
+#ifdef HAVE_CHARSET
+ GString *recode_buffer = NULL;
+#endif
+
+ if (vpath == NULL)
+ return NULL;
+
+ if (elements_count == 0 || elements_count > vfs_path_elements_count (vpath))
+ elements_count = vfs_path_elements_count (vpath);
+
+ if (elements_count < 0)
+ elements_count = vfs_path_elements_count (vpath) + elements_count;
+
+ buffer = g_string_new ("");
+
+ for (element_index = 0; element_index < elements_count; element_index++)
+ {
+ const vfs_path_element_t *element;
+ gboolean is_relative = vpath->relative && (element_index == 0);
+
+ element = vfs_path_get_by_index (vpath, element_index);
+ if (element->vfs_prefix != NULL)
+ {
+ GString *url_str;
+
+ if (!is_relative && (buffer->len == 0 || !IS_PATH_SEP (buffer->str[buffer->len - 1])))
+ g_string_append_c (buffer, PATH_SEP);
+
+ g_string_append (buffer, element->vfs_prefix);
+ g_string_append (buffer, VFS_PATH_URL_DELIMITER);
+
+ url_str = vfs_path_build_url_params_str (element, !(flags & VPF_STRIP_PASSWORD));
+ if (url_str->len != 0)
+ {
+ g_string_append_len (buffer, url_str->str, url_str->len);
+ g_string_append_c (buffer, PATH_SEP);
+ }
+
+ g_string_free (url_str, TRUE);
+ }
+
+#ifdef HAVE_CHARSET
+ if ((flags & VPF_RECODE) == 0 && vfs_path_element_need_cleanup_converter (element))
+ {
+ if ((flags & VPF_HIDE_CHARSET) == 0)
+ {
+ if ((!is_relative)
+ && (buffer->len == 0 || !IS_PATH_SEP (buffer->str[buffer->len - 1])))
+ g_string_append (buffer, PATH_SEP_STR);
+ g_string_append (buffer, VFS_ENCODING_PREFIX);
+ g_string_append (buffer, element->encoding);
+ }
+
+ if (recode_buffer == NULL)
+ recode_buffer = g_string_sized_new (32);
+ else
+ g_string_set_size (recode_buffer, 0);
+
+ str_vfs_convert_from (element->dir.converter, element->path, recode_buffer);
+ vfs_append_from_path (recode_buffer->str, is_relative);
+ }
+ else
+#endif
+ {
+ vfs_append_from_path (element->path, is_relative);
+ }
+ }
+
+#ifdef HAVE_CHARSET
+ if (recode_buffer != NULL)
+ g_string_free (recode_buffer, TRUE);
+#endif
+
+ return g_string_free (buffer, FALSE);
+}
+
+#undef vfs_append_from_path
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Convert first elements_count elements from vfs_path_t to string representation.
+ *
+ * @param vpath pointer to vfs_path_t object
+ * @param elements_count count of first elements for convert
+ *
+ * @return pointer to newly created string.
+ */
+
+char *
+vfs_path_to_str_elements_count (const vfs_path_t * vpath, int elements_count)
+{
+ return vfs_path_to_str_flags (vpath, elements_count, VPF_NONE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Split path string to path elements with flags for change parce process.
+ *
+ * @param path_str VFS-path
+ * @param flags flags for parser
+ *
+ * @return pointer to newly created vfs_path_t object with filled path elements array.
+ */
+
+vfs_path_t *
+vfs_path_from_str_flags (const char *path_str, vfs_path_flag_t flags)
+{
+ vfs_path_t *vpath;
+ char *path;
+
+ if (path_str == NULL)
+ return NULL;
+
+ if ((flags & VPF_NO_CANON) == 0)
+ path = vfs_canon (path_str);
+ else
+ path = g_strdup (path_str);
+
+ if (path == NULL)
+ return NULL;
+
+ if ((flags & VPF_USE_DEPRECATED_PARSER) != 0 && vfs_path_is_str_path_deprecated (path))
+ vpath = vfs_path_from_str_deprecated_parser (path);
+ else
+ vpath = vfs_path_from_str_uri_parser (path);
+
+ vpath->str = vfs_path_to_str_flags (vpath, 0, flags);
+ g_free (path);
+
+ return vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Split path string to path elements.
+ *
+ * @param path_str VFS-path
+ *
+ * @return pointer to newly created vfs_path_t object with filled path elements array.
+ */
+
+vfs_path_t *
+vfs_path_from_str (const char *path_str)
+{
+ return vfs_path_from_str_flags (path_str, VPF_NONE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Create new vfs_path_t object.
+ *
+ * @return pointer to newly created vfs_path_t object.
+ */
+
+vfs_path_t *
+vfs_path_new (gboolean relative)
+{
+ vfs_path_t *vpath;
+
+ vpath = g_new0 (vfs_path_t, 1);
+ vpath->path = g_array_new (FALSE, TRUE, sizeof (vfs_path_element_t *));
+ vpath->relative = relative;
+
+ return vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Get count of path elements.
+ *
+ * @param vpath pointer to vfs_path_t object
+ *
+ * @return count of path elements.
+ */
+
+int
+vfs_path_elements_count (const vfs_path_t * vpath)
+{
+ return (vpath != NULL && vpath->path != NULL) ? vpath->path->len : 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Add vfs_path_element_t object to end of list in vfs_path_t object
+ * @param vpath pointer to vfs_path_t object
+ * @param path_element pointer to vfs_path_element_t object
+ */
+
+void
+vfs_path_add_element (vfs_path_t * vpath, const vfs_path_element_t * path_element)
+{
+ g_array_append_val (vpath->path, path_element);
+ g_free (vpath->str);
+ vpath->str = vfs_path_to_str_flags (vpath, 0, VPF_NONE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Get one path element by index.
+ *
+ * @param vpath pointer to vfs_path_t object.
+ * May be NULL. In this case NULL is returned and errno set to 0.
+ * @param element_index element index. May have negative value (in this case count was started at
+ * the end of list). If @element_index is out of range, NULL is returned and
+ * errno set to EINVAL.
+ *
+ * @return path element
+ */
+
+const vfs_path_element_t *
+vfs_path_get_by_index (const vfs_path_t * vpath, int element_index)
+{
+ int n;
+
+ if (vpath == NULL)
+ {
+ errno = 0;
+ return NULL;
+ }
+
+ n = vfs_path_elements_count (vpath);
+
+ if (element_index < 0)
+ element_index += n;
+
+ if (element_index < 0 || element_index > n)
+ {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ return g_array_index (vpath->path, vfs_path_element_t *, element_index);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Clone one path element
+ *
+ * @param element pointer to vfs_path_element_t object
+ *
+ * @return Newly allocated path element
+ */
+
+vfs_path_element_t *
+vfs_path_element_clone (const vfs_path_element_t * element)
+{
+ vfs_path_element_t *new_element = g_new (vfs_path_element_t, 1);
+
+ new_element->user = g_strdup (element->user);
+ new_element->password = g_strdup (element->password);
+ new_element->host = g_strdup (element->host);
+ new_element->ipv6 = element->ipv6;
+ new_element->port = element->port;
+ new_element->path = g_strdup (element->path);
+ new_element->class = element->class;
+ new_element->vfs_prefix = g_strdup (element->vfs_prefix);
+#ifdef HAVE_CHARSET
+ new_element->encoding = g_strdup (element->encoding);
+ if (vfs_path_element_need_cleanup_converter (element) && new_element->encoding != NULL)
+ new_element->dir.converter = str_crt_conv_from (new_element->encoding);
+ else
+ new_element->dir.converter = element->dir.converter;
+#endif
+ new_element->dir.info = element->dir.info;
+
+ return new_element;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Free one path element.
+ *
+ * @param element pointer to vfs_path_element_t object
+ *
+ */
+
+void
+vfs_path_element_free (vfs_path_element_t * element)
+{
+ if (element == NULL)
+ return;
+
+ g_free (element->user);
+ g_free (element->password);
+ g_free (element->host);
+ g_free (element->path);
+ g_free (element->vfs_prefix);
+
+#ifdef HAVE_CHARSET
+ g_free (element->encoding);
+
+ if (vfs_path_element_need_cleanup_converter (element))
+ str_close_conv (element->dir.converter);
+#endif
+
+ g_free (element);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Clone path
+ *
+ * @param vpath pointer to vfs_path_t object
+ *
+ * @return Newly allocated path object
+ */
+
+vfs_path_t *
+vfs_path_clone (const vfs_path_t * vpath)
+{
+ vfs_path_t *new_vpath;
+ int vpath_element_index;
+
+ if (vpath == NULL)
+ return NULL;
+
+ new_vpath = vfs_path_new (vpath->relative);
+
+ for (vpath_element_index = 0; vpath_element_index < vfs_path_elements_count (vpath);
+ vpath_element_index++)
+ {
+ vfs_path_element_t *path_element;
+
+ path_element = vfs_path_element_clone (vfs_path_get_by_index (vpath, vpath_element_index));
+ g_array_append_val (new_vpath->path, path_element);
+ }
+ new_vpath->str = g_strdup (vpath->str);
+
+ return new_vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Free vfs_path_t object.
+ *
+ * @param vpath pointer to vfs_path_t object
+ * @param free_str if TRUE the string representation of vpath is freed as well
+ *
+ * @return the string representation of vpath (i.e. NULL if free_str is TRUE)
+ */
+
+char *
+vfs_path_free (vfs_path_t * vpath, gboolean free_str)
+{
+ int vpath_element_index;
+ char *ret;
+
+ if (vpath == NULL)
+ return NULL;
+
+ for (vpath_element_index = 0; vpath_element_index < vfs_path_elements_count (vpath);
+ vpath_element_index++)
+ {
+ vfs_path_element_t *path_element;
+
+ path_element = (vfs_path_element_t *) vfs_path_get_by_index (vpath, vpath_element_index);
+ vfs_path_element_free (path_element);
+ }
+
+ g_array_free (vpath->path, TRUE);
+
+ if (!free_str)
+ ret = vpath->str;
+ else
+ {
+ g_free (vpath->str);
+ ret = NULL;
+ }
+
+ g_free (vpath);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Remove one path element by index
+ *
+ * @param vpath pointer to vfs_path_t object
+ * @param element_index element index. May have negative value (in this case count was started at the end of list).
+ *
+ */
+
+void
+vfs_path_remove_element_by_index (vfs_path_t * vpath, int element_index)
+{
+ vfs_path_element_t *element;
+
+ if ((vpath == NULL) || (vfs_path_elements_count (vpath) == 1))
+ return;
+
+ if (element_index < 0)
+ element_index = vfs_path_elements_count (vpath) + element_index;
+
+ element = (vfs_path_element_t *) vfs_path_get_by_index (vpath, element_index);
+ vpath->path = g_array_remove_index (vpath->path, element_index);
+ vfs_path_element_free (element);
+ g_free (vpath->str);
+ vpath->str = vfs_path_to_str_flags (vpath, 0, VPF_NONE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Return VFS class for the given prefix */
+
+struct vfs_class *
+vfs_prefix_to_class (const char *prefix)
+{
+ guint i;
+
+ /* Avoid first class (localfs) that would accept any prefix */
+ for (i = 1; i < vfs__classes_list->len; i++)
+ {
+ struct vfs_class *vfs;
+
+ vfs = VFS_CLASS (g_ptr_array_index (vfs__classes_list, i));
+ if (vfs->which != NULL)
+ {
+ if (vfs->which (vfs, prefix) == -1)
+ continue;
+ return vfs;
+ }
+
+ if (vfs->prefix != NULL && strncmp (prefix, vfs->prefix, strlen (vfs->prefix)) == 0)
+ return vfs;
+ }
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_CHARSET
+
+/**
+ * Check if need cleanup charset converter for vfs_path_element_t
+ *
+ * @param element part of path
+ *
+ * @return TRUE if need cleanup converter or FALSE otherwise
+ */
+
+gboolean
+vfs_path_element_need_cleanup_converter (const vfs_path_element_t * element)
+{
+ return (element->dir.converter != str_cnv_from_term && element->dir.converter != INVALID_CONV);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Change encoding for last part (vfs_path_element_t) of vpath
+ *
+ * @param vpath pointer to path structure
+ * encoding name of charset
+ *
+ * @return pointer to path structure (for use function in another functions)
+ */
+vfs_path_t *
+vfs_path_change_encoding (vfs_path_t * vpath, const char *encoding)
+{
+ vfs_path_element_t *path_element;
+
+ path_element = (vfs_path_element_t *) vfs_path_get_by_index (vpath, -1);
+ /* don't add current encoding */
+ if ((path_element->encoding != NULL) && (strcmp (encoding, path_element->encoding) == 0))
+ return vpath;
+
+ g_free (path_element->encoding);
+ path_element->encoding = g_strdup (encoding);
+
+ if (vfs_path_element_need_cleanup_converter (path_element))
+ str_close_conv (path_element->dir.converter);
+
+ path_element->dir.converter = str_crt_conv_from (path_element->encoding);
+
+ g_free (vpath->str);
+ vpath->str = vfs_path_to_str_flags (vpath, 0, VPF_NONE);
+ return vpath;
+}
+
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Serialize vfs_path_t object to string
+ *
+ * @param vpath data for serialization
+ * @param error contain pointer to object for handle error code and message
+ *
+ * @return serialized vpath as newly allocated string
+ */
+
+char *
+vfs_path_serialize (const vfs_path_t * vpath, GError ** mcerror)
+{
+ mc_config_t *cpath;
+ ssize_t element_index;
+ char *ret_value;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ if ((vpath == NULL) || (vfs_path_elements_count (vpath) == 0))
+ {
+ mc_propagate_error (mcerror, 0, "%s", "vpath object is empty");
+ return NULL;
+ }
+
+ cpath = mc_config_init (NULL, FALSE);
+
+ for (element_index = 0; element_index < vfs_path_elements_count (vpath); element_index++)
+ {
+ char groupname[BUF_TINY];
+ const vfs_path_element_t *element;
+
+ g_snprintf (groupname, sizeof (groupname), "path-element-%zd", element_index);
+ element = vfs_path_get_by_index (vpath, element_index);
+ /* convert one element to config group */
+
+ mc_config_set_string_raw (cpath, groupname, "path", element->path);
+ mc_config_set_string_raw (cpath, groupname, "class-name", element->class->name);
+#ifdef HAVE_CHARSET
+ mc_config_set_string_raw (cpath, groupname, "encoding", element->encoding);
+#endif
+ mc_config_set_string_raw (cpath, groupname, "vfs_prefix", element->vfs_prefix);
+
+ mc_config_set_string_raw (cpath, groupname, "user", element->user);
+ mc_config_set_string_raw (cpath, groupname, "password", element->password);
+ mc_config_set_string_raw (cpath, groupname, "host", element->host);
+ if (element->port != 0)
+ mc_config_set_int (cpath, groupname, "port", element->port);
+ }
+
+ ret_value = mc_serialize_config (cpath, mcerror);
+ mc_config_deinit (cpath);
+ return ret_value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Deserialize string to vfs_path_t object
+ *
+ * @param data data for serialization
+ * @param error contain pointer to object for handle error code and message
+ *
+ * @return newly allocated vfs_path_t object
+ */
+
+vfs_path_t *
+vfs_path_deserialize (const char *data, GError ** mcerror)
+{
+ mc_config_t *cpath;
+ size_t element_index;
+ vfs_path_t *vpath;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ cpath = mc_deserialize_config (data, mcerror);
+ if (cpath == NULL)
+ return NULL;
+
+ vpath = vfs_path_new (FALSE);
+
+ for (element_index = 0;; element_index++)
+ {
+ struct vfs_class *eclass;
+ vfs_path_element_t *element;
+ char *cfg_value;
+ char groupname[BUF_TINY];
+
+ g_snprintf (groupname, sizeof (groupname), "path-element-%zu", element_index);
+ if (!mc_config_has_group (cpath, groupname))
+ break;
+
+ cfg_value = mc_config_get_string_raw (cpath, groupname, "class-name", NULL);
+ eclass = vfs_get_class_by_name (cfg_value);
+ if (eclass == NULL)
+ {
+ vfs_path_free (vpath, TRUE);
+ g_set_error (mcerror, MC_ERROR, 0, "Unable to find VFS class by name '%s'", cfg_value);
+ g_free (cfg_value);
+ mc_config_deinit (cpath);
+ return NULL;
+ }
+ g_free (cfg_value);
+
+ element = g_new0 (vfs_path_element_t, 1);
+ element->class = eclass;
+ element->path = mc_config_get_string_raw (cpath, groupname, "path", NULL);
+
+#ifdef HAVE_CHARSET
+ element->encoding = mc_config_get_string_raw (cpath, groupname, "encoding", NULL);
+ element->dir.converter =
+ (element->encoding != NULL) ? str_crt_conv_from (element->encoding) : INVALID_CONV;
+#endif
+
+ element->vfs_prefix = mc_config_get_string_raw (cpath, groupname, "vfs_prefix", NULL);
+
+ element->user = mc_config_get_string_raw (cpath, groupname, "user", NULL);
+ element->password = mc_config_get_string_raw (cpath, groupname, "password", NULL);
+ element->host = mc_config_get_string_raw (cpath, groupname, "host", NULL);
+ element->port = mc_config_get_int (cpath, groupname, "port", 0);
+
+ vpath->path = g_array_append_val (vpath->path, element);
+ }
+
+ mc_config_deinit (cpath);
+ if (vfs_path_elements_count (vpath) == 0)
+ {
+ vfs_path_free (vpath, TRUE);
+ g_set_error (mcerror, MC_ERROR, 0, "No any path elements found");
+ return NULL;
+ }
+ vpath->str = vfs_path_to_str_flags (vpath, 0, VPF_NONE);
+
+ return vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Build vfs_path_t object from arguments.
+ *
+ * @param first_element of path
+ * @param ... path tokens, terminated by NULL
+ *
+ * @return newly allocated vfs_path_t object
+ */
+
+vfs_path_t *
+vfs_path_build_filename (const char *first_element, ...)
+{
+ va_list args;
+ char *str_path;
+ vfs_path_t *vpath;
+
+ if (first_element == NULL)
+ return NULL;
+
+ va_start (args, first_element);
+ str_path = mc_build_filenamev (first_element, args);
+ va_end (args);
+ vpath = vfs_path_from_str (str_path);
+ g_free (str_path);
+ return vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Append tokens to path object
+ *
+ * @param vpath path object
+ * @param first_element of path
+ * @param ... NULL-terminated strings
+ *
+ * @return newly allocated path object
+ */
+
+vfs_path_t *
+vfs_path_append_new (const vfs_path_t * vpath, const char *first_element, ...)
+{
+ va_list args;
+ char *str_path;
+ const char *result_str;
+ vfs_path_t *ret_vpath;
+
+ if (vpath == NULL || first_element == NULL)
+ return NULL;
+
+ va_start (args, first_element);
+ str_path = mc_build_filenamev (first_element, args);
+ va_end (args);
+
+ result_str = vfs_path_as_str (vpath);
+ ret_vpath = vfs_path_build_filename (result_str, str_path, (char *) NULL);
+ g_free (str_path);
+
+ return ret_vpath;
+
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Append vpath_t tokens to path object
+ *
+ * @param first_vpath vpath objects
+ * @param ... NULL-terminated vpath objects
+ *
+ * @return newly allocated path object
+ */
+
+vfs_path_t *
+vfs_path_append_vpath_new (const vfs_path_t * first_vpath, ...)
+{
+ va_list args;
+ vfs_path_t *ret_vpath;
+ const vfs_path_t *current_vpath = first_vpath;
+
+ if (first_vpath == NULL)
+ return NULL;
+
+ ret_vpath = vfs_path_new (FALSE);
+
+ va_start (args, first_vpath);
+ do
+ {
+ int vindex;
+
+ for (vindex = 0; vindex < vfs_path_elements_count (current_vpath); vindex++)
+ {
+ vfs_path_element_t *path_element;
+
+ path_element = vfs_path_element_clone (vfs_path_get_by_index (current_vpath, vindex));
+ g_array_append_val (ret_vpath->path, path_element);
+ }
+ current_vpath = va_arg (args, const vfs_path_t *);
+ }
+ while (current_vpath != NULL);
+ va_end (args);
+
+ ret_vpath->str = vfs_path_to_str_flags (ret_vpath, 0, VPF_NONE);
+
+ return ret_vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * get tockens count in path.
+ *
+ * @param vpath path object
+ *
+ * @return count of tokens
+ */
+
+size_t
+vfs_path_tokens_count (const vfs_path_t * vpath)
+{
+ size_t count_tokens = 0;
+ int element_index;
+
+ if (vpath == NULL)
+ return 0;
+
+ for (element_index = 0; element_index < vfs_path_elements_count (vpath); element_index++)
+ {
+ const vfs_path_element_t *element;
+ const char *token, *prev_token;
+
+ element = vfs_path_get_by_index (vpath, element_index);
+
+ for (prev_token = element->path; (token = strchr (prev_token, PATH_SEP)) != NULL;
+ prev_token = token + 1)
+ {
+ /* skip empty substring */
+ if (token != prev_token)
+ count_tokens++;
+ }
+
+ if (*prev_token != '\0')
+ count_tokens++;
+ }
+
+ return count_tokens;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Get subpath by tokens
+ *
+ * @param vpath path object
+ * @param start_position first token for got/ Started from 0.
+ * If negative, then position will be relative to end of path
+ * @param length count of tokens
+ *
+ * @return newly allocated string with path tokens separated by slash
+ */
+
+char *
+vfs_path_tokens_get (const vfs_path_t * vpath, ssize_t start_position, ssize_t length)
+{
+ GString *ret_tokens, *element_tokens;
+ int element_index;
+ size_t tokens_count = vfs_path_tokens_count (vpath);
+
+ if (vpath == NULL)
+ return NULL;
+
+ if (length == 0)
+ length = tokens_count;
+
+ if (length < 0)
+ length = tokens_count + length;
+
+ if (start_position < 0)
+ start_position = (ssize_t) tokens_count + start_position;
+
+ if (start_position < 0)
+ return NULL;
+
+ if (start_position >= (ssize_t) tokens_count)
+ return NULL;
+
+ if (start_position + (ssize_t) length > (ssize_t) tokens_count)
+ length = tokens_count - start_position;
+
+ ret_tokens = g_string_sized_new (32);
+ element_tokens = g_string_sized_new (32);
+
+ for (element_index = 0; element_index < vfs_path_elements_count (vpath); element_index++)
+ {
+ const vfs_path_element_t *element;
+ char **path_tokens, **iterator;
+
+ g_string_assign (element_tokens, "");
+ element = vfs_path_get_by_index (vpath, element_index);
+ path_tokens = g_strsplit (element->path, PATH_SEP_STR, -1);
+
+ for (iterator = path_tokens; *iterator != NULL; iterator++)
+ {
+ if (**iterator != '\0')
+ {
+ if (start_position == 0)
+ {
+ if (length == 0)
+ {
+ vfs_path_tokens_add_class_info (element, ret_tokens, element_tokens);
+ g_string_free (element_tokens, TRUE);
+ g_strfreev (path_tokens);
+ return g_string_free (ret_tokens, FALSE);
+ }
+ length--;
+ if (element_tokens->len != 0)
+ g_string_append_c (element_tokens, PATH_SEP);
+ g_string_append (element_tokens, *iterator);
+ }
+ else
+ start_position--;
+ }
+ }
+ g_strfreev (path_tokens);
+ vfs_path_tokens_add_class_info (element, ret_tokens, element_tokens);
+ }
+
+ g_string_free (element_tokens, TRUE);
+ return g_string_free (ret_tokens, !(start_position == 0 && length == 0));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get subpath by tokens
+ *
+ * @param vpath path object
+ * @param start_position first token for got/ Started from 0.
+ * If negative, then position will be relative to end of path
+ * @param length count of tokens
+ *
+ * @return newly allocated path object with path tokens separated by slash
+ */
+
+vfs_path_t *
+vfs_path_vtokens_get (const vfs_path_t * vpath, ssize_t start_position, ssize_t length)
+{
+ char *str_tokens;
+ vfs_path_t *ret_vpath = NULL;
+
+ str_tokens = vfs_path_tokens_get (vpath, start_position, length);
+ if (str_tokens != NULL)
+ {
+ ret_vpath = vfs_path_from_str_flags (str_tokens, VPF_NO_CANON);
+ g_free (str_tokens);
+ }
+ return ret_vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Build URL parameters (such as user:pass @ host:port) from one path element object
+ *
+ * @param element path element
+ * @param keep_password TRUE or FALSE
+ *
+ * @return newly allocated string
+ */
+
+GString *
+vfs_path_build_url_params_str (const vfs_path_element_t * element, gboolean keep_password)
+{
+ GString *buffer;
+
+ if (element == NULL)
+ return NULL;
+
+ buffer = g_string_sized_new (64);
+
+ if (element->user != NULL)
+ g_string_append (buffer, element->user);
+
+ if (element->password != NULL && keep_password)
+ {
+ g_string_append_c (buffer, ':');
+ g_string_append (buffer, element->password);
+ }
+
+ if (element->host != NULL)
+ {
+ if ((element->user != NULL) || (element->password != NULL))
+ g_string_append_c (buffer, '@');
+ if (element->ipv6)
+ g_string_append_c (buffer, '[');
+ g_string_append (buffer, element->host);
+ if (element->ipv6)
+ g_string_append_c (buffer, ']');
+ }
+
+ if ((element->port) != 0 && (element->host != NULL))
+ {
+ g_string_append_c (buffer, ':');
+ g_string_append_printf (buffer, "%d", element->port);
+ }
+
+ return buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Build pretty string representation of one path_element_t object
+ *
+ * @param element path element
+ *
+ * @return newly allocated string
+ */
+
+GString *
+vfs_path_element_build_pretty_path_str (const vfs_path_element_t * element)
+{
+ GString *url_params, *pretty_path;
+
+ pretty_path = g_string_new (element->class->prefix);
+ g_string_append (pretty_path, VFS_PATH_URL_DELIMITER);
+
+ url_params = vfs_path_build_url_params_str (element, FALSE);
+ g_string_append_len (pretty_path, url_params->str, url_params->len);
+ g_string_free (url_params, TRUE);
+
+ if (!IS_PATH_SEP (*element->path))
+ g_string_append_c (pretty_path, PATH_SEP);
+
+ g_string_append (pretty_path, element->path);
+ return pretty_path;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Compare two path objects as strings
+ *
+ * @param vpath1 first path object
+ * @param vpath2 second vpath object
+ *
+ * @return integer value like to strcmp.
+ */
+
+gboolean
+vfs_path_equal (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ const char *path1, *path2;
+ gboolean ret_val;
+
+ if (vpath1 == NULL || vpath2 == NULL)
+ return FALSE;
+
+ path1 = vfs_path_as_str (vpath1);
+ path2 = vfs_path_as_str (vpath2);
+
+ ret_val = strcmp (path1, path2) == 0;
+
+ return ret_val;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Compare two path objects as strings
+ *
+ * @param vpath1 first path object
+ * @param vpath2 second vpath object
+ * @param len number of first 'len' characters
+ *
+ * @return integer value like to strcmp.
+ */
+
+gboolean
+vfs_path_equal_len (const vfs_path_t * vpath1, const vfs_path_t * vpath2, size_t len)
+{
+ const char *path1, *path2;
+ gboolean ret_val;
+
+ if (vpath1 == NULL || vpath2 == NULL)
+ return FALSE;
+
+ path1 = vfs_path_as_str (vpath1);
+ path2 = vfs_path_as_str (vpath2);
+
+ ret_val = strncmp (path1, path2, len) == 0;
+
+ return ret_val;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculate path length in string representation
+ *
+ * @param vpath path object
+ *
+ * @return length of path
+ */
+
+size_t
+vfs_path_len (const vfs_path_t * vpath)
+{
+ if (vpath == NULL)
+ return 0;
+
+ return strlen (vpath->str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Convert relative vpath object to absolute
+ *
+ * @param vpath path object
+ *
+ * @return absolute path object
+ */
+
+vfs_path_t *
+vfs_path_to_absolute (const vfs_path_t * vpath)
+{
+ vfs_path_t *absolute_vpath;
+ const char *path_str;
+
+ if (!vpath->relative)
+ return vfs_path_clone (vpath);
+
+ path_str = vfs_path_as_str (vpath);
+ absolute_vpath = vfs_path_from_str (path_str);
+ return absolute_vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/vfs/path.h b/lib/vfs/path.h
new file mode 100644
index 0000000..0887111
--- /dev/null
+++ b/lib/vfs/path.h
@@ -0,0 +1,149 @@
+#ifndef MC__VFS_PATH_H
+#define MC__VFS_PATH_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define VFS_PATH_URL_DELIMITER "://"
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ VPF_NONE = 0,
+ VPF_NO_CANON = 1 << 0,
+ VPF_USE_DEPRECATED_PARSER = 1 << 1,
+ VPF_RECODE = 1 << 2,
+ VPF_STRIP_HOME = 1 << 3,
+ VPF_STRIP_PASSWORD = 1 << 4,
+ VPF_HIDE_CHARSET = 1 << 5
+} vfs_path_flag_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+struct vfs_class;
+struct vfs_url_struct;
+
+typedef struct
+{
+ gboolean relative;
+ GArray *path;
+ char *str;
+} vfs_path_t;
+
+typedef struct
+{
+ char *user;
+ char *password;
+ char *host;
+ gboolean ipv6;
+ int port;
+ char *path;
+ struct vfs_class *class;
+#ifdef HAVE_CHARSET
+ char *encoding;
+#endif
+ char *vfs_prefix;
+
+ struct
+ {
+#ifdef HAVE_CHARSET
+ GIConv converter;
+#endif
+ DIR *info;
+ } dir;
+} vfs_path_element_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+vfs_path_t *vfs_path_new (gboolean relative);
+vfs_path_t *vfs_path_clone (const vfs_path_t * vpath);
+void vfs_path_remove_element_by_index (vfs_path_t * vpath, int element_index);
+char *vfs_path_free (vfs_path_t * path, gboolean free_str);
+int vfs_path_elements_count (const vfs_path_t * path);
+
+char *vfs_path_to_str_elements_count (const vfs_path_t * path, int elements_count);
+char *vfs_path_to_str_flags (const vfs_path_t * vpath, int elements_count, vfs_path_flag_t flags);
+vfs_path_t *vfs_path_from_str (const char *path_str);
+vfs_path_t *vfs_path_from_str_flags (const char *path_str, vfs_path_flag_t flags);
+vfs_path_t *vfs_path_build_filename (const char *first_element, ...);
+vfs_path_t *vfs_path_append_new (const vfs_path_t * vpath, const char *first_element, ...);
+vfs_path_t *vfs_path_append_vpath_new (const vfs_path_t * first_vpath, ...);
+size_t vfs_path_tokens_count (const vfs_path_t * vpath);
+char *vfs_path_tokens_get (const vfs_path_t * vpath, ssize_t start_position, ssize_t length);
+vfs_path_t *vfs_path_vtokens_get (const vfs_path_t * vpath, ssize_t start_position, ssize_t length);
+
+void vfs_path_add_element (vfs_path_t * vpath, const vfs_path_element_t * path_element);
+const vfs_path_element_t *vfs_path_get_by_index (const vfs_path_t * path, int element_index);
+vfs_path_element_t *vfs_path_element_clone (const vfs_path_element_t * element);
+void vfs_path_element_free (vfs_path_element_t * element);
+
+struct vfs_class *vfs_prefix_to_class (const char *prefix);
+
+#ifdef HAVE_CHARSET
+gboolean vfs_path_element_need_cleanup_converter (const vfs_path_element_t * element);
+vfs_path_t *vfs_path_change_encoding (vfs_path_t * vpath, const char *encoding);
+#endif
+
+char *vfs_path_serialize (const vfs_path_t * vpath, GError ** error);
+vfs_path_t *vfs_path_deserialize (const char *data, GError ** error);
+
+GString *vfs_path_build_url_params_str (const vfs_path_element_t * element, gboolean keep_password);
+GString *vfs_path_element_build_pretty_path_str (const vfs_path_element_t * element);
+
+size_t vfs_path_len (const vfs_path_t * vpath);
+gboolean vfs_path_equal (const vfs_path_t * vpath1, const vfs_path_t * vpath2);
+gboolean vfs_path_equal_len (const vfs_path_t * vpath1, const vfs_path_t * vpath2, size_t len);
+vfs_path_t *vfs_path_to_absolute (const vfs_path_t * vpath);
+
+/*** inline functions ****************************************************************************/
+
+static inline gboolean
+vfs_path_element_valid (const vfs_path_element_t * element)
+{
+ return (element != NULL && element->class != NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline const char *
+vfs_path_get_last_path_str (const vfs_path_t * vpath)
+{
+ const vfs_path_element_t *element;
+ if (vpath == NULL)
+ return NULL;
+ element = vfs_path_get_by_index (vpath, -1);
+ return (element != NULL) ? element->path : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline const struct vfs_class *
+vfs_path_get_last_path_vfs (const vfs_path_t * vpath)
+{
+ const vfs_path_element_t *element;
+ if (vpath == NULL)
+ return NULL;
+ element = vfs_path_get_by_index (vpath, -1);
+ return (element != NULL) ? element->class : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Convert vfs_path_t to string representation.
+ *
+ * @param vpath pointer to vfs_path_t object
+ *
+ * @return pointer to constant string
+ */
+
+static inline const char *
+vfs_path_as_str (const vfs_path_t * vpath)
+{
+ return (vpath == NULL ? NULL : vpath->str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#endif
diff --git a/lib/vfs/utilvfs.c b/lib/vfs/utilvfs.c
new file mode 100644
index 0000000..162eb4c
--- /dev/null
+++ b/lib/vfs/utilvfs.c
@@ -0,0 +1,374 @@
+/*
+ Utilities for VFS modules.
+
+ Copyright (C) 1988-2023
+ Free Software Foundation, Inc.
+
+ Copyright (C) 1995, 1996 Miguel de Icaza
+
+ 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: Utilities for VFS modules
+ * \author Miguel de Icaza
+ * \date 1995, 1996
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/unixcompat.h"
+#include "lib/widget.h" /* message() */
+#include "lib/strutil.h" /* INVALID_CONV */
+
+#include "vfs.h"
+#include "utilvfs.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#ifndef TUNMLEN
+#define TUNMLEN 256
+#endif
+#ifndef TGNMLEN
+#define TGNMLEN 256
+#endif
+
+#define MC_HISTORY_VFS_PASSWORD "mc.vfs.password"
+
+/*
+ * FIXME2, the "-993" is to reduce the chance of a hit on the first lookup.
+ */
+#define GUID_DEFAULT_CONST -993
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/** Get current username
+ *
+ * @return g_malloc()ed string with the name of the currently logged in
+ * user ("anonymous" if uid is not registered in the system)
+ */
+
+char *
+vfs_get_local_username (void)
+{
+ struct passwd *p_i;
+
+ p_i = getpwuid (geteuid ());
+
+ /* Unknown UID, strange */
+ return (p_i != NULL && p_i->pw_name != NULL) ? g_strdup (p_i->pw_name) : g_strdup ("anonymous");
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Look up a user or group name from a uid/gid, maintaining a cache.
+ * FIXME, for now it's a one-entry cache.
+ * This file should be modified for non-unix systems to do something
+ * reasonable.
+ */
+
+int
+vfs_finduid (const char *uname)
+{
+ static int saveuid = GUID_DEFAULT_CONST;
+ static char saveuname[TUNMLEN] = "\0";
+
+ size_t uname_len;
+
+ uname_len = strlen (uname);
+
+ if (uname[0] != saveuname[0] /* Quick test w/o proc call */
+ || strncmp (uname, saveuname, MIN (uname_len, TUNMLEN - 1)) != 0)
+ {
+ struct passwd *pw;
+
+ g_strlcpy (saveuname, uname, TUNMLEN);
+ pw = getpwnam (uname);
+ if (pw != NULL)
+ saveuid = pw->pw_uid;
+ else
+ {
+ static int my_uid = GUID_DEFAULT_CONST;
+
+ if (my_uid < 0)
+ my_uid = getuid ();
+
+ saveuid = my_uid;
+ }
+ }
+
+ return saveuid;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_findgid (const char *gname)
+{
+ static int savegid = GUID_DEFAULT_CONST;
+ static char savegname[TGNMLEN] = "\0";
+
+ size_t gname_len;
+
+ gname_len = strlen (gname);
+
+ if (gname[0] != savegname[0] /* Quick test w/o proc call */
+ || strncmp (gname, savegname, MIN (gname_len, TGNMLEN - 1)) != 0)
+ {
+ struct group *gr;
+
+ g_strlcpy (savegname, gname, TGNMLEN);
+ gr = getgrnam (gname);
+ if (gr != NULL)
+ savegid = gr->gr_gid;
+ else
+ {
+ static int my_gid = GUID_DEFAULT_CONST;
+
+ if (my_gid < 0)
+ my_gid = getgid ();
+
+ savegid = my_gid;
+ }
+ }
+
+ return savegid;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create a temporary file with a name resembling the original.
+ * This is needed e.g. for local copies requested by extfs.
+ * Some extfs scripts may look at the extension.
+ * We also protect stupid scripts against dangerous names.
+ */
+
+int
+vfs_mkstemps (vfs_path_t ** pname_vpath, const char *prefix, const char *param_basename)
+{
+ const char *p;
+ GString *suffix;
+ int shift;
+ int fd;
+
+ /* Strip directories */
+ p = strrchr (param_basename, PATH_SEP);
+ if (p == NULL)
+ p = param_basename;
+ else
+ p++;
+
+ /* Protection against very long names */
+ shift = strlen (p) - (MC_MAXPATHLEN - 16);
+ if (shift > 0)
+ p += shift;
+
+ suffix = g_string_sized_new (32);
+
+ /* Protection against unusual characters */
+ for (; *p != '\0' && *p != '#'; p++)
+ if (strchr (".-_@", *p) != NULL || g_ascii_isalnum (*p))
+ g_string_append_c (suffix, *p);
+
+ fd = mc_mkstemps (pname_vpath, prefix, suffix->str);
+ g_string_free (suffix, TRUE);
+
+ return fd;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Extract the hostname and username from the path
+ *
+ * Format of the path is [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
+ * ftp://joe:password@foo.se
+ *
+ * @param path is an input string to be parsed
+ * @param default_port is an input default port
+ * @param flags are parsing modifier flags (@see vfs_url_flags_t)
+ *
+ * @return g_malloc()ed url info.
+ * If the user is empty, e.g. ftp://@roxanne/private, and URL_USE_ANONYMOUS
+ * is not set, then the current login name is supplied.
+ * Return value is a g_malloc()ed structure with the pathname relative to the
+ * host.
+ */
+
+vfs_path_element_t *
+vfs_url_split (const char *path, int default_port, vfs_url_flags_t flags)
+{
+ vfs_path_element_t *path_element;
+
+ char *pcopy;
+ size_t pcopy_len;
+ const char *pend;
+ char *colon, *at, *rest;
+
+ path_element = g_new0 (vfs_path_element_t, 1);
+ path_element->port = default_port;
+
+ pcopy_len = strlen (path);
+ pcopy = g_strndup (path, pcopy_len);
+ pend = pcopy + pcopy_len;
+
+ if ((flags & URL_NOSLASH) == 0)
+ {
+ char *dir;
+
+ /* locate path component */
+ dir = strchr (pcopy, PATH_SEP);
+
+ if (dir == NULL)
+ path_element->path = g_strdup (PATH_SEP_STR);
+ else
+ {
+ path_element->path = g_strndup (dir, pcopy_len - (size_t) (dir - pcopy));
+ *dir = '\0';
+ }
+ }
+
+ /* search for any possible user */
+ at = strrchr (pcopy, '@');
+
+ /* We have a username */
+ if (at == NULL)
+ rest = pcopy;
+ else
+ {
+ char *inner_colon;
+
+ *at = '\0';
+ inner_colon = strchr (pcopy, ':');
+ if (inner_colon != NULL)
+ {
+ *inner_colon = '\0';
+ inner_colon++;
+ path_element->password = g_strdup (inner_colon);
+ }
+
+ if (*pcopy != '\0')
+ path_element->user = g_strdup (pcopy);
+
+ if (pend == at + 1)
+ rest = at;
+ else
+ rest = at + 1;
+ }
+
+ if ((flags & URL_USE_ANONYMOUS) == 0)
+ {
+ g_free (path_element->user);
+ path_element->user = vfs_get_local_username ();
+ }
+ /* Check if the host comes with a port spec, if so, chop it */
+ if (*rest != '[')
+ colon = strchr (rest, ':');
+ else
+ {
+ colon = strchr (++rest, ']');
+ if (colon != NULL)
+ {
+ colon[0] = '\0';
+ colon[1] = '\0';
+ colon++;
+ }
+ else
+ {
+ vfs_path_element_free (path_element);
+ g_free (pcopy);
+ return NULL;
+ }
+ }
+
+ if (colon != NULL)
+ {
+ *colon = '\0';
+ /* cppcheck-suppress invalidscanf */
+ if (sscanf (colon + 1, "%d", &path_element->port) == 1)
+ {
+ if (path_element->port <= 0 || path_element->port >= 65536)
+ path_element->port = default_port;
+ }
+ else
+ while (*(++colon) != '\0')
+ {
+ switch (*colon)
+ {
+ case 'C':
+ path_element->port = 1;
+ break;
+ case 'r':
+ path_element->port = 2;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ path_element->host = g_strdup (rest);
+ g_free (pcopy);
+#ifdef HAVE_CHARSET
+ path_element->dir.converter = INVALID_CONV;
+#endif
+
+ return path_element;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void __attribute__ ((noreturn)) vfs_die (const char *m)
+{
+ message (D_ERROR, _("Internal error:"), "%s", m);
+ exit (EXIT_FAILURE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+vfs_get_password (const char *msg)
+{
+ return input_dialog (msg, _("Password:"), MC_HISTORY_VFS_PASSWORD, INPUT_PASSWORD,
+ INPUT_COMPLETE_NONE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/vfs/utilvfs.h b/lib/vfs/utilvfs.h
new file mode 100644
index 0000000..d50d4b6
--- /dev/null
+++ b/lib/vfs/utilvfs.h
@@ -0,0 +1,64 @@
+
+/**
+ * \file
+ * \brief Header: Utilities for VFS modules
+ * \author Miguel de Icaza
+ * \date 1995, 1996
+ */
+
+#ifndef MC_VFS_UTILVFS_H
+#define MC_VFS_UTILVFS_H
+
+#include <sys/stat.h>
+
+#include "lib/global.h"
+#include "path.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/** Bit flags for vfs_url_split()
+ *
+ * Modify parsing parameters according to flag meaning.
+ * @see vfs_url_split()
+ */
+typedef enum
+{
+ URL_FLAGS_NONE = 0,
+ URL_USE_ANONYMOUS = 1, /**< if set, empty *user will contain NULL instead of current */
+ URL_NOSLASH = 2 /**< if set, 'proto://' part in url is not searched */
+} vfs_url_flags_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+int vfs_finduid (const char *name);
+int vfs_findgid (const char *name);
+
+vfs_path_element_t *vfs_url_split (const char *path, int default_port, vfs_url_flags_t flags);
+int vfs_split_text (char *p);
+
+int vfs_mkstemps (vfs_path_t ** pname_vpath, const char *prefix, const char *basename);
+void vfs_die (const char *msg);
+char *vfs_get_password (const char *msg);
+
+char *vfs_get_local_username (void);
+
+gboolean vfs_parse_filetype (const char *s, size_t * ret_skipped, mode_t * ret_type);
+gboolean vfs_parse_fileperms (const char *s, size_t * ret_skipped, mode_t * ret_perms);
+gboolean vfs_parse_filemode (const char *s, size_t * ret_skipped, mode_t * ret_mode);
+gboolean vfs_parse_raw_filemode (const char *s, size_t * ret_skipped, mode_t * ret_mode);
+
+void vfs_parse_ls_lga_init (void);
+gboolean vfs_parse_ls_lga (const char *p, struct stat *s, char **filename, char **linkname,
+ size_t * filename_pos);
+size_t vfs_parse_ls_lga_get_final_spaces (void);
+gboolean vfs_parse_month (const char *str, struct tm *tim);
+int vfs_parse_filedate (int idx, time_t * t);
+
+/*** inline functions ****************************************************************************/
+#endif
diff --git a/lib/vfs/vfs.c b/lib/vfs/vfs.c
new file mode 100644
index 0000000..ad57189
--- /dev/null
+++ b/lib/vfs/vfs.c
@@ -0,0 +1,775 @@
+/*
+ Virtual File System switch code
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by: 1995 Miguel de Icaza
+ Jakub Jelinek, 1995
+ Pavel Machek, 1998
+ Slava Zanko <slavazanko@gmail.com>, 2011-2013
+ Andrew Borodin <aborodin@vmail.ru>, 2011-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 switch code
+ * \author Miguel de Icaza
+ * \author Jakub Jelinek
+ * \author Pavel Machek
+ * \date 1995, 1998
+ * \warning functions like extfs_lstat() have right to destroy any
+ * strings you pass to them. This is actually ok as you g_strdup what
+ * you are passing to them, anyway; still, beware.
+ *
+ * Namespace: exports *many* functions with vfs_ prefix; exports
+ * parse_ls_lga and friends which do not have that prefix.
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <stdlib.h>
+
+#ifdef __linux__
+#ifdef HAVE_LINUX_FS_H
+#include <linux/fs.h>
+#endif /* HAVE_LINUX_FS_H */
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif /* HAVE_SYS_IOCTL_H */
+#endif /* __linux__ */
+
+#include "lib/global.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+#include "lib/widget.h" /* message() */
+#include "lib/event.h"
+
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "vfs.h"
+#include "utilvfs.h"
+#include "gc.h"
+
+/* TODO: move it to the separate .h */
+extern struct vfs_dirent *mc_readdir_result;
+extern GPtrArray *vfs__classes_list;
+extern GString *vfs_str_buffer;
+extern vfs_class *current_vfs;
+
+/*** global variables ****************************************************************************/
+
+struct vfs_dirent *mc_readdir_result = NULL;
+GPtrArray *vfs__classes_list = NULL;
+GString *vfs_str_buffer = NULL;
+vfs_class *current_vfs = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define VFS_FIRST_HANDLE 100
+
+/*** file scope type declarations ****************************************************************/
+
+struct vfs_openfile
+{
+ int handle;
+ vfs_class *vclass;
+ void *fsinfo;
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/** They keep track of the current directory */
+static vfs_path_t *current_path = NULL;
+
+static GPtrArray *vfs_openfiles = NULL;
+static long vfs_free_handle_list = -1;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/* now used only by vfs_translate_path, but could be used in other vfs
+ * plugin to automatic detect encoding
+ * path - path to translate
+ * size - how many bytes from path translate
+ * defcnv - converter, that is used as default, when path does not contain any
+ * #enc: substring
+ * buffer - used to store result of translation
+ */
+
+static estr_t
+_vfs_translate_path (const char *path, int size, GIConv defcnv, GString * buffer)
+{
+ estr_t state = ESTR_SUCCESS;
+#ifdef HAVE_CHARSET
+ const char *semi;
+
+ if (size == 0)
+ return ESTR_SUCCESS;
+
+ size = (size > 0) ? size : (signed int) strlen (path);
+
+ /* try found /#enc: */
+ semi = g_strrstr_len (path, size, VFS_ENCODING_PREFIX);
+ if (semi != NULL && (semi == path || IS_PATH_SEP (semi[-1])))
+ {
+ char encoding[16];
+ const char *slash;
+ GIConv coder = INVALID_CONV;
+ int ms;
+
+ /* first must be translated part before #enc: */
+ ms = semi - path;
+
+ state = _vfs_translate_path (path, ms, defcnv, buffer);
+
+ if (state != ESTR_SUCCESS)
+ return state;
+
+ /* now can be translated part after #enc: */
+ semi += strlen (VFS_ENCODING_PREFIX); /* skip "#enc:" */
+ slash = strchr (semi, PATH_SEP);
+ /* ignore slashes after size; */
+ if (slash - path >= size)
+ slash = NULL;
+
+ ms = (slash != NULL) ? slash - semi : (int) strlen (semi);
+ ms = MIN ((unsigned int) ms, sizeof (encoding) - 1);
+ /* limit encoding size (ms) to path size (size) */
+ if (semi + ms > path + size)
+ ms = path + size - semi;
+ memcpy (encoding, semi, ms);
+ encoding[ms] = '\0';
+
+ if (is_supported_encoding (encoding))
+ coder = str_crt_conv_to (encoding);
+
+ if (coder != INVALID_CONV)
+ {
+ if (slash != NULL)
+ state = str_vfs_convert_to (coder, slash + 1, path + size - slash - 1, buffer);
+ str_close_conv (coder);
+ return state;
+ }
+
+ errno = EINVAL;
+ state = ESTR_FAILURE;
+ }
+ else
+ {
+ /* path can be translated whole at once */
+ state = str_vfs_convert_to (defcnv, path, size, buffer);
+ }
+#else
+ (void) size;
+ (void) defcnv;
+
+ g_string_assign (buffer, path);
+#endif /* HAVE_CHARSET */
+
+ return state;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_openfile *
+vfs_get_openfile (int handle)
+{
+ struct vfs_openfile *h;
+
+ if (handle < VFS_FIRST_HANDLE || (guint) (handle - VFS_FIRST_HANDLE) >= vfs_openfiles->len)
+ return NULL;
+
+ h = (struct vfs_openfile *) g_ptr_array_index (vfs_openfiles, handle - VFS_FIRST_HANDLE);
+ if (h == NULL)
+ return NULL;
+
+ g_assert (h->handle == handle);
+
+ return h;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+vfs_test_current_dir (const vfs_path_t * vpath)
+{
+ struct stat my_stat, my_stat2;
+
+ return (mc_global.vfs.cd_symlinks && mc_stat (vpath, &my_stat) == 0
+ && mc_stat (vfs_get_raw_current_dir (), &my_stat2) == 0
+ && my_stat.st_ino == my_stat2.st_ino && my_stat.st_dev == my_stat2.st_dev);
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/** Free open file data for given file handle */
+
+void
+vfs_free_handle (int handle)
+{
+ const int idx = handle - VFS_FIRST_HANDLE;
+
+ if (handle >= VFS_FIRST_HANDLE && (guint) idx < vfs_openfiles->len)
+ {
+ struct vfs_openfile *h;
+
+ h = (struct vfs_openfile *) g_ptr_array_index (vfs_openfiles, idx);
+ g_free (h);
+ g_ptr_array_index (vfs_openfiles, idx) = (void *) vfs_free_handle_list;
+ vfs_free_handle_list = idx;
+ }
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+/** Find VFS class by file handle */
+
+struct vfs_class *
+vfs_class_find_by_handle (int handle, void **fsinfo)
+{
+ struct vfs_openfile *h;
+
+ h = vfs_get_openfile (handle);
+
+ if (h == NULL)
+ return NULL;
+
+ if (fsinfo != NULL)
+ *fsinfo = h->fsinfo;
+
+ return h->vclass;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Create new VFS handle and put it to the list
+ */
+
+int
+vfs_new_handle (struct vfs_class *vclass, void *fsinfo)
+{
+ struct vfs_openfile *h;
+
+ h = g_new (struct vfs_openfile, 1);
+ h->fsinfo = fsinfo;
+ h->vclass = vclass;
+
+ /* Allocate the first free handle */
+ h->handle = vfs_free_handle_list;
+ if (h->handle == -1)
+ {
+ /* No free allocated handles, allocate one */
+ h->handle = vfs_openfiles->len;
+ g_ptr_array_add (vfs_openfiles, h);
+ }
+ else
+ {
+ vfs_free_handle_list = (long) g_ptr_array_index (vfs_openfiles, vfs_free_handle_list);
+ g_ptr_array_index (vfs_openfiles, h->handle) = h;
+ }
+
+ h->handle += VFS_FIRST_HANDLE;
+ return h->handle;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_ferrno (struct vfs_class *vfs)
+{
+ return vfs->ferrno ? (*vfs->ferrno) (vfs) : E_UNKNOWN;
+ /* Hope that error message is obscure enough ;-) */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+vfs_register_class (struct vfs_class * vfs)
+{
+ if (vfs->init != NULL) /* vfs has own initialization function */
+ if (!vfs->init (vfs)) /* but it failed */
+ return FALSE;
+
+ g_ptr_array_add (vfs__classes_list, vfs);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_unregister_class (struct vfs_class *vfs)
+{
+ if (vfs->done != NULL)
+ vfs->done (vfs);
+
+ g_ptr_array_remove (vfs__classes_list, vfs);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Strip known vfs suffixes from a filename (possible improvement: strip
+ * suffix from last path component).
+ * \return a malloced string which has to be freed.
+ */
+
+char *
+vfs_strip_suffix_from_filename (const char *filename)
+{
+ char *semi, *p;
+
+ if (filename == NULL)
+ vfs_die ("vfs_strip_suffix_from_path got NULL: impossible");
+
+ p = g_strdup (filename);
+ semi = g_strrstr (p, VFS_PATH_URL_DELIMITER);
+ if (semi != NULL)
+ {
+ char *vfs_prefix;
+
+ *semi = '\0';
+ vfs_prefix = strrchr (p, PATH_SEP);
+ if (vfs_prefix == NULL)
+ *semi = *VFS_PATH_URL_DELIMITER;
+ else
+ *vfs_prefix = '\0';
+ }
+
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+vfs_translate_path (const char *path)
+{
+ estr_t state;
+
+ g_string_set_size (vfs_str_buffer, 0);
+ state = _vfs_translate_path (path, -1, str_cnv_from_term, vfs_str_buffer);
+ return (state != ESTR_FAILURE) ? vfs_str_buffer->str : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+vfs_translate_path_n (const char *path)
+{
+ const char *result;
+
+ result = vfs_translate_path (path);
+ return g_strdup (result);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get current directory without any OS calls.
+ *
+ * @return string contains current path
+ */
+
+const char *
+vfs_get_current_dir (void)
+{
+ return current_path->str;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get current directory without any OS calls.
+ *
+ * @return newly allocated string contains current path
+ */
+
+char *
+vfs_get_current_dir_n (void)
+{
+ return g_strdup (current_path->str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get raw current directory object without any OS calls.
+ *
+ * @return object contain current path
+ */
+
+const vfs_path_t *
+vfs_get_raw_current_dir (void)
+{
+ return current_path;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Set current directory object.
+ *
+ * @param vpath new path
+ */
+void
+vfs_set_raw_current_dir (const vfs_path_t * vpath)
+{
+ vfs_path_free (current_path, TRUE);
+ current_path = (vfs_path_t *) vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Return TRUE is the current VFS class is local */
+
+gboolean
+vfs_current_is_local (void)
+{
+ return (current_vfs->flags & VFSF_LOCAL) != 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Return flags of the VFS class of the given filename */
+
+vfs_flags_t
+vfs_file_class_flags (const vfs_path_t * vpath)
+{
+ const vfs_path_element_t *path_element;
+
+ path_element = vfs_path_get_by_index (vpath, -1);
+ if (!vfs_path_element_valid (path_element))
+ return VFSF_UNKNOWN;
+
+ return path_element->class->flags;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init (void)
+{
+ /* create the VFS handle arrays */
+ vfs__classes_list = g_ptr_array_new ();
+
+ /* create the VFS handle array */
+ vfs_openfiles = g_ptr_array_new ();
+
+ vfs_str_buffer = g_string_new ("");
+
+ mc_readdir_result = vfs_dirent_init (NULL, "", -1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_setup_work_dir (void)
+{
+ vfs_setup_cwd ();
+
+ /* FIXME: is we really need for this check? */
+ /*
+ if (strlen (current_dir) > MC_MAXPATHLEN - 2)
+ vfs_die ("Current dir too long.\n");
+ */
+
+ current_vfs = VFS_CLASS (vfs_path_get_last_path_vfs (current_path));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_shut (void)
+{
+ guint i;
+
+ vfs_gc_done ();
+
+ vfs_set_raw_current_dir (NULL);
+
+ for (i = 0; i < vfs__classes_list->len; i++)
+ {
+ struct vfs_class *vfs = VFS_CLASS (g_ptr_array_index (vfs__classes_list, i));
+
+ if (vfs->done != NULL)
+ vfs->done (vfs);
+ }
+
+ /* NULL-ize pointers to make unit tests happy */
+ g_ptr_array_free (vfs_openfiles, TRUE);
+ vfs_openfiles = NULL;
+ g_ptr_array_free (vfs__classes_list, TRUE);
+ vfs__classes_list = NULL;
+ g_string_free (vfs_str_buffer, TRUE);
+ vfs_str_buffer = NULL;
+ current_vfs = NULL;
+ vfs_free_handle_list = -1;
+ vfs_dirent_free (mc_readdir_result);
+ mc_readdir_result = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Init or create vfs_dirent structure
+ *
+ * @d vfs_dirent structure to init. If NULL, new structure is created.
+ * @fname file name
+ * @ino file inode number
+ *
+ * @return pointer to d if d isn't NULL, or pointer to newly created structure.
+ */
+
+struct vfs_dirent *
+vfs_dirent_init (struct vfs_dirent *d, const char *fname, ino_t ino)
+{
+ struct vfs_dirent *ret = d;
+
+ if (ret == NULL)
+ ret = g_new0 (struct vfs_dirent, 1);
+
+ if (ret->d_name_str == NULL)
+ ret->d_name_str = g_string_sized_new (MC_MAXFILENAMELEN);
+
+ vfs_dirent_assign (ret, fname, ino);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Assign members of vfs_dirent structure
+ *
+ * @d vfs_dirent structure for assignment
+ * @fname file name
+ * @ino file inode number
+ */
+
+void
+vfs_dirent_assign (struct vfs_dirent *d, const char *fname, ino_t ino)
+{
+ g_string_assign (d->d_name_str, fname);
+ d->d_name = d->d_name_str->str;
+ d->d_ino = ino;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Destroy vfs_dirent structure
+ *
+ * @d vfs_dirent structure to destroy.
+ */
+
+void
+vfs_dirent_free (struct vfs_dirent *d)
+{
+ g_string_free (d->d_name_str, TRUE);
+ g_free (d);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * These ones grab information from the VFS
+ * and handles them to an upper layer
+ */
+
+void
+vfs_fill_names (fill_names_f func)
+{
+ guint i;
+
+ for (i = 0; i < vfs__classes_list->len; i++)
+ {
+ struct vfs_class *vfs = VFS_CLASS (g_ptr_array_index (vfs__classes_list, i));
+
+ if (vfs->fill_names != NULL)
+ vfs->fill_names (vfs, func);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+vfs_file_is_local (const vfs_path_t * vpath)
+{
+ return (vfs_file_class_flags (vpath) & VFSF_LOCAL) != 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_print_message (const char *msg, ...)
+{
+ ev_vfs_print_message_t event_data;
+ va_list ap;
+
+ va_start (ap, msg);
+ event_data.msg = g_strdup_vprintf (msg, ap);
+ va_end (ap);
+
+ mc_event_raise (MCEVENT_GROUP_CORE, "vfs_print_message", (gpointer) & event_data);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * If it's local, reread the current directory
+ * from the OS.
+ */
+
+void
+vfs_setup_cwd (void)
+{
+ char *current_dir;
+ vfs_path_t *tmp_vpath;
+ const struct vfs_class *me;
+
+ if (vfs_get_raw_current_dir () == NULL)
+ {
+ current_dir = g_get_current_dir ();
+ vfs_set_raw_current_dir (vfs_path_from_str (current_dir));
+ g_free (current_dir);
+
+ current_dir = getenv ("PWD");
+ tmp_vpath = vfs_path_from_str (current_dir);
+
+ if (tmp_vpath != NULL)
+ {
+ if (vfs_test_current_dir (tmp_vpath))
+ vfs_set_raw_current_dir (tmp_vpath);
+ else
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+ }
+
+ me = vfs_path_get_last_path_vfs (vfs_get_raw_current_dir ());
+ if ((me->flags & VFSF_LOCAL) != 0)
+ {
+ current_dir = g_get_current_dir ();
+ tmp_vpath = vfs_path_from_str (current_dir);
+ g_free (current_dir);
+
+ if (tmp_vpath != NULL)
+ {
+ /* One of directories in the path is not readable */
+
+ /* Check if it is O.K. to use the current_dir */
+ if (!vfs_test_current_dir (tmp_vpath))
+ vfs_set_raw_current_dir (tmp_vpath);
+ else
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Return current directory. If it's local, reread the current directory
+ * from the OS.
+ */
+
+char *
+vfs_get_cwd (void)
+{
+ vfs_setup_cwd ();
+ return vfs_get_current_dir_n ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Preallocate space for file in new place for ensure that file
+ * will be fully copied with less fragmentation.
+ *
+ * @param dest_vfs_fd mc VFS file handler
+ * @param src_fsize source file size
+ * @param dest_fsize destination file size (if destination exists, otherwise should be 0)
+ *
+ * @return 0 if success and non-zero otherwise.
+ * Note: function doesn't touch errno global variable.
+ */
+
+int
+vfs_preallocate (int dest_vfs_fd, off_t src_fsize, off_t dest_fsize)
+{
+#ifndef HAVE_POSIX_FALLOCATE
+ (void) dest_vfs_fd;
+ (void) src_fsize;
+ (void) dest_fsize;
+ return 0;
+
+#else /* HAVE_POSIX_FALLOCATE */
+ void *dest_fd = NULL;
+ struct vfs_class *dest_class;
+
+ if (src_fsize == 0)
+ return 0;
+
+ dest_class = vfs_class_find_by_handle (dest_vfs_fd, &dest_fd);
+ if ((dest_class->flags & VFSF_LOCAL) == 0 || dest_fd == NULL)
+ return 0;
+
+ return posix_fallocate (*(int *) dest_fd, dest_fsize, src_fsize - dest_fsize);
+
+#endif /* HAVE_POSIX_FALLOCATE */
+}
+
+ /* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_clone_file (int dest_vfs_fd, int src_vfs_fd)
+{
+#ifdef FICLONE
+ void *dest_fd = NULL;
+ void *src_fd = NULL;
+ struct vfs_class *dest_class;
+ struct vfs_class *src_class;
+
+ dest_class = vfs_class_find_by_handle (dest_vfs_fd, &dest_fd);
+ if ((dest_class->flags & VFSF_LOCAL) == 0)
+ {
+ errno = ENOTSUP;
+ return (-1);
+ }
+ if (dest_fd == NULL)
+ {
+ errno = EBADF;
+ return (-1);
+ }
+
+ src_class = vfs_class_find_by_handle (src_vfs_fd, &src_fd);
+ if ((src_class->flags & VFSF_LOCAL) == 0)
+ {
+ errno = ENOTSUP;
+ return (-1);
+ }
+ if (src_fd == NULL)
+ {
+ errno = EBADF;
+ return (-1);
+ }
+
+ return ioctl (*(int *) dest_fd, FICLONE, *(int *) src_fd);
+#else
+ (void) dest_vfs_fd;
+ (void) src_vfs_fd;
+ errno = ENOTSUP;
+ return (-1);
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/vfs/vfs.h b/lib/vfs/vfs.h
new file mode 100644
index 0000000..ee78ff5
--- /dev/null
+++ b/lib/vfs/vfs.h
@@ -0,0 +1,343 @@
+
+/**
+ * \file
+ * \brief Header: Virtual File System switch code
+ */
+
+#ifndef MC__VFS_VFS_H
+#define MC__VFS_VFS_H
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h> /* DIR */
+#ifdef HAVE_UTIMENSAT
+#include <sys/time.h>
+#elif defined (HAVE_UTIME_H)
+#include <utime.h>
+#endif
+#include <stdio.h>
+#include <unistd.h>
+#include <stddef.h>
+
+#include "lib/global.h"
+
+#include "path.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define VFS_CLASS(a) ((struct vfs_class *) (a))
+
+#define VFS_ENCODING_PREFIX "#enc:"
+
+#define O_ALL (O_CREAT | O_EXCL | O_NOCTTY | O_NDELAY | O_SYNC | O_WRONLY | O_RDWR | O_RDONLY)
+/* Midnight commander code should _not_ use other flags than those
+ listed above and O_APPEND */
+
+#if (O_ALL & O_APPEND)
+#warning "Unexpected problem with flags, O_LINEAR disabled, contact pavel@ucw.cz"
+#define O_LINEAR 0
+#define IS_LINEAR(a) 0
+#define NO_LINEAR(a) a
+#else
+#define O_LINEAR O_APPEND
+#define IS_LINEAR(a) ((a) == (O_RDONLY | O_LINEAR)) /* Return only 0 and 1 ! */
+#define NO_LINEAR(a) (((a) == (O_RDONLY | O_LINEAR)) ? O_RDONLY : (a))
+#endif
+
+/* O_LINEAR is strange beast, be careful. If you open file asserting
+ * O_RDONLY | O_LINEAR, you promise:
+ *
+ * a) to read file linearly from beginning to the end
+ * b) not to open another file before you close this one
+ * (this will likely go away in future)
+ * as a special gift, you may
+ * c) lseek() immediately after open(), giving ftpfs chance to
+ * reget. Be warned that this lseek() can fail, and you _have_
+ * to handle that gratefully.
+ *
+ * O_LINEAR allows filesystems not to create temporary file in some
+ * cases (ftp transfer). -- pavel@ucw.cz
+ */
+
+/* And now some defines for our errors. */
+
+#ifdef ENOMSG
+#define E_UNKNOWN ENOMSG /* if we do not know what error happened */
+#else
+#define E_UNKNOWN EIO /* if we do not know what error happened */
+#endif
+
+#ifdef EREMOTEIO
+#define E_REMOTE EREMOTEIO /* if other side of ftp/fish reports error */
+#else
+#define E_REMOTE ENETUNREACH /* :-( there's no EREMOTEIO on some systems */
+#endif
+
+#ifdef EPROTO
+#define E_PROTO EPROTO /* if other side fails to follow protocol */
+#else
+#define E_PROTO EIO
+#endif
+
+/**
+ * This is the type of callback function passed to vfs_fill_names.
+ * It gets the name of the virtual file system as its first argument.
+ * See also:
+ * vfs_fill_names().
+ */
+typedef void (*fill_names_f) (const char *);
+
+typedef void *vfsid;
+
+#ifdef HAVE_UTIMENSAT
+typedef struct timespec mc_timesbuf_t[2];
+#else
+typedef struct utimbuf mc_timesbuf_t;
+#endif
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ VFSF_UNKNOWN = 0,
+ VFSF_LOCAL = 1 << 0, /* Class is local (not virtual) filesystem */
+ VFSF_NOLINKS = 1 << 1, /* Hard links not supported */
+
+ VFSF_REMOTE = 1 << 2,
+ VFSF_READONLY = 1 << 3,
+ VFSF_USETMP = 1 << 4
+} vfs_flags_t;
+
+/* Operations for mc_ctl - on open file */
+enum
+{
+ VFS_CTL_IS_NOTREADY
+};
+
+/* Operations for mc_setctl - on path */
+enum
+{
+ VFS_SETCTL_FORGET,
+ VFS_SETCTL_RUN,
+ VFS_SETCTL_LOGFILE,
+ VFS_SETCTL_FLUSH, /* invalidate directory cache */
+
+ /* Setting this makes vfs layer give out potentially incorrect data,
+ but it also makes some operations much faster. Use with caution. */
+ VFS_SETCTL_STALE_DATA
+};
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct vfs_class
+{
+ const char *name; /* "FIles over SHell" */
+ vfs_flags_t flags;
+ const char *prefix; /* "fish:" */
+ int verrno; /* can't use errno because glibc2 might define errno as function */
+ gboolean flush; /* if set to TRUE, invalidate directory cache */
+ FILE *logfile;
+
+ /* *INDENT-OFF* */
+ int (*init) (struct vfs_class * me);
+ void (*done) (struct vfs_class * me);
+
+ /**
+ * The fill_names method shall call the callback function for every
+ * filesystem name that this vfs module supports.
+ */
+ void (*fill_names) (struct vfs_class * me, fill_names_f);
+
+ /**
+ * The which() method shall return the index of the vfs subsystem
+ * or -1 if this vfs cannot handle the given pathname.
+ */
+ int (*which) (struct vfs_class * me, const char *path);
+
+ void *(*open) (const vfs_path_t * vpath, int flags, mode_t mode);
+ int (*close) (void *vfs_info);
+ ssize_t (*read) (void *vfs_info, char *buffer, size_t count);
+ ssize_t (*write) (void *vfs_info, const char *buf, size_t count);
+
+ void *(*opendir) (const vfs_path_t * vpath);
+ struct vfs_dirent *(*readdir) (void *vfs_info);
+ int (*closedir) (void *vfs_info);
+
+ int (*stat) (const vfs_path_t * vpath, struct stat * buf);
+ int (*lstat) (const vfs_path_t * vpath, struct stat * buf);
+ int (*fstat) (void *vfs_info, struct stat * buf);
+
+ int (*chmod) (const vfs_path_t * vpath, mode_t mode);
+ int (*chown) (const vfs_path_t * vpath, uid_t owner, gid_t group);
+
+ int (*fgetflags) (const vfs_path_t * vpath, unsigned long *flags);
+ int (*fsetflags) (const vfs_path_t * vpath, unsigned long flags);
+
+ int (*utime) (const vfs_path_t * vpath, mc_timesbuf_t * times);
+
+ int (*readlink) (const vfs_path_t * vpath, char *buf, size_t size);
+ int (*symlink) (const vfs_path_t * vpath1, const vfs_path_t * vpath2);
+ int (*link) (const vfs_path_t * vpath1, const vfs_path_t * vpath2);
+ int (*unlink) (const vfs_path_t * vpath);
+ int (*rename) (const vfs_path_t * vpath1, const vfs_path_t * vpath2);
+ int (*chdir) (const vfs_path_t * vpath);
+ int (*ferrno) (struct vfs_class * me);
+ off_t (*lseek) (void *vfs_info, off_t offset, int whence);
+ int (*mknod) (const vfs_path_t * vpath, mode_t mode, dev_t dev);
+
+ vfsid (*getid) (const vfs_path_t * vpath);
+
+ gboolean (*nothingisopen) (vfsid id);
+ void (*free) (vfsid id);
+
+ vfs_path_t *(*getlocalcopy) (const vfs_path_t * vpath);
+ int (*ungetlocalcopy) (const vfs_path_t * vpath, const vfs_path_t * local_vpath,
+ gboolean has_changed);
+
+ int (*mkdir) (const vfs_path_t * vpath, mode_t mode);
+ int (*rmdir) (const vfs_path_t * vpath);
+
+ int (*ctl) (void *vfs_info, int ctlop, void *arg);
+ int (*setctl) (const vfs_path_t * vpath, int ctlop, void *arg);
+ /* *INDENT-ON* */
+} vfs_class;
+
+/*
+ * This struct is used instead of standard dirent to hold file name of any length
+ * (not limited to NAME_MAX).
+ */
+struct vfs_dirent
+{
+ /* private */
+ GString *d_name_str;
+
+ /* public */
+ ino_t d_ino;
+ char *d_name; /* Alias of d_name_str->str */
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+extern int vfs_timeout;
+
+#ifdef ENABLE_VFS_NET
+extern int use_netrc;
+#endif
+
+/*** declarations of public functions ************************************************************/
+
+/* lib/vfs/direntry.c: */
+void vfs_init_class (struct vfs_class *vclass, const char *name, vfs_flags_t flags,
+ const char *prefix);
+
+void *vfs_s_open (const vfs_path_t * vpath, int flags, mode_t mode);
+int vfs_s_stat (const vfs_path_t * vpath, struct stat *buf);
+int vfs_s_lstat (const vfs_path_t * vpath, struct stat *buf);
+int vfs_s_fstat (void *fh, struct stat *buf);
+
+void vfs_adjust_stat (struct stat *s);
+
+vfsid vfs_getid (const vfs_path_t * vpath);
+
+void vfs_init (void);
+void vfs_shut (void);
+/* Register a file system class */
+gboolean vfs_register_class (struct vfs_class *vfs);
+void vfs_unregister_class (struct vfs_class *vfs);
+
+void vfs_setup_work_dir (void);
+
+void vfs_timeout_handler (void);
+int vfs_timeouts (void);
+void vfs_expire (gboolean now);
+
+const char *vfs_get_current_dir (void);
+char *vfs_get_current_dir_n (void);
+const vfs_path_t *vfs_get_raw_current_dir (void);
+void vfs_set_raw_current_dir (const vfs_path_t * vpath);
+
+gboolean vfs_current_is_local (void);
+gboolean vfs_file_is_local (const vfs_path_t * vpath);
+
+char *vfs_strip_suffix_from_filename (const char *filename);
+
+vfs_flags_t vfs_file_class_flags (const vfs_path_t * vpath);
+
+/* translate path back to terminal encoding, remove all #enc:
+ * every invalid character is replaced with question mark
+ * return static buffer */
+const char *vfs_translate_path (const char *path);
+/* return new string */
+char *vfs_translate_path_n (const char *path);
+
+void vfs_stamp_path (const vfs_path_t * path);
+
+void vfs_release_path (const vfs_path_t * vpath);
+
+struct vfs_dirent *vfs_dirent_init (struct vfs_dirent *d, const char *fname, ino_t ino);
+void vfs_dirent_assign (struct vfs_dirent *d, const char *fname, ino_t ino);
+void vfs_dirent_free (struct vfs_dirent *d);
+
+void vfs_fill_names (fill_names_f);
+
+/* *INDENT-OFF* */
+void vfs_print_message (const char *msg, ...) G_GNUC_PRINTF (1, 2);
+/* *INDENT-ON* */
+
+int vfs_ferrno (struct vfs_class *vfs);
+
+int vfs_new_handle (struct vfs_class *vclass, void *fsinfo);
+
+struct vfs_class *vfs_class_find_by_handle (int handle, void **fsinfo);
+
+void vfs_free_handle (int handle);
+
+void vfs_setup_cwd (void);
+char *vfs_get_cwd (void);
+
+int vfs_preallocate (int dest_desc, off_t src_fsize, off_t dest_fsize);
+
+int vfs_clone_file (int dest_vfs_fd, int src_vfs_fd);
+
+/**
+ * Interface functions described in interface.c
+ */
+ssize_t mc_read (int handle, void *buffer, size_t count);
+ssize_t mc_write (int handle, const void *buffer, size_t count);
+int mc_utime (const vfs_path_t * vpath, mc_timesbuf_t * times);
+int mc_readlink (const vfs_path_t * vpath, char *buf, size_t bufsiz);
+int mc_close (int handle);
+off_t mc_lseek (int fd, off_t offset, int whence);
+DIR *mc_opendir (const vfs_path_t * vpath);
+struct vfs_dirent *mc_readdir (DIR * dirp);
+int mc_closedir (DIR * dir);
+int mc_stat (const vfs_path_t * vpath, struct stat *buf);
+int mc_mknod (const vfs_path_t * vpath, mode_t mode, dev_t dev);
+int mc_link (const vfs_path_t * vpath1, const vfs_path_t * vpath2);
+int mc_mkdir (const vfs_path_t * vpath, mode_t mode);
+int mc_rmdir (const vfs_path_t * vpath);
+int mc_fstat (int fd, struct stat *buf);
+int mc_lstat (const vfs_path_t * vpath, struct stat *buf);
+int mc_symlink (const vfs_path_t * vpath1, const vfs_path_t * vpath2);
+int mc_rename (const vfs_path_t * vpath1, const vfs_path_t * vpath2);
+int mc_chmod (const vfs_path_t * vpath, mode_t mode);
+int mc_chown (const vfs_path_t * vpath, uid_t owner, gid_t group);
+int mc_fgetflags (const vfs_path_t * vpath, unsigned long *flags);
+int mc_fsetflags (const vfs_path_t * vpath, unsigned long flags);
+int mc_chdir (const vfs_path_t * vpath);
+int mc_unlink (const vfs_path_t * vpath);
+int mc_ctl (int fd, int ctlop, void *arg);
+int mc_setctl (const vfs_path_t * vpath, int ctlop, void *arg);
+int mc_open (const vfs_path_t * vpath, int flags, ...);
+vfs_path_t *mc_getlocalcopy (const vfs_path_t * pathname_vpath);
+int mc_ungetlocalcopy (const vfs_path_t * pathname_vpath, const vfs_path_t * local_vpath,
+ gboolean has_changed);
+int mc_mkstemps (vfs_path_t ** pname_vpath, const char *prefix, const char *suffix);
+
+/* Creating temporary files safely */
+const char *mc_tmpdir (void);
+
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC_VFS_VFS_H */
diff --git a/lib/vfs/xdirentry.h b/lib/vfs/xdirentry.h
new file mode 100644
index 0000000..e1244cb
--- /dev/null
+++ b/lib/vfs/xdirentry.h
@@ -0,0 +1,205 @@
+
+/**
+ * \file
+ * \brief Header: Virtual File System directory structure
+ */
+
+
+#ifndef MC__VFS_XDIRENTRY_H
+#define MC__VFS_XDIRENTRY_H
+
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "lib/global.h" /* GList */
+#include "lib/vfs/path.h" /* vfs_path_t */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define LINK_FOLLOW 15
+#define LINK_NO_FOLLOW -1
+
+/* For vfs_s_find_entry and vfs_s_find_inode */
+#define FL_NONE 0
+#define FL_MKDIR 1
+#define FL_MKFILE 2
+#define FL_DIR 4
+
+/* For open_super */
+#define FL_NO_OPEN 1
+
+/* For vfs_s_entry_from_path */
+#define FL_FOLLOW 1
+#define FL_DIR 4
+
+#define ERRNOR(a, b) do { me->verrno = a; return b; } while (0)
+
+#define VFS_SUBCLASS(a) ((struct vfs_s_subclass *) (a))
+
+#define VFS_SUPER(a) ((struct vfs_s_super *) (a))
+#define CONST_VFS_SUPER(a) ((const struct vfs_s_super *) (a))
+#define VFS_ENTRY(a) ((struct vfs_s_entry *) (a))
+#define VFS_INODE(a) ((struct vfs_s_inode *) (a))
+
+#define VFS_FILE_HANDLER(a) ((vfs_file_handler_t *) a)
+#define VFS_FILE_HANDLER_SUPER(a) VFS_FILE_HANDLER (a)->ino->super
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ LS_NOT_LINEAR = 0,
+ LS_LINEAR_CLOSED = 1,
+ LS_LINEAR_OPEN = 2,
+ LS_LINEAR_PREOPEN = 3
+} vfs_linear_state_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* Single connection or archive */
+struct vfs_s_super
+{
+ struct vfs_class *me;
+ struct vfs_s_inode *root;
+ char *name; /* My name, whatever it means */
+ int fd_usage; /* Number of open files */
+ int ino_usage; /* Usage count of this superblock */
+ gboolean want_stale; /* If set, we do not flush cache properly */
+#ifdef ENABLE_VFS_NET
+ vfs_path_element_t *path_element;
+#endif /* ENABLE_VFS_NET */
+};
+
+/*
+ * Single virtual file - directory entry. The same inode can have many
+ * entries (i.e. hard links), but usually has only one.
+ */
+struct vfs_s_entry
+{
+ struct vfs_s_inode *dir; /* Directory we are in, i.e. our parent */
+ char *name; /* Name of this entry */
+ struct vfs_s_inode *ino; /* ... and its inode */
+ ssize_t leading_spaces; /* number of leading spases in the file name */
+};
+
+/* Single virtual file - inode */
+struct vfs_s_inode
+{
+ struct vfs_s_super *super; /* Archive the file is on */
+ struct vfs_s_entry *ent; /* Our entry in the parent directory -
+ use only for directories because they
+ cannot be hardlinked */
+ GQueue *subdir; /* If this is a directory, its entry. List of vfs_s_entry */
+ struct stat st; /* Parameters of this inode */
+ char *linkname; /* Symlink's contents */
+ char *localname; /* Filename of local file, if we have one */
+ gint64 timestamp; /* Subclass specific */
+ off_t data_offset; /* Subclass specific */
+ void *user_data; /* Subclass specific */
+};
+
+/* Data associated with an open file */
+typedef struct
+{
+ struct vfs_s_inode *ino;
+ off_t pos; /* This is for module's use */
+ int handle; /* This is for module's use, but if != -1, will be mc_close()d */
+ gboolean changed; /* Did this file change? */
+ vfs_linear_state_t linear; /* Is that file open with O_LINEAR? */
+} vfs_file_handler_t;
+
+/*
+ * One of our subclasses (tar, cpio, fish, ftpfs) with data and methods.
+ * Extends vfs_class.
+ */
+struct vfs_s_subclass
+{
+ struct vfs_class base; /* base class */
+
+ GList *supers;
+ int inode_counter;
+ dev_t rdev;
+
+ /* *INDENT-OFF* */
+ int (*init_inode) (struct vfs_class * me, struct vfs_s_inode * ino); /* optional */
+ void (*free_inode) (struct vfs_class * me, struct vfs_s_inode * ino); /* optional */
+ int (*init_entry) (struct vfs_class * me, struct vfs_s_entry * entry); /* optional */
+
+ void *(*archive_check) (const vfs_path_t * vpath); /* optional */
+ int (*archive_same) (const vfs_path_element_t * vpath_element, struct vfs_s_super * psup,
+ const vfs_path_t * vpath, void *cookie);
+ struct vfs_s_super *(*new_archive) (struct vfs_class * me);
+ int (*open_archive) (struct vfs_s_super * psup,
+ const vfs_path_t * vpath, const vfs_path_element_t * vpath_element);
+ void (*free_archive) (struct vfs_class * me, struct vfs_s_super * psup);
+
+ vfs_file_handler_t *(*fh_new) (struct vfs_s_inode * ino, gboolean changed);
+ int (*fh_open) (struct vfs_class * me, vfs_file_handler_t * fh, int flags, mode_t mode);
+ int (*fh_close) (struct vfs_class * me, vfs_file_handler_t * fh);
+ void (*fh_free) (vfs_file_handler_t * fh);
+
+ struct vfs_s_entry *(*find_entry) (struct vfs_class * me,
+ struct vfs_s_inode * root,
+ const char *path, int follow, int flags);
+ int (*dir_load) (struct vfs_class * me, struct vfs_s_inode * ino, const char *path);
+ gboolean (*dir_uptodate) (struct vfs_class * me, struct vfs_s_inode * ino);
+ int (*file_store) (struct vfs_class * me, vfs_file_handler_t * fh, char *path, char *localname);
+
+ int (*linear_start) (struct vfs_class * me, vfs_file_handler_t * fh, off_t from);
+ ssize_t (*linear_read) (struct vfs_class * me, vfs_file_handler_t * fh, void *buf, size_t len);
+ void (*linear_close) (struct vfs_class * me, vfs_file_handler_t * fh);
+ /* *INDENT-ON* */
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/* entries and inodes */
+struct vfs_s_inode *vfs_s_new_inode (struct vfs_class *me,
+ struct vfs_s_super *super, struct stat *initstat);
+void vfs_s_free_inode (struct vfs_class *me, struct vfs_s_inode *ino);
+
+struct vfs_s_entry *vfs_s_new_entry (struct vfs_class *me, const char *name,
+ struct vfs_s_inode *inode);
+void vfs_s_free_entry (struct vfs_class *me, struct vfs_s_entry *ent);
+void vfs_s_insert_entry (struct vfs_class *me, struct vfs_s_inode *dir, struct vfs_s_entry *ent);
+int vfs_s_entry_compare (const void *a, const void *b);
+struct stat *vfs_s_default_stat (struct vfs_class *me, mode_t mode);
+
+struct vfs_s_entry *vfs_s_generate_entry (struct vfs_class *me, const char *name,
+ struct vfs_s_inode *parent, mode_t mode);
+struct vfs_s_inode *vfs_s_find_inode (struct vfs_class *me,
+ const struct vfs_s_super *super,
+ const char *path, int follow, int flags);
+struct vfs_s_inode *vfs_s_find_root (struct vfs_class *me, struct vfs_s_entry *entry);
+
+/* outside interface */
+void vfs_init_subclass (struct vfs_s_subclass *sub, const char *name, vfs_flags_t flags,
+ const char *prefix);
+const char *vfs_s_get_path (const vfs_path_t * vpath, struct vfs_s_super **archive, int flags);
+struct vfs_s_super *vfs_get_super_by_vpath (const vfs_path_t * vpath);
+
+void vfs_s_invalidate (struct vfs_class *me, struct vfs_s_super *super);
+char *vfs_s_fullpath (struct vfs_class *me, struct vfs_s_inode *ino);
+
+void vfs_s_init_fh (vfs_file_handler_t * fh, struct vfs_s_inode *ino, gboolean changed);
+
+/* network filesystems support */
+int vfs_s_select_on_two (int fd1, int fd2);
+int vfs_s_get_line (struct vfs_class *me, int sock, char *buf, int buf_len, char term);
+int vfs_s_get_line_interruptible (struct vfs_class *me, char *buffer, int size, int fd);
+/* misc */
+int vfs_s_retrieve_file (struct vfs_class *me, struct vfs_s_inode *ino);
+
+void vfs_s_normalize_filename_leading_spaces (struct vfs_s_inode *root_inode, size_t final_filepos);
+
+/*** inline functions ****************************************************************************/
+
+static inline void
+vfs_s_store_filename_leading_spaces (struct vfs_s_entry *entry, size_t position)
+{
+ entry->leading_spaces = (ssize_t) position;
+}
+
+#endif