summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/asterisk/cli.h127
-rw-r--r--main/cli.c230
2 files changed, 256 insertions, 101 deletions
diff --git a/include/asterisk/cli.h b/include/asterisk/cli.h
index e2a6f818b..050fe87d4 100644
--- a/include/asterisk/cli.h
+++ b/include/asterisk/cli.h
@@ -44,9 +44,106 @@ void ast_cli(int fd, char *fmt, ...)
#define AST_CLI_COMPLETE_EOF "_EOF_"
-/*! \brief A command line entry */
+/*!
+ CLI commands are described by a struct ast_cli_entry that contains
+ all the components for their implementation.
+ In the "old-style" format, the record must contain:
+ - a NULL-terminated array of words constituting the command, e.g.
+ { "set", "debug", "on", NULL },
+ - a summary string (short) and a usage string (longer);
+ - a handler which implements the command itself, invoked with
+ a file descriptor and argc/argv as typed by the user
+ - a 'generator' function which, given a partial string, can
+ generate legal completions for it.
+ An example is
+
+ int old_setdebug(int fd, int argc, char *argv[]);
+ char *dbg_complete(const char *line, const char *word, int pos, int n);
+
+ { { "set", "debug", "on", NULL }, do_setdebug, "Enable debugging",
+ set_debug_usage, dbg_complete },
+
+ In the "new-style" format, all the above functionalities are implemented
+ by a single function, and the arguments tell which output is required.
+
+ NOTE: ideally, the new-style handler would have a different prototype,
+ i.e. something like
+ int new_setdebug(const struct ast_cli *e, int function,
+ int fd, int argc, char *argv[], // handler args
+ int n, int pos, const char *line, const char *word // -complete args)
+ but at this moment we want to help the transition from old-style to new-style
+ functions so we keep the same interface and override some of the traditional
+ arguments.
+
+ To help the transition, a new-style entry has the same interface as the old one,
+ but it is declared as follows:
+
+ int new_setdebug(int fd, int argc, char *argv[]);
+
+ ...
+ // this is how we create the entry to register
+ NEW_CLI(new_setdebug, "short description")
+ ...
+
+ Called with the default arguments (argc > 0), the new_handler implements
+ the command as before.
+ A negative argc indicates one of the other functions, namely
+ generate the usage string, the full command, or implement the generator.
+ As a trick to extend the interface while being backward compatible,
+ argv[-1] points to a struct ast_cli_args, and, for the generator,
+ argv[0] is really a pointer to a struct ast_cli_args.
+ The return string is obtained by casting the result to char *
+
+ An example of new-style handler is the following
+
+\code
+static int test_new_cli(int fd, int argc, char *argv[])
+{
+ struct ast_cli_entry *e = (struct ast_cli_entry *)argv[-1];
+ struct ast_cli_args *a;
+ static char *choices = { "one", "two", "three", NULL };
+
+ switch(argc) {
+ case CLI_USAGE:
+ return (int)
+ "Usage: do this well <arg>\n"
+ " typically multiline with body indented\n";
+
+ case CLI_CMD_STRING:
+ return (int)"do this well";
+
+ case CLI_GENERATE:
+ a = (struct ast_cli_args *)argv[0];
+ if (a->pos > e->args)
+ return NULL;
+ return ast_cli_complete(a->word, choices, a->n);
+
+ default:
+ // we are guaranteed to be called with argc >= e->args;
+ if (argc > e->args + 1) // we accept one extra argument
+ return RESULT_SHOWUSAGE;
+ ast_cli(fd, "done this well for %s\n", e->args[argc-1]);
+ return RESULT_SUCCESS;
+ }
+}
+
+\endcode
+ *
+ */
+
+/*! \brief calling arguments for new-style handlers */
+enum ast_cli_fn {
+ CLI_USAGE = -1, /* return the usage string */
+ CLI_CMD_STRING = -2, /* return the command string */
+ CLI_GENERATE = -3, /* behave as 'generator', remap argv to struct ast_cli_args */
+};
+
+typedef int (*old_cli_fn)(int fd, int argc, char *argv[]);
+
+/*! \brief descriptor for a cli entry */
struct ast_cli_entry {
- char * const cmda[AST_MAX_CMD_LEN];
+ char * const cmda[AST_MAX_CMD_LEN]; /*!< words making up the command.
+ * set the first entry to NULL for a new-style entry. */
/*! Handler for the command (fd for output, # of args, argument list).
Returns RESULT_SHOWUSAGE for improper arguments.
argv[] has argc 'useful' entries, and an additional NULL entry
@@ -56,10 +153,8 @@ struct ast_cli_entry {
that this memory is deallocated after the handler returns.
*/
int (*handler)(int fd, int argc, char *argv[]);
- /*! Summary of the command (< 60 characters) */
- const char *summary;
- /*! Detailed usage information */
- const char *usage;
+ const char *summary; /*!< Summary of the command (< 60 characters) */
+ const char *usage; /*!< Detailed usage information */
/*! Generate the n-th (starting from 0) possible completion
for a given 'word' following 'line' in position 'pos'.
'line' and 'word' must not be modified.
@@ -70,22 +165,34 @@ struct ast_cli_entry {
*/
char *(*generator)(const char *line, const char *word, int pos, int n);
struct ast_cli_entry *deprecate_cmd;
- /*! For keeping track of usage */
- int inuse;
- struct module *module; /*! module this belongs to */
+ int inuse; /*!< For keeping track of usage */
+ struct module *module; /*!< module this belongs to */
char *_full_cmd; /* built at load time from cmda[] */
/* This gets set in ast_cli_register()
It then gets set to something different when the deprecated command
is run for the first time (ie; after we warn the user that it's deprecated)
*/
+ int args; /*!< number of non-null entries in cmda */
+ char *command; /*!< command, non-null for new-style entries */
int deprecated;
char *_deprecated_by; /* copied from the "parent" _full_cmd, on deprecated commands */
/*! For linking */
AST_LIST_ENTRY(ast_cli_entry) list;
};
+#define NEW_CLI(fn, txt) { .handler = (old_cli_fn)fn, .summary = txt }
+
+/* argument for new-style CLI handler */
+struct ast_cli_args {
+ char fake[4]; /* a fake string, in the first position, for safety */
+ const char *line; /* the current input line */
+ const char *word; /* the word we want to complete */
+ int pos; /* position of the word to complete */
+ int n; /* the iteration count (n-th entry we generate) */
+};
+
/*!
- * \brief Helper function to generate cli entries from a NULL-terminated array.
+ * Helper function to generate cli entries from a NULL-terminated array.
* Returns the n-th matching entry from the array, or NULL if not found.
* Can be used to implement generate() for static entries as below
* (in this example we complete the word in position 2):
diff --git a/main/cli.c b/main/cli.c
index e3a5ffe89..5949085e0 100644
--- a/main/cli.c
+++ b/main/cli.c
@@ -111,17 +111,6 @@ static char verbose_help[] =
" no messages should be displayed. Equivalent to -v[v[v...]]\n"
" on startup\n";
-static char debug_help[] =
-"Usage: core set debug <level> [filename]\n"
-" Sets level of core debug messages to be displayed. 0 means\n"
-" no messages should be displayed. Equivalent to -d[d[d...]]\n"
-" on startup. If filename is specified, debugging will be\n"
-" limited to just that file.\n";
-
-static char nodebug_help[] =
-"Usage: core set debug off\n"
-" Turns off core debug messages.\n";
-
static char logger_mute_help[] =
"Usage: logger mute\n"
" Disables logging output to the current console, making it possible to\n"
@@ -245,55 +234,61 @@ static int handle_verbose(int fd, int argc, char *argv[])
static int handle_set_debug(int fd, int argc, char *argv[])
{
+ struct ast_cli_entry *e = (struct ast_cli_entry *)argv[-1];
int oldval = option_debug;
int newlevel;
int atleast = 0;
char *filename = '\0';
-
- /* 'core set debug <level>'
- * 'core set debug <level> <fn>'
- * 'core set debug atleast <level>'
- * 'core set debug atleast <level> <fn>'
+ static char *choices[] = { "off", "atleast", NULL };
+ struct ast_cli_args *a;
+
+ switch (argc) {
+ case CLI_CMD_STRING:
+ return (int)"core set debug";
+
+ case CLI_USAGE:
+ return (int)
+ "Usage: core set debug [atleast] <level> [filename]\n"
+ " core set debug off\n"
+ " Sets level of core debug messages to be displayed. 0 or 'off' means\n"
+ " no messages should be displayed. Equivalent to -d[d[d...]]\n"
+ " on startup. If filename is specified, debugging will be\n"
+ " limited to just that file.\n";
+
+ case CLI_GENERATE:
+ a = (struct ast_cli_args *)argv[0];
+ if (a->pos > e->args)
+ return NULL;
+ return (int)ast_cli_complete(a->word, choices, a->n);
+ }
+ /* all the above return, so we proceed with the handler.
+ * we are guaranteed to be called with argc >= e->args;
*/
- if ((argc < 4) || (argc > 6))
+
+ if (argc < e->args + 1)
return RESULT_SHOWUSAGE;
- if (!strcasecmp(argv[3], "atleast"))
+ if (argc == e->args + 1 && !strcasecmp(argv[e->args], "off")) {
+ newlevel = 0;
+ goto done;
+ }
+ if (!strcasecmp(argv[e->args], "atleast"))
atleast = 1;
+ if (argc > e->args + atleast + 2)
+ return RESULT_SHOWUSAGE;
+ if (sscanf(argv[e->args + atleast], "%d", &newlevel) != 1)
+ return RESULT_SHOWUSAGE;
- if (!atleast) {
- if (argc > 5)
- return RESULT_SHOWUSAGE;
-
- if (sscanf(argv[3], "%d", &newlevel) != 1)
- return RESULT_SHOWUSAGE;
-
- if (argc == 4) {
- debug_filename[0] = '\0';
- } else {
- filename = argv[4];
- ast_copy_string(debug_filename, filename, sizeof(debug_filename));
- }
-
- option_debug = newlevel;
+ if (argc == e->args + atleast + 1) {
+ debug_filename[0] = '\0';
} else {
- if (argc < 5 || argc > 6)
- return RESULT_SHOWUSAGE;
-
- if (sscanf(argv[4], "%d", &newlevel) != 1)
- return RESULT_SHOWUSAGE;
-
- if (argc == 5) {
- debug_filename[0] = '\0';
- } else {
- filename = argv[5];
- ast_copy_string(debug_filename, filename, sizeof(debug_filename));
- }
-
- if (newlevel > option_debug)
- option_debug = newlevel;
+ ast_copy_string(debug_filename, argv[e->args + atleast + 1], sizeof(debug_filename));
}
+done:
+ if (!atleast || newlevel > option_debug)
+ option_debug = newlevel;
+
if (oldval > 0 && option_debug == 0)
ast_cli(fd, "Core debug is now OFF\n");
else if (option_debug > 0) {
@@ -407,10 +402,6 @@ static int modlist_modentry(const char *module, const char *description, int use
return 0;
}
-static char modlist_help[] =
-"Usage: module show [like keyword]\n"
-" Shows Asterisk modules currently in use, and usage statistics.\n";
-
static char uptime_help[] =
"Usage: core show uptime [seconds]\n"
" Shows Asterisk uptime information.\n"
@@ -483,17 +474,39 @@ static int handle_showuptime(int fd, int argc, char *argv[])
return RESULT_SUCCESS;
}
-/* core show modules [like keyword] */
static int handle_modlist(int fd, int argc, char *argv[])
{
- char *like = "";
- if (argc != 2 && argc != 4)
- return RESULT_SHOWUSAGE;
- else if (argc == 4) {
- if (strcmp(argv[2],"like"))
- return RESULT_SHOWUSAGE;
- like = argv[3];
+ struct ast_cli_entry *e = (struct ast_cli_entry *)argv[-1];
+ char *like;
+ struct ast_cli_args *a;
+
+ switch(argc) {
+ case CLI_CMD_STRING:
+ return (int)"module show";
+
+ case CLI_USAGE:
+ return (int)
+ "Usage: module show [like keyword]\n"
+ " Shows Asterisk modules currently in use, and usage statistics.\n";
+
+ case CLI_GENERATE:
+ a = (struct ast_cli_args *)argv[0];
+ if (a->pos == e->args)
+ return (int)(a->n == 0 ? strdup("like") : NULL);
+ else if (a->pos == e->args+1 && strcasestr(a->line," like "))
+ return (int)ast_module_helper(a->line, a->word, a->pos, a->n, a->pos, 0);
+ else
+ return (int)NULL;
}
+ /* all the above return, so we proceed with the handler.
+ * we are guaranteed to have argc >= e->args
+ */
+ if (argc == e->args)
+ like = "";
+ else if (argc == e->args + 2 && !strcmp(argv[e->args],"like"))
+ like = argv[e->args + 1];
+ else
+ return RESULT_SHOWUSAGE;
ast_mutex_lock(&climodentrylock);
climodentryfd = fd; /* global, protected by climodentrylock */
@@ -981,11 +994,6 @@ static char *complete_mod_3(const char *line, const char *word, int pos, int sta
return ast_module_helper(line, word, pos, state, 2, 1);
}
-static char *complete_mod_4(const char *line, const char *word, int pos, int state)
-{
- return ast_module_helper(line, word, pos, state, 3, 0);
-}
-
static char *complete_fn(const char *line, const char *word, int pos, int state)
{
char *c;
@@ -1129,13 +1137,7 @@ static struct ast_cli_entry cli_cli[] = {
handle_core_set_debug_channel, "Enable/disable debugging on a channel",
debugchan_help, complete_ch_5, &cli_debug_channel_deprecated },
- { { "core", "set", "debug", NULL },
- handle_set_debug, "Set level of debug chattiness",
- debug_help },
-
- { { "core", "set", "debug", "off", NULL },
- handle_nodebug, "Turns off debug chattiness",
- nodebug_help },
+ NEW_CLI(handle_set_debug, "Set level of debug chattiness"),
{ { "core", "set", "verbose", NULL },
handle_verbose, "Set level of verboseness",
@@ -1153,13 +1155,7 @@ static struct ast_cli_entry cli_cli[] = {
handle_logger_mute, "Toggle logging output to a console",
logger_mute_help },
- { { "module", "show", NULL },
- handle_modlist, "List modules and info",
- modlist_help },
-
- { { "module", "show", "like", NULL },
- handle_modlist, "List modules and info",
- modlist_help, complete_mod_4 },
+ NEW_CLI(handle_modlist, "List modules and info"),
{ { "module", "load", NULL },
handle_load, "Load a module by name",
@@ -1309,6 +1305,14 @@ static int __ast_cli_unregister(struct ast_cli_entry *e, struct ast_cli_entry *e
AST_LIST_REMOVE(&helpers, e, list);
AST_LIST_UNLOCK(&helpers);
free(e->_full_cmd);
+ 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;
+ }
}
return 0;
}
@@ -1317,8 +1321,29 @@ static int __ast_cli_register(struct ast_cli_entry *e, struct ast_cli_entry *ed)
{
struct ast_cli_entry *cur;
char fulle[80] ="";
- int lf, ret = -1;
-
+ 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);
AST_LIST_LOCK(&helpers);
@@ -1432,7 +1457,7 @@ static int help1(int fd, char *match[], int locked)
continue;
if (match && strncasecmp(matchstr, e->_full_cmd, len))
continue;
- ast_cli(fd, "%25.25s %s\n", e->_full_cmd, e->summary);
+ ast_cli(fd, "%25.25s %s\n", e->_full_cmd, S_OR(e->summary, "<no description available>"));
found++;
}
AST_LIST_UNLOCK(&helpers);
@@ -1622,7 +1647,7 @@ static char *__ast_cli_generator(const char *text, const char *word, int state,
}
if (lock)
AST_LIST_LOCK(&helpers);
- while ((e = cli_next(&i))) {
+ 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)) {
@@ -1632,11 +1657,30 @@ static char *__ast_cli_generator(const char *text, const char *word, int state,
break;
}
} else if (!strncasecmp(matchstr, e->_full_cmd, lc) && matchstr[lc] < 33) {
- /* We have a command in its entirity within us -- theoretically only one
- command can have this occur */
+ /* 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->generator)
ret = e->generator(matchstr, word, argindex, state);
- break;
+ 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,
+ .n = state };
+ char *args[] = { (char *)e, (char *)&a, NULL };
+ ret = (char *)e->handler(-1, CLI_GENERATE, args + 1);
+ }
+ if (ret)
+ break;
}
}
if (lock)
@@ -1652,24 +1696,28 @@ char *ast_cli_generator(const char *text, const char *word, int state)
int ast_cli_command(int fd, const char *s)
{
- char *argv[AST_MAX_ARGS];
+ char *args[AST_MAX_ARGS + 1];
struct ast_cli_entry *e;
int x;
char *dup;
int tws;
- if (!(dup = parse_args(s, &x, argv, sizeof(argv) / sizeof(argv[0]), &tws)))
+ 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) {
AST_LIST_LOCK(&helpers);
- e = find_cli(argv, 0);
+ e = find_cli(args + 1, 0);
if (e)
e->inuse++;
AST_LIST_UNLOCK(&helpers);
if (e) {
- switch(e->handler(fd, x, argv)) {
+ /* 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:
if (e->usage)
ast_cli(fd, "%s", e->usage);
@@ -1686,7 +1734,7 @@ int ast_cli_command(int fd, const char *s)
break;
}
} else
- ast_cli(fd, "No such command '%s' (type 'help' for help)\n", find_best(argv));
+ ast_cli(fd, "No such command '%s' (type 'help' for help)\n", find_best(args + 1));
if (e)
ast_atomic_fetchadd_int(&e->inuse, -1);
}