New command-line parser for bsdtar.

This replaces the getopt()/getopt_long() wrapper, the old-style
argument rewriter and the associated configuration glue with a more
straightforward custom command parser.  In particular, this ensures
that bsdtar will have consistent option parsing on every platform,
regardless of whether the platform supports getopt_long().

MFC after:	30 days
This commit is contained in:
Tim Kientzle 2008-11-08 04:43:24 +00:00
parent 76371b5a80
commit 85ae3122e9
6 changed files with 455 additions and 356 deletions

View File

@ -2,7 +2,7 @@
PROG= bsdtar
BSDTAR_VERSION_STRING=2.5.5
SRCS= bsdtar.c getdate.y matching.c read.c siginfo.c subst.c tree.c util.c write.c
SRCS= bsdtar.c cmdline.c getdate.y matching.c read.c siginfo.c subst.c tree.c util.c write.c
WARNS?= 5
DPADD= ${LIBARCHIVE} ${LIBBZ2} ${LIBZ}
LDADD= -larchive -lbz2 -lz
@ -11,6 +11,7 @@ CFLAGS+= -DPLATFORM_CONFIG_H=\"config_freebsd.h\"
CFLAGS+= -I${.CURDIR}
SYMLINKS= bsdtar ${BINDIR}/tar
MLINKS= bsdtar.1 tar.1
DEBUG_FLAGS=-g
.PHONY: check test
check test: $(PROG) bsdtar.1.gz

View File

