diff options
-rw-r--r-- | usr.bin/tar/Makefile | 21 | ||||
-rw-r--r-- | usr.bin/tar/bsdtar.1 | 577 | ||||
-rw-r--r-- | usr.bin/tar/bsdtar.c | 501 | ||||
-rw-r--r-- | usr.bin/tar/bsdtar.h | 101 | ||||
-rw-r--r-- | usr.bin/tar/bsdtar_platform.h | 94 | ||||
-rw-r--r-- | usr.bin/tar/matching.c | 243 | ||||
-rw-r--r-- | usr.bin/tar/read.c | 292 | ||||
-rw-r--r-- | usr.bin/tar/util.c | 160 | ||||
-rw-r--r-- | usr.bin/tar/write.c | 988 |
9 files changed, 2977 insertions, 0 deletions
diff --git a/usr.bin/tar/Makefile b/usr.bin/tar/Makefile new file mode 100644 index 000000000000..5679f3d4b2e2 --- /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 000000000000..4577481538c1 --- /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 000000000000..98c3c83333cf --- /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 000000000000..428c1b4dde17 --- /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 000000000000..fbabedb8a120 --- /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 000000000000..36c73ce137cf --- /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 000000000000..481238d78d74 --- /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 000000000000..a9ccdfc4cb5d --- /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 000000000000..4ad1509ca73b --- /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); +} |