aboutsummaryrefslogtreecommitdiff
path: root/usr.bin/cpio
diff options
context:
space:
mode:
authorTim Kientzle <kientzle@FreeBSD.org>2008-12-06 07:30:40 +0000
committerTim Kientzle <kientzle@FreeBSD.org>2008-12-06 07:30:40 +0000
commit4b058a88d83144dccb6302432c57f3ac9ddef79b (patch)
tree8dd93d95f99e6de2862b68d8331d5d51e643ddcc /usr.bin/cpio
parent5eca8160b554a0ee6d66be187fcaa2c46ccad633 (diff)
downloadsrc-4b058a88d83144dccb6302432c57f3ac9ddef79b.tar.gz
src-4b058a88d83144dccb6302432c57f3ac9ddef79b.zip
Custom command line parser for cpio; this is a little more
code but should be a lot fewer cross-platform compatibility headaches.
Notes
Notes: svn path=/head/; revision=185685
Diffstat (limited to 'usr.bin/cpio')
-rw-r--r--usr.bin/cpio/Makefile2
-rw-r--r--usr.bin/cpio/cmdline.c284
-rw-r--r--usr.bin/cpio/config_freebsd.h1
-rw-r--r--usr.bin/cpio/cpio.c25
-rw-r--r--usr.bin/cpio/cpio.h5
5 files changed, 198 insertions, 119 deletions
diff --git a/usr.bin/cpio/Makefile b/usr.bin/cpio/Makefile
index b33cb20a45e1..59763ffc7c74 100644
--- a/usr.bin/cpio/Makefile
+++ b/usr.bin/cpio/Makefile
@@ -3,7 +3,7 @@
.include <bsd.own.mk>
PROG= bsdcpio
-BSDCPIO_VERSION_STRING=1.0.0
+BSDCPIO_VERSION_STRING=1.1.0
SRCS= cpio.c cmdline.c err.c matching.c pathmatch.c
WARNS?= 6
DPADD= ${LIBARCHIVE} ${LIBZ} ${LIBBZ2}
diff --git a/usr.bin/cpio/cmdline.c b/usr.bin/cpio/cmdline.c
index a9774e9269f7..e08fd82f6c31 100644
--- a/usr.bin/cpio/cmdline.c
+++ b/usr.bin/cpio/cmdline.c
@@ -31,18 +31,6 @@ __FBSDID("$FreeBSD$");
#ifdef HAVE_ERRNO_H
#include <errno.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_GRP_H
#include <grp.h>
#endif
@@ -60,119 +48,215 @@ struct option {
#include "cpio.h"
/*
- *
- * Option parsing routines for bsdcpio.
- *
- */
-
-
-static const char *cpio_opts = "0AaBC:F:O:cdE:f:H:hijLlmopR:rtuvW:yZz";
-
-/*
- * On systems that lack getopt_long, long options can be specified
- * using -W longopt and -W longopt=value, e.g. "-W version" is the
- * same as "--version" and "-W format=ustar" is the same as "--format
- * ustar". This does not rely the GNU getopt() "W;" extension, so
- * should work correctly on any system with a POSIX-compliant
- * getopt().
+ * Short options for cpio. Please keep this sorted.
*/
+static const char *short_options = "0AaBC:F:O:cdE:f:H:hijLlmopR:rtuvW:yZz";
/*
- * If you add anything, be very careful to keep this list properly
- * sorted, as the -W logic below relies on it.
+ * Long options for cpio. Please keep this sorted.
*/
-static const struct option cpio_longopts[] = {
- { "create", no_argument, NULL, 'o' },
- { "extract", no_argument, NULL, 'i' },
- { "file", required_argument, NULL, 'F' },
- { "format", required_argument, NULL, 'H' },
- { "help", no_argument, NULL, 'h' },
- { "insecure", no_argument, NULL, OPTION_INSECURE },
- { "link", no_argument, NULL, 'l' },
- { "list", no_argument, NULL, 't' },
- { "make-directories", no_argument, NULL, 'd' },
- { "no-preserve-owner", no_argument, NULL, OPTION_NO_PRESERVE_OWNER },
- { "null", no_argument, NULL, '0' },
- { "owner", required_argument, NULL, 'R' },
- { "pass-through", no_argument, NULL, 'p' },
- { "preserve-modification-time", no_argument, NULL, 'm' },
- { "quiet", no_argument, NULL, OPTION_QUIET },
- { "unconditional", no_argument, NULL, 'u' },
- { "verbose", no_argument, NULL, 'v' },
- { "version", no_argument, NULL, OPTION_VERSION },
- { NULL, 0, NULL, 0 }
+static const struct option {
+ const char *name;
+ int required; /* 1 if this option requires an argument */
+ int equivalent; /* Equivalent short option. */
+} cpio_longopts[] = {
+ { "create", 0, 'o' },
+ { "extract", 0, 'i' },
+ { "file", 1, 'F' },
+ { "format", 1, 'H' },
+ { "help", 0, 'h' },
+ { "insecure", 0, OPTION_INSECURE },
+ { "link", 0, 'l' },
+ { "list", 0, 't' },
+ { "make-directories", 0, 'd' },
+ { "no-preserve-owner", 0, OPTION_NO_PRESERVE_OWNER },
+ { "null", 0, '0' },
+ { "owner", 1, 'R' },
+ { "pass-through", 0, 'p' },
+ { "preserve-modification-time", 0, 'm' },
+ { "quiet", 0, OPTION_QUIET },
+ { "unconditional", 0, 'u' },
+ { "verbose", 0, 'v' },
+ { "version", 0, OPTION_VERSION },
+ { NULL, 0, 0 }
};
/*
- * Parse command-line options using system-provided getopt() or getopt_long().
- * If option is -W, then parse argument as a long option.
+ * I used to try to select platform-provided getopt() or
+ * getopt_long(), but that caused a lot of headaches. In particular,
+ * I couldn't consistently use long options in the test harness
+ * because not all platforms have getopt_long(). That in turn led to
+ * overuse of the -W hack in the test harness, which made it rough to
+ * run the test harness against GNU cpio. (I periodically run the
+ * test harness here against GNU cpio as a sanity-check. Yes,
+ * I've found a couple of bugs in GNU cpio that way.)
*/
int
cpio_getopt(struct cpio *cpio)
{
- char *p, *q;
- const struct option *option, *option2;
- int opt;
- int option_index;
- size_t option_length;
+ enum { state_start = 0, state_next_word, state_short, state_long };
+ static int state = state_start;
+ static char *opt_word;
- option_index = -1;
+ const struct option *popt, *match = NULL, *match2 = NULL;
+ const char *p, *long_prefix = "--";
+ size_t optlength;
+ int opt = '?';
+ int required = 0;
-#ifdef HAVE_GETOPT_LONG
- opt = getopt_long(cpio->argc, cpio->argv, cpio_opts,
- cpio_longopts, &option_index);
-#else
- opt = getopt(cpio->argc, cpio->argv, cpio_opts);
-#endif
+ cpio->optarg = NULL;
- /* 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;
+ /* First time through, initialize everything. */
+ if (state == state_start) {
+ /* Skip program name. */
+ ++cpio->argv;
+ --cpio->argc;
+ state = state_next_word;
+ }
+
+ /*
+ * We're ready to look at the next word in argv.
+ */
+ if (state == state_next_word) {
+ /* No more arguments, so no more options. */
+ if (cpio->argv[0] == NULL)
+ return (-1);
+ /* Doesn't start with '-', so no more options. */
+ if (cpio->argv[0][0] != '-')
+ return (-1);
+ /* "--" marks end of options; consume it and return. */
+ if (strcmp(cpio->argv[0], "--") == 0) {
+ ++cpio->argv;
+ --cpio->argc;
+ return (-1);
+ }
+ /* Get next word for parsing. */
+ opt_word = *cpio->argv++;
+ --cpio->argc;
+ if (opt_word[1] == '-') {
+ /* Set up long option parser. */
+ state = state_long;
+ opt_word += 2; /* Skip leading '--' */
} else {
- option_length = strlen(p);
- optarg = NULL;
+ /* Set up short option parser. */
+ state = state_short;
+ ++opt_word; /* Skip leading '-' */
}
- option = cpio_longopts;
- while (option->name != NULL &&
- (strlen(option->name) < option_length ||
- strncmp(p, option->name, option_length) != 0 )) {
- option++;
+ }
+
+ /*
+ * 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 cpio_getopt(cpio);
}
- if (option->name != NULL) {
- option2 = option;
- opt = option->val;
+ /* Does this option take an argument? */
+ p = strchr(short_options, opt);
+ if (p == NULL)
+ return ('?');
+ if (p[1] == ':')
+ required = 1;
- /* If the first match was exact, we're done. */
- if (strncmp(p, option->name, strlen(option->name)) == 0) {
- while (option->name != NULL)
- option++;
+ /* 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 = *cpio->argv;
+ if (opt_word == NULL) {
+ cpio_warnc(0,
+ "Option -%c requires an argument",
+ opt);
+ return ('?');
+ }
+ ++cpio->argv;
+ --cpio->argc;
+ }
+ if (opt == 'W') {
+ state = state_long;
+ long_prefix = "-W "; /* For clearer errors. */
} 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++;
+ state = state_next_word;
+ cpio->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);
+ cpio->optarg = (char *)(uintptr_t)(p + 1);
+ } else {
+ optlength = strlen(opt_word);
+ }
+
+ /* Search the table for an unambiguous match. */
+ for (popt = cpio_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;
}
}
- if (option->name != NULL)
- cpio_errc(1, 0,
- "Ambiguous option %s "
- "(matches both %s and %s)",
- p, option2->name, option->name);
+ }
- if (option2->has_arg == required_argument
- && optarg == NULL)
- cpio_errc(1, 0,
- "Option \"%s\" requires argument", p);
+ /* Fail if there wasn't a unique match. */
+ if (match == NULL) {
+ cpio_warnc(0,
+ "Option %s%s is not supported",
+ long_prefix, opt_word);
+ return ('?');
+ }
+ if (match2 != NULL) {
+ cpio_warnc(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 (cpio->optarg == NULL) {
+ cpio->optarg = *cpio->argv;
+ if (cpio->optarg == NULL) {
+ cpio_warnc(0,
+ "Option %s%s requires an argument",
+ long_prefix, match->name);
+ return ('?');
+ }
+ ++cpio->argv;
+ --cpio->argc;
+ }
} else {
- opt = '?';
+ /* Argument forbidden: fail if there is one. */
+ if (cpio->optarg != NULL) {
+ cpio_warnc(0,
+ "Option %s%s does not allow an argument",
+ long_prefix, match->name);
+ return ('?');
+ }
}
+ return (match->equivalent);
}
return (opt);
diff --git a/usr.bin/cpio/config_freebsd.h b/usr.bin/cpio/config_freebsd.h
index 992c850f97cf..df376ff2d30a 100644
--- a/usr.bin/cpio/config_freebsd.h
+++ b/usr.bin/cpio/config_freebsd.h
@@ -52,7 +52,6 @@
#define HAVE_FNM_LEADING_DIR 1
#define HAVE_FTRUNCATE 1
#define HAVE_FUTIMES 1
-#define HAVE_GETOPT_LONG 1
#undef HAVE_GETXATTR
#define HAVE_GRP_H 1
#define HAVE_INTTYPES_H 1
diff --git a/usr.bin/cpio/cpio.c b/usr.bin/cpio/cpio.c
index b2f9a95a0843..f7f64ebbbb44 100644
--- a/usr.bin/cpio/cpio.c
+++ b/usr.bin/cpio/cpio.c
@@ -161,9 +161,9 @@ main(int argc, char *argv[])
cpio->bytes_per_block = 5120;
break;
case 'C': /* NetBSD/OpenBSD */
- cpio->bytes_per_block = atoi(optarg);
+ cpio->bytes_per_block = atoi(cpio->optarg);
if (cpio->bytes_per_block <= 0)
- cpio_errc(1, 0, "Invalid blocksize %s", optarg);
+ cpio_errc(1, 0, "Invalid blocksize %s", cpio->optarg);
break;
case 'c': /* POSIX 1997 */
cpio->format = "odc";
@@ -172,22 +172,22 @@ main(int argc, char *argv[])
cpio->extract_flags &= ~ARCHIVE_EXTRACT_NO_AUTODIR;
break;
case 'E': /* NetBSD/OpenBSD */
- include_from_file(cpio, optarg);
+ include_from_file(cpio, cpio->optarg);
break;
case 'F': /* NetBSD/OpenBSD/GNU cpio */
- cpio->filename = optarg;
+ cpio->filename = cpio->optarg;
break;
case 'f': /* POSIX 1997 */
- exclude(cpio, optarg);
+ exclude(cpio, cpio->optarg);
break;
case 'H': /* GNU cpio (also --format) */
- cpio->format = optarg;
+ cpio->format = cpio->optarg;
break;
case 'h':
long_help();
break;
case 'I': /* NetBSD/OpenBSD */
- cpio->filename = optarg;
+ cpio->filename = cpio->optarg;
break;
case 'i': /* POSIX 1997 */
cpio->mode = opt;
@@ -209,7 +209,7 @@ main(int argc, char *argv[])
cpio->extract_flags &= ~ARCHIVE_EXTRACT_OWNER;
break;
case 'O': /* GNU cpio */
- cpio->filename = optarg;
+ cpio->filename = cpio->optarg;
break;
case 'o': /* POSIX 1997 */
cpio->mode = opt;
@@ -222,7 +222,7 @@ main(int argc, char *argv[])
cpio->quiet = 1;
break;
case 'R': /* GNU cpio, also --owner */
- if (owner_parse(optarg, &uid, &gid))
+ if (owner_parse(cpio->optarg, &uid, &gid))
usage();
if (uid != -1)
cpio->uid_override = uid;
@@ -269,9 +269,6 @@ main(int argc, char *argv[])
/* TODO: Sanity-check args, error out on nonsensical combinations. */
- cpio->argc -= optind;
- cpio->argv += optind;
-
switch (cpio->mode) {
case 'o':
mode_out(cpio);
@@ -314,11 +311,7 @@ usage(void)
fprintf(stderr, " List: %s -it < archive\n", p);
fprintf(stderr, " Extract: %s -i < archive\n", p);
fprintf(stderr, " Create: %s -o < filenames > archive\n", p);
-#ifdef HAVE_GETOPT_LONG
fprintf(stderr, " Help: %s --help\n", p);
-#else
- fprintf(stderr, " Help: %s -h\n", p);
-#endif
exit(1);
}
diff --git a/usr.bin/cpio/cpio.h b/usr.bin/cpio/cpio.h
index e85345d1b71f..a378503acf2b 100644
--- a/usr.bin/cpio/cpio.h
+++ b/usr.bin/cpio/cpio.h
@@ -42,8 +42,11 @@
* functions.
*/
struct cpio {
+ /* Option parsing */
+ const char *optarg;
+
/* Options */
- char *filename;
+ const char *filename;
char mode; /* -i -o -p */
char compress; /* -j, -y, or -z */
const char *format; /* -H format */