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 'b':
257       bs_trace_raw(9,"%s%s set to %d\n", trace_prefix, long_d, *((bool*)dest));
258       break;
259     case 's':
260       bs_trace_raw(9,"%s%s set to %s\n", trace_prefix, long_d, *((char**)dest));
261       break;
262     case 'u':
263       bs_trace_raw(9,"%s%s set to %u\n", trace_prefix, long_d, *((uint32_t*)dest));
264       break;
265     case 'U':
266       bs_trace_raw(9,"%s%s set to %"PRIu64"\n", trace_prefix, long_d, *((uint64_t*)dest));
267       break;
268     case 'i':
269       bs_trace_raw(9,"%s%s set to %i\n", trace_prefix, long_d, *((int32_t*)dest));
270       break;
271     case 'I':
272       bs_trace_raw(9,"%s%s set to %"PRIi64"\n", trace_prefix, long_d, *((int64_t*)dest));
273       break;
274     case 'f':
275     case 'd':
276       bs_trace_raw(9,"%s%s set to %le\n", trace_prefix, long_d, *((double*)dest));
277       break;
278     default:
279       break;
280   }
281 }
282 
283 /**
284  * Initialize existing dest* to defaults based on type
285  */
bs_args_set_defaults(bs_args_struct_t args_struct[])286 void bs_args_set_defaults(bs_args_struct_t args_struct[])
287 {
288   int count = 0;
289 
290   while (args_struct[count].option != NULL) {
291 
292     if (args_struct[count].dest == NULL) {
293       count++;
294       continue;
295     }
296 
297     switch ( args_struct[count].type ){
298     case 0: //does not have an storage
299       break;
300     case 'b': //Boolean:
301       *(bool*)args_struct[count].dest = false;
302       break;
303     case 's': //Ponter to string:
304       *(char**)args_struct[count].dest = NULL;
305       break;
306     case 'u': //unsigned int
307       *(unsigned int*)args_struct[count].dest = UINT_MAX;
308       break;
309     case 'U': //64 bit unsigned int
310       *(uint64_t*)args_struct[count].dest = UINT64_MAX;
311       break;
312     case 'i': //integer
313       *(int*)args_struct[count].dest = INT_MAX;
314       break;
315     case 'I':
316       *(int64_t *)args_struct[count].dest = INT64_MAX;
317       break;
318     case 'd':
319     case 'f': //double
320       *(double*)args_struct[count].dest = NAN;
321       break;
322     case 'l': //list: We don't initialize it (theay are meant to be always manual)
323       break;
324     default:
325       bs_trace_error_line("Coding error: type %c not known\n", args_struct[count].type);
326       break;
327     }
328     count++;
329   }
330 }
331 
332 /**
333  * For the help messages:
334  * Generate a string containing how the option described by <args_s_el>
335  * should be used
336  *
337  * The string is saved in <buf> which has been allocated <size> bytes by the
338  * caller
339  */
bs_gen_switch_syntax(char * buf,size_t size,bs_args_struct_t * args_s_el)340 static void bs_gen_switch_syntax(char *buf, size_t size,
341                                  bs_args_struct_t *args_s_el){
342   int ret = 0;
343 
344   if (size <= 0) {
345     return;
346   }
347 
348   if ( args_s_el->is_mandatory == false ) {
349     *buf++ = '[';
350     size--;
351   }
352 
353   if ( args_s_el->is_switch == true ) {
354     ret = snprintf(buf, size, "-%s", args_s_el->option);
355   } else {
356     if ( args_s_el->type != 'l' ){
357       ret = snprintf(buf, size, "-%s=<%s>", args_s_el->option, args_s_el->name);
358     } else {
359       ret = snprintf(buf, size, "-%s <%s>...", args_s_el->option, args_s_el->name);
360     }
361   }
362 
363   if (ret < 0) {
364     bs_trace_error_line("Unexpected error in\n");
365   }
366   if (size - ret < 0) {
367     /*
368      * If we run out of space we can just stop,
369      * this is not critical
370      */
371     return;
372   }
373   buf += ret;
374   size -= ret;
375 
376   if ( args_s_el->is_mandatory == false ) {
377     snprintf(buf, size,"] ");
378   } else {
379     snprintf(buf, size," ");
380   }
381 }
382 
383 /**
384  * Print short list of avaliable switches
385  */
bs_args_print_switches_help(bs_args_struct_t args_struct[])386 void bs_args_print_switches_help(bs_args_struct_t args_struct[])
387 {
388   int count = 0;
389   int printed_in_line = strlen(_HELP_SWITCH) + 2;
390 
391   if ( overriden_executable_name ){
392     printed_in_line += strlen(overriden_executable_name);
393     fprintf(stdout, "%s %s ", overriden_executable_name, _HELP_SWITCH);
394   } else {
395     printed_in_line += strlen(executable_name);
396     fprintf(stdout, "%s %s ", executable_name, _HELP_SWITCH);
397   }
398 
399   while ( args_struct[count].option != NULL) {
400     char stringy[_MAX_STRINGY_LEN];
401 
402     bs_gen_switch_syntax(stringy, _MAX_STRINGY_LEN,
403                          &args_struct[count]);
404 
405     if ( printed_in_line + strlen(stringy) > _MAX_LINE_WIDTH ){
406       fprintf(stdout,"\n");
407       printed_in_line = 0;
408     }
409 
410     fprintf(stdout,"%s", stringy);
411     printed_in_line += strlen(stringy);
412     count++;
413   }
414 
415   fprintf(stdout,"\n");
416 }
417 
418 /**
419  * Print the long help message of the program
420  */
bs_args_print_long_help(bs_args_struct_t args_struct[])421 void bs_args_print_long_help(bs_args_struct_t args_struct[]){
422   int ret;
423   int count = 0;
424   int printed_in_line = 0;
425   char stringy[_MAX_STRINGY_LEN];
426 
427   bs_args_print_switches_help(args_struct);
428 
429   fprintf(stdout, "\n %-*s:%s\n", _LONG_HELP_ALIGN-1,
430     _HELP_SWITCH, _HELP_DESCR);
431 
432   while (args_struct[count].option != NULL) {
433     int printed_right;
434     char *toprint;
435     int total_to_print;
436 
437     bs_gen_switch_syntax(stringy, _MAX_STRINGY_LEN, &args_struct[count]);
438 
439     ret = fprintf(stdout, " %-*s:",_LONG_HELP_ALIGN-1, stringy);
440     printed_in_line = ret;
441     printed_right = 0;
442     toprint = args_struct[count].descript;
443     total_to_print = strlen(toprint);
444     ret = fprintf(stdout, "%.*s\n",
445                   _MAX_LINE_WIDTH - printed_in_line,
446                   &toprint[printed_right]);
447     printed_right += ret - 1;
448 
449     while (printed_right < total_to_print) {
450       fprintf(stdout, "%*s", _LONG_HELP_ALIGN, "");
451       ret = fprintf(stdout, "%.*s\n",
452               _MAX_LINE_WIDTH - _LONG_HELP_ALIGN,
453               &toprint[printed_right]);
454       printed_right += ret - 1;
455     }
456     count++;
457   }
458   post_help();
459 }
460 
461 /**
462  * Try to find if one argument is in the list (and it is not manual)
463  * if it does, try to parse it and set dest accordingly, and return true
464  * if it is not found, return false
465  */
bs_args_parse_one_arg(char * argv,bs_args_struct_t args_struct[])466 bool bs_args_parse_one_arg(char *argv, bs_args_struct_t args_struct[]){
467   bool found = false;
468   int offset;
469   if ( bs_is_help(argv) ) {
470     bs_args_print_long_help(args_struct);
471     bs_trace_silent_exit(0);
472   }
473 
474   int count = 0;
475   while ( args_struct[count].option != NULL ){
476     if ( ( !args_struct[count].manual ) &&
477          ( offset = bs_is_option(argv, args_struct[count].option , !args_struct[count].is_switch) ) ){
478 
479       if ( args_struct[count].is_switch ){
480         if ( args_struct[count].type == 'b' ){
481           if ( args_struct[count].dest != NULL ) {
482             *(bool*)args_struct[count].dest = true;
483             bs_trace_raw(9,"%s%s set\n", trace_prefix, args_struct[count].option);
484           }
485         } else {
486           bs_trace_error_line("Programming error: I only know how to automatically read boolean switches\n");
487         }
488 
489       } else { //if not switch
490         if ( args_struct[count].dest != NULL ){
491           bs_read_optionparam(&argv[offset],
492               args_struct[count].dest,
493               args_struct[count].type,
494               args_struct[count].name);
495         } else if ( args_struct[count].call_when_found == NULL ) {
496           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);
497         }
498       }
499       if ( args_struct[count].call_when_found ){
500         args_struct[count].call_when_found(argv, offset);
501       }
502       found = true;
503       break;
504     } //not manual and matches
505     count++;
506   }
507   return found;
508 }
509 
510 /**
511  * Parse *all* provided arguments
512  * Note that normally argv[0] is the program name, so you normally want to call bs_args_parse_cmd_line() instead
513  */
bs_args_parse_all_cmd_line(int argc,char * argv[],bs_args_struct_t args_struct[])514 void bs_args_parse_all_cmd_line(int argc, char *argv[], bs_args_struct_t args_struct[]){
515   int i;
516   bool found = false;
517   for (i=0; i<argc; i++){
518     found = bs_args_parse_one_arg(argv[i], args_struct);
519 
520     if ( !found ){
521       bs_args_print_switches_help(args_struct);
522       bs_trace_error_line("%sUnknown command line switch '%s'\n",trace_prefix,argv[i]);
523     }
524   }
525 }
526 
527 /**
528  * Try to parse all command line arguments of this program
529  * Note that this loop cannot handle manual or list types
530  * (in that case build your own loop calling bs_args_parse_one_arg directly)
531  */
bs_args_parse_cmd_line(int argc,char * argv[],bs_args_struct_t args_struct[])532 void bs_args_parse_cmd_line(int argc, char *argv[], bs_args_struct_t args_struct[]){
533   bs_args_parse_all_cmd_line(argc-1, argv+1, args_struct); //Skip the program name
534 }
535 
bs_args_override_exe_name(char * name)536 void bs_args_override_exe_name(char *name){
537   overriden_executable_name = name;
538 }
539 
bs_args_set_trace_prefix(char * name)540 void bs_args_set_trace_prefix(char *name){
541   trace_prefix = name;
542 }
543