/* * Copyright (c) 2005 Christophe Varoqui */ #include #include #include #include "memory.h" #include "vector.h" #include "structs.h" #include "structs_vec.h" #include "parser.h" #include "util.h" #include "version.h" #include #include "mpath_cmd.h" #include "cli.h" #include "debug.h" static vector keys; static vector handlers; static struct key * alloc_key (void) { return (struct key *)MALLOC(sizeof(struct key)); } static struct handler * alloc_handler (void) { return (struct handler *)MALLOC(sizeof(struct handler)); } static int add_key (vector vec, char * str, uint64_t code, int has_param) { struct key * kw; kw = alloc_key(); if (!kw) return 1; kw->code = code; kw->has_param = has_param; kw->str = STRDUP(str); if (!kw->str) goto out; if (!vector_alloc_slot(vec)) goto out1; vector_set_slot(vec, kw); return 0; out1: FREE(kw->str); out: FREE(kw); return 1; } int add_handler (uint64_t fp, int (*fn)(void *, char **, int *, void *)) { struct handler * h; h = alloc_handler(); if (!h) return 1; if (!vector_alloc_slot(handlers)) { FREE(h); return 1; } vector_set_slot(handlers, h); h->fingerprint = fp; h->fn = fn; return 0; } static struct handler * find_handler (uint64_t fp) { int i; struct handler *h; vector_foreach_slot (handlers, h, i) if (h->fingerprint == fp) return h; return NULL; } int set_handler_callback (uint64_t fp, int (*fn)(void *, char **, int *, void *)) { struct handler * h = find_handler(fp); if (!h) return 1; h->fn = fn; h->locked = 1; return 0; } int set_unlocked_handler_callback (uint64_t fp,int (*fn)(void *, char **, int *, void *)) { struct handler * h = find_handler(fp); if (!h) return 1; h->fn = fn; h->locked = 0; return 0; } static void free_key (struct key * kw) { if (kw->str) FREE(kw->str); if (kw->param) FREE(kw->param); FREE(kw); } void free_keys (vector vec) { int i; struct key * kw; vector_foreach_slot (vec, kw, i) free_key(kw); vector_free(vec); } void free_handlers (void) { int i; struct handler * h; vector_foreach_slot (handlers, h, i) FREE(h); vector_free(handlers); handlers = NULL; } int load_keys (void) { int r = 0; keys = vector_alloc(); if (!keys) return 1; r += add_key(keys, "list", LIST, 0); r += add_key(keys, "show", LIST, 0); r += add_key(keys, "add", ADD, 0); r += add_key(keys, "remove", DEL, 0); r += add_key(keys, "del", DEL, 0); r += add_key(keys, "switch", SWITCH, 0); r += add_key(keys, "switchgroup", SWITCH, 0); r += add_key(keys, "suspend", SUSPEND, 0); r += add_key(keys, "resume", RESUME, 0); r += add_key(keys, "reinstate", REINSTATE, 0); r += add_key(keys, "fail", FAIL, 0); r += add_key(keys, "resize", RESIZE, 0); r += add_key(keys, "reset", RESET, 0); r += add_key(keys, "reload", RELOAD, 0); r += add_key(keys, "forcequeueing", FORCEQ, 0); r += add_key(keys, "disablequeueing", DISABLEQ, 0); r += add_key(keys, "restorequeueing", RESTOREQ, 0); r += add_key(keys, "paths", PATHS, 0); r += add_key(keys, "maps", MAPS, 0); r += add_key(keys, "multipaths", MAPS, 0); r += add_key(keys, "path", PATH, 1); r += add_key(keys, "map", MAP, 1); r += add_key(keys, "multipath", MAP, 1); r += add_key(keys, "group", GROUP, 1); r += add_key(keys, "reconfigure", RECONFIGURE, 0); r += add_key(keys, "daemon", DAEMON, 0); r += add_key(keys, "status", STATUS, 0); r += add_key(keys, "stats", STATS, 0); r += add_key(keys, "topology", TOPOLOGY, 0); r += add_key(keys, "config", CONFIG, 0); r += add_key(keys, "blacklist", BLACKLIST, 0); r += add_key(keys, "devices", DEVICES, 0); r += add_key(keys, "raw", RAW, 0); r += add_key(keys, "wildcards", WILDCARDS, 0); r += add_key(keys, "quit", QUIT, 0); r += add_key(keys, "exit", QUIT, 0); r += add_key(keys, "shutdown", SHUTDOWN, 0); r += add_key(keys, "getprstatus", GETPRSTATUS, 0); r += add_key(keys, "setprstatus", SETPRSTATUS, 0); r += add_key(keys, "unsetprstatus", UNSETPRSTATUS, 0); r += add_key(keys, "format", FMT, 1); r += add_key(keys, "json", JSON, 0); r += add_key(keys, "getprkey", GETPRKEY, 0); r += add_key(keys, "setprkey", SETPRKEY, 0); r += add_key(keys, "unsetprkey", UNSETPRKEY, 0); r += add_key(keys, "key", KEY, 1); r += add_key(keys, "local", LOCAL, 0); r += add_key(keys, "setmarginal", SETMARGINAL, 0); r += add_key(keys, "unsetmarginal", UNSETMARGINAL, 0); if (r) { free_keys(keys); keys = NULL; return 1; } return 0; } static struct key * find_key (const char * str) { int i; int len, klen; struct key * kw = NULL; struct key * foundkw = NULL; len = strlen(str); vector_foreach_slot (keys, kw, i) { if (strncmp(kw->str, str, len)) continue; klen = strlen(kw->str); if (len == klen) return kw; /* exact match */ if (len < klen) { if (!foundkw) foundkw = kw; /* shortcut match */ else return NULL; /* ambiguous word */ } } return foundkw; } /* * get_cmdvec * * returns: * ENOMEM: not enough memory to allocate command * EAGAIN: command not found * EINVAL: argument missing for command */ static int get_cmdvec (char * cmd, vector *v) { int i; int r = 0; int get_param = 0; char * buff; struct key * kw = NULL; struct key * cmdkw = NULL; vector cmdvec, strvec; strvec = alloc_strvec(cmd); if (!strvec) return ENOMEM; cmdvec = vector_alloc(); if (!cmdvec) { free_strvec(strvec); return ENOMEM; } vector_foreach_slot(strvec, buff, i) { if (is_quote(buff)) continue; if (get_param) { get_param = 0; cmdkw->param = strdup(buff); continue; } kw = find_key(buff); if (!kw) { r = EAGAIN; goto out; } cmdkw = alloc_key(); if (!cmdkw) { r = ENOMEM; goto out; } if (!vector_alloc_slot(cmdvec)) { FREE(cmdkw); r = ENOMEM; goto out; } vector_set_slot(cmdvec, cmdkw); cmdkw->code = kw->code; cmdkw->has_param = kw->has_param; if (kw->has_param) get_param = 1; } if (get_param) { r = EINVAL; goto out; } *v = cmdvec; free_strvec(strvec); return 0; out: free_strvec(strvec); free_keys(cmdvec); return r; } static uint64_t fingerprint(vector vec) { int i; uint64_t fp = 0; struct key * kw; if (!vec) return 0; vector_foreach_slot(vec, kw, i) fp += kw->code; return fp; } int alloc_handlers (void) { handlers = vector_alloc(); if (!handlers) return 1; return 0; } static int genhelp_sprint_aliases (char * reply, int maxlen, vector keys, struct key * refkw) { int i, len = 0; struct key * kw; vector_foreach_slot (keys, kw, i) { if (kw->code == refkw->code && kw != refkw) { len += snprintf(reply + len, maxlen - len, "|%s", kw->str); if (len >= maxlen) return len; } } return len; } static int do_genhelp(char *reply, int maxlen, const char *cmd, int error) { int len = 0; int i, j; uint64_t fp; struct handler * h; struct key * kw; switch(error) { case ENOMEM: len += snprintf(reply + len, maxlen - len, "%s: Not enough memory\n", cmd); break; case EAGAIN: len += snprintf(reply + len, maxlen - len, "%s: not found\n", cmd); break; case EINVAL: len += snprintf(reply + len, maxlen - len, "%s: Missing argument\n", cmd); break; } if (len >= maxlen) goto out; len += snprintf(reply + len, maxlen - len, VERSION_STRING); if (len >= maxlen) goto out; len += snprintf(reply + len, maxlen - len, "CLI commands reference:\n"); if (len >= maxlen) goto out; vector_foreach_slot (handlers, h, i) { fp = h->fingerprint; vector_foreach_slot (keys, kw, j) { if ((kw->code & fp)) { fp -= kw->code; len += snprintf(reply + len , maxlen - len, " %s", kw->str); if (len >= maxlen) goto out; len += genhelp_sprint_aliases(reply + len, maxlen - len, keys, kw); if (len >= maxlen) goto out; if (kw->has_param) { len += snprintf(reply + len, maxlen - len, " $%s", kw->str); if (len >= maxlen) goto out; } } } len += snprintf(reply + len, maxlen - len, "\n"); if (len >= maxlen) goto out; } out: return len; } static char * genhelp_handler (const char *cmd, int error) { char * reply; char * p = NULL; int maxlen = INITIAL_REPLY_LEN; int again = 1; reply = MALLOC(maxlen); while (again) { if (!reply) return NULL; p = reply; p += do_genhelp(reply, maxlen, cmd, error); again = ((p - reply) >= maxlen); REALLOC_REPLY(reply, again, maxlen); } return reply; } int parse_cmd (char * cmd, char ** reply, int * len, void * data, int timeout ) { int r; struct handler * h; vector cmdvec = NULL; struct timespec tmo; r = get_cmdvec(cmd, &cmdvec); if (r) { *reply = genhelp_handler(cmd, r); if (*reply == NULL) return EINVAL; *len = strlen(*reply) + 1; return 0; } h = find_handler(fingerprint(cmdvec)); if (!h || !h->fn) { free_keys(cmdvec); *reply = genhelp_handler(cmd, EINVAL); if (*reply == NULL) return EINVAL; *len = strlen(*reply) + 1; return 0; } /* * execute handler */ if (clock_gettime(CLOCK_REALTIME, &tmo) == 0) { tmo.tv_sec += timeout; } else { tmo.tv_sec = 0; } if (h->locked) { int locked = 0; struct vectors * vecs = (struct vectors *)data; pthread_cleanup_push(cleanup_lock, &vecs->lock); if (tmo.tv_sec) { r = timedlock(&vecs->lock, &tmo); } else { lock(&vecs->lock); r = 0; } if (r == 0) { locked = 1; pthread_testcancel(); r = h->fn(cmdvec, reply, len, data); } pthread_cleanup_pop(locked); } else r = h->fn(cmdvec, reply, len, data); free_keys(cmdvec); return r; } char * get_keyparam (vector v, uint64_t code) { struct key * kw; int i; vector_foreach_slot(v, kw, i) if (kw->code == code) return kw->param; return NULL; } int cli_init (void) { if (load_keys()) return 1; if (alloc_handlers()) return 1; add_handler(LIST+PATHS, NULL); add_handler(LIST+PATHS+FMT, NULL); add_handler(LIST+PATHS+RAW+FMT, NULL); add_handler(LIST+PATH, NULL); add_handler(LIST+STATUS, NULL); add_handler(LIST+DAEMON, NULL); add_handler(LIST+MAPS, NULL); add_handler(LIST+MAPS+STATUS, NULL); add_handler(LIST+MAPS+STATS, NULL); add_handler(LIST+MAPS+FMT, NULL); add_handler(LIST+MAPS+RAW+FMT, NULL); add_handler(LIST+MAPS+TOPOLOGY, NULL); add_handler(LIST+MAPS+JSON, NULL); add_handler(LIST+TOPOLOGY, NULL); add_handler(LIST+MAP+TOPOLOGY, NULL); add_handler(LIST+MAP+JSON, NULL); add_handler(LIST+MAP+FMT, NULL); add_handler(LIST+MAP+RAW+FMT, NULL); add_handler(LIST+CONFIG, NULL); add_handler(LIST+CONFIG+LOCAL, NULL); add_handler(LIST+BLACKLIST, NULL); add_handler(LIST+DEVICES, NULL); add_handler(LIST+WILDCARDS, NULL); add_handler(RESET+MAPS+STATS, NULL); add_handler(RESET+MAP+STATS, NULL); add_handler(ADD+PATH, NULL); add_handler(DEL+PATH, NULL); add_handler(ADD+MAP, NULL); add_handler(DEL+MAP, NULL); add_handler(DEL+MAPS, NULL); add_handler(SWITCH+MAP+GROUP, NULL); add_handler(RECONFIGURE, NULL); add_handler(SUSPEND+MAP, NULL); add_handler(RESUME+MAP, NULL); add_handler(RESIZE+MAP, NULL); add_handler(RESET+MAP, NULL); add_handler(RELOAD+MAP, NULL); add_handler(DISABLEQ+MAP, NULL); add_handler(RESTOREQ+MAP, NULL); add_handler(DISABLEQ+MAPS, NULL); add_handler(RESTOREQ+MAPS, NULL); add_handler(REINSTATE+PATH, NULL); add_handler(FAIL+PATH, NULL); add_handler(QUIT, NULL); add_handler(SHUTDOWN, NULL); add_handler(GETPRSTATUS+MAP, NULL); add_handler(SETPRSTATUS+MAP, NULL); add_handler(UNSETPRSTATUS+MAP, NULL); add_handler(GETPRKEY+MAP, NULL); add_handler(SETPRKEY+MAP+KEY, NULL); add_handler(UNSETPRKEY+MAP, NULL); add_handler(FORCEQ+DAEMON, NULL); add_handler(RESTOREQ+DAEMON, NULL); add_handler(SETMARGINAL+PATH, NULL); add_handler(UNSETMARGINAL+PATH, NULL); add_handler(UNSETMARGINAL+MAP, NULL); return 0; } void cli_exit(void) { free_handlers(); free_keys(keys); keys = NULL; } static int key_match_fingerprint (struct key * kw, uint64_t 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, has_param; static uint64_t rlfp; struct key * kw; int i; struct handler *h; vector v = NULL; 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 progress, 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 == EINVAL) { 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. */ uint64_t 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); }