diff --git a/Makefile b/Makefile index 3e74d4bc749c..8a6ff9e54951 100644 --- a/Makefile +++ b/Makefile @@ -184,7 +184,7 @@ LDLIBS = -L/usr/local/lib -lm -lgmp -lsqlite3 -lz $(COVFLAGS) default: all-programs all-test-programs -config.vars ccan/config.h: configure +config.vars ccan/config.h: configure ccan/tools/configurator/configurator.c @if [ ! -f config.vars ]; then echo 'The 1990s are calling: use ./configure!' >&2; exit 1; fi ./configure --reconfigure diff --git a/ccan/README b/ccan/README index de2927b94c82..e08a994a973b 100644 --- a/ccan/README +++ b/ccan/README @@ -1,3 +1,3 @@ CCAN imported from http://ccodearchive.net. -CCAN version: init-2454-gc656dceb +CCAN version: init-2455-gd3d2242b diff --git a/ccan/ccan/compiler/compiler.h b/ccan/ccan/compiler/compiler.h index bce4f25a1226..caa89edc5968 100644 --- a/ccan/ccan/compiler/compiler.h +++ b/ccan/ccan/compiler/compiler.h @@ -228,4 +228,62 @@ #define WARN_UNUSED_RESULT #endif #endif + + +#if HAVE_ATTRIBUTE_DEPRECATED +/** + * WARN_DEPRECATED - warn that a function/type/variable is deprecated when used. + * + * Used to mark a function, type or variable should not be used. + * + * Example: + * WARN_DEPRECATED char *oldfunc(char *buf); + */ +#define WARN_DEPRECATED __attribute__((__deprecated__)) +#else +#define WARN_DEPRECATED +#endif + + +#if HAVE_ATTRIBUTE_NONNULL +/** + * NO_NULL_ARGS - specify that no arguments to this function can be NULL. + * + * The compiler will warn if any pointer args are NULL. + * + * Example: + * NO_NULL_ARGS char *my_copy(char *buf); + */ +#define NO_NULL_ARGS __attribute__((__nonnull__)) + +/** + * NON_NULL_ARGS - specify that some arguments to this function can't be NULL. + * @...: 1-based argument numbers for which args can't be NULL. + * + * The compiler will warn if any of the specified pointer args are NULL. + * + * Example: + * char *my_copy2(char *buf, char *maybenull) NON_NULL_ARGS(1, 2); + */ +#define NON_NULL_ARGS(index, ...) __attribute__((__nonnull__(index, __VA_ARGS__))) +#else +#define NO_NULL_ARGS +#define NON_NULL_ARGS(index, ...) +#endif + + +#if HAVE_ATTRIBUTE_SENTINEL +/** + * LAST_ARG_NULL - specify the last argument of a variadic function must be NULL. + * + * The compiler will warn if the last argument isn't NULL. + * + * Example: + * char *join_string(char *buf, ...) LAST_ARG_NULL; + */ +#define LAST_ARG_NULL __attribute__((__sentinel__)) +#else +#define LAST_ARG_NULL +#endif + #endif /* CCAN_COMPILER_H */ diff --git a/ccan/ccan/pipecmd/_info b/ccan/ccan/pipecmd/_info index a560bfea05cc..8c49511a250c 100644 --- a/ccan/ccan/pipecmd/_info +++ b/ccan/ccan/pipecmd/_info @@ -32,7 +32,7 @@ * exit(1); * exit(0); * } - * child = pipecmd(&outputfd, NULL, NULL, argv[0], "ignoredarg", NULL); + * child = pipecmd(NULL, &outputfd, NULL, argv[0], "ignoredarg", NULL); * if (child < 0) * err(1, "Creating child"); * if (read(outputfd, input, sizeof(input)) != sizeof(input)) diff --git a/ccan/ccan/pipecmd/pipecmd.c b/ccan/ccan/pipecmd/pipecmd.c index 32772a83bc7b..c2b514df5e97 100644 --- a/ccan/ccan/pipecmd/pipecmd.c +++ b/ccan/ccan/pipecmd/pipecmd.c @@ -6,6 +6,8 @@ #include #include +int pipecmd_preserve; + static char **gather_args(const char *arg0, va_list ap) { size_t n = 1; @@ -26,7 +28,7 @@ static char **gather_args(const char *arg0, va_list ap) return arr; } -pid_t pipecmdv(int *fd_fromchild, int *fd_tochild, int *fd_errfromchild, +pid_t pipecmdv(int *fd_tochild, int *fd_fromchild, int *fd_errfromchild, const char *cmd, va_list ap) { char **arr = gather_args(cmd, ap); @@ -36,12 +38,12 @@ pid_t pipecmdv(int *fd_fromchild, int *fd_tochild, int *fd_errfromchild, errno = ENOMEM; return -1; } - ret = pipecmdarr(fd_fromchild, fd_tochild, fd_errfromchild, arr); + ret = pipecmdarr(fd_tochild, fd_fromchild, fd_errfromchild, arr); free_noerr(arr); return ret; } -pid_t pipecmdarr(int *fd_fromchild, int *fd_tochild, int *fd_errfromchild, +pid_t pipecmdarr(int *fd_tochild, int *fd_fromchild, int *fd_errfromchild, char *const *arr) { int tochild[2], fromchild[2], errfromchild[2], execfail[2]; @@ -49,7 +51,10 @@ pid_t pipecmdarr(int *fd_fromchild, int *fd_tochild, int *fd_errfromchild, int err; if (fd_tochild) { - if (pipe(tochild) != 0) + if (fd_tochild == &pipecmd_preserve) { + tochild[0] = STDIN_FILENO; + fd_tochild = NULL; + } else if (pipe(tochild) != 0) goto fail; } else { tochild[0] = open("/dev/null", O_RDONLY); @@ -57,7 +62,10 @@ pid_t pipecmdarr(int *fd_fromchild, int *fd_tochild, int *fd_errfromchild, goto fail; } if (fd_fromchild) { - if (pipe(fromchild) != 0) + if (fd_fromchild == &pipecmd_preserve) { + fromchild[1] = STDOUT_FILENO; + fd_fromchild = NULL; + } else if (pipe(fromchild) != 0) goto close_tochild_fail; } else { fromchild[1] = open("/dev/null", O_WRONLY); @@ -65,7 +73,10 @@ pid_t pipecmdarr(int *fd_fromchild, int *fd_tochild, int *fd_errfromchild, goto close_tochild_fail; } if (fd_errfromchild) { - if (fd_errfromchild == fd_fromchild) { + if (fd_errfromchild == &pipecmd_preserve) { + errfromchild[1] = STDERR_FILENO; + fd_errfromchild = NULL; + } else if (fd_errfromchild == fd_fromchild) { errfromchild[0] = fromchild[0]; errfromchild[1] = fromchild[1]; } else { @@ -77,7 +88,7 @@ pid_t pipecmdarr(int *fd_fromchild, int *fd_tochild, int *fd_errfromchild, if (errfromchild[1] < 0) goto close_fromchild_fail; } - + if (pipe(execfail) != 0) goto close_errfromchild_fail; @@ -168,14 +179,14 @@ pid_t pipecmdarr(int *fd_fromchild, int *fd_tochild, int *fd_errfromchild, return -1; } -pid_t pipecmd(int *fd_fromchild, int *fd_tochild, int *fd_errfromchild, +pid_t pipecmd(int *fd_tochild, int *fd_fromchild, int *fd_errfromchild, const char *cmd, ...) { pid_t childpid; va_list ap; va_start(ap, cmd); - childpid = pipecmdv(fd_fromchild, fd_tochild, fd_errfromchild, cmd, ap); + childpid = pipecmdv(fd_tochild, fd_fromchild, fd_errfromchild, cmd, ap); va_end(ap); return childpid; diff --git a/ccan/ccan/pipecmd/pipecmd.h b/ccan/ccan/pipecmd/pipecmd.h index 4816950502f2..5bbaefc01234 100644 --- a/ccan/ccan/pipecmd/pipecmd.h +++ b/ccan/ccan/pipecmd/pipecmd.h @@ -18,6 +18,7 @@ * If @errfd is NULL, the child's stderr is (write-only) /dev/null. * * If @errfd == @outfd (and non-NULL) they will be shared. + * If @infd, @outfd or @errfd is &pipecmd_preserve, it is unchanged. * * The return value is the pid of the child, or -1. */ @@ -41,4 +42,10 @@ pid_t pipecmdv(int *infd, int *outfd, int *errfd, const char *cmd, va_list ap); * @arr: NULL-terminated array for arguments (first is program to run). */ pid_t pipecmdarr(int *infd, int *outfd, int *errfd, char *const *arr); + +/** + * pipecmd_preserve - special value for fds to indicate it is unchanged + */ +extern int pipecmd_preserve; + #endif /* CCAN_PIPECMD_H */ diff --git a/ccan/ccan/pipecmd/test/run-fdleak.c b/ccan/ccan/pipecmd/test/run-fdleak.c index 775ef333f8be..6b7729060e04 100644 --- a/ccan/ccan/pipecmd/test/run-fdleak.c +++ b/ccan/ccan/pipecmd/test/run-fdleak.c @@ -21,7 +21,7 @@ int main(int argc, char *argv[]) /* This is how many tests you plan to run */ plan_tests(13); - child = pipecmd(&outfd, NULL, NULL, argv[0], "out", NULL); + child = pipecmd(NULL, &outfd, NULL, argv[0], "out", NULL); if (!ok1(child > 0)) exit(1); ok1(read(outfd, buf, sizeof(buf)) == sizeof(buf)); diff --git a/ccan/ccan/pipecmd/test/run.c b/ccan/ccan/pipecmd/test/run.c index 45b96ab2f03b..e446da6d16b8 100644 --- a/ccan/ccan/pipecmd/test/run.c +++ b/ccan/ccan/pipecmd/test/run.c @@ -48,7 +48,7 @@ int main(int argc, char *argv[]) /* This is how many tests you plan to run */ plan_tests(67); - child = pipecmd(&outfd, &infd, &errfd, argv[0], "inout", NULL); + child = pipecmd(&infd, &outfd, &errfd, argv[0], "inout", NULL); if (!ok1(child > 0)) exit(1); ok1(write(infd, buf, sizeof(buf)) == sizeof(buf)); @@ -63,7 +63,7 @@ int main(int argc, char *argv[]) ok1(WIFEXITED(status)); ok1(WEXITSTATUS(status) == 0); - child = pipecmd(NULL, &infd, NULL, argv[0], "in", NULL); + child = pipecmd(&infd, NULL, NULL, argv[0], "in", NULL); if (!ok1(child > 0)) exit(1); ok1(write(infd, buf, sizeof(buf)) == sizeof(buf)); @@ -72,7 +72,7 @@ int main(int argc, char *argv[]) ok1(WIFEXITED(status)); ok1(WEXITSTATUS(status) == 0); - child = pipecmd(&outfd, NULL, NULL, argv[0], "out", NULL); + child = pipecmd(NULL, &outfd, NULL, argv[0], "out", NULL); if (!ok1(child > 0)) exit(1); ok1(read(outfd, buf, sizeof(buf)) == sizeof(buf)); @@ -94,7 +94,7 @@ int main(int argc, char *argv[]) ok1(WEXITSTATUS(status) == 0); /* errfd == outfd should work with both. */ - child = pipecmd(&errfd, NULL, &errfd, argv[0], "err", NULL); + child = pipecmd(NULL, &errfd, &errfd, argv[0], "err", NULL); if (!ok1(child > 0)) exit(1); ok1(read(errfd, buf, sizeof(buf)) == sizeof(buf)); @@ -104,7 +104,7 @@ int main(int argc, char *argv[]) ok1(WIFEXITED(status)); ok1(WEXITSTATUS(status) == 0); - child = pipecmd(&outfd, NULL, &outfd, argv[0], "out", NULL); + child = pipecmd(NULL, &outfd, &outfd, argv[0], "out", NULL); if (!ok1(child > 0)) exit(1); ok1(read(outfd, buf, sizeof(buf)) == sizeof(buf)); diff --git a/ccan/ccan/take/take.c b/ccan/ccan/take/take.c index c628aac0dc97..4833bf935764 100644 --- a/ccan/ccan/take/take.c +++ b/ccan/ccan/take/take.c @@ -32,9 +32,20 @@ void *take_(const void *p, const char *label) } takenarr = new; /* Once labelarr is set, we maintain it. */ - if (labelarr) - labelarr = realloc(labelarr, - sizeof(*labelarr) * (max_taken+1)); + if (labelarr) { + const char **labelarr_new; + labelarr_new = realloc(labelarr, + sizeof(*labelarr) * (max_taken+1)); + if (labelarr_new) { + labelarr = labelarr_new; + } else { + /* num_taken will be out of sync with the size of + * labelarr after realloc failure. + * Just pretend that we never had labelarr allocated. */ + free(labelarr); + labelarr = NULL; + } + } max_taken++; } if (unlikely(labelarr)) diff --git a/ccan/tools/configurator/configurator.c b/ccan/tools/configurator/configurator.c index 239e91c962b9..1386fc90c8d7 100644 --- a/ccan/tools/configurator/configurator.c +++ b/ccan/tools/configurator/configurator.c @@ -136,6 +136,15 @@ static const struct test base_tests[] = { { "HAVE_ATTRIBUTE_CONST", "__attribute__((const)) support", "DEFINES_FUNC", NULL, NULL, "static int __attribute__((const)) func(int x) { return x; }" }, + { "HAVE_ATTRIBUTE_DEPRECATED", "__attribute__((deprecated)) support", + "DEFINES_FUNC", NULL, NULL, + "static int __attribute__((deprecated)) func(int x) { return x; }" }, + { "HAVE_ATTRIBUTE_NONNULL", "__attribute__((nonnull)) support", + "DEFINES_FUNC", NULL, NULL, + "static char *__attribute__((nonnull)) func(char *p) { return p; }" }, + { "HAVE_ATTRIBUTE_SENTINEL", "__attribute__((sentinel)) support", + "DEFINES_FUNC", NULL, NULL, + "static int __attribute__((sentinel)) func(int i, ...) { return i; }" }, { "HAVE_ATTRIBUTE_PURE", "__attribute__((pure)) support", "DEFINES_FUNC", NULL, NULL, "static int __attribute__((pure)) func(int x) { return x; }" }, diff --git a/common/Makefile b/common/Makefile index e7f098d05c8d..7bd094b3005f 100644 --- a/common/Makefile +++ b/common/Makefile @@ -25,10 +25,13 @@ COMMON_SRC_NOGEN := \ common/initial_commit_tx.c \ common/io_lock.c \ common/json.c \ + common/json_escaped.c \ + common/json_tok.c \ common/key_derive.c \ common/keyset.c \ common/memleak.c \ common/msg_queue.c \ + common/param.c \ common/peer_billboard.c \ common/peer_failed.c \ common/permute_tx.c \ @@ -52,7 +55,7 @@ COMMON_SRC_NOGEN := \ COMMON_SRC_GEN := common/gen_status_wire.c common/gen_peer_status_wire.c -COMMON_HEADERS_NOGEN := $(COMMON_SRC_NOGEN:.c=.h) common/overflows.h common/htlc.h common/status_levels.h +COMMON_HEADERS_NOGEN := $(COMMON_SRC_NOGEN:.c=.h) common/overflows.h common/htlc.h common/status_levels.h common/json_command.h common/jsonrpc_errors.h COMMON_HEADERS_GEN := common/gen_htlc_state_names.h common/gen_status_wire.h common/gen_peer_status_wire.h COMMON_HEADERS := $(COMMON_HEADERS_GEN) $(COMMON_HEADERS_NOGEN) diff --git a/common/daemon.c b/common/daemon.c index 276bb4838ef8..67fc16e39638 100644 --- a/common/daemon.c +++ b/common/daemon.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -150,3 +151,23 @@ void daemon_shutdown(void) tal_free(tmpctx); wally_cleanup(0); } + +void daemon_maybe_debug(int argc, char *argv[]) +{ +#if DEVELOPER + for (int i = 1; i < argc; i++) { + if (!streq(argv[i], "--debugger")) + continue; + + /* Don't let this mess up stdout, so redir to /dev/null */ + char *cmd = tal_fmt(NULL, "${DEBUG_TERM:-gnome-terminal --} gdb -ex 'attach %u' %s >/dev/null &", getpid(), argv[0]); + fprintf(stderr, "Running %s\n", cmd); + /* warn_unused_result is fascist bullshit. + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66425 */ + if (system(cmd)) + ; + /* Continue in the debugger. */ + kill(getpid(), SIGSTOP); + } +#endif /* DEVELOPER */ +} diff --git a/common/daemon.h b/common/daemon.h index b211e37772db..d252dff3a5de 100644 --- a/common/daemon.h +++ b/common/daemon.h @@ -14,6 +14,9 @@ int daemon_poll(struct pollfd *fds, nfds_t nfds, int timeout); /* Shutdown for a valgrind-clean exit (frees everything) */ void daemon_shutdown(void); +/* Kick in a debugger if they set --debugger */ +void daemon_maybe_debug(int argc, char *argv[]); + struct backtrace_state *backtrace_state; #endif /* LIGHTNING_COMMON_DAEMON_H */ diff --git a/common/json.c b/common/json.c index 549e5445f7c2..3af957d51f8b 100644 --- a/common/json.c +++ b/common/json.c @@ -82,8 +82,8 @@ bool json_to_number(const char *buffer, const jsmntok_t *tok, return true; } -bool json_tok_bitcoin_amount(const char *buffer, const jsmntok_t *tok, - uint64_t *satoshi) +bool json_to_bitcoin_amount(const char *buffer, const jsmntok_t *tok, + uint64_t *satoshi) { char *end; unsigned long btc, sat; @@ -172,13 +172,14 @@ const jsmntok_t *json_get_arr(const jsmntok_t tok[], size_t index) return NULL; } -jsmntok_t *json_parse_input(const char *input, int len, bool *valid) +jsmntok_t *json_parse_input(const tal_t *ctx, + const char *input, int len, bool *valid) { jsmn_parser parser; jsmntok_t *toks; int ret; - toks = tal_arr(input, jsmntok_t, 10); + toks = tal_arr(ctx, jsmntok_t, 10); toks[0].type = JSMN_UNDEFINED; jsmn_init(&parser); diff --git a/common/json.h b/common/json.h index 38120ea15683..730430389e91 100644 --- a/common/json.h +++ b/common/json.h @@ -35,8 +35,8 @@ bool json_to_u64(const char *buffer, const jsmntok_t *tok, bool json_to_double(const char *buffer, const jsmntok_t *tok, double *num); /* Extract satoshis from this (may be a string, or a decimal number literal) */ -bool json_tok_bitcoin_amount(const char *buffer, const jsmntok_t *tok, - uint64_t *satoshi); +bool json_to_bitcoin_amount(const char *buffer, const jsmntok_t *tok, + uint64_t *satoshi); /* Is this a number? [0..9]+ */ bool json_tok_is_num(const char *buffer, const jsmntok_t *tok); @@ -55,7 +55,8 @@ const jsmntok_t *json_get_member(const char *buffer, const jsmntok_t tok[], const jsmntok_t *json_get_arr(const jsmntok_t tok[], size_t index); /* If input is complete and valid, return tokens. */ -jsmntok_t *json_parse_input(const char *input, int len, bool *valid); +jsmntok_t *json_parse_input(const tal_t *ctx, + const char *input, int len, bool *valid); /* Convert a jsmntype_t enum to a human readable string. */ const char *jsmntype_to_string(jsmntype_t t); diff --git a/common/json_command.h b/common/json_command.h new file mode 100644 index 000000000000..27140ae71127 --- /dev/null +++ b/common/json_command.h @@ -0,0 +1,24 @@ +/* These functions must be supplied by any binary linking with common/param + * so it can fail commands. */ +#ifndef LIGHTNING_COMMON_JSON_COMMAND_H +#define LIGHTNING_COMMON_JSON_COMMAND_H +#include "config.h" +#include +#include + +struct command; + +/* Caller supplied this: param assumes it can call it. */ +void PRINTF_FMT(3, 4) command_fail(struct command *cmd, int code, + const char *fmt, ...); + +/* Also caller supplied: is this invoked simply to get usage? */ +bool command_usage_only(const struct command *cmd); + +/* If so, this is called. */ +void command_set_usage(struct command *cmd, const char *usage); + +/* Also caller supplied: is this invoked simply to check parameters? */ +bool command_check_only(const struct command *cmd); + +#endif /* LIGHTNING_COMMON_JSON_COMMAND_H */ diff --git a/lightningd/json_escaped.c b/common/json_escaped.c similarity index 98% rename from lightningd/json_escaped.c rename to common/json_escaped.c index 5e62548e50d9..d60654b9277d 100644 --- a/lightningd/json_escaped.c +++ b/common/json_escaped.c @@ -1,4 +1,4 @@ -#include +#include #include struct json_escaped *json_escaped_string_(const tal_t *ctx, diff --git a/lightningd/json_escaped.h b/common/json_escaped.h similarity index 88% rename from lightningd/json_escaped.h rename to common/json_escaped.h index 8ef77123d484..0f742a86f39f 100644 --- a/lightningd/json_escaped.h +++ b/common/json_escaped.h @@ -1,5 +1,5 @@ -#ifndef LIGHTNING_LIGHTNINGD_JSON_ESCAPED_H -#define LIGHTNING_LIGHTNINGD_JSON_ESCAPED_H +#ifndef LIGHTNING_COMMON_JSON_ESCAPED_H +#define LIGHTNING_COMMON_JSON_ESCAPED_H #include "config.h" #include @@ -32,4 +32,4 @@ struct json_escaped *json_escaped_string_(const tal_t *ctx, /* Be very careful here! Can fail! Doesn't handle \u: use UTF-8 please. */ const char *json_escaped_unescape(const tal_t *ctx, const struct json_escaped *esc); -#endif /* LIGHTNING_LIGHTNINGD_JSON_ESCAPED_H */ +#endif /* LIGHTNING_COMMON_JSON_ESCAPED_H */ diff --git a/common/json_tok.c b/common/json_tok.c new file mode 100644 index 000000000000..6e2732216802 --- /dev/null +++ b/common/json_tok.c @@ -0,0 +1,184 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +bool json_tok_array(struct command *cmd, const char *name, + const char *buffer, const jsmntok_t *tok, + const jsmntok_t **arr) +{ + if (tok->type == JSMN_ARRAY) + return (*arr = tok); + + command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "'%s' should be an array, not '%.*s'", + name, tok->end - tok->start, buffer + tok->start); + return false; +} + +bool json_tok_bool(struct command *cmd, const char *name, + const char *buffer, const jsmntok_t *tok, + bool **b) +{ + *b = tal(cmd, bool); + if (tok->type == JSMN_PRIMITIVE) { + if (memeqstr(buffer + tok->start, tok->end - tok->start, "true")) { + **b = true; + return true; + } + if (memeqstr(buffer + tok->start, tok->end - tok->start, "false")) { + **b = false; + return true; + } + } + command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "'%s' should be 'true' or 'false', not '%.*s'", + name, tok->end - tok->start, buffer + tok->start); + return false; +} + +bool json_tok_double(struct command *cmd, const char *name, + const char *buffer, const jsmntok_t *tok, + double **num) +{ + *num = tal(cmd, double); + if (json_to_double(buffer, tok, *num)) + return true; + + command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "'%s' should be a double, not '%.*s'", + name, tok->end - tok->start, buffer + tok->start); + return false; +} + +bool json_tok_escaped_string(struct command *cmd, const char *name, + const char * buffer, const jsmntok_t *tok, + const char **str) +{ + struct json_escaped *esc = json_to_escaped_string(cmd, buffer, tok); + if (esc) { + *str = json_escaped_unescape(cmd, esc); + if (*str) + return true; + } + command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "'%s' should be a string, not '%.*s'" + " (note, we don't allow \\u)", + name, + tok->end - tok->start, buffer + tok->start); + return false; +} + +bool json_tok_string(struct command *cmd, const char *name, + const char * buffer, const jsmntok_t *tok, + const char **str) +{ + *str = tal_strndup(cmd, buffer + tok->start, + tok->end - tok->start); + return true; +} + +bool json_tok_label(struct command *cmd, const char *name, + const char * buffer, const jsmntok_t *tok, + struct json_escaped **label) +{ + /* We accept both strings and number literals here. */ + *label = json_escaped_string_(cmd, buffer + tok->start, tok->end - tok->start); + if (*label && (tok->type == JSMN_STRING || json_tok_is_num(buffer, tok))) + return true; + + command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "'%s' should be a string or number, not '%.*s'", + name, tok->end - tok->start, buffer + tok->start); + return false; +} + +bool json_tok_number(struct command *cmd, const char *name, + const char *buffer, const jsmntok_t *tok, + unsigned int **num) +{ + *num = tal(cmd, unsigned int); + if (json_to_number(buffer, tok, *num)) + return true; + + command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "'%s' should be an integer, not '%.*s'", + name, tok->end - tok->start, buffer + tok->start); + return false; +} + +bool json_tok_sha256(struct command *cmd, const char *name, + const char *buffer, const jsmntok_t *tok, + struct sha256 **hash) +{ + *hash = tal(cmd, struct sha256); + if (hex_decode(buffer + tok->start, + tok->end - tok->start, + *hash, sizeof(**hash))) + return true; + + command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "'%s' should be a 32 byte hex value, not '%.*s'", + name, tok->end - tok->start, buffer + tok->start); + return false; +} + +bool json_tok_msat(struct command *cmd, const char *name, + const char *buffer, const jsmntok_t * tok, + u64 **msatoshi_val) +{ + if (json_tok_streq(buffer, tok, "any")) { + *msatoshi_val = NULL; + return true; + } + *msatoshi_val = tal(cmd, u64); + + if (json_to_u64(buffer, tok, *msatoshi_val) && *msatoshi_val != 0) + return true; + + command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "'%s' should be a positive number or 'any', not '%.*s'", + name, + tok->end - tok->start, + buffer + tok->start); + return false; +} + +bool json_tok_percent(struct command *cmd, const char *name, + const char *buffer, const jsmntok_t *tok, + double **num) +{ + *num = tal(cmd, double); + if (json_to_double(buffer, tok, *num) && **num >= 0.0) + return true; + + command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "'%s' should be a positive double, not '%.*s'", + name, tok->end - tok->start, buffer + tok->start); + return false; +} + +bool json_tok_u64(struct command *cmd, const char *name, + const char *buffer, const jsmntok_t *tok, + uint64_t **num) +{ + *num = tal(cmd, uint64_t); + if (json_to_u64(buffer, tok, *num)) + return true; + + command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "'%s' should be an unsigned 64 bit integer, not '%.*s'", + name, tok->end - tok->start, buffer + tok->start); + return false; +} + +bool json_tok_tok(struct command *cmd, const char *name, + const char *buffer, const jsmntok_t * tok, + const jsmntok_t **out) +{ + return (*out = tok); +} diff --git a/common/json_tok.h b/common/json_tok.h new file mode 100644 index 000000000000..b0d6d1898cf9 --- /dev/null +++ b/common/json_tok.h @@ -0,0 +1,72 @@ +/* Helpers for use with param parsing. */ +#ifndef LIGHTNING_COMMON_JSON_TOK_H +#define LIGHTNING_COMMON_JSON_TOK_H +#include "config.h" +#include + +struct command; + +/* Extract json array token */ +bool json_tok_array(struct command *cmd, const char *name, + const char *buffer, const jsmntok_t *tok, + const jsmntok_t **arr); + +/* Extract boolean this (must be a true or false) */ +bool json_tok_bool(struct command *cmd, const char *name, + const char *buffer, const jsmntok_t *tok, + bool **b); + +/* Extract double from this (must be a number literal) */ +bool json_tok_double(struct command *cmd, const char *name, + const char *buffer, const jsmntok_t *tok, + double **num); + +/* Extract an escaped string (and unescape it) */ +bool json_tok_escaped_string(struct command *cmd, const char *name, + const char * buffer, const jsmntok_t *tok, + const char **str); + +/* Extract a string */ +bool json_tok_string(struct command *cmd, const char *name, + const char * buffer, const jsmntok_t *tok, + const char **str); + +/* Extract a label. It is either an escaped string or a number. */ +bool json_tok_label(struct command *cmd, const char *name, + const char * buffer, const jsmntok_t *tok, + struct json_escaped **label); + +/* Extract number from this (may be a string, or a number literal) */ +bool json_tok_number(struct command *cmd, const char *name, + const char *buffer, const jsmntok_t *tok, + unsigned int **num); + +/* Extract sha256 hash */ +bool json_tok_sha256(struct command *cmd, const char *name, + const char *buffer, const jsmntok_t *tok, + struct sha256 **hash); + +/* Extract positive integer, or NULL if tok is 'any'. */ +bool json_tok_msat(struct command *cmd, const char *name, + const char *buffer, const jsmntok_t * tok, + u64 **msatoshi_val); + +/* Extract double in range [0.0, 100.0] */ +bool json_tok_percent(struct command *cmd, const char *name, + const char *buffer, const jsmntok_t *tok, + double **num); + +/* Extract number from this (may be a string, or a number literal) */ +bool json_tok_u64(struct command *cmd, const char *name, + const char *buffer, const jsmntok_t *tok, + uint64_t **num); + +/* + * Set the address of @out to @tok. Used as a callback by handlers that + * want to unmarshal @tok themselves. + */ +bool json_tok_tok(struct command *cmd, const char *name, + const char *buffer, const jsmntok_t * tok, + const jsmntok_t **out); + +#endif /* LIGHTNING_COMMON_JSON_TOK_H */ diff --git a/lightningd/jsonrpc_errors.h b/common/jsonrpc_errors.h similarity index 88% rename from lightningd/jsonrpc_errors.h rename to common/jsonrpc_errors.h index 65757e734c6c..5d91eaf660f0 100644 --- a/lightningd/jsonrpc_errors.h +++ b/common/jsonrpc_errors.h @@ -1,8 +1,8 @@ -/* lightningd/jsonrpc_errors.h +/* common/jsonrpc_errors.h * Lists error codes for JSON-RPC. */ -#ifndef LIGHTNING_LIGHTNINGD_JSONRPC_ERRORS_H -#define LIGHTNING_LIGHTNINGD_JSONRPC_ERRORS_H +#ifndef LIGHTNING_COMMON_JSONRPC_ERRORS_H +#define LIGHTNING_COMMON_JSONRPC_ERRORS_H #include "config.h" /* Standard errors defined by JSON-RPC 2.0 standard */ @@ -44,4 +44,4 @@ #define INVOICE_LABEL_ALREADY_EXISTS 900 #define INVOICE_PREIMAGE_ALREADY_EXISTS 901 -#endif /* LIGHTNING_LIGHTNINGD_JSONRPC_ERRORS_H */ +#endif /* LIGHTNING_COMMON_JSONRPC_ERRORS_H */ diff --git a/lightningd/param.c b/common/param.c similarity index 87% rename from lightningd/param.c rename to common/param.c index bc1397755942..bcdd0773a5fd 100644 --- a/lightningd/param.c +++ b/common/param.c @@ -1,11 +1,9 @@ #include #include +#include +#include +#include #include -#include -#include -#include -#include -#include struct param { const char *name; @@ -63,7 +61,8 @@ static bool post_check(struct command *cmd, struct param *params) static bool parse_by_position(struct command *cmd, struct param *params, const char *buffer, - const jsmntok_t tokens[]) + const jsmntok_t tokens[], + bool allow_extra) { const jsmntok_t *tok = tokens + 1; const jsmntok_t *end = json_next(tokens); @@ -79,7 +78,7 @@ static bool parse_by_position(struct command *cmd, } /* check for unexpected trailing params */ - if (!cmd->allow_unused && tok != end) { + if (!allow_extra && tok != end) { command_fail(cmd, JSONRPC2_INVALID_PARAMS, "too many parameters:" " got %u, expected %zu", @@ -108,7 +107,8 @@ static struct param *find_param(struct param *params, const char *start, static bool parse_by_name(struct command *cmd, struct param *params, const char *buffer, - const jsmntok_t tokens[]) + const jsmntok_t tokens[], + bool allow_extra) { const jsmntok_t *first = tokens + 1; const jsmntok_t *last = json_next(tokens); @@ -117,7 +117,7 @@ static bool parse_by_name(struct command *cmd, struct param *p = find_param(params, buffer + first->start, first->end - first->start); if (!p) { - if (!cmd->allow_unused) { + if (!allow_extra) { command_fail(cmd, JSONRPC2_INVALID_PARAMS, "unknown parameter: '%.*s'", first->end - first->start, @@ -239,7 +239,8 @@ static char *param_usage(const tal_t *ctx, static bool param_arr(struct command *cmd, const char *buffer, const jsmntok_t tokens[], - struct param *params) + struct param *params, + bool allow_extra) { #if DEVELOPER if (!check_params(params)) { @@ -248,9 +249,9 @@ static bool param_arr(struct command *cmd, const char *buffer, } #endif if (tokens->type == JSMN_ARRAY) - return parse_by_position(cmd, params, buffer, tokens); + return parse_by_position(cmd, params, buffer, tokens, allow_extra); else if (tokens->type == JSMN_OBJECT) - return parse_by_name(cmd, params, buffer, tokens); + return parse_by_name(cmd, params, buffer, tokens, allow_extra); command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Expected array or object for params"); @@ -263,12 +264,17 @@ bool param(struct command *cmd, const char *buffer, struct param *params = tal_arr(cmd, struct param, 0); const char *name; va_list ap; + bool allow_extra = false; va_start(ap, tokens); while ((name = va_arg(ap, const char *)) != NULL) { bool required = va_arg(ap, int); param_cbx cbx = va_arg(ap, param_cbx); void *arg = va_arg(ap, void *); + if (streq(name, "")) { + allow_extra = true; + continue; + } if (!param_add(¶ms, name, required, cbx, arg)) { command_fail(cmd, PARAM_DEV_ERROR, "developer error: param_add %s", name); @@ -278,14 +284,13 @@ bool param(struct command *cmd, const char *buffer, } va_end(ap); - if (cmd->mode == CMD_USAGE) { - cmd->usage = param_usage(cmd, params); + if (command_usage_only(cmd)) { + command_set_usage(cmd, param_usage(cmd, params)); return false; } - /* Always return false for CMD_USAGE and CMD_CHECK, signaling the caller - * to return immediately. For CMD_NORMAL, return true if all parameters - * are valid. - */ - return param_arr(cmd, buffer, tokens, params) && cmd->mode == CMD_NORMAL; + /* Always return false if we're simply checking command parameters; + * normally this returns true if all parameters are valid. */ + return param_arr(cmd, buffer, tokens, params, allow_extra) + && !command_check_only(cmd); } diff --git a/lightningd/param.h b/common/param.h similarity index 88% rename from lightningd/param.h rename to common/param.h index 039dde46cf24..2306114e29df 100644 --- a/lightningd/param.h +++ b/common/param.h @@ -1,6 +1,9 @@ -#ifndef LIGHTNING_LIGHTNINGD_PARAM_H -#define LIGHTNING_LIGHTNINGD_PARAM_H +#ifndef LIGHTNING_COMMON_PARAM_H +#define LIGHTNING_COMMON_PARAM_H #include "config.h" +#include +#include +#include /*~ Greetings adventurer! * @@ -28,8 +31,9 @@ * * All the command handlers throughout the code use this system. * json_invoice() is a great example. The common callbacks can be found in - * lightningd/json.c. Use them directly or feel free to write your own. + * common/json_tok.c. Use them directly or feel free to write your own. */ +struct command; /* * Parse the json tokens. @params can be an array of values or an object @@ -90,4 +94,6 @@ typedef bool(*param_cbx)(struct command *cmd, (const jsmntok_t *)NULL, \ (arg)) == true); }) -#endif /* LIGHTNING_LIGHTNINGD_PARAM_H */ +/* Special flag for 'check' which allows any parameters. */ +#define p_opt_any() "", false, NULL, NULL +#endif /* LIGHTNING_COMMON_PARAM_H */ diff --git a/common/subdaemon.c b/common/subdaemon.c index ed3b8bd0bd80..58349f343b80 100644 --- a/common/subdaemon.c +++ b/common/subdaemon.c @@ -37,19 +37,10 @@ void subdaemon_setup(int argc, char *argv[]) logging_io = true; } + daemon_maybe_debug(argc, argv); + #if DEVELOPER - /* From debugger, set debugger_spin to 0. */ for (int i = 1; i < argc; i++) { - if (streq(argv[i], "--debugger")) { - char *cmd = tal_fmt(NULL, "${DEBUG_TERM:-gnome-terminal --} gdb -ex 'attach %u' %s &", getpid(), argv[0]); - fprintf(stderr, "Running %s\n", cmd); - /* warn_unused_result is fascist bullshit. - * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66425 */ - if (system(cmd)) - ; - /* Continue in the debugger. */ - kill(getpid(), SIGSTOP); - } if (strstarts(argv[i], "--dev-disconnect=")) { dev_disconnect_init(atoi(argv[i] + strlen("--dev-disconnect="))); diff --git a/common/test/run-json.c b/common/test/run-json.c index db52d6b01e84..cea278fa5738 100644 --- a/common/test/run-json.c +++ b/common/test/run-json.c @@ -18,7 +18,7 @@ static void do_json_tok_bitcoin_amount(const char* val, uint64_t expected) fprintf(stderr, "do_json_tok_bitcoin_amount(\"%s\", %"PRIu64"): ", val, expected); - assert(json_tok_bitcoin_amount(val, &tok, &amount) == true); + assert(json_to_bitcoin_amount(val, &tok, &amount) == true); assert(amount == expected); fprintf(stderr, "ok\n"); diff --git a/lightningd/test/run-json_escaped.c b/common/test/run-json_escaped.c similarity index 100% rename from lightningd/test/run-json_escaped.c rename to common/test/run-json_escaped.c diff --git a/common/test/run-json_remove.c b/common/test/run-json_remove.c index 5328e684ac1b..f20df9654e76 100644 --- a/common/test/run-json_remove.c +++ b/common/test/run-json_remove.c @@ -25,7 +25,7 @@ static struct json *json_parse(const tal_t * ctx, const char *str) j->buffer = tal_strdup(j, str); convert_quotes(j->buffer); bool ok; - j->toks = json_parse_input(j->buffer, strlen(j->buffer), &ok); + j->toks = json_parse_input(j, j->buffer, strlen(j->buffer), &ok); assert(ok); j->toks = json_tok_copy(j, j->toks); return j; diff --git a/lightningd/test/run-param.c b/common/test/run-param.c similarity index 91% rename from lightningd/test/run-param.c rename to common/test/run-param.c index c46c64c68257..cf86444ec875 100644 --- a/lightningd/test/run-param.c +++ b/common/test/run-param.c @@ -1,12 +1,11 @@ #include "config.h" #include "../json.c" #include "../json_escaped.c" -#include "../json_stream.c" +#include "../json_tok.c" #include "../param.c" #include #include #include -#include #include #include #include @@ -33,21 +32,35 @@ void command_fail(struct command *cmd, int code, const char *fmt, ...) } /* AUTOGENERATED MOCKS START */ -/* Generated stub for feerate_from_style */ -u32 feerate_from_style(u32 feerate UNNEEDED, enum feerate_style style UNNEEDED) -{ fprintf(stderr, "feerate_from_style called!\n"); abort(); } -/* Generated stub for feerate_name */ -const char *feerate_name(enum feerate feerate UNNEEDED) -{ fprintf(stderr, "feerate_name called!\n"); abort(); } -/* Generated stub for fmt_wireaddr_without_port */ -char *fmt_wireaddr_without_port(const tal_t *ctx UNNEEDED, const struct wireaddr *a UNNEEDED) -{ fprintf(stderr, "fmt_wireaddr_without_port called!\n"); abort(); } -/* Generated stub for json_feerate_estimate */ -bool json_feerate_estimate(struct command *cmd UNNEEDED, - u32 **feerate_per_kw UNNEEDED, enum feerate feerate UNNEEDED) -{ fprintf(stderr, "json_feerate_estimate called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ +/* We do this lightningd-style: */ +enum command_mode { + CMD_NORMAL, + CMD_USAGE, + CMD_CHECK +}; + +struct command { + enum command_mode mode; + const char *usage; +}; + +void command_set_usage(struct command *cmd, const char *usage) +{ + cmd->usage = usage; +} + +bool command_usage_only(const struct command *cmd) +{ + return cmd->mode == CMD_USAGE; +} + +bool command_check_only(const struct command *cmd) +{ + return cmd->mode == CMD_CHECK; +} + struct json { jsmntok_t *toks; char *buffer; @@ -353,7 +366,7 @@ static void five_hundred_params(void) /* first test object version */ struct json *j = json_parse(params, obj); - assert(param_arr(cmd, j->buffer, j->toks, params)); + assert(param_arr(cmd, j->buffer, j->toks, params, false)); for (int i = 0; i < tal_count(ints); ++i) { assert(ints[i]); assert(*ints[i] == i); @@ -362,7 +375,7 @@ static void five_hundred_params(void) /* now test array */ j = json_parse(params, arr); - assert(param_arr(cmd, j->buffer, j->toks, params)); + assert(param_arr(cmd, j->buffer, j->toks, params, false)); for (int i = 0; i < tal_count(ints); ++i) { assert(*ints[i] == i); } @@ -507,7 +520,9 @@ static void json_tok_tests(void) test_cb(json_tok_percent, double, "[ 'wow' ]", 0, false); } -static void test_invoice(struct command *cmd, const char *buffer, +static void test_invoice(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, const jsmntok_t *params) { u64 *msatoshi_val; @@ -534,20 +549,9 @@ static void test_invoice(struct command *cmd, const char *buffer, static void usage(void) { - /* Do this to simulate a call to our pretend handler (test_invoice) */ - struct json_command invoice_command = { - "invoice", - test_invoice, - "", - false, - "" - }; - cmd->mode = CMD_USAGE; - cmd->json_cmd = &invoice_command; - - cmd->json_cmd->dispatch(cmd, NULL, NULL); + test_invoice(cmd, NULL, NULL, NULL); assert(streq(cmd->usage, "msatoshi label description " "[expiry] [fallbacks] [preimage]")); @@ -561,7 +565,6 @@ int main(void) setup_tmpctx(); cmd = tal(tmpctx, struct command); cmd->mode = CMD_NORMAL; - cmd->allow_unused = false; fail_msg = tal_arr(cmd, char, 10000); zero_params(); diff --git a/common/wallet_tx.c b/common/wallet_tx.c index 88a36460b3e2..e627f0c7bdea 100644 --- a/common/wallet_tx.c +++ b/common/wallet_tx.c @@ -1,6 +1,7 @@ +#include +#include #include #include -#include #include void wtx_init(struct command *cmd, struct wallet_tx * wtx) diff --git a/lightningd/Makefile b/lightningd/Makefile index 62090aa4f252..d516b5e1d457 100644 --- a/lightningd/Makefile +++ b/lightningd/Makefile @@ -34,8 +34,11 @@ LIGHTNINGD_COMMON_OBJS := \ common/key_derive.o \ common/io_lock.o \ common/json.o \ + common/json_escaped.o \ + common/json_tok.o \ common/memleak.o \ common/msg_queue.o \ + common/param.o \ common/permute_tx.o \ common/pseudorand.o \ common/sphinx.o \ @@ -64,7 +67,6 @@ LIGHTNINGD_SRC := \ lightningd/htlc_end.c \ lightningd/invoice.c \ lightningd/json.c \ - lightningd/json_escaped.c \ lightningd/json_stream.c \ lightningd/jsonrpc.c \ lightningd/lightningd.c \ @@ -74,7 +76,6 @@ LIGHTNINGD_SRC := \ lightningd/onchain_control.c \ lightningd/opening_control.c \ lightningd/options.c \ - lightningd/param.c \ lightningd/pay.c \ lightningd/payalgo.c \ lightningd/peer_control.c \ @@ -92,8 +93,7 @@ ALL_OBJS += $(LIGHTNINGD_OBJS) # We accumulate all lightningd/ headers in these three: LIGHTNINGD_HEADERS_NOGEN = \ $(LIGHTNINGD_SRC:.c=.h) \ - lightningd/channel_state.h \ - lightningd/jsonrpc_errors.h + lightningd/channel_state.h # Generated headers LIGHTNINGD_HEADERS_GEN = \ diff --git a/lightningd/bitcoind.c b/lightningd/bitcoind.c index 55b261d2d194..e3fedec4f797 100644 --- a/lightningd/bitcoind.c +++ b/lightningd/bitcoind.c @@ -229,7 +229,7 @@ static void next_bcli(struct bitcoind *bitcoind, enum bitcoind_prio prio) if (!bcli) return; - bcli->pid = pipecmdarr(&bcli->fd, NULL, &bcli->fd, + bcli->pid = pipecmdarr(NULL, &bcli->fd, &bcli->fd, cast_const2(char **, bcli->args)); if (bcli->pid < 0) fatal("%s exec failed: %s", bcli->args[0], strerror(errno)); @@ -309,7 +309,7 @@ static bool extract_feerate(struct bitcoin_cli *bcli, const jsmntok_t *tokens, *feeratetok; bool valid; - tokens = json_parse_input(output, output_bytes, &valid); + tokens = json_parse_input(output, output, output_bytes, &valid); if (!tokens) fatal("%s: %s response", bcli_args(tmpctx, bcli), @@ -327,7 +327,7 @@ static bool extract_feerate(struct bitcoin_cli *bcli, if (!feeratetok) return false; - return json_tok_bitcoin_amount(output, feeratetok, feerate); + return json_to_bitcoin_amount(output, feeratetok, feerate); } struct estimatefee { @@ -541,7 +541,8 @@ static bool process_gettxout(struct bitcoin_cli *bcli) return true; } - tokens = json_parse_input(bcli->output, bcli->output_bytes, &valid); + tokens = json_parse_input(bcli->output, bcli->output, bcli->output_bytes, + &valid); if (!tokens) fatal("%s: %s response", bcli_args(tmpctx, bcli), valid ? "partial" : "invalid"); @@ -557,7 +558,7 @@ static bool process_gettxout(struct bitcoin_cli *bcli) bcli_args(tmpctx, bcli), (int)bcli->output_bytes, bcli->output); - if (!json_tok_bitcoin_amount(bcli->output, valuetok, &out.amount)) + if (!json_to_bitcoin_amount(bcli->output, valuetok, &out.amount)) fatal("%s: had bad value (%.*s)?", bcli_args(tmpctx, bcli), (int)bcli->output_bytes, bcli->output); @@ -601,7 +602,8 @@ static bool process_getblock(struct bitcoin_cli *bcli) struct bitcoin_txid txid; bool valid; - tokens = json_parse_input(bcli->output, bcli->output_bytes, &valid); + tokens = json_parse_input(bcli->output, bcli->output, bcli->output_bytes, + &valid); if (!tokens) { /* Most likely we are running on a pruned node, call * the callback with NULL to indicate failure */ @@ -806,7 +808,7 @@ void wait_for_bitcoind(struct bitcoind *bitcoind) bool printed = false; for (;;) { - child = pipecmdarr(&from, NULL, &from, cast_const2(char **,cmd)); + child = pipecmdarr(NULL, &from, &from, cast_const2(char **,cmd)); if (child < 0) { if (errno == ENOENT) { fatal_bitcoind_failure(bitcoind, "bitcoin-cli not found. Is bitcoin-cli (part of Bitcoin Core) available in your PATH?"); diff --git a/lightningd/chaintopology.c b/lightningd/chaintopology.c index 8a31e93267b2..c070169dc2eb 100644 --- a/lightningd/chaintopology.c +++ b/lightningd/chaintopology.c @@ -13,14 +13,15 @@ #include #include #include +#include +#include #include +#include #include #include #include #include #include -#include -#include /* Mutual recursion via timer. */ static void try_extend_tip(struct chain_topology *topo); @@ -463,7 +464,9 @@ u32 feerate_to_style(u32 feerate_perkw, enum feerate_style style) } static void json_feerates(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { struct chain_topology *topo = cmd->ld->topology; struct json_stream *response; diff --git a/lightningd/connect_control.c b/lightningd/connect_control.c index 46a75debe384..b488f7225de7 100644 --- a/lightningd/connect_control.c +++ b/lightningd/connect_control.c @@ -4,7 +4,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -18,11 +21,9 @@ #include #include #include -#include #include #include #include -#include #include #include #include @@ -74,7 +75,9 @@ static void connect_cmd_succeed(struct command *cmd, const struct pubkey *id) } static void json_connect(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { u32 *port; jsmntok_t *idtok; diff --git a/lightningd/gossip_control.c b/lightningd/gossip_control.c index 7ca3e54982d9..8c3f2538a71f 100644 --- a/lightningd/gossip_control.c +++ b/lightningd/gossip_control.c @@ -11,6 +11,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include @@ -22,12 +26,9 @@ #include #include #include -#include #include -#include #include #include -#include #include #include #include @@ -243,8 +244,10 @@ static void json_getnodes_reply(struct subd *gossip UNUSED, const u8 *reply, command_success(cmd, response); } -static void json_listnodes(struct command *cmd, const char *buffer, - const jsmntok_t *params) +static void json_listnodes(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { u8 *req; struct pubkey *id; @@ -286,7 +289,10 @@ static void json_getroute_reply(struct subd *gossip UNUSED, const u8 *reply, con command_success(cmd, response); } -static void json_getroute(struct command *cmd, const char *buffer, const jsmntok_t *params) +static void json_getroute(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { struct lightningd *ld = cmd->ld; struct pubkey *destination; @@ -393,8 +399,10 @@ static void json_listchannels_reply(struct subd *gossip UNUSED, const u8 *reply, command_success(cmd, response); } -static void json_listchannels(struct command *cmd, const char *buffer, - const jsmntok_t *params) +static void json_listchannels(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { u8 *req; struct short_channel_id *id; @@ -443,7 +451,9 @@ static void json_scids_reply(struct subd *gossip UNUSED, const u8 *reply, } static void json_dev_query_scids(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { u8 *msg; const jsmntok_t *scidstok; @@ -486,6 +496,7 @@ AUTODATA(json_command, &dev_query_scids_command); static void json_dev_send_timestamp_filter(struct command *cmd, const char *buffer, + const jsmntok_t *obj UNNEEDED, const jsmntok_t *params) { u8 *msg; @@ -555,6 +566,7 @@ static void json_channel_range_reply(struct subd *gossip UNUSED, const u8 *reply static void json_dev_query_channel_range(struct command *cmd, const char *buffer, + const jsmntok_t *obj UNNEEDED, const jsmntok_t *params) { u8 *msg; @@ -584,6 +596,7 @@ AUTODATA(json_command, &dev_query_channel_range_command); static void json_dev_set_max_scids_encode_size(struct command *cmd, const char *buffer, + const jsmntok_t *obj UNNEEDED, const jsmntok_t *params) { u8 *msg; @@ -609,6 +622,7 @@ AUTODATA(json_command, &dev_set_max_scids_encode_size); static void json_dev_suppress_gossip(struct command *cmd, const char *buffer, + const jsmntok_t *obj UNNEEDED, const jsmntok_t *params) { if (!param(cmd, buffer, params, NULL)) diff --git a/lightningd/invoice.c b/lightningd/invoice.c index 8e22b7bb8bb2..2c527971f34f 100644 --- a/lightningd/invoice.c +++ b/lightningd/invoice.c @@ -1,7 +1,6 @@ #include "invoice.h" #include "json.h" #include "jsonrpc.h" -#include "jsonrpc_errors.h" #include "lightningd.h" #include #include @@ -10,6 +9,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include @@ -18,11 +21,8 @@ #include #include #include -#include -#include #include #include -#include #include #include #include @@ -292,7 +292,9 @@ static void gossipd_incoming_channels_reply(struct subd *gossipd, } static void json_invoice(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { const jsmntok_t *fallbacks; const jsmntok_t *preimagetok; @@ -415,7 +417,9 @@ static void json_add_invoices(struct json_stream *response, } static void json_listinvoices(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { struct json_escaped *label; struct json_stream *response; @@ -441,7 +445,9 @@ static const struct json_command listinvoices_command = { AUTODATA(json_command, &listinvoices_command); static void json_delinvoice(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { struct invoice i; const struct invoice_details *details; @@ -492,7 +498,9 @@ static const struct json_command delinvoice_command = { }; AUTODATA(json_command, &delinvoice_command); -static void json_delexpiredinvoice(struct command *cmd, const char *buffer, +static void json_delexpiredinvoice(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, const jsmntok_t *params) { u64 *maxexpirytime; @@ -516,6 +524,7 @@ AUTODATA(json_command, &delexpiredinvoice_command); static void json_autocleaninvoice(struct command *cmd, const char *buffer, + const jsmntok_t *obj UNNEEDED, const jsmntok_t *params) { u64 *cycle; @@ -541,7 +550,9 @@ static const struct json_command autocleaninvoice_command = { AUTODATA(json_command, &autocleaninvoice_command); static void json_waitanyinvoice(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { u64 *pay_index; struct wallet *wallet = cmd->ld->wallet; @@ -575,7 +586,9 @@ AUTODATA(json_command, &waitanyinvoice_command); * waiters, if the payment is still pending. */ static void json_waitinvoice(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { struct invoice i; const struct invoice_details *details; @@ -648,7 +661,9 @@ static void json_add_fallback(struct json_stream *response, } static void json_decodepay(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { struct bolt11 *b11; struct json_stream *response; diff --git a/lightningd/json.c b/lightningd/json.c index 16c0068e970a..e94be2c34db9 100644 --- a/lightningd/json.c +++ b/lightningd/json.c @@ -3,15 +3,17 @@ #include #include #include +#include +#include +#include #include +#include #include #include #include #include -#include #include #include -#include #include #include #include @@ -95,175 +97,6 @@ void json_add_txid(struct json_stream *result, const char *fieldname, json_add_string(result, fieldname, hex); } -bool json_tok_array(struct command *cmd, const char *name, - const char *buffer, const jsmntok_t *tok, - const jsmntok_t **arr) -{ - if (tok->type == JSMN_ARRAY) - return (*arr = tok); - - command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "'%s' should be an array, not '%.*s'", - name, tok->end - tok->start, buffer + tok->start); - return false; -} - -bool json_tok_bool(struct command *cmd, const char *name, - const char *buffer, const jsmntok_t *tok, - bool **b) -{ - *b = tal(cmd, bool); - if (tok->type == JSMN_PRIMITIVE) { - if (memeqstr(buffer + tok->start, tok->end - tok->start, "true")) { - **b = true; - return true; - } - if (memeqstr(buffer + tok->start, tok->end - tok->start, "false")) { - **b = false; - return true; - } - } - command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "'%s' should be 'true' or 'false', not '%.*s'", - name, tok->end - tok->start, buffer + tok->start); - return false; -} - -bool json_tok_double(struct command *cmd, const char *name, - const char *buffer, const jsmntok_t *tok, - double **num) -{ - *num = tal(cmd, double); - if (json_to_double(buffer, tok, *num)) - return true; - - command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "'%s' should be a double, not '%.*s'", - name, tok->end - tok->start, buffer + tok->start); - return false; -} - -bool json_tok_escaped_string(struct command *cmd, const char *name, - const char * buffer, const jsmntok_t *tok, - const char **str) -{ - struct json_escaped *esc = json_to_escaped_string(cmd, buffer, tok); - if (esc) { - *str = json_escaped_unescape(cmd, esc); - if (*str) - return true; - } - command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "'%s' should be a string, not '%.*s'" - " (note, we don't allow \\u)", - name, - tok->end - tok->start, buffer + tok->start); - return false; -} - -bool json_tok_string(struct command *cmd, const char *name, - const char * buffer, const jsmntok_t *tok, - const char **str) -{ - *str = tal_strndup(cmd, buffer + tok->start, - tok->end - tok->start); - return true; -} - -bool json_tok_label(struct command *cmd, const char *name, - const char * buffer, const jsmntok_t *tok, - struct json_escaped **label) -{ - /* We accept both strings and number literals here. */ - *label = json_escaped_string_(cmd, buffer + tok->start, tok->end - tok->start); - if (*label && (tok->type == JSMN_STRING || json_tok_is_num(buffer, tok))) - return true; - - command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "'%s' should be a string or number, not '%.*s'", - name, tok->end - tok->start, buffer + tok->start); - return false; -} - -bool json_tok_number(struct command *cmd, const char *name, - const char *buffer, const jsmntok_t *tok, - unsigned int **num) -{ - *num = tal(cmd, unsigned int); - if (json_to_number(buffer, tok, *num)) - return true; - - command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "'%s' should be an integer, not '%.*s'", - name, tok->end - tok->start, buffer + tok->start); - return false; -} - -bool json_tok_sha256(struct command *cmd, const char *name, - const char *buffer, const jsmntok_t *tok, - struct sha256 **hash) -{ - *hash = tal(cmd, struct sha256); - if (hex_decode(buffer + tok->start, - tok->end - tok->start, - *hash, sizeof(**hash))) - return true; - - command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "'%s' should be a 32 byte hex value, not '%.*s'", - name, tok->end - tok->start, buffer + tok->start); - return false; -} - -bool json_tok_msat(struct command *cmd, const char *name, - const char *buffer, const jsmntok_t * tok, - u64 **msatoshi_val) -{ - if (json_tok_streq(buffer, tok, "any")) { - *msatoshi_val = NULL; - return true; - } - *msatoshi_val = tal(cmd, u64); - - if (json_to_u64(buffer, tok, *msatoshi_val) && *msatoshi_val != 0) - return true; - - command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "'%s' should be a positive number or 'any', not '%.*s'", - name, - tok->end - tok->start, - buffer + tok->start); - return false; -} - -bool json_tok_percent(struct command *cmd, const char *name, - const char *buffer, const jsmntok_t *tok, - double **num) -{ - *num = tal(cmd, double); - if (json_to_double(buffer, tok, *num) && **num >= 0.0) - return true; - - command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "'%s' should be a positive double, not '%.*s'", - name, tok->end - tok->start, buffer + tok->start); - return false; -} - -bool json_tok_u64(struct command *cmd, const char *name, - const char *buffer, const jsmntok_t *tok, - uint64_t **num) -{ - *num = tal(cmd, uint64_t); - if (json_to_u64(buffer, tok, *num)) - return true; - - command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "'%s' should be an unsigned 64 bit integer, not '%.*s'", - name, tok->end - tok->start, buffer + tok->start); - return false; -} - bool json_to_pubkey(const char *buffer, const jsmntok_t *tok, struct pubkey *pubkey) { @@ -470,13 +303,6 @@ void json_add_address_internal(struct json_stream *response, abort(); } -bool json_tok_tok(struct command *cmd, const char *name, - const char *buffer, const jsmntok_t * tok, - const jsmntok_t **out) -{ - return (*out = tok); -} - void json_add_num(struct json_stream *result, const char *fieldname, unsigned int value) { json_add_member(result, fieldname, "%u", value); diff --git a/lightningd/json.h b/lightningd/json.h index e960eb7a7c13..00f723bd0672 100644 --- a/lightningd/json.h +++ b/lightningd/json.h @@ -47,56 +47,6 @@ void json_add_pubkey(struct json_stream *response, void json_add_txid(struct json_stream *result, const char *fieldname, const struct bitcoin_txid *txid); -/* Extract json array token */ -bool json_tok_array(struct command *cmd, const char *name, - const char *buffer, const jsmntok_t *tok, - const jsmntok_t **arr); - -/* Extract boolean this (must be a true or false) */ -bool json_tok_bool(struct command *cmd, const char *name, - const char *buffer, const jsmntok_t *tok, - bool **b); - -/* Extract double from this (must be a number literal) */ -bool json_tok_double(struct command *cmd, const char *name, - const char *buffer, const jsmntok_t *tok, - double **num); - -/* Extract an escaped string (and unescape it) */ -bool json_tok_escaped_string(struct command *cmd, const char *name, - const char * buffer, const jsmntok_t *tok, - const char **str); - -/* Extract a string */ -bool json_tok_string(struct command *cmd, const char *name, - const char * buffer, const jsmntok_t *tok, - const char **str); - -/* Extract a label. It is either an escaped string or a number. */ -bool json_tok_label(struct command *cmd, const char *name, - const char * buffer, const jsmntok_t *tok, - struct json_escaped **label); - -/* Extract number from this (may be a string, or a number literal) */ -bool json_tok_number(struct command *cmd, const char *name, - const char *buffer, const jsmntok_t *tok, - unsigned int **num); - -/* Extract sha256 hash */ -bool json_tok_sha256(struct command *cmd, const char *name, - const char *buffer, const jsmntok_t *tok, - struct sha256 **hash); - -/* Extract positive integer, or NULL if tok is 'any'. */ -bool json_tok_msat(struct command *cmd, const char *name, - const char *buffer, const jsmntok_t * tok, - u64 **msatoshi_val); - -/* Extract double in range [0.0, 100.0] */ -bool json_tok_percent(struct command *cmd, const char *name, - const char *buffer, const jsmntok_t *tok, - double **num); - /* Extract a pubkey from this */ bool json_to_pubkey(const char *buffer, const jsmntok_t *tok, struct pubkey *pubkey); @@ -113,11 +63,6 @@ bool json_tok_short_channel_id(struct command *cmd, const char *name, const char *buffer, const jsmntok_t *tok, struct short_channel_id **scid); -/* Extract number from this (may be a string, or a number literal) */ -bool json_tok_u64(struct command *cmd, const char *name, - const char *buffer, const jsmntok_t *tok, - uint64_t **num); - enum feerate_style { FEERATE_PER_KSIPA, FEERATE_PER_KBYTE @@ -152,14 +97,6 @@ void json_add_address_internal(struct json_stream *response, const char *fieldname, const struct wireaddr_internal *addr); -/* - * Set the address of @out to @tok. Used as a callback by handlers that - * want to unmarshal @tok themselves. - */ -bool json_tok_tok(struct command *cmd, const char *name, - const char *buffer, const jsmntok_t * tok, - const jsmntok_t **out); - /* '"fieldname" : "value"' or '"value"' if fieldname is NULL. Turns * any non-printable chars into JSON escapes, but leaves existing escapes alone. diff --git a/lightningd/json_stream.c b/lightningd/json_stream.c index 11ab28150142..3ddf00b6796b 100644 --- a/lightningd/json_stream.c +++ b/lightningd/json_stream.c @@ -95,15 +95,18 @@ static void js_written_some(struct json_stream *js) io_wake(js); } -void json_stream_append(struct json_stream *js, const char *str) +void json_stream_append_part(struct json_stream *js, const char *str, size_t len) { - size_t len = strlen(str); - mkroom(js, len); memcpy(membuf_add(&js->outbuf, len), str, len); js_written_some(js); } +void json_stream_append(struct json_stream *js, const char *str) +{ + json_stream_append_part(js, str, strlen(str)); +} + static void json_stream_append_vfmt(struct json_stream *js, const char *fmt, va_list ap) { diff --git a/lightningd/json_stream.h b/lightningd/json_stream.h index 29c05fce3623..64632da39f87 100644 --- a/lightningd/json_stream.h +++ b/lightningd/json_stream.h @@ -55,6 +55,15 @@ void json_object_end(struct json_stream *js); */ void json_stream_append(struct json_stream *js, const char *str); +/** + * json_stream_append_part - literally insert part of string into json_stream. + * @js: the json_stream. + * @str: the string. + * @len: the length to append (<= strlen(str)). + */ +void json_stream_append_part(struct json_stream *js, const char *str, + size_t len); + /** * json_stream_append_fmt - insert formatted string into the json_stream. * @js: the json_stream. diff --git a/lightningd/jsonrpc.c b/lightningd/jsonrpc.c index 0c164e147434..1ccd0ed29d4e 100644 --- a/lightningd/jsonrpc.c +++ b/lightningd/jsonrpc.c @@ -23,7 +23,11 @@ #include #include #include +#include +#include +#include #include +#include #include #include #include @@ -32,12 +36,9 @@ #include #include #include -#include #include -#include #include #include -#include #include #include #include @@ -134,7 +135,9 @@ static void destroy_jcon(struct json_connection *jcon) } static void json_help(struct command *cmd, - const char *buffer, const jsmntok_t *params); + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params); static const struct json_command help_command = { "help", @@ -151,7 +154,9 @@ static const struct json_command help_command = { AUTODATA(json_command, &help_command); static void json_stop(struct command *cmd, - const char *buffer UNUSED, const jsmntok_t *params UNUSED) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { struct json_stream *response; @@ -174,7 +179,9 @@ AUTODATA(json_command, &stop_command); #if DEVELOPER static void json_rhash(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNUSED, + const jsmntok_t *params) { struct json_stream *response; struct sha256 *secret; @@ -222,7 +229,9 @@ static void slowcmd_start(struct slowcmd *sc) } static void json_slowcmd(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNUSED, + const jsmntok_t *params) { struct slowcmd *sc = tal(cmd, struct slowcmd); @@ -244,7 +253,9 @@ static const struct json_command dev_slowcmd_command = { AUTODATA(json_command, &dev_slowcmd_command); static void json_crash(struct command *cmd UNUSED, - const char *buffer UNUSED, const jsmntok_t *params UNUSED) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { if (!param(cmd, buffer, params, NULL)) return; @@ -277,7 +288,7 @@ static void json_add_help_command(struct command *cmd, { char *usage; cmd->mode = CMD_USAGE; - json_command->dispatch(cmd, NULL, NULL); + json_command->dispatch(cmd, NULL, NULL, NULL); usage = tal_fmt(cmd, "%s %s", json_command->name, cmd->usage); json_object_start(response, NULL); @@ -302,7 +313,9 @@ static void json_add_help_command(struct command *cmd, } static void json_help(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { struct json_stream *response; const jsmntok_t *cmdtok; @@ -374,14 +387,9 @@ static void destroy_command(struct command *cmd) list_del_from(&cmd->jcon->commands, &cmd->list); } -void command_success(struct command *cmd, struct json_stream *result) +void command_raw_complete(struct command *cmd, struct json_stream *result) { - assert(cmd); - assert(cmd->have_json_stream); - json_stream_append(result, " }\n\n"); json_stream_close(result, cmd); - if (cmd->ok) - *(cmd->ok) = true; /* If we have a jcon, it will free result for us. */ if (cmd->jcon) @@ -390,19 +398,22 @@ void command_success(struct command *cmd, struct json_stream *result) tal_free(cmd); } +void command_success(struct command *cmd, struct json_stream *result) +{ + assert(cmd); + assert(cmd->have_json_stream); + json_stream_append(result, " }\n\n"); + + command_raw_complete(cmd, result); +} + void command_failed(struct command *cmd, struct json_stream *result) { assert(cmd->have_json_stream); /* Have to close error */ json_stream_append(result, " } }\n\n"); - json_stream_close(result, cmd); - if (cmd->ok) - *(cmd->ok) = false; - /* If we have a jcon, it will free result for us. */ - if (cmd->jcon) - tal_steal(cmd->jcon, result); - tal_free(cmd); + command_raw_complete(cmd, result); } void PRINTF_FMT(3, 4) command_fail(struct command *cmd, int code, @@ -444,7 +455,7 @@ static void json_command_malformed(struct json_connection *jcon, json_stream_close(js, NULL); } -static struct json_stream *attach_json_stream(struct command *cmd) +struct json_stream *json_stream_raw_for_cmd(struct command *cmd) { struct json_stream *js; @@ -461,7 +472,7 @@ static struct json_stream *attach_json_stream(struct command *cmd) static struct json_stream *json_start(struct command *cmd) { - struct json_stream *js = attach_json_stream(cmd); + struct json_stream *js = json_stream_raw_for_cmd(cmd); json_stream_append_fmt(js, "{ \"jsonrpc\": \"2.0\", \"id\" : %s, ", cmd->id); @@ -536,8 +547,6 @@ static void parse_request(struct json_connection *jcon, const jsmntok_t tok[]) json_tok_contents(jcon->buffer, id), json_tok_len(id)); c->mode = CMD_NORMAL; - c->ok = NULL; - c->allow_unused = false; list_add_tail(&jcon->commands, &c->list); tal_add_destructor(c, destroy_command); @@ -570,7 +579,7 @@ static void parse_request(struct json_connection *jcon, const jsmntok_t tok[]) } db_begin_transaction(jcon->ld->wallet->db); - c->json_cmd->dispatch(c, jcon->buffer, params); + c->json_cmd->dispatch(c, jcon->buffer, tok, params); db_commit_transaction(jcon->ld->wallet->db); /* If they didn't complete it, they must call command_still_pending. @@ -638,7 +647,7 @@ static struct io_plan *read_json(struct io_conn *conn, return io_wait(conn, conn, read_json, jcon); } - toks = json_parse_input(jcon->buffer, jcon->used, &valid); + toks = json_parse_input(jcon->buffer, jcon->buffer, jcon->used, &valid); if (!toks) { if (!valid) { log_unusual(jcon->log, @@ -762,6 +771,21 @@ struct jsonrpc *jsonrpc_new(const tal_t *ctx, struct lightningd *ld) return jsonrpc; } +bool command_usage_only(const struct command *cmd) +{ + return cmd->mode == CMD_USAGE; +} + +void command_set_usage(struct command *cmd, const char *usage) +{ + cmd->usage = usage; +} + +bool command_check_only(const struct command *cmd) +{ + return cmd->mode == CMD_CHECK; +} + void jsonrpc_listen(struct jsonrpc *jsonrpc, struct lightningd *ld) { struct sockaddr_un addr; @@ -952,23 +976,31 @@ static bool json_tok_command(struct command *cmd, const char *name, return false; } +/* We add this destructor as a canary to detect cmd failing. */ +static void destroy_command_canary(struct command *cmd, bool *failed) +{ + *failed = true; +} + static void json_check(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { jsmntok_t *mod_params; const jsmntok_t *name_tok; - bool ok; + bool failed; struct json_stream *response; if (cmd->mode == CMD_USAGE) { mod_params = NULL; } else { mod_params = json_tok_copy(cmd, params); - cmd->allow_unused = true; } if (!param(cmd, buffer, mod_params, p_req("command_to_check", json_tok_command, &name_tok), + p_opt_any(), NULL)) return; @@ -979,14 +1011,11 @@ static void json_check(struct command *cmd, json_tok_remove(&mod_params, (jsmntok_t *)name_tok, 1); cmd->mode = CMD_CHECK; - cmd->allow_unused = false; - /* FIXME(wythe): Maybe change "ok" to "failed" since that's really what - * we're after and would be more clear. */ - ok = true; - cmd->ok = &ok; - cmd->json_cmd->dispatch(cmd, buffer, mod_params); - - if (!ok) + failed = false; + tal_add_destructor2(cmd, destroy_command_canary, &failed); + cmd->json_cmd->dispatch(cmd, buffer, mod_params, mod_params); + + if (failed) return; response = json_stream_success(cmd); diff --git a/lightningd/jsonrpc.h b/lightningd/jsonrpc.h index da21d36efa6b..ffc98a63bf4c 100644 --- a/lightningd/jsonrpc.h +++ b/lightningd/jsonrpc.h @@ -37,9 +37,6 @@ struct command { enum command_mode mode; /* This is created if mode is CMD_USAGE */ const char *usage; - bool *ok; - /* Do not report unused parameters as errors (default false). */ - bool allow_unused; /* Have we started a json stream already? For debugging. */ bool have_json_stream; }; @@ -47,7 +44,9 @@ struct command { struct json_command { const char *name; void (*dispatch)(struct command *, - const char *buffer, const jsmntok_t *params); + const char *buffer, + const jsmntok_t *obj, + const jsmntok_t *params); const char *description; bool deprecated; const char *verbose; @@ -65,7 +64,7 @@ struct json_stream *json_stream_success(struct command *cmd); /** * json_stream_fail - start streaming a failed json result. * @cmd: the command we're running. - * @code: the error code from lightningd/jsonrpc_errors.h + * @code: the error code from common/jsonrpc_errors.h * @errmsg: the error string. * * The returned value should go to command_failed() when done; @@ -78,7 +77,7 @@ struct json_stream *json_stream_fail(struct command *cmd, /** * json_stream_fail_nodata - start streaming a failed json result. * @cmd: the command we're running. - * @code: the error code from lightningd/jsonrpc_errors.h + * @code: the error code from common/jsonrpc_errors.h * @errmsg: the error string. * * This is used by command_fail(), which doesn't add any JSON data. @@ -90,12 +89,14 @@ struct json_stream *json_stream_fail_nodata(struct command *cmd, struct json_stream *null_response(struct command *cmd); void command_success(struct command *cmd, struct json_stream *response); void command_failed(struct command *cmd, struct json_stream *result); -void PRINTF_FMT(3, 4) command_fail(struct command *cmd, int code, - const char *fmt, ...); /* Mainly for documentation, that we plan to close this later. */ void command_still_pending(struct command *cmd); +/* For low-level JSON stream access: */ +struct json_stream *json_stream_raw_for_cmd(struct command *cmd); +void command_raw_complete(struct command *cmd, struct json_stream *result); + /** * Create a new jsonrpc to wrap all related information. * diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c index ebcac22dcbfc..d300c1a74199 100644 --- a/lightningd/lightningd.c +++ b/lightningd/lightningd.c @@ -57,6 +57,7 @@ /*~ This is common code: routines shared by one or more executables * (separate daemons, or the lightning-cli program). */ #include +#include #include #include #include @@ -68,7 +69,6 @@ #include #include #include -#include #include #include #include @@ -110,8 +110,8 @@ static struct lightningd *new_lightningd(const tal_t *ctx) * is a nod to keeping it minimal and explicit: we need this code for * testing, but its existence means we're not actually testing the * same exact code users will be running. */ + ld->dev_debug_subprocess = NULL; #if DEVELOPER - ld->dev_debug_subdaemon = NULL; ld->dev_disconnect_fd = -1; ld->dev_subdaemon_fail = false; ld->dev_allow_localhost = false; @@ -258,10 +258,10 @@ void test_subdaemons(const struct lightningd *ld) const char *dpath = path_join(tmpctx, ld->daemon_dir, subdaemons[i]); const char *verstring; /*~ CCAN's pipecmd module is like popen for grownups: it - * takes pointers to fill in stdout, stdin and stderr file + * takes pointers to fill in stdin, stdout and stderr file * descriptors if desired, and the remainder of arguments * are the command and its argument. */ - pid_t pid = pipecmd(&outfd, NULL, &outfd, + pid_t pid = pipecmd(NULL, &outfd, &outfd, dpath, "--version", NULL); /*~ Our logging system: spam goes in at log_debug level, but @@ -611,7 +611,7 @@ int main(int argc, char *argv[]) /*~ Initialize all the plugins we just registered, so they can * do their thing and tell us about themselves (including * options registration). */ - plugins_init(ld->plugins); + plugins_init(ld->plugins, ld->dev_debug_subprocess); /*~ Handle options and config; move to .lightningd (--lightning-dir) */ handle_opts(ld, argc, argv); diff --git a/lightningd/lightningd.h b/lightningd/lightningd.h index d508c02eb9e8..4c1ebb28639e 100644 --- a/lightningd/lightningd.h +++ b/lightningd/lightningd.h @@ -183,10 +183,10 @@ struct lightningd { * if we are the fundee. */ u32 max_funding_unconfirmed; -#if DEVELOPER - /* If we want to debug a subdaemon. */ - const char *dev_debug_subdaemon; + /* If we want to debug a subdaemon/plugin. */ + const char *dev_debug_subprocess; +#if DEVELOPER /* If we have a --dev-disconnect file */ int dev_disconnect_fd; diff --git a/lightningd/log.c b/lightningd/log.c index b09d96d62987..c8bb4c956050 100644 --- a/lightningd/log.c +++ b/lightningd/log.c @@ -10,17 +10,18 @@ #include #include #include +#include +#include #include +#include #include #include #include #include #include #include -#include #include #include -#include #include #include #include @@ -716,7 +717,9 @@ bool json_tok_loglevel(struct command *cmd, const char *name, } static void json_getlog(struct command *cmd, - const char *buffer, const jsmntok_t * params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t * params) { struct json_stream *response; enum log_level *minlevel; diff --git a/lightningd/memdump.c b/lightningd/memdump.c index eb940d7ed82c..5b3d683c72ac 100644 --- a/lightningd/memdump.c +++ b/lightningd/memdump.c @@ -4,7 +4,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -12,11 +15,9 @@ #include #include #include -#include #include #include #include -#include #include #include #include @@ -62,8 +63,9 @@ static void add_memdump(struct json_stream *response, } static void json_memdump(struct command *cmd, - const char *buffer UNNEEDED, - const jsmntok_t *params UNNEEDED) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { struct json_stream *response; @@ -283,8 +285,9 @@ void opening_memleak_done(struct command *cmd, struct subd *leaker) } static void json_memleak(struct command *cmd, - const char *buffer UNNEEDED, - const jsmntok_t *params UNNEEDED) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { if (!param(cmd, buffer, params, NULL)) return; diff --git a/lightningd/opening_control.c b/lightningd/opening_control.c index a11215a2298d..99c95cbb0e6c 100644 --- a/lightningd/opening_control.c +++ b/lightningd/opening_control.c @@ -4,7 +4,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -16,11 +19,9 @@ #include #include #include -#include #include #include #include -#include #include #include #include @@ -762,7 +763,9 @@ void opening_peer_no_active_channels(struct peer *peer) * json_fund_channel - Entrypoint for funding a channel */ static void json_fund_channel(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { const jsmntok_t *sattok; struct funding_channel * fc = tal(cmd, struct funding_channel); diff --git a/lightningd/options.c b/lightningd/options.c index 6569157caeec..8fd78f8ca37c 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -10,7 +10,11 @@ #include #include #include +#include +#include +#include #include +#include #include #include #include @@ -19,13 +23,10 @@ #include #include #include -#include #include -#include #include #include #include -#include #include #include #include @@ -435,6 +436,12 @@ static void config_register_opts(struct lightningd *ld) } #if DEVELOPER +static char *opt_subprocess_debug(const char *optarg, struct lightningd *ld) +{ + ld->dev_debug_subprocess = optarg; + return NULL; +} + static void dev_register_opts(struct lightningd *ld) { opt_register_noarg("--dev-no-reconnect", opt_set_invbool, @@ -442,8 +449,8 @@ static void dev_register_opts(struct lightningd *ld) "Disable automatic reconnect attempts"); opt_register_noarg("--dev-fail-on-subdaemon-fail", opt_set_bool, &ld->dev_subdaemon_fail, opt_hidden); - opt_register_arg("--dev-debugger=", opt_subd_debug, NULL, - ld, "Invoke gdb at start of "); + opt_register_early_arg("--dev-debugger=", opt_subprocess_debug, NULL, + ld, "Invoke gdb at start of "); opt_register_arg("--dev-broadcast-interval=", opt_set_uintval, opt_show_uintval, &ld->config.broadcast_interval_msec, "Time between gossip broadcasts in milliseconds"); @@ -1040,7 +1047,9 @@ static void add_config(struct lightningd *ld, } static void json_listconfigs(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { size_t i; struct json_stream *response = NULL; diff --git a/lightningd/pay.c b/lightningd/pay.c index 15aff6d03618..52ebd99d5c61 100644 --- a/lightningd/pay.c +++ b/lightningd/pay.c @@ -2,16 +2,17 @@ #include #include #include +#include +#include +#include #include #include #include #include #include -#include #include #include #include -#include #include #include #include @@ -941,7 +942,9 @@ static void json_sendpay_on_resolve(const struct sendpay_result* r, } static void json_sendpay(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { const jsmntok_t *routetok; const jsmntok_t *t, *end; @@ -1025,7 +1028,9 @@ static void waitsendpay_timeout(struct command *cmd) command_fail(cmd, PAY_IN_PROGRESS, "Timed out while waiting"); } -static void json_waitsendpay(struct command *cmd, const char *buffer, +static void json_waitsendpay(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, const jsmntok_t *params) { struct sha256 *rhash; @@ -1054,8 +1059,10 @@ static const struct json_command waitsendpay_command = { }; AUTODATA(json_command, &waitsendpay_command); -static void json_listpayments(struct command *cmd, const char *buffer, - const jsmntok_t *params) +static void json_listpayments(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { const struct wallet_payment **payments; struct json_stream *response; diff --git a/lightningd/payalgo.c b/lightningd/payalgo.c index fe78a6942515..f657bf0b1e15 100644 --- a/lightningd/payalgo.c +++ b/lightningd/payalgo.c @@ -5,6 +5,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -12,10 +15,8 @@ #include #include #include -#include #include #include -#include #include #include #include @@ -592,7 +593,9 @@ static void json_pay_stop_retrying(struct pay *pay) } static void json_pay(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { double *riskfactor; double *maxfeepercent; diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index 0607b5c8ac41..2984e4a5221d 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -15,7 +15,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -33,13 +36,11 @@ #include #include #include -#include #include #include #include #include #include -#include #include #include #include @@ -803,7 +804,9 @@ static void json_add_peer(struct lightningd *ld, } static void json_listpeers(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { enum log_level *ll; struct pubkey *specific_id; @@ -890,7 +893,9 @@ command_find_channel(struct command *cmd, } static void json_close(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { const jsmntok_t *idtok; struct peer *peer; @@ -1032,7 +1037,9 @@ void load_channels_from_wallet(struct lightningd *ld) } static void json_disconnect(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { struct pubkey *id; struct peer *peer; @@ -1071,7 +1078,9 @@ static const struct json_command disconnect_command = { AUTODATA(json_command, &disconnect_command); static void json_getinfo(struct command *cmd, - const char *buffer UNUSED, const jsmntok_t *params UNUSED) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { struct json_stream *response; struct peer *peer; @@ -1144,7 +1153,9 @@ AUTODATA(json_command, &getinfo_command); #if DEVELOPER static void json_sign_last_tx(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { struct pubkey *peerid; struct peer *peer; @@ -1191,7 +1202,9 @@ static const struct json_command dev_sign_last_tx = { AUTODATA(json_command, &dev_sign_last_tx); static void json_dev_fail(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { struct pubkey *peerid; struct peer *peer; @@ -1236,7 +1249,9 @@ static void dev_reenable_commit_finished(struct subd *channeld UNUSED, } static void json_dev_reenable_commit(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { struct pubkey *peerid; struct peer *peer; @@ -1325,7 +1340,9 @@ static void process_dev_forget_channel(struct bitcoind *bitcoind UNUSED, command_success(forget->cmd, response); } -static void json_dev_forget_channel(struct command *cmd, const char *buffer, +static void json_dev_forget_channel(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, const jsmntok_t *params) { struct pubkey *peerid; diff --git a/lightningd/peer_htlcs.c b/lightningd/peer_htlcs.c index 3eccba420ee8..bcac32520d81 100644 --- a/lightningd/peer_htlcs.c +++ b/lightningd/peer_htlcs.c @@ -5,19 +5,20 @@ #include #include #include +#include +#include +#include #include +#include #include #include #include #include #include #include -#include #include -#include #include #include -#include #include #include #include @@ -1791,7 +1792,9 @@ void htlcs_reconnect(struct lightningd *ld, #if DEVELOPER -static void json_dev_ignore_htlcs(struct command *cmd, const char *buffer, +static void json_dev_ignore_htlcs(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, const jsmntok_t *params) { struct pubkey *peerid; @@ -1846,8 +1849,10 @@ static void listforwardings_add_forwardings(struct json_stream *response, struct tal_free(forwardings); } -static void json_listforwards(struct command *cmd, const char *buffer, - const jsmntok_t *params) +static void json_listforwards(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { struct json_stream *response; diff --git a/lightningd/ping.c b/lightningd/ping.c index b7471aa5c865..b379b424428f 100644 --- a/lightningd/ping.c +++ b/lightningd/ping.c @@ -1,14 +1,15 @@ #include +#include +#include +#include #include #include #include #include #include #include -#include #include #include -#include #include #include #include @@ -79,7 +80,9 @@ void ping_reply(struct subd *subd, const u8 *msg) } static void json_ping(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { u8 *msg; unsigned int *len, *pongbytes; diff --git a/lightningd/plugin.c b/lightningd/plugin.c index 57bd6079226a..91962aa3d490 100644 --- a/lightningd/plugin.c +++ b/lightningd/plugin.c @@ -1,5 +1,6 @@ #include "lightningd/plugin.h" +#include #include #include #include @@ -8,12 +9,14 @@ #include #include #include +#include +#include #include +#include #include #include #include #include -#include #include #include #include @@ -61,19 +64,14 @@ struct plugin { struct plugin_request { u64 id; - struct plugin *plugin; - - /* Method to be called */ - const char *method; - - /* JSON encoded params, either a dict or an array */ - const char *json_params; - const char *response; - const jsmntok_t *resulttok, *errortok, *toks; struct json_stream *stream; - /* The response handler to be called on success or error */ - void (*cb)(const struct plugin_request *, void *); + /* The response handler to be called when plugin gives us an object. */ + void (*cb)(const struct plugin_request *, + const char *buffer, + const jsmntok_t *toks, + const jsmntok_t *idtok, + void *); void *arg; }; @@ -93,20 +91,6 @@ struct plugins { struct lightningd *ld; }; -/* Represents a pending JSON-RPC request that was forwarded to a - * plugin and is currently waiting for it to return the result. */ -struct plugin_rpc_request { - /* The json-serialized ID as it was passed to us by the - * client, will be used to return the result */ - const char *id; - - const char *method; - const char *params; - - struct plugin *plugin; - struct command *cmd; -}; - /* Simple storage for plugin options inbetween registering them on the * command line and passing them off to the plugin */ struct plugin_opt { @@ -203,25 +187,23 @@ static void PRINTF_FMT(2,3) plugin_kill(struct plugin *plugin, char *fmt, ...) /** * Create the header of a JSON-RPC request and return open stream. * - * This is a partial request, missing the params element, which the - * caller needs to add. We can't open it yet since we don't know - * whether it is supposed to be an object (name-value pairs) or an - * array. + * The caller needs to add the request to req->stream. */ static struct plugin_request * -plugin_request_new_(struct plugin *plugin, const char *method, - void (*cb)(const struct plugin_request *, void *), +plugin_request_new_(struct plugin *plugin, + void (*cb)(const struct plugin_request *, + const char *buffer, + const jsmntok_t *toks, + const jsmntok_t *idtok, + void *), void *arg) { static u64 next_request_id = 0; struct plugin_request *req = tal(plugin, struct plugin_request); - u64 request_id = next_request_id++; - req->id = request_id; - req->method = tal_strdup(req, method); + req->id = next_request_id++; req->cb = cb; req->arg = arg; - req->plugin = plugin; /* We will not concurrently drain, if we do we must set the * writer to non-NULL */ @@ -229,31 +211,27 @@ plugin_request_new_(struct plugin *plugin, const char *method, /* Add to map so we can find it later when routing the response */ uintmap_add(&plugin->plugins->pending_requests, req->id, req); - - json_object_start(req->stream, NULL); - json_add_string(req->stream, "jsonrpc", "2.0"); - json_add_string(req->stream, "method", method); - json_add_u64(req->stream, "id", request_id); return req; } -#define plugin_request_new(plugin, method, cb, arg) \ - plugin_request_new_( \ - (plugin), (method), \ - typesafe_cb_preargs(void, void *, (cb), (arg), \ - const struct plugin_request *), \ +#define plugin_request_new(plugin, cb, arg) \ + plugin_request_new_( \ + (plugin), \ + typesafe_cb_preargs(void, void *, (cb), (arg), \ + const struct plugin_request *, \ + const char *buffer, \ + const jsmntok_t *toks, \ + const jsmntok_t *idtok), \ (arg)) /** * Given a request, send it to the plugin. */ -static void plugin_request_queue(struct plugin_request *req) +static void plugin_request_queue(struct plugin *plugin, + struct plugin_request *req) { - /* Finish the `params` object and submit the request */ - json_object_end(req->stream); /* root element */ - json_stream_append(req->stream, "\n\n"); - *tal_arr_expand(&req->plugin->js_arr) = req->stream; - io_wake(req->plugin); + *tal_arr_expand(&plugin->js_arr) = req->stream; + io_wake(plugin); } /** @@ -267,16 +245,18 @@ static bool plugin_read_json_one(struct plugin *plugin) jsmntok_t *toks; bool valid; u64 id; - const jsmntok_t *idtok, *resulttok, *errortok; + const jsmntok_t *idtok; struct plugin_request *request; /* FIXME: This could be done more efficiently by storing the * toks and doing an incremental parse, like lightning-cli * does. */ - toks = json_parse_input(plugin->buffer, plugin->used, &valid); + toks = json_parse_input(plugin->buffer, plugin->buffer, plugin->used, + &valid); if (!toks) { if (!valid) { - plugin_kill(plugin, "Failed to parse JSON response"); + plugin_kill(plugin, "Failed to parse JSON response '%.*s'", + (int)plugin->used, plugin->buffer); return false; } /* We need more. */ @@ -289,19 +269,15 @@ static bool plugin_read_json_one(struct plugin *plugin) return false; } - resulttok = json_get_member(plugin->buffer, toks, "result"); - errortok = json_get_member(plugin->buffer, toks, "error"); idtok = json_get_member(plugin->buffer, toks, "id"); if (!idtok) { plugin_kill(plugin, "JSON-RPC response does not contain an \"id\"-field"); return false; - } else if (!resulttok && !errortok) { - plugin_kill(plugin, "JSON-RPC response does not contain a \"result\" or \"error\" field"); - return false; } - /* We only send u64 ids, so if this fails it's a critical error */ + /* 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)) { plugin_kill(plugin, "JSON-RPC response \"id\"-field is not a u64"); return false; @@ -315,12 +291,7 @@ static bool plugin_read_json_one(struct plugin *plugin) } /* We expect the request->cb to copy if needed */ - request->response = plugin->buffer; - request->errortok = errortok; - request->resulttok = resulttok; - request->toks = toks; - request->cb(request, request->arg); - + request->cb(request, plugin->buffer, toks, idtok, request->arg); tal_free(request); uintmap_del(&plugin->plugins->pending_requests, id); @@ -477,25 +448,25 @@ static bool plugin_opt_add(struct plugin *plugin, const char *buffer, /* Iterate through the options in the manifest response, and add them * to the plugin and the command line options */ -static bool plugin_opts_add(const struct plugin_request *req) +static bool plugin_opts_add(struct plugin *plugin, + const char *buffer, + const jsmntok_t *resulttok) { - const char *buffer = req->plugin->buffer; - const jsmntok_t *options = - json_get_member(req->plugin->buffer, req->resulttok, "options"); + const jsmntok_t *options = json_get_member(buffer, resulttok, "options"); if (!options) { - plugin_kill(req->plugin, + plugin_kill(plugin, "\"result.options\" was not found in the manifest"); return false; } if (options->type != JSMN_ARRAY) { - plugin_kill(req->plugin, "\"result.options\" is not an array"); + plugin_kill(plugin, "\"result.options\" is not an array"); return false; } for (size_t i = 0; i < options->size; i++) - if (!plugin_opt_add(req->plugin, buffer, json_get_arr(options, i))) + if (!plugin_opt_add(plugin, buffer, json_get_arr(options, i))) return false; return true; @@ -507,91 +478,91 @@ static void plugin_rpcmethod_destroy(struct json_command *cmd, jsonrpc_command_remove(rpc, cmd->name); } +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 (maybe inside a string, we + * don't care) */ + json_stream_append_part(stream, buffer + toks->start, + idtok->start - toks->start); + json_stream_append(stream, new_id); + json_stream_append_part(stream, buffer + idtok->end, + toks->end - idtok->end); + + /* We promise it will end in '\n\n' */ + /* It's an object (with an id!): definitely can't be less that "{}" */ + assert(toks->end - toks->start >= 2); + if (buffer[toks->end-1] != '\n') + json_stream_append(stream, "\n\n"); + else if (buffer[toks->end-2] != '\n') + json_stream_append(stream, "\n"); +} + static void plugin_rpcmethod_cb(const struct plugin_request *req, - struct plugin_rpc_request *rpc_req) + const char *buffer, + const jsmntok_t *toks, + const jsmntok_t *idtok, + struct command *cmd) { struct json_stream *response; - const jsmntok_t *res; - assert(req->resulttok || req->errortok); - - if (req->errortok) { - res = req->errortok; - command_fail(rpc_req->cmd, PLUGIN_ERROR, "%.*s", - res->end - res->start, req->response + res->start); - tal_free(rpc_req); - return; - } - res = req->resulttok; - response = json_stream_success(rpc_req->cmd); + response = json_stream_raw_for_cmd(cmd); + json_stream_forward_change_id(response, buffer, toks, idtok, cmd->id); + command_raw_complete(cmd, response); +} - json_add_member(response, NULL, "%.*s", json_tok_len(res), - json_tok_contents(req->response, res)); +static struct plugin *find_plugin_for_command(struct command *cmd) +{ + struct plugins *plugins = cmd->ld->plugins; + struct plugin *plugin; - command_success(rpc_req->cmd, response); - tal_free(rpc_req); + /* Find the plugin that registered this RPC call */ + list_for_each(&plugins->plugins, plugin, list) { + for (size_t i=0; imethods); i++) { + if (streq(cmd->json_cmd->name, plugin->methods[i])) + return plugin; + } + } + /* This should never happen, it'd mean that a plugin didn't + * cleanup after dying */ + abort(); } static void plugin_rpcmethod_dispatch(struct command *cmd, const char *buffer, - const jsmntok_t *params) + const jsmntok_t *toks, + const jsmntok_t *params UNNEEDED) { - const jsmntok_t *toks = params, *methtok, *idtok; - struct plugin_rpc_request *request; - struct plugins *plugins = cmd->ld->plugins; + const jsmntok_t *idtok; struct plugin *plugin; struct plugin_request *req; + char id[STR_MAX_CHARS(u64)]; if (cmd->mode == CMD_USAGE) { + /* FIXME! */ cmd->usage = "[params]"; return; } - /* We're given the params, but we need to walk back to the - * root object, so just walk backwards until the current - * element has no parents, that's going to be the root - * element. */ - while (toks->parent != -1) - toks--; + plugin = find_plugin_for_command(cmd); - methtok = json_get_member(buffer, toks, "method"); + /* Find ID again (We've parsed them before, this should not fail!) */ idtok = json_get_member(buffer, toks, "id"); - /* We've parsed them before, these should not fail! */ - assert(idtok != NULL && methtok != NULL); - - request = tal(NULL, struct plugin_rpc_request); - request->method = tal_strndup(request, buffer + methtok->start, - methtok->end - methtok->start); - request->id = tal_strndup(request, buffer + idtok->start, - idtok->end - idtok->start); - request->params = tal_strndup(request, buffer + params->start, - params->end - params->start); - request->plugin = NULL; - request->cmd = cmd; + assert(idtok != NULL); - /* Find the plugin that registered this RPC call */ - list_for_each(&plugins->plugins, plugin, list) { - for (size_t i=0; imethods); i++) { - if (streq(request->method, plugin->methods[i])) { - request->plugin = plugin; - goto found; - } - } - } + req = plugin_request_new(plugin, plugin_rpcmethod_cb, cmd); + snprintf(id, ARRAY_SIZE(id), "%"PRIu64, req->id); -found: - /* This should never happen, it'd mean that a plugin didn't - * cleanup after dying */ - assert(request->plugin); - - tal_steal(request->plugin, request); - req = plugin_request_new(request->plugin, request->method, plugin_rpcmethod_cb, request); - json_stream_append_fmt(req->stream, ", \"params\": %s", request->params); - plugin_request_queue(req); + json_stream_forward_change_id(req->stream, buffer, toks, idtok, id); + plugin_request_queue(plugin, req); command_still_pending(cmd); } -static bool plugin_rpcmethod_add(struct plugin *plugin, const char *buffer, +static bool plugin_rpcmethod_add(struct plugin *plugin, + const char *buffer, const jsmntok_t *meth) { const jsmntok_t *nametok, *desctok, *longdesctok; @@ -649,23 +620,24 @@ static bool plugin_rpcmethod_add(struct plugin *plugin, const char *buffer, return true; } -static bool plugin_rpcmethods_add(const struct plugin_request *req) +static bool plugin_rpcmethods_add(struct plugin *plugin, + const char *buffer, + const jsmntok_t *resulttok) { - const char *buffer = req->plugin->buffer; const jsmntok_t *methods = - json_get_member(req->plugin->buffer, req->resulttok, "rpcmethods"); + json_get_member(buffer, resulttok, "rpcmethods"); if (!methods) return false; if (methods->type != JSMN_ARRAY) { - plugin_kill(req->plugin, + plugin_kill(plugin, "\"result.rpcmethods\" is not an array"); return false; } for (size_t i = 0; i < methods->size; i++) - if (!plugin_rpcmethod_add(req->plugin, buffer, + if (!plugin_rpcmethod_add(plugin, buffer, json_get_arr(methods, i))) return false; return true; @@ -680,21 +652,29 @@ static void plugin_manifest_timeout(struct plugin *plugin) /** * Callback for the plugin_manifest request. */ -static void plugin_manifest_cb(const struct plugin_request *req, struct plugin *plugin) +static void plugin_manifest_cb(const struct plugin_request *req, + const char *buffer, + const jsmntok_t *toks, + const jsmntok_t *idtok, + struct plugin *plugin) { + const jsmntok_t *resulttok; + /* Check if all plugins have replied to getmanifest, and break * if they are */ plugin->plugins->pending_manifests--; if (plugin->plugins->pending_manifests == 0) io_break(plugin->plugins); - if (req->resulttok->type != JSMN_OBJECT) { + resulttok = json_get_member(buffer, toks, "result"); + if (!resulttok || resulttok->type != JSMN_OBJECT) { plugin_kill(plugin, - "\"getmanifest\" response is not an object"); + "\"getmanifest\" result is not an object"); return; } - if (!plugin_opts_add(req) || !plugin_rpcmethods_add(req)) + if (!plugin_opts_add(plugin, buffer, resulttok) + || !plugin_rpcmethods_add(plugin, buffer, resulttok)) plugin_kill(plugin, "Failed to register options or methods"); /* Reset timer, it'd kill us otherwise. */ tal_free(plugin->timeout_timer); @@ -725,11 +705,11 @@ static const char *plugin_fullpath(const tal_t *ctx, const char *dir, fullname = path_join(ctx, dir, basename); if (stat(fullname, &st) != 0) return tal_free(fullname); - if (!(st.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) || st.st_mode & S_IFDIR) + /* Only regular files please (or symlinks to such: stat not lstat!) */ + if ((st.st_mode & S_IFMT) != S_IFREG) return tal_free(fullname); - - /* Ignore directories, they have exec mode, but aren't executable. */ - if (st.st_mode & S_IFDIR) + /* Must be executable by someone. */ + if (!(st.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))) return tal_free(fullname); return fullname; } @@ -766,7 +746,23 @@ void clear_plugins(struct plugins *plugins) tal_free(p); } -void plugins_init(struct plugins *plugins) +/* For our own "getmanifest" and "init" requests: starts params[] */ +static void start_simple_request(struct plugin_request *req, const char *reqname) +{ + json_object_start(req->stream, NULL); + json_add_string(req->stream, "jsonrpc", "2.0"); + json_add_string(req->stream, "method", reqname); + json_add_u64(req->stream, "id", req->id); +} + +static void end_simple_request(struct plugin *plugin, struct plugin_request *req) +{ + json_object_end(req->stream); + json_stream_append(req->stream, "\n\n"); + plugin_request_queue(plugin, req); +} + +void plugins_init(struct plugins *plugins, const char *dev_plugin_debug) { struct plugin *p; char **cmd; @@ -778,10 +774,14 @@ void plugins_init(struct plugins *plugins) /* Spawn the plugin processes before entering the io_loop */ list_for_each(&plugins->plugins, p, list) { - cmd = tal_arr(p, char *, 2); + bool debug; + + debug = dev_plugin_debug && strends(p->cmd, dev_plugin_debug); + cmd = tal_arrz(p, char *, 2 + debug); cmd[0] = p->cmd; - cmd[1] = NULL; - p->pid = pipecmdarr(&stdout, &stdin, NULL, cmd); + if (debug) + cmd[1] = "--debugger"; + p->pid = pipecmdarr(&stdin, &stdout, &pipecmd_preserve, cmd); if (p->pid == -1) fatal("error starting plugin '%s': %s", p->cmd, @@ -793,14 +793,21 @@ void plugins_init(struct plugins *plugins) * write-only on p->stdout */ io_new_conn(p, stdout, plugin_stdout_conn_init, p); io_new_conn(p, stdin, plugin_stdin_conn_init, p); - req = plugin_request_new(p, "getmanifest", plugin_manifest_cb, p); + req = plugin_request_new(p, plugin_manifest_cb, p); + start_simple_request(req, "getmanifest"); json_array_start(req->stream, "params"); json_array_end(req->stream); - plugin_request_queue(req); + end_simple_request(p, req); plugins->pending_manifests++; - p->timeout_timer = new_reltimer( - &plugins->timers, p, time_from_sec(PLUGIN_MANIFEST_TIMEOUT), - plugin_manifest_timeout, p); + /* Don't timeout if they're running a debugger. */ + if (debug) + p->timeout_timer = NULL; + else { + p->timeout_timer + = new_reltimer(&plugins->timers, p, + time_from_sec(PLUGIN_MANIFEST_TIMEOUT), + plugin_manifest_timeout, p); + } tal_free(cmd); } @@ -814,6 +821,9 @@ void plugins_init(struct plugins *plugins) } static void plugin_config_cb(const struct plugin_request *req, + const char *buffer, + const jsmntok_t *toks, + const jsmntok_t *idtok, struct plugin *plugin) { /* Nothing to be done here, this is just a report */ @@ -830,7 +840,8 @@ static void plugin_config(struct plugin *plugin) struct lightningd *ld = plugin->plugins->ld; /* No writer since we don't flush concurrently. */ - req = plugin_request_new(plugin, "init", plugin_config_cb, plugin); + req = plugin_request_new(plugin, plugin_config_cb, plugin); + start_simple_request(req, "init"); json_object_start(req->stream, "params"); /* start of .params */ /* Add .params.options */ @@ -849,8 +860,7 @@ static void plugin_config(struct plugin *plugin) json_object_end(req->stream); json_object_end(req->stream); /* end of .params */ - - plugin_request_queue(req); + end_simple_request(plugin, req); } void plugins_config(struct plugins *plugins) diff --git a/lightningd/plugin.h b/lightningd/plugin.h index 2e9582e7b911..40d64caf428d 100644 --- a/lightningd/plugin.h +++ b/lightningd/plugin.h @@ -27,8 +27,10 @@ struct plugins *plugins_new(const tal_t *ctx, struct log_book *log_book, * arguments. In order to read the getmanifest reply from the plugins * we spin up our own io_loop that exits once all plugins have * responded. + * + * The dev_plugin_debug arg comes from --dev-debugger if DEVELOPER. */ -void plugins_init(struct plugins *plugins); +void plugins_init(struct plugins *plugins, const char *dev_plugin_debug); /** * Register a plugin for initialization and execution. diff --git a/lightningd/subd.c b/lightningd/subd.c index 41bb0dd17865..62c9361be098 100644 --- a/lightningd/subd.c +++ b/lightningd/subd.c @@ -626,7 +626,7 @@ static struct subd *new_subd(struct lightningd *ld, assert(name != NULL); #if DEVELOPER - debug_subd = ld->dev_debug_subdaemon; + debug_subd = ld->dev_debug_subprocess; disconnect_fd = ld->dev_disconnect_fd; #endif /* DEVELOPER */ @@ -784,12 +784,6 @@ void subd_release_channel(struct subd *owner, void *channel) } #if DEVELOPER -char *opt_subd_debug(const char *optarg, struct lightningd *ld) -{ - ld->dev_debug_subdaemon = optarg; - return NULL; -} - char *opt_subd_dev_disconnect(const char *optarg, struct lightningd *ld) { ld->dev_disconnect_fd = open(optarg, O_RDONLY); diff --git a/lightningd/subd.h b/lightningd/subd.h index 81730ef813f0..2cfaf395752b 100644 --- a/lightningd/subd.h +++ b/lightningd/subd.h @@ -206,7 +206,6 @@ void subd_shutdown(struct subd *subd, unsigned int seconds); const char *find_my_abspath(const tal_t *ctx, const char *argv0); #if DEVELOPER -char *opt_subd_debug(const char *optarg, struct lightningd *ld); char *opt_subd_dev_disconnect(const char *optarg, struct lightningd *ld); bool dev_disconnect_permanent(struct lightningd *ld); diff --git a/lightningd/test/Makefile b/lightningd/test/Makefile index 2941065087c3..f0b7c3504d84 100644 --- a/lightningd/test/Makefile +++ b/lightningd/test/Makefile @@ -13,6 +13,7 @@ LIGHTNINGD_TEST_COMMON_OBJS := \ common/htlc_state.o \ common/io_lock.o \ common/json.o \ + common/json_escaped.o \ common/key_derive.o \ common/pseudorand.o \ common/memleak.o \ diff --git a/lightningd/test/run-find_my_abspath.c b/lightningd/test/run-find_my_abspath.c index 143f211cf511..47c5e7ed4086 100644 --- a/lightningd/test/run-find_my_abspath.c +++ b/lightningd/test/run-find_my_abspath.c @@ -91,9 +91,6 @@ void hsm_init(struct lightningd *ld UNNEEDED) /* Generated stub for htlcs_notify_new_block */ void htlcs_notify_new_block(struct lightningd *ld UNNEEDED, u32 height UNNEEDED) { fprintf(stderr, "htlcs_notify_new_block called!\n"); abort(); } -/* Generated stub for json_escape */ -struct json_escaped *json_escape(const tal_t *ctx UNNEEDED, const char *str TAKES UNNEEDED) -{ fprintf(stderr, "json_escape called!\n"); abort(); } /* Generated stub for jsonrpc_listen */ void jsonrpc_listen(struct jsonrpc *rpc UNNEEDED, struct lightningd *ld UNNEEDED) { fprintf(stderr, "jsonrpc_listen called!\n"); abort(); } @@ -136,7 +133,7 @@ void onchaind_replay_channels(struct lightningd *ld UNNEEDED) void plugins_config(struct plugins *plugins UNNEEDED) { fprintf(stderr, "plugins_config called!\n"); abort(); } /* Generated stub for plugins_init */ -void plugins_init(struct plugins *plugins UNNEEDED) +void plugins_init(struct plugins *plugins UNNEEDED, const char *dev_plugin_debug UNNEEDED) { fprintf(stderr, "plugins_init called!\n"); abort(); } /* Generated stub for plugins_new */ struct plugins *plugins_new(const tal_t *ctx UNNEEDED, struct log_book *log_book UNNEEDED, diff --git a/lightningd/test/run-invoice-select-inchan.c b/lightningd/test/run-invoice-select-inchan.c index f4953520d134..58205b817029 100644 --- a/lightningd/test/run-invoice-select-inchan.c +++ b/lightningd/test/run-invoice-select-inchan.c @@ -164,9 +164,6 @@ void json_array_end(struct json_stream *js UNNEEDED) /* Generated stub for json_array_start */ void json_array_start(struct json_stream *js UNNEEDED, const char *fieldname UNNEEDED) { fprintf(stderr, "json_array_start called!\n"); abort(); } -/* Generated stub for json_escape */ -struct json_escaped *json_escape(const tal_t *ctx UNNEEDED, const char *str TAKES UNNEEDED) -{ fprintf(stderr, "json_escape called!\n"); abort(); } /* Generated stub for json_object_end */ void json_object_end(struct json_stream *js UNNEEDED) { fprintf(stderr, "json_object_end called!\n"); abort(); } diff --git a/lightningd/test/run-jsonrpc.c b/lightningd/test/run-jsonrpc.c index 146ae5d5f13a..7b1e787f43d6 100644 --- a/lightningd/test/run-jsonrpc.c +++ b/lightningd/test/run-jsonrpc.c @@ -1,4 +1,3 @@ -#include "../json_escaped.c" #include "../json_stream.c" #include "../jsonrpc.c" #include "../json.c" @@ -26,6 +25,21 @@ char *fmt_wireaddr_without_port(const tal_t *ctx UNNEEDED, const struct wireaddr bool json_feerate_estimate(struct command *cmd UNNEEDED, u32 **feerate_per_kw UNNEEDED, enum feerate feerate UNNEEDED) { fprintf(stderr, "json_feerate_estimate called!\n"); abort(); } +/* Generated stub for json_tok_number */ +bool json_tok_number(struct command *cmd UNNEEDED, const char *name UNNEEDED, + const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + unsigned int **num UNNEEDED) +{ fprintf(stderr, "json_tok_number called!\n"); abort(); } +/* Generated stub for json_tok_sha256 */ +bool json_tok_sha256(struct command *cmd UNNEEDED, const char *name UNNEEDED, + const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + struct sha256 **hash UNNEEDED) +{ fprintf(stderr, "json_tok_sha256 called!\n"); abort(); } +/* Generated stub for json_tok_tok */ +bool json_tok_tok(struct command *cmd UNNEEDED, const char *name UNNEEDED, + const char *buffer UNNEEDED, const jsmntok_t * tok UNNEEDED, + const jsmntok_t **out UNNEEDED) +{ fprintf(stderr, "json_tok_tok called!\n"); abort(); } /* Generated stub for log_ */ void log_(struct log *log UNNEEDED, enum log_level level UNNEEDED, const char *fmt UNNEEDED, ...) @@ -76,7 +90,7 @@ static int test_json_filter(void) str = tal_strndup(result, membuf_elems(&result->outbuf), membuf_num_elems(&result->outbuf)); - toks = json_parse_input(str, strlen(str), &valid); + toks = json_parse_input(str, str, strlen(str), &valid); assert(valid); assert(toks); @@ -174,7 +188,7 @@ static void test_json_stream(void) * timeout. */ input = "{\"x\":\"x\"}{\"y\":\"y\"}"; talstr = tal_strndup(NULL, input, strlen(input)); - toks = json_parse_input(talstr, strlen(talstr), &valid); + toks = json_parse_input(talstr, talstr, strlen(talstr), &valid); assert(toks); assert(tal_count(toks) == 4); assert(toks[0].start == 0 && toks[0].end == 9); @@ -185,7 +199,7 @@ static void test_json_stream(void) * accidentally getting the boundaries to match. */ input = "{\"x\":\"x\"}{\"y\":\"y\"}{\"z\":\"z"; talstr = tal_strndup(NULL, input, strlen(input)); - toks = json_parse_input(talstr, strlen(talstr), &valid); + toks = json_parse_input(talstr, talstr, strlen(talstr), &valid); assert(toks); assert(tal_count(toks) == 4); assert(toks[0].start == 0 && toks[0].end == 9); diff --git a/wallet/db.c b/wallet/db.c index 6b4574e29ea0..a7523161f6d4 100644 --- a/wallet/db.c +++ b/wallet/db.c @@ -1,9 +1,9 @@ #include "db.h" #include +#include #include #include -#include #include #include diff --git a/wallet/walletrpc.c b/wallet/walletrpc.c index 5f4a89838aaa..e2410c7e0dae 100644 --- a/wallet/walletrpc.c +++ b/wallet/walletrpc.c @@ -3,7 +3,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -16,10 +19,8 @@ #include #include #include -#include #include #include -#include #include #include #include @@ -85,7 +86,9 @@ static void wallet_withdrawal_broadcast(struct bitcoind *bitcoind UNUSED, * the HSM to generate the signatures. */ static void json_withdraw(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { const jsmntok_t *desttok, *sattok; struct withdrawal *withdraw = tal(cmd, struct withdrawal); @@ -239,8 +242,10 @@ static bool json_tok_newaddr(struct command *cmd, const char *name, return false; } -static void json_newaddr(struct command *cmd, const char *buffer UNUSED, - const jsmntok_t *params UNUSED) +static void json_newaddr(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { struct json_stream *response; struct ext_key ext; @@ -299,7 +304,9 @@ static const struct json_command newaddr_command = { AUTODATA(json_command, &newaddr_command); static void json_listaddrs(struct command *cmd, - const char *buffer, const jsmntok_t *params) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { struct json_stream *response; struct ext_key ext; @@ -376,8 +383,10 @@ static const struct json_command listaddrs_command = { }; AUTODATA(json_command, &listaddrs_command); -static void json_listfunds(struct command *cmd, const char *buffer UNUSED, - const jsmntok_t *params UNUSED) +static void json_listfunds(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { struct json_stream *response; struct peer *p; @@ -503,8 +512,9 @@ static void process_utxo_result(struct bitcoind *bitcoind, } static void json_dev_rescan_outputs(struct command *cmd, - const char *buffer UNUSED, - const jsmntok_t *params UNUSED) + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) { struct txo_rescan *rescan = tal(cmd, struct txo_rescan);