/usr.bin/tar/cmdline.c

https://bitbucket.org/freebsd/freebsd-head/ · C · 385 lines · 245 code · 23 blank · 117 comment · 70 complexity · dc33704bedb3df3680def6a80cf26942 MD5 · raw file

  1. /*-
  2. * Copyright (c) 2003-2008 Tim Kientzle
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions
  7. * are met:
  8. * 1. Redistributions of source code must retain the above copyright
  9. * notice, this list of conditions and the following disclaimer.
  10. * 2. Redistributions in binary form must reproduce the above copyright
  11. * notice, this list of conditions and the following disclaimer in the
  12. * documentation and/or other materials provided with the distribution.
  13. *
  14. * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
  15. * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  16. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  17. * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
  18. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  19. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  20. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  21. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  22. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  23. * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  24. */
  25. /*
  26. * Command line parser for tar.
  27. */
  28. #include "bsdtar_platform.h"
  29. __FBSDID("$FreeBSD$");
  30. #ifdef HAVE_ERRNO_H
  31. #include <errno.h>
  32. #endif
  33. #ifdef HAVE_STDLIB_H
  34. #include <stdlib.h>
  35. #endif
  36. #ifdef HAVE_STRING_H
  37. #include <string.h>
  38. #endif
  39. #include "bsdtar.h"
  40. #include "err.h"
  41. /*
  42. * Short options for tar. Please keep this sorted.
  43. */
  44. static const char *short_options
  45. = "Bb:C:cf:HhI:JjkLlmnOoPpqrSs:T:tUuvW:wX:xyZz";
  46. /*
  47. * Long options for tar. Please keep this list sorted.
  48. *
  49. * The symbolic names for options that lack a short equivalent are
  50. * defined in bsdtar.h. Also note that so far I've found no need
  51. * to support optional arguments to long options. That would be
  52. * a small change to the code below.
  53. */
  54. static struct option {
  55. const char *name;
  56. int required; /* 1 if this option requires an argument. */
  57. int equivalent; /* Equivalent short option. */
  58. } tar_longopts[] = {
  59. { "absolute-paths", 0, 'P' },
  60. { "append", 0, 'r' },
  61. { "block-size", 1, 'b' },
  62. { "bunzip2", 0, 'j' },
  63. { "bzip", 0, 'j' },
  64. { "bzip2", 0, 'j' },
  65. { "cd", 1, 'C' },
  66. { "check-links", 0, OPTION_CHECK_LINKS },
  67. { "chroot", 0, OPTION_CHROOT },
  68. { "compress", 0, 'Z' },
  69. { "confirmation", 0, 'w' },
  70. { "create", 0, 'c' },
  71. { "dereference", 0, 'L' },
  72. { "directory", 1, 'C' },
  73. { "exclude", 1, OPTION_EXCLUDE },
  74. { "exclude-from", 1, 'X' },
  75. { "extract", 0, 'x' },
  76. { "fast-read", 0, 'q' },
  77. { "file", 1, 'f' },
  78. { "files-from", 1, 'T' },
  79. { "format", 1, OPTION_FORMAT },
  80. { "gid", 1, OPTION_GID },
  81. { "gname", 1, OPTION_GNAME },
  82. { "gunzip", 0, 'z' },
  83. { "gzip", 0, 'z' },
  84. { "help", 0, OPTION_HELP },
  85. { "include", 1, OPTION_INCLUDE },
  86. { "interactive", 0, 'w' },
  87. { "insecure", 0, 'P' },
  88. { "keep-newer-files", 0, OPTION_KEEP_NEWER_FILES },
  89. { "keep-old-files", 0, 'k' },
  90. { "list", 0, 't' },
  91. { "lzma", 0, OPTION_LZMA },
  92. { "modification-time", 0, 'm' },
  93. { "newer", 1, OPTION_NEWER_CTIME },
  94. { "newer-ctime", 1, OPTION_NEWER_CTIME },
  95. { "newer-ctime-than", 1, OPTION_NEWER_CTIME_THAN },
  96. { "newer-mtime", 1, OPTION_NEWER_MTIME },
  97. { "newer-mtime-than", 1, OPTION_NEWER_MTIME_THAN },
  98. { "newer-than", 1, OPTION_NEWER_CTIME_THAN },
  99. { "nodump", 0, OPTION_NODUMP },
  100. { "norecurse", 0, 'n' },
  101. { "no-recursion", 0, 'n' },
  102. { "no-same-owner", 0, OPTION_NO_SAME_OWNER },
  103. { "no-same-permissions", 0, OPTION_NO_SAME_PERMISSIONS },
  104. { "null", 0, OPTION_NULL },
  105. { "numeric-owner", 0, OPTION_NUMERIC_OWNER },
  106. { "one-file-system", 0, OPTION_ONE_FILE_SYSTEM },
  107. { "options", 1, OPTION_OPTIONS },
  108. { "posix", 0, OPTION_POSIX },
  109. { "preserve-permissions", 0, 'p' },
  110. { "read-full-blocks", 0, 'B' },
  111. { "same-owner", 0, OPTION_SAME_OWNER },
  112. { "same-permissions", 0, 'p' },
  113. { "strip-components", 1, OPTION_STRIP_COMPONENTS },
  114. { "to-stdout", 0, 'O' },
  115. { "totals", 0, OPTION_TOTALS },
  116. { "uid", 1, OPTION_UID },
  117. { "uname", 1, OPTION_UNAME },
  118. { "uncompress", 0, 'Z' },
  119. { "unlink", 0, 'U' },
  120. { "unlink-first", 0, 'U' },
  121. { "update", 0, 'u' },
  122. { "use-compress-program", 1, OPTION_USE_COMPRESS_PROGRAM },
  123. { "verbose", 0, 'v' },
  124. { "version", 0, OPTION_VERSION },
  125. { "xz", 0, 'J' },
  126. { NULL, 0, 0 }
  127. };
  128. /*
  129. * This getopt implementation has two key features that common
  130. * getopt_long() implementations lack. Apart from those, it's a
  131. * straightforward option parser, considerably simplified by not
  132. * needing to support the wealth of exotic getopt_long() features. It
  133. * has, of course, been shamelessly tailored for bsdtar. (If you're
  134. * looking for a generic getopt_long() implementation for your
  135. * project, I recommend Gregory Pietsch's public domain getopt_long()
  136. * implementation.) The two additional features are:
  137. *
  138. * Old-style tar arguments: The original tar implementation treated
  139. * the first argument word as a list of single-character option
  140. * letters. All arguments follow as separate words. For example,
  141. * tar xbf 32 /dev/tape
  142. * Here, the "xbf" is three option letters, "32" is the argument for
  143. * "b" and "/dev/tape" is the argument for "f". We support this usage
  144. * if the first command-line argument does not begin with '-'. We
  145. * also allow regular short and long options to follow, e.g.,
  146. * tar xbf 32 /dev/tape -P --format=pax
  147. *
  148. * -W long options: There's an obscure GNU convention (only rarely
  149. * supported even there) that allows "-W option=argument" as an
  150. * alternative way to support long options. This was supported in
  151. * early bsdtar as a way to access long options on platforms that did
  152. * not support getopt_long() and is preserved here for backwards
  153. * compatibility. (Of course, if I'd started with a custom
  154. * command-line parser from the beginning, I would have had normal
  155. * long option support on every platform so that hack wouldn't have
  156. * been necessary. Oh, well. Some mistakes you just have to live
  157. * with.)
  158. *
  159. * TODO: We should be able to use this to pull files and intermingled
  160. * options (such as -C) from the command line in write mode. That
  161. * will require a little rethinking of the argument handling in
  162. * bsdtar.c.
  163. *
  164. * TODO: If we want to support arbitrary command-line options from -T
  165. * input (as GNU tar does), we may need to extend this to handle option
  166. * words from sources other than argv/arc. I'm not really sure if I
  167. * like that feature of GNU tar, so it's certainly not a priority.
  168. */
  169. int
  170. bsdtar_getopt(struct bsdtar *bsdtar)
  171. {
  172. enum { state_start = 0, state_old_tar, state_next_word,
  173. state_short, state_long };
  174. static int state = state_start;
  175. static char *opt_word;
  176. const struct option *popt, *match = NULL, *match2 = NULL;
  177. const char *p, *long_prefix = "--";
  178. size_t optlength;
  179. int opt = '?';
  180. int required = 0;
  181. bsdtar->optarg = NULL;
  182. /* First time through, initialize everything. */
  183. if (state == state_start) {
  184. /* Skip program name. */
  185. ++bsdtar->argv;
  186. --bsdtar->argc;
  187. if (*bsdtar->argv == NULL)
  188. return (-1);
  189. /* Decide between "new style" and "old style" arguments. */
  190. if (bsdtar->argv[0][0] == '-') {
  191. state = state_next_word;
  192. } else {
  193. state = state_old_tar;
  194. opt_word = *bsdtar->argv++;
  195. --bsdtar->argc;
  196. }
  197. }
  198. /*
  199. * We're parsing old-style tar arguments
  200. */
  201. if (state == state_old_tar) {
  202. /* Get the next option character. */
  203. opt = *opt_word++;
  204. if (opt == '\0') {
  205. /* New-style args can follow old-style. */
  206. state = state_next_word;
  207. } else {
  208. /* See if it takes an argument. */
  209. p = strchr(short_options, opt);
  210. if (p == NULL)
  211. return ('?');
  212. if (p[1] == ':') {
  213. bsdtar->optarg = *bsdtar->argv;
  214. if (bsdtar->optarg == NULL) {
  215. lafe_warnc(0,
  216. "Option %c requires an argument",
  217. opt);
  218. return ('?');
  219. }
  220. ++bsdtar->argv;
  221. --bsdtar->argc;
  222. }
  223. }
  224. }
  225. /*
  226. * We're ready to look at the next word in argv.
  227. */
  228. if (state == state_next_word) {
  229. /* No more arguments, so no more options. */
  230. if (bsdtar->argv[0] == NULL)
  231. return (-1);
  232. /* Doesn't start with '-', so no more options. */
  233. if (bsdtar->argv[0][0] != '-')
  234. return (-1);
  235. /* "--" marks end of options; consume it and return. */
  236. if (strcmp(bsdtar->argv[0], "--") == 0) {
  237. ++bsdtar->argv;
  238. --bsdtar->argc;
  239. return (-1);
  240. }
  241. /* Get next word for parsing. */
  242. opt_word = *bsdtar->argv++;
  243. --bsdtar->argc;
  244. if (opt_word[1] == '-') {
  245. /* Set up long option parser. */
  246. state = state_long;
  247. opt_word += 2; /* Skip leading '--' */
  248. } else {
  249. /* Set up short option parser. */
  250. state = state_short;
  251. ++opt_word; /* Skip leading '-' */
  252. }
  253. }
  254. /*
  255. * We're parsing a group of POSIX-style single-character options.
  256. */
  257. if (state == state_short) {
  258. /* Peel next option off of a group of short options. */
  259. opt = *opt_word++;
  260. if (opt == '\0') {
  261. /* End of this group; recurse to get next option. */
  262. state = state_next_word;
  263. return bsdtar_getopt(bsdtar);
  264. }
  265. /* Does this option take an argument? */
  266. p = strchr(short_options, opt);
  267. if (p == NULL)
  268. return ('?');
  269. if (p[1] == ':')
  270. required = 1;
  271. /* If it takes an argument, parse that. */
  272. if (required) {
  273. /* If arg is run-in, opt_word already points to it. */
  274. if (opt_word[0] == '\0') {
  275. /* Otherwise, pick up the next word. */
  276. opt_word = *bsdtar->argv;
  277. if (opt_word == NULL) {
  278. lafe_warnc(0,
  279. "Option -%c requires an argument",
  280. opt);
  281. return ('?');
  282. }
  283. ++bsdtar->argv;
  284. --bsdtar->argc;
  285. }
  286. if (opt == 'W') {
  287. state = state_long;
  288. long_prefix = "-W "; /* For clearer errors. */
  289. } else {
  290. state = state_next_word;
  291. bsdtar->optarg = opt_word;
  292. }
  293. }
  294. }
  295. /* We're reading a long option, including -W long=arg convention. */
  296. if (state == state_long) {
  297. /* After this long option, we'll be starting a new word. */
  298. state = state_next_word;
  299. /* Option name ends at '=' if there is one. */
  300. p = strchr(opt_word, '=');
  301. if (p != NULL) {
  302. optlength = (size_t)(p - opt_word);
  303. bsdtar->optarg = (char *)(uintptr_t)(p + 1);
  304. } else {
  305. optlength = strlen(opt_word);
  306. }
  307. /* Search the table for an unambiguous match. */
  308. for (popt = tar_longopts; popt->name != NULL; popt++) {
  309. /* Short-circuit if first chars don't match. */
  310. if (popt->name[0] != opt_word[0])
  311. continue;
  312. /* If option is a prefix of name in table, record it.*/
  313. if (strncmp(opt_word, popt->name, optlength) == 0) {
  314. match2 = match; /* Record up to two matches. */
  315. match = popt;
  316. /* If it's an exact match, we're done. */
  317. if (strlen(popt->name) == optlength) {
  318. match2 = NULL; /* Forget the others. */
  319. break;
  320. }
  321. }
  322. }
  323. /* Fail if there wasn't a unique match. */
  324. if (match == NULL) {
  325. lafe_warnc(0,
  326. "Option %s%s is not supported",
  327. long_prefix, opt_word);
  328. return ('?');
  329. }
  330. if (match2 != NULL) {
  331. lafe_warnc(0,
  332. "Ambiguous option %s%s (matches --%s and --%s)",
  333. long_prefix, opt_word, match->name, match2->name);
  334. return ('?');
  335. }
  336. /* We've found a unique match; does it need an argument? */
  337. if (match->required) {
  338. /* Argument required: get next word if necessary. */
  339. if (bsdtar->optarg == NULL) {
  340. bsdtar->optarg = *bsdtar->argv;
  341. if (bsdtar->optarg == NULL) {
  342. lafe_warnc(0,
  343. "Option %s%s requires an argument",
  344. long_prefix, match->name);
  345. return ('?');
  346. }
  347. ++bsdtar->argv;
  348. --bsdtar->argc;
  349. }
  350. } else {
  351. /* Argument forbidden: fail if there is one. */
  352. if (bsdtar->optarg != NULL) {
  353. lafe_warnc(0,
  354. "Option %s%s does not allow an argument",
  355. long_prefix, match->name);
  356. return ('?');
  357. }
  358. }
  359. return (match->equivalent);
  360. }
  361. return (opt);
  362. }