[multipathd] add CLI readline completion
authorChristophe Varoqui <cvaroqui@zezette.localdomain>
Sun, 6 May 2007 22:21:17 +0000 (00:21 +0200)
committerChristophe Varoqui <cvaroqui@zezette.localdomain>
Sun, 6 May 2007 22:21:17 +0000 (00:21 +0200)
This one needed a bit of work :

o Add a convenient vector_foreach_slot_after() to the vector lib
o Move all add_handler() calls from main.c to cli.c, but don't set handlers
  there. This way the multipathd binary execed as the CLI (-k flag) gains
  the leisure the load the CLI dictionnary too. Which is quite useful for
  keyword completion.
o Add a set_handler_callback() function and use it in main.c in place of
  former add_handler() calls.
o No need to compute len each vector_foreach_slot() iteration in find_key()
o get_cmdvec() returns more precise failure hints as int return value.
  The readline keyword generator needs those hints.

multipathd/cli.c
multipathd/cli.h
multipathd/main.c
multipathd/uxclnt.c

index 4edf1af..d786eef 100644 (file)
@@ -5,6 +5,7 @@
 #include <vector.h>
 #include <util.h>
 #include <version.h>
+#include <readline/readline.h>
 
 #include "cli.h"
 
@@ -73,6 +74,30 @@ add_handler (int fp, int (*fn)(void *, char **, int *, void *))
        return 0;
 }
 
+static struct handler *
+find_handler (int fp)
+{
+       int i;
+       struct handler *h;
+
+       vector_foreach_slot (handlers, h, i)
+               if (h->fingerprint == fp)
+                       return h;
+
+       return NULL;
+}
+
+int
+set_handler_callback (int fp, int (*fn)(void *, char **, int *, void *))
+{
+       struct handler * h = find_handler(fp);
+
+       if (!h)
+               return 1;
+       h->fn = fn;
+       return 0;
+}
+
 static void
 free_key (struct key * kw)
 {
@@ -149,27 +174,26 @@ load_keys (void)
                keys = NULL;
                return 1;
        }
-
        return 0;
 }
 
 static struct key *
