Newer
Older
ast_register_cleanup(cli_shutdown);
/*!
* match a word in the CLI entry.
* returns -1 on mismatch, 0 on match of an optional word,
* 1 on match of a full word.
*
* The pattern can be
* any_word match for equal
* [foo|bar|baz] optionally, one of these words
* {foo|bar|baz} exactly, one of these words
* % any word
*/
static int word_match(const char *cmd, const char *cli_word)
{
int l;
char *pos;
if (ast_strlen_zero(cmd) || ast_strlen_zero(cli_word))
return -1;
if (!strchr(cli_rsvd, cli_word[0])) /* normal match */
return (strcasecmp(cmd, cli_word) == 0) ? 1 : -1;
l = strlen(cmd);
/* wildcard match - will extend in the future */
if (l > 0 && cli_word[0] == '%') {
return 1; /* wildcard */
}
/* Start a search for the command entered against the cli word in question */
pos = strcasestr(cli_word, cmd);
while (pos) {
/*
*Check if the word matched with is surrounded by reserved characters on both sides
* and isn't at the beginning of the cli_word since that would make it check in a location we shouldn't know about.
* If it is surrounded by reserved chars and isn't at the beginning, it's a match.
*/
if (pos != cli_word && strchr(cli_rsvd, pos[-1]) && strchr(cli_rsvd, pos[l])) {
return 1; /* valid match */
}
/* Ok, that one didn't match, strcasestr to the next appearance of the command and start over.*/
pos = strcasestr(pos + 1, cmd);
}
/* If no matches were found over the course of the while loop, we hit the end of the string. It's a mismatch. */
return -1;
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
}
/*! \brief if word is a valid prefix for token, returns the pos-th
* match as a malloced string, or NULL otherwise.
* Always tell in *actual how many matches we got.
*/
static char *is_prefix(const char *word, const char *token,
int pos, int *actual)
{
int lw;
char *s, *t1;
*actual = 0;
if (ast_strlen_zero(token))
return NULL;
if (ast_strlen_zero(word))
word = ""; /* dummy */
lw = strlen(word);
if (strcspn(word, cli_rsvd) != lw)
return NULL; /* no match if word has reserved chars */
if (strchr(cli_rsvd, token[0]) == NULL) { /* regular match */
if (strncasecmp(token, word, lw)) /* no match */
return NULL;
*actual = 1;
return (pos != 0) ? NULL : ast_strdup(token);
}
/* now handle regexp match */
/* 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 void remove_shutdown_command(struct ast_cli_entry *e)
{
ast_rwlock_wrlock(&shutdown_commands_lock);
AST_VECTOR_REMOVE_ELEM_UNORDERED(&shutdown_commands, e, AST_VECTOR_ELEM_CLEANUP_NOOP);
ast_rwlock_unlock(&shutdown_commands_lock);
}
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;
/* Remove leading spaces from the command */
while (isspace(*s)) {
cur++;
s++;
}
/* 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)
int matches;
struct ast_vector_string *vec = ast_cli_completion_vector(text, word);
if (!vec) {
return 0;
}
matches = AST_VECTOR_SIZE(vec) - 1;
AST_VECTOR_CALLBACK_VOID(vec, ast_free);
AST_VECTOR_PTR_FREE(vec);
char **ast_cli_completion_matches(const char *text, const char *word)
struct ast_vector_string *vec = ast_cli_completion_vector(text, word);
char **match_list;
if (!vec) {
return NULL;
}
if (AST_VECTOR_APPEND(vec, NULL)) {
/* We failed to NULL terminate the elements */
AST_VECTOR_CALLBACK_VOID(vec, ast_free);
AST_VECTOR_PTR_FREE(vec);
return NULL;
match_list = AST_VECTOR_STEAL_ELEMENTS(vec);
AST_VECTOR_PTR_FREE(vec);
return match_list;
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
AST_THREADSTORAGE_RAW(completion_storage);
/*!
* \internal
* \brief Add a value to the vector.
*
* \param vec Vector to add \a value to. Must be from threadstorage.
* \param value The value to add.
*
* \retval 0 Success
* \retval -1 Failure
*/
static int cli_completion_vector_add(struct ast_vector_string *vec, char *value)
{
if (!value) {
return 0;
}
if (!vec || AST_VECTOR_ADD_SORTED(vec, value, strcasecmp)) {
if (vec) {
ast_threadstorage_set_ptr(&completion_storage, NULL);
AST_VECTOR_CALLBACK_VOID(vec, ast_free);
AST_VECTOR_FREE(vec);
}
ast_free(value);
return -1;
}
return 0;
}
int ast_cli_completion_add(char *value)
{
return cli_completion_vector_add(ast_threadstorage_get_ptr(&completion_storage), value);
}
struct ast_vector_string *ast_cli_completion_vector(const char *text, const char *word)
char *retstr, *prevstr;
size_t max_equal;
size_t which = 0;
struct ast_vector_string *vec = ast_calloc(1, sizeof(*vec));
/* Recursion into this function is a coding error. */
ast_assert(!ast_threadstorage_get_ptr(&completion_storage));
if (!vec) {
return NULL;
}
if (ast_threadstorage_set_ptr(&completion_storage, vec)) {
ast_log(LOG_ERROR, "Failed to initialize threadstorage for completion.\n");
ast_free(vec);
return NULL;
}
while ((retstr = ast_cli_generator(text, word, which)) != NULL) {
if (cli_completion_vector_add(vec, retstr)) {
ast_threadstorage_set_ptr(&completion_storage, NULL);
ast_threadstorage_set_ptr(&completion_storage, NULL);
if (!AST_VECTOR_SIZE(vec)) {
AST_VECTOR_PTR_FREE(vec);
return NULL;
prevstr = AST_VECTOR_GET(vec, 0);
max_equal = strlen(prevstr);
which = 1;
/* Find the longest substring that is common to all results
* (it is a candidate for completion), and store a copy in entry 0.
*/
while (which < AST_VECTOR_SIZE(vec)) {
size_t i = 0;
retstr = AST_VECTOR_GET(vec, which);
/* Check for and remove duplicate strings. */
if (!strcasecmp(prevstr, retstr)) {
AST_VECTOR_REMOVE(vec, which, 1);
ast_free(retstr);
}
while (i < max_equal && toupper(prevstr[i]) == toupper(retstr[i])) {
i++;
}
/* Insert longest match to position 0. */
retstr = ast_strndup(AST_VECTOR_GET(vec, 0), max_equal);
if (!retstr || AST_VECTOR_INSERT_AT(vec, 0, retstr)) {
ast_free(retstr);
vector_cleanup:
AST_VECTOR_CALLBACK_VOID(vec, ast_free);
AST_VECTOR_PTR_FREE(vec);
return NULL;
/*! \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);
}
static int allowed_on_shutdown(struct ast_cli_entry *e)
{
int found = 0;
int i;
ast_rwlock_rdlock(&shutdown_commands_lock);
for (i = 0; i < AST_VECTOR_SIZE(&shutdown_commands); ++i) {
if (e == AST_VECTOR_GET(&shutdown_commands, i)) {
found = 1;
break;
}
}
ast_rwlock_unlock(&shutdown_commands_lock);
return found;
}
int ast_cli_command_full(int uid, int gid, int fd, const char *s)
const char *args[AST_MAX_ARGS + 1];
struct ast_cli_entry *e = NULL;
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));
if (ast_shutting_down() && !allowed_on_shutdown(e)) {
ast_cli(fd, "Command '%s' cannot be run during shutdown\n", s);
goto done;
}
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);
/*
* 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);
if (e) {
ast_atomic_fetchadd_int(&e->inuse, -1);
}
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;
}
int ast_cli_allow_at_shutdown(struct ast_cli_entry *e)
{
int res;
ast_rwlock_wrlock(&shutdown_commands_lock);
res = AST_VECTOR_APPEND(&shutdown_commands, e);
ast_rwlock_unlock(&shutdown_commands_lock);
return res;
}