summaryrefslogtreecommitdiff
path: root/src/opt.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/opt.c')
-rw-r--r--src/opt.c299
1 files changed, 152 insertions, 147 deletions
diff --git a/src/opt.c b/src/opt.c
index ab482ad..118c159 100644
--- a/src/opt.c
+++ b/src/opt.c
@@ -1,6 +1,6 @@
/*
* libHX/opt.c
- * Copyright Jan Engelhardt, 2002-2011
+ * Copyright Jan Engelhardt, 2025
*
* This file is part of libHX. libHX is free software; you can
* redistribute it and/or modify it under the terms of the GNU Lesser
@@ -8,6 +8,7 @@
* either version 2.1 or (at your option) any later version.
*/
#include <errno.h>
+#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
@@ -119,13 +120,15 @@ enum {
/**
* struct HX_getopt_vars - option parser working variable set
* @arg0: saved program name
- * @remaining: list of extracted non-options
+ * @desc: list of extracted options
+ * @oarg: list of extracted options
+ * @uarg: list of extracted non-options
* @cbi: callback info
* @flags: flags setting the behavior for HX_getopt
*/
struct HX_getopt_vars {
const char *arg0;
- struct HXdeque *remaining;
+ struct HXdeque *desc, *oarg, *uarg;
struct HXoptcb cbi;
unsigned int flags;
};
@@ -218,6 +221,10 @@ static void do_assign(struct HXoptcb *cbi, const char *arg0)
if (opt->ptr != NULL)
*static_cast(char **, opt->ptr) = HX_strdup(cbi->data);
break;
+ case HXTYPE_STRP:
+ if (opt->ptr != nullptr)
+ *static_cast(const char **, opt->ptr) = cbi->data;
+ break;
case HXTYPE_STRDQ:
HXdeque_push(opt->ptr, HX_strdup(cbi->data));
break;
@@ -237,6 +244,15 @@ static void do_assign(struct HXoptcb *cbi, const char *arg0)
opt->cb(cbi);
}
+static int do_assign_front(struct HX_getopt_vars *par)
+{
+ if (HXdeque_push(par->desc, par->cbi.current) == nullptr ||
+ HXdeque_push(par->oarg, par->cbi.data) == nullptr)
+ return -errno;
+ do_assign(&par->cbi, par->arg0);
+ return 0;
+}
+
static __inline__ const struct HXoption *
lookup_short(const struct HXoption *table, char opt)
{
@@ -494,27 +510,14 @@ static int HX_getopt_error(int err, const char *key, unsigned int flags)
return HXOPT_I_ERROR;
}
-static int HX_getopt_twolong(const char *const *opt,
+static int HX_getopt_twolong(const char *key, const char *value,
struct HX_getopt_vars *par)
{
- const char *key = opt[0], *value = opt[1];
-
par->cbi.current = lookup_long_pfx(par->cbi.table, key + 2);
if (par->cbi.current == &HXopt_ambig_prefix)
return HX_getopt_error(HXOPT_E_AMBIG_PREFIX, key, par->flags);
- if (par->cbi.current == NULL) {
- if (par->flags & HXOPT_PTHRU) {
- char *tmp = HX_strdup(key);
- if (tmp == NULL)
- return -errno;
- if (HXdeque_push(par->remaining, tmp) == NULL) {
- free(tmp);
- return -errno;
- }
- return HXOPT_S_NORMAL | HXOPT_I_ADVARG;
- }
+ if (par->cbi.current == nullptr)
return HX_getopt_error(HXOPT_E_LONG_UNKNOWN, key, par->flags);
- }
par->cbi.flags = HXOPTCB_BY_LONG;
if (takes_void(par->cbi.current->type)) {
@@ -550,11 +553,21 @@ static int HX_getopt_long(const char *cur, struct HX_getopt_vars *par)
return -errno;
value = strchr(key, '=');
if (value == nullptr) {
- /* Cannot happen because state is always !S_TWOLONG */
+ /*
+ * Cannot happen because state is always !S_TWOLONG, but make
+ * static analyzers happy.
+ */
free(key);
return -EINVAL;
}
- *value++ = '\0';
+ *value = '\0';
+ value = strchr(cur, '=');
+ if (value == nullptr) {
+ /* Cannot happen either */
+ free(key);
+ return -EINVAL;
+ }
+ ++value;
par->cbi.current = lookup_long_pfx(par->cbi.table, key + 2);
if (par->cbi.current == &HXopt_ambig_prefix) {
ret = HX_getopt_error(HXOPT_E_AMBIG_PREFIX, key, par->flags);
@@ -562,15 +575,6 @@ static int HX_getopt_long(const char *cur, struct HX_getopt_vars *par)
return ret;
}
if (par->cbi.current == NULL) {
- if (par->flags & HXOPT_PTHRU) {
- /* Undo nuke of '=' and reuse alloc */
- value[-1] = '=';
- if (HXdeque_push(par->remaining, key) == NULL) {
- free(key);
- return -errno;
- }
- return HXOPT_S_NORMAL | HXOPT_I_ADVARG;
- }
ret = HX_getopt_error(HXOPT_E_LONG_UNKNOWN, key, par->flags);
free(key);
return ret;
@@ -585,58 +589,48 @@ static int HX_getopt_long(const char *cur, struct HX_getopt_vars *par)
return ret;
}
+ free(key);
par->cbi.flags = HXOPTCB_BY_LONG;
par->cbi.data = value;
- /* Not possible to use %HXOPT_I_ASSIGN due to transience of @key. */
- do_assign(&par->cbi, par->arg0);
- free(key);
+ /*
+ * Not possible to use %HXOPT_I_ASSIGN due to transience of @key. Thus
+ * manually call do_assign now rather than in the superordinate
+ * function.
+ */
+ ret = do_assign_front(par);
+ if (ret < 0)
+ return ret;
return HXOPT_S_NORMAL | HXOPT_I_ADVARG;
}
-static int HX_getopt_short(const char *const *opt, const char *cur,
+static int HX_getopt_short(const char *key, const char *value,
struct HX_getopt_vars *par)
{
- char op = *cur;
+ char op = *key;
if (op == '\0')
return HXOPT_S_NORMAL | HXOPT_I_ADVARG;
par->cbi.current = lookup_short(par->cbi.table, op);
- if (par->cbi.current == NULL) {
- if (par->flags & HXOPT_PTHRU) {
- /*
- * @cur-1 is always valid: it is either the previous
- * char, or it is '-'.
- */
- char *buf = HX_strdup(cur - 1);
- if (buf != NULL)
- *buf = '-';
- if (HXdeque_push(par->remaining, buf) == NULL) {
- free(buf);
- return -errno;
- }
- return HXOPT_S_NORMAL | HXOPT_I_ADVARG;
- }
+ if (par->cbi.current == nullptr)
return HX_getopt_error(HXOPT_E_SHORT_UNKNOWN, &op, par->flags);
- }
par->cbi.flags = HXOPTCB_BY_SHORT;
if (takes_void(par->cbi.current->type)) {
/* -A */
par->cbi.data = NULL;
return HXOPT_S_SHORT | HXOPT_I_ASSIGN | HXOPT_I_ADVCHAR;
- } else if (cur[1] != '\0') {
+ } else if (key[1] != '\0') {
/* -Avalue */
- par->cbi.data = cur + 1;
+ par->cbi.data = key + 1;
return HXOPT_S_NORMAL | HXOPT_I_ASSIGN | HXOPT_I_ADVARG;
}
- cur = *++opt;
if (par->cbi.current->type & HXOPT_OPTIONAL) {
- if (cur == NULL || *cur != '-' ||
- (cur[0] == '-' && cur[1] == '\0')) {
+ if (value == nullptr || *value != '-' ||
+ (value[0] == '-' && value[1] == '\0')) {
/* -f - -f bla */
- par->cbi.data = cur;
+ par->cbi.data = value;
return HXOPT_S_NORMAL | HXOPT_I_ASSIGN | HXOPT_I_ADVARG2;
} else {
/* -f -a-file --another --file -- endofoptions */
@@ -645,41 +639,28 @@ static int HX_getopt_short(const char *const *opt, const char *cur,
}
} else {
/* -A value */
- if (cur == NULL)
+ if (value == nullptr)
return HX_getopt_error(HXOPT_E_SHORT_MISSING, &op, par->flags);
- par->cbi.data = cur;
+ par->cbi.data = value;
return HXOPT_S_NORMAL | HXOPT_I_ASSIGN | HXOPT_I_ADVARG2;
}
}
static int HX_getopt_term(const char *cur, const struct HX_getopt_vars *par)
{
- char *tmp = HX_strdup(cur);
- if (tmp == NULL)
- return -errno;
- if (HXdeque_push(par->remaining, tmp) == NULL) {
- free(tmp);
+ if (HXdeque_push(par->uarg, cur) == nullptr)
return -errno;
- }
return HXOPT_S_TERMINATED | HXOPT_I_ADVARG;
}
static int HX_getopt_normal(const char *cur, const struct HX_getopt_vars *par)
{
if (cur[0] == '-' && cur[1] == '\0') {
- /* Note to popt developers: A single dash is NOT an option! */
- HXdeque_push(par->remaining, HX_strdup(cur));
+ HXdeque_push(par->uarg, cur);
return HXOPT_S_NORMAL | HXOPT_I_ADVARG;
}
- if (cur[0] == '-' && cur[1] == '-' && cur[2] == '\0') {
- /*
- * Double dash. If passthrough is on, "--" must be copied into
- * @remaining. This is done in the next round.
- */
- if (!(par->flags & HXOPT_PTHRU))
- return HXOPT_S_TERMINATED | HXOPT_I_ADVARG;
- return HXOPT_S_TERMINATED;
- }
+ if (cur[0] == '-' && cur[1] == '-' && cur[2] == '\0')
+ return HXOPT_S_TERMINATED | HXOPT_I_ADVARG;
if (cur[0] == '-' && cur[1] == '-') { /* long option */
if (strchr(cur + 2, '=') == NULL)
return HXOPT_S_TWOLONG;
@@ -692,62 +673,71 @@ static int HX_getopt_normal(const char *cur, const struct HX_getopt_vars *par)
if (par->flags & HXOPT_RQ_ORDER)
/* POSIX: first non-option implies option termination */
return HXOPT_S_TERMINATED;
- cur = HX_strdup(cur);
- if (cur == NULL || HXdeque_push(par->remaining, cur) == NULL)
+ if (HXdeque_push(par->uarg, cur) == nullptr)
return -errno;
return HXOPT_S_NORMAL | HXOPT_I_ADVARG;
}
-EXPORT_SYMBOL int HX_getopt5(const struct HXoption *table, char **orig_argv,
- int *new_argc, char ***new_argv, unsigned int flags)
+EXPORT_SYMBOL void HX_getopt6_clean(struct HXopt6_result *r)
+{
+ free(r->desc);
+ free(r->oarg);
+ free(r->uarg);
+ HX_zvecfree(r->dup_argv);
+ r->desc = nullptr;
+ r->oarg = nullptr;
+ r->uarg = nullptr;
+ r->dup_argv = nullptr;
+}
+
+EXPORT_SYMBOL int HX_getopt6(const struct HXoption *table, int argc,
+ char **argv, struct HXopt6_result *result, unsigned int flags)
{
struct HX_getopt_vars ps;
- const char **opt = const_cast(const char **, orig_argv);
+ const char **opt = const_cast(const char **, argv);
int state = HXOPT_S_NORMAL;
int ret = -ENOMEM;
- unsigned int argk = 0;
- if (new_argc != nullptr)
- *new_argc = 0;
- if (new_argv != nullptr)
- *new_argv = nullptr;
+ if ((flags & (HXOPT_RQ_ORDER | HXOPT_ANY_ORDER)) == (HXOPT_RQ_ORDER | HXOPT_ANY_ORDER))
+ return -EINVAL;
+ if (result == nullptr && flags & (HXOPT_ITER_OPTS | HXOPT_ITER_ARGS | HXOPT_DUP_ARGS))
+ return -EINVAL;
+ if (result != nullptr)
+ memset(result, 0, sizeof(*result));
memset(&ps, 0, sizeof(ps));
- ps.remaining = HXdeque_init();
- if (ps.remaining == NULL) {
+ ps.desc = HXdeque_init();
+ ps.oarg = HXdeque_init();
+ ps.uarg = HXdeque_init();
+ if (ps.desc == nullptr || ps.oarg == nullptr || ps.uarg == nullptr) {
ret = -errno;
goto out;
}
ps.flags = flags;
- ps.arg0 = *opt;
- ps.cbi.table = table;
-
- if (*opt != NULL) {
- /* put argv[0] back */
- char *arg = HX_strdup(*opt++);
- if (arg == NULL) {
- ret = -errno;
- goto out;
- }
- if (HXdeque_push(ps.remaining, arg) == NULL) {
- free(arg);
- ret = -errno;
- goto out;
- }
+ if (argc < 0) {
+ argc = 0;
+ for (const char **p = opt; *p != nullptr; ++p)
+ ++argc;
}
+ if (argc > 0) {
+ ps.arg0 = *opt++;
+ --argc;
+ }
+ ps.cbi.table = table;
- if (posix_me_harder())
+ if (!(ps.flags & HXOPT_ANY_ORDER) && posix_me_harder())
ps.flags |= HXOPT_RQ_ORDER;
- for (const char *cur = *opt; cur != NULL; ) {
+ const char *op0 = argc > 0 ? opt[0] : nullptr;
+ while (argc > 0) {
if (state == HXOPT_S_TWOLONG)
- state = HX_getopt_twolong(opt, &ps);
+ state = HX_getopt_twolong(op0, argc > 1 ? opt[1] : nullptr, &ps);
else if (state == HXOPT_S_LONG)
- state = HX_getopt_long(cur, &ps);
+ state = HX_getopt_long(op0, &ps);
else if (state == HXOPT_S_SHORT)
- state = HX_getopt_short(opt, cur, &ps);
+ state = HX_getopt_short(op0, argc > 1 ? opt[1] : nullptr, &ps);
else if (state == HXOPT_S_TERMINATED)
- state = HX_getopt_term(cur, &ps);
+ state = HX_getopt_term(op0, &ps);
else if (state == HXOPT_S_NORMAL)
- state = HX_getopt_normal(cur, &ps);
+ state = HX_getopt_normal(op0, &ps);
if (state < 0) {
ret = state;
@@ -757,31 +747,62 @@ EXPORT_SYMBOL int HX_getopt5(const struct HXoption *table, char **orig_argv,
ret = state & ~HXOPT_I_ERROR;
goto out;
}
- if (state & HXOPT_I_ASSIGN)
- do_assign(&ps.cbi, ps.arg0);
- if (state & HXOPT_I_ADVARG)
- cur = *++opt;
- else if (state & HXOPT_I_ADVARG2)
- cur = *(opt += 2);
- else if (state & HXOPT_I_ADVCHAR)
- ++cur;
+ if (state & HXOPT_I_ASSIGN) {
+ ret = do_assign_front(&ps);
+ if (ret < 0)
+ goto out;
+ }
+ if (state & HXOPT_I_ADVARG) {
+ --argc;
+ if (argc > 0)
+ op0 = *++opt;
+ } else if (state & HXOPT_I_ADVARG2) {
+ opt += 2;
+ argc -= 2;
+ if (argc > 0)
+ op0 = *opt;
+ } else if (state & HXOPT_I_ADVCHAR) {
+ ++op0;
+ }
state &= ~HXOPT_I_MASK;
}
- if (new_argv != nullptr) {
- *new_argv = reinterpret_cast(char **, HXdeque_to_vec(ps.remaining, &argk));
- if (*new_argv == nullptr) {
+ if (flags & HXOPT_ITER_OPTS) {
+ size_t nelem = 0;
+ result->desc = reinterpret_cast(const struct HXoption **, HXdeque_to_vec(ps.desc, &nelem));
+ result->oarg = reinterpret_cast(char **, HXdeque_to_vec(ps.oarg, &nelem));
+ if (result->desc == nullptr || result->oarg == nullptr) {
+ ret = -errno;
+ goto out;
+ }
+ result->nopts = nelem < INT_MAX ? nelem : INT_MAX;
+ }
+ if (flags & HXOPT_ITER_ARGS) {
+ size_t nelem = 0;
+ result->uarg = reinterpret_cast(char **, HXdeque_to_vecx(ps.uarg, &nelem));
+ if (result->uarg == nullptr) {
+ ret = -errno;
+ goto out;
+ }
+ result->nargs = nelem < INT_MAX ? nelem : INT_MAX;
+ }
+ if (flags & HXOPT_DUP_ARGS) {
+ if (ps.arg0 != nullptr && HXdeque_unshift(ps.uarg, ps.arg0) == nullptr) {
ret = -errno;
goto out;
}
- if (new_argc != nullptr)
- *new_argc = argk;
- /* pointers are owned by new_argv now, so free only the deque head */
- HXdeque_free(ps.remaining);
- ps.remaining = nullptr;
+ size_t nelem = 0;
+ result->dup_argv = HXdeque_to_vec_strdup(ps.uarg, &nelem);
+ if (result->dup_argv == nullptr) {
+ ret = -errno;
+ goto out;
+ }
+ result->dup_argc = nelem < INT_MAX ? nelem : INT_MAX;
}
ret = HXOPT_ERR_SUCCESS;
out:
+ if (ret != HXOPT_ERR_SUCCESS && result != nullptr)
+ HX_getopt6_clean(result);
if (ret == HXOPT_ERR_SUCCESS) {
} else if (ret < 0) {
if (!(ps.flags & HXOPT_QUIET))
@@ -793,28 +814,12 @@ EXPORT_SYMBOL int HX_getopt5(const struct HXoption *table, char **orig_argv,
else if (ps.flags & HXOPT_USAGEONERR)
HX_getopt_usage(&ps.cbi, stderr);
}
- if (ps.remaining != nullptr)
- HXdeque_genocide2(ps.remaining, free);
- return ret;
-}
-
-EXPORT_SYMBOL int HX_getopt(const struct HXoption *table, int *argc,
- char ***argv, unsigned int flags)
-{
- int new_argc = 0;
- char **new_argv = nullptr;
- int ret = HX_getopt5(table, *argv, &new_argc,
- flags & HXOPT_KEEP_ARGV ? nullptr : &new_argv, flags);
- if (ret != HXOPT_ERR_SUCCESS)
- return ret;
- if (flags & HXOPT_KEEP_ARGV)
- // NO_CREATE_NEW / DESTROY_NEW
- new_argv = *argv;
- else if (flags & HXOPT_DESTROY_OLD)
- HX_zvecfree(*argv);
- if (argc != nullptr)
- *argc = new_argc;
- *argv = new_argv;
+ if (ps.uarg != nullptr)
+ HXdeque_free(ps.uarg);
+ if (ps.oarg != nullptr)
+ HXdeque_free(ps.oarg);
+ if (ps.desc != nullptr)
+ HXdeque_free(ps.desc);
return ret;
}