From faebb87d015fd73f623f1e62ef16976a58142e5e Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 10 Jan 2017 15:38:33 +1030 Subject: [PATCH] lightningd/subdaemon: routines to create daemons and get request/response. Signed-off-by: Rusty Russell --- lightningd/Makefile | 6 +- lightningd/subdaemon.c | 346 +++++++++++++++++++++++++++++++++++++++++ lightningd/subdaemon.h | 83 ++++++++++ 3 files changed, 433 insertions(+), 2 deletions(-) create mode 100644 lightningd/subdaemon.c create mode 100644 lightningd/subdaemon.h diff --git a/lightningd/Makefile b/lightningd/Makefile index e85c9160e..e34c7c5a4 100644 --- a/lightningd/Makefile +++ b/lightningd/Makefile @@ -21,14 +21,16 @@ LIGHTNINGD_LIB_SRC := \ LIGHTNINGD_LIB_OBJS := $(LIGHTNINGD_LIB_SRC:.c=.o) LIGHTNINGD_SRC := \ - lightningd/lightningd.c + lightningd/lightningd.c \ + lightningd/subdaemon.c LIGHTNINGD_OBJS := $(LIGHTNINGD_SRC:.c=.o) LIGHTNINGD_JSMN_OBJS := daemon/jsmn.o LIGHTNINGD_JSMN_HEADERS := daemon/jsmn/jsmn.h -LIGHTNINGD_HEADERS := lightningd/lightningd.h +LIGHTNINGD_HEADERS := lightningd/lightningd.h \ + lightningd/subdaemon.h $(LIGHTNINGD_OBJS): $(LIGHTNINGD_HEADERS) $(LIGHTNINGD_JSMN_HEADERS) $(BITCOIN_HEADERS) $(CORE_HEADERS) $(GEN_HEADERS) $(CCAN_HEADERS) $(DAEMON_HEADERS) $(LIBBASE58_HEADERS) diff --git a/lightningd/subdaemon.c b/lightningd/subdaemon.c new file mode 100644 index 000000000..074fd062a --- /dev/null +++ b/lightningd/subdaemon.c @@ -0,0 +1,346 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* A single request/response for subdaemon. */ +struct subdaemon_req { + struct subdaemon *sd; + + /* In sd->reqs */ + struct list_node list; + + /* Request message. */ + const u8 *msg_out; + int fd_out; + + /* Response */ + u8 *req_in; + int *fd_in; + + /* Callback when response comes in. */ + void (*req)(struct subdaemon *, const u8 *msg_in, void *req_data); + void *req_data; +}; + +static bool move_fd(int from, int to) +{ + if (dup2(from, to) == -1) + return false; + close(from); + return true; +} + +/* We use sockets, not pipes, because fds are bidir. */ +static int subdaemon(const char *dir, const char *name, + int *statusfd, int *reqfd, va_list ap) +{ + int childreq[2], childstatus[2], execfail[2]; + pid_t childpid; + int err, fd; + + if (socketpair(AF_LOCAL, SOCK_STREAM, 0, childstatus) != 0) + goto fail; + + if (reqfd) { + if (socketpair(AF_LOCAL, SOCK_STREAM, 0, childreq) != 0) + goto close_childstatus_fail; + } else { + childreq[0] = open("/dev/null", O_RDONLY); + if (childreq[0] < 0) + goto close_childstatus_fail; + } + + if (pipe(execfail) != 0) + goto close_reqfd_fail; + + if (fcntl(execfail[1], F_SETFD, fcntl(execfail[1], F_GETFD) + | FD_CLOEXEC) < 0) + goto close_execfail_fail; + + childpid = fork(); + if (childpid < 0) + goto close_execfail_fail; + + if (childpid == 0) { + int fdnum = 4; + + if (reqfd) + close(childreq[0]); + close(childstatus[0]); + close(execfail[0]); + + // Status = STDOUT + if (childstatus[1] != STDOUT_FILENO) { + if (!move_fd(childstatus[1], STDOUT_FILENO)) + goto child_errno_fail; + } + // Req = STDIN. + if (childreq[1] != STDIN_FILENO) { + if (!move_fd(childreq[1], STDIN_FILENO)) + goto child_errno_fail; + } + /* Dup any extra fds up first. */ + while ((fd = va_arg(ap, int)) != -1) { + /* If these were stdin or stdout, dup2 closed! */ + assert(fd != STDIN_FILENO); + assert(fd != STDOUT_FILENO); + if (!move_fd(fd, fdnum)) + goto child_errno_fail; + fdnum++; + } + execl(path_join(NULL, dir, name), name, NULL); + + child_errno_fail: + err = errno; + /* Gcc's warn-unused-result fail. */ + if (write(execfail[1], &err, sizeof(err))) { + ; + } + exit(127); + } + + if (reqfd) + close(childreq[1]); + close(childstatus[1]); + close(execfail[1]); + + while ((fd = va_arg(ap, int)) != -1) + close(fd); + + /* Child will close this without writing on successful exec. */ + if (read(execfail[0], &err, sizeof(err)) == sizeof(err)) { + close(execfail[0]); + waitpid(childpid, NULL, 0); + errno = err; + return -1; + } + close(execfail[0]); + *statusfd = childstatus[0]; + if (reqfd) + *reqfd = childreq[0]; + return childpid; + +close_execfail_fail: + close_noerr(execfail[0]); + close_noerr(execfail[1]); +close_reqfd_fail: + if (reqfd) + close_noerr(childreq[1]); + close_noerr(childreq[0]); +close_childstatus_fail: + close_noerr(childstatus[0]); + close_noerr(childstatus[1]); +fail: + return -1; +} + +static struct io_plan *status_read(struct io_conn *conn, struct subdaemon *sd); + +static struct io_plan *status_process(struct io_conn *conn, struct subdaemon *sd) +{ + int type = fromwire_peektype(sd->status_in); + const char *str; + int str_len; + + if (type == -1) { + log_unusual(sd->log, "ERROR: Invalid status output"); + return io_close(conn); + } + + /* If it's a string. */ + str_len = tal_count(sd->status_in) - sizeof(be16); + str = (const char *)sd->status_in + sizeof(be16); + + if (type == STATUS_TRACE) + log_debug(sd->log, "TRACE: %.*s", str_len, str); + else if (type & STATUS_FAIL) + log_unusual(sd->log, "FAILURE %s: %.*s", + sd->statusname(type), str_len, str); + else { + log_info(sd->log, "UPDATE %s", sd->statusname(type)); + tal_free(sd->last_status); + /* Keep last status around. */ + sd->last_status = tal_steal(sd, sd->status_in); + sd->status_in = NULL; + } + sd->status_in = tal_free(sd->status_in); + return status_read(conn, sd); +} + +static struct io_plan *status_read(struct io_conn *conn, struct subdaemon *sd) +{ + return io_read_wire(conn, sd, &sd->status_in, status_process, sd); +} + +static struct io_plan *req_next(struct io_conn *conn, struct subdaemon *sd); + +static void destroy_subdaemon(struct subdaemon *sd) +{ + int status; + + switch (waitpid(sd->pid, &status, WNOHANG)) { + case 0: + log_debug(sd->log, "Status closed, but not exited. Killing"); + kill(sd->pid, SIGKILL); + waitpid(sd->pid, &status, 0); + break; + case -1: + log_unusual(sd->log, "Status closed, but waitpid %i says %s", + sd->pid, strerror(errno)); + status = -1; + break; + } + if (sd->finished) + sd->finished(sd, status); +} + +struct subdaemon *new_subdaemon(const tal_t *ctx, + struct lightningd *ld, + const char *name, + const char *(*statusname)(int status), + const char *(*reqname)(int req), + void (*finished)(struct subdaemon *, int), + ...) +{ + va_list ap; + struct subdaemon *sd = tal(ctx, struct subdaemon); + int req_fd, status_fd; + + va_start(ap, finished); + sd->pid = subdaemon(ld->daemon_dir, name, &status_fd, + reqname ? &req_fd : NULL, ap); + va_end(ap); + if (sd->pid == (pid_t)-1) { + log_unusual(ld->log, "subdaemon %s failed: %s", + name, strerror(errno)); + return tal_free(sd); + } + sd->ld = ld; + sd->log = new_log(sd, ld->dstate.log_book, "%s(%u):", name, sd->pid); + sd->name = name; + sd->finished = finished; + sd->statusname = statusname; + sd->last_status = NULL; + list_head_init(&sd->reqs); + tal_add_destructor(sd, destroy_subdaemon); + + /* Status conn actually owns daemon: we die when it does. */ + sd->status_conn = io_new_conn(ctx, status_fd, status_read, sd); + tal_steal(sd->status_conn, sd); + + sd->reqname = reqname; + if (reqname) + sd->req_conn = io_new_conn(sd, req_fd, req_next, sd); + else + sd->req_conn = NULL; + log_info(sd->log, "pid %u, statusfd %i, reqfd %i", + sd->pid, status_fd, req_fd); + + return sd; +} + +static struct io_plan *req_finished_reply(struct io_conn *conn, + struct subdaemon_req *sr) +{ + struct subdaemon *sd = sr->sd; + sr->req(sd, sr->req_in, sr->req_data); + tal_free(sr); + return req_next(conn, sd); +} + +static struct io_plan *req_process_replymsg(struct io_conn *conn, + struct subdaemon_req *sr) +{ + int type = fromwire_peektype(sr->req_in); + + if (type == -1) { + log_unusual(sr->sd->log, "ERROR: Invalid request output"); + return io_close(conn); + } + log_debug(sr->sd->log, "Received req response %s len %zu", + sr->sd->reqname(type), tal_count(sr->req_in)); + + /* If we're supposed to recv an fd, do it now. */ + if (sr->fd_in) + return io_recv_fd(conn, sr->fd_in, req_finished_reply, sr); + return req_finished_reply(conn, sr); +} + +static struct io_plan *req_read_reply(struct io_conn *conn, + struct subdaemon_req *sr) +{ + /* No callback? Don't expect reply. */ + if (!sr->req) { + struct subdaemon *sd = sr->sd; + tal_free(sr); + return req_next(conn, sd); + } + return io_read_wire(conn, sr, &sr->req_in, req_process_replymsg, sr); +} + +static struct io_plan *req_close_fd_out(struct io_conn *conn, + struct subdaemon_req *sr) +{ + close(sr->fd_out); + return req_read_reply(conn, sr); +} + +static struct io_plan *req_sent_msg(struct io_conn *conn, + struct subdaemon_req *sr) +{ + /* If we're supposed to pass an fd, do it now. */ + if (sr->fd_out >= 0) + return io_send_fd(conn, sr->fd_out, req_close_fd_out, sr); + return req_read_reply(conn, sr); +} + +static struct io_plan *req_next(struct io_conn *conn, struct subdaemon *sd) +{ + struct subdaemon_req *sr; + + sr = list_pop(&sd->reqs, struct subdaemon_req, list); + if (!sr) + return io_wait(conn, sd, req_next, sd); + log_debug(sd->log, "Sending req %s len %zu", + sd->reqname(fromwire_peektype(sr->msg_out)), + tal_count(sr->msg_out)); + + return io_write_wire(conn, sr->msg_out, req_sent_msg, sr); +} + +void subdaemon_req_(struct subdaemon *sd, + const u8 *msg_out, int fd_out, int *fd_in, + void (*reqcb)(struct subdaemon *, const u8 *, void *), + void *reqcb_data) +{ + struct subdaemon_req *sr = tal(sd, struct subdaemon_req); + + assert(sd->req_conn); + + sr->sd = sd; + if (msg_out) + sr->msg_out = tal_dup_arr(sr, u8, msg_out, tal_count(msg_out), 0); + else + sr->msg_out = NULL; + sr->fd_out = fd_out; + sr->fd_in = fd_in; + sr->req = reqcb; + sr->req_data = reqcb_data; + list_add_tail(&sd->reqs, &sr->list); + io_wake(sd); +} diff --git a/lightningd/subdaemon.h b/lightningd/subdaemon.h new file mode 100644 index 000000000..de2b4414f --- /dev/null +++ b/lightningd/subdaemon.h @@ -0,0 +1,83 @@ +#ifndef LIGHTNING_LIGHTNINGD_SUBDAEMON_H +#define LIGHTNING_LIGHTNINGD_SUBDAEMON_H +#include "config.h" +#include +#include +#include +#include + +struct io_conn; + +/* One of our subdaemons. */ +struct subdaemon { + /* Name, like John, or "lightningd_hsm" */ + const char *name; + /* The Big Cheese. */ + struct lightningd *ld; + /* pid, for waiting for status when it dies. */ + int pid; + /* Connection for status (read, then write) */ + struct io_conn *status_conn; + /* Connection for requests if any (write, then read) */ + struct io_conn *req_conn; + + /* For logging */ + struct log *log; + + const char *(*statusname)(int status); + const char *(*reqname)(int req); + void (*finished)(struct subdaemon *sd, int status); + + /* Buffer for input. */ + u8 *status_in; + + /* Status handler puts last status msg here. */ + u8 *last_status; + + /* Requests queue up here. */ + struct list_head reqs; +}; + +/** + * new_subdaemon - create a new subdaemon. + * @ctx: context to allocate from + * @ld: global state + * @name: basename of daemon + * @statusname: function to get name from status messages + * @reqname: function to get name from request messages, or NULL if no requests. + * @finished: function to call when it's finished (with exit status). + * @...: the fds to hand as fd 3, 4... terminated with -1. + * + * You should free it from finished(). + */ +struct subdaemon *new_subdaemon(const tal_t *ctx, + struct lightningd *ld, + const char *name, + const char *(*statusname)(int status), + const char *(*reqname)(int req), + void (*finished)(struct subdaemon *, int), ...); + +/** + * subdaemon_req - add a request to the subdaemon. + * @sd: subdaemon to request + * @msg_out: request message (can be take, can be NULL for fd passing only) + * @fd_out: if >=0 fd to pass at the end of the message (closed after) + * @fd_in: if not NULL, where to put fd read in at end of reply. + * @reqcb: callback when reply comes in + * @reqcb_data: final arg to hand to @reqcb + * + * The subdaemon must take requests. + */ +#define subdaemon_req(sd, msg_out, fd_out, fd_in, reqcb, reqcb_data) \ + subdaemon_req_((sd), (msg_out), (fd_out), (fd_in), \ + typesafe_cb_preargs(void, void *, \ + (reqcb), (reqcb_data), \ + struct subdaemon *, \ + const u8 *), \ + (reqcb_data)) +void subdaemon_req_(struct subdaemon *sd, + const u8 *msg_out, + int fd_out, int *fd_in, + void (*reqcb)(struct subdaemon *, const u8 *, void *), + void *reqcb_data); +#endif /* LIGHTNING_LIGHTNINGD_SUBDAEMON_H */