1 /*
2 * Copyright 2018 Oticon A/S
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6 #include <string.h>
7 #include <strings.h>
8 #include <stdint.h>
9 #include <limits.h>
10 #include <math.h>
11 #include "bs_cmd_line.h"
12 #include "bs_tracing.h"
13 #include "bs_types.h"
14 #include "bs_utils.h"
15 #include "bs_oswrap.h"
16
17 /*
18 * A default executable name and a post help message
19 * We define empty ones in case the component does not care to do so
20 * But normally a component would redefine these
21 */
22 char executable_name[] __attribute__((weak)) ="";
component_print_post_help()23 void __attribute__((weak)) component_print_post_help() {
24 fprintf(stdout, "\n");
25 }
26 static char empty_string[] ="";
27 static char *overriden_executable_name = NULL;
28 static char *trace_prefix = empty_string;
29
30 static void (*post_help)(void) = component_print_post_help;
31
32 /*
33 * Dynamically override the component post help function
34 */
bs_override_post_help(void (* new_f)(void))35 void bs_override_post_help(void (*new_f)(void)) {
36 post_help = new_f;
37 }
38
39
40 /**
41 * Check if <arg> is the option <option>
42 * The accepted syntax is:
43 * * For options without a value following:
44 * [-[-]]<option>
45 * * For options with value:
46 * [-[-]]<option>{:|=}<value>
47 *
48 * Returns 0 if it is not, or a number > 0 if it is.
49 * The returned number is the number of characters it went through
50 * to find the end of the option including the ':' or '=' character in case of
51 * options with value
52 *
53 */
bs_is_option(const char * arg,const char * option,int with_value)54 int bs_is_option(const char *arg, const char *option, int with_value) {
55 int of = 0;
56 size_t to_match_len = strlen(option);
57
58 if (arg[of] == '-') {
59 of++;
60 }
61 if (arg[of] == '-') {
62 of++;
63 }
64
65 if (!with_value) {
66 if (strcmp(&arg[of], option) != 0) {
67 return 0;
68 } else {
69 return of + to_match_len;
70 }
71 }
72
73 while (!(arg[of] == 0 && *option == 0)) {
74 if (*option == 0) {
75 if ((arg[of] == ':') || (arg[of] == '=')) {
76 of++;
77 break;
78 }
79 return 0;
80 }
81 if (arg[of] != *option) {
82 return 0;
83 }
84 of++;
85 option++;
86 }
87
88 if (arg[of] == 0) { /* we need a value to follow */
89 return 0;
90 }
91 return of;
92 }
93
94 /**
95 * Check if arg matches the option <option><x>
96 * withArgs means it shall be followed by a = or :
97 * e.g.: -d0=10 ("d", withargs=true) => index 0
98 * -argsmodem0 ("argsmodem", withargs=false) => index 0
99 * -pp10:0.1 ("pp", withargs=true) => index 10
100 *
101 * Returns 0 if it is not, or a number > 0 if it is.
102 * The number is the number of characters it went thru to find the option (if there is any paramter it follows)
103 *
104 * argv can start with - or --
105 */
bs_is_multi_opt(const char * arg,const char * option,uint * index,int with_value)106 int bs_is_multi_opt(const char *arg, const char *option, uint* index, int with_value) {
107 int of = 0;
108 size_t to_match_len = strlen(option);
109 if (arg[of] == '-') {
110 of++;
111 }
112 if (arg[of] == '-') { //we accept options with either 1 or 2 -
113 of++;
114 }
115
116 if (strncmp(&arg[of],option,to_match_len) == 0) {
117 of += to_match_len;
118
119 { //get the index
120 uint c = of;
121 uint n=0;
122 while ( ( arg[c] != 0 ) && ( arg[c]!= ':' ) && ( arg[c]!= '=' ) ){
123 if ( ( arg[c] >= '0' ) && ( arg[c] <= '9') ) {
124 n = n*10 + ( arg[c] - '0');
125 } else { //we found something else than a number
126 c = of;
127 break;
128 }
129 c++;
130 }
131 if ( c == of ) { //we didnt read any number out
132 of = 0;
133 } else {
134 of = c;
135 *index = n;
136 }
137 } //end of getting the index
138
139 //check if the option finishes here or not:
140 if ( with_value ) {
141 if ( ( arg[of] == ':' ) || ( arg[of] == '=' ) ) {
142 of++;
143 if ( arg[of] == 0 ) { //we need an option to follow
144 of = 0;
145 }
146 } else {
147 of = 0;
148 }
149 } else {
150 if ( arg[of] != 0 ) { //we dont accept any extra characters
151 of = 0;
152 }
153 }
154 } else {
155 of = 0;
156 }
157
158 return of;
159 }
160
161 /**
162 * Return 1 if <arg> matches an accepted help option.
163 * 0 otherwise
164 *
165 * Valid help options are [-[-]]{?|h|help}
166 * with the h or help in any case combination
167 */
bs_is_help(const char * arg)168 int bs_is_help(const char *arg){
169 if (arg[0] == '-') {
170 arg++;
171 }
172 if (arg[0] == '-') {
173 arg++;
174 }
175 if ((strcasecmp(arg, "?") == 0) ||
176 (strcasecmp(arg, "h") == 0) ||
177 (strcasecmp(arg, "help") == 0)) {
178 return 1;
179 } else {
180 return 0;
181 }
182 }
183
184
185 /**
186 * Read out a the value of the option parameter from str, and store it into
187 * <dest>
188 * <type> indicates the type of paramter (and type of dest pointer)
189 * 'b' : boolean
190 * 's' : string (char *)
191 * 'u' : unsigned integer
192 * 'U' : 64 bit unsigned integer
193 * 'i' : signed integer
194 * 'I' : 64 bit signed integer
195 * 'f'/'d' : *double* float
196 *
197 * <long_d> is the long name of the option
198 */
bs_read_optionparam(const char * str,void * dest,const char type,const char * long_d)199 void bs_read_optionparam(const char* str, void *dest, const char type,
200 const char *long_d) {
201 int error = 0;
202 char *endptr;
203
204 switch (type){
205 case 'b':
206 if (strcasecmp(str, "false") == 0) {
207 *(bool *)dest = false;
208 endptr = (char *)str + 5;
209 } else if (strcmp(str, "0") == 0) {
210 *(bool *)dest = false;
211 endptr = (char *)str + 1;
212 } else if (strcasecmp(str, "true") == 0) {
213 *(bool *)dest = true;
214 endptr = (char *)str + 4;
215 } else if (strcmp(str, "1") == 0) {
216 *(bool *)dest = true;
217 endptr = (char *)str + 1;
218 } else {
219 error = 1;
220 }
221 break;
222 case 's':
223 *((char**)dest) = (char*)str;
224 endptr = (char*)str + strlen(str);
225 break;
226 case 'u':
227 *(uint32_t *)dest = strtoul(str, &endptr, 0);
228 break;
229 case 'U':
230 *(uint64_t *)dest = strtoull(str, &endptr, 0);
231 break;
232 case 'i':
233 *(int32_t *)dest = strtol(str, &endptr, 0);
234 break;
235 case 'I':
236 *(int64_t *)dest = strtoll(str, &endptr, 0);
237 break;
238 case 'f':
239 case 'd':
240 *(double *)dest = strtod(str, &endptr);
241 break;
242 default:
243 bs_trace_error_line("Coding error: type %c not acceptable for automatically read option\n", type);
244 break;
245 }
246
247 if (!error && *endptr != 0) {
248 error = 1;
249 }
250
251 if (error){
252 bs_trace_error_line("Error reading option %s \"%s\"\nRun with --help for more information", long_d, str);
253 }
254
255 switch (type){
256 case 's':
257 bs_trace_raw(9,"%s%s set to %s\n", trace_prefix, long_d, *((char**)dest));
258 break;
259 case 'u':
260 bs_trace_raw(9,"%s%s set to %u\n", trace_prefix, long_d, *((uint32_t*)dest));
261 break;
262 case 'U':
263 bs_trace_raw(9,"%s%s set to %"PRIu64"\n", trace_prefix, long_d, *((uint64_t*)dest));
264 break;
265 case 'i':
266 bs_trace_raw(9,"%s%s set to %i\n", trace_prefix, long_d, *((int32_t*)dest));
267 break;
268 case 'I':
269 bs_trace_raw(9,"%s%s set to %"PRIi64"\n", trace_prefix, long_d, *((int64_t*)dest));
270 break;
271 case 'f':
272 case 'd':
273 bs_trace_raw(9,"%s%s set to %le\n", trace_prefix, long_d, *((double*)dest));
274 break;
275 default:
276 break;
277 }
278 }
279
280 /**
281 * Initialize existing dest* to defaults based on type
282 */
bs_args_set_defaults(bs_args_struct_t args_struct[])283 void bs_args_set_defaults(bs_args_struct_t args_struct[])
284 {
285 int count = 0;
286
287 while (args_struct[count].option != NULL) {
288
289 if (args_struct[count].dest == NULL) {
290 count++;
291 continue;
292 }
293
294 switch ( args_struct[count].type ){
295 case 0: //does not have an storage
296 break;
297 case 'b': //Boolean:
298 *(bool*)args_struct[count].dest = false;
299 break;
300 case 's': //Ponter to string:
301 *(char**)args_struct[count].dest = NULL;
302 break;
303 case 'u': //unsigned int
304 *(unsigned int*)args_struct[count].dest = UINT_MAX;
305 break;
306 case 'U': //64 bit unsigned int
307 *(uint64_t*)args_struct[count].dest = UINT64_MAX;
308 break;
309 case 'i': //integer
310 *(int*)args_struct[count].dest = INT_MAX;
311 break;
312 case 'I':
313 *(int64_t *)args_struct[count].dest = INT64_MAX;
314 break;
315 case 'd':
316 case 'f': //double
317 *(double*)args_struct[count].dest = NAN;
318 break;
319 case 'l': //list: We don't initialize it (theay are meant to be always manual)
320 break;
321 default:
322 bs_trace_error_line("Coding error: type %c not known\n", args_struct[count].type);
323 break;
324 }
325 count++;
326 }
327 }
328
329 /**
330 * For the help messages:
331 * Generate a string containing how the option described by <args_s_el>
332 * should be used
333 *
334 * The string is saved in <buf> which has been allocated <size> bytes by the
335 * caller
336 */
bs_gen_switch_syntax(char * buf,size_t size,bs_args_struct_t * args_s_el)337 static void bs_gen_switch_syntax(char *buf, size_t size,
338 bs_args_struct_t *args_s_el){
339 int ret = 0;
340
341 if (size <= 0) {
342 return;
343 }
344
345 if ( args_s_el->is_mandatory == false ) {
346 *buf++ = '[';
347 size--;
348 }
349
350 if ( args_s_el->is_switch == true ) {
351 ret = snprintf(buf, size, "-%s", args_s_el->option);
352 } else {
353 if ( args_s_el->type != 'l' ){
354 ret = snprintf(buf, size, "-%s=<%s>", args_s_el->option, args_s_el->name);
355 } else {
356 ret = snprintf(buf, size, "-%s <%s>...", args_s_el->option, args_s_el->name);
357 }
358 }
359
360 if (ret < 0) {
361 bs_trace_error_line("Unexpected error in\n");
362 }
363 if (size - ret < 0) {
364 /*
365 * If we run out of space we can just stop,
366 * this is not critical
367 */
368 return;
369 }
370 buf += ret;
371 size -= ret;
372
373 if ( args_s_el->is_mandatory == false ) {
374 snprintf(buf, size,"] ");
375 } else {
376 snprintf(buf, size," ");
377 }
378 }
379
380 /**
381 * Print short list of avaliable switches
382 */
bs_args_print_switches_help(bs_args_struct_t args_struct[])383 void bs_args_print_switches_help(bs_args_struct_t args_struct[])
384 {
385 int count = 0;
386 int printed_in_line = strlen(_HELP_SWITCH) + 2;
387
388 if ( overriden_executable_name ){
389 printed_in_line += strlen(overriden_executable_name);
390 fprintf(stdout, "%s %s ", overriden_executable_name, _HELP_SWITCH);
391 } else {
392 printed_in_line += strlen(executable_name);
393 fprintf(stdout, "%s %s ", executable_name, _HELP_SWITCH);
394 }
395
396 while ( args_struct[count].option != NULL) {
397 char stringy[_MAX_STRINGY_LEN];
398
399 bs_gen_switch_syntax(stringy, _MAX_STRINGY_LEN,
400 &args_struct[count]);
401
402 if ( printed_in_line + strlen(stringy) > _MAX_LINE_WIDTH ){
403 fprintf(stdout,"\n");
404 printed_in_line = 0;
405 }
406
407 fprintf(stdout,"%s", stringy);
408 printed_in_line += strlen(stringy);
409 count++;
410 }
411
412 fprintf(stdout,"\n");
413 }
414
415 /**
416 * Print the long help message of the program
417 */
bs_args_print_long_help(bs_args_struct_t args_struct[])418 void bs_args_print_long_help(bs_args_struct_t args_struct[]){
419 int ret;
420 int count = 0;
421 int printed_in_line = 0;
422 char stringy[_MAX_STRINGY_LEN];
423
424 bs_args_print_switches_help(args_struct);
425
426 fprintf(stdout, "\n %-*s:%s\n", _LONG_HELP_ALIGN-1,
427 _HELP_SWITCH, _HELP_DESCR);
428
429 while (args_struct[count].option != NULL) {
430 int printed_right;
431 char *toprint;
432 int total_to_print;
433
434 bs_gen_switch_syntax(stringy, _MAX_STRINGY_LEN, &args_struct[count]);
435
436 ret = fprintf(stdout, " %-*s:",_LONG_HELP_ALIGN-1, stringy);
437 printed_in_line = ret;
438 printed_right = 0;
439 toprint = args_struct[count].descript;
440 total_to_print = strlen(toprint);
441 ret = fprintf(stdout, "%.*s\n",
442 _MAX_LINE_WIDTH - printed_in_line,
443 &toprint[printed_right]);
444 printed_right += ret - 1;
445
446 while (printed_right < total_to_print) {
447 fprintf(stdout, "%*s", _LONG_HELP_ALIGN, "");
448 ret = fprintf(stdout, "%.*s\n",
449 _MAX_LINE_WIDTH - _LONG_HELP_ALIGN,
450 &toprint[printed_right]);
451 printed_right += ret - 1;
452 }
453 count++;
454 }
455 post_help();
456 }
457
458 /**
459 * Try to find if one argument is in the list (and it is not manual)
460 * if it does, try to parse it and set dest accordingly, and return true
461 * if it is not found, return false
462 */
bs_args_parse_one_arg(char * argv,bs_args_struct_t args_struct[])463 bool bs_args_parse_one_arg(char *argv, bs_args_struct_t args_struct[]){
464 bool found = false;
465 int offset;
466 if ( bs_is_help(argv) ) {
467 bs_args_print_long_help(args_struct);
468 bs_trace_silent_exit(0);
469 }
470
471 int count = 0;
472 while ( args_struct[count].option != NULL ){
473 if ( ( !args_struct[count].manual ) &&
474 ( offset = bs_is_option(argv, args_struct[count].option , !args_struct[count].is_switch) ) ){
475
476 if ( args_struct[count].is_switch ){
477 if ( args_struct[count].type == 'b' ){
478 if ( args_struct[count].dest != NULL ) {
479 *(bool*)args_struct[count].dest = true;
480 bs_trace_raw(9,"%s%s set\n", trace_prefix, args_struct[count].name);
481 }
482 } else {
483 bs_trace_error_line("Programming error: I only know how to automatically read boolean switches\n");
484 }
485
486 } else { //if not switch
487 if ( args_struct[count].dest != NULL ){
488 bs_read_optionparam(&argv[offset],
489 args_struct[count].dest,
490 args_struct[count].type,
491 args_struct[count].name);
492 } else if ( args_struct[count].call_when_found == NULL ) {
493 bs_trace_warning_line("Programming error(?): while processign command line (%s): both the destination pointer and the callback are NULL but the type is not manual\n", argv);
494 }
495 }
496 if ( args_struct[count].call_when_found ){
497 args_struct[count].call_when_found(argv, offset);
498 }
499 found = true;
500 break;
501 } //not manual and matches
502 count++;
503 }
504 return found;
505 }
506
507 /**
508 * Parse *all* provided arguments
509 * Note that normally argv[0] is the program name, so you normally want to call bs_args_parse_cmd_line() instead
510 */
bs_args_parse_all_cmd_line(int argc,char * argv[],bs_args_struct_t args_struct[])511 void bs_args_parse_all_cmd_line(int argc, char *argv[], bs_args_struct_t args_struct[]){
512 int i;
513 bool found = false;
514 for (i=0; i<argc; i++){
515 found = bs_args_parse_one_arg(argv[i], args_struct);
516
517 if ( !found ){
518 bs_args_print_switches_help(args_struct);
519 bs_trace_error_line("%sUnknown command line switch '%s'\n",trace_prefix,argv[i]);
520 }
521 }
522 }
523
524 /**
525 * Try to parse all command line arguments of this program
526 * Note that this loop cannot handle manual or list types
527 * (in that case build your own loop calling bs_args_parse_one_arg directly)
528 */
bs_args_parse_cmd_line(int argc,char * argv[],bs_args_struct_t args_struct[])529 void bs_args_parse_cmd_line(int argc, char *argv[], bs_args_struct_t args_struct[]){
530 bs_args_parse_all_cmd_line(argc-1, argv+1, args_struct); //Skip the program name
531 }
532
bs_args_override_exe_name(char * name)533 void bs_args_override_exe_name(char *name){
534 overriden_executable_name = name;
535 }
536
bs_args_set_trace_prefix(char * name)537 void bs_args_set_trace_prefix(char *name){
538 trace_prefix = name;
539 }
540