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