diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 46 | ||||
-rw-r--r-- | src/Makefile.in | 776 | ||||
-rw-r--r-- | src/arith.h | 27 | ||||
-rw-r--r-- | src/buffer.c | 1696 | ||||
-rw-r--r-- | src/common.h | 738 | ||||
-rw-r--r-- | src/compare.c | 609 | ||||
-rw-r--r-- | src/create.c | 1785 | ||||
-rw-r--r-- | src/delete.c | 391 | ||||
-rw-r--r-- | src/extract.c | 1379 | ||||
-rw-r--r-- | src/incremen.c | 1473 | ||||
-rw-r--r-- | src/list.c | 1341 | ||||
-rw-r--r-- | src/misc.c | 748 | ||||
-rw-r--r-- | src/names.c | 1022 | ||||
-rw-r--r-- | src/sparse.c | 1175 | ||||
-rw-r--r-- | src/system.c | 844 | ||||
-rw-r--r-- | src/tar.c | 2427 | ||||
-rw-r--r-- | src/tar.h | 331 | ||||
-rw-r--r-- | src/transform.c | 527 | ||||
-rw-r--r-- | src/update.c | 210 | ||||
-rw-r--r-- | src/utf8.c | 97 | ||||
-rw-r--r-- | src/xheader.c | 1516 |
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 (¤t_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 (¤t_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 (¤t_stat_info.file_name, + label->header.name); + current_stat_info.had_trailing_slash = + strip_trailing_slashes (current_stat_info.file_name); + + label->header.typeflag = GNUTYPE_VOLHDR; + TIME_TO_CHARS (start_time.tv_sec, label->header.mtime); + finish_header (¤t_stat_info, label, -1); + set_next_block_after (label); + } +} + +#define VOL_SUFFIX "Volume" + +/* Add a volume label to a part of multi-volume archive */ +static void +add_volume_label (void) +{ + char buf[UINTMAX_STRSIZE_BOUND]; + char *p = STRINGIFY_BIGINT (volno, buf); + char *s = xmalloc (strlen (volume_label_option) + sizeof VOL_SUFFIX + + strlen (p) + 2); + sprintf (s, "%s %s %s", volume_label_option, VOL_SUFFIX, p); + _write_volume_label (s); + free (s); +} + +static void +add_chunk_header () +{ + 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 (¤t_stat_info, block, -1); + verbose_option = tmp; + set_next_block_after (block); +} + +/* Add a multi volume header to the current archive. The exact header format + depends on the archive format. */ +static void +add_multi_volume_header (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 (¤t_stat_info, NULL); + } + else + { + report_difference (¤t_stat_info, + ngettext ("Could only read %lu of %lu byte", + "Could only read %lu of %lu bytes", + bytes), + (unsigned long) status, (unsigned long) bytes); + } + return 0; + } + + if (memcmp (buffer, diff_buffer, bytes)) + { + report_difference (¤t_stat_info, _("Contents differ")); + return 0; + } + + return 1; +} + +/* Some other routine wants SIZE bytes in the archive. For each chunk + of the archive, call PROCESSOR with the size of the chunk, and the + address of the chunk it can work with. The PROCESSOR should return + nonzero for success. Once it returns error, continue skipping + without calling PROCESSOR anymore. */ + +static void +read_and_process (struct tar_stat_info *st, int (*processor) (size_t, char *)) +{ + union block *data_block; + size_t data_size; + off_t size = st->stat.st_size; + + mv_begin (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 (¤t_stat_info, NULL); + return 0; + } + + return 1; +} + + +static void +diff_dir (void) +{ + struct stat stat_data; + + if (!get_stat_data (current_stat_info.file_name, &stat_data)) + return; + + if (!S_ISDIR (stat_data.st_mode)) + report_difference (¤t_stat_info, _("File type differs")); + else if ((current_stat_info.stat.st_mode & MODE_ALL) != + (stat_data.st_mode & MODE_ALL)) + report_difference (¤t_stat_info, _("Mode differs")); +} + +static void +diff_file (void) +{ + char const *file_name = current_stat_info.file_name; + struct stat stat_data; + + if (!get_stat_data (file_name, &stat_data)) + skip_member (); + else if (!S_ISREG (stat_data.st_mode)) + { + report_difference (¤t_stat_info, _("File type differs")); + skip_member (); + } + else + { + if ((current_stat_info.stat.st_mode & MODE_ALL) != + (stat_data.st_mode & MODE_ALL)) + report_difference (¤t_stat_info, _("Mode differs")); + + if (!sys_compare_uid (&stat_data, ¤t_stat_info.stat)) + report_difference (¤t_stat_info, _("Uid differs")); + if (!sys_compare_gid (&stat_data, ¤t_stat_info.stat)) + report_difference (¤t_stat_info, _("Gid differs")); + + if (tar_timespec_cmp (get_stat_mtime (&stat_data), + current_stat_info.mtime)) + report_difference (¤t_stat_info, _("Mod time differs")); + if (current_header->header.typeflag != GNUTYPE_SPARSE + && stat_data.st_size != current_stat_info.stat.st_size) + { + report_difference (¤t_stat_info, _("Size differs")); + skip_member (); + } + else + { + 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 (¤t_stat_info, NULL); + } + else + { + int status; + + if (current_stat_info.is_sparse) + sparse_diff_file (diff_handle, ¤t_stat_info); + else + read_and_process (¤t_stat_info, process_rawdata); + + if (atime_preserve_option == replace_atime_preserve) + { + 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 (¤t_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 (¤t_stat_info, NULL); + } + else if (status != len + || strncmp (current_stat_info.link_name, linkbuf, len) != 0) + report_difference (¤t_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 (¤t_stat_info, _("File type differs")); + return; + } + + if ((current_header->header.typeflag == CHRTYPE + || current_header->header.typeflag == BLKTYPE) + && current_stat_info.stat.st_rdev != stat_data.st_rdev) + { + report_difference (¤t_stat_info, _("Device number differs")); + return; + } + + if ((current_stat_info.stat.st_mode & MODE_ALL) != + (stat_data.st_mode & MODE_ALL)) + report_difference (¤t_stat_info, _("Mode differs")); +} + +static int +dumpdir_cmp (const char *a, const char *b) +{ + size_t len; + + while (*a) + switch (*a) + { + case 'Y': + case 'N': + if (!strchr ("YN", *b)) + return 1; + if (strcmp(a + 1, b + 1)) + return 1; + len = strlen (a) + 1; + a += len; + b += len; + break; + + case 'D': + if (strcmp(a, b)) + return 1; + len = strlen (a) + 1; + a += len; + b += len; + break; + + case 'R': + case 'T': + case 'X': + return *b; + } + return *b; +} + +static void +diff_dumpdir (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 (¤t_stat_info, _("Contents differ")); + } + else + read_and_process (¤t_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 (¤t_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 (¤t_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 (¤t_stat_info, NULL); + skip_member (); + return; + } + + if (lseek (fd, offset, SEEK_SET) < 0) + { + seek_error_details (current_stat_info.file_name, offset); + report_difference (¤t_stat_info, NULL); + return; + } + + read_and_process (¤t_stat_info, process_rawdata); + + status = close (fd); + if (status != 0) + close_error (current_stat_info.file_name); +} + +/* Diff a file against the archive. */ +void +diff_archive (void) +{ + + set_next_block_after (current_header); + decode_header (current_header, ¤t_stat_info, ¤t_format, 1); + + /* Print the block from current_header and current_stat_info. */ + + if (verbose_option) + { + if (now_verifying) + fprintf (stdlis, _("Verify ")); + print_header (¤t_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 (¤t_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 (¤t_stat_info); + } + + access_mode = ACCESS_WRITE; + now_verifying = 0; +} diff --git a/src/create.c b/src/create.c new file mode 100644 index 0000000..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 (¤t_stat_info); + + if (status == HEADER_ZERO_BLOCK && ignore_zeros_option) + { + set_next_block_after (current_header); + continue; + } + if (status == HEADER_END_OF_FILE || status == HEADER_ZERO_BLOCK) + { + logical_status = HEADER_END_OF_FILE; + break; + } + + if (status == HEADER_FAILURE) + { + ERROR ((0, 0, _("Deleting non-header from archive"))); + set_next_block_after (current_header); + continue; + } + + /* Found another header. */ + + if ((name = name_scan (current_stat_info.file_name)) != NULL) + { + name->found_count++; + if (ISFOUND(name)) + { + flush_file: + set_next_block_after (current_header); + blocks_to_skip = (current_stat_info.stat.st_size + + BLOCKSIZE - 1) / BLOCKSIZE; + + while (record_end - current_block <= blocks_to_skip) + { + blocks_to_skip -= (record_end - current_block); + flush_archive (); + } + current_block += blocks_to_skip; + blocks_to_skip = 0; + continue; + } + } + /* Copy header. */ + + if (current_stat_info.xhdr.size) + { + write_recent_bytes (current_stat_info.xhdr.buffer, + current_stat_info.xhdr.size); + } + else + { + write_recent_blocks (recent_long_name, recent_long_name_blocks); + write_recent_blocks (recent_long_link, recent_long_link_blocks); + } + new_record[new_blocks] = *current_header; + new_blocks++; + blocks_to_keep + = (current_stat_info.stat.st_size + BLOCKSIZE - 1) / BLOCKSIZE; + set_next_block_after (current_header); + if (new_blocks == blocking_factor) + write_record (1); + + /* Copy data. */ + + kept_blocks_in_record = record_end - current_block; + if (kept_blocks_in_record > blocks_to_keep) + kept_blocks_in_record = blocks_to_keep; + + while (blocks_to_keep) + { + int count; + + if (current_block == record_end) + { + flush_read (); + current_block = record_start; + kept_blocks_in_record = blocking_factor; + if (kept_blocks_in_record > blocks_to_keep) + kept_blocks_in_record = blocks_to_keep; + } + count = kept_blocks_in_record; + if (blocking_factor - new_blocks < count) + count = blocking_factor - new_blocks; + + if (! count) + abort (); + + memcpy (new_record + new_blocks, current_block, count * BLOCKSIZE); + new_blocks += count; + current_block += count; + blocks_to_keep -= count; + kept_blocks_in_record -= count; + + if (new_blocks == blocking_factor) + write_record (1); + } + } + + if (logical_status == HEADER_END_OF_FILE) + { + /* Write the end of tape. FIXME: we can't use write_eot here, + as it gets confused when the input is at end of file. */ + + int total_zero_blocks = 0; + + do + { + int zero_blocks = blocking_factor - new_blocks; + memset (new_record + new_blocks, 0, BLOCKSIZE * zero_blocks); + total_zero_blocks += zero_blocks; + write_record (total_zero_blocks < 2); + } + while (total_zero_blocks < 2); + } + + if (! acting_as_filter && ! _isrmt (archive)) + { + if (sys_truncate (archive)) + truncate_warn (archive_name_array[0]); + } + } + free (new_record); + + close_archive (); + names_notfound (); +} diff --git a/src/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, + ¤t_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, ¤t_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, ¤t_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, ¤t_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', ¤t_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 (¤t_stat_info); + if (current_stat_info.is_sparse) + sparse_extract_file (fd, ¤t_stat_info, &size); + else + for (size = current_stat_info.stat.st_size; size > 0; ) + { + mv_size_left (size); + + /* Locate data, determine max length writeable, write it, + block that we have used the data, then check if the write + worked. */ + + data_block = find_next_block (); + if (! data_block) + { + ERROR ((0, 0, _("Unexpected EOF in archive"))); + break; /* FIXME: What happens, then? */ + } + + written = available_space_after (data_block); + + if (written > size) + written = size; + errno = 0; + count = 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, ¤t_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 (¤t_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 (¤t_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, ¤t_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, ¤t_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, ¤t_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, ¤t_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, ¤t_stat_info, ¤t_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 (¤t_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 (¤t_stat_info) ? + GNUTYPE_SPARSE : current_header->header.typeflag; + + if (prepare_to_extract (current_stat_info.file_name, typeflag, &fun)) + { + if (fun && (*fun) (current_stat_info.file_name, typeflag) + && backup_option) + undo_last_backup (); + } + else + skip_member (); + +} + +/* Extract the 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 (¤t_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 (¤t_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, + ¤t_stat_info, ¤t_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 (¤t_stat_info)); + + close_archive (); + names_notfound (); /* print names not found */ +} + +/* Print a header block, based on tar options. */ +void +list_archive (void) +{ + off_t block_ordinal = current_block_ordinal (); + /* Print the header block. */ + + decode_header (current_header, ¤t_stat_info, ¤t_format, 0); + if (verbose_option) + print_header (¤t_stat_info, block_ordinal); + + if (incremental_option) + { + if (verbose_option > 2) + { + if (is_dumpdir (¤t_stat_info)) + list_dumpdir (current_stat_info.dumpdir, + dumpdir_size (current_stat_info.dumpdir)); + } + } + + skip_member (); +} + +/* Check header checksum */ +/* The standard BSD tar sources create the checksum by adding up the + bytes in the header as type char. I think the type char was unsigned + on the PDP-11, but it's signed on the Next and Sun. It looks like the + sources to BSD tar were never changed to compute the checksum + correctly, so both the Sun and Next add the bytes of the header as + signed chars. This doesn't cause a problem until you get a file with + a name containing characters with the high bit set. So tar_checksum + computes two checksums -- signed and unsigned. */ + +enum read_header +tar_checksum (union block *header, bool silent) +{ + size_t i; + int unsigned_sum = 0; /* the POSIX one :-) */ + int signed_sum = 0; /* the Sun one :-( */ + int recorded_sum; + 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 = ¤t_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, ¤t_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 (¤t_stat_info); + + if (current_stat_info.is_sparse) + sparse_skip_file (¤t_stat_info); + else if (save_typeflag != DIRTYPE) + skip_file (current_stat_info.stat.st_size); + + mv_end (); + } +} 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 (®ex); + } + + 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 (®ex, str, cflags); + + if (rc) + { + char errbuf[512]; + regerror (rc, ®ex, 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 (®ex, 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, ¤t_stat_info, + ¤t_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 (¤t_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 } +}; |