1 #ifndef _WIN32
2 #include <sys/ioctl.h>
3 #include <termios.h>
4 #else
5 #include <windows.h>
6 #endif
7 
8 #include <string.h>
9 #include <stdlib.h>
10 #include <stdio.h>
11 #include <limits.h>
12 #include <errno.h>
13 #include <ctype.h>
14 
15 #include "cliopts.h"
16 
17 
18 enum {
19     CLIOPTS_ERR_SUCCESS,
20     CLIOPTS_ERR_NEED_ARG,
21     CLIOPTS_ERR_ISSWITCH,
22     CLIOPTS_ERR_BADOPT,
23     CLIOPTS_ERR_BAD_VALUE,
24     CLIOPTS_ERR_UNRECOGNIZED
25 };
26 
27 struct cliopts_priv {
28     cliopts_entry *entries;
29 
30     cliopts_entry *prev;
31     cliopts_entry *current;
32     struct cliopts_extra_settings *settings;
33 
34     char *errstr;
35     int errnum;
36 
37     int argsplit;
38     int wanted;
39 
40     char current_key[4096];
41     char current_value[4096];
42 };
43 
44 enum {
45     WANT_OPTION,
46     WANT_VALUE,
47 
48     MODE_ERROR,
49     MODE_RESTARGS,
50     MODE_HELP
51 };
52 
53 #define INDENT "  "
54 
55 #ifdef CLIOPTS_DEBUG
56 
57 #define cliopt_debug(...) \
58     fprintf(stderr, "(%s:%d) ", __func__, __LINE__); \
59     fprintf(stderr, __VA_ARGS__); \
60     fprintf(stderr, "\n")
61 
62 #else
63 /** variadic macros not c89 */
cliopt_debug(void *unused, ...)64 static void cliopt_debug(void *unused, ...) { (void)unused; }
65 #endif /* CLIOPT_DEBUG */
66 
67 static int
68 parse_option(struct cliopts_priv *ctx, const char *key);
69 
70 
71 static int
72 parse_value(struct cliopts_priv *ctx, const char *value);
73 
74 static void
add_list_value(const char *src, size_t nsrc, cliopts_list *l)75 add_list_value(const char *src, size_t nsrc, cliopts_list *l)
76 {
77     char *cp = malloc(nsrc + 1);
78 
79     if (!l->nalloc) {
80         l->nalloc = 2;
81         l->values = malloc(l->nalloc * sizeof(*l->values));
82     } else {
83         l->nalloc *= 1.5;
84         l->values = realloc(l->values, sizeof(*l->values) * l->nalloc);
85     }
86 
87     l->values[l->nvalues++] = cp;
88     cp[nsrc] = '\0';
89     memcpy(cp, src, nsrc);
90 }
91 
92 CLIOPTS_API
93 void
cliopts_list_clear(cliopts_list *l)94 cliopts_list_clear(cliopts_list *l)
95 {
96     size_t ii;
97     for (ii = 0; ii < l->nvalues; ii++) {
98         free(l->values[ii]);
99     }
100     free(l->values);
101     l->values = NULL;
102     l->nvalues = 0;
103     l->nalloc = 0;
104 }
105 
106 /**
107  * Various extraction/conversion functions for numerics
108  */
109 
110 #define _VERIFY_INT_COMMON(m1, m2) \
111     if (value == m1 || value > m2) { *errp = "Value too large"; return -1; } \
112     if (*endptr != '\0') { *errp = "Trailing garbage"; return -1; }
113 
114 static int
extract_int(const char *s, void *dest, char **errp)115 extract_int(const char *s, void *dest, char **errp)
116 {
117     long int value;
118     char *endptr = NULL;
119     value = strtol(s, &endptr, 10);
120     _VERIFY_INT_COMMON(LONG_MAX, INT_MAX)
121     *(int*)dest = value;
122     return 0;
123 }
124 
125 static int
extract_uint(const char *s, void *dest, char **errp)126 extract_uint(const char *s, void *dest, char **errp)
127 {
128     unsigned long int value;
129     char *endptr = NULL;
130     value = strtoul(s, &endptr, 10);
131     _VERIFY_INT_COMMON(ULONG_MAX, UINT_MAX)
132     *(unsigned int*)dest = value;
133     return 0;
134 }
135 
136 static int
extract_hex(const char *s, void *dest, char **errp)137 extract_hex(const char *s, void *dest, char **errp)
138 {
139     unsigned long value;
140     char *endptr = NULL;
141     value = strtoul(s, &endptr, 16);
142     _VERIFY_INT_COMMON(ULONG_MAX, UINT_MAX);
143     *(unsigned int*)dest = value;
144     return 0;
145 }
146 
147 #undef _VERIFY_INT_COMMON
148 
149 static int
extract_float(const char *s, void *dest, char **errp)150 extract_float(const char *s, void *dest, char **errp)
151 {
152     char dummy_buf[4096];
153     float value;
154     if (sscanf(s, "%f%s", &value, dummy_buf) != 1) {
155         *errp = "Found trailing garbage";
156         return -1;
157     }
158     *(float*)dest = value;
159     return 0;
160 }
161 
162 typedef int(*cliopts_extractor_func)(const char*, void*, char**);
163 
164 
165 /**
166  * This function tries to extract a single value for an option key.
167  * If it successfully has extracted a value, it returns MODE_VALUE.
168  * If the entry takes no arguments, then the current string is a key,
169  * and it will return MODE_OPTION. On error, MODE_ERROR is set, and errp
170  * will point to a string.
171  *
172  * @param entry The current entry
173  * @param value the string which might be a value
174  * @errp a pointer which will be populated with the address of the error, if any
175  *
176  * @return a MODE_* type
177  */
178 static int
parse_value(struct cliopts_priv *ctx, const char *value)179 parse_value(struct cliopts_priv *ctx,
180             const char *value)
181 {
182     cliopts_entry *entry = ctx->current;
183 
184     size_t vlen = strlen(value);
185     cliopts_extractor_func exfn = NULL;
186     int exret;
187     int is_option = 0;
188 
189     cliopt_debug("Called with %s, want=%d", value, ctx->wanted);
190 
191     if (ctx->argsplit) {
192         if (vlen > 2 && strncmp(value, "--", 2) == 0) {
193             is_option = 1;
194         } else if (*value == '-') {
195             is_option = 1;
196         }
197     }
198 
199     if (is_option) {
200         ctx->errstr = "Expected option. Got '-' or '--' prefixed value "
201                         "(use = if this is really a value)";
202         ctx->errnum = CLIOPTS_ERR_NEED_ARG;
203         return MODE_ERROR;
204     }
205 
206     if (entry->ktype == CLIOPTS_ARGT_STRING) {
207         char *vp = malloc(vlen+1);
208         vp[vlen] = 0;
209         strcpy(vp, value);
210         *(char**)entry->dest = vp;
211         return WANT_OPTION;
212     }
213 
214     if (entry->ktype == CLIOPTS_ARGT_LIST) {
215         add_list_value(value, vlen, (cliopts_list *)entry->dest);
216         return WANT_OPTION;
217     }
218 
219     if (entry->ktype == CLIOPTS_ARGT_FLOAT) {
220         exfn = extract_float;
221     } else if (entry->ktype == CLIOPTS_ARGT_HEX) {
222         exfn = extract_hex;
223     } else if (entry->ktype == CLIOPTS_ARGT_INT) {
224         exfn = extract_int;
225     } else if (entry->ktype == CLIOPTS_ARGT_UINT) {
226         exfn = extract_uint;
227     } else {
228         fprintf(stderr, "Unrecognized type %d.\n", entry->ktype);
229         return MODE_ERROR;
230     }
231 
232     exret = exfn(value, entry->dest, &ctx->errstr);
233     if (exret == 0) {
234         return WANT_OPTION;
235     } else {
236         ctx->errnum = CLIOPTS_ERR_BAD_VALUE;
237     }
238 
239     return MODE_ERROR;
240 }
241 
242 /**
243  * Like parse_value, except for keys.
244  *
245  * @param entries all option entries
246  * @param key the current string from argv
247  * @param errp a pointer which will be populated with the address of an error
248  * string
249  *
250  * @param found_entry a pointer to be populated with the relevant entry
251  * structure
252  * @param kp a pointer which will be poplated with the address of the 'sanitized'
253  * key string
254  *
255  * @param valp if the string is actually a key-value pair (i.e. --foo=bar) then
256  * this will be populated with the address of that string
257  *
258  * @return MODE_OPTION if an option was found, MODE_VALUE if the current option
259  * is a value, or MODE_ERROR on error
260  */
261 static int
parse_option(struct cliopts_priv *ctx, const char *key)262 parse_option(struct cliopts_priv *ctx,
263           const char *key)
264 {
265     cliopts_entry *cur = NULL;
266     int prefix_len = 0;
267     unsigned ii = 0;
268     const char *valp = NULL;
269     size_t klen;
270 
271     klen = strlen(key);
272     ctx->errstr = NULL;
273     ctx->prev = ctx->current;
274     ctx->current = NULL;
275 
276     cliopt_debug("Called with %s, want=%d", key, ctx->wanted);
277     if (klen == 0) {
278         ctx->errstr = "Got an empty string";
279         ctx->errnum = CLIOPTS_ERR_BADOPT;
280         return MODE_ERROR;
281     }
282 
283     /**
284      * figure out what type of option it is..
285      * it can either be a -c, --long, or --long=value
286      */
287     while (*key == '-') {
288         key++;
289         prefix_len++;
290         klen--;
291     }
292 
293     for (ii = 0; ii < klen; ii++) {
294         if (key[ii] == '"' || key[ii] == '\'') {
295             ii = klen;
296             break;
297 
298         } else if (key[ii] == '=' && prefix_len == 2) {
299             /* only split on '=' if we're called as '--' */
300             valp = key + (ii + 1);
301             klen = ii;
302             break;
303         }
304     }
305 
306     GT_PARSEOPT:
307     memset(ctx->current_value, 0, sizeof(ctx->current_value));
308     memcpy(ctx->current_key, key, klen);
309     ctx->current_key[ii] = '\0';
310 
311     if (valp) {
312         strcpy(ctx->current_value, valp);
313     }
314 
315     if (prefix_len == 0 || prefix_len > 2) {
316         if (ctx->settings->restargs) {
317             key -= prefix_len;
318             ctx->settings->restargs[ctx->settings->nrestargs++] = key;
319             return WANT_OPTION;
320         } else if (ctx->prev && ctx->prev->ktype == CLIOPTS_ARGT_NONE) {
321             ctx->errstr = "Option does not accept a value";
322             ctx->errnum = CLIOPTS_ERR_ISSWITCH;
323         } else {
324             ctx->errstr = "Options must begin with either '-' or '--'";
325             ctx->errnum = CLIOPTS_ERR_BADOPT;
326         }
327         return MODE_ERROR;
328     }
329 
330     /**
331      * --help or -?
332      */
333 
334     if ( (prefix_len == 1 && *key == '?') ||
335             (prefix_len == 2 && strcmp(key, "help") == 0)) {
336         return MODE_HELP;
337     }
338 
339     /**
340      * Bare --
341      */
342     if (prefix_len == 2 && *key == '\0') {
343         if (ctx->settings->restargs) {
344 
345         }
346         if (ctx->wanted == WANT_VALUE) {
347             ctx->errnum = CLIOPTS_ERR_NEED_ARG;
348             ctx->errstr = "Found bare '--', but value wanted";
349             return MODE_ERROR;
350         }
351 
352         return MODE_RESTARGS;
353     }
354 
355     for (cur = ctx->entries; cur->dest; cur++) {
356         int optlen;
357         if (prefix_len == 1) {
358             if (cur->kshort == ctx->current_key[0]) {
359                 ctx->current = cur;
360                 break;
361             }
362             continue;
363         }
364         /** else, prefix_len is 2 */
365         if (cur->klong == NULL ||
366                 (optlen = strlen(cur->klong) != klen) ||
367                 strncmp(cur->klong, ctx->current_key, klen) != 0) {
368 
369             continue;
370         }
371 
372         ctx->current = cur;
373         break;
374     }
375 
376     if (!ctx->current) {
377         ctx->errstr = "Unknown option";
378         ctx->errnum = CLIOPTS_ERR_UNRECOGNIZED;
379         return MODE_ERROR;
380     }
381 
382     ctx->current->found++;
383     if (ctx->current->ktype != CLIOPTS_ARGT_NONE) {
384         ctx->wanted = WANT_VALUE;
385     }
386 
387     if (ctx->current_value[0]) {
388         /* --foo=bar */
389         if (ctx->current->ktype == CLIOPTS_ARGT_NONE) {
390             ctx->errnum = CLIOPTS_ERR_ISSWITCH;
391             ctx->errstr = "Option takes no arguments";
392             return MODE_ERROR;
393         } else {
394             return parse_value(ctx, ctx->current_value);
395         }
396     }
397 
398     if (ctx->current->ktype == CLIOPTS_ARGT_NONE) {
399         *(char*)ctx->current->dest = 1;
400 
401         if (prefix_len == 1 && klen > 1) {
402             /**
403              * e.g. ls -lsh
404              */
405             klen--;
406             key++;
407 
408             /**
409              * While we can also possibly recurse, this may be a security risk
410              * as it wouldn't take much to cause a deep recursion on the stack
411              * which will cause all sorts of nasties.
412              */
413             goto GT_PARSEOPT;
414         }
415         return WANT_OPTION;
416 
417     } else if (prefix_len == 1 && klen > 1) {
418 
419         /* e.g. patch -p0 */
420         ctx->wanted = WANT_VALUE;
421         return parse_value(ctx, key + 1);
422     }
423     return WANT_VALUE;
424 }
425 
426 static char *
get_option_name(cliopts_entry *entry, char *buf)427 get_option_name(cliopts_entry *entry, char *buf)
428 {
429     /* [-s,--option] */
430     char *bufp = buf;
431     bufp += sprintf(buf, "[");
432     if (entry->kshort) {
433         bufp += sprintf(bufp, "-%c", entry->kshort);
434     }
435     if (entry->klong) {
436         if (entry->kshort) {
437             bufp += sprintf(bufp, ",");
438         }
439         bufp += sprintf(bufp, "--%s", entry->klong);
440     }
441     sprintf(bufp, "]");
442     return buf;
443 }
444 
get_terminal_width(void)445 static int get_terminal_width(void)
446 {
447 #ifndef _WIN32
448     struct winsize max;
449     if (ioctl(0, TIOCGWINSZ, &max) != -1) {
450         return max.ws_col;
451     } else {
452         return 80;
453     }
454 #else
455     CONSOLE_SCREEN_BUFFER_INFO cbsi;
456     GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cbsi);
457     return cbsi.srWindow.Right - cbsi.srWindow.Left;
458 #endif
459 }
460 
461 static char*
format_option_help(cliopts_entry *entry, char *buf, struct cliopts_extra_settings *settings)462 format_option_help(cliopts_entry *entry,
463                    char *buf,
464                    struct cliopts_extra_settings *settings)
465 {
466     char *bufp = buf;
467     if (entry->kshort) {
468         bufp += sprintf(bufp, " -%c ", entry->kshort);
469     }
470 
471 #define _advance_margin(offset) \
472     while(bufp-buf < offset || *bufp) { \
473         if (!*bufp) { \
474             *bufp = ' '; \
475         } \
476         bufp++; \
477     }
478 
479     _advance_margin(4)
480 
481     if (entry->klong) {
482         bufp += sprintf(bufp, " --%s ", entry->klong);
483     }
484 
485     if (entry->vdesc) {
486         bufp += sprintf(bufp, " <%s> ", entry->vdesc);
487     }
488 
489     _advance_margin(35)
490 #undef _advance_margin
491 
492     if (entry->help) {
493         unsigned initial_indent = bufp - buf + 1;
494         int curpos = initial_indent;
495         const char *help_p = entry->help;
496 
497         for (; *help_p; help_p++, curpos++, bufp++) {
498 
499             if (curpos >= settings->line_max) {
500                 unsigned ii;
501                 if (!isspace(*help_p) && !isspace(*(help_p-1))) {
502                     *bufp = '-';
503                     bufp++;
504                 }
505                 *bufp = '\n';
506                 bufp++;
507 
508                 for (ii = 0; ii < initial_indent+1; ii++, bufp++) {
509                     *bufp = ' ';
510                 }
511 
512                 curpos = initial_indent;
513                 if (isspace(*help_p)) {
514                     bufp--;
515                     continue;
516                 }
517             }
518             *bufp = *help_p;
519         }
520     }
521 
522     *bufp = '\0';
523     return buf;
524 }
525 
526 static void
print_help(struct cliopts_priv *ctx, struct cliopts_extra_settings *settings)527 print_help(struct cliopts_priv *ctx, struct cliopts_extra_settings *settings)
528 {
529     cliopts_entry *cur;
530     cliopts_entry helpent = { 0 };
531     char helpbuf[1024] = { 0 };
532 
533     helpent.klong = "help";
534     helpent.kshort = '?';
535     helpent.help = "this message";
536 
537     fprintf(stderr, "Usage:\n");
538     fprintf(stderr, "  %s %s\n\n", settings->progname, settings->argstring);
539     if (settings->shortdesc) {
540         fprintf(stderr, "%s", settings->shortdesc);
541         fprintf(stderr, "\n");
542     }
543 
544 
545     for (cur = ctx->entries; cur->dest; cur++) {
546         if (cur->hidden) {
547             continue;
548         }
549 
550         memset(helpbuf, 0, sizeof(helpbuf));
551         format_option_help(cur, helpbuf, settings);
552         fprintf(stderr, INDENT "%s", helpbuf);
553 
554 
555         if (settings->show_defaults) {
556             fprintf(stderr, " [Default=");
557 
558             switch (cur->ktype) {
559             case CLIOPTS_ARGT_STRING:
560                 fprintf(stderr, "'%s'", (cur->dest && *(char **)cur->dest) ?
561                         *(char**)cur->dest : "");
562                 break;
563             case CLIOPTS_ARGT_LIST: {
564                 size_t ii;
565                 cliopts_list *l = (cliopts_list *)cur->dest;
566                 for (ii = 0; ii < l->nvalues; ii++) {
567                     fprintf(stderr, "'%s'", l->values[ii]);
568                     if (ii != l->nvalues-1) {
569                         fprintf(stderr, ", ");
570                     }
571                 }
572                 break;
573             }
574             case CLIOPTS_ARGT_FLOAT:
575                 fprintf(stderr, "%0.2f", *(float*)cur->dest);
576                 break;
577             case CLIOPTS_ARGT_HEX:
578                 fprintf(stderr, "0x%x", *(int*)cur->dest);
579                 break;
580             case CLIOPTS_ARGT_INT:
581                 fprintf(stderr, "%d", *(int*)cur->dest);
582                 break;
583             case CLIOPTS_ARGT_UINT:
584                 fprintf(stderr, "%u", *(unsigned int*)cur->dest);
585                 break;
586             case CLIOPTS_ARGT_NONE:
587                 fprintf(stderr, "%s", *(int*)cur->dest ? "TRUE" : "FALSE");
588                 break;
589             default:
590                 fprintf(stderr, "Unknown option type '%d'", (int)cur->ktype);
591                 break;
592             }
593             fprintf(stderr, "]");
594         }
595         fprintf(stderr, "\n");
596     }
597     memset(helpbuf, 0, sizeof(helpbuf));
598     fprintf(stderr, INDENT "%s\n",
599             format_option_help(&helpent, helpbuf, settings));
600 
601 }
602 
603 static void
dump_error(struct cliopts_priv *ctx)604 dump_error(struct cliopts_priv *ctx)
605 {
606     fprintf(stderr, "Couldn't parse options: %s\n", ctx->errstr);
607     if (ctx->errnum == CLIOPTS_ERR_BADOPT) {
608         fprintf(stderr, "Bad option: %s", ctx->current_key);
609     } else if (ctx->errnum == CLIOPTS_ERR_BAD_VALUE) {
610         fprintf(stderr, "Bad value '%s' for %s",
611                 ctx->current_value,
612                 ctx->current_key);
613     } else if (ctx->errnum == CLIOPTS_ERR_UNRECOGNIZED) {
614         fprintf(stderr, "No such option: %s", ctx->current_key);
615     } else if (ctx->errnum == CLIOPTS_ERR_ISSWITCH) {
616         char optbuf[64] = { 0 };
617         fprintf(stderr, "Option %s takes no arguments",
618                 get_option_name(ctx->prev, optbuf));
619     }
620     fprintf(stderr, "\n");
621 
622 }
623 
624 CLIOPTS_API
625 int
cliopts_parse_options(cliopts_entry *entries, int argc, char **argv, int *lastidx, struct cliopts_extra_settings *settings)626 cliopts_parse_options(cliopts_entry *entries,
627                       int argc,
628                       char **argv,
629                       int *lastidx,
630                       struct cliopts_extra_settings *settings)
631 {
632     /**
633      * Now let's build ourselves a
634      */
635     int curmode;
636     int ii, ret = 0, lastidx_s = 0;
637     struct cliopts_priv ctx = { 0 };
638     struct cliopts_extra_settings default_settings = { 0 };
639 
640     if (!lastidx) {
641         lastidx = &lastidx_s;
642     }
643 
644     ctx.entries = entries;
645 
646     if (!settings) {
647         settings = &default_settings;
648         settings->show_defaults = 1;
649     }
650     if (!settings->progname) {
651         settings->progname = argv[0];
652     }
653     if (!settings->argstring) {
654         settings->argstring = "[OPTIONS...]";
655     }
656     settings->nrestargs = 0;
657 
658     if (!settings->line_max) {
659         settings->line_max = get_terminal_width() - 3;
660     }
661 
662     ii = (settings->argv_noskip) ? 0 : 1;
663 
664     if (ii >= argc) {
665         *lastidx = 0;
666         ret = 0;
667         goto GT_CHECK_REQ;
668         return 0;
669     }
670 
671     curmode = WANT_OPTION;
672     ctx.wanted = curmode;
673     ctx.settings = settings;
674 
675     for (; ii < argc; ii++) {
676 
677         if (curmode == WANT_OPTION) {
678             curmode = parse_option(&ctx, argv[ii]);
679         } else if (curmode == WANT_VALUE) {
680             curmode = parse_value(&ctx, argv[ii]);
681         }
682 
683         if (curmode == MODE_ERROR) {
684             if (settings->error_nohelp == 0) {
685                 dump_error(&ctx);
686             }
687             ret = -1;
688             break;
689         } else if (curmode == MODE_HELP) {
690             if (settings->help_noflag) {
691                 /* ignore it ? */
692                 continue;
693             }
694 
695             print_help(&ctx, settings);
696             exit(0);
697 
698         } else if (curmode == MODE_RESTARGS) {
699             ii++;
700             break;
701         } else {
702             ctx.wanted = curmode;
703         }
704     }
705 
706     *lastidx = ii;
707 
708     if (curmode == WANT_VALUE) {
709         ret = -1;
710 
711         if (settings->error_nohelp == 0) {
712             fprintf(stderr,
713                     "Option %s requires argument\n",
714                     ctx.current_key);
715         }
716         goto GT_RET;
717     }
718 
719     GT_CHECK_REQ:
720     {
721         cliopts_entry *cur_ent;
722         for (cur_ent = entries; cur_ent->dest; cur_ent++) {
723             char entbuf[128] = { 0 };
724             if (cur_ent->found || cur_ent->required == 0) {
725                 continue;
726             }
727 
728             ret = -1;
729             if (settings->error_nohelp) {
730                 goto GT_RET;
731             }
732 
733             fprintf(stderr, "Required option %s missing\n",
734                     get_option_name(cur_ent, entbuf));
735         }
736     }
737 
738     GT_RET:
739     if (ret == -1) {
740         if (settings->error_nohelp == 0) {
741             print_help(&ctx, settings);
742         }
743         if (settings->error_noexit == 0) {
744             exit(EXIT_FAILURE);
745         }
746     }
747     return ret;
748 }
749