696 lines
18 KiB
C
696 lines
18 KiB
C
#include "config.h"
|
|
#include <ccan/cast/cast.h>
|
|
#include <ccan/err/err.h>
|
|
#include <ccan/opt/opt.h>
|
|
#include <ccan/opt/private.h>
|
|
#include <ccan/read_write_all/read_write_all.h>
|
|
#include <ccan/tal/grab_file/grab_file.h>
|
|
#include <ccan/tal/path/path.h>
|
|
#include <ccan/tal/str/str.h>
|
|
#include <common/configdir.h>
|
|
#include <common/configvar.h>
|
|
#include <common/json_command.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <lightningd/plugin.h>
|
|
#include <unistd.h>
|
|
|
|
static void json_add_source(struct json_stream *result,
|
|
const char *fieldname,
|
|
const struct configvar *cv)
|
|
{
|
|
const char *source;
|
|
|
|
if (!cv) {
|
|
source = "default";
|
|
} else {
|
|
source = NULL;
|
|
switch (cv->src) {
|
|
case CONFIGVAR_CMDLINE:
|
|
case CONFIGVAR_CMDLINE_SHORT:
|
|
source = "cmdline";
|
|
break;
|
|
case CONFIGVAR_EXPLICIT_CONF:
|
|
case CONFIGVAR_BASE_CONF:
|
|
case CONFIGVAR_NETWORK_CONF:
|
|
source = tal_fmt(tmpctx, "%s:%u", cv->file, cv->linenum);
|
|
break;
|
|
case CONFIGVAR_PLUGIN_START:
|
|
source = "pluginstart";
|
|
break;
|
|
case CONFIGVAR_SETCONFIG_TRANSIENT:
|
|
source = "setconfig transient";
|
|
break;
|
|
}
|
|
}
|
|
json_add_string(result, fieldname, source);
|
|
}
|
|
|
|
static const char *configval_fieldname(const struct opt_table *ot)
|
|
{
|
|
bool multi = (ot->type & OPT_MULTI);
|
|
if (ot->type & OPT_SHOWBOOL)
|
|
return multi ? "values_bool" : "value_bool";
|
|
if (ot->type & OPT_SHOWINT)
|
|
return multi ? "values_int" : "value_int";
|
|
if (ot->type & OPT_SHOWMSATS)
|
|
return multi ? "values_msat" : "value_msat";
|
|
return multi ? "values_str" : "value_str";
|
|
}
|
|
|
|
#define CONFIG_SHOW_BUFSIZE 4096
|
|
|
|
static const char *get_opt_val(const struct opt_table *ot,
|
|
char buf[],
|
|
const struct configvar *cv)
|
|
{
|
|
if (ot->type & OPT_CONCEAL)
|
|
return "...";
|
|
|
|
if (ot->show == (void *)opt_show_charp) {
|
|
/* Don't truncate or quote! */
|
|
return *(char **)ot->u.carg;
|
|
}
|
|
if (ot->show) {
|
|
/* Plugins options' show only shows defaults, so show val if
|
|
* we have it */
|
|
if (is_plugin_opt(ot) && cv)
|
|
return cv->optarg;
|
|
strcpy(buf + CONFIG_SHOW_BUFSIZE, "...");
|
|
if (ot->show(buf, CONFIG_SHOW_BUFSIZE, ot->u.carg))
|
|
return buf;
|
|
return NULL;
|
|
}
|
|
|
|
/* For everything else we only display if it's set,
|
|
* BUT we check here to make sure you've handled
|
|
* everything! */
|
|
if (is_known_opt_cb_arg(ot->cb_arg)
|
|
|| is_restricted_print_if_nonnull(ot->cb_arg)) {
|
|
/* Only if set! */
|
|
if (cv)
|
|
return cv->optarg;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/* Insert more decodes here! */
|
|
errx(1, "Unknown decode for %s", ot->names);
|
|
}
|
|
|
|
static void check_literal(const char *name, const char *val)
|
|
{
|
|
if (streq(val, "true") || streq(val, "false"))
|
|
return;
|
|
if (!streq(val, "") && strspn(val, "-0123456789.") == strlen(val))
|
|
return;
|
|
errx(1, "Bad literal for %s: %s", name, val);
|
|
}
|
|
|
|
static void json_add_configval(struct json_stream *result,
|
|
const char *fieldname,
|
|
const struct opt_table *ot,
|
|
const char *str)
|
|
{
|
|
if (ot->type & OPT_SHOWBOOL) {
|
|
json_add_bool(result, fieldname, opt_canon_bool(str));
|
|
} else if (ot->type & (OPT_SHOWMSATS|OPT_SHOWINT)) {
|
|
check_literal(ot->names, str);
|
|
json_add_primitive(result, fieldname, str);
|
|
} else
|
|
json_add_string(result, fieldname, str);
|
|
}
|
|
|
|
/* Config vars can have multiple names ("--large-channels|--wumbo"), but first
|
|
* is preferred.
|
|
* wrap_object means we wrap json in an object of that name, otherwise outputs
|
|
* raw fields.
|
|
*/
|
|
static void json_add_config(struct lightningd *ld,
|
|
struct json_stream *response,
|
|
bool always_include,
|
|
bool wrap_object,
|
|
const struct opt_table *ot,
|
|
const char **names)
|
|
{
|
|
char buf[CONFIG_SHOW_BUFSIZE + sizeof("...")];
|
|
const char *val;
|
|
struct configvar *cv;
|
|
|
|
/* This tells us if they actually set the option */
|
|
cv = configvar_first(ld->configvars, names);
|
|
|
|
/* Ignore dev/hidden options (deprecated) unless they actually used it */
|
|
if (!cv
|
|
&& (ot->desc == opt_hidden || (ot->type & OPT_DEV))
|
|
&& !always_include) {
|
|
return;
|
|
}
|
|
|
|
/* Ignore options which simply exit */
|
|
if (ot->type & OPT_EXITS)
|
|
return;
|
|
|
|
if (ot->type & OPT_NOARG) {
|
|
if (wrap_object)
|
|
json_object_start(response, names[0]);
|
|
json_add_bool(response, "set", cv != NULL);
|
|
json_add_source(response, "source", cv);
|
|
json_add_config_plugin(response, ld->plugins, "plugin", ot);
|
|
if (ot->type & OPT_DYNAMIC)
|
|
json_add_bool(response, "dynamic", true);
|
|
if (wrap_object)
|
|
json_object_end(response);
|
|
return;
|
|
}
|
|
|
|
assert(ot->type & OPT_HASARG);
|
|
if (ot->type & OPT_MULTI) {
|
|
if (wrap_object)
|
|
json_object_start(response, names[0]);
|
|
json_array_start(response, configval_fieldname(ot));
|
|
while (cv) {
|
|
val = get_opt_val(ot, buf, cv);
|
|
json_add_configval(response, NULL, ot, val);
|
|
cv = configvar_next(ld->configvars, cv, names);
|
|
}
|
|
json_array_end(response);
|
|
|
|
/* Iterate again, for sources */
|
|
json_array_start(response, "sources");
|
|
for (cv = configvar_first(ld->configvars, names);
|
|
cv;
|
|
cv = configvar_next(ld->configvars, cv, names)) {
|
|
json_add_source(response, NULL, cv);
|
|
}
|
|
json_array_end(response);
|
|
json_add_config_plugin(response, ld->plugins, "plugin", ot);
|
|
if (ot->type & OPT_DYNAMIC)
|
|
json_add_bool(response, "dynamic", true);
|
|
if (wrap_object)
|
|
json_object_end(response);
|
|
return;
|
|
}
|
|
|
|
/* Returns NULL if we don't want to print it */
|
|
val = get_opt_val(ot, buf, cv);
|
|
if (!val)
|
|
return;
|
|
|
|
if (wrap_object)
|
|
json_object_start(response, names[0]);
|
|
json_add_configval(response, configval_fieldname(ot), ot, val);
|
|
json_add_source(response, "source", cv);
|
|
json_add_config_plugin(response, ld->plugins, "plugin", ot);
|
|
if (ot->type & OPT_DYNAMIC)
|
|
json_add_bool(response, "dynamic", true);
|
|
if (wrap_object)
|
|
json_object_end(response);
|
|
}
|
|
|
|
static struct command_result *param_opt_config(struct command *cmd,
|
|
const char *name,
|
|
const char *buffer,
|
|
const jsmntok_t *tok,
|
|
const struct opt_table **config)
|
|
{
|
|
const char *name0 = json_strdup(tmpctx, buffer, tok);
|
|
*config = opt_find_long(name0, NULL);
|
|
if (*config)
|
|
return NULL;
|
|
|
|
return command_fail_badparam(cmd, name, buffer, tok,
|
|
"Unknown config option");
|
|
}
|
|
|
|
/* FIXME: This is a hack! Expose somehow in ccan/opt.*/
|
|
/* Returns string after first '-'. */
|
|
static const char *first_name(const char *names, unsigned *len)
|
|
{
|
|
*len = strcspn(names + 1, "|= ");
|
|
return names + 1;
|
|
}
|
|
|
|
static const char *next_name(const char *names, unsigned *len)
|
|
{
|
|
names += *len;
|
|
if (names[0] == ' ' || names[0] == '=' || names[0] == '\0')
|
|
return NULL;
|
|
return first_name(names + 1, len);
|
|
}
|
|
|
|
static const char **opt_names_arr(const tal_t *ctx,
|
|
const struct opt_table *ot)
|
|
{
|
|
const char **names = tal_arr(ctx, const char *, 0);
|
|
const char *name;
|
|
unsigned len;
|
|
|
|
for (name = first_name(ot->names, &len);
|
|
name;
|
|
name = next_name(name, &len)) {
|
|
/* Skips over first -, so just need to look for one */
|
|
if (name[0] != '-')
|
|
continue;
|
|
tal_arr_expand(&names,
|
|
tal_strndup(names, name+1, len-1));
|
|
}
|
|
return names;
|
|
}
|
|
|
|
static struct command_result *json_listconfigs(struct command *cmd,
|
|
const char *buffer,
|
|
const jsmntok_t *obj UNNEEDED,
|
|
const jsmntok_t *params)
|
|
{
|
|
struct json_stream *response = NULL;
|
|
const struct opt_table *config;
|
|
|
|
if (!param(cmd, buffer, params,
|
|
p_opt("config", param_opt_config, &config),
|
|
NULL))
|
|
return command_param_failed();
|
|
|
|
response = json_stream_success(cmd);
|
|
json_object_start(response, "configs");
|
|
for (size_t i = 0; i < opt_count; i++) {
|
|
const char **names;
|
|
|
|
/* FIXME: Print out comment somehow? */
|
|
if (opt_table[i].type == OPT_SUBTABLE)
|
|
continue;
|
|
|
|
if (config && config != &opt_table[i])
|
|
continue;
|
|
|
|
names = opt_names_arr(tmpctx, &opt_table[i]);
|
|
/* We don't usually print dev or deprecated options, unless
|
|
* they explicitly ask, or they're set. */
|
|
json_add_config(cmd->ld, response, config != NULL, true,
|
|
&opt_table[i], names);
|
|
}
|
|
json_object_end(response);
|
|
|
|
return command_success(cmd, response);
|
|
}
|
|
|
|
static const struct json_command listconfigs_command = {
|
|
"listconfigs",
|
|
json_listconfigs,
|
|
};
|
|
AUTODATA(json_command, &listconfigs_command);
|
|
|
|
static struct command_result *param_opt_dynamic_config(struct command *cmd,
|
|
const char *name,
|
|
const char *buffer,
|
|
const jsmntok_t *tok,
|
|
const struct opt_table **config)
|
|
{
|
|
struct command_result *ret;
|
|
|
|
ret = param_opt_config(cmd, name, buffer, tok, config);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!((*config)->type & OPT_DYNAMIC))
|
|
return command_fail_badparam(cmd, name, buffer, tok,
|
|
"Not a dynamic config option");
|
|
return NULL;
|
|
}
|
|
|
|
static void configvar_updated(struct lightningd *ld,
|
|
enum configvar_src src,
|
|
const char *fname,
|
|
size_t linenum,
|
|
const char *confline)
|
|
{
|
|
struct configvar *cv;
|
|
|
|
cv = configvar_new(ld->configvars, src, fname, linenum, confline);
|
|
configvar_unparsed(cv);
|
|
|
|
log_info(ld->log, "setconfig: %s %s (updated %s:%u)",
|
|
cv->optvar, cv->optarg ? cv->optarg : "SET",
|
|
cv->file ? cv->file : "NULL", cv->linenum);
|
|
|
|
tal_arr_expand(&ld->configvars, cv);
|
|
configvar_finalize_overrides(ld->configvars);
|
|
}
|
|
|
|
static size_t append_to_file(struct lightningd *ld,
|
|
const char *fname,
|
|
const char *str,
|
|
bool must_exist)
|
|
{
|
|
int fd;
|
|
const char *buffer;
|
|
|
|
fd = open(fname, O_RDWR|O_APPEND);
|
|
if (fd < 0) {
|
|
if (errno != ENOENT || must_exist)
|
|
fatal("Could not write to config %s: %s",
|
|
fname, strerror(errno));
|
|
fd = open(fname, O_RDWR|O_APPEND|O_CREAT, 0644);
|
|
if (fd < 0)
|
|
fatal("Could not create config file %s: %s",
|
|
fname, strerror(errno));
|
|
}
|
|
|
|
/* Note: always nul terminates */
|
|
buffer = grab_fd_str(tmpctx, fd);
|
|
if (!buffer)
|
|
fatal("Error reading %s: %s", fname, strerror(errno));
|
|
|
|
/* If there's a last character and it's not \n, add one */
|
|
if (tal_bytelen(buffer) > 1
|
|
&& buffer[tal_bytelen(buffer)-2] != '\n')
|
|
str = tal_strcat(tmpctx, "\n", str);
|
|
|
|
/* Always append a \n ourselves */
|
|
str = tal_strcat(tmpctx, str, "\n");
|
|
if (write(fd, str, strlen(str)) != strlen(str))
|
|
fatal("Could not write to config file %s: %s",
|
|
fname, strerror(errno));
|
|
if (fsync(fd) != 0)
|
|
fatal("Syncing %s: %s", fname, strerror(errno));
|
|
close(fd);
|
|
|
|
/* 1-based counter of where new stuff appeared */
|
|
return strcount(buffer, "\n") + 1;
|
|
}
|
|
|
|
static const char *grab_and_check(const tal_t *ctx,
|
|
const char *fname,
|
|
size_t linenum,
|
|
const char *expected,
|
|
char ***lines)
|
|
{
|
|
char *contents;
|
|
|
|
contents = grab_file_str(tmpctx, fname);
|
|
if (!contents)
|
|
return tal_fmt(ctx, "Could not load configfile %s: %s",
|
|
fname, strerror(errno));
|
|
|
|
/* These are 1-based! */
|
|
assert(linenum > 0);
|
|
*lines = tal_strsplit(ctx, contents, "\r\n", STR_EMPTY_OK);
|
|
if (linenum >= tal_count(*lines))
|
|
return tal_fmt(ctx, "Configfile %s no longer has %zu lines!",
|
|
fname, linenum);
|
|
|
|
if (!streq((*lines)[linenum - 1], expected))
|
|
return tal_fmt(ctx, "Configfile %s line %zu changed from %s to %s!",
|
|
fname, linenum,
|
|
expected,
|
|
(*lines)[linenum - 1]);
|
|
return NULL;
|
|
}
|
|
|
|
/* This comments out the config file entry or maybe replace one */
|
|
static void configfile_replace_var(struct lightningd *ld,
|
|
const struct configvar *cv,
|
|
const char *replace)
|
|
{
|
|
char **lines, *template, *contents;
|
|
const char *err;
|
|
int outfd;
|
|
|
|
switch (cv->src) {
|
|
case CONFIGVAR_CMDLINE:
|
|
case CONFIGVAR_CMDLINE_SHORT:
|
|
case CONFIGVAR_PLUGIN_START:
|
|
case CONFIGVAR_SETCONFIG_TRANSIENT:
|
|
/* These can't be commented out */
|
|
abort();
|
|
case CONFIGVAR_EXPLICIT_CONF:
|
|
case CONFIGVAR_BASE_CONF:
|
|
case CONFIGVAR_NETWORK_CONF:
|
|
break;
|
|
}
|
|
|
|
/* If it changed *now*, that's fatal: we already set it locally! */
|
|
err = grab_and_check(tmpctx, cv->file, cv->linenum, cv->configline,
|
|
&lines);
|
|
if (err)
|
|
fatal("%s", err);
|
|
|
|
if (replace)
|
|
lines[cv->linenum - 1] = cast_const(char *, replace);
|
|
else
|
|
lines[cv->linenum - 1] = tal_fmt(lines, "# setconfig commented out (see config.setconfig): %s",
|
|
lines[cv->linenum - 1]);
|
|
|
|
log_info(ld->log, "setconfig: %s line %u of %s (%s)",
|
|
replace ? "replaced" : "commented out",
|
|
cv->linenum, cv->file, cv->configline);
|
|
|
|
template = tal_fmt(tmpctx, "%s.setconfig.XXXXXX", cv->file);
|
|
outfd = mkstemp(template);
|
|
if (outfd < 0)
|
|
fatal("Creating %s: %s", template, strerror(errno));
|
|
|
|
contents = tal_strjoin(tmpctx, take(lines), "\n", STR_TRAIL);
|
|
if (!write_all(outfd, contents, strlen(contents)))
|
|
fatal("Writing %s: %s", template, strerror(errno));
|
|
if (fsync(outfd) != 0)
|
|
fatal("Syncing %s: %s", template, strerror(errno));
|
|
|
|
if (rename(template, cv->file) != 0)
|
|
fatal("Renaming %s over %s: %s",
|
|
template, cv->file, strerror(errno));
|
|
close(outfd);
|
|
}
|
|
|
|
static const char *base_conf_file(const tal_t *ctx,
|
|
struct lightningd *ld,
|
|
bool *must_exist)
|
|
{
|
|
/* Explicit --conf? Edit that, otherwise network-specific config. */
|
|
if (ld->config_filename) {
|
|
if (must_exist)
|
|
*must_exist = true;
|
|
return ld->config_filename;
|
|
} else {
|
|
if (must_exist)
|
|
*must_exist = false;
|
|
return path_join(ctx, ld->config_netdir, "config");
|
|
}
|
|
}
|
|
|
|
static void create_setconfig_include(struct lightningd *ld)
|
|
{
|
|
const char *lines;
|
|
time_t now = time(NULL);
|
|
const char *fname;
|
|
bool must_exist;
|
|
|
|
/* Usually config.setconfig, but could be different with --conf */
|
|
fname = base_conf_file(tmpctx, ld, &must_exist);
|
|
ld->setconfig_file = tal_fmt(ld, "%s.setconfig", fname);
|
|
|
|
/* We want to use a relative path here (ctime() includes \n!). */
|
|
lines = tal_fmt(tmpctx,
|
|
"# Inserted by setconfig %sinclude %s.setconfig",
|
|
ctime(&now), path_basename(tmpctx, fname));
|
|
append_to_file(ld, fname, lines, must_exist);
|
|
|
|
/* This creates the file */
|
|
append_to_file(ld, ld->setconfig_file,
|
|
"# Created and update by setconfig, but you can edit this manually when node is stopped.",
|
|
false);
|
|
}
|
|
|
|
static void configvar_save(struct lightningd *ld,
|
|
const char **names,
|
|
const char *confline)
|
|
{
|
|
/* Simple case: set in a config file. */
|
|
struct configvar *oldcv;
|
|
size_t linenum;
|
|
|
|
/* If we don't already have "include config.setconfig" add it */
|
|
if (!ld->setconfig_file)
|
|
create_setconfig_include(ld);
|
|
|
|
/* Is it already set in the config? */
|
|
oldcv = configvar_first(ld->configvars, names);
|
|
if (oldcv && oldcv->file) {
|
|
/* If it's already in config.setconfig, replace */
|
|
if (streq(oldcv->file, ld->setconfig_file)) {
|
|
configfile_replace_var(ld, oldcv, confline);
|
|
linenum = oldcv->linenum;
|
|
goto replaced;
|
|
}
|
|
configfile_replace_var(ld, oldcv, NULL);
|
|
}
|
|
|
|
linenum = append_to_file(ld, ld->setconfig_file, confline, true);
|
|
|
|
replaced:
|
|
configvar_updated(ld, CONFIGVAR_NETWORK_CONF,
|
|
ld->setconfig_file, linenum, confline);
|
|
}
|
|
|
|
static struct command_result *setconfig_success(struct command *cmd,
|
|
const struct opt_table *ot,
|
|
const char *val,
|
|
bool transient)
|
|
{
|
|
struct json_stream *response;
|
|
const char **names, *confline;
|
|
|
|
if (command_check_only(cmd))
|
|
return command_check_done(cmd);
|
|
|
|
names = opt_names_arr(tmpctx, ot);
|
|
|
|
if (val)
|
|
confline = tal_fmt(tmpctx, "%s=%s", names[0], val);
|
|
else
|
|
confline = names[0];
|
|
|
|
if (!transient)
|
|
configvar_save(cmd->ld, names, confline);
|
|
else
|
|
configvar_updated(cmd->ld, CONFIGVAR_SETCONFIG_TRANSIENT, NULL, 0, confline);
|
|
|
|
|
|
response = json_stream_success(cmd);
|
|
json_object_start(response, "config");
|
|
json_add_string(response, "config", names[0]);
|
|
json_add_config(cmd->ld, response, true, false, ot, names);
|
|
json_object_end(response);
|
|
return command_success(cmd, response);
|
|
}
|
|
|
|
static bool file_writable(const char *fname)
|
|
{
|
|
return access(fname, W_OK) == 0;
|
|
}
|
|
|
|
static bool dir_writable(const char *fname)
|
|
{
|
|
return access(path_dirname(tmpctx, fname), W_OK) == 0;
|
|
}
|
|
|
|
/* Returns config file name if not writable */
|
|
static const char *config_not_writable(const tal_t *ctx,
|
|
struct command *cmd,
|
|
const struct configvar *oldcv)
|
|
{
|
|
struct lightningd *ld = cmd->ld;
|
|
const char *fname;
|
|
|
|
/* If it exists before, we will need to replace that file (rename) */
|
|
if (oldcv && oldcv->file) {
|
|
/* We will rename */
|
|
if (!dir_writable(oldcv->file))
|
|
return oldcv->file;
|
|
}
|
|
|
|
/* If we don't have a setconfig file we'll have to create it, and
|
|
* amend the config file. */
|
|
if (!ld->setconfig_file) {
|
|
fname = base_conf_file(tmpctx, ld, NULL);
|
|
if (!dir_writable(fname))
|
|
return tal_steal(ctx, fname);
|
|
} else {
|
|
/* We will try to append config.setconfig */
|
|
if (!file_writable(ld->setconfig_file))
|
|
return tal_strdup(ctx, ld->setconfig_file);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static struct command_result *json_setconfig(struct command *cmd,
|
|
const char *buffer,
|
|
const jsmntok_t *obj UNNEEDED,
|
|
const jsmntok_t *params)
|
|
{
|
|
const struct opt_table *ot;
|
|
const char *val;
|
|
char *err;
|
|
void *arg;
|
|
bool *transient;
|
|
|
|
if (!param_check(cmd, buffer, params,
|
|
p_req("config", param_opt_dynamic_config, &ot),
|
|
p_opt("val", param_string, &val),
|
|
p_opt_def("transient", param_bool, &transient, false),
|
|
NULL))
|
|
return command_param_failed();
|
|
|
|
/* We don't handle DYNAMIC MULTI, at least yet! */
|
|
assert(!(ot->type & OPT_MULTI));
|
|
|
|
if (!*transient) {
|
|
const struct configvar *cv;
|
|
const char *fname;
|
|
|
|
cv = configvar_first(cmd->ld->configvars,
|
|
opt_names_arr(tmpctx, ot));
|
|
|
|
fname = config_not_writable(cmd, cmd, cv);
|
|
if (fname)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Cannot write to config file %s",
|
|
fname);
|
|
|
|
/* Check if old config has changed (so we couldn't be able
|
|
* to comment it out! */
|
|
if (cv && cv->file) {
|
|
const char *changed;
|
|
char **lines;
|
|
|
|
changed = grab_and_check(tmpctx,
|
|
cv->file, cv->linenum,
|
|
cv->configline,
|
|
&lines);
|
|
if (changed)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"%s", changed);
|
|
}
|
|
}
|
|
|
|
/* We use arg = NULL to tell callback it's only for testing */
|
|
if (command_check_only(cmd))
|
|
arg = NULL;
|
|
else
|
|
arg = ot->u.arg;
|
|
|
|
if (ot->type & OPT_NOARG) {
|
|
if (val)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"%s does not take a value",
|
|
ot->names + 2);
|
|
if (is_plugin_opt(ot))
|
|
return plugin_set_dynamic_opt(cmd, ot, NULL, *transient,
|
|
setconfig_success);
|
|
err = ot->cb(arg);
|
|
} else {
|
|
assert(ot->type & OPT_HASARG);
|
|
if (!val)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"%s requires a value",
|
|
ot->names + 2);
|
|
if (is_plugin_opt(ot))
|
|
return plugin_set_dynamic_opt(cmd, ot, val, *transient,
|
|
setconfig_success);
|
|
err = ot->cb_arg(val, arg);
|
|
}
|
|
|
|
if (err) {
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Error setting %s: %s", ot->names + 2, err);
|
|
}
|
|
|
|
return setconfig_success(cmd, ot, val, *transient);
|
|
}
|
|
|
|
static const struct json_command setconfig_command = {
|
|
"setconfig",
|
|
json_setconfig,
|
|
};
|
|
AUTODATA(json_command, &setconfig_command);
|