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