|
|
|
#include "configdir.h"
|
|
|
|
#include <assert.h>
|
|
|
|
#include <bitcoin/chainparams.h>
|
|
|
|
#include <ccan/cast/cast.h>
|
|
|
|
#include <ccan/err/err.h>
|
|
|
|
#include <ccan/opt/opt.h>
|
|
|
|
#include <ccan/tal/grab_file/grab_file.h>
|
|
|
|
#include <ccan/tal/path/path.h>
|
|
|
|
#include <ccan/tal/str/str.h>
|
|
|
|
#include <common/utils.h>
|
|
|
|
#include <common/version.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
bool deprecated_apis = true;
|
|
|
|
|
|
|
|
/* The regrettable globals */
|
|
|
|
static const tal_t *options_ctx;
|
|
|
|
|
|
|
|
/* Override a tal string; frees the old one. */
|
|
|
|
char *opt_set_talstr(const char *arg, char **p)
|
|
|
|
{
|
|
|
|
tal_free(*p);
|
|
|
|
return opt_set_charp(tal_strdup(options_ctx, arg), p);
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *opt_set_abspath(const char *arg, char **p)
|
|
|
|
{
|
|
|
|
tal_free(*p);
|
|
|
|
return opt_set_charp(path_join(options_ctx, take(path_cwd(NULL)), arg),
|
|
|
|
p);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Tal wrappers for opt. */
|
|
|
|
static void *opt_allocfn(size_t size)
|
|
|
|
{
|
|
|
|
return tal_arr_label(NULL, char, size,
|
|
|
|
TAL_LABEL(opt_allocfn_notleak, ""));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *tal_reallocfn(void *ptr, size_t size)
|
|
|
|
{
|
|
|
|
if (!ptr)
|
|
|
|
return opt_allocfn(size);
|
|
|
|
tal_resize_(&ptr, 1, size, false);
|
|
|
|
return ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
char *contents, **lines;
|
|
|
|
char **all_args; /*For each line: either `--`argument, include file, or NULL*/
|
|
|
|
char *argv[3];
|
|
|
|
int i, argc;
|
|
|
|
|
|
|
|
contents = grab_file(NULL, 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
lines = tal_strsplit(contents, contents, "\r\n", STR_EMPTY_OK);
|
|
|
|
|
|
|
|
/* 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, ""));
|
|
|
|
|
|
|
|
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);
|
|
|
|
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_free(contents);
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *default_base_configdir(const tal_t *ctx)
|
|
|
|
{
|
|
|
|
char *path;
|
|
|
|
const char *env = getenv("HOME");
|
|
|
|
if (!env)
|
|
|
|
return path_cwd(ctx);
|
|
|
|
|
|
|
|
path = path_join(ctx, env, ".lightning");
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *default_rpcfile(const tal_t *ctx)
|
|
|
|
{
|
|
|
|
return tal_strdup(ctx, "lightning-rpc");
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *opt_set_network(const char *arg, void *unused)
|
|
|
|
{
|
|
|
|
assert(arg != NULL);
|
|
|
|
|
|
|
|
/* Set the global chainparams instance */
|
|
|
|
chainparams = chainparams_for_network(arg);
|
|
|
|
if (!chainparams)
|
|
|
|
return tal_fmt(NULL, "Unknown network name '%s'", arg);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *opt_set_specific_network(const char *network)
|
|
|
|
{
|
|
|
|
return opt_set_network(network, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void opt_show_network(char buf[OPT_SHOW_LEN], const void *unused)
|
|
|
|
{
|
|
|
|
snprintf(buf, OPT_SHOW_LEN, "%s", chainparams->network_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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)
|
|
|
|
{
|
|
|
|
if (parse_state != CMDLINE)
|
|
|
|
return "not permitted in configuration files";
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *opt_restricted_toplevel_noarg(const void *unused)
|
|
|
|
{
|
|
|
|
if (parse_state == NETWORK_CONFIG)
|
|
|
|
return "not permitted in network-specific configuration files";
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool is_restricted_print_if_nonnull(const void *fn)
|
|
|
|
{
|
|
|
|
return fn == opt_restricted_cmdline;
|
|
|
|
}
|
|
|
|
|
|
|
|
void setup_option_allocators(void)
|
|
|
|
{
|
|
|
|
/*~ These functions make ccan/opt use tal for allocations */
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
const char *dir, *filename;
|
|
|
|
|
|
|
|
if (config_basedir)
|
|
|
|
dir = path_join(NULL, take(path_cwd(NULL)), config_basedir);
|
|
|
|
else
|
|
|
|
dir = default_base_configdir(NULL);
|
|
|
|
|
|
|
|
if (network)
|
|
|
|
dir = path_join(NULL, take(dir), network);
|
|
|
|
|
|
|
|
filename = path_join(NULL, take(dir), "config");
|
|
|
|
parse_include(filename, false, early, 0);
|
|
|
|
tal_free(filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If they specify --conf, we just read that.
|
|
|
|
* Otherwise we read <lightning-dir>/config then <lightning-dir>/<network>/config
|
|
|
|
*/
|
|
|
|
void parse_config_files(const char *config_filename,
|
|
|
|
const char *config_basedir,
|
|
|
|
bool early)
|
|
|
|
{
|
|
|
|
if (config_filename) {
|
|
|
|
parse_state = FORCED_CONFIG;
|
|
|
|
parse_include(config_filename, true, early, 0);
|
|
|
|
parse_state = CMDLINE;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Could be a yet-to-be-upgraded dir (definitely testnet), or could be
|
|
|
|
* it's been upgraded to testnet. */
|
|
|
|
static bool smells_like_old_testnet(const char *config_basedir)
|
|
|
|
{
|
|
|
|
struct stat st;
|
|
|
|
/* Doubles as convenient top-level ctx for this function */
|
|
|
|
const char *base = default_base_configdir(NULL);
|
|
|
|
|
|
|
|
if (!config_basedir)
|
|
|
|
config_basedir = base;
|
|
|
|
|
|
|
|
/* If it doesn't exist, it's not testnet. */
|
|
|
|
if (stat(config_basedir, &st) != 0) {
|
|
|
|
tal_free(base);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Does it have a bitcoin/ subdir and no testnet/ subdir? */
|
|
|
|
if (stat(path_join(base, config_basedir, "bitcoin"), &st) == 0
|
|
|
|
&& stat(path_join(base, config_basedir, "testnet"), &st) != 0) {
|
|
|
|
tal_free(base);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
tal_free(base);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void initial_config_opts(const tal_t *ctx,
|
|
|
|
int argc, char *argv[],
|
|
|
|
char **config_filename,
|
|
|
|
char **config_basedir,
|
|
|
|
char **config_netdir,
|
|
|
|
char **rpc_filename)
|
|
|
|
{
|
|
|
|
options_ctx = ctx;
|
|
|
|
|
|
|
|
/* First, they could specify a config, which specifies a lightning dir
|
|
|
|
* or a network. */
|
|
|
|
*config_filename = NULL;
|
|
|
|
opt_register_early_arg("--conf=<file>", opt_set_abspath, NULL,
|
|
|
|
config_filename,
|
|
|
|
"Specify configuration file");
|
|
|
|
|
|
|
|
/* Cmdline can also set lightning-dir. */
|
|
|
|
*config_basedir = NULL;
|
|
|
|
opt_register_early_arg("--lightning-dir=<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=<file>",
|
|
|
|
opt_restricted_cmdline, NULL,
|
|
|
|
config_filename,
|
|
|
|
"Specify configuration file");
|
|
|
|
|
|
|
|
/* If they set --conf it can still set --lightning-dir */
|
|
|
|
if (!*config_filename) {
|
|
|
|
opt_register_early_arg("--lightning-dir=<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=<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 */
|
|
|
|
opt_register_early_arg("--network", opt_set_network, opt_show_network,
|
|
|
|
NULL,
|
|
|
|
"Select the network parameters (bitcoin, testnet,"
|
|
|
|
" signet, regtest, litecoin or litecoin-testnet)");
|
|
|
|
opt_register_early_noarg("--testnet",
|
|
|
|
opt_set_specific_network, "testnet",
|
|
|
|
"Alias for --network=testnet");
|
|
|
|
opt_register_early_noarg("--signet",
|
|
|
|
opt_set_specific_network, "signet",
|
|
|
|
"Alias for --network=signet");
|
|
|
|
opt_register_early_noarg("--mainnet",
|
|
|
|
opt_set_specific_network, "bitcoin",
|
|
|
|
"Alias for --network=bitcoin");
|
|
|
|
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);
|
|
|
|
|
|
|
|
/* We use a global (in common/utils.h) for the chainparams. */
|
|
|
|
if (!chainparams) {
|
|
|
|
/* Use bitcoin default on new installations. */
|
|
|
|
if (deprecated_apis && smells_like_old_testnet(*config_basedir)) {
|
|
|
|
warnx("WARNING: default network changing in 2020:"
|
|
|
|
" please set network=testnet in config!");
|
|
|
|
chainparams = chainparams_for_network("testnet");
|
|
|
|
} else
|
|
|
|
chainparams = chainparams_for_network("bitcoin");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!*config_basedir)
|
|
|
|
*config_basedir = default_base_configdir(ctx);
|
|
|
|
|
|
|
|
*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=<file>",
|
|
|
|
opt_restricted_cmdline, NULL,
|
|
|
|
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=<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)");
|
|
|
|
}
|