1 /*
2  * Copyright (c) 2018 Oticon A/S
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <string.h>
8 #include <strings.h>
9 #include <stdbool.h>
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <math.h>
13 #include <zephyr/arch/posix/posix_trace.h>
14 #include "posix_board_if.h"
15 #include <zephyr/types.h>
16 #include "cmdline_common.h"
17 
18 /**
19  * Check if <arg> is the option <option>
20  * The accepted syntax is:
21  *   * For options without a value following:
22  *       [-[-]]<option>
23  *   * For options with value:
24  *       [-[-]]<option>{:|=}<value>
25  *
26  * Returns 0 if it is not, or a number > 0 if it is.
27  * The returned number is the number of characters it went through
28  * to find the end of the option including the ':' or '=' character in case of
29  * options with value
30  */
cmd_is_option(const char * arg,const char * option,int with_value)31 int cmd_is_option(const char *arg, const char *option, int with_value)
32 {
33 	int of = 0;
34 	size_t to_match_len = strlen(option);
35 
36 	if (arg[of] == '-') {
37 		of++;
38 	}
39 	if (arg[of] == '-') {
40 		of++;
41 	}
42 
43 	if (!with_value) {
44 		if (strcmp(&arg[of], option) != 0) {
45 			return 0;
46 		} else {
47 			return of + to_match_len;
48 		}
49 	}
50 
51 	while (!(arg[of] == 0 && *option == 0)) {
52 		if (*option == 0) {
53 			if ((arg[of] == ':') || (arg[of] == '=')) {
54 				of++;
55 				break;
56 			}
57 			return 0;
58 		}
59 		if (arg[of] != *option) {
60 			return 0;
61 		}
62 		of++;
63 		option++;
64 	}
65 
66 	if (arg[of] == 0) { /* we need a value to follow */
67 		posix_print_error_and_exit("Incorrect option syntax '%s'. The "
68 					   "value should follow the options. "
69 					   "For example --ratio=3\n",
70 					   arg);
71 	}
72 	return of;
73 }
74 
75 /**
76  * Return 1 if <arg> matches an accepted help option.
77  * 0 otherwise
78  *
79  * Valid help options are [-[-]]{?|h|help}
80  * with the h or help in any case combination
81  */
cmd_is_help_option(const char * arg)82 int cmd_is_help_option(const char *arg)
83 {
84 	if (arg[0] == '-') {
85 		arg++;
86 	}
87 	if (arg[0] == '-') {
88 		arg++;
89 	}
90 	if ((strcasecmp(arg, "?") == 0) ||
91 	    (strcasecmp(arg, "h") == 0) ||
92 	    (strcasecmp(arg, "help") == 0)) {
93 		return 1;
94 	} else {
95 		return 0;
96 	}
97 }
98 
99 #define CMD_TYPE_ERROR "Coding error: type %c not understood"
100 #define CMD_ERR_BOOL_SWI "Programming error: I only know how to "\
101 	"automatically read boolean switches\n"
102 
103 /**
104  * Read out a the value following an option from str, and store it into
105  * <dest>
106  * <type> indicates the type of parameter (and type of dest pointer)
107  *   'b' : boolean
108  *   's' : string (char *)
109  *   'u' : 32 bit unsigned integer
110  *   'U' : 64 bit unsigned integer
111  *   'i' : 32 bit signed integer
112  *   'I' : 64 bit signed integer
113  *   'd' : *double* float
114  *
115  * Note: list type ('l') cannot be handled by this function and must always be
116  *       manual
117  *
118  *  <long_d> is the long name of the option
119  */
cmd_read_option_value(const char * str,void * dest,const char type,const char * option)120 void cmd_read_option_value(const char *str, void *dest, const char type,
121 			   const char *option)
122 {
123 	int error = 0;
124 	char *endptr = NULL;
125 
126 	switch (type) {
127 	case 'b':
128 		if (strcasecmp(str, "false") == 0) {
129 			*(bool *)dest = false;
130 			endptr = (char *)str + 5;
131 		} else if (strcmp(str, "0") == 0) {
132 			*(bool *)dest = false;
133 			endptr = (char *)str + 1;
134 		} else if (strcasecmp(str, "true") == 0) {
135 			*(bool *)dest = true;
136 			endptr = (char *)str + 4;
137 		} else if (strcmp(str, "1") == 0) {
138 			*(bool *)dest = true;
139 			endptr = (char *)str + 1;
140 		} else {
141 			error = 1;
142 		}
143 		break;
144 	case 's':
145 		*(char **)dest = (char *)str;
146 		endptr = (char *)str + strlen(str);
147 		break;
148 	case 'u':
149 		*(uint32_t *)dest = strtoul(str, &endptr, 0);
150 		break;
151 	case 'U':
152 		*(uint64_t *)dest = strtoull(str, &endptr, 0);
153 		break;
154 	case 'i':
155 		*(int32_t *)dest = strtol(str, &endptr, 0);
156 		break;
157 	case 'I':
158 		*(int64_t *)dest = strtoll(str, &endptr, 0);
159 		break;
160 	case 'd':
161 		*(double *)dest = strtod(str, &endptr);
162 		break;
163 	default:
164 		posix_print_error_and_exit(CMD_TYPE_ERROR, type);
165 		/* Unreachable */
166 		break;
167 	}
168 
169 	if (!error && endptr && *endptr != 0) {
170 		error = 1;
171 	}
172 
173 	if (error) {
174 		posix_print_error_and_exit("Error reading value of %s '%s'. Use"
175 					   " --help for usage information\n",
176 					   option, str);
177 	}
178 }
179 
180 /**
181  * Initialize existing dest* to defaults based on type
182  */
cmd_args_set_defaults(struct args_struct_t args_struct[])183 void cmd_args_set_defaults(struct args_struct_t args_struct[])
184 {
185 	int count = 0;
186 
187 	while (args_struct[count].option != NULL) {
188 
189 		if (args_struct[count].dest == NULL) {
190 			count++;
191 			continue;
192 		}
193 
194 		switch (args_struct[count].type) {
195 		case 0: /* does not have storage */
196 			break;
197 		case 'b':
198 			*(bool *)args_struct[count].dest = false;
199 			break;
200 		case 's':
201 			*(char **)args_struct[count].dest = NULL;
202 			break;
203 		case 'u':
204 			*(uint32_t *)args_struct[count].dest = UINT32_MAX;
205 			break;
206 		case 'U':
207 			*(uint64_t *)args_struct[count].dest = UINT64_MAX;
208 			break;
209 		case 'i':
210 			*(int32_t *)args_struct[count].dest = INT32_MAX;
211 			break;
212 		case 'I':
213 			*(int64_t *)args_struct[count].dest = INT64_MAX;
214 			break;
215 		case 'd':
216 			*(double *)args_struct[count].dest = (double)NAN;
217 			break;
218 		default:
219 			posix_print_error_and_exit(CMD_TYPE_ERROR,
220 						   args_struct[count].type);
221 			break;
222 		}
223 		count++;
224 	}
225 }
226 
227 /**
228  * For the help messages:
229  * Generate a string containing how the option described by <args_s_el>
230  * should be used
231  *
232  * The string is saved in <buf> which has been allocated <size> bytes by the
233  * caller
234  */
cmd_gen_switch_syntax(char * buf,int size,struct args_struct_t * args_s_el)235 static void cmd_gen_switch_syntax(char *buf, int size,
236 				  struct args_struct_t *args_s_el)
237 {
238 	int ret = 0;
239 
240 	if (size <= 0) {
241 		return;
242 	}
243 
244 	if (args_s_el->is_mandatory == false) {
245 		*buf++ = '[';
246 		size--;
247 	}
248 
249 	if (args_s_el->is_switch == true) {
250 		ret = snprintf(buf, size, "-%s", args_s_el->option);
251 	} else {
252 		if (args_s_el->type != 'l') {
253 			ret = snprintf(buf, size, "-%s=<%s>",
254 					args_s_el->option, args_s_el->name);
255 		} else {
256 			ret = snprintf(buf, size, "-%s <%s>...",
257 					args_s_el->option, args_s_el->name);
258 		}
259 	}
260 
261 	if (ret < 0) {
262 		posix_print_error_and_exit("Unexpected error in %s %i\n",
263 					   __FILE__, __LINE__);
264 	}
265 	if (size - ret < 0) {
266 		/*
267 		 * If we run out of space we can just stop,
268 		 * this is not critical
269 		 */
270 		return;
271 	}
272 	buf += ret;
273 	size -= ret;
274 
275 	if (args_s_el->is_mandatory == false) {
276 		snprintf(buf, size, "] ");
277 	} else {
278 		snprintf(buf, size, " ");
279 	}
280 }
281 
282 /**
283  * Print short list of available switches
284  */
cmd_print_switches_help(struct args_struct_t args_struct[])285 void cmd_print_switches_help(struct args_struct_t args_struct[])
286 {
287 	int count = 0;
288 	int printed_in_line = strlen(_HELP_SWITCH) + 1;
289 
290 	fprintf(stdout, "%s ", _HELP_SWITCH);
291 
292 	while (args_struct[count].option != NULL) {
293 		char stringy[_MAX_STRINGY_LEN];
294 
295 		cmd_gen_switch_syntax(stringy, _MAX_STRINGY_LEN,
296 				      &args_struct[count]);
297 
298 		if (printed_in_line + strlen(stringy) > _MAX_LINE_WIDTH) {
299 			fprintf(stdout, "\n");
300 			printed_in_line = 0;
301 		}
302 
303 		fprintf(stdout, "%s", stringy);
304 		printed_in_line += strlen(stringy);
305 		count++;
306 	}
307 
308 	fprintf(stdout, "\n");
309 }
310 
311 /**
312  * Print the long help message of the program
313  */
cmd_print_long_help(struct args_struct_t args_struct[])314 void cmd_print_long_help(struct args_struct_t args_struct[])
315 {
316 	int ret;
317 	int count = 0;
318 	int printed_in_line = 0;
319 	char stringy[_MAX_STRINGY_LEN];
320 
321 	cmd_print_switches_help(args_struct);
322 
323 	fprintf(stdout, "\n %-*s:%s\n", _LONG_HELP_ALIGN-1,
324 		_HELP_SWITCH, _HELP_DESCR);
325 
326 	while (args_struct[count].option != NULL) {
327 		int printed_right;
328 		char *toprint;
329 		int total_to_print;
330 
331 		cmd_gen_switch_syntax(stringy, _MAX_STRINGY_LEN,
332 				      &args_struct[count]);
333 
334 		ret = fprintf(stdout, " %-*s:", _LONG_HELP_ALIGN-1, stringy);
335 		printed_in_line = ret;
336 		printed_right = 0;
337 		toprint = args_struct[count].descript;
338 		total_to_print = strlen(toprint);
339 		ret = fprintf(stdout, "%.*s\n",
340 				_MAX_LINE_WIDTH - printed_in_line,
341 				&toprint[printed_right]);
342 		printed_right += ret - 1;
343 
344 		while (printed_right < total_to_print) {
345 			fprintf(stdout, "%*s", _LONG_HELP_ALIGN, "");
346 			ret = fprintf(stdout, "%.*s\n",
347 				      _MAX_LINE_WIDTH - _LONG_HELP_ALIGN,
348 				      &toprint[printed_right]);
349 			printed_right += ret - 1;
350 		}
351 		count++;
352 	}
353 	fprintf(stdout, "\n");
354 	fprintf(stdout, "Note that which options are available depends on the "
355 		"enabled features/drivers\n\n");
356 }
357 
358 /*
359  * <argv> matched the argument described in <arg_element>
360  *
361  * If arg_element->dest points to a place to store a possible value, read it
362  * If there is a callback registered, call it after
363  */
cmd_handle_this_matched_arg(char * argv,int offset,struct args_struct_t * arg_element)364 static void cmd_handle_this_matched_arg(char *argv, int offset,
365 					struct args_struct_t *arg_element)
366 {
367 	if (arg_element->dest != NULL) {
368 		if (arg_element->is_switch) {
369 			if (arg_element->type == 'b') {
370 				*(bool *)arg_element->dest = true;
371 			} else {
372 				posix_print_error_and_exit(CMD_ERR_BOOL_SWI);
373 			}
374 		} else { /* if not a switch we need to read its value */
375 			cmd_read_option_value(&argv[offset],
376 					      arg_element->dest,
377 					      arg_element->type,
378 					      arg_element->option);
379 		}
380 	}
381 
382 	if (arg_element->call_when_found) {
383 		arg_element->call_when_found(argv, offset);
384 	}
385 }
386 
387 /**
388  * Try to find if this argument is in the list (and it is not manual)
389  * if it does, try to parse it, set its dest accordingly, and return true
390  * if it is not found, return false
391  */
cmd_parse_one_arg(char * argv,struct args_struct_t args_struct[])392 bool cmd_parse_one_arg(char *argv, struct args_struct_t args_struct[])
393 {
394 	int count = 0;
395 	int ret;
396 
397 	if (cmd_is_help_option(argv)) {
398 		cmd_print_long_help(args_struct);
399 		posix_exit(0);
400 	}
401 
402 	while (args_struct[count].option != NULL) {
403 		if (args_struct[count].manual) {
404 			count++;
405 			continue;
406 		}
407 		ret = cmd_is_option(argv, args_struct[count].option,
408 				    !args_struct[count].is_switch);
409 		if (ret) {
410 			cmd_handle_this_matched_arg(argv,
411 						    ret,
412 						    &args_struct[count]);
413 			return true;
414 		}
415 		count++;
416 	}
417 	return false;
418 }
419