1cdf63a70SMartin Matuska /*- 2*bd66c1b4SMartin Matuska * SPDX-License-Identifier: BSD-2-Clause 3*bd66c1b4SMartin Matuska * 4cdf63a70SMartin Matuska * Copyright (c) 2003-2008 Tim Kientzle 5cdf63a70SMartin Matuska * All rights reserved. 6cdf63a70SMartin Matuska */ 7cdf63a70SMartin Matuska 8cdf63a70SMartin Matuska /* 9d91bfe0fSMartin Matuska * Command line parser for bsdcat. 10cdf63a70SMartin Matuska */ 11cdf63a70SMartin Matuska 12cdf63a70SMartin Matuska #include "bsdcat_platform.h" 13cdf63a70SMartin Matuska 14cdf63a70SMartin Matuska #ifdef HAVE_ERRNO_H 15cdf63a70SMartin Matuska #include <errno.h> 16cdf63a70SMartin Matuska #endif 17cdf63a70SMartin Matuska #ifdef HAVE_STDLIB_H 18cdf63a70SMartin Matuska #include <stdlib.h> 19cdf63a70SMartin Matuska #endif 20cdf63a70SMartin Matuska #ifdef HAVE_STRING_H 21cdf63a70SMartin Matuska #include <string.h> 22cdf63a70SMartin Matuska #endif 23cdf63a70SMartin Matuska 24cdf63a70SMartin Matuska #include "bsdcat.h" 25cdf63a70SMartin Matuska #include "err.h" 26cdf63a70SMartin Matuska 27cdf63a70SMartin Matuska /* 28d91bfe0fSMartin Matuska * Short options for bsdcat. Please keep this sorted. 29cdf63a70SMartin Matuska */ 30cdf63a70SMartin Matuska static const char *short_options = "h"; 31cdf63a70SMartin Matuska 32cdf63a70SMartin Matuska /* 33d91bfe0fSMartin Matuska * Long options for bsdcat. Please keep this list sorted. 34cdf63a70SMartin Matuska * 35cdf63a70SMartin Matuska * The symbolic names for options that lack a short equivalent are 36cdf63a70SMartin Matuska * defined in bsdcat.h. Also note that so far I've found no need 37cdf63a70SMartin Matuska * to support optional arguments to long options. That would be 38cdf63a70SMartin Matuska * a small change to the code below. 39cdf63a70SMartin Matuska */ 40cdf63a70SMartin Matuska 41cdf63a70SMartin Matuska static const struct bsdcat_option { 42cdf63a70SMartin Matuska const char *name; 43cdf63a70SMartin Matuska int required; /* 1 if this option requires an argument. */ 44cdf63a70SMartin Matuska int equivalent; /* Equivalent short option. */ 45d91bfe0fSMartin Matuska } bsdcat_longopts[] = { 46cdf63a70SMartin Matuska { "help", 0, 'h' }, 47cdf63a70SMartin Matuska { "version", 0, OPTION_VERSION }, 48cdf63a70SMartin Matuska { NULL, 0, 0 } 49cdf63a70SMartin Matuska }; 50cdf63a70SMartin Matuska 51cdf63a70SMartin Matuska /* 52cdf63a70SMartin Matuska * This getopt implementation has two key features that common 53cdf63a70SMartin Matuska * getopt_long() implementations lack. Apart from those, it's a 54cdf63a70SMartin Matuska * straightforward option parser, considerably simplified by not 55cdf63a70SMartin Matuska * needing to support the wealth of exotic getopt_long() features. It 56cdf63a70SMartin Matuska * has, of course, been shamelessly tailored for bsdcat. (If you're 57cdf63a70SMartin Matuska * looking for a generic getopt_long() implementation for your 58cdf63a70SMartin Matuska * project, I recommend Gregory Pietsch's public domain getopt_long() 59cdf63a70SMartin Matuska * implementation.) The two additional features are: 60cdf63a70SMartin Matuska * 61cdf63a70SMartin Matuska * Old-style tar arguments: The original tar implementation treated 62cdf63a70SMartin Matuska * the first argument word as a list of single-character option 63cdf63a70SMartin Matuska * letters. All arguments follow as separate words. For example, 64cdf63a70SMartin Matuska * tar xbf 32 /dev/tape 65cdf63a70SMartin Matuska * Here, the "xbf" is three option letters, "32" is the argument for 66cdf63a70SMartin Matuska * "b" and "/dev/tape" is the argument for "f". We support this usage 67cdf63a70SMartin Matuska * if the first command-line argument does not begin with '-'. We 68cdf63a70SMartin Matuska * also allow regular short and long options to follow, e.g., 69cdf63a70SMartin Matuska * tar xbf 32 /dev/tape -P --format=pax 70cdf63a70SMartin Matuska * 71cdf63a70SMartin Matuska * -W long options: There's an obscure GNU convention (only rarely 72cdf63a70SMartin Matuska * supported even there) that allows "-W option=argument" as an 73cdf63a70SMartin Matuska * alternative way to support long options. This was supported in 74d91bfe0fSMartin Matuska * early bsdtar as a way to access long options on platforms that did 75cdf63a70SMartin Matuska * not support getopt_long() and is preserved here for backwards 76cdf63a70SMartin Matuska * compatibility. (Of course, if I'd started with a custom 77cdf63a70SMartin Matuska * command-line parser from the beginning, I would have had normal 78cdf63a70SMartin Matuska * long option support on every platform so that hack wouldn't have 79cdf63a70SMartin Matuska * been necessary. Oh, well. Some mistakes you just have to live 80cdf63a70SMartin Matuska * with.) 81cdf63a70SMartin Matuska * 82cdf63a70SMartin Matuska * TODO: We should be able to use this to pull files and intermingled 83cdf63a70SMartin Matuska * options (such as -C) from the command line in write mode. That 84cdf63a70SMartin Matuska * will require a little rethinking of the argument handling in 85cdf63a70SMartin Matuska * bsdcat.c. 86cdf63a70SMartin Matuska * 87cdf63a70SMartin Matuska * TODO: If we want to support arbitrary command-line options from -T 88cdf63a70SMartin Matuska * input (as GNU tar does), we may need to extend this to handle option 89cdf63a70SMartin Matuska * words from sources other than argv/argc. I'm not really sure if I 90cdf63a70SMartin Matuska * like that feature of GNU tar, so it's certainly not a priority. 91cdf63a70SMartin Matuska */ 92cdf63a70SMartin Matuska 93cdf63a70SMartin Matuska int 94cdf63a70SMartin Matuska bsdcat_getopt(struct bsdcat *bsdcat) 95cdf63a70SMartin Matuska { 96cdf63a70SMartin Matuska enum { state_start = 0, state_old_tar, state_next_word, 97cdf63a70SMartin Matuska state_short, state_long }; 98cdf63a70SMartin Matuska 9913d826ffSMartin Matuska const struct bsdcat_option *popt, *match, *match2; 10013d826ffSMartin Matuska const char *p, *long_prefix; 101cdf63a70SMartin Matuska size_t optlength; 10213d826ffSMartin Matuska int opt; 10313d826ffSMartin Matuska int required; 104cdf63a70SMartin Matuska 10513d826ffSMartin Matuska again: 10613d826ffSMartin Matuska match = NULL; 10713d826ffSMartin Matuska match2 = NULL; 10813d826ffSMartin Matuska long_prefix = "--"; 10913d826ffSMartin Matuska opt = '?'; 11013d826ffSMartin Matuska required = 0; 111cdf63a70SMartin Matuska bsdcat->argument = NULL; 112cdf63a70SMartin Matuska 113cdf63a70SMartin Matuska /* First time through, initialize everything. */ 114cdf63a70SMartin Matuska if (bsdcat->getopt_state == state_start) { 115cdf63a70SMartin Matuska /* Skip program name. */ 116cdf63a70SMartin Matuska ++bsdcat->argv; 117cdf63a70SMartin Matuska --bsdcat->argc; 118cdf63a70SMartin Matuska if (*bsdcat->argv == NULL) 119cdf63a70SMartin Matuska return (-1); 120cdf63a70SMartin Matuska /* Decide between "new style" and "old style" arguments. */ 121cdf63a70SMartin Matuska bsdcat->getopt_state = state_next_word; 122cdf63a70SMartin Matuska } 123cdf63a70SMartin Matuska 124cdf63a70SMartin Matuska /* 125cdf63a70SMartin Matuska * We're ready to look at the next word in argv. 126cdf63a70SMartin Matuska */ 127cdf63a70SMartin Matuska if (bsdcat->getopt_state == state_next_word) { 128cdf63a70SMartin Matuska /* No more arguments, so no more options. */ 129cdf63a70SMartin Matuska if (bsdcat->argv[0] == NULL) 130cdf63a70SMartin Matuska return (-1); 131cdf63a70SMartin Matuska /* Doesn't start with '-', so no more options. */ 132cdf63a70SMartin Matuska if (bsdcat->argv[0][0] != '-') 133cdf63a70SMartin Matuska return (-1); 134cdf63a70SMartin Matuska /* "--" marks end of options; consume it and return. */ 135cdf63a70SMartin Matuska if (strcmp(bsdcat->argv[0], "--") == 0) { 136cdf63a70SMartin Matuska ++bsdcat->argv; 137cdf63a70SMartin Matuska --bsdcat->argc; 138cdf63a70SMartin Matuska return (-1); 139cdf63a70SMartin Matuska } 140cdf63a70SMartin Matuska /* Get next word for parsing. */ 141cdf63a70SMartin Matuska bsdcat->getopt_word = *bsdcat->argv++; 142cdf63a70SMartin Matuska --bsdcat->argc; 143cdf63a70SMartin Matuska if (bsdcat->getopt_word[1] == '-') { 144cdf63a70SMartin Matuska /* Set up long option parser. */ 145cdf63a70SMartin Matuska bsdcat->getopt_state = state_long; 146cdf63a70SMartin Matuska bsdcat->getopt_word += 2; /* Skip leading '--' */ 147cdf63a70SMartin Matuska } else { 148cdf63a70SMartin Matuska /* Set up short option parser. */ 149cdf63a70SMartin Matuska bsdcat->getopt_state = state_short; 150cdf63a70SMartin Matuska ++bsdcat->getopt_word; /* Skip leading '-' */ 151cdf63a70SMartin Matuska } 152cdf63a70SMartin Matuska } 153cdf63a70SMartin Matuska 154cdf63a70SMartin Matuska /* 155cdf63a70SMartin Matuska * We're parsing a group of POSIX-style single-character options. 156cdf63a70SMartin Matuska */ 157cdf63a70SMartin Matuska if (bsdcat->getopt_state == state_short) { 158cdf63a70SMartin Matuska /* Peel next option off of a group of short options. */ 159cdf63a70SMartin Matuska opt = *bsdcat->getopt_word++; 160cdf63a70SMartin Matuska if (opt == '\0') { 161cdf63a70SMartin Matuska /* End of this group; recurse to get next option. */ 162cdf63a70SMartin Matuska bsdcat->getopt_state = state_next_word; 16313d826ffSMartin Matuska goto again; 164cdf63a70SMartin Matuska } 165cdf63a70SMartin Matuska 166cdf63a70SMartin Matuska /* Does this option take an argument? */ 167cdf63a70SMartin Matuska p = strchr(short_options, opt); 168cdf63a70SMartin Matuska if (p == NULL) 169cdf63a70SMartin Matuska return ('?'); 170cdf63a70SMartin Matuska if (p[1] == ':') 171cdf63a70SMartin Matuska required = 1; 172cdf63a70SMartin Matuska 173cdf63a70SMartin Matuska /* If it takes an argument, parse that. */ 174cdf63a70SMartin Matuska if (required) { 175cdf63a70SMartin Matuska /* If arg is run-in, bsdcat->getopt_word already points to it. */ 176cdf63a70SMartin Matuska if (bsdcat->getopt_word[0] == '\0') { 177cdf63a70SMartin Matuska /* Otherwise, pick up the next word. */ 178cdf63a70SMartin Matuska bsdcat->getopt_word = *bsdcat->argv; 179cdf63a70SMartin Matuska if (bsdcat->getopt_word == NULL) { 180cdf63a70SMartin Matuska lafe_warnc(0, 181cdf63a70SMartin Matuska "Option -%c requires an argument", 182cdf63a70SMartin Matuska opt); 183cdf63a70SMartin Matuska return ('?'); 184cdf63a70SMartin Matuska } 185cdf63a70SMartin Matuska ++bsdcat->argv; 186cdf63a70SMartin Matuska --bsdcat->argc; 187cdf63a70SMartin Matuska } 188cdf63a70SMartin Matuska if (opt == 'W') { 189cdf63a70SMartin Matuska bsdcat->getopt_state = state_long; 190cdf63a70SMartin Matuska long_prefix = "-W "; /* For clearer errors. */ 191cdf63a70SMartin Matuska } else { 192cdf63a70SMartin Matuska bsdcat->getopt_state = state_next_word; 193cdf63a70SMartin Matuska bsdcat->argument = bsdcat->getopt_word; 194cdf63a70SMartin Matuska } 195cdf63a70SMartin Matuska } 196cdf63a70SMartin Matuska } 197cdf63a70SMartin Matuska 198cdf63a70SMartin Matuska /* We're reading a long option, including -W long=arg convention. */ 199cdf63a70SMartin Matuska if (bsdcat->getopt_state == state_long) { 200cdf63a70SMartin Matuska /* After this long option, we'll be starting a new word. */ 201cdf63a70SMartin Matuska bsdcat->getopt_state = state_next_word; 202cdf63a70SMartin Matuska 203cdf63a70SMartin Matuska /* Option name ends at '=' if there is one. */ 204cdf63a70SMartin Matuska p = strchr(bsdcat->getopt_word, '='); 205cdf63a70SMartin Matuska if (p != NULL) { 206cdf63a70SMartin Matuska optlength = (size_t)(p - bsdcat->getopt_word); 207cdf63a70SMartin Matuska bsdcat->argument = (char *)(uintptr_t)(p + 1); 208cdf63a70SMartin Matuska } else { 209cdf63a70SMartin Matuska optlength = strlen(bsdcat->getopt_word); 210cdf63a70SMartin Matuska } 211cdf63a70SMartin Matuska 212cdf63a70SMartin Matuska /* Search the table for an unambiguous match. */ 213d91bfe0fSMartin Matuska for (popt = bsdcat_longopts; popt->name != NULL; popt++) { 214cdf63a70SMartin Matuska /* Short-circuit if first chars don't match. */ 215cdf63a70SMartin Matuska if (popt->name[0] != bsdcat->getopt_word[0]) 216cdf63a70SMartin Matuska continue; 217cdf63a70SMartin Matuska /* If option is a prefix of name in table, record it.*/ 218cdf63a70SMartin Matuska if (strncmp(bsdcat->getopt_word, popt->name, optlength) == 0) { 219cdf63a70SMartin Matuska match2 = match; /* Record up to two matches. */ 220cdf63a70SMartin Matuska match = popt; 221cdf63a70SMartin Matuska /* If it's an exact match, we're done. */ 222cdf63a70SMartin Matuska if (strlen(popt->name) == optlength) { 223cdf63a70SMartin Matuska match2 = NULL; /* Forget the others. */ 224cdf63a70SMartin Matuska break; 225cdf63a70SMartin Matuska } 226cdf63a70SMartin Matuska } 227cdf63a70SMartin Matuska } 228cdf63a70SMartin Matuska 229cdf63a70SMartin Matuska /* Fail if there wasn't a unique match. */ 230cdf63a70SMartin Matuska if (match == NULL) { 231cdf63a70SMartin Matuska lafe_warnc(0, 232cdf63a70SMartin Matuska "Option %s%s is not supported", 233cdf63a70SMartin Matuska long_prefix, bsdcat->getopt_word); 234cdf63a70SMartin Matuska return ('?'); 235cdf63a70SMartin Matuska } 236cdf63a70SMartin Matuska if (match2 != NULL) { 237cdf63a70SMartin Matuska lafe_warnc(0, 238cdf63a70SMartin Matuska "Ambiguous option %s%s (matches --%s and --%s)", 239cdf63a70SMartin Matuska long_prefix, bsdcat->getopt_word, match->name, match2->name); 240cdf63a70SMartin Matuska return ('?'); 241cdf63a70SMartin Matuska } 242cdf63a70SMartin Matuska 243cdf63a70SMartin Matuska /* We've found a unique match; does it need an argument? */ 244cdf63a70SMartin Matuska if (match->required) { 245cdf63a70SMartin Matuska /* Argument required: get next word if necessary. */ 246cdf63a70SMartin Matuska if (bsdcat->argument == NULL) { 247cdf63a70SMartin Matuska bsdcat->argument = *bsdcat->argv; 248cdf63a70SMartin Matuska if (bsdcat->argument == NULL) { 249cdf63a70SMartin Matuska lafe_warnc(0, 250cdf63a70SMartin Matuska "Option %s%s requires an argument", 251cdf63a70SMartin Matuska long_prefix, match->name); 252cdf63a70SMartin Matuska return ('?'); 253cdf63a70SMartin Matuska } 254cdf63a70SMartin Matuska ++bsdcat->argv; 255cdf63a70SMartin Matuska --bsdcat->argc; 256cdf63a70SMartin Matuska } 257cdf63a70SMartin Matuska } else { 258cdf63a70SMartin Matuska /* Argument forbidden: fail if there is one. */ 259cdf63a70SMartin Matuska if (bsdcat->argument != NULL) { 260cdf63a70SMartin Matuska lafe_warnc(0, 261cdf63a70SMartin Matuska "Option %s%s does not allow an argument", 262cdf63a70SMartin Matuska long_prefix, match->name); 263cdf63a70SMartin Matuska return ('?'); 264cdf63a70SMartin Matuska } 265cdf63a70SMartin Matuska } 266cdf63a70SMartin Matuska return (match->equivalent); 267cdf63a70SMartin Matuska } 268cdf63a70SMartin Matuska 269cdf63a70SMartin Matuska return (opt); 270cdf63a70SMartin Matuska } 271