diff --git a/cli/Makefile b/cli/Makefile index 5afdb39ca..82dea907e 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -8,6 +8,7 @@ ALL_PROGRAMS += cli/lightning-cli LIGHTNING_CLI_COMMON_OBJS := \ bitcoin/chainparams.o \ common/configdir.o \ + common/configvar.o \ common/json_parse_simple.o \ common/status_levels.o \ common/utils.o \ diff --git a/common/Makefile b/common/Makefile index 8b63eb3ca..1944a5f15 100644 --- a/common/Makefile +++ b/common/Makefile @@ -23,6 +23,7 @@ COMMON_SRC_NOGEN := \ common/close_tx.c \ common/coin_mvt.c \ common/configdir.c \ + common/configvar.c \ common/cryptomsg.c \ common/daemon.c \ common/daemon_conn.c \ diff --git a/common/configdir.c b/common/configdir.c index 0976fe0b7..a53c9273e 100644 --- a/common/configdir.c +++ b/common/configdir.c @@ -384,7 +384,9 @@ void initial_config_opts(const tal_t *ctx, opt_free_table(); opt_register_early_arg("--conf=", - opt_restricted_cmdline, NULL, + opt_restricted_cmdline, + /* This doesn't show if NULL! */ + opt_show_charp, config_filename, "Specify configuration file"); diff --git a/common/configvar.c b/common/configvar.c new file mode 100644 index 000000000..c58d840bf --- /dev/null +++ b/common/configvar.c @@ -0,0 +1,231 @@ +#include "config.h" +#include +#include +#include +#include +#include + +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); +} diff --git a/common/configvar.h b/common/configvar.h new file mode 100644 index 000000000..fc126e8f2 --- /dev/null +++ b/common/configvar.h @@ -0,0 +1,177 @@ +#ifndef LIGHTNING_COMMON_CONFIGVAR_H +#define LIGHTNING_COMMON_CONFIGVAR_H +#include "config.h" +#include +#include +#include + +/* There are five possible sources of config options: + * 1. The cmdline (and the cmdline, but it's a short option!) + * 2. An explicitly named config (--conf=) (and its includes) + * 3. An implied config (~/.lightning/config) (and includes) + * 4. A network config ((~/.lightning//config) (and includes) + * 5. A plugin start parameter. + * + * Turns out we care: you can't set network in a network config for + * example. + */ +enum configvar_src { + CONFIGVAR_CMDLINE, + CONFIGVAR_CMDLINE_SHORT, + CONFIGVAR_EXPLICIT_CONF, + CONFIGVAR_BASE_CONF, + CONFIGVAR_NETWORK_CONF, + CONFIGVAR_PLUGIN_START, +}; + +/* This represents the configuration variables specified; they are + * matched with ccan/option's opt_table which contains the + * available options. */ +struct configvar { + /* NULL if CONFIGVAR_CMDLINE* or CONFIGVAR_PLUGIN_START */ + const char *file; + /* 1-based line number (unused if !file) */ + u32 linenum; + /* Where did we get this from? */ + enum configvar_src src; + /* Never NULL, the whole line */ + const char *configline; + + /* These are filled in by configvar_parse */ + /* The variable name (without any =) */ + const char *optvar; + /* NULL for no-arg options, otherwise points after =. */ + const char *optarg; + /* Was this overridden by a following option? */ + bool overridden; +}; + +/* Set if multiple options accumulate (for listconfigs) */ +#define OPT_MULTI (1 << OPT_USER_START) +/* Set if developer-only */ +#define OPT_DEV (1 << (OPT_USER_START+1)) + +/* Use this instead of opt_register_*_arg if you want OPT_MULTI/OPT_DEV */ +#define clnopt_witharg(names, type, cb, show, arg, desc) \ + _opt_register((names), \ + OPT_CB_ARG((cb), (type), (show), (arg)), \ + (arg), (desc)) + +#define clnopt_noarg(names, type, cb, arg, desc) \ + _opt_register((names), \ + OPT_CB_NOARG((cb), (type), (arg)), \ + (arg), (desc)) + +/** + * configvar_new: allocate a fresh configvar + * @ctx: parent to tallocate off + * @src: where this came from + * @file: filename (or NULL if cmdline) + * @linenum: 1-based line number (or 0 for cmdline) + * @configline: literal option (for argv[], after `--`) + * + * optvar/optarg/multi/overridden are only set by configvar_parse. + */ +struct configvar *configvar_new(const tal_t *ctx, + enum configvar_src src, + const char *file TAKES, + size_t linenum, + const char *configline TAKES) + NON_NULL_ARGS(5); + +/** + * configvar_dup: copy a configvar + * @ctx: parent to tallocate off + * @cv: configvar to copy. + */ +struct configvar *configvar_dup(const tal_t *ctx, + const struct configvar *cv TAKES) + NON_NULL_ARGS(2); + +/** + * configvar_join: join two configvar arrays + * @ctx: parent to tallocate off + * @first: configvars to copy first + * @second: configvars to copy second. + */ +struct configvar **configvar_join(const tal_t *ctx, + struct configvar **first TAKES, + struct configvar **second TAKES); + +/** + * configvar_parse: parse this configuration variable + * @cv: the configuration setting. + * @early: if we're doing early parsing. + * @full_knowledge: error if we don't know this option. + * @developer: if we're in developer mode (allow OPT_DEV options). + * + * This returns a string if parsing failed: if early_and_incomplete is + * set, it doesn't complain about unknown options, and only parses + * OPT_EARLY options. Otherwise it parses all non-OPT_EARLY options, + * and returns an error if they don't exist. + * + * On NULL return (success), cv->optvar, cv->optarg, cv->mult are set. + */ +const char *configvar_parse(struct configvar *cv, + bool early, + bool full_knowledge, + bool developer) + NON_NULL_ARGS(1); + +/** + * configvar_unparsed: set up configuration variable, but don't parse it + * @cv: the configuration setting. + * + * This returns the opt_table which matches this configvar, if any, + * and if successful initializes cv->optvar and cv->optarg. + */ +const struct opt_table *configvar_unparsed(struct configvar *cv) + NON_NULL_ARGS(1); + +/** + * configvar_finalize_overrides: figure out which vars were overridden + * @cvs: the tal_arr of configuration settings. + * + * Any non-multi variables are overridden by successive ones. Sets + * cv->overridden for each configvar. + */ +void configvar_finalize_overrides(struct configvar **cvs); + +/** + * configvar_remove: remove the last configvar with this name if any. + * @cvs: pointer to tal_arr of configuration settings. + * @name: name to remove. + * @src: source type to remove. + * @optarg: if non-NULL, the argument to match too. + * + * We have to un-override the now-last setting, if any. + */ +void configvar_remove(struct configvar ***cvs, + const char *name, + enum configvar_src src, + const char *optarg) + NON_NULL_ARGS(1, 2); + +/** + * configvar_first: get the first non-overridden configvar of this name. + * @cvs: the tal_arr of configuration settings. + * @names: the tal_arr() of names to look for. + * + * Returns NULL if it wasn't set. + */ +struct configvar *configvar_first(struct configvar **cvs, const char **names); + +/** + * configvar_next: get the next non-overridden configvar of same name. + * @cvs: the tal_arr of configuration settings. + * @prev: the non-NULL return from configvar_first/configvar_next + * @names: the tal_arr() of names to look for. + * + * This can only return non-NULL for OPT_MULTI options which are actually + * specified multiple times. + */ +struct configvar *configvar_next(struct configvar **cvs, + const struct configvar *prev, + const char **names); + +#endif /* LIGHTNING_COMMON_CONFIGVAR_H */