1/* vi: set sw=4 ts=4: */
2/*
3 * universal getopt32 implementation for busybox
4 *
5 * Copyright (C) 2003-2005  Vladimir Oleynik  <dzo@simtreas.ru>
6 *
7 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
8 */
9
10#include <getopt.h>
11#include "libbb.h"
12
13
14/* Code here assumes that 'unsigned' is at least 32 bits wide */
15
16const char *opt_complementary;
17
18typedef struct {
19	int opt;
20	int list_flg;
21	unsigned switch_on;
22	unsigned switch_off;
23	unsigned incongruously;
24	unsigned requires;
25	void **optarg;               /* char **optarg or llist_t **optarg */
26	int *counter;
27} t_complementary;
28
29/* You can set applet_long_options for parse called long options */
30#if ENABLE_GETOPT_LONG
31static const struct option bb_null_long_options[1] = {
32	{ 0, 0, 0, 0 }
33};
34const char *applet_long_options;
35#endif
36
37uint32_t option_mask32;
38
39uint32_t
40getopt32(char **argv, const char *applet_opts, ...)
41{
42	int argc;
43	unsigned flags = 0;
44	unsigned requires = 0;
45	t_complementary complementary[33];
46	int c;
47	const unsigned char *s;
48	t_complementary *on_off;
49	va_list p;
50#if ENABLE_GETOPT_LONG
51	const struct option *l_o;
52	struct option *long_options = (struct option *) &bb_null_long_options;
53#endif
54	unsigned trigger;
55	char **pargv = NULL;
56	int min_arg = 0;
57	int max_arg = -1;
58
59#define SHOW_USAGE_IF_ERROR     1
60#define ALL_ARGV_IS_OPTS        2
61#define FIRST_ARGV_IS_OPT       4
62#define FREE_FIRST_ARGV_IS_OPT  8
63	int spec_flgs = 0;
64
65	argc = 0;
66	while (argv[argc])
67		argc++;
68
69	va_start(p, applet_opts);
70
71	c = 0;
72	on_off = complementary;
73	memset(on_off, 0, sizeof(complementary));
74
75	/* skip GNU extension */
76	s = (const unsigned char *)applet_opts;
77	if (*s == '+' || *s == '-')
78		s++;
79	while (*s) {
80		if (c >= 32) break;
81		on_off->opt = *s;
82		on_off->switch_on = (1 << c);
83		if (*++s == ':') {
84			on_off->optarg = va_arg(p, void **);
85			while (*++s == ':') /* skip */;
86		}
87		on_off++;
88		c++;
89	}
90
91#if ENABLE_GETOPT_LONG
92	if (applet_long_options) {
93		const char *optstr;
94		unsigned i, count;
95
96		count = 1;
97		optstr = applet_long_options;
98		while (optstr[0]) {
99			optstr += strlen(optstr) + 3; /* skip NUL, has_arg, val */
100			count++;
101		}
102		/* count == no. of longopts + 1 */
103		long_options = alloca(count * sizeof(*long_options));
104		memset(long_options, 0, count * sizeof(*long_options));
105		i = 0;
106		optstr = applet_long_options;
107		while (--count) {
108			long_options[i].name = optstr;
109			optstr += strlen(optstr) + 1;
110			long_options[i].has_arg = (unsigned char)(*optstr++);
111			/* long_options[i].flag = NULL; */
112			long_options[i].val = (unsigned char)(*optstr++);
113			i++;
114		}
115		for (l_o = long_options; l_o->name; l_o++) {
116			if (l_o->flag)
117				continue;
118			for (on_off = complementary; on_off->opt != 0; on_off++)
119				if (on_off->opt == l_o->val)
120					goto next_long;
121			if (c >= 32) break;
122			on_off->opt = l_o->val;
123			on_off->switch_on = (1 << c);
124			if (l_o->has_arg != no_argument)
125				on_off->optarg = va_arg(p, void **);
126			c++;
127 next_long: ;
128		}
129	}
130#endif /* ENABLE_GETOPT_LONG */
131	for (s = (const unsigned char *)opt_complementary; s && *s; s++) {
132		t_complementary *pair;
133		unsigned *pair_switch;
134
135		if (*s == ':')
136			continue;
137		c = s[1];
138		if (*s == '?') {
139			if (c < '0' || c > '9') {
140				spec_flgs |= SHOW_USAGE_IF_ERROR;
141			} else {
142				max_arg = c - '0';
143				s++;
144			}
145			continue;
146		}
147		if (*s == '-') {
148			if (c < '0' || c > '9') {
149				if (c == '-') {
150					spec_flgs |= FIRST_ARGV_IS_OPT;
151					s++;
152				} else
153					spec_flgs |= ALL_ARGV_IS_OPTS;
154			} else {
155				min_arg = c - '0';
156				s++;
157			}
158			continue;
159		}
160		if (*s == '=') {
161			min_arg = max_arg = c - '0';
162			s++;
163			continue;
164		}
165		for (on_off = complementary; on_off->opt; on_off++)
166			if (on_off->opt == *s)
167				break;
168		if (c == ':' && s[2] == ':') {
169			on_off->list_flg++;
170			continue;
171		}
172		if (c == ':' || c == '\0') {
173			requires |= on_off->switch_on;
174			continue;
175		}
176		if (c == '-' && (s[2] == ':' || s[2] == '\0')) {
177			flags |= on_off->switch_on;
178			on_off->incongruously |= on_off->switch_on;
179			s++;
180			continue;
181		}
182		if (c == *s) {
183			on_off->counter = va_arg(p, int *);
184			s++;
185		}
186		pair = on_off;
187		pair_switch = &(pair->switch_on);
188		for (s++; *s && *s != ':'; s++) {
189			if (*s == '?') {
190				pair_switch = &(pair->requires);
191			} else if (*s == '-') {
192				if (pair_switch == &(pair->switch_off))
193					pair_switch = &(pair->incongruously);
194				else
195					pair_switch = &(pair->switch_off);
196			} else {
197				for (on_off = complementary; on_off->opt; on_off++)
198					if (on_off->opt == *s) {
199						*pair_switch |= on_off->switch_on;
200						break;
201					}
202			}
203		}
204		s--;
205	}
206	va_end(p);
207
208	if (spec_flgs & FIRST_ARGV_IS_OPT) {
209		if (argv[1] && argv[1][0] != '-' && argv[1][0] != '\0') {
210			argv[1] = xasprintf("-%s", argv[1]);
211			if (ENABLE_FEATURE_CLEAN_UP)
212				spec_flgs |= FREE_FIRST_ARGV_IS_OPT;
213		}
214	}
215
216	/* In case getopt32 was already called, reinit some state */
217	optind = 1;
218	/* optarg = NULL; opterr = 0; optopt = 0; ?? */
219
220	/* Note: just "getopt() <= 0" will not work good for
221	 * "fake" short options, like this one:
222	 * wget $'-\203' "Test: test" http://kernel.org/
223	 * (supposed to act as --header, but doesn't) */
224#if ENABLE_GETOPT_LONG
225	while ((c = getopt_long(argc, argv, applet_opts,
226			long_options, NULL)) != -1) {
227#else
228	while ((c = getopt(argc, argv, applet_opts)) != -1) {
229#endif
230		c &= 0xff; /* fight libc's sign extends */
231 loop_arg_is_opt:
232		for (on_off = complementary; on_off->opt != c; on_off++) {
233			/* c==0 if long opt have non NULL flag */
234			if (on_off->opt == 0 && c != 0)
235				bb_show_usage();
236		}
237		if (flags & on_off->incongruously)
238			bb_show_usage();
239		trigger = on_off->switch_on & on_off->switch_off;
240		flags &= ~(on_off->switch_off ^ trigger);
241		flags |= on_off->switch_on ^ trigger;
242		flags ^= trigger;
243		if (on_off->counter)
244			(*(on_off->counter))++;
245		if (on_off->list_flg) {
246			llist_add_to_end((llist_t **)(on_off->optarg), optarg);
247		} else if (on_off->optarg) {
248			*(char **)(on_off->optarg) = optarg;
249		}
250		if (pargv != NULL)
251			break;
252	}
253
254	if (spec_flgs & ALL_ARGV_IS_OPTS) {
255		/* process argv is option, for example "ps" applet */
256		if (pargv == NULL)
257			pargv = argv + optind;
258		while (*pargv) {
259			c = **pargv;
260			if (c == '\0') {
261				pargv++;
262			} else {
263				(*pargv)++;
264				goto loop_arg_is_opt;
265			}
266		}
267	}
268
269#if (ENABLE_AR || ENABLE_TAR) && ENABLE_FEATURE_CLEAN_UP
270	if (spec_flgs & FREE_FIRST_ARGV_IS_OPT)
271		free(argv[1]);
272#endif
273	/* check depending requires for given options */
274	for (on_off = complementary; on_off->opt; on_off++) {
275		if (on_off->requires && (flags & on_off->switch_on) &&
276					(flags & on_off->requires) == 0)
277			bb_show_usage();
278	}
279	if (requires && (flags & requires) == 0)
280		bb_show_usage();
281	argc -= optind;
282	if (argc < min_arg || (max_arg >= 0 && argc > max_arg))
283		bb_show_usage();
284
285	option_mask32 = flags;
286	return flags;
287}
288