diff options
Diffstat (limited to '')
-rw-r--r-- | lib/vfs/HACKING | 104 | ||||
-rw-r--r-- | lib/vfs/Makefile.am | 19 | ||||
-rw-r--r-- | lib/vfs/Makefile.in | 767 | ||||
-rw-r--r-- | lib/vfs/README | 70 | ||||
-rw-r--r-- | lib/vfs/direntry.c | 1740 | ||||
-rw-r--r-- | lib/vfs/gc.c | 335 | ||||
-rw-r--r-- | lib/vfs/gc.h | 27 | ||||
-rw-r--r-- | lib/vfs/interface.c | 875 | ||||
-rw-r--r-- | lib/vfs/netutil.c | 83 | ||||
-rw-r--r-- | lib/vfs/netutil.h | 26 | ||||
-rw-r--r-- | lib/vfs/parse_ls_vga.c | 886 | ||||
-rw-r--r-- | lib/vfs/path.c | 1683 | ||||
-rw-r--r-- | lib/vfs/path.h | 149 | ||||
-rw-r--r-- | lib/vfs/utilvfs.c | 374 | ||||
-rw-r--r-- | lib/vfs/utilvfs.h | 64 | ||||
-rw-r--r-- | lib/vfs/vfs.c | 775 | ||||
-rw-r--r-- | lib/vfs/vfs.h | 343 | ||||
-rw-r--r-- | lib/vfs/xdirentry.h | 205 |
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 (¤t_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 |