[Midnightbsd-cvs] src: usr.bin/tar:

laffer1 at midnightbsd.org laffer1 at midnightbsd.org
Fri Mar 27 15:51:49 EDT 2009


Log Message:
-----------


Modified Files:
--------------
    src/usr.bin/tar:
        COPYING (r1.3 -> r1.4)
        Makefile (r1.3 -> r1.4)
        bsdtar.1 (r1.3 -> r1.4)
        bsdtar.c (r1.3 -> r1.4)
        bsdtar.h (r1.3 -> r1.4)
        bsdtar_platform.h (r1.3 -> r1.4)
        config_midnightbsd.h (r1.1 -> r1.2)
        matching.c (r1.3 -> r1.4)
        read.c (r1.3 -> r1.4)
        tree.c (r1.3 -> r1.4)
        util.c (r1.3 -> r1.4)
        write.c (r1.3 -> r1.4)

Added Files:
-----------
    src/usr.bin/tar:
        siginfo.c (r1.1)
        subst.c (r1.1)
    src/usr.bin/tar/test:
        Makefile (r1.1)
        main.c (r1.1)
        test.h (r1.1)
        test_0.c (r1.1)
        test_basic.c (r1.1)
        test_copy.c (r1.1)
        test_getdate.c (r1.1)
        test_help.c (r1.1)
        test_option_T.c (r1.1)
        test_option_q.c (r1.1)
        test_patterns.c (r1.1)
        test_patterns_2.tgz.uu (r1.1)
        test_patterns_3.tgz.uu (r1.1)
        test_stdio.c (r1.1)
        test_strip_components.c (r1.1)
        test_symlink_dir.c (r1.1)
        test_version.c (r1.1)

-------------- next part --------------
Index: util.c
===================================================================
RCS file: /home/cvs/src/usr.bin/tar/util.c,v
retrieving revision 1.3
retrieving revision 1.4
diff -L usr.bin/tar/util.c -L usr.bin/tar/util.c -u -r1.3 -r1.4
--- usr.bin/tar/util.c
+++ usr.bin/tar/util.c
@@ -24,8 +24,7 @@
  */
 
 #include "bsdtar_platform.h"
-__FBSDID("$FreeBSD: src/usr.bin/tar/util.c,v 1.17 2007/04/18 04:36:11 kientzle Exp $");
-__MBSDID("$MidnightBSD$");
+__FBSDID("$FreeBSD: src/usr.bin/tar/util.c,v 1.17.2.3 2008/12/11 05:56:47 kientzle Exp $");
 
 #ifdef HAVE_SYS_STAT_H
 #include <sys/stat.h>
@@ -52,6 +51,7 @@
 
 static void	bsdtar_vwarnc(struct bsdtar *, int code,
 		    const char *fmt, va_list ap);
+static const char *strip_components(const char *path, int elements);
 
 /*
  * Print a string, taking care with any non-printable characters.
@@ -179,7 +179,7 @@
 	fprintf(stderr, " (y/N)? ");
 	fflush(stderr);
 
-	l = read(2, buff, sizeof(buff));
+	l = read(2, buff, sizeof(buff) - 1);
 	if (l <= 0)
 		return (0);
 	buff[l] = 0;
@@ -200,56 +200,6 @@
 	return (0);
 }
 
-void
-bsdtar_strmode(struct archive_entry *entry, char *bp)
-{
-	static const char *perms = "?rwxrwxrwx ";
-	static const mode_t permbits[] =
-	    { S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP,
-	      S_IROTH, S_IWOTH, S_IXOTH };
-	mode_t mode;
-	int i;
-
-	/* Fill in a default string, then selectively override. */
-	strcpy(bp, perms);
-
-	mode = archive_entry_mode(entry);
-	switch (mode & S_IFMT) {
-	case S_IFREG:  bp[0] = '-'; break;
-	case S_IFBLK:  bp[0] = 'b'; break;
-	case S_IFCHR:  bp[0] = 'c'; break;
-	case S_IFDIR:  bp[0] = 'd'; break;
-	case S_IFLNK:  bp[0] = 'l'; break;
-	case S_IFSOCK: bp[0] = 's'; break;
-#ifdef S_IFIFO
-	case S_IFIFO:  bp[0] = 'p'; break;
-#endif
-#ifdef S_IFWHT
-	case S_IFWHT:  bp[0] = 'w'; break;
-#endif
-	}
-
-	for (i = 0; i < 9; i++)
-		if (!(mode & permbits[i]))
-			bp[i+1] = '-';
-
-	if (mode & S_ISUID) {
-		if (mode & S_IXUSR) bp[3] = 's';
-		else bp[3] = 'S';
-	}
-	if (mode & S_ISGID) {
-		if (mode & S_IXGRP) bp[6] = 's';
-		else bp[6] = 'S';
-	}
-	if (mode & S_ISVTX) {
-		if (mode & S_IXOTH) bp[9] = 't';
-		else bp[9] = 'T';
-	}
-	if (archive_entry_acl_count(entry, ARCHIVE_ENTRY_ACL_TYPE_ACCESS))
-		bp[10] = '+';
-}
-
-
 /*
  * Read lines from file and do something with each one.  If option_null
  * is set, lines are terminated with zero bytes; otherwise, they're
@@ -266,7 +216,7 @@
 {
 	FILE *f;
 	char *buff, *buff_end, *line_start, *line_end, *p;
-	size_t buff_length, bytes_read, bytes_wanted;
+	size_t buff_length, new_buff_length, bytes_read, bytes_wanted;
 	int separator;
 	int ret;
 
@@ -313,7 +263,12 @@
 			line_start = buff;
 		} else {
 			/* Line is too big; enlarge the buffer. */
-			p = realloc(buff, buff_length *= 2);
+			new_buff_length = buff_length * 2;
+			if (new_buff_length <= buff_length)
+				bsdtar_errc(bsdtar, 1, ENOMEM,
+				    "Line too long in %s", pathname);
+			buff_length = new_buff_length;
+			p = realloc(buff, buff_length);
 			if (p == NULL)
 				bsdtar_errc(bsdtar, 1, ENOMEM,
 				    "Line too long in %s", pathname);
@@ -392,6 +347,31 @@
 	bsdtar->pending_chdir = NULL;
 }
 
+const char *
+strip_components(const char *path, int elements)
+{
+	const char *p = path;
+
+	while (elements > 0) {
+		switch (*p++) {
+		case '/':
+			elements--;
+			path = p;
+			break;
+		case '\0':
+			/* Path is too short, skip it. */
+			return (NULL);
+		}
+	}
+
+	while (*path == '/')
+	       ++path;
+	if (*path == '\0')
+	       return (NULL);
+
+	return (path);
+}
+
 /*
  * Handle --strip-components and any future path-rewriting options.
  * Returns non-zero if the pathname should not be extracted.
@@ -402,22 +382,65 @@
 edit_pathname(struct bsdtar *bsdtar, struct archive_entry *entry)
 {
 	const char *name = archive_entry_pathname(entry);
+#if HAVE_REGEX_H
+	char *subst_name;
+#endif
+	int r;
+
+#if HAVE_REGEX_H
+	r = apply_substitution(bsdtar, name, &subst_name, 0);
+	if (r == -1) {
+		bsdtar_warnc(bsdtar, 0, "Invalid substituion, 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) {
+			bsdtar_warnc(bsdtar, 0, "Invalid substituion, 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) {
+			bsdtar_warnc(bsdtar, 0, "Invalid substituion, 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) {
-		int r = bsdtar->strip_components;
-		const char *p = name;
+		const char *linkname = archive_entry_hardlink(entry);
+
+		name = strip_components(name, bsdtar->strip_components);
+		if (name == NULL)
+			return (1);
 
-		while (r > 0) {
-			switch (*p++) {
-			case '/':
-				r--;
-				name = p;
-				break;
-			case '\0':
-				/* Path is too short, skip it. */
+		if (linkname != NULL) {
+			linkname = strip_components(linkname,
+			    bsdtar->strip_components);
+			if (linkname == NULL)
 				return (1);
-			}
+			archive_entry_copy_hardlink(entry, linkname);
 		}
 	}
 
--- /dev/null
+++ usr.bin/tar/siginfo.c
@@ -0,0 +1,147 @@
+/*-
+ * Copyright 2008 Colin Percival
+ * 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/siginfo.c,v 1.2.2.1 2008/08/10 07:07:00 kientzle Exp $");
+
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "bsdtar.h"
+
+/* Is there a pending SIGINFO or SIGUSR1? */
+static volatile sig_atomic_t siginfo_received = 0;
+
+struct siginfo_data {
+	/* What sort of operation are we doing? */
+	char * oper;
+
+	/* What path are we handling? */
+	char * path;
+
+	/* How large is the archive entry? */
+	int64_t size;
+
+	/* Old signal handlers. */
+#ifdef SIGINFO
+	void (*siginfo_old)(int);
+#endif
+	void (*sigusr1_old)(int);
+};
+
+static void		 siginfo_handler(int sig);
+
+/* Handler for SIGINFO / SIGUSR1. */
+static void
+siginfo_handler(int sig)
+{
+
+	(void)sig; /* UNUSED */
+
+	/* Record that SIGINFO or SIGUSR1 has been received. */
+	siginfo_received = 1;
+}
+
+void
+siginfo_init(struct bsdtar *bsdtar)
+{
+
+	/* Allocate space for internal structure. */
+	if ((bsdtar->siginfo = malloc(sizeof(struct siginfo_data))) == NULL)
+		bsdtar_errc(bsdtar, 1, errno, "malloc failed");
+
+	/* Set the strings to NULL so that free() is safe. */
+	bsdtar->siginfo->path = bsdtar->siginfo->oper = NULL;
+
+#ifdef SIGINFO
+	/* We want to catch SIGINFO, if it exists. */
+	bsdtar->siginfo->siginfo_old = signal(SIGINFO, siginfo_handler);
+#endif
+	/* ... and treat SIGUSR1 the same way as SIGINFO. */
+	bsdtar->siginfo->sigusr1_old = signal(SIGUSR1, siginfo_handler);
+}
+
+void
+siginfo_setinfo(struct bsdtar *bsdtar, const char * oper, const char * path,
+    int64_t size)
+{
+
+	/* Free old operation and path strings. */
+	free(bsdtar->siginfo->oper);
+	free(bsdtar->siginfo->path);
+
+	/* Duplicate strings and store entry size. */
+	if ((bsdtar->siginfo->oper = strdup(oper)) == NULL)
+		bsdtar_errc(bsdtar, 1, errno, "Cannot strdup");
+	if ((bsdtar->siginfo->path = strdup(path)) == NULL)
+		bsdtar_errc(bsdtar, 1, errno, "Cannot strdup");
+	bsdtar->siginfo->size = size;
+}
+
+void
+siginfo_printinfo(struct bsdtar *bsdtar, off_t progress)
+{
+
+	/* If there's a signal to handle and we know what we're doing... */
+	if ((siginfo_received == 1) &&
+	    (bsdtar->siginfo->path != NULL) &&
+	    (bsdtar->siginfo->oper != NULL)) {
+		if (bsdtar->verbose)
+			fprintf(stderr, "\n");
+		if (bsdtar->siginfo->size > 0) {
+			safe_fprintf(stderr, "%s %s (%ju / %" PRId64 ")",
+			    bsdtar->siginfo->oper, bsdtar->siginfo->path,
+			    (uintmax_t)progress, bsdtar->siginfo->size);
+		} else {
+			safe_fprintf(stderr, "%s %s",
+			    bsdtar->siginfo->oper, bsdtar->siginfo->path);
+		}
+		if (!bsdtar->verbose)
+			fprintf(stderr, "\n");
+		siginfo_received = 0;
+	}
+}
+
+void
+siginfo_done(struct bsdtar *bsdtar)
+{
+
+#ifdef SIGINFO
+	/* Restore old SIGINFO handler. */
+	signal(SIGINFO, bsdtar->siginfo->siginfo_old);
+#endif
+	/* And the old SIGUSR1 handler, too. */
+	signal(SIGUSR1, bsdtar->siginfo->sigusr1_old);
+
+	/* Free strings. */
+	free(bsdtar->siginfo->path);
+	free(bsdtar->siginfo->oper);
+
+	/* Free internal data structure. */
+	free(bsdtar->siginfo);
+}
Index: COPYING
===================================================================
RCS file: /home/cvs/src/usr.bin/tar/COPYING,v
retrieving revision 1.3
retrieving revision 1.4
diff -L usr.bin/tar/COPYING -L usr.bin/tar/COPYING -u -r1.3 -r1.4
--- usr.bin/tar/COPYING
+++ usr.bin/tar/COPYING
@@ -1,3 +1,5 @@
+$FreeBSD: src/usr.bin/tar/COPYING,v 1.2.2.1 2008/02/10 23:24:16 kientzle Exp $
+
 All of the C source code and documentation in this package is subject
 to the following:
 
