0280e5aa49
In addition to a number of bug fixes and minor changes: * --numeric-owner (ignore user/group names on create and extract) * -S (sparsify files on extraction) * -s (regex filename substitutions) * Use new libarchive 'linkify' to get correct hardlink handling for both old and new cpio formats * Rework 'copy' test to be insensitive to readdir() filename ordering Most of the credit for this work goes to Joerg Sonnenberger, who has been duplicating features from NetBSD's 'pax' program.
960 lines
28 KiB
C
960 lines
28 KiB
C
/*-
|
|
* Copyright (c) 2003-2007 Tim Kientzle
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "bsdtar_platform.h"
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#ifdef HAVE_SYS_PARAM_H
|
|
#include <sys/param.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_STAT_H
|
|
#include <sys/stat.h>
|
|
#endif
|
|
#ifdef HAVE_ERRNO_H
|
|
#include <errno.h>
|
|
#endif
|
|
#ifdef HAVE_FCNTL_H
|
|
#include <fcntl.h>
|
|
#endif
|
|
#ifdef HAVE_GETOPT_LONG
|
|
#include <getopt.h>
|
|
#else
|
|
struct option {
|
|
const char *name;
|
|
int has_arg;
|
|
int *flag;
|
|
int val;
|
|
};
|
|
#define no_argument 0
|
|
#define required_argument 1
|
|
#endif
|
|
#ifdef HAVE_LANGINFO_H
|
|
#include <langinfo.h>
|
|
#endif
|
|
#ifdef HAVE_LOCALE_H
|
|
#include <locale.h>
|
|
#endif
|
|
#ifdef HAVE_PATHS_H
|
|
#include <paths.h>
|
|
#endif
|
|
#include <stdio.h>
|
|
#ifdef HAVE_STDLIB_H
|
|
#include <stdlib.h>
|
|
#endif
|
|
#ifdef HAVE_STRING_H
|
|
#include <string.h>
|
|
#endif
|
|
#ifdef HAVE_TIME_H
|
|
#include <time.h>
|
|
#endif
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#if HAVE_ZLIB_H
|
|
#include <zlib.h>
|
|
#endif
|
|
|
|
#include "bsdtar.h"
|
|
|
|
#if !HAVE_DECL_OPTARG
|
|
extern int optarg;
|
|
#endif
|
|
|
|
#if !HAVE_DECL_OPTIND
|
|
extern int optind;
|
|
#endif
|
|
|
|
/*
|
|
* Per POSIX.1-1988, tar defaults to reading/writing archives to/from
|
|
* the default tape device for the system. Pick something reasonable here.
|
|
*/
|
|
#ifdef __linux
|
|
#define _PATH_DEFTAPE "/dev/st0"
|
|
#endif
|
|
|
|
#ifndef _PATH_DEFTAPE
|
|
#define _PATH_DEFTAPE "/dev/tape"
|
|
#endif
|
|
|
|
/* External function to parse a date/time string (from getdate.y) */
|
|
time_t get_date(const char *);
|
|
|
|
static int bsdtar_getopt(struct bsdtar *, const char *optstring,
|
|
const struct option **poption);
|
|
static void long_help(struct bsdtar *);
|
|
static void only_mode(struct bsdtar *, const char *opt,
|
|
const char *valid);
|
|
static char ** rewrite_argv(struct bsdtar *,
|
|
int *argc, char ** src_argv,
|
|
const char *optstring);
|
|
static void set_mode(struct bsdtar *, char opt);
|
|
static void version(void);
|
|
|
|
/*
|
|
* The leading '+' here forces the GNU version of getopt() (as well as
|
|
* both the GNU and BSD versions of getopt_long) to stop at the first
|
|
* non-option. Otherwise, GNU getopt() permutes the arguments and
|
|
* screws up -C processing.
|
|
*/
|
|
static const char *tar_opts = "+Bb:C:cf:HhI:jkLlmnOoPprts:ST:UuvW:wX:xyZz";
|
|
|
|
/*
|
|
* Most of these long options are deliberately not documented. They
|
|
* are provided only to make life easier for people who also use GNU tar.
|
|
* The only long options documented in the manual page are the ones
|
|
* with no corresponding short option, such as --exclude, --nodump,
|
|
* and --fast-read.
|
|
*
|
|
* On systems that lack getopt_long, long options can be specified
|
|
* using -W longopt and -W longopt=value, e.g. "-W nodump" is the same
|
|
* as "--nodump" and "-W exclude=pattern" is the same as "--exclude
|
|
* pattern". This does not rely the GNU getopt() "W;" extension, so
|
|
* should work correctly on any system with a POSIX-compliant getopt().
|
|
*/
|
|
|
|
/* Fake short equivalents for long options that otherwise lack them. */
|
|
enum {
|
|
OPTION_CHECK_LINKS = 1,
|
|
OPTION_CHROOT,
|
|
OPTION_EXCLUDE,
|
|
OPTION_FORMAT,
|
|
OPTION_HELP,
|
|
OPTION_INCLUDE,
|
|
OPTION_KEEP_NEWER_FILES,
|
|
OPTION_NEWER_CTIME,
|
|
OPTION_NEWER_CTIME_THAN,
|
|
OPTION_NEWER_MTIME,
|
|
OPTION_NEWER_MTIME_THAN,
|
|
OPTION_NODUMP,
|
|
OPTION_NO_SAME_OWNER,
|
|
OPTION_NO_SAME_PERMISSIONS,
|
|
OPTION_NULL,
|
|
OPTION_NUMERIC_OWNER,
|
|
OPTION_ONE_FILE_SYSTEM,
|
|
OPTION_POSIX,
|
|
OPTION_STRIP_COMPONENTS,
|
|
OPTION_TOTALS,
|
|
OPTION_USE_COMPRESS_PROGRAM,
|
|
OPTION_VERSION
|
|
};
|
|
|
|
/*
|
|
* If you add anything, be very careful to keep this list properly
|
|
* sorted, as the -W logic relies on it.
|
|
*/
|
|
static 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' },
|
|
{ "check-links", no_argument, NULL, OPTION_CHECK_LINKS },
|
|
{ "chroot", no_argument, NULL, OPTION_CHROOT },
|
|
{ "compress", no_argument, NULL, 'Z' },
|
|
{ "confirmation", no_argument, NULL, 'w' },
|
|
{ "create", no_argument, NULL, 'c' },
|
|
{ "dereference", no_argument, NULL, 'L' },
|
|
{ "directory", required_argument, NULL, 'C' },
|
|
{ "exclude", required_argument, NULL, OPTION_EXCLUDE },
|
|
{ "exclude-from", required_argument, NULL, 'X' },
|
|
{ "extract", no_argument, NULL, 'x' },
|
|
{ "fast-read", no_argument, NULL, 'q' },
|
|
{ "file", required_argument, NULL, 'f' },
|
|
{ "files-from", required_argument, NULL, 'T' },
|
|
{ "format", required_argument, NULL, OPTION_FORMAT },
|
|
{ "gunzip", no_argument, NULL, 'z' },
|
|
{ "gzip", no_argument, NULL, 'z' },
|
|
{ "help", no_argument, NULL, OPTION_HELP },
|
|
{ "include", required_argument, NULL, OPTION_INCLUDE },
|
|
{ "interactive", no_argument, NULL, 'w' },
|
|
{ "insecure", no_argument, NULL, 'P' },
|
|
{ "keep-newer-files", no_argument, NULL, OPTION_KEEP_NEWER_FILES },
|
|
{ "keep-old-files", no_argument, NULL, 'k' },
|
|
{ "list", no_argument, NULL, 't' },
|
|
{ "modification-time", no_argument, NULL, 'm' },
|
|
{ "newer", required_argument, NULL, OPTION_NEWER_CTIME },
|
|
{ "newer-ctime", required_argument, NULL, OPTION_NEWER_CTIME },
|
|
{ "newer-ctime-than", required_argument, NULL, OPTION_NEWER_CTIME_THAN },
|
|
{ "newer-mtime", required_argument, NULL, OPTION_NEWER_MTIME },
|
|
{ "newer-mtime-than", required_argument, NULL, OPTION_NEWER_MTIME_THAN },
|
|
{ "newer-than", required_argument, NULL, OPTION_NEWER_CTIME_THAN },
|
|
{ "nodump", no_argument, NULL, OPTION_NODUMP },
|
|
{ "norecurse", no_argument, NULL, 'n' },
|
|
{ "no-recursion", no_argument, NULL, 'n' },
|
|
{ "no-same-owner", no_argument, NULL, OPTION_NO_SAME_OWNER },
|
|
{ "no-same-permissions",no_argument, NULL, OPTION_NO_SAME_PERMISSIONS },
|
|
{ "null", no_argument, NULL, OPTION_NULL },
|
|
{ "numeric-owner", no_argument, NULL, OPTION_NUMERIC_OWNER },
|
|
{ "one-file-system", no_argument, NULL, OPTION_ONE_FILE_SYSTEM },
|
|
{ "posix", no_argument, NULL, OPTION_POSIX },
|
|
{ "preserve-permissions", no_argument, NULL, 'p' },
|
|
{ "read-full-blocks", no_argument, NULL, 'B' },
|
|
{ "same-permissions", no_argument, NULL, 'p' },
|
|
{ "strip-components", required_argument, NULL, OPTION_STRIP_COMPONENTS },
|
|
{ "to-stdout", no_argument, NULL, 'O' },
|
|
{ "totals", no_argument, NULL, OPTION_TOTALS },
|
|
{ "uncompress", no_argument, NULL, 'Z' },
|
|
{ "unlink", no_argument, NULL, 'U' },
|
|
{ "unlink-first", no_argument, NULL, 'U' },
|
|
{ "update", no_argument, NULL, 'u' },
|
|
{ "use-compress-program",
|
|
required_argument, NULL, OPTION_USE_COMPRESS_PROGRAM },
|
|
{ "verbose", no_argument, NULL, 'v' },
|
|
{ "version", no_argument, NULL, OPTION_VERSION },
|
|
{ NULL, 0, NULL, 0 }
|
|
};
|
|
|
|
/* A basic set of security flags to request from libarchive. */
|
|
#define SECURITY \
|
|
(ARCHIVE_EXTRACT_SECURE_SYMLINKS \
|
|
| ARCHIVE_EXTRACT_SECURE_NODOTDOT)
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
struct bsdtar *bsdtar, bsdtar_storage;
|
|
const struct option *option;
|
|
int opt, t;
|
|
char option_o;
|
|
char possible_help_request;
|
|
char buff[16];
|
|
|
|
/*
|
|
* 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" */
|
|
option_o = 0;
|
|
|
|
/* Need bsdtar->progname before calling bsdtar_warnc. */
|
|
if (*argv == NULL)
|
|
bsdtar->progname = "bsdtar";
|
|
else {
|
|
bsdtar->progname = strrchr(*argv, '/');
|
|
if (bsdtar->progname != NULL)
|
|
bsdtar->progname++;
|
|
else
|
|
bsdtar->progname = *argv;
|
|
}
|
|
|
|
if (setlocale(LC_ALL, "") == NULL)
|
|
bsdtar_warnc(bsdtar, 0, "Failed to set default locale");
|
|
#if defined(HAVE_NL_LANGINFO) && defined(HAVE_D_MD_ORDER)
|
|
bsdtar->day_first = (*nl_langinfo(D_MD_ORDER) == 'd');
|
|
#endif
|
|
possible_help_request = 0;
|
|
|
|
/* Look up uid of current user for future reference */
|
|
bsdtar->user_uid = geteuid();
|
|
|
|
/* Default: open tape drive. */
|
|
bsdtar->filename = getenv("TAPE");
|
|
if (bsdtar->filename == NULL)
|
|
bsdtar->filename = _PATH_DEFTAPE;
|
|
|
|
/* Default: preserve mod time on extract */
|
|
bsdtar->extract_flags = ARCHIVE_EXTRACT_TIME;
|
|
|
|
/* Default: Perform basic security checks. */
|
|
bsdtar->extract_flags |= SECURITY;
|
|
|
|
/* Defaults for root user: */
|
|
if (bsdtar->user_uid == 0) {
|
|
/* --same-owner */
|
|
bsdtar->extract_flags |= ARCHIVE_EXTRACT_OWNER;
|
|
/* -p */
|
|
bsdtar->extract_flags |= ARCHIVE_EXTRACT_PERM;
|
|
bsdtar->extract_flags |= ARCHIVE_EXTRACT_ACL;
|
|
bsdtar->extract_flags |= ARCHIVE_EXTRACT_XATTR;
|
|
bsdtar->extract_flags |= ARCHIVE_EXTRACT_FFLAGS;
|
|
}
|
|
|
|
/* Rewrite traditional-style tar arguments, if used. */
|
|
argv = rewrite_argv(bsdtar, &argc, argv, tar_opts);
|
|
|
|
bsdtar->argv = argv;
|
|
bsdtar->argc = argc;
|
|
|
|
/* Process all remaining arguments now. */
|
|
/*
|
|
* Comments following each option indicate where that option
|
|
* originated: SUSv2, POSIX, GNU tar, star, etc. If there's
|
|
* no such comment, then I don't know of anyone else who
|
|
* implements that option.
|
|
*/
|
|
while ((opt = bsdtar_getopt(bsdtar, tar_opts, &option)) != -1) {
|
|
switch (opt) {
|
|
case 'B': /* GNU tar */
|
|
/* libarchive doesn't need this; just ignore it. */
|
|
break;
|
|
case 'b': /* SUSv2 */
|
|
t = atoi(optarg);
|
|
if (t <= 0 || t > 1024)
|
|
bsdtar_errc(bsdtar, 1, 0,
|
|
"Argument to -b is out of range (1..1024)");
|
|
bsdtar->bytes_per_block = 512 * t;
|
|
break;
|
|
case 'C': /* GNU tar */
|
|
set_chdir(bsdtar, optarg);
|
|
break;
|
|
case 'c': /* SUSv2 */
|
|
set_mode(bsdtar, opt);
|
|
break;
|
|
case OPTION_CHECK_LINKS: /* GNU tar */
|
|
bsdtar->option_warn_links = 1;
|
|
break;
|
|
case OPTION_CHROOT: /* NetBSD */
|
|
bsdtar->option_chroot = 1;
|
|
break;
|
|
case OPTION_EXCLUDE: /* GNU tar */
|
|
if (exclude(bsdtar, optarg))
|
|
bsdtar_errc(bsdtar, 1, 0,
|
|
"Couldn't exclude %s\n", optarg);
|
|
break;
|
|
case OPTION_FORMAT: /* GNU tar, others */
|
|
bsdtar->create_format = optarg;
|
|
break;
|
|
case 'f': /* SUSv2 */
|
|
bsdtar->filename = optarg;
|
|
if (strcmp(bsdtar->filename, "-") == 0)
|
|
bsdtar->filename = NULL;
|
|
break;
|
|
case 'H': /* BSD convention */
|
|
bsdtar->symlink_mode = 'H';
|
|
break;
|
|
case 'h': /* Linux Standards Base, gtar; synonym for -L */
|
|
bsdtar->symlink_mode = 'L';
|
|
/* Hack: -h by itself is the "help" command. */
|
|
possible_help_request = 1;
|
|
break;
|
|
case OPTION_HELP: /* GNU tar, others */
|
|
long_help(bsdtar);
|
|
exit(0);
|
|
break;
|
|
case 'I': /* GNU tar */
|
|
/*
|
|
* TODO: Allow 'names' to come from an archive,
|
|
* not just a text file. Design a good UI for
|
|
* allowing names and mode/owner to be read
|
|
* from an archive, with contents coming from
|
|
* disk. This can be used to "refresh" an
|
|
* archive or to design archives with special
|
|
* permissions without having to create those
|
|
* permissions on disk.
|
|
*/
|
|
bsdtar->names_from_file = optarg;
|
|
break;
|
|
case OPTION_INCLUDE:
|
|
/*
|
|
* Noone else has the @archive extension, so
|
|
* noone else needs this to filter entries
|
|
* when transforming archives.
|
|
*/
|
|
if (include(bsdtar, optarg))
|
|
bsdtar_errc(bsdtar, 1, 0,
|
|
"Failed to add %s to inclusion list",
|
|
optarg);
|
|
break;
|
|
case 'j': /* GNU tar */
|
|
#if HAVE_LIBBZ2
|
|
if (bsdtar->create_compression != '\0')
|
|
bsdtar_errc(bsdtar, 1, 0,
|
|
"Can't specify both -%c and -%c", opt,
|
|
bsdtar->create_compression);
|
|
bsdtar->create_compression = opt;
|
|
#else
|
|
bsdtar_warnc(bsdtar, 0, "-j compression not supported by this version of bsdtar");
|
|
usage(bsdtar);
|
|
#endif
|
|
break;
|
|
case 'k': /* GNU tar */
|
|
bsdtar->extract_flags |= ARCHIVE_EXTRACT_NO_OVERWRITE;
|
|
break;
|
|
case OPTION_KEEP_NEWER_FILES: /* GNU tar */
|
|
bsdtar->extract_flags |= ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER;
|
|
break;
|
|
case 'L': /* BSD convention */
|
|
bsdtar->symlink_mode = 'L';
|
|
break;
|
|
case 'l': /* SUSv2 and GNU tar beginning with 1.16 */
|
|
/* GNU tar 1.13 used -l for --one-file-system */
|
|
bsdtar->option_warn_links = 1;
|
|
break;
|
|
case 'm': /* SUSv2 */
|
|
bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_TIME;
|
|
break;
|
|
case 'n': /* GNU tar */
|
|
bsdtar->option_no_subdirs = 1;
|
|
break;
|
|
/*
|
|
* Selecting files by time:
|
|
* --newer-?time='date' Only files newer than 'date'
|
|
* --newer-?time-than='file' Only files newer than time
|
|
* on specified file (useful for incremental backups)
|
|
* TODO: Add corresponding "older" options to reverse these.
|
|
*/
|
|
case OPTION_NEWER_CTIME: /* GNU tar */
|
|
bsdtar->newer_ctime_sec = get_date(optarg);
|
|
break;
|
|
case OPTION_NEWER_CTIME_THAN:
|
|
{
|
|
struct stat st;
|
|
if (stat(optarg, &st) != 0)
|
|
bsdtar_errc(bsdtar, 1, 0,
|
|
"Can't open file %s", optarg);
|
|
bsdtar->newer_ctime_sec = st.st_ctime;
|
|
bsdtar->newer_ctime_nsec =
|
|
ARCHIVE_STAT_CTIME_NANOS(&st);
|
|
}
|
|
break;
|
|
case OPTION_NEWER_MTIME: /* GNU tar */
|
|
bsdtar->newer_mtime_sec = get_date(optarg);
|
|
break;
|
|
case OPTION_NEWER_MTIME_THAN:
|
|
{
|
|
struct stat st;
|
|
if (stat(optarg, &st) != 0)
|
|
bsdtar_errc(bsdtar, 1, 0,
|
|
"Can't open file %s", optarg);
|
|
bsdtar->newer_mtime_sec = st.st_mtime;
|
|
bsdtar->newer_mtime_nsec =
|
|
ARCHIVE_STAT_MTIME_NANOS(&st);
|
|
}
|
|
break;
|
|
case OPTION_NODUMP: /* star */
|
|
bsdtar->option_honor_nodump = 1;
|
|
break;
|
|
case OPTION_NO_SAME_OWNER: /* GNU tar */
|
|
bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_OWNER;
|
|
break;
|
|
case OPTION_NO_SAME_PERMISSIONS: /* GNU tar */
|
|
bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_PERM;
|
|
bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_ACL;
|
|
bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_XATTR;
|
|
bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_FFLAGS;
|
|
break;
|
|
case OPTION_NULL: /* GNU tar */
|
|
bsdtar->option_null++;
|
|
break;
|
|
case OPTION_NUMERIC_OWNER: /* GNU tar */
|
|
bsdtar->option_numeric_owner++;
|
|
break;
|
|
case 'O': /* GNU tar */
|
|
bsdtar->option_stdout = 1;
|
|
break;
|
|
case 'o': /* SUSv2 and GNU conflict here, but not fatally */
|
|
option_o = 1; /* Record it and resolve it later. */
|
|
break;
|
|
case OPTION_ONE_FILE_SYSTEM: /* GNU tar */
|
|
bsdtar->option_dont_traverse_mounts = 1;
|
|
break;
|
|
#if 0
|
|
/*
|
|
* The common BSD -P option is not necessary, since
|
|
* our default is to archive symlinks, not follow
|
|
* them. This is convenient, as -P conflicts with GNU
|
|
* tar anyway.
|
|
*/
|
|
case 'P': /* BSD convention */
|
|
/* Default behavior, no option necessary. */
|
|
break;
|
|
#endif
|
|
case 'P': /* GNU tar */
|
|
bsdtar->extract_flags &= ~SECURITY;
|
|
bsdtar->option_absolute_paths = 1;
|
|
break;
|
|
case 'p': /* GNU tar, star */
|
|
bsdtar->extract_flags |= ARCHIVE_EXTRACT_PERM;
|
|
bsdtar->extract_flags |= ARCHIVE_EXTRACT_ACL;
|
|
bsdtar->extract_flags |= ARCHIVE_EXTRACT_XATTR;
|
|
bsdtar->extract_flags |= ARCHIVE_EXTRACT_FFLAGS;
|
|
break;
|
|
case OPTION_POSIX: /* GNU tar */
|
|
bsdtar->create_format = "pax";
|
|
break;
|
|
case 'q': /* FreeBSD GNU tar --fast-read, NetBSD -q */
|
|
bsdtar->option_fast_read = 1;
|
|
break;
|
|
case 'r': /* SUSv2 */
|
|
set_mode(bsdtar, opt);
|
|
break;
|
|
case 'S': /* NetBSD pax-as-tar */
|
|
bsdtar->extract_flags |= ARCHIVE_EXTRACT_SPARSE;
|
|
break;
|
|
case 's': /* NetBSD pax-as-tar */
|
|
#if HAVE_REGEX_H
|
|
add_substitution(bsdtar, optarg);
|
|
#else
|
|
bsdtar_warnc(bsdtar, 0, "-s is not supported by this version of bsdtar");
|
|
usage(bsdtar);
|
|
#endif
|
|
break;
|
|
case OPTION_STRIP_COMPONENTS: /* GNU tar 1.15 */
|
|
bsdtar->strip_components = atoi(optarg);
|
|
break;
|
|
case 'T': /* GNU tar */
|
|
bsdtar->names_from_file = optarg;
|
|
break;
|
|
case 't': /* SUSv2 */
|
|
set_mode(bsdtar, opt);
|
|
bsdtar->verbose++;
|
|
break;
|
|
case OPTION_TOTALS: /* GNU tar */
|
|
bsdtar->option_totals++;
|
|
break;
|
|
case 'U': /* GNU tar */
|
|
bsdtar->extract_flags |= ARCHIVE_EXTRACT_UNLINK;
|
|
bsdtar->option_unlink_first = 1;
|
|
break;
|
|
case 'u': /* SUSv2 */
|
|
set_mode(bsdtar, opt);
|
|
break;
|
|
case 'v': /* SUSv2 */
|
|
bsdtar->verbose++;
|
|
break;
|
|
case OPTION_VERSION: /* GNU convention */
|
|
version();
|
|
break;
|
|
#if 0
|
|
/*
|
|
* The -W longopt feature is handled inside of
|
|
* bsdtar_getop(), so -W is not available here.
|
|
*/
|
|
case 'W': /* Obscure, but useful GNU convention. */
|
|
break;
|
|
#endif
|
|
case 'w': /* SUSv2 */
|
|
bsdtar->option_interactive = 1;
|
|
break;
|
|
case 'X': /* GNU tar */
|
|
if (exclude_from_file(bsdtar, optarg))
|
|
bsdtar_errc(bsdtar, 1, 0,
|
|
"failed to process exclusions from file %s",
|
|
optarg);
|
|
break;
|
|
case 'x': /* SUSv2 */
|
|
set_mode(bsdtar, opt);
|
|
break;
|
|
case 'y': /* FreeBSD version of GNU tar */
|
|
#if HAVE_LIBBZ2
|
|
if (bsdtar->create_compression != '\0')
|
|
bsdtar_errc(bsdtar, 1, 0,
|
|
"Can't specify both -%c and -%c", opt,
|
|
bsdtar->create_compression);
|
|
bsdtar->create_compression = opt;
|
|
#else
|
|
bsdtar_warnc(bsdtar, 0, "-y compression not supported by this version of bsdtar");
|
|
usage(bsdtar);
|
|
#endif
|
|
break;
|
|
case 'Z': /* GNU tar */
|
|
if (bsdtar->create_compression != '\0')
|
|
bsdtar_errc(bsdtar, 1, 0,
|
|
"Can't specify both -%c and -%c", opt,
|
|
bsdtar->create_compression);
|
|
bsdtar->create_compression = opt;
|
|
break;
|
|
case 'z': /* GNU tar, star, many others */
|
|
#if HAVE_LIBZ
|
|
if (bsdtar->create_compression != '\0')
|
|
bsdtar_errc(bsdtar, 1, 0,
|
|
"Can't specify both -%c and -%c", opt,
|
|
bsdtar->create_compression);
|
|
bsdtar->create_compression = opt;
|
|
#else
|
|
bsdtar_warnc(bsdtar, 0, "-z compression not supported by this version of bsdtar");
|
|
usage(bsdtar);
|
|
#endif
|
|
break;
|
|
case OPTION_USE_COMPRESS_PROGRAM:
|
|
bsdtar->compress_program = optarg;
|
|
break;
|
|
default:
|
|
usage(bsdtar);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Sanity-check options.
|
|
*/
|
|
|
|
/* If no "real" mode was specified, treat -h as --help. */
|
|
if ((bsdtar->mode == '\0') && possible_help_request) {
|
|
long_help(bsdtar);
|
|
exit(0);
|
|
}
|
|
|
|
/* Otherwise, a mode is required. */
|
|
if (bsdtar->mode == '\0')
|
|
bsdtar_errc(bsdtar, 1, 0,
|
|
"Must specify one of -c, -r, -t, -u, -x");
|
|
|
|
/* Check boolean options only permitted in certain modes. */
|
|
if (bsdtar->option_dont_traverse_mounts)
|
|
only_mode(bsdtar, "--one-file-system", "cru");
|
|
if (bsdtar->option_fast_read)
|
|
only_mode(bsdtar, "--fast-read", "xt");
|
|
if (bsdtar->option_honor_nodump)
|
|
only_mode(bsdtar, "--nodump", "cru");
|
|
if (option_o > 0) {
|
|
switch (bsdtar->mode) {
|
|
case 'c':
|
|
/*
|
|
* In GNU tar, -o means "old format." The
|
|
* "ustar" format is the closest thing
|
|
* supported by libarchive.
|
|
*/
|
|
bsdtar->create_format = "ustar";
|
|
/* TODO: bsdtar->create_format = "v7"; */
|
|
break;
|
|
case 'x':
|
|
/* POSIX-compatible behavior. */
|
|
bsdtar->option_no_owner = 1;
|
|
bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_OWNER;
|
|
break;
|
|
default:
|
|
only_mode(bsdtar, "-o", "xc");
|
|
break;
|
|
}
|
|
}
|
|
if (bsdtar->option_no_subdirs)
|
|
only_mode(bsdtar, "-n", "cru");
|
|
if (bsdtar->option_stdout)
|
|
only_mode(bsdtar, "-O", "xt");
|
|
if (bsdtar->option_unlink_first)
|
|
only_mode(bsdtar, "-U", "x");
|
|
if (bsdtar->option_warn_links)
|
|
only_mode(bsdtar, "--check-links", "cr");
|
|
|
|
/* Check other parameters only permitted in certain modes. */
|
|
if (bsdtar->create_compression != '\0') {
|
|
strcpy(buff, "-?");
|
|
buff[1] = bsdtar->create_compression;
|
|
only_mode(bsdtar, buff, "cxt");
|
|
}
|
|
if (bsdtar->create_format != NULL)
|
|
only_mode(bsdtar, "--format", "cru");
|
|
if (bsdtar->symlink_mode != '\0') {
|
|
strcpy(buff, "-?");
|
|
buff[1] = bsdtar->symlink_mode;
|
|
only_mode(bsdtar, buff, "cru");
|
|
}
|
|
if (bsdtar->strip_components != 0)
|
|
only_mode(bsdtar, "--strip-components", "xt");
|
|
|
|
bsdtar->argc -= optind;
|
|
bsdtar->argv += optind;
|
|
|
|
switch(bsdtar->mode) {
|
|
case 'c':
|
|
tar_mode_c(bsdtar);
|
|
break;
|
|
case 'r':
|
|
tar_mode_r(bsdtar);
|
|
break;
|
|
case 't':
|
|
tar_mode_t(bsdtar);
|
|
break;
|
|
case 'u':
|
|
tar_mode_u(bsdtar);
|
|
break;
|
|
case 'x':
|
|
tar_mode_x(bsdtar);
|
|
break;
|
|
}
|
|
|
|
cleanup_exclusions(bsdtar);
|
|
#if HAVE_REGEX_H
|
|
cleanup_substitution(bsdtar);
|
|
#endif
|
|
|
|
if (bsdtar->return_value != 0)
|
|
bsdtar_warnc(bsdtar, 0,
|
|
"Error exit delayed from previous errors.");
|
|
return (bsdtar->return_value);
|
|
}
|
|
|
|
static void
|
|
set_mode(struct bsdtar *bsdtar, char opt)
|
|
{
|
|
if (bsdtar->mode != '\0' && bsdtar->mode != opt)
|
|
bsdtar_errc(bsdtar, 1, 0,
|
|
"Can't specify both -%c and -%c", opt, bsdtar->mode);
|
|
bsdtar->mode = opt;
|
|
}
|
|
|
|
/*
|
|
* Verify that the mode is correct.
|
|
*/
|
|
static void
|
|
only_mode(struct bsdtar *bsdtar, const char *opt, const char *valid_modes)
|
|
{
|
|
if (strchr(valid_modes, bsdtar->mode) == NULL)
|
|
bsdtar_errc(bsdtar, 1, 0,
|
|
"Option %s is not permitted in mode -%c",
|
|
opt, bsdtar->mode);
|
|
}
|
|
|
|
|
|
/*-
|
|
* Convert traditional tar arguments into new-style.
|
|
* For example,
|
|
* tar tvfb file.tar 32 --exclude FOO
|
|
* will 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(struct bsdtar *bsdtar, int *argc, char **src_argv,
|
|
const char *optstring)
|
|
{
|
|
char **new_argv, **dest_argv;
|
|
const char *p;
|
|
char *src, *dest;
|
|
|
|
if (src_argv[0] == NULL || src_argv[1] == NULL ||
|
|
src_argv[1][0] == '-' || src_argv[1][0] == '\0')
|
|
return (src_argv);
|
|
|
|
*argc += strlen(src_argv[1]) - 1;
|
|
new_argv = malloc((*argc + 1) * sizeof(new_argv[0]));
|
|
if (new_argv == NULL)
|
|
bsdtar_errc(bsdtar, 1, errno, "No Memory");
|
|
|
|
dest_argv = new_argv;
|
|
*dest_argv++ = *src_argv++;
|
|
|
|
dest = malloc(strlen(*src_argv) * 3);
|
|
if (dest == NULL)
|
|
bsdtar_errc(bsdtar, 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(bsdtar, 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(struct bsdtar *bsdtar)
|
|
{
|
|
const char *p;
|
|
|
|
p = bsdtar->progname;
|
|
|
|
fprintf(stderr, "Usage:\n");
|
|
fprintf(stderr, " List: %s -tf <archive-filename>\n", p);
|
|
fprintf(stderr, " Extract: %s -xf <archive-filename>\n", p);
|
|
fprintf(stderr, " Create: %s -cf <archive-filename> [filenames...]\n", p);
|
|
#ifdef HAVE_GETOPT_LONG
|
|
fprintf(stderr, " Help: %s --help\n", p);
|
|
#else
|
|
fprintf(stderr, " Help: %s -h\n", p);
|
|
#endif
|
|
exit(1);
|
|
}
|
|
|
|
static void
|
|
version(void)
|
|
{
|
|
printf("bsdtar %s - %s\n",
|
|
BSDTAR_VERSION_STRING,
|
|
archive_version());
|
|
exit(0);
|
|
}
|
|
|
|
static const char *long_help_msg =
|
|
"First option must be a mode specifier:\n"
|
|
" -c Create -r Add/Replace -t List -u Update -x Extract\n"
|
|
"Common Options:\n"
|
|
" -b # Use # 512-byte records per I/O block\n"
|
|
" -f <filename> Location of archive (default " _PATH_DEFTAPE ")\n"
|
|
" -v Verbose\n"
|
|
" -w Interactive\n"
|
|
"Create: %p -c [options] [<file> | <dir> | @<archive> | -C <dir> ]\n"
|
|
" <file>, <dir> add these items to archive\n"
|
|
" -z, -j Compress archive with gzip/bzip2\n"
|
|
" --format {ustar|pax|cpio|shar} Select archive format\n"
|
|
#ifdef HAVE_GETOPT_LONG
|
|
" --exclude <pattern> Skip files that match pattern\n"
|
|
#else
|
|
" -W exclude=<pattern> Skip files that match pattern\n"
|
|
#endif
|
|
" -C <dir> Change to <dir> before processing remaining files\n"
|
|
" @<archive> Add entries from <archive> to output\n"
|
|
"List: %p -t [options] [<patterns>]\n"
|
|
" <patterns> If specified, list only entries that match\n"
|
|
"Extract: %p -x [options] [<patterns>]\n"
|
|
" <patterns> If specified, extract only entries that match\n"
|
|
" -k Keep (don't overwrite) existing files\n"
|
|
" -m Don't restore modification times\n"
|
|
" -O Write entries to stdout, don't restore to disk\n"
|
|
" -p Restore permissions (including ACLs, owner, file flags)\n";
|
|
|
|
|
|
/*
|
|
* Note that the word 'bsdtar' will always appear in the first line
|
|
* of output.
|
|
*
|
|
* In particular, /bin/sh scripts that need to test for the presence
|
|
* of bsdtar can use the following template:
|
|
*
|
|
* if (tar --help 2>&1 | grep bsdtar >/dev/null 2>&1 ) then \
|
|
* echo bsdtar; else echo not bsdtar; fi
|
|
*/
|
|
static void
|
|
long_help(struct bsdtar *bsdtar)
|
|
{
|
|
const char *prog;
|
|
const char *p;
|
|
|
|
prog = bsdtar->progname;
|
|
|
|
fflush(stderr);
|
|
|
|
p = (strcmp(prog,"bsdtar") != 0) ? "(bsdtar)" : "";
|
|
printf("%s%s: manipulate archive files\n", prog, p);
|
|
|
|
for (p = long_help_msg; *p != '\0'; p++) {
|
|
if (*p == '%') {
|
|
if (p[1] == 'p') {
|
|
fputs(prog, stdout);
|
|
p++;
|
|
} else
|
|
putchar('%');
|
|
} else
|
|
putchar(*p);
|
|
}
|
|
version();
|
|
}
|
|
|
|
static int
|
|
bsdtar_getopt(struct bsdtar *bsdtar, const char *optstring,
|
|
const struct option **poption)
|
|
{
|
|
char *p, *q;
|
|
const struct option *option;
|
|
int opt;
|
|
int option_index;
|
|
size_t option_length;
|
|
|
|
option_index = -1;
|
|
*poption = NULL;
|
|
|
|
#ifdef HAVE_GETOPT_LONG
|
|
opt = getopt_long(bsdtar->argc, bsdtar->argv, optstring,
|
|
tar_longopts, &option_index);
|
|
if (option_index > -1)
|
|
*poption = tar_longopts + option_index;
|
|
#else
|
|
opt = getopt(bsdtar->argc, bsdtar->argv, optstring);
|
|
#endif
|
|
|
|
/* Support long options through -W longopt=value */
|
|
if (opt == 'W') {
|
|
p = optarg;
|
|
q = strchr(optarg, '=');
|
|
if (q != NULL) {
|
|
option_length = (size_t)(q - p);
|
|
optarg = q + 1;
|
|
} else {
|
|
option_length = strlen(p);
|
|
optarg = NULL;
|
|
}
|
|
option = tar_longopts;
|
|
while (option->name != NULL &&
|
|
(strlen(option->name) < option_length ||
|
|
strncmp(p, option->name, option_length) != 0 )) {
|
|
option++;
|
|
}
|
|
|
|
if (option->name != NULL) {
|
|
*poption = option;
|
|
opt = option->val;
|
|
|
|
/* If the first match was exact, we're done. */
|
|
if (strncmp(p, option->name, strlen(option->name)) == 0) {
|
|
while (option->name != NULL)
|
|
option++;
|
|
} else {
|
|
/* Check if there's another match. */
|
|
option++;
|
|
while (option->name != NULL &&
|
|
(strlen(option->name) < option_length ||
|
|
strncmp(p, option->name, option_length) != 0)) {
|
|
option++;
|
|
}
|
|
}
|
|
if (option->name != NULL)
|
|
bsdtar_errc(bsdtar, 1, 0,
|
|
"Ambiguous option %s "
|
|
"(matches both %s and %s)",
|
|
p, (*poption)->name, option->name);
|
|
|
|
if ((*poption)->has_arg == required_argument
|
|
&& optarg == NULL)
|
|
bsdtar_errc(bsdtar, 1, 0,
|
|
"Option \"%s\" requires argument", p);
|
|
} else {
|
|
opt = '?';
|
|
/* TODO: Set up a fake 'struct option' for
|
|
* error reporting... ? ? ? */
|
|
}
|
|
}
|
|
|
|
return (opt);
|
|
}
|