core-lightning/common/configvar.c
Rusty Russell 36200a6593 common/configvar: routines to manage config settings.
These are gathered from the config files and the commandline, but the
process is rather complex!  We want to remember where the options came
from in future (for a `setconfig` command), and also generalize
and simplify handling.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2023-06-03 10:50:29 +09:30

232 lines
5.7 KiB
C

#include "config.h"
#include <assert.h>
#include <ccan/cast/cast.h>
#include <ccan/tal/str/str.h>
#include <common/configvar.h>
#include <common/utils.h>
struct configvar *configvar_new(const tal_t *ctx,
enum configvar_src src,
const char *file,
size_t linenum,
const char *configline)
{
struct configvar *cv = tal(ctx, struct configvar);
if (file)
cv->file = tal_strdup(cv, file);
else
cv->file = NULL;
cv->src = src;
cv->linenum = linenum;
cv->configline = tal_strdup(cv, configline);
cv->overridden = false;
cv->optvar = NULL;
/* We fill in cv->optvar and cv->optarg when parsing! */
return cv;
}
const struct opt_table *configvar_unparsed(struct configvar *cv)
{
const struct opt_table *ot;
if (cv->src == CONFIGVAR_CMDLINE_SHORT) {
ot = opt_find_short(cv->configline[0]);
cv->optarg = NULL;
} else {
ot = opt_find_long(cv->configline, &cv->optarg);
}
if (!ot)
return NULL;
/* We get called multiple times, but we're expected to always
* finish the cv vars, even if they're added at the last minute
* on the cmdline, so we check this is only done once, and we
* do it even if we're not going to use it now. */
if (!cv->optvar) {
/* optvar is up to the = (i.e. one char before optarg) */
if (!cv->optarg)
cv->optvar = cv->configline;
else
cv->optvar = tal_strndup(cv, cv->configline,
cv->optarg - cv->configline - 1);
}
return ot;
}
const char *configvar_parse(struct configvar *cv,
bool early,
bool full_knowledge,
bool developer)
{
const struct opt_table *ot;
ot = configvar_unparsed(cv);
if (!ot) {
/* Do we ignore unknown entries? */
if (!full_knowledge)
return NULL;
return "unknown option";
}
if ((ot->type & OPT_DEV) && !developer)
return "requires DEVELOPER";
/* If we're early and we want late, or vv, ignore. */
if (!!(ot->type & OPT_EARLY) != early)
return NULL;
if (ot->type & OPT_NOARG) {
/* MULTI doesn't make sense with single args */
assert(!(ot->type & OPT_MULTI));
if (cv->optarg)
return "doesn't allow an argument";
return ot->cb(ot->u.arg);
} else {
if (!cv->optarg)
return "requires an argument";
return ot->cb_arg(cv->optarg, ot->u.arg);
}
}
/* This is O(N^2) but nobody cares */
void configvar_finalize_overrides(struct configvar **cvs)
{
/* Map to options: two different names can be the same option,
* given aliases! */
const struct opt_table **opts;
opts = tal_arr(tmpctx, const struct opt_table *, tal_count(cvs));
for (size_t i = 0; i < tal_count(cvs); i++) {
opts[i] = opt_find_long(cvs[i]->optvar, NULL);
/* If you're allowed multiple, they don't override */
if (opts[i]->type & OPT_MULTI)
continue;
for (size_t j = 0; j < i; j++) {
if (opts[j] == opts[i])
cvs[j]->overridden = true;
}
}
}
void configvar_remove(struct configvar ***cvs,
const char *name,
enum configvar_src src,
const char *optarg)
{
/* We remove all from this source, potentially restoring an overridden */
ssize_t prev = -1;
bool removed;
removed = false;
for (size_t i = 0; i < tal_count(*cvs); i++) {
/* This can happen if plugin fails during startup! */
if ((*cvs)[i]->optvar == NULL)
continue;
if (!streq((*cvs)[i]->optvar, name))
continue;
if (optarg && !streq((*cvs)[i]->optarg, optarg))
continue;
if ((*cvs)[i]->src == src) {
tal_free((*cvs)[i]);
tal_arr_remove(cvs, i);
i--;
removed = true;
continue;
}
/* Wrong type, correct name. */
prev = i;
}
/* Unmark prev if we removed overriding ones. If it's multi,
* this is a noop. */
if (removed && prev != -1)
(*cvs)[prev]->overridden = false;
}
struct configvar *configvar_dup(const tal_t *ctx, const struct configvar *cv)
{
struct configvar *ret;
if (taken(cv))
return tal_steal(ctx, cast_const(struct configvar *, cv));
ret = tal_dup(ctx, struct configvar, cv);
if (ret->file)
ret->file = tal_strdup(ret, ret->file);
if (ret->configline)
ret->configline = tal_strdup(ret, ret->configline);
if (ret->optvar) {
ret->optvar = tal_strdup(ret, ret->optvar);
/* Optarg, if non-NULL, points into cmdline! */
if (ret->optarg) {
size_t off = cv->optarg - cv->configline;
assert(off < strlen(cv->configline));
ret->optarg = ret->configline + off;
}
}
return ret;
}
struct configvar **configvar_join(const tal_t *ctx,
struct configvar **first,
struct configvar **second)
{
struct configvar **cvs;
size_t n = tal_count(first);
if (taken(first)) {
cvs = tal_steal(ctx, first);
tal_resize(&cvs, n + tal_count(second));
} else {
cvs = tal_arr(ctx, struct configvar *, n + tal_count(second));
for (size_t i = 0; i < n; i++) {
cvs[i] = configvar_dup(cvs, first[i]);
}
}
if (taken(second)) {
for (size_t i = 0; i < tal_count(second); i++)
cvs[n + i] = tal_steal(cvs, second[i]);
tal_free(second);
} else {
for (size_t i = 0; i < tal_count(second); i++)
cvs[n + i] = configvar_dup(cvs, second[i]);
}
return cvs;
}
static struct configvar *configvar_iter(struct configvar **cvs,
const char **names,
const struct configvar *firstcv)
{
for (size_t i = 0; i < tal_count(cvs); i++) {
/* Wait until we reach firstcv, if any */
if (firstcv) {
if (cvs[i] == firstcv)
firstcv = NULL;
continue;
}
for (size_t j = 0; j < tal_count(names); j++) {
/* In case we iterate before initialization! */
if (!cvs[i]->optvar)
continue;
if (streq(cvs[i]->optvar, names[j]) && !cvs[i]->overridden)
return cvs[i];
}
}
return NULL;
}
struct configvar *configvar_first(struct configvar **cvs, const char **names)
{
return configvar_iter(cvs, names, NULL);
}
struct configvar *configvar_next(struct configvar **cvs,
const struct configvar *cv,
const char **names)
{
return configvar_iter(cvs, names, cv);
}