Newer
Older
/* Wildcard always matches, so we never do is_prefix on them */
t1 = ast_strdupa(token + 1); /* copy, skipping first char */
while (pos >= 0 && (s = strsep(&t1, cli_rsvd)) && *s) {
if (*s == '%') /* wildcard */
continue;
if (strncasecmp(s, word, lw)) /* no match */
continue;
(*actual)++;
if (pos-- == 0)
}
return NULL;
}
* \brief locate a cli command in the 'helpers' list (which must be locked).
* The search compares word by word taking care of regexps in e->cmda
* This function will return NULL when nothing is matched, or the ast_cli_entry that matched.
* \param cmds
* \param match_type has 3 possible values:
* 0 returns if the search key is equal or longer than the entry.
* note that trailing optional arguments are skipped.
* -1 true if the mismatch is on the last word XXX not true!
* 1 true only on complete, exact match.
static struct ast_cli_entry *find_cli(const char * const cmds[], int match_type)
int matchlen = -1; /* length of longest match so far */
struct ast_cli_entry *cand = NULL, *e=NULL;
Russell Bryant
committed
while ( (e = cli_next(e)) ) {
/* word-by word regexp comparison */
const char * const *src = cmds;
const char * const *dst = e->cmda;
int n = 0;
for (;; dst++, src += n) {
n = word_match(*src, *dst);
if (n < 0)
if (ast_strlen_zero(*dst) || ((*dst)[0] == '[' && ast_strlen_zero(dst[1]))) {
/* no more words in 'e' */
if (ast_strlen_zero(*src)) /* exact match, cannot do better */
/* Here, cmds has more words than the entry 'e' */
if (match_type != 0) /* but we look for almost exact match... */
continue; /* so we skip this one. */
/* otherwise we like it (case 0) */
} else { /* still words in 'e' */
if (ast_strlen_zero(*src))
continue; /* cmds is shorter than 'e', not good */
/* Here we have leftover words in cmds and 'e',
* but there is a mismatch. We only accept this one if match_type == -1
* and this is the last word for both.
*/
if (match_type != -1 || !ast_strlen_zero(src[1]) ||
!ast_strlen_zero(dst[1])) /* not the one we look for */
/* good, we are in case match_type == -1 and mismatch on last word */
if (src - cmds > matchlen) { /* remember the candidate */
matchlen = src - cmds;
return e ? e : cand;
static char *find_best(const char *argv[])
/* See how close we get, then print the candidate */
const char *myargv[AST_MAX_CMD_LEN] = { NULL, };
for (x = 0; argv[x]; x++) {
myargv[x] = argv[x];
if (!find_cli(myargv, -1))
break;
}
ast_join(cmdline, sizeof(cmdline), myargv);
static int cli_is_registered(struct ast_cli_entry *e)
{
struct ast_cli_entry *cur = NULL;
while ((cur = cli_next(cur))) {
if (cur == e) {
return 1;
}
}
return 0;
}
static int __ast_cli_unregister(struct ast_cli_entry *e, struct ast_cli_entry *ed)
Kevin P. Fleming
committed
if (e->inuse) {
ast_log(LOG_WARNING, "Can't remove command that is in use\n");
} else {
AST_RWLIST_WRLOCK(&helpers);
AST_RWLIST_REMOVE(&helpers, e, list);
AST_RWLIST_UNLOCK(&helpers);
Tilghman Lesher
committed
ast_free(e->_full_cmd);
if (e->handler) {
/* this is a new-style entry. Reset fields and free memory. */
char *cmda = (char *) e->cmda;
memset(cmda, '\0', sizeof(e->cmda));
Tilghman Lesher
committed
ast_free(e->command);
e->command = NULL;
e->usage = NULL;
}
static int __ast_cli_register(struct ast_cli_entry *e, struct ast_cli_entry *ed)
Kevin P. Fleming
committed
struct ast_cli_entry *cur;
int i, lf, ret = -1;
struct ast_cli_args a; /* fake argument */
char **dst = (char **)e->cmda; /* need to cast as the entry is readonly */
char *s;
AST_RWLIST_WRLOCK(&helpers);
if (cli_is_registered(e)) {
ast_log(LOG_WARNING, "Command '%s' already registered (the same ast_cli_entry)\n",
S_OR(e->_full_cmd, e->command));
ret = 0; /* report success */
goto done;
}
e->handler(e, CLI_INIT, &a);
/* XXX check that usage and command are filled up */
s = ast_skip_blanks(e->command);
s = e->command = ast_strdup(s);
for (i=0; !ast_strlen_zero(s) && i < AST_MAX_CMD_LEN-1; i++) {
*dst++ = s; /* store string */
s = ast_skip_nonblanks(s);
if (*s == '\0') /* we are done */
break;
*s++ = '\0';
s = ast_skip_blanks(s);
*dst++ = NULL;
if (find_cli(e->cmda, 1)) {
ast_log(LOG_WARNING, "Command '%s' already registered (or something close enough)\n",
S_OR(e->_full_cmd, e->command));
if (set_full_cmd(e)) {
ast_log(LOG_WARNING, "Error registering CLI Command '%s'\n",
S_OR(e->_full_cmd, e->command));
goto done;
lf = e->cmdlen;
AST_RWLIST_TRAVERSE_SAFE_BEGIN(&helpers, cur, list) {
int len = cur->cmdlen;
if (lf < len)
len = lf;
if (strncasecmp(e->_full_cmd, cur->_full_cmd, len) < 0) {
AST_RWLIST_INSERT_BEFORE_CURRENT(e, list);
Kevin P. Fleming
committed
if (!cur)
AST_RWLIST_INSERT_TAIL(&helpers, e, list);
ret = 0; /* success */
if (ret) {
ast_free(e->command);
e->command = NULL;
}
/* wrapper function, so we can unregister deprecated commands recursively */
int ast_cli_unregister(struct ast_cli_entry *e)
{
return __ast_cli_unregister(e, NULL);
}
/* wrapper function, so we can register deprecated commands recursively */
int ast_cli_register(struct ast_cli_entry *e)
{
return __ast_cli_register(e, NULL);
}
Kevin P. Fleming
committed
/*
* register/unregister an array of entries.
*/
Russell Bryant
committed
int ast_cli_register_multiple(struct ast_cli_entry *e, int len)
Kevin P. Fleming
committed
{
Russell Bryant
committed
int i, res = 0;
Kevin P. Fleming
committed
for (i = 0; i < len; i++)
Russell Bryant
committed
res |= ast_cli_register(e + i);
return res;
Kevin P. Fleming
committed
}
Russell Bryant
committed
int ast_cli_unregister_multiple(struct ast_cli_entry *e, int len)
Kevin P. Fleming
committed
{
Russell Bryant
committed
int i, res = 0;
Kevin P. Fleming
committed
for (i = 0; i < len; i++)
Russell Bryant
committed
res |= ast_cli_unregister(e + i);
return res;
Kevin P. Fleming
committed
}
/*! \brief helper for final part of handle_help
* if locked = 1, assume the list is already locked
static char *help1(int fd, const char * const match[], int locked)
char matchstr[80] = "";
Russell Bryant
committed
struct ast_cli_entry *e = NULL;
int len = 0;
int found = 0;
if (match) {
ast_join(matchstr, sizeof(matchstr), match);
len = strlen(matchstr);
}
if (!locked)
Russell Bryant
committed
while ( (e = cli_next(e)) ) {
if (e->_full_cmd[0] == '_')
if (match && strncasecmp(matchstr, e->_full_cmd, len))
continue;
ast_cli(fd, "%-30s -- %s\n", e->_full_cmd,
S_OR(e->summary, "<no description available>"));
ast_cli(fd, "No such command '%s'.\n", matchstr);
static char *handle_help(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
" When called with a topic as an argument, displays usage\n"
" information on the given command. If called without a\n"
" topic, it provides a list of commands.\n";
return NULL;
} else if (cmd == CLI_GENERATE) {
/* skip first 14 or 15 chars, "core show help " */
/* XXX watch out, should stop to the non-generator parts */
return __ast_cli_generator(a->line + l, a->word, a->n, 0);
}
my_e = find_cli(a->argv + 3, 1); /* try exact match first */
res = help1(a->fd, a->argv + 3, 1 /* locked */);
AST_RWLIST_UNLOCK(&helpers);
return res;
}
if (my_e->usage)
ast_cli(a->fd, "%s", my_e->usage);
ast_join(fullcmd, sizeof(fullcmd), a->argv + 3);
ast_cli(a->fd, "No help text available for '%s'.\n", fullcmd);
static char *parse_args(const char *s, int *argc, const char *argv[], int max, int *trailingwhitespace)
char *duplicate, *cur;
int x = 0;
int quoted = 0;
int escaped = 0;
int whitespace = 1;
if (trailingwhitespace == NULL)
trailingwhitespace = &dummy;
if (s == NULL) /* invalid, though! */
return NULL;
/* make a copy to store the parsed string */
if (!(duplicate = ast_strdup(s)))
cur = duplicate;
/* scan the original string copying into cur when needed */
for (; *s ; s++) {
if (x >= max - 1) {
ast_log(LOG_WARNING, "Too many arguments, truncating at %s\n", s);
break;
}
if (*s == '"' && !escaped) {
if (quoted && whitespace) {
/* start a quoted string from previous whitespace: new argument */
argv[x++] = cur;
whitespace = 0;
}
} else if ((*s == ' ' || *s == '\t') && !(quoted || escaped)) {
/* If we are not already in whitespace, and not in a quoted string or
processing an escape sequence, and just entered whitespace, then
finalize the previous argument and remember that we are in whitespace
*/
if (!whitespace) {
} else if (*s == '\\' && !escaped) {
escaped = 1;
} else {
if (whitespace) {
/* we leave whitespace, and are not quoted. So it's a new argument */
argv[x++] = cur;
whitespace = 0;
*cur++ = '\0';
/* XXX put a NULL in the last argument, because some functions that take
* the array may want a null-terminated array.
* argc still reflects the number of non-NULL entries.
*/
argv[x] = NULL;
*argc = x;
return duplicate;
/*! \brief Return the number of unique matches for the generator */
Russell Bryant
committed
int ast_cli_generatornummatches(const char *text, const char *word)
while ((buf = ast_cli_generator(text, word, i++))) {
if (!oldbuf || strcmp(buf,oldbuf))
matches++;
if (oldbuf)
Tilghman Lesher
committed
ast_free(oldbuf);
Tilghman Lesher
committed
ast_free(oldbuf);
Russell Bryant
committed
char **ast_cli_completion_matches(const char *text, const char *word)
{
char **match_list = NULL, *retstr, *prevstr;
size_t match_list_len, max_equal, which, i;
int matches = 0;
/* leave entry 0 free for the longest common substring */
match_list_len = 1;
while ((retstr = ast_cli_generator(text, word, matches)) != NULL) {
if (matches + 1 >= match_list_len) {
match_list_len <<= 1;
Russell Bryant
committed
if (!(match_list = ast_realloc(match_list, match_list_len * sizeof(*match_list))))
return NULL;
}
match_list[++matches] = retstr;
}
if (!match_list)
return match_list; /* NULL */
/* Find the longest substring that is common to all results
* (it is a candidate for completion), and store a copy in entry 0.
*/
prevstr = match_list[1];
max_equal = strlen(prevstr);
for (which = 2; which <= matches; which++) {
for (i = 0; i < max_equal && toupper(prevstr[i]) == toupper(match_list[which][i]); i++)
if (!(retstr = ast_malloc(max_equal + 1))) {
ast_free(match_list);
Russell Bryant
committed
return NULL;
ast_copy_string(retstr, match_list[1], max_equal + 1);
/* ensure that the array is NULL terminated */
Russell Bryant
committed
if (matches + 1 >= match_list_len) {
if (!(match_list = ast_realloc(match_list, (match_list_len + 1) * sizeof(*match_list)))) {
ast_free(retstr);
Russell Bryant
committed
return NULL;
Russell Bryant
committed
}
match_list[matches + 1] = NULL;
return match_list;
/*! \brief returns true if there are more words to match */
static int more_words (const char * const *dst)
{
int i;
for (i = 0; dst[i]; i++) {
if (dst[i][0] != '[')
return -1;
}
return 0;
}
/*
* generate the entry at position 'state'
*/
Russell Bryant
committed
static char *__ast_cli_generator(const char *text, const char *word, int state, int lock)
const char *argv[AST_MAX_ARGS];
Russell Bryant
committed
struct ast_cli_entry *e = NULL;
int x = 0, argindex, matchlen;
Joshua Colp
committed
int tws = 0;
/* Split the argument into an array of words */
char *duplicate = parse_args(text, &x, argv, ARRAY_LEN(argv), &tws);
if (!duplicate) /* malloc error */
/* Compute the index of the last argument (could be an empty string) */
argindex = (!ast_strlen_zero(word) && x>0) ? x-1 : x;
/* rebuild the command, ignore terminating white space and flatten space */
ast_join(matchstr, sizeof(matchstr)-1, argv);
matchlen = strlen(matchstr);
Joshua Colp
committed
if (tws) {
strcat(matchstr, " "); /* XXX */
if (matchlen)
matchlen++;
}
Russell Bryant
committed
while ( (e = cli_next(e)) ) {
/* XXX repeated code */
int src = 0, dst = 0, n = 0;
if (e->command[0] == '_')
continue;
/*
* Try to match words, up to and excluding the last word, which
* is either a blank or something that we want to extend.
*/
for (;src < argindex; dst++, src += n) {
n = word_match(argv[src], e->cmda[dst]);
if (n < 0)
if (src != argindex && more_words(e->cmda + dst)) /* not a match */
continue;
ret = is_prefix(argv[src], e->cmda[dst], state - matchnum, &n);
matchnum += n; /* this many matches here */
if (ret) {
/*
* argv[src] is a valid prefix of the next word in this
* command. If this is also the correct entry, return it.
*/
if (matchnum > state)
break;
Tilghman Lesher
committed
ast_free(ret);
ret = NULL;
} else if (ast_strlen_zero(e->cmda[dst])) {
/*
* This entry is a prefix of the command string entered
* (only one entry in the list should have this property).
* Run the generator if one is available. In any case we are done.
*/
if (e->handler) { /* new style command */
struct ast_cli_args a = {
.line = matchstr, .word = word,
.pos = argindex,
.n = state - matchnum,
.argv = argv,
.argc = x};
ret = e->handler(e, CLI_GENERATE, &a);
}
if (ret)
break;
ast_free(duplicate);
Russell Bryant
committed
char *ast_cli_generator(const char *text, const char *word, int state)
{
return __ast_cli_generator(text, word, state, 1);
}
int ast_cli_command_full(int uid, int gid, int fd, const char *s)
const char *args[AST_MAX_ARGS + 1];
char *duplicate = parse_args(s, &x, args + 1, AST_MAX_ARGS, NULL);
char *retval = NULL;
struct ast_cli_args a = {
.fd = fd, .argc = x, .argv = args+1 };
if (duplicate == NULL)
return -1;
if (x < 1) /* We need at least one entry, otherwise ignore */
goto done;
e = find_cli(args + 1, 0);
if (e)
ast_atomic_fetchadd_int(&e->inuse, 1);
ast_cli(fd, "No such command '%s' (type 'core show help %s' for other possible commands)\n", s, find_best(args + 1));
ast_join(tmp, sizeof(tmp), args + 1);
/* Check if the user has rights to run this command. */
if (!cli_has_permissions(uid, gid, tmp)) {
ast_cli(fd, "You don't have permissions to run '%s' command\n", tmp);
ast_free(duplicate);
return 0;
}
/*
* Within the handler, argv[-1] contains a pointer to the ast_cli_entry.
* Remember that the array returned by parse_args is NULL-terminated.
*/
args[0] = (char *)e;
retval = e->handler(e, CLI_HANDLER, &a);
if (retval == CLI_SHOWUSAGE) {
ast_cli(fd, "%s", S_OR(e->usage, "Invalid usage, but no usage information available.\n"));
} else {
if (retval == CLI_FAILURE)
ast_cli(fd, "Command '%s' failed.\n", s);
ast_atomic_fetchadd_int(&e->inuse, -1);
done:
ast_free(duplicate);
int ast_cli_command_multiple_full(int uid, int gid, int fd, size_t size, const char *s)
{
char cmd[512];
int x, y = 0, count = 0;
for (x = 0; x < size; x++) {
cmd[y] = s[x];
y++;
if (s[x] == '\0') {
ast_cli_command_full(uid, gid, fd, cmd);
y = 0;
count++;
}
}
return count;
}