From e1e7e554097dee490cc72f706e5a5986981f4651 Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Wed, 2 May 2012 20:20:37 +0300 Subject: [PATCH] plugins: added a TCP-transport based console plugin. --- configure.ac | 1 + src/Makefile.am | 18 +++ src/plugins/plugin-console.c | 318 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 337 insertions(+) create mode 100644 src/plugins/plugin-console.c diff --git a/configure.ac b/configure.ac index cd81a35..c743401 100644 --- a/configure.ac +++ b/configure.ac @@ -172,6 +172,7 @@ function check_if_internal() { AM_CONDITIONAL(BUILTIN_PLUGIN_TEST, [check_if_internal test]) AM_CONDITIONAL(BUILTIN_PLUGIN_DBUS, [check_if_internal dbus]) AM_CONDITIONAL(BUILTIN_PLUGIN_GLIB, [check_if_internal glib]) +AM_CONDITIONAL(BUILTIN_PLUGIN_CONSOLE, [check_if_internal console]) # Check for Check (unit test framework). PKG_CHECK_MODULES(CHECK, diff --git a/src/Makefile.am b/src/Makefile.am index f981952..4f30ad5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -187,6 +187,24 @@ plugin_glib_la_LIBADD = $(GLIB_PLUGIN_LIBS) plugin_LTLIBRARIES += plugin-glib.la endif +# console plugin +CONSOLE_PLUGIN_SOURCES = plugins/plugin-console.c +CONSOLE_PLUGIN_CFLAGS = +CONSOLE_PLUGIN_LIBS = + +if BUILTIN_PLUGIN_CONSOLE +BUILTIN_PLUGINS += $(CONSOLE_PLUGIN_SOURCES) +BUILTIN_CFLAGS += $(CONSOLE_PLUGIN_CFLAGS) +BUILTIN_LIBS += $(CONSOLE_PLUGIN_LIBS) +else +plugin_console_la_SOURCES = $(CONSOLE_PLUGIN_SOURCES) +plugin_console_la_CFLAGS = $(CONSOLE_PLUGIN_CFLAGS) \ + $(MURPHY_CFLAGS) $(AM_CFLAGS) +plugin_console_la_LDFLAGS = -module -avoid-version +plugin_console_la_LIBADD = $(CONSOLE_PLUGIN_LIBS) + +plugin_LTLIBRARIES += plugin-console.la +endif ################################### # murphy daemon diff --git a/src/plugins/plugin-console.c b/src/plugins/plugin-console.c new file mode 100644 index 0000000..f17d055 --- /dev/null +++ b/src/plugins/plugin-console.c @@ -0,0 +1,318 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define console_info(fmt, args...) mrp_log_info("console: "fmt , ## args) +#define console_warn(fmt, args...) mrp_log_warning("console: "fmt , ## args) +#define console_error(fmt, args...) mrp_log_error("console: "fmt , ## args) + +#define MRP_CFG_MAXLINE 4096 /* input line length limit */ + +/* + * console plugin data + */ + +typedef struct { + const char *address; /* console address */ + int sock; /* main socket for new connections */ + mrp_io_watch_t *iow; /* main socket I/O watch */ + mrp_context_t *ctx; /* murphy context */ + mrp_list_hook_t consoles; /* active consoles */ +} data_t; + + +/* + * a console instance + */ + +typedef struct { + mrp_console_t *mc; /* associated murphy console */ + mrp_transport_t *t; /* associated transport */ +} console_t; + + +static int console_listen(const char *address) +{ + struct addrinfo *ai; + char node[512], *port; + int sock, error, on; + + strncpy(node, address, sizeof(node) - 1); + node[sizeof(node) - 1] = '\0'; + + if ((port = strrchr(node, ':')) == NULL) { + console_error("invalid console address '%s'.", address); + return FALSE; + } + *port++ = '\0'; + + if ((error = getaddrinfo(node, port, NULL, &ai)) != 0) { + console_error("invalid console address '%s' (%s).", + address, gai_strerror(error)); + return FALSE; + } + + if ((sock = socket(ai->ai_family, SOCK_STREAM, 0)) < 0) { + console_error("failed to create console socket (%d: %s).", + errno, strerror(errno)); + goto fail; + } + + on = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + if (bind(sock, ai->ai_addr, ai->ai_addrlen) != 0) { + console_error("failed to bind to address '%s' (%d: %s).", + address, errno, strerror(errno)); + goto fail; + } + + if (listen(sock, 4) < 0) { + console_error("failed to listen for connections (%d: %s).", + errno, strerror(errno)); + goto fail; + } + + freeaddrinfo(ai); + return sock; + + fail: + if (sock >= 0) + close(sock); + + if (ai != NULL) + freeaddrinfo(ai); + + return FALSE; +} + + +static ssize_t write_req(mrp_console_t *mc, void *buf, size_t size) +{ + console_t *c = (console_t *)mc->backend_data; + mrp_msg_t *msg; + + if ((msg = mrp_msg_create("output", buf, size, NULL)) != NULL) { + mrp_transport_send(c->t, msg); + mrp_msg_unref(msg); + + return size; + } + else + return -1; +} + + +static void close_req(mrp_console_t *mc) +{ + console_t *c = (console_t *)mc->backend_data; + + mrp_transport_disconnect(c->t); + mrp_transport_destroy(c->t); + c->t = NULL; +} + + +static void set_prompt_req(mrp_console_t *mc, const char *prompt) +{ + console_t *c = (console_t *)mc->backend_data; + mrp_msg_t *msg; + + msg = mrp_msg_create("prompt", prompt, strlen(prompt) + 1, NULL); + + if (msg != NULL) { + mrp_transport_send(c->t, msg); + mrp_msg_unref(msg); + } +} + + +static void free_req(void *backend_data) +{ + mrp_free(backend_data); +} + + +static void recv_evt(mrp_transport_t *t, mrp_msg_t *msg, void *user_data) +{ + console_t *c = (console_t *)user_data; + char *input; + size_t size; + + MRP_UNUSED(t); + + input = mrp_msg_find(msg, "input", &size); + + if (input != NULL) { + MRP_CONSOLE_BUSY(c->mc, { + c->mc->evt.input(c->mc, input, size); + }); + + c->mc->check_destroy(c->mc); + } + else + mrp_log_error("Received malformed console message."); + +#if 0 /* done by the transport layer... */ + mrp_msg_unref(msg); /* XXX TODO change to refcounting */ +#endif +} + + +static void closed_evt(mrp_transport_t *t, int error, void *user_data) +{ + console_t *c = (console_t *)user_data; + + if (error) + mrp_log_error("Connection closed with error %d (%s).", error, + strerror(error)); + else { + mrp_log_info("Peer has closed the console connection."); + + mrp_transport_disconnect(t); + mrp_transport_destroy(t); + c->t = NULL; + } +} + + +static void accept_cb(mrp_mainloop_t *ml, mrp_io_watch_t *iow, int fd, + mrp_io_event_t events, void *user_data) +{ + static mrp_transport_evt_t evt = { + .recv = recv_evt, + .recvfrom = NULL, + .closed = closed_evt, + }; + + static mrp_console_req_t req = { + .write = write_req, + .close = close_req, + .free = free_req, + .set_prompt = set_prompt_req, + }; + + data_t *data = (data_t *)user_data; + console_t *c; + + MRP_UNUSED(iow); + + c = NULL; + + if (events & MRP_IO_EVENT_IN) { + if ((c = mrp_allocz(sizeof(*c))) != NULL) { + c->t = mrp_transport_accept(ml, "tcp", &fd, &evt, c); + + if (c->t != NULL) { + c->mc = mrp_create_console(data->ctx, &req, c); + + if (c->mc != NULL) { + console_info("accepted new console connection."); + return; + } + else + console_error("failed to create new console."); + } + else + console_error("failed to accept console connection."); + } + else + console_error("failed to allocate new console."); + + if (c != NULL) { + if (c->t != NULL) + mrp_transport_destroy(c->t); + + mrp_free(c); + } + } +} + + +enum { + ARG_ADDRESS /* console address, 'address:port' */ +}; + + +static int console_init(mrp_plugin_t *plugin) +{ + data_t *data; + mrp_mainloop_t *ml; + mrp_io_event_t events; + + if ((data = mrp_allocz(sizeof(*data))) != NULL) { + mrp_list_init(&data->consoles); + + data->ctx = plugin->ctx; + data->address = plugin->args[ARG_ADDRESS].str; + data->sock = console_listen(data->address); + + if (data->sock < 0) + goto fail; + + ml = data->ctx->ml; + events = MRP_IO_EVENT_IN | MRP_IO_EVENT_HUP | MRP_IO_EVENT_ERR; + data->iow = mrp_add_io_watch(ml, data->sock, events, accept_cb, data); + + if (data->iow == NULL) { + console_error("failed to set up console I/O watch."); + goto fail; + } + + plugin->data = data; + console_info("set up at address '%s'.", data->address); + + return TRUE; + } + + fail: + if (data != NULL) { + if (data->sock >= 0) + close(data->sock); + + mrp_free(data); + } + + console_error("failed to set up console at address '%s'.", + plugin->args[ARG_ADDRESS].str); + + return FALSE; +} + + +static void console_exit(mrp_plugin_t *plugin) +{ + console_info("cleaning up instance '%s'...", plugin->instance); +} + + +#define CONSOLE_DESCRIPTION "A debug console for Murphy." +#define CONSOLE_HELP \ + "The debug console provides a telnet-like remote session and a\n" \ + "simple shell-like command interpreter with commands to help\n" \ + "development, debugging, and trouble-shooting. The set of commands\n" \ + "can be dynamically extended by registering new commands from\n" \ + "other plugins." + +#define CONSOLE_VERSION MRP_VERSION_INT(0, 0, 1) +#define CONSOLE_AUTHORS "Krisztian Litkey " + + +static mrp_plugin_arg_t console_args[] = { + MRP_PLUGIN_ARGIDX(ARG_ADDRESS, STRING, "address", "127.0.0.1:3000"), +}; + +MURPHY_REGISTER_CORE_PLUGIN("console", + CONSOLE_VERSION, CONSOLE_DESCRIPTION, + CONSOLE_AUTHORS, CONSOLE_HELP, MRP_SINGLETON, + console_init, console_exit, console_args); + + -- 2.7.4