1/*
2 * getopt.c
3 *
4 * getopt_long(), or at least a common subset thereof:
5 *
6 * - Option reordering is not supported
7 * - -W foo is not supported
8 * - First optstring character "-" not supported.
9 */
10
11#include <stdint.h>
12#include <unistd.h>
13#include <string.h>
14#include <getopt.h>
15
16char *optarg;
17int optind, opterr, optopt;
18static struct getopt_private_state {
19	const char *optptr;
20	const char *last_optstring;
21	char *const *last_argv;
22} pvt;
23
24static inline const char *option_matches(const char *arg_str,
25					 const char *opt_name)
26{
27	while (*arg_str != '\0' && *arg_str != '=') {
28		if (*arg_str++ != *opt_name++)
29			return NULL;
30	}
31
32	if (*opt_name)
33		return NULL;
34
35	return arg_str;
36}
37
38int getopt_long(int argc, char *const *argv, const char *optstring,
39		const struct option *longopts, int *longindex)
40{
41	const char *carg;
42	const char *osptr;
43	int opt;
44
45	/* getopt() relies on a number of different global state
46	   variables, which can make this really confusing if there is
47	   more than one use of getopt() in the same program.  This
48	   attempts to detect that situation by detecting if the
49	   "optstring" or "argv" argument have changed since last time
50	   we were called; if so, reinitialize the query state. */
51
52	if (optstring != pvt.last_optstring || argv != pvt.last_argv ||
53	    optind < 1 || optind > argc) {
54		/* optind doesn't match the current query */
55		pvt.last_optstring = optstring;
56		pvt.last_argv = argv;
57		optind = 1;
58		pvt.optptr = NULL;
59	}
60
61	carg = argv[optind];
62
63	/* First, eliminate all non-option cases */
64
65	if (!carg || carg[0] != '-' || !carg[1])
66		return -1;
67
68	if (carg[1] == '-') {
69		const struct option *lo;
70		const char *opt_end = NULL;
71
72		optind++;
73
74		/* Either it's a long option, or it's -- */
75		if (!carg[2]) {
76			/* It's -- */
77			return -1;
78		}
79
80		for (lo = longopts; lo->name; lo++) {
81			if ((opt_end = option_matches(carg+2, lo->name)))
82			    break;
83		}
84		if (!opt_end)
85			return '?';
86
87		if (longindex)
88			*longindex = lo-longopts;
89
90		if (*opt_end == '=') {
91			if (lo->has_arg)
92				optarg = (char *)opt_end+1;
93			else
94				return '?';
95		} else if (lo->has_arg == 1) {
96			if (!(optarg = argv[optind]))
97				return '?';
98			optind++;
99		}
100
101		if (lo->flag) {
102			*lo->flag = lo->val;
103			return 0;
104		} else {
105			return lo->val;
106		}
107	}
108
109	if ((uintptr_t) (pvt.optptr - carg) > (uintptr_t) strlen(carg)) {
110		/* Someone frobbed optind, change to new opt. */
111		pvt.optptr = carg + 1;
112	}
113
114	opt = *pvt.optptr++;
115
116	if (opt != ':' && (osptr = strchr(optstring, opt))) {
117		if (osptr[1] == ':') {
118			if (*pvt.optptr) {
119				/* Argument-taking option with attached
120				   argument */
121				optarg = (char *)pvt.optptr;
122				optind++;
123			} else {
124				/* Argument-taking option with non-attached
125				   argument */
126				if (argv[optind + 1]) {
127					optarg = (char *)argv[optind+1];
128					optind += 2;
129				} else {
130					/* Missing argument */
131					optind++;
132					return (optstring[0] == ':')
133						? ':' : '?';
134				}
135			}
136			return opt;
137		} else {
138			/* Non-argument-taking option */
139			/* pvt.optptr will remember the exact position to
140			   resume at */
141			if (!*pvt.optptr)
142				optind++;
143			return opt;
144		}
145	} else {
146		/* Unknown option */
147		optopt = opt;
148		if (!*pvt.optptr)
149			optind++;
150		return '?';
151	}
152}
153