]> Zhao Yanbai Git Server - minix.git/commitdiff
new command: bsdtar.
authorBen Gras <ben@minix3.org>
Tue, 13 Jul 2010 19:28:09 +0000 (19:28 +0000)
committerBen Gras <ben@minix3.org>
Tue, 13 Jul 2010 19:28:09 +0000 (19:28 +0000)
73 files changed:
commands/Makefile
commands/bsdtar/Makefile [new file with mode: 0644]
commands/bsdtar/bsdtar.1 [new file with mode: 0644]
commands/bsdtar/bsdtar.c [new file with mode: 0644]
commands/bsdtar/bsdtar.h [new file with mode: 0644]
commands/bsdtar/bsdtar_platform.h [new file with mode: 0644]
commands/bsdtar/cmdline.c [new file with mode: 0644]
commands/bsdtar/config.h [new file with mode: 0644]
commands/bsdtar/getdate.c [new file with mode: 0644]
commands/bsdtar/libarchive_fe/Makefile.inc [new file with mode: 0644]
commands/bsdtar/libarchive_fe/err.c [new file with mode: 0644]
commands/bsdtar/libarchive_fe/err.h [new file with mode: 0644]
commands/bsdtar/libarchive_fe/lafe_platform.h [new file with mode: 0644]
commands/bsdtar/libarchive_fe/line_reader.c [new file with mode: 0644]
commands/bsdtar/libarchive_fe/line_reader.h [new file with mode: 0644]
commands/bsdtar/libarchive_fe/matching.c [new file with mode: 0644]
commands/bsdtar/libarchive_fe/matching.h [new file with mode: 0644]
commands/bsdtar/libarchive_fe/pathmatch.c [new file with mode: 0644]
commands/bsdtar/libarchive_fe/pathmatch.h [new file with mode: 0644]
commands/bsdtar/read.c [new file with mode: 0644]
commands/bsdtar/subst.c [new file with mode: 0644]
commands/bsdtar/test/.deps/bsdtar_test-main.Po [new file with mode: 0644]
commands/bsdtar/test/.deps/bsdtar_test-test_0.Po [new file with mode: 0644]
commands/bsdtar/test/.deps/bsdtar_test-test_basic.Po [new file with mode: 0644]
commands/bsdtar/test/.deps/bsdtar_test-test_copy.Po [new file with mode: 0644]
commands/bsdtar/test/.deps/bsdtar_test-test_empty_mtree.Po [new file with mode: 0644]
commands/bsdtar/test/.deps/bsdtar_test-test_getdate.Po [new file with mode: 0644]
commands/bsdtar/test/.deps/bsdtar_test-test_help.Po [new file with mode: 0644]
commands/bsdtar/test/.deps/bsdtar_test-test_option_T_upper.Po [new file with mode: 0644]
commands/bsdtar/test/.deps/bsdtar_test-test_option_q.Po [new file with mode: 0644]
commands/bsdtar/test/.deps/bsdtar_test-test_option_r.Po [new file with mode: 0644]
commands/bsdtar/test/.deps/bsdtar_test-test_option_s.Po [new file with mode: 0644]
commands/bsdtar/test/.deps/bsdtar_test-test_patterns.Po [new file with mode: 0644]
commands/bsdtar/test/.deps/bsdtar_test-test_stdio.Po [new file with mode: 0644]
commands/bsdtar/test/.deps/bsdtar_test-test_strip_components.Po [new file with mode: 0644]
commands/bsdtar/test/.deps/bsdtar_test-test_symlink_dir.Po [new file with mode: 0644]
commands/bsdtar/test/.deps/bsdtar_test-test_version.Po [new file with mode: 0644]
commands/bsdtar/test/.deps/bsdtar_test-test_windows.Po [new file with mode: 0644]
commands/bsdtar/test/CMakeLists.txt [new file with mode: 0644]
commands/bsdtar/test/config.sh [new file with mode: 0755]
commands/bsdtar/test/list.h [new file with mode: 0644]
commands/bsdtar/test/main.c [new file with mode: 0644]
commands/bsdtar/test/test-acl.sh [new file with mode: 0755]
commands/bsdtar/test/test-basic.sh [new file with mode: 0755]
commands/bsdtar/test/test-deep-dir.sh [new file with mode: 0755]
commands/bsdtar/test/test-flags.sh [new file with mode: 0755]
commands/bsdtar/test/test-nodump.sh [new file with mode: 0755]
commands/bsdtar/test/test-overwrite.sh [new file with mode: 0755]
commands/bsdtar/test/test-utf8.sh [new file with mode: 0755]
commands/bsdtar/test/test.h [new file with mode: 0644]
commands/bsdtar/test/test_0.c [new file with mode: 0644]
commands/bsdtar/test/test_basic.c [new file with mode: 0644]
commands/bsdtar/test/test_copy.c [new file with mode: 0644]
commands/bsdtar/test/test_empty_mtree.c [new file with mode: 0644]
commands/bsdtar/test/test_getdate.c [new file with mode: 0644]
commands/bsdtar/test/test_help.c [new file with mode: 0644]
commands/bsdtar/test/test_option_T_upper.c [new file with mode: 0644]
commands/bsdtar/test/test_option_q.c [new file with mode: 0644]
commands/bsdtar/test/test_option_r.c [new file with mode: 0644]
commands/bsdtar/test/test_option_s.c [new file with mode: 0644]
commands/bsdtar/test/test_patterns.c [new file with mode: 0644]
commands/bsdtar/test/test_patterns_2.tar.uu [new file with mode: 0644]
commands/bsdtar/test/test_patterns_3.tar.uu [new file with mode: 0644]
commands/bsdtar/test/test_patterns_4.tar.uu [new file with mode: 0644]
commands/bsdtar/test/test_stdio.c [new file with mode: 0644]
commands/bsdtar/test/test_strip_components.c [new file with mode: 0644]
commands/bsdtar/test/test_symlink_dir.c [new file with mode: 0644]
commands/bsdtar/test/test_version.c [new file with mode: 0644]
commands/bsdtar/test/test_windows.c [new file with mode: 0644]
commands/bsdtar/tree.c [new file with mode: 0644]
commands/bsdtar/tree.h [new file with mode: 0644]
commands/bsdtar/util.c [new file with mode: 0644]
commands/bsdtar/write.c [new file with mode: 0644]

index 8b1e0b72f5949975e94df5cc176a4270454f372c..d8160d52d9c46f94f2afcd463626c5eb301cf445 100644 (file)
@@ -4,7 +4,7 @@
 
 SUBDIR=        aal add_route adduser advent arp ash at autil awk \
        backup badblocks banner basename bigmake binpackage \
-       binpackages binsizes bzip2 bzip2recover cal calendar \
+       binpackages binsizes bsdtar bzip2 bzip2recover cal calendar \
        cat cawf cd  cdprobe checkhier chmem \
        chmod chown chroot ci cksum cleantmp clear cmp co \
        comm compress cp crc cron crontab cut datasizes date \
diff --git a/commands/bsdtar/Makefile b/commands/bsdtar/Makefile
new file mode 100644 (file)
index 0000000..19c5e4a
--- /dev/null
@@ -0,0 +1,19 @@
+.include <bsd.own.mk>
+
+PROG=  bsdtar
+SRCS=  bsdtar.c \
+               cmdline.c \
+               getdate.c \
+               read.c \
+               subst.c \
+               tree.c \
+               util.c \
+               write.c
+.include "${.CURDIR}/libarchive_fe/Makefile.inc"
+
+DPADD+= ${LIBARCHIVE} ${LIBZ} ${LIBBZ2}
+LDADD+=        -larchive -lbz2 -lz
+CPPFLAGS+= -DHAVE_CONFIG_H
+CPPFLAGS+= -I${.CURDIR} -I${.CURDIR}/libarchive_fe
+
+.include <bsd.prog.mk>
diff --git a/commands/bsdtar/bsdtar.1 b/commands/bsdtar/bsdtar.1
new file mode 100644 (file)
index 0000000..67cac10
--- /dev/null
@@ -0,0 +1,921 @@
+.\" Copyright (c) 2003-2007 Tim Kientzle
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD: src/usr.bin/tar/bsdtar.1,v 1.46 2008/12/06 07:37:55 kientzle Exp $
+.\"
+.Dd Oct 12, 2009
+.Dt BSDTAR 1
+.Os
+.Sh NAME
+.Nm tar
+.Nd manipulate tape archives
+.Sh SYNOPSIS
+.Nm
+.Op Ar bundled-flags Ao args Ac
+.Op Ao Ar file Ac | Ao Ar pattern Ac ...
+.Nm
+.Brq Fl c
+.Op Ar options
+.Op Ar files | Ar directories
+.Nm
+.Brq Fl r | Fl u
+.Fl f Ar archive-file
+.Op Ar options
+.Op Ar files | Ar directories
+.Nm
+.Brq Fl t | Fl x
+.Op Ar options
+.Op Ar patterns
+.Sh DESCRIPTION
+.Nm
+creates and manipulates streaming archive files.
+This implementation can extract from tar, pax, cpio, zip, jar, ar,
+and ISO 9660 cdrom images and can create tar, pax, cpio, ar,
+and shar archives.
+.Pp
+The first synopsis form shows a
+.Dq bundled
+option word.
+This usage is provided for compatibility with historical implementations.
+See COMPATIBILITY below for details.
+.Pp
+The other synopsis forms show the preferred usage.
+The first option to
+.Nm
+is a mode indicator from the following list:
+.Bl -tag -compact -width indent
+.It Fl c
+Create a new archive containing the specified items.
+.It Fl r
+Like
+.Fl c ,
+but new entries are appended to the archive.
+Note that this only works on uncompressed archives stored in regular files.
+The
+.Fl f
+option is required.
+.It Fl t
+List archive contents to stdout.
+.It Fl u
+Like
+.Fl r ,
+but new entries are added only if they have a modification date
+newer than the corresponding entry in the archive.
+Note that this only works on uncompressed archives stored in regular files.
+The
+.Fl f
+option is required.
+.It Fl x
+Extract to disk from the archive.
+If a file with the same name appears more than once in the archive,
+each copy will be extracted, with later copies overwriting (replacing)
+earlier copies.
+.El
+.Pp
+In
+.Fl c ,
+.Fl r ,
+or
+.Fl u
+mode, each specified file or directory is added to the
+archive in the order specified on the command line.
+By default, the contents of each directory are also archived.
+.Pp
+In extract or list mode, the entire command line
+is read and parsed before the archive is opened.
+The pathnames or patterns on the command line indicate
+which items in the archive should be processed.
+Patterns are shell-style globbing patterns as
+documented in
+.Xr tcsh 1 .
+.Sh OPTIONS
+Unless specifically stated otherwise, options are applicable in
+all operating modes.
+.Bl -tag -width indent
+.It Cm @ Ns Pa archive
+(c and r mode only)
+The specified archive is opened and the entries
+in it will be appended to the current archive.
+As a simple example,
+.Dl Nm Fl c Fl f Pa - Pa newfile Cm @ Ns Pa original.tar
+writes a new archive to standard output containing a file
+.Pa newfile
+and all of the entries from
+.Pa original.tar .
+In contrast,
+.Dl Nm Fl c Fl f Pa - Pa newfile Pa original.tar
+creates a new archive with only two entries.
+Similarly,
+.Dl Nm Fl czf Pa - Fl -format Cm pax Cm @ Ns Pa -
+reads an archive from standard input (whose format will be determined
+automatically) and converts it into a gzip-compressed
+pax-format archive on stdout.
+In this way,
+.Nm
+can be used to convert archives from one format to another.
+.It Fl b Ar blocksize
+Specify the block size, in 512-byte records, for tape drive I/O.
+As a rule, this argument is only needed when reading from or writing
+to tape drives, and usually not even then as the default block size of
+20 records (10240 bytes) is very common.
+.It Fl C Ar directory
+In c and r mode, this changes the directory before adding
+the following files.
+In x mode, change directories after opening the archive
+but before extracting entries from the archive.
+.It Fl -check-links
+(c and r modes only)
+Issue a warning message unless all links to each file are archived.
+.It Fl -chroot
+(x mode only)
+.Fn chroot
+to the current directory after processing any
+.Fl C
+options and before extracting any files.
+.It Fl -exclude Ar pattern
+Do not process files or directories that match the
+specified pattern.
+Note that exclusions take precedence over patterns or filenames
+specified on the command line.
+.It Fl -format Ar format
+(c, r, u mode only)
+Use the specified format for the created archive.
+Supported formats include
+.Dq cpio ,
+.Dq pax ,
+.Dq shar ,
+and
+.Dq ustar .
+Other formats may also be supported; see
+.Xr libarchive-formats 5
+for more information about currently-supported formats.
+In r and u modes, when extending an existing archive, the format specified
+here must be compatible with the format of the existing archive on disk.
+.It Fl f Ar file
+Read the archive from or write the archive to the specified file.
+The filename can be
+.Pa -
+for standard input or standard output.
+If not specified, the default tape device will be used.
+(On
+.Fx ,
+the default tape device is
+.Pa /dev/sa0 . )
+.It Fl H
+(c and r mode only)
+Symbolic links named on the command line will be followed; the
+target of the link will be archived, not the link itself.
+.It Fl h
+(c and r mode only)
+Synonym for
+.Fl L .
+.It Fl I
+Synonym for
+.Fl T .
+.It Fl -include Ar pattern
+Process only files or directories that match the specified pattern.
+Note that exclusions specified with
+.Fl -exclude
+take precedence over inclusions.
+If no inclusions are explicitly specified, all entries are processed by
+default.
+The
+.Fl -include
+option is especially useful when filtering archives.
+For example, the command
+.Dl Nm Fl c Fl f Pa new.tar Fl -include='*foo*' Cm @ Ns Pa old.tgz
+creates a new archive
+.Pa new.tar
+containing only the entries from
+.Pa old.tgz
+containing the string
+.Sq foo .
+.It Fl j
+(c mode only)
+Compress the resulting archive with
+.Xr bzip2 1 .
+In extract or list modes, this option is ignored.
+Note that, unlike other
+.Nm tar
+implementations, this implementation recognizes bzip2 compression
+automatically when reading archives.
+.It Fl k
+(x mode only)
+Do not overwrite existing files.
+In particular, if a file appears more than once in an archive,
+later copies will not overwrite earlier copies.
+.It Fl -keep-newer-files
+(x mode only)
+Do not overwrite existing files that are newer than the
+versions appearing in the archive being extracted.
+.It Fl L
+(c and r mode only)
+All symbolic links will be followed.
+Normally, symbolic links are archived as such.
+With this option, the target of the link will be archived instead.
+.It Fl l
+This is a synonym for the
+.Fl -check-links
+option.
+.It Fl m
+(x mode only)
+Do not extract modification time.
+By default, the modification time is set to the time stored in the archive.
+.It Fl n
+(c, r, u modes only)
+Do not recursively archive the contents of directories.
+.It Fl -newer Ar date
+(c, r, u modes only)
+Only include files and directories newer than the specified date.
+This compares ctime entries.
+.It Fl -newer-mtime Ar date
+(c, r, u modes only)
+Like
+.Fl -newer ,
+except it compares mtime entries instead of ctime entries.
+.It Fl -newer-than Pa file
+(c, r, u modes only)
+Only include files and directories newer than the specified file.
+This compares ctime entries.
+.It Fl -newer-mtime-than Pa file
+(c, r, u modes only)
+Like
+.Fl -newer-than ,
+except it compares mtime entries instead of ctime entries.
+.It Fl -nodump
+(c and r modes only)
+Honor the nodump file flag by skipping this file.
+.It Fl -null
+(use with
+.Fl I ,
+.Fl T ,
+or
+.Fl X )
+Filenames or patterns are separated by null characters,
+not by newlines.
+This is often used to read filenames output by the
+.Fl print0
+option to
+.Xr find 1 .
+.It Fl -numeric-owner
+(x mode only)
+Ignore symbolic user and group names when restoring archives to disk,
+only numeric uid and gid values will be obeyed.
+.It Fl O
+(x, t modes only)
+In extract (-x) mode, files will be written to standard out rather than
+being extracted to disk.
+In list (-t) mode, the file listing will be written to stderr rather than
+the usual stdout.
+.It Fl o
+(x mode)
+Use the user and group of the user running the program rather
+than those specified in the archive.
+Note that this has no significance unless
+.Fl p
+is specified, and the program is being run by the root user.
+In this case, the file modes and flags from
+the archive will be restored, but ACLs or owner information in
+the archive will be discarded.
+.It Fl o
+(c, r, u mode)
+A synonym for
+.Fl -format Ar ustar
+.It Fl -one-file-system
+(c, r, and u modes)
+Do not cross mount points.
+.It Fl -options Ar options
+Select optional behaviors for particular modules.
+The argument is a text string containing comma-separated
+keywords and values.
+These are passed to the modules that handle particular
+formats to control how those formats will behave.
+Each option has one of the following forms:
+.Bl -tag -compact -width indent
+.It Ar key=value
+The key will be set to the specified value in every module that supports it.
+Modules that do not support this key will ignore it.
+.It Ar key
+The key will be enabled in every module that supports it.
+This is equivalent to
+.Ar key Ns Cm =1 .
+.It Ar !key
+The key will be disabled in every module that supports it.
+.It Ar module:key=value , Ar module:key , Ar module:!key
+As above, but the corresponding key and value will be provided
+only to modules whose name matches
+.Ar module .
+.El
+The currently supported modules and keys are:
+.Bl -tag -compact -width indent
+.It Cm iso9660:joliet
+Support Joliet extensions.
+This is enabled by default, use
+.Cm !joliet
+or
+.Cm iso9660:!joliet
+to disable.
+.It Cm iso9660:rockridge
+Support Rock Ridge extensions.
+This is enabled by default, use
+.Cm !rockridge
+or
+.Cm iso9660:!rockridge
+to disable.
+.It Cm gzip:compression-level
+A decimal integer from 0 to 9 specifying the gzip compression level.
+.It Cm xz:compression-level
+A decimal integer from 0 to 9 specifying the xz compression level.
+.It Cm mtree: Ns Ar keyword
+The mtree writer module allows you to specify which mtree keywords
+will be included in the output.
+Supported keywords include:
+.Cm cksum , Cm device , Cm flags , Cm gid , Cm gname , Cm indent ,
+.Cm link , Cm md5 , Cm mode , Cm nlink , Cm rmd160 , Cm sha1 , Cm sha256 ,
+.Cm sha384 , Cm sha512 , Cm size , Cm time , Cm uid , Cm uname .
+The default is equivalent to:
+.Dq device, flags, gid, gname, link, mode, nlink, size, time, type, uid, uname .
+.It Cm mtree:all
+Enables all of the above keywords.
+You can also use
+.Cm mtree:!all
+to disable all keywords.
+.It Cm mtree:use-set
+Enable generation of
+.Cm /set
+lines in the output.
+.It Cm mtree:indent
+Produce human-readable output by indenting options and splitting lines
+to fit into 80 columns.
+.It Cm zip:compression Ns = Ns Ar type
+Use
+.Ar type
+as compression method.
+Supported values are store (uncompressed) and deflate (gzip algorithm).
+.El
+If a provided option is not supported by any module, that
+is a fatal error.
+.It Fl P
+Preserve pathnames.
+By default, absolute pathnames (those that begin with a /
+character) have the leading slash removed both when creating archives
+and extracting from them.
+Also,
+.Nm
+will refuse to extract archive entries whose pathnames contain
+.Pa ..
+or whose target directory would be altered by a symlink.
+This option suppresses these behaviors.
+.It Fl p
+(x mode only)
+Preserve file permissions.
+Attempt to restore the full permissions, including owner, file modes, file
+flags and ACLs, if available, for each item extracted from the archive.
+By default, newly-created files are owned by the user running
+.Nm ,
+the file mode is restored for newly-created regular files, and
+all other types of entries receive default permissions.
+If
+.Nm
+is being run by root, the default is to restore the owner unless the
+.Fl o
+option is also specified.
+.It Fl q ( Fl -fast-read )
+(x and t mode only)
+Extract or list only the first archive entry that matches each pattern
+or filename operand.
+Exit as soon as each specified pattern or filename has been matched.
+By default, the archive is always read to the very end, since
+there can be multiple entries with the same name and, by convention,
+later entries overwrite earlier entries.
+This option is provided as a performance optimization.
+.It Fl S
+(x mode only)
+Extract files as sparse files.
+For every block on disk, check first if it contains only NULL bytes and seek
+over it otherwise.
+This works similiar to the conv=sparse option of dd.
+.It Fl -strip-components Ar count
+(x mode only)
+Remove the specified number of leading path elements.
+Pathnames with fewer elements will be silently skipped.
+Note that the pathname is edited after checking inclusion/exclusion patterns
+but before security checks.
+.It Fl s Ar pattern
+Modify file or archive member names according to
+.Pa pattern .
+The pattern has the format
+.Ar /old/new/ Ns Op gps
+where
+.Ar old
+is a basic regular expression,
+.Ar new
+is the replacement string of the matched part,
+and the optional trailing letters modify
+how the replacement is handled.
+If
+.Ar old
+is not matched, the pattern is skipped.
+Within
+.Ar new ,
+~ is substituted with the match, \1 to \9 with the content of
+the corresponding captured group.
+The optional trailing g specifies that matching should continue
+after the matched part and stopped on the first unmatched pattern.
+The optional trailing s specifies that the pattern applies to the value
+of symbolic links.
+The optional trailing p specifies that after a successful substitution
+the original path name and the new path name should be printed to
+standard error.
+.It Fl T Ar filename
+In x or t mode,
+.Nm
+will read the list of names to be extracted from
+.Pa filename .
+In c mode,
+.Nm
+will read names to be archived from
+.Pa filename .
+The special name
+.Dq -C
+on a line by itself will cause the current directory to be changed to
+the directory specified on the following line.
+Names are terminated by newlines unless
+.Fl -null
+is specified.
+Note that
+.Fl -null
+also disables the special handling of lines containing
+.Dq -C .
+.It Fl U
+(x mode only)
+Unlink files before creating them.
+Without this option,
+.Nm
+overwrites existing files, which preserves existing hardlinks.
+With this option, existing hardlinks will be broken, as will any
+symlink that would affect the location of an extracted file.
+.It Fl -use-compress-program Ar program
+Pipe the input (in x or t mode) or the output (in c mode) through
+.Pa program
+instead of using the builtin compression support.
+.It Fl v
+Produce verbose output.
+In create and extract modes,
+.Nm
+will list each file name as it is read from or written to
+the archive.
+In list mode,
+.Nm
+will produce output similar to that of
+.Xr ls 1 .
+Additional
+.Fl v
+options will provide additional detail.
+.It Fl -version
+Print version of
+.Nm
+and
+.Nm libarchive ,
+and exit.
+.It Fl w
+Ask for confirmation for every action.
+.It Fl X Ar filename
+Read a list of exclusion patterns from the specified file.
+See
+.Fl -exclude
+for more information about the handling of exclusions.
+.It Fl y
+(c mode only)
+Compress the resulting archive with
+.Xr bzip2 1 .
+In extract or list modes, this option is ignored.
+Note that, unlike other
+.Nm tar
+implementations, this implementation recognizes bzip2 compression
+automatically when reading archives.
+.It Fl z
+(c mode only)
+Compress the resulting archive with
+.Xr gzip 1 .
+In extract or list modes, this option is ignored.
+Note that, unlike other
+.Nm tar
+implementations, this implementation recognizes gzip compression
+automatically when reading archives.
+.It Fl Z
+(c mode only)
+Compress the resulting archive with
+.Xr compress 1 .
+In extract or list modes, this option is ignored.
+Note that, unlike other
+.Nm tar
+implementations, this implementation recognizes compress compression
+automatically when reading archives.
+.El
+.Sh ENVIRONMENT
+The following environment variables affect the execution of
+.Nm :
+.Bl -tag -width ".Ev BLOCKSIZE"
+.It Ev LANG
+The locale to use.
+See
+.Xr environ 7
+for more information.
+.It Ev TAPE
+The default tape device.
+The
+.Fl f
+option overrides this.
+.It Ev TZ
+The timezone to use when displaying dates.
+See
+.Xr environ 7
+for more information.
+.El
+.Sh FILES
+.Bl -tag -width ".Ev BLOCKSIZE"
+.It Pa /dev/sa0
+The default tape device, if not overridden by the
+.Ev TAPE
+environment variable or the
+.Fl f
+option.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+The following creates a new archive
+called
+.Ar file.tar.gz
+that contains two files
+.Ar source.c
+and
+.Ar source.h :
+.Dl Nm Fl czf Pa file.tar.gz Pa source.c Pa source.h
+.Pp
+To view a detailed table of contents for this
+archive:
+.Dl Nm Fl tvf Pa file.tar.gz
+.Pp
+To extract all entries from the archive on
+the default tape drive:
+.Dl Nm Fl x
+.Pp
+To examine the contents of an ISO 9660 cdrom image:
+.Dl Nm Fl tf Pa image.iso
+.Pp
+To move file hierarchies, invoke
+.Nm
+as
+.Dl Nm Fl cf Pa - Fl C Pa srcdir\ . | Nm Fl xpf Pa - Fl C Pa destdir
+or more traditionally
+.Dl cd srcdir \&; Nm Fl cf Pa -\ . | ( cd destdir \&; Nm Fl xpf Pa - )
+.Pp
+In create mode, the list of files and directories to be archived
+can also include directory change instructions of the form
+.Cm -C Ns Pa foo/baz
+and archive inclusions of the form
+.Cm @ Ns Pa archive-file .
+For example, the command line
+.Dl Nm Fl c Fl f Pa new.tar Pa foo1 Cm @ Ns Pa old.tgz Cm -C Ns Pa /tmp Pa foo2
+will create a new archive
+.Pa new.tar .
+.Nm
+will read the file
+.Pa foo1
+from the current directory and add it to the output archive.
+It will then read each entry from
+.Pa old.tgz
+and add those entries to the output archive.
+Finally, it will switch to the
+.Pa /tmp
+directory and add
+.Pa foo2
+to the output archive.
+.Pp
+An input file in
+.Xr mtree 5
+format can be used to create an output archive with arbitrary ownership,
+permissions, or names that differ from existing data on disk:
+.Pp
+.Dl $ cat input.mtree
+.Dl #mtree
+.Dl usr/bin uid=0 gid=0 mode=0755 type=dir
+.Dl usr/bin/ls uid=0 gid=0 mode=0755 type=file content=myls
+.Dl $ tar -cvf output.tar @input.mtree
+.Pp
+The
+.Fl -newer
+and
+.Fl -newer-mtime
+switches accept a variety of common date and time specifications, including
+.Dq 12 Mar 2005 7:14:29pm ,
+.Dq 2005-03-12 19:14 ,
+.Dq 5 minutes ago ,
+and
+.Dq 19:14 PST May 1 .
+.Pp
+The
+.Fl -options
+argument can be used to control various details of archive generation
+or reading.
+For example, you can generate mtree output which only contains
+.Cm type , Cm time ,
+and
+.Cm uid
+keywords:
+.Dl Nm Fl cf Pa file.tar Fl -format=mtree Fl -options='!all,type,time,uid' Pa dir
+or you can set the compression level used by gzip or xz compression:
+.Dl Nm Fl czf Pa file.tar Fl -options='compression-level=9' .
+For more details, see the explanation of the
+.Fn archive_read_set_options
+and
+.Fn archive_write_set_options
+API calls that are described in
+.Xr archive_read 3
+and
+.Xr archive_write 3 .
+.Sh COMPATIBILITY
+The bundled-arguments format is supported for compatibility
+with historic implementations.
+It consists of an initial word (with no leading - character) in which
+each character indicates an option.
+Arguments follow as separate words.
+The order of the arguments must match the order
+of the corresponding characters in the bundled command word.
+For example,
+.Dl Nm Cm tbf 32 Pa file.tar
+specifies three flags
+.Cm t ,
+.Cm b ,
+and
+.Cm f .
+The
+.Cm b
+and
+.Cm f
+flags both require arguments,
+so there must be two additional items
+on the command line.
+The
+.Ar 32
+is the argument to the
+.Cm b
+flag, and
+.Ar file.tar
+is the argument to the
+.Cm f
+flag.
+.Pp
+The mode options c, r, t, u, and x and the options
+b, f, l, m, o, v, and w comply with SUSv2.
+.Pp
+For maximum portability, scripts that invoke
+.Nm tar
+should use the bundled-argument format above, should limit
+themselves to the
+.Cm c ,
+.Cm t ,
+and
+.Cm x
+modes, and the
+.Cm b ,
+.Cm f ,
+.Cm m ,
+.Cm v ,
+and
+.Cm w
+options.
+.Pp
+Additional long options are provided to improve compatibility with other
+tar implementations.
+.Sh SECURITY
+Certain security issues are common to many archiving programs, including
+.Nm .
+In particular, carefully-crafted archives can request that
+.Nm
+extract files to locations outside of the target directory.
+This can potentially be used to cause unwitting users to overwrite
+files they did not intend to overwrite.
+If the archive is being extracted by the superuser, any file
+on the system can potentially be overwritten.
+There are three ways this can happen.
+Although
+.Nm
+has mechanisms to protect against each one,
+savvy users should be aware of the implications:
+.Bl -bullet -width indent
+.It
+Archive entries can have absolute pathnames.
+By default,
+.Nm
+removes the leading
+.Pa /
+character from filenames before restoring them to guard against this problem.
+.It
+Archive entries can have pathnames that include
+.Pa ..
+components.
+By default,
+.Nm
+will not extract files containing
+.Pa ..
+components in their pathname.
+.It
+Archive entries can exploit symbolic links to restore
+files to other directories.
+An archive can restore a symbolic link to another directory,
+then use that link to restore a file into that directory.
+To guard against this,
+.Nm
+checks each extracted path for symlinks.
+If the final path element is a symlink, it will be removed
+and replaced with the archive entry.
+If
+.Fl U
+is specified, any intermediate symlink will also be unconditionally removed.
+If neither
+.Fl U
+nor
+.Fl P
+is specified,
+.Nm
+will refuse to extract the entry.
+.El
+To protect yourself, you should be wary of any archives that
+come from untrusted sources.
+You should examine the contents of an archive with
+.Dl Nm Fl tf Pa filename
+before extraction.
+You should use the
+.Fl k
+option to ensure that
+.Nm
+will not overwrite any existing files or the
+.Fl U
+option to remove any pre-existing files.
+You should generally not extract archives while running with super-user
+privileges.
+Note that the
+.Fl P
+option to
+.Nm
+disables the security checks above and allows you to extract
+an archive while preserving any absolute pathnames,
+.Pa ..
+components, or symlinks to other directories.
+.Sh SEE ALSO
+.Xr bzip2 1 ,
+.Xr compress 1 ,
+.Xr cpio 1 ,
+.Xr gzip 1 ,
+.Xr mt 1 ,
+.Xr pax 1 ,
+.Xr shar 1 ,
+.Xr libarchive 3 ,
+.Xr libarchive-formats 5 ,
+.Xr tar 5
+.Sh STANDARDS
+There is no current POSIX standard for the tar command; it appeared
+in
+.St -p1003.1-96
+but was dropped from
+.St -p1003.1-2001 .
+The options used by this implementation were developed by surveying a
+number of existing tar implementations as well as the old POSIX specification
+for tar and the current POSIX specification for pax.
+.Pp
+The ustar and pax interchange file formats are defined by
+.St -p1003.1-2001
+for the pax command.
+.Sh HISTORY
+A
+.Nm tar
+command appeared in Seventh Edition Unix, which was released in January, 1979.
+There have been numerous other implementations,
+many of which extended the file format.
+John Gilmore's
+.Nm pdtar
+public-domain implementation (circa November, 1987)
+was quite influential, and formed the basis of GNU tar.
+GNU tar was included as the standard system tar
+in
+.Fx
+beginning with
+.Fx 1.0 .
+.Pp
+This is a complete re-implementation based on the
+.Xr libarchive 3
+library.
+.Sh BUGS
+This program follows
+.St -p1003.1-96
+for the definition of the
+.Fl l
+option.
+Note that GNU tar prior to version 1.15 treated
+.Fl l
+as a synonym for the
+.Fl -one-file-system
+option.
+.Pp
+The
+.Fl C Pa dir
+option may differ from historic implementations.
+.Pp
+All archive output is written in correctly-sized blocks, even
+if the output is being compressed.
+Whether or not the last output block is padded to a full
+block size varies depending on the format and the
+output device.
+For tar and cpio formats, the last block of output is padded
+to a full block size if the output is being
+written to standard output or to a character or block device such as
+a tape drive.
+If the output is being written to a regular file, the last block
+will not be padded.
+Many compressors, including
+.Xr gzip 1
+and
+.Xr bzip2 1 ,
+complain about the null padding when decompressing an archive created by
+.Nm ,
+although they still extract it correctly.
+.Pp
+The compression and decompression is implemented internally, so
+there may be insignificant differences between the compressed output
+generated by
+.Dl Nm Fl czf Pa - file
+and that generated by
+.Dl Nm Fl cf Pa - file | Nm gzip
+.Pp
+The default should be to read and write archives to the standard I/O paths,
+but tradition (and POSIX) dictates otherwise.
+.Pp
+The
+.Cm r
+and
+.Cm u
+modes require that the archive be uncompressed
+and located in a regular file on disk.
+Other archives can be modified using
+.Cm c
+mode with the
+.Pa @archive-file
+extension.
+.Pp
+To archive a file called
+.Pa @foo
+or
+.Pa -foo
+you must specify it as
+.Pa ./@foo
+or
+.Pa ./-foo ,
+respectively.
+.Pp
+In create mode, a leading
+.Pa ./
+is always removed.
+A leading
+.Pa /
+is stripped unless the
+.Fl P
+option is specified.
+.Pp
+There needs to be better support for file selection on both create
+and extract.
+.Pp
+There is not yet any support for multi-volume archives or for archiving
+sparse files.
+.Pp
+Converting between dissimilar archive formats (such as tar and cpio) using the
+.Cm @ Ns Pa -
+convention can cause hard link information to be lost.
+(This is a consequence of the incompatible ways that different archive
+formats store hardlink information.)
+.Pp
+There are alternative long options for many of the short options that
+are deliberately not documented.
diff --git a/commands/bsdtar/bsdtar.c b/commands/bsdtar/bsdtar.c
new file mode 100644 (file)
index 0000000..d8f8286
--- /dev/null
@@ -0,0 +1,734 @@
+/*-
+ * Copyright (c) 2003-2008 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "bsdtar_platform.h"
+__FBSDID("$FreeBSD: src/usr.bin/tar/bsdtar.c,v 1.93 2008/11/08 04:43:24 kientzle Exp $");
+
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#ifdef HAVE_LANGINFO_H
+#include <langinfo.h>
+#endif
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+#ifdef HAVE_PATHS_H
+#include <paths.h>
+#endif
+#ifdef HAVE_SIGNAL_H
+#include <signal.h>
+#endif
+#include <stdio.h>
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#ifdef HAVE_TIME_H
+#include <time.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_ZLIB_H
+#include <zlib.h>
+#endif
+
+#include "bsdtar.h"
+#include "err.h"
+
+/*
+ * Per POSIX.1-1988, tar defaults to reading/writing archives to/from
+ * the default tape device for the system.  Pick something reasonable here.
+ */
+#ifdef __linux
+#define        _PATH_DEFTAPE "/dev/st0"
+#endif
+#if defined(_WIN32) && !defined(__CYGWIN__)
+#define        _PATH_DEFTAPE "\\\\.\\tape0"
+#endif
+
+#ifndef _PATH_DEFTAPE
+#define        _PATH_DEFTAPE "/dev/tape"
+#endif
+
+#ifdef __MINGW32__
+int _CRT_glob = 0; /* Disable broken CRT globbing. */
+#endif
+
+static struct bsdtar *_bsdtar;
+
+#if defined(HAVE_SIGACTION) && (defined(SIGINFO) || defined(SIGUSR1))
+static volatile int siginfo_occurred;
+
+static void
+siginfo_handler(int sig)
+{
+       (void)sig; /* UNUSED */
+       siginfo_occurred = 1;
+}
+
+int
+need_report(void)
+{
+       int r = siginfo_occurred;
+       siginfo_occurred = 0;
+       return (r);
+}
+#else
+int
+need_report(void)
+{
+       return (0);
+}
+#endif
+
+/* External function to parse a date/time string */
+time_t get_date(time_t, const char *);
+
+static void             long_help(void);
+static void             only_mode(struct bsdtar *, const char *opt,
+                            const char *valid);
+static void             set_mode(struct bsdtar *, char opt);
+static void             version(void);
+
+/* A basic set of security flags to request from libarchive. */
+#define        SECURITY                                        \
+       (ARCHIVE_EXTRACT_SECURE_SYMLINKS                \
+        | ARCHIVE_EXTRACT_SECURE_NODOTDOT)
+
+int
+main(int argc, char **argv)
+{
+       struct bsdtar           *bsdtar, bsdtar_storage;
+       int                      opt, t;
+       char                     option_o;
+       char                     possible_help_request;
+       char                     buff[16];
+       time_t                   now;
+
+       /*
+        * Use a pointer for consistency, but stack-allocated storage
+        * for ease of cleanup.
+        */
+       _bsdtar = bsdtar = &bsdtar_storage;
+       memset(bsdtar, 0, sizeof(*bsdtar));
+       bsdtar->fd = -1; /* Mark as "unused" */
+       option_o = 0;
+
+#if defined(HAVE_SIGACTION) && (defined(SIGINFO) || defined(SIGUSR1))
+       { /* Catch SIGINFO and SIGUSR1, if they exist. */
+               struct sigaction sa;
+               sa.sa_handler = siginfo_handler;
+               sigemptyset(&sa.sa_mask);
+               sa.sa_flags = 0;
+#ifdef SIGINFO
+               if (sigaction(SIGINFO, &sa, NULL))
+                       lafe_errc(1, errno, "sigaction(SIGINFO) failed");
+#endif
+#ifdef SIGUSR1
+               /* ... and treat SIGUSR1 the same way as SIGINFO. */
+               if (sigaction(SIGUSR1, &sa, NULL))
+                       lafe_errc(1, errno, "sigaction(SIGUSR1) failed");
+#endif
+       }
+#endif
+
+
+       /* Need lafe_progname before calling lafe_warnc. */
+       if (*argv == NULL)
+               lafe_progname = "bsdtar";
+       else {
+#if defined(_WIN32) && !defined(__CYGWIN__)
+               lafe_progname = strrchr(*argv, '\\');
+#else
+               lafe_progname = strrchr(*argv, '/');
+#endif
+               if (lafe_progname != NULL)
+                       lafe_progname++;
+               else
+                       lafe_progname = *argv;
+       }
+
+       time(&now);
+
+#if HAVE_SETLOCALE
+       if (setlocale(LC_ALL, "") == NULL)
+               lafe_warnc(0, "Failed to set default locale");
+#endif
+#if defined(HAVE_NL_LANGINFO) && defined(HAVE_D_MD_ORDER)
+       bsdtar->day_first = (*nl_langinfo(D_MD_ORDER) == 'd');
+#endif
+       possible_help_request = 0;
+
+       /* Look up uid of current user for future reference */
+       bsdtar->user_uid = geteuid();
+
+       /* Default: open tape drive. */
+       bsdtar->filename = getenv("TAPE");
+       if (bsdtar->filename == NULL)
+               bsdtar->filename = _PATH_DEFTAPE;
+
+       /* Default: preserve mod time on extract */
+       bsdtar->extract_flags = ARCHIVE_EXTRACT_TIME;
+
+       /* Default: Perform basic security checks. */
+       bsdtar->extract_flags |= SECURITY;
+
+#ifndef _WIN32
+       /* On POSIX systems, assume --same-owner and -p when run by
+        * the root user.  This doesn't make any sense on Windows. */
+       if (bsdtar->user_uid == 0) {
+               /* --same-owner */
+               bsdtar->extract_flags |= ARCHIVE_EXTRACT_OWNER;
+               /* -p */
+               bsdtar->extract_flags |= ARCHIVE_EXTRACT_PERM;
+               bsdtar->extract_flags |= ARCHIVE_EXTRACT_ACL;
+               bsdtar->extract_flags |= ARCHIVE_EXTRACT_XATTR;
+               bsdtar->extract_flags |= ARCHIVE_EXTRACT_FFLAGS;
+       }
+#endif
+
+       bsdtar->argv = argv;
+       bsdtar->argc = argc;
+
+       /*
+        * Comments following each option indicate where that option
+        * originated:  SUSv2, POSIX, GNU tar, star, etc.  If there's
+        * no such comment, then I don't know of anyone else who
+        * implements that option.
+        */
+       while ((opt = bsdtar_getopt(bsdtar)) != -1) {
+               switch (opt) {
+               case 'B': /* GNU tar */
+                       /* libarchive doesn't need this; just ignore it. */
+                       break;
+               case 'b': /* SUSv2 */
+                       t = atoi(bsdtar->optarg);
+                       if (t <= 0 || t > 8192)
+                               lafe_errc(1, 0,
+                                   "Argument to -b is out of range (1..8192)");
+                       bsdtar->bytes_per_block = 512 * t;
+                       break;
+               case 'C': /* GNU tar */
+                       set_chdir(bsdtar, bsdtar->optarg);
+                       break;
+               case 'c': /* SUSv2 */
+                       set_mode(bsdtar, opt);
+                       break;
+               case OPTION_CHECK_LINKS: /* GNU tar */
+                       bsdtar->option_warn_links = 1;
+                       break;
+               case OPTION_CHROOT: /* NetBSD */
+                       bsdtar->option_chroot = 1;
+                       break;
+               case OPTION_EXCLUDE: /* GNU tar */
+                       if (lafe_exclude(&bsdtar->matching, bsdtar->optarg))
+                               lafe_errc(1, 0,
+                                   "Couldn't exclude %s\n", bsdtar->optarg);
+                       break;
+               case OPTION_FORMAT: /* GNU tar, others */
+                       bsdtar->create_format = bsdtar->optarg;
+                       break;
+               case OPTION_OPTIONS:
+                       bsdtar->option_options = bsdtar->optarg;
+                       break;
+               case 'f': /* SUSv2 */
+                       bsdtar->filename = bsdtar->optarg;
+                       if (strcmp(bsdtar->filename, "-") == 0)
+                               bsdtar->filename = NULL;
+                       break;
+               case 'H': /* BSD convention */
+                       bsdtar->symlink_mode = 'H';
+                       break;
+               case 'h': /* Linux Standards Base, gtar; synonym for -L */
+                       bsdtar->symlink_mode = 'L';
+                       /* Hack: -h by itself is the "help" command. */
+                       possible_help_request = 1;
+                       break;
+               case OPTION_HELP: /* GNU tar, others */
+                       long_help();
+                       exit(0);
+                       break;
+               case 'I': /* GNU tar */
+                       /*
+                        * TODO: Allow 'names' to come from an archive,
+                        * not just a text file.  Design a good UI for
+                        * allowing names and mode/owner to be read
+                        * from an archive, with contents coming from
+                        * disk.  This can be used to "refresh" an
+                        * archive or to design archives with special
+                        * permissions without having to create those
+                        * permissions on disk.
+                        */
+                       bsdtar->names_from_file = bsdtar->optarg;
+                       break;
+               case OPTION_INCLUDE:
+                       /*
+                        * Noone else has the @archive extension, so
+                        * noone else needs this to filter entries
+                        * when transforming archives.
+                        */
+                       if (lafe_include(&bsdtar->matching, bsdtar->optarg))
+                               lafe_errc(1, 0,
+                                   "Failed to add %s to inclusion list",
+                                   bsdtar->optarg);
+                       break;
+               case 'j': /* GNU tar */
+                       if (bsdtar->create_compression != '\0')
+                               lafe_errc(1, 0,
+                                   "Can't specify both -%c and -%c", opt,
+                                   bsdtar->create_compression);
+                       bsdtar->create_compression = opt;
+                       break;
+               case 'J': /* GNU tar 1.21 and later */
+                       if (bsdtar->create_compression != '\0')
+                               lafe_errc(1, 0,
+                                   "Can't specify both -%c and -%c", opt,
+                                   bsdtar->create_compression);
+                       bsdtar->create_compression = opt;
+                       break;
+               case 'k': /* GNU tar */
+                       bsdtar->extract_flags |= ARCHIVE_EXTRACT_NO_OVERWRITE;
+                       break;
+               case OPTION_KEEP_NEWER_FILES: /* GNU tar */
+                       bsdtar->extract_flags |= ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER;
+                       break;
+               case 'L': /* BSD convention */
+                       bsdtar->symlink_mode = 'L';
+                       break;
+               case 'l': /* SUSv2 and GNU tar beginning with 1.16 */
+                       /* GNU tar 1.13  used -l for --one-file-system */
+                       bsdtar->option_warn_links = 1;
+                       break;
+               case OPTION_LZMA:
+                       if (bsdtar->create_compression != '\0')
+                               lafe_errc(1, 0,
+                                   "Can't specify both -%c and -%c", opt,
+                                   bsdtar->create_compression);
+                       bsdtar->create_compression = opt;
+                       break;
+               case 'm': /* SUSv2 */
+                       bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_TIME;
+                       break;
+               case 'n': /* GNU tar */
+                       bsdtar->option_no_subdirs = 1;
+                       break;
+               /*
+                * Selecting files by time:
+                *    --newer-?time='date' Only files newer than 'date'
+                *    --newer-?time-than='file' Only files newer than time
+                *         on specified file (useful for incremental backups)
+                * TODO: Add corresponding "older" options to reverse these.
+                */
+               case OPTION_NEWER_CTIME: /* GNU tar */
+                       bsdtar->newer_ctime_sec = get_date(now, bsdtar->optarg);
+                       break;
+               case OPTION_NEWER_CTIME_THAN:
+                       {
+                               struct stat st;
+                               if (stat(bsdtar->optarg, &st) != 0)
+                                       lafe_errc(1, 0,
+                                           "Can't open file %s", bsdtar->optarg);
+                               bsdtar->newer_ctime_sec = st.st_ctime;
+                               bsdtar->newer_ctime_nsec =
+                                   ARCHIVE_STAT_CTIME_NANOS(&st);
+                       }
+                       break;
+               case OPTION_NEWER_MTIME: /* GNU tar */
+                       bsdtar->newer_mtime_sec = get_date(now, bsdtar->optarg);
+                       break;
+               case OPTION_NEWER_MTIME_THAN:
+                       {
+                               struct stat st;
+                               if (stat(bsdtar->optarg, &st) != 0)
+                                       lafe_errc(1, 0,
+                                           "Can't open file %s", bsdtar->optarg);
+                               bsdtar->newer_mtime_sec = st.st_mtime;
+                               bsdtar->newer_mtime_nsec =
+                                   ARCHIVE_STAT_MTIME_NANOS(&st);
+                       }
+                       break;
+               case OPTION_NODUMP: /* star */
+                       bsdtar->option_honor_nodump = 1;
+                       break;
+               case OPTION_NO_SAME_OWNER: /* GNU tar */
+                       bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_OWNER;
+                       break;
+               case OPTION_NO_SAME_PERMISSIONS: /* GNU tar */
+                       bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_PERM;
+                       bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_ACL;
+                       bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_XATTR;
+                       bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_FFLAGS;
+                       break;
+               case OPTION_NULL: /* GNU tar */
+                       bsdtar->option_null++;
+                       break;
+               case OPTION_NUMERIC_OWNER: /* GNU tar */
+                       bsdtar->option_numeric_owner++;
+                       break;
+               case 'O': /* GNU tar */
+                       bsdtar->option_stdout = 1;
+                       break;
+               case 'o': /* SUSv2 and GNU conflict here, but not fatally */
+                       option_o = 1; /* Record it and resolve it later. */
+                       break;
+               case OPTION_ONE_FILE_SYSTEM: /* GNU tar */
+                       bsdtar->option_dont_traverse_mounts = 1;
+                       break;
+#if 0
+               /*
+                * The common BSD -P option is not necessary, since
+                * our default is to archive symlinks, not follow
+                * them.  This is convenient, as -P conflicts with GNU
+                * tar anyway.
+                */
+               case 'P': /* BSD convention */
+                       /* Default behavior, no option necessary. */
+                       break;
+#endif
+               case 'P': /* GNU tar */
+                       bsdtar->extract_flags &= ~SECURITY;
+                       bsdtar->option_absolute_paths = 1;
+                       break;
+               case 'p': /* GNU tar, star */
+                       bsdtar->extract_flags |= ARCHIVE_EXTRACT_PERM;
+                       bsdtar->extract_flags |= ARCHIVE_EXTRACT_ACL;
+                       bsdtar->extract_flags |= ARCHIVE_EXTRACT_XATTR;
+                       bsdtar->extract_flags |= ARCHIVE_EXTRACT_FFLAGS;
+                       break;
+               case OPTION_POSIX: /* GNU tar */
+                       bsdtar->create_format = "pax";
+                       break;
+               case 'q': /* FreeBSD GNU tar --fast-read, NetBSD -q */
+                       bsdtar->option_fast_read = 1;
+                       break;
+               case 'r': /* SUSv2 */
+                       set_mode(bsdtar, opt);
+                       break;
+               case 'S': /* NetBSD pax-as-tar */
+                       bsdtar->extract_flags |= ARCHIVE_EXTRACT_SPARSE;
+                       break;
+               case 's': /* NetBSD pax-as-tar */
+#if HAVE_REGEX_H
+                       add_substitution(bsdtar, bsdtar->optarg);
+#else
+                       lafe_warnc(0,
+                           "-s is not supported by this version of bsdtar");
+                       usage();
+#endif
+                       break;
+               case OPTION_SAME_OWNER: /* GNU tar */
+                       bsdtar->extract_flags |= ARCHIVE_EXTRACT_OWNER;
+                       break;
+               case OPTION_STRIP_COMPONENTS: /* GNU tar 1.15 */
+                       bsdtar->strip_components = atoi(bsdtar->optarg);
+                       break;
+               case 'T': /* GNU tar */
+                       bsdtar->names_from_file = bsdtar->optarg;
+                       break;
+               case 't': /* SUSv2 */
+                       set_mode(bsdtar, opt);
+                       bsdtar->verbose++;
+                       break;
+               case OPTION_TOTALS: /* GNU tar */
+                       bsdtar->option_totals++;
+                       break;
+               case 'U': /* GNU tar */
+                       bsdtar->extract_flags |= ARCHIVE_EXTRACT_UNLINK;
+                       bsdtar->option_unlink_first = 1;
+                       break;
+               case 'u': /* SUSv2 */
+                       set_mode(bsdtar, opt);
+                       break;
+               case 'v': /* SUSv2 */
+                       bsdtar->verbose++;
+                       break;
+               case OPTION_VERSION: /* GNU convention */
+                       version();
+                       break;
+#if 0
+               /*
+                * The -W longopt feature is handled inside of
+                * bsdtar_getopt(), so -W is not available here.
+                */
+               case 'W': /* Obscure GNU convention. */
+                       break;
+#endif
+               case 'w': /* SUSv2 */
+                       bsdtar->option_interactive = 1;
+                       break;
+               case 'X': /* GNU tar */
+                       if (lafe_exclude_from_file(&bsdtar->matching, bsdtar->optarg))
+                               lafe_errc(1, 0,
+                                   "failed to process exclusions from file %s",
+                                   bsdtar->optarg);
+                       break;
+               case 'x': /* SUSv2 */
+                       set_mode(bsdtar, opt);
+                       break;
+               case 'y': /* FreeBSD version of GNU tar */
+                       if (bsdtar->create_compression != '\0')
+                               lafe_errc(1, 0,
+                                   "Can't specify both -%c and -%c", opt,
+                                   bsdtar->create_compression);
+                       bsdtar->create_compression = opt;
+                       break;
+               case 'Z': /* GNU tar */
+                       if (bsdtar->create_compression != '\0')
+                               lafe_errc(1, 0,
+                                   "Can't specify both -%c and -%c", opt,
+                                   bsdtar->create_compression);
+                       bsdtar->create_compression = opt;
+                       break;
+               case 'z': /* GNU tar, star, many others */
+                       if (bsdtar->create_compression != '\0')
+                               lafe_errc(1, 0,
+                                   "Can't specify both -%c and -%c", opt,
+                                   bsdtar->create_compression);
+                       bsdtar->create_compression = opt;
+                       break;
+               case OPTION_USE_COMPRESS_PROGRAM:
+                       bsdtar->compress_program = bsdtar->optarg;
+                       break;
+               default:
+                       usage();
+               }
+       }
+
+       /*
+        * Sanity-check options.
+        */
+
+       /* If no "real" mode was specified, treat -h as --help. */
+       if ((bsdtar->mode == '\0') && possible_help_request) {
+               long_help();
+               exit(0);
+       }
+
+       /* Otherwise, a mode is required. */
+       if (bsdtar->mode == '\0')
+               lafe_errc(1, 0,
+                   "Must specify one of -c, -r, -t, -u, -x");
+
+       /* Check boolean options only permitted in certain modes. */
+       if (bsdtar->option_dont_traverse_mounts)
+               only_mode(bsdtar, "--one-file-system", "cru");
+       if (bsdtar->option_fast_read)
+               only_mode(bsdtar, "--fast-read", "xt");
+       if (bsdtar->option_honor_nodump)
+               only_mode(bsdtar, "--nodump", "cru");
+       if (option_o > 0) {
+               switch (bsdtar->mode) {
+               case 'c':
+                       /*
+                        * In GNU tar, -o means "old format."  The
+                        * "ustar" format is the closest thing
+                        * supported by libarchive.
+                        */
+                       bsdtar->create_format = "ustar";
+                       /* TODO: bsdtar->create_format = "v7"; */
+                       break;
+               case 'x':
+                       /* POSIX-compatible behavior. */
+                       bsdtar->option_no_owner = 1;
+                       bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_OWNER;
+                       break;
+               default:
+                       only_mode(bsdtar, "-o", "xc");
+                       break;
+               }
+       }
+       if (bsdtar->option_no_subdirs)
+               only_mode(bsdtar, "-n", "cru");
+       if (bsdtar->option_stdout)
+               only_mode(bsdtar, "-O", "xt");
+       if (bsdtar->option_unlink_first)
+               only_mode(bsdtar, "-U", "x");
+       if (bsdtar->option_warn_links)
+               only_mode(bsdtar, "--check-links", "cr");
+
+       /* Check other parameters only permitted in certain modes. */
+       if (bsdtar->create_compression != '\0') {
+               strcpy(buff, "-?");
+               buff[1] = bsdtar->create_compression;
+               only_mode(bsdtar, buff, "cxt");
+       }
+       if (bsdtar->create_format != NULL)
+               only_mode(bsdtar, "--format", "cru");
+       if (bsdtar->symlink_mode != '\0') {
+               strcpy(buff, "-?");
+               buff[1] = bsdtar->symlink_mode;
+               only_mode(bsdtar, buff, "cru");
+       }
+       if (bsdtar->strip_components != 0)
+               only_mode(bsdtar, "--strip-components", "xt");
+
+       switch(bsdtar->mode) {
+       case 'c':
+               tar_mode_c(bsdtar);
+               break;
+       case 'r':
+               tar_mode_r(bsdtar);
+               break;
+       case 't':
+               tar_mode_t(bsdtar);
+               break;
+       case 'u':
+               tar_mode_u(bsdtar);
+               break;
+       case 'x':
+               tar_mode_x(bsdtar);
+               break;
+       }
+
+       lafe_cleanup_exclusions(&bsdtar->matching);
+#if HAVE_REGEX_H
+       cleanup_substitution(bsdtar);
+#endif
+
+       if (bsdtar->return_value != 0)
+               lafe_warnc(0,
+                   "Error exit delayed from previous errors.");
+       return (bsdtar->return_value);
+}
+
+static void
+set_mode(struct bsdtar *bsdtar, char opt)
+{
+       if (bsdtar->mode != '\0' && bsdtar->mode != opt)
+               lafe_errc(1, 0,
+                   "Can't specify both -%c and -%c", opt, bsdtar->mode);
+       bsdtar->mode = opt;
+}
+
+/*
+ * Verify that the mode is correct.
+ */
+static void
+only_mode(struct bsdtar *bsdtar, const char *opt, const char *valid_modes)
+{
+       if (strchr(valid_modes, bsdtar->mode) == NULL)
+               lafe_errc(1, 0,
+                   "Option %s is not permitted in mode -%c",
+                   opt, bsdtar->mode);
+}
+
+
+void
+usage(void)
+{
+       const char      *p;
+
+       p = lafe_progname;
+
+       fprintf(stderr, "Usage:\n");
+       fprintf(stderr, "  List:    %s -tf <archive-filename>\n", p);
+       fprintf(stderr, "  Extract: %s -xf <archive-filename>\n", p);
+       fprintf(stderr, "  Create:  %s -cf <archive-filename> [filenames...]\n", p);
+       fprintf(stderr, "  Help:    %s --help\n", p);
+       exit(1);
+}
+
+static void
+version(void)
+{
+       printf("bsdtar %s - %s\n",
+           BSDTAR_VERSION_STRING,
+           archive_version());
+       exit(0);
+}
+
+static const char *long_help_msg =
+       "First option must be a mode specifier:\n"
+       "  -c Create  -r Add/Replace  -t List  -u Update  -x Extract\n"
+       "Common Options:\n"
+       "  -b #  Use # 512-byte records per I/O block\n"
+       "  -f <filename>  Location of archive (default " _PATH_DEFTAPE ")\n"
+       "  -v    Verbose\n"
+       "  -w    Interactive\n"
+       "Create: %p -c [options] [<file> | <dir> | @<archive> | -C <dir> ]\n"
+       "  <file>, <dir>  add these items to archive\n"
+       "  -z, -j, -J, --lzma  Compress archive with gzip/bzip2/xz/lzma\n"
+       "  --format {ustar|pax|cpio|shar}  Select archive format\n"
+       "  --exclude <pattern>  Skip files that match pattern\n"
+       "  -C <dir>  Change to <dir> before processing remaining files\n"
+       "  @<archive>  Add entries from <archive> to output\n"
+       "List: %p -t [options] [<patterns>]\n"
+       "  <patterns>  If specified, list only entries that match\n"
+       "Extract: %p -x [options] [<patterns>]\n"
+       "  <patterns>  If specified, extract only entries that match\n"
+       "  -k    Keep (don't overwrite) existing files\n"
+       "  -m    Don't restore modification times\n"
+       "  -O    Write entries to stdout, don't restore to disk\n"
+       "  -p    Restore permissions (including ACLs, owner, file flags)\n";
+
+
+/*
+ * Note that the word 'bsdtar' will always appear in the first line
+ * of output.
+ *
+ * In particular, /bin/sh scripts that need to test for the presence
+ * of bsdtar can use the following template:
+ *
+ * if (tar --help 2>&1 | grep bsdtar >/dev/null 2>&1 ) then \
+ *          echo bsdtar; else echo not bsdtar; fi
+ */
+static void
+long_help(void)
+{
+       const char      *prog;
+       const char      *p;
+
+       prog = lafe_progname;
+
+       fflush(stderr);
+
+       p = (strcmp(prog,"bsdtar") != 0) ? "(bsdtar)" : "";
+       printf("%s%s: manipulate archive files\n", prog, p);
+
+       for (p = long_help_msg; *p != '\0'; p++) {
+               if (*p == '%') {
+                       if (p[1] == 'p') {
+                               fputs(prog, stdout);
+                               p++;
+                       } else
+                               putchar('%');
+               } else
+                       putchar(*p);
+       }
+       version();
+}
diff --git a/commands/bsdtar/bsdtar.h b/commands/bsdtar/bsdtar.h
new file mode 100644 (file)
index 0000000..a2a9b05
--- /dev/null
@@ -0,0 +1,165 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.bin/tar/bsdtar.h,v 1.37 2008/12/06 07:37:14 kientzle Exp $
+ */
+
+#include "bsdtar_platform.h"
+#include <stdio.h>
+
+#include "matching.h"
+
+#define        DEFAULT_BYTES_PER_BLOCK (20*512)
+
+/*
+ * The internal state for the "bsdtar" program.
+ *
+ * Keeping all of the state in a structure like this simplifies memory
+ * leak testing (at exit, anything left on the heap is suspect).  A
+ * pointer to this structure is passed to most bsdtar internal
+ * functions.
+ */
+struct bsdtar {
+       /* Options */
+       const char       *filename; /* -f filename */
+       const char       *create_format; /* -F format */
+       char             *pending_chdir; /* -C dir */
+       const char       *names_from_file; /* -T file */
+       time_t            newer_ctime_sec; /* --newer/--newer-than */
+       long              newer_ctime_nsec; /* --newer/--newer-than */
+       time_t            newer_mtime_sec; /* --newer-mtime */
+       long              newer_mtime_nsec; /* --newer-mtime-than */
+       int               bytes_per_block; /* -b block_size */
+       int               verbose;   /* -v */
+       int               extract_flags; /* Flags for extract operation */
+       int               strip_components; /* Remove this many leading dirs */
+       char              mode; /* Program mode: 'c', 't', 'r', 'u', 'x' */
+       char              symlink_mode; /* H or L, per BSD conventions */
+       char              create_compression; /* j, y, or z */
+       const char       *compress_program;
+       char              option_absolute_paths; /* -P */
+       char              option_chroot; /* --chroot */
+       char              option_dont_traverse_mounts; /* --one-file-system */
+       char              option_fast_read; /* --fast-read */
+       const char       *option_options; /* --options */
+       char              option_honor_nodump; /* --nodump */
+       char              option_interactive; /* -w */
+       char              option_no_owner; /* -o */
+       char              option_no_subdirs; /* -n */
+       char              option_null; /* --null */
+       char              option_numeric_owner; /* --numeric-owner */
+       char              option_stdout; /* -O */
+       char              option_totals; /* --totals */
+       char              option_unlink_first; /* -U */
+       char              option_warn_links; /* --check-links */
+       char              day_first; /* show day before month in -tv output */
+
+       /* If >= 0, then close this when done. */
+       int               fd;
+
+       /* Miscellaneous state information */
+       int               argc;
+       char            **argv;
+       const char       *optarg;
+       size_t            gs_width; /* For 'list_item' in read.c */
+       size_t            u_width; /* for 'list_item' in read.c */
+       uid_t             user_uid; /* UID running this program */
+       int               return_value; /* Value returned by main() */
+       char              warned_lead_slash; /* Already displayed warning */
+       char              next_line_is_dir; /* Used for -C parsing in -cT */
+
+       /*
+        * Data for various subsystems.  Full definitions are located in
+        * the file where they are used.
+        */
+       struct archive          *diskreader;    /* for write.c */
+       struct archive_entry_linkresolver *resolver; /* for write.c */
+       struct archive_dir      *archive_dir;   /* for write.c */
+       struct name_cache       *gname_cache;   /* for write.c */
+       char                    *buff;          /* for write.c */
+       struct lafe_matching    *matching;      /* for matching.c */
+       struct security         *security;      /* for read.c */
+       struct name_cache       *uname_cache;   /* for write.c */
+       struct siginfo_data     *siginfo;       /* for siginfo.c */
+       struct substitution     *substitution;  /* for subst.c */
+};
+
+/* Fake short equivalents for long options that otherwise lack them. */
+enum {
+       OPTION_CHECK_LINKS = 1,
+       OPTION_CHROOT,
+       OPTION_EXCLUDE,
+       OPTION_FORMAT,
+       OPTION_OPTIONS,
+       OPTION_HELP,
+       OPTION_INCLUDE,
+       OPTION_KEEP_NEWER_FILES,
+       OPTION_LZMA,
+       OPTION_NEWER_CTIME,
+       OPTION_NEWER_CTIME_THAN,
+       OPTION_NEWER_MTIME,
+       OPTION_NEWER_MTIME_THAN,
+       OPTION_NODUMP,
+       OPTION_NO_SAME_OWNER,
+       OPTION_NO_SAME_PERMISSIONS,
+       OPTION_NULL,
+       OPTION_NUMERIC_OWNER,
+       OPTION_ONE_FILE_SYSTEM,
+       OPTION_POSIX,
+       OPTION_SAME_OWNER,
+       OPTION_STRIP_COMPONENTS,
+       OPTION_TOTALS,
+       OPTION_USE_COMPRESS_PROGRAM,
+       OPTION_VERSION
+};
+
+
+int    bsdtar_getopt(struct bsdtar *);
+void   do_chdir(struct bsdtar *);
+int    edit_pathname(struct bsdtar *, struct archive_entry *);
+int    need_report(void);
+int    pathcmp(const char *a, const char *b);
+void   safe_fprintf(FILE *, const char *fmt, ...);
+void   set_chdir(struct bsdtar *, const char *newdir);
+#ifndef __minix
+const char *tar_i64toa(int64_t);
+#else
+/* This is not really 64itoa, but it is simpler to do this than replace
+ * tar_i64toa everywhere
+ */
+const char *tar_i64toa(int32_t);
+#endif
+void   tar_mode_c(struct bsdtar *bsdtar);
+void   tar_mode_r(struct bsdtar *bsdtar);
+void   tar_mode_t(struct bsdtar *bsdtar);
+void   tar_mode_u(struct bsdtar *bsdtar);
+void   tar_mode_x(struct bsdtar *bsdtar);
+void   usage(void);
+int    yes(const char *fmt, ...);
+
+#if HAVE_REGEX_H
+void   add_substitution(struct bsdtar *, const char *);
+int    apply_substitution(struct bsdtar *, const char *, char **, int);
+void   cleanup_substitution(struct bsdtar *);
+#endif
diff --git a/commands/bsdtar/bsdtar_platform.h b/commands/bsdtar/bsdtar_platform.h
new file mode 100644 (file)
index 0000000..fce9c99
--- /dev/null
@@ -0,0 +1,132 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.bin/tar/bsdtar_platform.h,v 1.26 2008/12/06 07:37:14 kientzle Exp $
+ */
+
+/*
+ * This header is the first thing included in any of the bsdtar
+ * source files.  As far as possible, platform-specific issues should
+ * be dealt with here and not within individual source files.
+ */
+
+#ifndef BSDTAR_PLATFORM_H_INCLUDED
+#define        BSDTAR_PLATFORM_H_INCLUDED
+
+#if defined(PLATFORM_CONFIG_H)
+/* Use hand-built config.h in environments that need it. */
+#include PLATFORM_CONFIG_H
+#else
+/* Not having a config.h of some sort is a serious problem. */
+#include "config.h"
+#endif
+
+/* Get a real definition for __FBSDID if we can */
+#if HAVE_SYS_CDEFS_H
+#include <sys/cdefs.h>
+#endif
+
+/* If not, define it so as to avoid dangling semicolons. */
+#ifndef __FBSDID
+#define        __FBSDID(a)     struct _undefined_hack
+#endif
+
+#ifdef HAVE_LIBARCHIVE
+/* If we're using the platform libarchive, include system headers. */
+#include <archive.h>
+#include <archive_entry.h>
+#else
+/* Otherwise, include user headers. */
+#include "archive.h"
+#include "archive_entry.h"
+#endif
+
+#ifdef HAVE_LIBACL
+#include <acl/libacl.h>
+#endif
+
+/*
+ * Include "dirent.h" (or it's equivalent on several different platforms).
+ *
+ * This is slightly modified from the GNU autoconf recipe.
+ * In particular, FreeBSD includes d_namlen in it's dirent structure,
+ * so my configure script includes an explicit test for the d_namlen
+ * field.
+ */
+#if HAVE_DIRENT_H
+# include <dirent.h>
+# if HAVE_DIRENT_D_NAMLEN
+#  define DIRENT_NAMLEN(dirent) (dirent)->d_namlen
+# else
+#  define DIRENT_NAMLEN(dirent) strlen((dirent)->d_name)
+# endif
+#else
+# define dirent direct
+# define DIRENT_NAMLEN(dirent) (dirent)->d_namlen
+# if HAVE_SYS_NDIR_H
+#  include <sys/ndir.h>
+# endif
+# if HAVE_SYS_DIR_H
+#  include <sys/dir.h>
+# endif
+# if HAVE_NDIR_H
+#  include <ndir.h>
+# endif
+#endif
+
+#if HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC
+#define        ARCHIVE_STAT_CTIME_NANOS(st)    (st)->st_ctimespec.tv_nsec
+#define        ARCHIVE_STAT_MTIME_NANOS(st)    (st)->st_mtimespec.tv_nsec
+#elif HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
+#define        ARCHIVE_STAT_CTIME_NANOS(st)    (st)->st_ctim.tv_nsec
+#define        ARCHIVE_STAT_MTIME_NANOS(st)    (st)->st_mtim.tv_nsec
+#elif HAVE_STRUCT_STAT_ST_MTIME_N
+#define        ARCHIVE_STAT_CTIME_NANOS(st)    (st)->st_ctime_n
+#define        ARCHIVE_STAT_MTIME_NANOS(st)    (st)->st_mtime_n
+#elif HAVE_STRUCT_STAT_ST_UMTIME
+#define        ARCHIVE_STAT_CTIME_NANOS(st)    (st)->st_uctime * 1000
+#define        ARCHIVE_STAT_MTIME_NANOS(st)    (st)->st_umtime * 1000
+#elif HAVE_STRUCT_STAT_ST_MTIME_USEC
+#define        ARCHIVE_STAT_CTIME_NANOS(st)    (st)->st_ctime_usec * 1000
+#define        ARCHIVE_STAT_MTIME_NANOS(st)    (st)->st_mtime_usec * 1000
+#else
+#define        ARCHIVE_STAT_CTIME_NANOS(st)    (0)
+#define        ARCHIVE_STAT_MTIME_NANOS(st)    (0)
+#endif
+
+/* How to mark functions that don't return. */
+/* This facilitates use of some newer static code analysis tools. */
+#undef __LA_DEAD
+#if defined(__GNUC__) && (__GNUC__ > 2 || \
+                         (__GNUC__ == 2 && __GNUC_MINOR__ >= 5))
+#define        __LA_DEAD       __attribute__((__noreturn__))
+#else
+#define        __LA_DEAD
+#endif
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+#include "bsdtar_windows.h"
+#endif
+
+#endif /* !BSDTAR_PLATFORM_H_INCLUDED */
diff --git a/commands/bsdtar/cmdline.c b/commands/bsdtar/cmdline.c
new file mode 100644 (file)
index 0000000..ba3e8a1
--- /dev/null
@@ -0,0 +1,381 @@
+/*-
+ * Copyright (c) 2003-2008 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Command line parser for tar.
+ */
+
+#include "bsdtar_platform.h"
+__FBSDID("$FreeBSD$");
+
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+
+#include "bsdtar.h"
+#include "err.h"
+
+/*
+ * Short options for tar.  Please keep this sorted.
+ */
+static const char *short_options
+       = "Bb:C:cf:HhI:JjkLlmnOoPpqrSs:T:tUuvW:wX:xyZz";
+
+/*
+ * Long options for tar.  Please keep this list sorted.
+ *
+ * The symbolic names for options that lack a short equivalent are
+ * defined in bsdtar.h.  Also note that so far I've found no need
+ * to support optional arguments to long options.  That would be
+ * a small change to the code below.
+ */
+
+static struct option {
+       const char *name;
+       int required;      /* 1 if this option requires an argument. */
+       int equivalent;    /* Equivalent short option. */
+} tar_longopts[] = {
+       { "absolute-paths",       0, 'P' },
+       { "append",               0, 'r' },
+       { "block-size",           1, 'b' },
+       { "bunzip2",              0, 'j' },
+       { "bzip",                 0, 'j' },
+       { "bzip2",                0, 'j' },
+       { "cd",                   1, 'C' },
+       { "check-links",          0, OPTION_CHECK_LINKS },
+       { "chroot",               0, OPTION_CHROOT },
+       { "compress",             0, 'Z' },
+       { "confirmation",         0, 'w' },
+       { "create",               0, 'c' },
+       { "dereference",          0, 'L' },
+       { "directory",            1, 'C' },
+       { "exclude",              1, OPTION_EXCLUDE },
+       { "exclude-from",         1, 'X' },
+       { "extract",              0, 'x' },
+       { "fast-read",            0, 'q' },
+       { "file",                 1, 'f' },
+       { "files-from",           1, 'T' },
+       { "format",               1, OPTION_FORMAT },
+       { "options",              1, OPTION_OPTIONS },
+       { "gunzip",               0, 'z' },
+       { "gzip",                 0, 'z' },
+       { "help",                 0, OPTION_HELP },
+       { "include",              1, OPTION_INCLUDE },
+       { "interactive",          0, 'w' },
+       { "insecure",             0, 'P' },
+       { "keep-newer-files",     0, OPTION_KEEP_NEWER_FILES },
+       { "keep-old-files",       0, 'k' },
+       { "list",                 0, 't' },
+       { "lzma",                 0, OPTION_LZMA },
+       { "modification-time",    0, 'm' },
+       { "newer",                1, OPTION_NEWER_CTIME },
+       { "newer-ctime",          1, OPTION_NEWER_CTIME },
+       { "newer-ctime-than",     1, OPTION_NEWER_CTIME_THAN },
+       { "newer-mtime",          1, OPTION_NEWER_MTIME },
+       { "newer-mtime-than",     1, OPTION_NEWER_MTIME_THAN },
+       { "newer-than",           1, OPTION_NEWER_CTIME_THAN },
+       { "nodump",               0, OPTION_NODUMP },
+       { "norecurse",            0, 'n' },
+       { "no-recursion",         0, 'n' },
+       { "no-same-owner",        0, OPTION_NO_SAME_OWNER },
+       { "no-same-permissions",  0, OPTION_NO_SAME_PERMISSIONS },
+       { "null",                 0, OPTION_NULL },
+       { "numeric-owner",        0, OPTION_NUMERIC_OWNER },
+       { "one-file-system",      0, OPTION_ONE_FILE_SYSTEM },
+       { "posix",                0, OPTION_POSIX },
+       { "preserve-permissions", 0, 'p' },
+       { "read-full-blocks",     0, 'B' },
+       { "same-owner",           0, OPTION_SAME_OWNER },
+       { "same-permissions",     0, 'p' },
+       { "strip-components",     1, OPTION_STRIP_COMPONENTS },
+       { "to-stdout",            0, 'O' },
+       { "totals",               0, OPTION_TOTALS },
+       { "uncompress",           0, 'Z' },
+       { "unlink",               0, 'U' },
+       { "unlink-first",         0, 'U' },
+       { "update",               0, 'u' },
+       { "use-compress-program", 1, OPTION_USE_COMPRESS_PROGRAM },
+       { "verbose",              0, 'v' },
+       { "version",              0, OPTION_VERSION },
+       { "xz",                   0, 'J' },
+       { NULL, 0, 0 }
+};
+
+/*
+ * This getopt implementation has two key features that common
+ * getopt_long() implementations lack.  Apart from those, it's a
+ * straightforward option parser, considerably simplified by not
+ * needing to support the wealth of exotic getopt_long() features.  It
+ * has, of course, been shamelessly tailored for bsdtar.  (If you're
+ * looking for a generic getopt_long() implementation for your
+ * project, I recommend Gregory Pietsch's public domain getopt_long()
+ * implementation.)  The two additional features are:
+ *
+ * Old-style tar arguments: The original tar implementation treated
+ * the first argument word as a list of single-character option
+ * letters.  All arguments follow as separate words.  For example,
+ *    tar xbf 32 /dev/tape
+ * Here, the "xbf" is three option letters, "32" is the argument for
+ * "b" and "/dev/tape" is the argument for "f".  We support this usage
+ * if the first command-line argument does not begin with '-'.  We
+ * also allow regular short and long options to follow, e.g.,
+ *    tar xbf 32 /dev/tape -P --format=pax
+ *
+ * -W long options: There's an obscure GNU convention (only rarely
+ * supported even there) that allows "-W option=argument" as an
+ * alternative way to support long options.  This was supported in
+ * early bsdtar as a way to access long options on platforms that did
+ * not support getopt_long() and is preserved here for backwards
+ * compatibility.  (Of course, if I'd started with a custom
+ * command-line parser from the beginning, I would have had normal
+ * long option support on every platform so that hack wouldn't have
+ * been necessary.  Oh, well.  Some mistakes you just have to live
+ * with.)
+ *
+ * TODO: We should be able to use this to pull files and intermingled
+ * options (such as -C) from the command line in write mode.  That
+ * will require a little rethinking of the argument handling in
+ * bsdtar.c.
+ *
+ * TODO: If we want to support arbitrary command-line options from -T
+ * input (as GNU tar does), we may need to extend this to handle option
+ * words from sources other than argv/arc.  I'm not really sure if I
+ * like that feature of GNU tar, so it's certainly not a priority.
+ */
+
+int
+bsdtar_getopt(struct bsdtar *bsdtar)
+{
+       enum { state_start = 0, state_old_tar, state_next_word,
+              state_short, state_long };
+       static int state = state_start;
+       static char *opt_word;
+
+       const struct option *popt, *match = NULL, *match2 = NULL;
+       const char *p, *long_prefix = "--";
+       size_t optlength;
+       int opt = '?';
+       int required = 0;
+
+       bsdtar->optarg = NULL;
+
+       /* First time through, initialize everything. */
+       if (state == state_start) {
+               /* Skip program name. */
+               ++bsdtar->argv;
+               --bsdtar->argc;
+               if (*bsdtar->argv == NULL)
+                       return (-1);
+               /* Decide between "new style" and "old style" arguments. */
+               if (bsdtar->argv[0][0] == '-') {
+                       state = state_next_word;
+               } else {
+                       state = state_old_tar;
+                       opt_word = *bsdtar->argv++;
+                       --bsdtar->argc;
+               }
+       }
+
+       /*
+        * We're parsing old-style tar arguments
+        */
+       if (state == state_old_tar) {
+               /* Get the next option character. */
+               opt = *opt_word++;
+               if (opt == '\0') {
+                       /* New-style args can follow old-style. */
+                       state = state_next_word;
+               } else {
+                       /* See if it takes an argument. */
+                       p = strchr(short_options, opt);
+                       if (p == NULL)
+                               return ('?');
+                       if (p[1] == ':') {
+                               bsdtar->optarg = *bsdtar->argv;
+                               if (bsdtar->optarg == NULL) {
+                                       lafe_warnc(0,
+                                           "Option %c requires an argument",
+                                           opt);
+                                       return ('?');
+                               }
+                               ++bsdtar->argv;
+                               --bsdtar->argc;
+                       }
+               }
+       }
+
+       /*
+        * We're ready to look at the next word in argv.
+        */
+       if (state == state_next_word) {
+               /* No more arguments, so no more options. */
+               if (bsdtar->argv[0] == NULL)
+                       return (-1);
+               /* Doesn't start with '-', so no more options. */
+               if (bsdtar->argv[0][0] != '-')
+                       return (-1);
+               /* "--" marks end of options; consume it and return. */
+               if (strcmp(bsdtar->argv[0], "--") == 0) {
+                       ++bsdtar->argv;
+                       --bsdtar->argc;
+                       return (-1);
+               }
+               /* Get next word for parsing. */
+               opt_word = *bsdtar->argv++;
+               --bsdtar->argc;
+               if (opt_word[1] == '-') {
+                       /* Set up long option parser. */
+                       state = state_long;
+                       opt_word += 2; /* Skip leading '--' */
+               } else {
+                       /* Set up short option parser. */
+                       state = state_short;
+                       ++opt_word;  /* Skip leading '-' */
+               }
+       }
+
+       /*
+        * We're parsing a group of POSIX-style single-character options.
+        */
+       if (state == state_short) {
+               /* Peel next option off of a group of short options. */
+               opt = *opt_word++;
+               if (opt == '\0') {
+                       /* End of this group; recurse to get next option. */
+                       state = state_next_word;
+                       return bsdtar_getopt(bsdtar);
+               }
+
+               /* Does this option take an argument? */
+               p = strchr(short_options, opt);
+               if (p == NULL)
+                       return ('?');
+               if (p[1] == ':')
+                       required = 1;
+
+               /* If it takes an argument, parse that. */
+               if (required) {
+                       /* If arg is run-in, opt_word already points to it. */
+                       if (opt_word[0] == '\0') {
+                               /* Otherwise, pick up the next word. */
+                               opt_word = *bsdtar->argv;
+                               if (opt_word == NULL) {
+                                       lafe_warnc(0,
+                                           "Option -%c requires an argument",
+                                           opt);
+                                       return ('?');
+                               }
+                               ++bsdtar->argv;
+                               --bsdtar->argc;
+                       }
+                       if (opt == 'W') {
+                               state = state_long;
+                               long_prefix = "-W "; /* For clearer errors. */
+                       } else {
+                               state = state_next_word;
+                               bsdtar->optarg = opt_word;
+                       }
+               }
+       }
+
+       /* We're reading a long option, including -W long=arg convention. */
+       if (state == state_long) {
+               /* After this long option, we'll be starting a new word. */
+               state = state_next_word;
+
+               /* Option name ends at '=' if there is one. */
+               p = strchr(opt_word, '=');
+               if (p != NULL) {
+                       optlength = (size_t)(p - opt_word);
+                       bsdtar->optarg = (char *)(uintptr_t)(p + 1);
+               } else {
+                       optlength = strlen(opt_word);
+               }
+
+               /* Search the table for an unambiguous match. */
+               for (popt = tar_longopts; popt->name != NULL; popt++) {
+                       /* Short-circuit if first chars don't match. */
+                       if (popt->name[0] != opt_word[0])
+                               continue;
+                       /* If option is a prefix of name in table, record it.*/
+                       if (strncmp(opt_word, popt->name, optlength) == 0) {
+                               match2 = match; /* Record up to two matches. */
+                               match = popt;
+                               /* If it's an exact match, we're done. */
+                               if (strlen(popt->name) == optlength) {
+                                       match2 = NULL; /* Forget the others. */
+                                       break;
+                               }
+                       }
+               }
+
+               /* Fail if there wasn't a unique match. */
+               if (match == NULL) {
+                       lafe_warnc(0,
+                           "Option %s%s is not supported",
+                           long_prefix, opt_word);
+                       return ('?');
+               }
+               if (match2 != NULL) {
+                       lafe_warnc(0,
+                           "Ambiguous option %s%s (matches --%s and --%s)",
+                           long_prefix, opt_word, match->name, match2->name);
+                       return ('?');
+               }
+
+               /* We've found a unique match; does it need an argument? */
+               if (match->required) {
+                       /* Argument required: get next word if necessary. */
+                       if (bsdtar->optarg == NULL) {
+                               bsdtar->optarg = *bsdtar->argv;
+                               if (bsdtar->optarg == NULL) {
+                                       lafe_warnc(0,
+                                           "Option %s%s requires an argument",
+                                           long_prefix, match->name);
+                                       return ('?');
+                               }
+                               ++bsdtar->argv;
+                               --bsdtar->argc;
+                       }
+               } else {
+                       /* Argument forbidden: fail if there is one. */
+                       if (bsdtar->optarg != NULL) {
+                               lafe_warnc(0,
+                                   "Option %s%s does not allow an argument",
+                                   long_prefix, match->name);
+                               return ('?');
+                       }
+               }
+               return (match->equivalent);
+       }
+
+       return (opt);
+}
diff --git a/commands/bsdtar/config.h b/commands/bsdtar/config.h
new file mode 100644 (file)
index 0000000..bc08082
--- /dev/null
@@ -0,0 +1,763 @@
+/* config.h.  Generated from config.h.in by configure.  */
+/* config.h.in.  Generated from configure.ac by autoheader.  */
+
+/* Version number of bsdcpio */
+#define BSDCPIO_VERSION_STRING "2.8.3"
+
+/* Version number of bsdtar */
+#define BSDTAR_VERSION_STRING "2.8.3"
+
+/* Define to 1 if you have the `acl_create_entry' function. */
+/* #undef HAVE_ACL_CREATE_ENTRY */
+
+/* Define to 1 if you have the `acl_get_link' function. */
+/* #undef HAVE_ACL_GET_LINK */
+
+/* Define to 1 if you have the `acl_get_link_np' function. */
+/* #undef HAVE_ACL_GET_LINK_NP */
+
+/* Define to 1 if you have the `acl_get_perm' function. */
+/* #undef HAVE_ACL_GET_PERM */
+
+/* Define to 1 if you have the `acl_get_perm_np' function. */
+/* #undef HAVE_ACL_GET_PERM_NP */
+
+/* Define to 1 if you have the `acl_init' function. */
+/* #undef HAVE_ACL_INIT */
+
+/* Define to 1 if you have the <acl/libacl.h> header file. */
+/* #undef HAVE_ACL_LIBACL_H */
+
+/* Define to 1 if the system has the type `acl_permset_t'. */
+/* #undef HAVE_ACL_PERMSET_T */
+
+/* Define to 1 if you have the `acl_set_fd' function. */
+/* #undef HAVE_ACL_SET_FD */
+
+/* Define to 1 if you have the `acl_set_fd_np' function. */
+/* #undef HAVE_ACL_SET_FD_NP */
+
+/* Define to 1 if you have the `acl_set_file' function. */
+/* #undef HAVE_ACL_SET_FILE */
+
+/* True for systems with POSIX ACL support */
+/* #undef HAVE_ACL_USER */
+
+/* Define to 1 if you have the <attr/xattr.h> header file. */
+/* #undef HAVE_ATTR_XATTR_H */
+
+/* Define to 1 if you have the <bzlib.h> header file. */
+#define HAVE_BZLIB_H 1
+
+/* Define to 1 if you have the `chflags' function. */
+/* #undef HAVE_CHFLAGS */
+
+/* Define to 1 if you have the `chown' function. */
+#define HAVE_CHOWN 1
+
+/* Define to 1 if you have the `chroot' function. */
+#define HAVE_CHROOT 1
+
+/* Define to 1 if you have the <ctype.h> header file. */
+#define HAVE_CTYPE_H 1
+
+/* Define to 1 if you have the `cygwin_conv_path' function. */
+/* #undef HAVE_CYGWIN_CONV_PATH */
+
+/* Define to 1 if you have the declaration of `INT64_MAX', and to 0 if you
+   don't. */
+#define HAVE_DECL_INT64_MAX 0
+
+/* Define to 1 if you have the declaration of `INT64_MIN', and to 0 if you
+   don't. */
+#define HAVE_DECL_INT64_MIN 0
+
+/* Define to 1 if you have the declaration of `SIZE_MAX', and to 0 if you
+   don't. */
+#define HAVE_DECL_SIZE_MAX 1
+
+/* Define to 1 if you have the declaration of `SSIZE_MAX', and to 0 if you
+   don't. */
+#define HAVE_DECL_SSIZE_MAX 1
+
+/* Define to 1 if you have the declaration of `strerror_r', and to 0 if you
+   don't. */
+#define HAVE_DECL_STRERROR_R 0
+
+/* Define to 1 if you have the declaration of `UINT32_MAX', and to 0 if you
+   don't. */
+#define HAVE_DECL_UINT32_MAX 1
+
+/* Define to 1 if you have the declaration of `UINT64_MAX', and to 0 if you
+   don't. */
+#define HAVE_DECL_UINT64_MAX 0
+
+/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'.
+   */
+#define HAVE_DIRENT_H 1
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+/* #undef HAVE_DLFCN_H */
+
+/* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */
+#define HAVE_DOPRNT 1
+
+/* Define to 1 if nl_langinfo supports D_MD_ORDER */
+/* #undef HAVE_D_MD_ORDER */
+
+/* A possible errno value for invalid file format errors */
+/* #undef HAVE_EFTYPE */
+
+/* A possible errno value for invalid file format errors */
+#define HAVE_EILSEQ 1
+
+/* Define to 1 if you have the <errno.h> header file. */
+#define HAVE_ERRNO_H 1
+
+/* Define to 1 if you have the <expat.h> header file. */
+/* #undef HAVE_EXPAT_H */
+
+/* Define to 1 if you have the <ext2fs/ext2_fs.h> header file. */
+/* #undef HAVE_EXT2FS_EXT2_FS_H */
+
+/* Define to 1 if you have the `extattr_get_file' function. */
+/* #undef HAVE_EXTATTR_GET_FILE */
+
+/* Define to 1 if you have the `extattr_list_file' function. */
+/* #undef HAVE_EXTATTR_LIST_FILE */
+
+/* Define to 1 if you have the `extattr_set_fd' function. */
+/* #undef HAVE_EXTATTR_SET_FD */
+
+/* Define to 1 if you have the `extattr_set_file' function. */
+/* #undef HAVE_EXTATTR_SET_FILE */
+
+/* Define to 1 if you have the `fchdir' function. */
+#define HAVE_FCHDIR 1
+
+/* Define to 1 if you have the `fchflags' function. */
+/* #undef HAVE_FCHFLAGS */
+
+/* Define to 1 if you have the `fchmod' function. */
+#define HAVE_FCHMOD 1
+
+/* Define to 1 if you have the `fchown' function. */
+#define HAVE_FCHOWN 1
+
+/* Define to 1 if you have the `fcntl' function. */
+#define HAVE_FCNTL 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the `fork' function. */
+#define HAVE_FORK 1
+
+/* Define to 1 if fseeko (and presumably ftello) exists and is declared. */
+/* #undef HAVE_FSEEKO */
+
+/* Define to 1 if you have the `fsetxattr' function. */
+/* #undef HAVE_FSETXATTR */
+
+/* Define to 1 if you have the `fstat' function. */
+#define HAVE_FSTAT 1
+
+/* Define to 1 if you have the `ftruncate' function. */
+#define HAVE_FTRUNCATE 1
+
+/* Define to 1 if you have the `futimens' function. */
+/* #undef HAVE_FUTIMENS */
+
+/* Define to 1 if you have the `futimes' function. */
+/* #undef HAVE_FUTIMES */
+
+/* Define to 1 if you have the `geteuid' function. */
+#define HAVE_GETEUID 1
+
+/* Define to 1 if you have the `getgrgid_r' function. */
+/* #undef HAVE_GETGRGID_R */
+
+/* Define to 1 if you have the `getgrnam_r' function. */
+/* #undef HAVE_GETGRNAM_R */
+
+/* Define to 1 if you have the `getpid' function. */
+#define HAVE_GETPID 1
+
+/* Define to 1 if you have the `getpwnam_r' function. */
+/* #undef HAVE_GETPWNAM_R */
+
+/* Define to 1 if you have the `getpwuid_r' function. */
+/* #undef HAVE_GETPWUID_R */
+
+/* Define to 1 if you have the `getxattr' function. */
+/* #undef HAVE_GETXATTR */
+
+/* Define to 1 if you have the <grp.h> header file. */
+#define HAVE_GRP_H 1
+
+/* Define to 1 if the system has the type `intmax_t'. */
+#define HAVE_INTMAX_T 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the <io.h> header file. */
+/* #undef HAVE_IO_H */
+
+/* Define to 1 if you have the <langinfo.h> header file. */
+/* #undef HAVE_LANGINFO_H */
+
+/* Define to 1 if you have the `lchflags' function. */
+/* #undef HAVE_LCHFLAGS */
+
+/* Define to 1 if you have the `lchmod' function. */
+/* #undef HAVE_LCHMOD */
+
+/* Define to 1 if you have the `lchown' function. */
+/* #undef HAVE_LCHOWN */
+
+/* Define to 1 if you have the `lgetxattr' function. */
+/* #undef HAVE_LGETXATTR */
+
+/* Define to 1 if you have the `acl' library (-lacl). */
+/* #undef HAVE_LIBACL */
+
+/* Define to 1 if you have the `attr' library (-lattr). */
+/* #undef HAVE_LIBATTR */
+
+/* Define to 1 if you have the `bz2' library (-lbz2). */
+#define HAVE_LIBBZ2 1
+
+/* Define to 1 if you have the `expat' library (-lexpat). */
+/* #undef HAVE_LIBEXPAT */
+
+/* Define to 1 if you have the `lzma' library (-llzma). */
+/* #undef HAVE_LIBLZMA */
+
+/* Define to 1 if you have the `lzmadec' library (-llzmadec). */
+/* #undef HAVE_LIBLZMADEC */
+
+/* Define to 1 if you have the `xml2' library (-lxml2). */
+/* #undef HAVE_LIBXML2 */
+
+/* Define to 1 if you have the <libxml/xmlreader.h> header file. */
+/* #undef HAVE_LIBXML_XMLREADER_H */
+
+/* Define to 1 if you have the `z' library (-lz). */
+#define HAVE_LIBZ 1
+
+/* Define to 1 if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the `link' function. */
+#define HAVE_LINK 1
+
+/* Define to 1 if you have the <linux/fs.h> header file. */
+/* #undef HAVE_LINUX_FS_H */
+
+/* Define to 1 if you have the `listxattr' function. */
+/* #undef HAVE_LISTXATTR */
+
+/* Define to 1 if you have the `llistxattr' function. */
+/* #undef HAVE_LLISTXATTR */
+
+/* Define to 1 if you have the <locale.h> header file. */
+#define HAVE_LOCALE_H 1
+
+/* Define to 1 if the system has the type `long long int'. */
+/* #undef HAVE_LONG_LONG_INT */
+
+/* Define to 1 if you have the `lsetxattr' function. */
+/* #undef HAVE_LSETXATTR */
+
+/* Define to 1 if you have the `lstat' function. */
+#define HAVE_LSTAT 1
+
+/* Define to 1 if `lstat' has the bug that it succeeds when given the
+   zero-length file name argument. */
+/* #undef HAVE_LSTAT_EMPTY_STRING_BUG */
+
+/* Define to 1 if you have the `lutimes' function. */
+/* #undef HAVE_LUTIMES */
+
+/* Define to 1 if you have the <lzmadec.h> header file. */
+/* #undef HAVE_LZMADEC_H */
+
+/* Define to 1 if you have the <lzma.h> header file. */
+/* #undef HAVE_LZMA_H */
+
+/* Define to 1 if you have the `MD5Init' function. */
+/* #undef HAVE_MD5INIT */
+
+/* Define to 1 if you have the <md5.h> header file. */
+/* #undef HAVE_MD5_H */
+
+/* Define to 1 if you have the `memmove' function. */
+#define HAVE_MEMMOVE 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+/* #undef HAVE_MEMORY_H */
+
+/* Define to 1 if you have the `memset' function. */
+#define HAVE_MEMSET 1
+
+/* Define to 1 if you have the `mkdir' function. */
+#define HAVE_MKDIR 1
+
+/* Define to 1 if you have the `mkfifo' function. */
+#define HAVE_MKFIFO 1
+
+/* Define to 1 if you have the `mknod' function. */
+#define HAVE_MKNOD 1
+
+/* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */
+/* #undef HAVE_NDIR_H */
+
+/* Define to 1 if you have the `nl_langinfo' function. */
+/* #undef HAVE_NL_LANGINFO */
+
+/* Define to 1 if you have the <openssl/md5.h> header file. */
+/* #undef HAVE_OPENSSL_MD5_H */
+
+/* Define to 1 if you have the <openssl/ripemd.h> header file. */
+/* #undef HAVE_OPENSSL_RIPEMD_H */
+
+/* Define to 1 if your openssl has the `SHA256_Init' function. */
+/* #undef HAVE_OPENSSL_SHA256_INIT */
+
+/* Define to 1 if your openssl has the `SHA384_Init' function. */
+/* #undef HAVE_OPENSSL_SHA384_INIT */
+
+/* Define to 1 if your openssl has the `SHA512_Init' function. */
+/* #undef HAVE_OPENSSL_SHA512_INIT */
+
+/* Define to 1 if you have the <openssl/sha.h> header file. */
+/* #undef HAVE_OPENSSL_SHA_H */
+
+/* Define to 1 if you have the <paths.h> header file. */
+/* #undef HAVE_PATHS_H */
+
+/* Define to 1 if you have the `pipe' function. */
+#define HAVE_PIPE 1
+
+/* Define to 1 if you have the `poll' function. */
+/* #undef HAVE_POLL */
+
+/* Define to 1 if you have the <poll.h> header file. */
+/* #undef HAVE_POLL_H */
+
+/* Define to 1 if you have the <pwd.h> header file. */
+#define HAVE_PWD_H 1
+
+/* Define to 1 if you have the `readlink' function. */
+#define HAVE_READLINK 1
+
+/* Define to 1 if you have the <regex.h> header file. */
+#define HAVE_REGEX_H 1
+
+/* Define to 1 if you have the <ripemd.h> header file. */
+/* #undef HAVE_RIPEMD_H */
+
+/* Define to 1 if you have the `RMD160Init' function. */
+/* #undef HAVE_RMD160INIT */
+
+/* Define to 1 if you have the <rmd160.h> header file. */
+/* #undef HAVE_RMD160_H */
+
+/* Define to 1 if you have the `select' function. */
+#define HAVE_SELECT 1
+
+/* Define to 1 if you have the `setenv' function. */
+#define HAVE_SETENV 1
+
+/* Define to 1 if you have the `setlocale' function. */
+#define HAVE_SETLOCALE 1
+
+/* Define to 1 if you have the `SHA1Init' function. */
+/* #undef HAVE_SHA1INIT */
+
+/* Define to 1 if you have the <sha1.h> header file. */
+/* #undef HAVE_SHA1_H */
+
+/* Define to 1 if you have the `SHA256Init' function. */
+/* #undef HAVE_SHA256INIT */
+
+/* Define to 1 if you have the <sha256.h> header file. */
+/* #undef HAVE_SHA256_H */
+
+/* Define to 1 if you have the `SHA256_Init' function. */
+/* #undef HAVE_SHA256_INIT */
+
+/* Define to 1 if you have the <sha2.h> header file. */
+/* #undef HAVE_SHA2_H */
+
+/* Define to 1 if you have the `SHA384Init' function. */
+/* #undef HAVE_SHA384INIT */
+
+/* Define to 1 if you have the `SHA384_Init' function. */
+/* #undef HAVE_SHA384_INIT */
+
+/* Define to 1 if you have the `SHA512Init' function. */
+/* #undef HAVE_SHA512INIT */
+
+/* Define to 1 if you have the `SHA512_Init' function. */
+/* #undef HAVE_SHA512_INIT */
+
+/* Define to 1 if you have the <sha.h> header file. */
+/* #undef HAVE_SHA_H */
+
+/* Define to 1 if you have the `sigaction' function. */
+#define HAVE_SIGACTION 1
+
+/* Define to 1 if you have the <signal.h> header file. */
+#define HAVE_SIGNAL_H 1
+
+/* Define to 1 if `stat' has the bug that it succeeds when given the
+   zero-length file name argument. */
+/* #undef HAVE_STAT_EMPTY_STRING_BUG */
+
+/* Define to 1 if you have the <stdarg.h> header file. */
+#define HAVE_STDARG_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `strchr' function. */
+#define HAVE_STRCHR 1
+
+/* Define to 1 if you have the `strdup' function. */
+#define HAVE_STRDUP 1
+
+/* Define to 1 if you have the `strerror' function. */
+#define HAVE_STRERROR 1
+
+/* Define to 1 if you have the `strerror_r' function. */
+/* #undef HAVE_STRERROR_R */
+
+/* Define to 1 if you have the `strftime' function. */
+#define HAVE_STRFTIME 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strncpy_s' function. */
+/* #undef HAVE_STRNCPY_S */
+
+/* Define to 1 if you have the `strrchr' function. */
+#define HAVE_STRRCHR 1
+
+/* Define to 1 if `st_birthtime' is a member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_BIRTHTIME */
+
+/* Define to 1 if `st_birthtimespec.tv_nsec' is a member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC_TV_NSEC */
+
+/* Define to 1 if `st_blksize' is a member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_BLKSIZE */
+
+/* Define to 1 if `st_flags' is a member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_FLAGS */
+
+/* Define to 1 if `st_mtimespec.tv_nsec' is a member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC */
+
+/* Define to 1 if `st_mtime_n' is a member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_MTIME_N */
+
+/* Define to 1 if `st_mtime_usec' is a member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_MTIME_USEC */
+
+/* Define to 1 if `st_mtim.tv_nsec' is a member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC */
+
+/* Define to 1 if `st_umtime' is a member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_UMTIME */
+
+/* Define to 1 if you have the `symlink' function. */
+#define HAVE_SYMLINK 1
+
+/* Define to 1 if you have the <sys/acl.h> header file. */
+/* #undef HAVE_SYS_ACL_H */
+
+/* Define to 1 if you have the <sys/cdefs.h> header file. */
+#define HAVE_SYS_CDEFS_H 1
+
+/* Define to 1 if you have the <sys/dir.h> header file, and it defines `DIR'.
+   */
+/* #undef HAVE_SYS_DIR_H */
+
+/* Define to 1 if you have the <sys/extattr.h> header file. */
+/* #undef HAVE_SYS_EXTATTR_H */
+
+/* Define to 1 if you have the <sys/ioctl.h> header file. */
+#define HAVE_SYS_IOCTL_H 1
+
+/* Define to 1 if you have the <sys/mkdev.h> header file. */
+/* #undef HAVE_SYS_MKDEV_H */
+
+/* Define to 1 if you have the <sys/ndir.h> header file, and it defines `DIR'.
+   */
+/* #undef HAVE_SYS_NDIR_H */
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#define HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the <sys/poll.h> header file. */
+/* #undef HAVE_SYS_POLL_H */
+
+/* Define to 1 if you have the <sys/select.h> header file. */
+#define HAVE_SYS_SELECT_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <sys/utime.h> header file. */
+/* #undef HAVE_SYS_UTIME_H */
+
+/* Define to 1 if you have <sys/wait.h> that is POSIX.1 compatible. */
+#define HAVE_SYS_WAIT_H 1
+
+/* Define to 1 if you have the <sys/xattr.h> header file. */
+/* #undef HAVE_SYS_XATTR_H */
+
+/* Define to 1 if you have the `timegm' function. */
+#define HAVE_TIMEGM 1
+
+/* Define to 1 if you have the <time.h> header file. */
+#define HAVE_TIME_H 1
+
+/* Define to 1 if you have the `tzset' function. */
+#define HAVE_TZSET 1
+
+/* Define to 1 if the system has the type `uintmax_t'. */
+#define HAVE_UINTMAX_T 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the `unsetenv' function. */
+#define HAVE_UNSETENV 1
+
+/* Define to 1 if the system has the type `unsigned long long'. */
+/* #undef HAVE_UNSIGNED_LONG_LONG */
+
+/* Define to 1 if the system has the type `unsigned long long int'. */
+/* #undef HAVE_UNSIGNED_LONG_LONG_INT */
+
+/* Define to 1 if you have the `utime' function. */
+#define HAVE_UTIME 1
+
+/* Define to 1 if you have the `utimensat' function. */
+/* #undef HAVE_UTIMENSAT */
+
+/* Define to 1 if you have the `utimes' function. */
+/* #undef HAVE_UTIMES */
+
+/* Define to 1 if you have the <utime.h> header file. */
+#define HAVE_UTIME_H 1
+
+/* Define to 1 if you have the `vfork' function. */
+/* #undef HAVE_VFORK */
+
+/* Define to 1 if you have the `vprintf' function. */
+#define HAVE_VPRINTF 1
+
+/* Define to 1 if you have the <wchar.h> header file. */
+#define HAVE_WCHAR_H 1
+
+/* Define to 1 if the system has the type `wchar_t'. */
+#define HAVE_WCHAR_T 1
+
+/* Define to 1 if you have the `wcrtomb' function. */
+/* #undef HAVE_WCRTOMB */
+
+/* Define to 1 if you have the `wcscpy' function. */
+#define HAVE_WCSCPY 1
+
+/* Define to 1 if you have the `wcslen' function. */
+#define HAVE_WCSLEN 1
+
+/* Define to 1 if you have the `wctomb' function. */
+#define HAVE_WCTOMB 1
+
+/* Define to 1 if you have the <wctype.h> header file. */
+/* #undef HAVE_WCTYPE_H */
+
+/* Define to 1 if you have the <windows.h> header file. */
+/* #undef HAVE_WINDOWS_H */
+
+/* Define to 1 if you have the `wmemcmp' function. */
+#define HAVE_WMEMCMP 1
+
+/* Define to 1 if you have the `wmemcpy' function. */
+#define HAVE_WMEMCPY 1
+
+/* Define to 1 if you have the <zlib.h> header file. */
+#define HAVE_ZLIB_H 1
+
+/* Version number of libarchive as a single integer */
+#define LIBARCHIVE_VERSION_NUMBER "2008003"
+
+/* Version number of libarchive */
+#define LIBARCHIVE_VERSION_STRING "2.8.3"
+
+/* Define to 1 if `lstat' dereferences a symlink specified with a trailing
+   slash. */
+#define LSTAT_FOLLOWS_SLASHED_SYMLINK 1
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+   */
+#define LT_OBJDIR ".libs/"
+
+/* Define to 1 if `major', `minor', and `makedev' are declared in <mkdev.h>.
+   */
+/* #undef MAJOR_IN_MKDEV */
+
+/* Define to 1 if `major', `minor', and `makedev' are declared in
+   <sysmacros.h>. */
+/* #undef MAJOR_IN_SYSMACROS */
+
+/* Define to 1 if your C compiler doesn't accept -c and -o together. */
+/* #undef NO_MINUS_C_MINUS_O */
+
+/* Name of package */
+#define PACKAGE "libarchive"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "kientzle@freebsd.org"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "libarchive"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "libarchive 2.8.3"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "libarchive"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "2.8.3"
+
+/* The size of `wchar_t', as computed by sizeof. */
+#define SIZEOF_WCHAR_T 1
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define to 1 if strerror_r returns char *. */
+/* #undef STRERROR_R_CHAR_P */
+
+/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
+#define TIME_WITH_SYS_TIME 1
+
+/* Enable extensions on AIX 3, Interix.  */
+#ifndef _ALL_SOURCE
+# define _ALL_SOURCE 1
+#endif
+/* Enable GNU extensions on systems that have them.  */
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE 1
+#endif
+/* Enable threading extensions on Solaris.  */
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# define _POSIX_PTHREAD_SEMANTICS 1
+#endif
+/* Enable extensions on HP NonStop.  */
+#ifndef _TANDEM_SOURCE
+# define _TANDEM_SOURCE 1
+#endif
+/* Enable general extensions on Solaris.  */
+#ifndef __EXTENSIONS__
+# define __EXTENSIONS__ 1
+#endif
+
+
+/* Version number of package */
+#define VERSION "2.8.3"
+
+/* Define to '0x0500' for Windows 2000 APIs. */
+/* #undef WINVER */
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+/* #undef _FILE_OFFSET_BITS */
+
+/* Define to 1 to make fseeko visible on some hosts (e.g. glibc 2.2). */
+/* #undef _LARGEFILE_SOURCE */
+
+/* Define for large files, on AIX-style hosts. */
+/* #undef _LARGE_FILES */
+
+/* Define to 1 if on MINIX. */
+#define _MINIX 1
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+   this defined. */
+#define _POSIX_1_SOURCE 2
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+#define _POSIX_SOURCE 1
+
+/* Define for Solaris 2.5.1 so the uint64_t typedef from <sys/synch.h>,
+   <pthread.h>, or <semaphore.h> is not used. If the typedef were allowed, the
+   #define below would cause a syntax error. */
+/* #undef _UINT64_T */
+
+/* Define to '0x0500' for Windows 2000 APIs. */
+/* #undef _WIN32_WINNT */
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to match typeof st_gid field of struct stat if <sys/types.h> doesn't
+   define. */
+/* #undef gid_t */
+
+/* Define to `unsigned long' if <sys/types.h> does not define. */
+#define id_t unsigned long
+
+/* Define to the type of a signed integer type of width exactly 64 bits if
+   such a type exists and the standard includes do not define it. */
+/* #undef int64_t */
+
+/* Define to the widest signed integer type if <stdint.h> and <inttypes.h> do
+   not define. */
+/* #undef intmax_t */
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef mode_t */
+
+/* Define to `long long' if <sys/types.h> does not define. */
+/* #undef off_t */
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+/* #undef size_t */
+
+/* Define to match typeof st_uid field of struct stat if <sys/types.h> doesn't
+   define. */
+/* #undef uid_t */
+
+/* Define to the type of an unsigned integer type of width exactly 64 bits if
+   such a type exists and the standard includes do not define it. */
+/* #undef uint64_t */
+
+/* Define to the widest unsigned integer type if <stdint.h> and <inttypes.h>
+   do not define. */
+/* #undef uintmax_t */
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+/* #undef uintptr_t */
diff --git a/commands/bsdtar/getdate.c b/commands/bsdtar/getdate.c
new file mode 100644 (file)
index 0000000..ffaa679
--- /dev/null
@@ -0,0 +1,1037 @@
+/*
+ * This code is in the public domain and has no copyright.
+ *
+ * This is a plain C recursive-descent translation of an old
+ * public-domain YACC grammar that has been used for parsing dates in
+ * very many open-source projects.
+ *
+ * Since the original authors were generous enough to donate their
+ * work to the public domain, I feel compelled to match their
+ * generosity.
+ *
+ * Tim Kientzle, February 2009.
+ */
+
+/*
+ * Header comment from original getdate.y:
+ */
+
+/*
+**  Originally written by Steven M. Bellovin <smb@research.att.com> while
+**  at the University of North Carolina at Chapel Hill.  Later tweaked by
+**  a couple of people on Usenet.  Completely overhauled by Rich $alz
+**  <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990;
+**
+**  This grammar has 10 shift/reduce conflicts.
+**
+**  This code is in the public domain and has no copyright.
+*/
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+#endif
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+/* This file defines a single public function. */
+time_t get_date(time_t now, char *);
+
+/* Basic time units. */
+#define        EPOCH           1970
+#define        MINUTE          (60L)
+#define        HOUR            (60L * MINUTE)
+#define        DAY             (24L * HOUR)
+
+/* Daylight-savings mode:  on, off, or not yet known. */
+enum DSTMODE { DSTon, DSToff, DSTmaybe };
+/* Meridian:  am or pm. */
+enum { tAM, tPM };
+/* Token types returned by nexttoken() */
+enum { tAGO = 260, tDAY, tDAYZONE, tAMPM, tMONTH, tMONTH_UNIT, tSEC_UNIT,
+       tUNUMBER, tZONE, tDST };
+struct token { int token; time_t value; };
+
+/*
+ * Parser state.
+ */
+struct gdstate {
+       struct token *tokenp; /* Pointer to next token. */
+       /* HaveXxxx counts how many of this kind of phrase we've seen;
+        * it's a fatal error to have more than one time, zone, day,
+        * or date phrase. */
+       int     HaveYear;
+       int     HaveMonth;
+       int     HaveDay;
+       int     HaveWeekDay; /* Day of week */
+       int     HaveTime; /* Hour/minute/second */
+       int     HaveZone; /* timezone and/or DST info */
+       int     HaveRel; /* time offset; we can have more than one */
+       /* Absolute time values. */
+       time_t  Timezone;  /* Seconds offset from GMT */
+       time_t  Day;
+       time_t  Hour;
+       time_t  Minutes;
+       time_t  Month;
+       time_t  Seconds;
+       time_t  Year;
+       /* DST selection */
+       enum DSTMODE    DSTmode;
+       /* Day of week accounting, e.g., "3rd Tuesday" */
+       time_t  DayOrdinal; /* "3" in "3rd Tuesday" */
+       time_t  DayNumber; /* "Tuesday" in "3rd Tuesday" */
+       /* Relative time values: hour/day/week offsets are measured in
+        * seconds, month/year are counted in months. */
+       time_t  RelMonth;
+       time_t  RelSeconds;
+};
+
+/*
+ * A series of functions that recognize certain common time phrases.
+ * Each function returns 1 if it managed to make sense of some of the
+ * tokens, zero otherwise.
+ */
+
+/*
+ *  hour:minute or hour:minute:second with optional AM, PM, or numeric
+ *  timezone offset
+ */
+static int
+timephrase(struct gdstate *gds)
+{
+       if (gds->tokenp[0].token == tUNUMBER
+           && gds->tokenp[1].token == ':'
+           && gds->tokenp[2].token == tUNUMBER
+           && gds->tokenp[3].token == ':'
+           && gds->tokenp[4].token == tUNUMBER) {
+               /* "12:14:18" or "22:08:07" */
+               ++gds->HaveTime;
+               gds->Hour = gds->tokenp[0].value;
+               gds->Minutes = gds->tokenp[2].value;
+               gds->Seconds = gds->tokenp[4].value;
+               gds->tokenp += 5;
+       }
+       else if (gds->tokenp[0].token == tUNUMBER
+           && gds->tokenp[1].token == ':'
+           && gds->tokenp[2].token == tUNUMBER) {
+               /* "12:14" or "22:08" */
+               ++gds->HaveTime;
+               gds->Hour = gds->tokenp[0].value;
+               gds->Minutes = gds->tokenp[2].value;
+               gds->Seconds = 0;
+               gds->tokenp += 3;
+       }
+       else if (gds->tokenp[0].token == tUNUMBER
+           && gds->tokenp[1].token == tAMPM) {
+               /* "7" is a time if it's followed by "am" or "pm" */
+               ++gds->HaveTime;
+               gds->Hour = gds->tokenp[0].value;
+               gds->Minutes = gds->Seconds = 0;
+               /* We'll handle the AM/PM below. */
+               gds->tokenp += 1;
+       } else {
+               /* We can't handle this. */
+               return 0;
+       }
+
+       if (gds->tokenp[0].token == tAMPM) {
+               /* "7:12pm", "12:20:13am" */
+               if (gds->Hour == 12)
+                       gds->Hour = 0;
+               if (gds->tokenp[0].value == tPM)
+                       gds->Hour += 12;
+               gds->tokenp += 1;
+       }
+       if (gds->tokenp[0].token == '+'
+           && gds->tokenp[1].token == tUNUMBER) {
+               /* "7:14+0700" */
+               gds->HaveZone++;
+               gds->DSTmode = DSToff;
+               gds->Timezone = - ((gds->tokenp[1].value / 100) * HOUR
+                   + (gds->tokenp[1].value % 100) * MINUTE);
+               gds->tokenp += 2;
+       }
+       if (gds->tokenp[0].token == '-'
+           && gds->tokenp[1].token == tUNUMBER) {
+               /* "19:14:12-0530" */
+               gds->HaveZone++;
+               gds->DSTmode = DSToff;
+               gds->Timezone = + ((gds->tokenp[1].value / 100) * HOUR
+                   + (gds->tokenp[1].value % 100) * MINUTE);
+               gds->tokenp += 2;
+       }
+       return 1;
+}
+
+/*
+ * Timezone name, possibly including DST.
+ */
+static int
+zonephrase(struct gdstate *gds)
+{
+       if (gds->tokenp[0].token == tZONE
+           && gds->tokenp[1].token == tDST) {
+               gds->HaveZone++;
+               gds->Timezone = gds->tokenp[0].value;
+               gds->DSTmode = DSTon;
+               gds->tokenp += 1;
+               return 1;
+       }
+
+       if (gds->tokenp[0].token == tZONE) {
+               gds->HaveZone++;
+               gds->Timezone = gds->tokenp[0].value;
+               gds->DSTmode = DSToff;
+               gds->tokenp += 1;
+               return 1;
+       }
+
+       if (gds->tokenp[0].token == tDAYZONE) {
+               gds->HaveZone++;
+               gds->Timezone = gds->tokenp[0].value;
+               gds->DSTmode = DSTon;
+               gds->tokenp += 1;
+               return 1;
+       }
+       return 0;
+}
+
+/*
+ * Year/month/day in various combinations.
+ */
+static int
+datephrase(struct gdstate *gds)
+{
+       if (gds->tokenp[0].token == tUNUMBER
+           && gds->tokenp[1].token == '/'
+           && gds->tokenp[2].token == tUNUMBER
+           && gds->tokenp[3].token == '/'
+           && gds->tokenp[4].token == tUNUMBER) {
+               gds->HaveYear++;
+               gds->HaveMonth++;
+               gds->HaveDay++;
+               if (gds->tokenp[0].value >= 13) {
+                       /* First number is big:  2004/01/29, 99/02/17 */
+                       gds->Year = gds->tokenp[0].value;
+                       gds->Month = gds->tokenp[2].value;
+                       gds->Day = gds->tokenp[4].value;
+               } else if ((gds->tokenp[4].value >= 13)
+                   || (gds->tokenp[2].value >= 13)) {
+                       /* Last number is big:  01/07/98 */
+                       /* Middle number is big:  01/29/04 */
+                       gds->Month = gds->tokenp[0].value;
+                       gds->Day = gds->tokenp[2].value;
+                       gds->Year = gds->tokenp[4].value;
+               } else {
+                       /* No significant clues: 02/03/04 */
+                       gds->Month = gds->tokenp[0].value;
+                       gds->Day = gds->tokenp[2].value;
+                       gds->Year = gds->tokenp[4].value;
+               }
+               gds->tokenp += 5;
+               return 1;
+       }
+
+       if (gds->tokenp[0].token == tUNUMBER
+           && gds->tokenp[1].token == '/'
+           && gds->tokenp[2].token == tUNUMBER) {
+               /* "1/15" */
+               gds->HaveMonth++;
+               gds->HaveDay++;
+               gds->Month = gds->tokenp[0].value;
+               gds->Day = gds->tokenp[2].value;
+               gds->tokenp += 3;
+               return 1;
+       }
+
+       if (gds->tokenp[0].token == tUNUMBER
+           && gds->tokenp[1].token == '-'
+           && gds->tokenp[2].token == tUNUMBER
+           && gds->tokenp[3].token == '-'
+           && gds->tokenp[4].token == tUNUMBER) {
+               /* ISO 8601 format.  yyyy-mm-dd.  */
+               gds->HaveYear++;
+               gds->HaveMonth++;
+               gds->HaveDay++;
+               gds->Year = gds->tokenp[0].value;
+               gds->Month = gds->tokenp[2].value;
+               gds->Day = gds->tokenp[4].value;
+               gds->tokenp += 5;
+               return 1;
+       }
+
+       if (gds->tokenp[0].token == tUNUMBER
+           && gds->tokenp[1].token == '-'
+           && gds->tokenp[2].token == tMONTH
+           && gds->tokenp[3].token == '-'
+           && gds->tokenp[4].token == tUNUMBER) {
+               gds->HaveYear++;
+               gds->HaveMonth++;
+               gds->HaveDay++;
+               if (gds->tokenp[0].value > 31) {
+                       /* e.g. 1992-Jun-17 */
+                       gds->Year = gds->tokenp[0].value;
+                       gds->Month = gds->tokenp[2].value;
+                       gds->Day = gds->tokenp[4].value;
+               } else {
+                       /* e.g. 17-JUN-1992.  */
+                       gds->Day = gds->tokenp[0].value;
+                       gds->Month = gds->tokenp[2].value;
+                       gds->Year = gds->tokenp[4].value;
+               }
+               gds->tokenp += 5;
+               return 1;
+       }
+
+       if (gds->tokenp[0].token == tMONTH
+           && gds->tokenp[1].token == tUNUMBER
+           && gds->tokenp[2].token == ','
+           && gds->tokenp[3].token == tUNUMBER) {
+               /* "June 17, 2001" */
+               gds->HaveYear++;
+               gds->HaveMonth++;
+               gds->HaveDay++;
+               gds->Month = gds->tokenp[0].value;
+               gds->Day = gds->tokenp[1].value;
+               gds->Year = gds->tokenp[3].value;
+               gds->tokenp += 4;
+               return 1;
+       }
+
+       if (gds->tokenp[0].token == tMONTH
+           && gds->tokenp[1].token == tUNUMBER) {
+               /* "May 3" */
+               gds->HaveMonth++;
+               gds->HaveDay++;
+               gds->Month = gds->tokenp[0].value;
+               gds->Day = gds->tokenp[1].value;
+               gds->tokenp += 2;
+               return 1;
+       }
+
+       if (gds->tokenp[0].token == tUNUMBER
+           && gds->tokenp[1].token == tMONTH
+           && gds->tokenp[2].token == tUNUMBER) {
+               /* "12 Sept 1997" */
+               gds->HaveYear++;
+               gds->HaveMonth++;
+               gds->HaveDay++;
+               gds->Day = gds->tokenp[0].value;
+               gds->Month = gds->tokenp[1].value;
+               gds->Year = gds->tokenp[2].value;
+               gds->tokenp += 3;
+               return 1;
+       }
+
+       if (gds->tokenp[0].token == tUNUMBER
+           && gds->tokenp[1].token == tMONTH) {
+               /* "12 Sept" */
+               gds->HaveMonth++;
+               gds->HaveDay++;
+               gds->Day = gds->tokenp[0].value;
+               gds->Month = gds->tokenp[1].value;
+               gds->tokenp += 2;
+               return 1;
+       }
+
+       return 0;
+}
+
+/*
+ * Relative time phrase: "tomorrow", "yesterday", "+1 hour", etc.
+ */
+static int
+relunitphrase(struct gdstate *gds)
+{
+       if (gds->tokenp[0].token == '-'
+           && gds->tokenp[1].token == tUNUMBER
+           && gds->tokenp[2].token == tSEC_UNIT) {
+               /* "-3 hours" */
+               gds->HaveRel++;
+               gds->RelSeconds -= gds->tokenp[1].value * gds->tokenp[2].value;
+               gds->tokenp += 3;
+               return 1;
+       }
+       if (gds->tokenp[0].token == '+'
+           && gds->tokenp[1].token == tUNUMBER
+           && gds->tokenp[2].token == tSEC_UNIT) {
+               /* "+1 minute" */
+               gds->HaveRel++;
+               gds->RelSeconds += gds->tokenp[1].value * gds->tokenp[2].value;
+               gds->tokenp += 3;
+               return 1;
+       }
+       if (gds->tokenp[0].token == tUNUMBER
+           && gds->tokenp[1].token == tSEC_UNIT) {
+               /* "1 day" */
+               gds->HaveRel++;
+               gds->RelSeconds += gds->tokenp[1].value * gds->tokenp[2].value;
+               gds->tokenp += 3;
+               return 1;
+       }
+       if (gds->tokenp[0].token == '-'
+           && gds->tokenp[1].token == tUNUMBER
+           && gds->tokenp[2].token == tMONTH_UNIT) {
+               /* "-3 months" */
+               gds->HaveRel++;
+               gds->RelMonth -= gds->tokenp[1].value * gds->tokenp[2].value;
+               gds->tokenp += 3;
+               return 1;
+       }
+       if (gds->tokenp[0].token == '+'
+           && gds->tokenp[1].token == tUNUMBER
+           && gds->tokenp[2].token == tMONTH_UNIT) {
+               /* "+5 years" */
+               gds->HaveRel++;
+               gds->RelMonth += gds->tokenp[1].value * gds->tokenp[2].value;
+               gds->tokenp += 3;
+               return 1;
+       }
+       if (gds->tokenp[0].token == tUNUMBER
+           && gds->tokenp[1].token == tMONTH_UNIT) {
+               /* "2 years" */
+               gds->HaveRel++;
+               gds->RelMonth += gds->tokenp[0].value * gds->tokenp[1].value;
+               gds->tokenp += 2;
+               return 1;
+       }
+       if (gds->tokenp[0].token == tSEC_UNIT) {
+               /* "now", "tomorrow" */
+               gds->HaveRel++;
+               gds->RelSeconds += gds->tokenp[0].value;
+               ++gds->tokenp;
+               return 1;
+       }
+       if (gds->tokenp[0].token == tMONTH_UNIT) {
+               /* "month" */
+               gds->HaveRel++;
+               gds->RelMonth += gds->tokenp[0].value;
+               gds->tokenp += 1;
+               return 1;
+       }
+       return 0;
+}
+
+/*
+ * Day of the week specification.
+ */
+static int
+dayphrase(struct gdstate *gds)
+{
+       if (gds->tokenp[0].token == tDAY) {
+               /* "tues", "wednesday," */
+               gds->HaveWeekDay++;
+               gds->DayOrdinal = 1;
+               gds->DayNumber = gds->tokenp[0].value;
+               gds->tokenp += 1;
+               if (gds->tokenp[0].token == ',')
+                       gds->tokenp += 1;
+               return 1;
+       }
+       if (gds->tokenp[0].token == tUNUMBER
+               && gds->tokenp[1].token == tDAY) {
+               /* "second tues" "3 wed" */
+               gds->HaveWeekDay++;
+               gds->DayOrdinal = gds->tokenp[0].value;
+               gds->DayNumber = gds->tokenp[1].value;
+               gds->tokenp += 2;
+               return 1;
+       }
+       return 0;
+}
+
+/*
+ * Try to match a phrase using one of the above functions.
+ * This layer also deals with a couple of generic issues.
+ */
+static int
+phrase(struct gdstate *gds)
+{
+       if (timephrase(gds))
+               return 1;
+       if (zonephrase(gds))
+               return 1;
+       if (datephrase(gds))
+               return 1;
+       if (dayphrase(gds))
+               return 1;
+       if (relunitphrase(gds)) {
+               if (gds->tokenp[0].token == tAGO) {
+                       gds->RelSeconds = -gds->RelSeconds;
+                       gds->RelMonth = -gds->RelMonth;
+                       gds->tokenp += 1;
+               }
+               return 1;
+       }
+
+       /* Bare numbers sometimes have meaning. */
+       if (gds->tokenp[0].token == tUNUMBER) {
+               if (gds->HaveTime && !gds->HaveYear && !gds->HaveRel) {
+                       gds->HaveYear++;
+                       gds->Year = gds->tokenp[0].value;
+                       gds->tokenp += 1;
+                       return 1;
+               }
+
+               if(gds->tokenp[0].value > 10000) {
+                       /* "20040301" */
+                       gds->HaveYear++;
+                       gds->HaveMonth++;
+                       gds->HaveDay++;
+                       gds->Day= (gds->tokenp[0].value)%100;
+                       gds->Month= (gds->tokenp[0].value/100)%100;
+                       gds->Year = gds->tokenp[0].value/10000;
+                       gds->tokenp += 1;
+                       return 1;
+               }
+
+               if (gds->tokenp[0].value < 24) {
+                       gds->HaveTime++;
+                       gds->Hour = gds->tokenp[0].value;
+                       gds->Minutes = 0;
+                       gds->Seconds = 0;
+                       gds->tokenp += 1;
+                       return 1;
+               }
+
+               if ((gds->tokenp[0].value / 100 < 24)
+                   && (gds->tokenp[0].value % 100 < 60)) {
+                       /* "513" is same as "5:13" */
+                       gds->Hour = gds->tokenp[0].value / 100;
+                       gds->Minutes = gds->tokenp[0].value % 100;
+                       gds->Seconds = 0;
+                       gds->tokenp += 1;
+                       return 1;
+               }
+       }
+
+       return 0;
+}
+
+/*
+ * A dictionary of time words.
+ */
+static struct LEXICON {
+       size_t          abbrev;
+       const char      *name;
+       int             type;
+       time_t          value;
+} const TimeWords[] = {
+       /* am/pm */
+       { 0, "am",              tAMPM,  tAM },
+       { 0, "pm",              tAMPM,  tPM },
+
+       /* Month names. */
+       { 3, "january",         tMONTH,  1 },
+       { 3, "february",        tMONTH,  2 },
+       { 3, "march",           tMONTH,  3 },
+       { 3, "april",           tMONTH,  4 },
+       { 3, "may",             tMONTH,  5 },
+       { 3, "june",            tMONTH,  6 },
+       { 3, "july",            tMONTH,  7 },
+       { 3, "august",          tMONTH,  8 },
+       { 3, "september",       tMONTH,  9 },
+       { 3, "october",         tMONTH, 10 },
+       { 3, "november",        tMONTH, 11 },
+       { 3, "december",        tMONTH, 12 },
+
+       /* Days of the week. */
+       { 2, "sunday",          tDAY, 0 },
+       { 3, "monday",          tDAY, 1 },
+       { 2, "tuesday",         tDAY, 2 },
+       { 3, "wednesday",       tDAY, 3 },
+       { 2, "thursday",        tDAY, 4 },
+       { 2, "friday",          tDAY, 5 },
+       { 2, "saturday",        tDAY, 6 },
+
+       /* Timezones: Offsets are in seconds. */
+       { 0, "gmt",  tZONE,     0*HOUR }, /* Greenwich Mean */
+       { 0, "ut",   tZONE,     0*HOUR }, /* Universal (Coordinated) */
+       { 0, "utc",  tZONE,     0*HOUR },
+       { 0, "wet",  tZONE,     0*HOUR }, /* Western European */
+       { 0, "bst",  tDAYZONE,  0*HOUR }, /* British Summer */
+       { 0, "wat",  tZONE,     1*HOUR }, /* West Africa */
+       { 0, "at",   tZONE,     2*HOUR }, /* Azores */
+       /* { 0, "bst", tZONE, 3*HOUR }, */ /* Brazil Standard: Conflict */
+       /* { 0, "gst", tZONE, 3*HOUR }, */ /* Greenland Standard: Conflict*/
+       { 0, "nft",  tZONE,     3*HOUR+30*MINUTE }, /* Newfoundland */
+       { 0, "nst",  tZONE,     3*HOUR+30*MINUTE }, /* Newfoundland Standard */
+       { 0, "ndt",  tDAYZONE,  3*HOUR+30*MINUTE }, /* Newfoundland Daylight */
+       { 0, "ast",  tZONE,     4*HOUR }, /* Atlantic Standard */
+       { 0, "adt",  tDAYZONE,  4*HOUR }, /* Atlantic Daylight */
+       { 0, "est",  tZONE,     5*HOUR }, /* Eastern Standard */
+       { 0, "edt",  tDAYZONE,  5*HOUR }, /* Eastern Daylight */
+       { 0, "cst",  tZONE,     6*HOUR }, /* Central Standard */
+       { 0, "cdt",  tDAYZONE,  6*HOUR }, /* Central Daylight */
+       { 0, "mst",  tZONE,     7*HOUR }, /* Mountain Standard */
+       { 0, "mdt",  tDAYZONE,  7*HOUR }, /* Mountain Daylight */
+       { 0, "pst",  tZONE,     8*HOUR }, /* Pacific Standard */
+       { 0, "pdt",  tDAYZONE,  8*HOUR }, /* Pacific Daylight */
+       { 0, "yst",  tZONE,     9*HOUR }, /* Yukon Standard */
+       { 0, "ydt",  tDAYZONE,  9*HOUR }, /* Yukon Daylight */
+       { 0, "hst",  tZONE,     10*HOUR }, /* Hawaii Standard */
+       { 0, "hdt",  tDAYZONE,  10*HOUR }, /* Hawaii Daylight */
+       { 0, "cat",  tZONE,     10*HOUR }, /* Central Alaska */
+       { 0, "ahst", tZONE,     10*HOUR }, /* Alaska-Hawaii Standard */
+       { 0, "nt",   tZONE,     11*HOUR }, /* Nome */
+       { 0, "idlw", tZONE,     12*HOUR }, /* Intl Date Line West */
+       { 0, "cet",  tZONE,     -1*HOUR }, /* Central European */
+       { 0, "met",  tZONE,     -1*HOUR }, /* Middle European */
+       { 0, "mewt", tZONE,     -1*HOUR }, /* Middle European Winter */
+       { 0, "mest", tDAYZONE,  -1*HOUR }, /* Middle European Summer */
+       { 0, "swt",  tZONE,     -1*HOUR }, /* Swedish Winter */
+       { 0, "sst",  tDAYZONE,  -1*HOUR }, /* Swedish Summer */
+       { 0, "fwt",  tZONE,     -1*HOUR }, /* French Winter */
+       { 0, "fst",  tDAYZONE,  -1*HOUR }, /* French Summer */
+       { 0, "eet",  tZONE,     -2*HOUR }, /* Eastern Eur, USSR Zone 1 */
+       { 0, "bt",   tZONE,     -3*HOUR }, /* Baghdad, USSR Zone 2 */
+       { 0, "it",   tZONE,     -3*HOUR-30*MINUTE },/* Iran */
+       { 0, "zp4",  tZONE,     -4*HOUR }, /* USSR Zone 3 */
+       { 0, "zp5",  tZONE,     -5*HOUR }, /* USSR Zone 4 */
+       { 0, "ist",  tZONE,     -5*HOUR-30*MINUTE },/* Indian Standard */
+       { 0, "zp6",  tZONE,     -6*HOUR }, /* USSR Zone 5 */
+       /* { 0, "nst",  tZONE, -6.5*HOUR }, */ /* North Sumatra: Conflict */
+       /* { 0, "sst", tZONE, -7*HOUR }, */ /* So Sumatra, USSR 6: Conflict */
+       { 0, "wast", tZONE,     -7*HOUR }, /* West Australian Standard */
+       { 0, "wadt", tDAYZONE,  -7*HOUR }, /* West Australian Daylight */
+       { 0, "jt",   tZONE,     -7*HOUR-30*MINUTE },/* Java (3pm in Cronusland!)*/
+       { 0, "cct",  tZONE,     -8*HOUR }, /* China Coast, USSR Zone 7 */
+       { 0, "jst",  tZONE,     -9*HOUR }, /* Japan Std, USSR Zone 8 */
+       { 0, "cast", tZONE,     -9*HOUR-30*MINUTE },/* Ctrl Australian Std */
+       { 0, "cadt", tDAYZONE,  -9*HOUR-30*MINUTE },/* Ctrl Australian Daylt */
+       { 0, "east", tZONE,     -10*HOUR }, /* Eastern Australian Std */
+       { 0, "eadt", tDAYZONE,  -10*HOUR }, /* Eastern Australian Daylt */
+       { 0, "gst",  tZONE,     -10*HOUR }, /* Guam Std, USSR Zone 9 */
+       { 0, "nzt",  tZONE,     -12*HOUR }, /* New Zealand */
+       { 0, "nzst", tZONE,     -12*HOUR }, /* New Zealand Standard */
+       { 0, "nzdt", tDAYZONE,  -12*HOUR }, /* New Zealand Daylight */
+       { 0, "idle", tZONE,     -12*HOUR }, /* Intl Date Line East */
+
+       { 0, "dst",  tDST,              0 },
+
+       /* Time units. */
+       { 4, "years",           tMONTH_UNIT,    12 },
+       { 5, "months",          tMONTH_UNIT,    1 },
+       { 9, "fortnights",      tSEC_UNIT,      14 * DAY },
+       { 4, "weeks",           tSEC_UNIT,      7 * DAY },
+       { 3, "days",            tSEC_UNIT,      DAY },
+       { 4, "hours",           tSEC_UNIT,      HOUR },
+       { 3, "minutes",         tSEC_UNIT,      MINUTE },
+       { 3, "seconds",         tSEC_UNIT,      1 },
+
+       /* Relative-time words. */
+       { 0, "tomorrow",        tSEC_UNIT,      DAY },
+       { 0, "yesterday",       tSEC_UNIT,      -DAY },
+       { 0, "today",           tSEC_UNIT,      0 },
+       { 0, "now",             tSEC_UNIT,      0 },
+       { 0, "last",            tUNUMBER,       -1 },
+       { 0, "this",            tSEC_UNIT,      0 },
+       { 0, "next",            tUNUMBER,       2 },
+       { 0, "first",           tUNUMBER,       1 },
+       { 0, "1st",             tUNUMBER,       1 },
+/*     { 0, "second",          tUNUMBER,       2 }, */
+       { 0, "2nd",             tUNUMBER,       2 },
+       { 0, "third",           tUNUMBER,       3 },
+       { 0, "3rd",             tUNUMBER,       3 },
+       { 0, "fourth",          tUNUMBER,       4 },
+       { 0, "4th",             tUNUMBER,       4 },
+       { 0, "fifth",           tUNUMBER,       5 },
+       { 0, "5th",             tUNUMBER,       5 },
+       { 0, "sixth",           tUNUMBER,       6 },
+       { 0, "seventh",         tUNUMBER,       7 },
+       { 0, "eighth",          tUNUMBER,       8 },
+       { 0, "ninth",           tUNUMBER,       9 },
+       { 0, "tenth",           tUNUMBER,       10 },
+       { 0, "eleventh",        tUNUMBER,       11 },
+       { 0, "twelfth",         tUNUMBER,       12 },
+       { 0, "ago",             tAGO,           1 },
+
+       /* Military timezones. */
+       { 0, "a",       tZONE,  1*HOUR },
+       { 0, "b",       tZONE,  2*HOUR },
+       { 0, "c",       tZONE,  3*HOUR },
+       { 0, "d",       tZONE,  4*HOUR },
+       { 0, "e",       tZONE,  5*HOUR },
+       { 0, "f",       tZONE,  6*HOUR },
+       { 0, "g",       tZONE,  7*HOUR },
+       { 0, "h",       tZONE,  8*HOUR },
+       { 0, "i",       tZONE,  9*HOUR },
+       { 0, "k",       tZONE,  10*HOUR },
+       { 0, "l",       tZONE,  11*HOUR },
+       { 0, "m",       tZONE,  12*HOUR },
+       { 0, "n",       tZONE,  -1*HOUR },
+       { 0, "o",       tZONE,  -2*HOUR },
+       { 0, "p",       tZONE,  -3*HOUR },
+       { 0, "q",       tZONE,  -4*HOUR },
+       { 0, "r",       tZONE,  -5*HOUR },
+       { 0, "s",       tZONE,  -6*HOUR },
+       { 0, "t",       tZONE,  -7*HOUR },
+       { 0, "u",       tZONE,  -8*HOUR },
+       { 0, "v",       tZONE,  -9*HOUR },
+       { 0, "w",       tZONE,  -10*HOUR },
+       { 0, "x",       tZONE,  -11*HOUR },
+       { 0, "y",       tZONE,  -12*HOUR },
+       { 0, "z",       tZONE,  0*HOUR },
+
+       /* End of table. */
+       { 0, NULL,      0,      0 }
+};
+
+/*
+ * Year is either:
+ *  = A number from 0 to 99, which means a year from 1970 to 2069, or
+ *  = The actual year (>=100).
+ */
+static time_t
+Convert(time_t Month, time_t Day, time_t Year,
+       time_t Hours, time_t Minutes, time_t Seconds,
+       time_t Timezone, enum DSTMODE DSTmode)
+{
+       static int DaysInMonth[12] = {
+               31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+       };
+       time_t  Julian;
+       int     i;
+
+       if (Year < 69)
+               Year += 2000;
+       else if (Year < 100)
+               Year += 1900;
+       DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
+           ? 29 : 28;
+       /* Checking for 2038 bogusly assumes that time_t is 32 bits.  But
+          I'm too lazy to try to check for time_t overflow in another way.  */
+       if (Year < EPOCH || Year > 2038
+           || Month < 1 || Month > 12
+           /* Lint fluff:  "conversion from long may lose accuracy" */
+           || Day < 1 || Day > DaysInMonth[(int)--Month]
+           || Hours < 0 || Hours > 23
+           || Minutes < 0 || Minutes > 59
+           || Seconds < 0 || Seconds > 59)
+               return -1;
+
+       Julian = Day - 1;
+       for (i = 0; i < Month; i++)
+               Julian += DaysInMonth[i];
+       for (i = EPOCH; i < Year; i++)
+               Julian += 365 + (i % 4 == 0);
+       Julian *= DAY;
+       Julian += Timezone;
+       Julian += Hours * HOUR + Minutes * MINUTE + Seconds;
+       if (DSTmode == DSTon
+           || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst))
+               Julian -= HOUR;
+       return Julian;
+}
+
+
+static time_t
+DSTcorrect(time_t Start, time_t Future)
+{
+       time_t  StartDay;
+       time_t  FutureDay;
+
+       StartDay = (localtime(&Start)->tm_hour + 1) % 24;
+       FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
+       return (Future - Start) + (StartDay - FutureDay) * HOUR;
+}
+
+
+static time_t
+RelativeDate(time_t Start, time_t zone, int dstmode,
+    time_t DayOrdinal, time_t DayNumber)
+{
+       struct tm       *tm;
+       time_t  t, now;
+
+       t = Start - zone;
+       tm = gmtime(&t);
+       now = Start;
+       now += DAY * ((DayNumber - tm->tm_wday + 7) % 7);
+       now += 7 * DAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
+       if (dstmode == DSTmaybe)
+               return DSTcorrect(Start, now);
+       return now - Start;
+}
+
+
+static time_t
+RelativeMonth(time_t Start, time_t Timezone, time_t RelMonth)
+{
+       struct tm       *tm;
+       time_t  Month;
+       time_t  Year;
+
+       if (RelMonth == 0)
+               return 0;
+       tm = localtime(&Start);
+       Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth;
+       Year = Month / 12;
+       Month = Month % 12 + 1;
+       return DSTcorrect(Start,
+           Convert(Month, (time_t)tm->tm_mday, Year,
+               (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
+               Timezone, DSTmaybe));
+}
+
+/*
+ * Tokenizer.
+ */
+static int
+nexttoken(char **in, time_t *value)
+{
+       char    c;
+       char    buff[64];
+
+       for ( ; ; ) {
+               while (isspace((unsigned char)**in))
+                       ++*in;
+
+               /* Skip parenthesized comments. */
+               if (**in == '(') {
+                       int Count = 0;
+                       do {
+                               c = *(*in)++;
+                               if (c == '\0')
+                                       return c;
+                               if (c == '(')
+                                       Count++;
+                               else if (c == ')')
+                                       Count--;
+                       } while (Count > 0);
+                       continue;
+               }
+
+               /* Try the next token in the word table first. */
+               /* This allows us to match "2nd", for example. */
+               {
+                       char *src = *in;
+                       const struct LEXICON *tp;
+                       unsigned i = 0;
+
+                       /* Force to lowercase and strip '.' characters. */
+                       while (*src != '\0'
+                           && (isalnum((unsigned char)*src) || *src == '.')
+                           && i < sizeof(buff)-1) {
+                               if (*src != '.') {
+                                       if (isupper((unsigned char)*src))
+                                               buff[i++] = tolower((unsigned char)*src);
+                                       else
+                                               buff[i++] = *src;
+                               }
+                               src++;
+                       }
+                       buff[i] = '\0';
+
+                       /*
+                        * Find the first match.  If the word can be
+                        * abbreviated, make sure we match at least
+                        * the minimum abbreviation.
+                        */
+                       for (tp = TimeWords; tp->name; tp++) {
+                               size_t abbrev = tp->abbrev;
+                               if (abbrev == 0)
+                                       abbrev = strlen(tp->name);
+                               if (strlen(buff) >= abbrev
+                                   && strncmp(tp->name, buff, strlen(buff))
+                                       == 0) {
+                                       /* Skip over token. */
+                                       *in = src;
+                                       /* Return the match. */
+                                       *value = tp->value;
+                                       return tp->type;
+                               }
+                       }
+               }
+
+               /*
+                * Not in the word table, maybe it's a number.  Note:
+                * Because '-' and '+' have other special meanings, I
+                * don't deal with signed numbers here.
+                */
+               if (isdigit((unsigned char)(c = **in))) {
+                       for (*value = 0; isdigit((unsigned char)(c = *(*in)++)); )
+                               *value = 10 * *value + c - '0';
+                       (*in)--;
+                       return (tUNUMBER);
+               }
+
+               return *(*in)++;
+       }
+}
+
+#define TM_YEAR_ORIGIN 1900
+
+/* Yield A - B, measured in seconds.  */
+static long
+difftm (struct tm *a, struct tm *b)
+{
+       int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
+       int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
+       int days = (
+               /* difference in day of year */
+               a->tm_yday - b->tm_yday
+               /* + intervening leap days */
+               +  ((ay >> 2) - (by >> 2))
+               -  (ay/100 - by/100)
+               +  ((ay/100 >> 2) - (by/100 >> 2))
+               /* + difference in years * 365 */
+               +  (long)(ay-by) * 365
+               );
+       return (days * DAY + (a->tm_hour - b->tm_hour) * HOUR
+           + (a->tm_min - b->tm_min) * MINUTE
+           + (a->tm_sec - b->tm_sec));
+}
+
+/*
+ *
+ * The public function.
+ *
+ * TODO: tokens[] array should be dynamically sized.
+ */
+time_t
+get_date(time_t now, char *p)
+{
+       struct token    tokens[256];
+       struct gdstate  _gds;
+       struct token    *lasttoken;
+       struct gdstate  *gds;
+       struct tm       local, *tm;
+       struct tm       gmt, *gmt_ptr;
+       time_t          Start;
+       time_t          tod;
+       long            tzone;
+
+       /* Clear out the parsed token array. */
+       memset(tokens, 0, sizeof(tokens));
+       /* Initialize the parser state. */
+       memset(&_gds, 0, sizeof(_gds));
+       gds = &_gds;
+
+       /* Look up the current time. */
+       memset(&local, 0, sizeof(local));
+       tm = localtime (&now);
+       if (tm == NULL)
+               return -1;
+       local = *tm;
+
+       /* Look up UTC if we can and use that to determine the current
+        * timezone offset. */
+       memset(&gmt, 0, sizeof(gmt));
+       gmt_ptr = gmtime (&now);
+       if (gmt_ptr != NULL) {
+               /* Copy, in case localtime and gmtime use the same buffer. */
+               gmt = *gmt_ptr;
+       }
+       if (gmt_ptr != NULL)
+               tzone = difftm (&gmt, &local);
+       else
+               /* This system doesn't understand timezones; fake it. */
+               tzone = 0;
+       if(local.tm_isdst)
+               tzone += HOUR;
+
+       /* Tokenize the input string. */
+       lasttoken = tokens;
+       while ((lasttoken->token = nexttoken(&p, &lasttoken->value)) != 0) {
+               ++lasttoken;
+               if (lasttoken > tokens + 255)
+                       return -1;
+       }
+       gds->tokenp = tokens;
+
+       /* Match phrases until we run out of input tokens. */
+       while (gds->tokenp < lasttoken) {
+               if (!phrase(gds))
+                       return -1;
+       }
+
+       /* Use current local timezone if none was specified. */
+       if (!gds->HaveZone) {
+               gds->Timezone = tzone;
+               gds->DSTmode = DSTmaybe;
+       }
+
+       /* If a timezone was specified, use that for generating the default
+        * time components instead of the local timezone. */
+       if (gds->HaveZone && gmt_ptr != NULL) {
+               now -= gds->Timezone;
+               gmt_ptr = gmtime (&now);
+               if (gmt_ptr != NULL)
+                       local = *gmt_ptr;
+               now += gds->Timezone;
+       }
+
+       if (!gds->HaveYear)
+               gds->Year = local.tm_year + 1900;
+       if (!gds->HaveMonth)
+               gds->Month = local.tm_mon + 1;
+       if (!gds->HaveDay)
+               gds->Day = local.tm_mday;
+       /* Note: No default for hour/min/sec; a specifier that just
+        * gives date always refers to 00:00 on that date. */
+
+       /* If we saw more than one time, timezone, weekday, year, month,
+        * or day, then give up. */
+       if (gds->HaveTime > 1 || gds->HaveZone > 1 || gds->HaveWeekDay > 1
+           || gds->HaveYear > 1 || gds->HaveMonth > 1 || gds->HaveDay > 1)
+               return -1;
+
+       /* Compute an absolute time based on whatever absolute information
+        * we collected. */
+       if (gds->HaveYear || gds->HaveMonth || gds->HaveDay
+           || gds->HaveTime || gds->HaveWeekDay) {
+               Start = Convert(gds->Month, gds->Day, gds->Year,
+                   gds->Hour, gds->Minutes, gds->Seconds,
+                   gds->Timezone, gds->DSTmode);
+               if (Start < 0)
+                       return -1;
+       } else {
+               Start = now;
+               if (!gds->HaveRel)
+                       Start -= local.tm_hour * HOUR + local.tm_min * MINUTE
+                           + local.tm_sec;
+       }
+
+       /* Add the relative offset. */
+       Start += gds->RelSeconds;
+       Start += RelativeMonth(Start, gds->Timezone, gds->RelMonth);
+
+       /* Adjust for day-of-week offsets. */
+       if (gds->HaveWeekDay
+           && !(gds->HaveYear || gds->HaveMonth || gds->HaveDay)) {
+               tod = RelativeDate(Start, gds->Timezone,
+                   gds->DSTmode, gds->DayOrdinal, gds->DayNumber);
+               Start += tod;
+       }
+
+       /* -1 is an error indicator, so return 0 instead of -1 if
+        * that's the actual time. */
+       return Start == -1 ? 0 : Start;
+}
+
+
+#if    defined(TEST)
+
+/* ARGSUSED */
+int
+main(int argc, char **argv)
+{
+    time_t     d;
+
+    while (*++argv != NULL) {
+           (void)printf("Input: %s\n", *argv);
+           d = get_date(*argv);
+           if (d == -1)
+                   (void)printf("Bad format - couldn't convert.\n");
+           else
+                   (void)printf("Output: %s\n", ctime(&d));
+    }
+    exit(0);
+    /* NOTREACHED */
+}
+#endif /* defined(TEST) */
diff --git a/commands/bsdtar/libarchive_fe/Makefile.inc b/commands/bsdtar/libarchive_fe/Makefile.inc
new file mode 100644 (file)
index 0000000..a70197f
--- /dev/null
@@ -0,0 +1,6 @@
+.PATH: ${.CURDIR}/libarchive_fe
+
+SRCS+= err.c \
+               line_reader.c \
+               matching.c \
+               pathmatch.c
diff --git a/commands/bsdtar/libarchive_fe/err.c b/commands/bsdtar/libarchive_fe/err.c
new file mode 100644 (file)
index 0000000..eb3f9f3
--- /dev/null
@@ -0,0 +1,74 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer
+ *    in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "lafe_platform.h"
+__FBSDID("$FreeBSD$");
+
+#ifdef HAVE_STDARG_H
+#include <stdarg.h>
+#endif
+#include <stdio.h>
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+
+#include "err.h"
+
+const char *lafe_progname;
+
+static void
+lafe_vwarnc(int code, const char *fmt, va_list ap)
+{
+       fprintf(stderr, "%s: ", lafe_progname);
+       vfprintf(stderr, fmt, ap);
+       if (code != 0)
+               fprintf(stderr, ": %s", strerror(code));
+       fprintf(stderr, "\n");
+}
+
+void
+lafe_warnc(int code, const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       lafe_vwarnc(code, fmt, ap);
+       va_end(ap);
+}
+
+void
+lafe_errc(int eval, int code, const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       lafe_vwarnc(code, fmt, ap);
+       va_end(ap);
+       exit(eval);
+}
diff --git a/commands/bsdtar/libarchive_fe/err.h b/commands/bsdtar/libarchive_fe/err.h
new file mode 100644 (file)
index 0000000..dd79448
--- /dev/null
@@ -0,0 +1,41 @@
+/*-
+ * Copyright (c) 2009 Joerg Sonnenberger
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LAFE_ERR_H
+#define LAFE_ERR_H
+
+#if defined(__GNUC__) && (__GNUC__ > 2 || \
+                          (__GNUC__ == 2 && __GNUC_MINOR__ >= 5))
+#define __LA_DEAD       __attribute__((__noreturn__))
+#else
+#define __LA_DEAD
+#endif
+
+extern const char *lafe_progname;
+
+void   lafe_warnc(int code, const char *fmt, ...);
+void   lafe_errc(int eval, int code, const char *fmt, ...) __LA_DEAD;
+
+#endif
diff --git a/commands/bsdtar/libarchive_fe/lafe_platform.h b/commands/bsdtar/libarchive_fe/lafe_platform.h
new file mode 100644 (file)
index 0000000..557124b
--- /dev/null
@@ -0,0 +1,55 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.bin/cpio/cpio_platform.h,v 1.2 2008/12/06 07:15:42 kientzle Exp $
+ */
+
+/*
+ * This header is the first thing included in any of the libarchive_fe
+ * source files.  As far as possible, platform-specific issues should
+ * be dealt with here and not within individual source files.
+ */
+
+#ifndef LAFE_PLATFORM_H_INCLUDED
+#define        LAFE_PLATFORM_H_INCLUDED
+
+#if defined(PLATFORM_CONFIG_H)
+/* Use hand-built config.h in environments that need it. */
+#include PLATFORM_CONFIG_H
+#else
+/* Read config.h or die trying. */
+#include "config.h"
+#endif
+
+/* Get a real definition for __FBSDID if we can */
+#if HAVE_SYS_CDEFS_H
+#include <sys/cdefs.h>
+#endif
+
+/* If not, define it so as to avoid dangling semicolons. */
+#ifndef __FBSDID
+#define        __FBSDID(a)     struct _undefined_hack
+#endif
+
+#endif
diff --git a/commands/bsdtar/libarchive_fe/line_reader.c b/commands/bsdtar/libarchive_fe/line_reader.c
new file mode 100644 (file)
index 0000000..4af60de
--- /dev/null
@@ -0,0 +1,171 @@
+/*-
+ * Copyright (c) 2008 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer
+ *    in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "lafe_platform.h"
+__FBSDID("$FreeBSD$");
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "err.h"
+#include "line_reader.h"
+
+#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__BORLANDC__)
+#define strdup _strdup
+#endif
+
+/*
+ * Read lines from file and do something with each one.  If option_null
+ * is set, lines are terminated with zero bytes; otherwise, they're
+ * terminated with newlines.
+ *
+ * This uses a self-sizing buffer to handle arbitrarily-long lines.
+ */
+struct lafe_line_reader {
+       FILE *f;
+       char *buff, *buff_end, *line_start, *line_end, *p;
+       char *pathname;
+       size_t buff_length;
+       int nullSeparator; /* Lines separated by null, not CR/CRLF/etc. */
+       int ret;
+};
+
+struct lafe_line_reader *
+lafe_line_reader(const char *pathname, int nullSeparator)
+{
+       struct lafe_line_reader *lr;
+
+       lr = calloc(1, sizeof(*lr));
+       if (lr == NULL)
+               lafe_errc(1, ENOMEM, "Can't open %s", pathname);
+
+       lr->nullSeparator = nullSeparator;
+       lr->pathname = strdup(pathname);
+
+       if (strcmp(pathname, "-") == 0)
+               lr->f = stdin;
+       else
+               lr->f = fopen(pathname, "r");
+       if (lr->f == NULL)
+               lafe_errc(1, errno, "Couldn't open %s", pathname);
+       lr->buff_length = 8192;
+       lr->buff = malloc(lr->buff_length);
+       if (lr->buff == NULL)
+               lafe_errc(1, ENOMEM, "Can't read %s", pathname);
+       lr->line_start = lr->line_end = lr->buff_end = lr->buff;
+
+       return (lr);
+}
+
+const char *
+lafe_line_reader_next(struct lafe_line_reader *lr)
+{
+       size_t bytes_wanted, bytes_read, new_buff_size;
+       char *line_start, *p;
+
+       for (;;) {
+               /* If there's a line in the buffer, return it immediately. */
+               while (lr->line_end < lr->buff_end) {
+                       if (lr->nullSeparator) {
+                               if (*lr->line_end == '\0') {
+                                       line_start = lr->line_start;
+                                       lr->line_start = lr->line_end + 1;
+                                       lr->line_end = lr->line_start;
+                                       return (line_start);
+                               }
+                       } else if (*lr->line_end == '\x0a' || *lr->line_end == '\x0d') {
+                               *lr->line_end = '\0';
+                               line_start = lr->line_start;
+                               lr->line_start = lr->line_end + 1;
+                               lr->line_end = lr->line_start;
+                               if (line_start[0] != '\0')
+                                       return (line_start);
+                       }
+                       lr->line_end++;
+               }
+
+               /* If we're at end-of-file, process the final data. */
+               if (lr->f == NULL) {
+                       /* If there's more text, return one last line. */
+                       if (lr->line_end > lr->line_start) {
+                               *lr->line_end = '\0';
+                               line_start = lr->line_start;
+                               lr->line_start = lr->line_end + 1;
+                               lr->line_end = lr->line_start;
+                               return (line_start);
+                       }
+                       /* Otherwise, we're done. */
+                       return (NULL);
+               }
+
+               /* Buffer only has part of a line. */
+               if (lr->line_start > lr->buff) {
+                       /* Move a leftover fractional line to the beginning. */
+                       memmove(lr->buff, lr->line_start,
+                           lr->buff_end - lr->line_start);
+                       lr->buff_end -= lr->line_start - lr->buff;
+                       lr->line_end -= lr->line_start - lr->buff;
+                       lr->line_start = lr->buff;
+               } else {
+                       /* Line is too big; enlarge the buffer. */
+                       new_buff_size = lr->buff_length * 2;
+                       if (new_buff_size <= lr->buff_length)
+                               lafe_errc(1, ENOMEM,
+                                   "Line too long in %s", lr->pathname);
+                       lr->buff_length = new_buff_size;
+                       p = realloc(lr->buff, new_buff_size);
+                       if (p == NULL)
+                               lafe_errc(1, ENOMEM,
+                                   "Line too long in %s", lr->pathname);
+                       lr->buff_end = p + (lr->buff_end - lr->buff);
+                       lr->line_end = p + (lr->line_end - lr->buff);
+                       lr->line_start = lr->buff = p;
+               }
+
+               /* Get some more data into the buffer. */
+               bytes_wanted = lr->buff + lr->buff_length - lr->buff_end;
+               bytes_read = fread(lr->buff_end, 1, bytes_wanted, lr->f);
+               lr->buff_end += bytes_read;
+
+               if (ferror(lr->f))
+                       lafe_errc(1, errno, "Can't read %s", lr->pathname);
+               if (feof(lr->f)) {
+                       if (lr->f != stdin)
+                               fclose(lr->f);
+                       lr->f = NULL;
+               }
+       }
+}
+
+void
+lafe_line_reader_free(struct lafe_line_reader *lr)
+{
+       free(lr->buff);
+       free(lr->pathname);
+       free(lr);
+}
diff --git a/commands/bsdtar/libarchive_fe/line_reader.h b/commands/bsdtar/libarchive_fe/line_reader.h
new file mode 100644 (file)
index 0000000..d092c05
--- /dev/null
@@ -0,0 +1,35 @@
+/*-
+ * Copyright (c) 2009 Joerg Sonnenberger
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LAFE_LINE_READER_H
+#define LAFE_LINE_READER_H
+
+struct lafe_line_reader;
+
+struct lafe_line_reader *lafe_line_reader(const char *, int nullSeparator);
+const char *lafe_line_reader_next(struct lafe_line_reader *);
+void   lafe_line_reader_free(struct lafe_line_reader *);
+
+#endif
diff --git a/commands/bsdtar/libarchive_fe/matching.c b/commands/bsdtar/libarchive_fe/matching.c
new file mode 100644 (file)
index 0000000..f774ac7
--- /dev/null
@@ -0,0 +1,284 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "lafe_platform.h"
+__FBSDID("$FreeBSD: src/usr.bin/cpio/matching.c,v 1.2 2008/06/21 02:20:20 kientzle Exp $");
+
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+
+#include "err.h"
+#include "line_reader.h"
+#include "matching.h"
+#include "pathmatch.h"
+
+struct match {
+       struct match     *next;
+       int               matches;
+       char              pattern[1];
+};
+
+struct lafe_matching {
+       struct match     *exclusions;
+       int               exclusions_count;
+       struct match     *inclusions;
+       int               inclusions_count;
+       int               inclusions_unmatched_count;
+};
+
+static void    add_pattern(struct match **list, const char *pattern);
+static void    initialize_matching(struct lafe_matching **);
+static int     match_exclusion(struct match *, const char *pathname);
+static int     match_inclusion(struct match *, const char *pathname);
+
+/*
+ * The matching logic here needs to be re-thought.  I started out to
+ * try to mimic gtar's matching logic, but it's not entirely
+ * consistent.  In particular 'tar -t' and 'tar -x' interpret patterns
+ * on the command line as anchored, but --exclude doesn't.
+ */
+
+/*
+ * Utility functions to manage exclusion/inclusion patterns
+ */
+
+int
+lafe_exclude(struct lafe_matching **matching, const char *pattern)
+{
+
+       if (*matching == NULL)
+               initialize_matching(matching);
+       add_pattern(&((*matching)->exclusions), pattern);
+       (*matching)->exclusions_count++;
+       return (0);
+}
+
+int
+lafe_exclude_from_file(struct lafe_matching **matching, const char *pathname)
+{
+       struct lafe_line_reader *lr;
+       const char *p;
+       int ret = 0;
+
+       lr = lafe_line_reader(pathname, 0);
+       while ((p = lafe_line_reader_next(lr)) != NULL) {
+               if (lafe_exclude(matching, p) != 0)
+                       ret = -1;
+       }
+       lafe_line_reader_free(lr);
+       return (ret);
+}
+
+int
+lafe_include(struct lafe_matching **matching, const char *pattern)
+{
+
+       if (*matching == NULL)
+               initialize_matching(matching);
+       add_pattern(&((*matching)->inclusions), pattern);
+       (*matching)->inclusions_count++;
+       (*matching)->inclusions_unmatched_count++;
+       return (0);
+}
+
+int
+lafe_include_from_file(struct lafe_matching **matching, const char *pathname,
+    int nullSeparator)
+{
+       struct lafe_line_reader *lr;
+       const char *p;
+       int ret = 0;
+
+       lr = lafe_line_reader(pathname, nullSeparator);
+       while ((p = lafe_line_reader_next(lr)) != NULL) {
+               if (lafe_include(matching, p) != 0)
+                       ret = -1;
+       }
+       lafe_line_reader_free(lr);
+       return (ret);
+}
+
+static void
+add_pattern(struct match **list, const char *pattern)
+{
+       struct match *match;
+       size_t len;
+
+       len = strlen(pattern);
+       match = malloc(sizeof(*match) + len + 1);
+       if (match == NULL)
+               lafe_errc(1, errno, "Out of memory");
+       strcpy(match->pattern, pattern);
+       /* Both "foo/" and "foo" should match "foo/bar". */
+       if (len && match->pattern[len - 1] == '/')
+               match->pattern[strlen(match->pattern)-1] = '\0';
+       match->next = *list;
+       *list = match;
+       match->matches = 0;
+}
+
+
+int
+lafe_excluded(struct lafe_matching *matching, const char *pathname)
+{
+       struct match *match;
+       struct match *matched;
+
+       if (matching == NULL)
+               return (0);
+
+       /* Exclusions take priority */
+       for (match = matching->exclusions; match != NULL; match = match->next){
+               if (match_exclusion(match, pathname))
+                       return (1);
+       }
+
+       /* Then check for inclusions */
+       matched = NULL;
+       for (match = matching->inclusions; match != NULL; match = match->next){
+               if (match_inclusion(match, pathname)) {
+                       /*
+                        * If this pattern has never been matched,
+                        * then we're done.
+                        */
+                       if (match->matches == 0) {
+                               match->matches++;
+                               matching->inclusions_unmatched_count--;
+                               return (0);
+                       }
+                       /*
+                        * Otherwise, remember the match but keep checking
+                        * in case we can tick off an unmatched pattern.
+                        */
+                       matched = match;
+               }
+       }
+       /*
+        * We didn't find a pattern that had never been matched, but
+        * we did find a match, so count it and exit.
+        */
+       if (matched != NULL) {
+               matched->matches++;
+               return (0);
+       }
+
+       /* If there were inclusions, default is to exclude. */
+       if (matching->inclusions != NULL)
+           return (1);
+
+       /* No explicit inclusions, default is to match. */
+       return (0);
+}
+
+/*
+ * This is a little odd, but it matches the default behavior of
+ * gtar.  In particular, 'a*b' will match 'foo/a1111/222b/bar'
+ *
+ */
+static int
+match_exclusion(struct match *match, const char *pathname)
+{
+       return (lafe_pathmatch(match->pattern,
+                   pathname,
+                   PATHMATCH_NO_ANCHOR_START | PATHMATCH_NO_ANCHOR_END));
+}
+
+/*
+ * Again, mimic gtar:  inclusions are always anchored (have to match
+ * the beginning of the path) even though exclusions are not anchored.
+ */
+static int
+match_inclusion(struct match *match, const char *pathname)
+{
+#if 0
+       return (lafe_pathmatch(match->pattern, pathname, 0));
+#else
+       return (lafe_pathmatch(match->pattern, pathname, PATHMATCH_NO_ANCHOR_END));
+#endif 
+}
+
+void
+lafe_cleanup_exclusions(struct lafe_matching **matching)
+{
+       struct match *p, *q;
+
+       if (*matching == NULL)
+               return;
+
+       for (p = (*matching)->inclusions; p != NULL; ) {
+               q = p;
+               p = p->next;
+               free(q);
+       }
+
+       for (p = (*matching)->exclusions; p != NULL; ) {
+               q = p;
+               p = p->next;
+               free(q);
+       }
+
+       free(*matching);
+       *matching = NULL;
+}
+
+static void
+initialize_matching(struct lafe_matching **matching)
+{
+       *matching = calloc(sizeof(**matching), 1);
+       if (*matching == NULL)
+               lafe_errc(1, errno, "No memory");
+}
+
+int
+lafe_unmatched_inclusions(struct lafe_matching *matching)
+{
+
+       if (matching == NULL)
+               return (0);
+       return (matching->inclusions_unmatched_count);
+}
+
+int
+lafe_unmatched_inclusions_warn(struct lafe_matching *matching, const char *msg)
+{
+       struct match *p;
+
+       if (matching == NULL)
+               return (0);
+
+       for (p = matching->inclusions; p != NULL; p = p->next) {
+               if (p->matches == 0)
+                       lafe_warnc(0, "%s: %s", p->pattern, msg);
+       }
+
+       return (matching->inclusions_unmatched_count);
+}
diff --git a/commands/bsdtar/libarchive_fe/matching.h b/commands/bsdtar/libarchive_fe/matching.h
new file mode 100644 (file)
index 0000000..f4edebd
--- /dev/null
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer
+ *    in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef MATCHING_H
+#define MATCHING_H
+
+struct lafe_matching;
+
+int    lafe_exclude(struct lafe_matching **matching, const char *pattern);
+int    lafe_exclude_from_file(struct lafe_matching **matching,
+                              const char *pathname);
+int    lafe_include(struct lafe_matching **matching, const char *pattern);
+int    lafe_include_from_file(struct lafe_matching **matching,
+                              const char *pathname, int nullSeparator);
+
+int    lafe_excluded(struct lafe_matching *, const char *pathname);
+void   lafe_cleanup_exclusions(struct lafe_matching **);
+int    lafe_unmatched_inclusions(struct lafe_matching *);
+int    lafe_unmatched_inclusions_warn(struct lafe_matching *, const char *msg);
+
+#endif
diff --git a/commands/bsdtar/libarchive_fe/pathmatch.c b/commands/bsdtar/libarchive_fe/pathmatch.c
new file mode 100644 (file)
index 0000000..85074bd
--- /dev/null
@@ -0,0 +1,255 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer
+ *    in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "lafe_platform.h"
+__FBSDID("$FreeBSD$");
+
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+
+#include "pathmatch.h"
+
+/*
+ * Check whether a character 'c' is matched by a list specification [...]:
+ *    * Leading '!' negates the class.
+ *    * <char>-<char> is a range of characters
+ *    * \<char> removes any special meaning for <char>
+ *
+ * Some interesting boundary cases:
+ *   a-d-e is one range (a-d) followed by two single characters - and e.
+ *   \a-\d is same as a-d
+ *   a\-d is three single characters: a, d, -
+ *   Trailing - is not special (so [a-] is two characters a and -).
+ *   Initial - is not special ([a-] is same as [-a] is same as [\\-a])
+ *   This function never sees a trailing \.
+ *   [] always fails
+ *   [!] always succeeds
+ */
+static int
+pm_list(const char *start, const char *end, const char c, int flags)
+{
+       const char *p = start;
+       char rangeStart = '\0', nextRangeStart;
+       int match = 1, nomatch = 0;
+
+       /* This will be used soon... */
+       (void)flags; /* UNUSED */
+
+       /* If this is a negated class, return success for nomatch. */
+       if (*p == '!' && p < end) {
+               match = 0;
+               nomatch = 1;
+               ++p;
+       }
+
+       while (p < end) {
+               nextRangeStart = '\0';
+               switch (*p) {
+               case '-':
+                       /* Trailing or initial '-' is not special. */
+                       if ((rangeStart == '\0') || (p == end - 1)) {
+                               if (*p == c)
+                                       return (match);
+                       } else {
+                               char rangeEnd = *++p;
+                               if (rangeEnd == '\\')
+                                       rangeEnd = *++p;
+                               if ((rangeStart <= c) && (c <= rangeEnd))
+                                       return (match);
+                       }
+                       break;
+               case '\\':
+                       ++p;
+                       /* Fall through */
+               default:
+                       if (*p == c)
+                               return (match);
+                       nextRangeStart = *p; /* Possible start of range. */
+               }
+               rangeStart = nextRangeStart;
+               ++p;
+       }
+       return (nomatch);
+}
+
+/*
+ * If s is pointing to "./", ".//", "./././" or the like, skip it.
+ */
+static const char *
+pm_slashskip(const char *s) {
+       while ((*s == '/')
+           || (s[0] == '.' && s[1] == '/')
+           || (s[0] == '.' && s[1] == '\0'))
+               ++s;
+       return (s);
+}
+
+static int
+pm(const char *p, const char *s, int flags)
+{
+       const char *end;
+
+       /*
+        * Ignore leading './', './/', '././', etc.
+        */
+       if (s[0] == '.' && s[1] == '/')
+               s = pm_slashskip(s + 1);
+       if (p[0] == '.' && p[1] == '/')
+               p = pm_slashskip(p + 1);
+
+       for (;;) {
+               switch (*p) {
+               case '\0':
+                       if (s[0] == '/') {
+                               if (flags & PATHMATCH_NO_ANCHOR_END)
+                                       return (1);
+                               /* "dir" == "dir/" == "dir/." */
+                               s = pm_slashskip(s);
+                       }
+                       return (*s == '\0');
+               case '?':
+                       /* ? always succeds, unless we hit end of 's' */
+                       if (*s == '\0')
+                               return (0);
+                       break;
+               case '*':
+                       /* "*" == "**" == "***" ... */
+                       while (*p == '*')
+                               ++p;
+                       /* Trailing '*' always succeeds. */
+                       if (*p == '\0')
+                               return (1);
+                       while (*s) {
+                               if (lafe_pathmatch(p, s, flags))
+                                       return (1);
+                               ++s;
+                       }
+                       return (0);
+               case '[':
+                       /*
+                        * Find the end of the [...] character class,
+                        * ignoring \] that might occur within the class.
+                        */
+                       end = p + 1;
+                       while (*end != '\0' && *end != ']') {
+                               if (*end == '\\' && end[1] != '\0')
+                                       ++end;
+                               ++end;
+                       }
+                       if (*end == ']') {
+                               /* We found [...], try to match it. */
+                               if (!pm_list(p + 1, end, *s, flags))
+                                       return (0);
+                               p = end; /* Jump to trailing ']' char. */
+                               break;
+                       } else
+                               /* No final ']', so just match '['. */
+                               if (*p != *s)
+                                       return (0);
+                       break;
+               case '\\':
+                       /* Trailing '\\' matches itself. */
+                       if (p[1] == '\0') {
+                               if (*s != '\\')
+                                       return (0);
+                       } else {
+                               ++p;
+                               if (*p != *s)
+                                       return (0);
+                       }
+                       break;
+               case '/':
+                       if (*s != '/' && *s != '\0')
+                               return (0);
+                       /* Note: pattern "/\./" won't match "/";
+                        * pm_slashskip() correctly stops at backslash. */
+                       p = pm_slashskip(p);
+                       s = pm_slashskip(s);
+                       if (*p == '\0' && (flags & PATHMATCH_NO_ANCHOR_END))
+                               return (1);
+                       --p; /* Counteract the increment below. */
+                       --s;
+                       break;
+               case '$':
+                       /* '$' is special only at end of pattern and only
+                        * if PATHMATCH_NO_ANCHOR_END is specified. */
+                       if (p[1] == '\0' && (flags & PATHMATCH_NO_ANCHOR_END)){
+                               /* "dir" == "dir/" == "dir/." */
+                               return (*pm_slashskip(s) == '\0');
+                       }
+                       /* Otherwise, '$' is not special. */
+                       /* FALL THROUGH */
+               default:
+                       if (*p != *s)
+                               return (0);
+                       break;
+               }
+               ++p;
+               ++s;
+       }
+}
+
+/* Main entry point. */
+int
+lafe_pathmatch(const char *p, const char *s, int flags)
+{
+       /* Empty pattern only matches the empty string. */
+       if (p == NULL || *p == '\0')
+               return (s == NULL || *s == '\0');
+
+       /* Leading '^' anchors the start of the pattern. */
+       if (*p == '^') {
+               ++p;
+               flags &= ~PATHMATCH_NO_ANCHOR_START;
+       }
+
+       if (*p == '/' && *s != '/')
+               return (0);
+
+       /* Certain patterns and file names anchor implicitly. */
+       if (*p == '*' || *p == '/' || *p == '/') {
+               while (*p == '/')
+                       ++p;
+               while (*s == '/')
+                       ++s;
+               return (pm(p, s, flags));
+       }
+
+       /* If start is unanchored, try to match start of each path element. */
+       if (flags & PATHMATCH_NO_ANCHOR_START) {
+               for ( ; s != NULL; s = strchr(s, '/')) {
+                       if (*s == '/')
+                               s++;
+                       if (pm(p, s, flags))
+                               return (1);
+               }
+               return (0);
+       }
+
+       /* Default: Match from beginning. */
+       return (pm(p, s, flags));
+}
diff --git a/commands/bsdtar/libarchive_fe/pathmatch.h b/commands/bsdtar/libarchive_fe/pathmatch.h
new file mode 100644 (file)
index 0000000..a92f3ae
--- /dev/null
@@ -0,0 +1,42 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer
+ *    in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef LAFE_PATHMATCH_H
+#define LAFE_PATHMATCH_H
+
+/* Don't anchor at beginning unless the pattern starts with "^" */
+#define PATHMATCH_NO_ANCHOR_START      1
+/* Don't anchor at end unless the pattern ends with "$" */
+#define PATHMATCH_NO_ANCHOR_END        2
+
+/* Note that "^" and "$" are not special unless you set the corresponding
+ * flag above. */
+
+int lafe_pathmatch(const char *p, const char *s, int flags);
+
+#endif
diff --git a/commands/bsdtar/read.c b/commands/bsdtar/read.c
new file mode 100644 (file)
index 0000000..5e3e96e
--- /dev/null
@@ -0,0 +1,440 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "bsdtar_platform.h"
+__FBSDID("$FreeBSD: src/usr.bin/tar/read.c,v 1.40 2008/08/21 06:41:14 kientzle Exp $");
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_GRP_H
+#include <grp.h>
+#endif
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdio.h>
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#ifdef HAVE_TIME_H
+#include <time.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "bsdtar.h"
+#include "err.h"
+
+struct progress_data {
+       struct bsdtar *bsdtar;
+       struct archive *archive;
+       struct archive_entry *entry;
+};
+
+static void    list_item_verbose(struct bsdtar *, FILE *,
+                   struct archive_entry *);
+static void    read_archive(struct bsdtar *bsdtar, char mode);
+
+void
+tar_mode_t(struct bsdtar *bsdtar)
+{
+       read_archive(bsdtar, 't');
+       if (lafe_unmatched_inclusions_warn(bsdtar->matching, "Not found in archive") != 0)
+               bsdtar->return_value = 1;
+}
+
+void
+tar_mode_x(struct bsdtar *bsdtar)
+{
+       read_archive(bsdtar, 'x');
+
+       if (lafe_unmatched_inclusions_warn(bsdtar->matching, "Not found in archive") != 0)
+               bsdtar->return_value = 1;
+}
+
+static void
+progress_func(void *cookie)
+{
+       struct progress_data *progress_data = cookie;
+       struct bsdtar *bsdtar = progress_data->bsdtar;
+       struct archive *a = progress_data->archive;
+       struct archive_entry *entry = progress_data->entry;
+#ifndef __minix
+       uint64_t comp, uncomp;
+#else
+       size_t comp, uncomp;
+#endif
+       if (!need_report())
+               return;
+
+       if (bsdtar->verbose)
+               fprintf(stderr, "\n");
+       if (a != NULL) {
+               comp = archive_position_compressed(a);
+               uncomp = archive_position_uncompressed(a);
+               fprintf(stderr,
+                   "In: %s bytes, compression %d%%;",
+                   tar_i64toa(comp), (int)((uncomp - comp) * 100 / uncomp));
+               fprintf(stderr, "  Out: %d files, %s bytes\n",
+                   archive_file_count(a), tar_i64toa(uncomp));
+       }
+       if (entry != NULL) {
+               safe_fprintf(stderr, "Current: %s",
+                   archive_entry_pathname(entry));
+               fprintf(stderr, " (%s bytes)\n",
+                   tar_i64toa(archive_entry_size(entry)));
+       }
+}
+
+/*
+ * Handle 'x' and 't' modes.
+ */
+static void
+read_archive(struct bsdtar *bsdtar, char mode)
+{
+       struct progress_data    progress_data;
+       FILE                     *out;
+       struct archive           *a;
+       struct archive_entry     *entry;
+       const struct stat        *st;
+       int                       r;
+
+       while (*bsdtar->argv) {
+               lafe_include(&bsdtar->matching, *bsdtar->argv);
+               bsdtar->argv++;
+       }
+
+       if (bsdtar->names_from_file != NULL)
+               lafe_include_from_file(&bsdtar->matching,
+                   bsdtar->names_from_file, bsdtar->option_null);
+
+       a = archive_read_new();
+       if (bsdtar->compress_program != NULL)
+               archive_read_support_compression_program(a, bsdtar->compress_program);
+       else
+               archive_read_support_compression_all(a);
+       archive_read_support_format_all(a);
+       if (ARCHIVE_OK != archive_read_set_options(a, bsdtar->option_options))
+               lafe_errc(1, 0, "%s", archive_error_string(a));
+       if (archive_read_open_file(a, bsdtar->filename,
+           bsdtar->bytes_per_block != 0 ? bsdtar->bytes_per_block :
+           DEFAULT_BYTES_PER_BLOCK))
+               lafe_errc(1, 0, "Error opening archive: %s",
+                   archive_error_string(a));
+
+       do_chdir(bsdtar);
+
+       if (mode == 'x') {
+               /* Set an extract callback so that we can handle SIGINFO. */
+               progress_data.bsdtar = bsdtar;
+               progress_data.archive = a;
+               archive_read_extract_set_progress_callback(a, progress_func,
+                   &progress_data);
+       }
+
+       if (mode == 'x' && bsdtar->option_chroot) {
+#if HAVE_CHROOT
+               if (chroot(".") != 0)
+                       lafe_errc(1, errno, "Can't chroot to \".\"");
+#else
+               lafe_errc(1, 0,
+                   "chroot isn't supported on this platform");
+#endif
+       }
+
+       for (;;) {
+               /* Support --fast-read option */
+               if (bsdtar->option_fast_read &&
+                   lafe_unmatched_inclusions(bsdtar->matching) == 0)
+                       break;
+
+               r = archive_read_next_header(a, &entry);
+               progress_data.entry = entry;
+               if (r == ARCHIVE_EOF)
+                       break;
+               if (r < ARCHIVE_OK)
+                       lafe_warnc(0, "%s", archive_error_string(a));
+               if (r <= ARCHIVE_WARN)
+                       bsdtar->return_value = 1;
+               if (r == ARCHIVE_RETRY) {
+                       /* Retryable error: try again */
+                       lafe_warnc(0, "Retrying...");
+                       continue;
+               }
+               if (r == ARCHIVE_FATAL)
+                       break;
+
+               if (bsdtar->option_numeric_owner) {
+                       archive_entry_set_uname(entry, NULL);
+                       archive_entry_set_gname(entry, NULL);
+               }
+
+               /*
+                * Exclude entries that are too old.
+                */
+               st = archive_entry_stat(entry);
+               if (bsdtar->newer_ctime_sec > 0) {
+                       if (st->st_ctime < bsdtar->newer_ctime_sec)
+                               continue; /* Too old, skip it. */
+                       if (st->st_ctime == bsdtar->newer_ctime_sec
+                           && ARCHIVE_STAT_CTIME_NANOS(st)
+                           <= bsdtar->newer_ctime_nsec)
+                               continue; /* Too old, skip it. */
+               }
+               if (bsdtar->newer_mtime_sec > 0) {
+                       if (st->st_mtime < bsdtar->newer_mtime_sec)
+                               continue; /* Too old, skip it. */
+                       if (st->st_mtime == bsdtar->newer_mtime_sec
+                           && ARCHIVE_STAT_MTIME_NANOS(st)
+                           <= bsdtar->newer_mtime_nsec)
+                               continue; /* Too old, skip it. */
+               }
+
+               /*
+                * Note that pattern exclusions are checked before
+                * pathname rewrites are handled.  This gives more
+                * control over exclusions, since rewrites always lose
+                * information.  (For example, consider a rewrite
+                * s/foo[0-9]/foo/.  If we check exclusions after the
+                * rewrite, there would be no way to exclude foo1/bar
+                * while allowing foo2/bar.)
+                */
+               if (lafe_excluded(bsdtar->matching, archive_entry_pathname(entry)))
+                       continue; /* Excluded by a pattern test. */
+
+               if (mode == 't') {
+                       /* Perversely, gtar uses -O to mean "send to stderr"
+                        * when used with -t. */
+                       out = bsdtar->option_stdout ? stderr : stdout;
+
+                       /*
+                        * TODO: Provide some reasonable way to
+                        * preview rewrites.  gtar always displays
+                        * the unedited path in -t output, which means
+                        * you cannot easily preview rewrites.
+                        */
+                       if (bsdtar->verbose < 2)
+                               safe_fprintf(out, "%s",
+                                   archive_entry_pathname(entry));
+                       else
+                               list_item_verbose(bsdtar, out, entry);
+                       fflush(out);
+                       r = archive_read_data_skip(a);
+                       if (r == ARCHIVE_WARN) {
+                               fprintf(out, "\n");
+                               lafe_warnc(0, "%s",
+                                   archive_error_string(a));
+                       }
+                       if (r == ARCHIVE_RETRY) {
+                               fprintf(out, "\n");
+                               lafe_warnc(0, "%s",
+                                   archive_error_string(a));
+                       }
+                       if (r == ARCHIVE_FATAL) {
+                               fprintf(out, "\n");
+                               lafe_warnc(0, "%s",
+                                   archive_error_string(a));
+                               bsdtar->return_value = 1;
+                               break;
+                       }
+                       fprintf(out, "\n");
+               } else {
+                       /* Note: some rewrite failures prevent extraction. */
+                       if (edit_pathname(bsdtar, entry))
+                               continue; /* Excluded by a rewrite failure. */
+
+                       if (bsdtar->option_interactive &&
+                           !yes("extract '%s'", archive_entry_pathname(entry)))
+                               continue;
+
+                       /*
+                        * Format here is from SUSv2, including the
+                        * deferred '\n'.
+                        */
+                       if (bsdtar->verbose) {
+                               safe_fprintf(stderr, "x %s",
+                                   archive_entry_pathname(entry));
+                               fflush(stderr);
+                       }
+
+                       /* TODO siginfo_printinfo(bsdtar, 0); */
+
+                       if (bsdtar->option_stdout)
+                               r = archive_read_data_into_fd(a, 1);
+                       else
+                               r = archive_read_extract(a, entry,
+                                   bsdtar->extract_flags);
+                       if (r != ARCHIVE_OK) {
+                               if (!bsdtar->verbose)
+                                       safe_fprintf(stderr, "%s",
+                                           archive_entry_pathname(entry));
+                               safe_fprintf(stderr, ": %s",
+                                   archive_error_string(a));
+                               if (!bsdtar->verbose)
+                                       fprintf(stderr, "\n");
+                               bsdtar->return_value = 1;
+                       }
+                       if (bsdtar->verbose)
+                               fprintf(stderr, "\n");
+                       if (r == ARCHIVE_FATAL)
+                               break;
+               }
+       }
+
+
+       r = archive_read_close(a);
+       if (r != ARCHIVE_OK)
+               lafe_warnc(0, "%s", archive_error_string(a));
+       if (r <= ARCHIVE_WARN)
+               bsdtar->return_value = 1;
+
+       if (bsdtar->verbose > 2)
+               fprintf(stdout, "Archive Format: %s,  Compression: %s\n",
+                   archive_format_name(a), archive_compression_name(a));
+
+       archive_read_finish(a);
+}
+
+
+/*
+ * Display information about the current file.
+ *
+ * The format here roughly duplicates the output of 'ls -l'.
+ * This is based on SUSv2, where 'tar tv' is documented as
+ * listing additional information in an "unspecified format,"
+ * and 'pax -l' is documented as using the same format as 'ls -l'.
+ */
+static void
+list_item_verbose(struct bsdtar *bsdtar, FILE *out, struct archive_entry *entry)
+{
+       char                     tmp[100];
+       size_t                   w;
+       const char              *p;
+       const char              *fmt;
+       time_t                   tim;
+       static time_t            now;
+
+       /*
+        * We avoid collecting the entire list in memory at once by
+        * listing things as we see them.  However, that also means we can't
+        * just pre-compute the field widths.  Instead, we start with guesses
+        * and just widen them as necessary.  These numbers are completely
+        * arbitrary.
+        */
+       if (!bsdtar->u_width) {
+               bsdtar->u_width = 6;
+               bsdtar->gs_width = 13;
+       }
+       if (!now)
+               time(&now);
+       fprintf(out, "%s %d ",
+           archive_entry_strmode(entry),
+           archive_entry_nlink(entry));
+
+       /* Use uname if it's present, else uid. */
+       p = archive_entry_uname(entry);
+       if ((p == NULL) || (*p == '\0')) {
+               sprintf(tmp, "%lu ",
+                   (unsigned long)archive_entry_uid(entry));
+               p = tmp;
+       }
+       w = strlen(p);
+       if (w > bsdtar->u_width)
+               bsdtar->u_width = w;
+       fprintf(out, "%-*s ", (int)bsdtar->u_width, p);
+
+       /* Use gname if it's present, else gid. */
+       p = archive_entry_gname(entry);
+       if (p != NULL && p[0] != '\0') {
+               fprintf(out, "%s", p);
+               w = strlen(p);
+       } else {
+               sprintf(tmp, "%lu",
+                   (unsigned long)archive_entry_gid(entry));
+               w = strlen(tmp);
+               fprintf(out, "%s", tmp);
+       }
+
+       /*
+        * Print device number or file size, right-aligned so as to make
+        * total width of group and devnum/filesize fields be gs_width.
+        * If gs_width is too small, grow it.
+        */
+       if (archive_entry_filetype(entry) == AE_IFCHR
+           || archive_entry_filetype(entry) == AE_IFBLK) {
+               sprintf(tmp, "%lu,%lu",
+                   (unsigned long)archive_entry_rdevmajor(entry),
+                   (unsigned long)archive_entry_rdevminor(entry));
+       } else {
+               strcpy(tmp, tar_i64toa(archive_entry_size(entry)));
+       }
+       if (w + strlen(tmp) >= bsdtar->gs_width)
+               bsdtar->gs_width = w+strlen(tmp)+1;
+       fprintf(out, "%*s", (int)(bsdtar->gs_width - w), tmp);
+
+       /* Format the time using 'ls -l' conventions. */
+       tim = archive_entry_mtime(entry);
+#define HALF_YEAR (time_t)365 * 86400 / 2
+#if defined(_WIN32) && !defined(__CYGWIN__)
+#define DAY_FMT  "%d"  /* Windows' strftime function does not support %e format. */
+#else
+#define DAY_FMT  "%e"  /* Day number without leading zeros */
+#endif
+       if (tim < now - HALF_YEAR || tim > now + HALF_YEAR)
+               fmt = bsdtar->day_first ? DAY_FMT " %b  %Y" : "%b " DAY_FMT "  %Y";
+       else
+               fmt = bsdtar->day_first ? DAY_FMT " %b %H:%M" : "%b " DAY_FMT " %H:%M";
+       strftime(tmp, sizeof(tmp), fmt, localtime(&tim));
+       fprintf(out, " %s ", tmp);
+       safe_fprintf(out, "%s", archive_entry_pathname(entry));
+
+       /* Extra information for links. */
+       if (archive_entry_hardlink(entry)) /* Hard link */
+               safe_fprintf(out, " link to %s",
+                   archive_entry_hardlink(entry));
+       else if (archive_entry_symlink(entry)) /* Symbolic link */
+               safe_fprintf(out, " -> %s", archive_entry_symlink(entry));
+}
diff --git a/commands/bsdtar/subst.c b/commands/bsdtar/subst.c
new file mode 100644 (file)
index 0000000..3982054
--- /dev/null
@@ -0,0 +1,289 @@
+/*-
+ * Copyright (c) 2008 Joerg Sonnenberger
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "bsdtar_platform.h"
+__FBSDID("$FreeBSD: src/usr.bin/tar/subst.c,v 1.4 2008/06/15 10:08:16 kientzle Exp $");
+
+#if HAVE_REGEX_H
+#include "bsdtar.h"
+
+#include <errno.h>
+#include <regex.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef REG_BASIC
+#define        REG_BASIC 0
+#endif
+
+#include "err.h"
+
+struct subst_rule {
+       struct subst_rule *next;
+       regex_t re;
+       char *result;
+       unsigned int global:1, print:1, symlink:1;
+};
+
+struct substitution {
+       struct subst_rule *first_rule, *last_rule;
+};
+
+static void
+init_substitution(struct bsdtar *bsdtar)
+{
+       struct substitution *subst;
+
+       bsdtar->substitution = subst = malloc(sizeof(*subst));
+       if (subst == NULL)
+               lafe_errc(1, errno, "Out of memory");
+       subst->first_rule = subst->last_rule = NULL;
+}
+
+void
+add_substitution(struct bsdtar *bsdtar, const char *rule_text)
+{
+       struct subst_rule *rule;
+       struct substitution *subst;
+       const char *end_pattern, *start_subst;
+       char *pattern;
+       int r;
+
+       if ((subst = bsdtar->substitution) == NULL) {
+               init_substitution(bsdtar);
+               subst = bsdtar->substitution;
+       }
+
+       rule = malloc(sizeof(*rule));
+       if (rule == NULL)
+               lafe_errc(1, errno, "Out of memory");
+       rule->next = NULL;
+
+       if (subst->last_rule == NULL)
+               subst->first_rule = rule;
+       else
+               subst->last_rule->next = rule;
+       subst->last_rule = rule;
+
+       if (*rule_text == '\0')
+               lafe_errc(1, 0, "Empty replacement string");
+       end_pattern = strchr(rule_text + 1, *rule_text);
+       if (end_pattern == NULL)
+               lafe_errc(1, 0, "Invalid replacement string");
+
+       pattern = malloc(end_pattern - rule_text);
+       if (pattern == NULL)
+               lafe_errc(1, errno, "Out of memory");
+       memcpy(pattern, rule_text + 1, end_pattern - rule_text - 1);
+       pattern[end_pattern - rule_text - 1] = '\0';
+
+       if ((r = regcomp(&rule->re, pattern, REG_BASIC)) != 0) {
+               char buf[80];
+               regerror(r, &rule->re, buf, sizeof(buf));
+               lafe_errc(1, 0, "Invalid regular expression: %s", buf);
+       }
+       free(pattern);
+
+       start_subst = end_pattern + 1;
+       end_pattern = strchr(start_subst, *rule_text);
+       if (end_pattern == NULL)
+               lafe_errc(1, 0, "Invalid replacement string");
+
+       rule->result = malloc(end_pattern - start_subst + 1);
+       if (rule->result == NULL)
+               lafe_errc(1, errno, "Out of memory");
+       memcpy(rule->result, start_subst, end_pattern - start_subst);
+       rule->result[end_pattern - start_subst] = '\0';
+
+       rule->global = 0;
+       rule->print = 0;
+       rule->symlink = 0;
+
+       while (*++end_pattern) {
+               switch (*end_pattern) {
+               case 'g':
+               case 'G':
+                       rule->global = 1;
+                       break;
+               case 'p':
+               case 'P':
+                       rule->print = 1;
+                       break;
+               case 's':
+               case 'S':
+                       rule->symlink = 1;
+                       break;
+               default:
+                       lafe_errc(1, 0, "Invalid replacement flag %c", *end_pattern);
+               }
+       }
+}
+
+static void
+realloc_strncat(char **str, const char *append, size_t len)
+{
+       char *new_str;
+       size_t old_len;
+
+       if (*str == NULL)
+               old_len = 0;
+       else
+               old_len = strlen(*str);
+
+       new_str = malloc(old_len + len + 1);
+       if (new_str == NULL)
+               lafe_errc(1, errno, "Out of memory");
+       memcpy(new_str, *str, old_len);
+       memcpy(new_str + old_len, append, len);
+       new_str[old_len + len] = '\0';
+       free(*str);
+       *str = new_str;
+}
+
+static void
+realloc_strcat(char **str, const char *append)
+{
+       char *new_str;
+       size_t old_len;
+
+       if (*str == NULL)
+               old_len = 0;
+       else
+               old_len = strlen(*str);
+
+       new_str = malloc(old_len + strlen(append) + 1);
+       if (new_str == NULL)
+               lafe_errc(1, errno, "Out of memory");
+       memcpy(new_str, *str, old_len);
+       strcpy(new_str + old_len, append);
+       free(*str);
+       *str = new_str;
+}
+
+int
+apply_substitution(struct bsdtar *bsdtar, const char *name, char **result, int symlink_only)
+{
+       const char *path = name;
+       regmatch_t matches[10];
+       size_t i, j;
+       struct subst_rule *rule;
+       struct substitution *subst;
+       int c, got_match, print_match;
+
+       *result = NULL;
+
+       if ((subst = bsdtar->substitution) == NULL)
+               return 0;
+
+       got_match = 0;
+       print_match = 0;
+
+       for (rule = subst->first_rule; rule != NULL; rule = rule->next) {
+               if (symlink_only && !rule->symlink)
+                       continue;
+               if (regexec(&rule->re, name, 10, matches, 0))
+                       continue;
+
+               got_match = 1;
+               print_match |= rule->print;
+               realloc_strncat(result, name, matches[0].rm_so);
+
+               for (i = 0, j = 0; rule->result[i] != '\0'; ++i) {
+                       if (rule->result[i] == '~') {
+                               realloc_strncat(result, rule->result + j, i - j);
+                               realloc_strncat(result, name, matches[0].rm_eo);
+                               j = i + 1;
+                               continue;
+                       }
+                       if (rule->result[i] != '\\')
+                               continue;
+
+                       ++i;
+                       c = rule->result[i];
+                       switch (c) {
+                       case '~':
+                       case '\\':
+                               realloc_strncat(result, rule->result + j, i - j - 1);
+                               j = i;
+                               break;
+                       case '1':
+                       case '2':
+                       case '3':
+                       case '4':
+                       case '5':
+                       case '6':
+                       case '7':
+                       case '8':
+                       case '9':
+                               realloc_strncat(result, rule->result + j, i - j - 1);
+                               if ((size_t)(c - '0') > (size_t)(rule->re.re_nsub)) {
+                                       free(*result);
+                                       *result = NULL;
+                                       return -1;
+                               }
+                               realloc_strncat(result, name + matches[c - '0'].rm_so, matches[c - '0'].rm_eo - matches[c - '0'].rm_so);
+                               j = i + 1;
+                               break;
+                       default:
+                               /* Just continue; */
+                               break;
+                       }
+
+               }
+
+               realloc_strcat(result, rule->result + j);
+
+               name += matches[0].rm_eo;
+
+               if (!rule->global)
+                       break;
+       }
+
+       if (got_match)
+               realloc_strcat(result, name);
+
+       if (print_match)
+               fprintf(stderr, "%s >> %s\n", path, *result);
+
+       return got_match;
+}
+
+void
+cleanup_substitution(struct bsdtar *bsdtar)
+{
+       struct subst_rule *rule;
+       struct substitution *subst;
+
+       if ((subst = bsdtar->substitution) == NULL)
+               return;
+
+       while ((rule = subst->first_rule) != NULL) {
+               subst->first_rule = rule->next;
+               free(rule->result);
+               free(rule);
+       }
+       free(subst);
+}
+#endif /* HAVE_REGEX_H */
diff --git a/commands/bsdtar/test/.deps/bsdtar_test-main.Po b/commands/bsdtar/test/.deps/bsdtar_test-main.Po
new file mode 100644 (file)
index 0000000..9ce06a8
--- /dev/null
@@ -0,0 +1 @@
+# dummy
diff --git a/commands/bsdtar/test/.deps/bsdtar_test-test_0.Po b/commands/bsdtar/test/.deps/bsdtar_test-test_0.Po
new file mode 100644 (file)
index 0000000..9ce06a8
--- /dev/null
@@ -0,0 +1 @@
+# dummy
diff --git a/commands/bsdtar/test/.deps/bsdtar_test-test_basic.Po b/commands/bsdtar/test/.deps/bsdtar_test-test_basic.Po
new file mode 100644 (file)
index 0000000..9ce06a8
--- /dev/null
@@ -0,0 +1 @@
+# dummy
diff --git a/commands/bsdtar/test/.deps/bsdtar_test-test_copy.Po b/commands/bsdtar/test/.deps/bsdtar_test-test_copy.Po
new file mode 100644 (file)
index 0000000..9ce06a8
--- /dev/null
@@ -0,0 +1 @@
+# dummy
diff --git a/commands/bsdtar/test/.deps/bsdtar_test-test_empty_mtree.Po b/commands/bsdtar/test/.deps/bsdtar_test-test_empty_mtree.Po
new file mode 100644 (file)
index 0000000..9ce06a8
--- /dev/null
@@ -0,0 +1 @@
+# dummy
diff --git a/commands/bsdtar/test/.deps/bsdtar_test-test_getdate.Po b/commands/bsdtar/test/.deps/bsdtar_test-test_getdate.Po
new file mode 100644 (file)
index 0000000..9ce06a8
--- /dev/null
@@ -0,0 +1 @@
+# dummy
diff --git a/commands/bsdtar/test/.deps/bsdtar_test-test_help.Po b/commands/bsdtar/test/.deps/bsdtar_test-test_help.Po
new file mode 100644 (file)
index 0000000..9ce06a8
--- /dev/null
@@ -0,0 +1 @@
+# dummy
diff --git a/commands/bsdtar/test/.deps/bsdtar_test-test_option_T_upper.Po b/commands/bsdtar/test/.deps/bsdtar_test-test_option_T_upper.Po
new file mode 100644 (file)
index 0000000..9ce06a8
--- /dev/null
@@ -0,0 +1 @@
+# dummy
diff --git a/commands/bsdtar/test/.deps/bsdtar_test-test_option_q.Po b/commands/bsdtar/test/.deps/bsdtar_test-test_option_q.Po
new file mode 100644 (file)
index 0000000..9ce06a8
--- /dev/null
@@ -0,0 +1 @@
+# dummy
diff --git a/commands/bsdtar/test/.deps/bsdtar_test-test_option_r.Po b/commands/bsdtar/test/.deps/bsdtar_test-test_option_r.Po
new file mode 100644 (file)
index 0000000..9ce06a8
--- /dev/null
@@ -0,0 +1 @@
+# dummy
diff --git a/commands/bsdtar/test/.deps/bsdtar_test-test_option_s.Po b/commands/bsdtar/test/.deps/bsdtar_test-test_option_s.Po
new file mode 100644 (file)
index 0000000..9ce06a8
--- /dev/null
@@ -0,0 +1 @@
+# dummy
diff --git a/commands/bsdtar/test/.deps/bsdtar_test-test_patterns.Po b/commands/bsdtar/test/.deps/bsdtar_test-test_patterns.Po
new file mode 100644 (file)
index 0000000..9ce06a8
--- /dev/null
@@ -0,0 +1 @@
+# dummy
diff --git a/commands/bsdtar/test/.deps/bsdtar_test-test_stdio.Po b/commands/bsdtar/test/.deps/bsdtar_test-test_stdio.Po
new file mode 100644 (file)
index 0000000..9ce06a8
--- /dev/null
@@ -0,0 +1 @@
+# dummy
diff --git a/commands/bsdtar/test/.deps/bsdtar_test-test_strip_components.Po b/commands/bsdtar/test/.deps/bsdtar_test-test_strip_components.Po
new file mode 100644 (file)
index 0000000..9ce06a8
--- /dev/null
@@ -0,0 +1 @@
+# dummy
diff --git a/commands/bsdtar/test/.deps/bsdtar_test-test_symlink_dir.Po b/commands/bsdtar/test/.deps/bsdtar_test-test_symlink_dir.Po
new file mode 100644 (file)
index 0000000..9ce06a8
--- /dev/null
@@ -0,0 +1 @@
+# dummy
diff --git a/commands/bsdtar/test/.deps/bsdtar_test-test_version.Po b/commands/bsdtar/test/.deps/bsdtar_test-test_version.Po
new file mode 100644 (file)
index 0000000..9ce06a8
--- /dev/null
@@ -0,0 +1 @@
+# dummy
diff --git a/commands/bsdtar/test/.deps/bsdtar_test-test_windows.Po b/commands/bsdtar/test/.deps/bsdtar_test-test_windows.Po
new file mode 100644 (file)
index 0000000..9ce06a8
--- /dev/null
@@ -0,0 +1 @@
+# dummy
diff --git a/commands/bsdtar/test/CMakeLists.txt b/commands/bsdtar/test/CMakeLists.txt
new file mode 100644 (file)
index 0000000..6064e14
--- /dev/null
@@ -0,0 +1,67 @@
+############################################
+#
+# How to build bsdtar_test
+#
+############################################
+IF(ENABLE_TAR AND ENABLE_TEST)
+  SET(bsdtar_test_SOURCES
+    ../getdate.c
+    main.c
+    test.h
+    test_0.c
+    test_basic.c
+    test_copy.c
+    test_empty_mtree.c
+    test_getdate.c
+    test_help.c
+    test_option_T_upper.c
+    test_option_q.c
+    test_option_r.c
+    test_option_s.c
+    test_patterns.c
+    test_stdio.c
+    test_strip_components.c
+    test_symlink_dir.c
+    test_version.c
+    test_windows.c
+  )
+  IF(WIN32 AND NOT CYGWIN)
+    LIST(APPEND bsdtar_test_SOURCES ../bsdtar_windows.c)
+    LIST(APPEND bsdtar_test_SOURCES ../bsdtar_windows.h)
+  ENDIF(WIN32 AND NOT CYGWIN)
+
+  #
+  # Register target
+  #
+  ADD_EXECUTABLE(bsdtar_test ${bsdtar_test_SOURCES})
+  SET_PROPERTY(TARGET bsdtar_test PROPERTY COMPILE_DEFINITIONS LIST_H)
+
+  #
+  # Generate list.h by grepping DEFINE_TEST() lines out of the C sources.
+  #
+  GENERATE_LIST_H(${CMAKE_CURRENT_BINARY_DIR}/list.h
+    ${CMAKE_CURRENT_LIST_FILE} ${bsdtar_test_SOURCES})
+  SET_PROPERTY(DIRECTORY APPEND PROPERTY INCLUDE_DIRECTORIES
+    ${CMAKE_CURRENT_BINARY_DIR})
+
+  # list.h has a line DEFINE_TEST(testname) for every
+  # test.  We can use that to define the tests for cmake by
+  # defining a DEFINE_TEST macro and reading list.h in.
+  MACRO (DEFINE_TEST _testname)
+    ADD_TEST_28(
+      NAME bsdtar_${_testname}
+      COMMAND bsdtar_test -vv
+                          -p $<TARGET_FILE:bsdtar>
+                          -r ${CMAKE_CURRENT_SOURCE_DIR}
+                          ${_testname})
+  ENDMACRO (DEFINE_TEST _testname)
+
+  INCLUDE(${CMAKE_CURRENT_BINARY_DIR}/list.h)
+
+  # Experimental new test handling
+  ADD_CUSTOM_TARGET(run_bsdtar_test
+       COMMAND bsdtar_test -p ${BSDTAR} -r ${CMAKE_CURRENT_SOURCE_DIR})
+  ADD_DEPENDENCIES(run_bsdtar_test bsdtar)
+  ADD_DEPENDENCIES(run_all_tests run_bsdtar_test)
+
+ENDIF (ENABLE_TAR AND ENABLE_TEST)
diff --git a/commands/bsdtar/test/config.sh b/commands/bsdtar/test/config.sh
new file mode 100755 (executable)
index 0000000..2d884f8
--- /dev/null
@@ -0,0 +1,75 @@
+#
+# Copyright (c) 2007 Tim Kientzle
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# $FreeBSD: src/usr.bin/tar/test/config.sh,v 1.2 2007/03/11 19:33:45 kientzle Exp $
+
+THISDIR=`cd \`dirname $0\`;/bin/pwd`
+
+# TESTDIR defaults to /tmp/bsdtar- + the name of the script
+if [ -z "$TESTDIR" ]; then
+    TESTDIR=/tmp/bsdtar-`echo $0 | sed -e 's|.*/||' -e 's|\.sh||' -e 's/[^a-z0-9_-]/_/g'`
+fi
+
+# Find bsdtar
+# The first three paths here are the usual locations of a bsdtar
+# that has just been built.  The remaining paths might find a bsdtar
+# installed on the local system somewhere.
+if [ -z "$BSDTAR" ]; then
+    for T in "$THISDIR/../bsdtar" "$THISDIR/../../bsdtar"              \
+       "/usr/obj`dirname $THISDIR`/bsdtar" "/usr/local/bin/bsdtar"     \
+       "/usr/bin/bsdtar" "/usr/bin/tar" "bsdtar" "tar"
+      do
+      if ( /bin/sh -c "$T --version" | grep "bsdtar" ) >/dev/null 2>&1; then
+         BSDTAR="$T"
+         break
+      fi
+    done
+fi
+
+# Find GNU tar
+if [ -z "$GTAR" ]; then
+    for T in gtar gnutar tar /usr/local/bin/gtar* /usr/local/bin/gnutar* /usr/bin/gtar* /usr/bin/gnutar*
+    do
+       if ( /bin/sh -c "$T --version" | grep "GNU tar" ) >/dev/null 2>&1; then
+           GTAR="$T"
+           break
+       fi
+    done
+fi
+
+# Find CPIO
+if [ -z "$CPIO" ]; then
+    CPIO=cpio
+fi
+
+echo BSDTAR=$BSDTAR '('`$BSDTAR --version`')'
+echo GTAR=$GTAR '('`$GTAR --version | head -n 1`')'
+echo CPIO=$CPIO '('`$CPIO --version`')'
+
+# Remove and recreate the directory we'll use for these tests
+rm -rf $TESTDIR
+mkdir -p $TESTDIR || exit 1
+cd $TESTDIR || exit 1
+echo TESTDIR=$TESTDIR
+
diff --git a/commands/bsdtar/test/list.h b/commands/bsdtar/test/list.h
new file mode 100644 (file)
index 0000000..4b91fb6
--- /dev/null
@@ -0,0 +1,16 @@
+DEFINE_TEST(test_0)
+DEFINE_TEST(test_basic)
+DEFINE_TEST(test_copy)
+DEFINE_TEST(test_empty_mtree)
+DEFINE_TEST(test_getdate)
+DEFINE_TEST(test_help)
+DEFINE_TEST(test_option_T_upper)
+DEFINE_TEST(test_option_q)
+DEFINE_TEST(test_option_r)
+DEFINE_TEST(test_option_s)
+DEFINE_TEST(test_patterns)
+DEFINE_TEST(test_stdio)
+DEFINE_TEST(test_strip_components)
+DEFINE_TEST(test_symlink_dir)
+DEFINE_TEST(test_version)
+DEFINE_TEST(test_windows)
diff --git a/commands/bsdtar/test/main.c b/commands/bsdtar/test/main.c
new file mode 100644 (file)
index 0000000..6028d77
--- /dev/null
@@ -0,0 +1,2226 @@
+/*
+ * Copyright (c) 2003-2009 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "test.h"
+#include <errno.h>
+#include <locale.h>
+#include <stdarg.h>
+#include <time.h>
+
+/*
+ * This same file is used pretty much verbatim for all test harnesses.
+ *
+ * The next few lines are the only differences.
+ * TODO: Move this into a separate configuration header, have all test
+ * suites share one copy of this file.
+ */
+__FBSDID("$FreeBSD: src/usr.bin/tar/test/main.c,v 1.6 2008/11/05 06:40:53 kientzle Exp $");
+#define KNOWNREF       "test_patterns_2.tar.uu"
+#define ENVBASE "BSDTAR"  /* Prefix for environment variables. */
+#define        PROGRAM "bsdtar"  /* Name of program being tested. */
+#undef LIBRARY           /* Not testing a library. */
+#undef EXTRA_DUMP           /* How to dump extra data */
+/* How to generate extra version info. */
+#define        EXTRA_VERSION    (systemf("%s --version", testprog) ? "" : "")
+
+/*
+ *
+ * Windows support routines
+ *
+ * Note: Configuration is a tricky issue.  Using HAVE_* feature macros
+ * in the test harness is dangerous because they cover up
+ * configuration errors.  The classic example of this is omitting a
+ * configure check.  If libarchive and libarchive_test both look for
+ * the same feature macro, such errors are hard to detect.  Platform
+ * macros (e.g., _WIN32 or __GNUC__) are a little better, but can
+ * easily lead to very messy code.  It's best to limit yourself
+ * to only the most generic programming techniques in the test harness
+ * and thus avoid conditionals altogether.  Where that's not possible,
+ * try to minimize conditionals by grouping platform-specific tests in
+ * one place (e.g., test_acl_freebsd) or by adding new assert()
+ * functions (e.g., assertMakeHardlink()) to cover up platform
+ * differences.  Platform-specific coding in libarchive_test is often
+ * a symptom that some capability is missing from libarchive itself.
+ */
+#if defined(_WIN32) && !defined(__CYGWIN__)
+#include <io.h>
+#include <windows.h>
+#ifndef F_OK
+#define F_OK (0)
+#endif
+#ifndef S_ISDIR
+#define S_ISDIR(m)  ((m) & _S_IFDIR)
+#endif
+#ifndef S_ISREG
+#define S_ISREG(m)  ((m) & _S_IFREG)
+#endif
+#if !defined(__BORLANDC__)
+#define access _access
+#undef chdir
+#define chdir _chdir
+#endif
+#ifndef fileno
+#define fileno _fileno
+#endif
+/*#define fstat _fstat64*/
+#if !defined(__BORLANDC__)
+#define getcwd _getcwd
+#endif
+#define lstat stat
+/*#define lstat _stat64*/
+/*#define stat _stat64*/
+#define rmdir _rmdir
+#if !defined(__BORLANDC__)
+#define strdup _strdup
+#define umask _umask
+#endif
+#define int64_t __int64
+#endif
+
+#if defined(HAVE__CrtSetReportMode)
+# include <crtdbg.h>
+#endif
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+void *GetFunctionKernel32(const char *name)
+{
+       static HINSTANCE lib;
+       static int set;
+       if (!set) {
+               set = 1;
+               lib = LoadLibrary("kernel32.dll");
+       }
+       if (lib == NULL) {
+               fprintf(stderr, "Can't load kernel32.dll?!\n");
+               exit(1);
+       }
+       return (void *)GetProcAddress(lib, name);
+}
+
+static int
+my_CreateSymbolicLinkA(const char *linkname, const char *target, int flags)
+{
+       static BOOLEAN (WINAPI *f)(LPCSTR, LPCSTR, DWORD);
+       static int set;
+       if (!set) {
+               set = 1;
+               f = GetFunctionKernel32("CreateSymbolicLinkA");
+       }
+       return f == NULL ? 0 : (*f)(linkname, target, flags);
+}
+
+static int
+my_CreateHardLinkA(const char *linkname, const char *target)
+{
+       static BOOLEAN (WINAPI *f)(LPCSTR, LPCSTR, LPSECURITY_ATTRIBUTES);
+       static int set;
+       if (!set) {
+               set = 1;
+               f = GetFunctionKernel32("CreateHardLinkA");
+       }
+       return f == NULL ? 0 : (*f)(linkname, target, NULL);
+}
+
+int
+my_GetFileInformationByName(const char *path, BY_HANDLE_FILE_INFORMATION *bhfi)
+{
+       HANDLE h;
+       int r;
+
+       memset(bhfi, 0, sizeof(*bhfi));
+       h = CreateFile(path, FILE_READ_ATTRIBUTES, 0, NULL,
+               OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+       if (h == INVALID_HANDLE_VALUE)
+               return (0);
+       r = GetFileInformationByHandle(h, bhfi);
+       CloseHandle(h);
+       return (r);
+}
+#endif
+
+#if defined(HAVE__CrtSetReportMode)
+static void
+invalid_parameter_handler(const wchar_t * expression,
+    const wchar_t * function, const wchar_t * file,
+    unsigned int line, uintptr_t pReserved)
+{
+       /* nop */
+}
+#endif
+
+/*
+ *
+ * OPTIONS FLAGS
+ *
+ */
+
+/* Enable core dump on failure. */
+static int dump_on_failure = 0;
+/* Default is to remove temp dirs and log data for successful tests. */
+static int keep_temp_files = 0;
+/* Default is to just report pass/fail for each test. */
+static int verbosity = 0;
+#define        VERBOSITY_SUMMARY_ONLY -1 /* -q */
+#define VERBOSITY_PASSFAIL 0   /* Default */
+#define VERBOSITY_LIGHT_REPORT 1 /* -v */
+#define VERBOSITY_FULL 2 /* -vv */
+/* A few places generate even more output for verbosity > VERBOSITY_FULL,
+ * mostly for debugging the test harness itself. */
+/* Cumulative count of assertion failures. */
+static int failures = 0;
+/* Cumulative count of reported skips. */
+static int skips = 0;
+/* Cumulative count of assertions checked. */
+static int assertions = 0;
+
+/* Directory where uuencoded reference files can be found. */
+static const char *refdir;
+
+/*
+ * Report log information selectively to console and/or disk log.
+ */
+static int log_console = 0;
+static FILE *logfile;
+static void
+vlogprintf(const char *fmt, va_list ap)
+{
+#ifdef va_copy
+       va_list lfap;
+       va_copy(lfap, ap);
+#endif
+       if (log_console)
+               vfprintf(stdout, fmt, ap);
+       if (logfile != NULL)
+#ifdef va_copy
+               vfprintf(logfile, fmt, lfap);
+       va_end(lfap);
+#else
+               vfprintf(logfile, fmt, ap);
+#endif
+}
+
+static void
+logprintf(const char *fmt, ...)
+{
+       va_list ap;
+       va_start(ap, fmt);
+       vlogprintf(fmt, ap);
+       va_end(ap);
+}
+
+/* Set up a message to display only if next assertion fails. */
+static char msgbuff[4096];
+static const char *msg, *nextmsg;
+void
+failure(const char *fmt, ...)
+{
+       va_list ap;
+       va_start(ap, fmt);
+       vsprintf(msgbuff, fmt, ap);
+       va_end(ap);
+       nextmsg = msgbuff;
+}
+
+/*
+ * Copy arguments into file-local variables.
+ * This was added to permit vararg assert() functions without needing
+ * variadic wrapper macros.  Turns out that the vararg capability is almost
+ * never used, so almost all of the vararg assertions can be simplified
+ * by removing the vararg capability and reworking the wrapper macro to
+ * pass __FILE__, __LINE__ directly into the function instead of using
+ * this hook.  I suspect this machinery is used so rarely that we
+ * would be better off just removing it entirely.  That would simplify
+ * the code here noticably.
+ */
+static const char *test_filename;
+static int test_line;
+static void *test_extra;
+void assertion_setup(const char *filename, int line)
+{
+       test_filename = filename;
+       test_line = line;
+}
+
+/* Called at the beginning of each assert() function. */
+static void
+assertion_count(const char *file, int line)
+{
+       (void)file; /* UNUSED */
+       (void)line; /* UNUSED */
+       ++assertions;
+       /* Proper handling of "failure()" message. */
+       msg = nextmsg;
+       nextmsg = NULL;
+       /* Uncomment to print file:line after every assertion.
+        * Verbose, but occasionally useful in tracking down crashes. */
+       /* printf("Checked %s:%d\n", file, line); */
+}
+
+/*
+ * For each test source file, we remember how many times each
+ * assertion was reported.  Cleared before each new test,
+ * used by test_summarize().
+ */
+static struct line {
+       int count;
+       int skip;
+}  failed_lines[10000];
+
+/* Count this failure, setup up log destination and handle initial report. */
+static void
+failure_start(const char *filename, int line, const char *fmt, ...)
+{
+       va_list ap;
+
+       /* Record another failure for this line. */
+       ++failures;
+       /* test_filename = filename; */
+       failed_lines[line].count++;
+
+       /* Determine whether to log header to console. */
+       switch (verbosity) {
+       case VERBOSITY_FULL:
+               log_console = 1;
+               break;
+       case VERBOSITY_LIGHT_REPORT:
+               log_console = (failed_lines[line].count < 2);
+               break;
+       default:
+               log_console = 0;
+       }
+
+       /* Log file:line header for this failure */
+       va_start(ap, fmt);
+#if _MSC_VER
+       logprintf("%s(%d): ", filename, line);
+#else
+       logprintf("%s:%d: ", filename, line);
+#endif
+       vlogprintf(fmt, ap);
+       va_end(ap);
+       logprintf("\n");
+
+       if (msg != NULL && msg[0] != '\0') {
+               logprintf("   Description: %s\n", msg);
+               msg = NULL;
+       }
+
+       /* Determine whether to log details to console. */
+       if (verbosity == VERBOSITY_LIGHT_REPORT)
+               log_console = 0;
+}
+
+/* Complete reporting of failed tests. */
+/*
+ * The 'extra' hook here is used by libarchive to include libarchive
+ * error messages with assertion failures.  It could also be used
+ * to add strerror() output, for example.  Just define the EXTRA_DUMP()
+ * macro appropriately.
+ */
+static void
+failure_finish(void *extra)
+{
+       (void)extra; /* UNUSED (maybe) */
+#ifdef EXTRA_DUMP
+       if (extra != NULL)
+               logprintf("   detail: %s\n", EXTRA_DUMP(extra));
+#endif
+
+       if (dump_on_failure) {
+               fprintf(stderr,
+                   " *** forcing core dump so failure can be debugged ***\n");
+               *(char *)(NULL) = 0;
+               exit(1);
+       }
+}
+
+/* Inform user that we're skipping some checks. */
+void
+test_skipping(const char *fmt, ...)
+{
+       char buff[1024];
+       va_list ap;
+
+       va_start(ap, fmt);
+       vsprintf(buff, fmt, ap);
+       va_end(ap);
+       /* failure_start() isn't quite right, but is awfully convenient. */
+       failure_start(test_filename, test_line, "SKIPPING: %s", buff);
+       --failures; /* Undo failures++ in failure_start() */
+       /* Don't failure_finish() here. */
+       /* Mark as skip, so doesn't count as failed test. */
+       failed_lines[test_line].skip = 1;
+       ++skips;
+}
+
+/*
+ *
+ * ASSERTIONS
+ *
+ */
+
+/* Generic assert() just displays the failed condition. */
+int
+assertion_assert(const char *file, int line, int value,
+    const char *condition, void *extra)
+{
+       assertion_count(file, line);
+       if (!value) {
+               failure_start(file, line, "Assertion failed: %s", condition);
+               failure_finish(extra);
+       }
+       return (value);
+}
+
+/* chdir() and report any errors */
+int
+assertion_chdir(const char *file, int line, const char *pathname)
+{
+       assertion_count(file, line);
+       if (chdir(pathname) == 0)
+               return (1);
+       failure_start(file, line, "chdir(\"%s\")", pathname);
+       failure_finish(NULL);
+       return (0);
+
+}
+
+/* Verify two integers are equal. */
+int
+assertion_equal_int(const char *file, int line,
+    long long v1, const char *e1, long long v2, const char *e2, void *extra)
+{
+       assertion_count(file, line);
+       if (v1 == v2)
+               return (1);
+       failure_start(file, line, "%s != %s", e1, e2);
+       logprintf("      %s=%lld (0x%llx, 0%llo)\n", e1, v1, v1, v1);
+       logprintf("      %s=%lld (0x%llx, 0%llo)\n", e2, v2, v2, v2);
+       failure_finish(extra);
+       return (0);
+}
+
+static void strdump(const char *e, const char *p)
+{
+       const char *q = p;
+
+       logprintf("      %s = ", e);
+       if (p == NULL) {
+               logprintf("NULL");
+               return;
+       }
+       logprintf("\"");
+       while (*p != '\0') {
+               unsigned int c = 0xff & *p++;
+               switch (c) {
+               case '\a': printf("\a"); break;
+               case '\b': printf("\b"); break;
+               case '\n': printf("\n"); break;
+               case '\r': printf("\r"); break;
+               default:
+                       if (c >= 32 && c < 127)
+                               logprintf("%c", c);
+                       else
+                               logprintf("\\x%02X", c);
+               }
+       }
+       logprintf("\"");
+       logprintf(" (length %d)\n", q == NULL ? -1 : (int)strlen(q));
+}
+
+/* Verify two strings are equal, dump them if not. */
+int
+assertion_equal_string(const char *file, int line,
+    const char *v1, const char *e1,
+    const char *v2, const char *e2,
+    void *extra)
+{
+       assertion_count(file, line);
+       if (v1 == v2 || (v1 != NULL && v2 != NULL && strcmp(v1, v2) == 0))
+               return (1);
+       failure_start(file, line, "%s != %s", e1, e2);
+       strdump(e1, v1);
+       strdump(e2, v2);
+       failure_finish(extra);
+       return (0);
+}
+
+static void
+wcsdump(const char *e, const wchar_t *w)
+{
+       logprintf("      %s = ", e);
+       if (w == NULL) {
+               logprintf("(null)");
+               return;
+       }
+       logprintf("\"");
+       while (*w != L'\0') {
+               unsigned int c = *w++;
+               if (c >= 32 && c < 127)
+                       logprintf("%c", c);
+               else if (c < 256)
+                       logprintf("\\x%02X", c);
+               else if (c < 0x10000)
+                       logprintf("\\u%04X", c);
+               else
+                       logprintf("\\U%08X", c);
+       }
+       logprintf("\"\n");
+}
+
+/* Verify that two wide strings are equal, dump them if not. */
+int
+assertion_equal_wstring(const char *file, int line,
+    const wchar_t *v1, const char *e1,
+    const wchar_t *v2, const char *e2,
+    void *extra)
+{
+       assertion_count(file, line);
+       if (v1 == v2 || wcscmp(v1, v2) == 0)
+               return (1);
+       failure_start(file, line, "%s != %s", e1, e2);
+       wcsdump(e1, v1);
+       wcsdump(e2, v2);
+       failure_finish(extra);
+       return (0);
+}
+
+/*
+ * Pretty standard hexdump routine.  As a bonus, if ref != NULL, then
+ * any bytes in p that differ from ref will be highlighted with '_'
+ * before and after the hex value.
+ */
+static void
+hexdump(const char *p, const char *ref, size_t l, size_t offset)
+{
+       size_t i, j;
+       char sep;
+
+       if (p == NULL) {
+               logprintf("(null)\n");
+               return;
+       }
+       for(i=0; i < l; i+=16) {
+               logprintf("%04x", (unsigned)(i + offset));
+               sep = ' ';
+               for (j = 0; j < 16 && i + j < l; j++) {
+                       if (ref != NULL && p[i + j] != ref[i + j])
+                               sep = '_';
+                       logprintf("%c%02x", sep, 0xff & (int)p[i+j]);
+                       if (ref != NULL && p[i + j] == ref[i + j])
+                               sep = ' ';
+               }
+               for (; j < 16; j++) {
+                       logprintf("%c  ", sep);
+                       sep = ' ';
+               }
+               logprintf("%c", sep);
+               for (j=0; j < 16 && i + j < l; j++) {
+                       int c = p[i + j];
+                       if (c >= ' ' && c <= 126)
+                               logprintf("%c", c);
+                       else
+                               logprintf(".");
+               }
+               logprintf("\n");
+       }
+}
+
+/* Verify that two blocks of memory are the same, display the first
+ * block of differences if they're not. */
+int
+assertion_equal_mem(const char *file, int line,
+    const void *_v1, const char *e1,
+    const void *_v2, const char *e2,
+    size_t l, const char *ld, void *extra)
+{
+       const char *v1 = (const char *)_v1;
+       const char *v2 = (const char *)_v2;
+       size_t offset;
+
+       assertion_count(file, line);
+       if (v1 == v2 || (v1 != NULL && v2 != NULL && memcmp(v1, v2, l) == 0))
+               return (1);
+
+       failure_start(file, line, "%s != %s", e1, e2);
+       logprintf("      size %s = %d\n", ld, (int)l);
+       /* Dump 48 bytes (3 lines) so that the first difference is
+        * in the second line. */
+       offset = 0;
+       while (l > 64 && memcmp(v1, v2, 32) == 0) {
+               /* Two lines agree, so step forward one line. */
+               v1 += 16;
+               v2 += 16;
+               l -= 16;
+               offset += 16;
+       }
+       logprintf("      Dump of %s\n", e1);
+       hexdump(v1, v2, l < 64 ? l : 64, offset);
+       logprintf("      Dump of %s\n", e2);
+       hexdump(v2, v1, l < 64 ? l : 64, offset);
+       logprintf("\n");
+       failure_finish(extra);
+       return (0);
+}
+
+/* Verify that the named file exists and is empty. */
+int
+assertion_empty_file(const char *f1fmt, ...)
+{
+       char buff[1024];
+       char f1[1024];
+       struct stat st;
+       va_list ap;
+       ssize_t s;
+       FILE *f;
+
+       assertion_count(test_filename, test_line);
+       va_start(ap, f1fmt);
+       vsprintf(f1, f1fmt, ap);
+       va_end(ap);
+
+       if (stat(f1, &st) != 0) {
+               failure_start(test_filename, test_line, "Stat failed: %s", f1);
+               failure_finish(NULL);
+               return (0);
+       }
+       if (st.st_size == 0)
+               return (1);
+
+       failure_start(test_filename, test_line, "File should be empty: %s", f1);
+       logprintf("    File size: %d\n", (int)st.st_size);
+       logprintf("    Contents:\n");
+       f = fopen(f1, "rb");
+       if (f == NULL) {
+               logprintf("    Unable to open %s\n", f1);
+       } else {
+               s = ((off_t)sizeof(buff) < st.st_size) ?
+                   (ssize_t)sizeof(buff) : (ssize_t)st.st_size;
+               s = fread(buff, 1, s, f);
+               hexdump(buff, NULL, s, 0);
+               fclose(f);
+       }
+       failure_finish(NULL);
+       return (0);
+}
+
+/* Verify that the named file exists and is not empty. */
+int
+assertion_non_empty_file(const char *f1fmt, ...)
+{
+       char f1[1024];
+       struct stat st;
+       va_list ap;
+
+       assertion_count(test_filename, test_line);
+       va_start(ap, f1fmt);
+       vsprintf(f1, f1fmt, ap);
+       va_end(ap);
+
+       if (stat(f1, &st) != 0) {
+               failure_start(test_filename, test_line, "Stat failed: %s", f1);
+               failure_finish(NULL);
+               return (0);
+       }
+       if (st.st_size == 0) {
+               failure_start(test_filename, test_line, "File empty: %s", f1);
+               failure_finish(NULL);
+               return (0);
+       }
+       return (1);
+}
+
+/* Verify that two files have the same contents. */
+/* TODO: hexdump the first bytes that actually differ. */
+int
+assertion_equal_file(const char *fn1, const char *f2pattern, ...)
+{
+       char fn2[1024];
+       va_list ap;
+       char buff1[1024];
+       char buff2[1024];
+       FILE *f1, *f2;
+       int n1, n2;
+
+       assertion_count(test_filename, test_line);
+       va_start(ap, f2pattern);
+       vsprintf(fn2, f2pattern, ap);
+       va_end(ap);
+
+       f1 = fopen(fn1, "rb");
+       f2 = fopen(fn2, "rb");
+       for (;;) {
+               n1 = fread(buff1, 1, sizeof(buff1), f1);
+               n2 = fread(buff2, 1, sizeof(buff2), f2);
+               if (n1 != n2)
+                       break;
+               if (n1 == 0 && n2 == 0) {
+                       fclose(f1);
+                       fclose(f2);
+                       return (1);
+               }
+               if (memcmp(buff1, buff2, n1) != 0)
+                       break;
+       }
+       fclose(f1);
+       fclose(f2);
+       failure_start(test_filename, test_line, "Files not identical");
+       logprintf("  file1=\"%s\"\n", fn1);
+       logprintf("  file2=\"%s\"\n", fn2);
+       failure_finish(test_extra);
+       return (0);
+}
+
+/* Verify that the named file does exist. */
+int
+assertion_file_exists(const char *fpattern, ...)
+{
+       char f[1024];
+       va_list ap;
+
+       assertion_count(test_filename, test_line);
+       va_start(ap, fpattern);
+       vsprintf(f, fpattern, ap);
+       va_end(ap);
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       if (!_access(f, 0))
+               return (1);
+#else
+       if (!access(f, F_OK))
+               return (1);
+#endif
+       failure_start(test_filename, test_line, "File should exist: %s", f);
+       failure_finish(test_extra);
+       return (0);
+}
+
+/* Verify that the named file doesn't exist. */
+int
+assertion_file_not_exists(const char *fpattern, ...)
+{
+       char f[1024];
+       va_list ap;
+
+       assertion_count(test_filename, test_line);
+       va_start(ap, fpattern);
+       vsprintf(f, fpattern, ap);
+       va_end(ap);
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       if (_access(f, 0))
+               return (1);
+#else
+       if (access(f, F_OK))
+               return (1);
+#endif
+       failure_start(test_filename, test_line, "File should not exist: %s", f);
+       failure_finish(test_extra);
+       return (0);
+}
+
+/* Compare the contents of a file to a block of memory. */
+int
+assertion_file_contents(const void *buff, int s, const char *fpattern, ...)
+{
+       char fn[1024];
+       va_list ap;
+       char *contents;
+       FILE *f;
+       int n;
+
+       assertion_count(test_filename, test_line);
+       va_start(ap, fpattern);
+       vsprintf(fn, fpattern, ap);
+       va_end(ap);
+
+       f = fopen(fn, "rb");
+       if (f == NULL) {
+               failure_start(test_filename, test_line,
+                   "File should exist: %s", fn);
+               failure_finish(test_extra);
+               return (0);
+       }
+       contents = malloc(s * 2);
+       n = fread(contents, 1, s * 2, f);
+       fclose(f);
+       if (n == s && memcmp(buff, contents, s) == 0) {
+               free(contents);
+               return (1);
+       }
+       failure_start(test_filename, test_line, "File contents don't match");
+       logprintf("  file=\"%s\"\n", fn);
+       if (n > 0)
+               hexdump(contents, buff, n > 512 ? 512 : n, 0);
+       else {
+               logprintf("  File empty, contents should be:\n");
+               hexdump(buff, NULL, s > 512 ? 512 : s, 0);
+       }
+       failure_finish(test_extra);
+       free(contents);
+       return (0);
+}
+
+/* Check the contents of a text file, being tolerant of line endings. */
+int
+assertion_text_file_contents(const char *buff, const char *fn)
+{
+       char *contents;
+       const char *btxt, *ftxt;
+       FILE *f;
+       int n, s;
+
+       assertion_count(test_filename, test_line);
+       f = fopen(fn, "r");
+       s = strlen(buff);
+       contents = malloc(s * 2 + 128);
+       n = fread(contents, 1, s * 2 + 128 - 1, f);
+       if (n >= 0)
+               contents[n] = '\0';
+       fclose(f);
+       /* Compare texts. */
+       btxt = buff;
+       ftxt = (const char *)contents;
+       while (*btxt != '\0' && *ftxt != '\0') {
+               if (*btxt == *ftxt) {
+                       ++btxt;
+                       ++ftxt;
+                       continue;
+               }
+               if (btxt[0] == '\n' && ftxt[0] == '\r' && ftxt[1] == '\n') {
+                       /* Pass over different new line characters. */
+                       ++btxt;
+                       ftxt += 2;
+                       continue;
+               }
+               break;
+       }
+       if (*btxt == '\0' && *ftxt == '\0') {
+               free(contents);
+               return (1);
+       }
+       failure_start(test_filename, test_line, "Contents don't match");
+       logprintf("  file=\"%s\"\n", fn);
+       if (n > 0)
+               hexdump(contents, buff, n, 0);
+       else {
+               logprintf("  File empty, contents should be:\n");
+               hexdump(buff, NULL, s, 0);
+       }
+       failure_finish(test_extra);
+       free(contents);
+       return (0);
+}
+
+/* Verify that a text file contains the specified lines, regardless of order */
+/* This could be more efficient if we sorted both sets of lines, etc, but
+ * since this is used only for testing and only ever deals with a dozen or so
+ * lines at a time, this relatively crude approach is just fine. */
+int
+assertion_file_contains_lines_any_order(const char *file, int line,
+    const char *pathname, const char *lines[])
+{
+       char *buff;
+       size_t buff_size;
+       size_t expected_count, actual_count, i, j;
+       char **expected;
+       char *p, **actual;
+       char c;
+       int expected_failure = 0, actual_failure = 0;
+
+       assertion_count(file, line);
+
+       buff = slurpfile(&buff_size, "%s", pathname);
+       if (buff == NULL) {
+               failure_start(pathname, line, "Can't read file: %s", pathname);
+               failure_finish(NULL);
+               return (0);
+       }
+
+       // Make a copy of the provided lines and count up the expected file size.
+       expected_count = 0;
+       for (i = 0; lines[i] != NULL; ++i) {
+       }
+       expected_count = i;
+       expected = malloc(sizeof(char *) * expected_count);
+       for (i = 0; lines[i] != NULL; ++i) {
+               expected[i] = strdup(lines[i]);
+       }
+
+       // Break the file into lines
+       actual_count = 0;
+       for (c = '\0', p = buff; p < buff + buff_size; ++p) {
+               if (*p == '\x0d' || *p == '\x0a')
+                       *p = '\0';
+               if (c == '\0' && *p != '\0')
+                       ++actual_count;
+               c = *p;
+       }
+       actual = malloc(sizeof(char *) * actual_count);
+       for (j = 0, p = buff; p < buff + buff_size; p += 1 + strlen(p)) {
+               if (*p != '\0') {
+                       actual[j] = p;
+                       ++j;
+               }
+       }
+
+       // Erase matching lines from both lists
+       for (i = 0; i < expected_count; ++i) {
+               if (expected[i] == NULL)
+                       continue;
+               for (j = 0; j < actual_count; ++j) {
+                       if (actual[j] == NULL)
+                               continue;
+                       if (strcmp(expected[i], actual[j]) == 0) {
+                               free(expected[i]);
+                               expected[i] = NULL;
+                               actual[j] = NULL;
+                               break;
+                       }
+               }
+       }
+
+       // If there's anything left, it's a failure
+       for (i = 0; i < expected_count; ++i) {
+               if (expected[i] != NULL)
+                       ++expected_failure;
+       }
+       for (j = 0; j < actual_count; ++j) {
+               if (actual[j] != NULL)
+                       ++actual_failure;
+       }
+       if (expected_failure == 0 && actual_failure == 0) {
+               free(buff);
+               free(expected);
+               free(actual);
+               return (1);
+       }
+       failure_start(file, line, "File doesn't match: %s", pathname);
+       for (i = 0; i < expected_count; ++i) {
+               if (expected[i] != NULL) {
+                       logprintf("  Expected but not present: %s\n", expected[i]);
+                       free(expected[i]);
+               }
+       }
+       for (j = 0; j < actual_count; ++j) {
+               if (actual[j] != NULL)
+                       logprintf("  Present but not expected: %s\n", actual[j]);
+       }
+       failure_finish(NULL);
+       free(buff);
+       free(expected);
+       free(actual);
+       return (0);
+}
+
+/* Test that two paths point to the same file. */
+/* As a side-effect, asserts that both files exist. */
+static int
+is_hardlink(const char *file, int line,
+    const char *path1, const char *path2)
+{
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       BY_HANDLE_FILE_INFORMATION bhfi1, bhfi2;
+       int r;
+
+       assertion_count(file, line);
+       r = my_GetFileInformationByName(path1, &bhfi1);
+       if (r == 0) {
+               failure_start(file, line, "File %s can't be inspected?", path1);
+               failure_finish(NULL);
+               return (0);
+       }
+       r = my_GetFileInformationByName(path2, &bhfi2);
+       if (r == 0) {
+               failure_start(file, line, "File %s can't be inspected?", path2);
+               failure_finish(NULL);
+               return (0);
+       }
+       return (bhfi1.dwVolumeSerialNumber == bhfi2.dwVolumeSerialNumber
+               && bhfi1.nFileIndexHigh == bhfi2.nFileIndexHigh
+               && bhfi1.nFileIndexLow == bhfi2.nFileIndexLow);
+#else
+       struct stat st1, st2;
+       int r;
+
+       assertion_count(file, line);
+       r = lstat(path1, &st1);
+       if (r != 0) {
+               failure_start(file, line, "File should exist: %s", path1);
+               failure_finish(NULL);
+               return (0);
+       }
+       r = lstat(path2, &st2);
+       if (r != 0) {
+               failure_start(file, line, "File should exist: %s", path2);
+               failure_finish(NULL);
+               return (0);
+       }
+       return (st1.st_ino == st2.st_ino && st1.st_dev == st2.st_dev);
+#endif
+}
+
+int
+assertion_is_hardlink(const char *file, int line,
+    const char *path1, const char *path2)
+{
+       if (is_hardlink(file, line, path1, path2))
+               return (1);
+       failure_start(file, line,
+           "Files %s and %s are not hardlinked", path1, path2);
+       failure_finish(NULL);
+       return (0);
+}
+
+int
+assertion_is_not_hardlink(const char *file, int line,
+    const char *path1, const char *path2)
+{
+       if (!is_hardlink(file, line, path1, path2))
+               return (1);
+       failure_start(file, line,
+           "Files %s and %s should not be hardlinked", path1, path2);
+       failure_finish(NULL);
+       return (0);
+}
+
+/* Verify a/b/mtime of 'pathname'. */
+/* If 'recent', verify that it's within last 10 seconds. */
+static int
+assertion_file_time(const char *file, int line,
+    const char *pathname, long t, long nsec, char type, int recent)
+{
+       long long filet, filet_nsec;
+       int r;
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+#define EPOC_TIME      (116444736000000000ULL)
+       FILETIME ftime, fbirthtime, fatime, fmtime;
+       ULARGE_INTEGER wintm;
+       HANDLE h;
+       ftime.dwLowDateTime = 0;
+       ftime.dwHighDateTime = 0;
+
+       assertion_count(file, line);
+       h = CreateFile(pathname, FILE_READ_ATTRIBUTES, 0, NULL,
+           OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+       if (h == INVALID_HANDLE_VALUE) {
+               failure_start(file, line, "Can't access %s\n", pathname);
+               failure_finish(NULL);
+               return (0);
+       }
+       r = GetFileTime(h, &fbirthtime, &fatime, &fmtime);
+       switch (type) {
+       case 'a': ftime = fatime; break;
+       case 'b': ftime = fbirthtime; break;
+       case 'm': ftime = fmtime; break;
+       }
+       CloseHandle(h);
+       if (r == 0) {
+               failure_start(file, line, "Can't GetFileTime %s\n", pathname);
+               failure_finish(NULL);
+               return (0);
+       }
+       wintm.LowPart = ftime.dwLowDateTime;
+       wintm.HighPart = ftime.dwHighDateTime;
+       filet = (wintm.QuadPart - EPOC_TIME) / 10000000;
+       filet_nsec = ((wintm.QuadPart - EPOC_TIME) % 10000000) * 100;
+       nsec = (nsec / 100) * 100; /* Round the request */
+#else
+       struct stat st;
+
+       assertion_count(file, line);
+       r = lstat(pathname, &st);
+       if (r != 0) {
+               failure_start(file, line, "Can't stat %s\n", pathname);
+               failure_finish(NULL);
+               return (0);
+       }
+       switch (type) {
+       case 'a': filet = st.st_atime; break;
+       case 'm': filet = st.st_mtime; break;
+       case 'b': filet = 0; break;
+       default: fprintf(stderr, "INTERNAL: Bad type %c for file time", type);
+               exit(1);
+       }
+#if defined(__FreeBSD__)
+       switch (type) {
+       case 'a': filet_nsec = st.st_atimespec.tv_nsec; break;
+       case 'b': filet = st.st_birthtime;
+               filet_nsec = st.st_birthtimespec.tv_nsec; break;
+       case 'm': filet_nsec = st.st_mtimespec.tv_nsec; break;
+       default: fprintf(stderr, "INTERNAL: Bad type %c for file time", type);
+               exit(1);
+       }
+       /* FreeBSD generally only stores to microsecond res, so round. */
+       filet_nsec = (filet_nsec / 1000) * 1000;
+       nsec = (nsec / 1000) * 1000;
+#else
+       filet_nsec = nsec = 0;  /* Generic POSIX only has whole seconds. */
+       if (type == 'b') return (1); /* Generic POSIX doesn't have birthtime */
+#if defined(__HAIKU__)
+       if (type == 'a') return (1); /* Haiku doesn't have atime. */
+#endif
+#endif
+#endif
+       if (recent) {
+               /* Check that requested time is up-to-date. */
+               time_t now = time(NULL);
+               if (filet < now - 10 || filet > now + 1) {
+                       failure_start(file, line,
+                           "File %s has %ctime %ld, %ld seconds ago\n",
+                           pathname, type, filet, now - filet);
+                       failure_finish(NULL);
+                       return (0);
+               }
+       } else if (filet != t || filet_nsec != nsec) {
+               failure_start(file, line,
+                   "File %s has %ctime %ld.%09ld, expected %ld.%09ld",
+                   pathname, type, filet, filet_nsec, t, nsec);
+               failure_finish(NULL);
+               return (0);
+       }
+       return (1);
+}
+
+/* Verify atime of 'pathname'. */
+int
+assertion_file_atime(const char *file, int line,
+    const char *pathname, long t, long nsec)
+{
+       return assertion_file_time(file, line, pathname, t, nsec, 'a', 0);
+}
+
+/* Verify atime of 'pathname' is up-to-date. */
+int
+assertion_file_atime_recent(const char *file, int line, const char *pathname)
+{
+       return assertion_file_time(file, line, pathname, 0, 0, 'a', 1);
+}
+
+/* Verify birthtime of 'pathname'. */
+int
+assertion_file_birthtime(const char *file, int line,
+    const char *pathname, long t, long nsec)
+{
+       return assertion_file_time(file, line, pathname, t, nsec, 'b', 0);
+}
+
+/* Verify birthtime of 'pathname' is up-to-date. */
+int
+assertion_file_birthtime_recent(const char *file, int line,
+    const char *pathname)
+{
+       return assertion_file_time(file, line, pathname, 0, 0, 'b', 1);
+}
+
+/* Verify mtime of 'pathname'. */
+int
+assertion_file_mtime(const char *file, int line,
+    const char *pathname, long t, long nsec)
+{
+       return assertion_file_time(file, line, pathname, t, nsec, 'm', 0);
+}
+
+/* Verify mtime of 'pathname' is up-to-date. */
+int
+assertion_file_mtime_recent(const char *file, int line, const char *pathname)
+{
+       return assertion_file_time(file, line, pathname, 0, 0, 'm', 1);
+}
+
+/* Verify number of links to 'pathname'. */
+int
+assertion_file_nlinks(const char *file, int line,
+    const char *pathname, int nlinks)
+{
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       BY_HANDLE_FILE_INFORMATION bhfi;
+       int r;
+
+       assertion_count(file, line);
+       r = my_GetFileInformationByName(pathname, &bhfi);
+       if (r != 0 && bhfi.nNumberOfLinks == (DWORD)nlinks)
+               return (1);
+       failure_start(file, line, "File %s has %d links, expected %d",
+           pathname, bhfi.nNumberOfLinks, nlinks);
+       failure_finish(NULL);
+       return (0);
+#else
+       struct stat st;
+       int r;
+
+       assertion_count(file, line);
+       r = lstat(pathname, &st);
+       if (r == 0 && st.st_nlink == nlinks)
+                       return (1);
+       failure_start(file, line, "File %s has %d links, expected %d",
+           pathname, st.st_nlink, nlinks);
+       failure_finish(NULL);
+       return (0);
+#endif
+}
+
+/* Verify size of 'pathname'. */
+int
+assertion_file_size(const char *file, int line, const char *pathname, long size)
+{
+       int64_t filesize;
+       int r;
+
+       assertion_count(file, line);
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       {
+               BY_HANDLE_FILE_INFORMATION bhfi;
+               r = !my_GetFileInformationByName(pathname, &bhfi);
+               filesize = ((int64_t)bhfi.nFileSizeHigh << 32) + bhfi.nFileSizeLow;
+       }
+#else
+       {
+               struct stat st;
+               r = lstat(pathname, &st);
+               filesize = st.st_size;
+       }
+#endif
+       if (r == 0 && filesize == size)
+                       return (1);
+       failure_start(file, line, "File %s has size %ld, expected %ld",
+           pathname, (long)filesize, (long)size);
+       failure_finish(NULL);
+       return (0);
+}
+
+/* Assert that 'pathname' is a dir.  If mode >= 0, verify that too. */
+int
+assertion_is_dir(const char *file, int line, const char *pathname, int mode)
+{
+       struct stat st;
+       int r;
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       (void)mode; /* UNUSED */
+#endif
+       assertion_count(file, line);
+       r = lstat(pathname, &st);
+       if (r != 0) {
+               failure_start(file, line, "Dir should exist: %s", pathname);
+               failure_finish(NULL);
+               return (0);
+       }
+       if (!S_ISDIR(st.st_mode)) {
+               failure_start(file, line, "%s is not a dir", pathname);
+               failure_finish(NULL);
+               return (0);
+       }
+#if !defined(_WIN32) || defined(__CYGWIN__)
+       /* Windows doesn't handle permissions the same way as POSIX,
+        * so just ignore the mode tests. */
+       /* TODO: Can we do better here? */
+       if (mode >= 0 && mode != (st.st_mode & 07777)) {
+               failure_start(file, line, "Dir %s has wrong mode", pathname);
+               logprintf("  Expected: 0%3o\n", mode);
+               logprintf("  Found: 0%3o\n", st.st_mode & 07777);
+               failure_finish(NULL);
+               return (0);
+       }
+#endif
+       return (1);
+}
+
+/* Verify that 'pathname' is a regular file.  If 'mode' is >= 0,
+ * verify that too. */
+int
+assertion_is_reg(const char *file, int line, const char *pathname, int mode)
+{
+       struct stat st;
+       int r;
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       (void)mode; /* UNUSED */
+#endif
+       assertion_count(file, line);
+       r = lstat(pathname, &st);
+       if (r != 0 || !S_ISREG(st.st_mode)) {
+               failure_start(file, line, "File should exist: %s", pathname);
+               failure_finish(NULL);
+               return (0);
+       }
+#if !defined(_WIN32) || defined(__CYGWIN__)
+       /* Windows doesn't handle permissions the same way as POSIX,
+        * so just ignore the mode tests. */
+       /* TODO: Can we do better here? */
+       if (mode >= 0 && mode != (st.st_mode & 07777)) {
+               failure_start(file, line, "File %s has wrong mode", pathname);
+               logprintf("  Expected: 0%3o\n", mode);
+               logprintf("  Found: 0%3o\n", st.st_mode & 07777);
+               failure_finish(NULL);
+               return (0);
+       }
+#endif
+       return (1);
+}
+
+/* Check whether 'pathname' is a symbolic link.  If 'contents' is
+ * non-NULL, verify that the symlink has those contents. */
+static int
+is_symlink(const char *file, int line,
+    const char *pathname, const char *contents)
+{
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       (void)pathname; /* UNUSED */
+       (void)contents; /* UNUSED */
+       assertion_count(file, line);
+       /* Windows sort-of has real symlinks, but they're only usable
+        * by privileged users and are crippled even then, so there's
+        * really not much point in bothering with this. */
+       return (0);
+#else
+       char buff[300];
+       struct stat st;
+       ssize_t linklen;
+       int r;
+
+       assertion_count(file, line);
+       r = lstat(pathname, &st);
+       if (r != 0) {
+               failure_start(file, line,
+                   "Symlink should exist: %s", pathname);
+               failure_finish(NULL);
+               return (0);
+       }
+       if (!S_ISLNK(st.st_mode))
+               return (0);
+       if (contents == NULL)
+               return (1);
+       linklen = readlink(pathname, buff, sizeof(buff));
+       if (linklen < 0) {
+               failure_start(file, line, "Can't read symlink %s", pathname);
+               failure_finish(NULL);
+               return (0);
+       }
+       buff[linklen] = '\0';
+       if (strcmp(buff, contents) != 0)
+               return (0);
+       return (1);
+#endif
+}
+
+/* Assert that path is a symlink that (optionally) contains contents. */
+int
+assertion_is_symlink(const char *file, int line,
+    const char *path, const char *contents)
+{
+       if (is_symlink(file, line, path, contents))
+               return (1);
+       if (contents)
+               failure_start(file, line, "File %s is not a symlink to %s",
+                   path, contents);
+       else
+               failure_start(file, line, "File %s is not a symlink", path);
+       failure_finish(NULL);
+       return (0);
+}
+
+
+/* Create a directory and report any errors. */
+int
+assertion_make_dir(const char *file, int line, const char *dirname, int mode)
+{
+       assertion_count(file, line);
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       (void)mode; /* UNUSED */
+       if (0 == _mkdir(dirname))
+               return (1);
+#else
+       if (0 == mkdir(dirname, mode))
+               return (1);
+#endif
+       failure_start(file, line, "Could not create directory %s", dirname);
+       failure_finish(NULL);
+       return(0);
+}
+
+/* Create a file with the specified contents and report any failures. */
+int
+assertion_make_file(const char *file, int line,
+    const char *path, int mode, const char *contents)
+{
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       /* TODO: Rework this to set file mode as well. */
+       FILE *f;
+       (void)mode; /* UNUSED */
+       assertion_count(file, line);
+       f = fopen(path, "wb");
+       if (f == NULL) {
+               failure_start(file, line, "Could not create file %s", path);
+               failure_finish(NULL);
+               return (0);
+       }
+       if (contents != NULL) {
+               if (strlen(contents)
+                   != fwrite(contents, 1, strlen(contents), f)) {
+                       fclose(f);
+                       failure_start(file, line,
+                           "Could not write file %s", path);
+                       failure_finish(NULL);
+                       return (0);
+               }
+       }
+       fclose(f);
+       return (1);
+#else
+       int fd;
+       assertion_count(file, line);
+       fd = open(path, O_CREAT | O_WRONLY, mode >= 0 ? mode : 0644);
+       if (fd < 0) {
+               failure_start(file, line, "Could not create %s", path);
+               failure_finish(NULL);
+               return (0);
+       }
+       if (contents != NULL) {
+               if ((ssize_t)strlen(contents)
+                   != write(fd, contents, strlen(contents))) {
+                       close(fd);
+                       failure_start(file, line, "Could not write to %s", path);
+                       failure_finish(NULL);
+                       return (0);
+               }
+       }
+       close(fd);
+       return (1);
+#endif
+}
+
+/* Create a hardlink and report any failures. */
+int
+assertion_make_hardlink(const char *file, int line,
+    const char *newpath, const char *linkto)
+{
+       int succeeded;
+
+       assertion_count(file, line);
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       succeeded = my_CreateHardLinkA(newpath, linkto);
+#elif HAVE_LINK
+       succeeded = !link(linkto, newpath);
+#else
+       succeeded = 0;
+#endif
+       if (succeeded)
+               return (1);
+       failure_start(file, line, "Could not create hardlink");
+       logprintf("   New link: %s\n", newpath);
+       logprintf("   Old name: %s\n", linkto);
+       failure_finish(NULL);
+       return(0);
+}
+
+/* Create a symlink and report any failures. */
+int
+assertion_make_symlink(const char *file, int line,
+    const char *newpath, const char *linkto)
+{
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       int targetIsDir = 0;  /* TODO: Fix this */
+       assertion_count(file, line);
+       if (my_CreateSymbolicLinkA(newpath, linkto, targetIsDir))
+               return (1);
+#elif HAVE_SYMLINK
+       assertion_count(file, line);
+       if (0 == symlink(linkto, newpath))
+               return (1);
+#endif
+       failure_start(file, line, "Could not create symlink");
+       logprintf("   New link: %s\n", newpath);
+       logprintf("   Old name: %s\n", linkto);
+       failure_finish(NULL);
+       return(0);
+}
+
+/* Set umask, report failures. */
+int
+assertion_umask(const char *file, int line, int mask)
+{
+       assertion_count(file, line);
+       (void)file; /* UNUSED */
+       (void)line; /* UNUSED */
+       umask(mask);
+       return (1);
+}
+
+/*
+ *
+ *  UTILITIES for use by tests.
+ *
+ */
+
+/*
+ * Check whether platform supports symlinks.  This is intended
+ * for tests to use in deciding whether to bother testing symlink
+ * support; if the platform doesn't support symlinks, there's no point
+ * in checking whether the program being tested can create them.
+ *
+ * Note that the first time this test is called, we actually go out to
+ * disk to create and verify a symlink.  This is necessary because
+ * symlink support is actually a property of a particular filesystem
+ * and can thus vary between directories on a single system.  After
+ * the first call, this returns the cached result from memory, so it's
+ * safe to call it as often as you wish.
+ */
+int
+canSymlink(void)
+{
+       /* Remember the test result */
+       static int value = 0, tested = 0;
+       if (tested)
+               return (value);
+
+       ++tested;
+       assertion_make_file(__FILE__, __LINE__, "canSymlink.0", 0644, "a");
+       /* Note: Cygwin has its own symlink() emulation that does not
+        * use the Win32 CreateSymbolicLink() function. */
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       value = my_CreateSymbolicLinkA("canSymlink.1", "canSymlink.0", 0)
+           && is_symlink(__FILE__, __LINE__, "canSymlink.1", "canSymlink.0");
+#elif HAVE_SYMLINK
+       value = (0 == symlink("canSymlink.0", "canSymlink.1"))
+           && is_symlink(__FILE__, __LINE__, "canSymlink.1","canSymlink.0");
+#endif
+       return (value);
+}
+
+/*
+ * Can this platform run the gzip program?
+ */
+/* Platform-dependent options for hiding the output of a subcommand. */
+#if defined(_WIN32) && !defined(__CYGWIN__)
+static const char *redirectArgs = ">NUL 2>NUL"; /* Win32 cmd.exe */
+#else
+static const char *redirectArgs = ">/dev/null 2>/dev/null"; /* POSIX 'sh' */
+#endif
+int
+canGzip(void)
+{
+       static int tested = 0, value = 0;
+       if (!tested) {
+               tested = 1;
+               if (systemf("gzip -V %s", redirectArgs) == 0)
+                       value = 1;
+       }
+       return (value);
+}
+
+/*
+ * Can this platform run the gunzip program?
+ */
+int
+canGunzip(void)
+{
+       static int tested = 0, value = 0;
+       if (!tested) {
+               tested = 1;
+               if (systemf("gunzip -V %s", redirectArgs) == 0)
+                       value = 1;
+       }
+       return (value);
+}
+
+/*
+ * Sleep as needed; useful for verifying disk timestamp changes by
+ * ensuring that the wall-clock time has actually changed before we
+ * go back to re-read something from disk.
+ */
+void
+sleepUntilAfter(time_t t)
+{
+       while (t >= time(NULL))
+#if defined(_WIN32) && !defined(__CYGWIN__)
+               Sleep(500);
+#else
+               sleep(1);
+#endif
+}
+
+/*
+ * Call standard system() call, but build up the command line using
+ * sprintf() conventions.
+ */
+int
+systemf(const char *fmt, ...)
+{
+       char buff[8192];
+       va_list ap;
+       int r;
+
+       va_start(ap, fmt);
+       vsprintf(buff, fmt, ap);
+       if (verbosity > VERBOSITY_FULL)
+               logprintf("Cmd: %s\n", buff);
+       r = system(buff);
+       va_end(ap);
+       return (r);
+}
+
+/*
+ * Slurp a file into memory for ease of comparison and testing.
+ * Returns size of file in 'sizep' if non-NULL, null-terminates
+ * data in memory for ease of use.
+ */
+char *
+slurpfile(size_t * sizep, const char *fmt, ...)
+{
+       char filename[8192];
+       struct stat st;
+       va_list ap;
+       char *p;
+       ssize_t bytes_read;
+       FILE *f;
+       int r;
+
+       va_start(ap, fmt);
+       vsprintf(filename, fmt, ap);
+       va_end(ap);
+
+       f = fopen(filename, "rb");
+       if (f == NULL) {
+               /* Note: No error; non-existent file is okay here. */
+               return (NULL);
+       }
+       r = fstat(fileno(f), &st);
+       if (r != 0) {
+               logprintf("Can't stat file %s\n", filename);
+               fclose(f);
+               return (NULL);
+       }
+       p = malloc((size_t)st.st_size + 1);
+       if (p == NULL) {
+               logprintf("Can't allocate %ld bytes of memory to read file %s\n",
+                   (long int)st.st_size, filename);
+               fclose(f);
+               return (NULL);
+       }
+       bytes_read = fread(p, 1, (size_t)st.st_size, f);
+       if (bytes_read < st.st_size) {
+               logprintf("Can't read file %s\n", filename);
+               fclose(f);
+               free(p);
+               return (NULL);
+       }
+       p[st.st_size] = '\0';
+       if (sizep != NULL)
+               *sizep = (size_t)st.st_size;
+       fclose(f);
+       return (p);
+}
+
+/* Read a uuencoded file from the reference directory, decode, and
+ * write the result into the current directory. */
+#define        UUDECODE(c) (((c) - 0x20) & 0x3f)
+void
+extract_reference_file(const char *name)
+{
+       char buff[1024];
+       FILE *in, *out;
+
+       sprintf(buff, "%s/%s.uu", refdir, name);
+       in = fopen(buff, "r");
+       failure("Couldn't open reference file %s", buff);
+       assert(in != NULL);
+       if (in == NULL)
+               return;
+       /* Read up to and including the 'begin' line. */
+       for (;;) {
+               if (fgets(buff, sizeof(buff), in) == NULL) {
+                       /* TODO: This is a failure. */
+                       return;
+               }
+               if (memcmp(buff, "begin ", 6) == 0)
+                       break;
+       }
+       /* Now, decode the rest and write it. */
+       /* Not a lot of error checking here; the input better be right. */
+       out = fopen(name, "wb");
+       while (fgets(buff, sizeof(buff), in) != NULL) {
+               char *p = buff;
+               int bytes;
+
+               if (memcmp(buff, "end", 3) == 0)
+                       break;
+
+               bytes = UUDECODE(*p++);
+               while (bytes > 0) {
+                       int n = 0;
+                       /* Write out 1-3 bytes from that. */
+                       if (bytes > 0) {
+                               n = UUDECODE(*p++) << 18;
+                               n |= UUDECODE(*p++) << 12;
+                               fputc(n >> 16, out);
+                               --bytes;
+                       }
+                       if (bytes > 0) {
+                               n |= UUDECODE(*p++) << 6;
+                               fputc((n >> 8) & 0xFF, out);
+                               --bytes;
+                       }
+                       if (bytes > 0) {
+                               n |= UUDECODE(*p++);
+                               fputc(n & 0xFF, out);
+                               --bytes;
+                       }
+               }
+       }
+       fclose(out);
+       fclose(in);
+}
+
+/*
+ *
+ * TEST management
+ *
+ */
+
+/*
+ * "list.h" is simply created by "grep DEFINE_TEST test_*.c"; it has
+ * a line like
+ *      DEFINE_TEST(test_function)
+ * for each test.
+ */
+
+/* Use "list.h" to declare all of the test functions. */
+#undef DEFINE_TEST
+#define        DEFINE_TEST(name) void name(void);
+#include "list.h"
+
+/* Use "list.h" to create a list of all tests (functions and names). */
+#undef DEFINE_TEST
+#define        DEFINE_TEST(n) { n, #n, 0 },
+struct { void (*func)(void); const char *name; int failures; } tests[] = {
+       #include "list.h"
+};
+
+/*
+ * Summarize repeated failures in the just-completed test.
+ */
+static void
+test_summarize(const char *filename, int failed)
+{
+       unsigned int i;
+
+       switch (verbosity) {
+       case VERBOSITY_SUMMARY_ONLY:
+               printf(failed ? "E" : ".");
+               fflush(stdout);
+               break;
+       case VERBOSITY_PASSFAIL:
+               printf(failed ? "FAIL\n" : "ok\n");
+               break;
+       }
+
+       log_console = (verbosity == VERBOSITY_LIGHT_REPORT);
+
+       for (i = 0; i < sizeof(failed_lines)/sizeof(failed_lines[0]); i++) {
+               if (failed_lines[i].count > 1 && !failed_lines[i].skip)
+                       logprintf("%s:%d: Summary: Failed %d times\n",
+                           filename, i, failed_lines[i].count);
+       }
+       /* Clear the failure history for the next file. */
+       memset(failed_lines, 0, sizeof(failed_lines));
+}
+
+/*
+ * Actually run a single test, with appropriate setup and cleanup.
+ */
+static int
+test_run(int i, const char *tmpdir)
+{
+       char logfilename[64];
+       int failures_before = failures;
+       int oldumask;
+
+       switch (verbosity) {
+       case VERBOSITY_SUMMARY_ONLY: /* No per-test reports at all */
+               break;
+       case VERBOSITY_PASSFAIL: /* rest of line will include ok/FAIL marker */
+               printf("%3d: %-50s", i, tests[i].name);
+               fflush(stdout);
+               break;
+       default: /* Title of test, details will follow */
+               printf("%3d: %s\n", i, tests[i].name);
+       }
+
+       /* Chdir to the top-level work directory. */
+       if (!assertChdir(tmpdir)) {
+               fprintf(stderr,
+                   "ERROR: Can't chdir to top work dir %s\n", tmpdir);
+               exit(1);
+       }
+       /* Create a log file for this test. */
+       sprintf(logfilename, "%s.log", tests[i].name);
+       logfile = fopen(logfilename, "w");
+       fprintf(logfile, "%s\n\n", tests[i].name);
+       /* Chdir() to a work dir for this specific test. */
+       if (!assertMakeDir(tests[i].name, 0755)
+           || !assertChdir(tests[i].name)) {
+               fprintf(stderr,
+                   "ERROR: Can't chdir to work dir %s/%s\n",
+                   tmpdir, tests[i].name);
+               exit(1);
+       }
+       /* Explicitly reset the locale before each test. */
+       setlocale(LC_ALL, "C");
+       /* Record the umask before we run the test. */
+       umask(oldumask = umask(0));
+       /*
+        * Run the actual test.
+        */
+       (*tests[i].func)();
+       /*
+        * Clean up and report afterwards.
+        */
+       /* Restore umask */
+       umask(oldumask);
+       /* Reset locale. */
+       setlocale(LC_ALL, "C");
+       /* Reset directory. */
+       if (!assertChdir(tmpdir)) {
+               fprintf(stderr, "ERROR: Couldn't chdir to temp dir %s\n",
+                   tmpdir);
+               exit(1);
+       }
+       /* Report per-test summaries. */
+       tests[i].failures = failures - failures_before;
+       test_summarize(test_filename, tests[i].failures);
+       /* Close the per-test log file. */
+       fclose(logfile);
+       logfile = NULL;
+       /* If there were no failures, we can remove the work dir and logfile. */
+       if (tests[i].failures == 0) {
+               if (!keep_temp_files && assertChdir(tmpdir)) {
+#if defined(_WIN32) && !defined(__CYGWIN__)
+                       /* Make sure not to leave empty directories.
+                        * Sometimes a processing of closing files used by tests
+                        * is not done, then rmdir will be failed and it will
+                        * leave a empty test directory. So we should wait a few
+                        * seconds and retry rmdir. */
+                       int r, t;
+                       for (t = 0; t < 10; t++) {
+                               if (t > 0)
+                                       Sleep(1000);
+                               r = systemf("rmdir /S /Q %s", tests[i].name);
+                               if (r == 0)
+                                       break;
+                       }
+                       systemf("del %s", logfilename);
+#else
+                       systemf("rm -rf %s", tests[i].name);
+                       systemf("rm %s", logfilename);
+#endif
+               }
+       }
+       /* Return appropriate status. */
+       return (tests[i].failures);
+}
+
+/*
+ *
+ *
+ * MAIN and support routines.
+ *
+ *
+ */
+
+static void
+usage(const char *program)
+{
+       static const int limit = sizeof(tests) / sizeof(tests[0]);
+       int i;
+
+       printf("Usage: %s [options] <test> <test> ...\n", program);
+       printf("Default is to run all tests.\n");
+       printf("Otherwise, specify the numbers of the tests you wish to run.\n");
+       printf("Options:\n");
+       printf("  -d  Dump core after any failure, for debugging.\n");
+       printf("  -k  Keep all temp files.\n");
+       printf("      Default: temp files for successful tests deleted.\n");
+#ifdef PROGRAM
+       printf("  -p <path>  Path to executable to be tested.\n");
+       printf("      Default: path taken from " ENVBASE " environment variable.\n");
+#endif
+       printf("  -q  Quiet.\n");
+       printf("  -r <dir>   Path to dir containing reference files.\n");
+       printf("      Default: Current directory.\n");
+       printf("  -v  Verbose.\n");
+       printf("Available tests:\n");
+       for (i = 0; i < limit; i++)
+               printf("  %d: %s\n", i, tests[i].name);
+       exit(1);
+}
+
+static char *
+get_refdir(const char *d)
+{
+       char tried[512] = { '\0' };
+       char buff[128];
+       char *pwd, *p;
+
+       /* If a dir was specified, try that */
+       if (d != NULL) {
+               pwd = NULL;
+               snprintf(buff, sizeof(buff), "%s", d);
+               p = slurpfile(NULL, "%s/%s", buff, KNOWNREF);
+               if (p != NULL) goto success;
+               strncat(tried, buff, sizeof(tried) - strlen(tried) - 1);
+               strncat(tried, "\n", sizeof(tried) - strlen(tried) - 1);
+               goto failure;
+       }
+
+       /* Get the current dir. */
+       pwd = getcwd(NULL, 0);
+       while (pwd[strlen(pwd) - 1] == '\n')
+               pwd[strlen(pwd) - 1] = '\0';
+
+       /* Look for a known file. */
+       snprintf(buff, sizeof(buff), "%s", pwd);
+       p = slurpfile(NULL, "%s/%s", buff, KNOWNREF);
+       if (p != NULL) goto success;
+       strncat(tried, buff, sizeof(tried) - strlen(tried) - 1);
+       strncat(tried, "\n", sizeof(tried) - strlen(tried) - 1);
+
+       snprintf(buff, sizeof(buff), "%s/test", pwd);
+       p = slurpfile(NULL, "%s/%s", buff, KNOWNREF);
+       if (p != NULL) goto success;
+       strncat(tried, buff, sizeof(tried) - strlen(tried) - 1);
+       strncat(tried, "\n", sizeof(tried) - strlen(tried) - 1);
+
+#if defined(LIBRARY)
+       snprintf(buff, sizeof(buff), "%s/%s/test", pwd, LIBRARY);
+#else
+       snprintf(buff, sizeof(buff), "%s/%s/test", pwd, PROGRAM);
+#endif
+       p = slurpfile(NULL, "%s/%s", buff, KNOWNREF);
+       if (p != NULL) goto success;
+       strncat(tried, buff, sizeof(tried) - strlen(tried) - 1);
+       strncat(tried, "\n", sizeof(tried) - strlen(tried) - 1);
+
+       if (memcmp(pwd, "/usr/obj", 8) == 0) {
+               snprintf(buff, sizeof(buff), "%s", pwd + 8);
+               p = slurpfile(NULL, "%s/%s", buff, KNOWNREF);
+               if (p != NULL) goto success;
+               strncat(tried, buff, sizeof(tried) - strlen(tried) - 1);
+               strncat(tried, "\n", sizeof(tried) - strlen(tried) - 1);
+
+               snprintf(buff, sizeof(buff), "%s/test", pwd + 8);
+               p = slurpfile(NULL, "%s/%s", buff, KNOWNREF);
+               if (p != NULL) goto success;
+               strncat(tried, buff, sizeof(tried) - strlen(tried) - 1);
+               strncat(tried, "\n", sizeof(tried) - strlen(tried) - 1);
+       }
+
+failure:
+       printf("Unable to locate known reference file %s\n", KNOWNREF);
+       printf("  Checked following directories:\n%s\n", tried);
+#if defined(_WIN32) && !defined(__CYGWIN__) && defined(_DEBUG)
+       DebugBreak();
+#endif
+       exit(1);
+
+success:
+       free(p);
+       free(pwd);
+       return strdup(buff);
+}
+
+int
+main(int argc, char **argv)
+{
+       static const int limit = sizeof(tests) / sizeof(tests[0]);
+       int i, tests_run = 0, tests_failed = 0, option;
+       time_t now;
+       char *refdir_alloc = NULL;
+       const char *progname;
+       const char *tmp, *option_arg, *p;
+       char tmpdir[256];
+       char tmpdir_timestamp[256];
+
+       (void)argc; /* UNUSED */
+
+#if defined(HAVE__CrtSetReportMode)
+       /* To stop to run the default invalid parameter handler. */
+       _set_invalid_parameter_handler(invalid_parameter_handler);
+       /* Disable annoying assertion message box. */
+       _CrtSetReportMode(_CRT_ASSERT, 0);
+#endif
+
+       /*
+        * Name of this program, used to build root of our temp directory
+        * tree.
+        */
+       progname = p = argv[0];
+       while (*p != '\0') {
+               /* Support \ or / dir separators for Windows compat. */
+               if (*p == '/' || *p == '\\')
+                       progname = p + 1;
+               ++p;
+       }
+
+#ifdef PROGRAM
+       /* Get the target program from environment, if available. */
+       testprogfile = getenv(ENVBASE);
+#endif
+
+       if (getenv("TMPDIR") != NULL)
+               tmp = getenv("TMPDIR");
+       else if (getenv("TMP") != NULL)
+               tmp = getenv("TMP");
+       else if (getenv("TEMP") != NULL)
+               tmp = getenv("TEMP");
+       else if (getenv("TEMPDIR") != NULL)
+               tmp = getenv("TEMPDIR");
+       else
+               tmp = "/tmp";
+
+       /* Allow -d to be controlled through the environment. */
+       if (getenv(ENVBASE "_DEBUG") != NULL)
+               dump_on_failure = 1;
+
+       /* Get the directory holding test files from environment. */
+       refdir = getenv(ENVBASE "_TEST_FILES");
+
+       /*
+        * Parse options, without using getopt(), which isn't available
+        * on all platforms.
+        */
+       ++argv; /* Skip program name */
+       while (*argv != NULL) {
+               if (**argv != '-')
+                       break;
+               p = *argv++;
+               ++p; /* Skip '-' */
+               while (*p != '\0') {
+                       option = *p++;
+                       option_arg = NULL;
+                       /* If 'opt' takes an argument, parse that. */
+                       if (option == 'p' || option == 'r') {
+                               if (*p != '\0')
+                                       option_arg = p;
+                               else if (*argv == NULL) {
+                                       fprintf(stderr,
+                                           "Option -%c requires argument.\n",
+                                           option);
+                                       usage(progname);
+                               } else
+                                       option_arg = *argv++;
+                               p = ""; /* End of this option word. */
+                       }
+
+                       /* Now, handle the option. */
+                       switch (option) {
+                       case 'd':
+                               dump_on_failure = 1;
+                               break;
+                       case 'k':
+                               keep_temp_files = 1;
+                               break;
+                       case 'p':
+#ifdef PROGRAM
+                               testprogfile = option_arg;
+#else
+                               fprintf(stderr, "-p option not permitted\n");
+                               usage(progname);
+#endif
+                               break;
+                       case 'q':
+                               verbosity--;
+                               break;
+                       case 'r':
+                               refdir = option_arg;
+                               break;
+                       case 'v':
+                               verbosity++;
+                               break;
+                       default:
+                               fprintf(stderr, "Unrecognized option '%c'\n",
+                                   option);
+                               usage(progname);
+                       }
+               }
+       }
+
+       /*
+        * Sanity-check that our options make sense.
+        */
+#ifdef PROGRAM
+       if (testprogfile == NULL) {
+               fprintf(stderr, "Program executable required\n");
+               usage(progname);
+       }
+
+       {
+               char *testprg;
+#if defined(_WIN32) && !defined(__CYGWIN__)
+               /* Command.com sometimes rejects '/' separators. */
+               testprg = strdup(testprogfile);
+               for (i = 0; testprg[i] != '\0'; i++) {
+                       if (testprg[i] == '/')
+                               testprg[i] = '\\';
+               }
+               testprogfile = testprg;
+#endif
+               /* Quote the name that gets put into shell command lines. */
+               testprg = malloc(strlen(testprogfile) + 3);
+               strcpy(testprg, "\"");
+               strcat(testprg, testprogfile);
+               strcat(testprg, "\"");
+               testprog = testprg;
+       }
+#endif
+
+       /*
+        * Create a temp directory for the following tests.
+        * Include the time the tests started as part of the name,
+        * to make it easier to track the results of multiple tests.
+        */
+       now = time(NULL);
+       for (i = 0; ; i++) {
+               strftime(tmpdir_timestamp, sizeof(tmpdir_timestamp),
+                   "%Y-%m-%dT%H.%M.%S",
+                   localtime(&now));
+               sprintf(tmpdir, "%s/%s.%s-%03d", tmp, progname,
+                   tmpdir_timestamp, i);
+               if (assertMakeDir(tmpdir,0755))
+                       break;
+               if (i >= 999) {
+                       fprintf(stderr,
+                           "ERROR: Unable to create temp directory %s\n",
+                           tmpdir);
+                       exit(1);
+               }
+       }
+
+       /*
+        * If the user didn't specify a directory for locating
+        * reference files, try to find the reference files in
+        * the "usual places."
+        */
+       refdir = refdir_alloc = get_refdir(refdir);
+
+       /*
+        * Banner with basic information.
+        */
+       printf("\n");
+       printf("If tests fail or crash, details will be in:\n");
+       printf("   %s\n", tmpdir);
+       printf("\n");
+       if (verbosity > VERBOSITY_SUMMARY_ONLY) {
+               printf("Reference files will be read from: %s\n", refdir);
+#ifdef PROGRAM
+               printf("Running tests on: %s\n", testprog);
+#endif
+               printf("Exercising: ");
+               fflush(stdout);
+               printf("%s\n", EXTRA_VERSION);
+       } else {
+               printf("Running ");
+               fflush(stdout);
+       }
+
+       /*
+        * Run some or all of the individual tests.
+        */
+       if (*argv == NULL) {
+               /* Default: Run all tests. */
+               for (i = 0; i < limit; i++) {
+                       if (test_run(i, tmpdir))
+                               tests_failed++;
+                       tests_run++;
+               }
+       } else {
+               while (*(argv) != NULL) {
+                       if (**argv >= '0' && **argv <= '9') {
+                               i = atoi(*argv);
+                               if (i < 0 || i >= limit) {
+                                       printf("*** INVALID Test %s\n", *argv);
+                                       free(refdir_alloc);
+                                       usage(progname);
+                                       /* usage() never returns */
+                               }
+                       } else {
+                               for (i = 0; i < limit; ++i) {
+                                       if (strcmp(*argv, tests[i].name) == 0)
+                                               break;
+                               }
+                               if (i >= limit) {
+                                       printf("*** INVALID Test ``%s''\n",
+                                              *argv);
+                                       free(refdir_alloc);
+                                       usage(progname);
+                                       /* usage() never returns */
+                               }
+                       }
+                       if (test_run(i, tmpdir))
+                               tests_failed++;
+                       tests_run++;
+                       argv++;
+               }
+       }
+
+       /*
+        * Report summary statistics.
+        */
+       if (verbosity > VERBOSITY_SUMMARY_ONLY) {
+               printf("\n");
+               printf("Totals:\n");
+               printf("  Tests run:         %8d\n", tests_run);
+               printf("  Tests failed:      %8d\n", tests_failed);
+               printf("  Assertions checked:%8d\n", assertions);
+               printf("  Assertions failed: %8d\n", failures);
+               printf("  Skips reported:    %8d\n", skips);
+       }
+       if (failures) {
+               printf("\n");
+               printf("Failing tests:\n");
+               for (i = 0; i < limit; ++i) {
+                       if (tests[i].failures)
+                               printf("  %d: %s (%d failures)\n", i,
+                                   tests[i].name, tests[i].failures);
+               }
+               printf("\n");
+               printf("Details for failing tests: %s\n", tmpdir);
+               printf("\n");
+       } else {
+               if (verbosity == VERBOSITY_SUMMARY_ONLY)
+                       printf("\n");
+               printf("%d tests passed, no failures\n", tests_run);
+       }
+
+       free(refdir_alloc);
+
+       /* If the final tmpdir is empty, we can remove it. */
+       /* This should be the usual case when all tests succeed. */
+       assertChdir("..");
+       rmdir(tmpdir);
+
+       return (tests_failed ? 1 : 0);
+}
diff --git a/commands/bsdtar/test/test-acl.sh b/commands/bsdtar/test/test-acl.sh
new file mode 100755 (executable)
index 0000000..818607d
--- /dev/null
@@ -0,0 +1,76 @@
+#!/bin/sh
+# Copyright (c) 2007 Tim Kientzle
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# $FreeBSD: src/usr.bin/tar/test/test-acl.sh,v 1.1 2007/03/11 10:36:42 kientzle Exp $
+
+# Exercise copying of ACLs
+echo "ACL handling"
+# Basic test configuration
+TESTDIR=/mnt/da0/acl-test
+. `dirname $0`/config.sh
+
+# Create some files with ACLs
+mkdir original
+cd original
+touch a
+chmod 664 a
+setfacl -m user:bin:rw- -m group:78:r-x a \
+    || echo XXX failed to set access ACL on a XXX
+mkdir d
+chmod 775 d
+setfacl -m user:daemon:rw- -m group:78:r-x d \
+    || echo XXX failed to set access ACL on d XXX
+setfacl -d   -m user::rw- -m group::rw- -m other::rw- -m group:79:r-- d \
+    || echo XXX failed to set default ACL on d XXX
+cd ..
+
+# Copy the dir with -p
+echo "  -p preserves ACLs"
+mkdir copy
+(cd original && ${BSDTAR} -cf - .) | (cd copy; ${BSDTAR} -xpf -)
+
+# Verify the ACLs
+cd copy
+if [ "user::rw- user:bin:rw- group::rw- group:78:r-x mask::rwx other::r--" \
+    = "`echo \`getfacl -q a\``" ]; then
+    # It matches!!
+else
+    echo XXX a has wrong ACL XXX `getfacl -q a`
+fi
+
+if [ "user::rwx user:daemon:rw- group::rwx group:78:r-x mask::rwx other::r-x" \
+    = "`echo \`getfacl -q d\``" ]; then
+    # It matches!!
+else
+    echo XXX d has wrong ACL XXX `getfacl -q d`
+fi
+
+
+if [ "user::rw- group::rw- group:79:r-- mask::rw- other::rw-" \
+    = "`echo \`getfacl -q -d d\``" ]; then
+    # It matches!!
+else
+    echo XXX d has wrong ACL XXX `getfacl -q -d d`
+fi
+
diff --git a/commands/bsdtar/test/test-basic.sh b/commands/bsdtar/test/test-basic.sh
new file mode 100755 (executable)
index 0000000..0564bc7
--- /dev/null
@@ -0,0 +1,432 @@
+#!/bin/sh
+# Copyright (c) 2007 Tim Kientzle
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# $FreeBSD: src/usr.bin/tar/test/test-basic.sh,v 1.5 2007/04/18 04:35:17 kientzle Exp $
+
+# Generate a dir tree with various data and copy it using
+# a variety of tools and flags.  This mostly checks that
+# we can read archives we write and those written by gtar
+# and cpio.
+
+echo "Basic archiving/copy interoperability tests"
+# Basic configuration
+. `dirname $0`/config.sh
+
+# We need some files to archive; generate some random files and files
+# with very long names and other special attributes
+mkdir -p original
+cd original
+# Create some long files with random text data
+for f in f0 f1 f2 f3 f4 f5 f6 f7 f8 f9; do
+    dd if=/dev/urandom bs=1k count=100 2>/dev/null | od > $f
+done
+# A sparse file
+dd if=/dev/zero of=sparse bs=1 count=1 oseek=100000 2>/dev/null
+# Files with long names
+touch a
+touch ab
+touch abc
+touch abcd
+touch abcde
+touch abcdef
+touch abcdefg
+touch abcdefgh
+touch abcdefghi
+touch abcdefghij
+touch abcdefghijk
+touch abcdefghijkl
+touch abcdefghijklm
+touch abcdefghijklmn
+touch abcdefghijklmno
+touch abcdefghijklmnop
+touch abcdefghijklmnopq
+touch abcdefghijklmnopqr
+touch abcdefghijklmnopqrs
+touch abcdefghijklmnopqrst
+touch abcdefghijklmnopqrstu
+touch abcdefghijklmnopqrstuv
+touch abcdefghijklmnopqrstuvw
+touch abcdefghijklmnopqrstuvwx
+touch abcdefghijklmnopqrstuvwxy
+touch abcdefghijklmnopqrstuvwxyz
+
+touch abcdefghijklmnopqrstuvwxyza
+touch abcdefghijklmnopqrstuvwxyzab
+touch abcdefghijklmnopqrstuvwxyzabc
+touch abcdefghijklmnopqrstuvwxyzabcd
+touch abcdefghijklmnopqrstuvwxyzabcde
+touch abcdefghijklmnopqrstuvwxyzabcdef
+touch abcdefghijklmnopqrstuvwxyzabcdefg
+touch abcdefghijklmnopqrstuvwxyzabcdefgh
+touch abcdefghijklmnopqrstuvwxyzabcdefghi
+touch abcdefghijklmnopqrstuvwxyzabcdefghij
+touch abcdefghijklmnopqrstuvwxyzabcdefghijk
+touch abcdefghijklmnopqrstuvwxyzabcdefghijkl
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklm
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmn
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmno
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnop
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopq
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqr
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrs
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstu
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvw
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
+
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyza
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzab
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcde
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgh
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghi
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghij
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmn
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmno
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnop
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopq
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqr
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrs
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstu
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvw
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
+
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyza
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzab
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcde
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgh
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghi
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghij
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmn
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmno
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnop
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopq
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqr
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrs
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstu
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvw
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
+
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyza
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzab
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcde
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgh
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghi
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghij
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmn
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmno
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnop
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopq
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqr
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrs
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstu
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvw
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
+
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyza
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzab
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcde
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgh
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghi
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghij
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmn
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmno
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnop
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopq
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqr
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrs
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstu
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvw
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
+
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyza
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzab
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcde
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgh
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghi
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghij
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmn
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmno
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnop
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopq
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqr
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrs
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstu
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvw
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
+
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyza
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzab
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcde
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgh
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghi
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghij
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmn
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmno
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnop
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopq
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqr
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrs
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstu
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvw
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
+
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyza
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzab
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcde
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgh
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghi
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghij
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmn
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmno
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnop
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopq
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqr
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrs
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstu
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvw
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy
+touch abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
+
+# A file with a long pathname
+mkdir -p 1abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
+cd ..
+
+# Basic test of archiving/dearchiving
+echo "  bsdtar -c | bsdtar -x"
+mkdir copy-default
+(cd original && ${BSDTAR} -cf - .) | (cd copy-default; ${BSDTAR} -xf -)
+(diff -r original copy-default || echo XXX FAILED XXX 1>&2) | head
+
+# Exercise gzip compression (test compressed output with gunzip -t
+echo "  bsdtar -cz | gunzip -t"
+(cd original && ${BSDTAR} -czf - .) | gunzip -tq
+
+# Ensure our compression works with gunzip program
+echo "  bsdtar -cz | gunzip | bsdtar -x"
+mkdir copy-gzip2
+(cd original && ${BSDTAR} -czf - .) | gunzip -q | (cd copy-gzip2; ${BSDTAR} -xf -)
+(diff -r original copy-gzip2 || echo XXX FAILED XXX 1>&2) | head
+
+# Ensure our decompression works with gzip program
+echo "  bsdtar -c | gzip | bsdtar -x"
+mkdir copy-gunzip
+(cd original && ${BSDTAR} -cf - .) | gzip | (cd copy-gunzip; ${BSDTAR} -xf -)
+(diff -r original copy-gunzip || echo XXX FAILED XXX 1>&2) | head
+
+# Ensure our gzip compression/decompression work with each other
+echo "  bsdtar -cz | bsdtar -x"
+mkdir copy-gzip-gunzip
+(cd original && ${BSDTAR} -czf - .) | (cd copy-gzip-gunzip; ${BSDTAR} -xf -)
+(diff -r original copy-gzip-gunzip || echo XXX FAILED XXX 1>&2) | head
+
+# Ensure our decompression works with bzip2 program
+echo "  bsdtar -c | bzip2 | bsdtar -x"
+mkdir copy-bunzip
+(cd original && ${BSDTAR} -cf - .) | bzip2 | (cd copy-bunzip; ${BSDTAR} -xf -)
+(diff -r original copy-bunzip || echo XXX FAILED XXX 1>&2) | head
+
+# Ensure our compression works with bunzip2 program
+echo "  bsdtar -cy | bunzip2 | bsdtar -x"
+mkdir copy-bzip2
+(cd original && ${BSDTAR} -cyf - .) | bunzip2 -q | (cd copy-bzip2; ${BSDTAR} -xf -)
+(diff -r original copy-bzip2 || echo XXX FAILED XXX 1>&2) | head
+
+# Ensure our bzip2 compression/decompression work with each other
+echo "  bsdtar -cy | bsdtar -x"
+mkdir copy-bzip2-bunzip2
+(cd original && ${BSDTAR} -cyf - .) | (cd copy-bzip2-bunzip2; ${BSDTAR} -xf -)
+(diff -r original copy-bzip2-bunzip2 || echo XXX FAILED XXX 1>&2) | head
+
+# Ensure that archive listing works
+echo "  bsdtar -c | bsdtar -t"
+(cd original && find .) | sort > list-original
+(cd original && ${BSDTAR} -cf - .) | ${BSDTAR} -tf - | sed 's|/$||' | sort > list-default
+(diff list-original list-default || echo XXX FAILED XXX 1>&2) | head
+
+# Ensure that listing of deflated archives works
+echo "  bsdtar -cz | bsdtar -t"
+(cd original && ${BSDTAR} -czf - .) | ${BSDTAR} -tf - | sed 's|/$||' | sort > list-gzip
+(diff list-original list-gzip || echo XXX FAILED XXX 1>&2) | head
+
+# Ensure that listing of bzip2ed archives works
+echo "  bsdtar -cy | bsdtar -t"
+(cd original && ${BSDTAR} -cyf - .) | ${BSDTAR} -tf - | sed 's|/$||' |  sort > list-bzip2
+(diff list-original list-bzip2 || echo XXX FAILED XXX 1>&2) | head
+
+# Filtering exercises different areas of the library.
+echo "  Convert tar archive to a tar archive"
+mkdir filter-tar-tar
+(cd original && ${BSDTAR} -cf - .) | ${BSDTAR} -cf - @- | (cd filter-tar-tar; ${BSDTAR} -xf -)
+(diff -r original filter-tar-tar || echo XXX FAILED XXX 1>&2) | head
+
+# Make sure that reading and writing a tar archive doesn't change it.
+echo "  bsdtar -cf- @- | cmp"
+(cd original && ${BSDTAR} -cf - .) > original.tar
+${BSDTAR} -cf - @- < original.tar | cmp - original.tar || echo XXX FAILED XXX
+
+# Filtering as format conversion
+echo "  Convert tar archive to cpio archive"
+mkdir filter-tar-cpio
+(cd original && ${BSDTAR} -cf - .) | ${BSDTAR} -cf - --format=cpio @- | (cd filter-tar-cpio; ${BSDTAR} -xf -)
+(diff -r original filter-tar-cpio || echo XXX FAILED XXX 1>&2) | head
+
+# Test basic --include selection logic
+echo "  Convert tar to cpio with selection"
+mkdir filter-tar-selected
+(cd original && ${BSDTAR} -cf - .) | ${BSDTAR} -cf - --format=cpio --include=./f3 @- | (cd filter-tar-selected; ${BSDTAR} -xf -)
+(diff -r original/f3 filter-tar-selected/f3 || echo XXX FAILED XXX 1>&2) | head
+# Should be no files in copy except for 'f3'
+(cd filter-tar-selected ; ls | grep -v f3 | grep .) && echo XXX FAILED XXX
+
+# Test --include with wildcards
+echo "  Convert tar to cpio selecting with wildcards"
+mkdir filter-tar-selected2
+(cd original && ${BSDTAR} -cf - .) | ${BSDTAR} -cf - --format=cpio --include='./f*' @- | (cd filter-tar-selected2; ${BSDTAR} -xf -)
+for f in f1 f2 f3 f4 f5 f6 f7 f8 f9; do
+    (diff -r original/$f filter-tar-selected2/$f || echo XXX FAILED XXX 1>&2) | head
+done
+# Should be no files in copy except for 'f[0-9]'
+(cd filter-tar-selected2 ; ls | grep -v 'f[0-9]' | grep .) && echo XXX FAILED XXX
+
+# Check read/write of basic odc cpio format
+echo "  bsdtar -c --format=cpio | bsdtar -x"
+mkdir copy-cpio
+(cd original && ${BSDTAR} -cf - --format cpio .) | (cd copy-cpio; ${BSDTAR} -xf -)
+(diff -r original copy-cpio || echo XXX FAILED XXX 1>&2) | head
+
+# Ensure we can read gtar archives
+echo "  gtar -c | bsdtar -x"
+mkdir copy-gtar
+(cd original && ${GTAR} -cf - .) | (cd copy-gtar; ${BSDTAR} -xf -)
+(diff -r original copy-gtar || echo XXX FAILED XXX 1>&2) | head
+
+# Ensure we can read svr4crc cpio archives
+echo "  cpio -H crc | bsdtar -x"
+mkdir copy-svr4crc
+(cd original && find . | ${CPIO} -o -H crc 2>/dev/null) | (cd copy-svr4crc; ${BSDTAR} -xf -)
+(diff -r original copy-svr4crc || echo XXX FAILED XXX 1>&2) | head
+
+# Ensure we generate proper shar output
+echo "  bsdtar -c --format=shar | /bin/sh"
+mkdir copy-shar
+(cd original && ${BSDTAR} -cf - --format=shar --exclude=sparse .) | (cd copy-shar; /bin/sh >/dev/null)
+(diff -r --exclude=sparse original copy-shar || echo XXX FAILED XXX 1>&2) | head
+
+# Check that -u (update) picks up no new files
+echo "  bsdtar -u doesn't pick up unchanged files"
+(cd original && ${BSDTAR} -cf ../test-u.tar -b 1 .)
+cp test-u.tar test-u1.tar
+(cd original && ${BSDTAR} -uf ../test-u1.tar .)
+(diff test-u.tar test-u1.tar || echo XXX FAILED XXX 1>&2) | head
+
+# Check that -u (update) does pick up actual changed files
+echo "  bsdtar -u does pick up changed files"
+(cd original && echo hello >>f0)
+cp test-u.tar test-u2.tar
+(cd original && ${BSDTAR} -uf ../test-u2.tar .)
+# All this really tests is that the archive did change.
+cmp -s test-u.tar test-u2.tar && echo XXX FAILED XXX
+# Now, unpack the archive and verify the contents (including the change to f0)
+mkdir copy-u-test2
+(cd copy-u-test2 && ${BSDTAR} -xf ../test-u2.tar)
+(diff -r original copy-u-test2 || echo XXX FAILED XXX 1>&2) | head
diff --git a/commands/bsdtar/test/test-deep-dir.sh b/commands/bsdtar/test/test-deep-dir.sh
new file mode 100755 (executable)
index 0000000..22cf1c8
--- /dev/null
@@ -0,0 +1,60 @@
+#!/bin/sh
+# Copyright (c) 2007 Tim Kientzle
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# $FreeBSD: src/usr.bin/tar/test/test-deep-dir.sh,v 1.1 2007/03/11 10:36:42 kientzle Exp $
+
+# Stress the deep directory logic; the actual depth here seems to
+# be limited by the shell.  This should be restructured to get around
+# that limit (possibly by using perl to build the deep tree?)
+echo Deep directory tests
+
+# Basic test configuration
+. `dirname $0`/config.sh
+
+# Create a deep dir (shell seems to be limited by PATH_MAX)
+mkdir original
+cd original
+I=0
+while [ $I -lt 200 ]
+do
+    mkdir a$I
+    cd a$I
+    I=$(($I + 1))
+done
+while [ $I -gt 0 ] ; do cd ..; I=$(($I - 1)); done
+cd ..
+
+# Copy this using bsdtar
+echo "  tar -c | tar -x"
+mkdir copy
+(cd original; ${BSDTAR} -cf - .) | (cd copy; ${BSDTAR} -xf -)
+diff -r original copy || echo XXX FAILURE XXX
+
+# Copy gtar->bsdtar
+echo "  gtar -c | tar -x"
+mkdir copy-gtar
+(cd original; ${GTAR} -cf - .) | (cd copy-gtar; ${BSDTAR} -xf -)
+diff -r original copy-gtar || echo XXX FAILURE XXX
+cd ..
+
diff --git a/commands/bsdtar/test/test-flags.sh b/commands/bsdtar/test/test-flags.sh
new file mode 100755 (executable)
index 0000000..bfbf542
--- /dev/null
@@ -0,0 +1,74 @@
+#!/bin/sh
+# Copyright (c) 2007 Tim Kientzle
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# $FreeBSD: src/usr.bin/tar/test/test-flags.sh,v 1.1 2007/03/11 10:36:42 kientzle Exp $
+
+# Exercise copying of file flags
+echo "File Flag handling"
+# Basic test configuration
+. `dirname $0`/config.sh
+
+# Create some files with various flags set
+mkdir original
+FLAGS='uchg opaque nodump uappnd'
+for f in $FLAGS; do
+    touch original/test.$f
+    chflags $f original/test.$f
+done
+#ls -ol ${TESTDIR}/original
+
+# Copy the dir with -p
+echo "  -p preserves flags"
+mkdir copy
+(cd original && ${BSDTAR} -cf - .) | (cd copy; ${BSDTAR} -xpf -)
+# Verify that the flags are set
+for f in $FLAGS; do
+    [ "$f" = `ls -ol copy/test.$f | awk '{print $5}'` ]                \
+       || echo XXX FAIL: $f not preserved with -p XXX
+done
+#ls -ol ${TESTDIR}/copy
+
+# Copy the dir without -p
+echo "  flags omitted without -p"
+mkdir copy2
+(cd original && ${BSDTAR} -cf - .) | (cd copy2; ${BSDTAR} -xf -)
+# Verify that the flags are not set
+for f in $FLAGS; do
+    [ "$f" = `ls -ol copy2/test.$f | awk '{print $5}'` ]       \
+       && echo XXX FAIL: $f copied without -p XXX
+done
+#ls -ol ${TESTDIR}/copy2
+
+# Strip off the flags so we can clean this directory on the next test
+for f in $FLAGS; do
+    if [ $f = 'nodump' ]; then
+       chflags dump original/test.$f
+       chflags dump copy/test.$f
+    else
+       chflags no$f original/test.$f
+       chflags no$f copy/test.$f
+    fi
+done
+cd ..
+
diff --git a/commands/bsdtar/test/test-nodump.sh b/commands/bsdtar/test/test-nodump.sh
new file mode 100755 (executable)
index 0000000..1d21b49
--- /dev/null
@@ -0,0 +1,52 @@
+#!/bin/sh
+# Copyright (c) 2007 Tim Kientzle
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# $FreeBSD: src/usr.bin/tar/test/test-nodump.sh,v 1.1 2007/03/11 10:36:42 kientzle Exp $
+
+# Test that archiving obeys the 'nodump' flag
+echo "Verify 'nodump'"
+# Basic test configuration
+. `dirname $0`/config.sh
+
+# Create some sample files, 'b' is marked nodump
+mkdir original
+cd original
+touch a
+touch b
+touch c
+# 'chflags' on FreeBSD, 'chattr' on Linux
+( chflags nodump b || chattr +d b ) >/dev/null 2>&1 || echo XXX NO chflags/chattr command XXX
+
+# Copy files with --nodump
+cd ..
+mkdir copy
+(cd original && ${BSDTAR} -cf - --nodump .) | (cd copy; ${BSDTAR} -xf -)
+
+# Verify that 'b' wasn't copied
+echo "  File marked nodump wasn't copied"
+if [ -e copy/b ] ; then echo XXX Copied nodump file XXX; fi
+echo "  File not marked nodump was copied"
+if [ \! -e copy/a ] ; then echo XXX Failed to copy non-nodump file a XXX; fi
+diff -r --exclude=b original copy || echo XXX FAILURE XXX
+cd ..
diff --git a/commands/bsdtar/test/test-overwrite.sh b/commands/bsdtar/test/test-overwrite.sh
new file mode 100755 (executable)
index 0000000..b920890
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/sh
+# Copyright (c) 2007 Tim Kientzle
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# $FreeBSD: src/usr.bin/tar/test/test-overwrite.sh,v 1.1 2007/03/11 10:36:42 kientzle Exp $
+
+echo "Test overwrite avoidance"
+. `dirname $0`/config.sh
+
+# Create a file with some data.
+# This ensures that test.tar actually has some data in it
+# by the time tar tries to add it to itself.
+dd if=/dev/urandom of=a bs=1k count=100 >/dev/null 2>&1
+
+# Now try to implicitly add archive to itself
+${BSDTAR} -cf test.tar . || echo XXX FAILED XXX
+
+# Create another file
+dd if=/dev/urandom of=b bs=1k count=100 >/dev/null 2>&1
+
+# Try again.
+${BSDTAR} -cf test.tar . || echo XXX FAILED XXX
+
+# Extract the archive and check that the two files got archived, despite the warning
+mkdir compare
+cd compare
+${BSDTAR} -xf ../test.tar
+cmp a ../a || echo XXX a didn't archive correctly XXX
+cmp b ../b || echo XXX b didn't archive correctly XXX
+
+# TODO: Test overwrite avoidance on extract
diff --git a/commands/bsdtar/test/test-utf8.sh b/commands/bsdtar/test/test-utf8.sh
new file mode 100755 (executable)
index 0000000..c1b18a6
--- /dev/null
@@ -0,0 +1,40 @@
+#!/bin/sh
+# Copyright (c) 2007 Tim Kientzle
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# $FreeBSD: src/usr.bin/tar/test/test-utf8.sh,v 1.1 2007/03/11 10:36:42 kientzle Exp $
+
+echo "Test UTF8 filenames"
+. `dirname $0`/config.sh
+
+# Create some files with names in UTF8
+export LC_ALL=en_US.UTF-8
+touch "Greek: Γειά σας"
+touch "Hebrew: שלום"
+touch "Russian: Здравствуйте!"
+touch "Japanese: �����, コンニチハ"
+touch "Chinese: ��"
+
+tar -cf test.tar .
+
+# TODO: Verify the resulting archive
\ No newline at end of file
diff --git a/commands/bsdtar/test/test.h b/commands/bsdtar/test/test.h
new file mode 100644 (file)
index 0000000..54c8b8b
--- /dev/null
@@ -0,0 +1,289 @@
+/*
+ * Copyright (c) 2003-2006 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.bin/tar/test/test.h,v 1.4 2008/08/21 07:04:57 kientzle Exp $
+ */
+
+/* Every test program should #include "test.h" as the first thing. */
+
+/*
+ * The goal of this file (and the matching test.c) is to
+ * simplify the very repetitive test-*.c test programs.
+ */
+#if defined(HAVE_CONFIG_H)
+/* Most POSIX platforms use the 'configure' script to build config.h */
+#include "config.h"
+#elif defined(__FreeBSD__)
+/* Building as part of FreeBSD system requires a pre-built config.h. */
+#include "config_freebsd.h"
+#elif defined(_WIN32) && !defined(__CYGWIN__)
+/* Win32 can't run the 'configure' script. */
+#include "config_windows.h"
+#else
+/* Warn if the library hasn't been (automatically or manually) configured. */
+#error Oops: No config.h and no pre-built configuration in test.h.
+#endif
+
+#include <sys/types.h>  /* Windows requires this before sys/stat.h */
+#include <sys/stat.h>
+
+#ifdef USE_DMALLOC
+#include <dmalloc.h>
+#endif
+#if HAVE_DIRENT_H
+#include <dirent.h>
+#endif
+#ifdef HAVE_DIRECT_H
+#include <direct.h>
+#define dirent direct
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#ifdef HAVE_IO_H
+#include <io.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <wchar.h>
+#ifdef HAVE_WINDOWS_H
+#include <windows.h>
+#endif
+
+/*
+ * System-specific tweaks.  We really want to minimize these
+ * as much as possible, since they make it harder to understand
+ * the mainline code.
+ */
+
+/* Windows (including Visual Studio and MinGW but not Cygwin) */
+#if defined(_WIN32) && !defined(__CYGWIN__)
+#include "../bsdtar_windows.h"
+#if !defined(__BORLANDC__)
+#define strdup _strdup
+#endif
+#define LOCALE_DE      "deu"
+#else
+#define LOCALE_DE      "de_DE.UTF-8"
+#endif
+
+/* Visual Studio */
+#ifdef _MSC_VER
+#define snprintf       sprintf_s
+#endif
+
+/* Cygwin */
+#if defined(__CYGWIN__)
+/* Cygwin-1.7.x is lazy about populating nlinks, so don't
+ * expect it to be accurate. */
+# define NLINKS_INACCURATE_FOR_DIRS
+#endif
+
+/* Haiku OS */
+#if defined(__HAIKU__)
+/* Haiku has typedefs in stdint.h (needed for int64_t) */
+#include <stdint.h>
+#endif
+
+/* Get a real definition for __FBSDID if we can */
+#if HAVE_SYS_CDEFS_H
+#include <sys/cdefs.h>
+#endif
+
+/* If not, define it so as to avoid dangling semicolons. */
+#ifndef __FBSDID
+#define        __FBSDID(a)     struct _undefined_hack
+#endif
+
+#ifndef O_BINARY
+#define        O_BINARY 0
+#endif
+
+/*
+ * Redefine DEFINE_TEST for use in defining the test functions.
+ */
+#undef DEFINE_TEST
+#define DEFINE_TEST(name) void name(void); void name(void)
+
+/* An implementation of the standard assert() macro */
+#define assert(e)   assertion_assert(__FILE__, __LINE__, (e), #e, NULL)
+/* chdir() and error if it fails */
+#define assertChdir(path)  \
+  assertion_chdir(__FILE__, __LINE__, path)
+/* Assert two integers are the same.  Reports value of each one if not. */
+#define assertEqualInt(v1,v2) \
+  assertion_equal_int(__FILE__, __LINE__, (v1), #v1, (v2), #v2, NULL)
+/* Assert two strings are the same.  Reports value of each one if not. */
+#define assertEqualString(v1,v2)   \
+  assertion_equal_string(__FILE__, __LINE__, (v1), #v1, (v2), #v2, NULL)
+/* As above, but v1 and v2 are wchar_t * */
+#define assertEqualWString(v1,v2)   \
+  assertion_equal_wstring(__FILE__, __LINE__, (v1), #v1, (v2), #v2, NULL)
+/* As above, but raw blocks of bytes. */
+#define assertEqualMem(v1, v2, l)      \
+  assertion_equal_mem(__FILE__, __LINE__, (v1), #v1, (v2), #v2, (l), #l, NULL)
+/* Assert two files are the same; allow printf-style expansion of second name.
+ * See below for comments about variable arguments here...
+ */
+#define assertEqualFile                \
+  assertion_setup(__FILE__, __LINE__);assertion_equal_file
+/* Assert that a file is empty; supports printf-style arguments. */
+#define assertEmptyFile                \
+  assertion_setup(__FILE__, __LINE__);assertion_empty_file
+/* Assert that a file is not empty; supports printf-style arguments. */
+#define assertNonEmptyFile             \
+  assertion_setup(__FILE__, __LINE__);assertion_non_empty_file
+#define assertFileAtime(pathname, sec, nsec)   \
+  assertion_file_atime(__FILE__, __LINE__, pathname, sec, nsec)
+#define assertFileAtimeRecent(pathname)        \
+  assertion_file_atime_recent(__FILE__, __LINE__, pathname)
+#define assertFileBirthtime(pathname, sec, nsec)       \
+  assertion_file_birthtime(__FILE__, __LINE__, pathname, sec, nsec)
+#define assertFileBirthtimeRecent(pathname) \
+  assertion_file_birthtime_recent(__FILE__, __LINE__, pathname)
+/* Assert that a file exists; supports printf-style arguments. */
+#define assertFileExists               \
+  assertion_setup(__FILE__, __LINE__);assertion_file_exists
+/* Assert that a file exists; supports printf-style arguments. */
+#define assertFileNotExists            \
+  assertion_setup(__FILE__, __LINE__);assertion_file_not_exists
+/* Assert that file contents match a string; supports printf-style arguments. */
+#define assertFileContents             \
+  assertion_setup(__FILE__, __LINE__);assertion_file_contents
+#define assertFileMtime(pathname, sec, nsec)   \
+  assertion_file_mtime(__FILE__, __LINE__, pathname, sec, nsec)
+#define assertFileMtimeRecent(pathname) \
+  assertion_file_mtime_recent(__FILE__, __LINE__, pathname)
+#define assertFileNLinks(pathname, nlinks)  \
+  assertion_file_nlinks(__FILE__, __LINE__, pathname, nlinks)
+#define assertFileSize(pathname, size)  \
+  assertion_file_size(__FILE__, __LINE__, pathname, size)
+#define assertTextFileContents         \
+  assertion_setup(__FILE__, __LINE__);assertion_text_file_contents
+#define assertFileContainsLinesAnyOrder(pathname, lines)       \
+       assertion_file_contains_lines_any_order(__FILE__, __LINE__, pathname, lines)
+#define assertIsDir(pathname, mode)            \
+  assertion_is_dir(__FILE__, __LINE__, pathname, mode)
+#define assertIsHardlink(path1, path2) \
+  assertion_is_hardlink(__FILE__, __LINE__, path1, path2)
+#define assertIsNotHardlink(path1, path2)      \
+  assertion_is_not_hardlink(__FILE__, __LINE__, path1, path2)
+#define assertIsReg(pathname, mode)            \
+  assertion_is_reg(__FILE__, __LINE__, pathname, mode)
+#define assertIsSymlink(pathname, contents)    \
+  assertion_is_symlink(__FILE__, __LINE__, pathname, contents)
+/* Create a directory, report error if it fails. */
+#define assertMakeDir(dirname, mode)   \
+  assertion_make_dir(__FILE__, __LINE__, dirname, mode)
+#define assertMakeFile(path, mode, contents) \
+  assertion_make_file(__FILE__, __LINE__, path, mode, contents)
+#define assertMakeHardlink(newfile, oldfile)   \
+  assertion_make_hardlink(__FILE__, __LINE__, newfile, oldfile)
+#define assertMakeSymlink(newfile, linkto)     \
+  assertion_make_symlink(__FILE__, __LINE__, newfile, linkto)
+#define assertUmask(mask)      \
+  assertion_umask(__FILE__, __LINE__, mask)
+
+/*
+ * This would be simple with C99 variadic macros, but I don't want to
+ * require that.  Instead, I insert a function call before each
+ * skipping() call to pass the file and line information down.  Crude,
+ * but effective.
+ */
+#define skipping       \
+  assertion_setup(__FILE__, __LINE__);test_skipping
+
+/* Function declarations.  These are defined in test_utility.c. */
+void failure(const char *fmt, ...);
+int assertion_assert(const char *, int, int, const char *, void *);
+int assertion_chdir(const char *, int, const char *);
+int assertion_empty_file(const char *, ...);
+int assertion_equal_file(const char *, const char *, ...);
+int assertion_equal_int(const char *, int, long long, const char *, long long, const char *, void *);
+int assertion_equal_mem(const char *, int, const void *, const char *, const void *, const char *, size_t, const char *, void *);
+int assertion_equal_string(const char *, int, const char *v1, const char *, const char *v2, const char *, void *);
+int assertion_equal_wstring(const char *, int, const wchar_t *v1, const char *, const wchar_t *v2, const char *, void *);
+int assertion_file_atime(const char *, int, const char *, long, long);
+int assertion_file_atime_recent(const char *, int, const char *);
+int assertion_file_birthtime(const char *, int, const char *, long, long);
+int assertion_file_birthtime_recent(const char *, int, const char *);
+int assertion_file_contains_lines_any_order(const char *, int, const char *, const char **);
+int assertion_file_contents(const void *, int, const char *, ...);
+int assertion_file_exists(const char *, ...);
+int assertion_file_mtime(const char *, int, const char *, long, long);
+int assertion_file_mtime_recent(const char *, int, const char *);
+int assertion_file_nlinks(const char *, int, const char *, int);
+int assertion_file_not_exists(const char *, ...);
+int assertion_file_size(const char *, int, const char *, long);
+int assertion_is_dir(const char *, int, const char *, int);
+int assertion_is_hardlink(const char *, int, const char *, const char *);
+int assertion_is_not_hardlink(const char *, int, const char *, const char *);
+int assertion_is_reg(const char *, int, const char *, int);
+int assertion_is_symlink(const char *, int, const char *, const char *);
+int assertion_make_dir(const char *, int, const char *, int);
+int assertion_make_file(const char *, int, const char *, int, const char *);
+int assertion_make_hardlink(const char *, int, const char *newpath, const char *);
+int assertion_make_symlink(const char *, int, const char *newpath, const char *);
+int assertion_non_empty_file(const char *, ...);
+int assertion_text_file_contents(const char *buff, const char *f);
+int assertion_umask(const char *, int, int);
+void assertion_setup(const char *, int);
+
+void test_skipping(const char *fmt, ...);
+
+/* Like sprintf, then system() */
+int systemf(const char * fmt, ...);
+
+/* Delay until time() returns a value after this. */
+void sleepUntilAfter(time_t);
+
+/* Return true if this platform can create symlinks. */
+int canSymlink(void);
+
+/* Return true if this platform can run the "gzip" program. */
+int canGzip(void);
+
+/* Return true if this platform can run the "gunzip" program. */
+int canGunzip(void);
+
+/* Suck file into string allocated via malloc(). Call free() when done. */
+/* Supports printf-style args: slurpfile(NULL, "%s/myfile", refdir); */
+char *slurpfile(size_t *, const char *fmt, ...);
+
+/* Extracts named reference file to the current directory. */
+void extract_reference_file(const char *);
+
+/*
+ * Special interfaces for program test harness.
+ */
+
+/* Pathname of exe to be tested. */
+const char *testprogfile;
+/* Name of exe to use in printf-formatted command strings. */
+/* On Windows, this includes leading/trailing quotes. */
+const char *testprog;
diff --git a/commands/bsdtar/test/test_0.c b/commands/bsdtar/test/test_0.c
new file mode 100644 (file)
index 0000000..c9277da
--- /dev/null
@@ -0,0 +1,67 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+__FBSDID("$FreeBSD: src/usr.bin/tar/test/test_0.c,v 1.2 2008/05/26 17:10:10 kientzle Exp $");
+
+/*
+ * This first test does basic sanity checks on the environment.  For
+ * most of these, we just exit on failure.
+ */
+#if !defined(_WIN32) || defined(__CYGWIN__)
+#define DEV_NULL "/dev/null"
+#else
+#define DEV_NULL "NUL"
+#endif
+
+DEFINE_TEST(test_0)
+{
+       struct stat st;
+
+       failure("File %s does not exist?!", testprog);
+       if (!assertEqualInt(0, stat(testprogfile, &st)))
+               exit(1);
+
+       failure("%s is not executable?!", testprog);
+       if (!assert((st.st_mode & 0111) != 0))
+               exit(1);
+
+       /*
+        * Try to succesfully run the program; this requires that
+        * we know some option that will succeed.
+        */
+       if (0 == systemf("%s --version >" DEV_NULL, testprog)) {
+               /* This worked. */
+       } else if (0 == systemf("%s -W version >" DEV_NULL, testprog)) {
+               /* This worked. */
+       } else {
+               failure("Unable to successfully run any of the following:\n"
+                   "  * %s --version\n"
+                   "  * %s -W version\n",
+                   testprog, testprog);
+               assert(0);
+       }
+
+       /* TODO: Ensure that our reference files are available. */
+}
diff --git a/commands/bsdtar/test/test_basic.c b/commands/bsdtar/test/test_basic.c
new file mode 100644 (file)
index 0000000..4dc7cf6
--- /dev/null
@@ -0,0 +1,115 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+__FBSDID("$FreeBSD: src/usr.bin/tar/test/test_basic.c,v 1.2 2008/05/26 17:10:10 kientzle Exp $");
+
+
+static void
+basic_tar(const char *target, const char *pack_options,
+    const char *unpack_options, const char *flist)
+{
+       int r;
+
+       assertMakeDir(target, 0775);
+
+       /* Use the tar program to create an archive. */
+       r = systemf("%s cf - %s %s >%s/archive 2>%s/pack.err", testprog, pack_options, flist, target, target);
+       failure("Error invoking %s cf -", testprog, pack_options);
+       assertEqualInt(r, 0);
+
+       assertChdir(target);
+
+       /* Verify that nothing went to stderr. */
+       assertEmptyFile("pack.err");
+
+       /*
+        * Use tar to unpack the archive into another directory.
+        */
+       r = systemf("%s xf archive %s >unpack.out 2>unpack.err", testprog, unpack_options);
+       failure("Error invoking %s xf archive %s", testprog, unpack_options);
+       assertEqualInt(r, 0);
+
+       /* Verify that nothing went to stderr. */
+       assertEmptyFile("unpack.err");
+
+       /*
+        * Verify unpacked files.
+        */
+
+       /* Regular file with 2 links. */
+       assertIsReg("file", -1);
+       assertFileSize("file", 10);
+       failure("%s", target);
+       assertFileNLinks("file", 2);
+
+       /* Another name for the same file. */
+       assertIsReg("linkfile", -1);
+       assertFileSize("linkfile", 10);
+       assertFileNLinks("linkfile", 2);
+       assertIsHardlink("file", "linkfile");
+
+       /* Symlink */
+       if (canSymlink())
+               assertIsSymlink("symlink", "file");
+
+       /* dir */
+       assertIsDir("dir", 0775);
+       assertChdir("..");
+}
+
+DEFINE_TEST(test_basic)
+{
+       FILE *f;
+       const char *flist;
+
+       assertUmask(0);
+
+       /* File with 10 bytes content. */
+       f = fopen("file", "wb");
+       assert(f != NULL);
+       assertEqualInt(10, fwrite("123456789", 1, 10, f));
+       fclose(f);
+
+       /* hardlink to above file. */
+       assertMakeHardlink("linkfile", "file");
+       assertIsHardlink("file", "linkfile");
+
+       /* Symlink to above file. */
+       if (canSymlink())
+               assertMakeSymlink("symlink", "file");
+
+       /* Directory. */
+       assertMakeDir("dir", 0775);
+
+       if (canSymlink())
+               flist = "file linkfile symlink dir";
+       else
+               flist = "file linkfile dir";
+       /* Archive/dearchive with a variety of options. */
+       basic_tar("copy", "", "", flist);
+       /* tar doesn't handle cpio symlinks correctly */
+       /* basic_tar("copy_odc", "--format=odc", ""); */
+       basic_tar("copy_ustar", "--format=ustar", "", flist);
+}
diff --git a/commands/bsdtar/test/test_copy.c b/commands/bsdtar/test/test_copy.c
new file mode 100644 (file)
index 0000000..6911290
--- /dev/null
@@ -0,0 +1,372 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+__FBSDID("$FreeBSD: src/usr.bin/tar/test/test_copy.c,v 1.3 2008/08/15 06:12:02 kientzle Exp $");
+
+#if defined(__CYGWIN__)
+# include <limits.h>
+# include <sys/cygwin.h>
+#endif
+
+/*
+ * Try to figure out how deep we can go in our tests.  Assumes that
+ * the first call to this function has the longest starting cwd (which
+ * is currently "<testdir>/original").  This is mostly to work around
+ * limits in our Win32 support.
+ *
+ * Background: On Posix systems, PATH_MAX is merely a limit on the
+ * length of the string passed into a system call.  By repeatedly
+ * calling chdir(), you can work with arbitrarily long paths on such
+ * systems.  In contrast, Win32 APIs apply PATH_MAX limits to the full
+ * absolute path, so the permissible length of a system call argument
+ * varies with the cwd. Some APIs actually enforce limits
+ * significantly less than PATH_MAX to ensure that you can create
+ * files within the current working directory.  The Win32 limits also
+ * apply to Cygwin before 1.7.
+ *
+ * Someday, I want to convert the Win32 support to use newer
+ * wide-character paths with '\\?\' prefix, which has a 32k PATH_MAX
+ * instead of the rather anemic 260 character limit of the older
+ * system calls.  Then we can drop this mess (unless we want to
+ * continue to special-case Cygwin 1.5 and earlier).
+ */
+static int
+compute_loop_max(void)
+{
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       static int LOOP_MAX = 0;
+       char buf[MAX_PATH];
+       size_t cwdlen;
+
+       if (LOOP_MAX == 0) {
+               assert(_getcwd(buf, MAX_PATH) != NULL);
+               cwdlen = strlen(buf);
+               /* 12 characters = length of 8.3 filename */
+               /* 4 characters = length of "/../" used in symlink tests */
+               /* 1 character = length of extra "/" separator */
+               LOOP_MAX = MAX_PATH - (int)cwdlen - 12 - 4 - 1;
+       }
+       return LOOP_MAX;
+#elif defined(__CYGWIN__) && !defined(HAVE_CYGWIN_CONV_PATH)
+       static int LOOP_MAX = 0;
+       if (LOOP_MAX == 0) {
+               char wbuf[PATH_MAX];
+               char pbuf[PATH_MAX];
+               size_t wcwdlen;
+               size_t pcwdlen;
+               size_t cwdlen;
+               assert(getcwd(pbuf, PATH_MAX) != NULL);
+               pcwdlen = strlen(pbuf);
+               cygwin_conv_to_full_win32_path(pbuf, wbuf);
+               wcwdlen = strlen(wbuf);
+               cwdlen = ((wcwdlen > pcwdlen) ? wcwdlen : pcwdlen);
+               /* Cygwin helper needs an extra few characters. */
+               LOOP_MAX = PATH_MAX - (int)cwdlen - 12 - 4 - 4;
+       }
+       return LOOP_MAX;
+#else
+       /* cygwin-1.7 ends up here, along with "normal" unix */
+       return 200; /* restore pre-r278 depth */
+#endif
+}
+
+/* filenames[i] is a distinctive filename of length i. */
+/* To simplify interpreting failures, each filename ends with a
+ * decimal integer which is the length of the filename.  E.g., A
+ * filename ending in "_92" is 92 characters long.  To detect errors
+ * which drop or misplace characters, the filenames use a repeating
+ * "abcdefghijklmnopqrstuvwxyz..." pattern. */
+static char *filenames[201];
+
+static void
+compute_filenames(void)
+{
+       char buff[250];
+       size_t i,j;
+
+       filenames[0] = strdup("");
+       filenames[1] = strdup("1");
+       filenames[2] = strdup("a2");
+       for (i = 3; i < sizeof(filenames)/sizeof(filenames[0]); ++i) {
+               /* Fill with "abcdefghij..." */
+               for (j = 0; j < i; ++j)
+                       buff[j] = 'a' + (j % 26);
+               buff[j--] = '\0';
+               /* Work from the end to fill in the number portion. */
+               buff[j--] = '0' + (i % 10);
+               if (i > 9) {
+                       buff[j--] = '0' + ((i / 10) % 10);
+                       if (i > 99)
+                               buff[j--] = '0' + (i / 100);
+               }
+               buff[j] = '_';
+               /* Guard against obvious screwups in the above code. */
+               assertEqualInt(strlen(buff), i);
+               filenames[i] = strdup(buff);
+       }
+}
+
+static void
+create_tree(void)
+{
+       char buff[260];
+       char buff2[260];
+       int i;
+       int LOOP_MAX;
+
+       compute_filenames();
+
+       /* Log that we'll be omitting some checks. */
+       if (!canSymlink()) {
+               skipping("Symlink checks");
+       }
+
+       assertMakeDir("original", 0775);
+       assertEqualInt(0, chdir("original"));
+       LOOP_MAX = compute_loop_max();
+
+       assertMakeDir("f", 0775);
+       assertMakeDir("l", 0775);
+       assertMakeDir("m", 0775);
+       assertMakeDir("s", 0775);
+       assertMakeDir("d", 0775);
+
+       for (i = 1; i < LOOP_MAX; i++) {
+               failure("Internal sanity check failed: i = %d", i);
+               assert(filenames[i] != NULL);
+
+               sprintf(buff, "f/%s", filenames[i]);
+               assertMakeFile(buff, 0777, buff);
+
+               /* Create a link named "l/abcdef..." to the above. */
+               sprintf(buff2, "l/%s", filenames[i]);
+               assertMakeHardlink(buff2, buff);
+
+               /* Create a link named "m/abcdef..." to the above. */
+               sprintf(buff2, "m/%s", filenames[i]);
+               assertMakeHardlink(buff2, buff);
+
+               if (canSymlink()) {
+                       /* Create a symlink named "s/abcdef..." to the above. */
+                       sprintf(buff, "s/%s", filenames[i]);
+                       sprintf(buff2, "../f/%s", filenames[i]);
+                       failure("buff=\"%s\" buff2=\"%s\"", buff, buff2);
+                       assertMakeSymlink(buff, buff2);
+               }
+               /* Create a dir named "d/abcdef...". */
+               buff[0] = 'd';
+               failure("buff=\"%s\"", buff);
+               assertMakeDir(buff, 0775);
+       }
+
+       assertEqualInt(0, chdir(".."));
+}
+
+#define LIMIT_NONE 200
+#define LIMIT_USTAR 100
+
+static void
+verify_tree(size_t limit)
+{
+       char name1[260];
+       char name2[260];
+       size_t i, LOOP_MAX;
+
+       LOOP_MAX = compute_loop_max();
+
+       /* Generate the names we know should be there and verify them. */
+       for (i = 1; i < LOOP_MAX; i++) {
+               /* Verify a file named "f/abcdef..." */
+               sprintf(name1, "f/%s", filenames[i]);
+               if (i <= limit) {
+                       assertFileExists(name1);
+                       assertFileContents(name1, strlen(name1), name1);
+               }
+
+               sprintf(name2, "l/%s", filenames[i]);
+               if (i + 2 <= limit) {
+                       /* Verify hardlink "l/abcdef..." */
+                       assertIsHardlink(name1, name2);
+                       /* Verify hardlink "m/abcdef..." */
+                       name2[0] = 'm';
+                       assertIsHardlink(name1, name2);
+               }
+
+               if (canSymlink()) {
+                       /* Verify symlink "s/abcdef..." */
+                       sprintf(name1, "s/%s", filenames[i]);
+                       sprintf(name2, "../f/%s", filenames[i]);
+                       if (strlen(name2) <= limit)
+                               assertIsSymlink(name1, name2);
+               }
+
+               /* Verify dir "d/abcdef...". */
+               sprintf(name1, "d/%s", filenames[i]);
+               if (i + 1 <= limit) { /* +1 for trailing slash */
+                       if (assertIsDir(name1, -1)) {
+                               /* TODO: opendir/readdir this
+                                * directory and make sure
+                                * it's empty.
+                                */
+                       }
+               }
+       }
+
+#if !defined(_WIN32) || defined(__CYGWIN__)
+       {
+               const char *dp;
+               /* Now make sure nothing is there that shouldn't be. */
+               for (dp = "dflms"; *dp != '\0'; ++dp) {
+                       DIR *d;
+                       struct dirent *de;
+                       char dir[2];
+                       dir[0] = *dp; dir[1] = '\0';
+                       d = opendir(dir);
+                       failure("Unable to open dir '%s'", dir);
+                       if (!assert(d != NULL))
+                               continue;
+                       while ((de = readdir(d)) != NULL) {
+                               char *p = de->d_name;
+                               if (p[0] == '.')
+                                       continue;
+                               switch(dp[0]) {
+                               case 'l': case 'm': case 'd':
+                                       failure("strlen(p)=%d", strlen(p));
+                                       assert(strlen(p) < limit);
+                                       assertEqualString(p,
+                                           filenames[strlen(p)]);
+                                       break;
+                               case 'f': case 's':
+                                       failure("strlen(p)=%d", strlen(p));
+                                       assert(strlen(p) < limit + 1);
+                                       assertEqualString(p,
+                                           filenames[strlen(p)]);
+                                       break;
+                               default:
+                                       failure("File %s shouldn't be here", p);
+                                       assert(0);
+                               }
+                       }
+                       closedir(d);
+               }
+       }
+#endif
+}
+
+static void
+copy_basic(void)
+{
+       int r;
+
+       /* NOTE: for proper operation on cygwin-1.5 and windows, the
+        * length of the name of the directory below, "plain", must be
+        * less than or equal to the lengthe of the name of the original
+        * directory, "original"  This restriction derives from the
+        * extremely limited pathname lengths on those platforms.
+        */
+       assertMakeDir("plain", 0775);
+       assertEqualInt(0, chdir("plain"));
+
+       /*
+        * Use the tar program to create an archive.
+        */
+       r = systemf("%s cf archive -C ../original f d l m s >pack.out 2>pack.err",
+           testprog);
+       failure("Error invoking \"%s cf\"", testprog);
+       assertEqualInt(r, 0);
+
+       /* Verify that nothing went to stdout or stderr. */
+       assertEmptyFile("pack.err");
+       assertEmptyFile("pack.out");
+
+       /*
+        * Use tar to unpack the archive into another directory.
+        */
+       r = systemf("%s xf archive >unpack.out 2>unpack.err", testprog);
+       failure("Error invoking %s xf archive", testprog);
+       assertEqualInt(r, 0);
+
+       /* Verify that nothing went to stdout or stderr. */
+       assertEmptyFile("unpack.err");
+       assertEmptyFile("unpack.out");
+
+       verify_tree(LIMIT_NONE);
+       assertEqualInt(0, chdir(".."));
+}
+
+static void
+copy_ustar(void)
+{
+       const char *target = "ustar";
+       int r;
+
+       /* NOTE: for proper operation on cygwin-1.5 and windows, the
+        * length of the name of the directory below, "ustar", must be
+        * less than or equal to the lengthe of the name of the original
+        * directory, "original"  This restriction derives from the
+        * extremely limited pathname lengths on those platforms.
+        */
+       assertMakeDir(target, 0775);
+       assertEqualInt(0, chdir(target));
+
+       /*
+        * Use the tar program to create an archive.
+        */
+       r = systemf("%s cf archive --format=ustar -C ../original f d l m s >pack.out 2>pack.err",
+           testprog);
+       failure("Error invoking \"%s cf archive --format=ustar\"", testprog);
+       assertEqualInt(r, 0);
+
+       /* Verify that nothing went to stdout. */
+       assertEmptyFile("pack.out");
+       /* Stderr is non-empty, since there are a bunch of files
+        * with filenames too long to archive. */
+
+       /*
+        * Use tar to unpack the archive into another directory.
+        */
+       r = systemf("%s xf archive >unpack.out 2>unpack.err", testprog);
+       failure("Error invoking %s xf archive", testprog);
+       assertEqualInt(r, 0);
+
+       /* Verify that nothing went to stdout or stderr. */
+       assertEmptyFile("unpack.err");
+       assertEmptyFile("unpack.out");
+
+       verify_tree(LIMIT_USTAR);
+       assertEqualInt(0, chdir("../.."));
+}
+
+DEFINE_TEST(test_copy)
+{
+       assertUmask(0);
+       create_tree(); /* Create sample files in "original" dir. */
+
+       /* Test simple "tar -c | tar -x" pipeline copy. */
+       copy_basic();
+
+       /* Same, but constrain to ustar format. */
+       copy_ustar();
+}
diff --git a/commands/bsdtar/test/test_empty_mtree.c b/commands/bsdtar/test/test_empty_mtree.c
new file mode 100644 (file)
index 0000000..6f8a5e9
--- /dev/null
@@ -0,0 +1,45 @@
+/*-
+ * Copyright (c) 2003-2009 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+__FBSDID("$FreeBSD$");
+
+/*
+ * Regression test:  We used to get a bogus error message when we
+ * asked tar to copy entries out of an empty archive.  See
+ * Issue 51 on libarchive.googlecode.com for details.
+ */
+DEFINE_TEST(test_empty_mtree)
+{
+       int r;
+
+       assertMakeFile("test1.mtree", 0777, "#mtree\n");
+
+       r = systemf("%s cf test1.tar @test1.mtree >test1.out 2>test1.err",
+           testprog);
+       failure("Error invoking %s cf", testprog);
+       assertEqualInt(r, 0);
+       assertEmptyFile("test1.out");
+       assertEmptyFile("test1.err");
+}
diff --git a/commands/bsdtar/test/test_getdate.c b/commands/bsdtar/test/test_getdate.c
new file mode 100644 (file)
index 0000000..eac5710
--- /dev/null
@@ -0,0 +1,80 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+__FBSDID("$FreeBSD: src/usr.bin/tar/test/test_getdate.c,v 1.2 2008/05/26 17:10:10 kientzle Exp $");
+
+#include <time.h>
+
+/*
+ * Verify that the getdate() function works.
+ */
+
+time_t get_date(time_t, const char *);
+
+DEFINE_TEST(test_getdate)
+{
+       time_t now = time(NULL);
+
+       assertEqualInt(get_date(now, "Jan 1, 1970 UTC"), 0);
+       assertEqualInt(get_date(now, "7:12:18-0530 4 May 1983"), 420900138);
+       assertEqualInt(get_date(now, "2004/01/29 513 mest"), 1075345980);
+       assertEqualInt(get_date(now, "99/02/17 7pm utc"), 919278000);
+       assertEqualInt(get_date(now, "02/17/99 7:11am est"), 919253460);
+       /* It's important that we handle ctime() format. */
+       assertEqualInt(get_date(now, "Sun Feb 22 17:38:26 PST 2009"),
+           1235353106);
+       /* Basic relative offsets. */
+       /* If we use the actual current time as the reference, then
+        * these tests break around DST changes, so it's actually
+        * important to use a specific reference time here. */
+       assertEqualInt(get_date(0, "tomorrow"), 24 * 60 * 60);
+       assertEqualInt(get_date(0, "yesterday"), - 24 * 60 * 60);
+       assertEqualInt(get_date(0, "now + 1 hour"), 60 * 60);
+       assertEqualInt(get_date(0, "now + 1 hour + 1 minute"), 60 * 60 + 60);
+       /* Repeat the above for a different start time. */
+       now = 1231113600; /* Jan 5, 2009 00:00 UTC */
+       assertEqualInt(get_date(0, "Jan 5, 2009 00:00 UTC"), now);
+       assertEqualInt(get_date(now, "tomorrow"), now + 24 * 60 * 60);
+       assertEqualInt(get_date(now, "yesterday"), now - 24 * 60 * 60);
+       assertEqualInt(get_date(now, "now + 1 hour"), now + 60 * 60);
+       assertEqualInt(get_date(now, "now + 1 hour + 1 minute"),
+           now + 60 * 60 + 60);
+       assertEqualInt(get_date(now, "tomorrow 5:16am UTC"),
+           now + 24 * 60 * 60 + 5 * 60 * 60 + 16 * 60);
+       assertEqualInt(get_date(now, "UTC 5:16am tomorrow"),
+           now + 24 * 60 * 60 + 5 * 60 * 60 + 16 * 60);
+
+       /* Jan 5, 2009 was a Monday. */
+       assertEqualInt(get_date(now, "monday UTC"), now);
+       assertEqualInt(get_date(now, "sunday UTC"), now + 6 * 24 * 60 * 60);
+       assertEqualInt(get_date(now, "tuesday UTC"), now + 24 * 60 * 60);
+       /* "next tuesday" is one week after "tuesday" */
+       assertEqualInt(get_date(now, "UTC next tuesday"),
+           now + 8 * 24 * 60 * 60);
+       /* "last tuesday" is one week before "tuesday" */
+       assertEqualInt(get_date(now, "last tuesday UTC"),
+           now - 6 * 24 * 60 * 60);
+       /* TODO: Lots more tests here. */
+}
diff --git a/commands/bsdtar/test/test_help.c b/commands/bsdtar/test/test_help.c
new file mode 100644 (file)
index 0000000..3bb517d
--- /dev/null
@@ -0,0 +1,84 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+__FBSDID("$FreeBSD: src/usr.bin/tar/test/test_help.c,v 1.2 2008/05/26 17:10:10 kientzle Exp $");
+
+/*
+ * Test that "--help", "-h", and "-W help" options all work and
+ * generate reasonable output.
+ */
+
+static int
+in_first_line(const char *p, const char *substring)
+{
+       size_t l = strlen(substring);
+
+       while (*p != '\0' && *p != '\n') {
+               if (memcmp(p, substring, l) == 0)
+                       return (1);
+               ++p;
+       }
+       return (0);
+}
+
+DEFINE_TEST(test_help)
+{
+       int r;
+       char *p;
+       size_t plen;
+
+       /* Exercise --help option. */
+       r = systemf("%s --help >help.stdout 2>help.stderr", testprog);
+       assertEqualInt(r, 0);
+       failure("--help should generate nothing to stderr.");
+       assertEmptyFile("help.stderr");
+       /* Help message should start with name of program. */
+       p = slurpfile(&plen, "help.stdout");
+       failure("Help output should be long enough.");
+       assert(plen >= 6);
+       failure("First line of help output should contain 'bsdtar': %s", p);
+       assert(in_first_line(p, "bsdtar"));
+       /*
+        * TODO: Extend this check to further verify that --help output
+        * looks approximately right.
+        */
+       free(p);
+
+       /* -h option should generate the same output. */
+       r = systemf("%s -h >h.stdout 2>h.stderr", testprog);
+       assertEqualInt(r, 0);
+       failure("-h should generate nothing to stderr.");
+       assertEmptyFile("h.stderr");
+       failure("stdout should be same for -h and --help");
+       assertEqualFile("h.stdout", "help.stdout");
+
+       /* -W help should be another synonym. */
+       r = systemf("%s -W help >Whelp.stdout 2>Whelp.stderr", testprog);
+       assertEqualInt(r, 0);
+       failure("-W help should generate nothing to stderr.");
+       assertEmptyFile("Whelp.stderr");
+       failure("stdout should be same for -W help and --help");
+       assertEqualFile("Whelp.stdout", "help.stdout");
+}
diff --git a/commands/bsdtar/test/test_option_T_upper.c b/commands/bsdtar/test/test_option_T_upper.c
new file mode 100644 (file)
index 0000000..3d2a8b1
--- /dev/null
@@ -0,0 +1,188 @@
+/*-
+ * Copyright (c) 2003-2008 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+__FBSDID("$FreeBSD: src/usr.bin/tar/test/test_option_T.c,v 1.3 2008/08/15 06:12:02 kientzle Exp $");
+
+static int
+touch(const char *fn, int fail)
+{
+       FILE *f = fopen(fn, "w");
+       if (fail) {
+               failure("Couldn't create file '%s', errno=%d (%s)\n",
+                   fn, errno, strerror(errno));
+               if (!assert(f != NULL))
+                       return (0); /* Failure. */
+       } else {
+               if (f == NULL)
+                       return (0); /* Soft failure. */
+       }
+       fclose(f);
+       return (1); /* Success */
+}
+
+DEFINE_TEST(test_option_T_upper)
+{
+       FILE *f;
+       int r;
+       struct stat st;
+       int gnarlyFilesSupported;
+
+       /* Create a simple dir heirarchy; bail if anything fails. */
+       if (!assertMakeDir("d1", 0755)) return;
+       if (!assertMakeDir("d1/d2", 0755))      return;
+       if (!touch("f", 1)) return;
+       if (!touch("d1/f1", 1)) return;
+       if (!touch("d1/f2", 1)) return;
+       if (!touch("d1/d2/f3", 1)) return;
+       if (!touch("d1/d2/f4", 1)) return;
+       if (!touch("d1/d2/f5", 1)) return;
+       if (!touch("d1/d2/f6", 1)) return;
+       /* Some platforms don't permit such things; just skip it. */
+       gnarlyFilesSupported = touch("d1/d2/f\x0a", 0);
+
+       /* Populate a file list */
+       f = fopen("filelist", "w+");
+       if (!assert(f != NULL))
+               return;
+       /* Use a variety of text line endings. */
+       fprintf(f, "f\x0d"); /* CR */
+       fprintf(f, "d1/f1\x0d\x0a"); /* CRLF */
+       fprintf(f, "d1/d2/f4\x0a"); /* NL */
+       fprintf(f, "d1/d2/f6"); /* EOF */
+       fclose(f);
+
+       /* Populate a second file list */
+       f = fopen("filelist2", "w+");
+       if (!assert(f != NULL))
+               return;
+       /* Use null-terminated names. */
+       fprintf(f, "d1/d2/f3");
+       fwrite("\0", 1, 1, f);
+       fprintf(f, "d1/d2/f5");
+       fwrite("\0", 1, 1, f);
+       if (gnarlyFilesSupported) {
+               fprintf(f, "d1/d2/f\x0a");
+               fwrite("\0", 1, 1, f);
+       }
+       fclose(f);
+
+       /* Use -c -T to archive up the files. */
+       r = systemf("%s -c -f test1.tar -T filelist > test1.out 2> test1.err",
+           testprog);
+       assert(r == 0);
+       assertEmptyFile("test1.out");
+       assertEmptyFile("test1.err");
+
+       /* Use -x -T to dearchive the files */
+       if (!assertMakeDir("test1", 0755)) return;
+       systemf("%s -x -f test1.tar -T filelist -C test1"
+           " > test1b.out 2> test1b.err", testprog);
+       assertEmptyFile("test1b.out");
+       assertEmptyFile("test1b.err");
+
+       /* Verify the files were extracted. */
+       assertFileExists("test1/f");
+       assertFileExists("test1/d1/f1");
+       assertFileNotExists("test1/d1/f2");
+       assertFileNotExists("test1/d1/d2/f3");
+       assertFileExists("test1/d1/d2/f4");
+       assertFileNotExists("test1/d1/d2/f5");
+       assertFileExists("test1/d1/d2/f6");
+       if (gnarlyFilesSupported) {
+               assertFileNotExists("test1/d1/d2/f\x0a");
+       }
+
+       /* Use -r -T to add more files to the archive. */
+       systemf("%s -r -f test1.tar --null -T filelist2 > test2.out 2> test2.err",
+           testprog);
+       assertEmptyFile("test2.out");
+       assertEmptyFile("test2.err");
+
+       /* Use -x without -T to dearchive the files (ensure -r worked) */
+       if (!assertMakeDir("test3", 0755)) return;
+       systemf("%s -x -f test1.tar -C test3"
+           " > test3.out 2> test3.err", testprog);
+       assertEmptyFile("test3.out");
+       assertEmptyFile("test3.err");
+       /* Verify the files were extracted.*/
+       assertFileExists("test3/f");
+       assertFileExists("test3/d1/f1");
+       assertFileNotExists("test3/d1/f2");
+       assertFileExists("test3/d1/d2/f3");
+       assertFileExists("test3/d1/d2/f4");
+       assertFileExists("test3/d1/d2/f5");
+       assertFileExists("test3/d1/d2/f6");
+       if (gnarlyFilesSupported) {
+               assertFileExists("test3/d1/d2/f\x0a");
+       }
+
+       /* Use -x -T to dearchive the files (verify -x -T together) */
+       if (!assertMakeDir("test2", 0755)) return;
+       systemf("%s -x -f test1.tar -T filelist -C test2"
+           " > test2b.out 2> test2b.err", testprog);
+       assertEmptyFile("test2b.out");
+       assertEmptyFile("test2b.err");
+       /* Verify the files were extracted.*/
+       assertFileExists("test2/f");
+       assertFileExists("test2/d1/f1");
+       assertFileNotExists("test2/d1/f2");
+       assertFileNotExists("test2/d1/d2/f3");
+       assertFileExists("test2/d1/d2/f4");
+       assertFileNotExists("test2/d1/d2/f5");
+       assertFileExists("test2/d1/d2/f6");
+       if (gnarlyFilesSupported) {
+               assertFileNotExists("test2/d1/d2/f\x0a");
+       }
+
+       assertMakeDir("test4", 0755);
+       assertMakeDir("test4_out", 0755);
+       assertMakeDir("test4_out2", 0755);
+       assertMakeDir("test4/d1", 0755);
+       assertEqualInt(1, touch("test4/d1/foo", 0));
+
+       /* Does bsdtar support -s option ? */
+       systemf("%s -cf - -s /foo/bar/ test4/d1/foo > check.out 2> check.err",
+           testprog);
+       assertEqualInt(0, stat("check.err", &st));
+       if (st.st_size == 0) {
+               systemf("%s -cf - -s /foo/bar/ test4/d1/foo | %s -xf - -C test4_out",
+                   testprog, testprog);
+               assertEmptyFile("test4_out/test4/d1/bar");
+               systemf("%s -cf - -s /d1/d2/ test4/d1/foo | %s -xf - -C test4_out",
+                   testprog, testprog);
+               assertEmptyFile("test4_out/test4/d2/foo");
+               systemf("%s -cf - -s ,test4/d1/foo,, test4/d1/foo | %s -tvf - > test4.lst",
+                   testprog, testprog);
+               assertEmptyFile("test4.lst");
+               systemf("%s -cf - test4/d1/foo | %s -xf - -s /foo/bar/ -C test4_out2",
+                   testprog, testprog);
+               assertEmptyFile("test4_out2/test4/d1/bar");
+       } else {
+               skipping("bsdtar does not support -s option on this platform");
+       }
+
+       /* TODO: Include some use of -C directory-changing within the filelist. */
+       /* I'm pretty sure -C within the filelist is broken on extract. */
+}
diff --git a/commands/bsdtar/test/test_option_q.c b/commands/bsdtar/test/test_option_q.c
new file mode 100644 (file)
index 0000000..68867b5
--- /dev/null
@@ -0,0 +1,129 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+__FBSDID("$FreeBSD: src/usr.bin/tar/test/test_option_q.c,v 1.3 2008/08/22 01:35:08 kientzle Exp $");
+
+DEFINE_TEST(test_option_q)
+{
+       FILE *f;
+       int r;
+
+       /*
+        * Create an archive with several different versions of the
+        * same files.  By default, the last version will overwrite
+        * any earlier versions.  The -q/--fast-read option will
+        * stop early, so we can verify -q/--fast-read by seeing
+        * which version of each file actually ended up being
+        * extracted.  This also exercises -r mode, since that's
+        * what we use to build up the test archive.
+        */
+
+       f = fopen("foo", "w");
+       assert(f != NULL);
+       fprintf(f, "foo1");
+       fclose(f);
+
+       assertEqualInt(0, systemf("%s -cf archive.tar foo", testprog));
+
+       f = fopen("foo", "w");
+       assert(f != NULL);
+       fprintf(f, "foo2");
+       fclose(f);
+
+       assertEqualInt(0, systemf("%s -rf archive.tar foo", testprog));
+
+       f = fopen("bar", "w");
+       assert(f != NULL);
+       fprintf(f, "bar1");
+       fclose(f);
+
+       assertEqualInt(0, systemf("%s -rf archive.tar bar", testprog));
+
+       f = fopen("foo", "w");
+       assert(f != NULL);
+       fprintf(f, "foo3");
+       fclose(f);
+
+       assertEqualInt(0, systemf("%s -rf archive.tar foo", testprog));
+
+       f = fopen("bar", "w");
+       assert(f != NULL);
+       fprintf(f, "bar2");
+       fclose(f);
+
+       assertEqualInt(0, systemf("%s -rf archive.tar bar", testprog));
+
+       /*
+        * Now, try extracting from the test archive with various
+        * combinations of -q.
+        */
+
+       /* Test 1: -q foo should only extract the first foo. */
+       assertMakeDir("test1", 0755);
+       assertChdir("test1");
+       r = systemf("%s -xf ../archive.tar -q foo >test.out 2>test.err",
+           testprog);
+       failure("Fatal error trying to use -q option");
+       if (!assertEqualInt(0, r))
+               return;
+
+       assertFileContents("foo1", 4, "foo");
+       assertEmptyFile("test.out");
+       assertEmptyFile("test.err");
+       assertChdir("..");
+
+       /* Test 2: -q foo bar should extract up to the first bar. */
+       assertMakeDir("test2", 0755);
+       assertChdir("test2");
+       assertEqualInt(0,
+           systemf("%s -xf ../archive.tar -q foo bar >test.out 2>test.err", testprog));
+       assertFileContents("foo2", 4, "foo");
+       assertFileContents("bar1", 4, "bar");
+       assertEmptyFile("test.out");
+       assertEmptyFile("test.err");
+       assertChdir("..");
+
+       /* Test 3: Same as test 2, but use --fast-read spelling. */
+       assertMakeDir("test3", 0755);
+       assertChdir("test3");
+       assertEqualInt(0,
+           systemf("%s -xf ../archive.tar --fast-read foo bar >test.out 2>test.err", testprog));
+       assertFileContents("foo2", 4, "foo");
+       assertFileContents("bar1", 4, "bar");
+       assertEmptyFile("test.out");
+       assertEmptyFile("test.err");
+       assertChdir("..");
+
+       /* Test 4: Without -q, should extract everything. */
+       assertMakeDir("test4", 0755);
+       assertChdir("test4");
+       assertEqualInt(0,
+           systemf("%s -xf ../archive.tar foo bar >test.out 2>test.err", testprog));
+       assertFileContents("foo3", 4, "foo");
+       assertFileContents("bar2", 4, "bar");
+       assertEmptyFile("test.out");
+       assertEmptyFile("test.err");
+       assertChdir("..");
+}
diff --git a/commands/bsdtar/test/test_option_r.c b/commands/bsdtar/test/test_option_r.c
new file mode 100644 (file)
index 0000000..516a830
--- /dev/null
@@ -0,0 +1,117 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+__FBSDID("$FreeBSD$");
+
+/*
+ * Also see test_option_q for additional validation of -r support.
+ */
+DEFINE_TEST(test_option_r)
+{
+       char buff[15];
+       char *p0, *p1;
+       size_t s;
+       FILE *f;
+       int r;
+
+       /* Create a file */
+       f = fopen("f1", "w");
+       if (!assert(f != NULL))
+               return;
+       assertEqualInt(3, fwrite("abc", 1, 3, f));
+       fclose(f);
+
+       /* Archive that one file. */
+       r = systemf("%s cf archive.tar --format=ustar f1 >step1.out 2>step1.err", testprog);
+       failure("Error invoking %s cf archive.tar f1", testprog);
+       assertEqualInt(r, 0);
+
+       /* Verify that nothing went to stdout or stderr. */
+       assertEmptyFile("step1.out");
+       assertEmptyFile("step1.err");
+
+
+       /* Do some basic validation of the constructed archive. */
+       p0 = slurpfile(&s, "archive.tar");
+       if (!assert(p0 != NULL))
+               return;
+       if (!assert(s >= 2048)) {
+               free(p0);
+               return;
+       }
+       assertEqualMem(p0 + 0, "f1", 3);
+       assertEqualMem(p0 + 512, "abc", 3);
+       assertEqualMem(p0 + 1024, "\0\0\0\0\0\0\0\0", 8);
+       assertEqualMem(p0 + 1536, "\0\0\0\0\0\0\0\0", 8);
+
+       /* Edit that file */
+       f = fopen("f1", "w");
+       if (!assert(f != NULL))
+               return;
+       assertEqualInt(3, fwrite("123", 1, 3, f));
+       fclose(f);
+
+       /* Update the archive. */
+       r = systemf("%s rf archive.tar --format=ustar f1 >step2.out 2>step2.err", testprog);
+       failure("Error invoking %s rf archive.tar f1", testprog);
+       assertEqualInt(r, 0);
+
+       /* Verify that nothing went to stdout or stderr. */
+       assertEmptyFile("step2.out");
+       assertEmptyFile("step2.err");
+
+       /* Do some basic validation of the constructed archive. */
+       p1 = slurpfile(&s, "archive.tar");
+       if (!assert(p1 != NULL)) {
+               free(p0);
+               return;
+       }
+       assert(s >= 3072);
+       /* Verify first entry is unchanged. */
+       assertEqualMem(p0, p1, 1024);
+       /* Verify that second entry is correct. */
+       assertEqualMem(p1 + 1024, "f1", 3);
+       assertEqualMem(p1 + 1536, "123", 3);
+       /* Verify end-of-archive marker. */
+       assertEqualMem(p1 + 2048, "\0\0\0\0\0\0\0\0", 8);
+       assertEqualMem(p1 + 2560, "\0\0\0\0\0\0\0\0", 8);
+       free(p0);
+       free(p1);
+
+       /* Unpack both items */
+       assertMakeDir("step3", 0775);
+       assertChdir("step3");
+       r = systemf("%s xf ../archive.tar", testprog);
+       failure("Error invoking %s xf archive.tar", testprog);
+       assertEqualInt(r, 0);
+
+       /* Verify that the second one overwrote the first. */
+       f = fopen("f1", "r");
+       if (assert(f != NULL)) {
+               assertEqualInt(3, fread(buff, 1, 3, f));
+               assertEqualMem(buff, "123", 3);
+               fclose(f);
+       }
+}
diff --git a/commands/bsdtar/test/test_option_s.c b/commands/bsdtar/test/test_option_s.c
new file mode 100644 (file)
index 0000000..8eb415e
--- /dev/null
@@ -0,0 +1,107 @@
+/*-
+ * Copyright (c) 2003-2008 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+__FBSDID("$FreeBSD: src/usr.bin/tar/test/test_option_T.c,v 1.3 2008/08/15 06:12:02 kientzle Exp $");
+
+static int
+mkfile(const char *fn, const char *contents)
+{
+       FILE *f = fopen(fn, "w");
+       failure("Couldn't create file '%s', errno=%d (%s)\n",
+           fn, errno, strerror(errno));
+       if (!assert(f != NULL))
+               return (1); /* Failure. */
+       if (contents != NULL)
+               assertEqualInt(strlen(contents),
+                   fwrite(contents, 1, strlen(contents), f));
+       assertEqualInt(0, fclose(f));
+       return (0); /* Success */
+}
+
+DEFINE_TEST(test_option_s)
+{
+       struct stat st;
+
+       /* Create a sample file heirarchy. */
+       assertMakeDir("in", 0755);
+       assertMakeDir("in/d1", 0755);
+       assertEqualInt(0, mkfile("in/d1/foo", "foo"));
+       assertEqualInt(0, mkfile("in/d1/bar", "bar"));
+
+       /* Does bsdtar support -s option ? */
+       systemf("%s -cf - -s /foo/bar/ in/d1/foo > NUL 2> check.err",
+           testprog);
+       assertEqualInt(0, stat("check.err", &st));
+       if (st.st_size != 0) {
+               skipping("%s does not support -s option on this platform",
+                       testprog);
+               return;
+       }
+
+       /*
+        * Test 1: Filename substitution when creating archives.
+        */
+       assertMakeDir("test1", 0755);
+       systemf("%s -cf - -s /foo/bar/ in/d1/foo | %s -xf - -C test1",
+           testprog, testprog);
+       assertFileContents("foo", 3, "test1/in/d1/bar");
+       systemf("%s -cf - -s /d1/d2/ in/d1/foo | %s -xf - -C test1",
+           testprog, testprog);
+       assertFileContents("foo", 3, "test1/in/d2/foo");
+
+
+       /*
+        * Test 2: Basic substitution when extracting archive.
+        */
+       assertMakeDir("test2", 0755);
+       systemf("%s -cf - in/d1/foo | %s -xf - -s /foo/bar/ -C test2",
+           testprog, testprog);
+       assertFileContents("foo", 3, "test2/in/d1/bar");
+
+       /*
+        * Test 3: Files with empty names shouldn't be archived.
+        */
+       systemf("%s -cf - -s ,in/d1/foo,, in/d1/foo | %s -tvf - > in.lst",
+           testprog, testprog);
+       assertEmptyFile("in.lst");
+
+       /*
+        * Test 4: Multiple substitutions when extracting archive.
+        */
+       assertMakeDir("test4", 0755);
+       systemf("%s -cf - in/d1/foo in/d1/bar | %s -xf - -s /foo/bar/ -s }bar}baz} -C test4",
+           testprog, testprog);
+       assertFileContents("foo", 3, "test4/in/d1/bar");
+       assertFileContents("bar", 3, "test4/in/d1/baz");
+
+       /*
+        * Test 5: Name-switching substitutions when extracting archive.
+        */
+       assertMakeDir("test5", 0755);
+       systemf("%s -cf - in/d1/foo in/d1/bar | %s -xf - -s /foo/bar/ -s }bar}foo} -C test5",
+           testprog, testprog);
+       assertFileContents("foo", 3, "test5/in/d1/bar");
+       assertFileContents("bar", 3, "test5/in/d1/foo");
+}
diff --git a/commands/bsdtar/test/test_patterns.c b/commands/bsdtar/test/test_patterns.c
new file mode 100644 (file)
index 0000000..fee98be
--- /dev/null
@@ -0,0 +1,184 @@
+/*-
+ * Copyright (c) 2009 Michihiro NAKAJIMA
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+__FBSDID("$FreeBSD: src/usr.bin/tar/test/test_patterns.c,v 1.6 2008/08/21 22:28:00 kientzle Exp $");
+
+DEFINE_TEST(test_patterns)
+{
+       FILE *f;
+       int r;
+       const char *reffile2 = "test_patterns_2.tar";
+       const char *reffile3 = "test_patterns_3.tar";
+       const char *reffile4 = "test_patterns_4.tar";
+
+       const char *tar2aExpected[] = {
+               "/tmp/foo/bar/",
+               "/tmp/foo/bar/baz",
+               NULL
+       };
+
+       /*
+        * Test basic command-line pattern handling.
+        */
+
+       /*
+        * Test 1: Files on the command line that don't get matched
+        * didn't produce an error.
+        *
+        * John Baldwin reported this problem in PR bin/121598
+        */
+       f = fopen("foo", "w");
+       assert(f != NULL);
+       fclose(f);
+       r = systemf("%s cfv tar1.tgz foo > tar1a.out 2> tar1a.err", testprog);
+       assertEqualInt(r, 0);
+       r = systemf("%s xv --no-same-owner -f tar1.tgz foo bar > tar1b.out 2> tar1b.err", testprog);
+       failure("tar should return non-zero because a file was given on the command line that's not in the archive");
+       assert(r != 0);
+
+       /*
+        * Test 2: Check basic matching of full paths that start with /
+        */
+       extract_reference_file(reffile2);
+
+       r = systemf("%s tf %s /tmp/foo/bar > tar2a.out 2> tar2a.err",
+           testprog, reffile2);
+       assertEqualInt(r, 0);
+       assertFileContainsLinesAnyOrder("tar2a.out", tar2aExpected);
+       assertEmptyFile("tar2a.err");
+
+       /*
+        * Test 3 archive has some entries starting with '/' and some not.
+        */
+       extract_reference_file(reffile3);
+
+       /* Test 3a:  Pattern tmp/foo/bar should not match /tmp/foo/bar */
+       r = systemf("%s x --no-same-owner -f %s tmp/foo/bar > tar3a.out 2> tar3a.err",
+           testprog, reffile3);
+       assert(r != 0);
+       assertEmptyFile("tar3a.out");
+
+       /* Test 3b:  Pattern /tmp/foo/baz should not match tmp/foo/baz */
+       assertNonEmptyFile("tar3a.err");
+       /* Again, with the '/' */
+       r = systemf("%s x --no-same-owner -f %s /tmp/foo/baz > tar3b.out 2> tar3b.err",
+           testprog, reffile3);
+       assert(r != 0);
+       assertEmptyFile("tar3b.out");
+       assertNonEmptyFile("tar3b.err");
+
+       /* Test 3c: ./tmp/foo/bar should not match /tmp/foo/bar */
+       r = systemf("%s x --no-same-owner -f %s ./tmp/foo/bar > tar3c.out 2> tar3c.err",
+           testprog, reffile3);
+       assert(r != 0);
+       assertEmptyFile("tar3c.out");
+       assertNonEmptyFile("tar3c.err");
+
+       /* Test 3d: ./tmp/foo/baz should match tmp/foo/baz */
+       r = systemf("%s x --no-same-owner -f %s ./tmp/foo/baz > tar3d.out 2> tar3d.err",
+           testprog, reffile3);
+       assertEqualInt(r, 0);
+       assertEmptyFile("tar3d.out");
+       assertEmptyFile("tar3d.err");
+       assertFileExists("tmp/foo/baz/bar");
+
+       /*
+        * Test 4 archive has some entries starting with windows drive letters
+        * such as 'c:\', '//./c:/' or '//?/c:/'.
+        */
+       extract_reference_file(reffile4);
+
+       r = systemf("%s x --no-same-owner -f %s -C tmp > tar4.out 2> tar4.err",
+           testprog, reffile4);
+       assert(r != 0);
+       assertEmptyFile("tar4.out");
+       assertNonEmptyFile("tar4.err");
+
+       for (r = 1; r <= 54; r++) {
+               char file_a[] = "tmp/fileXX";
+               char file_b1[] = "tmp/server/share/fileXX";
+               char file_b2[] = "tmp/server\\share\\fileXX";
+               char file_c[] = "tmp/../fileXX";
+               char *filex;
+               int xsize;
+
+               switch (r) {
+               case 15: case 18:
+                       /*
+                        * Including server and share names.
+                        * //?/UNC/server/share/file15
+                        * //?/unc/server/share/file18
+                        */
+                       filex = file_b1;
+                       xsize = sizeof(file_b1);
+                       break;
+               case 35: case 38: case 52:
+                       /*
+                        * Including server and share names.
+                        * \\?\UNC\server\share\file35
+                        * \\?\unc\server\share\file38
+                        * \/?/uNc/server\share\file52
+                        */
+                       filex = file_b2;
+                       xsize = sizeof(file_b2);
+                       break;
+               default:
+                       filex = file_a;
+                       xsize = sizeof(file_a);
+                       break;
+               }
+               filex[xsize-3] = '0' + r / 10;
+               filex[xsize-2] = '0' + r % 10;
+               switch (r) {
+               case 5: case 6: case 17: case 20: case 25:
+               case 26: case 37: case 40: case 43: case 54:
+                       /*
+                        * Not extracted patterns.
+                        * D:../file05
+                        * c:../../file06
+                        * //?/UNC/../file17
+                        * //?/unc/../file20
+                        * z:..\file25
+                        * c:..\..\file26
+                        * \\?\UNC\..\file37
+                        * \\?\unc\..\file40
+                        * c:../..\file43
+                        * \/?\UnC\../file54
+                        */
+                       assertFileNotExists(filex);
+                       filex = file_c;
+                       xsize = sizeof(file_c);
+                       filex[xsize-3] = '0' + r / 10;
+                       filex[xsize-2] = '0' + r % 10;
+                       assertFileNotExists(filex);
+                       break;
+               default:
+                       /* Extracted patterns. */
+                       assertFileExists(filex);
+                       break;
+               }
+       }
+}
diff --git a/commands/bsdtar/test/test_patterns_2.tar.uu b/commands/bsdtar/test/test_patterns_2.tar.uu
new file mode 100644 (file)
index 0000000..eba2dae
--- /dev/null
@@ -0,0 +1,231 @@
+begin 644 test_patterns_2.tar
+M+W1M<"]F;V\O````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````#`P,#<U-2``,#`Q-S4P(``P,#`P,#`@`#`P,#`P,#`P,#`P
+M(#$Q,#4Q,C$R-C4V(#`Q,C0T,0`@-0``````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````````!U<W1A<@`P,'1I;0``
+M````````````````````````````````````=VAE96P`````````````````
+M```````````````````P,#`P,#`@`#`P,#`P,"``````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M```````````````````````O=&UP+V9O;R]B87(O````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````,#`P-S4U(``P,#$W-3`@`#`P
+M,#`P,"``,#`P,#`P,#`P,#`@,3$P-3$R,3(V-3,@,#$S,C`R`"`U````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````'5S=&%R`#`P=&EM``````````````````````````````````````!W
+M:&5E;````````````````````````````````````#`P,#`P,"``,#`P,#`P
+M(```````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````"]T;7`O9F]O+V)A
+M>@``````````````````````````````````````````````````````````
+M```````````````````````````````````````````````````````````P
+M,#`V-#0@`#`P,3<U,"``,#`P,#`P(``P,#`P,#`P,#`P,"`Q,3`U,3(Q,C8U
+M-B`P,3,Q,C8`(#``````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````=7-T87(`,#!T:6T`````````````````
+M`````````````````````'=H965L````````````````````````````````
+M````,#`P,#`P(``P,#`P,#`@````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````+W1M<"]F;V\O8F%R+V)A>@``````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````#`P,#8T-"``,#`Q-S4P(``P,#`P,#`@`#`P,#`P
+M,#`P,#`P(#$Q,#4Q,C$R-C4S(#`Q,S8V-P`@,```````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````````````````!U<W1A<@`P
+M,'1I;0``````````````````````````````````````=VAE96P`````````
+M```````````````````````````P,#`P,#`@`#`P,#`P,"``````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+9````````````````````````````````````
+`
+end
diff --git a/commands/bsdtar/test/test_patterns_3.tar.uu b/commands/bsdtar/test/test_patterns_3.tar.uu
new file mode 100644 (file)
index 0000000..de60b8d
--- /dev/null
@@ -0,0 +1,231 @@
+begin 644 test_patterns_3.tar
+M+W1M<"]F;V\O8F%R+P``````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````#`P,#<U-2``,#`Q-S4P(``P,#`P,#`@`#`P,#`P,#`P,#`P
+M(#$Q,#4S,C`W-34R(#`Q,S(P-@`@-0``````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````````!U<W1A<@`P,'1I;0``
+M````````````````````````````````````=VAE96P`````````````````
+M```````````````````P,#`P,#`@`#`P,#`P,"``````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M```````````````````````O=&UP+V9O;R]B87(O8F%Z+P``````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````,#`P-S4U(``P,#$W-3`@`#`P
+M,#`P,"``,#`P,#`P,#`P,#`@,3$P-3,R,#<U-3(@,#$S-S8R`"`U````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````'5S=&%R`#`P=&EM``````````````````````````````````````!W
+M:&5E;````````````````````````````````````#`P,#`P,"``,#`P,#`P
+M(```````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````'1M<"]F;V\O8F%Z
+M+P``````````````````````````````````````````````````````````
+M```````````````````````````````````````````````````````````P
+M,#`W-34@`#`P,3<U,"``,#`P,#`P(``P,#`P,#`P,#`P,"`Q,3`U,S(P-S4V
+M,"`P,3,Q,S8`(#4`````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````=7-T87(`,#!T:6T`````````````````
+M`````````````````````'=H965L````````````````````````````````
+M````,#`P,#`P(``P,#`P,#`@````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````=&UP+V9O;R]B87HO8F%R+P``````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````#`P,#<U-2``,#`Q-S4P(``P,#`P,#`@`#`P,#`P
+M,#`P,#`P(#$Q,#4S,C`W-38P(#`Q,S<P,@`@-0``````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````````````````!U<W1A<@`P
+M,'1I;0``````````````````````````````````````=VAE96P`````````
+M```````````````````````````P,#`P,#`@`#`P,#`P,"``````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+9````````````````````````````````````
+`
+end
diff --git a/commands/bsdtar/test/test_patterns_4.tar.uu b/commands/bsdtar/test/test_patterns_4.tar.uu
new file mode 100644 (file)
index 0000000..240af20
--- /dev/null
@@ -0,0 +1,641 @@
+begin 644 test_patterns_4.tar
+M+V9I;&4P,0``````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````#`P,#8T-"``,#`Q-S4Q(``P,#$W-3$@`#`P,#`P,#`P,#`P
+M(#$Q,34P-C<T-C0R(#`Q,#,S-@`@,```````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````````!U<W1A<@`P,```````
+M````````````````````````````````````````````````````````````
+M```````````````````P,#`P,#`@`#`P,#`P,"``````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M```````````````````````O+BXO9FEL93`R````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````,#`P-C0T(``P,#$W-3$@`#`P
+M,3<U,2``,#`P,#`P,#`P,#`@,3$Q-3`V-S0V-#(@,#$P-34R`"`P````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````'5S=&%R`#`P````````````````````````````````````````````
+M`````````````````````````````````````````#`P,#`P,"``,#`P,#`P
+M(```````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````"\N+B\N+B]F:6QE
+M,#,`````````````````````````````````````````````````````````
+M```````````````````````````````````````````````````````````P
+M,#`V-#0@`#`P,3<U,2``,#`Q-S4Q(``P,#`P,#`P,#`P,"`Q,3$U,#8W-#8T
+M,B`P,3`W-C8`(#``````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````=7-T87(`,#``````````````````````
+M````````````````````````````````````````````````````````````
+M````,#`P,#`P(``P,#`P,#`@````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````8SHO9FEL93`T````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````#`P,#8T-"``,#`Q-S4Q(``P,#$W-3$@`#`P,#`P
+M,#`P,#`P(#$Q,34P-C<T-C0R(#`Q,#4W-@`@,```````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````````````````!U<W1A<@`P
+M,```````````````````````````````````````````````````````````
+M```````````````````````````P,#`P,#`@`#`P,#`P,"``````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````!$.BXN+V9I;&4P-0``````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````,#`P-C0T(``P,#$W
+M-3$@`#`P,3<U,2``,#`P,#`P,#`P,#`@,3$Q-3`V-S0V-#(@,#$P-C<T`"`P
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````'5S=&%R`#`P````````````````````````````````````
+M`````````````````````````````````````````````````#`P,#`P,"``
+M,#`P,#`P(```````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````````````&,Z+BXO
+M+BXO9FEL93`V````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M```````P,#`V-#0@`#`P,3<U,2``,#`Q-S4Q(``P,#`P,#`P,#`P,"`Q,3$U
+M,#8W-#8T,B`P,3$Q-#<`(#``````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````=7-T87(`,#``````````````
+M````````````````````````````````````````````````````````````
+M````````````,#`P,#`P(``P,#`P,#`@````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````0SHO+BXO9FEL93`W````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````#`P,#8T-"``,#`Q-S4Q(``P,#$W-3$@
+M`#`P,#`P,#`P,#`P(#$Q,34P-C<T-C0R(#`Q,#<U-``@,```````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````````````````````````!U
+M<W1A<@`P,```````````````````````````````````````````````````
+M```````````````````````````````````P,#`P,#`@`#`P,#`P,"``````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````!A.B\N+B\N+B]F:6QE,#@`
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````,#`P-C0T
+M(``P,#$W-3$@`#`P,3<U,2``,#`P,#`P,#`P,#`@,3$Q-3`V-S0V-#(@,#$Q
+M,C(V`"`P````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````'5S=&%R`#`P````````````````````````````
+M`````````````````````````````````````````````````````````#`P
+M,#`P,"``,#`P,#`P(```````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`"\O+B]C.B]F:6QE,#D`````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M```````````````P,#`V-#0@`#`P,3<U,2``,#`Q-S4Q(``P,#`P,#`P,#`P
+M,"`Q,3$U,#8W-#8T,B`P,3$P-S8`(#``````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````=7-T87(`,#``````
+M````````````````````````````````````````````````````````````
+M````````````````````,#`P,#`P(``P,#`P,#`@````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````+R\N+T,Z+RXN+V9I;&4Q,```````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````#`P,#8T-"``,#`Q-S4Q(``P
+M,#$W-3$@`#`P,#`P,#`P,#`P(#$Q,34P-C<T-C0R(#`Q,3(T,0`@,```````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````!U<W1A<@`P,```````````````````````````````````````````
+M```````````````````````````````````````````P,#`P,#`@`#`P,#`P
+M,"``````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M```````````````````````````````````````````````O+S\O8SHO9FEL
+M93$Q````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M,#`P-C0T(``P,#$W-3$@`#`P,3<U,2``,#`P,#`P,#`P,#`@,3$Q-3`V-S0V
+M-#(@,#$Q,3$P`"`P````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````'5S=&%R`#`P````````````````````
+M````````````````````````````````````````````````````````````
+M`````#`P,#`P,"``,#`P,#`P(```````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````"\O/R]#.B\N+B]F:6QE,3(`````````````````````````````
+M````````````````````````````````````````````````````````````
+M```````````````````````P,#`V-#0@`#`P,3<U,2``,#`Q-S4Q(``P,#`P
+M,#`P,#`P,"`Q,3$U,#8W-#8T,B`P,3$R-C0`(#``````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````=7-T87(`
+M,#``````````````````````````````````````````````````````````
+M````````````````````````````,#`P,#`P(``P,#`P,#`@````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````+R\O+V,Z+V9I;&4Q,P``````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````#`P,#8T-"``,#`Q
+M-S4Q(``P,#$W-3$@`#`P,#`P,#`P,#`P(#$Q,34P-C<T-C0R(#`Q,3`W,@`@
+M,```````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````!U<W1A<@`P,```````````````````````````````````
+M```````````````````````````````````````````````````P,#`P,#`@
+M`#`P,#`P,"``````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M```````````````````````````````````````````````````````O+R\O
+M0SHO+R\O+V9I;&4Q-```````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````,#`P-C0T(``P,#$W-3$@`#`P,3<U,2``,#`P,#`P,#`P,#`@,3$Q
+M-3`V-S0V-#(@,#$Q,S(W`"`P````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````'5S=&%R`#`P````````````
+M````````````````````````````````````````````````````````````
+M`````````````#`P,#`P,"``,#`P,#`P(```````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````"\O/R]53D,O<V5R=F5R+W-H87)E+V9I;&4Q-0``````
+M````````````````````````````````````````````````````````````
+M```````````````````````````````P,#`V-#0@`#`P,3<U,2``,#`Q-S4Q
+M(``P,#`P,#`P,#`P,"`Q,3$U,#8W-#8T,B`P,3,V,S4`(#``````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M=7-T87(`,#``````````````````````````````````````````````````
+M````````````````````````````````````,#`P,#`P(``P,#`P,#`@````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````+R\_+U5.0R]F:6QE,38`
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````````````#`P,#8T
+M-"``,#`Q-S4Q(``P,#$W-3$@`#`P,#`P,#`P,#`P(#$Q,34P-C<T-C0R(#`Q
+M,3(R-@`@,```````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````!U<W1A<@`P,```````````````````````````
+M```````````````````````````````````````````````````````````P
+M,#`P,#`@`#`P,#`P,"``````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M```O+S\O54Y#+RXN+V9I;&4Q-P``````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````,#`P-C0T(``P,#$W-3$@`#`P,3<U,2``,#`P,#`P,#`P
+M,#`@,3$Q-3`V-S0V-#(@,#$Q-#0R`"`P````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````'5S=&%R`#`P````
+M````````````````````````````````````````````````````````````
+M`````````````````````#`P,#`P,"``,#`P,#`P(```````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````"\O/R]U;F,O<V5R=F5R+W-H87)E+V9I;&4Q
+M.```````````````````````````````````````````````````````````
+M```````````````````````````````````````P,#`V-#0@`#`P,3<U,2``
+M,#`Q-S4Q(``P,#`P,#`P,#`P,"`Q,3$U,#8W-#8T,B`P,30P,#``(#``````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````=7-T87(`,#``````````````````````````````````````````
+M````````````````````````````````````````````,#`P,#`P(``P,#`P
+M,#`@````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````+R\_+W5N8R]F
+M:6QE,3D`````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`#`P,#8T-"``,#`Q-S4Q(``P,#$W-3$@`#`P,#`P,#`P,#`P(#$Q,34P-C<T
+M-C0R(#`Q,3,W,0`@,```````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````!U<W1A<@`P,```````````````````
+M````````````````````````````````````````````````````````````
+M```````P,#`P,#`@`#`P,#`P,"``````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M```````````O+S\O=6YC+RXN+V9I;&4R,```````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````,#`P-C0T(``P,#$W-3$@`#`P,3<U,2``,#`P
+M,#`P,#`P,#`@,3$Q-3`V-S0V-#(@,#$Q-3<T`"`P````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````````````'5S=&%R
+M`#`P````````````````````````````````````````````````````````
+M`````````````````````````````#`P,#`P,"``,#`P,#`P(```````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````%QF:6QE,C$`````````````````
+M````````````````````````````````````````````````````````````
+M```````````````````````````````````````````````P,#`V-#0@`#`P
+M,3<U,2``,#`Q-S4Q(``P,#`P,#`P,#`P,"`Q,3$U,#8W-#8T,B`P,3`T,34`
+M(#``````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````=7-T87(`,#``````````````````````````````````
+M````````````````````````````````````````````````````,#`P,#`P
+M(``P,#`P,#`@````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````7"XN
+M7&9I;&4R,@``````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````#`P,#8T-"``,#`Q-S4Q(``P,#$W-3$@`#`P,#`P,#`P,#`P(#$Q
+M,34P-C<T-C0R(#`Q,#<P-@`@,```````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````!U<W1A<@`P,```````````
+M````````````````````````````````````````````````````````````
+M```````````````P,#`P,#`@`#`P,#`P,"``````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````!<+BY<+BY<9FEL93(S````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````,#`P-C0T(``P,#$W-3$@`#`P,3<U
+M,2``,#`P,#`P,#`P,#`@,3$Q-3`V-S0V-#(@,#$Q,3<W`"`P````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`'5S=&%R`#`P````````````````````````````````````````````````
+M`````````````````````````````````````#`P,#`P,"``,#`P,#`P(```
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````$,Z7&9I;&4R-```````
+M````````````````````````````````````````````````````````````
+M```````````````````````````````````````````````````````P,#`V
+M-#0@`#`P,3<U,2``,#`Q-S4Q(``P,#`P,#`P,#`P,"`Q,3$U,#8W-#8T,B`P
+M,3`V,34`(#``````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````=7-T87(`,#``````````````````````````
+M````````````````````````````````````````````````````````````
+M,#`P,#`P(``P,#`P,#`@````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````>CHN+EQF:6QE,C4`````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````#`P,#8T-"``,#`Q-S4Q(``P,#$W-3$@`#`P,#`P,#`P
+M,#`P(#$Q,34P-C<T-C0R(#`Q,3`T,0`@,```````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````````````!U<W1A<@`P,```
+M````````````````````````````````````````````````````````````
+M```````````````````````P,#`P,#`@`#`P,#`P,"``````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````!C.BXN7"XN7&9I;&4R-@``````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````,#`P-C0T(``P,#$W-3$@
+M`#`P,3<U,2``,#`P,#`P,#`P,#`@,3$Q-3`V-S0V-#(@,#$Q,S`S`"`P````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````'5S=&%R`#`P````````````````````````````````````````
+M`````````````````````````````````````````````#`P,#`P,"``,#`P
+M,#`P(```````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````````%HZ7"XN7&9I
+M;&4R-P``````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M```P,#`V-#0@`#`P,3<U,2``,#`Q-S4Q(``P,#`P,#`P,#`P,"`Q,3$U,#8W
+M-#8T,B`P,3$Q,S<`(#``````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````=7-T87(`,#``````````````````
+M````````````````````````````````````````````````````````````
+M````````,#`P,#`P(``P,#`P,#`@````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````0SI<+BY<+BY<9FEL93(X````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````#`P,#8T-"``,#`Q-S4Q(``P,#$W-3$@`#`P
+M,#`P,#`P,#`P(#$Q,34P-C<T-C0R(#`Q,30P,0`@,```````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````````````````````!U<W1A
+M<@`P,```````````````````````````````````````````````````````
+M```````````````````````````````P,#`P,#`@`#`P,#`P,"``````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````!<7"Y<8SI<9FEL93(Y````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````,#`P-C0T(``P
+M,#$W-3$@`#`P,3<U,2``,#`P,#`P,#`P,#`@,3$Q-3`V-S0V-#(@,#$Q,S8T
+M`"`P````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````'5S=&%R`#`P````````````````````````````````
+M`````````````````````````````````````````````````````#`P,#`P
+M,"``,#`P,#`P(```````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````````````````%Q<
+M+EQ#.EPN+EQF:6QE,S``````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M```````````P,#`V-#0@`#`P,3<U,2``,#`Q-S4Q(``P,#`P,#`P,#`P,"`Q
+M,3$U,#8W-#8T,B`P,3$V,#0`(#``````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````=7-T87(`,#``````````
+M````````````````````````````````````````````````````````````
+M````````````````,#`P,#`P(``P,#`P,#`@````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````7%P_7&,Z7&9I;&4S,0``````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````#`P,#8T-"``,#`Q-S4Q(``P,#$W
+M-3$@`#`P,#`P,#`P,#`P(#$Q,34P-C<T-C0R(#`Q,3,W-@`@,```````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``!U<W1A<@`P,```````````````````````````````````````````````
+M```````````````````````````````````````P,#`P,#`@`#`P,#`P,"``
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````````!<7#]<1#I<+BY<9FEL
+M93,R````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````,#`P
+M-C0T(``P,#$W-3$@`#`P,3<U,2``,#`P,#`P,#`P,#`@,3$Q-3`V-S0V-#(@
+M,#$Q-C,P`"`P````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````'5S=&%R`#`P````````````````````````
+M````````````````````````````````````````````````````````````
+M`#`P,#`P,"``,#`P,#`P(```````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````%Q<7%QC.EQF:6QE,S,`````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M```````````````````P,#`V-#0@`#`P,3<U,2``,#`Q-S4Q(``P,#`P,#`P
+M,#`P,"`Q,3$U,#8W-#8T,B`P,3$T,S4`(#``````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````=7-T87(`,#``
+M````````````````````````````````````````````````````````````
+M````````````````````````,#`P,#`P(``P,#`P,#`@````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````7%Q<7$,Z7%Q<7%QF:6QE,S0`````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````#`P,#8T-"``,#`Q-S4Q
+M(``P,#$W-3$@`#`P,#`P,#`P,#`P(#$Q,34P-C<T-C0R(#`Q,C$U-@`@,```
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````!U<W1A<@`P,```````````````````````````````````````
+M```````````````````````````````````````````````P,#`P,#`@`#`P
+M,#`P,"``````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````````````````!<7#]<54Y#
+M7'-E<G9E<EQS:&%R95QF:6QE,S4`````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````,#`P-C0T(``P,#$W-3$@`#`P,3<U,2``,#`P,#`P,#`P,#`@,3$Q-3`V
+M-S0V-#(@,#$T,C4U`"`P````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````'5S=&%R`#`P````````````````
+M````````````````````````````````````````````````````````````
+M`````````#`P,#`P,"``,#`P,#`P(```````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````%Q</UQ53D-<9FEL93,V````````````````````````````
+M````````````````````````````````````````````````````````````
+M```````````````````````````P,#`V-#0@`#`P,3<U,2``,#`Q-S4Q(``P
+M,#`P,#`P,#`P,"`Q,3$U,#8W-#8T,B`P,3$U,30`(#``````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````=7-T
+M87(`,#``````````````````````````````````````````````````````
+M````````````````````````````````,#`P,#`P(``P,#`P,#`@````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````7%P_7%5.0UPN+EQF:6QE,S<`
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````````#`P,#8T-"``
+M,#`Q-S4Q(``P,#$W-3$@`#`P,#`P,#`P,#`P(#$Q,34P-C<T-C0R(#`Q,C`P
+M-0`@,```````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````!U<W1A<@`P,```````````````````````````````
+M```````````````````````````````````````````````````````P,#`P
+M,#`@`#`P,#`P,"``````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````````````````````````!<
+M7#]<=6YC7'-E<G9E<EQS:&%R95QF:6QE,S@`````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````,#`P-C0T(``P,#$W-3$@`#`P,3<U,2``,#`P,#`P,#`P,#`@
+M,3$Q-3`V-S0V-#(@,#$T-#(P`"`P````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````'5S=&%R`#`P````````
+M````````````````````````````````````````````````````````````
+M`````````````````#`P,#`P,"``,#`P,#`P(```````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````%Q</UQU;F-<9FEL93,Y````````````````````
+M````````````````````````````````````````````````````````````
+M```````````````````````````````````P,#`V-#0@`#`P,3<U,2``,#`Q
+M-S4Q(``P,#`P,#`P,#`P,"`Q,3$U,#8W-#8T,B`P,3$V-3<`(#``````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````=7-T87(`,#``````````````````````````````````````````````
+M````````````````````````````````````````,#`P,#`P(``P,#`P,#`@
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````7%P_7'5N8UPN+EQF
+M:6QE-#``````````````````````````````````````````````````````
+M`````````````````````````````````````````````````````````#`P
+M,#8T-"``,#`Q-S4Q(``P,#$W-3$@`#`P,#`P,#`P,#`P(#$Q,34P-C<T-C0R
+M(#`Q,C$S-P`@,```````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````!U<W1A<@`P,```````````````````````
+M````````````````````````````````````````````````````````````
+M```P,#`P,#`@`#`P,#`P,"``````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````!<+BXO9FEL930Q````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````,#`P-C0T(``P,#$W-3$@`#`P,3<U,2``,#`P,#`P
+M,#`P,#`@,3$Q-3`V-S0V-#(@,#$P-C,R`"`P````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````````'5S=&%R`#`P
+M````````````````````````````````````````````````````````````
+M`````````````````````````#`P,#`P,"``,#`P,#`P(```````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````%PN+B\N+EQF:6QE-#(`````````````
+M````````````````````````````````````````````````````````````
+M```````````````````````````````````````````P,#`V-#0@`#`P,3<U
+M,2``,#`Q-S4Q(``P,#`P,#`P,#`P,"`Q,3$U,#8W-#8T,B`P,3$Q,C,`(#``
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````=7-T87(`,#``````````````````````````````````````
+M````````````````````````````````````````````````,#`P,#`P(``P
+M,#`P,#`@````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````8SHN+B\N
+M+EQF:6QE-#,`````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````#`P,#8T-"``,#`Q-S4Q(``P,#$W-3$@`#`P,#`P,#`P,#`P(#$Q,34P
+M-C<T-C0R(#`Q,3(R-0`@,```````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````!U<W1A<@`P,```````````````
+M````````````````````````````````````````````````````````````
+M```````````P,#`P,#`@`#`P,#`P,"``````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````!#.B\N+EQF:6QE-#0`````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````,#`P-C0T(``P,#$W-3$@`#`P,3<U,2``
+M,#`P,#`P,#`P,#`@,3$Q-3`V-S0V-#(@,#$Q,#,R`"`P````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````````````````'5S
+M=&%R`#`P````````````````````````````````````````````````````
+M`````````````````````````````````#`P,#`P,"``,#`P,#`P(```````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````$0Z7"XN+RXN7&9I;&4T-0``
+M````````````````````````````````````````````````````````````
+M```````````````````````````````````````````````````P,#`V-#0@
+M`#`P,3<U,2``,#`Q-S4Q(``P,#`P,#`P,#`P,"`Q,3$U,#8W-#8T,B`P,3$S
+M,C0`(#``````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````=7-T87(`,#``````````````````````````````
+M````````````````````````````````````````````````````````,#`P
+M,#`P(``P,#`P,#`@````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M7"\N+V,Z7&9I;&4T-@``````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````#`P,#8T-"``,#`Q-S4Q(``P,#$W-3$@`#`P,#`P,#`P,#`P
+M(#$Q,34P-C<T-C0R(#`Q,3(S,0`@,```````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````````!U<W1A<@`P,```````
+M````````````````````````````````````````````````````````````
+M```````````````````P,#`P,#`@`#`P,#`P,"``````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````!<7"XO0SI<+BY<9FEL930W````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````,#`P-C0T(``P,#$W-3$@`#`P
+M,3<U,2``,#`P,#`P,#`P,#`@,3$Q-3`V-S0V-#(@,#$Q-3,W`"`P````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````'5S=&%R`#`P````````````````````````````````````````````
+M`````````````````````````````````````````#`P,#`P,"``,#`P,#`P
+M(```````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````%PO/UQC.B]F:6QE
+M-#@`````````````````````````````````````````````````````````
+M```````````````````````````````````````````````````````````P
+M,#`V-#0@`#`P,3<U,2``,#`Q-S4Q(``P,#`P,#`P,#`P,"`Q,3$U,#8W-#8T
+M,B`P,3$R-30`(#``````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````=7-T87(`,#``````````````````````
+M````````````````````````````````````````````````````````````
+M````,#`P,#`P(``P,#`P,#`@````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````7%P_+T0Z+RXN7&9I;&4T.0``````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````#`P,#8T-"``,#`Q-S4Q(``P,#$W-3$@`#`P,#`P
+M,#`P,#`P(#$Q,34P-C<T-C0R(#`Q,34P-@`@,```````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````````````````!U<W1A<@`P
+M,```````````````````````````````````````````````````````````
+M```````````````````````````P,#`P,#`@`#`P,#`P,"``````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````!<+R]<1#I<9FEL934P````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````,#`P-C0T(``P,#$W
+M-3$@`#`P,3<U,2``,#`P,#`P,#`P,#`@,3$Q-3`V-S0V-#(@,#$Q,C0S`"`P
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````'5S=&%R`#`P````````````````````````````````````
+M`````````````````````````````````````````````````#`P,#`P,"``
+M,#`P,#`P(```````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````````````%Q<+R]C
+M.EPO+UQ<9FEL934Q````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M```````P,#`V-#0@`#`P,3<U,2``,#`Q-S4Q(``P,#`P,#`P,#`P,"`Q,3$U
+M,#8W-#8T,B`P,3$W,S$`(#``````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````=7-T87(`,#``````````````
+M````````````````````````````````````````````````````````````
+M````````````,#`P,#`P(``P,#`P,#`@````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````7"\_+W5.8R]S97)V97)<<VAA<F5<9FEL934R````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````#`P,#8T-"``,#`Q-S4Q(``P,#$W-3$@
+M`#`P,#`P,#`P,#`P(#$Q,34P-C<T-C0R(#`Q-#$T-0`@,```````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````````````````````````!U
+M<W1A<@`P,```````````````````````````````````````````````````
+M```````````````````````````````````P,#`P,#`@`#`P,#`P,"``````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````!<7#\O54YC7&9I;&4U,P``
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````,#`P-C0T
+M(``P,#$W-3$@`#`P,3<U,2``,#`P,#`P,#`P,#`@,3$Q-3`V-S0V-#(@,#$Q
+M-#<V`"`P````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````'5S=&%R`#`P````````````````````````````
+M`````````````````````````````````````````````````````````#`P
+M,#`P,"``,#`P,#`P(```````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`%PO/UQ5;D-<+BXO9FEL934T````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M```````````````P,#`V-#0@`#`P,3<U,2``,#`Q-S4Q(``P,#`P,#`P,#`P
+M,"`Q,3$U,#8W-#8T,B`P,3$W,3(`(#``````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````=7-T87(`,#``````
+M````````````````````````````````````````````````````````````
+M````````````````````,#`P,#`P(``P,#`P,#`@````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+'````````````
+`
+end
diff --git a/commands/bsdtar/test/test_stdio.c b/commands/bsdtar/test/test_stdio.c
new file mode 100644 (file)
index 0000000..b95a4e3
--- /dev/null
@@ -0,0 +1,125 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+__FBSDID("$FreeBSD: src/usr.bin/tar/test/test_stdio.c,v 1.2 2008/05/26 17:10:10 kientzle Exp $");
+
+DEFINE_TEST(test_stdio)
+{
+       FILE *filelist;
+       char *p;
+       size_t s;
+       int r;
+
+       assertUmask(0);
+
+       /*
+        * Create a couple of files on disk.
+        */
+       /* File */
+       assertMakeFile("f", 0755, "abc");
+       /* Link to above file. */
+       assertMakeHardlink("l", "f");
+
+       /* Create file list (text mode here) */
+       filelist = fopen("filelist", "w");
+       assert(filelist != NULL);
+       fprintf(filelist, "f\n");
+       fprintf(filelist, "l\n");
+       fclose(filelist);
+
+       /*
+        * Archive/dearchive with a variety of options, verifying
+        * stdio paths.
+        */
+
+       /* 'cf' should generate no output unless there's an error. */
+       r = systemf("%s cf archive f l >cf.out 2>cf.err", testprog);
+       assertEqualInt(r, 0);
+       assertEmptyFile("cf.out");
+       assertEmptyFile("cf.err");
+
+       /* 'cvf' should generate file list on stderr, empty stdout. */
+       r = systemf("%s cvf archive f l >cvf.out 2>cvf.err", testprog);
+       assertEqualInt(r, 0);
+       failure("'cv' writes filenames to stderr, nothing to stdout (SUSv2)\n"
+           "Note that GNU tar writes the file list to stdout by default.");
+       assertEmptyFile("cvf.out");
+       /* TODO: Verify cvf.err has file list in SUSv2-prescribed format. */
+
+       /* 'cvf -' should generate file list on stderr, archive on stdout. */
+       r = systemf("%s cvf - f l >cvf-.out 2>cvf-.err", testprog);
+       assertEqualInt(r, 0);
+       failure("cvf - should write archive to stdout");
+       /* TODO: Verify cvf-.out has archive. */
+       failure("cvf - should write file list to stderr (SUSv2)");
+       /* TODO: Verify cvf-.err has verbose file list. */
+
+       /* 'tf' should generate file list on stdout, empty stderr. */
+       r = systemf("%s tf archive >tf.out 2>tf.err", testprog);
+       assertEqualInt(r, 0);
+       assertEmptyFile("tf.err");
+       failure("'t' mode should write results to stdout");
+       /* TODO: Verify tf.out has file list. */
+
+       /* 'tvf' should generate file list on stdout, empty stderr. */
+       r = systemf("%s tvf archive >tvf.out 2>tvf.err", testprog);
+       assertEqualInt(r, 0);
+       assertEmptyFile("tvf.err");
+       failure("'tv' mode should write results to stdout");
+       /* TODO: Verify tvf.out has file list. */
+
+       /* 'tvf -' uses stdin, file list on stdout, empty stderr. */
+       r = systemf("%s tvf - < archive >tvf-.out 2>tvf-.err", testprog);
+       assertEqualInt(r, 0);
+       assertEmptyFile("tvf-.err");
+       /* TODO: Verify tvf-.out has file list. */
+
+       /* Basic 'xf' should generate no output on stdout or stderr. */
+       r = systemf("%s xf archive >xf.out 2>xf.err", testprog);
+       assertEqualInt(r, 0);
+       assertEmptyFile("xf.err");
+       assertEmptyFile("xf.out");
+
+       /* 'xvf' should generate list on stderr, empty stdout. */
+       r = systemf("%s xvf archive >xvf.out 2>xvf.err", testprog);
+       assertEqualInt(r, 0);
+       assertEmptyFile("xvf.out");
+       /* TODO: Verify xvf.err */
+
+       /* 'xvOf' should generate list on stderr, file contents on stdout. */
+       r = systemf("%s xvOf archive >xvOf.out 2>xvOf.err", testprog);
+       assertEqualInt(r, 0);
+       /* Verify xvOf.out is the file contents */
+       p = slurpfile(&s, "xvOf.out");
+       assert(s = 3);
+       assertEqualMem(p, "abc", 3);
+       /* TODO: Verify xvf.err */
+
+       /* 'xvf -' should generate list on stderr, empty stdout. */
+       r = systemf("%s xvf - < archive >xvf-.out 2>xvf-.err", testprog);
+       assertEqualInt(r, 0);
+       assertEmptyFile("xvf-.out");
+       /* TODO: Verify xvf-.err */
+}
diff --git a/commands/bsdtar/test/test_strip_components.c b/commands/bsdtar/test/test_strip_components.c
new file mode 100644 (file)
index 0000000..c9028a4
--- /dev/null
@@ -0,0 +1,109 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+__FBSDID("$FreeBSD: src/usr.bin/tar/test/test_strip_components.c,v 1.2 2008/11/10 05:24:13 kientzle Exp $");
+
+static int
+touch(const char *fn)
+{
+       FILE *f = fopen(fn, "w");
+       failure("Couldn't create file '%s', errno=%d (%s)\n",
+           fn, errno, strerror(errno));
+       if (!assert(f != NULL))
+               return (0); /* Failure. */
+       fclose(f);
+       return (1); /* Success */
+}
+
+DEFINE_TEST(test_strip_components)
+{
+       assertMakeDir("d0", 0755);
+       assertChdir("d0");
+       assertMakeDir("d1", 0755);
+       assertMakeDir("d1/d2", 0755);
+       assertMakeDir("d1/d2/d3", 0755);
+       assertEqualInt(1, touch("d1/d2/f1"));
+       assertMakeHardlink("l1", "d1/d2/f1");
+       assertMakeHardlink("d1/l2", "d1/d2/f1");
+       if (canSymlink()) {
+               assertMakeSymlink("s1", "d1/d2/f1");
+               assertMakeSymlink("d1/s2", "d2/f1");
+       }
+       assertChdir("..");
+
+       assertEqualInt(0, systemf("%s -cf test.tar d0", testprog));
+
+       assertMakeDir("target", 0755);
+       assertEqualInt(0, systemf("%s -x -C target --strip-components 2 "
+           "-f test.tar", testprog));
+
+       failure("d0/ is too short and should not get restored");
+       assertFileNotExists("target/d0");
+       failure("d0/d1/ is too short and should not get restored");
+       assertFileNotExists("target/d1");
+       failure("d0/d1/s2 is a symlink to something that won't be extracted");
+       /* If platform supports symlinks, target/s2 is a broken symlink. */
+       /* If platform does not support symlink, target/s2 doesn't exist. */
+       assertFileNotExists("target/s2");
+       if (canSymlink())
+               assertIsSymlink("target/s2", "d2/f1");
+       failure("d0/d1/d2 should be extracted");
+       assertIsDir("target/d2", -1);
+
+       /*
+        * This next is a complicated case.  d0/l1, d0/d1/l2, and
+        * d0/d1/d2/f1 are all hardlinks to the same file; d0/l1 can't
+        * be extracted with --strip-components=2 and the other two
+        * can.  Remember that tar normally stores the first file with
+        * a body and the other as hardlink entries to the first
+        * appearance.  So the final result depends on the order in
+        * which these three names get archived.  If d0/l1 is first,
+        * none of the three can be restored.  If either of the longer
+        * names are first, then the two longer ones can both be
+        * restored.
+        *
+        * The tree-walking code used by bsdtar always visits files
+        * before subdirectories, so bsdtar's behavior is fortunately
+        * deterministic:  d0/l1 will always get stored first and the
+        * other two will be stored as hardlinks to d0/l1.  Since
+        * d0/l1 can't be extracted, none of these three will be
+        * extracted.
+        *
+        * It may be worth extending this test to force a particular
+        * archiving order so as to exercise both of the cases described
+        * above.
+        *
+        * Of course, this is all totally different for cpio and newc
+        * formats because the hardlink management is different.
+        * TODO: Rename this to test_strip_components_tar and create
+        * parallel tests for cpio and newc formats.
+        */
+       failure("d0/l1 is too short and should not get restored");
+       assertFileNotExists("target/l1");
+       failure("d0/d1/l2 is a hardlink to file whose name was too short");
+       assertFileNotExists("target/l2");
+       failure("d0/d1/d2/f1 is a hardlink to file whose name was too short");
+       assertFileNotExists("target/d2/f1");
+}
diff --git a/commands/bsdtar/test/test_symlink_dir.c b/commands/bsdtar/test/test_symlink_dir.c
new file mode 100644 (file)
index 0000000..aa80ba6
--- /dev/null
@@ -0,0 +1,160 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+__FBSDID("$FreeBSD: src/usr.bin/tar/test/test_symlink_dir.c,v 1.1 2008/09/14 02:16:04 kientzle Exp $");
+
+/*
+ * tar -x -P should follow existing symlinks for dirs, but not other
+ * content.  Plain tar -x should remove symlinks when they're in the
+ * way of a dir extraction.
+ */
+
+static int
+mkfile(const char *name, int mode, const char *contents, size_t size)
+{
+       FILE *f = fopen(name, "wb");
+       size_t written;
+
+       (void)mode; /* UNUSED */
+       if (f == NULL)
+               return (-1);
+       written = fwrite(contents, 1, size, f);
+       fclose(f);
+       if (size != written)
+               return (-1);
+       return (0);
+}
+
+DEFINE_TEST(test_symlink_dir)
+{
+       assertUmask(0);
+
+       assertMakeDir("source", 0755);
+       assertEqualInt(0, mkfile("source/file", 0755, "a", 1));
+       assertEqualInt(0, mkfile("source/file2", 0755, "ab", 2));
+       assertMakeDir("source/dir", 0755);
+       assertMakeDir("source/dir/d", 0755);
+       assertEqualInt(0, mkfile("source/dir/f", 0755, "abc", 3));
+       assertMakeDir("source/dir2", 0755);
+       assertMakeDir("source/dir2/d2", 0755);
+       assertEqualInt(0, mkfile("source/dir2/f2", 0755, "abcd", 4));
+       assertMakeDir("source/dir3", 0755);
+       assertMakeDir("source/dir3/d3", 0755);
+       assertEqualInt(0, mkfile("source/dir3/f3", 0755, "abcde", 5));
+
+       assertEqualInt(0,
+           systemf("%s -cf test.tar -C source dir dir2 dir3 file file2",
+               testprog));
+
+       /*
+        * Extract with -x and without -P.
+        */
+       assertMakeDir("dest1", 0755);
+       /* "dir" is a symlink to an existing "dest1/real_dir" */
+       assertMakeDir("dest1/real_dir", 0755);
+       if (canSymlink()) {
+               assertMakeSymlink("dest1/dir", "real_dir");
+               /* "dir2" is a symlink to a non-existing "real_dir2" */
+               assertMakeSymlink("dest1/dir2", "real_dir2");
+       } else {
+               skipping("some symlink checks");
+       }
+       /* "dir3" is a symlink to an existing "non_dir3" */
+       assertEqualInt(0, mkfile("dest1/non_dir3", 0755, "abcdef", 6));
+       if (canSymlink())
+               assertMakeSymlink("dest1/dir3", "non_dir3");
+       /* "file" is a symlink to existing "real_file" */
+       assertEqualInt(0, mkfile("dest1/real_file", 0755, "abcdefg", 7));
+       if (canSymlink()) {
+               assertMakeSymlink("dest1/file", "real_file");
+               /* "file2" is a symlink to non-existing "real_file2" */
+               assertMakeSymlink("dest1/file2", "real_file2");
+       }
+       assertEqualInt(0, systemf("%s -xf test.tar -C dest1", testprog));
+
+       /* dest1/dir symlink should be replaced */
+       failure("symlink to dir was followed when it shouldn't be");
+       assertIsDir("dest1/dir", -1);
+       /* dest1/dir2 symlink should be replaced */
+       failure("Broken symlink wasn't replaced with dir");
+       assertIsDir("dest1/dir2", -1);
+       /* dest1/dir3 symlink should be replaced */
+       failure("Symlink to non-dir wasn't replaced with dir");
+       assertIsDir("dest1/dir3", -1);
+       /* dest1/file symlink should be replaced */
+       failure("Symlink to existing file should be replaced");
+       assertIsReg("dest1/file", -1);
+       /* dest1/file2 symlink should be replaced */
+       failure("Symlink to non-existing file should be replaced");
+       assertIsReg("dest1/file2", -1);
+
+       /*
+        * Extract with both -x and -P
+        */
+       assertMakeDir("dest2", 0755);
+       /* "dir" is a symlink to existing "real_dir" */
+       assertMakeDir("dest2/real_dir", 0755);
+       if (canSymlink())
+               assertMakeSymlink("dest2/dir", "real_dir");
+       /* "dir2" is a symlink to a non-existing "real_dir2" */
+       if (canSymlink())
+               assertMakeSymlink("dest2/dir2", "real_dir2");
+       /* "dir3" is a symlink to an existing "non_dir3" */
+       assertEqualInt(0, mkfile("dest2/non_dir3", 0755, "abcdefgh", 8));
+       if (canSymlink())
+               assertMakeSymlink("dest2/dir3", "non_dir3");
+       /* "file" is a symlink to existing "real_file" */
+       assertEqualInt(0, mkfile("dest2/real_file", 0755, "abcdefghi", 9));
+       if (canSymlink())
+               assertMakeSymlink("dest2/file", "real_file");
+       /* "file2" is a symlink to non-existing "real_file2" */
+       if (canSymlink())
+               assertMakeSymlink("dest2/file2", "real_file2");
+       assertEqualInt(0, systemf("%s -xPf test.tar -C dest2", testprog));
+
+       /* dest2/dir symlink should be followed */
+       if (canSymlink()) {
+               assertIsSymlink("dest2/dir", "real_dir");
+               assertIsDir("dest2/real_dir", -1);
+       }
+
+       /* Contents of 'dir' should be restored */
+       assertIsDir("dest2/dir/d", -1);
+       assertIsReg("dest2/dir/f", -1);
+       assertFileSize("dest2/dir/f", 3);
+       /* dest2/dir2 symlink should be removed */
+       failure("Broken symlink wasn't replaced with dir");
+       assertIsDir("dest2/dir2", -1);
+       /* dest2/dir3 symlink should be removed */
+       failure("Symlink to non-dir wasn't replaced with dir");
+       assertIsDir("dest2/dir3", -1);
+       /* dest2/file symlink should be removed;
+        * even -P shouldn't follow symlinks for files */
+       failure("Symlink to existing file should be removed");
+       assertIsReg("dest2/file", -1);
+       /* dest2/file2 symlink should be removed */
+       failure("Symlink to non-existing file should be removed");
+       assertIsReg("dest2/file2", -1);
+}
diff --git a/commands/bsdtar/test/test_version.c b/commands/bsdtar/test/test_version.c
new file mode 100644 (file)
index 0000000..42472d1
--- /dev/null
@@ -0,0 +1,97 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+__FBSDID("$FreeBSD: src/usr.bin/tar/test/test_version.c,v 1.2 2008/05/26 17:10:10 kientzle Exp $");
+
+/*
+ * Test that --version option works and generates reasonable output.
+ */
+
+DEFINE_TEST(test_version)
+{
+       int r;
+       char *p, *q;
+       size_t s;
+
+
+       r = systemf("%s --version >version.stdout 2>version.stderr", testprog);
+       if (r != 0)
+               r = systemf("%s -W version >version.stdout 2>version.stderr",
+                   testprog);
+       failure("Unable to run either %s --version or %s -W version",
+           testprog, testprog);
+       if (!assert(r == 0))
+               return;
+
+       /* --version should generate nothing to stdout. */
+       assertEmptyFile("version.stderr");
+       /* Verify format of version message. */
+       q = p = slurpfile(&s, "version.stdout");
+       /* Version message should start with name of program, then space. */
+       assert(s > 6);
+       failure("Version must start with 'bsdtar': ``%s''", p);
+       if (!assertEqualMem(q, "bsdtar ", 7))
+               return;
+       q += 7; s -= 7;
+       /* Version number is a series of digits and periods. */
+       while (s > 0 && (*q == '.' || (*q >= '0' && *q <= '9'))) {
+               ++q;
+               --s;
+       }
+       /* Version number terminated by space. */
+       failure("No space after bsdtar version: ``%s''", p);
+       assert(s > 1);
+       /* Skip a single trailing a,b,c, or d. */
+       if (*q == 'a' || *q == 'b' || *q == 'c' || *q == 'd')
+               ++q;
+       failure("No space after bsdtar version: ``%s''", p);
+       assert(*q == ' ');
+       ++q; --s;
+       /* Separator. */
+       failure("No `-' between bsdtar and libarchive versions: ``%s''", p);
+       assertEqualMem(q, "- ", 2);
+       q += 2; s -= 2;
+       /* libarchive name and version number */
+       failure("Not long enough for libarchive version: ``%s''", p);
+       assert(s > 11);
+       failure("Libarchive version must start with `libarchive': ``%s''", p);
+       assertEqualMem(q, "libarchive ", 11);
+       q += 11; s -= 11;
+       /* Version number is a series of digits and periods. */
+       while (s > 0 && (*q == '.' || (*q >= '0' && *q <= '9'))) {
+               ++q;
+               --s;
+       }
+       /* Skip a single trailing a,b,c, or d. */
+       if (*q == 'a' || *q == 'b' || *q == 'c' || *q == 'd')
+               ++q;
+       /* All terminated by end-of-line. */
+       assert(s >= 1);
+       /* Skip an optional CR character (e.g., Windows) */
+       failure("Version output must end with \\n or \\r\\n");
+       if (*q == '\r') { ++q; --s; }
+       assertEqualMem(q, "\n", 1);
+       free(p);
+}
diff --git a/commands/bsdtar/test/test_windows.c b/commands/bsdtar/test/test_windows.c
new file mode 100644 (file)
index 0000000..a2d0c21
--- /dev/null
@@ -0,0 +1,323 @@
+/*-
+ * Copyright (c) 2009 Michihiro NAKAJIMA
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+#include <windows.h>
+
+static void
+mkfile(const char *name)
+{
+       FILE *f;
+
+       f = fopen(name, "wb");
+       assert(f != NULL);
+       assertEqualInt(5, fwrite("01234", 1, 5, f));
+       fclose(f);
+}
+
+static void
+mkfullpath(char **path1, char **path2, const char *tpath, int type)
+{
+       char *fp1 = NULL, *fp2 = NULL, *p1 = NULL, *p2 = NULL;
+       size_t l;
+
+       /*
+        * Get full path name of "tpath"
+        */
+       l = GetFullPathNameA(tpath, 0, NULL, NULL);
+       assert(0 != l);
+       fp1 = malloc(l);
+       assert(NULL != fp1);
+       fp2 = malloc(l*2);
+       assert(NULL != fp2);
+       l = GetFullPathNameA(tpath, l, fp1, NULL);
+       if ((type & 0x01) == 0) {
+               for (p1 = fp1; *p1 != '\0'; p1++)
+                       if (*p1 == '\\')
+                               *p1 = '/';
+       }
+
+       switch(type) {
+       case 0: /* start with "/" */
+       case 1: /* start with "\" */
+               /* strip "c:" */
+               memmove(fp1, fp1 + 2, l - 2);
+               fp1[l -2] = '\0';
+               p1 = fp1 + 1;
+               break;
+       case 2: /* start with "c:/" */
+       case 3: /* start with "c:\" */
+               p1 = fp1 + 3;
+               break;
+       case 4: /* start with "//./c:/" */
+       case 5: /* start with "\\.\c:\" */
+       case 6: /* start with "//?/c:/" */
+       case 7: /* start with "\\?\c:\" */
+               p1 = malloc(l + 4 + 1);
+               assert(NULL != p1);
+               if (type & 0x1)
+                       memcpy(p1, "\\\\.\\", 4);
+               else
+                       memcpy(p1, "//./", 4);
+               if (type == 6 || type == 7)
+                       p1[2] = '?';
+               memcpy(p1 + 4, fp1, l);
+               p1[l + 4] = '\0';
+               free(fp1);
+               fp1 = p1;
+               p1 = fp1 + 7;
+               break;
+       }
+
+       /*
+        * Strip leading drive names and converting "\" to "\\"
+        */
+       p2 = fp2;
+       while (*p1 != '\0') {
+               if (*p1 == '\\')
+                       *p2 = '/';
+               else
+                       *p2 = *p1;
+               ++p1;
+               ++p2;
+       }
+       *p2++ = '\r';
+       *p2++ = '\n';
+       *p2 = '\0';
+
+       *path1 = fp1;
+       *path2 = fp2;
+}
+
+static const char *list1[] = {"aaa/", "aaa/file1", "aaa/xxa/", "aaa/xxb/",
+       "aaa/zzc/", "aaa/zzc/file1", "aaa/xxb/file1", "aaa/xxa/file1",
+       "aab/", "aac/", "abb/", "abc/", "abd/", NULL};
+static const char *list2[] = {"bbb/", "bbb/file1", "bbb/xxa/", "bbb/xxb/",
+       "bbb/zzc/", "bbb/zzc/file1", "bbb/xxb/file1", "bbb/xxa/file1", "bbc/",
+       "bbd/", "bcc/", "bcd/", "bce/", NULL};
+static const char *list3[] = {"aac/", "abc/", "bbc/", "bcc/", "ccc/", NULL};
+static const char *list4[] = {"fff/abca", "fff/acca", NULL};
+static const char *list5[] = {"aaa/file1", "aaa/xxa/", "aaa/xxa/file1",
+       "aaa/xxb/", "aaa/xxb/file1", "aaa/zzc/", "aaa/zzc/file1", NULL};
+static const char *list6[] = {"fff/abca", "fff/acca", "aaa/xxa/",
+       "aaa/xxa/file1", "aaa/xxb/", "aaa/xxb/file1", NULL};
+#endif /* _WIN32 && !__CYGWIN__ */
+
+DEFINE_TEST(test_windows)
+{
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       char *fp1, *fp2;
+
+       /*
+        * Preparre tests.
+        * Create directories and files.
+        */
+       assertMakeDir("tmp", 0775);
+       assertChdir("tmp");
+
+       assertMakeDir("aaa", 0775);
+       assertMakeDir("aaa/xxa", 0775);
+       assertMakeDir("aaa/xxb", 0775);
+       assertMakeDir("aaa/zzc", 0775);
+       mkfile("aaa/file1");
+       mkfile("aaa/xxa/file1");
+       mkfile("aaa/xxb/file1");
+       mkfile("aaa/zzc/file1");
+       assertMakeDir("aab", 0775);
+       assertMakeDir("aac", 0775);
+       assertMakeDir("abb", 0775);
+       assertMakeDir("abc", 0775);
+       assertMakeDir("abd", 0775);
+       assertMakeDir("bbb", 0775);
+       assertMakeDir("bbb/xxa", 0775);
+       assertMakeDir("bbb/xxb", 0775);
+       assertMakeDir("bbb/zzc", 0775);
+       mkfile("bbb/file1");
+       mkfile("bbb/xxa/file1");
+       mkfile("bbb/xxb/file1");
+       mkfile("bbb/zzc/file1");
+       assertMakeDir("bbc", 0775);
+       assertMakeDir("bbd", 0775);
+       assertMakeDir("bcc", 0775);
+       assertMakeDir("bcd", 0775);
+       assertEqualInt(0, _mkdir("bce"));
+       assertEqualInt(0, _mkdir("ccc"));
+       assertEqualInt(0, _mkdir("fff"));
+       mkfile("fff/aaaa");
+       mkfile("fff/abba");
+       mkfile("fff/abca");
+       mkfile("fff/acba");
+       mkfile("fff/acca");
+
+       /*
+        * Test1: Command line pattern matching.
+        */
+       assertEqualInt(0,
+           systemf("%s -cf ../archive1.tar a*", testprog));
+       assertEqualInt(0,
+           systemf("%s -tf ../archive1.tar > ../list1", testprog));
+       assertFileContainsLinesAnyOrder("../list1", list1);
+
+       assertEqualInt(0,
+           systemf("%s -cf ../archive2.tar b*", testprog));
+       assertEqualInt(0,
+           systemf("%s -tf ../archive2.tar > ../list2", testprog));
+       assertFileContainsLinesAnyOrder("../list2", list2);
+
+       assertEqualInt(0,
+           systemf("%s -cf ../archive3.tar ??c", testprog));
+       assertEqualInt(0,
+           systemf("%s -tf ../archive3.tar > ../list3", testprog));
+       assertFileContainsLinesAnyOrder("../list3", list3);
+
+       assertEqualInt(0,
+           systemf("%s -cf ../archive3b.tar *c", testprog));
+       assertEqualInt(0,
+           systemf("%s -tf ../archive3b.tar > ../list3b", testprog));
+       assertFileContainsLinesAnyOrder("../list3b", list3);
+
+       assertEqualInt(0,
+           systemf("%s -cf ../archive4.tar fff/a?ca", testprog));
+       assertEqualInt(0,
+           systemf("%s -tf ../archive4.tar > ../list4", testprog));
+       assertFileContainsLinesAnyOrder("../list4", list4);
+
+       assertEqualInt(0,
+           systemf("%s -cf ../archive5.tar aaa\\*", testprog));
+       assertEqualInt(0,
+           systemf("%s -tf ../archive5.tar > ../list5", testprog));
+       assertFileContainsLinesAnyOrder("../list5", list5);
+
+       assertEqualInt(0,
+           systemf("%s -cf ../archive6.tar fff\\a?ca aaa\\xx*", testprog));
+       assertEqualInt(0,
+           systemf("%s -tf ../archive6.tar > ../list6", testprog));
+       assertFileContainsLinesAnyOrder("../list6", list6);
+
+       /*
+        * Test2: Archive the file start with drive letters.
+        */
+       /* Test2a: start with "/" */
+       mkfullpath(&fp1, &fp2, "aaa/file1", 0);
+       assertEqualInt(0,
+           systemf("%s -cf ../archive10.tar %s > ../out10 2> ../err10",
+               testprog, fp1));
+       assertEqualInt(0,
+           systemf("%s -tf ../archive10.tar > ../list10", testprog));
+       /* Check drive letters have been stripped. */
+       assertFileContents(fp2, strlen(fp2), "../list10");
+       free(fp1);
+       free(fp2);
+
+       /* Test2b: start with "\" */
+       mkfullpath(&fp1, &fp2, "aaa/file1", 1);
+       assertEqualInt(0,
+           systemf("%s -cf ../archive11.tar %s > ../out11 2> ../err11",
+               testprog, fp1));
+       assertEqualInt(0,
+           systemf("%s -tf ../archive11.tar > ../list11", testprog));
+       /* Check drive letters have been stripped. */
+       assertFileContents(fp2, strlen(fp2), "../list11");
+       free(fp1);
+       free(fp2);
+
+       /* Test2c: start with "c:/" */
+       mkfullpath(&fp1, &fp2, "aaa/file1", 2);
+       assertEqualInt(0,
+           systemf("%s -cf ../archive12.tar %s > ../out12 2> ../err12",
+               testprog, fp1));
+       assertEqualInt(0,
+           systemf("%s -tf ../archive12.tar > ../list12", testprog));
+       /* Check drive letters have been stripped. */
+       assertFileContents(fp2, strlen(fp2), "../list12");
+       free(fp1);
+       free(fp2);
+
+       /* Test2d: start with "c:\" */
+       mkfullpath(&fp1, &fp2, "aaa/file1", 3);
+       assertEqualInt(0,
+           systemf("%s -cf ../archive13.tar %s > ../out13 2> ../err13",
+               testprog, fp1));
+       assertEqualInt(0,
+           systemf("%s -tf ../archive13.tar > ../list13", testprog));
+       /* Check drive letters have been stripped. */
+       assertFileContents(fp2, strlen(fp2), "../list13");
+       free(fp1);
+       free(fp2);
+
+       /* Test2e: start with "//./c:/" */
+       mkfullpath(&fp1, &fp2, "aaa/file1", 4);
+       assertEqualInt(0,
+           systemf("%s -cf ../archive14.tar %s > ../out14 2> ../err14",
+               testprog, fp1));
+       assertEqualInt(0,
+           systemf("%s -tf ../archive14.tar > ../list14", testprog));
+       /* Check drive letters have been stripped. */
+       assertFileContents(fp2, strlen(fp2), "../list14");
+       free(fp1);
+       free(fp2);
+
+       /* Test2f: start with "\\.\c:\" */
+       mkfullpath(&fp1, &fp2, "aaa/file1", 5);
+       assertEqualInt(0,
+           systemf("%s -cf ../archive15.tar %s > ../out15 2> ../err15",
+               testprog, fp1));
+       assertEqualInt(0,
+           systemf("%s -tf ../archive15.tar > ../list15", testprog));
+       /* Check drive letters have been stripped. */
+       assertFileContents(fp2, strlen(fp2), "../list15");
+       free(fp1);
+       free(fp2);
+
+       /* Test2g: start with "//?/c:/" */
+       mkfullpath(&fp1, &fp2, "aaa/file1", 6);
+       failure("fp1=%s, fp2=%s", fp1, fp2);
+       assertEqualInt(0,
+           systemf("%s -cf ../archive16.tar %s > ../out16 2> ../err16",
+               testprog, fp1));
+       assertEqualInt(0,
+           systemf("%s -tf ../archive16.tar > ../list16", testprog));
+       /* Check drive letters have been stripped. */
+       assertFileContents(fp2, strlen(fp2), "../list16");
+       free(fp1);
+       free(fp2);
+
+       /* Test2h: start with "\\?\c:\" */
+       mkfullpath(&fp1, &fp2, "aaa/file1", 7);
+       failure("fp1=%s, fp2=%s", fp1, fp2);
+       assertEqualInt(0,
+           systemf("%s -cf ../archive17.tar %s > ../out17 2> ../err17",
+               testprog, fp1));
+       assertEqualInt(0,
+           systemf("%s -tf ../archive17.tar > ../list17", testprog));
+       /* Check drive letters have been stripped. */
+       assertFileContents(fp2, strlen(fp2), "../list17");
+       free(fp1);
+       free(fp2);
+#else
+       skipping("Windows specific test");
+#endif /* _WIN32 && !__CYGWIN__ */
+}
diff --git a/commands/bsdtar/tree.c b/commands/bsdtar/tree.c
new file mode 100644 (file)
index 0000000..5773c24
--- /dev/null
@@ -0,0 +1,821 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * This is a new directory-walking system that addresses a number
+ * of problems I've had with fts(3).  In particular, it has no
+ * pathname-length limits (other than the size of 'int'), handles
+ * deep logical traversals, uses considerably less memory, and has
+ * an opaque interface (easier to modify in the future).
+ *
+ * Internally, it keeps a single list of "tree_entry" items that
+ * represent filesystem objects that require further attention.
+ * Non-directories are not kept in memory: they are pulled from
+ * readdir(), returned to the client, then freed as soon as possible.
+ * Any directory entry to be traversed gets pushed onto the stack.
+ *
+ * There is surprisingly little information that needs to be kept for
+ * each item on the stack.  Just the name, depth (represented here as the
+ * string length of the parent directory's pathname), and some markers
+ * indicating how to get back to the parent (via chdir("..") for a
+ * regular dir or via fchdir(2) for a symlink).
+ */
+#include "bsdtar_platform.h"
+__FBSDID("$FreeBSD: src/usr.bin/tar/tree.c,v 1.9 2008/11/27 05:49:52 kientzle Exp $");
+
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_DIRECT_H
+#include <direct.h>
+#endif
+#ifdef HAVE_DIRENT_H
+#include <dirent.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if defined(HAVE_WINDOWS_H) && !defined(__CYGWIN__)
+#include <windows.h>
+#endif
+
+#include "tree.h"
+
+/*
+ * TODO:
+ *    1) Loop checking.
+ *    3) Arbitrary logical traversals by closing/reopening intermediate fds.
+ */
+
+struct tree_entry {
+       int depth;
+       struct tree_entry *next;
+       struct tree_entry *parent;
+       char *name;
+       size_t dirname_length;
+       dev_t dev;
+       ino_t ino;
+       int flags;
+       /* How to return back to the parent of a symlink. */
+#ifdef HAVE_FCHDIR
+       int symlink_parent_fd;
+#elif defined(_WIN32) && !defined(__CYGWIN__)
+       char *symlink_parent_path;
+#else
+#error fchdir function required.
+#endif
+};
+
+/* Definitions for tree_entry.flags bitmap. */
+#define        isDir 1 /* This entry is a regular directory. */
+#define        isDirLink 2 /* This entry is a symbolic link to a directory. */
+#define needsFirstVisit 4 /* This is an initial entry. */
+#define        needsDescent 8 /* This entry needs to be previsited. */
+#define needsOpen 16 /* This is a directory that needs to be opened. */
+#define        needsAscent 32 /* This entry needs to be postvisited. */
+
+/*
+ * On Windows, "first visit" is handled as a pattern to be handed to
+ * _findfirst().  This is consistent with Windows conventions that
+ * file patterns are handled within the application.  On Posix,
+ * "first visit" is just returned to the client.
+ */
+
+/*
+ * Local data for this package.
+ */
+struct tree {
+       struct tree_entry       *stack;
+       struct tree_entry       *current;
+#if defined(HAVE_WINDOWS_H) && !defined(__CYGWIN__)
+       HANDLE d;
+       BY_HANDLE_FILE_INFORMATION fileInfo;
+#define INVALID_DIR_HANDLE INVALID_HANDLE_VALUE
+       WIN32_FIND_DATA _findData;
+       WIN32_FIND_DATA *findData;
+#else
+       DIR     *d;
+#define INVALID_DIR_HANDLE NULL
+       struct dirent *de;
+#endif
+       int      flags;
+       int      visit_type;
+       int      tree_errno; /* Error code from last failed operation. */
+
+       /* Dynamically-sized buffer for holding path */
+       char    *buff;
+       size_t   buff_length;
+
+       const char *basename; /* Last path element */
+       size_t   dirname_length; /* Leading dir length */
+       size_t   path_length; /* Total path length */
+
+       int      depth;
+       int      openCount;
+       int      maxOpenCount;
+
+       struct stat     lst;
+       struct stat     st;
+};
+
+/* Definitions for tree.flags bitmap. */
+#define hasStat 16  /* The st entry is valid. */
+#define hasLstat 32 /* The lst entry is valid. */
+#define        hasFileInfo 64 /* The Windows fileInfo entry is valid. */
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+static int
+tree_dir_next_windows(struct tree *t, const char *pattern);
+#else
+static int
+tree_dir_next_posix(struct tree *t);
+#endif
+
+#ifdef HAVE_DIRENT_D_NAMLEN
+/* BSD extension; avoids need for a strlen() call. */
+#define D_NAMELEN(dp)  (dp)->d_namlen
+#else
+#define D_NAMELEN(dp)  (strlen((dp)->d_name))
+#endif
+
+#include <stdio.h>
+void
+tree_dump(struct tree *t, FILE *out)
+{
+       char buff[300];
+       struct tree_entry *te;
+
+       fprintf(out, "\tdepth: %d\n", t->depth);
+       fprintf(out, "\tbuff: %s\n", t->buff);
+       fprintf(out, "\tpwd: %s\n", getcwd(buff, sizeof(buff)));
+       fprintf(out, "\tbasename: %s\n", t->basename);
+       fprintf(out, "\tstack:\n");
+       for (te = t->stack; te != NULL; te = te->next) {
+               fprintf(out, "\t\t%s%d:\"%s\" %s%s%s%s%s%s\n",
+                   t->current == te ? "*" : " ",
+                   te->depth,
+                   te->name,
+                   te->flags & needsFirstVisit ? "V" : "",
+                   te->flags & needsDescent ? "D" : "",
+                   te->flags & needsOpen ? "O" : "",
+                   te->flags & needsAscent ? "A" : "",
+                   te->flags & isDirLink ? "L" : "",
+                   (t->current == te && t->d) ? "+" : ""
+               );
+       }
+}
+
+/*
+ * Add a directory path to the current stack.
+ */
+static void
+tree_push(struct tree *t, const char *path)
+{
+       struct tree_entry *te;
+
+       te = malloc(sizeof(*te));
+       memset(te, 0, sizeof(*te));
+       te->next = t->stack;
+       te->parent = t->current;
+       if (te->parent)
+               te->depth = te->parent->depth + 1;
+       t->stack = te;
+#ifdef HAVE_FCHDIR
+       te->symlink_parent_fd = -1;
+       te->name = strdup(path);
+#elif defined(_WIN32) && !defined(__CYGWIN__)
+       te->symlink_parent_path = NULL;
+       te->name = strdup(path);
+#endif
+       te->flags = needsDescent | needsOpen | needsAscent;
+       te->dirname_length = t->dirname_length;
+}
+
+/*
+ * Append a name to the current dir path.
+ */
+static void
+tree_append(struct tree *t, const char *name, size_t name_length)
+{
+       char *p;
+       size_t size_needed;
+
+       if (t->buff != NULL)
+               t->buff[t->dirname_length] = '\0';
+       /* Strip trailing '/' from name, unless entire name is "/". */
+       while (name_length > 1 && name[name_length - 1] == '/')
+               name_length--;
+
+       /* Resize pathname buffer as needed. */
+       size_needed = name_length + 1 + t->dirname_length;
+       if (t->buff_length < size_needed) {
+               if (t->buff_length < 1024)
+                       t->buff_length = 1024;
+               while (t->buff_length < size_needed)
+                       t->buff_length *= 2;
+               t->buff = realloc(t->buff, t->buff_length);
+       }
+       if (t->buff == NULL)
+               abort();
+       p = t->buff + t->dirname_length;
+       t->path_length = t->dirname_length + name_length;
+       /* Add a separating '/' if it's needed. */
+       if (t->dirname_length > 0 && p[-1] != '/') {
+               *p++ = '/';
+               t->path_length ++;
+       }
+#if HAVE_STRNCPY_S
+       strncpy_s(p, t->buff_length - (p - t->buff), name, name_length);
+#else
+       strncpy(p, name, name_length);
+#endif
+       p[name_length] = '\0';
+       t->basename = p;
+}
+
+/*
+ * Open a directory tree for traversal.
+ */
+struct tree *
+tree_open(const char *path)
+{
+#ifdef HAVE_FCHDIR
+       struct tree *t;
+
+       t = malloc(sizeof(*t));
+       memset(t, 0, sizeof(*t));
+       /* First item is set up a lot like a symlink traversal. */
+       tree_push(t, path);
+       t->stack->flags = needsFirstVisit | isDirLink | needsAscent;
+       t->stack->symlink_parent_fd = open(".", O_RDONLY);
+       t->openCount++;
+       t->d = INVALID_DIR_HANDLE;
+       return (t);
+#elif defined(_WIN32) && !defined(__CYGWIN__)
+       struct tree *t;
+       char *cwd = _getcwd(NULL, 0);
+       char *pathname = strdup(path), *p, *base;
+
+       if (pathname == NULL)
+               abort();
+       for (p = pathname; *p != '\0'; ++p) {
+               if (*p == '\\')
+                       *p = '/';
+       }
+       base = pathname;
+
+       t = malloc(sizeof(*t));
+       memset(t, 0, sizeof(*t));
+       /* First item is set up a lot like a symlink traversal. */
+       /* printf("Looking for wildcard in %s\n", path); */
+       /* TODO: wildcard detection here screws up on \\?\c:\ UNC names */
+       if (strchr(base, '*') || strchr(base, '?')) {
+               // It has a wildcard in it...
+               // Separate the last element.
+               p = strrchr(base, '/');
+               if (p != NULL) {
+                       *p = '\0';
+                       chdir(base);
+                       tree_append(t, base, p - base);
+                       t->dirname_length = t->path_length;
+                       base = p + 1;
+               }
+       }
+       tree_push(t, base);
+       free(pathname);
+       t->stack->flags = needsFirstVisit | isDirLink | needsAscent;
+       t->stack->symlink_parent_path = cwd;
+       t->d = INVALID_DIR_HANDLE;
+       return (t);
+#endif
+}
+
+/*
+ * We've finished a directory; ascend back to the parent.
+ */
+static int
+tree_ascend(struct tree *t)
+{
+       struct tree_entry *te;
+       int r = 0;
+
+       te = t->stack;
+       t->depth--;
+       if (te->flags & isDirLink) {
+#ifdef HAVE_FCHDIR
+               if (fchdir(te->symlink_parent_fd) != 0) {
+                       t->tree_errno = errno;
+                       r = TREE_ERROR_FATAL;
+               }
+               close(te->symlink_parent_fd);
+#elif defined(_WIN32) && !defined(__CYGWIN__)
+               if (SetCurrentDirectory(te->symlink_parent_path) == 0) {
+                       t->tree_errno = errno;
+                       r = TREE_ERROR_FATAL;
+               }
+               free(te->symlink_parent_path);
+               te->symlink_parent_path = NULL;
+#endif
+               t->openCount--;
+       } else {
+#if defined(_WIN32) && !defined(__CYGWIN__)
+               if (SetCurrentDirectory("..") == 0) {
+#else
+               if (chdir("..") != 0) {
+#endif
+                       t->tree_errno = errno;
+                       r = TREE_ERROR_FATAL;
+               }
+       }
+       return (r);
+}
+
+/*
+ * Pop the working stack.
+ */
+static void
+tree_pop(struct tree *t)
+{
+       struct tree_entry *te;
+
+       if (t->buff)
+               t->buff[t->dirname_length] = '\0';
+       if (t->stack == t->current && t->current != NULL)
+               t->current = t->current->parent;
+       te = t->stack;
+       t->stack = te->next;
+       t->dirname_length = te->dirname_length;
+       if (t->buff) {
+               t->basename = t->buff + t->dirname_length;
+               while (t->basename[0] == '/')
+                       t->basename++;
+       }
+       free(te->name);
+       free(te);
+}
+
+/*
+ * Get the next item in the tree traversal.
+ */
+int
+tree_next(struct tree *t)
+{
+       int r;
+
+       /* If we're called again after a fatal error, that's an API
+        * violation.  Just crash now. */
+       if (t->visit_type == TREE_ERROR_FATAL) {
+               fprintf(stderr, "Unable to continue traversing"
+                   " directory heirarchy after a fatal error.");
+               abort();
+       }
+
+       while (t->stack != NULL) {
+               /* If there's an open dir, get the next entry from there. */
+               if (t->d != INVALID_DIR_HANDLE) {
+#if defined(_WIN32) && !defined(__CYGWIN__)
+                       r = tree_dir_next_windows(t, NULL);
+#else
+                       r = tree_dir_next_posix(t);
+#endif
+                       if (r == 0)
+                               continue;
+                       return (r);
+               }
+
+               if (t->stack->flags & needsFirstVisit) {
+#if defined(_WIN32) && !defined(__CYGWIN__)
+                       char *d = t->stack->name;
+                       t->stack->flags &= ~needsFirstVisit;
+                       if (strchr(d, '*') || strchr(d, '?')) {
+                               r = tree_dir_next_windows(t, d);
+                               if (r == 0)
+                                       continue;
+                               return (r);
+                       }
+                       // Not a pattern, handle it as-is...
+#endif
+                       /* Top stack item needs a regular visit. */
+                       t->current = t->stack;
+                       tree_append(t, t->stack->name, strlen(t->stack->name));
+                       /* t->dirname_length = t->path_length; */
+                       /* tree_pop(t); */
+                       t->stack->flags &= ~needsFirstVisit;
+                       return (t->visit_type = TREE_REGULAR);
+               } else if (t->stack->flags & needsDescent) {
+                       /* Top stack item is dir to descend into. */
+                       t->current = t->stack;
+                       tree_append(t, t->stack->name, strlen(t->stack->name));
+                       t->stack->flags &= ~needsDescent;
+                       /* If it is a link, set up fd for the ascent. */
+                       if (t->stack->flags & isDirLink) {
+#ifdef HAVE_FCHDIR
+                               t->stack->symlink_parent_fd = open(".", O_RDONLY);
+                               t->openCount++;
+                               if (t->openCount > t->maxOpenCount)
+                                       t->maxOpenCount = t->openCount;
+#elif defined(_WIN32) && !defined(__CYGWIN__)
+                               t->stack->symlink_parent_path = _getcwd(NULL, 0);
+#endif
+                       }
+                       t->dirname_length = t->path_length;
+#if defined(_WIN32) && !defined(__CYGWIN__)
+                       if (t->path_length == 259 || !SetCurrentDirectory(t->stack->name) != 0)
+#else
+                       if (chdir(t->stack->name) != 0)
+#endif
+                       {
+                               /* chdir() failed; return error */
+                               tree_pop(t);
+                               t->tree_errno = errno;
+                               return (t->visit_type = TREE_ERROR_DIR);
+                       }
+                       t->depth++;
+                       return (t->visit_type = TREE_POSTDESCENT);
+               } else if (t->stack->flags & needsOpen) {
+                       t->stack->flags &= ~needsOpen;
+#if defined(_WIN32) && !defined(__CYGWIN__)
+                       r = tree_dir_next_windows(t, "*");
+#else
+                       r = tree_dir_next_posix(t);
+#endif
+                       if (r == 0)
+                               continue;
+                       return (r);
+               } else if (t->stack->flags & needsAscent) {
+                       /* Top stack item is dir and we're done with it. */
+                       r = tree_ascend(t);
+                       tree_pop(t);
+                       t->visit_type = r != 0 ? r : TREE_POSTASCENT;
+                       return (t->visit_type);
+               } else {
+                       /* Top item on stack is dead. */
+                       tree_pop(t);
+                       t->flags &= ~hasLstat;
+                       t->flags &= ~hasStat;
+               }
+       }
+       return (t->visit_type = 0);
+}
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+static int
+tree_dir_next_windows(struct tree *t, const char *pattern)
+{
+       const char *name;
+       size_t namelen;
+       int r;
+
+       for (;;) {
+               if (pattern != NULL) {
+                       t->d = FindFirstFile(pattern, &t->_findData);
+                       if (t->d == INVALID_DIR_HANDLE) {
+                               r = tree_ascend(t); /* Undo "chdir" */
+                               tree_pop(t);
+                               t->tree_errno = errno;
+                               t->visit_type = r != 0 ? r : TREE_ERROR_DIR;
+                               return (t->visit_type);
+                       }
+                       t->findData = &t->_findData;
+                       pattern = NULL;
+               } else if (!FindNextFile(t->d, &t->_findData)) {
+                       FindClose(t->d);
+                       t->d = INVALID_DIR_HANDLE;
+                       t->findData = NULL;
+                       return (0);
+               }
+               name = t->findData->cFileName;
+               namelen = strlen(name);
+               t->flags &= ~hasLstat;
+               t->flags &= ~hasStat;
+               if (name[0] == '.' && name[1] == '\0')
+                       continue;
+               if (name[0] == '.' && name[1] == '.' && name[2] == '\0')
+                       continue;
+               tree_append(t, name, namelen);
+               return (t->visit_type = TREE_REGULAR);
+       }
+}
+#else
+static int
+tree_dir_next_posix(struct tree *t)
+{
+       int r;
+       const char *name;
+       size_t namelen;
+
+       if (t->d == NULL) {
+               if ((t->d = opendir(".")) == NULL) {
+                       r = tree_ascend(t); /* Undo "chdir" */
+                       tree_pop(t);
+                       t->tree_errno = errno;
+                       t->visit_type = r != 0 ? r : TREE_ERROR_DIR;
+                       return (t->visit_type);
+               }
+       }
+       for (;;) {
+               t->de = readdir(t->d);
+               if (t->de == NULL) {
+                       closedir(t->d);
+                       t->d = INVALID_DIR_HANDLE;
+                       return (0);
+               }
+               name = t->de->d_name;
+               namelen = D_NAMELEN(t->de);
+               t->flags &= ~hasLstat;
+               t->flags &= ~hasStat;
+               if (name[0] == '.' && name[1] == '\0')
+                       continue;
+               if (name[0] == '.' && name[1] == '.' && name[2] == '\0')
+                       continue;
+               tree_append(t, name, namelen);
+               return (t->visit_type = TREE_REGULAR);
+       }
+}
+#endif
+
+/*
+ * Return error code.
+ */
+int
+tree_errno(struct tree *t)
+{
+       return (t->tree_errno);
+}
+
+/*
+ * Called by the client to mark the directory just returned from
+ * tree_next() as needing to be visited.
+ */
+void
+tree_descend(struct tree *t)
+{
+       if (t->visit_type != TREE_REGULAR)
+               return;
+
+       if (tree_current_is_physical_dir(t)) {
+               tree_push(t, t->basename);
+               t->stack->flags |= isDir;
+       } else if (tree_current_is_dir(t)) {
+               tree_push(t, t->basename);
+               t->stack->flags |= isDirLink;
+       }
+}
+
+/*
+ * Get the stat() data for the entry just returned from tree_next().
+ */
+const struct stat *
+tree_current_stat(struct tree *t)
+{
+       if (!(t->flags & hasStat)) {
+               if (stat(tree_current_access_path(t), &t->st) != 0)
+                       return NULL;
+               t->flags |= hasStat;
+       }
+       return (&t->st);
+}
+
+#if defined(HAVE_WINDOWS_H) && !defined(__CYGWIN__)
+const BY_HANDLE_FILE_INFORMATION *
+tree_current_file_information(struct tree *t)
+{
+       if (!(t->flags & hasFileInfo)) {
+               HANDLE h = CreateFile(tree_current_access_path(t),
+                       0, 0, NULL,
+                       OPEN_EXISTING,
+                       FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
+                       NULL);
+               if (h == INVALID_HANDLE_VALUE)
+                       return NULL;
+               if (!GetFileInformationByHandle(h, &t->fileInfo)) {
+                       CloseHandle(h);
+                       return NULL;
+               }
+               CloseHandle(h);
+               t->flags |= hasFileInfo;
+       }
+       return (&t->fileInfo);
+}
+#endif
+/*
+ * Get the lstat() data for the entry just returned from tree_next().
+ */
+const struct stat *
+tree_current_lstat(struct tree *t)
+{
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       return (tree_current_stat(t));
+#else
+       if (!(t->flags & hasLstat)) {
+               if (lstat(tree_current_access_path(t), &t->lst) != 0)
+                       return NULL;
+               t->flags |= hasLstat;
+       }
+       return (&t->lst);
+#endif
+}
+
+/*
+ * Test whether current entry is a dir or link to a dir.
+ */
+int
+tree_current_is_dir(struct tree *t)
+{
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       if (t->findData)
+               return (t->findData->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
+       if (tree_current_file_information(t))
+               return (t->fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
+       return (0);
+#else
+       const struct stat *st;
+       /*
+        * If we already have lstat() info, then try some
+        * cheap tests to determine if this is a dir.
+        */
+       if (t->flags & hasLstat) {
+               /* If lstat() says it's a dir, it must be a dir. */
+               if (S_ISDIR(tree_current_lstat(t)->st_mode))
+                       return 1;
+               /* Not a dir; might be a link to a dir. */
+               /* If it's not a link, then it's not a link to a dir. */
+               if (!S_ISLNK(tree_current_lstat(t)->st_mode))
+                       return 0;
+               /*
+                * It's a link, but we don't know what it's a link to,
+                * so we'll have to use stat().
+                */
+       }
+
+       st = tree_current_stat(t);
+       /* If we can't stat it, it's not a dir. */
+       if (st == NULL)
+               return 0;
+       /* Use the definitive test.  Hopefully this is cached. */
+       return (S_ISDIR(st->st_mode));
+#endif
+}
+
+/*
+ * Test whether current entry is a physical directory.  Usually, we
+ * already have at least one of stat() or lstat() in memory, so we
+ * use tricks to try to avoid an extra trip to the disk.
+ */
+int
+tree_current_is_physical_dir(struct tree *t)
+{
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       if (tree_current_is_physical_link(t))
+               return (0);
+       return (tree_current_is_dir(t));
+#else
+       const struct stat *st;
+
+       /*
+        * If stat() says it isn't a dir, then it's not a dir.
+        * If stat() data is cached, this check is free, so do it first.
+        */
+       if ((t->flags & hasStat)
+           && (!S_ISDIR(tree_current_stat(t)->st_mode)))
+               return 0;
+
+       /*
+        * Either stat() said it was a dir (in which case, we have
+        * to determine whether it's really a link to a dir) or
+        * stat() info wasn't available.  So we use lstat(), which
+        * hopefully is already cached.
+        */
+
+       st = tree_current_lstat(t);
+       /* If we can't stat it, it's not a dir. */
+       if (st == NULL)
+               return 0;
+       /* Use the definitive test.  Hopefully this is cached. */
+       return (S_ISDIR(st->st_mode));
+#endif
+}
+
+/*
+ * Test whether current entry is a symbolic link.
+ */
+int
+tree_current_is_physical_link(struct tree *t)
+{
+#if defined(_WIN32) && !defined(__CYGWIN__)
+#ifndef IO_REPARSE_TAG_SYMLINK
+/* Old SDKs do not provide IO_REPARSE_TAG_SYMLINK */
+#define IO_REPARSE_TAG_SYMLINK 0xA000000CL
+#endif
+       if (t->findData)
+               return ((t->findData->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
+                               && (t->findData->dwReserved0 == IO_REPARSE_TAG_SYMLINK));
+       return (0);
+#else
+       const struct stat *st = tree_current_lstat(t);
+       if (st == NULL)
+               return 0;
+       return (S_ISLNK(st->st_mode));
+#endif
+}
+
+/*
+ * Return the access path for the entry just returned from tree_next().
+ */
+const char *
+tree_current_access_path(struct tree *t)
+{
+       return (t->basename);
+}
+
+/*
+ * Return the full path for the entry just returned from tree_next().
+ */
+const char *
+tree_current_path(struct tree *t)
+{
+       return (t->buff);
+}
+
+/*
+ * Return the length of the path for the entry just returned from tree_next().
+ */
+size_t
+tree_current_pathlen(struct tree *t)
+{
+       return (t->path_length);
+}
+
+/*
+ * Return the nesting depth of the entry just returned from tree_next().
+ */
+int
+tree_current_depth(struct tree *t)
+{
+       return (t->depth);
+}
+
+/*
+ * Terminate the traversal and release any resources.
+ */
+void
+tree_close(struct tree *t)
+{
+       /* Release anything remaining in the stack. */
+       while (t->stack != NULL)
+               tree_pop(t);
+       free(t->buff);
+       /* TODO: Ensure that premature close() resets cwd */
+#if 0
+#ifdef HAVE_FCHDIR
+       if (t->initialDirFd >= 0) {
+               int s = fchdir(t->initialDirFd);
+               (void)s; /* UNUSED */
+               close(t->initialDirFd);
+               t->initialDirFd = -1;
+       }
+#elif defined(_WIN32) && !defined(__CYGWIN__)
+       if (t->initialDir != NULL) {
+               SetCurrentDir(t->initialDir);
+               free(t->initialDir);
+               t->initialDir = NULL;
+       }
+#endif
+#endif
+       free(t);
+}
diff --git a/commands/bsdtar/tree.h b/commands/bsdtar/tree.h
new file mode 100644 (file)
index 0000000..9a7e0d3
--- /dev/null
@@ -0,0 +1,141 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.bin/tar/tree.h,v 1.4 2008/11/27 05:49:52 kientzle Exp $
+ */
+
+/*-
+ * A set of routines for traversing directory trees.
+ * Similar in concept to the fts library, but with a few
+ * important differences:
+ *    * Uses less memory.  In particular, fts stores an entire directory
+ *      in memory at a time.  This package only keeps enough subdirectory
+ *      information in memory to track the traversal.  Information
+ *      about non-directories is discarded as soon as possible.
+ *    * Supports very deep logical traversals.  The fts package
+ *      uses "non-chdir" approach for logical traversals.  This
+ *      package does use a chdir approach for logical traversals
+ *      and can therefore handle pathnames much longer than PATH_MAX.
+ *    * Supports deep physical traversals "out of the box."
+ *      Due to the memory optimizations above, there's no need to
+ *      limit dir names to 32k.
+ */
+
+#include <sys/stat.h>
+#include <stdio.h>
+
+struct tree;
+
+/* Initiate/terminate a tree traversal. */
+struct tree *tree_open(const char * /* pathname */);
+void tree_close(struct tree *);
+
+/*
+ * tree_next() returns Zero if there is no next entry, non-zero if
+ * there is.  Note that directories are visited three times.
+ * Directories are always visited first as part of enumerating their
+ * parent; that is a "regular" visit.  If tree_descend() is invoked at
+ * that time, the directory is added to a work list and will
+ * subsequently be visited two more times: once just after descending
+ * into the directory ("postdescent") and again just after ascending
+ * back to the parent ("postascent").
+ *
+ * TREE_ERROR_DIR is returned if the descent failed (because the
+ * directory couldn't be opened, for instance).  This is returned
+ * instead of TREE_POSTDESCENT/TREE_POSTASCENT.  TREE_ERROR_DIR is not a
+ * fatal error, but it does imply that the relevant subtree won't be
+ * visited.  TREE_ERROR_FATAL is returned for an error that left the
+ * traversal completely hosed.  Right now, this is only returned for
+ * chdir() failures during ascent.
+ */
+#define        TREE_REGULAR    1
+#define        TREE_POSTDESCENT        2
+#define        TREE_POSTASCENT 3
+#define        TREE_ERROR_DIR  -1
+#define        TREE_ERROR_FATAL -2
+
+int tree_next(struct tree *);
+
+/* Errno value associated with the last traversal error. */
+int tree_errno(struct tree *);
+
+/*
+ * Request that current entry be visited.  If you invoke it on every
+ * directory, you'll get a physical traversal.  This is ignored if the
+ * current entry isn't a directory or a link to a directory.  So, if
+ * you invoke this on every returned path, you'll get a full logical
+ * traversal.
+ */
+void tree_descend(struct tree *);
+
+/*
+ * Return information about the current entry.
+ */
+
+/* Current depth in the traversal. */
+int tree_current_depth(struct tree *);
+
+/*
+ * The current full pathname, length of the full pathname, and a name
+ * that can be used to access the file.  Because tree does use chdir
+ * extensively, the access path is almost never the same as the full
+ * current path.
+ *
+ * TODO: Flesh out this interface to provide other information.  In
+ * particular, Windows can provide file size, mode, and some permission
+ * information without invoking stat() at all.
+ *
+ * TODO: On platforms that support it, use openat()-style operations
+ * to eliminate the chdir() operations entirely while still supporting
+ * arbitrarily deep traversals.  This makes access_path troublesome to
+ * support, of course, which means we'll need a rich enough interface
+ * that clients can function without it.  (In particular, we'll need
+ * tree_current_open() that returns an open file descriptor.)
+ *
+ * TODO: Provide tree_current_archive_entry().
+ */
+const char *tree_current_path(struct tree *);
+size_t tree_current_pathlen(struct tree *);
+const char *tree_current_access_path(struct tree *);
+
+/*
+ * Request the lstat() or stat() data for the current path.  Since the
+ * tree package needs to do some of this anyway, and caches the
+ * results, you should take advantage of it here if you need it rather
+ * than make a redundant stat() or lstat() call of your own.
+ */
+const struct stat *tree_current_stat(struct tree *);
+const struct stat *tree_current_lstat(struct tree *);
+
+/* The following functions use tricks to avoid a certain number of
+ * stat()/lstat() calls. */
+/* "is_physical_dir" is equivalent to S_ISDIR(tree_current_lstat()->st_mode) */
+int tree_current_is_physical_dir(struct tree *);
+/* "is_physical_link" is equivalent to S_ISLNK(tree_current_lstat()->st_mode) */
+int tree_current_is_physical_link(struct tree *);
+/* "is_dir" is equivalent to S_ISDIR(tree_current_stat()->st_mode) */
+int tree_current_is_dir(struct tree *);
+
+/* For testing/debugging: Dump the internal status to the given filehandle. */
+void tree_dump(struct tree *, FILE *);
diff --git a/commands/bsdtar/util.c b/commands/bsdtar/util.c
new file mode 100644 (file)
index 0000000..4219a35
--- /dev/null
@@ -0,0 +1,577 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "bsdtar_platform.h"
+__FBSDID("$FreeBSD: src/usr.bin/tar/util.c,v 1.23 2008/12/15 06:00:25 kientzle Exp $");
+
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>  /* Linux doesn't define mode_t, etc. in sys/stat.h. */
+#endif
+#include <ctype.h>
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_IO_H
+#include <io.h>
+#endif
+#ifdef HAVE_STDARG_H
+#include <stdarg.h>
+#endif
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdio.h>
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#ifdef HAVE_WCTYPE_H 
+#include <wctype.h>
+#else
+/* If we don't have wctype, we need to hack up some version of iswprint(). */
+#define iswprint isprint
+#endif
+
+#include "bsdtar.h"
+#include "err.h"
+
+static size_t  bsdtar_expand_char(char *, size_t, char);
+static const char *strip_components(const char *path, int elements);
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+#define read _read
+#endif
+
+/* TODO:  Hack up a version of mbtowc for platforms with no wide
+ * character support at all.  I think the following might suffice,
+ * but it needs careful testing.
+ * #if !HAVE_MBTOWC
+ * #define mbtowc(wcp, p, n) ((*wcp = *p), 1)
+ * #endif
+ */
+
+/*
+ * Print a string, taking care with any non-printable characters.
+ *
+ * Note that we use a stack-allocated buffer to receive the formatted
+ * string if we can.  This is partly performance (avoiding a call to
+ * malloc()), partly out of expedience (we have to call vsnprintf()
+ * before malloc() anyway to find out how big a buffer we need; we may
+ * as well point that first call at a small local buffer in case it
+ * works), but mostly for safety (so we can use this to print messages
+ * about out-of-memory conditions).
+ */
+
+void
+safe_fprintf(FILE *f, const char *fmt, ...)
+{
+       char fmtbuff_stack[256]; /* Place to format the printf() string. */
+       char outbuff[256]; /* Buffer for outgoing characters. */
+       char *fmtbuff_heap; /* If fmtbuff_stack is too small, we use malloc */
+       char *fmtbuff;  /* Pointer to fmtbuff_stack or fmtbuff_heap. */
+       int fmtbuff_length;
+       int length, n;
+       va_list ap;
+       const char *p;
+       unsigned i;
+       wchar_t wc;
+       char try_wc;
+
+       /* Use a stack-allocated buffer if we can, for speed and safety. */
+       fmtbuff_heap = NULL;
+       fmtbuff_length = sizeof(fmtbuff_stack);
+       fmtbuff = fmtbuff_stack;
+
+       /* Try formatting into the stack buffer. */
+       va_start(ap, fmt);
+       length = vsnprintf(fmtbuff, fmtbuff_length, fmt, ap);
+       va_end(ap);
+
+       /* If the result was too large, allocate a buffer on the heap. */
+       if (length >= fmtbuff_length) {
+               fmtbuff_length = length+1;
+               fmtbuff_heap = malloc(fmtbuff_length);
+
+               /* Reformat the result into the heap buffer if we can. */
+               if (fmtbuff_heap != NULL) {
+                       fmtbuff = fmtbuff_heap;
+                       va_start(ap, fmt);
+                       length = vsnprintf(fmtbuff, fmtbuff_length, fmt, ap);
+                       va_end(ap);
+               } else {
+                       /* Leave fmtbuff pointing to the truncated
+                        * string in fmtbuff_stack. */
+                       length = sizeof(fmtbuff_stack) - 1;
+               }
+       }
+
+       /* Note: mbrtowc() has a cleaner API, but mbtowc() seems a bit
+        * more portable, so we use that here instead. */
+       n = mbtowc(NULL, NULL, 1); /* Reset the shift state. */
+
+       /* Write data, expanding unprintable characters. */
+       p = fmtbuff;
+       i = 0;
+       try_wc = 1;
+       while (*p != '\0') {
+
+               /* Convert to wide char, test if the wide
+                * char is printable in the current locale. */
+               if (try_wc && (n = mbtowc(&wc, p, length)) != -1) {
+                       length -= n;
+                       if (iswprint(wc) && wc != L'\\') {
+                               /* Printable, copy the bytes through. */
+                               while (n-- > 0)
+                                       outbuff[i++] = *p++;
+                       } else {
+                               /* Not printable, format the bytes. */
+                               while (n-- > 0)
+                                       i += (unsigned)bsdtar_expand_char(
+                                           outbuff, i, *p++);
+                       }
+               } else {
+                       /* After any conversion failure, don't bother
+                        * trying to convert the rest. */
+                       i += (unsigned)bsdtar_expand_char(outbuff, i, *p++);
+                       try_wc = 0;
+               }
+
+               /* If our output buffer is full, dump it and keep going. */
+               if (i > (sizeof(outbuff) - 20)) {
+                       outbuff[i] = '\0';
+                       fprintf(f, "%s", outbuff);
+                       i = 0;
+               }
+       }
+       outbuff[i] = '\0';
+       fprintf(f, "%s", outbuff);
+
+       /* If we allocated a heap-based formatting buffer, free it now. */
+       if (fmtbuff_heap != NULL)
+               free(fmtbuff_heap);
+}
+
+/*
+ * Render an arbitrary sequence of bytes into printable ASCII characters.
+ */
+static size_t
+bsdtar_expand_char(char *buff, size_t offset, char c)
+{
+       size_t i = offset;
+
+       if (isprint((unsigned char)c) && c != '\\')
+               buff[i++] = c;
+       else {
+               buff[i++] = '\\';
+               switch (c) {
+               case '\a': buff[i++] = 'a'; break;
+               case '\b': buff[i++] = 'b'; break;
+               case '\f': buff[i++] = 'f'; break;
+               case '\n': buff[i++] = 'n'; break;
+#if '\r' != '\n'
+               /* On some platforms, \n and \r are the same. */
+               case '\r': buff[i++] = 'r'; break;
+#endif
+               case '\t': buff[i++] = 't'; break;
+               case '\v': buff[i++] = 'v'; break;
+               case '\\': buff[i++] = '\\'; break;
+               default:
+                       sprintf(buff + i, "%03o", 0xFF & (int)c);
+                       i += 3;
+               }
+       }
+
+       return (i - offset);
+}
+
+int
+yes(const char *fmt, ...)
+{
+       char buff[32];
+       char *p;
+       ssize_t l;
+
+       va_list ap;
+       va_start(ap, fmt);
+       vfprintf(stderr, fmt, ap);
+       va_end(ap);
+       fprintf(stderr, " (y/N)? ");
+       fflush(stderr);
+
+       l = read(2, buff, sizeof(buff) - 1);
+       if (l <= 0)
+               return (0);
+       buff[l] = 0;
+
+       for (p = buff; *p != '\0'; p++) {
+               if (isspace((unsigned char)*p))
+                       continue;
+               switch(*p) {
+               case 'y': case 'Y':
+                       return (1);
+               case 'n': case 'N':
+                       return (0);
+               default:
+                       return (0);
+               }
+       }
+
+       return (0);
+}
+
+/*-
+ * The logic here for -C <dir> attempts to avoid
+ * chdir() as long as possible.  For example:
+ * "-C /foo -C /bar file"          needs chdir("/bar") but not chdir("/foo")
+ * "-C /foo -C bar file"           needs chdir("/foo/bar")
+ * "-C /foo -C bar /file1"         does not need chdir()
+ * "-C /foo -C bar /file1 file2"   needs chdir("/foo/bar") before file2
+ *
+ * The only correct way to handle this is to record a "pending" chdir
+ * request and combine multiple requests intelligently until we
+ * need to process a non-absolute file.  set_chdir() adds the new dir
+ * to the pending list; do_chdir() actually executes any pending chdir.
+ *
+ * This way, programs that build tar command lines don't have to worry
+ * about -C with non-existent directories; such requests will only
+ * fail if the directory must be accessed.
+ *
+ * TODO: Make this handle Windows paths correctly.
+ */
+void
+set_chdir(struct bsdtar *bsdtar, const char *newdir)
+{
+       if (newdir[0] == '/') {
+               /* The -C /foo -C /bar case; dump first one. */
+               free(bsdtar->pending_chdir);
+               bsdtar->pending_chdir = NULL;
+       }
+       if (bsdtar->pending_chdir == NULL)
+               /* Easy case: no previously-saved dir. */
+               bsdtar->pending_chdir = strdup(newdir);
+       else {
+               /* The -C /foo -C bar case; concatenate */
+               char *old_pending = bsdtar->pending_chdir;
+               size_t old_len = strlen(old_pending);
+               bsdtar->pending_chdir = malloc(old_len + strlen(newdir) + 2);
+               if (old_pending[old_len - 1] == '/')
+                       old_pending[old_len - 1] = '\0';
+               if (bsdtar->pending_chdir != NULL)
+                       sprintf(bsdtar->pending_chdir, "%s/%s",
+                           old_pending, newdir);
+               free(old_pending);
+       }
+       if (bsdtar->pending_chdir == NULL)
+               lafe_errc(1, errno, "No memory");
+}
+
+void
+do_chdir(struct bsdtar *bsdtar)
+{
+       if (bsdtar->pending_chdir == NULL)
+               return;
+
+       if (chdir(bsdtar->pending_chdir) != 0) {
+               lafe_errc(1, 0, "could not chdir to '%s'\n",
+                   bsdtar->pending_chdir);
+       }
+       free(bsdtar->pending_chdir);
+       bsdtar->pending_chdir = NULL;
+}
+
+static const char *
+strip_components(const char *p, int elements)
+{
+       /* Skip as many elements as necessary. */
+       while (elements > 0) {
+               switch (*p++) {
+               case '/':
+#if defined(_WIN32) && !defined(__CYGWIN__)
+               case '\\': /* Support \ path sep on Windows ONLY. */
+#endif
+                       elements--;
+                       break;
+               case '\0':
+                       /* Path is too short, skip it. */
+                       return (NULL);
+               }
+       }
+
+       /* Skip any / characters.  This handles short paths that have
+        * additional / termination.  This also handles the case where
+        * the logic above stops in the middle of a duplicate //
+        * sequence (which would otherwise get converted to an
+        * absolute path). */
+       for (;;) {
+               switch (*p) {
+               case '/':
+#if defined(_WIN32) && !defined(__CYGWIN__)
+               case '\\': /* Support \ path sep on Windows ONLY. */
+#endif
+                       ++p;
+                       break;
+               case '\0':
+                       return (NULL);
+               default:
+                       return (p);
+               }
+       }
+}
+
+/*
+ * Handle --strip-components and any future path-rewriting options.
+ * Returns non-zero if the pathname should not be extracted.
+ *
+ * TODO: Support pax-style regex path rewrites.
+ */
+int
+edit_pathname(struct bsdtar *bsdtar, struct archive_entry *entry)
+{
+       const char *name = archive_entry_pathname(entry);
+#if HAVE_REGEX_H
+       char *subst_name;
+       int r;
+#endif
+
+#if HAVE_REGEX_H
+       r = apply_substitution(bsdtar, name, &subst_name, 0);
+       if (r == -1) {
+               lafe_warnc(0, "Invalid substitution, skipping entry");
+               return 1;
+       }
+       if (r == 1) {
+               archive_entry_copy_pathname(entry, subst_name);
+               if (*subst_name == '\0') {
+                       free(subst_name);
+                       return -1;
+               } else
+                       free(subst_name);
+               name = archive_entry_pathname(entry);
+       }
+
+       if (archive_entry_hardlink(entry)) {
+               r = apply_substitution(bsdtar, archive_entry_hardlink(entry), &subst_name, 1);
+               if (r == -1) {
+                       lafe_warnc(0, "Invalid substitution, skipping entry");
+                       return 1;
+               }
+               if (r == 1) {
+                       archive_entry_copy_hardlink(entry, subst_name);
+                       free(subst_name);
+               }
+       }
+       if (archive_entry_symlink(entry) != NULL) {
+               r = apply_substitution(bsdtar, archive_entry_symlink(entry), &subst_name, 1);
+               if (r == -1) {
+                       lafe_warnc(0, "Invalid substitution, skipping entry");
+                       return 1;
+               }
+               if (r == 1) {
+                       archive_entry_copy_symlink(entry, subst_name);
+                       free(subst_name);
+               }
+       }
+#endif
+
+       /* Strip leading dir names as per --strip-components option. */
+       if (bsdtar->strip_components > 0) {
+               const char *linkname = archive_entry_hardlink(entry);
+
+               name = strip_components(name, bsdtar->strip_components);
+               if (name == NULL)
+                       return (1);
+
+               if (linkname != NULL) {
+                       linkname = strip_components(linkname,
+                           bsdtar->strip_components);
+                       if (linkname == NULL)
+                               return (1);
+                       archive_entry_copy_hardlink(entry, linkname);
+               }
+       }
+
+       /* By default, don't write or restore absolute pathnames. */
+       if (!bsdtar->option_absolute_paths) {
+               const char *rp, *p = name;
+               int slashonly = 1;
+
+               /* Remove leading "//./" or "//?/" or "//?/UNC/"
+                * (absolute path prefixes used by Windows API) */
+               if ((p[0] == '/' || p[0] == '\\') &&
+                   (p[1] == '/' || p[1] == '\\') &&
+                   (p[2] == '.' || p[2] == '?') &&
+                   (p[3] == '/' || p[3] == '\\'))
+               {
+                       if (p[2] == '?' &&
+                           (p[4] == 'U' || p[4] == 'u') &&
+                           (p[5] == 'N' || p[5] == 'n') &&
+                           (p[6] == 'C' || p[6] == 'c') &&
+                           (p[7] == '/' || p[7] == '\\'))
+                               p += 8;
+                       else
+                               p += 4;
+                       slashonly = 0;
+               }
+               do {
+                       rp = p;
+                       /* Remove leading drive letter from archives created
+                        * on Windows. */
+                       if (((p[0] >= 'a' && p[0] <= 'z') ||
+                            (p[0] >= 'A' && p[0] <= 'Z')) &&
+                                p[1] == ':') {
+                               p += 2;
+                               slashonly = 0;
+                       }
+                       /* Remove leading "/../", "//", etc. */
+                       while (p[0] == '/' || p[0] == '\\') {
+                               if (p[1] == '.' && p[2] == '.' &&
+                                       (p[3] == '/' || p[3] == '\\')) {
+                                       p += 3; /* Remove "/..", leave "/"
+                                                        * for next pass. */
+                                       slashonly = 0;
+                               } else
+                                       p += 1; /* Remove "/". */
+                       }
+               } while (rp != p);
+
+               if (p != name && !bsdtar->warned_lead_slash) {
+                       /* Generate a warning the first time this happens. */
+                       if (slashonly)
+                               lafe_warnc(0,
+                                   "Removing leading '%c' from member names",
+                                   name[0]);
+                       else
+                               lafe_warnc(0,
+                                   "Removing leading drive letter from "
+                                   "member names");
+                       bsdtar->warned_lead_slash = 1;
+               }
+
+               /* Special case: Stripping everything yields ".". */
+               if (*p == '\0')
+                       name = ".";
+               else
+                       name = p;
+       } else {
+               /* Strip redundant leading '/' characters. */
+               while (name[0] == '/' && name[1] == '/')
+                       name++;
+       }
+
+       /* Safely replace name in archive_entry. */
+       if (name != archive_entry_pathname(entry)) {
+               char *q = strdup(name);
+               archive_entry_copy_pathname(entry, q);
+               free(q);
+       }
+       return (0);
+}
+
+/*
+ * It would be nice to just use printf() for formatting large numbers,
+ * but the compatibility problems are quite a headache.  Hence the
+ * following simple utility function.
+ */
+#ifndef __minix
+const char *
+tar_i64toa(int64_t n0)
+{
+       static char buff[24];
+       int64_t n = n0 < 0 ? -n0 : n0;
+       char *p = buff + sizeof(buff);
+
+       *--p = '\0';
+       do {
+               *--p = '0' + (int)(n % 10);
+               n /= 10;
+       } while (n > 0);
+       if (n0 < 0)
+               *--p = '-';
+       return p;
+}
+#else
+const char *
+tar_i64toa(int32_t n0)
+{
+       static char buff[24];
+       int32_t n = n0 < 0 ? -n0 : n0;
+       char *p = buff + sizeof(buff);
+
+       *--p = '\0';
+       do {
+               *--p = '0' + (int)(n % 10);
+               n /= 10;
+       } while (n > 0);
+       if (n0 < 0)
+               *--p = '-';
+       return p;
+}
+#endif
+/*
+ * Like strcmp(), but try to be a little more aware of the fact that
+ * we're comparing two paths.  Right now, it just handles leading
+ * "./" and trailing '/' specially, so that "a/b/" == "./a/b"
+ *
+ * TODO: Make this better, so that "./a//b/./c/" == "a/b/c"
+ * TODO: After this works, push it down into libarchive.
+ * TODO: Publish the path normalization routines in libarchive so
+ * that bsdtar can normalize paths and use fast strcmp() instead
+ * of this.
+ *
+ * Note: This is currently only used within write.c, so should
+ * not handle \ path separators.
+ */
+
+int
+pathcmp(const char *a, const char *b)
+{
+       /* Skip leading './' */
+       if (a[0] == '.' && a[1] == '/' && a[2] != '\0')
+               a += 2;
+       if (b[0] == '.' && b[1] == '/' && b[2] != '\0')
+               b += 2;
+       /* Find the first difference, or return (0) if none. */
+       while (*a == *b) {
+               if (*a == '\0')
+                       return (0);
+               a++;
+               b++;
+       }
+       /*
+        * If one ends in '/' and the other one doesn't,
+        * they're the same.
+        */
+       if (a[0] == '/' && a[1] == '\0' && b[0] == '\0')
+               return (0);
+       if (a[0] == '\0' && b[0] == '/' && b[1] == '\0')
+               return (0);
+       /* They're really different, return the correct sign. */
+       return (*(const unsigned char *)a - *(const unsigned char *)b);
+}
diff --git a/commands/bsdtar/write.c b/commands/bsdtar/write.c
new file mode 100644 (file)
index 0000000..67595e7
--- /dev/null
@@ -0,0 +1,1180 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "bsdtar_platform.h"
+__FBSDID("$FreeBSD: src/usr.bin/tar/write.c,v 1.79 2008/11/27 05:49:52 kientzle Exp $");
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_ATTR_XATTR_H
+#include <attr/xattr.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#ifdef HAVE_GRP_H
+#include <grp.h>
+#endif
+#ifdef HAVE_IO_H
+#include <io.h>
+#endif
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+#ifdef HAVE_LINUX_FS_H
+#include <linux/fs.h>  /* for Linux file flags */
+#endif
+/*
+ * Some Linux distributions have both linux/ext2_fs.h and ext2fs/ext2_fs.h.
+ * As the include guards don't agree, the order of include is important.
+ */
+#ifdef HAVE_LINUX_EXT2_FS_H
+#include <linux/ext2_fs.h>     /* for Linux file flags */
+#endif
+#if defined(HAVE_EXT2FS_EXT2_FS_H) && !defined(__CYGWIN__)
+/* This header exists but is broken on Cygwin. */
+#include <ext2fs/ext2_fs.h>
+#endif
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdio.h>
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "bsdtar.h"
+#include "err.h"
+#include "line_reader.h"
+#include "tree.h"
+
+/* Size of buffer for holding file data prior to writing. */
+#define FILEDATABUFLEN 65536
+
+/* Fixed size of uname/gname caches. */
+#define        name_cache_size 101
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+static const char * const NO_NAME = "(noname)";
+
+struct archive_dir_entry {
+       struct archive_dir_entry        *next;
+       time_t                   mtime_sec;
+       int                      mtime_nsec;
+       char                    *name;
+};
+
+struct archive_dir {
+       struct archive_dir_entry *head, *tail;
+};
+
+struct name_cache {
+       int     probes;
+       int     hits;
+       size_t  size;
+       struct {
+               id_t id;
+               const char *name;
+       } cache[name_cache_size];
+};
+
+static void             add_dir_list(struct bsdtar *bsdtar, const char *path,
+                            time_t mtime_sec, int mtime_nsec);
+static int              append_archive(struct bsdtar *, struct archive *,
+                            struct archive *ina);
+static int              append_archive_filename(struct bsdtar *,
+                            struct archive *, const char *fname);
+static void             archive_names_from_file(struct bsdtar *bsdtar,
+                            struct archive *a);
+static int              copy_file_data(struct bsdtar *, struct archive *a,
+                            struct archive *ina, struct archive_entry *);
+static int              new_enough(struct bsdtar *, const char *path,
+                            const struct stat *);
+#ifndef __minix
+static void             report_write(struct bsdtar *, struct archive *,
+                            struct archive_entry *, int64_t progress);
+#else
+static void             report_write(struct bsdtar *, struct archive *,
+                            struct archive_entry *, int32_t progress);
+#endif
+static void             test_for_append(struct bsdtar *);
+static void             write_archive(struct archive *, struct bsdtar *);
+static void             write_entry_backend(struct bsdtar *, struct archive *,
+                            struct archive_entry *);
+static int              write_file_data(struct bsdtar *, struct archive *,
+                            struct archive_entry *, int fd);
+static void             write_hierarchy(struct bsdtar *, struct archive *,
+                            const char *);
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+/* Not a full lseek() emulation, but enough for our needs here. */
+static int
+seek_file(int fd, int64_t offset, int whence)
+{
+       LARGE_INTEGER distance;
+       (void)whence; /* UNUSED */
+       distance.QuadPart = offset;
+       return (SetFilePointerEx((HANDLE)_get_osfhandle(fd),
+               distance, NULL, FILE_BEGIN) ? 1 : -1);
+}
+#define open _open
+#define close _close
+#define read _read
+#define lseek seek_file
+#endif
+
+void
+tar_mode_c(struct bsdtar *bsdtar)
+{
+       struct archive *a;
+       int r;
+
+       if (*bsdtar->argv == NULL && bsdtar->names_from_file == NULL)
+               lafe_errc(1, 0, "no files or directories specified");
+
+       a = archive_write_new();
+
+       /* Support any format that the library supports. */
+       if (bsdtar->create_format == NULL) {
+               r = archive_write_set_format_pax_restricted(a);
+               bsdtar->create_format = "pax restricted";
+       } else {
+               r = archive_write_set_format_by_name(a, bsdtar->create_format);
+       }
+       if (r != ARCHIVE_OK) {
+               fprintf(stderr, "Can't use format %s: %s\n",
+                   bsdtar->create_format,
+                   archive_error_string(a));
+               usage();
+       }
+
+       /*
+        * If user explicitly set the block size, then assume they
+        * want the last block padded as well.  Otherwise, use the
+        * default block size and accept archive_write_open_file()'s
+        * default padding decisions.
+        */
+       if (bsdtar->bytes_per_block != 0) {
+               archive_write_set_bytes_per_block(a, bsdtar->bytes_per_block);
+               archive_write_set_bytes_in_last_block(a,
+                   bsdtar->bytes_per_block);
+       } else
+               archive_write_set_bytes_per_block(a, DEFAULT_BYTES_PER_BLOCK);
+
+       if (bsdtar->compress_program) {
+               archive_write_set_compression_program(a, bsdtar->compress_program);
+       } else {
+               switch (bsdtar->create_compression) {
+               case 0:
+                       r = archive_write_set_compression_none(a);
+                       break;
+               case 'j': case 'y':
+                       r = archive_write_set_compression_bzip2(a);
+                       break;
+               case 'J':
+                       r = archive_write_set_compression_xz(a);
+                       break;
+               case OPTION_LZMA:
+                       r = archive_write_set_compression_lzma(a);
+                       break;
+               case 'z':
+                       r = archive_write_set_compression_gzip(a);
+                       break;
+               case 'Z':
+                       r = archive_write_set_compression_compress(a);
+                       break;
+               default:
+                       lafe_errc(1, 0,
+                           "Unrecognized compression option -%c",
+                           bsdtar->create_compression);
+               }
+               if (r != ARCHIVE_OK) {
+                       lafe_errc(1, 0,
+                           "Unsupported compression option -%c",
+                           bsdtar->create_compression);
+               }
+       }
+
+       if (ARCHIVE_OK != archive_write_set_options(a, bsdtar->option_options))
+               lafe_errc(1, 0, "%s", archive_error_string(a));
+       if (ARCHIVE_OK != archive_write_open_file(a, bsdtar->filename))
+               lafe_errc(1, 0, "%s", archive_error_string(a));
+       write_archive(a, bsdtar);
+}
+
+/*
+ * Same as 'c', except we only support tar or empty formats in
+ * uncompressed files on disk.
+ */
+void
+tar_mode_r(struct bsdtar *bsdtar)
+{
+#ifndef __minix
+       int64_t end_offset;
+#else
+       off_t   end_offset;
+#endif
+       int     format;
+       struct archive *a;
+       struct archive_entry *entry;
+       int     r;
+
+       /* Sanity-test some arguments and the file. */
+       test_for_append(bsdtar);
+
+       format = ARCHIVE_FORMAT_TAR_PAX_RESTRICTED;
+
+#if defined(__BORLANDC__)
+       bsdtar->fd = open(bsdtar->filename, O_RDWR | O_CREAT | O_BINARY);
+#else
+       bsdtar->fd = open(bsdtar->filename, O_RDWR | O_CREAT | O_BINARY, 0666);
+#endif
+       if (bsdtar->fd < 0)
+               lafe_errc(1, errno,
+                   "Cannot open %s", bsdtar->filename);
+
+       a = archive_read_new();
+       archive_read_support_compression_all(a);
+       archive_read_support_format_tar(a);
+       archive_read_support_format_gnutar(a);
+       r = archive_read_open_fd(a, bsdtar->fd, 10240);
+       if (r != ARCHIVE_OK)
+               lafe_errc(1, archive_errno(a),
+                   "Can't read archive %s: %s", bsdtar->filename,
+                   archive_error_string(a));
+       while (0 == archive_read_next_header(a, &entry)) {
+               if (archive_compression(a) != ARCHIVE_COMPRESSION_NONE) {
+                       archive_read_finish(a);
+                       close(bsdtar->fd);
+                       lafe_errc(1, 0,
+                           "Cannot append to compressed archive.");
+               }
+               /* Keep going until we hit end-of-archive */
+               format = archive_format(a);
+       }
+
+       end_offset = archive_read_header_position(a);
+       archive_read_finish(a);
+
+       /* Re-open archive for writing */
+       a = archive_write_new();
+       archive_write_set_compression_none(a);
+       /*
+        * Set the format to be used for writing.  To allow people to
+        * extend empty files, we need to allow them to specify the format,
+        * which opens the possibility that they will specify a format that
+        * doesn't match the existing format.  Hence, the following bit
+        * of arcane ugliness.
+        */
+
+       if (bsdtar->create_format != NULL) {
+               /* If the user requested a format, use that, but ... */
+               archive_write_set_format_by_name(a,
+                   bsdtar->create_format);
+               /* ... complain if it's not compatible. */
+               format &= ARCHIVE_FORMAT_BASE_MASK;
+               if (format != (int)(archive_format(a) & ARCHIVE_FORMAT_BASE_MASK)
+                   && format != ARCHIVE_FORMAT_EMPTY) {
+                       lafe_errc(1, 0,
+                           "Format %s is incompatible with the archive %s.",
+                           bsdtar->create_format, bsdtar->filename);
+               }
+       } else {
+               /*
+                * Just preserve the current format, with a little care
+                * for formats that libarchive can't write.
+                */
+               if (format == ARCHIVE_FORMAT_TAR_GNUTAR)
+                       /* TODO: When gtar supports pax, use pax restricted. */
+                       format = ARCHIVE_FORMAT_TAR_USTAR;
+               if (format == ARCHIVE_FORMAT_EMPTY)
+                       format = ARCHIVE_FORMAT_TAR_PAX_RESTRICTED;
+               archive_write_set_format(a, format);
+       }
+       if (lseek(bsdtar->fd, end_offset, SEEK_SET) < 0)
+               lafe_errc(1, errno, "Could not seek to archive end");
+       if (ARCHIVE_OK != archive_write_set_options(a, bsdtar->option_options))
+               lafe_errc(1, 0, "%s", archive_error_string(a));
+       if (ARCHIVE_OK != archive_write_open_fd(a, bsdtar->fd))
+               lafe_errc(1, 0, "%s", archive_error_string(a));
+
+       write_archive(a, bsdtar); /* XXX check return val XXX */
+
+       close(bsdtar->fd);
+       bsdtar->fd = -1;
+}
+
+void
+tar_mode_u(struct bsdtar *bsdtar)
+{
+#ifndef __minix
+       int64_t                  end_offset;
+#else
+       off_t                    end_offset;
+#endif
+       struct archive          *a;
+       struct archive_entry    *entry;
+       int                      format;
+       struct archive_dir_entry        *p;
+       struct archive_dir       archive_dir;
+
+       bsdtar->archive_dir = &archive_dir;
+       memset(&archive_dir, 0, sizeof(archive_dir));
+
+       format = ARCHIVE_FORMAT_TAR_PAX_RESTRICTED;
+
+       /* Sanity-test some arguments and the file. */
+       test_for_append(bsdtar);
+
+       bsdtar->fd = open(bsdtar->filename, O_RDWR | O_BINARY);
+       if (bsdtar->fd < 0)
+               lafe_errc(1, errno,
+                   "Cannot open %s", bsdtar->filename);
+
+       a = archive_read_new();
+       archive_read_support_compression_all(a);
+       archive_read_support_format_tar(a);
+       archive_read_support_format_gnutar(a);
+       if (archive_read_open_fd(a, bsdtar->fd,
+           bsdtar->bytes_per_block != 0 ? bsdtar->bytes_per_block :
+               DEFAULT_BYTES_PER_BLOCK) != ARCHIVE_OK) {
+               lafe_errc(1, 0,
+                   "Can't open %s: %s", bsdtar->filename,
+                   archive_error_string(a));
+       }
+
+       /* Build a list of all entries and their recorded mod times. */
+       while (0 == archive_read_next_header(a, &entry)) {
+               if (archive_compression(a) != ARCHIVE_COMPRESSION_NONE) {
+                       archive_read_finish(a);
+                       close(bsdtar->fd);
+                       lafe_errc(1, 0,
+                           "Cannot append to compressed archive.");
+               }
+               add_dir_list(bsdtar, archive_entry_pathname(entry),
+                   archive_entry_mtime(entry),
+                   archive_entry_mtime_nsec(entry));
+               /* Record the last format determination we see */
+               format = archive_format(a);
+               /* Keep going until we hit end-of-archive */
+       }
+
+       end_offset = archive_read_header_position(a);
+       archive_read_finish(a);
+
+       /* Re-open archive for writing. */
+       a = archive_write_new();
+       archive_write_set_compression_none(a);
+       /*
+        * Set format to same one auto-detected above, except that
+        * we don't write GNU tar format, so use ustar instead.
+        */
+       if (format == ARCHIVE_FORMAT_TAR_GNUTAR)
+               format = ARCHIVE_FORMAT_TAR_USTAR;
+       archive_write_set_format(a, format);
+       if (bsdtar->bytes_per_block != 0) {
+               archive_write_set_bytes_per_block(a, bsdtar->bytes_per_block);
+               archive_write_set_bytes_in_last_block(a,
+                   bsdtar->bytes_per_block);
+       } else
+               archive_write_set_bytes_per_block(a, DEFAULT_BYTES_PER_BLOCK);
+       if (lseek(bsdtar->fd, end_offset, SEEK_SET) < 0)
+               lafe_errc(1, errno, "Could not seek to archive end");
+       if (ARCHIVE_OK != archive_write_set_options(a, bsdtar->option_options))
+               lafe_errc(1, 0, "%s", archive_error_string(a));
+       if (ARCHIVE_OK != archive_write_open_fd(a, bsdtar->fd))
+               lafe_errc(1, 0, "%s", archive_error_string(a));
+
+       write_archive(a, bsdtar);
+
+       close(bsdtar->fd);
+       bsdtar->fd = -1;
+
+       while (bsdtar->archive_dir->head != NULL) {
+               p = bsdtar->archive_dir->head->next;
+               free(bsdtar->archive_dir->head->name);
+               free(bsdtar->archive_dir->head);
+               bsdtar->archive_dir->head = p;
+       }
+       bsdtar->archive_dir->tail = NULL;
+}
+
+
+/*
+ * Write user-specified files/dirs to opened archive.
+ */
+static void
+write_archive(struct archive *a, struct bsdtar *bsdtar)
+{
+       const char *arg;
+       struct archive_entry *entry, *sparse_entry;
+
+       /* Allocate a buffer for file data. */
+       if ((bsdtar->buff = malloc(FILEDATABUFLEN)) == NULL)
+               lafe_errc(1, 0, "cannot allocate memory");
+
+       if ((bsdtar->resolver = archive_entry_linkresolver_new()) == NULL)
+               lafe_errc(1, 0, "cannot create link resolver");
+       archive_entry_linkresolver_set_strategy(bsdtar->resolver,
+           archive_format(a));
+       if ((bsdtar->diskreader = archive_read_disk_new()) == NULL)
+               lafe_errc(1, 0, "Cannot create read_disk object");
+       archive_read_disk_set_standard_lookup(bsdtar->diskreader);
+
+       if (bsdtar->names_from_file != NULL)
+               archive_names_from_file(bsdtar, a);
+
+       while (*bsdtar->argv) {
+               arg = *bsdtar->argv;
+               if (arg[0] == '-' && arg[1] == 'C') {
+                       arg += 2;
+                       if (*arg == '\0') {
+                               bsdtar->argv++;
+                               arg = *bsdtar->argv;
+                               if (arg == NULL) {
+                                       lafe_warnc(0, "%s",
+                                           "Missing argument for -C");
+                                       bsdtar->return_value = 1;
+                                       goto cleanup;
+                               }
+                       }
+                       set_chdir(bsdtar, arg);
+               } else {
+                       if (*arg != '/' && (arg[0] != '@' || arg[1] != '/'))
+                               do_chdir(bsdtar); /* Handle a deferred -C */
+                       if (*arg == '@') {
+                               if (append_archive_filename(bsdtar, a,
+                                   arg + 1) != 0)
+                                       break;
+                       } else
+                               write_hierarchy(bsdtar, a, arg);
+               }
+               bsdtar->argv++;
+       }
+
+       entry = NULL;
+       archive_entry_linkify(bsdtar->resolver, &entry, &sparse_entry);
+       while (entry != NULL) {
+               write_entry_backend(bsdtar, a, entry);
+               archive_entry_free(entry);
+               entry = NULL;
+               archive_entry_linkify(bsdtar->resolver, &entry, &sparse_entry);
+       }
+
+       if (archive_write_close(a)) {
+               lafe_warnc(0, "%s", archive_error_string(a));
+               bsdtar->return_value = 1;
+       }
+
+cleanup:
+       /* Free file data buffer. */
+       free(bsdtar->buff);
+       archive_entry_linkresolver_free(bsdtar->resolver);
+       bsdtar->resolver = NULL;
+       archive_read_finish(bsdtar->diskreader);
+       bsdtar->diskreader = NULL;
+
+       if (bsdtar->option_totals) {
+               fprintf(stderr, "Total bytes written: %s\n",
+                   tar_i64toa(archive_position_compressed(a)));
+       }
+
+       archive_write_finish(a);
+}
+
+/*
+ * Archive names specified in file.
+ *
+ * Unless --null was specified, a line containing exactly "-C" will
+ * cause the next line to be a directory to pass to chdir().  If
+ * --null is specified, then a line "-C" is just another filename.
+ */
+static void
+archive_names_from_file(struct bsdtar *bsdtar, struct archive *a)
+{
+       struct lafe_line_reader *lr;
+       const char *line;
+
+       bsdtar->next_line_is_dir = 0;
+
+       lr = lafe_line_reader(bsdtar->names_from_file, bsdtar->option_null);
+       while ((line = lafe_line_reader_next(lr)) != NULL) {
+               if (bsdtar->next_line_is_dir) {
+                       set_chdir(bsdtar, line);
+                       bsdtar->next_line_is_dir = 0;
+               } else if (!bsdtar->option_null && strcmp(line, "-C") == 0)
+                       bsdtar->next_line_is_dir = 1;
+               else {
+                       if (*line != '/')
+                               do_chdir(bsdtar); /* Handle a deferred -C */
+                       write_hierarchy(bsdtar, a, line);
+               }
+       }
+       lafe_line_reader_free(lr);
+       if (bsdtar->next_line_is_dir)
+               lafe_errc(1, errno,
+                   "Unexpected end of filename list; "
+                   "directory expected after -C");
+}
+
+/*
+ * Copy from specified archive to current archive.  Returns non-zero
+ * for write errors (which force us to terminate the entire archiving
+ * operation).  If there are errors reading the input archive, we set
+ * bsdtar->return_value but return zero, so the overall archiving
+ * operation will complete and return non-zero.
+ */
+static int
+append_archive_filename(struct bsdtar *bsdtar, struct archive *a,
+    const char *filename)
+{
+       struct archive *ina;
+       int rc;
+
+       if (strcmp(filename, "-") == 0)
+               filename = NULL; /* Library uses NULL for stdio. */
+
+       ina = archive_read_new();
+       archive_read_support_format_all(ina);
+       archive_read_support_compression_all(ina);
+       if (archive_read_open_file(ina, filename, 10240)) {
+               lafe_warnc(0, "%s", archive_error_string(ina));
+               bsdtar->return_value = 1;
+               return (0);
+       }
+
+       rc = append_archive(bsdtar, a, ina);
+
+       if (rc != ARCHIVE_OK) {
+               lafe_warnc(0, "Error reading archive %s: %s",
+                   filename, archive_error_string(ina));
+               bsdtar->return_value = 1;
+       }
+       archive_read_finish(ina);
+
+       return (rc);
+}
+
+static int
+append_archive(struct bsdtar *bsdtar, struct archive *a, struct archive *ina)
+{
+       struct archive_entry *in_entry;
+       int e;
+
+       while (0 == archive_read_next_header(ina, &in_entry)) {
+               if (!new_enough(bsdtar, archive_entry_pathname(in_entry),
+                       archive_entry_stat(in_entry)))
+                       continue;
+               if (lafe_excluded(bsdtar->matching, archive_entry_pathname(in_entry)))
+                       continue;
+               if (bsdtar->option_interactive &&
+                   !yes("copy '%s'", archive_entry_pathname(in_entry)))
+                       continue;
+               if (bsdtar->verbose)
+                       safe_fprintf(stderr, "a %s",
+                           archive_entry_pathname(in_entry));
+               if (need_report())
+                       report_write(bsdtar, a, in_entry, 0);
+
+               e = archive_write_header(a, in_entry);
+               if (e != ARCHIVE_OK) {
+                       if (!bsdtar->verbose)
+                               lafe_warnc(0, "%s: %s",
+                                   archive_entry_pathname(in_entry),
+                                   archive_error_string(a));
+                       else
+                               fprintf(stderr, ": %s", archive_error_string(a));
+               }
+               if (e == ARCHIVE_FATAL)
+                       exit(1);
+
+               if (e >= ARCHIVE_WARN) {
+                       if (archive_entry_size(in_entry) == 0)
+                               archive_read_data_skip(ina);
+                       else if (copy_file_data(bsdtar, a, ina, in_entry))
+                               exit(1);
+               }
+
+               if (bsdtar->verbose)
+                       fprintf(stderr, "\n");
+       }
+
+       /* Note: If we got here, we saw no write errors, so return success. */
+       return (0);
+}
+
+/* Helper function to copy data between archives. */
+static int
+copy_file_data(struct bsdtar *bsdtar, struct archive *a,
+    struct archive *ina, struct archive_entry *entry)
+{
+       ssize_t bytes_read;
+       ssize_t bytes_written;
+#ifndef __minix
+       int64_t progress = 0;
+#else
+       int32_t progress = 0;
+#endif
+       bytes_read = archive_read_data(ina, bsdtar->buff, FILEDATABUFLEN);
+       while (bytes_read > 0) {
+               if (need_report())
+                       report_write(bsdtar, a, entry, progress);
+
+               bytes_written = archive_write_data(a, bsdtar->buff,
+                   bytes_read);
+               if (bytes_written < bytes_read) {
+                       lafe_warnc(0, "%s", archive_error_string(a));
+                       return (-1);
+               }
+               progress += bytes_written;
+               bytes_read = archive_read_data(ina, bsdtar->buff,
+                   FILEDATABUFLEN);
+       }
+
+       return (0);
+}
+
+/*
+ * Add the file or dir hierarchy named by 'path' to the archive
+ */
+static void
+write_hierarchy(struct bsdtar *bsdtar, struct archive *a, const char *path)
+{
+       struct archive_entry *entry = NULL, *spare_entry = NULL;
+       struct tree *tree;
+       char symlink_mode = bsdtar->symlink_mode;
+       dev_t first_dev = 0;
+       int dev_recorded = 0;
+       int tree_ret;
+
+       tree = tree_open(path);
+
+       if (!tree) {
+               lafe_warnc(errno, "%s: Cannot open", path);
+               bsdtar->return_value = 1;
+               return;
+       }
+
+       while ((tree_ret = tree_next(tree)) != 0) {
+               int r;
+               const char *name = tree_current_path(tree);
+               const struct stat *st = NULL; /* info to use for this entry */
+               const struct stat *lst = NULL; /* lstat() information */
+               int descend;
+
+               if (tree_ret == TREE_ERROR_FATAL)
+                       lafe_errc(1, tree_errno(tree),
+                           "%s: Unable to continue traversing directory tree",
+                           name);
+               if (tree_ret == TREE_ERROR_DIR) {
+                       lafe_warnc(errno,
+                           "%s: Couldn't visit directory", name);
+                       bsdtar->return_value = 1;
+               }
+               if (tree_ret != TREE_REGULAR)
+                       continue;
+
+               /*
+                * If this file/dir is excluded by a filename
+                * pattern, skip it.
+                */
+               if (lafe_excluded(bsdtar->matching, name))
+                       continue;
+
+               /*
+                * Get lstat() info from the tree library.
+                */
+               lst = tree_current_lstat(tree);
+               if (lst == NULL) {
+                       /* Couldn't lstat(); must not exist. */
+                       lafe_warnc(errno, "%s: Cannot stat", name);
+                       /* Return error if files disappear during traverse. */
+                       bsdtar->return_value = 1;
+                       continue;
+               }
+
+               /*
+                * Distinguish 'L'/'P'/'H' symlink following.
+                */
+               switch(symlink_mode) {
+               case 'H':
+                       /* 'H': After the first item, rest like 'P'. */
+                       symlink_mode = 'P';
+                       /* 'H': First item (from command line) like 'L'. */
+                       /* FALLTHROUGH */
+               case 'L':
+                       /* 'L': Do descend through a symlink to dir. */
+                       descend = tree_current_is_dir(tree);
+                       /* 'L': Follow symlinks to files. */
+                       archive_read_disk_set_symlink_logical(bsdtar->diskreader);
+                       /* 'L': Archive symlinks as targets, if we can. */
+                       st = tree_current_stat(tree);
+                       if (st != NULL)
+                               break;
+                       /* If stat fails, we have a broken symlink;
+                        * in that case, don't follow the link. */
+                       /* FALLTHROUGH */
+               default:
+                       /* 'P': Don't descend through a symlink to dir. */
+                       descend = tree_current_is_physical_dir(tree);
+                       /* 'P': Don't follow symlinks to files. */
+                       archive_read_disk_set_symlink_physical(bsdtar->diskreader);
+                       /* 'P': Archive symlinks as symlinks. */
+                       st = lst;
+                       break;
+               }
+
+               /*
+                * Are we about to cross to a new filesystem?
+                */
+               if (!dev_recorded) {
+                       /* This is the initial file system. */
+                       first_dev = lst->st_dev;
+                       dev_recorded = 1;
+               } else if (lst->st_dev == first_dev) {
+                       /* The starting file system is always acceptable. */
+               } else if (descend == 0) {
+                       /* We're not descending, so no need to check. */
+               } else if (bsdtar->option_dont_traverse_mounts) {
+                       /* User has asked us not to cross mount points. */
+                       descend = 0;
+               } else {
+                       /* We're prepared to cross a mount point. */
+
+                       /* XXX TODO: check whether this filesystem is
+                        * synthetic and/or local.  Add a new
+                        * --local-only option to skip non-local
+                        * filesystems.  Skip synthetic filesystems
+                        * regardless.
+                        *
+                        * The results should be cached, since
+                        * tree.c doesn't usually visit a directory
+                        * and the directory contents together.  A simple
+                        * move-to-front list should perform quite well.
+                        *
+                        * This is going to be heavily OS dependent:
+                        * FreeBSD's statfs() in conjunction with getvfsbyname()
+                        * provides all of this; NetBSD's statvfs() does
+                        * most of it; other systems will vary.
+                        */
+               }
+
+               /*
+                * In -u mode, check that the file is newer than what's
+                * already in the archive; in all modes, obey --newerXXX flags.
+                */
+               if (!new_enough(bsdtar, name, st))
+                       continue;
+
+               archive_entry_free(entry);
+               entry = archive_entry_new();
+
+               archive_entry_set_pathname(entry, name);
+               archive_entry_copy_sourcepath(entry,
+                   tree_current_access_path(tree));
+
+               /* Populate the archive_entry with metadata from the disk. */
+               /* XXX TODO: Arrange to open a regular file before
+                * calling this so we can pass in an fd and shorten
+                * the race to query metadata.  The linkify dance
+                * makes this more complex than it might sound. */
+#if defined(_WIN32) && !defined(__CYGWIN__)
+               /* TODO: tree.c uses stat(), which is badly broken
+                * on Windows.  To fix this, we should
+                * deprecate tree_current_stat() and provide a new
+                * call tree_populate_entry(t, entry).  This call
+                * would use stat() internally on POSIX and
+                * GetInfoByFileHandle() internally on Windows.
+                * This would be another step towards a tree-walker
+                * that can be integrated deep into libarchive.
+                * For now, just set st to NULL on Windows;
+                * archive_read_disk_entry_from_file() should
+                * be smart enough to use platform-appropriate
+                * ways to probe file information.
+                */
+               st = NULL;
+#endif
+               r = archive_read_disk_entry_from_file(bsdtar->diskreader,
+                   entry, -1, st);
+               if (r != ARCHIVE_OK)
+                       lafe_warnc(archive_errno(bsdtar->diskreader),
+                           "%s", archive_error_string(bsdtar->diskreader));
+               if (r < ARCHIVE_WARN)
+                       continue;
+
+               /* XXX TODO: Just use flag data from entry; avoid the
+                * duplicate check here. */
+
+               /*
+                * If this file/dir is flagged "nodump" and we're
+                * honoring such flags, skip this file/dir.
+                */
+#if defined(HAVE_STRUCT_STAT_ST_FLAGS) && defined(UF_NODUMP)
+               /* BSD systems store flags in struct stat */
+               if (bsdtar->option_honor_nodump &&
+                   (lst->st_flags & UF_NODUMP))
+                       continue;
+#endif
+
+#if defined(EXT2_IOC_GETFLAGS) && defined(EXT2_NODUMP_FL)
+               /* Linux uses ioctl to read flags. */
+               if (bsdtar->option_honor_nodump) {
+                       int fd = open(name, O_RDONLY | O_NONBLOCK | O_BINARY);
+                       if (fd >= 0) {
+                               unsigned long fflags;
+                               int r = ioctl(fd, EXT2_IOC_GETFLAGS, &fflags);
+                               close(fd);
+                               if (r >= 0 && (fflags & EXT2_NODUMP_FL))
+                                       continue;
+                       }
+               }
+#endif
+
+               /*
+                * If the user vetoes this file/directory, skip it.
+                * We want this to be fairly late; if some other
+                * check would veto this file, we shouldn't bother
+                * the user with it.
+                */
+               if (bsdtar->option_interactive &&
+                   !yes("add '%s'", name))
+                       continue;
+
+               /* Note: if user vetoes, we won't descend. */
+               if (descend && !bsdtar->option_no_subdirs)
+                       tree_descend(tree);
+
+               /*
+                * Rewrite the pathname to be archived.  If rewrite
+                * fails, skip the entry.
+                */
+               if (edit_pathname(bsdtar, entry))
+                       continue;
+
+               /* Display entry as we process it.
+                * This format is required by SUSv2. */
+               if (bsdtar->verbose)
+                       safe_fprintf(stderr, "a %s",
+                           archive_entry_pathname(entry));
+
+               /* Non-regular files get archived with zero size. */
+               if (archive_entry_filetype(entry) != AE_IFREG)
+                       archive_entry_set_size(entry, 0);
+
+               archive_entry_linkify(bsdtar->resolver, &entry, &spare_entry);
+
+               while (entry != NULL) {
+                       write_entry_backend(bsdtar, a, entry);
+                       archive_entry_free(entry);
+                       entry = spare_entry;
+                       spare_entry = NULL;
+               }
+
+               if (bsdtar->verbose)
+                       fprintf(stderr, "\n");
+       }
+       archive_entry_free(entry);
+       tree_close(tree);
+}
+
+/*
+ * Backend for write_entry.
+ */
+static void
+write_entry_backend(struct bsdtar *bsdtar, struct archive *a,
+    struct archive_entry *entry)
+{
+       int fd = -1;
+       int e;
+
+       if (archive_entry_size(entry) > 0) {
+               const char *pathname = archive_entry_sourcepath(entry);
+               fd = open(pathname, O_RDONLY | O_BINARY);
+               if (fd == -1) {
+                       if (!bsdtar->verbose)
+                               lafe_warnc(errno,
+                                   "%s: could not open file", pathname);
+                       else
+                               fprintf(stderr, ": %s", strerror(errno));
+                       return;
+               }
+       }
+
+       e = archive_write_header(a, entry);
+       if (e != ARCHIVE_OK) {
+               if (!bsdtar->verbose)
+                       lafe_warnc(0, "%s: %s",
+                           archive_entry_pathname(entry),
+                           archive_error_string(a));
+               else
+                       fprintf(stderr, ": %s", archive_error_string(a));
+       }
+
+       if (e == ARCHIVE_FATAL)
+               exit(1);
+
+       /*
+        * If we opened a file earlier, write it out now.  Note that
+        * the format handler might have reset the size field to zero
+        * to inform us that the archive body won't get stored.  In
+        * that case, just skip the write.
+        */
+       if (e >= ARCHIVE_WARN && fd >= 0 && archive_entry_size(entry) > 0) {
+               if (write_file_data(bsdtar, a, entry, fd))
+                       exit(1);
+       }
+
+       /*
+        * If we opened a file, close it now even if there was an error
+        * which made us decide not to write the archive body.
+        */
+       if (fd >= 0)
+               close(fd);
+}
+
+#ifndef __minix
+static void
+report_write(struct bsdtar *bsdtar, struct archive *a,
+    struct archive_entry *entry, int64_t progress)
+{
+       uint64_t comp, uncomp;
+       if (bsdtar->verbose)
+               fprintf(stderr, "\n");
+       comp = archive_position_compressed(a);
+       uncomp = archive_position_uncompressed(a);
+       fprintf(stderr, "In: %d files, %s bytes;",
+           archive_file_count(a), tar_i64toa(uncomp));
+       fprintf(stderr,
+           " Out: %s bytes, compression %d%%\n",
+           tar_i64toa(comp), (int)((uncomp - comp) * 100 / uncomp));
+       /* Can't have two calls to tar_i64toa() pending, so split the output. */
+       safe_fprintf(stderr, "Current: %s (%s",
+           archive_entry_pathname(entry),
+           tar_i64toa(progress));
+       fprintf(stderr, "/%s bytes)\n",
+           tar_i64toa(archive_entry_size(entry)));
+}
+#else
+static void
+report_write(struct bsdtar *bsdtar, struct archive *a,
+    struct archive_entry *entry, int32_t progress)
+{
+       size_t comp, uncomp;
+       if (bsdtar->verbose)
+               fprintf(stderr, "\n");
+       comp = archive_position_compressed(a);
+       uncomp = archive_position_uncompressed(a);
+       fprintf(stderr, "In: %d files, %s bytes;",
+           archive_file_count(a), tar_i64toa(uncomp));
+       fprintf(stderr,
+           " Out: %s bytes, compression %d%%\n",
+           tar_i64toa(comp), (int)((uncomp - comp) * 100 / uncomp));
+       /* Can't have two calls to tar_i64toa() pending, so split the output. */
+       safe_fprintf(stderr, "Current: %s (%s",
+           archive_entry_pathname(entry),
+           tar_i64toa(progress));
+       fprintf(stderr, "/%s bytes)\n",
+           tar_i64toa(archive_entry_size(entry)));
+}
+#endif
+
+/* Helper function to copy file to archive. */
+static int
+write_file_data(struct bsdtar *bsdtar, struct archive *a,
+    struct archive_entry *entry, int fd)
+{
+       ssize_t bytes_read;
+       ssize_t bytes_written;
+#ifndef __minix
+       int64_t progress = 0;
+#else
+       int32_t progress = 0;
+#endif
+       bytes_read = read(fd, bsdtar->buff, FILEDATABUFLEN);
+       while (bytes_read > 0) {
+               if (need_report())
+                       report_write(bsdtar, a, entry, progress);
+
+               bytes_written = archive_write_data(a, bsdtar->buff,
+                   bytes_read);
+               if (bytes_written < 0) {
+                       /* Write failed; this is bad */
+                       lafe_warnc(0, "%s", archive_error_string(a));
+                       return (-1);
+               }
+               if (bytes_written < bytes_read) {
+                       /* Write was truncated; warn but continue. */
+                       lafe_warnc(0,
+                           "%s: Truncated write; file may have grown while being archived.",
+                           archive_entry_pathname(entry));
+                       return (0);
+               }
+               progress += bytes_written;
+               bytes_read = read(fd, bsdtar->buff, FILEDATABUFLEN);
+       }
+       return 0;
+}
+
+/*
+ * Test if the specified file is new enough to include in the archive.
+ */
+static int
+new_enough(struct bsdtar *bsdtar, const char *path, const struct stat *st)
+{
+       struct archive_dir_entry *p;
+
+       /*
+        * If this file/dir is excluded by a time comparison, skip it.
+        */
+       if (bsdtar->newer_ctime_sec > 0) {
+               if (st->st_ctime < bsdtar->newer_ctime_sec)
+                       return (0); /* Too old, skip it. */
+               if (st->st_ctime == bsdtar->newer_ctime_sec
+                   && ARCHIVE_STAT_CTIME_NANOS(st)
+                   <= bsdtar->newer_ctime_nsec)
+                       return (0); /* Too old, skip it. */
+       }
+       if (bsdtar->newer_mtime_sec > 0) {
+               if (st->st_mtime < bsdtar->newer_mtime_sec)
+                       return (0); /* Too old, skip it. */
+               if (st->st_mtime == bsdtar->newer_mtime_sec
+                   && ARCHIVE_STAT_MTIME_NANOS(st)
+                   <= bsdtar->newer_mtime_nsec)
+                       return (0); /* Too old, skip it. */
+       }
+
+       /*
+        * In -u mode, we only write an entry if it's newer than
+        * what was already in the archive.
+        */
+       if (bsdtar->archive_dir != NULL &&
+           bsdtar->archive_dir->head != NULL) {
+               for (p = bsdtar->archive_dir->head; p != NULL; p = p->next) {
+                       if (pathcmp(path, p->name)==0)
+                               return (p->mtime_sec < st->st_mtime ||
+                                   (p->mtime_sec == st->st_mtime &&
+                                       p->mtime_nsec
+                                       < ARCHIVE_STAT_MTIME_NANOS(st)));
+               }
+       }
+
+       /* If the file wasn't rejected, include it. */
+       return (1);
+}
+
+/*
+ * Add an entry to the dir list for 'u' mode.
+ *
+ * XXX TODO: Make this fast.
+ */
+static void
+add_dir_list(struct bsdtar *bsdtar, const char *path,
+    time_t mtime_sec, int mtime_nsec)
+{
+       struct archive_dir_entry        *p;
+
+       /*
+        * Search entire list to see if this file has appeared before.
+        * If it has, override the timestamp data.
+        */
+       p = bsdtar->archive_dir->head;
+       while (p != NULL) {
+               if (strcmp(path, p->name)==0) {
+                       p->mtime_sec = mtime_sec;
+                       p->mtime_nsec = mtime_nsec;
+                       return;
+               }
+               p = p->next;
+       }
+
+       p = malloc(sizeof(*p));
+       if (p == NULL)
+               lafe_errc(1, ENOMEM, "Can't read archive directory");
+
+       p->name = strdup(path);
+       if (p->name == NULL)
+               lafe_errc(1, ENOMEM, "Can't read archive directory");
+       p->mtime_sec = mtime_sec;
+       p->mtime_nsec = mtime_nsec;
+       p->next = NULL;
+       if (bsdtar->archive_dir->tail == NULL) {
+               bsdtar->archive_dir->head = bsdtar->archive_dir->tail = p;
+       } else {
+               bsdtar->archive_dir->tail->next = p;
+               bsdtar->archive_dir->tail = p;
+       }
+}
+
+static void
+test_for_append(struct bsdtar *bsdtar)
+{
+       struct stat s;
+
+       if (*bsdtar->argv == NULL && bsdtar->names_from_file == NULL)
+               lafe_errc(1, 0, "no files or directories specified");
+       if (bsdtar->filename == NULL)
+               lafe_errc(1, 0, "Cannot append to stdout.");
+
+       if (bsdtar->create_compression != 0)
+               lafe_errc(1, 0,
+                   "Cannot append to %s with compression", bsdtar->filename);
+
+       if (stat(bsdtar->filename, &s) != 0)
+               return;
+
+       if (!S_ISREG(s.st_mode) && !S_ISBLK(s.st_mode))
+               lafe_errc(1, 0,
+                   "Cannot append to %s: not a regular file.",
+                   bsdtar->filename);
+
+/* Is this an appropriate check here on Windows? */
+/*
+       if (GetFileType(handle) != FILE_TYPE_DISK)
+               lafe_errc(1, 0, "Cannot append");
+*/
+
+}