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