@@ -24,4 +26,37 @@
 (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/COPYING,v 1.2 2007/01/09 08:12:17 kientzle Exp $
+Some of the filename pattern matching code is based on code subject
+to the following license:
+
+/*
+ * Copyright (c) 1989, 1993, 1994
+ *      The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Guido van Rossum.
+ *
+ * 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+ */
Index: matching.c
===================================================================
RCS file: /home/cvs/src/usr.bin/tar/matching.c,v
retrieving revision 1.3
retrieving revision 1.4
diff -L usr.bin/tar/matching.c -L usr.bin/tar/matching.c -u -r1.3 -r1.4
--- usr.bin/tar/matching.c
+++ usr.bin/tar/matching.c
@@ -24,8 +24,7 @@
  */
 
 #include "bsdtar_platform.h"
-__FBSDID("$FreeBSD: src/usr.bin/tar/matching.c,v 1.11 2007/03/11 10:36:42 kientzle Exp $");
-__MBSDID("$MidnightBSD$");
+__FBSDID("$FreeBSD: src/usr.bin/tar/matching.c,v 1.11.2.4 2008/08/27 04:59:00 kientzle Exp $");
 
 #ifdef HAVE_ERRNO_H
 #include <errno.h>
@@ -60,6 +59,7 @@
 static void	initialize_matching(struct bsdtar *);
 static int	match_exclusion(struct match *, const char *pathname);
 static int	match_inclusion(struct match *, const char *pathname);
+static int	pathmatch(const char *p, const char *s);
 
 /*
  * The matching logic here needs to be re-thought.  I started out to
@@ -119,8 +119,6 @@
 	match = malloc(sizeof(*match) + strlen(pattern) + 1);
 	if (match == NULL)
 		bsdtar_errc(bsdtar, 1, errno, "Out of memory");
-	if (pattern[0] == '/')
-		pattern++;
 	strcpy(match->pattern, pattern);
 	/* Both "foo/" and "foo" should match "foo/bar". */
 	if (match->pattern[strlen(match->pattern)-1] == '/')
@@ -158,7 +156,7 @@
 			 */
 			if (match->matches == 0) {
 				match->matches++;
-				matching->inclusions_unmatched_count++;
+				matching->inclusions_unmatched_count--;
 				return (0);
 			}
 			/*
@@ -196,12 +194,12 @@
 	const char *p;
 
 	if (*match->pattern == '*' || *match->pattern == '/')
-		return (bsdtar_fnmatch(match->pattern, pathname) == 0);
+		return (pathmatch(match->pattern, pathname) == 0);
 
 	for (p = pathname; p != NULL; p = strchr(p, '/')) {
 		if (*p == '/')
 			p++;
-		if (bsdtar_fnmatch(match->pattern, p) == 0)
+		if (pathmatch(match->pattern, p) == 0)
 			return (1);
 	}
 	return (0);
@@ -214,7 +212,7 @@
 int
 match_inclusion(struct match *match, const char *pathname)
 {
-	return (bsdtar_fnmatch(match->pattern, pathname) == 0);
+	return (pathmatch(match->pattern, pathname) == 0);
 }
 
 void
@@ -260,6 +258,64 @@
 }
 
 
+int
+unmatched_inclusions_warn(struct bsdtar *bsdtar, const char *msg)
+{
+	struct matching *matching;
+	struct match *p;
+
+	matching = bsdtar->matching;
+	if (matching == NULL)
+		return (0);
+
+	p = matching->inclusions;
+	while (p != NULL) {
+		if (p->matches == 0) {
+			bsdtar->return_value = 1;
+			bsdtar_warnc(bsdtar, 0, "%s: %s",
+			    p->pattern, msg);
+		}
+		p = p->next;
+	}
+	return (matching->inclusions_unmatched_count);
+}
+
+/*
+ * TODO: Extend this so that the following matches work:
+ *     "foo//bar" == "foo/bar"
+ *     "foo/./bar" == "foo/bar"
+ *     "./foo" == "foo"
+ *
+ * The POSIX fnmatch() function doesn't handle any of these, but
+ * all are common situations that arise when paths are generated within
+ * large scripts.  E.g., the following is quite common:
+ *      MYPATH=foo/  TARGET=$MYPATH/bar
+ * It may be worthwhile to edit such paths at write time as well,
+ * especially when such editing may avoid the need for long pathname
+ * extensions.
+ */
+static int
+pathmatch(const char *pattern, const char *string)
+{
+	/*
+	 * Strip leading "./" or ".//" so that, e.g.,
+	 * "foo" matches "./foo".  In particular, this
+	 * opens up an optimization for the writer to
+	 * elide leading "./".
+	 */
+	if (pattern[0] == '.' && pattern[1] == '/') {
+		pattern += 2;
+		while (pattern[0] == '/')
+			++pattern;
+	}
+	if (string[0] == '.' && string[1] == '/') {
+		string += 2;
+		while (string[0] == '/')
+			++string;
+	}
+	return (bsdtar_fnmatch(pattern, string));
+}
+
 
 #if defined(HAVE_FNMATCH) && defined(HAVE_FNM_LEADING_DIR)
 
Index: config_midnightbsd.h
===================================================================
RCS file: /home/cvs/src/usr.bin/tar/config_midnightbsd.h,v
retrieving revision 1.1
retrieving revision 1.2
diff -L usr.bin/tar/config_midnightbsd.h -L usr.bin/tar/config_midnightbsd.h -u -r1.1 -r1.2
--- usr.bin/tar/config_midnightbsd.h
+++ usr.bin/tar/config_midnightbsd.h
@@ -22,21 +22,25 @@
  * (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/config_freebsd.h,v 1.1 2007/03/11 10:36:42 kientzle Exp $
- # $MidnightBSD$
+ * $FreeBSD: src/usr.bin/tar/config_freebsd.h,v 1.1.4.3 2008/08/10 07:35:55 kientzle Exp $
  */
 
 /* A default configuration for FreeBSD, used if there is no config.h. */
 
-#define	PACKAGE_NAME "bsdtar"
+#include <sys/param.h>  /* __FreeBSD_version */
 
+#if __FreeBSD__ > 4
 #define	HAVE_ACL_GET_PERM 0
 #define	HAVE_ACL_GET_PERM_NP 1
 #define	HAVE_ACL_PERMSET_T 1
 #define	HAVE_ACL_USER 1
+#endif
 #undef	HAVE_ATTR_XATTR_H
 #define	HAVE_BZLIB_H 1
 #define	HAVE_CHFLAGS 1
+#define	HAVE_CHROOT 1
+#define	HAVE_DECL_OPTARG 1
+#define	HAVE_DECL_OPTIND 1
 #define	HAVE_DIRENT_D_NAMLEN 1
 #define	HAVE_DIRENT_H 1
 #define	HAVE_D_MD_ORDER 1
@@ -68,9 +72,12 @@
 #define	HAVE_MEMMOVE 1
 #define	HAVE_MEMORY_H 1
 #define	HAVE_MEMSET 1
+#if __FreeBSD_version >= 450002 /* nl_langinfo introduced */
 #define	HAVE_NL_LANGINFO 1
+#endif
 #define	HAVE_PATHS_H 1
 #define	HAVE_PWD_H 1
+#define	HAVE_REGEX_H 1
 #define	HAVE_SETLOCALE 1
 #define	HAVE_STDARG_H 1
 #define	HAVE_STDINT_H 1
@@ -84,7 +91,6 @@
 #define	HAVE_STRRCHR 1
 #undef	HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
 #define	HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC 1
-#define	HAVE_STRUCT_STAT_ST_RDEV 1
 #define	HAVE_SYS_ACL_H 1
 #define	HAVE_SYS_IOCTL_H 1
 #define	HAVE_SYS_PARAM_H 1
Index: bsdtar.h
===================================================================
RCS file: /home/cvs/src/usr.bin/tar/bsdtar.h,v
retrieving revision 1.3
retrieving revision 1.4
diff -L usr.bin/tar/bsdtar.h -L usr.bin/tar/bsdtar.h -u -r1.3 -r1.4
--- usr.bin/tar/bsdtar.h
+++ usr.bin/tar/bsdtar.h
@@ -22,8 +22,7 @@
  * (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.28 2007/05/29 05:39:10 kientzle Exp $
- * $MidnightBSD$
+ * $FreeBSD: src/usr.bin/tar/bsdtar.h,v 1.28.2.6 2008/08/10 07:48:41 kientzle Exp $
  */
 
 #include "bsdtar_platform.h"
@@ -58,6 +57,7 @@
 	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 */
 	char		  option_honor_nodump; /* --nodump */
@@ -65,6 +65,7 @@
 	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 */
@@ -90,17 +91,19 @@
 	 * Data for various subsystems.  Full definitions are located in
 	 * the file where they are used.
 	 */
+	struct archive_entry_linkresolver *resolver;
 	struct archive_dir	*archive_dir;	/* for write.c */
 	struct name_cache	*gname_cache;	/* for write.c */
-	struct links_cache	*links_cache;	/* for write.c */
+	char			*buff;		/* for write.c */
 	struct 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 */
 };
 
 void	bsdtar_errc(struct bsdtar *, int _eval, int _code,
-	    const char *fmt, ...);
-void	bsdtar_strmode(struct archive_entry *entry, char *bp);
+	    const char *fmt, ...) __dead2;
 void	bsdtar_warnc(struct bsdtar *, int _code, const char *fmt, ...);
 void	cleanup_exclusions(struct bsdtar *);
 void	do_chdir(struct bsdtar *);
@@ -115,12 +118,23 @@
 	    int (*process)(struct bsdtar *, const char *));
 void	safe_fprintf(FILE *, const char *fmt, ...);
 void	set_chdir(struct bsdtar *, const char *newdir);
+void	siginfo_init(struct bsdtar *);
+void	siginfo_setinfo(struct bsdtar *, const char * oper,
+	    const char * path, int64_t size);
+void	siginfo_printinfo(struct bsdtar *, off_t progress);
+void	siginfo_done(struct bsdtar *);
 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);
 int	unmatched_inclusions(struct bsdtar *bsdtar);
+int	unmatched_inclusions_warn(struct bsdtar *bsdtar, const char *msg);
 void	usage(struct bsdtar *);
 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
Index: write.c
===================================================================
RCS file: /home/cvs/src/usr.bin/tar/write.c,v
retrieving revision 1.3
retrieving revision 1.4
diff -L usr.bin/tar/write.c -L usr.bin/tar/write.c -u -r1.3 -r1.4
--- usr.bin/tar/write.c
+++ usr.bin/tar/write.c
@@ -24,8 +24,7 @@
  */
 
 #include "bsdtar_platform.h"
-__FBSDID("$FreeBSD: src/usr.bin/tar/write.c,v 1.63 2007/05/29 05:39:10 kientzle Exp $");
-__MBSDID("$MidnightBSD$");
+__FBSDID("$FreeBSD: src/usr.bin/tar/write.c,v 1.63.2.11 2008/11/28 20:13:23 kientzle Exp $");
 
 #ifdef HAVE_SYS_TYPES_H
 #include <sys/types.h>
@@ -63,9 +62,6 @@
 #ifdef HAVE_LINUX_FS_H
 #include <linux/fs.h>	/* for Linux file flags */
 #endif
-#ifdef HAVE_LINUX_EXT2_FS_H
-#include <linux/ext2_fs.h>	/* for Linux file flags */
-#endif
 #ifdef HAVE_PWD_H
 #include <pwd.h>
 #endif
@@ -83,14 +79,14 @@
 #include "bsdtar.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
 
 static const char * const NO_NAME = "(noname)";
 
-/* Initial size of link cache. */
-#define	links_cache_initial_size 1024
-
 struct archive_dir_entry {
 	struct archive_dir_entry	*next;
 	time_t			 mtime_sec;
@@ -102,21 +98,6 @@
 	struct archive_dir_entry *head, *tail;
 };
 
-struct links_cache {
-	unsigned long		  number_entries;
-	size_t			  number_buckets;
-	struct links_entry	**buckets;
-};
-
-struct links_entry {
-	struct links_entry	*next;
-	struct links_entry	*previous;
-	int			 links;
-	dev_t			 dev;
-	ino_t			 ino;
-	char			*name;
-};
-
 struct name_cache {
 	int	probes;
 	int	hits;
@@ -140,13 +121,10 @@
 static int		 copy_file_data(struct bsdtar *bsdtar,
 			     struct archive *a, struct archive *ina);
 static void		 create_cleanup(struct bsdtar *);
-static void		 free_buckets(struct bsdtar *, struct links_cache *);
 static void		 free_cache(struct name_cache *cache);
 static const char *	 lookup_gname(struct bsdtar *bsdtar, gid_t gid);
 static int		 lookup_gname_helper(struct bsdtar *bsdtar,
 			     const char **name, id_t gid);
-static void		 lookup_hardlink(struct bsdtar *,
-			     struct archive_entry *entry, const struct stat *);
 static const char *	 lookup_uname(struct bsdtar *bsdtar, uid_t uid);
 static int		 lookup_uname_helper(struct bsdtar *bsdtar,
 			     const char **name, id_t uid);
@@ -161,8 +139,10 @@
 static void		 write_entry(struct bsdtar *, struct archive *,
 			     const struct stat *, const char *pathname,
 			     const char *accpath);
+static void		 write_entry_backend(struct bsdtar *, struct archive *,
+			     struct archive_entry *);
 static int		 write_file_data(struct bsdtar *, struct archive *,
-			     int fd);
+			     struct archive_entry *, int fd);
 static void		 write_hierarchy(struct bsdtar *, struct archive *,
 			     const char *);
 
@@ -221,6 +201,9 @@
 			archive_write_set_compression_gzip(a);
 			break;
 #endif
+		case 'Z':
+			archive_write_set_compression_compress(a);
+			break;
 		default:
 			bsdtar_errc(bsdtar, 1, 0,
 			    "Unrecognized compression option -%c",
@@ -233,13 +216,6 @@
 		bsdtar_errc(bsdtar, 1, 0, archive_error_string(a));
 
 	write_archive(a, bsdtar);
-
-	if (bsdtar->option_totals) {
-		fprintf(stderr, "Total bytes written: " BSDTAR_FILESIZE_PRINTF "\n",
-		    (BSDTAR_FILESIZE_TYPE)archive_position_compressed(a));
-	}
-
-	archive_write_finish(a);
 }
 
 /*
@@ -328,12 +304,6 @@
 
 	write_archive(a, bsdtar); /* XXX check return val XXX */
 
-	if (bsdtar->option_totals) {
-		fprintf(stderr, "Total bytes written: " BSDTAR_FILESIZE_PRINTF "\n",
-		    (BSDTAR_FILESIZE_TYPE)archive_position_compressed(a));
-	}
-
-	archive_write_finish(a);
 	close(bsdtar->fd);
 	bsdtar->fd = -1;
 }
@@ -414,12 +384,6 @@
 
 	write_archive(a, bsdtar);
 
-	if (bsdtar->option_totals) {
-		fprintf(stderr, "Total bytes written: " BSDTAR_FILESIZE_PRINTF "\n",
-		    (BSDTAR_FILESIZE_TYPE)archive_position_compressed(a));
-	}
-
-	archive_write_finish(a);
 	close(bsdtar->fd);
 	bsdtar->fd = -1;
 
@@ -440,6 +404,19 @@
 write_archive(struct archive *a, struct bsdtar *bsdtar)
 {
 	const char *arg;
+	struct archive_entry *entry, *sparse_entry;
+
+	/* We want to catch SIGINFO and SIGUSR1. */
+	siginfo_init(bsdtar);
+
+	/* Allocate a buffer for file data. */
+	if ((bsdtar->buff = malloc(FILEDATABUFLEN)) == NULL)
+		bsdtar_errc(bsdtar, 1, 0, "cannot allocate memory");
+
+	if ((bsdtar->resolver = archive_entry_linkresolver_new()) == NULL)
+		bsdtar_errc(bsdtar, 1, 0, "cannot create link resolver");
+	archive_entry_linkresolver_set_strategy(bsdtar->resolver,
+	    archive_format(a));
 
 	if (bsdtar->names_from_file != NULL)
 		archive_names_from_file(bsdtar, a);
@@ -455,7 +432,7 @@
 					bsdtar_warnc(bsdtar, 1, 0,
 					    "Missing argument for -C");
 					bsdtar->return_value = 1;
-					return;
+					goto cleanup;
 				}
 			}
 			set_chdir(bsdtar, arg);
@@ -472,11 +449,34 @@
 		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);
+	}
+
 	create_cleanup(bsdtar);
 	if (archive_write_close(a)) {
 		bsdtar_warnc(bsdtar, 0, "%s", archive_error_string(a));
 		bsdtar->return_value = 1;
 	}
