diff --git a/cli/lightning-cli.c b/cli/lightning-cli.c index 936af1e98..392ad88fc 100644 --- a/cli/lightning-cli.c +++ b/cli/lightning-cli.c @@ -650,7 +650,7 @@ int main(int argc, char *argv[]) jsmntok_t *toks; const jsmntok_t *result, *error, *id; const tal_t *ctx = tal(NULL, char); - char *config_filename, *lightning_dir, *net_dir, *rpc_filename; + char *net_dir, *rpc_filename; jsmn_parser parser; int parserr; enum format format = DEFAULT_FORMAT; @@ -667,9 +667,8 @@ int main(int argc, char *argv[]) setup_option_allocators(); - initial_config_opts(ctx, argc, argv, - &config_filename, &lightning_dir, &net_dir, - &rpc_filename); + opt_exitcode = ERROR_USAGE; + minimal_config_opts(ctx, argc, argv, &net_dir, &rpc_filename); opt_register_noarg("--help|-h", opt_usage_and_exit, " [...]", "Show this message. Use the command help (without hyphens -- \"lightning-cli help\") to get a list of all RPC commands"); @@ -695,8 +694,6 @@ int main(int argc, char *argv[]) NULL, &commando, "Send this as a commando command to nodeid:rune"); - opt_register_version(); - opt_early_parse(argc, argv, opt_log_stderr_exit_usage); opt_parse(&argc, argv, opt_log_stderr_exit_usage); diff --git a/cli/test/Makefile b/cli/test/Makefile index 331018413..de38487ed 100644 --- a/cli/test/Makefile +++ b/cli/test/Makefile @@ -10,6 +10,7 @@ ALL_TEST_PROGRAMS += $(CLI_TEST_PROGRAMS) CLI_TEST_COMMON_OBJS := \ common/autodata.o \ common/configdir.o \ + common/configvar.o \ common/daemon_conn.o \ common/htlc_state.o \ common/json_parse_simple.o \ diff --git a/cli/test/run-human-mode.c b/cli/test/run-human-mode.c index 880a790ff..d71303ff7 100644 --- a/cli/test/run-human-mode.c +++ b/cli/test/run-human-mode.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include diff --git a/cli/test/run-large-input.c b/cli/test/run-large-input.c index 2e2c0647c..aee9bee5c 100644 --- a/cli/test/run-large-input.c +++ b/cli/test/run-large-input.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include diff --git a/cli/test/run-remove-hint.c b/cli/test/run-remove-hint.c index 06783b26f..f081ec79f 100644 --- a/cli/test/run-remove-hint.c +++ b/cli/test/run-remove-hint.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include diff --git a/common/configdir.c b/common/configdir.c index a53c9273e..3dd54b6a1 100644 --- a/common/configdir.c +++ b/common/configdir.c @@ -4,17 +4,21 @@ #include #include #include +#include #include #include #include #include +#include #include #include bool deprecated_apis = true; +int opt_exitcode = 1; /* The regrettable globals */ static const tal_t *options_ctx; +static struct configvar *current_cv; /* Override a tal string; frees the old one. */ char *opt_set_talstr(const char *arg, char **p) @@ -50,113 +54,55 @@ static void tal_freefn(void *ptr) tal_free(ptr); } -static int config_parse_line_number; - -static void config_log_stderr_exit(const char *fmt, ...) -{ - char *msg; - va_list ap; - - va_start(ap, fmt); - - /* This is the format we expect:*/ - if (streq(fmt, "%s: %.*s: %s")) { - const char *argv0 = va_arg(ap, const char *); - unsigned int len = va_arg(ap, unsigned int); - const char *arg = va_arg(ap, const char *); - const char *problem = va_arg(ap, const char *); - - assert(argv0 != NULL); - assert(arg != NULL); - assert(problem != NULL); - /*mangle it to remove '--' and add the line number.*/ - msg = tal_fmt(NULL, "%s line %d: %.*s: %s", - argv0, - config_parse_line_number, len-2, arg+2, problem); - } else { - msg = tal_vfmt(NULL, fmt, ap); - } - va_end(ap); - - errx(1, "%s", msg); -} - -static void parse_include(const char *filename, bool must_exist, bool early, - size_t depth) +static struct configvar **gather_file_configvars(const tal_t *ctx, + enum configvar_src src, + const char *filename, + bool must_exist, + size_t include_depth) { char *contents, **lines; - char **all_args; /*For each line: either `--`argument, include file, or NULL*/ - char *argv[3]; - int i, argc; + struct configvar **cvs = tal_arr(ctx, struct configvar *, 0); - contents = grab_file(NULL, filename); + contents = grab_file(tmpctx, filename); /* The default config doesn't have to exist, but if the config was * specified on the command line it has to exist. */ if (!contents) { if (must_exist) err(1, "Opening and reading %s", filename); - return; + return cvs; } + /* Break into lines. */ lines = tal_strsplit(contents, contents, "\r\n", STR_EMPTY_OK); + for (size_t i = 0; i < tal_count(lines) - 1; i++) { + /* Comments & blank lines*/ + if (strstarts(lines[i], "#") || streq(lines[i], "")) + continue; - /* We have to keep all_args around, since opt will point into it: use - * magic tal name to tell memleak this isn't one. */ - all_args = tal_arr_label(options_ctx, char *, tal_count(lines) - 1, - TAL_LABEL(options_array_notleak, "")); + if (strstarts(lines[i], "include ")) { + const char *included = lines[i] + strlen("include "); + struct configvar **sub; + + if (include_depth > 100) + errx(1, "Include loop with %s and %s", filename, included); - for (i = 0; i < tal_count(lines) - 1; i++) { - if (strstarts(lines[i], "#")) { - all_args[i] = NULL; - } else if (strstarts(lines[i], "include ")) { /* If relative, it's relative to current config file */ - all_args[i] = path_join(all_args, - take(path_dirname(NULL, - filename)), - lines[i] + strlen("include ")); - } else { - /* Only valid forms are "foo" and "foo=bar" */ - all_args[i] = tal_fmt(all_args, "--%s", lines[i]); - } - /* This isn't a leak either */ - if (all_args[i]) - tal_set_name(all_args[i], TAL_LABEL(config_notleak, "")); - } - - /* - For each line we construct a fake argc,argv commandline. - argv[1] is the only element that changes between iterations. - */ - argc = 2; - argv[0] = cast_const(char *, filename); - argv[argc] = NULL; - - for (i = 0; i < tal_count(all_args); i++) { - if (all_args[i] == NULL) - continue; - - if (!strstarts(all_args[i], "--")) { - /* There could be more, but this gives a hint. */ - if (depth > 100) - errx(1, "Include loop with %s and %s", - filename, all_args[i]); - parse_include(all_args[i], true, early, ++depth); + sub = gather_file_configvars(NULL, + src, + path_join(tmpctx, + take(path_dirname(NULL, filename)), + included), + true, + include_depth + 1); + cvs = configvar_join(ctx, take(cvs), take(sub)); continue; } - config_parse_line_number = i + 1; - argv[1] = all_args[i]; - if (early) { - opt_early_parse_incomplete(argc, argv, - config_log_stderr_exit); - } else { - opt_parse(&argc, argv, config_log_stderr_exit); - argc = 2; /* opt_parse might have changed it */ - } + tal_arr_expand(&cvs, + configvar_new(cvs, src, filename, i+1, lines[i])); } - - tal_free(contents); + return cvs; } static char *default_base_configdir(const tal_t *ctx) @@ -179,6 +125,12 @@ static char *opt_set_network(const char *arg, void *unused) { assert(arg != NULL); + /* Ignore if called directly from opt (e.g. lightning-cli) */ + if (!current_cv) + return NULL; + + if (current_cv->src == CONFIGVAR_NETWORK_CONF) + return "not permitted in network-specific configuration files"; /* Set the global chainparams instance */ chainparams = chainparams_for_network(arg); if (!chainparams) @@ -197,46 +149,27 @@ static bool opt_show_network(char *buf, size_t len, const void *unused) return true; } -/* We track where we're getting options from, so we can detect misuse */ -enum parse_state { - CMDLINE = 1, - FORCED_CONFIG = 2, - TOPLEVEL_CONFIG = 4, - NETWORK_CONFIG = 8, -}; -static enum parse_state parse_state = CMDLINE; - -static char *opt_restricted_cmdline(const char *arg, const void *unused) +static char *opt_set_config_filename(const char *arg, char **p) { - if (parse_state != CMDLINE) - return "not permitted in configuration files"; - return NULL; + /* Ignore if called directly from opt (e.g. lightning-cli) */ + if (!current_cv) + return NULL; + + if (current_cv->src == CONFIGVAR_CMDLINE) + return opt_set_abspath(arg, p); + return "not permitted in configuration files"; } -static char *opt_restricted_toplevel_noarg(const void *unused) +static char *opt_set_lightning_dir(const char *arg, char **p) { - if (parse_state == NETWORK_CONFIG) - return "not permitted in network-specific configuration files"; - return NULL; -} + /* Ignore if called directly from opt (e.g. lightning-cli) */ + if (!current_cv) + return NULL; -static char *opt_restricted_toplevel(const char *arg, const void *unused) -{ - return opt_restricted_toplevel_noarg(NULL); -} - -static char *opt_restricted_forceconf_only(const char *arg, const void *unused) -{ - if (parse_state != CMDLINE && parse_state != FORCED_CONFIG) - return "not permitted in implicit configuration files"; - return NULL; -} - -bool is_restricted_ignored(const void *fn) -{ - return fn == opt_restricted_toplevel_noarg - || fn == opt_restricted_toplevel - || fn == opt_restricted_forceconf_only; + if (current_cv->src == CONFIGVAR_CMDLINE + || current_cv->src == CONFIGVAR_EXPLICIT_CONF) + return opt_set_abspath(arg, p); + return "not permitted in implicit configuration files"; } void setup_option_allocators(void) @@ -245,103 +178,155 @@ void setup_option_allocators(void) opt_set_alloc(opt_allocfn, tal_reallocfn, tal_freefn); } -/* network is NULL for parsing top-level config file. */ -static void parse_implied_config_file(const char *config_basedir, - const char *network, - bool early) +static void parse_configvars(struct configvar **cvs, + bool early, + bool full_knowledge) { - const char *dir, *filename; + for (size_t i = 0; i < tal_count(cvs); i++) { + const char *problem; + bool should_know; - if (config_basedir) - dir = path_join(NULL, take(path_cwd(NULL)), config_basedir); - else - dir = default_base_configdir(NULL); + should_know = full_knowledge; + /* We should always know cmdline args in final parse */ + if (!early && cvs[i]->src == CONFIGVAR_CMDLINE) + should_know = true; - if (network) - dir = path_join(NULL, take(dir), network); + current_cv = cvs[i]; + problem = configvar_parse(cvs[i], + early, + should_know, + IFDEV(true, false)); + current_cv = NULL; + if (!problem) + continue; - filename = path_join(NULL, take(dir), "config"); - parse_include(filename, false, early, 0); - tal_free(filename); + if (cvs[i]->file) { + errx(opt_exitcode, "Config file %s line %u: %s: %s", + cvs[i]->file, cvs[i]->linenum, + cvs[i]->configline, problem); + } else { + errx(opt_exitcode, "--%s: %s", cvs[i]->configline, problem); + } + } } -/* If they specify --conf, we just read that. - * Otherwise we read /config then //config - */ -void parse_config_files(const char *config_filename, - const char *config_basedir, - bool early) +static void finished_arg(int *argc, char **argv, size_t *idx, + bool remove_args) { - if (config_filename) { - parse_state = FORCED_CONFIG; - parse_include(config_filename, true, early, 0); - parse_state = CMDLINE; + if (!remove_args) { + (*idx)++; return; } - - parse_state = TOPLEVEL_CONFIG; - parse_implied_config_file(config_basedir, NULL, early); - parse_state = NETWORK_CONFIG; - parse_implied_config_file(config_basedir, chainparams->network_name, early); - parse_state = CMDLINE; + memmove(argv + *idx, argv + 1 + *idx, (*argc - *idx) * sizeof(char *)); + (*argc)--; } -void initial_config_opts(const tal_t *ctx, +/* Now all options are known, we can turn cmdline into configvars */ +static struct configvar **gather_cmdline_args(const tal_t *ctx, + int *argc, char **argv, + bool remove_args) +{ + struct configvar **cvs = tal_arr(ctx, struct configvar *, 0); + + assert(argv[*argc] == NULL); + for (size_t i = 1; argv[i];) { + struct opt_table *ot; + const char *configline, *arg, *optarg; + enum configvar_src src; + bool extra_arg; + + /* End of options? */ + if (streq(argv[i], "--")) + break; + + if (!strstarts(argv[i], "-")) { + i++; + continue; + } + + if (strstarts(argv[i], "--")) { + arg = argv[i] + 2; + ot = opt_find_long(arg, &optarg); + src = CONFIGVAR_CMDLINE; + } else { + /* FIXME: We don't handle multiple short + * options here! */ + arg = argv[i] + 1; + ot = opt_find_short(arg[0]); + optarg = NULL; + src = CONFIGVAR_CMDLINE_SHORT; + } + if (ot) { + extra_arg = (ot->type & OPT_HASARG) && !optarg; + } else { + /* Unknown (yet!). Guess if next arg is for this! */ + extra_arg = ((src == CONFIGVAR_CMDLINE_SHORT + || !strchr(arg, '=')) + && argv[i+1] + && !strstarts(argv[i+1], "-")); + } + finished_arg(argc, argv, &i, remove_args); + /* We turn `--foo bar` into `--foo=bar` here */ + if (extra_arg) { + configline = tal_fmt(tmpctx, "%s=%s", arg, argv[i]); + finished_arg(argc, argv, &i, remove_args); + } else { + configline = arg; + } + tal_arr_expand(&cvs, configvar_new(cvs, src, + NULL, 0, configline)); + } + assert(argv[*argc] == NULL); + return cvs; +} + +void minimal_config_opts(const tal_t *ctx, int argc, char *argv[], - char **config_filename, - char **config_basedir, char **config_netdir, char **rpc_filename) { + char *unused_filename, *unused_basedir; + + initial_config_opts(tmpctx, &argc, argv, false, + &unused_filename, + &unused_basedir, + config_netdir, + rpc_filename); + tal_steal(ctx, *config_netdir); + tal_steal(ctx, *rpc_filename); +} + +struct configvar **initial_config_opts(const tal_t *ctx, + int *argc, char *argv[], + bool remove_args, + char **config_filename, + char **config_basedir, + char **config_netdir, + char **rpc_filename) +{ + struct configvar **cmdline_cvs, **config_cvs, **cvs; + options_ctx = ctx; - /* First, they could specify a config, which specifies a lightning dir - * or a network. */ + /* This helps opt_usage. */ + opt_argv0 = argv[0]; + + /* Default chain (a global) is bitcoin. */ + chainparams = chainparams_for_network("bitcoin"); + + /* First, they could specify a config, or base dir. */ *config_filename = NULL; - opt_register_early_arg("--conf=", opt_set_abspath, NULL, - config_filename, - "Specify configuration file"); - - /* Cmdline can also set lightning-dir. */ - *config_basedir = NULL; - opt_register_early_arg("--lightning-dir=", - opt_set_abspath, NULL, - config_basedir, - "Set base directory: network-specific subdirectory is under here"); - - /* Handle --version (and exit) here too */ - opt_register_version(); - - opt_early_parse_incomplete(argc, argv, opt_log_stderr_exit); - - /* Now, reset and ignore --conf option from now on. */ - opt_free_table(); - - /* This is only ever valid on cmdline */ opt_register_early_arg("--conf=", - opt_restricted_cmdline, - /* This doesn't show if NULL! */ + opt_set_config_filename, + /* Doesn't show if it's NULL! */ opt_show_charp, config_filename, "Specify configuration file"); - - /* If they set --conf it can still set --lightning-dir */ - if (!*config_filename) { - opt_register_early_arg("--lightning-dir=", - opt_restricted_forceconf_only, opt_show_charp, - config_basedir, - "Set base directory: network-specific subdirectory is under here"); - } else { - opt_register_early_arg("--lightning-dir=", - opt_set_abspath, NULL, - config_basedir, - "Set base directory: network-specific subdirectory is under here"); - } - - /* Now, config file (or cmdline) can set network and lightning-dir */ - - /* We need to know network early, so we can set defaults (which normal - * options can change) and default config_netdir */ + *config_basedir = default_base_configdir(ctx); + opt_register_early_arg("--lightning-dir=", + opt_set_lightning_dir, opt_show_charp, + config_basedir, + "Set base directory: network-specific subdirectory is under here"); opt_register_early_arg("--network", opt_set_network, opt_show_network, NULL, "Select the network parameters (bitcoin, testnet," @@ -355,70 +340,81 @@ void initial_config_opts(const tal_t *ctx, opt_register_early_noarg("--mainnet", opt_set_specific_network, "bitcoin", "Alias for --network=bitcoin"); + /* Handle --version (and exit) here too */ + opt_register_version(); + + /* For convenience, we set deprecated_apis and rpc_filename now, too */ opt_register_early_arg("--allow-deprecated-apis", opt_set_bool_arg, opt_show_bool, &deprecated_apis, "Enable deprecated options, JSONRPC commands, fields, etc."); - /* Read config file first, since cmdline must override */ - if (*config_filename) - parse_include(*config_filename, true, true, 0); - else - parse_implied_config_file(*config_basedir, NULL, true); - opt_early_parse_incomplete(argc, argv, opt_log_stderr_exit); + /* Allow them to override rpc-file too. */ + *rpc_filename = default_rpcfile(ctx); + opt_register_early_arg("--rpc-file", opt_set_talstr, opt_show_charp, + rpc_filename, + "Set JSON-RPC socket (or /dev/tty)"); - /* We use a global (in common/utils.h) for the chainparams. */ - if (!chainparams) - chainparams = chainparams_for_network("bitcoin"); + cmdline_cvs = gather_cmdline_args(tmpctx, argc, argv, remove_args); + parse_configvars(cmdline_cvs, true, false); - if (!*config_basedir) - *config_basedir = default_base_configdir(ctx); + /* Base default or direct config can set network */ + if (*config_filename) { + config_cvs = gather_file_configvars(NULL, + CONFIGVAR_EXPLICIT_CONF, + *config_filename, true, 0); + } else { + struct configvar **base_cvs, **net_cvs; + char *dir = path_join(tmpctx, take(path_cwd(NULL)), *config_basedir); + /* Optional: .lightning/config */ + base_cvs = gather_file_configvars(tmpctx, + CONFIGVAR_BASE_CONF, + path_join(tmpctx, dir, "config"), + false, 0); + /* This might set network! */ + parse_configvars(configvar_join(tmpctx, base_cvs, cmdline_cvs), + true, false); + + /* Now, we can get network config */ + dir = path_join(tmpctx, dir, chainparams->network_name); + net_cvs = gather_file_configvars(tmpctx, + CONFIGVAR_NETWORK_CONF, + path_join(tmpctx, dir, "config"), + false, 0); + config_cvs = configvar_join(NULL, take(base_cvs), take(net_cvs)); + } + cvs = configvar_join(ctx, take(config_cvs), cmdline_cvs); + + /* This will be called again, once caller has added their own + * early vars! */ + parse_configvars_early(cvs); *config_netdir = path_join(NULL, *config_basedir, chainparams->network_name); /* Make sure it's absolute */ *config_netdir = path_join(ctx, take(path_cwd(NULL)), take(*config_netdir)); - - /* Now, reset and ignore those options from now on. */ - opt_free_table(); - - opt_register_early_arg("--conf=", - opt_restricted_cmdline, - /* This doesn't show if NULL! */ - opt_show_charp, - config_filename, - "Specify configuration file"); - - /* This is never in a default config file (since we used the defaults to find it!). */ - opt_register_early_arg("--lightning-dir=", - opt_restricted_forceconf_only, opt_show_charp, - config_basedir, - "Set base directory: network-specific subdirectory is under here"); - opt_register_early_arg("--network", - opt_restricted_toplevel, opt_show_network, - NULL, - "Select the network parameters (bitcoin, testnet," - " signet, regtest, litecoin or litecoin-testnet)"); - opt_register_early_noarg("--mainnet", - opt_restricted_toplevel_noarg, NULL, - "Alias for --network=bitcoin"); - opt_register_early_noarg("--testnet", - opt_restricted_toplevel_noarg, NULL, - "Alias for --network=testnet"); - opt_register_early_noarg("--signet", - opt_restricted_toplevel_noarg, NULL, - "Alias for --network=signet"); - - /* They can set this later, it's just less effective. */ - opt_register_early_arg("--allow-deprecated-apis", - opt_set_bool_arg, opt_show_bool, - &deprecated_apis, - "Enable deprecated options, JSONRPC commands, fields, etc."); - - /* Set this up for when they parse cmdline proper. */ - *rpc_filename = default_rpcfile(ctx); - opt_register_arg("--rpc-file", opt_set_talstr, opt_show_charp, - rpc_filename, - "Set JSON-RPC socket (or /dev/tty)"); + return cvs; +} + +void parse_configvars_early(struct configvar **cvs) +{ + parse_configvars(cvs, true, false); +} + +void parse_configvars_final(struct configvar **cvs, + bool full_knowledge) +{ + parse_configvars(cvs, false, full_knowledge); + configvar_finalize_overrides(cvs); +} + +bool is_restricted_ignored(const void *fn) +{ + return fn == opt_set_specific_network; +} + +bool is_restricted_print_if_nonnull(const void *fn) +{ + return fn == opt_set_config_filename; } diff --git a/common/configdir.h b/common/configdir.h index 1f2d713d2..d50a0d047 100644 --- a/common/configdir.h +++ b/common/configdir.h @@ -7,26 +7,37 @@ * them early. */ extern bool deprecated_apis; +/* Unless overridden, we exit with status 1 when option parsing fails */ +extern int opt_exitcode; + /* Helper for options which are tal() strings. */ char *opt_set_talstr(const char *arg, char **p); /* Initial options setup */ void setup_option_allocators(void); -/* Parse minimal config options and files */ -void initial_config_opts(const tal_t *ctx, +/* Minimal config parsing for tools: use opt_early_parse/opt_parse after */ +void minimal_config_opts(const tal_t *ctx, int argc, char *argv[], - char **config_filename, - char **config_basedir, char **config_netdir, char **rpc_filename); -/* If they specify --conf, we just read that. - * Otherwise, we read basedir/config (toplevel), and basedir//config - * (network-level) */ -void parse_config_files(const char *config_filename, - const char *config_basedir, - bool early); +/* Parse initial config options and files */ +struct configvar **initial_config_opts(const tal_t *ctx, + int *argc, char *argv[], + bool remove_args, + char **config_filename, + char **config_basedir, + char **config_netdir, + char **rpc_filename); + +/* This is called before we know all the options. */ +void parse_configvars_early(struct configvar **cvs); + +/* This is called once, after we know all the options (if full_knowledge + * is false, ignore unknown non-cmdline options). */ +void parse_configvars_final(struct configvar **cvs, + bool full_knowledge); /* For listconfigs to detect. */ bool is_restricted_ignored(const void *fn); diff --git a/devtools/Makefile b/devtools/Makefile index 440d72c18..50150ab6c 100644 --- a/devtools/Makefile +++ b/devtools/Makefile @@ -24,7 +24,6 @@ DEVTOOLS_COMMON_OBJS := \ common/bolt11.o \ common/blockheight_states.o \ common/channel_id.o \ - common/configdir.o \ common/decode_array.o \ common/features.o \ common/fee_states.o \ @@ -87,7 +86,7 @@ devtools/mkgossip: $(DEVTOOLS_COMMON_OBJS) $(BITCOIN_OBJS) wire/fromwire.o wire/ devtools/mkencoded: $(DEVTOOLS_COMMON_OBJS) $(BITCOIN_OBJS) wire/fromwire.o wire/towire.o devtools/mkencoded.o -devtools/checkchannels: $(DEVTOOLS_COMMON_OBJS) $(BITCOIN_OBJS) common/configdir.o wire/fromwire.o wire/towire.o devtools/checkchannels.o +devtools/checkchannels: $(DEVTOOLS_COMMON_OBJS) $(BITCOIN_OBJS) wire/fromwire.o wire/towire.o devtools/checkchannels.o common/configdir.o common/configvar.o devtools/mkquery: $(DEVTOOLS_COMMON_OBJS) $(BITCOIN_OBJS) wire/fromwire.o wire/towire.o devtools/mkquery.o diff --git a/devtools/bolt12-cli.c b/devtools/bolt12-cli.c index 9d815cff8..610a6ec18 100644 --- a/devtools/bolt12-cli.c +++ b/devtools/bolt12-cli.c @@ -414,6 +414,8 @@ static bool print_extra_fields(const struct tlv_field *fields) return ok; } +bool deprecated_apis = true; + int main(int argc, char *argv[]) { const tal_t *ctx = tal(NULL, char); @@ -424,7 +426,6 @@ int main(int argc, char *argv[]) bool to_hex = false; common_setup(argv[0]); - deprecated_apis = true; opt_set_alloc(opt_allocfn, tal_reallocfn, tal_freefn); opt_register_noarg("--help|-h", opt_usage_and_exit, diff --git a/devtools/checkchannels.c b/devtools/checkchannels.c index 85d4184dd..538912938 100644 --- a/devtools/checkchannels.c +++ b/devtools/checkchannels.c @@ -109,7 +109,7 @@ static void copy_column(void *dst, size_t size, int main(int argc, char *argv[]) { - char *config_dir, *net_dir, *config_filename, *rpc_filename, *hsmfile, *dbfile; + char *net_dir, *rpc_filename, *hsmfile, *dbfile; sqlite3 *sql; sqlite3_stmt *stmt; int flags = SQLITE_OPEN_READONLY, dberr; @@ -124,14 +124,13 @@ int main(int argc, char *argv[]) setup_option_allocators(); - initial_config_opts(top_ctx, argc, argv, - &config_filename, &config_dir, &net_dir, - &rpc_filename); + minimal_config_opts(top_ctx, argc, argv, &net_dir, &rpc_filename); opt_register_noarg("-v|--verbose", opt_set_bool, &verbose, "Print everything"); opt_parse(&argc, argv, opt_log_stderr_exit); + if (argc != 1) errx(1, "no arguments accepted"); diff --git a/lightningd/Makefile b/lightningd/Makefile index 6bca00b05..b20175d37 100644 --- a/lightningd/Makefile +++ b/lightningd/Makefile @@ -85,6 +85,7 @@ LIGHTNINGD_COMMON_OBJS := \ common/channel_type.o \ common/coin_mvt.o \ common/configdir.o \ + common/configvar.o \ common/daemon.o \ common/derive_basepoints.o \ common/ecdh_hsmd.o \ diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c index 9c9b5a325..cb5da09bd 100644 --- a/lightningd/lightningd.c +++ b/lightningd/lightningd.c @@ -1020,7 +1020,7 @@ int main(int argc, char *argv[]) fatal("Could not initialize the plugins, see above for details."); /*~ Handle options and config. */ - handle_opts(ld, argc, argv); + handle_opts(ld); /*~ Now create the PID file: this errors out if there's already a * daemon running, so we call before doing almost anything else. */ diff --git a/lightningd/lightningd.h b/lightningd/lightningd.h index a56286bb9..b04252540 100644 --- a/lightningd/lightningd.h +++ b/lightningd/lightningd.h @@ -128,6 +128,8 @@ struct lightningd { char *config_filename; /* Configuration settings. */ struct config config; + /* Where each configuration setting came from */ + struct configvar **configvars; /* This log_book is owned by all the struct logs */ struct log_book *log_book; diff --git a/lightningd/options.c b/lightningd/options.c index 4e98fe780..b459daee8 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -27,20 +27,6 @@ #include #include -/* Unless overridden, we exit with status 1 when option parsing fails */ -static int opt_exitcode = 1; - -static void opt_log_stderr_exitcode(const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - fprintf(stderr, "\n"); - va_end(ap); - exit(opt_exitcode); -} - /* FIXME: Put into ccan/time. */ #define TIME_FROM_SEC(sec) { { .tv_nsec = 0, .tv_sec = sec } } #define TIME_FROM_MSEC(msec) \ @@ -1562,11 +1548,14 @@ void handle_early_opts(struct lightningd *ld, int argc, char *argv[]) ld, opt_hidden); /*~ This does enough parsing to get us the base configuration options */ - initial_config_opts(ld, argc, argv, - &ld->config_filename, - &ld->config_basedir, - &ld->config_netdir, - &ld->rpc_filename); + ld->configvars = initial_config_opts(ld, &argc, argv, true, + &ld->config_filename, + &ld->config_basedir, + &ld->config_netdir, + &ld->rpc_filename); + + if (argc != 1) + errx(1, "no arguments accepted"); /* Copy in default config, to be modified by further options */ if (chainparams->testnet) @@ -1611,28 +1600,20 @@ void handle_early_opts(struct lightningd *ld, int argc, char *argv[]) * mimic this API here, even though they're on separate lines.*/ register_opts(ld); - /* Now look inside config file(s), but only handle the early + /* Now, first-pass of parsing. But only handle the early * options (testnet, plugins etc), others may be added on-demand */ - parse_config_files(ld->config_filename, ld->config_basedir, true); - - /* Early cmdline options now override config file options. */ - opt_early_parse_incomplete(argc, argv, opt_log_stderr_exit); + parse_configvars_early(ld->configvars); /* Finalize the logging subsystem now. */ logging_options_parsed(ld->log_book); } -void handle_opts(struct lightningd *ld, int argc, char *argv[]) +void handle_opts(struct lightningd *ld) { - /* Now look for config file, but only handle non-early - * options, early ones have been parsed in - * handle_early_opts */ - parse_config_files(ld->config_filename, ld->config_basedir, false); + /* Now we know all the options, finish parsing and finish + * populating ld->configvars with cmdline. */ + parse_configvars_final(ld->configvars, true); - /* Now parse cmdline, which overrides config. */ - opt_parse(&argc, argv, opt_log_stderr_exitcode); - if (argc != 1) - errx(1, "no arguments accepted"); /* We keep a separate variable rather than overriding always_use_proxy, * so listconfigs shows the correct thing. */ if (tal_count(ld->proposed_wireaddr) != 0 @@ -1784,7 +1765,7 @@ static void add_config(struct lightningd *ld, * OPT_HASARG options. */ } else { /* Insert more decodes here! */ - assert(!"A noarg option was added but was not handled"); + errx(1, "Unknown decode for %s", opt->names); } } else if (opt->type & OPT_HASARG) { if (opt->desc == opt_hidden) { @@ -1886,7 +1867,7 @@ static void add_config(struct lightningd *ld, #endif } else { /* Insert more decodes here! */ - abort(); + errx(1, "Unknown decode for %s", opt->names); } } diff --git a/lightningd/options.h b/lightningd/options.h index 72bc039d9..637d65297 100644 --- a/lightningd/options.h +++ b/lightningd/options.h @@ -9,7 +9,7 @@ struct lightningd; void handle_early_opts(struct lightningd *ld, int argc, char *argv[]); /* After this we're in the .lightning dir, and we've parsed all options */ -void handle_opts(struct lightningd *ld, int argc, char *argv[]); +void handle_opts(struct lightningd *ld); /* Derive default color and alias from the pubkey. */ void setup_color_and_alias(struct lightningd *ld); diff --git a/lightningd/test/run-find_my_abspath.c b/lightningd/test/run-find_my_abspath.c index bd59df06e..db9556d3e 100644 --- a/lightningd/test/run-find_my_abspath.c +++ b/lightningd/test/run-find_my_abspath.c @@ -99,7 +99,7 @@ void gossip_notify_new_block(struct lightningd *ld UNNEEDED, u32 blockheight UNN void handle_early_opts(struct lightningd *ld UNNEEDED, int argc UNNEEDED, char *argv[]) { fprintf(stderr, "handle_early_opts called!\n"); abort(); } /* Generated stub for handle_opts */ -void handle_opts(struct lightningd *ld UNNEEDED, int argc UNNEEDED, char *argv[]) +void handle_opts(struct lightningd *ld UNNEEDED) { fprintf(stderr, "handle_opts called!\n"); abort(); } /* Generated stub for hash_htlc_key */ size_t hash_htlc_key(const struct htlc_key *htlc_key UNNEEDED) diff --git a/tests/fuzz/Makefile b/tests/fuzz/Makefile index 07fb66ba6..4d292858f 100644 --- a/tests/fuzz/Makefile +++ b/tests/fuzz/Makefile @@ -20,6 +20,7 @@ FUZZ_COMMON_OBJS := \ common/channel_config.o \ common/close_tx.o \ common/configdir.o \ + common/configvar.o \ common/channel_id.o \ common/channel_type.o \ common/daemon.o \ diff --git a/tests/test_misc.py b/tests/test_misc.py index 7a502d294..8d1236ef8 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -2253,7 +2253,7 @@ def test_config_in_subdir(node_factory, chainparams): out = subprocess.run(['lightningd/lightningd', '--lightning-dir={}'.format(l1.daemon.opts.get("lightning-dir"))], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=TIMEOUT) assert out.returncode == 1 assert "conf: not permitted in configuration files" in out.stderr.decode('utf-8') @@ -2272,7 +2272,7 @@ def test_config_in_subdir(node_factory, chainparams): '--lightning-dir={}'.format(l1.daemon.opts.get("lightning-dir"))], stdout=subprocess.PIPE, stderr=subprocess.PIPE) assert out.returncode == 1 - assert "network: not permitted in network-specific configuration files" in out.stderr.decode('utf-8') + assert "network={}: not permitted in network-specific configuration files".format(network) in out.stderr.decode('utf-8') # lightning-dir only allowed if we explicitly use --conf os.unlink(os.path.join(subdir, "config")) @@ -2283,7 +2283,7 @@ def test_config_in_subdir(node_factory, chainparams): '--lightning-dir={}'.format(l1.daemon.opts.get("lightning-dir"))], stdout=subprocess.PIPE, stderr=subprocess.PIPE) assert out.returncode == 1 - assert "lightning-dir: not permitted in implicit configuration files" in out.stderr.decode('utf-8') + assert "lightning-dir={}/test: not permitted in implicit configuration files".format(l1.daemon.opts.get("lightning-dir")) in out.stderr.decode('utf-8') l1.daemon.opts['conf'] = os.path.join(l1.daemon.opts.get("lightning-dir"), "config") l1.start() diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 3503d6a5b..993e0ffa0 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -113,7 +113,7 @@ def test_option_types(node_factory): # the node should fail after start, and we get a stderr msg n.daemon.start(wait_for_initialized=False, stderr_redir=True) assert n.daemon.wait() == 1 - wait_for(lambda: n.daemon.is_in_stderr('bool_opt: ! does not parse as type bool')) + wait_for(lambda: n.daemon.is_in_stderr('--bool_opt=!: ! does not parse as type bool')) # What happens if we give it a bad int-option? n = node_factory.get_node(options={ @@ -126,7 +126,7 @@ def test_option_types(node_factory): # the node should fail after start, and we get a stderr msg n.daemon.start(wait_for_initialized=False, stderr_redir=True) assert n.daemon.wait() == 1 - assert n.daemon.is_in_stderr('--int_opt: notok does not parse as type int') + assert n.daemon.is_in_stderr('--int_opt=notok: notok does not parse as type int') # Flag opts shouldn't allow any input n = node_factory.get_node(options={ @@ -140,7 +140,7 @@ def test_option_types(node_factory): # the node should fail after start, and we get a stderr msg n.daemon.start(wait_for_initialized=False, stderr_redir=True) assert n.daemon.wait() == 1 - assert n.daemon.is_in_stderr("--flag_opt: doesn't allow an argument") + assert n.daemon.is_in_stderr("--flag_opt=True: doesn't allow an argument") n = node_factory.get_node(options={ 'plugin': plugin_path, @@ -1568,7 +1568,7 @@ def test_libplugin(node_factory): l1.daemon.start(wait_for_initialized=False, stderr_redir=True) # Will exit with failure code. assert l1.daemon.wait() == 1 - assert l1.daemon.is_in_stderr(r"somearg-deprecated: deprecated option") + assert l1.daemon.is_in_stderr(r"somearg-deprecated=test_opt: deprecated option") del l1.daemon.opts["somearg-deprecated"] l1.start() diff --git a/tools/Makefile b/tools/Makefile index 6684b5bec..a8241f612 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -17,7 +17,7 @@ tools/headerversions: $(FORCE) tools/headerversions.o libccan.a tools/check-bolt: tools/check-bolt.o $(TOOLS_COMMON_OBJS) -tools/hsmtool: tools/hsmtool.o $(TOOLS_COMMON_OBJS) $(BITCOIN_OBJS) common/amount.o common/autodata.o common/bech32.o common/bigsize.o common/configdir.o common/derive_basepoints.o common/descriptor_checksum.o common/hsm_encryption.o common/node_id.o common/type_to_string.o common/version.o wire/fromwire.o wire/towire.o +tools/hsmtool: tools/hsmtool.o $(TOOLS_COMMON_OBJS) $(BITCOIN_OBJS) common/amount.o common/autodata.o common/bech32.o common/bigsize.o common/configdir.o common/configvar.o common/derive_basepoints.o common/descriptor_checksum.o common/hsm_encryption.o common/node_id.o common/type_to_string.o common/version.o wire/fromwire.o wire/towire.o tools/lightning-hsmtool: tools/hsmtool cp $< $@