xref: /freebsd/contrib/bc/src/opt.c (revision 20634f026179ac73d4ea780138789195e335f275)
1 /*
2  * *****************************************************************************
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  *
6  * Copyright (c) 2018-2021 Gavin D. Howard and contributors.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  * * Redistributions of source code must retain the above copyright notice, this
12  *   list of conditions and the following disclaimer.
13  *
14  * * Redistributions in binary form must reproduce the above copyright notice,
15  *   this list of conditions and the following disclaimer in the documentation
16  *   and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  *
30  * *****************************************************************************
31  *
32  * Adapted from https://github.com/skeeto/optparse
33  *
34  * *****************************************************************************
35  *
36  * Code for getopt_long() replacement. It turns out that getopt_long() has
37  * different behavior on different platforms.
38  *
39  */
40 
41 #include <assert.h>
42 #include <stdbool.h>
43 #include <stdlib.h>
44 #include <string.h>
45 
46 #include <status.h>
47 #include <opt.h>
48 #include <vm.h>
49 
50 /**
51  * Returns true if index @a i is the end of the longopts array.
52  * @param longopts  The long options array.
53  * @param i         The index to test.
54  * @return          True if @a i is the last index, false otherwise.
55  */
56 static inline bool bc_opt_longoptsEnd(const BcOptLong *longopts, size_t i) {
57 	return !longopts[i].name && !longopts[i].val;
58 }
59 
60 /**
61  * Returns the name of the long option that matches the character @a c.
62  * @param longopts  The long options array.
63  * @param c         The character to match against.
64  * @return          The name of the long option that matches @a c, or "NULL".
65  */
66 static const char* bc_opt_longopt(const BcOptLong *longopts, int c) {
67 
68 	size_t i;
69 
70 	for (i = 0; !bc_opt_longoptsEnd(longopts, i); ++i) {
71 		if (longopts[i].val == c) return longopts[i].name;
72 	}
73 
74 	BC_UNREACHABLE
75 
76 	return "NULL";
77 }
78 
79 /**
80  * Issues a fatal error for an option parsing failure.
81  * @param err        The error.
82  * @param c          The character for the failing option.
83  * @param str        Either the string for the failing option, or the invalid
84  *                   option.
85  * @param use_short  True if the short option should be used for error printing,
86  *                   false otherwise.
87  */
88 static void bc_opt_error(BcErr err, int c, const char *str, bool use_short) {
89 
90 	if (err == BC_ERR_FATAL_OPTION) {
91 
92 		if (use_short) {
93 
94 			char short_str[2];
95 
96 			short_str[0] = (char) c;
97 			short_str[1] = '\0';
98 
99 			bc_error(err, 0, short_str);
100 		}
101 		else bc_error(err, 0, str);
102 	}
103 	else bc_error(err, 0, (int) c, str);
104 }
105 
106 /**
107  * Returns the type of the long option that matches @a c.
108  * @param longopts  The long options array.
109  * @param c         The character to match against.
110  * @return          The type of the long option as an integer, or -1 if none.
111  */
112 static int bc_opt_type(const BcOptLong *longopts, char c) {
113 
114 	size_t i;
115 
116 	if (c == ':') return -1;
117 
118 	for (i = 0; !bc_opt_longoptsEnd(longopts, i) && longopts[i].val != c; ++i);
119 
120 	if (bc_opt_longoptsEnd(longopts, i)) return -1;
121 
122 	return (int) longopts[i].type;
123 }
124 
125 /**
126  * Parses a short option.
127  * @param o         The option parser.
128  * @param longopts  The long options array.
129  * @return          The character for the short option, or -1 if none left.
130  */
131 static int bc_opt_parseShort(BcOpt *o, const BcOptLong *longopts) {
132 
133 	int type;
134 	char *next;
135 	char *option = o->argv[o->optind];
136 	int ret = -1;
137 
138 	// Make sure to clear these.
139 	o->optopt = 0;
140 	o->optarg = NULL;
141 
142 	// Get the next option.
143 	option += o->subopt + 1;
144 	o->optopt = option[0];
145 
146 	// Get the type and the next data.
147 	type = bc_opt_type(longopts, option[0]);
148 	next = o->argv[o->optind + 1];
149 
150 	switch (type) {
151 
152 		case -1:
153 		case BC_OPT_BC_ONLY:
154 		case BC_OPT_DC_ONLY:
155 		{
156 			// Check for invalid option and barf if so.
157 			if (type == -1 || (type == BC_OPT_BC_ONLY && BC_IS_DC) ||
158 			    (type == BC_OPT_DC_ONLY && BC_IS_BC))
159 			{
160 				char str[2] = {0, 0};
161 
162 				str[0] = option[0];
163 				o->optind += 1;
164 
165 				bc_opt_error(BC_ERR_FATAL_OPTION, option[0], str, true);
166 			}
167 		}
168 		// Fallthrough.
169 		BC_FALLTHROUGH
170 
171 		case BC_OPT_NONE:
172 		{
173 			// If there is something else, update the suboption.
174 			if (option[1]) o->subopt += 1;
175 			else {
176 
177 				// Go to the next argument.
178 				o->subopt = 0;
179 				o->optind += 1;
180 			}
181 
182 			ret = (int) option[0];
183 
184 			break;
185 		}
186 
187 		case BC_OPT_REQUIRED_BC_ONLY:
188 		{
189 			if (BC_IS_DC)
190 				bc_opt_error(BC_ERR_FATAL_OPTION, option[0],
191 				             bc_opt_longopt(longopts, option[0]), true);
192 		}
193 		// Fallthrough
194 		BC_FALLTHROUGH
195 
196 		case BC_OPT_REQUIRED:
197 		{
198 			// Always go to the next argument.
199 			o->subopt = 0;
200 			o->optind += 1;
201 
202 			// Use the next characters, if they exist.
203 			if (option[1]) o->optarg = option + 1;
204 			else if (next != NULL) {
205 
206 				// USe the next.
207 				o->optarg = next;
208 				o->optind += 1;
209 			}
210 			// No argument, barf.
211 			else bc_opt_error(BC_ERR_FATAL_OPTION_NO_ARG, option[0],
212 			                  bc_opt_longopt(longopts, option[0]), true);
213 
214 
215 			ret = (int) option[0];
216 
217 			break;
218 		}
219 	}
220 
221 	return ret;
222 }
223 
224 /**
225  * Ensures that a long option argument matches a long option name, regardless of
226  * "=<data>" at the end.
227  * @param name    The name to match.
228  * @param option  The command-line argument.
229  * @return        True if @a option matches @a name, false otherwise.
230  */
231 static bool bc_opt_longoptsMatch(const char *name, const char *option) {
232 
233 	const char *a = option, *n = name;
234 
235 	// Can never match a NULL name.
236 	if (name == NULL) return false;
237 
238 	// Loop through.
239 	for (; *a && *n && *a != '='; ++a, ++n) {
240 		if (*a != *n) return false;
241 	}
242 
243 	// Ensure they both end at the same place.
244 	return (*n == '\0' && (*a == '\0' || *a == '='));
245 }
246 
247 /**
248  * Returns a pointer to the argument of a long option, or NULL if it not in the
249  * same argument.
250  * @param option  The option to find the argument of.
251  * @return        A pointer to the argument of the option, or NULL if none.
252  */
253 static char* bc_opt_longoptsArg(char *option) {
254 
255 	// Find the end or equals sign.
256 	for (; *option && *option != '='; ++option);
257 
258 	if (*option == '=') return option + 1;
259 	else return NULL;
260 }
261 
262 int bc_opt_parse(BcOpt *o, const BcOptLong *longopts) {
263 
264 	size_t i;
265 	char *option;
266 	bool empty;
267 
268 	// This just eats empty options.
269 	do {
270 
271 		option = o->argv[o->optind];
272 		if (option == NULL) return -1;
273 
274 		empty = !strcmp(option, "");
275 		o->optind += empty;
276 
277 	} while (empty);
278 
279 	// If the option is just a "--".
280 	if (BC_OPT_ISDASHDASH(option)) {
281 
282 		// Consume "--".
283 		o->optind += 1;
284 		return -1;
285 	}
286 	// Parse a short option.
287 	else if (BC_OPT_ISSHORTOPT(option)) return bc_opt_parseShort(o, longopts);
288 	// If the option is not long at this point, we are done.
289 	else if (!BC_OPT_ISLONGOPT(option)) return -1;
290 
291 	// Clear these.
292 	o->optopt = 0;
293 	o->optarg = NULL;
294 
295 	// Skip "--" at beginning of the option.
296 	option += 2;
297 	o->optind += 1;
298 
299 	// Loop through the valid long options.
300 	for (i = 0; !bc_opt_longoptsEnd(longopts, i); i++) {
301 
302 		const char *name = longopts[i].name;
303 
304 		// If we have a match...
305 		if (bc_opt_longoptsMatch(name, option)) {
306 
307 			char *arg;
308 
309 			// Get the option char and the argument.
310 			o->optopt = longopts[i].val;
311 			arg = bc_opt_longoptsArg(option);
312 
313 			// Error if the option is invalid..
314 			if ((longopts[i].type == BC_OPT_BC_ONLY && BC_IS_DC) ||
315 			    (longopts[i].type == BC_OPT_REQUIRED_BC_ONLY && BC_IS_DC) ||
316 			    (longopts[i].type == BC_OPT_DC_ONLY && BC_IS_BC))
317 			{
318 				bc_opt_error(BC_ERR_FATAL_OPTION, o->optopt, name, false);
319 			}
320 
321 			// Error if we have an argument and should not.
322 			if (longopts[i].type == BC_OPT_NONE && arg != NULL)
323 			{
324 				bc_opt_error(BC_ERR_FATAL_OPTION_ARG, o->optopt, name, false);
325 			}
326 
327 			// Set the argument, or check the next argument if we don't have
328 			// one.
329 			if (arg != NULL) o->optarg = arg;
330 			else if (longopts[i].type == BC_OPT_REQUIRED ||
331 			         longopts[i].type == BC_OPT_REQUIRED_BC_ONLY)
332 			{
333 				// Get the next argument.
334 				o->optarg = o->argv[o->optind];
335 
336 				// All's good if it exists; otherwise, barf.
337 				if (o->optarg != NULL) o->optind += 1;
338 				else bc_opt_error(BC_ERR_FATAL_OPTION_NO_ARG,
339 				                  o->optopt, name, false);
340 			}
341 
342 			return o->optopt;
343 		}
344 	}
345 
346 	// If we reach this point, the option is invalid.
347 	bc_opt_error(BC_ERR_FATAL_OPTION, 0, option, false);
348 
349 	BC_UNREACHABLE
350 
351 	return -1;
352 }
353 
354 void bc_opt_init(BcOpt *o, char *argv[]) {
355 	o->argv = argv;
356 	o->optind = 1;
357 	o->subopt = 0;
358 	o->optarg = NULL;
359 }
360