summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2016-05-16 09:22:21 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2016-05-16 09:22:21 +0000
commitd4fdeab4db0d0e699c8fbbb07f12c4e1f64d0f94 (patch)
tree72231a38ed1cdd48ac6e02acb8b3917acb9dbcf4 /src
downloadtar-tarball-master.tar.gz
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am55
-rw-r--r--src/Makefile.in1587
-rw-r--r--src/arith.h28
-rw-r--r--src/buffer.c2048
-rw-r--r--src/checkpoint.c438
-rw-r--r--src/common.h974
-rw-r--r--src/compare.c649
-rw-r--r--src/create.c1972
-rw-r--r--src/delete.c394
-rw-r--r--src/exclist.c329
-rw-r--r--src/exit.c39
-rw-r--r--src/extract.c1820
-rw-r--r--src/incremen.c1805
-rw-r--r--src/list.c1463
-rw-r--r--src/map.c283
-rw-r--r--src/misc.c1250
-rw-r--r--src/names.c1838
-rw-r--r--src/sparse.c1295
-rw-r--r--src/suffix.c114
-rw-r--r--src/system.c900
-rw-r--r--src/tar.c2852
-rw-r--r--src/tar.h379
-rw-r--r--src/transform.c637
-rw-r--r--src/unlink.c237
-rw-r--r--src/update.c234
-rw-r--r--src/utf8.c99
-rw-r--r--src/warning.c107
-rw-r--r--src/xattrs.c755
-rw-r--r--src/xattrs.h50
-rw-r--r--src/xheader.c1798
30 files changed, 26429 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..08fc24c
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,55 @@
+# Makefile for GNU tar sources.
+
+# Copyright 1994-1997, 1999-2001, 2003, 2006-2007, 2009, 2013-2014, 2016
+# Free Software Foundation, Inc.
+
+# This file is part of GNU tar.
+
+# GNU tar 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.
+
+# GNU tar 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/>.
+
+bin_PROGRAMS = tar
+
+noinst_HEADERS = arith.h common.h tar.h xattrs.h
+tar_SOURCES = \
+ buffer.c\
+ checkpoint.c\
+ compare.c\
+ create.c\
+ delete.c\
+ exit.c\
+ exclist.c\
+ extract.c\
+ xheader.c\
+ incremen.c\
+ list.c\
+ map.c\
+ misc.c\
+ names.c\
+ sparse.c\
+ suffix.c\
+ system.c\
+ tar.c\
+ transform.c\
+ unlink.c\
+ update.c\
+ utf8.c\
+ warning.c\
+ xattrs.c
+
+AM_CPPFLAGS = -I$(top_srcdir)/gnu -I../ -I../gnu -I$(top_srcdir)/lib -I../lib
+AM_CFLAGS = $(WARN_CFLAGS) $(WERROR_CFLAGS)
+
+LDADD = ../lib/libtar.a ../gnu/libgnu.a $(LIBINTL) $(LIBICONV)
+
+tar_LDADD = $(LIBS) $(LDADD) $(LIB_CLOCK_GETTIME) $(LIB_EACCESS) $(LIB_SELINUX)
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 0000000..91567f5
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,1587 @@
+# Makefile.in generated by automake 1.14 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 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@
+
+# Makefile for GNU tar sources.
+
+# Copyright 1994-1997, 1999-2001, 2003, 2006-2007, 2009, 2013-2014, 2016
+# Free Software Foundation, Inc.
+
+# This file is part of GNU tar.
+
+# GNU tar 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.
+
+# GNU tar 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/>.
+
+
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+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@
+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@
+bin_PROGRAMS = tar$(EXEEXT)
+subdir = src
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
+ $(top_srcdir)/build-aux/depcomp $(noinst_HEADERS)
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/00gnulib.m4 \
+ $(top_srcdir)/m4/absolute-header.m4 $(top_srcdir)/m4/acl.m4 \
+ $(top_srcdir)/m4/alloca.m4 $(top_srcdir)/m4/argp.m4 \
+ $(top_srcdir)/m4/backupfile.m4 $(top_srcdir)/m4/bison.m4 \
+ $(top_srcdir)/m4/btowc.m4 $(top_srcdir)/m4/canonicalize.m4 \
+ $(top_srcdir)/m4/chdir-long.m4 $(top_srcdir)/m4/chown.m4 \
+ $(top_srcdir)/m4/clock_time.m4 \
+ $(top_srcdir)/m4/close-stream.m4 $(top_srcdir)/m4/close.m4 \
+ $(top_srcdir)/m4/closedir.m4 $(top_srcdir)/m4/closeout.m4 \
+ $(top_srcdir)/m4/codeset.m4 $(top_srcdir)/m4/configmake.m4 \
+ $(top_srcdir)/m4/d-ino.m4 $(top_srcdir)/m4/dirent-safer.m4 \
+ $(top_srcdir)/m4/dirent_h.m4 $(top_srcdir)/m4/dirfd.m4 \
+ $(top_srcdir)/m4/dirname.m4 \
+ $(top_srcdir)/m4/double-slash-root.m4 $(top_srcdir)/m4/dup.m4 \
+ $(top_srcdir)/m4/dup2.m4 $(top_srcdir)/m4/eealloc.m4 \
+ $(top_srcdir)/m4/environ.m4 $(top_srcdir)/m4/errno_h.m4 \
+ $(top_srcdir)/m4/error.m4 $(top_srcdir)/m4/euidaccess.m4 \
+ $(top_srcdir)/m4/exponentd.m4 $(top_srcdir)/m4/extensions.m4 \
+ $(top_srcdir)/m4/extern-inline.m4 \
+ $(top_srcdir)/m4/faccessat.m4 $(top_srcdir)/m4/fchdir.m4 \
+ $(top_srcdir)/m4/fchmodat.m4 $(top_srcdir)/m4/fchownat.m4 \
+ $(top_srcdir)/m4/fcntl-o.m4 $(top_srcdir)/m4/fcntl.m4 \
+ $(top_srcdir)/m4/fcntl_h.m4 $(top_srcdir)/m4/fdopendir.m4 \
+ $(top_srcdir)/m4/fileblocks.m4 $(top_srcdir)/m4/filenamecat.m4 \
+ $(top_srcdir)/m4/flexmember.m4 $(top_srcdir)/m4/float_h.m4 \
+ $(top_srcdir)/m4/fnmatch.m4 $(top_srcdir)/m4/fpending.m4 \
+ $(top_srcdir)/m4/fseek.m4 $(top_srcdir)/m4/fseeko.m4 \
+ $(top_srcdir)/m4/fstat.m4 $(top_srcdir)/m4/fstatat.m4 \
+ $(top_srcdir)/m4/futimens.m4 \
+ $(top_srcdir)/m4/getcwd-abort-bug.m4 \
+ $(top_srcdir)/m4/getcwd-path-max.m4 $(top_srcdir)/m4/getcwd.m4 \
+ $(top_srcdir)/m4/getdelim.m4 $(top_srcdir)/m4/getdtablesize.m4 \
+ $(top_srcdir)/m4/getgroups.m4 $(top_srcdir)/m4/getline.m4 \
+ $(top_srcdir)/m4/getopt.m4 $(top_srcdir)/m4/getpagesize.m4 \
+ $(top_srcdir)/m4/gettext.m4 $(top_srcdir)/m4/gettime.m4 \
+ $(top_srcdir)/m4/gettimeofday.m4 $(top_srcdir)/m4/glibc21.m4 \
+ $(top_srcdir)/m4/gnulib-common.m4 \
+ $(top_srcdir)/m4/gnulib-comp.m4 \
+ $(top_srcdir)/m4/group-member.m4 $(top_srcdir)/m4/human.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/include_next.m4 \
+ $(top_srcdir)/m4/intlmacosx.m4 $(top_srcdir)/m4/intmax_t.m4 \
+ $(top_srcdir)/m4/inttostr.m4 $(top_srcdir)/m4/inttypes-pri.m4 \
+ $(top_srcdir)/m4/inttypes.m4 $(top_srcdir)/m4/inttypes_h.m4 \
+ $(top_srcdir)/m4/iswblank.m4 $(top_srcdir)/m4/langinfo_h.m4 \
+ $(top_srcdir)/m4/largefile.m4 $(top_srcdir)/m4/lchown.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 \
+ $(top_srcdir)/m4/libunistring-base.m4 \
+ $(top_srcdir)/m4/link-follow.m4 $(top_srcdir)/m4/link.m4 \
+ $(top_srcdir)/m4/linkat.m4 $(top_srcdir)/m4/localcharset.m4 \
+ $(top_srcdir)/m4/locale-fr.m4 $(top_srcdir)/m4/locale-ja.m4 \
+ $(top_srcdir)/m4/locale-zh.m4 $(top_srcdir)/m4/locale_h.m4 \
+ $(top_srcdir)/m4/localeconv.m4 $(top_srcdir)/m4/longlong.m4 \
+ $(top_srcdir)/m4/lseek.m4 $(top_srcdir)/m4/lstat.m4 \
+ $(top_srcdir)/m4/malloc.m4 $(top_srcdir)/m4/malloca.m4 \
+ $(top_srcdir)/m4/manywarnings.m4 $(top_srcdir)/m4/mbchar.m4 \
+ $(top_srcdir)/m4/mbiter.m4 $(top_srcdir)/m4/mbrtowc.m4 \
+ $(top_srcdir)/m4/mbsinit.m4 $(top_srcdir)/m4/mbsrtowcs.m4 \
+ $(top_srcdir)/m4/mbstate_t.m4 $(top_srcdir)/m4/mbtowc.m4 \
+ $(top_srcdir)/m4/memchr.m4 $(top_srcdir)/m4/mempcpy.m4 \
+ $(top_srcdir)/m4/memrchr.m4 $(top_srcdir)/m4/mkdir.m4 \
+ $(top_srcdir)/m4/mkdirat.m4 $(top_srcdir)/m4/mkdtemp.m4 \
+ $(top_srcdir)/m4/mkfifo.m4 $(top_srcdir)/m4/mkfifoat.m4 \
+ $(top_srcdir)/m4/mknod.m4 $(top_srcdir)/m4/mktime.m4 \
+ $(top_srcdir)/m4/mmap-anon.m4 $(top_srcdir)/m4/mode_t.m4 \
+ $(top_srcdir)/m4/modechange.m4 $(top_srcdir)/m4/msvc-inval.m4 \
+ $(top_srcdir)/m4/msvc-nothrow.m4 $(top_srcdir)/m4/multiarch.m4 \
+ $(top_srcdir)/m4/nl_langinfo.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/nocrash.m4 $(top_srcdir)/m4/obstack.m4 \
+ $(top_srcdir)/m4/off_t.m4 $(top_srcdir)/m4/open.m4 \
+ $(top_srcdir)/m4/openat.m4 $(top_srcdir)/m4/opendir.m4 \
+ $(top_srcdir)/m4/parse-datetime.m4 $(top_srcdir)/m4/pathmax.m4 \
+ $(top_srcdir)/m4/paxutils.m4 $(top_srcdir)/m4/po.m4 \
+ $(top_srcdir)/m4/printf.m4 $(top_srcdir)/m4/priv-set.m4 \
+ $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/m4/quote.m4 \
+ $(top_srcdir)/m4/quotearg.m4 $(top_srcdir)/m4/raise.m4 \
+ $(top_srcdir)/m4/rawmemchr.m4 $(top_srcdir)/m4/read.m4 \
+ $(top_srcdir)/m4/readdir.m4 $(top_srcdir)/m4/readlink.m4 \
+ $(top_srcdir)/m4/readlinkat.m4 $(top_srcdir)/m4/realloc.m4 \
+ $(top_srcdir)/m4/regex.m4 $(top_srcdir)/m4/rename.m4 \
+ $(top_srcdir)/m4/renameat.m4 $(top_srcdir)/m4/rewinddir.m4 \
+ $(top_srcdir)/m4/rmdir.m4 $(top_srcdir)/m4/rmt.m4 \
+ $(top_srcdir)/m4/rpmatch.m4 $(top_srcdir)/m4/rtapelib.m4 \
+ $(top_srcdir)/m4/safe-read.m4 $(top_srcdir)/m4/safe-write.m4 \
+ $(top_srcdir)/m4/save-cwd.m4 $(top_srcdir)/m4/savedir.m4 \
+ $(top_srcdir)/m4/secure_getenv.m4 \
+ $(top_srcdir)/m4/selinux-context-h.m4 \
+ $(top_srcdir)/m4/selinux-selinux-h.m4 \
+ $(top_srcdir)/m4/setenv.m4 $(top_srcdir)/m4/signal_h.m4 \
+ $(top_srcdir)/m4/size_max.m4 $(top_srcdir)/m4/sleep.m4 \
+ $(top_srcdir)/m4/snprintf.m4 $(top_srcdir)/m4/ssize_t.m4 \
+ $(top_srcdir)/m4/stat-time.m4 $(top_srcdir)/m4/stat.m4 \
+ $(top_srcdir)/m4/stdalign.m4 $(top_srcdir)/m4/stdarg.m4 \
+ $(top_srcdir)/m4/stdbool.m4 $(top_srcdir)/m4/stddef_h.m4 \
+ $(top_srcdir)/m4/stdint.m4 $(top_srcdir)/m4/stdint_h.m4 \
+ $(top_srcdir)/m4/stdio_h.m4 $(top_srcdir)/m4/stdlib_h.m4 \
+ $(top_srcdir)/m4/stpcpy.m4 $(top_srcdir)/m4/strcase.m4 \
+ $(top_srcdir)/m4/strchrnul.m4 $(top_srcdir)/m4/strdup.m4 \
+ $(top_srcdir)/m4/strerror.m4 $(top_srcdir)/m4/strftime.m4 \
+ $(top_srcdir)/m4/string_h.m4 $(top_srcdir)/m4/strings_h.m4 \
+ $(top_srcdir)/m4/strndup.m4 $(top_srcdir)/m4/strnlen.m4 \
+ $(top_srcdir)/m4/strtoimax.m4 $(top_srcdir)/m4/strtol.m4 \
+ $(top_srcdir)/m4/strtoll.m4 $(top_srcdir)/m4/strtoul.m4 \
+ $(top_srcdir)/m4/strtoull.m4 $(top_srcdir)/m4/strtoumax.m4 \
+ $(top_srcdir)/m4/symlink.m4 $(top_srcdir)/m4/symlinkat.m4 \
+ $(top_srcdir)/m4/sys_socket_h.m4 \
+ $(top_srcdir)/m4/sys_stat_h.m4 $(top_srcdir)/m4/sys_time_h.m4 \
+ $(top_srcdir)/m4/sys_types_h.m4 $(top_srcdir)/m4/sysexits.m4 \
+ $(top_srcdir)/m4/system.m4 $(top_srcdir)/m4/tempname.m4 \
+ $(top_srcdir)/m4/time_h.m4 $(top_srcdir)/m4/time_r.m4 \
+ $(top_srcdir)/m4/time_rz.m4 $(top_srcdir)/m4/timegm.m4 \
+ $(top_srcdir)/m4/timespec.m4 $(top_srcdir)/m4/tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ulonglong.m4 $(top_srcdir)/m4/unistd-safer.m4 \
+ $(top_srcdir)/m4/unistd_h.m4 $(top_srcdir)/m4/unlink.m4 \
+ $(top_srcdir)/m4/unlinkat.m4 $(top_srcdir)/m4/unlinkdir.m4 \
+ $(top_srcdir)/m4/unlocked-io.m4 $(top_srcdir)/m4/utimbuf.m4 \
+ $(top_srcdir)/m4/utimens.m4 $(top_srcdir)/m4/utimensat.m4 \
+ $(top_srcdir)/m4/utimes.m4 $(top_srcdir)/m4/vasnprintf.m4 \
+ $(top_srcdir)/m4/vasprintf.m4 $(top_srcdir)/m4/version-etc.m4 \
+ $(top_srcdir)/m4/vsnprintf.m4 $(top_srcdir)/m4/warn-on-use.m4 \
+ $(top_srcdir)/m4/warnings.m4 $(top_srcdir)/m4/wchar_h.m4 \
+ $(top_srcdir)/m4/wchar_t.m4 $(top_srcdir)/m4/wcrtomb.m4 \
+ $(top_srcdir)/m4/wctype_h.m4 $(top_srcdir)/m4/wcwidth.m4 \
+ $(top_srcdir)/m4/wint_t.m4 $(top_srcdir)/m4/write.m4 \
+ $(top_srcdir)/m4/xalloc.m4 $(top_srcdir)/m4/xgetcwd.m4 \
+ $(top_srcdir)/m4/xsize.m4 $(top_srcdir)/m4/xstrndup.m4 \
+ $(top_srcdir)/m4/xstrtol.m4 $(top_srcdir)/m4/xvasprintf.m4 \
+ $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+am_tar_OBJECTS = buffer.$(OBJEXT) checkpoint.$(OBJEXT) \
+ compare.$(OBJEXT) create.$(OBJEXT) delete.$(OBJEXT) \
+ exit.$(OBJEXT) exclist.$(OBJEXT) extract.$(OBJEXT) \
+ xheader.$(OBJEXT) incremen.$(OBJEXT) list.$(OBJEXT) \
+ map.$(OBJEXT) misc.$(OBJEXT) names.$(OBJEXT) sparse.$(OBJEXT) \
+ suffix.$(OBJEXT) system.$(OBJEXT) tar.$(OBJEXT) \
+ transform.$(OBJEXT) unlink.$(OBJEXT) update.$(OBJEXT) \
+ utf8.$(OBJEXT) warning.$(OBJEXT) xattrs.$(OBJEXT)
+tar_OBJECTS = $(am_tar_OBJECTS)
+am__DEPENDENCIES_1 =
+am__DEPENDENCIES_2 = ../lib/libtar.a ../gnu/libgnu.a \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+tar_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_2) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_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)/build-aux/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+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 = $(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 = $(tar_SOURCES)
+DIST_SOURCES = $(tar_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+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)`
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+pkglibexecdir = @pkglibexecdir@
+ACLOCAL = @ACLOCAL@
+ALLOCA = @ALLOCA@
+ALLOCA_H = @ALLOCA_H@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPLE_UNIVERSAL_BUILD = @APPLE_UNIVERSAL_BUILD@
+AR = @AR@
+ARFLAGS = @ARFLAGS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOM4TE = @AUTOM4TE@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BACKUP_LIBEXEC_SCRIPTS = @BACKUP_LIBEXEC_SCRIPTS@
+BACKUP_SBIN_SCRIPTS = @BACKUP_SBIN_SCRIPTS@
+BACKUP_SED_COND = @BACKUP_SED_COND@
+BITSIZEOF_PTRDIFF_T = @BITSIZEOF_PTRDIFF_T@
+BITSIZEOF_SIG_ATOMIC_T = @BITSIZEOF_SIG_ATOMIC_T@
+BITSIZEOF_SIZE_T = @BITSIZEOF_SIZE_T@
+BITSIZEOF_WCHAR_T = @BITSIZEOF_WCHAR_T@
+BITSIZEOF_WINT_T = @BITSIZEOF_WINT_T@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFAULT_ARCHIVE = @DEFAULT_ARCHIVE@
+DEFAULT_ARCHIVE_FORMAT = @DEFAULT_ARCHIVE_FORMAT@
+DEFAULT_BLOCKING = @DEFAULT_BLOCKING@
+DEFAULT_QUOTING_STYLE = @DEFAULT_QUOTING_STYLE@
+DEFAULT_RMT_COMMAND = @DEFAULT_RMT_COMMAND@
+DEFAULT_RMT_DIR = @DEFAULT_RMT_DIR@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EMULTIHOP_HIDDEN = @EMULTIHOP_HIDDEN@
+EMULTIHOP_VALUE = @EMULTIHOP_VALUE@
+ENOLINK_HIDDEN = @ENOLINK_HIDDEN@
+ENOLINK_VALUE = @ENOLINK_VALUE@
+EOVERFLOW_HIDDEN = @EOVERFLOW_HIDDEN@
+EOVERFLOW_VALUE = @EOVERFLOW_VALUE@
+ERRNO_H = @ERRNO_H@
+EXEEXT = @EXEEXT@
+FLOAT_H = @FLOAT_H@
+FNMATCH_H = @FNMATCH_H@
+GETOPT_H = @GETOPT_H@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIBC21 = @GLIBC21@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GNULIB_ALPHASORT = @GNULIB_ALPHASORT@
+GNULIB_ATOLL = @GNULIB_ATOLL@
+GNULIB_BTOWC = @GNULIB_BTOWC@
+GNULIB_CALLOC_POSIX = @GNULIB_CALLOC_POSIX@
+GNULIB_CANONICALIZE_FILE_NAME = @GNULIB_CANONICALIZE_FILE_NAME@
+GNULIB_CHDIR = @GNULIB_CHDIR@
+GNULIB_CHOWN = @GNULIB_CHOWN@
+GNULIB_CLOSE = @GNULIB_CLOSE@
+GNULIB_CLOSEDIR = @GNULIB_CLOSEDIR@
+GNULIB_DIRFD = @GNULIB_DIRFD@
+GNULIB_DPRINTF = @GNULIB_DPRINTF@
+GNULIB_DUP = @GNULIB_DUP@
+GNULIB_DUP2 = @GNULIB_DUP2@
+GNULIB_DUP3 = @GNULIB_DUP3@
+GNULIB_DUPLOCALE = @GNULIB_DUPLOCALE@
+GNULIB_ENVIRON = @GNULIB_ENVIRON@
+GNULIB_EUIDACCESS = @GNULIB_EUIDACCESS@
+GNULIB_FACCESSAT = @GNULIB_FACCESSAT@
+GNULIB_FCHDIR = @GNULIB_FCHDIR@
+GNULIB_FCHMODAT = @GNULIB_FCHMODAT@
+GNULIB_FCHOWNAT = @GNULIB_FCHOWNAT@
+GNULIB_FCLOSE = @GNULIB_FCLOSE@
+GNULIB_FCNTL = @GNULIB_FCNTL@
+GNULIB_FDATASYNC = @GNULIB_FDATASYNC@
+GNULIB_FDOPEN = @GNULIB_FDOPEN@
+GNULIB_FDOPENDIR = @GNULIB_FDOPENDIR@
+GNULIB_FFLUSH = @GNULIB_FFLUSH@
+GNULIB_FFS = @GNULIB_FFS@
+GNULIB_FFSL = @GNULIB_FFSL@
+GNULIB_FFSLL = @GNULIB_FFSLL@
+GNULIB_FGETC = @GNULIB_FGETC@
+GNULIB_FGETS = @GNULIB_FGETS@
+GNULIB_FOPEN = @GNULIB_FOPEN@
+GNULIB_FPRINTF = @GNULIB_FPRINTF@
+GNULIB_FPRINTF_POSIX = @GNULIB_FPRINTF_POSIX@
+GNULIB_FPURGE = @GNULIB_FPURGE@
+GNULIB_FPUTC = @GNULIB_FPUTC@
+GNULIB_FPUTS = @GNULIB_FPUTS@
+GNULIB_FREAD = @GNULIB_FREAD@
+GNULIB_FREOPEN = @GNULIB_FREOPEN@
+GNULIB_FSCANF = @GNULIB_FSCANF@
+GNULIB_FSEEK = @GNULIB_FSEEK@
+GNULIB_FSEEKO = @GNULIB_FSEEKO@
+GNULIB_FSTAT = @GNULIB_FSTAT@
+GNULIB_FSTATAT = @GNULIB_FSTATAT@
+GNULIB_FSYNC = @GNULIB_FSYNC@
+GNULIB_FTELL = @GNULIB_FTELL@
+GNULIB_FTELLO = @GNULIB_FTELLO@
+GNULIB_FTRUNCATE = @GNULIB_FTRUNCATE@
+GNULIB_FUTIMENS = @GNULIB_FUTIMENS@
+GNULIB_FWRITE = @GNULIB_FWRITE@
+GNULIB_GETC = @GNULIB_GETC@
+GNULIB_GETCHAR = @GNULIB_GETCHAR@
+GNULIB_GETCWD = @GNULIB_GETCWD@
+GNULIB_GETDELIM = @GNULIB_GETDELIM@
+GNULIB_GETDOMAINNAME = @GNULIB_GETDOMAINNAME@
+GNULIB_GETDTABLESIZE = @GNULIB_GETDTABLESIZE@
+GNULIB_GETGROUPS = @GNULIB_GETGROUPS@
+GNULIB_GETHOSTNAME = @GNULIB_GETHOSTNAME@
+GNULIB_GETLINE = @GNULIB_GETLINE@
+GNULIB_GETLOADAVG = @GNULIB_GETLOADAVG@
+GNULIB_GETLOGIN = @GNULIB_GETLOGIN@
+GNULIB_GETLOGIN_R = @GNULIB_GETLOGIN_R@
+GNULIB_GETPAGESIZE = @GNULIB_GETPAGESIZE@
+GNULIB_GETSUBOPT = @GNULIB_GETSUBOPT@
+GNULIB_GETTIMEOFDAY = @GNULIB_GETTIMEOFDAY@
+GNULIB_GETUSERSHELL = @GNULIB_GETUSERSHELL@
+GNULIB_GL_UNISTD_H_GETOPT = @GNULIB_GL_UNISTD_H_GETOPT@
+GNULIB_GRANTPT = @GNULIB_GRANTPT@
+GNULIB_GROUP_MEMBER = @GNULIB_GROUP_MEMBER@
+GNULIB_IMAXABS = @GNULIB_IMAXABS@
+GNULIB_IMAXDIV = @GNULIB_IMAXDIV@
+GNULIB_ISATTY = @GNULIB_ISATTY@
+GNULIB_ISWBLANK = @GNULIB_ISWBLANK@
+GNULIB_ISWCTYPE = @GNULIB_ISWCTYPE@
+GNULIB_LCHMOD = @GNULIB_LCHMOD@
+GNULIB_LCHOWN = @GNULIB_LCHOWN@
+GNULIB_LINK = @GNULIB_LINK@
+GNULIB_LINKAT = @GNULIB_LINKAT@
+GNULIB_LOCALECONV = @GNULIB_LOCALECONV@
+GNULIB_LSEEK = @GNULIB_LSEEK@
+GNULIB_LSTAT = @GNULIB_LSTAT@
+GNULIB_MALLOC_POSIX = @GNULIB_MALLOC_POSIX@
+GNULIB_MBRLEN = @GNULIB_MBRLEN@
+GNULIB_MBRTOWC = @GNULIB_MBRTOWC@
+GNULIB_MBSCASECMP = @GNULIB_MBSCASECMP@
+GNULIB_MBSCASESTR = @GNULIB_MBSCASESTR@
+GNULIB_MBSCHR = @GNULIB_MBSCHR@
+GNULIB_MBSCSPN = @GNULIB_MBSCSPN@
+GNULIB_MBSINIT = @GNULIB_MBSINIT@
+GNULIB_MBSLEN = @GNULIB_MBSLEN@
+GNULIB_MBSNCASECMP = @GNULIB_MBSNCASECMP@
+GNULIB_MBSNLEN = @GNULIB_MBSNLEN@
+GNULIB_MBSNRTOWCS = @GNULIB_MBSNRTOWCS@
+GNULIB_MBSPBRK = @GNULIB_MBSPBRK@
+GNULIB_MBSPCASECMP = @GNULIB_MBSPCASECMP@
+GNULIB_MBSRCHR = @GNULIB_MBSRCHR@
+GNULIB_MBSRTOWCS = @GNULIB_MBSRTOWCS@
+GNULIB_MBSSEP = @GNULIB_MBSSEP@
+GNULIB_MBSSPN = @GNULIB_MBSSPN@
+GNULIB_MBSSTR = @GNULIB_MBSSTR@
+GNULIB_MBSTOK_R = @GNULIB_MBSTOK_R@
+GNULIB_MBTOWC = @GNULIB_MBTOWC@
+GNULIB_MEMCHR = @GNULIB_MEMCHR@
+GNULIB_MEMMEM = @GNULIB_MEMMEM@
+GNULIB_MEMPCPY = @GNULIB_MEMPCPY@
+GNULIB_MEMRCHR = @GNULIB_MEMRCHR@
+GNULIB_MKDIRAT = @GNULIB_MKDIRAT@
+GNULIB_MKDTEMP = @GNULIB_MKDTEMP@
+GNULIB_MKFIFO = @GNULIB_MKFIFO@
+GNULIB_MKFIFOAT = @GNULIB_MKFIFOAT@
+GNULIB_MKNOD = @GNULIB_MKNOD@
+GNULIB_MKNODAT = @GNULIB_MKNODAT@
+GNULIB_MKOSTEMP = @GNULIB_MKOSTEMP@
+GNULIB_MKOSTEMPS = @GNULIB_MKOSTEMPS@
+GNULIB_MKSTEMP = @GNULIB_MKSTEMP@
+GNULIB_MKSTEMPS = @GNULIB_MKSTEMPS@
+GNULIB_MKTIME = @GNULIB_MKTIME@
+GNULIB_NANOSLEEP = @GNULIB_NANOSLEEP@
+GNULIB_NL_LANGINFO = @GNULIB_NL_LANGINFO@
+GNULIB_NONBLOCKING = @GNULIB_NONBLOCKING@
+GNULIB_OBSTACK_PRINTF = @GNULIB_OBSTACK_PRINTF@
+GNULIB_OBSTACK_PRINTF_POSIX = @GNULIB_OBSTACK_PRINTF_POSIX@
+GNULIB_OPEN = @GNULIB_OPEN@
+GNULIB_OPENAT = @GNULIB_OPENAT@
+GNULIB_OPENDIR = @GNULIB_OPENDIR@
+GNULIB_PCLOSE = @GNULIB_PCLOSE@
+GNULIB_PERROR = @GNULIB_PERROR@
+GNULIB_PIPE = @GNULIB_PIPE@
+GNULIB_PIPE2 = @GNULIB_PIPE2@
+GNULIB_POPEN = @GNULIB_POPEN@
+GNULIB_POSIX_OPENPT = @GNULIB_POSIX_OPENPT@
+GNULIB_PREAD = @GNULIB_PREAD@
+GNULIB_PRINTF = @GNULIB_PRINTF@
+GNULIB_PRINTF_POSIX = @GNULIB_PRINTF_POSIX@
+GNULIB_PTHREAD_SIGMASK = @GNULIB_PTHREAD_SIGMASK@
+GNULIB_PTSNAME = @GNULIB_PTSNAME@
+GNULIB_PTSNAME_R = @GNULIB_PTSNAME_R@
+GNULIB_PUTC = @GNULIB_PUTC@
+GNULIB_PUTCHAR = @GNULIB_PUTCHAR@
+GNULIB_PUTENV = @GNULIB_PUTENV@
+GNULIB_PUTS = @GNULIB_PUTS@
+GNULIB_PWRITE = @GNULIB_PWRITE@
+GNULIB_QSORT_R = @GNULIB_QSORT_R@
+GNULIB_RAISE = @GNULIB_RAISE@
+GNULIB_RANDOM = @GNULIB_RANDOM@
+GNULIB_RANDOM_R = @GNULIB_RANDOM_R@
+GNULIB_RAWMEMCHR = @GNULIB_RAWMEMCHR@
+GNULIB_READ = @GNULIB_READ@
+GNULIB_READDIR = @GNULIB_READDIR@
+GNULIB_READLINK = @GNULIB_READLINK@
+GNULIB_READLINKAT = @GNULIB_READLINKAT@
+GNULIB_REALLOC_POSIX = @GNULIB_REALLOC_POSIX@
+GNULIB_REALPATH = @GNULIB_REALPATH@
+GNULIB_REMOVE = @GNULIB_REMOVE@
+GNULIB_RENAME = @GNULIB_RENAME@
+GNULIB_RENAMEAT = @GNULIB_RENAMEAT@
+GNULIB_REWINDDIR = @GNULIB_REWINDDIR@
+GNULIB_RMDIR = @GNULIB_RMDIR@
+GNULIB_RPMATCH = @GNULIB_RPMATCH@
+GNULIB_SCANDIR = @GNULIB_SCANDIR@
+GNULIB_SCANF = @GNULIB_SCANF@
+GNULIB_SECURE_GETENV = @GNULIB_SECURE_GETENV@
+GNULIB_SETENV = @GNULIB_SETENV@
+GNULIB_SETHOSTNAME = @GNULIB_SETHOSTNAME@
+GNULIB_SETLOCALE = @GNULIB_SETLOCALE@
+GNULIB_SIGACTION = @GNULIB_SIGACTION@
+GNULIB_SIGNAL_H_SIGPIPE = @GNULIB_SIGNAL_H_SIGPIPE@
+GNULIB_SIGPROCMASK = @GNULIB_SIGPROCMASK@
+GNULIB_SLEEP = @GNULIB_SLEEP@
+GNULIB_SNPRINTF = @GNULIB_SNPRINTF@
+GNULIB_SPRINTF_POSIX = @GNULIB_SPRINTF_POSIX@
+GNULIB_STAT = @GNULIB_STAT@
+GNULIB_STDIO_H_NONBLOCKING = @GNULIB_STDIO_H_NONBLOCKING@
+GNULIB_STDIO_H_SIGPIPE = @GNULIB_STDIO_H_SIGPIPE@
+GNULIB_STPCPY = @GNULIB_STPCPY@
+GNULIB_STPNCPY = @GNULIB_STPNCPY@
+GNULIB_STRCASESTR = @GNULIB_STRCASESTR@
+GNULIB_STRCHRNUL = @GNULIB_STRCHRNUL@
+GNULIB_STRDUP = @GNULIB_STRDUP@
+GNULIB_STRERROR = @GNULIB_STRERROR@
+GNULIB_STRERROR_R = @GNULIB_STRERROR_R@
+GNULIB_STRNCAT = @GNULIB_STRNCAT@
+GNULIB_STRNDUP = @GNULIB_STRNDUP@
+GNULIB_STRNLEN = @GNULIB_STRNLEN@
+GNULIB_STRPBRK = @GNULIB_STRPBRK@
+GNULIB_STRPTIME = @GNULIB_STRPTIME@
+GNULIB_STRSEP = @GNULIB_STRSEP@
+GNULIB_STRSIGNAL = @GNULIB_STRSIGNAL@
+GNULIB_STRSTR = @GNULIB_STRSTR@
+GNULIB_STRTOD = @GNULIB_STRTOD@
+GNULIB_STRTOIMAX = @GNULIB_STRTOIMAX@
+GNULIB_STRTOK_R = @GNULIB_STRTOK_R@
+GNULIB_STRTOLL = @GNULIB_STRTOLL@
+GNULIB_STRTOULL = @GNULIB_STRTOULL@
+GNULIB_STRTOUMAX = @GNULIB_STRTOUMAX@
+GNULIB_STRVERSCMP = @GNULIB_STRVERSCMP@
+GNULIB_SYMLINK = @GNULIB_SYMLINK@
+GNULIB_SYMLINKAT = @GNULIB_SYMLINKAT@
+GNULIB_SYSTEM_POSIX = @GNULIB_SYSTEM_POSIX@
+GNULIB_TEST_WARN_CFLAGS = @GNULIB_TEST_WARN_CFLAGS@
+GNULIB_TIMEGM = @GNULIB_TIMEGM@
+GNULIB_TIME_R = @GNULIB_TIME_R@
+GNULIB_TIME_RZ = @GNULIB_TIME_RZ@
+GNULIB_TMPFILE = @GNULIB_TMPFILE@
+GNULIB_TOWCTRANS = @GNULIB_TOWCTRANS@
+GNULIB_TTYNAME_R = @GNULIB_TTYNAME_R@
+GNULIB_UNISTD_H_NONBLOCKING = @GNULIB_UNISTD_H_NONBLOCKING@
+GNULIB_UNISTD_H_SIGPIPE = @GNULIB_UNISTD_H_SIGPIPE@
+GNULIB_UNLINK = @GNULIB_UNLINK@
+GNULIB_UNLINKAT = @GNULIB_UNLINKAT@
+GNULIB_UNLOCKPT = @GNULIB_UNLOCKPT@
+GNULIB_UNSETENV = @GNULIB_UNSETENV@
+GNULIB_USLEEP = @GNULIB_USLEEP@
+GNULIB_UTIMENSAT = @GNULIB_UTIMENSAT@
+GNULIB_VASPRINTF = @GNULIB_VASPRINTF@
+GNULIB_VDPRINTF = @GNULIB_VDPRINTF@
+GNULIB_VFPRINTF = @GNULIB_VFPRINTF@
+GNULIB_VFPRINTF_POSIX = @GNULIB_VFPRINTF_POSIX@
+GNULIB_VFSCANF = @GNULIB_VFSCANF@
+GNULIB_VPRINTF = @GNULIB_VPRINTF@
+GNULIB_VPRINTF_POSIX = @GNULIB_VPRINTF_POSIX@
+GNULIB_VSCANF = @GNULIB_VSCANF@
+GNULIB_VSNPRINTF = @GNULIB_VSNPRINTF@
+GNULIB_VSPRINTF_POSIX = @GNULIB_VSPRINTF_POSIX@
+GNULIB_WARN_CFLAGS = @GNULIB_WARN_CFLAGS@
+GNULIB_WCPCPY = @GNULIB_WCPCPY@
+GNULIB_WCPNCPY = @GNULIB_WCPNCPY@
+GNULIB_WCRTOMB = @GNULIB_WCRTOMB@
+GNULIB_WCSCASECMP = @GNULIB_WCSCASECMP@
+GNULIB_WCSCAT = @GNULIB_WCSCAT@
+GNULIB_WCSCHR = @GNULIB_WCSCHR@
+GNULIB_WCSCMP = @GNULIB_WCSCMP@
+GNULIB_WCSCOLL = @GNULIB_WCSCOLL@
+GNULIB_WCSCPY = @GNULIB_WCSCPY@
+GNULIB_WCSCSPN = @GNULIB_WCSCSPN@
+GNULIB_WCSDUP = @GNULIB_WCSDUP@
+GNULIB_WCSLEN = @GNULIB_WCSLEN@
+GNULIB_WCSNCASECMP = @GNULIB_WCSNCASECMP@
+GNULIB_WCSNCAT = @GNULIB_WCSNCAT@
+GNULIB_WCSNCMP = @GNULIB_WCSNCMP@
+GNULIB_WCSNCPY = @GNULIB_WCSNCPY@
+GNULIB_WCSNLEN = @GNULIB_WCSNLEN@
+GNULIB_WCSNRTOMBS = @GNULIB_WCSNRTOMBS@
+GNULIB_WCSPBRK = @GNULIB_WCSPBRK@
+GNULIB_WCSRCHR = @GNULIB_WCSRCHR@
+GNULIB_WCSRTOMBS = @GNULIB_WCSRTOMBS@
+GNULIB_WCSSPN = @GNULIB_WCSSPN@
+GNULIB_WCSSTR = @GNULIB_WCSSTR@
+GNULIB_WCSTOK = @GNULIB_WCSTOK@
+GNULIB_WCSWIDTH = @GNULIB_WCSWIDTH@
+GNULIB_WCSXFRM = @GNULIB_WCSXFRM@
+GNULIB_WCTOB = @GNULIB_WCTOB@
+GNULIB_WCTOMB = @GNULIB_WCTOMB@
+GNULIB_WCTRANS = @GNULIB_WCTRANS@
+GNULIB_WCTYPE = @GNULIB_WCTYPE@
+GNULIB_WCWIDTH = @GNULIB_WCWIDTH@
+GNULIB_WMEMCHR = @GNULIB_WMEMCHR@
+GNULIB_WMEMCMP = @GNULIB_WMEMCMP@
+GNULIB_WMEMCPY = @GNULIB_WMEMCPY@
+GNULIB_WMEMMOVE = @GNULIB_WMEMMOVE@
+GNULIB_WMEMSET = @GNULIB_WMEMSET@
+GNULIB_WRITE = @GNULIB_WRITE@
+GNULIB__EXIT = @GNULIB__EXIT@
+GREP = @GREP@
+HAVE_ALPHASORT = @HAVE_ALPHASORT@
+HAVE_ATOLL = @HAVE_ATOLL@
+HAVE_BTOWC = @HAVE_BTOWC@
+HAVE_CANONICALIZE_FILE_NAME = @HAVE_CANONICALIZE_FILE_NAME@
+HAVE_CHOWN = @HAVE_CHOWN@
+HAVE_CLOSEDIR = @HAVE_CLOSEDIR@
+HAVE_DECL_DIRFD = @HAVE_DECL_DIRFD@
+HAVE_DECL_ENVIRON = @HAVE_DECL_ENVIRON@
+HAVE_DECL_FCHDIR = @HAVE_DECL_FCHDIR@
+HAVE_DECL_FDATASYNC = @HAVE_DECL_FDATASYNC@
+HAVE_DECL_FDOPENDIR = @HAVE_DECL_FDOPENDIR@
+HAVE_DECL_FPURGE = @HAVE_DECL_FPURGE@
+HAVE_DECL_FSEEKO = @HAVE_DECL_FSEEKO@
+HAVE_DECL_FTELLO = @HAVE_DECL_FTELLO@
+HAVE_DECL_GETDELIM = @HAVE_DECL_GETDELIM@
+HAVE_DECL_GETDOMAINNAME = @HAVE_DECL_GETDOMAINNAME@
+HAVE_DECL_GETLINE = @HAVE_DECL_GETLINE@
+HAVE_DECL_GETLOADAVG = @HAVE_DECL_GETLOADAVG@
+HAVE_DECL_GETLOGIN_R = @HAVE_DECL_GETLOGIN_R@
+HAVE_DECL_GETPAGESIZE = @HAVE_DECL_GETPAGESIZE@
+HAVE_DECL_GETUSERSHELL = @HAVE_DECL_GETUSERSHELL@
+HAVE_DECL_IMAXABS = @HAVE_DECL_IMAXABS@
+HAVE_DECL_IMAXDIV = @HAVE_DECL_IMAXDIV@
+HAVE_DECL_LOCALTIME_R = @HAVE_DECL_LOCALTIME_R@
+HAVE_DECL_MEMMEM = @HAVE_DECL_MEMMEM@
+HAVE_DECL_MEMRCHR = @HAVE_DECL_MEMRCHR@
+HAVE_DECL_OBSTACK_PRINTF = @HAVE_DECL_OBSTACK_PRINTF@
+HAVE_DECL_SETENV = @HAVE_DECL_SETENV@
+HAVE_DECL_SETHOSTNAME = @HAVE_DECL_SETHOSTNAME@
+HAVE_DECL_SNPRINTF = @HAVE_DECL_SNPRINTF@
+HAVE_DECL_STRDUP = @HAVE_DECL_STRDUP@
+HAVE_DECL_STRERROR_R = @HAVE_DECL_STRERROR_R@
+HAVE_DECL_STRNCASECMP = @HAVE_DECL_STRNCASECMP@
+HAVE_DECL_STRNDUP = @HAVE_DECL_STRNDUP@
+HAVE_DECL_STRNLEN = @HAVE_DECL_STRNLEN@
+HAVE_DECL_STRSIGNAL = @HAVE_DECL_STRSIGNAL@
+HAVE_DECL_STRTOIMAX = @HAVE_DECL_STRTOIMAX@
+HAVE_DECL_STRTOK_R = @HAVE_DECL_STRTOK_R@
+HAVE_DECL_STRTOUMAX = @HAVE_DECL_STRTOUMAX@
+HAVE_DECL_TTYNAME_R = @HAVE_DECL_TTYNAME_R@
+HAVE_DECL_UNSETENV = @HAVE_DECL_UNSETENV@
+HAVE_DECL_VSNPRINTF = @HAVE_DECL_VSNPRINTF@
+HAVE_DECL_WCTOB = @HAVE_DECL_WCTOB@
+HAVE_DECL_WCWIDTH = @HAVE_DECL_WCWIDTH@
+HAVE_DIRENT_H = @HAVE_DIRENT_H@
+HAVE_DPRINTF = @HAVE_DPRINTF@
+HAVE_DUP2 = @HAVE_DUP2@
+HAVE_DUP3 = @HAVE_DUP3@
+HAVE_DUPLOCALE = @HAVE_DUPLOCALE@
+HAVE_EUIDACCESS = @HAVE_EUIDACCESS@
+HAVE_FACCESSAT = @HAVE_FACCESSAT@
+HAVE_FCHDIR = @HAVE_FCHDIR@
+HAVE_FCHMODAT = @HAVE_FCHMODAT@
+HAVE_FCHOWNAT = @HAVE_FCHOWNAT@
+HAVE_FCNTL = @HAVE_FCNTL@
+HAVE_FDATASYNC = @HAVE_FDATASYNC@
+HAVE_FDOPENDIR = @HAVE_FDOPENDIR@
+HAVE_FEATURES_H = @HAVE_FEATURES_H@
+HAVE_FFS = @HAVE_FFS@
+HAVE_FFSL = @HAVE_FFSL@
+HAVE_FFSLL = @HAVE_FFSLL@
+HAVE_FSEEKO = @HAVE_FSEEKO@
+HAVE_FSTATAT = @HAVE_FSTATAT@
+HAVE_FSYNC = @HAVE_FSYNC@
+HAVE_FTELLO = @HAVE_FTELLO@
+HAVE_FTRUNCATE = @HAVE_FTRUNCATE@
+HAVE_FUTIMENS = @HAVE_FUTIMENS@
+HAVE_GETDTABLESIZE = @HAVE_GETDTABLESIZE@
+HAVE_GETGROUPS = @HAVE_GETGROUPS@
+HAVE_GETHOSTNAME = @HAVE_GETHOSTNAME@
+HAVE_GETLOGIN = @HAVE_GETLOGIN@
+HAVE_GETOPT_H = @HAVE_GETOPT_H@
+HAVE_GETPAGESIZE = @HAVE_GETPAGESIZE@
+HAVE_GETSUBOPT = @HAVE_GETSUBOPT@
+HAVE_GETTIMEOFDAY = @HAVE_GETTIMEOFDAY@
+HAVE_GRANTPT = @HAVE_GRANTPT@
+HAVE_GROUP_MEMBER = @HAVE_GROUP_MEMBER@
+HAVE_INTTYPES_H = @HAVE_INTTYPES_H@
+HAVE_ISWBLANK = @HAVE_ISWBLANK@
+HAVE_ISWCNTRL = @HAVE_ISWCNTRL@
+HAVE_LANGINFO_CODESET = @HAVE_LANGINFO_CODESET@
+HAVE_LANGINFO_ERA = @HAVE_LANGINFO_ERA@
+HAVE_LANGINFO_H = @HAVE_LANGINFO_H@
+HAVE_LANGINFO_T_FMT_AMPM = @HAVE_LANGINFO_T_FMT_AMPM@
+HAVE_LANGINFO_YESEXPR = @HAVE_LANGINFO_YESEXPR@
+HAVE_LCHMOD = @HAVE_LCHMOD@
+HAVE_LCHOWN = @HAVE_LCHOWN@
+HAVE_LINK = @HAVE_LINK@
+HAVE_LINKAT = @HAVE_LINKAT@
+HAVE_LONG_LONG_INT = @HAVE_LONG_LONG_INT@
+HAVE_LSTAT = @HAVE_LSTAT@
+HAVE_MAX_ALIGN_T = @HAVE_MAX_ALIGN_T@
+HAVE_MBRLEN = @HAVE_MBRLEN@
+HAVE_MBRTOWC = @HAVE_MBRTOWC@
+HAVE_MBSINIT = @HAVE_MBSINIT@
+HAVE_MBSLEN = @HAVE_MBSLEN@
+HAVE_MBSNRTOWCS = @HAVE_MBSNRTOWCS@
+HAVE_MBSRTOWCS = @HAVE_MBSRTOWCS@
+HAVE_MEMCHR = @HAVE_MEMCHR@
+HAVE_MEMPCPY = @HAVE_MEMPCPY@
+HAVE_MKDIRAT = @HAVE_MKDIRAT@
+HAVE_MKDTEMP = @HAVE_MKDTEMP@
+HAVE_MKFIFO = @HAVE_MKFIFO@
+HAVE_MKFIFOAT = @HAVE_MKFIFOAT@
+HAVE_MKNOD = @HAVE_MKNOD@
+HAVE_MKNODAT = @HAVE_MKNODAT@
+HAVE_MKOSTEMP = @HAVE_MKOSTEMP@
+HAVE_MKOSTEMPS = @HAVE_MKOSTEMPS@
+HAVE_MKSTEMP = @HAVE_MKSTEMP@
+HAVE_MKSTEMPS = @HAVE_MKSTEMPS@
+HAVE_MSVC_INVALID_PARAMETER_HANDLER = @HAVE_MSVC_INVALID_PARAMETER_HANDLER@
+HAVE_NANOSLEEP = @HAVE_NANOSLEEP@
+HAVE_NL_LANGINFO = @HAVE_NL_LANGINFO@
+HAVE_OPENAT = @HAVE_OPENAT@
+HAVE_OPENDIR = @HAVE_OPENDIR@
+HAVE_OS_H = @HAVE_OS_H@
+HAVE_PCLOSE = @HAVE_PCLOSE@
+HAVE_PIPE = @HAVE_PIPE@
+HAVE_PIPE2 = @HAVE_PIPE2@
+HAVE_POPEN = @HAVE_POPEN@
+HAVE_POSIX_OPENPT = @HAVE_POSIX_OPENPT@
+HAVE_POSIX_SIGNALBLOCKING = @HAVE_POSIX_SIGNALBLOCKING@
+HAVE_PREAD = @HAVE_PREAD@
+HAVE_PTHREAD_SIGMASK = @HAVE_PTHREAD_SIGMASK@
+HAVE_PTSNAME = @HAVE_PTSNAME@
+HAVE_PTSNAME_R = @HAVE_PTSNAME_R@
+HAVE_PWRITE = @HAVE_PWRITE@
+HAVE_RAISE = @HAVE_RAISE@
+HAVE_RANDOM = @HAVE_RANDOM@
+HAVE_RANDOM_H = @HAVE_RANDOM_H@
+HAVE_RANDOM_R = @HAVE_RANDOM_R@
+HAVE_RAWMEMCHR = @HAVE_RAWMEMCHR@
+HAVE_READDIR = @HAVE_READDIR@
+HAVE_READLINK = @HAVE_READLINK@
+HAVE_READLINKAT = @HAVE_READLINKAT@
+HAVE_REALPATH = @HAVE_REALPATH@
+HAVE_RENAMEAT = @HAVE_RENAMEAT@
+HAVE_REWINDDIR = @HAVE_REWINDDIR@
+HAVE_RPMATCH = @HAVE_RPMATCH@
+HAVE_SCANDIR = @HAVE_SCANDIR@
+HAVE_SECURE_GETENV = @HAVE_SECURE_GETENV@
+HAVE_SETENV = @HAVE_SETENV@
+HAVE_SETHOSTNAME = @HAVE_SETHOSTNAME@
+HAVE_SIGACTION = @HAVE_SIGACTION@
+HAVE_SIGHANDLER_T = @HAVE_SIGHANDLER_T@
+HAVE_SIGINFO_T = @HAVE_SIGINFO_T@
+HAVE_SIGNED_SIG_ATOMIC_T = @HAVE_SIGNED_SIG_ATOMIC_T@
+HAVE_SIGNED_WCHAR_T = @HAVE_SIGNED_WCHAR_T@
+HAVE_SIGNED_WINT_T = @HAVE_SIGNED_WINT_T@
+HAVE_SIGSET_T = @HAVE_SIGSET_T@
+HAVE_SLEEP = @HAVE_SLEEP@
+HAVE_STDINT_H = @HAVE_STDINT_H@
+HAVE_STPCPY = @HAVE_STPCPY@
+HAVE_STPNCPY = @HAVE_STPNCPY@
+HAVE_STRCASECMP = @HAVE_STRCASECMP@
+HAVE_STRCASESTR = @HAVE_STRCASESTR@
+HAVE_STRCHRNUL = @HAVE_STRCHRNUL@
+HAVE_STRINGS_H = @HAVE_STRINGS_H@
+HAVE_STRPBRK = @HAVE_STRPBRK@
+HAVE_STRPTIME = @HAVE_STRPTIME@
+HAVE_STRSEP = @HAVE_STRSEP@
+HAVE_STRTOD = @HAVE_STRTOD@
+HAVE_STRTOLL = @HAVE_STRTOLL@
+HAVE_STRTOULL = @HAVE_STRTOULL@
+HAVE_STRUCT_RANDOM_DATA = @HAVE_STRUCT_RANDOM_DATA@
+HAVE_STRUCT_SIGACTION_SA_SIGACTION = @HAVE_STRUCT_SIGACTION_SA_SIGACTION@
+HAVE_STRUCT_TIMEVAL = @HAVE_STRUCT_TIMEVAL@
+HAVE_STRVERSCMP = @HAVE_STRVERSCMP@
+HAVE_SYMLINK = @HAVE_SYMLINK@
+HAVE_SYMLINKAT = @HAVE_SYMLINKAT@
+HAVE_SYSEXITS_H = @HAVE_SYSEXITS_H@
+HAVE_SYS_BITYPES_H = @HAVE_SYS_BITYPES_H@
+HAVE_SYS_INTTYPES_H = @HAVE_SYS_INTTYPES_H@
+HAVE_SYS_LOADAVG_H = @HAVE_SYS_LOADAVG_H@
+HAVE_SYS_PARAM_H = @HAVE_SYS_PARAM_H@
+HAVE_SYS_TIME_H = @HAVE_SYS_TIME_H@
+HAVE_SYS_TYPES_H = @HAVE_SYS_TYPES_H@
+HAVE_TIMEGM = @HAVE_TIMEGM@
+HAVE_TIMEZONE_T = @HAVE_TIMEZONE_T@
+HAVE_TYPE_VOLATILE_SIG_ATOMIC_T = @HAVE_TYPE_VOLATILE_SIG_ATOMIC_T@
+HAVE_UNISTD_H = @HAVE_UNISTD_H@
+HAVE_UNLINKAT = @HAVE_UNLINKAT@
+HAVE_UNLOCKPT = @HAVE_UNLOCKPT@
+HAVE_UNSIGNED_LONG_LONG_INT = @HAVE_UNSIGNED_LONG_LONG_INT@
+HAVE_USLEEP = @HAVE_USLEEP@
+HAVE_UTIMENSAT = @HAVE_UTIMENSAT@
+HAVE_VASPRINTF = @HAVE_VASPRINTF@
+HAVE_VDPRINTF = @HAVE_VDPRINTF@
+HAVE_WCHAR_H = @HAVE_WCHAR_H@
+HAVE_WCHAR_T = @HAVE_WCHAR_T@
+HAVE_WCPCPY = @HAVE_WCPCPY@
+HAVE_WCPNCPY = @HAVE_WCPNCPY@
+HAVE_WCRTOMB = @HAVE_WCRTOMB@
+HAVE_WCSCASECMP = @HAVE_WCSCASECMP@
+HAVE_WCSCAT = @HAVE_WCSCAT@
+HAVE_WCSCHR = @HAVE_WCSCHR@
+HAVE_WCSCMP = @HAVE_WCSCMP@
+HAVE_WCSCOLL = @HAVE_WCSCOLL@
+HAVE_WCSCPY = @HAVE_WCSCPY@
+HAVE_WCSCSPN = @HAVE_WCSCSPN@
+HAVE_WCSDUP = @HAVE_WCSDUP@
+HAVE_WCSLEN = @HAVE_WCSLEN@
+HAVE_WCSNCASECMP = @HAVE_WCSNCASECMP@
+HAVE_WCSNCAT = @HAVE_WCSNCAT@
+HAVE_WCSNCMP = @HAVE_WCSNCMP@
+HAVE_WCSNCPY = @HAVE_WCSNCPY@
+HAVE_WCSNLEN = @HAVE_WCSNLEN@
+HAVE_WCSNRTOMBS = @HAVE_WCSNRTOMBS@
+HAVE_WCSPBRK = @HAVE_WCSPBRK@
+HAVE_WCSRCHR = @HAVE_WCSRCHR@
+HAVE_WCSRTOMBS = @HAVE_WCSRTOMBS@
+HAVE_WCSSPN = @HAVE_WCSSPN@
+HAVE_WCSSTR = @HAVE_WCSSTR@
+HAVE_WCSTOK = @HAVE_WCSTOK@
+HAVE_WCSWIDTH = @HAVE_WCSWIDTH@
+HAVE_WCSXFRM = @HAVE_WCSXFRM@
+HAVE_WCTRANS_T = @HAVE_WCTRANS_T@
+HAVE_WCTYPE_H = @HAVE_WCTYPE_H@
+HAVE_WCTYPE_T = @HAVE_WCTYPE_T@
+HAVE_WINSOCK2_H = @HAVE_WINSOCK2_H@
+HAVE_WINT_T = @HAVE_WINT_T@
+HAVE_WMEMCHR = @HAVE_WMEMCHR@
+HAVE_WMEMCMP = @HAVE_WMEMCMP@
+HAVE_WMEMCPY = @HAVE_WMEMCPY@
+HAVE_WMEMMOVE = @HAVE_WMEMMOVE@
+HAVE_WMEMSET = @HAVE_WMEMSET@
+HAVE_XLOCALE_H = @HAVE_XLOCALE_H@
+HAVE__BOOL = @HAVE__BOOL@
+HAVE__EXIT = @HAVE__EXIT@
+INCLUDE_NEXT = @INCLUDE_NEXT@
+INCLUDE_NEXT_AS_FIRST_DIRECTIVE = @INCLUDE_NEXT_AS_FIRST_DIRECTIVE@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INT32_MAX_LT_INTMAX_MAX = @INT32_MAX_LT_INTMAX_MAX@
+INT64_MAX_EQ_LONG_MAX = @INT64_MAX_EQ_LONG_MAX@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBGNU_LIBDEPS = @LIBGNU_LIBDEPS@
+LIBGNU_LTLIBDEPS = @LIBGNU_LTLIBDEPS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBUNISTRING_UNITYPES_H = @LIBUNISTRING_UNITYPES_H@
+LIBUNISTRING_UNIWIDTH_H = @LIBUNISTRING_UNIWIDTH_H@
+LIB_ACL = @LIB_ACL@
+LIB_CLOCK_GETTIME = @LIB_CLOCK_GETTIME@
+LIB_EACCESS = @LIB_EACCESS@
+LIB_HAS_ACL = @LIB_HAS_ACL@
+LIB_SELINUX = @LIB_SELINUX@
+LIB_SETSOCKOPT = @LIB_SETSOCKOPT@
+LOCALCHARSET_TESTS_ENVIRONMENT = @LOCALCHARSET_TESTS_ENVIRONMENT@
+LOCALE_FR = @LOCALE_FR@
+LOCALE_FR_UTF8 = @LOCALE_FR_UTF8@
+LOCALE_JA = @LOCALE_JA@
+LOCALE_ZH_CN = @LOCALE_ZH_CN@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NEXT_AS_FIRST_DIRECTIVE_DIRENT_H = @NEXT_AS_FIRST_DIRECTIVE_DIRENT_H@
+NEXT_AS_FIRST_DIRECTIVE_ERRNO_H = @NEXT_AS_FIRST_DIRECTIVE_ERRNO_H@
+NEXT_AS_FIRST_DIRECTIVE_FCNTL_H = @NEXT_AS_FIRST_DIRECTIVE_FCNTL_H@
+NEXT_AS_FIRST_DIRECTIVE_FLOAT_H = @NEXT_AS_FIRST_DIRECTIVE_FLOAT_H@
+NEXT_AS_FIRST_DIRECTIVE_GETOPT_H = @NEXT_AS_FIRST_DIRECTIVE_GETOPT_H@
+NEXT_AS_FIRST_DIRECTIVE_INTTYPES_H = @NEXT_AS_FIRST_DIRECTIVE_INTTYPES_H@
+NEXT_AS_FIRST_DIRECTIVE_LANGINFO_H = @NEXT_AS_FIRST_DIRECTIVE_LANGINFO_H@
+NEXT_AS_FIRST_DIRECTIVE_LOCALE_H = @NEXT_AS_FIRST_DIRECTIVE_LOCALE_H@
+NEXT_AS_FIRST_DIRECTIVE_SELINUX_SELINUX_H = @NEXT_AS_FIRST_DIRECTIVE_SELINUX_SELINUX_H@
+NEXT_AS_FIRST_DIRECTIVE_SIGNAL_H = @NEXT_AS_FIRST_DIRECTIVE_SIGNAL_H@
+NEXT_AS_FIRST_DIRECTIVE_STDARG_H = @NEXT_AS_FIRST_DIRECTIVE_STDARG_H@
+NEXT_AS_FIRST_DIRECTIVE_STDDEF_H = @NEXT_AS_FIRST_DIRECTIVE_STDDEF_H@
+NEXT_AS_FIRST_DIRECTIVE_STDINT_H = @NEXT_AS_FIRST_DIRECTIVE_STDINT_H@
+NEXT_AS_FIRST_DIRECTIVE_STDIO_H = @NEXT_AS_FIRST_DIRECTIVE_STDIO_H@
+NEXT_AS_FIRST_DIRECTIVE_STDLIB_H = @NEXT_AS_FIRST_DIRECTIVE_STDLIB_H@
+NEXT_AS_FIRST_DIRECTIVE_STRINGS_H = @NEXT_AS_FIRST_DIRECTIVE_STRINGS_H@
+NEXT_AS_FIRST_DIRECTIVE_STRING_H = @NEXT_AS_FIRST_DIRECTIVE_STRING_H@
+NEXT_AS_FIRST_DIRECTIVE_SYSEXITS_H = @NEXT_AS_FIRST_DIRECTIVE_SYSEXITS_H@
+NEXT_AS_FIRST_DIRECTIVE_SYS_STAT_H = @NEXT_AS_FIRST_DIRECTIVE_SYS_STAT_H@
+NEXT_AS_FIRST_DIRECTIVE_SYS_TIME_H = @NEXT_AS_FIRST_DIRECTIVE_SYS_TIME_H@
+NEXT_AS_FIRST_DIRECTIVE_SYS_TYPES_H = @NEXT_AS_FIRST_DIRECTIVE_SYS_TYPES_H@
+NEXT_AS_FIRST_DIRECTIVE_TIME_H = @NEXT_AS_FIRST_DIRECTIVE_TIME_H@
+NEXT_AS_FIRST_DIRECTIVE_UNISTD_H = @NEXT_AS_FIRST_DIRECTIVE_UNISTD_H@
+NEXT_AS_FIRST_DIRECTIVE_WCHAR_H = @NEXT_AS_FIRST_DIRECTIVE_WCHAR_H@
+NEXT_AS_FIRST_DIRECTIVE_WCTYPE_H = @NEXT_AS_FIRST_DIRECTIVE_WCTYPE_H@
+NEXT_DIRENT_H = @NEXT_DIRENT_H@
+NEXT_ERRNO_H = @NEXT_ERRNO_H@
+NEXT_FCNTL_H = @NEXT_FCNTL_H@
+NEXT_FLOAT_H = @NEXT_FLOAT_H@
+NEXT_GETOPT_H = @NEXT_GETOPT_H@
+NEXT_INTTYPES_H = @NEXT_INTTYPES_H@
+NEXT_LANGINFO_H = @NEXT_LANGINFO_H@
+NEXT_LOCALE_H = @NEXT_LOCALE_H@
+NEXT_SELINUX_SELINUX_H = @NEXT_SELINUX_SELINUX_H@
+NEXT_SIGNAL_H = @NEXT_SIGNAL_H@
+NEXT_STDARG_H = @NEXT_STDARG_H@
+NEXT_STDDEF_H = @NEXT_STDDEF_H@
+NEXT_STDINT_H = @NEXT_STDINT_H@
+NEXT_STDIO_H = @NEXT_STDIO_H@
+NEXT_STDLIB_H = @NEXT_STDLIB_H@
+NEXT_STRINGS_H = @NEXT_STRINGS_H@
+NEXT_STRING_H = @NEXT_STRING_H@
+NEXT_SYSEXITS_H = @NEXT_SYSEXITS_H@
+NEXT_SYS_STAT_H = @NEXT_SYS_STAT_H@
+NEXT_SYS_TIME_H = @NEXT_SYS_TIME_H@
+NEXT_SYS_TYPES_H = @NEXT_SYS_TYPES_H@
+NEXT_TIME_H = @NEXT_TIME_H@
+NEXT_UNISTD_H = @NEXT_UNISTD_H@
+NEXT_WCHAR_H = @NEXT_WCHAR_H@
+NEXT_WCTYPE_H = @NEXT_WCTYPE_H@
+OBJEXT = @OBJEXT@
+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@
+POSUB = @POSUB@
+PRAGMA_COLUMNS = @PRAGMA_COLUMNS@
+PRAGMA_SYSTEM_HEADER = @PRAGMA_SYSTEM_HEADER@
+PRIPTR_PREFIX = @PRIPTR_PREFIX@
+PRI_MACROS_BROKEN = @PRI_MACROS_BROKEN@
+PTHREAD_H_DEFINES_STRUCT_TIMESPEC = @PTHREAD_H_DEFINES_STRUCT_TIMESPEC@
+PTRDIFF_T_SUFFIX = @PTRDIFF_T_SUFFIX@
+PU_RMT_PROG = @PU_RMT_PROG@
+RANLIB = @RANLIB@
+REPLACE_BTOWC = @REPLACE_BTOWC@
+REPLACE_CALLOC = @REPLACE_CALLOC@
+REPLACE_CANONICALIZE_FILE_NAME = @REPLACE_CANONICALIZE_FILE_NAME@
+REPLACE_CHOWN = @REPLACE_CHOWN@
+REPLACE_CLOSE = @REPLACE_CLOSE@
+REPLACE_CLOSEDIR = @REPLACE_CLOSEDIR@
+REPLACE_DIRFD = @REPLACE_DIRFD@
+REPLACE_DPRINTF = @REPLACE_DPRINTF@
+REPLACE_DUP = @REPLACE_DUP@
+REPLACE_DUP2 = @REPLACE_DUP2@
+REPLACE_DUPLOCALE = @REPLACE_DUPLOCALE@
+REPLACE_FCHOWNAT = @REPLACE_FCHOWNAT@
+REPLACE_FCLOSE = @REPLACE_FCLOSE@
+REPLACE_FCNTL = @REPLACE_FCNTL@
+REPLACE_FDOPEN = @REPLACE_FDOPEN@
+REPLACE_FDOPENDIR = @REPLACE_FDOPENDIR@
+REPLACE_FFLUSH = @REPLACE_FFLUSH@
+REPLACE_FOPEN = @REPLACE_FOPEN@
+REPLACE_FPRINTF = @REPLACE_FPRINTF@
+REPLACE_FPURGE = @REPLACE_FPURGE@
+REPLACE_FREOPEN = @REPLACE_FREOPEN@
+REPLACE_FSEEK = @REPLACE_FSEEK@
+REPLACE_FSEEKO = @REPLACE_FSEEKO@
+REPLACE_FSTAT = @REPLACE_FSTAT@
+REPLACE_FSTATAT = @REPLACE_FSTATAT@
+REPLACE_FTELL = @REPLACE_FTELL@
+REPLACE_FTELLO = @REPLACE_FTELLO@
+REPLACE_FTRUNCATE = @REPLACE_FTRUNCATE@
+REPLACE_FUTIMENS = @REPLACE_FUTIMENS@
+REPLACE_GETCWD = @REPLACE_GETCWD@
+REPLACE_GETDELIM = @REPLACE_GETDELIM@
+REPLACE_GETDOMAINNAME = @REPLACE_GETDOMAINNAME@
+REPLACE_GETDTABLESIZE = @REPLACE_GETDTABLESIZE@
+REPLACE_GETGROUPS = @REPLACE_GETGROUPS@
+REPLACE_GETLINE = @REPLACE_GETLINE@
+REPLACE_GETLOGIN_R = @REPLACE_GETLOGIN_R@
+REPLACE_GETPAGESIZE = @REPLACE_GETPAGESIZE@
+REPLACE_GETTIMEOFDAY = @REPLACE_GETTIMEOFDAY@
+REPLACE_GMTIME = @REPLACE_GMTIME@
+REPLACE_ISATTY = @REPLACE_ISATTY@
+REPLACE_ISWBLANK = @REPLACE_ISWBLANK@
+REPLACE_ISWCNTRL = @REPLACE_ISWCNTRL@
+REPLACE_ITOLD = @REPLACE_ITOLD@
+REPLACE_LCHOWN = @REPLACE_LCHOWN@
+REPLACE_LINK = @REPLACE_LINK@
+REPLACE_LINKAT = @REPLACE_LINKAT@
+REPLACE_LOCALECONV = @REPLACE_LOCALECONV@
+REPLACE_LOCALTIME = @REPLACE_LOCALTIME@
+REPLACE_LOCALTIME_R = @REPLACE_LOCALTIME_R@
+REPLACE_LSEEK = @REPLACE_LSEEK@
+REPLACE_LSTAT = @REPLACE_LSTAT@
+REPLACE_MALLOC = @REPLACE_MALLOC@
+REPLACE_MBRLEN = @REPLACE_MBRLEN@
+REPLACE_MBRTOWC = @REPLACE_MBRTOWC@
+REPLACE_MBSINIT = @REPLACE_MBSINIT@
+REPLACE_MBSNRTOWCS = @REPLACE_MBSNRTOWCS@
+REPLACE_MBSRTOWCS = @REPLACE_MBSRTOWCS@
+REPLACE_MBSTATE_T = @REPLACE_MBSTATE_T@
+REPLACE_MBTOWC = @REPLACE_MBTOWC@
+REPLACE_MEMCHR = @REPLACE_MEMCHR@
+REPLACE_MEMMEM = @REPLACE_MEMMEM@
+REPLACE_MKDIR = @REPLACE_MKDIR@
+REPLACE_MKFIFO = @REPLACE_MKFIFO@
+REPLACE_MKNOD = @REPLACE_MKNOD@
+REPLACE_MKSTEMP = @REPLACE_MKSTEMP@
+REPLACE_MKTIME = @REPLACE_MKTIME@
+REPLACE_NANOSLEEP = @REPLACE_NANOSLEEP@
+REPLACE_NL_LANGINFO = @REPLACE_NL_LANGINFO@
+REPLACE_NULL = @REPLACE_NULL@
+REPLACE_OBSTACK_PRINTF = @REPLACE_OBSTACK_PRINTF@
+REPLACE_OPEN = @REPLACE_OPEN@
+REPLACE_OPENAT = @REPLACE_OPENAT@
+REPLACE_OPENDIR = @REPLACE_OPENDIR@
+REPLACE_PERROR = @REPLACE_PERROR@
+REPLACE_POPEN = @REPLACE_POPEN@
+REPLACE_PREAD = @REPLACE_PREAD@
+REPLACE_PRINTF = @REPLACE_PRINTF@
+REPLACE_PTHREAD_SIGMASK = @REPLACE_PTHREAD_SIGMASK@
+REPLACE_PTSNAME = @REPLACE_PTSNAME@
+REPLACE_PTSNAME_R = @REPLACE_PTSNAME_R@
+REPLACE_PUTENV = @REPLACE_PUTENV@
+REPLACE_PWRITE = @REPLACE_PWRITE@
+REPLACE_QSORT_R = @REPLACE_QSORT_R@
+REPLACE_RAISE = @REPLACE_RAISE@
+REPLACE_RANDOM_R = @REPLACE_RANDOM_R@
+REPLACE_READ = @REPLACE_READ@
+REPLACE_READLINK = @REPLACE_READLINK@
+REPLACE_READLINKAT = @REPLACE_READLINKAT@
+REPLACE_REALLOC = @REPLACE_REALLOC@
+REPLACE_REALPATH = @REPLACE_REALPATH@
+REPLACE_REMOVE = @REPLACE_REMOVE@
+REPLACE_RENAME = @REPLACE_RENAME@
+REPLACE_RENAMEAT = @REPLACE_RENAMEAT@
+REPLACE_RMDIR = @REPLACE_RMDIR@
+REPLACE_SETENV = @REPLACE_SETENV@
+REPLACE_SETLOCALE = @REPLACE_SETLOCALE@
+REPLACE_SLEEP = @REPLACE_SLEEP@
+REPLACE_SNPRINTF = @REPLACE_SNPRINTF@
+REPLACE_SPRINTF = @REPLACE_SPRINTF@
+REPLACE_STAT = @REPLACE_STAT@
+REPLACE_STDIO_READ_FUNCS = @REPLACE_STDIO_READ_FUNCS@
+REPLACE_STDIO_WRITE_FUNCS = @REPLACE_STDIO_WRITE_FUNCS@
+REPLACE_STPNCPY = @REPLACE_STPNCPY@
+REPLACE_STRCASESTR = @REPLACE_STRCASESTR@
+REPLACE_STRCHRNUL = @REPLACE_STRCHRNUL@
+REPLACE_STRDUP = @REPLACE_STRDUP@
+REPLACE_STRERROR = @REPLACE_STRERROR@
+REPLACE_STRERROR_R = @REPLACE_STRERROR_R@
+REPLACE_STRNCAT = @REPLACE_STRNCAT@
+REPLACE_STRNDUP = @REPLACE_STRNDUP@
+REPLACE_STRNLEN = @REPLACE_STRNLEN@
+REPLACE_STRSIGNAL = @REPLACE_STRSIGNAL@
+REPLACE_STRSTR = @REPLACE_STRSTR@
+REPLACE_STRTOD = @REPLACE_STRTOD@
+REPLACE_STRTOIMAX = @REPLACE_STRTOIMAX@
+REPLACE_STRTOK_R = @REPLACE_STRTOK_R@
+REPLACE_STRTOUMAX = @REPLACE_STRTOUMAX@
+REPLACE_STRUCT_LCONV = @REPLACE_STRUCT_LCONV@
+REPLACE_STRUCT_TIMEVAL = @REPLACE_STRUCT_TIMEVAL@
+REPLACE_SYMLINK = @REPLACE_SYMLINK@
+REPLACE_SYMLINKAT = @REPLACE_SYMLINKAT@
+REPLACE_TIMEGM = @REPLACE_TIMEGM@
+REPLACE_TMPFILE = @REPLACE_TMPFILE@
+REPLACE_TOWLOWER = @REPLACE_TOWLOWER@
+REPLACE_TTYNAME_R = @REPLACE_TTYNAME_R@
+REPLACE_UNLINK = @REPLACE_UNLINK@
+REPLACE_UNLINKAT = @REPLACE_UNLINKAT@
+REPLACE_UNSETENV = @REPLACE_UNSETENV@
+REPLACE_USLEEP = @REPLACE_USLEEP@
+REPLACE_UTIMENSAT = @REPLACE_UTIMENSAT@
+REPLACE_VASPRINTF = @REPLACE_VASPRINTF@
+REPLACE_VDPRINTF = @REPLACE_VDPRINTF@
+REPLACE_VFPRINTF = @REPLACE_VFPRINTF@
+REPLACE_VPRINTF = @REPLACE_VPRINTF@
+REPLACE_VSNPRINTF = @REPLACE_VSNPRINTF@
+REPLACE_VSPRINTF = @REPLACE_VSPRINTF@
+REPLACE_WCRTOMB = @REPLACE_WCRTOMB@
+REPLACE_WCSNRTOMBS = @REPLACE_WCSNRTOMBS@
+REPLACE_WCSRTOMBS = @REPLACE_WCSRTOMBS@
+REPLACE_WCSWIDTH = @REPLACE_WCSWIDTH@
+REPLACE_WCTOB = @REPLACE_WCTOB@
+REPLACE_WCTOMB = @REPLACE_WCTOMB@
+REPLACE_WCWIDTH = @REPLACE_WCWIDTH@
+REPLACE_WRITE = @REPLACE_WRITE@
+RSH = @RSH@
+SED = @SED@
+SELINUX_CONTEXT_H = @SELINUX_CONTEXT_H@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SIG_ATOMIC_T_SUFFIX = @SIG_ATOMIC_T_SUFFIX@
+SIZE_T_SUFFIX = @SIZE_T_SUFFIX@
+STDALIGN_H = @STDALIGN_H@
+STDARG_H = @STDARG_H@
+STDBOOL_H = @STDBOOL_H@
+STDDEF_H = @STDDEF_H@
+STDINT_H = @STDINT_H@
+STRIP = @STRIP@
+SYSEXITS_H = @SYSEXITS_H@
+SYS_TIME_H_DEFINES_STRUCT_TIMESPEC = @SYS_TIME_H_DEFINES_STRUCT_TIMESPEC@
+TIME_H_DEFINES_STRUCT_TIMESPEC = @TIME_H_DEFINES_STRUCT_TIMESPEC@
+UINT32_MAX_LT_UINTMAX_MAX = @UINT32_MAX_LT_UINTMAX_MAX@
+UINT64_MAX_EQ_ULONG_MAX = @UINT64_MAX_EQ_ULONG_MAX@
+UNDEFINE_STRTOK_R = @UNDEFINE_STRTOK_R@
+UNISTD_H_DEFINES_STRUCT_TIMESPEC = @UNISTD_H_DEFINES_STRUCT_TIMESPEC@
+UNISTD_H_HAVE_WINSOCK2_H = @UNISTD_H_HAVE_WINSOCK2_H@
+UNISTD_H_HAVE_WINSOCK2_H_AND_USE_SOCKETS = @UNISTD_H_HAVE_WINSOCK2_H_AND_USE_SOCKETS@
+USE_ACL = @USE_ACL@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+WARN_CFLAGS = @WARN_CFLAGS@
+WCHAR_T_SUFFIX = @WCHAR_T_SUFFIX@
+WERROR_CFLAGS = @WERROR_CFLAGS@
+WINDOWS_64_BIT_OFF_T = @WINDOWS_64_BIT_OFF_T@
+WINDOWS_64_BIT_ST_SIZE = @WINDOWS_64_BIT_ST_SIZE@
+WINT_T_SUFFIX = @WINT_T_SUFFIX@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+YACC = @YACC@
+YFLAGS = @YFLAGS@
+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@
+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@
+gl_LIBOBJS = @gl_LIBOBJS@
+gl_LTLIBOBJS = @gl_LTLIBOBJS@
+gltests_LIBOBJS = @gltests_LIBOBJS@
+gltests_LTLIBOBJS = @gltests_LTLIBOBJS@
+gltests_WITNESS = @gltests_WITNESS@
+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@
+lispdir = @lispdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_HEADERS = arith.h common.h tar.h xattrs.h
+tar_SOURCES = \
+ buffer.c\
+ checkpoint.c\
+ compare.c\
+ create.c\
+ delete.c\
+ exit.c\
+ exclist.c\
+ extract.c\
+ xheader.c\
+ incremen.c\
+ list.c\
+ map.c\
+ misc.c\
+ names.c\
+ sparse.c\
+ suffix.c\
+ system.c\
+ tar.c\
+ transform.c\
+ unlink.c\
+ update.c\
+ utf8.c\
+ warning.c\
+ xattrs.c
+
+AM_CPPFLAGS = -I$(top_srcdir)/gnu -I../ -I../gnu -I$(top_srcdir)/lib -I../lib
+AM_CFLAGS = $(WARN_CFLAGS) $(WERROR_CFLAGS)
+LDADD = ../lib/libtar.a ../gnu/libgnu.a $(LIBINTL) $(LIBICONV)
+tar_LDADD = $(LIBS) $(LDADD) $(LIB_CLOCK_GETTIME) $(LIB_EACCESS) $(LIB_SELINUX)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in: $(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) --gnits src/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnits src/Makefile
+.PRECIOUS: 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__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
+
+installcheck-binPROGRAMS: $(bin_PROGRAMS)
+ bad=0; pid=$$$$; list="$(bin_PROGRAMS)"; for p in $$list; do \
+ case ' $(AM_INSTALLCHECK_STD_OPTIONS_EXEMPT) ' in \
+ *" $$p "* | *" $(srcdir)/$$p "*) continue;; \
+ esac; \
+ f=`echo "$$p" | \
+ sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \
+ for opt in --help --version; do \
+ if "$(DESTDIR)$(bindir)/$$f" $$opt >c$${pid}_.out \
+ 2>c$${pid}_.err </dev/null \
+ && test -n "`cat c$${pid}_.out`" \
+ && test -z "`cat c$${pid}_.err`"; then :; \
+ else echo "$$f does not support $$opt" 1>&2; bad=1; fi; \
+ done; \
+ done; rm -f c$${pid}_.???; exit $$bad
+
+tar$(EXEEXT): $(tar_OBJECTS) $(tar_DEPENDENCIES) $(EXTRA_tar_DEPENDENCIES)
+ @rm -f tar$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(tar_OBJECTS) $(tar_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/buffer.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/checkpoint.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/compare.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/create.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/delete.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/exclist.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/exit.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/extract.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/incremen.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/list.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/map.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/misc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/names.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sparse.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/suffix.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/system.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tar.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/transform.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unlink.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/update.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utf8.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/warning.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xattrs.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xheader.Po@am__quote@
+
+.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) '$<'`
+
+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: $(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 $(PROGRAMS) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(bindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -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-binPROGRAMS
+
+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: installcheck-binPROGRAMS
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean \
+ clean-binPROGRAMS clean-generic cscopelist-am ctags ctags-am \
+ distclean distclean-compile distclean-generic distclean-tags \
+ distdir dvi dvi-am html html-am info info-am install \
+ install-am install-binPROGRAMS 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 \
+ installcheck-binPROGRAMS installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-binPROGRAMS
+
+
+# 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/arith.h b/src/arith.h
new file mode 100644
index 0000000..ed930ac
--- /dev/null
+++ b/src/arith.h
@@ -0,0 +1,28 @@
+/* Long integers, for GNU tar.
+ Copyright 1999, 2007, 2013-2014, 2016 Free Software Foundation, Inc.
+
+ This file is part of GNU tar.
+
+ GNU tar 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.
+
+ GNU tar 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/>. */
+
+/* Handle large integers for calculating big tape lengths and the
+ like. In practice, double precision does for now. On the vast
+ majority of machines, it counts up to 2**52 bytes without any loss
+ of information, and counts up to 2**62 bytes if data are always
+ blocked in 1 kB boundaries. We'll need arbitrary precision
+ arithmetic anyway once we get into the 2**64 range, so there's no
+ point doing anything fancy before then. */
+
+#define TARLONG_FORMAT "%.0f"
+typedef double tarlong;
diff --git a/src/buffer.c b/src/buffer.c
new file mode 100644
index 0000000..dcbfd02
--- /dev/null
+++ b/src/buffer.c
@@ -0,0 +1,2048 @@
+/* Buffer management for tar.
+
+ Copyright 1988, 1992-1994, 1996-1997, 1999-2010, 2013-2014, 2016 Free
+ Software Foundation, Inc.
+
+ This file is part of GNU tar.
+
+ GNU tar 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.
+
+ GNU tar 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/>.
+
+ Written by John Gilmore, on 1985-08-25. */
+
+#include <system.h>
+#include <system-ioctl.h>
+
+#include <signal.h>
+
+#include <closeout.h>
+#include <fnmatch.h>
+#include <human.h>
+#include <quotearg.h>
+
+#include "common.h"
+#include <rmt.h>
+
+/* Number of retries before giving up on read. */
+#define READ_ERROR_MAX 10
+
+/* Variables. */
+
+static tarlong prev_written; /* bytes written on previous volumes */
+static tarlong bytes_written; /* bytes written on this volume */
+static void *record_buffer[2]; /* allocated memory */
+static union block *record_buffer_aligned[2];
+static int record_index;
+
+/* FIXME: The following variables should ideally be static to this
+ module. However, this cannot be done yet. The cleanup continues! */
+
+union block *record_start; /* start of record of archive */
+union block *record_end; /* last+1 block of archive record */
+union block *current_block; /* current block of archive */
+enum access_mode access_mode; /* how do we handle the archive */
+off_t records_read; /* number of records read from this archive */
+off_t records_written; /* likewise, for records written */
+extern off_t records_skipped; /* number of records skipped at the start
+ of the archive, defined in delete.c */
+
+static off_t record_start_block; /* block ordinal at record_start */
+
+/* Where we write list messages (not errors, not interactions) to. */
+FILE *stdlis;
+
+static void backspace_output (void);
+
+/* PID of child program, if compress_option or remote archive access. */
+static pid_t child_pid;
+
+/* Error recovery stuff */
+static int read_error_count;
+
+/* Have we hit EOF yet? */
+static bool hit_eof;
+
+static bool read_full_records = false;
+
+/* We're reading, but we just read the last block and it's time to update.
+ Declared in update.c
+
+ FIXME: Either eliminate it or move it to common.h.
+*/
+extern bool time_to_start_writing;
+
+bool write_archive_to_stdout;
+
+static void (*flush_write_ptr) (size_t);
+static void (*flush_read_ptr) (void);
+
+
+char *volume_label;
+char *continued_file_name;
+uintmax_t continued_file_size;
+uintmax_t continued_file_offset;
+
+
+static int volno = 1; /* which volume of a multi-volume tape we're
+ on */
+static int global_volno = 1; /* volume number to print in external
+ messages */
+
+bool write_archive_to_stdout;
+
+
+/* Multi-volume tracking support */
+
+/* When creating a multi-volume archive, each 'bufmap' represents
+ a member stored (perhaps partly) in the current record buffer.
+ After flushing the record to the output media, all bufmaps that
+ represent fully written members are removed from the list, then
+ the sizeleft and start numbers in the remaining bufmaps are updated.
+
+ When reading from a multi-volume archive, the list degrades to a
+ single element, which keeps information about the member currently
+ being read.
+*/
+
+struct bufmap
+{
+ struct bufmap *next; /* Pointer to the next map entry */
+ size_t start; /* Offset of the first data block */
+ char *file_name; /* Name of the stored file */
+ off_t sizetotal; /* Size of the stored file */
+ off_t sizeleft; /* Size left to read/write */
+};
+static struct bufmap *bufmap_head, *bufmap_tail;
+
+/* This variable, when set, inhibits updating the bufmap chain after
+ a write. This is necessary when writing extended POSIX headers. */
+static int inhibit_map;
+
+void
+mv_begin_write (const char *file_name, off_t totsize, off_t sizeleft)
+{
+ if (multi_volume_option)
+ {
+ struct bufmap *bp = xmalloc (sizeof bp[0]);
+ if (bufmap_tail)
+ bufmap_tail->next = bp;
+ else
+ bufmap_head = bp;
+ bufmap_tail = bp;
+
+ bp->next = NULL;
+ bp->start = current_block - record_start;
+ bp->file_name = xstrdup (file_name);
+ bp->sizetotal = totsize;
+ bp->sizeleft = sizeleft;
+ }
+}
+
+static struct bufmap *
+bufmap_locate (size_t off)
+{
+ struct bufmap *map;
+
+ for (map = bufmap_head; map; map = map->next)
+ {
+ if (!map->next
+ || off < map->next->start * BLOCKSIZE)
+ break;
+ }
+ return map;
+}
+
+static void
+bufmap_free (struct bufmap *mark)
+{
+ struct bufmap *map;
+ for (map = bufmap_head; map && map != mark; )
+ {
+ struct bufmap *next = map->next;
+ free (map->file_name);
+ free (map);
+ map = next;
+ }
+ bufmap_head = map;
+ if (!bufmap_head)
+ bufmap_tail = bufmap_head;
+}
+
+static void
+bufmap_reset (struct bufmap *map, ssize_t fixup)
+{
+ bufmap_free (map);
+ if (map)
+ {
+ for (; map; map = map->next)
+ map->start += fixup;
+ }
+}
+
+
+static struct tar_stat_info dummy;
+
+void
+buffer_write_global_xheader (void)
+{
+ xheader_write_global (&dummy.xhdr);
+}
+
+void
+mv_begin_read (struct tar_stat_info *st)
+{
+ mv_begin_write (st->orig_file_name, st->stat.st_size, st->stat.st_size);
+}
+
+void
+mv_end (void)
+{
+ if (multi_volume_option)
+ bufmap_free (NULL);
+}
+
+void
+mv_size_left (off_t size)
+{
+ if (bufmap_head)
+ bufmap_head->sizeleft = size;
+}
+
+
+/* Functions. */
+
+void
+clear_read_error_count (void)
+{
+ read_error_count = 0;
+}
+
+
+/* Time-related functions */
+
+static double duration;
+
+void
+set_start_time (void)
+{
+ gettime (&start_time);
+ volume_start_time = start_time;
+ last_stat_time = start_time;
+}
+
+static void
+set_volume_start_time (void)
+{
+ gettime (&volume_start_time);
+ last_stat_time = volume_start_time;
+}
+
+double
+compute_duration (void)
+{
+ struct timespec now;
+ gettime (&now);
+ duration += ((now.tv_sec - last_stat_time.tv_sec)
+ + (now.tv_nsec - last_stat_time.tv_nsec) / 1e9);
+ gettime (&last_stat_time);
+ return duration;
+}
+
+
+/* Compression detection */
+
+enum compress_type {
+ ct_none, /* Unknown compression type */
+ ct_tar, /* Plain tar file */
+ ct_compress,
+ ct_gzip,
+ ct_bzip2,
+ ct_lzip,
+ ct_lzma,
+ ct_lzop,
+ ct_xz
+};
+
+static enum compress_type archive_compression_type = ct_none;
+
+struct zip_magic
+{
+ enum compress_type type;
+ size_t length;
+ char const *magic;
+};
+
+struct zip_program
+{
+ enum compress_type type;
+ char const *program;
+ char const *option;
+};
+
+static struct zip_magic const magic[] = {
+ { ct_none, 0, 0 },
+ { ct_tar, 0, 0 },
+ { ct_compress, 2, "\037\235" },
+ { ct_gzip, 2, "\037\213" },
+ { ct_bzip2, 3, "BZh" },
+ { ct_lzip, 4, "LZIP" },
+ { ct_lzma, 6, "\xFFLZMA" },
+ { ct_lzop, 4, "\211LZO" },
+ { ct_xz, 6, "\xFD" "7zXZ" },
+};
+
+#define NMAGIC (sizeof(magic)/sizeof(magic[0]))
+
+static struct zip_program zip_program[] = {
+ { ct_compress, COMPRESS_PROGRAM, "-Z" },
+ { ct_compress, GZIP_PROGRAM, "-z" },
+ { ct_gzip, GZIP_PROGRAM, "-z" },
+ { ct_bzip2, BZIP2_PROGRAM, "-j" },
+ { ct_bzip2, "lbzip2", "-j" },
+ { ct_lzip, LZIP_PROGRAM, "--lzip" },
+ { ct_lzma, LZMA_PROGRAM, "--lzma" },
+ { ct_lzma, XZ_PROGRAM, "-J" },
+ { ct_lzop, LZOP_PROGRAM, "--lzop" },
+ { ct_xz, XZ_PROGRAM, "-J" },
+ { ct_none }
+};
+
+static struct zip_program const *
+find_zip_program (enum compress_type type, int *pstate)
+{
+ int i;
+
+ for (i = *pstate; zip_program[i].type != ct_none; i++)
+ {
+ if (zip_program[i].type == type)
+ {
+ *pstate = i + 1;
+ return zip_program + i;
+ }
+ }
+ *pstate = i;
+ return NULL;
+}
+
+const char *
+first_decompress_program (int *pstate)
+{
+ struct zip_program const *zp;
+
+ if (use_compress_program_option)
+ return use_compress_program_option;
+
+ if (archive_compression_type == ct_none)
+ return NULL;
+
+ *pstate = 0;
+ zp = find_zip_program (archive_compression_type, pstate);
+ return zp ? zp->program : NULL;
+}
+
+const char *
+next_decompress_program (int *pstate)
+{
+ struct zip_program const *zp;
+
+ if (use_compress_program_option)
+ return NULL;
+ zp = find_zip_program (archive_compression_type, pstate);
+ return zp ? zp->program : NULL;
+}
+
+static const char *
+compress_option (enum compress_type type)
+{
+ struct zip_program const *zp;
+ int i = 0;
+ zp = find_zip_program (type, &i);
+ return zp ? zp->option : NULL;
+}
+
+/* Check if the file ARCHIVE is a compressed archive. */
+static enum compress_type
+check_compressed_archive (bool *pshort)
+{
+ struct zip_magic const *p;
+ bool sfr;
+ bool temp;
+
+ if (!pshort)
+ pshort = &temp;
+
+ /* Prepare global data needed for find_next_block: */
+ record_end = record_start; /* set up for 1st record = # 0 */
+ sfr = read_full_records;
+ read_full_records = true; /* Suppress fatal error on reading a partial
+ record */
+ *pshort = find_next_block () == 0;
+
+ /* Restore global values */
+ read_full_records = sfr;
+
+ if ((strcmp (record_start->header.magic, TMAGIC) == 0 ||
+ strcmp (record_start->buffer + offsetof (struct posix_header, magic),
+ OLDGNU_MAGIC) == 0) &&
+ tar_checksum (record_start, true) == HEADER_SUCCESS)
+ /* Probably a valid header */
+ return ct_tar;
+
+ for (p = magic + 2; p < magic + NMAGIC; p++)
+ if (memcmp (record_start->buffer, p->magic, p->length) == 0)
+ return p->type;
+
+ return ct_none;
+}
+
+/* Guess if the archive is seekable. */
+static void
+guess_seekable_archive (void)
+{
+ struct stat st;
+
+ if (subcommand_option == DELETE_SUBCOMMAND)
+ {
+ /* The current code in delete.c is based on the assumption that
+ skip_member() reads all data from the archive. So, we should
+ make sure it won't use seeks. On the other hand, the same code
+ depends on the ability to backspace a record in the archive,
+ so setting seekable_archive to false is technically incorrect.
+ However, it is tested only in skip_member(), so it's not a
+ problem. */
+ seekable_archive = false;
+ }
+
+ if (seek_option != -1)
+ {
+ seekable_archive = !!seek_option;
+ return;
+ }
+
+ if (!multi_volume_option && !use_compress_program_option
+ && fstat (archive, &st) == 0)
+ seekable_archive = S_ISREG (st.st_mode);
+ else
+ seekable_archive = false;
+}
+
+/* Open an archive named archive_name_array[0]. Detect if it is
+ a compressed archive of known type and use corresponding decompression
+ program if so */
+static int
+open_compressed_archive (void)
+{
+ archive = rmtopen (archive_name_array[0], O_RDONLY | O_BINARY,
+ MODE_RW, rsh_command_option);
+ if (archive == -1)
+ return archive;
+
+ if (!multi_volume_option)
+ {
+ if (!use_compress_program_option)
+ {
+ bool shortfile;
+ enum compress_type type = check_compressed_archive (&shortfile);
+
+ switch (type)
+ {
+ case ct_tar:
+ if (shortfile)
+ ERROR ((0, 0, _("This does not look like a tar archive")));
+ return archive;
+
+ case ct_none:
+ if (shortfile)
+ ERROR ((0, 0, _("This does not look like a tar archive")));
+ set_compression_program_by_suffix (archive_name_array[0], NULL);
+ if (!use_compress_program_option)
+ return archive;
+ break;
+
+ default:
+ archive_compression_type = type;
+ break;
+ }
+ }
+
+ /* FD is not needed any more */
+ rmtclose (archive);
+
+ hit_eof = false; /* It might have been set by find_next_block in
+ check_compressed_archive */
+
+ /* Open compressed archive */
+ child_pid = sys_child_open_for_uncompress ();
+ read_full_records = true;
+ }
+
+ records_read = 0;
+ record_end = record_start; /* set up for 1st record = # 0 */
+
+ return archive;
+}
+
+static int
+print_stats (FILE *fp, const char *text, tarlong numbytes)
+{
+ char abbr[LONGEST_HUMAN_READABLE + 1];
+ char rate[LONGEST_HUMAN_READABLE + 1];
+ int n = 0;
+
+ int human_opts = human_autoscale | human_base_1024 | human_SI | human_B;
+
+ if (text && text[0])
+ n += fprintf (fp, "%s: ", gettext (text));
+ return n + fprintf (fp, TARLONG_FORMAT " (%s, %s/s)",
+ numbytes,
+ human_readable (numbytes, abbr, human_opts, 1, 1),
+ (0 < duration && numbytes / duration < (uintmax_t) -1
+ ? human_readable (numbytes / duration, rate, human_opts, 1, 1)
+ : "?"));
+}
+
+/* Format totals to file FP. FORMATS is an array of strings to output
+ before each data item (bytes read, written, deleted, in that order).
+ EOR is a delimiter to output after each item (used only if deleting
+ from the archive), EOL is a delimiter to add at the end of the output
+ line. */
+int
+format_total_stats (FILE *fp, char const *const *formats, int eor, int eol)
+{
+ int n;
+
+ switch (subcommand_option)
+ {
+ case CREATE_SUBCOMMAND:
+ case CAT_SUBCOMMAND:
+ case UPDATE_SUBCOMMAND:
+ case APPEND_SUBCOMMAND:
+ n = print_stats (fp, formats[TF_WRITE],
+ prev_written + bytes_written);
+ break;
+
+ case DELETE_SUBCOMMAND:
+ {
+ char buf[UINTMAX_STRSIZE_BOUND];
+ n = print_stats (fp, formats[TF_READ],
+ records_read * record_size);
+
+ fputc (eor, fp);
+ n++;
+
+ n += print_stats (fp, formats[TF_WRITE],
+ prev_written + bytes_written);
+
+ fputc (eor, fp);
+ n++;
+
+ if (formats[TF_DELETED] && formats[TF_DELETED][0])
+ n += fprintf (fp, "%s: ", gettext (formats[TF_DELETED]));
+ n += fprintf (fp, "%s",
+ STRINGIFY_BIGINT ((records_read - records_skipped)
+ * record_size
+ - (prev_written + bytes_written), buf));
+ }
+ break;
+
+ case EXTRACT_SUBCOMMAND:
+ case LIST_SUBCOMMAND:
+ case DIFF_SUBCOMMAND:
+ n = print_stats (fp, _(formats[TF_READ]),
+ records_read * record_size);
+ break;
+
+ default:
+ abort ();
+ }
+ if (eol)
+ {
+ fputc (eol, fp);
+ n++;
+ }
+ return n;
+}
+
+static char const *const default_total_format[] = {
+ N_("Total bytes read"),
+ /* Amanda 2.4.1p1 looks for "Total bytes written: [0-9][0-9]*". */
+ N_("Total bytes written"),
+ N_("Total bytes deleted")
+};
+
+void
+print_total_stats (void)
+{
+ format_total_stats (stderr, default_total_format, '\n', '\n');
+}
+
+/* Compute and return the block ordinal at current_block. */
+off_t
+current_block_ordinal (void)
+{
+ return record_start_block + (current_block - record_start);
+}
+
+/* If the EOF flag is set, reset it, as well as current_block, etc. */
+void
+reset_eof (void)
+{
+ if (hit_eof)
+ {
+ hit_eof = false;
+ current_block = record_start;
+ record_end = record_start + blocking_factor;
+ access_mode = ACCESS_WRITE;
+ }
+}
+
+/* Return the location of the next available input or output block.
+ Return zero for EOF. Once we have returned zero, we just keep returning
+ it, to avoid accidentally going on to the next file on the tape. */
+union block *
+find_next_block (void)
+{
+ if (current_block == record_end)
+ {
+ if (hit_eof)
+ return 0;
+ flush_archive ();
+ if (current_block == record_end)
+ {
+ hit_eof = true;
+ return 0;
+ }
+ }
+ return current_block;
+}
+
+/* Indicate that we have used all blocks up thru BLOCK. */
+void
+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
+ set_next_block_after could mean the next block (if the input record
+ is exactly one block long), which is not what is intended. */
+
+ if (current_block > record_end)
+ abort ();
+}
+
+/* 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 of previous find_next_block call. */
+size_t
+available_space_after (union block *pointer)
+{
+ return record_end->buffer - pointer->buffer;
+}
+
+/* Close file having descriptor FD, and abort if close unsuccessful. */
+void
+xclose (int fd)
+{
+ if (close (fd) != 0)
+ close_error (_("(pipe)"));
+}
+
+static void
+init_buffer (void)
+{
+ if (! record_buffer_aligned[record_index])
+ record_buffer_aligned[record_index] =
+ page_aligned_alloc (&record_buffer[record_index], record_size);
+
+ record_start = record_buffer_aligned[record_index];
+ current_block = record_start;
+ record_end = record_start + blocking_factor;
+}
+
+static void
+check_tty (enum access_mode mode)
+{
+ /* Refuse to read archive from and write it to a tty. */
+ if (strcmp (archive_name_array[0], "-") == 0
+ && isatty (mode == ACCESS_READ ? STDIN_FILENO : STDOUT_FILENO))
+ {
+ FATAL_ERROR ((0, 0,
+ mode == ACCESS_READ
+ ? _("Refusing to read archive contents from terminal "
+ "(missing -f option?)")
+ : _("Refusing to write archive contents to terminal "
+ "(missing -f option?)")));
+ }
+}
+
+/* Open an archive file. The argument specifies whether we are
+ reading or writing, or both. */
+static void
+_open_archive (enum access_mode wanted_access)
+{
+ int backed_up_flag = 0;
+
+ if (record_size == 0)
+ FATAL_ERROR ((0, 0, _("Invalid value for record_size")));
+
+ if (archive_names == 0)
+ FATAL_ERROR ((0, 0, _("No archive name given")));
+
+ tar_stat_destroy (&current_stat_info);
+
+ record_index = 0;
+ init_buffer ();
+
+ /* When updating the archive, we start with reading. */
+ access_mode = wanted_access == ACCESS_UPDATE ? ACCESS_READ : wanted_access;
+ check_tty (access_mode);
+
+ read_full_records = read_full_records_option;
+
+ records_read = 0;
+
+ if (use_compress_program_option)
+ {
+ switch (wanted_access)
+ {
+ case ACCESS_READ:
+ child_pid = sys_child_open_for_uncompress ();
+ read_full_records = true;
+ record_end = record_start; /* set up for 1st record = # 0 */
+ break;
+
+ case ACCESS_WRITE:
+ child_pid = sys_child_open_for_compress ();
+ break;
+
+ case ACCESS_UPDATE:
+ abort (); /* Should not happen */
+ break;
+ }
+
+ if (!index_file_name
+ && wanted_access == ACCESS_WRITE
+ && strcmp (archive_name_array[0], "-") == 0)
+ stdlis = stderr;
+ }
+ else if (strcmp (archive_name_array[0], "-") == 0)
+ {
+ read_full_records = true; /* could be a pipe, be safe */
+ if (verify_option)
+ FATAL_ERROR ((0, 0, _("Cannot verify stdin/stdout archive")));
+
+ switch (wanted_access)
+ {
+ case ACCESS_READ:
+ {
+ bool shortfile;
+ enum compress_type type;
+
+ archive = STDIN_FILENO;
+ type = check_compressed_archive (&shortfile);
+ if (type != ct_tar && type != ct_none)
+ FATAL_ERROR ((0, 0,
+ _("Archive is compressed. Use %s option"),
+ compress_option (type)));
+ if (shortfile)
+ ERROR ((0, 0, _("This does not look like a tar archive")));
+ }
+ break;
+
+ case ACCESS_WRITE:
+ archive = STDOUT_FILENO;
+ if (!index_file_name)
+ stdlis = stderr;
+ break;
+
+ case ACCESS_UPDATE:
+ archive = STDIN_FILENO;
+ write_archive_to_stdout = true;
+ record_end = record_start; /* set up for 1st record = # 0 */
+ if (!index_file_name)
+ stdlis = stderr;
+ break;
+ }
+ }
+ else
+ switch (wanted_access)
+ {
+ case ACCESS_READ:
+ archive = open_compressed_archive ();
+ if (archive >= 0)
+ guess_seekable_archive ();
+ break;
+
+ case ACCESS_WRITE:
+ if (backup_option)
+ {
+ maybe_backup_file (archive_name_array[0], 1);
+ backed_up_flag = 1;
+ }
+ if (verify_option)
+ archive = rmtopen (archive_name_array[0], O_RDWR | O_CREAT | O_BINARY,
+ MODE_RW, rsh_command_option);
+ else
+ archive = rmtcreat (archive_name_array[0], MODE_RW,
+ rsh_command_option);
+ break;
+
+ case ACCESS_UPDATE:
+ archive = rmtopen (archive_name_array[0],
+ O_RDWR | O_CREAT | O_BINARY,
+ MODE_RW, rsh_command_option);
+
+ switch (check_compressed_archive (NULL))
+ {
+ case ct_none:
+ case ct_tar:
+ break;
+
+ default:
+ FATAL_ERROR ((0, 0,
+ _("Cannot update compressed archives")));
+ }
+ break;
+ }
+
+ if (archive < 0
+ || (! _isrmt (archive) && !sys_get_archive_stat ()))
+ {
+ int saved_errno = errno;
+
+ if (backed_up_flag)
+ undo_last_backup ();
+ errno = saved_errno;
+ open_fatal (archive_name_array[0]);
+ }
+
+ sys_detect_dev_null_output ();
+ sys_save_archive_dev_ino ();
+ SET_BINARY_MODE (archive);
+
+ switch (wanted_access)
+ {
+ case ACCESS_READ:
+ find_next_block (); /* read it in, check for EOF */
+ break;
+
+ case ACCESS_UPDATE:
+ case ACCESS_WRITE:
+ records_written = 0;
+ break;
+ }
+}
+
+/* Perform a write to flush the buffer. */
+static ssize_t
+_flush_write (void)
+{
+ ssize_t status;
+
+ checkpoint_run (true);
+ if (tape_length_option && tape_length_option <= bytes_written)
+ {
+ errno = ENOSPC;
+ status = 0;
+ }
+ else if (dev_null_output)
+ status = record_size;
+ else
+ status = sys_write_archive_buffer ();
+
+ if (status && multi_volume_option && !inhibit_map)
+ {
+ struct bufmap *map = bufmap_locate (status);
+ if (map)
+ {
+ size_t delta = status - map->start * BLOCKSIZE;
+ if (delta > map->sizeleft)
+ delta = map->sizeleft;
+ map->sizeleft -= delta;
+ if (map->sizeleft == 0)
+ map = map->next;
+ bufmap_reset (map, map ? (- map->start) : 0);
+ }
+ }
+ return status;
+}
+
+/* Handle write errors on the archive. Write errors are always fatal.
+ Hitting the end of a volume does not cause a write error unless the
+ write was the first record of the volume. */
+void
+archive_write_error (ssize_t status)
+{
+ /* It might be useful to know how much was written before the error
+ occurred. */
+ if (totals_option)
+ {
+ int e = errno;
+ print_total_stats ();
+ errno = e;
+ }
+
+ write_fatal_details (*archive_name_cursor, status, record_size);
+}
+
+/* Handle read errors on the archive. If the read should be retried,
+ return to the caller. */
+void
+archive_read_error (void)
+{
+ read_error (*archive_name_cursor);
+
+ if (record_start_block == 0)
+ FATAL_ERROR ((0, 0, _("At beginning of tape, quitting now")));
+
+ /* Read error in mid archive. We retry up to READ_ERROR_MAX times and
+ then give up on reading the archive. */
+
+ if (read_error_count++ > READ_ERROR_MAX)
+ FATAL_ERROR ((0, 0, _("Too many errors, quitting")));
+ return;
+}
+
+static bool
+archive_is_dev (void)
+{
+ struct stat st;
+
+ if (fstat (archive, &st))
+ {
+ stat_diag (*archive_name_cursor);
+ return false;
+ }
+ return S_ISBLK (st.st_mode) || S_ISCHR (st.st_mode);
+}
+
+static void
+short_read (size_t status)
+{
+ size_t left; /* bytes left */
+ char *more; /* pointer to next byte to read */
+
+ more = record_start->buffer + status;
+ left = record_size - status;
+
+ if (left && left % BLOCKSIZE == 0
+ && (warning_option & WARN_RECORD_SIZE)
+ && record_start_block == 0 && status != 0
+ && archive_is_dev ())
+ {
+ unsigned long rsize = status / BLOCKSIZE;
+ WARN ((0, 0,
+ ngettext ("Record size = %lu block",
+ "Record size = %lu blocks",
+ rsize),
+ rsize));
+ }
+
+ while (left % BLOCKSIZE != 0
+ || (left && status && read_full_records))
+ {
+ if (status)
+ while ((status = rmtread (archive, more, left)) == SAFE_READ_ERROR)
+ archive_read_error ();
+
+ if (status == 0)
+ break;
+
+ if (! read_full_records)
+ {
+ unsigned long rest = record_size - left;
+
+ FATAL_ERROR ((0, 0,
+ ngettext ("Unaligned block (%lu byte) in archive",
+ "Unaligned block (%lu bytes) in archive",
+ rest),
+ rest));
+ }
+
+ left -= status;
+ more += status;
+ }
+
+ record_end = record_start + (record_size - left) / BLOCKSIZE;
+ records_read++;
+}
+
+/* Flush the current buffer to/from the archive. */
+void
+flush_archive (void)
+{
+ size_t buffer_level;
+
+ if (access_mode == ACCESS_READ && time_to_start_writing)
+ {
+ access_mode = ACCESS_WRITE;
+ time_to_start_writing = false;
+ backspace_output ();
+ if (record_end - record_start < blocking_factor)
+ {
+ memset (record_end, 0,
+ (blocking_factor - (record_end - record_start))
+ * BLOCKSIZE);
+ record_end = record_start + blocking_factor;
+ return;
+ }
+ }
+
+ buffer_level = current_block->buffer - record_start->buffer;
+ record_start_block += record_end - record_start;
+ current_block = record_start;
+ record_end = record_start + blocking_factor;
+
+ switch (access_mode)
+ {
+ case ACCESS_READ:
+ flush_read ();
+ break;
+
+ case ACCESS_WRITE:
+ flush_write_ptr (buffer_level);
+ break;
+
+ case ACCESS_UPDATE:
+ abort ();
+ }
+}
+
+/* Backspace the archive descriptor by one record worth. If it's a
+ tape, MTIOCTOP will work. If it's something else, try to seek on
+ it. If we can't seek, we lose! */
+static void
+backspace_output (void)
+{
+#ifdef MTIOCTOP
+ {
+ struct mtop operation;
+
+ operation.mt_op = MTBSR;
+ operation.mt_count = 1;
+ if (rmtioctl (archive, MTIOCTOP, (char *) &operation) >= 0)
+ return;
+ if (errno == EIO && rmtioctl (archive, MTIOCTOP, (char *) &operation) >= 0)
+ return;
+ }
+#endif
+
+ {
+ off_t position = rmtlseek (archive, (off_t) 0, SEEK_CUR);
+
+ /* Seek back to the beginning of this record and start writing there. */
+
+ position -= record_end->buffer - record_start->buffer;
+ if (position < 0)
+ position = 0;
+ if (rmtlseek (archive, position, SEEK_SET) != position)
+ {
+ /* Lseek failed. Try a different method. */
+
+ WARN ((0, 0,
+ _("Cannot backspace archive file; it may be unreadable without -i")));
+
+ /* Replace the first part of the record with NULs. */
+
+ if (record_start->buffer != output_start)
+ memset (record_start->buffer, 0,
+ output_start - record_start->buffer);
+ }
+ }
+}
+
+off_t
+seek_archive (off_t size)
+{
+ off_t start = current_block_ordinal ();
+ off_t offset;
+ off_t nrec, nblk;
+ off_t skipped = (blocking_factor - (current_block - record_start))
+ * BLOCKSIZE;
+
+ if (size <= skipped)
+ return 0;
+
+ /* Compute number of records to skip */
+ nrec = (size - skipped) / record_size;
+ if (nrec == 0)
+ return 0;
+ offset = rmtlseek (archive, nrec * record_size, SEEK_CUR);
+ if (offset < 0)
+ return offset;
+
+ if (offset % record_size)
+ FATAL_ERROR ((0, 0, _("rmtlseek not stopped at a record boundary")));
+
+ /* Convert to number of records */
+ offset /= BLOCKSIZE;
+ /* Compute number of skipped blocks */
+ nblk = offset - start;
+
+ /* Update buffering info */
+ records_read += nblk / blocking_factor;
+ record_start_block = offset - blocking_factor;
+ current_block = record_end;
+
+ return nblk;
+}
+
+/* Close the archive file. */
+void
+close_archive (void)
+{
+ if (time_to_start_writing || access_mode == ACCESS_WRITE)
+ {
+ flush_archive ();
+ if (current_block > record_start)
+ flush_archive ();
+ }
+
+ compute_duration ();
+ if (verify_option)
+ verify_volume ();
+
+ if (rmtclose (archive) != 0)
+ close_error (*archive_name_cursor);
+
+ sys_wait_for_child (child_pid, hit_eof);
+
+ tar_stat_destroy (&current_stat_info);
+ free (record_buffer[0]);
+ free (record_buffer[1]);
+ bufmap_free (NULL);
+}
+
+void
+write_fatal_details (char const *name, ssize_t status, size_t size)
+{
+ write_error_details (name, status, size);
+ if (rmtclose (archive) != 0)
+ close_error (*archive_name_cursor);
+ sys_wait_for_child (child_pid, false);
+ fatal_exit ();
+}
+
+/* Called to initialize the global volume number. */
+void
+init_volume_number (void)
+{
+ FILE *file = fopen (volno_file_option, "r");
+
+ if (file)
+ {
+ if (fscanf (file, "%d", &global_volno) != 1
+ || global_volno < 0)
+ FATAL_ERROR ((0, 0, _("%s: contains invalid volume number"),
+ quotearg_colon (volno_file_option)));
+ if (ferror (file))
+ read_error (volno_file_option);
+ if (fclose (file) != 0)
+ close_error (volno_file_option);
+ }
+ else if (errno != ENOENT)
+ open_error (volno_file_option);
+}
+
+/* Called to write out the closing global volume number. */
+void
+closeout_volume_number (void)
+{
+ FILE *file = fopen (volno_file_option, "w");
+
+ if (file)
+ {
+ fprintf (file, "%d\n", global_volno);
+ if (ferror (file))
+ write_error (volno_file_option);
+ if (fclose (file) != 0)
+ close_error (volno_file_option);
+ }
+ else
+ open_error (volno_file_option);
+}
+
+
+static void
+increase_volume_number (void)
+{
+ global_volno++;
+ if (global_volno < 0)
+ FATAL_ERROR ((0, 0, _("Volume number overflow")));
+ volno++;
+}
+
+static void
+change_tape_menu (FILE *read_file)
+{
+ char *input_buffer = NULL;
+ size_t size = 0;
+ bool stop = false;
+
+ while (!stop)
+ {
+ fputc ('\007', stderr);
+ fprintf (stderr,
+ _("Prepare volume #%d for %s and hit return: "),
+ global_volno + 1, quote (*archive_name_cursor));
+ fflush (stderr);
+
+ if (getline (&input_buffer, &size, read_file) <= 0)
+ {
+ WARN ((0, 0, _("EOF where user reply was expected")));
+
+ if (subcommand_option != EXTRACT_SUBCOMMAND
+ && subcommand_option != LIST_SUBCOMMAND
+ && subcommand_option != DIFF_SUBCOMMAND)
+ WARN ((0, 0, _("WARNING: Archive is incomplete")));
+
+ fatal_exit ();
+ }
+
+ if (input_buffer[0] == '\n'
+ || input_buffer[0] == 'y'
+ || input_buffer[0] == 'Y')
+ break;
+
+ switch (input_buffer[0])
+ {
+ case '?':
+ {
+ fprintf (stderr, _("\
+ n name Give a new file name for the next (and subsequent) volume(s)\n\
+ q Abort tar\n\
+ y or newline Continue operation\n"));
+ if (!restrict_option)
+ fprintf (stderr, _(" ! Spawn a subshell\n"));
+ fprintf (stderr, _(" ? Print this list\n"));
+ }
+ break;
+
+ case 'q':
+ /* Quit. */
+
+ WARN ((0, 0, _("No new volume; exiting.\n")));
+
+ if (subcommand_option != EXTRACT_SUBCOMMAND
+ && subcommand_option != LIST_SUBCOMMAND
+ && subcommand_option != DIFF_SUBCOMMAND)
+ WARN ((0, 0, _("WARNING: Archive is incomplete")));
+
+ fatal_exit ();
+
+ case 'n':
+ /* Get new file name. */
+
+ {
+ char *name;
+ char *cursor;
+
+ for (name = input_buffer + 1;
+ *name == ' ' || *name == '\t';
+ name++)
+ ;
+
+ for (cursor = name; *cursor && *cursor != '\n'; cursor++)
+ ;
+ *cursor = '\0';
+
+ if (name[0])
+ {
+ /* FIXME: the following allocation is never reclaimed. */
+ *archive_name_cursor = xstrdup (name);
+ stop = true;
+ }
+ else
+ fprintf (stderr, "%s",
+ _("File name not specified. Try again.\n"));
+ }
+ break;
+
+ case '!':
+ if (!restrict_option)
+ {
+ sys_spawn_shell ();
+ break;
+ }
+ /* FALL THROUGH */
+
+ default:
+ fprintf (stderr, _("Invalid input. Type ? for help.\n"));
+ }
+ }
+ free (input_buffer);
+}
+
+/* We've hit the end of the old volume. Close it and open the next one.
+ Return nonzero on success.
+*/
+static bool
+new_volume (enum access_mode mode)
+{
+ static FILE *read_file;
+ static int looped;
+ int prompt;
+
+ if (!read_file && !info_script_option)
+ /* FIXME: if fopen is used, it will never be closed. */
+ read_file = archive == STDIN_FILENO ? fopen (TTY_NAME, "r") : stdin;
+
+ if (now_verifying)
+ return false;
+ if (verify_option)
+ verify_volume ();
+
+ assign_string (&volume_label, NULL);
+ assign_string (&continued_file_name, NULL);
+ continued_file_size = continued_file_offset = 0;
+ current_block = record_start;
+
+ if (rmtclose (archive) != 0)
+ close_error (*archive_name_cursor);
+
+ archive_name_cursor++;
+ if (archive_name_cursor == archive_name_array + archive_names)
+ {
+ archive_name_cursor = archive_name_array;
+ looped = 1;
+ }
+ prompt = looped;
+
+ tryagain:
+ if (prompt)
+ {
+ /* We have to prompt from now on. */
+
+ if (info_script_option)
+ {
+ if (volno_file_option)
+ closeout_volume_number ();
+ if (sys_exec_info_script (archive_name_cursor, global_volno+1))
+ FATAL_ERROR ((0, 0, _("%s command failed"),
+ quote (info_script_option)));
+ }
+ else
+ change_tape_menu (read_file);
+ }
+
+ if (strcmp (archive_name_cursor[0], "-") == 0)
+ {
+ read_full_records = true;
+ archive = STDIN_FILENO;
+ }
+ else if (verify_option)
+ archive = rmtopen (*archive_name_cursor, O_RDWR | O_CREAT, MODE_RW,
+ rsh_command_option);
+ else
+ switch (mode)
+ {
+ case ACCESS_READ:
+ archive = rmtopen (*archive_name_cursor, O_RDONLY, MODE_RW,
+ rsh_command_option);
+ guess_seekable_archive ();
+ break;
+
+ case ACCESS_WRITE:
+ if (backup_option)
+ maybe_backup_file (*archive_name_cursor, 1);
+ archive = rmtcreat (*archive_name_cursor, MODE_RW,
+ rsh_command_option);
+ break;
+
+ case ACCESS_UPDATE:
+ archive = rmtopen (*archive_name_cursor, O_RDWR | O_CREAT, MODE_RW,
+ rsh_command_option);
+ break;
+ }
+
+ if (archive < 0)
+ {
+ open_warn (*archive_name_cursor);
+ if (!verify_option && mode == ACCESS_WRITE && backup_option)
+ undo_last_backup ();
+ prompt = 1;
+ goto tryagain;
+ }
+
+ SET_BINARY_MODE (archive);
+
+ return true;
+}
+
+static bool
+read_header0 (struct tar_stat_info *info)
+{
+ enum read_header rc;
+
+ tar_stat_init (info);
+ rc = read_header (&current_header, info, read_header_auto);
+ if (rc == HEADER_SUCCESS)
+ {
+ set_next_block_after (current_header);
+ return true;
+ }
+ ERROR ((0, 0, _("This does not look like a tar archive")));
+ return false;
+}
+
+static bool
+try_new_volume (void)
+{
+ size_t status;
+ union block *header;
+ enum access_mode acc;
+
+ switch (subcommand_option)
+ {
+ case APPEND_SUBCOMMAND:
+ case CAT_SUBCOMMAND:
+ case UPDATE_SUBCOMMAND:
+ acc = ACCESS_UPDATE;
+ break;
+
+ default:
+ acc = ACCESS_READ;
+ break;
+ }
+
+ if (!new_volume (acc))
+ return true;
+
+ while ((status = rmtread (archive, record_start->buffer, record_size))
+ == SAFE_READ_ERROR)
+ archive_read_error ();
+
+ if (status != record_size)
+ short_read (status);
+
+ header = find_next_block ();
+ if (!header)
+ {
+ WARN ((0, 0, _("This does not look like a tar archive")));
+ return false;
+ }
+
+ switch (header->header.typeflag)
+ {
+ case XGLTYPE:
+ {
+ tar_stat_init (&dummy);
+ if (read_header (&header, &dummy, read_header_x_global)
+ != HEADER_SUCCESS_EXTENDED)
+ {
+ WARN ((0, 0, _("This does not look like a tar archive")));
+ return false;
+ }
+
+ xheader_decode (&dummy); /* decodes values from the global header */
+ tar_stat_destroy (&dummy);
+
+ /* The initial global header must be immediately followed by
+ an extended PAX header for the first member in this volume.
+ However, in some cases tar may split volumes in the middle
+ of a PAX header. This is incorrect, and should be fixed
+ in the future versions. In the meantime we must be
+ prepared to correctly list and extract such archives.
+
+ If this happens, the following call to read_header returns
+ HEADER_FAILURE, which is ignored.
+
+ See also tests/multiv07.at */
+
+ switch (read_header (&header, &dummy, read_header_auto))
+ {
+ case HEADER_SUCCESS:
+ set_next_block_after (header);
+ break;
+
+ case HEADER_FAILURE:
+ break;
+
+ default:
+ WARN ((0, 0, _("This does not look like a tar archive")));
+ return false;
+ }
+ break;
+ }
+
+ case GNUTYPE_VOLHDR:
+ if (!read_header0 (&dummy))
+ return false;
+ tar_stat_destroy (&dummy);
+ assign_string (&volume_label, current_header->header.name);
+ set_next_block_after (header);
+ header = find_next_block ();
+ if (header->header.typeflag != GNUTYPE_MULTIVOL)
+ break;
+ /* FALL THROUGH */
+
+ case GNUTYPE_MULTIVOL:
+ if (!read_header0 (&dummy))
+ return false;
+ tar_stat_destroy (&dummy);
+ assign_string (&continued_file_name, current_header->header.name);
+ continued_file_size =
+ UINTMAX_FROM_HEADER (current_header->header.size);
+ continued_file_offset =
+ UINTMAX_FROM_HEADER (current_header->oldgnu_header.offset);
+ break;
+
+ default:
+ break;
+ }
+
+ if (bufmap_head)
+ {
+ uintmax_t s;
+ if (!continued_file_name)
+ {
+ WARN ((0, 0, _("%s is not continued on this volume"),
+ quote (bufmap_head->file_name)));
+ return false;
+ }
+
+ if (strcmp (continued_file_name, bufmap_head->file_name))
+ {
+ if ((archive_format == GNU_FORMAT || archive_format == OLDGNU_FORMAT)
+ && strlen (bufmap_head->file_name) >= NAME_FIELD_SIZE
+ && strncmp (continued_file_name, bufmap_head->file_name,
+ NAME_FIELD_SIZE) == 0)
+ WARN ((0, 0,
+ _("%s is possibly continued on this volume: header contains truncated name"),
+ quote (bufmap_head->file_name)));
+ else
+ {
+ WARN ((0, 0, _("%s is not continued on this volume"),
+ quote (bufmap_head->file_name)));
+ return false;
+ }
+ }
+
+ s = continued_file_size + continued_file_offset;
+
+ if (bufmap_head->sizetotal != s || s < continued_file_offset)
+ {
+ char totsizebuf[UINTMAX_STRSIZE_BOUND];
+ char s1buf[UINTMAX_STRSIZE_BOUND];
+ char s2buf[UINTMAX_STRSIZE_BOUND];
+
+ WARN ((0, 0, _("%s is the wrong size (%s != %s + %s)"),
+ quote (continued_file_name),
+ STRINGIFY_BIGINT (bufmap_head->sizetotal, totsizebuf),
+ STRINGIFY_BIGINT (continued_file_size, s1buf),
+ STRINGIFY_BIGINT (continued_file_offset, s2buf)));
+ return false;
+ }
+
+ if (bufmap_head->sizetotal - bufmap_head->sizeleft !=
+ continued_file_offset)
+ {
+ char totsizebuf[UINTMAX_STRSIZE_BOUND];
+ char s1buf[UINTMAX_STRSIZE_BOUND];
+ char s2buf[UINTMAX_STRSIZE_BOUND];
+
+ WARN ((0, 0, _("This volume is out of sequence (%s - %s != %s)"),
+ STRINGIFY_BIGINT (bufmap_head->sizetotal, totsizebuf),
+ STRINGIFY_BIGINT (bufmap_head->sizeleft, s1buf),
+ STRINGIFY_BIGINT (continued_file_offset, s2buf)));
+
+ return false;
+ }
+ }
+
+ increase_volume_number ();
+ return true;
+}
+
+
+#define VOLUME_TEXT " Volume "
+#define VOLUME_TEXT_LEN (sizeof VOLUME_TEXT - 1)
+
+char *
+drop_volume_label_suffix (const char *label)
+{
+ const char *p;
+ size_t len = strlen (label);
+
+ if (len < 1)
+ return NULL;
+
+ for (p = label + len - 1; p > label && isdigit ((unsigned char) *p); p--)
+ ;
+ if (p > label && p - (VOLUME_TEXT_LEN - 1) > label)
+ {
+ p -= VOLUME_TEXT_LEN - 1;
+ if (memcmp (p, VOLUME_TEXT, VOLUME_TEXT_LEN) == 0)
+ {
+ char *s = xmalloc ((len = p - label) + 1);
+ memcpy (s, label, len);
+ s[len] = 0;
+ return s;
+ }
+ }
+
+ return NULL;
+}
+
+/* Check LABEL against the volume label, seen as a globbing
+ pattern. Return true if the pattern matches. In case of failure,
+ retry matching a volume sequence number before giving up in
+ multi-volume mode. */
+static bool
+check_label_pattern (const char *label)
+{
+ char *string;
+ bool result = false;
+
+ if (fnmatch (volume_label_option, label, 0) == 0)
+ return true;
+
+ if (!multi_volume_option)
+ return false;
+
+ string = drop_volume_label_suffix (label);
+ if (string)
+ {
+ result = fnmatch (string, volume_label_option, 0) == 0;
+ free (string);
+ }
+ return result;
+}
+
+/* Check if the next block contains a volume label and if this matches
+ the one given in the command line */
+static void
+match_volume_label (void)
+{
+ if (!volume_label)
+ {
+ union block *label = find_next_block ();
+
+ if (!label)
+ FATAL_ERROR ((0, 0, _("Archive not labeled to match %s"),
+ quote (volume_label_option)));
+ if (label->header.typeflag == GNUTYPE_VOLHDR)
+ {
+ if (memchr (label->header.name, '\0', sizeof label->header.name))
+ assign_string (&volume_label, label->header.name);
+ else
+ {
+ volume_label = xmalloc (sizeof (label->header.name) + 1);
+ memcpy (volume_label, label->header.name,
+ sizeof (label->header.name));
+ volume_label[sizeof (label->header.name)] = 0;
+ }
+ }
+ else if (label->header.typeflag == XGLTYPE)
+ {
+ struct tar_stat_info st;
+ tar_stat_init (&st);
+ xheader_read (&st.xhdr, label,
+ OFF_FROM_HEADER (label->header.size));
+ xheader_decode (&st);
+ tar_stat_destroy (&st);
+ }
+ }
+
+ if (!volume_label)
+ FATAL_ERROR ((0, 0, _("Archive not labeled to match %s"),
+ quote (volume_label_option)));
+
+ if (!check_label_pattern (volume_label))
+ FATAL_ERROR ((0, 0, _("Volume %s does not match %s"),
+ quote_n (0, volume_label),
+ quote_n (1, volume_label_option)));
+}
+
+/* Mark the archive with volume label STR. */
+static void
+_write_volume_label (const char *str)
+{
+ if (archive_format == POSIX_FORMAT)
+ xheader_store ("GNU.volume.label", &dummy, str);
+ else
+ {
+ union block *label = find_next_block ();
+
+ memset (label, 0, BLOCKSIZE);
+
+ strcpy (label->header.name, str);
+ assign_string (&current_stat_info.file_name,
+ label->header.name);
+ current_stat_info.had_trailing_slash =
+ strip_trailing_slashes (current_stat_info.file_name);
+
+ label->header.typeflag = GNUTYPE_VOLHDR;
+ TIME_TO_CHARS (start_time.tv_sec, label->header.mtime);
+ finish_header (&current_stat_info, label, -1);
+ set_next_block_after (label);
+ }
+}
+
+#define VOL_SUFFIX "Volume"
+
+/* Add a volume label to a part of multi-volume archive */
+static void
+add_volume_label (void)
+{
+ char buf[UINTMAX_STRSIZE_BOUND];
+ char *p = STRINGIFY_BIGINT (volno, buf);
+ char *s = xmalloc (strlen (volume_label_option) + sizeof VOL_SUFFIX
+ + strlen (p) + 2);
+ sprintf (s, "%s %s %s", volume_label_option, VOL_SUFFIX, p);
+ _write_volume_label (s);
+ free (s);
+}
+
+static void
+add_chunk_header (struct bufmap *map)
+{
+ if (archive_format == POSIX_FORMAT)
+ {
+ off_t block_ordinal;
+ union block *blk;
+ struct tar_stat_info st;
+
+ memset (&st, 0, sizeof st);
+ st.orig_file_name = st.file_name = map->file_name;
+ st.stat.st_mode = S_IFREG|S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH;
+ st.stat.st_uid = getuid ();
+ st.stat.st_gid = getgid ();
+ st.orig_file_name = xheader_format_name (&st,
+ "%d/GNUFileParts.%p/%f.%n",
+ volno);
+ st.file_name = st.orig_file_name;
+ st.archive_file_size = st.stat.st_size = map->sizeleft;
+
+ block_ordinal = current_block_ordinal ();
+ blk = start_header (&st);
+ if (!blk)
+ abort (); /* FIXME */
+ finish_header (&st, blk, block_ordinal);
+ free (st.orig_file_name);
+ }
+}
+
+
+/* Add a volume label to the current archive */
+static void
+write_volume_label (void)
+{
+ if (multi_volume_option)
+ add_volume_label ();
+ else
+ _write_volume_label (volume_label_option);
+}
+
+/* Write GNU multi-volume header */
+static void
+gnu_add_multi_volume_header (struct bufmap *map)
+{
+ int tmp;
+ union block *block = find_next_block ();
+
+ if (strlen (map->file_name) > NAME_FIELD_SIZE)
+ WARN ((0, 0,
+ _("%s: file name too long to be stored in a GNU multivolume header, truncated"),
+ quotearg_colon (map->file_name)));
+
+ memset (block, 0, BLOCKSIZE);
+
+ strncpy (block->header.name, map->file_name, NAME_FIELD_SIZE);
+ block->header.typeflag = GNUTYPE_MULTIVOL;
+
+ OFF_TO_CHARS (map->sizeleft, block->header.size);
+ OFF_TO_CHARS (map->sizetotal - map->sizeleft,
+ block->oldgnu_header.offset);
+
+ tmp = verbose_option;
+ verbose_option = 0;
+ finish_header (&current_stat_info, block, -1);
+ verbose_option = tmp;
+ set_next_block_after (block);
+}
+
+/* Add a multi volume header to the current archive. The exact header format
+ depends on the archive format. */
+static void
+add_multi_volume_header (struct bufmap *map)
+{
+ if (archive_format == POSIX_FORMAT)
+ {
+ off_t d = map->sizetotal - map->sizeleft;
+ xheader_store ("GNU.volume.filename", &dummy, map->file_name);
+ xheader_store ("GNU.volume.size", &dummy, &map->sizeleft);
+ xheader_store ("GNU.volume.offset", &dummy, &d);
+ }
+ else
+ gnu_add_multi_volume_header (map);
+}
+
+
+/* Low-level flush functions */
+
+/* Simple flush read (no multi-volume or label extensions) */
+static void
+simple_flush_read (void)
+{
+ size_t status; /* result from system call */
+
+ checkpoint_run (false);
+
+ /* Clear the count of errors. This only applies to a single call to
+ flush_read. */
+
+ read_error_count = 0; /* clear error count */
+
+ if (write_archive_to_stdout && record_start_block != 0)
+ {
+ archive = STDOUT_FILENO;
+ status = sys_write_archive_buffer ();
+ archive = STDIN_FILENO;
+ if (status != record_size)
+ archive_write_error (status);
+ }
+
+ for (;;)
+ {
+ status = rmtread (archive, record_start->buffer, record_size);
+ if (status == record_size)
+ {
+ records_read++;
+ return;
+ }
+ if (status == SAFE_READ_ERROR)
+ {
+ archive_read_error ();
+ continue; /* try again */
+ }
+ break;
+ }
+ short_read (status);
+}
+
+/* Simple flush write (no multi-volume or label extensions) */
+static void
+simple_flush_write (size_t level __attribute__((unused)))
+{
+ ssize_t status;
+
+ status = _flush_write ();
+ if (status != record_size)
+ archive_write_error (status);
+ else
+ {
+ records_written++;
+ bytes_written += status;
+ }
+}
+
+
+/* GNU flush functions. These support multi-volume and archive labels in
+ GNU and PAX archive formats. */
+
+static void
+_gnu_flush_read (void)
+{
+ size_t status; /* result from system call */
+
+ checkpoint_run (false);
+
+ /* Clear the count of errors. This only applies to a single call to
+ flush_read. */
+
+ read_error_count = 0; /* clear error count */
+
+ if (write_archive_to_stdout && record_start_block != 0)
+ {
+ archive = STDOUT_FILENO;
+ status = sys_write_archive_buffer ();
+ archive = STDIN_FILENO;
+ if (status != record_size)
+ archive_write_error (status);
+ }
+
+ for (;;)
+ {
+ status = rmtread (archive, record_start->buffer, record_size);
+ if (status == record_size)
+ {
+ records_read++;
+ return;
+ }
+
+ /* The condition below used to include
+ || (status > 0 && !read_full_records)
+ This is incorrect since even if new_volume() succeeds, the
+ subsequent call to rmtread will overwrite the chunk of data
+ already read in the buffer, so the processing will fail */
+ if ((status == 0
+ || (status == SAFE_READ_ERROR && errno == ENOSPC))
+ && multi_volume_option)
+ {
+ while (!try_new_volume ())
+ ;
+ if (current_block == record_end)
+ /* Necessary for blocking_factor == 1 */
+ flush_archive();
+ return;
+ }
+ else if (status == SAFE_READ_ERROR)
+ {
+ archive_read_error ();
+ continue;
+ }
+ break;
+ }
+ short_read (status);
+}
+
+static void
+gnu_flush_read (void)
+{
+ flush_read_ptr = simple_flush_read; /* Avoid recursion */
+ _gnu_flush_read ();
+ flush_read_ptr = gnu_flush_read;
+}
+
+static void
+_gnu_flush_write (size_t buffer_level)
+{
+ ssize_t status;
+ union block *header;
+ char *copy_ptr;
+ size_t copy_size;
+ size_t bufsize;
+ struct bufmap *map;
+
+ status = _flush_write ();
+ if (status != record_size && !multi_volume_option)
+ archive_write_error (status);
+ else
+ {
+ if (status)
+ records_written++;
+ bytes_written += status;
+ }
+
+ if (status == record_size)
+ {
+ return;
+ }
+
+ map = bufmap_locate (status);
+
+ if (status % BLOCKSIZE)
+ {
+ ERROR ((0, 0, _("write did not end on a block boundary")));
+ archive_write_error (status);
+ }
+
+ /* In multi-volume mode. */
+ /* ENXIO is for the UNIX PC. */
+ if (status < 0 && errno != ENOSPC && errno != EIO && errno != ENXIO)
+ archive_write_error (status);
+
+ if (!new_volume (ACCESS_WRITE))
+ return;
+
+ tar_stat_destroy (&dummy);
+
+ increase_volume_number ();
+ prev_written += bytes_written;
+ bytes_written = 0;
+
+ copy_ptr = record_start->buffer + status;
+ copy_size = buffer_level - status;
+
+ /* Switch to the next buffer */
+ record_index = !record_index;
+ init_buffer ();
+
+ inhibit_map = 1;
+
+ if (volume_label_option)
+ add_volume_label ();
+
+ if (map)
+ add_multi_volume_header (map);
+
+ write_extended (true, &dummy, find_next_block ());
+ tar_stat_destroy (&dummy);
+
+ if (map)
+ add_chunk_header (map);
+ header = find_next_block ();
+ bufmap_reset (map, header - record_start);
+ bufsize = available_space_after (header);
+ inhibit_map = 0;
+ while (bufsize < copy_size)
+ {
+ memcpy (header->buffer, copy_ptr, bufsize);
+ copy_ptr += bufsize;
+ copy_size -= bufsize;
+ set_next_block_after (header + (bufsize - 1) / BLOCKSIZE);
+ header = find_next_block ();
+ bufsize = available_space_after (header);
+ }
+ memcpy (header->buffer, copy_ptr, copy_size);
+ memset (header->buffer + copy_size, 0, bufsize - copy_size);
+ set_next_block_after (header + (copy_size - 1) / BLOCKSIZE);
+ find_next_block ();
+}
+
+static void
+gnu_flush_write (size_t buffer_level)
+{
+ flush_write_ptr = simple_flush_write; /* Avoid recursion */
+ _gnu_flush_write (buffer_level);
+ flush_write_ptr = gnu_flush_write;
+}
+
+void
+flush_read (void)
+{
+ flush_read_ptr ();
+}
+
+void
+flush_write (void)
+{
+ flush_write_ptr (record_size);
+}
+
+void
+open_archive (enum access_mode wanted_access)
+{
+ flush_read_ptr = gnu_flush_read;
+ flush_write_ptr = gnu_flush_write;
+
+ _open_archive (wanted_access);
+ switch (wanted_access)
+ {
+ case ACCESS_READ:
+ case ACCESS_UPDATE:
+ if (volume_label_option)
+ match_volume_label ();
+ break;
+
+ case ACCESS_WRITE:
+ records_written = 0;
+ if (volume_label_option)
+ write_volume_label ();
+ break;
+ }
+ set_volume_start_time ();
+}
diff --git a/src/checkpoint.c b/src/checkpoint.c
new file mode 100644
index 0000000..1a253c2
--- /dev/null
+++ b/src/checkpoint.c
@@ -0,0 +1,438 @@
+/* Checkpoint management for tar.
+
+ Copyright 2007, 2013-2014, 2016 Free Software Foundation, Inc.
+
+ This file is part of GNU tar.
+
+ GNU tar 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.
+
+ GNU tar is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <system.h>
+#include "common.h"
+#include "wordsplit.h"
+#include <sys/ioctl.h>
+#include <termios.h>
+#include "fprintftime.h"
+
+enum checkpoint_opcode
+ {
+ cop_dot,
+ cop_bell,
+ cop_echo,
+ cop_ttyout,
+ cop_sleep,
+ cop_exec,
+ cop_totals
+ };
+
+struct checkpoint_action
+{
+ struct checkpoint_action *next;
+ enum checkpoint_opcode opcode;
+ union
+ {
+ time_t time;
+ char *command;
+ } v;
+};
+
+/* Checkpointing counter */
+static unsigned checkpoint;
+
+/* List of checkpoint actions */
+static struct checkpoint_action *checkpoint_action, *checkpoint_action_tail;
+
+static struct checkpoint_action *
+alloc_action (enum checkpoint_opcode opcode)
+{
+ struct checkpoint_action *p = xzalloc (sizeof *p);
+ if (checkpoint_action_tail)
+ checkpoint_action_tail->next = p;
+ else
+ checkpoint_action = p;
+ checkpoint_action_tail = p;
+ p->opcode = opcode;
+ return p;
+}
+
+static char *
+copy_string_unquote (const char *str)
+{
+ char *output = xstrdup (str);
+ size_t len = strlen (output);
+ if ((*output == '"' || *output == '\'')
+ && output[len-1] == *output)
+ {
+ memmove (output, output+1, len-2);
+ output[len-2] = 0;
+ }
+ unquote_string (output);
+ return output;
+}
+
+void
+checkpoint_compile_action (const char *str)
+{
+ struct checkpoint_action *act;
+
+ if (strcmp (str, ".") == 0 || strcmp (str, "dot") == 0)
+ alloc_action (cop_dot);
+ else if (strcmp (str, "bell") == 0)
+ alloc_action (cop_bell);
+ else if (strcmp (str, "echo") == 0)
+ alloc_action (cop_echo);
+ else if (strncmp (str, "echo=", 5) == 0)
+ {
+ act = alloc_action (cop_echo);
+ act->v.command = copy_string_unquote (str + 5);
+ }
+ else if (strncmp (str, "exec=", 5) == 0)
+ {
+ act = alloc_action (cop_exec);
+ act->v.command = copy_string_unquote (str + 5);
+ }
+ else if (strncmp (str, "ttyout=", 7) == 0)
+ {
+ act = alloc_action (cop_ttyout);
+ act->v.command = copy_string_unquote (str + 7);
+ }
+ else if (strncmp (str, "sleep=", 6) == 0)
+ {
+ char *p;
+ time_t n = strtoul (str+6, &p, 10);
+ if (*p)
+ FATAL_ERROR ((0, 0, _("%s: not a valid timeout"), str));
+ act = alloc_action (cop_sleep);
+ act->v.time = n;
+ }
+ else if (strcmp (str, "totals") == 0)
+ alloc_action (cop_totals);
+ else
+ FATAL_ERROR ((0, 0, _("%s: unknown checkpoint action"), str));
+}
+
+void
+checkpoint_finish_compile (void)
+{
+ if (checkpoint_option)
+ {
+ if (!checkpoint_action)
+ /* Provide a historical default */
+ checkpoint_compile_action ("echo");
+ }
+ else if (checkpoint_action)
+ /* Otherwise, set default checkpoint rate */
+ checkpoint_option = DEFAULT_CHECKPOINT;
+}
+
+static const char *checkpoint_total_format[] = {
+ "R",
+ "W",
+ "D"
+};
+
+static long
+getwidth (FILE *fp)
+{
+ char const *columns;
+
+#ifdef TIOCGWINSZ
+ struct winsize ws;
+ if (ioctl (fileno (fp), TIOCGWINSZ, &ws) == 0 && 0 < ws.ws_col)
+ return ws.ws_col;
+#endif
+
+ columns = getenv ("COLUMNS");
+ if (columns)
+ {
+ long int col = strtol (columns, NULL, 10);
+ if (0 < col)
+ return col;
+ }
+
+ return 80;
+}
+
+static char *
+getarg (const char *input, const char ** endp, char **argbuf, size_t *arglen)
+{
+ if (input[0] == '{')
+ {
+ char *p = strchr (input + 1, '}');
+ if (p)
+ {
+ size_t n = p - input;
+ if (n > *arglen)
+ {
+ *arglen = n;
+ *argbuf = xrealloc (*argbuf, *arglen);
+ }
+ n--;
+ memcpy (*argbuf, input + 1, n);
+ (*argbuf)[n] = 0;
+ *endp = p + 1;
+ return *argbuf;
+ }
+ }
+
+ *endp = input;
+ return NULL;
+}
+
+static int tty_cleanup;
+
+static const char *def_format =
+ "%{%Y-%m-%d %H:%M:%S}t: %ds, %{read,wrote}T%*\r";
+
+static int
+format_checkpoint_string (FILE *fp, size_t len,
+ const char *input, bool do_write,
+ unsigned cpn)
+{
+ const char *opstr = do_write ? gettext ("write") : gettext ("read");
+ char uintbuf[UINTMAX_STRSIZE_BOUND];
+ char *cps = STRINGIFY_BIGINT (cpn, uintbuf);
+ const char *ip;
+
+ static char *argbuf = NULL;
+ static size_t arglen = 0;
+ char *arg = NULL;
+
+ if (!input)
+ {
+ if (do_write)
+ /* TRANSLATORS: This is a "checkpoint of write operation",
+ *not* "Writing a checkpoint".
+ E.g. in Spanish "Punto de comprobaci@'on de escritura",
+ *not* "Escribiendo un punto de comprobaci@'on" */
+ input = gettext ("Write checkpoint %u");
+ else
+ /* TRANSLATORS: This is a "checkpoint of read operation",
+ *not* "Reading a checkpoint".
+ E.g. in Spanish "Punto de comprobaci@'on de lectura",
+ *not* "Leyendo un punto de comprobaci@'on" */
+ input = gettext ("Read checkpoint %u");
+ }
+
+ for (ip = input; *ip; ip++)
+ {
+ if (*ip == '%')
+ {
+ if (*++ip == '{')
+ {
+ arg = getarg (ip, &ip, &argbuf, &arglen);
+ if (!arg)
+ {
+ fputc ('%', fp);
+ fputc (*ip, fp);
+ len += 2;
+ continue;
+ }
+ }
+ switch (*ip)
+ {
+ case 'c':
+ len += format_checkpoint_string (fp, len, def_format, do_write,
+ cpn);
+ break;
+
+ case 'u':
+ fputs (cps, fp);
+ len += strlen (cps);
+ break;
+
+ case 's':
+ fputs (opstr, fp);
+ len += strlen (opstr);
+ break;
+
+ case 'd':
+ len += fprintf (fp, "%.0f", compute_duration ());
+ break;
+
+ case 'T':
+ {
+ const char **fmt = checkpoint_total_format, *fmtbuf[3];
+ struct wordsplit ws;
+ compute_duration ();
+
+ if (arg)
+ {
+ ws.ws_delim = ",";
+ if (wordsplit (arg, &ws, WRDSF_NOVAR | WRDSF_NOCMD |
+ WRDSF_QUOTE | WRDSF_DELIM))
+ ERROR ((0, 0, _("cannot split string '%s': %s"),
+ arg, wordsplit_strerror (&ws)));
+ else
+ {
+ int i;
+
+ for (i = 0; i < ws.ws_wordc; i++)
+ fmtbuf[i] = ws.ws_wordv[i];
+ for (; i < 3; i++)
+ fmtbuf[i] = NULL;
+ fmt = fmtbuf;
+ }
+ }
+ len += format_total_stats (fp, fmt, ',', 0);
+ if (arg)
+ wordsplit_free (&ws);
+ }
+ break;
+
+ case 't':
+ {
+ struct timeval tv;
+ struct tm *tm;
+ const char *fmt = arg ? arg : "%c";
+
+ gettimeofday (&tv, NULL);
+ tm = localtime (&tv.tv_sec);
+ len += fprintftime (fp, fmt, tm, 0, tv.tv_usec * 1000);
+ }
+ break;
+
+ case '*':
+ {
+ long w = arg ? strtol (arg, NULL, 10) : getwidth (fp);
+ for (; w > len; len++)
+ fputc (' ', fp);
+ }
+ break;
+
+ default:
+ fputc ('%', fp);
+ fputc (*ip, fp);
+ len += 2;
+ break;
+ }
+ arg = NULL;
+ }
+ else
+ {
+ fputc (*ip, fp);
+ if (*ip == '\r')
+ {
+ len = 0;
+ tty_cleanup = 1;
+ }
+ else
+ len++;
+ }
+ }
+ fflush (fp);
+ return len;
+}
+
+static FILE *tty = NULL;
+
+static void
+run_checkpoint_actions (bool do_write)
+{
+ struct checkpoint_action *p;
+
+ for (p = checkpoint_action; p; p = p->next)
+ {
+ switch (p->opcode)
+ {
+ case cop_dot:
+ fputc ('.', stdlis);
+ fflush (stdlis);
+ break;
+
+ case cop_bell:
+ if (!tty)
+ tty = fopen ("/dev/tty", "w");
+ if (tty)
+ {
+ fputc ('\a', tty);
+ fflush (tty);
+ }
+ break;
+
+ case cop_echo:
+ {
+ int n = fprintf (stderr, "%s: ", program_name);
+ format_checkpoint_string (stderr, n, p->v.command, do_write,
+ checkpoint);
+ fputc ('\n', stderr);
+ }
+ break;
+
+ case cop_ttyout:
+ if (!tty)
+ tty = fopen ("/dev/tty", "w");
+ if (tty)
+ format_checkpoint_string (tty, 0, p->v.command, do_write,
+ checkpoint);
+ break;
+
+ case cop_sleep:
+ sleep (p->v.time);
+ break;
+
+ case cop_exec:
+ sys_exec_checkpoint_script (p->v.command,
+ archive_name_cursor[0],
+ checkpoint);
+ break;
+
+ case cop_totals:
+ compute_duration ();
+ print_total_stats ();
+ }
+ }
+}
+
+void
+checkpoint_flush_actions (void)
+{
+ struct checkpoint_action *p;
+
+ for (p = checkpoint_action; p; p = p->next)
+ {
+ switch (p->opcode)
+ {
+ case cop_ttyout:
+ if (tty && tty_cleanup)
+ {
+ long w = getwidth (tty);
+ while (w--)
+ fputc (' ', tty);
+ fputc ('\r', tty);
+ fflush (tty);
+ }
+ break;
+ default:
+ /* nothing */;
+ }
+ }
+}
+
+void
+checkpoint_run (bool do_write)
+{
+ if (checkpoint_option && !(++checkpoint % checkpoint_option))
+ run_checkpoint_actions (do_write);
+}
+
+void
+checkpoint_finish (void)
+{
+ if (checkpoint_option)
+ {
+ checkpoint_flush_actions ();
+ if (tty)
+ fclose (tty);
+ }
+}
diff --git a/src/common.h b/src/common.h
new file mode 100644
index 0000000..50c34cc
--- /dev/null
+++ b/src/common.h
@@ -0,0 +1,974 @@
+/* Common declarations for the tar program.
+
+ Copyright 1988, 1992-1994, 1996-1997, 1999-2010, 2012-2016 Free
+ Software Foundation, Inc.
+
+ This file is part of GNU tar.
+
+ GNU tar 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.
+
+ GNU tar 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/>. */
+
+/* Declare the GNU tar archive format. */
+#include "tar.h"
+
+/* The checksum field is filled with this while the checksum is computed. */
+#define CHKBLANKS " " /* 8 blanks, no null */
+
+/* Some constants from POSIX are given names. */
+#define NAME_FIELD_SIZE 100
+#define PREFIX_FIELD_SIZE 155
+#define UNAME_FIELD_SIZE 32
+#define GNAME_FIELD_SIZE 32
+
+
+
+/* Some various global definitions. */
+
+/* Name of file to use for interacting with user. */
+
+/* GLOBAL is defined to empty in tar.c only, and left alone in other *.c
+ modules. Here, we merely set it to "extern" if it is not already set.
+ GNU tar does depend on the system loader to preset all GLOBAL variables to
+ neutral (or zero) values, explicit initialization is usually not done. */
+#ifndef GLOBAL
+# define GLOBAL extern
+#endif
+
+#define TAREXIT_SUCCESS PAXEXIT_SUCCESS
+#define TAREXIT_DIFFERS PAXEXIT_DIFFERS
+#define TAREXIT_FAILURE PAXEXIT_FAILURE
+
+
+#include "arith.h"
+#include <backupfile.h>
+#include <exclude.h>
+#include <full-write.h>
+#include <modechange.h>
+#include <quote.h>
+#include <safe-read.h>
+#include <stat-time.h>
+#include <timespec.h>
+#define obstack_chunk_alloc xmalloc
+#define obstack_chunk_free free
+#include <obstack.h>
+#include <progname.h>
+#include <xvasprintf.h>
+
+#include <paxlib.h>
+
+/* Log base 2 of common values. */
+#define LG_8 3
+#define LG_64 6
+#define LG_256 8
+
+_GL_INLINE_HEADER_BEGIN
+#ifndef COMMON_INLINE
+# define COMMON_INLINE _GL_INLINE
+#endif
+
+/* Information gleaned from the command line. */
+
+/* Main command option. */
+
+enum subcommand
+{
+ UNKNOWN_SUBCOMMAND, /* none of the following */
+ APPEND_SUBCOMMAND, /* -r */
+ CAT_SUBCOMMAND, /* -A */
+ CREATE_SUBCOMMAND, /* -c */
+ DELETE_SUBCOMMAND, /* -D */
+ DIFF_SUBCOMMAND, /* -d */
+ EXTRACT_SUBCOMMAND, /* -x */
+ LIST_SUBCOMMAND, /* -t */
+ UPDATE_SUBCOMMAND, /* -u */
+ TEST_LABEL_SUBCOMMAND, /* --test-label */
+};
+
+GLOBAL enum subcommand subcommand_option;
+
+/* Selected format for output archive. */
+GLOBAL enum archive_format archive_format;
+
+/* Size of each record, once in blocks, once in bytes. Those two variables
+ are always related, the second being BLOCKSIZE times the first. They do
+ not have _option in their name, even if their values is derived from
+ option decoding, as these are especially important in tar. */
+GLOBAL int blocking_factor;
+GLOBAL size_t record_size;
+
+GLOBAL bool absolute_names_option;
+
+/* Display file times in UTC */
+GLOBAL bool utc_option;
+/* Output file timestamps to the full resolution */
+GLOBAL bool full_time_option;
+
+/* This variable tells how to interpret newer_mtime_option, below. If zero,
+ files get archived if their mtime is not less than newer_mtime_option.
+ If nonzero, files get archived if *either* their ctime or mtime is not less
+ than newer_mtime_option. */
+GLOBAL int after_date_option;
+
+enum atime_preserve
+{
+ no_atime_preserve,
+ replace_atime_preserve,
+ system_atime_preserve
+};
+GLOBAL enum atime_preserve atime_preserve_option;
+
+GLOBAL bool backup_option;
+
+/* Type of backups being made. */
+GLOBAL enum backup_type backup_type;
+
+GLOBAL bool block_number_option;
+
+GLOBAL unsigned checkpoint_option;
+#define DEFAULT_CHECKPOINT 10
+
+/* Specified name of compression program, or "gzip" as implied by -z. */
+GLOBAL const char *use_compress_program_option;
+
+GLOBAL bool dereference_option;
+GLOBAL bool hard_dereference_option;
+
+/* Patterns that match file names to be excluded. */
+GLOBAL struct exclude *excluded;
+
+enum exclusion_tag_type
+ {
+ exclusion_tag_none,
+ /* Exclude the directory contents, but preserve the directory
+ itself and the exclusion tag file */
+ exclusion_tag_contents,
+ /* Exclude everything below the directory, preserving the directory
+ itself */
+ exclusion_tag_under,
+ /* Exclude entire directory */
+ exclusion_tag_all,
+ };
+
+/* Specified value to be put into tar file in place of stat () results, or
+ just null and -1 if such an override should not take place. */
+GLOBAL char const *group_name_option;
+GLOBAL gid_t group_option;
+
+GLOBAL bool ignore_failed_read_option;
+
+GLOBAL bool ignore_zeros_option;
+
+GLOBAL bool incremental_option;
+
+/* Specified name of script to run at end of each tape change. */
+GLOBAL const char *info_script_option;
+
+GLOBAL bool interactive_option;
+
+/* If nonzero, extract only Nth occurrence of each named file */
+GLOBAL uintmax_t occurrence_option;
+
+enum old_files
+{
+ DEFAULT_OLD_FILES, /* default */
+ NO_OVERWRITE_DIR_OLD_FILES, /* --no-overwrite-dir */
+ OVERWRITE_OLD_FILES, /* --overwrite */
+ UNLINK_FIRST_OLD_FILES, /* --unlink-first */
+ KEEP_OLD_FILES, /* --keep-old-files */
+ SKIP_OLD_FILES, /* --skip-old-files */
+ KEEP_NEWER_FILES /* --keep-newer-files */
+};
+GLOBAL enum old_files old_files_option;
+
+GLOBAL bool keep_directory_symlink_option;
+
+/* Specified file name for incremental list. */
+GLOBAL const char *listed_incremental_option;
+/* Incremental dump level */
+GLOBAL int incremental_level;
+/* Check device numbers when doing incremental dumps. */
+GLOBAL bool check_device_option;
+
+/* Specified mode change string. */
+GLOBAL struct mode_change *mode_option;
+
+/* Initial umask, if needed for mode change string. */
+GLOBAL mode_t initial_umask;
+
+GLOBAL bool multi_volume_option;
+
+/* Specified threshold date and time. Files having an older time stamp
+ do not get archived (also see after_date_option above). */
+GLOBAL struct timespec newer_mtime_option;
+
+enum set_mtime_option_mode
+{
+ USE_FILE_MTIME,
+ FORCE_MTIME,
+ CLAMP_MTIME,
+};
+
+/* Override actual mtime if set to FORCE_MTIME or CLAMP_MTIME */
+GLOBAL enum set_mtime_option_mode set_mtime_option;
+/* Value to use when forcing or clamping the mtime header field. */
+GLOBAL struct timespec mtime_option;
+
+/* Return true if mtime_option or newer_mtime_option is initialized. */
+#define TIME_OPTION_INITIALIZED(opt) (0 <= (opt).tv_nsec)
+
+/* Return true if the struct stat ST's M time is less than
+ newer_mtime_option. */
+#define OLDER_STAT_TIME(st, m) \
+ (timespec_cmp (get_stat_##m##time (&(st)), newer_mtime_option) < 0)
+
+/* Likewise, for struct tar_stat_info ST. */
+#define OLDER_TAR_STAT_TIME(st, m) \
+ (timespec_cmp ((st).m##time, newer_mtime_option) < 0)
+
+/* Zero if there is no recursion, otherwise FNM_LEADING_DIR. */
+GLOBAL int recursion_option;
+
+GLOBAL bool numeric_owner_option;
+
+GLOBAL bool one_file_system_option;
+
+/* Create a top-level directory for extracting based on the archive name. */
+GLOBAL bool one_top_level_option;
+GLOBAL char *one_top_level_dir;
+
+/* Specified value to be put into tar file in place of stat () results, or
+ just null and -1 if such an override should not take place. */
+GLOBAL char const *owner_name_option;
+GLOBAL uid_t owner_option;
+
+GLOBAL bool recursive_unlink_option;
+
+GLOBAL bool read_full_records_option;
+
+GLOBAL bool remove_files_option;
+
+/* Specified remote shell command. */
+GLOBAL const char *rsh_command_option;
+
+GLOBAL bool same_order_option;
+
+/* If positive, preserve ownership when extracting. */
+GLOBAL int same_owner_option;
+
+/* If positive, preserve permissions when extracting. */
+GLOBAL int same_permissions_option;
+
+/* If positive, save the SELinux context. */
+GLOBAL int selinux_context_option;
+
+/* If positive, save the ACLs. */
+GLOBAL int acls_option;
+
+/* If positive, save the user and root xattrs. */
+GLOBAL int xattrs_option;
+
+/* When set, strip the given number of file name components from the file name
+ before extracting */
+GLOBAL size_t strip_name_components;
+
+GLOBAL bool show_omitted_dirs_option;
+
+GLOBAL bool sparse_option;
+GLOBAL unsigned tar_sparse_major;
+GLOBAL unsigned tar_sparse_minor;
+
+enum hole_detection_method
+ {
+ HOLE_DETECTION_DEFAULT,
+ HOLE_DETECTION_RAW,
+ HOLE_DETECTION_SEEK
+ };
+
+GLOBAL enum hole_detection_method hole_detection;
+
+GLOBAL bool starting_file_option;
+
+/* Specified maximum byte length of each tape volume (multiple of 1024). */
+GLOBAL tarlong tape_length_option;
+
+GLOBAL bool to_stdout_option;
+
+GLOBAL bool totals_option;
+
+GLOBAL bool touch_option;
+
+GLOBAL char *to_command_option;
+GLOBAL bool ignore_command_error_option;
+
+/* Restrict some potentially harmful tar options */
+GLOBAL bool restrict_option;
+
+/* Return true if the extracted files are not being written to disk */
+#define EXTRACT_OVER_PIPE (to_stdout_option || to_command_option)
+
+/* Count how many times the option has been set, multiple setting yields
+ more verbose behavior. Value 0 means no verbosity, 1 means file name
+ only, 2 means file name and all attributes. More than 2 is just like 2. */
+GLOBAL int verbose_option;
+
+GLOBAL bool verify_option;
+
+/* Specified name of file containing the volume number. */
+GLOBAL const char *volno_file_option;
+
+/* Specified value or pattern. */
+GLOBAL const char *volume_label_option;
+
+/* Other global variables. */
+
+/* File descriptor for archive file. */
+GLOBAL int archive;
+
+/* Nonzero when outputting to /dev/null. */
+GLOBAL bool dev_null_output;
+
+/* Timestamps: */
+GLOBAL struct timespec start_time; /* when we started execution */
+GLOBAL struct timespec volume_start_time; /* when the current volume was
+ opened*/
+GLOBAL struct timespec last_stat_time; /* when the statistics was last
+ computed */
+
+GLOBAL struct tar_stat_info current_stat_info;
+
+/* List of tape drive names, number of such tape drives,
+ and current cursor in list. */
+GLOBAL const char **archive_name_array;
+GLOBAL size_t archive_names;
+GLOBAL const char **archive_name_cursor;
+
+/* Output index file name. */
+GLOBAL char const *index_file_name;
+
+/* Opaque structure for keeping directory meta-data */
+struct directory;
+
+/* Structure for keeping track of filenames and lists thereof. */
+struct name
+ {
+ struct name *next; /* Link to the next element */
+ struct name *prev; /* Link to the previous element */
+
+ char *name; /* File name or globbing pattern */
+ size_t length; /* cached strlen (name) */
+ int matching_flags; /* wildcard flags if name is a pattern */
+ bool cmdline; /* true if this name was given in the
+ command line */
+
+ int change_dir; /* Number of the directory to change to.
+ Set with the -C option. */
+ uintmax_t found_count; /* number of times a matching file has
+ been found */
+
+ /* The following members are used for incremental dumps only,
+ if this struct name represents a directory;
+ see incremen.c */
+ struct directory *directory;/* directory meta-data and contents */
+ struct name *parent; /* pointer to the parent hierarchy */
+ struct name *child; /* pointer to the first child */
+ struct name *sibling; /* pointer to the next sibling */
+ char *caname; /* canonical name */
+ };
+
+/* Obnoxious test to see if dimwit is trying to dump the archive. */
+GLOBAL dev_t ar_dev;
+GLOBAL ino_t ar_ino;
+
+/* Flags for reading, searching, and fstatatting files. */
+GLOBAL int open_read_flags;
+GLOBAL int open_searchdir_flags;
+GLOBAL int fstatat_flags;
+
+GLOBAL int seek_option;
+GLOBAL bool seekable_archive;
+
+GLOBAL dev_t root_device;
+
+/* Unquote filenames */
+GLOBAL bool unquote_option;
+
+GLOBAL int savedir_sort_order;
+
+/* Show file or archive names after transformation.
+ In particular, when creating archive in verbose mode, list member names
+ as stored in the archive */
+GLOBAL bool show_transformed_names_option;
+
+/* Delay setting modification times and permissions of extracted directories
+ until the end of extraction. This variable helps correctly restore directory
+ timestamps from archives with an unusual member order. It is automatically
+ set for incremental archives. */
+GLOBAL bool delay_directory_restore_option;
+
+/* When set, tar will not refuse to create empty archives */
+GLOBAL bool files_from_option;
+
+/* Declarations for each module. */
+
+/* FIXME: compare.c should not directly handle the following variable,
+ instead, this should be done in buffer.c only. */
+
+enum access_mode
+{
+ ACCESS_READ,
+ ACCESS_WRITE,
+ ACCESS_UPDATE
+};
+extern enum access_mode access_mode;
+
+/* Module buffer.c. */
+
+extern FILE *stdlis;
+extern bool write_archive_to_stdout;
+extern char *volume_label;
+extern size_t volume_label_count;
+extern char *continued_file_name;
+extern uintmax_t continued_file_size;
+extern uintmax_t continued_file_offset;
+extern off_t records_written;
+
+char *drop_volume_label_suffix (const char *label);
+
+size_t available_space_after (union block *pointer);
+off_t current_block_ordinal (void);
+void close_archive (void);
+void closeout_volume_number (void);
+double compute_duration (void);
+union block *find_next_block (void);
+void flush_read (void);
+void flush_write (void);
+void flush_archive (void);
+void init_volume_number (void);
+void open_archive (enum access_mode mode);
+void print_total_stats (void);
+void reset_eof (void);
+void set_next_block_after (union block *block);
+void clear_read_error_count (void);
+void xclose (int fd);
+void archive_write_error (ssize_t status) __attribute__ ((noreturn));
+void archive_read_error (void);
+off_t seek_archive (off_t size);
+void set_start_time (void);
+
+#define TF_READ 0
+#define TF_WRITE 1
+#define TF_DELETED 2
+int format_total_stats (FILE *fp, char const *const *formats, int eor, int eol);
+void print_total_stats (void);
+
+void mv_begin_write (const char *file_name, off_t totsize, off_t sizeleft);
+
+void mv_begin_read (struct tar_stat_info *st);
+void mv_end (void);
+void mv_size_left (off_t size);
+
+void buffer_write_global_xheader (void);
+
+const char *first_decompress_program (int *pstate);
+const char *next_decompress_program (int *pstate);
+
+/* Module create.c. */
+
+enum dump_status
+ {
+ dump_status_ok,
+ dump_status_short,
+ dump_status_fail,
+ dump_status_not_implemented
+ };
+
+void add_exclusion_tag (const char *name, enum exclusion_tag_type type,
+ bool (*predicate) (int));
+bool cachedir_file_p (int fd);
+char *get_directory_entries (struct tar_stat_info *st);
+
+void create_archive (void);
+void pad_archive (off_t size_left);
+void dump_file (struct tar_stat_info *parent, char const *name,
+ char const *fullname);
+union block *start_header (struct tar_stat_info *st);
+void finish_header (struct tar_stat_info *st, union block *header,
+ off_t block_ordinal);
+void simple_finish_header (union block *header);
+union block * write_extended (bool global, struct tar_stat_info *st,
+ union block *old_header);
+union block *start_private_header (const char *name, size_t size, time_t t);
+void write_eot (void);
+void check_links (void);
+int subfile_open (struct tar_stat_info const *dir, char const *file, int flags);
+void restore_parent_fd (struct tar_stat_info const *st);
+void exclusion_tag_warning (const char *dirname, const char *tagname,
+ const char *message);
+enum exclusion_tag_type check_exclusion_tags (struct tar_stat_info const *st,
+ const char **tag_file_name);
+
+#define OFF_TO_CHARS(val, where) off_to_chars (val, where, sizeof (where))
+#define TIME_TO_CHARS(val, where) time_to_chars (val, where, sizeof (where))
+
+bool off_to_chars (off_t off, char *buf, size_t size);
+bool time_to_chars (time_t t, char *buf, size_t size);
+
+/* Module diffarch.c. */
+
+extern bool now_verifying;
+
+void diff_archive (void);
+void diff_init (void);
+void verify_volume (void);
+
+/* Module extract.c. */
+
+void extr_init (void);
+void extract_archive (void);
+void extract_finish (void);
+bool rename_directory (char *src, char *dst);
+
+void remove_delayed_set_stat (const char *fname);
+
+/* Module delete.c. */
+
+void delete_archive_members (void);
+
+/* Module incremen.c. */
+
+struct directory *scan_directory (struct tar_stat_info *st);
+const char *directory_contents (struct directory *dir);
+const char *safe_directory_contents (struct directory *dir);
+
+void rebase_directory (struct directory *dir,
+ const char *samp, size_t slen,
+ const char *repl, size_t rlen);
+
+void append_incremental_renames (struct directory *dir);
+void show_snapshot_field_ranges (void);
+void read_directory_file (void);
+void write_directory_file (void);
+void purge_directory (char const *directory_name);
+void list_dumpdir (char *buffer, size_t size);
+void update_parent_directory (struct tar_stat_info *st);
+
+size_t dumpdir_size (const char *p);
+bool is_dumpdir (struct tar_stat_info *stat_info);
+void clear_directory_table (void);
+
+/* Module list.c. */
+
+enum read_header
+{
+ HEADER_STILL_UNREAD, /* for when read_header has not been called */
+ HEADER_SUCCESS, /* header successfully read and checksummed */
+ HEADER_SUCCESS_EXTENDED, /* likewise, but we got an extended header */
+ 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 */
+};
+
+/* Operation mode for read_header: */
+
+enum read_header_mode
+{
+ read_header_auto, /* process extended headers automatically */
+ read_header_x_raw, /* return raw extended headers (return
+ HEADER_SUCCESS_EXTENDED) */
+ read_header_x_global /* when POSIX global extended header is read,
+ decode it and return
+ HEADER_SUCCESS_EXTENDED */
+};
+extern union block *current_header;
+extern enum archive_format current_format;
+extern size_t recent_long_name_blocks;
+extern size_t recent_long_link_blocks;
+
+void decode_header (union block *header, struct tar_stat_info *stat_info,
+ enum archive_format *format_pointer, int do_user_group);
+void transform_stat_info (int typeflag, struct tar_stat_info *stat_info);
+char const *tartime (struct timespec t, bool full_time);
+
+#define OFF_FROM_HEADER(where) off_from_header (where, sizeof (where))
+#define UINTMAX_FROM_HEADER(where) uintmax_from_header (where, sizeof (where))
+
+off_t off_from_header (const char *buf, size_t size);
+uintmax_t uintmax_from_header (const char *buf, size_t size);
+
+void list_archive (void);
+void test_archive_label (void);
+void print_for_mkdir (char *dirname, int length, mode_t mode);
+void print_header (struct tar_stat_info *st, union block *blk,
+ off_t block_ordinal);
+void read_and (void (*do_something) (void));
+enum read_header read_header (union block **return_block,
+ struct tar_stat_info *info,
+ enum read_header_mode m);
+enum read_header tar_checksum (union block *header, bool silent);
+void skip_file (off_t size);
+void skip_member (void);
+
+/* Module misc.c. */
+
+#define min(a, b) ((a) < (b) ? (a) : (b))
+#define max(a, b) ((a) < (b) ? (b) : (a))
+void assign_string (char **dest, const char *src);
+int unquote_string (char *str);
+char *zap_slashes (char *name);
+char *normalize_filename (int cdidx, const char *name);
+void normalize_filename_x (char *name);
+void replace_prefix (char **pname, const char *samp, size_t slen,
+ const char *repl, size_t rlen);
+char *tar_savedir (const char *name, int must_exist);
+
+typedef struct namebuf *namebuf_t;
+namebuf_t namebuf_create (const char *dir);
+void namebuf_free (namebuf_t buf);
+char *namebuf_name (namebuf_t buf, const char *name);
+
+const char *tar_dirname (void);
+
+/* 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 "represent_uintmax returns intmax_t to represent uintmax_t"
+#endif
+COMMON_INLINE intmax_t
+represent_uintmax (uintmax_t n)
+{
+ if (n <= INTMAX_MAX)
+ return n;
+ else
+ {
+ /* Avoid signed integer overflow on picky platforms. */
+ intmax_t nd = n - INTMAX_MIN;
+ return nd + INTMAX_MIN;
+ }
+}
+
+enum { SYSINT_BUFSIZE =
+ max (UINTMAX_STRSIZE_BOUND, INT_BUFSIZE_BOUND (intmax_t)) };
+char *sysinttostr (uintmax_t, intmax_t, uintmax_t, char buf[SYSINT_BUFSIZE]);
+intmax_t strtosysint (char const *, char **, intmax_t, uintmax_t);
+void code_ns_fraction (int ns, char *p);
+char const *code_timespec (struct timespec ts, char *sbuf);
+enum { BILLION = 1000000000, LOG10_BILLION = 9 };
+enum { TIMESPEC_STRSIZE_BOUND =
+ UINTMAX_STRSIZE_BOUND + LOG10_BILLION + sizeof "-." - 1 };
+struct timespec decode_timespec (char const *, char **, bool);
+
+/* Return true if T does not represent an out-of-range or invalid value. */
+COMMON_INLINE bool
+valid_timespec (struct timespec t)
+{
+ return 0 <= t.tv_nsec;
+}
+
+bool must_be_dot_or_slash (char const *);
+
+enum remove_option
+{
+ ORDINARY_REMOVE_OPTION,
+ RECURSIVE_REMOVE_OPTION,
+
+ /* FIXME: The following value is never used. It seems to be intended
+ as a placeholder for a hypothetical option that should instruct tar
+ to recursively remove subdirectories in purge_directory(),
+ as opposed to the functionality of --recursive-unlink
+ (RECURSIVE_REMOVE_OPTION value), which removes them in
+ prepare_to_extract() phase. However, with the addition of more
+ meta-info to the incremental dumps, this should become unnecessary */
+ WANT_DIRECTORY_REMOVE_OPTION
+};
+int remove_any_file (const char *file_name, enum remove_option option);
+bool maybe_backup_file (const char *file_name, bool this_is_the_archive);
+void undo_last_backup (void);
+
+int deref_stat (char const *name, struct stat *buf);
+
+size_t blocking_read (int fd, void *buf, size_t count);
+size_t blocking_write (int fd, void const *buf, size_t count);
+
+extern int chdir_current;
+extern int chdir_fd;
+int chdir_arg (char const *dir);
+void chdir_do (int dir);
+int chdir_count (void);
+
+void close_diag (char const *name);
+void open_diag (char const *name);
+void read_diag_details (char const *name, off_t offset, size_t size);
+void readlink_diag (char const *name);
+void savedir_diag (char const *name);
+void seek_diag_details (char const *name, off_t offset);
+void stat_diag (char const *name);
+void file_removed_diag (const char *name, bool top_level,
+ void (*diagfn) (char const *name));
+void write_error_details (char const *name, size_t status, size_t size);
+void write_fatal (char const *name) __attribute__ ((noreturn));
+void write_fatal_details (char const *name, ssize_t status, size_t size)
+ __attribute__ ((noreturn));
+
+pid_t xfork (void);
+void xpipe (int fd[2]);
+
+void *page_aligned_alloc (void **ptr, size_t size);
+int set_file_atime (int fd, int parentfd, char const *file,
+ struct timespec atime);
+
+/* Module names.c. */
+
+extern size_t name_count;
+extern struct name *gnu_list_name;
+
+void gid_to_gname (gid_t gid, char **gname);
+int gname_to_gid (char const *gname, gid_t *pgid);
+void uid_to_uname (uid_t uid, char **uname);
+int uname_to_uid (char const *uname, uid_t *puid);
+
+void name_init (void);
+void name_add_name (const char *name);
+void name_term (void);
+const char *name_next (int change_dirs);
+void name_gather (void);
+struct name *addname (char const *string, int change_dir,
+ bool cmdline, struct name *parent);
+void remname (struct name *name);
+bool name_match (const char *name);
+void names_notfound (void);
+void label_notfound (void);
+void collect_and_sort_names (void);
+struct name *name_scan (const char *name);
+struct name const *name_from_list (void);
+void blank_name_list (void);
+char *make_file_name (const char *dir_name, const char *name);
+size_t stripped_prefix_len (char const *file_name, size_t num);
+bool all_names_found (struct tar_stat_info *st);
+
+void add_avoided_name (char const *name);
+bool is_avoided_name (char const *name);
+
+bool contains_dot_dot (char const *name);
+
+#define ISFOUND(c) (occurrence_option == 0 \
+ ? (c)->found_count != 0 \
+ : (c)->found_count == occurrence_option)
+#define WASFOUND(c) (occurrence_option == 0 \
+ ? (c)->found_count != 0 \
+ : (c)->found_count >= occurrence_option)
+
+/* Module tar.c. */
+
+void usage (int);
+
+int confirm (const char *message_action, const char *name);
+
+void tar_stat_init (struct tar_stat_info *st);
+bool tar_stat_close (struct tar_stat_info *st);
+void tar_stat_destroy (struct tar_stat_info *st);
+void usage (int) __attribute__ ((noreturn));
+int tar_timespec_cmp (struct timespec a, struct timespec b);
+const char *archive_format_string (enum archive_format fmt);
+const char *subcommand_string (enum subcommand c);
+void set_exit_status (int val);
+
+void request_stdin (const char *option);
+
+/* Where an option comes from: */
+enum option_source
+ {
+ OPTS_ENVIRON, /* Environment variable TAR_OPTIONS */
+ OPTS_COMMAND_LINE, /* Command line */
+ OPTS_FILE /* File supplied by --files-from */
+ };
+
+/* Option location */
+struct option_locus
+{
+ enum option_source source; /* Option origin */
+ char const *name; /* File or variable name */
+ size_t line; /* Number of input line if source is OPTS_FILE */
+ struct option_locus *prev; /* Previous occurrence of the option of same
+ class */
+};
+
+void more_options (int argc, char **argv, struct option_locus *loc);
+
+/* Module update.c. */
+
+extern char *output_start;
+
+void update_archive (void);
+
+/* Module attrs.c. */
+#include "xattrs.h"
+
+/* Module xheader.c. */
+
+void xheader_decode (struct tar_stat_info *stat);
+void xheader_decode_global (struct xheader *xhdr);
+void xheader_store (char const *keyword, struct tar_stat_info *st,
+ void const *data);
+void xheader_read (struct xheader *xhdr, union block *header, off_t size);
+void xheader_write (char type, char *name, time_t t, struct xheader *xhdr);
+void xheader_write_global (struct xheader *xhdr);
+void xheader_finish (struct xheader *hdr);
+void xheader_destroy (struct xheader *hdr);
+char *xheader_xhdr_name (struct tar_stat_info *st);
+char *xheader_ghdr_name (void);
+void xheader_set_option (char *string);
+void xheader_string_begin (struct xheader *xhdr);
+void xheader_string_add (struct xheader *xhdr, char const *s);
+bool xheader_string_end (struct xheader *xhdr, char const *keyword);
+bool xheader_keyword_deleted_p (const char *kw);
+char *xheader_format_name (struct tar_stat_info *st, const char *fmt,
+ size_t n);
+void xheader_xattr_init (struct tar_stat_info *st);
+void xheader_xattr_free (struct xattr_array *vals, size_t sz);
+void xheader_xattr_copy (const struct tar_stat_info *st,
+ struct xattr_array **vals, size_t *sz);
+void xheader_xattr_add (struct tar_stat_info *st,
+ const char *key, const char *val, size_t len);
+
+/* Module system.c */
+
+void sys_detect_dev_null_output (void);
+void sys_save_archive_dev_ino (void);
+void sys_wait_for_child (pid_t, bool);
+void sys_spawn_shell (void);
+bool sys_compare_uid (struct stat *a, struct stat *b);
+bool sys_compare_gid (struct stat *a, struct stat *b);
+bool sys_file_is_archive (struct tar_stat_info *p);
+bool sys_compare_links (struct stat *link_data, struct stat *stat_data);
+int sys_truncate (int fd);
+pid_t sys_child_open_for_compress (void);
+pid_t sys_child_open_for_uncompress (void);
+size_t sys_write_archive_buffer (void);
+bool sys_get_archive_stat (void);
+int sys_exec_command (char *file_name, int typechar, struct tar_stat_info *st);
+void sys_wait_command (void);
+int sys_exec_info_script (const char **archive_name, int volume_number);
+void sys_exec_checkpoint_script (const char *script_name,
+ const char *archive_name,
+ int checkpoint_number);
+
+/* Module compare.c */
+void report_difference (struct tar_stat_info *st, const char *message, ...)
+ __attribute__ ((format (printf, 2, 3)));
+
+/* Module sparse.c */
+bool sparse_member_p (struct tar_stat_info *st);
+bool sparse_fixup_header (struct tar_stat_info *st);
+enum dump_status sparse_dump_file (int, struct tar_stat_info *st);
+enum dump_status sparse_extract_file (int fd, struct tar_stat_info *st,
+ off_t *size);
+enum dump_status sparse_skip_file (struct tar_stat_info *st);
+bool sparse_diff_file (int, struct tar_stat_info *st);
+
+/* Module utf8.c */
+bool string_ascii_p (const char *str);
+bool utf8_convert (bool to_utf, char const *input, char **output);
+
+/* Module transform.c */
+#define XFORM_REGFILE 0x01
+#define XFORM_LINK 0x02
+#define XFORM_SYMLINK 0x04
+#define XFORM_ALL (XFORM_REGFILE|XFORM_LINK|XFORM_SYMLINK)
+
+void set_transform_expr (const char *expr);
+bool transform_name (char **pinput, int type);
+bool transform_name_fp (char **pinput, int type,
+ char *(*fun)(char *, void *), void *);
+bool transform_program_p (void);
+
+/* Module suffix.c */
+void set_compression_program_by_suffix (const char *name, const char *defprog);
+char *strip_compression_suffix (const char *name);
+
+/* Module checkpoint.c */
+void checkpoint_compile_action (const char *str);
+void checkpoint_finish_compile (void);
+void checkpoint_run (bool do_write);
+void checkpoint_finish (void);
+void checkpoint_flush_actions (void);
+
+/* Module warning.c */
+#define WARN_ALONE_ZERO_BLOCK 0x00000001
+#define WARN_BAD_DUMPDIR 0x00000002
+#define WARN_CACHEDIR 0x00000004
+#define WARN_CONTIGUOUS_CAST 0x00000008
+#define WARN_FILE_CHANGED 0x00000010
+#define WARN_FILE_IGNORED 0x00000020
+#define WARN_FILE_REMOVED 0x00000040
+#define WARN_FILE_SHRANK 0x00000080
+#define WARN_FILE_UNCHANGED 0x00000100
+#define WARN_FILENAME_WITH_NULS 0x00000200
+#define WARN_IGNORE_ARCHIVE 0x00000400
+#define WARN_IGNORE_NEWER 0x00000800
+#define WARN_NEW_DIRECTORY 0x00001000
+#define WARN_RENAME_DIRECTORY 0x00002000
+#define WARN_SYMLINK_CAST 0x00004000
+#define WARN_TIMESTAMP 0x00008000
+#define WARN_UNKNOWN_CAST 0x00010000
+#define WARN_UNKNOWN_KEYWORD 0x00020000
+#define WARN_XDEV 0x00040000
+#define WARN_DECOMPRESS_PROGRAM 0x00080000
+#define WARN_EXISTING_FILE 0x00100000
+#define WARN_XATTR_WRITE 0x00200000
+#define WARN_RECORD_SIZE 0x00400000
+
+/* These warnings are enabled by default in verbose mode: */
+#define WARN_VERBOSE_WARNINGS (WARN_RENAME_DIRECTORY|WARN_NEW_DIRECTORY|\
+ WARN_DECOMPRESS_PROGRAM|WARN_EXISTING_FILE|\
+ WARN_RECORD_SIZE)
+#define WARN_ALL (~WARN_VERBOSE_WARNINGS)
+
+void set_warning_option (const char *arg);
+
+extern int warning_option;
+
+#define WARNOPT(opt,args) \
+ do \
+ { \
+ if (warning_option & opt) WARN (args); \
+ } \
+ while (0)
+
+/* Module unlink.c */
+
+void queue_deferred_unlink (const char *name, bool is_dir);
+void finish_deferred_unlinks (void);
+
+/* Module exit.c */
+extern void (*fatal_exit_hook) (void);
+
+/* Module exclist.c */
+#define EXCL_DEFAULT 0x00
+#define EXCL_RECURSIVE 0x01
+#define EXCL_NON_RECURSIVE 0x02
+
+void excfile_add (const char *name, int flags);
+void info_attach_exclist (struct tar_stat_info *dir);
+void info_free_exclist (struct tar_stat_info *dir);
+bool excluded_name (char const *name, struct tar_stat_info *st);
+void exclude_vcs_ignores (void);
+
+/* Module map.c */
+void owner_map_read (char const *name);
+int owner_map_translate (uid_t uid, uid_t *new_uid, char const **new_name);
+void group_map_read (char const *file);
+int group_map_translate (gid_t gid, gid_t *new_gid, char const **new_name);
+
+
+_GL_INLINE_HEADER_END
diff --git a/src/compare.c b/src/compare.c
new file mode 100644
index 0000000..3f91279
--- /dev/null
+++ b/src/compare.c
@@ -0,0 +1,649 @@
+/* Diff files from a tar archive.
+
+ Copyright 1988, 1992-1994, 1996-1997, 1999-2001, 2003-2007,
+ 2009-2010, 2012-2014, 2016 Free Software Foundation, Inc.
+
+ This file is part of GNU tar.
+
+ GNU tar 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.
+
+ GNU tar 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/>.
+
+ Written by John Gilmore, on 1987-04-30. */
+
+#include <system.h>
+#include <system-ioctl.h>
+
+#if HAVE_LINUX_FD_H
+# include <linux/fd.h>
+#endif
+
+#include "common.h"
+#include <quotearg.h>
+#include <rmt.h>
+#include <stdarg.h>
+
+/* Nonzero if we are verifying at the moment. */
+bool now_verifying;
+
+/* File descriptor for the file we are diffing. */
+static int diff_handle;
+
+/* Area for reading file contents into. */
+static char *diff_buffer;
+
+/* Initialize for a diff operation. */
+void
+diff_init (void)
+{
+ void *ptr;
+ diff_buffer = page_aligned_alloc (&ptr, record_size);
+ if (listed_incremental_option)
+ read_directory_file ();
+}
+
+/* Sigh about something that differs by writing a MESSAGE to stdlis,
+ given MESSAGE is nonzero. Also set the exit status if not already. */
+void
+report_difference (struct tar_stat_info *st, const char *fmt, ...)
+{
+ if (fmt)
+ {
+ va_list ap;
+
+ fprintf (stdlis, "%s: ", quotearg_colon (st->file_name));
+ va_start (ap, fmt);
+ vfprintf (stdlis, fmt, ap);
+ va_end (ap);
+ fprintf (stdlis, "\n");
+ }
+
+ set_exit_status (TAREXIT_DIFFERS);
+}
+
+/* Take a buffer returned by read_and_process and do nothing with it. */
+static int
+process_noop (size_t size __attribute__ ((unused)),
+ char *data __attribute__ ((unused)))
+{
+ return 1;
+}
+
+static int
+process_rawdata (size_t bytes, char *buffer)
+{
+ size_t status = blocking_read (diff_handle, diff_buffer, bytes);
+
+ if (status != bytes)
+ {
+ if (status == SAFE_READ_ERROR)
+ {
+ read_error (current_stat_info.file_name);
+ report_difference (&current_stat_info, NULL);
+ }
+ else
+ {
+ report_difference (&current_stat_info,
+ ngettext ("Could only read %lu of %lu byte",
+ "Could only read %lu of %lu bytes",
+ bytes),
+ (unsigned long) status, (unsigned long) bytes);
+ }
+ return 0;
+ }
+
+ if (memcmp (buffer, diff_buffer, bytes))
+ {
+ report_difference (&current_stat_info, _("Contents differ"));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Some other routine wants SIZE bytes in the archive. For each chunk
+ of the archive, call PROCESSOR with the size of the chunk, and the
+ address of the chunk it can work with. The PROCESSOR should return
+ nonzero for success. Once it returns error, continue skipping
+ without calling PROCESSOR anymore. */
+
+static void
+read_and_process (struct tar_stat_info *st, int (*processor) (size_t, char *))
+{
+ union block *data_block;
+ size_t data_size;
+ off_t size = st->stat.st_size;
+
+ mv_begin_read (st);
+ while (size)
+ {
+ data_block = find_next_block ();
+ if (! data_block)
+ {
+ ERROR ((0, 0, _("Unexpected EOF in archive")));
+ return;
+ }
+
+ data_size = available_space_after (data_block);
+ if (data_size > size)
+ data_size = size;
+ if (!(*processor) (data_size, data_block->buffer))
+ processor = process_noop;
+ set_next_block_after ((union block *)
+ (data_block->buffer + data_size - 1));
+ size -= data_size;
+ mv_size_left (size);
+ }
+ mv_end ();
+}
+
+/* Call either stat or lstat over STAT_DATA, depending on
+ --dereference (-h), for a file which should exist. Diagnose any
+ problem. Return nonzero for success, zero otherwise. */
+static int
+get_stat_data (char const *file_name, struct stat *stat_data)
+{
+ int status = deref_stat (file_name, stat_data);
+
+ if (status != 0)
+ {
+ if (errno == ENOENT)
+ stat_warn (file_name);
+ else
+ stat_error (file_name);
+ report_difference (&current_stat_info, NULL);
+ return 0;
+ }
+
+ return 1;
+}
+
+
+static void
+diff_dir (void)
+{
+ struct stat stat_data;
+
+ if (!get_stat_data (current_stat_info.file_name, &stat_data))
+ return;
+
+ if (!S_ISDIR (stat_data.st_mode))
+ report_difference (&current_stat_info, _("File type differs"));
+ else if ((current_stat_info.stat.st_mode & MODE_ALL) !=
+ (stat_data.st_mode & MODE_ALL))
+ report_difference (&current_stat_info, _("Mode differs"));
+}
+
+static void
+diff_file (void)
+{
+ char const *file_name = current_stat_info.file_name;
+ struct stat stat_data;
+
+ if (!get_stat_data (file_name, &stat_data))
+ skip_member ();
+ else if (!S_ISREG (stat_data.st_mode))
+ {
+ report_difference (&current_stat_info, _("File type differs"));
+ skip_member ();
+ }
+ else
+ {
+ if ((current_stat_info.stat.st_mode & MODE_ALL) !=
+ (stat_data.st_mode & MODE_ALL))
+ report_difference (&current_stat_info, _("Mode differs"));
+
+ if (!sys_compare_uid (&stat_data, &current_stat_info.stat))
+ report_difference (&current_stat_info, _("Uid differs"));
+ if (!sys_compare_gid (&stat_data, &current_stat_info.stat))
+ report_difference (&current_stat_info, _("Gid differs"));
+
+ if (tar_timespec_cmp (get_stat_mtime (&stat_data),
+ current_stat_info.mtime))
+ report_difference (&current_stat_info, _("Mod time differs"));
+ if (current_header->header.typeflag != GNUTYPE_SPARSE
+ && stat_data.st_size != current_stat_info.stat.st_size)
+ {
+ report_difference (&current_stat_info, _("Size differs"));
+ skip_member ();
+ }
+ else
+ {
+ diff_handle = openat (chdir_fd, file_name, open_read_flags);
+
+ if (diff_handle < 0)
+ {
+ open_error (file_name);
+ skip_member ();
+ report_difference (&current_stat_info, NULL);
+ }
+ else
+ {
+ int status;
+
+ if (current_stat_info.is_sparse)
+ sparse_diff_file (diff_handle, &current_stat_info);
+ else
+ read_and_process (&current_stat_info, process_rawdata);
+
+ if (atime_preserve_option == replace_atime_preserve
+ && stat_data.st_size != 0)
+ {
+ struct timespec atime = get_stat_atime (&stat_data);
+ if (set_file_atime (diff_handle, chdir_fd, file_name, atime)
+ != 0)
+ utime_error (file_name);
+ }
+
+ status = close (diff_handle);
+ if (status != 0)
+ close_error (file_name);
+ }
+ }
+ }
+}
+
+static void
+diff_link (void)
+{
+ struct stat file_data;
+ struct stat link_data;
+
+ if (get_stat_data (current_stat_info.file_name, &file_data)
+ && get_stat_data (current_stat_info.link_name, &link_data)
+ && !sys_compare_links (&file_data, &link_data))
+ report_difference (&current_stat_info,
+ _("Not linked to %s"),
+ quote (current_stat_info.link_name));
+}
+
+#ifdef HAVE_READLINK
+static void
+diff_symlink (void)
+{
+ char buf[1024];
+ size_t len = strlen (current_stat_info.link_name);
+ char *linkbuf = len < sizeof buf ? buf : xmalloc (len + 1);
+
+ ssize_t status = readlinkat (chdir_fd, current_stat_info.file_name,
+ linkbuf, len + 1);
+
+ if (status < 0)
+ {
+ if (errno == ENOENT)
+ readlink_warn (current_stat_info.file_name);
+ else
+ readlink_error (current_stat_info.file_name);
+ report_difference (&current_stat_info, NULL);
+ }
+ else if (status != len
+ || memcmp (current_stat_info.link_name, linkbuf, len) != 0)
+ report_difference (&current_stat_info, _("Symlink differs"));
+
+ if (linkbuf != buf)
+ free (linkbuf);
+}
+#endif
+
+static void
+diff_special (void)
+{
+ struct stat stat_data;
+
+ /* FIXME: deal with umask. */
+
+ if (!get_stat_data (current_stat_info.file_name, &stat_data))
+ return;
+
+ if (current_header->header.typeflag == CHRTYPE
+ ? !S_ISCHR (stat_data.st_mode)
+ : current_header->header.typeflag == BLKTYPE
+ ? !S_ISBLK (stat_data.st_mode)
+ : /* current_header->header.typeflag == FIFOTYPE */
+ !S_ISFIFO (stat_data.st_mode))
+ {
+ report_difference (&current_stat_info, _("File type differs"));
+ return;
+ }
+
+ if ((current_header->header.typeflag == CHRTYPE
+ || current_header->header.typeflag == BLKTYPE)
+ && current_stat_info.stat.st_rdev != stat_data.st_rdev)
+ {
+ report_difference (&current_stat_info, _("Device number differs"));
+ return;
+ }
+
+ if ((current_stat_info.stat.st_mode & MODE_ALL) !=
+ (stat_data.st_mode & MODE_ALL))
+ report_difference (&current_stat_info, _("Mode differs"));
+}
+
+static int
+dumpdir_cmp (const char *a, const char *b)
+{
+ size_t len;
+
+ while (*a)
+ switch (*a)
+ {
+ case 'Y':
+ case 'N':
+ if (!strchr ("YN", *b))
+ return 1;
+ if (strcmp(a + 1, b + 1))
+ return 1;
+ len = strlen (a) + 1;
+ a += len;
+ b += len;
+ break;
+
+ case 'D':
+ if (strcmp(a, b))
+ return 1;
+ len = strlen (a) + 1;
+ a += len;
+ b += len;
+ break;
+
+ case 'R':
+ case 'T':
+ case 'X':
+ return *b;
+ }
+ return *b;
+}
+
+static void
+diff_dumpdir (struct tar_stat_info *dir)
+{
+ const char *dumpdir_buffer;
+
+ if (dir->fd == 0)
+ {
+ void (*diag) (char const *) = NULL;
+ int fd = subfile_open (dir->parent, dir->orig_file_name, open_read_flags);
+ if (fd < 0)
+ diag = open_diag;
+ else if (fstat (fd, &dir->stat))
+ {
+ diag = stat_diag;
+ close (fd);
+ }
+ else
+ dir->fd = fd;
+ if (diag)
+ {
+ file_removed_diag (dir->orig_file_name, false, diag);
+ return;
+ }
+ }
+ dumpdir_buffer = directory_contents (scan_directory (dir));
+
+ if (dumpdir_buffer)
+ {
+ if (dumpdir_cmp (dir->dumpdir, dumpdir_buffer))
+ report_difference (dir, _("Contents differ"));
+ }
+ else
+ read_and_process (dir, process_noop);
+}
+
+static void
+diff_multivol (void)
+{
+ struct stat stat_data;
+ int fd, status;
+ off_t offset;
+
+ if (current_stat_info.had_trailing_slash)
+ {
+ diff_dir ();
+ return;
+ }
+
+ if (!get_stat_data (current_stat_info.file_name, &stat_data))
+ return;
+
+ if (!S_ISREG (stat_data.st_mode))
+ {
+ report_difference (&current_stat_info, _("File type differs"));
+ skip_member ();
+ return;
+ }
+
+ offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
+ if (offset < 0
+ || INT_ADD_OVERFLOW (current_stat_info.stat.st_size, offset)
+ || stat_data.st_size != current_stat_info.stat.st_size + offset)
+ {
+ report_difference (&current_stat_info, _("Size differs"));
+ skip_member ();
+ return;
+ }
+
+
+ fd = openat (chdir_fd, current_stat_info.file_name, open_read_flags);
+
+ if (fd < 0)
+ {
+ open_error (current_stat_info.file_name);
+ report_difference (&current_stat_info, NULL);
+ skip_member ();
+ return;
+ }
+
+ if (lseek (fd, offset, SEEK_SET) < 0)
+ {
+ seek_error_details (current_stat_info.file_name, offset);
+ report_difference (&current_stat_info, NULL);
+ }
+ else
+ read_and_process (&current_stat_info, process_rawdata);
+
+ status = close (fd);
+ if (status != 0)
+ close_error (current_stat_info.file_name);
+}
+
+/* Diff a file against the archive. */
+void
+diff_archive (void)
+{
+
+ set_next_block_after (current_header);
+
+ /* Print the block from current_header and current_stat_info. */
+
+ if (verbose_option)
+ {
+ if (now_verifying)
+ fprintf (stdlis, _("Verify "));
+ print_header (&current_stat_info, current_header, -1);
+ }
+
+ switch (current_header->header.typeflag)
+ {
+ default:
+ ERROR ((0, 0, _("%s: Unknown file type '%c', diffed as normal file"),
+ quotearg_colon (current_stat_info.file_name),
+ current_header->header.typeflag));
+ /* Fall through. */
+
+ case AREGTYPE:
+ case REGTYPE:
+ case GNUTYPE_SPARSE:
+ case CONTTYPE:
+
+ /* Appears to be a file. See if it's really a directory. */
+
+ if (current_stat_info.had_trailing_slash)
+ diff_dir ();
+ else
+ diff_file ();
+ break;
+
+ case LNKTYPE:
+ diff_link ();
+ break;
+
+#ifdef HAVE_READLINK
+ case SYMTYPE:
+ diff_symlink ();
+ break;
+#endif
+
+ case CHRTYPE:
+ case BLKTYPE:
+ case FIFOTYPE:
+ diff_special ();
+ break;
+
+ case GNUTYPE_DUMPDIR:
+ case DIRTYPE:
+ if (is_dumpdir (&current_stat_info))
+ diff_dumpdir (&current_stat_info);
+ diff_dir ();
+ break;
+
+ case GNUTYPE_VOLHDR:
+ break;
+
+ case GNUTYPE_MULTIVOL:
+ diff_multivol ();
+ }
+}
+
+void
+verify_volume (void)
+{
+ int may_fail = 0;
+ if (removed_prefixes_p ())
+ {
+ WARN((0, 0,
+ _("Archive contains file names with leading prefixes removed.")));
+ may_fail = 1;
+ }
+ if (transform_program_p ())
+ {
+ WARN((0, 0,
+ _("Archive contains transformed file names.")));
+ may_fail = 1;
+ }
+ if (may_fail)
+ WARN((0, 0,
+ _("Verification may fail to locate original files.")));
+
+ clear_directory_table ();
+
+ if (!diff_buffer)
+ diff_init ();
+
+ /* Verifying an archive is meant to check if the physical media got it
+ correctly, so try to defeat clever in-memory buffering pertaining to
+ this particular media. On Linux, for example, the floppy drive would
+ not even be accessed for the whole verification.
+
+ The code was using fsync only when the ioctl is unavailable, but
+ Marty Leisner says that the ioctl does not work when not preceded by
+ fsync. So, until we know better, or maybe to please Marty, let's do it
+ the unbelievable way :-). */
+
+#if HAVE_FSYNC
+ fsync (archive);
+#endif
+#ifdef FDFLUSH
+ ioctl (archive, FDFLUSH);
+#endif
+
+#ifdef MTIOCTOP
+ {
+ struct mtop operation;
+ int status;
+
+ operation.mt_op = MTBSF;
+ operation.mt_count = 1;
+ if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0)
+ {
+ if (errno != EIO
+ || (status = rmtioctl (archive, MTIOCTOP, (char *) &operation),
+ status < 0))
+ {
+#endif
+ if (rmtlseek (archive, (off_t) 0, SEEK_SET) != 0)
+ {
+ /* Lseek failed. Try a different method. */
+ seek_warn (archive_name_array[0]);
+ return;
+ }
+#ifdef MTIOCTOP
+ }
+ }
+ }
+#endif
+
+ access_mode = ACCESS_READ;
+ now_verifying = 1;
+
+ flush_read ();
+ while (1)
+ {
+ enum read_header status = read_header (&current_header,
+ &current_stat_info,
+ read_header_auto);
+
+ if (status == HEADER_FAILURE)
+ {
+ int counter = 0;
+
+ do
+ {
+ counter++;
+ set_next_block_after (current_header);
+ status = read_header (&current_header, &current_stat_info,
+ read_header_auto);
+ }
+ while (status == HEADER_FAILURE);
+
+ ERROR ((0, 0,
+ ngettext ("VERIFY FAILURE: %d invalid header detected",
+ "VERIFY FAILURE: %d invalid headers detected",
+ counter), counter));
+ }
+ if (status == HEADER_END_OF_FILE)
+ break;
+ if (status == HEADER_ZERO_BLOCK)
+ {
+ set_next_block_after (current_header);
+ if (!ignore_zeros_option)
+ {
+ char buf[UINTMAX_STRSIZE_BOUND];
+
+ status = read_header (&current_header, &current_stat_info,
+ read_header_auto);
+ if (status == HEADER_ZERO_BLOCK)
+ break;
+ WARNOPT (WARN_ALONE_ZERO_BLOCK,
+ (0, 0, _("A lone zero block at %s"),
+ STRINGIFY_BIGINT (current_block_ordinal (), buf)));
+ }
+ continue;
+ }
+
+ decode_header (current_header, &current_stat_info, &current_format, 1);
+ diff_archive ();
+ tar_stat_destroy (&current_stat_info);
+ }
+
+ access_mode = ACCESS_WRITE;
+ now_verifying = 0;
+}
diff --git a/src/create.c b/src/create.c
new file mode 100644
index 0000000..3a0f2dc
--- /dev/null
+++ b/src/create.c
@@ -0,0 +1,1972 @@
+/* Create a tar archive.
+
+ Copyright 1985, 1992-1994, 1996-1997, 1999-2001, 2003-2007,
+ 2009-2010, 2012-2014, 2016 Free Software Foundation, Inc.
+
+ This file is part of GNU tar.
+
+ GNU tar 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.
+
+ GNU tar 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/>.
+
+ Written by John Gilmore, on 1985-08-25. */
+
+#include <system.h>
+
+#include <areadlink.h>
+#include <quotearg.h>
+
+#include "common.h"
+#include <hash.h>
+
+/* Error number to use when an impostor is discovered.
+ Pretend the impostor isn't there. */
+enum { IMPOSTOR_ERRNO = ENOENT };
+
+struct link
+ {
+ dev_t dev;
+ ino_t ino;
+ nlink_t nlink;
+ char name[1];
+ };
+
+struct exclusion_tag
+{
+ const char *name;
+ size_t length;
+ enum exclusion_tag_type type;
+ bool (*predicate) (int fd);
+ struct exclusion_tag *next;
+};
+
+static struct exclusion_tag *exclusion_tags;
+
+void
+add_exclusion_tag (const char *name, enum exclusion_tag_type type,
+ bool (*predicate) (int fd))
+{
+ struct exclusion_tag *tag = xmalloc (sizeof tag[0]);
+ tag->next = exclusion_tags;
+ tag->name = name;
+ tag->type = type;
+ tag->predicate = predicate;
+ tag->length = strlen (name);
+ exclusion_tags = tag;
+}
+
+void
+exclusion_tag_warning (const char *dirname, const char *tagname,
+ const char *message)
+{
+ if (verbose_option)
+ WARNOPT (WARN_CACHEDIR,
+ (0, 0,
+ _("%s: contains a cache directory tag %s; %s"),
+ quotearg_colon (dirname),
+ quotearg_n (1, tagname),
+ message));
+}
+
+enum exclusion_tag_type
+check_exclusion_tags (struct tar_stat_info const *st, char const **tag_file_name)
+{
+ struct exclusion_tag *tag;
+
+ for (tag = exclusion_tags; tag; tag = tag->next)
+ {
+ int tagfd = subfile_open (st, tag->name, open_read_flags);
+ if (0 <= tagfd)
+ {
+ bool satisfied = !tag->predicate || tag->predicate (tagfd);
+ close (tagfd);
+ if (satisfied)
+ {
+ if (tag_file_name)
+ *tag_file_name = tag->name;
+ return tag->type;
+ }
+ }
+ }
+
+ return exclusion_tag_none;
+}
+
+/* Exclusion predicate to test if the named file (usually "CACHEDIR.TAG")
+ contains a valid header, as described at:
+ http://www.brynosaurus.com/cachedir
+ Applications can write this file into directories they create
+ for use as caches containing purely regenerable, non-precious data,
+ allowing us to avoid archiving them if --exclude-caches is specified. */
+
+#define CACHEDIR_SIGNATURE "Signature: 8a477f597d28d172789f06886806bc55"
+#define CACHEDIR_SIGNATURE_SIZE (sizeof CACHEDIR_SIGNATURE - 1)
+
+bool
+cachedir_file_p (int fd)
+{
+ char tagbuf[CACHEDIR_SIGNATURE_SIZE];
+
+ return
+ (read (fd, tagbuf, CACHEDIR_SIGNATURE_SIZE) == CACHEDIR_SIGNATURE_SIZE
+ && memcmp (tagbuf, CACHEDIR_SIGNATURE, CACHEDIR_SIGNATURE_SIZE) == 0);
+}
+
+
+/* The maximum uintmax_t value that can be represented with DIGITS digits,
+ assuming that each digit is BITS_PER_DIGIT wide. */
+#define MAX_VAL_WITH_DIGITS(digits, bits_per_digit) \
+ ((digits) * (bits_per_digit) < sizeof (uintmax_t) * CHAR_BIT \
+ ? ((uintmax_t) 1 << ((digits) * (bits_per_digit))) - 1 \
+ : (uintmax_t) -1)
+
+/* The maximum uintmax_t value that can be represented with octal
+ digits and a trailing NUL in BUFFER. */
+#define MAX_OCTAL_VAL(buffer) MAX_VAL_WITH_DIGITS (sizeof (buffer) - 1, LG_8)
+
+/* Convert VALUE to an octal representation suitable for tar headers.
+ Output to buffer WHERE with size SIZE.
+ The result is undefined if SIZE is 0 or if VALUE is too large to fit. */
+
+static void
+to_octal (uintmax_t value, char *where, size_t size)
+{
+ uintmax_t v = value;
+ size_t i = size;
+
+ do
+ {
+ where[--i] = '0' + (v & ((1 << LG_8) - 1));
+ v >>= LG_8;
+ }
+ while (i);
+}
+
+/* Copy at most LEN bytes from the string SRC to DST. Terminate with
+ NUL unless SRC is LEN or more bytes long. */
+
+static void
+tar_copy_str (char *dst, const char *src, size_t len)
+{
+ size_t i;
+ for (i = 0; i < len; i++)
+ if (! (dst[i] = src[i]))
+ break;
+}
+
+/* Same as tar_copy_str, but always terminate with NUL if using
+ is OLDGNU format */
+
+static void
+tar_name_copy_str (char *dst, const char *src, size_t len)
+{
+ tar_copy_str (dst, src, len);
+ if (archive_format == OLDGNU_FORMAT)
+ dst[len-1] = 0;
+}
+
+/* Convert NEGATIVE VALUE to a base-256 representation suitable for
+ tar headers. NEGATIVE is 1 if VALUE was negative before being cast
+ to uintmax_t, 0 otherwise. Output to buffer WHERE with size SIZE.
+ The result is undefined if SIZE is 0 or if VALUE is too large to
+ fit. */
+
+static void
+to_base256 (int negative, uintmax_t value, char *where, size_t size)
+{
+ uintmax_t v = value;
+ uintmax_t propagated_sign_bits =
+ ((uintmax_t) - negative << (CHAR_BIT * sizeof v - LG_256));
+ size_t i = size;
+
+ do
+ {
+ where[--i] = v & ((1 << LG_256) - 1);
+ v = propagated_sign_bits | (v >> LG_256);
+ }
+ while (i);
+}
+
+#define GID_TO_CHARS(val, where) gid_to_chars (val, where, sizeof (where))
+#define MAJOR_TO_CHARS(val, where) major_to_chars (val, where, sizeof (where))
+#define MINOR_TO_CHARS(val, where) minor_to_chars (val, where, sizeof (where))
+#define MODE_TO_CHARS(val, where) mode_to_chars (val, where, sizeof (where))
+#define UID_TO_CHARS(val, where) uid_to_chars (val, where, sizeof (where))
+
+#define UNAME_TO_CHARS(name,buf) string_to_chars (name, buf, sizeof(buf))
+#define GNAME_TO_CHARS(name,buf) string_to_chars (name, buf, sizeof(buf))
+
+static bool
+to_chars (int negative, uintmax_t value, size_t valsize,
+ uintmax_t (*substitute) (int *),
+ char *where, size_t size, const char *type);
+
+static bool
+to_chars_subst (int negative, int gnu_format, uintmax_t value, size_t valsize,
+ uintmax_t (*substitute) (int *),
+ char *where, size_t size, const char *type)
+{
+ uintmax_t maxval = (gnu_format
+ ? MAX_VAL_WITH_DIGITS (size - 1, LG_256)
+ : MAX_VAL_WITH_DIGITS (size - 1, LG_8));
+ char valbuf[UINTMAX_STRSIZE_BOUND + 1];
+ char maxbuf[UINTMAX_STRSIZE_BOUND];
+ char minbuf[UINTMAX_STRSIZE_BOUND + 1];
+ char const *minval_string;
+ char const *maxval_string = STRINGIFY_BIGINT (maxval, maxbuf);
+ char const *value_string;
+
+ if (gnu_format)
+ {
+ uintmax_t m = maxval + 1 ? maxval + 1 : maxval / 2 + 1;
+ char *p = STRINGIFY_BIGINT (m, minbuf + 1);
+ *--p = '-';
+ minval_string = p;
+ }
+ else
+ minval_string = "0";
+
+ if (negative)
+ {
+ char *p = STRINGIFY_BIGINT (- value, valbuf + 1);
+ *--p = '-';
+ value_string = p;
+ }
+ else
+ value_string = STRINGIFY_BIGINT (value, valbuf);
+
+ if (substitute)
+ {
+ int negsub;
+ uintmax_t sub = substitute (&negsub) & maxval;
+ /* NOTE: This is one of the few places where GNU_FORMAT differs from
+ OLDGNU_FORMAT. The actual differences are:
+
+ 1. In OLDGNU_FORMAT all strings in a tar header end in \0
+ 2. Incremental archives use oldgnu_header.
+
+ Apart from this they are completely identical. */
+ uintmax_t s = (negsub &= archive_format == GNU_FORMAT) ? - sub : sub;
+ char subbuf[UINTMAX_STRSIZE_BOUND + 1];
+ char *sub_string = STRINGIFY_BIGINT (s, subbuf + 1);
+ if (negsub)
+ *--sub_string = '-';
+ WARN ((0, 0, _("value %s out of %s range %s..%s; substituting %s"),
+ value_string, type, minval_string, maxval_string,
+ sub_string));
+ return to_chars (negsub, s, valsize, 0, where, size, type);
+ }
+ else
+ ERROR ((0, 0, _("value %s out of %s range %s..%s"),
+ value_string, type, minval_string, maxval_string));
+ return false;
+}
+
+/* Convert NEGATIVE VALUE (which was originally of size VALSIZE) to
+ external form, using SUBSTITUTE (...) if VALUE won't fit. Output
+ to buffer WHERE with size SIZE. NEGATIVE is 1 iff VALUE was
+ negative before being cast to uintmax_t; its original bitpattern
+ can be deduced from VALSIZE, its original size before casting.
+ TYPE is the kind of value being output (useful for diagnostics).
+ Prefer the POSIX format of SIZE - 1 octal digits (with leading zero
+ digits), followed by '\0'. If this won't work, and if GNU or
+ OLDGNU format is allowed, use '\200' followed by base-256, or (if
+ NEGATIVE is nonzero) '\377' followed by two's complement base-256.
+ If neither format works, use SUBSTITUTE (...) instead. Pass to
+ SUBSTITUTE the address of an 0-or-1 flag recording whether the
+ substitute value is negative. */
+
+static bool
+to_chars (int negative, uintmax_t value, size_t valsize,
+ uintmax_t (*substitute) (int *),
+ char *where, size_t size, const char *type)
+{
+ int gnu_format = (archive_format == GNU_FORMAT
+ || archive_format == OLDGNU_FORMAT);
+
+ /* Generate the POSIX octal representation if the number fits. */
+ if (! negative && value <= MAX_VAL_WITH_DIGITS (size - 1, LG_8))
+ {
+ where[size - 1] = '\0';
+ to_octal (value, where, size - 1);
+ return true;
+ }
+ else if (gnu_format)
+ {
+ /* Try to cope with the number by using traditional GNU format
+ methods */
+
+ /* Generate the base-256 representation if the number fits. */
+ if (((negative ? -1 - value : value)
+ <= MAX_VAL_WITH_DIGITS (size - 1, LG_256)))
+ {
+ where[0] = negative ? -1 : 1 << (LG_256 - 1);
+ to_base256 (negative, value, where + 1, size - 1);
+ return true;
+ }
+
+ /* Otherwise, if the number is negative, and if it would not cause
+ ambiguity on this host by confusing positive with negative
+ values, then generate the POSIX octal representation of the value
+ modulo 2**(field bits). The resulting tar file is
+ machine-dependent, since it depends on the host word size. Yuck!
+ But this is the traditional behavior. */
+ else if (negative && valsize * CHAR_BIT <= (size - 1) * LG_8)
+ {
+ static int warned_once;
+ if (! warned_once)
+ {
+ warned_once = 1;
+ WARN ((0, 0, _("Generating negative octal headers")));
+ }
+ where[size - 1] = '\0';
+ to_octal (value & MAX_VAL_WITH_DIGITS (valsize * CHAR_BIT, 1),
+ where, size - 1);
+ return true;
+ }
+ /* Otherwise fall back to substitution, if possible: */
+ }
+ else
+ substitute = NULL; /* No substitution for formats, other than GNU */
+
+ return to_chars_subst (negative, gnu_format, value, valsize, substitute,
+ where, size, type);
+}
+
+static uintmax_t
+gid_substitute (int *negative)
+{
+ gid_t r;
+#ifdef GID_NOBODY
+ r = GID_NOBODY;
+#else
+ static gid_t gid_nobody;
+ if (!gid_nobody && !gname_to_gid ("nobody", &gid_nobody))
+ gid_nobody = -2;
+ r = gid_nobody;
+#endif
+ *negative = r < 0;
+ return r;
+}
+
+static bool
+gid_to_chars (gid_t v, char *p, size_t s)
+{
+ return to_chars (v < 0, (uintmax_t) v, sizeof v, gid_substitute, p, s, "gid_t");
+}
+
+static bool
+major_to_chars (major_t v, char *p, size_t s)
+{
+ return to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "major_t");
+}
+
+static bool
+minor_to_chars (minor_t v, char *p, size_t s)
+{
+ return to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "minor_t");
+}
+
+static bool
+mode_to_chars (mode_t v, char *p, size_t s)
+{
+ /* In the common case where the internal and external mode bits are the same,
+ and we are not using POSIX or GNU format,
+ propagate all unknown bits to the external mode.
+ This matches historical practice.
+ Otherwise, just copy the bits we know about. */
+ int negative;
+ uintmax_t u;
+ if (S_ISUID == TSUID && S_ISGID == TSGID && S_ISVTX == TSVTX
+ && S_IRUSR == TUREAD && S_IWUSR == TUWRITE && S_IXUSR == TUEXEC
+ && S_IRGRP == TGREAD && S_IWGRP == TGWRITE && S_IXGRP == TGEXEC
+ && S_IROTH == TOREAD && S_IWOTH == TOWRITE && S_IXOTH == TOEXEC
+ && archive_format != POSIX_FORMAT
+ && archive_format != USTAR_FORMAT
+ && archive_format != GNU_FORMAT)
+ {
+ negative = v < 0;
+ u = v;
+ }
+ else
+ {
+ negative = 0;
+ u = ((v & S_ISUID ? TSUID : 0)
+ | (v & S_ISGID ? TSGID : 0)
+ | (v & S_ISVTX ? TSVTX : 0)
+ | (v & S_IRUSR ? TUREAD : 0)
+ | (v & S_IWUSR ? TUWRITE : 0)
+ | (v & S_IXUSR ? TUEXEC : 0)
+ | (v & S_IRGRP ? TGREAD : 0)
+ | (v & S_IWGRP ? TGWRITE : 0)
+ | (v & S_IXGRP ? TGEXEC : 0)
+ | (v & S_IROTH ? TOREAD : 0)
+ | (v & S_IWOTH ? TOWRITE : 0)
+ | (v & S_IXOTH ? TOEXEC : 0));
+ }
+ return to_chars (negative, u, sizeof v, 0, p, s, "mode_t");
+}
+
+bool
+off_to_chars (off_t v, char *p, size_t s)
+{
+ return to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "off_t");
+}
+
+bool
+time_to_chars (time_t v, char *p, size_t s)
+{
+ return to_chars (v < 0, (uintmax_t) v, sizeof v, 0, p, s, "time_t");
+}
+
+static uintmax_t
+uid_substitute (int *negative)
+{
+ uid_t r;
+#ifdef UID_NOBODY
+ r = UID_NOBODY;
+#else
+ static uid_t uid_nobody;
+ if (!uid_nobody && !uname_to_uid ("nobody", &uid_nobody))
+ uid_nobody = -2;
+ r = uid_nobody;
+#endif
+ *negative = r < 0;
+ return r;
+}
+
+static bool
+uid_to_chars (uid_t v, char *p, size_t s)
+{
+ return to_chars (v < 0, (uintmax_t) v, sizeof v, uid_substitute, p, s, "uid_t");
+}
+
+static bool
+uintmax_to_chars (uintmax_t v, char *p, size_t s)
+{
+ return to_chars (0, v, sizeof v, 0, p, s, "uintmax_t");
+}
+
+static void
+string_to_chars (char const *str, char *p, size_t s)
+{
+ tar_copy_str (p, str, s);
+ p[s - 1] = '\0';
+}
+
+
+/* A directory is always considered dumpable.
+ Otherwise, only regular and contiguous files are considered dumpable.
+ Such a file is dumpable if it is sparse and both --sparse and --totals
+ are specified.
+ Otherwise, it is dumpable unless any of the following conditions occur:
+
+ a) it is empty *and* world-readable, or
+ b) current archive is /dev/null */
+
+static bool
+file_dumpable_p (struct stat const *st)
+{
+ if (S_ISDIR (st->st_mode))
+ return true;
+ if (! (S_ISREG (st->st_mode) || S_ISCTG (st->st_mode)))
+ return false;
+ if (dev_null_output)
+ return totals_option && sparse_option && ST_IS_SPARSE (*st);
+ return ! (st->st_size == 0 && (st->st_mode & MODE_R) == MODE_R);
+}
+
+
+/* Writing routines. */
+
+/* Write the EOT block(s). Zero at least two blocks, through the end
+ of the record. Old tar, as previous versions of GNU tar, writes
+ garbage after two zeroed blocks. */
+void
+write_eot (void)
+{
+ union block *pointer = find_next_block ();
+ memset (pointer->buffer, 0, BLOCKSIZE);
+ set_next_block_after (pointer);
+ pointer = find_next_block ();
+ memset (pointer->buffer, 0, available_space_after (pointer));
+ set_next_block_after (pointer);
+}
+
+/* Write a "private" header */
+union block *
+start_private_header (const char *name, size_t size, time_t t)
+{
+ union block *header = find_next_block ();
+
+ memset (header->buffer, 0, sizeof (union block));
+
+ tar_name_copy_str (header->header.name, name, NAME_FIELD_SIZE);
+ OFF_TO_CHARS (size, header->header.size);
+
+ TIME_TO_CHARS (t < 0 ? 0 : min (t, MAX_OCTAL_VAL (header->header.mtime)),
+ header->header.mtime);
+ MODE_TO_CHARS (S_IFREG|S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, header->header.mode);
+ UID_TO_CHARS (0, header->header.uid);
+ GID_TO_CHARS (0, header->header.gid);
+ strncpy (header->header.magic, TMAGIC, TMAGLEN);
+ strncpy (header->header.version, TVERSION, TVERSLEN);
+ return header;
+}
+
+/* Create a new header and store there at most NAME_FIELD_SIZE bytes of
+ the file name */
+
+static union block *
+write_short_name (struct tar_stat_info *st)
+{
+ union block *header = find_next_block ();
+ memset (header->buffer, 0, sizeof (union block));
+ tar_name_copy_str (header->header.name, st->file_name, NAME_FIELD_SIZE);
+ return header;
+}
+
+/* Write a GNUTYPE_LONGLINK or GNUTYPE_LONGNAME block. */
+static void
+write_gnu_long_link (struct tar_stat_info *st, const char *p, char type)
+{
+ size_t size = strlen (p) + 1;
+ size_t bufsize;
+ union block *header;
+ char *tmpname;
+
+ header = start_private_header ("././@LongLink", size, 0);
+ uid_to_uname (0, &tmpname);
+ UNAME_TO_CHARS (tmpname, header->header.uname);
+ free (tmpname);
+ gid_to_gname (0, &tmpname);
+ GNAME_TO_CHARS (tmpname, header->header.gname);
+ free (tmpname);
+
+ strcpy (header->buffer + offsetof (struct posix_header, magic),
+ OLDGNU_MAGIC);
+ header->header.typeflag = type;
+ finish_header (st, header, -1);
+
+ header = find_next_block ();
+
+ bufsize = available_space_after (header);
+
+ while (bufsize < size)
+ {
+ memcpy (header->buffer, p, bufsize);
+ p += bufsize;
+ size -= bufsize;
+ set_next_block_after (header + (bufsize - 1) / BLOCKSIZE);
+ header = find_next_block ();
+ bufsize = available_space_after (header);
+ }
+ memcpy (header->buffer, p, size);
+ memset (header->buffer + size, 0, bufsize - size);
+ set_next_block_after (header + (size - 1) / BLOCKSIZE);
+}
+
+static size_t
+split_long_name (const char *name, size_t length)
+{
+ size_t i;
+
+ if (length > PREFIX_FIELD_SIZE + 1)
+ length = PREFIX_FIELD_SIZE + 1;
+ else if (ISSLASH (name[length - 1]))
+ length--;
+ for (i = length - 1; i > 0; i--)
+ if (ISSLASH (name[i]))
+ break;
+ return i;
+}
+
+static union block *
+write_ustar_long_name (const char *name)
+{
+ size_t length = strlen (name);
+ size_t i, nlen;
+ union block *header;
+
+ if (length > PREFIX_FIELD_SIZE + NAME_FIELD_SIZE + 1)
+ {
+ ERROR ((0, 0, _("%s: file name is too long (max %d); not dumped"),
+ quotearg_colon (name),
+ PREFIX_FIELD_SIZE + NAME_FIELD_SIZE + 1));
+ return NULL;
+ }
+
+ i = split_long_name (name, length);
+ if (i == 0 || (nlen = length - i - 1) > NAME_FIELD_SIZE || nlen == 0)
+ {
+ ERROR ((0, 0,
+ _("%s: file name is too long (cannot be split); not dumped"),
+ quotearg_colon (name)));
+ return NULL;
+ }
+
+ header = find_next_block ();
+ memset (header->buffer, 0, sizeof (header->buffer));
+ memcpy (header->header.prefix, name, i);
+ memcpy (header->header.name, name + i + 1, length - i - 1);
+
+ return header;
+}
+
+/* Write a long link name, depending on the current archive format */
+static void
+write_long_link (struct tar_stat_info *st)
+{
+ switch (archive_format)
+ {
+ case POSIX_FORMAT:
+ xheader_store ("linkpath", st, NULL);
+ break;
+
+ case V7_FORMAT: /* old V7 tar format */
+ case USTAR_FORMAT:
+ case STAR_FORMAT:
+ ERROR ((0, 0,
+ _("%s: link name is too long; not dumped"),
+ quotearg_colon (st->link_name)));
+ break;
+
+ case OLDGNU_FORMAT:
+ case GNU_FORMAT:
+ write_gnu_long_link (st, st->link_name, GNUTYPE_LONGLINK);
+ break;
+
+ default:
+ abort(); /*FIXME*/
+ }
+}
+
+static union block *
+write_long_name (struct tar_stat_info *st)
+{
+ switch (archive_format)
+ {
+ case POSIX_FORMAT:
+ xheader_store ("path", st, NULL);
+ break;
+
+ case V7_FORMAT:
+ if (strlen (st->file_name) > NAME_FIELD_SIZE-1)
+ {
+ ERROR ((0, 0, _("%s: file name is too long (max %d); not dumped"),
+ quotearg_colon (st->file_name),
+ NAME_FIELD_SIZE - 1));
+ return NULL;
+ }
+ break;
+
+ case USTAR_FORMAT:
+ case STAR_FORMAT:
+ return write_ustar_long_name (st->file_name);
+
+ case OLDGNU_FORMAT:
+ case GNU_FORMAT:
+ write_gnu_long_link (st, st->file_name, GNUTYPE_LONGNAME);
+ break;
+
+ default:
+ abort(); /*FIXME*/
+ }
+ return write_short_name (st);
+}
+
+union block *
+write_extended (bool global, struct tar_stat_info *st, union block *old_header)
+{
+ union block *header, hp;
+ char *p;
+ int type;
+ time_t t;
+
+ if (st->xhdr.buffer || st->xhdr.stk == NULL)
+ return old_header;
+
+ xheader_finish (&st->xhdr);
+ memcpy (hp.buffer, old_header, sizeof (hp));
+ if (global)
+ {
+ type = XGLTYPE;
+ p = xheader_ghdr_name ();
+ t = start_time.tv_sec;
+ }
+ else
+ {
+ type = XHDTYPE;
+ p = xheader_xhdr_name (st);
+ t = set_mtime_option ? mtime_option.tv_sec : st->stat.st_mtime;
+ }
+ xheader_write (type, p, t, &st->xhdr);
+ free (p);
+ header = find_next_block ();
+ memcpy (header, &hp.buffer, sizeof (hp.buffer));
+ return header;
+}
+
+static union block *
+write_header_name (struct tar_stat_info *st)
+{
+ if (archive_format == POSIX_FORMAT && !string_ascii_p (st->file_name))
+ {
+ xheader_store ("path", st, NULL);
+ return write_short_name (st);
+ }
+ else if (NAME_FIELD_SIZE - (archive_format == OLDGNU_FORMAT)
+ < strlen (st->file_name))
+ return write_long_name (st);
+ else
+ return write_short_name (st);
+}
+
+
+/* Header handling. */
+
+/* Make a header block for the file whose stat info is st,
+ and return its address. */
+
+union block *
+start_header (struct tar_stat_info *st)
+{
+ union block *header;
+ char const *uname = NULL;
+ char const *gname = NULL;
+
+ header = write_header_name (st);
+ if (!header)
+ return NULL;
+
+ /* Override some stat fields, if requested to do so. */
+ owner_map_translate (st->stat.st_uid, &st->stat.st_uid, &uname);
+ group_map_translate (st->stat.st_gid, &st->stat.st_gid, &gname);
+
+ if (mode_option)
+ st->stat.st_mode =
+ ((st->stat.st_mode & ~MODE_ALL)
+ | mode_adjust (st->stat.st_mode, S_ISDIR (st->stat.st_mode) != 0,
+ initial_umask, mode_option, NULL));
+
+ /* Paul Eggert tried the trivial test ($WRITER cf a b; $READER tvf a)
+ for a few tars and came up with the following interoperability
+ matrix:
+
+ WRITER
+ 1 2 3 4 5 6 7 8 9 READER
+ . . . . . . . . . 1 = SunOS 4.2 tar
+ # . . # # . . # # 2 = NEC SVR4.0.2 tar
+ . . . # # . . # . 3 = Solaris 2.1 tar
+ . . . . . . . . . 4 = GNU tar 1.11.1
+ . . . . . . . . . 5 = HP-UX 8.07 tar
+ . . . . . . . . . 6 = Ultrix 4.1
+ . . . . . . . . . 7 = AIX 3.2
+ . . . . . . . . . 8 = Hitachi HI-UX 1.03
+ . . . . . . . . . 9 = Omron UNIOS-B 4.3BSD 1.60Beta
+
+ . = works
+ # = "impossible file type"
+
+ The following mask for old archive removes the '#'s in column 4
+ above, thus making GNU tar both a universal donor and a universal
+ acceptor for Paul's test. */
+
+ if (archive_format == V7_FORMAT || archive_format == USTAR_FORMAT)
+ MODE_TO_CHARS (st->stat.st_mode & MODE_ALL, header->header.mode);
+ else
+ MODE_TO_CHARS (st->stat.st_mode, header->header.mode);
+
+ {
+ uid_t uid = st->stat.st_uid;
+ if (archive_format == POSIX_FORMAT
+ && MAX_OCTAL_VAL (header->header.uid) < uid)
+ {
+ xheader_store ("uid", st, NULL);
+ uid = 0;
+ }
+ if (!UID_TO_CHARS (uid, header->header.uid))
+ return NULL;
+ }
+
+ {
+ gid_t gid = st->stat.st_gid;
+ if (archive_format == POSIX_FORMAT
+ && MAX_OCTAL_VAL (header->header.gid) < gid)
+ {
+ xheader_store ("gid", st, NULL);
+ gid = 0;
+ }
+ if (!GID_TO_CHARS (gid, header->header.gid))
+ return NULL;
+ }
+
+ {
+ off_t size = st->stat.st_size;
+ if (archive_format == POSIX_FORMAT
+ && MAX_OCTAL_VAL (header->header.size) < size)
+ {
+ xheader_store ("size", st, NULL);
+ size = 0;
+ }
+ if (!OFF_TO_CHARS (size, header->header.size))
+ return NULL;
+ }
+
+ {
+ struct timespec mtime;
+
+ switch (set_mtime_option)
+ {
+ case USE_FILE_MTIME:
+ mtime = st->mtime;
+ break;
+
+ case FORCE_MTIME:
+ mtime = mtime_option;
+ break;
+
+ case CLAMP_MTIME:
+ mtime = timespec_cmp (st->mtime, mtime_option) > 0
+ ? mtime_option : st->mtime;
+ break;
+ }
+
+ if (archive_format == POSIX_FORMAT)
+ {
+ if (MAX_OCTAL_VAL (header->header.mtime) < mtime.tv_sec
+ || mtime.tv_nsec != 0)
+ xheader_store ("mtime", st, &mtime);
+ if (MAX_OCTAL_VAL (header->header.mtime) < mtime.tv_sec)
+ mtime.tv_sec = 0;
+ }
+ if (!TIME_TO_CHARS (mtime.tv_sec, header->header.mtime))
+ return NULL;
+ }
+
+ /* FIXME */
+ if (S_ISCHR (st->stat.st_mode)
+ || S_ISBLK (st->stat.st_mode))
+ {
+ major_t devmajor = major (st->stat.st_rdev);
+ minor_t devminor = minor (st->stat.st_rdev);
+
+ if (archive_format == POSIX_FORMAT
+ && MAX_OCTAL_VAL (header->header.devmajor) < devmajor)
+ {
+ xheader_store ("devmajor", st, NULL);
+ devmajor = 0;
+ }
+ if (!MAJOR_TO_CHARS (devmajor, header->header.devmajor))
+ return NULL;
+
+ if (archive_format == POSIX_FORMAT
+ && MAX_OCTAL_VAL (header->header.devminor) < devminor)
+ {
+ xheader_store ("devminor", st, NULL);
+ devminor = 0;
+ }
+ if (!MINOR_TO_CHARS (devminor, header->header.devminor))
+ return NULL;
+ }
+ else if (archive_format != GNU_FORMAT && archive_format != OLDGNU_FORMAT)
+ {
+ if (!(MAJOR_TO_CHARS (0, header->header.devmajor)
+ && MINOR_TO_CHARS (0, header->header.devminor)))
+ return NULL;
+ }
+
+ if (archive_format == POSIX_FORMAT)
+ {
+ xheader_store ("atime", st, NULL);
+ xheader_store ("ctime", st, NULL);
+ }
+ else if (incremental_option)
+ if (archive_format == OLDGNU_FORMAT || archive_format == GNU_FORMAT)
+ {
+ TIME_TO_CHARS (st->atime.tv_sec, header->oldgnu_header.atime);
+ TIME_TO_CHARS (st->ctime.tv_sec, header->oldgnu_header.ctime);
+ }
+
+ header->header.typeflag = archive_format == V7_FORMAT ? AREGTYPE : REGTYPE;
+
+ switch (archive_format)
+ {
+ case V7_FORMAT:
+ break;
+
+ case OLDGNU_FORMAT:
+ case GNU_FORMAT: /*FIXME?*/
+ /* Overwrite header->header.magic and header.version in one blow. */
+ strcpy (header->buffer + offsetof (struct posix_header, magic),
+ OLDGNU_MAGIC);
+ break;
+
+ case POSIX_FORMAT:
+ case USTAR_FORMAT:
+ strncpy (header->header.magic, TMAGIC, TMAGLEN);
+ strncpy (header->header.version, TVERSION, TVERSLEN);
+ break;
+
+ default:
+ abort ();
+ }
+
+ if (archive_format == V7_FORMAT || numeric_owner_option)
+ {
+ /* header->header.[ug]name are left as the empty string. */
+ }
+ else
+ {
+ if (uname)
+ st->uname = xstrdup (uname);
+ else
+ uid_to_uname (st->stat.st_uid, &st->uname);
+
+ if (gname)
+ st->gname = xstrdup (gname);
+ else
+ gid_to_gname (st->stat.st_gid, &st->gname);
+
+ if (archive_format == POSIX_FORMAT
+ && (strlen (st->uname) > UNAME_FIELD_SIZE
+ || !string_ascii_p (st->uname)))
+ xheader_store ("uname", st, NULL);
+ UNAME_TO_CHARS (st->uname, header->header.uname);
+
+ if (archive_format == POSIX_FORMAT
+ && (strlen (st->gname) > GNAME_FIELD_SIZE
+ || !string_ascii_p (st->gname)))
+ xheader_store ("gname", st, NULL);
+ GNAME_TO_CHARS (st->gname, header->header.gname);
+ }
+
+ if (archive_format == POSIX_FORMAT)
+ {
+ if (acls_option > 0)
+ {
+ if (st->acls_a_ptr)
+ xheader_store ("SCHILY.acl.access", st, NULL);
+ if (st->acls_d_ptr)
+ xheader_store ("SCHILY.acl.default", st, NULL);
+ }
+ if ((selinux_context_option > 0) && st->cntx_name)
+ xheader_store ("RHT.security.selinux", st, NULL);
+ if (xattrs_option > 0)
+ {
+ size_t scan_xattr = 0;
+ struct xattr_array *xattr_map = st->xattr_map;
+
+ while (scan_xattr < st->xattr_map_size)
+ {
+ xheader_store (xattr_map[scan_xattr].xkey, st, &scan_xattr);
+ ++scan_xattr;
+ }
+ }
+ }
+
+ return header;
+}
+
+void
+simple_finish_header (union block *header)
+{
+ size_t i;
+ int sum;
+ char *p;
+
+ memcpy (header->header.chksum, CHKBLANKS, sizeof header->header.chksum);
+
+ sum = 0;
+ p = header->buffer;
+ for (i = sizeof *header; i-- != 0; )
+ /* We can't use unsigned char here because of old compilers, e.g. V7. */
+ sum += 0xFF & *p++;
+
+ /* Fill in the checksum field. It's formatted differently from the
+ other fields: it has [6] digits, a null, then a space -- rather than
+ digits, then a null. We use to_chars.
+ The final space is already there, from
+ checksumming, and to_chars doesn't modify it.
+
+ This is a fast way to do:
+
+ sprintf(header->header.chksum, "%6o", sum); */
+
+ uintmax_to_chars ((uintmax_t) sum, header->header.chksum, 7);
+
+ set_next_block_after (header);
+}
+
+/* Finish off a filled-in header block and write it out. We also
+ print the file name and/or full info if verbose is on. If BLOCK_ORDINAL
+ is not negative, is the block ordinal of the first record for this
+ file, which may be a preceding long name or long link record. */
+void
+finish_header (struct tar_stat_info *st,
+ union block *header, off_t block_ordinal)
+{
+ /* Note: It is important to do this before the call to write_extended(),
+ so that the actual ustar header is printed */
+ if (verbose_option
+ && header->header.typeflag != GNUTYPE_LONGLINK
+ && header->header.typeflag != GNUTYPE_LONGNAME
+ && header->header.typeflag != XHDTYPE
+ && header->header.typeflag != XGLTYPE)
+ {
+ /* FIXME: This global is used in print_header, sigh. */
+ current_format = archive_format;
+ print_header (st, header, block_ordinal);
+ }
+
+ header = write_extended (false, st, header);
+ simple_finish_header (header);
+}
+
+
+void
+pad_archive (off_t size_left)
+{
+ union block *blk;
+ while (size_left > 0)
+ {
+ blk = find_next_block ();
+ memset (blk->buffer, 0, BLOCKSIZE);
+ set_next_block_after (blk);
+ size_left -= BLOCKSIZE;
+ }
+}
+
+static enum dump_status
+dump_regular_file (int fd, struct tar_stat_info *st)
+{
+ off_t size_left = st->stat.st_size;
+ off_t block_ordinal;
+ union block *blk;
+
+ block_ordinal = current_block_ordinal ();
+ blk = start_header (st);
+ if (!blk)
+ return dump_status_fail;
+
+ /* Mark contiguous files, if we support them. */
+ if (archive_format != V7_FORMAT && S_ISCTG (st->stat.st_mode))
+ blk->header.typeflag = CONTTYPE;
+
+ finish_header (st, blk, block_ordinal);
+
+ mv_begin_write (st->file_name, st->stat.st_size, st->stat.st_size);
+ while (size_left > 0)
+ {
+ size_t bufsize, count;
+
+ blk = find_next_block ();
+
+ bufsize = available_space_after (blk);
+
+ if (size_left < bufsize)
+ {
+ /* Last read -- zero out area beyond. */
+ bufsize = size_left;
+ count = bufsize % BLOCKSIZE;
+ if (count)
+ memset (blk->buffer + size_left, 0, BLOCKSIZE - count);
+ }
+
+ count = (fd <= 0) ? bufsize : blocking_read (fd, blk->buffer, bufsize);
+ if (count == SAFE_READ_ERROR)
+ {
+ read_diag_details (st->orig_file_name,
+ st->stat.st_size - size_left, bufsize);
+ pad_archive (size_left);
+ return dump_status_short;
+ }
+ size_left -= count;
+ set_next_block_after (blk + (bufsize - 1) / BLOCKSIZE);
+
+ if (count != bufsize)
+ {
+ char buf[UINTMAX_STRSIZE_BOUND];
+ memset (blk->buffer + count, 0, bufsize - count);
+ WARNOPT (WARN_FILE_SHRANK,
+ (0, 0,
+ ngettext ("%s: File shrank by %s byte; padding with zeros",
+ "%s: File shrank by %s bytes; padding with zeros",
+ size_left),
+ quotearg_colon (st->orig_file_name),
+ STRINGIFY_BIGINT (size_left, buf)));
+ if (! ignore_failed_read_option)
+ set_exit_status (TAREXIT_DIFFERS);
+ pad_archive (size_left - (bufsize - count));
+ return dump_status_short;
+ }
+ }
+ return dump_status_ok;
+}
+
+
+/* Copy info from the directory identified by ST into the archive.
+ DIRECTORY contains the directory's entries. */
+
+static void
+dump_dir0 (struct tar_stat_info *st, char const *directory)
+{
+ bool top_level = ! st->parent;
+ const char *tag_file_name;
+ union block *blk = NULL;
+ off_t block_ordinal = current_block_ordinal ();
+
+ st->stat.st_size = 0; /* force 0 size on dir */
+
+ blk = start_header (st);
+ if (!blk)
+ return;
+
+ info_attach_exclist (st);
+
+ if (incremental_option && archive_format != POSIX_FORMAT)
+ blk->header.typeflag = GNUTYPE_DUMPDIR;
+ else /* if (standard_option) */
+ blk->header.typeflag = DIRTYPE;
+
+ /* If we're gnudumping, we aren't done yet so don't close it. */
+
+ if (!incremental_option)
+ finish_header (st, blk, block_ordinal);
+ else if (gnu_list_name->directory)
+ {
+ if (archive_format == POSIX_FORMAT)
+ {
+ xheader_store ("GNU.dumpdir", st,
+ safe_directory_contents (gnu_list_name->directory));
+ finish_header (st, blk, block_ordinal);
+ }
+ else
+ {
+ off_t size_left;
+ off_t totsize;
+ size_t bufsize;
+ ssize_t count;
+ const char *buffer, *p_buffer;
+
+ block_ordinal = current_block_ordinal ();
+ buffer = safe_directory_contents (gnu_list_name->directory);
+ totsize = dumpdir_size (buffer);
+ OFF_TO_CHARS (totsize, blk->header.size);
+ finish_header (st, blk, block_ordinal);
+ p_buffer = buffer;
+ size_left = totsize;
+
+ mv_begin_write (st->file_name, totsize, totsize);
+ while (size_left > 0)
+ {
+ blk = find_next_block ();
+ bufsize = available_space_after (blk);
+ if (size_left < bufsize)
+ {
+ bufsize = size_left;
+ count = bufsize % BLOCKSIZE;
+ if (count)
+ memset (blk->buffer + size_left, 0, BLOCKSIZE - count);
+ }
+ memcpy (blk->buffer, p_buffer, bufsize);
+ size_left -= bufsize;
+ p_buffer += bufsize;
+ set_next_block_after (blk + (bufsize - 1) / BLOCKSIZE);
+ }
+ }
+ return;
+ }
+
+ if (!recursion_option)
+ return;
+
+ if (one_file_system_option
+ && !top_level
+ && st->parent->stat.st_dev != st->stat.st_dev)
+ {
+ if (verbose_option)
+ WARNOPT (WARN_XDEV,
+ (0, 0,
+ _("%s: file is on a different filesystem; not dumped"),
+ quotearg_colon (st->orig_file_name)));
+ }
+ else
+ {
+ char *name_buf;
+ size_t name_size;
+
+ switch (check_exclusion_tags (st, &tag_file_name))
+ {
+ case exclusion_tag_all:
+ /* Handled in dump_file0 */
+ break;
+
+ case exclusion_tag_none:
+ {
+ char const *entry;
+ size_t entry_len;
+ size_t name_len;
+
+ name_buf = xstrdup (st->orig_file_name);
+ name_size = name_len = strlen (name_buf);
+
+ /* Now output all the files in the directory. */
+ for (entry = directory; (entry_len = strlen (entry)) != 0;
+ entry += entry_len + 1)
+ {
+ if (name_size < name_len + entry_len)
+ {
+ name_size = name_len + entry_len;
+ name_buf = xrealloc (name_buf, name_size + 1);
+ }
+ strcpy (name_buf + name_len, entry);
+ if (!excluded_name (name_buf, st))
+ dump_file (st, entry, name_buf);
+ }
+
+ free (name_buf);
+ }
+ break;
+
+ case exclusion_tag_contents:
+ exclusion_tag_warning (st->orig_file_name, tag_file_name,
+ _("contents not dumped"));
+ name_size = strlen (st->orig_file_name) + strlen (tag_file_name) + 1;
+ name_buf = xmalloc (name_size);
+ strcpy (name_buf, st->orig_file_name);
+ strcat (name_buf, tag_file_name);
+ dump_file (st, tag_file_name, name_buf);
+ free (name_buf);
+ break;
+
+ case exclusion_tag_under:
+ exclusion_tag_warning (st->orig_file_name, tag_file_name,
+ _("contents not dumped"));
+ break;
+ }
+ }
+}
+
+/* Ensure exactly one trailing slash. */
+static void
+ensure_slash (char **pstr)
+{
+ size_t len = strlen (*pstr);
+ while (len >= 1 && ISSLASH ((*pstr)[len - 1]))
+ len--;
+ if (!ISSLASH ((*pstr)[len]))
+ *pstr = xrealloc (*pstr, len + 2);
+ (*pstr)[len++] = '/';
+ (*pstr)[len] = '\0';
+}
+
+/* If we just ran out of file descriptors, release a file descriptor
+ in the directory chain somewhere leading from DIR->parent->parent
+ up through the root. Return true if successful, false (preserving
+ errno == EMFILE) otherwise.
+
+ Do not release DIR's file descriptor, or DIR's parent, as other
+ code assumes that they work. On some operating systems, another
+ process can claim file descriptor resources as we release them, and
+ some calls or their emulations require multiple file descriptors,
+ so callers should not give up if a single release doesn't work. */
+
+static bool
+open_failure_recover (struct tar_stat_info const *dir)
+{
+ if (errno == EMFILE && dir && dir->parent)
+ {
+ struct tar_stat_info *p;
+ for (p = dir->parent->parent; p; p = p->parent)
+ if (0 < p->fd && (! p->parent || p->parent->fd <= 0))
+ {
+ tar_stat_close (p);
+ return true;
+ }
+ errno = EMFILE;
+ }
+
+ return false;
+}
+
+/* Return the directory entries of ST, in a dynamically allocated buffer,
+ each entry followed by '\0' and the last followed by an extra '\0'.
+ Return null on failure, setting errno. */
+char *
+get_directory_entries (struct tar_stat_info *st)
+{
+ while (! (st->dirstream = fdopendir (st->fd)))
+ if (! open_failure_recover (st))
+ return 0;
+ return streamsavedir (st->dirstream, savedir_sort_order);
+}
+
+/* Dump the directory ST. Return true if successful, false (emitting
+ diagnostics) otherwise. Get ST's entries, recurse through its
+ subdirectories, and clean up file descriptors afterwards. */
+static bool
+dump_dir (struct tar_stat_info *st)
+{
+ char *directory = get_directory_entries (st);
+ if (! directory)
+ {
+ savedir_diag (st->orig_file_name);
+ return false;
+ }
+
+ dump_dir0 (st, directory);
+
+ restore_parent_fd (st);
+ free (directory);
+ return true;
+}
+
+
+/* Number of links a file can have without having to be entered into
+ the link table. Typically this is 1, but in trickier circumstances
+ it is 0. */
+static nlink_t trivial_link_count;
+
+
+/* Main functions of this module. */
+
+void
+create_archive (void)
+{
+ struct name const *p;
+
+ trivial_link_count = name_count <= 1 && ! dereference_option;
+
+ open_archive (ACCESS_WRITE);
+ buffer_write_global_xheader ();
+
+ if (incremental_option)
+ {
+ size_t buffer_size = 1000;
+ char *buffer = xmalloc (buffer_size);
+ const char *q;
+
+ collect_and_sort_names ();
+
+ while ((p = name_from_list ()) != NULL)
+ if (!excluded_name (p->name, NULL))
+ dump_file (0, p->name, p->name);
+
+ blank_name_list ();
+ while ((p = name_from_list ()) != NULL)
+ if (!excluded_name (p->name, NULL))
+ {
+ struct tar_stat_info st;
+ size_t plen = strlen (p->name);
+ if (buffer_size <= plen)
+ {
+ while ((buffer_size *= 2) <= plen)
+ continue;
+ buffer = xrealloc (buffer, buffer_size);
+ }
+ memcpy (buffer, p->name, plen);
+ if (! ISSLASH (buffer[plen - 1]))
+ buffer[plen++] = DIRECTORY_SEPARATOR;
+ tar_stat_init (&st);
+ q = directory_contents (p->directory);
+ if (q)
+ while (*q)
+ {
+ size_t qlen = strlen (q);
+ if (*q == 'Y')
+ {
+ if (! st.orig_file_name)
+ {
+ int fd = openat (chdir_fd, p->name,
+ open_searchdir_flags);
+ if (fd < 0)
+ {
+ open_diag (p->name);
+ break;
+ }
+ st.fd = fd;
+ if (fstat (fd, &st.stat) != 0)
+ {
+ stat_diag (p->name);
+ break;
+ }
+ st.orig_file_name = xstrdup (p->name);
+ }
+ if (buffer_size < plen + qlen)
+ {
+ while ((buffer_size *=2 ) < plen + qlen)
+ continue;
+ buffer = xrealloc (buffer, buffer_size);
+ }
+ strcpy (buffer + plen, q + 1);
+ dump_file (&st, q + 1, buffer);
+ }
+ q += qlen + 1;
+ }
+ tar_stat_destroy (&st);
+ }
+ free (buffer);
+ }
+ else
+ {
+ const char *name;
+ while ((name = name_next (1)) != NULL)
+ if (!excluded_name (name, NULL))
+ dump_file (0, name, name);
+ }
+
+ write_eot ();
+ close_archive ();
+ finish_deferred_unlinks ();
+ if (listed_incremental_option)
+ write_directory_file ();
+}
+
+
+/* Calculate the hash of a link. */
+static size_t
+hash_link (void const *entry, size_t n_buckets)
+{
+ struct link const *l = entry;
+ uintmax_t num = l->dev ^ l->ino;
+ return num % n_buckets;
+}
+
+/* Compare two links for equality. */
+static bool
+compare_links (void const *entry1, void const *entry2)
+{
+ struct link const *link1 = entry1;
+ struct link const *link2 = entry2;
+ return ((link1->dev ^ link2->dev) | (link1->ino ^ link2->ino)) == 0;
+}
+
+static void
+unknown_file_error (char const *p)
+{
+ WARNOPT (WARN_FILE_IGNORED,
+ (0, 0, _("%s: Unknown file type; file ignored"),
+ quotearg_colon (p)));
+ if (!ignore_failed_read_option)
+ set_exit_status (TAREXIT_FAILURE);
+}
+
+
+/* Handling of hard links */
+
+/* Table of all non-directories that we've written so far. Any time
+ we see another, we check the table and avoid dumping the data
+ again if we've done it once already. */
+static Hash_table *link_table;
+
+/* Try to dump stat as a hard link to another file in the archive.
+ Return true if successful. */
+static bool
+dump_hard_link (struct tar_stat_info *st)
+{
+ if (link_table
+ && (trivial_link_count < st->stat.st_nlink || remove_files_option))
+ {
+ struct link lp;
+ struct link *duplicate;
+ off_t block_ordinal;
+ union block *blk;
+
+ lp.ino = st->stat.st_ino;
+ lp.dev = st->stat.st_dev;
+
+ if ((duplicate = hash_lookup (link_table, &lp)))
+ {
+ /* We found a link. */
+ char const *link_name = safer_name_suffix (duplicate->name, true,
+ absolute_names_option);
+ if (duplicate->nlink)
+ duplicate->nlink--;
+
+ block_ordinal = current_block_ordinal ();
+ assign_string (&st->link_name, link_name);
+ if (NAME_FIELD_SIZE - (archive_format == OLDGNU_FORMAT)
+ < strlen (link_name))
+ write_long_link (st);
+
+ st->stat.st_size = 0;
+ blk = start_header (st);
+ if (!blk)
+ return false;
+ tar_copy_str (blk->header.linkname, link_name, NAME_FIELD_SIZE);
+
+ blk->header.typeflag = LNKTYPE;
+ finish_header (st, blk, block_ordinal);
+
+ if (remove_files_option)
+ queue_deferred_unlink (st->orig_file_name, false);
+
+ return true;
+ }
+ }
+ return false;
+}
+
+static void
+file_count_links (struct tar_stat_info *st)
+{
+ if (hard_dereference_option)
+ return;
+ if (trivial_link_count < st->stat.st_nlink)
+ {
+ struct link *duplicate;
+ char *linkname = NULL;
+ struct link *lp;
+
+ assign_string (&linkname, st->orig_file_name);
+ transform_name (&linkname, XFORM_LINK);
+
+ lp = xmalloc (offsetof (struct link, name)
+ + strlen (linkname) + 1);
+ lp->ino = st->stat.st_ino;
+ lp->dev = st->stat.st_dev;
+ lp->nlink = st->stat.st_nlink;
+ strcpy (lp->name, linkname);
+ free (linkname);
+
+ if (! ((link_table
+ || (link_table = hash_initialize (0, 0, hash_link,
+ compare_links, 0)))
+ && (duplicate = hash_insert (link_table, lp))))
+ xalloc_die ();
+
+ if (duplicate != lp)
+ abort ();
+ lp->nlink--;
+ }
+}
+
+/* For each dumped file, check if all its links were dumped. Emit
+ warnings if it is not so. */
+void
+check_links (void)
+{
+ struct link *lp;
+
+ if (!link_table)
+ return;
+
+ for (lp = hash_get_first (link_table); lp;
+ lp = hash_get_next (link_table, lp))
+ {
+ if (lp->nlink)
+ {
+ WARN ((0, 0, _("Missing links to %s."), quote (lp->name)));
+ }
+ }
+}
+
+/* Assuming DIR is the working directory, open FILE, using FLAGS to
+ control the open. A null DIR means to use ".". If we are low on
+ file descriptors, try to release one or more from DIR's parents to
+ reuse it. */
+int
+subfile_open (struct tar_stat_info const *dir, char const *file, int flags)
+{
+ int fd;
+
+ static bool initialized;
+ if (! initialized)
+ {
+ /* Initialize any tables that might be needed when file
+ descriptors are exhausted, and whose initialization might
+ require a file descriptor. This includes the system message
+ catalog and tar's message catalog. */
+ initialized = true;
+ strerror (ENOENT);
+ gettext ("");
+ }
+
+ while ((fd = openat (dir ? dir->fd : chdir_fd, file, flags)) < 0
+ && open_failure_recover (dir))
+ continue;
+ return fd;
+}
+
+/* Restore the file descriptor for ST->parent, if it was temporarily
+ closed to conserve file descriptors. On failure, set the file
+ descriptor to the negative of the corresponding errno value. Call
+ this every time a subdirectory is ascended from. */
+void
+restore_parent_fd (struct tar_stat_info const *st)
+{
+ struct tar_stat_info *parent = st->parent;
+ if (parent && ! parent->fd)
+ {
+ int parentfd = openat (st->fd, "..", open_searchdir_flags);
+ struct stat parentstat;
+
+ if (parentfd < 0)
+ parentfd = - errno;
+ else if (! (fstat (parentfd, &parentstat) == 0
+ && parent->stat.st_ino == parentstat.st_ino
+ && parent->stat.st_dev == parentstat.st_dev))
+ {
+ close (parentfd);
+ parentfd = IMPOSTOR_ERRNO;
+ }
+
+ if (parentfd < 0)
+ {
+ int origfd = openat (chdir_fd, parent->orig_file_name,
+ open_searchdir_flags);
+ if (0 <= origfd)
+ {
+ if (fstat (parentfd, &parentstat) == 0
+ && parent->stat.st_ino == parentstat.st_ino
+ && parent->stat.st_dev == parentstat.st_dev)
+ parentfd = origfd;
+ else
+ close (origfd);
+ }
+ }
+
+ parent->fd = parentfd;
+ }
+}
+
+/* Dump a single file, recursing on directories. ST is the file's
+ status info, NAME its name relative to the parent directory, and P
+ its full name (which may be relative to the working directory). */
+
+/* FIXME: One should make sure that for *every* path leading to setting
+ exit_status to failure, a clear diagnostic has been issued. */
+
+static void
+dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
+{
+ union block *header;
+ char type;
+ off_t original_size;
+ struct timespec original_ctime;
+ off_t block_ordinal = -1;
+ int fd = 0;
+ bool is_dir;
+ struct tar_stat_info const *parent = st->parent;
+ bool top_level = ! parent;
+ int parentfd = top_level ? chdir_fd : parent->fd;
+ void (*diag) (char const *) = 0;
+
+ if (interactive_option && !confirm ("add", p))
+ return;
+
+ assign_string (&st->orig_file_name, p);
+ assign_string (&st->file_name,
+ safer_name_suffix (p, false, absolute_names_option));
+
+ transform_name (&st->file_name, XFORM_REGFILE);
+
+ if (parentfd < 0 && ! top_level)
+ {
+ errno = - parentfd;
+ diag = open_diag;
+ }
+ else if (fstatat (parentfd, name, &st->stat, fstatat_flags) != 0)
+ diag = stat_diag;
+ else if (file_dumpable_p (&st->stat))
+ {
+ fd = subfile_open (parent, name, open_read_flags);
+ if (fd < 0)
+ diag = open_diag;
+ else
+ {
+ st->fd = fd;
+ if (fstat (fd, &st->stat) != 0)
+ diag = stat_diag;
+ }
+ }
+ if (diag)
+ {
+ file_removed_diag (p, top_level, diag);
+ return;
+ }
+
+ st->archive_file_size = original_size = st->stat.st_size;
+ st->atime = get_stat_atime (&st->stat);
+ st->mtime = get_stat_mtime (&st->stat);
+ st->ctime = original_ctime = get_stat_ctime (&st->stat);
+
+#ifdef S_ISHIDDEN
+ if (S_ISHIDDEN (st->stat.st_mode))
+ {
+ char *new = (char *) alloca (strlen (p) + 2);
+ if (new)
+ {
+ strcpy (new, p);
+ strcat (new, "@");
+ p = new;
+ }
+ }
+#endif
+
+ /* See if we want only new files, and check if this one is too old to
+ put in the archive.
+
+ This check is omitted if incremental_option is set *and* the
+ requested file is not explicitly listed in the command line. */
+
+ if (! (incremental_option && ! top_level)
+ && !S_ISDIR (st->stat.st_mode)
+ && OLDER_TAR_STAT_TIME (*st, m)
+ && (!after_date_option || OLDER_TAR_STAT_TIME (*st, c)))
+ {
+ if (!incremental_option && verbose_option)
+ WARNOPT (WARN_FILE_UNCHANGED,
+ (0, 0, _("%s: file is unchanged; not dumped"),
+ quotearg_colon (p)));
+ return;
+ }
+
+ /* See if we are trying to dump the archive. */
+ if (sys_file_is_archive (st))
+ {
+ WARNOPT (WARN_IGNORE_ARCHIVE,
+ (0, 0, _("%s: file is the archive; not dumped"),
+ quotearg_colon (p)));
+ return;
+ }
+
+ is_dir = S_ISDIR (st->stat.st_mode) != 0;
+
+ if (!is_dir && dump_hard_link (st))
+ return;
+
+ if (is_dir || S_ISREG (st->stat.st_mode) || S_ISCTG (st->stat.st_mode))
+ {
+ bool ok;
+ struct stat final_stat;
+
+ xattrs_acls_get (parentfd, name, st, 0, !is_dir);
+ xattrs_selinux_get (parentfd, name, st, fd);
+ xattrs_xattrs_get (parentfd, name, st, fd);
+
+ if (is_dir)
+ {
+ const char *tag_file_name;
+ ensure_slash (&st->orig_file_name);
+ ensure_slash (&st->file_name);
+
+ if (check_exclusion_tags (st, &tag_file_name) == exclusion_tag_all)
+ {
+ exclusion_tag_warning (st->orig_file_name, tag_file_name,
+ _("directory not dumped"));
+ return;
+ }
+
+ ok = dump_dir (st);
+
+ fd = st->fd;
+ parentfd = top_level ? chdir_fd : parent->fd;
+ }
+ else
+ {
+ enum dump_status status;
+
+ if (fd && sparse_option && ST_IS_SPARSE (st->stat))
+ {
+ status = sparse_dump_file (fd, st);
+ if (status == dump_status_not_implemented)
+ status = dump_regular_file (fd, st);
+ }
+ else
+ status = dump_regular_file (fd, st);
+
+ switch (status)
+ {
+ case dump_status_ok:
+ case dump_status_short:
+ file_count_links (st);
+ break;
+
+ case dump_status_fail:
+ break;
+
+ case dump_status_not_implemented:
+ abort ();
+ }
+
+ ok = status == dump_status_ok;
+ }
+
+ if (ok)
+ {
+ if (fd < 0)
+ {
+ errno = - fd;
+ ok = false;
+ }
+ else if (fd == 0)
+ {
+ if (parentfd < 0 && ! top_level)
+ {
+ errno = - parentfd;
+ ok = false;
+ }
+ else
+ ok = fstatat (parentfd, name, &final_stat, fstatat_flags) == 0;
+ }
+ else
+ ok = fstat (fd, &final_stat) == 0;
+
+ if (! ok)
+ file_removed_diag (p, top_level, stat_diag);
+ }
+
+ if (ok)
+ {
+ if ((timespec_cmp (get_stat_ctime (&final_stat), original_ctime) != 0
+ /* Original ctime will change if the file is a directory and
+ --remove-files is given */
+ && !(remove_files_option && is_dir))
+ || original_size < final_stat.st_size)
+ {
+ WARNOPT (WARN_FILE_CHANGED,
+ (0, 0, _("%s: file changed as we read it"),
+ quotearg_colon (p)));
+ set_exit_status (TAREXIT_DIFFERS);
+ }
+ else if (atime_preserve_option == replace_atime_preserve
+ && fd && (is_dir || original_size != 0)
+ && set_file_atime (fd, parentfd, name, st->atime) != 0)
+ utime_error (p);
+ }
+
+ ok &= tar_stat_close (st);
+ if (ok && remove_files_option)
+ queue_deferred_unlink (p, is_dir);
+
+ return;
+ }
+#ifdef HAVE_READLINK
+ else if (S_ISLNK (st->stat.st_mode))
+ {
+ st->link_name = areadlinkat_with_size (parentfd, name, st->stat.st_size);
+ if (!st->link_name)
+ {
+ if (errno == ENOMEM)
+ xalloc_die ();
+ file_removed_diag (p, top_level, readlink_diag);
+ return;
+ }
+ transform_name (&st->link_name, XFORM_SYMLINK);
+ if (NAME_FIELD_SIZE - (archive_format == OLDGNU_FORMAT)
+ < strlen (st->link_name))
+ write_long_link (st);
+
+ xattrs_selinux_get (parentfd, name, st, 0);
+ xattrs_xattrs_get (parentfd, name, st, 0);
+
+ block_ordinal = current_block_ordinal ();
+ st->stat.st_size = 0; /* force 0 size on symlink */
+ header = start_header (st);
+ if (!header)
+ return;
+ tar_copy_str (header->header.linkname, st->link_name, NAME_FIELD_SIZE);
+ header->header.typeflag = SYMTYPE;
+ finish_header (st, header, block_ordinal);
+ /* nothing more to do to it */
+
+ if (remove_files_option)
+ queue_deferred_unlink (p, false);
+
+ file_count_links (st);
+ return;
+ }
+#endif
+ else if (S_ISCHR (st->stat.st_mode))
+ {
+ type = CHRTYPE;
+ xattrs_acls_get (parentfd, name, st, 0, true);
+ xattrs_selinux_get (parentfd, name, st, 0);
+ xattrs_xattrs_get (parentfd, name, st, 0);
+ }
+ else if (S_ISBLK (st->stat.st_mode))
+ {
+ type = BLKTYPE;
+ xattrs_acls_get (parentfd, name, st, 0, true);
+ xattrs_selinux_get (parentfd, name, st, 0);
+ xattrs_xattrs_get (parentfd, name, st, 0);
+ }
+ else if (S_ISFIFO (st->stat.st_mode))
+ {
+ type = FIFOTYPE;
+ xattrs_acls_get (parentfd, name, st, 0, true);
+ xattrs_selinux_get (parentfd, name, st, 0);
+ xattrs_xattrs_get (parentfd, name, st, 0);
+ }
+ else if (S_ISSOCK (st->stat.st_mode))
+ {
+ WARNOPT (WARN_FILE_IGNORED,
+ (0, 0, _("%s: socket ignored"), quotearg_colon (p)));
+ return;
+ }
+ else if (S_ISDOOR (st->stat.st_mode))
+ {
+ WARNOPT (WARN_FILE_IGNORED,
+ (0, 0, _("%s: door ignored"), quotearg_colon (p)));
+ return;
+ }
+ else
+ {
+ unknown_file_error (p);
+ return;
+ }
+
+ if (archive_format == V7_FORMAT)
+ {
+ unknown_file_error (p);
+ return;
+ }
+
+ block_ordinal = current_block_ordinal ();
+ st->stat.st_size = 0; /* force 0 size */
+ header = start_header (st);
+ if (!header)
+ return;
+ header->header.typeflag = type;
+
+ if (type != FIFOTYPE)
+ {
+ MAJOR_TO_CHARS (major (st->stat.st_rdev),
+ header->header.devmajor);
+ MINOR_TO_CHARS (minor (st->stat.st_rdev),
+ header->header.devminor);
+ }
+
+ finish_header (st, header, block_ordinal);
+ if (remove_files_option)
+ queue_deferred_unlink (p, false);
+}
+
+/* Dump a file, recursively. PARENT describes the file's parent
+ directory, NAME is the file's name relative to PARENT, and FULLNAME
+ its full name, possibly relative to the working directory. NAME
+ may contain slashes at the top level of invocation. */
+
+void
+dump_file (struct tar_stat_info *parent, char const *name,
+ char const *fullname)
+{
+ struct tar_stat_info st;
+ tar_stat_init (&st);
+ st.parent = parent;
+ dump_file0 (&st, name, fullname);
+ if (parent && listed_incremental_option)
+ update_parent_directory (parent);
+ tar_stat_destroy (&st);
+}
diff --git a/src/delete.c b/src/delete.c
new file mode 100644
index 0000000..93e8d8d
--- /dev/null
+++ b/src/delete.c
@@ -0,0 +1,394 @@
+/* Delete entries from a tar archive.
+
+ Copyright 1988, 1992, 1994, 1996-1997, 2000-2001, 2003-2006, 2010,
+ 2013-2014, 2016 Free Software Foundation, Inc.
+
+ This file is part of GNU tar.
+
+ GNU tar 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.
+
+ GNU tar is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <system.h>
+#include <system-ioctl.h>
+
+#include "common.h"
+#include <rmt.h>
+
+static union block *new_record;
+static int new_blocks;
+static bool acting_as_filter;
+
+/* FIXME: This module should not directly handle the following
+ variables, instead, the interface should be cleaned up. */
+extern union block *record_start;
+extern union block *record_end;
+extern union block *current_block;
+extern union block *recent_long_name;
+extern union block *recent_long_link;
+extern off_t records_read;
+
+/* The number of records skipped at the start of the archive, when
+ passing over members that are not deleted. */
+off_t records_skipped;
+
+/* Move archive descriptor by COUNT records worth. If COUNT is
+ positive we move forward, else we move negative. If it's a tape,
+ MTIOCTOP had better work. If it's something else, we try to seek
+ on it. If we can't seek, we lose! */
+static void
+move_archive (off_t count)
+{
+ if (count == 0)
+ return;
+
+#ifdef MTIOCTOP
+ {
+ struct mtop operation;
+
+ if (count < 0
+ ? (operation.mt_op = MTBSR,
+ operation.mt_count = -count,
+ operation.mt_count == -count)
+ : (operation.mt_op = MTFSR,
+ operation.mt_count = count,
+ operation.mt_count == count))
+ {
+ if (0 <= rmtioctl (archive, MTIOCTOP, (char *) &operation))
+ return;
+
+ if (errno == EIO
+ && 0 <= rmtioctl (archive, MTIOCTOP, (char *) &operation))
+ return;
+ }
+ }
+#endif /* MTIOCTOP */
+
+ {
+ off_t position0 = rmtlseek (archive, (off_t) 0, SEEK_CUR);
+ off_t increment = record_size * (off_t) count;
+ off_t position = position0 + increment;
+
+ if (increment / count != record_size
+ || (position < position0) != (increment < 0)
+ || (position = position < 0 ? 0 : position,
+ rmtlseek (archive, position, SEEK_SET) != position))
+ seek_error_details (archive_name_array[0], position);
+
+ return;
+ }
+}
+
+/* Write out the record which has been filled. If MOVE_BACK_FLAG,
+ backspace to where we started. */
+static void
+write_record (int move_back_flag)
+{
+ union block *save_record = record_start;
+ record_start = new_record;
+
+ if (acting_as_filter)
+ {
+ archive = STDOUT_FILENO;
+ flush_write ();
+ archive = STDIN_FILENO;
+ }
+ else
+ {
+ move_archive ((records_written + records_skipped) - records_read);
+ flush_write ();
+ }
+
+ record_start = save_record;
+
+ if (move_back_flag)
+ {
+ /* Move the tape head back to where we were. */
+
+ if (! acting_as_filter)
+ move_archive (records_read - (records_written + records_skipped));
+ }
+
+ new_blocks = 0;
+}
+
+static void
+write_recent_blocks (union block *h, size_t blocks)
+{
+ size_t i;
+ for (i = 0; i < blocks; i++)
+ {
+ new_record[new_blocks++] = h[i];
+ if (new_blocks == blocking_factor)
+ write_record (1);
+ }
+}
+
+static void
+write_recent_bytes (char *data, size_t bytes)
+{
+ size_t blocks = bytes / BLOCKSIZE;
+ size_t rest = bytes - blocks * BLOCKSIZE;
+
+ write_recent_blocks ((union block *)data, blocks);
+ memcpy (new_record[new_blocks].buffer, data + blocks * BLOCKSIZE, rest);
+ if (rest < BLOCKSIZE)
+ memset (new_record[new_blocks].buffer + rest, 0, BLOCKSIZE - rest);
+ new_blocks++;
+ if (new_blocks == blocking_factor)
+ write_record (1);
+}
+
+void
+delete_archive_members (void)
+{
+ enum read_header logical_status = HEADER_STILL_UNREAD;
+ enum read_header previous_status = HEADER_STILL_UNREAD;
+
+ /* FIXME: Should clean the routine before cleaning these variables :-( */
+ struct name *name;
+ off_t blocks_to_skip = 0;
+ off_t blocks_to_keep = 0;
+ int kept_blocks_in_record;
+
+ name_gather ();
+ open_archive (ACCESS_UPDATE);
+ acting_as_filter = strcmp (archive_name_array[0], "-") == 0;
+
+ do
+ {
+ enum read_header status = read_header (&current_header,
+ &current_stat_info,
+ read_header_x_raw);
+
+ switch (status)
+ {
+ case HEADER_STILL_UNREAD:
+ abort ();
+
+ case HEADER_SUCCESS:
+ if ((name = name_scan (current_stat_info.file_name)) == NULL)
+ {
+ skip_member ();
+ break;
+ }
+ name->found_count++;
+ if (!ISFOUND(name))
+ {
+ skip_member ();
+ break;
+ }
+
+ /* Fall through. */
+ case HEADER_SUCCESS_EXTENDED:
+ logical_status = status;
+ break;
+
+ case HEADER_ZERO_BLOCK:
+ if (ignore_zeros_option)
+ {
+ set_next_block_after (current_header);
+ break;
+ }
+ /* Fall through. */
+ case HEADER_END_OF_FILE:
+ logical_status = HEADER_END_OF_FILE;
+ break;
+
+ case HEADER_FAILURE:
+ set_next_block_after (current_header);
+ switch (previous_status)
+ {
+ case HEADER_STILL_UNREAD:
+ WARN ((0, 0, _("This does not look like a tar archive")));
+ /* Fall through. */
+
+ case HEADER_SUCCESS:
+ case HEADER_SUCCESS_EXTENDED:
+ case HEADER_ZERO_BLOCK:
+ ERROR ((0, 0, _("Skipping to next header")));
+ /* Fall through. */
+
+ case HEADER_FAILURE:
+ break;
+
+ case HEADER_END_OF_FILE:
+ abort ();
+ }
+ break;
+ }
+
+ previous_status = status;
+ }
+ while (logical_status == HEADER_STILL_UNREAD);
+
+ records_skipped = records_read - 1;
+ new_record = xmalloc (record_size);
+
+ if (logical_status == HEADER_SUCCESS
+ || logical_status == HEADER_SUCCESS_EXTENDED)
+ {
+ write_archive_to_stdout = false;
+
+ /* Save away blocks before this one in this record. */
+
+ new_blocks = current_block - record_start;
+ if (new_blocks)
+ memcpy (new_record, record_start, new_blocks * BLOCKSIZE);
+
+ if (logical_status == HEADER_SUCCESS)
+ {
+ /* FIXME: Pheew! This is crufty code! */
+ logical_status = HEADER_STILL_UNREAD;
+ goto flush_file;
+ }
+
+ /* FIXME: Solaris 2.4 Sun cc (the ANSI one, not the old K&R) says:
+ "delete.c", line 223: warning: loop not entered at top
+ Reported by Bruno Haible. */
+ while (1)
+ {
+ enum read_header status;
+
+ /* Fill in a record. */
+
+ if (current_block == record_end)
+ flush_archive ();
+ status = read_header (&current_header, &current_stat_info,
+ read_header_auto);
+
+ xheader_decode (&current_stat_info);
+
+ if (status == HEADER_ZERO_BLOCK && ignore_zeros_option)
+ {
+ set_next_block_after (current_header);
+ continue;
+ }
+ if (status == HEADER_END_OF_FILE || status == HEADER_ZERO_BLOCK)
+ {
+ logical_status = HEADER_END_OF_FILE;
+ break;
+ }
+
+ if (status == HEADER_FAILURE)
+ {
+ ERROR ((0, 0, _("Deleting non-header from archive")));
+ set_next_block_after (current_header);
+ continue;
+ }
+
+ /* Found another header. */
+
+ if ((name = name_scan (current_stat_info.file_name)) != NULL)
+ {
+ name->found_count++;
+ if (ISFOUND(name))
+ {
+ flush_file:
+ set_next_block_after (current_header);
+ blocks_to_skip = (current_stat_info.stat.st_size
+ + BLOCKSIZE - 1) / BLOCKSIZE;
+
+ while (record_end - current_block <= blocks_to_skip)
+ {
+ blocks_to_skip -= (record_end - current_block);
+ flush_archive ();
+ }
+ current_block += blocks_to_skip;
+ blocks_to_skip = 0;
+ continue;
+ }
+ }
+ /* Copy header. */
+
+ if (current_stat_info.xhdr.size)
+ {
+ write_recent_bytes (current_stat_info.xhdr.buffer,
+ current_stat_info.xhdr.size);
+ }
+ else
+ {
+ write_recent_blocks (recent_long_name, recent_long_name_blocks);
+ write_recent_blocks (recent_long_link, recent_long_link_blocks);
+ }
+ new_record[new_blocks] = *current_header;
+ new_blocks++;
+ blocks_to_keep
+ = (current_stat_info.stat.st_size + BLOCKSIZE - 1) / BLOCKSIZE;
+ set_next_block_after (current_header);
+ if (new_blocks == blocking_factor)
+ write_record (1);
+
+ /* Copy data. */
+
+ kept_blocks_in_record = record_end - current_block;
+ if (kept_blocks_in_record > blocks_to_keep)
+ kept_blocks_in_record = blocks_to_keep;
+
+ while (blocks_to_keep)
+ {
+ int count;
+
+ if (current_block == record_end)
+ {
+ flush_read ();
+ current_block = record_start;
+ kept_blocks_in_record = blocking_factor;
+ if (kept_blocks_in_record > blocks_to_keep)
+ kept_blocks_in_record = blocks_to_keep;
+ }
+ count = kept_blocks_in_record;
+ if (blocking_factor - new_blocks < count)
+ count = blocking_factor - new_blocks;
+
+ if (! count)
+ abort ();
+
+ memcpy (new_record + new_blocks, current_block, count * BLOCKSIZE);
+ new_blocks += count;
+ current_block += count;
+ blocks_to_keep -= count;
+ kept_blocks_in_record -= count;
+
+ if (new_blocks == blocking_factor)
+ write_record (1);
+ }
+ }
+
+ if (logical_status == HEADER_END_OF_FILE)
+ {
+ /* Write the end of tape. FIXME: we can't use write_eot here,
+ as it gets confused when the input is at end of file. */
+
+ int total_zero_blocks = 0;
+
+ do
+ {
+ int zero_blocks = blocking_factor - new_blocks;
+ memset (new_record + new_blocks, 0, BLOCKSIZE * zero_blocks);
+ total_zero_blocks += zero_blocks;
+ write_record (total_zero_blocks < 2);
+ }
+ while (total_zero_blocks < 2);
+ }
+
+ if (! acting_as_filter && ! _isrmt (archive))
+ {
+ if (sys_truncate (archive))
+ truncate_warn (archive_name_array[0]);
+ }
+ }
+ free (new_record);
+
+ close_archive ();
+ names_notfound ();
+}
diff --git a/src/exclist.c b/src/exclist.c
new file mode 100644
index 0000000..f6e8853
--- /dev/null
+++ b/src/exclist.c
@@ -0,0 +1,329 @@
+/* Per-directory exclusion files for tar.
+
+ Copyright 2014, 2016 Free Software Foundation, Inc.
+
+ This file is part of GNU tar.
+
+ GNU tar 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.
+
+ GNU tar is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <system.h>
+#include <quotearg.h>
+#include <fnmatch.h>
+#include <wordsplit.h>
+#include "common.h"
+
+typedef void (*add_fn) (struct exclude *, char const *, int, void *);
+
+struct vcs_ignore_file
+{
+ char const *filename;
+ int flags;
+ add_fn addfn;
+ void *(*initfn) (void *);
+ void *data;
+};
+
+static struct vcs_ignore_file *get_vcs_ignore_file (const char *name);
+
+struct excfile
+{
+ struct excfile *next;
+ int flags;
+ char name[1];
+};
+
+static struct excfile *excfile_head, *excfile_tail;
+
+void
+excfile_add (const char *name, int flags)
+{
+ struct excfile *p = xmalloc (sizeof (*p) + strlen (name));
+ p->next = NULL;
+ p->flags = flags;
+ strcpy (p->name, name);
+ if (excfile_tail)
+ excfile_tail->next = p;
+ else
+ excfile_head = p;
+ excfile_tail = p;
+}
+
+struct exclist
+{
+ struct exclist *next, *prev;
+ int flags;
+ struct exclude *excluded;
+};
+
+void
+info_attach_exclist (struct tar_stat_info *dir)
+{
+ struct excfile *file;
+ struct exclist *head = NULL, *tail = NULL, *ent;
+ struct vcs_ignore_file *vcsfile;
+
+ if (dir->exclude_list)
+ return;
+ for (file = excfile_head; file; file = file->next)
+ {
+ if (faccessat (dir ? dir->fd : chdir_fd, file->name, F_OK, 0) == 0)
+ {
+ FILE *fp;
+ struct exclude *ex = NULL;
+ int fd = subfile_open (dir, file->name, O_RDONLY);
+ if (fd == -1)
+ {
+ open_error (file->name);
+ continue;
+ }
+ fp = fdopen (fd, "r");
+ if (!fp)
+ {
+ ERROR ((0, errno, _("%s: fdopen failed"), file->name));
+ close (fd);
+ continue;
+ }
+
+ if (!ex)
+ ex = new_exclude ();
+
+ vcsfile = get_vcs_ignore_file (file->name);
+
+ if (vcsfile->initfn)
+ vcsfile->data = vcsfile->initfn (vcsfile->data);
+
+ if (add_exclude_fp (vcsfile->addfn, ex, fp,
+ EXCLUDE_WILDCARDS|EXCLUDE_ANCHORED, '\n',
+ vcsfile->data))
+ {
+ int e = errno;
+ FATAL_ERROR ((0, e, "%s", quotearg_colon (file->name)));
+ }
+ fclose (fp);
+
+ ent = xmalloc (sizeof (*ent));
+ ent->excluded = ex;
+ ent->flags = file->flags == EXCL_DEFAULT
+ ? file->flags : vcsfile->flags;
+ ent->prev = tail;
+ ent->next = NULL;
+
+ if (tail)
+ tail->next = ent;
+ else
+ head = ent;
+ tail = ent;
+ }
+ }
+ dir->exclude_list = head;
+}
+
+void
+info_free_exclist (struct tar_stat_info *dir)
+{
+ struct exclist *ep = dir->exclude_list;
+
+ while (ep)
+ {
+ struct exclist *next = ep->next;
+ free_exclude (ep->excluded);
+ free (ep);
+ ep = next;
+ }
+
+ dir->exclude_list = NULL;
+}
+
+
+/* Return nonzero if file NAME is excluded. */
+bool
+excluded_name (char const *name, struct tar_stat_info *st)
+{
+ struct exclist *ep;
+ const char *rname = NULL;
+ char *bname = NULL;
+ bool result;
+ int nr = 0;
+
+ name += FILE_SYSTEM_PREFIX_LEN (name);
+
+ /* Try global exclusion list first */
+ if (excluded_file_name (excluded, name))
+ return true;
+
+ if (!st)
+ return false;
+
+ for (result = false; st && !result; st = st->parent, nr = EXCL_NON_RECURSIVE)
+ {
+ for (ep = st->exclude_list; ep; ep = ep->next)
+ {
+ if (ep->flags & nr)
+ continue;
+ if ((result = excluded_file_name (ep->excluded, name)))
+ break;
+
+ if (!rname)
+ {
+ rname = name;
+ /* Skip leading ./ */
+ while (*rname == '.' && ISSLASH (rname[1]))
+ rname += 2;
+ }
+ if ((result = excluded_file_name (ep->excluded, rname)))
+ break;
+
+ if (!bname)
+ bname = base_name (name);
+ if ((result = excluded_file_name (ep->excluded, bname)))
+ break;
+ }
+ }
+
+ free (bname);
+
+ return result;
+}
+
+static void
+cvs_addfn (struct exclude *ex, char const *pattern, int options, void *data)
+{
+ struct wordsplit ws;
+ size_t i;
+
+ if (wordsplit (pattern, &ws,
+ WRDSF_NOVAR | WRDSF_NOCMD | WRDSF_SQUEEZE_DELIMS))
+ return;
+ for (i = 0; i < ws.ws_wordc; i++)
+ add_exclude (ex, ws.ws_wordv[i], options);
+ wordsplit_free (&ws);
+}
+
+static void
+git_addfn (struct exclude *ex, char const *pattern, int options, void *data)
+{
+ while (isspace (*pattern))
+ ++pattern;
+ if (*pattern == 0 || *pattern == '#')
+ return;
+ if (*pattern == '\\' && pattern[1] == '#')
+ ++pattern;
+ add_exclude (ex, pattern, options);
+}
+
+static void
+bzr_addfn (struct exclude *ex, char const *pattern, int options, void *data)
+{
+ while (isspace (*pattern))
+ ++pattern;
+ if (*pattern == 0 || *pattern == '#')
+ return;
+ if (*pattern == '!')
+ {
+ if (*++pattern == '!')
+ ++pattern;
+ else
+ options |= EXCLUDE_INCLUDE;
+ }
+ /* FIXME: According to the docs, globbing patterns are rsync-style,
+ and regexps are perl-style. */
+ if (strncmp (pattern, "RE:", 3) == 0)
+ {
+ pattern += 3;
+ options &= ~EXCLUDE_WILDCARDS;
+ options |= EXCLUDE_REGEX;
+ }
+ add_exclude (ex, pattern, options);
+}
+
+static void *
+hg_initfn (void *data)
+{
+ static int hg_options;
+ int *hgopt = data ? data : &hg_options;
+ *hgopt = EXCLUDE_REGEX;
+ return hgopt;
+}
+
+static void
+hg_addfn (struct exclude *ex, char const *pattern, int options, void *data)
+{
+ int *hgopt = data;
+ size_t len;
+
+ while (isspace (*pattern))
+ ++pattern;
+ if (*pattern == 0 || *pattern == '#')
+ return;
+ if (strncmp (pattern, "syntax:", 7) == 0)
+ {
+ for (pattern += 7; isspace (*pattern); ++pattern)
+ ;
+ if (strcmp (pattern, "regexp") == 0)
+ /* FIXME: Regexps must be perl-style */
+ *hgopt = EXCLUDE_REGEX;
+ else if (strcmp (pattern, "glob") == 0)
+ *hgopt = EXCLUDE_WILDCARDS;
+ /* Ignore unknown syntax */
+ return;
+ }
+
+ len = strlen(pattern);
+ if (pattern[len-1] == '/')
+ {
+ char *p;
+
+ --len;
+ p = xmalloc (len+1);
+ memcpy (p, pattern, len);
+ p[len] = 0;
+ pattern = p;
+ exclude_add_pattern_buffer (ex, p);
+ options |= FNM_LEADING_DIR|EXCLUDE_ALLOC;
+ }
+
+ add_exclude (ex, pattern,
+ ((*hgopt == EXCLUDE_REGEX)
+ ? (options & ~EXCLUDE_WILDCARDS)
+ : (options & ~EXCLUDE_REGEX)) | *hgopt);
+}
+
+static struct vcs_ignore_file vcs_ignore_files[] = {
+ { ".cvsignore", EXCL_NON_RECURSIVE, cvs_addfn, NULL, NULL },
+ { ".gitignore", 0, git_addfn, NULL, NULL },
+ { ".bzrignore", 0, bzr_addfn, NULL, NULL },
+ { ".hgignore", 0, hg_addfn, hg_initfn, NULL },
+ { NULL, 0, git_addfn, NULL, NULL }
+};
+
+static struct vcs_ignore_file *
+get_vcs_ignore_file (const char *name)
+{
+ struct vcs_ignore_file *p;
+
+ for (p = vcs_ignore_files; p->filename; p++)
+ if (strcmp (p->filename, name) == 0)
+ break;
+
+ return p;
+}
+
+void
+exclude_vcs_ignores (void)
+{
+ struct vcs_ignore_file *p;
+
+ for (p = vcs_ignore_files; p->filename; p++)
+ excfile_add (p->filename, EXCL_DEFAULT);
+}
diff --git a/src/exit.c b/src/exit.c
new file mode 100644
index 0000000..17dc1ff
--- /dev/null
+++ b/src/exit.c
@@ -0,0 +1,39 @@
+/* Exit from GNU tar.
+
+ Copyright 2009, 2013-2014, 2016 Free Software Foundation, Inc.
+
+ This file is part of GNU tar.
+
+ GNU tar 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.
+
+ GNU tar is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <system.h>
+#include "common.h"
+
+void (*fatal_exit_hook) (void);
+
+void
+fatal_exit (void)
+{
+ if (fatal_exit_hook)
+ fatal_exit_hook ();
+ error (TAREXIT_FAILURE, 0, _("Error is not recoverable: exiting now"));
+ abort ();
+}
+
+void
+xalloc_die (void)
+{
+ error (0, 0, "%s", _("memory exhausted"));
+ fatal_exit ();
+}
diff --git a/src/extract.c b/src/extract.c
new file mode 100644
index 0000000..f982433
--- /dev/null
+++ b/src/extract.c
@@ -0,0 +1,1820 @@
+/* Extract files from a tar archive.
+
+ Copyright 1988, 1992-1994, 1996-2001, 2003-2007, 2010, 2012-2014,
+ 2016 Free Software Foundation, Inc.
+
+ This file is part of GNU tar.
+
+ GNU tar 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.
+
+ GNU tar 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/>.
+
+ Written by John Gilmore, on 1985-11-19. */
+
+#include <system.h>
+#include <quotearg.h>
+#include <errno.h>
+#include <priv-set.h>
+#include <root-uid.h>
+#include <utimens.h>
+
+#include "common.h"
+
+static bool we_are_root; /* true if our effective uid == 0 */
+static mode_t newdir_umask; /* umask when creating new directories */
+static mode_t current_umask; /* current umask (which is set to 0 if -p) */
+
+#define ALL_MODE_BITS ((mode_t) ~ (mode_t) 0)
+
+#if ! HAVE_FCHMOD && ! defined fchmod
+# define fchmod(fd, mode) (errno = ENOSYS, -1)
+#endif
+#if ! HAVE_FCHOWN && ! defined fchown
+# define fchown(fd, uid, gid) (errno = ENOSYS, -1)
+#endif
+
+/* Return true if an error number ERR means the system call is
+ supported in this case. */
+static bool
+implemented (int err)
+{
+ return ! (err == ENOSYS
+ || err == ENOTSUP
+ || (EOPNOTSUPP != ENOTSUP && err == EOPNOTSUPP));
+}
+
+/* List of directories whose statuses we need to extract after we've
+ finished extracting their subsidiary files. If you consider each
+ contiguous subsequence of elements of the form [D]?[^D]*, where [D]
+ represents an element where AFTER_LINKS is nonzero and [^D]
+ represents an element where AFTER_LINKS is zero, then the head
+ of the subsequence has the longest name, and each non-head element
+ in the prefix is an ancestor (in the directory hierarchy) of the
+ preceding element. */
+
+struct delayed_set_stat
+ {
+ /* Next directory in list. */
+ struct delayed_set_stat *next;
+
+ /* Metadata for this directory. */
+ dev_t dev;
+ ino_t ino;
+ mode_t mode; /* The desired mode is MODE & ~ current_umask. */
+ uid_t uid;
+ gid_t gid;
+ struct timespec atime;
+ struct timespec mtime;
+
+ /* An estimate of the directory's current mode, along with a mask
+ specifying which bits of this estimate are known to be correct.
+ If CURRENT_MODE_MASK is zero, CURRENT_MODE's value doesn't
+ matter. */
+ mode_t current_mode;
+ mode_t current_mode_mask;
+
+ /* This directory is an intermediate directory that was created
+ as an ancestor of some other directory; it was not mentioned
+ in the archive, so do not set its uid, gid, atime, or mtime,
+ and don't alter its mode outside of MODE_RWX. */
+ bool interdir;
+
+ /* Whether symbolic links should be followed when accessing the
+ directory. */
+ int atflag;
+
+ /* Do not set the status of this directory until after delayed
+ links are created. */
+ bool after_links;
+
+ /* Directory that the name is relative to. */
+ int change_dir;
+
+ /* extended attributes*/
+ char *cntx_name;
+ char *acls_a_ptr;
+ size_t acls_a_len;
+ char *acls_d_ptr;
+ size_t acls_d_len;
+ size_t xattr_map_size;
+ struct xattr_array *xattr_map;
+ /* Length and contents of name. */
+ size_t file_name_len;
+ char *file_name;
+ };
+
+static struct delayed_set_stat *delayed_set_stat_head;
+
+/* List of links whose creation we have delayed. */
+struct delayed_link
+ {
+ /* The next delayed link in the list. */
+ struct delayed_link *next;
+
+ /* The device, inode number and birthtime of the placeholder.
+ birthtime.tv_nsec is negative if the birthtime is not available.
+ Don't use mtime as this would allow for false matches if some
+ other process removes the placeholder. Don't use ctime as
+ this would cause race conditions and other screwups, e.g.,
+ when restoring hard-linked symlinks. */
+ dev_t dev;
+ ino_t ino;
+ struct timespec birthtime;
+
+ /* True if the link is symbolic. */
+ bool is_symlink;
+
+ /* The desired metadata, valid only the link is symbolic. */
+ mode_t mode;
+ uid_t uid;
+ gid_t gid;
+ struct timespec atime;
+ struct timespec mtime;
+
+ /* The directory that the sources and target are relative to. */
+ int change_dir;
+
+ /* A list of sources for this link. The sources are all to be
+ hard-linked together. */
+ struct string_list *sources;
+
+ /* SELinux context */
+ char *cntx_name;
+
+ /* ACLs */
+ char *acls_a_ptr;
+ size_t acls_a_len;
+ char *acls_d_ptr;
+ size_t acls_d_len;
+
+ size_t xattr_map_size;
+ struct xattr_array *xattr_map;
+
+ /* The desired target of the desired link. */
+ char target[1];
+ };
+
+static struct delayed_link *delayed_link_head;
+
+struct string_list
+ {
+ struct string_list *next;
+ char string[1];
+ };
+
+/* Set up to extract files. */
+void
+extr_init (void)
+{
+ we_are_root = geteuid () == ROOT_UID;
+ same_permissions_option += we_are_root;
+ same_owner_option += we_are_root;
+
+ /* Option -p clears the kernel umask, so it does not affect proper
+ restoration of file permissions. New intermediate directories will
+ comply with umask at start of program. */
+
+ newdir_umask = umask (0);
+ if (0 < same_permissions_option)
+ current_umask = 0;
+ else
+ {
+ umask (newdir_umask); /* restore the kernel umask */
+ current_umask = newdir_umask;
+ }
+}
+
+/* Use fchmod if possible, fchmodat otherwise. */
+static int
+fd_chmod (int fd, char const *file, mode_t mode, int atflag)
+{
+ if (0 <= fd)
+ {
+ int result = fchmod (fd, mode);
+ if (result == 0 || implemented (errno))
+ return result;
+ }
+ return fchmodat (chdir_fd, file, mode, atflag);
+}
+
+/* Use fchown if possible, fchownat otherwise. */
+static int
+fd_chown (int fd, char const *file, uid_t uid, gid_t gid, int atflag)
+{
+ if (0 <= fd)
+ {
+ int result = fchown (fd, uid, gid);
+ if (result == 0 || implemented (errno))
+ return result;
+ }
+ return fchownat (chdir_fd, file, uid, gid, atflag);
+}
+
+/* Use fstat if possible, fstatat otherwise. */
+static int
+fd_stat (int fd, char const *file, struct stat *st, int atflag)
+{
+ return (0 <= fd
+ ? fstat (fd, st)
+ : fstatat (chdir_fd, file, st, atflag));
+}
+
+/* Set the mode for FILE_NAME to MODE.
+ MODE_MASK specifies the bits of MODE that we care about;
+ thus if MODE_MASK is zero, do nothing.
+ If FD is nonnegative, it is a file descriptor for the file.
+ CURRENT_MODE and CURRENT_MODE_MASK specify information known about
+ the file's current mode, using the style of struct delayed_set_stat.
+ TYPEFLAG specifies the type of the file.
+ ATFLAG specifies the flag to use when statting the file. */
+static void
+set_mode (char const *file_name,
+ mode_t mode, mode_t mode_mask, int fd,
+ mode_t current_mode, mode_t current_mode_mask,
+ char typeflag, int atflag)
+{
+ if (((current_mode ^ mode) | ~ current_mode_mask) & mode_mask)
+ {
+ if (MODE_ALL & ~ mode_mask & ~ current_mode_mask)
+ {
+ struct stat st;
+ if (fd_stat (fd, file_name, &st, atflag) != 0)
+ {
+ stat_error (file_name);
+ return;
+ }
+ current_mode = st.st_mode;
+ }
+
+ current_mode &= MODE_ALL;
+ mode = (current_mode & ~ mode_mask) | (mode & mode_mask);
+
+ if (current_mode != mode)
+ {
+ int chmod_errno =
+ fd_chmod (fd, file_name, mode, atflag) == 0 ? 0 : errno;
+
+ /* On Solaris, chmod may fail if we don't have PRIV_ALL, because
+ setuid-root files would otherwise be a backdoor. See
+ http://opensolaris.org/jive/thread.jspa?threadID=95826
+ (2009-09-03). */
+ if (chmod_errno == EPERM && (mode & S_ISUID)
+ && priv_set_restore_linkdir () == 0)
+ {
+ chmod_errno =
+ fd_chmod (fd, file_name, mode, atflag) == 0 ? 0 : errno;
+ priv_set_remove_linkdir ();
+ }
+
+ /* Linux fchmodat does not support AT_SYMLINK_NOFOLLOW, and
+ returns ENOTSUP even when operating on non-symlinks, try
+ again with the flag disabled if it does not appear to be
+ supported and if the file is not a symlink. This
+ introduces a race, alas. */
+ if (atflag && typeflag != SYMTYPE && ! implemented (chmod_errno))
+ chmod_errno = fd_chmod (fd, file_name, mode, 0) == 0 ? 0 : errno;
+
+ if (chmod_errno
+ && (typeflag != SYMTYPE || implemented (chmod_errno)))
+ {
+ errno = chmod_errno;
+ chmod_error_details (file_name, mode);
+ }
+ }
+ }
+}
+
+/* Check time after successfully setting FILE_NAME's time stamp to T. */
+static void
+check_time (char const *file_name, struct timespec t)
+{
+ if (t.tv_sec < 0)
+ WARNOPT (WARN_TIMESTAMP,
+ (0, 0, _("%s: implausibly old time stamp %s"),
+ file_name, tartime (t, true)));
+ else if (timespec_cmp (volume_start_time, t) < 0)
+ {
+ struct timespec now;
+ gettime (&now);
+ if (timespec_cmp (now, t) < 0)
+ {
+ char buf[TIMESPEC_STRSIZE_BOUND];
+ struct timespec diff;
+ diff.tv_sec = t.tv_sec - now.tv_sec;
+ diff.tv_nsec = t.tv_nsec - now.tv_nsec;
+ if (diff.tv_nsec < 0)
+ {
+ diff.tv_nsec += BILLION;
+ diff.tv_sec--;
+ }
+ WARNOPT (WARN_TIMESTAMP,
+ (0, 0, _("%s: time stamp %s is %s s in the future"),
+ file_name, tartime (t, true), code_timespec (diff, buf)));
+ }
+ }
+}
+
+/* Restore stat attributes (owner, group, mode and times) for
+ FILE_NAME, using information given in *ST.
+ If FD is nonnegative, it is a file descriptor for the file.
+ CURRENT_MODE and CURRENT_MODE_MASK specify information known about
+ the file's current mode, using the style of struct delayed_set_stat.
+ TYPEFLAG specifies the type of the file.
+ If INTERDIR, this is an intermediate directory.
+ ATFLAG specifies the flag to use when statting the file. */
+
+static void
+set_stat (char const *file_name,
+ struct tar_stat_info const *st,
+ int fd, mode_t current_mode, mode_t current_mode_mask,
+ char typeflag, bool interdir, int atflag)
+{
+ /* Do the utime before the chmod because some versions of utime are
+ broken and trash the modes of the file. */
+
+ if (! touch_option && ! interdir)
+ {
+ struct timespec ts[2];
+ if (incremental_option)
+ ts[0] = st->atime;
+ else
+ ts[0].tv_nsec = UTIME_OMIT;
+ ts[1] = st->mtime;
+
+ if (fdutimensat (fd, chdir_fd, file_name, ts, atflag) == 0)
+ {
+ if (incremental_option)
+ check_time (file_name, ts[0]);
+ check_time (file_name, ts[1]);
+ }
+ else if (typeflag != SYMTYPE || implemented (errno))
+ utime_error (file_name);
+ }
+
+ if (0 < same_owner_option && ! interdir)
+ {
+ /* Some systems allow non-root users to give files away. Once this
+ done, it is not possible anymore to change file permissions.
+ However, setting file permissions now would be incorrect, since
+ they would apply to the wrong user, and there would be a race
+ condition. So, don't use systems that allow non-root users to
+ give files away. */
+ uid_t uid = st->stat.st_uid;
+ gid_t gid = st->stat.st_gid;
+
+ if (fd_chown (fd, file_name, uid, gid, atflag) == 0)
+ {
+ /* Changing the owner can clear st_mode bits in some cases. */
+ if ((current_mode | ~ current_mode_mask) & S_IXUGO)
+ current_mode_mask &= ~ (current_mode & (S_ISUID | S_ISGID));
+ }
+ else if (typeflag != SYMTYPE || implemented (errno))
+ chown_error_details (file_name, uid, gid);
+ }
+
+ set_mode (file_name,
+ st->stat.st_mode & ~ current_umask,
+ 0 < same_permissions_option && ! interdir ? MODE_ALL : MODE_RWX,
+ fd, current_mode, current_mode_mask, typeflag, atflag);
+
+ /* these three calls must be done *after* fd_chown() call because fd_chown
+ causes that linux capabilities becomes cleared. */
+ xattrs_xattrs_set (st, file_name, typeflag, 1);
+ xattrs_acls_set (st, file_name, typeflag);
+ xattrs_selinux_set (st, file_name, typeflag);
+}
+
+/* For each entry H in the leading prefix of entries in HEAD that do
+ not have after_links marked, mark H and fill in its dev and ino
+ members. Assume HEAD && ! HEAD->after_links. */
+static void
+mark_after_links (struct delayed_set_stat *head)
+{
+ struct delayed_set_stat *h = head;
+
+ do
+ {
+ struct stat st;
+ h->after_links = 1;
+
+ if (deref_stat (h->file_name, &st) != 0)
+ stat_error (h->file_name);
+ else
+ {
+ h->dev = st.st_dev;
+ h->ino = st.st_ino;
+ }
+ }
+ while ((h = h->next) && ! h->after_links);
+}
+
+/* Remember to restore stat attributes (owner, group, mode and times)
+ for the directory FILE_NAME, using information given in *ST,
+ once we stop extracting files into that directory.
+
+ If ST is null, merely create a placeholder node for an intermediate
+ directory that was created by make_directories.
+
+ NOTICE: this works only if the archive has usual member order, i.e.
+ directory, then the files in that directory. Incremental archive have
+ somewhat reversed order: first go subdirectories, then all other
+ members. To help cope with this case the variable
+ delay_directory_restore_option is set by prepare_to_extract.
+
+ If an archive was explicitely created so that its member order is
+ reversed, some directory timestamps can be restored incorrectly,
+ e.g.:
+ tar --no-recursion -cf archive dir dir/file1 foo dir/file2
+*/
+static void
+delay_set_stat (char const *file_name, struct tar_stat_info const *st,
+ mode_t current_mode, mode_t current_mode_mask,
+ mode_t mode, int atflag)
+{
+ size_t file_name_len = strlen (file_name);
+ struct delayed_set_stat *data = xmalloc (sizeof (*data));
+ data->next = delayed_set_stat_head;
+ data->mode = mode;
+ if (st)
+ {
+ data->dev = st->stat.st_dev;
+ data->ino = st->stat.st_ino;
+ data->uid = st->stat.st_uid;
+ data->gid = st->stat.st_gid;
+ data->atime = st->atime;
+ data->mtime = st->mtime;
+ }
+ data->file_name_len = file_name_len;
+ data->file_name = xstrdup (file_name);
+ data->current_mode = current_mode;
+ data->current_mode_mask = current_mode_mask;
+ data->interdir = ! st;
+ data->atflag = atflag;
+ data->after_links = 0;
+ data->change_dir = chdir_current;
+ data->cntx_name = NULL;
+ if (st)
+ assign_string (&data->cntx_name, st->cntx_name);
+ if (st && st->acls_a_ptr)
+ {
+ data->acls_a_ptr = xmemdup (st->acls_a_ptr, st->acls_a_len + 1);
+ data->acls_a_len = st->acls_a_len;
+ }
+ else
+ {
+ data->acls_a_ptr = NULL;
+ data->acls_a_len = 0;
+ }
+ if (st && st->acls_d_ptr)
+ {
+ data->acls_d_ptr = xmemdup (st->acls_d_ptr, st->acls_d_len + 1);
+ data->acls_d_len = st->acls_d_len;
+ }
+ else
+ {
+ data->acls_d_ptr = NULL;
+ data->acls_d_len = 0;
+ }
+ if (st)
+ xheader_xattr_copy (st, &data->xattr_map, &data->xattr_map_size);
+ else
+ {
+ data->xattr_map = NULL;
+ data->xattr_map_size = 0;
+ }
+ strcpy (data->file_name, file_name);
+ delayed_set_stat_head = data;
+ if (must_be_dot_or_slash (file_name))
+ mark_after_links (data);
+}
+
+/* Update the delayed_set_stat info for an intermediate directory
+ created within the file name of DIR. The intermediate directory turned
+ out to be the same as this directory, e.g. due to ".." or symbolic
+ links. *DIR_STAT_INFO is the status of the directory. */
+static void
+repair_delayed_set_stat (char const *dir,
+ struct stat const *dir_stat_info)
+{
+ struct delayed_set_stat *data;
+ for (data = delayed_set_stat_head; data; data = data->next)
+ {
+ struct stat st;
+ if (fstatat (chdir_fd, data->file_name, &st, data->atflag) != 0)
+ {
+ stat_error (data->file_name);
+ return;
+ }
+
+ if (st.st_dev == dir_stat_info->st_dev
+ && st.st_ino == dir_stat_info->st_ino)
+ {
+ data->dev = current_stat_info.stat.st_dev;
+ data->ino = current_stat_info.stat.st_ino;
+ data->mode = current_stat_info.stat.st_mode;
+ data->uid = current_stat_info.stat.st_uid;
+ data->gid = current_stat_info.stat.st_gid;
+ data->atime = current_stat_info.atime;
+ data->mtime = current_stat_info.mtime;
+ data->current_mode = st.st_mode;
+ data->current_mode_mask = ALL_MODE_BITS;
+ data->interdir = false;
+ return;
+ }
+ }
+
+ ERROR ((0, 0, _("%s: Unexpected inconsistency when making directory"),
+ quotearg_colon (dir)));
+}
+
+static void
+free_delayed_set_stat (struct delayed_set_stat *data)
+{
+ free (data->file_name);
+ xheader_xattr_free (data->xattr_map, data->xattr_map_size);
+ free (data->cntx_name);
+ free (data->acls_a_ptr);
+ free (data->acls_d_ptr);
+ free (data);
+}
+
+void
+remove_delayed_set_stat (const char *fname)
+{
+ struct delayed_set_stat *data, *next, *prev = NULL;
+ for (data = delayed_set_stat_head; data; data = next)
+ {
+ next = data->next;
+ if (chdir_current == data->change_dir
+ && strcmp (data->file_name, fname) == 0)
+ {
+ free_delayed_set_stat (data);
+ if (prev)
+ prev->next = next;
+ else
+ delayed_set_stat_head = next;
+ return;
+ }
+ else
+ prev = data;
+ }
+}
+
+static void
+fixup_delayed_set_stat (char const *src, char const *dst)
+{
+ struct delayed_set_stat *data;
+ for (data = delayed_set_stat_head; data; data = data->next)
+ {
+ if (chdir_current == data->change_dir
+ && strcmp (data->file_name, src) == 0)
+ {
+ free (data->file_name);
+ data->file_name = xstrdup (dst);
+ data->file_name_len = strlen (dst);
+ return;
+ }
+ }
+}
+
+/* After a file/link/directory creation has failed, see if
+ it's because some required directory was not present, and if so,
+ create all required directories. Return zero if all the required
+ directories were created, nonzero (issuing a diagnostic) otherwise.
+ Set *INTERDIR_MADE if at least one directory was created. */
+static int
+make_directories (char *file_name, bool *interdir_made)
+{
+ char *cursor0 = file_name + FILE_SYSTEM_PREFIX_LEN (file_name);
+ char *cursor; /* points into the file name */
+
+ for (cursor = cursor0; *cursor; cursor++)
+ {
+ mode_t mode;
+ mode_t desired_mode;
+ int status;
+
+ if (! ISSLASH (*cursor))
+ continue;
+
+ /* Avoid mkdir of empty string, if leading or double '/'. */
+
+ if (cursor == cursor0 || ISSLASH (cursor[-1]))
+ continue;
+
+ /* Avoid mkdir where last part of file name is "." or "..". */
+
+ if (cursor[-1] == '.'
+ && (cursor == cursor0 + 1 || ISSLASH (cursor[-2])
+ || (cursor[-2] == '.'
+ && (cursor == cursor0 + 2 || ISSLASH (cursor[-3])))))
+ continue;
+
+ *cursor = '\0'; /* truncate the name there */
+ desired_mode = MODE_RWX & ~ newdir_umask;
+ mode = desired_mode | (we_are_root ? 0 : MODE_WXUSR);
+ status = mkdirat (chdir_fd, file_name, mode);
+
+ if (status == 0)
+ {
+ /* Create a struct delayed_set_stat even if
+ mode == desired_mode, because
+ repair_delayed_set_stat may need to update the struct. */
+ delay_set_stat (file_name,
+ 0, mode & ~ current_umask, MODE_RWX,
+ desired_mode, AT_SYMLINK_NOFOLLOW);
+
+ print_for_mkdir (file_name, cursor - file_name, desired_mode);
+ *interdir_made = true;
+ }
+ else if (errno == EEXIST)
+ status = 0;
+ else
+ {
+ /* Check whether the desired file exists. Even when the
+ file exists, mkdir can fail with some errno value E other
+ than EEXIST, so long as E describes an error condition
+ that also applies. */
+ int e = errno;
+ struct stat st;
+ status = fstatat (chdir_fd, file_name, &st, 0);
+ if (status)
+ {
+ errno = e;
+ mkdir_error (file_name);
+ }
+ }
+
+ *cursor = '/';
+ if (status)
+ return status;
+ }
+
+ return 0;
+}
+
+/* Return true if FILE_NAME (with status *STP, if STP) is not a
+ directory, and has a time stamp newer than (or equal to) that of
+ TAR_STAT. */
+static bool
+file_newer_p (const char *file_name, struct stat const *stp,
+ struct tar_stat_info *tar_stat)
+{
+ struct stat st;
+
+ if (!stp)
+ {
+ if (deref_stat (file_name, &st) != 0)
+ {
+ if (errno != ENOENT)
+ {
+ stat_warn (file_name);
+ /* Be safer: if the file exists, assume it is newer. */
+ return true;
+ }
+ return false;
+ }
+ stp = &st;
+ }
+
+ return (! S_ISDIR (stp->st_mode)
+ && tar_timespec_cmp (tar_stat->mtime, get_stat_mtime (stp)) <= 0);
+}
+
+#define RECOVER_NO 0
+#define RECOVER_OK 1
+#define RECOVER_SKIP 2
+
+/* Attempt repairing what went wrong with the extraction. Delete an
+ already existing file or create missing intermediate directories.
+ Return RECOVER_OK if we somewhat increased our chances at a successful
+ extraction, RECOVER_NO if there are no chances, and RECOVER_SKIP if the
+ caller should skip extraction of that member. The value of errno is
+ properly restored on returning RECOVER_NO.
+
+ If REGULAR, the caller was trying to extract onto a regular file.
+
+ Set *INTERDIR_MADE if an intermediate directory is made as part of
+ the recovery process. */
+
+static int
+maybe_recoverable (char *file_name, bool regular, bool *interdir_made)
+{
+ int e = errno;
+ struct stat st;
+ struct stat const *stp = 0;
+
+ if (*interdir_made)
+ return RECOVER_NO;
+
+ switch (e)
+ {
+ case ELOOP:
+
+ /* With open ("symlink", O_NOFOLLOW|...), POSIX says errno == ELOOP,
+ but some operating systems do not conform to the standard. */
+#ifdef EFTYPE
+ /* NetBSD uses errno == EFTYPE; see <http://gnats.netbsd.org/43154>. */
+ case EFTYPE:
+#endif
+ /* FreeBSD 8.1 uses errno == EMLINK. */
+ case EMLINK:
+ /* Tru64 5.1B uses errno == ENOTSUP. */
+ case ENOTSUP:
+
+ if (! regular
+ || old_files_option != OVERWRITE_OLD_FILES || dereference_option)
+ break;
+ if (strchr (file_name, '/'))
+ {
+ if (deref_stat (file_name, &st) != 0)
+ break;
+ stp = &st;
+ }
+
+ /* The caller tried to open a symbolic link with O_NOFOLLOW.
+ Fall through, treating it as an already-existing file. */
+
+ case EEXIST:
+ /* Remove an old file, if the options allow this. */
+
+ switch (old_files_option)
+ {
+ case SKIP_OLD_FILES:
+ WARNOPT (WARN_EXISTING_FILE,
+ (0, 0, _("%s: skipping existing file"), file_name));
+ return RECOVER_SKIP;
+
+ case KEEP_OLD_FILES:
+ return RECOVER_NO;
+
+ case KEEP_NEWER_FILES:
+ if (file_newer_p (file_name, stp, &current_stat_info))
+ break;
+ /* FALL THROUGH */
+
+ case DEFAULT_OLD_FILES:
+ case NO_OVERWRITE_DIR_OLD_FILES:
+ case OVERWRITE_OLD_FILES:
+ if (0 < remove_any_file (file_name, ORDINARY_REMOVE_OPTION))
+ return RECOVER_OK;
+ break;
+
+ case UNLINK_FIRST_OLD_FILES:
+ break;
+ }
+
+ case ENOENT:
+ /* Attempt creating missing intermediate directories. */
+ if (make_directories (file_name, interdir_made) == 0 && *interdir_made)
+ return RECOVER_OK;
+ break;
+
+ default:
+ /* Just say we can't do anything about it... */
+ break;
+ }
+
+ errno = e;
+ return RECOVER_NO;
+}
+
+/* Restore stat extended attributes (xattr) for FILE_NAME, using information
+ given in *ST. Restore before extraction because they may affect file layout
+ (e.g. on Lustre distributed parallel filesystem - setting info about how many
+ servers is this file striped over, stripe size, mirror copies, etc.
+ in advance dramatically improves the following performance of reading and
+ writing a file). If not restoring permissions, invert the INVERT_PERMISSIONS
+ bits from the file's current permissions. TYPEFLAG specifies the type of the
+ file. FILE_CREATED indicates set_xattr has created the file */
+static int
+set_xattr (char const *file_name, struct tar_stat_info const *st,
+ mode_t invert_permissions, char typeflag, int *file_created)
+{
+ int status = 0;
+
+#ifdef HAVE_XATTRS
+ bool interdir_made = false;
+
+ if ((xattrs_option > 0) && st->xattr_map_size)
+ {
+ mode_t mode = current_stat_info.stat.st_mode & MODE_RWX & ~ current_umask;
+
+ do
+ status = mknodat (chdir_fd, file_name, mode ^ invert_permissions, 0);
+ while (status && maybe_recoverable ((char *)file_name, false,
+ &interdir_made));
+
+ xattrs_xattrs_set (st, file_name, typeflag, 0);
+ *file_created = 1;
+ }
+#endif
+
+ return(status);
+}
+
+/* Fix the statuses of all directories whose statuses need fixing, and
+ which are not ancestors of FILE_NAME. If AFTER_LINKS is
+ nonzero, do this for all such directories; otherwise, stop at the
+ first directory that is marked to be fixed up only after delayed
+ links are applied. */
+static void
+apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links)
+{
+ size_t file_name_len = strlen (file_name);
+ bool check_for_renamed_directories = 0;
+
+ while (delayed_set_stat_head)
+ {
+ struct delayed_set_stat *data = delayed_set_stat_head;
+ bool skip_this_one = 0;
+ struct stat st;
+ mode_t current_mode = data->current_mode;
+ mode_t current_mode_mask = data->current_mode_mask;
+
+ check_for_renamed_directories |= data->after_links;
+
+ if (after_links < data->after_links
+ || (data->file_name_len < file_name_len
+ && file_name[data->file_name_len]
+ && (ISSLASH (file_name[data->file_name_len])
+ || ISSLASH (file_name[data->file_name_len - 1]))
+ && memcmp (file_name, data->file_name, data->file_name_len) == 0))
+ break;
+
+ chdir_do (data->change_dir);
+
+ if (check_for_renamed_directories)
+ {
+ if (fstatat (chdir_fd, data->file_name, &st, data->atflag) != 0)
+ {
+ stat_error (data->file_name);
+ skip_this_one = 1;
+ }
+ else
+ {
+ current_mode = st.st_mode;
+ current_mode_mask = ALL_MODE_BITS;
+ if (! (st.st_dev == data->dev && st.st_ino == data->ino))
+ {
+ ERROR ((0, 0,
+ _("%s: Directory renamed before its status could be extracted"),
+ quotearg_colon (data->file_name)));
+ skip_this_one = 1;
+ }
+ }
+ }
+
+ if (! skip_this_one)
+ {
+ struct tar_stat_info sb;
+ sb.stat.st_mode = data->mode;
+ sb.stat.st_uid = data->uid;
+ sb.stat.st_gid = data->gid;
+ sb.atime = data->atime;
+ sb.mtime = data->mtime;
+ sb.cntx_name = data->cntx_name;
+ sb.acls_a_ptr = data->acls_a_ptr;
+ sb.acls_a_len = data->acls_a_len;
+ sb.acls_d_ptr = data->acls_d_ptr;
+ sb.acls_d_len = data->acls_d_len;
+ sb.xattr_map = data->xattr_map;
+ sb.xattr_map_size = data->xattr_map_size;
+ set_stat (data->file_name, &sb,
+ -1, current_mode, current_mode_mask,
+ DIRTYPE, data->interdir, data->atflag);
+ }
+
+ delayed_set_stat_head = data->next;
+ free_delayed_set_stat (data);
+ }
+}
+
+
+static bool
+is_directory_link (const char *file_name)
+{
+ struct stat st;
+ int e = errno;
+ int res;
+
+ res = (fstatat (chdir_fd, file_name, &st, AT_SYMLINK_NOFOLLOW) == 0 &&
+ S_ISLNK (st.st_mode) &&
+ fstatat (chdir_fd, file_name, &st, 0) == 0 &&
+ S_ISDIR (st.st_mode));
+ errno = e;
+ return res;
+}
+
+/* Extractor functions for various member types */
+
+static int
+extract_dir (char *file_name, int typeflag)
+{
+ int status;
+ mode_t mode;
+ mode_t current_mode = 0;
+ mode_t current_mode_mask = 0;
+ int atflag = 0;
+ bool interdir_made = false;
+
+ /* Save 'root device' to avoid purging mount points. */
+ if (one_file_system_option && root_device == 0)
+ {
+ struct stat st;
+
+ if (fstatat (chdir_fd, ".", &st, 0) != 0)
+ stat_diag (".");
+ else
+ root_device = st.st_dev;
+ }
+
+ if (incremental_option)
+ /* Read the entry and delete files that aren't listed in the archive. */
+ purge_directory (file_name);
+ else if (typeflag == GNUTYPE_DUMPDIR)
+ skip_member ();
+
+ /* If ownership or permissions will be restored later, create the
+ directory with restrictive permissions at first, so that in the
+ meantime processes owned by other users do not inadvertently
+ create files under this directory that inherit the wrong owner,
+ group, or permissions from the directory. If not root, though,
+ make the directory writeable and searchable at first, so that
+ files can be created under it. */
+ mode = ((current_stat_info.stat.st_mode
+ & (0 < same_owner_option || 0 < same_permissions_option
+ ? S_IRWXU
+ : MODE_RWX))
+ | (we_are_root ? 0 : MODE_WXUSR));
+
+ for (;;)
+ {
+ status = mkdirat (chdir_fd, file_name, mode);
+ if (status == 0)
+ {
+ current_mode = mode & ~ current_umask;
+ current_mode_mask = MODE_RWX;
+ atflag = AT_SYMLINK_NOFOLLOW;
+ break;
+ }
+
+ if (errno == EEXIST
+ && (interdir_made
+ || keep_directory_symlink_option
+ || old_files_option == DEFAULT_OLD_FILES
+ || old_files_option == OVERWRITE_OLD_FILES))
+ {
+ struct stat st;
+
+ if (keep_directory_symlink_option && is_directory_link (file_name))
+ return 0;
+
+ if (deref_stat (file_name, &st) == 0)
+ {
+ current_mode = st.st_mode;
+ current_mode_mask = ALL_MODE_BITS;
+
+ if (S_ISDIR (current_mode))
+ {
+ if (interdir_made)
+ {
+ repair_delayed_set_stat (file_name, &st);
+ return 0;
+ }
+ break;
+ }
+ }
+ errno = EEXIST;
+ }
+
+ switch (maybe_recoverable (file_name, false, &interdir_made))
+ {
+ case RECOVER_OK:
+ continue;
+
+ case RECOVER_SKIP:
+ break;
+
+ case RECOVER_NO:
+ if (errno != EEXIST)
+ {
+ mkdir_error (file_name);
+ return 1;
+ }
+ break;
+ }
+ break;
+ }
+
+ if (status == 0
+ || old_files_option == DEFAULT_OLD_FILES
+ || old_files_option == OVERWRITE_OLD_FILES)
+ delay_set_stat (file_name, &current_stat_info,
+ current_mode, current_mode_mask,
+ current_stat_info.stat.st_mode, atflag);
+ return status;
+}
+
+
+
+static int
+open_output_file (char const *file_name, int typeflag, mode_t mode,
+ int file_created, mode_t *current_mode,
+ mode_t *current_mode_mask)
+{
+ int fd;
+ bool overwriting_old_files = old_files_option == OVERWRITE_OLD_FILES;
+ int openflag = (O_WRONLY | O_BINARY | O_CLOEXEC | O_NOCTTY | O_NONBLOCK
+ | O_CREAT
+ | (overwriting_old_files
+ ? O_TRUNC | (dereference_option ? 0 : O_NOFOLLOW)
+ : O_EXCL));
+
+ /* File might be created in set_xattr. So clear O_EXCL to avoid open() fail */
+ if (file_created)
+ openflag = openflag & ~O_EXCL;
+
+ if (typeflag == CONTTYPE)
+ {
+ static int conttype_diagnosed;
+
+ if (!conttype_diagnosed)
+ {
+ conttype_diagnosed = 1;
+ WARNOPT (WARN_CONTIGUOUS_CAST,
+ (0, 0, _("Extracting contiguous files as regular files")));
+ }
+ }
+
+ /* If O_NOFOLLOW is needed but does not work, check for a symlink
+ separately. There's a race condition, but that cannot be avoided
+ on hosts lacking O_NOFOLLOW. */
+ if (! HAVE_WORKING_O_NOFOLLOW
+ && overwriting_old_files && ! dereference_option)
+ {
+ struct stat st;
+ if (fstatat (chdir_fd, file_name, &st, AT_SYMLINK_NOFOLLOW) == 0
+ && S_ISLNK (st.st_mode))
+ {
+ errno = ELOOP;
+ return -1;
+ }
+ }
+
+ fd = openat (chdir_fd, file_name, openflag, mode);
+ if (0 <= fd)
+ {
+ if (overwriting_old_files)
+ {
+ struct stat st;
+ if (fstat (fd, &st) != 0)
+ {
+ int e = errno;
+ close (fd);
+ errno = e;
+ return -1;
+ }
+ if (! S_ISREG (st.st_mode))
+ {
+ close (fd);
+ errno = EEXIST;
+ return -1;
+ }
+ *current_mode = st.st_mode;
+ *current_mode_mask = ALL_MODE_BITS;
+ }
+ else
+ {
+ *current_mode = mode & ~ current_umask;
+ *current_mode_mask = MODE_RWX;
+ }
+ }
+
+ return fd;
+}
+
+static int
+extract_file (char *file_name, int typeflag)
+{
+ int fd;
+ off_t size;
+ union block *data_block;
+ int status;
+ size_t count;
+ size_t written;
+ bool interdir_made = false;
+ mode_t mode = (current_stat_info.stat.st_mode & MODE_RWX
+ & ~ (0 < same_owner_option ? S_IRWXG | S_IRWXO : 0));
+ mode_t invert_permissions = 0 < same_owner_option ? mode & (S_IRWXG | S_IRWXO)
+ : 0;
+ mode_t current_mode = 0;
+ mode_t current_mode_mask = 0;
+
+ if (to_stdout_option)
+ fd = STDOUT_FILENO;
+ else if (to_command_option)
+ {
+ fd = sys_exec_command (file_name, 'f', &current_stat_info);
+ if (fd < 0)
+ {
+ skip_member ();
+ return 0;
+ }
+ }
+ else
+ {
+ int file_created = 0;
+ if (set_xattr (file_name, &current_stat_info, invert_permissions,
+ typeflag, &file_created))
+ {
+ skip_member ();
+ open_error (file_name);
+ return 1;
+ }
+
+ while ((fd = open_output_file (file_name, typeflag, mode,
+ file_created, &current_mode,
+ &current_mode_mask))
+ < 0)
+ {
+ int recover = maybe_recoverable (file_name, true, &interdir_made);
+ if (recover != RECOVER_OK)
+ {
+ skip_member ();
+ if (recover == RECOVER_SKIP)
+ return 0;
+ open_error (file_name);
+ return 1;
+ }
+ }
+ }
+
+ mv_begin_read (&current_stat_info);
+ if (current_stat_info.is_sparse)
+ sparse_extract_file (fd, &current_stat_info, &size);
+ else
+ for (size = current_stat_info.stat.st_size; size > 0; )
+ {
+ mv_size_left (size);
+
+ /* Locate data, determine max length writeable, write it,
+ block that we have used the data, then check if the write
+ worked. */
+
+ data_block = find_next_block ();
+ if (! data_block)
+ {
+ ERROR ((0, 0, _("Unexpected EOF in archive")));
+ break; /* FIXME: What happens, then? */
+ }
+
+ written = available_space_after (data_block);
+
+ if (written > size)
+ written = size;
+ errno = 0;
+ count = blocking_write (fd, data_block->buffer, written);
+ size -= written;
+
+ set_next_block_after ((union block *)
+ (data_block->buffer + written - 1));
+ if (count != written)
+ {
+ if (!to_command_option)
+ write_error_details (file_name, count, written);
+ /* FIXME: shouldn't we restore from backup? */
+ break;
+ }
+ }
+
+ skip_file (size);
+
+ mv_end ();
+
+ /* If writing to stdout, don't try to do anything to the filename;
+ it doesn't exist, or we don't want to touch it anyway. */
+
+ if (to_stdout_option)
+ return 0;
+
+ if (! to_command_option)
+ set_stat (file_name, &current_stat_info, fd,
+ current_mode, current_mode_mask, typeflag, false,
+ (old_files_option == OVERWRITE_OLD_FILES
+ ? 0 : AT_SYMLINK_NOFOLLOW));
+
+ status = close (fd);
+ if (status < 0)
+ close_error (file_name);
+
+ if (to_command_option)
+ sys_wait_command ();
+
+ return status;
+}
+
+/* Create a placeholder file with name FILE_NAME, which will be
+ replaced after other extraction is done by a symbolic link if
+ IS_SYMLINK is true, and by a hard link otherwise. Set
+ *INTERDIR_MADE if an intermediate directory is made in the
+ process. */
+
+static int
+create_placeholder_file (char *file_name, bool is_symlink, bool *interdir_made)
+{
+ int fd;
+ struct stat st;
+
+ while ((fd = openat (chdir_fd, file_name, O_WRONLY | O_CREAT | O_EXCL, 0)) < 0)
+ {
+ switch (maybe_recoverable (file_name, false, interdir_made))
+ {
+ case RECOVER_OK:
+ continue;
+
+ case RECOVER_SKIP:
+ return 0;
+
+ case RECOVER_NO:
+ open_error (file_name);
+ return -1;
+ }
+ }
+
+ if (fstat (fd, &st) != 0)
+ {
+ stat_error (file_name);
+ close (fd);
+ }
+ else if (close (fd) != 0)
+ close_error (file_name);
+ else
+ {
+ struct delayed_set_stat *h;
+ struct delayed_link *p =
+ xmalloc (offsetof (struct delayed_link, target)
+ + strlen (current_stat_info.link_name)
+ + 1);
+ p->next = delayed_link_head;
+ delayed_link_head = p;
+ p->dev = st.st_dev;
+ p->ino = st.st_ino;
+ p->birthtime = get_stat_birthtime (&st);
+ p->is_symlink = is_symlink;
+ if (is_symlink)
+ {
+ p->mode = current_stat_info.stat.st_mode;
+ p->uid = current_stat_info.stat.st_uid;
+ p->gid = current_stat_info.stat.st_gid;
+ p->atime = current_stat_info.atime;
+ p->mtime = current_stat_info.mtime;
+ }
+ p->change_dir = chdir_current;
+ p->sources = xmalloc (offsetof (struct string_list, string)
+ + strlen (file_name) + 1);
+ p->sources->next = 0;
+ strcpy (p->sources->string, file_name);
+ p->cntx_name = NULL;
+ assign_string (&p->cntx_name, current_stat_info.cntx_name);
+ p->acls_a_ptr = NULL;
+ p->acls_a_len = 0;
+ p->acls_d_ptr = NULL;
+ p->acls_d_len = 0;
+ xheader_xattr_copy (&current_stat_info, &p->xattr_map, &p->xattr_map_size);
+ strcpy (p->target, current_stat_info.link_name);
+
+ h = delayed_set_stat_head;
+ if (h && ! h->after_links
+ && strncmp (file_name, h->file_name, h->file_name_len) == 0
+ && ISSLASH (file_name[h->file_name_len])
+ && (last_component (file_name) == file_name + h->file_name_len + 1))
+ mark_after_links (h);
+
+ return 0;
+ }
+
+ return -1;
+}
+
+static int
+extract_link (char *file_name, int typeflag)
+{
+ bool interdir_made = false;
+ char const *link_name;
+ int rc;
+
+ link_name = current_stat_info.link_name;
+
+ if (! absolute_names_option && contains_dot_dot (link_name))
+ return create_placeholder_file (file_name, false, &interdir_made);
+
+ do
+ {
+ struct stat st1, st2;
+ int e;
+ int status = linkat (chdir_fd, link_name, chdir_fd, file_name, 0);
+ e = errno;
+
+ if (status == 0)
+ {
+ struct delayed_link *ds = delayed_link_head;
+ if (ds
+ && fstatat (chdir_fd, link_name, &st1, AT_SYMLINK_NOFOLLOW) == 0)
+ for (; ds; ds = ds->next)
+ if (ds->change_dir == chdir_current
+ && ds->dev == st1.st_dev
+ && ds->ino == st1.st_ino
+ && (timespec_cmp (ds->birthtime, get_stat_birthtime (&st1))
+ == 0))
+ {
+ struct string_list *p = xmalloc (offsetof (struct string_list, string)
+ + strlen (file_name) + 1);
+ strcpy (p->string, file_name);
+ p->next = ds->sources;
+ ds->sources = p;
+ break;
+ }
+ return 0;
+ }
+ else if ((e == EEXIST && strcmp (link_name, file_name) == 0)
+ || ((fstatat (chdir_fd, link_name, &st1, AT_SYMLINK_NOFOLLOW)
+ == 0)
+ && (fstatat (chdir_fd, file_name, &st2, AT_SYMLINK_NOFOLLOW)
+ == 0)
+ && st1.st_dev == st2.st_dev
+ && st1.st_ino == st2.st_ino))
+ return 0;
+
+ errno = e;
+ }
+ while ((rc = maybe_recoverable (file_name, false, &interdir_made))
+ == RECOVER_OK);
+
+ if (rc == RECOVER_SKIP)
+ return 0;
+ if (!(incremental_option && errno == EEXIST))
+ {
+ link_error (link_name, file_name);
+ return 1;
+ }
+ return 0;
+}
+
+static int
+extract_symlink (char *file_name, int typeflag)
+{
+#ifdef HAVE_SYMLINK
+ bool interdir_made = false;
+
+ if (! absolute_names_option
+ && (IS_ABSOLUTE_FILE_NAME (current_stat_info.link_name)
+ || contains_dot_dot (current_stat_info.link_name)))
+ return create_placeholder_file (file_name, true, &interdir_made);
+
+ while (symlinkat (current_stat_info.link_name, chdir_fd, file_name) != 0)
+ switch (maybe_recoverable (file_name, false, &interdir_made))
+ {
+ case RECOVER_OK:
+ continue;
+
+ case RECOVER_SKIP:
+ return 0;
+
+ case RECOVER_NO:
+ symlink_error (current_stat_info.link_name, file_name);
+ return -1;
+ }
+
+ set_stat (file_name, &current_stat_info, -1, 0, 0,
+ SYMTYPE, false, AT_SYMLINK_NOFOLLOW);
+ return 0;
+
+#else
+ static int warned_once;
+
+ if (!warned_once)
+ {
+ warned_once = 1;
+ WARNOPT (WARN_SYMLINK_CAST,
+ (0, 0,
+ _("Attempting extraction of symbolic links as hard links")));
+ }
+ return extract_link (file_name, typeflag);
+#endif
+}
+
+#if S_IFCHR || S_IFBLK
+static int
+extract_node (char *file_name, int typeflag)
+{
+ bool interdir_made = false;
+ mode_t mode = (current_stat_info.stat.st_mode & (MODE_RWX | S_IFBLK | S_IFCHR)
+ & ~ (0 < same_owner_option ? S_IRWXG | S_IRWXO : 0));
+
+ while (mknodat (chdir_fd, file_name, mode, current_stat_info.stat.st_rdev)
+ != 0)
+ switch (maybe_recoverable (file_name, false, &interdir_made))
+ {
+ case RECOVER_OK:
+ continue;
+
+ case RECOVER_SKIP:
+ return 0;
+
+ case RECOVER_NO:
+ mknod_error (file_name);
+ return -1;
+ }
+
+ set_stat (file_name, &current_stat_info, -1,
+ mode & ~ current_umask, MODE_RWX,
+ typeflag, false, AT_SYMLINK_NOFOLLOW);
+ return 0;
+}
+#endif
+
+#if HAVE_MKFIFO || defined mkfifo
+static int
+extract_fifo (char *file_name, int typeflag)
+{
+ bool interdir_made = false;
+ mode_t mode = (current_stat_info.stat.st_mode & MODE_RWX
+ & ~ (0 < same_owner_option ? S_IRWXG | S_IRWXO : 0));
+
+ while (mkfifoat (chdir_fd, file_name, mode) != 0)
+ switch (maybe_recoverable (file_name, false, &interdir_made))
+ {
+ case RECOVER_OK:
+ continue;
+
+ case RECOVER_SKIP:
+ return 0;
+
+ case RECOVER_NO:
+ mkfifo_error (file_name);
+ return -1;
+ }
+
+ set_stat (file_name, &current_stat_info, -1,
+ mode & ~ current_umask, MODE_RWX,
+ typeflag, false, AT_SYMLINK_NOFOLLOW);
+ return 0;
+}
+#endif
+
+static int
+extract_volhdr (char *file_name, int typeflag)
+{
+ skip_member ();
+ return 0;
+}
+
+static int
+extract_failure (char *file_name, int typeflag)
+{
+ return 1;
+}
+
+static int
+extract_skip (char *file_name, int typeflag)
+{
+ skip_member ();
+ return 0;
+}
+
+typedef int (*tar_extractor_t) (char *file_name, int typeflag);
+
+
+
+/* Prepare to extract a file. Find extractor function.
+ Return zero if extraction should not proceed. */
+
+static int
+prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun)
+{
+ int rc = 1;
+
+ if (EXTRACT_OVER_PIPE)
+ rc = 0;
+
+ /* Select the extractor */
+ switch (typeflag)
+ {
+ case GNUTYPE_SPARSE:
+ *fun = extract_file;
+ rc = 1;
+ break;
+
+ case AREGTYPE:
+ case REGTYPE:
+ case CONTTYPE:
+ /* Appears to be a file. But BSD tar uses the convention that a slash
+ suffix means a directory. */
+ if (current_stat_info.had_trailing_slash)
+ *fun = extract_dir;
+ else
+ {
+ *fun = extract_file;
+ rc = 1;
+ }
+ break;
+
+ case SYMTYPE:
+ *fun = extract_symlink;
+ break;
+
+ case LNKTYPE:
+ *fun = extract_link;
+ break;
+
+#if S_IFCHR
+ case CHRTYPE:
+ current_stat_info.stat.st_mode |= S_IFCHR;
+ *fun = extract_node;
+ break;
+#endif
+
+#if S_IFBLK
+ case BLKTYPE:
+ current_stat_info.stat.st_mode |= S_IFBLK;
+ *fun = extract_node;
+ break;
+#endif
+
+#if HAVE_MKFIFO || defined mkfifo
+ case FIFOTYPE:
+ *fun = extract_fifo;
+ break;
+#endif
+
+ case DIRTYPE:
+ case GNUTYPE_DUMPDIR:
+ *fun = extract_dir;
+ if (current_stat_info.is_dumpdir)
+ delay_directory_restore_option = true;
+ break;
+
+ case GNUTYPE_VOLHDR:
+ *fun = extract_volhdr;
+ break;
+
+ case GNUTYPE_MULTIVOL:
+ ERROR ((0, 0,
+ _("%s: Cannot extract -- file is continued from another volume"),
+ quotearg_colon (current_stat_info.file_name)));
+ *fun = extract_skip;
+ break;
+
+ case GNUTYPE_LONGNAME:
+ case GNUTYPE_LONGLINK:
+ ERROR ((0, 0, _("Unexpected long name header")));
+ *fun = extract_failure;
+ break;
+
+ default:
+ WARNOPT (WARN_UNKNOWN_CAST,
+ (0, 0,
+ _("%s: Unknown file type '%c', extracted as normal file"),
+ quotearg_colon (file_name), typeflag));
+ *fun = extract_file;
+ }
+
+ /* Determine whether the extraction should proceed */
+ if (rc == 0)
+ return 0;
+
+ switch (old_files_option)
+ {
+ case UNLINK_FIRST_OLD_FILES:
+ if (!remove_any_file (file_name,
+ recursive_unlink_option ? RECURSIVE_REMOVE_OPTION
+ : ORDINARY_REMOVE_OPTION)
+ && errno && errno != ENOENT)
+ {
+ unlink_error (file_name);
+ return 0;
+ }
+ break;
+
+ case KEEP_NEWER_FILES:
+ if (file_newer_p (file_name, 0, &current_stat_info))
+ {
+ WARNOPT (WARN_IGNORE_NEWER,
+ (0, 0, _("Current %s is newer or same age"),
+ quote (file_name)));
+ return 0;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return 1;
+}
+
+/* Extract a file from the archive. */
+void
+extract_archive (void)
+{
+ char typeflag;
+ tar_extractor_t fun;
+
+ fatal_exit_hook = extract_finish;
+
+ set_next_block_after (current_header);
+
+ if (!current_stat_info.file_name[0]
+ || (interactive_option
+ && !confirm ("extract", current_stat_info.file_name)))
+ {
+ skip_member ();
+ return;
+ }
+
+ /* Print the block from current_header and current_stat. */
+ if (verbose_option)
+ print_header (&current_stat_info, current_header, -1);
+
+ /* Restore stats for all non-ancestor directories, unless
+ it is an incremental archive.
+ (see NOTICE in the comment to delay_set_stat above) */
+ if (!delay_directory_restore_option)
+ {
+ int dir = chdir_current;
+ apply_nonancestor_delayed_set_stat (current_stat_info.file_name, 0);
+ chdir_do (dir);
+ }
+
+ /* Take a safety backup of a previously existing file. */
+
+ if (backup_option)
+ if (!maybe_backup_file (current_stat_info.file_name, 0))
+ {
+ int e = errno;
+ ERROR ((0, e, _("%s: Was unable to backup this file"),
+ quotearg_colon (current_stat_info.file_name)));
+ skip_member ();
+ return;
+ }
+
+ /* Extract the archive entry according to its type. */
+ /* KLUDGE */
+ typeflag = sparse_member_p (&current_stat_info) ?
+ GNUTYPE_SPARSE : current_header->header.typeflag;
+
+ if (prepare_to_extract (current_stat_info.file_name, typeflag, &fun))
+ {
+ if (fun && (*fun) (current_stat_info.file_name, typeflag)
+ && backup_option)
+ undo_last_backup ();
+ }
+ else
+ skip_member ();
+
+}
+
+/* Extract the links whose final extraction were delayed. */
+static void
+apply_delayed_links (void)
+{
+ struct delayed_link *ds;
+
+ for (ds = delayed_link_head; ds; )
+ {
+ struct string_list *sources = ds->sources;
+ char const *valid_source = 0;
+
+ chdir_do (ds->change_dir);
+
+ for (sources = ds->sources; sources; sources = sources->next)
+ {
+ char const *source = sources->string;
+ struct stat st;
+
+ /* Make sure the placeholder file is still there. If not,
+ don't create a link, as the placeholder was probably
+ removed by a later extraction. */
+ if (fstatat (chdir_fd, source, &st, AT_SYMLINK_NOFOLLOW) == 0
+ && st.st_dev == ds->dev
+ && st.st_ino == ds->ino
+ && timespec_cmp (get_stat_birthtime (&st), ds->birthtime) == 0)
+ {
+ /* Unlink the placeholder, then create a hard link if possible,
+ a symbolic link otherwise. */
+ if (unlinkat (chdir_fd, source, 0) != 0)
+ unlink_error (source);
+ else if (valid_source
+ && (linkat (chdir_fd, valid_source, chdir_fd, source, 0)
+ == 0))
+ ;
+ else if (!ds->is_symlink)
+ {
+ if (linkat (chdir_fd, ds->target, chdir_fd, source, 0) != 0)
+ link_error (ds->target, source);
+ }
+ else if (symlinkat (ds->target, chdir_fd, source) != 0)
+ symlink_error (ds->target, source);
+ else
+ {
+ struct tar_stat_info st1;
+ st1.stat.st_mode = ds->mode;
+ st1.stat.st_uid = ds->uid;
+ st1.stat.st_gid = ds->gid;
+ st1.atime = ds->atime;
+ st1.mtime = ds->mtime;
+ st1.cntx_name = ds->cntx_name;
+ st1.acls_a_ptr = ds->acls_a_ptr;
+ st1.acls_a_len = ds->acls_a_len;
+ st1.acls_d_ptr = ds->acls_d_ptr;
+ st1.acls_d_len = ds->acls_d_len;
+ st1.xattr_map = ds->xattr_map;
+ st1.xattr_map_size = ds->xattr_map_size;
+ set_stat (source, &st1, -1, 0, 0, SYMTYPE,
+ false, AT_SYMLINK_NOFOLLOW);
+ valid_source = source;
+ }
+ }
+ }
+
+ for (sources = ds->sources; sources; )
+ {
+ struct string_list *next = sources->next;
+ free (sources);
+ sources = next;
+ }
+
+ xheader_xattr_free (ds->xattr_map, ds->xattr_map_size);
+ free (ds->cntx_name);
+
+ {
+ struct delayed_link *next = ds->next;
+ free (ds);
+ ds = next;
+ }
+ }
+
+ delayed_link_head = 0;
+}
+
+/* Finish the extraction of an archive. */
+void
+extract_finish (void)
+{
+ /* First, fix the status of ordinary directories that need fixing. */
+ apply_nonancestor_delayed_set_stat ("", 0);
+
+ /* Then, apply delayed links, so that they don't affect delayed
+ directory status-setting for ordinary directories. */
+ apply_delayed_links ();
+
+ /* Finally, fix the status of directories that are ancestors
+ of delayed links. */
+ apply_nonancestor_delayed_set_stat ("", 1);
+}
+
+bool
+rename_directory (char *src, char *dst)
+{
+ if (renameat (chdir_fd, src, chdir_fd, dst) == 0)
+ fixup_delayed_set_stat (src, dst);
+ else
+ {
+ int e = errno;
+ bool interdir_made;
+
+ switch (e)
+ {
+ case ENOENT:
+ if (make_directories (dst, &interdir_made) == 0)
+ {
+ if (renameat (chdir_fd, src, chdir_fd, dst) == 0)
+ return true;
+ e = errno;
+ }
+ break;
+
+ case EXDEV:
+ /* FIXME: Fall back to recursive copying */
+
+ default:
+ break;
+ }
+
+ ERROR ((0, e, _("Cannot rename %s to %s"),
+ quote_n (0, src),
+ quote_n (1, dst)));
+ return false;
+ }
+ return true;
+}
diff --git a/src/incremen.c b/src/incremen.c
new file mode 100644
index 0000000..19d0b9b
--- /dev/null
+++ b/src/incremen.c
@@ -0,0 +1,1805 @@
+/* GNU dump extensions to tar.
+
+ Copyright 1988, 1992-1994, 1996-1997, 1999-2001, 2003-2009,
+ 2013-2014, 2016 Free Software Foundation, Inc.
+
+ This file is part of GNU tar.
+
+ GNU tar 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.
+
+ GNU tar is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <system.h>
+#include <hash.h>
+#include <quotearg.h>
+#include "common.h"
+
+/* Incremental dump specialities. */
+
+/* Which child files to save under a directory. */
+enum children
+ {
+ NO_CHILDREN,
+ CHANGED_CHILDREN,
+ ALL_CHILDREN
+ };
+
+#define DIRF_INIT 0x0001 /* directory structure is initialized
+ (procdir called at least once) */
+#define DIRF_NFS 0x0002 /* directory is mounted on nfs */
+#define DIRF_FOUND 0x0004 /* directory is found on fs */
+#define DIRF_NEW 0x0008 /* directory is new (not found
+ in the previous dump) */
+#define DIRF_RENAMED 0x0010 /* directory is renamed */
+
+#define DIR_IS_INITED(d) ((d)->flags & DIRF_INIT)
+#define DIR_IS_NFS(d) ((d)->flags & DIRF_NFS)
+#define DIR_IS_FOUND(d) ((d)->flags & DIRF_FOUND)
+/* #define DIR_IS_NEW(d) ((d)->flags & DIRF_NEW) FIXME: not used */
+#define DIR_IS_RENAMED(d) ((d)->flags & DIRF_RENAMED)
+
+#define DIR_SET_FLAG(d,f) (d)->flags |= (f)
+#define DIR_CLEAR_FLAG(d,f) (d)->flags &= ~(f)
+
+struct dumpdir /* Dump directory listing */
+{
+ char *contents; /* Actual contents */
+ size_t total; /* Total number of elements */
+ size_t elc; /* Number of D/N/Y elements. */
+ char **elv; /* Array of D/N/Y elements */
+};
+
+/* Directory attributes. */
+struct directory
+ {
+ struct directory *next;
+ struct timespec mtime; /* Modification time */
+ dev_t device_number; /* device number for directory */
+ ino_t inode_number; /* inode number for directory */
+ struct dumpdir *dump; /* Directory contents */
+ struct dumpdir *idump; /* Initial contents if the directory was
+ rescanned */
+ enum children children; /* What to save under this directory */
+ unsigned flags; /* See DIRF_ macros above */
+ struct directory *orig; /* If the directory was renamed, points to
+ the original directory structure */
+ const char *tagfile; /* Tag file, if the directory falls under
+ exclusion_tag_under */
+ char *caname; /* canonical name */
+ char *name; /* file name of directory */
+ };
+
+static struct dumpdir *
+dumpdir_create0 (const char *contents, const char *cmask)
+{
+ struct dumpdir *dump;
+ size_t i, total, ctsize, len;
+ char *p;
+ const char *q;
+
+ for (i = 0, total = 0, ctsize = 1, q = contents; *q; total++, q += len)
+ {
+ len = strlen (q) + 1;
+ ctsize += len;
+ if (!cmask || strchr (cmask, *q))
+ i++;
+ }
+ dump = xmalloc (sizeof (*dump) + ctsize);
+ dump->contents = (char*)(dump + 1);
+ memcpy (dump->contents, contents, ctsize);
+ dump->total = total;
+ dump->elc = i;
+ dump->elv = xcalloc (i + 1, sizeof (dump->elv[0]));
+
+ for (i = 0, p = dump->contents; *p; p += strlen (p) + 1)
+ {
+ if (!cmask || strchr (cmask, *p))
+ dump->elv[i++] = p + 1;
+ }
+ dump->elv[i] = NULL;
+ return dump;
+}
+
+static struct dumpdir *
+dumpdir_create (const char *contents)
+{
+ return dumpdir_create0 (contents, "YND");
+}
+
+static void
+dumpdir_free (struct dumpdir *dump)
+{
+ free (dump->elv);
+ free (dump);
+}
+
+static int
+compare_dirnames (const void *first, const void *second)
+{
+ char const *const *name1 = first;
+ char const *const *name2 = second;
+ return strcmp (*name1, *name2);
+}
+
+/* Locate NAME in the dumpdir array DUMP.
+ Return pointer to the slot in DUMP->contents, or NULL if not found */
+static char *
+dumpdir_locate (struct dumpdir *dump, const char *name)
+{
+ char **ptr;
+ if (!dump)
+ return NULL;
+
+ ptr = bsearch (&name, dump->elv, dump->elc, sizeof (dump->elv[0]),
+ compare_dirnames);
+ return ptr ? *ptr - 1: NULL;
+}
+
+struct dumpdir_iter
+{
+ struct dumpdir *dump; /* Dumpdir being iterated */
+ int all; /* Iterate over all entries, not only D/N/Y */
+ size_t next; /* Index of the next element */
+};
+
+static char *
+dumpdir_next (struct dumpdir_iter *itr)
+{
+ size_t cur = itr->next;
+ char *ret = NULL;
+
+ if (itr->all)
+ {
+ ret = itr->dump->contents + cur;
+ if (*ret == 0)
+ return NULL;
+ itr->next += strlen (ret) + 1;
+ }
+ else if (cur < itr->dump->elc)
+ {
+ ret = itr->dump->elv[cur] - 1;
+ itr->next++;
+ }
+
+ return ret;
+}
+
+static char *
+dumpdir_first (struct dumpdir *dump, int all, struct dumpdir_iter **pitr)
+{
+ struct dumpdir_iter *itr = xmalloc (sizeof (*itr));
+ itr->dump = dump;
+ itr->all = all;
+ itr->next = 0;
+ *pitr = itr;
+ return dumpdir_next (itr);
+}
+
+/* Return size in bytes of the dumpdir array P */
+size_t
+dumpdir_size (const char *p)
+{
+ size_t totsize = 0;
+
+ while (*p)
+ {
+ size_t size = strlen (p) + 1;
+ totsize += size;
+ p += size;
+ }
+ return totsize + 1;
+}
+
+
+static struct directory *dirhead, *dirtail;
+static Hash_table *directory_table;
+static Hash_table *directory_meta_table;
+
+#if HAVE_ST_FSTYPE_STRING
+ static char const nfs_string[] = "nfs";
+# define NFS_FILE_STAT(st) (strcmp ((st).st_fstype, nfs_string) == 0)
+#else
+# define ST_DEV_MSB(st) (~ (dev_t) 0 << (sizeof (st).st_dev * CHAR_BIT - 1))
+# define NFS_FILE_STAT(st) (((st).st_dev & ST_DEV_MSB (st)) != 0)
+#endif
+
+/* Calculate the hash of a directory. */
+static size_t
+hash_directory_canonical_name (void const *entry, size_t n_buckets)
+{
+ struct directory const *directory = entry;
+ return hash_string (directory->caname, n_buckets);
+}
+
+/* Compare two directories for equality of their names. */
+static bool
+compare_directory_canonical_names (void const *entry1, void const *entry2)
+{
+ struct directory const *directory1 = entry1;
+ struct directory const *directory2 = entry2;
+ return strcmp (directory1->caname, directory2->caname) == 0;
+}
+
+static size_t
+hash_directory_meta (void const *entry, size_t n_buckets)
+{
+ struct directory const *directory = entry;
+ /* FIXME: Work out a better algorytm */
+ return (directory->device_number + directory->inode_number) % n_buckets;
+}
+
+/* Compare two directories for equality of their device and inode numbers. */
+static bool
+compare_directory_meta (void const *entry1, void const *entry2)
+{
+ struct directory const *directory1 = entry1;
+ struct directory const *directory2 = entry2;
+ return directory1->device_number == directory2->device_number
+ && directory1->inode_number == directory2->inode_number;
+}
+
+/* Make a directory entry for given relative NAME and canonical name CANAME.
+ The latter is "stolen", i.e. the returned directory contains pointer to
+ it. */
+static struct directory *
+make_directory (const char *name, char *caname)
+{
+ size_t namelen = strlen (name);
+ struct directory *directory = xmalloc (sizeof (*directory));
+ directory->next = NULL;
+ directory->dump = directory->idump = NULL;
+ directory->orig = NULL;
+ directory->flags = false;
+ if (namelen > 1 && ISSLASH (name[namelen - 1]))
+ namelen--;
+ directory->name = xmalloc (namelen + 1);
+ memcpy (directory->name, name, namelen);
+ directory->name[namelen] = 0;
+ directory->caname = caname;
+ directory->tagfile = NULL;
+ return directory;
+}
+
+static void
+free_directory (struct directory *dir)
+{
+ free (dir->caname);
+ free (dir->name);
+ free (dir);
+}
+
+static struct directory *
+attach_directory (const char *name)
+{
+ char *cname = normalize_filename (chdir_current, name);
+ struct directory *dir = make_directory (name, cname);
+ if (dirtail)
+ dirtail->next = dir;
+ else
+ dirhead = dir;
+ dirtail = dir;
+ return dir;
+}
+
+
+static void
+dirlist_replace_prefix (const char *pref, const char *repl)
+{
+ struct directory *dp;
+ size_t pref_len = strlen (pref);
+ size_t repl_len = strlen (repl);
+ for (dp = dirhead; dp; dp = dp->next)
+ replace_prefix (&dp->name, pref, pref_len, repl, repl_len);
+}
+
+void
+clear_directory_table (void)
+{
+ struct directory *dp;
+
+ if (directory_table)
+ hash_clear (directory_table);
+ if (directory_meta_table)
+ hash_clear (directory_meta_table);
+ for (dp = dirhead; dp; )
+ {
+ struct directory *next = dp->next;
+ free_directory (dp);
+ dp = next;
+ }
+ dirhead = dirtail = NULL;
+}
+
+/* Create and link a new directory entry for directory NAME, having a
+ device number DEV and an inode number INO, with NFS indicating
+ whether it is an NFS device and FOUND indicating whether we have
+ found that the directory exists. */
+static struct directory *
+note_directory (char const *name, struct timespec mtime,
+ dev_t dev, ino_t ino, bool nfs, bool found,
+ const char *contents)
+{
+ struct directory *directory = attach_directory (name);
+
+ directory->mtime = mtime;
+ directory->device_number = dev;
+ directory->inode_number = ino;
+ directory->children = CHANGED_CHILDREN;
+ if (nfs)
+ DIR_SET_FLAG (directory, DIRF_NFS);
+ if (found)
+ DIR_SET_FLAG (directory, DIRF_FOUND);
+ if (contents)
+ directory->dump = dumpdir_create (contents);
+ else
+ directory->dump = NULL;
+
+ if (! ((directory_table
+ || (directory_table = hash_initialize (0, 0,
+ hash_directory_canonical_name,
+ compare_directory_canonical_names,
+ 0)))
+ && hash_insert (directory_table, directory)))
+ xalloc_die ();
+
+ if (! ((directory_meta_table
+ || (directory_meta_table = hash_initialize (0, 0,
+ hash_directory_meta,
+ compare_directory_meta,
+ 0)))
+ && hash_insert (directory_meta_table, directory)))
+ xalloc_die ();
+
+ return directory;
+}
+
+/* Return a directory entry for a given file NAME, or zero if none found. */
+static struct directory *
+find_directory (const char *name)
+{
+ if (! directory_table)
+ return 0;
+ else
+ {
+ char *caname = normalize_filename (chdir_current, name);
+ struct directory *dir = make_directory (name, caname);
+ struct directory *ret = hash_lookup (directory_table, dir);
+ free_directory (dir);
+ return ret;
+ }
+}
+
+#if 0
+/* Remove directory entry for the given CANAME */
+void
+remove_directory (const char *caname)
+{
+ struct directory *dir = make_directory (caname, xstrdup (caname));
+ struct directory *ret = hash_delete (directory_table, dir);
+ if (ret)
+ free_directory (ret);
+ free_directory (dir);
+}
+#endif
+
+/* If first OLD_PREFIX_LEN bytes of DIR->NAME name match OLD_PREFIX,
+ replace them with NEW_PREFIX. */
+void
+rebase_directory (struct directory *dir,
+ const char *old_prefix, size_t old_prefix_len,
+ const char *new_prefix, size_t new_prefix_len)
+{
+ replace_prefix (&dir->name, old_prefix, old_prefix_len,
+ new_prefix, new_prefix_len);
+}
+
+/* Return a directory entry for a given combination of device and inode
+ numbers, or zero if none found. */
+static struct directory *
+find_directory_meta (dev_t dev, ino_t ino)
+{
+ if (! directory_meta_table)
+ return 0;
+ else
+ {
+ struct directory *dir = make_directory ("", NULL);
+ struct directory *ret;
+ dir->device_number = dev;
+ dir->inode_number = ino;
+ ret = hash_lookup (directory_meta_table, dir);
+ free_directory (dir);
+ return ret;
+ }
+}
+
+void
+update_parent_directory (struct tar_stat_info *parent)
+{
+ struct directory *directory = find_directory (parent->orig_file_name);
+ if (directory)
+ {
+ struct stat st;
+ if (fstat (parent->fd, &st) != 0)
+ stat_diag (directory->name);
+ else
+ directory->mtime = get_stat_mtime (&st);
+ }
+}
+
+#define PD_FORCE_CHILDREN 0x10
+#define PD_FORCE_INIT 0x20
+#define PD_CHILDREN(f) ((f) & 3)
+
+static struct directory *
+procdir (const char *name_buffer, struct tar_stat_info *st,
+ int flag,
+ char *entry)
+{
+ struct directory *directory;
+ struct stat *stat_data = &st->stat;
+ bool nfs = NFS_FILE_STAT (*stat_data);
+ bool perhaps_renamed = false;
+
+ if ((directory = find_directory (name_buffer)) != NULL)
+ {
+ if (DIR_IS_INITED (directory))
+ {
+ if (flag & PD_FORCE_INIT)
+ {
+ assign_string (&directory->name, name_buffer);
+ }
+ else
+ {
+ *entry = 'N'; /* Avoid duplicating this directory */
+ return directory;
+ }
+ }
+
+ if (strcmp (directory->name, name_buffer))
+ {
+ *entry = 'N';
+ return directory;
+ }
+
+ /* With NFS, the same file can have two different devices
+ if an NFS directory is mounted in multiple locations,
+ which is relatively common when automounting.
+ To avoid spurious incremental redumping of
+ directories, consider all NFS devices as equal,
+ relying on the i-node to establish differences. */
+
+ if (! ((!check_device_option
+ || (DIR_IS_NFS (directory) && nfs)
+ || directory->device_number == stat_data->st_dev)
+ && directory->inode_number == stat_data->st_ino))
+ {
+ /* FIXME: find_directory_meta ignores nfs */
+ struct directory *d = find_directory_meta (stat_data->st_dev,
+ stat_data->st_ino);
+ if (d)
+ {
+ if (strcmp (d->name, name_buffer))
+ {
+ WARNOPT (WARN_RENAME_DIRECTORY,
+ (0, 0,
+ _("%s: Directory has been renamed from %s"),
+ quotearg_colon (name_buffer),
+ quote_n (1, d->name)));
+ directory->orig = d;
+ DIR_SET_FLAG (directory, DIRF_RENAMED);
+ dirlist_replace_prefix (d->name, name_buffer);
+ }
+ directory->children = CHANGED_CHILDREN;
+ }
+ else
+ {
+ perhaps_renamed = true;
+ directory->children = ALL_CHILDREN;
+ directory->device_number = stat_data->st_dev;
+ directory->inode_number = stat_data->st_ino;
+ }
+ if (nfs)
+ DIR_SET_FLAG (directory, DIRF_NFS);
+ }
+ else
+ directory->children = CHANGED_CHILDREN;
+
+ DIR_SET_FLAG (directory, DIRF_FOUND);
+ }
+ else
+ {
+ struct directory *d = find_directory_meta (stat_data->st_dev,
+ stat_data->st_ino);
+
+ directory = note_directory (name_buffer,
+ get_stat_mtime (stat_data),
+ stat_data->st_dev,
+ stat_data->st_ino,
+ nfs,
+ true,
+ NULL);
+
+ if (d)
+ {
+ if (strcmp (d->name, name_buffer))
+ {
+ WARNOPT (WARN_RENAME_DIRECTORY,
+ (0, 0, _("%s: Directory has been renamed from %s"),
+ quotearg_colon (name_buffer),
+ quote_n (1, d->name)));
+ directory->orig = d;
+ DIR_SET_FLAG (directory, DIRF_RENAMED);
+ dirlist_replace_prefix (d->name, name_buffer);
+ }
+ directory->children = CHANGED_CHILDREN;
+ }
+ else
+ {
+ DIR_SET_FLAG (directory, DIRF_NEW);
+ WARNOPT (WARN_NEW_DIRECTORY,
+ (0, 0, _("%s: Directory is new"),
+ quotearg_colon (name_buffer)));
+ directory->children =
+ (listed_incremental_option
+ || (OLDER_STAT_TIME (*stat_data, m)
+ || (after_date_option
+ && OLDER_STAT_TIME (*stat_data, c))))
+ ? ALL_CHILDREN
+ : CHANGED_CHILDREN;
+ }
+ }
+
+ if (one_file_system_option && st->parent
+ && stat_data->st_dev != st->parent->stat.st_dev)
+ {
+ WARNOPT (WARN_XDEV,
+ (0, 0,
+ _("%s: directory is on a different filesystem; not dumped"),
+ quotearg_colon (directory->name)));
+ directory->children = NO_CHILDREN;
+ /* If there is any dumpdir info in that directory, remove it */
+ if (directory->dump)
+ {
+ dumpdir_free (directory->dump);
+ directory->dump = NULL;
+ }
+ perhaps_renamed = false;
+ }
+
+ else if (flag & PD_FORCE_CHILDREN)
+ {
+ directory->children = PD_CHILDREN(flag);
+ if (directory->children == NO_CHILDREN)
+ *entry = 'N';
+ }
+
+ if (perhaps_renamed)
+ WARNOPT (WARN_RENAME_DIRECTORY,
+ (0, 0, _("%s: Directory has been renamed"),
+ quotearg_colon (name_buffer)));
+
+ DIR_SET_FLAG (directory, DIRF_INIT);
+
+ if (directory->children != NO_CHILDREN)
+ {
+ const char *tag_file_name;
+
+ switch (check_exclusion_tags (st, &tag_file_name))
+ {
+ case exclusion_tag_all:
+ /* This warning can be duplicated by code in dump_file0, but only
+ in case when the topmost directory being archived contains
+ an exclusion tag. */
+ exclusion_tag_warning (name_buffer, tag_file_name,
+ _("directory not dumped"));
+ *entry = 'N';
+ directory->children = NO_CHILDREN;
+ break;
+
+ case exclusion_tag_contents:
+ exclusion_tag_warning (name_buffer, tag_file_name,
+ _("contents not dumped"));
+ directory->children = NO_CHILDREN;
+ directory->tagfile = tag_file_name;
+ break;
+
+ case exclusion_tag_under:
+ exclusion_tag_warning (name_buffer, tag_file_name,
+ _("contents not dumped"));
+ directory->tagfile = tag_file_name;
+ break;
+
+ case exclusion_tag_none:
+ break;
+ }
+ }
+
+ return directory;
+}
+
+/* Compare dumpdir array from DIRECTORY with directory listing DIR and
+ build a new dumpdir template.
+
+ DIR must be returned by a previous call to savedir().
+
+ File names in DIRECTORY->dump->contents must be sorted
+ alphabetically.
+
+ DIRECTORY->dump is replaced with the created template. Each entry is
+ prefixed with ' ' if it was present in DUMP and with 'Y' otherwise. */
+
+static void
+makedumpdir (struct directory *directory, const char *dir)
+{
+ size_t i,
+ dirsize, /* Number of elements in DIR */
+ len; /* Length of DIR, including terminating nul */
+ const char *p;
+ char const **array;
+ char *new_dump, *new_dump_ptr;
+ struct dumpdir *dump;
+
+ if (directory->children == ALL_CHILDREN)
+ dump = NULL;
+ else if (DIR_IS_RENAMED (directory))
+ dump = directory->orig->idump ?
+ directory->orig->idump : directory->orig->dump;
+ else
+ dump = directory->dump;
+
+ /* Count the size of DIR and the number of elements it contains */
+ dirsize = 0;
+ len = 0;
+ for (p = dir; *p; p += strlen (p) + 1, dirsize++)
+ len += strlen (p) + 2;
+ len++;
+
+ /* Create a sorted directory listing */
+ array = xcalloc (dirsize, sizeof array[0]);
+ for (i = 0, p = dir; *p; p += strlen (p) + 1, i++)
+ array[i] = p;
+
+ qsort (array, dirsize, sizeof (array[0]), compare_dirnames);
+
+ /* Prepare space for new dumpdir */
+ new_dump = xmalloc (len);
+ new_dump_ptr = new_dump;
+
+ /* Fill in the dumpdir template */
+ for (i = 0; i < dirsize; i++)
+ {
+ const char *loc = dumpdir_locate (dump, array[i]);
+ if (loc)
+ {
+ if (directory->tagfile)
+ *new_dump_ptr = 'I';
+ else
+ *new_dump_ptr = ' ';
+ new_dump_ptr++;
+ }
+ else if (directory->tagfile)
+ *new_dump_ptr++ = 'I';
+ else
+ *new_dump_ptr++ = 'Y'; /* New entry */
+
+ /* Copy the file name */
+ for (p = array[i]; (*new_dump_ptr++ = *p++); )
+ ;
+ }
+ *new_dump_ptr = 0;
+ directory->idump = directory->dump;
+ directory->dump = dumpdir_create0 (new_dump, NULL);
+ free (new_dump);
+ free (array);
+}
+
+/* Create a dumpdir containing only one entry: that for the
+ tagfile. */
+static void
+maketagdumpdir (struct directory *directory)
+{
+ size_t len = strlen (directory->tagfile) + 1;
+ char *new_dump = xmalloc (len + 2);
+ new_dump[0] = 'Y';
+ memcpy (new_dump + 1, directory->tagfile, len);
+ new_dump[len + 1] = 0;
+
+ directory->idump = directory->dump;
+ directory->dump = dumpdir_create0 (new_dump, NULL);
+ free (new_dump);
+}
+
+/* Recursively scan the directory identified by ST. */
+struct directory *
+scan_directory (struct tar_stat_info *st)
+{
+ char const *dir = st->orig_file_name;
+ char *dirp = get_directory_entries (st);
+ dev_t device = st->stat.st_dev;
+ bool cmdline = ! st->parent;
+ namebuf_t nbuf;
+ char *tmp;
+ struct directory *directory;
+ char ch;
+
+ if (! dirp)
+ savedir_error (dir);
+
+ info_attach_exclist (st);
+
+ tmp = xstrdup (dir);
+ zap_slashes (tmp);
+
+ directory = procdir (tmp, st,
+ (cmdline ? PD_FORCE_INIT : 0),
+ &ch);
+
+ free (tmp);
+
+ nbuf = namebuf_create (dir);
+
+ if (dirp)
+ {
+ if (directory->children != NO_CHILDREN)
+ {
+ char *entry; /* directory entry being scanned */
+ struct dumpdir_iter *itr;
+
+ makedumpdir (directory, dirp);
+
+ for (entry = dumpdir_first (directory->dump, 1, &itr);
+ entry;
+ entry = dumpdir_next (itr))
+ {
+ char *full_name = namebuf_name (nbuf, entry + 1);
+
+ if (*entry == 'I') /* Ignored entry */
+ *entry = 'N';
+ else if (excluded_name (full_name, st))
+ *entry = 'N';
+ else
+ {
+ int fd = st->fd;
+ void (*diag) (char const *) = 0;
+ struct tar_stat_info stsub;
+ tar_stat_init (&stsub);
+
+ if (fd < 0)
+ {
+ errno = - fd;
+ diag = open_diag;
+ }
+ else if (fstatat (fd, entry + 1, &stsub.stat,
+ fstatat_flags) != 0)
+ diag = stat_diag;
+ else if (S_ISDIR (stsub.stat.st_mode))
+ {
+ int subfd = subfile_open (st, entry + 1,
+ open_read_flags);
+ if (subfd < 0)
+ diag = open_diag;
+ else
+ {
+ stsub.fd = subfd;
+ if (fstat (subfd, &stsub.stat) != 0)
+ diag = stat_diag;
+ }
+ }
+
+ if (diag)
+ {
+ file_removed_diag (full_name, false, diag);
+ *entry = 'N';
+ }
+ else if (S_ISDIR (stsub.stat.st_mode))
+ {
+ int pd_flag = 0;
+ if (!recursion_option)
+ pd_flag |= PD_FORCE_CHILDREN | NO_CHILDREN;
+ else if (directory->children == ALL_CHILDREN)
+ pd_flag |= PD_FORCE_CHILDREN | ALL_CHILDREN;
+ *entry = 'D';
+
+ stsub.parent = st;
+ procdir (full_name, &stsub, pd_flag, entry);
+ restore_parent_fd (&stsub);
+ }
+ else if (one_file_system_option &&
+ device != stsub.stat.st_dev)
+ *entry = 'N';
+ else if (*entry == 'Y')
+ /* New entry, skip further checks */;
+ /* FIXME: if (S_ISHIDDEN (stat_data.st_mode))?? */
+ else if (OLDER_STAT_TIME (stsub.stat, m)
+ && (!after_date_option
+ || OLDER_STAT_TIME (stsub.stat, c)))
+ *entry = 'N';
+ else
+ *entry = 'Y';
+
+ tar_stat_destroy (&stsub);
+ }
+ }
+ free (itr);
+ }
+ else if (directory->tagfile)
+ maketagdumpdir (directory);
+ }
+
+ namebuf_free (nbuf);
+
+ free (dirp);
+
+ return directory;
+}
+
+/* Return pointer to the contents of the directory DIR */
+const char *
+directory_contents (struct directory *dir)
+{
+ if (!dir)
+ return NULL;
+ return dir->dump ? dir->dump->contents : NULL;
+}
+
+/* A "safe" version of directory_contents, which never returns NULL. */
+const char *
+safe_directory_contents (struct directory *dir)
+{
+ const char *ret = directory_contents (dir);
+ return ret ? ret : "\0\0\0\0";
+}
+
+
+static void
+obstack_code_rename (struct obstack *stk, char const *from, char const *to)
+{
+ char const *s;
+
+ s = from[0] == 0 ? from :
+ safer_name_suffix (from, false, absolute_names_option);
+ obstack_1grow (stk, 'R');
+ obstack_grow (stk, s, strlen (s) + 1);
+
+ s = to[0] == 0 ? to:
+ safer_name_suffix (to, false, absolute_names_option);
+ obstack_1grow (stk, 'T');
+ obstack_grow (stk, s, strlen (s) + 1);
+}
+
+static void
+store_rename (struct directory *dir, struct obstack *stk)
+{
+ if (DIR_IS_RENAMED (dir))
+ {
+ struct directory *prev, *p;
+
+ /* Detect eventual cycles and clear DIRF_RENAMED flag, so these entries
+ are ignored when hit by this function next time.
+ If the chain forms a cycle, prev points to the entry DIR is renamed
+ from. In this case it still retains DIRF_RENAMED flag, which will be
+ cleared in the 'else' branch below */
+ for (prev = dir; prev && prev->orig != dir; prev = prev->orig)
+ DIR_CLEAR_FLAG (prev, DIRF_RENAMED);
+
+ if (prev == NULL)
+ {
+ for (p = dir; p && p->orig; p = p->orig)
+ obstack_code_rename (stk, p->orig->name, p->name);
+ }
+ else
+ {
+ char *temp_name;
+
+ DIR_CLEAR_FLAG (prev, DIRF_RENAMED);
+
+ /* Break the cycle by using a temporary name for one of its
+ elements.
+ First, create a temp name stub entry. */
+ temp_name = dir_name (dir->name);
+ obstack_1grow (stk, 'X');
+ obstack_grow (stk, temp_name, strlen (temp_name) + 1);
+
+ obstack_code_rename (stk, dir->name, "");
+
+ for (p = dir; p != prev; p = p->orig)
+ obstack_code_rename (stk, p->orig->name, p->name);
+
+ obstack_code_rename (stk, "", prev->name);
+ }
+ }
+}
+
+void
+append_incremental_renames (struct directory *dir)
+{
+ struct obstack stk;
+ size_t size;
+ struct directory *dp;
+ const char *dump;
+
+ if (dirhead == NULL)
+ return;
+
+ obstack_init (&stk);
+ dump = directory_contents (dir);
+ if (dump)
+ {
+ size = dumpdir_size (dump) - 1;
+ obstack_grow (&stk, dump, size);
+ }
+ else
+ size = 0;
+
+ for (dp = dirhead; dp; dp = dp->next)
+ store_rename (dp, &stk);
+
+ /* FIXME: Is this the right thing to do when DIR is null? */
+ if (dir && obstack_object_size (&stk) != size)
+ {
+ obstack_1grow (&stk, 0);
+ dumpdir_free (dir->dump);
+ dir->dump = dumpdir_create (obstack_finish (&stk));
+ }
+ obstack_free (&stk, NULL);
+}
+
+
+
+static FILE *listed_incremental_stream;
+
+/* Version of incremental format snapshots (directory files) used by this
+ tar. Currently it is supposed to be a single decimal number. 0 means
+ incremental snapshots as per tar version before 1.15.2.
+
+ The current tar version supports incremental versions from
+ 0 up to TAR_INCREMENTAL_VERSION, inclusive.
+ It is able to create only snapshots of TAR_INCREMENTAL_VERSION */
+
+#define TAR_INCREMENTAL_VERSION 2
+
+/* Read incremental snapshot formats 0 and 1 */
+static void
+read_incr_db_01 (int version, const char *initbuf)
+{
+ int n;
+ uintmax_t u;
+ char *buf = NULL;
+ size_t bufsize = 0;
+ char *ebuf;
+ long lineno = 1;
+
+ if (version == 1)
+ {
+ if (getline (&buf, &bufsize, listed_incremental_stream) <= 0)
+ {
+ read_error (listed_incremental_option);
+ free (buf);
+ return;
+ }
+ ++lineno;
+ }
+ else
+ {
+ buf = strdup (initbuf);
+ bufsize = strlen (buf) + 1;
+ }
+
+ newer_mtime_option = decode_timespec (buf, &ebuf, false);
+
+ if (! valid_timespec (newer_mtime_option))
+ ERROR ((0, errno, "%s:%ld: %s",
+ quotearg_colon (listed_incremental_option),
+ lineno,
+ _("Invalid time stamp")));
+ else
+ {
+ if (version == 1 && *ebuf)
+ {
+ char const *buf_ns = ebuf + 1;
+ errno = 0;
+ u = strtoumax (buf_ns, &ebuf, 10);
+ if (!errno && BILLION <= u)
+ errno = ERANGE;
+ if (errno || buf_ns == ebuf)
+ {
+ ERROR ((0, errno, "%s:%ld: %s",
+ quotearg_colon (listed_incremental_option),
+ lineno,
+ _("Invalid time stamp")));
+ newer_mtime_option.tv_sec = TYPE_MINIMUM (time_t);
+ newer_mtime_option.tv_nsec = -1;
+ }
+ else
+ newer_mtime_option.tv_nsec = u;
+ }
+ }
+
+ while (0 < (n = getline (&buf, &bufsize, listed_incremental_stream)))
+ {
+ dev_t dev;
+ ino_t ino;
+ bool nfs = buf[0] == '+';
+ char *strp = buf + nfs;
+ struct timespec mtime;
+
+ lineno++;
+
+ if (buf[n - 1] == '\n')
+ buf[n - 1] = '\0';
+
+ if (version == 1)
+ {
+ mtime = decode_timespec (strp, &ebuf, false);
+ strp = ebuf;
+ if (!valid_timespec (mtime) || *strp != ' ')
+ ERROR ((0, errno, "%s:%ld: %s",
+ quotearg_colon (listed_incremental_option), lineno,
+ _("Invalid modification time")));
+
+ errno = 0;
+ u = strtoumax (strp, &ebuf, 10);
+ if (!errno && BILLION <= u)
+ errno = ERANGE;
+ if (errno || strp == ebuf || *ebuf != ' ')
+ {
+ ERROR ((0, errno, "%s:%ld: %s",
+ quotearg_colon (listed_incremental_option), lineno,
+ _("Invalid modification time (nanoseconds)")));
+ mtime.tv_nsec = -1;
+ }
+ else
+ mtime.tv_nsec = u;
+ strp = ebuf;
+ }
+ else
+ mtime.tv_sec = mtime.tv_nsec = 0;
+
+ dev = strtosysint (strp, &ebuf,
+ TYPE_MINIMUM (dev_t), TYPE_MAXIMUM (dev_t));
+ strp = ebuf;
+ if (errno || *strp != ' ')
+ ERROR ((0, errno, "%s:%ld: %s",
+ quotearg_colon (listed_incremental_option), lineno,
+ _("Invalid device number")));
+
+ ino = strtosysint (strp, &ebuf,
+ TYPE_MINIMUM (ino_t), TYPE_MAXIMUM (ino_t));
+ strp = ebuf;
+ if (errno || *strp != ' ')
+ ERROR ((0, errno, "%s:%ld: %s",
+ quotearg_colon (listed_incremental_option), lineno,
+ _("Invalid inode number")));
+
+ strp++;
+ unquote_string (strp);
+ note_directory (strp, mtime, dev, ino, nfs, false, NULL);
+ }
+ free (buf);
+}
+
+/* Read a nul-terminated string from FP and store it in STK.
+ Store the number of bytes read (including nul terminator) in PCOUNT.
+
+ Return the last character read or EOF on end of file. */
+static int
+read_obstack (FILE *fp, struct obstack *stk, size_t *pcount)
+{
+ int c;
+ size_t i;
+
+ for (i = 0, c = getc (fp); c != EOF && c != 0; c = getc (fp), i++)
+ obstack_1grow (stk, c);
+ obstack_1grow (stk, 0);
+
+ *pcount = i;
+ return c;
+}
+
+/* Read from file FP a null-terminated string and convert it to an
+ integer. FIELDNAME is the intended use of the integer, useful for
+ diagnostics. MIN_VAL and MAX_VAL are its minimum and maximum
+ permissible values; MIN_VAL must be nonpositive and MAX_VAL positive.
+ Store into *PVAL the resulting value, converted to intmax_t.
+
+ Throw a fatal error if the string cannot be converted or if the
+ converted value is out of range.
+
+ Return true if successful, false if end of file. */
+
+static bool
+read_num (FILE *fp, char const *fieldname,
+ intmax_t min_val, uintmax_t max_val, intmax_t *pval)
+{
+ int i;
+ char buf[INT_BUFSIZE_BOUND (intmax_t)];
+ char offbuf[INT_BUFSIZE_BOUND (off_t)];
+ char minbuf[INT_BUFSIZE_BOUND (intmax_t)];
+ char maxbuf[INT_BUFSIZE_BOUND (intmax_t)];
+ int conversion_errno;
+ int c = getc (fp);
+ bool negative = c == '-';
+
+ for (i = 0; (i == 0 && negative) || ISDIGIT (c); i++)
+ {
+ buf[i] = c;
+ if (i == sizeof buf - 1)
+ FATAL_ERROR ((0, 0,
+ _("%s: byte %s: %s %.*s... too long"),
+ quotearg_colon (listed_incremental_option),
+ offtostr (ftello (fp), offbuf),
+ fieldname, i + 1, buf));
+ c = getc (fp);
+ }
+
+ buf[i] = 0;
+
+ if (c < 0)
+ {
+ if (ferror (fp))
+ read_fatal (listed_incremental_option);
+ if (i != 0)
+ FATAL_ERROR ((0, 0, "%s: %s",
+ quotearg_colon (listed_incremental_option),
+ _("Unexpected EOF in snapshot file")));
+ return false;
+ }
+
+ if (c)
+ {
+ unsigned uc = c;
+ FATAL_ERROR ((0, 0,
+ _("%s: byte %s: %s %s followed by invalid byte 0x%02x"),
+ quotearg_colon (listed_incremental_option),
+ offtostr (ftello (fp), offbuf),
+ fieldname, buf, uc));
+ }
+
+ *pval = strtosysint (buf, NULL, min_val, max_val);
+ conversion_errno = errno;
+
+ switch (conversion_errno)
+ {
+ case ERANGE:
+ FATAL_ERROR ((0, conversion_errno,
+ _("%s: byte %s: (valid range %s..%s)\n\t%s %s"),
+ quotearg_colon (listed_incremental_option),
+ offtostr (ftello (fp), offbuf),
+ imaxtostr (min_val, minbuf),
+ umaxtostr (max_val, maxbuf), fieldname, buf));
+ default:
+ FATAL_ERROR ((0, conversion_errno,
+ _("%s: byte %s: %s %s"),
+ quotearg_colon (listed_incremental_option),
+ offtostr (ftello (fp), offbuf), fieldname, buf));
+ case 0:
+ break;
+ }
+
+ return true;
+}
+
+/* Read from FP two NUL-terminated strings representing a struct
+ timespec. Return the resulting value in PVAL.
+
+ Throw a fatal error if the string cannot be converted. */
+
+static void
+read_timespec (FILE *fp, struct timespec *pval)
+{
+ intmax_t s, ns;
+
+ if (read_num (fp, "sec", TYPE_MINIMUM (time_t), TYPE_MAXIMUM (time_t), &s)
+ && read_num (fp, "nsec", 0, BILLION - 1, &ns))
+ {
+ pval->tv_sec = s;
+ pval->tv_nsec = ns;
+ }
+ else
+ {
+ FATAL_ERROR ((0, 0, "%s: %s",
+ quotearg_colon (listed_incremental_option),
+ _("Unexpected EOF in snapshot file")));
+ }
+}
+
+/* Read incremental snapshot format 2 */
+static void
+read_incr_db_2 (void)
+{
+ struct obstack stk;
+ char offbuf[INT_BUFSIZE_BOUND (off_t)];
+
+ obstack_init (&stk);
+
+ read_timespec (listed_incremental_stream, &newer_mtime_option);
+
+ for (;;)
+ {
+ intmax_t i;
+ struct timespec mtime;
+ dev_t dev;
+ ino_t ino;
+ bool nfs;
+ char *name;
+ char *content;
+ size_t s;
+
+ if (! read_num (listed_incremental_stream, "nfs", 0, 1, &i))
+ return; /* Normal return */
+
+ nfs = i;
+
+ read_timespec (listed_incremental_stream, &mtime);
+
+ if (! read_num (listed_incremental_stream, "dev",
+ TYPE_MINIMUM (dev_t), TYPE_MAXIMUM (dev_t), &i))
+ break;
+ dev = i;
+
+ if (! read_num (listed_incremental_stream, "ino",
+ TYPE_MINIMUM (ino_t), TYPE_MAXIMUM (ino_t), &i))
+ break;
+ ino = i;
+
+ if (read_obstack (listed_incremental_stream, &stk, &s))
+ break;
+
+ name = obstack_finish (&stk);
+
+ while (read_obstack (listed_incremental_stream, &stk, &s) == 0 && s > 1)
+ ;
+ if (getc (listed_incremental_stream) != 0)
+ FATAL_ERROR ((0, 0, _("%s: byte %s: %s"),
+ quotearg_colon (listed_incremental_option),
+ offtostr (ftello (listed_incremental_stream), offbuf),
+ _("Missing record terminator")));
+
+ content = obstack_finish (&stk);
+ note_directory (name, mtime, dev, ino, nfs, false, content);
+ obstack_free (&stk, content);
+ }
+ FATAL_ERROR ((0, 0, "%s: %s",
+ quotearg_colon (listed_incremental_option),
+ _("Unexpected EOF in snapshot file")));
+}
+
+/* Display (to stdout) the range of allowed values for each field
+ in the snapshot file. The array below should be kept in sync
+ with any changes made to the read_num() calls in the parsing
+ loop inside read_incr_db_2().
+
+ (This function is invoked via the --show-snapshot-field-ranges
+ command line option.) */
+
+struct field_range
+{
+ char const *fieldname;
+ intmax_t min_val;
+ uintmax_t max_val;
+};
+
+static struct field_range const field_ranges[] = {
+ { "nfs", 0, 1 },
+ { "timestamp_sec", TYPE_MINIMUM (time_t), TYPE_MAXIMUM (time_t) },
+ { "timestamp_nsec", 0, BILLION - 1 },
+ { "dev", TYPE_MINIMUM (dev_t), TYPE_MAXIMUM (dev_t) },
+ { "ino", TYPE_MINIMUM (ino_t), TYPE_MAXIMUM (ino_t) },
+ { NULL, 0, 0 }
+};
+
+void
+show_snapshot_field_ranges (void)
+{
+ struct field_range const *p;
+ char minbuf[SYSINT_BUFSIZE];
+ char maxbuf[SYSINT_BUFSIZE];
+
+ printf("This tar's snapshot file field ranges are\n");
+ printf (" (%-15s => [ %s, %s ]):\n\n", "field name", "min", "max");
+
+ for (p=field_ranges; p->fieldname != NULL; p++)
+ {
+ printf (" %-15s => [ %s, %s ],\n", p->fieldname,
+ sysinttostr (p->min_val, p->min_val, p->max_val, minbuf),
+ sysinttostr (p->max_val, p->min_val, p->max_val, maxbuf));
+
+ }
+
+ printf("\n");
+}
+
+/* Read incremental snapshot file (directory file).
+ If the file has older incremental version, make sure that it is processed
+ correctly and that tar will use the most conservative backup method among
+ possible alternatives (i.e. prefer ALL_CHILDREN over CHANGED_CHILDREN,
+ etc.) This ensures that the snapshots are updated to the recent version
+ without any loss of data. */
+void
+read_directory_file (void)
+{
+ int fd;
+ char *buf = NULL;
+ size_t bufsize = 0;
+ int flags = O_RDWR | O_CREAT;
+
+ if (incremental_level == 0)
+ flags |= O_TRUNC;
+ /* Open the file for both read and write. That way, we can write
+ it later without having to reopen it, and don't have to worry if
+ we chdir in the meantime. */
+ fd = open (listed_incremental_option, flags, MODE_RW);
+ if (fd < 0)
+ {
+ open_error (listed_incremental_option);
+ return;
+ }
+
+ listed_incremental_stream = fdopen (fd, "r+");
+ if (! listed_incremental_stream)
+ {
+ open_error (listed_incremental_option);
+ close (fd);
+ return;
+ }
+
+ /* Consume the first name from the name list and reset the
+ list afterwards. This is done to change to the new
+ directory, if the first name is a chdir request (-C dir),
+ which is necessary to recreate absolute file names. */
+ name_from_list ();
+ blank_name_list ();
+
+ if (0 < getline (&buf, &bufsize, listed_incremental_stream))
+ {
+ char *ebuf;
+ uintmax_t incremental_version;
+
+ if (strncmp (buf, PACKAGE_NAME, sizeof PACKAGE_NAME - 1) == 0)
+ {
+ ebuf = buf + sizeof PACKAGE_NAME - 1;
+ if (*ebuf++ != '-')
+ ERROR((1, 0, _("Bad incremental file format")));
+ for (; *ebuf != '-'; ebuf++)
+ if (!*ebuf)
+ ERROR((1, 0, _("Bad incremental file format")));
+
+ incremental_version = strtoumax (ebuf + 1, NULL, 10);
+ }
+ else
+ incremental_version = 0;
+
+ switch (incremental_version)
+ {
+ case 0:
+ case 1:
+ read_incr_db_01 (incremental_version, buf);
+ break;
+
+ case TAR_INCREMENTAL_VERSION:
+ read_incr_db_2 ();
+ break;
+
+ default:
+ ERROR ((1, 0, _("Unsupported incremental format version: %"PRIuMAX),
+ incremental_version));
+ }
+
+ }
+
+ if (ferror (listed_incremental_stream))
+ read_error (listed_incremental_option);
+ free (buf);
+}
+
+/* Output incremental data for the directory ENTRY to the file DATA.
+ Return nonzero if successful, preserving errno on write failure. */
+static bool
+write_directory_file_entry (void *entry, void *data)
+{
+ struct directory const *directory = entry;
+ FILE *fp = data;
+
+ if (DIR_IS_FOUND (directory))
+ {
+ char buf[SYSINT_BUFSIZE];
+ char const *s;
+
+ s = DIR_IS_NFS (directory) ? "1" : "0";
+ fwrite (s, 2, 1, fp);
+ s = sysinttostr (directory->mtime.tv_sec, TYPE_MINIMUM (time_t),
+ TYPE_MAXIMUM (time_t), buf);
+ fwrite (s, strlen (s) + 1, 1, fp);
+ s = imaxtostr (directory->mtime.tv_nsec, buf);
+ fwrite (s, strlen (s) + 1, 1, fp);
+ s = sysinttostr (directory->device_number,
+ TYPE_MINIMUM (dev_t), TYPE_MAXIMUM (dev_t), buf);
+ fwrite (s, strlen (s) + 1, 1, fp);
+ s = sysinttostr (directory->inode_number,
+ TYPE_MINIMUM (ino_t), TYPE_MAXIMUM (ino_t), buf);
+ fwrite (s, strlen (s) + 1, 1, fp);
+
+ fwrite (directory->name, strlen (directory->name) + 1, 1, fp);
+ if (directory->dump)
+ {
+ const char *p;
+ struct dumpdir_iter *itr;
+
+ for (p = dumpdir_first (directory->dump, 0, &itr);
+ p;
+ p = dumpdir_next (itr))
+ fwrite (p, strlen (p) + 1, 1, fp);
+ free (itr);
+ }
+ fwrite ("\0\0", 2, 1, fp);
+ }
+
+ return ! ferror (fp);
+}
+
+void
+write_directory_file (void)
+{
+ FILE *fp = listed_incremental_stream;
+ char buf[UINTMAX_STRSIZE_BOUND];
+ char *s;
+
+ if (! fp)
+ return;
+
+ if (fseeko (fp, 0L, SEEK_SET) != 0)
+ seek_error (listed_incremental_option);
+ if (sys_truncate (fileno (fp)) != 0)
+ truncate_error (listed_incremental_option);
+
+ fprintf (fp, "%s-%s-%d\n", PACKAGE_NAME, PACKAGE_VERSION,
+ TAR_INCREMENTAL_VERSION);
+
+ s = (TYPE_SIGNED (time_t)
+ ? imaxtostr (start_time.tv_sec, buf)
+ : umaxtostr (start_time.tv_sec, buf));
+ fwrite (s, strlen (s) + 1, 1, fp);
+ s = umaxtostr (start_time.tv_nsec, buf);
+ fwrite (s, strlen (s) + 1, 1, fp);
+
+ if (! ferror (fp) && directory_table)
+ hash_do_for_each (directory_table, write_directory_file_entry, fp);
+
+ if (ferror (fp))
+ write_error (listed_incremental_option);
+ if (fclose (fp) != 0)
+ close_error (listed_incremental_option);
+}
+
+
+/* Restoration of incremental dumps. */
+
+static void
+get_gnu_dumpdir (struct tar_stat_info *stat_info)
+{
+ size_t size;
+ size_t copied;
+ union block *data_block;
+ char *to;
+ char *archive_dir;
+
+ size = stat_info->stat.st_size;
+
+ archive_dir = xmalloc (size);
+ to = archive_dir;
+
+ set_next_block_after (current_header);
+ mv_begin_read (stat_info);
+
+ for (; size > 0; size -= copied)
+ {
+ mv_size_left (size);
+ data_block = find_next_block ();
+ if (!data_block)
+ ERROR ((1, 0, _("Unexpected EOF in archive")));
+ copied = available_space_after (data_block);
+ if (copied > size)
+ copied = size;
+ memcpy (to, data_block->buffer, copied);
+ to += copied;
+ set_next_block_after ((union block *)
+ (data_block->buffer + copied - 1));
+ }
+
+ mv_end ();
+
+ stat_info->dumpdir = archive_dir;
+ stat_info->skipped = true; /* For skip_member() and friends
+ to work correctly */
+}
+
+/* Return T if STAT_INFO represents a dumpdir archive member.
+ Note: can invalidate current_header. It happens if flush_archive()
+ gets called within get_gnu_dumpdir() */
+bool
+is_dumpdir (struct tar_stat_info *stat_info)
+{
+ if (stat_info->is_dumpdir && !stat_info->dumpdir)
+ get_gnu_dumpdir (stat_info);
+ return stat_info->is_dumpdir;
+}
+
+static bool
+dumpdir_ok (char *dumpdir)
+{
+ char *p;
+ int has_tempdir = 0;
+ int expect = 0;
+
+ for (p = dumpdir; *p; p += strlen (p) + 1)
+ {
+ if (expect && *p != expect)
+ {
+ unsigned char uc = *p;
+ ERROR ((0, 0,
+ _("Malformed dumpdir: expected '%c' but found %#3o"),
+ expect, uc));
+ return false;
+ }
+ switch (*p)
+ {
+ case 'X':
+ if (has_tempdir)
+ {
+ ERROR ((0, 0,
+ _("Malformed dumpdir: 'X' duplicated")));
+ return false;
+ }
+ else
+ has_tempdir = 1;
+ break;
+
+ case 'R':
+ if (p[1] == 0)
+ {
+ if (!has_tempdir)
+ {
+ ERROR ((0, 0,
+ _("Malformed dumpdir: empty name in 'R'")));
+ return false;
+ }
+ else
+ has_tempdir = 0;
+ }
+ expect = 'T';
+ break;
+
+ case 'T':
+ if (expect != 'T')
+ {
+ ERROR ((0, 0,
+ _("Malformed dumpdir: 'T' not preceded by 'R'")));
+ return false;
+ }
+ if (p[1] == 0 && !has_tempdir)
+ {
+ ERROR ((0, 0,
+ _("Malformed dumpdir: empty name in 'T'")));
+ return false;
+ }
+ expect = 0;
+ break;
+
+ case 'N':
+ case 'Y':
+ case 'D':
+ break;
+
+ default:
+ /* FIXME: bail out? */
+ break;
+ }
+ }
+
+ if (expect)
+ {
+ ERROR ((0, 0,
+ _("Malformed dumpdir: expected '%c' but found end of data"),
+ expect));
+ return false;
+ }
+
+ if (has_tempdir)
+ WARNOPT (WARN_BAD_DUMPDIR,
+ (0, 0, _("Malformed dumpdir: 'X' never used")));
+
+ return true;
+}
+
+/* Examine the directories under directory_name and delete any
+ files that were not there at the time of the back-up. */
+static bool
+try_purge_directory (char const *directory_name)
+{
+ char *current_dir;
+ char *cur, *arc, *p;
+ char *temp_stub = NULL;
+ struct dumpdir *dump;
+
+ if (!is_dumpdir (&current_stat_info))
+ return false;
+
+ current_dir = tar_savedir (directory_name, 0);
+
+ if (!current_dir)
+ /* The directory doesn't exist now. It'll be created. In any
+ case, we don't have to delete any files out of it. */
+ return false;
+
+ /* Verify if dump directory is sane */
+ if (!dumpdir_ok (current_stat_info.dumpdir))
+ return false;
+
+ /* Process renames */
+ for (arc = current_stat_info.dumpdir; *arc; arc += strlen (arc) + 1)
+ {
+ if (*arc == 'X')
+ {
+#define TEMP_DIR_TEMPLATE "tar.XXXXXX"
+ size_t len = strlen (arc + 1);
+ temp_stub = xrealloc (temp_stub, len + 1 + sizeof TEMP_DIR_TEMPLATE);
+ memcpy (temp_stub, arc + 1, len);
+ temp_stub[len] = '/';
+ memcpy (temp_stub + len + 1, TEMP_DIR_TEMPLATE,
+ sizeof TEMP_DIR_TEMPLATE);
+ if (!mkdtemp (temp_stub))
+ {
+ ERROR ((0, errno,
+ _("Cannot create temporary directory using template %s"),
+ quote (temp_stub)));
+ free (temp_stub);
+ free (current_dir);
+ return false;
+ }
+ }
+ else if (*arc == 'R')
+ {
+ char *src, *dst;
+ src = arc + 1;
+ arc += strlen (arc) + 1;
+ dst = arc + 1;
+
+ /* Ensure that neither source nor destination are absolute file
+ names (unless permitted by -P option), and that they do not
+ contain dubious parts (e.g. ../).
+
+ This is an extra safety precaution. Besides, it might be
+ necessary to extract from archives created with tar versions
+ prior to 1.19. */
+
+ if (*src)
+ src = safer_name_suffix (src, false, absolute_names_option);
+ if (*dst)
+ dst = safer_name_suffix (dst, false, absolute_names_option);
+
+ if (*src == 0)
+ src = temp_stub;
+ else if (*dst == 0)
+ dst = temp_stub;
+
+ if (!rename_directory (src, dst))
+ {
+ free (temp_stub);
+ free (current_dir);
+ /* FIXME: Make sure purge_directory(dst) will return
+ immediately */
+ return false;
+ }
+ }
+ }
+
+ free (temp_stub);
+
+ /* Process deletes */
+ dump = dumpdir_create (current_stat_info.dumpdir);
+ p = NULL;
+ for (cur = current_dir; *cur; cur += strlen (cur) + 1)
+ {
+ const char *entry;
+ struct stat st;
+ free (p);
+ p = make_file_name (directory_name, cur);
+
+ if (deref_stat (p, &st) != 0)
+ {
+ if (errno != ENOENT) /* FIXME: Maybe keep a list of renamed
+ dirs and check it here? */
+ {
+ stat_diag (p);
+ WARN ((0, 0, _("%s: Not purging directory: unable to stat"),
+ quotearg_colon (p)));
+ }
+ continue;
+ }
+
+ if (!(entry = dumpdir_locate (dump, cur))
+ || (*entry == 'D' && !S_ISDIR (st.st_mode))
+ || (*entry == 'Y' && S_ISDIR (st.st_mode)))
+ {
+ if (one_file_system_option && st.st_dev != root_device)
+ {
+ WARN ((0, 0,
+ _("%s: directory is on a different device: not purging"),
+ quotearg_colon (p)));
+ continue;
+ }
+
+ if (! interactive_option || confirm ("delete", p))
+ {
+ if (verbose_option)
+ fprintf (stdlis, _("%s: Deleting %s\n"),
+ program_name, quote (p));
+ if (! remove_any_file (p, RECURSIVE_REMOVE_OPTION))
+ {
+ int e = errno;
+ ERROR ((0, e, _("%s: Cannot remove"), quotearg_colon (p)));
+ }
+ }
+ }
+ }
+ free (p);
+ dumpdir_free (dump);
+
+ free (current_dir);
+ return true;
+}
+
+void
+purge_directory (char const *directory_name)
+{
+ if (!try_purge_directory (directory_name))
+ skip_member ();
+}
+
+void
+list_dumpdir (char *buffer, size_t size)
+{
+ int state = 0;
+ while (size)
+ {
+ switch (*buffer)
+ {
+ case 'Y':
+ case 'N':
+ case 'D':
+ case 'R':
+ case 'T':
+ case 'X':
+ fprintf (stdlis, "%c", *buffer);
+ if (state == 0)
+ {
+ fprintf (stdlis, " ");
+ state = 1;
+ }
+ buffer++;
+ size--;
+ break;
+
+ case 0:
+ fputc ('\n', stdlis);
+ buffer++;
+ size--;
+ state = 0;
+ break;
+
+ default:
+ fputc (*buffer, stdlis);
+ buffer++;
+ size--;
+ }
+ }
+}
diff --git a/src/list.c b/src/list.c
new file mode 100644
index 0000000..8eef25d
--- /dev/null
+++ b/src/list.c
@@ -0,0 +1,1463 @@
+/* List a tar archive, with support routines for reading a tar archive.
+
+ Copyright 1988, 1992-1994, 1996-2001, 2003-2007, 2010, 2012-2016 Free
+ Software Foundation, Inc.
+
+ This file is part of GNU tar.
+
+ GNU tar 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.
+
+ GNU tar 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/>.
+
+ Written by John Gilmore, on 1985-08-26. */
+
+#include <system.h>
+#include <inttostr.h>
+#include <quotearg.h>
+
+#include "common.h"
+
+union block *current_header; /* points to current archive header */
+enum archive_format current_format; /* recognized format */
+union block *recent_long_name; /* recent long name header and contents */
+union block *recent_long_link; /* likewise, for long link */
+size_t recent_long_name_blocks; /* number of blocks in recent_long_name */
+size_t recent_long_link_blocks; /* likewise, for long link */
+static union block *recent_global_header; /* Recent global header block */
+
+#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))
+
+static gid_t gid_from_header (const char *buf, size_t size);
+static major_t major_from_header (const char *buf, size_t size);
+static minor_t minor_from_header (const char *buf, size_t size);
+static mode_t mode_from_header (const char *buf, size_t size, bool *hbits);
+static time_t time_from_header (const char *buf, size_t size);
+static uid_t uid_from_header (const char *buf, size_t size);
+static intmax_t from_header (const char *, size_t, const char *,
+ intmax_t, uintmax_t, bool, bool);
+
+/* Base 64 digits; see Internet 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[UCHAR_MAX + 1];
+
+static void
+base64_init (void)
+{
+ int i;
+ memset (base64_map, 64, sizeof base64_map);
+ for (i = 0; i < 64; i++)
+ base64_map[(int) base_64_digits[i]] = i;
+}
+
+static char *
+decode_xform (char *file_name, void *data)
+{
+ int type = *(int*)data;
+
+ switch (type)
+ {
+ case XFORM_SYMLINK:
+ /* FIXME: It is not quite clear how and to which extent are the symbolic
+ links subject to filename transformation. In the absence of another
+ solution, symbolic links are exempt from component stripping and
+ name suffix normalization, but subject to filename transformation
+ proper. */
+ return file_name;
+
+ case XFORM_LINK:
+ file_name = safer_name_suffix (file_name, true, absolute_names_option);
+ break;
+
+ case XFORM_REGFILE:
+ file_name = safer_name_suffix (file_name, false, absolute_names_option);
+ break;
+ }
+
+ if (strip_name_components)
+ {
+ size_t prefix_len = stripped_prefix_len (file_name,
+ strip_name_components);
+ if (prefix_len == (size_t) -1)
+ prefix_len = strlen (file_name);
+ file_name += prefix_len;
+ }
+ return file_name;
+}
+
+static bool
+transform_member_name (char **pinput, int type)
+{
+ return transform_name_fp (pinput, type, decode_xform, &type);
+}
+
+static void
+enforce_one_top_level (char **pfile_name)
+{
+ char *file_name = *pfile_name;
+ char *p;
+
+ for (p = file_name; *p && (ISSLASH (*p) || *p == '.'); p++)
+ ;
+
+ if (*p)
+ {
+ int pos = strlen (one_top_level_dir);
+ if (strncmp (p, one_top_level_dir, pos) == 0)
+ {
+ if (ISSLASH (p[pos]) || p[pos] == 0)
+ return;
+ }
+
+ *pfile_name = make_file_name (one_top_level_dir, file_name);
+ normalize_filename_x (*pfile_name);
+ }
+ else
+ *pfile_name = xstrdup (one_top_level_dir);
+ free (file_name);
+}
+
+void
+transform_stat_info (int typeflag, struct tar_stat_info *stat_info)
+{
+ if (typeflag == GNUTYPE_VOLHDR)
+ /* Name transformations don't apply to volume headers. */
+ return;
+
+ transform_member_name (&stat_info->file_name, XFORM_REGFILE);
+ switch (typeflag)
+ {
+ case SYMTYPE:
+ transform_member_name (&stat_info->link_name, XFORM_SYMLINK);
+ break;
+
+ case LNKTYPE:
+ transform_member_name (&stat_info->link_name, XFORM_LINK);
+ }
+
+ if (one_top_level_option)
+ enforce_one_top_level (&current_stat_info.file_name);
+}
+
+/* Main loop for reading an archive. */
+void
+read_and (void (*do_something) (void))
+{
+ enum read_header status = HEADER_STILL_UNREAD;
+ enum read_header prev_status;
+ struct timespec mtime;
+
+ base64_init ();
+ name_gather ();
+
+ open_archive (ACCESS_READ);
+ do
+ {
+ prev_status = status;
+ tar_stat_destroy (&current_stat_info);
+
+ status = read_header (&current_header, &current_stat_info,
+ read_header_auto);
+ switch (status)
+ {
+ case HEADER_STILL_UNREAD:
+ case HEADER_SUCCESS_EXTENDED:
+ abort ();
+
+ case HEADER_SUCCESS:
+
+ /* Valid header. We should decode next field (mode) first.
+ Ensure incoming names are null terminated. */
+ decode_header (current_header, &current_stat_info,
+ &current_format, 1);
+ if (! name_match (current_stat_info.file_name)
+ || (TIME_OPTION_INITIALIZED (newer_mtime_option)
+ /* FIXME: We get mtime now, and again later; this causes
+ duplicate diagnostics if header.mtime is bogus. */
+ && ((mtime.tv_sec
+ = TIME_FROM_HEADER (current_header->header.mtime)),
+ /* FIXME: Grab fractional time stamps from
+ extended header. */
+ mtime.tv_nsec = 0,
+ current_stat_info.mtime = mtime,
+ OLDER_TAR_STAT_TIME (current_stat_info, m)))
+ || excluded_name (current_stat_info.file_name,
+ current_stat_info.parent))
+ {
+ switch (current_header->header.typeflag)
+ {
+ case GNUTYPE_VOLHDR:
+ case GNUTYPE_MULTIVOL:
+ break;
+
+ case DIRTYPE:
+ if (show_omitted_dirs_option)
+ WARN ((0, 0, _("%s: Omitting"),
+ quotearg_colon (current_stat_info.file_name)));
+ /* Fall through. */
+ default:
+ skip_member ();
+ continue;
+ }
+ }
+
+ transform_stat_info (current_header->header.typeflag,
+ &current_stat_info);
+ (*do_something) ();
+ continue;
+
+ case HEADER_ZERO_BLOCK:
+ if (block_number_option)
+ {
+ char buf[UINTMAX_STRSIZE_BOUND];
+ fprintf (stdlis, _("block %s: ** Block of NULs **\n"),
+ STRINGIFY_BIGINT (current_block_ordinal (), buf));
+ }
+
+ set_next_block_after (current_header);
+
+ if (!ignore_zeros_option)
+ {
+ char buf[UINTMAX_STRSIZE_BOUND];
+
+ status = read_header (&current_header, &current_stat_info,
+ read_header_auto);
+ if (status == HEADER_ZERO_BLOCK)
+ break;
+ WARNOPT (WARN_ALONE_ZERO_BLOCK,
+ (0, 0, _("A lone zero block at %s"),
+ STRINGIFY_BIGINT (current_block_ordinal (), buf)));
+ break;
+ }
+ status = prev_status;
+ continue;
+
+ case HEADER_END_OF_FILE:
+ if (block_number_option)
+ {
+ char buf[UINTMAX_STRSIZE_BOUND];
+ fprintf (stdlis, _("block %s: ** End of File **\n"),
+ STRINGIFY_BIGINT (current_block_ordinal (), buf));
+ }
+ break;
+
+ case HEADER_FAILURE:
+ /* If the previous header was good, tell them that we are
+ skipping bad ones. */
+ set_next_block_after (current_header);
+ switch (prev_status)
+ {
+ case HEADER_STILL_UNREAD:
+ ERROR ((0, 0, _("This does not look like a tar archive")));
+ /* Fall through. */
+
+ case HEADER_ZERO_BLOCK:
+ case HEADER_SUCCESS:
+ if (block_number_option)
+ {
+ char buf[UINTMAX_STRSIZE_BOUND];
+ off_t block_ordinal = current_block_ordinal ();
+ block_ordinal -= recent_long_name_blocks;
+ block_ordinal -= recent_long_link_blocks;
+ fprintf (stdlis, _("block %s: "),
+ STRINGIFY_BIGINT (block_ordinal, buf));
+ }
+ ERROR ((0, 0, _("Skipping to next header")));
+ break;
+
+ case HEADER_END_OF_FILE:
+ case HEADER_FAILURE:
+ /* We are in the middle of a cascade of errors. */
+ break;
+
+ case HEADER_SUCCESS_EXTENDED:
+ abort ();
+ }
+ continue;
+ }
+ break;
+ }
+ while (!all_names_found (&current_stat_info));
+
+ close_archive ();
+ names_notfound (); /* print names not found */
+}
+
+/* Print a header block, based on tar options. */
+void
+list_archive (void)
+{
+ off_t block_ordinal = current_block_ordinal ();
+
+ /* Print the header block. */
+ if (verbose_option)
+ print_header (&current_stat_info, current_header, block_ordinal);
+
+ if (incremental_option)
+ {
+ if (verbose_option > 2)
+ {
+ if (is_dumpdir (&current_stat_info))
+ list_dumpdir (current_stat_info.dumpdir,
+ dumpdir_size (current_stat_info.dumpdir));
+ }
+ }
+
+ skip_member ();
+}
+
+/* Check header checksum */
+/* The standard BSD tar sources create the checksum by adding up the
+ bytes in the header as type char. I think the type char was unsigned
+ on the PDP-11, but it's signed on the Next and Sun. It looks like the
+ sources to BSD tar were never changed to compute the checksum
+ correctly, so both the Sun and Next add the bytes of the header as
+ signed chars. This doesn't cause a problem until you get a file with
+ a name containing characters with the high bit set. So tar_checksum
+ computes two checksums -- signed and unsigned. */
+
+enum read_header
+tar_checksum (union block *header, bool silent)
+{
+ size_t i;
+ int unsigned_sum = 0; /* the POSIX one :-) */
+ int signed_sum = 0; /* the Sun one :-( */
+ int recorded_sum;
+ int parsed_sum;
+ char *p;
+
+ 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 = from_header (header->header.chksum,
+ sizeof header->header.chksum, 0,
+ 0, INT_MAX, true, silent);
+ 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;
+}
+
+/* Read a block that's supposed to be a header block. Return its
+ address in *RETURN_BLOCK, and if it is good, the file's size
+ and names (file name, link name) in *INFO.
+
+ Return one of enum read_header describing the status of the
+ operation.
+
+ The MODE parameter instructs read_header what to do with special
+ header blocks, i.e.: extended POSIX, GNU long name or long link,
+ etc.:
+
+ read_header_auto process them automatically,
+ read_header_x_raw when a special header is read, return
+ HEADER_SUCCESS_EXTENDED without actually
+ processing the header,
+ read_header_x_global when a POSIX global header is read,
+ decode it and return HEADER_SUCCESS_EXTENDED.
+
+ You must always set_next_block_after(*return_block) to skip past
+ the header which this routine reads. */
+
+enum read_header
+read_header (union block **return_block, struct tar_stat_info *info,
+ enum read_header_mode mode)
+{
+ union block *header;
+ union block *header_copy;
+ char *bp;
+ union block *data_block;
+ size_t size, written;
+ union block *next_long_name = 0;
+ union block *next_long_link = 0;
+ size_t next_long_name_blocks = 0;
+ size_t next_long_link_blocks = 0;
+
+ while (1)
+ {
+ enum read_header status;
+
+ header = find_next_block ();
+ *return_block = header;
+ if (!header)
+ return HEADER_END_OF_FILE;
+
+ if ((status = tar_checksum (header, false)) != HEADER_SUCCESS)
+ return status;
+
+ /* Good block. Decode file size and return. */
+
+ if (header->header.typeflag == LNKTYPE)
+ info->stat.st_size = 0; /* links 0 size on tape */
+ else
+ {
+ info->stat.st_size = OFF_FROM_HEADER (header->header.size);
+ if (info->stat.st_size < 0)
+ return HEADER_FAILURE;
+ }
+
+ if (header->header.typeflag == GNUTYPE_LONGNAME
+ || header->header.typeflag == GNUTYPE_LONGLINK
+ || header->header.typeflag == XHDTYPE
+ || header->header.typeflag == XGLTYPE
+ || header->header.typeflag == SOLARIS_XHDTYPE)
+ {
+ if (mode == read_header_x_raw)
+ return HEADER_SUCCESS_EXTENDED;
+ else if (header->header.typeflag == GNUTYPE_LONGNAME
+ || header->header.typeflag == GNUTYPE_LONGLINK)
+ {
+ size_t name_size = info->stat.st_size;
+ size_t n = name_size % BLOCKSIZE;
+ size = name_size + BLOCKSIZE;
+ if (n)
+ size += BLOCKSIZE - n;
+
+ if (name_size != info->stat.st_size || size < name_size)
+ xalloc_die ();
+
+ header_copy = xmalloc (size + 1);
+
+ if (header->header.typeflag == GNUTYPE_LONGNAME)
+ {
+ free (next_long_name);
+ next_long_name = header_copy;
+ next_long_name_blocks = size / BLOCKSIZE;
+ }
+ else
+ {
+ free (next_long_link);
+ next_long_link = header_copy;
+ next_long_link_blocks = size / BLOCKSIZE;
+ }
+
+ set_next_block_after (header);
+ *header_copy = *header;
+ bp = header_copy->buffer + BLOCKSIZE;
+
+ for (size -= BLOCKSIZE; size > 0; size -= written)
+ {
+ data_block = find_next_block ();
+ if (! data_block)
+ {
+ ERROR ((0, 0, _("Unexpected EOF in archive")));
+ break;
+ }
+ written = available_space_after (data_block);
+ if (written > size)
+ written = size;
+
+ memcpy (bp, data_block->buffer, written);
+ bp += written;
+ set_next_block_after ((union block *)
+ (data_block->buffer + written - 1));
+ }
+
+ *bp = '\0';
+ }
+ else if (header->header.typeflag == XHDTYPE
+ || header->header.typeflag == SOLARIS_XHDTYPE)
+ xheader_read (&info->xhdr, header,
+ OFF_FROM_HEADER (header->header.size));
+ else if (header->header.typeflag == XGLTYPE)
+ {
+ struct xheader xhdr;
+
+ if (!recent_global_header)
+ recent_global_header = xmalloc (sizeof *recent_global_header);
+ memcpy (recent_global_header, header,
+ sizeof *recent_global_header);
+ memset (&xhdr, 0, sizeof xhdr);
+ xheader_read (&xhdr, header,
+ OFF_FROM_HEADER (header->header.size));
+ xheader_decode_global (&xhdr);
+ xheader_destroy (&xhdr);
+ if (mode == read_header_x_global)
+ return HEADER_SUCCESS_EXTENDED;
+ }
+
+ /* Loop! */
+
+ }
+ else
+ {
+ char const *name;
+ struct posix_header const *h = &header->header;
+ char namebuf[sizeof h->prefix + 1 + NAME_FIELD_SIZE + 1];
+
+ free (recent_long_name);
+
+ if (next_long_name)
+ {
+ name = next_long_name->buffer + BLOCKSIZE;
+ recent_long_name = next_long_name;
+ recent_long_name_blocks = next_long_name_blocks;
+ }
+ else
+ {
+ /* Accept file names as specified by POSIX.1-1996
+ section 10.1.1. */
+ char *np = namebuf;
+
+ if (h->prefix[0] && strcmp (h->magic, TMAGIC) == 0)
+ {
+ memcpy (np, h->prefix, sizeof h->prefix);
+ np[sizeof h->prefix] = '\0';
+ np += strlen (np);
+ *np++ = '/';
+ }
+ memcpy (np, h->name, sizeof h->name);
+ np[sizeof h->name] = '\0';
+ name = namebuf;
+ recent_long_name = 0;
+ recent_long_name_blocks = 0;
+ }
+ assign_string (&info->orig_file_name, name);
+ assign_string (&info->file_name, name);
+ info->had_trailing_slash = strip_trailing_slashes (info->file_name);
+
+ free (recent_long_link);
+
+ if (next_long_link)
+ {
+ name = next_long_link->buffer + BLOCKSIZE;
+ recent_long_link = next_long_link;
+ recent_long_link_blocks = next_long_link_blocks;
+ }
+ else
+ {
+ memcpy (namebuf, h->linkname, sizeof h->linkname);
+ namebuf[sizeof h->linkname] = '\0';
+ name = namebuf;
+ recent_long_link = 0;
+ recent_long_link_blocks = 0;
+ }
+ assign_string (&info->link_name, name);
+
+ return HEADER_SUCCESS;
+ }
+ }
+}
+
+#define ISOCTAL(c) ((c)>='0'&&(c)<='7')
+
+/* Decode things from a file HEADER block into STAT_INFO, also setting
+ *FORMAT_POINTER depending on the header block format. If
+ DO_USER_GROUP, decode the user/group information (this is useful
+ for extraction, but waste time when merely listing).
+
+ read_header() has already decoded the checksum and length, so we don't.
+
+ This routine should *not* be called twice for the same block, since
+ the two calls might use different DO_USER_GROUP values and thus
+ might end up with different uid/gid for the two calls. If anybody
+ wants the uid/gid they should decode it first, and other callers
+ should decode it without uid/gid before calling a routine,
+ e.g. print_header, that assumes decoded data. */
+void
+decode_header (union block *header, struct tar_stat_info *stat_info,
+ enum archive_format *format_pointer, int do_user_group)
+{
+ enum archive_format format;
+ bool hbits;
+ mode_t mode = MODE_FROM_HEADER (header->header.mode, &hbits);
+
+ if (strcmp (header->header.magic, TMAGIC) == 0)
+ {
+ if (header->star_header.prefix[130] == 0
+ && ISOCTAL (header->star_header.atime[0])
+ && header->star_header.atime[11] == ' '
+ && ISOCTAL (header->star_header.ctime[0])
+ && header->star_header.ctime[11] == ' ')
+ format = STAR_FORMAT;
+ else if (stat_info->xhdr.size)
+ format = POSIX_FORMAT;
+ else
+ format = USTAR_FORMAT;
+ }
+ else if (strcmp (header->buffer + offsetof (struct posix_header, magic),
+ OLDGNU_MAGIC)
+ == 0)
+ format = hbits ? OLDGNU_FORMAT : GNU_FORMAT;
+ else
+ format = V7_FORMAT;
+ *format_pointer = format;
+
+ stat_info->stat.st_mode = mode;
+ stat_info->mtime.tv_sec = TIME_FROM_HEADER (header->header.mtime);
+ stat_info->mtime.tv_nsec = 0;
+ assign_string (&stat_info->uname,
+ header->header.uname[0] ? header->header.uname : NULL);
+ assign_string (&stat_info->gname,
+ header->header.gname[0] ? header->header.gname : NULL);
+
+ xheader_xattr_init (stat_info);
+
+ if (format == OLDGNU_FORMAT && incremental_option)
+ {
+ stat_info->atime.tv_sec = TIME_FROM_HEADER (header->oldgnu_header.atime);
+ stat_info->ctime.tv_sec = TIME_FROM_HEADER (header->oldgnu_header.ctime);
+ stat_info->atime.tv_nsec = stat_info->ctime.tv_nsec = 0;
+ }
+ else if (format == STAR_FORMAT)
+ {
+ stat_info->atime.tv_sec = TIME_FROM_HEADER (header->star_header.atime);
+ stat_info->ctime.tv_sec = TIME_FROM_HEADER (header->star_header.ctime);
+ stat_info->atime.tv_nsec = stat_info->ctime.tv_nsec = 0;
+ }
+ else
+ stat_info->atime = stat_info->ctime = start_time;
+
+ if (format == V7_FORMAT)
+ {
+ stat_info->stat.st_uid = UID_FROM_HEADER (header->header.uid);
+ stat_info->stat.st_gid = GID_FROM_HEADER (header->header.gid);
+ stat_info->stat.st_rdev = 0;
+ }
+ else
+ {
+ if (do_user_group)
+ {
+ /* FIXME: Decide if this should somewhat depend on -p. */
+
+ if (numeric_owner_option
+ || !*header->header.uname
+ || !uname_to_uid (header->header.uname, &stat_info->stat.st_uid))
+ stat_info->stat.st_uid = UID_FROM_HEADER (header->header.uid);
+
+ if (numeric_owner_option
+ || !*header->header.gname
+ || !gname_to_gid (header->header.gname, &stat_info->stat.st_gid))
+ stat_info->stat.st_gid = GID_FROM_HEADER (header->header.gid);
+ }
+
+ switch (header->header.typeflag)
+ {
+ case BLKTYPE:
+ case CHRTYPE:
+ stat_info->stat.st_rdev =
+ makedev (MAJOR_FROM_HEADER (header->header.devmajor),
+ MINOR_FROM_HEADER (header->header.devminor));
+ break;
+
+ default:
+ stat_info->stat.st_rdev = 0;
+ }
+ }
+
+ xheader_decode (stat_info);
+
+ if (sparse_member_p (stat_info))
+ {
+ sparse_fixup_header (stat_info);
+ stat_info->is_sparse = true;
+ }
+ else
+ {
+ stat_info->is_sparse = false;
+ if (((current_format == GNU_FORMAT
+ || current_format == OLDGNU_FORMAT)
+ && current_header->header.typeflag == GNUTYPE_DUMPDIR)
+ || stat_info->dumpdir)
+ stat_info->is_dumpdir = true;
+ }
+}
+
+
+/* Convert buffer at WHERE0 of size DIGS from external format to
+ intmax_t. DIGS must be positive. If TYPE is nonnull, the 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. Return -1 on error,
+ diagnosing the error if TYPE is nonnull and if !SILENT. */
+#if ! (INTMAX_MAX <= UINTMAX_MAX && - (INTMAX_MIN + 1) <= UINTMAX_MAX)
+# error "from_header internally represents intmax_t as uintmax_t + sign"
+#endif
+#if ! (UINTMAX_MAX / 2 <= INTMAX_MAX)
+# error "from_header returns intmax_t to represent uintmax_t"
+#endif
+static intmax_t
+from_header (char const *where0, size_t digs, char const *type,
+ intmax_t minval, uintmax_t maxval,
+ bool octal_only, bool silent)
+{
+ uintmax_t value;
+ uintmax_t uminval = minval;
+ uintmax_t minus_minval = - uminval;
+ char const *where = where0;
+ char const *lim = where + digs;
+ bool negative = false;
+
+ /* Accommodate buggy tar of unknown vintage, which outputs leading
+ NUL if the previous field overflows. */
+ where += !*where;
+
+ /* Accommodate older tars, which output leading spaces. */
+ for (;;)
+ {
+ if (where == lim)
+ {
+ if (type && !silent)
+ ERROR ((0, 0,
+ /* TRANSLATORS: %s is type of the value (gid_t, uid_t,
+ etc.) */
+ _("Blanks in header where numeric %s value expected"),
+ type));
+ return -1;
+ }
+ if (!isspace ((unsigned char) *where))
+ break;
+ where++;
+ }
+
+ value = 0;
+ if (ISODIGIT (*where))
+ {
+ char const *where1 = where;
+ bool overflow = false;
+
+ for (;;)
+ {
+ 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) && '2' <= *where1 && type)
+ {
+ /* Compute the negative of the input value, assuming two's
+ complement. */
+ int digit = (*where1 - '0') | 4;
+ overflow = 0;
+ value = 0;
+ where = where1;
+ for (;;)
+ {
+ 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;
+
+ if (!overflow && value <= minus_minval)
+ {
+ if (!silent)
+ WARN ((0, 0,
+ /* TRANSLATORS: Second %s is a type name (gid_t,uid_t,etc.) */
+ _("Archive octal value %.*s is out of %s range; assuming two's complement"),
+ (int) (where - where1), where1, type));
+ negative = true;
+ }
+ }
+
+ if (overflow)
+ {
+ if (type && !silent)
+ ERROR ((0, 0,
+ /* TRANSLATORS: Second %s is a type name (gid_t,uid_t,etc.) */
+ _("Archive octal value %.*s is out of %s range"),
+ (int) (where - where1), where1, type));
+ 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 releases. */
+ int dig;
+ if (!silent)
+ {
+ static bool warned_once;
+ if (! warned_once)
+ {
+ warned_once = true;
+ WARN ((0, 0, _("Archive contains obsolescent base-64 headers")));
+ }
+ }
+ negative = *where++ == '-';
+ while (where != lim
+ && (dig = base64_map[(unsigned char) *where]) < 64)
+ {
+ if (value << LG_64 >> LG_64 != value)
+ {
+ char *string = alloca (digs + 1);
+ memcpy (string, where0, digs);
+ string[digs] = '\0';
+ if (type && !silent)
+ ERROR ((0, 0,
+ _("Archive signed base-64 string %s is out of %s range"),
+ quote (string), type));
+ return -1;
+ }
+ value = (value << LG_64) | dig;
+ where++;
+ }
+ }
+ else if (*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 = *where & (1 << (LG_256 - 2));
+ uintmax_t topbits = (((uintmax_t) - signbit)
+ << (CHAR_BIT * sizeof (uintmax_t)
+ - LG_256 - (LG_256 - 2)));
+ value = (*where++ & ((1 << (LG_256 - 2)) - 1)) - signbit;
+ for (;;)
+ {
+ value = (value << LG_256) + (unsigned char) *where++;
+ if (where == lim)
+ break;
+ if (((value << LG_256 >> LG_256) | topbits) != value)
+ {
+ if (type && !silent)
+ ERROR ((0, 0,
+ _("Archive base-256 value is out of %s range"),
+ type));
+ return -1;
+ }
+ }
+ negative = signbit != 0;
+ if (negative)
+ value = -value;
+ }
+
+ if (where != lim && *where && !isspace ((unsigned char) *where))
+ {
+ if (type)
+ {
+ char buf[1000]; /* Big enough to represent any header. */
+ static struct quoting_options *o;
+
+ if (!o)
+ {
+ o = clone_quoting_options (0);
+ set_quoting_style (o, locale_quoting_style);
+ }
+
+ while (where0 != lim && ! lim[-1])
+ lim--;
+ quotearg_buffer (buf, sizeof buf, where0, lim - where0, o);
+ if (!silent)
+ ERROR ((0, 0,
+ /* TRANSLATORS: Second %s is a type name (gid_t,uid_t,etc.) */
+ _("Archive contains %.*s where numeric %s value expected"),
+ (int) sizeof buf, buf, type));
+ }
+
+ return -1;
+ }
+
+ if (value <= (negative ? minus_minval : maxval))
+ return represent_uintmax (negative ? -value : value);
+
+ if (type && !silent)
+ {
+ char minval_buf[UINTMAX_STRSIZE_BOUND + 1];
+ char maxval_buf[UINTMAX_STRSIZE_BOUND];
+ char value_buf[UINTMAX_STRSIZE_BOUND + 1];
+ char *minval_string = STRINGIFY_BIGINT (minus_minval, minval_buf + 1);
+ char *value_string = STRINGIFY_BIGINT (value, value_buf + 1);
+ if (negative)
+ *--value_string = '-';
+ if (minus_minval)
+ *--minval_string = '-';
+ /* TRANSLATORS: Second %s is type name (gid_t,uid_t,etc.) */
+ ERROR ((0, 0, _("Archive value %s is out of %s range %s..%s"),
+ value_string, type,
+ minval_string, STRINGIFY_BIGINT (maxval, maxval_buf)));
+ }
+
+ return -1;
+}
+
+static gid_t
+gid_from_header (const char *p, size_t s)
+{
+ return from_header (p, s, "gid_t",
+ TYPE_MINIMUM (gid_t), TYPE_MAXIMUM (gid_t),
+ false, false);
+}
+
+static major_t
+major_from_header (const char *p, size_t s)
+{
+ return from_header (p, s, "major_t",
+ TYPE_MINIMUM (major_t), TYPE_MAXIMUM (major_t),
+ false, false);
+}
+
+static minor_t
+minor_from_header (const char *p, size_t s)
+{
+ return from_header (p, s, "minor_t",
+ TYPE_MINIMUM (minor_t), TYPE_MAXIMUM (minor_t),
+ false, false);
+}
+
+/* Convert P to the file mode, as understood by tar.
+ Set *HBITS if there are any unrecognized bits. */
+static mode_t
+mode_from_header (const char *p, size_t s, bool *hbits)
+{
+ intmax_t u = from_header (p, s, "mode_t",
+ INTMAX_MIN, UINTMAX_MAX,
+ false, false);
+ mode_t 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));
+ *hbits = (u & ~07777) != 0;
+ return mode;
+}
+
+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 from_header (p, s, "off_t",
+ 0, TYPE_MAXIMUM (off_t),
+ false, false);
+}
+
+static time_t
+time_from_header (const char *p, size_t s)
+{
+ return from_header (p, s, "time_t",
+ TYPE_MINIMUM (time_t), TYPE_MAXIMUM (time_t),
+ false, false);
+}
+
+static uid_t
+uid_from_header (const char *p, size_t s)
+{
+ return from_header (p, s, "uid_t",
+ TYPE_MINIMUM (uid_t), TYPE_MAXIMUM (uid_t),
+ false, false);
+}
+
+uintmax_t
+uintmax_from_header (const char *p, size_t s)
+{
+ return from_header (p, s, "uintmax_t", 0, UINTMAX_MAX, false, false);
+}
+
+
+/* Return a printable representation of T. The result points to
+ static storage that can be reused in the next call to this
+ function, to ctime, or to asctime. If FULL_TIME, then output the
+ time stamp to its full resolution; otherwise, just output it to
+ 1-minute resolution. */
+char const *
+tartime (struct timespec t, bool full_time)
+{
+ enum { fraclen = sizeof ".FFFFFFFFF" - 1 };
+ static char buffer[max (UINTMAX_STRSIZE_BOUND + 1,
+ INT_STRLEN_BOUND (int) + 16)
+ + fraclen];
+ struct tm *tm;
+ time_t s = t.tv_sec;
+ int ns = t.tv_nsec;
+ bool negative = s < 0;
+ char *p;
+
+ if (negative && ns != 0)
+ {
+ s++;
+ ns = 1000000000 - ns;
+ }
+
+ tm = utc_option ? gmtime (&s) : localtime (&s);
+ if (tm)
+ {
+ if (full_time)
+ {
+ sprintf (buffer, "%04ld-%02d-%02d %02d:%02d:%02d",
+ tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec);
+ code_ns_fraction (ns, buffer + strlen (buffer));
+ }
+ else
+ sprintf (buffer, "%04ld-%02d-%02d %02d:%02d",
+ tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
+ tm->tm_hour, tm->tm_min);
+ return buffer;
+ }
+
+ /* The time stamp cannot be broken down, most likely because it
+ is out of range. Convert it as an integer,
+ right-adjusted in a field with the same width as the usual
+ 4-year ISO time format. */
+ p = umaxtostr (negative ? - (uintmax_t) s : s,
+ buffer + sizeof buffer - UINTMAX_STRSIZE_BOUND - fraclen);
+ if (negative)
+ *--p = '-';
+ while ((buffer + sizeof buffer - sizeof "YYYY-MM-DD HH:MM"
+ + (full_time ? sizeof ":SS.FFFFFFFFF" - 1 : 0))
+ < p)
+ *--p = ' ';
+ if (full_time)
+ code_ns_fraction (ns, buffer + sizeof buffer - 1 - fraclen);
+ return p;
+}
+
+/* Actually print it.
+
+ Plain and fancy file header block logging. Non-verbose just prints
+ the name, e.g. for "tar t" or "tar x". This should just contain
+ file names, so it can be fed back into tar with xargs or the "-T"
+ option. The verbose option can give a bunch of info, one line per
+ file. I doubt anybody tries to parse its format, or if they do,
+ they shouldn't. Unix tar is pretty random here anyway. */
+
+
+/* Width of "user/group size", with initial value chosen
+ heuristically. This grows as needed, though this may cause some
+ stairstepping in the output. Make it too small and the output will
+ almost always look ragged. Make it too large and the output will
+ be spaced out too far. */
+static int ugswidth = 19;
+
+/* Width of printed time stamps. It grows if longer time stamps are
+ found (typically, those with nanosecond resolution). Like
+ USGWIDTH, some stairstepping may occur. */
+static int datewidth = sizeof "YYYY-MM-DD HH:MM" - 1;
+
+static bool volume_label_printed = false;
+
+static void
+simple_print_header (struct tar_stat_info *st, union block *blk,
+ off_t block_ordinal)
+{
+ char modes[12];
+ char const *time_stamp;
+ int time_stamp_len;
+ char *temp_name;
+
+ /* These hold formatted ints. */
+ char uform[max (INT_BUFSIZE_BOUND (intmax_t), UINTMAX_STRSIZE_BOUND)];
+ char gform[sizeof uform];
+ char *user, *group;
+ char size[2 * UINTMAX_STRSIZE_BOUND];
+ /* holds formatted size or major,minor */
+ char uintbuf[UINTMAX_STRSIZE_BOUND];
+ int pad;
+ int sizelen;
+
+ if (show_transformed_names_option)
+ temp_name = st->file_name ? st->file_name : st->orig_file_name;
+ else
+ temp_name = st->orig_file_name ? st->orig_file_name : st->file_name;
+
+ if (block_number_option)
+ {
+ char buf[UINTMAX_STRSIZE_BOUND];
+ if (block_ordinal < 0)
+ block_ordinal = current_block_ordinal ();
+ block_ordinal -= recent_long_name_blocks;
+ block_ordinal -= recent_long_link_blocks;
+ fprintf (stdlis, _("block %s: "),
+ STRINGIFY_BIGINT (block_ordinal, buf));
+ }
+
+ if (verbose_option <= 1)
+ {
+ /* Just the fax, mam. */
+ fputs (quotearg (temp_name), stdlis);
+ if (show_transformed_names_option && st->had_trailing_slash)
+ fputc ('/', stdlis);
+ fputc ('\n', stdlis);
+ }
+ else
+ {
+ /* File type and modes. */
+
+ modes[0] = '?';
+ switch (blk->header.typeflag)
+ {
+ case GNUTYPE_VOLHDR:
+ volume_label_printed = true;
+ modes[0] = 'V';
+ break;
+
+ case GNUTYPE_MULTIVOL:
+ modes[0] = 'M';
+ break;
+
+ case GNUTYPE_LONGNAME:
+ case GNUTYPE_LONGLINK:
+ modes[0] = 'L';
+ ERROR ((0, 0, _("Unexpected long name header")));
+ break;
+
+ case GNUTYPE_SPARSE:
+ case REGTYPE:
+ case AREGTYPE:
+ modes[0] = st->had_trailing_slash ? 'd' : '-';
+ break;
+ case LNKTYPE:
+ modes[0] = 'h';
+ break;
+ case GNUTYPE_DUMPDIR:
+ modes[0] = 'd';
+ break;
+ case DIRTYPE:
+ modes[0] = 'd';
+ break;
+ case SYMTYPE:
+ modes[0] = 'l';
+ break;
+ case BLKTYPE:
+ modes[0] = 'b';
+ break;
+ case CHRTYPE:
+ modes[0] = 'c';
+ break;
+ case FIFOTYPE:
+ modes[0] = 'p';
+ break;
+ case CONTTYPE:
+ modes[0] = 'C';
+ break;
+ }
+
+ pax_decode_mode (st->stat.st_mode, modes + 1);
+
+ /* extended attributes: GNU `ls -l'-like preview */
+ xattrs_print_char (st, modes + 10);
+
+ /* Time stamp. */
+
+ time_stamp = tartime (st->mtime, full_time_option);
+ time_stamp_len = strlen (time_stamp);
+ if (datewidth < time_stamp_len)
+ datewidth = time_stamp_len;
+
+ /* User and group names. */
+
+ if (st->uname
+ && st->uname[0]
+ && current_format != V7_FORMAT
+ && !numeric_owner_option)
+ user = st->uname;
+ else
+ user = STRINGIFY_BIGINT (st->stat.st_uid, uform);
+
+ if (st->gname
+ && st->gname[0]
+ && current_format != V7_FORMAT
+ && !numeric_owner_option)
+ group = st->gname;
+ else
+ group = STRINGIFY_BIGINT (st->stat.st_gid, gform);
+
+ /* Format the file size or major/minor device numbers. */
+
+ switch (blk->header.typeflag)
+ {
+ case CHRTYPE:
+ case BLKTYPE:
+ strcpy (size,
+ STRINGIFY_BIGINT (major (st->stat.st_rdev), uintbuf));
+ strcat (size, ",");
+ strcat (size,
+ STRINGIFY_BIGINT (minor (st->stat.st_rdev), uintbuf));
+ break;
+
+ default:
+ /* st->stat.st_size keeps stored file size */
+ strcpy (size, STRINGIFY_BIGINT (st->stat.st_size, uintbuf));
+ break;
+ }
+
+ /* Figure out padding and print the whole line. */
+
+ sizelen = strlen (size);
+ pad = strlen (user) + 1 + strlen (group) + 1 + sizelen;
+ if (pad > ugswidth)
+ ugswidth = pad;
+
+ fprintf (stdlis, "%s %s/%s %*s %-*s",
+ modes, user, group, ugswidth - pad + sizelen, size,
+ datewidth, time_stamp);
+
+ fprintf (stdlis, " %s", quotearg (temp_name));
+ if (show_transformed_names_option && st->had_trailing_slash)
+ fputc ('/', stdlis);
+
+ switch (blk->header.typeflag)
+ {
+ case SYMTYPE:
+ fprintf (stdlis, " -> %s\n", quotearg (st->link_name));
+ break;
+
+ case LNKTYPE:
+ fprintf (stdlis, _(" link to %s\n"), quotearg (st->link_name));
+ break;
+
+ default:
+ {
+ char type_string[2];
+ type_string[0] = blk->header.typeflag;
+ type_string[1] = '\0';
+ fprintf (stdlis, _(" unknown file type %s\n"),
+ quote (type_string));
+ }
+ break;
+
+ case AREGTYPE:
+ case REGTYPE:
+ case GNUTYPE_SPARSE:
+ case CHRTYPE:
+ case BLKTYPE:
+ case DIRTYPE:
+ case FIFOTYPE:
+ case CONTTYPE:
+ case GNUTYPE_DUMPDIR:
+ putc ('\n', stdlis);
+ break;
+
+ case GNUTYPE_LONGLINK:
+ fprintf (stdlis, _("--Long Link--\n"));
+ break;
+
+ case GNUTYPE_LONGNAME:
+ fprintf (stdlis, _("--Long Name--\n"));
+ break;
+
+ case GNUTYPE_VOLHDR:
+ fprintf (stdlis, _("--Volume Header--\n"));
+ break;
+
+ case GNUTYPE_MULTIVOL:
+ strcpy (size,
+ STRINGIFY_BIGINT
+ (UINTMAX_FROM_HEADER (blk->oldgnu_header.offset),
+ uintbuf));
+ fprintf (stdlis, _("--Continued at byte %s--\n"), size);
+ break;
+ }
+ }
+ fflush (stdlis);
+ xattrs_print (st);
+}
+
+
+static void
+print_volume_label (void)
+{
+ struct tar_stat_info vstat;
+ union block vblk;
+ enum archive_format dummy;
+
+ memset (&vblk, 0, sizeof (vblk));
+ vblk.header.typeflag = GNUTYPE_VOLHDR;
+ if (recent_global_header)
+ memcpy (vblk.header.mtime, recent_global_header->header.mtime,
+ sizeof vblk.header.mtime);
+ tar_stat_init (&vstat);
+ assign_string (&vstat.file_name, ".");
+ decode_header (&vblk, &vstat, &dummy, 0);
+ assign_string (&vstat.file_name, volume_label);
+ simple_print_header (&vstat, &vblk, 0);
+ tar_stat_destroy (&vstat);
+}
+
+void
+print_header (struct tar_stat_info *st, union block *blk,
+ off_t block_ordinal)
+{
+ if (current_format == POSIX_FORMAT && !volume_label_printed && volume_label)
+ {
+ print_volume_label ();
+ volume_label_printed = true;
+ }
+
+ simple_print_header (st, blk, block_ordinal);
+}
+
+/* Print a similar line when we make a directory automatically. */
+void
+print_for_mkdir (char *dirname, int length, mode_t mode)
+{
+ char modes[11];
+
+ if (verbose_option > 1)
+ {
+ /* File type and modes. */
+
+ modes[0] = 'd';
+ pax_decode_mode (mode, modes + 1);
+
+ if (block_number_option)
+ {
+ char buf[UINTMAX_STRSIZE_BOUND];
+ fprintf (stdlis, _("block %s: "),
+ STRINGIFY_BIGINT (current_block_ordinal (), buf));
+ }
+
+ fprintf (stdlis, "%s %*s %s\n", modes, ugswidth + 1 + datewidth,
+ _("Creating directory:"), quotearg (dirname));
+ }
+}
+
+/* Skip over SIZE bytes of data in blocks in the archive. */
+void
+skip_file (off_t size)
+{
+ union block *x;
+
+ /* FIXME: Make sure mv_begin_read is always called before it */
+
+ if (seekable_archive)
+ {
+ off_t nblk = seek_archive (size);
+ if (nblk >= 0)
+ size -= nblk * BLOCKSIZE;
+ else
+ seekable_archive = false;
+ }
+
+ mv_size_left (size);
+
+ while (size > 0)
+ {
+ x = find_next_block ();
+ if (! x)
+ FATAL_ERROR ((0, 0, _("Unexpected EOF in archive")));
+
+ set_next_block_after (x);
+ size -= BLOCKSIZE;
+ mv_size_left (size);
+ }
+}
+
+/* Skip the current member in the archive.
+ NOTE: Current header must be decoded before calling this function. */
+void
+skip_member (void)
+{
+ if (!current_stat_info.skipped)
+ {
+ char save_typeflag = current_header->header.typeflag;
+ set_next_block_after (current_header);
+
+ mv_begin_read (&current_stat_info);
+
+ if (current_stat_info.is_sparse)
+ sparse_skip_file (&current_stat_info);
+ else if (save_typeflag != DIRTYPE)
+ skip_file (current_stat_info.stat.st_size);
+
+ mv_end ();
+ }
+}
+
+void
+test_archive_label (void)
+{
+ base64_init ();
+ name_gather ();
+
+ open_archive (ACCESS_READ);
+ if (read_header (&current_header, &current_stat_info, read_header_auto)
+ == HEADER_SUCCESS)
+ {
+ decode_header (current_header,
+ &current_stat_info, &current_format, 0);
+ if (current_header->header.typeflag == GNUTYPE_VOLHDR)
+ assign_string (&volume_label, current_header->header.name);
+
+ if (volume_label)
+ {
+ if (verbose_option)
+ print_volume_label ();
+ if (!name_match (volume_label) && multi_volume_option)
+ {
+ char *s = drop_volume_label_suffix (volume_label);
+ name_match (s);
+ free (s);
+ }
+ }
+ }
+ close_archive ();
+ label_notfound ();
+}
diff --git a/src/map.c b/src/map.c
new file mode 100644
index 0000000..0cd9861
--- /dev/null
+++ b/src/map.c
@@ -0,0 +1,283 @@
+/* Owner/group mapping for tar
+
+ Copyright 2015-2016 Free Software Foundation, Inc.
+
+ This file is part of GNU tar.
+
+ GNU tar 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.
+
+ GNU tar is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <system.h>
+#include "common.h"
+#include "wordsplit.h"
+#include <hash.h>
+#include <pwd.h>
+
+struct mapentry
+{
+ uintmax_t orig_id;
+ uintmax_t new_id;
+ char *new_name;
+};
+
+static size_t
+map_hash (void const *entry, size_t nbuckets)
+{
+ struct mapentry const *map = entry;
+ return map->orig_id % nbuckets;
+}
+
+static bool
+map_compare (void const *entry1, void const *entry2)
+{
+ struct mapentry const *map1 = entry1;
+ struct mapentry const *map2 = entry2;
+ return map1->orig_id == map2->orig_id;
+}
+
+static int
+parse_id (uintmax_t *retval,
+ char const *arg, char const *what, uintmax_t maxval,
+ char const *file, unsigned line)
+{
+ uintmax_t v;
+ char *p;
+
+ errno = 0;
+ v = strtoumax (arg, &p, 10);
+ if (*p || errno)
+ {
+ error (0, 0, _("%s:%u: invalid %s: %s"), file, line, what, arg);
+ return -1;
+ }
+ if (v > maxval)
+ {
+ error (0, 0, _("%s:%u: %s out of range: %s"), file, line, what, arg);
+ return -1;
+ }
+ *retval = v;
+ return 0;
+}
+
+static void
+map_read (Hash_table **ptab, char const *file,
+ uintmax_t (*name_to_id) (char const *), char const *what,
+ uintmax_t maxval)
+{
+ FILE *fp;
+ char *buf = NULL;
+ size_t bufsize = 0;
+ ssize_t n;
+ struct wordsplit ws;
+ int wsopt;
+ unsigned line;
+ int err = 0;
+
+ fp = fopen (file, "r");
+ if (!fp)
+ open_fatal (file);
+
+ ws.ws_comment = "#";
+ wsopt = WRDSF_COMMENT | WRDSF_NOVAR | WRDSF_NOCMD | WRDSF_SQUEEZE_DELIMS
+ | WRDSF_QUOTE;
+ line = 0;
+ while ((n = getline (&buf, &bufsize, fp)) > 0)
+ {
+ struct mapentry *ent;
+ uintmax_t orig_id, new_id;
+ char *name = NULL;
+ char *colon;
+
+ ++line;
+ if (wordsplit (buf, &ws, wsopt))
+ FATAL_ERROR ((0, 0, _("%s:%u: cannot split line: %s"),
+ file, line, wordsplit_strerror (&ws)));
+ wsopt |= WRDSF_REUSE;
+ if (ws.ws_wordc == 0)
+ continue;
+ if (ws.ws_wordc != 2)
+ {
+ error (0, 0, _("%s:%u: malformed line"), file, line);
+ err = 1;
+ continue;
+ }
+
+ if (ws.ws_wordv[0][0] == '+')
+ {
+ if (parse_id (&orig_id, ws.ws_wordv[0]+1, what, maxval, file, line))
+ {
+ err = 1;
+ continue;
+ }
+ }
+ else if (name_to_id)
+ {
+ orig_id = name_to_id (ws.ws_wordv[0]);
+ if (orig_id == UINTMAX_MAX)
+ {
+ error (0, 0, _("%s:%u: can't obtain %s of %s"),
+ file, line, what, ws.ws_wordv[0]);
+ err = 1;
+ continue;
+ }
+ }
+
+ colon = strchr (ws.ws_wordv[1], ':');
+ if (colon)
+ {
+ if (colon > ws.ws_wordv[1])
+ name = ws.ws_wordv[1];
+ *colon++ = 0;
+ if (parse_id (&new_id, colon, what, maxval, file, line))
+ {
+ err = 1;
+ continue;
+ }
+ }
+ else if (ws.ws_wordv[1][0] == '+')
+ {
+ if (parse_id (&new_id, ws.ws_wordv[1], what, maxval, file, line))
+ {
+ err = 1;
+ continue;
+ }
+ }
+ else
+ {
+ name = ws.ws_wordv[1];
+ new_id = name_to_id (ws.ws_wordv[1]);
+ if (new_id == UINTMAX_MAX)
+ {
+ error (0, 0, _("%s:%u: can't obtain %s of %s"),
+ file, line, what, ws.ws_wordv[1]);
+ err = 1;
+ continue;
+ }
+ }
+
+ ent = xmalloc (sizeof (*ent));
+ ent->orig_id = orig_id;
+ ent->new_id = new_id;
+ ent->new_name = name ? xstrdup (name) : NULL;
+
+ if (!((*ptab
+ || (*ptab = hash_initialize (0, 0, map_hash, map_compare, 0)))
+ && hash_insert (*ptab, ent)))
+ xalloc_die ();
+ }
+ if (wsopt & WRDSF_REUSE)
+ wordsplit_free (&ws);
+ fclose (fp);
+ if (err)
+ FATAL_ERROR ((0, 0, _("errors reading map file")));
+}
+
+/* UID translation */
+
+static Hash_table *owner_map;
+
+static uintmax_t
+name_to_uid (char const *name)
+{
+ struct passwd *pw = getpwnam (name);
+ return pw ? pw->pw_uid : UINTMAX_MAX;
+}
+
+void
+owner_map_read (char const *file)
+{
+ map_read (&owner_map, file, name_to_uid, "UID", TYPE_MAXIMUM (uid_t));
+}
+
+int
+owner_map_translate (uid_t uid, uid_t *new_uid, char const **new_name)
+{
+ int rc = 1;
+
+ if (owner_map)
+ {
+ struct mapentry ent, *res;
+
+ ent.orig_id = uid;
+ res = hash_lookup (owner_map, &ent);
+ if (res)
+ {
+ *new_uid = res->new_id;
+ *new_name = res->new_name;
+ return 0;
+ }
+ }
+
+ if (owner_option != (uid_t) -1)
+ {
+ *new_uid = owner_option;
+ rc = 0;
+ }
+ if (owner_name_option)
+ {
+ *new_name = owner_name_option;
+ rc = 0;
+ }
+
+ return rc;
+}
+
+/* GID translation */
+
+static Hash_table *group_map;
+
+static uintmax_t
+name_to_gid (char const *name)
+{
+ struct group *gr = getgrnam (name);
+ return gr ? gr->gr_gid : UINTMAX_MAX;
+}
+
+void
+group_map_read (char const *file)
+{
+ map_read (&group_map, file, name_to_gid, "GID", TYPE_MAXIMUM (gid_t));
+}
+
+int
+group_map_translate (gid_t gid, gid_t *new_gid, char const **new_name)
+{
+ int rc = 1;
+
+ if (group_map)
+ {
+ struct mapentry ent, *res;
+
+ ent.orig_id = gid;
+ res = hash_lookup (group_map, &ent);
+ if (res)
+ {
+ *new_gid = res->new_id;
+ *new_name = res->new_name;
+ return 0;
+ }
+ }
+
+ if (group_option != (uid_t) -1)
+ {
+ *new_gid = group_option;
+ rc = 0;
+ }
+ if (group_name_option)
+ {
+ *new_name = group_name_option;
+ rc = 0;
+ }
+
+ return rc;
+}
diff --git a/src/misc.c b/src/misc.c
new file mode 100644
index 0000000..071cf2a
--- /dev/null
+++ b/src/misc.c
@@ -0,0 +1,1250 @@
+/* Miscellaneous functions, not really specific to GNU tar.
+
+ Copyright 1988, 1992, 1994-1997, 1999-2001, 2003-2007, 2009-2010,
+ 2012-2014, 2016 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 3, or (at your option) any later
+ version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#define COMMON_INLINE _GL_EXTERN_INLINE
+#include <system.h>
+#include <rmt.h>
+#include "common.h"
+#include <quotearg.h>
+#include <xgetcwd.h>
+#include <unlinkdir.h>
+#include <utimens.h>
+
+#ifndef DOUBLE_SLASH_IS_DISTINCT_ROOT
+# define DOUBLE_SLASH_IS_DISTINCT_ROOT 0
+#endif
+
+static void namebuf_add_dir (namebuf_t, char const *);
+static char *namebuf_finish (namebuf_t);
+static const char *tar_getcdpath (int);
+
+
+/* Handling strings. */
+
+/* Assign STRING to a copy of VALUE if not zero, or to zero. If
+ STRING was nonzero, it is freed first. */
+void
+assign_string (char **string, const char *value)
+{
+ free (*string);
+ *string = value ? xstrdup (value) : 0;
+}
+
+#if 0
+/* This function is currently unused; perhaps it should be removed? */
+
+/* Allocate a copy of the string quoted as in C, and returns that. If
+ the string does not have to be quoted, it returns a null pointer.
+ The allocated copy should normally be freed with free() after the
+ caller is done with it.
+
+ This is used in one context only: generating the directory file in
+ incremental dumps. The quoted string is not intended for human
+ consumption; it is intended only for unquote_string. The quoting
+ is locale-independent, so that users needn't worry about locale
+ when reading directory files. This means that we can't use
+ quotearg, as quotearg is locale-dependent and is meant for human
+ consumption. */
+static char *
+quote_copy_string (const char *string)
+{
+ const char *source = string;
+ char *destination = 0;
+ char *buffer = 0;
+ int copying = 0;
+
+ while (*source)
+ {
+ int character = *source++;
+
+ switch (character)
+ {
+ case '\n': case '\\':
+ if (!copying)
+ {
+ size_t length = (source - string) - 1;
+
+ copying = 1;
+ buffer = xmalloc (length + 2 + 2 * strlen (source) + 1);
+ memcpy (buffer, string, length);
+ destination = buffer + length;
+ }
+ *destination++ = '\\';
+ *destination++ = character == '\\' ? '\\' : 'n';
+ break;
+
+ default:
+ if (copying)
+ *destination++ = character;
+ break;
+ }
+ }
+ if (copying)
+ {
+ *destination = '\0';
+ return buffer;
+ }
+ return 0;
+}
+#endif
+
+/* Takes a quoted C string (like those produced by quote_copy_string)
+ and turns it back into the un-quoted original. This is done in
+ place. Returns 0 only if the string was not properly quoted, but
+ completes the unquoting anyway.
+
+ This is used for reading the saved directory file in incremental
+ dumps. It is used for decoding old 'N' records (demangling names).
+ But also, it is used for decoding file arguments, would they come
+ from the shell or a -T file, and for decoding the --exclude
+ argument. */
+int
+unquote_string (char *string)
+{
+ int result = 1;
+ char *source = string;
+ char *destination = string;
+
+ /* Escape sequences other than \\ and \n are no longer generated by
+ quote_copy_string, but accept them for backwards compatibility,
+ and also because unquote_string is used for purposes other than
+ parsing the output of quote_copy_string. */
+
+ while (*source)
+ if (*source == '\\')
+ switch (*++source)
+ {
+ case '\\':
+ *destination++ = '\\';
+ source++;
+ break;
+
+ case 'a':
+ *destination++ = '\a';
+ source++;
+ break;
+
+ case 'b':
+ *destination++ = '\b';
+ source++;
+ break;
+
+ case 'f':
+ *destination++ = '\f';
+ source++;
+ break;
+
+ case 'n':
+ *destination++ = '\n';
+ source++;
+ break;
+
+ case 'r':
+ *destination++ = '\r';
+ source++;
+ break;
+
+ case 't':
+ *destination++ = '\t';
+ source++;
+ break;
+
+ case 'v':
+ *destination++ = '\v';
+ source++;
+ break;
+
+ case '?':
+ *destination++ = 0177;
+ source++;
+ break;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ {
+ int value = *source++ - '0';
+
+ if (*source < '0' || *source > '7')
+ {
+ *destination++ = value;
+ break;
+ }
+ value = value * 8 + *source++ - '0';
+ if (*source < '0' || *source > '7')
+ {
+ *destination++ = value;
+ break;
+ }
+ value = value * 8 + *source++ - '0';
+ *destination++ = value;
+ break;
+ }
+
+ default:
+ result = 0;
+ *destination++ = '\\';
+ if (*source)
+ *destination++ = *source++;
+ break;
+ }
+ else if (source != destination)
+ *destination++ = *source++;
+ else
+ source++, destination++;
+
+ if (source != destination)
+ *destination = '\0';
+ return result;
+}
+
+/* Zap trailing slashes. */
+char *
+zap_slashes (char *name)
+{
+ char *q;
+
+ if (!name || *name == 0)
+ return name;
+ q = name + strlen (name) - 1;
+ while (q > name && ISSLASH (*q))
+ *q-- = '\0';
+ return name;
+}
+
+/* Normalize FILE_NAME by removing redundant slashes and "."
+ components, including redundant trailing slashes.
+ Leave ".." alone, as it may be significant in the presence
+ of symlinks and on platforms where "/.." != "/".
+
+ Destructive version: modifies its argument. */
+void
+normalize_filename_x (char *file_name)
+{
+ char *name = file_name + FILE_SYSTEM_PREFIX_LEN (file_name);
+ char *p;
+ char const *q;
+ char c;
+
+ /* Don't squeeze leading "//" to "/", on hosts where they're distinct. */
+ name += (DOUBLE_SLASH_IS_DISTINCT_ROOT
+ && ISSLASH (*name) && ISSLASH (name[1]) && ! ISSLASH (name[2]));
+
+ /* Omit redundant leading "." components. */
+ for (q = p = name; (*p = *q) == '.' && ISSLASH (q[1]); p += !*q)
+ for (q += 2; ISSLASH (*q); q++)
+ continue;
+
+ /* Copy components from Q to P, omitting redundant slashes and
+ internal "." components. */
+ while ((*p++ = c = *q++) != '\0')
+ if (ISSLASH (c))
+ while (ISSLASH (q[*q == '.']))
+ q += (*q == '.') + 1;
+
+ /* Omit redundant trailing "." component and slash. */
+ if (2 < p - name)
+ {
+ p -= p[-2] == '.' && ISSLASH (p[-3]);
+ p -= 2 < p - name && ISSLASH (p[-2]);
+ p[-1] = '\0';
+ }
+}
+
+/* Normalize NAME by removing redundant slashes and "." components,
+ including redundant trailing slashes.
+
+ Return a normalized newly-allocated copy. */
+
+char *
+normalize_filename (int cdidx, const char *name)
+{
+ char *copy = NULL;
+
+ if (IS_RELATIVE_FILE_NAME (name))
+ {
+ /* Set COPY to the absolute path for this name.
+
+ FIXME: There should be no need to get the absolute file name.
+ tar_getcdpath does not return a true "canonical" path, so
+ this following approach may lead to situations where the same
+ file or directory is processed twice under different absolute
+ paths without that duplication being detected. Perhaps we
+ should use dev+ino pairs instead of names? (See listed03.at for
+ a related test case.) */
+ const char *cdpath = tar_getcdpath (cdidx);
+ size_t copylen;
+ bool need_separator;
+
+ if (!cdpath)
+ call_arg_fatal ("getcwd", ".");
+ copylen = strlen (cdpath);
+ need_separator = ! (DOUBLE_SLASH_IS_DISTINCT_ROOT
+ && copylen == 2 && ISSLASH (cdpath[1]));
+ copy = xmalloc (copylen + need_separator + strlen (name) + 1);
+ strcpy (copy, cdpath);
+ copy[copylen] = DIRECTORY_SEPARATOR;
+ strcpy (copy + copylen + need_separator, name);
+ }
+
+ if (!copy)
+ copy = xstrdup (name);
+ normalize_filename_x (copy);
+ return copy;
+}
+
+
+void
+replace_prefix (char **pname, const char *samp, size_t slen,
+ const char *repl, size_t rlen)
+{
+ char *name = *pname;
+ size_t nlen = strlen (name);
+ if (nlen > slen && memcmp (name, samp, slen) == 0 && ISSLASH (name[slen]))
+ {
+ if (rlen > slen)
+ {
+ name = xrealloc (name, nlen - slen + rlen + 1);
+ *pname = name;
+ }
+ memmove (name + rlen, name + slen, nlen - slen + 1);
+ memcpy (name, repl, rlen);
+ }
+}
+
+
+/* Handling numbers. */
+
+/* Convert VALUE, which is converted from a system integer type whose
+ minimum value is MINVAL and maximum MINVAL, to an decimal
+ integer string. Use the storage in BUF and return a pointer to the
+ converted string. If VALUE is converted from a negative integer in
+ the range MINVAL .. -1, represent it with a string representation
+ of the negative integer, using leading '-'. */
+#if ! (INTMAX_MAX <= UINTMAX_MAX / 2)
+# error "sysinttostr: uintmax_t cannot represent all intmax_t values"
+#endif
+char *
+sysinttostr (uintmax_t value, intmax_t minval, uintmax_t maxval,
+ char buf[SYSINT_BUFSIZE])
+{
+ if (value <= maxval)
+ return umaxtostr (value, buf);
+ else
+ {
+ intmax_t i = value - minval;
+ return imaxtostr (i + minval, buf);
+ }
+}
+
+/* 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
+intmax_t
+strtosysint (char const *arg, char **arglim, intmax_t minval, uintmax_t maxval)
+{
+ errno = 0;
+ if (maxval <= INTMAX_MAX)
+ {
+ if (ISDIGIT (arg[*arg == '-']))
+ {
+ intmax_t i = strtoimax (arg, arglim, 10);
+ intmax_t imaxval = maxval;
+ if (minval <= i && i <= imaxval)
+ return i;
+ errno = ERANGE;
+ return i < minval ? minval : maxval;
+ }
+ }
+ else
+ {
+ if (ISDIGIT (*arg))
+ {
+ uintmax_t i = strtoumax (arg, arglim, 10);
+ if (i <= maxval)
+ return represent_uintmax (i);
+ errno = ERANGE;
+ return maxval;
+ }
+ }
+
+ errno = EINVAL;
+ return 0;
+}
+
+/* Output fraction and trailing digits appropriate for a nanoseconds
+ count equal to NS, but don't output unnecessary '.' or trailing
+ zeros. */
+
+void
+code_ns_fraction (int ns, char *p)
+{
+ if (ns == 0)
+ *p = '\0';
+ else
+ {
+ int i = 9;
+ *p++ = '.';
+
+ while (ns % 10 == 0)
+ {
+ ns /= 10;
+ i--;
+ }
+
+ p[i] = '\0';
+
+ for (;;)
+ {
+ p[--i] = '0' + ns % 10;
+ if (i == 0)
+ break;
+ ns /= 10;
+ }
+ }
+}
+
+char const *
+code_timespec (struct timespec t, char sbuf[TIMESPEC_STRSIZE_BOUND])
+{
+ time_t s = t.tv_sec;
+ int ns = t.tv_nsec;
+ char *np;
+ bool negative = s < 0;
+
+ /* ignore invalid values of ns */
+ if (BILLION <= ns || ns < 0)
+ ns = 0;
+
+ if (negative && ns != 0)
+ {
+ s++;
+ ns = BILLION - ns;
+ }
+
+ np = umaxtostr (negative ? - (uintmax_t) s : (uintmax_t) s, sbuf + 1);
+ if (negative)
+ *--np = '-';
+ code_ns_fraction (ns, sbuf + UINTMAX_STRSIZE_BOUND);
+ return np;
+}
+
+struct timespec
+decode_timespec (char const *arg, char **arg_lim, bool parse_fraction)
+{
+ time_t s = TYPE_MINIMUM (time_t);
+ int ns = -1;
+ char const *p = arg;
+ bool negative = *arg == '-';
+ struct timespec r;
+
+ if (! ISDIGIT (arg[negative]))
+ errno = EINVAL;
+ else
+ {
+ errno = 0;
+
+ if (negative)
+ {
+ intmax_t i = strtoimax (arg, arg_lim, 10);
+ if (TYPE_SIGNED (time_t) ? TYPE_MINIMUM (time_t) <= i : 0 <= i)
+ s = i;
+ else
+ errno = ERANGE;
+ }
+ else
+ {
+ uintmax_t i = strtoumax (arg, arg_lim, 10);
+ if (i <= TYPE_MAXIMUM (time_t))
+ s = i;
+ else
+ errno = ERANGE;
+ }
+
+ p = *arg_lim;
+ ns = 0;
+
+ if (parse_fraction && *p == '.')
+ {
+ int digits = 0;
+ bool trailing_nonzero = false;
+
+ while (ISDIGIT (*++p))
+ if (digits < LOG10_BILLION)
+ digits++, ns = 10 * ns + (*p - '0');
+ else
+ trailing_nonzero |= *p != '0';
+
+ 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. */
+ ns += trailing_nonzero;
+ 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;
+}
+
+/* File handling. */
+
+/* Saved names in case backup needs to be undone. */
+static char *before_backup_name;
+static char *after_backup_name;
+
+/* Return 1 if FILE_NAME is obviously "." or "/". */
+bool
+must_be_dot_or_slash (char const *file_name)
+{
+ file_name += FILE_SYSTEM_PREFIX_LEN (file_name);
+
+ if (ISSLASH (file_name[0]))
+ {
+ for (;;)
+ if (ISSLASH (file_name[1]))
+ file_name++;
+ else if (file_name[1] == '.'
+ && ISSLASH (file_name[2 + (file_name[2] == '.')]))
+ file_name += 2 + (file_name[2] == '.');
+ else
+ return ! file_name[1];
+ }
+ else
+ {
+ while (file_name[0] == '.' && ISSLASH (file_name[1]))
+ {
+ file_name += 2;
+ while (ISSLASH (*file_name))
+ file_name++;
+ }
+
+ return ! file_name[0] || (file_name[0] == '.' && ! file_name[1]);
+ }
+}
+
+/* Some implementations of rmdir let you remove '.' or '/'.
+ Report an error with errno set to zero for obvious cases of this;
+ otherwise call rmdir. */
+static int
+safer_rmdir (const char *file_name)
+{
+ if (must_be_dot_or_slash (file_name))
+ {
+ errno = 0;
+ return -1;
+ }
+
+ if (unlinkat (chdir_fd, file_name, AT_REMOVEDIR) == 0)
+ {
+ remove_delayed_set_stat (file_name);
+ return 0;
+ }
+ return -1;
+}
+
+/* Remove FILE_NAME, returning 1 on success. If FILE_NAME is a directory,
+ then if OPTION is RECURSIVE_REMOVE_OPTION is set remove FILE_NAME
+ recursively; otherwise, remove it only if it is empty. If FILE_NAME is
+ a directory that cannot be removed (e.g., because it is nonempty)
+ and if OPTION is WANT_DIRECTORY_REMOVE_OPTION, then return -1.
+ Return 0 on error, with errno set; if FILE_NAME is obviously the working
+ directory return zero with errno set to zero. */
+int
+remove_any_file (const char *file_name, enum remove_option option)
+{
+ /* Try unlink first if we cannot unlink directories, as this saves
+ us a system call in the common case where we're removing a
+ non-directory. */
+ bool try_unlink_first = cannot_unlink_dir ();
+
+ if (try_unlink_first)
+ {
+ if (unlinkat (chdir_fd, file_name, 0) == 0)
+ return 1;
+
+ /* POSIX 1003.1-2001 requires EPERM when attempting to unlink a
+ directory without appropriate privileges, but many Linux
+ kernels return the more-sensible EISDIR. */
+ if (errno != EPERM && errno != EISDIR)
+ return 0;
+ }
+
+ if (safer_rmdir (file_name) == 0)
+ return 1;
+
+ switch (errno)
+ {
+ case ENOTDIR:
+ return !try_unlink_first && unlinkat (chdir_fd, file_name, 0) == 0;
+
+ case 0:
+ case EEXIST:
+#if defined ENOTEMPTY && ENOTEMPTY != EEXIST
+ case ENOTEMPTY:
+#endif
+ switch (option)
+ {
+ case ORDINARY_REMOVE_OPTION:
+ break;
+
+ case WANT_DIRECTORY_REMOVE_OPTION:
+ return -1;
+
+ case RECURSIVE_REMOVE_OPTION:
+ {
+ char *directory = tar_savedir (file_name, 0);
+ char const *entry;
+ size_t entrylen;
+
+ if (! directory)
+ return 0;
+
+ for (entry = directory;
+ (entrylen = strlen (entry)) != 0;
+ entry += entrylen + 1)
+ {
+ char *file_name_buffer = make_file_name (file_name, entry);
+ int r = remove_any_file (file_name_buffer,
+ RECURSIVE_REMOVE_OPTION);
+ int e = errno;
+ free (file_name_buffer);
+
+ if (! r)
+ {
+ free (directory);
+ errno = e;
+ return 0;
+ }
+ }
+
+ free (directory);
+ return safer_rmdir (file_name) == 0;
+ }
+ }
+ break;
+ }
+
+ return 0;
+}
+
+/* Check if FILE_NAME already exists and make a backup of it right now.
+ Return success (nonzero) only if the backup is either unneeded, or
+ successful. For now, directories are considered to never need
+ backup. If THIS_IS_THE_ARCHIVE is nonzero, this is the archive and
+ so, we do not have to backup block or character devices, nor remote
+ entities. */
+bool
+maybe_backup_file (const char *file_name, bool this_is_the_archive)
+{
+ struct stat file_stat;
+
+ assign_string (&before_backup_name, file_name);
+
+ /* A run situation may exist between Emacs or other GNU programs trying to
+ make a backup for the same file simultaneously. If theoretically
+ possible, real problems are unlikely. Doing any better would require a
+ convention, GNU-wide, for all programs doing backups. */
+
+ assign_string (&after_backup_name, 0);
+
+ /* Check if we really need to backup the file. */
+
+ if (this_is_the_archive && _remdev (file_name))
+ return true;
+
+ if (deref_stat (file_name, &file_stat) != 0)
+ {
+ if (errno == ENOENT)
+ return true;
+
+ stat_error (file_name);
+ return false;
+ }
+
+ if (S_ISDIR (file_stat.st_mode))
+ return true;
+
+ if (this_is_the_archive
+ && (S_ISBLK (file_stat.st_mode) || S_ISCHR (file_stat.st_mode)))
+ return true;
+
+ after_backup_name = find_backup_file_name (file_name, backup_type);
+ if (! after_backup_name)
+ xalloc_die ();
+
+ if (renameat (chdir_fd, before_backup_name, chdir_fd, after_backup_name)
+ == 0)
+ {
+ if (verbose_option)
+ fprintf (stdlis, _("Renaming %s to %s\n"),
+ quote_n (0, before_backup_name),
+ quote_n (1, after_backup_name));
+ return true;
+ }
+ else
+ {
+ /* The backup operation failed. */
+ int e = errno;
+ ERROR ((0, e, _("%s: Cannot rename to %s"),
+ quotearg_colon (before_backup_name),
+ quote_n (1, after_backup_name)));
+ assign_string (&after_backup_name, 0);
+ return false;
+ }
+}
+
+/* Try to restore the recently backed up file to its original name.
+ This is usually only needed after a failed extraction. */
+void
+undo_last_backup (void)
+{
+ if (after_backup_name)
+ {
+ if (renameat (chdir_fd, after_backup_name, chdir_fd, before_backup_name)
+ != 0)
+ {
+ int e = errno;
+ ERROR ((0, e, _("%s: Cannot rename to %s"),
+ quotearg_colon (after_backup_name),
+ quote_n (1, before_backup_name)));
+ }
+ if (verbose_option)
+ fprintf (stdlis, _("Renaming %s back to %s\n"),
+ quote_n (0, after_backup_name),
+ quote_n (1, before_backup_name));
+ assign_string (&after_backup_name, 0);
+ }
+}
+
+/* Apply either stat or lstat to (NAME, BUF), depending on the
+ presence of the --dereference option. NAME is relative to the
+ most-recent argument to chdir_do. */
+int
+deref_stat (char const *name, struct stat *buf)
+{
+ return fstatat (chdir_fd, name, buf, fstatat_flags);
+}
+
+/* Read from FD into the buffer BUF with COUNT bytes. Attempt to fill
+ BUF. Wait until input is available; this matters because files are
+ opened O_NONBLOCK for security reasons, and on some file systems
+ this can cause read to fail with errno == EAGAIN. Return the
+ actual number of bytes read, zero for EOF, or
+ SAFE_READ_ERROR upon error. */
+size_t
+blocking_read (int fd, void *buf, size_t count)
+{
+ size_t bytes = safe_read (fd, buf, count);
+
+#if defined F_SETFL && O_NONBLOCK
+ if (bytes == SAFE_READ_ERROR && errno == EAGAIN)
+ {
+ int flags = fcntl (fd, F_GETFL);
+ if (0 <= flags && flags & O_NONBLOCK
+ && fcntl (fd, F_SETFL, flags & ~O_NONBLOCK) != -1)
+ bytes = safe_read (fd, buf, count);
+ }
+#endif
+
+ return bytes;
+}
+
+/* Write to FD from the buffer BUF with COUNT bytes. Do a full write.
+ Wait until an output buffer is available; this matters because
+ files are opened O_NONBLOCK for security reasons, and on some file
+ systems this can cause write to fail with errno == EAGAIN. Return
+ the actual number of bytes written, setting errno if that is less
+ than COUNT. */
+size_t
+blocking_write (int fd, void const *buf, size_t count)
+{
+ size_t bytes = full_write (fd, buf, count);
+
+#if defined F_SETFL && O_NONBLOCK
+ if (bytes < count && errno == EAGAIN)
+ {
+ int flags = fcntl (fd, F_GETFL);
+ if (0 <= flags && flags & O_NONBLOCK
+ && fcntl (fd, F_SETFL, flags & ~O_NONBLOCK) != -1)
+ {
+ char const *buffer = buf;
+ bytes += full_write (fd, buffer + bytes, count - bytes);
+ }
+ }
+#endif
+
+ return bytes;
+}
+
+/* Set FD's (i.e., assuming the working directory is PARENTFD, FILE's)
+ access time to ATIME. */
+int
+set_file_atime (int fd, int parentfd, char const *file, struct timespec atime)
+{
+ struct timespec ts[2];
+ ts[0] = atime;
+ ts[1].tv_nsec = UTIME_OMIT;
+ return fdutimensat (fd, parentfd, file, ts, fstatat_flags);
+}
+
+/* A description of a working directory. */
+struct wd
+{
+ /* The directory's name. */
+ char const *name;
+ /* "Absolute" path representing this directory; in the contrast to
+ the real absolute pathname, it can contain /../ components (see
+ normalize_filename_x for the reason of it). It is NULL if the
+ absolute path could not be determined. */
+ char *abspath;
+ /* If nonzero, the file descriptor of the directory, or AT_FDCWD if
+ the working directory. If zero, the directory needs to be opened
+ to be used. */
+ int fd;
+};
+
+/* A vector of chdir targets. wd[0] is the initial working directory. */
+static struct wd *wd;
+
+/* The number of working directories in the vector. */
+static size_t wd_count;
+
+/* The allocated size of the vector. */
+static size_t wd_alloc;
+
+/* The maximum number of chdir targets with open directories.
+ Don't make it too large, as many operating systems have a small
+ limit on the number of open file descriptors. Also, the current
+ implementation does not scale well. */
+enum { CHDIR_CACHE_SIZE = 16 };
+
+/* Indexes into WD of chdir targets with open file descriptors, sorted
+ most-recently used first. Zero indexes are unused. */
+static int wdcache[CHDIR_CACHE_SIZE];
+
+/* Number of nonzero entries in WDCACHE. */
+static size_t wdcache_count;
+
+int
+chdir_count (void)
+{
+ if (wd_count == 0)
+ return wd_count;
+ return wd_count - 1;
+}
+
+/* DIR is the operand of a -C option; add it to vector of chdir targets,
+ and return the index of its location. */
+int
+chdir_arg (char const *dir)
+{
+ char *absdir;
+
+ if (wd_count == wd_alloc)
+ {
+ if (wd_alloc == 0)
+ wd_alloc = 2;
+ wd = x2nrealloc (wd, &wd_alloc, sizeof *wd);
+
+ if (! wd_count)
+ {
+ wd[wd_count].name = ".";
+ wd[wd_count].abspath = xgetcwd ();
+ wd[wd_count].fd = AT_FDCWD;
+ wd_count++;
+ }
+ }
+
+ /* Optimize the common special case of the working directory,
+ or the working directory as a prefix. */
+ if (dir[0])
+ {
+ while (dir[0] == '.' && ISSLASH (dir[1]))
+ for (dir += 2; ISSLASH (*dir); dir++)
+ continue;
+ if (! dir[dir[0] == '.'])
+ return wd_count - 1;
+ }
+
+
+ /* If the given name is absolute, use it to represent this directory;
+ otherwise, construct a name based on the previous -C option. */
+ if (IS_ABSOLUTE_FILE_NAME (dir))
+ absdir = xstrdup (dir);
+ else if (wd[wd_count - 1].abspath)
+ {
+ namebuf_t nbuf = namebuf_create (wd[wd_count - 1].abspath);
+ namebuf_add_dir (nbuf, dir);
+ absdir = namebuf_finish (nbuf);
+ }
+ else
+ absdir = 0;
+
+ wd[wd_count].name = dir;
+ wd[wd_count].abspath = absdir;
+ wd[wd_count].fd = 0;
+ return wd_count++;
+}
+
+/* Index of current directory. */
+int chdir_current;
+
+/* Value suitable for use as the first argument to openat, and in
+ similar locations for fstatat, etc. This is an open file
+ descriptor, or AT_FDCWD if the working directory is current. It is
+ valid until the next invocation of chdir_do. */
+int chdir_fd = AT_FDCWD;
+
+/* Change to directory I, in a virtual way. This does not actually
+ invoke chdir; it merely sets chdir_fd to an int suitable as the
+ first argument for openat, etc. If I is 0, change to the initial
+ working directory; otherwise, I must be a value returned by
+ chdir_arg. */
+void
+chdir_do (int i)
+{
+ if (chdir_current != i)
+ {
+ struct wd *curr = &wd[i];
+ int fd = curr->fd;
+
+ if (! fd)
+ {
+ if (! IS_ABSOLUTE_FILE_NAME (curr->name))
+ chdir_do (i - 1);
+ fd = openat (chdir_fd, curr->name,
+ open_searchdir_flags & ~ O_NOFOLLOW);
+ if (fd < 0)
+ open_fatal (curr->name);
+
+ curr->fd = fd;
+
+ /* Add I to the cache, tossing out the lowest-ranking entry if the
+ cache is full. */
+ if (wdcache_count < CHDIR_CACHE_SIZE)
+ wdcache[wdcache_count++] = i;
+ else
+ {
+ struct wd *stale = &wd[wdcache[CHDIR_CACHE_SIZE - 1]];
+ if (close (stale->fd) != 0)
+ close_diag (stale->name);
+ stale->fd = 0;
+ wdcache[CHDIR_CACHE_SIZE - 1] = i;
+ }
+ }
+
+ if (0 < fd)
+ {
+ /* Move the i value to the front of the cache. This is
+ O(CHDIR_CACHE_SIZE), but the cache is small. */
+ size_t ci;
+ int prev = wdcache[0];
+ for (ci = 1; prev != i; ci++)
+ {
+ int cur = wdcache[ci];
+ wdcache[ci] = prev;
+ if (cur == i)
+ break;
+ prev = cur;
+ }
+ wdcache[0] = i;
+ }
+
+ chdir_current = i;
+ chdir_fd = fd;
+ }
+}
+
+const char *
+tar_dirname (void)
+{
+ return wd[chdir_current].name;
+}
+
+/* Return the absolute path that represents the working
+ directory referenced by IDX.
+
+ If wd is empty, then there were no -C options given, and
+ chdir_args() has never been called, so we simply return the
+ process's actual cwd. (Note that in this case IDX is ignored,
+ since it should always be 0.) */
+static const char *
+tar_getcdpath (int idx)
+{
+ if (!wd)
+ {
+ static char *cwd;
+ if (!cwd)
+ cwd = xgetcwd ();
+ return cwd;
+ }
+ return wd[idx].abspath;
+}
+
+void
+close_diag (char const *name)
+{
+ if (ignore_failed_read_option)
+ close_warn (name);
+ else
+ close_error (name);
+}
+
+void
+open_diag (char const *name)
+{
+ if (ignore_failed_read_option)
+ open_warn (name);
+ else
+ open_error (name);
+}
+
+void
+read_diag_details (char const *name, off_t offset, size_t size)
+{
+ if (ignore_failed_read_option)
+ read_warn_details (name, offset, size);
+ else
+ read_error_details (name, offset, size);
+}
+
+void
+readlink_diag (char const *name)
+{
+ if (ignore_failed_read_option)
+ readlink_warn (name);
+ else
+ readlink_error (name);
+}
+
+void
+savedir_diag (char const *name)
+{
+ if (ignore_failed_read_option)
+ savedir_warn (name);
+ else
+ savedir_error (name);
+}
+
+void
+seek_diag_details (char const *name, off_t offset)
+{
+ if (ignore_failed_read_option)
+ seek_warn_details (name, offset);
+ else
+ seek_error_details (name, offset);
+}
+
+void
+stat_diag (char const *name)
+{
+ if (ignore_failed_read_option)
+ stat_warn (name);
+ else
+ stat_error (name);
+}
+
+void
+file_removed_diag (const char *name, bool top_level,
+ void (*diagfn) (char const *name))
+{
+ if (!top_level && errno == ENOENT)
+ {
+ WARNOPT (WARN_FILE_REMOVED,
+ (0, 0, _("%s: File removed before we read it"),
+ quotearg_colon (name)));
+ set_exit_status (TAREXIT_DIFFERS);
+ }
+ else
+ diagfn (name);
+}
+
+/* Fork, aborting if unsuccessful. */
+pid_t
+xfork (void)
+{
+ pid_t p = fork ();
+ if (p == (pid_t) -1)
+ call_arg_fatal ("fork", _("child process"));
+ return p;
+}
+
+/* Create a pipe, aborting if unsuccessful. */
+void
+xpipe (int fd[2])
+{
+ if (pipe (fd) < 0)
+ call_arg_fatal ("pipe", _("interprocess channel"));
+}
+
+/* Return PTR, aligned upward to the next multiple of ALIGNMENT.
+ ALIGNMENT must be nonzero. The caller must arrange for ((char *)
+ PTR) through ((char *) PTR + ALIGNMENT - 1) to be addressable
+ locations. */
+
+static inline void *
+ptr_align (void *ptr, size_t alignment)
+{
+ char *p0 = ptr;
+ char *p1 = p0 + alignment - 1;
+ return p1 - (size_t) p1 % alignment;
+}
+
+/* Return the address of a page-aligned buffer of at least SIZE bytes.
+ The caller should free *PTR when done with the buffer. */
+
+void *
+page_aligned_alloc (void **ptr, size_t size)
+{
+ size_t alignment = getpagesize ();
+ size_t size1 = size + alignment;
+ if (size1 < size)
+ xalloc_die ();
+ *ptr = xmalloc (size1);
+ return ptr_align (*ptr, alignment);
+}
+
+
+
+struct namebuf
+{
+ char *buffer; /* directory, '/', and directory member */
+ size_t buffer_size; /* allocated size of name_buffer */
+ size_t dir_length; /* length of directory part in buffer */
+};
+
+namebuf_t
+namebuf_create (const char *dir)
+{
+ namebuf_t buf = xmalloc (sizeof (*buf));
+ buf->buffer_size = strlen (dir) + 2;
+ buf->buffer = xmalloc (buf->buffer_size);
+ strcpy (buf->buffer, dir);
+ buf->dir_length = strlen (buf->buffer);
+ if (!ISSLASH (buf->buffer[buf->dir_length - 1]))
+ buf->buffer[buf->dir_length++] = DIRECTORY_SEPARATOR;
+ return buf;
+}
+
+void
+namebuf_free (namebuf_t buf)
+{
+ free (buf->buffer);
+ free (buf);
+}
+
+char *
+namebuf_name (namebuf_t buf, const char *name)
+{
+ size_t len = strlen (name);
+ while (buf->dir_length + len + 1 >= buf->buffer_size)
+ buf->buffer = x2realloc (buf->buffer, &buf->buffer_size);
+ strcpy (buf->buffer + buf->dir_length, name);
+ return buf->buffer;
+}
+
+static void
+namebuf_add_dir (namebuf_t buf, const char *name)
+{
+ static char dirsep[] = { DIRECTORY_SEPARATOR, 0 };
+ if (!ISSLASH (buf->buffer[buf->dir_length - 1]))
+ {
+ namebuf_name (buf, dirsep);
+ buf->dir_length++;
+ }
+ namebuf_name (buf, name);
+ buf->dir_length += strlen (name);
+}
+
+static char *
+namebuf_finish (namebuf_t buf)
+{
+ char *res = buf->buffer;
+
+ if (ISSLASH (buf->buffer[buf->dir_length - 1]))
+ buf->buffer[buf->dir_length] = 0;
+ free (buf);
+ return res;
+}
+
+/* Return the filenames in directory NAME, relative to the chdir_fd.
+ If the directory does not exist, report error if MUST_EXIST is
+ true.
+
+ Return NULL on errors.
+*/
+char *
+tar_savedir (const char *name, int must_exist)
+{
+ char *ret = NULL;
+ DIR *dir = NULL;
+ int fd = openat (chdir_fd, name, open_read_flags | O_DIRECTORY);
+ if (fd < 0)
+ {
+ if (!must_exist && errno == ENOENT)
+ return NULL;
+ open_error (name);
+ }
+ else if (! ((dir = fdopendir (fd))
+ && (ret = streamsavedir (dir, savedir_sort_order))))
+ savedir_error (name);
+
+ if (dir ? closedir (dir) != 0 : 0 <= fd && close (fd) != 0)
+ savedir_error (name);
+
+ return ret;
+}
diff --git a/src/names.c b/src/names.c
new file mode 100644
index 0000000..037b869
--- /dev/null
+++ b/src/names.c
@@ -0,0 +1,1838 @@
+/* Various processing of names.
+
+ Copyright 1988, 1992, 1994, 1996-2001, 2003-2007, 2009, 2013-2016
+ Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 3, or (at your option) any later
+ version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <system.h>
+
+#include <fnmatch.h>
+#include <hash.h>
+#include <quotearg.h>
+#include <wordsplit.h>
+#include <argp.h>
+
+#include "common.h"
+
+static void name_add_option (int option, const char *arg);
+static void name_add_dir (const char *name);
+static void name_add_file (const char *name);
+
+enum
+ {
+ EXCLUDE_BACKUPS_OPTION = 256,
+ EXCLUDE_CACHES_OPTION,
+ EXCLUDE_CACHES_UNDER_OPTION,
+ EXCLUDE_CACHES_ALL_OPTION,
+ EXCLUDE_OPTION,
+ EXCLUDE_IGNORE_OPTION,
+ EXCLUDE_IGNORE_RECURSIVE_OPTION,
+ EXCLUDE_TAG_OPTION,
+ EXCLUDE_TAG_UNDER_OPTION,
+ EXCLUDE_TAG_ALL_OPTION,
+ EXCLUDE_VCS_OPTION,
+ EXCLUDE_VCS_IGNORES_OPTION,
+ IGNORE_CASE_OPTION,
+ NO_IGNORE_CASE_OPTION,
+ ANCHORED_OPTION,
+ NO_ANCHORED_OPTION,
+ RECURSION_OPTION,
+ NO_RECURSION_OPTION,
+ UNQUOTE_OPTION,
+ NO_UNQUOTE_OPTION,
+ NO_VERBATIM_FILES_FROM_OPTION,
+ NO_WILDCARDS_MATCH_SLASH_OPTION,
+ NO_WILDCARDS_OPTION,
+ NULL_OPTION,
+ NO_NULL_OPTION,
+ VERBATIM_FILES_FROM_OPTION,
+ WILDCARDS_MATCH_SLASH_OPTION,
+ WILDCARDS_OPTION
+ };
+
+static struct argp_option names_options[] = {
+#define GRID 100
+ {NULL, 0, NULL, 0,
+ N_("Local file name selection:"), GRID },
+
+ {"add-file", ARGP_KEY_ARG, N_("FILE"), 0,
+ N_("add given FILE to the archive (useful if its name starts with a dash)"), GRID+1 },
+ {"directory", 'C', N_("DIR"), 0,
+ N_("change to directory DIR"), GRID+1 },
+ {"files-from", 'T', N_("FILE"), 0,
+ N_("get names to extract or create from FILE"), GRID+1 },
+ {"null", NULL_OPTION, 0, 0,
+ N_("-T reads null-terminated names; implies --verbatim-files-from"),
+ GRID+1 },
+ {"no-null", NO_NULL_OPTION, 0, 0,
+ N_("disable the effect of the previous --null option"), GRID+1 },
+ {"unquote", UNQUOTE_OPTION, 0, 0,
+ N_("unquote input file or member names (default)"), GRID+1 },
+ {"no-unquote", NO_UNQUOTE_OPTION, 0, 0,
+ N_("do not unquote input file or member names"), GRID+1 },
+ {"verbatim-files-from", VERBATIM_FILES_FROM_OPTION, 0, 0,
+ N_("-T reads file names verbatim (no option handling)"), GRID+1 },
+ {"no-verbatim-files-from", NO_VERBATIM_FILES_FROM_OPTION, 0, 0,
+ N_("-T treats file names starting with dash as options (default)"),
+ GRID+1 },
+ {"exclude", EXCLUDE_OPTION, N_("PATTERN"), 0,
+ N_("exclude files, given as a PATTERN"), GRID+1 },
+ {"exclude-from", 'X', N_("FILE"), 0,
+ N_("exclude patterns listed in FILE"), GRID+1 },
+ {"exclude-caches", EXCLUDE_CACHES_OPTION, 0, 0,
+ N_("exclude contents of directories containing CACHEDIR.TAG, "
+ "except for the tag file itself"), GRID+1 },
+ {"exclude-caches-under", EXCLUDE_CACHES_UNDER_OPTION, 0, 0,
+ N_("exclude everything under directories containing CACHEDIR.TAG"),
+ GRID+1 },
+ {"exclude-caches-all", EXCLUDE_CACHES_ALL_OPTION, 0, 0,
+ N_("exclude directories containing CACHEDIR.TAG"), GRID+1 },
+ {"exclude-tag", EXCLUDE_TAG_OPTION, N_("FILE"), 0,
+ N_("exclude contents of directories containing FILE, except"
+ " for FILE itself"), GRID+1 },
+ {"exclude-ignore", EXCLUDE_IGNORE_OPTION, N_("FILE"), 0,
+ N_("read exclude patterns for each directory from FILE, if it exists"),
+ GRID+1 },
+ {"exclude-ignore-recursive", EXCLUDE_IGNORE_RECURSIVE_OPTION, N_("FILE"), 0,
+ N_("read exclude patterns for each directory and its subdirectories "
+ "from FILE, if it exists"), GRID+1 },
+ {"exclude-tag-under", EXCLUDE_TAG_UNDER_OPTION, N_("FILE"), 0,
+ N_("exclude everything under directories containing FILE"), GRID+1 },
+ {"exclude-tag-all", EXCLUDE_TAG_ALL_OPTION, N_("FILE"), 0,
+ N_("exclude directories containing FILE"), GRID+1 },
+ {"exclude-vcs", EXCLUDE_VCS_OPTION, NULL, 0,
+ N_("exclude version control system directories"), GRID+1 },
+ {"exclude-vcs-ignores", EXCLUDE_VCS_IGNORES_OPTION, NULL, 0,
+ N_("read exclude patterns from the VCS ignore files"), GRID+1 },
+ {"exclude-backups", EXCLUDE_BACKUPS_OPTION, NULL, 0,
+ N_("exclude backup and lock files"), GRID+1 },
+ {"recursion", RECURSION_OPTION, 0, 0,
+ N_("recurse into directories (default)"), GRID+1 },
+ {"no-recursion", NO_RECURSION_OPTION, 0, 0,
+ N_("avoid descending automatically in directories"), GRID+1 },
+#undef GRID
+
+#define GRID 120
+ {NULL, 0, NULL, 0,
+ N_("File name matching options (affect both exclude and include patterns):"),
+ GRID },
+ {"anchored", ANCHORED_OPTION, 0, 0,
+ N_("patterns match file name start"), GRID+1 },
+ {"no-anchored", NO_ANCHORED_OPTION, 0, 0,
+ N_("patterns match after any '/' (default for exclusion)"), GRID+1 },
+ {"ignore-case", IGNORE_CASE_OPTION, 0, 0,
+ N_("ignore case"), GRID+1 },
+ {"no-ignore-case", NO_IGNORE_CASE_OPTION, 0, 0,
+ N_("case sensitive matching (default)"), GRID+1 },
+ {"wildcards", WILDCARDS_OPTION, 0, 0,
+ N_("use wildcards (default for exclusion)"), GRID+1 },
+ {"no-wildcards", NO_WILDCARDS_OPTION, 0, 0,
+ N_("verbatim string matching"), GRID+1 },
+ {"wildcards-match-slash", WILDCARDS_MATCH_SLASH_OPTION, 0, 0,
+ N_("wildcards match '/' (default for exclusion)"), GRID+1 },
+ {"no-wildcards-match-slash", NO_WILDCARDS_MATCH_SLASH_OPTION, 0, 0,
+ N_("wildcards do not match '/'"), GRID+1 },
+#undef GRID
+
+ {NULL}
+};
+
+static bool
+is_file_selection_option (int key)
+{
+ struct argp_option *p;
+
+ for (p = names_options;
+ !(p->name == NULL && p->key == 0 && p->doc == NULL); p++)
+ if (p->key == key)
+ return true;
+ return false;
+}
+
+/* Either NL or NUL, as decided by the --null option. */
+static char filename_terminator = '\n';
+/* Treat file names read from -T input verbatim */
+static bool verbatim_files_from_option;
+
+static error_t
+names_parse_opt (int key, char *arg, struct argp_state *state)
+{
+ switch (key)
+ {
+ case 'C':
+ name_add_dir (arg);
+ break;
+
+ case 'T':
+ name_add_file (arg);
+ /* Indicate we've been given -T option. This is for backward
+ compatibility only, so that `tar cfT archive /dev/null will
+ succeed */
+ files_from_option = true;
+ break;
+
+ default:
+ if (is_file_selection_option (key))
+ name_add_option (key, arg);
+ else
+ return ARGP_ERR_UNKNOWN;
+ }
+ return 0;
+}
+
+/* Wildcard matching settings */
+enum wildcards
+ {
+ default_wildcards, /* For exclusion == enable_wildcards,
+ for inclusion == disable_wildcards */
+ disable_wildcards,
+ enable_wildcards
+ };
+
+static enum wildcards wildcards = default_wildcards;
+ /* Wildcard settings (--wildcards/--no-wildcards) */
+static int matching_flags = 0;
+ /* exclude_fnmatch options */
+static int include_anchored = EXCLUDE_ANCHORED;
+ /* Pattern anchoring options used for file inclusion */
+
+#define EXCLUDE_OPTIONS \
+ (((wildcards != disable_wildcards) ? EXCLUDE_WILDCARDS : 0) \
+ | matching_flags \
+ | recursion_option)
+
+#define INCLUDE_OPTIONS \
+ (((wildcards == enable_wildcards) ? EXCLUDE_WILDCARDS : 0) \
+ | include_anchored \
+ | matching_flags \
+ | recursion_option)
+
+static char const * const vcs_file_table[] = {
+ /* CVS: */
+ "CVS",
+ ".cvsignore",
+ /* RCS: */
+ "RCS",
+ /* SCCS: */
+ "SCCS",
+ /* SVN: */
+ ".svn",
+ /* git: */
+ ".git",
+ ".gitignore",
+ ".gitattributes",
+ ".gitmodules",
+ /* Arch: */
+ ".arch-ids",
+ "{arch}",
+ "=RELEASE-ID",
+ "=meta-update",
+ "=update",
+ /* Bazaar */
+ ".bzr",
+ ".bzrignore",
+ ".bzrtags",
+ /* Mercurial */
+ ".hg",
+ ".hgignore",
+ ".hgtags",
+ /* darcs */
+ "_darcs",
+ NULL
+};
+
+static char const * const backup_file_table[] = {
+ ".#*",
+ "*~",
+ "#*#",
+ NULL
+};
+
+static void
+add_exclude_array (char const * const * fv, int opts)
+{
+ int i;
+
+ for (i = 0; fv[i]; i++)
+ add_exclude (excluded, fv[i], opts);
+}
+
+static void
+handle_file_selection_option (int key, const char *arg)
+{
+ switch (key)
+ {
+ case EXCLUDE_BACKUPS_OPTION:
+ add_exclude_array (backup_file_table, EXCLUDE_WILDCARDS);
+ break;
+
+ case EXCLUDE_OPTION:
+ add_exclude (excluded, arg, EXCLUDE_OPTIONS);
+ break;
+
+ case EXCLUDE_CACHES_OPTION:
+ add_exclusion_tag ("CACHEDIR.TAG", exclusion_tag_contents,
+ cachedir_file_p);
+ break;
+
+ case EXCLUDE_CACHES_UNDER_OPTION:
+ add_exclusion_tag ("CACHEDIR.TAG", exclusion_tag_under,
+ cachedir_file_p);
+ break;
+
+ case EXCLUDE_CACHES_ALL_OPTION:
+ add_exclusion_tag ("CACHEDIR.TAG", exclusion_tag_all,
+ cachedir_file_p);
+ break;
+
+ case EXCLUDE_IGNORE_OPTION:
+ excfile_add (arg, EXCL_NON_RECURSIVE);
+ break;
+
+ case EXCLUDE_IGNORE_RECURSIVE_OPTION:
+ excfile_add (arg, EXCL_RECURSIVE);
+ break;
+
+ case EXCLUDE_TAG_OPTION:
+ add_exclusion_tag (arg, exclusion_tag_contents, NULL);
+ break;
+
+ case EXCLUDE_TAG_UNDER_OPTION:
+ add_exclusion_tag (arg, exclusion_tag_under, NULL);
+ break;
+
+ case EXCLUDE_TAG_ALL_OPTION:
+ add_exclusion_tag (arg, exclusion_tag_all, NULL);
+ break;
+
+ case EXCLUDE_VCS_OPTION:
+ add_exclude_array (vcs_file_table, 0);
+ break;
+
+ case EXCLUDE_VCS_IGNORES_OPTION:
+ exclude_vcs_ignores ();
+ break;
+
+ case RECURSION_OPTION:
+ recursion_option = FNM_LEADING_DIR;
+ break;
+
+ case NO_RECURSION_OPTION:
+ recursion_option = 0;
+ break;
+
+ case UNQUOTE_OPTION:
+ unquote_option = true;
+ break;
+
+ case NO_UNQUOTE_OPTION:
+ unquote_option = false;
+ break;
+
+ case NULL_OPTION:
+ filename_terminator = '\0';
+ verbatim_files_from_option = true;
+ break;
+
+ case NO_NULL_OPTION:
+ filename_terminator = '\n';
+ verbatim_files_from_option = false;
+ break;
+
+ case 'X':
+ if (add_exclude_file (add_exclude, excluded, arg, EXCLUDE_OPTIONS, '\n')
+ != 0)
+ {
+ int e = errno;
+ FATAL_ERROR ((0, e, "%s", quotearg_colon (arg)));
+ }
+ break;
+
+ case ANCHORED_OPTION:
+ matching_flags |= EXCLUDE_ANCHORED;
+ break;
+
+ case NO_ANCHORED_OPTION:
+ include_anchored = 0; /* Clear the default for comman line args */
+ matching_flags &= ~ EXCLUDE_ANCHORED;
+ break;
+
+ case IGNORE_CASE_OPTION:
+ matching_flags |= FNM_CASEFOLD;
+ break;
+
+ case NO_IGNORE_CASE_OPTION:
+ matching_flags &= ~ FNM_CASEFOLD;
+ break;
+
+ case WILDCARDS_OPTION:
+ wildcards = enable_wildcards;
+ break;
+
+ case NO_WILDCARDS_OPTION:
+ wildcards = disable_wildcards;
+ break;
+
+ case WILDCARDS_MATCH_SLASH_OPTION:
+ matching_flags &= ~ FNM_FILE_NAME;
+ break;
+
+ case NO_WILDCARDS_MATCH_SLASH_OPTION:
+ matching_flags |= FNM_FILE_NAME;
+ break;
+
+ case VERBATIM_FILES_FROM_OPTION:
+ verbatim_files_from_option = true;
+ break;
+
+ case NO_VERBATIM_FILES_FROM_OPTION:
+ verbatim_files_from_option = false;
+ break;
+
+ default:
+ FATAL_ERROR ((0, 0, "unhandled positional option %d", key));
+ }
+}
+
+static struct argp names_argp = {
+ names_options,
+ names_parse_opt,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+struct argp_child names_argp_children[] = {
+ { &names_argp, 0, "", 0 },
+ { NULL }
+};
+
+/* User and group names. */
+
+/* Make sure you link with the proper libraries if you are running the
+ Yellow Peril (thanks for the good laugh, Ian J.!), or, euh... NIS.
+ This code should also be modified for non-UNIX systems to do something
+ reasonable. */
+
+static char *cached_uname;
+static char *cached_gname;
+
+static uid_t cached_uid; /* valid only if cached_uname is not empty */
+static gid_t cached_gid; /* valid only if cached_gname is not empty */
+
+/* These variables are valid only if nonempty. */
+static char *cached_no_such_uname;
+static char *cached_no_such_gname;
+
+/* These variables are valid only if nonzero. It's not worth optimizing
+ the case for weird systems where 0 is not a valid uid or gid. */
+static uid_t cached_no_such_uid;
+static gid_t cached_no_such_gid;
+
+/* Given UID, find the corresponding UNAME. */
+void
+uid_to_uname (uid_t uid, char **uname)
+{
+ struct passwd *passwd;
+
+ if (uid != 0 && uid == cached_no_such_uid)
+ {
+ *uname = xstrdup ("");
+ return;
+ }
+
+ if (!cached_uname || uid != cached_uid)
+ {
+ passwd = getpwuid (uid);
+ if (passwd)
+ {
+ cached_uid = uid;
+ assign_string (&cached_uname, passwd->pw_name);
+ }
+ else
+ {
+ cached_no_such_uid = uid;
+ *uname = xstrdup ("");
+ return;
+ }
+ }
+ *uname = xstrdup (cached_uname);
+}
+
+/* Given GID, find the corresponding GNAME. */
+void
+gid_to_gname (gid_t gid, char **gname)
+{
+ struct group *group;
+
+ if (gid != 0 && gid == cached_no_such_gid)
+ {
+ *gname = xstrdup ("");
+ return;
+ }
+
+ if (!cached_gname || gid != cached_gid)
+ {
+ group = getgrgid (gid);
+ if (group)
+ {
+ cached_gid = gid;
+ assign_string (&cached_gname, group->gr_name);
+ }
+ else
+ {
+ cached_no_such_gid = gid;
+ *gname = xstrdup ("");
+ return;
+ }
+ }
+ *gname = xstrdup (cached_gname);
+}
+
+/* Given UNAME, set the corresponding UID and return 1, or else, return 0. */
+int
+uname_to_uid (char const *uname, uid_t *uidp)
+{
+ struct passwd *passwd;
+
+ if (cached_no_such_uname
+ && strcmp (uname, cached_no_such_uname) == 0)
+ return 0;
+
+ if (!cached_uname
+ || uname[0] != cached_uname[0]
+ || strcmp (uname, cached_uname) != 0)
+ {
+ passwd = getpwnam (uname);
+ if (passwd)
+ {
+ cached_uid = passwd->pw_uid;
+ assign_string (&cached_uname, passwd->pw_name);
+ }
+ else
+ {
+ assign_string (&cached_no_such_uname, uname);
+ return 0;
+ }
+ }
+ *uidp = cached_uid;
+ return 1;
+}
+
+/* Given GNAME, set the corresponding GID and return 1, or else, return 0. */
+int
+gname_to_gid (char const *gname, gid_t *gidp)
+{
+ struct group *group;
+
+ if (cached_no_such_gname
+ && strcmp (gname, cached_no_such_gname) == 0)
+ return 0;
+
+ if (!cached_gname
+ || gname[0] != cached_gname[0]
+ || strcmp (gname, cached_gname) != 0)
+ {
+ group = getgrnam (gname);
+ if (group)
+ {
+ cached_gid = group->gr_gid;
+ assign_string (&cached_gname, gname);
+ }
+ else
+ {
+ assign_string (&cached_no_such_gname, gname);
+ return 0;
+ }
+ }
+ *gidp = cached_gid;
+ return 1;
+}
+
+
+static struct name *
+make_name (const char *file_name)
+{
+ struct name *p = xzalloc (sizeof (*p));
+ if (!file_name)
+ file_name = "";
+ p->name = xstrdup (file_name);
+ p->length = strlen (p->name);
+ return p;
+}
+
+static void
+free_name (struct name *p)
+{
+ if (p)
+ {
+ free (p->name);
+ free (p->caname);
+ free (p);
+ }
+}
+
+
+/* Names from the command call. */
+
+static struct name *namelist; /* first name in list, if any */
+static struct name *nametail; /* end of name list */
+
+/* File name arguments are processed in two stages: first a
+ name element list (see below) is filled, then the names from it
+ are moved into the namelist.
+
+ This awkward process is needed only to implement --same-order option,
+ which is meant to help process large archives on machines with
+ limited memory. With this option on, namelist contains at most one
+ entry, which diminishes the memory consumption.
+
+ However, I very much doubt if we still need this -- Sergey */
+
+/* A name_list element contains entries of three types: */
+
+enum nelt_type
+ {
+ NELT_NAME, /* File name */
+ NELT_CHDIR, /* Change directory request */
+ NELT_FILE, /* Read file names from that file */
+ NELT_NOOP, /* No operation */
+ NELT_OPTION /* Filename-selection option */
+ };
+
+struct name_elt /* A name_array element. */
+{
+ struct name_elt *next, *prev;
+ enum nelt_type type; /* Element type, see NELT_* constants above */
+ union
+ {
+ const char *name; /* File or directory name */
+ struct /* File, if type == NELT_FILE */
+ {
+ const char *name;/* File name */
+ size_t line; /* Input line number */
+ int term; /* File name terminator in the list */
+ bool verbatim; /* Verbatim handling of file names: no white-space
+ trimming, no option processing */
+ FILE *fp;
+ } file;
+ struct
+ {
+ int option;
+ char const *arg;
+ } opt; /* NELT_OPTION */
+ } v;
+};
+
+static struct name_elt *name_head; /* store a list of names */
+size_t name_count; /* how many of the entries are names? */
+
+static struct name_elt *
+name_elt_alloc (void)
+{
+ struct name_elt *elt;
+
+ elt = xmalloc (sizeof (*elt));
+ if (!name_head)
+ {
+ name_head = elt;
+ name_head->prev = name_head->next = NULL;
+ name_head->type = NELT_NOOP;
+ elt = xmalloc (sizeof (*elt));
+ }
+
+ elt->prev = name_head->prev;
+ if (name_head->prev)
+ name_head->prev->next = elt;
+ elt->next = name_head;
+ name_head->prev = elt;
+ return elt;
+}
+
+static void
+name_list_adjust (void)
+{
+ if (name_head)
+ while (name_head->prev)
+ name_head = name_head->prev;
+}
+
+static void
+name_list_advance (void)
+{
+ struct name_elt *elt = name_head;
+ name_head = elt->next;
+ if (name_head)
+ name_head->prev = NULL;
+ free (elt);
+}
+
+
+/* Add to name_array the file NAME with fnmatch options MATFLAGS */
+void
+name_add_name (const char *name)
+{
+ struct name_elt *ep = name_elt_alloc ();
+
+ ep->type = NELT_NAME;
+ ep->v.name = name;
+ name_count++;
+}
+
+static void
+name_add_option (int option, const char *arg)
+{
+ struct name_elt *elt = name_elt_alloc ();
+ elt->type = NELT_OPTION;
+ elt->v.opt.option = option;
+ elt->v.opt.arg = arg;
+}
+
+/* Add to name_array a chdir request for the directory NAME */
+static void
+name_add_dir (const char *name)
+{
+ struct name_elt *ep = name_elt_alloc ();
+ ep->type = NELT_CHDIR;
+ ep->v.name = name;
+}
+
+static void
+name_add_file (const char *name)
+{
+ struct name_elt *ep = name_elt_alloc ();
+
+ ep->type = NELT_FILE;
+ ep->v.file.name = name;
+ ep->v.file.line = 0;
+ ep->v.file.fp = NULL;
+}
+
+/* Names from external name file. */
+
+static char *name_buffer; /* buffer to hold the current file name */
+static size_t name_buffer_length; /* allocated length of name_buffer */
+
+/* Set up to gather file names for tar. They can either come from a
+ file or were saved from decoding arguments. */
+void
+name_init (void)
+{
+ name_buffer = xmalloc (NAME_FIELD_SIZE + 2);
+ name_buffer_length = NAME_FIELD_SIZE;
+ name_list_adjust ();
+}
+
+void
+name_term (void)
+{
+ free (name_buffer);
+}
+
+/* Prevent recursive inclusion of the same file */
+struct file_id_list
+{
+ struct file_id_list *next;
+ ino_t ino;
+ dev_t dev;
+ const char *from_file;
+};
+
+static struct file_id_list *file_id_list;
+
+/* Return the name of the file from which the file names and options
+ are being read.
+*/
+static const char *
+file_list_name (void)
+{
+ struct name_elt *elt;
+
+ for (elt = name_head; elt; elt = elt->next)
+ if (elt->type == NELT_FILE && elt->v.file.fp)
+ return elt->v.file.name;
+ return _("command line");
+}
+
+static int
+add_file_id (const char *filename)
+{
+ struct file_id_list *p;
+ struct stat st;
+ const char *reading_from;
+
+ if (stat (filename, &st))
+ stat_fatal (filename);
+ reading_from = file_list_name ();
+ for (p = file_id_list; p; p = p->next)
+ if (p->ino == st.st_ino && p->dev == st.st_dev)
+ {
+ int oldc = set_char_quoting (NULL, ':', 1);
+ ERROR ((0, 0,
+ _("%s: file list requested from %s already read from %s"),
+ quotearg_n (0, filename),
+ reading_from, p->from_file));
+ set_char_quoting (NULL, ':', oldc);
+ return 1;
+ }
+ p = xmalloc (sizeof *p);
+ p->next = file_id_list;
+ p->ino = st.st_ino;
+ p->dev = st.st_dev;
+ p->from_file = reading_from;
+ file_id_list = p;
+ return 0;
+}
+
+/* Chop trailing slashes. */
+static void
+chopslash (char *str)
+{
+ char *p = str + strlen (str) - 1;
+ while (p > str && ISSLASH (*p))
+ *p-- = '\0';
+}
+
+enum read_file_list_state /* Result of reading file name from the list file */
+ {
+ file_list_success, /* OK, name read successfully */
+ file_list_end, /* End of list file */
+ file_list_zero, /* Zero separator encountered where it should not */
+ file_list_skip /* Empty (zero-length) entry encountered, skip it */
+ };
+
+/* Read from FP a sequence of characters up to TERM and put them
+ into STK.
+ */
+static enum read_file_list_state
+read_name_from_file (struct name_elt *ent)
+{
+ int c;
+ size_t counter = 0;
+ FILE *fp = ent->v.file.fp;
+ int term = ent->v.file.term;
+
+ ++ent->v.file.line;
+ for (c = getc (fp); c != EOF && c != term; c = getc (fp))
+ {
+ if (counter == name_buffer_length)
+ name_buffer = x2realloc (name_buffer, &name_buffer_length);
+ name_buffer[counter++] = c;
+ if (c == 0)
+ {
+ /* We have read a zero separator. The file possibly is
+ zero-separated */
+ return file_list_zero;
+ }
+ }
+
+ if (counter == 0 && c != EOF)
+ return file_list_skip;
+
+ if (counter == name_buffer_length)
+ name_buffer = x2realloc (name_buffer, &name_buffer_length);
+ name_buffer[counter] = 0;
+ chopslash (name_buffer);
+ return (counter == 0 && c == EOF) ? file_list_end : file_list_success;
+}
+
+static int
+handle_option (const char *str, struct name_elt const *ent)
+{
+ struct wordsplit ws;
+ int i;
+ struct option_locus loc;
+
+ while (*str && isspace (*str))
+ ++str;
+ if (*str != '-')
+ return 1;
+
+ ws.ws_offs = 1;
+ if (wordsplit (str, &ws, WRDSF_DEFFLAGS|WRDSF_DOOFFS))
+ FATAL_ERROR ((0, 0, _("cannot split string '%s': %s"),
+ str, wordsplit_strerror (&ws)));
+ ws.ws_wordv[0] = (char *) program_name;
+ loc.source = OPTS_FILE;
+ loc.name = ent->v.file.name;
+ loc.line = ent->v.file.line;
+ more_options (ws.ws_wordc+ws.ws_offs, ws.ws_wordv, &loc);
+ for (i = 0; i < ws.ws_wordc+ws.ws_offs; i++)
+ ws.ws_wordv[i] = NULL;
+
+ wordsplit_free (&ws);
+ return 0;
+}
+
+static int
+read_next_name (struct name_elt *ent, struct name_elt *ret)
+{
+ if (!ent->v.file.fp)
+ {
+ if (!strcmp (ent->v.file.name, "-"))
+ {
+ request_stdin ("-T");
+ ent->v.file.fp = stdin;
+ }
+ else
+ {
+ if (add_file_id (ent->v.file.name))
+ {
+ name_list_advance ();
+ return 1;
+ }
+ if ((ent->v.file.fp = fopen (ent->v.file.name, "r")) == NULL)
+ open_fatal (ent->v.file.name);
+ }
+ ent->v.file.term = filename_terminator;
+ ent->v.file.verbatim = verbatim_files_from_option;
+ }
+
+ while (1)
+ {
+ switch (read_name_from_file (ent))
+ {
+ case file_list_skip:
+ continue;
+
+ case file_list_zero:
+ WARNOPT (WARN_FILENAME_WITH_NULS,
+ (0, 0, N_("%s: file name read contains nul character"),
+ quotearg_colon (ent->v.file.name)));
+ ent->v.file.term = 0;
+ /* fall through */
+ case file_list_success:
+ if (unquote_option)
+ unquote_string (name_buffer);
+ if (!ent->v.file.verbatim && handle_option (name_buffer, ent) == 0)
+ {
+ name_list_adjust ();
+ return 1;
+ }
+ ret->type = NELT_NAME;
+ ret->v.name = name_buffer;
+ return 0;
+
+ case file_list_end:
+ if (strcmp (ent->v.file.name, "-"))
+ fclose (ent->v.file.fp);
+ ent->v.file.fp = NULL;
+ name_list_advance ();
+ return 1;
+ }
+ }
+}
+
+static void
+copy_name (struct name_elt *ep)
+{
+ const char *source;
+ size_t source_len;
+
+ source = ep->v.name;
+ source_len = strlen (source);
+ if (name_buffer_length < source_len)
+ {
+ do
+ {
+ name_buffer_length *= 2;
+ if (! name_buffer_length)
+ xalloc_die ();
+ }
+ while (name_buffer_length < source_len);
+
+ free (name_buffer);
+ name_buffer = xmalloc(name_buffer_length + 2);
+ }
+ strcpy (name_buffer, source);
+ chopslash (name_buffer);
+}
+
+
+/* Get the next NELT_NAME element from name_array. Result is in
+ static storage and can't be relied upon across two calls.
+
+ If CHANGE_DIRS is true, treat any entries of type NELT_CHDIR as
+ the request to change to the given directory.
+
+*/
+static struct name_elt *
+name_next_elt (int change_dirs)
+{
+ static struct name_elt entry;
+ struct name_elt *ep;
+
+ while ((ep = name_head) != NULL)
+ {
+ switch (ep->type)
+ {
+ case NELT_NOOP:
+ name_list_advance ();
+ break;
+
+ case NELT_FILE:
+ if (read_next_name (ep, &entry) == 0)
+ return &entry;
+ continue;
+
+ case NELT_CHDIR:
+ if (change_dirs)
+ {
+ chdir_do (chdir_arg (xstrdup (ep->v.name)));
+ name_list_advance ();
+ break;
+ }
+ /* fall through */
+ case NELT_NAME:
+ copy_name (ep);
+ if (unquote_option)
+ unquote_string (name_buffer);
+ entry.type = ep->type;
+ entry.v.name = name_buffer;
+ name_list_advance ();
+ return &entry;
+
+ case NELT_OPTION:
+ handle_file_selection_option (ep->v.opt.option, ep->v.opt.arg);
+ name_list_advance ();
+ continue;
+ }
+ }
+
+ return NULL;
+}
+
+const char *
+name_next (int change_dirs)
+{
+ struct name_elt *nelt = name_next_elt (change_dirs);
+ return nelt ? nelt->v.name : NULL;
+}
+
+/* Gather names in a list for scanning. Could hash them later if we
+ really care.
+
+ If the names are already sorted to match the archive, we just read
+ them one by one. name_gather reads the first one, and it is called
+ by name_match as appropriate to read the next ones. At EOF, the
+ last name read is just left in the buffer. This option lets users
+ of small machines extract an arbitrary number of files by doing
+ "tar t" and editing down the list of files. */
+
+void
+name_gather (void)
+{
+ /* Buffer able to hold a single name. */
+ static struct name *buffer = NULL;
+
+ struct name_elt *ep;
+
+ if (same_order_option)
+ {
+ static int change_dir;
+
+ while ((ep = name_next_elt (0)) && ep->type == NELT_CHDIR)
+ change_dir = chdir_arg (xstrdup (ep->v.name));
+
+ if (ep)
+ {
+ free_name (buffer);
+ buffer = make_name (ep->v.name);
+ buffer->change_dir = change_dir;
+ buffer->next = 0;
+ buffer->found_count = 0;
+ buffer->matching_flags = INCLUDE_OPTIONS;
+ buffer->directory = NULL;
+ buffer->parent = NULL;
+ buffer->cmdline = true;
+
+ namelist = nametail = buffer;
+ }
+ else if (change_dir)
+ addname (0, change_dir, false, NULL);
+ }
+ else
+ {
+ /* Non sorted names -- read them all in. */
+ int change_dir = 0;
+
+ for (;;)
+ {
+ int change_dir0 = change_dir;
+ while ((ep = name_next_elt (0)) && ep->type == NELT_CHDIR)
+ change_dir = chdir_arg (xstrdup (ep->v.name));
+
+ if (ep)
+ addname (ep->v.name, change_dir, true, NULL);
+ else
+ {
+ if (change_dir != change_dir0)
+ addname (NULL, change_dir, false, NULL);
+ break;
+ }
+ }
+ }
+}
+
+/* Add a name to the namelist. */
+struct name *
+addname (char const *string, int change_dir, bool cmdline, struct name *parent)
+{
+ struct name *name = make_name (string);
+
+ name->prev = nametail;
+ name->next = NULL;
+ name->found_count = 0;
+ name->matching_flags = INCLUDE_OPTIONS;
+ name->change_dir = change_dir;
+ name->directory = NULL;
+ name->parent = parent;
+ name->cmdline = cmdline;
+
+ if (nametail)
+ nametail->next = name;
+ else
+ namelist = name;
+ nametail = name;
+ return name;
+}
+
+/* Find a match for FILE_NAME (whose string length is LENGTH) in the name
+ list. */
+static struct name *
+namelist_match (char const *file_name, size_t length)
+{
+ struct name *p;
+
+ for (p = namelist; p; p = p->next)
+ {
+ if (p->name[0]
+ && exclude_fnmatch (p->name, file_name, p->matching_flags))
+ return p;
+ }
+
+ return NULL;
+}
+
+void
+remname (struct name *name)
+{
+ struct name *p;
+
+ if ((p = name->prev) != NULL)
+ p->next = name->next;
+ else
+ namelist = name->next;
+
+ if ((p = name->next) != NULL)
+ p->prev = name->prev;
+ else
+ nametail = name->prev;
+}
+
+/* Return true if and only if name FILE_NAME (from an archive) matches any
+ name from the namelist. */
+bool
+name_match (const char *file_name)
+{
+ size_t length = strlen (file_name);
+
+ while (1)
+ {
+ struct name *cursor = namelist;
+
+ if (!cursor)
+ return true;
+
+ if (cursor->name[0] == 0)
+ {
+ chdir_do (cursor->change_dir);
+ namelist = NULL;
+ nametail = NULL;
+ return true;
+ }
+
+ cursor = namelist_match (file_name, length);
+ if (cursor)
+ {
+ if (!(ISSLASH (file_name[cursor->length]) && recursion_option)
+ || cursor->found_count == 0)
+ cursor->found_count++; /* remember it matched */
+ if (starting_file_option)
+ {
+ free (namelist);
+ namelist = NULL;
+ nametail = NULL;
+ }
+ chdir_do (cursor->change_dir);
+
+ /* We got a match. */
+ return ISFOUND (cursor);
+ }
+
+ /* Filename from archive not found in namelist. If we have the whole
+ namelist here, just return 0. Otherwise, read the next name in and
+ compare it. If this was the last name, namelist->found_count will
+ remain on. If not, we loop to compare the newly read name. */
+
+ if (same_order_option && namelist->found_count)
+ {
+ name_gather (); /* read one more */
+ if (namelist->found_count)
+ return false;
+ }
+ else
+ return false;
+ }
+}
+
+/* Returns true if all names from the namelist were processed.
+ P is the stat_info of the most recently processed entry.
+ The decision is postponed until the next entry is read if:
+
+ 1) P ended with a slash (i.e. it was a directory)
+ 2) P matches any entry from the namelist *and* represents a subdirectory
+ or a file lying under this entry (in the terms of directory structure).
+
+ This is necessary to handle contents of directories. */
+bool
+all_names_found (struct tar_stat_info *p)
+{
+ struct name const *cursor;
+ size_t len;
+
+ if (!p->file_name || occurrence_option == 0 || p->had_trailing_slash)
+ return false;
+ len = strlen (p->file_name);
+ for (cursor = namelist; cursor; cursor = cursor->next)
+ {
+ if ((cursor->name[0] && !WASFOUND (cursor))
+ || (len >= cursor->length && ISSLASH (p->file_name[cursor->length])))
+ return false;
+ }
+ return true;
+}
+
+static int
+regex_usage_warning (const char *name)
+{
+ static int warned_once = 0;
+
+ /* Warn about implicit use of the wildcards in command line arguments.
+ (Default for tar prior to 1.15.91, but changed afterwards) */
+ if (wildcards == default_wildcards
+ && fnmatch_pattern_has_wildcards (name, 0))
+ {
+ warned_once = 1;
+ WARN ((0, 0,
+ _("Pattern matching characters used in file names")));
+ WARN ((0, 0,
+ _("Use --wildcards to enable pattern matching,"
+ " or --no-wildcards to suppress this warning")));
+ }
+ return warned_once;
+}
+
+/* Print the names of things in the namelist that were not matched. */
+void
+names_notfound (void)
+{
+ struct name const *cursor;
+
+ for (cursor = namelist; cursor; cursor = cursor->next)
+ if (!WASFOUND (cursor) && cursor->name[0])
+ {
+ regex_usage_warning (cursor->name);
+ ERROR ((0, 0,
+ (cursor->found_count == 0) ?
+ _("%s: Not found in archive") :
+ _("%s: Required occurrence not found in archive"),
+ quotearg_colon (cursor->name)));
+ }
+
+ /* Don't bother freeing the name list; we're about to exit. */
+ namelist = NULL;
+ nametail = NULL;
+
+ if (same_order_option)
+ {
+ const char *name;
+
+ while ((name = name_next (1)) != NULL)
+ {
+ regex_usage_warning (name);
+ ERROR ((0, 0, _("%s: Not found in archive"),
+ quotearg_colon (name)));
+ }
+ }
+}
+
+void
+label_notfound (void)
+{
+ struct name const *cursor;
+
+ if (!namelist)
+ return;
+
+ for (cursor = namelist; cursor; cursor = cursor->next)
+ if (WASFOUND (cursor))
+ return;
+
+ if (verbose_option)
+ error (0, 0, _("Archive label mismatch"));
+ set_exit_status (TAREXIT_DIFFERS);
+
+ for (cursor = namelist; cursor; cursor = cursor->next)
+ {
+ if (regex_usage_warning (cursor->name))
+ break;
+ }
+
+ /* Don't bother freeing the name list; we're about to exit. */
+ namelist = NULL;
+ nametail = NULL;
+
+ if (same_order_option)
+ {
+ const char *name;
+
+ while ((name = name_next (1)) != NULL
+ && regex_usage_warning (name) == 0)
+ ;
+ }
+}
+
+/* Sorting name lists. */
+
+/* Sort *singly* linked LIST of names, of given LENGTH, using COMPARE
+ to order names. Return the sorted list. Note that after calling
+ this function, the 'prev' links in list elements are messed up.
+
+ Apart from the type 'struct name' and the definition of SUCCESSOR,
+ this is a generic list-sorting function, but it's too painful to
+ make it both generic and portable
+ in C. */
+
+static struct name *
+merge_sort_sll (struct name *list, int length,
+ int (*compare) (struct name const*, struct name const*))
+{
+ struct name *first_list;
+ struct name *second_list;
+ int first_length;
+ int second_length;
+ struct name *result;
+ struct name **merge_point;
+ struct name *cursor;
+ int counter;
+
+# define SUCCESSOR(name) ((name)->next)
+
+ if (length == 1)
+ return list;
+
+ if (length == 2)
+ {
+ if ((*compare) (list, SUCCESSOR (list)) > 0)
+ {
+ result = SUCCESSOR (list);
+ SUCCESSOR (result) = list;
+ SUCCESSOR (list) = 0;
+ return result;
+ }
+ return list;
+ }
+
+ first_list = list;
+ first_length = (length + 1) / 2;
+ second_length = length / 2;
+ for (cursor = list, counter = first_length - 1;
+ counter;
+ cursor = SUCCESSOR (cursor), counter--)
+ continue;
+ second_list = SUCCESSOR (cursor);
+ SUCCESSOR (cursor) = 0;
+
+ first_list = merge_sort_sll (first_list, first_length, compare);
+ second_list = merge_sort_sll (second_list, second_length, compare);
+
+ merge_point = &result;
+ while (first_list && second_list)
+ if ((*compare) (first_list, second_list) < 0)
+ {
+ cursor = SUCCESSOR (first_list);
+ *merge_point = first_list;
+ merge_point = &SUCCESSOR (first_list);
+ first_list = cursor;
+ }
+ else
+ {
+ cursor = SUCCESSOR (second_list);
+ *merge_point = second_list;
+ merge_point = &SUCCESSOR (second_list);
+ second_list = cursor;
+ }
+ if (first_list)
+ *merge_point = first_list;
+ else
+ *merge_point = second_list;
+
+ return result;
+
+#undef SUCCESSOR
+}
+
+/* Sort doubly linked LIST of names, of given LENGTH, using COMPARE
+ to order names. Return the sorted list. */
+static struct name *
+merge_sort (struct name *list, int length,
+ int (*compare) (struct name const*, struct name const*))
+{
+ struct name *head, *p, *prev;
+ head = merge_sort_sll (list, length, compare);
+ /* Fixup prev pointers */
+ for (prev = NULL, p = head; p; prev = p, p = p->next)
+ p->prev = prev;
+ return head;
+}
+
+/* A comparison function for sorting names. Put found names last;
+ break ties by string comparison. */
+
+static int
+compare_names_found (struct name const *n1, struct name const *n2)
+{
+ int found_diff = WASFOUND (n2) - WASFOUND (n1);
+ return found_diff ? found_diff : strcmp (n1->name, n2->name);
+}
+
+/* Simple comparison by names. */
+static int
+compare_names (struct name const *n1, struct name const *n2)
+{
+ return strcmp (n1->name, n2->name);
+}
+
+
+/* Add all the dirs under ST to the namelist NAME, descending the
+ directory hierarchy recursively. */
+
+static void
+add_hierarchy_to_namelist (struct tar_stat_info *st, struct name *name)
+{
+ const char *buffer;
+
+ name->directory = scan_directory (st);
+ buffer = directory_contents (name->directory);
+ if (buffer)
+ {
+ struct name *child_head = NULL, *child_tail = NULL;
+ size_t name_length = name->length;
+ size_t allocated_length = (name_length >= NAME_FIELD_SIZE
+ ? name_length + NAME_FIELD_SIZE
+ : NAME_FIELD_SIZE);
+ char *namebuf = xmalloc (allocated_length + 1);
+ /* FIXME: + 2 above? */
+ const char *string;
+ size_t string_length;
+ int change_dir = name->change_dir;
+
+ strcpy (namebuf, name->name);
+ if (! ISSLASH (namebuf[name_length - 1]))
+ {
+ namebuf[name_length++] = '/';
+ namebuf[name_length] = '\0';
+ }
+
+ for (string = buffer; *string; string += string_length + 1)
+ {
+ string_length = strlen (string);
+ if (*string == 'D')
+ {
+ struct name *np;
+ struct tar_stat_info subdir;
+ int subfd;
+
+ if (allocated_length <= name_length + string_length)
+ {
+ do
+ {
+ allocated_length *= 2;
+ if (! allocated_length)
+ xalloc_die ();
+ }
+ while (allocated_length <= name_length + string_length);
+
+ namebuf = xrealloc (namebuf, allocated_length + 1);
+ }
+ strcpy (namebuf + name_length, string + 1);
+ np = addname (namebuf, change_dir, false, name);
+ if (!child_head)
+ child_head = np;
+ else
+ child_tail->sibling = np;
+ child_tail = np;
+
+ tar_stat_init (&subdir);
+ subdir.parent = st;
+ if (st->fd < 0)
+ {
+ subfd = -1;
+ errno = - st->fd;
+ }
+ else
+ subfd = subfile_open (st, string + 1,
+ open_read_flags | O_DIRECTORY);
+ if (subfd < 0)
+ open_diag (namebuf);
+ else
+ {
+ subdir.fd = subfd;
+ if (fstat (subfd, &subdir.stat) != 0)
+ stat_diag (namebuf);
+ else if (! (O_DIRECTORY || S_ISDIR (subdir.stat.st_mode)))
+ {
+ errno = ENOTDIR;
+ open_diag (namebuf);
+ }
+ else
+ {
+ subdir.orig_file_name = xstrdup (namebuf);
+ add_hierarchy_to_namelist (&subdir, np);
+ restore_parent_fd (&subdir);
+ }
+ }
+
+ tar_stat_destroy (&subdir);
+ }
+ }
+
+ free (namebuf);
+ name->child = child_head;
+ }
+}
+
+/* Auxiliary functions for hashed table of struct name's. */
+
+static size_t
+name_hash (void const *entry, size_t n_buckets)
+{
+ struct name const *name = entry;
+ return hash_string (name->caname, n_buckets);
+}
+
+/* Compare two directories for equality of their names. */
+static bool
+name_compare (void const *entry1, void const *entry2)
+{
+ struct name const *name1 = entry1;
+ struct name const *name2 = entry2;
+ return strcmp (name1->caname, name2->caname) == 0;
+}
+
+
+/* Rebase 'name' member of CHILD and all its siblings to
+ the new PARENT. */
+static void
+rebase_child_list (struct name *child, struct name *parent)
+{
+ size_t old_prefix_len = child->parent->length;
+ size_t new_prefix_len = parent->length;
+ char *new_prefix = parent->name;
+
+ for (; child; child = child->sibling)
+ {
+ size_t size = child->length - old_prefix_len + new_prefix_len;
+ char *newp = xmalloc (size + 1);
+ strcpy (newp, new_prefix);
+ strcat (newp, child->name + old_prefix_len);
+ free (child->name);
+ child->name = newp;
+ child->length = size;
+
+ rebase_directory (child->directory,
+ child->parent->name, old_prefix_len,
+ new_prefix, new_prefix_len);
+ }
+}
+
+/* Collect all the names from argv[] (or whatever), expand them into a
+ directory tree, and sort them. This gets only subdirectories, not
+ all files. */
+
+void
+collect_and_sort_names (void)
+{
+ struct name *name;
+ struct name *next_name, *prev_name = NULL;
+ int num_names;
+ Hash_table *nametab;
+
+ name_gather ();
+
+ if (!namelist)
+ addname (".", 0, false, NULL);
+
+ if (listed_incremental_option)
+ {
+ switch (chdir_count ())
+ {
+ case 0:
+ break;
+
+ case 1:
+ if (namelist->change_dir == 0)
+ USAGE_ERROR ((0, 0,
+ _("Using -C option inside file list is not "
+ "allowed with --listed-incremental")));
+ break;
+
+ default:
+ USAGE_ERROR ((0, 0,
+ _("Only one -C option is allowed with "
+ "--listed-incremental")));
+ }
+
+ read_directory_file ();
+ }
+
+ num_names = 0;
+ for (name = namelist; name; name = name->next, num_names++)
+ {
+ struct tar_stat_info st;
+
+ if (name->found_count || name->directory)
+ continue;
+ if (name->matching_flags & EXCLUDE_WILDCARDS)
+ /* NOTE: EXCLUDE_ANCHORED is not relevant here */
+ /* FIXME: just skip regexps for now */
+ continue;
+ chdir_do (name->change_dir);
+
+ if (name->name[0] == 0)
+ continue;
+
+ tar_stat_init (&st);
+
+ if (deref_stat (name->name, &st.stat) != 0)
+ {
+ stat_diag (name->name);
+ continue;
+ }
+ if (S_ISDIR (st.stat.st_mode))
+ {
+ int dir_fd = openat (chdir_fd, name->name,
+ open_read_flags | O_DIRECTORY);
+ if (dir_fd < 0)
+ open_diag (name->name);
+ else
+ {
+ st.fd = dir_fd;
+ if (fstat (dir_fd, &st.stat) != 0)
+ stat_diag (name->name);
+ else if (O_DIRECTORY || S_ISDIR (st.stat.st_mode))
+ {
+ st.orig_file_name = xstrdup (name->name);
+ name->found_count++;
+ add_hierarchy_to_namelist (&st, name);
+ }
+ }
+ }
+
+ tar_stat_destroy (&st);
+ }
+
+ namelist = merge_sort (namelist, num_names, compare_names);
+
+ num_names = 0;
+ nametab = hash_initialize (0, 0, name_hash, name_compare, NULL);
+ for (name = namelist; name; name = next_name)
+ {
+ next_name = name->next;
+ name->caname = normalize_filename (name->change_dir, name->name);
+ if (prev_name)
+ {
+ struct name *p = hash_lookup (nametab, name);
+ if (p)
+ {
+ /* Keep the one listed in the command line */
+ if (!name->parent)
+ {
+ if (p->child)
+ rebase_child_list (p->child, name);
+ hash_delete (nametab, name);
+ /* FIXME: remove_directory (p->caname); ? */
+ remname (p);
+ free_name (p);
+ num_names--;
+ }
+ else
+ {
+ if (name->child)
+ rebase_child_list (name->child, p);
+ /* FIXME: remove_directory (name->caname); ? */
+ remname (name);
+ free_name (name);
+ continue;
+ }
+ }
+ }
+ name->found_count = 0;
+ if (!hash_insert (nametab, name))
+ xalloc_die ();
+ prev_name = name;
+ num_names++;
+ }
+ nametail = prev_name;
+ hash_free (nametab);
+
+ namelist = merge_sort (namelist, num_names, compare_names_found);
+
+ if (listed_incremental_option)
+ {
+ for (name = namelist; name && name->name[0] == 0; name++)
+ ;
+ if (name)
+ append_incremental_renames (name->directory);
+ }
+}
+
+/* This is like name_match, except that
+ 1. It returns a pointer to the name it matched, and doesn't set FOUND
+ in structure. The caller will have to do that if it wants to.
+ 2. If the namelist is empty, it returns null, unlike name_match, which
+ returns TRUE. */
+struct name *
+name_scan (const char *file_name)
+{
+ size_t length = strlen (file_name);
+
+ while (1)
+ {
+ struct name *cursor = namelist_match (file_name, length);
+ if (cursor)
+ return cursor;
+
+ /* Filename from archive not found in namelist. If we have the whole
+ namelist here, just return 0. Otherwise, read the next name in and
+ compare it. If this was the last name, namelist->found_count will
+ remain on. If not, we loop to compare the newly read name. */
+
+ if (same_order_option && namelist && namelist->found_count)
+ {
+ name_gather (); /* read one more */
+ if (namelist->found_count)
+ return 0;
+ }
+ else
+ return 0;
+ }
+}
+
+/* This returns a name from the namelist which doesn't have ->found
+ set. It sets ->found before returning, so successive calls will
+ find and return all the non-found names in the namelist. */
+struct name *gnu_list_name;
+
+struct name const *
+name_from_list (void)
+{
+ if (!gnu_list_name)
+ gnu_list_name = namelist;
+ while (gnu_list_name
+ && (gnu_list_name->found_count || gnu_list_name->name[0] == 0))
+ gnu_list_name = gnu_list_name->next;
+ if (gnu_list_name)
+ {
+ gnu_list_name->found_count++;
+ chdir_do (gnu_list_name->change_dir);
+ return gnu_list_name;
+ }
+ return NULL;
+}
+
+void
+blank_name_list (void)
+{
+ struct name *name;
+
+ gnu_list_name = 0;
+ for (name = namelist; name; name = name->next)
+ name->found_count = 0;
+}
+
+/* Yield a newly allocated file name consisting of DIR_NAME concatenated to
+ NAME, with an intervening slash if DIR_NAME does not already end in one. */
+char *
+make_file_name (const char *directory_name, const char *name)
+{
+ size_t dirlen = strlen (directory_name);
+ size_t namelen = strlen (name) + 1;
+ int slash = dirlen && ! ISSLASH (directory_name[dirlen - 1]);
+ char *buffer = xmalloc (dirlen + slash + namelen);
+ memcpy (buffer, directory_name, dirlen);
+ buffer[dirlen] = '/';
+ memcpy (buffer + dirlen + slash, name, namelen);
+ return buffer;
+}
+
+
+
+/* Return the size of the prefix of FILE_NAME that is removed after
+ stripping NUM leading file name components. NUM must be
+ positive. */
+
+size_t
+stripped_prefix_len (char const *file_name, size_t num)
+{
+ char const *p = file_name + FILE_SYSTEM_PREFIX_LEN (file_name);
+ while (ISSLASH (*p))
+ p++;
+ while (*p)
+ {
+ bool slash = ISSLASH (*p);
+ p++;
+ if (slash)
+ {
+ if (--num == 0)
+ return p - file_name;
+ while (ISSLASH (*p))
+ p++;
+ }
+ }
+ return -1;
+}
+
+/* Return nonzero if NAME contains ".." as a file name component. */
+bool
+contains_dot_dot (char const *name)
+{
+ char const *p = name + FILE_SYSTEM_PREFIX_LEN (name);
+
+ for (;; p++)
+ {
+ if (p[0] == '.' && p[1] == '.' && (ISSLASH (p[2]) || !p[2]))
+ return 1;
+
+ while (! ISSLASH (*p))
+ {
+ if (! *p++)
+ return 0;
+ }
+ }
+}
diff --git a/src/sparse.c b/src/sparse.c
new file mode 100644
index 0000000..4e78401
--- /dev/null
+++ b/src/sparse.c
@@ -0,0 +1,1295 @@
+/* Functions for dealing with sparse files
+
+ Copyright 2003-2007, 2010, 2013-2016 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 3, or (at your option) any later
+ version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <system.h>
+#include <inttostr.h>
+#include <quotearg.h>
+#include "common.h"
+
+struct tar_sparse_file;
+static bool sparse_select_optab (struct tar_sparse_file *file);
+
+enum sparse_scan_state
+ {
+ scan_begin,
+ scan_block,
+ scan_end
+ };
+
+struct tar_sparse_optab
+{
+ bool (*init) (struct tar_sparse_file *);
+ bool (*done) (struct tar_sparse_file *);
+ bool (*sparse_member_p) (struct tar_sparse_file *);
+ bool (*dump_header) (struct tar_sparse_file *);
+ bool (*fixup_header) (struct tar_sparse_file *);
+ bool (*decode_header) (struct tar_sparse_file *);
+ bool (*scan_block) (struct tar_sparse_file *, enum sparse_scan_state,
+ void *);
+ bool (*dump_region) (struct tar_sparse_file *, size_t);
+ bool (*extract_region) (struct tar_sparse_file *, size_t);
+};
+
+struct tar_sparse_file
+{
+ int fd; /* File descriptor */
+ bool seekable; /* Is fd seekable? */
+ off_t offset; /* Current offset in fd if seekable==false.
+ Otherwise unused */
+ 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; /* Operation table */
+ void *closure; /* Any additional data optab calls might
+ require */
+};
+
+/* Dump zeros to file->fd until offset is reached. It is used instead of
+ lseek if the output file is not seekable */
+static bool
+dump_zeros (struct tar_sparse_file *file, off_t offset)
+{
+ static char const zero_buf[BLOCKSIZE];
+
+ if (offset < file->offset)
+ {
+ errno = EINVAL;
+ return false;
+ }
+
+ while (file->offset < offset)
+ {
+ size_t size = (BLOCKSIZE < offset - file->offset
+ ? BLOCKSIZE
+ : offset - file->offset);
+ ssize_t wrbytes;
+
+ wrbytes = write (file->fd, zero_buf, size);
+ if (wrbytes <= 0)
+ {
+ if (wrbytes == 0)
+ errno = EINVAL;
+ return false;
+ }
+ file->offset += wrbytes;
+ }
+
+ return true;
+}
+
+static bool
+tar_sparse_member_p (struct tar_sparse_file *file)
+{
+ if (file->optab->sparse_member_p)
+ return file->optab->sparse_member_p (file);
+ return false;
+}
+
+static bool
+tar_sparse_init (struct tar_sparse_file *file)
+{
+ memset (file, 0, sizeof *file);
+
+ if (!sparse_select_optab (file))
+ return false;
+
+ if (file->optab->init)
+ return file->optab->init (file);
+
+ return true;
+}
+
+static bool
+tar_sparse_done (struct tar_sparse_file *file)
+{
+ if (file->optab->done)
+ return file->optab->done (file);
+ return true;
+}
+
+static bool
+tar_sparse_scan (struct tar_sparse_file *file, enum sparse_scan_state state,
+ void *block)
+{
+ if (file->optab->scan_block)
+ return file->optab->scan_block (file, state, block);
+ return true;
+}
+
+static bool
+tar_sparse_dump_region (struct tar_sparse_file *file, size_t i)
+{
+ if (file->optab->dump_region)
+ return file->optab->dump_region (file, i);
+ return false;
+}
+
+static bool
+tar_sparse_extract_region (struct tar_sparse_file *file, size_t i)
+{
+ if (file->optab->extract_region)
+ return file->optab->extract_region (file, i);
+ return false;
+}
+
+static bool
+tar_sparse_dump_header (struct tar_sparse_file *file)
+{
+ if (file->optab->dump_header)
+ return file->optab->dump_header (file);
+ return false;
+}
+
+static bool
+tar_sparse_decode_header (struct tar_sparse_file *file)
+{
+ if (file->optab->decode_header)
+ return file->optab->decode_header (file);
+ return true;
+}
+
+static bool
+tar_sparse_fixup_header (struct tar_sparse_file *file)
+{
+ if (file->optab->fixup_header)
+ return file->optab->fixup_header (file);
+ return true;
+}
+
+
+static bool
+lseek_or_error (struct tar_sparse_file *file, off_t offset)
+{
+ if (file->seekable
+ ? lseek (file->fd, offset, SEEK_SET) < 0
+ : ! dump_zeros (file, offset))
+ {
+ seek_diag_details (file->stat_info->orig_file_name, offset);
+ return false;
+ }
+ return true;
+}
+
+/* Takes a blockful of data and basically cruises through it to see if
+ it's made *entirely* of zeros, returning a 0 the instant it finds
+ something that is a nonzero, i.e., useful data. */
+static bool
+zero_block_p (char const *buffer, size_t size)
+{
+ while (size--)
+ if (*buffer++)
+ return false;
+ return true;
+}
+
+static void
+sparse_add_map (struct tar_stat_info *st, struct sp_array const *sp)
+{
+ struct sp_array *sparse_map = st->sparse_map;
+ size_t avail = st->sparse_map_avail;
+ if (avail == st->sparse_map_size)
+ st->sparse_map = sparse_map =
+ x2nrealloc (sparse_map, &st->sparse_map_size, sizeof *sparse_map);
+ sparse_map[avail] = *sp;
+ st->sparse_map_avail = avail + 1;
+}
+
+/* Scan the sparse file byte-by-byte and create its map. */
+static bool
+sparse_scan_file_raw (struct tar_sparse_file *file)
+{
+ struct tar_stat_info *st = file->stat_info;
+ int fd = file->fd;
+ char buffer[BLOCKSIZE];
+ size_t count = 0;
+ off_t offset = 0;
+ struct sp_array sp = {0, 0};
+
+ st->archive_file_size = 0;
+
+ if (!tar_sparse_scan (file, scan_begin, NULL))
+ return false;
+
+ while ((count = blocking_read (fd, buffer, sizeof buffer)) != 0
+ && count != SAFE_READ_ERROR)
+ {
+ /* Analyze the block. */
+ if (zero_block_p (buffer, count))
+ {
+ if (sp.numbytes)
+ {
+ sparse_add_map (st, &sp);
+ sp.numbytes = 0;
+ if (!tar_sparse_scan (file, scan_block, NULL))
+ return false;
+ }
+ }
+ else
+ {
+ if (sp.numbytes == 0)
+ sp.offset = offset;
+ sp.numbytes += count;
+ st->archive_file_size += count;
+ if (!tar_sparse_scan (file, scan_block, buffer))
+ return false;
+ }
+
+ offset += count;
+ }
+
+ /* save one more sparse segment of length 0 to indicate that
+ the file ends with a hole */
+ if (sp.numbytes == 0)
+ sp.offset = offset;
+
+ sparse_add_map (st, &sp);
+ st->archive_file_size += count;
+ return tar_sparse_scan (file, scan_end, NULL);
+}
+
+static bool
+sparse_scan_file_wholesparse (struct tar_sparse_file *file)
+{
+ struct tar_stat_info *st = file->stat_info;
+ struct sp_array sp = {0, 0};
+
+ /* Note that this function is called only for truly sparse files of size >= 1
+ block size (checked via ST_IS_SPARSE before). See the thread
+ http://www.mail-archive.com/bug-tar@gnu.org/msg04209.html for more info */
+ if (ST_NBLOCKS (st->stat) == 0)
+ {
+ st->archive_file_size = 0;
+ sp.offset = st->stat.st_size;
+ sparse_add_map (st, &sp);
+ return true;
+ }
+
+ return false;
+}
+
+#ifdef SEEK_HOLE
+/* Try to engage SEEK_HOLE/SEEK_DATA feature. */
+static bool
+sparse_scan_file_seek (struct tar_sparse_file *file)
+{
+ struct tar_stat_info *st = file->stat_info;
+ int fd = file->fd;
+ struct sp_array sp = {0, 0};
+ off_t offset = 0;
+ off_t data_offset;
+ off_t hole_offset;
+
+ st->archive_file_size = 0;
+
+ for (;;)
+ {
+ /* locate first chunk of data */
+ data_offset = lseek (fd, offset, SEEK_DATA);
+
+ if (data_offset == (off_t)-1)
+ /* ENXIO == EOF; error otherwise */
+ {
+ if (errno == ENXIO)
+ {
+ /* file ends with hole, add one more empty chunk of data */
+ sp.numbytes = 0;
+ sp.offset = st->stat.st_size;
+ sparse_add_map (st, &sp);
+ return true;
+ }
+ return false;
+ }
+
+ hole_offset = lseek (fd, data_offset, SEEK_HOLE);
+
+ /* according to specs, if FS does not fully support
+ SEEK_DATA/SEEK_HOLE it may just implement kind of "wrapper" around
+ classic lseek() call. We must detect it here and try to use other
+ hole-detection methods. */
+ if (offset == 0 /* first loop */
+ && data_offset == 0
+ && hole_offset == st->stat.st_size)
+ {
+ lseek (fd, 0, SEEK_SET);
+ return false;
+ }
+
+ sp.offset = data_offset;
+ sp.numbytes = hole_offset - data_offset;
+ sparse_add_map (st, &sp);
+
+ st->archive_file_size += sp.numbytes;
+ offset = hole_offset;
+ }
+
+ return true;
+}
+#endif
+
+static bool
+sparse_scan_file (struct tar_sparse_file *file)
+{
+ /* always check for completely sparse files */
+ if (sparse_scan_file_wholesparse (file))
+ return true;
+
+ switch (hole_detection)
+ {
+ case HOLE_DETECTION_DEFAULT:
+ case HOLE_DETECTION_SEEK:
+#ifdef SEEK_HOLE
+ if (sparse_scan_file_seek (file))
+ return true;
+#else
+ if (hole_detection == HOLE_DETECTION_SEEK)
+ WARN((0, 0,
+ _("\"seek\" hole detection is not supported, using \"raw\".")));
+ /* fall back to "raw" for this and all other files */
+ hole_detection = HOLE_DETECTION_RAW;
+#endif
+ case HOLE_DETECTION_RAW:
+ if (sparse_scan_file_raw (file))
+ return true;
+ }
+
+ return false;
+}
+
+static struct tar_sparse_optab const oldgnu_optab;
+static struct tar_sparse_optab const star_optab;
+static struct tar_sparse_optab const pax_optab;
+
+static bool
+sparse_select_optab (struct tar_sparse_file *file)
+{
+ switch (current_format == DEFAULT_FORMAT ? archive_format : current_format)
+ {
+ case V7_FORMAT:
+ case USTAR_FORMAT:
+ return false;
+
+ case OLDGNU_FORMAT:
+ case GNU_FORMAT: /*FIXME: This one should disappear? */
+ file->optab = &oldgnu_optab;
+ break;
+
+ case POSIX_FORMAT:
+ file->optab = &pax_optab;
+ break;
+
+ case STAR_FORMAT:
+ file->optab = &star_optab;
+ break;
+
+ default:
+ return false;
+ }
+ return true;
+}
+
+static bool
+sparse_dump_region (struct tar_sparse_file *file, size_t i)
+{
+ union block *blk;
+ off_t bytes_left = file->stat_info->sparse_map[i].numbytes;
+
+ if (!lseek_or_error (file, file->stat_info->sparse_map[i].offset))
+ return false;
+
+ while (bytes_left > 0)
+ {
+ size_t bufsize = (bytes_left > BLOCKSIZE) ? BLOCKSIZE : bytes_left;
+ size_t bytes_read;
+
+ blk = find_next_block ();
+ bytes_read = safe_read (file->fd, blk->buffer, bufsize);
+ if (bytes_read == SAFE_READ_ERROR)
+ {
+ read_diag_details (file->stat_info->orig_file_name,
+ (file->stat_info->sparse_map[i].offset
+ + file->stat_info->sparse_map[i].numbytes
+ - bytes_left),
+ bufsize);
+ return false;
+ }
+
+ memset (blk->buffer + bytes_read, 0, BLOCKSIZE - bytes_read);
+ bytes_left -= bytes_read;
+ file->dumped_size += bytes_read;
+ set_next_block_after (blk);
+ }
+
+ return true;
+}
+
+static bool
+sparse_extract_region (struct tar_sparse_file *file, size_t i)
+{
+ off_t write_size;
+
+ if (!lseek_or_error (file, file->stat_info->sparse_map[i].offset))
+ return false;
+
+ write_size = file->stat_info->sparse_map[i].numbytes;
+
+ if (write_size == 0)
+ {
+ /* Last block of the file is a hole */
+ if (file->seekable && sys_truncate (file->fd))
+ truncate_warn (file->stat_info->orig_file_name);
+ }
+ else while (write_size > 0)
+ {
+ size_t count;
+ size_t wrbytes = (write_size > BLOCKSIZE) ? BLOCKSIZE : write_size;
+ union block *blk = find_next_block ();
+ if (!blk)
+ {
+ ERROR ((0, 0, _("Unexpected EOF in archive")));
+ return false;
+ }
+ set_next_block_after (blk);
+ count = blocking_write (file->fd, blk->buffer, wrbytes);
+ write_size -= count;
+ file->dumped_size += count;
+ mv_size_left (file->stat_info->archive_file_size - file->dumped_size);
+ file->offset += count;
+ if (count != wrbytes)
+ {
+ write_error_details (file->stat_info->orig_file_name,
+ count, wrbytes);
+ return false;
+ }
+ }
+ return true;
+}
+
+
+
+/* Interface functions */
+enum dump_status
+sparse_dump_file (int fd, struct tar_stat_info *st)
+{
+ bool rc;
+ struct tar_sparse_file file;
+
+ if (!tar_sparse_init (&file))
+ return dump_status_not_implemented;
+
+ file.stat_info = st;
+ file.fd = fd;
+ file.seekable = true; /* File *must* be seekable for dump to work */
+
+ rc = sparse_scan_file (&file);
+ if (rc && file.optab->dump_region)
+ {
+ tar_sparse_dump_header (&file);
+
+ if (fd >= 0)
+ {
+ size_t i;
+
+ mv_begin_write (file.stat_info->file_name,
+ file.stat_info->stat.st_size,
+ file.stat_info->archive_file_size - file.dumped_size);
+ for (i = 0; rc && i < file.stat_info->sparse_map_avail; i++)
+ rc = tar_sparse_dump_region (&file, i);
+ }
+ }
+
+ pad_archive (file.stat_info->archive_file_size - file.dumped_size);
+ return (tar_sparse_done (&file) && rc) ? dump_status_ok : dump_status_short;
+}
+
+bool
+sparse_member_p (struct tar_stat_info *st)
+{
+ struct tar_sparse_file file;
+
+ if (!tar_sparse_init (&file))
+ return false;
+ file.stat_info = st;
+ return tar_sparse_member_p (&file);
+}
+
+bool
+sparse_fixup_header (struct tar_stat_info *st)
+{
+ struct tar_sparse_file file;
+
+ if (!tar_sparse_init (&file))
+ return false;
+ file.stat_info = st;
+ return tar_sparse_fixup_header (&file);
+}
+
+enum dump_status
+sparse_extract_file (int fd, struct tar_stat_info *st, off_t *size)
+{
+ bool rc = true;
+ struct tar_sparse_file file;
+ size_t i;
+
+ if (!tar_sparse_init (&file))
+ return dump_status_not_implemented;
+
+ file.stat_info = st;
+ file.fd = fd;
+ file.seekable = lseek (fd, 0, SEEK_SET) == 0;
+ file.offset = 0;
+
+ rc = tar_sparse_decode_header (&file);
+ for (i = 0; rc && i < file.stat_info->sparse_map_avail; i++)
+ rc = tar_sparse_extract_region (&file, i);
+ *size = file.stat_info->archive_file_size - file.dumped_size;
+ return (tar_sparse_done (&file) && rc) ? dump_status_ok : dump_status_short;
+}
+
+enum dump_status
+sparse_skip_file (struct tar_stat_info *st)
+{
+ bool rc = true;
+ struct tar_sparse_file file;
+
+ if (!tar_sparse_init (&file))
+ return dump_status_not_implemented;
+
+ file.stat_info = st;
+ file.fd = -1;
+
+ rc = tar_sparse_decode_header (&file);
+ skip_file (file.stat_info->archive_file_size - file.dumped_size);
+ return (tar_sparse_done (&file) && rc) ? dump_status_ok : dump_status_short;
+}
+
+
+static bool
+check_sparse_region (struct tar_sparse_file *file, off_t beg, off_t end)
+{
+ if (!lseek_or_error (file, beg))
+ return false;
+
+ while (beg < end)
+ {
+ size_t bytes_read;
+ size_t rdsize = BLOCKSIZE < end - beg ? BLOCKSIZE : end - beg;
+ char diff_buffer[BLOCKSIZE];
+
+ bytes_read = safe_read (file->fd, diff_buffer, rdsize);
+ if (bytes_read == SAFE_READ_ERROR)
+ {
+ read_diag_details (file->stat_info->orig_file_name,
+ beg,
+ rdsize);
+ return false;
+ }
+ if (!zero_block_p (diff_buffer, bytes_read))
+ {
+ char begbuf[INT_BUFSIZE_BOUND (off_t)];
+ report_difference (file->stat_info,
+ _("File fragment at %s is not a hole"),
+ offtostr (beg, begbuf));
+ return false;
+ }
+
+ beg += bytes_read;
+ }
+ return true;
+}
+
+static bool
+check_data_region (struct tar_sparse_file *file, size_t i)
+{
+ off_t size_left;
+
+ if (!lseek_or_error (file, file->stat_info->sparse_map[i].offset))
+ return false;
+ size_left = file->stat_info->sparse_map[i].numbytes;
+ mv_size_left (file->stat_info->archive_file_size - file->dumped_size);
+
+ while (size_left > 0)
+ {
+ size_t bytes_read;
+ size_t rdsize = (size_left > BLOCKSIZE) ? BLOCKSIZE : size_left;
+ char diff_buffer[BLOCKSIZE];
+
+ union block *blk = find_next_block ();
+ if (!blk)
+ {
+ ERROR ((0, 0, _("Unexpected EOF in archive")));
+ return false;
+ }
+ set_next_block_after (blk);
+ bytes_read = safe_read (file->fd, diff_buffer, rdsize);
+ if (bytes_read == SAFE_READ_ERROR)
+ {
+ read_diag_details (file->stat_info->orig_file_name,
+ (file->stat_info->sparse_map[i].offset
+ + file->stat_info->sparse_map[i].numbytes
+ - size_left),
+ rdsize);
+ return false;
+ }
+ file->dumped_size += bytes_read;
+ size_left -= bytes_read;
+ mv_size_left (file->stat_info->archive_file_size - file->dumped_size);
+ if (memcmp (blk->buffer, diff_buffer, rdsize))
+ {
+ report_difference (file->stat_info, _("Contents differ"));
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+sparse_diff_file (int fd, struct tar_stat_info *st)
+{
+ bool rc = true;
+ struct tar_sparse_file file;
+ size_t i;
+ off_t offset = 0;
+
+ if (!tar_sparse_init (&file))
+ return dump_status_not_implemented;
+
+ file.stat_info = st;
+ file.fd = fd;
+ file.seekable = true; /* File *must* be seekable for compare to work */
+
+ rc = tar_sparse_decode_header (&file);
+ mv_begin_read (st);
+ for (i = 0; rc && i < file.stat_info->sparse_map_avail; i++)
+ {
+ rc = check_sparse_region (&file,
+ offset, file.stat_info->sparse_map[i].offset)
+ && check_data_region (&file, i);
+ offset = file.stat_info->sparse_map[i].offset
+ + file.stat_info->sparse_map[i].numbytes;
+ }
+
+ if (!rc)
+ skip_file (file.stat_info->archive_file_size - file.dumped_size);
+ mv_end ();
+
+ tar_sparse_done (&file);
+ return rc;
+}
+
+
+/* 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... */
+
+enum oldgnu_add_status
+ {
+ add_ok,
+ add_finish,
+ add_fail
+ };
+
+static bool
+oldgnu_sparse_member_p (struct tar_sparse_file *file __attribute__ ((unused)))
+{
+ return current_header->header.typeflag == GNUTYPE_SPARSE;
+}
+
+/* Add a sparse item to the sparse file and its obstack */
+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 bool
+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 = 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 0 <= realsize;
+}
+
+/* Convert old GNU format sparse data to internal representation */
+static bool
+oldgnu_get_sparse_info (struct tar_sparse_file *file)
+{
+ size_t i;
+ union block *h = current_header;
+ int ext_p;
+ enum oldgnu_add_status rc;
+
+ file->stat_info->sparse_map_avail = 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;
+ rc == add_ok && ext_p; ext_p = h->sparse_header.isextended)
+ {
+ h = find_next_block ();
+ if (!h)
+ {
+ ERROR ((0, 0, _("Unexpected EOF in archive")));
+ return false;
+ }
+ 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]);
+ }
+
+ if (rc == add_fail)
+ {
+ ERROR ((0, 0, _("%s: invalid sparse archive member"),
+ file->stat_info->orig_file_name));
+ return false;
+ }
+ return true;
+}
+
+static void
+oldgnu_store_sparse_info (struct tar_sparse_file *file, size_t *pindex,
+ struct sparse *sp, size_t sparse_size)
+{
+ for (; *pindex < file->stat_info->sparse_map_avail
+ && sparse_size > 0; sparse_size--, sp++, ++*pindex)
+ {
+ OFF_TO_CHARS (file->stat_info->sparse_map[*pindex].offset,
+ sp->offset);
+ OFF_TO_CHARS (file->stat_info->sparse_map[*pindex].numbytes,
+ sp->numbytes);
+ }
+}
+
+static bool
+oldgnu_dump_header (struct tar_sparse_file *file)
+{
+ off_t block_ordinal = current_block_ordinal ();
+ union block *blk;
+ size_t i;
+
+ blk = start_header (file->stat_info);
+ blk->header.typeflag = GNUTYPE_SPARSE;
+ if (file->stat_info->sparse_map_avail > SPARSES_IN_OLDGNU_HEADER)
+ blk->oldgnu_header.isextended = 1;
+
+ /* Store the real file size */
+ OFF_TO_CHARS (file->stat_info->stat.st_size, blk->oldgnu_header.realsize);
+ /* Store the effective (shrunken) file size */
+ OFF_TO_CHARS (file->stat_info->archive_file_size, blk->header.size);
+
+ i = 0;
+ oldgnu_store_sparse_info (file, &i,
+ blk->oldgnu_header.sp,
+ SPARSES_IN_OLDGNU_HEADER);
+ blk->oldgnu_header.isextended = i < file->stat_info->sparse_map_avail;
+ finish_header (file->stat_info, blk, block_ordinal);
+
+ while (i < file->stat_info->sparse_map_avail)
+ {
+ blk = find_next_block ();
+ memset (blk->buffer, 0, BLOCKSIZE);
+ oldgnu_store_sparse_info (file, &i,
+ blk->sparse_header.sp,
+ SPARSES_IN_SPARSE_HEADER);
+ if (i < file->stat_info->sparse_map_avail)
+ blk->sparse_header.isextended = 1;
+ set_next_block_after (blk);
+ }
+ return true;
+}
+
+static struct tar_sparse_optab const oldgnu_optab = {
+ NULL, /* No init function */
+ NULL, /* No done function */
+ oldgnu_sparse_member_p,
+ oldgnu_dump_header,
+ oldgnu_fixup_header,
+ oldgnu_get_sparse_info,
+ NULL, /* No scan_block function */
+ sparse_dump_region,
+ sparse_extract_region,
+};
+
+
+/* Star */
+
+static bool
+star_sparse_member_p (struct tar_sparse_file *file __attribute__ ((unused)))
+{
+ return current_header->header.typeflag == GNUTYPE_SPARSE;
+}
+
+static bool
+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 = 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 0 <= realsize;
+}
+
+/* Convert STAR format sparse data to internal representation */
+static bool
+star_get_sparse_info (struct tar_sparse_file *file)
+{
+ size_t i;
+ union block *h = current_header;
+ int ext_p;
+ enum oldgnu_add_status rc = add_ok;
+
+ file->stat_info->sparse_map_avail = 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;
+ }
+ else
+ ext_p = 1;
+
+ for (; rc == add_ok && ext_p; ext_p = h->star_ext_header.isextended)
+ {
+ h = find_next_block ();
+ if (!h)
+ {
+ ERROR ((0, 0, _("Unexpected EOF in archive")));
+ return false;
+ }
+ 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;
+ }
+
+ if (rc == add_fail)
+ {
+ ERROR ((0, 0, _("%s: invalid sparse archive member"),
+ file->stat_info->orig_file_name));
+ return false;
+ }
+ return true;
+}
+
+
+static struct tar_sparse_optab const star_optab = {
+ NULL, /* No init function */
+ NULL, /* No done function */
+ star_sparse_member_p,
+ NULL,
+ star_fixup_header,
+ star_get_sparse_info,
+ NULL, /* No scan_block function */
+ NULL, /* No dump region function */
+ sparse_extract_region,
+};
+
+
+/* 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
+ explicitely 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 bool
+pax_sparse_member_p (struct tar_sparse_file *file)
+{
+ return file->stat_info->sparse_map_avail > 0
+ || file->stat_info->sparse_major > 0;
+}
+
+/* Start a header that uses the effective (shrunken) file size. */
+static union block *
+pax_start_header (struct tar_stat_info *st)
+{
+ off_t realsize = st->stat.st_size;
+ union block *blk;
+ st->stat.st_size = st->archive_file_size;
+ blk = start_header (st);
+ st->stat.st_size = realsize;
+ return blk;
+}
+
+static bool
+pax_dump_header_0 (struct tar_sparse_file *file)
+{
+ off_t block_ordinal = current_block_ordinal ();
+ union block *blk;
+ size_t i;
+ char nbuf[UINTMAX_STRSIZE_BOUND];
+ struct sp_array *map = file->stat_info->sparse_map;
+ char *save_file_name = NULL;
+
+ /* Store the real file size */
+ xheader_store ("GNU.sparse.size", file->stat_info, NULL);
+ xheader_store ("GNU.sparse.numblocks", file->stat_info, NULL);
+
+ if (xheader_keyword_deleted_p ("GNU.sparse.map")
+ || tar_sparse_minor == 0)
+ {
+ for (i = 0; i < file->stat_info->sparse_map_avail; i++)
+ {
+ xheader_store ("GNU.sparse.offset", file->stat_info, &i);
+ xheader_store ("GNU.sparse.numbytes", file->stat_info, &i);
+ }
+ }
+ else
+ {
+ xheader_store ("GNU.sparse.name", file->stat_info, NULL);
+ save_file_name = file->stat_info->file_name;
+ file->stat_info->file_name = xheader_format_name (file->stat_info,
+ "%d/GNUSparseFile.%p/%f", 0);
+
+ xheader_string_begin (&file->stat_info->xhdr);
+ for (i = 0; i < file->stat_info->sparse_map_avail; i++)
+ {
+ if (i)
+ xheader_string_add (&file->stat_info->xhdr, ",");
+ xheader_string_add (&file->stat_info->xhdr,
+ umaxtostr (map[i].offset, nbuf));
+ xheader_string_add (&file->stat_info->xhdr, ",");
+ xheader_string_add (&file->stat_info->xhdr,
+ umaxtostr (map[i].numbytes, nbuf));
+ }
+ if (!xheader_string_end (&file->stat_info->xhdr,
+ "GNU.sparse.map"))
+ {
+ free (file->stat_info->file_name);
+ file->stat_info->file_name = save_file_name;
+ return false;
+ }
+ }
+ blk = pax_start_header (file->stat_info);
+ finish_header (file->stat_info, blk, block_ordinal);
+ if (save_file_name)
+ {
+ free (file->stat_info->file_name);
+ file->stat_info->file_name = save_file_name;
+ }
+ return true;
+}
+
+static bool
+pax_dump_header_1 (struct tar_sparse_file *file)
+{
+ off_t block_ordinal = current_block_ordinal ();
+ union block *blk;
+ char *p, *q;
+ size_t i;
+ char nbuf[UINTMAX_STRSIZE_BOUND];
+ off_t size = 0;
+ struct sp_array *map = file->stat_info->sparse_map;
+ char *save_file_name = file->stat_info->file_name;
+
+#define COPY_STRING(b,dst,src) do \
+ { \
+ char *endp = b->buffer + BLOCKSIZE; \
+ char const *srcp = src; \
+ while (*srcp) \
+ { \
+ if (dst == endp) \
+ { \
+ set_next_block_after (b); \
+ b = find_next_block (); \
+ dst = b->buffer; \
+ endp = b->buffer + BLOCKSIZE; \
+ } \
+ *dst++ = *srcp++; \
+ } \
+ } while (0)
+
+ /* Compute stored file size */
+ p = umaxtostr (file->stat_info->sparse_map_avail, nbuf);
+ size += strlen (p) + 1;
+ for (i = 0; i < file->stat_info->sparse_map_avail; i++)
+ {
+ p = umaxtostr (map[i].offset, nbuf);
+ size += strlen (p) + 1;
+ p = umaxtostr (map[i].numbytes, nbuf);
+ size += strlen (p) + 1;
+ }
+ size = (size + BLOCKSIZE - 1) / BLOCKSIZE;
+ file->stat_info->archive_file_size += size * BLOCKSIZE;
+ file->dumped_size += size * BLOCKSIZE;
+
+ /* Store sparse file identification */
+ xheader_store ("GNU.sparse.major", file->stat_info, NULL);
+ xheader_store ("GNU.sparse.minor", file->stat_info, NULL);
+ xheader_store ("GNU.sparse.name", file->stat_info, NULL);
+ xheader_store ("GNU.sparse.realsize", file->stat_info, NULL);
+
+ file->stat_info->file_name =
+ xheader_format_name (file->stat_info, "%d/GNUSparseFile.%p/%f", 0);
+ /* Make sure the created header name is shorter than NAME_FIELD_SIZE: */
+ if (strlen (file->stat_info->file_name) > NAME_FIELD_SIZE)
+ file->stat_info->file_name[NAME_FIELD_SIZE] = 0;
+
+ blk = pax_start_header (file->stat_info);
+ finish_header (file->stat_info, blk, block_ordinal);
+ free (file->stat_info->file_name);
+ file->stat_info->file_name = save_file_name;
+
+ blk = find_next_block ();
+ q = blk->buffer;
+ p = umaxtostr (file->stat_info->sparse_map_avail, nbuf);
+ COPY_STRING (blk, q, p);
+ COPY_STRING (blk, q, "\n");
+ for (i = 0; i < file->stat_info->sparse_map_avail; i++)
+ {
+ p = umaxtostr (map[i].offset, nbuf);
+ COPY_STRING (blk, q, p);
+ COPY_STRING (blk, q, "\n");
+ p = umaxtostr (map[i].numbytes, nbuf);
+ COPY_STRING (blk, q, p);
+ COPY_STRING (blk, q, "\n");
+ }
+ memset (q, 0, BLOCKSIZE - (q - blk->buffer));
+ set_next_block_after (blk);
+ return true;
+}
+
+static bool
+pax_dump_header (struct tar_sparse_file *file)
+{
+ file->stat_info->sparse_major = tar_sparse_major;
+ file->stat_info->sparse_minor = tar_sparse_minor;
+
+ return (file->stat_info->sparse_major == 0) ?
+ pax_dump_header_0 (file) : pax_dump_header_1 (file);
+}
+
+static bool
+decode_num (uintmax_t *num, char const *arg, uintmax_t maxval)
+{
+ uintmax_t u;
+ char *arg_lim;
+
+ if (!ISDIGIT (*arg))
+ return false;
+
+ errno = 0;
+ u = strtoumax (arg, &arg_lim, 10);
+
+ if (! (u <= maxval && errno != ERANGE) || *arg_lim)
+ return false;
+
+ *num = u;
+ return true;
+}
+
+static bool
+pax_decode_header (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 i;
+
+#define COPY_BUF(b,buf,src) do \
+ { \
+ char *endp = b->buffer + BLOCKSIZE; \
+ char *dst = buf; \
+ do \
+ { \
+ if (dst == buf + UINTMAX_STRSIZE_BOUND -1) \
+ { \
+ ERROR ((0, 0, _("%s: numeric overflow in sparse archive member"), \
+ file->stat_info->orig_file_name)); \
+ return false; \
+ } \
+ if (src == endp) \
+ { \
+ set_next_block_after (b); \
+ file->dumped_size += BLOCKSIZE; \
+ b = find_next_block (); \
+ src = b->buffer; \
+ endp = b->buffer + BLOCKSIZE; \
+ } \
+ *dst = *src++; \
+ } \
+ while (*dst++ != '\n'); \
+ dst[-1] = 0; \
+ } while (0)
+
+ set_next_block_after (current_header);
+ file->dumped_size += BLOCKSIZE;
+ blk = find_next_block ();
+ p = blk->buffer;
+ COPY_BUF (blk,nbuf,p);
+ if (!decode_num (&u, nbuf, TYPE_MAXIMUM (size_t)))
+ {
+ ERROR ((0, 0, _("%s: malformed sparse archive member"),
+ file->stat_info->orig_file_name));
+ return false;
+ }
+ file->stat_info->sparse_map_size = u;
+ file->stat_info->sparse_map = xcalloc (file->stat_info->sparse_map_size,
+ sizeof (*file->stat_info->sparse_map));
+ file->stat_info->sparse_map_avail = 0;
+ for (i = 0; i < file->stat_info->sparse_map_size; i++)
+ {
+ struct sp_array sp;
+
+ COPY_BUF (blk,nbuf,p);
+ if (!decode_num (&u, nbuf, TYPE_MAXIMUM (off_t)))
+ {
+ ERROR ((0, 0, _("%s: malformed sparse archive member"),
+ file->stat_info->orig_file_name));
+ return false;
+ }
+ sp.offset = u;
+ COPY_BUF (blk,nbuf,p);
+ if (!decode_num (&u, nbuf, TYPE_MAXIMUM (off_t)))
+ {
+ ERROR ((0, 0, _("%s: malformed sparse archive member"),
+ file->stat_info->orig_file_name));
+ return false;
+ }
+ sp.numbytes = u;
+ sparse_add_map (file->stat_info, &sp);
+ }
+ set_next_block_after (blk);
+ }
+
+ return true;
+}
+
+static struct tar_sparse_optab const pax_optab = {
+ NULL, /* No init function */
+ NULL, /* No done function */
+ pax_sparse_member_p,
+ pax_dump_header,
+ NULL,
+ pax_decode_header,
+ NULL, /* No scan_block function */
+ sparse_dump_region,
+ sparse_extract_region,
+};
diff --git a/src/suffix.c b/src/suffix.c
new file mode 100644
index 0000000..a7695b9
--- /dev/null
+++ b/src/suffix.c
@@ -0,0 +1,114 @@
+/* This file is part of GNU tar.
+ Copyright 2007, 2009, 2013-2014, 2016 Free Software Foundation, Inc.
+
+ Written by Sergey Poznyakoff.
+
+ GNU tar 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, or (at your option) any later
+ version.
+
+ GNU tar 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 GNU tar. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <system.h>
+#include "common.h"
+
+struct compression_suffix
+{
+ const char *suffix;
+ size_t length;
+ const char *program;
+};
+
+static struct compression_suffix compression_suffixes[] = {
+#define __CAT2__(a,b) a ## b
+#define S(s,p) #s, sizeof (#s) - 1, __CAT2__(p,_PROGRAM)
+ { "tar", 3, NULL },
+ { S(gz, GZIP) },
+ { S(tgz, GZIP) },
+ { S(taz, GZIP) },
+ { S(Z, COMPRESS) },
+ { S(taZ, COMPRESS) },
+ { S(bz2, BZIP2) },
+ { S(tbz, BZIP2) },
+ { S(tbz2, BZIP2) },
+ { S(tz2, BZIP2) },
+ { S(lz, LZIP) },
+ { S(lzma, LZMA) },
+ { S(tlz, LZMA) },
+ { S(lzo, LZOP) },
+ { S(xz, XZ) },
+ { S(txz, XZ) }, /* Slackware */
+ { NULL }
+#undef S
+#undef __CAT2__
+};
+
+static struct compression_suffix const *
+find_compression_suffix (const char *name, size_t *ret_len)
+{
+ char *suf = strrchr (name, '.');
+
+ if (suf)
+ {
+ size_t len;
+ struct compression_suffix *p;
+
+ suf++;
+ len = strlen (suf);
+
+ for (p = compression_suffixes; p->suffix; p++)
+ {
+ if (p->length == len && memcmp (p->suffix, suf, len) == 0)
+ {
+ if (ret_len)
+ *ret_len = strlen (name) - len - 1;
+ return p;
+ }
+ }
+ }
+ return NULL;
+}
+
+static const char *
+find_compression_program (const char *name, const char *defprog)
+{
+ struct compression_suffix const *p = find_compression_suffix (name, NULL);
+ if (p)
+ return p->program;
+ return defprog;
+}
+
+void
+set_compression_program_by_suffix (const char *name, const char *defprog)
+{
+ const char *program = find_compression_program (name, defprog);
+ if (program)
+ use_compress_program_option = program;
+}
+
+char *
+strip_compression_suffix (const char *name)
+{
+ char *s = NULL;
+ size_t len;
+
+ if (find_compression_suffix (name, &len))
+ {
+ if (strncmp (name + len - 4, ".tar", 4) == 0)
+ len -= 4;
+ if (len == 0)
+ return NULL;
+ s = xmalloc (len + 1);
+ memcpy (s, name, len);
+ s[len] = 0;
+ }
+ return s;
+}
+
diff --git a/src/system.c b/src/system.c
new file mode 100644
index 0000000..71a812d
--- /dev/null
+++ b/src/system.c
@@ -0,0 +1,900 @@
+/* System-dependent calls for tar.
+
+ Copyright 2003-2008, 2010, 2013-2014, 2016 Free Software Foundation,
+ Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 3, or (at your option) any later
+ version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <system.h>
+
+#include "common.h"
+#include <priv-set.h>
+#include <rmt.h>
+#include <signal.h>
+#include <wordsplit.h>
+
+static _Noreturn void
+xexec (const char *cmd)
+{
+ char *argv[4];
+
+ argv[0] = (char *) "/bin/sh";
+ argv[1] = (char *) "-c";
+ argv[2] = (char *) cmd;
+ argv[3] = NULL;
+
+ execv ("/bin/sh", argv);
+ exec_fatal (cmd);
+}
+
+#if MSDOS
+
+bool
+sys_get_archive_stat (void)
+{
+ return 0;
+}
+
+bool
+sys_file_is_archive (struct tar_stat_info *p)
+{
+ return false;
+}
+
+void
+sys_save_archive_dev_ino (void)
+{
+}
+
+void
+sys_detect_dev_null_output (void)
+{
+ static char const dev_null[] = "nul";
+
+ dev_null_output = (strcmp (archive_name_array[0], dev_null) == 0
+ || (! _isrmt (archive)));
+}
+
+void
+sys_wait_for_child (pid_t child_pid, bool eof)
+{
+}
+
+void
+sys_spawn_shell (void)
+{
+ spawnl (P_WAIT, getenv ("COMSPEC"), "-", 0);
+}
+
+/* stat() in djgpp's C library gives a constant number of 42 as the
+ uid and gid of a file. So, comparing an FTP'ed archive just after
+ unpack would fail on MSDOS. */
+
+bool
+sys_compare_uid (struct stat *a, struct stat *b)
+{
+ return true;
+}
+
+bool
+sys_compare_gid (struct stat *a, struct stat *b)
+{
+ return true;
+}
+
+void
+sys_compare_links (struct stat *link_data, struct stat *stat_data)
+{
+ return true;
+}
+
+int
+sys_truncate (int fd)
+{
+ return write (fd, "", 0);
+}
+
+size_t
+sys_write_archive_buffer (void)
+{
+ return full_write (archive, record_start->buffer, record_size);
+}
+
+/* Set ARCHIVE for writing, then compressing an archive. */
+void
+sys_child_open_for_compress (void)
+{
+ FATAL_ERROR ((0, 0, _("Cannot use compressed or remote archives")));
+}
+
+/* Set ARCHIVE for uncompressing, then reading an archive. */
+void
+sys_child_open_for_uncompress (void)
+{
+ FATAL_ERROR ((0, 0, _("Cannot use compressed or remote archives")));
+}
+
+#else
+
+extern union block *record_start; /* FIXME */
+
+static struct stat archive_stat; /* stat block for archive file */
+
+bool
+sys_get_archive_stat (void)
+{
+ return fstat (archive, &archive_stat) == 0;
+}
+
+bool
+sys_file_is_archive (struct tar_stat_info *p)
+{
+ return (ar_dev && p->stat.st_dev == ar_dev && p->stat.st_ino == ar_ino);
+}
+
+/* Save archive file inode and device numbers */
+void
+sys_save_archive_dev_ino (void)
+{
+ if (!_isrmt (archive) && S_ISREG (archive_stat.st_mode))
+ {
+ ar_dev = archive_stat.st_dev;
+ ar_ino = archive_stat.st_ino;
+ }
+ else
+ ar_dev = 0;
+}
+
+/* Detect if outputting to "/dev/null". */
+void
+sys_detect_dev_null_output (void)
+{
+ static char const dev_null[] = "/dev/null";
+ struct stat dev_null_stat;
+
+ dev_null_output = (strcmp (archive_name_array[0], dev_null) == 0
+ || (! _isrmt (archive)
+ && S_ISCHR (archive_stat.st_mode)
+ && stat (dev_null, &dev_null_stat) == 0
+ && archive_stat.st_dev == dev_null_stat.st_dev
+ && archive_stat.st_ino == dev_null_stat.st_ino));
+}
+
+void
+sys_wait_for_child (pid_t child_pid, bool eof)
+{
+ if (child_pid)
+ {
+ int wait_status;
+
+ while (waitpid (child_pid, &wait_status, 0) == -1)
+ if (errno != EINTR)
+ {
+ waitpid_error (use_compress_program_option);
+ break;
+ }
+
+ if (WIFSIGNALED (wait_status))
+ {
+ int sig = WTERMSIG (wait_status);
+ if (!(!eof && sig == SIGPIPE))
+ FATAL_ERROR ((0, 0, _("Child died with signal %d"), sig));
+ }
+ else if (WEXITSTATUS (wait_status) != 0)
+ FATAL_ERROR ((0, 0, _("Child returned status %d"),
+ WEXITSTATUS (wait_status)));
+ }
+}
+
+void
+sys_spawn_shell (void)
+{
+ pid_t child;
+ const char *shell = getenv ("SHELL");
+ if (! shell)
+ shell = "/bin/sh";
+ child = xfork ();
+ if (child == 0)
+ {
+ priv_set_restore_linkdir ();
+ execlp (shell, "-sh", "-i", NULL);
+ exec_fatal (shell);
+ }
+ else
+ {
+ int wait_status;
+ while (waitpid (child, &wait_status, 0) == -1)
+ if (errno != EINTR)
+ {
+ waitpid_error (shell);
+ break;
+ }
+ }
+}
+
+bool
+sys_compare_uid (struct stat *a, struct stat *b)
+{
+ return a->st_uid == b->st_uid;
+}
+
+bool
+sys_compare_gid (struct stat *a, struct stat *b)
+{
+ return a->st_gid == b->st_gid;
+}
+
+bool
+sys_compare_links (struct stat *link_data, struct stat *stat_data)
+{
+ return stat_data->st_dev == link_data->st_dev
+ && stat_data->st_ino == link_data->st_ino;
+}
+
+int
+sys_truncate (int fd)
+{
+ off_t pos = lseek (fd, (off_t) 0, SEEK_CUR);
+ return pos < 0 ? -1 : ftruncate (fd, pos);
+}
+
+/* Return nonzero if NAME is the name of a regular file, or if the file
+ does not exist (so it would be created as a regular file). */
+static int
+is_regular_file (const char *name)
+{
+ struct stat stbuf;
+
+ if (stat (name, &stbuf) == 0)
+ return S_ISREG (stbuf.st_mode);
+ else
+ return errno == ENOENT;
+}
+
+size_t
+sys_write_archive_buffer (void)
+{
+ return rmtwrite (archive, record_start->buffer, record_size);
+}
+
+#define PREAD 0 /* read file descriptor from pipe() */
+#define PWRITE 1 /* write file descriptor from pipe() */
+
+/* Duplicate file descriptor FROM into becoming INTO.
+ INTO is closed first and has to be the next available slot. */
+static void
+xdup2 (int from, int into)
+{
+ if (from != into)
+ {
+ int status = close (into);
+
+ if (status != 0 && errno != EBADF)
+ {
+ int e = errno;
+ FATAL_ERROR ((0, e, _("Cannot close")));
+ }
+ status = dup (from);
+ if (status != into)
+ {
+ if (status < 0)
+ {
+ int e = errno;
+ FATAL_ERROR ((0, e, _("Cannot dup")));
+ }
+ abort ();
+ }
+ xclose (from);
+ }
+}
+
+static void wait_for_grandchild (pid_t pid) __attribute__ ((__noreturn__));
+
+/* Propagate any failure of the grandchild back to the parent. */
+static void
+wait_for_grandchild (pid_t pid)
+{
+ int wait_status;
+ int exit_code = 0;
+
+ while (waitpid (pid, &wait_status, 0) == -1)
+ if (errno != EINTR)
+ {
+ waitpid_error (use_compress_program_option);
+ break;
+ }
+
+ if (WIFSIGNALED (wait_status))
+ raise (WTERMSIG (wait_status));
+ else if (WEXITSTATUS (wait_status) != 0)
+ exit_code = WEXITSTATUS (wait_status);
+
+ exit (exit_code);
+}
+
+/* Set ARCHIVE for writing, then compressing an archive. */
+pid_t
+sys_child_open_for_compress (void)
+{
+ int parent_pipe[2];
+ int child_pipe[2];
+ pid_t grandchild_pid;
+ pid_t child_pid;
+
+ signal (SIGPIPE, SIG_IGN);
+ xpipe (parent_pipe);
+ child_pid = xfork ();
+
+ if (child_pid > 0)
+ {
+ /* The parent tar is still here! Just clean up. */
+
+ archive = parent_pipe[PWRITE];
+ xclose (parent_pipe[PREAD]);
+ return child_pid;
+ }
+
+ /* The new born child tar is here! */
+
+ set_program_name (_("tar (child)"));
+ signal (SIGPIPE, SIG_DFL);
+
+ xdup2 (parent_pipe[PREAD], STDIN_FILENO);
+ xclose (parent_pipe[PWRITE]);
+
+ /* Check if we need a grandchild tar. This happens only if either:
+ a) the file is to be accessed by rmt: compressor doesn't know how;
+ b) the file is not a plain file. */
+
+ if (!_remdev (archive_name_array[0])
+ && is_regular_file (archive_name_array[0]))
+ {
+ if (backup_option)
+ maybe_backup_file (archive_name_array[0], 1);
+
+ /* We don't need a grandchild tar. Open the archive and launch the
+ compressor. */
+ if (strcmp (archive_name_array[0], "-"))
+ {
+ archive = creat (archive_name_array[0], MODE_RW);
+ if (archive < 0)
+ {
+ int saved_errno = errno;
+
+ if (backup_option)
+ undo_last_backup ();
+ errno = saved_errno;
+ open_fatal (archive_name_array[0]);
+ }
+ xdup2 (archive, STDOUT_FILENO);
+ }
+ priv_set_restore_linkdir ();
+ xexec (use_compress_program_option);
+ }
+
+ /* We do need a grandchild tar. */
+
+ xpipe (child_pipe);
+ grandchild_pid = xfork ();
+
+ if (grandchild_pid == 0)
+ {
+ /* The newborn grandchild tar is here! Launch the compressor. */
+
+ set_program_name (_("tar (grandchild)"));
+
+ xdup2 (child_pipe[PWRITE], STDOUT_FILENO);
+ xclose (child_pipe[PREAD]);
+ priv_set_restore_linkdir ();
+ xexec (use_compress_program_option);
+ }
+
+ /* The child tar is still here! */
+
+ /* Prepare for reblocking the data from the compressor into the archive. */
+
+ xdup2 (child_pipe[PREAD], STDIN_FILENO);
+ xclose (child_pipe[PWRITE]);
+
+ if (strcmp (archive_name_array[0], "-") == 0)
+ archive = STDOUT_FILENO;
+ else
+ {
+ archive = rmtcreat (archive_name_array[0], MODE_RW, rsh_command_option);
+ if (archive < 0)
+ open_fatal (archive_name_array[0]);
+ }
+
+ /* Let's read out of the stdin pipe and write an archive. */
+
+ while (1)
+ {
+ size_t status = 0;
+ char *cursor;
+ size_t length;
+
+ /* Assemble a record. */
+
+ for (length = 0, cursor = record_start->buffer;
+ length < record_size;
+ length += status, cursor += status)
+ {
+ size_t size = record_size - length;
+
+ status = safe_read (STDIN_FILENO, cursor, size);
+ if (status == SAFE_READ_ERROR)
+ read_fatal (use_compress_program_option);
+ if (status == 0)
+ break;
+ }
+
+ /* Copy the record. */
+
+ if (status == 0)
+ {
+ /* We hit the end of the file. Write last record at
+ full length, as the only role of the grandchild is
+ doing proper reblocking. */
+
+ if (length > 0)
+ {
+ memset (record_start->buffer + length, 0, record_size - length);
+ status = sys_write_archive_buffer ();
+ if (status != record_size)
+ archive_write_error (status);
+ }
+
+ /* There is nothing else to read, break out. */
+ break;
+ }
+
+ status = sys_write_archive_buffer ();
+ if (status != record_size)
+ archive_write_error (status);
+ }
+
+ wait_for_grandchild (grandchild_pid);
+}
+
+static void
+run_decompress_program (void)
+{
+ int i;
+ const char *p, *prog = NULL;
+ struct wordsplit ws;
+ int wsflags = (WRDSF_DEFFLAGS | WRDSF_ENV | WRDSF_DOOFFS) & ~WRDSF_NOVAR;
+
+ ws.ws_env = (const char **) environ;
+ ws.ws_offs = 1;
+
+ for (p = first_decompress_program (&i); p; p = next_decompress_program (&i))
+ {
+ if (prog)
+ {
+ WARNOPT (WARN_DECOMPRESS_PROGRAM,
+ (0, errno, _("cannot run %s"), prog));
+ WARNOPT (WARN_DECOMPRESS_PROGRAM,
+ (0, 0, _("trying %s"), p));
+ }
+ if (wordsplit (p, &ws, wsflags))
+ FATAL_ERROR ((0, 0, _("cannot split string '%s': %s"),
+ p, wordsplit_strerror (&ws)));
+ wsflags |= WRDSF_REUSE;
+ memmove(ws.ws_wordv, ws.ws_wordv + ws.ws_offs,
+ sizeof(ws.ws_wordv[0])*ws.ws_wordc);
+ ws.ws_wordv[ws.ws_wordc] = (char *) "-d";
+ prog = p;
+ execvp (ws.ws_wordv[0], ws.ws_wordv);
+ ws.ws_wordv[ws.ws_wordc] = NULL;
+ }
+ if (!prog)
+ FATAL_ERROR ((0, 0, _("unable to run decompression program")));
+ exec_fatal (prog);
+}
+
+/* Set ARCHIVE for uncompressing, then reading an archive. */
+pid_t
+sys_child_open_for_uncompress (void)
+{
+ int parent_pipe[2];
+ int child_pipe[2];
+ pid_t grandchild_pid;
+ pid_t child_pid;
+
+ xpipe (parent_pipe);
+ child_pid = xfork ();
+
+ if (child_pid > 0)
+ {
+ /* The parent tar is still here! Just clean up. */
+
+ archive = parent_pipe[PREAD];
+ xclose (parent_pipe[PWRITE]);
+ return child_pid;
+ }
+
+ /* The newborn child tar is here! */
+
+ set_program_name (_("tar (child)"));
+ signal (SIGPIPE, SIG_DFL);
+
+ xdup2 (parent_pipe[PWRITE], STDOUT_FILENO);
+ xclose (parent_pipe[PREAD]);
+
+ /* Check if we need a grandchild tar. This happens only if either:
+ a) we're reading stdin: to force unblocking;
+ b) the file is to be accessed by rmt: compressor doesn't know how;
+ c) the file is not a plain file. */
+
+ if (strcmp (archive_name_array[0], "-") != 0
+ && !_remdev (archive_name_array[0])
+ && is_regular_file (archive_name_array[0]))
+ {
+ /* We don't need a grandchild tar. Open the archive and lauch the
+ uncompressor. */
+
+ archive = open (archive_name_array[0], O_RDONLY | O_BINARY, MODE_RW);
+ if (archive < 0)
+ open_fatal (archive_name_array[0]);
+ xdup2 (archive, STDIN_FILENO);
+ priv_set_restore_linkdir ();
+ run_decompress_program ();
+ }
+
+ /* We do need a grandchild tar. */
+
+ xpipe (child_pipe);
+ grandchild_pid = xfork ();
+
+ if (grandchild_pid == 0)
+ {
+ /* The newborn grandchild tar is here! Launch the uncompressor. */
+
+ set_program_name (_("tar (grandchild)"));
+
+ xdup2 (child_pipe[PREAD], STDIN_FILENO);
+ xclose (child_pipe[PWRITE]);
+ priv_set_restore_linkdir ();
+ run_decompress_program ();
+ }
+
+ /* The child tar is still here! */
+
+ /* Prepare for unblocking the data from the archive into the
+ uncompressor. */
+
+ xdup2 (child_pipe[PWRITE], STDOUT_FILENO);
+ xclose (child_pipe[PREAD]);
+
+ if (strcmp (archive_name_array[0], "-") == 0)
+ archive = STDIN_FILENO;
+ else
+ archive = rmtopen (archive_name_array[0], O_RDONLY | O_BINARY,
+ MODE_RW, rsh_command_option);
+ if (archive < 0)
+ open_fatal (archive_name_array[0]);
+
+ /* Let's read the archive and pipe it into stdout. */
+
+ while (1)
+ {
+ char *cursor;
+ size_t maximum;
+ size_t count;
+ size_t status;
+
+ clear_read_error_count ();
+
+ error_loop:
+ status = rmtread (archive, record_start->buffer, record_size);
+ if (status == SAFE_READ_ERROR)
+ {
+ archive_read_error ();
+ goto error_loop;
+ }
+ if (status == 0)
+ break;
+ cursor = record_start->buffer;
+ maximum = status;
+ while (maximum)
+ {
+ count = maximum < BLOCKSIZE ? maximum : BLOCKSIZE;
+ if (full_write (STDOUT_FILENO, cursor, count) != count)
+ write_error (use_compress_program_option);
+ cursor += count;
+ maximum -= count;
+ }
+ }
+
+ xclose (STDOUT_FILENO);
+
+ wait_for_grandchild (grandchild_pid);
+}
+
+
+
+static void
+dec_to_env (char const *envar, uintmax_t num)
+{
+ char buf[UINTMAX_STRSIZE_BOUND];
+ char *numstr;
+
+ numstr = STRINGIFY_BIGINT (num, buf);
+ if (setenv (envar, numstr, 1) != 0)
+ xalloc_die ();
+}
+
+static void
+time_to_env (char const *envar, struct timespec t)
+{
+ char buf[TIMESPEC_STRSIZE_BOUND];
+ if (setenv (envar, code_timespec (t, buf), 1) != 0)
+ xalloc_die ();
+}
+
+static void
+oct_to_env (char const *envar, unsigned long num)
+{
+ char buf[1+1+(sizeof(unsigned long)*CHAR_BIT+2)/3];
+
+ snprintf (buf, sizeof buf, "0%lo", num);
+ if (setenv (envar, buf, 1) != 0)
+ xalloc_die ();
+}
+
+static void
+str_to_env (char const *envar, char const *str)
+{
+ if (str)
+ {
+ if (setenv (envar, str, 1) != 0)
+ xalloc_die ();
+ }
+ else
+ unsetenv (envar);
+}
+
+static void
+chr_to_env (char const *envar, char c)
+{
+ char buf[2];
+ buf[0] = c;
+ buf[1] = 0;
+ if (setenv (envar, buf, 1) != 0)
+ xalloc_die ();
+}
+
+static void
+stat_to_env (char *name, char type, struct tar_stat_info *st)
+{
+ str_to_env ("TAR_VERSION", PACKAGE_VERSION);
+ str_to_env ("TAR_ARCHIVE", *archive_name_cursor);
+ dec_to_env ("TAR_VOLUME", archive_name_cursor - archive_name_array + 1);
+ dec_to_env ("TAR_BLOCKING_FACTOR", blocking_factor);
+ str_to_env ("TAR_FORMAT",
+ archive_format_string (current_format == DEFAULT_FORMAT ?
+ archive_format : current_format));
+ chr_to_env ("TAR_FILETYPE", type);
+ oct_to_env ("TAR_MODE", st->stat.st_mode);
+ str_to_env ("TAR_FILENAME", name);
+ str_to_env ("TAR_REALNAME", st->file_name);
+ str_to_env ("TAR_UNAME", st->uname);
+ str_to_env ("TAR_GNAME", st->gname);
+ time_to_env ("TAR_ATIME", st->atime);
+ time_to_env ("TAR_MTIME", st->mtime);
+ time_to_env ("TAR_CTIME", st->ctime);
+ dec_to_env ("TAR_SIZE", st->stat.st_size);
+ dec_to_env ("TAR_UID", st->stat.st_uid);
+ dec_to_env ("TAR_GID", st->stat.st_gid);
+
+ switch (type)
+ {
+ case 'b':
+ case 'c':
+ dec_to_env ("TAR_MINOR", minor (st->stat.st_rdev));
+ dec_to_env ("TAR_MAJOR", major (st->stat.st_rdev));
+ unsetenv ("TAR_LINKNAME");
+ break;
+
+ case 'l':
+ case 'h':
+ unsetenv ("TAR_MINOR");
+ unsetenv ("TAR_MAJOR");
+ str_to_env ("TAR_LINKNAME", st->link_name);
+ break;
+
+ default:
+ unsetenv ("TAR_MINOR");
+ unsetenv ("TAR_MAJOR");
+ unsetenv ("TAR_LINKNAME");
+ break;
+ }
+}
+
+static pid_t global_pid;
+static void (*pipe_handler) (int sig);
+
+int
+sys_exec_command (char *file_name, int typechar, struct tar_stat_info *st)
+{
+ int p[2];
+
+ xpipe (p);
+ pipe_handler = signal (SIGPIPE, SIG_IGN);
+ global_pid = xfork ();
+
+ if (global_pid != 0)
+ {
+ xclose (p[PREAD]);
+ return p[PWRITE];
+ }
+
+ /* Child */
+ xdup2 (p[PREAD], STDIN_FILENO);
+ xclose (p[PWRITE]);
+
+ stat_to_env (file_name, typechar, st);
+
+ priv_set_restore_linkdir ();
+ xexec (to_command_option);
+}
+
+void
+sys_wait_command (void)
+{
+ int status;
+
+ if (global_pid < 0)
+ return;
+
+ signal (SIGPIPE, pipe_handler);
+ while (waitpid (global_pid, &status, 0) == -1)
+ if (errno != EINTR)
+ {
+ global_pid = -1;
+ waitpid_error (to_command_option);
+ return;
+ }
+
+ if (WIFEXITED (status))
+ {
+ if (!ignore_command_error_option && WEXITSTATUS (status))
+ ERROR ((0, 0, _("%lu: Child returned status %d"),
+ (unsigned long) global_pid, WEXITSTATUS (status)));
+ }
+ else if (WIFSIGNALED (status))
+ {
+ WARN ((0, 0, _("%lu: Child terminated on signal %d"),
+ (unsigned long) global_pid, WTERMSIG (status)));
+ }
+ else
+ ERROR ((0, 0, _("%lu: Child terminated on unknown reason"),
+ (unsigned long) global_pid));
+
+ global_pid = -1;
+}
+
+int
+sys_exec_info_script (const char **archive_name, int volume_number)
+{
+ pid_t pid;
+ char uintbuf[UINTMAX_STRSIZE_BOUND];
+ int p[2];
+ static void (*saved_handler) (int sig);
+
+ xpipe (p);
+ saved_handler = signal (SIGPIPE, SIG_IGN);
+
+ pid = xfork ();
+
+ if (pid != 0)
+ {
+ /* Master */
+
+ int rc;
+ int status;
+ char *buf = NULL;
+ size_t size = 0;
+ FILE *fp;
+
+ xclose (p[PWRITE]);
+ fp = fdopen (p[PREAD], "r");
+ rc = getline (&buf, &size, fp);
+ fclose (fp);
+
+ if (rc > 0 && buf[rc-1] == '\n')
+ buf[--rc] = 0;
+
+ while (waitpid (pid, &status, 0) == -1)
+ if (errno != EINTR)
+ {
+ signal (SIGPIPE, saved_handler);
+ waitpid_error (info_script_option);
+ return -1;
+ }
+
+ signal (SIGPIPE, saved_handler);
+
+ if (WIFEXITED (status))
+ {
+ if (WEXITSTATUS (status) == 0 && rc > 0)
+ *archive_name = buf;
+ else
+ free (buf);
+ return WEXITSTATUS (status);
+ }
+
+ free (buf);
+ return -1;
+ }
+
+ /* Child */
+ setenv ("TAR_VERSION", PACKAGE_VERSION, 1);
+ setenv ("TAR_ARCHIVE", *archive_name, 1);
+ setenv ("TAR_VOLUME", STRINGIFY_BIGINT (volume_number, uintbuf), 1);
+ setenv ("TAR_BLOCKING_FACTOR",
+ STRINGIFY_BIGINT (blocking_factor, uintbuf), 1);
+ setenv ("TAR_SUBCOMMAND", subcommand_string (subcommand_option), 1);
+ setenv ("TAR_FORMAT",
+ archive_format_string (current_format == DEFAULT_FORMAT ?
+ archive_format : current_format), 1);
+ setenv ("TAR_FD", STRINGIFY_BIGINT (p[PWRITE], uintbuf), 1);
+
+ xclose (p[PREAD]);
+
+ priv_set_restore_linkdir ();
+ xexec (info_script_option);
+}
+
+void
+sys_exec_checkpoint_script (const char *script_name,
+ const char *archive_name,
+ int checkpoint_number)
+{
+ pid_t pid;
+ char uintbuf[UINTMAX_STRSIZE_BOUND];
+
+ pid = xfork ();
+
+ if (pid != 0)
+ {
+ /* Master */
+
+ int status;
+
+ while (waitpid (pid, &status, 0) == -1)
+ if (errno != EINTR)
+ {
+ waitpid_error (script_name);
+ break;
+ }
+
+ return;
+ }
+
+ /* Child */
+ setenv ("TAR_VERSION", PACKAGE_VERSION, 1);
+ setenv ("TAR_ARCHIVE", archive_name, 1);
+ setenv ("TAR_CHECKPOINT", STRINGIFY_BIGINT (checkpoint_number, uintbuf), 1);
+ setenv ("TAR_BLOCKING_FACTOR",
+ STRINGIFY_BIGINT (blocking_factor, uintbuf), 1);
+ setenv ("TAR_SUBCOMMAND", subcommand_string (subcommand_option), 1);
+ setenv ("TAR_FORMAT",
+ archive_format_string (current_format == DEFAULT_FORMAT ?
+ archive_format : current_format), 1);
+ priv_set_restore_linkdir ();
+ xexec (script_name);
+}
+
+#endif /* not MSDOS */
diff --git a/src/tar.c b/src/tar.c
new file mode 100644
index 0000000..ba24c43
--- /dev/null
+++ b/src/tar.c
@@ -0,0 +1,2852 @@
+/* A tar (tape archiver) program.
+
+ Copyright 1988, 1992-1997, 1999-2001, 2003-2007, 2012-2016 Free
+ Software Foundation, Inc.
+
+ Written by John Gilmore, starting 1985-08-25.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 3, or (at your option) any later
+ version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <system.h>
+
+#include <fnmatch.h>
+#include <argp.h>
+#include <argp-namefrob.h>
+#include <argp-fmtstream.h>
+#include <argp-version-etc.h>
+
+#include <signal.h>
+#if ! defined SIGCHLD && defined SIGCLD
+# define SIGCHLD SIGCLD
+#endif
+
+/* The following causes "common.h" to produce definitions of all the global
+ variables, rather than just "extern" declarations of them. GNU tar does
+ depend on the system loader to preset all GLOBAL variables to neutral (or
+ zero) values; explicit initialization is usually not done. */
+#define GLOBAL
+#include "common.h"
+
+#include <argmatch.h>
+#include <closeout.h>
+#include <configmake.h>
+#include <exitfail.h>
+#include <parse-datetime.h>
+#include <rmt.h>
+#include <rmt-command.h>
+#include <wordsplit.h>
+#include <sysexits.h>
+#include <quotearg.h>
+#include <version-etc.h>
+#include <xstrtol.h>
+#include <stdopen.h>
+#include <priv-set.h>
+#include <savedir.h>
+
+/* Local declarations. */
+
+#ifndef DEFAULT_ARCHIVE_FORMAT
+# define DEFAULT_ARCHIVE_FORMAT GNU_FORMAT
+#endif
+
+#ifndef DEFAULT_ARCHIVE
+# define DEFAULT_ARCHIVE "tar.out"
+#endif
+
+#ifndef DEFAULT_BLOCKING
+# define DEFAULT_BLOCKING 20
+#endif
+
+/* Print a message if not all links are dumped */
+static int check_links_option;
+
+/* Number of allocated tape drive names. */
+static size_t allocated_archive_names;
+
+
+/* Miscellaneous. */
+
+/* Name of option using stdin. */
+static const char *stdin_used_by;
+
+/* Doesn't return if stdin already requested. */
+void
+request_stdin (const char *option)
+{
+ if (stdin_used_by)
+ USAGE_ERROR ((0, 0, _("Options '%s' and '%s' both want standard input"),
+ stdin_used_by, option));
+
+ stdin_used_by = option;
+}
+
+extern int rpmatch (char const *response);
+
+/* Returns true if and only if the user typed an affirmative response. */
+int
+confirm (const char *message_action, const char *message_name)
+{
+ static FILE *confirm_file;
+ static int confirm_file_EOF;
+ bool status = false;
+
+ if (!confirm_file)
+ {
+ if (archive == 0 || stdin_used_by)
+ {
+ confirm_file = fopen (TTY_NAME, "r");
+ if (! confirm_file)
+ open_fatal (TTY_NAME);
+ }
+ else
+ {
+ request_stdin ("-w");
+ confirm_file = stdin;
+ }
+ }
+
+ fprintf (stdlis, "%s %s?", message_action, quote (message_name));
+ fflush (stdlis);
+
+ if (!confirm_file_EOF)
+ {
+ char *response = NULL;
+ size_t response_size = 0;
+ if (getline (&response, &response_size, confirm_file) < 0)
+ confirm_file_EOF = 1;
+ else
+ status = rpmatch (response) > 0;
+ free (response);
+ }
+
+ if (confirm_file_EOF)
+ {
+ fputc ('\n', stdlis);
+ fflush (stdlis);
+ }
+
+ return status;
+}
+
+static struct fmttab {
+ char const *name;
+ enum archive_format fmt;
+} const fmttab[] = {
+ { "v7", V7_FORMAT },
+ { "oldgnu", OLDGNU_FORMAT },
+ { "ustar", USTAR_FORMAT },
+ { "posix", POSIX_FORMAT },
+#if 0 /* not fully supported yet */
+ { "star", STAR_FORMAT },
+#endif
+ { "gnu", GNU_FORMAT },
+ { "pax", POSIX_FORMAT }, /* An alias for posix */
+ { NULL, 0 }
+};
+
+static void
+set_archive_format (char const *name)
+{
+ struct fmttab const *p;
+
+ for (p = fmttab; strcmp (p->name, name) != 0; )
+ if (! (++p)->name)
+ USAGE_ERROR ((0, 0, _("%s: Invalid archive format"),
+ quotearg_colon (name)));
+
+ archive_format = p->fmt;
+}
+
+static void
+set_xattr_option (int value)
+{
+ if (value == 1)
+ set_archive_format ("posix");
+ xattrs_option = value;
+}
+
+const char *
+archive_format_string (enum archive_format fmt)
+{
+ struct fmttab const *p;
+
+ for (p = fmttab; p->name; p++)
+ if (p->fmt == fmt)
+ return p->name;
+ return "unknown?";
+}
+
+#define FORMAT_MASK(n) (1<<(n))
+
+static void
+assert_format(unsigned fmt_mask)
+{
+ if ((FORMAT_MASK (archive_format) & fmt_mask) == 0)
+ USAGE_ERROR ((0, 0,
+ _("GNU features wanted on incompatible archive format")));
+}
+
+const char *
+subcommand_string (enum subcommand c)
+{
+ switch (c)
+ {
+ case UNKNOWN_SUBCOMMAND:
+ return "unknown?";
+
+ case APPEND_SUBCOMMAND:
+ return "-r";
+
+ case CAT_SUBCOMMAND:
+ return "-A";
+
+ case CREATE_SUBCOMMAND:
+ return "-c";
+
+ case DELETE_SUBCOMMAND:
+ return "-D";
+
+ case DIFF_SUBCOMMAND:
+ return "-d";
+
+ case EXTRACT_SUBCOMMAND:
+ return "-x";
+
+ case LIST_SUBCOMMAND:
+ return "-t";
+
+ case UPDATE_SUBCOMMAND:
+ return "-u";
+
+ case TEST_LABEL_SUBCOMMAND:
+ return "--test-label";
+ }
+ abort ();
+}
+
+static void
+tar_list_quoting_styles (struct obstack *stk, char const *prefix)
+{
+ int i;
+ size_t prefixlen = strlen (prefix);
+
+ for (i = 0; quoting_style_args[i]; i++)
+ {
+ obstack_grow (stk, prefix, prefixlen);
+ obstack_grow (stk, quoting_style_args[i],
+ strlen (quoting_style_args[i]));
+ obstack_1grow (stk, '\n');
+ }
+}
+
+static void
+tar_set_quoting_style (char *arg)
+{
+ int i;
+
+ for (i = 0; quoting_style_args[i]; i++)
+ if (strcmp (arg, quoting_style_args[i]) == 0)
+ {
+ set_quoting_style (NULL, i);
+ return;
+ }
+ FATAL_ERROR ((0, 0,
+ _("Unknown quoting style '%s'. Try '%s --quoting-style=help' to get a list."), arg, program_name));
+}
+
+
+/* Options. */
+
+enum
+{
+ ACLS_OPTION = CHAR_MAX + 1,
+ ATIME_PRESERVE_OPTION,
+ BACKUP_OPTION,
+ CHECK_DEVICE_OPTION,
+ CHECKPOINT_OPTION,
+ CHECKPOINT_ACTION_OPTION,
+ CLAMP_MTIME_OPTION,
+ DELAY_DIRECTORY_RESTORE_OPTION,
+ HARD_DEREFERENCE_OPTION,
+ DELETE_OPTION,
+ FORCE_LOCAL_OPTION,
+ FULL_TIME_OPTION,
+ GROUP_OPTION,
+ GROUP_MAP_OPTION,
+ IGNORE_COMMAND_ERROR_OPTION,
+ IGNORE_FAILED_READ_OPTION,
+ INDEX_FILE_OPTION,
+ KEEP_DIRECTORY_SYMLINK_OPTION,
+ KEEP_NEWER_FILES_OPTION,
+ LEVEL_OPTION,
+ LZIP_OPTION,
+ LZMA_OPTION,
+ LZOP_OPTION,
+ MODE_OPTION,
+ MTIME_OPTION,
+ NEWER_MTIME_OPTION,
+ NO_ACLS_OPTION,
+ NO_AUTO_COMPRESS_OPTION,
+ NO_CHECK_DEVICE_OPTION,
+ NO_DELAY_DIRECTORY_RESTORE_OPTION,
+ NO_IGNORE_COMMAND_ERROR_OPTION,
+ NO_OVERWRITE_DIR_OPTION,
+ NO_QUOTE_CHARS_OPTION,
+ NO_SAME_OWNER_OPTION,
+ NO_SAME_PERMISSIONS_OPTION,
+ NO_SEEK_OPTION,
+ NO_SELINUX_CONTEXT_OPTION,
+ NO_XATTR_OPTION,
+ NUMERIC_OWNER_OPTION,
+ OCCURRENCE_OPTION,
+ OLD_ARCHIVE_OPTION,
+ ONE_FILE_SYSTEM_OPTION,
+ ONE_TOP_LEVEL_OPTION,
+ OVERWRITE_DIR_OPTION,
+ OVERWRITE_OPTION,
+ OWNER_OPTION,
+ OWNER_MAP_OPTION,
+ PAX_OPTION,
+ POSIX_OPTION,
+ QUOTE_CHARS_OPTION,
+ QUOTING_STYLE_OPTION,
+ RECORD_SIZE_OPTION,
+ RECURSIVE_UNLINK_OPTION,
+ REMOVE_FILES_OPTION,
+ RESTRICT_OPTION,
+ RMT_COMMAND_OPTION,
+ RSH_COMMAND_OPTION,
+ SAME_OWNER_OPTION,
+ SELINUX_CONTEXT_OPTION,
+ SHOW_DEFAULTS_OPTION,
+ SHOW_OMITTED_DIRS_OPTION,
+ SHOW_SNAPSHOT_FIELD_RANGES_OPTION,
+ SHOW_TRANSFORMED_NAMES_OPTION,
+ SKIP_OLD_FILES_OPTION,
+ SORT_OPTION,
+ HOLE_DETECTION_OPTION,
+ SPARSE_VERSION_OPTION,
+ STRIP_COMPONENTS_OPTION,
+ SUFFIX_OPTION,
+ TEST_LABEL_OPTION,
+ TOTALS_OPTION,
+ TO_COMMAND_OPTION,
+ TRANSFORM_OPTION,
+ UTC_OPTION,
+ VOLNO_FILE_OPTION,
+ WARNING_OPTION,
+ XATTR_OPTION,
+ XATTR_EXCLUDE,
+ XATTR_INCLUDE
+};
+
+static char const doc[] = N_("\
+GNU 'tar' saves many files together into a single tape or disk archive, \
+and can restore individual files from the archive.\n\
+\n\
+Examples:\n\
+ tar -cf archive.tar foo bar # Create archive.tar from files foo and bar.\n\
+ tar -tvf archive.tar # List all files in archive.tar verbosely.\n\
+ tar -xf archive.tar # Extract all files from archive.tar.\n")
+"\v"
+N_("The backup suffix is '~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\
+The version control may be set with --backup or VERSION_CONTROL, values are:\n\n\
+ none, off never make backups\n\
+ t, numbered make numbered backups\n\
+ nil, existing numbered if numbered backups exist, simple otherwise\n\
+ never, simple always make simple backups\n");
+
+
+/* NOTE:
+
+ Available option letters are DEQY and eqy. Consider the following
+ assignments:
+
+ [For Solaris tar compatibility =/= Is it important at all?]
+ e exit immediately with a nonzero exit status if unexpected errors occur
+ E use extended headers (--format=posix)
+
+ [q alias for --occurrence=1 =/= this would better be used for quiet?]
+
+ y per-file gzip compression
+ Y per-block gzip compression.
+
+ Additionally, the 'n' letter is assigned for option --seek, which
+ is probably not needed and should be marked as deprecated, so that
+ -n may become available in the future.
+*/
+
+static struct argp_option options[] = {
+#define GRID 10
+ {NULL, 0, NULL, 0,
+ N_("Main operation mode:"), GRID },
+
+ {"list", 't', 0, 0,
+ N_("list the contents of an archive"), GRID+1 },
+ {"extract", 'x', 0, 0,
+ N_("extract files from an archive"), GRID+1 },
+ {"get", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
+ {"create", 'c', 0, 0,
+ N_("create a new archive"), GRID+1 },
+ {"diff", 'd', 0, 0,
+ N_("find differences between archive and file system"), GRID+1 },
+ {"compare", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
+ {"append", 'r', 0, 0,
+ N_("append files to the end of an archive"), GRID+1 },
+ {"update", 'u', 0, 0,
+ N_("only append files newer than copy in archive"), GRID+1 },
+ {"catenate", 'A', 0, 0,
+ N_("append tar files to an archive"), GRID+1 },
+ {"concatenate", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
+ {"delete", DELETE_OPTION, 0, 0,
+ N_("delete from the archive (not on mag tapes!)"), GRID+1 },
+ {"test-label", TEST_LABEL_OPTION, NULL, 0,
+ N_("test the archive volume label and exit"), GRID+1 },
+#undef GRID
+
+#define GRID 20
+ {NULL, 0, NULL, 0,
+ N_("Operation modifiers:"), GRID },
+
+ {"sparse", 'S', 0, 0,
+ N_("handle sparse files efficiently"), GRID+1 },
+ {"hole-detection", HOLE_DETECTION_OPTION, N_("TYPE"), 0,
+ N_("technique to detect holes"), GRID+1 },
+ {"sparse-version", SPARSE_VERSION_OPTION, N_("MAJOR[.MINOR]"), 0,
+ N_("set version of the sparse format to use (implies --sparse)"), GRID+1},
+ {"incremental", 'G', 0, 0,
+ N_("handle old GNU-format incremental backup"), GRID+1 },
+ {"listed-incremental", 'g', N_("FILE"), 0,
+ N_("handle new GNU-format incremental backup"), GRID+1 },
+ {"level", LEVEL_OPTION, N_("NUMBER"), 0,
+ N_("dump level for created listed-incremental archive"), GRID+1 },
+ {"ignore-failed-read", IGNORE_FAILED_READ_OPTION, 0, 0,
+ N_("do not exit with nonzero on unreadable files"), GRID+1 },
+ {"occurrence", OCCURRENCE_OPTION, N_("NUMBER"), OPTION_ARG_OPTIONAL,
+ N_("process only the NUMBERth occurrence of each file in the archive;"
+ " this option is valid only in conjunction with one of the subcommands"
+ " --delete, --diff, --extract or --list and when a list of files"
+ " is given either on the command line or via the -T option;"
+ " NUMBER defaults to 1"), GRID+1 },
+ {"seek", 'n', NULL, 0,
+ N_("archive is seekable"), GRID+1 },
+ {"no-seek", NO_SEEK_OPTION, NULL, 0,
+ N_("archive is not seekable"), GRID+1 },
+ {"no-check-device", NO_CHECK_DEVICE_OPTION, NULL, 0,
+ N_("do not check device numbers when creating incremental archives"),
+ GRID+1 },
+ {"check-device", CHECK_DEVICE_OPTION, NULL, 0,
+ N_("check device numbers when creating incremental archives (default)"),
+ GRID+1 },
+#undef GRID
+
+#define GRID 30
+ {NULL, 0, NULL, 0,
+ N_("Overwrite control:"), GRID },
+
+ {"verify", 'W', 0, 0,
+ N_("attempt to verify the archive after writing it"), GRID+1 },
+ {"remove-files", REMOVE_FILES_OPTION, 0, 0,
+ N_("remove files after adding them to the archive"), GRID+1 },
+ {"keep-old-files", 'k', 0, 0,
+ N_("don't replace existing files when extracting, "
+ "treat them as errors"), GRID+1 },
+ {"skip-old-files", SKIP_OLD_FILES_OPTION, 0, 0,
+ N_("don't replace existing files when extracting, silently skip over them"),
+ GRID+1 },
+ {"keep-newer-files", KEEP_NEWER_FILES_OPTION, 0, 0,
+ N_("don't replace existing files that are newer than their archive copies"), GRID+1 },
+ {"overwrite", OVERWRITE_OPTION, 0, 0,
+ N_("overwrite existing files when extracting"), GRID+1 },
+ {"unlink-first", 'U', 0, 0,
+ N_("remove each file prior to extracting over it"), GRID+1 },
+ {"recursive-unlink", RECURSIVE_UNLINK_OPTION, 0, 0,
+ N_("empty hierarchies prior to extracting directory"), GRID+1 },
+ {"no-overwrite-dir", NO_OVERWRITE_DIR_OPTION, 0, 0,
+ N_("preserve metadata of existing directories"), GRID+1 },
+ {"overwrite-dir", OVERWRITE_DIR_OPTION, 0, 0,
+ N_("overwrite metadata of existing directories when extracting (default)"),
+ GRID+1 },
+ {"keep-directory-symlink", KEEP_DIRECTORY_SYMLINK_OPTION, 0, 0,
+ N_("preserve existing symlinks to directories when extracting"),
+ GRID+1 },
+ {"one-top-level", ONE_TOP_LEVEL_OPTION, N_("DIR"), OPTION_ARG_OPTIONAL,
+ N_("create a subdirectory to avoid having loose files extracted"),
+ GRID+1 },
+#undef GRID
+
+#define GRID 40
+ {NULL, 0, NULL, 0,
+ N_("Select output stream:"), GRID },
+
+ {"to-stdout", 'O', 0, 0,
+ N_("extract files to standard output"), GRID+1 },
+ {"to-command", TO_COMMAND_OPTION, N_("COMMAND"), 0,
+ N_("pipe extracted files to another program"), GRID+1 },
+ {"ignore-command-error", IGNORE_COMMAND_ERROR_OPTION, 0, 0,
+ N_("ignore exit codes of children"), GRID+1 },
+ {"no-ignore-command-error", NO_IGNORE_COMMAND_ERROR_OPTION, 0, 0,
+ N_("treat non-zero exit codes of children as error"), GRID+1 },
+#undef GRID
+
+#define GRID 50
+ {NULL, 0, NULL, 0,
+ N_("Handling of file attributes:"), GRID },
+
+ {"owner", OWNER_OPTION, N_("NAME"), 0,
+ N_("force NAME as owner for added files"), GRID+1 },
+ {"group", GROUP_OPTION, N_("NAME"), 0,
+ N_("force NAME as group for added files"), GRID+1 },
+ {"owner-map", OWNER_MAP_OPTION, N_("FILE"), 0,
+ N_("use FILE to map file owner UIDs and names"), GRID+1 },
+ {"group-map", GROUP_MAP_OPTION, N_("FILE"), 0,
+ N_("use FILE to map file owner GIDs and names"), GRID+1 },
+ {"mtime", MTIME_OPTION, N_("DATE-OR-FILE"), 0,
+ N_("set mtime for added files from DATE-OR-FILE"), GRID+1 },
+ {"clamp-mtime", CLAMP_MTIME_OPTION, 0, 0,
+ N_("only set time when the file is more recent than what was given with --mtime"), GRID+1 },
+ {"mode", MODE_OPTION, N_("CHANGES"), 0,
+ N_("force (symbolic) mode CHANGES for added files"), GRID+1 },
+ {"atime-preserve", ATIME_PRESERVE_OPTION,
+ N_("METHOD"), OPTION_ARG_OPTIONAL,
+ N_("preserve access times on dumped files, either by restoring the times"
+ " after reading (METHOD='replace'; default) or by not setting the times"
+ " in the first place (METHOD='system')"), GRID+1 },
+ {"touch", 'm', 0, 0,
+ N_("don't extract file modified time"), GRID+1 },
+ {"same-owner", SAME_OWNER_OPTION, 0, 0,
+ N_("try extracting files with the same ownership as exists in the archive (default for superuser)"), GRID+1 },
+ {"no-same-owner", NO_SAME_OWNER_OPTION, 0, 0,
+ N_("extract files as yourself (default for ordinary users)"), GRID+1 },
+ {"numeric-owner", NUMERIC_OWNER_OPTION, 0, 0,
+ N_("always use numbers for user/group names"), GRID+1 },
+ {"preserve-permissions", 'p', 0, 0,
+ N_("extract information about file permissions (default for superuser)"),
+ GRID+1 },
+ {"same-permissions", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
+ {"no-same-permissions", NO_SAME_PERMISSIONS_OPTION, 0, 0,
+ N_("apply the user's umask when extracting permissions from the archive (default for ordinary users)"), GRID+1 },
+ {"preserve-order", 's', 0, 0,
+ N_("member arguments are listed in the same order as the "
+ "files in the archive"), GRID+1 },
+ {"same-order", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
+ {"delay-directory-restore", DELAY_DIRECTORY_RESTORE_OPTION, 0, 0,
+ N_("delay setting modification times and permissions of extracted"
+ " directories until the end of extraction"), GRID+1 },
+ {"no-delay-directory-restore", NO_DELAY_DIRECTORY_RESTORE_OPTION, 0, 0,
+ N_("cancel the effect of --delay-directory-restore option"), GRID+1 },
+ {"sort", SORT_OPTION, N_("ORDER"), 0,
+#if D_INO_IN_DIRENT
+ N_("directory sorting order: none (default), name or inode"
+#else
+ N_("directory sorting order: none (default) or name"
+#endif
+ ), GRID+1 },
+#undef GRID
+
+#define GRID 55
+ {NULL, 0, NULL, 0,
+ N_("Handling of extended file attributes:"), GRID },
+
+ {"xattrs", XATTR_OPTION, 0, 0,
+ N_("Enable extended attributes support"), GRID+1 },
+ {"no-xattrs", NO_XATTR_OPTION, 0, 0,
+ N_("Disable extended attributes support"), GRID+1 },
+ {"xattrs-include", XATTR_INCLUDE, N_("MASK"), 0,
+ N_("specify the include pattern for xattr keys"), GRID+1 },
+ {"xattrs-exclude", XATTR_EXCLUDE, N_("MASK"), 0,
+ N_("specify the exclude pattern for xattr keys"), GRID+1 },
+ {"selinux", SELINUX_CONTEXT_OPTION, 0, 0,
+ N_("Enable the SELinux context support"), GRID+1 },
+ {"no-selinux", NO_SELINUX_CONTEXT_OPTION, 0, 0,
+ N_("Disable the SELinux context support"), GRID+1 },
+ {"acls", ACLS_OPTION, 0, 0,
+ N_("Enable the POSIX ACLs support"), GRID+1 },
+ {"no-acls", NO_ACLS_OPTION, 0, 0,
+ N_("Disable the POSIX ACLs support"), GRID+1 },
+#undef GRID
+
+#define GRID 60
+ {NULL, 0, NULL, 0,
+ N_("Device selection and switching:"), GRID },
+
+ {"file", 'f', N_("ARCHIVE"), 0,
+ N_("use archive file or device ARCHIVE"), GRID+1 },
+ {"force-local", FORCE_LOCAL_OPTION, 0, 0,
+ N_("archive file is local even if it has a colon"), GRID+1 },
+ {"rmt-command", RMT_COMMAND_OPTION, N_("COMMAND"), 0,
+ N_("use given rmt COMMAND instead of rmt"), GRID+1 },
+ {"rsh-command", RSH_COMMAND_OPTION, N_("COMMAND"), 0,
+ N_("use remote COMMAND instead of rsh"), GRID+1 },
+#ifdef DEVICE_PREFIX
+ {"-[0-7][lmh]", 0, NULL, OPTION_DOC, /* It is OK, since 'name' will never be
+ translated */
+ N_("specify drive and density"), GRID+1 },
+#endif
+ {NULL, '0', NULL, OPTION_HIDDEN, NULL, GRID+1 },
+ {NULL, '1', NULL, OPTION_HIDDEN, NULL, GRID+1 },
+ {NULL, '2', NULL, OPTION_HIDDEN, NULL, GRID+1 },
+ {NULL, '3', NULL, OPTION_HIDDEN, NULL, GRID+1 },
+ {NULL, '4', NULL, OPTION_HIDDEN, NULL, GRID+1 },
+ {NULL, '5', NULL, OPTION_HIDDEN, NULL, GRID+1 },
+ {NULL, '6', NULL, OPTION_HIDDEN, NULL, GRID+1 },
+ {NULL, '7', NULL, OPTION_HIDDEN, NULL, GRID+1 },
+ {NULL, '8', NULL, OPTION_HIDDEN, NULL, GRID+1 },
+ {NULL, '9', NULL, OPTION_HIDDEN, NULL, GRID+1 },
+
+ {"multi-volume", 'M', 0, 0,
+ N_("create/list/extract multi-volume archive"), GRID+1 },
+ {"tape-length", 'L', N_("NUMBER"), 0,
+ N_("change tape after writing NUMBER x 1024 bytes"), GRID+1 },
+ {"info-script", 'F', N_("NAME"), 0,
+ N_("run script at end of each tape (implies -M)"), GRID+1 },
+ {"new-volume-script", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
+ {"volno-file", VOLNO_FILE_OPTION, N_("FILE"), 0,
+ N_("use/update the volume number in FILE"), GRID+1 },
+#undef GRID
+
+#define GRID 70
+ {NULL, 0, NULL, 0,
+ N_("Device blocking:"), GRID },
+
+ {"blocking-factor", 'b', N_("BLOCKS"), 0,
+ N_("BLOCKS x 512 bytes per record"), GRID+1 },
+ {"record-size", RECORD_SIZE_OPTION, N_("NUMBER"), 0,
+ N_("NUMBER of bytes per record, multiple of 512"), GRID+1 },
+ {"ignore-zeros", 'i', 0, 0,
+ N_("ignore zeroed blocks in archive (means EOF)"), GRID+1 },
+ {"read-full-records", 'B', 0, 0,
+ N_("reblock as we read (for 4.2BSD pipes)"), GRID+1 },
+#undef GRID
+
+#define GRID 80
+ {NULL, 0, NULL, 0,
+ N_("Archive format selection:"), GRID },
+
+ {"format", 'H', N_("FORMAT"), 0,
+ N_("create archive of the given format"), GRID+1 },
+
+ {NULL, 0, NULL, 0, N_("FORMAT is one of the following:"), GRID+2 },
+ {" v7", 0, NULL, OPTION_DOC|OPTION_NO_TRANS, N_("old V7 tar format"),
+ GRID+3 },
+ {" oldgnu", 0, NULL, OPTION_DOC|OPTION_NO_TRANS,
+ N_("GNU format as per tar <= 1.12"), GRID+3 },
+ {" gnu", 0, NULL, OPTION_DOC|OPTION_NO_TRANS,
+ N_("GNU tar 1.13.x format"), GRID+3 },
+ {" ustar", 0, NULL, OPTION_DOC|OPTION_NO_TRANS,
+ N_("POSIX 1003.1-1988 (ustar) format"), GRID+3 },
+ {" pax", 0, NULL, OPTION_DOC|OPTION_NO_TRANS,
+ N_("POSIX 1003.1-2001 (pax) format"), GRID+3 },
+ {" posix", 0, NULL, OPTION_DOC|OPTION_NO_TRANS, N_("same as pax"), GRID+3 },
+
+ {"old-archive", OLD_ARCHIVE_OPTION, 0, 0, /* FIXME */
+ N_("same as --format=v7"), GRID+8 },
+ {"portability", 0, 0, OPTION_ALIAS, NULL, GRID+8 },
+ {"posix", POSIX_OPTION, 0, 0,
+ N_("same as --format=posix"), GRID+8 },
+ {"pax-option", PAX_OPTION, N_("keyword[[:]=value][,keyword[[:]=value]]..."), 0,
+ N_("control pax keywords"), GRID+8 },
+ {"label", 'V', N_("TEXT"), 0,
+ N_("create archive with volume name TEXT; at list/extract time, use TEXT as a globbing pattern for volume name"), GRID+8 },
+#undef GRID
+
+#define GRID 90
+ {NULL, 0, NULL, 0,
+ N_("Compression options:"), GRID },
+ {"auto-compress", 'a', 0, 0,
+ N_("use archive suffix to determine the compression program"), GRID+1 },
+ {"no-auto-compress", NO_AUTO_COMPRESS_OPTION, 0, 0,
+ N_("do not use archive suffix to determine the compression program"),
+ GRID+1 },
+ {"use-compress-program", 'I', N_("PROG"), 0,
+ N_("filter through PROG (must accept -d)"), GRID+1 },
+ /* Note: docstrings for the options below are generated by tar_help_filter */
+ {"bzip2", 'j', 0, 0, NULL, GRID+1 },
+ {"gzip", 'z', 0, 0, NULL, GRID+1 },
+ {"gunzip", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
+ {"ungzip", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
+ {"compress", 'Z', 0, 0, NULL, GRID+1 },
+ {"uncompress", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
+ {"lzip", LZIP_OPTION, 0, 0, NULL, GRID+1 },
+ {"lzma", LZMA_OPTION, 0, 0, NULL, GRID+1 },
+ {"lzop", LZOP_OPTION, 0, 0, NULL, GRID+1 },
+ {"xz", 'J', 0, 0, NULL, GRID+1 },
+#undef GRID
+
+#define GRID 100
+ {NULL, 0, NULL, 0,
+ N_("Local file selection:"), GRID },
+ {"one-file-system", ONE_FILE_SYSTEM_OPTION, 0, 0,
+ N_("stay in local file system when creating archive"), GRID+1 },
+ {"absolute-names", 'P', 0, 0,
+ N_("don't strip leading '/'s from file names"), GRID+1 },
+ {"dereference", 'h', 0, 0,
+ N_("follow symlinks; archive and dump the files they point to"), GRID+1 },
+ {"hard-dereference", HARD_DEREFERENCE_OPTION, 0, 0,
+ N_("follow hard links; archive and dump the files they refer to"), GRID+1 },
+ {"starting-file", 'K', N_("MEMBER-NAME"), 0,
+ N_("begin at member MEMBER-NAME when reading the archive"), GRID+1 },
+ {"newer", 'N', N_("DATE-OR-FILE"), 0,
+ N_("only store files newer than DATE-OR-FILE"), GRID+1 },
+ {"after-date", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
+ {"newer-mtime", NEWER_MTIME_OPTION, N_("DATE"), 0,
+ N_("compare date and time when data changed only"), GRID+1 },
+ {"backup", BACKUP_OPTION, N_("CONTROL"), OPTION_ARG_OPTIONAL,
+ N_("backup before removal, choose version CONTROL"), GRID+1 },
+ {"suffix", SUFFIX_OPTION, N_("STRING"), 0,
+ N_("backup before removal, override usual suffix ('~' unless overridden by environment variable SIMPLE_BACKUP_SUFFIX)"), GRID+1 },
+#undef GRID
+
+#define GRID 110
+ {NULL, 0, NULL, 0,
+ N_("File name transformations:"), GRID },
+ {"strip-components", STRIP_COMPONENTS_OPTION, N_("NUMBER"), 0,
+ N_("strip NUMBER leading components from file names on extraction"),
+ GRID+1 },
+ {"transform", TRANSFORM_OPTION, N_("EXPRESSION"), 0,
+ N_("use sed replace EXPRESSION to transform file names"), GRID+1 },
+ {"xform", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
+#undef GRID
+
+#define GRID 130
+ {NULL, 0, NULL, 0,
+ N_("Informative output:"), GRID },
+
+ {"verbose", 'v', 0, 0,
+ N_("verbosely list files processed"), GRID+1 },
+ {"warning", WARNING_OPTION, N_("KEYWORD"), 0,
+ N_("warning control"), GRID+1 },
+ {"checkpoint", CHECKPOINT_OPTION, N_("NUMBER"), OPTION_ARG_OPTIONAL,
+ N_("display progress messages every NUMBERth record (default 10)"),
+ GRID+1 },
+ {"checkpoint-action", CHECKPOINT_ACTION_OPTION, N_("ACTION"), 0,
+ N_("execute ACTION on each checkpoint"),
+ GRID+1 },
+ {"check-links", 'l', 0, 0,
+ N_("print a message if not all links are dumped"), GRID+1 },
+ {"totals", TOTALS_OPTION, N_("SIGNAL"), OPTION_ARG_OPTIONAL,
+ N_("print total bytes after processing the archive; "
+ "with an argument - print total bytes when this SIGNAL is delivered; "
+ "Allowed signals are: SIGHUP, SIGQUIT, SIGINT, SIGUSR1 and SIGUSR2; "
+ "the names without SIG prefix are also accepted"), GRID+1 },
+ {"utc", UTC_OPTION, 0, 0,
+ N_("print file modification times in UTC"), GRID+1 },
+ {"full-time", FULL_TIME_OPTION, 0, 0,
+ N_("print file time to its full resolution"), GRID+1 },
+ {"index-file", INDEX_FILE_OPTION, N_("FILE"), 0,
+ N_("send verbose output to FILE"), GRID+1 },
+ {"block-number", 'R', 0, 0,
+ N_("show block number within archive with each message"), GRID+1 },
+ {"interactive", 'w', 0, 0,
+ N_("ask for confirmation for every action"), GRID+1 },
+ {"confirmation", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
+ {"show-defaults", SHOW_DEFAULTS_OPTION, 0, 0,
+ N_("show tar defaults"), GRID+1 },
+ {"show-snapshot-field-ranges", SHOW_SNAPSHOT_FIELD_RANGES_OPTION, 0, 0,
+ N_("show valid ranges for snapshot-file fields"), GRID+1 },
+ {"show-omitted-dirs", SHOW_OMITTED_DIRS_OPTION, 0, 0,
+ N_("when listing or extracting, list each directory that does not match search criteria"), GRID+1 },
+ {"show-transformed-names", SHOW_TRANSFORMED_NAMES_OPTION, 0, 0,
+ N_("show file or archive names after transformation"),
+ GRID+1 },
+ {"show-stored-names", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
+ {"quoting-style", QUOTING_STYLE_OPTION, N_("STYLE"), 0,
+ N_("set name quoting style; see below for valid STYLE values"), GRID+1 },
+ {"quote-chars", QUOTE_CHARS_OPTION, N_("STRING"), 0,
+ N_("additionally quote characters from STRING"), GRID+1 },
+ {"no-quote-chars", NO_QUOTE_CHARS_OPTION, N_("STRING"), 0,
+ N_("disable quoting for characters from STRING"), GRID+1 },
+#undef GRID
+
+#define GRID 140
+ {NULL, 0, NULL, 0,
+ N_("Compatibility options:"), GRID },
+
+ {NULL, 'o', 0, 0,
+ N_("when creating, same as --old-archive; when extracting, same as --no-same-owner"), GRID+1 },
+#undef GRID
+
+#define GRID 150
+ {NULL, 0, NULL, 0,
+ N_("Other options:"), GRID },
+
+ {"restrict", RESTRICT_OPTION, 0, 0,
+ N_("disable use of some potentially harmful options"), -1 },
+#undef GRID
+
+ {0, 0, 0, 0, 0, 0}
+};
+
+static char const *const atime_preserve_args[] =
+{
+ "replace", "system", NULL
+};
+
+static enum atime_preserve const atime_preserve_types[] =
+{
+ replace_atime_preserve, system_atime_preserve
+};
+
+/* Make sure atime_preserve_types has as much entries as atime_preserve_args
+ (minus 1 for NULL guard) */
+ARGMATCH_VERIFY (atime_preserve_args, atime_preserve_types);
+
+struct tar_args /* Variables used during option parsing */
+{
+ struct option_locus *loc;
+
+ struct textual_date *textual_date; /* Keeps the arguments to --newer-mtime
+ and/or --date option if they are
+ textual dates */
+ bool o_option; /* True if -o option was given */
+ bool pax_option; /* True if --pax-option was given */
+ char const *backup_suffix_string; /* --suffix option argument */
+ char const *version_control_string; /* --backup option argument */
+ bool input_files; /* True if some input files where given */
+ int compress_autodetect; /* True if compression autodetection should
+ be attempted when creating archives */
+};
+
+
+static char *
+format_default_settings (void)
+{
+ return xasprintf (
+ "--format=%s -f%s -b%d --quoting-style=%s --rmt-command=%s"
+#ifdef REMOTE_SHELL
+ " --rsh-command=%s"
+#endif
+ ,
+ archive_format_string (DEFAULT_ARCHIVE_FORMAT),
+ DEFAULT_ARCHIVE, DEFAULT_BLOCKING,
+ quoting_style_args[DEFAULT_QUOTING_STYLE],
+ DEFAULT_RMT_COMMAND
+#ifdef REMOTE_SHELL
+ , REMOTE_SHELL
+#endif
+ );
+}
+
+static void
+option_conflict_error (const char *a, const char *b)
+{
+ /* TRANSLATORS: Both %s in this statement are replaced with
+ option names. */
+ USAGE_ERROR ((0, 0, _("'%s' cannot be used with '%s'"), a, b));
+}
+
+/* Classes of options that can conflict: */
+enum option_class
+ {
+ OC_COMPRESS, /* Compress options: -JjZz, -I, etc. */
+ OC_OCCURRENCE, /* --occurrence */
+ OC_LISTED_INCREMENTAL, /* --listed-incremental */
+ OC_NEWER, /* --newer, --newer-mtime, --after-date */
+ OC_VERIFY, /* --verify */
+ OC_STARTING_FILE, /* --starting-file */
+ OC_SAME_ORDER, /* --same-order */
+ OC_ONE_TOP_LEVEL, /* --one-top-level */
+ OC_ABSOLUTE_NAMES, /* --absolute-names */
+ OC_OLD_FILES, /* --keep-old-files, --overwrite, etc. */
+ OC_MAX
+ };
+
+/* Table of locations of potentially conflicting options. Two options can
+ conflict only if they procede from the command line. Otherwise, options
+ in command line silently override those defined in TAR_OPTIONS. */
+static struct option_locus *option_class[OC_MAX];
+
+/* Save location of an option of class ID. Return location of a previous
+ occurrence of an option of that class, or NULL. */
+static struct option_locus *
+optloc_save (unsigned int id, struct option_locus *loc)
+{
+ struct option_locus *optloc;
+ char *p;
+ size_t s;
+
+ if (id >= sizeof (option_class) / sizeof (option_class[0]))
+ abort ();
+ s = sizeof (*loc);
+ if (loc->name)
+ s += strlen (loc->name) + 1;
+ optloc = xmalloc (s);
+ if (loc->name)
+ {
+ p = (char*) optloc + sizeof (*loc);
+ strcpy (p, loc->name);
+ optloc->name = p;
+ }
+ else
+ optloc->name = NULL;
+ optloc->source = loc->source;
+ optloc->line = loc->line;
+ optloc->prev = option_class[id];
+ option_class[id] = optloc;
+ return optloc->prev;
+}
+
+/* Return location of a recent option of class ID */
+static struct option_locus *
+optloc_lookup (int id)
+{
+ return option_class[id];
+}
+
+/* Return true if the latest occurrence of option ID was in the command line */
+static int
+option_set_in_cl (int id)
+{
+ struct option_locus *loc = optloc_lookup (id);
+ if (!loc)
+ return 0;
+ return loc->source == OPTS_COMMAND_LINE;
+}
+
+/* Compare two option locations */
+static int
+optloc_eq (struct option_locus *a, struct option_locus *b)
+{
+ if (a->source != b->source)
+ return 0;
+ if (a->source == OPTS_COMMAND_LINE)
+ return 1;
+ return strcmp (a->name, b->name) == 0;
+}
+
+static void
+set_subcommand_option (enum subcommand subcommand)
+{
+ if (subcommand_option != UNKNOWN_SUBCOMMAND
+ && subcommand_option != subcommand)
+ USAGE_ERROR ((0, 0,
+ _("You may not specify more than one '-Acdtrux', '--delete' or '--test-label' option")));
+
+ subcommand_option = subcommand;
+}
+
+static void
+set_use_compress_program_option (const char *string, struct option_locus *loc)
+{
+ struct option_locus *p = optloc_save (OC_COMPRESS, loc);
+ if (use_compress_program_option
+ && strcmp (use_compress_program_option, string) != 0
+ && p->source == OPTS_COMMAND_LINE)
+ USAGE_ERROR ((0, 0, _("Conflicting compression options")));
+
+ use_compress_program_option = string;
+}
+
+static void
+sigstat (int signo)
+{
+ compute_duration ();
+ print_total_stats ();
+#ifndef HAVE_SIGACTION
+ signal (signo, sigstat);
+#endif
+}
+
+static void
+stat_on_signal (int signo)
+{
+#ifdef HAVE_SIGACTION
+# ifndef SA_RESTART
+# define SA_RESTART 0
+# endif
+ struct sigaction act;
+ act.sa_handler = sigstat;
+ sigemptyset (&act.sa_mask);
+ act.sa_flags = SA_RESTART;
+ sigaction (signo, &act, NULL);
+#else
+ signal (signo, sigstat);
+#endif
+}
+
+static void
+set_stat_signal (const char *name)
+{
+ static struct sigtab
+ {
+ char const *name;
+ int signo;
+ } const sigtab[] = {
+ { "SIGUSR1", SIGUSR1 },
+ { "USR1", SIGUSR1 },
+ { "SIGUSR2", SIGUSR2 },
+ { "USR2", SIGUSR2 },
+ { "SIGHUP", SIGHUP },
+ { "HUP", SIGHUP },
+ { "SIGINT", SIGINT },
+ { "INT", SIGINT },
+ { "SIGQUIT", SIGQUIT },
+ { "QUIT", SIGQUIT }
+ };
+ struct sigtab const *p;
+
+ for (p = sigtab; p < sigtab + sizeof (sigtab) / sizeof (sigtab[0]); p++)
+ if (strcmp (p->name, name) == 0)
+ {
+ stat_on_signal (p->signo);
+ return;
+ }
+ FATAL_ERROR ((0, 0, _("Unknown signal name: %s"), name));
+}
+
+
+struct textual_date
+{
+ struct textual_date *next;
+ struct timespec ts;
+ const char *option;
+ char *date;
+};
+
+static int
+get_date_or_file (struct tar_args *args, const char *option,
+ const char *str, struct timespec *ts)
+{
+ if (FILE_SYSTEM_PREFIX_LEN (str) != 0
+ || ISSLASH (*str)
+ || *str == '.')
+ {
+ struct stat st;
+ if (stat (str, &st) != 0)
+ {
+ stat_error (str);
+ USAGE_ERROR ((0, 0, _("Date sample file not found")));
+ }
+ *ts = get_stat_mtime (&st);
+ }
+ else
+ {
+ if (! parse_datetime (ts, str, NULL))
+ {
+ WARN ((0, 0, _("Substituting %s for unknown date format %s"),
+ tartime (*ts, false), quote (str)));
+ ts->tv_nsec = 0;
+ return 1;
+ }
+ else
+ {
+ struct textual_date *p = xmalloc (sizeof (*p));
+ p->ts = *ts;
+ p->option = option;
+ p->date = xstrdup (str);
+ p->next = args->textual_date;
+ args->textual_date = p;
+ }
+ }
+ return 0;
+}
+
+static void
+report_textual_dates (struct tar_args *args)
+{
+ struct textual_date *p;
+ for (p = args->textual_date; p; )
+ {
+ struct textual_date *next = p->next;
+ if (verbose_option)
+ {
+ char const *treated_as = tartime (p->ts, true);
+ if (strcmp (p->date, treated_as) != 0)
+ WARN ((0, 0, _("Option %s: Treating date '%s' as %s"),
+ p->option, p->date, treated_as));
+ }
+ free (p->date);
+ free (p);
+ p = next;
+ }
+}
+
+
+/* Default density numbers for [0-9][lmh] device specifications */
+
+#if defined DEVICE_PREFIX && !defined DENSITY_LETTER
+# ifndef LOW_DENSITY_NUM
+# define LOW_DENSITY_NUM 0
+# endif
+
+# ifndef MID_DENSITY_NUM
+# define MID_DENSITY_NUM 8
+# endif
+
+# ifndef HIGH_DENSITY_NUM
+# define HIGH_DENSITY_NUM 16
+# endif
+#endif
+
+
+static char *
+tar_help_filter (int key, const char *text, void *input)
+{
+ struct obstack stk;
+ char *s;
+
+ switch (key)
+ {
+ default:
+ s = (char*) text;
+ break;
+
+ case 'j':
+ s = xasprintf (_("filter the archive through %s"), BZIP2_PROGRAM);
+ break;
+
+ case 'z':
+ s = xasprintf (_("filter the archive through %s"), GZIP_PROGRAM);
+ break;
+
+ case 'Z':
+ s = xasprintf (_("filter the archive through %s"), COMPRESS_PROGRAM);
+ break;
+
+ case LZIP_OPTION:
+ s = xasprintf (_("filter the archive through %s"), LZIP_PROGRAM);
+ break;
+
+ case LZMA_OPTION:
+ s = xasprintf (_("filter the archive through %s"), LZMA_PROGRAM);
+ break;
+
+ case LZOP_OPTION:
+ s = xasprintf (_("filter the archive through %s"), LZOP_PROGRAM);
+
+ case 'J':
+ s = xasprintf (_("filter the archive through %s"), XZ_PROGRAM);
+ break;
+
+ case ARGP_KEY_HELP_EXTRA:
+ {
+ const char *tstr;
+
+ obstack_init (&stk);
+ tstr = _("Valid arguments for the --quoting-style option are:");
+ obstack_grow (&stk, tstr, strlen (tstr));
+ obstack_grow (&stk, "\n\n", 2);
+ tar_list_quoting_styles (&stk, " ");
+ tstr = _("\n*This* tar defaults to:\n");
+ obstack_grow (&stk, tstr, strlen (tstr));
+ s = format_default_settings ();
+ obstack_grow (&stk, s, strlen (s));
+ obstack_1grow (&stk, '\n');
+ obstack_1grow (&stk, 0);
+ s = xstrdup (obstack_finish (&stk));
+ obstack_free (&stk, NULL);
+ }
+ }
+ return s;
+}
+
+static char *
+expand_pax_option (struct tar_args *targs, const char *arg)
+{
+ struct obstack stk;
+ char *res;
+
+ obstack_init (&stk);
+ while (*arg)
+ {
+ size_t seglen = strcspn (arg, ",");
+ char *p = memchr (arg, '=', seglen);
+ if (p)
+ {
+ size_t len = p - arg + 1;
+ obstack_grow (&stk, arg, len);
+ len = seglen - len;
+ for (++p; *p && isspace ((unsigned char) *p); p++)
+ len--;
+ if (*p == '{' && p[len-1] == '}')
+ {
+ struct timespec ts;
+ char *tmp = xmalloc (len);
+ memcpy (tmp, p + 1, len-2);
+ tmp[len-2] = 0;
+ if (get_date_or_file (targs, "--pax-option", tmp, &ts) == 0)
+ {
+ char buf[TIMESPEC_STRSIZE_BOUND];
+ char const *s = code_timespec (ts, buf);
+ obstack_grow (&stk, s, strlen (s));
+ }
+ else
+ obstack_grow (&stk, p, len);
+ free (tmp);
+ }
+ else
+ obstack_grow (&stk, p, len);
+ }
+ else
+ obstack_grow (&stk, arg, seglen);
+
+ arg += seglen;
+ if (*arg)
+ {
+ obstack_1grow (&stk, *arg);
+ arg++;
+ }
+ }
+ obstack_1grow (&stk, 0);
+ res = xstrdup (obstack_finish (&stk));
+ obstack_free (&stk, NULL);
+ return res;
+}
+
+
+static uintmax_t
+parse_owner_group (char *arg, uintmax_t field_max, char const **name_option)
+{
+ uintmax_t u = UINTMAX_MAX;
+ char *end;
+ char const *name = 0;
+ char const *invalid_num = 0;
+ char *colon = strchr (arg, ':');
+
+ if (colon)
+ {
+ char const *num = colon + 1;
+ *colon = '\0';
+ if (*arg)
+ name = arg;
+ if (num && (! (xstrtoumax (num, &end, 10, &u, "") == LONGINT_OK
+ && u <= field_max)))
+ invalid_num = num;
+ }
+ else
+ {
+ uintmax_t u1;
+ switch ('0' <= *arg && *arg <= '9'
+ ? xstrtoumax (arg, &end, 10, &u1, "")
+ : LONGINT_INVALID)
+ {
+ default:
+ name = arg;
+ break;
+
+ case LONGINT_OK:
+ if (u1 <= field_max)
+ {
+ u = u1;
+ break;
+ }
+ /* Fall through. */
+ case LONGINT_OVERFLOW:
+ invalid_num = arg;
+ break;
+ }
+ }
+
+ if (invalid_num)
+ FATAL_ERROR ((0, 0, "%s: %s", quotearg_colon (invalid_num),
+ _("Invalid owner or group ID")));
+ if (name)
+ *name_option = name;
+ return u;
+}
+
+#define TAR_SIZE_SUFFIXES "bBcGgkKMmPTtw"
+
+static char const *const sort_mode_arg[] = {
+ "none",
+ "name",
+#if D_INO_IN_DIRENT
+ "inode",
+#endif
+ NULL
+};
+
+static int sort_mode_flag[] = {
+ SAVEDIR_SORT_NONE,
+ SAVEDIR_SORT_NAME,
+#if D_INO_IN_DIRENT
+ SAVEDIR_SORT_INODE
+#endif
+};
+
+ARGMATCH_VERIFY (sort_mode_arg, sort_mode_flag);
+
+static char const *const hole_detection_args[] =
+{
+ "raw", "seek", NULL
+};
+
+static int const hole_detection_types[] =
+{
+ HOLE_DETECTION_RAW, HOLE_DETECTION_SEEK
+};
+
+ARGMATCH_VERIFY (hole_detection_args, hole_detection_types);
+
+
+static void
+set_old_files_option (int code, struct option_locus *loc)
+{
+ struct option_locus *prev;
+ static char const *const code_to_opt[] = {
+ "--overwrite-dir",
+ "--no-overwrite-dir",
+ "--overwrite",
+ "--unlink-first",
+ "--keep-old-files",
+ "--skip-old-files",
+ "--keep-newer-files"
+ };
+
+ prev = optloc_save (OC_OLD_FILES, loc);
+ if (prev && optloc_eq (loc, prev) && code != old_files_option)
+ option_conflict_error (code_to_opt[code], code_to_opt[old_files_option]);
+
+ old_files_option = code;
+}
+
+
+static error_t
+parse_opt (int key, char *arg, struct argp_state *state)
+{
+ struct tar_args *args = state->input;
+
+ switch (key)
+ {
+ case ARGP_KEY_ARG:
+ /* File name or non-parsed option, because of ARGP_IN_ORDER */
+ name_add_name (arg);
+ args->input_files = true;
+ break;
+
+ case 'A':
+ set_subcommand_option (CAT_SUBCOMMAND);
+ break;
+
+ case 'a':
+ args->compress_autodetect = true;
+ break;
+
+ case NO_AUTO_COMPRESS_OPTION:
+ args->compress_autodetect = false;
+ break;
+
+ case 'b':
+ {
+ uintmax_t u;
+ if (! (xstrtoumax (arg, 0, 10, &u, "") == LONGINT_OK
+ && u == (blocking_factor = u)
+ && 0 < blocking_factor
+ && u == (record_size = u * BLOCKSIZE) / BLOCKSIZE))
+ USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
+ _("Invalid blocking factor")));
+ }
+ break;
+
+ case 'B':
+ /* Try to reblock input records. For reading 4.2BSD pipes. */
+
+ /* It would surely make sense to exchange -B and -R, but it seems
+ that -B has been used for a long while in Sun tar and most
+ BSD-derived systems. This is a consequence of the block/record
+ terminology confusion. */
+
+ read_full_records_option = true;
+ break;
+
+ case 'c':
+ set_subcommand_option (CREATE_SUBCOMMAND);
+ break;
+
+ case CLAMP_MTIME_OPTION:
+ set_mtime_option = CLAMP_MTIME;
+ break;
+
+ case 'd':
+ set_subcommand_option (DIFF_SUBCOMMAND);
+ break;
+
+ case 'f':
+ if (archive_names == allocated_archive_names)
+ archive_name_array = x2nrealloc (archive_name_array,
+ &allocated_archive_names,
+ sizeof (archive_name_array[0]));
+
+ archive_name_array[archive_names++] = arg;
+ break;
+
+ case 'F':
+ /* Since -F is only useful with -M, make it implied. Run this
+ script at the end of each tape. */
+
+ info_script_option = arg;
+ multi_volume_option = true;
+ break;
+
+ case FULL_TIME_OPTION:
+ full_time_option = true;
+ break;
+
+ case 'g':
+ optloc_save (OC_LISTED_INCREMENTAL, args->loc);
+ listed_incremental_option = arg;
+ after_date_option = true;
+ /* Fall through. */
+
+ case 'G':
+ /* We are making an incremental dump (FIXME: are we?); save
+ directories at the beginning of the archive, and include in each
+ directory its contents. */
+
+ incremental_option = true;
+ break;
+
+ case 'h':
+ /* Follow symbolic links. */
+ dereference_option = true;
+ break;
+
+ case HARD_DEREFERENCE_OPTION:
+ hard_dereference_option = true;
+ break;
+
+ case 'i':
+ /* Ignore zero blocks (eofs). This can't be the default,
+ because Unix tar writes two blocks of zeros, then pads out
+ the record with garbage. */
+
+ ignore_zeros_option = true;
+ break;
+
+ case 'j':
+ set_use_compress_program_option (BZIP2_PROGRAM, args->loc);
+ break;
+
+ case 'J':
+ set_use_compress_program_option (XZ_PROGRAM, args->loc);
+ break;
+
+ case 'k':
+ /* Don't replace existing files. */
+ set_old_files_option (KEEP_OLD_FILES, args->loc);
+ break;
+
+ case 'K':
+ optloc_save (OC_STARTING_FILE, args->loc);
+ starting_file_option = true;
+ addname (arg, 0, true, NULL);
+ break;
+
+ case ONE_FILE_SYSTEM_OPTION:
+ /* When dumping directories, don't dump files/subdirectories
+ that are on other filesystems. */
+ one_file_system_option = true;
+ break;
+
+ case ONE_TOP_LEVEL_OPTION:
+ optloc_save (OC_ONE_TOP_LEVEL, args->loc);
+ one_top_level_option = true;
+ one_top_level_dir = arg;
+ break;
+
+ case 'l':
+ check_links_option = 1;
+ break;
+
+ case 'L':
+ {
+ uintmax_t u;
+ char *p;
+
+ if (xstrtoumax (arg, &p, 10, &u, TAR_SIZE_SUFFIXES) != LONGINT_OK)
+ USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
+ _("Invalid tape length")));
+ if (p > arg && !strchr (TAR_SIZE_SUFFIXES, p[-1]))
+ tape_length_option = 1024 * (tarlong) u;
+ else
+ tape_length_option = (tarlong) u;
+ multi_volume_option = true;
+ }
+ break;
+
+ case LEVEL_OPTION:
+ {
+ char *p;
+ incremental_level = strtoul (arg, &p, 10);
+ if (*p)
+ USAGE_ERROR ((0, 0, _("Invalid incremental level value")));
+ }
+ break;
+
+ case LZIP_OPTION:
+ set_use_compress_program_option (LZIP_PROGRAM, args->loc);
+ break;
+
+ case LZMA_OPTION:
+ set_use_compress_program_option (LZMA_PROGRAM, args->loc);
+ break;
+
+ case LZOP_OPTION:
+ set_use_compress_program_option (LZOP_PROGRAM, args->loc);
+ break;
+
+ case 'm':
+ touch_option = true;
+ break;
+
+ case 'M':
+ /* Make multivolume archive: when we can't write any more into
+ the archive, re-open it, and continue writing. */
+
+ multi_volume_option = true;
+ break;
+
+ case MTIME_OPTION:
+ get_date_or_file (args, "--mtime", arg, &mtime_option);
+ if (set_mtime_option == USE_FILE_MTIME)
+ set_mtime_option = FORCE_MTIME;
+ break;
+
+ case 'n':
+ seek_option = 1;
+ break;
+
+ case NO_SEEK_OPTION:
+ seek_option = 0;
+ break;
+
+ case 'N':
+ after_date_option = true;
+ /* Fall through. */
+
+ case NEWER_MTIME_OPTION:
+ if (TIME_OPTION_INITIALIZED (newer_mtime_option))
+ USAGE_ERROR ((0, 0, _("More than one threshold date")));
+ get_date_or_file (args,
+ key == NEWER_MTIME_OPTION ? "--newer-mtime"
+ : "--after-date", arg, &newer_mtime_option);
+ optloc_save (OC_NEWER, args->loc);
+ break;
+
+ case 'o':
+ args->o_option = true;
+ break;
+
+ case 'O':
+ to_stdout_option = true;
+ break;
+
+ case 'p':
+ same_permissions_option = true;
+ break;
+
+ case 'P':
+ optloc_save (OC_ABSOLUTE_NAMES, args->loc);
+ absolute_names_option = true;
+ break;
+
+ case 'r':
+ set_subcommand_option (APPEND_SUBCOMMAND);
+ break;
+
+ case 'R':
+ /* Print block numbers for debugging bad tar archives. */
+
+ /* It would surely make sense to exchange -B and -R, but it seems
+ that -B has been used for a long while in Sun tar and most
+ BSD-derived systems. This is a consequence of the block/record
+ terminology confusion. */
+
+ block_number_option = true;
+ break;
+
+ case 's':
+ /* Names to extract are sorted. */
+ optloc_save (OC_SAME_ORDER, args->loc);
+ same_order_option = true;
+ break;
+
+ case 'S':
+ sparse_option = true;
+ break;
+
+ case SKIP_OLD_FILES_OPTION:
+ set_old_files_option (SKIP_OLD_FILES, args->loc);
+ break;
+
+ case HOLE_DETECTION_OPTION:
+ hole_detection = XARGMATCH ("--hole-detection", arg,
+ hole_detection_args, hole_detection_types);
+ sparse_option = true;
+ break;
+
+ case SPARSE_VERSION_OPTION:
+ sparse_option = true;
+ {
+ char *p;
+ tar_sparse_major = strtoul (arg, &p, 10);
+ if (*p)
+ {
+ if (*p != '.')
+ USAGE_ERROR ((0, 0, _("Invalid sparse version value")));
+ tar_sparse_minor = strtoul (p + 1, &p, 10);
+ if (*p)
+ USAGE_ERROR ((0, 0, _("Invalid sparse version value")));
+ }
+ }
+ break;
+
+ case 't':
+ set_subcommand_option (LIST_SUBCOMMAND);
+ verbose_option++;
+ break;
+
+ case TEST_LABEL_OPTION:
+ set_subcommand_option (TEST_LABEL_SUBCOMMAND);
+ break;
+
+ case 'u':
+ set_subcommand_option (UPDATE_SUBCOMMAND);
+ break;
+
+ case 'U':
+ set_old_files_option (UNLINK_FIRST_OLD_FILES, args->loc);
+ break;
+
+ case UTC_OPTION:
+ utc_option = true;
+ break;
+
+ case 'v':
+ verbose_option++;
+ warning_option |= WARN_VERBOSE_WARNINGS;
+ break;
+
+ case 'V':
+ volume_label_option = arg;
+ break;
+
+ case 'w':
+ interactive_option = true;
+ break;
+
+ case 'W':
+ optloc_save (OC_VERIFY, args->loc);
+ verify_option = true;
+ break;
+
+ case 'x':
+ set_subcommand_option (EXTRACT_SUBCOMMAND);
+ break;
+
+ case 'z':
+ set_use_compress_program_option (GZIP_PROGRAM, args->loc);
+ break;
+
+ case 'Z':
+ set_use_compress_program_option (COMPRESS_PROGRAM, args->loc);
+ break;
+
+ case ATIME_PRESERVE_OPTION:
+ atime_preserve_option =
+ (arg
+ ? XARGMATCH ("--atime-preserve", arg,
+ atime_preserve_args, atime_preserve_types)
+ : replace_atime_preserve);
+ if (! O_NOATIME && atime_preserve_option == system_atime_preserve)
+ FATAL_ERROR ((0, 0,
+ _("--atime-preserve='system' is not supported"
+ " on this platform")));
+ break;
+
+ case CHECK_DEVICE_OPTION:
+ check_device_option = true;
+ break;
+
+ case NO_CHECK_DEVICE_OPTION:
+ check_device_option = false;
+ break;
+
+ case CHECKPOINT_OPTION:
+ if (arg)
+ {
+ char *p;
+
+ if (*arg == '.')
+ {
+ checkpoint_compile_action (".");
+ arg++;
+ }
+ checkpoint_option = strtoul (arg, &p, 0);
+ if (*p)
+ FATAL_ERROR ((0, 0,
+ _("--checkpoint value is not an integer")));
+ }
+ else
+ checkpoint_option = DEFAULT_CHECKPOINT;
+ break;
+
+ case CHECKPOINT_ACTION_OPTION:
+ checkpoint_compile_action (arg);
+ break;
+
+ case BACKUP_OPTION:
+ backup_option = true;
+ if (arg)
+ args->version_control_string = arg;
+ break;
+
+ case DELAY_DIRECTORY_RESTORE_OPTION:
+ delay_directory_restore_option = true;
+ break;
+
+ case NO_DELAY_DIRECTORY_RESTORE_OPTION:
+ delay_directory_restore_option = false;
+ break;
+
+ case DELETE_OPTION:
+ set_subcommand_option (DELETE_SUBCOMMAND);
+ break;
+
+ case FORCE_LOCAL_OPTION:
+ force_local_option = true;
+ break;
+
+ case 'H':
+ set_archive_format (arg);
+ break;
+
+ case INDEX_FILE_OPTION:
+ index_file_name = arg;
+ break;
+
+ case IGNORE_COMMAND_ERROR_OPTION:
+ ignore_command_error_option = true;
+ break;
+
+ case IGNORE_FAILED_READ_OPTION:
+ ignore_failed_read_option = true;
+ break;
+
+ case KEEP_DIRECTORY_SYMLINK_OPTION:
+ keep_directory_symlink_option = true;
+ break;
+
+ case KEEP_NEWER_FILES_OPTION:
+ set_old_files_option (KEEP_NEWER_FILES, args->loc);
+ break;
+
+ case GROUP_OPTION:
+ {
+ uintmax_t u = parse_owner_group (arg, TYPE_MAXIMUM (gid_t),
+ &group_name_option);
+ if (u == UINTMAX_MAX)
+ {
+ group_option = -1;
+ if (group_name_option)
+ gname_to_gid (group_name_option, &group_option);
+ }
+ else
+ group_option = u;
+ }
+ break;
+
+ case GROUP_MAP_OPTION:
+ group_map_read (arg);
+ break;
+
+ case MODE_OPTION:
+ mode_option = mode_compile (arg);
+ if (!mode_option)
+ FATAL_ERROR ((0, 0, _("Invalid mode given on option")));
+ initial_umask = umask (0);
+ umask (initial_umask);
+ break;
+
+ case NO_IGNORE_COMMAND_ERROR_OPTION:
+ ignore_command_error_option = false;
+ break;
+
+ case NO_OVERWRITE_DIR_OPTION:
+ set_old_files_option (NO_OVERWRITE_DIR_OLD_FILES, args->loc);
+ break;
+
+ case NO_QUOTE_CHARS_OPTION:
+ for (;*arg; arg++)
+ set_char_quoting (NULL, *arg, 0);
+ break;
+
+ case NUMERIC_OWNER_OPTION:
+ numeric_owner_option = true;
+ break;
+
+ case OCCURRENCE_OPTION:
+ optloc_save (OC_OCCURRENCE, args->loc);
+ if (!arg)
+ occurrence_option = 1;
+ else
+ {
+ uintmax_t u;
+ if (xstrtoumax (arg, 0, 10, &u, "") == LONGINT_OK)
+ occurrence_option = u;
+ else
+ FATAL_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
+ _("Invalid number")));
+ }
+ break;
+
+ case OLD_ARCHIVE_OPTION:
+ set_archive_format ("v7");
+ break;
+
+ case OVERWRITE_DIR_OPTION:
+ set_old_files_option (DEFAULT_OLD_FILES, args->loc);
+ break;
+
+ case OVERWRITE_OPTION:
+ set_old_files_option (OVERWRITE_OLD_FILES, args->loc);
+ break;
+
+ case OWNER_OPTION:
+ {
+ uintmax_t u = parse_owner_group (arg, TYPE_MAXIMUM (uid_t),
+ &owner_name_option);
+ if (u == UINTMAX_MAX)
+ {
+ owner_option = -1;
+ if (owner_name_option)
+ uname_to_uid (owner_name_option, &owner_option);
+ }
+ else
+ owner_option = u;
+ }
+ break;
+
+ case OWNER_MAP_OPTION:
+ owner_map_read (arg);
+ break;
+
+ case QUOTE_CHARS_OPTION:
+ for (;*arg; arg++)
+ set_char_quoting (NULL, *arg, 1);
+ break;
+
+ case QUOTING_STYLE_OPTION:
+ tar_set_quoting_style (arg);
+ break;
+
+ case PAX_OPTION:
+ {
+ char *tmp = expand_pax_option (args, arg);
+ args->pax_option = true;
+ xheader_set_option (tmp);
+ free (tmp);
+ }
+ break;
+
+ case POSIX_OPTION:
+ set_archive_format ("posix");
+ break;
+
+ case RECORD_SIZE_OPTION:
+ {
+ uintmax_t u;
+
+ if (! (xstrtoumax (arg, NULL, 10, &u, TAR_SIZE_SUFFIXES) == LONGINT_OK
+ && u == (size_t) u))
+ USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
+ _("Invalid record size")));
+ record_size = u;
+ if (record_size % BLOCKSIZE != 0)
+ USAGE_ERROR ((0, 0, _("Record size must be a multiple of %d."),
+ BLOCKSIZE));
+ blocking_factor = record_size / BLOCKSIZE;
+ }
+ break;
+
+ case RECURSIVE_UNLINK_OPTION:
+ recursive_unlink_option = true;
+ break;
+
+ case REMOVE_FILES_OPTION:
+ remove_files_option = true;
+ break;
+
+ case RESTRICT_OPTION:
+ restrict_option = true;
+ break;
+
+ case RMT_COMMAND_OPTION:
+ rmt_command = arg;
+ break;
+
+ case RSH_COMMAND_OPTION:
+ rsh_command_option = arg;
+ break;
+
+ case SHOW_DEFAULTS_OPTION:
+ {
+ char *s = format_default_settings ();
+ printf ("%s\n", s);
+ close_stdout ();
+ free (s);
+ exit (0);
+ }
+
+ case SHOW_SNAPSHOT_FIELD_RANGES_OPTION:
+ show_snapshot_field_ranges ();
+ close_stdout ();
+ exit (0);
+
+ case STRIP_COMPONENTS_OPTION:
+ {
+ uintmax_t u;
+ if (! (xstrtoumax (arg, 0, 10, &u, "") == LONGINT_OK
+ && u == (size_t) u))
+ USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
+ _("Invalid number of elements")));
+ strip_name_components = u;
+ }
+ break;
+
+ case SHOW_OMITTED_DIRS_OPTION:
+ show_omitted_dirs_option = true;
+ break;
+
+ case SHOW_TRANSFORMED_NAMES_OPTION:
+ show_transformed_names_option = true;
+ break;
+
+ case SORT_OPTION:
+ savedir_sort_order = XARGMATCH ("--sort", arg,
+ sort_mode_arg, sort_mode_flag);
+ break;
+
+ case SUFFIX_OPTION:
+ backup_option = true;
+ args->backup_suffix_string = arg;
+ break;
+
+ case TO_COMMAND_OPTION:
+ if (to_command_option)
+ USAGE_ERROR ((0, 0, _("Only one --to-command option allowed")));
+ to_command_option = arg;
+ break;
+
+ case TOTALS_OPTION:
+ if (arg)
+ set_stat_signal (arg);
+ else
+ totals_option = true;
+ break;
+
+ case TRANSFORM_OPTION:
+ set_transform_expr (arg);
+ break;
+
+ case 'I':
+ set_use_compress_program_option (arg, args->loc);
+ break;
+
+ case VOLNO_FILE_OPTION:
+ volno_file_option = arg;
+ break;
+
+ case NO_SAME_OWNER_OPTION:
+ same_owner_option = -1;
+ break;
+
+ case NO_SAME_PERMISSIONS_OPTION:
+ same_permissions_option = -1;
+ break;
+
+ case ACLS_OPTION:
+ set_archive_format ("posix");
+ acls_option = 1;
+ break;
+
+ case NO_ACLS_OPTION:
+ acls_option = -1;
+ break;
+
+ case SELINUX_CONTEXT_OPTION:
+ set_archive_format ("posix");
+ selinux_context_option = 1;
+ break;
+
+ case NO_SELINUX_CONTEXT_OPTION:
+ selinux_context_option = -1;
+ break;
+
+ case XATTR_OPTION:
+ set_xattr_option (1);
+ break;
+
+ case NO_XATTR_OPTION:
+ set_xattr_option (-1);
+ break;
+
+ case XATTR_INCLUDE:
+ case XATTR_EXCLUDE:
+ set_xattr_option (1);
+ xattrs_mask_add (arg, (key == XATTR_INCLUDE));
+ break;
+
+ case SAME_OWNER_OPTION:
+ same_owner_option = 1;
+ break;
+
+ case WARNING_OPTION:
+ set_warning_option (arg);
+ break;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+
+#ifdef DEVICE_PREFIX
+ {
+ int device = key - '0';
+ int density;
+ static char buf[sizeof DEVICE_PREFIX + 10];
+ char *cursor;
+
+ if (arg[1])
+ argp_error (state, _("Malformed density argument: %s"), quote (arg));
+
+ strcpy (buf, DEVICE_PREFIX);
+ cursor = buf + strlen (buf);
+
+#ifdef DENSITY_LETTER
+
+ sprintf (cursor, "%d%c", device, arg[0]);
+
+#else /* not DENSITY_LETTER */
+
+ switch (arg[0])
+ {
+ case 'l':
+ device += LOW_DENSITY_NUM;
+ break;
+
+ case 'm':
+ device += MID_DENSITY_NUM;
+ break;
+
+ case 'h':
+ device += HIGH_DENSITY_NUM;
+ break;
+
+ default:
+ argp_error (state, _("Unknown density: '%c'"), arg[0]);
+ }
+ sprintf (cursor, "%d", device);
+
+#endif /* not DENSITY_LETTER */
+
+ if (archive_names == allocated_archive_names)
+ archive_name_array = x2nrealloc (archive_name_array,
+ &allocated_archive_names,
+ sizeof (archive_name_array[0]));
+ archive_name_array[archive_names++] = xstrdup (buf);
+ }
+ break;
+
+#else /* not DEVICE_PREFIX */
+
+ argp_error (state,
+ _("Options '-[0-7][lmh]' not supported by *this* tar"));
+
+#endif /* not DEVICE_PREFIX */
+
+ case ARGP_KEY_ERROR:
+ if (args->loc->source == OPTS_FILE)
+ error (0, 0, _("%s:%lu: location of the error"), args->loc->name,
+ (unsigned long) args->loc->line);
+ else if (args->loc->source == OPTS_ENVIRON)
+ error (0, 0, _("error parsing %s"), args->loc->name);
+ exit (EX_USAGE);
+
+ default:
+ return ARGP_ERR_UNKNOWN;
+ }
+ return 0;
+}
+
+extern struct argp_child names_argp_children[];
+
+static struct argp argp = {
+ options,
+ parse_opt,
+ N_("[FILE]..."),
+ doc,
+ names_argp_children,
+ tar_help_filter,
+ NULL
+};
+
+void
+usage (int status)
+{
+ argp_help (&argp, stderr, ARGP_HELP_SEE, (char*) program_name);
+ close_stdout ();
+ exit (status);
+}
+
+/* Parse the options for tar. */
+
+static struct argp_option const *
+find_argp_option_key (struct argp_option const *o, int key)
+{
+ for (;
+ !(o->name == NULL
+ && o->key == 0
+ && o->arg == 0
+ && o->flags == 0
+ && o->doc == NULL); o++)
+ if (o->key == key)
+ return o;
+ return NULL;
+}
+
+static struct argp_option const *
+find_argp_option (struct argp *ap, int key)
+{
+ struct argp_option const *p = NULL;
+ struct argp_child const *child;
+
+ p = find_argp_option_key (ap->options, key);
+ if (!p && ap->children)
+ {
+ for (child = ap->children; child->argp; child++)
+ {
+ p = find_argp_option_key (child->argp->options, key);
+ if (p)
+ break;
+ }
+ }
+ return p;
+}
+
+static const char *tar_authors[] = {
+ "John Gilmore",
+ "Jay Fenlason",
+ NULL
+};
+
+/* Subcommand classes */
+#define SUBCL_READ 0x01 /* subcommand reads from the archive */
+#define SUBCL_WRITE 0x02 /* subcommand writes to the archive */
+#define SUBCL_UPDATE 0x04 /* subcommand updates existing archive */
+#define SUBCL_TEST 0x08 /* subcommand tests archive header or meta-info */
+#define SUBCL_OCCUR 0x10 /* subcommand allows the use of the occurrence
+ option */
+
+static int subcommand_class[] = {
+ /* UNKNOWN_SUBCOMMAND */ 0,
+ /* APPEND_SUBCOMMAND */ SUBCL_WRITE|SUBCL_UPDATE,
+ /* CAT_SUBCOMMAND */ SUBCL_WRITE,
+ /* CREATE_SUBCOMMAND */ SUBCL_WRITE,
+ /* DELETE_SUBCOMMAND */ SUBCL_WRITE|SUBCL_UPDATE|SUBCL_OCCUR,
+ /* DIFF_SUBCOMMAND */ SUBCL_READ|SUBCL_OCCUR,
+ /* EXTRACT_SUBCOMMAND */ SUBCL_READ|SUBCL_OCCUR,
+ /* LIST_SUBCOMMAND */ SUBCL_READ|SUBCL_OCCUR,
+ /* UPDATE_SUBCOMMAND */ SUBCL_WRITE|SUBCL_UPDATE,
+ /* TEST_LABEL_SUBCOMMAND */ SUBCL_TEST
+};
+
+/* Return t if the subcommand_option is in class(es) f */
+#define IS_SUBCOMMAND_CLASS(f) (subcommand_class[subcommand_option] & (f))
+
+static struct tar_args args;
+
+void
+more_options (int argc, char **argv, struct option_locus *loc)
+{
+ int idx;
+
+ args.loc = loc;
+ if (argp_parse (&argp, argc, argv, ARGP_IN_ORDER|ARGP_NO_EXIT, &idx, &args))
+ abort (); /* shouldn't happen */
+ if (loc->source == OPTS_ENVIRON && args.input_files)
+ USAGE_ERROR ((0, 0, _("non-option arguments in %s"), loc->name));
+}
+
+static void
+parse_default_options (void)
+{
+ char *opts = getenv ("TAR_OPTIONS");
+ struct wordsplit ws;
+ struct option_locus loc = { OPTS_ENVIRON, "TAR_OPTIONS", 0, 0 };
+
+ if (!opts)
+ return;
+
+ ws.ws_offs = 1;
+ if (wordsplit (opts, &ws, WRDSF_DEFFLAGS|WRDSF_DOOFFS))
+ FATAL_ERROR ((0, 0, _("cannot split TAR_OPTIONS: %s"),
+ wordsplit_strerror (&ws)));
+ if (ws.ws_wordc)
+ {
+ ws.ws_wordv[0] = (char*) program_name;
+ more_options (ws.ws_offs + ws.ws_wordc, ws.ws_wordv, &loc);
+ }
+
+ wordsplit_free (&ws);
+}
+
+static void
+decode_options (int argc, char **argv)
+{
+ int idx;
+ struct option_locus loc = { OPTS_COMMAND_LINE, 0, 0, 0 };
+
+ argp_version_setup ("tar", tar_authors);
+
+ /* Set some default option values. */
+ args.textual_date = NULL;
+ args.o_option = false;
+ args.pax_option = false;
+ args.backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");
+ args.version_control_string = 0;
+ args.input_files = false;
+ args.compress_autodetect = false;
+
+ subcommand_option = UNKNOWN_SUBCOMMAND;
+ archive_format = DEFAULT_FORMAT;
+ blocking_factor = DEFAULT_BLOCKING;
+ record_size = DEFAULT_BLOCKING * BLOCKSIZE;
+ excluded = new_exclude ();
+ hole_detection = HOLE_DETECTION_DEFAULT;
+
+ newer_mtime_option.tv_sec = TYPE_MINIMUM (time_t);
+ newer_mtime_option.tv_nsec = -1;
+ mtime_option.tv_sec = TYPE_MINIMUM (time_t);
+ mtime_option.tv_nsec = -1;
+ recursion_option = FNM_LEADING_DIR;
+ unquote_option = true;
+ tar_sparse_major = 1;
+ tar_sparse_minor = 0;
+
+ savedir_sort_order = SAVEDIR_SORT_NONE;
+
+ owner_option = -1; owner_name_option = NULL;
+ group_option = -1; group_name_option = NULL;
+
+ check_device_option = true;
+
+ incremental_level = -1;
+
+ seek_option = -1;
+
+ /* Convert old-style tar call by exploding option element and rearranging
+ options accordingly. */
+
+ if (argc > 1 && argv[1][0] != '-')
+ {
+ int new_argc; /* argc value for rearranged arguments */
+ char **new_argv; /* argv value for rearranged arguments */
+ char *const *in; /* cursor into original argv */
+ char **out; /* cursor into rearranged argv */
+ const char *letter; /* cursor into old option letters */
+ char buffer[3]; /* constructed option buffer */
+
+ /* Initialize a constructed option. */
+
+ buffer[0] = '-';
+ buffer[2] = '\0';
+
+ /* Allocate a new argument array, and copy program name in it. */
+
+ new_argc = argc - 1 + strlen (argv[1]);
+ new_argv = xmalloc ((new_argc + 1) * sizeof (char *));
+ in = argv;
+ out = new_argv;
+ *out++ = *in++;
+
+ /* Copy each old letter option as a separate option, and have the
+ corresponding argument moved next to it. */
+
+ for (letter = *in++; *letter; letter++)
+ {
+ struct argp_option const *opt;
+
+ buffer[1] = *letter;
+ *out++ = xstrdup (buffer);
+ opt = find_argp_option (&argp, *letter);
+ if (opt && opt->arg)
+ {
+ if (in < argv + argc)
+ *out++ = *in++;
+ else
+ USAGE_ERROR ((0, 0, _("Old option '%c' requires an argument."),
+ *letter));
+ }
+ }
+
+ /* Copy all remaining options. */
+
+ while (in < argv + argc)
+ *out++ = *in++;
+ *out = 0;
+
+ /* Replace the old option list by the new one. */
+
+ argc = new_argc;
+ argv = new_argv;
+ }
+
+ /* Parse all options and non-options as they appear. */
+ parse_default_options ();
+
+ args.loc = &loc;
+ if (argp_parse (&argp, argc, argv, ARGP_IN_ORDER, &idx, &args))
+ exit (TAREXIT_FAILURE);
+
+ /* Special handling for 'o' option:
+
+ GNU tar used to say "output old format".
+ UNIX98 tar says don't chown files after extracting (we use
+ "--no-same-owner" for this).
+
+ The old GNU tar semantics is retained when used with --create
+ option, otherwise UNIX98 semantics is assumed */
+
+ if (args.o_option)
+ {
+ if (subcommand_option == CREATE_SUBCOMMAND)
+ {
+ /* GNU Tar <= 1.13 compatibility */
+ set_archive_format ("v7");
+ }
+ else
+ {
+ /* UNIX98 compatibility */
+ same_owner_option = -1;
+ }
+ }
+
+ /* Handle operands after any "--" argument. */
+ for (; idx < argc; idx++)
+ {
+ name_add_name (argv[idx]);
+ args.input_files = true;
+ }
+
+ /* Derive option values and check option consistency. */
+
+ if (archive_format == DEFAULT_FORMAT)
+ {
+ if (args.pax_option)
+ archive_format = POSIX_FORMAT;
+ else
+ archive_format = DEFAULT_ARCHIVE_FORMAT;
+ }
+
+ if ((volume_label_option && subcommand_option == CREATE_SUBCOMMAND)
+ || incremental_option
+ || multi_volume_option
+ || sparse_option)
+ assert_format (FORMAT_MASK (OLDGNU_FORMAT)
+ | FORMAT_MASK (GNU_FORMAT)
+ | FORMAT_MASK (POSIX_FORMAT));
+
+ if (occurrence_option)
+ {
+ if (!args.input_files)
+ USAGE_ERROR ((0, 0,
+ _("--occurrence is meaningless without a file list")));
+ if (!IS_SUBCOMMAND_CLASS (SUBCL_OCCUR))
+ {
+ if (option_set_in_cl (OC_OCCURRENCE))
+ option_conflict_error ("--occurrence",
+ subcommand_string (subcommand_option));
+ else
+ occurrence_option = 0;
+ }
+ }
+
+ if (archive_names == 0)
+ {
+ /* If no archive file name given, try TAPE from the environment, or
+ else, DEFAULT_ARCHIVE from the configuration process. */
+
+ archive_names = 1;
+ archive_name_array[0] = getenv ("TAPE");
+ if (! archive_name_array[0])
+ archive_name_array[0] = DEFAULT_ARCHIVE;
+ }
+
+ /* Allow multiple archives only with '-M'. */
+
+ if (archive_names > 1 && !multi_volume_option)
+ USAGE_ERROR ((0, 0,
+ _("Multiple archive files require '-M' option")));
+
+ if (listed_incremental_option
+ && TIME_OPTION_INITIALIZED (newer_mtime_option))
+ {
+ struct option_locus *listed_loc = optloc_lookup (OC_LISTED_INCREMENTAL);
+ struct option_locus *newer_loc = optloc_lookup (OC_NEWER);
+ if (optloc_eq (listed_loc, newer_loc))
+ option_conflict_error ("--listed-incremental", "--newer");
+ else if (listed_loc->source == OPTS_COMMAND_LINE)
+ listed_incremental_option = NULL;
+ else
+ memset (&newer_mtime_option, 0, sizeof (newer_mtime_option));
+ }
+
+ if (incremental_level != -1 && !listed_incremental_option)
+ WARN ((0, 0,
+ _("--level is meaningless without --listed-incremental")));
+
+ if (volume_label_option)
+ {
+ if (archive_format == GNU_FORMAT || archive_format == OLDGNU_FORMAT)
+ {
+ size_t volume_label_max_len =
+ (sizeof current_header->header.name
+ - 1 /* for trailing '\0' */
+ - (multi_volume_option
+ ? (sizeof " Volume "
+ - 1 /* for null at end of " Volume " */
+ + INT_STRLEN_BOUND (int) /* for volume number */
+ - 1 /* for sign, as 0 <= volno */)
+ : 0));
+ if (volume_label_max_len < strlen (volume_label_option))
+ USAGE_ERROR ((0, 0,
+ ngettext ("%s: Volume label is too long (limit is %lu byte)",
+ "%s: Volume label is too long (limit is %lu bytes)",
+ volume_label_max_len),
+ quotearg_colon (volume_label_option),
+ (unsigned long) volume_label_max_len));
+ }
+ /* else FIXME
+ Label length in PAX format is limited by the volume size. */
+ }
+
+ if (verify_option)
+ {
+ if (multi_volume_option)
+ USAGE_ERROR ((0, 0, _("Cannot verify multi-volume archives")));
+ if (use_compress_program_option)
+ USAGE_ERROR ((0, 0, _("Cannot verify compressed archives")));
+ if (!IS_SUBCOMMAND_CLASS (SUBCL_WRITE))
+ {
+ if (option_set_in_cl (OC_VERIFY))
+ option_conflict_error ("--verify",
+ subcommand_string (subcommand_option));
+ else
+ verify_option = false;
+ }
+ }
+
+ if (use_compress_program_option)
+ {
+ if (multi_volume_option)
+ USAGE_ERROR ((0, 0, _("Cannot use multi-volume compressed archives")));
+ if (IS_SUBCOMMAND_CLASS (SUBCL_UPDATE))
+ USAGE_ERROR ((0, 0, _("Cannot update compressed archives")));
+ if (subcommand_option == CAT_SUBCOMMAND)
+ USAGE_ERROR ((0, 0, _("Cannot concatenate compressed archives")));
+ }
+
+ if (set_mtime_option == CLAMP_MTIME)
+ {
+ if (!TIME_OPTION_INITIALIZED (mtime_option))
+ USAGE_ERROR ((0, 0,
+ _("--clamp-mtime needs a date specified using --mtime")));
+ }
+
+ /* It is no harm to use --pax-option on non-pax archives in archive
+ reading mode. It may even be useful, since it allows to override
+ file attributes from tar headers. Therefore I allow such usage.
+ --gray */
+ if (args.pax_option
+ && archive_format != POSIX_FORMAT
+ && !IS_SUBCOMMAND_CLASS (SUBCL_READ))
+ USAGE_ERROR ((0, 0, _("--pax-option can be used only on POSIX archives")));
+
+ /* star creates non-POSIX typed archives with xattr support, so allow the
+ extra headers when reading */
+ if ((acls_option > 0)
+ && archive_format != POSIX_FORMAT
+ && !IS_SUBCOMMAND_CLASS (SUBCL_READ))
+ USAGE_ERROR ((0, 0, _("--acls can be used only on POSIX archives")));
+
+ if ((selinux_context_option > 0)
+ && archive_format != POSIX_FORMAT
+ && !IS_SUBCOMMAND_CLASS (SUBCL_READ))
+ USAGE_ERROR ((0, 0, _("--selinux can be used only on POSIX archives")));
+
+ if ((xattrs_option > 0)
+ && archive_format != POSIX_FORMAT
+ && !IS_SUBCOMMAND_CLASS (SUBCL_READ))
+ USAGE_ERROR ((0, 0, _("--xattrs can be used only on POSIX archives")));
+
+ if (starting_file_option && !IS_SUBCOMMAND_CLASS (SUBCL_READ))
+ {
+ if (option_set_in_cl (OC_STARTING_FILE))
+ option_conflict_error ("--starting-file",
+ subcommand_string (subcommand_option));
+ else
+ starting_file_option = false;
+ }
+
+ if (same_order_option && !IS_SUBCOMMAND_CLASS (SUBCL_READ))
+ {
+ if (option_set_in_cl (OC_SAME_ORDER))
+ option_conflict_error ("--same-order",
+ subcommand_string (subcommand_option));
+ else
+ same_order_option = false;
+ }
+
+ if (one_top_level_option)
+ {
+ char *base;
+
+ if (absolute_names_option)
+ {
+ struct option_locus *one_top_level_loc =
+ optloc_lookup (OC_ONE_TOP_LEVEL);
+ struct option_locus *absolute_names_loc =
+ optloc_lookup (OC_ABSOLUTE_NAMES);
+
+ if (optloc_eq (one_top_level_loc, absolute_names_loc))
+ option_conflict_error ("--one-top-level", "--absolute-names");
+ else if (one_top_level_loc->source == OPTS_COMMAND_LINE)
+ absolute_names_option = false;
+ else
+ one_top_level_option = false;
+ }
+
+ if (one_top_level_option && !one_top_level_dir)
+ {
+ /* If the user wants to guarantee that everything is under one
+ directory, determine its name now and let it be created later. */
+ base = base_name (archive_name_array[0]);
+ one_top_level_dir = strip_compression_suffix (base);
+ free (base);
+
+ if (!one_top_level_dir)
+ USAGE_ERROR ((0, 0,
+ _("Cannot deduce top-level directory name; "
+ "please set it explicitly with --one-top-level=DIR")));
+ }
+ }
+
+ /* If ready to unlink hierarchies, so we are for simpler files. */
+ if (recursive_unlink_option)
+ old_files_option = UNLINK_FIRST_OLD_FILES;
+
+ /* Flags for accessing files to be read from or copied into. POSIX says
+ O_NONBLOCK has unspecified effect on most types of files, but in
+ practice it never harms and sometimes helps. */
+ {
+ int base_open_flags =
+ (O_BINARY | O_CLOEXEC | O_NOCTTY | O_NONBLOCK
+ | (dereference_option ? 0 : O_NOFOLLOW)
+ | (atime_preserve_option == system_atime_preserve ? O_NOATIME : 0));
+ open_read_flags = O_RDONLY | base_open_flags;
+ open_searchdir_flags = O_SEARCH | O_DIRECTORY | base_open_flags;
+ }
+ fstatat_flags = dereference_option ? 0 : AT_SYMLINK_NOFOLLOW;
+
+ if (subcommand_option == TEST_LABEL_SUBCOMMAND)
+ {
+ /* --test-label is silent if the user has specified the label name to
+ compare against. */
+ if (!args.input_files)
+ verbose_option++;
+ }
+ else if (utc_option)
+ verbose_option = 2;
+
+ if (tape_length_option && tape_length_option < record_size)
+ USAGE_ERROR ((0, 0, _("Volume length cannot be less than record size")));
+
+ if (same_order_option && listed_incremental_option)
+ {
+ struct option_locus *preserve_order_loc = optloc_lookup (OC_SAME_ORDER);
+ struct option_locus *listed_incremental_loc =
+ optloc_lookup (OC_LISTED_INCREMENTAL);
+
+ if (optloc_eq (preserve_order_loc, listed_incremental_loc))
+ option_conflict_error ("--preserve-order", "--listed-incremental");
+ else if (preserve_order_loc->source == OPTS_COMMAND_LINE)
+ listed_incremental_option = false;
+ else
+ same_order_option = false;
+ }
+
+ /* Forbid using -c with no input files whatsoever. Check that '-f -',
+ explicit or implied, is used correctly. */
+
+ switch (subcommand_option)
+ {
+ case CREATE_SUBCOMMAND:
+ if (!args.input_files && !files_from_option)
+ USAGE_ERROR ((0, 0,
+ _("Cowardly refusing to create an empty archive")));
+ if (args.compress_autodetect && archive_names
+ && strcmp (archive_name_array[0], "-"))
+ set_compression_program_by_suffix (archive_name_array[0],
+ use_compress_program_option);
+ break;
+
+ case EXTRACT_SUBCOMMAND:
+ case LIST_SUBCOMMAND:
+ case DIFF_SUBCOMMAND:
+ case TEST_LABEL_SUBCOMMAND:
+ for (archive_name_cursor = archive_name_array;
+ archive_name_cursor < archive_name_array + archive_names;
+ archive_name_cursor++)
+ if (!strcmp (*archive_name_cursor, "-"))
+ request_stdin ("-f");
+ break;
+
+ case CAT_SUBCOMMAND:
+ case UPDATE_SUBCOMMAND:
+ case APPEND_SUBCOMMAND:
+ for (archive_name_cursor = archive_name_array;
+ archive_name_cursor < archive_name_array + archive_names;
+ archive_name_cursor++)
+ if (!strcmp (*archive_name_cursor, "-"))
+ USAGE_ERROR ((0, 0,
+ _("Options '-Aru' are incompatible with '-f -'")));
+
+ default:
+ break;
+ }
+
+ /* Initialize stdlis */
+ if (index_file_name)
+ {
+ stdlis = fopen (index_file_name, "w");
+ if (! stdlis)
+ open_fatal (index_file_name);
+ }
+ else
+ stdlis = to_stdout_option ? stderr : stdout;
+
+ archive_name_cursor = archive_name_array;
+
+ /* Prepare for generating backup names. */
+
+ if (args.backup_suffix_string)
+ simple_backup_suffix = xstrdup (args.backup_suffix_string);
+
+ if (backup_option)
+ {
+ backup_type = xget_version ("--backup", args.version_control_string);
+ /* No backup is needed either if explicitely disabled or if
+ the extracted files are not being written to disk. */
+ if (backup_type == no_backups || EXTRACT_OVER_PIPE)
+ backup_option = false;
+ }
+
+ checkpoint_finish_compile ();
+
+ report_textual_dates (&args);
+}
+
+
+/* Tar proper. */
+
+/* Main routine for tar. */
+int
+main (int argc, char **argv)
+{
+ set_start_time ();
+ set_program_name (argv[0]);
+
+ setlocale (LC_ALL, "");
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+
+ exit_failure = TAREXIT_FAILURE;
+ exit_status = TAREXIT_SUCCESS;
+ error_hook = checkpoint_flush_actions;
+
+ set_quoting_style (0, DEFAULT_QUOTING_STYLE);
+
+ /* Make sure we have first three descriptors available */
+ stdopen ();
+
+ /* Pre-allocate a few structures. */
+
+ allocated_archive_names = 10;
+ archive_name_array =
+ xmalloc (sizeof (const char *) * allocated_archive_names);
+ archive_names = 0;
+
+ /* System V fork+wait does not work if SIGCHLD is ignored. */
+ signal (SIGCHLD, SIG_DFL);
+
+ /* Try to disable the ability to unlink a directory. */
+ priv_set_remove_linkdir ();
+
+ /* Decode options. */
+
+ decode_options (argc, argv);
+
+ name_init ();
+
+ /* Main command execution. */
+
+ if (volno_file_option)
+ init_volume_number ();
+
+ switch (subcommand_option)
+ {
+ case UNKNOWN_SUBCOMMAND:
+ USAGE_ERROR ((0, 0,
+ _("You must specify one of the '-Acdtrux', '--delete' or '--test-label' options")));
+
+ case CAT_SUBCOMMAND:
+ case UPDATE_SUBCOMMAND:
+ case APPEND_SUBCOMMAND:
+ update_archive ();
+ break;
+
+ case DELETE_SUBCOMMAND:
+ delete_archive_members ();
+ break;
+
+ case CREATE_SUBCOMMAND:
+ create_archive ();
+ break;
+
+ case EXTRACT_SUBCOMMAND:
+ extr_init ();
+ read_and (extract_archive);
+
+ /* FIXME: should extract_finish () even if an ordinary signal is
+ received. */
+ extract_finish ();
+
+ break;
+
+ case LIST_SUBCOMMAND:
+ read_and (list_archive);
+ break;
+
+ case DIFF_SUBCOMMAND:
+ diff_init ();
+ read_and (diff_archive);
+ break;
+
+ case TEST_LABEL_SUBCOMMAND:
+ test_archive_label ();
+ }
+
+ checkpoint_finish ();
+
+ if (totals_option)
+ print_total_stats ();
+
+ if (check_links_option)
+ check_links ();
+
+ if (volno_file_option)
+ closeout_volume_number ();
+
+ /* Dispose of allocated memory, and return. */
+
+ free (archive_name_array);
+ xattrs_clear_setup ();
+ name_term ();
+
+ if (exit_status == TAREXIT_FAILURE)
+ error (0, 0, _("Exiting with failure status due to previous errors"));
+
+ if (stdlis == stdout)
+ close_stdout ();
+ else if (ferror (stderr) || fclose (stderr) != 0)
+ set_exit_status (TAREXIT_FAILURE);
+
+ return exit_status;
+}
+
+void
+tar_stat_init (struct tar_stat_info *st)
+{
+ memset (st, 0, sizeof (*st));
+}
+
+/* Close the stream or file descriptor associated with ST, and remove
+ all traces of it from ST. Return true if successful, false (with a
+ diagnostic) otherwise. */
+bool
+tar_stat_close (struct tar_stat_info *st)
+{
+ int status = (st->dirstream ? closedir (st->dirstream)
+ : 0 < st->fd ? close (st->fd)
+ : 0);
+ st->dirstream = 0;
+ st->fd = 0;
+
+ if (status == 0)
+ return true;
+ else
+ {
+ close_diag (st->orig_file_name);
+ return false;
+ }
+}
+
+void
+tar_stat_destroy (struct tar_stat_info *st)
+{
+ tar_stat_close (st);
+ xheader_xattr_free (st->xattr_map, st->xattr_map_size);
+ free (st->orig_file_name);
+ free (st->file_name);
+ free (st->link_name);
+ free (st->uname);
+ free (st->gname);
+ free (st->cntx_name);
+ free (st->acls_a_ptr);
+ free (st->acls_d_ptr);
+ free (st->sparse_map);
+ free (st->dumpdir);
+ xheader_destroy (&st->xhdr);
+ info_free_exclist (st);
+ memset (st, 0, sizeof (*st));
+}
+
+/* Format mask for all available formats that support nanosecond
+ timestamp resolution. */
+#define NS_PRECISION_FORMAT_MASK FORMAT_MASK (POSIX_FORMAT)
+
+/* Same as timespec_cmp, but ignore nanoseconds if current archive
+ format does not provide sufficient resolution. */
+int
+tar_timespec_cmp (struct timespec a, struct timespec b)
+{
+ if (!(FORMAT_MASK (current_format) & NS_PRECISION_FORMAT_MASK))
+ a.tv_nsec = b.tv_nsec = 0;
+ return timespec_cmp (a, b);
+}
+
+/* Set tar exit status to VAL, unless it is already indicating
+ a more serious condition. This relies on the fact that the
+ values of TAREXIT_ constants are ranged by severity. */
+void
+set_exit_status (int val)
+{
+ if (val > exit_status)
+ exit_status = val;
+}
diff --git a/src/tar.h b/src/tar.h
new file mode 100644
index 0000000..07b5bc1
--- /dev/null
+++ b/src/tar.h
@@ -0,0 +1,379 @@
+/* GNU tar Archive Format description.
+
+ Copyright 1988-1989, 1991-1997, 2000-2001, 2003-2007, 2012-2014, 2016
+ Free Software Foundation, Inc.
+
+ This file is part of GNU tar.
+
+ GNU tar 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.
+
+ GNU tar 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/>. */
+
+/* tar Header Block, from POSIX 1003.1-1990. */
+
+/* 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 */
+};
+
+#define TMAGIC "ustar" /* ustar and a null */
+#define TMAGLEN 6
+#define TVERSION "00" /* 00 and no null */
+#define TVERSLEN 2
+
+/* Values used in typeflag field. */
+#define REGTYPE '0' /* regular file */
+#define AREGTYPE '\0' /* regular file */
+#define LNKTYPE '1' /* link */
+#define SYMTYPE '2' /* reserved */
+#define CHRTYPE '3' /* character special */
+#define BLKTYPE '4' /* block special */
+#define DIRTYPE '5' /* directory */
+#define FIFOTYPE '6' /* FIFO special */
+#define CONTTYPE '7' /* reserved */
+
+#define XHDTYPE 'x' /* Extended header referring to the
+ next file in the archive */
+#define XGLTYPE 'g' /* Global extended header */
+
+/* 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 */
+
+/* tar Header Block, GNU extensions. */
+
+/* In GNU tar, SYMTYPE is for to symbolic links, and CONTTYPE is for
+ contiguous files, so maybe disobeying the "reserved" comment in POSIX
+ header description. I suspect these were meant to be used this way, and
+ should not have really been "reserved" in the published standards. */
+
+/* *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. */
+
+/* Descriptor for a single file hole. */
+
+struct sparse
+{ /* byte offset */
+ char offset[12]; /* 0 */
+ char numbytes[12]; /* 12 */
+ /* 24 */
+};
+
+/* 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
+
+/* 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
+ the 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 */
+};
+
+/* 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 */
+
+/* The standards committee allows only capital A through capital Z for
+ user-defined expansion. Other letters in use include:
+
+ 'A' Solaris Access Control List
+ 'E' Solaris Extended Attribute File
+ 'I' Inode only, as in 'star'
+ 'N' Obsolete GNU tar, for file names that do not fit into the main header.
+ 'X' POSIX 1003.1-2001 eXtended (VU version) */
+
+/* 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'
+
+/* This is the continuation of a file that began on another volume. */
+#define GNUTYPE_MULTIVOL 'M'
+
+/* This is for sparse files. */
+#define GNUTYPE_SPARSE 'S'
+
+/* This file is a tape/volume header. Ignore it on extraction. */
+#define GNUTYPE_VOLHDR 'V'
+
+/* Solaris extended header */
+#define SOLARIS_XHDTYPE 'X'
+
+/* 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 */
+};
+
+#define SPARSES_IN_STAR_HEADER 4
+#define SPARSES_IN_STAR_EXT_HEADER 21
+
+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;
+};
+
+/* END */
+
+
+/* tar Header Block, overall structure. */
+
+/* tar files are made in basic blocks of this size. */
+#define BLOCKSIZE 512
+
+enum archive_format
+{
+ DEFAULT_FORMAT, /* format to be decided later */
+ V7_FORMAT, /* old V7 tar format */
+ OLDGNU_FORMAT, /* GNU format as per before tar 1.12 */
+ USTAR_FORMAT, /* POSIX.1-1988 (ustar) format */
+ POSIX_FORMAT, /* POSIX.1-2001 format */
+ STAR_FORMAT, /* Star format defined in 1994 */
+ GNU_FORMAT /* Same as OLDGNU_FORMAT with one exception:
+ see FIXME note for to_chars() function
+ (create.c:189) */
+};
+
+/* Information about a sparse file. */
+struct sp_array
+{
+ off_t offset;
+ off_t numbytes;
+};
+
+struct xheader
+{
+ struct obstack *stk;
+ size_t size;
+ char *buffer;
+ uintmax_t string_length;
+};
+
+/* Information about xattrs for a file. */
+struct xattr_array
+ {
+ char *xkey;
+ char *xval_ptr;
+ size_t xval_len;
+ };
+
+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. */
+ bool had_trailing_slash; /* true if the current archive entry had a
+ trailing slash before it was normalized. */
+ char *link_name; /* name of link for the current archive entry. */
+
+ char *uname; /* user name of owner */
+ char *gname; /* group name of owner */
+
+ char *cntx_name; /* SELinux context for the current archive entry. */
+
+ char *acls_a_ptr; /* Access ACLs for the current archive entry. */
+ size_t acls_a_len; /* Access ACLs for the current archive entry. */
+
+ char *acls_d_ptr; /* Default ACLs for the current archive entry. */
+ size_t acls_d_len; /* Default ACLs for the current archive entry. */
+
+ 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 */
+
+ bool is_sparse; /* Is the file sparse */
+
+ /* For sparse files: */
+ unsigned sparse_major;
+ unsigned sparse_minor;
+ size_t sparse_map_avail; /* Index to the first unused element in
+ sparse_map array. Zero if the file is
+ not sparse */
+ size_t sparse_map_size; /* Size of the sparse map */
+ struct sp_array *sparse_map;
+
+ off_t real_size; /* The real size of sparse file */
+ int real_size_set; /* True when GNU.sparse.realsize is set in
+ archived file */
+
+ size_t xattr_map_size; /* Size of the xattr map */
+ struct xattr_array *xattr_map;
+
+ /* Extended headers */
+ struct xheader xhdr;
+
+ /* For dumpdirs */
+ bool is_dumpdir; /* Is the member a dumpdir? */
+ bool skipped; /* The member contents is already read
+ (for GNUTYPE_DUMPDIR) */
+ char *dumpdir; /* Contents of the dump directory */
+
+ /* Parent directory, if creating an archive. This is null if the
+ file is at the top level. */
+ struct tar_stat_info *parent;
+
+ /* Directory stream. If this is not null, it is in control of FD,
+ and should be closed instead of FD. */
+ DIR *dirstream;
+
+ /* File descriptor, if creating an archive, and if a directory or a
+ regular file or a contiguous file.
+
+ It is zero if no file descriptor is available, either because it
+ was never needed or because it was open and then closed to
+ conserve on file descriptors. (Standard input is never used
+ here, so zero cannot be a valid file descriptor.)
+
+ It is negative if it could not be reopened after it was closed.
+ Negate it to find out what errno was when the reopen failed. */
+ int fd;
+
+ /* Exclusion list */
+ struct exclist *exclude_list;
+};
+
+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;
+};
diff --git a/src/transform.c b/src/transform.c
new file mode 100644
index 0000000..155d369
--- /dev/null
+++ b/src/transform.c
@@ -0,0 +1,637 @@
+/* This file is part of GNU tar.
+ Copyright 2006-2008, 2013-2014, 2016 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 3, or (at your option) any later
+ version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <system.h>
+#include <regex.h>
+#include "common.h"
+
+enum transform_type
+ {
+ transform_first,
+ transform_global
+ };
+
+enum replace_segm_type
+ {
+ segm_literal, /* Literal segment */
+ segm_backref, /* Back-reference segment */
+ segm_case_ctl /* Case control segment (GNU extension) */
+ };
+
+enum case_ctl_type
+ {
+ ctl_stop, /* Stop case conversion */
+ ctl_upcase_next,/* Turn the next character to uppercase */
+ ctl_locase_next,/* Turn the next character to lowercase */
+ ctl_upcase, /* Turn the replacement to uppercase until ctl_stop */
+ ctl_locase /* Turn the replacement to lowercase until ctl_stop */
+ };
+
+struct replace_segm
+{
+ struct replace_segm *next;
+ enum replace_segm_type type;
+ union
+ {
+ struct
+ {
+ char *ptr;
+ size_t size;
+ } literal; /* type == segm_literal */
+ size_t ref; /* type == segm_backref */
+ enum case_ctl_type ctl; /* type == segm_case_ctl */
+ } v;
+};
+
+struct transform
+{
+ struct transform *next;
+ enum transform_type transform_type;
+ int flags;
+ unsigned match_number;
+ regex_t regex;
+ /* Compiled replacement expression */
+ struct replace_segm *repl_head, *repl_tail;
+ size_t segm_count; /* Number of elements in the above list */
+};
+
+
+
+static int transform_flags = XFORM_ALL;
+static struct transform *transform_head, *transform_tail;
+
+static struct transform *
+new_transform (void)
+{
+ struct transform *p = xzalloc (sizeof *p);
+ if (transform_tail)
+ transform_tail->next = p;
+ else
+ transform_head = p;
+ transform_tail = p;
+ return p;
+}
+
+static struct replace_segm *
+add_segment (struct transform *tf)
+{
+ struct replace_segm *segm = xmalloc (sizeof *segm);
+ segm->next = NULL;
+ if (tf->repl_tail)
+ tf->repl_tail->next = segm;
+ else
+ tf->repl_head = segm;
+ tf->repl_tail = segm;
+ tf->segm_count++;
+ return segm;
+}
+
+static void
+add_literal_segment (struct transform *tf, char *str, char *end)
+{
+ size_t len = end - str;
+ if (len)
+ {
+ struct replace_segm *segm = add_segment (tf);
+ segm->type = segm_literal;
+ segm->v.literal.ptr = xmalloc (len + 1);
+ memcpy (segm->v.literal.ptr, str, len);
+ segm->v.literal.ptr[len] = 0;
+ segm->v.literal.size = len;
+ }
+}
+
+static void
+add_char_segment (struct transform *tf, int chr)
+{
+ struct replace_segm *segm = add_segment (tf);
+ segm->type = segm_literal;
+ segm->v.literal.ptr = xmalloc (2);
+ segm->v.literal.ptr[0] = chr;
+ segm->v.literal.ptr[1] = 0;
+ segm->v.literal.size = 1;
+}
+
+static void
+add_backref_segment (struct transform *tf, size_t ref)
+{
+ struct replace_segm *segm = add_segment (tf);
+ segm->type = segm_backref;
+ segm->v.ref = ref;
+}
+
+static int
+parse_xform_flags (int *pflags, int c)
+{
+ switch (c)
+ {
+ case 'r':
+ *pflags |= XFORM_REGFILE;
+ break;
+
+ case 'R':
+ *pflags &= ~XFORM_REGFILE;
+ break;
+
+ case 'h':
+ *pflags |= XFORM_LINK;
+ break;
+
+ case 'H':
+ *pflags &= ~XFORM_LINK;
+ break;
+
+ case 's':
+ *pflags |= XFORM_SYMLINK;
+ break;
+
+ case 'S':
+ *pflags &= ~XFORM_SYMLINK;
+ break;
+
+ default:
+ return 1;
+ }
+ return 0;
+}
+
+static void
+add_case_ctl_segment (struct transform *tf, enum case_ctl_type ctl)
+{
+ struct replace_segm *segm = add_segment (tf);
+ segm->type = segm_case_ctl;
+ segm->v.ctl = ctl;
+}
+
+static const char *
+parse_transform_expr (const char *expr)
+{
+ int delim;
+ int i, j, rc;
+ char *str, *beg, *cur;
+ const char *p;
+ int cflags = 0;
+ struct transform *tf = new_transform ();
+
+ if (expr[0] != 's')
+ {
+ if (strncmp (expr, "flags=", 6) == 0)
+ {
+ transform_flags = 0;
+ for (expr += 6; *expr; expr++)
+ {
+ if (*expr == ';')
+ {
+ expr++;
+ break;
+ }
+ if (parse_xform_flags (&transform_flags, *expr))
+ USAGE_ERROR ((0, 0, _("Unknown transform flag: %c"),
+ *expr));
+ }
+ return expr;
+ }
+ USAGE_ERROR ((0, 0, _("Invalid transform expression")));
+ }
+
+ delim = expr[1];
+
+ /* Scan regular expression */
+ for (i = 2; expr[i] && expr[i] != delim; i++)
+ if (expr[i] == '\\' && expr[i+1])
+ i++;
+
+ if (expr[i] != delim)
+ USAGE_ERROR ((0, 0, _("Invalid transform expression")));
+
+ /* Scan replacement expression */
+ for (j = i + 1; expr[j] && expr[j] != delim; j++)
+ if (expr[j] == '\\' && expr[j+1])
+ j++;
+
+ if (expr[j] != delim)
+ USAGE_ERROR ((0, 0, _("Invalid transform expression")));
+
+ /* Check flags */
+ tf->transform_type = transform_first;
+ tf->flags = transform_flags;
+ for (p = expr + j + 1; *p && *p != ';'; p++)
+ switch (*p)
+ {
+ case 'g':
+ tf->transform_type = transform_global;
+ break;
+
+ case 'i':
+ cflags |= REG_ICASE;
+ break;
+
+ case 'x':
+ cflags |= REG_EXTENDED;
+ break;
+
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ tf->match_number = strtoul (p, (char**) &p, 0);
+ p--;
+ break;
+
+ default:
+ if (parse_xform_flags (&tf->flags, *p))
+ USAGE_ERROR ((0, 0, _("Unknown flag in transform expression: %c"),
+ *p));
+ }
+
+ if (*p == ';')
+ p++;
+
+ /* Extract and compile regex */
+ str = xmalloc (i - 1);
+ memcpy (str, expr + 2, i - 2);
+ str[i - 2] = 0;
+
+ rc = regcomp (&tf->regex, str, cflags);
+
+ if (rc)
+ {
+ char errbuf[512];
+ regerror (rc, &tf->regex, errbuf, sizeof (errbuf));
+ USAGE_ERROR ((0, 0, _("Invalid transform expression: %s"), errbuf));
+ }
+
+ if (str[0] == '^' || str[strlen (str) - 1] == '$')
+ tf->transform_type = transform_first;
+
+ free (str);
+
+ /* Extract and compile replacement expr */
+ i++;
+ str = xmalloc (j - i + 1);
+ memcpy (str, expr + i, j - i);
+ str[j - i] = 0;
+
+ for (cur = beg = str; *cur;)
+ {
+ if (*cur == '\\')
+ {
+ size_t n;
+
+ add_literal_segment (tf, beg, cur);
+ switch (*++cur)
+ {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ n = strtoul (cur, &cur, 10);
+ if (n > tf->regex.re_nsub)
+ USAGE_ERROR ((0, 0, _("Invalid transform replacement: back reference out of range")));
+ add_backref_segment (tf, n);
+ break;
+
+ case '\\':
+ add_char_segment (tf, '\\');
+ cur++;
+ break;
+
+ case 'a':
+ add_char_segment (tf, '\a');
+ cur++;
+ break;
+
+ case 'b':
+ add_char_segment (tf, '\b');
+ cur++;
+ break;
+
+ case 'f':
+ add_char_segment (tf, '\f');
+ cur++;
+ break;
+
+ case 'n':
+ add_char_segment (tf, '\n');
+ cur++;
+ break;
+
+ case 'r':
+ add_char_segment (tf, '\r');
+ cur++;
+ break;
+
+ case 't':
+ add_char_segment (tf, '\t');
+ cur++;
+ break;
+
+ case 'v':
+ add_char_segment (tf, '\v');
+ cur++;
+ break;
+
+ case '&':
+ add_char_segment (tf, '&');
+ cur++;
+ break;
+
+ case 'L':
+ /* Turn the replacement to lowercase until a '\U' or '\E'
+ is found, */
+ add_case_ctl_segment (tf, ctl_locase);
+ cur++;
+ break;
+
+ case 'l':
+ /* Turn the next character to lowercase, */
+ add_case_ctl_segment (tf, ctl_locase_next);
+ cur++;
+ break;
+
+ case 'U':
+ /* Turn the replacement to uppercase until a '\L' or '\E'
+ is found, */
+ add_case_ctl_segment (tf, ctl_upcase);
+ cur++;
+ break;
+
+ case 'u':
+ /* Turn the next character to uppercase, */
+ add_case_ctl_segment (tf, ctl_upcase_next);
+ cur++;
+ break;
+
+ case 'E':
+ /* Stop case conversion started by '\L' or '\U'. */
+ add_case_ctl_segment (tf, ctl_stop);
+ cur++;
+ break;
+
+ default:
+ if (*cur == delim)
+ add_char_segment (tf, delim);
+ else
+ {
+ char buf[2];
+ buf[0] = '\\';
+ buf[1] = *cur;
+ add_literal_segment (tf, buf, buf + 2);
+ }
+ cur++;
+ break;
+ }
+ beg = cur;
+ }
+ else if (*cur == '&')
+ {
+ add_literal_segment (tf, beg, cur);
+ add_backref_segment (tf, 0);
+ beg = ++cur;
+ }
+ else
+ cur++;
+ }
+ add_literal_segment (tf, beg, cur);
+
+ return p;
+}
+
+void
+set_transform_expr (const char *expr)
+{
+ while (*expr)
+ expr = parse_transform_expr (expr);
+}
+
+/* Run case conversion specified by CASE_CTL on array PTR of SIZE
+ characters. Returns pointer to statically allocated storage. */
+static char *
+run_case_conv (enum case_ctl_type case_ctl, char *ptr, size_t size)
+{
+ static char *case_ctl_buffer;
+ static size_t case_ctl_bufsize;
+ char *p;
+
+ if (case_ctl_bufsize < size)
+ {
+ case_ctl_bufsize = size;
+ case_ctl_buffer = xrealloc (case_ctl_buffer, case_ctl_bufsize);
+ }
+ memcpy (case_ctl_buffer, ptr, size);
+ switch (case_ctl)
+ {
+ case ctl_upcase_next:
+ case_ctl_buffer[0] = toupper ((unsigned char) case_ctl_buffer[0]);
+ break;
+
+ case ctl_locase_next:
+ case_ctl_buffer[0] = tolower ((unsigned char) case_ctl_buffer[0]);
+ break;
+
+ case ctl_upcase:
+ for (p = case_ctl_buffer; p < case_ctl_buffer + size; p++)
+ *p = toupper ((unsigned char) *p);
+ break;
+
+ case ctl_locase:
+ for (p = case_ctl_buffer; p < case_ctl_buffer + size; p++)
+ *p = tolower ((unsigned char) *p);
+ break;
+
+ case ctl_stop:
+ break;
+ }
+ return case_ctl_buffer;
+}
+
+
+static struct obstack stk;
+static bool stk_init;
+
+static void
+_single_transform_name_to_obstack (struct transform *tf, char *input)
+{
+ regmatch_t *rmp;
+ int rc;
+ size_t nmatches = 0;
+ enum case_ctl_type case_ctl = ctl_stop, /* Current case conversion op */
+ save_ctl = ctl_stop; /* Saved case_ctl for \u and \l */
+
+ /* Reset case conversion after a single-char operation */
+#define CASE_CTL_RESET() if (case_ctl == ctl_upcase_next \
+ || case_ctl == ctl_locase_next) \
+ { \
+ case_ctl = save_ctl; \
+ save_ctl = ctl_stop; \
+ }
+
+ rmp = xmalloc ((tf->regex.re_nsub + 1) * sizeof (*rmp));
+
+ while (*input)
+ {
+ size_t disp;
+ char *ptr;
+
+ rc = regexec (&tf->regex, input, tf->regex.re_nsub + 1, rmp, 0);
+
+ if (rc == 0)
+ {
+ struct replace_segm *segm;
+
+ disp = rmp[0].rm_eo;
+
+ if (rmp[0].rm_so)
+ obstack_grow (&stk, input, rmp[0].rm_so);
+
+ nmatches++;
+ if (tf->match_number && nmatches < tf->match_number)
+ {
+ obstack_grow (&stk, input, disp);
+ input += disp;
+ continue;
+ }
+
+ for (segm = tf->repl_head; segm; segm = segm->next)
+ {
+ switch (segm->type)
+ {
+ case segm_literal: /* Literal segment */
+ if (case_ctl == ctl_stop)
+ ptr = segm->v.literal.ptr;
+ else
+ {
+ ptr = run_case_conv (case_ctl,
+ segm->v.literal.ptr,
+ segm->v.literal.size);
+ CASE_CTL_RESET();
+ }
+ obstack_grow (&stk, ptr, segm->v.literal.size);
+ break;
+
+ case segm_backref: /* Back-reference segment */
+ if (rmp[segm->v.ref].rm_so != -1
+ && rmp[segm->v.ref].rm_eo != -1)
+ {
+ size_t size = rmp[segm->v.ref].rm_eo
+ - rmp[segm->v.ref].rm_so;
+ ptr = input + rmp[segm->v.ref].rm_so;
+ if (case_ctl != ctl_stop)
+ {
+ ptr = run_case_conv (case_ctl, ptr, size);
+ CASE_CTL_RESET();
+ }
+
+ obstack_grow (&stk, ptr, size);
+ }
+ break;
+
+ case segm_case_ctl:
+ switch (segm->v.ctl)
+ {
+ case ctl_upcase_next:
+ case ctl_locase_next:
+ switch (save_ctl)
+ {
+ case ctl_stop:
+ case ctl_upcase:
+ case ctl_locase:
+ save_ctl = case_ctl;
+ default:
+ break;
+ }
+ /*FALL THROUGH*/
+
+ case ctl_upcase:
+ case ctl_locase:
+ case ctl_stop:
+ case_ctl = segm->v.ctl;
+ }
+ }
+ }
+ }
+ else
+ {
+ disp = strlen (input);
+ obstack_grow (&stk, input, disp);
+ }
+
+ input += disp;
+
+ if (tf->transform_type == transform_first)
+ {
+ obstack_grow (&stk, input, strlen (input));
+ break;
+ }
+ }
+
+ obstack_1grow (&stk, 0);
+ free (rmp);
+}
+
+static bool
+_transform_name_to_obstack (int flags, char *input, char **output)
+{
+ struct transform *tf;
+ bool alloced = false;
+
+ if (!stk_init)
+ {
+ obstack_init (&stk);
+ stk_init = true;
+ }
+
+ for (tf = transform_head; tf; tf = tf->next)
+ {
+ if (tf->flags & flags)
+ {
+ _single_transform_name_to_obstack (tf, input);
+ input = obstack_finish (&stk);
+ alloced = true;
+ }
+ }
+ *output = input;
+ return alloced;
+}
+
+bool
+transform_name_fp (char **pinput, int flags,
+ char *(*fun)(char *, void *), void *dat)
+{
+ char *str;
+ bool ret = _transform_name_to_obstack (flags, *pinput, &str);
+ if (ret)
+ {
+ assign_string (pinput, fun ? fun (str, dat) : str);
+ obstack_free (&stk, str);
+ }
+ else if (fun)
+ {
+ *pinput = NULL;
+ assign_string (pinput, fun (str, dat));
+ free (str);
+ ret = true;
+ }
+ return ret;
+}
+
+bool
+transform_name (char **pinput, int type)
+{
+ return transform_name_fp (pinput, type, NULL, NULL);
+}
+
+bool
+transform_program_p (void)
+{
+ return transform_head != NULL;
+}
diff --git a/src/unlink.c b/src/unlink.c
new file mode 100644
index 0000000..6ae51ce
--- /dev/null
+++ b/src/unlink.c
@@ -0,0 +1,237 @@
+/* Unlink files.
+
+ Copyright 2009, 2013-2014, 2016 Free Software Foundation, Inc.
+
+ This file is part of GNU tar.
+
+ GNU tar 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.
+
+ GNU tar is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <system.h>
+#include "common.h"
+#include <quotearg.h>
+
+struct deferred_unlink
+ {
+ struct deferred_unlink *next; /* Next unlink in the queue */
+ int dir_idx; /* Directory index in wd */
+ char *file_name; /* Name of the file to unlink, relative
+ to dir_idx */
+ bool is_dir; /* True if file_name is a directory */
+ off_t records_written; /* Number of records written when this
+ entry got added to the queue */
+ };
+
+#define IS_CWD(p) \
+ ((p)->is_dir \
+ && ((p)->file_name[0] == 0 || strcmp ((p)->file_name, ".") == 0))
+
+/* The unlink queue */
+static struct deferred_unlink *dunlink_head, *dunlink_tail;
+
+/* Number of entries in the queue */
+static size_t dunlink_count;
+
+/* List of entries available for allocation */
+static struct deferred_unlink *dunlink_avail;
+
+/* Delay (number of records written) between adding entry to the
+ list and its actual removal. */
+static size_t deferred_unlink_delay = 0;
+
+static struct deferred_unlink *
+dunlink_alloc (void)
+{
+ struct deferred_unlink *p;
+ if (dunlink_avail)
+ {
+ p = dunlink_avail;
+ dunlink_avail = p->next;
+ p->next = NULL;
+ }
+ else
+ p = xmalloc (sizeof (*p));
+ return p;
+}
+
+static void
+dunlink_insert (struct deferred_unlink *anchor, struct deferred_unlink *p)
+{
+ if (anchor)
+ {
+ p->next = anchor->next;
+ anchor->next = p;
+ }
+ else
+ {
+ p->next = dunlink_head;
+ dunlink_head = p;
+ }
+ if (!p->next)
+ dunlink_tail = p;
+ dunlink_count++;
+}
+
+static void
+dunlink_reclaim (struct deferred_unlink *p)
+{
+ free (p->file_name);
+ p->next = dunlink_avail;
+ dunlink_avail = p;
+}
+
+static void
+flush_deferred_unlinks (bool force)
+{
+ struct deferred_unlink *p, *prev = NULL;
+ int saved_chdir = chdir_current;
+
+ for (p = dunlink_head; p; )
+ {
+ struct deferred_unlink *next = p->next;
+
+ if (force
+ || records_written > p->records_written + deferred_unlink_delay)
+ {
+ chdir_do (p->dir_idx);
+ if (p->is_dir)
+ {
+ const char *fname;
+
+ if (p->dir_idx && IS_CWD (p))
+ {
+ prev = p;
+ p = next;
+ continue;
+ }
+ else
+ fname = p->file_name;
+
+ if (unlinkat (chdir_fd, fname, AT_REMOVEDIR) != 0)
+ {
+ switch (errno)
+ {
+ case ENOENT:
+ /* nothing to worry about */
+ break;
+ case EEXIST:
+ /* OpenSolaris >=10 sets EEXIST instead of ENOTEMPTY
+ if trying to remove a non-empty directory */
+ case ENOTEMPTY:
+ /* Keep the record in list, in the hope we'll
+ be able to remove it later */
+ prev = p;
+ p = next;
+ continue;
+
+ default:
+ rmdir_error (fname);
+ }
+ }
+ }
+ else
+ {
+ if (unlinkat (chdir_fd, p->file_name, 0) != 0 && errno != ENOENT)
+ unlink_error (p->file_name);
+ }
+ dunlink_reclaim (p);
+ dunlink_count--;
+ p = next;
+ if (prev)
+ prev->next = p;
+ else
+ dunlink_head = p;
+ }
+ else
+ {
+ prev = p;
+ p = next;
+ }
+ }
+ if (!dunlink_head)
+ dunlink_tail = NULL;
+ else if (force)
+ {
+ for (p = dunlink_head; p; )
+ {
+ struct deferred_unlink *next = p->next;
+ const char *fname;
+
+ chdir_do (p->dir_idx);
+ if (p->dir_idx && IS_CWD (p))
+ {
+ fname = tar_dirname ();
+ chdir_do (p->dir_idx - 1);
+ }
+ else
+ fname = p->file_name;
+
+ if (unlinkat (chdir_fd, fname, AT_REMOVEDIR) != 0)
+ {
+ if (errno != ENOENT)
+ rmdir_error (fname);
+ }
+ dunlink_reclaim (p);
+ dunlink_count--;
+ p = next;
+ }
+ dunlink_head = dunlink_tail = NULL;
+ }
+
+ chdir_do (saved_chdir);
+}
+
+void
+finish_deferred_unlinks (void)
+{
+ flush_deferred_unlinks (true);
+
+ while (dunlink_avail)
+ {
+ struct deferred_unlink *next = dunlink_avail->next;
+ free (dunlink_avail);
+ dunlink_avail = next;
+ }
+}
+
+void
+queue_deferred_unlink (const char *name, bool is_dir)
+{
+ struct deferred_unlink *p;
+
+ if (dunlink_head
+ && records_written > dunlink_head->records_written + deferred_unlink_delay)
+ flush_deferred_unlinks (false);
+
+ p = dunlink_alloc ();
+ p->next = NULL;
+ p->dir_idx = chdir_current;
+ p->file_name = xstrdup (name);
+ normalize_filename_x (p->file_name);
+ p->is_dir = is_dir;
+ p->records_written = records_written;
+
+ if (IS_CWD (p))
+ {
+ struct deferred_unlink *q, *prev;
+ for (q = dunlink_head, prev = NULL; q; prev = q, q = q->next)
+ if (IS_CWD (q) && q->dir_idx < p->dir_idx)
+ break;
+ if (q)
+ dunlink_insert (prev, p);
+ else
+ dunlink_insert (dunlink_tail, p);
+ }
+ else
+ dunlink_insert (dunlink_tail, p);
+}
diff --git a/src/update.c b/src/update.c
new file mode 100644
index 0000000..ad7a6bf
--- /dev/null
+++ b/src/update.c
@@ -0,0 +1,234 @@
+/* Update a tar archive.
+
+ Copyright 1988, 1992, 1994, 1996-1997, 1999-2001, 2003-2005, 2007,
+ 2010, 2013-2014, 2016 Free Software Foundation, Inc.
+
+ This file is part of GNU tar.
+
+ GNU tar 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.
+
+ GNU tar 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/>. */
+
+/* Implement the 'r', 'u' and 'A' options for tar. 'A' means that the
+ file names are tar files, and they should simply be appended to the end
+ of the archive. No attempt is made to record the reads from the args; if
+ they're on raw tape or something like that, it'll probably lose... */
+
+#include <system.h>
+#include <quotearg.h>
+#include "common.h"
+
+/* FIXME: This module should not directly handle the following variable,
+ instead, this should be done in buffer.c only. */
+extern union block *current_block;
+
+/* We've hit the end of the old stuff, and its time to start writing new
+ stuff to the tape. This involves seeking back one record and
+ re-writing the current record (which has been changed).
+ FIXME: Either eliminate it or move it to common.h.
+*/
+bool time_to_start_writing;
+
+/* Pointer to where we started to write in the first record we write out.
+ This is used if we can't backspace the output and have to null out the
+ first part of the record. */
+char *output_start;
+
+/* Catenate file FILE_NAME to the archive without creating a header for it.
+ It had better be a tar file or the archive is screwed. */
+static void
+append_file (char *file_name)
+{
+ int handle = openat (chdir_fd, file_name, O_RDONLY | O_BINARY);
+ struct stat stat_data;
+
+ if (handle < 0)
+ {
+ open_error (file_name);
+ return;
+ }
+
+ if (fstat (handle, &stat_data) != 0)
+ stat_error (file_name);
+ else
+ {
+ off_t bytes_left = stat_data.st_size;
+
+ while (bytes_left > 0)
+ {
+ union block *start = find_next_block ();
+ size_t buffer_size = available_space_after (start);
+ size_t status;
+ char buf[UINTMAX_STRSIZE_BOUND];
+
+ if (bytes_left < buffer_size)
+ {
+ buffer_size = bytes_left;
+ status = buffer_size % BLOCKSIZE;
+ if (status)
+ memset (start->buffer + bytes_left, 0, BLOCKSIZE - status);
+ }
+
+ status = safe_read (handle, start->buffer, buffer_size);
+ if (status == SAFE_READ_ERROR)
+ read_fatal_details (file_name, stat_data.st_size - bytes_left,
+ buffer_size);
+ if (status == 0)
+ FATAL_ERROR ((0, 0,
+ ngettext ("%s: File shrank by %s byte",
+ "%s: File shrank by %s bytes",
+ bytes_left),
+ quotearg_colon (file_name),
+ STRINGIFY_BIGINT (bytes_left, buf)));
+
+ bytes_left -= status;
+
+ set_next_block_after (start + (status - 1) / BLOCKSIZE);
+ }
+ }
+
+ if (close (handle) != 0)
+ close_error (file_name);
+}
+
+/* Implement the 'r' (add files to end of archive), and 'u' (add files
+ to end of archive if they aren't there, or are more up to date than
+ the version in the archive) commands. */
+void
+update_archive (void)
+{
+ enum read_header previous_status = HEADER_STILL_UNREAD;
+ bool found_end = false;
+
+ name_gather ();
+ open_archive (ACCESS_UPDATE);
+ buffer_write_global_xheader ();
+
+ while (!found_end)
+ {
+ enum read_header status = read_header (&current_header,
+ &current_stat_info,
+ read_header_auto);
+
+ switch (status)
+ {
+ case HEADER_STILL_UNREAD:
+ case HEADER_SUCCESS_EXTENDED:
+ abort ();
+
+ case HEADER_SUCCESS:
+ {
+ struct name *name;
+
+ decode_header (current_header, &current_stat_info,
+ &current_format, 0);
+ transform_stat_info (current_header->header.typeflag,
+ &current_stat_info);
+ archive_format = current_format;
+
+ if (subcommand_option == UPDATE_SUBCOMMAND
+ && (name = name_scan (current_stat_info.file_name)) != NULL)
+ {
+ struct stat s;
+
+ chdir_do (name->change_dir);
+ if (deref_stat (current_stat_info.file_name, &s) == 0)
+ {
+ if (S_ISDIR (s.st_mode))
+ {
+ char *p, *dirp = tar_savedir (name->name, 1);
+ if (dirp)
+ {
+ namebuf_t nbuf = namebuf_create (name->name);
+
+ for (p = dirp; *p; p += strlen (p) + 1)
+ addname (namebuf_name (nbuf, p),
+ 0, false, NULL);
+
+ namebuf_free (nbuf);
+ free (dirp);
+
+ remname (name);
+ }
+ }
+ else if (tar_timespec_cmp (get_stat_mtime (&s),
+ current_stat_info.mtime)
+ <= 0)
+ remname (name);
+ }
+ }
+
+ skip_member ();
+ break;
+ }
+
+ case HEADER_ZERO_BLOCK:
+ current_block = current_header;
+ found_end = true;
+ break;
+
+ case HEADER_END_OF_FILE:
+ found_end = true;
+ break;
+
+ case HEADER_FAILURE:
+ set_next_block_after (current_header);
+ switch (previous_status)
+ {
+ case HEADER_STILL_UNREAD:
+ WARN ((0, 0, _("This does not look like a tar archive")));
+ /* Fall through. */
+
+ case HEADER_SUCCESS:
+ case HEADER_ZERO_BLOCK:
+ ERROR ((0, 0, _("Skipping to next header")));
+ /* Fall through. */
+
+ case HEADER_FAILURE:
+ break;
+
+ case HEADER_END_OF_FILE:
+ case HEADER_SUCCESS_EXTENDED:
+ abort ();
+ }
+ break;
+ }
+
+ tar_stat_destroy (&current_stat_info);
+ previous_status = status;
+ }
+
+ reset_eof ();
+ time_to_start_writing = true;
+ output_start = current_block->buffer;
+
+ {
+ struct name const *p;
+ while ((p = name_from_list ()) != NULL)
+ {
+ char *file_name = p->name;
+ if (excluded_name (file_name, NULL))
+ continue;
+ if (interactive_option && !confirm ("add", file_name))
+ continue;
+ if (subcommand_option == CAT_SUBCOMMAND)
+ append_file (file_name);
+ else
+ dump_file (0, file_name, file_name);
+ }
+ }
+
+ write_eot ();
+ close_archive ();
+ finish_deferred_unlinks ();
+ names_notfound ();
+}
diff --git a/src/utf8.c b/src/utf8.c
new file mode 100644
index 0000000..195fef5
--- /dev/null
+++ b/src/utf8.c
@@ -0,0 +1,99 @@
+/* Charset handling for GNU tar.
+
+ Copyright 2004, 2006-2007, 2013-2014, 2016 Free Software Foundation,
+ Inc.
+
+ This file is part of GNU tar.
+
+ GNU tar 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.
+
+ GNU tar is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <system.h>
+#include <quotearg.h>
+#include <localcharset.h>
+#include "common.h"
+#ifdef HAVE_ICONV_H
+# include <iconv.h>
+#endif
+
+#ifndef ICONV_CONST
+# define ICONV_CONST
+#endif
+
+#ifndef HAVE_ICONV
+
+# undef iconv_open
+# define iconv_open(tocode, fromcode) ((iconv_t) -1)
+
+# undef iconv
+# define iconv(cd, inbuf, inbytesleft, outbuf, outbytesleft) ((size_t) 0)
+
+# undef iconv_close
+# define iconv_close(cd) 0
+
+#endif
+
+
+
+
+static iconv_t conv_desc[2] = { (iconv_t) -1, (iconv_t) -1 };
+
+static iconv_t
+utf8_init (bool to_utf)
+{
+ if (conv_desc[(int) to_utf] == (iconv_t) -1)
+ {
+ if (to_utf)
+ conv_desc[(int) to_utf] = iconv_open ("UTF-8", locale_charset ());
+ else
+ conv_desc[(int) to_utf] = iconv_open (locale_charset (), "UTF-8");
+ }
+ return conv_desc[(int) to_utf];
+}
+
+bool
+utf8_convert (bool to_utf, char const *input, char **output)
+{
+ char ICONV_CONST *ib;
+ char *ob;
+ size_t inlen;
+ size_t outlen;
+ size_t rc;
+ iconv_t cd = utf8_init (to_utf);
+
+ if (cd == 0)
+ {
+ *output = xstrdup (input);
+ return true;
+ }
+ else if (cd == (iconv_t)-1)
+ return false;
+
+ inlen = strlen (input) + 1;
+ outlen = inlen * MB_LEN_MAX + 1;
+ ob = *output = xmalloc (outlen);
+ ib = (char ICONV_CONST *) input;
+ rc = iconv (cd, &ib, &inlen, &ob, &outlen);
+ *ob = 0;
+ return rc != -1;
+}
+
+
+bool
+string_ascii_p (char const *p)
+{
+ for (; *p; p++)
+ if (*p & ~0x7f)
+ return false;
+ return true;
+}
diff --git a/src/warning.c b/src/warning.c
new file mode 100644
index 0000000..86a0fd1
--- /dev/null
+++ b/src/warning.c
@@ -0,0 +1,107 @@
+/* Warnings for GNU tar.
+
+ Copyright 2009, 2012-2014, 2016 Free Software Foundation, Inc.
+
+ This file is part of GNU tar.
+
+ GNU tar 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.
+
+ GNU tar is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <system.h>
+#include <argmatch.h>
+
+#include "common.h"
+
+static char const *const warning_args[] = {
+ "all",
+ "alone-zero-block",
+ "bad-dumpdir",
+ "cachedir",
+ "contiguous-cast",
+ "file-changed",
+ "file-ignored",
+ "file-removed",
+ "file-shrank",
+ "file-unchanged",
+ "filename-with-nuls",
+ "ignore-archive",
+ "ignore-newer",
+ "new-directory",
+ "rename-directory",
+ "symlink-cast",
+ "timestamp",
+ "unknown-cast",
+ "unknown-keyword",
+ "xdev",
+ "decompress-program",
+ "existing-file",
+ "xattr-write",
+ "record-size",
+ NULL
+};
+
+static int warning_types[] = {
+ WARN_ALL,
+ WARN_ALONE_ZERO_BLOCK,
+ WARN_BAD_DUMPDIR,
+ WARN_CACHEDIR,
+ WARN_CONTIGUOUS_CAST,
+ WARN_FILE_CHANGED,
+ WARN_FILE_IGNORED,
+ WARN_FILE_REMOVED,
+ WARN_FILE_SHRANK,
+ WARN_FILE_UNCHANGED,
+ WARN_FILENAME_WITH_NULS,
+ WARN_IGNORE_ARCHIVE,
+ WARN_IGNORE_NEWER,
+ WARN_NEW_DIRECTORY,
+ WARN_RENAME_DIRECTORY,
+ WARN_SYMLINK_CAST,
+ WARN_TIMESTAMP,
+ WARN_UNKNOWN_CAST,
+ WARN_UNKNOWN_KEYWORD,
+ WARN_XDEV,
+ WARN_DECOMPRESS_PROGRAM,
+ WARN_EXISTING_FILE,
+ WARN_XATTR_WRITE,
+ WARN_RECORD_SIZE
+};
+
+ARGMATCH_VERIFY (warning_args, warning_types);
+
+int warning_option = WARN_ALL;
+
+void
+set_warning_option (const char *arg)
+{
+ int negate = 0;
+ int option;
+
+ if (strcmp (arg, "none") == 0)
+ {
+ warning_option = 0;
+ return;
+ }
+ if (strlen (arg) > 2 && memcmp (arg, "no-", 3) == 0)
+ {
+ negate = 1;
+ arg += 3;
+ }
+
+ option = XARGMATCH ("--warning", arg,
+ warning_args, warning_types);
+ if (negate)
+ warning_option &= ~option;
+ else
+ warning_option |= option;
+}
diff --git a/src/xattrs.c b/src/xattrs.c
new file mode 100644
index 0000000..8e56168
--- /dev/null
+++ b/src/xattrs.c
@@ -0,0 +1,755 @@
+/* Support for extended attributes.
+
+ Copyright (C) 2006-2014, 2016 Free Software Foundation, Inc.
+
+ This file is part of GNU tar.
+
+ GNU tar 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.
+
+ GNU tar 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/>.
+
+ Written by James Antill, on 2006-07-27. */
+
+#include <config.h>
+#include <system.h>
+
+#include <fnmatch.h>
+#include <quotearg.h>
+
+#include "common.h"
+
+#include "xattr-at.h"
+#include "selinux-at.h"
+
+struct xattrs_mask_map
+{
+ const char **masks;
+ size_t size;
+ size_t used;
+};
+
+/* list of fnmatch patterns */
+static struct
+{
+ /* lists of fnmatch patterns */
+ struct xattrs_mask_map incl;
+ struct xattrs_mask_map excl;
+} xattrs_setup;
+
+/* disable posix acls when problem found in gnulib script m4/acl.m4 */
+#if ! USE_ACL
+# undef HAVE_POSIX_ACLS
+#endif
+
+#ifdef HAVE_POSIX_ACLS
+# include "acl.h"
+# include <sys/acl.h>
+#endif
+
+#ifdef HAVE_POSIX_ACLS
+
+/* acl-at wrappers, TODO: move to gnulib in future? */
+static acl_t acl_get_file_at (int, const char *, acl_type_t);
+static int acl_set_file_at (int, const char *, acl_type_t, acl_t);
+static int file_has_acl_at (int, char const *, struct stat const *);
+static int acl_delete_def_file_at (int, char const *);
+
+/* acl_get_file_at */
+#define AT_FUNC_NAME acl_get_file_at
+#define AT_FUNC_RESULT acl_t
+#define AT_FUNC_FAIL (acl_t)NULL
+#define AT_FUNC_F1 acl_get_file
+#define AT_FUNC_POST_FILE_PARAM_DECLS , acl_type_t type
+#define AT_FUNC_POST_FILE_ARGS , type
+#include "at-func.c"
+#undef AT_FUNC_NAME
+#undef AT_FUNC_F1
+#undef AT_FUNC_RESULT
+#undef AT_FUNC_FAIL
+#undef AT_FUNC_POST_FILE_PARAM_DECLS
+#undef AT_FUNC_POST_FILE_ARGS
+
+/* acl_set_file_at */
+#define AT_FUNC_NAME acl_set_file_at
+#define AT_FUNC_F1 acl_set_file
+#define AT_FUNC_POST_FILE_PARAM_DECLS , acl_type_t type, acl_t acl
+#define AT_FUNC_POST_FILE_ARGS , type, acl
+#include "at-func.c"
+#undef AT_FUNC_NAME
+#undef AT_FUNC_F1
+#undef AT_FUNC_POST_FILE_PARAM_DECLS
+#undef AT_FUNC_POST_FILE_ARGS
+
+/* acl_delete_def_file_at */
+#define AT_FUNC_NAME acl_delete_def_file_at
+#define AT_FUNC_F1 acl_delete_def_file
+#define AT_FUNC_POST_FILE_PARAM_DECLS
+#define AT_FUNC_POST_FILE_ARGS
+#include "at-func.c"
+#undef AT_FUNC_NAME
+#undef AT_FUNC_F1
+#undef AT_FUNC_POST_FILE_PARAM_DECLS
+#undef AT_FUNC_POST_FILE_ARGS
+
+/* gnulib file_has_acl_at */
+#define AT_FUNC_NAME file_has_acl_at
+#define AT_FUNC_F1 file_has_acl
+#define AT_FUNC_POST_FILE_PARAM_DECLS , struct stat const *st
+#define AT_FUNC_POST_FILE_ARGS , st
+#include "at-func.c"
+#undef AT_FUNC_NAME
+#undef AT_FUNC_F1
+#undef AT_FUNC_POST_FILE_PARAM_DECLS
+#undef AT_FUNC_POST_FILE_ARGS
+
+/* convert unix permissions into an ACL ... needed due to "default" ACLs */
+static acl_t
+perms2acl (int perms)
+{
+ char val[] = "user::---,group::---,other::---";
+ /* 0123456789 123456789 123456789 123456789 */
+
+ /* user */
+ if (perms & 0400)
+ val[6] = 'r';
+ if (perms & 0200)
+ val[7] = 'w';
+ if (perms & 0100)
+ val[8] = 'x';
+
+ /* group */
+ if (perms & 0040)
+ val[17] = 'r';
+ if (perms & 0020)
+ val[18] = 'w';
+ if (perms & 0010)
+ val[19] = 'x';
+
+ /* other */
+ if (perms & 0004)
+ val[28] = 'r';
+ if (perms & 0002)
+ val[29] = 'w';
+ if (perms & 0001)
+ val[30] = 'x';
+
+ return acl_from_text (val);
+}
+
+static char *
+skip_to_ext_fields (char *ptr)
+{
+ /* skip tag name (user/group/default/mask) */
+ ptr += strcspn (ptr, ":,\n");
+
+ if (*ptr != ':')
+ return ptr;
+ ++ptr;
+
+ ptr += strcspn (ptr, ":,\n"); /* skip user/group name */
+
+ if (*ptr != ':')
+ return ptr;
+ ++ptr;
+
+ ptr += strcspn (ptr, ":,\n"); /* skip perms */
+
+ return ptr;
+}
+
+/* The POSIX draft allows extra fields after the three main ones. Star
+ uses this to add a fourth field for user/group which is the numeric ID.
+ This function removes such extra fields by overwriting them with the
+ characters that follow. */
+static char *
+fixup_extra_acl_fields (char *ptr)
+{
+ char *src = ptr;
+ char *dst = ptr;
+
+ while (*src)
+ {
+ const char *old = src;
+ size_t len = 0;
+
+ src = skip_to_ext_fields (src);
+ len = src - old;
+ if (old != dst)
+ memmove (dst, old, len);
+ dst += len;
+
+ if (*src == ':') /* We have extra fields, skip them all */
+ src += strcspn (src, "\n,");
+
+ if ((*src == '\n') || (*src == ','))
+ *dst++ = *src++; /* also done when dst == src, but that's ok */
+ }
+ if (src != dst)
+ *dst = 0;
+
+ return ptr;
+}
+
+/* Set the "system.posix_acl_access/system.posix_acl_default" extended
+ attribute. Called only when acls_option > 0. */
+static void
+xattrs__acls_set (struct tar_stat_info const *st,
+ char const *file_name, int type,
+ char *ptr, size_t len, bool def)
+{
+ acl_t acl;
+
+ if (ptr)
+ {
+ /* assert (strlen (ptr) == len); */
+ ptr = fixup_extra_acl_fields (ptr);
+ acl = acl_from_text (ptr);
+ }
+ else if (def)
+ {
+ /* No "default" IEEE 1003.1e ACL set for directory. At this moment,
+ FILE_NAME may already have inherited default acls from parent
+ directory; clean them up. */
+ if (acl_delete_def_file_at (chdir_fd, file_name))
+ WARNOPT (WARN_XATTR_WRITE,
+ (0, errno,
+ _("acl_delete_def_file_at: Cannot drop default POSIX ACLs "
+ "for file '%s'"),
+ file_name));
+ return;
+ }
+ else
+ acl = perms2acl (st->stat.st_mode);
+
+ if (!acl)
+ {
+ call_arg_warn ("acl_from_text", file_name);
+ return;
+ }
+
+ if (acl_set_file_at (chdir_fd, file_name, type, acl) == -1)
+ /* warn even if filesystem does not support acls */
+ WARNOPT (WARN_XATTR_WRITE,
+ (0, errno,
+ _ ("acl_set_file_at: Cannot set POSIX ACLs for file '%s'"),
+ file_name));
+
+ acl_free (acl);
+}
+
+static void
+xattrs__acls_get_a (int parentfd, const char *file_name,
+ struct tar_stat_info *st,
+ char **ret_ptr, size_t * ret_len)
+{
+ char *val = NULL;
+ ssize_t len;
+ acl_t acl;
+
+ if (!(acl = acl_get_file_at (parentfd, file_name, ACL_TYPE_ACCESS)))
+ {
+ if (errno != ENOTSUP)
+ call_arg_warn ("acl_get_file_at", file_name);
+ return;
+ }
+
+ val = acl_to_text (acl, &len);
+ acl_free (acl);
+
+ if (!val)
+ {
+ call_arg_warn ("acl_to_text", file_name);
+ return;
+ }
+
+ *ret_ptr = xstrdup (val);
+ *ret_len = len;
+
+ acl_free (val);
+}
+
+/* "system.posix_acl_default" */
+static void
+xattrs__acls_get_d (int parentfd, char const *file_name,
+ struct tar_stat_info *st,
+ char **ret_ptr, size_t * ret_len)
+{
+ char *val = NULL;
+ ssize_t len;
+ acl_t acl;
+
+ if (!(acl = acl_get_file_at (parentfd, file_name, ACL_TYPE_DEFAULT)))
+ {
+ if (errno != ENOTSUP)
+ call_arg_warn ("acl_get_file_at", file_name);
+ return;
+ }
+
+ val = acl_to_text (acl, &len);
+ acl_free (acl);
+
+ if (!val)
+ {
+ call_arg_warn ("acl_to_text", file_name);
+ return;
+ }
+
+ *ret_ptr = xstrdup (val);
+ *ret_len = len;
+
+ acl_free (val);
+}
+#endif /* HAVE_POSIX_ACLS */
+
+static void
+acls_one_line (const char *prefix, char delim,
+ const char *aclstring, size_t len)
+{
+ /* support both long and short text representation of posix acls */
+ struct obstack stk;
+ int pref_len = strlen (prefix);
+ const char *oldstring = aclstring;
+ int pos = 0;
+
+ if (!aclstring || !len)
+ return;
+
+ obstack_init (&stk);
+ while (pos <= len)
+ {
+ int move = strcspn (aclstring, ",\n");
+ if (!move)
+ break;
+
+ if (oldstring != aclstring)
+ obstack_1grow (&stk, delim);
+
+ obstack_grow (&stk, prefix, pref_len);
+ obstack_grow (&stk, aclstring, move);
+
+ aclstring += move + 1;
+ }
+
+ obstack_1grow (&stk, '\0');
+
+ fprintf (stdlis, "%s", (char *) obstack_finish (&stk));
+
+ obstack_free (&stk, NULL);
+}
+
+void
+xattrs_acls_get (int parentfd, char const *file_name,
+ struct tar_stat_info *st, int fd, int xisfile)
+{
+ if (acls_option > 0)
+ {
+#ifndef HAVE_POSIX_ACLS
+ static int done = 0;
+ if (!done)
+ WARN ((0, 0, _("POSIX ACL support is not available")));
+ done = 1;
+#else
+ int err = file_has_acl_at (parentfd, file_name, &st->stat);
+ if (err == 0)
+ return;
+ if (err == -1)
+ {
+ call_arg_warn ("file_has_acl_at", file_name);
+ return;
+ }
+
+ xattrs__acls_get_a (parentfd, file_name, st,
+ &st->acls_a_ptr, &st->acls_a_len);
+ if (!xisfile)
+ xattrs__acls_get_d (parentfd, file_name, st,
+ &st->acls_d_ptr, &st->acls_d_len);
+#endif
+ }
+}
+
+void
+xattrs_acls_set (struct tar_stat_info const *st,
+ char const *file_name, char typeflag)
+{
+ if (acls_option > 0 && typeflag != SYMTYPE)
+ {
+#ifndef HAVE_POSIX_ACLS
+ static int done = 0;
+ if (!done)
+ WARN ((0, 0, _("POSIX ACL support is not available")));
+ done = 1;
+#else
+ xattrs__acls_set (st, file_name, ACL_TYPE_ACCESS,
+ st->acls_a_ptr, st->acls_a_len, false);
+ if (typeflag == DIRTYPE || typeflag == GNUTYPE_DUMPDIR)
+ xattrs__acls_set (st, file_name, ACL_TYPE_DEFAULT,
+ st->acls_d_ptr, st->acls_d_len, true);
+#endif
+ }
+}
+
+static void
+mask_map_realloc (struct xattrs_mask_map *map)
+{
+ if (map->used == map->size)
+ {
+ if (map->size == 0)
+ map->size = 4;
+ map->masks = x2nrealloc (map->masks, &map->size, sizeof (map->masks[0]));
+ }
+}
+
+void
+xattrs_mask_add (const char *mask, bool incl)
+{
+ struct xattrs_mask_map *mask_map =
+ incl ? &xattrs_setup.incl : &xattrs_setup.excl;
+ /* ensure there is enough space */
+ mask_map_realloc (mask_map);
+ /* just assign pointers -- we silently expect that pointer "mask" is valid
+ through the whole program (pointer to argv array) */
+ mask_map->masks[mask_map->used++] = mask;
+}
+
+static void
+clear_mask_map (struct xattrs_mask_map *mask_map)
+{
+ if (mask_map->size)
+ free (mask_map->masks);
+}
+
+void
+xattrs_clear_setup (void)
+{
+ clear_mask_map (&xattrs_setup.incl);
+ clear_mask_map (&xattrs_setup.excl);
+}
+
+/* get all xattrs from file given by FILE_NAME or FD (when non-zero). This
+ includes all the user.*, security.*, system.*, etc. available domains */
+void
+xattrs_xattrs_get (int parentfd, char const *file_name,
+ struct tar_stat_info *st, int fd)
+{
+ if (xattrs_option > 0)
+ {
+#ifndef HAVE_XATTRS
+ static int done = 0;
+ if (!done)
+ WARN ((0, 0, _("XATTR support is not available")));
+ done = 1;
+#else
+ static size_t xsz = 1024;
+ static char *xatrs = NULL;
+ ssize_t xret = -1;
+
+ if (!xatrs)
+ xatrs = x2nrealloc (xatrs, &xsz, 1);
+
+ while (((fd == 0) ?
+ ((xret =
+ llistxattrat (parentfd, file_name, xatrs, xsz)) == -1) :
+ ((xret = flistxattr (fd, xatrs, xsz)) == -1))
+ && (errno == ERANGE))
+ {
+ xatrs = x2nrealloc (xatrs, &xsz, 1);
+ }
+
+ if (xret == -1)
+ call_arg_warn ((fd == 0) ? "llistxattrat" : "flistxattr", file_name);
+ else
+ {
+ const char *attr = xatrs;
+ static size_t asz = 1024;
+ static char *val = NULL;
+
+ if (!val)
+ val = x2nrealloc (val, &asz, 1);
+
+ while (xret > 0)
+ {
+ size_t len = strlen (attr);
+ ssize_t aret = 0;
+
+ /* Archive all xattrs during creation, decide at extraction time
+ * which ones are of interest/use for the target filesystem. */
+ while (((fd == 0)
+ ? ((aret = lgetxattrat (parentfd, file_name, attr,
+ val, asz)) == -1)
+ : ((aret = fgetxattr (fd, attr, val, asz)) == -1))
+ && (errno == ERANGE))
+ {
+ val = x2nrealloc (val, &asz, 1);
+ }
+
+ if (aret != -1)
+ xheader_xattr_add (st, attr, val, aret);
+ else if (errno != ENOATTR)
+ call_arg_warn ((fd == 0) ? "lgetxattrat"
+ : "fgetxattr", file_name);
+
+ attr += len + 1;
+ xret -= len + 1;
+ }
+ }
+#endif
+ }
+}
+
+#ifdef HAVE_XATTRS
+static void
+xattrs__fd_set (struct tar_stat_info const *st,
+ char const *file_name, char typeflag,
+ const char *attr, const char *ptr, size_t len)
+{
+ if (ptr)
+ {
+ const char *sysname = "setxattrat";
+ int ret = -1;
+
+ if (typeflag != SYMTYPE)
+ ret = setxattrat (chdir_fd, file_name, attr, ptr, len, 0);
+ else
+ {
+ sysname = "lsetxattr";
+ ret = lsetxattrat (chdir_fd, file_name, attr, ptr, len, 0);
+ }
+
+ if (ret == -1)
+ WARNOPT (WARN_XATTR_WRITE,
+ (0, errno,
+ _("%s: Cannot set '%s' extended attribute for file '%s'"),
+ sysname, attr, file_name));
+ }
+}
+#endif
+
+/* lgetfileconat is called against FILE_NAME iff the FD parameter is set to
+ zero, otherwise the fgetfileconat is used against correct file descriptor */
+void
+xattrs_selinux_get (int parentfd, char const *file_name,
+ struct tar_stat_info *st, int fd)
+{
+ if (selinux_context_option > 0)
+ {
+#if HAVE_SELINUX_SELINUX_H != 1
+ static int done = 0;
+ if (!done)
+ WARN ((0, 0, _("SELinux support is not available")));
+ done = 1;
+#else
+ int result = fd ?
+ fgetfilecon (fd, &st->cntx_name)
+ : lgetfileconat (parentfd, file_name, &st->cntx_name);
+
+ if (result == -1 && errno != ENODATA && errno != ENOTSUP)
+ call_arg_warn (fd ? "fgetfilecon" : "lgetfileconat", file_name);
+#endif
+ }
+}
+
+void
+xattrs_selinux_set (struct tar_stat_info const *st,
+ char const *file_name, char typeflag)
+{
+ if (selinux_context_option > 0)
+ {
+#if HAVE_SELINUX_SELINUX_H != 1
+ static int done = 0;
+ if (!done)
+ WARN ((0, 0, _("SELinux support is not available")));
+ done = 1;
+#else
+ const char *sysname = "setfilecon";
+ int ret;
+
+ if (!st->cntx_name)
+ return;
+
+ if (typeflag != SYMTYPE)
+ {
+ ret = setfileconat (chdir_fd, file_name, st->cntx_name);
+ sysname = "setfileconat";
+ }
+ else
+ {
+ ret = lsetfileconat (chdir_fd, file_name, st->cntx_name);
+ sysname = "lsetfileconat";
+ }
+
+ if (ret == -1)
+ WARNOPT (WARN_XATTR_WRITE,
+ (0, errno,
+ _("%s: Cannot set SELinux context for file '%s'"),
+ sysname, file_name));
+#endif
+ }
+}
+
+static bool
+xattrs_matches_mask (const char *kw, struct xattrs_mask_map *mm)
+{
+ int i;
+
+ if (!mm->size)
+ return false;
+
+ for (i = 0; i < mm->used; i++)
+ if (fnmatch (mm->masks[i], kw, 0) == 0)
+ return true;
+
+ return false;
+}
+
+#define USER_DOT_PFX "user."
+
+static bool
+xattrs_kw_included (const char *kw, bool archiving)
+{
+ if (xattrs_setup.incl.size)
+ return xattrs_matches_mask (kw, &xattrs_setup.incl);
+ else if (archiving)
+ return true;
+ else
+ return strncmp (kw, USER_DOT_PFX, sizeof (USER_DOT_PFX) - 1) == 0;
+}
+
+static bool
+xattrs_kw_excluded (const char *kw, bool archiving)
+{
+ return xattrs_setup.excl.size ?
+ xattrs_matches_mask (kw, &xattrs_setup.excl) : false;
+}
+
+/* Check whether the xattr with keyword KW should be discarded from list of
+ attributes that are going to be archived/excluded (set ARCHIVING=true for
+ archiving, false for excluding) */
+static bool
+xattrs_masked_out (const char *kw, bool archiving)
+{
+ return xattrs_kw_included (kw, archiving) ?
+ xattrs_kw_excluded (kw, archiving) : true;
+}
+
+void
+xattrs_xattrs_set (struct tar_stat_info const *st,
+ char const *file_name, char typeflag, int later_run)
+{
+ if (xattrs_option > 0)
+ {
+#ifndef HAVE_XATTRS
+ static int done = 0;
+ if (!done)
+ WARN ((0, 0, _("XATTR support is not available")));
+ done = 1;
+#else
+ size_t scan = 0;
+
+ if (!st->xattr_map_size)
+ return;
+
+ for (; scan < st->xattr_map_size; ++scan)
+ {
+ char *keyword = st->xattr_map[scan].xkey;
+ keyword += strlen ("SCHILY.xattr.");
+
+ /* TODO: this 'later_run' workaround is temporary solution -> once
+ capabilities should become fully supported by it's API and there
+ should exist something like xattrs_capabilities_set() call.
+ For a regular files: all extended attributes are restored during
+ the first run except 'security.capability' which is restored in
+ 'later_run == 1'. */
+ if (typeflag == REGTYPE
+ && later_run == !!strcmp (keyword, "security.capability"))
+ continue;
+
+ if (xattrs_masked_out (keyword, false /* extracting */ ))
+ /* we don't want to restore this keyword */
+ continue;
+
+ xattrs__fd_set (st, file_name, typeflag, keyword,
+ st->xattr_map[scan].xval_ptr,
+ st->xattr_map[scan].xval_len);
+ }
+#endif
+ }
+}
+
+void
+xattrs_print_char (struct tar_stat_info const *st, char *output)
+{
+ int i;
+
+ if (verbose_option < 2)
+ {
+ *output = 0;
+ return;
+ }
+
+ if (xattrs_option > 0 || selinux_context_option > 0 || acls_option > 0)
+ {
+ /* placeholders */
+ *output = ' ';
+ output[1] = 0;
+ }
+
+ if (xattrs_option > 0 && st->xattr_map_size)
+ for (i = 0; i < st->xattr_map_size; ++i)
+ {
+ char *keyword = st->xattr_map[i].xkey + strlen ("SCHILY.xattr.");
+ if (!xattrs_masked_out (keyword, false /* like extracting */ ))
+ {
+ *output = '*';
+ break;
+ }
+ }
+
+ if (selinux_context_option > 0 && st->cntx_name)
+ *output = '.';
+
+ if (acls_option > 0 && (st->acls_a_len || st->acls_d_len))
+ *output = '+';
+}
+
+void
+xattrs_print (struct tar_stat_info const *st)
+{
+ if (verbose_option < 3)
+ return;
+
+ /* selinux */
+ if (selinux_context_option > 0 && st->cntx_name)
+ fprintf (stdlis, " s: %s\n", st->cntx_name);
+
+ /* acls */
+ if (acls_option > 0 && (st->acls_a_len || st->acls_d_len))
+ {
+ fprintf (stdlis, " a: ");
+ acls_one_line ("", ',', st->acls_a_ptr, st->acls_a_len);
+ acls_one_line ("default:", ',', st->acls_d_ptr, st->acls_d_len);
+ fprintf (stdlis, "\n");
+ }
+
+ /* xattrs */
+ if (xattrs_option > 0 && st->xattr_map_size)
+ {
+ int i;
+
+ for (i = 0; i < st->xattr_map_size; ++i)
+ {
+ char *keyword = st->xattr_map[i].xkey + strlen ("SCHILY.xattr.");
+ if (!xattrs_masked_out (keyword, false /* like extracting */ ))
+ fprintf (stdlis, " x: %lu %s\n",
+ (unsigned long) st->xattr_map[i].xval_len, keyword);
+ }
+ }
+}
diff --git a/src/xattrs.h b/src/xattrs.h
new file mode 100644
index 0000000..a475bb6
--- /dev/null
+++ b/src/xattrs.h
@@ -0,0 +1,50 @@
+/* Support for extended attributes.
+
+ Copyright (C) 2006-2014, 2016 Free Software Foundation, Inc.
+
+ This file is part of GNU tar.
+
+ GNU tar 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.
+
+ GNU tar 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/>.
+
+ Written by James Antill, on 2006-07-27. */
+
+#ifndef GUARD_XATTTRS_H
+#define GUARD_XATTTRS_H
+
+/* Add include/exclude fnmatch pattern for xattr key domain. Set INCL parameter
+ to true/false if you want to add include/exclude pattern */
+extern void xattrs_mask_add (const char *mask, bool incl);
+
+/* clear helping structures when tar finishes */
+extern void xattrs_clear_setup (void);
+
+extern void xattrs_acls_get (int parentfd, char const *file_name,
+ struct tar_stat_info *st, int fd, int xisfile);
+extern void xattrs_selinux_get (int parentfd, char const *file_name,
+ struct tar_stat_info *st, int fd);
+extern void xattrs_xattrs_get (int parentfd, char const *file_name,
+ struct tar_stat_info *st, int fd);
+
+extern void xattrs_acls_set (struct tar_stat_info const *st,
+ char const *file_name, char typeflag);
+extern void xattrs_selinux_set (struct tar_stat_info const *st,
+ char const *file_name, char typeflag);
+extern void xattrs_xattrs_set (struct tar_stat_info const *st,
+ char const *file_name, char typeflag,
+ int later_run);
+
+extern void xattrs_print_char (struct tar_stat_info const *st, char *output);
+extern void xattrs_print (struct tar_stat_info const *st);
+
+#endif /* GUARD_XATTTRS_H */
diff --git a/src/xheader.c b/src/xheader.c
new file mode 100644
index 0000000..8dda580
--- /dev/null
+++ b/src/xheader.c
@@ -0,0 +1,1798 @@
+/* POSIX extended headers for tar.
+
+ Copyright (C) 2003-2007, 2009-2010, 2012-2014, 2016 Free Software
+ Foundation, Inc.
+
+ This file is part of GNU tar.
+
+ GNU tar 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.
+
+ GNU tar is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <system.h>
+
+#include <fnmatch.h>
+#include <hash.h>
+#include <inttostr.h>
+#include <quotearg.h>
+
+#include "common.h"
+
+static void xheader_init (struct xheader *xhdr);
+static bool xheader_protected_pattern_p (char const *pattern);
+static bool xheader_protected_keyword_p (char const *keyword);
+static void xheader_set_single_keyword (char *) __attribute__ ((noreturn));
+
+/* Used by xheader_finish() */
+static void code_string (char const *string, char const *keyword,
+ struct xheader *xhdr);
+
+/* Number of global headers written so far. */
+static size_t global_header_count;
+/* FIXME: Possibly it should be reset after changing the volume.
+ POSIX %n specification says that it is expanded to the sequence
+ number of current global header in *the* archive. However, for
+ multi-volume archives this will yield duplicate header names
+ in different volumes, which I'd like to avoid. The best way
+ to solve this would be to use per-archive header count as required
+ by POSIX *and* set globexthdr.name to, say,
+ $TMPDIR/GlobalHead.%p.$NUMVOLUME.%n.
+
+ However it should wait until buffer.c is finally rewritten */
+
+
+/* Interface functions to obstacks */
+
+static void
+x_obstack_grow (struct xheader *xhdr, const char *ptr, size_t length)
+{
+ obstack_grow (xhdr->stk, ptr, length);
+ xhdr->size += length;
+}
+
+static void
+x_obstack_1grow (struct xheader *xhdr, char c)
+{
+ obstack_1grow (xhdr->stk, c);
+ xhdr->size++;
+}
+
+static void
+x_obstack_blank (struct xheader *xhdr, size_t length)
+{
+ obstack_blank (xhdr->stk, length);
+ xhdr->size += length;
+}
+
+
+/* Keyword options */
+
+struct keyword_list
+{
+ struct keyword_list *next;
+ char *pattern;
+ char *value;
+};
+
+
+/* List of keyword patterns set by delete= option */
+static struct keyword_list *keyword_pattern_list;
+
+/* List of keyword/value pairs set by 'keyword=value' option */
+static struct keyword_list *keyword_global_override_list;
+
+/* List of keyword/value pairs set by 'keyword:=value' option */
+static struct keyword_list *keyword_override_list;
+
+/* List of keyword/value pairs decoded from the last 'g' type header */
+static struct keyword_list *global_header_override_list;
+
+/* Template for the name field of an 'x' type header */
+static char *exthdr_name;
+
+static char *exthdr_mtime_option;
+static time_t exthdr_mtime;
+
+/* Template for the name field of a 'g' type header */
+static char *globexthdr_name;
+
+static char *globexthdr_mtime_option;
+static time_t globexthdr_mtime;
+
+bool
+xheader_keyword_deleted_p (const char *kw)
+{
+ struct keyword_list *kp;
+
+ for (kp = keyword_pattern_list; kp; kp = kp->next)
+ if (fnmatch (kp->pattern, kw, 0) == 0)
+ return true;
+ return false;
+}
+
+static bool
+xheader_keyword_override_p (const char *keyword)
+{
+ struct keyword_list *kp;
+
+ for (kp = keyword_override_list; kp; kp = kp->next)
+ if (strcmp (kp->pattern, keyword) == 0)
+ return true;
+ return false;
+}
+
+static void
+xheader_list_append (struct keyword_list **root, char const *kw,
+ char const *value)
+{
+ struct keyword_list *kp = xmalloc (sizeof *kp);
+ kp->pattern = xstrdup (kw);
+ kp->value = value ? xstrdup (value) : NULL;
+ kp->next = *root;
+ *root = kp;
+}
+
+static void
+xheader_list_destroy (struct keyword_list **root)
+{
+ if (root)
+ {
+ struct keyword_list *kw = *root;
+ while (kw)
+ {
+ struct keyword_list *next = kw->next;
+ free (kw->pattern);
+ free (kw->value);
+ free (kw);
+ kw = next;
+ }
+ *root = NULL;
+ }
+}
+
+static void
+xheader_set_single_keyword (char *kw)
+{
+ USAGE_ERROR ((0, 0, _("Keyword %s is unknown or not yet implemented"), kw));
+}
+
+static void
+assign_time_option (char **sval, time_t *tval, const char *input)
+{
+ char *p;
+ struct timespec t = decode_timespec (input, &p, false);
+ if (! valid_timespec (t) || *p)
+ ERROR ((0, 0, _("Time stamp is out of allowed range")));
+ else
+ {
+ *tval = t.tv_sec;
+ assign_string (sval, input);
+ }
+}
+
+static void
+xheader_set_keyword_equal (char *kw, char *eq)
+{
+ bool global = true;
+ char *p = eq;
+
+ if (eq[-1] == ':')
+ {
+ p--;
+ global = false;
+ }
+
+ while (p > kw && isspace ((unsigned char) *p))
+ p--;
+
+ *p = 0;
+
+ for (p = eq + 1; *p && isspace ((unsigned char) *p); p++)
+ ;
+
+ if (strcmp (kw, "delete") == 0)
+ {
+ if (xheader_protected_pattern_p (p))
+ USAGE_ERROR ((0, 0, _("Pattern %s cannot be used"), quote (p)));
+ xheader_list_append (&keyword_pattern_list, p, NULL);
+ }
+ else if (strcmp (kw, "exthdr.name") == 0)
+ assign_string (&exthdr_name, p);
+ else if (strcmp (kw, "globexthdr.name") == 0)
+ assign_string (&globexthdr_name, p);
+ else if (strcmp (kw, "exthdr.mtime") == 0)
+ assign_time_option (&exthdr_mtime_option, &exthdr_mtime, p);
+ else if (strcmp (kw, "globexthdr.mtime") == 0)
+ assign_time_option (&globexthdr_mtime_option, &globexthdr_mtime, p);
+ else
+ {
+ if (xheader_protected_keyword_p (kw))
+ USAGE_ERROR ((0, 0, _("Keyword %s cannot be overridden"), kw));
+ if (global)
+ xheader_list_append (&keyword_global_override_list, kw, p);
+ else
+ xheader_list_append (&keyword_override_list, kw, p);
+ }
+}
+
+void
+xheader_set_option (char *string)
+{
+ char *token;
+ for (token = strtok (string, ","); token; token = strtok (NULL, ","))
+ {
+ char *p = strchr (token, '=');
+ if (!p)
+ xheader_set_single_keyword (token);
+ else
+ xheader_set_keyword_equal (token, p);
+ }
+}
+
+/*
+ string Includes: Replaced By:
+ %d The directory name of the file,
+ equivalent to the result of the
+ dirname utility on the translated
+ file name.
+ %f The filename of the file, equivalent
+ to the result of the basename
+ utility on the translated file name.
+ %p The process ID of the pax process.
+ %n The value of the 3rd argument.
+ %% A '%' character. */
+
+char *
+xheader_format_name (struct tar_stat_info *st, const char *fmt, size_t n)
+{
+ char *buf;
+ size_t len = strlen (fmt);
+ char *q;
+ const char *p;
+ char *dirp = NULL;
+ char *dir = NULL;
+ char *base = NULL;
+ char pidbuf[UINTMAX_STRSIZE_BOUND];
+ char const *pptr = NULL;
+ char nbuf[UINTMAX_STRSIZE_BOUND];
+ char const *nptr = NULL;
+
+ for (p = fmt; *p && (p = strchr (p, '%')); )
+ {
+ switch (p[1])
+ {
+ case '%':
+ len--;
+ break;
+
+ case 'd':
+ if (st)
+ {
+ if (!dirp)
+ dirp = dir_name (st->orig_file_name);
+ dir = safer_name_suffix (dirp, false, absolute_names_option);
+ len += strlen (dir) - 2;
+ }
+ break;
+
+ case 'f':
+ if (st)
+ {
+ base = last_component (st->orig_file_name);
+ len += strlen (base) - 2;
+ }
+ break;
+
+ case 'p':
+ pptr = umaxtostr (getpid (), pidbuf);
+ len += pidbuf + sizeof pidbuf - 1 - pptr - 2;
+ break;
+
+ case 'n':
+ nptr = umaxtostr (n, nbuf);
+ len += nbuf + sizeof nbuf - 1 - nptr - 2;
+ break;
+ }
+ p++;
+ }
+
+ buf = xmalloc (len + 1);
+ for (q = buf, p = fmt; *p; )
+ {
+ if (*p == '%')
+ {
+ switch (p[1])
+ {
+ case '%':
+ *q++ = *p++;
+ p++;
+ break;
+
+ case 'd':
+ if (dir)
+ q = stpcpy (q, dir);
+ p += 2;
+ break;
+
+ case 'f':
+ if (base)
+ q = stpcpy (q, base);
+ p += 2;
+ break;
+
+ case 'p':
+ q = stpcpy (q, pptr);
+ p += 2;
+ break;
+
+ case 'n':
+ q = stpcpy (q, nptr);
+ p += 2;
+ break;
+
+
+ default:
+ *q++ = *p++;
+ if (*p)
+ *q++ = *p++;
+ }
+ }
+ else
+ *q++ = *p++;
+ }
+
+ free (dirp);
+
+ /* Do not allow it to end in a slash */
+ while (q > buf && ISSLASH (q[-1]))
+ q--;
+ *q = 0;
+ return buf;
+}
+
+char *
+xheader_xhdr_name (struct tar_stat_info *st)
+{
+ if (!exthdr_name)
+ assign_string (&exthdr_name, "%d/PaxHeaders.%p/%f");
+ return xheader_format_name (st, exthdr_name, 0);
+}
+
+#define GLOBAL_HEADER_TEMPLATE "/GlobalHead.%p.%n"
+
+char *
+xheader_ghdr_name (void)
+{
+ if (!globexthdr_name)
+ {
+ size_t len;
+ const char *tmp = getenv ("TMPDIR");
+ if (!tmp)
+ tmp = "/tmp";
+ len = strlen (tmp) + sizeof (GLOBAL_HEADER_TEMPLATE); /* Includes nul */
+ globexthdr_name = xmalloc (len);
+ strcpy(globexthdr_name, tmp);
+ strcat(globexthdr_name, GLOBAL_HEADER_TEMPLATE);
+ }
+
+ return xheader_format_name (NULL, globexthdr_name, global_header_count + 1);
+}
+
+void
+xheader_write (char type, char *name, time_t t, struct xheader *xhdr)
+{
+ union block *header;
+ size_t size;
+ char *p;
+
+ size = xhdr->size;
+ switch (type)
+ {
+ case XGLTYPE:
+ if (globexthdr_mtime_option)
+ t = globexthdr_mtime;
+ break;
+
+ case XHDTYPE:
+ if (exthdr_mtime_option)
+ t = exthdr_mtime;
+ break;
+ }
+ header = start_private_header (name, size, t);
+ header->header.typeflag = type;
+
+ simple_finish_header (header);
+
+ p = xhdr->buffer;
+
+ do
+ {
+ size_t len;
+
+ header = find_next_block ();
+ len = BLOCKSIZE;
+ if (len > size)
+ len = size;
+ memcpy (header->buffer, p, len);
+ if (len < BLOCKSIZE)
+ memset (header->buffer + len, 0, BLOCKSIZE - len);
+ p += len;
+ size -= len;
+ set_next_block_after (header);
+ }
+ while (size > 0);
+ xheader_destroy (xhdr);
+
+ if (type == XGLTYPE)
+ global_header_count++;
+}
+
+void
+xheader_write_global (struct xheader *xhdr)
+{
+ if (keyword_global_override_list)
+ {
+ struct keyword_list *kp;
+
+ xheader_init (xhdr);
+ for (kp = keyword_global_override_list; kp; kp = kp->next)
+ code_string (kp->value, kp->pattern, xhdr);
+ }
+ if (xhdr->stk)
+ {
+ char *name;
+
+ xheader_finish (xhdr);
+ name = xheader_ghdr_name ();
+ xheader_write (XGLTYPE, name, start_time.tv_sec, xhdr);
+ free (name);
+ }
+}
+
+void
+xheader_xattr_init (struct tar_stat_info *st)
+{
+ st->xattr_map = NULL;
+ st->xattr_map_size = 0;
+
+ st->acls_a_ptr = NULL;
+ st->acls_a_len = 0;
+ st->acls_d_ptr = NULL;
+ st->acls_d_len = 0;
+ st->cntx_name = NULL;
+}
+
+void
+xheader_xattr_free (struct xattr_array *xattr_map, size_t xattr_map_size)
+{
+ size_t scan = 0;
+
+ while (scan < xattr_map_size)
+ {
+ free (xattr_map[scan].xkey);
+ free (xattr_map[scan].xval_ptr);
+
+ ++scan;
+ }
+ free (xattr_map);
+}
+
+static void
+xheader_xattr__add (struct xattr_array **xattr_map,
+ size_t *xattr_map_size,
+ const char *key, const char *val, size_t len)
+{
+ size_t pos = (*xattr_map_size)++;
+
+ *xattr_map = xrealloc (*xattr_map,
+ *xattr_map_size * sizeof(struct xattr_array));
+ (*xattr_map)[pos].xkey = xstrdup (key);
+ (*xattr_map)[pos].xval_ptr = xmemdup (val, len + 1);
+ (*xattr_map)[pos].xval_len = len;
+}
+
+/* This is reversal function for xattr_encode_keyword. See comment for
+ xattr_encode_keyword() for more info. */
+static void
+xattr_decode_keyword (char *keyword)
+{
+ char *kpr, *kpl; /* keyword pointer left/right */
+ kpr = kpl = keyword;
+
+ for (;;)
+ {
+ if (*kpr == '%')
+ {
+ if (kpr[1] == '3' && kpr[2] == 'D')
+ {
+ *kpl = '=';
+ kpr += 3;
+ kpl ++;
+ continue;
+ }
+ else if (kpr[1] == '2' && kpr[2] == '5')
+ {
+ *kpl = '%';
+ kpr += 3;
+ kpl ++;
+ continue;
+ }
+ }
+
+ *kpl = *kpr;
+
+ if (*kpr == 0)
+ break;
+
+ kpr++;
+ kpl++;
+ }
+}
+
+void
+xheader_xattr_add (struct tar_stat_info *st,
+ const char *key, const char *val, size_t len)
+{
+ size_t klen = strlen (key);
+ char *xkey = xmalloc (strlen("SCHILY.xattr.") + klen + 1);
+ char *tmp = xkey;
+
+ tmp = stpcpy (tmp, "SCHILY.xattr.");
+ stpcpy (tmp, key);
+
+ xheader_xattr__add (&st->xattr_map, &st->xattr_map_size, xkey, val, len);
+
+ free (xkey);
+}
+
+void
+xheader_xattr_copy (const struct tar_stat_info *st,
+ struct xattr_array **xattr_map, size_t *xattr_map_size)
+{
+ size_t scan = 0;
+
+ *xattr_map = NULL;
+ *xattr_map_size = 0;
+
+ while (scan < st->xattr_map_size)
+ {
+ char *key = st->xattr_map[scan].xkey;
+ char *val = st->xattr_map[scan].xval_ptr;
+ size_t len = st->xattr_map[scan].xval_len;
+
+ xheader_xattr__add(xattr_map, xattr_map_size, key, val, len);
+
+ ++scan;
+ }
+}
+
+
+/* General Interface */
+
+#define XHDR_PROTECTED 0x01
+#define XHDR_GLOBAL 0x02
+
+struct xhdr_tab
+{
+ char const *keyword;
+ void (*coder) (struct tar_stat_info const *, char const *,
+ struct xheader *, void const *data);
+ void (*decoder) (struct tar_stat_info *, char const *, char const *, size_t);
+ int flags;
+ bool prefix; /* select handler comparing prefix only */
+};
+
+/* This declaration must be extern, because ISO C99 section 6.9.2
+ prohibits a tentative definition that has both internal linkage and
+ incomplete type. If we made it static, we'd have to declare its
+ size which would be a maintenance pain; if we put its initializer
+ here, we'd need a boatload of forward declarations, which would be
+ even more of a pain. */
+extern struct xhdr_tab const xhdr_tab[];
+
+static struct xhdr_tab const *
+locate_handler (char const *keyword)
+{
+ struct xhdr_tab const *p;
+
+ for (p = xhdr_tab; p->keyword; p++)
+ if (p->prefix)
+ {
+ if (strncmp (p->keyword, keyword, strlen(p->keyword)) == 0)
+ return p;
+ }
+ else
+ {
+ if (strcmp (p->keyword, keyword) == 0)
+ return p;
+ }
+
+ return NULL;
+}
+
+static bool
+xheader_protected_pattern_p (const char *pattern)
+{
+ struct xhdr_tab const *p;
+
+ for (p = xhdr_tab; p->keyword; p++)
+ if (!p->prefix && (p->flags & XHDR_PROTECTED)
+ && fnmatch (pattern, p->keyword, 0) == 0)
+ return true;
+ return false;
+}
+
+static bool
+xheader_protected_keyword_p (const char *keyword)
+{
+ struct xhdr_tab const *p;
+
+ for (p = xhdr_tab; p->keyword; p++)
+ if (!p->prefix && (p->flags & XHDR_PROTECTED)
+ && strcmp (p->keyword, keyword) == 0)
+ return true;
+ return false;
+}
+
+/* Decode a single extended header record, advancing *PTR to the next record.
+ Return true on success, false otherwise. */
+static bool
+decode_record (struct xheader *xhdr,
+ char **ptr,
+ void (*handler) (void *, char const *, char const *, size_t),
+ void *data)
+{
+ char *start = *ptr;
+ char *p = start;
+ size_t len;
+ char *len_lim;
+ char const *keyword;
+ char *nextp;
+ size_t len_max = xhdr->buffer + xhdr->size - start;
+
+ while (*p == ' ' || *p == '\t')
+ p++;
+
+ if (! ISDIGIT (*p))
+ {
+ if (*p)
+ ERROR ((0, 0, _("Malformed extended header: missing length")));
+ return false;
+ }
+
+ len = strtoumax (p, &len_lim, 10);
+
+ if (len_max < len)
+ {
+ int len_len = len_lim - p;
+ ERROR ((0, 0, _("Extended header length %*s is out of range"),
+ len_len, p));
+ return false;
+ }
+
+ nextp = start + len;
+
+ for (p = len_lim; *p == ' ' || *p == '\t'; p++)
+ continue;
+ if (p == len_lim)
+ {
+ ERROR ((0, 0,
+ _("Malformed extended header: missing blank after length")));
+ return false;
+ }
+
+ keyword = p;
+ p = strchr (p, '=');
+ if (! (p && p < nextp))
+ {
+ ERROR ((0, 0, _("Malformed extended header: missing equal sign")));
+ return false;
+ }
+
+ if (nextp[-1] != '\n')
+ {
+ ERROR ((0, 0, _("Malformed extended header: missing newline")));
+ return false;
+ }
+
+ *p = nextp[-1] = '\0';
+ handler (data, keyword, p + 1, nextp - p - 2); /* '=' + trailing '\n' */
+ *p = '=';
+ nextp[-1] = '\n';
+ *ptr = nextp;
+ return true;
+}
+
+static void
+run_override_list (struct keyword_list *kp, struct tar_stat_info *st)
+{
+ for (; kp; kp = kp->next)
+ {
+ struct xhdr_tab const *t = locate_handler (kp->pattern);
+ if (t)
+ t->decoder (st, t->keyword, kp->value, strlen (kp->value));
+ }
+}
+
+static void
+decx (void *data, char const *keyword, char const *value, size_t size)
+{
+ struct xhdr_tab const *t;
+ struct tar_stat_info *st = data;
+
+ if (xheader_keyword_deleted_p (keyword)
+ || xheader_keyword_override_p (keyword))
+ return;
+
+ t = locate_handler (keyword);
+ if (t)
+ t->decoder (st, keyword, value, size);
+ else
+ WARNOPT (WARN_UNKNOWN_KEYWORD,
+ (0, 0, _("Ignoring unknown extended header keyword '%s'"),
+ keyword));
+}
+
+void
+xheader_decode (struct tar_stat_info *st)
+{
+ run_override_list (keyword_global_override_list, st);
+ run_override_list (global_header_override_list, st);
+
+ if (st->xhdr.size)
+ {
+ char *p = st->xhdr.buffer + BLOCKSIZE;
+ while (decode_record (&st->xhdr, &p, decx, st))
+ continue;
+ }
+ run_override_list (keyword_override_list, st);
+
+ /* 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;
+}
+
+static void
+decg (void *data, char const *keyword, char const *value,
+ size_t size __attribute__((unused)))
+{
+ struct keyword_list **kwl = data;
+ struct xhdr_tab const *tab = locate_handler (keyword);
+ if (tab && (tab->flags & XHDR_GLOBAL))
+ tab->decoder (data, keyword, value, size);
+ else
+ xheader_list_append (kwl, keyword, value);
+}
+
+void
+xheader_decode_global (struct xheader *xhdr)
+{
+ if (xhdr->size)
+ {
+ char *p = xhdr->buffer + BLOCKSIZE;
+
+ xheader_list_destroy (&global_header_override_list);
+ while (decode_record (xhdr, &p, decg, &global_header_override_list))
+ continue;
+ }
+}
+
+static void
+xheader_init (struct xheader *xhdr)
+{
+ if (!xhdr->stk)
+ {
+ xhdr->stk = xmalloc (sizeof *xhdr->stk);
+ obstack_init (xhdr->stk);
+ }
+}
+
+void
+xheader_store (char const *keyword, struct tar_stat_info *st,
+ void const *data)
+{
+ struct xhdr_tab const *t;
+
+ if (st->xhdr.buffer)
+ return;
+ t = locate_handler (keyword);
+ if (!t || !t->coder)
+ return;
+ if (xheader_keyword_deleted_p (keyword))
+ return;
+ xheader_init (&st->xhdr);
+ if (!xheader_keyword_override_p (keyword))
+ t->coder (st, keyword, &st->xhdr, data);
+}
+
+void
+xheader_read (struct xheader *xhdr, union block *p, off_t size)
+{
+ size_t j = 0;
+
+ if (size < 0)
+ size = 0; /* Already diagnosed. */
+
+ if (SIZE_MAX - BLOCKSIZE <= size)
+ xalloc_die ();
+
+ size += BLOCKSIZE;
+ xhdr->size = size;
+ xhdr->buffer = xmalloc (size + 1);
+ xhdr->buffer[size] = '\0';
+
+ do
+ {
+ size_t len = size;
+
+ if (len > BLOCKSIZE)
+ len = BLOCKSIZE;
+
+ if (!p)
+ FATAL_ERROR ((0, 0, _("Unexpected EOF in archive")));
+
+ memcpy (&xhdr->buffer[j], p->buffer, len);
+ set_next_block_after (p);
+
+ p = find_next_block ();
+
+ j += len;
+ size -= len;
+ }
+ while (size > 0);
+}
+
+/* xattr_encode_keyword() substitutes '=' ~~> '%3D' and '%' ~~> '%25'
+ in extended attribute keywords. This is needed because the '=' character
+ has special purpose in extended attribute header - it splits keyword and
+ value part of header. If there was the '=' occurrence allowed inside
+ keyword, there would be no unambiguous way how to decode this extended
+ attribute.
+
+ (http://lists.gnu.org/archive/html/bug-tar/2012-10/msg00017.html)
+ */
+static char *
+xattr_encode_keyword(const char *keyword)
+{
+ static char *encode_buffer = NULL;
+ static size_t encode_buffer_size = 0;
+ size_t bp; /* keyword/buffer pointers */
+
+ if (!encode_buffer)
+ {
+ encode_buffer_size = 256;
+ encode_buffer = xmalloc (encode_buffer_size);
+ }
+ else
+ *encode_buffer = 0;
+
+ for (bp = 0; *keyword != 0; ++bp, ++keyword)
+ {
+ char c = *keyword;
+
+ if (bp + 2 /* enough for URL encoding also.. */ >= encode_buffer_size)
+ {
+ encode_buffer = x2realloc (encode_buffer, &encode_buffer_size);
+ }
+
+ if (c == '%')
+ {
+ strcpy (encode_buffer + bp, "%25");
+ bp += 2;
+ }
+ else if (c == '=')
+ {
+ strcpy (encode_buffer + bp, "%3D");
+ bp += 2;
+ }
+ else
+ encode_buffer[bp] = c;
+ }
+
+ encode_buffer[bp] = 0;
+
+ return encode_buffer;
+}
+
+static void
+xheader_print_n (struct xheader *xhdr, char const *keyword,
+ char const *value, size_t vsize)
+{
+ size_t p;
+ size_t n = 0;
+ char nbuf[UINTMAX_STRSIZE_BOUND];
+ char const *np;
+ size_t len, klen;
+
+ keyword = xattr_encode_keyword (keyword);
+ klen = strlen (keyword);
+ len = klen + vsize + 3; /* ' ' + '=' + '\n' */
+
+ do
+ {
+ p = n;
+ np = umaxtostr (len + p, nbuf);
+ n = nbuf + sizeof nbuf - 1 - np;
+ }
+ while (n != p);
+
+ x_obstack_grow (xhdr, np, n);
+ x_obstack_1grow (xhdr, ' ');
+ x_obstack_grow (xhdr, keyword, klen);
+ x_obstack_1grow (xhdr, '=');
+ x_obstack_grow (xhdr, value, vsize);
+ x_obstack_1grow (xhdr, '\n');
+}
+
+static void
+xheader_print (struct xheader *xhdr, char const *keyword, char const *value)
+{
+ xheader_print_n (xhdr, keyword, value, strlen (value));
+}
+
+void
+xheader_finish (struct xheader *xhdr)
+{
+ struct keyword_list *kp;
+
+ for (kp = keyword_override_list; kp; kp = kp->next)
+ code_string (kp->value, kp->pattern, xhdr);
+
+ xhdr->buffer = obstack_finish (xhdr->stk);
+}
+
+void
+xheader_destroy (struct xheader *xhdr)
+{
+ if (xhdr->stk)
+ {
+ obstack_free (xhdr->stk, NULL);
+ free (xhdr->stk);
+ xhdr->stk = NULL;
+ }
+ else
+ free (xhdr->buffer);
+ xhdr->buffer = 0;
+ xhdr->size = 0;
+}
+
+
+/* Buildable strings */
+
+void
+xheader_string_begin (struct xheader *xhdr)
+{
+ xhdr->string_length = 0;
+}
+
+void
+xheader_string_add (struct xheader *xhdr, char const *s)
+{
+ if (xhdr->buffer)
+ return;
+ xheader_init (xhdr);
+ xhdr->string_length += strlen (s);
+ x_obstack_grow (xhdr, s, strlen (s));
+}
+
+bool
+xheader_string_end (struct xheader *xhdr, char const *keyword)
+{
+ uintmax_t len;
+ uintmax_t p;
+ uintmax_t n = 0;
+ size_t size;
+ char nbuf[UINTMAX_STRSIZE_BOUND];
+ char const *np;
+ char *cp;
+
+ if (xhdr->buffer)
+ return false;
+ xheader_init (xhdr);
+
+ len = strlen (keyword) + xhdr->string_length + 3; /* ' ' + '=' + '\n' */
+
+ do
+ {
+ p = n;
+ np = umaxtostr (len + p, nbuf);
+ n = nbuf + sizeof nbuf - 1 - np;
+ }
+ while (n != p);
+
+ p = strlen (keyword) + n + 2;
+ size = p;
+ if (size != p)
+ {
+ ERROR ((0, 0,
+ _("Generated keyword/value pair is too long (keyword=%s, length=%s)"),
+ keyword, nbuf));
+ obstack_free (xhdr->stk, obstack_finish (xhdr->stk));
+ return false;
+ }
+ x_obstack_blank (xhdr, p);
+ x_obstack_1grow (xhdr, '\n');
+ cp = (char*) obstack_next_free (xhdr->stk) - xhdr->string_length - p - 1;
+ memmove (cp + p, cp, xhdr->string_length);
+ cp = stpcpy (cp, np);
+ *cp++ = ' ';
+ cp = stpcpy (cp, keyword);
+ *cp++ = '=';
+ return true;
+}
+
+
+/* Implementations */
+
+static void
+out_of_range_header (char const *keyword, char const *value,
+ intmax_t minval, uintmax_t maxval)
+{
+ char minval_buf[INT_BUFSIZE_BOUND (intmax_t)];
+ char maxval_buf[UINTMAX_STRSIZE_BOUND];
+ char *minval_string = imaxtostr (minval, minval_buf);
+ char *maxval_string = umaxtostr (maxval, maxval_buf);
+
+ /* TRANSLATORS: The first %s is the pax extended header keyword
+ (atime, gid, etc.). */
+ ERROR ((0, 0, _("Extended header %s=%s is out of range %s..%s"),
+ keyword, value, minval_string, maxval_string));
+}
+
+static void
+code_string (char const *string, char const *keyword, struct xheader *xhdr)
+{
+ char *outstr;
+ if (!utf8_convert (true, string, &outstr))
+ {
+ /* FIXME: report error */
+ outstr = xstrdup (string);
+ }
+ xheader_print (xhdr, keyword, outstr);
+ free (outstr);
+}
+
+static void
+decode_string (char **string, char const *arg)
+{
+ if (*string)
+ {
+ free (*string);
+ *string = NULL;
+ }
+ if (!utf8_convert (false, arg, string))
+ {
+ /* FIXME: report error and act accordingly to --pax invalid=UTF-8 */
+ assign_string (string, arg);
+ }
+}
+
+static void
+code_time (struct timespec t, char const *keyword, struct xheader *xhdr)
+{
+ char buf[TIMESPEC_STRSIZE_BOUND];
+ xheader_print (xhdr, keyword, code_timespec (t, buf));
+}
+
+static bool
+decode_time (struct timespec *ts, char const *arg, char const *keyword)
+{
+ char *arg_lim;
+ struct timespec t = decode_timespec (arg, &arg_lim, true);
+
+ if (! valid_timespec (t))
+ {
+ if (arg < arg_lim && !*arg_lim)
+ out_of_range_header (keyword, arg, TYPE_MINIMUM (time_t),
+ TYPE_MAXIMUM (time_t));
+ else
+ ERROR ((0, 0, _("Malformed extended header: invalid %s=%s"),
+ keyword, arg));
+ return false;
+ }
+
+ *ts = t;
+ return true;
+}
+
+static void
+code_signed_num (uintmax_t value, char const *keyword,
+ intmax_t minval, uintmax_t maxval, struct xheader *xhdr)
+{
+ char sbuf[SYSINT_BUFSIZE];
+ xheader_print (xhdr, keyword, sysinttostr (value, minval, maxval, sbuf));
+}
+
+static void
+code_num (uintmax_t value, char const *keyword, struct xheader *xhdr)
+{
+ code_signed_num (value, keyword, 0, UINTMAX_MAX, xhdr);
+}
+
+static bool
+decode_signed_num (intmax_t *num, char const *arg,
+ intmax_t minval, uintmax_t maxval,
+ char const *keyword)
+{
+ char *arg_lim;
+ intmax_t u = strtosysint (arg, &arg_lim, minval, maxval);
+
+ if (errno == EINVAL || *arg_lim)
+ {
+ ERROR ((0, 0, _("Malformed extended header: invalid %s=%s"),
+ keyword, arg));
+ return false;
+ }
+
+ if (errno == ERANGE)
+ {
+ out_of_range_header (keyword, arg, minval, maxval);
+ return false;
+ }
+
+ *num = u;
+ return true;
+}
+
+static bool
+decode_num (uintmax_t *num, char const *arg, uintmax_t maxval,
+ char const *keyword)
+{
+ intmax_t i;
+ if (! decode_signed_num (&i, arg, 0, maxval, keyword))
+ return false;
+ *num = i;
+ return true;
+}
+
+static void
+dummy_coder (struct tar_stat_info const *st __attribute__ ((unused)),
+ char const *keyword __attribute__ ((unused)),
+ struct xheader *xhdr __attribute__ ((unused)),
+ void const *data __attribute__ ((unused)))
+{
+}
+
+static void
+dummy_decoder (struct tar_stat_info *st __attribute__ ((unused)),
+ char const *keyword __attribute__ ((unused)),
+ char const *arg __attribute__ ((unused)),
+ size_t size __attribute__((unused)))
+{
+}
+
+static void
+atime_coder (struct tar_stat_info const *st, char const *keyword,
+ struct xheader *xhdr, void const *data __attribute__ ((unused)))
+{
+ code_time (st->atime, keyword, xhdr);
+}
+
+static void
+atime_decoder (struct tar_stat_info *st,
+ char const *keyword,
+ char const *arg,
+ size_t size __attribute__((unused)))
+{
+ struct timespec ts;
+ if (decode_time (&ts, arg, keyword))
+ st->atime = ts;
+}
+
+static void
+gid_coder (struct tar_stat_info const *st, char const *keyword,
+ struct xheader *xhdr, void const *data __attribute__ ((unused)))
+{
+ code_signed_num (st->stat.st_gid, keyword,
+ TYPE_MINIMUM (gid_t), TYPE_MAXIMUM (gid_t), xhdr);
+}
+
+static void
+gid_decoder (struct tar_stat_info *st,
+ char const *keyword,
+ char const *arg,
+ size_t size __attribute__((unused)))
+{
+ intmax_t u;
+ if (decode_signed_num (&u, arg, TYPE_MINIMUM (gid_t),
+ TYPE_MAXIMUM (gid_t), keyword))
+ st->stat.st_gid = u;
+}
+
+static void
+gname_coder (struct tar_stat_info const *st, char const *keyword,
+ struct xheader *xhdr, void const *data __attribute__ ((unused)))
+{
+ code_string (st->gname, keyword, xhdr);
+}
+
+static void
+gname_decoder (struct tar_stat_info *st,
+ char const *keyword __attribute__((unused)),
+ char const *arg,
+ size_t size __attribute__((unused)))
+{
+ decode_string (&st->gname, arg);
+}
+
+static void
+linkpath_coder (struct tar_stat_info const *st, char const *keyword,
+ struct xheader *xhdr, void const *data __attribute__ ((unused)))
+{
+ code_string (st->link_name, keyword, xhdr);
+}
+
+static void
+linkpath_decoder (struct tar_stat_info *st,
+ char const *keyword __attribute__((unused)),
+ char const *arg,
+ size_t size __attribute__((unused)))
+{
+ decode_string (&st->link_name, arg);
+}
+
+static void
+ctime_coder (struct tar_stat_info const *st, char const *keyword,
+ struct xheader *xhdr, void const *data __attribute__ ((unused)))
+{
+ code_time (st->ctime, keyword, xhdr);
+}
+
+static void
+ctime_decoder (struct tar_stat_info *st,
+ char const *keyword,
+ char const *arg,
+ size_t size __attribute__((unused)))
+{
+ struct timespec ts;
+ if (decode_time (&ts, arg, keyword))
+ st->ctime = ts;
+}
+
+static void
+mtime_coder (struct tar_stat_info const *st, char const *keyword,
+ struct xheader *xhdr, void const *data)
+{
+ struct timespec const *mtime = data;
+ code_time (mtime ? *mtime : st->mtime, keyword, xhdr);
+}
+
+static void
+mtime_decoder (struct tar_stat_info *st,
+ char const *keyword,
+ char const *arg,
+ size_t size __attribute__((unused)))
+{
+ struct timespec ts;
+ if (decode_time (&ts, arg, keyword))
+ st->mtime = ts;
+}
+
+static void
+path_coder (struct tar_stat_info const *st, char const *keyword,
+ struct xheader *xhdr, void const *data __attribute__ ((unused)))
+{
+ code_string (st->file_name, keyword, xhdr);
+}
+
+static void
+path_decoder (struct tar_stat_info *st,
+ char const *keyword __attribute__((unused)),
+ char const *arg,
+ size_t size __attribute__((unused)))
+{
+ decode_string (&st->orig_file_name, arg);
+ decode_string (&st->file_name, arg);
+ st->had_trailing_slash = strip_trailing_slashes (st->file_name);
+}
+
+static void
+size_coder (struct tar_stat_info const *st, char const *keyword,
+ struct xheader *xhdr, void const *data __attribute__ ((unused)))
+{
+ code_num (st->stat.st_size, keyword, xhdr);
+}
+
+static void
+size_decoder (struct tar_stat_info *st,
+ char const *keyword,
+ char const *arg,
+ size_t size __attribute__((unused)))
+{
+ uintmax_t u;
+ if (decode_num (&u, arg, TYPE_MAXIMUM (off_t), keyword))
+ st->stat.st_size = u;
+}
+
+static void
+uid_coder (struct tar_stat_info const *st, char const *keyword,
+ struct xheader *xhdr, void const *data __attribute__ ((unused)))
+{
+ code_signed_num (st->stat.st_uid, keyword,
+ TYPE_MINIMUM (uid_t), TYPE_MAXIMUM (uid_t), xhdr);
+}
+
+static void
+uid_decoder (struct tar_stat_info *st,
+ char const *keyword,
+ char const *arg,
+ size_t size __attribute__((unused)))
+{
+ intmax_t u;
+ if (decode_signed_num (&u, arg, TYPE_MINIMUM (uid_t),
+ TYPE_MAXIMUM (uid_t), keyword))
+ st->stat.st_uid = u;
+}
+
+static void
+uname_coder (struct tar_stat_info const *st, char const *keyword,
+ struct xheader *xhdr, void const *data __attribute__ ((unused)))
+{
+ code_string (st->uname, keyword, xhdr);
+}
+
+static void
+uname_decoder (struct tar_stat_info *st,
+ char const *keyword __attribute__((unused)),
+ char const *arg,
+ size_t size __attribute__((unused)))
+{
+ decode_string (&st->uname, arg);
+}
+
+static void
+sparse_size_coder (struct tar_stat_info const *st, char const *keyword,
+ struct xheader *xhdr, void const *data)
+{
+ size_coder (st, keyword, xhdr, data);
+}
+
+static void
+sparse_size_decoder (struct tar_stat_info *st,
+ char const *keyword,
+ char const *arg,
+ size_t size __attribute__((unused)))
+{
+ uintmax_t u;
+ if (decode_num (&u, arg, TYPE_MAXIMUM (off_t), keyword))
+ {
+ st->real_size_set = 1;
+ st->real_size = u;
+ }
+}
+
+static void
+sparse_numblocks_coder (struct tar_stat_info const *st, char const *keyword,
+ struct xheader *xhdr,
+ void const *data __attribute__ ((unused)))
+{
+ code_num (st->sparse_map_avail, keyword, xhdr);
+}
+
+static void
+sparse_numblocks_decoder (struct tar_stat_info *st,
+ char const *keyword,
+ char const *arg,
+ size_t size __attribute__((unused)))
+{
+ uintmax_t u;
+ if (decode_num (&u, arg, SIZE_MAX, keyword))
+ {
+ st->sparse_map_size = u;
+ st->sparse_map = xcalloc (u, sizeof st->sparse_map[0]);
+ st->sparse_map_avail = 0;
+ }
+}
+
+static void
+sparse_offset_coder (struct tar_stat_info const *st, char const *keyword,
+ struct xheader *xhdr, void const *data)
+{
+ size_t const *pi = data;
+ code_num (st->sparse_map[*pi].offset, keyword, xhdr);
+}
+
+static void
+sparse_offset_decoder (struct tar_stat_info *st,
+ char const *keyword,
+ char const *arg,
+ size_t size __attribute__((unused)))
+{
+ uintmax_t u;
+ if (decode_num (&u, arg, TYPE_MAXIMUM (off_t), keyword))
+ {
+ if (st->sparse_map_avail < st->sparse_map_size)
+ st->sparse_map[st->sparse_map_avail].offset = u;
+ else
+ ERROR ((0, 0, _("Malformed extended header: excess %s=%s"),
+ "GNU.sparse.offset", arg));
+ }
+}
+
+static void
+sparse_numbytes_coder (struct tar_stat_info const *st, char const *keyword,
+ struct xheader *xhdr, void const *data)
+{
+ size_t const *pi = data;
+ code_num (st->sparse_map[*pi].numbytes, keyword, xhdr);
+}
+
+static void
+sparse_numbytes_decoder (struct tar_stat_info *st,
+ char const *keyword,
+ char const *arg,
+ size_t size __attribute__((unused)))
+{
+ uintmax_t u;
+ if (decode_num (&u, arg, TYPE_MAXIMUM (off_t), keyword))
+ {
+ if (st->sparse_map_avail < st->sparse_map_size)
+ st->sparse_map[st->sparse_map_avail++].numbytes = u;
+ else
+ ERROR ((0, 0, _("Malformed extended header: excess %s=%s"),
+ keyword, arg));
+ }
+}
+
+static void
+sparse_map_decoder (struct tar_stat_info *st,
+ char const *keyword,
+ char const *arg,
+ size_t size __attribute__((unused)))
+{
+ int offset = 1;
+ struct sp_array e;
+
+ st->sparse_map_avail = 0;
+ while (1)
+ {
+ intmax_t u;
+ char *delim;
+
+ if (!ISDIGIT (*arg))
+ {
+ ERROR ((0, 0, _("Malformed extended header: invalid %s=%s"),
+ keyword, arg));
+ return;
+ }
+
+ errno = 0;
+ u = strtoimax (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_header (keyword, arg, 0, TYPE_MAXIMUM (off_t));
+ return;
+ }
+ }
+ else
+ {
+ e.numbytes = u;
+ if (errno == ERANGE)
+ {
+ out_of_range_header (keyword, arg, 0, TYPE_MAXIMUM (off_t));
+ return;
+ }
+ if (st->sparse_map_avail < st->sparse_map_size)
+ st->sparse_map[st->sparse_map_avail++] = e;
+ else
+ {
+ ERROR ((0, 0, _("Malformed extended header: excess %s=%s"),
+ keyword, arg));
+ return;
+ }
+ }
+
+ offset = !offset;
+
+ if (*delim == 0)
+ break;
+ else if (*delim != ',')
+ {
+ ERROR ((0, 0,
+ _("Malformed extended header: invalid %s: unexpected delimiter %c"),
+ keyword, *delim));
+ return;
+ }
+
+ arg = delim + 1;
+ }
+
+ if (!offset)
+ ERROR ((0, 0,
+ _("Malformed extended header: invalid %s: odd number of values"),
+ keyword));
+}
+
+static void
+dumpdir_coder (struct tar_stat_info const *st, char const *keyword,
+ struct xheader *xhdr, void const *data)
+{
+ xheader_print_n (xhdr, keyword, data, dumpdir_size (data));
+}
+
+static void
+dumpdir_decoder (struct tar_stat_info *st,
+ char const *keyword __attribute__((unused)),
+ char const *arg,
+ size_t size)
+{
+ st->dumpdir = xmalloc (size);
+ memcpy (st->dumpdir, arg, size);
+}
+
+static void
+volume_label_coder (struct tar_stat_info const *st, char const *keyword,
+ struct xheader *xhdr, void const *data)
+{
+ code_string (data, keyword, xhdr);
+}
+
+static void
+volume_label_decoder (struct tar_stat_info *st,
+ char const *keyword __attribute__((unused)),
+ char const *arg,
+ size_t size __attribute__((unused)))
+{
+ decode_string (&volume_label, arg);
+}
+
+static void
+volume_size_coder (struct tar_stat_info const *st, char const *keyword,
+ struct xheader *xhdr, void const *data)
+{
+ off_t const *v = data;
+ code_num (*v, keyword, xhdr);
+}
+
+static void
+volume_size_decoder (struct tar_stat_info *st,
+ char const *keyword,
+ char const *arg, size_t size)
+{
+ uintmax_t u;
+ if (decode_num (&u, arg, TYPE_MAXIMUM (uintmax_t), keyword))
+ continued_file_size = u;
+}
+
+/* FIXME: Merge with volume_size_coder */
+static void
+volume_offset_coder (struct tar_stat_info const *st, char const *keyword,
+ struct xheader *xhdr, void const *data)
+{
+ off_t const *v = data;
+ code_num (*v, keyword, xhdr);
+}
+
+static void
+volume_offset_decoder (struct tar_stat_info *st,
+ char const *keyword,
+ char const *arg, size_t size)
+{
+ uintmax_t u;
+ if (decode_num (&u, arg, TYPE_MAXIMUM (uintmax_t), keyword))
+ continued_file_offset = u;
+}
+
+static void
+volume_filename_decoder (struct tar_stat_info *st,
+ char const *keyword __attribute__((unused)),
+ char const *arg,
+ size_t size __attribute__((unused)))
+{
+ decode_string (&continued_file_name, arg);
+}
+
+static void
+xattr_selinux_coder (struct tar_stat_info const *st, char const *keyword,
+ struct xheader *xhdr, void const *data)
+{
+ code_string (st->cntx_name, keyword, xhdr);
+}
+
+static void
+xattr_selinux_decoder (struct tar_stat_info *st,
+ char const *keyword, char const *arg, size_t size)
+{
+ decode_string (&st->cntx_name, arg);
+}
+
+static void
+xattr_acls_a_coder (struct tar_stat_info const *st , char const *keyword,
+ struct xheader *xhdr, void const *data)
+{
+ xheader_print_n (xhdr, keyword, st->acls_a_ptr, st->acls_a_len);
+}
+
+static void
+xattr_acls_a_decoder (struct tar_stat_info *st,
+ char const *keyword, char const *arg, size_t size)
+{
+ st->acls_a_ptr = xmemdup (arg, size + 1);
+ st->acls_a_len = size;
+}
+
+static void
+xattr_acls_d_coder (struct tar_stat_info const *st , char const *keyword,
+ struct xheader *xhdr, void const *data)
+{
+ xheader_print_n (xhdr, keyword, st->acls_d_ptr, st->acls_d_len);
+}
+
+static void
+xattr_acls_d_decoder (struct tar_stat_info *st,
+ char const *keyword, char const *arg, size_t size)
+{
+ st->acls_d_ptr = xmemdup (arg, size + 1);
+ st->acls_d_len = size;
+}
+
+static void
+xattr_coder (struct tar_stat_info const *st, char const *keyword,
+ struct xheader *xhdr, void const *data)
+{
+ struct xattr_array *xattr_map = st->xattr_map;
+ const size_t *off = data;
+ xheader_print_n (xhdr, keyword,
+ xattr_map[*off].xval_ptr, xattr_map[*off].xval_len);
+}
+
+static void
+xattr_decoder (struct tar_stat_info *st,
+ char const *keyword, char const *arg, size_t size)
+{
+ char *xstr, *xkey;
+
+ /* copy keyword */
+ size_t klen_raw = strlen (keyword);
+ xkey = alloca (klen_raw + 1);
+ memcpy (xkey, keyword, klen_raw + 1) /* including null-terminating */;
+
+ /* copy value */
+ xstr = alloca (size + 1);
+ memcpy (xstr, arg, size + 1); /* separator included, for GNU tar '\n' */;
+
+ xattr_decode_keyword (xkey);
+
+ xheader_xattr_add (st, xkey + strlen("SCHILY.xattr."), xstr, size);
+}
+
+static void
+sparse_major_coder (struct tar_stat_info const *st, char const *keyword,
+ struct xheader *xhdr, void const *data)
+{
+ code_num (st->sparse_major, keyword, xhdr);
+}
+
+static void
+sparse_major_decoder (struct tar_stat_info *st,
+ char const *keyword,
+ char const *arg,
+ size_t size)
+{
+ uintmax_t u;
+ if (decode_num (&u, arg, TYPE_MAXIMUM (unsigned), keyword))
+ st->sparse_major = u;
+}
+
+static void
+sparse_minor_coder (struct tar_stat_info const *st, char const *keyword,
+ struct xheader *xhdr, void const *data)
+{
+ code_num (st->sparse_minor, keyword, xhdr);
+}
+
+static void
+sparse_minor_decoder (struct tar_stat_info *st,
+ char const *keyword,
+ char const *arg,
+ size_t size)
+{
+ uintmax_t u;
+ if (decode_num (&u, arg, TYPE_MAXIMUM (unsigned), keyword))
+ st->sparse_minor = u;
+}
+
+struct xhdr_tab const xhdr_tab[] = {
+ { "atime", atime_coder, atime_decoder, 0, false },
+ { "comment", dummy_coder, dummy_decoder, 0, false },
+ { "charset", dummy_coder, dummy_decoder, 0, false },
+ { "ctime", ctime_coder, ctime_decoder, 0, false },
+ { "gid", gid_coder, gid_decoder, 0, false },
+ { "gname", gname_coder, gname_decoder, 0, false },
+ { "linkpath", linkpath_coder, linkpath_decoder, 0, false },
+ { "mtime", mtime_coder, mtime_decoder, 0, false },
+ { "path", path_coder, path_decoder, 0, false },
+ { "size", size_coder, size_decoder, 0, false },
+ { "uid", uid_coder, uid_decoder, 0, false },
+ { "uname", uname_coder, uname_decoder, 0, false },
+
+ /* Sparse file handling */
+ { "GNU.sparse.name", path_coder, path_decoder,
+ XHDR_PROTECTED, false },
+ { "GNU.sparse.major", sparse_major_coder, sparse_major_decoder,
+ XHDR_PROTECTED, false },
+ { "GNU.sparse.minor", sparse_minor_coder, sparse_minor_decoder,
+ XHDR_PROTECTED, false },
+ { "GNU.sparse.realsize", sparse_size_coder, sparse_size_decoder,
+ XHDR_PROTECTED, false },
+ { "GNU.sparse.numblocks", sparse_numblocks_coder, sparse_numblocks_decoder,
+ XHDR_PROTECTED, false },
+
+ /* tar 1.14 - 1.15.90 keywords. */
+ { "GNU.sparse.size", sparse_size_coder, sparse_size_decoder,
+ XHDR_PROTECTED, false },
+ /* 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_coder, sparse_offset_decoder,
+ XHDR_PROTECTED, false },
+ { "GNU.sparse.numbytes", sparse_numbytes_coder, sparse_numbytes_decoder,
+ XHDR_PROTECTED, false },
+ /* tar 1.15.90 keyword, introduced to remove the above-mentioned conflict. */
+ { "GNU.sparse.map", NULL /* Unused, see pax_dump_header() */,
+ sparse_map_decoder, 0, false },
+
+ { "GNU.dumpdir", dumpdir_coder, dumpdir_decoder,
+ XHDR_PROTECTED, false },
+
+ /* Keeps the tape/volume label. May be present only in the global headers.
+ Equivalent to GNUTYPE_VOLHDR. */
+ { "GNU.volume.label", volume_label_coder, volume_label_decoder,
+ XHDR_PROTECTED | XHDR_GLOBAL, false },
+
+ /* These may be present in a first global header of the archive.
+ They provide the same functionality as GNUTYPE_MULTIVOL header.
+ The GNU.volume.size keeps the real_s_sizeleft value, which is
+ otherwise kept in the size field of a multivolume header. The
+ GNU.volume.offset keeps the offset of the start of this volume,
+ otherwise kept in oldgnu_header.offset. */
+ { "GNU.volume.filename", volume_label_coder, volume_filename_decoder,
+ XHDR_PROTECTED | XHDR_GLOBAL, false },
+ { "GNU.volume.size", volume_size_coder, volume_size_decoder,
+ XHDR_PROTECTED | XHDR_GLOBAL, false },
+ { "GNU.volume.offset", volume_offset_coder, volume_offset_decoder,
+ XHDR_PROTECTED | XHDR_GLOBAL, false },
+
+ /* We get the SELinux value from filecon, so add a namespace for SELinux
+ instead of storing it in SCHILY.xattr.* (which would be RAW). */
+ { "RHT.security.selinux",
+ xattr_selinux_coder, xattr_selinux_decoder, 0, false },
+
+ /* ACLs, use the star format... */
+ { "SCHILY.acl.access",
+ xattr_acls_a_coder, xattr_acls_a_decoder, 0, false },
+
+ { "SCHILY.acl.default",
+ xattr_acls_d_coder, xattr_acls_d_decoder, 0, false },
+
+ /* We are storing all extended attributes using this rule even if some of them
+ were stored by some previous rule (duplicates) -- we just have to make sure
+ they are restored *only once* during extraction later on. */
+ { "SCHILY.xattr", xattr_coder, xattr_decoder, 0, true },
+
+ { NULL, NULL, NULL, 0, false }
+};