jcs's openbsd hax
openbsd
at jcs 643 lines 18 kB view raw
1/* $OpenBSD: getopt_long.c,v 1.1.1.1 2025/05/03 15:09:38 tb 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@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 * 3. All advertising materials mentioning features or use of this software 39 * must display the following acknowledgement: 40 * This product includes software developed by the NetBSD 41 * Foundation, Inc. and its contributors. 42 * 4. Neither the name of The NetBSD Foundation nor the names of its 43 * contributors may be used to endorse or promote products derived 44 * from this software without specific prior written permission. 45 * 46 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 47 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 48 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 49 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 50 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 51 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 52 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 53 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 54 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 55 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 56 * POSSIBILITY OF SUCH DAMAGE. 57 */ 58 59#include "getopt_long.h" 60 61#include <errno.h> 62#include <stdio.h> 63#include <stdlib.h> 64#include <string.h> 65#ifndef _WIN32 66#include <unistd.h> 67#endif 68 69#define PKGCONF_HACK_LOGICAL_OR_ALL_VALUES 70 71int pkg_opterr = 1; /* if error message should be printed */ 72int pkg_optind = 1; /* index into parent argv vector */ 73int pkg_optopt = '?'; /* character checked for validity */ 74int pkg_optreset; /* reset getopt */ 75char *pkg_optarg; /* argument associated with option */ 76 77#define PRINT_ERROR ((pkg_opterr) && (*options != ':')) 78 79#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */ 80#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */ 81#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */ 82 83/* return values */ 84#define BADCH (int)'?' 85#define BADARG ((*options == ':') ? (int)':' : (int)'?') 86#define INORDER (int)1 87 88/* add some padding to EMSG to avoid overrun */ 89#define EMSG "\0\0\0\0" 90 91#ifdef GNU_COMPATIBLE 92#define NO_PREFIX (-1) 93#define D_PREFIX 0 94#define DD_PREFIX 1 95#define W_PREFIX 2 96#endif 97 98static int getopt_internal(int, char * const *, const char *, 99 const struct pkg_option *, int *, int); 100static int parse_long_options(char * const *, const char *, 101 const struct pkg_option *, int *, int, int); 102static int gcd(int, int); 103static void permute_args(int, int, int, char * const *); 104 105static char *place = EMSG; /* option letter processing */ 106 107/* XXX: set pkg_optreset to 1 rather than these two */ 108static int nonopt_start = -1; /* first non option argument (for permute) */ 109static int nonopt_end = -1; /* first option after non options (for permute) */ 110 111/* Error messages */ 112static const char recargchar[] = "option requires an argument -- %c"; 113static const char illoptchar[] = "illegal option -- %c"; /* From P1003.2 */ 114#ifdef GNU_COMPATIBLE 115static int dash_prefix = NO_PREFIX; 116static const char gnuoptchar[] = "invalid option -- %c"; 117 118static const char recargstring[] = "option `%s%s' requires an argument"; 119static const char ambig[] = "option `%s%.*s' is ambiguous"; 120static const char noarg[] = "option `%s%.*s' doesn't allow an argument"; 121static const char illoptstring[] = "unrecognized option `%s%s'"; 122#else 123static const char recargstring[] = "option requires an argument -- %s"; 124static const char ambig[] = "ambiguous option -- %.*s"; 125static const char noarg[] = "option doesn't take an argument -- %.*s"; 126static const char illoptstring[] = "unknown option -- %s"; 127#endif 128 129/* 130 * Compute the greatest common divisor of a and b. 131 */ 132static int 133gcd(int a, int b) 134{ 135 int c; 136 137 c = a % b; 138 while (c != 0) { 139 a = b; 140 b = c; 141 c = a % b; 142 } 143 144 return (b); 145} 146 147/* 148 * Exchange the block from nonopt_start to nonopt_end with the block 149 * from nonopt_end to opt_end (keeping the same order of arguments 150 * in each block). 151 */ 152static void 153permute_args(int panonopt_start, int panonopt_end, int opt_end, 154 char * const *nargv) 155{ 156 int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; 157 char *swap; 158 159 /* 160 * compute lengths of blocks and number and size of cycles 161 */ 162 nnonopts = panonopt_end - panonopt_start; 163 nopts = opt_end - panonopt_end; 164 ncycle = gcd(nnonopts, nopts); 165 cyclelen = (opt_end - panonopt_start) / ncycle; 166 167 for (i = 0; i < ncycle; i++) { 168 cstart = panonopt_end+i; 169 pos = cstart; 170 for (j = 0; j < cyclelen; j++) { 171 if (pos >= panonopt_end) 172 pos -= nnonopts; 173 else 174 pos += nopts; 175 swap = nargv[pos]; 176 /* LINTED const cast */ 177 ((char **) nargv)[pos] = nargv[cstart]; 178 /* LINTED const cast */ 179 ((char **)nargv)[cstart] = swap; 180 } 181 } 182} 183 184/* 185 * parse_long_options -- 186 * Parse long options in argc/argv argument vector. 187 * Returns -1 if short_too is set and the option does not match long_options. 188 */ 189static int 190parse_long_options(char * const *nargv, const char *options, 191 const struct pkg_option *long_options, int *idx, int short_too, int flags) 192{ 193 char *current_argv, *has_equal; 194#ifdef GNU_COMPATIBLE 195 char *current_dash; 196#endif 197 size_t current_argv_len; 198 int i, match, exact_match, second_partial_match; 199 200 current_argv = place; 201#ifdef GNU_COMPATIBLE 202 switch (dash_prefix) { 203 case D_PREFIX: 204 current_dash = "-"; 205 break; 206 case DD_PREFIX: 207 current_dash = "--"; 208 break; 209 case W_PREFIX: 210 current_dash = "-W "; 211 break; 212 default: 213 current_dash = ""; 214 break; 215 } 216#endif 217 match = -1; 218 exact_match = 0; 219 second_partial_match = 0; 220 221 pkg_optind++; 222 223 if ((has_equal = strchr(current_argv, '=')) != NULL) { 224 /* argument found (--option=arg) */ 225 current_argv_len = has_equal - current_argv; 226 has_equal++; 227 } else 228 current_argv_len = strlen(current_argv); 229 230 for (i = 0; long_options[i].name; i++) { 231 /* find matching long option */ 232 if (strncmp(current_argv, long_options[i].name, 233 current_argv_len)) 234 continue; 235 236 if (strlen(long_options[i].name) == current_argv_len) { 237 /* exact match */ 238 match = i; 239 exact_match = 1; 240 break; 241 } 242 /* 243 * If this is a known short option, don't allow 244 * a partial match of a single character. 245 */ 246 if (short_too && current_argv_len == 1) 247 continue; 248 249 if (match == -1) /* first partial match */ 250 match = i; 251 else if ((flags & FLAG_LONGONLY) || 252 long_options[i].has_arg != 253 long_options[match].has_arg || 254 long_options[i].flag != long_options[match].flag || 255 long_options[i].val != long_options[match].val) 256 second_partial_match = 1; 257 } 258 if (!exact_match && second_partial_match) { 259 /* ambiguous abbreviation */ 260 if (PRINT_ERROR) { 261 fprintf(stderr, "pkgconf: "); 262 fprintf(stderr, ambig, 263#ifdef GNU_COMPATIBLE 264 current_dash, 265#endif 266 (int)current_argv_len, 267 current_argv); 268 fprintf(stderr, "\n"); 269 } 270 pkg_optopt = 0; 271 return (BADCH); 272 } 273 if (match != -1) { /* option found */ 274 if (long_options[match].has_arg == no_argument 275 && has_equal) { 276 if (PRINT_ERROR) { 277 fprintf(stderr, "pkgconf: "); 278 fprintf(stderr, noarg, 279#ifdef GNU_COMPATIBLE 280 current_dash, 281#endif 282 (int)current_argv_len, 283 current_argv); 284 fprintf(stderr, "\n"); 285 } 286 /* 287 * XXX: GNU sets pkg_optopt to val regardless of flag 288 */ 289 if (long_options[match].flag == NULL) 290 pkg_optopt = (int)long_options[match].val; 291 else 292 pkg_optopt = 0; 293#ifdef GNU_COMPATIBLE 294 return (BADCH); 295#else 296 return (BADARG); 297#endif 298 } 299 if (long_options[match].has_arg == required_argument || 300 long_options[match].has_arg == optional_argument) { 301 if (has_equal) 302 pkg_optarg = has_equal; 303 else if (long_options[match].has_arg == 304 required_argument) { 305 /* 306 * optional argument doesn't use next nargv 307 */ 308 pkg_optarg = nargv[pkg_optind++]; 309 } 310 } 311 if ((long_options[match].has_arg == required_argument) 312 && (pkg_optarg == NULL)) { 313 /* 314 * Missing argument; leading ':' indicates no error 315 * should be generated. 316 */ 317 if (PRINT_ERROR) { 318 fprintf(stderr, "pkgconf: "); 319 fprintf(stderr, recargstring, 320#ifdef GNU_COMPATIBLE 321 current_dash, 322#endif 323 current_argv); 324 fprintf(stderr, "\n"); 325 } 326 /* 327 * XXX: GNU sets pkg_optopt to val regardless of flag 328 */ 329 if (long_options[match].flag == NULL) 330 pkg_optopt = (int)long_options[match].val; 331 else 332 pkg_optopt = 0; 333 --pkg_optind; 334 return (BADARG); 335 } 336 } else { /* unknown option */ 337 if (short_too) { 338 --pkg_optind; 339 return (-1); 340 } 341 if (PRINT_ERROR) { 342 fprintf(stderr, "pkgconf: "); 343 fprintf(stderr, illoptstring, 344#ifdef GNU_COMPATIBLE 345 current_dash, 346#endif 347 current_argv); 348 fprintf(stderr, "\n"); 349 } 350 pkg_optopt = 0; 351 return (BADCH); 352 } 353 if (idx) 354 *idx = match; 355 if (long_options[match].flag) { 356#ifdef PKGCONF_HACK_LOGICAL_OR_ALL_VALUES 357 *long_options[match].flag |= long_options[match].val; 358#else 359 *long_options[match].flag = long_options[match].val; 360#endif 361 return (0); 362 } else 363 return ((int)long_options[match].val); 364} 365 366/* 367 * getopt_internal -- 368 * Parse argc/argv argument vector. Called by user level routines. 369 */ 370static int 371getopt_internal(int nargc, char * const *nargv, const char *options, 372 const struct pkg_option *long_options, int *idx, int flags) 373{ 374 char *oli; /* option letter list index */ 375 int optchar, short_too; 376 int posixly_correct; /* no static, can be changed on the fly */ 377 378 if (options == NULL) 379 return (-1); 380 381 /* 382 * Disable GNU extensions if POSIXLY_CORRECT is set or options 383 * string begins with a '+'. 384 */ 385 posixly_correct = (getenv("POSIXLY_CORRECT") != NULL); 386#ifdef GNU_COMPATIBLE 387 if (*options == '-') 388 flags |= FLAG_ALLARGS; 389 else if (posixly_correct || *options == '+') 390 flags &= ~FLAG_PERMUTE; 391#else 392 if (posixly_correct || *options == '+') 393 flags &= ~FLAG_PERMUTE; 394 else if (*options == '-') 395 flags |= FLAG_ALLARGS; 396#endif 397 398 if (*options == '+' || *options == '-') 399 options++; 400 401 /* 402 * XXX Some GNU programs (like cvs) set pkg_optind to 0 instead of 403 * XXX using pkg_optreset. Work around this braindamage. 404 */ 405 if (pkg_optind == 0) 406 pkg_optind = pkg_optreset = 1; 407 408 pkg_optarg = NULL; 409 if (pkg_optreset) 410 nonopt_start = nonopt_end = -1; 411start: 412 if (pkg_optreset || !*place) { /* update scanning pointer */ 413 pkg_optreset = 0; 414 if (pkg_optind >= nargc) { /* end of argument vector */ 415 place = EMSG; 416 if (nonopt_end != -1) { 417 /* do permutation, if we have to */ 418 permute_args(nonopt_start, nonopt_end, 419 pkg_optind, nargv); 420 pkg_optind -= nonopt_end - nonopt_start; 421 } 422 else if (nonopt_start != -1) { 423 /* 424 * If we skipped non-options, set pkg_optind 425 * to the first of them. 426 */ 427 pkg_optind = nonopt_start; 428 } 429 nonopt_start = nonopt_end = -1; 430 return (-1); 431 } 432 if (*(place = nargv[pkg_optind]) != '-' || 433#ifdef GNU_COMPATIBLE 434 place[1] == '\0') { 435#else 436 (place[1] == '\0' && strchr(options, '-') == NULL)) { 437#endif 438 place = EMSG; /* found non-option */ 439 if (flags & FLAG_ALLARGS) { 440 /* 441 * GNU extension: 442 * return non-option as argument to option 1 443 */ 444 pkg_optarg = nargv[pkg_optind++]; 445 return (INORDER); 446 } 447 if (!(flags & FLAG_PERMUTE)) { 448 /* 449 * If no permutation wanted, stop parsing 450 * at first non-option. 451 */ 452 return (-1); 453 } 454 /* do permutation */ 455 if (nonopt_start == -1) 456 nonopt_start = pkg_optind; 457 else if (nonopt_end != -1) { 458 permute_args(nonopt_start, nonopt_end, 459 pkg_optind, nargv); 460 nonopt_start = pkg_optind - 461 (nonopt_end - nonopt_start); 462 nonopt_end = -1; 463 } 464 pkg_optind++; 465 /* process next argument */ 466 goto start; 467 } 468 if (nonopt_start != -1 && nonopt_end == -1) 469 nonopt_end = pkg_optind; 470 471 /* 472 * If we have "-" do nothing, if "--" we are done. 473 */ 474 if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { 475 pkg_optind++; 476 place = EMSG; 477 /* 478 * We found an option (--), so if we skipped 479 * non-options, we have to permute. 480 */ 481 if (nonopt_end != -1) { 482 permute_args(nonopt_start, nonopt_end, 483 pkg_optind, nargv); 484 pkg_optind -= nonopt_end - nonopt_start; 485 } 486 nonopt_start = nonopt_end = -1; 487 return (-1); 488 } 489 } 490 491 /* 492 * Check long options if: 493 * 1) we were passed some 494 * 2) the arg is not just "-" 495 * 3) either the arg starts with -- we are getopt_long_only() 496 */ 497 if (long_options != NULL && place != nargv[pkg_optind] && 498 (*place == '-' || (flags & FLAG_LONGONLY))) { 499 short_too = 0; 500#ifdef GNU_COMPATIBLE 501 dash_prefix = D_PREFIX; 502#endif 503 if (*place == '-') { 504 place++; /* --foo long option */ 505#ifdef GNU_COMPATIBLE 506 dash_prefix = DD_PREFIX; 507#endif 508 } else if (*place != ':' && strchr(options, *place) != NULL) 509 short_too = 1; /* could be short option too */ 510 511 optchar = parse_long_options(nargv, options, long_options, 512 idx, short_too, flags); 513 if (optchar != -1) { 514 place = EMSG; 515 return (optchar); 516 } 517 } 518 519 if ((optchar = (int)*place++) == (int)':' || 520 (optchar == (int)'-' && *place != '\0') || 521 (oli = strchr(options, optchar)) == NULL) { 522 /* 523 * If the user specified "-" and '-' isn't listed in 524 * options, return -1 (non-option) as per POSIX. 525 * Otherwise, it is an unknown option character (or ':'). 526 */ 527 if (optchar == (int)'-' && *place == '\0') 528 return (-1); 529 if (!*place) 530 ++pkg_optind; 531#ifdef GNU_COMPATIBLE 532 if (PRINT_ERROR) { 533 fprintf(stderr, "pkgconf: "); 534 fprintf(stderr, posixly_correct ? illoptchar : gnuoptchar, 535 optchar); 536 fprintf(stderr, "\n"); 537 } 538#else 539 if (PRINT_ERROR) { 540 fprintf(stderr, "pkgconf: "); 541 fprintf(stderr, illoptchar, optchar); 542 fprintf(stderr, "\n"); 543 } 544#endif 545 pkg_optopt = optchar; 546 return (BADCH); 547 } 548 if (long_options != NULL && optchar == 'W' && oli[1] == ';') { 549 /* -W long-option */ 550 if (*place) /* no space */ 551 /* NOTHING */; 552 else if (++pkg_optind >= nargc) { /* no arg */ 553 place = EMSG; 554 if (PRINT_ERROR) { 555 fprintf(stderr, "pkgconf: "); 556 fprintf(stderr, recargchar, optchar); 557 fprintf(stderr, "\n"); 558 } 559 pkg_optopt = optchar; 560 return (BADARG); 561 } else /* white space */ 562 place = nargv[pkg_optind]; 563#ifdef GNU_COMPATIBLE 564 dash_prefix = W_PREFIX; 565#endif 566 optchar = parse_long_options(nargv, options, long_options, 567 idx, 0, flags); 568 place = EMSG; 569 return (optchar); 570 } 571 if (*++oli != ':') { /* doesn't take argument */ 572 if (!*place) 573 ++pkg_optind; 574 } else { /* takes (optional) argument */ 575 pkg_optarg = NULL; 576 if (*place) /* no white space */ 577 pkg_optarg = place; 578 else if (oli[1] != ':') { /* arg not optional */ 579 if (++pkg_optind >= nargc) { /* no arg */ 580 place = EMSG; 581 if (PRINT_ERROR) { 582 fprintf(stderr, "pkgconf: "); 583 fprintf(stderr, recargchar, optchar); 584 fprintf(stderr, "\n"); 585 } 586 pkg_optopt = optchar; 587 return (BADARG); 588 } else 589 pkg_optarg = nargv[pkg_optind]; 590 } 591 place = EMSG; 592 ++pkg_optind; 593 } 594 /* dump back option letter */ 595 return (optchar); 596} 597 598/* 599 * getopt -- 600 * Parse argc/argv argument vector. 601 * 602 * [eventually this will replace the BSD getopt] 603 */ 604int 605pkg_getopt(int nargc, char * const *nargv, const char *options) 606{ 607 608 /* 609 * We don't pass FLAG_PERMUTE to getopt_internal() since 610 * the BSD getopt(3) (unlike GNU) has never done this. 611 * 612 * Furthermore, since many privileged programs call getopt() 613 * before dropping privileges it makes sense to keep things 614 * as simple (and bug-free) as possible. 615 */ 616 return (getopt_internal(nargc, nargv, options, NULL, NULL, 0)); 617} 618 619/* 620 * getopt_long -- 621 * Parse argc/argv argument vector. 622 */ 623int 624pkg_getopt_long(int nargc, char * const *nargv, const char *options, 625 const struct pkg_option *long_options, int *idx) 626{ 627 628 return (getopt_internal(nargc, nargv, options, long_options, idx, 629 FLAG_PERMUTE)); 630} 631 632/* 633 * getopt_long_only -- 634 * Parse argc/argv argument vector. 635 */ 636int 637pkg_getopt_long_only(int nargc, char * const *nargv, const char *options, 638 const struct pkg_option *long_options, int *idx) 639{ 640 641 return (getopt_internal(nargc, nargv, options, long_options, idx, 642 FLAG_PERMUTE|FLAG_LONGONLY)); 643}