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