diff --git a/daemon/Makefile b/daemon/Makefile index 0f487585c..cf7cadd93 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -4,18 +4,24 @@ daemon-wrongdir: $(MAKE) -C .. daemon-all -daemon-all: daemon/lightningd +daemon-all: daemon/lightningd daemon/lightning-cli -DAEMON_SRC := \ +DAEMON_LIB_SRC := \ daemon/configdir.c \ daemon/json.c \ + daemon/log.c \ + daemon/pseudorand.c +DAEMON_LIB_OBJS := $(DAEMON_LIB_SRC:.c=.o) + +DAEMON_SRC := \ daemon/jsonrpc.c \ daemon/lightningd.c \ - daemon/log.c \ - daemon/pseudorand.c \ daemon/timeout.c DAEMON_OBJS := $(DAEMON_SRC:.c=.o) +DAEMON_CLI_SRC := daemon/lightning-cli.c +DAEMON_CLI_OBJS := $(DAEMON_CLI_SRC:.c=.o) + DAEMON_JSMN_OBJS := daemon/jsmn.o DAEMON_JSMN_HEADERS := daemon/jsmn/jsmn.h @@ -28,10 +34,12 @@ DAEMON_HEADERS := \ daemon/pseudorand.h \ daemon/timeout.h -$(DAEMON_OBJS): $(DAEMON_HEADERS) $(DAEMON_JSMN_HEADERS) $(BITCOIN_HEADERS) $(CORE_HEADERS) $(GEN_HEADERS) $(CCAN_HEADERS) +$(DAEMON_OBJS) $(DAEMON_LIB_OBJS) $(DAEMON_CLI_OBJS): $(DAEMON_HEADERS) $(DAEMON_JSMN_HEADERS) $(BITCOIN_HEADERS) $(CORE_HEADERS) $(GEN_HEADERS) $(CCAN_HEADERS) $(DAEMON_JSMN_OBJS): $(DAEMON_JSMN_HEADERS) check-source: $(DAEMON_SRC:%=check-src-include-order/%) +check-source: $(DAEMON_LIB_SRC:%=check-src-include-order/%) +check-source: $(DAEMON_CLI_SRC:%=check-src-include-order/%) check-source: $(DAEMON_HEADERS:%=check-hdr-include-order/%) check-daemon-makefile: @if [ "`echo daemon/*.h`" != "$(DAEMON_HEADERS)" ]; then echo DAEMON_HEADERS incorrect; exit 1; fi @@ -44,4 +52,6 @@ daemon/jsmn/jsmn.c: daemon/jsmn.o: daemon/jsmn/jsmn.c $(COMPILE.c) -DJSMN_STRICT=1 $(OUTPUT_OPTION) $< -daemon/lightningd: $(DAEMON_OBJS) $(DAEMON_JSMN_OBJS) $(CORE_OBJS) $(BITCOIN_OBJS) $(CCAN_OBJS) libsecp256k1.a +daemon/lightningd: $(DAEMON_OBJS) $(DAEMON_LIB_OBJS) $(DAEMON_JSMN_OBJS) $(CORE_OBJS) $(BITCOIN_OBJS) $(CCAN_OBJS) libsecp256k1.a + +daemon/lightning-cli: $(DAEMON_CLI_OBJS) $(DAEMON_LIB_OBJS) $(DAEMON_JSMN_OBJS) $(CORE_OBJS) $(BITCOIN_OBJS) $(CCAN_OBJS) libsecp256k1.a diff --git a/daemon/lightning-cli.c b/daemon/lightning-cli.c new file mode 100644 index 000000000..a43bc5964 --- /dev/null +++ b/daemon/lightning-cli.c @@ -0,0 +1,172 @@ +/* + * Helper to submit via JSON-RPC and get back response. + */ +#include "configdir.h" +#include "json.h" +#include "version.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NO_ERROR 0 +#define ERROR_FROM_LIGHTNINGD 1 +#define ERROR_TALKING_TO_LIGHTNINGD 2 +#define ERROR_USAGE 3 + +/* Tal wrappers for opt. */ +static void *opt_allocfn(size_t size) +{ + return tal_alloc_(NULL, size, false, TAL_LABEL("opt_allocfn", "")); +} + +static void *tal_reallocfn(void *ptr, size_t size) +{ + if (!ptr) + return opt_allocfn(size); + tal_resize_(&ptr, 1, size, false); + return ptr; +} + +static void tal_freefn(void *ptr) +{ + tal_free(ptr); +} + +int main(int argc, char *argv[]) +{ + int fd, i, off; + const char *method; + char *cmd, *resp, *idstr, *rpc_filename; + char *result_end; + struct sockaddr_un addr; + jsmntok_t *toks; + const jsmntok_t *result, *error, *id; + char *lightning_dir; + const tal_t *ctx = tal(NULL, char); + size_t num_opens, num_closes; + bool valid; + + err_set_progname(argv[0]); + + opt_set_alloc(opt_allocfn, tal_reallocfn, tal_freefn); + configdir_register_opts(ctx, &lightning_dir, &rpc_filename); + + opt_register_noarg("--help|-h", opt_usage_and_exit, + " [...]", "Show this message"); + opt_register_version(); + + opt_early_parse(argc, argv, opt_log_stderr_exit); + opt_parse(&argc, argv, opt_log_stderr_exit); + + method = argv[1]; + if (!method) + errx(ERROR_USAGE, "Need at least one argument\n%s", + opt_usage(argv[0], NULL)); + + if (chdir(lightning_dir) != 0) + err(ERROR_TALKING_TO_LIGHTNINGD, "Moving into '%s'", + lightning_dir); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (strlen(rpc_filename) + 1 > sizeof(addr.sun_path)) + errx(ERROR_USAGE, "rpc filename '%s' too long", rpc_filename); + strcpy(addr.sun_path, rpc_filename); + addr.sun_family = AF_UNIX; + + if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) + err(ERROR_TALKING_TO_LIGHTNINGD, + "Connecting to '%s'", rpc_filename); + + idstr = tal_fmt(ctx, "lightning-cli-%i", getpid()); + cmd = tal_fmt(ctx, + "{ \"method\" : \"%s\", \"id\" : \"%s\", \"params\" : [ ", + method, idstr); + + for (i = 2; i < argc; i++) { + /* Numbers are left unquoted, and quoted things left alone. */ + if (strspn(argv[i], "0123456789") == strlen(argv[i]) + || argv[i][0] == '"') + tal_append_fmt(&cmd, "%s", argv[i]); + else + tal_append_fmt(&cmd, "\"%s\"", argv[i]); + if (i != argc - 1) + tal_append_fmt(&cmd, ", "); + } + tal_append_fmt(&cmd, "] }"); + + if (!write_all(fd, cmd, strlen(cmd))) + err(ERROR_TALKING_TO_LIGHTNINGD, "Writing command"); + + resp = tal_arr(cmd, char, 100); + off = 0; + num_opens = num_closes = 0; + while ((i = read(fd, resp + off, tal_count(resp) - 1 - off)) > 0) { + resp[off + i] = '\0'; + num_opens += strcount(resp + off, "{"); + num_closes += strcount(resp + off, "}"); + + off += i; + if (off == tal_count(resp) - 1) + tal_resize(&resp, tal_count(resp) * 2); + + /* parsing huge outputs is slow: do quick check first. */ + if (num_opens == num_closes && strstr(resp, "\"result\"")) + break; + } + if (i < 0) + err(ERROR_TALKING_TO_LIGHTNINGD, "reading response"); + + /* Parsing huge results is too slow, so hack fastpath common case */ + result_end = tal_fmt(ctx, ", \"error\" : null, \"id\" : \"%s\" }\n", + idstr); + + if (strstarts(resp, "{ \"result\" : ") && strends(resp, result_end)) { + /* Result is OK, so dump it */ + resp += strlen("{ \"result\" : "); + printf("%.*s\n", (int)(strlen(resp) - strlen(result_end)), resp); + tal_free(ctx); + return 0; + } + + toks = json_parse_input(resp, off, &valid); + if (!toks || !valid) + errx(ERROR_TALKING_TO_LIGHTNINGD, + "Malformed response '%s'", resp); + + result = json_get_member(resp, toks, "result"); + if (!result) + errx(ERROR_TALKING_TO_LIGHTNINGD, + "Missing 'result' in response '%s'", resp); + error = json_get_member(resp, toks, "error"); + if (!error) + errx(ERROR_TALKING_TO_LIGHTNINGD, + "Missing 'error' in response '%s'", resp); + id = json_get_member(resp, toks, "id"); + if (!id) + errx(ERROR_TALKING_TO_LIGHTNINGD, + "Missing 'id' in response '%s'", resp); + if (!json_tok_streq(resp, id, idstr)) + errx(ERROR_TALKING_TO_LIGHTNINGD, + "Incorrect 'id' in response: %.*s", + json_tok_len(id), json_tok_contents(resp, id)); + + if (json_tok_is_null(resp, error)) { + printf("%.*s\n", + json_tok_len(result), + json_tok_contents(resp, result)); + tal_free(ctx); + return 0; + } + + printf("%.*s\n", + json_tok_len(error), json_tok_contents(resp, error)); + tal_free(ctx); + return 1; +}