Welcome to little lamb

Code » limb » commit e93f316

loadopt(): Take a stralloc & add OPT_SA, OPT_ESC, ..

author Olivier Brunel
2023-05-12 07:51:56 UTC
committer Olivier Brunel
2023-07-05 07:37:02 UTC
parent c1732b0d03e9454f1c688772e936f16aa1f743dd

loadopt(): Take a stralloc & add OPT_SA, OPT_ESC, ..

..OPT_PATH & PATH_FILE to automatically copy the value into the given sa,
also unescape the value if within double-quotes, also check it doesn't
contain any NUL-byte, also check it doesn't contain any slashes (`/`).

src/doc/loadopt.h.0.md +15 -14
src/doc/loadopt.h/loadopt.3.md +71 -21
src/doc/parseopt.h/parseopt.3.md +6 -8
src/liblimb/include/limb/loadopt.h +22 -12
src/liblimb/include/limb/parseopt.h +6 -5
src/liblimb/loadopt.h/loadopt.c +148 -40

diff --git a/src/doc/loadopt.h.0.md b/src/doc/loadopt.h.0.md
index 53c61ee..4068082 100644
--- a/src/doc/loadopt.h.0.md
+++ b/src/doc/loadopt.h.0.md
@@ -18,41 +18,42 @@ optionally configuration file.
 
 The following constants are defined :
 
-: *OPT_SKIP*
-:: Set an option not be loaded from configuration file
-
-: *OPT_REQ*
-:: Set an option to be required
+: *OPT_SKIP*, *OPT_REQ*, *OPT_SA*, *OPT_ESC*, *OPT_PATH*, *OPT_FILE*, *OPT_RPT*
+:: Flags that can be used when defining options/arguments to [loadopt](3).
 
 ## Macros
 
 The following macros are defined :
 
-: *LOADOPT_ARGUMENTS*, *LOADOPT_DONE_OPEN*, *LOADOPT_DONE*
+: *LOADOPT_ARGUMENTS*, *LOADOPT_DONE*, *LOADOPT_STOP*
 :: To be used as element in a *struct option* array
 
 : *LOADOPT_INIT*
 :: Value to initialize a *struct loadopt*.
 
-: *ARGUMENT_REQ*, *ARGUMENT_OPT*
+: *ARGUMENT_REQ*(`arg`), *ARGUMENT_OPT*(`arg`)
 :: To define required or optional argument in a *struct option* array to be
 :: checked by [loadopt](3)
 
-: *AUTOOPT_NOCONFIG*
+: *AUTOOPT_NOCONFIG*(`shortopt`, `longopt`, `flags`, `id`)
 :: To define the option to disable loading (specified) options from
 :: configuration
 
-: *LO_ARG(`ctx`)*
+: *LO_ARG*(`ctx`)
 :: To get a pointer to the current option's argument.
 
-: *LO_CUR(`ctx`)*
-:: To get the index of the first argument in argv.
+: *LO_OFF*(`ctx`)
+:: To get the offset to the current option's argument. See [loadopt](3) for
+:: more.
+
+: *LO_CUR*(`ctx`)
+:: To get the index of the first argument in `argv`.
 
-: *LO_IDX(`ctx`)*
+: *LO_IDX*(`ctx`)
 :: To get the index of the current option.
 
-: *LO_FROMFILE(`ctx`)*
-:: Return whether or not the option was set in configuration file.
+: *LO_FROMFILE*(`ctx`)
+:: Return whether or not the option was set from configuration directory.
 
 ## Structures
 