-find_key (char * str)
+find_key (const char * str)
 {
        int i;
        int len, klen;
        struct key * kw = NULL;
        struct key * foundkw = NULL;
 
-       vector_foreach_slot (keys, kw, i) {
-               len = strlen(str);
-               klen = strlen(kw->str);
+       len = strlen(str);
 
+       vector_foreach_slot (keys, kw, i) {
                if (strncmp(kw->str, str, len))
                        continue;
-               else if (len == klen)
+               klen = strlen(kw->str);
+               if (len == klen)
                        return kw; /* exact match */
-               else if (len < klen) {
+               if (len < klen) {
                        if (!foundkw)
                                foundkw = kw; /* shortcut match */
                        else
@@ -178,24 +202,16 @@ find_key (char * str)
        }
        return foundkw;
 }
-               
-static struct handler *
-find_handler (int fp)
-{
-       int i;
-       struct handler *h;
-
-       vector_foreach_slot (handlers, h, i)
-               if (h->fingerprint == fp)
-                       return h;
 
-       return NULL;
-}
+#define E_SYNTAX       1
+#define E_NOPARM       2
+#define E_NOMEM                3
 
-static vector
-get_cmdvec (char * cmd)
+static int
+get_cmdvec (char * cmd, vector *v)
 {
        int fwd = 1;
+       int r = 0;
        char * p = cmd;
        char * buff;
        struct key * kw = NULL;
@@ -203,9 +219,10 @@ get_cmdvec (char * cmd)
        vector cmdvec;
 
        cmdvec = vector_alloc();
+       *v = cmdvec;
 
        if (!cmdvec)
-               return NULL;
+               return E_NOMEM;
 
        while (fwd) {
                fwd = get_word(p, &buff);
@@ -218,18 +235,19 @@ get_cmdvec (char * cmd)
                FREE(buff);
 
                if (!kw)
-                       goto out; /* synthax error */
+                       return E_SYNTAX;
 
                cmdkw = alloc_key();
 
-               if (!cmdkw)
+               if (!cmdkw) {
+                       r = E_NOMEM;
                        goto out;
-
+               }
                if (!vector_alloc_slot(cmdvec)) {
                        FREE(cmdkw);
+                       r = E_NOMEM;
                        goto out;
                }
-
                vector_set_slot(cmdvec, cmdkw);
                cmdkw->code = kw->code;
                cmdkw->has_param = kw->has_param;
@@ -241,18 +259,18 @@ get_cmdvec (char * cmd)
                        fwd = get_word(p, &buff);
 
                        if (!buff)
-                               goto out;
+                               return E_NOPARM;
 
                        p += fwd;
                        cmdkw->param = buff;
                }
        }
-
-       return cmdvec;
+       return 0;
 
 out:
        free_keys(cmdvec);
-       return NULL;
+       *v = NULL;
+       return r;
 }
 
 static int 
@@ -262,6 +280,9 @@ fingerprint(vector vec)
        int fp = 0;
        struct key * kw;
 
+       if (!vec)
+               return 0;
+
        vector_foreach_slot(vec, kw, i)
                fp += kw->code;
 
@@ -334,9 +355,13 @@ parse_cmd (char * cmd, char ** reply, int * len, void * data)
 {
        int r;
        struct handler * h;
-       vector cmdvec = get_cmdvec(cmd);
+       vector cmdvec;
+
+       r = get_cmdvec(cmd, &cmdvec);
 
-       if (!cmdvec) {
+       if (r) {
+               if (cmdvec)
+                       free_keys(cmdvec);
                *reply = genhelp_handler();
                *len = strlen(*reply) + 1;
                return 0;
@@ -371,3 +396,141 @@ get_keyparam (vector v, int code)
 
        return NULL;
 }
+
+int
+cli_init (void) {
+       if (load_keys())
+               return 1;
+
+       if (alloc_handlers())
+               return 1;
+
+       add_handler(LIST+PATHS, NULL);
+       add_handler(LIST+MAPS, NULL);
+       add_handler(LIST+MAPS+STATUS, NULL);
+       add_handler(LIST+MAPS+STATS, NULL);
+       add_handler(LIST+MAPS+TOPOLOGY, NULL);
+       add_handler(LIST+TOPOLOGY, NULL);
+       add_handler(LIST+MAP+TOPOLOGY, NULL);
+       add_handler(LIST+CONFIG, NULL);
+       add_handler(LIST+BLACKLIST, NULL);
+       add_handler(LIST+DEVICES, NULL);
+       add_handler(ADD+PATH, NULL);
+       add_handler(DEL+PATH, NULL);
+       add_handler(ADD+MAP, NULL);
+       add_handler(DEL+MAP, NULL);
+       add_handler(SWITCH+MAP+GROUP, NULL);
+       add_handler(RECONFIGURE, NULL);
+       add_handler(SUSPEND+MAP, NULL);
+       add_handler(RESUME+MAP, NULL);
+       add_handler(REINSTATE+PATH, NULL);
+       add_handler(FAIL+PATH, NULL);
+
+       return 0;
+}
+
+static int
+key_match_fingerprint (struct key * kw, int fp)
+{
+       if (!fp)
+               return 0;
+
+       return ((fp & kw->code) == kw->code);
+}
+
+/*
+ * This is the readline completion handler
+ */
+char *
+key_generator (const char * str, int state)
+{
+       static int index, len, rlfp, has_param;
+       struct key * kw;
+       int i;
+       struct handler *h;
+       vector v;
+
+       if (!state) {
+               index = 0;
+               has_param = 0;
+               rlfp = 0;
+               len = strlen(str);
+               int r = get_cmdvec(rl_line_buffer, &v);
+               /*
+                * If a word completion is in progess, we don't want
+                * to take an exact keyword match in the fingerprint.
+                * For ex "show map[tab]" would validate "map" and discard
+                * "maps" as a valid candidate.
+                */
+               if (v && len)
+                       vector_del_slot(v, VECTOR_SIZE(v) - 1);
+               /*
+                * Clean up the mess if we dropped the last slot of a 1-slot
+                * vector
+                */
+               if (v && !VECTOR_SIZE(v)) {
+                       vector_free(v);
+                       v = NULL;
+               }
+               /*
+                * If last keyword takes a param, don't even try to guess
+                */
+               if (r == E_NOPARM) {
+                       has_param = 1;
+                       return (strdup("(value)"));
+               }
+               /*
+                * Compute a command fingerprint to find out possible completions.
+                * Once done, the vector is useless. Free it.
+                */
+               if (v) {
+                       rlfp = fingerprint(v);
+                       free_keys(v);
+               }
+       }
+       /*
+        * No more completions for parameter placeholder.
+        * Brave souls might try to add parameter completion by walking paths and
+        * multipaths vectors.
+        */
+       if (has_param)
+               return ((char *)NULL);
+       /*
+        * Loop through keywords for completion candidates
+        */
+       vector_foreach_slot_after (keys, kw, index) {
+               if (!strncmp(kw->str, str, len)) {
+                       /*
+                        * Discard keywords already in the command line
+                        */
+                       if (key_match_fingerprint(kw, rlfp)) {
+                               struct key * curkw = find_key(str);
+                               if (!curkw || (curkw != kw))
+                                       continue;
+                       }
+                       /*
+                        * Discard keywords making syntax errors.
+                        *
+                        * nfp is the candidate fingerprint we try to
+                        * validate against all known command fingerprints.
+                        */
+                       int nfp = rlfp | kw->code;
+                       vector_foreach_slot(handlers, h, i) {
+                               if (!rlfp || ((h->fingerprint & nfp) == nfp)) {
+                                       /*
+                                        * At least one full command is
+                                        * possible with this keyword :
+                                        * Consider it validated
+                                        */
+                                       index++;
+                                       return (strdup(kw->str));
+                               }
+                       }
+               }
+       }
+       /*
+        * No more candidates
+        */
+       return ((char *)NULL);
+}
+
index 99dfcbd..a2397df 100644 (file)
@@ -61,8 +61,11 @@ vector handlers;
 
 int alloc_handlers (void);
 int add_handler (int fp, int (*fn)(void *, char **, int *, void *));
+int set_handler_callback (int fp, int (*fn)(void *, char **, int *, void *));
 int parse_cmd (char * cmd, char ** reply, int * len, void *);
 int load_keys (void);
 char * get_keyparam (vector v, int code);
 void free_keys (vector vec);
 void free_handlers (vector vec);
+int cli_init (void);
+char * key_generator (const char * str, int state);
index dbd12bb..f2f9a96 100644 (file)
@@ -697,32 +697,29 @@ ueventloop (void * ap)
 static void *
 uxlsnrloop (void * ap)
 {
-       if (load_keys())
-               return NULL;
-       
-       if (alloc_handlers())
+       if (cli_init())
                return NULL;
 
-       add_handler(LIST+PATHS, cli_list_paths);
-       add_handler(LIST+MAPS, cli_list_maps);
-       add_handler(LIST+MAPS+STATUS, cli_list_maps_status);
-       add_handler(LIST+MAPS+STATS, cli_list_maps_stats);
-       add_handler(LIST+MAPS+TOPOLOGY, cli_list_maps_topology);
-       add_handler(LIST+TOPOLOGY, cli_list_maps_topology);
-       add_handler(LIST+MAP+TOPOLOGY, cli_list_map_topology);
-       add_handler(LIST+CONFIG, cli_list_config);
-       add_handler(LIST+BLACKLIST, cli_list_blacklist);
-       add_handler(LIST+DEVICES, cli_list_devices);
-       add_handler(ADD+PATH, cli_add_path);
-       add_handler(DEL+PATH, cli_del_path);
-       add_handler(ADD+MAP, cli_add_map);
-       add_handler(DEL+MAP, cli_del_map);
-       add_handler(SWITCH+MAP+GROUP, cli_switch_group);
-       add_handler(RECONFIGURE, cli_reconfigure);
-       add_handler(SUSPEND+MAP, cli_suspend);
-       add_handler(RESUME+MAP, cli_resume);
-       add_handler(REINSTATE+PATH, cli_reinstate);
-       add_handler(FAIL+PATH, cli_fail);
+       set_handler_callback(LIST+PATHS, cli_list_paths);
+       set_handler_callback(LIST+MAPS, cli_list_maps);
+       set_handler_callback(LIST+MAPS+STATUS, cli_list_maps_status);
+       set_handler_callback(LIST+MAPS+STATS, cli_list_maps_stats);
+       set_handler_callback(LIST+MAPS+TOPOLOGY, cli_list_maps_topology);
+       set_handler_callback(LIST+TOPOLOGY, cli_list_maps_topology);
+       set_handler_callback(LIST+MAP+TOPOLOGY, cli_list_map_topology);
+       set_handler_callback(LIST+CONFIG, cli_list_config);
+       set_handler_callback(LIST+BLACKLIST, cli_list_blacklist);
+       set_handler_callback(LIST+DEVICES, cli_list_devices);
+       set_handler_callback(ADD+PATH, cli_add_path);
+       set_handler_callback(DEL+PATH, cli_del_path);
+       set_handler_callback(ADD+MAP, cli_add_map);
+       set_handler_callback(DEL+MAP, cli_del_map);
+       set_handler_callback(SWITCH+MAP+GROUP, cli_switch_group);
+       set_handler_callback(RECONFIGURE, cli_reconfigure);
+       set_handler_callback(SUSPEND+MAP, cli_suspend);
+       set_handler_callback(RESUME+MAP, cli_resume);
+       set_handler_callback(REINSTATE+PATH, cli_reinstate);
+       set_handler_callback(FAIL+PATH, cli_fail);
 
        uxsock_listen(&uxsock_trigger, ap);
 
index ff7b578..009e5cb 100644 (file)
@@ -21,6 +21,9 @@
 #include <memory.h>
 #include <defaults.h>
 
+#include <vector.h>
+#include "cli.h"
+
 /*
  * process the client 
  */
@@ -29,6 +32,9 @@ static void process(int fd)
        char *line;
        char *reply;
 
+       cli_init();
+       rl_readline_name = "multipathd";
+       rl_completion_entry_function = key_generator;
        while ((line = readline("multipathd> "))) {
                size_t len;
                size_t llen = strlen(line);