+
+cleanup:
+	/* Free file data buffer. */
+	free(bsdtar->buff);
+
+	if (bsdtar->option_totals) {
+		fprintf(stderr, "Total bytes written: " BSDTAR_FILESIZE_PRINTF "\n",
+		    (BSDTAR_FILESIZE_TYPE)archive_position_compressed(a));
+	}
+
+	archive_write_finish(a);
+
+	/* Restore old SIGINFO + SIGUSR1 handlers. */
+	siginfo_done(bsdtar);
 }
 
 /*
@@ -572,6 +572,10 @@
 		if (bsdtar->verbose)
 			safe_fprintf(stderr, "a %s",
 			    archive_entry_pathname(in_entry));
+		siginfo_setinfo(bsdtar, "copying",
+		    archive_entry_pathname(in_entry),
+		    archive_entry_size(in_entry));
+		siginfo_printinfo(bsdtar, 0);
 
 		e = archive_write_header(a, in_entry);
 		if (e != ARCHIVE_OK) {
@@ -585,9 +589,12 @@
 		if (e == ARCHIVE_FATAL)
 			exit(1);
 
-		if (e >= ARCHIVE_WARN)
-			if (copy_file_data(bsdtar, a, ina))
+		if (e >= ARCHIVE_WARN) {
+			if (archive_entry_size(in_entry) == 0)
+				archive_read_data_skip(ina);
+			else if (copy_file_data(bsdtar, a, ina))
 				exit(1);
+		}
 
 		if (bsdtar->verbose)
 			fprintf(stderr, "\n");
@@ -601,18 +608,23 @@
 static int
 copy_file_data(struct bsdtar *bsdtar, struct archive *a, struct archive *ina)
 {
-	char	buff[64*1024];
 	ssize_t	bytes_read;
 	ssize_t	bytes_written;
+	off_t	progress = 0;
 
-	bytes_read = archive_read_data(ina, buff, sizeof(buff));
+	bytes_read = archive_read_data(ina, bsdtar->buff, FILEDATABUFLEN);
 	while (bytes_read > 0) {
-		bytes_written = archive_write_data(a, buff, bytes_read);
+		siginfo_printinfo(bsdtar, progress);
+
+		bytes_written = archive_write_data(a, bsdtar->buff,
+		    bytes_read);
 		if (bytes_written < bytes_read) {
 			bsdtar_warnc(bsdtar, 0, "%s", archive_error_string(a));
 			return (-1);
 		}
-		bytes_read = archive_read_data(ina, buff, sizeof(buff));
+		progress += bytes_written;
+		bytes_read = archive_read_data(ina, bsdtar->buff,
+		    FILEDATABUFLEN);
 	}
 
 	return (0);
@@ -647,8 +659,10 @@
 		const struct stat *st = NULL, *lst = NULL;
 		int descend;
 
-		if (tree_ret == TREE_ERROR_DIR)
+		if (tree_ret == TREE_ERROR_DIR) {
 			bsdtar_warnc(bsdtar, errno, "%s: Couldn't visit directory", name);
+			bsdtar->return_value = 1;
+		}
 		if (tree_ret != TREE_REGULAR)
 			continue;
 		lst = tree_current_lstat(tree);
@@ -775,25 +789,78 @@
 }
 
 /*
+ * 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);
+		if (fd == -1) {
+			if (!bsdtar->verbose)
+				bsdtar_warnc(bsdtar, 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)
+			bsdtar_warnc(bsdtar, 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);
+}
+
+/*
  * Add a single filesystem object to the archive.
  */
 static void
 write_entry(struct bsdtar *bsdtar, struct archive *a, const struct stat *st,
     const char *pathname, const char *accpath)
 {
-	struct archive_entry	*entry;
-	int			 e;
-	int			 fd;
+	struct archive_entry	*entry, *sparse_entry;
 #ifdef __linux
 	int			 r;
 	unsigned long		 stflags;
 #endif
 	static char		 linkbuffer[PATH_MAX+1];
 
-	fd = -1;
 	entry = archive_entry_new();
 
 	archive_entry_set_pathname(entry, pathname);
+	archive_entry_copy_sourcepath(entry, accpath);
 
 	/*
 	 * Rewrite the pathname to be archived.  If rewrite
@@ -809,9 +876,6 @@
 	if (!new_enough(bsdtar, archive_entry_pathname(entry), st))
 		goto abort;
 
-	if (!S_ISDIR(st->st_mode) && (st->st_nlink > 1))
-		lookup_hardlink(bsdtar, entry, st);
-
 	/* Display entry as we process it. This format is required by SUSv2. */
 	if (bsdtar->verbose)
 		safe_fprintf(stderr, "a %s", archive_entry_pathname(entry));
@@ -846,6 +910,7 @@
 #endif
 
 #ifdef __linux
+	int fd;
 	if ((S_ISREG(st->st_mode) || S_ISDIR(st->st_mode)) &&
 	    ((fd = open(accpath, O_RDONLY|O_NONBLOCK)) >= 0) &&
 	    ((r = ioctl(fd, EXT2_IOC_GETFLAGS, &stflags)), close(fd), (fd = -1), r) >= 0 &&
@@ -858,76 +923,50 @@
 	setup_acls(bsdtar, entry, accpath);
 	setup_xattrs(bsdtar, entry, accpath);
 
-	/*
-	 * If it's a regular file (and non-zero in size) make sure we
-	 * can open it before we start to write.  In particular, note
-	 * that we can always archive a zero-length file, even if we
-	 * can't read it.
-	 */
-	if (S_ISREG(st->st_mode) && st->st_size > 0) {
-		fd = open(accpath, O_RDONLY);
-		if (fd < 0) {
-			if (!bsdtar->verbose)
-				bsdtar_warnc(bsdtar, errno, "%s: could not open file", pathname);
-			else
-				fprintf(stderr, ": %s", strerror(errno));
-			goto cleanup;
-		}
-	}
-
 	/* Non-regular files get archived with zero size. */
 	if (!S_ISREG(st->st_mode))
 		archive_entry_set_size(entry, 0);
 
-	e = archive_write_header(a, entry);
-	if (e != ARCHIVE_OK) {
-		if (!bsdtar->verbose)
-			bsdtar_warnc(bsdtar, 0, "%s: %s", pathname,
-			    archive_error_string(a));
-		else
-			fprintf(stderr, ": %s", archive_error_string(a));
-	}
+	/* Record what we're doing, for the benefit of SIGINFO / SIGUSR1. */
+	siginfo_setinfo(bsdtar, "adding", archive_entry_pathname(entry),
+	    archive_entry_size(entry));
+	archive_entry_linkify(bsdtar->resolver, &entry, &sparse_entry);
 
-	if (e == ARCHIVE_FATAL)
-		exit(1);
+	/* Handle SIGINFO / SIGUSR1 request if one was made. */
+	siginfo_printinfo(bsdtar, 0);
 
-	/*
-	 * 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, fd))
-			exit(1);
+	while (entry != NULL) {
+		write_entry_backend(bsdtar, a, entry);
+		archive_entry_free(entry);
+		entry = sparse_entry;
+		sparse_entry = NULL;
+	}
 
 cleanup:
 	if (bsdtar->verbose)
 		fprintf(stderr, "\n");
 
 abort:
-	if (fd >= 0)
-		close(fd);
-
 	if (entry != NULL)
 		archive_entry_free(entry);
 }
 
 
-/* Helper function to copy file to archive, with stack-allocated buffer. */
+/* Helper function to copy file to archive. */
 static int
-write_file_data(struct bsdtar *bsdtar, struct archive *a, int fd)
+write_file_data(struct bsdtar *bsdtar, struct archive *a,
+    struct archive_entry *entry, int fd)
 {
-	char	buff[64*1024];
 	ssize_t	bytes_read;
 	ssize_t	bytes_written;
+	off_t	progress = 0;
 
-	/* XXX TODO: Allocate buffer on heap and store pointer to
-	 * it in bsdtar structure; arrange cleanup as well. XXX */
-
-	bytes_read = read(fd, buff, sizeof(buff));
+	bytes_read = read(fd, bsdtar->buff, FILEDATABUFLEN);
 	while (bytes_read > 0) {
-		bytes_written = archive_write_data(a, buff, bytes_read);
+		siginfo_printinfo(bsdtar, progress);
+
+		bytes_written = archive_write_data(a, bsdtar->buff,
+		    FILEDATABUFLEN);
 		if (bytes_written < 0) {
 			/* Write failed; this is bad */
 			bsdtar_warnc(bsdtar, 0, "%s", archive_error_string(a));
@@ -936,10 +975,12 @@
 		if (bytes_written < bytes_read) {
 			/* Write was truncated; warn but continue. */
 			bsdtar_warnc(bsdtar, 0,
-			    "Truncated write; file may have grown while being archived.");
+			    "%s: Truncated write; file may have grown while being archived.",
+			    archive_entry_pathname(entry));
 			return (0);
 		}
-		bytes_read = read(fd, buff, sizeof(buff));
+		progress += bytes_written;
+		bytes_read = read(fd, bsdtar->buff, FILEDATABUFLEN);
 	}
 	return 0;
 }
@@ -948,169 +989,12 @@
 static void
 create_cleanup(struct bsdtar *bsdtar)
 {
-	/* Free inode->pathname map used for hardlink detection. */
-	if (bsdtar->links_cache != NULL) {
-		free_buckets(bsdtar, bsdtar->links_cache);
-		free(bsdtar->links_cache);
-		bsdtar->links_cache = NULL;
-	}
-
 	free_cache(bsdtar->uname_cache);
 	bsdtar->uname_cache = NULL;
 	free_cache(bsdtar->gname_cache);
 	bsdtar->gname_cache = NULL;
 }
 
-
-static void
-free_buckets(struct bsdtar *bsdtar, struct links_cache *links_cache)
-{
-	size_t i;
-
-	if (links_cache->buckets == NULL)
-		return;
-
-	for (i = 0; i < links_cache->number_buckets; i++) {
-		while (links_cache->buckets[i] != NULL) {
-			struct links_entry *lp = links_cache->buckets[i]->next;
-			if (bsdtar->option_warn_links)
-				bsdtar_warnc(bsdtar, 0, "Missing links to %s",
-				    links_cache->buckets[i]->name);
-			if (links_cache->buckets[i]->name != NULL)
-				free(links_cache->buckets[i]->name);
-			free(links_cache->buckets[i]);
-			links_cache->buckets[i] = lp;
-		}
-	}
-	free(links_cache->buckets);
-	links_cache->buckets = NULL;
-}
-
-static void
-lookup_hardlink(struct bsdtar *bsdtar, struct archive_entry *entry,
-    const struct stat *st)
-{
-	struct links_cache	*links_cache;
-	struct links_entry	*le, **new_buckets;
-	int			 hash;
-	size_t			 i, new_size;
-
-	/* If necessary, initialize the links cache. */
-	links_cache = bsdtar->links_cache;
-	if (links_cache == NULL) {
-		bsdtar->links_cache = malloc(sizeof(struct links_cache));
-		if (bsdtar->links_cache == NULL)
-			bsdtar_errc(bsdtar, 1, ENOMEM,
-			    "No memory for hardlink detection.");
-		links_cache = bsdtar->links_cache;
-		memset(links_cache, 0, sizeof(struct links_cache));
-		links_cache->number_buckets = links_cache_initial_size;
-		links_cache->buckets = malloc(links_cache->number_buckets *
-		    sizeof(links_cache->buckets[0]));
-		if (links_cache->buckets == NULL) {
-			bsdtar_errc(bsdtar, 1, ENOMEM,
-			    "No memory for hardlink detection.");
-		}
-		for (i = 0; i < links_cache->number_buckets; i++)
-			links_cache->buckets[i] = NULL;
-	}
-
-	/* If the links cache overflowed and got flushed, don't bother. */
-	if (links_cache->buckets == NULL)
-		return;
-
-	/* If the links cache is getting too full, enlarge the hash table. */
-	if (links_cache->number_entries > links_cache->number_buckets * 2)
-	{
-		new_size = links_cache->number_buckets * 2;
-		new_buckets = malloc(new_size * sizeof(struct links_entry *));
-
-		if (new_buckets != NULL) {
-			memset(new_buckets, 0,
-			    new_size * sizeof(struct links_entry *));
-			for (i = 0; i < links_cache->number_buckets; i++) {
-				while (links_cache->buckets[i] != NULL) {
-					/* Remove entry from old bucket. */
-					le = links_cache->buckets[i];
-					links_cache->buckets[i] = le->next;
-
-					/* Add entry to new bucket. */
-					hash = (le->dev ^ le->ino) % new_size;
-
-					if (new_buckets[hash] != NULL)
-						new_buckets[hash]->previous =
-						    le;
-					le->next = new_buckets[hash];
-					le->previous = NULL;
-					new_buckets[hash] = le;
-				}
-			}
-			free(links_cache->buckets);
-			links_cache->buckets = new_buckets;
-			links_cache->number_buckets = new_size;
-		} else {
-			free_buckets(bsdtar, links_cache);
-			bsdtar_warnc(bsdtar, ENOMEM,
-			    "No more memory for recording hard links");
-			bsdtar_warnc(bsdtar, 0,
-			    "Remaining links will be dumped as full files");
-		}
-	}
-
-	/* Try to locate this entry in the links cache. */
-	hash = ( st->st_dev ^ st->st_ino ) % links_cache->number_buckets;
-	for (le = links_cache->buckets[hash]; le != NULL; le = le->next) {
-		if (le->dev == st->st_dev && le->ino == st->st_ino) {
-			archive_entry_copy_hardlink(entry, le->name);
-
-			/*
-			 * Decrement link count each time and release
-			 * the entry if it hits zero.  This saves
-			 * memory and is necessary for proper -l
-			 * implementation.
-			 */
-			if (--le->links <= 0) {
-				if (le->previous != NULL)
-					le->previous->next = le->next;
-				if (le->next != NULL)
-					le->next->previous = le->previous;
-				if (le->name != NULL)
-					free(le->name);
-				if (links_cache->buckets[hash] == le)
-					links_cache->buckets[hash] = le->next;
-				links_cache->number_entries--;
-				free(le);
-			}
-
-			return;
-		}
-	}
-
-	/* Add this entry to the links cache. */
-	le = malloc(sizeof(struct links_entry));
-	if (le != NULL)
-		le->name = strdup(archive_entry_pathname(entry));
-	if ((le == NULL) || (le->name == NULL)) {
-		free_buckets(bsdtar, links_cache);
-		bsdtar_warnc(bsdtar, ENOMEM,
-		    "No more memory for recording hard links");
-		bsdtar_warnc(bsdtar, 0,
-		    "Remaining hard links will be dumped as full files");
-		if (le != NULL)
-			free(le);
-		return;
-	}
-	if (links_cache->buckets[hash] != NULL)
-		links_cache->buckets[hash]->previous = le;
-	links_cache->number_entries++;
-	le->next = links_cache->buckets[hash];
-	le->previous = NULL;
-	links_cache->buckets[hash] = le;
-	le->dev = st->st_dev;
-	le->ino = st->st_ino;
-	le->links = st->st_nlink - 1;
-}
-
 #ifdef HAVE_POSIX_ACL
 static void		setup_acl(struct bsdtar *bsdtar,
 			     struct archive_entry *entry, const char *accpath,
@@ -1532,7 +1416,7 @@
 {
 	struct stat s;
 
-	if (*bsdtar->argv == NULL)
+	if (*bsdtar->argv == NULL && bsdtar->names_from_file == NULL)
 		bsdtar_errc(bsdtar, 1, 0, "no files or directories specified");
 	if (bsdtar->filename == NULL)
 		bsdtar_errc(bsdtar, 1, 0, "Cannot append to stdout.");
@@ -1544,7 +1428,7 @@
 	if (stat(bsdtar->filename, &s) != 0)
 		return;
 
-	if (!S_ISREG(s.st_mode))
+	if (!S_ISREG(s.st_mode) && !S_ISBLK(s.st_mode))
 		bsdtar_errc(bsdtar, 1, 0,
 		    "Cannot append to %s: not a regular file.",
 		    bsdtar->filename);
--- /dev/null
+++ usr.bin/tar/subst.c
@@ -0,0 +1,287 @@
+/*-
+ * 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.2.1 2008/08/10 07:35:55 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
+
+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)
+		bsdtar_errc(bsdtar, 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)
+		bsdtar_errc(bsdtar, 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')
+		bsdtar_errc(bsdtar, 1, 0, "Empty replacement string");
+	end_pattern = strchr(rule_text + 1, *rule_text);
+	if (end_pattern == NULL)
+		bsdtar_errc(bsdtar, 1, 0, "Invalid replacement string");
+
+	pattern = malloc(end_pattern - rule_text);
+	if (pattern == NULL)
+		bsdtar_errc(bsdtar, 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));
+		bsdtar_errc(bsdtar, 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)
+		bsdtar_errc(bsdtar, 1, 0, "Invalid replacement string");
+
+	rule->result = malloc(end_pattern - start_subst + 1);
+	if (rule->result == NULL)
+		bsdtar_errc(bsdtar, 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:
+			bsdtar_errc(bsdtar, 1, 0, "Invalid replacement flag %c", *end_pattern);
+		}
+	}
+}
+
+static void
+realloc_strncat(struct bsdtar *bsdtar, 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)
+		bsdtar_errc(bsdtar, 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(struct bsdtar *bsdtar, 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)
+		bsdtar_errc(bsdtar, 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))
+			break;
+
+		got_match = 1;
+		print_match |= rule->print;
+		realloc_strncat(bsdtar, result, name, matches[0].rm_so);
+
+		for (i = 0, j = 0; rule->result[i] != '\0'; ++i) {
+			if (rule->result[i] == '~') {
+				realloc_strncat(bsdtar, result, rule->result + j, i - j);
+				realloc_strncat(bsdtar, 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(bsdtar, 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(bsdtar, 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(bsdtar, 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(bsdtar, result, rule->result + j);
+
+		name += matches[0].rm_eo;
+
+		if (!rule->global)
+			break;
+	}
+
+	if (got_match)
+		realloc_strcat(bsdtar, 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 */
Index: bsdtar.1
===================================================================
RCS file: /home/cvs/src/usr.bin/tar/bsdtar.1,v
retrieving revision 1.3
retrieving revision 1.4
diff -L usr.bin/tar/bsdtar.1 -L usr.bin/tar/bsdtar.1 -u -r1.3 -r1.4
--- usr.bin/tar/bsdtar.1
+++ usr.bin/tar/bsdtar.1
@@ -22,10 +22,9 @@
 .\" 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.35 2007/05/29 05:39:10 kientzle Exp $
-.\" $MidnightBSD$
+.\" $FreeBSD: src/usr.bin/tar/bsdtar.1,v 1.35.2.8 2008/08/10 07:47:34 kientzle Exp $
 .\"
-.Dd April 13, 2004
+.Dd May 15, 2008
 .Dt BSDTAR 1
 .Os
 .Sh NAME
@@ -148,13 +147,19 @@
 .It Fl -check-links ( Fl W Cm check-links )
 (c and r modes only)
 Issue a warning message unless all links to each file are archived.
+.It Fl -chroot ( Fl W Cm 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 ( Fl W Cm exclude Ns = Ns 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 ( Fl W Cm format Ns = Ns Ar format )
-(c mode only)
+(c, r, u mode only)
 Use the specified format for the created archive.
 Supported formats include
 .Dq cpio ,
@@ -165,6 +170,8 @@
 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
@@ -175,15 +182,6 @@
 .Fx ,
 the default tape device is
 .Pa /dev/sa0 . )
-.It Fl -fast-read ( Fl W Cm 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 H
 (c and r mode only)
 Symbolic links named on the command line will be followed; the
@@ -227,22 +225,19 @@
 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 ( Fl W Cm 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
-If
-.Ev POSIXLY_CORRECT
-is specified in the environment, this is a synonym for the
+This is a synonym for the
 .Fl -check-links
 option.
-Otherwise, an error will be displayed.
-Users who desire behavior compatible with GNU tar should use
-the
-.Fl -one-file-system
-option instead.
 .It Fl m
 (x mode only)
 Do not extract modification time.
@@ -283,6 +278,10 @@
 .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
@@ -290,7 +289,7 @@
 In list (-t) mode, the file listing will be written to stderr rather than
 the usual stdout.
 .It Fl o
-(x mode only)
+(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
@@ -299,6 +298,10 @@
 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 ( Fl W Cm one-file-system )
 (c, r, and u modes)
 Do not cross mount points.
@@ -327,12 +330,43 @@
 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 ( Fl W Cm strip-components Ns = Ns Ar count )
 (x and t 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 /old/new/[gps].
+old is a basic regular expression.
+If it doesn't apply, the pattern is skipped.
+new is the replacement string of the matched part.
+~ 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
@@ -378,6 +412,12 @@
 Additional
 .Fl v
 options will provide additional detail.
+.It Fl -version
+Print version of
+.Nm
+and
+.Nm libarchive ,
+and exit.
 .It Fl W Ar longopt=value
 Long options (preceded by
 .Fl - )
@@ -413,6 +453,15 @@
 .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
@@ -423,11 +472,6 @@
 See
 .Xr environ 7
 for more information.
-.It Ev POSIXLY_CORRECT
-If this environment variable is defined, the
-.Fl l
-option will be interpreted in accordance with
-.St -p1003.1-96 .
 .It Ev TAPE
 The default tape device.
 The
@@ -500,6 +544,17 @@
 .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
@@ -641,6 +696,7 @@
 components, or symlinks to other directories.
 .Sh SEE ALSO
 .Xr bzip2 1 ,
+.Xr compress 1 ,
 .Xr cpio 1 ,
 .Xr gzip 1 ,
 .Xr mt 1 ,
@@ -665,7 +721,7 @@
 .Sh HISTORY
 A
 .Nm tar
-command appeared in Seventh Edition Unix.
+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
@@ -682,13 +738,16 @@
 .Xr libarchive 3
 library.
 .Sh BUGS
-POSIX and GNU violently disagree about the meaning of the
+This program follows
+.St -p1003.1-96
+for the definition of the
 .Fl l
 option.
-Because of the potential for disaster if someone expects
-one behavior and gets the other, the
+Note that GNU tar prior to version 1.15 treated
 .Fl l
-option is deliberately broken in this implementation.
+as a synonym for the
+.Fl -one-file-system
+option.
 .Pp
 The
 .Fl C Pa dir
Index: bsdtar.c
===================================================================
RCS file: /home/cvs/src/usr.bin/tar/bsdtar.c,v
retrieving revision 1.3
retrieving revision 1.4
diff -L usr.bin/tar/bsdtar.c -L usr.bin/tar/bsdtar.c -u -r1.3 -r1.4
--- usr.bin/tar/bsdtar.c
+++ usr.bin/tar/bsdtar.c
@@ -24,8 +24,7 @@
  */
 
 #include "bsdtar_platform.h"
-__FBSDID("$FreeBSD: src/usr.bin/tar/bsdtar.c,v 1.77 2007/09/09 00:07:18 kientzle Exp $");
-__MBSDID("$MidnightBSD$");
+__FBSDID("$FreeBSD: src/usr.bin/tar/bsdtar.c,v 1.77.2.13 2008/08/25 02:14:52 kientzle Exp $");
 
 #ifdef HAVE_SYS_PARAM_H
 #include <sys/param.h>
@@ -79,6 +78,14 @@
 
 #include "bsdtar.h"
 
+#if !HAVE_DECL_OPTARG
+extern int optarg;
+#endif
+
+#if !HAVE_DECL_OPTIND
+extern int optind;
+#endif
+
 /*
  * Per POSIX.1-1988, tar defaults to reading/writing archives to/from
  * the default tape device for the system.  Pick something reasonable here.
@@ -111,7 +118,7 @@
  * non-option.  Otherwise, GNU getopt() permutes the arguments and
  * screws up -C processing.
  */
-static const char *tar_opts = "+Bb:C:cf:HhI:jkLlmnOoPprtT:UuvW:wX:xyZz";
+static const char *tar_opts = "+Bb:C:cf:HhI:jkLlmnOoPpqrts:ST:UuvW:wX:xyZz";
 
 /*
  * Most of these long options are deliberately not documented.  They
@@ -129,12 +136,13 @@
 
 /* Fake short equivalents for long options that otherwise lack them. */
 enum {
-	OPTION_CHECK_LINKS=1,
+	OPTION_CHECK_LINKS = 1,
+	OPTION_CHROOT,
 	OPTION_EXCLUDE,
-	OPTION_FAST_READ,
 	OPTION_FORMAT,
 	OPTION_HELP,
 	OPTION_INCLUDE,
+	OPTION_KEEP_NEWER_FILES,
 	OPTION_NEWER_CTIME,
 	OPTION_NEWER_CTIME_THAN,
 	OPTION_NEWER_MTIME,
@@ -143,6 +151,7 @@
 	OPTION_NO_SAME_OWNER,
 	OPTION_NO_SAME_PERMISSIONS,
 	OPTION_NULL,
+	OPTION_NUMERIC_OWNER,
 	OPTION_ONE_FILE_SYSTEM,
 	OPTION_POSIX,
 	OPTION_STRIP_COMPONENTS,
@@ -164,6 +173,8 @@
 	{ "bzip2",              no_argument,       NULL, 'j' },
 	{ "cd",                 required_argument, NULL, 'C' },
 	{ "check-links",        no_argument,       NULL, OPTION_CHECK_LINKS },
+	{ "chroot",             no_argument,       NULL, OPTION_CHROOT },
+	{ "compress",           no_argument,       NULL, 'Z' },
 	{ "confirmation",       no_argument,       NULL, 'w' },
 	{ "create",             no_argument,       NULL, 'c' },
 	{ "dereference",	no_argument,	   NULL, 'L' },
@@ -171,7 +182,7 @@
 	{ "exclude",            required_argument, NULL, OPTION_EXCLUDE },
 	{ "exclude-from",       required_argument, NULL, 'X' },
 	{ "extract",            no_argument,       NULL, 'x' },
-	{ "fast-read",          no_argument,       NULL, OPTION_FAST_READ },
+	{ "fast-read",          no_argument,       NULL, 'q' },
 	{ "file",               required_argument, NULL, 'f' },
 	{ "files-from",         required_argument, NULL, 'T' },
 	{ "format",             required_argument, NULL, OPTION_FORMAT },
@@ -180,6 +191,8 @@
 	{ "help",               no_argument,       NULL, OPTION_HELP },
 	{ "include",            required_argument, NULL, OPTION_INCLUDE },
 	{ "interactive",        no_argument,       NULL, 'w' },
+	{ "insecure",           no_argument,       NULL, 'P' },
+	{ "keep-newer-files",   no_argument,       NULL, OPTION_KEEP_NEWER_FILES },
 	{ "keep-old-files",     no_argument,       NULL, 'k' },
 	{ "list",               no_argument,       NULL, 't' },
 	{ "modification-time",  no_argument,       NULL, 'm' },
@@ -195,6 +208,7 @@
 	{ "no-same-owner",	no_argument,	   NULL, OPTION_NO_SAME_OWNER },
 	{ "no-same-permissions",no_argument,	   NULL, OPTION_NO_SAME_PERMISSIONS },
 	{ "null",		no_argument,	   NULL, OPTION_NULL },
+	{ "numeric-owner",	no_argument,	   NULL, OPTION_NUMERIC_OWNER },
 	{ "one-file-system",	no_argument,	   NULL, OPTION_ONE_FILE_SYSTEM },
 	{ "posix",		no_argument,	   NULL, OPTION_POSIX },
 	{ "preserve-permissions", no_argument,     NULL, 'p' },
@@ -203,6 +217,7 @@
 	{ "strip-components",	required_argument, NULL, OPTION_STRIP_COMPONENTS },
 	{ "to-stdout",          no_argument,       NULL, 'O' },
 	{ "totals",		no_argument,       NULL, OPTION_TOTALS },
+	{ "uncompress",         no_argument,       NULL, 'Z' },
 	{ "unlink",		no_argument,       NULL, 'U' },
 	{ "unlink-first",	no_argument,       NULL, 'U' },
 	{ "update",             no_argument,       NULL, 'u' },
@@ -314,6 +329,9 @@
 		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 (exclude(bsdtar, optarg))
 				bsdtar_errc(bsdtar, 1, 0,
@@ -327,9 +345,6 @@
 			if (strcmp(bsdtar->filename, "-") == 0)
 				bsdtar->filename = NULL;
 			break;
-		case OPTION_FAST_READ: /* GNU tar */
-			bsdtar->option_fast_read = 1;
-			break;
 		case 'H': /* BSD convention */
 			bsdtar->symlink_mode = 'H';
 			break;
@@ -381,22 +396,15 @@
 		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 conflict badly here */
-			if (getenv("POSIXLY_CORRECT") != NULL) {
-				/* User has asked for POSIX/SUS behavior. */
-				bsdtar->option_warn_links = 1;
-			} else {
-				fprintf(stderr,
-"Error: -l has different behaviors in different tar programs.\n");
-				fprintf(stderr,
-"  For the GNU behavior, use --one-file-system instead.\n");
-				fprintf(stderr,
-"  For the POSIX behavior, use --check-links instead.\n");
-				usage(bsdtar);
-			}
+	        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 'm': /* SUSv2 */
 			bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_TIME;
@@ -454,6 +462,9 @@
 		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;
@@ -487,9 +498,23 @@
 		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, optarg);
+#else
+			bsdtar_warnc(bsdtar, 0, "-s is not supported by this version of bsdtar");
+			usage(bsdtar);
+#endif
+			break;
 		case OPTION_STRIP_COMPONENTS: /* GNU tar 1.15 */
 			bsdtar->strip_components = atoi(optarg);
 			break;
@@ -628,17 +653,13 @@
 		only_mode(bsdtar, "--check-links", "cr");
 
 	/* Check other parameters only permitted in certain modes. */
-	if (bsdtar->create_compression == 'Z' && bsdtar->mode == 'c') {
-		bsdtar_warnc(bsdtar, 0, ".Z compression not supported");
-		usage(bsdtar);
-	}
 	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", "c");
+		only_mode(bsdtar, "--format", "cru");
 	if (bsdtar->symlink_mode != '\0') {
 		strcpy(buff, "-?");
 		buff[1] = bsdtar->symlink_mode;
@@ -669,6 +690,10 @@
 	}
 
 	cleanup_exclusions(bsdtar);
+#if HAVE_REGEX_H
+	cleanup_substitution(bsdtar);
+#endif
+
 	if (bsdtar->return_value != 0)
 		bsdtar_warnc(bsdtar, 0,
 		    "Error exit delayed from previous errors.");
@@ -721,8 +746,8 @@
 	const char *p;
 	char *src, *dest;
 
-	if (src_argv[0] == NULL ||
-	    src_argv[1] == NULL || src_argv[1][0] == '-')
+	if (src_argv[0] == NULL || src_argv[1] == NULL ||
+	    src_argv[1][0] == '-' || src_argv[1][0] == '\0')
 		return (src_argv);
 
 	*argc += strlen(src_argv[1]) - 1;
@@ -785,8 +810,10 @@
 static void
 version(void)
 {
-	printf("bsdtar %s - %s\n", PACKAGE_VERSION, archive_version());
-	exit(1);
+	printf("bsdtar %s - %s\n",
+	    BSDTAR_VERSION_STRING,
+	    archive_version());
+	exit(0);
 }
 
 static const char *long_help_msg =
Index: Makefile
===================================================================
RCS file: /home/cvs/src/usr.bin/tar/Makefile,v
retrieving revision 1.3
retrieving revision 1.4
diff -L usr.bin/tar/Makefile -L usr.bin/tar/Makefile -u -r1.3 -r1.4
--- usr.bin/tar/Makefile
+++ usr.bin/tar/Makefile
@@ -1,13 +1,12 @@
-# $FreeBSD: src/usr.bin/tar/Makefile,v 1.32 2007/07/20 01:24:49 kientzle Exp $
-# $MidnightBSD$
+# $FreeBSD: src/usr.bin/tar/Makefile,v 1.32.2.4 2008/08/25 02:18:12 kientzle Exp $
 
 PROG=	bsdtar
-VERSION=	2.2.5
-SRCS=	bsdtar.c getdate.y matching.c read.c tree.c util.c write.c
+BSDTAR_VERSION_STRING=2.5.5
+SRCS=	bsdtar.c getdate.y matching.c read.c siginfo.c subst.c tree.c util.c write.c
 WARNS?=	5
 DPADD=	${LIBARCHIVE} ${LIBBZ2} ${LIBZ}
 LDADD=	-larchive -lbz2 -lz
-CFLAGS+=	-DPACKAGE_VERSION=\"${VERSION}\"
+CFLAGS+=	-DBSDTAR_VERSION_STRING=\"${BSDTAR_VERSION_STRING}\"
 CFLAGS+=	-DPLATFORM_CONFIG_H=\"config_midnightbsd.h\"
 CFLAGS+=	-I${.CURDIR}
 SYMLINKS=	bsdtar ${BINDIR}/tar
Index: tree.c
===================================================================
RCS file: /home/cvs/src/usr.bin/tar/tree.c,v
retrieving revision 1.3
retrieving revision 1.4
diff -L usr.bin/tar/tree.c -L usr.bin/tar/tree.c -u -r1.3 -r1.4
--- usr.bin/tar/tree.c
+++ usr.bin/tar/tree.c
@@ -44,7 +44,6 @@
  */
 #include "bsdtar_platform.h"
 __FBSDID("$FreeBSD: src/usr.bin/tar/tree.c,v 1.8 2007/03/11 10:36:42 kientzle Exp $");
-__MBSDID("$MidnightBSD$");
 
 #ifdef HAVE_SYS_STAT_H
 #include <sys/stat.h>
Index: read.c
===================================================================
RCS file: /home/cvs/src/usr.bin/tar/read.c,v
retrieving revision 1.3
retrieving revision 1.4
diff -L usr.bin/tar/read.c -L usr.bin/tar/read.c -u -r1.3 -r1.4
--- usr.bin/tar/read.c
+++ usr.bin/tar/read.c
@@ -24,8 +24,7 @@
  */
 
 #include "bsdtar_platform.h"
-__FBSDID("$FreeBSD: src/usr.bin/tar/read.c,v 1.34 2007/07/20 01:24:49 kientzle Exp $");
-__MBSDID("$MidnightBSD$");
+__FBSDID("$FreeBSD: src/usr.bin/tar/read.c,v 1.34.2.5 2008/08/27 04:59:00 kientzle Exp $");
 
 #ifdef HAVE_SYS_TYPES_H
 #include <sys/types.h>
@@ -78,12 +77,28 @@
 tar_mode_t(struct bsdtar *bsdtar)
 {
 	read_archive(bsdtar, 't');
+	unmatched_inclusions_warn(bsdtar, "Not found in archive");
 }
 
 void
 tar_mode_x(struct bsdtar *bsdtar)
 {
+	/* We want to catch SIGINFO and SIGUSR1. */
+	siginfo_init(bsdtar);
+
 	read_archive(bsdtar, 'x');
+
+	unmatched_inclusions_warn(bsdtar, "Not found in archive");
+	/* Restore old SIGINFO + SIGUSR1 handlers. */
+	siginfo_done(bsdtar);
+}
+
+static void
+progress_func(void * cookie)
+{
+	struct bsdtar * bsdtar = cookie;
+
+	siginfo_printinfo(bsdtar, 0);
 }
 
 /*
@@ -119,6 +134,23 @@
 		    archive_error_string(a));
 
 	do_chdir(bsdtar);
+
+	if (mode == 'x') {
+		/* Set an extract callback so that we can handle SIGINFO. */
+		archive_read_extract_set_progress_callback(a, progress_func,
+		    bsdtar);
+	}
+
+	if (mode == 'x' && bsdtar->option_chroot) {
+#if HAVE_CHROOT
+		if (chroot(".") != 0)
+			bsdtar_errc(bsdtar, 1, errno, "Can't chroot to \".\"");
+#else
+		bsdtar_errc(bsdtar, 1, 0,
+		    "chroot isn't supported on this platform");
+#endif
+	}
+
 	for (;;) {
 		/* Support --fast-read option */
 		if (bsdtar->option_fast_read &&
@@ -140,6 +172,11 @@
 		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.
 		 */
@@ -173,22 +210,17 @@
 		if (excluded(bsdtar, archive_entry_pathname(entry)))
 			continue; /* Excluded by a pattern test. */
 
-		/*
-		 * Modify the pathname as requested by the user.  We
-		 * do this for -t as well to give users a way to
-		 * preview the effects of their rewrites.  We also do
-		 * this before extraction security checks (including
-		 * leading '/' removal).  Note that some rewrite
-		 * failures prevent extraction.
-		 */
-		if (edit_pathname(bsdtar, entry))
-			continue; /* Excluded by a rewrite failure. */
-
 		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));
@@ -215,6 +247,10 @@
 			}
 			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;
@@ -228,6 +264,12 @@
 				    archive_entry_pathname(entry));
 				fflush(stderr);
 			}
+
+			/* Tell the SIGINFO-handler code what we're doing. */
+			siginfo_setinfo(bsdtar, "extracting",
+			    archive_entry_pathname(entry), 0);
+			siginfo_printinfo(bsdtar, 0);
+
 			if (bsdtar->option_stdout)
 				r = archive_read_data_into_fd(a, 1);
 			else
@@ -292,8 +334,9 @@
 	}
 	if (!now)
 		time(&now);
-	bsdtar_strmode(entry, tmp);
-	fprintf(out, "%s %d ", tmp, (int)(st->st_nlink));
+	fprintf(out, "%s %d ",
+	    archive_entry_strmode(entry),
+	    (int)(st->st_nlink));
 
 	/* Use uname if it's present, else uid. */
 	p = archive_entry_uname(entry);
@@ -344,7 +387,7 @@
 	if (abs(tim - now) > (365/2)*86400)
 		fmt = bsdtar->day_first ? "%e %b  %Y" : "%b %e  %Y";
 	else
-		fmt = bsdtar->day_first ? "%e %b %R" : "%b %e %R";
+		fmt = bsdtar->day_first ? "%e %b %H:%M" : "%b %e %H:%M";
 	strftime(tmp, sizeof(tmp), fmt, localtime(&tim));
 	fprintf(out, " %s ", tmp);
 	safe_fprintf(out, "%s", archive_entry_pathname(entry));
Index: bsdtar_platform.h
===================================================================
RCS file: /home/cvs/src/usr.bin/tar/bsdtar_platform.h,v
retrieving revision 1.3
retrieving revision 1.4
diff -L usr.bin/tar/bsdtar_platform.h -L usr.bin/tar/bsdtar_platform.h -u -r1.3 -r1.4
--- usr.bin/tar/bsdtar_platform.h
+++ usr.bin/tar/bsdtar_platform.h
@@ -22,8 +22,7 @@
  * (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.24 2007/04/12 04:45:32 kientzle Exp $
- * $MidnightBSD$
+ * $FreeBSD: src/usr.bin/tar/bsdtar_platform.h,v 1.24.2.1 2008/02/10 23:24:16 kientzle Exp $
  */
 
 /*
@@ -50,7 +49,8 @@
 #ifdef __FreeBSD__
 #include <sys/cdefs.h>  /* For __FBSDID */
 #else
-#define	__FBSDID(a)     /* null */
+/* Just leaving this macro replacement empty leads to a dangling semicolon. */
+#define	__FBSDID(a)     struct _undefined_hack
 #endif
 
 #ifdef HAVE_LIBARCHIVE
--- /dev/null
+++ usr.bin/tar/test/test_version.c
@@ -0,0 +1,93 @@
+/*-
+ * 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.2.1 2008/08/10 06:53:28 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: %s", p);
+	assertEqualMem(q, "bsdtar ", 7);
+	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("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("Version: %s", p);
+	assert(*q == ' ');
+	++q; --s;
+	/* Separator. */
+	failure("Version: %s", p);
+	assertEqualMem(q, "- ", 2);
+	q += 2; s -= 2;
+	/* libarchive name and version number */
+	failure("Version: %s", p);
+	assert(s > 11);
+	failure("Version: %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 a newline. */
+	assert(s >= 1);
+	assertEqualMem(q, "\n", 1);
+	free(p);
+}
--- /dev/null
+++ usr.bin/tar/test/test_copy.c
@@ -0,0 +1,329 @@
+/*-
+ * 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.2.2.3 2008/08/25 02:01:47 kientzle Exp $");
+
+static void
+create_tree(void)
+{
+	char buff[260];
+	char buff2[260];
+	int i;
+	int fd;
+
+	assertEqualInt(0, mkdir("original", 0775));
+	chdir("original");
+	assertEqualInt(0, mkdir("f", 0775));
+	assertEqualInt(0, mkdir("l", 0775));
+	assertEqualInt(0, mkdir("m", 0775));
+	assertEqualInt(0, mkdir("s", 0775));
+	assertEqualInt(0, mkdir("d", 0775));
+
+	for (i = 0; i < 200; i++) {
+		buff[0] = 'f';
+		buff[1] = '/';
+		/* Create a file named "f/abcdef..." */
+		buff[i + 2] = 'a' + (i % 26);
+		buff[i + 3] = '\0';
+		fd = open(buff, O_CREAT | O_WRONLY, 0644);
+		assert(fd >= 0);
+		assertEqualInt(i + 3, write(fd, buff, strlen(buff)));
+		close(fd);
+
+		/* Create a link named "l/abcdef..." to the above. */
+		strcpy(buff2, buff);
+		buff2[0] = 'l';
+		assertEqualInt(0, link(buff, buff2));
+
+		/* Create a link named "m/abcdef..." to the above. */
+		strcpy(buff2, buff);
+		buff2[0] = 'm';
+		assertEqualInt(0, link(buff, buff2));
+
+		/* Create a symlink named "s/abcdef..." to the above. */
+		strcpy(buff2 + 3, buff);
+		buff[0] = 's';
+		buff2[0] = '.';
+		buff2[1] = '.';
+		buff2[2] = '/';
+		assertEqualInt(0, symlink(buff2, buff));
+
+		/* Create a dir named "d/abcdef...". */
+		buff[0] = 'd';
+		assertEqualInt(0, mkdir(buff, 0775));
+	}
+
+	chdir("..");
+}
+
+#define LIMIT_NONE 0
+#define LIMIT_USTAR 1
+
+static void
+verify_tree(int limit)
+{
+	struct stat st, st2;
+	char filename[260];
+	char name1[260];
+	char name2[260];
+	char contents[260];
+	int i, j, r;
+	int fd;
+	int len;
+	const char *p, *dp;
+	DIR *d;
+	struct dirent *de;
+
+	/* Generate the names we know should be there and verify them. */
+	for (i = 1; i < 200; i++) {
+		/* Generate a base name of the correct length. */
+		for (j = 0; j < i; ++j)
+			filename[j] = 'a' + (j % 26);
+#if 0
+		for (n = i; n > 0; n /= 10)
+			filename[--j] = '0' + (n % 10);
+#endif
+		filename[i] = '\0';
+
+		/* Verify a file named "f/abcdef..." */
+		strcpy(name1, "f/");
+		strcat(name1, filename);
+		if (limit != LIMIT_USTAR || strlen(filename) <= 100) {
+			fd = open(name1, O_RDONLY);
+			failure("Couldn't open \"%s\": %s",
+			    name1, strerror(errno));
+			if (assert(fd >= 0)) {
+				len = read(fd, contents, i + 10);
+				close(fd);
+				assertEqualInt(len, i + 2);
+				/* Verify contents of 'contents' */
+				contents[len] = '\0';
+				failure("Each test file contains its own name");
+				assertEqualString(name1, contents);
+				/* stat() for dev/ino for next check */
+				assertEqualInt(0, lstat(name1, &st));
+			}
+		}
+
+		/*
+		 * ustar allows 100 chars for links, and we have
+		 * "original/" as part of the name, so the link
+		 * names here can't exceed 91 chars.
+		 */
+		strcpy(name2, "l/");
+		strcat(name2, filename);
+		if (limit != LIMIT_USTAR || strlen(name2) <= 100) {
+			/* Verify hardlink "l/abcdef..." */
+			assertEqualInt(0, (r = lstat(name2, &st2)));
+			if (r == 0) {
+				assertEqualInt(st2.st_dev, st.st_dev);
+				assertEqualInt(st2.st_ino, st.st_ino);
+			}
+
+			/* Verify hardlink "m_abcdef..." */
+			name2[0] = 'm';
+			assertEqualInt(0, (r = lstat(name2, &st2)));
+			if (r == 0) {
+				assertEqualInt(st2.st_dev, st.st_dev);
+				assertEqualInt(st2.st_ino, st.st_ino);
+			}
+		}
+
+		/*
+		 * Symlink text doesn't include the 'original/' prefix,
+		 * so the limit here is 100 characters.
+		 */
+		/* Verify symlink "s/abcdef..." */
+		strcpy(name2, "../s/");
+		strcat(name2, filename);
+		if (limit != LIMIT_USTAR || strlen(name2) <= 100) {
+			/* This is a symlink. */
+			failure("Couldn't stat %s (length %d)",
+			    filename, strlen(filename));
+			if (assertEqualInt(0, lstat(name2 + 3, &st2))) {
+				assert(S_ISLNK(st2.st_mode));
+				/* This is a symlink to the file above. */
+				failure("Couldn't stat %s", name2 + 3);
+				if (assertEqualInt(0, stat(name2 + 3, &st2))) {
+					assertEqualInt(st2.st_dev, st.st_dev);
+					assertEqualInt(st2.st_ino, st.st_ino);
+				}
+			}
+		}
+
+		/* Verify dir "d/abcdef...". */
+		strcpy(name1, "d/");
+		strcat(name1, filename);
+		if (limit != LIMIT_USTAR || strlen(filename) < 100) {
+			/* This is a dir. */
+			failure("Couldn't stat %s (length %d)",
+			    name1, strlen(filename));
+			if (assertEqualInt(0, lstat(name1, &st2))) {
+				if (assert(S_ISDIR(st2.st_mode))) {
+					/* TODO: opendir/readdir this
+					 * directory and make sure
+					 * it's empty.
+					 */
+				}
+			}
+		}
+	}
+
+	/* Now make sure nothing is there that shouldn't be. */
+	for (dp = "dflms"; *dp != '\0'; ++dp) {
+		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) {
+			p = de->d_name;
+			switch(dp[0]) {
+			case 'l': case 'm':
+				if (limit == LIMIT_USTAR) {
+					failure("strlen(p) = %d", strlen(p));
+					assert(strlen(p) <= 100);
+				}
+			case 'd':
+				if (limit == LIMIT_USTAR) {
+					failure("strlen(p)=%d", strlen(p));
+					assert(strlen(p) < 100);
+				}
+			case 'f': case 's':
+				if (limit == LIMIT_USTAR) {
+					failure("strlen(p)=%d", strlen(p));
+					assert(strlen(p) < 101);
+				}
+				/* Our files have very particular filename patterns. */
+				if (p[0] != '.' || (p[1] != '.' && p[1] != '\0')) {
+					for (i = 0; p[i] != '\0' && i < 200; i++) {
+						failure("i=%d, p[i]='%c' 'a'+(i%%26)='%c'", i, p[i], 'a' + (i % 26));
+						assertEqualInt(p[i], 'a' + (i % 26));
+					}
+					assert(p[i] == '\0');
+				}
+				break;
+			case '.':
+				assert(p[1] == '\0' || (p[1] == '.' && p[2] == '\0'));
+				break;
+			default:
+				failure("File %s shouldn't be here", p);
+				assert(0);
+			}
+		}
+		closedir(d);
+	}
+}
+
+static void
+copy_basic(void)
+{
+	int r;
+
+	assertEqualInt(0, mkdir("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;
+
+	assertEqualInt(0, mkdir(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");
+
+	chdir("original");
+	verify_tree(LIMIT_USTAR);
+	chdir("../..");
+}
+
+DEFINE_TEST(test_copy)
+{
+	int oldumask;
+
+	oldumask = umask(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();
+
+	umask(oldumask);
+}
--- /dev/null
+++ usr.bin/tar/test/test_stdio.c
@@ -0,0 +1,124 @@
+/*-
+ * 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.2.1 2008/08/10 06:53:28 kientzle Exp $");
+
+DEFINE_TEST(test_stdio)
+{
+	int fd;
+	int filelist;
+	int oldumask;
+	int r;
+
+	oldumask = umask(0);
+
+	/*
+	 * Create a couple of files on disk.
+	 */
+	filelist = open("filelist", O_CREAT | O_WRONLY, 0644);
+	/* File */
+	fd = open("f", O_CREAT | O_WRONLY, 0644);
+	assert(fd >= 0);
+	write(fd, "f\n", 2);
+	close(fd);
+	write(filelist, "f\n", 2);
+	/* Link to above file. */
+	assertEqualInt(0, link("f", "l"));
+	write(filelist, "l\n", 2);
+	close(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);
+	/* TODO: Verify xvOf.out */
+	/* 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 */
+
+	umask(oldumask);
+}
--- /dev/null
+++ usr.bin/tar/test/test_strip_components.c
@@ -0,0 +1,106 @@
+/*-
+ * 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.4.1 2008/12/11 05:56:47 kientzle Exp $");
+
+static int
+touch(const char *fn)
+{
+	int fd = open(fn, O_RDWR | O_CREAT | 0644);
+	failure("Couldn't create file '%s', fd=%d, errno=%d (%s)\n",
+	    fn, fd, errno, strerror(errno));
+	if (!assert(fd > 0))
+		return (0); /* Failure. */
+	close(fd);
+	return (1); /* Success */
+}
+
+DEFINE_TEST(test_strip_components)
+{
+	struct stat st;
+
+	assertEqualInt(0, mkdir("d0", 0755));
+	assertEqualInt(0, chdir("d0"));
+	assertEqualInt(0, mkdir("d1", 0755));
+	assertEqualInt(0, mkdir("d1/d2", 0755));
+	assertEqualInt(0, mkdir("d1/d2/d3", 0755));
+	assertEqualInt(1, touch("d1/d2/f1"));
+	assertEqualInt(0, link("d1/d2/f1", "l1"));
+	assertEqualInt(0, link("d1/d2/f1", "d1/l2"));
+	assertEqualInt(0, symlink("d1/d2/f1", "s1"));
+	assertEqualInt(0, symlink("d2/f1", "d1/s2"));
+	assertEqualInt(0, chdir(".."));
+
+	assertEqualInt(0, systemf("%s -cf test.tar d0", testprog));
+
+	assertEqualInt(0, mkdir("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");
+	assertEqualInt(-1, lstat("target/d0", &st));
+	failure("d0/d1/ is too short and should not get restored");
+	assertEqualInt(-1, lstat("target/d1", &st));
+	failure("d0/d1/s2 is a symlink to something that won't be extracted");
+	assertEqualInt(-1, stat("target/s2", &st));
+	assertEqualInt(0, lstat("target/s2", &st));
+	failure("d0/d1/d2 should be extracted");
+	assertEqualInt(0, lstat("target/d2", &st));
+
+	/*
+	 * 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");
+	assertEqualInt(-1, lstat("target/l1", &st));
+	failure("d0/d1/l2 is a hardlink to file whose name was too short");
+	assertEqualInt(-1, lstat("target/l2", &st));
+	failure("d0/d1/d2/f1 is a hardlink to file whose name was too short");
+	assertEqualInt(-1, lstat("target/d2/f1", &st));
+}
--- /dev/null
+++ usr.bin/tar/test/test_symlink_dir.c
@@ -0,0 +1,172 @@
+/*-
+ * 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.4.1 2008/12/11 05:56:47 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, ssize_t size)
+{
+	int fd = open(name, O_CREAT | O_WRONLY, mode);
+	if (fd < 0)
+		return (-1);
+	if (size != write(fd, contents, size)) {
+		close(fd);
+		return (-1);
+	}
+	close(fd);
+	return (0);
+}
+
+DEFINE_TEST(test_symlink_dir)
+{
+	struct stat st, st2;
+	int oldumask;
+
+	oldumask = umask(0);
+
+	assertEqualInt(0, mkdir("source", 0755));
+	assertEqualInt(0, mkfile("source/file", 0755, "a", 1));
+	assertEqualInt(0, mkfile("source/file2", 0755, "ab", 2));
+	assertEqualInt(0, mkdir("source/dir", 0755));
+	assertEqualInt(0, mkdir("source/dir/d", 0755));
+	assertEqualInt(0, mkfile("source/dir/f", 0755, "abc", 3));
+	assertEqualInt(0, mkdir("source/dir2", 0755));
+	assertEqualInt(0, mkdir("source/dir2/d2", 0755));
+	assertEqualInt(0, mkfile("source/dir2/f2", 0755, "abcd", 4));
+	assertEqualInt(0, mkdir("source/dir3", 0755));
+	assertEqualInt(0, mkdir("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.
+	 */
+	assertEqualInt(0, mkdir("dest1", 0755));
+	/* "dir" is a symlink to an existing "real_dir" */
+	assertEqualInt(0, mkdir("dest1/real_dir", 0755));
+	assertEqualInt(0, symlink("real_dir", "dest1/dir"));
+	/* "dir2" is a symlink to a non-existing "real_dir2" */
+	assertEqualInt(0, symlink("real_dir2", "dest1/dir2"));
+	/* "dir3" is a symlink to an existing "non_dir3" */
+	assertEqualInt(0, mkfile("dest1/non_dir3", 0755, "abcdef", 6));
+	assertEqualInt(0, symlink("non_dir3", "dest1/dir3"));
+	/* "file" is a symlink to existing "real_file" */
+	assertEqualInt(0, mkfile("dest1/real_file", 0755, "abcdefg", 7));
+	assertEqualInt(0, symlink("real_file", "dest1/file"));
+	/* "file2" is a symlink to non-existing "real_file2" */
+	assertEqualInt(0, symlink("real_file2", "dest1/file2"));
+
+	assertEqualInt(0, systemf("%s -xf test.tar -C dest1", testprog));
+
+	/* dest1/dir symlink should be removed */
+	assertEqualInt(0, lstat("dest1/dir", &st));
+	failure("symlink to dir was followed when it shouldn't be");
+	assert(S_ISDIR(st.st_mode));
+	/* dest1/dir2 symlink should be removed */
+	assertEqualInt(0, lstat("dest1/dir2", &st));
+	failure("Broken symlink wasn't replaced with dir");
+	assert(S_ISDIR(st.st_mode));
+	/* dest1/dir3 symlink should be removed */
+	assertEqualInt(0, lstat("dest1/dir3", &st));
+	failure("Symlink to non-dir wasn't replaced with dir");
+	assert(S_ISDIR(st.st_mode));
+	/* dest1/file symlink should be removed */
+	assertEqualInt(0, lstat("dest1/file", &st));
+	failure("Symlink to existing file should be removed");
+	assert(S_ISREG(st.st_mode));
+	/* dest1/file2 symlink should be removed */
+	assertEqualInt(0, lstat("dest1/file2", &st));
+	failure("Symlink to non-existing file should be removed");
+	assert(S_ISREG(st.st_mode));
+
+	/*
+	 * Extract with both -x and -P
+	 */
+	assertEqualInt(0, mkdir("dest2", 0755));
+	/* "dir" is a symlink to existing "real_dir" */
+	assertEqualInt(0, mkdir("dest2/real_dir", 0755));
+	assertEqualInt(0, symlink("real_dir", "dest2/dir"));
+	/* "dir2" is a symlink to a non-existing "real_dir2" */
+	assertEqualInt(0, symlink("real_dir2", "dest2/dir2"));
+	/* "dir3" is a symlink to an existing "non_dir3" */
+	assertEqualInt(0, mkfile("dest2/non_dir3", 0755, "abcdefgh", 8));
+	assertEqualInt(0, symlink("non_dir3", "dest2/dir3"));
+	/* "file" is a symlink to existing "real_file" */
+	assertEqualInt(0, mkfile("dest2/real_file", 0755, "abcdefghi", 9));
+	assertEqualInt(0, symlink("real_file", "dest2/file"));
+	/* "file2" is a symlink to non-existing "real_file2" */
+	assertEqualInt(0, symlink("real_file2", "dest2/file2"));
+
+	assertEqualInt(0, systemf("%s -xPf test.tar -C dest2", testprog));
+
+	/* dest2/dir symlink should be followed */
+	assertEqualInt(0, lstat("dest2/dir", &st));
+	failure("tar -xP removed symlink instead of following it");
+	if (assert(S_ISLNK(st.st_mode))) {
+		/* Only verify what the symlink points to if it
+		 * really is a symlink. */
+		failure("The symlink should point to a directory");
+		assertEqualInt(0, stat("dest2/dir", &st));
+		assert(S_ISDIR(st.st_mode));
+		failure("The pre-existing directory should still be there");
+		assertEqualInt(0, lstat("dest2/real_dir", &st2));
+		assert(S_ISDIR(st2.st_mode));
+		assertEqualInt(st.st_dev, st2.st_dev);
+		failure("symlink should still point to the existing directory");
+		assertEqualInt(st.st_ino, st2.st_ino);
+	}
+	/* Contents of 'dir' should be restored */
+	assertEqualInt(0, lstat("dest2/dir/d", &st));
+	assert(S_ISDIR(st.st_mode));
+	assertEqualInt(0, lstat("dest2/dir/f", &st));
+	assert(S_ISREG(st.st_mode));
+	assertEqualInt(3, st.st_size);
+	/* dest2/dir2 symlink should be removed */
+	assertEqualInt(0, lstat("dest2/dir2", &st));
+	failure("Broken symlink wasn't replaced with dir");
+	assert(S_ISDIR(st.st_mode));
+	/* dest2/dir3 symlink should be removed */
+	assertEqualInt(0, lstat("dest2/dir3", &st));
+	failure("Symlink to non-dir wasn't replaced with dir");
+	assert(S_ISDIR(st.st_mode));
+	/* dest2/file symlink should be removed;
+	 * even -P shouldn't follow symlinks for files */
+	assertEqualInt(0, lstat("dest2/file", &st));
+	failure("Symlink to existing file should be removed");
+	assert(S_ISREG(st.st_mode));
+	/* dest2/file2 symlink should be removed */
+	assertEqualInt(0, lstat("dest2/file2", &st));
+	failure("Symlink to non-existing file should be removed");
+	assert(S_ISREG(st.st_mode));
+}
--- /dev/null
+++ usr.bin/tar/test/main.c
@@ -0,0 +1,1046 @@
+/*
+ * 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.
+ */
+
+/*
+ * Various utility routines useful for test programs.
+ * Each test program is linked against this file.
+ */
+#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.
+ */
+#define	PROGRAM "bsdtar" /* Name of program being tested. */
+#define ENVBASE "BSDTAR" /* Prefix for environment variables. */
+#undef	EXTRA_DUMP	     /* How to dump extra data */
+/* How to generate extra version info. */
+#define	EXTRA_VERSION    (systemf("%s --version", testprog) ? "" : "")
+__FBSDID("$FreeBSD: src/usr.bin/tar/test/main.c,v 1.3.2.3 2008/08/27 04:59:00 kientzle Exp $");
+
+/*
+ * "list.h" is simply created by "grep DEFINE_TEST"; it has
+ * a line like
+ *      DEFINE_TEST(test_function)
+ * for each test.
+ * Include it here with a suitable DEFINE_TEST to declare all of the
+ * test functions.
+ */
+#undef DEFINE_TEST
+#define	DEFINE_TEST(name) void name(void);
+#include "list.h"
+
+/* Interix doesn't define these in a standard header. */
+#if __INTERIX__
+extern char *optarg;
+extern int optind;
+#endif
+
+/* Enable core dump on failure. */
+static int dump_on_failure = 0;
+/* Default is to remove temp dirs for successful tests. */
+static int keep_temp_files = 0;
+/* Default is to print some basic information about each test. */
+static int quiet_flag = 0;
+/* Default is to summarize repeated failures. */
+static int verbose = 0;
+/* Cumulative count of component failures. */
+static int failures = 0;
+/* Cumulative count of skipped component tests. */
+static int skips = 0;
+/* Cumulative count of assertions. */
+static int assertions = 0;
+
+/* Directory where uuencoded reference files can be found. */
+static char *refdir;
+
+/*
+ * My own implementation of the standard assert() macro emits the
+ * message in the same format as GCC (file:line: message).
+ * It also includes some additional useful information.
+ * This makes it a lot easier to skim through test failures in
+ * Emacs.  ;-)
+ *
+ * It also supports a few special features specifically to simplify
+ * test harnesses:
+ *    failure(fmt, args) -- Stores a text string that gets
+ *          printed if the following assertion fails, good for
+ *          explaining subtle tests.
+ */
+static char msg[4096];
+
+/*
+ * For each test source file, we remember how many times each
+ * failure was reported.
+ */
+static const char *failed_filename = NULL;
+static struct line {
+	int line;
+	int count;
+}  failed_lines[1000];
+
+/*
+ * Count this failure; return the number of previous failures.
+ */
+static int
+previous_failures(const char *filename, int line)
+{
+	unsigned int i;
+	int count;
+
+	if (failed_filename == NULL || strcmp(failed_filename, filename) != 0)
+		memset(failed_lines, 0, sizeof(failed_lines));
+	failed_filename = filename;
+
+	for (i = 0; i < sizeof(failed_lines)/sizeof(failed_lines[0]); i++) {
+		if (failed_lines[i].line == line) {
+			count = failed_lines[i].count;
+			failed_lines[i].count++;
+			return (count);
+		}
+		if (failed_lines[i].line == 0) {
+			failed_lines[i].line = line;
+			failed_lines[i].count = 1;
+			return (0);
+		}
+	}
+	return (0);
+}
+
+/*
+ * Copy arguments into file-local variables.
+ */
+static const char *test_filename;
+static int test_line;
+static void *test_extra;
+void test_setup(const char *filename, int line)
+{
+	test_filename = filename;
+	test_line = line;
+}
+
+/*
+ * Inform user that we're skipping a test.
+ */
+void
+test_skipping(const char *fmt, ...)
+{
+	va_list ap;
+
+	if (previous_failures(test_filename, test_line))
+		return;
+
+	va_start(ap, fmt);
+	fprintf(stderr, " *** SKIPPING: ");
+	vfprintf(stderr, fmt, ap);
+	fprintf(stderr, "\n");
+	va_end(ap);
+	++skips;
+}
+
+/* Common handling of failed tests. */
+static void
+report_failure(void *extra)
+{
+	if (msg[0] != '\0') {
+		fprintf(stderr, "   Description: %s\n", msg);
+		msg[0] = '\0';
+	}
+
+#ifdef EXTRA_DUMP
+	if (extra != NULL)
+		fprintf(stderr, "   detail: %s\n", EXTRA_DUMP(extra));
+#else
+	(void)extra; /* UNUSED */
+#endif
+
+	if (dump_on_failure) {
+		fprintf(stderr,
+		    " *** forcing core dump so failure can be debugged ***\n");
+		*(char *)(NULL) = 0;
+		exit(1);
+	}
+}
+
+/*
+ * Summarize repeated failures in the just-completed test file.
+ * The reports above suppress multiple failures from the same source
+ * line; this reports on any tests that did fail multiple times.
+ */
+static int
+summarize_comparator(const void *a0, const void *b0)
+{
+	const struct line *a = a0, *b = b0;
+	if (a->line == 0 && b->line == 0)
+		return (0);
+	if (a->line == 0)
+		return (1);
+	if (b->line == 0)
+		return (-1);
+	return (a->line - b->line);
+}
+
+static void
+summarize(void)
+{
+	unsigned int i;
+
+	qsort(failed_lines, sizeof(failed_lines)/sizeof(failed_lines[0]),
+	    sizeof(failed_lines[0]), summarize_comparator);
+	for (i = 0; i < sizeof(failed_lines)/sizeof(failed_lines[0]); i++) {
+		if (failed_lines[i].line == 0)
+			break;
+		if (failed_lines[i].count > 1)
+			fprintf(stderr, "%s:%d: Failed %d times\n",
+			    failed_filename, failed_lines[i].line,
+			    failed_lines[i].count);
+	}
+	/* Clear the failure history for the next file. */
+	memset(failed_lines, 0, sizeof(failed_lines));
+}
+
+/* Set up a message to display only after a test fails. */
+void
+failure(const char *fmt, ...)
+{
+	va_list ap;
+	va_start(ap, fmt);
+	vsprintf(msg, fmt, ap);
+	va_end(ap);
+}
+
+/* Generic assert() just displays the failed condition. */
+int
+test_assert(const char *file, int line, int value, const char *condition, void *extra)
+{
+	++assertions;
+	if (value) {
+		msg[0] = '\0';
+		return (value);
+	}
+	failures ++;
+	if (!verbose && previous_failures(file, line))
+		return (value);
+	fprintf(stderr, "%s:%d: Assertion failed\n", file, line);
+	fprintf(stderr, "   Condition: %s\n", condition);
+	report_failure(extra);
+	return (value);
+}
+
+/* assertEqualInt() displays the values of the two integers. */
+int
+test_assert_equal_int(const char *file, int line,
+    int v1, const char *e1, int v2, const char *e2, void *extra)
+{
+	++assertions;
+	if (v1 == v2) {
+		msg[0] = '\0';
+		return (1);
+	}
+	failures ++;
+	if (!verbose && previous_failures(file, line))
+		return (0);
+	fprintf(stderr, "%s:%d: Assertion failed: Ints not equal\n",
+	    file, line);
+	fprintf(stderr, "      %s=%d\n", e1, v1);
+	fprintf(stderr, "      %s=%d\n", e2, v2);
+	report_failure(extra);
+	return (0);
+}
+
+static void strdump(const char *p)
+{
+	if (p == NULL) {
+		fprintf(stderr, "(null)");
+		return;
+	}
+	fprintf(stderr, "\"");
+	while (*p != '\0') {
+		unsigned int c = 0xff & *p++;
+		switch (c) {
+		case '\a': fprintf(stderr, "\a"); break;
+		case '\b': fprintf(stderr, "\b"); break;
+		case '\n': fprintf(stderr, "\n"); break;
+		case '\r': fprintf(stderr, "\r"); break;
+		default:
+			if (c >= 32 && c < 127)
+				fprintf(stderr, "%c", c);
+			else
+				fprintf(stderr, "\\x%02X", c);
+		}
+	}
+	fprintf(stderr, "\"");
+}
+
+/* assertEqualString() displays the values of the two strings. */
+int
+test_assert_equal_string(const char *file, int line,
+    const char *v1, const char *e1,
+    const char *v2, const char *e2,
+    void *extra)
+{
+	++assertions;
+	if (v1 == NULL || v2 == NULL) {
+		if (v1 == v2) {
+			msg[0] = '\0';
+			return (1);
+		}
+	} else if (strcmp(v1, v2) == 0) {
+		msg[0] = '\0';
+		return (1);
+	}
+	failures ++;
+	if (!verbose && previous_failures(file, line))
+		return (0);
+	fprintf(stderr, "%s:%d: Assertion failed: Strings not equal\n",
+	    file, line);
+	fprintf(stderr, "      %s = ", e1);
+	strdump(v1);
+	fprintf(stderr, " (length %d)\n", v1 == NULL ? 0 : strlen(v1));
+	fprintf(stderr, "      %s = ", e2);
+	strdump(v2);
+	fprintf(stderr, " (length %d)\n", v2 == NULL ? 0 : strlen(v2));
+	report_failure(extra);
+	return (0);
+}
+
+static void wcsdump(const wchar_t *w)
+{
+	if (w == NULL) {
+		fprintf(stderr, "(null)");
+		return;
+	}
+	fprintf(stderr, "\"");
+	while (*w != L'\0') {
+		unsigned int c = *w++;
+		if (c >= 32 && c < 127)
+			fprintf(stderr, "%c", c);
+		else if (c < 256)
+			fprintf(stderr, "\\x%02X", c);
+		else if (c < 0x10000)
+			fprintf(stderr, "\\u%04X", c);
+		else
+			fprintf(stderr, "\\U%08X", c);
+	}
+	fprintf(stderr, "\"");
+}
+
+/* assertEqualWString() displays the values of the two strings. */
+int
+test_assert_equal_wstring(const char *file, int line,
+    const wchar_t *v1, const char *e1,
+    const wchar_t *v2, const char *e2,
+    void *extra)
+{
+	++assertions;
+	if (v1 == NULL) {
+		if (v2 == NULL) {
+			msg[0] = '\0';
+			return (1);
+		}
+	} else if (v2 == NULL) {
+		if (v1 == NULL) {
+			msg[0] = '\0';
+			return (1);
+		}
+	} else if (wcscmp(v1, v2) == 0) {
+		msg[0] = '\0';
+		return (1);
+	}
+	failures ++;
+	if (!verbose && previous_failures(file, line))
+		return (0);
+	fprintf(stderr, "%s:%d: Assertion failed: Unicode strings not equal\n",
+	    file, line);
+	fprintf(stderr, "      %s = ", e1);
+	wcsdump(v1);
+	fprintf(stderr, "\n");
+	fprintf(stderr, "      %s = ", e2);
+	wcsdump(v2);
+	fprintf(stderr, "\n");
+	report_failure(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;
+
+	for(i=0; i < l; i+=16) {
+		fprintf(stderr, "%04x", i + offset);
+		sep = ' ';
+		for (j = 0; j < 16 && i + j < l; j++) {
+			if (ref != NULL && p[i + j] != ref[i + j])
+				sep = '_';
+			fprintf(stderr, "%c%02x", sep, 0xff & (int)p[i+j]);
+			if (ref != NULL && p[i + j] == ref[i + j])
+				sep = ' ';
+		}
+		for (; j < 16; j++) {
+			fprintf(stderr, "%c  ", sep);
+			sep = ' ';
+		}
+		fprintf(stderr, "%c", sep);
+		for (j=0; j < 16 && i + j < l; j++) {
+			int c = p[i + j];
+			if (c >= ' ' && c <= 126)
+				fprintf(stderr, "%c", c);
+			else
+				fprintf(stderr, ".");
+		}
+		fprintf(stderr, "\n");
+	}
+}
+
+/* assertEqualMem() displays the values of the two memory blocks. */
+/* TODO: For long blocks, hexdump the first bytes that actually differ. */
+int
+test_assert_equal_mem(const char *file, int line,
+    const char *v1, const char *e1,
+    const char *v2, const char *e2,
+    size_t l, const char *ld, void *extra)
+{
+	++assertions;
+	if (v1 == NULL || v2 == NULL) {
+		if (v1 == v2) {
+			msg[0] = '\0';
+			return (1);
+		}
+	} else if (memcmp(v1, v2, l) == 0) {
+		msg[0] = '\0';
+		return (1);
+	}
+	failures ++;
+	if (!verbose && previous_failures(file, line))
+		return (0);
+	fprintf(stderr, "%s:%d: Assertion failed: memory not equal\n",
+	    file, line);
+	fprintf(stderr, "      size %s = %d\n", ld, (int)l);
+	fprintf(stderr, "      Dump of %s\n", e1);
+	hexdump(v1, v2, l < 32 ? l : 32, 0);
+	fprintf(stderr, "      Dump of %s\n", e2);
+	hexdump(v2, v1, l < 32 ? l : 32, 0);
+	fprintf(stderr, "\n");
+	report_failure(extra);
+	return (0);
+}
+
+int
+test_assert_empty_file(const char *f1fmt, ...)
+{
+	char buff[1024];
+	char f1[1024];
+	struct stat st;
+	va_list ap;
+	ssize_t s;
+	int fd;
+
+
+	va_start(ap, f1fmt);
+	vsprintf(f1, f1fmt, ap);
+	va_end(ap);
+
+	if (stat(f1, &st) != 0) {
+		fprintf(stderr, "%s:%d: Could not stat: %s\n", test_filename, test_line, f1);
+		report_failure(NULL);
+		return (0);
+	}
+	if (st.st_size == 0)
+		return (1);
+
+	failures ++;
+	if (!verbose && previous_failures(test_filename, test_line))
+		return (0);
+
+	fprintf(stderr, "%s:%d: File not empty: %s\n", test_filename, test_line, f1);
+	fprintf(stderr, "    File size: %d\n", (int)st.st_size);
+	fprintf(stderr, "    Contents:\n");
+	fd = open(f1, O_RDONLY);
+	if (fd < 0) {
+		fprintf(stderr, "    Unable to open %s\n", f1);
+	} else {
+		s = sizeof(buff) < st.st_size ? sizeof(buff) : st.st_size;
+		s = read(fd, buff, s);
+		hexdump(buff, NULL, s, 0);
+	}
+	report_failure(NULL);
+	return (0);
+}
+
+int
+test_assert_non_empty_file(const char *f1fmt, ...)
+{
+	char f1[1024];
+	struct stat st;
+	va_list ap;
+
+
+	va_start(ap, f1fmt);
+	vsprintf(f1, f1fmt, ap);
+	va_end(ap);
+
+	if (stat(f1, &st) != 0) {
+		fprintf(stderr, "%s:%d: Could not stat: %s\n",
+		    test_filename, test_line, f1);
+		report_failure(NULL);
+		return (0);
+	}
+	if (st.st_size != 0)
+		return (1);
+
+	failures ++;
+	if (!verbose && previous_failures(test_filename, test_line))
+		return (0);
+
+	fprintf(stderr, "%s:%d: File empty: %s\n",
+	    test_filename, test_line, f1);
+	report_failure(NULL);
+	return (0);
+}
+
+/* assertEqualFile() asserts that two files have the same contents. */
+/* TODO: hexdump the first bytes that actually differ. */
+int
+test_assert_equal_file(const char *f1, const char *f2pattern, ...)
+{
+	char f2[1024];
+	va_list ap;
+	char buff1[1024];
+	char buff2[1024];
+	int fd1, fd2;
+	int n1, n2;
+
+	va_start(ap, f2pattern);
+	vsprintf(f2, f2pattern, ap);
+	va_end(ap);
+
+	fd1 = open(f1, O_RDONLY);
+	fd2 = open(f2, O_RDONLY);
+	for (;;) {
+		n1 = read(fd1, buff1, sizeof(buff1));
+		n2 = read(fd2, buff2, sizeof(buff2));
+		if (n1 != n2)
+			break;
+		if (n1 == 0 && n2 == 0)
+			return (1);
+		if (memcmp(buff1, buff2, n1) != 0)
+			break;
+	}
+	failures ++;
+	if (!verbose && previous_failures(test_filename, test_line))
+		return (0);
+	fprintf(stderr, "%s:%d: Files are not identical\n",
+	    test_filename, test_line);
+	fprintf(stderr, "  file1=\"%s\"\n", f1);
+	fprintf(stderr, "  file2=\"%s\"\n", f2);
+	report_failure(test_extra);
+	return (0);
+}
+
+int
+test_assert_file_exists(const char *fpattern, ...)
+{
+	char f[1024];
+	va_list ap;
+
+	va_start(ap, fpattern);
+	vsprintf(f, fpattern, ap);
+	va_end(ap);
+
+	if (!access(f, F_OK))
+		return (1);
+	if (!previous_failures(test_filename, test_line)) {
+		fprintf(stderr, "%s:%d: File doesn't exist\n",
+		    test_filename, test_line);
+		fprintf(stderr, "  file=\"%s\"\n", f);
+		report_failure(test_extra);
+	}
+	return (0);
+}
+
+int
+test_assert_file_not_exists(const char *fpattern, ...)
+{
+	char f[1024];
+	va_list ap;
+
+	va_start(ap, fpattern);
+	vsprintf(f, fpattern, ap);
+	va_end(ap);
+
+	if (access(f, F_OK))
+		return (1);
+	if (!previous_failures(test_filename, test_line)) {
+		fprintf(stderr, "%s:%d: File exists and shouldn't\n",
+		    test_filename, test_line);
+		fprintf(stderr, "  file=\"%s\"\n", f);
+		report_failure(test_extra);
+	}
+	return (0);
+}
+
+/* assertFileContents() asserts the contents of a file. */
+int
+test_assert_file_contents(const void *buff, int s, const char *fpattern, ...)
+{
+	char f[1024];
+	va_list ap;
+	char *contents;
+	int fd;
+	int n;
+
+	va_start(ap, fpattern);
+	vsprintf(f, fpattern, ap);
+	va_end(ap);
+
+	fd = open(f, O_RDONLY);
+	contents = malloc(s * 2);
+	n = read(fd, contents, s * 2);
+	if (n == s && memcmp(buff, contents, s) == 0) {
+		free(contents);
+		return (1);
+	}
+	failures ++;
+	if (!previous_failures(test_filename, test_line)) {
+		fprintf(stderr, "%s:%d: File contents don't match\n",
+		    test_filename, test_line);
+		fprintf(stderr, "  file=\"%s\"\n", f);
+		if (n > 0)
+			hexdump(contents, buff, n, 0);
+		else {
+			fprintf(stderr, "  File empty, contents should be:\n");
+			hexdump(buff, NULL, s, 0);
+		}
+		report_failure(test_extra);
+	}
+	free(contents);
+	return (0);
+}
+
+/*
+ * 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);
+	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;
+	int fd;
+	int r;
+
+	va_start(ap, fmt);
+	vsprintf(filename, fmt, ap);
+	va_end(ap);
+
+	fd = open(filename, O_RDONLY);
+	if (fd < 0) {
+		/* Note: No error; non-existent file is okay here. */
+		return (NULL);
+	}
+	r = fstat(fd, &st);
+	if (r != 0) {
+		fprintf(stderr, "Can't stat file %s\n", filename);
+		close(fd);
+		return (NULL);
+	}
+	p = malloc(st.st_size + 1);
+	if (p == NULL) {
+		fprintf(stderr, "Can't allocate %ld bytes of memory to read file %s\n", (long int)st.st_size, filename);
+		close(fd);
+		return (NULL);
+	}
+	bytes_read = read(fd, p, st.st_size);
+	if (bytes_read < st.st_size) {
+		fprintf(stderr, "Can't read file %s\n", filename);
+		close(fd);
+		free(p);
+		return (NULL);
+	}
+	p[st.st_size] = '\0';
+	if (sizep != NULL)
+		*sizep = (size_t)st.st_size;
+	close(fd);
+	return (p);
+}
+
+/*
+ * "list.h" is automatically generated; it just has a lot of lines like:
+ * 	DEFINE_TEST(function_name)
+ * It's used above to declare all of the test functions.
+ * We reuse it here to define a list of all tests (functions and names).
+ */
+#undef DEFINE_TEST
+#define	DEFINE_TEST(n) { n, #n },
+struct { void (*func)(void); const char *name; } tests[] = {
+	#include "list.h"
+};
+
+/*
+ * Each test is run in a private work dir.  Those work dirs
+ * do have consistent and predictable names, in case a group
+ * of tests need to collaborate.  However, there is no provision
+ * for requiring that tests run in a certain order.
+ */
+static int test_run(int i, const char *tmpdir)
+{
+	int failures_before = failures;
+
+	if (!quiet_flag) {
+		printf("%d: %s\n", i, tests[i].name);
+		fflush(stdout);
+	}
+
+	/*
+	 * Always explicitly chdir() in case the last test moved us to
+	 * a strange place.
+	 */
+	if (chdir(tmpdir)) {
+		fprintf(stderr,
+		    "ERROR: Couldn't chdir to temp dir %s\n",
+		    tmpdir);
+		exit(1);
+	}
+	/* Create a temp directory for this specific test. */
+	if (mkdir(tests[i].name, 0755)) {
+		fprintf(stderr,
+		    "ERROR: Couldn't create temp dir ``%s''\n",
+		    tests[i].name);
+		exit(1);
+	}
+	/* Chdir() to that work directory. */
+	if (chdir(tests[i].name)) {
+		fprintf(stderr,
+		    "ERROR: Couldn't chdir to temp dir ``%s''\n",
+		    tests[i].name);
+		exit(1);
+	}
+	/* Explicitly reset the locale before each test. */
+	setlocale(LC_ALL, "C");
+	/* Run the actual test. */
+	(*tests[i].func)();
+	/* Summarize the results of this test. */
+	summarize();
+	/* If there were no failures, we can remove the work dir. */
+	if (failures == failures_before) {
+		if (!keep_temp_files && chdir(tmpdir) == 0) {
+			systemf("rm -rf %s", tests[i].name);
+		}
+	}
+	/* Return appropriate status. */
+	return (failures == failures_before ? 0 : 1);
+}
+
+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);
+}
+
+#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, "w");
+	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);
+}
+
+
+int main(int argc, char **argv)
+{
+	static const int limit = sizeof(tests) / sizeof(tests[0]);
+	int i, tests_run = 0, tests_failed = 0, opt;
+	time_t now;
+	char *refdir_alloc = NULL;
+	char *progname, *p;
+	char tmpdir[256];
+	char tmpdir_timestamp[256];
+
+	/*
+	 * Name of this program, used to build root of our temp directory
+	 * tree.
+	 */
+	progname = p = argv[0];
+	while (*p != '\0') {
+		if (*p == '/')
+			progname = p + 1;
+		++p;
+	}
+
+#ifdef PROGRAM
+	/* Get the target program from environment, if available. */
+	testprog = getenv(ENVBASE);
+#endif
+
+	/* 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.
+	 */
+	while ((opt = getopt(argc, argv, "dkp:qr:v")) != -1) {
+		switch (opt) {
+		case 'd':
+			dump_on_failure = 1;
+			break;
+		case 'k':
+			keep_temp_files = 1;
+			break;
+		case 'p':
+#ifdef PROGRAM
+			testprog = optarg;
+#else
+			usage(progname);
+#endif
+			break;
+		case 'q':
+			quiet_flag++;
+			break;
+		case 'r':
+			refdir = optarg;
+			break;
+		case 'v':
+			verbose = 1;
+			break;
+		case '?':
+		default:
+			usage(progname);
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	/*
+	 * Sanity-check that our options make sense.
+	 */
+#ifdef PROGRAM
+	if (testprog == NULL)
+		usage(progname);
+#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 < 1000; i++) {
+		strftime(tmpdir_timestamp, sizeof(tmpdir_timestamp),
+		    "%Y-%m-%dT%H.%M.%S",
+		    localtime(&now));
+		sprintf(tmpdir, "/tmp/%s.%s-%03d", progname, tmpdir_timestamp, i);
+		if (mkdir(tmpdir,0755) == 0)
+			break;
+		if (errno == EEXIST)
+			continue;
+		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, use the current directory for that.
+	 */
+	if (refdir == NULL) {
+		systemf("/bin/pwd > %s/refdir", tmpdir);
+		refdir = refdir_alloc = slurpfile(NULL, "%s/refdir", tmpdir);
+		p = refdir + strlen(refdir);
+		while (p[-1] == '\n') {
+			--p;
+			*p = '\0';
+		}
+		systemf("rm %s/refdir", tmpdir);
+	}
+
+	/*
+	 * Banner with basic information.
+	 */
+	if (!quiet_flag) {
+		printf("Running tests in: %s\n", tmpdir);
+		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);
+	}
+
+	/*
+	 * Run some or all of the individual tests.
+	 */
+	if (argc == 0) {
+		/* Default: Run all tests. */
+		for (i = 0; i < limit; i++) {
+			if (test_run(i, tmpdir))
+				tests_failed++;
+			tests_run++;
+		}
+	} else {
+		while (*(argv) != NULL) {
+			i = atoi(*argv);
+			if (**argv < '0' || **argv > '9' || i < 0 || i >= limit) {
+				printf("*** INVALID Test %s\n", *argv);
+				usage(progname);
+			} else {
+				if (test_run(i, tmpdir))
+					tests_failed++;
+				tests_run++;
+			}
+			argv++;
+		}
+	}
+
+	/*
+	 * Report summary statistics.
+	 */
+	if (!quiet_flag) {
+		printf("\n");
+		printf("%d of %d tests reported failures\n",
+		    tests_failed, tests_run);
+		printf(" Total of %d assertions checked.\n", assertions);
+		printf(" Total of %d assertions failed.\n", failures);
+		printf(" Total of %d assertions skipped.\n", skips);
+	}
+
+	free(refdir_alloc);
+
+	/* If the final tmpdir is empty, we can remove it. */
+	/* This should be the usual case when all tests succeed. */
+	rmdir(tmpdir);
+
+	return (tests_failed);
+}
--- /dev/null
+++ usr.bin/tar/test/test_option_q.c
@@ -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_option_q.c,v 1.3.2.1 2008/08/25 02:14:52 kientzle Exp $");
+
+DEFINE_TEST(test_option_q)
+{
+	int fd;
+
+	/*
+	 * 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.
+	 */
+
+	fd = open("foo", O_CREAT | O_WRONLY, 0644);
+	assert(fd >= 0);
+	assertEqualInt(4, write(fd, "foo1", 4));
+	close(fd);
+
+	assertEqualInt(0, systemf("%s -cf archive.tar foo", testprog));
+
+	fd = open("foo", O_TRUNC | O_WRONLY, 0644);
+	assert(fd >= 0);
+	assertEqualInt(4, write(fd, "foo2", 4));
+	close(fd);
+
+	assertEqualInt(0, systemf("%s -rf archive.tar foo", testprog));
+
+	fd = open("bar", O_CREAT | O_WRONLY, 0644);
+	assert(fd >= 0);
+	assertEqualInt(4, write(fd, "bar1", 4));
+	close(fd);
+
+	assertEqualInt(0, systemf("%s -rf archive.tar bar", testprog));
+
+	fd = open("foo", O_TRUNC | O_WRONLY, 0644);
+	assert(fd >= 0);
+	assertEqualInt(4, write(fd, "foo3", 4));
+	close(fd);
+
+	assertEqualInt(0, systemf("%s -rf archive.tar foo", testprog));
+
+	fd = open("bar", O_TRUNC | O_WRONLY, 0644);
+	assert(fd >= 0);
+	assertEqualInt(4, write(fd, "bar2", 4));
+	close(fd);
+
+	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. */
+	assertEqualInt(0, mkdir("test1", 0755));
+	assertEqualInt(0, chdir("test1"));
+	assertEqualInt(0,
+	    systemf("%s -xf ../archive.tar -q foo >test.out 2>test.err",
+		testprog));
+	assertFileContents("foo1", 4, "foo");
+	assertEmptyFile("test.out");
+	assertEmptyFile("test.err");
+	assertEqualInt(0, chdir(".."));
+
+	/* Test 2: -q foo bar should extract up to the first bar. */
+	assertEqualInt(0, mkdir("test2", 0755));
+	assertEqualInt(0, chdir("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");
+	assertEqualInt(0, chdir(".."));
+
+	/* Test 3: Same as test 2, but use --fast-read spelling. */
+	assertEqualInt(0, mkdir("test3", 0755));
+	assertEqualInt(0, chdir("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");
+	assertEqualInt(0, chdir(".."));
+
+	/* Test 4: Without -q, should extract everything. */
+	assertEqualInt(0, mkdir("test4", 0755));
+	assertEqualInt(0, chdir("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");
+	assertEqualInt(0, chdir(".."));
+}
--- /dev/null
+++ usr.bin/tar/test/test_patterns_3.tgz.uu
@@ -0,0 +1,8 @@
+$FreeBSD: src/usr.bin/tar/test/test_patterns_3.tgz.uu,v 1.1.2.1 2008/08/27 04:59:00 kientzle Exp $
+begin 644 test_patterns_3.tgz
+M'XL(`)P/K4@``^W5T0K"(!3&\3V*;^#1H3Z/@T5!8[&,H*=O%J/M9M3(7?U_
+M-T?1BR.?HD[=11_Z7C=QT%49(A*<4V,UP4FNV53?$V/$U3;OLTK,./*5<H7Z
+M6;A=4QS&5M*I6]UW/[;M>65]>2CUUQX+TO/\F_ at H<0<VY!^\)?\]?.(O$OW+
+K3_E[R?F;FO>_BWG^I;Z`#?D'X?T#``````````````!\Y0FE&!YR`"@`````
+`
+end
--- /dev/null
+++ usr.bin/tar/test/test_basic.c
@@ -0,0 +1,158 @@
+/*-
+ * 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.2.1 2008/08/10 06:53:28 kientzle Exp $");
+
+
+static void
+basic_tar(const char *target, const char *pack_options, const char *unpack_options)
+{
+	struct stat st, st2;
+	char buff[128];
+	int r;
+
+	assertEqualInt(0, mkdir(target, 0775));
+
+	/* Use the tar program to create an archive. */
+	r = systemf("%s cf - %s `cat filelist` >%s/archive 2>%s/pack.err", testprog, pack_options, target, target);
+	failure("Error invoking %s cf -", testprog, pack_options);
+	assertEqualInt(r, 0);
+
+	chdir(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. */
+	r = lstat("file", &st);
+	failure("Failed to stat file %s/file, errno=%d", target, errno);
+	assertEqualInt(r, 0);
+	if (r == 0) {
+		assert(S_ISREG(st.st_mode));
+		assertEqualInt(0644, st.st_mode & 0777);
+		assertEqualInt(10, st.st_size);
+		failure("file %s/file", target);
+		assertEqualInt(2, st.st_nlink);
+	}
+
+	/* Another name for the same file. */
+	r = lstat("linkfile", &st2);
+	failure("Failed to stat file %s/linkfile, errno=%d", target, errno);
+	assertEqualInt(r, 0);
+	if (r == 0) {
+		assert(S_ISREG(st2.st_mode));
+		assertEqualInt(0644, st2.st_mode & 0777);
+		assertEqualInt(10, st2.st_size);
+		failure("file %s/linkfile", target);
+		assertEqualInt(2, st2.st_nlink);
+		/* Verify that the two are really hardlinked. */
+		assertEqualInt(st.st_dev, st2.st_dev);
+		failure("%s/linkfile and %s/file aren't really hardlinks", target, target);
+		assertEqualInt(st.st_ino, st2.st_ino);
+	}
+
+	/* Symlink */
+	r = lstat("symlink", &st);
+	failure("Failed to stat file %s/symlink, errno=%d", target, errno);
+	assertEqualInt(r, 0);
+	if (r == 0) {
+		failure("symlink should be a symlink; actual mode is %o",
+		    st.st_mode);
+		assert(S_ISLNK(st.st_mode));
+		if (S_ISLNK(st.st_mode)) {
+			r = readlink("symlink", buff, sizeof(buff));
+			assertEqualInt(r, 4);
+			buff[r] = '\0';
+			assertEqualString(buff, "file");
+		}
+	}
+
+	/* dir */
+	r = lstat("dir", &st);
+	if (r == 0) {
+		assertEqualInt(r, 0);
+		assert(S_ISDIR(st.st_mode));
+		assertEqualInt(0775, st.st_mode & 0777);
+	}
+
+	chdir("..");
+}
+
+DEFINE_TEST(test_basic)
+{
+	int fd;
+	int filelist;
+	int oldumask;
+
+	oldumask = umask(0);
+
+	/*
+	 * Create an assortment of files on disk.
+	 */
+	filelist = open("filelist", O_CREAT | O_WRONLY, 0644);
+
+	/* File with 10 bytes content. */
+	fd = open("file", O_CREAT | O_WRONLY, 0644);
+	assert(fd >= 0);
+	assertEqualInt(10, write(fd, "123456789", 10));
+	close(fd);
+	write(filelist, "file\n", 5);
+
+	/* hardlink to above file. */
+	assertEqualInt(0, link("file", "linkfile"));
+	write(filelist, "linkfile\n", 9);
+
+	/* Symlink to above file. */
+	assertEqualInt(0, symlink("file", "symlink"));
+	write(filelist, "symlink\n", 8);
+
+	/* Directory. */
+	assertEqualInt(0, mkdir("dir", 0775));
+	write(filelist, "dir\n", 4);
+	/* All done. */
+	close(filelist);
+
+	/* Archive/dearchive with a variety of options. */
+	basic_tar("copy", "", "");
+	/* tar doesn't handle cpio symlinks correctly */
+	/* basic_tar("copy_odc", "--format=odc", ""); */
+	basic_tar("copy_ustar", "--format=ustar", "");
+
+	umask(oldumask);
+}
--- /dev/null
+++ usr.bin/tar/test/Makefile
@@ -0,0 +1,65 @@
+# $FreeBSD: src/usr.bin/tar/test/Makefile,v 1.2.2.4 2008/12/11 05:56:47 kientzle Exp $
+
+# Where to find the tar sources (for the internal unit tests)
+TAR_SRCDIR=${.CURDIR}/..
+.PATH: ${TAR_SRCDIR}
+
+# Some tar sources are pulled in for white-box tests
+TAR_SRCS=					\
+	../getdate.y
+
+TESTS=	\
+	test_0.c				\
+	test_basic.c				\
+	test_copy.c				\
+	test_getdate.c				\
+	test_help.c				\
+	test_option_T.c				\
+	test_option_q.c				\
+	test_patterns.c				\
+	test_stdio.c				\
+	test_strip_components.c			\
+	test_symlink_dir.c			\
+	test_version.c
+
+# Build the test program
+SRCS= ${TAR_SRCS}				\
+	${TESTS}				\
+	list.h					\
+	main.c
+
+CLEANFILES+= list.h
+
+NO_MAN=yes
+
+PROG=bsdtar_test
+DPADD=${LIBARCHIVE} ${LIBBZ2} ${LIBZ}
+CFLAGS+=	-DPLATFORM_CONFIG_H=\"config_freebsd.h\"
+CFLAGS+=	-I..
+LDADD= -larchive -lz -lbz2
+CFLAGS+= -static -g -O2 -Wall
+CFLAGS+= -I${.OBJDIR}
+CFLAGS+= -I${TAR_SRCDIR}
+
+# Uncomment to link against dmalloc
+#LDADD+= -L/usr/local/lib -ldmalloc
+#CFLAGS+= -I/usr/local/include -DUSE_DMALLOC
+WARNS=6
+
+check test:	bsdtar_test
+	./bsdtar_test -p ${.OBJDIR}/../bsdtar -r ${.CURDIR}
+
+list.h: ${TESTS} Makefile
+	(cd ${.CURDIR}; cat ${TESTS}) | grep DEFINE_TEST > list.h
+
+clean:
+	rm -f *.out
+	rm -f *.o
+	rm -f *.core
+	rm -f *~
+	rm -f list.h
+	rm -f archive.h ../archive.h
+	-chmod -R +w /tmp/bsdtar_test.*
+	rm -rf /tmp/bsdtar_test.*
+
+.include <bsd.prog.mk>
--- /dev/null
+++ usr.bin/tar/test/test_0.c
@@ -0,0 +1,62 @@
+/*-
+ * 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.2.1 2008/08/10 06:53:28 kientzle Exp $");
+
+/*
+ * This first test does basic sanity checks on the environment.  For
+ * most of these, we just exit on failure.
+ */
+
+DEFINE_TEST(test_0)
+{
+	struct stat st;
+
+	failure("File %s does not exist?!", testprog);
+	if (!assertEqualInt(0, stat(testprog, &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. */
+}
--- /dev/null
+++ usr.bin/tar/test/test.h
@@ -0,0 +1,154 @@
+/*
+ * 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.3.2.3 2008/08/27 04:59:00 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)
+/* 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 <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+#include <wchar.h>
+
+#ifdef USE_DMALLOC
+#include <dmalloc.h>
+#endif
+
+/* No non-FreeBSD platform will have __FBSDID, so just define it here. */
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>  /* For __FBSDID */
+#else
+#define	__FBSDID(a)     /* null */
+#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)   test_assert(__FILE__, __LINE__, (e), #e, NULL)
+
+/* Assert two integers are the same.  Reports value of each one if not. */
+#define assertEqualInt(v1,v2)   \
+  test_assert_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)   \
+  test_assert_equal_string(__FILE__, __LINE__, (v1), #v1, (v2), #v2, NULL)
+/* As above, but v1 and v2 are wchar_t * */
+#define assertEqualWString(v1,v2)   \
+  test_assert_equal_wstring(__FILE__, __LINE__, (v1), #v1, (v2), #v2, NULL)
+/* As above, but raw blocks of bytes. */
+#define assertEqualMem(v1, v2, l)	\
+  test_assert_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		\
+  test_setup(__FILE__, __LINE__);test_assert_equal_file
+/* Assert that a file is empty; supports printf-style arguments. */
+#define assertEmptyFile		\
+  test_setup(__FILE__, __LINE__);test_assert_empty_file
+/* Assert that a file is not empty; supports printf-style arguments. */
+#define assertNonEmptyFile		\
+  test_setup(__FILE__, __LINE__);test_assert_non_empty_file
+/* Assert that a file exists; supports printf-style arguments. */
+#define assertFileExists		\
+  test_setup(__FILE__, __LINE__);test_assert_file_exists
+/* Assert that a file exists; supports printf-style arguments. */
+#define assertFileNotExists		\
+  test_setup(__FILE__, __LINE__);test_assert_file_not_exists
+/* Assert that file contents match a string; supports printf-style arguments. */
+#define assertFileContents             \
+  test_setup(__FILE__, __LINE__);test_assert_file_contents
+
+/*
+ * 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	\
+  test_setup(__FILE__, __LINE__);test_skipping
+
+/* Function declarations.  These are defined in test_utility.c. */
+void failure(const char *fmt, ...);
+void test_setup(const char *, int);
+void test_skipping(const char *fmt, ...);
+int test_assert(const char *, int, int, const char *, void *);
+int test_assert_empty_file(const char *, ...);
+int test_assert_non_empty_file(const char *, ...);
+int test_assert_equal_file(const char *, const char *, ...);
+int test_assert_equal_int(const char *, int, int, const char *, int, const char *, void *);
+int test_assert_equal_string(const char *, int, const char *v1, const char *, const char *v2, const char *, void *);
+int test_assert_equal_wstring(const char *, int, const wchar_t *v1, const char *, const wchar_t *v2, const char *, void *);
+int test_assert_equal_mem(const char *, int, const char *, const char *, const char *, const char *, size_t, const char *, void *);
+int test_assert_file_contents(const void *, int, const char *, ...);
+int test_assert_file_exists(const char *, ...);
+int test_assert_file_not_exists(const char *, ...);
+
+/* Like sprintf, then system() */
+int systemf(const char * fmt, ...);
+
+/* 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. */
+char *testprog;
--- /dev/null
+++ usr.bin/tar/test/test_getdate.c
@@ -0,0 +1,38 @@
+/*-
+ * 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.2.1 2008/08/10 06:53:28 kientzle Exp $");
+
+/*
+ * Verify that the getdate() function works.
+ */
+
+time_t get_date(const char *);
+
+DEFINE_TEST(test_getdate)
+{
+	assertEqualInt(0, get_date("Jan 1, 1970 UTC"));
+	/* TODO: Lots more tests here. */
+}
--- /dev/null
+++ usr.bin/tar/test/test_patterns_2.tgz.uu
@@ -0,0 +1,9 @@
+$FreeBSD: src/usr.bin/tar/test/test_patterns_2.tgz.uu,v 1.1.2.1 2008/08/27 04:59:00 kientzle Exp $
+begin 644 test_patterns_2.tgz
+M'XL(`,P5I4@``^W3T0J",!3&<1]E;[!SYC:?Q\`H2`PS at IZ^F5AV(PFMJ__O
+MYB@;>.:W8X?V;/==9XM\1*0*P:2J59"QCN8ZO:A*4*<NAFA$G?=:F)"QIY?K
+M9:C[U,IP;%?WW0Y-<UI9_SR4^6F/&=DY_UW=Y[H#V_(O4_ZE$T?^_[#(_Y[K
+M&^E_1.^WS'^9'@HCN1I:(O_W_&>Z`]OR?\Y_C!7Y`P``````````````?.,!
+(*>E$>P`H````
+`
+end
--- /dev/null
+++ usr.bin/tar/test/test_patterns.c
@@ -0,0 +1,100 @@
+/*-
+ * 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.1.2.3 2008/08/27 04:59:00 kientzle Exp $");
+
+DEFINE_TEST(test_patterns)
+{
+	int fd, r;
+	const char *reffile2 = "test_patterns_2.tgz";
+	const char *reffile3 = "test_patterns_3.tgz";
+	const char *p;
+
+	/*
+	 * 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
+	 */
+	fd = open("foo", O_CREAT | O_WRONLY, 0644);
+	assert(fd >= 0);
+	close(fd);
+	r = systemf("%s zcfv tar1.tgz foo > tar1a.out 2> tar1a.err", testprog);
+	assertEqualInt(r, 0);
+	r = systemf("%s zxfv 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);
+	p = "/tmp/foo/bar/\n/tmp/foo/bar/baz\n";
+	assertFileContents(p, strlen(p), "tar2a.out");
+	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 xf %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 xf %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 xf %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 xf %s ./tmp/foo/baz > tar3d.out 2> tar3d.err",
+	    testprog, reffile3);
+	assertEqualInt(r, 0);
+	assertEmptyFile("tar3d.out");
+	assertEmptyFile("tar3d.err");
+	assertEqualInt(0, access("tmp/foo/baz/bar", F_OK));
+}
--- /dev/null
+++ usr.bin/tar/test/test_help.c
@@ -0,0 +1,81 @@
+/*-
+ * 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.2.1 2008/08/10 06:53:28 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);
+	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);
+	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);
+	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");
+}
--- /dev/null
+++ usr.bin/tar/test/test_option_T.c
@@ -0,0 +1,145 @@
+/*-
+ * 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.2.2.3 2008/08/25 02:01:47 kientzle Exp $");
+
+static int
+touch(const char *fn)
+{
+	int fd = open(fn, O_RDWR | O_CREAT);
+	failure("Couldn't create file '%s', fd=%d, errno=%d (%s)\n",
+	    fn, fd, errno, strerror(errno));
+	if (!assert(fd > 0))
+		return (0); /* Failure. */
+	close(fd);
+	return (1); /* Success */
+}
+
+DEFINE_TEST(test_option_T)
+{
+	FILE *f;
+	int r;
+
+	/* Create a simple dir heirarchy; bail if anything fails. */
+	if (!assertEqualInt(0, mkdir("d1", 0755))) return;
+	if (!assertEqualInt(0, mkdir("d1/d2", 0755)))	return;
+	if (!touch("d1/f1")) return;
+	if (!touch("d1/f2")) return;
+	if (!touch("d1/d2/f3")) return;
+	if (!touch("d1/d2/f4")) return;
+	if (!touch("d1/d2/f5")) return;
+
+	/* Populate a file list */
+	f = fopen("filelist", "w+");
+	if (!assert(f != NULL))
+		return;
+	fprintf(f, "d1/f1\n");
+	fprintf(f, "d1/d2/f4\n");
+	fclose(f);
+
+	/* Populate a second file list */
+	f = fopen("filelist2", "w+");
+	if (!assert(f != NULL))
+		return;
+	fprintf(f, "d1/d2/f3\n");
+	fprintf(f, "d1/d2/f5\n");
+	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);
+	failure("Failure here probably means that tar can't archive zero-length files without reading them");
+	assert(r == 0);
+	assertEmptyFile("test1.out");
+	assertEmptyFile("test1.err");
+
+	/* Use -x -T to dearchive the files */
+	if (!assertEqualInt(0, mkdir("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/d1/f1");
+	assertFileNotExists("test1/d1/f2");
+	assertFileNotExists("test1/d1/d2/f3");
+	assertFileExists("test1/d1/d2/f4");
+	assertFileNotExists("test1/d1/d2/f5");
+
+	/* Use -r -T to add more files to the archive. */
+	systemf("%s -r -f test1.tar -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 (!assertEqualInt(0, mkdir("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/d1/f1");
+	assertFileNotExists("test3/d1/f2");
+	assertFileExists("test3/d1/d2/f3");
+	assertFileExists("test3/d1/d2/f4");
+	assertFileExists("test3/d1/d2/f5");
+
+	/* Use -x -T to dearchive the files (verify -x -T together) */
+	if (!assertEqualInt(0, mkdir("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/d1/f1");
+	assertFileNotExists("test2/d1/f2");
+	assertFileNotExists("test2/d1/d2/f3");
+	assertFileExists("test2/d1/d2/f4");
+	assertFileNotExists("test2/d1/d2/f5");
+
+	assertEqualInt(0, mkdir("test4", 0755));
+	assertEqualInt(0, mkdir("test4_out", 0755));
+	assertEqualInt(0, mkdir("test4_out2", 0755));
+	assertEqualInt(0, mkdir("test4/d1", 0755));
+	assertEqualInt(1, touch("test4/d1/foo"));
+
+	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");
+
+	/* TODO: Include some use of -C directory-changing within the filelist. */
+	/* I'm pretty sure -C within the filelist is broken on extract. */
+}


More information about the Midnightbsd-cvs mailing list