diff --git a/src/doc/loadopt.h/loadopt.3.md b/src/doc/loadopt.h/loadopt.3.md
index 0395f37..767ee5b 100644
--- a/src/doc/loadopt.h/loadopt.3.md
+++ b/src/doc/loadopt.h/loadopt.3.md
@@ -10,19 +10,20 @@ loadopt - parse options from command-line and (optionally) configuration file
     #include <limb/loadopt.h>
 
 ```pre hl
-int loadopt(int <em>argc</em>, const char **<em>argv</em>, const struct option *<em>options</em>,
-            int dirfd, const char *<em>confdir</em>, char *<em>buf</em>, size_t <em>blen</em>,
-            unsigned int <em>flags</em>, struct loadopt *<em>ctx</em>)
+int loadopt(stralloc *<em>sa</em>, int <em>argc</em>, const char **<em>argv</em>, const struct option *<em>options</em>,
+            int dirfd, const char *<em>confdir</em>, unsigned int <em>poflags</em>, struct loadopt *<em>ctx</em>)
 
 const char *LO_ARG(struct loadopt *<em>ctx</em>)
-u16 LO_CUR(struct loadopt *<em>ctx</em>)
-int LO_IDX(struct loadopt *<em>ctx</em>)
+size_t *LO_OFF*(struct loadopt *<em>ctx</em>)
+u16 *LO_CUR*(struct loadopt *<em>ctx</em>)
+int *LO_IDX*(struct loadopt *<em>ctx</em>)
+int *LO_FROMFILE*(struct loadopt *<em>ctx</em>)
 ```
 
 # DESCRIPTION
 
-The `loadopt`() function parses command-line arguments. Then, `confdir` is not
-*NULL*, it will try to load any options that hasn't been set yet nor marked
+The `loadopt`() function parses command-line arguments. Then, if `confdir` is
+not *NULL*, it will try to load any options that hasn't been set yet nor marked
 *OPT_SKIP* from the configuration directory `confdir`. If `confdir` describes a
 relative path, it is relative to the directory associated with the file
 descriptor `dirfd`, which may be *AT_FDCWD* to use the current working
@@ -49,14 +50,45 @@ value is constructed as a bitwise-inclusive OR of the following :
 :: directory), the option has not been set.
 :: Mostly useful with options that require an argument.
 
+: *OPT_SA*
+:: The value of the option's argument will be appended into the stralloc `sa`,
+:: and available through the macro *LO_OFF*(), indicating the offset at which
+:: it begins. When used, the macro *LO_ARG*() will always return *NULL*.
+:: This can be useful if the argument's value must be kept after option parsing,
+:: even when the option is set from configuration directory.
+:: Additionally, in case some pre-processing is applied, see below.
+
+: *OPT_ESC*
+:: Implies *OPT_SA*. If the option's argument is within double-quotes (`"`) then
+:: it will be un-escaped (as described in [esc_scan](3)) before being placed
+:: into `sa`, thus ensuring the available value is always ready for use.
+
+: *OPT_PATH*
+:: Implies *OPT_ESC*. Ensures that the (possibly unescaped) value does /not/
+:: contain any NUL byte, and is thusly a valid path name.
+
+: *OPT_FILE*
+:: Implies *OPT_PATH*. Ensures that the (possibly unescaped) value does /not/
+:: contain any slashes (`/`), and is thusly a valid file name only.
+
+: *OPT_RPT*
+:: Can only be used for the last-defined argument, not options. It indicates
+:: that this argument can be "repeated", i.e. from this position (in `argv`) on,
+:: any and all further arguments are processed as this one.
+
 The last argument `ctx` is an opaque structure that should be initialized to
 *LOADOPT_ZERO*.
 
+The stralloc pointed by `sa` is required when `confdir` is not *NULL*, as it
+will be used as temporary space to read files as needed. If at also required if
+at least one element of `options` has its *OPT_SA* flag set.
+
 Accessing information is done through macros `LO_ARG`(), `LO_IDX`() and
 `LO_CUR`(), which are similar to their [parseopt](3) counterparts *PO_ARG*(),
 *PO_IDX*() and *PO_CUR*() respectively.
 
-Additionally, macro `LO_FROMFILE`() will return 1 when the current option came
+Additionally, macro `LO_OFF`() must be used when *OPT_SA* was set, as described
+above, and macro `LO_FROMFILE`() will return 1 when the current option came
 from configuration directory, zero otherwise (i.e. from command-line).
 
 ## Configuration Directory
@@ -83,6 +115,9 @@ specified on command-line.
 ! Note that when an option was specified on configuration directory, its
 ! argument, if any, will /not/ be preserved/remain valid once parsing the option
 ! is done. Thus, if its value is needed for later use it needs to be copied.
