diff --git a/usr.bin/tar/bsdtar.1 b/usr.bin/tar/bsdtar.1 index 6a374ee49749..fb5224f4fd87 100644 --- a/usr.bin/tar/bsdtar.1 +++ b/usr.bin/tar/bsdtar.1 @@ -136,23 +136,10 @@ As a rule, this argument is only needed when reading from or writing to tape drives, and usually not even then as the default block size of 20 records (10240 bytes) is very common. .It Fl C Ar directory -Change directories. -The directory is changed after the archive -is opened, but before any entries are extracted or written. -(In particular, it does not affect the interpretation of the -.Fl f -option.) -In create mode, note that -.Fl C -options are all processed before any files are read. -To change directories between files, use -.Cm C= Ns Pa dir . -.It Cm C= Ns Pa dir -(c and r mode only) -Change to the specified directory before adding the following files. -(Note that this is not an option in the sense of -.Xr getopt 3 , -and is therefore processed as the files are processed.) +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 -exclude Ar pattern Do not process files or directories that match the specified pattern. @@ -358,11 +345,11 @@ the default tape drive: .Pp In create mode, the list of files and directories to be archived can also include directory change instructions of the form -.Cm C= Ns Pa foo/baz +.Cm -C Ns Pa foo/baz and archive inclusions of the form .Cm @ Ns Pa archive-file . For example, the command line -.Dl Nm Fl c Fl f Pa new.tar Pa foo1 Cm @ Ns Pa old.tgz Cm C= Ns Pa /tmp Pa foo2 +.Dl Nm Fl c Fl f Pa new.tar Pa foo1 Cm @ Ns Pa old.tgz Cm -C Ns Pa /tmp Pa foo2 will create a new archive .Pa new.tar . .Nm @@ -560,14 +547,7 @@ options do not. .Pp The .Fl C Pa dir -option differs from historic implementations. -In order to provide the range of historic behaviors while -retaining some consistency with POSIX option-processing -conventions, this implementation uses -.Fl C Pa dir -and -.Cm C= Ns Pa dir -for the two distinct behaviors. +option may differ from historic implementations. .Pp All archive output is written in correctly-sized blocks, even if the output is being compressed. @@ -611,11 +591,6 @@ mode with the extension. .Pp To archive a file called -.Pa C=foo , -you must specify it as -.Pa ./C=foo -on the command line. -Similarly, to archive a file called .Pa @foo or .Pa -foo diff --git a/usr.bin/tar/bsdtar.c b/usr.bin/tar/bsdtar.c index 477e5a088e0b..17672f9539d6 100644 --- a/usr.bin/tar/bsdtar.c +++ b/usr.bin/tar/bsdtar.c @@ -37,6 +37,15 @@ __FBSDID("$FreeBSD$"); #include #ifdef HAVE_GETOPT_LONG #include +#else +struct option { + const char *name; + int has_arg; + int *flag; + int val; +}; +#define no_argument 0 +#define required_argument 1 #endif #include #include @@ -48,6 +57,8 @@ __FBSDID("$FreeBSD$"); #include "bsdtar.h" +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 *, char mode, const char *opt, const char *valid); @@ -55,21 +66,29 @@ static char ** rewrite_argv(struct bsdtar *, int *argc, char ** src_argv, const char *optstring); -const char *tar_opts = "Bb:C:cF:f:HhjkLlmnOoPprtT:UuvwX:xyZz"; - -#ifdef HAVE_GETOPT_LONG /* - * These long options are deliberately not documented. They are - * provided only to make life easier for people using GNU tar. The - * only long options documented in the manual page are the ones with - * no corresponding short option (currently, --exclude, --nodump, and - * --fast-read). + * 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. + */ +const char *tar_opts = "+Bb:C:cF:f:HhjkLlmnOoPprtT: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. * - * XXX TODO: Provide short options for --exclude, --nodump and --fast-read - * so that bsdtar is usable on systems that do not have (or do not want - * to use) getopt_long(). + * 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. */ #define OPTION_EXCLUDE 1 #define OPTION_FAST_READ 2 #define OPTION_NODUMP 3 @@ -119,17 +138,17 @@ const struct option tar_longopts[] = { { "verbose", no_argument, NULL, 'v' }, { NULL, 0, NULL, 0 } }; -#endif int main(int argc, char **argv) { struct bsdtar *bsdtar, bsdtar_storage; + const struct option *option; struct passwd *pwent; int opt; char mode; - char buff[16]; char possible_help_request; + char buff[16]; /* * Use a pointer for consistency, but stack-allocated storage @@ -177,12 +196,7 @@ main(int argc, char **argv) bsdtar->argc = argc; /* Process all remaining arguments now. */ -#ifdef HAVE_GETOPT_LONG - while ((opt = getopt_long(bsdtar->argc, bsdtar->argv, - tar_opts, tar_longopts, NULL)) != -1) { -#else - while ((opt = getopt(bsdtar->argc, bsdtar->argv, tar_opts)) != -1) { -#endif + while ((opt = bsdtar_getopt(bsdtar, tar_opts, &option)) != -1) { switch (opt) { case 'B': /* GNU tar */ /* @@ -194,7 +208,7 @@ main(int argc, char **argv) bsdtar->bytes_per_block = 512 * atoi(optarg); break; case 'C': /* GNU tar */ - /* XXX How should multiple -C options be handled? */ + /* Defer first -C until after -f is opened. */ bsdtar->start_dir = optarg; break; case 'c': /* SUSv2 */ @@ -204,11 +218,9 @@ main(int argc, char **argv) opt, mode); mode = opt; break; -#ifdef HAVE_GETOPT_LONG case OPTION_EXCLUDE: /* GNU tar */ exclude(bsdtar, optarg); break; -#endif case 'F': bsdtar->create_format = optarg; break; @@ -217,11 +229,9 @@ main(int argc, char **argv) if (strcmp(bsdtar->filename, "-") == 0) bsdtar->filename = NULL; break; -#ifdef HAVE_GETOPT_LONG case OPTION_FAST_READ: /* GNU tar */ bsdtar->option_fast_read = 1; break; -#endif case 'H': /* BSD convention */ bsdtar->symlink_mode = 'H'; break; @@ -230,17 +240,13 @@ main(int argc, char **argv) /* Hack: -h by itself is the "help" command. */ possible_help_request = 1; break; -#ifdef HAVE_GETOPT_LONG case OPTION_HELP: long_help(bsdtar); exit(0); break; -#endif -#ifdef HAVE_GETOPT_LONG case OPTION_INCLUDE: include(bsdtar, optarg); break; -#endif case 'j': /* GNU tar */ if (bsdtar->create_compression != '\0') bsdtar_errc(bsdtar, 1, 0, @@ -263,12 +269,9 @@ main(int argc, char **argv) case 'n': /* GNU tar */ bsdtar->option_no_subdirs = 1; break; -#ifdef HAVE_GETOPT_LONG case OPTION_NODUMP: /* star */ bsdtar->option_honor_nodump = 1; break; -#endif -#ifdef HAVE_GETOPT_LONG case OPTION_NO_SAME_PERMISSIONS: /* GNU tar */ /* * This is always the default in FreeBSD's @@ -277,18 +280,15 @@ main(int argc, char **argv) * command-line option as a no-op. */ break; -#endif case 'O': /* GNU tar */ bsdtar->option_stdout = 1; break; case 'o': /* SUSv2; note that GNU -o conflicts */ bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_OWNER; break; -#if HAVE_GETOPT_LONG case OPTION_ONE_FILE_SYSTEM: /* -l in GNU tar */ bsdtar->option_dont_traverse_mounts = 1; break; -#endif #if 0 /* * The common BSD -P option is not necessary, since @@ -472,7 +472,7 @@ only_mode(struct bsdtar *bsdtar, char mode, * Convert traditional tar arguments into new-style. * For example, * tar tvfb file.tar 32 --exclude FOO - * must be converted to + * 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 @@ -566,6 +566,8 @@ static const char *long_help_msg[] = { " -F {ustar|pax|cpio|shar} Select archive format\n", #ifdef HAVE_GETOPT_LONG " --exclude Skip files that match pattern\n", +#else + " -W exclude= Skip files that match pattern\n", #endif " C= Change to before processing remaining files\n", " @ Add entries from to output\n", @@ -624,3 +626,70 @@ long_help(struct bsdtar *bsdtar) } fflush(stderr); } + +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 = 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; + + /* 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); + + } else { + opt = '?'; + /* TODO: Set up a fake 'struct option' for + * error reporting... ? ? ? */ + } + } + + return (opt); +} diff --git a/usr.bin/tar/write.c b/usr.bin/tar/write.c index edf3909f9b1b..da88154d0004 100644 --- a/usr.bin/tar/write.c +++ b/usr.bin/tar/write.c @@ -358,19 +358,26 @@ write_archive(struct archive *a, struct bsdtar *bsdtar) while (*bsdtar->argv) { arg = *bsdtar->argv; - if (arg[0] == 'C' && arg[1] == '=') { + if (arg[0] == '-' && arg[1] == 'C') { arg += 2; + if (*arg == '\0') { + bsdtar->argv++; + arg = *bsdtar->argv; + if (arg == NULL) + bsdtar_errc(bsdtar, 1, 0, + "Missing argument for -C"); + } /*- - * The logic here for C= attempts to avoid + * The logic here for -C attempts to avoid * chdir() as long as possible. For example: - * "C=/foo C=/bar file" + * "-C /foo -C /bar file" * needs chdir("/bar") but not chdir("/foo") - * "C=/foo C=bar file" + * "-C /foo -C bar file" * needs chdir("/foo/bar") - * "C=/foo C=bar /file1" + * "-C /foo -C bar /file1" * does not need chdir() - * "C=/foo C=bar /file1 file2" + * "-C /foo -C bar /file1 file2" * needs chdir("/foo/bar") before file2 * * The only correct way to handle this is to @@ -380,17 +387,17 @@ write_archive(struct archive *a, struct bsdtar *bsdtar) * * I went to all this work so that programs * that build tar command lines don't have to - * worry about C= with non-existent + * worry about -C with non-existent * directories; such requests will only fail * if the directory must be accessed. */ if (pending_dir && *arg == '/') { - /* The C=/foo C=/bar case; dump first one. */ + /* The -C /foo -C /bar case; dump first one. */ free(pending_dir); pending_dir = NULL; } if (pending_dir) { - /* The C=/foo C=bar case; concatenate */ + /* The -C /foo -C bar case; concatenate */ char *old_pending = pending_dir; int old_len = strlen(old_pending); @@ -409,8 +416,7 @@ write_archive(struct archive *a, struct bsdtar *bsdtar) } else { if (pending_dir && (*arg != '/' || (*arg == '@' && arg[1] != '/'))) { - /* Handle a deferred -C request, see - * comments above. */ + /* Handle a deferred -C */ if (chdir(pending_dir)) bsdtar_errc(bsdtar, 1, 0, "could not chdir to '%s'\n",