@ -144,21 +144,21 @@ In c and r mode, this changes the directory before adding
the following files.
In x mode, change directories after opening the archive
but before extracting entries from the archive.
.It Fl -check-links ( Fl W Cm check-links )
.It Fl -check-links
(c and r modes only)
Issue a warning message unless all links to each file are archived.
.It Fl -chroot ( Fl W Cm chroot )
.It Fl -chroot
(x mode only)
.Fn chroot
to the current directory after processing any
.Fl C
options and before extracting any files.
.It Fl -exclude Ar pattern ( Fl W Cm exclude Ns = Ns Ar pattern )
.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 -format Ar format ( Fl W Cm format Ns = Ns Ar format )
.It Fl -format Ar format
(c, r, u mode only)
Use the specified format for the created archive.
Supported formats include
@ -193,7 +193,7 @@ Synonym for
.It Fl I
Synonym for
.Fl T .
.It Fl -include Ar pattern ( Fl W Cm include Ns = Ns Ar pattern )
.It Fl -include Ar pattern
Process only files or directories that match the specified pattern.
Note that exclusions specified with
.Fl -exclude
@ -225,7 +225,7 @@ automatically when reading archives.
Do not overwrite existing files.
In particular, if a file appears more than once in an archive,
later copies will not overwrite earlier copies.
.It Fl -keep-newer-files ( Fl W Cm keep-newer-files )
.It Fl -keep-newer-files
(x mode only)
Do not overwrite existing files that are newer than the
versions appearing in the archive being extracted.
@ -245,28 +245,28 @@ 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 -newer Ar date ( Fl W Cm newer Ns = Ns Ar date )
.It Fl -newer Ar date
(c, r, u modes only)
Only include files and directories newer than the specified date.
This compares ctime entries.
.It Fl -newer-mtime Ar date ( Fl W Cm newer-mtime Ns = Ns Ar date )
.It Fl -newer-mtime Ar date
(c, r, u modes only)
Like
.Fl -newer ,
except it compares mtime entries instead of ctime entries.
.It Fl -newer-than Pa file ( Fl W Cm newer-than Ns = Ns Pa file )
.It Fl -newer-than Pa file
(c, r, u modes only)
Only include files and directories newer than the specified file.
This compares ctime entries.
.It Fl -newer-mtime-than Pa file ( Fl W Cm newer-mtime-than Ns = Ns Pa file )
.It Fl -newer-mtime-than Pa file
(c, r, u modes only)
Like
.Fl -newer-than ,
except it compares mtime entries instead of ctime entries.
.It Fl -nodump ( Fl W Cm nodump )
.It Fl -nodump
(c and r modes only)
Honor the nodump file flag by skipping this file.
.It Fl -null ( Fl W Cm null )
.It Fl -null
(use with
.Fl I ,
.Fl T ,
@ -302,7 +302,7 @@ the archive will be discarded.
(c, r, u mode)
A synonym for
.Fl -format Ar ustar
.It Fl -one-file-system ( Fl W Cm one-file-system )
.It Fl -one-file-system
(c, r, and u modes)
Do not cross mount points.
.It Fl P
@ -345,8 +345,8 @@ Extract files as sparse files.
For every block on disk, check first if it contains only NULL bytes and seek
over it otherwise.
This works similiar to the conv=sparse option of dd.
.It Fl -strip-components Ar count ( Fl W Cm strip-components Ns = Ns Ar count )
(x and t mode only)
.It Fl -strip-components Ar count
(x mode only)
Remove the specified number of leading path elements.
Pathnames with fewer elements will be silently skipped.
Note that the pathname is edited after checking inclusion/exclusion patterns
@ -418,16 +418,6 @@ Print version of
and
.Nm libarchive ,
and exit.
.It Fl W Ar longopt=value
Long options (preceded by
.Fl - )
are only supported directly on systems that have the
.Xr getopt_long 3
function.
The
.Fl W
option can be used to access long options on systems that
do not support this function.
.It Fl w
Ask for confirmation for every action.
.It Fl X Ar filename

View File

@ -1,5 +1,5 @@
/*-
* Copyright (c) 2003-2007 Tim Kientzle
* Copyright (c) 2003-2008 Tim Kientzle
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -38,18 +38,6 @@ __FBSDID("$FreeBSD$");
#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
@ -78,14 +66,6 @@ struct option {
#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.
@ -101,133 +81,12 @@ extern int optind;
/* 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:jkLlmnOoPpqrts: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 \
@ -237,7 +96,6 @@ 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;
@ -295,33 +153,29 @@ main(int argc, char **argv)
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) {
while ((opt = bsdtar_getopt(bsdtar)) != -1) {
switch (opt) {
case 'B': /* GNU tar */
/* libarchive doesn't need this; just ignore it. */
break;
case 'b': /* SUSv2 */
t = atoi(optarg);
t = atoi(bsdtar->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);
set_chdir(bsdtar, bsdtar->optarg);
break;
case 'c': /* SUSv2 */
set_mode(bsdtar, opt);
@ -333,15 +187,15 @@ main(int argc, char **argv)
bsdtar->option_chroot = 1;
break;
case OPTION_EXCLUDE: /* GNU tar */
if (exclude(bsdtar, optarg))
if (exclude(bsdtar, bsdtar->optarg))
bsdtar_errc(bsdtar, 1, 0,
"Couldn't exclude %s\n", optarg);
"Couldn't exclude %s\n", bsdtar->optarg);
break;
case OPTION_FORMAT: /* GNU tar, others */
bsdtar->create_format = optarg;
bsdtar->create_format = bsdtar->optarg;
break;
case 'f': /* SUSv2 */
bsdtar->filename = optarg;
bsdtar->filename = bsdtar->optarg;
if (strcmp(bsdtar->filename, "-") == 0)
bsdtar->filename = NULL;
break;
@ -368,7 +222,7 @@ main(int argc, char **argv)
* permissions without having to create those
* permissions on disk.
*/
bsdtar->names_from_file = optarg;
bsdtar->names_from_file = bsdtar->optarg;
break;
case OPTION_INCLUDE:
/*
@ -376,10 +230,10 @@ main(int argc, char **argv)
* noone else needs this to filter entries
* when transforming archives.
*/
if (include(bsdtar, optarg))
if (include(bsdtar, bsdtar->optarg))
bsdtar_errc(bsdtar, 1, 0,
"Failed to add %s to inclusion list",
optarg);
bsdtar->optarg);
break;
case 'j': /* GNU tar */
#if HAVE_LIBBZ2
@ -389,7 +243,8 @@ main(int argc, char **argv)
bsdtar->create_compression);
bsdtar->create_compression = opt;
#else
bsdtar_warnc(bsdtar, 0, "-j compression not supported by this version of bsdtar");
bsdtar_warnc(bsdtar, 0,
"bzip2 compression not supported by this version of bsdtar");
usage(bsdtar);
#endif
break;
@ -420,28 +275,28 @@ main(int argc, char **argv)
* TODO: Add corresponding "older" options to reverse these.
*/
case OPTION_NEWER_CTIME: /* GNU tar */
bsdtar->newer_ctime_sec = get_date(optarg);
bsdtar->newer_ctime_sec = get_date(bsdtar->optarg);
break;
case OPTION_NEWER_CTIME_THAN:
{
struct stat st;
if (stat(optarg, &st) != 0)
if (stat(bsdtar->optarg, &st) != 0)
bsdtar_errc(bsdtar, 1, 0,
"Can't open file %s", optarg);
"Can't open file %s", bsdtar->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);
bsdtar->newer_mtime_sec = get_date(bsdtar->optarg);
break;
case OPTION_NEWER_MTIME_THAN:
{
struct stat st;
if (stat(optarg, &st) != 0)
if (stat(bsdtar->optarg, &st) != 0)
bsdtar_errc(bsdtar, 1, 0,
"Can't open file %s", optarg);
"Can't open file %s", bsdtar->optarg);
bsdtar->newer_mtime_sec = st.st_mtime;
bsdtar->newer_mtime_nsec =
ARCHIVE_STAT_MTIME_NANOS(&st);
@ -509,17 +364,18 @@ main(int argc, char **argv)
break;
case 's': /* NetBSD pax-as-tar */
#if HAVE_REGEX_H
add_substitution(bsdtar, optarg);
add_substitution(bsdtar, bsdtar->optarg);
#else
bsdtar_warnc(bsdtar, 0, "-s is not supported by this version of bsdtar");
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);
bsdtar->strip_components = atoi(bsdtar->optarg);
break;
case 'T': /* GNU tar */
bsdtar->names_from_file = optarg;
bsdtar->names_from_file = bsdtar->optarg;
break;
case 't': /* SUSv2 */
set_mode(bsdtar, opt);
@ -544,19 +400,19 @@ main(int argc, char **argv)
#if 0
/*
* The -W longopt feature is handled inside of
* bsdtar_getop(), so -W is not available here.
* bsdtar_getopt(), so -W is not available here.
*/
case 'W': /* Obscure, but useful GNU convention. */
case 'W': /* Obscure GNU convention. */
break;
#endif
case 'w': /* SUSv2 */
bsdtar->option_interactive = 1;
break;
case 'X': /* GNU tar */
if (exclude_from_file(bsdtar, optarg))
if (exclude_from_file(bsdtar, bsdtar->optarg))
bsdtar_errc(bsdtar, 1, 0,
"failed to process exclusions from file %s",
optarg);
bsdtar->optarg);
break;
case 'x': /* SUSv2 */
set_mode(bsdtar, opt);
@ -569,7 +425,8 @@ main(int argc, char **argv)
bsdtar->create_compression);
bsdtar->create_compression = opt;
#else
bsdtar_warnc(bsdtar, 0, "-y compression not supported by this version of bsdtar");
bsdtar_warnc(bsdtar, 0,
"bzip2 compression not supported by this version of bsdtar");
usage(bsdtar);
#endif
break;
@ -588,12 +445,13 @@ main(int argc, char **argv)
bsdtar->create_compression);
bsdtar->create_compression = opt;
#else
bsdtar_warnc(bsdtar, 0, "-z compression not supported by this version of bsdtar");
bsdtar_warnc(bsdtar, 0,
"gzip compression not supported by this version of bsdtar");
usage(bsdtar);
#endif
break;
case OPTION_USE_COMPRESS_PROGRAM:
bsdtar->compress_program = optarg;
bsdtar->compress_program = bsdtar->optarg;
break;
default:
usage(bsdtar);
@ -668,9 +526,6 @@ main(int argc, char **argv)
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);
@ -722,72 +577,6 @@ only_mode(struct bsdtar *bsdtar, const char *opt, const char *valid_modes)
}
/*-
* 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)
{
@ -799,11 +588,7 @@ usage(struct bsdtar *bsdtar)
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);
}
@ -828,11 +613,7 @@ static const char *long_help_msg =
" <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"
@ -880,80 +661,3 @@ long_help(struct bsdtar *bsdtar)
}
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);
}

View File

@ -80,6 +80,7 @@ struct bsdtar {
const char *progname;
int argc;
char **argv;
const char *optarg;
size_t gs_width; /* For 'list_item' in read.c */
size_t u_width; /* for 'list_item' in read.c */
uid_t user_uid; /* UID running this program */
@ -102,8 +103,36 @@ struct bsdtar {
struct substitution *substitution; /* for subst.c */
};
/* 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
};
void bsdtar_errc(struct bsdtar *, int _eval, int _code,
const char *fmt, ...) __dead2;
int bsdtar_getopt(struct bsdtar *);
void bsdtar_warnc(struct bsdtar *, int _code, const char *fmt, ...);
void cleanup_exclusions(struct bsdtar *);
void do_chdir(struct bsdtar *);

376
usr.bin/tar/cmdline.c Normal file
View File

@ -0,0 +1,376 @@
/*-
* Copyright (c) 2003-2008 Tim Kientzle
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Command line parser for tar.
*/
#include "bsdtar_platform.h"
__FBSDID("$FreeBSD$");
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#include "bsdtar.h"
/*
* Short options for tar. Please keep this sorted.
*/
static const char *short_options
= "Bb:C:cf:HhI:jkLlmnOoPpqrSs:T:tUuvW:wX:xyZz";
/*
* Long options for tar. Please keep this list sorted.
*
* The symbolic names for options that lack a short equivalent are
* defined in bsdtar.h. Also note that so far I've found no need
* to support optional arguments to long options. That would be
* a small change to the code below.
*/
static struct option {
const char *name;
int required; /* 1 if this option requires an argument. */
int equivalent; /* Equivalent short option. */
} tar_longopts[] = {
{ "absolute-paths", 0, 'P' },
{ "append", 0, 'r' },
{ "block-size", 1, 'b' },
{ "bunzip2", 0, 'j' },
{ "bzip", 0, 'j' },
{ "bzip2", 0, 'j' },
{ "cd", 1, 'C' },
{ "check-links", 0, OPTION_CHECK_LINKS },
{ "chroot", 0, OPTION_CHROOT },
{ "compress", 0, 'Z' },
{ "confirmation", 0, 'w' },
{ "create", 0, 'c' },
{ "dereference", 0, 'L' },
{ "directory", 1, 'C' },
{ "exclude", 1, OPTION_EXCLUDE },
{ "exclude-from", 1, 'X' },
{ "extract", 0, 'x' },
{ "fast-read", 0, 'q' },
{ "file", 1, 'f' },
{ "files-from", 1, 'T' },
{ "format", 1, OPTION_FORMAT },
{ "gunzip", 0, 'z' },
{ "gzip", 0, 'z' },
{ "help", 0, OPTION_HELP },
{ "include", 1, OPTION_INCLUDE },
{ "interactive", 0, 'w' },
{ "insecure", 0, 'P' },
{ "keep-newer-files", 0, OPTION_KEEP_NEWER_FILES },
{ "keep-old-files", 0, 'k' },
{ "list", 0, 't' },
{ "modification-time", 0, 'm' },
{ "newer", 1, OPTION_NEWER_CTIME },
{ "newer-ctime", 1, OPTION_NEWER_CTIME },
{ "newer-ctime-than", 1, OPTION_NEWER_CTIME_THAN },
{ "newer-mtime", 1, OPTION_NEWER_MTIME },
{ "newer-mtime-than", 1, OPTION_NEWER_MTIME_THAN },
{ "newer-than", 1, OPTION_NEWER_CTIME_THAN },
{ "nodump", 0, OPTION_NODUMP },
{ "norecurse", 0, 'n' },
{ "no-recursion", 0, 'n' },
{ "no-same-owner", 0, OPTION_NO_SAME_OWNER },
{ "no-same-permissions", 0, OPTION_NO_SAME_PERMISSIONS },
{ "null", 0, OPTION_NULL },
{ "numeric-owner", 0, OPTION_NUMERIC_OWNER },
{ "one-file-system", 0, OPTION_ONE_FILE_SYSTEM },
{ "posix", 0, OPTION_POSIX },
{ "preserve-permissions", 0, 'p' },
{ "read-full-blocks", 0, 'B' },
{ "same-permissions", 0, 'p' },
{ "strip-components", 1, OPTION_STRIP_COMPONENTS },
{ "to-stdout", 0, 'O' },
{ "totals", 0, OPTION_TOTALS },
{ "uncompress", 0, 'Z' },
{ "unlink", 0, 'U' },
{ "unlink-first", 0, 'U' },
{ "update", 0, 'u' },
{ "use-compress-program", 1, OPTION_USE_COMPRESS_PROGRAM },
{ "verbose", 0, 'v' },
{ "version", 0, OPTION_VERSION },
{ NULL, 0, 0 }
};
/*
* This getopt implementation has two key features that common
* getopt_long() implementations lack. Apart from those, it's a
* straightforward option parser, considerably simplified by not
* needing to support the wealth of exotic getopt_long() features. It
* has, of course, been shamelessly tailored for bsdtar. (If you're
* looking for a generic getopt_long() implementation for your
* project, I recommend Gregory Pietsch's public domain getopt_long()
* implementation.) The two additional features are:
*
* Old-style tar arguments: The original tar implementation treated
* the first argument word as a list of single-character option
* letters. All arguments follow as separate words. For example,
* tar xbf 32 /dev/tape
* Here, the "xbf" is three option letters, "32" is the argument for
* "b" and "/dev/tape" is the argument for "f". We support this usage
* if the first command-line argument does not begin with '-'. We
* also allow regular short and long options to follow, e.g.,
* tar xbf 32 /dev/tape -P --format=pax
*
* -W long options: There's an obscure GNU convention (only rarely
* supported even there) that allows "-W option=argument" as an
* alternative way to support long options. This was supported in
* early bsdtar as a way to access long options on platforms that did
* not support getopt_long() and is preserved here for backwards
* compatibility. (Of course, if I'd started with a custom
* command-line parser from the beginning, I would have had normal
* long option support on every platform so that hack wouldn't have
* been necessary. Oh, well. Some mistakes you just have to live
* with.)
*
* TODO: We should be able to use this to pull files and intermingled
* options (such as -C) from the command line in write mode. That
* will require a little rethinking of the argument handling in
* bsdtar.c.
*
* TODO: If we want to support arbitrary command-line options from -T
* input (as GNU tar does), we may need to extend this to handle option
* words from sources other than argv/arc. I'm not really sure if I
* like that feature of GNU tar, so it's certainly not a priority.
*/
int
bsdtar_getopt(struct bsdtar *bsdtar)
{
enum { state_start = 0, state_old_tar, state_next_word,
state_short, state_long };
static int state = state_start;
static char *opt_word;
const struct option *popt, *match = NULL, *match2 = NULL;
const char *p, *long_prefix = "--";
size_t optlength;
int opt = '?';
int required = 0;
bsdtar->optarg = NULL;
/* First time through, initialize everything. */
if (state == state_start) {
/* Skip program name. */
++bsdtar->argv;
--bsdtar->argc;
if (*bsdtar->argv == NULL)
return (-1);
/* Decide between "new style" and "old style" arguments. */
if (bsdtar->argv[0][0] == '-') {
state = state_next_word;
} else {
state = state_old_tar;
opt_word = *bsdtar->argv++;
--bsdtar->argc;
}
}
/*
* We're parsing old-style tar arguments
*/
if (state == state_old_tar) {
/* Get the next option character. */
opt = *opt_word++;
if (opt == '\0') {
/* New-style args can follow old-style. */
state = state_next_word;
} else {
/* See if it takes an argument. */
p = strchr(short_options, opt);
if (p == NULL)
return ('?');
if (p[1] == ':') {
bsdtar->optarg = *bsdtar->argv;
if (bsdtar->optarg == NULL) {
bsdtar_warnc(bsdtar, 0,
"Option %c requires an argument",
opt);
return ('?');
}
++bsdtar->argv;
--bsdtar->argc;
}
}
}
/*
* We're ready to look at the next word in argv.
*/
if (state == state_next_word) {
/* No more arguments, so no more options. */
if (bsdtar->argv[0] == NULL)
return (-1);
/* Doesn't start with '-', so no more options. */
if (bsdtar->argv[0][0] != '-')
return (-1);
/* "--" marks end of options; consume it and return. */
if (strcmp(bsdtar->argv[0], "--") == 0) {
++bsdtar->argv;
--bsdtar->argc;
return (-1);
}
/* Get next word for parsing. */
opt_word = *bsdtar->argv++;
--bsdtar->argc;
if (opt_word[1] == '-') {
/* Set up long option parser. */
state = state_long;
opt_word += 2; /* Skip leading '--' */
} else {
/* Set up short option parser. */
state = state_short;
++opt_word; /* Skip leading '-' */
}
}
/*
* We're parsing a group of POSIX-style single-character options.
*/
if (state == state_short) {
/* Peel next option off of a group of short options. */
opt = *opt_word++;
if (opt == '\0') {
/* End of this group; recurse to get next option. */
state = state_next_word;
return bsdtar_getopt(bsdtar);
}
/* Does this option take an argument? */
p = strchr(short_options, opt);
if (p == NULL)
return ('?');
if (p[1] == ':')
required = 1;
/* If it takes an argument, parse that. */
if (required) {
/* If arg is run-in, opt_word already points to it. */
if (opt_word[0] == '\0') {
/* Otherwise, pick up the next word. */
opt_word = *bsdtar->argv;
if (opt_word == NULL) {
bsdtar_warnc(bsdtar, 0,
"Option -%c requires an argument",
opt);
return ('?');
}
++bsdtar->argv;
--bsdtar->argc;
}
if (opt == 'W') {
state = state_long;
long_prefix = "-W "; /* For clearer errors. */
} else {
state = state_next_word;
bsdtar->optarg = opt_word;
}
}
}
/* We're reading a long option, including -W long=arg convention. */
if (state == state_long) {
/* After this long option, we'll be starting a new word. */
state = state_next_word;
/* Option name ends at '=' if there is one. */
p = strchr(opt_word, '=');
if (p != NULL) {
optlength = (size_t)(p - opt_word);
bsdtar->optarg = (char *)(uintptr_t)(p + 1);
} else {
optlength = strlen(opt_word);
}
/* Search the table for an unambiguous match. */
for (popt = tar_longopts; popt->name != NULL; popt++) {
/* Short-circuit if first chars don't match. */
if (popt->name[0] != opt_word[0])
continue;
/* If option is a prefix of name in table, record it.*/
if (strncmp(opt_word, popt->name, optlength) == 0) {
match2 = match; /* Record up to two matches. */
match = popt;
/* If it's an exact match, we're done. */
if (strlen(popt->name) == optlength) {
match2 = NULL; /* Forget the others. */
break;
}
}
}
/* Fail if there wasn't a unique match. */
if (match == NULL) {
bsdtar_warnc(bsdtar, 0,
"Option %s%s is not supported",
long_prefix, opt_word);
return ('?');
}
if (match2 != NULL) {
bsdtar_warnc(bsdtar, 0,
"Ambiguous option %s%s (matches --%s and --%s)",
long_prefix, opt_word, match->name, match2->name);
return ('?');
}
/* We've found a unique match; does it need an argument? */
if (match->required) {
/* Argument required: get next word if necessary. */
if (bsdtar->optarg == NULL) {
bsdtar->optarg = *bsdtar->argv;
if (bsdtar->optarg == NULL) {
bsdtar_warnc(bsdtar, 0,
"Option %s%s requires an argument",
long_prefix, match->name);
return ('?');
}
++bsdtar->argv;
--bsdtar->argc;
}
} else {
/* Argument forbidden: fail if there is one. */
if (bsdtar->optarg != NULL) {
bsdtar_warnc(bsdtar, 0,
"Option %s%s does not allow an argument",
long_prefix, match->name);
return ('?');
}
}
return (match->equivalent);
}
return (opt);
}

View File

@ -52,7 +52,6 @@
#define HAVE_FNMATCH_H 1
#define HAVE_FNM_LEADING_DIR 1
#define HAVE_FTRUNCATE 1
#define HAVE_GETOPT_LONG 1
#undef HAVE_GETXATTR
#define HAVE_GRP_H 1
#define HAVE_INTTYPES_H 1