rgb-cln/common/configdir.c

460 lines
13 KiB
C
Raw Normal View History

#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]);
}
}
/*
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,"
" 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,"
" regtest, litecoin or litecoin-testnet)");
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");
opt_register_early_noarg("--mainnet",
opt_restricted_toplevel_noarg, NULL,
"Alias for --network=bitcoin");
/* 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)");
}