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