+! An easy way to achieve this is using the *OPT_SA* flag, to have it placed into
+! the stralloc `sa` automatically (whether or not it comes from configuration
+! directory).
 
 ## Command-line Arguments
 
@@ -97,9 +132,8 @@ referring to an argument instead of an option.
 Note that *LOADOPT_ARGUMENTS* can be the first element of the array, if there
 are no options.
 
-For elements referring to arguments, members `shortopt`, `id` and `flags` are
-ignored. The `longopt` member is only used in warnings to the user, to refer to
-said argument.
+For elements referring to arguments, member `shortopt` is ignored. The `longopt`
+member is only used in warnings to the user, to refer to said argument.
 
 The important member is `arg` which is treated as defining whether the
 argument is required (*ARG_REQ*) or optional (*ARG_OPT*). If required and no
@@ -108,26 +142,42 @@ specified, `loadopt`() successfully ends, returning 0.
 
 To define them, you can use the following convenience macros :
 
-: *ARGUMENT_OPT*(`name`)
+: *ARGUMENT_OPT*(`name`, `flags`, `id`)
 :: To define argument `arg` as optional
 
-: *ARGUMENT_REQ*(`name`)
+: *ARGUMENT_REQ*(`name`, `flags`, `id`)
 : To define argument `arg` as required
 
+The actual parsing/handling of arguments is no different than of options,
+`loadopt`() will return the corresponding `id` (which must be valid as described
+above for options, with the exception that *OPTID_SHORTOPT* cannot be used), the
+same macros are to be used.
+
+Flags can also be used, so it is possible to have arguments' values be copied
+into the stralloc `sa` automatically, unescaped if needed and checked. Note
+however that *OPT_SKIP* and *OPT_REQ* cannot be used here.
+
 Finally, after all such argument definitions in the array `options`, you must
-terminate it with either one of the macros *LOADOPT_DONE_OPEN* and
-*LOADOPT_DONE*
+terminate it with either one of the macros *LOADOPT_DONE* or *LOADOPT_STOP*.
 
-If the former was specified, `loadopt`() ends successfully regardless of whether
-more arguments were specified on command-line or not. With the later however,
+If the later was specified, `loadopt`() ends successfully regardless of whether
+more arguments were specified on command-line or not. With the former however,
 any more arguments will result in an error for "too many arguments".
 
+! NOTE:
+! *LOADOPT_DONE* is actually the same as *OPTION_DONE* as described from
+! [parseopt](3).
+
 Note that either of those can be specified even without any actual arguments,
-i.e. as last element right after all the options, and must be specified as last
-element.
+i.e. as last element right after all the options, but must always be specified
+as last element.
+
+### Argument Repeating
 
-In other words, *LOADOPT_DONE* is the same as *OPTION_DONE* as described from
-[parseopt](3).
+It is possible, instead of using one of *LOADOPT_DONE* or *LOADOPT_STOP*, to set
+the flag *OPT_RPT* in the last argument. In which case any more argument
+specified on command-line will be processed as such an argument, over & over
+until all command-line has been parsed.
 
 # RETURN VALUE
 
diff --git a/src/doc/parseopt.h/parseopt.3.md b/src/doc/parseopt.h/parseopt.3.md
index 82763de..652e489 100644
--- a/src/doc/parseopt.h/parseopt.3.md
+++ b/src/doc/parseopt.h/parseopt.3.md
@@ -78,16 +78,14 @@ optional or not), what follows next within the element will be treated as the
 option's argument. (Note that it remains possible to specify a /required/
 argument as next element.)
 
-`options` must be a pointer to the first element of an array of *struct option*
-declared as such :
+`options` must be a pointer to the first element of an array of *struct option*,
+which contains the following members :
 
-    struct option {
         const char  shortopt;
         const char *longopt;
-        u8 arg      : 2;
-        u8 flags    : 6;
+        u16 arg     : 2;
+        u16 flags   : 10;
         int id;
-    };
 
 The meanings of different members are :
 
@@ -102,10 +100,10 @@ The meanings of different members are :
 : `arg`
 :: One of the constants *ARG_NONE*, *ARG_REQ* or *ARG_OPT* to indicate whether
 :: the option takes no argument, requires an argument, or accepts an optional
