xref: /freebsd/contrib/bc/src/opt.c (revision 38a52bd3)
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
57 bc_opt_longoptsEnd(const BcOptLong* longopts, size_t i)
58 {
59 	return !longopts[i].name && !longopts[i].val;
60 }
61 
62 /**
63  * Returns the name of the long option that matches the character @a c.
64  * @param longopts  The long options array.
65  * @param c         The character to match against.
66  * @return          The name of the long option that matches @a c, or "NULL".
67  */
68 static const char*
69 bc_opt_longopt(const BcOptLong* longopts, int c)
70 {
71 	size_t i;
72 
73 	for (i = 0; !bc_opt_longoptsEnd(longopts, i); ++i)
74 	{
75 		if (longopts[i].val == c) return longopts[i].name;
76 	}
77 
78 	BC_UNREACHABLE
79 
80 	return "NULL";
81 }
82 
83 /**
84  * Issues a fatal error for an option parsing failure.
85  * @param err        The error.
86  * @param c          The character for the failing option.
87  * @param str        Either the string for the failing option, or the invalid
88  *                   option.
89  * @param use_short  True if the short option should be used for error printing,
90  *                   false otherwise.
91  */
92 static void
93 bc_opt_error(BcErr err, int c, const char* str, bool use_short)
94 {
95 	if (err == BC_ERR_FATAL_OPTION)
96 	{
97 		if (use_short)
98 		{
99 			char short_str[2];
100 
101 			short_str[0] = (char) c;
102 			short_str[1] = '\0';
103 
104 			bc_error(err, 0, short_str);
105 		}
106 		else bc_error(err, 0, str);
107 	}
108 	else bc_error(err, 0, (int) c, str);
109 }
110 
111 /**
112  * Returns the type of the long option that matches @a c.
113  * @param longopts  The long options array.
114  * @param c         The character to match against.
115  * @return          The type of the long option as an integer, or -1 if none.
116  */
117 static int
118 bc_opt_type(const BcOptLong* longopts, char c)
119 {
120 	size_t i;
121 
122 	if (c == ':') return -1;
123 
124 	for (i = 0; !bc_opt_longoptsEnd(longopts, i) && longopts[i].val != c; ++i)
125 	{
126 		continue;
127 	}
128 
129 	if (bc_opt_longoptsEnd(longopts, i)) return -1;
130 
131 	return (int) longopts[i].type;
132 }
133 
134 /**
135  * Parses a short option.
136  * @param o         The option parser.
137  * @param longopts  The long options array.
138  * @return          The character for the short option, or -1 if none left.
139  */
140 static int
141 bc_opt_parseShort(BcOpt* o, const BcOptLong* longopts)
142 {
143 	int type;
144 	char* next;
145 	char* option = o->argv[o->optind];
146 	int ret = -1;
147 
148 	// Make sure to clear these.
149 	o->optopt = 0;
150 	o->optarg = NULL;
151 
152 	// Get the next option.
153 	option += o->subopt + 1;
154 	o->optopt = option[0];
155 
156 	// Get the type and the next data.
157 	type = bc_opt_type(longopts, option[0]);
158 	next = o->argv[o->optind + 1];
159 
160 	switch (type)
161 	{
162 		case -1:
163 		case BC_OPT_BC_ONLY:
164 		case BC_OPT_DC_ONLY:
165 		{
166 			// Check for invalid option and barf if so.
167 			if (type == -1 || (type == BC_OPT_BC_ONLY && BC_IS_DC) ||
168 			    (type == BC_OPT_DC_ONLY && BC_IS_BC))
169 			{
170 				char str[2] = { 0, 0 };
171 
172 				str[0] = option[0];
173 				o->optind += 1;
174 
175 				bc_opt_error(BC_ERR_FATAL_OPTION, option[0], str, true);
176 			}
177 
178 			// Fallthrough.
179 			BC_FALLTHROUGH
180 		}
181 
182 		case BC_OPT_NONE:
183 		{
184 			// If there is something else, update the suboption.
185 			if (option[1]) o->subopt += 1;
186 			else
187 			{
188 				// Go to the next argument.
189 				o->subopt = 0;
190 				o->optind += 1;
191 			}
192 
193 			ret = (int) option[0];
194 
195 			break;
196 		}
197 
198 		case BC_OPT_REQUIRED_BC_ONLY:
199 		{
200 			if (BC_IS_DC)
201 			{
202 				bc_opt_error(BC_ERR_FATAL_OPTION, option[0],
203 				             bc_opt_longopt(longopts, option[0]), true);
204 			}
205 
206 			// Fallthrough
207 			BC_FALLTHROUGH
208 		}
209 
210 		case BC_OPT_REQUIRED:
211 		{
212 			// Always go to the next argument.
213 			o->subopt = 0;
214 			o->optind += 1;
215 
216 			// Use the next characters, if they exist.
217 			if (option[1]) o->optarg = option + 1;
218 			else if (next != NULL)
219 			{
220 				// USe the next.
221 				o->optarg = next;
222 				o->optind += 1;
223 			}
224 			// No argument, barf.
225 			else
226 			{
227 				bc_opt_error(BC_ERR_FATAL_OPTION_NO_ARG, option[0],
228 				             bc_opt_longopt(longopts, option[0]), true);
229 			}
230 
231 			ret = (int) option[0];
232 
233 			break;
234 		}
235 	}
236 
237 	return ret;
238 }
239 
240 /**
241  * Ensures that a long option argument matches a long option name, regardless of
242  * "=<data>" at the end.
243  * @param name    The name to match.
244  * @param option  The command-line argument.
245  * @return        True if @a option matches @a name, false otherwise.
246  */
247 static bool
248 bc_opt_longoptsMatch(const char* name, const char* option)
249 {
250 	const char* a = option;
251 	const char* n = name;
252 
253 	// Can never match a NULL name.
254 	if (name == NULL) return false;
255 
256 	// Loop through.
257 	for (; *a && *n && *a != '='; ++a, ++n)
258 	{
259 		if (*a != *n) return false;
260 	}
261 
262 	// Ensure they both end at the same place.
263 	return (*n == '\0' && (*a == '\0' || *a == '='));
264 }
265 
266 /**
267  * Returns a pointer to the argument of a long option, or NULL if it not in the
268  * same argument.
269  * @param option  The option to find the argument of.
270  * @return        A pointer to the argument of the option, or NULL if none.
271  */
272 static char*
273 bc_opt_longoptsArg(char* option)
274 {
275 	// Find the end or equals sign.
276 	for (; *option && *option != '='; ++option)
277 	{
278 		continue;
279 	}
280 
281 	if (*option == '=') return option + 1;
282 	else return NULL;
283 }
284 
285 int
286 bc_opt_parse(BcOpt* o, const BcOptLong* longopts)
287 {
288 	size_t i;
289 	char* option;
290 	bool empty;
291 
292 	// This just eats empty options.
293 	do
294 	{
295 		option = o->argv[o->optind];
296 		if (option == NULL) return -1;
297 
298 		empty = !strcmp(option, "");
299 		o->optind += empty;
300 	}
301 	while (empty);
302 
303 	// If the option is just a "--".
304 	if (BC_OPT_ISDASHDASH(option))
305 	{
306 		// Consume "--".
307 		o->optind += 1;
308 		return -1;
309 	}
310 	// Parse a short option.
311 	else if (BC_OPT_ISSHORTOPT(option)) return bc_opt_parseShort(o, longopts);
312 	// If the option is not long at this point, we are done.
313 	else if (!BC_OPT_ISLONGOPT(option)) return -1;
314 
315 	// Clear these.
316 	o->optopt = 0;
317 	o->optarg = NULL;
318 
319 	// Skip "--" at beginning of the option.
320 	option += 2;
321 	o->optind += 1;
322 
323 	// Loop through the valid long options.
324 	for (i = 0; !bc_opt_longoptsEnd(longopts, i); i++)
325 	{
326 		const char* name = longopts[i].name;
327 
328 		// If we have a match...
329 		if (bc_opt_longoptsMatch(name, option))
330 		{
331 			char* arg;
332 
333 			// Get the option char and the argument.
334 			o->optopt = longopts[i].val;
335 			arg = bc_opt_longoptsArg(option);
336 
337 			// Error if the option is invalid..
338 			if ((longopts[i].type == BC_OPT_BC_ONLY && BC_IS_DC) ||
339 			    (longopts[i].type == BC_OPT_REQUIRED_BC_ONLY && BC_IS_DC) ||
340 			    (longopts[i].type == BC_OPT_DC_ONLY && BC_IS_BC))
341 			{
342 				bc_opt_error(BC_ERR_FATAL_OPTION, o->optopt, name, false);
343 			}
344 
345 			// Error if we have an argument and should not.
346 			if (longopts[i].type == BC_OPT_NONE && arg != NULL)
347 			{
348 				bc_opt_error(BC_ERR_FATAL_OPTION_ARG, o->optopt, name, false);
349 			}
350 
351 			// Set the argument, or check the next argument if we don't have
352 			// one.
353 			if (arg != NULL) o->optarg = arg;
354 			else if (longopts[i].type == BC_OPT_REQUIRED ||
355 			         longopts[i].type == BC_OPT_REQUIRED_BC_ONLY)
356 			{
357 				// Get the next argument.
358 				o->optarg = o->argv[o->optind];
359 
360 				// All's good if it exists; otherwise, barf.
361 				if (o->optarg != NULL) o->optind += 1;
362 				else
363 				{
364 					bc_opt_error(BC_ERR_FATAL_OPTION_NO_ARG, o->optopt, name,
365 					             false);
366 				}
367 			}
368 
369 			return o->optopt;
370 		}
371 	}
372 
373 	// If we reach this point, the option is invalid.
374 	bc_opt_error(BC_ERR_FATAL_OPTION, 0, option, false);
375 
376 	BC_UNREACHABLE
377 
378 	return -1;
379 }
380 
381 void
382 bc_opt_init(BcOpt* o, char* argv[])
383 {
384 	o->argv = argv;
385 	o->optind = 1;
386 	o->subopt = 0;
387 	o->optarg = NULL;
388 }
389