Newer
Older
Russell Bryant
committed
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
struct ast_channel *c = NULL;
int numchans = 0;
struct ast_var_t *current;
struct varshead *headp;
regex_t regexbuf;
int havepattern = 0;
if (argc < 3 || argc > 4)
return RESULT_SHOWUSAGE;
if (argc == 4) {
if (regcomp(®exbuf, argv[3], REG_EXTENDED | REG_NOSUB))
return RESULT_SHOWUSAGE;
havepattern = 1;
}
ast_cli(fd, FORMAT_STRING, "Channel", "Group", "Category");
while ( (c = ast_channel_walk_locked(c)) != NULL) {
headp=&c->varshead;
AST_LIST_TRAVERSE(headp,current,entries) {
if (!strncmp(ast_var_name(current), GROUP_CATEGORY_PREFIX "_", strlen(GROUP_CATEGORY_PREFIX) + 1)) {
if (!havepattern || !regexec(®exbuf, ast_var_value(current), 0, NULL, 0)) {
ast_cli(fd, FORMAT_STRING, c->name, ast_var_value(current),
(ast_var_name(current) + strlen(GROUP_CATEGORY_PREFIX) + 1));
numchans++;
}
} else if (!strcmp(ast_var_name(current), GROUP_CATEGORY_PREFIX)) {
if (!havepattern || !regexec(®exbuf, ast_var_value(current), 0, NULL, 0)) {
ast_cli(fd, FORMAT_STRING, c->name, ast_var_value(current), "(default)");
numchans++;
}
}
}
numchans++;
ast_channel_unlock(c);
Russell Bryant
committed
}
if (havepattern)
regfree(®exbuf);
ast_cli(fd, "%d active channel%s\n", numchans, (numchans != 1) ? "s" : "");
return RESULT_SUCCESS;
#undef FORMAT_STRING
}
static int handle_help(int fd, int argc, char *argv[]);
Russell Bryant
committed
static char * complete_help(const char *text, const char *word, int pos, int state)
{
/* skip first 4 or 5 chars, "help "*/
int l = strlen(text);
if (l > 5)
l = 5;
text += l;
/* XXX watch out, should stop to the non-generator parts */
return __ast_cli_generator(text, word, state, 0);
/* XXX Nothing in this array can currently be deprecated...
You have to change the way find_cli works in order to remove this array
I recommend doing this eventually...
*/
/* Keep alphabetized, with longer matches first (example: abcd before abc) */
{ { "_command", "complete", NULL },
handle_commandcomplete, "Command complete",
commandcomplete_help },
{ { "_command", "nummatches", NULL },
handle_commandnummatches, "Returns number of command matches",
commandnummatches_help },
{ { "_command", "matchesarray", NULL },
handle_commandmatchesarray, "Returns command matches array",
commandmatchesarray_help },
static struct ast_cli_entry cli_debug_channel_deprecated = {
{ "debug", "channel", NULL },
handle_debugchan_deprecated, NULL,
NULL, complete_ch_3 };
static struct ast_cli_entry cli_module_load_deprecated = {
{ "load", NULL },
handle_load_deprecated, NULL,
NULL, complete_fn };
static struct ast_cli_entry cli_module_reload_deprecated = {
{ "reload", NULL },
handle_reload_deprecated, NULL,
NULL, complete_mod_2 };
static struct ast_cli_entry cli_module_unload_deprecated = {
{ "unload", NULL },
handle_unload_deprecated, NULL,
NULL, complete_mod_2 };
static struct ast_cli_entry cli_cli[] = {
/* Deprecated, but preferred command is now consolidated (and already has a deprecated command for it). */
{ { "no", "debug", "channel", NULL },
handle_nodebugchan_deprecated, NULL,
NULL, complete_ch_4 },
{ { "core", "show", "channels", NULL },
handle_chanlist, "Display information on channels",
chanlist_help, complete_show_channels },
{ { "core", "show", "channel", NULL },
handle_showchan, "Display information on a specific channel",
showchan_help, complete_ch_4 },
{ { "core", "set", "debug", "channel", NULL },
handle_core_set_debug_channel, "Enable/disable debugging on a channel",
debugchan_help, complete_ch_5, &cli_debug_channel_deprecated },
NEW_CLI(handle_set_debug, "Set level of debug chattiness"),
NEW_CLI(handle_verbose, "Set level of verboseness"),
{ { "group", "show", "channels", NULL },
group_show_channels, "Display active channels with group(s)",
{ { "help", NULL },
handle_help, "Display help list, or specific help on a command",
help_help, complete_help },
{ { "logger", "mute", NULL },
handle_logger_mute, "Toggle logging output to a console",
logger_mute_help },
NEW_CLI(handle_modlist, "List modules and info"),
{ { "module", "load", NULL },
handle_load, "Load a module by name",
load_help, complete_fn, &cli_module_load_deprecated },
{ { "module", "reload", NULL },
handle_reload, "Reload configuration",
reload_help, complete_mod_3, &cli_module_reload_deprecated },
{ { "module", "unload", NULL },
handle_unload, "Unload a module by name",
unload_help, complete_mod_3_nr, &cli_module_unload_deprecated },
Luigi Rizzo
committed
NEW_CLI(handle_showuptime, "Show uptime information"),
{ { "soft", "hangup", NULL },
handle_softhangup, "Request a hangup on a given channel",
softhangup_help, complete_ch_3 },
};
/*! \brief initialize the _full_cmd string in * each of the builtins. */
void ast_builtins_init(void)
struct ast_cli_entry *e;
for (e = builtins; e->cmda[0] != NULL; e++) {
char buf[80];
ast_join(buf, sizeof(buf), e->cmda);
e->_full_cmd = strdup(buf);
if (!e->_full_cmd)
ast_log(LOG_WARNING, "-- cannot allocate <%s>\n", buf);
ast_cli_register_multiple(cli_cli, sizeof(cli_cli) / sizeof(struct ast_cli_entry));
/*
* We have two sets of commands: builtins are stored in a
* NULL-terminated array of ast_cli_entry, whereas external
* commands are in a list.
* When navigating, we need to keep two pointers and get
* the next one in lexicographic order. For the purpose,
* we use a structure.
*/
struct cli_iterator {
struct ast_cli_entry *builtins;
struct ast_cli_entry *helpers;
};
static struct ast_cli_entry *cli_next(struct cli_iterator *i)
struct ast_cli_entry *e;
Russell Bryant
committed
if (i->builtins == NULL && i->helpers == NULL) {
/* initialize */
i->builtins = builtins;
i->helpers = AST_LIST_FIRST(&helpers);
}
e = i->builtins; /* temporary */
if (!e->cmda[0] || (i->helpers &&
strcmp(i->helpers->_full_cmd, e->_full_cmd) < 0)) {
/* Use helpers */
e = i->helpers;
if (e)
i->helpers = AST_LIST_NEXT(e, list);
} else { /* use builtin. e is already set */
(i->builtins)++; /* move to next */
}
return e;
/*!
* \brief locate a cli command in the 'helpers' list (which must be locked).
* exact has 3 values:
* 0 returns if the search key is equal or longer than the entry.
* -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(char *const cmds[], int match_type)
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
int matchlen = -1; /* length of longest match so far */
struct ast_cli_entry *cand = NULL, *e=NULL;
struct cli_iterator i = { NULL, NULL};
while( (e = cli_next(&i)) ) {
int y;
for (y = 0 ; cmds[y] && e->cmda[y]; y++) {
if (strcasecmp(e->cmda[y], cmds[y]))
break;
}
if (e->cmda[y] == NULL) { /* no more words in candidate */
if (cmds[y] == NULL) /* this is an exact match, cannot do better */
break;
/* here the search key is longer than the candidate */
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 candidate */
if (cmds[y] == NULL) /* search key is shorter, not good */
continue;
/* if we get here, both words exist but there is a mismatch */
if (match_type == 0) /* not the one we look for */
continue;
if (match_type == 1) /* not the one we look for */
continue;
if (cmds[y+1] != NULL || e->cmda[y+1] != NULL) /* not the one we look for */
continue;
/* we are in case match_type == -1 and mismatch on last word */
}
if (cand == NULL || y > matchlen) /* remember the candidate */
cand = e;
return e ? e : cand;
static char *find_best(char *argv[])
{
static char cmdline[80];
int x;
/* See how close we get, then print the candidate */
char *myargv[AST_MAX_CMD_LEN];
for (x=0;x<AST_MAX_CMD_LEN;x++)
myargv[x]=NULL;
AST_LIST_LOCK(&helpers);
for (x=0;argv[x];x++) {
myargv[x] = argv[x];
if (!find_cli(myargv, -1))
break;
}
AST_LIST_UNLOCK(&helpers);
ast_join(cmdline, sizeof(cmdline), myargv);
static int __ast_cli_unregister(struct ast_cli_entry *e, struct ast_cli_entry *ed)
if (e->deprecate_cmd) {
__ast_cli_unregister(e->deprecate_cmd, e);
}
Kevin P. Fleming
committed
if (e->inuse) {
ast_log(LOG_WARNING, "Can't remove command that is in use\n");
} else {
AST_LIST_LOCK(&helpers);
AST_LIST_REMOVE(&helpers, e, list);
AST_LIST_UNLOCK(&helpers);
e->_full_cmd = NULL;
if (e->command) {
/* this is a new-style entry. Reset fields and free memory. */
((char **)e->cmda)[0] = NULL;
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;
char fulle[80] ="";
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
int i, lf, ret = -1;
if (e->cmda[0] == NULL) { /* new style entry, run the handler to init fields */
char *args[2] = { (char *)e, NULL };
char *s = (char *)(e->handler(-1, CLI_CMD_STRING, args+1));
char **dst = (char **)e->cmda; /* need to cast as the entry is readonly */
s = ast_skip_blanks(s);
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;
e->usage = (char *)(e->handler(-1, CLI_USAGE, args+1));
}
for (i = 0; e->cmda[i]; i++)
;
e->args = i;
ast_join(fulle, sizeof(fulle), e->cmda);
Kevin P. Fleming
committed
AST_LIST_LOCK(&helpers);
if (find_cli(e->cmda, 1)) {
Kevin P. Fleming
committed
AST_LIST_UNLOCK(&helpers);
ast_log(LOG_WARNING, "Command '%s' already registered (or something close enough)\n", fulle);
e->_full_cmd = ast_strdup(fulle);
if (!e->_full_cmd)
goto done;
if (ed) {
e->deprecated = 1;
e->summary = ed->summary;
e->usage = ed->usage;
/* XXX If command A deprecates command B, and command B deprecates command C...
Do we want to show command A or command B when telling the user to use new syntax?
This currently would show command A.
To show command B, you just need to always use ed->_full_cmd.
*/
e->_deprecated_by = S_OR(ed->_deprecated_by, ed->_full_cmd);
} else {
e->deprecated = 0;
lf = strlen(fulle);
Kevin P. Fleming
committed
AST_LIST_TRAVERSE_SAFE_BEGIN(&helpers, cur, list) {
int len = strlen(cur->_full_cmd);
if (lf < len)
len = lf;
if (strncasecmp(fulle, cur->_full_cmd, len) < 0) {
Kevin P. Fleming
committed
AST_LIST_INSERT_BEFORE_CURRENT(&helpers, e, list);
Kevin P. Fleming
committed
AST_LIST_TRAVERSE_SAFE_END;
Kevin P. Fleming
committed
if (!cur)
AST_LIST_INSERT_TAIL(&helpers, e, list);
ret = 0; /* success */
Kevin P. Fleming
committed
AST_LIST_UNLOCK(&helpers);
if (e->deprecate_cmd) {
/* This command deprecates another command. Register that one also. */
__ast_cli_register(e->deprecate_cmd, e);
}
/* 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.
*/
void ast_cli_register_multiple(struct ast_cli_entry *e, int len)
{
int i;
for (i = 0; i < len; i++)
Kevin P. Fleming
committed
ast_cli_register(e + i);
}
void ast_cli_unregister_multiple(struct ast_cli_entry *e, int len)
{
int i;
for (i = 0; i < len; i++)
Kevin P. Fleming
committed
ast_cli_unregister(e + i);
}
/*! \brief helper for help_workhorse and final part of
* handle_help. if locked = 0 it's just help_workhorse,
* otherwise assume the list is already locked and print
* an error message if not found.
*/
static int help1(int fd, char *match[], int locked)
char matchstr[80] = "";
struct ast_cli_entry *e;
int len = 0;
int found = 0;
struct cli_iterator i = { NULL, NULL};
if (match) {
ast_join(matchstr, sizeof(matchstr), match);
len = strlen(matchstr);
}
if (!locked)
AST_LIST_LOCK(&helpers);
while ( (e = cli_next(&i)) ) {
if (e->_full_cmd[0] == '_')
/* Hide commands that are marked as deprecated. */
if (e->deprecated)
continue;
if (match && strncasecmp(matchstr, e->_full_cmd, len))
continue;
ast_cli(fd, "%25.25s %s\n", e->_full_cmd, S_OR(e->summary, "<no description available>"));
AST_LIST_UNLOCK(&helpers);
if (!locked && !found && matchstr[0])
ast_cli(fd, "No such command '%s'.\n", matchstr);
static int help_workhorse(int fd, char *match[])
{
return help1(fd, match, 0 /* do not print errors */);
}
static int handle_help(int fd, int argc, char *argv[])
{
struct ast_cli_entry *e;
if (argc < 1)
AST_LIST_LOCK(&helpers);
e = find_cli(argv + 1, 1); /* try exact match first */
if (!e)
return help1(fd, argv + 1, 1 /* locked */);
if (e->usage)
ast_cli(fd, "%s", e->usage);
else {
ast_join(fullcmd, sizeof(fullcmd), argv+1);
ast_cli(fd, "No help text available for '%s'.\n", fullcmd);
AST_LIST_UNLOCK(&helpers);
Russell Bryant
committed
static char *parse_args(const char *s, int *argc, char *argv[], int max, int *trailingwhitespace)
int x = 0;
int quoted = 0;
int escaped = 0;
int whitespace = 1;
if (s == NULL) /* invalid, though! */
return NULL;
/* make a copy to store the parsed string */
Russell Bryant
committed
if (!(dup = ast_strdup(s)))
return NULL;
cur = dup;
/* 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;
/*! \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)
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++)
Russell Bryant
committed
if (!(retstr = ast_malloc(max_equal + 1)))
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))))
return NULL;
}
match_list[matches + 1] = NULL;
return match_list;
Russell Bryant
committed
static char *__ast_cli_generator(const char *text, const char *word, int state, int lock)
struct ast_cli_entry *e;
struct cli_iterator i = { NULL, NULL };
int x = 0, argindex, matchlen;
Joshua Colp
committed
int tws = 0;
char *dup = parse_args(text, &x, argv, sizeof(argv) / sizeof(argv[0]), &tws);
if (!dup) /* error */
return NULL;
argindex = (!ast_strlen_zero(word) && x>0) ? x-1 : x;
/* rebuild the command, ignore tws */
ast_join(matchstr, sizeof(matchstr)-1, argv);
matchlen = strlen(matchstr);
Joshua Colp
committed
if (tws) {
strcat(matchstr, " "); /* XXX */
if (matchlen)
matchlen++;
}
if (lock)
AST_LIST_LOCK(&helpers);
while ( (e = cli_next(&i)) ) {
int lc = strlen(e->_full_cmd);
if (e->_full_cmd[0] != '_' && lc > 0 && matchlen <= lc &&
!strncasecmp(matchstr, e->_full_cmd, matchlen)) {
/* Found initial part, return a copy of the next word... */
if (e->cmda[argindex] && ++matchnum > state) {
ret = strdup(e->cmda[argindex]); /* we need a malloced string */
break;
}
} else if (!strncasecmp(matchstr, e->_full_cmd, lc) && matchstr[lc] < 33) {
/* 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.
*/
ret = e->generator(matchstr, word, argindex, state - matchnum);
else if (e->command) { /* new style command */
/* prepare fake arguments for the generator.
* argv[-1] is the cli entry we use,
* argv[0] is a pointer to the generator arguments,
* with a fake string '-' at the beginning so we can
* dereference it as a string with no trouble,
* and then the usual NULL terminator.
*/
struct ast_cli_args a = {
.fake = "-",
.line = matchstr, .word = word,
.pos = argindex,
char *args[] = { (char *)e, (char *)&a, NULL };
ret = (char *)e->handler(-1, CLI_GENERATE, args + 1);
}
if (ret)
break;
if (lock)
AST_LIST_UNLOCK(&helpers);
free(dup);
return ret;
Russell Bryant
committed
char *ast_cli_generator(const char *text, const char *word, int state)
{
return __ast_cli_generator(text, word, state, 1);
}
Russell Bryant
committed
int ast_cli_command(int fd, const char *s)
char *args[AST_MAX_ARGS + 1];
struct ast_cli_entry *e;
int x;
char *dup;
Russell Bryant
committed
if (!(dup = parse_args(s, &x, args + 1, AST_MAX_ARGS, &tws)))
return -1;
/* We need at least one entry, or ignore */
if (x > 0) {
Kevin P. Fleming
committed
AST_LIST_LOCK(&helpers);
e = find_cli(args + 1, 0);
if (e)
e->inuse++;
Kevin P. Fleming
committed
AST_LIST_UNLOCK(&helpers);
if (e) {
/* within calling the handler, argv[-1] contains a pointer
* to the cli entry, and the array is null-terminated
*/
args[0] = (char *)e;
switch(e->handler(fd, x, args + 1)) {
case RESULT_SHOWUSAGE:
Russell Bryant
committed
if (e->usage)
ast_cli(fd, "%s", e->usage);
else
ast_cli(fd, "Invalid usage, but no usage information available.\n");
break;
default:
AST_LIST_LOCK(&helpers);
if (e->deprecated == 1) {
ast_cli(fd, "The '%s' command is deprecated and will be removed in a future release. Please use '%s' instead.\n", e->_full_cmd, e->_deprecated_by);
e->deprecated = 2;
}
AST_LIST_UNLOCK(&helpers);
break;
}
} else
ast_cli(fd, "No such command '%s' (type 'help' for help)\n", find_best(args + 1));
Russell Bryant
committed
if (e)
ast_atomic_fetchadd_int(&e->inuse, -1);
free(dup);