Skip to content
Snippets Groups Projects
cli.c 41 KiB
Newer Older
 *      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)
Mark Spencer's avatar
Mark Spencer committed
{
	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;
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_LIST_LOCK(&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_LIST_UNLOCK(&helpers);
	ast_join(cmdline, sizeof(cmdline), myargv);
Mark Spencer's avatar
Mark Spencer committed
	return cmdline;
}

Mark Spencer's avatar
Mark Spencer committed
int ast_cli_unregister(struct ast_cli_entry *e)
{
	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);
Mark Spencer's avatar
Mark Spencer committed
int ast_cli_register(struct ast_cli_entry *e)
{
	char fulle[80] ="";
	int lf, ret = -1;
	ast_join(fulle, sizeof(fulle), e->cmda);
	if (find_cli(e->cmda, 1)) {
Mark Spencer's avatar
Mark Spencer committed
		ast_log(LOG_WARNING, "Command '%s' already registered (or something close enough)\n", fulle);
Mark Spencer's avatar
Mark Spencer committed
	}
	e->_full_cmd = ast_strdup(fulle);
	if (!e->_full_cmd)
		goto done;
	lf = strlen(fulle);
	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) {
			AST_LIST_INSERT_BEFORE_CURRENT(&helpers, e, list); 
	if (!cur)
		AST_LIST_INSERT_TAIL(&helpers, e, list); 
	ret = 0;	/* success */
/*
 * register/unregister an array of entries.
 */
void ast_cli_register_multiple(struct ast_cli_entry *e, int len)
{
	int i;

		ast_cli_register(e + i);
}

void ast_cli_unregister_multiple(struct ast_cli_entry *e, int len)
{
	int 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)
Mark Spencer's avatar
Mark Spencer committed
{
	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)) ) {
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;
		if (match && strncasecmp(matchstr, e->_full_cmd, len))
			continue;
		ast_cli(fd, "%25.25s  %s\n", e->_full_cmd, e->summary);
		found++;
Mark Spencer's avatar
Mark Spencer committed
	}
	AST_LIST_UNLOCK(&helpers);
	if (!locked && !found && matchstr[0])
		ast_cli(fd, "No such command '%s'.\n", matchstr);
Mark Spencer's avatar
Mark Spencer committed
	return 0;
}

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[])
{
Mark Spencer's avatar
Mark Spencer committed
	char fullcmd[80];
	struct ast_cli_entry *e;

	if (argc < 1)
Mark Spencer's avatar
Mark Spencer committed
		return RESULT_SHOWUSAGE;
Mark Spencer's avatar
Mark Spencer committed
		return help_workhorse(fd, NULL);

	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);
Mark Spencer's avatar
Mark Spencer committed
	}
	AST_LIST_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;

	*trailingwhitespace = 0;
	if (s == NULL)	/* invalid, though! */
		return NULL;
	/* make a copy to store the parsed string */
	if (!(dup = 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) {
			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)
			free(oldbuf);
Mark Spencer's avatar
Mark Spencer committed
		oldbuf = buf;
	}
	if (oldbuf)
		free(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;
	
	strncpy(retstr, match_list[1], max_equal);
Mark Spencer's avatar
Mark Spencer committed
	retstr[max_equal] = '\0';
	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;
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];
	struct ast_cli_entry *e;
	struct cli_iterator i = { NULL, NULL };
	int x = 0, argindex, matchlen;
Mark Spencer's avatar
Mark Spencer committed
	int matchnum=0;
	char matchstr[80] = "";
	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);
	if (tws)
		strcat(matchstr, " "); /* XXX */
	matchlen = strlen(matchstr);
	if (lock)
		AST_LIST_LOCK(&helpers);
	while( !ret && (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 */
		} else if (e->generator && !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 */
			ret = e->generator(matchstr, word, argindex, state);
	if (lock)
		AST_LIST_UNLOCK(&helpers);
	free(dup);
	return ret;
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 *argv[AST_MAX_ARGS];
	struct ast_cli_entry *e;
	int x;
	char *dup;
	
	if (!(dup = parse_args(s, &x, argv, sizeof(argv) / sizeof(argv[0]), &tws))) {
		ast_log(LOG_ERROR, "Memory allocation failure\n");
		return -1;
	}

	/* We need at least one entry, or ignore */
	if (x > 0) {
		if (e) {
			switch(e->handler(fd, x, argv)) {
			case RESULT_SHOWUSAGE:
				if (e->usage)
					ast_cli(fd, "%s", e->usage);
				else
					ast_cli(fd, "Invalid usage, but no usage information available.\n");
				break;
			}
		} else 
			ast_cli(fd, "No such command '%s' (type 'help' for help)\n", find_best(argv));
		if (e) {
			e->inuse--;	/* XXX here an atomic dec would suffice */
Mark Spencer's avatar
Mark Spencer committed
	return 0;
}