1 /* $OpenBSD: getopt_long.c,v 1.26 2013/06/08 22:47:56 millert Exp $ */ 2 /* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */ 3 4 /* 5 * Copyright (c) 2002 Todd C. Miller <Todd.Miller (at) courtesan.com> 6 * 7 * Permission to use, copy, modify, and distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 * 19 * Sponsored in part by the Defense Advanced Research Projects 20 * Agency (DARPA) and Air Force Research Laboratory, Air Force 21 * Materiel Command, USAF, under agreement number F39502-99-1-0512. 22 */ 23 /*- 24 * Copyright (c) 2000 The NetBSD Foundation, Inc. 25 * All rights reserved. 26 * 27 * This code is derived from software contributed to The NetBSD Foundation 28 * by Dieter Baron and Thomas Klausner. 29 * 30 * Redistribution and use in source and binary forms, with or without 31 * modification, are permitted provided that the following conditions 32 * are met: 33 * 1. Redistributions of source code must retain the above copyright 34 * notice, this list of conditions and the following disclaimer. 35 * 2. Redistributions in binary form must reproduce the above copyright 36 * notice, this list of conditions and the following disclaimer in the 37 * documentation and/or other materials provided with the distribution. 38 * 39 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 40 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 41 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 42 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 43 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 44 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 45 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 46 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 47 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 48 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 49 * POSSIBILITY OF SUCH DAMAGE. 50 */ 51 52 #include <stdbool.h> 53 #include <stdio.h> 54 #include <stdlib.h> 55 #include <string.h> 56 #include <sys/cdefs.h> 57 58 #include <log/getopt.h> 59 60 #define PRINT_ERROR ((context->opterr) && (*options != ':')) 61 62 #define FLAG_PERMUTE 0x01 // permute non-options to the end of argv 63 #define FLAG_ALLARGS 0x02 // treat non-options as args to option "-1" 64 65 // return values 66 #define BADCH (int)'?' 67 #define BADARG ((*options == ':') ? (int)':' : (int)'?') 68 #define INORDER (int)1 69 70 #define D_PREFIX 0 71 #define DD_PREFIX 1 72 #define W_PREFIX 2 73 74 // Compute the greatest common divisor of a and b. 75 static int gcd(int a, int b) { 76 int c = a % b; 77 while (c) { 78 a = b; 79 b = c; 80 c = a % b; 81 } 82 return b; 83 } 84 85 // Exchange the block from nonopt_start to nonopt_end with the block from 86 // nonopt_end to opt_end (keeping the same order of arguments in each block). 87 // Returns optind - (nonopt_end - nonopt_start) for convenience. 88 static int permute_args(getopt_context* context, char* const* nargv) { 89 // compute lengths of blocks and number and size of cycles 90 int nnonopts = context->nonopt_end - context->nonopt_start; 91 int nopts = context->optind - context->nonopt_end; 92 int ncycle = gcd(nnonopts, nopts); 93 int cyclelen = (context->optind - context->nonopt_start) / ncycle; 94 95 for (int i = 0; i < ncycle; i++) { 96 int cstart = context->nonopt_end + i; 97 int pos = cstart; 98 for (int j = 0; j < cyclelen; j++) { 99 if (pos >= context->nonopt_end) { 100 pos -= nnonopts; 101 } else { 102 pos += nopts; 103 } 104 char* swap = nargv[pos]; 105 const_cast<char**>(nargv)[pos] = nargv[cstart]; 106 const_cast<char**>(nargv)[cstart] = swap; 107 } 108 } 109 return context->optind - (context->nonopt_end - context->nonopt_start); 110 } 111 112 // parse_long_options_r -- 113 // Parse long options in argc/argv argument vector. 114 // Returns -1 if short_too is set and the option does not match long_options. 115 static int parse_long_options_r(char* const* nargv, const char* options, 116 const struct option* long_options, int* idx, 117 bool short_too, struct getopt_context* context) { 118 const char* current_argv = context->place; 119 const char* current_dash; 120 switch (context->dash_prefix) { 121 case D_PREFIX: 122 current_dash = "-"; 123 break; 124 case DD_PREFIX: 125 current_dash = "--"; 126 break; 127 case W_PREFIX: 128 current_dash = "-W "; 129 break; 130 default: 131 current_dash = ""; 132 break; 133 } 134 context->optind++; 135 136 const char* has_equal; 137 size_t current_argv_len; 138 if (!!(has_equal = strchr(current_argv, '='))) { 139 // argument found (--option=arg) 140 current_argv_len = has_equal - current_argv; 141 has_equal++; 142 } else { 143 current_argv_len = strlen(current_argv); 144 } 145 146 int match = -1; 147 bool exact_match = false; 148 bool second_partial_match = false; 149 for (int i = 0; long_options[i].name; i++) { 150 // find matching long option 151 if (strncmp(current_argv, long_options[i].name, current_argv_len)) { 152 continue; 153 } 154 155 if (strlen(long_options[i].name) == current_argv_len) { 156 // exact match 157 match = i; 158 exact_match = true; 159 break; 160 } 161 // If this is a known short option, don't allow 162 // a partial match of a single character. 163 if (short_too && current_argv_len == 1) continue; 164 165 if (match == -1) { // first partial match 166 match = i; 167 } else if (long_options[i].has_arg != long_options[match].has_arg || 168 long_options[i].flag != long_options[match].flag || 169 long_options[i].val != long_options[match].val) { 170 second_partial_match = true; 171 } 172 } 173 if (!exact_match && second_partial_match) { 174 // ambiguous abbreviation 175 if (PRINT_ERROR) { 176 fprintf(context->optstderr ?: stderr, 177 "option `%s%.*s' is ambiguous", current_dash, 178 (int)current_argv_len, current_argv); 179 } 180 context->optopt = 0; 181 return BADCH; 182 } 183 if (match != -1) { // option found 184 if (long_options[match].has_arg == no_argument && has_equal) { 185 if (PRINT_ERROR) { 186 fprintf(context->optstderr ?: stderr, 187 "option `%s%.*s' doesn't allow an argument", 188 current_dash, (int)current_argv_len, current_argv); 189 } 190 // XXX: GNU sets optopt to val regardless of flag 191 context->optopt = 192 long_options[match].flag ? 0 : long_options[match].val; 193 return BADCH; 194 } 195 if (long_options[match].has_arg == required_argument || 196 long_options[match].has_arg == optional_argument) { 197 if (has_equal) { 198 context->optarg = has_equal; 199 } else if (long_options[match].has_arg == required_argument) { 200 // optional argument doesn't use next nargv 201 context->optarg = nargv[context->optind++]; 202 } 203 } 204 if ((long_options[match].has_arg == required_argument) && 205 !context->optarg) { 206 // Missing argument; leading ':' indicates no error 207 // should be generated. 208 if (PRINT_ERROR) { 209 fprintf(context->optstderr ?: stderr, 210 "option `%s%s' requires an argument", current_dash, 211 current_argv); 212 } 213 // XXX: GNU sets optopt to val regardless of flag 214 context->optopt = 215 long_options[match].flag ? 0 : long_options[match].val; 216 context->optind--; 217 return BADARG; 218 } 219 } else { // unknown option 220 if (short_too) { 221 context->optind--; 222 return -1; 223 } 224 if (PRINT_ERROR) { 225 fprintf(context->optstderr ?: stderr, "unrecognized option `%s%s'", 226 current_dash, current_argv); 227 } 228 context->optopt = 0; 229 return BADCH; 230 } 231 if (idx) *idx = match; 232 if (long_options[match].flag) { 233 *long_options[match].flag = long_options[match].val; 234 return 0; 235 } 236 return long_options[match].val; 237 } 238 239 // getopt_long_r -- 240 // Parse argc/argv argument vector. 241 int getopt_long_r(int nargc, char* const* nargv, const char* options, 242 const struct option* long_options, int* idx, 243 struct getopt_context* context) { 244 if (!options) return -1; 245 246 // XXX Some GNU programs (like cvs) set optind to 0 instead of 247 // XXX using optreset. Work around this braindamage. 248 if (!context->optind) context->optind = context->optreset = 1; 249 250 // Disable GNU extensions if options string begins with a '+'. 251 int flags = FLAG_PERMUTE; 252 if (*options == '-') { 253 flags |= FLAG_ALLARGS; 254 } else if (*options == '+') { 255 flags &= ~FLAG_PERMUTE; 256 } 257 if (*options == '+' || *options == '-') options++; 258 259 context->optarg = nullptr; 260 if (context->optreset) context->nonopt_start = context->nonopt_end = -1; 261 start: 262 if (context->optreset || !*context->place) { // update scanning pointer 263 context->optreset = 0; 264 if (context->optind >= nargc) { // end of argument vector 265 context->place = EMSG; 266 if (context->nonopt_end != -1) { 267 // do permutation, if we have to 268 context->optind = permute_args(context, nargv); 269 } else if (context->nonopt_start != -1) { 270 // If we skipped non-options, set optind to the first of them. 271 context->optind = context->nonopt_start; 272 } 273 context->nonopt_start = context->nonopt_end = -1; 274 return -1; 275 } 276 if (*(context->place = nargv[context->optind]) != '-' || 277 context->place[1] == '\0') { 278 context->place = EMSG; // found non-option 279 if (flags & FLAG_ALLARGS) { 280 // GNU extension: return non-option as argument to option 1 281 context->optarg = nargv[context->optind++]; 282 return INORDER; 283 } 284 if (!(flags & FLAG_PERMUTE)) { 285 // If no permutation wanted, stop parsing at first non-option. 286 return -1; 287 } 288 // do permutation 289 if (context->nonopt_start == -1) { 290 context->nonopt_start = context->optind; 291 } else if (context->nonopt_end != -1) { 292 context->nonopt_start = permute_args(context, nargv); 293 context->nonopt_end = -1; 294 } 295 context->optind++; 296 // process next argument 297 goto start; 298 } 299 if (context->nonopt_start != -1 && context->nonopt_end == -1) { 300 context->nonopt_end = context->optind; 301 } 302 303 // If we have "-" do nothing, if "--" we are done. 304 if (context->place[1] != '\0' && *++(context->place) == '-' && 305 context->place[1] == '\0') { 306 context->optind++; 307 context->place = EMSG; 308 // We found an option (--), so if we skipped 309 // non-options, we have to permute. 310 if (context->nonopt_end != -1) { 311 context->optind = permute_args(context, nargv); 312 } 313 context->nonopt_start = context->nonopt_end = -1; 314 return -1; 315 } 316 } 317 318 int optchar; 319 // Check long options if: 320 // 1) we were passed some 321 // 2) the arg is not just "-" 322 // 3) either the arg starts with -- we are getopt_long_only() 323 if (long_options && context->place != nargv[context->optind] && 324 (*context->place == '-')) { 325 bool short_too = false; 326 context->dash_prefix = D_PREFIX; 327 if (*context->place == '-') { 328 context->place++; // --foo long option 329 context->dash_prefix = DD_PREFIX; 330 } else if (*context->place != ':' && strchr(options, *context->place)) { 331 short_too = true; // could be short option too 332 } 333 334 optchar = parse_long_options_r(nargv, options, long_options, idx, 335 short_too, context); 336 if (optchar != -1) { 337 context->place = EMSG; 338 return optchar; 339 } 340 } 341 342 const char* oli; // option letter list index 343 if ((optchar = (int)*(context->place)++) == (int)':' || 344 (optchar == (int)'-' && *context->place != '\0') || 345 !(oli = strchr(options, optchar))) { 346 // If the user specified "-" and '-' isn't listed in 347 // options, return -1 (non-option) as per POSIX. 348 // Otherwise, it is an unknown option character (or ':'). 349 if (optchar == (int)'-' && *context->place == '\0') return -1; 350 if (!*context->place) context->optind++; 351 if (PRINT_ERROR) { 352 fprintf(context->optstderr ?: stderr, "invalid option -- %c", 353 optchar); 354 } 355 context->optopt = optchar; 356 return BADCH; 357 } 358 359 static const char recargchar[] = "option requires an argument -- %c"; 360 if (long_options && optchar == 'W' && oli[1] == ';') { 361 // -W long-option 362 if (*context->place) { // no space 363 ; // NOTHING 364 } else if (++(context->optind) >= nargc) { // no arg 365 context->place = EMSG; 366 if (PRINT_ERROR) { 367 fprintf(context->optstderr ?: stderr, recargchar, optchar); 368 } 369 context->optopt = optchar; 370 return BADARG; 371 } else { // white space 372 context->place = nargv[context->optind]; 373 } 374 context->dash_prefix = W_PREFIX; 375 optchar = parse_long_options_r(nargv, options, long_options, idx, false, 376 context); 377 context->place = EMSG; 378 return optchar; 379 } 380 if (*++oli != ':') { // doesn't take argument 381 if (!*context->place) context->optind++; 382 } else { // takes (optional) argument 383 context->optarg = nullptr; 384 if (*context->place) { // no white space 385 context->optarg = context->place; 386 } else if (oli[1] != ':') { // arg not optional 387 if (++(context->optind) >= nargc) { // no arg 388 context->place = EMSG; 389 if (PRINT_ERROR) { 390 fprintf(context->optstderr ?: stderr, recargchar, optchar); 391 } 392 context->optopt = optchar; 393 return BADARG; 394 } 395 context->optarg = nargv[context->optind]; 396 } 397 context->place = EMSG; 398 context->optind++; 399 } 400 // dump back option letter 401 return optchar; 402 } 403