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