Initial commit for bsdtar.

This commit is contained in:
Tim Kientzle 2004-04-05 21:32:18 +00:00
parent 29ae923f44
commit d7fe3b4f52
9 changed files with 2977 additions and 0 deletions

21
usr.bin/tar/Makefile Normal file
View File

@ -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>

577
usr.bin/tar/bsdtar.1 Normal file
View File

@ -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.

501
usr.bin/tar/bsdtar.c Normal file
View File

@ -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);
}

101
usr.bin/tar/bsdtar.h Normal file
View File

@ -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, ...);

View File

@ -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 */

243
usr.bin/tar/matching.c Normal file
View File

@ -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);
}

292
usr.bin/tar/read.c Normal file
View File

@ -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));
}

160
usr.bin/tar/util.c Normal file
View File

@ -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);
}

988
usr.bin/tar/write.c Normal file
View File

@ -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);
}