summaryrefslogtreecommitdiffstats
path: root/src/vfs/tar
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:22:03 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:22:03 +0000
commitffccd5b2b05243e7976db80f90f453dccfae9886 (patch)
tree39a43152d27f7390d8f7a6fb276fa6887f87c6e8 /src/vfs/tar
parentInitial commit. (diff)
downloadmc-0acba638a84ac029b0ce3ee33c0f8b7d9f3fa027.tar.xz
mc-0acba638a84ac029b0ce3ee33c0f8b7d9f3fa027.zip
Adding upstream version 3:4.8.30.upstream/3%4.8.30
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/vfs/tar/Makefile.am10
-rw-r--r--src/vfs/tar/Makefile.in750
-rw-r--r--src/vfs/tar/tar-internal.c482
-rw-r--r--src/vfs/tar/tar-internal.h351
-rw-r--r--src/vfs/tar/tar-sparse.c777
-rw-r--r--src/vfs/tar/tar-xheader.c1051
-rw-r--r--src/vfs/tar/tar.c1302
-rw-r--r--src/vfs/tar/tar.h18
8 files changed, 4741 insertions, 0 deletions
diff --git a/src/vfs/tar/Makefile.am b/src/vfs/tar/Makefile.am
new file mode 100644
index 0000000..16642f0
--- /dev/null
+++ b/src/vfs/tar/Makefile.am
@@ -0,0 +1,10 @@
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+
+noinst_LTLIBRARIES = libvfs-tar.la
+
+libvfs_tar_la_SOURCES = \
+ tar-internal.c tar-internal.h \
+ tar-sparse.c \
+ tar-xheader.c \
+ tar.c tar.h
diff --git a/src/vfs/tar/Makefile.in b/src/vfs/tar/Makefile.in
new file mode 100644
index 0000000..c89786e
--- /dev/null
+++ b/src/vfs/tar/Makefile.in
@@ -0,0 +1,750 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/vfs/tar
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libvfs_tar_la_LIBADD =
+am_libvfs_tar_la_OBJECTS = tar-internal.lo tar-sparse.lo \
+ tar-xheader.lo tar.lo
+libvfs_tar_la_OBJECTS = $(am_libvfs_tar_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/tar-internal.Plo \
+ ./$(DEPDIR)/tar-sparse.Plo ./$(DEPDIR)/tar-xheader.Plo \
+ ./$(DEPDIR)/tar.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libvfs_tar_la_SOURCES)
+DIST_SOURCES = $(libvfs_tar_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+noinst_LTLIBRARIES = libvfs-tar.la
+libvfs_tar_la_SOURCES = \
+ tar-internal.c tar-internal.h \
+ tar-sparse.c \
+ tar-xheader.c \
+ tar.c tar.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/vfs/tar/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/tar/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libvfs-tar.la: $(libvfs_tar_la_OBJECTS) $(libvfs_tar_la_DEPENDENCIES) $(EXTRA_libvfs_tar_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libvfs_tar_la_OBJECTS) $(libvfs_tar_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tar-internal.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tar-sparse.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tar-xheader.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tar.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/tar-internal.Plo
+ -rm -f ./$(DEPDIR)/tar-sparse.Plo
+ -rm -f ./$(DEPDIR)/tar-xheader.Plo
+ -rm -f ./$(DEPDIR)/tar.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/tar-internal.Plo
+ -rm -f ./$(DEPDIR)/tar-sparse.Plo
+ -rm -f ./$(DEPDIR)/tar-xheader.Plo
+ -rm -f ./$(DEPDIR)/tar.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/vfs/tar/tar-internal.c b/src/vfs/tar/tar-internal.c
new file mode 100644
index 0000000..f77b1b3
--- /dev/null
+++ b/src/vfs/tar/tar-internal.c
@@ -0,0 +1,482 @@
+/*
+ Virtual File System: GNU Tar file system.
+
+ Copyright (C) 2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2023
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * \brief Source: Virtual File System: GNU Tar file system
+ * \author Andrew Borodin
+ * \date 2022
+ */
+
+#include <config.h>
+
+#include <ctype.h> /* isspace() */
+#include <inttypes.h> /* uintmax_t */
+#include <stdint.h> /* UINTMAX_MAX, etc */
+
+#include "lib/global.h"
+#include "lib/widget.h" /* message() */
+#include "lib/vfs/vfs.h" /* mc_read() */
+
+#include "tar-internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/* Log base 2 of common values. */
+#define LG_8 3
+#define LG_64 6
+#define LG_256 8
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* Base 64 digits; see RFC 2045 Table 1. */
+static char const base_64_digits[64] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
+};
+
+/* Table of base 64 digit values indexed by unsigned chars.
+ The value is 64 for unsigned chars that are not base 64 digits. */
+static char base64_map[1 + (unsigned char) (-1)];
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+tar_short_read (size_t status, tar_super_t * archive)
+{
+ size_t left; /* bytes left */
+ char *more; /* pointer to next byte to read */
+
+ more = archive->record_start->buffer + status;
+ left = record_size - status;
+
+ while (left % BLOCKSIZE != 0 || (left != 0 && status != 0))
+ {
+ if (status != 0)
+ {
+ ssize_t r;
+
+ r = mc_read (archive->fd, more, left);
+ if (r == -1)
+ return FALSE;
+
+ status = (size_t) r;
+ }
+
+ if (status == 0)
+ break;
+
+ left -= status;
+ more += status;
+ }
+
+ record_end = archive->record_start + (record_size - left) / BLOCKSIZE;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+tar_flush_read (tar_super_t * archive)
+{
+ size_t status;
+
+ status = mc_read (archive->fd, archive->record_start->buffer, record_size);
+ if (status == record_size)
+ return TRUE;
+
+ return tar_short_read (status, archive);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Flush the current buffer from the archive.
+ */
+static gboolean
+tar_flush_archive (tar_super_t * archive)
+{
+ record_start_block += record_end - archive->record_start;
+ current_block = archive->record_start;
+ record_end = archive->record_start + blocking_factor;
+
+ return tar_flush_read (archive);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+tar_seek_archive (tar_super_t * archive, off_t size)
+{
+ off_t start, offset;
+ off_t nrec, nblk;
+ off_t skipped;
+
+ skipped = (blocking_factor - (current_block - archive->record_start)) * BLOCKSIZE;
+ if (size <= skipped)
+ return 0;
+
+ /* Compute number of records to skip */
+ nrec = (size - skipped) / record_size;
+ if (nrec == 0)
+ return 0;
+
+ start = tar_current_block_ordinal (archive);
+
+ offset = mc_lseek (archive->fd, nrec * record_size, SEEK_CUR);
+ if (offset < 0)
+ return offset;
+
+#if 0
+ if ((offset % record_size) != 0)
+ {
+ message (D_ERROR, MSG_ERROR, _("tar: mc_lseek not stopped at a record boundary"));
+ return -1;
+ }
+#endif
+
+ /* Convert to number of records */
+ offset /= BLOCKSIZE;
+ /* Compute number of skipped blocks */
+ nblk = offset - start;
+
+ /* Update buffering info */
+ record_start_block = offset - blocking_factor;
+ current_block = record_end;
+
+ return nblk;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tar_base64_init (void)
+{
+ size_t i;
+
+ memset (base64_map, 64, sizeof base64_map);
+ for (i = 0; i < 64; i++)
+ base64_map[(int) base_64_digits[i]] = i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tar_assign_string (char **string, char *value)
+{
+ g_free (*string);
+ *string = value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tar_assign_string_dup (char **string, const char *value)
+{
+ g_free (*string);
+ *string = g_strdup (value);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tar_assign_string_dup_n (char **string, const char *value, size_t n)
+{
+ g_free (*string);
+ *string = g_strndup (value, n);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Convert buffer at @where0 of size @digs from external format to intmax_t.
+ * @digs must be positive.
+ * If @type is non-NULL, data are of type @type.
+ * The buffer must represent a value in the range -@minval through @maxval;
+ * if the mathematically correct result V would be greater than INTMAX_MAX,
+ * return a negative integer V such that (uintmax_t) V yields the correct result.
+ * If @octal_only, allow only octal numbers instead of the other GNU extensions.
+ *
+ * Result is -1 if the field is invalid.
+ */
+#if !(INTMAX_MAX <= UINTMAX_MAX && - (INTMAX_MIN + 1) <= UINTMAX_MAX)
+#error "tar_from_header() internally represents intmax_t as uintmax_t + sign"
+#endif
+#if !(UINTMAX_MAX / 2 <= INTMAX_MAX)
+#error "tar_from_header() returns intmax_t to represent uintmax_t"
+#endif
+intmax_t
+tar_from_header (const char *where0, size_t digs, char const *type, intmax_t minval,
+ uintmax_t maxval, gboolean octal_only)
+{
+ uintmax_t value = 0;
+ uintmax_t uminval = minval;
+ uintmax_t minus_minval = -uminval;
+ const char *where = where0;
+ char const *lim = where + digs;
+ gboolean negative = FALSE;
+
+ /* Accommodate buggy tar of unknown vintage, which outputs leading
+ NUL if the previous field overflows. */
+ if (*where == '\0')
+ where++;
+
+ /* Accommodate older tars, which output leading spaces. */
+ while (TRUE)
+ {
+ if (where == lim)
+ return (-1);
+
+ if (!isspace ((unsigned char) *where))
+ break;
+
+ where++;
+ }
+
+ if (isodigit (*where))
+ {
+ char const *where1 = where;
+ gboolean overflow = FALSE;
+
+ while (TRUE)
+ {
+ value += *where++ - '0';
+ if (where == lim || !isodigit (*where))
+ break;
+ overflow |= value != (value << LG_8 >> LG_8);
+ value <<= LG_8;
+ }
+
+ /* Parse the output of older, unportable tars, which generate
+ negative values in two's complement octal. If the leading
+ nonzero digit is 1, we can't recover the original value
+ reliably; so do this only if the digit is 2 or more. This
+ catches the common case of 32-bit negative time stamps. */
+ if ((overflow || maxval < value) && *where1 >= 2 && type != NULL)
+ {
+ /* Compute the negative of the input value, assuming two's complement. */
+ int digit;
+
+ digit = (*where1 - '0') | 4;
+ overflow = FALSE;
+ value = 0;
+ where = where1;
+
+ while (TRUE)
+ {
+ value += 7 - digit;
+ where++;
+ if (where == lim || !isodigit (*where))
+ break;
+ digit = *where - '0';
+ overflow |= value != (value << LG_8 >> LG_8);
+ value <<= LG_8;
+ }
+
+ value++;
+ overflow |= value == 0;
+
+ if (!overflow && value <= minus_minval)
+ negative = TRUE;
+ }
+
+ if (overflow)
+ return (-1);
+ }
+ else if (octal_only)
+ {
+ /* Suppress the following extensions. */
+ }
+ else if (*where == '-' || *where == '+')
+ {
+ /* Parse base-64 output produced only by tar test versions
+ 1.13.6 (1999-08-11) through 1.13.11 (1999-08-23).
+ Support for this will be withdrawn in future tar releases. */
+ int dig;
+
+ negative = *where++ == '-';
+
+ while (where != lim && (dig = base64_map[(unsigned char) *where]) < 64)
+ {
+ if (value << LG_64 >> LG_64 != value)
+ return (-1);
+ value = (value << LG_64) | dig;
+ where++;
+ }
+ }
+ else if (where <= lim - 2 && (*where == '\200' /* positive base-256 */
+ || *where == '\377' /* negative base-256 */ ))
+ {
+ /* Parse base-256 output. A nonnegative number N is
+ represented as (256**DIGS)/2 + N; a negative number -N is
+ represented as (256**DIGS) - N, i.e. as two's complement.
+ The representation guarantees that the leading bit is
+ always on, so that we don't confuse this format with the
+ others (assuming ASCII bytes of 8 bits or more). */
+
+ int signbit;
+ uintmax_t topbits;
+
+ signbit = *where & (1 << (LG_256 - 2));
+ topbits =
+ (((uintmax_t) - signbit) << (CHAR_BIT * sizeof (uintmax_t) - LG_256 - (LG_256 - 2)));
+
+ value = (*where++ & ((1 << (LG_256 - 2)) - 1)) - signbit;
+
+ while (TRUE)
+ {
+ value = (value << LG_256) + (unsigned char) *where++;
+ if (where == lim)
+ break;
+
+ if (((value << LG_256 >> LG_256) | topbits) != value)
+ return (-1);
+ }
+
+ negative = signbit != 0;
+ if (negative)
+ value = -value;
+ }
+
+ if (where != lim && *where != '\0' && !isspace ((unsigned char) *where))
+ return (-1);
+
+ if (value <= (negative ? minus_minval : maxval))
+ return tar_represent_uintmax (negative ? -value : value);
+
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+off_t
+off_from_header (const char *p, size_t s)
+{
+ /* Negative offsets are not allowed in tar files, so invoke
+ from_header with minimum value 0, not TYPE_MINIMUM (off_t). */
+ return tar_from_header (p, s, "off_t", 0, TYPE_MAXIMUM (off_t), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Return the location of the next available input or output block.
+ * Return NULL for EOF.
+ */
+union block *
+tar_find_next_block (tar_super_t * archive)
+{
+ if (current_block == record_end)
+ {
+ if (hit_eof)
+ return NULL;
+
+ if (!tar_flush_archive (archive))
+ {
+ message (D_ERROR, MSG_ERROR, _("Inconsistent tar archive"));
+ return NULL;
+ }
+
+ if (current_block == record_end)
+ {
+ hit_eof = TRUE;
+ return NULL;
+ }
+ }
+
+ return current_block;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Indicate that we have used all blocks up thru @block.
+ */
+gboolean
+tar_set_next_block_after (union block * block)
+{
+ while (block >= current_block)
+ current_block++;
+
+ /* Do *not* flush the archive here. If we do, the same argument to tar_set_next_block_after()
+ could mean the next block (if the input record is exactly one block long), which is not
+ what is intended. */
+
+ return !(current_block > record_end);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Compute and return the block ordinal at current_block.
+ */
+off_t
+tar_current_block_ordinal (const tar_super_t * archive)
+{
+ return record_start_block + (current_block - archive->record_start);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Skip over @size bytes of data in blocks in the archive.
+ */
+gboolean
+tar_skip_file (tar_super_t * archive, off_t size)
+{
+ union block *x;
+ off_t nblk;
+
+ nblk = tar_seek_archive (archive, size);
+ if (nblk >= 0)
+ size -= nblk * BLOCKSIZE;
+
+ while (size > 0)
+ {
+ x = tar_find_next_block (archive);
+ if (x == NULL)
+ return FALSE;
+
+ tar_set_next_block_after (x);
+ size -= BLOCKSIZE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/tar/tar-internal.h b/src/vfs/tar/tar-internal.h
new file mode 100644
index 0000000..7b3bb53
--- /dev/null
+++ b/src/vfs/tar/tar-internal.h
@@ -0,0 +1,351 @@
+
+#ifndef MC__VFS_TAR_INTERNAL_H
+#define MC__VFS_TAR_INTERNAL_H
+
+#include <inttypes.h> /* (u)intmax_t */
+#include <limits.h> /* CHAR_BIT, INT_MAX, etc */
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "lib/vfs/xdirentry.h" /* vfs_s_super */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/* tar files are made in basic blocks of this size. */
+#define BLOCKSIZE 512
+
+#define DEFAULT_BLOCKING 20
+
+/* Sparse files are not supported in POSIX ustar format. For sparse files
+ with a POSIX header, a GNU extra header is provided which holds overall
+ sparse information and a few sparse descriptors. When an old GNU header
+ replaces both the POSIX header and the GNU extra header, it holds some
+ sparse descriptors too. Whether POSIX or not, if more sparse descriptors
+ are still needed, they are put into as many successive sparse headers as
+ necessary. The following constants tell how many sparse descriptors fit
+ in each kind of header able to hold them. */
+
+#define SPARSES_IN_EXTRA_HEADER 16
+#define SPARSES_IN_OLDGNU_HEADER 4
+#define SPARSES_IN_SPARSE_HEADER 21
+
+#define SPARSES_IN_STAR_HEADER 4
+#define SPARSES_IN_STAR_EXT_HEADER 21
+
+/* *BEWARE* *BEWARE* *BEWARE* that the following information is still
+ boiling, and may change. Even if the OLDGNU format description should be
+ accurate, the so-called GNU format is not yet fully decided. It is
+ surely meant to use only extensions allowed by POSIX, but the sketch
+ below repeats some ugliness from the OLDGNU format, which should rather
+ go away. Sparse files should be saved in such a way that they do *not*
+ require two passes at archive creation time. Huge files get some POSIX
+ fields to overflow, alternate solutions have to be sought for this. */
+
+/* This is a dir entry that contains the names of files that were in the
+ dir at the time the dump was made. */
+#define GNUTYPE_DUMPDIR 'D'
+
+/* Identifies the *next* file on the tape as having a long linkname. */
+#define GNUTYPE_LONGLINK 'K'
+
+/* Identifies the *next* file on the tape as having a long name. */
+#define GNUTYPE_LONGNAME 'L'
+
+/* Solaris extended header */
+#define SOLARIS_XHDTYPE 'X'
+
+#define GNUTYPE_SPARSE 'S'
+
+
+/* These macros work even on ones'-complement hosts (!).
+ The extra casts work around some compiler bugs. */
+#define TYPE_SIGNED(t) (!((t) 0 < (t) (-1)))
+#define TYPE_MINIMUM(t) (TYPE_SIGNED (t) ? ~(t) 0 << (sizeof (t) * CHAR_BIT - 1) : (t) 0)
+#define TYPE_MAXIMUM(t) (~(t) 0 - TYPE_MINIMUM (t))
+
+#define OFF_FROM_HEADER(where) off_from_header (where, sizeof (where))
+
+#define isodigit(c) ( ((c) >= '0') && ((c) <= '7') )
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* *INDENT-OFF* */
+
+/* POSIX header */
+struct posix_header
+{ /* byte offset */
+ char name[100]; /* 0 */
+ char mode[8]; /* 100 */
+ char uid[8]; /* 108 */
+ char gid[8]; /* 116 */
+ char size[12]; /* 124 */
+ char mtime[12]; /* 136 */
+ char chksum[8]; /* 148 */
+ char typeflag; /* 156 */
+ char linkname[100]; /* 157 */
+ char magic[6]; /* 257 */
+ char version[2]; /* 263 */
+ char uname[32]; /* 265 */
+ char gname[32]; /* 297 */
+ char devmajor[8]; /* 329 */
+ char devminor[8]; /* 337 */
+ char prefix[155]; /* 345 */
+ /* 500 */
+};
+
+/* Descriptor for a single file hole */
+struct sparse
+{ /* byte offset */
+ /* cppcheck-suppress unusedStructMember */
+ char offset[12]; /* 0 */
+ /* cppcheck-suppress unusedStructMember */
+ char numbytes[12]; /* 12 */
+ /* 24 */
+};
+
+/* Extension header for sparse files, used immediately after the GNU extra
+ header, and used only if all sparse information cannot fit into that
+ extra header. There might even be many such extension headers, one after
+ the other, until all sparse information has been recorded. */
+struct sparse_header
+{ /* byte offset */
+ struct sparse sp[SPARSES_IN_SPARSE_HEADER];
+ /* 0 */
+ char isextended; /* 504 */
+ /* 505 */
+};
+
+/* The old GNU format header conflicts with POSIX format in such a way that
+ POSIX archives may fool old GNU tar's, and POSIX tar's might well be
+ fooled by old GNU tar archives. An old GNU format header uses the space
+ used by the prefix field in a POSIX header, and cumulates information
+ normally found in a GNU extra header. With an old GNU tar header, we
+ never see any POSIX header nor GNU extra header. Supplementary sparse
+ headers are allowed, however. */
+struct oldgnu_header
+{ /* byte offset */
+ char unused_pad1[345]; /* 0 */
+ char atime[12]; /* 345 Incr. archive: atime of the file */
+ char ctime[12]; /* 357 Incr. archive: ctime of the file */
+ char offset[12]; /* 369 Multivolume archive: the offset of start of this volume */
+ char longnames[4]; /* 381 Not used */
+ char unused_pad2; /* 385 */
+ struct sparse sp[SPARSES_IN_OLDGNU_HEADER];
+ /* 386 */
+ char isextended; /* 482 Sparse file: Extension sparse header follows */
+ char realsize[12]; /* 483 Sparse file: Real size */
+ /* 495 */
+};
+
+/* J@"org Schilling star header */
+struct star_header
+{ /* byte offset */
+ char name[100]; /* 0 */
+ char mode[8]; /* 100 */
+ char uid[8]; /* 108 */
+ char gid[8]; /* 116 */
+ char size[12]; /* 124 */
+ char mtime[12]; /* 136 */
+ char chksum[8]; /* 148 */
+ char typeflag; /* 156 */
+ char linkname[100]; /* 157 */
+ char magic[6]; /* 257 */
+ char version[2]; /* 263 */
+ char uname[32]; /* 265 */
+ char gname[32]; /* 297 */
+ char devmajor[8]; /* 329 */
+ char devminor[8]; /* 337 */
+ char prefix[131]; /* 345 */
+ char atime[12]; /* 476 */
+ char ctime[12]; /* 488 */
+ /* 500 */
+};
+
+struct star_in_header
+{
+ char fill[345]; /* 0 Everything that is before t_prefix */
+ char prefix[1]; /* 345 t_name prefix */
+ char fill2; /* 346 */
+ char fill3[8]; /* 347 */
+ char isextended; /* 355 */
+ struct sparse sp[SPARSES_IN_STAR_HEADER]; /* 356 */
+ char realsize[12]; /* 452 Actual size of the file */
+ char offset[12]; /* 464 Offset of multivolume contents */
+ char atime[12]; /* 476 */
+ char ctime[12]; /* 488 */
+ char mfill[8]; /* 500 */
+ char xmagic[4]; /* 508 "tar" */
+};
+
+struct star_ext_header
+{
+ struct sparse sp[SPARSES_IN_STAR_EXT_HEADER];
+ char isextended;
+};
+
+/* *INDENT-ON* */
+
+/* tar Header Block, overall structure */
+union block
+{
+ char buffer[BLOCKSIZE];
+ struct posix_header header;
+ struct star_header star_header;
+ struct oldgnu_header oldgnu_header;
+ struct sparse_header sparse_header;
+ struct star_in_header star_in_header;
+ struct star_ext_header star_ext_header;
+};
+
+/* Information about a sparse file */
+struct sp_array
+{
+ off_t offset; /* chunk offset in file */
+ off_t numbytes; /* length of chunk */
+ off_t arch_offset; /* chunk offset in archive */
+};
+
+enum dump_status
+{
+ dump_status_ok,
+ dump_status_short,
+ dump_status_fail,
+ dump_status_not_implemented
+};
+
+enum archive_format
+{
+ TAR_UNKNOWN = 0, /**< format to be decided later */
+ TAR_V7, /**< old V7 tar format */
+ TAR_OLDGNU, /**< GNU format as per before tar 1.12 */
+ TAR_USTAR, /**< POSIX.1-1988 (ustar) format */
+ TAR_POSIX, /**< POSIX.1-2001 format */
+ TAR_STAR, /**< star format defined in 1994 */
+ TAR_GNU /**< almost same as OLDGNU_FORMAT */
+};
+
+typedef struct
+{
+ struct vfs_s_super base; /* base class */
+
+ int fd;
+ struct stat st;
+ enum archive_format type; /**< type of the archive */
+ union block *record_start; /**< start of record of archive */
+} tar_super_t;
+
+struct xheader
+{
+ size_t size;
+ char *buffer;
+};
+
+struct tar_stat_info
+{
+ char *orig_file_name; /**< name of file read from the archive header */
+ char *file_name; /**< name of file for the current archive entry after being normalized */
+ char *link_name; /**< name of link for the current archive entry */
+#if 0
+ char *uname; /**< user name of owner */
+ char *gname; /**< group name of owner */
+#endif
+ struct stat stat; /**< regular filesystem stat */
+
+ /* stat() doesn't always have access, data modification, and status
+ change times in a convenient form, so store them separately. */
+ struct timespec atime;
+ struct timespec mtime;
+ struct timespec ctime;
+
+ off_t archive_file_size; /**< size of file as stored in the archive.
+ Equals stat.st_size for non-sparse files */
+ gboolean is_sparse; /**< is the file sparse */
+
+ /* For sparse files */
+ unsigned int sparse_major;
+ unsigned int sparse_minor;
+ GArray *sparse_map; /**< array of struct sp_array */
+
+ off_t real_size; /**< real size of sparse file */
+ gboolean real_size_set; /**< TRUE when GNU.sparse.realsize is set in archived file */
+
+ gboolean sparse_name_done; /**< TRUE if 'GNU.sparse.name' header was processed pax header parsing.
+ Following 'path' header (lower priority) will be ignored. */
+
+ /* Extended headers */
+ struct xheader xhdr;
+
+ /* For dumpdirs */
+ gboolean is_dumpdir; /**< is the member a dumpdir? */
+ gboolean skipped; /**< the member contents is already read (for GNUTYPE_DUMPDIR) */
+ char *dumpdir; /**< contents of the dump directory */
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+extern const int blocking_factor;
+extern const size_t record_size;
+
+extern union block *record_end; /* last+1 block of archive record */
+extern union block *current_block; /* current block of archive */
+extern off_t record_start_block; /* block ordinal at record_start */
+
+extern union block *current_header;
+
+/* Have we hit EOF yet? */
+extern gboolean hit_eof;
+
+extern struct tar_stat_info current_stat_info;
+
+/*** declarations of public functions ************************************************************/
+
+/* tar-internal.c */
+void tar_base64_init (void);
+void tar_assign_string (char **string, char *value);
+void tar_assign_string_dup (char **string, const char *value);
+void tar_assign_string_dup_n (char **string, const char *value, size_t n);
+intmax_t tar_from_header (const char *where0, size_t digs, char const *type, intmax_t minval,
+ uintmax_t maxval, gboolean octal_only);
+off_t off_from_header (const char *p, size_t s);
+union block *tar_find_next_block (tar_super_t * archive);
+gboolean tar_set_next_block_after (union block *block);
+off_t tar_current_block_ordinal (const tar_super_t * archive);
+gboolean tar_skip_file (tar_super_t * archive, off_t size);
+
+/* tar-sparse.c */
+gboolean tar_sparse_member_p (tar_super_t * archive, struct tar_stat_info *st);
+gboolean tar_sparse_fixup_header (tar_super_t * archive, struct tar_stat_info *st);
+enum dump_status tar_sparse_skip_file (tar_super_t * archive, struct tar_stat_info *st);
+
+/* tar-xheader.c */
+gboolean tar_xheader_decode (struct tar_stat_info *st);
+gboolean tar_xheader_read (tar_super_t * archive, struct xheader *xhdr, union block *header,
+ off_t size);
+gboolean tar_xheader_decode_global (struct xheader *xhdr);
+void tar_xheader_destroy (struct xheader *xhdr);
+
+/*** inline functions ****************************************************************************/
+
+/**
+ * Represent @n using a signed integer I such that (uintmax_t) I == @n.
+ With a good optimizing compiler, this is equivalent to (intmax_t) i
+ and requires zero machine instructions. */
+#if !(UINTMAX_MAX / 2 <= INTMAX_MAX)
+#error "tar_represent_uintmax() returns intmax_t to represent uintmax_t"
+#endif
+static inline intmax_t
+tar_represent_uintmax (uintmax_t n)
+{
+ intmax_t nd;
+
+ if (n <= INTMAX_MAX)
+ return n;
+
+ /* Avoid signed integer overflow on picky platforms. */
+ nd = n - INTMAX_MIN;
+ return nd + INTMAX_MIN;
+}
+
+#endif /* MC__VFS_TAR_INTERNAL_H */
diff --git a/src/vfs/tar/tar-sparse.c b/src/vfs/tar/tar-sparse.c
new file mode 100644
index 0000000..0bc169b
--- /dev/null
+++ b/src/vfs/tar/tar-sparse.c
@@ -0,0 +1,777 @@
+/*
+ Virtual File System: GNU Tar file system.
+
+ Copyright (C) 2003-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2023
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * \brief Source: Virtual File System: GNU Tar file system
+ */
+
+/*
+ * Avoid following error:
+ * comparison of unsigned expression < 0 is always false [-Werror=type-limits]
+ *
+ * https://www.boost.org/doc/libs/1_55_0/libs/integer/test/cstdint_test.cpp
+ * We can't suppress this warning on the command line as not all GCC versions support -Wno-type-limits
+ */
+#if defined(__GNUC__) && (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 4))
+#pragma GCC diagnostic ignored "-Wtype-limits"
+#endif
+
+#include <config.h>
+
+#include <ctype.h> /* isdigit() */
+#include <errno.h>
+#include <inttypes.h> /* uintmax_t */
+
+#include "lib/global.h"
+
+#include "tar-internal.h"
+
+/* Old GNU Format.
+ The sparse file information is stored in the oldgnu_header in the following manner:
+
+ The header is marked with type 'S'. Its 'size' field contains the cumulative size
+ of all non-empty blocks of the file. The actual file size is stored in `realsize'
+ member of oldgnu_header.
+
+ The map of the file is stored in a list of 'struct sparse'. Each struct contains
+ offset to the block of data and its size (both as octal numbers). The first file
+ header contains at most 4 such structs (SPARSES_IN_OLDGNU_HEADER). If the map
+ contains more structs, then the field 'isextended' of the main header is set to
+ 1 (binary) and the 'struct sparse_header' header follows, containing at most
+ 21 following structs (SPARSES_IN_SPARSE_HEADER). If more structs follow, 'isextended'
+ field of the extended header is set and next next extension header follows, etc...
+ */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/* The width in bits of the integer type or expression T.
+ Do not evaluate T. T must not be a bit-field expression.
+ Padding bits are not supported; this is checked at compile-time below. */
+#define TYPE_WIDTH(t) (sizeof (t) * CHAR_BIT)
+
+/* Bound on length of the string representing an unsigned integer
+ value representable in B bits. log10 (2.0) < 146/485. The
+ smallest value of B where this bound is not tight is 2621. */
+#define INT_BITS_STRLEN_BOUND(b) (((b) * 146 + 484) / 485)
+
+/* Does the __typeof__ keyword work? This could be done by
+ 'configure', but for now it's easier to do it by hand. */
+#if (2 <= __GNUC__ \
+ || (4 <= __clang_major__) \
+ || (1210 <= __IBMC__ && defined __IBM__TYPEOF__) \
+ || (0x5110 <= __SUNPRO_C && !__STDC__))
+#define _GL_HAVE___TYPEOF__ 1
+#else
+#define _GL_HAVE___TYPEOF__ 0
+#endif
+
+/* Return 1 if the integer type or expression T might be signed. Return 0
+ if it is definitely unsigned. T must not be a bit-field expression.
+ This macro does not evaluate its argument, and expands to an
+ integer constant expression. */
+#if _GL_HAVE___TYPEOF__
+#define _GL_SIGNED_TYPE_OR_EXPR(t) TYPE_SIGNED (__typeof__ (t))
+#else
+#define _GL_SIGNED_TYPE_OR_EXPR(t) 1
+#endif
+
+/* Return a value with the common real type of E and V and the value of V.
+ Do not evaluate E. */
+#define _GL_INT_CONVERT(e, v) ((1 ? 0 : (e)) + (v))
+
+/* Act like _GL_INT_CONVERT (E, -V) but work around a bug in IRIX 6.5 cc; see
+ <https://lists.gnu.org/r/bug-gnulib/2011-05/msg00406.html>. */
+#define _GL_INT_NEGATE_CONVERT(e, v) ((1 ? 0 : (e)) - (v))
+
+/* Return 1 if the real expression E, after promotion, has a
+ signed or floating type. Do not evaluate E. */
+#define EXPR_SIGNED(e) (_GL_INT_NEGATE_CONVERT (e, 1) < 0)
+
+#define _GL_SIGNED_INT_MAXIMUM(e) \
+ (((_GL_INT_CONVERT (e, 1) << (TYPE_WIDTH (+ (e)) - 2)) - 1) * 2 + 1)
+
+/* The maximum and minimum values for the type of the expression E,
+ after integer promotion. E is not evaluated. */
+#define _GL_INT_MINIMUM(e) \
+ (EXPR_SIGNED (e) \
+ ? ~_GL_SIGNED_INT_MAXIMUM (e) \
+ : _GL_INT_CONVERT (e, 0))
+#define _GL_INT_MAXIMUM(e) \
+ (EXPR_SIGNED (e) \
+ ? _GL_SIGNED_INT_MAXIMUM (e) \
+ : _GL_INT_NEGATE_CONVERT (e, 1))
+
+/* Return 1 if the expression A <op> B would overflow,
+ where OP_RESULT_OVERFLOW (A, B, MIN, MAX) does the actual test,
+ assuming MIN and MAX are the minimum and maximum for the result type.
+ Arguments should be free of side effects. */
+#define _GL_BINARY_OP_OVERFLOW(a, b, op_result_overflow) \
+ op_result_overflow (a, b, \
+ _GL_INT_MINIMUM (_GL_INT_CONVERT (a, b)), \
+ _GL_INT_MAXIMUM (_GL_INT_CONVERT (a, b)))
+
+#define INT_ADD_RANGE_OVERFLOW(a, b, min, max) \
+ ((b) < 0 \
+ ? (a) < (min) - (b) \
+ : (max) - (b) < (a))
+
+
+/* True if __builtin_add_overflow_p (A, B, C) works, and similarly for
+ __builtin_sub_overflow_p and __builtin_mul_overflow_p. */
+#if defined __clang__ || defined __ICC
+/* Clang 11 lacks __builtin_mul_overflow_p, and even if it did it
+ would presumably run afoul of Clang bug 16404. ICC 2021.1's
+ __builtin_add_overflow_p etc. are not treated as integral constant
+ expressions even when all arguments are. */
+#define _GL_HAS_BUILTIN_OVERFLOW_P 0
+#elif defined __has_builtin
+#define _GL_HAS_BUILTIN_OVERFLOW_P __has_builtin (__builtin_mul_overflow_p)
+#else
+#define _GL_HAS_BUILTIN_OVERFLOW_P (7 <= __GNUC__)
+#endif
+
+/* The _GL*_OVERFLOW macros have the same restrictions as the
+ *_RANGE_OVERFLOW macros, except that they do not assume that operands
+ (e.g., A and B) have the same type as MIN and MAX. Instead, they assume
+ that the result (e.g., A + B) has that type. */
+#if _GL_HAS_BUILTIN_OVERFLOW_P
+#define _GL_ADD_OVERFLOW(a, b, min, max) \
+ __builtin_add_overflow_p (a, b, (__typeof__ ((a) + (b))) 0)
+#else
+#define _GL_ADD_OVERFLOW(a, b, min, max) \
+ ((min) < 0 ? INT_ADD_RANGE_OVERFLOW (a, b, min, max) \
+ : (a) < 0 ? (b) <= (a) + (b) \
+ : (b) < 0 ? (a) <= (a) + (b) \
+ : (a) + (b) < (b))
+#endif
+
+/* Bound on length of the string representing an integer type or expression T.
+ T must not be a bit-field expression.
+
+ Subtract 1 for the sign bit if T is signed, and then add 1 more for
+ a minus sign if needed.
+
+ Because _GL_SIGNED_TYPE_OR_EXPR sometimes returns 1 when its argument is
+ unsigned, this macro may overestimate the true bound by one byte when
+ applied to unsigned types of size 2, 4, 16, ... bytes. */
+#define INT_STRLEN_BOUND(t) \
+ (INT_BITS_STRLEN_BOUND (TYPE_WIDTH (t) - _GL_SIGNED_TYPE_OR_EXPR (t)) \
+ + _GL_SIGNED_TYPE_OR_EXPR (t))
+
+/* Bound on buffer size needed to represent an integer type or expression T,
+ including the terminating null. T must not be a bit-field expression. */
+#define INT_BUFSIZE_BOUND(t) (INT_STRLEN_BOUND (t) + 1)
+
+#define UINTMAX_STRSIZE_BOUND INT_BUFSIZE_BOUND (uintmax_t)
+
+#define INT_ADD_OVERFLOW(a, b) \
+ _GL_BINARY_OP_OVERFLOW (a, b, _GL_ADD_OVERFLOW)
+
+#define SPARSES_INIT_COUNT SPARSES_IN_SPARSE_HEADER
+
+#define COPY_BUF(arch,b,buf,src) \
+do \
+{ \
+ char *endp = b->buffer + BLOCKSIZE; \
+ char *dst = buf; \
+ do \
+ { \
+ if (dst == buf + UINTMAX_STRSIZE_BOUND - 1) \
+ /* numeric overflow in sparse archive member */ \
+ return FALSE; \
+ if (src == endp) \
+ { \
+ tar_set_next_block_after (b); \
+ b = tar_find_next_block (arch); \
+ if (b == NULL) \
+ /* unexpected EOF in archive */ \
+ return FALSE; \
+ src = b->buffer; \
+ endp = b->buffer + BLOCKSIZE; \
+ } \
+ *dst = *src++; \
+ } \
+ while (*dst++ != '\n'); \
+ dst[-1] = '\0'; \
+} \
+while (FALSE)
+
+/*** file scope type declarations ****************************************************************/
+
+struct tar_sparse_file;
+
+struct tar_sparse_optab
+{
+ gboolean (*init) (struct tar_sparse_file * file);
+ gboolean (*done) (struct tar_sparse_file * file);
+ gboolean (*sparse_member_p) (struct tar_sparse_file * file);
+ gboolean (*fixup_header) (struct tar_sparse_file * file);
+ gboolean (*decode_header) (tar_super_t * archive, struct tar_sparse_file * file);
+};
+
+struct tar_sparse_file
+{
+ int fd; /**< File descriptor */
+ off_t dumped_size; /**< Number of bytes actually written to the archive */
+ struct tar_stat_info *stat_info; /**< Information about the file */
+ struct tar_sparse_optab const *optab;
+ void *closure; /**< Any additional data optab calls might reqiure */
+};
+
+enum oldgnu_add_status
+{
+ add_ok,
+ add_finish,
+ add_fail
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static gboolean oldgnu_sparse_member_p (struct tar_sparse_file *file);
+static gboolean oldgnu_fixup_header (struct tar_sparse_file *file);
+static gboolean oldgnu_get_sparse_info (tar_super_t * archive, struct tar_sparse_file *file);
+
+static gboolean star_sparse_member_p (struct tar_sparse_file *file);
+static gboolean star_fixup_header (struct tar_sparse_file *file);
+static gboolean star_get_sparse_info (tar_super_t * archive, struct tar_sparse_file *file);
+
+static gboolean pax_sparse_member_p (struct tar_sparse_file *file);
+static gboolean pax_decode_header (tar_super_t * archive, struct tar_sparse_file *file);
+
+/*** file scope variables ************************************************************************/
+
+/* *INDENT-OFF* */
+static struct tar_sparse_optab const oldgnu_optab =
+{
+ .init = NULL, /* No init function */
+ .done = NULL, /* No done function */
+ .sparse_member_p = oldgnu_sparse_member_p,
+ .fixup_header = oldgnu_fixup_header,
+ .decode_header = oldgnu_get_sparse_info
+};
+/* *INDENT-ON* */
+
+/* *INDENT-OFF* */
+static struct tar_sparse_optab const star_optab =
+{
+ .init = NULL, /* No init function */
+ .done = NULL, /* No done function */
+ .sparse_member_p = star_sparse_member_p,
+ .fixup_header = star_fixup_header,
+ .decode_header = star_get_sparse_info
+};
+/* *INDENT-ON* */
+
+/* GNU PAX sparse file format. There are several versions:
+ * 0.0
+
+ The initial version of sparse format used by tar 1.14-1.15.1.
+ The sparse file map is stored in x header:
+
+ GNU.sparse.size Real size of the stored file
+ GNU.sparse.numblocks Number of blocks in the sparse map repeat numblocks time
+ GNU.sparse.offset Offset of the next data block
+ GNU.sparse.numbytes Size of the next data block end repeat
+
+ This has been reported as conflicting with the POSIX specs. The reason is
+ that offsets and sizes of non-zero data blocks were stored in multiple instances
+ of GNU.sparse.offset/GNU.sparse.numbytes variables, whereas POSIX requires the
+ latest occurrence of the variable to override all previous occurrences.
+
+ To avoid this incompatibility two following versions were introduced.
+
+ * 0.1
+
+ Used by tar 1.15.2 -- 1.15.91 (alpha releases).
+
+ The sparse file map is stored in x header:
+
+ GNU.sparse.size Real size of the stored file
+ GNU.sparse.numblocks Number of blocks in the sparse map
+ GNU.sparse.map Map of non-null data chunks. A string consisting of comma-separated
+ values "offset,size[,offset,size]..."
+
+ The resulting GNU.sparse.map string can be *very* long. While POSIX does not impose
+ any limit on the length of a x header variable, this can confuse some tars.
+
+ * 1.0
+
+ Starting from this version, the exact sparse format version is specified explicitly
+ in the header using the following variables:
+
+ GNU.sparse.major Major version
+ GNU.sparse.minor Minor version
+
+ X header keeps the following variables:
+
+ GNU.sparse.name Real file name of the sparse file
+ GNU.sparse.realsize Real size of the stored file (corresponds to the old GNU.sparse.size
+ variable)
+
+ The name field of the ustar header is constructed using the pattern "%d/GNUSparseFile.%p/%f".
+
+ The sparse map itself is stored in the file data block, preceding the actual file data.
+ It consists of a series of octal numbers of arbitrary length, delimited by newlines.
+ The map is padded with nulls to the nearest block boundary.
+
+ The first number gives the number of entries in the map. Following are map entries, each one
+ consisting of two numbers giving the offset and size of the data block it describes.
+
+ The format is designed in such a way that non-posix aware tars and tars not supporting
+ GNU.sparse.* keywords will extract each sparse file in its condensed form with the file map
+ attached and will place it into a separate directory. Then, using a simple program it would be
+ possible to expand the file to its original form even without GNU tar.
+
+ Bu default, v.1.0 archives are created. To use other formats, --sparse-version option is provided.
+ Additionally, v.0.0 can be obtained by deleting GNU.sparse.map from 0.1 format:
+ --sparse-version 0.1 --pax-option delete=GNU.sparse.map
+ */
+
+static struct tar_sparse_optab const pax_optab = {
+ .init = NULL, /* No init function */
+ .done = NULL, /* No done function */
+ .sparse_member_p = pax_sparse_member_p,
+ .fixup_header = NULL, /* No fixup_header function */
+ .decode_header = pax_decode_header
+};
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decode_num (uintmax_t * num, const char *arg, uintmax_t maxval)
+{
+ uintmax_t u;
+ char *arg_lim;
+
+ if (!isdigit (*arg))
+ return FALSE;
+
+ errno = 0;
+ u = (uintmax_t) g_ascii_strtoll (arg, &arg_lim, 10);
+
+ if (!(u <= maxval && errno != ERANGE) || *arg_lim != '\0')
+ return FALSE;
+
+ *num = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_select_optab (const tar_super_t * archive, struct tar_sparse_file *file)
+{
+ switch (archive->type)
+ {
+ case TAR_V7:
+ case TAR_USTAR:
+ return FALSE;
+
+ case TAR_OLDGNU:
+ case TAR_GNU: /* FIXME: This one should disappear? */
+ file->optab = &oldgnu_optab;
+ break;
+
+ case TAR_POSIX:
+ file->optab = &pax_optab;
+ break;
+
+ case TAR_STAR:
+ file->optab = &star_optab;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_init (tar_super_t * archive, struct tar_sparse_file *file)
+{
+ memset (file, 0, sizeof (*file));
+
+ if (!sparse_select_optab (archive, file))
+ return FALSE;
+
+ if (file->optab->init != NULL)
+ return file->optab->init (file);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_done (struct tar_sparse_file *file)
+{
+ if (file->optab->done != NULL)
+ return file->optab->done (file);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_member_p (struct tar_sparse_file *file)
+{
+ if (file->optab->sparse_member_p != NULL)
+ return file->optab->sparse_member_p (file);
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_fixup_header (struct tar_sparse_file *file)
+{
+ if (file->optab->fixup_header != NULL)
+ return file->optab->fixup_header (file);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_decode_header (tar_super_t * archive, struct tar_sparse_file *file)
+{
+ if (file->optab->decode_header != NULL)
+ return file->optab->decode_header (archive, file);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+sparse_add_map (struct tar_stat_info *st, struct sp_array *sp)
+{
+ if (st->sparse_map == NULL)
+ st->sparse_map = g_array_sized_new (FALSE, FALSE, sizeof (struct sp_array), 1);
+ g_array_append_val (st->sparse_map, *sp);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Add a sparse item to the sparse file
+ */
+static enum oldgnu_add_status
+oldgnu_add_sparse (struct tar_sparse_file *file, struct sparse *s)
+{
+ struct sp_array sp;
+
+ if (s->numbytes[0] == '\0')
+ return add_finish;
+
+ sp.offset = OFF_FROM_HEADER (s->offset);
+ sp.numbytes = OFF_FROM_HEADER (s->numbytes);
+
+ if (sp.offset < 0 || sp.numbytes < 0
+ || INT_ADD_OVERFLOW (sp.offset, sp.numbytes)
+ || file->stat_info->stat.st_size < sp.offset + sp.numbytes
+ || file->stat_info->archive_file_size < 0)
+ return add_fail;
+
+ sparse_add_map (file->stat_info, &sp);
+
+ return add_ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+oldgnu_sparse_member_p (struct tar_sparse_file *file)
+{
+ (void) file;
+
+ return current_header->header.typeflag == GNUTYPE_SPARSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+oldgnu_fixup_header (struct tar_sparse_file *file)
+{
+ /* NOTE! st_size was initialized from the header which actually contains archived size.
+ The following fixes it */
+ off_t realsize;
+
+ realsize = OFF_FROM_HEADER (current_header->oldgnu_header.realsize);
+ file->stat_info->archive_file_size = file->stat_info->stat.st_size;
+ file->stat_info->stat.st_size = MAX (0, realsize);
+
+ return (realsize >= 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Convert old GNU format sparse data to internal representation.
+ */
+static gboolean
+oldgnu_get_sparse_info (tar_super_t * archive, struct tar_sparse_file *file)
+{
+ size_t i;
+ union block *h = current_header;
+ int ext_p;
+ enum oldgnu_add_status rc;
+
+ if (file->stat_info->sparse_map != NULL)
+ g_array_set_size (file->stat_info->sparse_map, 0);
+
+ for (i = 0; i < SPARSES_IN_OLDGNU_HEADER; i++)
+ {
+ rc = oldgnu_add_sparse (file, &h->oldgnu_header.sp[i]);
+ if (rc != add_ok)
+ break;
+ }
+
+ for (ext_p = h->oldgnu_header.isextended ? 1 : 0; rc == add_ok && ext_p != 0;
+ ext_p = h->sparse_header.isextended ? 1 : 0)
+ {
+ h = tar_find_next_block (archive);
+ if (h == NULL)
+ return FALSE;
+
+ tar_set_next_block_after (h);
+
+ for (i = 0; i < SPARSES_IN_SPARSE_HEADER && rc == add_ok; i++)
+ rc = oldgnu_add_sparse (file, &h->sparse_header.sp[i]);
+ }
+
+ return (rc != add_fail);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+star_sparse_member_p (struct tar_sparse_file *file)
+{
+ (void) file;
+
+ return current_header->header.typeflag == GNUTYPE_SPARSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+star_fixup_header (struct tar_sparse_file *file)
+{
+ /* NOTE! st_size was initialized from the header which actually contains archived size.
+ The following fixes it */
+ off_t realsize;
+
+ realsize = OFF_FROM_HEADER (current_header->star_in_header.realsize);
+ file->stat_info->archive_file_size = file->stat_info->stat.st_size;
+ file->stat_info->stat.st_size = MAX (0, realsize);
+
+ return (realsize >= 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Convert STAR format sparse data to internal representation
+ */
+static gboolean
+star_get_sparse_info (tar_super_t * archive, struct tar_sparse_file *file)
+{
+ size_t i;
+ union block *h = current_header;
+ int ext_p = 1;
+ enum oldgnu_add_status rc = add_ok;
+
+ if (file->stat_info->sparse_map != NULL)
+ g_array_set_size (file->stat_info->sparse_map, 0);
+
+ if (h->star_in_header.prefix[0] == '\0' && h->star_in_header.sp[0].offset[10] != '\0')
+ {
+ /* Old star format */
+ for (i = 0; i < SPARSES_IN_STAR_HEADER; i++)
+ {
+ rc = oldgnu_add_sparse (file, &h->star_in_header.sp[i]);
+ if (rc != add_ok)
+ break;
+ }
+
+ ext_p = h->star_in_header.isextended ? 1 : 0;
+ }
+
+ for (; rc == add_ok && ext_p != 0; ext_p = h->star_ext_header.isextended ? 1 : 0)
+ {
+ h = tar_find_next_block (archive);
+ if (h == NULL)
+ return FALSE;
+
+ tar_set_next_block_after (h);
+
+ for (i = 0; i < SPARSES_IN_STAR_EXT_HEADER && rc == add_ok; i++)
+ rc = oldgnu_add_sparse (file, &h->star_ext_header.sp[i]);
+
+ file->dumped_size += BLOCKSIZE;
+ }
+
+ return (rc != add_fail);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+pax_sparse_member_p (struct tar_sparse_file *file)
+{
+ return file->stat_info->sparse_map != NULL && file->stat_info->sparse_map->len > 0
+ && file->stat_info->sparse_major > 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+pax_decode_header (tar_super_t * archive, struct tar_sparse_file *file)
+{
+ if (file->stat_info->sparse_major > 0)
+ {
+ uintmax_t u;
+ char nbuf[UINTMAX_STRSIZE_BOUND];
+ union block *blk;
+ char *p;
+ size_t sparse_map_len;
+ size_t i;
+ off_t start;
+
+ start = tar_current_block_ordinal (archive);
+ tar_set_next_block_after (current_header);
+ blk = tar_find_next_block (archive);
+ if (blk == NULL)
+ /* unexpected EOF in archive */
+ return FALSE;
+ p = blk->buffer;
+ COPY_BUF (archive, blk, nbuf, p);
+
+ if (!decode_num (&u, nbuf, TYPE_MAXIMUM (size_t)))
+ {
+ /* malformed sparse archive member */
+ return FALSE;
+ }
+
+ if (file->stat_info->sparse_map == NULL)
+ file->stat_info->sparse_map =
+ g_array_sized_new (FALSE, FALSE, sizeof (struct sp_array), u);
+ else
+ g_array_set_size (file->stat_info->sparse_map, u);
+
+ sparse_map_len = u;
+
+ for (i = 0; i < sparse_map_len; i++)
+ {
+ struct sp_array sp;
+
+ COPY_BUF (archive, blk, nbuf, p);
+ if (!decode_num (&u, nbuf, TYPE_MAXIMUM (off_t)))
+ {
+ /* malformed sparse archive member */
+ return FALSE;
+ }
+ sp.offset = u;
+ COPY_BUF (archive, blk, nbuf, p);
+ if (!decode_num (&u, nbuf, TYPE_MAXIMUM (size_t)) || INT_ADD_OVERFLOW (sp.offset, u)
+ || (uintmax_t) file->stat_info->stat.st_size < sp.offset + u)
+ {
+ /* malformed sparse archive member */
+ return FALSE;
+ }
+ sp.numbytes = u;
+ sparse_add_map (file->stat_info, &sp);
+ }
+
+ tar_set_next_block_after (blk);
+
+ file->dumped_size += BLOCKSIZE * (tar_current_block_ordinal (archive) - start);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+tar_sparse_member_p (tar_super_t * archive, struct tar_stat_info * st)
+{
+ struct tar_sparse_file file;
+
+ if (!sparse_init (archive, &file))
+ return FALSE;
+
+ file.stat_info = st;
+ return sparse_member_p (&file);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+tar_sparse_fixup_header (tar_super_t * archive, struct tar_stat_info * st)
+{
+ struct tar_sparse_file file;
+
+ if (!sparse_init (archive, &file))
+ return FALSE;
+
+ file.stat_info = st;
+ return sparse_fixup_header (&file);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+enum dump_status
+tar_sparse_skip_file (tar_super_t * archive, struct tar_stat_info *st)
+{
+ gboolean rc = TRUE;
+ struct tar_sparse_file file;
+
+ if (!sparse_init (archive, &file))
+ return dump_status_not_implemented;
+
+ file.stat_info = st;
+ file.fd = -1;
+
+ rc = sparse_decode_header (archive, &file);
+ (void) tar_skip_file (archive, file.stat_info->archive_file_size - file.dumped_size);
+ return (sparse_done (&file) && rc) ? dump_status_ok : dump_status_short;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/tar/tar-xheader.c b/src/vfs/tar/tar-xheader.c
new file mode 100644
index 0000000..5062ed1
--- /dev/null
+++ b/src/vfs/tar/tar-xheader.c
@@ -0,0 +1,1051 @@
+/*
+ Virtual File System: GNU Tar file system.
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2023
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * \brief Source: Virtual File System: GNU Tar file system
+ */
+
+#include <config.h>
+
+#include <ctype.h> /* isdigit() */
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/util.h" /* MC_PTR_FREE */
+
+#include "tar-internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define XHDR_PROTECTED 0x01
+#define XHDR_GLOBAL 0x02
+
+/*** file scope type declarations ****************************************************************/
+
+/* General Interface */
+
+/* Since tar VFS is read-only, inplement decodes only */
+/* *INDENT-OFF* */
+struct xhdr_tab
+{
+ const char *keyword;
+ gboolean (*decoder) (struct tar_stat_info * st, const char *keyword, const char *arg, size_t size);
+ int flags;
+};
+/* *INDENT-ON* */
+
+/* Keyword options */
+struct keyword_item
+{
+ char *pattern;
+ char *value;
+};
+
+enum decode_record_status
+{
+ decode_record_ok,
+ decode_record_finish,
+ decode_record_fail
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static gboolean dummy_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean atime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean gid_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+#if 0
+static gboolean gname_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+#endif
+static gboolean linkpath_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean mtime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean ctime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean path_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean size_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean uid_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+#if 0
+static gboolean uname_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+#endif
+static gboolean sparse_path_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean sparse_major_decoder (struct tar_stat_info *st, const char *keyword,
+ const char *arg, size_t size);
+static gboolean sparse_minor_decoder (struct tar_stat_info *st, const char *keyword,
+ const char *arg, size_t size);
+static gboolean sparse_size_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean sparse_numblocks_decoder (struct tar_stat_info *st, const char *keyword,
+ const char *arg, size_t size);
+static gboolean sparse_offset_decoder (struct tar_stat_info *st, const char *keyword,
+ const char *arg, size_t size);
+static gboolean sparse_numbytes_decoder (struct tar_stat_info *st, const char *keyword,
+ const char *arg, size_t size);
+static gboolean sparse_map_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean dumpdir_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+
+/*** file scope variables ************************************************************************/
+
+enum
+{
+ BILLION = 1000000000,
+ LOG10_BILLION = 9
+};
+
+/* *INDENT-OFF* */
+static struct xhdr_tab xhdr_tab[] =
+{
+ { "atime", atime_decoder, 0 },
+ { "comment", dummy_decoder, 0 },
+ { "charset", dummy_decoder, 0 },
+ { "ctime", ctime_decoder, 0 },
+ { "gid", gid_decoder, 0 },
+#if 0
+ { "gname", gname_decoder, 0 },
+#endif
+ { "linkpath", linkpath_decoder, 0 },
+ { "mtime", mtime_decoder, 0 },
+ { "path", path_decoder, 0 },
+ { "size", size_decoder, 0 },
+ { "uid", uid_decoder, 0 },
+#if 0
+ { "uname", uname_decoder, 0 },
+#endif
+
+ /* Sparse file handling */
+ { "GNU.sparse.name", sparse_path_decoder, XHDR_PROTECTED },
+ { "GNU.sparse.major", sparse_major_decoder, XHDR_PROTECTED },
+ { "GNU.sparse.minor", sparse_minor_decoder, XHDR_PROTECTED },
+ { "GNU.sparse.realsize", sparse_size_decoder, XHDR_PROTECTED },
+ { "GNU.sparse.numblocks", sparse_numblocks_decoder, XHDR_PROTECTED },
+
+ { "GNU.sparse.size", sparse_size_decoder, XHDR_PROTECTED },
+ /* tar 1.14 - 1.15.1 keywords. Multiple instances of these appeared in 'x'
+ headers, and each of them was meaningful. It confilcted with POSIX specs,
+ which requires that "when extended header records conflict, the last one
+ given in the header shall take precedence." */
+ { "GNU.sparse.offset", sparse_offset_decoder, XHDR_PROTECTED },
+ { "GNU.sparse.numbytes", sparse_numbytes_decoder, XHDR_PROTECTED },
+ /* tar 1.15.90 keyword, introduced to remove the above-mentioned conflict. */
+ { "GNU.sparse.map", sparse_map_decoder, 0 },
+
+ { "GNU.dumpdir", dumpdir_decoder, XHDR_PROTECTED },
+
+ { NULL, NULL, 0 }
+};
+/* *INDENT-ON* */
+
+/* List of keyword/value pairs decoded from the last 'g' type header */
+static GSList *global_header_override_list = NULL;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* Convert a prefix of the string @arg to a system integer type whose minimum value is @minval
+ and maximum @maxval. If @minval is negative, negative integers @minval .. -1 are assumed
+ to be represented using leading '-' in the usual way. If the represented value exceeds INTMAX_MAX,
+ return a negative integer V such that (uintmax_t) V yields the represented value. If @arglim is
+ nonnull, store into *@arglim a pointer to the first character after the prefix.
+
+ This is the inverse of sysinttostr.
+
+ On a normal return, set errno = 0.
+ On conversion error, return 0 and set errno = EINVAL.
+ On overflow, return an extreme value and set errno = ERANGE.
+ */
+#if ! (INTMAX_MAX <= UINTMAX_MAX)
+#error "strtosysint: nonnegative intmax_t does not fit in uintmax_t"
+#endif
+static intmax_t
+strtosysint (const char *arg, char **arglim, intmax_t minval, uintmax_t maxval)
+{
+ errno = 0;
+
+ if (maxval <= INTMAX_MAX)
+ {
+ if (isdigit (arg[*arg == '-' ? 1 : 0]))
+ {
+ gint64 i;
+
+ i = g_ascii_strtoll (arg, arglim, 10);
+ if ((gint64) minval <= i && i <= (gint64) maxval)
+ return (intmax_t) i;
+
+ errno = ERANGE;
+ return i < (gint64) minval ? minval : (intmax_t) maxval;
+ }
+ }
+ else
+ {
+ if (isdigit (*arg))
+ {
+ guint64 i;
+
+ i = g_ascii_strtoull (arg, arglim, 10);
+ if (i <= (guint64) maxval)
+ return tar_represent_uintmax ((uintmax_t) i);
+
+ errno = ERANGE;
+ return maxval;
+ }
+ }
+
+ errno = EINVAL;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct xhdr_tab *
+locate_handler (const char *keyword)
+{
+ struct xhdr_tab *p;
+
+ for (p = xhdr_tab; p->keyword != NULL; p++)
+ if (strcmp (p->keyword, keyword) == 0)
+ return p;
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+keyword_item_run (gpointer data, gpointer user_data)
+{
+ struct keyword_item *kp = (struct keyword_item *) data;
+ struct tar_stat_info *st = (struct tar_stat_info *) user_data;
+ struct xhdr_tab const *t;
+
+ t = locate_handler (kp->pattern);
+ if (t != NULL)
+ return t->decoder (st, t->keyword, kp->value, strlen (kp->value));
+
+ return TRUE; /* FIXME */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+keyword_item_free (gpointer data)
+{
+ struct keyword_item *kp = (struct keyword_item *) data;
+
+ g_free (kp->pattern);
+ g_free (kp->value);
+ g_free (kp);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+xheader_list_append (GSList ** root, const char *kw, const char *value)
+{
+ struct keyword_item *kp;
+
+ kp = g_new (struct keyword_item, 1);
+ kp->pattern = g_strdup (kw);
+ kp->value = g_strdup (value);
+ *root = g_slist_prepend (*root, kp);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+xheader_list_destroy (GSList ** root)
+{
+ g_slist_free_full (*root, keyword_item_free);
+ *root = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+run_override_list (GSList * kp, struct tar_stat_info *st)
+{
+ g_slist_foreach (kp, (GFunc) keyword_item_run, st);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct timespec
+decode_timespec (const char *arg, char **arg_lim, gboolean parse_fraction)
+{
+ time_t s = TYPE_MINIMUM (time_t);
+ int ns = -1;
+ const char *p = arg;
+ gboolean negative = *arg == '-';
+ struct timespec r;
+
+ if (!isdigit (arg[negative]))
+ errno = EINVAL;
+ else
+ {
+ errno = 0;
+
+ if (negative)
+ {
+ gint64 i;
+
+ i = g_ascii_strtoll (arg, arg_lim, 10);
+ if (TYPE_SIGNED (time_t) ? TYPE_MINIMUM (time_t) <= i : 0 <= i)
+ s = (intmax_t) i;
+ else
+ errno = ERANGE;
+ }
+ else
+ {
+ guint64 i;
+
+ i = g_ascii_strtoull (arg, arg_lim, 10);
+ if (i <= TYPE_MAXIMUM (time_t))
+ s = (uintmax_t) i;
+ else
+ errno = ERANGE;
+ }
+
+ p = *arg_lim;
+ ns = 0;
+
+ if (parse_fraction && *p == '.')
+ {
+ int digits = 0;
+ gboolean trailing_nonzero = FALSE;
+
+ while (isdigit (*++p))
+ if (digits < LOG10_BILLION)
+ {
+ digits++;
+ ns = 10 * ns + (*p - '0');
+ }
+ else if (*p != '0')
+ trailing_nonzero = TRUE;
+
+ while (digits < LOG10_BILLION)
+ {
+ digits++;
+ ns *= 10;
+ }
+
+ if (negative)
+ {
+ /* Convert "-1.10000000000001" to s == -2, ns == 89999999.
+ I.e., truncate time stamps towards minus infinity while
+ converting them to internal form. */
+ if (trailing_nonzero)
+ ns++;
+ if (ns != 0)
+ {
+ if (s == TYPE_MINIMUM (time_t))
+ ns = -1;
+ else
+ {
+ s--;
+ ns = BILLION - ns;
+ }
+ }
+ }
+ }
+
+ if (errno == ERANGE)
+ ns = -1;
+ }
+
+ *arg_lim = (char *) p;
+ r.tv_sec = s;
+ r.tv_nsec = ns;
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decode_time (struct timespec *ts, const char *arg, const char *keyword)
+{
+ char *arg_lim;
+ struct timespec t;
+
+ (void) keyword;
+
+ t = decode_timespec (arg, &arg_lim, TRUE);
+
+ if (t.tv_nsec < 0)
+ /* Malformed extended header */
+ return FALSE;
+
+ if (*arg_lim != '\0')
+ /* Malformed extended header */
+ return FALSE;
+
+ *ts = t;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decode_signed_num (intmax_t * num, const char *arg, intmax_t minval, uintmax_t maxval,
+ const char *keyword)
+{
+ char *arg_lim;
+ intmax_t u;
+
+ (void) keyword;
+
+ if (!isdigit (*arg))
+ return FALSE; /* malformed extended header */
+
+ u = strtosysint (arg, &arg_lim, minval, maxval);
+
+ if (errno == EINVAL || *arg_lim != '\0')
+ return FALSE; /* malformed extended header */
+
+ if (errno == ERANGE)
+ return FALSE; /* out of range */
+
+ *num = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decode_num (uintmax_t * num, const char *arg, uintmax_t maxval, const char *keyword)
+{
+ intmax_t i;
+
+ if (!decode_signed_num (&i, arg, 0, maxval, keyword))
+ return FALSE;
+
+ *num = i;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+raw_path_decoder (struct tar_stat_info *st, const char *arg)
+{
+ if (*arg != '\0')
+ {
+ tar_assign_string_dup (&st->orig_file_name, arg);
+ tar_assign_string_dup (&st->file_name, arg);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+dummy_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) st;
+ (void) keyword;
+ (void) arg;
+ (void) size;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+atime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ struct timespec ts;
+
+ (void) size;
+
+ if (!decode_time (&ts, arg, keyword))
+ return FALSE;
+
+ st->atime = ts;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+gid_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ intmax_t u;
+
+ (void) size;
+
+ if (!decode_signed_num (&u, arg, TYPE_MINIMUM (gid_t), TYPE_MINIMUM (gid_t), keyword))
+ return FALSE;
+
+ st->stat.st_gid = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if 0
+static gboolean
+gname_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+ (void) size;
+
+ tar_assign_string_dup (&st->gname, arg);
+ return TRUE;
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+linkpath_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+ (void) size;
+
+ tar_assign_string_dup (&st->link_name, arg);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+ctime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ struct timespec ts;
+
+ (void) size;
+
+ if (!decode_time (&ts, arg, keyword))
+ return FALSE;
+
+ st->ctime = ts;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mtime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ struct timespec ts;
+
+ (void) size;
+
+ if (!decode_time (&ts, arg, keyword))
+ return FALSE;
+
+ st->mtime = ts;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+path_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+ (void) size;
+
+ if (!st->sparse_name_done)
+ return raw_path_decoder (st, arg);
+
+ return TRUE; /* FIXME */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+size_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ uintmax_t u;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, TYPE_MAXIMUM (off_t), keyword))
+ return FALSE;
+
+ st->stat.st_size = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+uid_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ intmax_t u;
+
+ (void) size;
+
+ if (!decode_signed_num (&u, arg, TYPE_MINIMUM (uid_t), TYPE_MAXIMUM (uid_t), keyword))
+ return FALSE;
+
+ st->stat.st_uid = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if 0
+static gboolean
+uname_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+ (void) size;
+
+ tar_assign_string_dup (&st->uname, arg);
+ return TRUE;
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+dumpdir_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+
+#if GLIB_CHECK_VERSION (2, 68, 0)
+ st->dumpdir = g_memdup2 (arg, size);
+#else
+ st->dumpdir = g_memdup (arg, size);
+#endif
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Decodes a single extended header record, advancing @ptr to the next record.
+ *
+ * @param p pointer to extended header record
+ * @param st stat info
+ *
+ * @return decode_record_ok or decode_record_finish on success, decode_record_fail otherwize
+ */
+static enum decode_record_status
+decode_record (struct xheader *xhdr, char **ptr,
+ gboolean (*handler) (void *data, const char *keyword, const char *value,
+ size_t size), void *data)
+{
+ char *start = *ptr;
+ char *p = start;
+ size_t len;
+ char *len_lim;
+ const char *keyword;
+ char *nextp;
+ size_t len_max;
+ gboolean ret;
+
+ len_max = xhdr->buffer + xhdr->size - start;
+
+ while (*p == ' ' || *p == '\t')
+ p++;
+
+ if (!isdigit (*p))
+ return (*p != '\0' ? decode_record_fail : decode_record_finish);
+
+ len = (uintmax_t) g_ascii_strtoull (p, &len_lim, 10);
+ if (len_max < len)
+ return decode_record_fail;
+
+ nextp = start + len;
+
+ for (p = len_lim; *p == ' ' || *p == '\t'; p++)
+ ;
+
+ if (p == len_lim)
+ return decode_record_fail;
+
+ keyword = p;
+ p = strchr (p, '=');
+ if (!(p != NULL && p < nextp))
+ return decode_record_fail;
+
+ if (nextp[-1] != '\n')
+ return decode_record_fail;
+
+ *p = nextp[-1] = '\0';
+ ret = handler (data, keyword, p + 1, nextp - p - 2); /* '=' + trailing '\n' */
+ *p = '=';
+ nextp[-1] = '\n';
+ *ptr = nextp;
+
+ return (ret ? decode_record_ok : decode_record_fail);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decg (void *data, const char *keyword, const char *value, size_t size)
+{
+ GSList **kwl = (GSList **) data;
+ struct xhdr_tab const *tab;
+
+ (void) size;
+
+ tab = locate_handler (keyword);
+ if (tab != NULL && (tab->flags & XHDR_GLOBAL) != 0)
+ {
+ if (!tab->decoder (data, keyword, value, size))
+ return FALSE;
+ }
+ else
+ xheader_list_append (kwl, keyword, value);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decx (void *data, const char *keyword, const char *value, size_t size)
+{
+ struct keyword_item kp = {
+ .pattern = (char *) keyword,
+ .value = (char *) value
+ };
+
+ (void) size;
+
+ return keyword_item_run (&kp, data);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_path_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+ (void) size;
+
+ st->sparse_name_done = TRUE;
+ return raw_path_decoder (st, arg);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_major_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ uintmax_t u;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, TYPE_MAXIMUM (unsigned), keyword))
+ return FALSE;
+
+ st->sparse_major = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_minor_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ uintmax_t u;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, TYPE_MAXIMUM (unsigned), keyword))
+ return FALSE;
+
+ st->sparse_minor = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_size_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ uintmax_t u;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, TYPE_MAXIMUM (off_t), keyword))
+ return FALSE;
+
+ st->real_size_set = TRUE;
+ st->real_size = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_numblocks_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size)
+{
+ uintmax_t u;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, SIZE_MAX, keyword))
+ return FALSE;
+
+ if (st->sparse_map == NULL)
+ st->sparse_map = g_array_sized_new (FALSE, FALSE, sizeof (struct sp_array), u);
+ else
+ g_array_set_size (st->sparse_map, u);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_offset_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ uintmax_t u;
+ struct sp_array *s;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, TYPE_MAXIMUM (off_t), keyword))
+ return FALSE;
+
+ s = &g_array_index (st->sparse_map, struct sp_array, st->sparse_map->len - 1);
+ s->offset = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_numbytes_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size)
+{
+ uintmax_t u;
+ struct sp_array s;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, SIZE_MAX, keyword))
+ return FALSE;
+
+ s.offset = 0;
+ s.numbytes = u;
+ g_array_append_val (st->sparse_map, s);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_map_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ gboolean offset = TRUE;
+ struct sp_array e;
+
+ (void) keyword;
+ (void) size;
+
+ if (st->sparse_map != NULL)
+ g_array_set_size (st->sparse_map, 0);
+
+ while (TRUE)
+ {
+ gint64 u;
+ char *delim;
+
+ if (!isdigit (*arg))
+ {
+ /* malformed extended header */
+ return FALSE;
+ }
+
+ errno = 0;
+ u = g_ascii_strtoll (arg, &delim, 10);
+ if (TYPE_MAXIMUM (off_t) < u)
+ {
+ u = TYPE_MAXIMUM (off_t);
+ errno = ERANGE;
+ }
+ if (offset)
+ {
+ e.offset = u;
+ if (errno == ERANGE)
+ {
+ /* out of range */
+ return FALSE;
+ }
+ }
+ else
+ {
+ e.numbytes = u;
+ if (errno == ERANGE)
+ {
+ /* out of range */
+ return FALSE;
+ }
+
+ g_array_append_val (st->sparse_map, e);
+ }
+
+ offset = !offset;
+
+ if (*delim == '\0')
+ break;
+ if (*delim != ',')
+ {
+ /* malformed extended header */
+ return FALSE;
+ }
+
+ arg = delim + 1;
+ }
+
+ if (!offset)
+ {
+ /* malformed extended header */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Decodes an extended header.
+ *
+ * @param st stat info
+ *
+ * @return TRUE on success, FALSE otherwize
+ */
+gboolean
+tar_xheader_decode (struct tar_stat_info * st)
+{
+ char *p;
+ enum decode_record_status status;
+
+ run_override_list (global_header_override_list, st);
+
+ p = st->xhdr.buffer + BLOCKSIZE;
+
+ while ((status = decode_record (&st->xhdr, &p, decx, st)) == decode_record_ok)
+ ;
+
+ if (status == decode_record_fail)
+ return FALSE;
+
+ /* The archived (effective) file size is always set directly in tar header
+ field, possibly overridden by "size" extended header - in both cases,
+ result is now decoded in st->stat.st_size */
+ st->archive_file_size = st->stat.st_size;
+
+ /* The real file size (given by stat()) may be redefined for sparse
+ files in "GNU.sparse.realsize" extended header */
+ if (st->real_size_set)
+ st->stat.st_size = st->real_size;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+tar_xheader_read (tar_super_t * archive, struct xheader * xhdr, union block * p, off_t size)
+{
+ size_t j = 0;
+
+ size = MAX (0, size);
+ size += BLOCKSIZE;
+
+ xhdr->size = size;
+ xhdr->buffer = g_malloc (size + 1);
+ xhdr->buffer[size] = '\0';
+
+ do
+ {
+ size_t len;
+
+ if (p == NULL)
+ return FALSE; /* Unexpected EOF in archive */
+
+ len = MIN (size, BLOCKSIZE);
+
+ memcpy (xhdr->buffer + j, p->buffer, len);
+ tar_set_next_block_after (p);
+ p = tar_find_next_block (archive);
+
+ j += len;
+ size -= len;
+ }
+ while (size > 0);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+tar_xheader_decode_global (struct xheader * xhdr)
+{
+ char *p;
+ gboolean ret;
+
+ p = xhdr->buffer + BLOCKSIZE;
+
+ xheader_list_destroy (&global_header_override_list);
+
+ while ((ret = decode_record (xhdr, &p, decg, &global_header_override_list)) == decode_record_ok)
+ ;
+
+ return (ret == decode_record_finish);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tar_xheader_destroy (struct xheader *xhdr)
+{
+ MC_PTR_FREE (xhdr->buffer);
+ xhdr->size = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/tar/tar.c b/src/vfs/tar/tar.c
new file mode 100644
index 0000000..2d32111
--- /dev/null
+++ b/src/vfs/tar/tar.c
@@ -0,0 +1,1302 @@
+/*
+ Virtual File System: GNU Tar file system.
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Jakub Jelinek, 1995
+ Pavel Machek, 1998
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * \brief Source: Virtual File System: GNU Tar file system
+ * \author Jakub Jelinek
+ * \author Pavel Machek
+ * \date 1995, 1998
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <string.h> /* memset() */
+
+#ifdef hpux
+/* major() and minor() macros (among other things) defined here for hpux */
+#include <sys/mknod.h>
+#endif
+
+#include "lib/global.h"
+#include "lib/util.h"
+#include "lib/unixcompat.h" /* makedev() */
+#include "lib/widget.h" /* message() */
+
+#include "lib/vfs/vfs.h"
+#include "lib/vfs/utilvfs.h"
+#include "lib/vfs/gc.h" /* vfs_rmstamp */
+
+#include "tar-internal.h"
+#include "tar.h"
+
+/*** global variables ****************************************************************************/
+
+/* Size of each record, once in blocks, once in bytes. Those two variables are always related,
+ the second being BLOCKSIZE times the first. */
+const int blocking_factor = DEFAULT_BLOCKING;
+const size_t record_size = DEFAULT_BLOCKING * BLOCKSIZE;
+
+/* As we open one archive at a time, it is safe to have these static */
+union block *record_end; /* last+1 block of archive record */
+union block *current_block; /* current block of archive */
+off_t record_start_block; /* block ordinal at record_start */
+
+union block *current_header;
+
+/* Have we hit EOF yet? */
+gboolean hit_eof;
+
+struct tar_stat_info current_stat_info;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define TAR_SUPER(super) ((tar_super_t *) (super))
+
+/* tar Header Block, from POSIX 1003.1-1990. */
+
+/* The magic field is filled with this if uname and gname are valid. */
+#define TMAGIC "ustar" /* ustar and a null */
+
+#define XHDTYPE 'x' /* Extended header referring to the next file in the archive */
+#define XGLTYPE 'g' /* Global extended header */
+
+/* Values used in typeflag field. */
+#define LNKTYPE '1' /* link */
+#define SYMTYPE '2' /* symbolic link */
+#define CHRTYPE '3' /* character special */
+#define BLKTYPE '4' /* block special */
+#define DIRTYPE '5' /* directory */
+#define FIFOTYPE '6' /* FIFO special */
+
+
+/* OLDGNU_MAGIC uses both magic and version fields, which are contiguous.
+ Found in an archive, it indicates an old GNU header format, which will be
+ hopefully become obsolescent. With OLDGNU_MAGIC, uname and gname are
+ valid, though the header is not truly POSIX conforming. */
+#define OLDGNU_MAGIC "ustar " /* 7 chars and a null */
+
+
+/* Bits used in the mode field, values in octal. */
+#define TSUID 04000 /* set UID on execution */
+#define TSGID 02000 /* set GID on execution */
+#define TSVTX 01000 /* reserved */
+ /* file permissions */
+#define TUREAD 00400 /* read by owner */
+#define TUWRITE 00200 /* write by owner */
+#define TUEXEC 00100 /* execute/search by owner */
+#define TGREAD 00040 /* read by group */
+#define TGWRITE 00020 /* write by group */
+#define TGEXEC 00010 /* execute/search by group */
+#define TOREAD 00004 /* read by other */
+#define TOWRITE 00002 /* write by other */
+#define TOEXEC 00001 /* execute/search by other */
+
+#define GID_FROM_HEADER(where) gid_from_header (where, sizeof (where))
+#define MAJOR_FROM_HEADER(where) major_from_header (where, sizeof (where))
+#define MINOR_FROM_HEADER(where) minor_from_header (where, sizeof (where))
+#define MODE_FROM_HEADER(where,hbits) mode_from_header (where, sizeof (where), hbits)
+#define TIME_FROM_HEADER(where) time_from_header (where, sizeof (where))
+#define UID_FROM_HEADER(where) uid_from_header (where, sizeof (where))
+#define UINTMAX_FROM_HEADER(where) uintmax_from_header (where, sizeof (where))
+
+/*** file scope type declarations ****************************************************************/
+
+typedef enum
+{
+ HEADER_STILL_UNREAD, /* for when read_header has not been called */
+ HEADER_SUCCESS, /* header successfully read and checksummed */
+ HEADER_ZERO_BLOCK, /* zero block where header expected */
+ HEADER_END_OF_FILE, /* true end of file while header expected */
+ HEADER_FAILURE /* ill-formed header, or bad checksum */
+} read_header;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static struct vfs_s_subclass tarfs_subclass;
+static struct vfs_class *vfs_tarfs_ops = VFS_CLASS (&tarfs_subclass);
+
+static struct timespec start_time;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tar_stat_destroy (struct tar_stat_info *st)
+{
+ g_free (st->orig_file_name);
+ g_free (st->file_name);
+ g_free (st->link_name);
+#if 0
+ g_free (st->uname);
+ g_free (st->gname);
+#endif
+ if (st->sparse_map != NULL)
+ {
+ g_array_free (st->sparse_map, TRUE);
+ st->sparse_map = NULL;
+ }
+ tar_xheader_destroy (&st->xhdr);
+ memset (st, 0, sizeof (*st));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline gid_t
+gid_from_header (const char *p, size_t s)
+{
+ return tar_from_header (p, s, "gid_t", TYPE_MINIMUM (gid_t), TYPE_MAXIMUM (gid_t), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline major_t
+major_from_header (const char *p, size_t s)
+{
+ return tar_from_header (p, s, "major_t", TYPE_MINIMUM (major_t), TYPE_MAXIMUM (major_t), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline minor_t
+minor_from_header (const char *p, size_t s)
+{
+ return tar_from_header (p, s, "minor_t", TYPE_MINIMUM (minor_t), TYPE_MAXIMUM (minor_t), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Convert @p to the file mode, as understood by tar.
+ * Store unrecognized mode bits (from 10th up) in @hbits.
+ * Set *hbits if there are any unrecognized bits.
+ * */
+static inline mode_t
+mode_from_header (const char *p, size_t s, gboolean * hbits)
+{
+ unsigned int u;
+ mode_t mode;
+
+ /* Do not complain about unrecognized mode bits. */
+ u = tar_from_header (p, s, "mode_t", INTMAX_MIN, UINTMAX_MAX, FALSE);
+
+ /* *INDENT-OFF* */
+ mode = ((u & TSUID ? S_ISUID : 0)
+ | (u & TSGID ? S_ISGID : 0)
+ | (u & TSVTX ? S_ISVTX : 0)
+ | (u & TUREAD ? S_IRUSR : 0)
+ | (u & TUWRITE ? S_IWUSR : 0)
+ | (u & TUEXEC ? S_IXUSR : 0)
+ | (u & TGREAD ? S_IRGRP : 0)
+ | (u & TGWRITE ? S_IWGRP : 0)
+ | (u & TGEXEC ? S_IXGRP : 0)
+ | (u & TOREAD ? S_IROTH : 0)
+ | (u & TOWRITE ? S_IWOTH : 0)
+ | (u & TOEXEC ? S_IXOTH : 0));
+ /* *INDENT-ON* */
+
+ if (hbits != NULL)
+ *hbits = (u & ~07777) != 0;
+
+ return mode;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline time_t
+time_from_header (const char *p, size_t s)
+{
+ return tar_from_header (p, s, "time_t", TYPE_MINIMUM (time_t), TYPE_MAXIMUM (time_t), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline uid_t
+uid_from_header (const char *p, size_t s)
+{
+ return tar_from_header (p, s, "uid_t", TYPE_MINIMUM (uid_t), TYPE_MAXIMUM (uid_t), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline uintmax_t
+uintmax_from_header (const char *p, size_t s)
+{
+ return tar_from_header (p, s, "uintmax_t", 0, UINTMAX_MAX, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tar_calc_sparse_offsets (struct vfs_s_inode *inode)
+{
+ off_t begin = inode->data_offset;
+ GArray *sm = (GArray *) inode->user_data;
+ size_t i;
+
+ for (i = 0; i < sm->len; i++)
+ {
+ struct sp_array *sp;
+
+ sp = &g_array_index (sm, struct sp_array, i);
+ sp->arch_offset = begin;
+ begin += BLOCKSIZE * (sp->numbytes / BLOCKSIZE + sp->numbytes % BLOCKSIZE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+tar_skip_member (tar_super_t * archive, struct vfs_s_inode *inode)
+{
+ char save_typeflag;
+
+ if (current_stat_info.skipped)
+ return TRUE;
+
+ save_typeflag = current_header->header.typeflag;
+
+ tar_set_next_block_after (current_header);
+
+ if (current_stat_info.is_sparse)
+ {
+ if (inode != NULL)
+ inode->data_offset = BLOCKSIZE * tar_current_block_ordinal (archive);
+
+ (void) tar_sparse_skip_file (archive, &current_stat_info);
+
+ if (inode != NULL)
+ {
+ /* use vfs_s_inode::user_data to keep the sparse map */
+ inode->user_data = current_stat_info.sparse_map;
+ current_stat_info.sparse_map = NULL;
+
+ tar_calc_sparse_offsets (inode);
+ }
+ }
+ else if (save_typeflag != DIRTYPE)
+ {
+ if (inode != NULL)
+ inode->data_offset = BLOCKSIZE * tar_current_block_ordinal (archive);
+
+ return tar_skip_file (archive, current_stat_info.stat.st_size);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Return the number of bytes comprising the space between @pointer through the end
+ * of the current buffer of blocks. This space is available for filling with data,
+ * or taking data from. @pointer is usually (but not always) the result previous
+ * tar_find_next_block() call.
+ */
+static inline size_t
+tar_available_space_after (const union block *pointer)
+{
+ return record_end->buffer - pointer->buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Check header checksum.
+ */
+static read_header
+tar_checksum (const union block *header)
+{
+ size_t i;
+ int unsigned_sum = 0; /* the POSIX one :-) */
+ int signed_sum = 0; /* the Sun one :-( */
+ int recorded_sum;
+ int parsed_sum;
+ const char *p = header->buffer;
+
+ for (i = sizeof (*header); i-- != 0;)
+ {
+ unsigned_sum += (unsigned char) *p;
+ signed_sum += (signed char) (*p++);
+ }
+
+ if (unsigned_sum == 0)
+ return HEADER_ZERO_BLOCK;
+
+ /* Adjust checksum to count the "chksum" field as blanks. */
+ for (i = sizeof (header->header.chksum); i-- != 0;)
+ {
+ unsigned_sum -= (unsigned char) header->header.chksum[i];
+ signed_sum -= (signed char) (header->header.chksum[i]);
+ }
+
+ unsigned_sum += ' ' * sizeof (header->header.chksum);
+ signed_sum += ' ' * sizeof (header->header.chksum);
+
+ parsed_sum =
+ tar_from_header (header->header.chksum, sizeof (header->header.chksum), NULL, 0,
+ INT_MAX, TRUE);
+ if (parsed_sum < 0)
+ return HEADER_FAILURE;
+
+ recorded_sum = parsed_sum;
+
+ if (unsigned_sum != recorded_sum && signed_sum != recorded_sum)
+ return HEADER_FAILURE;
+
+ return HEADER_SUCCESS;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tar_decode_header (union block *header, tar_super_t * arch)
+{
+ gboolean hbits = FALSE;
+
+ current_stat_info.stat.st_mode = MODE_FROM_HEADER (header->header.mode, &hbits);
+
+ /*
+ * Try to determine the archive format.
+ */
+ if (arch->type == TAR_UNKNOWN)
+ {
+ if (strcmp (header->header.magic, TMAGIC) == 0)
+ {
+ if (header->star_header.prefix[130] == 0 && isodigit (header->star_header.atime[0])
+ && header->star_header.atime[11] == ' ' && isodigit (header->star_header.ctime[0])
+ && header->star_header.ctime[11] == ' ')
+ arch->type = TAR_STAR;
+ else if (current_stat_info.xhdr.buffer != NULL)
+ arch->type = TAR_POSIX;
+ else
+ arch->type = TAR_USTAR;
+ }
+ else if (strcmp (header->buffer + offsetof (struct posix_header, magic), OLDGNU_MAGIC) == 0)
+ arch->type = hbits ? TAR_OLDGNU : TAR_GNU;
+ else
+ arch->type = TAR_V7;
+ }
+
+ /*
+ * typeflag on BSDI tar (pax) always '\000'
+ */
+ if (header->header.typeflag == '\000')
+ {
+ size_t len;
+
+ if (header->header.name[sizeof (header->header.name) - 1] != '\0')
+ len = sizeof (header->header.name);
+ else
+ len = strlen (header->header.name);
+
+ if (len != 0 && IS_PATH_SEP (header->header.name[len - 1]))
+ header->header.typeflag = DIRTYPE;
+ }
+
+ if (header->header.typeflag == GNUTYPE_DUMPDIR)
+ if (arch->type == TAR_UNKNOWN)
+ arch->type = TAR_GNU;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tar_fill_stat (struct vfs_s_super *archive, union block *header)
+{
+ tar_super_t *arch = TAR_SUPER (archive);
+
+ /* Adjust current_stat_info.stat.st_mode because there are tar-files with
+ * typeflag==SYMTYPE and S_ISLNK(mod)==0. I don't
+ * know about the other modes but I think I cause no new
+ * problem when I adjust them, too. -- Norbert.
+ */
+ if (header->header.typeflag == DIRTYPE || header->header.typeflag == GNUTYPE_DUMPDIR)
+ current_stat_info.stat.st_mode |= S_IFDIR;
+ else if (header->header.typeflag == SYMTYPE)
+ current_stat_info.stat.st_mode |= S_IFLNK;
+ else if (header->header.typeflag == CHRTYPE)
+ current_stat_info.stat.st_mode |= S_IFCHR;
+ else if (header->header.typeflag == BLKTYPE)
+ current_stat_info.stat.st_mode |= S_IFBLK;
+ else if (header->header.typeflag == FIFOTYPE)
+ current_stat_info.stat.st_mode |= S_IFIFO;
+ else
+ current_stat_info.stat.st_mode |= S_IFREG;
+
+ current_stat_info.stat.st_dev = 0;
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ current_stat_info.stat.st_rdev = 0;
+#endif
+
+ switch (arch->type)
+ {
+ case TAR_USTAR:
+ case TAR_POSIX:
+ case TAR_GNU:
+ case TAR_OLDGNU:
+ /* *INDENT-OFF* */
+ current_stat_info.stat.st_uid = *header->header.uname != '\0'
+ ? (uid_t) vfs_finduid (header->header.uname)
+ : UID_FROM_HEADER (header->header.uid);
+ current_stat_info.stat.st_gid = *header->header.gname != '\0'
+ ? (gid_t) vfs_findgid (header->header.gname)
+ : GID_FROM_HEADER (header->header.gid);
+ /* *INDENT-ON* */
+
+ switch (header->header.typeflag)
+ {
+ case BLKTYPE:
+ case CHRTYPE:
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ current_stat_info.stat.st_rdev =
+ makedev (MAJOR_FROM_HEADER (header->header.devmajor),
+ MINOR_FROM_HEADER (header->header.devminor));
+#endif
+ break;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ current_stat_info.stat.st_uid = UID_FROM_HEADER (header->header.uid);
+ current_stat_info.stat.st_gid = GID_FROM_HEADER (header->header.gid);
+ break;
+ }
+
+ current_stat_info.atime.tv_nsec = 0;
+ current_stat_info.mtime.tv_nsec = 0;
+ current_stat_info.ctime.tv_nsec = 0;
+
+ current_stat_info.mtime.tv_sec = TIME_FROM_HEADER (header->header.mtime);
+ if (arch->type == TAR_GNU || arch->type == TAR_OLDGNU)
+ {
+ current_stat_info.atime.tv_sec = TIME_FROM_HEADER (header->oldgnu_header.atime);
+ current_stat_info.ctime.tv_sec = TIME_FROM_HEADER (header->oldgnu_header.ctime);
+ }
+ else if (arch->type == TAR_STAR)
+ {
+ current_stat_info.atime.tv_sec = TIME_FROM_HEADER (header->star_header.atime);
+ current_stat_info.ctime.tv_sec = TIME_FROM_HEADER (header->star_header.ctime);
+ }
+ else
+ current_stat_info.atime = current_stat_info.ctime = start_time;
+
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ current_stat_info.stat.st_blksize = 8 * 1024; /* FIXME */
+#endif
+ vfs_adjust_stat (&current_stat_info.stat);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tar_free_inode (struct vfs_class *me, struct vfs_s_inode *ino)
+{
+ (void) me;
+
+ /* free sparse_map */
+ if (ino->user_data != NULL)
+ g_array_free ((GArray *) ino->user_data, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static read_header
+tar_insert_entry (struct vfs_class *me, struct vfs_s_super *archive, union block *header,
+ struct vfs_s_inode **inode)
+{
+ char *p, *q;
+ char *file_name = current_stat_info.file_name;
+ char *link_name = current_stat_info.link_name;
+ size_t len;
+ struct vfs_s_inode *parent;
+ struct vfs_s_entry *entry;
+
+ p = strrchr (file_name, PATH_SEP);
+ if (p == NULL)
+ {
+ len = strlen (file_name);
+ p = file_name;
+ q = file_name + len; /* "" */
+ }
+ else
+ {
+ *(p++) = '\0';
+ q = file_name;
+ }
+
+ parent = vfs_s_find_inode (me, archive, q, LINK_NO_FOLLOW, FL_MKDIR);
+ if (parent == NULL)
+ return HEADER_FAILURE;
+
+ *inode = NULL;
+
+ if (header->header.typeflag == LNKTYPE)
+ {
+ if (*link_name != '\0')
+ {
+ len = strlen (link_name);
+ if (IS_PATH_SEP (link_name[len - 1]))
+ link_name[len - 1] = '\0';
+
+ *inode = vfs_s_find_inode (me, archive, link_name, LINK_NO_FOLLOW, FL_NONE);
+ }
+
+ if (*inode == NULL)
+ return HEADER_FAILURE;
+ }
+ else
+ {
+ if (S_ISDIR (current_stat_info.stat.st_mode))
+ {
+ entry = VFS_SUBCLASS (me)->find_entry (me, parent, p, LINK_NO_FOLLOW, FL_NONE);
+ if (entry != NULL)
+ return HEADER_SUCCESS;
+ }
+
+ *inode = vfs_s_new_inode (me, archive, &current_stat_info.stat);
+ /* assgin timestamps after decoding of extended headers */
+ (*inode)->st.st_mtime = current_stat_info.mtime.tv_sec;
+ (*inode)->st.st_atime = current_stat_info.atime.tv_sec;
+ (*inode)->st.st_ctime = current_stat_info.ctime.tv_sec;
+ (*inode)->data_offset = BLOCKSIZE * tar_current_block_ordinal (TAR_SUPER (archive));
+
+ if (link_name != NULL && *link_name != '\0')
+ (*inode)->linkname = g_strdup (link_name);
+ }
+
+ entry = vfs_s_new_entry (me, p, *inode);
+ vfs_s_insert_entry (me, parent, entry);
+
+ return HEADER_SUCCESS;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static read_header
+tar_read_header (struct vfs_class *me, struct vfs_s_super *archive)
+{
+ tar_super_t *arch = TAR_SUPER (archive);
+ union block *header;
+ union block *next_long_name = NULL, *next_long_link = NULL;
+ read_header status = HEADER_SUCCESS;
+
+ while (TRUE)
+ {
+ header = tar_find_next_block (arch);
+ current_header = header;
+ if (header == NULL)
+ {
+ status = HEADER_END_OF_FILE;
+ goto ret;
+ }
+
+ status = tar_checksum (header);
+ if (status != HEADER_SUCCESS)
+ goto ret;
+
+ if (header->header.typeflag == LNKTYPE || header->header.typeflag == DIRTYPE)
+ current_stat_info.stat.st_size = 0; /* Links 0 size on tape */
+ else
+ {
+ current_stat_info.stat.st_size = OFF_FROM_HEADER (header->header.size);
+ if (current_stat_info.stat.st_size < 0)
+ {
+ status = HEADER_FAILURE;
+ goto ret;
+ }
+ }
+
+ tar_decode_header (header, arch);
+ tar_fill_stat (archive, header);
+
+ if (header->header.typeflag == GNUTYPE_LONGNAME
+ || header->header.typeflag == GNUTYPE_LONGLINK)
+ {
+ size_t name_size = current_stat_info.stat.st_size;
+ size_t n;
+ off_t size;
+ union block *header_copy;
+ char *bp;
+ size_t written;
+
+ if (arch->type == TAR_UNKNOWN)
+ arch->type = TAR_GNU;
+
+ n = name_size % BLOCKSIZE;
+ size = name_size + BLOCKSIZE;
+ if (n != 0)
+ size += BLOCKSIZE - n;
+ if ((off_t) name_size != current_stat_info.stat.st_size || size < (off_t) name_size)
+ {
+ message (D_ERROR, MSG_ERROR, _("Inconsistent tar archive"));
+ status = HEADER_FAILURE;
+ goto ret;
+ }
+
+ header_copy = g_malloc (size + 1);
+
+ if (header->header.typeflag == GNUTYPE_LONGNAME)
+ {
+ g_free (next_long_name);
+ next_long_name = header_copy;
+ }
+ else
+ {
+ g_free (next_long_link);
+ next_long_link = header_copy;
+ }
+
+ tar_set_next_block_after (header);
+ *header_copy = *header;
+ bp = header_copy->buffer + BLOCKSIZE;
+
+ for (size -= BLOCKSIZE; size > 0; size -= written)
+ {
+ union block *data_block;
+
+ data_block = tar_find_next_block (arch);
+ if (data_block == NULL)
+ {
+ g_free (header_copy);
+ message (D_ERROR, MSG_ERROR, _("Unexpected EOF on archive file"));
+ status = HEADER_FAILURE;
+ goto ret;
+ }
+
+ written = tar_available_space_after (data_block);
+ if ((off_t) written > size)
+ written = (size_t) size;
+
+ memcpy (bp, data_block->buffer, written);
+ bp += written;
+ tar_set_next_block_after ((union block *) (data_block->buffer + written - 1));
+ }
+
+ *bp = '\0';
+ }
+ else if (header->header.typeflag == XHDTYPE || header->header.typeflag == SOLARIS_XHDTYPE)
+ {
+ if (arch->type == TAR_UNKNOWN)
+ arch->type = TAR_POSIX;
+ if (!tar_xheader_read
+ (arch, &current_stat_info.xhdr, header, OFF_FROM_HEADER (header->header.size)))
+ {
+ message (D_ERROR, MSG_ERROR, _("Unexpected EOF on archive file"));
+ status = HEADER_FAILURE;
+ goto ret;
+ }
+ }
+ else if (header->header.typeflag == XGLTYPE)
+ {
+ struct xheader xhdr;
+ gboolean ok;
+
+ if (arch->type == TAR_UNKNOWN)
+ arch->type = TAR_POSIX;
+
+ memset (&xhdr, 0, sizeof (xhdr));
+ tar_xheader_read (arch, &xhdr, header, OFF_FROM_HEADER (header->header.size));
+ ok = tar_xheader_decode_global (&xhdr);
+ tar_xheader_destroy (&xhdr);
+
+ if (!ok)
+ {
+ message (D_ERROR, MSG_ERROR, _("Inconsistent tar archive"));
+ status = HEADER_FAILURE;
+ goto ret;
+ }
+ }
+ else
+ break;
+ }
+
+ {
+ static union block *recent_long_name = NULL, *recent_long_link = NULL;
+ struct posix_header const *h = &header->header;
+ char *file_name = NULL;
+ char *link_name;
+ struct vfs_s_inode *inode = NULL;
+
+ g_free (recent_long_name);
+
+ if (next_long_name != NULL)
+ {
+ file_name = g_strdup (next_long_name->buffer + BLOCKSIZE);
+ recent_long_name = next_long_name;
+ }
+ else
+ {
+ /* Accept file names as specified by POSIX.1-1996 section 10.1.1. */
+ char *s1 = NULL;
+ char *s2;
+
+ /* Don't parse TAR_OLDGNU incremental headers as POSIX prefixes. */
+ if (h->prefix[0] != '\0' && strcmp (h->magic, TMAGIC) == 0)
+ s1 = g_strndup (h->prefix, sizeof (h->prefix));
+
+ s2 = g_strndup (h->name, sizeof (h->name));
+
+ if (s1 == NULL)
+ file_name = s2;
+ else
+ {
+ file_name = g_strconcat (s1, PATH_SEP_STR, s2, (char *) NULL);
+ g_free (s1);
+ g_free (s2);
+ }
+
+ recent_long_name = NULL;
+ }
+
+ tar_assign_string_dup (&current_stat_info.orig_file_name, file_name);
+ canonicalize_pathname (file_name);
+ tar_assign_string (&current_stat_info.file_name, file_name);
+
+ g_free (recent_long_link);
+
+ if (next_long_link != NULL)
+ {
+ link_name = g_strdup (next_long_link->buffer + BLOCKSIZE);
+ recent_long_link = next_long_link;
+ }
+ else
+ {
+ link_name = g_strndup (h->linkname, sizeof (h->linkname));
+ recent_long_link = NULL;
+ }
+
+ tar_assign_string (&current_stat_info.link_name, link_name);
+
+ if (current_stat_info.xhdr.buffer != NULL && !tar_xheader_decode (&current_stat_info))
+ {
+ status = HEADER_FAILURE;
+ goto ret;
+ }
+
+ if (tar_sparse_member_p (arch, &current_stat_info))
+ {
+ if (!tar_sparse_fixup_header (arch, &current_stat_info))
+ {
+ status = HEADER_FAILURE;
+ goto ret;
+ }
+
+ current_stat_info.is_sparse = TRUE;
+ }
+ else
+ {
+ current_stat_info.is_sparse = FALSE;
+
+ if (((arch->type == TAR_GNU || arch->type == TAR_OLDGNU)
+ && current_header->header.typeflag == GNUTYPE_DUMPDIR)
+ || current_stat_info.dumpdir != NULL)
+ current_stat_info.is_dumpdir = TRUE;
+ }
+
+ status = tar_insert_entry (me, archive, header, &inode);
+ if (status != HEADER_SUCCESS)
+ {
+ message (D_ERROR, MSG_ERROR, _("Inconsistent tar archive"));
+ goto ret;
+ }
+
+ if (recent_long_name == next_long_name)
+ recent_long_name = NULL;
+
+ if (recent_long_link == next_long_link)
+ recent_long_link = NULL;
+
+ if (tar_skip_member (arch, inode))
+ status = HEADER_SUCCESS;
+ else if (hit_eof)
+ status = HEADER_END_OF_FILE;
+ else
+ status = HEADER_FAILURE;
+ }
+
+ ret:
+ g_free (next_long_name);
+ g_free (next_long_link);
+
+ return status;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_super *
+tar_new_archive (struct vfs_class *me)
+{
+ tar_super_t *arch;
+ gint64 usec;
+
+ arch = g_new0 (tar_super_t, 1);
+ arch->base.me = me;
+ arch->fd = -1;
+ arch->type = TAR_UNKNOWN;
+
+ /* Prepare global data needed for tar_find_next_block: */
+ record_start_block = 0;
+ arch->record_start = g_malloc (record_size);
+ record_end = arch->record_start; /* set up for 1st record = # 0 */
+ current_block = arch->record_start;
+ hit_eof = FALSE;
+
+ /* time in microseconds */
+ usec = g_get_real_time ();
+ /* time in seconds and nanoseconds */
+ start_time.tv_sec = usec / G_USEC_PER_SEC;
+ start_time.tv_nsec = (usec % G_USEC_PER_SEC) * 1000;
+
+ return VFS_SUPER (arch);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tar_free_archive (struct vfs_class *me, struct vfs_s_super *archive)
+{
+ tar_super_t *arch = TAR_SUPER (archive);
+
+ (void) me;
+
+ if (arch->fd != -1)
+ {
+ mc_close (arch->fd);
+ arch->fd = -1;
+ }
+
+ g_free (arch->record_start);
+ tar_stat_destroy (&current_stat_info);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Returns status of the tar archive open */
+static gboolean
+tar_open_archive_int (struct vfs_class *me, const vfs_path_t * vpath, struct vfs_s_super *archive)
+{
+ tar_super_t *arch = TAR_SUPER (archive);
+ int result, type;
+ mode_t mode;
+ struct vfs_s_inode *root;
+
+ result = mc_open (vpath, O_RDONLY);
+ if (result == -1)
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot open tar archive\n%s"), vfs_path_as_str (vpath));
+ ERRNOR (ENOENT, FALSE);
+ }
+
+ archive->name = g_strdup (vfs_path_as_str (vpath));
+ mc_stat (vpath, &arch->st);
+
+ /* Find out the method to handle this tar file */
+ type = get_compression_type (result, archive->name);
+ if (type == COMPRESSION_NONE)
+ mc_lseek (result, 0, SEEK_SET);
+ else
+ {
+ char *s;
+ vfs_path_t *tmp_vpath;
+
+ mc_close (result);
+ s = g_strconcat (archive->name, decompress_extension (type), (char *) NULL);
+ tmp_vpath = vfs_path_from_str_flags (s, VPF_NO_CANON);
+ result = mc_open (tmp_vpath, O_RDONLY);
+ vfs_path_free (tmp_vpath, TRUE);
+ if (result == -1)
+ message (D_ERROR, MSG_ERROR, _("Cannot open tar archive\n%s"), s);
+ g_free (s);
+ if (result == -1)
+ {
+ MC_PTR_FREE (archive->name);
+ ERRNOR (ENOENT, FALSE);
+ }
+ }
+
+ arch->fd = result;
+ mode = arch->st.st_mode & 07777;
+ if (mode & 0400)
+ mode |= 0100;
+ if (mode & 0040)
+ mode |= 0010;
+ if (mode & 0004)
+ mode |= 0001;
+ mode |= S_IFDIR;
+
+ root = vfs_s_new_inode (me, archive, &arch->st);
+ root->st.st_mode = mode;
+ root->data_offset = -1;
+ root->st.st_nlink++;
+ root->st.st_dev = VFS_SUBCLASS (me)->rdev++;
+
+ archive->root = root;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Main loop for reading an archive.
+ * Returns 0 on success, -1 on error.
+ */
+static int
+tar_open_archive (struct vfs_s_super *archive, const vfs_path_t * vpath,
+ const vfs_path_element_t * vpath_element)
+{
+ tar_super_t *arch = TAR_SUPER (archive);
+ /* Initial status at start of archive */
+ read_header status = HEADER_STILL_UNREAD;
+
+ /* Open for reading */
+ if (!tar_open_archive_int (vpath_element->class, vpath, archive))
+ return -1;
+
+ tar_find_next_block (arch);
+
+ while (TRUE)
+ {
+ read_header prev_status;
+
+ prev_status = status;
+ tar_stat_destroy (&current_stat_info);
+ status = tar_read_header (vpath_element->class, archive);
+
+ switch (status)
+ {
+ case HEADER_STILL_UNREAD:
+ message (D_ERROR, MSG_ERROR, _("%s\ndoesn't look like a tar archive"),
+ vfs_path_as_str (vpath));
+ return -1;
+
+ case HEADER_SUCCESS:
+ continue;
+
+ /* Record of zeroes */
+ case HEADER_ZERO_BLOCK:
+ tar_set_next_block_after (current_header);
+ (void) tar_read_header (vpath_element->class, archive);
+ status = prev_status;
+ continue;
+
+ case HEADER_END_OF_FILE:
+ break;
+
+ /* Invalid header:
+ * If the previous header was good, tell them that we are skipping bad ones. */
+ case HEADER_FAILURE:
+ tar_set_next_block_after (current_header);
+
+ switch (prev_status)
+ {
+ case HEADER_STILL_UNREAD:
+ message (D_ERROR, MSG_ERROR, _("%s\ndoesn't look like a tar archive"),
+ vfs_path_as_str (vpath));
+ return -1;
+
+ case HEADER_ZERO_BLOCK:
+ case HEADER_SUCCESS:
+ /* Skipping to next header. */
+ break; /* AB: FIXME */
+
+ case HEADER_END_OF_FILE:
+ case HEADER_FAILURE:
+ /* We are in the middle of a cascade of errors. */
+ /* AB: FIXME: TODO: show an error message here */
+ return -1;
+
+ default:
+ break;
+ }
+ continue;
+
+ default:
+ break;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+tar_super_check (const vfs_path_t * vpath)
+{
+ static struct stat stat_buf;
+ int stat_result;
+
+ stat_result = mc_stat (vpath, &stat_buf);
+
+ return (stat_result != 0) ? NULL : &stat_buf;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+tar_super_same (const vfs_path_element_t * vpath_element, struct vfs_s_super *parc,
+ const vfs_path_t * vpath, void *cookie)
+{
+ struct stat *archive_stat = cookie; /* stat of main archive */
+
+ (void) vpath_element;
+
+ if (strcmp (parc->name, vfs_path_as_str (vpath)) != 0)
+ return 0;
+
+ /* Has the cached archive been changed on the disk? */
+ if (parc != NULL && TAR_SUPER (parc)->st.st_mtime < archive_stat->st_mtime)
+ {
+ /* Yes, reload! */
+ vfs_tarfs_ops->free ((vfsid) parc);
+ vfs_rmstamp (vfs_tarfs_ops, (vfsid) parc);
+ return 2;
+ }
+ /* Hasn't been modified, give it a new timeout */
+ vfs_stamp (vfs_tarfs_ops, (vfsid) parc);
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Get indes of current data chunk in a sparse file.
+ *
+ * @param sparse_map map of the sparse file
+ * @param offset offset in the sparse file
+ *
+ * @return an index of ahole or a data chunk
+ * positive: pointer to the data chunk;
+ * negative: pointer to the hole before data chunk;
+ * zero: pointer to the hole after last data chunk
+ *
+ * +--------+--------+-------+--------+-----+-------+--------+---------+
+ * | hole1 | chunk1 | hole2 | chunk2 | ... | holeN | chunkN | holeN+1 |
+ * +--------+--------+-------+--------+-----+-------+--------+---------+
+ * -1 1 -2 2 -N N 0
+ */
+
+static ssize_t
+tar_get_sparse_chunk_idx (const GArray * sparse_map, off_t offset)
+{
+ size_t k;
+
+ for (k = 1; k <= sparse_map->len; k++)
+ {
+ const struct sp_array *chunk;
+
+ chunk = &g_array_index (sparse_map, struct sp_array, k - 1);
+
+ /* are we in the current chunk? */
+ if (offset >= chunk->offset && offset < chunk->offset + chunk->numbytes)
+ return k;
+
+ /* are we before the current chunk? */
+ if (offset < chunk->offset)
+ return -k;
+ }
+
+ /* after the last chunk */
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+tar_read_sparse (vfs_file_handler_t * fh, char *buffer, size_t count)
+{
+ int fd = TAR_SUPER (fh->ino->super)->fd;
+ const GArray *sm = (const GArray *) fh->ino->user_data;
+ ssize_t chunk_idx;
+ const struct sp_array *chunk;
+ off_t remain;
+ ssize_t res;
+
+ chunk_idx = tar_get_sparse_chunk_idx (sm, fh->pos);
+ if (chunk_idx > 0)
+ {
+ /* we are in the chunk -- read data until chunk end */
+ chunk = &g_array_index (sm, struct sp_array, chunk_idx - 1);
+ remain = MIN ((off_t) count, chunk->offset + chunk->numbytes - fh->pos);
+ res = mc_read (fd, buffer, (size_t) remain);
+ }
+ else
+ {
+ if (chunk_idx == 0)
+ {
+ /* we are in the hole after last chunk -- return zeros until file end */
+ remain = MIN ((off_t) count, fh->ino->st.st_size - fh->pos);
+ /* FIXME: can remain be negative? */
+ remain = MAX (remain, 0);
+ }
+ else /* chunk_idx < 0 */
+ {
+ /* we are in the hole -- return zeros until next chunk start */
+ chunk = &g_array_index (sm, struct sp_array, -chunk_idx - 1);
+ remain = MIN ((off_t) count, chunk->offset - fh->pos);
+ }
+
+ memset (buffer, 0, (size_t) remain);
+ res = (ssize_t) remain;
+ }
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+tar_lseek_sparse (vfs_file_handler_t * fh, off_t offset)
+{
+ off_t saved_offset = offset;
+ int fd = TAR_SUPER (fh->ino->super)->fd;
+ const GArray *sm = (const GArray *) fh->ino->user_data;
+ ssize_t chunk_idx;
+ const struct sp_array *chunk;
+ off_t res;
+
+ chunk_idx = tar_get_sparse_chunk_idx (sm, offset);
+ if (chunk_idx > 0)
+ {
+ /* we are in the chunk */
+
+ chunk = &g_array_index (sm, struct sp_array, chunk_idx - 1);
+ /* offset in the chunk */
+ offset -= chunk->offset;
+ /* offset in the archive */
+ offset += chunk->arch_offset;
+ }
+ else
+ {
+ /* we are in the hole */
+
+ /* we cannot lseek in hole so seek to the hole begin or end */
+ switch (chunk_idx)
+ {
+ case -1:
+ offset = fh->ino->data_offset;
+ break;
+
+ case 0:
+ chunk = &g_array_index (sm, struct sp_array, sm->len - 1);
+ /* FIXME: can we seek beyond tar archive EOF here? */
+ offset = chunk->arch_offset + chunk->numbytes;
+ break;
+
+ default:
+ chunk = &g_array_index (sm, struct sp_array, -chunk_idx - 1);
+ offset = chunk->arch_offset + chunk->numbytes;
+ break;
+ }
+ }
+
+ res = mc_lseek (fd, offset, SEEK_SET);
+ /* return requested offset in success */
+ if (res == offset)
+ res = saved_offset;
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+tar_read (void *fh, char *buffer, size_t count)
+{
+ struct vfs_class *me = VFS_FILE_HANDLER_SUPER (fh)->me;
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ int fd = TAR_SUPER (VFS_FILE_HANDLER_SUPER (fh))->fd;
+ off_t begin = file->pos;
+ ssize_t res;
+
+ if (file->ino->user_data != NULL)
+ {
+ if (tar_lseek_sparse (file, begin) != begin)
+ ERRNOR (EIO, -1);
+
+ res = tar_read_sparse (file, buffer, count);
+ }
+ else
+ {
+ begin += file->ino->data_offset;
+
+ if (mc_lseek (fd, begin, SEEK_SET) != begin)
+ ERRNOR (EIO, -1);
+
+ count = (size_t) MIN ((off_t) count, file->ino->st.st_size - file->pos);
+ res = mc_read (fd, buffer, count);
+ }
+
+ if (res == -1)
+ ERRNOR (errno, -1);
+
+ file->pos += res;
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+tar_fh_open (struct vfs_class *me, vfs_file_handler_t * fh, int flags, mode_t mode)
+{
+ (void) fh;
+ (void) mode;
+
+ if ((flags & O_ACCMODE) != O_RDONLY)
+ ERRNOR (EROFS, -1);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init_tarfs (void)
+{
+ /* FIXME: tarfs used own temp files */
+ vfs_init_subclass (&tarfs_subclass, "tarfs", VFSF_READONLY, "utar");
+ vfs_tarfs_ops->read = tar_read;
+ vfs_tarfs_ops->setctl = NULL;
+ tarfs_subclass.archive_check = tar_super_check;
+ tarfs_subclass.archive_same = tar_super_same;
+ tarfs_subclass.new_archive = tar_new_archive;
+ tarfs_subclass.open_archive = tar_open_archive;
+ tarfs_subclass.free_archive = tar_free_archive;
+ tarfs_subclass.free_inode = tar_free_inode;
+ tarfs_subclass.fh_open = tar_fh_open;
+ vfs_register_class (vfs_tarfs_ops);
+
+ tar_base64_init ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/tar/tar.h b/src/vfs/tar/tar.h
new file mode 100644
index 0000000..5ad11b5
--- /dev/null
+++ b/src/vfs/tar/tar.h
@@ -0,0 +1,18 @@
+#ifndef MC__VFS_TAR_H
+#define MC__VFS_TAR_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void vfs_init_tarfs (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__VFS_TAR_H */