Welcome to little lamb

Code » limb » commit a7b7a9b

loadopt(): Use confdir via readopt()

author Olivier Brunel
2023-05-09 18:51:31 UTC
committer Olivier Brunel
2023-07-05 07:37:02 UTC
parent a74e0973120aa441eb95e0f793ef72f3409372b0

loadopt(): Use confdir via readopt()

src/doc/loadopt.h/loadopt.3.md +40 -43
src/include/loadopt.h +1 -2
src/liblimb/include/limb/loadopt.h +6 -6
src/liblimb/loadopt.h/loadopt.c +66 -103

diff --git a/src/doc/loadopt.h/loadopt.3.md b/src/doc/loadopt.h/loadopt.3.md
index 4b6b24a..0395f37 100644
--- a/src/doc/loadopt.h/loadopt.3.md
+++ b/src/doc/loadopt.h/loadopt.3.md
@@ -11,8 +11,8 @@ loadopt - parse options from command-line and (optionally) configuration file
 
 ```pre hl
 int loadopt(int <em>argc</em>, const char **<em>argv</em>, const struct option *<em>options</em>,
-            const char *<em>file</em>, const char *<em>section</em>, unsigned int <em>flags</em>,
-            struct loadopt *<em>ctx</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>)
 
 const char *LO_ARG(struct loadopt *<em>ctx</em>)
 u16 LO_CUR(struct loadopt *<em>ctx</em>)
@@ -21,10 +21,15 @@ int LO_IDX(struct loadopt *<em>ctx</em>)
 
 # DESCRIPTION
 
-The `loadopt`() function parses command-line arguments. Then, optionally, it
-will read a configuration file to load any options that hasn't been set yet.
-After that it ensures any option marked as required has been set, then may check
-for non-options arguments.
+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
+*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
+directory.
+
+After that it ensures any option marked as required (via *OPT_REQ*) has been
+set, then may check for non-options arguments.
 
 The actual parsing of options is done through [parseopt](3), as such many of the
 arguments to `loadopt`() are the same as to [parseopt](3), namely `argc`,
@@ -35,14 +40,14 @@ relevant here : it allows to define option-specific flags. Specifically, its
 value is constructed as a bitwise-inclusive OR of the following :
 
 : *OPT_SKIP*
-:: To indicate this option shall not be set from configuration file, i.e. can
-:: only be used from command-line.
+:: To indicate this option shall not be set from configuration directory, i.e.
+:: can only be used from command-line.
 
 : *OPT_REQ*
 :: To indicate this option is required, i.e. error out if after having parsed
 :: all command-line options (and, optionally, options set in configuration
-:: file), the option has not been set.
-:: Mostly useful when option that require an argument.
+:: directory), the option has not been set.
+:: Mostly useful with options that require an argument.
 
 The last argument `ctx` is an opaque structure that should be initialized to
 *LOADOPT_ZERO*.
@@ -52,40 +57,32 @@ Accessing information is done through macros `LO_ARG`(), `LO_IDX`() and
 *PO_IDX*() and *PO_CUR*() respectively.
 
 Additionally, macro `LO_FROMFILE`() will return 1 when the current option came
-from configuration file, zero otherwise (i.e. from command-line).
+from configuration directory, zero otherwise (i.e. from command-line).
 
-## Configuration File
+## Configuration Directory
 
 When called, `loadopt`() will first defer to [parseopt](3) to handle parsing of
-command-line arguments. Once done, if `file` was specified (i.e. is not NULL)
-and the corresponding file exists, it will be read and parsed as an INI-like
-configuration file.
-
-That is, it expects to find on every line a long option name (without the "--"),
-optionally followed by an argument. No escaping of any kind is supported.
-Such options are then simply processed through [parseopt](3) as-if they had been
-specified on command-line, and can thusly be processed exactly as such by the
-caller.
-Note however that any option already set (on command-line) will be ignored, as
-command-line options overwrite the defaults from configuration file.
+command-line arguments. Once done, if `confdir` was specified (i.e. is not
+*NULL*) and the corresponding directory exists, it will be read and options
+loaded from it.
+
+That is, for every option that hasn't been set nor marked via *OPT_SKIP* it will
+try and read the /first line/ of a file whose name is that of the full long
+option name (member `longopt`). Anything after the first newline (`\n`) is
+ignored (can be thought of as comments).
+
+The content of the first line, if any, is treated /as is/ as the option's
+argument (no trimming is performed, though the actual newline byte is
+obviously not included), and process happens just as it would for an option
+specified on command-line.
 
 ! HINT:
 ! You can use `LO_FROMFILE`() to know whether an option was specified on
-! command-line or from configuration file 
+! command-line or from configuration directory 
 !
-! Note that when an option was specified on configuration file, its argument, if
-! any, will /not/ be preserved/remain valid once parsing the option is done.
-
-If `section` was not NULL, `loadopt`() will first look for a section of the
-specified name in the file (defined by a line containing nothing but an open
-square bracket '[', the section's name as specified (case-sensitive), and a
-close square bracket ']'), and only process options from within said section.
-
-Very little is done in parsing the file, however any space at the beginning of
-a line will be skipped/ignored (except for the section header, as described
-above). Additionally, if the first character (save for spaces) on a line is
-either a semi-colon (`;`) or a number sign (`#`) then the entire line is
-ignored, allowing for comments.
+! 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.
 
 ## Command-line Arguments
 
