summaryrefslogtreecommitdiffstats
path: root/usr.bin/tar
diff options
context:
space:
mode:
authorkientzle <kientzle@FreeBSD.org>2004-04-05 21:32:18 +0000
committerkientzle <kientzle@FreeBSD.org>2004-04-05 21:32:18 +0000
commit2588e5ad7af384b20a9acae84814c857640b02df (patch)
tree94404e37133ce8caf755c405d38fdbfb7a04dcef /usr.bin/tar
parent21f7c2660054dab8275ac2471dbb1055c9cb5c66 (diff)
downloadFreeBSD-src-2588e5ad7af384b20a9acae84814c857640b02df.zip
FreeBSD-src-2588e5ad7af384b20a9acae84814c857640b02df.tar.gz
Initial commit for bsdtar.
Diffstat (limited to 'usr.bin/tar')
-rw-r--r--usr.bin/tar/Makefile21
-rw-r--r--usr.bin/tar/bsdtar.1577
-rw-r--r--usr.bin/tar/bsdtar.c501
-rw-r--r--usr.bin/tar/bsdtar.h101
-rw-r--r--usr.bin/tar/bsdtar_platform.h94
-rw-r--r--usr.bin/tar/matching.c243
-rw-r--r--usr.bin/tar/read.c292
-rw-r--r--usr.bin/tar/util.c160
-rw-r--r--usr.bin/tar/write.c988
9 files changed, 2977 insertions, 0 deletions
diff --git a/usr.bin/tar/Makefile b/usr.bin/tar/Makefile
new file mode 100644
index 0000000..5679f3d
--- /dev/null
+++ b/usr.bin/tar/Makefile
@@ -0,0 +1,21 @@
+# Makefile for bsdtar
+#
+# $FreeBSD$
+#
+DEBUG_FLAGS= -g
+
+PROG= bsdtar
+SRCS= bsdtar.c matching.c read.c util.c write.c
+MAN = bsdtar.1
+
+BINDIR?= /usr/bin
+WARNS?= 6
+LDADD += -larchive -lz -lbz2
+
+.if defined(DMALLOC)
+CFLAGS += -DDMALLOC -I/usr/local/include
+LDADD += -L/usr/local/lib -ldmalloc
+.endif
+
+.include <bsd.prog.mk>
+
diff --git a/usr.bin/tar/bsdtar.1 b/usr.bin/tar/bsdtar.1
new file mode 100644
index 0000000..4577481
--- /dev/null
+++ b/usr.bin/tar/bsdtar.1
@@ -0,0 +1,577 @@
+.\" Copyright (c) 2003 Tim Kientzle
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 5, 2004
+.Dt BSDTAR 1
+.Os
+.Sh NAME
+.Nm bsdtar
+.Nd manipulate tape archives
+.Sh SYNOPSIS
+.Nm
+.Op Ar bundled-flags Ao args Ac
+.Op Ao Ar file Ac | Ao Ar pattern Ac ...
+.Nm
+.Brq Fl c | Fl t | Fl x
+.Op Ar options
+.Op Ar files | patterns | directories
+.Sh DESCRIPTION
+.Nm
+creates and manipulates streaming archive files.
+.Pp
+The first synopsis form shows a
+.Dq bundled
+option word.
+This usage is provided for compatibility with historical implementations.
+See COMPATIBILITY below for details.
+.Pp
+The preferred usage is illustrated in the second synopsis.
+The first option to
+.Nm
+must be a mode indicator from the following list:
+.Bl -tag -compact -width indent
+.It Fl c
+Create a new archive containing the specified items.
+.It Fl r
+Like
+.Fl c ,
+but new entries are appended to the archive specified with the
+.Fl f
+option, which is required.
+If a new entry has the same name as an existing entry, it will normally
+overwrite (replace) that entry on extraction.
+Note that this only works on uncompressed archives stored in regular files.
+.It Fl t
+List archive contents to stdout.
+.It Fl u
+Like
+.Fl r ,
+but new entries are written only if they have a modification date
+newer than the corresponding entry in the archive.
+Note that this only works on uncompressed archives stored in regular files.
+.It Fl x
+Extract to disk from the archive.
+.El
+.Pp
+In
+.Fl c ,
+.Fl r ,
+or
+.Fl u
+mode, each specified file or directory is added to the
+archive in the order specified on the command line.
+By default, the contents of each directory are also archived.
+.Pp
+In extract or list mode, the entire command line
+is read and parsed before the archive is opened.
+The pathnames or patterns on the command line indicate
+which items in the archive should be processed.
+Patterns are shell-style globbing patterns as
+documented in XXXX.
+.Sh OPTIONS
+Unless specifically stated otherwise, options are applicable in
+all operating modes.
+.Bl -tag -width indent
+.It Cm @ Ns Pa archive
+(c and r mode only)
+The specified archive is opened and the entries
+in it will be appended to the current archive.
+As a simple example,
+.Dl Nm Fl c Fl f Pa - Pa newfile Cm @ Ns Pa original.tar
+writes a new archive to standard output containing a file
+.Pa newfile
+and all of the entries from
+.Pa original.tar .
+In contrast,
+.Dl Nm Fl c Fl f Pa - Pa newfile Pa original.tar
+creates a new archive with only two entries.
+Similarly,
+.Dl Nm Fl czf Pa - Fl F Cm pax Cm @ Ns Pa -
+reads an archive from standard input (whose format will be determined
+automatically) and converts it into a gzip-compressed
+pax-format archive on stdout.
+In this way,
+.Nm
+can be used to convert archives from one format to another.
+.It Fl b Ar blocksize
+Specify the block size, in 512-byte records, for tape drive I/O.
+As a rule, this argument is only needed when reading from or writing
+to tape drives, and usually not even then as the default block size of
+20 records (10240 bytes) is very common.
+.It Fl C Ar directory
+Change directories.
+The directory is changed after the archive
+is opened, but before any entries are extracted or written.
+(In particular, it does not affect the interpretation of the
+.Fl f
+option.)
+In create mode, note that
+.Fl C
+options are all processed before any files are read.
+To change directories between files, use
+.Cm C=
+instead.
+.It Cm C= Ns Pa dir
+(c and r mode only)
+Change to the specified directory before adding the following files.
+(Note that this is not an option in the sense of
+.Xr getopt 3 ,
+and is therefore processed as the files are processed.)
+.It Fl -exclude Ar pattern
+Do not process files or directories that match the
+specified pattern.
+Note that exclusions take precedence over patterns or filenames
+specified on the command line.
+.It Fl F Ar format
+(c mode only)
+Use the specified format for the created archive.
+Supported formats include
+.Dq cpio ,
+.Dq pax ,
+.Dq shar ,
+.Dq shardump ,
+and
+.Dq ustar .
+.It Fl f Ar file
+Read the archive from or write the archive to the specified file.
+The filename can be
+.Pa -
+for standard input or standard output.
+.It 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 H
+(c and r mode only)
+Symbolic links named on the command line will be followed; the
+target of the link will be archived, not the link itself.
+.It Fl j
+(c mode only)
+Compress the resulting archive with
+.Xr bzip2 1 .
+Note that, unlike other
+.Nm tar
+implementations, this implementation recognizes bzip2 compression
+automatically when reading archives.
+This option is ignored in extract or list modes.
+.It Fl k
+(x mode only)
+Do not overwrite existing files.
+.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
+(c mode only)
+Issue a warning message unless all links to each file are archived.
+.It Fl m
+(x mode only)
+Do not extract modification time.
+By default, the modification time is set to the time stored in the archive.
+.It Fl n
+(c, r, u modes only)
+Do not recursively archive the contents of directories.
+.It Fl -nodump
+(c and r modes only)
+Honor the nodump file flag by skipping this file.
+.It Fl O
+(x mode only)
+Extracted files are written to standard out rather than
+being extracted to disk.
+.It Fl o
+(x mode only)
+Use the user and group of the user running the program rather
+than those specified in the archive.
+Note that this has no significance unless
+.Fl p
+is specified, and the program is being run by the root user.
+In this case, the file modes and flags from
+the archive will be restored, but ACLs or owner information in
+the archive will be discarded.
+(not yet implemented)
+.It Fl P
+Preserve leading slashes.
+By default, absolute pathnames (those that begin with a / character)
+have the leading slash removed.
+This option suppresses that behavior.
+.It Fl p
+(x mode only)
+Preserve file permissions.
+Attempt to restore the full permissions, including owner, file modes, file
+flags and ACLs, if available, for each item extracted from the archive.
+By default, newly-created regular files have the file mode restored and
+all other types of entries receive default permissions.
+.It Fl U
+(x mode only)
+Unlink files before creating them.
+(Not yet implemented.)
+.It Fl v
+Produce verbose output.
+In create and extract modes,
+.Nm
+will list each file name as it is read from or written to
+the archive.
+In list mode,
+.Nm
+will produce output similar to that of
+.Xr ls 1 .
+Additional
+.Fl v
+options will provide additional detail.
+.It Fl w
+Ask for confirmation for every action.
+.It Fl X
+(c, r, u modes)
+When visiting subdirectories, ignore any that are on different devices.
+.It Fl y
+(c mode only)
+Compress the resulting archive with
+.Xr bzip2 1 .
+.It Fl z
+(c mode only)
+Compress the resulting archive with
+.Xr gzip 1 .
+Note that, unlike other
+.Nm tar
+implementations, this implementation recognizes gzip
+and bzip2 compression automatically when reading archives.
+The
+.Fl j , y , No and Fl z
+options are ignored for extract or list mode.
+.El
+.Sh EXAMPLES
+The following creates a new archive
+called
+.Ar file.tar
+that contains two files
+.Ar source.c
+and
+.Ar source.h :
+.Dl Nm Fl czf Pa file.tar Pa source.c Pa source.h
+.Pp
+To view a detailed table of contents for this
+archive:
+.Dl Nm Fl tvf Pa file.tar
+.Pp
+To extract all entries from the archive on
+the default tape drive:
+.Dl Nm Fl x
+.Pp
+In create mode, the list of files and directories to be archived
+can also include directory change instructions of the form
+.Cm C= Ns Pa foo/baz
+and archive inclusions of the form
+.Cm @ Ns Pa archive-file .
+For example, the command line
+.Dl Nm Fl c Fl f Pa new.tar Pa foo1 Cm @ Ns Pa old.tgz Cm C= Ns Pa /tmp Pa foo2
+will create a new archive
+.Pa new.tar .
+.Nm
+will read the file
+.Pa foo1
+from the current directory and add it to the output archive.
+It will then read each entry from
+.Pa old.tgz
+and add those entries to the output archive.
+Finally, it will switch to the
+.Pa /tmp
+directory and add
+.Pa foo2
+to the output archive.
+.Sh DIAGNOSTICS
+.Ex -std
+.Sh ENVIRONMENT
+The following environment variables affect the execution of
+.Nm :
+.Bl -tag -width ".Ev BLOCKSIZE"
+.It Ev LANG
+The locale to use.
+See
+.Xr environ 7
+for more information.
+.It Ev TZ
+The timezone to use when displaying dates.
+See
+.Xr environ 7
+for more information.
+.El
+.Sh COMPATIBILITY
+The bundled-arguments format is supported for compatibility
+with historic implementations.
+It consists of an initial word (with no leading - character) in which
+each character indicates an option.
+Arguments follow as separate words.
+The order of the arguments must match the order
+of the corresponding characters in the bundled command word.
+For example,
+.Dl Nm Cm tbf 32 Pa file.tar
+specifies three flags
+.Cm t ,
+.Cm b ,
+and
+.Cm f .
+The
+.Cm b
+and
+.Cm f
+flags both require arguments,
+so there must be two additional items
+on the command line. The
+.Ar 32
+is the argument to the
+.Cm b
+flag, and
+.Ar file.tar
+is the argument to the
+.Cm f
+flag.
+.Pp
+The mode options c, r, t, u, and x and the options
+b, f, l, m, o, v, and w are implemented to be compatible
+with SUSv2.
+.Pp
+On systems that support getopt_long(), additional long options
+are available to improve compatibility with other tar implementations.
+.Pp
+The
+.Nm
+program reads and writes a variety of streaming archive formats, including:
+.Bl -tag -width indent
+.It Cm cpio
+The octet-oriented cpio format standardized by POSIX.
+.It Cm gnutar
+.Nm
+has limited read support for GNU-format tar archives.
+.It Cm pax interchange
+The pax interchange format is a POSIX-standard tar format that removes
+essentially all of the historic limitations in a standard-conforming fashion.
+This format is supported by standard implementations of
+.Xr pax 1
+as well as by some
+.Nm tar
+programs, including
+.Nm star .
+.It Cm shar
+A
+.Dq shar
+format archive is a shell script that, when executed on a POSIX-compliant
+system, will recreate the specified files.
+Note that shar-format archives will be plain text files only if all of the
+files being archived are themselves plain text files.
+.It Cm shardump
+This format is similar to shar but encodes binary files so that the result
+will be a plain text file regardless of the file contents.
+It also includes additional shell commands that attempt to reproduce as
+many file attributes as possible, including owner, mode, and flags.
+.It Cm tar
+.Nm
+can read most older tar archives, including many that violate
+the POSIX standard.
+.It Cm ustar
+The format first standardized by POSIX.
+It has the following limitations:
+.Bl -bullet -compact
+.It
+Device major and minor numbers are limited to 21 bits.
+Nodes with larger numbers will not be added to the archive.
+.It
+Path names in the archive are limited to 255 bytes.
+(Shorter if there is no / character in exactly the right place.)
+.It
+Symbolic links and hard links are stored in the archive with
+the name of the referenced file.
+This name is limited to 100 bytes.
+.It
+Extended attributes, file flags, and other extended
+security information cannot be stored.
+.It
+Archive entries are limited to 2 gigabytes in size.
+.El
+Note that the pax interchange has none of these restrictions.
+.Nm
+also supports a variety of extensions to this format
+used by particular archivers.
+In particular, it supports base-256 values in certain numeric fields.
+This essentially removes the limitations on file size, modification time,
+and device numbers.
+.El
+.Sh SEE ALSO
+.Xr ar 1 ,
+.Xr bzip2 1 ,
+.Xr gzip 1 ,
+.Xr mt 1 ,
+.Xr pax 1 ,
+.Xr shar 1 ,
+.Xr libarchive 3 ,
+.Xr tar 5 .
+.Sh STANDARDS
+There is no current POSIX standard for the tar command; it appeared
+in SUSv2 but was dropped from SUSv3.
+The options used by this implementation were developed by surveying a
+number of existing tar implementations as well as the old SUSv2 specification
+for tar and the current SUSv3 specification for pax.
+.Pp
+The ustar and pax interchange file formats are defined by
+.St -p1003.1-2001
+for the pax command.
+.Sh BUGS
+The
+.Fl l
+and
+.Fl o
+options follow POSIX.
+GNU tar's
+.Fl l
+and
+.Fl o
+options do not.
+(This is, of course, a bug in GNU tar and not bsdtar.)
+.Pp
+The distinction between the
+.Fl C Pa dir
+option and the
+.Cm C= Ns Pa dir
+operation is prompted by the use of
+.Xr getopt_long 3
+for parsing the command line.
+Recall that
+.Xr getopt_long 3
+processes all options before all non-options.
+In particular,
+.Cm C= Ns Pa dir
+is not an option, and is therefore processed in the order it appears
+on the command line.
+In contrast,
+.Fl C Pa dir
+is an option, and therefore, in accordance with POSIX
+conventions, is handled in a manner that does not
+depend on the order of command-line options.
+This behavior differs from that of implementations that do
+not follow standard getopt argument parsing conventions.
+.Pp
+Since many options depend on the particular operating mode,
+the mode option itself must be specified first on the command line.
+This allows for more accurate detection and reporting of
+incorrect option usage.
+.Pp
+All archive output is written in correctly-sized blocks, even
+if the output is being compressed.
+Whether or not the last output block is padded to a full
+block size varies depending on the format and the
+output device.
+For tar and cpio formats, the last block of output is padded
+to a full block size if the output is being
+written to standard output or to a character or block device such as
+a tape drive.
+If the output is being written to a regular file, the last block
+will not be padded.
+Many compressors, including
+.Xr gzip 1
+and
+.Xr bzip2 1 ,
+complain about the null padding when decompressing an archive created by
+.Nm ,
+although they still extract it correctly.
+.Pp
+The compression and decompression is implemented internally, so
+there may be insignificant differences between the compressed output
+generated by
+.Dl Nm Fl czf Pa - file
+and that generated by
+.Dl Nm Fl cf Pa - file | Nm gzip
+.Pp
+The default should be to read and write archives to the standard I/O paths,
+but tradition dictates otherwise.
+.Pp
+The
+.Cm r
+and
+.Cm u
+modes require that the archive be uncompressed
+and located in a regular file on disk.
+Other archives can be modified using
+.Cm c
+mode with the
+.Pa @archive-file
+extension.
+.Pp
+To archive a file called
+.Pa C=foo ,
+you must specify it as
+.Pa ./C=foo
+on the command line.
+Similarly, to archive a file called
+.Pa @foo
+or
+.Pa -foo
+you must specify it as
+.Pa ./@foo
+or
+.Pa ./-foo ,
+respectively.
+.Pp
+In create mode, a leading
+.Pa ./
+is always removed.
+A leading
+.Pa /
+is stripped unless the
+.Fl P
+option is specified.
+.Pp
+There needs to be better support for file selection on both create
+and extract.
+.Pp
+There is not yet any support for multi-volume archives or sparse files.
+.Pp
+All features should be available using only short options in order
+to enhance portability to platforms that lack
+.Fn getopt_long .
+.Pp
+There are alternative long options for many of the short options that
+are deliberately not documented.
+.Sh HISTORY
+A
+.Nm tar
+command appeared in Sixth Edition Unix.
+There have been numerous other implementations,
+many of which extended the file format.
+John Gilmore's
+.Nm pdtar
+public-domain implementation (circa November, 1987)
+was quite influential, and formed the basis of GNU tar.
+GNU tar was included as the standard system tar
+in FreeBSD beginning with FreeBSD 1.0.
+.Pp
+This is a complete re-implementation based on the
+.Xr libarchive 3
+library.
diff --git a/usr.bin/tar/bsdtar.c b/usr.bin/tar/bsdtar.c
new file mode 100644
index 0000000..98c3c83
--- /dev/null
+++ b/usr.bin/tar/bsdtar.c
@@ -0,0 +1,501 @@
+/*-
+ * Copyright (c) 2003-2004 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "bsdtar_platform.h"
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <archive.h>
+#include <archive_entry.h>
+#include <dirent.h>
+#ifdef DMALLOC
+#include <dmalloc.h>
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#ifdef HAVE_GETOPT_LONG
+#include <getopt.h>
+#endif
+#include <locale.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "bsdtar.h"
+
+static void long_help(void);
+static void only_mode(char mode, char opt, const char *valid);
+static const char *progname;
+static char ** rewrite_argv(int *argc, char ** src_argv,
+ const char *optstring);
+
+const char *tar_opts = "b:C:cF:f:HhjkLlmnOoPprtUuvwXxyZz";
+
+#ifdef HAVE_GETOPT_LONG
+/*
+ * These long options are deliberately not documented. They are
+ * provided only to make life easier for people using GNU tar. The
+ * only long options documented in the manual page are the ones with
+ * no corresponding short option (currently, --exclude, --nodump, and
+ * --fast-read).
+ *
+ * XXX TODO: Provide short options for --exclude, --nodump and --fast-read
+ * so that bsdtar is usable on systems that do not have (or do not want
+ * to use) getopt_long().
+ */
+
+#define OPTION_EXCLUDE 1
+#define OPTION_FAST_READ 2
+#define OPTION_NODUMP 3
+
+const struct option tar_longopts[] = {
+ { "absolute-paths", no_argument, NULL, 'P' },
+ { "append", no_argument, NULL, 'r' },
+ { "block-size", required_argument, NULL, 'b' },
+ { "bunzip2", no_argument, NULL, 'j' },
+ { "bzip", no_argument, NULL, 'j' },
+ { "bzip2", no_argument, NULL, 'j' },
+ { "cd", required_argument, NULL, 'C' },
+ { "confirmation", no_argument, NULL, 'w' },
+ { "create", no_argument, NULL, 'c' },
+ { "directory", required_argument, NULL, 'C' },
+ { "exclude", required_argument, NULL, OPTION_EXCLUDE },
+ { "extract", no_argument, NULL, 'x' },
+ { "fast-read", no_argument, NULL, OPTION_FAST_READ },
+ { "file", required_argument, NULL, 'f' },
+ { "format", required_argument, NULL, 'F' },
+ { "gunzip", no_argument, NULL, 'z' },
+ { "gzip", no_argument, NULL, 'z' },
+ { "help", no_argument, NULL, 'h' },
+ { "interactive", no_argument, NULL, 'w' },
+ { "keep-old-files", no_argument, NULL, 'k' },
+ { "list", no_argument, NULL, 't' },
+ { "modification-time", no_argument, NULL, 'm' },
+ { "nodump", no_argument, NULL, OPTION_NODUMP },
+ { "norecurse", no_argument, NULL, 'n' },
+ { "preserve-permissions", no_argument, NULL, 'p' },
+ { "same-permissions", no_argument, NULL, 'p' },
+ { "to-stdout", no_argument, NULL, 'O' },
+ { "update", no_argument, NULL, 'u' },
+ { "verbose", no_argument, NULL, 'v' },
+ { NULL, 0, NULL, 0 }
+};
+#endif
+
+int
+main(int argc, char **argv)
+{
+ struct bsdtar *bsdtar, bsdtar_storage;
+ struct passwd *pwent;
+ int opt, mode;
+
+ if (setlocale(LC_ALL, "") == NULL)
+ bsdtar_warnc(0, "Failed to set default locale");
+
+ /*
+ * Use a pointer for consistency, but stack-allocated storage
+ * for ease of cleanup.
+ */
+ bsdtar = &bsdtar_storage;
+ memset(bsdtar, 0, sizeof(*bsdtar));
+ bsdtar->fd = -1; /* Mark as "unused" */
+
+ /* Look up uid/uname of current user for future reference */
+ bsdtar->user_uid = geteuid();
+ bsdtar->user_uname = NULL;
+ if ((pwent = getpwuid(bsdtar->user_uid))) {
+ bsdtar->user_uname = (char *)malloc(strlen(pwent->pw_name)+1);
+ if (bsdtar->user_uname)
+ strcpy(bsdtar->user_uname, pwent->pw_name);
+ }
+
+ /* Default: open tape drive. */
+ bsdtar->filename = getenv("TAPE");
+ if (bsdtar->filename == NULL)
+ bsdtar->filename = _PATH_DEFTAPE;
+
+ bsdtar->bytes_per_block = 10240;
+
+ /* Default: preserve mod time on extract */
+ bsdtar->extract_flags = ARCHIVE_EXTRACT_TIME;
+
+ if (bsdtar->user_uid == 0)
+ bsdtar->extract_flags = ARCHIVE_EXTRACT_OWNER;
+
+ progname = *argv;
+
+ /* Rewrite traditional-style tar arguments, if used. */
+ argv = rewrite_argv(&argc, argv, tar_opts);
+
+ bsdtar->argv = argv;
+ bsdtar->argc = argc;
+
+ /* First option must be mode selector */
+#ifdef HAVE_GETOPT_LONG
+ mode = getopt_long(bsdtar->argc, bsdtar->argv, tar_opts, tar_longopts,
+ NULL);
+#else
+ mode = getopt(bsdtar->argc, bsdtar->argv, tar_opts);
+#endif
+
+ switch (mode) {
+ case -1:
+ usage();
+ break;
+ case 'h':
+ long_help();
+ break;
+ case 't':
+ bsdtar->verbose = 1;
+ break;
+ case 'c': case 'r': case 'u': case 'x':
+ break;
+ default:
+ fprintf(stderr,
+ "First option must be one of: -c, -r, -t, -u, -x\n");
+ usage();
+ exit(1);
+ }
+
+ /* Process all remaining arguments now. */
+#ifdef HAVE_GETOPT_LONG
+ while ((opt = getopt_long(bsdtar->argc, bsdtar->argv,
+ tar_opts, tar_longopts, NULL)) != -1) {
+#else
+ while ((opt = getopt(bsdtar->argc, bsdtar->argv, tar_opts)) != -1) {
+#endif
+ /* XXX TODO: Augment the compatibility notes below. */
+ switch (opt) {
+ case 'b': /* SUSv2 */
+ bsdtar->bytes_per_block = 512 * atoi(optarg);
+ break;
+ case 'C': /* GNU tar */
+ bsdtar->start_dir = optarg;
+ break;
+#ifdef HAVE_GETOPT_LONG
+ case OPTION_EXCLUDE: /* GNU tar */
+ only_mode(mode, opt, "xtcr");
+ exclude(bsdtar, optarg);
+ break;
+#endif
+ case 'F':
+ only_mode(mode, opt, "c");
+ bsdtar->create_format = optarg;
+ break;
+ case 'f': /* SUSv2 */
+ bsdtar->filename = optarg;
+ if (strcmp(bsdtar->filename, "-") == 0)
+ bsdtar->filename = NULL;
+ break;
+#ifdef HAVE_GETOPT_LONG
+ case OPTION_FAST_READ: /* GNU tar */
+ only_mode(mode, opt, "tx");
+ bsdtar->option_fast_read = 1;
+ break;
+#endif
+ case 'H': /* BSD convention */
+ only_mode(mode, opt, "cr");
+ bsdtar->symlink_mode = 'H';
+ break;
+ case 'k': /* GNU tar */
+ only_mode(mode, opt, "x");
+ bsdtar->extract_flags |= ARCHIVE_EXTRACT_NO_OVERWRITE;
+ break;
+ case 'L': /* BSD convention */
+ only_mode(mode, opt, "cr");
+ bsdtar->symlink_mode = 'L';
+ break;
+ case 'l': /* SUSv2 */
+ only_mode(mode, opt, "cr");
+ bsdtar->option_warn_links = 1;
+ break;
+ case 'm': /* SUSv2 */
+ only_mode(mode, opt, "x");
+ bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_TIME;
+ break;
+ case 'n': /* GNU tar */
+ only_mode(mode, opt, "cr");
+ bsdtar->option_no_subdirs = 1;
+ break;
+#ifdef HAVE_GETOPT_LONG
+ case OPTION_NODUMP: /* star */
+ only_mode(mode, opt, "cr");
+ bsdtar->option_honor_nodump = 1;
+ break;
+#endif
+ case 'O': /* GNU tar */
+ only_mode(mode, opt, "x");
+ bsdtar->option_stdout = 1;
+ break;
+ case 'o': /* SUSv2 */
+ only_mode(mode, opt, "x");
+ bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_OWNER;
+ break;
+#if 0
+ /*
+ * The common BSD -P option is not necessary, since
+ * our default is to archive symlinks, not follow
+ * them. This is convenient, as -P conflicts with GNU
+ * tar anyway.
+ */
+ case 'P': /* BSD convention */
+ /* Default behavior, no option necessary. */
+ break;
+#endif
+ case 'P': /* GNU tar */
+ only_mode(mode, opt, "xcru");
+ bsdtar->option_absolute_paths = 1;
+ break;
+ case 'p': /* GNU tar, star */
+ only_mode(mode, opt, "x");
+ umask(0);
+ bsdtar->extract_flags |= ARCHIVE_EXTRACT_PERM;
+ break;
+ case 'U': /* GNU tar */
+ only_mode(mode, opt, "x");
+ bsdtar->extract_flags |= ARCHIVE_EXTRACT_UNLINK;
+ break;
+ case 'v': /* SUSv2 */
+ bsdtar->verbose++;
+ break;
+ case 'w': /* SUSv2 */
+ bsdtar->option_interactive = 1;
+ break;
+ case 'X': /* -l in GNU tar */
+ only_mode(mode, opt, "cr");
+ bsdtar->option_dont_traverse_mounts = 1;
+ break;
+ case 'j': /* GNU tar */
+ case 'y': /* FreeBSD version of GNU tar */
+ case 'z': /* GNU tar, star */
+ /*
+ * Ignored in x/t modes, used in 'c' mode,
+ * forbidden in r/u modes.
+ */
+ only_mode(mode, opt, "cxt");
+ bsdtar->create_compression = opt;
+ break;
+ case 'Z': /* GNU tar */
+ bsdtar_warnc(0, ".Z compression not supported");
+ usage();
+ break;
+ default:
+ usage();
+ }
+ }
+
+ bsdtar->argc -= optind;
+ bsdtar->argv += optind;
+
+ switch(mode) {
+ case 'c':
+ tar_mode_c(bsdtar);
+ break;
+ case 'r':
+ tar_mode_r(bsdtar);
+ break;
+ case 't':
+ tar_mode_t(bsdtar);
+ break;
+ case 'u':
+ tar_mode_u(bsdtar);
+ break;
+ case 'x':
+ tar_mode_x(bsdtar);
+ break;
+ }
+
+
+
+ if (bsdtar->user_uname != NULL)
+ free(bsdtar->user_uname);
+
+ return 0;
+}
+
+/*
+ * Verify that the mode is correct.
+ */
+static void
+only_mode(char mode, char opt, const char *valid_modes)
+{
+ if (strchr(valid_modes, mode) == NULL)
+ bsdtar_errc(1, 0, "Option -%c is not permitted in mode -%c",
+ opt, mode);
+}
+
+
+/*-
+ * Convert traditional tar arguments into new-style.
+ * For example,
+ * tar tvfb file.tar 32 --exclude FOO
+ * must be converted to
+ * tar -t -v -f file.tar -b 32 --exclude FOO
+ *
+ * This requires building a new argv array. The initial bundled word
+ * gets expanded into a new string that looks like "-t\0-v\0-f\0-b\0".
+ * The new argv array has pointers into this string intermingled with
+ * pointers to the existing arguments. Arguments are moved to
+ * immediately follow their options.
+ *
+ * The optstring argument here is the same one passed to getopt(3).
+ * It is used to determine which option letters have trailing arguments.
+ */
+char **
+rewrite_argv(int *argc, char ** src_argv, const char *optstring)
+{
+ char **new_argv, **dest_argv;
+ const char *p;
+ char *src, *dest;
+
+ if (src_argv[1] == NULL || src_argv[1][0] == '-')
+ return (src_argv);
+
+ *argc += strlen(src_argv[1]) - 1;
+ new_argv = malloc((*argc + 1) * sizeof(new_argv[0]));
+ if (new_argv == NULL)
+ bsdtar_errc(1, errno, "No Memory");
+
+ dest_argv = new_argv;
+ *dest_argv++ = *src_argv++;
+
+ dest = malloc(strlen(*src_argv) * 3);
+ if (dest == NULL)
+ bsdtar_errc(1, errno, "No memory");
+ for (src = *src_argv++; *src != '\0'; src++) {
+ *dest_argv++ = dest;
+ *dest++ = '-';
+ *dest++ = *src;
+ *dest++ = '\0';
+ /* If option takes an argument, insert that into the list. */
+ for (p = optstring; p != NULL && *p != '\0'; p++) {
+ if (*p != *src)
+ continue;
+ if (p[1] != ':') /* No arg required, done. */
+ break;
+ if (*src_argv == NULL) /* No arg available? Error. */
+ bsdtar_errc(1, 0,
+ "Option %c requires an argument",
+ *src);
+ *dest_argv++ = *src_argv++;
+ break;
+ }
+ }
+
+ /* Copy remaining arguments, including trailing NULL. */
+ while ((*dest_argv++ = *src_argv++) != NULL)
+ ;
+
+ return (new_argv);
+}
+
+void
+usage(void)
+{
+ const char *p;
+
+ p = strrchr(progname, '/');
+ if (p != NULL)
+ p++;
+ else
+ p = progname;
+
+ printf("Basic Usage:\n");
+ printf(" List: %s -tf [archive-filename]\n", p);
+ printf(" Extract: %s -xf [archive-filename]\n", p);
+ printf(" Create: %s -cf [archive-filename] [filenames...]\n", p);
+ printf(" Help: %s -h\n", p);
+ exit(1);
+}
+
+static const char *long_help_msg[] = {
+ "First option must be a mode specifier:\n",
+ " -c Create -r Add/Replace -t List -u Update -x Extract\n",
+ "Common Options:\n",
+ " -b # Use # 512-byte records per I/O block\n",
+ " -f <filename> Location of archive (default " _PATH_DEFTAPE ")\n",
+ " -v Verbose\n",
+ " -w Interactive\n",
+ "Create: %p -c [options] [<file> | <dir> | @<archive> | C=<dir> ]\n",
+ " <file>, <dir> add these items to archive\n",
+ " -z, -j Compress archive with gzip/bzip2\n",
+ " -F {ustar|pax|cpio|shar} Select archive format\n",
+ " --exclude <pattern> Skip files that match pattern\n",
+ " C=<dir> Change to <dir> before processing remaining files\n",
+ " @<archive> Add entries from <archive> to output\n",
+ "List: %p -t [options] [<patterns>]\n",
+ " <patterns> If specified, list only entries that match\n",
+ "Extract: %p -x [options] [<patterns>]\n",
+ " <patterns> If specified, extract only entries that match\n",
+ " -k Keep (don't overwrite) existing files\n",
+ " -m Don't restore modification times\n",
+ " -O Write entries to stdout, don't restore to disk\n",
+ " -p Restore permissions (including ACLs, owner, file flags)\n",
+ NULL
+};
+
+
+static void
+long_help(void)
+{
+ const char *prog;
+ const char *p;
+ const char **msg;
+
+ prog = strrchr(progname, '/');
+ if (prog != NULL)
+ prog++;
+ else
+ prog = progname;
+
+ printf("%s: manipulate archive files\n", prog);
+
+ for (msg = long_help_msg; *msg != NULL; msg++) {
+ for (p = *msg; p != NULL; p++) {
+ if (*p == '\0')
+ break;
+ else if (*p == '%') {
+ if (p[1] == 'p') {
+ fputs(prog, stdout);
+ p++;
+ } else
+ putchar('%');
+ } else
+ putchar(*p);
+ }
+ }
+}
+
+const char *
+bsdtar_progname(void)
+{
+ return (progname);
+}
diff --git a/usr.bin/tar/bsdtar.h b/usr.bin/tar/bsdtar.h
new file mode 100644
index 0000000..428c1b4
--- /dev/null
+++ b/usr.bin/tar/bsdtar.h
@@ -0,0 +1,101 @@
+/*-
+ * Copyright (c) 2003-2004 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name(s) of the author(s) may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * 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$
+ */
+
+#include <archive.h>
+#include <stdio.h>
+
+/* Data for exclusion/inclusion handling: defined in matching.c */
+struct matching;
+struct links_entry;
+struct archive_dir_entry;
+
+/*
+ * The internal state for the "bsdtar" program. This is registered
+ * with the 'archive' structure so that this information will be
+ * available to the read/write callbacks.
+ */
+struct bsdtar {
+ /* Options */
+ const char *filename; /* -f filename */
+ const char *create_format; /* -F format */
+ const char *start_dir; /* -C dir */
+ int bytes_per_block; /* -b block_size */
+ int records_per_block;
+ int verbose; /* -v */
+ int extract_flags; /* Flags for extract operation */
+ char symlink_mode; /* H or L, per BSD conventions */
+ char create_compression; /* j, y, or z */
+ char option_absolute_paths; /* -P */
+ char option_dont_traverse_mounts; /* -X */
+ char option_fast_read; /* --fast-read */
+ char option_honor_nodump; /* --nodump */
+ char option_interactive; /* -w */
+ char option_no_subdirs; /* -d */
+ char option_stdout; /* -p */
+ char option_warn_links; /* -l */
+
+ /* If >= 0, then close this when done. */
+ int fd;
+
+ /* Miscellaneous state information */
+ size_t u_width; /* for 'list_item' */
+ size_t gs_width; /* For 'list_item' */
+ char *user_uname; /* User running this program */
+ uid_t user_uid; /* UID running this program */
+ int argc;
+ char **argv;
+
+ struct matching *matching;
+
+ struct links_entry *links_head;
+ struct archive_dir_entry *archive_dir_head, *archive_dir_tail;
+};
+
+const char *bsdtar_progname(void);
+void bsdtar_errc(int _eval, int _code, const char *fmt, ...);
+void bsdtar_warnc(int _code, const char *fmt, ...);
+void cleanup_exclusions(struct bsdtar *);
+void exclude(struct bsdtar *, const char *pattern);
+int excluded(struct bsdtar *, const char *pathname);
+void include(struct bsdtar *, const char *pattern);
+
+void safe_fprintf(FILE *, const char *fmt, ...);
+
+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);
+void usage(void);
+int yes(const char *fmt, ...);
+
diff --git a/usr.bin/tar/bsdtar_platform.h b/usr.bin/tar/bsdtar_platform.h
new file mode 100644
index 0000000..fbabedb
--- /dev/null
+++ b/usr.bin/tar/bsdtar_platform.h
@@ -0,0 +1,94 @@
+/*-
+ * Copyright (c) 2003-2004 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+/*
+ * This header is the first thing included in any of the bsdtar
+ * source files. As far as possible, platform-specific issues should
+ * be dealt with here and not within individual source files.
+ */
+
+#ifndef BSDTAR_PLATFORM_H_INCLUDED
+#define BSDTAR_PLATFORM_H_INCLUDED
+
+/* FreeBSD-specific definitions. */
+#ifdef __FreeBSD__
+#include <sys/cdefs.h> /* For __FBSDID */
+#include <paths.h> /* For _PATH_DEFTAPE */
+
+#define HAVE_CHFLAGS 1
+
+#if __FreeBSD__ > 4
+#define HAVE_GETOPT_LONG 1
+#define HAVE_POSIX_ACL 1
+#endif
+
+/*
+ * We need to be able to display a filesize using printf(). The type
+ * and format string here must be compatible with one another and
+ * large enough for any file.
+ */
+#include <inttypes.h> /* for uintmax_t, if it exists */
+#ifdef UINTMAX_MAX
+#define BSDTAR_FILESIZE_TYPE uintmax_t
+#define BSDTAR_FILESIZE_PRINTF "%ju"
+#else
+#define BSDTAR_FILESIZE_TYPE unsigned long long
+#define BSDTAR_FILESIZE_PRINTF "%llu"
+#endif
+
+#endif /* __FreeBSD__ */
+
+/* No non-FreeBSD platform will have __FBSDID, so just define it here. */
+#ifndef __FreeBSD__
+#define __FBSDID(a) /* null */
+#endif
+
+/* Linux */
+#ifdef LINUX
+#include <stdint.h> /* for uintmax_t */
+#define BSDTAR_FILESIZE_TYPE uintmax_t
+#define BSDTAR_FILESIZE_PRINTF "%ju"
+/* XXX get fnmatch GNU extensions (FNM_LEADING_DIR)
+ * (should probably use AC_FUNC_FNMATCH_GNU once using autoconf...) */
+#define _GNU_SOURCE
+#define _PATH_DEFTAPE "/dev/st0"
+#define HAVE_GETOPT_LONG 1
+#define st_atimespec st_atim
+#define st_mtimespec st_mtim
+#define st_ctimespec st_ctim
+#endif
+
+/*
+ * XXX TODO: Use autoconf to handle non-FreeBSD platforms.
+ *
+ * #if !defined(__FreeBSD__)
+ * #include "config.h"
+ * #endif
+ */
+
+#endif /* !ARCHIVE_H_INCLUDED */
diff --git a/usr.bin/tar/matching.c b/usr.bin/tar/matching.c
new file mode 100644
index 0000000..36c73ce
--- /dev/null
+++ b/usr.bin/tar/matching.c
@@ -0,0 +1,243 @@
+/*-
+ * Copyright (c) 2003-2004 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "bsdtar_platform.h"
+__FBSDID("$FreeBSD$");
+
+#ifdef DMALLOC
+#include <dmalloc.h>
+#endif
+#include <errno.h>
+#include <fnmatch.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "bsdtar.h"
+
+struct match {
+ struct match *next;
+ int matches;
+ char pattern[1];
+};
+
+struct matching {
+ struct match *exclusions;
+ int exclusions_count;
+ struct match *inclusions;
+ int inclusions_count;
+ int inclusions_unmatched_count;
+};
+
+
+static void add_pattern(struct match **list, const char *pattern);
+static void initialize_matching(struct bsdtar *);
+static int match_exclusion(struct match *, const char *pathname);
+static int match_inclusion(struct match *, const char *pathname);
+
+/*
+ * The matching logic here needs to be re-thought. I started
+ * out to try to mimic gtar's matching logic, but found it wasn't
+ * really consistent. In particular 'tar -t' and 'tar -x' interpret
+ * patterns on the command line as anchored, but --exclude doesn't.
+ */
+
+/*
+ * Utility functions to manage exclusion/inclusion patterns
+ */
+
+void
+exclude(struct bsdtar *bsdtar, const char *pattern)
+{
+ struct matching *matching;
+
+ if (bsdtar->matching == NULL)
+ initialize_matching(bsdtar);
+ matching = bsdtar->matching;
+ add_pattern(&(matching->exclusions), pattern);
+ matching->exclusions_count++;
+}
+
+void
+include(struct bsdtar *bsdtar, const char *pattern)
+{
+ struct matching *matching;
+
+ if (bsdtar->matching == NULL)
+ initialize_matching(bsdtar);
+ matching = bsdtar->matching;
+ add_pattern(&(matching->inclusions), pattern);
+ matching->inclusions_count++;
+ matching->inclusions_unmatched_count++;
+}
+
+static void
+add_pattern(struct match **list, const char *pattern)
+{
+ struct match *match;
+
+ match = malloc(sizeof(*match) + strlen(pattern) + 1);
+ if (match == NULL)
+ bsdtar_errc(1, errno, "Out of memory");
+ if (pattern[0] == '/')
+ pattern++;
+ strcpy(match->pattern, pattern);
+ match->next = *list;
+ *list = match;
+ match->matches = 0;
+}
+
+
+int
+excluded(struct bsdtar *bsdtar, const char *pathname)
+{
+ struct matching *matching;
+ struct match *match;
+ struct match *matched;
+
+ matching = bsdtar->matching;
+ if (matching == NULL)
+ return (0);
+
+ /* Exclusions take priority */
+ for (match = matching->exclusions; match != NULL; match = match->next){
+ if (match_exclusion(match, pathname))
+ return (1);
+ }
+
+ /* Then check for inclusions */
+ matched = NULL;
+ for (match = matching->inclusions; match != NULL; match = match->next){
+ if (match_inclusion(match, pathname)) {
+ /*
+ * If this pattern has never been matched,
+ * then we're done.
+ */
+ if (match->matches == 0) {
+ match->matches++;
+ matching->inclusions_unmatched_count++;
+ return (0);
+ }
+ /*
+ * Otherwise, remember the match but keep checking
+ * in case we can tick off an unmatched pattern.
+ */
+ matched = match;
+ }
+ }
+ /*
+ * We didn't find a pattern that had never been matched, but
+ * we did find a match, so count it and exit.
+ */
+ if (matched != NULL) {
+ matched->matches++;
+ return (0);
+ }
+
+ /* If there were inclusions, default is to exclude. */
+ if (matching->inclusions != NULL)
+ return (1);
+
+ /* No explicit inclusions, default is to match. */
+ return (0);
+}
+
+/*
+ * This is a little odd, but it matches the default behavior of
+ * gtar. In particular, 'a*b' will match 'foo/a1111/222b/bar'
+ *
+ * XXX TODO: fnmatch isn't the most portable thing around, and even
+ * worse, FNM_LEADING_DIR is a non-POSIX extension. <sigh> Thus, the
+ * following two functions need to eventually be replaced with code
+ * that does not rely on fnmatch().
+ */
+int
+match_exclusion(struct match *match, const char *pathname)
+{
+ const char *p;
+
+ if (*match->pattern == '*' || *match->pattern == '/')
+ return (fnmatch(match->pattern, pathname, FNM_LEADING_DIR) == 0);
+
+ for (p = pathname; p != NULL; p = strchr(p, '/')) {
+ if (*p == '/')
+ p++;
+ if (fnmatch(match->pattern, p, FNM_LEADING_DIR) == 0)
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Again, mimic gtar: inclusions are always anchored (have to match
+ * the beginning of the path) even though exclusions are not anchored.
+ */
+int
+match_inclusion(struct match *match, const char *pathname)
+{
+ return (fnmatch(match->pattern, pathname, FNM_LEADING_DIR) == 0);
+}
+
+void
+cleanup_exclusions(struct bsdtar *bsdtar)
+{
+ struct match *p, *q;
+
+ if (bsdtar->matching) {
+ p = bsdtar->matching->inclusions;
+ while (p != NULL) {
+ q = p;
+ p = p->next;
+ free(q);
+ }
+ p = bsdtar->matching->exclusions;
+ while (p != NULL) {
+ q = p;
+ p = p->next;
+ free(q);
+ }
+ free(bsdtar->matching);
+ }
+}
+
+static void
+initialize_matching(struct bsdtar *bsdtar)
+{
+ bsdtar->matching = malloc(sizeof(*bsdtar->matching));
+ if (bsdtar->matching == NULL)
+ bsdtar_errc(1, errno, "No memory");
+ memset(bsdtar->matching, 0, sizeof(*bsdtar->matching));
+}
+
+int
+unmatched_inclusions(struct bsdtar *bsdtar)
+{
+ struct matching *matching;
+
+ matching = bsdtar->matching;
+ if (matching == NULL)
+ return (0);
+ return (matching->inclusions_unmatched_count);
+}
diff --git a/usr.bin/tar/read.c b/usr.bin/tar/read.c
new file mode 100644
index 0000000..481238d
--- /dev/null
+++ b/usr.bin/tar/read.c
@@ -0,0 +1,292 @@
+/*-
+ * Copyright (c) 2003-2004 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "bsdtar_platform.h"
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <archive.h>
+#include <archive_entry.h>
+#ifdef DMALLOC
+#include <dmalloc.h>
+#endif
+#include <errno.h>
+#include <grp.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "bsdtar.h"
+
+static void list_item_verbose(struct bsdtar *, struct archive_entry *);
+static void read_archive(struct bsdtar *bsdtar, char mode);
+
+void
+tar_mode_t(struct bsdtar *bsdtar)
+{
+ read_archive(bsdtar, 't');
+}
+
+void
+tar_mode_x(struct bsdtar *bsdtar)
+{
+ read_archive(bsdtar, 'x');
+}
+
+/*
+ * Handle 'x' and 't' modes.
+ */
+void
+read_archive(struct bsdtar *bsdtar, char mode)
+{
+ struct archive *a;
+ struct archive_entry *entry;
+ int format;
+ const char *name;
+ int r;
+
+ while (*bsdtar->argv) {
+ include(bsdtar, *bsdtar->argv);
+ bsdtar->argv++;
+ }
+
+ format = -1;
+
+ a = archive_read_new();
+ archive_read_support_compression_all(a);
+ archive_read_support_format_all(a);
+ if (archive_read_open_file(a, bsdtar->filename, bsdtar->bytes_per_block))
+ bsdtar_errc(1, 0, "Error opening archive: %s",
+ archive_error_string(a));
+
+ if (bsdtar->verbose > 2)
+ fprintf(stdout, "Compression: %s\n",
+ archive_compression_name(a));
+
+ if (bsdtar->start_dir != NULL && chdir(bsdtar->start_dir))
+ bsdtar_errc(1, errno, "chdir(%s) failed", bsdtar->start_dir);
+
+ for (;;) {
+ /* Support --fast-read option */
+ if (bsdtar->option_fast_read &&
+ unmatched_inclusions(bsdtar) == 0)
+ break;
+
+ r = archive_read_next_header(a, &entry);
+ if (r == ARCHIVE_EOF)
+ break;
+ if (r == ARCHIVE_WARN)
+ bsdtar_warnc(0, "%s", archive_error_string(a));
+ if (r == ARCHIVE_FATAL) {
+ bsdtar_warnc(0, "%s", archive_error_string(a));
+ break;
+ }
+ if (r == ARCHIVE_RETRY) {
+ /* Retryable error: try again */
+ bsdtar_warnc(0, "%s", archive_error_string(a));
+ bsdtar_warnc(0, "Retrying...");
+ continue;
+ }
+
+ if (bsdtar->verbose > 2 && format != archive_format(a)) {
+ format = archive_format(a);
+ fprintf(stdout, "Archive Format: %s\n",
+ archive_format_name(a));
+ }
+
+ if (excluded(bsdtar, archive_entry_pathname(entry)))
+ continue;
+
+ name = archive_entry_pathname(entry);
+ if (name[0] == '/' && !bsdtar->option_absolute_paths) {
+ name++;
+ archive_entry_set_pathname(entry, name);
+ }
+
+ if (mode == 't') {
+ if (bsdtar->verbose < 2)
+ safe_fprintf(stdout, "%s",
+ archive_entry_pathname(entry));
+ else
+ list_item_verbose(bsdtar, entry);
+ fflush(stdout);
+ switch (archive_read_data_skip(a)) {
+ case ARCHIVE_OK:
+ break;
+ case ARCHIVE_WARN:
+ case ARCHIVE_RETRY:
+ fprintf(stdout, "\n");
+ bsdtar_warnc(0, "%s", archive_error_string(a));
+ break;
+ case ARCHIVE_FATAL:
+ fprintf(stdout, "\n");
+ bsdtar_errc(1, 0, "%s",
+ archive_error_string(a));
+ break;
+ }
+ fprintf(stdout, "\n");
+ } else {
+ if (bsdtar->option_interactive &&
+ !yes("extract '%s'", archive_entry_pathname(entry)))
+ continue;
+
+ /*
+ * Format here is from SUSv2, including the
+ * deferred '\n'.
+ */
+ if (bsdtar->verbose) {
+ safe_fprintf(stderr, "x %s",
+ archive_entry_pathname(entry));
+ fflush(stderr);
+ }
+ if (bsdtar->option_stdout) {
+ /* TODO: Catch/recover any errors here. */
+ archive_read_data_into_fd(a, 1);
+ } else if (archive_read_extract(a, entry,
+ bsdtar->extract_flags)) {
+ if (!bsdtar->verbose)
+ safe_fprintf(stderr, "%s",
+ archive_entry_pathname(entry));
+ safe_fprintf(stderr, ": %s",
+ archive_error_string(a));
+ if (!bsdtar->verbose)
+ fprintf(stderr, "\n");
+ /*
+ * TODO: Decide how to handle
+ * extraction error... <sigh>
+ */
+ }
+ if (bsdtar->verbose)
+ fprintf(stderr, "\n");
+ }
+ }
+ archive_read_finish(a);
+}
+
+
+/*
+ * Display information about the current file.
+ *
+ * The format here roughly duplicates the output of 'ls -l'.
+ * This is based on SUSv2, where 'tar tv' is documented as
+ * listing additional information in an "unspecified format,"
+ * and 'pax -l' is documented as using the same format as 'ls -l'.
+ */
+static void
+list_item_verbose(struct bsdtar *bsdtar, struct archive_entry *entry)
+{
+ FILE *out = stdout;
+ const struct stat *st;
+ char tmp[100];
+ size_t w;
+ const char *p;
+ time_t tim;
+ static time_t now;
+
+ st = archive_entry_stat(entry);
+
+ /*
+ * We avoid collecting the entire list in memory at once by
+ * listing things as we see them. However, that also means we can't
+ * just pre-compute the field widths. Instead, we start with guesses
+ * and just widen them as necessary. These numbers are completely
+ * arbitrary.
+ */
+ if (!bsdtar->u_width) {
+ bsdtar->u_width = 6;
+ bsdtar->gs_width = 13;
+ }
+ if (!now)
+ time(&now);
+ strmode(st->st_mode, tmp);
+ fprintf(out, "%s %d ", tmp, st->st_nlink);
+
+ /* Use uname if it's present, else uid. */
+ w = 0;
+ p = archive_entry_uname(entry);
+ if (p && *p) {
+ sprintf(tmp, "%s ", p);
+ } else {
+ sprintf(tmp, "%d ", st->st_uid);
+ }
+ w = strlen(tmp);
+ if (w > bsdtar->u_width)
+ bsdtar->u_width = w;
+ fprintf(out, "%-*s", (int)bsdtar->u_width, tmp);
+
+ /* Use gname if it's present, else gid. */
+ w = 0;
+ p = archive_entry_gname(entry);
+ if (p && *p) {
+ fprintf(out, "%s", p);
+ w += strlen(p);
+ } else {
+ sprintf(tmp, "%d", st->st_gid);
+ w += strlen(tmp);
+ fprintf(out, "%s", tmp);
+ }
+
+ /*
+ * Print device number or file size, right-aligned so as to make
+ * total width of group and devnum/filesize fields be gs_width.
+ * If gs_width is too small, grow it.
+ */
+ if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) {
+ sprintf(tmp, "%u,%u", major(st->st_rdev), minor(st->st_rdev));
+ } else {
+ /*
+ * Note the use of platform-dependent macros to format
+ * the filesize here. We need the format string and the
+ * corresponding type for the cast.
+ */
+ sprintf(tmp, BSDTAR_FILESIZE_PRINTF,
+ (BSDTAR_FILESIZE_TYPE)st->st_size);
+ }
+ if (w + strlen(tmp) >= bsdtar->gs_width)
+ bsdtar->gs_width = w+strlen(tmp)+1;
+ fprintf(out, "%*s", (int)(bsdtar->gs_width - w), tmp);
+
+ /* Format the time using 'ls -l' conventions. */
+ tim = (time_t)st->st_mtime;
+ if (tim < now - 6*30*24*60*60 || tim > now + 6*30*24*60*60)
+ strftime(tmp, sizeof(tmp), "%b %e %Y", localtime(&tim));
+ else
+ strftime(tmp, sizeof(tmp), "%b %e %R", localtime(&tim));
+ safe_fprintf(out, " %s %s", tmp, archive_entry_pathname(entry));
+
+ /* Extra information for links. */
+ if (archive_entry_hardlink(entry)) /* Hard link */
+ safe_fprintf(out, " link to %s",
+ archive_entry_hardlink(entry));
+ else if (S_ISLNK(st->st_mode)) /* Symbolic link */
+ safe_fprintf(out, " -> %s", archive_entry_symlink(entry));
+}
diff --git a/usr.bin/tar/util.c b/usr.bin/tar/util.c
new file mode 100644
index 0000000..a9ccdfc
--- /dev/null
+++ b/usr.bin/tar/util.c
@@ -0,0 +1,160 @@
+/*-
+ * Copyright (c) 2003-2004 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "bsdtar_platform.h"
+__FBSDID("$FreeBSD$");
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "bsdtar.h"
+
+static void bsdtar_vwarnc(int code, const char *fmt, va_list ap);
+
+/*
+ * Print a string, taking care with any non-printable characters.
+ */
+
+void
+safe_fprintf(FILE *f, const char *fmt, ...)
+{
+ char *buff;
+ int bufflength;
+ int length;
+ va_list ap;
+ char *p;
+
+ bufflength = 512;
+ buff = malloc(bufflength);
+
+ va_start(ap, fmt);
+ length = vsnprintf(buff, bufflength, fmt, ap);
+ if (length >= bufflength) {
+ bufflength = length+1;
+ buff = realloc(buff, bufflength);
+ length = vsnprintf(buff, bufflength, fmt, ap);
+ }
+ va_end(ap);
+
+ for (p=buff; *p != '\0'; p++) {
+ unsigned char c = *p;
+ if (isprint(c) && c != '\\')
+ putc(c, f);
+ else
+ switch (c) {
+ case '\a': putc('\\', f); putc('a', f); break;
+ case '\b': putc('\\', f); putc('b', f); break;
+ case '\f': putc('\\', f); putc('f', f); break;
+ case '\n': putc('\\', f); putc('n', f); break;
+#if '\r' != '\n'
+ /* On some platforms, \n and \r are the same. */
+ case '\r': putc('\\', f); putc('r', f); break;
+#endif
+ case '\t': putc('\\', f); putc('t', f); break;
+ case '\v': putc('\\', f); putc('v', f); break;
+ case '\\': putc('\\', f); putc('\\', f); break;
+ default:
+ fprintf(f, "\\%03o", c);
+ }
+ }
+ free(buff);
+}
+
+static void
+bsdtar_vwarnc(int code, const char *fmt, va_list ap)
+{
+ const char *p;
+
+ p = strrchr(bsdtar_progname(), '/');
+ if (p != NULL)
+ p++;
+ else
+ p = bsdtar_progname();
+ fprintf(stderr, "%s: ", p);
+
+ vfprintf(stderr, fmt, ap);
+ if (code != 0)
+ fprintf(stderr, ": %s", strerror(code));
+
+ fprintf(stderr, "\n");
+}
+
+void
+bsdtar_warnc(int code, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ bsdtar_vwarnc(code, fmt, ap);
+ va_end(ap);
+}
+
+void
+bsdtar_errc(int eval, int code, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ bsdtar_vwarnc(code, fmt, ap);
+ va_end(ap);
+ exit(eval);
+}
+
+int
+yes(const char *fmt, ...)
+{
+ char buff[32];
+ char *p;
+ ssize_t l;
+
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, " (y/N)? ");
+ fflush(stderr);
+
+ l = read(2, buff, sizeof(buff));
+ buff[l] = 0;
+
+ for (p = buff; *p != '\0'; p++) {
+ if (isspace(*p))
+ continue;
+ switch(*p) {
+ case 'y': case 'Y':
+ return (1);
+ case 'n': case 'N':
+ return (0);
+ default:
+ return (0);
+ }
+ }
+
+ return (0);
+}
diff --git a/usr.bin/tar/write.c b/usr.bin/tar/write.c
new file mode 100644
index 0000000..4ad1509
--- /dev/null
+++ b/usr.bin/tar/write.c
@@ -0,0 +1,988 @@
+/*-
+ * Copyright (c) 2003-2004 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "bsdtar_platform.h"
+__FBSDID("$FreeBSD$");
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#ifdef HAVE_POSIX_ACL
+#include <sys/acl.h>
+#endif
+#include <archive.h>
+#include <archive_entry.h>
+#ifdef DMALLOC
+#include <dmalloc.h>
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <fts.h>
+#include <grp.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "bsdtar.h"
+
+struct links_entry {
+ struct links_entry *next;
+ struct links_entry *previous;
+ int links;
+ dev_t dev;
+ ino_t ino;
+ char *name;
+};
+
+struct archive_dir_entry {
+ struct archive_dir_entry *next;
+ time_t mtime_sec;
+ int mtime_nsec;
+ char *name;
+};
+
+
+static void add_dir_list(struct bsdtar *bsdtar, const char *path,
+ time_t mtime_sec, int mtime_nsec);
+static void create_cleanup(struct bsdtar *);
+static int append_archive(struct bsdtar *, struct archive *,
+ const char *fname);
+static const char * lookup_gname(struct bsdtar *bsdtar, gid_t gid);
+static const char * lookup_uname(struct bsdtar *bsdtar, uid_t uid);
+static int new_enough(struct bsdtar *, const char *path,
+ time_t mtime_sec, int mtime_nsec);
+static void record_hardlink(struct bsdtar *,
+ struct archive_entry *entry, const struct stat *);
+void setup_acls(struct bsdtar *, struct archive_entry *,
+ const char *path);
+void test_for_append(struct bsdtar *);
+static void write_archive(struct archive *, struct bsdtar *);
+static void write_entry(struct bsdtar *, struct archive *,
+ struct stat *, const char *pathname,
+ unsigned pathlen, const char *accpath);
+static int write_file_data(struct archive *, int fd);
+static void write_heirarchy(struct bsdtar *, struct archive *,
+ const char *);
+
+void
+tar_mode_c(struct bsdtar *bsdtar)
+{
+ struct archive *a;
+ int r;
+
+ if (*bsdtar->argv == NULL)
+ bsdtar_errc(1, 0, "no files or directories specified");
+
+ a = archive_write_new();
+
+ /* Support any format that the library supports. */
+ if (bsdtar->create_format == NULL)
+ archive_write_set_format_pax_restricted(a);
+ else {
+ r = archive_write_set_format_by_name(a, bsdtar->create_format);
+ if (r != ARCHIVE_OK) {
+ fprintf(stderr, "Can't use format %s: %s\n",
+ bsdtar->create_format,
+ archive_error_string(a));
+ usage();
+ }
+ }
+
+ archive_write_set_bytes_per_block(a, bsdtar->bytes_per_block);
+
+ switch (bsdtar->create_compression) {
+ case 'j': case 'y':
+ archive_write_set_compression_bzip2(a);
+ break;
+ case 'z':
+ archive_write_set_compression_gzip(a);
+ break;
+ }
+
+ r = archive_write_open_file(a, bsdtar->filename);
+ if (r != ARCHIVE_OK)
+ bsdtar_errc(1, archive_errno(a),
+ archive_error_string(a));
+
+ write_archive(a, bsdtar);
+
+ archive_write_finish(a);
+}
+
+/*
+ * Same as 'c', except we only support tar formats in uncompressed
+ * files on disk.
+ */
+void
+tar_mode_r(struct bsdtar *bsdtar)
+{
+ off_t end_offset;
+ int format;
+ struct archive *a;
+ struct archive_entry *entry;
+
+ /* Sanity-test some arguments and the file. */
+ test_for_append(bsdtar);
+
+ format = ARCHIVE_FORMAT_TAR_PAX_RESTRICTED;
+
+ bsdtar->fd = open(bsdtar->filename, O_RDWR);
+ if (bsdtar->fd < 0)
+ bsdtar_errc(1, errno, "Cannot open %s", bsdtar->filename);
+
+ a = archive_read_new();
+ archive_read_support_compression_all(a);
+ archive_read_support_format_tar(a);
+ archive_read_support_format_gnutar(a);
+ archive_read_open_fd(a, bsdtar->fd, 10240);
+ while (0 == archive_read_next_header(a, &entry)) {
+ if (archive_compression(a) != ARCHIVE_COMPRESSION_NONE) {
+ archive_read_finish(a);
+ close(bsdtar->fd);
+ bsdtar_errc(1, 0,
+ "Cannot append to compressed archive.");
+ }
+ /* Keep going until we hit end-of-archive */
+ format = archive_format(a);
+ }
+
+ end_offset = archive_read_header_position(a);
+ archive_read_finish(a);
+
+ /* Re-open archive for writing */
+ a = archive_write_new();
+ archive_write_set_compression_none(a);
+ /*
+ * Set format to same one auto-detected above, except use
+ * ustar for appending to GNU tar, since the library doesn't
+ * write GNU tar format.
+ */
+ if (format == ARCHIVE_FORMAT_TAR_GNUTAR)
+ format = ARCHIVE_FORMAT_TAR_USTAR;
+ archive_write_set_format(a, format);
+ lseek(bsdtar->fd, end_offset, SEEK_SET);
+ archive_write_open_fd(a, bsdtar->fd);
+
+ write_archive(a, bsdtar);
+
+ archive_write_finish(a);
+ close(bsdtar->fd);
+ bsdtar->fd = -1;
+}
+
+void
+tar_mode_u(struct bsdtar *bsdtar)
+{
+ off_t end_offset;
+ struct archive *a;
+ struct archive_entry *entry;
+ const char *filename;
+ int format;
+ struct archive_dir_entry *p;
+
+ filename = NULL;
+ format = ARCHIVE_FORMAT_TAR_PAX_RESTRICTED;
+
+ /* Sanity-test some arguments and the file. */
+ test_for_append(bsdtar);
+
+ bsdtar->fd = open(bsdtar->filename, O_RDWR);
+ if (bsdtar->fd < 0)
+ bsdtar_errc(1, errno, "Cannot open %s", bsdtar->filename);
+
+ a = archive_read_new();
+ archive_read_support_compression_all(a);
+ archive_read_support_format_tar(a);
+ archive_read_support_format_gnutar(a);
+ archive_read_open_fd(a, bsdtar->fd, bsdtar->bytes_per_block);
+
+ /* Build a list of all entries and their recorded mod times. */
+ while (0 == archive_read_next_header(a, &entry)) {
+ if (archive_compression(a) != ARCHIVE_COMPRESSION_NONE) {
+ archive_read_finish(a);
+ close(bsdtar->fd);
+ bsdtar_errc(1, 0,
+ "Cannot append to compressed archive.");
+ }
+ add_dir_list(bsdtar, archive_entry_pathname(entry),
+ archive_entry_mtime(entry),
+ archive_entry_mtime_nsec(entry));
+ /* Record the last format determination we see */
+ format = archive_format(a);
+ /* Keep going until we hit end-of-archive */
+ }
+
+ end_offset = archive_read_header_position(a);
+ archive_read_finish(a);
+
+ /* Re-open archive for writing. */
+ a = archive_write_new();
+ archive_write_set_compression_none(a);
+ /*
+ * Set format to same one auto-detected above, except that
+ * we don't write GNU tar format, so use ustar instead.
+ */
+ if (format == ARCHIVE_FORMAT_TAR_GNUTAR)
+ format = ARCHIVE_FORMAT_TAR_USTAR;
+ archive_write_set_format(a, format);
+ archive_write_set_bytes_per_block(a, bsdtar->bytes_per_block);
+ lseek(bsdtar->fd, end_offset, SEEK_SET);
+ ftruncate(bsdtar->fd, end_offset);
+ archive_write_open_fd(a, bsdtar->fd);
+
+ write_archive(a, bsdtar);
+
+ archive_write_finish(a);
+ close(bsdtar->fd);
+ bsdtar->fd = -1;
+
+ while (bsdtar->archive_dir_head != NULL) {
+ p = bsdtar->archive_dir_head->next;
+ free(bsdtar->archive_dir_head->name);
+ free(bsdtar->archive_dir_head);
+ bsdtar->archive_dir_head = p;
+ }
+ bsdtar->archive_dir_tail = NULL;
+}
+
+
+/*
+ * Write files/dirs given on command line to opened archive.
+ */
+static void
+write_archive(struct archive *a, struct bsdtar *bsdtar)
+{
+ const char *arg;
+ char *pending_dir;
+
+ pending_dir = NULL;
+
+ if (bsdtar->start_dir != NULL && chdir(bsdtar->start_dir))
+ bsdtar_errc(1, errno, "chdir(%s) failed", bsdtar->start_dir);
+
+ while (*bsdtar->argv) {
+ arg = *bsdtar->argv;
+ if (arg[0] == 'C' && arg[1] == '=') {
+ arg += 2;
+
+ /*-
+ * The logic here for C=<dir> attempts to avoid
+ * chdir() as long as possible. For example:
+ * "C=/foo C=/bar file"
+ * needs chdir("/bar") but not chdir("/foo")
+ * "C=/foo C=bar file"
+ * needs chdir("/foo/bar")
+ * "C=/foo C=bar /file1"
+ * does not need chdir()
+ * "C=/foo C=bar /file1 file2"
+ * needs chdir("/foo/bar") before file2
+ *
+ * The only correct way to handle this is to
+ * record a "pending" chdir request and only
+ * execute the real chdir when a non-absolute
+ * filename is seen on the command line.
+ *
+ * I went to all this work so that programs
+ * that build tar command lines don't have to
+ * worry about C= with non-existent
+ * directories; such requests will only fail
+ * if the directory must be accessed.
+ */
+ if (pending_dir && *arg == '/') {
+ /* The C=/foo C=/bar case; dump first one. */
+ free(pending_dir);
+ pending_dir = NULL;
+ }
+ if (pending_dir) {
+ /* The C=/foo C=bar case; concatenate */
+ char *old_pending = pending_dir;
+ int old_len = strlen(old_pending);
+
+ pending_dir =
+ malloc(old_len + 1 + strlen(arg));
+ strcpy(pending_dir, old_pending);
+ if (pending_dir[old_len - 1] != '/') {
+ pending_dir[old_len] = '/';
+ old_len ++;
+ }
+ strcpy(pending_dir + old_len, arg);
+ } else {
+ /* Easy case: no previously-saved dir. */
+ pending_dir = strdup(arg);
+ }
+ } else {
+ if (pending_dir &&
+ (*arg != '/' || (*arg == '@' && arg[1] != '/'))) {
+ /* Handle a deferred -C request, see
+ * comments above. */
+ if (chdir(pending_dir))
+ bsdtar_errc(1, 0,
+ "could not chdir to '%s'\n",
+ pending_dir);
+ free(pending_dir);
+ pending_dir = NULL;
+ }
+
+ if (*arg == '@')
+ append_archive(bsdtar, a, arg+1);
+ else
+ write_heirarchy(bsdtar, a, arg);
+ }
+ bsdtar->argv++;
+ }
+
+ create_cleanup(bsdtar);
+}
+
+
+/* Copy from specified archive to current archive. */
+static int
+append_archive(struct bsdtar *bsdtar, struct archive *a, const char *filename)
+{
+ struct archive *ina;
+ struct archive_entry *in_entry;
+ int bytes_read, bytes_written;
+ char buff[8192];
+
+ ina = archive_read_new();
+ archive_read_support_format_all(ina);
+ archive_read_support_compression_all(ina);
+ archive_read_open_file(ina, filename, 10240);
+ while (0 == archive_read_next_header(ina, &in_entry)) {
+ if (!new_enough(bsdtar, archive_entry_pathname(in_entry),
+ archive_entry_mtime(in_entry),
+ archive_entry_mtime_nsec(in_entry)))
+ continue;
+ if (excluded(bsdtar, archive_entry_pathname(in_entry)))
+ continue;
+ if (bsdtar->option_interactive &&
+ !yes("copy '%s'", archive_entry_pathname(in_entry)))
+ continue;
+ if (bsdtar->verbose)
+ safe_fprintf(stderr, "a %s",
+ archive_entry_pathname(in_entry));
+ /* XXX handle/report errors XXX */
+ archive_write_header(a, in_entry);
+ bytes_read = archive_read_data(ina, buff, sizeof(buff));
+ while (bytes_read > 0) {
+ bytes_written =
+ archive_write_data(a, buff, bytes_read);
+ if (bytes_written < bytes_read) {
+ bsdtar_warnc( archive_errno(a), "%s",
+ archive_error_string(a));
+ exit(1);
+ }
+ bytes_read =
+ archive_read_data(ina, buff, sizeof(buff));
+ }
+ if (bsdtar->verbose)
+ fprintf(stderr, "\n");
+
+ }
+ if (archive_errno(ina))
+ bsdtar_warnc(0, "Error reading archive %s: %s", filename,
+ archive_error_string(ina));
+
+ return (0); /* TODO: Return non-zero on error */
+}
+
+/*
+ * Add the file or dir heirarchy named by 'path' to the archive
+ */
+static void
+write_heirarchy(struct bsdtar *bsdtar, struct archive *a, const char *path)
+{
+ FTS *fts;
+ FTSENT *ftsent;
+ int ftsoptions;
+ char *fts_argv[2];
+
+ /*
+ * Sigh: fts_open modifies it's first parameter, so we have to
+ * copy 'path' to mutable storage.
+ */
+ fts_argv[0] = strdup(path);
+ fts_argv[1] = NULL;
+ ftsoptions = FTS_PHYSICAL;
+ switch (bsdtar->symlink_mode) {
+ case 'H':
+ ftsoptions |= FTS_COMFOLLOW;
+ break;
+ case 'L':
+ ftsoptions = FTS_COMFOLLOW | FTS_LOGICAL;
+ break;
+ }
+ if (bsdtar->option_dont_traverse_mounts)
+ ftsoptions |= FTS_XDEV;
+
+ fts = fts_open(fts_argv, ftsoptions, NULL);
+
+
+ if (!fts) {
+ bsdtar_warnc(errno, "%s: Cannot open", path);
+ return;
+ }
+
+ while ((ftsent = fts_read(fts))) {
+ switch (ftsent->fts_info) {
+ case FTS_NS:
+ bsdtar_warnc(ftsent->fts_errno, "%s: Could not stat",
+ ftsent->fts_path);
+ break;
+ case FTS_ERR:
+ bsdtar_warnc(ftsent->fts_errno, "%s", ftsent->fts_path);
+ break;
+ case FTS_DNR:
+ bsdtar_warnc(ftsent->fts_errno,
+ "%s: Cannot read directory contents",
+ ftsent->fts_path);
+ break;
+ case FTS_W: /* Skip Whiteout entries */
+ break;
+ case FTS_DC: /* Directory that causes cycle */
+ /* XXX Does this need special handling ? */
+ break;
+ case FTS_D:
+ /*
+ * If this dir is flagged "nodump" and we're
+ * honoring such flags, tell FTS to skip the
+ * entire tree and don't write the entry for the
+ * directory itself.
+ */
+#ifdef HAVE_CHFLAGS
+ if (bsdtar->option_honor_nodump &&
+ (ftsent->fts_statp->st_flags & UF_NODUMP)) {
+ fts_set(fts, ftsent, FTS_SKIP);
+ break;
+ }
+#endif
+
+ /*
+ * In -u mode, we need to check whether this
+ * is newer than what's already in the archive.
+ */
+ if (!new_enough(bsdtar, ftsent->fts_path,
+ ftsent->fts_statp->st_mtime,
+ ftsent->fts_statp->st_mtimespec.tv_nsec))
+ break;
+ /*
+ * If this dir is excluded by a filename
+ * pattern, tell FTS to skip the entire tree
+ * and don't write the entry for the directory
+ * itself.
+ */
+ if (excluded(bsdtar, ftsent->fts_path)) {
+ fts_set(fts, ftsent, FTS_SKIP);
+ break;
+ }
+
+ /*
+ * If the user vetoes the directory, skip
+ * the whole thing.
+ */
+ if (bsdtar->option_interactive &&
+ !yes("add '%s'", ftsent->fts_path)) {
+ fts_set(fts, ftsent, FTS_SKIP);
+ break;
+ }
+
+ /*
+ * If we're not recursing, tell FTS to skip the
+ * tree but do fall through and write the entry
+ * for the dir itself.
+ */
+ if (bsdtar->option_no_subdirs)
+ fts_set(fts, ftsent, FTS_SKIP);
+ write_entry(bsdtar, a, ftsent->fts_statp,
+ ftsent->fts_path, ftsent->fts_pathlen,
+ ftsent->fts_accpath);
+ break;
+ case FTS_F:
+ case FTS_SL:
+ case FTS_SLNONE:
+ case FTS_DEFAULT:
+ /*
+ * Skip this file if it's flagged "nodump" and we're
+ * honoring that flag.
+ */
+#ifdef HAVE_CHFLAGS
+ if (bsdtar->option_honor_nodump &&
+ (ftsent->fts_statp->st_flags & UF_NODUMP))
+ break;
+#endif
+ /*
+ * Skip this file if it's excluded by a
+ * filename pattern.
+ */
+ if (excluded(bsdtar, ftsent->fts_path))
+ break;
+
+ /*
+ * In -u mode, we need to check whether this
+ * is newer than what's already in the archive.
+ */
+ if (!new_enough(bsdtar, ftsent->fts_path,
+ ftsent->fts_statp->st_mtime,
+ ftsent->fts_statp->st_mtimespec.tv_nsec))
+ break;
+
+ if (bsdtar->option_interactive &&
+ !yes("add '%s'", ftsent->fts_path)) {
+ break;
+ }
+
+ write_entry(bsdtar, a, ftsent->fts_statp,
+ ftsent->fts_path, ftsent->fts_pathlen,
+ ftsent->fts_accpath);
+ break;
+ case FTS_DP:
+ break;
+ default:
+ bsdtar_warnc(0, "%s: Heirarchy traversal error %d\n",
+ ftsent->fts_path,
+ ftsent->fts_info);
+ break;
+ }
+
+ }
+ if (errno)
+ bsdtar_warnc(errno, "%s", path);
+ if (fts_close(fts))
+ bsdtar_warnc(errno, "fts_close failed");
+ free(fts_argv[0]);
+}
+
+/*
+ * Add a single filesystem object to the archive.
+ */
+static void
+write_entry(struct bsdtar *bsdtar, struct archive *a, struct stat *st,
+ const char *pathname, unsigned pathlen, const char *accpath)
+{
+ struct archive_entry *entry;
+ int e;
+ int fd;
+ char *fflags = NULL;
+ static char linkbuffer[PATH_MAX+1];
+
+ (void)pathlen; /* UNUSED */
+
+ fd = -1;
+ entry = archive_entry_new();
+ archive_entry_set_pathname(entry, pathname);
+
+ /* If there are hard links, record it for later use */
+ if (!S_ISDIR(st->st_mode) && (st->st_nlink > 1))
+ record_hardlink(bsdtar, entry, st);
+
+ /* Non-regular files get archived with zero size. */
+ if (!S_ISREG(st->st_mode))
+ st->st_size = 0;
+
+ /* Strip redundant "./" from start of filename. */
+ if (pathname && pathname[0] == '.' && pathname[1] == '/') {
+ pathname += 2;
+ if (*pathname == 0) /* This is the "./" directory. */
+ goto cleanup; /* Don't archive it ever. */
+ }
+
+ /* Strip leading '/' unless user has asked us not to. */
+ if (pathname && pathname[0] == '/' && !bsdtar->option_absolute_paths)
+ pathname++;
+
+ /* Display entry as we process it. This format is required by SUSv2. */
+ if (bsdtar->verbose)
+ safe_fprintf(stderr, "a %s", pathname);
+
+ /* Read symbolic link information. */
+ if ((st->st_mode & S_IFMT) == S_IFLNK) {
+ int lnklen;
+
+ lnklen = readlink(accpath, linkbuffer, PATH_MAX);
+ if (lnklen < 0) {
+ if (!bsdtar->verbose)
+ bsdtar_warnc(errno,
+ "%s: Couldn't read symbolic link",
+ pathname);
+ else
+ safe_fprintf(stderr,
+ ": Couldn't read symbolic link: %s",
+ strerror(errno));
+ goto cleanup;
+ }
+ linkbuffer[lnklen] = 0;
+ archive_entry_set_symlink(entry, linkbuffer);
+ }
+
+ /* Look up username and group name. */
+ archive_entry_set_uname(entry, lookup_uname(bsdtar, st->st_uid));
+ archive_entry_set_gname(entry, lookup_gname(bsdtar, st->st_gid));
+
+#ifdef HAVE_CHFLAGS
+ if (st->st_flags != 0) {
+ fflags = fflagstostr(st->st_flags);
+ archive_entry_set_fflags(entry, fflags);
+ }
+#endif
+
+ setup_acls(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(errno, "%s", pathname);
+ else
+ fprintf(stderr, ": %s", strerror(errno));
+ goto cleanup;
+ }
+ }
+
+ archive_entry_copy_stat(entry, st);
+ archive_entry_set_pathname(entry, pathname);
+
+ e = archive_write_header(a, entry);
+ if (e != ARCHIVE_OK) {
+ if (!bsdtar->verbose)
+ bsdtar_warnc(0, "%s: %s", pathname,
+ 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 (fd >= 0 && archive_entry_size(entry) > 0)
+ write_file_data(a, fd);
+
+cleanup:
+ if (fd >= 0)
+ close(fd);
+
+ if (entry != NULL)
+ archive_entry_free(entry);
+
+ if (bsdtar->verbose)
+ fprintf(stderr, "\n");
+
+ if (fflags != NULL) free(fflags);
+}
+
+
+/* Helper function to copy file to archive, with stack-allocated buffer. */
+static int
+write_file_data(struct archive *a, int fd)
+{
+ char buff[8192];
+ ssize_t bytes_read;
+ ssize_t bytes_written;
+
+ bytes_read = read(fd, buff, sizeof(buff));
+ while (bytes_read > 0) {
+ bytes_written = archive_write_data(a, buff, bytes_read);
+
+ if (bytes_written == 0 && errno) {
+ return -1; /* Write failed; this is bad */
+ }
+ bytes_read = read(fd, buff, sizeof(buff));
+ }
+ return 0;
+}
+
+
+static void
+create_cleanup(struct bsdtar * bsdtar)
+{
+ /* Free inode->name map */
+ while (bsdtar->links_head != NULL) {
+ struct links_entry *lp = bsdtar->links_head->next;
+
+ if (bsdtar->option_warn_links)
+ bsdtar_warnc(0, "Missing links to %s",
+ bsdtar->links_head->name);
+
+ if (bsdtar->links_head->name != NULL)
+ free(bsdtar->links_head->name);
+ free(bsdtar->links_head);
+ bsdtar->links_head = lp;
+ }
+ cleanup_exclusions(bsdtar);
+}
+
+
+static void
+record_hardlink(struct bsdtar *bsdtar, struct archive_entry *entry,
+ const struct stat *st)
+{
+ struct links_entry *le;
+
+ /*
+ * First look in the list of multiply-linked files. If we've
+ * already dumped it, convert this entry to a hard link entry.
+ */
+ for (le = bsdtar->links_head; le != NULL; le = le->next) {
+ if (le->dev == st->st_dev && le->ino == st->st_ino) {
+ archive_entry_set_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 (bsdtar->links_head == le)
+ bsdtar->links_head = le->next;
+ free(le);
+ }
+
+ return;
+ }
+ }
+
+ le = malloc(sizeof(struct links_entry));
+ if (bsdtar->links_head != NULL)
+ bsdtar->links_head->previous = le;
+ le->next = bsdtar->links_head;
+ le->previous = NULL;
+ bsdtar->links_head = le;
+ le->dev = st->st_dev;
+ le->ino = st->st_ino;
+ le->links = st->st_nlink - 1;
+ le->name = strdup(archive_entry_pathname(entry));
+}
+
+#ifdef HAVE_POSIX_ACL
+void
+setup_acls(struct bsdtar *bsdtar, struct archive_entry *entry,
+ const char *accpath)
+{
+ acl_t acl;
+ acl_tag_t acl_tag;
+ acl_entry_t acl_entry;
+ acl_permset_t acl_permset;
+ int s, ae_id, ae_tag, ae_perm;
+ const char *ae_name;
+
+ archive_entry_acl_clear(entry);
+
+ /* Retrieve access ACL from file. */
+ acl = acl_get_file(accpath, ACL_TYPE_ACCESS);
+ if (acl != NULL) {
+ s = acl_get_entry(acl, ACL_FIRST_ENTRY, &acl_entry);
+ while (s == 1) {
+ ae_id = -1;
+ ae_name = NULL;
+
+ acl_get_tag_type(acl_entry, &acl_tag);
+ if (acl_tag == ACL_USER) {
+ ae_id = (int)*(uid_t *)acl_get_qualifier(acl_entry);
+ ae_name = lookup_uname(bsdtar, ae_id);
+ ae_tag = ARCHIVE_ENTRY_ACL_USER;
+ } else if (acl_tag == ACL_GROUP) {
+ ae_id = (int)*(gid_t *)acl_get_qualifier(acl_entry);
+ ae_name = lookup_gname(bsdtar, ae_id);
+ ae_tag = ARCHIVE_ENTRY_ACL_GROUP;
+ } else if (acl_tag == ACL_MASK) {
+ ae_tag = ARCHIVE_ENTRY_ACL_MASK;
+ } else if (acl_tag == ACL_USER_OBJ) {
+ ae_tag = ARCHIVE_ENTRY_ACL_USER_OBJ;
+ } else if (acl_tag == ACL_GROUP_OBJ) {
+ ae_tag = ARCHIVE_ENTRY_ACL_GROUP_OBJ;
+ } else if (acl_tag == ACL_OTHER) {
+ ae_tag = ARCHIVE_ENTRY_ACL_OTHER;
+ } else {
+ /* Skip types that libarchive can't support. */
+ continue;
+ }
+
+ acl_get_permset(acl_entry, &acl_permset);
+ ae_perm = 0;
+ if (acl_get_perm_np(acl_permset, ACL_EXECUTE))
+ ae_perm |= ARCHIVE_ENTRY_ACL_EXECUTE;
+ if (acl_get_perm_np(acl_permset, ACL_READ))
+ ae_perm |= ARCHIVE_ENTRY_ACL_READ;
+ if (acl_get_perm_np(acl_permset, ACL_WRITE))
+ ae_perm |= ARCHIVE_ENTRY_ACL_WRITE;
+
+ archive_entry_acl_add_entry(entry,
+ ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ae_perm, ae_tag,
+ ae_id, ae_name);
+
+ s = acl_get_entry(acl, ACL_NEXT_ENTRY, &acl_entry);
+ }
+ acl_free(acl);
+ }
+
+ /* XXX TODO: Default acl ?? XXX */
+}
+#else
+void
+setup_acls(struct archive_entry *entry, const char *accpath)
+{
+ (void)entry;
+ (void)accpath;
+}
+#endif
+
+/*
+ * Lookup gid from gname and uid from uname.
+ *
+ * TODO: Cache gname/uname lookups to improve performance on
+ * large extracts.
+ */
+const char *
+lookup_uname(struct bsdtar *bsdtar, uid_t uid)
+{
+ struct passwd *pwent;
+
+ (void)bsdtar; /* UNUSED */
+
+ pwent = getpwuid(uid);
+ if (pwent)
+ return (pwent->pw_name);
+ if (errno)
+ bsdtar_warnc(errno, "getpwuid(%d) failed", uid);
+ return (NULL);
+}
+
+const char *
+lookup_gname(struct bsdtar *bsdtar, gid_t gid)
+{
+ struct group *grent;
+
+ (void)bsdtar; /* UNUSED */
+ grent = getgrgid(gid);
+ if (grent)
+ return (grent->gr_name);
+ if (errno)
+ bsdtar_warnc(errno, "getgrgid(%d) failed", gid);
+ return (NULL);
+}
+
+/*
+ * Test if the specified file is newer than what's already
+ * in the archive.
+ */
+int
+new_enough(struct bsdtar *bsdtar, const char *path,
+ time_t mtime_sec, int mtime_nsec)
+{
+ struct archive_dir_entry *p;
+
+ if (path[0] == '.' && path[1] == '/' && path[2] != '\0')
+ path += 2;
+
+ if (bsdtar->archive_dir_head == NULL)
+ return (1);
+
+ for (p = bsdtar->archive_dir_head; p != NULL; p = p->next) {
+ if (strcmp(path, p->name)==0)
+ return (p->mtime_sec < mtime_sec ||
+ (p->mtime_sec == mtime_sec &&
+ p->mtime_nsec < mtime_nsec));
+ }
+ return (1);
+}
+
+/*
+ * Add an entry to the dir list for 'u' mode.
+ *
+ * XXX TODO: Make this fast.
+ */
+static void
+add_dir_list(struct bsdtar *bsdtar, const char *path,
+ time_t mtime_sec, int mtime_nsec)
+{
+ struct archive_dir_entry *p;
+
+ if (path[0] == '.' && path[1] == '/' && path[2] != '\0')
+ path += 2;
+
+ p = bsdtar->archive_dir_head;
+ while (p != NULL) {
+ if (strcmp(path, p->name)==0) {
+ p->mtime_sec = mtime_sec;
+ p->mtime_nsec = mtime_nsec;
+ return;
+ }
+ p = p->next;
+ }
+
+ p = malloc(sizeof(*p));
+ p->name = strdup(path);
+ p->mtime_sec = mtime_sec;
+ p->mtime_nsec = mtime_nsec;
+ p->next = NULL;
+ if (bsdtar->archive_dir_tail == NULL) {
+ bsdtar->archive_dir_head = bsdtar->archive_dir_tail = p;
+ } else {
+ bsdtar->archive_dir_tail->next = p;
+ bsdtar->archive_dir_tail = p;
+ }
+}
+
+void
+test_for_append(struct bsdtar *bsdtar)
+{
+ struct stat s;
+
+ if (*bsdtar->argv == NULL)
+ bsdtar_errc(1, 0, "no files or directories specified");
+ if (bsdtar->filename == NULL)
+ bsdtar_errc(1, 0, "Cannot append to stdout.");
+
+ if (bsdtar->create_compression != 0)
+ bsdtar_errc(1, 0, "Cannot append to %s with compression",
+ bsdtar->filename);
+
+ if (stat(bsdtar->filename, &s) != 0)
+ bsdtar_errc(1, errno, "Cannot stat %s", bsdtar->filename);
+
+ if (!S_ISREG(s.st_mode))
+ bsdtar_errc(1, 0, "Cannot append to %s: not a regular file.",
+ bsdtar->filename);
+}
OpenPOWER on IntegriCloud