core: added core console infra, a few basic commands for test.
authorKrisztian Litkey <krisztian.litkey@intel.com>
Wed, 2 May 2012 17:14:12 +0000 (20:14 +0300)
committerKrisztian Litkey <krisztian.litkey@intel.com>
Wed, 2 May 2012 17:57:00 +0000 (20:57 +0300)
src/Makefile.am
src/core/console-command.c [new file with mode: 0644]
src/core/console-command.h [new file with mode: 0644]
src/core/console-priv.h [new file with mode: 0644]
src/core/console.c [new file with mode: 0644]
src/core/console.h [new file with mode: 0644]
src/core/context.c
src/core/context.h

index 6dbdf70..f981952 100644 (file)
@@ -93,11 +93,14 @@ libmurphy_core_ladir      =         \
 
 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)
diff --git a/src/core/console-command.c b/src/core/console-command.c
new file mode 100644 (file)
index 0000000..8a76486
--- /dev/null
@@ -0,0 +1,178 @@
+
+
+/*
+ * 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)
+});
diff --git a/src/core/console-command.h b/src/core/console-command.h
new file mode 100644 (file)
index 0000000..7f3d7c4
--- /dev/null
@@ -0,0 +1,135 @@
+#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__ */
diff --git a/src/core/console-priv.h b/src/core/console-priv.h
new file mode 100644 (file)
index 0000000..ecf37bb
--- /dev/null
@@ -0,0 +1,9 @@
+#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__ */
diff --git a/src/core/console.c b/src/core/console.c
new file mode 100644 (file)
index 0000000..1f5bb54
--- /dev/null
@@ -0,0 +1,777 @@
+#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;
+    }
+}
diff --git a/src/core/console.h b/src/core/console.h
new file mode 100644 (file)
index 0000000..8ac4e75
--- /dev/null
@@ -0,0 +1,148 @@
+#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__ */
index eacd2e6..163d1f5 100644 (file)
@@ -2,7 +2,7 @@
 #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)
 {
@@ -10,6 +10,7 @@ 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.");
@@ -25,8 +26,11 @@ mrp_context_t *mrp_context_create(void)
 void mrp_context_destroy(mrp_context_t *c)
 {
     if (c != NULL) {
+       console_cleanup(c);
        mrp_mainloop_destroy(c->ml);
        mrp_free(c);
     }
+
+
 }
 
index 50b55e6..4057ee9 100644 (file)
@@ -19,6 +19,8 @@ typedef struct {
     /* 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. */