Skip to content
Snippets Groups Projects
cli.c 51.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • 		cdrtime, c->_bridge ? c->_bridge->name : "<none>", ast_bridged_channel(c) ? ast_bridged_channel(c)->name : "<none>", 
    
    		c->context, c->exten, c->priority, c->callgroup, c->pickupgroup, ( c->appl ? c->appl : "(N/A)" ),
    
    		( c-> data ? S_OR(c->data, "(Empty)") : "(None)"),
    
    		(ast_test_flag(c, AST_FLAG_BLOCKING) ? c->blockproc : "(Not Blocking)"));
    	
    
    	if (pbx_builtin_serialize_variables(c, &out))
    
    		ast_cli(a->fd,"      Variables:\n%s\n", out->str);
    
    	if (c->cdr && ast_cdr_serialize_variables(c->cdr, &out, '=', '\n', 1))
    
    		ast_cli(a->fd,"  CDR Variables:\n%s\n", out->str);
    
    /*
     * helper function to generate CLI matches from a fixed set of values.
     * A NULL word is acceptable.
     */
    char *ast_cli_complete(const char *word, char *const choices[], int state)
    
    	int i, which = 0, len;
    	len = ast_strlen_zero(word) ? 0 : strlen(word);
    
    	for (i = 0; choices[i]; i++) {
    		if ((!len || !strncasecmp(word, choices[i], len)) && ++which > state)
    			return ast_strdup(choices[i]);
    
    char *ast_complete_channels(const char *line, const char *word, int pos, int state, int rpos)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	char notfound = '\0';
    	char *ret = &notfound; /* so NULL can break the loop */
    
    	if (pos != rpos)
    		return NULL;
    
    	while (ret == &notfound && (c = ast_channel_walk_locked(c))) {
    		if (!strncasecmp(word, c->name, wordlen) && ++which > state)
    			ret = ast_strdup(c->name);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	return ret == &notfound ? NULL : ret;
    
    static char *group_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    
    	struct ast_group_info *gi = NULL;
    
    	int numchans = 0;
    	regex_t regexbuf;
    	int havepattern = 0;
    
    
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "group show channels";
    		e->usage = 
    			"Usage: group show channels [pattern]\n"
    			"       Lists all currently active channels with channel group(s) specified.\n"
    			"       Optional regular expression pattern is matched to group names for each\n"
    			"       channel.\n";
    		return NULL;
    	case CLI_GENERATE:
    		return NULL;
    	}
    
    	if (a->argc < 3 || a->argc > 4)
    		return CLI_SHOWUSAGE;
    
    	if (a->argc == 4) {
    		if (regcomp(&regexbuf, a->argv[3], REG_EXTENDED | REG_NOSUB))
    			return CLI_SHOWUSAGE;
    
    	ast_cli(a->fd, FORMAT_STRING, "Channel", "Group", "Category");
    
    	ast_app_group_list_rdlock();
    
    	
    	gi = ast_app_group_list_head();
    	while (gi) {
    		if (!havepattern || !regexec(&regexbuf, gi->group, 0, NULL, 0)) {
    
    			ast_cli(a->fd, FORMAT_STRING, gi->chan->name, gi->group, (ast_strlen_zero(gi->category) ? "(default)" : gi->category));
    
    		gi = AST_LIST_NEXT(gi, list);
    
    	
    	ast_app_group_list_unlock();
    	
    
    	ast_cli(a->fd, "%d active channel%s\n", numchans, ESS(numchans));
    	return CLI_SUCCESS;
    
    static struct ast_cli_entry cli_debug_channel_deprecated = AST_CLI_DEFINE(handle_debugchan_deprecated, "Enable debugging on channel");
    static struct ast_cli_entry cli_module_load_deprecated = AST_CLI_DEFINE(handle_load_deprecated, "Load a module");
    static struct ast_cli_entry cli_module_reload_deprecated = AST_CLI_DEFINE(handle_reload_deprecated, "reload modules by name");
    static struct ast_cli_entry cli_module_unload_deprecated = AST_CLI_DEFINE(handle_unload_deprecated, "unload modules by name");
    
    static char *handle_help(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
    
    
    static struct ast_cli_entry cli_cli[] = {
    
    	/* Deprecated, but preferred command is now consolidated (and already has a deprecated command for it). */
    
    	AST_CLI_DEFINE(handle_commandcomplete, "Command complete"),
    	AST_CLI_DEFINE(handle_commandnummatches, "Returns number of command matches"),
    	AST_CLI_DEFINE(handle_commandmatchesarray, "Returns command matches array"),
    
    	AST_CLI_DEFINE(handle_nodebugchan_deprecated, "Disable debugging on channel(s)"),
    
    	AST_CLI_DEFINE(handle_chanlist, "Display information on channels"),
    
    	AST_CLI_DEFINE(handle_showchan, "Display information on a specific channel"),
    
    	AST_CLI_DEFINE(handle_core_set_debug_channel, "Enable/disable debugging on a channel",
    
    		.deprecate_cmd = &cli_debug_channel_deprecated),
    
    	AST_CLI_DEFINE(handle_verbose, "Set level of debug/verbose chattiness"),
    
    	AST_CLI_DEFINE(group_show_channels, "Display active channels with group(s)"),
    
    	AST_CLI_DEFINE(handle_help, "Display help list, or specific help on a command"),
    
    	AST_CLI_DEFINE(handle_logger_mute, "Toggle logging output to a console"),
    
    	AST_CLI_DEFINE(handle_modlist, "List modules and info"),
    
    	AST_CLI_DEFINE(handle_load, "Load a module by name", .deprecate_cmd = &cli_module_load_deprecated),
    
    	AST_CLI_DEFINE(handle_reload, "Reload configuration", .deprecate_cmd = &cli_module_reload_deprecated),
    
    	AST_CLI_DEFINE(handle_unload, "Unload a module by name", .deprecate_cmd = &cli_module_unload_deprecated ),
    
    	AST_CLI_DEFINE(handle_showuptime, "Show uptime information"),
    
    	AST_CLI_DEFINE(handle_softhangup, "Request a hangup on a given channel"),
    
    /*!
     * Some regexp characters in cli arguments are reserved and used as separators.
     */
    
    static const char cli_rsvd[] = "[]{}|*%";
    
    
    /*!
     * initialize the _full_cmd string and related parameters,
     * return 0 on success, -1 on error.
     */
    static int set_full_cmd(struct ast_cli_entry *e)
    {
    	int i;
    	char buf[80];
    
    	ast_join(buf, sizeof(buf), e->cmda);
    
    	e->_full_cmd = ast_strdup(buf);
    
    	if (!e->_full_cmd) {
    		ast_log(LOG_WARNING, "-- cannot allocate <%s>\n", buf);
    		return -1;
    	}
    	e->cmdlen = strcspn(e->_full_cmd, cli_rsvd);
    	for (i = 0; e->cmda[i]; i++)
    		;
    	e->args = i;
    	return 0;
    }
    
    
    /*! \brief initialize the _full_cmd string in * each of the builtins. */
    void ast_builtins_init(void)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	ast_cli_register_multiple(cli_cli, sizeof(cli_cli) / sizeof(struct ast_cli_entry));
    
    static struct ast_cli_entry *cli_next(struct ast_cli_entry *e)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	if (e == NULL)
    		e = AST_LIST_FIRST(&helpers);
    	if (e) 
    		e = AST_LIST_NEXT(e, list);
    
    /*!
     * 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;
    	/* regexp match, takes [foo|bar] or {foo|bar} */
    	l = strlen(cmd);
    
    	/* wildcard match - will extend in the future */
    	if (l > 0 && cli_word[0] == '%') {
    		return 1;	/* wildcard */
    	}
    
    	pos = strcasestr(cli_word, cmd);
    	if (pos == NULL) /* not found, say ok if optional */
    		return cli_word[0] == '[' ? 0 : -1;
    	if (pos == cli_word)	/* no valid match at the beginning */
    		return -1;
    	if (strchr(cli_rsvd, pos[-1]) && strchr(cli_rsvd, pos[l]))
    		return 1;	/* valid match */
    	return -1;	/* not found */
    }
    
    /*! \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 ast_strdup(s);
    
    /*!
     * \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.
    
     *		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.
    
     *
     * The search compares word by word taking care of regexps in e->cmda
    
     */
    static struct ast_cli_entry *find_cli(char *const cmds[], int match_type)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	int matchlen = -1;	/* length of longest match so far */
    	struct ast_cli_entry *cand = NULL, *e=NULL;
    
    
    		/* word-by word regexp comparison */
    		char * const *src = cmds;
    		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;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    static char *find_best(char *argv[])
    {
    	static char cmdline[80];
    	int x;
    
    	/* See how close we get, then print the candidate */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char *myargv[AST_MAX_CMD_LEN];
    	for (x=0;x<AST_MAX_CMD_LEN;x++)
    		myargv[x]=NULL;
    
    	AST_RWLIST_RDLOCK(&helpers);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	for (x=0;argv[x];x++) {
    		myargv[x] = argv[x];
    		if (!find_cli(myargv, -1))
    			break;
    	}
    
    	AST_RWLIST_UNLOCK(&helpers);
    
    	ast_join(cmdline, sizeof(cmdline), myargv);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return cmdline;
    }
    
    
    static int __ast_cli_unregister(struct ast_cli_entry *e, struct ast_cli_entry *ed)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	if (e->deprecate_cmd) {
    		__ast_cli_unregister(e->deprecate_cmd, e);
    	}
    
    	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);
    
    		e->_full_cmd = NULL;
    
    			/* this is a new-style entry. Reset fields and free memory. */
    
    			bzero((char **)(e->cmda), sizeof(e->cmda));
    
    			e->command = NULL;
    			e->usage = NULL;
    		}
    
    static int __ast_cli_register(struct ast_cli_entry *e, struct ast_cli_entry *ed)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	if (e->handler == NULL) {	/* new style entry, run the handler to init fields */
    		struct ast_cli_args a;	/* fake argument */
    
    		char **dst = (char **)e->cmda;	/* need to cast as the entry is readonly */
    
    		bzero (&a, sizeof(a));
    		e->new_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;
    	}
    
    	AST_RWLIST_WRLOCK(&helpers);
    
    	if (find_cli(e->cmda, 1)) {
    
    		ast_log(LOG_WARNING, "Command '%s' already registered (or something close enough)\n", e->_full_cmd);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	if (!ed) {
    		e->deprecated = 0;
    	} else {
    
    		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);
    	}
    
    
    	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&helpers, cur, list) {
    
    		if (strncasecmp(e->_full_cmd, cur->_full_cmd, len) < 0) {
    
    			AST_RWLIST_INSERT_BEFORE_CURRENT(e, list); 
    
    	AST_RWLIST_TRAVERSE_SAFE_END;
    
    		AST_RWLIST_INSERT_TAIL(&helpers, e, list); 
    
    	ret = 0;	/* success */
    
    	AST_RWLIST_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);
    }
    
    
    /*
     * register/unregister an array of entries.
     */
    
    int ast_cli_register_multiple(struct ast_cli_entry *e, int len)
    
    int ast_cli_unregister_multiple(struct ast_cli_entry *e, int len)
    
    /*! \brief helper for 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 char *help1(int fd, char *match[], int locked)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	char matchstr[80] = "";
    
    	int len = 0;
    	int found = 0;
    
    	if (match) {
    		ast_join(matchstr, sizeof(matchstr), match);
    		len = strlen(matchstr);
    	}
    	if (!locked)
    
    		AST_RWLIST_RDLOCK(&helpers);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* Hide commands that start with '_' */
    
    		if (e->_full_cmd[0] == '_')
    
    Mark Spencer's avatar
    Mark Spencer committed
    			continue;
    
    		/* Hide commands that are marked as deprecated. */
    		if (e->deprecated)
    			continue;
    
    		if (match && strncasecmp(matchstr, e->_full_cmd, len))
    			continue;
    
    		ast_cli(fd, "%30.30s %s\n", e->_full_cmd, S_OR(e->summary, "<no description available>"));
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	if (!locked)
    
    		AST_RWLIST_UNLOCK(&helpers);
    
    	if (!locked && !found && matchstr[0])
    		ast_cli(fd, "No such command '%s'.\n", matchstr);
    
    	return CLI_SUCCESS;
    
    static char *handle_help(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char fullcmd[80];
    
    	struct ast_cli_entry *my_e;
    
    	if (cmd == CLI_INIT) {
    		e->command = "help";
    		e->usage =
    			"Usage: help [topic]\n"
    			"       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 4 or 5 chars, "help " */
    		int l = strlen(a->line);
    
    		if (l > 5)
    			l = 5;
    		/* XXX watch out, should stop to the non-generator parts */
    		return __ast_cli_generator(a->line + l, a->word, a->n, 0);
    	}
    	if (a->argc == 1)
    		return help1(a->fd, NULL, 0);
    
    	AST_RWLIST_RDLOCK(&helpers);
    
    	my_e = find_cli(a->argv + 1, 1);	/* try exact match first */
    	if (!my_e)
    		return help1(a->fd, a->argv + 1, 1 /* locked */);
    	if (my_e->usage)
    		ast_cli(a->fd, "%s", my_e->usage);
    
    		ast_join(fullcmd, sizeof(fullcmd), a->argv+1);
    		ast_cli(a->fd, "No help text available for '%s'.\n", fullcmd);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	AST_RWLIST_UNLOCK(&helpers);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return RESULT_SUCCESS;
    }
    
    
    static char *parse_args(const char *s, int *argc, char *argv[], int max, int *trailingwhitespace)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	char *dup, *cur;
    
    	int x = 0;
    	int quoted = 0;
    	int escaped = 0;
    	int whitespace = 1;
    
    	int dummy = 0;
    
    	if (trailingwhitespace == NULL)
    		trailingwhitespace = &dummy;
    
    	*trailingwhitespace = 0;
    
    	if (s == NULL)	/* invalid, though! */
    		return NULL;
    	/* make a copy to store the parsed string */
    
    		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) {
    
    			quoted = !quoted;
    
    			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;
    
    Mark Spencer's avatar
    Mark Spencer committed
    			}
    
    	/* Null terminate */
    
    	*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;
    
    	*trailingwhitespace = whitespace;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return dup;
    }
    
    
    /*! \brief Return the number of unique matches for the generator */
    
    int ast_cli_generatornummatches(const char *text, const char *word)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	int matches = 0, i = 0;
    
    	char *buf = NULL, *oldbuf = NULL;
    
    	while ((buf = ast_cli_generator(text, word, i++))) {
    
    		if (!oldbuf || strcmp(buf,oldbuf))
    			matches++;
    		if (oldbuf)
    
    Mark Spencer's avatar
    Mark Spencer committed
    		oldbuf = buf;
    	}
    
    	if (oldbuf)
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return matches;
    }
    
    
    char **ast_cli_completion_matches(const char *text, const char *word)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	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 */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	match_list_len = 1;
    	while ((retstr = ast_cli_generator(text, word, matches)) != NULL) {
    		if (matches + 1 >= match_list_len) {
    			match_list_len <<= 1;
    
    			if (!(match_list = ast_realloc(match_list, match_list_len * sizeof(*match_list))))
    				return NULL;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    		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.
    	 */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	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++)
    
    Mark Spencer's avatar
    Mark Spencer committed
    			continue;
    		max_equal = i;
    	}
    
    
    	if (!(retstr = ast_malloc(max_equal + 1)))
    		return NULL;
    	
    
    	ast_copy_string(retstr, match_list[1], max_equal + 1);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	match_list[0] = retstr;
    
    
    	/* ensure that the array is NULL terminated */
    
    	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;
    
    /*! \brief returns true if there are more words to match */
    static int more_words (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'
     */
    
    static char *__ast_cli_generator(const char *text, const char *word, int state, int lock)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	char *argv[AST_MAX_ARGS];
    
    	int x = 0, argindex, matchlen;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int matchnum=0;
    
    	char matchstr[80] = "";
    
    	/* Split the argument into an array of words */
    
    	char *dup = parse_args(text, &x, argv, sizeof(argv) / sizeof(argv[0]), &tws);
    
    	if (!dup)	/* 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);
    
    	if (tws) {
    		strcat(matchstr, " "); /* XXX */
    		if (matchlen)
    			matchlen++;
    	}
    
    		AST_RWLIST_RDLOCK(&helpers);
    
    		/* XXX repeated code */
    		int src = 0, dst = 0, n = 0;
    
    
    		/*
    		 * 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.
    			 */
    
    			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.
    			 */
    
    				ret = e->generator(matchstr, word, argindex, state - matchnum);
    
    			else if (e->new_handler) {	/* new style command */
    
    				struct ast_cli_args a = {
    					.line = matchstr, .word = word,
    					.pos = argindex,
    
    					.n = state - matchnum };
    
    				ret = e->new_handler(e, CLI_GENERATE, &a);
    
    		AST_RWLIST_UNLOCK(&helpers);
    
    char *ast_cli_generator(const char *text, const char *word, int state)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	return __ast_cli_generator(text, word, state, 1);
    }
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	char *args[AST_MAX_ARGS + 1];
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct ast_cli_entry *e;
    	int x;
    
    	int res;
    	char *dup = parse_args(s, &x, args + 1, AST_MAX_ARGS, NULL);
    
    	if (dup == NULL)
    
    	if (x < 1)	/* We need at least one entry, otherwise ignore */
    		goto done;
    
    
    	AST_RWLIST_RDLOCK(&helpers);
    
    	e = find_cli(args + 1, 0);
    	if (e)
    		ast_atomic_fetchadd_int(&e->inuse, 1);
    
    	AST_RWLIST_UNLOCK(&helpers);
    
    	if (e == NULL) {
    		ast_cli(fd, "No such command '%s' (type 'help' for help)\n", find_best(args + 1));
    		goto done;
    	}
    	/*
    	 * 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;
    
    	if (!e->new_handler)	/* old style */
    		res = e->handler(fd, x, args + 1);
    	else {
    		struct ast_cli_args a = {
    			.fd = fd, .argc = x, .argv = args+1 };
    		char *retval = e->new_handler(e, CLI_HANDLER, &a);
    
    		if (retval == CLI_SUCCESS)
    			res = RESULT_SUCCESS;
    		else if (retval == CLI_SHOWUSAGE)
    			res = RESULT_SHOWUSAGE;
    		else
    			res = RESULT_FAILURE;
    	}
    	switch (res) {
    	case RESULT_SHOWUSAGE:
    		ast_cli(fd, "%s", S_OR(e->usage, "Invalid usage, but no usage information available.\n"));
    
    		AST_RWLIST_RDLOCK(&helpers);
    
    		if (e->deprecated)
    			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);
    
    		AST_RWLIST_UNLOCK(&helpers);
    
    		break;
    	case RESULT_FAILURE:
    		ast_cli(fd, "Command '%s' failed.\n", s);
    		/* FALLTHROUGH */
    	default:
    
    		AST_RWLIST_RDLOCK(&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_RWLIST_UNLOCK(&helpers);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	ast_atomic_fetchadd_int(&e->inuse, -1);
    done:
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return 0;
    }
    
    
    int ast_cli_command_multiple(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(fd, cmd);
    			y = 0;
    			count++;
    		}
    	}
    	return count;
    }