1 /*	$OpenBSD: getopt_long.c,v 1.22 2006/10/04 21:29:04 jmc Exp $	*/
2 /*	$NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $	*/
3 /* SPDX-License-Identifier: BSD-3-Clause */
4 
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  *
40  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
41  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
42  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
43  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
44  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
45  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
46  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
47  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
48  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
49  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
50  * POSSIBILITY OF SUCH DAMAGE.
51  */
52 
53 #include <string.h>
54 #include <zephyr/sys/sys_getopt.h>
55 #include <zephyr/sys/util.h>
56 #include "getopt_common.h"
57 
58 #include <zephyr/logging/log.h>
59 LOG_MODULE_DECLARE(sys_getopt);
60 
61 #define GNU_COMPATIBLE /* Be more compatible, configure's use us! */
62 
63 #define PRINT_ERROR ((state->opterr) && (*options != ':'))
64 
65 #define FLAG_PERMUTE  0x01 /* permute non-options to the end of argv */
66 #define FLAG_ALLARGS  0x02 /* treat non-options as args to option "-1" */
67 #define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */
68 
69 /* return values */
70 #define BADCH   (int)'?'
71 #define BADARG  ((*options == ':') ? (int)':' : (int)'?')
72 #define INORDER 1
73 
74 #define EMSG ""
75 
76 #ifdef GNU_COMPATIBLE
77 #define NO_PREFIX (-1)
78 #define D_PREFIX  0
79 #define DD_PREFIX 1
80 #define W_PREFIX  2
81 #endif
82 
83 static int getopt_internal(struct sys_getopt_state *state, int nargc, char *const *nargv,
84 			   const char *options, const struct sys_getopt_option *long_options,
85 			   int *idx, int flags);
86 static int parse_long_options(struct sys_getopt_state *state, char *const *nargv,
87 			      const char *options, const struct sys_getopt_option *long_options,
88 			      int *idx, int short_too, int flags);
89 static void permute_args(int panonopt_start, int panonopt_end, int opt_end, char *const *nargv);
90 
91 /* Error messages */
92 #define RECARGCHAR "option requires an argument -- %c"
93 #define ILLOPTCHAR "illegal option -- %c" /* From P1003.2 */
94 #ifdef GNU_COMPATIBLE
95 static int dash_prefix = NO_PREFIX;
96 #define GNUOPTCHAR "invalid option -- %c"
97 
98 #define RECARGSTRING "option `%s%s' requires an argument"
99 #define AMBIG        "option `%s%.*s' is ambiguous"
100 #define NOARG        "option `%s%.*s' doesn't allow an argument"
101 #define ILLOPTSTRING "unrecognized option `%s%s'"
102 #else
103 #define RECARGSTRING "option requires an argument -- %s"
104 #define AMBIG        "ambiguous option -- %.*s"
105 #define NOARG        "option doesn't take an argument -- %.*s"
106 #define ILLOPTSTRING "unknown option -- %s"
107 #endif
108 
109 /*
110  * Exchange the block from nonopt_start to nonopt_end with the block
111  * from nonopt_end to opt_end (keeping the same order of arguments
112  * in each block).
113  */
permute_args(int panonopt_start,int panonopt_end,int opt_end,char * const * nargv)114 static void permute_args(int panonopt_start, int panonopt_end, int opt_end, char *const *nargv)
115 {
116 	int cstart, cyclelen, ncycle, nnonopts, nopts, pos;
117 	char *swap;
118 
119 	/*
120 	 * compute lengths of blocks and number and size of cycles
121 	 */
122 	nnonopts = panonopt_end - panonopt_start;
123 	nopts = opt_end - panonopt_end;
124 	ncycle = sys_gcd(nnonopts, nopts);
125 	cyclelen = (opt_end - panonopt_start) / ncycle;
126 
127 	for (int i = 0; i < ncycle; i++) {
128 		cstart = panonopt_end + i;
129 		pos = cstart;
130 		for (int j = 0; j < cyclelen; j++) {
131 			if (pos >= panonopt_end) {
132 				pos -= nnonopts;
133 			} else {
134 				pos += nopts;
135 			}
136 			swap = nargv[pos];
137 			/* LINTED const cast */
138 			((char **)nargv)[pos] = nargv[cstart];
139 			/* LINTED const cast */
140 			((char **)nargv)[cstart] = swap;
141 		}
142 	}
143 }
144 
145 /*
146  * parse_long_options --
147  *	Parse long options in argc/argv argument vector.
148  * Returns -1 if short_too is set and the option does not match long_options.
149  */
parse_long_options(struct sys_getopt_state * state,char * const * nargv,const char * options,const struct sys_getopt_option * long_options,int * idx,int short_too,int flags)150 static int parse_long_options(struct sys_getopt_state *state, char *const *nargv,
151 			      const char *options, const struct sys_getopt_option *long_options,
152 			      int *idx, int short_too, int flags)
153 {
154 	char *current_argv, *has_equal;
155 #ifdef GNU_COMPATIBLE
156 	char *current_dash = "";
157 #endif
158 	size_t current_argv_len;
159 	int match, exact_match, second_partial_match;
160 
161 	current_argv = state->place;
162 #ifdef GNU_COMPATIBLE
163 	if (PRINT_ERROR) {
164 		switch (dash_prefix) {
165 		case D_PREFIX:
166 			current_dash = "-";
167 			break;
168 		case DD_PREFIX:
169 			current_dash = "--";
170 			break;
171 		case W_PREFIX:
172 			current_dash = "-W ";
173 			break;
174 		default:
175 			break;
176 		}
177 	}
178 #endif
179 	match = -1;
180 	exact_match = 0;
181 	second_partial_match = 0;
182 
183 	state->optind++;
184 
185 	has_equal = strchr(current_argv, '=');
186 	if (has_equal != NULL) {
187 		/* argument found (--option=arg) */
188 		current_argv_len = has_equal - current_argv;
189 		has_equal++;
190 	} else {
191 		current_argv_len = strlen(current_argv);
192 	}
193 
194 	for (int i = 0; long_options[i].name; i++) {
195 		/* find matching long option */
196 		if (strncmp(current_argv, long_options[i].name, current_argv_len)) {
197 			continue;
198 		}
199 
200 		if (strlen(long_options[i].name) == current_argv_len) {
201 			/* exact match */
202 			match = i;
203 			exact_match = 1;
204 			break;
205 		}
206 		/*
207 		 * If this is a known short option, don't allow
208 		 * a partial match of a single character.
209 		 */
210 		if (short_too && current_argv_len == 1) {
211 			continue;
212 		}
213 
214 		if (match == -1) { /* first partial match */
215 			match = i;
216 		} else if ((flags & FLAG_LONGONLY) ||
217 			   long_options[i].has_arg != long_options[match].has_arg ||
218 			   long_options[i].flag != long_options[match].flag ||
219 			   long_options[i].val != long_options[match].val) {
220 			second_partial_match = 1;
221 		}
222 	}
223 	if (!exact_match && second_partial_match) {
224 		/* ambiguous abbreviation */
225 		if (PRINT_ERROR) {
226 			LOG_WRN(AMBIG,
227 #ifdef GNU_COMPATIBLE
228 				current_dash,
229 #endif
230 				(int)current_argv_len, current_argv);
231 		}
232 		state->optopt = 0;
233 		return BADCH;
234 	}
235 	if (match != -1) { /* option found */
236 		if (long_options[match].has_arg == sys_getopt_no_argument && has_equal) {
237 			if (PRINT_ERROR) {
238 				LOG_WRN(NOARG,
239 #ifdef GNU_COMPATIBLE
240 					current_dash,
241 #endif
242 					(int)current_argv_len, current_argv);
243 			}
244 			/*
245 			 * XXX: GNU sets optopt to val regardless of flag
246 			 */
247 			if (long_options[match].flag == NULL) {
248 				state->optopt = long_options[match].val;
249 			} else {
250 				state->optopt = 0;
251 			}
252 #ifdef GNU_COMPATIBLE
253 			return BADCH;
254 #else
255 			return BADARG;
256 #endif
257 		}
258 		if (long_options[match].has_arg == sys_getopt_required_argument ||
259 		    long_options[match].has_arg == sys_getopt_optional_argument) {
260 			if (has_equal) {
261 				state->optarg = has_equal;
262 			} else if (long_options[match].has_arg == sys_getopt_required_argument) {
263 				/*
264 				 * optional argument doesn't use next nargv
265 				 */
266 				state->optarg = nargv[state->optind++];
267 			}
268 		}
269 		if ((long_options[match].has_arg == sys_getopt_required_argument) &&
270 		    (state->optarg == NULL)) {
271 			/*
272 			 * Missing argument; leading ':' indicates no error
273 			 * should be generated.
274 			 */
275 			if (PRINT_ERROR) {
276 				LOG_WRN(RECARGSTRING,
277 #ifdef GNU_COMPATIBLE
278 					current_dash,
279 #endif
280 					current_argv);
281 			}
282 			/*
283 			 * XXX: GNU sets optopt to val regardless of flag
284 			 */
285 			if (long_options[match].flag == NULL) {
286 				state->optopt = long_options[match].val;
287 			} else {
288 				state->optopt = 0;
289 			}
290 			--state->optind;
291 			return BADARG;
292 		}
293 	} else { /* unknown option */
294 		if (short_too) {
295 			--state->optind;
296 			return -1;
297 		}
298 		if (PRINT_ERROR) {
299 			LOG_WRN(ILLOPTSTRING,
300 #ifdef GNU_COMPATIBLE
301 				current_dash,
302 #endif
303 				current_argv);
304 		}
305 		state->optopt = 0;
306 		return BADCH;
307 	}
308 	if (idx) {
309 		*idx = match;
310 	}
311 	if (long_options[match].flag) {
312 		*long_options[match].flag = long_options[match].val;
313 		return 0;
314 	} else {
315 		return long_options[match].val;
316 	}
317 }
318 
319 /*
320  * getopt_internal --
321  *	Parse argc/argv argument vector.  Called by user level routines.
322  */
getopt_internal(struct sys_getopt_state * state,int nargc,char * const * nargv,const char * options,const struct sys_getopt_option * long_options,int * idx,int flags)323 static int getopt_internal(struct sys_getopt_state *state, int nargc, char *const *nargv,
324 			   const char *options, const struct sys_getopt_option *long_options,
325 			   int *idx, int flags)
326 {
327 	char *oli; /* option letter list index */
328 	int optchar, short_too;
329 
330 	if (options == NULL) {
331 		return -1;
332 	}
333 
334 	/*
335 	 * Disable GNU extensions if options string begins with a '+'.
336 	 */
337 #ifdef GNU_COMPATIBLE
338 	if (*options == '-') {
339 		flags |= FLAG_ALLARGS;
340 	} else if (*options == '+') {
341 		flags &= ~FLAG_PERMUTE;
342 	}
343 #else
344 	if (*options == '+') {
345 		flags &= ~FLAG_PERMUTE;
346 	} else if (*options == '-') {
347 		flags |= FLAG_ALLARGS;
348 	}
349 #endif
350 	if (*options == '+' || *options == '-') {
351 		options++;
352 	}
353 
354 	/*
355 	 * XXX Some GNU programs (like cvs) set optind to 0 instead of
356 	 * XXX using optreset.  Work around this braindamage.
357 	 */
358 	if (state->optind == 0) {
359 		state->optind = state->optreset = 1;
360 	}
361 
362 	state->optarg = NULL;
363 	if (state->optreset) {
364 		state->nonopt_start = state->nonopt_end = -1;
365 	}
366 start:
367 	if (state->optreset || !*(state->place)) { /* update scanning pointer */
368 		state->optreset = 0;
369 		if (state->optind >= nargc) { /* end of argument vector */
370 			state->place = EMSG;
371 			if (state->nonopt_end != -1) {
372 				/* do permutation, if we have to */
373 				permute_args(state->nonopt_start, state->nonopt_end, state->optind,
374 					     nargv);
375 				state->optind -= state->nonopt_end - state->nonopt_start;
376 			} else if (state->nonopt_start != -1) {
377 				/*
378 				 * If we skipped non-options, set optind
379 				 * to the first of them.
380 				 */
381 				state->optind = state->nonopt_start;
382 			}
383 			state->nonopt_start = state->nonopt_end = -1;
384 			return -1;
385 		}
386 		state->place = nargv[state->optind];
387 		if (*(state->place) != '-' ||
388 #ifdef GNU_COMPATIBLE
389 		    state->place[1] == '\0') {
390 #else
391 		    (state->place[1] == '\0' && strchr(options, '-') == NULL)) {
392 #endif
393 			state->place = EMSG; /* found non-option */
394 			if (flags & FLAG_ALLARGS) {
395 				/*
396 				 * GNU extension:
397 				 * return non-option as argument to option 1
398 				 */
399 				state->optarg = nargv[state->optind++];
400 				return INORDER;
401 			}
402 			if (!(flags & FLAG_PERMUTE)) {
403 				/*
404 				 * If no permutation wanted, stop parsing
405 				 * at first non-option.
406 				 */
407 				return -1;
408 			}
409 			/* do permutation */
410 			if (state->nonopt_start == -1) {
411 				state->nonopt_start = state->optind;
412 			} else if (state->nonopt_end != -1) {
413 				permute_args(state->nonopt_start, state->nonopt_end, state->optind,
414 					     nargv);
415 				state->nonopt_start =
416 					state->optind - (state->nonopt_end - state->nonopt_start);
417 				state->nonopt_end = -1;
418 			}
419 			state->optind++;
420 			/* process next argument */
421 			goto start;
422 		}
423 		if (state->nonopt_start != -1 && state->nonopt_end == -1) {
424 			state->nonopt_end = state->optind;
425 		}
426 
427 		/*
428 		 * If we have "-" do nothing, if "--" we are done.
429 		 */
430 		if (state->place[1] != '\0' && *++(state->place) == '-' &&
431 		    state->place[1] == '\0') {
432 			state->optind++;
433 			state->place = EMSG;
434 			/*
435 			 * We found an option (--), so if we skipped
436 			 * non-options, we have to permute.
437 			 */
438 			if (state->nonopt_end != -1) {
439 				permute_args(state->nonopt_start, state->nonopt_end, state->optind,
440 					     nargv);
441 				state->optind -= state->nonopt_end - state->nonopt_start;
442 			}
443 			state->nonopt_start = state->nonopt_end = -1;
444 			return -1;
445 		}
446 	}
447 
448 	/*
449 	 * Check long options if:
450 	 *  1) we were passed some
451 	 *  2) the arg is not just "-"
452 	 *  3) either the arg starts with -- we are sys_getopt_long_only()
453 	 */
454 	if (long_options != NULL && state->place != nargv[state->optind] &&
455 	    (*(state->place) == '-' || (flags & FLAG_LONGONLY))) {
456 		short_too = 0;
457 #ifdef GNU_COMPATIBLE
458 		dash_prefix = D_PREFIX;
459 #endif
460 		if (*(state->place) == '-') {
461 			state->place++; /* --foo long option */
462 #ifdef GNU_COMPATIBLE
463 			dash_prefix = DD_PREFIX;
464 #endif
465 		} else if (*(state->place) != ':' && strchr(options, *(state->place)) != NULL) {
466 			short_too = 1; /* could be short option too */
467 		}
468 
469 		optchar = parse_long_options(state, nargv, options, long_options, idx, short_too,
470 					     flags);
471 		if (optchar != -1) {
472 			state->place = EMSG;
473 			return optchar;
474 		}
475 	}
476 	optchar = (int)*(state->place)++;
477 	oli = strchr(options, optchar);
478 	if (optchar == (int)':' || (optchar == (int)'-' && *(state->place) != '\0') ||
479 	    oli == NULL) {
480 		/*
481 		 * If the user specified "-" and  '-' isn't listed in
482 		 * options, return -1 (non-option) as per POSIX.
483 		 * Otherwise, it is an unknown option character (or ':').
484 		 */
485 		if (optchar == (int)'-' && *(state->place) == '\0') {
486 			return -1;
487 		}
488 		if (!*(state->place)) {
489 			++state->optind;
490 		}
491 #ifdef GNU_COMPATIBLE
492 		if (PRINT_ERROR) {
493 			LOG_WRN(GNUOPTCHAR, optchar);
494 		}
495 #else
496 		if (PRINT_ERROR) {
497 			LOG_WRN(ILLOPTCHAR, optchar);
498 		}
499 #endif
500 		state->optopt = optchar;
501 		return BADCH;
502 	}
503 	if (long_options != NULL && optchar == 'W' && oli[1] == ';') {
504 		/* -W long-option */
505 		if (*(state->place)) {                   /* no space */
506 			/* NOTHING */
507 		} else if (++(state->optind) >= nargc) { /* no arg */
508 			state->place = EMSG;
509 			if (PRINT_ERROR) {
510 				LOG_WRN(RECARGCHAR, optchar);
511 			}
512 			state->optopt = optchar;
513 			return BADARG;
514 		} else if ((state->optind) < nargc) {
515 			state->place = nargv[state->optind];
516 		}
517 #ifdef GNU_COMPATIBLE
518 		dash_prefix = W_PREFIX;
519 #endif
520 		optchar = parse_long_options(state, nargv, options, long_options, idx, 0, flags);
521 		state->place = EMSG;
522 		return optchar;
523 	}
524 	if (*++oli != ':') { /* doesn't take argument */
525 		if (!*(state->place)) {
526 			++state->optind;
527 		}
528 	} else { /* takes (optional) argument */
529 		state->optarg = NULL;
530 		if (*(state->place)) { /* no white space */
531 			state->optarg = state->place;
532 		} else if (oli[1] != ':') {             /* arg not optional */
533 			if (++state->optind >= nargc) { /* no arg */
534 				state->place = EMSG;
535 				if (PRINT_ERROR) {
536 					LOG_WRN(RECARGCHAR, optchar);
537 				}
538 				state->optopt = optchar;
539 				return BADARG;
540 			}
541 			state->optarg = nargv[state->optind];
542 		}
543 		state->place = EMSG;
544 		++state->optind;
545 	}
546 	/* dump back option letter */
547 	return optchar;
548 }
549 
550 /*
551  * getopt_long --
552  *	Parse argc/argv argument vector.
553  */
554 int sys_getopt_long(int nargc, char *const *nargv, const char *options,
555 		    const struct sys_getopt_option *long_options, int *idx)
556 {
557 	struct sys_getopt_state *state;
558 	int ret;
559 
560 	/* Get state of the current thread */
561 	state = sys_getopt_state_get();
562 
563 	ret = getopt_internal(state, nargc, nargv, options, long_options, idx, FLAG_PERMUTE);
564 
565 	z_getopt_global_state_update(state);
566 
567 	return ret;
568 }
569 
570 /*
571  * getopt_long_only --
572  *	Parse argc/argv argument vector.
573  */
574 int sys_getopt_long_only(int nargc, char *const *nargv, const char *options,
575 			 const struct sys_getopt_option *long_options, int *idx)
576 {
577 	struct sys_getopt_state *state;
578 	int ret;
579 
580 	/* Get state of the current thread */
581 	state = sys_getopt_state_get();
582 
583 	ret = getopt_internal(state, nargc, nargv, options, long_options, idx,
584 			      FLAG_PERMUTE | FLAG_LONGONLY);
585 
586 	z_getopt_global_state_update(state);
587 
588 	return ret;
589 }
590