xref: /5.5.2/subjson/contrib/cliopts/cliopts.c (revision e83c5086)
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
18enum {
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
27struct 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
44enum {
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 */
64static void cliopt_debug(void *unused, ...) { (void)unused; }
65#endif /* CLIOPT_DEBUG */
66
67static int
68parse_option(struct cliopts_priv *ctx, const char *key);
69
70
71static int
72parse_value(struct cliopts_priv *ctx, const char *value);
73
74static void
75add_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
92CLIOPTS_API
93void
94cliopts_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
114static int
115extract_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
125static int
126extract_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
136static int
137extract_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
149static int
150extract_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
162typedef 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 */
178static int
179parse_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. Abort.\n", entry->ktype);
229    }
230
231    exret = exfn(value, entry->dest, &ctx->errstr);
232    if (exret == 0) {
233        return WANT_OPTION;
234    } else {
235        ctx->errnum = CLIOPTS_ERR_BAD_VALUE;
236    }
237
238    return MODE_ERROR;
239}
240
241/**
242 * Like parse_value, except for keys.
243 *
244 * @param entries all option entries
245 * @param key the current string from argv
246 * @param errp a pointer which will be populated with the address of an error
247 * string
248 *
249 * @param found_entry a pointer to be populated with the relevant entry
250 * structure
251 * @param kp a pointer which will be poplated with the address of the 'sanitized'
252 * key string
253 *
254 * @param valp if the string is actually a key-value pair (i.e. --foo=bar) then
255 * this will be populated with the address of that string
256 *
257 * @return MODE_OPTION if an option was found, MODE_VALUE if the current option
258 * is a value, or MODE_ERROR on error
259 */
260static int
261parse_option(struct cliopts_priv *ctx,
262          const char *key)
263{
264    cliopts_entry *cur = NULL;
265    int prefix_len = 0;
266    unsigned ii = 0;
267    const char *valp = NULL;
268    size_t klen;
269
270    klen = strlen(key);
271    ctx->errstr = NULL;
272    ctx->prev = ctx->current;
273    ctx->current = NULL;
274
275    cliopt_debug("Called with %s, want=%d", key, ctx->wanted);
276    if (klen == 0) {
277        ctx->errstr = "Got an empty string";
278        ctx->errnum = CLIOPTS_ERR_BADOPT;
279        return MODE_ERROR;
280    }
281
282    /**
283     * figure out what type of option it is..
284     * it can either be a -c, --long, or --long=value
285     */
286    while (*key == '-') {
287        key++;
288        prefix_len++;
289        klen--;
290    }
291
292    for (ii = 0; ii < klen; ii++) {
293        if (key[ii] == '"' || key[ii] == '\'') {
294            ii = klen;
295            break;
296
297        } else if (key[ii] == '=' && prefix_len == 2) {
298            /* only split on '=' if we're called as '--' */
299            valp = key + (ii + 1);
300            klen = ii;
301            break;
302        }
303    }
304
305    GT_PARSEOPT:
306    memset(ctx->current_value, 0, sizeof(ctx->current_value));
307    memcpy(ctx->current_key, key, klen);
308    ctx->current_key[ii] = '\0';
309
310    if (valp) {
311        strcpy(ctx->current_value, valp);
312    }
313
314    if (prefix_len == 0 || prefix_len > 2) {
315        if (ctx->settings->restargs) {
316            key -= prefix_len;
317            ctx->settings->restargs[ctx->settings->nrestargs++] = key;
318            return WANT_OPTION;
319        } else if (ctx->prev && ctx->prev->ktype == CLIOPTS_ARGT_NONE) {
320            ctx->errstr = "Option does not accept a value";
321            ctx->errnum = CLIOPTS_ERR_ISSWITCH;
322        } else {
323            ctx->errstr = "Options must begin with either '-' or '--'";
324            ctx->errnum = CLIOPTS_ERR_BADOPT;
325        }
326        return MODE_ERROR;
327    }
328
329    /**
330     * --help or -?
331     */
332
333    if ( (prefix_len == 1 && *key == '?') ||
334            (prefix_len == 2 && strcmp(key, "help") == 0)) {
335        return MODE_HELP;
336    }
337
338    /**
339     * Bare --
340     */
341    if (prefix_len == 2 && *key == '\0') {
342        if (ctx->settings->restargs) {
343
344        }
345        if (ctx->wanted == WANT_VALUE) {
346            ctx->errnum = CLIOPTS_ERR_NEED_ARG;
347            ctx->errstr = "Found bare '--', but value wanted";
348            return MODE_ERROR;
349        }
350
351        return MODE_RESTARGS;
352    }
353
354    for (cur = ctx->entries; cur->dest; cur++) {
355        int optlen;
356        if (prefix_len == 1) {
357            if (cur->kshort == ctx->current_key[0]) {
358                ctx->current = cur;
359                break;
360            }
361            continue;
362        }
363        /** else, prefix_len is 2 */
364        if (cur->klong == NULL ||
365                (optlen = strlen(cur->klong) != klen) ||
366                strncmp(cur->klong, ctx->current_key, klen) != 0) {
367
368            continue;
369        }
370
371        ctx->current = cur;
372        break;
373    }
374
375    if (!ctx->current) {
376        ctx->errstr = "Unknown option";
377        ctx->errnum = CLIOPTS_ERR_UNRECOGNIZED;
378        return MODE_ERROR;
379    }
380
381    ctx->current->found++;
382    if (ctx->current->ktype != CLIOPTS_ARGT_NONE) {
383        ctx->wanted = WANT_VALUE;
384    }
385
386    if (ctx->current_value[0]) {
387        /* --foo=bar */
388        if (ctx->current->ktype == CLIOPTS_ARGT_NONE) {
389            ctx->errnum = CLIOPTS_ERR_ISSWITCH;
390            ctx->errstr = "Option takes no arguments";
391            return MODE_ERROR;
392        } else {
393            return parse_value(ctx, ctx->current_value);
394        }
395    }
396
397    if (ctx->current->ktype == CLIOPTS_ARGT_NONE) {
398        *(char*)ctx->current->dest = 1;
399
400        if (prefix_len == 1 && klen > 1) {
401            /**
402             * e.g. ls -lsh
403             */
404            klen--;
405            key++;
406
407            /**
408             * While we can also possibly recurse, this may be a security risk
409             * as it wouldn't take much to cause a deep recursion on the stack
410             * which will cause all sorts of nasties.
411             */
412            goto GT_PARSEOPT;
413        }
414        return WANT_OPTION;
415
416    } else if (prefix_len == 1 && klen > 1) {
417
418        /* e.g. patch -p0 */
419        ctx->wanted = WANT_VALUE;
420        return parse_value(ctx, key + 1);
421    }
422    return WANT_VALUE;
423}
424
425static char *
426get_option_name(cliopts_entry *entry, char *buf)
427{
428    /* [-s,--option] */
429    char *bufp = buf;
430    bufp += sprintf(buf, "[");
431    if (entry->kshort) {
432        bufp += sprintf(bufp, "-%c", entry->kshort);
433    }
434    if (entry->klong) {
435        if (entry->kshort) {
436            bufp += sprintf(bufp, ",");
437        }
438        bufp += sprintf(bufp, "--%s", entry->klong);
439    }
440    sprintf(bufp, "]");
441    return buf;
442}
443
444static int get_terminal_width(void)
445{
446#ifndef _WIN32
447    struct winsize max;
448    if (ioctl(0, TIOCGWINSZ, &max) != -1) {
449        return max.ws_col;
450    } else {
451        return 80;
452    }
453#else
454    CONSOLE_SCREEN_BUFFER_INFO cbsi;
455    GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cbsi);
456    return cbsi.srWindow.Right - cbsi.srWindow.Left;
457#endif
458}
459
460static char*
461format_option_help(cliopts_entry *entry,
462                   char *buf,
463                   struct cliopts_extra_settings *settings)
464{
465    char *bufp = buf;
466    if (entry->kshort) {
467        bufp += sprintf(bufp, " -%c ", entry->kshort);
468    }
469
470#define _advance_margin(offset) \
471    while(bufp-buf < offset || *bufp) { \
472        if (!*bufp) { \
473            *bufp = ' '; \
474        } \
475        bufp++; \
476    }
477
478    _advance_margin(4)
479
480    if (entry->klong) {
481        bufp += sprintf(bufp, " --%s ", entry->klong);
482    }
483
484    if (entry->vdesc) {
485        bufp += sprintf(bufp, " <%s> ", entry->vdesc);
486    }
487
488    _advance_margin(35)
489#undef _advance_margin
490
491    if (entry->help) {
492        unsigned initial_indent = bufp - buf + 1;
493        int curpos = initial_indent;
494        const char *help_p = entry->help;
495
496        for (; *help_p; help_p++, curpos++, bufp++) {
497
498            if (curpos >= settings->line_max) {
499                unsigned ii;
500                if (!isspace(*help_p) && !isspace(*(help_p-1))) {
501                    *bufp = '-';
502                    bufp++;
503                }
504                *bufp = '\n';
505                bufp++;
506
507                for (ii = 0; ii < initial_indent+1; ii++, bufp++) {
508                    *bufp = ' ';
509                }
510
511                curpos = initial_indent;
512                if (isspace(*help_p)) {
513                    bufp--;
514                    continue;
515                }
516            }
517            *bufp = *help_p;
518        }
519    }
520
521    *bufp = '\0';
522    return buf;
523}
524
525static void
526print_help(struct cliopts_priv *ctx, struct cliopts_extra_settings *settings)
527{
528    cliopts_entry *cur;
529    cliopts_entry helpent = { 0 };
530    char helpbuf[1024] = { 0 };
531
532    helpent.klong = "help";
533    helpent.kshort = '?';
534    helpent.help = "this message";
535
536    fprintf(stderr, "Usage:\n");
537    fprintf(stderr, "  %s %s\n\n", settings->progname, settings->argstring);
538    if (settings->shortdesc) {
539        fprintf(stderr, "%s", settings->shortdesc);
540        fprintf(stderr, "\n");
541    }
542
543
544    for (cur = ctx->entries; cur->dest; cur++) {
545        if (cur->hidden) {
546            continue;
547        }
548
549        memset(helpbuf, 0, sizeof(helpbuf));
550        format_option_help(cur, helpbuf, settings);
551        fprintf(stderr, INDENT "%s", helpbuf);
552
553
554        if (settings->show_defaults) {
555            fprintf(stderr, " [Default=");
556
557            switch (cur->ktype) {
558            case CLIOPTS_ARGT_STRING:
559                fprintf(stderr, "'%s'", (cur->dest && *(char **)cur->dest) ?
560                        *(char**)cur->dest : "");
561                break;
562            case CLIOPTS_ARGT_LIST: {
563                size_t ii;
564                cliopts_list *l = (cliopts_list *)cur->dest;
565                for (ii = 0; ii < l->nvalues; ii++) {
566                    fprintf(stderr, "'%s'", l->values[ii]);
567                    if (ii != l->nvalues-1) {
568                        fprintf(stderr, ", ");
569                    }
570                }
571                break;
572            }
573            case CLIOPTS_ARGT_FLOAT:
574                fprintf(stderr, "%0.2f", *(float*)cur->dest);
575                break;
576            case CLIOPTS_ARGT_HEX:
577                fprintf(stderr, "0x%x", *(int*)cur->dest);
578                break;
579            case CLIOPTS_ARGT_INT:
580                fprintf(stderr, "%d", *(int*)cur->dest);
581                break;
582            case CLIOPTS_ARGT_UINT:
583                fprintf(stderr, "%u", *(unsigned int*)cur->dest);
584                break;
585            case CLIOPTS_ARGT_NONE:
586                fprintf(stderr, "%s", *(int*)cur->dest ? "TRUE" : "FALSE");
587                break;
588            default:
589                fprintf(stderr, "Unknown option type '%d'", (int)cur->ktype);
590                break;
591            }
592            fprintf(stderr, "]");
593        }
594        fprintf(stderr, "\n");
595    }
596    memset(helpbuf, 0, sizeof(helpbuf));
597    fprintf(stderr, INDENT "%s\n",
598            format_option_help(&helpent, helpbuf, settings));
599
600}
601
602static void
603dump_error(struct cliopts_priv *ctx)
604{
605    fprintf(stderr, "Couldn't parse options: %s\n", ctx->errstr);
606    if (ctx->errnum == CLIOPTS_ERR_BADOPT) {
607        fprintf(stderr, "Bad option: %s", ctx->current_key);
608    } else if (ctx->errnum == CLIOPTS_ERR_BAD_VALUE) {
609        fprintf(stderr, "Bad value '%s' for %s",
610                ctx->current_value,
611                ctx->current_key);
612    } else if (ctx->errnum == CLIOPTS_ERR_UNRECOGNIZED) {
613        fprintf(stderr, "No such option: %s", ctx->current_key);
614    } else if (ctx->errnum == CLIOPTS_ERR_ISSWITCH) {
615        char optbuf[64] = { 0 };
616        fprintf(stderr, "Option %s takes no arguments",
617                get_option_name(ctx->prev, optbuf));
618    }
619    fprintf(stderr, "\n");
620
621}
622
623CLIOPTS_API
624int
625cliopts_parse_options(cliopts_entry *entries,
626                      int argc,
627                      char **argv,
628                      int *lastidx,
629                      struct cliopts_extra_settings *settings)
630{
631    /**
632     * Now let's build ourselves a
633     */
634    int curmode;
635    int ii, ret = 0, lastidx_s = 0;
636    struct cliopts_priv ctx = { 0 };
637    struct cliopts_extra_settings default_settings = { 0 };
638
639    if (!lastidx) {
640        lastidx = &lastidx_s;
641    }
642
643    ctx.entries = entries;
644
645    if (!settings) {
646        settings = &default_settings;
647        settings->show_defaults = 1;
648    }
649    if (!settings->progname) {
650        settings->progname = argv[0];
651    }
652    if (!settings->argstring) {
653        settings->argstring = "[OPTIONS...]";
654    }
655    settings->nrestargs = 0;
656
657    if (!settings->line_max) {
658        settings->line_max = get_terminal_width() - 3;
659    }
660
661    ii = (settings->argv_noskip) ? 0 : 1;
662
663    if (ii >= argc) {
664        *lastidx = 0;
665        ret = 0;
666        goto GT_CHECK_REQ;
667        return 0;
668    }
669
670    curmode = WANT_OPTION;
671    ctx.wanted = curmode;
672    ctx.settings = settings;
673
674    for (; ii < argc; ii++) {
675
676        if (curmode == WANT_OPTION) {
677            curmode = parse_option(&ctx, argv[ii]);
678        } else if (curmode == WANT_VALUE) {
679            curmode = parse_value(&ctx, argv[ii]);
680        }
681
682        if (curmode == MODE_ERROR) {
683            if (settings->error_nohelp == 0) {
684                dump_error(&ctx);
685            }
686            ret = -1;
687            break;
688        } else if (curmode == MODE_HELP) {
689            if (settings->help_noflag) {
690                /* ignore it ? */
691                continue;
692            }
693
694            print_help(&ctx, settings);
695            exit(0);
696
697        } else if (curmode == MODE_RESTARGS) {
698            ii++;
699            break;
700        } else {
701            ctx.wanted = curmode;
702        }
703    }
704
705    *lastidx = ii;
706
707    if (curmode == WANT_VALUE) {
708        ret = -1;
709
710        if (settings->error_nohelp == 0) {
711            fprintf(stderr,
712                    "Option %s requires argument\n",
713                    ctx.current_key);
714        }
715        goto GT_RET;
716    }
717
718    GT_CHECK_REQ:
719    {
720        cliopts_entry *cur_ent;
721        for (cur_ent = entries; cur_ent->dest; cur_ent++) {
722            char entbuf[128] = { 0 };
723            if (cur_ent->found || cur_ent->required == 0) {
724                continue;
725            }
726
727            ret = -1;
728            if (settings->error_nohelp) {
729                goto GT_RET;
730            }
731
732            fprintf(stderr, "Required option %s missing\n",
733                    get_option_name(cur_ent, entbuf));
734        }
735    }
736
737    GT_RET:
738    if (ret == -1) {
739        if (settings->error_nohelp == 0) {
740            print_help(&ctx, settings);
741        }
742        if (settings->error_noexit == 0) {
743            exit(EXIT_FAILURE);
744        }
745    }
746    return ret;
747}
748