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