core-lightning/lightningd/plugin.c
Christian Decker 2834053457 plugin: Fix an issue with string IDs ending up quoted twice
The transparent passthrough that was recently introduced would end up
causing phantom quotes to appear around IDs when one of them was a
string. This happened for example when using `lightning-cli`, the code
would copy the quotes from the original request, insert our u64 ID,
and then re-add them on the way back as well.

Signed-off-by: Christian Decker <decker.christian@gmail.com>
2018-12-10 18:56:49 +00:00

887 lines
24 KiB
C

#include "lightningd/plugin.h"
#include <ccan/array_size/array_size.h>
#include <ccan/intmap/intmap.h>
#include <ccan/io/io.h>
#include <ccan/list/list.h>
#include <ccan/opt/opt.h>
#include <ccan/pipecmd/pipecmd.h>
#include <ccan/tal/path/path.h>
#include <ccan/tal/str/str.h>
#include <ccan/utf8/utf8.h>
#include <common/json_command.h>
#include <common/jsonrpc_errors.h>
#include <common/memleak.h>
#include <common/param.h>
#include <common/timeout.h>
#include <dirent.h>
#include <errno.h>
#include <lightningd/json.h>
#include <lightningd/lightningd.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
/* How many seconds may the plugin take to reply to the `getmanifest
* call`? This is the maximum delay to `lightningd --help` and until
* we can start the main `io_loop` to communicate with peers. If this
* hangs we can't do much, so we put an upper bound on the time we're
* willing to wait. Plugins shouldn't do any initialization in the
* `getmanifest` call anyway, that's what `init `is for. */
#define PLUGIN_MANIFEST_TIMEOUT 10
struct plugin {
struct list_node list;
pid_t pid;
char *cmd;
struct io_conn *stdin_conn, *stdout_conn;
bool stop;
struct plugins *plugins;
const char **plugin_path;
/* Stuff we read */
char *buffer;
size_t used, len_read;
/* Our json_streams. Since multiple streams could start
* returning data at once, we always service these in order,
* freeing once empty. */
struct json_stream **js_arr;
struct log *log;
/* List of options that this plugin registered */
struct list_head plugin_opts;
const char **methods;
/* Timer to add a timeout to some plugin RPC calls. Used to
* guarantee that `getmanifest` doesn't block indefinitely. */
const struct oneshot *timeout_timer;
};
struct plugin_request {
u64 id;
struct json_stream *stream;
/* The response handler to be called when plugin gives us an object. */
void (*cb)(const struct plugin_request *,
const char *buffer,
const jsmntok_t *toks,
const jsmntok_t *idtok,
void *);
void *arg;
};
struct plugins {
struct list_head plugins;
size_t pending_manifests;
/* Currently pending requests by their request ID */
UINTMAP(struct plugin_request *) pending_requests;
struct log *log;
struct log_book *log_book;
/* RPC interface to bind JSON-RPC methods to */
struct jsonrpc *rpc;
struct timers timers;
struct lightningd *ld;
};
/* Simple storage for plugin options inbetween registering them on the
* command line and passing them off to the plugin */
struct plugin_opt {
struct list_node list;
const char *name;
const char *description;
char *value;
};
struct plugins *plugins_new(const tal_t *ctx, struct log_book *log_book,
struct jsonrpc *rpc, struct lightningd *ld)
{
struct plugins *p;
p = tal(ctx, struct plugins);
list_head_init(&p->plugins);
p->log_book = log_book;
p->log = new_log(p, log_book, "plugin-manager");
p->rpc = rpc;
timers_init(&p->timers, time_mono());
p->ld = ld;
return p;
}
void plugin_register(struct plugins *plugins, const char* path TAKES)
{
struct plugin *p;
p = tal(plugins, struct plugin);
list_add_tail(&plugins->plugins, &p->list);
p->plugins = plugins;
p->cmd = tal_strdup(p, path);
p->js_arr = tal_arr(p, struct json_stream *, 0);
p->used = 0;
p->log = new_log(p, plugins->log_book, "plugin-%s",
path_basename(tmpctx, p->cmd));
p->methods = tal_arr(p, const char *, 0);
list_head_init(&p->plugin_opts);
}
static bool paths_match(const char *cmd, const char *name)
{
if (strchr(name, PATH_SEP)) {
const char *cmd_canon, *name_canon;
if (streq(cmd, name))
return true;
/* These return NULL path doesn't exist */
cmd_canon = path_canon(tmpctx, cmd);
name_canon = path_canon(tmpctx, name);
return cmd_canon && name_canon && streq(name_canon, cmd_canon);
} else {
/* No path separator means a basename match. */
const char *base = path_basename(tmpctx, cmd);
return streq(base, name);
}
}
bool plugin_remove(struct plugins *plugins, const char *name)
{
struct plugin *p, *next;
bool removed = false;
list_for_each_safe(&plugins->plugins, p, next, list) {
if (paths_match(p->cmd, name)) {
list_del_from(&plugins->plugins, &p->list);
tal_free(p);
removed = true;
}
}
return removed;
}
/**
* Kill a plugin process, with an error message.
*/
static void PRINTF_FMT(2,3) plugin_kill(struct plugin *plugin, char *fmt, ...)
{
char *msg;
va_list ap;
va_start(ap, fmt);
msg = tal_vfmt(plugin, fmt, ap);
va_end(ap);
log_broken(plugin->log, "Killing plugin: %s", msg);
plugin->stop = true;
io_wake(plugin);
kill(plugin->pid, SIGKILL);
list_del(&plugin->list);
}
/**
* Create the header of a JSON-RPC request and return open stream.
*
* The caller needs to add the request to req->stream.
*/
static struct plugin_request *
plugin_request_new_(struct plugin *plugin,
void (*cb)(const struct plugin_request *,
const char *buffer,
const jsmntok_t *toks,
const jsmntok_t *idtok,
void *),
void *arg)
{
static u64 next_request_id = 0;
struct plugin_request *req = tal(plugin, struct plugin_request);
req->id = next_request_id++;
req->cb = cb;
req->arg = arg;
/* We will not concurrently drain, if we do we must set the
* writer to non-NULL */
req->stream = new_json_stream(req, NULL);
/* Add to map so we can find it later when routing the response */
uintmap_add(&plugin->plugins->pending_requests, req->id, req);
return req;
}
#define plugin_request_new(plugin, cb, arg) \
plugin_request_new_( \
(plugin), \
typesafe_cb_preargs(void, void *, (cb), (arg), \
const struct plugin_request *, \
const char *buffer, \
const jsmntok_t *toks, \
const jsmntok_t *idtok), \
(arg))
/**
* Given a request, send it to the plugin.
*/
static void plugin_request_queue(struct plugin *plugin,
struct plugin_request *req)
{
*tal_arr_expand(&plugin->js_arr) = req->stream;
io_wake(plugin);
}
/**
* Try to parse a complete message from the plugin's buffer.
*
* Internally calls the handler if it was able to fully parse a JSON message,
* and returns true in that case.
*/
static bool plugin_read_json_one(struct plugin *plugin)
{
jsmntok_t *toks;
bool valid;
u64 id;
const jsmntok_t *idtok;
struct plugin_request *request;
/* FIXME: This could be done more efficiently by storing the
* toks and doing an incremental parse, like lightning-cli
* does. */
toks = json_parse_input(plugin->buffer, plugin->buffer, plugin->used,
&valid);
if (!toks) {
if (!valid) {
plugin_kill(plugin, "Failed to parse JSON response '%.*s'",
(int)plugin->used, plugin->buffer);
return false;
}
/* We need more. */
return false;
}
/* Empty buffer? (eg. just whitespace). */
if (tal_count(toks) == 1) {
plugin->used = 0;
return false;
}
idtok = json_get_member(plugin->buffer, toks, "id");
if (!idtok) {
plugin_kill(plugin, "JSON-RPC response does not contain an \"id\"-field");
return false;
}
/* We only send u64 ids, so if this fails it's a critical error (note
* that this also works if id is inside a JSON string!). */
if (!json_to_u64(plugin->buffer, idtok, &id)) {
plugin_kill(plugin, "JSON-RPC response \"id\"-field is not a u64");
return false;
}
request = uintmap_get(&plugin->plugins->pending_requests, id);
if (!request) {
plugin_kill(plugin, "Received a JSON-RPC response for non-existent request");
return false;
}
/* We expect the request->cb to copy if needed */
request->cb(request, plugin->buffer, toks, idtok, request->arg);
tal_free(request);
uintmap_del(&plugin->plugins->pending_requests, id);
/* Move this object out of the buffer */
memmove(plugin->buffer, plugin->buffer + toks[0].end,
tal_count(plugin->buffer) - toks[0].end);
plugin->used -= toks[0].end;
tal_free(toks);
return true;
}
static struct io_plan *plugin_read_json(struct io_conn *conn UNUSED,
struct plugin *plugin)
{
bool success;
plugin->used += plugin->len_read;
if (plugin->used == tal_count(plugin->buffer))
tal_resize(&plugin->buffer, plugin->used * 2);
/* Read and process all messages from the connection */
do {
success = plugin_read_json_one(plugin);
} while (success);
if (plugin->stop)
return io_close(plugin->stdout_conn);
/* Now read more from the connection */
return io_read_partial(plugin->stdout_conn,
plugin->buffer + plugin->used,
tal_count(plugin->buffer) - plugin->used,
&plugin->len_read, plugin_read_json, plugin);
}
/* Mutual recursion */
static struct io_plan *plugin_write_json(struct io_conn *conn,
struct plugin *plugin);
static struct io_plan *plugin_stream_complete(struct io_conn *conn, struct json_stream *js, struct plugin *plugin)
{
assert(tal_count(plugin->js_arr) > 0);
/* Remove js and shift all remainig over */
tal_arr_remove(&plugin->js_arr, 0);
return plugin_write_json(conn, plugin);
}
static struct io_plan *plugin_write_json(struct io_conn *conn,
struct plugin *plugin)
{
if (tal_count(plugin->js_arr)) {
return json_stream_output(plugin->js_arr[0], plugin->stdin_conn, plugin_stream_complete, plugin);
} else if (plugin->stop) {
return io_close(conn);
}
return io_out_wait(conn, plugin, plugin_write_json, plugin);
}
/**
* Finalizer for both stdin and stdout connections.
*
* Takes care of final cleanup, once the plugin is definitely dead.
*/
static void plugin_conn_finish(struct io_conn *conn, struct plugin *plugin)
{
if (conn == plugin->stdin_conn)
plugin->stdin_conn = NULL;
else if (conn == plugin->stdout_conn)
plugin->stdout_conn = NULL;
if (plugin->stdin_conn == NULL && plugin->stdout_conn == NULL)
tal_free(plugin);
}
static struct io_plan *plugin_stdin_conn_init(struct io_conn *conn,
struct plugin *plugin)
{
/* We write to their stdin */
/* We don't have anything queued yet, wait for notification */
plugin->stdin_conn = conn;
io_set_finish(conn, plugin_conn_finish, plugin);
return io_wait(plugin->stdin_conn, plugin, plugin_write_json, plugin);
}
static struct io_plan *plugin_stdout_conn_init(struct io_conn *conn,
struct plugin *plugin)
{
/* We read from their stdout */
plugin->stdout_conn = conn;
io_set_finish(conn, plugin_conn_finish, plugin);
return io_read_partial(plugin->stdout_conn, plugin->buffer,
tal_bytelen(plugin->buffer), &plugin->len_read,
plugin_read_json, plugin);
}
/* Callback called when parsing options. It just stores the value in
* the plugin_opt */
static char *plugin_opt_set(const char *arg, struct plugin_opt *popt)
{
tal_free(popt->value);
popt->value = tal_strdup(popt, arg);
return NULL;
}
/* Add a single plugin option to the plugin as well as registering it with the
* command line options. */
static bool plugin_opt_add(struct plugin *plugin, const char *buffer,
const jsmntok_t *opt)
{
const jsmntok_t *nametok, *typetok, *defaulttok, *desctok;
struct plugin_opt *popt;
nametok = json_get_member(buffer, opt, "name");
typetok = json_get_member(buffer, opt, "type");
desctok = json_get_member(buffer, opt, "description");
defaulttok = json_get_member(buffer, opt, "default");
if (!typetok || !nametok || !desctok) {
plugin_kill(plugin,
"An option is missing either \"name\", \"description\" or \"type\"");
return false;
}
/* FIXME(cdecker) Support numeric and boolean options as well */
if (!json_tok_streq(buffer, typetok, "string")) {
plugin_kill(plugin,
"Only \"string\" options currently supported");
return false;
}
popt = tal(plugin, struct plugin_opt);
popt->name = tal_fmt(plugin, "--%.*s", nametok->end - nametok->start,
buffer + nametok->start);
popt->value = NULL;
if (defaulttok) {
popt->value = tal_strndup(popt, buffer + defaulttok->start,
defaulttok->end - defaulttok->start);
popt->description = tal_fmt(
popt, "%.*s (default: %s)", desctok->end - desctok->start,
buffer + desctok->start, popt->value);
} else {
popt->description = tal_strndup(popt, buffer + desctok->start,
desctok->end - desctok->start);
}
list_add_tail(&plugin->plugin_opts, &popt->list);
opt_register_arg(popt->name, plugin_opt_set, NULL, popt,
popt->description);
return true;
}
/* Iterate through the options in the manifest response, and add them
* to the plugin and the command line options */
static bool plugin_opts_add(struct plugin *plugin,
const char *buffer,
const jsmntok_t *resulttok)
{
const jsmntok_t *options = json_get_member(buffer, resulttok, "options");
if (!options) {
plugin_kill(plugin,
"\"result.options\" was not found in the manifest");
return false;
}
if (options->type != JSMN_ARRAY) {
plugin_kill(plugin, "\"result.options\" is not an array");
return false;
}
for (size_t i = 0; i < options->size; i++)
if (!plugin_opt_add(plugin, buffer, json_get_arr(options, i)))
return false;
return true;
}
static void plugin_rpcmethod_destroy(struct json_command *cmd,
struct jsonrpc *rpc)
{
jsonrpc_command_remove(rpc, cmd->name);
}
static void json_stream_forward_change_id(struct json_stream *stream,
const char *buffer,
const jsmntok_t *toks,
const jsmntok_t *idtok,
const char *new_id)
{
/* We copy everything, but replace the id. Special care has to
* be taken when the id that is being replaced is a string. If
* we don't crop the quotes off we'll transform a numeric
* new_id into a string, or even worse, quote a string id
* twice. */
size_t offset = idtok->type==JSMN_STRING?1:0;
json_stream_append_part(stream, buffer + toks->start,
idtok->start - toks->start - offset);
json_stream_append(stream, new_id);
json_stream_append_part(stream, buffer + idtok->end + offset,
toks->end - idtok->end - offset);
/* We promise it will end in '\n\n' */
/* It's an object (with an id!): definitely can't be less that "{}" */
assert(toks->end - toks->start >= 2);
if (buffer[toks->end-1] != '\n')
json_stream_append(stream, "\n\n");
else if (buffer[toks->end-2] != '\n')
json_stream_append(stream, "\n");
}
static void plugin_rpcmethod_cb(const struct plugin_request *req,
const char *buffer,
const jsmntok_t *toks,
const jsmntok_t *idtok,
struct command *cmd)
{
struct json_stream *response;
response = json_stream_raw_for_cmd(cmd);
json_stream_forward_change_id(response, buffer, toks, idtok, cmd->id);
command_raw_complete(cmd, response);
}
static struct plugin *find_plugin_for_command(struct command *cmd)
{
struct plugins *plugins = cmd->ld->plugins;
struct plugin *plugin;
/* Find the plugin that registered this RPC call */
list_for_each(&plugins->plugins, plugin, list) {
for (size_t i=0; i<tal_count(plugin->methods); i++) {
if (streq(cmd->json_cmd->name, plugin->methods[i]))
return plugin;
}
}
/* This should never happen, it'd mean that a plugin didn't
* cleanup after dying */
abort();
}
static void plugin_rpcmethod_dispatch(struct command *cmd, const char *buffer,
const jsmntok_t *toks,
const jsmntok_t *params UNNEEDED)
{
const jsmntok_t *idtok;
struct plugin *plugin;
struct plugin_request *req;
char id[STR_MAX_CHARS(u64)];
if (cmd->mode == CMD_USAGE) {
/* FIXME! */
cmd->usage = "[params]";
return;
}
plugin = find_plugin_for_command(cmd);
/* Find ID again (We've parsed them before, this should not fail!) */
idtok = json_get_member(buffer, toks, "id");
assert(idtok != NULL);
req = plugin_request_new(plugin, plugin_rpcmethod_cb, cmd);
snprintf(id, ARRAY_SIZE(id), "%"PRIu64, req->id);
json_stream_forward_change_id(req->stream, buffer, toks, idtok, id);
plugin_request_queue(plugin, req);
command_still_pending(cmd);
}
static bool plugin_rpcmethod_add(struct plugin *plugin,
const char *buffer,
const jsmntok_t *meth)
{
const jsmntok_t *nametok, *desctok, *longdesctok;
struct json_command *cmd;
nametok = json_get_member(buffer, meth, "name");
desctok = json_get_member(buffer, meth, "description");
longdesctok = json_get_member(buffer, meth, "long_description");
if (!nametok || nametok->type != JSMN_STRING) {
plugin_kill(plugin,
"rpcmethod does not have a string \"name\": %.*s",
meth->end - meth->start, buffer + meth->start);
return false;
}
if (!desctok || desctok->type != JSMN_STRING) {
plugin_kill(plugin,
"rpcmethod does not have a string "
"\"description\": %.*s",
meth->end - meth->start, buffer + meth->start);
return false;
}
if (longdesctok && longdesctok->type != JSMN_STRING) {
plugin_kill(plugin,
"\"long_description\" is not a string: %.*s",
meth->end - meth->start, buffer + meth->start);
return false;
}
cmd = notleak(tal(plugin, struct json_command));
cmd->name = tal_strndup(cmd, buffer + nametok->start,
nametok->end - nametok->start);
cmd->description = tal_strndup(cmd, buffer + desctok->start,
desctok->end - desctok->start);
if (longdesctok)
cmd->verbose =
tal_strndup(cmd, buffer + longdesctok->start,
longdesctok->end - longdesctok->start);
else
cmd->verbose = cmd->description;
cmd->deprecated = false;
cmd->dispatch = plugin_rpcmethod_dispatch;
tal_add_destructor2(cmd, plugin_rpcmethod_destroy, plugin->plugins->rpc);
if (!jsonrpc_command_add(plugin->plugins->rpc, cmd)) {
log_broken(plugin->log,
"Could not register method \"%s\", a method with "
"that name is already registered",
cmd->name);
return false;
}
*tal_arr_expand(&plugin->methods) = cmd->name;
return true;
}
static bool plugin_rpcmethods_add(struct plugin *plugin,
const char *buffer,
const jsmntok_t *resulttok)
{
const jsmntok_t *methods =
json_get_member(buffer, resulttok, "rpcmethods");
if (!methods)
return false;
if (methods->type != JSMN_ARRAY) {
plugin_kill(plugin,
"\"result.rpcmethods\" is not an array");
return false;
}
for (size_t i = 0; i < methods->size; i++)
if (!plugin_rpcmethod_add(plugin, buffer,
json_get_arr(methods, i)))
return false;
return true;
}
static void plugin_manifest_timeout(struct plugin *plugin)
{
log_broken(plugin->log, "The plugin failed to respond to \"getmanifest\" in time, terminating.");
fatal("Can't recover from plugin failure, terminating.");
}
/**
* Callback for the plugin_manifest request.
*/
static void plugin_manifest_cb(const struct plugin_request *req,
const char *buffer,
const jsmntok_t *toks,
const jsmntok_t *idtok,
struct plugin *plugin)
{
const jsmntok_t *resulttok;
/* Check if all plugins have replied to getmanifest, and break
* if they are */
plugin->plugins->pending_manifests--;
if (plugin->plugins->pending_manifests == 0)
io_break(plugin->plugins);
resulttok = json_get_member(buffer, toks, "result");
if (!resulttok || resulttok->type != JSMN_OBJECT) {
plugin_kill(plugin,
"\"getmanifest\" result is not an object");
return;
}
if (!plugin_opts_add(plugin, buffer, resulttok)
|| !plugin_rpcmethods_add(plugin, buffer, resulttok))
plugin_kill(plugin, "Failed to register options or methods");
/* Reset timer, it'd kill us otherwise. */
tal_free(plugin->timeout_timer);
}
/* If this is a valid plugin return full path name, otherwise NULL */
static const char *plugin_fullpath(const tal_t *ctx, const char *dir,
const char *basename)
{
struct stat st;
const char *fullname;
struct utf8_state utf8 = UTF8_STATE_INIT;
for (size_t i = 0; basename[i]; i++) {
if (!utf8_decode(&utf8, basename[i]))
continue;
/* Not valid UTF8? Let's not go there... */
if (errno != 0)
return NULL;
if (utf8.used_len != 1)
continue;
if (!cispunct(utf8.c))
continue;
if (utf8.c != '-' && utf8.c != '_' && utf8.c != '.')
return NULL;
}
fullname = path_join(ctx, dir, basename);
if (stat(fullname, &st) != 0)
return tal_free(fullname);
/* Only regular files please (or symlinks to such: stat not lstat!) */
if ((st.st_mode & S_IFMT) != S_IFREG)
return tal_free(fullname);
/* Must be executable by someone. */
if (!(st.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))
return tal_free(fullname);
return fullname;
}
char *add_plugin_dir(struct plugins *plugins, const char *dir, bool nonexist_ok)
{
struct dirent *di;
DIR *d = opendir(dir);
if (!d) {
if (nonexist_ok && errno == ENOENT)
return NULL;
return tal_fmt("Failed to open plugin-dir %s: %s",
dir, strerror(errno));
}
while ((di = readdir(d)) != NULL) {
const char *fullpath;
if (streq(di->d_name, ".") || streq(di->d_name, ".."))
continue;
fullpath = plugin_fullpath(NULL, dir, di->d_name);
if (fullpath)
plugin_register(plugins, take(fullpath));
}
return NULL;
}
void clear_plugins(struct plugins *plugins)
{
struct plugin *p;
log_info(plugins->log, "clear-plugins removing all plugins");
while ((p = list_pop(&plugins->plugins, struct plugin, list)) != NULL)
tal_free(p);
}
/* For our own "getmanifest" and "init" requests: starts params[] */
static void start_simple_request(struct plugin_request *req, const char *reqname)
{
json_object_start(req->stream, NULL);
json_add_string(req->stream, "jsonrpc", "2.0");
json_add_string(req->stream, "method", reqname);
json_add_u64(req->stream, "id", req->id);
}
static void end_simple_request(struct plugin *plugin, struct plugin_request *req)
{
json_object_end(req->stream);
json_stream_append(req->stream, "\n\n");
plugin_request_queue(plugin, req);
}
void plugins_init(struct plugins *plugins, const char *dev_plugin_debug)
{
struct plugin *p;
char **cmd;
int stdin, stdout;
struct timer *expired;
struct plugin_request *req;
plugins->pending_manifests = 0;
uintmap_init(&plugins->pending_requests);
/* Spawn the plugin processes before entering the io_loop */
list_for_each(&plugins->plugins, p, list) {
bool debug;
debug = dev_plugin_debug && strends(p->cmd, dev_plugin_debug);
cmd = tal_arrz(p, char *, 2 + debug);
cmd[0] = p->cmd;
if (debug)
cmd[1] = "--debugger";
p->pid = pipecmdarr(&stdin, &stdout, &pipecmd_preserve, cmd);
if (p->pid == -1)
fatal("error starting plugin '%s': %s", p->cmd,
strerror(errno));
p->buffer = tal_arr(p, char, 64);
p->stop = false;
/* Create two connections, one read-only on top of p->stdin, and one
* write-only on p->stdout */
io_new_conn(p, stdout, plugin_stdout_conn_init, p);
io_new_conn(p, stdin, plugin_stdin_conn_init, p);
req = plugin_request_new(p, plugin_manifest_cb, p);
start_simple_request(req, "getmanifest");
json_array_start(req->stream, "params");
json_array_end(req->stream);
end_simple_request(p, req);
plugins->pending_manifests++;
/* Don't timeout if they're running a debugger. */
if (debug)
p->timeout_timer = NULL;
else {
p->timeout_timer
= new_reltimer(&plugins->timers, p,
time_from_sec(PLUGIN_MANIFEST_TIMEOUT),
plugin_manifest_timeout, p);
}
tal_free(cmd);
}
while (plugins->pending_manifests > 0) {
void *v = io_loop(&plugins->timers, &expired);
if (v == plugins)
break;
if (expired)
timer_expired(plugins, expired);
}
}
static void plugin_config_cb(const struct plugin_request *req,
const char *buffer,
const jsmntok_t *toks,
const jsmntok_t *idtok,
struct plugin *plugin)
{
/* Nothing to be done here, this is just a report */
}
/* FIXME(cdecker) This just builds a string for the request because
* the json_stream is tightly bound to the command interface. It
* should probably be generalized and fixed up. */
static void plugin_config(struct plugin *plugin)
{
struct plugin_opt *opt;
const char *name;
struct plugin_request *req;
struct lightningd *ld = plugin->plugins->ld;
/* No writer since we don't flush concurrently. */
req = plugin_request_new(plugin, plugin_config_cb, plugin);
start_simple_request(req, "init");
json_object_start(req->stream, "params"); /* start of .params */
/* Add .params.options */
json_object_start(req->stream, "options");
list_for_each(&plugin->plugin_opts, opt, list) {
/* Trim the `--` that we added before */
name = opt->name + 2;
json_add_string(req->stream, name, opt->value);
}
json_object_end(req->stream); /* end of .params.options */
/* Add .params.configuration */
json_object_start(req->stream, "configuration");
json_add_string(req->stream, "lightning-dir", ld->config_dir);
json_add_string(req->stream, "rpc-file", ld->rpc_filename);
json_object_end(req->stream);
json_object_end(req->stream); /* end of .params */
end_simple_request(plugin, req);
}
void plugins_config(struct plugins *plugins)
{
struct plugin *p;
list_for_each(&plugins->plugins, p, list) {
plugin_config(p);
}
}
void json_add_opt_plugins(struct json_stream *response,
const struct plugins *plugins)
{
struct plugin *p;
list_for_each(&plugins->plugins, p, list) {
json_add_string(response, "plugin", p->cmd);
}
}