cmdline.c revision 315433
1251881Speter/*-
2251881Speter * Copyright (c) 2003-2008 Tim Kientzle
3251881Speter * All rights reserved.
4251881Speter *
5251881Speter * Redistribution and use in source and binary forms, with or without
6251881Speter * modification, are permitted provided that the following conditions
7251881Speter * are met:
8251881Speter * 1. Redistributions of source code must retain the above copyright
9251881Speter *    notice, this list of conditions and the following disclaimer.
10251881Speter * 2. Redistributions in binary form must reproduce the above copyright
11251881Speter *    notice, this list of conditions and the following disclaimer in the
12251881Speter *    documentation and/or other materials provided with the distribution.
13251881Speter *
14251881Speter * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
15251881Speter * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16251881Speter * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17251881Speter * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
18251881Speter * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19251881Speter * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20251881Speter * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21251881Speter * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22251881Speter * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23299742Sdim * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24251881Speter */
25251881Speter
26251881Speter/*
27299742Sdim * Command line parser for tar.
28299742Sdim */
29299742Sdim
30251881Speter#include "bsdtar_platform.h"
31251881Speter__FBSDID("$FreeBSD: stable/10/contrib/libarchive/tar/cmdline.c 315433 2017-03-16 23:08:18Z mm $");
32299742Sdim
33299742Sdim#ifdef HAVE_ERRNO_H
34251881Speter#include <errno.h>
35251881Speter#endif
36251881Speter#ifdef HAVE_STDLIB_H
37299742Sdim#include <stdlib.h>
38251881Speter#endif
39299742Sdim#ifdef HAVE_STRING_H
40251881Speter#include <string.h>
41299742Sdim#endif
42299742Sdim
43299742Sdim#include "bsdtar.h"
44299742Sdim#include "err.h"
45251881Speter
46299742Sdim/*
47299742Sdim * Short options for tar.  Please keep this sorted.
48251881Speter */
49251881Speterstatic const char *short_options
50251881Speter	= "aBb:C:cf:HhI:JjkLlmnOoPpqrSs:T:tUuvW:wX:xyZz";
51251881Speter
52251881Speter/*
53251881Speter * Long options for tar.  Please keep this list sorted.
54251881Speter *
55251881Speter * The symbolic names for options that lack a short equivalent are
56251881Speter * defined in bsdtar.h.  Also note that so far I've found no need
57251881Speter * to support optional arguments to long options.  That would be
58251881Speter * a small change to the code below.
59251881Speter */
60251881Speter
61251881Speterstatic const struct bsdtar_option {
62251881Speter	const char *name;
63251881Speter	int required;      /* 1 if this option requires an argument. */
64251881Speter	int equivalent;    /* Equivalent short option. */
65251881Speter} tar_longopts[] = {
66251881Speter	{ "absolute-paths",       0, 'P' },
67251881Speter	{ "append",               0, 'r' },
68251881Speter	{ "acls",                 0, OPTION_ACLS },
69251881Speter	{ "auto-compress",        0, 'a' },
70251881Speter	{ "b64encode",            0, OPTION_B64ENCODE },
71251881Speter	{ "block-size",           1, 'b' },
72251881Speter	{ "blocking-factor",	  1, 'b' },
73251881Speter	{ "bunzip2",              0, 'j' },
74251881Speter	{ "bzip",                 0, 'j' },
75251881Speter	{ "bzip2",                0, 'j' },
76251881Speter	{ "cd",                   1, 'C' },
77251881Speter	{ "check-links",          0, OPTION_CHECK_LINKS },
78251881Speter	{ "chroot",               0, OPTION_CHROOT },
79251881Speter	{ "clear-nochange-fflags", 0, OPTION_CLEAR_NOCHANGE_FFLAGS },
80251881Speter	{ "compress",             0, 'Z' },
81251881Speter	{ "confirmation",         0, 'w' },
82251881Speter	{ "create",               0, 'c' },
83251881Speter	{ "dereference",	  0, 'L' },
84251881Speter	{ "directory",            1, 'C' },
85251881Speter	{ "disable-copyfile",	  0, OPTION_NO_MAC_METADATA },
86251881Speter	{ "exclude",              1, OPTION_EXCLUDE },
87251881Speter	{ "exclude-from",         1, 'X' },
88251881Speter	{ "extract",              0, 'x' },
89251881Speter	{ "fast-read",            0, 'q' },
90299742Sdim	{ "fflags",               0, OPTION_FFLAGS },
91251881Speter	{ "file",                 1, 'f' },
92251881Speter	{ "files-from",           1, 'T' },
93251881Speter	{ "format",               1, OPTION_FORMAT },
94251881Speter	{ "gid",		  1, OPTION_GID },
95251881Speter	{ "gname",		  1, OPTION_GNAME },
96251881Speter	{ "grzip",                0, OPTION_GRZIP },
97251881Speter	{ "gunzip",               0, 'z' },
98251881Speter	{ "gzip",                 0, 'z' },
99251881Speter	{ "help",                 0, OPTION_HELP },
100251881Speter	{ "hfsCompression",       0, OPTION_HFS_COMPRESSION },
101251881Speter	{ "ignore-zeros",         0, OPTION_IGNORE_ZEROS },
102251881Speter	{ "include",              1, OPTION_INCLUDE },
103251881Speter	{ "insecure",             0, 'P' },
104251881Speter	{ "interactive",          0, 'w' },
105251881Speter	{ "keep-newer-files",     0, OPTION_KEEP_NEWER_FILES },
106251881Speter	{ "keep-old-files",       0, 'k' },
107251881Speter	{ "list",                 0, 't' },
108251881Speter	{ "lrzip",                0, OPTION_LRZIP },
109251881Speter	{ "lz4",                  0, OPTION_LZ4 },
110251881Speter	{ "lzip",                 0, OPTION_LZIP },
111251881Speter	{ "lzma",                 0, OPTION_LZMA },
112251881Speter	{ "lzop",                 0, OPTION_LZOP },
113299742Sdim	{ "mac-metadata",         0, OPTION_MAC_METADATA },
114299742Sdim	{ "modification-time",    0, 'm' },
115299742Sdim	{ "newer",		  1, OPTION_NEWER_CTIME },
116299742Sdim	{ "newer-ctime",	  1, OPTION_NEWER_CTIME },
117299742Sdim	{ "newer-ctime-than",	  1, OPTION_NEWER_CTIME_THAN },
118251881Speter	{ "newer-mtime",	  1, OPTION_NEWER_MTIME },
119299742Sdim	{ "newer-mtime-than",	  1, OPTION_NEWER_MTIME_THAN },
120251881Speter	{ "newer-than",		  1, OPTION_NEWER_CTIME_THAN },
121251881Speter	{ "no-acls",              0, OPTION_NO_ACLS },
122299742Sdim	{ "no-fflags",            0, OPTION_NO_FFLAGS },
123299742Sdim	{ "no-mac-metadata",      0, OPTION_NO_MAC_METADATA },
124299742Sdim	{ "no-recursion",         0, 'n' },
125299742Sdim	{ "no-same-owner",	  0, OPTION_NO_SAME_OWNER },
126299742Sdim	{ "no-same-permissions",  0, OPTION_NO_SAME_PERMISSIONS },
127251881Speter	{ "no-xattr",             0, OPTION_NO_XATTRS },
128299742Sdim	{ "no-xattrs",            0, OPTION_NO_XATTRS },
129299742Sdim	{ "nodump",               0, OPTION_NODUMP },
130299742Sdim	{ "nopreserveHFSCompression",0, OPTION_NOPRESERVE_HFS_COMPRESSION },
131251881Speter	{ "norecurse",            0, 'n' },
132251881Speter	{ "null",		  0, OPTION_NULL },
133299742Sdim	{ "numeric-owner",	  0, OPTION_NUMERIC_OWNER },
134299742Sdim	{ "older",		  1, OPTION_OLDER_CTIME },
135251881Speter	{ "older-ctime",	  1, OPTION_OLDER_CTIME },
136299742Sdim	{ "older-ctime-than",	  1, OPTION_OLDER_CTIME_THAN },
137299742Sdim	{ "older-mtime",	  1, OPTION_OLDER_MTIME },
138299742Sdim	{ "older-mtime-than",	  1, OPTION_OLDER_MTIME_THAN },
139299742Sdim	{ "older-than",		  1, OPTION_OLDER_CTIME_THAN },
140299742Sdim	{ "one-file-system",	  0, OPTION_ONE_FILE_SYSTEM },
141299742Sdim	{ "options",              1, OPTION_OPTIONS },
142299742Sdim	{ "passphrase",		  1, OPTION_PASSPHRASE },
143299742Sdim	{ "posix",		  0, OPTION_POSIX },
144299742Sdim	{ "preserve-permissions", 0, 'p' },
145299742Sdim	{ "read-full-blocks",	  0, 'B' },
146299742Sdim	{ "same-owner",	          0, OPTION_SAME_OWNER },
147299742Sdim	{ "same-permissions",     0, 'p' },
148251881Speter	{ "strip-components",	  1, OPTION_STRIP_COMPONENTS },
149299742Sdim	{ "to-stdout",            0, 'O' },
150299742Sdim	{ "totals",		  0, OPTION_TOTALS },
151251881Speter	{ "uid",		  1, OPTION_UID },
152299742Sdim	{ "uname",		  1, OPTION_UNAME },
153299742Sdim	{ "uncompress",           0, 'Z' },
154299742Sdim	{ "unlink",		  0, 'U' },
155251881Speter	{ "unlink-first",	  0, 'U' },
156299742Sdim	{ "update",               0, 'u' },
157299742Sdim	{ "use-compress-program", 1, OPTION_USE_COMPRESS_PROGRAM },
158251881Speter	{ "uuencode",             0, OPTION_UUENCODE },
159299742Sdim	{ "verbose",              0, 'v' },
160299742Sdim	{ "version",              0, OPTION_VERSION },
161251881Speter	{ "xattrs",               0, OPTION_XATTRS },
162299742Sdim	{ "xz",                   0, 'J' },
163299742Sdim	{ NULL, 0, 0 }
164299742Sdim};
165299742Sdim
166251881Speter/*
167299742Sdim * This getopt implementation has two key features that common
168299742Sdim * getopt_long() implementations lack.  Apart from those, it's a
169299742Sdim * straightforward option parser, considerably simplified by not
170251881Speter * needing to support the wealth of exotic getopt_long() features.  It
171299742Sdim * has, of course, been shamelessly tailored for bsdtar.  (If you're
172299742Sdim * looking for a generic getopt_long() implementation for your
173299742Sdim * project, I recommend Gregory Pietsch's public domain getopt_long()
174299742Sdim * implementation.)  The two additional features are:
175299742Sdim *
176251881Speter * Old-style tar arguments: The original tar implementation treated
177299742Sdim * the first argument word as a list of single-character option
178299742Sdim * letters.  All arguments follow as separate words.  For example,
179251881Speter *    tar xbf 32 /dev/tape
180299742Sdim * Here, the "xbf" is three option letters, "32" is the argument for
181299742Sdim * "b" and "/dev/tape" is the argument for "f".  We support this usage
182299742Sdim * if the first command-line argument does not begin with '-'.  We
183299742Sdim * also allow regular short and long options to follow, e.g.,
184251881Speter *    tar xbf 32 /dev/tape -P --format=pax
185299742Sdim *
186299742Sdim * -W long options: There's an obscure GNU convention (only rarely
187299742Sdim * supported even there) that allows "-W option=argument" as an
188299742Sdim * alternative way to support long options.  This was supported in
189299742Sdim * early bsdtar as a way to access long options on platforms that did
190299742Sdim * not support getopt_long() and is preserved here for backwards
191251881Speter * compatibility.  (Of course, if I'd started with a custom
192299742Sdim * command-line parser from the beginning, I would have had normal
193299742Sdim * long option support on every platform so that hack wouldn't have
194251881Speter * been necessary.  Oh, well.  Some mistakes you just have to live
195299742Sdim * with.)
196251881Speter *
197299742Sdim * TODO: We should be able to use this to pull files and intermingled
198299742Sdim * options (such as -C) from the command line in write mode.  That
199251881Speter * will require a little rethinking of the argument handling in
200299742Sdim * bsdtar.c.
201299742Sdim *
202299742Sdim * TODO: If we want to support arbitrary command-line options from -T
203299742Sdim * input (as GNU tar does), we may need to extend this to handle option
204299742Sdim * words from sources other than argv/argc.  I'm not really sure if I
205299742Sdim * like that feature of GNU tar, so it's certainly not a priority.
206299742Sdim */
207299742Sdim
208299742Sdimint
209251881Speterbsdtar_getopt(struct bsdtar *bsdtar)
210299742Sdim{
211299742Sdim	enum { state_start = 0, state_old_tar, state_next_word,
212299742Sdim	       state_short, state_long };
213299742Sdim
214299742Sdim	const struct bsdtar_option *popt, *match = NULL, *match2 = NULL;
215299742Sdim	const char *p, *long_prefix = "--";
216299742Sdim	size_t optlength;
217299742Sdim	int opt = '?';
218299742Sdim	int required = 0;
219251881Speter
220299742Sdim	bsdtar->argument = NULL;
221299742Sdim
222251881Speter	/* First time through, initialize everything. */
223251881Speter	if (bsdtar->getopt_state == state_start) {
224299742Sdim		/* Skip program name. */
225299742Sdim		++bsdtar->argv;
226251881Speter		--bsdtar->argc;
227299742Sdim		if (*bsdtar->argv == NULL)
228251881Speter			return (-1);
229251881Speter		/* Decide between "new style" and "old style" arguments. */
230299742Sdim		if (bsdtar->argv[0][0] == '-') {
231251881Speter			bsdtar->getopt_state = state_next_word;
232299742Sdim		} else {
233299742Sdim			bsdtar->getopt_state = state_old_tar;
234299742Sdim			bsdtar->getopt_word = *bsdtar->argv++;
235299742Sdim			--bsdtar->argc;
236299742Sdim		}
237251881Speter	}
238299742Sdim
239299742Sdim	/*
240251881Speter	 * We're parsing old-style tar arguments
241299742Sdim	 */
242251881Speter	if (bsdtar->getopt_state == state_old_tar) {
243251881Speter		/* Get the next option character. */
244299742Sdim		opt = *bsdtar->getopt_word++;
245299742Sdim		if (opt == '\0') {
246251881Speter			/* New-style args can follow old-style. */
247299742Sdim			bsdtar->getopt_state = state_next_word;
248299742Sdim		} else {
249299742Sdim			/* See if it takes an argument. */
250299742Sdim			p = strchr(short_options, opt);
251251881Speter			if (p == NULL)
252299742Sdim				return ('?');
253299742Sdim			if (p[1] == ':') {
254299742Sdim				bsdtar->argument = *bsdtar->argv;
255299742Sdim				if (bsdtar->argument == NULL) {
256299742Sdim					lafe_warnc(0,
257299742Sdim					    "Option %c requires an argument",
258251881Speter					    opt);
259299742Sdim					return ('?');
260251881Speter				}
261251881Speter				++bsdtar->argv;
262299742Sdim				--bsdtar->argc;
263299742Sdim			}
264299742Sdim		}
265299742Sdim	}
266299742Sdim
267299742Sdim	/*
268251881Speter	 * We're ready to look at the next word in argv.
269251881Speter	 */
270299742Sdim	if (bsdtar->getopt_state == state_next_word) {
271299742Sdim		/* No more arguments, so no more options. */
272299742Sdim		if (bsdtar->argv[0] == NULL)
273299742Sdim			return (-1);
274299742Sdim		/* Doesn't start with '-', so no more options. */
275299742Sdim		if (bsdtar->argv[0][0] != '-')
276251881Speter			return (-1);
277299742Sdim		/* "--" marks end of options; consume it and return. */
278299742Sdim		if (strcmp(bsdtar->argv[0], "--") == 0) {
279299742Sdim			++bsdtar->argv;
280299742Sdim			--bsdtar->argc;
281299742Sdim			return (-1);
282299742Sdim		}
283251881Speter		/* Get next word for parsing. */
284251881Speter		bsdtar->getopt_word = *bsdtar->argv++;
285251881Speter		--bsdtar->argc;
286299742Sdim		if (bsdtar->getopt_word[1] == '-') {
287299742Sdim			/* Set up long option parser. */
288299742Sdim			bsdtar->getopt_state = state_long;
289299742Sdim			bsdtar->getopt_word += 2; /* Skip leading '--' */
290299742Sdim		} else {
291299742Sdim			/* Set up short option parser. */
292299742Sdim			bsdtar->getopt_state = state_short;
293299742Sdim			++bsdtar->getopt_word;  /* Skip leading '-' */
294251881Speter		}
295299742Sdim	}
296251881Speter
297251881Speter	/*
298299742Sdim	 * We're parsing a group of POSIX-style single-character options.
299299742Sdim	 */
300299742Sdim	if (bsdtar->getopt_state == state_short) {
301299742Sdim		/* Peel next option off of a group of short options. */
302251881Speter		opt = *bsdtar->getopt_word++;
303299742Sdim		if (opt == '\0') {
304299742Sdim			/* End of this group; recurse to get next option. */
305299742Sdim			bsdtar->getopt_state = state_next_word;
306299742Sdim			return bsdtar_getopt(bsdtar);
307251881Speter		}
308299742Sdim
309299742Sdim		/* Does this option take an argument? */
310251881Speter		p = strchr(short_options, opt);
311299742Sdim		if (p == NULL)
312299742Sdim			return ('?');
313299742Sdim		if (p[1] == ':')
314251881Speter			required = 1;
315299742Sdim
316299742Sdim		/* If it takes an argument, parse that. */
317299742Sdim		if (required) {
318251881Speter			/* If arg is run-in, bsdtar->getopt_word already points to it. */
319299742Sdim			if (bsdtar->getopt_word[0] == '\0') {
320251881Speter				/* Otherwise, pick up the next word. */
321251881Speter				bsdtar->getopt_word = *bsdtar->argv;
322299742Sdim				if (bsdtar->getopt_word == NULL) {
323299742Sdim					lafe_warnc(0,
324299742Sdim					    "Option -%c requires an argument",
325299742Sdim					    opt);
326299742Sdim					return ('?');
327299742Sdim				}
328299742Sdim				++bsdtar->argv;
329299742Sdim				--bsdtar->argc;
330251881Speter			}
331299742Sdim			if (opt == 'W') {
332299742Sdim				bsdtar->getopt_state = state_long;
333299742Sdim				long_prefix = "-W "; /* For clearer errors. */
334251881Speter			} else {
335299742Sdim				bsdtar->getopt_state = state_next_word;
336299742Sdim				bsdtar->argument = bsdtar->getopt_word;
337251881Speter			}
338299742Sdim		}
339299742Sdim	}
340299742Sdim
341299742Sdim	/* We're reading a long option, including -W long=arg convention. */
342251881Speter	if (bsdtar->getopt_state == state_long) {
343299742Sdim		/* After this long option, we'll be starting a new word. */
344299742Sdim		bsdtar->getopt_state = state_next_word;
345251881Speter
346299742Sdim		/* Option name ends at '=' if there is one. */
347299742Sdim		p = strchr(bsdtar->getopt_word, '=');
348299742Sdim		if (p != NULL) {
349299742Sdim			optlength = (size_t)(p - bsdtar->getopt_word);
350251881Speter			bsdtar->argument = (char *)(uintptr_t)(p + 1);
351299742Sdim		} else {
352299742Sdim			optlength = strlen(bsdtar->getopt_word);
353299742Sdim		}
354251881Speter
355299742Sdim		/* Search the table for an unambiguous match. */
356251881Speter		for (popt = tar_longopts; popt->name != NULL; popt++) {
357251881Speter			/* Short-circuit if first chars don't match. */
358251881Speter			if (popt->name[0] != bsdtar->getopt_word[0])
359251881Speter				continue;
360251881Speter			/* If option is a prefix of name in table, record it.*/
361251881Speter			if (strncmp(bsdtar->getopt_word, popt->name, optlength) == 0) {
362251881Speter				match2 = match; /* Record up to two matches. */
363251881Speter				match = popt;
364251881Speter				/* If it's an exact match, we're done. */
365299742Sdim				if (strlen(popt->name) == optlength) {
366299742Sdim					match2 = NULL; /* Forget the others. */
367299742Sdim					break;
368251881Speter				}
369251881Speter			}
370299742Sdim		}
371299742Sdim
372299742Sdim		/* Fail if there wasn't a unique match. */
373299742Sdim		if (match == NULL) {
374299742Sdim			lafe_warnc(0,
375299742Sdim			    "Option %s%s is not supported",
376251881Speter			    long_prefix, bsdtar->getopt_word);
377299742Sdim			return ('?');
378299742Sdim		}
379299742Sdim		if (match2 != NULL) {
380251881Speter			lafe_warnc(0,
381251881Speter			    "Ambiguous option %s%s (matches --%s and --%s)",
382299742Sdim			    long_prefix, bsdtar->getopt_word, match->name, match2->name);
383299742Sdim			return ('?');
384299742Sdim		}
385299742Sdim
386299742Sdim		/* We've found a unique match; does it need an argument? */
387299742Sdim		if (match->required) {
388251881Speter			/* Argument required: get next word if necessary. */
389299742Sdim			if (bsdtar->argument == NULL) {
390299742Sdim				bsdtar->argument = *bsdtar->argv;
391299742Sdim				if (bsdtar->argument == NULL) {
392251881Speter					lafe_warnc(0,
393251881Speter					    "Option %s%s requires an argument",
394299742Sdim					    long_prefix, match->name);
395299742Sdim					return ('?');
396299742Sdim				}
397299742Sdim				++bsdtar->argv;
398299742Sdim				--bsdtar->argc;
399299742Sdim			}
400251881Speter		} else {
401299742Sdim			/* Argument forbidden: fail if there is one. */
402251881Speter			if (bsdtar->argument != NULL) {
403299742Sdim				lafe_warnc(0,
404299742Sdim				    "Option %s%s does not allow an argument",
405299742Sdim				    long_prefix, match->name);
406299742Sdim				return ('?');
407299742Sdim			}
408251881Speter		}
409299742Sdim		return (match->equivalent);
410299742Sdim	}
411251881Speter
412299742Sdim	return (opt);
413299742Sdim}
414251881Speter