[svn-commits] rizzo: trunk r47606 - in /trunk: include/asterisk/cli.h main/cli.c

svn-commits at lists.digium.com svn-commits at lists.digium.com
Tue Nov 14 08:23:35 MST 2006


Author: rizzo
Date: Tue Nov 14 09:23:35 2006
New Revision: 47606

URL: http://svn.digium.com/view/asterisk?view=rev&rev=47606
Log:
Bring in the improved internal API for the CLI.
WATCH OUT: this changes the binary interface (ABI) for modules,
so e.g. users of g729 codecs need a rebuilt module (but read below).

The new way to write CLI handlers is described in detail in cli.h,
and there are a few converted handlers in cli.c, look for NEW_CLI.

After converting a couple of commands i am convinced that
it is reasonably convenient to use, and it makes it easier to fix the
pending CLI issues.

On passing, note a bug with the current 'complete' architecture:
if a command is a prefix of multiple CLI entries, we miss some
of the possible options. As an example, "core set debug" can
continue with "channel" from one CLI entry, and "off" or "atleast"
from another one.

We address this problem in a separate commit
(when i have figured out a fix, that is).

ABI issues:
I asked Kevin if it was ok to make this change and he said yes.
While it would have been possible to make the change without breaking
the module ABI, the code would have been more convoluted.

I am happy to restore the old ABI (while still being able
to use the "new style" handlers) if there is demand.



Modified:
    trunk/include/asterisk/cli.h
    trunk/main/cli.c

Modified: trunk/include/asterisk/cli.h
URL: http://svn.digium.com/view/asterisk/trunk/include/asterisk/cli.h?view=diff&rev=47606&r1=47605&r2=47606
==============================================================================
--- trunk/include/asterisk/cli.h (original)
+++ trunk/include/asterisk/cli.h Tue Nov 14 09:23:35 2006
@@ -44,9 +44,106 @@
 
 #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 @@
 	  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 @@
 	 */
 	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;
 };
 
-/*!
- * \brief Helper function to generate cli entries from a NULL-terminated array.
+#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) */
+};
+
+/*!
+ * 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):

Modified: trunk/main/cli.c
URL: http://svn.digium.com/view/asterisk/trunk/main/cli.c?view=diff&rev=47606&r1=47605&r2=47606
==============================================================================
--- trunk/main/cli.c (original)
+++ trunk/main/cli.c Tue Nov 14 09:23:35 2006
@@ -111,17 +111,6 @@
 "       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,54 +234,60 @@
 
 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))
-		return RESULT_SHOWUSAGE;
-
-	if (!strcasecmp(argv[3], "atleast"))
+
+	if (argc < e->args + 1)
+		return RESULT_SHOWUSAGE;
+
+	if (argc == e->args + 1 && !strcasecmp(argv[e->args], "off")) {
+		newlevel = 0;
+		goto done;
+	}
+	if (!strcasecmp(argv[e->args], "atleast"))
 		atleast = 1;
-
-	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));
-		}
-
+	if (argc > e->args + atleast + 2)
+		return RESULT_SHOWUSAGE;
+	if (sscanf(argv[e->args + atleast], "%d", &newlevel) != 1)
+		return RESULT_SHOWUSAGE;
+
+	if (argc == e->args + atleast + 1) {
+		debug_filename[0] = '\0';
+	} else {
+		ast_copy_string(debug_filename, argv[e->args + atleast + 1], sizeof(debug_filename));
+	}
+
+done:
+	if (!atleast || newlevel > option_debug)
 		option_debug = newlevel;
-	} 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;
-	}
 
 	if (oldval > 0 && option_debug == 0)
 		ast_cli(fd, "Core debug is now OFF\n");
@@ -407,10 +402,6 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 		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 @@
 {
 	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 @@
 			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 @@
 	}
 	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 @@
 				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 @@
 
 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 @@
 				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);
 	}



More information about the svn-commits mailing list