diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 55 | ||||
-rw-r--r-- | src/Makefile.in | 1587 | ||||
-rw-r--r-- | src/arith.h | 28 | ||||
-rw-r--r-- | src/buffer.c | 2048 | ||||
-rw-r--r-- | src/checkpoint.c | 438 | ||||
-rw-r--r-- | src/common.h | 974 | ||||
-rw-r--r-- | src/compare.c | 649 | ||||
-rw-r--r-- | src/create.c | 1972 | ||||
-rw-r--r-- | src/delete.c | 394 | ||||
-rw-r--r-- | src/exclist.c | 329 | ||||
-rw-r--r-- | src/exit.c | 39 | ||||
-rw-r--r-- | src/extract.c | 1820 | ||||
-rw-r--r-- | src/incremen.c | 1805 | ||||
-rw-r--r-- | src/list.c | 1463 | ||||
-rw-r--r-- | src/map.c | 283 | ||||
-rw-r--r-- | src/misc.c | 1250 | ||||
-rw-r--r-- | src/names.c | 1838 | ||||
-rw-r--r-- | src/sparse.c | 1295 | ||||
-rw-r--r-- | src/suffix.c | 114 | ||||
-rw-r--r-- | src/system.c | 900 | ||||
-rw-r--r-- | src/tar.c | 2852 | ||||
-rw-r--r-- | src/tar.h | 379 | ||||
-rw-r--r-- | src/transform.c | 637 | ||||
-rw-r--r-- | src/unlink.c | 237 | ||||
-rw-r--r-- | src/update.c | 234 | ||||
-rw-r--r-- | src/utf8.c | 99 | ||||
-rw-r--r-- | src/warning.c | 107 | ||||
-rw-r--r-- | src/xattrs.c | 755 | ||||
-rw-r--r-- | src/xattrs.h | 50 | ||||
-rw-r--r-- | src/xheader.c | 1798 |
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 (¤t_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 (¤t_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 (¤t_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 (¤t_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 (¤t_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 (¤t_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 (¤t_stat_info, NULL); + } + else + { + report_difference (¤t_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 (¤t_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 (¤t_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 (¤t_stat_info, _("File type differs")); + else if ((current_stat_info.stat.st_mode & MODE_ALL) != + (stat_data.st_mode & MODE_ALL)) + report_difference (¤t_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 (¤t_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 (¤t_stat_info, _("Mode differs")); + + if (!sys_compare_uid (&stat_data, ¤t_stat_info.stat)) + report_difference (¤t_stat_info, _("Uid differs")); + if (!sys_compare_gid (&stat_data, ¤t_stat_info.stat)) + report_difference (¤t_stat_info, _("Gid differs")); + + if (tar_timespec_cmp (get_stat_mtime (&stat_data), + current_stat_info.mtime)) + report_difference (¤t_stat_info, _("Mod time differs")); + if (current_header->header.typeflag != GNUTYPE_SPARSE + && stat_data.st_size != current_stat_info.stat.st_size) + { + report_difference (¤t_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 (¤t_stat_info, NULL); + } + else + { + int status; + + if (current_stat_info.is_sparse) + sparse_diff_file (diff_handle, ¤t_stat_info); + else + read_and_process (¤t_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 (¤t_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 (¤t_stat_info, NULL); + } + else if (status != len + || memcmp (current_stat_info.link_name, linkbuf, len) != 0) + report_difference (¤t_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 (¤t_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 (¤t_stat_info, _("Device number differs")); + return; + } + + if ((current_stat_info.stat.st_mode & MODE_ALL) != + (stat_data.st_mode & MODE_ALL)) + report_difference (¤t_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 (¤t_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 (¤t_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 (¤t_stat_info, NULL); + skip_member (); + return; + } + + if (lseek (fd, offset, SEEK_SET) < 0) + { + seek_error_details (current_stat_info.file_name, offset); + report_difference (¤t_stat_info, NULL); + } + else + read_and_process (¤t_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 (¤t_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 (¤t_stat_info)) + diff_dumpdir (¤t_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 (¤t_header, + ¤t_stat_info, + read_header_auto); + + if (status == HEADER_FAILURE) + { + int counter = 0; + + do + { + counter++; + set_next_block_after (current_header); + status = read_header (¤t_header, ¤t_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 (¤t_header, ¤t_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, ¤t_stat_info, ¤t_format, 1); + diff_archive (); + tar_stat_destroy (¤t_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 (¤t_header, + ¤t_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 (¤t_header, ¤t_stat_info, + read_header_auto); + + xheader_decode (¤t_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, ¤t_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, ¤t_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', ¤t_stat_info); + if (fd < 0) + { + skip_member (); + return 0; + } + } + else + { + int file_created = 0; + if (set_xattr (file_name, ¤t_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, ¤t_mode, + ¤t_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 (¤t_stat_info); + if (current_stat_info.is_sparse) + sparse_extract_file (fd, ¤t_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, ¤t_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 (¤t_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, ¤t_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, ¤t_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, ¤t_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, ¤t_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 (¤t_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 (¤t_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 (¤t_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 (¤t_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 (¤t_stat_info); + + status = read_header (¤t_header, ¤t_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, ¤t_stat_info, + ¤t_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, + ¤t_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 (¤t_header, ¤t_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 (¤t_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 (¤t_stat_info, current_header, block_ordinal); + + if (incremental_option) + { + if (verbose_option > 2) + { + if (is_dumpdir (¤t_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 (¤t_stat_info); + + if (current_stat_info.is_sparse) + sparse_skip_file (¤t_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 (¤t_header, ¤t_stat_info, read_header_auto) + == HEADER_SUCCESS) + { + decode_header (current_header, + ¤t_stat_info, ¤t_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 (¤t_header, + ¤t_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, ¤t_stat_info, + ¤t_format, 0); + transform_stat_info (current_header->header.typeflag, + ¤t_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 (¤t_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 } +}; |