diff --git a/lightningd/jsonrpc.c b/lightningd/jsonrpc.c index 1ed9a7e80..6b36cef8b 100644 --- a/lightningd/jsonrpc.c +++ b/lightningd/jsonrpc.c @@ -77,6 +77,18 @@ struct json_connection { struct json_stream **js_arr; }; +/** + * `jsonrpc` encapsulates the entire state of the JSON-RPC interface, + * including a list of methods that the interface supports (can be + * appended dynamically, e.g., for plugins, and logs. It also serves + * as a convenient `tal`-parent for all JSON-RPC related allocations. + */ +struct jsonrpc { + struct io_listener *rpc_listener; + struct json_command **commands; + struct log *log; +}; + /* The command itself usually owns the stream, because jcon may get closed. * The command transfers ownership once it's done though. */ static struct json_stream *jcon_new_json_stream(const tal_t *ctx, @@ -292,10 +304,9 @@ static void json_add_help_command(struct command *cmd, static void json_help(struct command *cmd, const char *buffer, const jsmntok_t *params) { - unsigned int i; struct json_stream *response; - struct json_command **cmdlist = get_cmdlist(); const jsmntok_t *cmdtok; + struct json_command **commands = cmd->ld->jsonrpc->commands; if (!param(cmd, buffer, params, p_opt("command", json_tok_tok, &cmdtok), @@ -303,10 +314,10 @@ static void json_help(struct command *cmd, return; if (cmdtok) { - for (i = 0; i < num_cmdlist; i++) { - if (json_tok_streq(buffer, cmdtok, cmdlist[i]->name)) { + for (size_t i = 0; i < tal_count(commands); i++) { + if (json_tok_streq(buffer, cmdtok, commands[i]->name)) { response = json_stream_success(cmd); - json_add_help_command(cmd, response, cmdlist[i]); + json_add_help_command(cmd, response, commands[i]); goto done; } } @@ -320,8 +331,8 @@ static void json_help(struct command *cmd, response = json_stream_success(cmd); json_object_start(response, NULL); json_array_start(response, "help"); - for (i = 0; i < num_cmdlist; i++) { - json_add_help_command(cmd, response, cmdlist[i]); + for (size_t i=0; icommands; - /* cmdlist[i]->name can be NULL in test code. */ - for (i = 0; i < num_cmdlist; i++) - if (cmdlist[i]->name - && json_tok_streq(buffer, tok, cmdlist[i]->name)) - return cmdlist[i]; + /* commands[i]->name can be NULL in test code. */ + for (size_t i=0; iname && + json_tok_streq(buffer, tok, commands[i]->name)) + return commands[i]; return NULL; } @@ -516,9 +527,9 @@ static void parse_request(struct json_connection *jcon, const jsmntok_t tok[]) return; } - /* This is a convenient tal parent for duration of command - * (which may outlive the conn!). */ - c = tal(jcon->ld->rpc_listener, struct command); + /* Allocate the command off of the `jsonrpc` object and not + * the connection since the command may outlive `conn`. */ + c = tal(jcon->ld->jsonrpc, struct command); c->jcon = jcon; c->ld = jcon->ld; c->pending = false; @@ -543,8 +554,8 @@ static void parse_request(struct json_connection *jcon, const jsmntok_t tok[]) return; } - c->json_cmd = find_cmd(jcon->buffer, method); - if (!c->json_cmd) { + c->json_cmd = find_cmd(jcon->ld->jsonrpc, jcon->buffer, method); + if (!c->json_cmd) { command_fail(c, JSONRPC2_METHOD_NOT_FOUND, "Unknown command '%.*s'", method->end - method->start, @@ -713,10 +724,41 @@ static struct io_plan *incoming_jcon_connected(struct io_conn *conn, return jcon_connected(notleak(conn), ld); } -void setup_jsonrpc(struct lightningd *ld, const char *rpc_filename) +bool jsonrpc_command_add(struct jsonrpc *rpc, struct json_command *command) +{ + size_t count = tal_count(rpc->commands); + + /* Check that we don't clobber a method */ + for (size_t i = 0; i < count; i++) + if (rpc->commands[i] != NULL && + streq(rpc->commands[i]->name, command->name)) + return false; + + *tal_arr_expand(&rpc->commands) = command; + return true; +} + +static struct jsonrpc *jsonrpc_new(const tal_t *ctx, struct lightningd *ld, int fd) +{ + struct jsonrpc *jsonrpc = tal(ctx, struct jsonrpc); + struct json_command **commands = get_cmdlist(); + + jsonrpc->commands = tal_arr(jsonrpc, struct json_command *, 0); + jsonrpc->log = new_log(jsonrpc, ld->log_book, "jsonrpc"); + for (size_t i=0; irpc_listener = io_new_listener( + ld->rpc_filename, fd, incoming_jcon_connected, ld); + log_debug(jsonrpc->log, "Listening on '%s'", ld->rpc_filename); + return jsonrpc; +} + +struct jsonrpc *setup_jsonrpc(struct lightningd *ld) { struct sockaddr_un addr; int fd, old_umask; + const char *rpc_filename = ld->rpc_filename; if (streq(rpc_filename, "/dev/tty")) { fd = open(rpc_filename, O_RDWR); @@ -724,7 +766,7 @@ void setup_jsonrpc(struct lightningd *ld, const char *rpc_filename) err(1, "Opening %s", rpc_filename); /* Technically this is a leak, but there's only one */ notleak(io_new_conn(ld, fd, jcon_connected, ld)); - return; + return jsonrpc_new(ld, ld, fd); } fd = socket(AF_UNIX, SOCK_STREAM, 0); @@ -750,8 +792,7 @@ void setup_jsonrpc(struct lightningd *ld, const char *rpc_filename) if (listen(fd, 1) != 0) err(1, "Listening on '%s'", rpc_filename); - log_debug(ld->log, "Listening on '%s'", rpc_filename); - ld->rpc_listener = io_new_listener(ld->rpc_filename, fd, incoming_jcon_connected, ld); + return jsonrpc_new(ld, ld, fd); } /** diff --git a/lightningd/jsonrpc.h b/lightningd/jsonrpc.h index c51c75565..108a47625 100644 --- a/lightningd/jsonrpc.h +++ b/lightningd/jsonrpc.h @@ -93,7 +93,15 @@ void PRINTF_FMT(3, 4) command_fail(struct command *cmd, int code, void command_still_pending(struct command *cmd); /* For initialization */ -void setup_jsonrpc(struct lightningd *ld, const char *rpc_filename); +struct jsonrpc *setup_jsonrpc(struct lightningd *ld); + +/** + * Add a new command/method to the JSON-RPC interface. + * + * Returns true if the command was added correctly, false if adding + * this would clobber a command name. + */ +bool jsonrpc_command_add(struct jsonrpc *rpc, struct json_command *command); AUTODATA_TYPE(json_command, struct json_command); #endif /* LIGHTNING_LIGHTNINGD_JSONRPC_H */ diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c index 4c02f3c32..293f13fe2 100644 --- a/lightningd/lightningd.c +++ b/lightningd/lightningd.c @@ -695,7 +695,7 @@ int main(int argc, char *argv[]) /*~ Create RPC socket: now lightning-cli can send us JSON RPC commands * over a UNIX domain socket specified by `ld->rpc_filename`. */ - setup_jsonrpc(ld, ld->rpc_filename); + ld->jsonrpc = setup_jsonrpc(ld); /*~ We defer --daemon until we've completed most initialization: that * way we'll exit with an error rather than silently exiting 0, then @@ -778,7 +778,7 @@ int main(int argc, char *argv[]) * it might actually be touching the DB in some destructors, e.g., * unreserving UTXOs (see #1737) */ db_begin_transaction(ld->wallet->db); - tal_free(ld->rpc_listener); + tal_free(ld->jsonrpc); db_commit_transaction(ld->wallet->db); remove(ld->pidfile); diff --git a/lightningd/lightningd.h b/lightningd/lightningd.h index 4ae38849e..d508c02eb 100644 --- a/lightningd/lightningd.h +++ b/lightningd/lightningd.h @@ -84,10 +84,11 @@ struct lightningd { /* Location of the RPC socket. */ char *rpc_filename; - /* The listener for the RPC socket. Can be shut down separately from the - * rest of the daemon to allow a clean shutdown, which frees all pending - * cmds in a DB transaction. */ - struct io_listener *rpc_listener; + /* The root of the jsonrpc interface. Can be shut down + * separately from the rest of the daemon to allow a clean + * shutdown, which frees all pending cmds in a DB + * transaction. */ + struct jsonrpc *jsonrpc; /* Configuration file name */ char *config_filename; diff --git a/lightningd/test/run-find_my_abspath.c b/lightningd/test/run-find_my_abspath.c index 09a991a6a..03c5ad076 100644 --- a/lightningd/test/run-find_my_abspath.c +++ b/lightningd/test/run-find_my_abspath.c @@ -138,7 +138,7 @@ void register_opts(struct lightningd *ld UNNEEDED) void setup_color_and_alias(struct lightningd *ld UNNEEDED) { fprintf(stderr, "setup_color_and_alias called!\n"); abort(); } /* Generated stub for setup_jsonrpc */ -void setup_jsonrpc(struct lightningd *ld UNNEEDED, const char *rpc_filename UNNEEDED) +struct jsonrpc *setup_jsonrpc(struct lightningd *ld UNNEEDED) { fprintf(stderr, "setup_jsonrpc called!\n"); abort(); } /* Generated stub for setup_topology */ void setup_topology(struct chain_topology *topology UNNEEDED, struct timers *timers UNNEEDED,