diff --git a/plugins/Makefile b/plugins/Makefile index 398226f99..a0b2d5efe 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -63,6 +63,9 @@ PLUGIN_FUNDER_HEADER := \ plugins/funder_policy.h PLUGIN_FUNDER_OBJS := $(PLUGIN_FUNDER_SRC:.c=.o) +PLUGIN_RECKLESSRPC_SRC := plugins/recklessrpc.c +PLUGIN_RECKLESSRPC_OBJS := $(PLUGIN_RECKLESSRPC_SRC:.c=.o) + PLUGIN_ALL_SRC := \ $(PLUGIN_AUTOCLEAN_SRC) \ $(PLUGIN_chanbackup_SRC) \ @@ -77,7 +80,8 @@ PLUGIN_ALL_SRC := \ $(PLUGIN_PAY_LIB_SRC) \ $(PLUGIN_PAY_SRC) \ $(PLUGIN_SPENDER_SRC) \ - $(PLUGIN_RECOVER_SRC) + $(PLUGIN_RECOVER_SRC) \ + $(PLUGIN_RECKLESSRPC_SRC) PLUGIN_ALL_HEADER := \ $(PLUGIN_PAY_HEADER) \ @@ -97,6 +101,7 @@ C_PLUGINS := \ plugins/keysend \ plugins/offers \ plugins/pay \ + plugins/recklessrpc \ plugins/recover \ plugins/txprepare \ plugins/cln-renepay \ @@ -216,6 +221,8 @@ plugins/funder: bitcoin/psbt.o common/psbt_open.o $(PLUGIN_FUNDER_OBJS) $(PLUGIN plugins/recover: common/gossmap.o common/sciddir_or_pubkey.o common/fp16.o $(PLUGIN_RECOVER_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) +plugins/recklessrpc: $(PLUGIN_RECKLESSRPC_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) + # This covers all the low-level list RPCs which return simple arrays SQL_LISTRPCS := listchannels listforwards listhtlcs listinvoices listnodes listoffers listpeers listpeerchannels listclosedchannels listtransactions listsendpays bkpr-listaccountevents bkpr-listincome SQL_LISTRPCS_SCHEMAS := $(foreach l,$(SQL_LISTRPCS),doc/schemas/lightning-$l.json) diff --git a/plugins/recklessrpc.c b/plugins/recklessrpc.c new file mode 100644 index 000000000..416c7230c --- /dev/null +++ b/plugins/recklessrpc.c @@ -0,0 +1,190 @@ +/* This plugin provides RPC access to the reckless standalone utility. + */ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct plugin *plugin; + +struct reckless { + struct command *cmd; + int stdinfd; + int stdoutfd; + int stderrfd; + char *stdoutbuf; + char *stderrbuf; + size_t stdout_read; /* running total */ + size_t stdout_new; /* new since last read */ + pid_t pid; +}; + +static struct io_plan *read_more(struct io_conn *conn, struct reckless *rkls) +{ + rkls->stdout_read += rkls->stdout_new; + if (rkls->stdout_read == tal_count(rkls->stdoutbuf)) + tal_resize(&rkls->stdoutbuf, rkls->stdout_read * 2); + return io_read_partial(conn, rkls->stdoutbuf + rkls->stdout_read, + tal_count(rkls->stdoutbuf) - rkls->stdout_read, + &rkls->stdout_new, read_more, rkls); +} + +static struct command_result *reckless_result(struct io_conn *conn, + struct reckless *reckless) +{ + struct json_stream *response; + response = jsonrpc_stream_success(reckless->cmd); + json_array_start(response, "result"); + const jsmntok_t *results, *result, *logs, *log; + size_t i; + jsmn_parser parser; + jsmntok_t *toks; + toks = tal_arr(reckless, jsmntok_t, 500); + jsmn_init(&parser); + if (jsmn_parse(&parser, reckless->stdoutbuf, + strlen(reckless->stdoutbuf), toks, tal_count(toks)) <= 0) { + plugin_log(plugin, LOG_DBG, "need more json tokens"); + assert(false); + } + + results = json_get_member(reckless->stdoutbuf, toks, "result"); + json_for_each_arr(i, result, results) { + json_add_string(response, + NULL, + json_strdup(reckless, reckless->stdoutbuf, + result)); + } + json_array_end(response); + json_array_start(response, "log"); + logs = json_get_member(reckless->stdoutbuf, toks, "log"); + json_for_each_arr(i, log, logs) { + json_add_string(response, + NULL, + json_strdup(reckless, reckless->stdoutbuf, + log)); + } + json_array_end(response); + + return command_finished(reckless->cmd, response); +} + +static void reckless_conn_finish(struct io_conn *conn, + struct reckless *reckless) +{ + /* FIXME: avoid EBADFD - leave stdin fd open? */ + if (errno && errno != 9) + plugin_log(plugin, LOG_DBG, "err: %s", strerror(errno)); + reckless_result(conn, reckless); + if (reckless->pid > 0) { + int status; + pid_t p; + p = waitpid(reckless->pid, &status, WNOHANG); + /* Did the reckless process exit? */ + if (p != reckless->pid && reckless->pid) { + plugin_log(plugin, LOG_DBG, "reckless failed to exit " + "(%i), killing now.", status); + kill(reckless->pid, SIGKILL); + } + } + plugin_log(plugin, LOG_DBG, "Reckless subprocess complete."); + plugin_log(plugin, LOG_DBG, "output: %s", reckless->stdoutbuf); + io_close(conn); + tal_free(reckless); +} + +static struct io_plan *conn_init(struct io_conn *conn, struct reckless *rkls) +{ + io_set_finish(conn, reckless_conn_finish, rkls); + return read_more(conn, rkls); +} + +static struct command_result *reckless_call(struct command *cmd, + const char *call) +{ + if (!call) + return command_fail(cmd, PLUGIN_ERROR, "invalid reckless call"); + char **my_call; + my_call = tal_arrz(tmpctx, char *, 0); + tal_arr_expand(&my_call, "reckless"); + tal_arr_expand(&my_call, "-v"); + tal_arr_expand(&my_call, "--json"); + tal_arr_expand(&my_call, "search"); + tal_arr_expand(&my_call, (char *) call); + tal_arr_expand(&my_call, NULL); + struct reckless *reckless; + reckless = tal(NULL, struct reckless); + reckless->cmd = cmd; + reckless->stdoutbuf = tal_arrz(reckless, char, 1024); + reckless->stderrbuf = tal_arrz(reckless, char, 1024); + reckless->stdout_read = 0; + reckless->stdout_new = 0; + char * full_cmd; + full_cmd = tal_fmt(tmpctx, "calling:"); + for (int i=0; ipid = pipecmdarr(&reckless->stdinfd, &reckless->stdoutfd, + &reckless->stderrfd, my_call); + + /* FIXME: fail if invalid pid*/ + io_new_conn(reckless, reckless->stdoutfd, conn_init, reckless); + tal_free(my_call); + return command_still_pending(cmd); +} + +static struct command_result *json_search(struct command *cmd, + const char *buf, + const jsmntok_t *params) +{ + const char *search_target; + /* Allow check command to evaluate. */ + if (!param(cmd, buf, params, + p_req("plugin", param_string, &search_target), + NULL)) + return command_param_failed(); + return reckless_call(cmd, search_target); +} + +static const char *init(struct plugin *p, + const char *buf UNUSED, + const jsmntok_t *config UNUSED) +{ + plugin = p; + plugin_log(p, LOG_DBG, "plugin initialized!"); + /* FIXME: TODO: scan for reckless config info */ + /* FIXME: TODO: assume default reckless config using ld config dets */ + return NULL; +} + +static const struct plugin_command commands[] = { + { + "reckless-search", + json_search, + }, +}; + +int main(int argc, char **argv) +{ + setup_locale(); + + plugin_main(argv, init, NULL, PLUGIN_RESTARTABLE, true, + NULL, + commands, ARRAY_SIZE(commands), + NULL, 0, /* Notifications */ + NULL, 0, /* Hooks */ + NULL, 0, /* Notification topics */ + NULL); /* plugin options */ + + return 0; +} +