author | Olivier Brunel
<jjk@jjacky.com> 2023-05-09 18:51:31 UTC |
committer | Olivier Brunel
<jjk@jjacky.com> 2023-07-05 07:37:02 UTC |
parent | a74e0973120aa441eb95e0f793ef72f3409372b0 |
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) {