]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Custom from-scratch command-line parser for bsdtar. This is
authorTim Kientzle <kientzle@gmail.com>
Mon, 6 Oct 2008 22:55:16 +0000 (18:55 -0400)
committerTim Kientzle <kientzle@gmail.com>
Mon, 6 Oct 2008 22:55:16 +0000 (18:55 -0400)
both more portable and more straightforward than the earlier
getopt()/getopt_long() wrapper approach, requires no fancy
configure/make glue to choose a platform implementation, and
gives me consistent command line parsing on every platform,
which should greatly simplify my attempts at building a robust
test suite.

I should have done this years ago.

SVN-Revision: 219

Makefile.am
tar/Makefile
tar/bsdtar.1
tar/bsdtar.c
tar/bsdtar.h
tar/cmdline.c [new file with mode: 0644]
tar/config_freebsd.h

index ae4fa32d74b15cd179eaf5539285804d2319cb5d..cccdc33cdcdfceb9610ae091a3441948c81214ad 100644 (file)
@@ -273,6 +273,7 @@ bsdtar_SOURCES=                             \
                tar/bsdtar.c            \
                tar/bsdtar.h            \
                tar/bsdtar_platform.h   \
+               tar/cmdline.c           \
                tar/getdate.y           \
                tar/matching.c          \
                tar/read.c              \
index 67f2b04743353649815b47fc22130ede4a0f7939..0996faacdb5d5d4ff727587eb66d1ca12799712b 100644 (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
index a6015dc6e63fe28eb9240ca9aca4293a2bf08d78..7c7b05278c5f4fb6be9b590637a449a54a08bad7 100644 (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,7 +345,7 @@ 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 )
+.It Fl -strip-components Ar count
 (x and t mode only)
 Remove the specified number of leading path elements.
 Pathnames with fewer elements will be silently skipped.
@@ -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
index 40ddd78a451e9b53f49385e754680a762394850c..2f2e1048bf874a9d4c2785ab961afa505b2ac9e4 100644 (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: src/usr.bin/tar/bsdtar.c,v 1.92 2008/08/22 01:22:55 kientzle
 #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);
-}
index fb256d8dfc4ac73fd42dd0d0ac707b750cf253c5..d3bf854cf2c4c8044f70a14cb2ddf670f0b8be66 100644 (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, ...) __LA_DEAD;
+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 *);
diff --git a/tar/cmdline.c b/tar/cmdline.c
new file mode 100644 (file)
index 0000000..09818a3
--- /dev/null
@@ -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);
+}
index 25aceea717691bff2bc20df7f18ffc13a3655b51..4686cd834e6a40b3f1817e96039b94ec13a4f387 100644 (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