|
|
|
|
@@ -3,7 +3,6 @@
|
|
|
|
|
#include <ccan/intmap/intmap.h>
|
|
|
|
|
#include <ccan/io/io.h>
|
|
|
|
|
#include <ccan/json_out/json_out.h>
|
|
|
|
|
#include <ccan/membuf/membuf.h>
|
|
|
|
|
#include <ccan/read_write_all/read_write_all.h>
|
|
|
|
|
#include <ccan/strmap/strmap.h>
|
|
|
|
|
#include <ccan/tal/str/str.h>
|
|
|
|
|
@@ -39,56 +38,11 @@ bool deprecated_apis;
|
|
|
|
|
|
|
|
|
|
extern const struct chainparams *chainparams;
|
|
|
|
|
|
|
|
|
|
struct plugin {
|
|
|
|
|
/* lightningd interaction */
|
|
|
|
|
struct io_conn *stdin_conn;
|
|
|
|
|
struct io_conn *stdout_conn;
|
|
|
|
|
|
|
|
|
|
/* To read from lightningd */
|
|
|
|
|
char *buffer;
|
|
|
|
|
size_t used, len_read;
|
|
|
|
|
|
|
|
|
|
/* To write to lightningd */
|
|
|
|
|
struct json_stream **js_arr;
|
|
|
|
|
|
|
|
|
|
enum plugin_restartability restartability;
|
|
|
|
|
const struct plugin_command *commands;
|
|
|
|
|
size_t num_commands;
|
|
|
|
|
const struct plugin_notification *notif_subs;
|
|
|
|
|
size_t num_notif_subs;
|
|
|
|
|
const struct plugin_hook *hook_subs;
|
|
|
|
|
size_t num_hook_subs;
|
|
|
|
|
struct plugin_option *opts;
|
|
|
|
|
|
|
|
|
|
/* Anything special to do at init ? */
|
|
|
|
|
void (*init)(struct plugin_conn *,
|
|
|
|
|
const char *buf, const jsmntok_t *);
|
|
|
|
|
/* Has the manifest been sent already ? */
|
|
|
|
|
bool manifested;
|
|
|
|
|
/* Has init been received ? */
|
|
|
|
|
bool initialized;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct plugin_timer {
|
|
|
|
|
struct timer timer;
|
|
|
|
|
struct command_result *(*cb)(void);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct plugin_conn {
|
|
|
|
|
int fd;
|
|
|
|
|
MEMBUF(char) mb;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* Connection to make RPC requests. */
|
|
|
|
|
static struct plugin_conn rpc_conn;
|
|
|
|
|
|
|
|
|
|
struct command {
|
|
|
|
|
u64 *id;
|
|
|
|
|
const char *methodname;
|
|
|
|
|
bool usage_only;
|
|
|
|
|
struct plugin *plugin;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct out_req {
|
|
|
|
|
/* The unique id of this request. */
|
|
|
|
|
u64 id;
|
|
|
|
|
@@ -109,7 +63,7 @@ struct out_req {
|
|
|
|
|
|
|
|
|
|
/* command_result is mainly used as a compile-time check to encourage you
|
|
|
|
|
* to return as soon as you get one (and not risk use-after-free of command).
|
|
|
|
|
* Here we use two values: complete (cmd freed) an pending (still going) */
|
|
|
|
|
* Here we use two values: complete (cmd freed) and pending (still going) */
|
|
|
|
|
struct command_result {
|
|
|
|
|
char c;
|
|
|
|
|
};
|
|
|
|
|
@@ -177,30 +131,30 @@ static void *membuf_tal_realloc(struct membuf *mb, void *rawelems,
|
|
|
|
|
return p;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int read_json(struct plugin_conn *conn)
|
|
|
|
|
static int read_json_from_rpc(struct plugin *p)
|
|
|
|
|
{
|
|
|
|
|
char *end;
|
|
|
|
|
|
|
|
|
|
/* We rely on the double-\n marker which only terminates JSON top
|
|
|
|
|
* levels. Thanks lightningd! */
|
|
|
|
|
while ((end = memmem(membuf_elems(&conn->mb),
|
|
|
|
|
membuf_num_elems(&conn->mb), "\n\n", 2))
|
|
|
|
|
while ((end = memmem(membuf_elems(&p->rpc_conn.mb),
|
|
|
|
|
membuf_num_elems(&p->rpc_conn.mb), "\n\n", 2))
|
|
|
|
|
== NULL) {
|
|
|
|
|
ssize_t r;
|
|
|
|
|
|
|
|
|
|
/* Make sure we've room for at least READ_CHUNKSIZE. */
|
|
|
|
|
membuf_prepare_space(&conn->mb, READ_CHUNKSIZE);
|
|
|
|
|
r = read(conn->fd, membuf_space(&conn->mb),
|
|
|
|
|
membuf_num_space(&conn->mb));
|
|
|
|
|
membuf_prepare_space(&p->rpc_conn.mb, READ_CHUNKSIZE);
|
|
|
|
|
r = read(p->rpc_conn.fd, membuf_space(&p->rpc_conn.mb),
|
|
|
|
|
membuf_num_space(&p->rpc_conn.mb));
|
|
|
|
|
/* lightningd goes away, we go away. */
|
|
|
|
|
if (r == 0)
|
|
|
|
|
exit(0);
|
|
|
|
|
if (r < 0)
|
|
|
|
|
plugin_err("Reading JSON input: %s", strerror(errno));
|
|
|
|
|
membuf_added(&conn->mb, r);
|
|
|
|
|
membuf_added(&p->rpc_conn.mb, r);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return end + 2 - membuf_elems(&conn->mb);
|
|
|
|
|
return end + 2 - membuf_elems(&p->rpc_conn.mb);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* This starts a JSON RPC message with boilerplate */
|
|
|
|
|
@@ -355,7 +309,7 @@ void command_set_usage(struct command *cmd, const char *usage TAKES)
|
|
|
|
|
/* Reads rpc reply and returns tokens, setting contents to 'error' or
|
|
|
|
|
* 'result' (depending on *error). */
|
|
|
|
|
static const jsmntok_t *read_rpc_reply(const tal_t *ctx,
|
|
|
|
|
struct plugin_conn *rpc,
|
|
|
|
|
struct plugin *plugin,
|
|
|
|
|
const jsmntok_t **contents,
|
|
|
|
|
bool *error,
|
|
|
|
|
int *reqlen)
|
|
|
|
|
@@ -363,22 +317,22 @@ static const jsmntok_t *read_rpc_reply(const tal_t *ctx,
|
|
|
|
|
const jsmntok_t *toks;
|
|
|
|
|
bool valid;
|
|
|
|
|
|
|
|
|
|
*reqlen = read_json(rpc);
|
|
|
|
|
*reqlen = read_json_from_rpc(plugin);
|
|
|
|
|
|
|
|
|
|
toks = json_parse_input(ctx, membuf_elems(&rpc->mb), *reqlen, &valid);
|
|
|
|
|
toks = json_parse_input(ctx, membuf_elems(&plugin->rpc_conn.mb), *reqlen, &valid);
|
|
|
|
|
if (!valid)
|
|
|
|
|
plugin_err("Malformed JSON reply '%.*s'",
|
|
|
|
|
*reqlen, membuf_elems(&rpc->mb));
|
|
|
|
|
*reqlen, membuf_elems(&plugin->rpc_conn.mb));
|
|
|
|
|
|
|
|
|
|
*contents = json_get_member(membuf_elems(&rpc->mb), toks, "error");
|
|
|
|
|
*contents = json_get_member(membuf_elems(&plugin->rpc_conn.mb), toks, "error");
|
|
|
|
|
if (*contents)
|
|
|
|
|
*error = true;
|
|
|
|
|
else {
|
|
|
|
|
*contents = json_get_member(membuf_elems(&rpc->mb), toks,
|
|
|
|
|
*contents = json_get_member(membuf_elems(&plugin->rpc_conn.mb), toks,
|
|
|
|
|
"result");
|
|
|
|
|
if (!*contents)
|
|
|
|
|
plugin_err("JSON reply with no 'result' nor 'error'? '%.*s'",
|
|
|
|
|
*reqlen, membuf_elems(&rpc->mb));
|
|
|
|
|
*reqlen, membuf_elems(&plugin->rpc_conn.mb));
|
|
|
|
|
*error = false;
|
|
|
|
|
}
|
|
|
|
|
return toks;
|
|
|
|
|
@@ -402,9 +356,10 @@ static struct json_out *start_json_request(const tal_t *ctx,
|
|
|
|
|
|
|
|
|
|
/* Synchronous routine to send command and extract single field from response */
|
|
|
|
|
const char *rpc_delve(const tal_t *ctx,
|
|
|
|
|
struct plugin *plugin,
|
|
|
|
|
const char *method,
|
|
|
|
|
const struct json_out *params TAKES,
|
|
|
|
|
struct plugin_conn *rpc, const char *guide)
|
|
|
|
|
const char *guide)
|
|
|
|
|
{
|
|
|
|
|
bool error;
|
|
|
|
|
const jsmntok_t *contents, *t;
|
|
|
|
|
@@ -413,24 +368,24 @@ const char *rpc_delve(const tal_t *ctx,
|
|
|
|
|
struct json_out *jout;
|
|
|
|
|
|
|
|
|
|
jout = start_json_request(tmpctx, 0, method, params);
|
|
|
|
|
finish_and_send_json(rpc->fd, jout);
|
|
|
|
|
finish_and_send_json(plugin->rpc_conn.fd, jout);
|
|
|
|
|
|
|
|
|
|
read_rpc_reply(tmpctx, rpc, &contents, &error, &reqlen);
|
|
|
|
|
read_rpc_reply(tmpctx, plugin, &contents, &error, &reqlen);
|
|
|
|
|
if (error)
|
|
|
|
|
plugin_err("Got error reply to %s: '%.*s'",
|
|
|
|
|
method, reqlen, membuf_elems(&rpc->mb));
|
|
|
|
|
method, reqlen, membuf_elems(&plugin->rpc_conn.mb));
|
|
|
|
|
|
|
|
|
|
t = json_delve(membuf_elems(&rpc->mb), contents, guide);
|
|
|
|
|
t = json_delve(membuf_elems(&plugin->rpc_conn.mb), contents, guide);
|
|
|
|
|
if (!t)
|
|
|
|
|
plugin_err("Could not find %s in reply to %s: '%.*s'",
|
|
|
|
|
guide, method, reqlen, membuf_elems(&rpc->mb));
|
|
|
|
|
guide, method, reqlen, membuf_elems(&plugin->rpc_conn.mb));
|
|
|
|
|
|
|
|
|
|
ret = json_strdup(ctx, membuf_elems(&rpc->mb), t);
|
|
|
|
|
membuf_consume(&rpc->mb, reqlen);
|
|
|
|
|
ret = json_strdup(ctx, membuf_elems(&plugin->rpc_conn.mb), t);
|
|
|
|
|
membuf_consume(&plugin->rpc_conn.mb, reqlen);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void handle_rpc_reply(struct plugin_conn *rpc)
|
|
|
|
|
static void handle_rpc_reply(struct plugin *plugin)
|
|
|
|
|
{
|
|
|
|
|
int reqlen;
|
|
|
|
|
const jsmntok_t *toks, *contents, *t;
|
|
|
|
|
@@ -439,33 +394,33 @@ static void handle_rpc_reply(struct plugin_conn *rpc)
|
|
|
|
|
u64 id;
|
|
|
|
|
bool error;
|
|
|
|
|
|
|
|
|
|
toks = read_rpc_reply(tmpctx, rpc, &contents, &error, &reqlen);
|
|
|
|
|
toks = read_rpc_reply(tmpctx, plugin, &contents, &error, &reqlen);
|
|
|
|
|
|
|
|
|
|
t = json_get_member(membuf_elems(&rpc->mb), toks, "id");
|
|
|
|
|
t = json_get_member(membuf_elems(&plugin->rpc_conn.mb), toks, "id");
|
|
|
|
|
if (!t)
|
|
|
|
|
plugin_err("JSON reply without id '%.*s'",
|
|
|
|
|
reqlen, membuf_elems(&rpc->mb));
|
|
|
|
|
if (!json_to_u64(membuf_elems(&rpc->mb), t, &id))
|
|
|
|
|
reqlen, membuf_elems(&plugin->rpc_conn.mb));
|
|
|
|
|
if (!json_to_u64(membuf_elems(&plugin->rpc_conn.mb), t, &id))
|
|
|
|
|
plugin_err("JSON reply without numeric id '%.*s'",
|
|
|
|
|
reqlen, membuf_elems(&rpc->mb));
|
|
|
|
|
reqlen, membuf_elems(&plugin->rpc_conn.mb));
|
|
|
|
|
out = uintmap_get(&out_reqs, id);
|
|
|
|
|
if (!out)
|
|
|
|
|
plugin_err("JSON reply with unknown id '%.*s' (%"PRIu64")",
|
|
|
|
|
reqlen, membuf_elems(&rpc->mb), id);
|
|
|
|
|
reqlen, membuf_elems(&plugin->rpc_conn.mb), id);
|
|
|
|
|
|
|
|
|
|
/* We want to free this if callback doesn't. */
|
|
|
|
|
tal_steal(tmpctx, out);
|
|
|
|
|
uintmap_del(&out_reqs, out->id);
|
|
|
|
|
|
|
|
|
|
if (error)
|
|
|
|
|
res = out->errcb(out->cmd, membuf_elems(&rpc->mb), contents,
|
|
|
|
|
out->arg);
|
|
|
|
|
res = out->errcb(out->cmd, membuf_elems(&plugin->rpc_conn.mb),
|
|
|
|
|
contents, out->arg);
|
|
|
|
|
else
|
|
|
|
|
res = out->cb(out->cmd, membuf_elems(&rpc->mb), contents,
|
|
|
|
|
out->arg);
|
|
|
|
|
res = out->cb(out->cmd, membuf_elems(&plugin->rpc_conn.mb),
|
|
|
|
|
contents, out->arg);
|
|
|
|
|
|
|
|
|
|
assert(res == &pending || res == &complete);
|
|
|
|
|
membuf_consume(&rpc->mb, reqlen);
|
|
|
|
|
membuf_consume(&plugin->rpc_conn.mb, reqlen);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct command_result *
|
|
|
|
|
@@ -494,7 +449,7 @@ send_outreq_(struct command *cmd,
|
|
|
|
|
uintmap_add(&out_reqs, out->id, out);
|
|
|
|
|
|
|
|
|
|
jout = start_json_request(tmpctx, out->id, method, params);
|
|
|
|
|
finish_and_send_json(rpc_conn.fd, jout);
|
|
|
|
|
finish_and_send_json(cmd->plugin->rpc_conn.fd, jout);
|
|
|
|
|
|
|
|
|
|
return &pending;
|
|
|
|
|
}
|
|
|
|
|
@@ -572,7 +527,7 @@ static struct command_result *handle_init(struct command *cmd,
|
|
|
|
|
chainparams = chainparams_for_network(network);
|
|
|
|
|
|
|
|
|
|
rpctok = json_delve(buf, configtok, ".rpc-file");
|
|
|
|
|
rpc_conn.fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
|
|
|
p->rpc_conn.fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
|
|
|
if (rpctok->end - rpctok->start + 1 > sizeof(addr.sun_path))
|
|
|
|
|
plugin_err("rpc filename '%.*s' too long",
|
|
|
|
|
rpctok->end - rpctok->start,
|
|
|
|
|
@@ -581,15 +536,14 @@ static struct command_result *handle_init(struct command *cmd,
|
|
|
|
|
addr.sun_path[rpctok->end - rpctok->start] = '\0';
|
|
|
|
|
addr.sun_family = AF_UNIX;
|
|
|
|
|
|
|
|
|
|
if (connect(rpc_conn.fd, (struct sockaddr *)&addr, sizeof(addr)) != 0)
|
|
|
|
|
if (connect(p->rpc_conn.fd, (struct sockaddr *)&addr, sizeof(addr)) != 0)
|
|
|
|
|
plugin_err("Connecting to '%.*s': %s",
|
|
|
|
|
rpctok->end - rpctok->start, buf + rpctok->start,
|
|
|
|
|
strerror(errno));
|
|
|
|
|
|
|
|
|
|
param_obj = json_out_obj(NULL, "config", "allow-deprecated-apis");
|
|
|
|
|
deprecated_apis = streq(rpc_delve(tmpctx, "listconfigs",
|
|
|
|
|
deprecated_apis = streq(rpc_delve(tmpctx, p, "listconfigs",
|
|
|
|
|
take(param_obj),
|
|
|
|
|
&rpc_conn,
|
|
|
|
|
".allow-deprecated-apis"),
|
|
|
|
|
"true");
|
|
|
|
|
opttok = json_get_member(buf, params, "options");
|
|
|
|
|
@@ -610,7 +564,7 @@ static struct command_result *handle_init(struct command *cmd,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (p->init)
|
|
|
|
|
p->init(&rpc_conn, buf, configtok);
|
|
|
|
|
p->init(p, buf, configtok);
|
|
|
|
|
|
|
|
|
|
return command_success_str(cmd, NULL);
|
|
|
|
|
}
|
|
|
|
|
@@ -652,7 +606,7 @@ static void setup_command_usage(const struct plugin_command *commands,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void call_plugin_timer(struct plugin_conn *rpc, struct timer *timer)
|
|
|
|
|
static void call_plugin_timer(struct rpc_conn *rpc, struct timer *timer)
|
|
|
|
|
{
|
|
|
|
|
struct plugin_timer *t = container_of(timer, struct plugin_timer, timer);
|
|
|
|
|
|
|
|
|
|
@@ -667,7 +621,7 @@ static void destroy_plugin_timer(struct plugin_timer *timer)
|
|
|
|
|
timer_del(&timers, &timer->timer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct plugin_timer *plugin_timer(struct plugin_conn *rpc, struct timerel t,
|
|
|
|
|
struct plugin_timer *plugin_timer(struct rpc_conn *rpc, struct timerel t,
|
|
|
|
|
struct command_result *(*cb)(void))
|
|
|
|
|
{
|
|
|
|
|
struct plugin_timer *timer = tal(NULL, struct plugin_timer);
|
|
|
|
|
@@ -927,7 +881,7 @@ static struct io_plan *stdout_conn_init(struct io_conn *conn,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct plugin *new_plugin(const tal_t *ctx,
|
|
|
|
|
void (*init)(struct plugin_conn *rpc,
|
|
|
|
|
void (*init)(struct plugin *p,
|
|
|
|
|
const char *buf, const jsmntok_t *),
|
|
|
|
|
const enum plugin_restartability restartability,
|
|
|
|
|
const struct plugin_command *commands,
|
|
|
|
|
@@ -945,6 +899,10 @@ static struct plugin *new_plugin(const tal_t *ctx,
|
|
|
|
|
p->js_arr = tal_arr(p, struct json_stream *, 0);
|
|
|
|
|
p->used = 0;
|
|
|
|
|
p->len_read = 0;
|
|
|
|
|
/* rpc. TODO: use ccan/io also for RPC */
|
|
|
|
|
membuf_init(&p->rpc_conn.mb,
|
|
|
|
|
tal_arr(p, char, READ_CHUNKSIZE), READ_CHUNKSIZE,
|
|
|
|
|
membuf_tal_realloc);
|
|
|
|
|
|
|
|
|
|
p->init = init;
|
|
|
|
|
p->manifested = p->initialized = false;
|
|
|
|
|
@@ -972,7 +930,7 @@ static struct plugin *new_plugin(const tal_t *ctx,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void plugin_main(char *argv[],
|
|
|
|
|
void (*init)(struct plugin_conn *rpc,
|
|
|
|
|
void (*init)(struct plugin *p,
|
|
|
|
|
const char *buf, const jsmntok_t *),
|
|
|
|
|
const enum plugin_restartability restartability,
|
|
|
|
|
const struct plugin_command *commands,
|
|
|
|
|
@@ -1000,12 +958,9 @@ void plugin_main(char *argv[],
|
|
|
|
|
notif_subs, num_notif_subs, hook_subs,
|
|
|
|
|
num_hook_subs, ap);
|
|
|
|
|
va_end(ap);
|
|
|
|
|
uintmap_init(&out_reqs);
|
|
|
|
|
|
|
|
|
|
timers_init(&timers, time_mono());
|
|
|
|
|
membuf_init(&rpc_conn.mb,
|
|
|
|
|
tal_arr(plugin, char, READ_CHUNKSIZE), READ_CHUNKSIZE,
|
|
|
|
|
membuf_tal_realloc);
|
|
|
|
|
uintmap_init(&out_reqs);
|
|
|
|
|
|
|
|
|
|
io_new_conn(plugin, STDIN_FILENO, stdin_conn_init, plugin);
|
|
|
|
|
io_new_conn(plugin, STDOUT_FILENO, stdout_conn_init, plugin);
|
|
|
|
|
@@ -1015,14 +970,14 @@ void plugin_main(char *argv[],
|
|
|
|
|
|
|
|
|
|
clean_tmpctx();
|
|
|
|
|
|
|
|
|
|
if (membuf_num_elems(&rpc_conn.mb) != 0) {
|
|
|
|
|
handle_rpc_reply(&rpc_conn);
|
|
|
|
|
if (membuf_num_elems(&plugin->rpc_conn.mb) != 0) {
|
|
|
|
|
handle_rpc_reply(plugin);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Will only exit if a timer has expired. */
|
|
|
|
|
io_loop(&timers, &expired);
|
|
|
|
|
call_plugin_timer(&rpc_conn, expired);
|
|
|
|
|
call_plugin_timer(&plugin->rpc_conn, expired);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tal_free(plugin);
|
|
|
|
|
|