You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1891 lines
53 KiB

#include <ccan/array_size/array_size.h>
#include <ccan/list/list.h>
#include <ccan/mem/mem.h>
#include <ccan/opt/opt.h>
#include <ccan/tal/str/str.h>
#include <ccan/utf8/utf8.h>
#include <common/features.h>
#include <common/json_helpers.h>
#include <common/utils.h>
#include <common/version.h>
#include <lightningd/json.h>
#include <lightningd/notification.h>
#include <lightningd/options.h>
#include <lightningd/plugin.h>
#include <lightningd/plugin_control.h>
#include <lightningd/plugin_hook.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
/* Only this file can include this generated header! */
# include <plugins/list_of_builtin_plugins_gen.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 60
/* A simple struct associating an incoming RPC method call with the plugin
* that is handling it and the downstream jsonrpc_request. */
struct plugin_rpccall {
struct list_node list;
struct command *cmd;
struct plugin *plugin;
struct jsonrpc_request *request;
};
#if DEVELOPER
static void memleak_help_pending_requests(struct htable *memtable,
struct plugins *plugins)
{
memleak_remove_uintmap(memtable, &plugins->pending_requests);
}
#endif /* DEVELOPER */
struct plugins *plugins_new(const tal_t *ctx, struct log_book *log_book,
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, NULL, "plugin-manager");
p->ld = ld;
p->startup = true;
p->json_cmds = tal_arr(p, struct command *, 0);
p->blacklist = tal_arr(p, const char *, 0);
p->shutdown = false;
p->plugin_idx = 0;
#if DEVELOPER
p->dev_builtin_plugins_unimportant = false;
#endif /* DEVELOPER */
uintmap_init(&p->pending_requests);
memleak_add_helper(p, memleak_help_pending_requests);
return p;
}
void plugins_free(struct plugins *plugins)
{
struct plugin *p;
plugins->shutdown = true;
/* Plugins are usually the unit of allocation, and they are internally
* consistent, so let's free each plugin first. */
while (!list_empty(&plugins->plugins)) {
p = list_pop(&plugins->plugins, struct plugin, list);
tal_free(p);
}
tal_free(plugins);
}
/* Once they've all replied with their manifests, we can order them. */
static void check_plugins_manifests(struct plugins *plugins)
{
struct plugin **depfail;
if (plugins_any_in_state(plugins, AWAITING_GETMANIFEST_RESPONSE))
return;
/* Now things are settled, try to order hooks. */
depfail = plugin_hooks_make_ordered(tmpctx);
for (size_t i = 0; i < tal_count(depfail); i++) {
/* Only complain and free plugins! */
if (depfail[i]->plugin_state != NEEDS_INIT)
continue;
plugin_kill(depfail[i],
"Cannot meet required hook dependencies");
}
/* As startup, we break out once all getmanifest are returned */
if (plugins->startup)
io_break(plugins);
else
/* Otherwise we go straight into configuring them */
plugins_config(plugins);
}
static void check_plugins_initted(struct plugins *plugins)
{
struct command **json_cmds;
if (!plugins_all_in_state(plugins, INIT_COMPLETE))
return;
/* Clear commands first, in case callbacks add new ones.
* Paranoia, but wouldn't that be a nasty bug to find? */
json_cmds = plugins->json_cmds;
plugins->json_cmds = tal_arr(plugins, struct command *, 0);
for (size_t i = 0; i < tal_count(json_cmds); i++)
plugin_cmd_all_complete(plugins, json_cmds[i]);
tal_free(json_cmds);
}
struct command_result *plugin_register_all_complete(struct lightningd *ld,
struct command *cmd)
{
if (plugins_all_in_state(ld->plugins, INIT_COMPLETE))
return plugin_cmd_all_complete(ld->plugins, cmd);
tal_arr_expand(&ld->plugins->json_cmds, cmd);
return NULL;
}
static void destroy_plugin(struct plugin *p)
{
struct plugin_rpccall *call;
list_del(&p->list);
/* Terminate all pending RPC calls with an error. */
list_for_each(&p->pending_rpccalls, call, list) {
was_pending(command_fail(
call->cmd, PLUGIN_TERMINATED,
"Plugin terminated before replying to RPC call."));
}
/* Reset, so calls below don't try to fail it again! */
list_head_init(&p->pending_rpccalls);
/* If this was last one manifests were waiting for, handle deps */
if (p->plugin_state == AWAITING_GETMANIFEST_RESPONSE)
check_plugins_manifests(p->plugins);
/* If this was the last one init was waiting for, handle cmd replies */
if (p->plugin_state == AWAITING_INIT_RESPONSE)
check_plugins_initted(p->plugins);
/* If we are shutting down, do not continue to checking if
* the dying plugin is important. */
if (p->plugins->shutdown)
return;
/* Now check if the dying plugin is important. */
if (p->important) {
log_broken(p->log,
"Plugin marked as important, "
"shutting down lightningd!");
lightningd_exit(p->plugins->ld, 1);
}
}
struct plugin *plugin_register(struct plugins *plugins, const char* path TAKES,
struct command *start_cmd, bool important,
const char *parambuf STEALS,
const jsmntok_t *params STEALS)
{
struct plugin *p, *p_temp;
/* Don't register an already registered plugin */
list_for_each(&plugins->plugins, p_temp, list) {
if (streq(path, p_temp->cmd)) {
if (taken(path))
tal_free(path);
/* If added as "important", upgrade to "important". */
if (important)
p_temp->important = true;
return NULL;
}
}
p = tal(plugins, struct plugin);
p->plugins = plugins;
p->cmd = tal_strdup(p, path);
p->start_cmd = start_cmd;
p->plugin_state = UNCONFIGURED;
p->js_arr = tal_arr(p, struct json_stream *, 0);
p->used = 0;
p->subscriptions = NULL;
p->dynamic = false;
p->index = plugins->plugin_idx++;
p->log = new_log(p, plugins->log_book, NULL, "plugin-%s",
path_basename(tmpctx, p->cmd));
p->methods = tal_arr(p, const char *, 0);
list_head_init(&p->plugin_opts);
list_add_tail(&plugins->plugins, &p->list);
tal_add_destructor(p, destroy_plugin);
list_head_init(&p->pending_rpccalls);
p->important = important;
p->parambuf = tal_steal(p, parambuf);
p->params = tal_steal(p, params);
return p;
}
bool plugin_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);
}
}
void plugin_blacklist(struct plugins *plugins, const char *name)
{
struct plugin *p, *next;
log_debug(plugins->log, "blacklist for %s", name);
list_for_each_safe(&plugins->plugins, p, next, list) {
if (plugin_paths_match(p->cmd, name)) {
log_info(plugins->log, "%s: disabled via disable-plugin",
p->cmd);
list_del_from(&plugins->plugins, &p->list);
/* disable-plugin overrides important-plugin. */
p->important = false;
tal_free(p);
}
}
tal_arr_expand(&plugins->blacklist,
tal_strdup(plugins->blacklist, name));
}
bool plugin_blacklisted(struct plugins *plugins, const char *name)
{
for (size_t i = 0; i < tal_count(plugins->blacklist); i++)
if (plugin_paths_match(name, plugins->blacklist[i]))
return true;
return false;
}
void plugin_kill(struct plugin *plugin, const char *msg)
{
log_info(plugin->log, "Killing plugin: %s", msg);
kill(plugin->pid, SIGKILL);
if (plugin->start_cmd) {
plugin_cmd_killed(plugin->start_cmd, plugin, msg);
plugin->start_cmd = NULL;
}
/* Don't come back when we free stdout_conn! */
io_set_finish(plugin->stdout_conn, NULL, NULL);
tal_free(plugin);
}
/**
* Send a JSON-RPC message (request or notification) to the plugin.
*/
static void plugin_send(struct plugin *plugin, struct json_stream *stream)
{
tal_steal(plugin->js_arr, stream);
tal_arr_expand(&plugin->js_arr, stream);
io_wake(plugin);
}
/* Returns the error string, or NULL */
static const char *plugin_log_handle(struct plugin *plugin,
const jsmntok_t *paramstok)
WARN_UNUSED_RESULT;
static const char *plugin_log_handle(struct plugin *plugin,
const jsmntok_t *paramstok)
{
const jsmntok_t *msgtok, *leveltok;
enum log_level level;
bool call_notifier;
msgtok = json_get_member(plugin->buffer, paramstok, "message");
leveltok = json_get_member(plugin->buffer, paramstok, "level");
if (!msgtok || msgtok->type != JSMN_STRING) {
return tal_fmt(plugin, "Log notification from plugin doesn't have "
"a string \"message\" field");
}
if (!leveltok)
level = LOG_INFORM;
else if (!log_level_parse(plugin->buffer + leveltok->start,
leveltok->end - leveltok->start,
&level)
/* FIXME: Allow io logging? */
|| level == LOG_IO_IN
|| level == LOG_IO_OUT) {
return tal_fmt(plugin,
"Unknown log-level %.*s, valid values are "
"\"debug\", \"info\", \"warn\", or \"error\".",
json_tok_full_len(leveltok),
json_tok_full(plugin->buffer, leveltok));
}
call_notifier = (level == LOG_BROKEN || level == LOG_UNUSUAL)? true : false;
/* FIXME: Let plugin specify node_id? */
log_(plugin->log, level, NULL, call_notifier, "%.*s", msgtok->end - msgtok->start,
plugin->buffer + msgtok->start);
return NULL;
}
static const char *plugin_notify_handle(struct plugin *plugin,
const jsmntok_t *methodtok,
const jsmntok_t *paramstok)
{
const jsmntok_t *idtok;
u64 id;
struct jsonrpc_request *request;
/* id inside params tells us which id to redirect to. */
idtok = json_get_member(plugin->buffer, paramstok, "id");
if (!idtok || !json_to_u64(plugin->buffer, idtok, &id)) {
return tal_fmt(plugin,
"JSON-RPC notify \"id\"-field is not a u64");
}
request = uintmap_get(&plugin->plugins->pending_requests, id);
if (!request) {
return tal_fmt(
plugin,
"Received a JSON-RPC notify for non-existent request");
}
/* Ignore if they don't have a callback */
if (request->notify_cb)
request->notify_cb(plugin->buffer, methodtok, paramstok, idtok,
request->response_cb_arg);
return NULL;
}
/* Returns the error string, or NULL */
static const char *plugin_notification_handle(struct plugin *plugin,
const jsmntok_t *toks)
WARN_UNUSED_RESULT;
static const char *plugin_notification_handle(struct plugin *plugin,
const jsmntok_t *toks)
{
const jsmntok_t *methtok, *paramstok;
methtok = json_get_member(plugin->buffer, toks, "method");
paramstok = json_get_member(plugin->buffer, toks, "params");
if (!methtok || !paramstok) {
return tal_fmt(plugin,
"Malformed JSON-RPC notification missing "
"\"method\" or \"params\": %.*s",
toks->end - toks->start,
plugin->buffer + toks->start);
}
/* Dispatch incoming notifications. This is currently limited
* to just a few method types, should this ever become
* unwieldy we can switch to the AUTODATA construction to
* register notification handlers in a variety of places. */
if (json_tok_streq(plugin->buffer, methtok, "log")) {
return plugin_log_handle(plugin, paramstok);
} else if (json_tok_streq(plugin->buffer, methtok, "message")
|| json_tok_streq(plugin->buffer, methtok, "progress")) {
return plugin_notify_handle(plugin, methtok, paramstok);
} else {
return tal_fmt(plugin, "Unknown notification method %.*s",
json_tok_full_len(methtok),
json_tok_full(plugin->buffer, methtok));
}
}
/* Returns the error string, or NULL */
static const char *plugin_response_handle(struct plugin *plugin,
const jsmntok_t *toks,
const jsmntok_t *idtok)
WARN_UNUSED_RESULT;
static const char *plugin_response_handle(struct plugin *plugin,
const jsmntok_t *toks,
const jsmntok_t *idtok)
{
struct plugin_destroyed *pd;
struct jsonrpc_request *request;
u64 id;
/* 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)) {
return tal_fmt(plugin,
"JSON-RPC response \"id\"-field is not a u64");
}
request = uintmap_get(&plugin->plugins->pending_requests, id);
if (!request) {
return tal_fmt(
plugin,
"Received a JSON-RPC response for non-existent request");
}
/* We expect the request->cb to copy if needed */
pd = plugin_detect_destruction(plugin);
request->response_cb(plugin->buffer, toks, idtok, request->response_cb_arg);
/* Note that in the case of 'plugin stop' this can free request (since
* plugin is parent), so detect that case */
if (!was_plugin_destroyed(pd))
tal_free(request);
return NULL;
}
/**
* Try to parse a complete message from the plugin's buffer.
*
* Returns NULL if there was no error.
* If it can parse a JSON message, sets *@complete, and returns any error
* from the callback.
*
* If @destroyed was set, it means the plugin called plugin stop on itself.
*/
static const char *plugin_read_json_one(struct plugin *plugin,
bool *complete,
bool *destroyed)
{
const jsmntok_t *jrtok, *idtok;
struct plugin_destroyed *pd;
const char *err;
*destroyed = false;
/* Note that in the case of 'plugin stop' this can free request (since
* plugin is parent), so detect that case */
if (!json_parse_input(&plugin->parser, &plugin->toks,
plugin->buffer, plugin->used,
complete)) {
return tal_fmt(plugin,
"Failed to parse JSON response '%.*s'",
(int)plugin->used, plugin->buffer);
}
if (!*complete) {
/* We need more. */
return NULL;
}
/* Empty buffer? (eg. just whitespace). */
if (tal_count(plugin->toks) == 1) {
plugin->used = 0;
jsmn_init(&plugin->parser);
toks_reset(plugin->toks);
/* We need more. */
*complete = false;
return NULL;
}
jrtok = json_get_member(plugin->buffer, plugin->toks, "jsonrpc");
idtok = json_get_member(plugin->buffer, plugin->toks, "id");
if (!jrtok) {
return tal_fmt(
plugin,
"JSON-RPC message does not contain \"jsonrpc\" field");
}
pd = plugin_detect_destruction(plugin);
if (!idtok) {
/* A Notification is a Request object without an "id"
* member. A Request object that is a Notification
* signifies the Client's lack of interest in the
* corresponding Response object, and as such no
* Response object needs to be returned to the
* client. The Server MUST NOT reply to a
* Notification, including those that are within a
* batch request.
*
* https://www.jsonrpc.org/specification#notification
*/
err = plugin_notification_handle(plugin, plugin->toks);
} else {
/* When a rpc call is made, the Server MUST reply with
* a Response, except for in the case of
* Notifications. The Response is expressed as a
* single JSON Object, with the following members:
*
* - jsonrpc: A String specifying the version of the
* JSON-RPC protocol. MUST be exactly "2.0".
*
* - result: This member is REQUIRED on success. This
* member MUST NOT exist if there was an error
* invoking the method. The value of this member is
* determined by the method invoked on the Server.
*
* - error: This member is REQUIRED on error. This
* member MUST NOT exist if there was no error
* triggered during invocation.
*
* - id: This member is REQUIRED. It MUST be the same
* as the value of the id member in the Request
* Object. If there was an error in detecting the id
* in the Request object (e.g. Parse error/Invalid
* Request), it MUST be Null. Either the result
* member or error member MUST be included, but both
* members MUST NOT be included.
*
* https://www.jsonrpc.org/specification#response_object
*/
err = plugin_response_handle(plugin, plugin->toks, idtok);
}
/* Corner case: rpc_command hook can destroy plugin for 'plugin
* stop'! */
if (was_plugin_destroyed(pd)) {
*destroyed = true;
} else {
/* Move this object out of the buffer */
memmove(plugin->buffer, plugin->buffer + plugin->toks[0].end,
tal_count(plugin->buffer) - plugin->toks[0].end);
plugin->used -= plugin->toks[0].end;
jsmn_init(&plugin->parser);
toks_reset(plugin->toks);
}
return err;
}
static struct io_plan *plugin_read_json(struct io_conn *conn,
struct plugin *plugin)
{
bool success;
bool have_full;
log_io(plugin->log, LOG_IO_IN, NULL, "",
plugin->buffer + plugin->used, plugin->len_read);
/* Our JSON parser is pretty good at incremental parsing, but
* `getrawblock` gives a giant 2MB token, which forces it to re-parse
* every time until we have all of it. However, we can't complete a
* JSON object without a '}', so we do a cheaper check here.
*/
have_full = memchr(plugin->buffer + plugin->used, '}',
plugin->len_read);
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 */
if (have_full) {
do {
bool destroyed;
const char *err;
err =
plugin_read_json_one(plugin, &success, &destroyed);
/* If it's destroyed, conn is already freed! */
if (destroyed)
return io_close(NULL);
if (err) {
plugin_kill(plugin, err);
/* plugin_kill frees plugin */
return io_close(NULL);
}
} while (success);
}
/* 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);
/* It got dropped off the queue, free it. */
tal_free(js);
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);
}
return io_out_wait(conn, plugin, plugin_write_json, plugin);
}
/* This catches the case where their stdout closes (usually they're dead). */
static void plugin_conn_finish(struct io_conn *conn, struct plugin *plugin)
{
plugin_kill(plugin, "Plugin exited before completing handshake.");
}
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 */
return io_wait(conn, plugin, plugin_write_json, plugin);
}
struct io_plan *plugin_stdout_conn_init(struct io_conn *conn,
struct plugin *plugin)
{
/* We read from their stdout */
io_set_finish(conn, plugin_conn_finish, plugin);
return io_read_partial(conn, plugin->buffer,
tal_bytelen(plugin->buffer), &plugin->len_read,
plugin_read_json, plugin);
}
/* Returns NULL if invalid value for that type */
static struct plugin_opt_value *plugin_opt_value(const tal_t *ctx,
const char *type,
const char *arg)
{
struct plugin_opt_value *v = tal(ctx, struct plugin_opt_value);
v->as_str = tal_strdup(v, arg);
if (streq(type, "int")) {
long long l;
char *endp;
errno = 0;
l = strtoll(arg, &endp, 0);
if (errno || *endp)
return tal_free(v);
v->as_int = l;
/* Check if the number did not fit in `s64` (in case `long long`
* is a bigger type). */
if (v->as_int != l)
return tal_free(v);
} else if (streq(type, "bool")) {
/* valid values are 'true', 'True', '1', '0', 'false', 'False', or '' */
if (streq(arg, "true") || streq(arg, "True") || streq(arg, "1")) {
v->as_bool = true;
} else if (streq(arg, "false") || streq(arg, "False")
|| streq(arg, "0")) {
v->as_bool = false;
} else
return tal_free(v);
} else if (streq(type, "flag")) {
v->as_bool = true;
}
return v;
}
char *plugin_opt_flag_set(struct plugin_opt *popt)
{
/* A set flag is a true */
tal_free(popt->values);
popt->values = tal_arr(popt, struct plugin_opt_value *, 1);
popt->values[0] = plugin_opt_value(popt->values, popt->type, "true");
return NULL;
}
char *plugin_opt_set(const char *arg, struct plugin_opt *popt)
{
struct plugin_opt_value *v;
/* Warn them that this is deprecated */
if (popt->deprecated && !deprecated_apis)
return tal_fmt(tmpctx, "deprecated option (will be removed!)");
if (!popt->multi) {
tal_free(popt->values);
popt->values = tal_arr(popt, struct plugin_opt_value *, 0);
}
v = plugin_opt_value(popt->values, popt->type, arg);
if (!v)
return tal_fmt(tmpctx, "%s does not parse as type %s",
arg, popt->type);
tal_arr_expand(&popt->values, v);
return NULL;
}
static void destroy_plugin_opt(struct plugin_opt *opt)
{
if (!opt_unregister(opt->name))
fatal("Could not unregister %s", opt->name);
list_del(&opt->list);
}
/* Add a single plugin option to the plugin as well as registering it with the
* command line options. */
static const char *plugin_opt_add(struct plugin *plugin, const char *buffer,
const jsmntok_t *opt)
{
const jsmntok_t *nametok, *typetok, *defaulttok, *desctok, *deptok, *multitok;
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");
deptok = json_get_member(buffer, opt, "deprecated");
multitok = json_get_member(buffer, opt, "multi");
if (!typetok || !nametok || !desctok) {
return tal_fmt(plugin,
"An option is missing either \"name\", \"description\" or \"type\"");
}
popt = tal(plugin, struct plugin_opt);
popt->values = tal_arr(popt, struct plugin_opt_value *, 0);
popt->name = tal_fmt(popt, "--%.*s", nametok->end - nametok->start,
buffer + nametok->start);
popt->description = NULL;
if (deptok) {
if (!json_to_bool(buffer, deptok, &popt->deprecated))
return tal_fmt(plugin,
"%s: invalid \"deprecated\" field %.*s",
popt->name,
deptok->end - deptok->start,
buffer + deptok->start);
} else
popt->deprecated = false;
if (multitok) {
if (!json_to_bool(buffer, multitok, &popt->multi))
return tal_fmt(plugin,
"%s: invalid \"multi\" field %.*s",
popt->name,
multitok->end - multitok->start,
buffer + multitok->start);
} else
popt->multi = false;
popt->def = NULL;
if (json_tok_streq(buffer, typetok, "string")) {
popt->type = "string";
} else if (json_tok_streq(buffer, typetok, "int")) {
popt->type = "int";
} else if (json_tok_streq(buffer, typetok, "bool")
|| json_tok_streq(buffer, typetok, "flag")) {
popt->type = json_strdup(popt, buffer, typetok);
if (popt->multi)
return tal_fmt(plugin,
"%s type \"%s\" cannot have multi",
popt->name, popt->type);
/* We default flags to false, the default token is ignored */
if (json_tok_streq(buffer, typetok, "flag"))
defaulttok = NULL;
} else {
return tal_fmt(plugin,
"Only \"string\", \"int\", \"bool\", and \"flag\" options are supported");
}
if (defaulttok) {
popt->def = plugin_opt_value(popt, popt->type,
json_strdup(tmpctx, buffer, defaulttok));
if (!popt->def)
return tal_fmt(tmpctx, "default %.*s is not a valid %s",
json_tok_full_len(defaulttok),
json_tok_full(buffer, defaulttok),
popt->type);
}
if (!popt->description)
popt->description = json_strdup(popt, buffer, desctok);
list_add_tail(&plugin->plugin_opts, &popt->list);
if (streq(popt->type, "flag"))
opt_register_noarg(popt->name, plugin_opt_flag_set, popt,
popt->description);
else
opt_register_arg(popt->name, plugin_opt_set, NULL, popt,
popt->description);
tal_add_destructor(popt, destroy_plugin_opt);
return NULL;
}
/* Iterate through the options in the manifest response, and add them
* to the plugin and the command line options */
static const char *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) {
return tal_fmt(plugin,
"\"result.options\" was not found in the manifest");
}
if (options->type != JSMN_ARRAY) {
return tal_fmt(plugin, "\"result.options\" is not an array");
}
for (size_t i = 0; i < options->size; i++) {
const char *err;
err = plugin_opt_add(plugin, buffer, json_get_arr(options, i));
if (err)
return err;
}
return NULL;
}
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(stream, buffer + toks->start,
idtok->start - toks->start - offset);
json_stream_append(stream, new_id, strlen(new_id));
json_stream_append(stream, buffer + idtok->end + offset,
toks->end - idtok->end - offset);
}
static void plugin_rpcmethod_cb(const char *buffer,
const jsmntok_t *toks,
const jsmntok_t *idtok,
struct plugin_rpccall *call)
{
struct command *cmd = call->cmd;
struct json_stream *response;
response = json_stream_raw_for_cmd(cmd);
json_stream_forward_change_id(response, buffer, toks, idtok, cmd->id);
json_stream_double_cr(response);
command_raw_complete(cmd, response);
list_del(&call->list);
tal_free(call);
}
static void plugin_notify_cb(const char *buffer,
const jsmntok_t *methodtok,
const jsmntok_t *paramtoks,
const jsmntok_t *idtok,
struct plugin_rpccall *call)
{
struct command *cmd = call->cmd;
struct json_stream *response;
if (!cmd->jcon || !cmd->send_notifications)
return;
response = json_stream_raw_for_cmd(cmd);
json_object_start(response, NULL);
json_add_string(response, "jsonrpc", "2.0");
json_add_tok(response, "method", methodtok, buffer);
json_stream_append(response, ",\"params\":", strlen(",\"params\":"));
json_stream_forward_change_id(response, buffer,
paramtoks, idtok, cmd->id);
json_object_end(response);
json_stream_double_cr(response);
json_stream_flush(response);
}
struct plugin *find_plugin_for_command(struct lightningd *ld,
const char *cmd_name)
{
struct plugins *plugins = 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_name, plugin->methods[i]))
return plugin;
}
}
return NULL;
}
static struct command_result *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 jsonrpc_request *req;
char id[STR_MAX_CHARS(u64)];
struct plugin_rpccall *call;
if (cmd->mode == CMD_CHECK)
return command_param_failed();
plugin = find_plugin_for_command(cmd->ld, cmd->json_cmd->name);
if (!plugin)
fatal("No plugin for %s ?", cmd->json_cmd->name);
/* Find ID again (We've parsed them before, this should not fail!) */
idtok = json_get_member(buffer, toks, "id");
assert(idtok != NULL);
call = tal(plugin, struct plugin_rpccall);
call->cmd = cmd;
req = jsonrpc_request_start(plugin, NULL, plugin->log,
plugin_notify_cb,
plugin_rpcmethod_cb, call);
call->request = req;
call->plugin = plugin;
list_add_tail(&plugin->pending_rpccalls, &call->list);
snprintf(id, ARRAY_SIZE(id), "%"PRIu64, req->id);
json_stream_forward_change_id(req->stream, buffer, toks, idtok, id);
json_stream_double_cr(req->stream);
plugin_request_send(plugin, req);
req->stream = NULL;
return command_still_pending(cmd);
}
static const char *plugin_rpcmethod_add(struct plugin *plugin,
const char *buffer,
const jsmntok_t *meth)
{
const jsmntok_t *nametok, *categorytok, *desctok, *longdesctok,
*usagetok, *deptok;
struct json_command *cmd;
const char *usage;
nametok = json_get_member(buffer, meth, "name");
categorytok = json_get_member(buffer, meth, "category");
desctok = json_get_member(buffer, meth, "description");
longdesctok = json_get_member(buffer, meth, "long_description");
usagetok = json_get_member(buffer, meth, "usage");
deptok = json_get_member(buffer, meth, "deprecated");
if (!nametok || nametok->type != JSMN_STRING) {
return tal_fmt(plugin,
"rpcmethod does not have a string \"name\": %.*s",
meth->end - meth->start, buffer + meth->start);
}
if (!desctok || desctok->type != JSMN_STRING) {
return tal_fmt(plugin,
"rpcmethod does not have a string "
"\"description\": %.*s",
meth->end - meth->start, buffer + meth->start);
}
if (longdesctok && longdesctok->type != JSMN_STRING) {
return tal_fmt(plugin,
"\"long_description\" is not a string: %.*s",
meth->end - meth->start, buffer + meth->start);
}
if (usagetok && usagetok->type != JSMN_STRING) {
return tal_fmt(plugin,
"\"usage\" is not a string: %.*s",
meth->end - meth->start, buffer + meth->start);
}
cmd = notleak(tal(plugin, struct json_command));
cmd->name = json_strdup(cmd, buffer, nametok);
if (categorytok)
cmd->category = json_strdup(cmd, buffer, categorytok);
else
cmd->category = "plugin";
cmd->description = json_strdup(cmd, buffer, desctok);
if (longdesctok)
cmd->verbose = json_strdup(cmd, buffer, longdesctok);
else
cmd->verbose = cmd->description;
if (usagetok)
usage = json_strdup(tmpctx, buffer, usagetok);
else if (!deprecated_apis) {
return tal_fmt(plugin,
"\"usage\" not provided by plugin");
} else
usage = "[params]";
if (deptok) {
if (!json_to_bool(buffer, deptok, &cmd->deprecated))
return tal_fmt(plugin,
"%s: invalid \"deprecated\" field %.*s",
cmd->name,
deptok->end - deptok->start,
buffer + deptok->start);
} else
cmd->deprecated = false;
cmd->dispatch = plugin_rpcmethod_dispatch;
if (!jsonrpc_command_add(plugin->plugins->ld->jsonrpc, cmd, usage)) {
return tal_fmt(plugin,
"Could not register method \"%s\", a method with "
"that name is already registered",
cmd->name);
}
tal_arr_expand(&plugin->methods, cmd->name);
return NULL;
}
static const char *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 tal_fmt(plugin, "\"result.rpcmethods\" missing");
if (methods->type != JSMN_ARRAY) {
return tal_fmt(plugin,
"\"result.rpcmethods\" is not an array");
}
for (size_t i = 0; i < methods->size; i++) {
const char *err;
err = plugin_rpcmethod_add(plugin, buffer,
json_get_arr(methods, i));
if (err)
return err;
}
return NULL;
}
static const char *plugin_subscriptions_add(struct plugin *plugin,
const char *buffer,
const jsmntok_t *resulttok)
{
const jsmntok_t *subscriptions =
json_get_member(buffer, resulttok, "subscriptions");
if (!subscriptions) {
plugin->subscriptions = NULL;
return NULL;
}
plugin->subscriptions = tal_arr(plugin, char *, 0);
if (subscriptions->type != JSMN_ARRAY) {
return tal_fmt(plugin, "\"result.subscriptions\" is not an array");
}
for (int i = 0; i < subscriptions->size; i++) {
char *topic;
const jsmntok_t *s = json_get_arr(subscriptions, i);
if (s->type != JSMN_STRING) {
return tal_fmt(plugin,
"result.subscriptions[%d] is not a string: '%.*s'", i,
json_tok_full_len(s),
json_tok_full(buffer, s));
}
topic = json_strdup(plugin, plugin->buffer, s);
if (!notifications_have_topic(topic)) {
return tal_fmt(
plugin,
"topic '%s' is not a known notification topic", topic);
}
tal_arr_expand(&plugin->subscriptions, topic);
}
return NULL;
}
static const char *plugin_hooks_add(struct plugin *plugin, const char *buffer,
const jsmntok_t *resulttok)
{
const jsmntok_t *t, *hookstok, *beforetok, *aftertok;
size_t i;
hookstok = json_get_member(buffer, resulttok, "hooks");
if (!hookstok)
return NULL;
json_for_each_arr(i, t, hookstok) {
char *name;
struct plugin_hook *hook;
if (t->type == JSMN_OBJECT) {
const jsmntok_t *nametok;
nametok = json_get_member(buffer, t, "name");
if (!nametok)
return tal_fmt(plugin, "no name in hook obj %.*s",
json_tok_full_len(t),
json_tok_full(buffer, t));
name = json_strdup(tmpctx, buffer, nametok);
beforetok = json_get_member(buffer, t, "before");
aftertok = json_get_member(buffer, t, "after");
} else {
/* FIXME: deprecate in 3 releases after v0.9.2! */
name = json_strdup(tmpctx, plugin->buffer, t);
beforetok = aftertok = NULL;
}
hook = plugin_hook_register(plugin, name);
if (!hook) {
return tal_fmt(plugin,
"could not register hook '%s', either the "
"name doesn't exist or another plugin "
"already registered it.",
name);
}
plugin_hook_add_deps(hook, plugin, buffer, beforetok, aftertok);
tal_free(name);
}
return NULL;
}
static struct plugin_opt *plugin_opt_find(struct plugin *plugin,
const char *name, size_t namelen)
{
struct plugin_opt *opt;
list_for_each(&plugin->plugin_opts, opt, list) {
/* Trim the `--` that we added before */
if (memeqstr(name, namelen, opt->name + 2))
return opt;
}
return NULL;
}
/* start command might have included plugin-specific parameters */
static const char *plugin_add_params(struct plugin *plugin)
{
size_t i;
const jsmntok_t *t;
if (!plugin->params)
return NULL;
json_for_each_obj(i, t, plugin->params) {
struct plugin_opt *popt;
char *err;
popt = plugin_opt_find(plugin,
plugin->parambuf + t->start,
t->end - t->start);
if (!popt) {
return tal_fmt(plugin, "unknown parameter %.*s",
json_tok_full_len(t),
json_tok_full(plugin->parambuf, t));
}
err = plugin_opt_set(json_strdup(tmpctx, plugin->parambuf,
t + 1), popt);
if (err)
return err;
}
return NULL;
}
static void plugin_manifest_timeout(struct plugin *plugin)
{
bool startup = plugin->plugins->startup;
if (plugin->plugin_state == AWAITING_GETMANIFEST_RESPONSE)
plugin_kill(plugin,
"failed to respond to 'getmanifest' in time, terminating.");
else
plugin_kill(plugin,
"failed to respond to 'init' in time, terminating.");
if (startup)
fatal("Can't recover from plugin failure, terminating.");
}
static const char *plugin_parse_getmanifest_response(const char *buffer,
const jsmntok_t *toks,
const jsmntok_t *idtok,
struct plugin *plugin)
{
const jsmntok_t *resulttok, *dynamictok, *featurestok, *tok;
const char *err;
resulttok = json_get_member(buffer, toks, "result");
if (!resulttok || resulttok->type != JSMN_OBJECT)
return tal_fmt(plugin, "Invalid/missing result tok in '%.*s'",
json_tok_full_len(toks),
json_tok_full(buffer, toks));
dynamictok = json_get_member(buffer, resulttok, "dynamic");
if (dynamictok && !json_to_bool(buffer, dynamictok, &plugin->dynamic)) {
return tal_fmt(plugin, "Bad 'dynamic' field ('%.*s')",
json_tok_full_len(dynamictok),
json_tok_full(buffer, dynamictok));
}
featurestok = json_get_member(buffer, resulttok, "featurebits");
if (featurestok) {
bool have_featurebits = false;
struct feature_set *fset = talz(tmpctx, struct feature_set);
BUILD_ASSERT(ARRAY_SIZE(feature_place_names)
== ARRAY_SIZE(fset->bits));
for (int i = 0; i < ARRAY_SIZE(fset->bits); i++) {
/* We don't allow setting the obs global init */
if (!feature_place_names[i])
continue;
tok = json_get_member(buffer, featurestok,
feature_place_names[i]);
if (!tok)
continue;
fset->bits[i] = json_tok_bin_from_hex(fset, buffer, tok);
have_featurebits |= tal_bytelen(fset->bits[i]) > 0;
if (!fset->bits[i]) {
return tal_fmt(
plugin,
"Featurebits returned by plugin is not a "
"valid hexadecimal string: %.*s",
tok->end - tok->start, buffer + tok->start);
}
}
if (plugin->dynamic && have_featurebits) {
return tal_fmt(plugin,
"Custom featurebits only allows for non-dynamic "
"plugins: dynamic=%d, featurebits=%.*s",
plugin->dynamic,
featurestok->end - featurestok->start,
buffer + featurestok->start);
}
if (!feature_set_or(plugin->plugins->ld->our_features, fset)) {
return tal_fmt(plugin,
"Custom featurebits already present");
}
}
err = plugin_opts_add(plugin, buffer, resulttok);
if (!err)
err = plugin_rpcmethods_add(plugin, buffer, resulttok);
if (!err)
err = plugin_subscriptions_add(plugin, buffer, resulttok);
if (!err)
err = plugin_hooks_add(plugin, buffer, resulttok);
if (!err)
err = plugin_add_params(plugin);
plugin->plugin_state = NEEDS_INIT;
return err;
}
bool plugins_any_in_state(const struct plugins *plugins, enum plugin_state state)
{
const struct plugin *p;
list_for_each(&plugins->plugins, p, list) {
if (p->plugin_state == state)
return true;
}
return false;
}
bool plugins_all_in_state(const struct plugins *plugins, enum plugin_state state)
{
const struct plugin *p;
list_for_each(&plugins->plugins, p, list) {
if (p->plugin_state != state)
return false;
}
return true;
}
/**
* Callback for the plugin_manifest request.
*/
static void plugin_manifest_cb(const char *buffer,
const jsmntok_t *toks,
const jsmntok_t *idtok,
struct plugin *plugin)
{
const char *err;
err = plugin_parse_getmanifest_response(buffer, toks, idtok, plugin);
if (err) {
plugin_kill(plugin, err);
return;
}
/* Reset timer, it'd kill us otherwise. */
plugin->timeout_timer = tal_free(plugin->timeout_timer);
if (!plugin->plugins->startup && !plugin->dynamic)
plugin_kill(plugin, "Not a dynamic plugin");
else
check_plugins_manifests(plugin->plugins);
}
/* 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);
/* Someone actually runs this on NTFS, where everything apparently is
* executable! This prevents the most obvious damage. */
if (streq(basename, "README.md"))
return tal_free(fullname);
return fullname;
}
char *add_plugin_dir(struct plugins *plugins, const char *dir, bool error_ok)
{
struct dirent *di;
DIR *d = opendir(dir);
struct plugin *p;
if (!d) {
if (!error_ok && errno == ENOENT)
return NULL;
return tal_fmt(NULL, "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(tmpctx, dir, di->d_name);
if (!fullpath)
continue;
if (plugin_blacklisted(plugins, fullpath)) {
log_info(plugins->log, "%s: disabled via disable-plugin",
fullpath);
} else {
p = plugin_register(plugins, fullpath, NULL, false,
NULL, NULL);
if (!p && !error_ok)
return tal_fmt(NULL, "Failed to register %s: %s",
fullpath, strerror(errno));
}
}
closedir(d);
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);
}
void plugins_add_default_dir(struct plugins *plugins)
{
DIR *d = opendir(plugins->default_dir);
if (d) {
struct dirent *di;
/* Add this directory itself, and recurse down once. */
add_plugin_dir(plugins, plugins->default_dir, true);
while ((di = readdir(d)) != NULL) {
if (streq(di->d_name, ".") || streq(di->d_name, ".."))
continue;
add_plugin_dir(plugins, path_join(tmpctx, plugins->default_dir,
di->d_name), true);
}
closedir(d);
}
}
static void plugin_set_timeout(struct plugin *p)
{
bool debug = false;
#if DEVELOPER
if (p->plugins->ld->dev_debug_subprocess
&& strends(p->cmd, p->plugins->ld->dev_debug_subprocess))
debug = true;
#endif
/* Don't timeout if they're running a debugger. */
if (debug)
p->timeout_timer = NULL;
else {
p->timeout_timer
= new_reltimer(p->plugins->ld->timers, p,
time_from_sec(PLUGIN_MANIFEST_TIMEOUT),
plugin_manifest_timeout, p);
}
}
const char *plugin_send_getmanifest(struct plugin *p)
{
char **cmd;
int stdinfd, stdoutfd;
struct jsonrpc_request *req;
bool debug = false;
#if DEVELOPER
if (p->plugins->ld->dev_debug_subprocess
&& strends(p->cmd, p->plugins->ld->dev_debug_subprocess))
debug = true;
#endif
cmd = tal_arrz(tmpctx, char *, 2 + debug);
cmd[0] = p->cmd;
if (debug)
cmd[1] = "--debugger";
p->pid = pipecmdarr(&stdinfd, &stdoutfd, &pipecmd_preserve, cmd);
if (p->pid == -1)
return tal_fmt(p, "opening pipe: %s", strerror(errno));
log_debug(p->plugins->log, "started(%u) %s", p->pid, p->cmd);
p->buffer = tal_arr(p, char, 64);
jsmn_init(&p->parser);
p->toks = toks_alloc(p);
/* Create two connections, one read-only on top of p->stdout, and one
* write-only on p->stdin */
p->stdout_conn = io_new_conn(p, stdoutfd, plugin_stdout_conn_init, p);
p->stdin_conn = io_new_conn(p, stdinfd, plugin_stdin_conn_init, p);
req = jsonrpc_request_start(p, "getmanifest", p->log,
NULL, plugin_manifest_cb, p);
/* Adding allow-deprecated-apis is part of the deprecation cycle! */
if (!deprecated_apis)
json_add_bool(req->stream, "allow-deprecated-apis", deprecated_apis);
jsonrpc_request_end(req);
plugin_request_send(p, req);
p->plugin_state = AWAITING_GETMANIFEST_RESPONSE;
plugin_set_timeout(p);
return NULL;
}
bool plugins_send_getmanifest(struct plugins *plugins)
{
struct plugin *p, *next;
bool sent = false;
/* Spawn the plugin processes before entering the io_loop */
list_for_each_safe(&plugins->plugins, p, next, list) {
const char *err;
if (p->plugin_state != UNCONFIGURED)
continue;
err = plugin_send_getmanifest(p);
if (!err) {
sent = true;
continue;
}
if (plugins->startup)
fatal("error starting plugin '%s': %s", p->cmd, err);
plugin_kill(p, err);
}
return sent;
}
void plugins_init(struct plugins *plugins)
{
plugins->default_dir = path_join(plugins, plugins->ld->config_basedir, "plugins");
plugins_add_default_dir(plugins);
#if DEVELOPER
if (plugins->dev_builtin_plugins_unimportant) {
size_t i;
log_debug(plugins->log, "Builtin plugins now unimportant");
/* For each builtin plugin, check for a matching plugin
* and make it unimportant. */
for (i = 0; list_of_builtin_plugins[i]; ++i) {
const char *name = list_of_builtin_plugins[i];
struct plugin *p;
list_for_each(&plugins->plugins, p, list) {
if (plugin_paths_match(p->cmd, name)) {
p->important = false;
break;
}
}
}
}
#endif /* DEVELOPER */
setenv("LIGHTNINGD_PLUGIN", "1", 1);
setenv("LIGHTNINGD_VERSION", version(), 1);
if (plugins_send_getmanifest(plugins))
io_loop_with_timers(plugins->ld);
}
static void plugin_config_cb(const char *buffer,
const jsmntok_t *toks,
const jsmntok_t *idtok,
struct plugin *plugin)
{
plugin->plugin_state = INIT_COMPLETE;
plugin->timeout_timer = tal_free(plugin->timeout_timer);
if (plugin->start_cmd) {
plugin_cmd_succeeded(plugin->start_cmd, plugin);
plugin->start_cmd = NULL;
}
check_plugins_initted(plugin->plugins);
}
static void json_add_plugin_opt(struct json_stream *stream,
const char *name,
const char *type,
const struct plugin_opt_value *value)
{
if (streq(type, "flag")) {
/* We don't include 'flag' types if they're not
* flagged on */
if (value->as_bool)
json_add_bool(stream, name, value->as_bool);
} else if (streq(type, "bool")) {
json_add_bool(stream, name, value->as_bool);
} else if (streq(type, "string")) {
json_add_string(stream, name, value->as_str);
} else if (streq(type, "int")) {
json_add_s64(stream, name, value->as_int);
}
}
void
plugin_populate_init_request(struct plugin *plugin, struct jsonrpc_request *req)
{
const char *name;
struct plugin_opt *opt;
struct lightningd *ld = plugin->plugins->ld;
/* 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;
/* If no values, assign default (if any!) */
if (tal_count(opt->values) == 0) {
if (opt->def)
tal_arr_expand(&opt->values, opt->def);
else
continue;
}
if (opt->multi) {
json_array_start(req->stream, name);
for (size_t i = 0; i < tal_count(opt->values); i++)
json_add_plugin_opt(req->stream, NULL,
opt->type, opt->values[i]);
json_array_end(req->stream);
} else {
json_add_plugin_opt(req->stream, name,
opt->type, opt->values[0]);
}
}
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_netdir);
json_add_string(req->stream, "rpc-file", ld->rpc_filename);
json_add_bool(req->stream, "startup", plugin->plugins->startup);
json_add_string(req->stream, "network", chainparams->network_name);
if (ld->proxyaddr) {
json_add_address(req->stream, "proxy", ld->proxyaddr);
json_add_bool(req->stream, "torv3-enabled", ld->config.use_v3_autotor);
json_add_bool(req->stream, "use_proxy_always", ld->use_proxy_always);
}
json_object_start(req->stream, "feature_set");
for (enum feature_place fp = 0; fp < NUM_FEATURE_PLACE; fp++) {
if (feature_place_names[fp]) {
json_add_hex_talarr(req->stream,
feature_place_names[fp],
ld->our_features->bits[fp]);
}
}
json_object_end(req->stream);
json_object_end(req->stream);
}
static void
plugin_config(struct plugin *plugin)
{
struct jsonrpc_request *req;
plugin_set_timeout(plugin);
req = jsonrpc_request_start(plugin, "init", plugin->log,
NULL, plugin_config_cb, plugin);
plugin_populate_init_request(plugin, req);
jsonrpc_request_end(req);
plugin_request_send(plugin, req);
plugin->plugin_state = AWAITING_INIT_RESPONSE;
}
void plugins_config(struct plugins *plugins)
{
struct plugin *p;
list_for_each(&plugins->plugins, p, list) {
if (p->plugin_state == NEEDS_INIT)
plugin_config(p);
}
plugins->startup = false;
}
/** json_add_opt_plugins_array
*
* @brief add a named array of plugins to the given response,
* depending on whether it is important or not important.
*
* @param response - the `json_stream` to write into.
* @param name - the field name of the array.
* @param plugins - the plugins object to query.
* @param important - match the `important` setting of the
* plugins to be added.
*/
static
void json_add_opt_plugins_array(struct json_stream *response,
const char *name,
const struct plugins *plugins,
bool important)
{
struct plugin *p;
const char *plugin_name;
struct plugin_opt *opt;
const char *opt_name;
/* we output 'plugins' and their options as an array of substructures */
json_array_start(response, name);
list_for_each(&plugins->plugins, p, list) {
/* Skip if not matching. */
if (p->important != important)
continue;
json_object_start(response, NULL);
json_add_string(response, "path", p->cmd);
/* FIXME: use executables basename until plugins can define their names */
plugin_name = path_basename(NULL, p->cmd);
json_add_string(response, "name", plugin_name);
tal_free(plugin_name);
if (!list_empty(&p->plugin_opts)) {
json_object_start(response, "options");
list_for_each(&p->plugin_opts, opt, list) {
if (!deprecated_apis && opt->deprecated)
continue;
/* Trim the `--` that we added before */
opt_name = opt->name + 2;
if (opt->multi) {
json_array_start(response, opt_name);
for (size_t i = 0; i < tal_count(opt->values); i++)
json_add_plugin_opt(response,
NULL,
opt->type,
opt->values[i]);
json_array_end(response);
} else if (tal_count(opt->values)) {
json_add_plugin_opt(response,
opt_name,
opt->type,
opt->values[0]);
} else {
json_add_null(response, opt_name);
}
}
json_object_end(response);
}
json_object_end(response);
}
json_array_end(response);
}
void json_add_opt_plugins(struct json_stream *response,
const struct plugins *plugins)
{
json_add_opt_plugins_array(response, "plugins", plugins, false);
json_add_opt_plugins_array(response, "important-plugins", plugins, true);
}
void json_add_opt_disable_plugins(struct json_stream *response,
const struct plugins *plugins)
{
json_array_start(response, "disable-plugin");
for (size_t i = 0; i < tal_count(plugins->blacklist); i++)
json_add_string(response, NULL, plugins->blacklist[i]);
json_array_end(response);
}
/**
* Determine whether a plugin is subscribed to a given topic/method.
*/
static bool plugin_subscriptions_contains(struct plugin *plugin,
const char *method)
{
for (size_t i = 0; i < tal_count(plugin->subscriptions); i++)
if (streq(method, plugin->subscriptions[i]))
return true;
return false;
}
void plugins_notify(struct plugins *plugins,
const struct jsonrpc_notification *n TAKES)
{
struct plugin *p;
/* If we're shutting down, ld->plugins will be NULL */
if (plugins) {
list_for_each(&plugins->plugins, p, list) {
if (plugin_subscriptions_contains(p, n->method))
plugin_send(p, json_stream_dup(p, n->stream,
p->log));
}
}
if (taken(n))
tal_free(n);
}
static void destroy_request(struct jsonrpc_request *req,
struct plugin *plugin)
{
uintmap_del(&plugin->plugins->pending_requests, req->id);
}
void plugin_request_send(struct plugin *plugin,
struct jsonrpc_request *req TAKES)
{
/* Add to map so we can find it later when routing the response */
tal_steal(plugin, req);
uintmap_add(&plugin->plugins->pending_requests, req->id, req);
/* Add destructor in case plugin dies. */
tal_add_destructor2(req, destroy_request, plugin);
plugin_send(plugin, req->stream);
/* plugin_send steals the stream, so remove the dangling
* pointer here */
req->stream = NULL;
}
void *plugins_exclusive_loop(struct plugin **plugins)
{
void *ret;
size_t i;
bool last = false;
assert(tal_count(plugins) != 0);
for (i = 0; i < tal_count(plugins); ++i) {
io_conn_out_exclusive(plugins[i]->stdin_conn, true);
io_conn_exclusive(plugins[i]->stdout_conn, true);
}
/* We don't service timers here, either! */
ret = io_loop(NULL, NULL);
for (i = 0; i < tal_count(plugins); ++i) {
io_conn_out_exclusive(plugins[i]->stdin_conn, false);
last = io_conn_exclusive(plugins[i]->stdout_conn, false);
}
if (last)
fatal("Still io_exclusive after removing plugin %s?",
plugins[tal_count(plugins) - 1]->cmd);
return ret;
}
struct log *plugin_get_log(struct plugin *plugin)
{
return plugin->log;
}
void plugins_set_builtin_plugins_dir(struct plugins *plugins,
const char *dir)
{
/*~ Load the builtin plugins as important. */
for (size_t i = 0; list_of_builtin_plugins[i]; ++i)
plugin_register(plugins,
take(path_join(NULL, dir,
list_of_builtin_plugins[i])),
NULL,
/* important = */ true,
NULL, NULL);
}
struct plugin_destroyed {
const struct plugin *plugin;
};
static void mark_plugin_destroyed(const struct plugin *unused,
struct plugin_destroyed *pd)
{
pd->plugin = NULL;
}
struct plugin_destroyed *plugin_detect_destruction(const struct plugin *plugin)
{
struct plugin_destroyed *pd = tal(NULL, struct plugin_destroyed);
pd->plugin = plugin;
tal_add_destructor2(plugin, mark_plugin_destroyed, pd);
return pd;
}
bool was_plugin_destroyed(struct plugin_destroyed *pd)
{
if (pd->plugin) {
tal_del_destructor2(pd->plugin, mark_plugin_destroyed, pd);
tal_free(pd);
return false;
}
tal_free(pd);
return true;
}