xref: /6.0.3/subjson/contrib/cliopts/cliopts.c (revision efef0c81)
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.\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 */
261static int
262parse_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
426static char *
427get_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
445static 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
461static char*
462format_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
526static void
527print_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
603static void
604dump_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
624CLIOPTS_API
625int
626cliopts_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