summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am46
-rw-r--r--src/Makefile.in776
-rw-r--r--src/arith.h27
-rw-r--r--src/buffer.c1696
-rw-r--r--src/common.h738
-rw-r--r--src/compare.c609
-rw-r--r--src/create.c1785
-rw-r--r--src/delete.c391
-rw-r--r--src/extract.c1379
-rw-r--r--src/incremen.c1473
-rw-r--r--src/list.c1341
-rw-r--r--src/misc.c748
-rw-r--r--src/names.c1022
-rw-r--r--src/sparse.c1175
-rw-r--r--src/system.c844
-rw-r--r--src/tar.c2427
-rw-r--r--src/tar.h331
-rw-r--r--src/transform.c527
-rw-r--r--src/update.c210
-rw-r--r--src/utf8.c97
-rw-r--r--src/xheader.c1516
21 files changed, 19158 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..61f6cbd
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,46 @@
+# Makefile for GNU tar sources.
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1999, 2000, 2001, 2003, 2006
+# 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 2, 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, write to the Free Software
+## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+## 02110-1301, USA.
+
+bin_PROGRAMS = tar
+
+noinst_HEADERS = arith.h common.h tar.h
+tar_SOURCES = \
+ buffer.c\
+ compare.c\
+ create.c\
+ delete.c\
+ extract.c\
+ xheader.c\
+ incremen.c\
+ list.c\
+ misc.c\
+ names.c\
+ sparse.c\
+ system.c\
+ tar.c\
+ transform.c\
+ update.c\
+ utf8.c
+
+INCLUDES = -I$(top_srcdir)/lib -I../ -I../lib
+
+LDADD = ../lib/libtar.a $(LIBINTL) $(LIBICONV)
+
+tar_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME)
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 0000000..148448a
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,776 @@
+# Makefile.in generated by automake 1.10a from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007 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 (C) 1994, 1995, 1996, 1997, 1999, 2000, 2001, 2003, 2006
+# Free Software Foundation, Inc.
+
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+bin_PROGRAMS = tar$(EXEEXT)
+subdir = src
+DIST_COMMON = $(noinst_HEADERS) $(srcdir)/Makefile.am \
+ $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/absolute-header.m4 \
+ $(top_srcdir)/m4/alloca.m4 $(top_srcdir)/m4/allocsa.m4 \
+ $(top_srcdir)/m4/argmatch.m4 $(top_srcdir)/m4/argp.m4 \
+ $(top_srcdir)/m4/backupfile.m4 $(top_srcdir)/m4/bison.m4 \
+ $(top_srcdir)/m4/canonicalize-lgpl.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/closeout.m4 \
+ $(top_srcdir)/m4/codeset.m4 $(top_srcdir)/m4/d-ino.m4 \
+ $(top_srcdir)/m4/dirfd.m4 $(top_srcdir)/m4/dirname.m4 \
+ $(top_srcdir)/m4/dos.m4 $(top_srcdir)/m4/double-slash-root.m4 \
+ $(top_srcdir)/m4/dup2.m4 $(top_srcdir)/m4/eealloc.m4 \
+ $(top_srcdir)/m4/eoverflow.m4 $(top_srcdir)/m4/error.m4 \
+ $(top_srcdir)/m4/exclude.m4 $(top_srcdir)/m4/exitfail.m4 \
+ $(top_srcdir)/m4/extensions.m4 $(top_srcdir)/m4/fchdir.m4 \
+ $(top_srcdir)/m4/fcntl-safer.m4 $(top_srcdir)/m4/fcntl_h.m4 \
+ $(top_srcdir)/m4/fileblocks.m4 $(top_srcdir)/m4/float_h.m4 \
+ $(top_srcdir)/m4/fnmatch.m4 $(top_srcdir)/m4/fpending.m4 \
+ $(top_srcdir)/m4/ftruncate.m4 \
+ $(top_srcdir)/m4/getcwd-abort-bug.m4 \
+ $(top_srcdir)/m4/getcwd-path-max.m4 $(top_srcdir)/m4/getcwd.m4 \
+ $(top_srcdir)/m4/getdate.m4 $(top_srcdir)/m4/getdelim.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/hash.m4 \
+ $(top_srcdir)/m4/human.m4 $(top_srcdir)/m4/iconv.m4 \
+ $(top_srcdir)/m4/inline.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/lchown.m4 $(top_srcdir)/m4/lib-ld.m4 \
+ $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \
+ $(top_srcdir)/m4/localcharset.m4 $(top_srcdir)/m4/longlong.m4 \
+ $(top_srcdir)/m4/lstat.m4 $(top_srcdir)/m4/mbchar.m4 \
+ $(top_srcdir)/m4/mbiter.m4 $(top_srcdir)/m4/mbrtowc.m4 \
+ $(top_srcdir)/m4/mbscasecmp.m4 $(top_srcdir)/m4/mbstate_t.m4 \
+ $(top_srcdir)/m4/memchr.m4 $(top_srcdir)/m4/mempcpy.m4 \
+ $(top_srcdir)/m4/memrchr.m4 $(top_srcdir)/m4/memset.m4 \
+ $(top_srcdir)/m4/mkdtemp.m4 $(top_srcdir)/m4/mktime.m4 \
+ $(top_srcdir)/m4/modechange.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/openat.m4 $(top_srcdir)/m4/pathmax.m4 \
+ $(top_srcdir)/m4/paxutils.m4 $(top_srcdir)/m4/po.m4 \
+ $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/m4/quote.m4 \
+ $(top_srcdir)/m4/quotearg.m4 $(top_srcdir)/m4/readlink.m4 \
+ $(top_srcdir)/m4/regex.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/setenv.m4 \
+ $(top_srcdir)/m4/sleep.m4 $(top_srcdir)/m4/ssize_t.m4 \
+ $(top_srcdir)/m4/stat-time.m4 $(top_srcdir)/m4/stdarg.m4 \
+ $(top_srcdir)/m4/stdbool.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/string_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/sys_stat_h.m4 \
+ $(top_srcdir)/m4/sys_time_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/timespec.m4 $(top_srcdir)/m4/tm_gmtoff.m4 \
+ $(top_srcdir)/m4/unistd-safer.m4 $(top_srcdir)/m4/unistd_h.m4 \
+ $(top_srcdir)/m4/unlinkdir.m4 $(top_srcdir)/m4/unlocked-io.m4 \
+ $(top_srcdir)/m4/utimbuf.m4 $(top_srcdir)/m4/utime.m4 \
+ $(top_srcdir)/m4/utimens.m4 $(top_srcdir)/m4/utimes-null.m4 \
+ $(top_srcdir)/m4/utimes.m4 $(top_srcdir)/m4/vasnprintf.m4 \
+ $(top_srcdir)/m4/vsnprintf.m4 $(top_srcdir)/m4/wchar.m4 \
+ $(top_srcdir)/m4/wchar_t.m4 $(top_srcdir)/m4/wctype.m4 \
+ $(top_srcdir)/m4/wcwidth.m4 $(top_srcdir)/m4/wint_t.m4 \
+ $(top_srcdir)/m4/xalloc.m4 $(top_srcdir)/m4/xgetcwd.m4 \
+ $(top_srcdir)/m4/xstrndup.m4 $(top_srcdir)/m4/xstrtol.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+binPROGRAMS_INSTALL = $(INSTALL_PROGRAM)
+PROGRAMS = $(bin_PROGRAMS)
+am_tar_OBJECTS = buffer.$(OBJEXT) compare.$(OBJEXT) create.$(OBJEXT) \
+ delete.$(OBJEXT) extract.$(OBJEXT) xheader.$(OBJEXT) \
+ incremen.$(OBJEXT) list.$(OBJEXT) misc.$(OBJEXT) \
+ names.$(OBJEXT) sparse.$(OBJEXT) system.$(OBJEXT) \
+ tar.$(OBJEXT) transform.$(OBJEXT) update.$(OBJEXT) \
+ utf8.$(OBJEXT)
+tar_OBJECTS = $(am_tar_OBJECTS)
+am__DEPENDENCIES_1 =
+am__DEPENDENCIES_2 = ../lib/libtar.a $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+tar_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1)
+DEFAULT_INCLUDES = -I. -I$(top_builddir)@am__isrc@
+depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp
+am__depfiles_maybe = depfiles
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+SOURCES = $(tar_SOURCES)
+DIST_SOURCES = $(tar_SOURCES)
+HEADERS = $(noinst_HEADERS)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ABSOLUTE_DIRENT_H = @ABSOLUTE_DIRENT_H@
+ABSOLUTE_FCNTL_H = @ABSOLUTE_FCNTL_H@
+ABSOLUTE_FLOAT_H = @ABSOLUTE_FLOAT_H@
+ABSOLUTE_INTTYPES_H = @ABSOLUTE_INTTYPES_H@
+ABSOLUTE_STDINT_H = @ABSOLUTE_STDINT_H@
+ABSOLUTE_STDIO_H = @ABSOLUTE_STDIO_H@
+ABSOLUTE_STDLIB_H = @ABSOLUTE_STDLIB_H@
+ABSOLUTE_STRING_H = @ABSOLUTE_STRING_H@
+ABSOLUTE_SYSEXITS_H = @ABSOLUTE_SYSEXITS_H@
+ABSOLUTE_SYS_STAT_H = @ABSOLUTE_SYS_STAT_H@
+ABSOLUTE_SYS_TIME_H = @ABSOLUTE_SYS_TIME_H@
+ABSOLUTE_TIME_H = @ABSOLUTE_TIME_H@
+ABSOLUTE_UNISTD_H = @ABSOLUTE_UNISTD_H@
+ABSOLUTE_WCHAR_H = @ABSOLUTE_WCHAR_H@
+ABSOLUTE_WCTYPE_H = @ABSOLUTE_WCTYPE_H@
+ACLOCAL = @ACLOCAL@
+ALLOCA = @ALLOCA@
+ALLOCA_H = @ALLOCA_H@
+AMTAR = @AMTAR@
+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@
+DIRENT_H = @DIRENT_H@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EOVERFLOW = @EOVERFLOW@
+EXEEXT = @EXEEXT@
+FCNTL_H = @FCNTL_H@
+FLOAT_H = @FLOAT_H@
+FNMATCH_H = @FNMATCH_H@
+GETOPT_H = @GETOPT_H@
+GLIBC21 = @GLIBC21@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GNULIB_CHOWN = @GNULIB_CHOWN@
+GNULIB_DUP2 = @GNULIB_DUP2@
+GNULIB_FCHDIR = @GNULIB_FCHDIR@
+GNULIB_FFLUSH = @GNULIB_FFLUSH@
+GNULIB_FPRINTF_POSIX = @GNULIB_FPRINTF_POSIX@
+GNULIB_FSEEK = @GNULIB_FSEEK@
+GNULIB_FSEEKO = @GNULIB_FSEEKO@
+GNULIB_FTELL = @GNULIB_FTELL@
+GNULIB_FTELLO = @GNULIB_FTELLO@
+GNULIB_FTRUNCATE = @GNULIB_FTRUNCATE@
+GNULIB_GETCWD = @GNULIB_GETCWD@
+GNULIB_GETLOGIN_R = @GNULIB_GETLOGIN_R@
+GNULIB_GETSUBOPT = @GNULIB_GETSUBOPT@
+GNULIB_IMAXABS = @GNULIB_IMAXABS@
+GNULIB_IMAXDIV = @GNULIB_IMAXDIV@
+GNULIB_LSEEK = @GNULIB_LSEEK@
+GNULIB_MBSCASECMP = @GNULIB_MBSCASECMP@
+GNULIB_MBSCASESTR = @GNULIB_MBSCASESTR@
+GNULIB_MBSCHR = @GNULIB_MBSCHR@
+GNULIB_MBSCSPN = @GNULIB_MBSCSPN@
+GNULIB_MBSLEN = @GNULIB_MBSLEN@
+GNULIB_MBSNCASECMP = @GNULIB_MBSNCASECMP@
+GNULIB_MBSPBRK = @GNULIB_MBSPBRK@
+GNULIB_MBSPCASECMP = @GNULIB_MBSPCASECMP@
+GNULIB_MBSRCHR = @GNULIB_MBSRCHR@
+GNULIB_MBSSEP = @GNULIB_MBSSEP@
+GNULIB_MBSSPN = @GNULIB_MBSSPN@
+GNULIB_MBSSTR = @GNULIB_MBSSTR@
+GNULIB_MBSTOK_R = @GNULIB_MBSTOK_R@
+GNULIB_MEMMEM = @GNULIB_MEMMEM@
+GNULIB_MEMPCPY = @GNULIB_MEMPCPY@
+GNULIB_MEMRCHR = @GNULIB_MEMRCHR@
+GNULIB_MKDTEMP = @GNULIB_MKDTEMP@
+GNULIB_MKSTEMP = @GNULIB_MKSTEMP@
+GNULIB_PRINTF_POSIX = @GNULIB_PRINTF_POSIX@
+GNULIB_READLINK = @GNULIB_READLINK@
+GNULIB_SLEEP = @GNULIB_SLEEP@
+GNULIB_SNPRINTF = @GNULIB_SNPRINTF@
+GNULIB_SPRINTF_POSIX = @GNULIB_SPRINTF_POSIX@
+GNULIB_STPCPY = @GNULIB_STPCPY@
+GNULIB_STPNCPY = @GNULIB_STPNCPY@
+GNULIB_STRCASESTR = @GNULIB_STRCASESTR@
+GNULIB_STRCHRNUL = @GNULIB_STRCHRNUL@
+GNULIB_STRDUP = @GNULIB_STRDUP@
+GNULIB_STRNDUP = @GNULIB_STRNDUP@
+GNULIB_STRNLEN = @GNULIB_STRNLEN@
+GNULIB_STRPBRK = @GNULIB_STRPBRK@
+GNULIB_STRSEP = @GNULIB_STRSEP@
+GNULIB_STRTOIMAX = @GNULIB_STRTOIMAX@
+GNULIB_STRTOK_R = @GNULIB_STRTOK_R@
+GNULIB_STRTOUMAX = @GNULIB_STRTOUMAX@
+GNULIB_VASPRINTF = @GNULIB_VASPRINTF@
+GNULIB_VFPRINTF_POSIX = @GNULIB_VFPRINTF_POSIX@
+GNULIB_VPRINTF_POSIX = @GNULIB_VPRINTF_POSIX@
+GNULIB_VSNPRINTF = @GNULIB_VSNPRINTF@
+GNULIB_VSPRINTF_POSIX = @GNULIB_VSPRINTF_POSIX@
+GREP = @GREP@
+HAVE_DECL_GETLOGIN_R = @HAVE_DECL_GETLOGIN_R@
+HAVE_DECL_IMAXABS = @HAVE_DECL_IMAXABS@
+HAVE_DECL_IMAXDIV = @HAVE_DECL_IMAXDIV@
+HAVE_DECL_MEMMEM = @HAVE_DECL_MEMMEM@
+HAVE_DECL_MEMRCHR = @HAVE_DECL_MEMRCHR@
+HAVE_DECL_MKDIR = @HAVE_DECL_MKDIR@
+HAVE_DECL_SNPRINTF = @HAVE_DECL_SNPRINTF@
+HAVE_DECL_STRDUP = @HAVE_DECL_STRDUP@
+HAVE_DECL_STRNCASECMP = @HAVE_DECL_STRNCASECMP@
+HAVE_DECL_STRNDUP = @HAVE_DECL_STRNDUP@
+HAVE_DECL_STRNLEN = @HAVE_DECL_STRNLEN@
+HAVE_DECL_STRTOIMAX = @HAVE_DECL_STRTOIMAX@
+HAVE_DECL_STRTOK_R = @HAVE_DECL_STRTOK_R@
+HAVE_DECL_STRTOUMAX = @HAVE_DECL_STRTOUMAX@
+HAVE_DECL_VSNPRINTF = @HAVE_DECL_VSNPRINTF@
+HAVE_DUP2 = @HAVE_DUP2@
+HAVE_FSEEKO = @HAVE_FSEEKO@
+HAVE_FTELLO = @HAVE_FTELLO@
+HAVE_FTRUNCATE = @HAVE_FTRUNCATE@
+HAVE_GETSUBOPT = @HAVE_GETSUBOPT@
+HAVE_INTTYPES_H = @HAVE_INTTYPES_H@
+HAVE_IO_H = @HAVE_IO_H@
+HAVE_ISWCNTRL = @HAVE_ISWCNTRL@
+HAVE_LONG_LONG_INT = @HAVE_LONG_LONG_INT@
+HAVE_LSTAT = @HAVE_LSTAT@
+HAVE_MEMPCPY = @HAVE_MEMPCPY@
+HAVE_MKDTEMP = @HAVE_MKDTEMP@
+HAVE_READLINK = @HAVE_READLINK@
+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_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_STRNDUP = @HAVE_STRNDUP@
+HAVE_STRPBRK = @HAVE_STRPBRK@
+HAVE_STRSEP = @HAVE_STRSEP@
+HAVE_STRUCT_TIMEVAL = @HAVE_STRUCT_TIMEVAL@
+HAVE_SYSEXITS_H = @HAVE_SYSEXITS_H@
+HAVE_SYS_BITYPES_H = @HAVE_SYS_BITYPES_H@
+HAVE_SYS_INTTYPES_H = @HAVE_SYS_INTTYPES_H@
+HAVE_SYS_TIME_H = @HAVE_SYS_TIME_H@
+HAVE_SYS_TYPES_H = @HAVE_SYS_TYPES_H@
+HAVE_UNISTD_H = @HAVE_UNISTD_H@
+HAVE_UNSIGNED_LONG_LONG_INT = @HAVE_UNSIGNED_LONG_LONG_INT@
+HAVE_VASPRINTF = @HAVE_VASPRINTF@
+HAVE_WCTYPE_H = @HAVE_WCTYPE_H@
+HAVE_WINT_T = @HAVE_WINT_T@
+HAVE__BOOL = @HAVE__BOOL@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+INTTYPES_H = @INTTYPES_H@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTAR_LIBDEPS = @LIBTAR_LIBDEPS@
+LIBTAR_LTLIBDEPS = @LIBTAR_LTLIBDEPS@
+LIB_CLOCK_GETTIME = @LIB_CLOCK_GETTIME@
+LIB_SETSOCKOPT = @LIB_SETSOCKOPT@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+POSUB = @POSUB@
+PRIPTR_PREFIX = @PRIPTR_PREFIX@
+PRI_MACROS_BROKEN = @PRI_MACROS_BROKEN@
+PTRDIFF_T_SUFFIX = @PTRDIFF_T_SUFFIX@
+PU_RMT_PROG = @PU_RMT_PROG@
+RANLIB = @RANLIB@
+REPLACE_CHOWN = @REPLACE_CHOWN@
+REPLACE_FCHDIR = @REPLACE_FCHDIR@
+REPLACE_FFLUSH = @REPLACE_FFLUSH@
+REPLACE_FPRINTF = @REPLACE_FPRINTF@
+REPLACE_FSEEK = @REPLACE_FSEEK@
+REPLACE_FSEEKO = @REPLACE_FSEEKO@
+REPLACE_FTELL = @REPLACE_FTELL@
+REPLACE_FTELLO = @REPLACE_FTELLO@
+REPLACE_GETCWD = @REPLACE_GETCWD@
+REPLACE_GETTIMEOFDAY = @REPLACE_GETTIMEOFDAY@
+REPLACE_LOCALTIME_R = @REPLACE_LOCALTIME_R@
+REPLACE_LSEEK = @REPLACE_LSEEK@
+REPLACE_MKSTEMP = @REPLACE_MKSTEMP@
+REPLACE_NANOSLEEP = @REPLACE_NANOSLEEP@
+REPLACE_PRINTF = @REPLACE_PRINTF@
+REPLACE_SNPRINTF = @REPLACE_SNPRINTF@
+REPLACE_SPRINTF = @REPLACE_SPRINTF@
+REPLACE_STRPTIME = @REPLACE_STRPTIME@
+REPLACE_TIMEGM = @REPLACE_TIMEGM@
+REPLACE_VASPRINTF = @REPLACE_VASPRINTF@
+REPLACE_VFPRINTF = @REPLACE_VFPRINTF@
+REPLACE_VPRINTF = @REPLACE_VPRINTF@
+REPLACE_VSNPRINTF = @REPLACE_VSNPRINTF@
+REPLACE_VSPRINTF = @REPLACE_VSPRINTF@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SIG_ATOMIC_T_SUFFIX = @SIG_ATOMIC_T_SUFFIX@
+SIZE_T_SUFFIX = @SIZE_T_SUFFIX@
+STDBOOL_H = @STDBOOL_H@
+STDINT_H = @STDINT_H@
+STRIP = @STRIP@
+SYSEXITS_H = @SYSEXITS_H@
+SYS_STAT_H = @SYS_STAT_H@
+SYS_TIME_H = @SYS_TIME_H@
+SYS_TIME_H_DEFINES_STRUCT_TIMESPEC = @SYS_TIME_H_DEFINES_STRUCT_TIMESPEC@
+TIME_H_DEFINES_STRUCT_TIMESPEC = @TIME_H_DEFINES_STRUCT_TIMESPEC@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+WCHAR_H = @WCHAR_H@
+WCHAR_T_SUFFIX = @WCHAR_T_SUFFIX@
+WCTYPE_H = @WCTYPE_H@
+WINT_T_SUFFIX = @WINT_T_SUFFIX@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+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_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@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_HEADERS = arith.h common.h tar.h
+tar_SOURCES = \
+ buffer.c\
+ compare.c\
+ create.c\
+ delete.c\
+ extract.c\
+ xheader.c\
+ incremen.c\
+ list.c\
+ misc.c\
+ names.c\
+ sparse.c\
+ system.c\
+ tar.c\
+ transform.c\
+ update.c\
+ utf8.c
+
+INCLUDES = -I$(top_srcdir)/lib -I../ -I../lib
+LDADD = ../lib/libtar.a $(LIBINTL) $(LIBICONV)
+tar_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME)
+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 \
+ && exit 0; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnits src/Makefile'; \
+ 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
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)"
+ @list='$(bin_PROGRAMS)'; for p in $$list; do \
+ p1=`echo $$p|sed 's/$(EXEEXT)$$//'`; \
+ if test -f $$p \
+ ; then \
+ f=`echo "$$p1" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \
+ echo " $(INSTALL_PROGRAM_ENV) $(binPROGRAMS_INSTALL) '$$p' '$(DESTDIR)$(bindir)/$$f'"; \
+ $(INSTALL_PROGRAM_ENV) $(binPROGRAMS_INSTALL) "$$p" "$(DESTDIR)$(bindir)/$$f" || exit 1; \
+ else :; fi; \
+ done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; for p in $$list; do \
+ f=`echo "$$p" | sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \
+ echo " rm -f '$(DESTDIR)$(bindir)/$$f'"; \
+ rm -f "$(DESTDIR)$(bindir)/$$f"; \
+ done
+
+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)
+ @rm -f tar$(EXEEXT)
+ $(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)/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)/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)/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)/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)/update.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utf8.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xheader.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) ' { files[$$0] = 1; } \
+ END { for (i in files) print i; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ tags=; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) ' { files[$$0] = 1; } \
+ END { for (i in files) print i; }'`; \
+ if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$tags $$unique; \
+ fi
+ctags: CTAGS
+CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ tags=; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) ' { files[$$0] = 1; } \
+ END { for (i in files) print i; }'`; \
+ test -z "$(CTAGS_ARGS)$$tags$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$tags $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && cd $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) $$here
+
+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 $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
+ fi; \
+ cp -pR $$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:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_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
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-am
+
+install-info: install-info-am
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-ps: 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 all all-am check check-am clean clean-binPROGRAMS \
+ clean-generic ctags 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 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..2bc5ced
--- /dev/null
+++ b/src/arith.h
@@ -0,0 +1,27 @@
+/* Long integers, for GNU tar.
+ Copyright 1999 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 2, 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, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+/* 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..f103463
--- /dev/null
+++ b/src/buffer.c
@@ -0,0 +1,1696 @@
+/* Buffer management for tar.
+
+ Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001,
+ 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
+
+ Written by John Gilmore, on 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 2, 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#include <system.h>
+#include <system-ioctl.h>
+
+#include <signal.h>
+
+#include <closeout.h>
+#include <fnmatch.h>
+#include <getline.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
+
+/* Globbing pattern to append to volume label if initial match failed. */
+#define VOLUME_LABEL_APPEND " Volume [1-9]*"
+
+/* 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 */
+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;
+
+/* Checkpointing counter */
+static unsigned checkpoint;
+
+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
+
+ As least EXTERN like this one as possible. (?? --gray)
+ FIXME: Either eliminate it or move it to common.h.
+*/
+extern bool time_to_start_writing;
+
+bool write_archive_to_stdout;
+
+void (*flush_write_ptr) (size_t);
+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;
+
+/* Used by flush_read and flush_write to store the real info about saved
+ names. */
+static char *real_s_name;
+static off_t real_s_totsize;
+static off_t real_s_sizeleft;
+
+
+/* Multi-volume tracking support */
+static char *save_name; /* name of the file we are currently writing */
+static off_t save_totsize; /* total size of file we are writing, only
+ valid if save_name is nonzero */
+static off_t save_sizeleft; /* where we are in the file we are writing,
+ only valid if save_name is nonzero */
+
+
+static struct tar_stat_info dummy;
+
+void
+buffer_write_global_xheader ()
+{
+ xheader_write_global (&dummy.xhdr);
+}
+
+void
+mv_begin (struct tar_stat_info *st)
+{
+ if (multi_volume_option)
+ {
+ assign_string (&save_name, st->orig_file_name);
+ save_totsize = save_sizeleft = st->stat.st_size;
+ }
+}
+
+void
+mv_end ()
+{
+ if (multi_volume_option)
+ assign_string (&save_name, 0);
+}
+
+void
+mv_total_size (off_t size)
+{
+ save_totsize = size;
+}
+
+void
+mv_size_left (off_t size)
+{
+ save_sizeleft = size;
+}
+
+
+/* Functions. */
+
+void
+clear_read_error_count (void)
+{
+ read_error_count = 0;
+}
+
+
+/* Time-related functions */
+
+double duration;
+
+void
+set_start_time ()
+{
+ gettime (&start_time);
+ volume_start_time = start_time;
+ last_stat_time = start_time;
+}
+
+void
+set_volume_start_time ()
+{
+ gettime (&volume_start_time);
+ last_stat_time = volume_start_time;
+}
+
+void
+compute_duration ()
+{
+ 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);
+}
+
+
+/* Compression detection */
+
+enum compress_type {
+ ct_none,
+ ct_compress,
+ ct_gzip,
+ ct_bzip2
+};
+
+struct zip_magic
+{
+ enum compress_type type;
+ size_t length;
+ char *magic;
+ char *program;
+ char *option;
+};
+
+static struct zip_magic const magic[] = {
+ { ct_none, },
+ { ct_compress, 2, "\037\235", "compress", "-Z" },
+ { ct_gzip, 2, "\037\213", "gzip", "-z" },
+ { ct_bzip2, 3, "BZh", "bzip2", "-j" },
+};
+
+#define NMAGIC (sizeof(magic)/sizeof(magic[0]))
+
+#define compress_option(t) magic[t].option
+#define compress_program(t) magic[t].program
+
+/* Check if the file ARCHIVE is a compressed archive. */
+enum compress_type
+check_compressed_archive ()
+{
+ struct zip_magic const *p;
+ bool sfr;
+
+ /* 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 */
+ find_next_block ();
+
+ /* Restore global values */
+ read_full_records = sfr;
+
+ if (tar_checksum (record_start, true) == HEADER_SUCCESS)
+ /* Probably a valid header */
+ return ct_none;
+
+ for (p = magic + 1; p < magic + NMAGIC; p++)
+ if (memcmp (record_start->buffer, p->magic, p->length) == 0)
+ return p->type;
+
+ return ct_none;
+}
+
+/* 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 */
+int
+open_compressed_archive ()
+{
+ archive = rmtopen (archive_name_array[0], O_RDONLY | O_BINARY,
+ MODE_RW, rsh_command_option);
+ if (archive == -1)
+ return archive;
+
+ if (!multi_volume_option)
+ {
+ enum compress_type type = check_compressed_archive ();
+
+ if (type == ct_none)
+ return archive;
+
+ /* 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 */
+ use_compress_program_option = compress_program (type);
+ 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 void
+print_stats (FILE *fp, const char *text, tarlong numbytes)
+{
+ char bytes[sizeof (tarlong) * CHAR_BIT];
+ char abbr[LONGEST_HUMAN_READABLE + 1];
+ char rate[LONGEST_HUMAN_READABLE + 1];
+
+ int human_opts = human_autoscale | human_base_1024 | human_SI | human_B;
+
+ sprintf (bytes, TARLONG_FORMAT, numbytes);
+
+ fprintf (fp, "%s: %s (%s, %s/s)\n",
+ text, bytes,
+ human_readable (numbytes, abbr, human_opts, 1, 1),
+ (0 < duration && numbytes / duration < (uintmax_t) -1
+ ? human_readable (numbytes / duration, rate, human_opts, 1, 1)
+ : "?"));
+}
+
+void
+print_total_stats ()
+{
+ switch (subcommand_option)
+ {
+ case CREATE_SUBCOMMAND:
+ case CAT_SUBCOMMAND:
+ case UPDATE_SUBCOMMAND:
+ case APPEND_SUBCOMMAND:
+ /* Amanda 2.4.1p1 looks for "Total bytes written: [0-9][0-9]*". */
+ print_stats (stderr, _("Total bytes written"),
+ prev_written + bytes_written);
+ break;
+
+ case DELETE_SUBCOMMAND:
+ {
+ char buf[UINTMAX_STRSIZE_BOUND];
+ print_stats (stderr, _("Total bytes read"),
+ records_read * record_size);
+ print_stats (stderr, _("Total bytes written"),
+ prev_written + bytes_written);
+ fprintf (stderr, _("Total bytes deleted: %s\n"),
+ STRINGIFY_BIGINT ((records_read - records_skipped)
+ * record_size
+ - (prev_written + bytes_written), buf));
+ }
+ break;
+
+ case EXTRACT_SUBCOMMAND:
+ case LIST_SUBCOMMAND:
+ case DIFF_SUBCOMMAND:
+ print_stats (stderr, _("Total bytes read"),
+ records_read * record_size);
+ break;
+
+ default:
+ abort ();
+ }
+}
+
+/* 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 ()
+{
+ 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;
+}
+
+/* Open an archive file. The argument specifies whether we are
+ reading or writing, or both. */
+static void
+_open_archive (enum access_mode wanted_access)
+{
+ int backed_up_flag = 0;
+
+ if (record_size == 0)
+ FATAL_ERROR ((0, 0, _("Invalid value for record_size")));
+
+ if (archive_names == 0)
+ FATAL_ERROR ((0, 0, _("No archive name given")));
+
+ tar_stat_destroy (&current_stat_info);
+ save_name = 0;
+ real_s_name = 0;
+
+ record_index = 0;
+ init_buffer ();
+
+ /* When updating the archive, we start with reading. */
+ access_mode = wanted_access == ACCESS_UPDATE ? ACCESS_READ : wanted_access;
+
+ 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:
+ {
+ enum compress_type type;
+
+ archive = STDIN_FILENO;
+
+ type = check_compressed_archive ();
+ if (type != ct_none)
+ FATAL_ERROR ((0, 0,
+ _("Archive is compressed. Use %s option"),
+ compress_option (type)));
+ }
+ 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 if (verify_option)
+ archive = rmtopen (archive_name_array[0], O_RDWR | O_CREAT | O_BINARY,
+ MODE_RW, rsh_command_option);
+ else
+ switch (wanted_access)
+ {
+ case ACCESS_READ:
+ archive = open_compressed_archive ();
+ break;
+
+ case ACCESS_WRITE:
+ if (backup_option)
+ {
+ maybe_backup_file (archive_name_array[0], 1);
+ backed_up_flag = 1;
+ }
+ 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);
+
+ if (check_compressed_archive () != ct_none)
+ 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;
+ }
+}
+
+static void
+do_checkpoint (bool write)
+{
+ if (checkpoint_option && !(++checkpoint % checkpoint_option))
+ {
+ switch (checkpoint_style)
+ {
+ case checkpoint_dot:
+ fputc ('.', stdlis);
+ fflush (stdlis);
+ break;
+
+ case checkpoint_text:
+ if (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'' */
+ WARN ((0, 0, _("Write checkpoint %u"), checkpoint));
+ 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'' */
+ WARN ((0, 0, _("Read checkpoint %u"), checkpoint));
+ break;
+ }
+ }
+}
+
+/* Perform a write to flush the buffer. */
+ssize_t
+_flush_write (void)
+{
+ ssize_t status;
+
+ do_checkpoint (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 ();
+
+ 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 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;
+
+ 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));
+ }
+
+ /* User warned us about this. Fix up. */
+
+ left -= status;
+ more += status;
+ }
+
+ /* FIXME: for size=0, multi-volume support. On the first record, warn
+ about the problem. */
+
+ if (!read_full_records && verbose_option > 1
+ && record_start_block == 0 && status != 0)
+ {
+ unsigned long rsize = (record_size - left) / BLOCKSIZE;
+ WARN ((0, 0,
+ ngettext ("Record size = %lu block",
+ "Record size = %lu blocks",
+ rsize),
+ rsize));
+ }
+
+ 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 = current_block->buffer - record_start->buffer;
+ record_start_block += record_end - record_start;
+ current_block = record_start;
+ record_end = record_start + blocking_factor;
+
+ if (access_mode == ACCESS_READ && time_to_start_writing)
+ {
+ access_mode = ACCESS_WRITE;
+ time_to_start_writing = false;
+ backspace_output ();
+ }
+
+ 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_size;
+ 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));
+
+ size -= skipped * BLOCKSIZE;
+
+ if (size < record_size)
+ return 0;
+ /* FIXME: flush? */
+
+ /* Compute number of records to skip */
+ nrec = size / record_size;
+ 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 ();
+ }
+
+ sys_drain_input_pipe ();
+
+ compute_duration ();
+ if (verify_option)
+ verify_volume ();
+
+ if (rmtclose (archive) != 0)
+ close_warn (*archive_name_cursor);
+
+ sys_wait_for_child (child_pid);
+
+ tar_stat_destroy (&current_stat_info);
+ if (save_name)
+ free (save_name);
+ if (real_s_name)
+ free (real_s_name);
+ free (record_buffer[0]);
+ free (record_buffer[1]);
+}
+
+/* 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 ()
+{
+ global_volno++;
+ if (global_volno < 0)
+ FATAL_ERROR ((0, 0, _("Volume number overflow")));
+ volno++;
+}
+
+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_warn (*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);
+ 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_primitive (false, info);
+ 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;
+}
+
+bool
+try_new_volume ()
+{
+ size_t status;
+ union block *header;
+ int access;
+
+ switch (subcommand_option)
+ {
+ case APPEND_SUBCOMMAND:
+ case CAT_SUBCOMMAND:
+ case UPDATE_SUBCOMMAND:
+ access = ACCESS_UPDATE;
+ break;
+
+ default:
+ access = ACCESS_READ;
+ break;
+ }
+
+ if (!new_volume (access))
+ 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)
+ return false;
+
+ switch (header->header.typeflag)
+ {
+ case XGLTYPE:
+ {
+ if (!read_header0 (&dummy))
+ return false;
+ xheader_decode (&dummy); /* decodes values from the global header */
+ tar_stat_destroy (&dummy);
+ if (!real_s_name)
+ {
+ /* We have read the extended header of the first member in
+ this volume. Put it back, so next read_header works as
+ expected. */
+ current_block = record_start;
+ }
+ 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 (real_s_name)
+ {
+ uintmax_t s;
+ if (!continued_file_name
+ || strcmp (continued_file_name, real_s_name))
+ {
+ if ((archive_format == GNU_FORMAT || archive_format == OLDGNU_FORMAT)
+ && strlen (real_s_name) >= NAME_FIELD_SIZE
+ && strncmp (continued_file_name, real_s_name,
+ NAME_FIELD_SIZE) == 0)
+ WARN ((0, 0,
+ _("%s is possibly continued on this volume: header contains truncated name"),
+ quote (real_s_name)));
+ else
+ {
+ WARN ((0, 0, _("%s is not continued on this volume"),
+ quote (real_s_name)));
+ return false;
+ }
+ }
+
+ s = continued_file_size + continued_file_offset;
+
+ if (real_s_totsize != 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 (save_totsize, totsizebuf),
+ STRINGIFY_BIGINT (continued_file_size, s1buf),
+ STRINGIFY_BIGINT (continued_file_offset, s2buf)));
+ return false;
+ }
+
+ if (real_s_totsize - real_s_sizeleft != continued_file_offset)
+ {
+ WARN ((0, 0, _("This volume is out of sequence")));
+ return false;
+ }
+ }
+
+ increase_volume_number ();
+ return true;
+}
+
+
+/* Check the LABEL block 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 (union block *label)
+{
+ char *string;
+ bool result;
+
+ if (! memchr (label->header.name, '\0', sizeof label->header.name))
+ return false;
+
+ if (fnmatch (volume_label_option, label->header.name, 0) == 0)
+ return true;
+
+ if (!multi_volume_option)
+ return false;
+
+ string = xmalloc (strlen (volume_label_option)
+ + sizeof VOLUME_LABEL_APPEND + 1);
+ strcpy (string, volume_label_option);
+ strcat (string, VOLUME_LABEL_APPEND);
+ result = fnmatch (string, label->header.name, 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)
+{
+ union block *label = find_next_block ();
+
+ if (!label)
+ FATAL_ERROR ((0, 0, _("Archive not labeled to match %s"),
+ quote (volume_label_option)));
+ if (!check_label_pattern (label))
+ FATAL_ERROR ((0, 0, _("Volume %s does not match %s"),
+ quote_n (0, label->header.name),
+ 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, volume_label_option);
+ assign_string (&current_stat_info.file_name,
+ label->header.name);
+ current_stat_info.had_trailing_slash =
+ strip_trailing_slashes (current_stat_info.file_name);
+
+ label->header.typeflag = GNUTYPE_VOLHDR;
+ TIME_TO_CHARS (start_time.tv_sec, label->header.mtime);
+ finish_header (&current_stat_info, label, -1);
+ set_next_block_after (label);
+ }
+}
+
+#define VOL_SUFFIX "Volume"
+
+/* Add a volume label to a part of multi-volume archive */
+static void
+add_volume_label (void)
+{
+ char buf[UINTMAX_STRSIZE_BOUND];
+ char *p = STRINGIFY_BIGINT (volno, buf);
+ char *s = xmalloc (strlen (volume_label_option) + sizeof VOL_SUFFIX
+ + strlen (p) + 2);
+ sprintf (s, "%s %s %s", volume_label_option, VOL_SUFFIX, p);
+ _write_volume_label (s);
+ free (s);
+}
+
+static void
+add_chunk_header ()
+{
+ if (archive_format == POSIX_FORMAT)
+ {
+ off_t block_ordinal;
+ union block *blk;
+ struct tar_stat_info st;
+ static size_t real_s_part_no; /* FIXME */
+
+ real_s_part_no++;
+ memset (&st, 0, sizeof st);
+ st.orig_file_name = st.file_name = real_s_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",
+ real_s_part_no);
+ st.file_name = st.orig_file_name;
+ st.archive_file_size = st.stat.st_size = real_s_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 (void)
+{
+ int tmp;
+ union block *block = find_next_block ();
+
+ if (strlen (real_s_name) > NAME_FIELD_SIZE)
+ WARN ((0, 0,
+ _("%s: file name too long to be stored in a GNU multivolume header, truncated"),
+ quotearg_colon (real_s_name)));
+
+ memset (block, 0, BLOCKSIZE);
+
+ /* FIXME: Michael P Urban writes: [a long name file] is being written
+ when a new volume rolls around [...] Looks like the wrong value is
+ being preserved in real_s_name, though. */
+
+ strncpy (block->header.name, real_s_name, NAME_FIELD_SIZE);
+ block->header.typeflag = GNUTYPE_MULTIVOL;
+
+ OFF_TO_CHARS (real_s_sizeleft, block->header.size);
+ OFF_TO_CHARS (real_s_totsize - real_s_sizeleft,
+ block->oldgnu_header.offset);
+
+ tmp = verbose_option;
+ verbose_option = 0;
+ finish_header (&current_stat_info, block, -1);
+ verbose_option = tmp;
+ set_next_block_after (block);
+}
+
+/* Add a multi volume header to the current archive. The exact header format
+ depends on the archive format. */
+static void
+add_multi_volume_header (void)
+{
+ if (archive_format == POSIX_FORMAT)
+ {
+ off_t d = real_s_totsize - real_s_sizeleft;
+ xheader_store ("GNU.volume.filename", &dummy, real_s_name);
+ xheader_store ("GNU.volume.size", &dummy, &real_s_sizeleft);
+ xheader_store ("GNU.volume.offset", &dummy, &d);
+ }
+ else
+ gnu_add_multi_volume_header ();
+}
+
+/* Synchronize multi-volume globals */
+static void
+multi_volume_sync ()
+{
+ if (multi_volume_option)
+ {
+ if (save_name)
+ {
+ assign_string (&real_s_name,
+ safer_name_suffix (save_name, false,
+ absolute_names_option));
+ real_s_totsize = save_totsize;
+ real_s_sizeleft = save_sizeleft;
+ }
+ else
+ {
+ assign_string (&real_s_name, 0);
+ real_s_totsize = 0;
+ real_s_sizeleft = 0;
+ }
+ }
+}
+
+
+/* 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 */
+
+ do_checkpoint (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 */
+
+ do_checkpoint (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);
+ }
+
+ multi_volume_sync ();
+
+ 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 ())
+ ;
+ 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;
+
+ status = _flush_write ();
+ if (status != record_size && !multi_volume_option)
+ archive_write_error (status);
+ else
+ {
+ records_written++;
+ bytes_written += status;
+ }
+
+ if (status == record_size)
+ {
+ multi_volume_sync ();
+ return;
+ }
+
+ /* 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 ();
+
+ if (volume_label_option)
+ add_volume_label ();
+
+ if (real_s_name)
+ add_multi_volume_header ();
+
+ write_extended (true, &dummy, find_next_block ());
+ tar_stat_destroy (&dummy);
+
+ if (real_s_name)
+ add_chunk_header ();
+ header = find_next_block ();
+ bufsize = available_space_after (header);
+ 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 ()
+{
+ flush_read_ptr ();
+}
+
+void
+flush_write ()
+{
+ 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:
+ if (volume_label_option)
+ match_volume_label ();
+ break;
+
+ case ACCESS_WRITE:
+ records_written = 0;
+ if (volume_label_option)
+ write_volume_label ();
+ break;
+
+ default:
+ break;
+ }
+ set_volume_start_time ();
+}
diff --git a/src/common.h b/src/common.h
new file mode 100644
index 0000000..6dd1abd
--- /dev/null
+++ b/src/common.h
@@ -0,0 +1,738 @@
+/* Common declarations for the tar program.
+
+ Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001,
+ 2003, 2004, 2005, 2006, 2007 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 2, 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+/* 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 <paxlib.h>
+
+/* Log base 2 of common values. */
+#define LG_8 3
+#define LG_64 6
+#define LG_256 8
+
+/* Information gleaned from the command line. */
+
+/* Name of this program. */
+GLOBAL const char *program_name;
+
+/* 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 */
+};
+
+GLOBAL enum subcommand subcommand_option;
+
+/* Selected format for output archive. */
+GLOBAL enum archive_format archive_format;
+
+/* Either NL or NUL, as decided by the --null option. */
+GLOBAL char filename_terminator;
+
+/* 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;
+
+/* 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;
+
+enum checkpoint_style
+ {
+ checkpoint_text,
+ checkpoint_dot
+ };
+
+GLOBAL enum checkpoint_style checkpoint_style;
+
+/* Specified name of compression program, or "gzip" as implied by -z. */
+GLOBAL const char *use_compress_program_option;
+
+GLOBAL bool dereference_option;
+
+/* Print a message if not all links are dumped */
+GLOBAL int check_links_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 -1 if such an override should not take place. */
+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 */
+ KEEP_NEWER_FILES /* --keep-newer-files */
+};
+GLOBAL enum old_files old_files_option;
+
+/* Specified file name for incremental list. */
+GLOBAL const char *listed_incremental_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;
+
+/* If true, override actual mtime (see below) */
+GLOBAL bool set_mtime_option;
+/* Value to be put in mtime header field instead of the actual mtime */
+GLOBAL struct timespec mtime_option;
+
+/* Return true if newer_mtime_option is initialized. */
+#define NEWER_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;
+
+/* Specified value to be put into tar file in place of stat () results, or
+ just -1 if such an override should not take place. */
+GLOBAL uid_t owner_option;
+
+GLOBAL bool recursive_unlink_option;
+
+GLOBAL bool read_full_records_option;
+
+GLOBAL bool remove_files_option;
+
+/* Specified rmt command. */
+GLOBAL const char *rmt_command_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;
+
+/* 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;
+
+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, allocated number,
+ and current cursor in list. */
+GLOBAL const char **archive_name_array;
+GLOBAL size_t archive_names;
+GLOBAL size_t allocated_archive_names;
+GLOBAL const char **archive_name_cursor;
+
+/* Output index file name. */
+GLOBAL char const *index_file_name;
+
+/* Structure for keeping track of filenames and lists thereof. */
+struct name
+ {
+ struct name *next; /* Link to the next element */
+ 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 */
+ int matching_flags; /* this name is a regexp, not literal */
+ char const *dir_contents; /* for incremental_option */
+
+ size_t length; /* cached strlen(name) */
+ char name[1];
+ };
+
+/* Obnoxious test to see if dimwit is trying to dump the archive. */
+GLOBAL dev_t ar_dev;
+GLOBAL ino_t ar_ino;
+
+GLOBAL bool seekable_archive;
+
+GLOBAL dev_t root_device;
+
+/* Unquote filenames */
+GLOBAL bool unquote_option;
+
+GLOBAL bool test_label_option; /* Test archive volume label and exit */
+
+/* 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;
+
+/* Warn about implicit use of the wildcards in command line arguments.
+ (Default for tar prior to 1.15.91, but changed afterwards */
+GLOBAL bool warn_regex_usage;
+
+/* 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 char *continued_file_name;
+extern uintmax_t continued_file_size;
+extern uintmax_t continued_file_offset;
+
+size_t available_space_after (union block *pointer);
+off_t current_block_ordinal (void);
+void close_archive (void);
+void closeout_volume_number (void);
+void 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);
+
+void mv_begin (struct tar_stat_info *st);
+void mv_end (void);
+void mv_total_size (off_t size);
+void mv_size_left (off_t size);
+
+void buffer_write_global_xheader (void);
+
+/* 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 (*)(const char*));
+bool cachedir_file_p (const char *name);
+
+bool file_dumpable_p (struct tar_stat_info *st);
+void create_archive (void);
+void pad_archive (off_t size_left);
+void dump_file (const char *st, int top_level, dev_t parent_device);
+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);
+void write_eot (void);
+void check_links (void);
+
+#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 OFF_TO_CHARS(val, where) off_to_chars (val, where, sizeof (where))
+#define SIZE_TO_CHARS(val, where) size_to_chars (val, where, sizeof (where))
+#define TIME_TO_CHARS(val, where) time_to_chars (val, where, sizeof (where))
+#define UID_TO_CHARS(val, where) uid_to_chars (val, where, sizeof (where))
+#define UINTMAX_TO_CHARS(val, where) uintmax_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))
+
+bool gid_to_chars (gid_t gid, char *buf, size_t size);
+bool major_to_chars (major_t m, char *buf, size_t size);
+bool minor_to_chars (minor_t m, char *buf, size_t size);
+bool mode_to_chars (mode_t m, char *buf, size_t size);
+bool off_to_chars (off_t off, char *buf, size_t size);
+bool size_to_chars (size_t v, char *buf, size_t size);
+bool time_to_chars (time_t t, char *buf, size_t size);
+bool uid_to_chars (uid_t uid, char *buf, size_t size);
+bool uintmax_to_chars (uintmax_t v, char *buf, size_t size);
+void string_to_chars (char const *s, 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);
+
+/* Module delete.c. */
+
+void delete_archive_members (void);
+
+/* Module incremen.c. */
+
+char *get_directory_contents (char *dir_name, dev_t device);
+const char *append_incremental_renames (const char *dump);
+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 (const char *name);
+
+size_t dumpdir_size (const char *p);
+bool is_dumpdir (struct tar_stat_info *stat_info);
+
+/* 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 */
+};
+
+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);
+char const *tartime (struct timespec t, bool full_time);
+
+#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) mode_from_header (where, sizeof (where))
+#define OFF_FROM_HEADER(where) off_from_header (where, sizeof (where))
+#define SIZE_FROM_HEADER(where) size_from_header (where, sizeof (where))
+#define TIME_FROM_HEADER(where) time_from_header (where, sizeof (where))
+#define UID_FROM_HEADER(where) uid_from_header (where, sizeof (where))
+#define UINTMAX_FROM_HEADER(where) uintmax_from_header (where, sizeof (where))
+
+gid_t gid_from_header (const char *buf, size_t size);
+major_t major_from_header (const char *buf, size_t size);
+minor_t minor_from_header (const char *buf, size_t size);
+mode_t mode_from_header (const char *buf, size_t size);
+off_t off_from_header (const char *buf, size_t size);
+size_t size_from_header (const char *buf, size_t size);
+time_t time_from_header (const char *buf, size_t size);
+uid_t uid_from_header (const char *buf, size_t size);
+uintmax_t uintmax_from_header (const char * buf, size_t size);
+
+void list_archive (void);
+void print_for_mkdir (char *dirname, int length, mode_t mode);
+void print_header (struct tar_stat_info *st, off_t block_ordinal);
+void read_and (void (*do_something) (void));
+enum read_header read_header_primitive (bool raw_extended_headers,
+ struct tar_stat_info *info);
+enum read_header read_header (bool raw_extended_headers);
+enum read_header tar_checksum (union block *header, bool silent);
+void skip_file (off_t size);
+void skip_member (void);
+
+/* Module misc.c. */
+
+void assign_string (char **dest, const char *src);
+char *quote_copy_string (const char *str);
+int unquote_string (char *str);
+
+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 };
+
+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 (bool deref, char const *name, struct stat *buf);
+
+int chdir_arg (char const *dir);
+void chdir_do (int dir);
+
+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 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, char const *file,
+ struct timespec const timespec[2]);
+
+/* Module names.c. */
+
+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, int matching_flags);
+void name_add_dir (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 name_match (const char *name);
+void names_notfound (void);
+void collect_and_sort_names (void);
+struct name *name_scan (const char *name);
+char *name_from_list (void);
+void blank_name_list (void);
+char *new_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);
+
+bool excluded_name (char const *name);
+
+void add_avoided_name (char const *name);
+bool is_avoided_name (char const *name);
+bool is_individual_file (char const *name);
+
+bool contains_dot_dot (char const *name);
+
+#define ISFOUND(c) ((occurrence_option == 0) ? (c)->found_count : \
+ (c)->found_count == occurrence_option)
+#define WASFOUND(c) ((occurrence_option == 0) ? (c)->found_count : \
+ (c)->found_count >= occurrence_option)
+
+/* Module tar.c. */
+
+void usage (int);
+
+int confirm (const char *message_action, const char *name);
+void request_stdin (const char *option);
+
+void tar_stat_init (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);
+
+/* Module update.c. */
+
+extern char *output_start;
+
+void update_archive (void);
+
+/* Module xheader.c. */
+
+void xheader_init (struct xheader *xhdr);
+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, size_t size);
+void xheader_write (char type, char *name, 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);
+
+/* Module system.c */
+
+void sys_detect_dev_null_output (void);
+void sys_save_archive_dev_ino (void);
+void sys_drain_input_pipe (void);
+void sys_wait_for_child (pid_t);
+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);
+
+/* Module compare.c */
+void report_difference (struct tar_stat_info *st, const char *message, ...);
+
+/* 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 */
+typedef enum
+ {
+ xform_regfile,
+ xform_link,
+ xform_symlink
+ } xform_type;
+
+void set_transform_expr (const char *expr);
+bool transform_name (char **pinput);
+bool transform_member_name (char **pinput, xform_type type);
+bool transform_name_fp (char **pinput, char *(*fun)(char *, void *), void *);
diff --git a/src/compare.c b/src/compare.c
new file mode 100644
index 0000000..7df3d98
--- /dev/null
+++ b/src/compare.c
@@ -0,0 +1,609 @@
+/* Diff files from a tar archive.
+
+ Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001,
+ 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
+
+ Written by John Gilmore, on 1987-04-30.
+
+ 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 2, 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#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");
+ }
+
+ if (exit_status == TAREXIT_SUCCESS)
+ 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 = safe_read (diff_handle, diff_buffer, bytes);
+
+ if (status != bytes)
+ {
+ if (status == SAFE_READ_ERROR)
+ {
+ read_error (current_stat_info.file_name);
+ report_difference (&current_stat_info, NULL);
+ }
+ else
+ {
+ report_difference (&current_stat_info,
+ ngettext ("Could only read %lu of %lu byte",
+ "Could only read %lu of %lu bytes",
+ bytes),
+ (unsigned long) status, (unsigned long) bytes);
+ }
+ return 0;
+ }
+
+ if (memcmp (buffer, diff_buffer, bytes))
+ {
+ report_difference (&current_stat_info, _("Contents differ"));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Some other routine wants SIZE bytes in the archive. For each chunk
+ of the archive, call PROCESSOR with the size of the chunk, and the
+ address of the chunk it can work with. The PROCESSOR should return
+ nonzero for success. Once it returns error, continue skipping
+ without calling PROCESSOR anymore. */
+
+static void
+read_and_process (struct tar_stat_info *st, int (*processor) (size_t, char *))
+{
+ union block *data_block;
+ size_t data_size;
+ off_t size = st->stat.st_size;
+
+ mv_begin (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 (dereference_option, file_name, stat_data);
+
+ if (status != 0)
+ {
+ if (errno == ENOENT)
+ stat_warn (file_name);
+ else
+ stat_error (file_name);
+ report_difference (&current_stat_info, NULL);
+ return 0;
+ }
+
+ return 1;
+}
+
+
+static void
+diff_dir (void)
+{
+ struct stat stat_data;
+
+ if (!get_stat_data (current_stat_info.file_name, &stat_data))
+ return;
+
+ if (!S_ISDIR (stat_data.st_mode))
+ report_difference (&current_stat_info, _("File type differs"));
+ else if ((current_stat_info.stat.st_mode & MODE_ALL) !=
+ (stat_data.st_mode & MODE_ALL))
+ report_difference (&current_stat_info, _("Mode differs"));
+}
+
+static void
+diff_file (void)
+{
+ char const *file_name = current_stat_info.file_name;
+ struct stat stat_data;
+
+ if (!get_stat_data (file_name, &stat_data))
+ skip_member ();
+ else if (!S_ISREG (stat_data.st_mode))
+ {
+ report_difference (&current_stat_info, _("File type differs"));
+ skip_member ();
+ }
+ else
+ {
+ if ((current_stat_info.stat.st_mode & MODE_ALL) !=
+ (stat_data.st_mode & MODE_ALL))
+ report_difference (&current_stat_info, _("Mode differs"));
+
+ if (!sys_compare_uid (&stat_data, &current_stat_info.stat))
+ report_difference (&current_stat_info, _("Uid differs"));
+ if (!sys_compare_gid (&stat_data, &current_stat_info.stat))
+ report_difference (&current_stat_info, _("Gid differs"));
+
+ if (tar_timespec_cmp (get_stat_mtime (&stat_data),
+ current_stat_info.mtime))
+ report_difference (&current_stat_info, _("Mod time differs"));
+ if (current_header->header.typeflag != GNUTYPE_SPARSE
+ && stat_data.st_size != current_stat_info.stat.st_size)
+ {
+ report_difference (&current_stat_info, _("Size differs"));
+ skip_member ();
+ }
+ else
+ {
+ int atime_flag =
+ (atime_preserve_option == system_atime_preserve
+ ? O_NOATIME
+ : 0);
+
+ diff_handle = open (file_name, O_RDONLY | O_BINARY | atime_flag);
+
+ if (diff_handle < 0)
+ {
+ open_error (file_name);
+ skip_member ();
+ report_difference (&current_stat_info, NULL);
+ }
+ else
+ {
+ int status;
+
+ if (current_stat_info.is_sparse)
+ sparse_diff_file (diff_handle, &current_stat_info);
+ else
+ read_and_process (&current_stat_info, process_rawdata);
+
+ if (atime_preserve_option == replace_atime_preserve)
+ {
+ struct timespec ts[2];
+ ts[0] = get_stat_atime (&stat_data);
+ ts[1] = get_stat_mtime (&stat_data);
+ if (set_file_atime (diff_handle, file_name, ts) != 0)
+ utime_error (file_name);
+ }
+
+ status = close (diff_handle);
+ if (status != 0)
+ close_error (file_name);
+ }
+ }
+ }
+}
+
+static void
+diff_link (void)
+{
+ struct stat file_data;
+ struct stat link_data;
+
+ if (get_stat_data (current_stat_info.file_name, &file_data)
+ && get_stat_data (current_stat_info.link_name, &link_data)
+ && !sys_compare_links (&file_data, &link_data))
+ report_difference (&current_stat_info,
+ _("Not linked to %s"),
+ quote (current_stat_info.link_name));
+}
+
+#ifdef HAVE_READLINK
+static void
+diff_symlink (void)
+{
+ size_t len = strlen (current_stat_info.link_name);
+ char *linkbuf = alloca (len + 1);
+
+ int status = readlink (current_stat_info.file_name, linkbuf, len + 1);
+
+ if (status < 0)
+ {
+ if (errno == ENOENT)
+ readlink_warn (current_stat_info.file_name);
+ else
+ readlink_error (current_stat_info.file_name);
+ report_difference (&current_stat_info, NULL);
+ }
+ else if (status != len
+ || strncmp (current_stat_info.link_name, linkbuf, len) != 0)
+ report_difference (&current_stat_info, _("Symlink differs"));
+}
+#endif
+
+static void
+diff_special (void)
+{
+ struct stat stat_data;
+
+ /* FIXME: deal with umask. */
+
+ if (!get_stat_data (current_stat_info.file_name, &stat_data))
+ return;
+
+ if (current_header->header.typeflag == CHRTYPE
+ ? !S_ISCHR (stat_data.st_mode)
+ : current_header->header.typeflag == BLKTYPE
+ ? !S_ISBLK (stat_data.st_mode)
+ : /* current_header->header.typeflag == FIFOTYPE */
+ !S_ISFIFO (stat_data.st_mode))
+ {
+ report_difference (&current_stat_info, _("File type differs"));
+ return;
+ }
+
+ if ((current_header->header.typeflag == CHRTYPE
+ || current_header->header.typeflag == BLKTYPE)
+ && current_stat_info.stat.st_rdev != stat_data.st_rdev)
+ {
+ report_difference (&current_stat_info, _("Device number differs"));
+ return;
+ }
+
+ if ((current_stat_info.stat.st_mode & MODE_ALL) !=
+ (stat_data.st_mode & MODE_ALL))
+ report_difference (&current_stat_info, _("Mode differs"));
+}
+
+static int
+dumpdir_cmp (const char *a, const char *b)
+{
+ size_t len;
+
+ while (*a)
+ switch (*a)
+ {
+ case 'Y':
+ case 'N':
+ if (!strchr ("YN", *b))
+ return 1;
+ if (strcmp(a + 1, b + 1))
+ return 1;
+ len = strlen (a) + 1;
+ a += len;
+ b += len;
+ break;
+
+ case 'D':
+ if (strcmp(a, b))
+ return 1;
+ len = strlen (a) + 1;
+ a += len;
+ b += len;
+ break;
+
+ case 'R':
+ case 'T':
+ case 'X':
+ return *b;
+ }
+ return *b;
+}
+
+static void
+diff_dumpdir (void)
+{
+ char *dumpdir_buffer;
+ dev_t dev = 0;
+ struct stat stat;
+
+ if (deref_stat (true, current_stat_info.file_name, &stat))
+ {
+ if (errno == ENOENT)
+ stat_warn (current_stat_info.file_name);
+ else
+ stat_error (current_stat_info.file_name);
+ }
+ else
+ dev = stat.st_dev;
+
+ dumpdir_buffer = get_directory_contents (current_stat_info.file_name, dev);
+
+ if (dumpdir_buffer)
+ {
+ if (dumpdir_cmp (current_stat_info.dumpdir, dumpdir_buffer))
+ report_difference (&current_stat_info, _("Contents differ"));
+ }
+ else
+ read_and_process (&current_stat_info, process_noop);
+}
+
+static void
+diff_multivol (void)
+{
+ struct stat stat_data;
+ int fd, status;
+ off_t offset;
+
+ if (current_stat_info.had_trailing_slash)
+ {
+ diff_dir ();
+ return;
+ }
+
+ if (!get_stat_data (current_stat_info.file_name, &stat_data))
+ return;
+
+ if (!S_ISREG (stat_data.st_mode))
+ {
+ report_difference (&current_stat_info, _("File type differs"));
+ skip_member ();
+ return;
+ }
+
+ offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
+ if (stat_data.st_size != current_stat_info.stat.st_size + offset)
+ {
+ report_difference (&current_stat_info, _("Size differs"));
+ skip_member ();
+ return;
+ }
+
+ fd = open (current_stat_info.file_name, O_RDONLY | O_BINARY);
+
+ if (fd < 0)
+ {
+ open_error (current_stat_info.file_name);
+ report_difference (&current_stat_info, NULL);
+ skip_member ();
+ return;
+ }
+
+ if (lseek (fd, offset, SEEK_SET) < 0)
+ {
+ seek_error_details (current_stat_info.file_name, offset);
+ report_difference (&current_stat_info, NULL);
+ return;
+ }
+
+ read_and_process (&current_stat_info, process_rawdata);
+
+ status = close (fd);
+ if (status != 0)
+ close_error (current_stat_info.file_name);
+}
+
+/* Diff a file against the archive. */
+void
+diff_archive (void)
+{
+
+ set_next_block_after (current_header);
+ decode_header (current_header, &current_stat_info, &current_format, 1);
+
+ /* Print the block from current_header and current_stat_info. */
+
+ if (verbose_option)
+ {
+ if (now_verifying)
+ fprintf (stdlis, _("Verify "));
+ print_header (&current_stat_info, -1);
+ }
+
+ switch (current_header->header.typeflag)
+ {
+ default:
+ ERROR ((0, 0, _("%s: Unknown file type `%c', diffed as normal file"),
+ quotearg_colon (current_stat_info.file_name),
+ current_header->header.typeflag));
+ /* Fall through. */
+
+ case AREGTYPE:
+ case REGTYPE:
+ case GNUTYPE_SPARSE:
+ case CONTTYPE:
+
+ /* Appears to be a file. See if it's really a directory. */
+
+ if (current_stat_info.had_trailing_slash)
+ diff_dir ();
+ else
+ diff_file ();
+ break;
+
+ case LNKTYPE:
+ diff_link ();
+ break;
+
+#ifdef HAVE_READLINK
+ case SYMTYPE:
+ diff_symlink ();
+ break;
+#endif
+
+ case CHRTYPE:
+ case BLKTYPE:
+ case FIFOTYPE:
+ diff_special ();
+ break;
+
+ case GNUTYPE_DUMPDIR:
+ case DIRTYPE:
+ if (is_dumpdir (&current_stat_info))
+ diff_dumpdir ();
+ diff_dir ();
+ break;
+
+ case GNUTYPE_VOLHDR:
+ break;
+
+ case GNUTYPE_MULTIVOL:
+ diff_multivol ();
+ }
+}
+
+void
+verify_volume (void)
+{
+ if (removed_prefixes_p ())
+ {
+ WARN((0, 0,
+ _("Archive contains file names with leading prefixes removed.")));
+ WARN((0, 0,
+ _("Verification may fail to locate original files.")));
+ }
+
+ 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 (false);
+
+ if (status == HEADER_FAILURE)
+ {
+ int counter = 0;
+
+ do
+ {
+ counter++;
+ set_next_block_after (current_header);
+ status = read_header (false);
+ }
+ 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_ZERO_BLOCK || status == HEADER_END_OF_FILE)
+ break;
+
+ diff_archive ();
+ tar_stat_destroy (&current_stat_info);
+ }
+
+ access_mode = ACCESS_WRITE;
+ now_verifying = 0;
+}
diff --git a/src/create.c b/src/create.c
new file mode 100644
index 0000000..1b31a3d
--- /dev/null
+++ b/src/create.c
@@ -0,0 +1,1785 @@
+/* Create a tar archive.
+
+ Copyright (C) 1985, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001,
+ 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
+
+ Written by John Gilmore, on 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 2, 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#include <system.h>
+
+#include <quotearg.h>
+
+#include "common.h"
+#include <hash.h>
+
+struct link
+ {
+ dev_t dev;
+ ino_t ino;
+ size_t nlink;
+ char name[1];
+ };
+
+struct exclusion_tag
+{
+ const char *name;
+ size_t length;
+ enum exclusion_tag_type type;
+ bool (*predicate) (const char *name);
+ struct exclusion_tag *next;
+};
+
+static struct exclusion_tag *exclusion_tags;
+
+void
+add_exclusion_tag (const char *name, enum exclusion_tag_type type,
+ bool (*predicate) (const char *name))
+{
+ 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;
+}
+
+static void
+exclusion_tag_warning (const char *dirname, const char *tagname,
+ const char *message)
+{
+ if (verbose_option)
+ WARN ((0, 0,
+ _("%s: contains a cache directory tag %s; %s"),
+ quotearg_colon (dirname),
+ quotearg_n (1, tagname),
+ message));
+}
+
+static enum exclusion_tag_type
+check_exclusion_tags (char *dirname, const char **tag_file_name)
+{
+ static char *tagname;
+ static size_t tagsize;
+ struct exclusion_tag *tag;
+ size_t dlen = strlen (dirname);
+ char *nptr = NULL;
+ char *ret = NULL;
+
+ for (tag = exclusion_tags; tag; tag = tag->next)
+ {
+ size_t size = dlen + tag->length + 1;
+ if (size > tagsize)
+ {
+ tagsize = size;
+ tagname = xrealloc (tagname, tagsize);
+ }
+
+ if (!nptr)
+ {
+ strcpy (tagname, dirname);
+ nptr = tagname + dlen;
+ }
+ strcpy (nptr, tag->name);
+ if (access (tagname, F_OK) == 0
+ && (!tag->predicate || tag->predicate (tagname)))
+ {
+ 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 (const char *name)
+{
+ bool tag_present = false;
+ int fd = open (name, O_RDONLY);
+ if (fd >= 0)
+ {
+ static char tagbuf[CACHEDIR_SIGNATURE_SIZE];
+
+ if (read (fd, tagbuf, CACHEDIR_SIGNATURE_SIZE)
+ == CACHEDIR_SIGNATURE_SIZE
+ && memcmp (tagbuf, CACHEDIR_SIGNATURE, CACHEDIR_SIGNATURE_SIZE) == 0)
+ tag_present = true;
+
+ close (fd);
+ }
+ return tag_present;
+}
+
+
+/* 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);
+}
+
+
+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;
+}
+
+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");
+}
+
+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");
+}
+
+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");
+}
+
+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
+ && archive_format != OLDGNU_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
+size_to_chars (size_t v, char *p, size_t s)
+{
+ return to_chars (0, (uintmax_t) v, sizeof v, 0, p, s, "size_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;
+}
+
+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");
+}
+
+bool
+uintmax_to_chars (uintmax_t v, char *p, size_t s)
+{
+ return to_chars (0, v, sizeof v, 0, p, s, "uintmax_t");
+}
+
+void
+string_to_chars (char const *str, char *p, size_t s)
+{
+ tar_copy_str (p, str, s);
+ p[s - 1] = '\0';
+}
+
+
+/* A file is considered 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 */
+
+bool
+file_dumpable_p (struct tar_stat_info *st)
+{
+ if (dev_null_output)
+ return totals_option && sparse_option && ST_IS_SPARSE (st->stat);
+ return !(st->archive_file_size == 0
+ && (st->stat.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 (&t);
+ TIME_TO_CHARS (t, header->header.mtime);
+ MODE_TO_CHARS (S_IFREG|S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, header->header.mode);
+ UID_TO_CHARS (getuid (), header->header.uid);
+ GID_TO_CHARS (getgid (), header->header.gid);
+ MAJOR_TO_CHARS (0, header->header.devmajor);
+ MINOR_TO_CHARS (0, header->header.devminor);
+ 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;
+}
+
+#define FILL(field,byte) do { \
+ memset(field, byte, sizeof(field)-1); \
+ (field)[sizeof(field)-1] = 0; \
+} while (0)
+
+/* 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);
+ FILL(header->header.mtime, '0');
+ FILL(header->header.mode, '0');
+ FILL(header->header.uid, '0');
+ FILL(header->header.gid, '0');
+ FILL(header->header.devmajor, 0);
+ FILL(header->header.devminor, 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->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)
+ length = PREFIX_FIELD_SIZE + 1;
+ 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;
+ 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 || length - i - 1 > NAME_FIELD_SIZE)
+ {
+ 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;
+
+ 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 ();
+ }
+ else
+ {
+ type = XHDTYPE;
+ p = xheader_xhdr_name (st);
+ }
+ xheader_write (type, p, &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;
+
+ header = write_header_name (st);
+ if (!header)
+ return NULL;
+
+ /* Override some stat fields, if requested to do so. */
+
+ if (owner_option != (uid_t) -1)
+ st->stat.st_uid = owner_option;
+ if (group_option != (gid_t) -1)
+ st->stat.st_gid = group_option;
+ 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 = set_mtime_option ? mtime_option : st->mtime;
+ 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->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
+ {
+ uid_to_uname (st->stat.st_uid, &st->uname);
+ 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);
+ }
+
+ 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)
+ {
+ /* These globals are parameters to print_header, sigh. */
+
+ current_header = header;
+ current_format = archive_format;
+ print_header (st, 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)
+ {
+ mv_size_left (size_left);
+ 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 (st);
+ while (size_left > 0)
+ {
+ size_t bufsize, count;
+
+ mv_size_left (size_left);
+
+ 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 : safe_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;
+ if (count)
+ set_next_block_after (blk + (bufsize - 1) / BLOCKSIZE);
+
+ if (count != bufsize)
+ {
+ char buf[UINTMAX_STRSIZE_BOUND];
+ memset (blk->buffer + count, 0, bufsize - count);
+ WARN ((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)
+ exit_status = TAREXIT_DIFFERS;
+ pad_archive (size_left - (bufsize-count));
+ return dump_status_short;
+ }
+ }
+ return dump_status_ok;
+}
+
+
+static void
+dump_dir0 (char *directory,
+ struct tar_stat_info *st, int top_level, dev_t parent_device)
+{
+ dev_t our_device = st->stat.st_dev;
+ const char *tag_file_name;
+
+ if (!is_avoided_name (st->orig_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;
+
+ 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->dir_contents)
+ {
+ if (archive_format == POSIX_FORMAT)
+ {
+ xheader_store ("GNU.dumpdir", st, gnu_list_name->dir_contents);
+ 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 = gnu_list_name->dir_contents;
+ if (buffer)
+ totsize = dumpdir_size (buffer);
+ else
+ totsize = 0;
+ OFF_TO_CHARS (totsize, blk->header.size);
+ finish_header (st, blk, block_ordinal);
+ p_buffer = buffer;
+ size_left = totsize;
+
+ mv_begin (st);
+ mv_total_size (totsize);
+ while (size_left > 0)
+ {
+ mv_size_left (size_left);
+ 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);
+ }
+ mv_end ();
+ }
+ return;
+ }
+ }
+
+ if (!recursion_option)
+ return;
+
+ if (one_file_system_option
+ && !top_level
+ && parent_device != st->stat.st_dev)
+ {
+ if (verbose_option)
+ WARN ((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->orig_file_name, &tag_file_name))
+ {
+ case exclusion_tag_none:
+ case exclusion_tag_all:
+ {
+ 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. */
+ /* FIXME: Should speed this up by cd-ing into the dir. */
+ 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))
+ dump_file (name_buf, 0, our_device);
+ }
+
+ 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 (name_buf, 0, our_device);
+ 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';
+}
+
+static bool
+dump_dir (int fd, struct tar_stat_info *st, int top_level, dev_t parent_device)
+{
+ char *directory = fdsavedir (fd);
+ if (!directory)
+ {
+ savedir_diag (st->orig_file_name);
+ return false;
+ }
+
+ dump_dir0 (directory, st, top_level, parent_device);
+
+ free (directory);
+ return true;
+}
+
+
+/* Main functions of this module. */
+
+void
+create_archive (void)
+{
+ const char *p;
+
+ 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))
+ dump_file (p, -1, (dev_t) 0);
+
+ blank_name_list ();
+ while ((p = name_from_list ()) != NULL)
+ if (!excluded_name (p))
+ {
+ size_t plen = strlen (p);
+ if (buffer_size <= plen)
+ {
+ while ((buffer_size *= 2) <= plen)
+ continue;
+ buffer = xrealloc (buffer, buffer_size);
+ }
+ memcpy (buffer, p, plen);
+ if (! ISSLASH (buffer[plen - 1]))
+ buffer[plen++] = '/';
+ q = gnu_list_name->dir_contents;
+ if (q)
+ while (*q)
+ {
+ size_t qlen = strlen (q);
+ if (*q == 'Y')
+ {
+ if (buffer_size < plen + qlen)
+ {
+ while ((buffer_size *=2 ) < plen + qlen)
+ continue;
+ buffer = xrealloc (buffer, buffer_size);
+ }
+ strcpy (buffer + plen, q + 1);
+ dump_file (buffer, -1, (dev_t) 0);
+ }
+ q += qlen + 1;
+ }
+ }
+ free (buffer);
+ }
+ else
+ {
+ while ((p = name_next (1)) != NULL)
+ if (!excluded_name (p))
+ dump_file (p, 1, (dev_t) 0);
+ }
+
+ write_eot ();
+ close_archive ();
+
+ 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)
+{
+ WARN ((0, 0, _("%s: Unknown file type; file ignored"),
+ quotearg_colon (p)));
+ if (!ignore_failed_read_option)
+ 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 && st->stat.st_nlink > 1)
+ {
+ 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);
+
+ 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 && unlink (st->orig_file_name) != 0)
+ unlink_error (st->orig_file_name);
+
+ return true;
+ }
+ }
+ return false;
+}
+
+static void
+file_count_links (struct tar_stat_info *st)
+{
+ if (st->stat.st_nlink > 1)
+ {
+ struct link *duplicate;
+ struct link *lp = xmalloc (offsetof (struct link, name)
+ + strlen (st->orig_file_name) + 1);
+ lp->ino = st->stat.st_ino;
+ lp->dev = st->stat.st_dev;
+ lp->nlink = st->stat.st_nlink;
+ strcpy (lp->name, st->orig_file_name);
+
+ 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.\n"), quote (lp->name)));
+ }
+ }
+}
+
+
+/* Dump a single file, recursing on directories. P is the file name
+ to dump. TOP_LEVEL tells whether this is a top-level call; zero
+ means no, positive means yes, and negative means the top level
+ of an incremental dump. PARENT_DEVICE is the device of P's
+ parent directory; it is examined only if TOP_LEVEL is zero. */
+
+/* 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, const char *p,
+ int top_level, dev_t parent_device)
+{
+ union block *header;
+ char type;
+ off_t original_size;
+ struct timespec original_ctime;
+ struct timespec restore_times[2];
+ off_t block_ordinal = -1;
+ bool is_dir;
+
+ 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);
+
+ if (deref_stat (dereference_option, p, &st->stat) != 0)
+ {
+ stat_diag (p);
+ return;
+ }
+ st->archive_file_size = original_size = st->stat.st_size;
+ st->atime = restore_times[0] = get_stat_atime (&st->stat);
+ st->mtime = restore_times[1] = 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 explicitely listed in the command line. */
+
+ if (!(incremental_option && !is_individual_file (p))
+ && !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)
+ WARN ((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))
+ {
+ WARN ((0, 0, _("%s: file is the archive; not dumped"),
+ quotearg_colon (p)));
+ return;
+ }
+
+ if (is_avoided_name (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;
+ int fd = -1;
+ struct stat final_stat;
+
+ if (is_dir || file_dumpable_p (st))
+ {
+ fd = open (p,
+ (O_RDONLY | O_BINARY
+ | (is_dir ? O_DIRECTORY | O_NONBLOCK : 0)
+ | (atime_preserve_option == system_atime_preserve
+ ? O_NOATIME
+ : 0)));
+ if (fd < 0)
+ {
+ if (!top_level && errno == ENOENT)
+ WARN ((0, 0, _("%s: File removed before we read it"),
+ quotearg_colon (p)));
+ else
+ open_diag (p);
+ return;
+ }
+ }
+
+ if (is_dir)
+ {
+ const char *tag_file_name;
+ ensure_slash (&st->orig_file_name);
+ ensure_slash (&st->file_name);
+
+ if (check_exclusion_tags (st->orig_file_name, &tag_file_name)
+ == exclusion_tag_all)
+ {
+ exclusion_tag_warning (st->orig_file_name, tag_file_name,
+ _("directory not dumped"));
+ return;
+ }
+
+ ok = dump_dir (fd, st, top_level, parent_device);
+
+ /* dump_dir consumes FD if successful. */
+ if (ok)
+ fd = -1;
+ }
+ else
+ {
+ enum dump_status status;
+
+ if (fd != -1 && 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:
+ mv_end ();
+ break;
+
+ case dump_status_fail:
+ break;
+
+ case dump_status_not_implemented:
+ abort ();
+ }
+
+ file_count_links (st);
+
+ ok = status == dump_status_ok;
+ }
+
+ if (ok)
+ {
+ /* If possible, reopen a directory if we are preserving
+ atimes, so that we can set just the atime on systems with
+ _FIOSATIME. */
+ if (fd < 0 && is_dir
+ && atime_preserve_option == replace_atime_preserve)
+ fd = open (p, O_RDONLY | O_BINARY | O_DIRECTORY | O_NONBLOCK);
+
+ if ((fd < 0
+ ? deref_stat (dereference_option, p, &final_stat)
+ : fstat (fd, &final_stat))
+ != 0)
+ {
+ stat_diag (p);
+ ok = false;
+ }
+ }
+
+ 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)
+ {
+ WARN ((0, 0, _("%s: file changed as we read it"),
+ quotearg_colon (p)));
+ if (exit_status == TAREXIT_SUCCESS)
+ exit_status = TAREXIT_DIFFERS;
+ }
+ else if (atime_preserve_option == replace_atime_preserve
+ && set_file_atime (fd, p, restore_times) != 0)
+ utime_error (p);
+ }
+
+ if (0 <= fd && close (fd) != 0)
+ {
+ close_diag (p);
+ ok = false;
+ }
+
+ if (ok && remove_files_option)
+ {
+ if (is_dir)
+ {
+ if (rmdir (p) != 0 && errno != ENOTEMPTY)
+ rmdir_error (p);
+ }
+ else
+ {
+ if (unlink (p) != 0)
+ unlink_error (p);
+ }
+ }
+
+ return;
+ }
+#ifdef HAVE_READLINK
+ else if (S_ISLNK (st->stat.st_mode))
+ {
+ char *buffer;
+ int size;
+ size_t linklen = st->stat.st_size;
+ if (linklen != st->stat.st_size || linklen + 1 == 0)
+ xalloc_die ();
+ buffer = (char *) alloca (linklen + 1);
+ size = readlink (p, buffer, linklen + 1);
+ if (size < 0)
+ {
+ readlink_diag (p);
+ return;
+ }
+ buffer[size] = '\0';
+ assign_string (&st->link_name, buffer);
+ if (NAME_FIELD_SIZE - (archive_format == OLDGNU_FORMAT) < size)
+ write_long_link (st);
+
+ 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, buffer, NAME_FIELD_SIZE);
+ header->header.typeflag = SYMTYPE;
+ finish_header (st, header, block_ordinal);
+ /* nothing more to do to it */
+
+ if (remove_files_option)
+ {
+ if (unlink (p) == -1)
+ unlink_error (p);
+ }
+ file_count_links (st);
+ return;
+ }
+#endif
+ else if (S_ISCHR (st->stat.st_mode))
+ type = CHRTYPE;
+ else if (S_ISBLK (st->stat.st_mode))
+ type = BLKTYPE;
+ else if (S_ISFIFO (st->stat.st_mode))
+ type = FIFOTYPE;
+ else if (S_ISSOCK (st->stat.st_mode))
+ {
+ WARN ((0, 0, _("%s: socket ignored"), quotearg_colon (p)));
+ return;
+ }
+ else if (S_ISDOOR (st->stat.st_mode))
+ {
+ WARN ((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)
+ {
+ if (unlink (p) == -1)
+ unlink_error (p);
+ }
+}
+
+void
+dump_file (const char *p, int top_level, dev_t parent_device)
+{
+ struct tar_stat_info st;
+ tar_stat_init (&st);
+ dump_file0 (&st, p, top_level, parent_device);
+ if (listed_incremental_option)
+ update_parent_directory (p);
+ tar_stat_destroy (&st);
+}
diff --git a/src/delete.c b/src/delete.c
new file mode 100644
index 0000000..e649739
--- /dev/null
+++ b/src/delete.c
@@ -0,0 +1,391 @@
+/* Delete entries from a tar archive.
+
+ Copyright (C) 1988, 1992, 1994, 1996, 1997, 2000, 2001, 2003, 2004,
+ 2005, 2006 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 2, 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#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;
+extern off_t records_written;
+
+/* 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 (true);
+
+ 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 (false);
+
+ xheader_decode (&current_stat_info);
+
+ if (status == HEADER_ZERO_BLOCK && ignore_zeros_option)
+ {
+ set_next_block_after (current_header);
+ continue;
+ }
+ if (status == HEADER_END_OF_FILE || status == HEADER_ZERO_BLOCK)
+ {
+ logical_status = HEADER_END_OF_FILE;
+ break;
+ }
+
+ if (status == HEADER_FAILURE)
+ {
+ ERROR ((0, 0, _("Deleting non-header from archive")));
+ set_next_block_after (current_header);
+ continue;
+ }
+
+ /* Found another header. */
+
+ if ((name = name_scan (current_stat_info.file_name)) != NULL)
+ {
+ name->found_count++;
+ if (ISFOUND(name))
+ {
+ flush_file:
+ set_next_block_after (current_header);
+ blocks_to_skip = (current_stat_info.stat.st_size
+ + BLOCKSIZE - 1) / BLOCKSIZE;
+
+ while (record_end - current_block <= blocks_to_skip)
+ {
+ blocks_to_skip -= (record_end - current_block);
+ flush_archive ();
+ }
+ current_block += blocks_to_skip;
+ blocks_to_skip = 0;
+ continue;
+ }
+ }
+ /* Copy header. */
+
+ if (current_stat_info.xhdr.size)
+ {
+ write_recent_bytes (current_stat_info.xhdr.buffer,
+ current_stat_info.xhdr.size);
+ }
+ else
+ {
+ write_recent_blocks (recent_long_name, recent_long_name_blocks);
+ write_recent_blocks (recent_long_link, recent_long_link_blocks);
+ }
+ new_record[new_blocks] = *current_header;
+ new_blocks++;
+ blocks_to_keep
+ = (current_stat_info.stat.st_size + BLOCKSIZE - 1) / BLOCKSIZE;
+ set_next_block_after (current_header);
+ if (new_blocks == blocking_factor)
+ write_record (1);
+
+ /* Copy data. */
+
+ kept_blocks_in_record = record_end - current_block;
+ if (kept_blocks_in_record > blocks_to_keep)
+ kept_blocks_in_record = blocks_to_keep;
+
+ while (blocks_to_keep)
+ {
+ int count;
+
+ if (current_block == record_end)
+ {
+ flush_read ();
+ current_block = record_start;
+ kept_blocks_in_record = blocking_factor;
+ if (kept_blocks_in_record > blocks_to_keep)
+ kept_blocks_in_record = blocks_to_keep;
+ }
+ count = kept_blocks_in_record;
+ if (blocking_factor - new_blocks < count)
+ count = blocking_factor - new_blocks;
+
+ if (! count)
+ abort ();
+
+ memcpy (new_record + new_blocks, current_block, count * BLOCKSIZE);
+ new_blocks += count;
+ current_block += count;
+ blocks_to_keep -= count;
+ kept_blocks_in_record -= count;
+
+ if (new_blocks == blocking_factor)
+ write_record (1);
+ }
+ }
+
+ if (logical_status == HEADER_END_OF_FILE)
+ {
+ /* Write the end of tape. FIXME: we can't use write_eot here,
+ as it gets confused when the input is at end of file. */
+
+ int total_zero_blocks = 0;
+
+ do
+ {
+ int zero_blocks = blocking_factor - new_blocks;
+ memset (new_record + new_blocks, 0, BLOCKSIZE * zero_blocks);
+ total_zero_blocks += zero_blocks;
+ write_record (total_zero_blocks < 2);
+ }
+ while (total_zero_blocks < 2);
+ }
+
+ if (! acting_as_filter && ! _isrmt (archive))
+ {
+ if (sys_truncate (archive))
+ truncate_warn (archive_name_array[0]);
+ }
+ }
+ free (new_record);
+
+ close_archive ();
+ names_notfound ();
+}
diff --git a/src/extract.c b/src/extract.c
new file mode 100644
index 0000000..1f231e3
--- /dev/null
+++ b/src/extract.c
@@ -0,0 +1,1379 @@
+/* Extract files from a tar archive.
+
+ Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1998, 1999, 2000,
+ 2001, 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
+
+ Written by John Gilmore, on 1985-11-19.
+
+ 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 2, 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#include <system.h>
+#include <quotearg.h>
+#include <utimens.h>
+#include <errno.h>
+#include <xgetcwd.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) */
+
+/* Status of the permissions of a file that we are extracting. */
+enum permstatus
+{
+ /* This file may have existed already; its permissions are unknown. */
+ UNKNOWN_PERMSTATUS,
+
+ /* This file was created using the permissions from the archive,
+ except with S_IRWXG | S_IRWXO masked out if 0 < same_owner_option. */
+ ARCHIVED_PERMSTATUS,
+
+ /* This is an intermediate directory; the archive did not specify
+ its permissions. */
+ INTERDIR_PERMSTATUS
+};
+
+/* 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
+ {
+ struct delayed_set_stat *next;
+ dev_t dev;
+ ino_t ino;
+ mode_t mode;
+ uid_t uid;
+ gid_t gid;
+ struct timespec atime;
+ struct timespec mtime;
+ size_t file_name_len;
+ mode_t invert_permissions;
+ enum permstatus permstatus;
+ bool after_links;
+ char file_name[1];
+ };
+
+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 last-modified time of the placeholder. */
+ dev_t dev;
+ ino_t ino;
+ struct timespec mtime;
+
+ /* True if the link is symbolic. */
+ bool is_symlink;
+
+ /* The desired owner and group of the link, if it is a symlink. */
+ uid_t uid;
+ gid_t gid;
+
+ /* A list of sources for this link. The sources are all to be
+ hard-linked together. */
+ struct string_list *sources;
+
+ /* 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 () == 0;
+ 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;
+ }
+}
+
+/* If restoring permissions, restore the mode for FILE_NAME from
+ information given in *STAT_INFO (where *CUR_INFO gives
+ the current status if CUR_INFO is nonzero); otherwise invert the
+ INVERT_PERMISSIONS bits from the file's current permissions.
+ PERMSTATUS specifies the status of the file's permissions.
+ TYPEFLAG specifies the type of the file. */
+static void
+set_mode (char const *file_name,
+ struct stat const *stat_info,
+ struct stat const *cur_info,
+ mode_t invert_permissions, enum permstatus permstatus,
+ char typeflag)
+{
+ mode_t mode;
+
+ if (0 < same_permissions_option
+ && permstatus != INTERDIR_PERMSTATUS)
+ {
+ mode = stat_info->st_mode;
+
+ /* If we created the file and it has a mode that we set already
+ with O_CREAT, then its mode is often set correctly already.
+ But if we are changing ownership, the mode's group and and
+ other permission bits were omitted originally, so it's less
+ likely that the mode is OK now. Also, on many hosts, some
+ directories inherit the setgid bits from their parents, so we
+ we must set directories' modes explicitly. */
+ if ((permstatus == ARCHIVED_PERMSTATUS
+ && ! (mode & ~ (0 < same_owner_option ? S_IRWXU : MODE_RWX)))
+ && typeflag != DIRTYPE
+ && typeflag != GNUTYPE_DUMPDIR)
+ return;
+ }
+ else if (! invert_permissions)
+ return;
+ else
+ {
+ /* We must inspect a directory's current permissions, since the
+ directory may have inherited its setgid bit from its parent.
+
+ INVERT_PERMISSIONS happens to be nonzero only for directories
+ that we created, so there's no point optimizing this code for
+ other cases. */
+ struct stat st;
+ if (! cur_info)
+ {
+ if (stat (file_name, &st) != 0)
+ {
+ stat_error (file_name);
+ return;
+ }
+ cur_info = &st;
+ }
+ mode = cur_info->st_mode ^ invert_permissions;
+ }
+
+ if (chmod (file_name, mode) != 0)
+ 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)
+ WARN ((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--;
+ }
+ WARN ((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 CUR_INFO is nonzero, *CUR_INFO is the
+ file's current status.
+ If not restoring permissions, invert the
+ INVERT_PERMISSIONS bits from the file's current permissions.
+ PERMSTATUS specifies the status of the file's permissions.
+ TYPEFLAG specifies the type of the file. */
+
+/* FIXME: About proper restoration of symbolic link attributes, we still do
+ not have it right. Pretesters' reports tell us we need further study and
+ probably more configuration. For now, just use lchown if it exists, and
+ punt for the rest. Sigh! */
+
+static void
+set_stat (char const *file_name,
+ struct tar_stat_info const *st,
+ struct stat const *cur_info,
+ mode_t invert_permissions, enum permstatus permstatus,
+ char typeflag)
+{
+ if (typeflag != SYMTYPE)
+ {
+ /* We do the utime before the chmod because some versions of utime are
+ broken and trash the modes of the file. */
+
+ if (! touch_option && permstatus != INTERDIR_PERMSTATUS)
+ {
+ /* We set the accessed time to `now', which is really the time we
+ started extracting files, unless incremental_option is used, in
+ which case .st_atime is used. */
+
+ /* FIXME: incremental_option should set ctime too, but how? */
+
+ struct timespec ts[2];
+ if (incremental_option)
+ ts[0] = st->atime;
+ else
+ ts[0] = start_time;
+ ts[1] = st->mtime;
+
+ if (utimens (file_name, ts) != 0)
+ utime_error (file_name);
+ else
+ {
+ check_time (file_name, ts[0]);
+ check_time (file_name, ts[1]);
+ }
+ }
+
+ /* 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. */
+ }
+
+ if (0 < same_owner_option && permstatus != INTERDIR_PERMSTATUS)
+ {
+ /* When lchown exists, it should be used to change the attributes of
+ the symbolic link itself. In this case, a mere chown would change
+ the attributes of the file the symbolic link is pointing to, and
+ should be avoided. */
+ int chown_result = 1;
+
+ if (typeflag == SYMTYPE)
+ {
+#if HAVE_LCHOWN
+ chown_result = lchown (file_name, st->stat.st_uid, st->stat.st_gid);
+#endif
+ }
+ else
+ {
+ chown_result = chown (file_name, st->stat.st_uid, st->stat.st_gid);
+ }
+
+ if (chown_result == 0)
+ {
+ /* Changing the owner can flip st_mode bits in some cases, so
+ ignore cur_info if it might be obsolete now. */
+ if (cur_info
+ && cur_info->st_mode & S_IXUGO
+ && cur_info->st_mode & (S_ISUID | S_ISGID))
+ cur_info = NULL;
+ }
+ else if (chown_result < 0)
+ chown_error_details (file_name,
+ st->stat.st_uid, st->stat.st_gid);
+ }
+
+ if (typeflag != SYMTYPE)
+ set_mode (file_name, &st->stat, cur_info,
+ invert_permissions, permstatus, typeflag);
+}
+
+/* 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 not restoring permissions, remember to invert the
+ INVERT_PERMISSIONS bits from the file's current permissions.
+ PERMSTATUS specifies the status of the file's permissions.
+
+ 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 invert_permissions, enum permstatus permstatus)
+{
+ size_t file_name_len = strlen (file_name);
+ struct delayed_set_stat *data =
+ xmalloc (offsetof (struct delayed_set_stat, file_name)
+ + file_name_len + 1);
+ data->next = delayed_set_stat_head;
+ data->dev = st->stat.st_dev;
+ data->ino = st->stat.st_ino;
+ data->mode = st->stat.st_mode;
+ 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->invert_permissions = invert_permissions;
+ data->permstatus = permstatus;
+ data->after_links = 0;
+ strcpy (data->file_name, file_name);
+ delayed_set_stat_head = 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 (stat (data->file_name, &st) != 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->invert_permissions =
+ ((current_stat_info.stat.st_mode ^ st.st_mode)
+ & MODE_RWX & ~ current_umask);
+ data->permstatus = ARCHIVED_PERMSTATUS;
+ return;
+ }
+ }
+
+ ERROR ((0, 0, _("%s: Unexpected inconsistency when making directory"),
+ quotearg_colon (dir)));
+}
+
+/* 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 non-zero if a directory
+ was created. */
+static int
+make_directories (char *file_name)
+{
+ char *cursor0 = file_name + FILE_SYSTEM_PREFIX_LEN (file_name);
+ char *cursor; /* points into the file name */
+ int did_something = 0; /* did we do anything yet? */
+ int mode;
+ int invert_permissions;
+ int status;
+
+ for (cursor = cursor0; *cursor; cursor++)
+ {
+ 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 */
+ mode = MODE_RWX & ~ newdir_umask;
+ invert_permissions = we_are_root ? 0 : MODE_WXUSR & ~ mode;
+ status = mkdir (file_name, mode ^ invert_permissions);
+
+ if (status == 0)
+ {
+ /* Create a struct delayed_set_stat even if
+ invert_permissions is zero, because
+ repair_delayed_set_stat may need to update the struct. */
+ delay_set_stat (file_name,
+ &current_stat_info,
+ invert_permissions, INTERDIR_PERMSTATUS);
+
+ print_for_mkdir (file_name, cursor - file_name, mode);
+ did_something = 1;
+
+ *cursor = '/';
+ continue;
+ }
+
+ *cursor = '/';
+
+ if (errno == EEXIST)
+ continue; /* Directory already exists. */
+ else if ((errno == ENOSYS /* Automounted dirs on Solaris return
+ this. Reported by Warren Hyde
+ <Warren.Hyde@motorola.com> */
+ || ERRNO_IS_EACCES) /* Turbo C mkdir gives a funny errno. */
+ && access (file_name, W_OK) == 0)
+ continue;
+
+ /* Some other error in the mkdir. We return to the caller. */
+ break;
+ }
+
+ return did_something; /* tell them to retry if we made one */
+}
+
+static bool
+file_newer_p (const char *file_name, struct tar_stat_info *tar_stat)
+{
+ struct stat st;
+
+ if (stat (file_name, &st))
+ {
+ stat_warn (file_name);
+ /* Be on the safe side: if the file does exist assume it is newer */
+ return errno != ENOENT;
+ }
+ if (!S_ISDIR (st.st_mode)
+ && tar_timespec_cmp (tar_stat->mtime, get_stat_mtime (&st)) <= 0)
+ {
+ return true;
+ }
+ return false;
+}
+
+/* Attempt repairing what went wrong with the extraction. Delete an
+ already existing file or create missing intermediate directories.
+ Return nonzero if we somewhat increased our chances at a successful
+ extraction. errno is properly restored on zero return. */
+static int
+maybe_recoverable (char *file_name, int *interdir_made)
+{
+ int e = errno;
+
+ if (*interdir_made)
+ return 0;
+
+ switch (errno)
+ {
+ case EEXIST:
+ /* Remove an old file, if the options allow this. */
+
+ switch (old_files_option)
+ {
+ case KEEP_OLD_FILES:
+ return 0;
+
+ case KEEP_NEWER_FILES:
+ if (file_newer_p (file_name, &current_stat_info))
+ {
+ errno = e;
+ return 0;
+ }
+ /* FALL THROUGH */
+
+ case DEFAULT_OLD_FILES:
+ case NO_OVERWRITE_DIR_OLD_FILES:
+ case OVERWRITE_OLD_FILES:
+ {
+ int r = remove_any_file (file_name, ORDINARY_REMOVE_OPTION);
+ errno = EEXIST;
+ return r;
+ }
+
+ case UNLINK_FIRST_OLD_FILES:
+ break;
+ }
+
+ case ENOENT:
+ /* Attempt creating missing intermediate directories. */
+ if (! make_directories (file_name))
+ {
+ errno = ENOENT;
+ return 0;
+ }
+ *interdir_made = 1;
+ return 1;
+
+ default:
+ /* Just say we can't do anything about it... */
+
+ return 0;
+ }
+}
+
+/* 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;
+ struct stat const *cur_info = 0;
+
+ 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;
+
+ if (check_for_renamed_directories)
+ {
+ cur_info = &st;
+ if (stat (data->file_name, &st) != 0)
+ {
+ stat_error (data->file_name);
+ skip_this_one = 1;
+ }
+ else 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 st;
+ st.stat.st_mode = data->mode;
+ st.stat.st_uid = data->uid;
+ st.stat.st_gid = data->gid;
+ st.atime = data->atime;
+ st.mtime = data->mtime;
+ set_stat (data->file_name, &st, cur_info,
+ data->invert_permissions, data->permstatus, DIRTYPE);
+ }
+
+ delayed_set_stat_head = data->next;
+ free (data);
+ }
+}
+
+
+
+/* Extractor functions for various member types */
+
+static int
+extract_dir (char *file_name, int typeflag)
+{
+ int status;
+ mode_t mode;
+ int interdir_made = 0;
+
+ /* Save 'root device' to avoid purging mount points. */
+ if (one_file_system_option && root_device == 0)
+ {
+ struct stat st;
+ char *dir = xgetcwd ();
+
+ if (deref_stat (true, dir, &st))
+ stat_diag (dir);
+ else
+ root_device = st.st_dev;
+ free (dir);
+ }
+
+ 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 ();
+
+ mode = current_stat_info.stat.st_mode | (we_are_root ? 0 : MODE_WXUSR);
+ if (0 < same_owner_option || current_stat_info.stat.st_mode & ~ MODE_RWX)
+ mode &= S_IRWXU;
+
+ while ((status = mkdir (file_name, mode)))
+ {
+ if (errno == EEXIST
+ && (interdir_made
+ || old_files_option == DEFAULT_OLD_FILES
+ || old_files_option == OVERWRITE_OLD_FILES))
+ {
+ struct stat st;
+ if (stat (file_name, &st) == 0)
+ {
+ if (interdir_made)
+ {
+ repair_delayed_set_stat (file_name, &st);
+ return 0;
+ }
+ if (S_ISDIR (st.st_mode))
+ {
+ mode = st.st_mode;
+ break;
+ }
+ }
+ errno = EEXIST;
+ }
+
+ if (maybe_recoverable (file_name, &interdir_made))
+ continue;
+
+ if (errno != EEXIST)
+ {
+ mkdir_error (file_name);
+ return 1;
+ }
+ break;
+ }
+
+ if (status == 0
+ || old_files_option == DEFAULT_OLD_FILES
+ || old_files_option == OVERWRITE_OLD_FILES)
+ {
+ if (status == 0)
+ delay_set_stat (file_name, &current_stat_info,
+ ((mode ^ current_stat_info.stat.st_mode)
+ & MODE_RWX & ~ current_umask),
+ ARCHIVED_PERMSTATUS);
+ else /* For an already existing directory, invert_perms must be 0 */
+ delay_set_stat (file_name, &current_stat_info,
+ 0,
+ UNKNOWN_PERMSTATUS);
+ }
+ return status;
+}
+
+
+static int
+open_output_file (char *file_name, int typeflag, mode_t mode)
+{
+ int fd;
+ int openflag = (O_WRONLY | O_BINARY | O_CREAT
+ | (old_files_option == OVERWRITE_OLD_FILES
+ ? O_TRUNC
+ : O_EXCL));
+
+#if O_CTG
+ /* Contiguous files (on the Masscomp) have to specify the size in
+ the open call that creates them. */
+
+ if (typeflag == CONTTYPE)
+ fd = open (file_name, openflag | O_CTG, mode, current_stat_info.stat.st_size);
+ else
+ fd = open (file_name, openflag, mode);
+
+#else /* not O_CTG */
+ if (typeflag == CONTTYPE)
+ {
+ static int conttype_diagnosed;
+
+ if (!conttype_diagnosed)
+ {
+ conttype_diagnosed = 1;
+ WARN ((0, 0, _("Extracting contiguous files as regular files")));
+ }
+ }
+ fd = open (file_name, openflag, mode);
+
+#endif /* not O_CTG */
+
+ 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;
+ int interdir_made = 0;
+ mode_t mode = current_stat_info.stat.st_mode & MODE_RWX & ~ current_umask;
+ mode_t invert_permissions =
+ 0 < same_owner_option ? mode & (S_IRWXG | S_IRWXO) : 0;
+
+ /* FIXME: deal with protection issues. */
+
+ if (to_stdout_option)
+ fd = STDOUT_FILENO;
+ else if (to_command_option)
+ {
+ fd = sys_exec_command (file_name, 'f', &current_stat_info);
+ if (fd < 0)
+ {
+ skip_member ();
+ return 0;
+ }
+ }
+ else
+ {
+ do
+ fd = open_output_file (file_name, typeflag, mode ^ invert_permissions);
+ while (fd < 0 && maybe_recoverable (file_name, &interdir_made));
+
+ if (fd < 0)
+ {
+ skip_member ();
+ open_error (file_name);
+ return 1;
+ }
+ }
+
+ mv_begin (&current_stat_info);
+ if (current_stat_info.is_sparse)
+ sparse_extract_file (fd, &current_stat_info, &size);
+ else
+ for (size = current_stat_info.stat.st_size; size > 0; )
+ {
+ mv_size_left (size);
+
+ /* Locate data, determine max length writeable, write it,
+ block that we have used the data, then check if the write
+ worked. */
+
+ data_block = find_next_block ();
+ if (! data_block)
+ {
+ ERROR ((0, 0, _("Unexpected EOF in archive")));
+ break; /* FIXME: What happens, then? */
+ }
+
+ written = available_space_after (data_block);
+
+ if (written > size)
+ written = size;
+ errno = 0;
+ count = full_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;
+
+ status = close (fd);
+ if (status < 0)
+ close_error (file_name);
+
+ if (to_command_option)
+ sys_wait_command ();
+ else
+ set_stat (file_name, &current_stat_info, NULL, invert_permissions,
+ (old_files_option == OVERWRITE_OLD_FILES ?
+ UNKNOWN_PERMSTATUS : ARCHIVED_PERMSTATUS),
+ typeflag);
+
+ 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, int *interdir_made)
+{
+ int fd;
+ struct stat st;
+
+ while ((fd = open (file_name, O_WRONLY | O_CREAT | O_EXCL, 0)) < 0)
+ if (! maybe_recoverable (file_name, interdir_made))
+ break;
+
+ if (fd < 0)
+ open_error (file_name);
+ else 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->mtime = get_stat_mtime (&st);
+ p->is_symlink = is_symlink;
+ if (is_symlink)
+ {
+ p->uid = current_stat_info.stat.st_uid;
+ p->gid = current_stat_info.stat.st_gid;
+ }
+ p->sources = xmalloc (offsetof (struct string_list, string)
+ + strlen (file_name) + 1);
+ p->sources->next = 0;
+ strcpy (p->sources->string, file_name);
+ 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))
+ {
+ do
+ {
+ h->after_links = 1;
+
+ if (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);
+ }
+
+ return 0;
+ }
+
+ return -1;
+}
+
+static int
+extract_link (char *file_name, int typeflag)
+{
+ int interdir_made = 0;
+ char const *link_name;
+
+ transform_member_name (&current_stat_info.link_name, xform_link);
+ 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 = link (link_name, file_name);
+ e = errno;
+
+ if (status == 0)
+ {
+ struct delayed_link *ds = delayed_link_head;
+ if (ds && lstat (link_name, &st1) == 0)
+ for (; ds; ds = ds->next)
+ if (ds->dev == st1.st_dev
+ && ds->ino == st1.st_ino
+ && timespec_cmp (ds->mtime, get_stat_mtime (&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)
+ || (lstat (link_name, &st1) == 0
+ && lstat (file_name, &st2) == 0
+ && st1.st_dev == st2.st_dev
+ && st1.st_ino == st2.st_ino))
+ return 0;
+
+ errno = e;
+ }
+ while (maybe_recoverable (file_name, &interdir_made));
+
+ 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
+ int status;
+ int interdir_made = 0;
+
+ transform_member_name (&current_stat_info.link_name, xform_symlink);
+
+ 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 ((status = symlink (current_stat_info.link_name, file_name)))
+ if (!maybe_recoverable (file_name, &interdir_made))
+ break;
+
+ if (status == 0)
+ set_stat (file_name, &current_stat_info, NULL, 0, 0, SYMTYPE);
+ else
+ symlink_error (current_stat_info.link_name, file_name);
+ return status;
+
+#else
+ static int warned_once;
+
+ if (!warned_once)
+ {
+ warned_once = 1;
+ WARN ((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)
+{
+ int status;
+ int interdir_made = 0;
+ mode_t mode = current_stat_info.stat.st_mode & ~ current_umask;
+ mode_t invert_permissions =
+ 0 < same_owner_option ? mode & (S_IRWXG | S_IRWXO) : 0;
+
+ do
+ status = mknod (file_name, mode ^ invert_permissions,
+ current_stat_info.stat.st_rdev);
+ while (status && maybe_recoverable (file_name, &interdir_made));
+
+ if (status != 0)
+ mknod_error (file_name);
+ else
+ set_stat (file_name, &current_stat_info, NULL, invert_permissions,
+ ARCHIVED_PERMSTATUS, typeflag);
+ return status;
+}
+#endif
+
+#if HAVE_MKFIFO || defined mkfifo
+static int
+extract_fifo (char *file_name, int typeflag)
+{
+ int status;
+ int interdir_made = 0;
+ mode_t mode = current_stat_info.stat.st_mode & ~ current_umask;
+ mode_t invert_permissions =
+ 0 < same_owner_option ? mode & (S_IRWXG | S_IRWXO) : 0;
+
+ while ((status = mkfifo (file_name, mode)) != 0)
+ if (!maybe_recoverable (file_name, &interdir_made))
+ break;
+
+ if (status == 0)
+ set_stat (file_name, &current_stat_info, NULL, invert_permissions,
+ ARCHIVED_PERMSTATUS, typeflag);
+ else
+ mkfifo_error (file_name);
+ return status;
+}
+#endif
+
+static int
+extract_volhdr (char *file_name, int typeflag)
+{
+ if (verbose_option)
+ fprintf (stdlis, _("Reading %s\n"), quote (current_stat_info.file_name));
+ skip_member ();
+ return 0;
+}
+
+static int
+extract_failure (char *file_name, int typeflag)
+{
+ return 1;
+}
+
+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_failure;
+ break;
+
+ case GNUTYPE_LONGNAME:
+ case GNUTYPE_LONGLINK:
+ ERROR ((0, 0, _("Unexpected long name header")));
+ *fun = extract_failure;
+ break;
+
+ default:
+ WARN ((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, &current_stat_info))
+ {
+ WARN ((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;
+
+ set_next_block_after (current_header);
+ decode_header (current_header, &current_stat_info, &current_format, 1);
+ if (!current_stat_info.file_name[0]
+ || (interactive_option
+ && !confirm ("extract", current_stat_info.file_name)))
+ {
+ skip_member ();
+ return;
+ }
+
+ /* Print the block from current_header and current_stat. */
+ if (verbose_option)
+ print_header (&current_stat_info, -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)
+ apply_nonancestor_delayed_set_stat (current_stat_info.file_name, 0);
+
+ /* Take a safety backup of a previously existing file. */
+
+ if (backup_option)
+ if (!maybe_backup_file (current_stat_info.file_name, 0))
+ {
+ int e = errno;
+ ERROR ((0, e, _("%s: Was unable to backup this file"),
+ quotearg_colon (current_stat_info.file_name)));
+ skip_member ();
+ return;
+ }
+
+ /* Extract the archive entry according to its type. */
+ /* KLUDGE */
+ typeflag = sparse_member_p (&current_stat_info) ?
+ GNUTYPE_SPARSE : current_header->header.typeflag;
+
+ if (prepare_to_extract (current_stat_info.file_name, typeflag, &fun))
+ {
+ if (fun && (*fun) (current_stat_info.file_name, typeflag)
+ && backup_option)
+ undo_last_backup ();
+ }
+ else
+ skip_member ();
+
+}
+
+/* Extract the symbolic 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;
+
+ 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 (lstat (source, &st) == 0
+ && st.st_dev == ds->dev
+ && st.st_ino == ds->ino
+ && timespec_cmp (get_stat_mtime (&st), ds->mtime) == 0)
+ {
+ /* Unlink the placeholder, then create a hard link if possible,
+ a symbolic link otherwise. */
+ if (unlink (source) != 0)
+ unlink_error (source);
+ else if (valid_source && link (valid_source, source) == 0)
+ ;
+ else if (!ds->is_symlink)
+ {
+ if (link (ds->target, source) != 0)
+ link_error (ds->target, source);
+ }
+ else if (symlink (ds->target, source) != 0)
+ symlink_error (ds->target, source);
+ else
+ {
+ struct tar_stat_info st1;
+ st1.stat.st_uid = ds->uid;
+ st1.stat.st_gid = ds->gid;
+ set_stat (source, &st1, NULL, 0, 0, SYMTYPE);
+ valid_source = source;
+ }
+ }
+ }
+
+ for (sources = ds->sources; sources; )
+ {
+ struct string_list *next = sources->next;
+ free (sources);
+ sources = next;
+ }
+
+ {
+ 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 (rename (src, dst))
+ {
+ int e = errno;
+
+ switch (e)
+ {
+ case ENOENT:
+ if (make_directories (dst))
+ {
+ if (rename (src, 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;
+}
+
+void
+fatal_exit (void)
+{
+ extract_finish ();
+ 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/incremen.c b/src/incremen.c
new file mode 100644
index 0000000..59215e5
--- /dev/null
+++ b/src/incremen.c
@@ -0,0 +1,1473 @@
+/* GNU dump extensions to tar.
+
+ Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001,
+ 2003, 2004, 2005, 2006, 2007 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 2, 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#include <system.h>
+#include <getline.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)
+#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)
+
+/* Directory attributes. */
+struct directory
+ {
+ struct timespec mtime; /* Modification time */
+ dev_t device_number; /* device number for directory */
+ ino_t inode_number; /* inode number for directory */
+ char *contents; /* Directory contents */
+ char *icontents; /* 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 */
+ char name[1]; /* file name of directory */
+ };
+
+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_name (void const *entry, size_t n_buckets)
+{
+ struct directory const *directory = entry;
+ return hash_string (directory->name, n_buckets);
+}
+
+/* Compare two directories for equality of their names. */
+static bool
+compare_directory_names (void const *entry1, void const *entry2)
+{
+ struct directory const *directory1 = entry1;
+ struct directory const *directory2 = entry2;
+ return strcmp (directory1->name, directory2->name) == 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 NAME */
+static struct directory *
+make_directory (const char *name)
+{
+ size_t namelen = strlen (name);
+ size_t size = offsetof (struct directory, name) + namelen + 1;
+ struct directory *directory = xmalloc (size);
+ directory->contents = directory->icontents = NULL;
+ directory->orig = NULL;
+ directory->flags = false;
+ strcpy (directory->name, name);
+ if (ISSLASH (directory->name[namelen-1]))
+ directory->name[namelen-1] = 0;
+ return directory;
+}
+
+/* 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, char *contents)
+{
+ struct directory *directory = make_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)
+ {
+ size_t size = dumpdir_size (contents);
+ directory->contents = xmalloc (size);
+ memcpy (directory->contents, contents, size);
+ }
+ else
+ directory->contents = NULL;
+
+ if (! ((directory_table
+ || (directory_table = hash_initialize (0, 0,
+ hash_directory_name,
+ compare_directory_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
+ {
+ struct directory *dir = make_directory (name);
+ struct directory *ret = hash_lookup (directory_table, dir);
+ free (dir);
+ return ret;
+ }
+}
+
+/* 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 ("");
+ struct directory *ret;
+ dir->device_number = dev;
+ dir->inode_number = ino;
+ ret = hash_lookup (directory_meta_table, dir);
+ free (dir);
+ return ret;
+ }
+}
+
+void
+update_parent_directory (const char *name)
+{
+ struct directory *directory;
+ char *p;
+
+ p = dir_name (name);
+ directory = find_directory (p);
+ if (directory)
+ {
+ struct stat st;
+ if (deref_stat (dereference_option, p, &st) != 0)
+ stat_diag (name);
+ else
+ directory->mtime = get_stat_mtime (&st);
+ }
+ free (p);
+}
+
+static struct directory *
+procdir (char *name_buffer, struct stat *stat_data,
+ dev_t device,
+ enum children children,
+ bool verbose)
+{
+ struct directory *directory;
+ bool nfs = NFS_FILE_STAT (*stat_data);
+
+ if ((directory = find_directory (name_buffer)) != NULL)
+ {
+ if (DIR_IS_INITED (directory))
+ 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 (! (((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 (verbose_option)
+ WARN ((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);
+ directory->children = CHANGED_CHILDREN;
+ }
+ else
+ {
+ if (verbose_option)
+ WARN ((0, 0, _("%s: Directory has been renamed"),
+ quotearg_colon (name_buffer)));
+ 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 (verbose)
+ WARN ((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);
+ directory->children = CHANGED_CHILDREN;
+ }
+ else
+ {
+ DIR_SET_FLAG (directory, DIRF_NEW);
+ if (verbose)
+ WARN ((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 the directory is on another device and --one-file-system was given,
+ omit it... */
+ if (one_file_system_option && device != stat_data->st_dev
+ /* ... except if it was explicitely given in the command line */
+ && !is_individual_file (name_buffer))
+ directory->children = NO_CHILDREN;
+ else if (children == ALL_CHILDREN)
+ directory->children = ALL_CHILDREN;
+
+ DIR_SET_FLAG (directory, DIRF_INIT);
+
+ return directory;
+}
+
+/* Locate NAME in the dumpdir array DUMP.
+ Return pointer to the slot in the array, or NULL if not found */
+const char *
+dumpdir_locate (const char *dump, const char *name)
+{
+ if (dump)
+ while (*dump)
+ {
+ /* Ignore 'R' (rename) and 'X' (tempname) entries, since they break
+ alphabetical ordering.
+ They normally do not occur in dumpdirs from the snapshot files,
+ but this function is also used by purge_directory, which operates
+ on a dumpdir from the archive, hence the need for this test. */
+ if (!strchr ("RX", *dump))
+ {
+ int rc = strcmp (dump + 1, name);
+ if (rc == 0)
+ return dump;
+ if (rc > 1)
+ break;
+ }
+ dump += strlen (dump) + 1;
+ }
+ return NULL;
+}
+
+/* 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 int
+compare_dirnames (const void *first, const void *second)
+{
+ char const *const *name1 = first;
+ char const *const *name2 = second;
+ return strcmp (*name1, *name2);
+}
+
+/* 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->contents must be sorted
+ alphabetically.
+
+ DIRECTORY->contents is replaced with the created template. Each entry is
+ prefixed with ' ' if it was present in DUMP and with 'Y' otherwise. */
+
+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;
+ const char *dump;
+
+ if (directory->children == ALL_CHILDREN)
+ dump = NULL;
+ else if (DIR_IS_RENAMED (directory))
+ dump = directory->orig->icontents ?
+ directory->orig->icontents : directory->orig->contents;
+ else
+ dump = directory->contents;
+
+ /* 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)
+ {
+ *new_dump_ptr++ = ' ';
+ dump = loc + strlen (loc) + 1;
+ }
+ else
+ *new_dump_ptr++ = 'Y'; /* New entry */
+
+ /* Copy the file name */
+ for (p = array[i]; (*new_dump_ptr++ = *p++); )
+ ;
+ }
+ *new_dump_ptr = 0;
+ directory->icontents = directory->contents;
+ directory->contents = new_dump;
+ free (array);
+}
+
+/* Recursively scan the given directory. */
+static char *
+scan_directory (char *dir_name, dev_t device)
+{
+ char *dirp = savedir (dir_name); /* for scanning directory */
+ char *name_buffer; /* directory, `/', and directory member */
+ size_t name_buffer_size; /* allocated size of name_buffer, minus 2 */
+ size_t name_length; /* used length in name_buffer */
+ struct stat stat_data;
+ struct directory *directory;
+
+ if (! dirp)
+ savedir_error (dir_name);
+
+ name_buffer_size = strlen (dir_name) + NAME_FIELD_SIZE;
+ name_buffer = xmalloc (name_buffer_size + 2);
+ strcpy (name_buffer, dir_name);
+ if (! ISSLASH (dir_name[strlen (dir_name) - 1]))
+ strcat (name_buffer, "/");
+ name_length = strlen (name_buffer);
+
+ if (deref_stat (dereference_option, name_buffer, &stat_data))
+ {
+ stat_diag (name_buffer);
+ /* FIXME: used to be
+ children = CHANGED_CHILDREN;
+ but changed to: */
+ free (name_buffer);
+ free (dirp);
+ return NULL;
+ }
+
+ directory = procdir (name_buffer, &stat_data, device, NO_CHILDREN, false);
+
+ if (dirp && directory->children != NO_CHILDREN)
+ {
+ char *entry; /* directory entry being scanned */
+ size_t entrylen; /* length of directory entry */
+
+ makedumpdir (directory, dirp);
+
+ for (entry = directory->contents;
+ (entrylen = strlen (entry)) != 0;
+ entry += entrylen + 1)
+ {
+ if (name_buffer_size <= entrylen - 1 + name_length)
+ {
+ do
+ name_buffer_size += NAME_FIELD_SIZE;
+ while (name_buffer_size <= entrylen - 1 + name_length);
+ name_buffer = xrealloc (name_buffer, name_buffer_size + 2);
+ }
+ strcpy (name_buffer + name_length, entry + 1);
+
+ if (excluded_name (name_buffer))
+ *entry = 'N';
+ else
+ {
+ if (deref_stat (dereference_option, name_buffer, &stat_data))
+ {
+ stat_diag (name_buffer);
+ *entry = 'N';
+ continue;
+ }
+
+ if (S_ISDIR (stat_data.st_mode))
+ {
+ procdir (name_buffer, &stat_data, device,
+ directory->children,
+ verbose_option);
+ *entry = 'D';
+ }
+
+ else if (one_file_system_option && device != stat_data.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 (stat_data, m)
+ && (!after_date_option
+ || OLDER_STAT_TIME (stat_data, c)))
+ *entry = 'N';
+ else
+ *entry = 'Y';
+ }
+ }
+ }
+
+ free (name_buffer);
+ if (dirp)
+ free (dirp);
+
+ return directory->contents;
+}
+
+char *
+get_directory_contents (char *dir_name, dev_t device)
+{
+ return scan_directory (dir_name, device);
+}
+
+
+static void
+obstack_code_rename (struct obstack *stk, char *from, char *to)
+{
+ obstack_1grow (stk, 'R');
+ obstack_grow (stk, from, strlen (from) + 1);
+ obstack_1grow (stk, 'T');
+ obstack_grow (stk, to, strlen (to) + 1);
+}
+
+static bool
+rename_handler (void *data, void *proc_data)
+{
+ struct directory *dir = data;
+ struct obstack *stk = proc_data;
+
+ 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);
+ }
+ }
+ return true;
+}
+
+const char *
+append_incremental_renames (const char *dump)
+{
+ struct obstack stk;
+ size_t size;
+
+ if (directory_table == NULL)
+ return dump;
+
+ obstack_init (&stk);
+ if (dump)
+ {
+ size = dumpdir_size (dump) - 1;
+ obstack_grow (&stk, dump, size);
+ }
+ else
+ size = 0;
+
+ hash_do_for_each (directory_table, rename_handler, &stk);
+ if (obstack_object_size (&stk) != size)
+ {
+ obstack_1grow (&stk, 0);
+ dump = obstack_finish (&stk);
+ }
+ else
+ obstack_free (&stk, NULL);
+ return dump;
+}
+
+
+
+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;
+ time_t sec;
+ long int nsec;
+ char *buf = 0;
+ size_t bufsize;
+ 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;
+ }
+
+ sec = TYPE_MINIMUM (time_t);
+ nsec = -1;
+ errno = 0;
+ u = strtoumax (buf, &ebuf, 10);
+ if (!errno && TYPE_MAXIMUM (time_t) < u)
+ errno = ERANGE;
+ if (errno || buf == ebuf)
+ ERROR ((0, errno, "%s:%ld: %s",
+ quotearg_colon (listed_incremental_option),
+ lineno,
+ _("Invalid time stamp")));
+ else
+ {
+ sec = u;
+
+ 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")));
+ sec = TYPE_MINIMUM (time_t);
+ }
+ else
+ nsec = u;
+ }
+ else
+ {
+ /* pre-1 incremental format does not contain nanoseconds */
+ nsec = 0;
+ }
+ }
+ newer_mtime_option.tv_sec = sec;
+ newer_mtime_option.tv_nsec = nsec;
+
+
+ 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)
+ {
+ errno = 0;
+ u = strtoumax (strp, &ebuf, 10);
+ if (!errno && TYPE_MAXIMUM (time_t) < u)
+ errno = ERANGE;
+ if (errno || strp == ebuf || *ebuf != ' ')
+ {
+ ERROR ((0, errno, "%s:%ld: %s",
+ quotearg_colon (listed_incremental_option), lineno,
+ _("Invalid modification time (seconds)")));
+ sec = (time_t) -1;
+ }
+ else
+ sec = u;
+ strp = ebuf;
+
+ 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)")));
+ nsec = -1;
+ }
+ else
+ nsec = u;
+ mtime.tv_sec = sec;
+ mtime.tv_nsec = nsec;
+ strp = ebuf;
+ }
+ else
+ memset (&mtime, 0, sizeof mtime);
+
+ errno = 0;
+ u = strtoumax (strp, &ebuf, 10);
+ if (!errno && TYPE_MAXIMUM (dev_t) < u)
+ errno = ERANGE;
+ if (errno || strp == ebuf || *ebuf != ' ')
+ {
+ ERROR ((0, errno, "%s:%ld: %s",
+ quotearg_colon (listed_incremental_option), lineno,
+ _("Invalid device number")));
+ dev = (dev_t) -1;
+ }
+ else
+ dev = u;
+ strp = ebuf;
+
+ errno = 0;
+ u = strtoumax (strp, &ebuf, 10);
+ if (!errno && TYPE_MAXIMUM (ino_t) < u)
+ errno = ERANGE;
+ if (errno || strp == ebuf || *ebuf != ' ')
+ {
+ ERROR ((0, errno, "%s:%ld: %s",
+ quotearg_colon (listed_incremental_option), lineno,
+ _("Invalid inode number")));
+ ino = (ino_t) -1;
+ }
+ else
+ ino = u;
+ strp = ebuf;
+
+ 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 nul-terminated string and convert it to
+ intmax_t. Return the resulting value in PVAL. Assume '-' has
+ already been read.
+
+ Throw a fatal error if the string cannot be converted or if the
+ converted value is less than MIN_VAL. */
+
+static void
+read_negative_num (FILE *fp, intmax_t min_val, intmax_t *pval)
+{
+ int c;
+ size_t i;
+ char buf[INT_BUFSIZE_BOUND (intmax_t)];
+ char *ep;
+ buf[0] = '-';
+
+ for (i = 1; ISDIGIT (c = getc (fp)); i++)
+ {
+ if (i == sizeof buf - 1)
+ FATAL_ERROR ((0, 0, _("Field too long while reading snapshot file")));
+ buf[i] = c;
+ }
+
+ if (c < 0)
+ {
+ if (ferror (fp))
+ FATAL_ERROR ((0, errno, _("Read error in snapshot file")));
+ else
+ FATAL_ERROR ((0, 0, _("Unexpected EOF in snapshot file")));
+ }
+
+ buf[i] = 0;
+ errno = 0;
+ *pval = strtoimax (buf, &ep, 10);
+ if (c || errno || *pval < min_val)
+ FATAL_ERROR ((0, errno, _("Unexpected field value in snapshot file")));
+}
+
+/* Read from file FP a nul-terminated string and convert it to
+ uintmax_t. Return the resulting value in PVAL. Assume C has
+ already been read.
+
+ Throw a fatal error if the string cannot be converted or if the
+ converted value exceeds MAX_VAL.
+
+ Return the last character read or EOF on end of file. */
+
+static int
+read_unsigned_num (int c, FILE *fp, uintmax_t max_val, uintmax_t *pval)
+{
+ size_t i;
+ char buf[UINTMAX_STRSIZE_BOUND], *ep;
+
+ for (i = 0; ISDIGIT (c); i++)
+ {
+ if (i == sizeof buf - 1)
+ FATAL_ERROR ((0, 0, _("Field too long while reading snapshot file")));
+ buf[i] = c;
+ c = getc (fp);
+ }
+
+ if (c < 0)
+ {
+ if (ferror (fp))
+ FATAL_ERROR ((0, errno, _("Read error in snapshot file")));
+ else if (i == 0)
+ return c;
+ else
+ FATAL_ERROR ((0, 0, _("Unexpected EOF in snapshot file")));
+ }
+
+ buf[i] = 0;
+ errno = 0;
+ *pval = strtoumax (buf, &ep, 10);
+ if (c || errno || max_val < *pval)
+ FATAL_ERROR ((0, errno, _("Unexpected field value in snapshot file")));
+ return c;
+}
+
+/* Read from file FP a nul-terminated string and convert it to
+ uintmax_t. Return the resulting value in PVAL.
+
+ Throw a fatal error if the string cannot be converted or if the
+ converted value exceeds MAX_VAL.
+
+ Return the last character read or EOF on end of file. */
+
+static int
+read_num (FILE *fp, uintmax_t max_val, uintmax_t *pval)
+{
+ return read_unsigned_num (getc (fp), fp, max_val, pval);
+}
+
+/* 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)
+{
+ int c = getc (fp);
+ intmax_t i;
+ uintmax_t u;
+
+ if (c == '-')
+ {
+ read_negative_num (fp, TYPE_MINIMUM (time_t), &i);
+ c = 0;
+ pval->tv_sec = i;
+ }
+ else
+ {
+ c = read_unsigned_num (c, fp, TYPE_MAXIMUM (time_t), &u);
+ pval->tv_sec = u;
+ }
+
+ if (c || read_num (fp, BILLION - 1, &u))
+ FATAL_ERROR ((0, 0, "%s: %s",
+ quotearg_colon (listed_incremental_option),
+ _("Unexpected EOF in snapshot file")));
+ pval->tv_nsec = u;
+}
+
+/* Read incremental snapshot format 2 */
+static void
+read_incr_db_2 ()
+{
+ uintmax_t u;
+ struct obstack stk;
+
+ obstack_init (&stk);
+
+ read_timespec (listed_incremental_stream, &newer_mtime_option);
+
+ for (;;)
+ {
+ struct timespec mtime;
+ dev_t dev;
+ ino_t ino;
+ bool nfs;
+ char *name;
+ char *content;
+ size_t s;
+
+ if (read_num (listed_incremental_stream, 1, &u))
+ return; /* Normal return */
+
+ nfs = u;
+
+ read_timespec (listed_incremental_stream, &mtime);
+
+ if (read_num (listed_incremental_stream, TYPE_MAXIMUM (dev_t), &u))
+ break;
+ dev = u;
+
+ if (read_num (listed_incremental_stream, TYPE_MAXIMUM (ino_t), &u))
+ break;
+ ino = u;
+
+ 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: %s",
+ quotearg_colon (listed_incremental_option),
+ _("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")));
+}
+
+/* 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 = 0;
+ size_t bufsize;
+
+ /* 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, O_RDWR | O_CREAT, 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;
+ }
+
+ 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);
+ if (buf)
+ 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[UINTMAX_STRSIZE_BOUND];
+ char *s;
+
+ s = DIR_IS_NFS (directory) ? "1" : "0";
+ fwrite (s, 2, 1, fp);
+ s = (TYPE_SIGNED (time_t)
+ ? imaxtostr (directory->mtime.tv_sec, buf)
+ : umaxtostr (directory->mtime.tv_sec, buf));
+ fwrite (s, strlen (s) + 1, 1, fp);
+ s = umaxtostr (directory->mtime.tv_nsec, buf);
+ fwrite (s, strlen (s) + 1, 1, fp);
+ s = umaxtostr (directory->device_number, buf);
+ fwrite (s, strlen (s) + 1, 1, fp);
+ s = umaxtostr (directory->inode_number, buf);
+ fwrite (s, strlen (s) + 1, 1, fp);
+
+ fwrite (directory->name, strlen (directory->name) + 1, 1, fp);
+ if (directory->contents)
+ {
+ char *p;
+ for (p = directory->contents; *p; p += strlen (p) + 1)
+ {
+ if (strchr ("YND", *p))
+ fwrite (p, strlen (p) + 1, 1, fp);
+ }
+ }
+ 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 (fseek (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 (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)
+ {
+ ERROR ((0, 0,
+ _("Malformed dumpdir: expected '%c' but found %#3o"),
+ expect, *p));
+ 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 preceeded 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)
+ WARN ((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;
+
+ if (!is_dumpdir (&current_stat_info))
+ return false;
+
+ current_dir = savedir (directory_name);
+
+ 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;
+
+ 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 */
+ p = NULL;
+ for (cur = current_dir; *cur; cur += strlen (cur) + 1)
+ {
+ const char *entry;
+ struct stat st;
+ if (p)
+ free (p);
+ p = new_name (directory_name, cur);
+
+ if (deref_stat (false, p, &st))
+ {
+ 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 (current_stat_info.dumpdir, 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);
+
+ 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..75837f6
--- /dev/null
+++ b/src/list.c
@@ -0,0 +1,1341 @@
+/* List a tar archive, with support routines for reading a tar archive.
+
+ Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1998, 1999, 2000,
+ 2001, 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
+
+ Written by John Gilmore, on 1985-08-26.
+
+ 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 2, 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#include <system.h>
+#include <inttostr.h>
+#include <quotearg.h>
+
+#include "common.h"
+
+#define max(a, b) ((a) < (b) ? (b) : (a))
+
+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 uintmax_t from_header (const char *, size_t, const char *,
+ uintmax_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;
+}
+
+/* Main loop for reading an archive. */
+void
+read_and (void (*do_something) (void))
+{
+ enum read_header status = HEADER_STILL_UNREAD;
+ enum read_header prev_status;
+ struct timespec mtime;
+
+ base64_init ();
+ name_gather ();
+
+ open_archive (ACCESS_READ);
+ do
+ {
+ prev_status = status;
+ tar_stat_destroy (&current_stat_info);
+
+ status = read_header (false);
+ 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. */
+
+ if (! name_match (current_stat_info.file_name)
+ || (NEWER_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))
+ {
+ 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:
+ decode_header (current_header,
+ &current_stat_info, &current_format, 0);
+ skip_member ();
+ continue;
+ }
+ }
+
+ (*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 (false);
+ if (status == HEADER_ZERO_BLOCK)
+ break;
+ WARN ((0, 0, _("A lone zero block at %s"),
+ STRINGIFY_BIGINT (current_block_ordinal (), buf)));
+ break;
+ }
+ status = prev_status;
+ continue;
+
+ case HEADER_END_OF_FILE:
+ if (block_number_option)
+ {
+ char buf[UINTMAX_STRSIZE_BOUND];
+ fprintf (stdlis, _("block %s: ** End of File **\n"),
+ STRINGIFY_BIGINT (current_block_ordinal (), buf));
+ }
+ break;
+
+ case HEADER_FAILURE:
+ /* If the previous header was good, tell them that we are
+ skipping bad ones. */
+ set_next_block_after (current_header);
+ switch (prev_status)
+ {
+ case HEADER_STILL_UNREAD:
+ ERROR ((0, 0, _("This does not look like a tar archive")));
+ /* Fall through. */
+
+ case HEADER_ZERO_BLOCK:
+ case HEADER_SUCCESS:
+ if (block_number_option)
+ {
+ char buf[UINTMAX_STRSIZE_BOUND];
+ off_t block_ordinal = current_block_ordinal ();
+ block_ordinal -= recent_long_name_blocks;
+ block_ordinal -= recent_long_link_blocks;
+ fprintf (stdlis, _("block %s: "),
+ STRINGIFY_BIGINT (block_ordinal, buf));
+ }
+ ERROR ((0, 0, _("Skipping to next header")));
+ break;
+
+ case HEADER_END_OF_FILE:
+ case HEADER_FAILURE:
+ /* We are in the middle of a cascade of errors. */
+ break;
+
+ case HEADER_SUCCESS_EXTENDED:
+ abort ();
+ }
+ continue;
+ }
+ break;
+ }
+ while (!all_names_found (&current_stat_info));
+
+ close_archive ();
+ names_notfound (); /* print names not found */
+}
+
+/* Print a header block, based on tar options. */
+void
+list_archive (void)
+{
+ off_t block_ordinal = current_block_ordinal ();
+ /* Print the header block. */
+
+ decode_header (current_header, &current_stat_info, &current_format, 0);
+ if (verbose_option)
+ print_header (&current_stat_info, block_ordinal);
+
+ if (incremental_option)
+ {
+ if (verbose_option > 2)
+ {
+ if (is_dumpdir (&current_stat_info))
+ list_dumpdir (current_stat_info.dumpdir,
+ dumpdir_size (current_stat_info.dumpdir));
+ }
+ }
+
+ skip_member ();
+}
+
+/* Check header checksum */
+/* The standard BSD tar sources create the checksum by adding up the
+ bytes in the header as type char. I think the type char was unsigned
+ on the PDP-11, but it's signed on the Next and Sun. It looks like the
+ sources to BSD tar were never changed to compute the checksum
+ correctly, so both the Sun and Next add the bytes of the header as
+ signed chars. This doesn't cause a problem until you get a file with
+ a name containing characters with the high bit set. So tar_checksum
+ computes two checksums -- signed and unsigned. */
+
+enum read_header
+tar_checksum (union block *header, bool silent)
+{
+ size_t i;
+ int unsigned_sum = 0; /* the POSIX one :-) */
+ int signed_sum = 0; /* the Sun one :-( */
+ int recorded_sum;
+ uintmax_t 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,
+ (uintmax_t) 0,
+ (uintmax_t) TYPE_MAXIMUM (int), true, silent);
+ if (parsed_sum == (uintmax_t) -1)
+ 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 "current_header", and if it is good, the file's size
+ and names (file name, link name) in *info.
+
+ Return 1 for success, 0 if the checksum is bad, EOF on eof, 2 for a
+ block full of zeros (EOF marker).
+
+ If RAW_EXTENDED_HEADERS is nonzero, do not automagically fold the
+ GNU long name and link headers into later headers.
+
+ You must always set_next_block_after(current_header) to skip past
+ the header which this routine reads. */
+
+enum read_header
+read_header_primitive (bool raw_extended_headers, struct tar_stat_info *info)
+{
+ 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;
+ size_t next_long_link_blocks;
+
+ while (1)
+ {
+ enum read_header status;
+
+ header = find_next_block ();
+ current_header = 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 (header->header.typeflag == GNUTYPE_LONGNAME
+ || header->header.typeflag == GNUTYPE_LONGLINK
+ || header->header.typeflag == XHDTYPE
+ || header->header.typeflag == XGLTYPE
+ || header->header.typeflag == SOLARIS_XHDTYPE)
+ {
+ if (raw_extended_headers)
+ 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)
+ {
+ if (next_long_name)
+ free (next_long_name);
+ next_long_name = header_copy;
+ next_long_name_blocks = size / BLOCKSIZE;
+ }
+ else
+ {
+ if (next_long_link)
+ 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;
+ memset (&xhdr, 0, sizeof xhdr);
+ xheader_read (&xhdr, header,
+ OFF_FROM_HEADER (header->header.size));
+ xheader_decode_global (&xhdr);
+ xheader_destroy (&xhdr);
+ }
+
+ /* Loop! */
+
+ }
+ else
+ {
+ char const *name;
+ struct posix_header const *h = &current_header->header;
+ char namebuf[sizeof h->prefix + 1 + NAME_FIELD_SIZE + 1];
+
+ if (recent_long_name)
+ 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);
+
+ if (recent_long_link)
+ 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;
+ }
+ }
+}
+
+enum read_header
+read_header (bool raw_extended_headers)
+{
+ return read_header_primitive (raw_extended_headers, &current_stat_info);
+}
+
+static char *
+decode_xform (char *file_name, void *data)
+{
+ xform_type type = *(xform_type*)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;
+}
+
+bool
+transform_member_name (char **pinput, xform_type type)
+{
+ return transform_name_fp (pinput, decode_xform, &type);
+}
+
+#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;
+
+ 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->header.magic, OLDGNU_MAGIC) == 0)
+ format = OLDGNU_FORMAT;
+ else
+ format = V7_FORMAT;
+ *format_pointer = format;
+
+ stat_info->stat.st_mode = MODE_FROM_HEADER (header->header.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);
+
+ 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;
+ }
+ }
+
+ stat_info->archive_file_size = stat_info->stat.st_size;
+ 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;
+ }
+
+ transform_member_name (&stat_info->file_name, xform_regfile);
+}
+
+/* Convert buffer at WHERE0 of size DIGS from external format to
+ uintmax_t. DIGS must be positive. If TYPE is nonnull, the data
+ are of type TYPE. The buffer must represent a value in the range
+ -MINUS_MINVAL through MAXVAL. 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. */
+static uintmax_t
+from_header (char const *where0, size_t digs, char const *type,
+ uintmax_t minus_minval, uintmax_t maxval,
+ bool octal_only, bool silent)
+{
+ uintmax_t value;
+ char const *where = where0;
+ char const *lim = where + digs;
+ int negative = 0;
+
+ /* 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;
+ uintmax_t overflow = 0;
+
+ 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 = 1;
+ }
+ }
+
+ 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;
+ 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 - where, 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 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;
+}
+
+gid_t
+gid_from_header (const char *p, size_t s)
+{
+ return from_header (p, s, "gid_t",
+ - (uintmax_t) TYPE_MINIMUM (gid_t),
+ (uintmax_t) TYPE_MAXIMUM (gid_t),
+ false, false);
+}
+
+major_t
+major_from_header (const char *p, size_t s)
+{
+ return from_header (p, s, "major_t",
+ - (uintmax_t) TYPE_MINIMUM (major_t),
+ (uintmax_t) TYPE_MAXIMUM (major_t), false, false);
+}
+
+minor_t
+minor_from_header (const char *p, size_t s)
+{
+ return from_header (p, s, "minor_t",
+ - (uintmax_t) TYPE_MINIMUM (minor_t),
+ (uintmax_t) TYPE_MAXIMUM (minor_t), false, false);
+}
+
+mode_t
+mode_from_header (const char *p, size_t s)
+{
+ /* Do not complain about unrecognized mode bits. */
+ unsigned u = from_header (p, s, "mode_t",
+ - (uintmax_t) TYPE_MINIMUM (mode_t),
+ TYPE_MAXIMUM (uintmax_t), false, false);
+ return ((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));
+}
+
+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", (uintmax_t) 0,
+ (uintmax_t) TYPE_MAXIMUM (off_t), false, false);
+}
+
+size_t
+size_from_header (const char *p, size_t s)
+{
+ return from_header (p, s, "size_t", (uintmax_t) 0,
+ (uintmax_t) TYPE_MAXIMUM (size_t), false, false);
+}
+
+time_t
+time_from_header (const char *p, size_t s)
+{
+ return from_header (p, s, "time_t",
+ - (uintmax_t) TYPE_MINIMUM (time_t),
+ (uintmax_t) TYPE_MAXIMUM (time_t), false, false);
+}
+
+uid_t
+uid_from_header (const char *p, size_t s)
+{
+ return from_header (p, s, "uid_t",
+ - (uintmax_t) TYPE_MINIMUM (uid_t),
+ (uintmax_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", (uintmax_t) 0,
+ TYPE_MAXIMUM (uintmax_t), 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. */
+
+
+/* FIXME: Note that print_header uses the globals HEAD, HSTAT, and
+ HEAD_STANDARD, which must be set up in advance. Not very clean.. */
+
+/* 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;
+
+void
+print_header (struct tar_stat_info *st, off_t block_ordinal)
+{
+ char modes[11];
+ char const *time_stamp;
+ int time_stamp_len;
+ char *temp_name;
+
+ /* These hold formatted ints. */
+ char uform[UINTMAX_STRSIZE_BOUND], gform[UINTMAX_STRSIZE_BOUND];
+ 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 (test_label_option && current_header->header.typeflag != GNUTYPE_VOLHDR)
+ return;
+
+ 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. */
+ fprintf (stdlis, "%s\n", quotearg (temp_name));
+ }
+ else
+ {
+ /* File type and modes. */
+
+ modes[0] = '?';
+ switch (current_header->header.typeflag)
+ {
+ case GNUTYPE_VOLHDR:
+ 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] = '-';
+ if (temp_name[strlen (temp_name) - 1] == '/')
+ modes[0] = '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);
+
+ /* Time stamp. */
+
+ time_stamp = tartime (st->mtime, false);
+ 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
+ {
+ /* Try parsing it as an unsigned integer first, and as a
+ uid_t if that fails. This method can list positive user
+ ids that are too large to fit in a uid_t. */
+ uintmax_t u = from_header (current_header->header.uid,
+ sizeof current_header->header.uid, 0,
+ (uintmax_t) 0,
+ (uintmax_t) TYPE_MAXIMUM (uintmax_t),
+ false, false);
+ if (u != -1)
+ user = STRINGIFY_BIGINT (u, uform);
+ else
+ {
+ sprintf (uform, "%ld",
+ (long) UID_FROM_HEADER (current_header->header.uid));
+ user = uform;
+ }
+ }
+
+ if (st->gname
+ && st->gname[0]
+ && current_format != V7_FORMAT
+ && !numeric_owner_option)
+ group = st->gname;
+ else
+ {
+ /* Try parsing it as an unsigned integer first, and as a
+ gid_t if that fails. This method can list positive group
+ ids that are too large to fit in a gid_t. */
+ uintmax_t g = from_header (current_header->header.gid,
+ sizeof current_header->header.gid, 0,
+ (uintmax_t) 0,
+ (uintmax_t) TYPE_MAXIMUM (uintmax_t),
+ false, false);
+ if (g != -1)
+ group = STRINGIFY_BIGINT (g, gform);
+ else
+ {
+ sprintf (gform, "%ld",
+ (long) GID_FROM_HEADER (current_header->header.gid));
+ group = gform;
+ }
+ }
+
+ /* Format the file size or major/minor device numbers. */
+
+ switch (current_header->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));
+
+ switch (current_header->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] = current_header->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 (current_header->oldgnu_header.offset),
+ uintbuf));
+ fprintf (stdlis, _("--Continued at byte %s--\n"), size);
+ break;
+ }
+ }
+ fflush (stdlis);
+}
+
+/* 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:"), length, 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 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 (&current_stat_info);
+
+ if (current_stat_info.is_sparse)
+ sparse_skip_file (&current_stat_info);
+ else if (save_typeflag != DIRTYPE)
+ skip_file (current_stat_info.stat.st_size);
+
+ mv_end ();
+ }
+}
diff --git a/src/misc.c b/src/misc.c
new file mode 100644
index 0000000..a886b06
--- /dev/null
+++ b/src/misc.c
@@ -0,0 +1,748 @@
+/* Miscellaneous functions, not really specific to GNU tar.
+
+ Copyright (C) 1988, 1992, 1994, 1995, 1996, 1997, 1999, 2000, 2001,
+ 2003, 2004, 2005, 2006, 2007 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 2, 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#include <system.h>
+#include <rmt.h>
+#include "common.h"
+#include <quotearg.h>
+#include <save-cwd.h>
+#include <xgetcwd.h>
+#include <unlinkdir.h>
+#include <utimens.h>
+
+#if HAVE_STROPTS_H
+# include <stropts.h>
+#endif
+#if HAVE_SYS_FILIO_H
+# include <sys/filio.h>
+#endif
+
+
+/* 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)
+{
+ if (*string)
+ free (*string);
+ *string = value ? xstrdup (value) : 0;
+}
+
+/* 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. */
+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;
+}
+
+/* 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;
+}
+
+/* Handling numbers. */
+
+/* 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;
+
+ 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;
+}
+
+/* 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 "/". */
+static 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;
+ }
+
+ return rmdir (file_name);
+}
+
+/* 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 (unlink (file_name) == 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 && unlink (file_name) == 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 = savedir (file_name);
+ char const *entry;
+ size_t entrylen;
+
+ if (! directory)
+ return 0;
+
+ for (entry = directory;
+ (entrylen = strlen (entry)) != 0;
+ entry += entrylen + 1)
+ {
+ char *file_name_buffer = new_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;
+
+ /* Check if we really need to backup the file. */
+
+ if (this_is_the_archive && _remdev (file_name))
+ return true;
+
+ if (stat (file_name, &file_stat))
+ {
+ 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;
+
+ 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);
+ after_backup_name = find_backup_file_name (file_name, backup_type);
+ if (! after_backup_name)
+ xalloc_die ();
+
+ if (rename (before_backup_name, 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 (rename (after_backup_name, 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);
+ }
+}
+
+/* Depending on DEREF, apply either stat or lstat to (NAME, BUF). */
+int
+deref_stat (bool deref, char const *name, struct stat *buf)
+{
+ return deref ? stat (name, buf) : lstat (name, buf);
+}
+
+/* Set FD's (i.e., FILE's) access time to TIMESPEC[0]. If that's not
+ possible to do by itself, set its access and data modification
+ times to TIMESPEC[0] and TIMESPEC[1], respectively. */
+int
+set_file_atime (int fd, char const *file, struct timespec const timespec[2])
+{
+#ifdef _FIOSATIME
+ if (0 <= fd)
+ {
+ struct timeval timeval;
+ timeval.tv_sec = timespec[0].tv_sec;
+ timeval.tv_usec = timespec[0].tv_nsec / 1000;
+ if (ioctl (fd, _FIOSATIME, &timeval) == 0)
+ return 0;
+ }
+#endif
+
+ return gl_futimens (fd, file, timespec);
+}
+
+/* A description of a working directory. */
+struct wd
+{
+ char const *name;
+ int saved;
+ struct saved_cwd saved_cwd;
+};
+
+/* 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 wds;
+
+/* The allocated size of the vector. */
+static size_t wd_alloc;
+
+/* 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)
+{
+ if (wds == wd_alloc)
+ {
+ if (wd_alloc == 0)
+ {
+ wd_alloc = 2;
+ wd = xmalloc (sizeof *wd * wd_alloc);
+ }
+ else
+ wd = x2nrealloc (wd, &wd_alloc, sizeof *wd);
+
+ if (! wds)
+ {
+ wd[wds].name = ".";
+ wd[wds].saved = 0;
+ wds++;
+ }
+ }
+
+ /* 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 wds - 1;
+ }
+
+ wd[wds].name = dir;
+ wd[wds].saved = 0;
+ return wds++;
+}
+
+/* Change to directory I. 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)
+{
+ static int previous;
+
+ if (previous != i)
+ {
+ struct wd *prev = &wd[previous];
+ struct wd *curr = &wd[i];
+
+ if (! prev->saved)
+ {
+ int err = 0;
+ prev->saved = 1;
+ if (save_cwd (&prev->saved_cwd) != 0)
+ err = errno;
+ else if (0 <= prev->saved_cwd.desc)
+ {
+ /* Make sure we still have at least one descriptor available. */
+ int fd1 = prev->saved_cwd.desc;
+ int fd2 = dup (fd1);
+ if (0 <= fd2)
+ close (fd2);
+ else if (errno == EMFILE)
+ {
+ /* Force restore_cwd to use chdir_long. */
+ close (fd1);
+ prev->saved_cwd.desc = -1;
+ prev->saved_cwd.name = xgetcwd ();
+ }
+ else
+ err = errno;
+ }
+
+ if (err)
+ FATAL_ERROR ((0, err, _("Cannot save working directory")));
+ }
+
+ if (curr->saved)
+ {
+ if (restore_cwd (&curr->saved_cwd))
+ FATAL_ERROR ((0, 0, _("Cannot change working directory")));
+ }
+ else
+ {
+ if (i && ! ISSLASH (curr->name[0]))
+ chdir_do (i - 1);
+ if (chdir (curr->name) != 0)
+ chdir_fatal (curr->name);
+ }
+
+ previous = i;
+ }
+}
+
+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
+write_fatal_details (char const *name, ssize_t status, size_t size)
+{
+ write_error_details (name, status, size);
+ fatal_exit ();
+}
+
+/* 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);
+}
diff --git a/src/names.c b/src/names.c
new file mode 100644
index 0000000..2eb7629
--- /dev/null
+++ b/src/names.c
@@ -0,0 +1,1022 @@
+/* Various processing of names.
+
+ Copyright (C) 1988, 1992, 1994, 1996, 1997, 1998, 1999, 2000, 2001,
+ 2003, 2004, 2005, 2006 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 2, 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#include <system.h>
+
+#include <fnmatch.h>
+#include <hash.h>
+#include <quotearg.h>
+
+#include "common.h"
+
+/* User and group names. */
+
+struct group *getgrnam ();
+struct passwd *getpwnam ();
+#if ! HAVE_DECL_GETPWUID
+struct passwd *getpwuid ();
+#endif
+#if ! HAVE_DECL_GETGRGID
+struct group *getgrgid ();
+#endif
+
+/* 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;
+
+static void register_individual_file (char const *name);
+
+/* 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;
+}
+
+
+/* Names from the command call. */
+
+static struct name *namelist; /* first name in list, if any */
+static struct name **nametail = &namelist; /* end of name list */
+
+/* File name arguments are processed in two stages: first a
+ name_array (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_array element contains entries of three types: */
+
+#define NELT_NAME 0 /* File name */
+#define NELT_CHDIR 1 /* Change directory request */
+#define NELT_FMASK 2 /* Change fnmatch options request */
+
+struct name_elt /* A name_array element. */
+{
+ char type; /* Element type, see NELT_* constants above */
+ union
+ {
+ const char *name; /* File or directory name */
+ int matching_flags;/* fnmatch options if type == NELT_FMASK */
+ } v;
+};
+
+static struct name_elt *name_array; /* store an array of names */
+static size_t allocated_names; /* how big is the array? */
+static size_t names; /* how many entries does it have? */
+static size_t name_index; /* how many of the entries have we scanned? */
+
+/* Check the size of name_array, reallocating it as necessary. */
+static void
+check_name_alloc ()
+{
+ if (names == allocated_names)
+ {
+ if (allocated_names == 0)
+ allocated_names = 10; /* Set initial allocation */
+ name_array = x2nrealloc (name_array, &allocated_names,
+ sizeof (name_array[0]));
+ }
+}
+
+/* Add to name_array the file NAME with fnmatch options MATCHING_FLAGS */
+void
+name_add_name (const char *name, int matching_flags)
+{
+ static int prev_flags = 0; /* FIXME: Or EXCLUDE_ANCHORED? */
+ struct name_elt *ep;
+
+ check_name_alloc ();
+ ep = &name_array[names++];
+ if (prev_flags != matching_flags)
+ {
+ ep->type = NELT_FMASK;
+ ep->v.matching_flags = matching_flags;
+ prev_flags = matching_flags;
+ check_name_alloc ();
+ ep = &name_array[names++];
+ }
+ ep->type = NELT_NAME;
+ ep->v.name = name;
+}
+
+/* Add to name_array a chdir request for the directory NAME */
+void
+name_add_dir (const char *name)
+{
+ struct name_elt *ep;
+ check_name_alloc ();
+ ep = &name_array[names++];
+ ep->type = NELT_CHDIR;
+ ep->v.name = name;
+}
+
+
+/* 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;
+}
+
+void
+name_term (void)
+{
+ free (name_buffer);
+ free (name_array);
+}
+
+static int matching_flags; /* exclude_fnmatch options */
+
+/* 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. If filename_terminator
+ is NUL, CHANGE_DIRS is effectively always false.
+
+ Entries of type NELT_FMASK cause updates of the matching_flags
+ value. */
+struct name_elt *
+name_next_elt (int change_dirs)
+{
+ static struct name_elt entry;
+ const char *source;
+ char *cursor;
+
+ if (filename_terminator == '\0')
+ change_dirs = 0;
+
+ while (name_index != names)
+ {
+ struct name_elt *ep;
+ size_t source_len;
+
+ ep = &name_array[name_index++];
+ if (ep->type == NELT_FMASK)
+ {
+ matching_flags = ep->v.matching_flags;
+ continue;
+ }
+
+ 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);
+
+ /* Zap trailing slashes. */
+
+ cursor = name_buffer + strlen (name_buffer) - 1;
+ while (cursor > name_buffer && ISSLASH (*cursor))
+ *cursor-- = '\0';
+
+ if (change_dirs && ep->type == NELT_CHDIR)
+ {
+ if (chdir (name_buffer) < 0)
+ chdir_fatal (name_buffer);
+ }
+ else
+ {
+ if (unquote_option)
+ unquote_string (name_buffer);
+ if (incremental_option)
+ register_individual_file (name_buffer);
+ entry.type = ep->type;
+ entry.v.name = name_buffer;
+ return &entry;
+ }
+ }
+
+ 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;
+ static size_t allocated_size;
+
+ struct name_elt *ep;
+
+ if (same_order_option)
+ {
+ static int change_dir;
+
+ if (allocated_size == 0)
+ {
+ allocated_size = offsetof (struct name, name) + NAME_FIELD_SIZE + 1;
+ buffer = xmalloc (allocated_size);
+ /* FIXME: This memset is overkill, and ugly... */
+ memset (buffer, 0, allocated_size);
+ }
+
+ while ((ep = name_next_elt (0)) && ep->type == NELT_CHDIR)
+ change_dir = chdir_arg (xstrdup (ep->v.name));
+
+ if (ep)
+ {
+ size_t needed_size;
+
+ buffer->length = strlen (ep->v.name);
+ needed_size = offsetof (struct name, name) + buffer->length + 1;
+ if (allocated_size < needed_size)
+ {
+ do
+ {
+ allocated_size *= 2;
+ if (! allocated_size)
+ xalloc_die ();
+ }
+ while (allocated_size < needed_size);
+
+ buffer = xrealloc (buffer, allocated_size);
+ }
+ buffer->change_dir = change_dir;
+ strcpy (buffer->name, ep->v.name);
+ buffer->next = 0;
+ buffer->found_count = 0;
+ buffer->matching_flags = matching_flags;
+
+ namelist = buffer;
+ nametail = &namelist->next;
+ }
+ else if (change_dir)
+ addname (0, change_dir);
+ }
+ 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);
+ else
+ {
+ if (change_dir != change_dir0)
+ addname (0, change_dir);
+ break;
+ }
+ }
+ }
+}
+
+/* Add a name to the namelist. */
+struct name *
+addname (char const *string, int change_dir)
+{
+ size_t length = string ? strlen (string) : 0;
+ struct name *name = xmalloc (offsetof (struct name, name) + length + 1);
+
+ if (string)
+ strcpy (name->name, string);
+ else
+ name->name[0] = 0;
+
+ name->next = NULL;
+ name->length = length;
+ name->found_count = 0;
+ name->matching_flags = matching_flags;
+ name->change_dir = change_dir;
+ name->dir_contents = NULL;
+
+ *nametail = name;
+ nametail = &name->next;
+ 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;
+}
+
+/* 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 = 0;
+ nametail = &namelist;
+ 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 = 0;
+ nametail = &namelist;
+ }
+ 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 (test_label_option)
+ return true;
+ 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 inline int
+is_pattern (const char *string)
+{
+ return strchr (string, '*') || strchr (string, '[') || strchr (string, '?');
+}
+
+static void
+regex_usage_warning (const char *name)
+{
+ static int warned_once = 0;
+
+ if (warn_regex_usage && is_pattern (name))
+ {
+ warned_once = 1;
+ WARN ((0, 0,
+ /* TRANSLATORS: The following three msgids form a single sentence.
+ */
+ _("Pattern matching characters used in file names. Please,")));
+ WARN ((0, 0,
+ _("use --wildcards to enable pattern matching, or --no-wildcards to")));
+ WARN ((0, 0,
+ _("suppress this warning.")));
+ }
+}
+
+/* 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);
+ if (cursor->found_count == 0)
+ ERROR ((0, 0, _("%s: Not found in archive"),
+ quotearg_colon (cursor->name)));
+ else
+ ERROR ((0, 0, _("%s: Required occurrence not found in archive"),
+ quotearg_colon (cursor->name)));
+ }
+
+ /* Don't bother freeing the name list; we're about to exit. */
+ namelist = 0;
+ nametail = &namelist;
+
+ 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)));
+ }
+ }
+}
+
+/* Sorting name lists. */
+
+/* Sort linked LIST of names, of given LENGTH, using COMPARE to order
+ names. Return the sorted list. 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 (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 (first_list, first_length, compare);
+ second_list = merge_sort (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
+}
+
+/* A comparison function for sorting names. Put found names last;
+ break ties by string comparison. */
+
+static int
+compare_names (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);
+}
+
+/* Add all the dirs under NAME, which names a directory, to the namelist.
+ If any of the files is a directory, recurse on the subdirectory.
+ DEVICE is the device not to leave, if the -l option is specified. */
+
+static void
+add_hierarchy_to_namelist (struct name *name, dev_t device)
+{
+ char *file_name = name->name;
+ char *buffer = get_directory_contents (file_name, device);
+
+ if (! buffer)
+ name->dir_contents = "\0\0\0\0";
+ else
+ {
+ 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? */
+ char *string;
+ size_t string_length;
+ int change_dir = name->change_dir;
+
+ name->dir_contents = buffer;
+ strcpy (namebuf, file_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;
+
+ 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);
+ add_hierarchy_to_namelist (np, device);
+ }
+ }
+
+ free (namebuf);
+ }
+}
+
+/* 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;
+ int num_names;
+ struct stat statbuf;
+
+ name_gather ();
+
+ if (listed_incremental_option)
+ read_directory_file ();
+
+ if (!namelist)
+ addname (".", 0);
+
+ for (name = namelist; name; name = next_name)
+ {
+ next_name = name->next;
+ if (name->found_count || name->dir_contents)
+ 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;
+
+ if (deref_stat (dereference_option, name->name, &statbuf) != 0)
+ {
+ stat_diag (name->name);
+ continue;
+ }
+ if (S_ISDIR (statbuf.st_mode))
+ {
+ name->found_count++;
+ add_hierarchy_to_namelist (name, statbuf.st_dev);
+ }
+ }
+
+ num_names = 0;
+ for (name = namelist; name; name = name->next)
+ num_names++;
+ namelist = merge_sort (namelist, num_names, compare_names);
+
+ for (name = namelist; name; name = name->next)
+ name->found_count = 0;
+
+ if (listed_incremental_option)
+ {
+ for (name = namelist; name && name->name[0] == 0; name++)
+ ;
+ if (name)
+ name->dir_contents = append_incremental_renames (name->dir_contents);
+ }
+}
+
+/* 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;
+
+char *
+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->name;
+ }
+ return 0;
+}
+
+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 FILE_NAME concatenated to
+ NAME, with an intervening slash if FILE_NAME does not already end in one. */
+char *
+new_name (const char *file_name, const char *name)
+{
+ size_t file_name_len = strlen (file_name);
+ size_t namesize = strlen (name) + 1;
+ int slash = file_name_len && ! ISSLASH (file_name[file_name_len - 1]);
+ char *buffer = xmalloc (file_name_len + slash + namesize);
+ memcpy (buffer, file_name, file_name_len);
+ buffer[file_name_len] = '/';
+ memcpy (buffer + file_name_len + slash, name, namesize);
+ return buffer;
+}
+
+/* Return nonzero if file NAME is excluded. */
+bool
+excluded_name (char const *name)
+{
+ return excluded_file_name (excluded, name + FILE_SYSTEM_PREFIX_LEN (name));
+}
+
+/* Names to avoid dumping. */
+static Hash_table *avoided_name_table;
+
+/* Remember to not archive NAME. */
+void
+add_avoided_name (char const *name)
+{
+ hash_string_insert (&avoided_name_table, name);
+}
+
+/* Should NAME be avoided when archiving? */
+bool
+is_avoided_name (char const *name)
+{
+ return hash_string_lookup (avoided_name_table, name);
+}
+
+
+static Hash_table *individual_file_table;
+
+static void
+register_individual_file (char const *name)
+{
+ struct stat st;
+
+ if (deref_stat (dereference_option, name, &st) != 0)
+ return; /* Will be complained about later */
+ if (S_ISDIR (st.st_mode))
+ return;
+
+ hash_string_insert (&individual_file_table, name);
+}
+
+bool
+is_individual_file (char const *name)
+{
+ return hash_string_lookup (individual_file_table, name);
+}
+
+
+
+/* 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;
+
+ do
+ {
+ if (! *p++)
+ return 0;
+ }
+ while (! ISSLASH (*p));
+ }
+}
diff --git a/src/sparse.c b/src/sparse.c
new file mode 100644
index 0000000..aa76c61
--- /dev/null
+++ b/src/sparse.c
@@ -0,0 +1,1175 @@
+/* Functions for dealing with sparse files
+
+ Copyright (C) 2003, 2004, 2005, 2006, 2007 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 2, 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#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 and create its map */
+static bool
+sparse_scan_file (struct tar_sparse_file *file)
+{
+ struct tar_stat_info *st = file->stat_info;
+ int fd = file->fd;
+ char buffer[BLOCKSIZE];
+ size_t count;
+ off_t offset = 0;
+ struct sp_array sp = {0, 0};
+
+ if (!lseek_or_error (file, 0))
+ return false;
+
+ st->archive_file_size = 0;
+
+ if (!tar_sparse_scan (file, scan_begin, NULL))
+ return false;
+
+ while ((count = safe_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;
+ }
+
+ 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 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;
+ mv_size_left (file->stat_info->archive_file_size - file->dumped_size);
+ set_next_block_after (blk);
+ }
+
+ return true;
+}
+
+static bool
+sparse_extract_region (struct tar_sparse_file *file, size_t i)
+{
+ size_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 = full_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 (file.stat_info);
+ for (i = 0; rc && i < file.stat_info->sparse_map_avail; i++)
+ rc = tar_sparse_dump_region (&file, i);
+ mv_end ();
+ }
+ }
+
+ 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)
+{
+ size_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 (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 = SIZE_FROM_HEADER (s->numbytes);
+ if (sp.offset < 0
+ || 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 */
+ file->stat_info->archive_file_size = file->stat_info->stat.st_size;
+ file->stat_info->stat.st_size =
+ OFF_FROM_HEADER (current_header->oldgnu_header.realsize);
+ return true;
+}
+
+/* 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);
+ SIZE_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 */
+ file->stat_info->archive_file_size = file->stat_info->stat.st_size;
+ file->stat_info->stat.st_size =
+ OFF_FROM_HEADER (current_header->star_in_header.realsize);
+ return true;
+}
+
+/* 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]);
+ }
+
+ 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;
+}
+
+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 = start_header (file->stat_info);
+ /* Store the effective (shrunken) file size */
+ OFF_TO_CHARS (file->stat_info->archive_file_size, blk->header.size);
+ 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 *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);
+
+ blk = start_header (file->stat_info);
+ /* Store the effective (shrunken) file size */
+ OFF_TO_CHARS (file->stat_info->archive_file_size, blk->header.size);
+ 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;
+
+ 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 (size_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/system.c b/src/system.c
new file mode 100644
index 0000000..e2d0431
--- /dev/null
+++ b/src/system.c
@@ -0,0 +1,844 @@
+/* System-dependent calls for tar.
+
+ Copyright (C) 2003, 2004, 2005, 2006 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 2, 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#include <system.h>
+#include <getline.h>
+#include <setenv.h>
+
+#include "common.h"
+#include <rmt.h>
+#include <signal.h>
+
+#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_drain_input_pipe (void)
+{
+}
+
+void
+sys_wait_for_child (pid_t child_pid)
+{
+}
+
+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));
+}
+
+/* Manage to fully drain a pipe we might be reading, so to not break it on
+ the producer after the EOF block. FIXME: one of these days, GNU tar
+ might become clever enough to just stop working, once there is no more
+ work to do, we might have to revise this area in such time. */
+
+void
+sys_drain_input_pipe (void)
+{
+ size_t r;
+
+ if (access_mode == ACCESS_READ
+ && ! _isrmt (archive)
+ && (S_ISFIFO (archive_stat.st_mode) || S_ISSOCK (archive_stat.st_mode)))
+ while ((r = rmtread (archive, record_start->buffer, record_size)) != 0
+ && r != SAFE_READ_ERROR)
+ continue;
+}
+
+void
+sys_wait_for_child (pid_t child_pid)
+{
+ 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))
+ ERROR ((0, 0, _("Child died with signal %d"),
+ WTERMSIG (wait_status)));
+ else if (WEXITSTATUS (wait_status) != 0)
+ 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)
+ {
+ execlp (shell, "-sh", "-i", (char *) 0);
+ 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);
+ }
+}
+
+/* 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;
+ int wait_status;
+
+ 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! */
+
+ program_name = _("tar (child)");
+
+ 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);
+ }
+ execlp (use_compress_program_option, use_compress_program_option, NULL);
+ exec_fatal (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. */
+
+ program_name = _("tar (grandchild)");
+
+ xdup2 (child_pipe[PWRITE], STDOUT_FILENO);
+ xclose (child_pipe[PREAD]);
+ execlp (use_compress_program_option, use_compress_program_option,
+ (char *) 0);
+ exec_fatal (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);
+ }
+
+ /* Propagate any failure of the grandchild back to the parent. */
+
+ while (waitpid (grandchild_pid, &wait_status, 0) == -1)
+ if (errno != EINTR)
+ {
+ waitpid_error (use_compress_program_option);
+ break;
+ }
+
+ if (WIFSIGNALED (wait_status))
+ {
+ kill (child_pid, WTERMSIG (wait_status));
+ exit_status = TAREXIT_FAILURE;
+ }
+ else if (WEXITSTATUS (wait_status) != 0)
+ exit_status = WEXITSTATUS (wait_status);
+
+ exit (exit_status);
+}
+
+/* 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;
+ int wait_status;
+
+ 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! */
+
+ program_name = _("tar (child)");
+
+ 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);
+ execlp (use_compress_program_option, use_compress_program_option,
+ "-d", (char *) 0);
+ exec_fatal (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 uncompressor. */
+
+ program_name = _("tar (grandchild)");
+
+ xdup2 (child_pipe[PREAD], STDIN_FILENO);
+ xclose (child_pipe[PWRITE]);
+ execlp (use_compress_program_option, use_compress_program_option,
+ "-d", (char *) 0);
+ exec_fatal (use_compress_program_option);
+ }
+
+ /* 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);
+
+ /* Propagate any failure of the grandchild back to the parent. */
+
+ while (waitpid (grandchild_pid, &wait_status, 0) == -1)
+ if (errno != EINTR)
+ {
+ waitpid_error (use_compress_program_option);
+ break;
+ }
+
+ if (WIFSIGNALED (wait_status))
+ {
+ kill (child_pid, WTERMSIG (wait_status));
+ exit_status = TAREXIT_FAILURE;
+ }
+ else if (WEXITSTATUS (wait_status) != 0)
+ exit_status = WEXITSTATUS (wait_status);
+
+ exit (exit_status);
+}
+
+
+
+static void
+dec_to_env (char *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 *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 *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 *envar, char const *str)
+{
+ if (str)
+ {
+ if (setenv (envar, str, 1) != 0)
+ xalloc_die ();
+ }
+ else
+ unsetenv (envar);
+}
+
+static void
+chr_to_env (char *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);
+ 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 pid;
+static RETSIGTYPE (*pipe_handler) (int sig);
+
+int
+sys_exec_command (char *file_name, int typechar, struct tar_stat_info *st)
+{
+ int p[2];
+ char *argv[4];
+
+ xpipe (p);
+ pipe_handler = signal (SIGPIPE, SIG_IGN);
+ pid = xfork ();
+
+ if (pid != 0)
+ {
+ xclose (p[PREAD]);
+ return p[PWRITE];
+ }
+
+ /* Child */
+ xdup2 (p[PREAD], STDIN_FILENO);
+ xclose (p[PWRITE]);
+
+ stat_to_env (file_name, typechar, st);
+
+ argv[0] = "/bin/sh";
+ argv[1] = "-c";
+ argv[2] = to_command_option;
+ argv[3] = NULL;
+
+ execv ("/bin/sh", argv);
+
+ exec_fatal (file_name);
+}
+
+void
+sys_wait_command (void)
+{
+ int status;
+
+ if (pid < 0)
+ return;
+
+ signal (SIGPIPE, pipe_handler);
+ while (waitpid (pid, &status, 0) == -1)
+ if (errno != EINTR)
+ {
+ 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) pid, WEXITSTATUS (status)));
+ }
+ else if (WIFSIGNALED (status))
+ {
+ WARN ((0, 0, _("%lu: Child terminated on signal %d"),
+ (unsigned long) pid, WTERMSIG (status)));
+ }
+ else
+ ERROR ((0, 0, _("%lu: Child terminated on unknown reason"),
+ (unsigned long) pid));
+
+ pid = -1;
+}
+
+int
+sys_exec_info_script (const char **archive_name, int volume_number)
+{
+ pid_t pid;
+ char *argv[4];
+ char uintbuf[UINTMAX_STRSIZE_BOUND];
+ int p[2];
+
+ xpipe (p);
+ pipe_handler = signal (SIGPIPE, SIG_IGN);
+
+ pid = xfork ();
+
+ if (pid != 0)
+ {
+ /* Master */
+
+ int rc;
+ int status;
+ char *buf;
+ 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)
+ {
+ waitpid_error (info_script_option);
+ return -1;
+ }
+
+ 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_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]);
+
+ argv[0] = "/bin/sh";
+ argv[1] = "-c";
+ argv[2] = (char*) info_script_option;
+ argv[3] = NULL;
+
+ execv (argv[0], argv);
+
+ exec_fatal (info_script_option);
+}
+
+
+#endif /* not MSDOS */
diff --git a/src/tar.c b/src/tar.c
new file mode 100644
index 0000000..1b95ccf
--- /dev/null
+++ b/src/tar.c
@@ -0,0 +1,2427 @@
+/* A tar (tape archiver) program.
+
+ Copyright (C) 1988, 1992, 1993, 1994, 1995, 1996, 1997, 1999, 2000,
+ 2001, 2003, 2004, 2005, 2006, 2007 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 2, 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#include <system.h>
+
+#include <fnmatch.h>
+#include <getline.h>
+#include <argp.h>
+#include <argp-namefrob.h>
+#include <argp-fmtstream.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 <getdate.h>
+#include <rmt.h>
+#include <rmt-command.h>
+#include <prepargs.h>
+#include <quotearg.h>
+#include <version-etc.h>
+#include <xstrtol.h>
+#include <stdopen.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
+
+
+/* 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;
+}
+
+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";
+
+ default:
+ abort ();
+ }
+}
+
+void
+tar_list_quoting_styles (argp_fmtstream_t fs, char *prefix)
+{
+ int i;
+
+ for (i = 0; quoting_style_args[i]; i++)
+ argp_fmtstream_printf (fs, "%s%s\n", prefix, quoting_style_args[i]);
+}
+
+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_invocation_short_name));
+}
+
+
+/* Options. */
+
+enum
+{
+ ANCHORED_OPTION = CHAR_MAX + 1,
+ ATIME_PRESERVE_OPTION,
+ BACKUP_OPTION,
+ CHECKPOINT_OPTION,
+ DELAY_DIRECTORY_RESTORE_OPTION,
+ DELETE_OPTION,
+ EXCLUDE_CACHES_OPTION,
+ EXCLUDE_CACHES_UNDER_OPTION,
+ EXCLUDE_CACHES_ALL_OPTION,
+ EXCLUDE_OPTION,
+ EXCLUDE_TAG_OPTION,
+ EXCLUDE_TAG_UNDER_OPTION,
+ EXCLUDE_TAG_ALL_OPTION,
+ FORCE_LOCAL_OPTION,
+ GROUP_OPTION,
+ HANG_OPTION,
+ IGNORE_CASE_OPTION,
+ IGNORE_COMMAND_ERROR_OPTION,
+ IGNORE_FAILED_READ_OPTION,
+ INDEX_FILE_OPTION,
+ KEEP_NEWER_FILES_OPTION,
+ MODE_OPTION,
+ MTIME_OPTION,
+ NEWER_MTIME_OPTION,
+ NO_ANCHORED_OPTION,
+ NO_DELAY_DIRECTORY_RESTORE_OPTION,
+ NO_IGNORE_CASE_OPTION,
+ NO_IGNORE_COMMAND_ERROR_OPTION,
+ NO_OVERWRITE_DIR_OPTION,
+ NO_QUOTE_CHARS_OPTION,
+ NO_RECURSION_OPTION,
+ NO_SAME_OWNER_OPTION,
+ NO_SAME_PERMISSIONS_OPTION,
+ NO_UNQUOTE_OPTION,
+ NO_WILDCARDS_MATCH_SLASH_OPTION,
+ NO_WILDCARDS_OPTION,
+ NULL_OPTION,
+ NUMERIC_OWNER_OPTION,
+ OCCURRENCE_OPTION,
+ OLD_ARCHIVE_OPTION,
+ ONE_FILE_SYSTEM_OPTION,
+ OVERWRITE_DIR_OPTION,
+ OVERWRITE_OPTION,
+ OWNER_OPTION,
+ PAX_OPTION,
+ POSIX_OPTION,
+ PRESERVE_OPTION,
+ QUOTE_CHARS_OPTION,
+ QUOTING_STYLE_OPTION,
+ RECORD_SIZE_OPTION,
+ RECURSION_OPTION,
+ RECURSIVE_UNLINK_OPTION,
+ REMOVE_FILES_OPTION,
+ RESTRICT_OPTION,
+ RMT_COMMAND_OPTION,
+ RSH_COMMAND_OPTION,
+ SAME_OWNER_OPTION,
+ SHOW_DEFAULTS_OPTION,
+ SHOW_OMITTED_DIRS_OPTION,
+ SHOW_TRANSFORMED_NAMES_OPTION,
+ SPARSE_VERSION_OPTION,
+ STRIP_COMPONENTS_OPTION,
+ SUFFIX_OPTION,
+ TEST_LABEL_OPTION,
+ TOTALS_OPTION,
+ TO_COMMAND_OPTION,
+ TRANSFORM_OPTION,
+ UNQUOTE_OPTION,
+ USAGE_OPTION,
+ USE_COMPRESS_PROGRAM_OPTION,
+ UTC_OPTION,
+ VERSION_OPTION,
+ VOLNO_FILE_OPTION,
+ WILDCARDS_MATCH_SLASH_OPTION,
+ WILDCARDS_OPTION
+};
+
+const char *argp_program_version = "tar (" PACKAGE_NAME ") " VERSION;
+const char *argp_program_bug_address = "<" PACKAGE_BUGREPORT ">";
+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 DEIJQY and aeqy. 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?]
+ [I same as T =/= will harm star compatibility]
+
+ y per-file gzip compression
+ Y per-block gzip compression */
+
+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 },
+ {"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 },
+ {"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 },
+#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"), 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 },
+#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 },
+ {"mtime", MTIME_OPTION, N_("DATE-OR-FILE"), 0,
+ N_("set mtime for added files from DATE-OR-FILE"), 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"), GRID+1 },
+ {"no-same-owner", NO_SAME_OWNER_OPTION, 0, 0,
+ N_("extract files as yourself"), 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_("sort names to extract to match archive"), GRID+1 },
+ {"same-order", 0, 0, OPTION_ALIAS, NULL, GRID+1 },
+ {"preserve", PRESERVE_OPTION, 0, 0,
+ N_("same as both -p and -s"), 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 },
+#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 },
+ {"bzip2", 'j', 0, 0,
+ N_("filter the archive through bzip2"), GRID+8 },
+ {"gzip", 'z', 0, 0,
+ N_("filter the archive through gzip"), GRID+8 },
+ {"gunzip", 0, 0, OPTION_ALIAS, NULL, GRID+8 },
+ {"ungzip", 0, 0, OPTION_ALIAS, NULL, GRID+8 },
+ {"compress", 'Z', 0, 0,
+ N_("filter the archive through compress"), GRID+8 },
+ {"uncompress", 0, 0, OPTION_ALIAS, NULL, GRID+8 },
+ {"use-compress-program", USE_COMPRESS_PROGRAM_OPTION, N_("PROG"), 0,
+ N_("filter through PROG (must accept -d)"), GRID+8 },
+#undef GRID
+
+#define GRID 90
+ {NULL, 0, NULL, 0,
+ N_("Local file 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, disable -C"), GRID+1 },
+ {"unquote", UNQUOTE_OPTION, 0, 0,
+ N_("unquote filenames read with -T (default)"), GRID+1 },
+ {"no-unquote", NO_UNQUOTE_OPTION, 0, 0,
+ N_("do not unquote filenames read with -T"), 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-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 },
+ {"no-recursion", NO_RECURSION_OPTION, 0, 0,
+ N_("avoid descending automatically in directories"), GRID+1 },
+ {"one-file-system", ONE_FILE_SYSTEM_OPTION, 0, 0,
+ N_("stay in local file system when creating archive"), GRID+1 },
+ {"recursion", RECURSION_OPTION, 0, 0,
+ N_("recurse into directories (default)"), 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 },
+ {"starting-file", 'K', N_("MEMBER-NAME"), 0,
+ N_("begin at member MEMBER-NAME in 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 92
+ {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 },
+#undef GRID
+
+#define GRID 95
+ {NULL, 0, NULL, 0,
+ N_("File name matching options (affect both exclude and include patterns):"),
+ GRID },
+ {"ignore-case", IGNORE_CASE_OPTION, 0, 0,
+ N_("ignore case"), GRID+1 },
+ {"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 },
+ {"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 },
+ {"no-wildcards-match-slash", NO_WILDCARDS_MATCH_SLASH_OPTION, 0, 0,
+ N_("wildcards do not match `/'"), GRID+1 },
+ {"wildcards-match-slash", WILDCARDS_MATCH_SLASH_OPTION, 0, 0,
+ N_("wildcards match `/' (default for exclusion)"), GRID+1 },
+#undef GRID
+
+#define GRID 100
+ {NULL, 0, NULL, 0,
+ N_("Informative output:"), GRID },
+
+ {"verbose", 'v', 0, 0,
+ N_("verbosely list files processed"), GRID+1 },
+ {"checkpoint", CHECKPOINT_OPTION, N_("[.]NUMBER"), OPTION_ARG_OPTIONAL,
+ N_("display progress messages every NUMBERth record (default 10)"),
+ 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 dates in UTC"), 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-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 110
+ {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 120
+ {NULL, 0, NULL, 0,
+ N_("Other options:"), GRID },
+
+ {"restrict", RESTRICT_OPTION, 0, 0,
+ N_("disable use of some potentially harmful options"), -1 },
+
+ {"help", '?', 0, 0, N_("give this help list"), -1},
+ {"usage", USAGE_OPTION, 0, 0, N_("give a short usage message"), -1},
+ {"version", VERSION_OPTION, 0, 0, N_("print program version"), -1},
+ /* FIXME -V (--label) conflicts with the default short option for
+ --version */
+ {"HANG", HANG_OPTION, "SECS", OPTION_ARG_OPTIONAL | OPTION_HIDDEN,
+ N_("hang for SECS seconds (default 3600)"), 0},
+#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);
+
+/* Wildcard matching settings */
+enum wildcards
+ {
+ default_wildcards, /* For exclusion == enable_wildcards,
+ for inclusion == disable_wildcards */
+ disable_wildcards,
+ enable_wildcards
+ };
+
+struct tar_args /* Variables used during option parsing */
+{
+ struct textual_date *textual_date; /* Keeps the arguments to --newer-mtime
+ and/or --date option if they are
+ textual dates */
+ enum wildcards wildcards; /* Wildcard settings (--wildcards/
+ --no-wildcards) */
+ int matching_flags; /* exclude_fnmatch options */
+ int include_anchored; /* Pattern anchoring options used for
+ file inclusion */
+ 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 */
+};
+
+#define MAKE_EXCL_OPTIONS(args) \
+ ((((args)->wildcards != disable_wildcards) ? EXCLUDE_WILDCARDS : 0) \
+ | (args)->matching_flags \
+ | recursion_option)
+
+#define MAKE_INCL_OPTIONS(args) \
+ ((((args)->wildcards == enable_wildcards) ? EXCLUDE_WILDCARDS : 0) \
+ | (args)->include_anchored \
+ | (args)->matching_flags \
+ | recursion_option)
+
+#ifdef REMOTE_SHELL
+# define DECL_SHOW_DEFAULT_SETTINGS(stream, printer) \
+{ \
+ printer (stream, \
+ "--format=%s -f%s -b%d --quoting-style=%s --rmt-command=%s", \
+ archive_format_string (DEFAULT_ARCHIVE_FORMAT), \
+ DEFAULT_ARCHIVE, DEFAULT_BLOCKING, \
+ quoting_style_args[DEFAULT_QUOTING_STYLE], \
+ DEFAULT_RMT_COMMAND); \
+ printer (stream, " --rsh-command=%s", REMOTE_SHELL); \
+ printer (stream, "\n"); \
+}
+#else
+# define DECL_SHOW_DEFAULT_SETTINGS(stream, printer) \
+{ \
+ printer (stream, \
+ "--format=%s -f%s -b%d --quoting-style=%s --rmt-command=%s", \
+ archive_format_string (DEFAULT_ARCHIVE_FORMAT), \
+ DEFAULT_ARCHIVE, DEFAULT_BLOCKING, \
+ quoting_style_args[DEFAULT_QUOTING_STYLE], \
+ DEFAULT_RMT_COMMAND); \
+ printer (stream, "\n"); \
+}
+#endif
+
+static void
+show_default_settings (FILE *fp)
+ DECL_SHOW_DEFAULT_SETTINGS(fp, fprintf)
+
+static void
+show_default_settings_fs (argp_fmtstream_t fs)
+ DECL_SHOW_DEFAULT_SETTINGS(fs, argp_fmtstream_printf)
+
+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' option")));
+
+ subcommand_option = subcommand;
+}
+
+static void
+set_use_compress_program_option (const char *string)
+{
+ if (use_compress_program_option
+ && strcmp (use_compress_program_option, string) != 0)
+ USAGE_ERROR ((0, 0, _("Conflicting compression options")));
+
+ use_compress_program_option = string;
+}
+
+static RETSIGTYPE
+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
+ struct sigaction act;
+ act.sa_handler = sigstat;
+ sigemptyset (&act.sa_mask);
+ act.sa_flags = 0;
+ sigaction (signo, &act, NULL);
+#else
+ signal (signo, sigstat);
+#endif
+}
+
+void
+set_stat_signal (const char *name)
+{
+ static struct sigtab
+ {
+ char *name;
+ int signo;
+ } sigtab[] = {
+ { "SIGUSR1", SIGUSR1 },
+ { "USR1", SIGUSR1 },
+ { "SIGUSR2", SIGUSR2 },
+ { "USR2", SIGUSR2 },
+ { "SIGHUP", SIGHUP },
+ { "HUP", SIGHUP },
+ { "SIGINT", SIGINT },
+ { "INT", SIGINT },
+ { "SIGQUIT", SIGQUIT },
+ { "QUIT", SIGQUIT }
+ };
+ struct sigtab *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;
+ const char *date;
+};
+
+static void
+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 (deref_stat (dereference_option, str, &st) != 0)
+ {
+ stat_error (str);
+ USAGE_ERROR ((0, 0, _("Date sample file not found")));
+ }
+ *ts = get_stat_mtime (&st);
+ }
+ else
+ {
+ if (! get_date (ts, str, NULL))
+ {
+ WARN ((0, 0, _("Substituting %s for unknown date format %s"),
+ tartime (*ts, false), quote (str)));
+ ts->tv_nsec = 0;
+ }
+ else
+ {
+ struct textual_date *p = xmalloc (sizeof (*p));
+ p->ts = ts;
+ p->option = option;
+ p->date = str;
+ p->next = args->textual_date;
+ args->textual_date = p;
+ }
+ }
+}
+
+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;
+ 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);
+ p = next;
+ }
+}
+
+
+static volatile int _argp_hang;
+
+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 FILENAME_TERMINATOR and put them
+ into STK.
+ */
+static enum read_file_list_state
+read_name_from_file (FILE *fp, struct obstack *stk)
+{
+ int c;
+ size_t counter = 0;
+
+ for (c = getc (fp); c != EOF && c != filename_terminator; c = getc (fp))
+ {
+ if (c == 0)
+ {
+ /* We have read a zero separator. The file possibly is
+ zero-separated */
+ return file_list_zero;
+ }
+ obstack_1grow (stk, c);
+ counter++;
+ }
+
+ if (counter == 0 && c != EOF)
+ return file_list_skip;
+
+ obstack_1grow (stk, 0);
+
+ return (counter == 0 && c == EOF) ? file_list_end : file_list_success;
+}
+
+
+static bool files_from_option; /* When set, tar will not refuse to create
+ empty archives */
+static struct obstack argv_stk; /* Storage for additional command line options
+ read using -T option */
+
+/* Prevent recursive inclusion of the same file */
+struct file_id_list
+{
+ struct file_id_list *next;
+ ino_t ino;
+ dev_t dev;
+};
+
+static struct file_id_list *file_id_list;
+
+static void
+add_file_id (const char *filename)
+{
+ struct file_id_list *p;
+ struct stat st;
+
+ if (stat (filename, &st))
+ stat_fatal (filename);
+ for (p = file_id_list; p; p = p->next)
+ if (p->ino == st.st_ino && p->dev == st.st_dev)
+ {
+ FATAL_ERROR ((0, 0, _("%s: file list already read"),
+ quotearg_colon (filename)));
+ }
+ p = xmalloc (sizeof *p);
+ p->next = file_id_list;
+ p->ino = st.st_ino;
+ p->dev = st.st_dev;
+ file_id_list = p;
+}
+
+/* Default density numbers for [0-9][lmh] device specifications */
+
+#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
+
+static void
+update_argv (const char *filename, struct argp_state *state)
+{
+ FILE *fp;
+ size_t count = 0, i;
+ char *start, *p;
+ char **new_argv;
+ size_t new_argc;
+ bool is_stdin = false;
+ enum read_file_list_state read_state;
+
+ if (!strcmp (filename, "-"))
+ {
+ is_stdin = true;
+ request_stdin ("-T");
+ fp = stdin;
+ }
+ else
+ {
+ add_file_id (filename);
+ if ((fp = fopen (filename, "r")) == NULL)
+ open_fatal (filename);
+ }
+
+ while ((read_state = read_name_from_file (fp, &argv_stk)) != file_list_end)
+ {
+ switch (read_state)
+ {
+ case file_list_success:
+ count++;
+ break;
+
+ case file_list_end: /* won't happen, just to pacify gcc */
+ break;
+
+ case file_list_zero:
+ {
+ size_t size;
+
+ WARN ((0, 0, N_("%s: file name read contains nul character"),
+ quotearg_colon (filename)));
+
+ /* Prepare new stack contents */
+ size = obstack_object_size (&argv_stk);
+ p = obstack_finish (&argv_stk);
+ for (; size > 0; size--, p++)
+ if (*p)
+ obstack_1grow (&argv_stk, *p);
+ else
+ obstack_1grow (&argv_stk, '\n');
+ obstack_1grow (&argv_stk, 0);
+ count = 1;
+ /* Read rest of files using new filename terminator */
+ filename_terminator = 0;
+ break;
+ }
+
+ case file_list_skip:
+ break;
+ }
+ }
+
+ if (!is_stdin)
+ fclose (fp);
+
+ if (count == 0)
+ return;
+
+ start = obstack_finish (&argv_stk);
+
+ if (filename_terminator == 0)
+ for (p = start; *p; p += strlen (p) + 1)
+ if (p[0] == '-')
+ count++;
+
+ new_argc = state->argc + count;
+ new_argv = xmalloc (sizeof (state->argv[0]) * (new_argc + 1));
+ memcpy (new_argv, state->argv, sizeof (state->argv[0]) * (state->argc + 1));
+ state->argv = new_argv;
+ memmove (&state->argv[state->next + count], &state->argv[state->next],
+ (state->argc - state->next + 1) * sizeof (state->argv[0]));
+
+ state->argc = new_argc;
+
+ for (i = state->next, p = start; *p; p += strlen (p) + 1, i++)
+ {
+ if (filename_terminator == 0 && p[0] == '-')
+ state->argv[i++] = "--add-file";
+ state->argv[i] = p;
+ }
+}
+
+
+static void
+tar_help (struct argp_state *state)
+{
+ argp_fmtstream_t fs;
+ state->flags |= ARGP_NO_EXIT;
+ argp_state_help (state, state->out_stream,
+ ARGP_HELP_STD_HELP & ~ARGP_HELP_BUG_ADDR);
+ /* FIXME: use struct uparams.rmargin (from argp-help.c) instead of 79 */
+ fs = argp_make_fmtstream (state->out_stream, 0, 79, 0);
+
+ argp_fmtstream_printf (fs, "\n%s\n\n",
+ _("Valid arguments for --quoting-style options are:"));
+ tar_list_quoting_styles (fs, " ");
+
+ argp_fmtstream_puts (fs, _("\n*This* tar defaults to:\n"));
+ show_default_settings_fs (fs);
+ argp_fmtstream_putc (fs, '\n');
+ argp_fmtstream_printf (fs, _("Report bugs to %s.\n"),
+ argp_program_bug_address);
+ argp_fmtstream_free (fs);
+}
+
+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, MAKE_INCL_OPTIONS (args));
+ args->input_files = true;
+ break;
+
+ case 'A':
+ set_subcommand_option (CAT_SUBCOMMAND);
+ 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 'C':
+ name_add_dir (arg);
+ 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 'g':
+ 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 '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 'I':
+ USAGE_ERROR ((0, 0,
+ _("Warning: the -I option is not supported;"
+ " perhaps you meant -j or -T?")));
+ break;
+
+ case 'j':
+ set_use_compress_program_option ("bzip2");
+ break;
+
+ case 'k':
+ /* Don't replace existing files. */
+ old_files_option = KEEP_OLD_FILES;
+ break;
+
+ case 'K':
+ starting_file_option = true;
+ addname (arg, 0);
+ 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 'l':
+ check_links_option = 1;
+ break;
+
+ case 'L':
+ {
+ uintmax_t u;
+ if (xstrtoumax (arg, 0, 10, &u, "") != LONGINT_OK)
+ USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
+ _("Invalid tape length")));
+ tape_length_option = 1024 * (tarlong) u;
+ multi_volume_option = true;
+ }
+ 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);
+ set_mtime_option = true;
+ break;
+
+ case 'n':
+ seekable_archive = true;
+ break;
+
+ case 'N':
+ after_date_option = true;
+ /* Fall through. */
+
+ case NEWER_MTIME_OPTION:
+ if (NEWER_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);
+ break;
+
+ case 'o':
+ args->o_option = true;
+ break;
+
+ case 'O':
+ to_stdout_option = true;
+ break;
+
+ case 'p':
+ same_permissions_option = true;
+ break;
+
+ case 'P':
+ 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. */
+
+ same_order_option = true;
+ break;
+
+ case 'S':
+ 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 (LIST_SUBCOMMAND);
+ test_label_option = true;
+ break;
+
+ case 'T':
+ update_argv (arg, state);
+ /* 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;
+
+ case 'u':
+ set_subcommand_option (UPDATE_SUBCOMMAND);
+ break;
+
+ case 'U':
+ old_files_option = UNLINK_FIRST_OLD_FILES;
+ break;
+
+ case UTC_OPTION:
+ utc_option = true;
+ break;
+
+ case 'v':
+ verbose_option++;
+ break;
+
+ case 'V':
+ volume_label_option = arg;
+ break;
+
+ case 'w':
+ interactive_option = true;
+ break;
+
+ case 'W':
+ verify_option = true;
+ break;
+
+ case 'x':
+ set_subcommand_option (EXTRACT_SUBCOMMAND);
+ break;
+
+ case 'X':
+ if (add_exclude_file (add_exclude, excluded, arg,
+ MAKE_EXCL_OPTIONS (args), '\n')
+ != 0)
+ {
+ int e = errno;
+ FATAL_ERROR ((0, e, "%s", quotearg_colon (arg)));
+ }
+ break;
+
+ case 'z':
+ set_use_compress_program_option ("gzip");
+ break;
+
+ case 'Z':
+ set_use_compress_program_option ("compress");
+ break;
+
+ case ANCHORED_OPTION:
+ args->matching_flags |= EXCLUDE_ANCHORED;
+ 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 CHECKPOINT_OPTION:
+ if (arg)
+ {
+ char *p;
+
+ if (*arg == '.')
+ {
+ checkpoint_style = checkpoint_dot;
+ arg++;
+ }
+ checkpoint_option = strtoul (arg, &p, 0);
+ if (*p)
+ FATAL_ERROR ((0, 0,
+ _("--checkpoint value is not an integer")));
+ }
+ else
+ checkpoint_option = 10;
+ 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 EXCLUDE_OPTION:
+ add_exclude (excluded, arg, MAKE_EXCL_OPTIONS (args));
+ 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_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 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_CASE_OPTION:
+ args->matching_flags |= FNM_CASEFOLD;
+ 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_NEWER_FILES_OPTION:
+ old_files_option = KEEP_NEWER_FILES;
+ break;
+
+ case GROUP_OPTION:
+ if (! (strlen (arg) < GNAME_FIELD_SIZE
+ && gname_to_gid (arg, &group_option)))
+ {
+ uintmax_t g;
+ if (xstrtoumax (arg, 0, 10, &g, "") == LONGINT_OK
+ && g == (gid_t) g)
+ group_option = g;
+ else
+ FATAL_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
+ _("%s: Invalid group")));
+ }
+ 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_ANCHORED_OPTION:
+ args->include_anchored = 0; /* Clear the default for comman line args */
+ args->matching_flags &= ~ EXCLUDE_ANCHORED;
+ break;
+
+ case NO_IGNORE_CASE_OPTION:
+ args->matching_flags &= ~ FNM_CASEFOLD;
+ break;
+
+ case NO_IGNORE_COMMAND_ERROR_OPTION:
+ ignore_command_error_option = false;
+ break;
+
+ case NO_OVERWRITE_DIR_OPTION:
+ old_files_option = NO_OVERWRITE_DIR_OLD_FILES;
+ break;
+
+ case NO_QUOTE_CHARS_OPTION:
+ for (;*arg; arg++)
+ set_char_quoting (NULL, *arg, 0);
+ break;
+
+ case NO_WILDCARDS_OPTION:
+ args->wildcards = disable_wildcards;
+ break;
+
+ case NO_WILDCARDS_MATCH_SLASH_OPTION:
+ args->matching_flags |= FNM_FILE_NAME;
+ break;
+
+ case NULL_OPTION:
+ filename_terminator = '\0';
+ break;
+
+ case NUMERIC_OWNER_OPTION:
+ numeric_owner_option = true;
+ break;
+
+ case OCCURRENCE_OPTION:
+ 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 OVERWRITE_DIR_OPTION:
+ old_files_option = DEFAULT_OLD_FILES;
+ break;
+
+ case OVERWRITE_OPTION:
+ old_files_option = OVERWRITE_OLD_FILES;
+ break;
+
+ case OWNER_OPTION:
+ if (! (strlen (arg) < UNAME_FIELD_SIZE
+ && uname_to_uid (arg, &owner_option)))
+ {
+ uintmax_t u;
+ if (xstrtoumax (arg, 0, 10, &u, "") == LONGINT_OK
+ && u == (uid_t) u)
+ owner_option = u;
+ else
+ FATAL_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
+ _("Invalid owner")));
+ }
+ 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:
+ args->pax_option = true;
+ xheader_set_option (arg);
+ break;
+
+ case POSIX_OPTION:
+ set_archive_format ("posix");
+ break;
+
+ case PRESERVE_OPTION:
+ /* FIXME: What it is good for? */
+ same_permissions_option = true;
+ same_order_option = true;
+ break;
+
+ case RECORD_SIZE_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 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:
+ show_default_settings (stdout);
+ 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 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 USE_COMPRESS_PROGRAM_OPTION:
+ set_use_compress_program_option (arg);
+ break;
+
+ case VOLNO_FILE_OPTION:
+ volno_file_option = arg;
+ break;
+
+ case WILDCARDS_OPTION:
+ args->wildcards = enable_wildcards;
+ break;
+
+ case WILDCARDS_MATCH_SLASH_OPTION:
+ args->matching_flags &= ~ FNM_FILE_NAME;
+ break;
+
+ case NO_RECURSION_OPTION:
+ recursion_option = 0;
+ break;
+
+ case NO_SAME_OWNER_OPTION:
+ same_owner_option = -1;
+ break;
+
+ case NO_SAME_PERMISSIONS_OPTION:
+ same_permissions_option = -1;
+ break;
+
+ case RECURSION_OPTION:
+ recursion_option = FNM_LEADING_DIR;
+ break;
+
+ case SAME_OWNER_OPTION:
+ same_owner_option = 1;
+ break;
+
+ case UNQUOTE_OPTION:
+ unquote_option = true;
+ break;
+
+ case NO_UNQUOTE_OPTION:
+ unquote_option = false;
+ 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 '?':
+ tar_help (state);
+ close_stdout ();
+ exit (0);
+
+ case USAGE_OPTION:
+ argp_state_help (state, state->out_stream, ARGP_HELP_USAGE);
+ close_stdout ();
+ exit (0);
+
+ case VERSION_OPTION:
+ version_etc (state->out_stream, "tar", PACKAGE_NAME, VERSION,
+ "John Gilmore", "Jay Fenlason", (char *) NULL);
+ close_stdout ();
+ exit (0);
+
+ case HANG_OPTION:
+ _argp_hang = atoi (arg ? arg : "3600");
+ while (_argp_hang-- > 0)
+ sleep (1);
+ break;
+
+ default:
+ return ARGP_ERR_UNKNOWN;
+ }
+ return 0;
+}
+
+static struct argp argp = {
+ options,
+ parse_opt,
+ N_("[FILE]..."),
+ doc,
+ NULL,
+ NULL,
+ 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 *
+find_argp_option (struct argp_option *options, int letter)
+{
+ for (;
+ !(options->name == NULL
+ && options->key == 0
+ && options->arg == 0
+ && options->flags == 0
+ && options->doc == NULL); options++)
+ if (options->key == letter)
+ return options;
+ return NULL;
+}
+
+static void
+decode_options (int argc, char **argv)
+{
+ int index;
+ struct tar_args args;
+
+ /* Set some default option values. */
+ args.textual_date = NULL;
+ args.wildcards = default_wildcards;
+ args.matching_flags = 0;
+ args.include_anchored = EXCLUDE_ANCHORED;
+ 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;
+
+ subcommand_option = UNKNOWN_SUBCOMMAND;
+ archive_format = DEFAULT_FORMAT;
+ blocking_factor = DEFAULT_BLOCKING;
+ record_size = DEFAULT_BLOCKING * BLOCKSIZE;
+ excluded = new_exclude ();
+ newer_mtime_option.tv_sec = TYPE_MINIMUM (time_t);
+ newer_mtime_option.tv_nsec = -1;
+ recursion_option = FNM_LEADING_DIR;
+ unquote_option = true;
+ tar_sparse_major = 1;
+ tar_sparse_minor = 0;
+
+ owner_option = -1;
+ group_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 *opt;
+
+ buffer[1] = *letter;
+ *out++ = xstrdup (buffer);
+ opt = find_argp_option (options, *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. */
+
+ prepend_default_options (getenv ("TAR_OPTIONS"), &argc, &argv);
+
+ if (argp_parse (&argp, argc, argv, ARGP_IN_ORDER|ARGP_NO_HELP,
+ &index, &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 (; index < argc; index++)
+ {
+ name_add_name (argv[index], MAKE_INCL_OPTIONS (&args));
+ args.input_files = true;
+ }
+
+ /* Warn about implicit use of the wildcards in command line arguments.
+ See TODO */
+ warn_regex_usage = args.wildcards == default_wildcards;
+
+ /* 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 (subcommand_option != DELETE_SUBCOMMAND
+ && subcommand_option != DIFF_SUBCOMMAND
+ && subcommand_option != EXTRACT_SUBCOMMAND
+ && subcommand_option != LIST_SUBCOMMAND)
+ USAGE_ERROR ((0, 0,
+ _("--occurrence cannot be used in the requested operation mode")));
+ }
+
+ if (seekable_archive && 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 (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
+ && NEWER_OPTION_INITIALIZED (newer_mtime_option))
+ USAGE_ERROR ((0, 0,
+ _("Cannot combine --listed-incremental with --newer")));
+
+ 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 (use_compress_program_option)
+ {
+ if (multi_volume_option)
+ USAGE_ERROR ((0, 0, _("Cannot use multi-volume compressed archives")));
+ if (subcommand_option == UPDATE_SUBCOMMAND
+ || subcommand_option == APPEND_SUBCOMMAND
+ || subcommand_option == DELETE_SUBCOMMAND)
+ USAGE_ERROR ((0, 0, _("Cannot update compressed archives")));
+ if (subcommand_option == CAT_SUBCOMMAND)
+ USAGE_ERROR ((0, 0, _("Cannot concatenate compressed archives")));
+ }
+
+ /* 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
+ && (subcommand_option != EXTRACT_SUBCOMMAND
+ || subcommand_option != DIFF_SUBCOMMAND
+ || subcommand_option != LIST_SUBCOMMAND))
+ USAGE_ERROR ((0, 0, _("--pax-option can be used only on POSIX archives")));
+
+ /* If ready to unlink hierarchies, so we are for simpler files. */
+ if (recursive_unlink_option)
+ old_files_option = UNLINK_FIRST_OLD_FILES;
+
+
+ if (test_label_option)
+ {
+ /* --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;
+
+ /* 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")));
+ break;
+
+ case EXTRACT_SUBCOMMAND:
+ case LIST_SUBCOMMAND:
+ case DIFF_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_error (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;
+ }
+
+ if (verbose_option)
+ report_textual_dates (&args);
+}
+
+
+/* Tar proper. */
+
+/* Main routine for tar. */
+int
+main (int argc, char **argv)
+{
+ set_start_time ();
+ program_name = argv[0];
+
+ setlocale (LC_ALL, "");
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+
+ exit_failure = TAREXIT_FAILURE;
+ exit_status = TAREXIT_SUCCESS;
+ filename_terminator = '\n';
+ 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;
+
+ obstack_init (&argv_stk);
+
+#ifdef SIGCHLD
+ /* System V fork+wait does not work if SIGCHLD is ignored. */
+ signal (SIGCHLD, SIG_DFL);
+#endif
+
+ /* 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' 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;
+ }
+
+ 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);
+ name_term ();
+
+ if (exit_status == TAREXIT_FAILURE)
+ error (0, 0, _("Error exit delayed from previous errors"));
+
+ if (stdlis == stdout)
+ close_stdout ();
+ else if (ferror (stderr) || fclose (stderr) != 0)
+ exit_status = TAREXIT_FAILURE;
+
+ return exit_status;
+}
+
+void
+tar_stat_init (struct tar_stat_info *st)
+{
+ memset (st, 0, sizeof (*st));
+}
+
+void
+tar_stat_destroy (struct tar_stat_info *st)
+{
+ free (st->orig_file_name);
+ free (st->file_name);
+ free (st->link_name);
+ free (st->uname);
+ free (st->gname);
+ free (st->sparse_map);
+ free (st->dumpdir);
+ xheader_destroy (&st->xhdr);
+ 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);
+}
diff --git a/src/tar.h b/src/tar.h
new file mode 100644
index 0000000..439273e
--- /dev/null
+++ b/src/tar.h
@@ -0,0 +1,331 @@
+/* GNU tar Archive Format description.
+
+ Copyright (C) 1988, 1989, 1991, 1992, 1993, 1994, 1995, 1996, 1997,
+ 2000, 2001, 2003, 2004, 2005, 2006 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 2, 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+/* 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;
+ size_t numbytes;
+};
+
+struct xheader
+{
+ struct obstack *stk;
+ size_t size;
+ char *buffer;
+ uintmax_t string_length;
+};
+
+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 */
+ 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;
+
+ /* 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 */
+};
+
+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..a74146b
--- /dev/null
+++ b/src/transform.c
@@ -0,0 +1,527 @@
+/* This file is part of GNU tar.
+ Copyright (C) 2006 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 2, 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#include <system.h>
+#include <regex.h>
+#include "common.h"
+
+static enum transform_type
+ {
+ transform_none,
+ transform_first,
+ transform_global
+ }
+transform_type = transform_none;
+static unsigned match_number = 0;
+static regex_t regex;
+static struct obstack stk;
+
+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;
+};
+
+/* Compiled replacement expression */
+static struct replace_segm *repl_head, *repl_tail;
+static size_t segm_count; /* Number of elements in the above list */
+
+static struct replace_segm *
+add_segment (void)
+{
+ struct replace_segm *segm = xmalloc (sizeof *segm);
+ segm->next = NULL;
+ if (repl_tail)
+ repl_tail->next = segm;
+ else
+ repl_head = segm;
+ repl_tail = segm;
+ segm_count++;
+ return segm;
+}
+
+static void
+add_literal_segment (char *str, char *end)
+{
+ size_t len = end - str;
+ if (len)
+ {
+ struct replace_segm *segm = add_segment ();
+ 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 (int chr)
+{
+ struct replace_segm *segm = add_segment ();
+ 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 (size_t ref)
+{
+ struct replace_segm *segm = add_segment ();
+ segm->type = segm_backref;
+ segm->v.ref = ref;
+}
+
+static void
+add_case_ctl_segment (enum case_ctl_type ctl)
+{
+ struct replace_segm *segm = add_segment ();
+ segm->type = segm_case_ctl;
+ segm->v.ctl = ctl;
+}
+
+void
+set_transform_expr (const char *expr)
+{
+ int delim;
+ int i, j, rc;
+ char *str, *beg, *cur;
+ const char *p;
+ int cflags = 0;
+
+ if (transform_type == transform_none)
+ obstack_init (&stk);
+ else
+ {
+ /* Redefinition of the transform expression */
+ regfree (&regex);
+ }
+
+ if (expr[0] != 's')
+ 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 */
+ transform_type = transform_first;
+ for (p = expr + j + 1; *p; p++)
+ switch (*p)
+ {
+ case 'g':
+ 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':
+ match_number = strtoul (p, (char**) &p, 0);
+ p--;
+ break;
+
+ default:
+ USAGE_ERROR ((0, 0, _("Unknown flag in transform expression")));
+ }
+
+ /* Extract and compile regex */
+ str = xmalloc (i - 1);
+ memcpy (str, expr + 2, i - 2);
+ str[i - 2] = 0;
+
+ rc = regcomp (&regex, str, cflags);
+
+ if (rc)
+ {
+ char errbuf[512];
+ regerror (rc, &regex, errbuf, sizeof (errbuf));
+ USAGE_ERROR ((0, 0, _("Invalid transform expression: %s"), errbuf));
+ }
+
+ if (str[0] == '^' || str[strlen (str) - 1] == '$')
+ 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 (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 > regex.re_nsub)
+ USAGE_ERROR ((0, 0, _("Invalid transform replacement: back reference out of range")));
+ add_backref_segment (n);
+ break;
+
+ case '\\':
+ add_char_segment ('\\');
+ cur++;
+ break;
+
+ case 'a':
+ add_char_segment ('\a');
+ cur++;
+ break;
+
+ case 'b':
+ add_char_segment ('\b');
+ cur++;
+ break;
+
+ case 'f':
+ add_char_segment ('\f');
+ cur++;
+ break;
+
+ case 'n':
+ add_char_segment ('\n');
+ cur++;
+ break;
+
+ case 'r':
+ add_char_segment ('\r');
+ cur++;
+ break;
+
+ case 't':
+ add_char_segment ('\t');
+ cur++;
+ break;
+
+ case 'v':
+ add_char_segment ('\v');
+ cur++;
+ break;
+
+ case '&':
+ add_char_segment ('&');
+ cur++;
+ break;
+
+ case 'L':
+ /* Turn the replacement to lowercase until a `\U' or `\E'
+ is found, */
+ add_case_ctl_segment (ctl_locase);
+ cur++;
+ break;
+
+ case 'l':
+ /* Turn the next character to lowercase, */
+ add_case_ctl_segment (ctl_locase_next);
+ cur++;
+ break;
+
+ case 'U':
+ /* Turn the replacement to uppercase until a `\L' or `\E'
+ is found, */
+ add_case_ctl_segment (ctl_upcase);
+ cur++;
+ break;
+
+ case 'u':
+ /* Turn the next character to uppercase, */
+ add_case_ctl_segment (ctl_upcase_next);
+ cur++;
+ break;
+
+ case 'E':
+ /* Stop case conversion started by `\L' or `\U'. */
+ add_case_ctl_segment (ctl_stop);
+ cur++;
+ break;
+
+ default:
+ /* Try to be nice */
+ {
+ char buf[2];
+ buf[0] = '\\';
+ buf[1] = *cur;
+ add_literal_segment (buf, buf + 2);
+ }
+ cur++;
+ break;
+ }
+ beg = cur;
+ }
+ else if (*cur == '&')
+ {
+ add_literal_segment (beg, cur);
+ add_backref_segment (0);
+ beg = ++cur;
+ }
+ else
+ cur++;
+ }
+ add_literal_segment (beg, cur);
+
+}
+
+/* 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 (case_ctl_buffer[0]);
+ break;
+
+ case ctl_locase_next:
+ case_ctl_buffer[0] = tolower (case_ctl_buffer[0]);
+ break;
+
+ case ctl_upcase:
+ for (p = case_ctl_buffer; p < case_ctl_buffer + size; p++)
+ *p = toupper (*p);
+ break;
+
+ case ctl_locase:
+ for (p = case_ctl_buffer; p < case_ctl_buffer + size; p++)
+ *p = tolower (*p);
+ break;
+
+ case ctl_stop:
+ break;
+ }
+ return case_ctl_buffer;
+}
+
+bool
+_transform_name_to_obstack (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; \
+ }
+
+ if (transform_type == transform_none)
+ return false;
+
+ rmp = xmalloc ((regex.re_nsub + 1) * sizeof (*rmp));
+
+ while (*input)
+ {
+ size_t disp;
+ char *ptr;
+
+ rc = regexec (&regex, input, 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 (match_number && nmatches < match_number)
+ {
+ obstack_grow (&stk, input, disp);
+ input += disp;
+ continue;
+ }
+
+ for (segm = 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 (transform_type == transform_first)
+ {
+ obstack_grow (&stk, input, strlen (input));
+ break;
+ }
+ }
+
+ obstack_1grow (&stk, 0);
+ free (rmp);
+ return true;
+}
+
+bool
+transform_name_fp (char **pinput, char *(*fun)(char *, void *), void *dat)
+{
+ char *str;
+ bool ret = _transform_name_to_obstack (*pinput);
+ if (ret)
+ {
+ str = obstack_finish (&stk);
+ assign_string (pinput, fun ? fun (str, dat) : str);
+ obstack_free (&stk, str);
+ }
+ else if (fun)
+ {
+ str = *pinput;
+ *pinput = NULL;
+ assign_string (pinput, fun (str, dat));
+ free (str);
+ ret = true;
+ }
+ return ret;
+}
+
+bool
+transform_name (char **pinput)
+{
+ return transform_name_fp (pinput, NULL, NULL);
+}
+
diff --git a/src/update.c b/src/update.c
new file mode 100644
index 0000000..fa18c1d
--- /dev/null
+++ b/src/update.c
@@ -0,0 +1,210 @@
+/* Update a tar archive.
+
+ Copyright (C) 1988, 1992, 1994, 1996, 1997, 1999, 2000, 2001, 2003,
+ 2004, 2005 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 2, 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+/* 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 = open (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 (false);
+
+ switch (status)
+ {
+ case HEADER_STILL_UNREAD:
+ case HEADER_SUCCESS_EXTENDED:
+ abort ();
+
+ case HEADER_SUCCESS:
+ {
+ struct name *name;
+
+ decode_header (current_header, &current_stat_info,
+ &current_format, 0);
+ 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 (dereference_option,
+ current_stat_info.file_name, &s) == 0
+ && (tar_timespec_cmp (get_stat_mtime (&s),
+ current_stat_info.mtime)
+ <= 0))
+ add_avoided_name (current_stat_info.file_name);
+ }
+
+ skip_member ();
+ break;
+ }
+
+ case HEADER_ZERO_BLOCK:
+ current_block = current_header;
+ found_end = true;
+ break;
+
+ case HEADER_END_OF_FILE:
+ found_end = true;
+ break;
+
+ case HEADER_FAILURE:
+ set_next_block_after (current_header);
+ switch (previous_status)
+ {
+ case HEADER_STILL_UNREAD:
+ WARN ((0, 0, _("This does not look like a tar archive")));
+ /* Fall through. */
+
+ case HEADER_SUCCESS:
+ case HEADER_ZERO_BLOCK:
+ ERROR ((0, 0, _("Skipping to next header")));
+ /* Fall through. */
+
+ case HEADER_FAILURE:
+ break;
+
+ case HEADER_END_OF_FILE:
+ case HEADER_SUCCESS_EXTENDED:
+ abort ();
+ }
+ break;
+ }
+
+ tar_stat_destroy (&current_stat_info);
+ previous_status = status;
+ }
+
+ reset_eof ();
+ time_to_start_writing = true;
+ output_start = current_block->buffer;
+
+ {
+ char *file_name;
+
+ while ((file_name = name_from_list ()) != NULL)
+ {
+ if (excluded_name (file_name))
+ continue;
+ if (interactive_option && !confirm ("add", file_name))
+ continue;
+ if (subcommand_option == CAT_SUBCOMMAND)
+ append_file (file_name);
+ else
+ dump_file (file_name, 1, (dev_t) 0);
+ }
+ }
+
+ write_eot ();
+ close_archive ();
+ names_notfound ();
+}
diff --git a/src/utf8.c b/src/utf8.c
new file mode 100644
index 0000000..75b7c1d
--- /dev/null
+++ b/src/utf8.c
@@ -0,0 +1,97 @@
+/* Charset handling for GNU tar.
+
+ Copyright (C) 2004, 2006 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 2, 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#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 (! (0 <= *p && *p <= 127))
+ return false;
+ return true;
+}
diff --git a/src/xheader.c b/src/xheader.c
new file mode 100644
index 0000000..aed2eb0
--- /dev/null
+++ b/src/xheader.c
@@ -0,0 +1,1516 @@
+/* POSIX extended headers for tar.
+
+ Copyright (C) 2003, 2004, 2005, 2006, 2007 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 2, 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#include <system.h>
+
+#include <fnmatch.h>
+#include <hash.h>
+#include <inttostr.h>
+#include <quotearg.h>
+
+#include "common.h"
+
+#include <fnmatch.h>
+
+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;
+
+/* Template for the name field of a 'g' type header */
+static char *globexthdr_name;
+
+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
+xheader_set_keyword_equal (char *kw, char *eq)
+{
+ bool global = true;
+ char *p = eq;
+
+ if (eq[-1] == ':')
+ {
+ p--;
+ global = false;
+ }
+
+ while (p > kw && isspace (*p))
+ p--;
+
+ *p = 0;
+
+ for (p = eq + 1; *p && isspace (*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 (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;
+ 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':
+ if (nptr)
+ {
+ q = stpcpy (q, nptr);
+ p += 2;
+ break;
+ }
+ /* else fall through */
+
+ 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, struct xheader *xhdr)
+{
+ union block *header;
+ size_t size;
+ char *p;
+
+ size = xhdr->size;
+ header = start_private_header (name, size);
+ 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)
+{
+ char *name;
+ struct keyword_list *kp;
+
+ if (!keyword_global_override_list)
+ return;
+
+ xheader_init (xhdr);
+ for (kp = keyword_global_override_list; kp; kp = kp->next)
+ code_string (kp->value, kp->pattern, xhdr);
+ xheader_finish (xhdr);
+ xheader_write (XGLTYPE, name = xheader_ghdr_name (), xhdr);
+ free (name);
+}
+
+
+/* General Interface */
+
+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);
+ bool protect;
+};
+
+/* 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 (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->protect && 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->protect && 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;
+ uintmax_t u;
+ 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;
+ }
+
+ errno = 0;
+ len = u = strtoumax (p, &len_lim, 10);
+ if (len != u || errno == ERANGE)
+ {
+ ERROR ((0, 0, _("Extended header length is out of allowed range")));
+ return false;
+ }
+
+ 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
+ WARN((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);
+}
+
+static void
+decg (void *data, char const *keyword, char const *value,
+ size_t size __attribute__((unused)))
+{
+ struct keyword_list **kwl = data;
+ 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;
+ }
+}
+
+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)
+ || xheader_keyword_override_p (keyword))
+ return;
+ xheader_init (&st->xhdr);
+ t->coder (st, keyword, &st->xhdr, data);
+}
+
+void
+xheader_read (struct xheader *xhdr, union block *p, size_t size)
+{
+ size_t j = 0;
+
+ xheader_init (xhdr);
+ size += BLOCKSIZE;
+ xhdr->size = size;
+ xhdr->buffer = xmalloc (size + 1);
+ xhdr->buffer[size] = '\0';
+
+ do
+ {
+ size_t len = size;
+
+ if (len > BLOCKSIZE)
+ len = BLOCKSIZE;
+
+ memcpy (&xhdr->buffer[j], p->buffer, len);
+ set_next_block_after (p);
+
+ p = find_next_block ();
+
+ j += len;
+ size -= len;
+ }
+ while (size > 0);
+}
+
+static void
+xheader_print_n (struct xheader *xhdr, char const *keyword,
+ char const *value, size_t vsize)
+{
+ size_t len = strlen (keyword) + vsize + 3; /* ' ' + '=' + '\n' */
+ size_t p;
+ size_t n = 0;
+ char nbuf[UINTMAX_STRSIZE_BOUND];
+ char const *np;
+
+ 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, strlen (keyword));
+ 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 = 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,
+ uintmax_t minus_minval, uintmax_t maxval)
+{
+ char minval_buf[UINTMAX_STRSIZE_BOUND + 1];
+ char maxval_buf[UINTMAX_STRSIZE_BOUND];
+ char *minval_string = umaxtostr (minus_minval, minval_buf + 1);
+ char *maxval_string = umaxtostr (maxval, maxval_buf);
+ if (minus_minval)
+ *--minval_string = '-';
+
+ /* 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));
+}
+
+enum decode_time_status
+ {
+ decode_time_success,
+ decode_time_range,
+ decode_time_bad_header
+ };
+
+static enum decode_time_status
+_decode_time (struct timespec *ts, char const *arg, char const *keyword)
+{
+ time_t s;
+ unsigned long int ns = 0;
+ char *p;
+ char *arg_lim;
+ bool negative = *arg == '-';
+
+ errno = 0;
+
+ if (ISDIGIT (arg[negative]))
+ {
+ if (negative)
+ {
+ intmax_t i = strtoimax (arg, &arg_lim, 10);
+ if (TYPE_SIGNED (time_t) ? i < TYPE_MINIMUM (time_t) : i < 0)
+ return decode_time_range;
+ s = i;
+ }
+ else
+ {
+ uintmax_t i = strtoumax (arg, &arg_lim, 10);
+ if (TYPE_MAXIMUM (time_t) < i)
+ return decode_time_range;
+ s = i;
+ }
+
+ p = arg_lim;
+
+ if (errno == ERANGE)
+ return decode_time_range;
+
+ if (*p == '.')
+ {
+ int digits = 0;
+ bool trailing_nonzero = false;
+
+ while (ISDIGIT (*++p))
+ if (digits < LOG10_BILLION)
+ {
+ ns = 10 * ns + (*p - '0');
+ digits++;
+ }
+ else
+ trailing_nonzero |= *p != '0';
+
+ while (digits++ < LOG10_BILLION)
+ 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))
+ return decode_time_range;
+ s--;
+ ns = BILLION - ns;
+ }
+ }
+ }
+
+ if (! *p)
+ {
+ ts->tv_sec = s;
+ ts->tv_nsec = ns;
+ return decode_time_success;
+ }
+ }
+
+ return decode_time_bad_header;
+}
+
+static bool
+decode_time (struct timespec *ts, char const *arg, char const *keyword)
+{
+ switch (_decode_time (ts, arg, keyword))
+ {
+ case decode_time_success:
+ return true;
+ case decode_time_bad_header:
+ ERROR ((0, 0, _("Malformed extended header: invalid %s=%s"),
+ keyword, arg));
+ return false;
+ case decode_time_range:
+ out_of_range_header (keyword, arg, - (uintmax_t) TYPE_MINIMUM (time_t),
+ TYPE_MAXIMUM (time_t));
+ return false;
+ }
+ return true;
+}
+
+
+
+static void
+code_num (uintmax_t value, char const *keyword, struct xheader *xhdr)
+{
+ char sbuf[UINTMAX_STRSIZE_BOUND];
+ xheader_print (xhdr, keyword, umaxtostr (value, sbuf));
+}
+
+static bool
+decode_num (uintmax_t *num, char const *arg, uintmax_t maxval,
+ char const *keyword)
+{
+ uintmax_t u;
+ char *arg_lim;
+
+ if (! (ISDIGIT (*arg)
+ && (errno = 0, u = strtoumax (arg, &arg_lim, 10), !*arg_lim)))
+ {
+ ERROR ((0, 0, _("Malformed extended header: invalid %s=%s"),
+ keyword, arg));
+ return false;
+ }
+
+ if (! (u <= maxval && errno != ERANGE))
+ {
+ out_of_range_header (keyword, arg, 0, maxval);
+ return false;
+ }
+
+ *num = u;
+ 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_num (st->stat.st_gid, keyword, xhdr);
+}
+
+static void
+gid_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 (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_num (st->stat.st_uid, keyword, xhdr);
+}
+
+static void
+uid_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 (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->stat.st_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, SIZE_MAX, 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;
+
+ st->sparse_map_avail = 0;
+ while (1)
+ {
+ uintmax_t u;
+ char *delim;
+ struct sp_array e;
+
+ if (!ISDIGIT (*arg))
+ {
+ ERROR ((0, 0, _("Malformed extended header: invalid %s=%s"),
+ keyword, arg));
+ return;
+ }
+
+ errno = 0;
+ u = strtoumax (arg, &delim, 10);
+ if (offset)
+ {
+ e.offset = u;
+ if (!(u == e.offset && errno != ERANGE))
+ {
+ out_of_range_header (keyword, arg, 0, TYPE_MAXIMUM (off_t));
+ return;
+ }
+ }
+ else
+ {
+ e.numbytes = u;
+ if (!(u == e.numbytes && errno != ERANGE))
+ {
+ out_of_range_header (keyword, arg, 0, TYPE_MAXIMUM (size_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
+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, false },
+ { "comment", dummy_coder, dummy_decoder, false },
+ { "charset", dummy_coder, dummy_decoder, false },
+ { "ctime", ctime_coder, ctime_decoder, false },
+ { "gid", gid_coder, gid_decoder, false },
+ { "gname", gname_coder, gname_decoder, false },
+ { "linkpath", linkpath_coder, linkpath_decoder, false },
+ { "mtime", mtime_coder, mtime_decoder, false },
+ { "path", path_coder, path_decoder, false },
+ { "size", size_coder, size_decoder, false },
+ { "uid", uid_coder, uid_decoder, false },
+ { "uname", uname_coder, uname_decoder, false },
+
+ /* Sparse file handling */
+ { "GNU.sparse.name", path_coder, path_decoder,
+ true },
+ { "GNU.sparse.major", sparse_major_coder, sparse_major_decoder,
+ true },
+ { "GNU.sparse.minor", sparse_minor_coder, sparse_minor_decoder,
+ true },
+ { "GNU.sparse.realsize", sparse_size_coder, sparse_size_decoder,
+ true },
+ { "GNU.sparse.numblocks", sparse_numblocks_coder, sparse_numblocks_decoder,
+ true },
+
+ /* tar 1.14 - 1.15.90 keywords. */
+ { "GNU.sparse.size", sparse_size_coder, sparse_size_decoder, true },
+ /* 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,
+ true },
+ { "GNU.sparse.numbytes", sparse_numbytes_coder, sparse_numbytes_decoder,
+ true },
+ /* tar 1.15.90 keyword, introduced to remove the above-mentioned conflict. */
+ { "GNU.sparse.map", NULL /* Unused, see pax_dump_header() */,
+ sparse_map_decoder, false },
+
+ { "GNU.dumpdir", dumpdir_coder, dumpdir_decoder,
+ true },
+
+ /* 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, true },
+
+ /* 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,
+ true },
+ { "GNU.volume.size", volume_size_coder, volume_size_decoder, true },
+ { "GNU.volume.offset", volume_offset_coder, volume_offset_decoder, true },
+
+ { NULL, NULL, NULL, false }
+};