libmurphy_core_la_HEADERS = \
core/context.h \
- core/plugin.h
+ core/plugin.h \
+ core/console-command.h \
+ core/console.h
libmurphy_core_la_SOURCES = \
core/context.c \
- core/plugin.c
+ core/plugin.c \
+ core/console.c
libmurphy_core_la_CFLAGS = \
$(AM_CFLAGS)
--- /dev/null
+
+
+/*
+ * top-level console commands
+ */
+
+
+#define NPRINT(mc, fmt, args...) fprintf(mc->stdout, fmt , ## args)
+#define EPRINT(mc, fmt, args...) fprintf(mc->stderr, fmt , ## args)
+
+
+#define DOTS "........................................................" \
+ "......................."
+
+static void help_overview(mrp_console_t *mc)
+{
+ mrp_console_group_t *grp;
+ mrp_console_cmd_t *cmd;
+ mrp_list_hook_t *p, *n;
+ int i, l, dend;
+ size_t nlen, slen, tmax, nmax, smax;
+
+ tmax = nmax = smax = 0;
+ mrp_list_foreach(&mc->ctx->cmd_groups, p, n) {
+ grp = mrp_list_entry(p, typeof(*grp), hook);
+
+ for (i = 0, cmd = grp->commands; i < grp->ncommand; i++, cmd++) {
+ nlen = strlen(cmd->name);
+ slen = strlen(cmd->summary);
+ nmax = MRP_MAX(nmax, nlen);
+ smax = MRP_MAX(smax, slen);
+ tmax = MRP_MAX(tmax, nlen + slen);
+
+
+ }
+ }
+
+ if (4 + 2 + 2 + tmax < 79) {
+ dend = 79 - smax - 2;
+ }
+ else
+ dend = tmax + 20;
+
+ NPRINT(mc, "The following commands are available:\n\n");
+
+ mrp_list_foreach(&mc->ctx->cmd_groups, p, n) {
+ grp = mrp_list_entry(p, typeof(*grp), hook);
+
+ if (*grp->name)
+ NPRINT(mc, " commands in group '%s':\n", grp->name);
+ else
+ NPRINT(mc, " general commands:\n");
+
+ for (i = 0, cmd = grp->commands; i < grp->ncommand; i++, cmd++) {
+ NPRINT(mc, " %s %n", cmd->name, &l);
+ NPRINT(mc, "%*.*s %s\n", dend - l, dend - l, DOTS, cmd->summary);
+ }
+
+ NPRINT(mc, "\n");
+ }
+}
+
+
+static void help_group(mrp_console_t *mc, const char *name)
+{
+ mrp_console_group_t *grp;
+ mrp_console_cmd_t *cmd;
+ mrp_list_hook_t *p, *n;
+ const char *t;
+ int i;
+
+ grp = find_group(mc->ctx, name);
+
+ if (grp != NULL) {
+ NPRINT(mc, "The following commands are available in group '%s':\n",
+ grp->name);
+ NPRINT(mc, "\n");
+
+ for (i = 0, cmd = grp->commands; i < grp->ncommand; i++, cmd++) {
+ NPRINT(mc, "Command %s (%s):\n", cmd->name, cmd->summary);
+ NPRINT(mc, "\n");
+ NPRINT(mc, "%s\n", cmd->description);
+ NPRINT(mc, "\n");
+ }
+ }
+ else {
+ EPRINT(mc, "Command group '%s' does not exist.\n", name);
+ EPRINT(mc, "The existing groups are: ");
+ t = "";
+ mrp_list_foreach(&mc->ctx->cmd_groups, p, n) {
+ grp = mrp_list_entry(p, typeof(*grp), hook);
+ if (*grp->name) {
+ EPRINT(mc, "%s'%s'", t, grp->name);
+ t = ", ";
+ }
+ }
+ EPRINT(mc, ".\n");
+ }
+
+
+}
+
+
+#define HELP_HELP "Give a help on a command group or a command."
+#define HELP_DESCRIPTION "Give general help or help on a specific command."
+
+static void cmd_help(mrp_console_t *mc, void *user_data, int argc, char **argv)
+{
+ console_t *c = (console_t *)mc;
+ char *ha[2];
+
+ MRP_UNUSED(c);
+
+ switch (argc) {
+ case 1:
+ help_overview(mc);
+ break;
+
+ case 2:
+ help_group(mc, argv[1]);
+ /*fprintf(mc->stdout, "Help on command '%s'.\n", argv[1]);*/
+ break;
+
+ case 3:
+ /*help_command(mc, argv[1], argv[2]);*/
+ fprintf(mc->stdout, "Help for command '%s/%s'.\n", argv[1], argv[2]);
+ break;
+
+ default:
+ ha[0] = "help";
+ ha[1] = "help";
+ fprintf(mc->stderr, "help: invalid arguments (%d).\n", argc);
+ fflush(mc->stderr);
+ cmd_help(mc, user_data, 2, ha);
+ }
+}
+
+
+#define EXIT_HELP "Exit from a command group or the console."
+#define EXIT_DESCRIPTION "Exit current console mode, or close the console."
+
+static void cmd_exit(mrp_console_t *mc, void *user_data, int argc, char **argv)
+{
+ console_t *c = (console_t *)mc;
+ char *ha[2];
+
+ MRP_UNUSED(c);
+
+ switch (argc) {
+ case 1:
+ if (c->grp != NULL)
+ c->grp = NULL;
+ else {
+ fprintf(mc->stdout, "Goodbye....\n");
+ mrp_destroy_console(mc);
+ }
+ break;
+
+ case 2:
+ if (!strcmp(argv[1], "console")) {
+ mrp_destroy_console(mc);
+ break;
+ }
+ /* intentional fall-through */
+
+ default:
+ ha[0] = "help";
+ ha[1] = "exit";
+ fprintf(mc->stderr, "exit: invalid arguments\n");
+ cmd_help(mc, user_data, 2, ha);
+ }
+}
+
+
+MRP_CONSOLE_GROUP(builtin_cmd_group, "", NULL, {
+ MRP_PARSED_CMD("help", HELP_HELP, HELP_DESCRIPTION, cmd_help),
+ MRP_PARSED_CMD("exit", EXIT_HELP, EXIT_DESCRIPTION, cmd_exit)
+});
--- /dev/null
+#ifndef __MURPHY_CONSOLE_COMMAND_H__
+#define __MURPHY_CONSOLE_COMMAND_H__
+
+
+/**
+ * Convenience macro for declaring console commands.
+ *
+ * Here is how you can use these macros to declare a group of commands
+ * for instance from/for your plugin:
+ *
+ * #define DESCRIPTION1 "Test command 1 (description of command 1)..."
+ * #define HELP1 "Help for test command1...
+ * #define DESCRIPTION2 "Test command 2 (description of command 2)..."
+ * #define HELP2 "Help for test command2...
+ * #define DESCRIPTION3 "Test command 3 (description of command 3)..."
+ * #define HELP3 "Help for test command3...
+ * #define DESCRIPTION4 "Test command 4 (description of command 4)..."
+ * #define HELP4 "Help for test command4...
+ *
+ * static void cmd1_cb(mrp_console_t *c, void *user_data, int argc, char **argv)
+ * {
+ * int i;
+ *
+ * for (i = 0; i < argc, i++) {
+ * mrp_console_printf(c, "%s(): arg #%d: '%s'\n", __FUNCTION__,
+ * i, argv[i]);
+ * }
+ * }
+ *
+ * ...
+ *
+ * MRP_CONSOLE_GROUP(test_cmd_group, "test", NULL, {
+ * MRP_PARSED_CMD("cmd1", CMD1_HELP, CMD1_DESCRIPTION, cmd1_cb),
+ * MRP_PARSED_CMD("cmd2", CMD2_HELP, CMD2_DESCRIPTION, cmd2_cb),
+ * MRP_PARSED_CMD("cmd3", CMD3_HELP, CMD3_DESCRIPTION, cmd3_cb),
+ * MRP_PARSED_CMD("cmd4", CMD4_HELP, CMD4_DESCRIPTION, cmd4_cb)
+ * });
+ *
+ * ...
+ *
+ * static int plugin_init(mrp_plugin_t *plugin)
+ * {
+ * ...
+ * mrp_add_console_group(plugin->ctx, &test_cmd_group);
+ * ...
+ * }
+ *
+ *
+ * static int plugin_exit(mrp_plugin_t *plugin)
+ * {
+ * ...
+ * mrp_del_console_group(plugin->ctx, &test_cmd_group);
+ * ...
+ * }
+ *
+ */
+
+#define MRP_CONSOLE_COMMANDS(_var, ...) \
+ static mrp_console_cmd_t _var[] = __VA_ARGS__
+
+#define MRP_CONSOLE_GROUP(_var, _name, _data, ...) \
+ MRP_CONSOLE_COMMANDS(_var##_cmds, __VA_ARGS__); \
+ static mrp_console_group_t _var = { \
+ .name = _name, \
+ .user_data = _data, \
+ .commands = _var##_cmds, \
+ .ncommand = MRP_ARRAY_SIZE(_var##_cmds), \
+ .hook = MRP_LIST_INIT(_var.hook), \
+ };
+
+#define MRP_PARSED_CMD(_name, _summ, _descr, _cb) { \
+ .name = _name, \
+ .summary = _summ, \
+ .description = _descr, \
+ .tok = _cb \
+ }
+
+#define MRP_RAW_CMD(_name, _summ, _descr, _cb) { \
+ .name = _name, \
+ .summary = _summ, \
+ .description = _descr, \
+ .flags = MRP_CONSOLE_RAWINPUT, \
+ .raw = _cb \
+ }
+
+typedef struct mrp_console_s mrp_console_t;
+
+
+/*
+ * console/command flags
+ */
+
+typedef enum {
+ MRP_CONSOLE_TOKENIZE = 0x0, /* callback wants tokenized input */
+ MRP_CONSOLE_RAWINPUT = 0x1, /* callback wants 'raw' input */
+} mrp_console_flag_t;
+
+
+/*
+ * a console command
+ */
+
+typedef struct {
+ const char *name; /* command name */
+ const char *summary; /* short help */
+ const char *description; /* long command description */
+ mrp_console_flag_t flags; /* command flags */
+ union { /* tokenized or raw input cb */
+ int (*raw)(mrp_console_t *c, void *user_data, char *input);
+ void (*tok)(mrp_console_t *c, void *user_data, int argc, char **argv);
+ };
+} mrp_console_cmd_t;
+
+
+/*
+ * a console command group
+ */
+
+typedef struct {
+ const char *name; /* command group name/prefix */
+ void *user_data; /* opaque callback data */
+ mrp_console_cmd_t *commands; /* commands in this group */
+ int ncommand; /* number of commands */
+ mrp_list_hook_t hook; /* to list of command groups */
+} mrp_console_group_t;
+
+
+/** Register a console command group. */
+int mrp_add_console_group(mrp_context_t *ctx, mrp_console_group_t *group);
+
+/** Unregister a console command group. */
+int mrp_del_console_group(mrp_context_t *ctx, mrp_console_group_t *group);
+
+
+#endif /* __MURPHY_CONSOLE_COMMAND_H__ */
--- /dev/null
+#ifndef __MURPHY_CONSOLE_PRIV_H__
+#define __MURPHY_CONSOLE_PRIV_H__
+
+#include <murphy/core/console.h>
+
+int console_setup(mrp_context_t *ctx);
+void console_cleanup(mrp_context_t *ctx);
+
+#endif /* __MURPHY_CONSOLE_PRIV_H__ */
--- /dev/null
+#define _GNU_SOURCE /* we want fopencookie */
+#include <stdio.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+#include <murphy/common/log.h>
+#include <murphy/common/msg.h>
+#include <murphy/common/transport.h>
+
+#include <murphy/core/console.h>
+
+#define MAX_PROMPT 64 /* ie. way too long */
+
+#define COLOR "\E"
+#define YELLOW "33m"
+#define WHITE "37m"
+#define RED "31m"
+
+#define CNORM COLOR""WHITE
+#define CWARN COLOR""YELLOW
+#define CERR COLOR""RED
+
+#define MRP_CFG_MAXLINE 4096 /* input line length limit */
+#define MRP_CFG_MAXARGS 64 /* command argument limit */
+
+typedef struct {
+ char buf[MRP_CFG_MAXLINE]; /* input buffer */
+ char *token; /* current token */
+ char *in; /* filling pointer */
+ char *out; /* consuming pointer */
+ char *next; /* next token buffer position */
+ int fd; /* input file */
+ int error; /* whether has encounted and error */
+ char *file; /* file being processed */
+ int line; /* line number */
+ int next_newline;
+ int was_newline;
+} input_t;
+
+static int get_next_line(input_t *in, char **args, size_t size);
+
+
+
+/*
+ * an active console
+ */
+
+typedef struct {
+ MRP_CONSOLE_PUBLIC_FIELDS; /* publicly visible fields */
+ mrp_console_group_t *grp; /* active group if any */
+ char prompt[MAX_PROMPT]; /* current prompt */
+ input_t in; /* input buffer */
+ mrp_list_hook_t hook; /* to list of active consoles */
+} console_t;
+
+
+static int check_destroy(mrp_console_t *mc);
+static int purge_destroyed(mrp_console_t *mc);
+static FILE *console_fopen(mrp_console_t *mc);
+
+static ssize_t input_evt(mrp_console_t *mc, void *buf, size_t size);
+static void disconnected_evt(mrp_console_t *c, int error);
+static ssize_t complete_evt(mrp_console_t *c, void *input, size_t isize,
+ char **completions, size_t csize);
+
+
+
+static void register_commands(mrp_context_t *ctx);
+static void unregister_commands(mrp_context_t *ctx);
+
+void console_setup(mrp_context_t *ctx)
+{
+ mrp_list_init(&ctx->cmd_groups);
+ mrp_list_init(&ctx->consoles);
+
+ register_commands(ctx);
+}
+
+
+void console_cleanup(mrp_context_t *ctx)
+{
+ mrp_list_hook_t *p, *n;
+ console_t *c;
+
+ mrp_list_foreach(&ctx->consoles, p, n) {
+ c = mrp_list_entry(p, typeof(*c), hook);
+ mrp_destroy_console((mrp_console_t *)c);
+ }
+
+ mrp_list_init(&ctx->cmd_groups);
+
+ unregister_commands(ctx);
+}
+
+
+mrp_console_t *mrp_create_console(mrp_context_t *ctx, mrp_console_req_t *req,
+ void *backend_data)
+{
+ static mrp_console_evt_t evt = {
+ .input = input_evt,
+ .disconnected = disconnected_evt,
+ .complete = complete_evt
+ };
+
+ console_t *c;
+
+ if (req->write == NULL || req->close == NULL ||
+ req->free == NULL || req->set_prompt == NULL)
+ return NULL;
+
+ if ((c = mrp_allocz(sizeof(*c))) != NULL) {
+ mrp_list_init(&c->hook);
+ c->ctx = ctx;
+ c->req = *req;
+ c->evt = evt;
+
+ c->stdout = console_fopen((mrp_console_t *)c);
+ c->stderr = console_fopen((mrp_console_t *)c);
+
+ if (c->stdout == NULL || c->stderr == NULL)
+ goto fail;
+
+ c->backend_data = backend_data;
+ c->check_destroy = check_destroy;
+
+ c->in.file = "<console input>";
+ c->in.line = 0;
+ c->in.fd = -1;
+
+ mrp_list_append(&ctx->consoles, &c->hook);
+ mrp_set_console_prompt((mrp_console_t *)c);
+ }
+ else {
+ fail:
+ if (c != NULL) {
+ if (c->stdout != NULL)
+ fclose(c->stdout);
+ if (c->stderr != NULL)
+ fclose(c->stderr);
+ mrp_free(c);
+ c = NULL;
+ }
+ }
+
+ return (mrp_console_t *)c;
+}
+
+
+static int purge_destroyed(mrp_console_t *mc)
+{
+ console_t *c = (console_t *)mc;
+
+ if (c->destroyed && !c->busy) {
+ mrp_debug("Purging destroyed console %p...", c);
+
+ mrp_list_delete(&c->hook);
+
+ fclose(c->stdout);
+ fclose(c->stderr);
+
+ c->req.free(c->backend_data);
+ mrp_free(c);
+
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+
+void mrp_destroy_console(mrp_console_t *mc)
+{
+ if (mc != NULL && !mc->destroyed) {
+ if (mc->stdout != NULL)
+ fflush(mc->stdout);
+ if (mc->stderr != NULL)
+ fflush(mc->stderr);
+
+ mc->destroyed = TRUE;
+
+ if (mc->backend_data != NULL) {
+ MRP_CONSOLE_BUSY(mc, {
+ mc->req.close(mc);
+ });
+ }
+
+ purge_destroyed(mc);
+ }
+}
+
+
+static int check_destroy(mrp_console_t *c)
+{
+ return purge_destroyed(c);
+}
+
+
+void mrp_console_printf(mrp_console_t *mc, const char *fmt, ...)
+{
+ console_t *c = (console_t *)mc;
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(c->stdout, fmt, ap);
+ va_end(ap);
+
+ fflush(c->stdout);
+}
+
+
+void mrp_set_console_prompt(mrp_console_t *mc)
+{
+ console_t *c = (console_t *)mc;
+ char *prompt, buf[MAX_PROMPT];
+
+ if (c->destroyed)
+ return;
+
+ if (c->grp != NULL) {
+ prompt = buf;
+
+ snprintf(buf, sizeof(buf), "murphy %s", c->grp->name);
+ }
+ else
+ prompt = "murphy";
+
+ if (strcmp(prompt, c->prompt)) {
+ strcpy(c->prompt, prompt);
+ c->req.set_prompt(mc, prompt);
+ }
+}
+
+
+static mrp_console_group_t *find_group(mrp_context_t *ctx, const char *name)
+{
+ mrp_list_hook_t *p, *n;
+ mrp_console_group_t *grp;
+
+ mrp_list_foreach(&ctx->cmd_groups, p, n) {
+ grp = mrp_list_entry(p, typeof(*grp), hook);
+ if (!strcmp(grp->name, name))
+ return grp;
+ }
+
+ return NULL;
+}
+
+
+static mrp_console_cmd_t *find_command(mrp_console_group_t *group,
+ const char *command)
+{
+ mrp_console_cmd_t *cmd;
+ int i;
+
+ for (i = 0, cmd = group->commands; i < group->ncommand; i++, cmd++) {
+ if (!strcmp(cmd->name, command))
+ return cmd;
+ }
+
+ return NULL;
+}
+
+
+int mrp_add_console_group(mrp_context_t *ctx, mrp_console_group_t *group)
+{
+ if (group != NULL && find_group(ctx, group->name) == NULL) {
+ mrp_list_append(&ctx->cmd_groups, &group->hook);
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+
+int mrp_del_console_group(mrp_context_t *ctx, mrp_console_group_t *group)
+{
+ if (group != NULL && find_group(ctx, group->name) == group) {
+ mrp_list_delete(&group->hook);
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+
+static ssize_t input_evt(mrp_console_t *mc, void *buf, size_t size)
+{
+ console_t *c = (console_t *)mc;
+ mrp_console_group_t *grp;
+ mrp_console_cmd_t *cmd;
+ char *args[MRP_CFG_MAXARGS];
+ int argc;
+ char **argv;
+ int len;
+
+ len = size;
+ strncpy(c->in.buf, buf, len);
+ c->in.buf[len++] = '\n';
+ c->in.buf[len] = '\0';
+
+ c->in.token = c->in.buf;
+ c->in.out = c->in.buf;
+ c->in.next = c->in.buf;
+ c->in.in = c->in.buf + len;
+ c->in.line = 1;
+ *c->in.in = '\0';
+
+ argv = args + 1;
+ argc = get_next_line(&c->in, argv, MRP_ARRAY_SIZE(args) - 1);
+ grp = c->grp;
+ cmd = NULL;
+
+ /*
+ * Notes: Uhmmkay... so this will need t get replaced eventually with
+ * decent input processing login.
+ */
+
+ if (argc < 0)
+ fprintf(c->stderr, "failed to parse command: '%.*s'\n",
+ (int)size, (char *)buf);
+ else if (argc == 0)
+ goto prompt;
+
+ if (argc == 1) {
+ if (!strcmp(argv[0], "exit")) {
+ if (grp != NULL) {
+ c->grp = NULL;
+ goto prompt;
+ }
+ else {
+ grp = find_group(c->ctx, "");
+ cmd = find_command(grp, "exit");
+ goto execute;
+ }
+ }
+ else {
+ if (grp == NULL || *argv[0] == '/') {
+ grp = find_group(c->ctx, argv[0]);
+ if (c->grp == NULL && grp != NULL) {
+ c->grp = grp;
+ goto prompt;
+ }
+ else {
+ grp = find_group(c->ctx, "");
+ cmd = find_command(grp, argv[0]);
+
+ if (cmd == NULL && *argv[0] == '/')
+ cmd = find_command(grp, argv[0] + 1);
+
+ goto execute;
+ }
+ }
+ else {
+ if (grp != NULL) {
+ cmd = find_command(grp, argv[0]);
+
+ if (cmd == NULL) {
+ grp = find_group(c->ctx, "");
+ cmd = find_command(grp, argv[0] + 1);
+ }
+ }
+ else {
+ grp = find_group(c->ctx, "");
+ cmd = find_command(grp, argv[0]);
+
+ if (cmd == NULL && *argv[0] == '/')
+ cmd = find_command(grp, argv[0] + 1);
+ }
+
+ goto execute;
+ }
+ }
+ }
+ else {
+ if (*argv[0] == '/') {
+ grp = find_group(c->ctx, argv[0] + 1);
+
+ if (grp != NULL)
+ cmd = find_command(grp, argv[1]);
+ else {
+ grp = find_group(c->ctx, "");
+ cmd = find_command(grp, argv[0] + 1);
+ }
+
+ goto execute;
+ }
+ else {
+ if (c->grp != NULL) {
+ grp = c->grp;
+ cmd = find_command(grp, argv[0]);
+
+ if (cmd == NULL) {
+ grp = find_group(c->ctx, "");
+ cmd = find_command(grp, argv[0]);
+ }
+ }
+ else {
+ grp = find_group(c->ctx, argv[0]);
+ cmd = grp ? find_command(grp, argv[1]) : NULL;
+ }
+
+ goto execute;
+ }
+ }
+
+ execute:
+ if (cmd != NULL) {
+ MRP_CONSOLE_BUSY(mc, {
+ cmd->tok(mc, grp->user_data, argc, argv);
+ });
+ }
+ else
+ fprintf(mc->stderr, "invalid command '%.*s'\n", (int)size, (char *)buf);
+
+ prompt:
+ if (mc->check_destroy(mc))
+ return size;
+
+ fflush(mc->stdout);
+ fflush(mc->stderr);
+
+ mrp_set_console_prompt(mc);
+
+ return size;
+}
+
+
+static void disconnected_evt(mrp_console_t *c, int error)
+{
+ mrp_log_info("Console %p has been disconnected (error: %d).", c, error);
+}
+
+
+static ssize_t complete_evt(mrp_console_t *c, void *input, size_t isize,
+ char **completions, size_t csize)
+{
+ MRP_UNUSED(c);
+ MRP_UNUSED(input);
+ MRP_UNUSED(isize);
+ MRP_UNUSED(completions);
+ MRP_UNUSED(csize);
+
+ return 0;
+}
+
+
+/*
+ * stream-based console I/O
+ */
+
+static ssize_t cookie_write(void *cptr, const char *buf, size_t size)
+{
+ console_t *c = (console_t *)cptr;
+ ssize_t ssize;
+
+ if (c->destroyed)
+ return size;
+
+ MRP_CONSOLE_BUSY(c, {
+ ssize = c->req.write((mrp_console_t *)c, (char *)buf, size);
+ });
+
+ return ssize;
+}
+
+
+static int cookie_close(void *cptr)
+{
+ MRP_UNUSED(cptr);
+
+ return 0;
+}
+
+
+static FILE *console_fopen(mrp_console_t *mc)
+{
+ static cookie_io_functions_t io_func = {
+ .read = NULL,
+ .write = cookie_write,
+ .seek = NULL,
+ .close = cookie_close
+ };
+
+ return fopencookie((void *)mc, "w", io_func);
+}
+
+
+/*
+ * builtin console commands
+ */
+
+#include "console-command.c"
+
+static void register_commands(mrp_context_t *ctx)
+{
+ mrp_add_console_group(ctx, &builtin_cmd_group);
+}
+
+
+static void unregister_commands(mrp_context_t *ctx)
+{
+ mrp_del_console_group(ctx, &builtin_cmd_group);
+}
+
+
+/*
+ * XXX TODO Verbatim copy of config.c tokenizer. Separate this out
+ * to common (maybe common/text-utils.c), generalize and
+ * clean it up.
+ */
+
+#define MRP_START_COMMENT '#'
+
+static char *get_next_token(input_t *in);
+
+static int get_next_line(input_t *in, char **args, size_t size)
+{
+ char *token;
+ int narg;
+
+ narg = 0;
+ while ((token = get_next_token(in)) != NULL && narg < (int)size) {
+ if (in->error)
+ return -1;
+
+ if (token[0] != '\n')
+ args[narg++] = token;
+ else {
+ if (*args[0] != MRP_START_COMMENT && narg && *args[0] != '\n')
+ return narg;
+ else
+ narg = 0;
+ }
+ }
+
+ if (in->error)
+ return -1;
+
+ if (narg >= (int)size) {
+ mrp_log_error("Too many tokens on line %d of %s.",
+ in->line - 1, in->file);
+ return -1;
+ }
+ else {
+ if (*args[0] != MRP_START_COMMENT && *args[0] != '\n')
+ return narg;
+ else
+ return 0;
+ }
+}
+
+
+static inline void skip_whitespace(input_t *in)
+{
+ while ((*in->out == ' ' || *in->out == '\t') && in->out < in->in)
+ in->out++;
+}
+
+
+static char *get_next_token(input_t *in)
+{
+ ssize_t len;
+ int diff, size;
+ int quote, quote_line;
+ char *p, *q;
+
+ /*
+ * Newline:
+ *
+ * If the previous token was terminated by a newline,
+ * take care of properly returning and administering
+ * the newline token here.
+ */
+
+ if (in->next_newline) {
+ in->next_newline = FALSE;
+ in->was_newline = TRUE;
+ in->line++;
+
+ return "\n";
+ }
+
+
+ /*
+ * if we just finished a line, discard all old data/tokens
+ */
+
+ if (*in->token == '\n' || in->was_newline) {
+ diff = in->out - in->buf;
+ size = in->in - in->out;
+ memmove(in->buf, in->out, size);
+ in->out -= diff;
+ in->in -= diff;
+ in->next = in->buf;
+ *in->in = '\0';
+ }
+
+ /*
+ * refill the buffer if we're empty or just flushed all tokens
+ */
+
+ if (in->token == in->buf && in->fd != -1) {
+ size = sizeof(in->buf) - 1 - (in->in - in->buf);
+ len = read(in->fd, in->in, size);
+
+ if (len < size) {
+ close(in->fd);
+ in->fd = -1;
+ }
+
+ if (len < 0) {
+ mrp_log_error("Failed to read from config file (%d: %s).",
+ errno, strerror(errno));
+ in->error = TRUE;
+ close(in->fd);
+ in->fd = -1;
+
+ return NULL;
+ }
+
+ in->in += len;
+ *in->in = '\0';
+ }
+
+ if (in->out >= in->in)
+ return NULL;
+
+ skip_whitespace(in);
+
+ quote = FALSE;
+ quote_line = 0;
+
+ p = in->out;
+ q = in->next;
+ in->token = q;
+
+ while (p < in->in) {
+ /*printf("[%c]\n", *p == '\n' ? '.' : *p);*/
+ switch (*p) {
+ /*
+ * Quoting:
+ *
+ * If we're not within a quote, mark a quote started.
+ * Otherwise if quote matches, close quoting. Otherwise
+ * copy the quoted quote verbatim.
+ */
+ case '\'':
+ case '\"':
+ if (!quote) {
+ quote = *p++;
+ quote_line = in->line;
+ }
+ else {
+ if (*p == quote) {
+ quote = FALSE;
+ quote_line = 0;
+ p++;
+ }
+ else {
+ *q++ = *p++;
+ }
+ }
+ in->was_newline = FALSE;
+ break;
+
+ /*
+ * Whitespace:
+ *
+ * If we're quoting, copy verbatim. Otherwise mark the end
+ * of the token.
+ */
+ case ' ':
+ case '\t':
+ if (quote)
+ *q++ = *p++;
+ else {
+ p++;
+ *q++ = '\0';
+
+ in->out = p;
+ in->next = q;
+
+ return in->token;
+ }
+ in->was_newline = FALSE;
+ break;
+
+ /*
+ * Escaping:
+ *
+ * If the last character in the input, copy verbatim.
+ * Otherwise if it escapes a '\n', skip both. Otherwise
+ * copy the escaped character verbatim.
+ */
+ case '\\':
+ if (p < in->in - 1) {
+ p++;
+ if (*p != '\n')
+ *q++ = *p++;
+ else {
+ p++;
+ in->line++;
+ in->out = p;
+ skip_whitespace(in);
+ p = in->out;
+ }
+ }
+ else
+ *q++ = *p++;
+ in->was_newline = FALSE;
+ break;
+
+ /*
+ * Newline:
+ *
+ * We don't allow newlines to be quoted. Otherwise
+ * if the token is not the newline itself, we mark
+ * the next token to be newline and return the token
+ * it terminated.
+ */
+ case '\n':
+ if (quote) {
+ mrp_log_error("%s:%d: Unterminated quote (%c) started "
+ "on line %d.", in->file, in->line, quote,
+ quote_line);
+ in->error = TRUE;
+
+ return NULL;
+ }
+ else {
+ *q = '\0';
+ p++;
+
+ in->out = p;
+ in->next = q;
+
+ if (in->token == q) {
+ in->line++;
+ in->was_newline = TRUE;
+ return "\n";
+ }
+ else {
+ in->next_newline = TRUE;
+ return in->token;
+ }
+ }
+ break;
+
+ /*
+ * CR: just ignore it
+ */
+ case '\r':
+ p++;
+ break;
+
+ default:
+ *q++ = *p++;
+ in->was_newline = FALSE;
+ }
+ }
+
+ if (in->fd == -1) {
+ *q = '\0';
+ in->out = p;
+ in->in = q;
+
+ return in->token;
+ }
+ else {
+ mrp_log_error("Input line %d of file %s exceeds allowed length.",
+ in->line, in->file);
+ return NULL;
+ }
+}
--- /dev/null
+#ifndef __MURPHY_CONSOLE_H__
+#define __MURPHY_CONSOLE_H__
+
+#include <murphy/common/list.h>
+#include <murphy/common/msg.h>
+#include <murphy/core/context.h>
+
+#include <murphy/core/console-command.h>
+
+
+
+/*
+ * console requests
+ *
+ * Console request correspond to top-down event propagation in the console
+ * communication stack. These requests are made by the core console to the
+ * underlying actual console implementation, typically either as a result
+ * of calls to the console abstraction layer, or in reponse to requests
+ * (ie. input) coming from the actual console implementation.
+ */
+
+typedef struct {
+ /** Deliver a buffer of data to the given console. */
+ ssize_t (*write)(mrp_console_t *c, void *buf, size_t size);
+ /** Console being closed, close the backend (do not release memory yet). */
+ void (*close)(mrp_console_t *c);
+ /** Console has been destroyed, release resources allocated by backend. */
+ void (*free)(void *data);
+ /** Set the prompt shown to the user at the console. */
+ void (*set_prompt)(mrp_console_t *c, const char *prompt);
+} mrp_console_req_t;
+
+
+/*
+ * console events
+ *
+ * Console events correspond to bottom-up event propagation in the console
+ * communication stack. These callbacks are made by the console backend to
+ * the core console to inform about relevant console events, such as new
+ * console input or disconnect by the peer.
+ */
+
+typedef struct {
+ /** New input available from console. */
+ ssize_t (*input)(mrp_console_t *c, void *buf, size_t size);
+ /** Peer has disconnected from the console. */
+ void (*disconnected)(mrp_console_t *c, int error);
+ /** Generate possible completions for the given input. */
+ ssize_t (*complete)(mrp_console_t *c, void *input, size_t insize,
+ char **completions, size_t csize);
+} mrp_console_evt_t;
+
+
+#define MRP_CONSOLE_PUBLIC_FIELDS \
+ mrp_context_t *ctx; \
+ mrp_console_req_t req; \
+ mrp_console_evt_t evt; \
+ int (*check_destroy)(mrp_console_t *c); \
+ FILE *stdout; \
+ FILE *stderr; \
+ void *backend_data; \
+ int busy; \
+ int destroyed : 1
+
+struct mrp_console_s {
+ MRP_CONSOLE_PUBLIC_FIELDS;
+};
+
+
+/**
+ * Macro to mark a console busy while running a block of code.
+ *
+ * The backend needs to make sure the console is not freed while any console
+ * request or event callback function is active. Similarly, the backend needs
+ * to check if the console has been marked for destruction whenever an event
+ * callback returns and trigger destruction if it is necessary and possible
+ * (ie. the above criterium of not being active is fullfilled).
+ *
+ * These are the easiest to accomplish using the provided MRP_CONSOLE_BUSY
+ * macro and the check_destroy callback member provided by mrp_console_t.
+ *
+ * 1) Use the provided MRP_CONSOLE_BUSY macro to enclose al blocks of
+ * code that invoke event callbacks. Do not do a return directly
+ * from within the enclosed call blocks, rather just set a flag
+ * within the block, check it after the block and do the return
+ * there if necessary.
+ *
+ * 2) Call mrp_console_t->check_destroy after any call to an console
+ * event callback. check_destroy will check for any pending destroy
+ * request and perform the actual destruction if it is both necessary
+ * and possible. If the console has been left intact, check_destroy
+ * returns FALSE. However, if the console has been destroyed and freed
+ * it returns TRUE, in which case the caller must not attempt to use
+ * or dereference the console any more.
+ */
+
+#ifndef __MRP_CONSOLE_DISABLE_CODE_CHECK__
+# define __CONSOLE_CHK_BLOCK(...) do { \
+ static int __warned = 0; \
+ \
+ if (MRP_UNLIKELY(__warned == 0 && \
+ strstr(#__VA_ARGS__, "return") != NULL)) { \
+ mrp_log_error("********************* WARNING *********************"); \
+ mrp_log_error("* You seem to directly do a return from a block *"); \
+ mrp_log_error("* of code protected by MRP_CONSOLE_BUSY. Are *"); \
+ mrp_log_error("* you absolutely sure you know what you are doing *"); \
+ mrp_log_error("* and that you are also doing it correctly ? *"); \
+ mrp_log_error("***************************************************"); \
+ mrp_log_error("The suspicious code block is located at: "); \
+ mrp_log_error(" %s@%s:%d", __FUNCTION__, __FILE__, __LINE__); \
+ mrp_log_error("and it looks like this:"); \
+ mrp_log_error("---------------------------------------------"); \
+ mrp_log_error("%s", #__VA_ARGS__); \
+ mrp_log_error("---------------------------------------------"); \
+ mrp_log_error("If you understand what MRP_CONSOLE_BUSY does"); \
+ mrp_log_error("and how, and you are sure about the correctness of"); \
+ mrp_log_error("your code you can disable this error message by"); \
+ mrp_log_error("#defining __MRP_CONSOLE_DISABLE_CODE_CHECK__"); \
+ mrp_log_error("when compiling %s.", __FILE__); \
+ __warned = 1; \
+ } \
+ } while (0)
+#else
+# define __CONSOLE_CHK_BLOCK(...) do { } while (0)
+#endif
+
+#define MRP_CONSOLE_BUSY(c, ...) do { \
+ __CONSOLE_CHK_BLOCK(__VA_ARGS__); \
+ (c)->busy++; \
+ __VA_ARGS__ \
+ (c)->busy--; \
+ } while (0)
+
+
+/** Create a new console instance. */
+mrp_console_t *mrp_create_console(mrp_context_t *ctx, mrp_console_req_t *req,
+ void *backend_data);
+
+/** Close and mark a console for destruction. */
+void mrp_destroy_console(mrp_console_t *mc);
+
+/** Send (printf-compatible) formatted output to a console. */
+void mrp_console_printf(mrp_console_t *mc, const char *fmt, ...);
+
+/** Set the prompt of a console. */
+void mrp_set_console_prompt(mrp_console_t *mc);
+
+#endif /* __MURPHY_CONSOLE_H__ */
#include <murphy/common/log.h>
#include <murphy/common/mm.h>
#include <murphy/core/context.h>
-
+#include <murphy/core/console-priv.h>
mrp_context_t *mrp_context_create(void)
{
if ((c = mrp_allocz(sizeof(*c))) != NULL) {
mrp_list_init(&c->plugins);
+ console_setup(c);
if ((c->ml = mrp_mainloop_create()) == NULL) {
mrp_log_error("Failed to create mainloop.");
void mrp_context_destroy(mrp_context_t *c)
{
if (c != NULL) {
+ console_cleanup(c);
mrp_mainloop_destroy(c->ml);
mrp_free(c);
}
+
+
}
/* actual runtime context data */
mrp_mainloop_t *ml; /* mainloop */
mrp_list_hook_t plugins; /* list of loaded plugins */
+ mrp_list_hook_t cmd_groups; /* console command groups */
+ mrp_list_hook_t consoles; /* active consoles */
} mrp_context_t;
/** Create a new murphy context. */