-:: argument.
+:: argument, respectively.
 
 : `flags`
-:: Not used, leave at 0.
+:: Not used, leave at 0. (See [loadopt](3) for possible use.)
 
 : `id`
 :: An integer value unique to this option, to recognize when it is parsed. It
diff --git a/src/liblimb/include/limb/loadopt.h b/src/liblimb/include/limb/loadopt.h
index c18fc15..32f0fa3 100644
--- a/src/liblimb/include/limb/loadopt.h
+++ b/src/liblimb/include/limb/loadopt.h
@@ -6,6 +6,7 @@
 
 #include <limb/direntry.h>
 #include <limb/parseopt.h>
+#include <limb/stralloc.h>
 
 enum {
     /* private/internal */
@@ -13,26 +14,35 @@ enum {
     OPT_DONE    = 1 << 0,
     OPT_SET     = 1 << 1,
     /* public * */
-    OPT_SKIP    = 1 << 2,   /* don't load from file */
-    OPT_REQ     = 1 << 3,   /* must be specified */
+    OPT_SKIP    = 1 << 2,               /* don't load from file */
+    OPT_REQ     = 1 << 3,               /* must be specified */
+    OPT_SA      = 1 << 4,               /* copy into sa */
+    OPT_ESC_    = 1 << 5,
+    OPT_ESC     = OPT_ESC_ | OPT_SA,    /* + unesc if w/in quotes */
+    OPT_PATH_   = 1 << 6,
+    OPT_PATH    = OPT_PATH_ | OPT_ESC,  /* + ensure no NUL byte */
+    OPT_FILE_   = 1 << 7,
+    OPT_FILE    = OPT_FILE_ | OPT_PATH, /* + ensure no '/' */
+    OPT_RPT     = 1 << 8,               /* for last argument only */
 };
 
 /* end of options, on to arguments */
-#define LOADOPT_ARGUMENTS   { 0, 0, ARG_REQ,  OPT_DONE, 0 }
+#define LOADOPT_ARGUMENTS   { 0, 0, ARG_REQ,  OPT_DONE, 0, 0 }
 
 /* to define required argument, optional argument */
-#define ARGUMENT_REQ(a)     { 0, a, ARG_REQ, 0, 0 }
-#define ARGUMENT_OPT(a)     { 0, a, ARG_OPT, 0, 0 }
+#define ARGUMENT_REQ(a,f,id)    { 0, a, ARG_REQ, f, 0, id }
+#define ARGUMENT_OPT(a,f,id)    { 0, a, ARG_OPT, f, 0, id }
 
-/* eof of arguments, either no more arguments or there can be more */
-#define LOADOPT_DONE        { 0, 0, ARG_NONE, OPT_DONE, 0 }
-#define LOADOPT_DONE_OPEN   { 0, 0, ARG_OPT,  OPT_DONE, 0 }
+/* end of arguments (more == "too many args" */
+#define LOADOPT_DONE        { 0, 0, ARG_NONE, OPT_DONE, 0, 0 }
+/* stop parsing here, any more args will not our call */
+#define LOADOPT_STOP        { 0, 0, ARG_OPT,  OPT_DONE, 0, 0 }
 
 /* opaque structure */
 struct loadopt {
     struct parseopt po;
     DIR *confdir;
-    size_t saoff;
+    size_t optoff;  /* public, when OPT_SA is used */
     u8 optflags[64];
     u16 loflags         : 4;    /* LOADOPT_* */
     u16 state           : 4;
@@ -44,13 +54,13 @@ struct loadopt {
 #define LOADOPT_ZERO        { PARSEOPT_ZERO, NULL, 0, { 0 }, 0, 0, 0, 0 }
 
 #define LO_ARG(ctx)         (ctx)->po.arg
+#define LO_OFF(ctx)         (ctx)->optoff
 #define LO_IDX(ctx)         (ctx)->po.idx
 #define LO_CUR(ctx)         (ctx)->po.cur
 #define LO_FROMFILE(ctx)    (ctx)->from_file
 
-extern int loadopt(int argc, const char **argv, const struct option *options,
-                   int bfd, const char *confdir, char *buf, size_t blen,
-                   unsigned int poflags, struct loadopt *ctx);
+extern int loadopt(stralloc *sa, int argc, const char **argv, const struct option *options,
+                   int bfd, const char *confdir, unsigned int poflags, struct loadopt *ctx);
 
 /* dont load (specified) options fro config file */
 #define AUTOOPT_NOCONFIG(s,l,f,i)   OPTION_ARG_OPT( s, l, OPT_SKIP | f, i)
diff --git a/src/liblimb/include/limb/parseopt.h b/src/liblimb/include/limb/parseopt.h
index d346da7..ad214a4 100644
--- a/src/liblimb/include/limb/parseopt.h
+++ b/src/liblimb/include/limb/parseopt.h
@@ -13,9 +13,9 @@ enum {
 };
 
 /* to define options with no arg, optinal arg, required arg */
-#define OPTION_ARG_NONE(s,l,f,i)    { s, l, ARG_NONE, f, i }
-#define OPTION_ARG_OPT(s,l,f,i)     { s, l, ARG_OPT,  f, i }
-#define OPTION_ARG_REQ(s,l,f,i)     { s, l, ARG_REQ,  f, i }
+#define OPTION_ARG_NONE(s,l,f,i)    { s, l, ARG_NONE, f, 0, i }
+#define OPTION_ARG_OPT(s,l,f,i)     { s, l, ARG_OPT,  f, 0, i }
+#define OPTION_ARG_REQ(s,l,f,i)     { s, l, ARG_REQ,  f, 0, i }
 
 /* last element in struct option[] to indicate the end */
 #define OPTION_DONE                 { .flags = 1 }
@@ -29,8 +29,9 @@ enum {
 struct option {
     const char  shortopt;
     const char *longopt;
-    u8 arg      : 2; /* ARG_* */
-    u8 flags    : 6; /* OPT_* -- loadopt only */
+    u16 arg     :  2; /* ARG_* */
+    u16 flags   : 10; /* OPT_* -- loadopt only */
+    u16 _unused :  4;
     int id;
 };
 
diff --git a/src/liblimb/loadopt.h/loadopt.c b/src/liblimb/loadopt.h/loadopt.c
index 3097938..d39905c 100644
--- a/src/liblimb/loadopt.h/loadopt.c
+++ b/src/liblimb/loadopt.h/loadopt.c
@@ -5,6 +5,7 @@
 #include <errno.h>
 #include <limb/bytestr.h>
 #include <limb/djbunix.h>
+#include <limb/esc.h>
 #include <limb/loadopt.h>
 #include <limb/output.h>
 #include <limb/readopt.h>
@@ -29,10 +30,58 @@ get_optflags(const u8 *optflags, int idx)
     return b & 0xf;
 }
 
+enum {
+    ERR_PA_BADESC       = -1,
+    ERR_PA_INVAL_PATH   = -2,
+    ERR_PA_INVAL_FILE   = -3,
+};
+
+static int
+process_arg(stralloc *sa, const struct option *option, struct loadopt *ctx)
+{
+    const char * const arg = sa->s + ctx->optoff;
+    size_t len = sa->len - ctx->optoff - 1;
+
+    /* need to unescape the argument? */
+    if (len >= 2 && (option->flags & OPT_ESC_) && arg[0] == '"' && arg[len - 1] == '"') {
+        len -= 2;
+        ssize_t rr = esc_scan(sa->s + ctx->optoff, len, arg + 1, len);
+        if (rr < 0) return ERR_PA_BADESC;
+        sa->len = ctx->optoff + rr;
+        sa->s[sa->len++] = 0;
+        len = rr;
+    }
+
+    /* check for NUL */
+    if ((option->flags & OPT_PATH_) && byte_chr(arg, len, 0) < len)
+        return ERR_PA_INVAL_PATH;
+
+    /* check for '/' */
+    if ((option->flags & OPT_FILE_) && byte_chr(arg, len, '/') < len)
+        return ERR_PA_INVAL_FILE;
+
+    return 0;
+}
+
+static void
+pa_warn(int r, int is_arg, const struct option *option)
+{
+    const char *m;
+    switch (r) {
+        case ERR_PA_BADESC:     m = "bad escaping"; break;
+        case ERR_PA_INVAL_PATH: m = "invalid path: contains a NUL byte"; break;
+        case ERR_PA_INVAL_FILE: m = "invalid file: contains a '/'"; break;
+        default: return;
+
+    }
+    char buf[2] = { option->shortopt, 0 };
+    warn("invalid argument ", (is_arg) ? "<" : "for option --", option->longopt,
+         (*buf) ? "/-" : "", (is_arg) ? ">" : buf, ": ", m);
+}
+
 int
-loadopt(int argc, const char **argv, const struct option *options,
-        int bfd, const char *confdir, char *buf, size_t blen,
-        unsigned int poflags, struct loadopt *ctx)
+loadopt(stralloc *sa, int argc, const char **argv, const struct option *options,
+        int bfd, const char *confdir, unsigned int poflags, struct loadopt *ctx)
 {
     /* init */
     if (!ctx->state) {
@@ -47,8 +96,24 @@ loadopt(int argc, const char **argv, const struct option *options,
         int c;
         c = parseopt(argc, argv, options, poflags, &ctx->po);
 
-        if (c > 0)
+        if (c > 0) {
             add_optflags(ctx->optflags, ctx->po.idx, OPT_SET);
+            /* put argument into sa? */
+            if (ctx->po.arg && (options[ctx->po.idx].flags & OPT_SA)) {
+                ctx->optoff = sa->len;
+                if (!stralloc_cats0(sa, ctx->po.arg))
+                    return -1;
+                ctx->po.arg = NULL;
+
+                /* extra processing: unescaping, path/file checking */
+                int r = process_arg(sa, &options[ctx->po.idx], ctx);
+                if (r < 0) {
+                    if (!(poflags & PARSEOPT_SILENT))
+                        pa_warn(r, 0, &options[ctx->po.idx]);
+                    return -1;
+                }
+            }
+        }
 
         if (c)
             return c;
@@ -103,10 +168,18 @@ loadopt(int argc, const char **argv, const struct option *options,
                 continue;
 
             ssize_t r;
-            r = readopt(buf, blen - 1, dirfd(ctx->confdir), de->d_name);
-            if (r < 0) {
-                warnusys("read ", ESC, confdir, "/", de->d_name, ESC);
-                return -1;
+            for (int i = 1; ; ++i) {
+                if (!stralloc_readyplus(sa, i * 256))
+                    r = -1;
+                else
+                    r = readopt(sa->s + sa->len, sa->a - sa->len - 1,
+                                dirfd(ctx->confdir), de->d_name);
+
+                if (r >= 0) break;
+                if (r < 0 && errno != ENOBUFS) {
+                    warnusys("read ", ESC, confdir, "/", de->d_name, ESC);
+                    return -1;
+                }
             }
 
             ctx->po.idx = i;
@@ -128,10 +201,26 @@ loadopt(int argc, const char **argv, const struct option *options,
             if (options[i].arg == ARG_NONE || !r) {
                 ctx->po.arg = NULL;
             } else {
-                buf[r] = 0;
-                ctx->po.arg = buf;
+                sa->s[sa->len + r] = 0;
+                ctx->po.arg = sa->s + sa->len;
             }
             ctx->from_file = 1;
+
+            /* put argument into sa */
+            if (ctx->po.arg && (options[i].flags & OPT_SA)) {
+                ctx->optoff = sa->len;
+                sa->len += r + 1;
+                ctx->po.arg = NULL;
+
+                /* extra processing: unescaping, path/file checking */
+                int r = process_arg(sa, &options[i], ctx);
+                if (r < 0) {
+                    if (!(poflags & PARSEOPT_SILENT))
+                        pa_warn(r, 0, &options[i]);
+                    return -1;
+                }
+            }
+
             return (options[i].id) ? options[i].id : options[i].shortopt;
         }
         dir_close(ctx->confdir);
@@ -144,51 +233,70 @@ loadopt(int argc, const char **argv, const struct option *options,
 
     /* confdir done, check all required options were set */
     if (ctx->state == STATE_CONFIG) {
-        int i;
-        for (i = 0; options[i].longopt; ++i) {
-            if ((get_optflags(ctx->optflags, i) & (OPT_REQ | OPT_SET)) == OPT_REQ) {
-                char buf[2] = { options[i].shortopt, 0 };
-                warn("option --", options[i].longopt, (*buf) ? "/-" : "", buf, " missing");
+        for (ctx->po.idx = 0; options[ctx->po.idx].longopt; ++ctx->po.idx) {
+            if ((get_optflags(ctx->optflags, ctx->po.idx) & (OPT_REQ | OPT_SET)) == OPT_REQ) {
+                char buf[2] = { options[ctx->po.idx].shortopt, 0 };
+                warn("option --", options[ctx->po.idx].longopt, (*buf) ? "/-" : "", buf, " missing");
                 return (errno = ENOKEY, -1);
             }
         }
-        /* re-use off as the current argument's index from w/in options */
-        ctx->po.off = i;
-        /* set arg as the first argument */
-        ctx->po.arg = (const char *) (uintptr_t) ctx->po.cur;
+        --ctx->po.idx;
+        ctx->left = 0;
         ctx->state = STATE_OPTIONS;
     }
 
-    /* options done, on to arguments */
+    /* options done, arguments? */
     if (ctx->state == STATE_OPTIONS) {
-        while (ctx->state == STATE_OPTIONS) {
-            const struct option *arg = &options[ctx->po.off];
-            if (arg->flags & OPT_DONE) {
+nextarg:
+        ++ctx->po.idx;
+        const struct option *arg = &options[ctx->po.idx];
+        if (arg->flags & OPT_DONE) {
+            /* LOADOPT_DONE / OPTION_DONE || LOADOPT_STOP */
+            if (arg->arg == ARG_NONE || arg->arg == ARG_OPT) {
+                /* DONE means there shouldn't be any more args */
                 if (arg->arg == ARG_NONE && ctx->po.cur < argc) {
                     warn("too many arguments");
                     return (errno = ETOOMANYREFS, -1);
-                } else if (arg->arg == ARG_REQ) {
-                    ++ctx->po.off;
-                    continue;
                 }
-                /* ARG_NONE w/out args, or ARG_OPT; i.e. ok we're done */
-                break;
+                ctx->state = STATE_ARGS;
+            } else if (arg->arg == ARG_REQ) {
+                /* LOADOPT_ARGUMENTS: on to arguments now... */
+                goto nextarg;
             }
+        } else {
+            if (ctx->po.cur == argc) {
+                if (arg->arg == ARG_REQ && !ctx->left) {
+                    warn("argument <", arg->longopt, "> missing");
+                    return (errno = ENODATA, -1);
+                }
+                ctx->state = STATE_ARGS;
+            } else {
+                ctx->po.arg = argv[ctx->po.cur];
 
-            if (arg->arg == ARG_REQ && ctx->po.cur == argc) {
-                warn("argument ", ESC, arg->longopt, ESC," missing");
-                return (errno = ENODATA, -1);
-            }
+                /* put argument into sa? */
+                if (arg->flags & OPT_SA) {
+                    ctx->optoff = sa->len;
+                    if (!stralloc_cats0(sa, ctx->po.arg))
+                        return -1;
+                    ctx->po.arg = NULL;
 
-            /* next argument from command line */
-            ++ctx->po.cur;
-            /* next argument defined/to check for */
-            ++ctx->po.off;
+                    /* extra processing: unescaping, path/file checking */
+                    int r = process_arg(sa, arg, ctx);
+                    if (r < 0) {
+                        if (!(poflags & PARSEOPT_SILENT))
+                            pa_warn(r, 1, arg);
+                        return -1;
+                    }
+                }
+
+                if (arg->flags & OPT_RPT) {
+                    ctx->left = 1;
+                    --ctx->po.idx;
+                }
+                ++ctx->po.cur;
+                return arg->id;
+            }
         }
-        /* set cur to the first argument */
-        ctx->po.cur = (uintptr_t) ctx->po.arg & 0xffff;
-        /* done */
-        ctx->state = STATE_ARGS;
     }
 
     /* arguments done, we're finally done */