diff options
Diffstat (limited to '')
-rw-r--r-- | src/vfs/tar/Makefile.am | 10 | ||||
-rw-r--r-- | src/vfs/tar/Makefile.in | 750 | ||||
-rw-r--r-- | src/vfs/tar/tar-internal.c | 482 | ||||
-rw-r--r-- | src/vfs/tar/tar-internal.h | 351 | ||||
-rw-r--r-- | src/vfs/tar/tar-sparse.c | 777 | ||||
-rw-r--r-- | src/vfs/tar/tar-xheader.c | 1051 | ||||
-rw-r--r-- | src/vfs/tar/tar.c | 1302 | ||||
-rw-r--r-- | src/vfs/tar/tar.h | 18 |
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, ¤t_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 (¤t_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, ¤t_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, ¤t_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 (¤t_stat_info.orig_file_name, file_name); + canonicalize_pathname (file_name); + tar_assign_string (¤t_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 (¤t_stat_info.link_name, link_name); + + if (current_stat_info.xhdr.buffer != NULL && !tar_xheader_decode (¤t_stat_info)) + { + status = HEADER_FAILURE; + goto ret; + } + + if (tar_sparse_member_p (arch, ¤t_stat_info)) + { + if (!tar_sparse_fixup_header (arch, ¤t_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 (¤t_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 (¤t_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 */ |