@@ -150,20 +147,20 @@ error.
 The `loadopt`() function may fail if :
 
 : *ENOMEM*
-:: Not enough memory to load configuration file
+:: Not enough memory to read from configuration directory.
 
 : *ENOKEY*
-:: A required option was missing/not specified
+:: A required option was missing/not specified.
 
 : *ENODATA*
-:: A required argument was missing/not specified
+:: A required argument was missing/not specified.
 
 : *ETOOMANYREFS*
-:: Too many arguments were specified
+:: Too many arguments were specified.
 
 The `loadopt`() function may also fail for any of the errors specified for
-[parseopt](3), [openslurpclose](3) save for *ENOENT*.
+[parseopt](3), [readdir](3) and [readopt](3), save for *ENOBUFS*.
 
 # SEE ALSO
 
-[autoopt](3)
+[autoopt](3), [readopt](3)
diff --git a/src/include/loadopt.h b/src/include/loadopt.h
index 6103b1f..b90db60 100644
--- a/src/include/loadopt.h
+++ b/src/include/loadopt.h
@@ -11,8 +11,7 @@ enum state {
     STATE_NONE = 0,
     STATE_INIT,
     STATE_CMDLINE,
-    STATE_FILE,
-    STATE_SECTION,
+    STATE_CONFDIR,
     STATE_CONFIG,
     STATE_OPTIONS,
     STATE_ARGS,
diff --git a/src/liblimb/include/limb/loadopt.h b/src/liblimb/include/limb/loadopt.h
index be8f949..c18fc15 100644
--- a/src/liblimb/include/limb/loadopt.h
+++ b/src/liblimb/include/limb/loadopt.h
@@ -4,7 +4,7 @@
 #ifndef LIMB_LOADOPT_H
 #define LIMB_LOADOPT_H
 
-#include <skalibs/stralloc.h>
+#include <limb/direntry.h>
 #include <limb/parseopt.h>
 
 enum {
@@ -31,17 +31,17 @@ enum {
 /* opaque structure */
 struct loadopt {
     struct parseopt po;
-    stralloc sa;
+    DIR *confdir;
     size_t saoff;
     u8 optflags[64];
     u16 loflags         : 4;    /* LOADOPT_* */
     u16 state           : 4;
-    u16 _unused         : 7;
+    u16 left            : 7;
     /* public (read-only) */
     u16 from_file       : 1;
 };
 
-#define LOADOPT_ZERO        { PARSEOPT_ZERO, STRALLOC_ZERO, 0, { 0 }, 0, 0, 0, 0 }
+#define LOADOPT_ZERO        { PARSEOPT_ZERO, NULL, 0, { 0 }, 0, 0, 0, 0 }
 
 #define LO_ARG(ctx)         (ctx)->po.arg
 #define LO_IDX(ctx)         (ctx)->po.idx
@@ -49,8 +49,8 @@ struct loadopt {
 #define LO_FROMFILE(ctx)    (ctx)->from_file
 
 extern int loadopt(int argc, const char **argv, const struct option *options,
-                   const char *file, const char *section, unsigned int poflags,
-                   struct loadopt *ctx);
+                   int bfd, const char *confdir, char *buf, size_t blen,
+                   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/loadopt.h/loadopt.c b/src/liblimb/loadopt.h/loadopt.c
index b14af80..3097938 100644
--- a/src/liblimb/loadopt.h/loadopt.c
+++ b/src/liblimb/loadopt.h/loadopt.c
@@ -7,6 +7,7 @@
 #include <limb/djbunix.h>
 #include <limb/loadopt.h>
 #include <limb/output.h>
+#include <limb/readopt.h>
 #include "parseopt.h"
 #include "loadopt.h"
 
@@ -30,8 +31,8 @@ get_optflags(const u8 *optflags, int idx)
 
 int
 loadopt(int argc, const char **argv, const struct option *options,
-        const char *file, const char *section, unsigned int poflags,
-        struct loadopt *ctx)
+        int bfd, const char *confdir, char *buf, size_t blen,
+        unsigned int poflags, struct loadopt *ctx)
 {
     /* init */
     if (!ctx->state) {
@@ -54,132 +55,94 @@ loadopt(int argc, const char **argv, const struct option *options,
         ctx->state = STATE_CMDLINE;
     }
 
-    /* command line is done, check if there's still a need to load from file */
-    if (ctx->state == STATE_CMDLINE || ctx->state == STATE_SECTION) {
-        if (file) {
-            int i;
+    /* command line is done, check if there's still a need to load from confdir */
+    if (ctx->state == STATE_CMDLINE) {
+        if (confdir) {
             /* find next option to be read from file */
-            for (i = 0; options[i].longopt; ++i)
+            int i;
+            for (ctx->left = 0, i = 0; options[i].longopt; ++i)
                 if (!(get_optflags(ctx->optflags, i) & (OPT_SET | OPT_SKIP)))
-                    break;
-            if (!options[i].longopt) {
+                    ++ctx->left;
+            if (!ctx->left)
                 /* all options are set or to be skipped from file */
-                if (ctx->state == STATE_SECTION)
-                    stralloc_free(&ctx->sa);
                 ctx->state = STATE_CONFIG;
-            }
         } else {
             ctx->state = STATE_CONFIG;
         }
     }
 
-    /* command line is done, now onto the defined file */
+    /* command line is done, now onto the confdir */
     if (ctx->state == STATE_CMDLINE) {
-        /* prepend buffer with LF to make searching for section easier */
-        if (!stralloc_catb(&ctx->sa, "\n", 1)
-                || !openslurpclose(&ctx->sa, file)) {
-            stralloc_free(&ctx->sa);
+        ctx->confdir = opendirat(bfd, confdir);
+        if (ctx->confdir) {
+            ctx->state = STATE_CONFDIR;
+        } else {
             ctx->state = STATE_CONFIG;
             if (errno != ENOENT) {
-                warnusys("read ", ESC, file, ESC);
+                warnusys("open ", ESC, confdir, ESC);
                 return -1;
             }
-        } else {
-            ctx->state = STATE_FILE;
         }
     }
 
-    /* file is read, position into the right section */
-    if (ctx->state == STATE_FILE) {
-        if (section) {
-            /* we prepend a LF as we did into the sa to make searching easier,
-             * we also append a another LF because it there's not, it's EOF and
-             * therefore and empty section, which means nothing to do */
-            size_t llen = 4 + strlen(section);
-            char line[llen];
-            line[0] = '\n';
-            line[1] = '[';
-            memcpy(line + 2, section, llen - 4);
-            line[llen - 2] = ']';
-            line[llen - 1] = '\n';
-
-            size_t o = byte_str(ctx->sa.s, ctx->sa.len, line, llen);
-            /* eof? section doesn't exist, so we're done */
-            if (o == ctx->sa.len) {
-                stralloc_free(&ctx->sa);
-                ctx->state = STATE_CONFIG;
-            } else {
-                /* position into the section */
-                ctx->saoff = o + llen;
-                ctx->state = STATE_SECTION;
-            }
-        } else {
-            ctx->state = STATE_SECTION;
-        }
-    }
+    /* confdir is opened, parse options */
+    if (ctx->state == STATE_CONFDIR) {
+        direntry *de;
 
-    /* file is opened at the right section, parse options */
-    if (ctx->state == STATE_SECTION) {
-nextfileopt:
-
-        /* allow/ignore spaces at beginning of line */
-        for ( ; ctx->saoff < ctx->sa.len; ++ctx->saoff) {
-            char c = ctx->sa.s[ctx->saoff];
-            /* allow comments */
-            if (c == ';' || c == '#')
-                ctx->saoff += byte_chr(ctx->sa.s + ctx->saoff,
-                                       ctx->sa.len - ctx->saoff, '\n');
-            else if (!isspace(c))
-                break;
-        }
+        errno = 0;
+        while ((de = readdir(ctx->confdir))) {
+            if (ISDOTDOT(de)) continue;
 
-        if (ctx->saoff >= ctx->sa.len || ctx->sa.s[ctx->saoff] == '[') {
-            stralloc_free(&ctx->sa);
-            ctx->state = STATE_CONFIG;
-        } else {
-            /* we want a full line */
-            size_t o = ctx->saoff + byte_chr(ctx->sa.s + ctx->saoff,
-                                             ctx->sa.len - ctx->saoff, '\n');
-            /* if found (i.e. not last line in the file) NUL-terminate it */
-            if (o < ctx->sa.len)
-                ctx->sa.s[o] = 0;
-
-            struct parseopt po = { 0 };
-            const char *argv[] = { "", ctx->sa.s + ctx->saoff };
-
-            errno = 0;
-            int c = parseopt(2, argv, options, PARSEOPT_IS_LONG
-                             | PARSEOPT_STRICT | PARSEOPT_SILENT, &po);
-
-            /* seek past the line */
-            ctx->saoff = o + 1;
-
-            /* ignore already set option, and "argument required" error for
-             * already set option */
-            if ((c > 0 || errno == ENOMSG)
-                    && (get_optflags(ctx->optflags, po.idx) & OPT_SET)) {
-                goto nextfileopt;
+            int i;
+            for (i = 0; options[i].longopt; ++i)
+                if (!strcmp(options[i].longopt, de->d_name))
+                    break;
+            if (!options[i].longopt
+                    /* ignore options to skip or already set */
+                    || (get_optflags(ctx->optflags, i) & (OPT_SET | OPT_SKIP)))
+                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;
             }
 
-            if (c > 0)
-                add_optflags(ctx->optflags, po.idx, OPT_SET);
-            else if (c < 0 && !(poflags & PARSEOPT_SILENT))
-                parseopt_warn(argv, options, &po);
-
-            if (c) {
-                /* adjust returned value */
-                ctx->po.arg = po.arg;
-                ctx->po.idx = po.idx;
-                ctx->from_file = 1;
-                return c;
+            ctx->po.idx = i;
+            /* mark option as set */
+            add_optflags(ctx->optflags, i, OPT_SET);
+            if (!--ctx->left) {
+                /* no more option could be set from confdir */
+                dir_close(ctx->confdir);
+                ctx->state = STATE_CONFIG;
             }
-
-            stralloc_free(&ctx->sa);
-            ctx->state = STATE_CONFIG;
+            /* if required, ensure there's an argument */
+            if (options[i].arg == ARG_REQ && !r) {
+                errno = ENOMSG;
+                if (!(poflags & PARSEOPT_SILENT))
+                    parseopt_warn(argv, options, &ctx->po);
+                return -1;
+            }
+            /* set our return values */
+            if (options[i].arg == ARG_NONE || !r) {
+                ctx->po.arg = NULL;
+            } else {
+                buf[r] = 0;
+                ctx->po.arg = buf;
+            }
+            ctx->from_file = 1;
+            return (options[i].id) ? options[i].id : options[i].shortopt;
+        }
+        dir_close(ctx->confdir);
+        ctx->state = STATE_CONFIG;
+        if (errno) {
+            warnusys("read ", ESC, confdir, ESC);
+            return -1;
         }
     }
 
-    /* config file done, check all required options were set */
+    /* confdir done, check all required options were set */
     if (ctx->state == STATE_CONFIG) {
         int i;
         for (i = 0; options[i].longopt; ++i) {