File:  [ELWIX - Embedded LightWeight unIX -] / libaitcli / src / aitcli.c
Revision 1.2.2.18: download - view: text, annotated - select for diffs - revision graph
Tue Dec 7 15:09:02 2010 UTC (13 years, 7 months ago) by misho
Branches: cli2_0
Diff to: branchpoint 1.2: preferred, colored
added function and fix net logic

/*************************************************************************
* (C) 2010 AITNET ltd - Sofia/Bulgaria - <misho@aitbg.com>
*  by Michael Pounov <misho@openbsd-bg.org>
*
* $Author: misho $
* $Id: aitcli.c,v 1.2.2.18 2010/12/07 15:09:02 misho Exp $
*
*************************************************************************/
#include "global.h"
#include "cli.h"


#pragma GCC visibility push(hidden)

// ------------------------------------------------

int cli_Errno;
char cli_Error[STRSIZ];

#pragma GCC visibility pop

// cli_GetErrno() Get error code of last operation
inline int
cli_GetErrno()
{
	return cli_Errno;
}

// io_GetError() Get error text of last operation
inline const char *
cli_GetError()
{
	return cli_Error;
}

// cli_SetErr() Set error to variables for internal use!!!
inline void
cli_SetErr(int eno, char *estr, ...)
{
	va_list lst;

	cli_Errno = eno;
	memset(cli_Error, 0, STRSIZ);
	va_start(lst, estr);
	vsnprintf(cli_Error, STRSIZ, estr, lst);
	va_end(lst);
}

// ------------------------------------------------------------

static inline void
clrscrEOL(linebuffer_t * __restrict buf)
{
	register int i;

	if (buf) {
		write(buf->line_out, K_CR, 1);

		for (i = 0; i < buf->line_len; i++)
			write(buf->line_out, K_SPACE, 1);
	}
}

static inline void
printfEOL(linebuffer_t * __restrict buf, int len, int prompt)
{
	if (buf) {
		write(buf->line_out, K_CR, 1);

		if (prompt && buf->line_prompt)
			write(buf->line_out, buf->line_prompt, buf->line_bol);

		write(buf->line_out, buf->line_buf, len == -1 ? buf->line_eol - buf->line_bol: len);
	}
}

static inline void
printfCR(linebuffer_t * __restrict buf, int prompt)
{
	if (buf) {
		write(buf->line_out, K_CR, 1);

		if (prompt)
			if (prompt && buf->line_prompt)
				write(buf->line_out, buf->line_prompt, buf->line_bol);
	}
}

static inline void
printfNL(linebuffer_t * __restrict buf, int prompt)
{
	if (buf) {
		write(buf->line_out, K_ENTER, 1);

		if (prompt)
			if (prompt && buf->line_prompt)
				write(buf->line_out, buf->line_prompt, buf->line_bol);
	}
}

// ------------------------------------------------------------

static int
bufCHAR(int idx, void * __restrict buffer)
{
	linebuffer_t *buf = buffer;
	int pos;

	if (!buffer || idx < 0 || idx > MAX_BINDKEY)
		return RETCODE_ERR;

	pos = buf->line_eol - buf->line_bol;

	if (buf->line_mode == LINEMODE_INS)
		memmove(buf->line_buf + pos + buf->line_keys[idx].key_len, buf->line_buf + pos, 
				buf->line_len - buf->line_eol);
	if (buf->line_mode == LINEMODE_INS || buf->line_eol == buf->line_len - 1)
		buf->line_len += buf->line_keys[idx].key_len;
	buf->line_eol += buf->line_keys[idx].key_len;

	memcpy(buf->line_buf + pos, buf->line_keys[idx].key_ch, buf->line_keys[idx].key_len);
	buf->line_buf[buf->line_len - 1] = 0;

	write(buf->line_out, buf->line_keys[idx].key_ch, buf->line_keys[idx].key_len);

	if (buf->line_mode == LINEMODE_INS) {
		write(buf->line_out, (const u_char*) buf->line_buf + pos + buf->line_keys[idx].key_len, 
				buf->line_len - buf->line_eol);
		printfEOL(buf, -1, 1);
	}
	return RETCODE_OK;
}

static int
bufEOL(int idx, void * __restrict buffer)
{
	if (!buffer || idx < 0 || idx > MAX_BINDKEY)
		return RETCODE_ERR;

	printfCR(buffer, 1);
	return RETCODE_EOL;
}

static int
bufEOF(int idx, void * __restrict buffer)
{
	/*
	if (!buffer || idx < 0 || idx > MAX_BINDKEY)
		return RETCODE_ERR;
	*/

	return RETCODE_EOF;
}

static int
bufUP(int idx, void * __restrict buffer)
{
	linebuffer_t *buf = buffer;
	int pos;

	if (!buffer || idx < 0 || idx > MAX_BINDKEY)
		return RETCODE_ERR;

	if (!buf->line_h)
		buf->line_h = TAILQ_FIRST(&buf->line_history);
	else
		buf->line_h = TAILQ_NEXT(buf->line_h, hist_next);
	if (!buf->line_h)
		return RETCODE_OK;

	clrscrEOL(buf);
	cli_freeLine(buf);

	pos = buf->line_eol - buf->line_bol;

	buf->line_len += buf->line_h->hist_len;
	buf->line_eol += buf->line_h->hist_len;

	memcpy(buf->line_buf + pos, buf->line_h->hist_line, buf->line_h->hist_len);
	buf->line_buf[buf->line_len - 1] = 0;

	printfEOL(buf, -1, 1);
	return RETCODE_OK;
}

static int
bufDOWN(int idx, void * __restrict buffer)
{
	linebuffer_t *buf = buffer;
	int pos;

	if (!buffer || idx < 0 || idx > MAX_BINDKEY)
		return RETCODE_ERR;

	if (!buf->line_h)
		buf->line_h = TAILQ_LAST(&buf->line_history, tqHistoryHead);
	else
		buf->line_h = TAILQ_PREV(buf->line_h, tqHistoryHead, hist_next);
	if (!buf->line_h)
		return RETCODE_OK;

	clrscrEOL(buf);
	cli_freeLine(buf);

	pos = buf->line_eol - buf->line_bol;

	buf->line_len += buf->line_h->hist_len;
	buf->line_eol += buf->line_h->hist_len;

	memcpy(buf->line_buf + pos, buf->line_h->hist_line, buf->line_h->hist_len);
	buf->line_buf[buf->line_len - 1] = 0;

	printfEOL(buf, -1, 1);
	return RETCODE_OK;
}

static int
bufCLR(int idx, void * __restrict buffer)
{
	if (!buffer || idx < 0 || idx > MAX_BINDKEY)
		return RETCODE_ERR;

	clrscrEOL(buffer);
	cli_freeLine(buffer);

	printfCR(buffer, 1);
	return RETCODE_OK;
}

static int
bufBS(int idx, void * __restrict buffer)
{
	linebuffer_t *buf = buffer;

	if (!buffer || idx < 0 || idx > MAX_BINDKEY)
		return RETCODE_ERR;

	if (buf->line_bol < buf->line_eol) {
		clrscrEOL(buf);

		buf->line_eol--;
		buf->line_len--;
		memmove(buf->line_buf + buf->line_eol - buf->line_bol, 
				buf->line_buf + buf->line_eol - buf->line_bol + 1, 
				buf->line_len - buf->line_eol);
		buf->line_buf[buf->line_len - 1] = 0;

		printfEOL(buf, buf->line_len - 1, 1);
		printfEOL(buf, -1, 1);
	}

	return RETCODE_OK;
}

static int
bufBTAB(int idx, void * __restrict buffer)
{
	linebuffer_t *buf = buffer;

	if (!buffer || idx < 0 || idx > MAX_BINDKEY)
		return RETCODE_ERR;

	if (buf->line_bol < buf->line_eol) {
		clrscrEOL(buf);

		buf->line_len = buf->line_eol - buf->line_bol + 1;
		buf->line_buf[buf->line_len - 1] = 0;

		printfEOL(buf, -1, 1);
	}

	return RETCODE_OK;
}

static int
bufMODE(int idx, void * __restrict buffer)
{
	linebuffer_t *buf = buffer;

	if (!buffer || idx < 0 || idx > MAX_BINDKEY)
		return RETCODE_ERR;

	buf->line_mode = !buf->line_mode ? LINEMODE_OVER : LINEMODE_INS;
	return RETCODE_OK;
}

static int
bufBEGIN(int idx, void * __restrict buffer)
{
	linebuffer_t *buf = buffer;

	if (!buffer || idx < 0 || idx > MAX_BINDKEY)
		return RETCODE_ERR;

	buf->line_eol = buf->line_bol;

	printfCR(buf, 1);
	return RETCODE_OK;
}

static int
bufEND(int idx, void * __restrict buffer)
{
	linebuffer_t *buf = buffer;

	if (!buffer || idx < 0 || idx > MAX_BINDKEY)
		return RETCODE_ERR;

	buf->line_eol = buf->line_len - 1;

	printfEOL(buf, -1, 1);
	return RETCODE_OK;
}

static int
bufLEFT(int idx, void * __restrict buffer)
{
	linebuffer_t *buf = buffer;

	if (!buffer || idx < 0 || idx > MAX_BINDKEY)
		return RETCODE_ERR;

	if (buf->line_bol < buf->line_eol)
		printfEOL(buf, --buf->line_eol - buf->line_bol, 1);

	return RETCODE_OK;
}

static int
bufRIGHT(int idx, void * __restrict buffer)
{
	linebuffer_t *buf = buffer;

	if (!buffer || idx < 0 || idx > MAX_BINDKEY)
		return RETCODE_ERR;

	if (buf->line_eol < buf->line_len - 1)
		printfEOL(buf, ++buf->line_eol - buf->line_bol, 1);

	return RETCODE_OK;
}

static int
bufDEL(int idx, void * __restrict buffer)
{
	linebuffer_t *buf = buffer;

	if (!buffer || idx < 0 || idx > MAX_BINDKEY)
		return RETCODE_ERR;

	clrscrEOL(buf);

	buf->line_len--;
	memmove(buf->line_buf + buf->line_eol - buf->line_bol, 
			buf->line_buf + buf->line_eol - buf->line_bol + 1, 
			buf->line_len - buf->line_eol);
	buf->line_buf[buf->line_len - 1] = 0;

	printfEOL(buf, buf->line_len - 1, 1);
	printfEOL(buf, -1, 1);

	return RETCODE_OK;
}

static int
bufComp(int idx, void * __restrict buffer)
{
	linebuffer_t *buf = buffer;
	char *str, *s, **app, *items[MAX_PROMPT_ITEMS], szLine[STRSIZ];
	register int i, j;
	struct tagCommand *cmd, *c;
	int pos, ret = RETCODE_OK;

	if (!buffer || idx < 0 || idx > MAX_BINDKEY)
		return RETCODE_ERR;

	str = strdup(buf->line_buf);
	if (!str)
		return RETCODE_ERR;
	else {
		s = str;
		io_TrimStr((u_char*) s);
	}

	i = j = 0;
	c = NULL;
	memset(szLine, 0, STRSIZ);
	if (*s) {
		memset(items, 0, sizeof(char*) * MAX_PROMPT_ITEMS);
		for (app = items, i = 0; app < items + MAX_PROMPT_ITEMS - 1 && (*app = strsep(&s, " \t")); 
			 	*app ? i++ : i, *app ? app++ : app);

		if (i) {
			SLIST_FOREACH(cmd, &buf->line_cmds, cmd_next) {
				if (cmd->cmd_level == buf->line_level && 
						!strncmp(cmd->cmd_name, items[0], strlen(items[0]))) {
					if (strncmp(cmd->cmd_name, CLI_CMD_SEP, strlen(CLI_CMD_SEP))) {
						j++;
						c = cmd;
						strlcat(szLine, " ", STRSIZ);
						strlcat(szLine, cmd->cmd_name, STRSIZ);
					}
				}
			}

			if (i > 1 && c) {
				/* we are on argument of command and has complition info */
				j++;	// always must be j > 1 ;) for arguments
				strlcpy(szLine, c->cmd_info, STRSIZ);
			}
		} else {
			/* we have valid char but i == 0, this case is illegal */
			ret = RETCODE_ERR;
			goto endcomp;
		}
	} else {
		/* we on 0 position of prompt, show commands for this level */
		SLIST_FOREACH(cmd, &buf->line_cmds, cmd_next) {
			if (cmd->cmd_level == buf->line_level)
				if (strncmp(cmd->cmd_name, CLI_CMD_SEP, strlen(CLI_CMD_SEP))) {
					j++;
					c = cmd;
					strlcat(szLine, " ", STRSIZ);
					strlcat(szLine, cmd->cmd_name, STRSIZ);
				}
		}
	}

	/* completion show actions ... */
	if (j > 1 && c) {
		printfNL(buf, 0);
		write(buf->line_out, szLine, strlen(szLine));
		printfNL(buf, 1);
		printfEOL(buf, buf->line_len - 1, 1);
		printfEOL(buf, -1, 1);
	}
	if (j == 1 && c) {
		clrscrEOL(buf);
		cli_freeLine(buf);

		pos = buf->line_eol - buf->line_bol;

		buf->line_len += c->cmd_len + 1;
		buf->line_eol += c->cmd_len + 1;

		memcpy(buf->line_buf + pos, c->cmd_name, c->cmd_len);
		buf->line_buf[pos + c->cmd_len] = (u_char) *K_SPACE;
		buf->line_buf[buf->line_len - 1] = 0;

		printfEOL(buf, -1, 1);
	}

endcomp:
	free(str);
	return ret;
}

static int
bufHelp(int idx, void * __restrict buffer)
{
	linebuffer_t *buf = buffer;

	if (!buffer || idx < 0 || idx > MAX_BINDKEY)
		return RETCODE_ERR;

	cli_Cmd_Help(buf, -1, NULL);

	printfEOL(buf, buf->line_len - 1, 1);
	printfEOL(buf, -1, 1);
	return RETCODE_OK;
}

// ---------------------------------------------------------------

/*
 * cli_Printf() Send message to CLI session
 * @buffer = CLI buffer
 * @fmt = printf format string
 * @... = arguments defined in fmt
 * return: none
*/
inline void
cli_Printf(linebuffer_t * __restrict buffer, char *fmt, ...)
{
	va_list lst;
	FILE *f;

	if (fmt) {
		f = fdopen(buffer->line_out, "a");
		if (!f) {
			LOGERR;
			return;
		}

		va_start(lst, fmt);
		vfprintf(f, fmt, lst);
		va_end(lst);
	} else
		cli_SetErr(EINVAL, "Error:: invalid input parameters ...");
}

/*
 * cli_PrintHelp() Print help screen
 * @buffer = CLI buffer
 * return: none
*/
inline void
cli_PrintHelp(linebuffer_t * __restrict buffer)
{
	if (buffer)
		bufHelp(0, buffer);
	else
		cli_SetErr(EINVAL, "Error:: invalid input parameters ...");
}


/*
 * cli_BindKey() Bind function to key
 * @key = key structure
 * @buffer = CLI buffer
 * return: RETCODE_ERR error, RETCODE_OK ok, >0 bind at position
*/
int
cli_BindKey(bindkey_t * __restrict key, linebuffer_t * __restrict buffer)
{
	register int i;

	if (!key || !buffer) {
		cli_SetErr(EINVAL, "Error:: invalid input parameters ...");
		return RETCODE_ERR;
	}

	for (i = 0; i < MAX_BINDKEY; i++)
		if (key->key_len == buffer->line_keys[i].key_len && 
				!memcmp(key->key_ch, buffer->line_keys[i].key_ch, key->key_len)) {
			buffer->line_keys[i].key_func = key->key_func;
			return i;
		}

	return RETCODE_OK;
}


/*
 * cli_addCommand() Add command to CLI session
 * @buffer = CLI buffer
 * @csCmd = Command name
 * @cliLevel = Level in CLI, -1 unprivi(view from all), 0 main config, 1 sub config ...
 * @funcCmd = Callback function when user call command
 * @csInfo = Inline information for command
 * @csHelp = Help line when call help
 * return: RETCODE_ERR error, RETCODE_OK ok
*/
int
cli_addCommand(linebuffer_t * __restrict buffer, const char *csCmd, int cliLevel, cmd_func_t funcCmd, 
		const char *csInfo, const char *csHelp)
{
	struct tagCommand *cmd;

	if (!buffer || !csCmd) {
		cli_SetErr(EINVAL, "Error:: invalid input parameters ...");
		return RETCODE_ERR;
	}

	cmd = malloc(sizeof(struct tagCommand));
	if (!cmd) {
		LOGERR;
		return RETCODE_ERR;
	} else
		memset(cmd, 0, sizeof(struct tagCommand));

	cmd->cmd_level = cliLevel;
	cmd->cmd_func = funcCmd;
	cmd->cmd_len = strlcpy(cmd->cmd_name, csCmd, STRSIZ);
	if (csInfo)
		strlcpy(cmd->cmd_info, csInfo, STRSIZ);
	if (csHelp)
		strlcpy(cmd->cmd_help, csHelp, STRSIZ);
	SLIST_INSERT_HEAD(&buffer->line_cmds, cmd, cmd_next);
	return RETCODE_OK;
}

/*
 * cli_delCommand() Delete command from CLI session
 * @buffer = CLI buffer
 * @csCmd = Command name
 * @cliLevel = Level in CLI, -1 unprivi(view from all), 0 main config, 1 sub config ...
 * return: RETCODE_ERR error, RETCODE_OK ok
*/
int
cli_delCommand(linebuffer_t * __restrict buffer, const char *csCmd, int cliLevel)
{
	struct tagCommand *cmd;
	int ret = RETCODE_OK;

	if (!buffer || !csCmd) {
		cli_SetErr(EINVAL, "Error:: invalid input parameters ...");
		return RETCODE_ERR;
	}

	SLIST_FOREACH(cmd, &buffer->line_cmds, cmd_next) 
		if (cmd->cmd_level == cliLevel && !strcmp(cmd->cmd_name, csCmd)) {
			ret = 1;
			SLIST_REMOVE(&buffer->line_cmds, cmd, tagCommand, cmd_next);
			free(cmd);
			break;
		}

	return ret;
}

/*
 * cli_updCommand() Update command in CLI session
 * @buffer = CLI buffer
 * @csCmd = Command name
 * @cliLevel = Level in CLI, -1 unprivi(view from all), 0 main config, 1 sub config ...
 * @funcCmd = Callback function when user call command
 * @csInfo = Inline information for command
 * @csHelp = Help line when call help
 * return: RETCODE_ERR error, RETCODE_OK ok
*/
int
cli_updCommand(linebuffer_t * __restrict buffer, const char *csCmd, int cliLevel, cmd_func_t funcCmd, 
		const char *csInfo, const char *csHelp)
{
	struct tagCommand *cmd;
	int ret = RETCODE_OK;

	if (!buffer || !csCmd) {
		cli_SetErr(EINVAL, "Error:: invalid input parameters ...");
		return RETCODE_ERR;
	}

	SLIST_FOREACH(cmd, &buffer->line_cmds, cmd_next) 
		if (cmd->cmd_level == cliLevel && !strcmp(cmd->cmd_name, csCmd)) {
			ret = 1;

			if (funcCmd)
				cmd->cmd_func = funcCmd;
			if (csInfo)
				strlcpy(cmd->cmd_info, csInfo, STRSIZ);
			if (csHelp)
				strlcpy(cmd->cmd_help, csHelp, STRSIZ);

			break;
		}

	return ret;
}


/*
 * cli_addHistory() Add line to history
 * @buffer = CLI buffer
 * @str = Add custom text or if NULL use readed line from CLI buffer
 * return: RETCODE_ERR error, RETCODE_OK ok
*/
int
cli_addHistory(linebuffer_t * __restrict buffer, const char * __restrict str)
{
	struct tagHistory *h;

	if (!buffer) {
		cli_SetErr(EINVAL, "Error:: invalid input parameters ...");
		return RETCODE_ERR;
	}

	if (!(h = malloc(sizeof(struct tagHistory)))) {
		LOGERR;
		return RETCODE_ERR;
	} else
		memset(h, 0, sizeof(struct tagHistory));

	if (str) {
		if (!*str) {
			free(h);
			return RETCODE_OK;
		}

		h->hist_len = strlcpy(h->hist_line, str, BUFSIZ);
	} else {
		if (!*buffer->line_buf || buffer->line_len < 2) {
			free(h);
			return RETCODE_OK;
		}

		memcpy(h->hist_line, buffer->line_buf, (h->hist_len = buffer->line_len));
		io_TrimStr((u_char*) h->hist_line);
		h->hist_len = strlen(h->hist_line);
	}

	TAILQ_INSERT_HEAD(&buffer->line_history, h, hist_next);
	return h->hist_len;
}

/*
 * cli_saveHistory() Save history to file
 * @buffer = CLI buffer
 * @histfile = History filename, if NULL will be use default name
 * @lines = Maximum history lines to save
 * return: RETCODE_ERR error, RETCODE_OK ok
*/
int
cli_saveHistory(linebuffer_t * __restrict buffer, const char *histfile, int lines)
{
	FILE *f;
	mode_t mode;
	char szFName[MAXPATHLEN];
	struct tagHistory *h;

	if (!buffer) {
		cli_SetErr(EINVAL, "Error:: invalid input parameters ...");
		return RETCODE_ERR;
	}
	if (!histfile)
		strlcpy(szFName, HISTORY_FILE, MAXPATHLEN);
	else
		strlcpy(szFName, histfile, MAXPATHLEN);

	mode = umask(0177);
	f = fopen(szFName, "w");
	if (!f) {
		LOGERR;
		return RETCODE_ERR;
	}

	TAILQ_FOREACH(h, &buffer->line_history, hist_next) {
		fprintf(f, "%s\n", h->hist_line);

		if (lines)
			lines--;
		else
			break;
	}

	fclose(f);
	umask(mode);

	return RETCODE_OK;
}

/*
 * cli_loadHistory() Load history from file
 * @buffer = CLI buffer
 * @histfile = History filename, if NULL will be use default name
 * return: RETCODE_ERR error, RETCODE_OK ok
*/
int
cli_loadHistory(linebuffer_t * __restrict buffer, const char *histfile)
{
	FILE *f;
	char szFName[MAXPATHLEN], buf[BUFSIZ];
	struct tagHistory *h;

	if (!buffer) {
		cli_SetErr(EINVAL, "Error:: invalid input parameters ...");
		return RETCODE_ERR;
	}
	if (!histfile)
		strlcpy(szFName, HISTORY_FILE, MAXPATHLEN);
	else
		strlcpy(szFName, histfile, MAXPATHLEN);

	f = fopen(szFName, "r");
	if (!f)
		return RETCODE_OK;

	while (fgets(buf, BUFSIZ, f)) {
		if (!*buf || *buf == '#')
			continue;
		else
			io_TrimStr((u_char*) buf);

		if (!(h = malloc(sizeof(struct tagHistory)))) {
			LOGERR;
			fclose(f);
			return RETCODE_ERR;
		} else
			memset(h, 0, sizeof(struct tagHistory));

		h->hist_len = strlcpy(h->hist_line, buf, BUFSIZ);
		TAILQ_INSERT_TAIL(&buffer->line_history, h, hist_next);
	}

	fclose(f);

	return RETCODE_OK;
}

/*
 * cli_resetHistory() Reset history search in CLI session
 * @buffer = CLI buffer
 * return: none
*/
inline void
cli_resetHistory(linebuffer_t * __restrict buffer)
{
	buffer->line_h = NULL;
}


/*
 * cli_freeLine() Clear entire line
 * @buffer = CLI buffer
 * return: RETCODE_ERR error, RETCODE_OK ok
*/
inline int
cli_freeLine(linebuffer_t * __restrict buffer)
{
	int code = RETCODE_ERR;

	if (buffer) {
		if (buffer->line_buf)
			free(buffer->line_buf);

		buffer->line_buf = malloc(BUFSIZ);
		if (buffer->line_buf) {
			memset(buffer->line_buf, 0, BUFSIZ);
			buffer->line_eol = buffer->line_bol;
			buffer->line_len = 1 + buffer->line_eol;

			code = RETCODE_OK;
		} else
			LOGERR;
	} else
		cli_SetErr(EINVAL, "Error:: invalid input parameters ...");

	return code;
}

/*
 * cli_setPrompt() Set new prompt for CLI session
 * @buffer = CLI buffer
 * @prompt = new text for prompt or if NULL disable prompt
 * return: none
*/
inline void
cli_setPrompt(linebuffer_t * __restrict buffer, const char *prompt)
{
	if (buffer) {
		if (buffer->line_prompt) {
			free(buffer->line_prompt);
			buffer->line_prompt = NULL;
			buffer->line_bol = 0;
		}

		if (prompt) {
			buffer->line_prompt = strdup(prompt);
			if (buffer->line_prompt) {
				buffer->line_bol = strlen(buffer->line_prompt);
				buffer->line_eol = buffer->line_bol;
				buffer->line_len = 1 + buffer->line_eol;
			} else
				LOGERR;
		}
	} else
		cli_SetErr(EINVAL, "Error:: invalid input parameters ...");
}


/*
 * cliEnd() Clear data, Free resources and close CLI session
 * @buffer = CLI buffer
 * return: RETCODE_ERR error, RETCODE_OK ok
*/
void
cliEnd(linebuffer_t * __restrict buffer)
{
	struct tagHistory *h;
	struct tagCommand *c;

	if (buffer) {
		while ((c = SLIST_FIRST(&buffer->line_cmds))) {
			SLIST_REMOVE_HEAD(&buffer->line_cmds, cmd_next);
			free(c);
		}
		while ((h = TAILQ_FIRST(&buffer->line_history))) {
			TAILQ_REMOVE(&buffer->line_history, h, hist_next);
			free(h);
		}

		if (buffer->line_prompt)
			free(buffer->line_prompt);

		if (buffer->line_keys)
			free(buffer->line_keys);
		if (buffer->line_buf)
			free(buffer->line_buf);

		free(buffer);
		buffer = NULL;
	} else
		cli_SetErr(EINVAL, "Error:: invalid input parameters ...");
}

/*
 * cliInit() Start CLI session, allocate memory for resources and bind keys
 * @fin = Input device handle
 * @fout = Output device handle
 * @prompt = text for prompt, if NULL disable prompt
 * return: NULL if error or !=NULL CLI buffer
*/
linebuffer_t *
cliInit(int fin, int fout, const char *prompt)
{
	linebuffer_t *buffer;
	bindkey_t *keys;
	register int i;

	/* init buffer */
	buffer = malloc(sizeof(linebuffer_t));
	if (!buffer) {
		LOGERR;
		return NULL;
	} else {
		memset(buffer, 0, sizeof(linebuffer_t));

		buffer->line_in = fin;
		buffer->line_out = fout;

		TAILQ_INIT(&buffer->line_history);
		SLIST_INIT(&buffer->line_cmds);

		if (prompt) {
			buffer->line_prompt = strdup(prompt);
			if (!buffer->line_prompt) {
				LOGERR;
				free(buffer);
				return NULL;
			} else
				buffer->line_eol = buffer->line_bol = strlen(buffer->line_prompt);
		}
	}
	buffer->line_buf = malloc(BUFSIZ);
	if (!buffer->line_buf) {
		LOGERR;
		if (buffer->line_prompt)
			free(buffer->line_prompt);
		free(buffer);
		return NULL;
	} else {
		memset(buffer->line_buf, 0, BUFSIZ);
		buffer->line_len = 1 + buffer->line_eol;
	}
	keys = calloc(MAX_BINDKEY + 1, sizeof(bindkey_t));
	if (!keys) {
		LOGERR;
		if (buffer->line_prompt)
			free(buffer->line_prompt);
		free(buffer->line_buf);
		free(buffer);
		return NULL;
	} else
		memset(keys, 0, sizeof(bindkey_t) * (MAX_BINDKEY + 1));

	/* add helper functions */
	cli_addCommand(buffer, "exit", 0, cli_Cmd_Exit, "exit <cr>", "Exit from console");
	cli_addCommand(buffer, "help", 0, cli_Cmd_Help, "help [command] <cr>", "Help screen");
	cli_addCommand(buffer, "-------", 0, NULL, "-------------------------", NULL);

	/* fill key bindings */
	// ascii chars & ctrl+chars
	for (i = 0; i < 256; i++) {
		*keys[i].key_ch = (u_char) i;
		keys[i].key_len = 1;

		if (!i || i == *K_CTRL_D)
			keys[i].key_func = bufEOF;
		if (i == *K_CTRL_M || i == *K_CTRL_J)
			keys[i].key_func = bufEOL;
		if (i == *K_CTRL_H || i == *K_BACKSPACE)
			keys[i].key_func = bufBS;
		if (i == *K_CTRL_C)
			keys[i].key_func = bufCLR;
		if (i == *K_CTRL_A)
			keys[i].key_func = bufBEGIN;
		if (i == *K_CTRL_E)
			keys[i].key_func = bufEND;
		if (i == *K_TAB)
			keys[i].key_func = bufComp;
		if (i >= *K_SPACE && i < *K_BACKSPACE)
			keys[i].key_func = bufCHAR;
		if (i > *K_BACKSPACE && i < 0xff)
			keys[i].key_func = bufCHAR;
		if (i == '?')
			keys[i].key_func = bufHelp;
	}
	// alt+chars
	for (i = 256; i < 512; i++) {
		keys[i].key_ch[0] = 0x1b;
		keys[i].key_ch[1] = (u_char) i - 256;
		keys[i].key_len = 2;
	}

	// 3 bytes
	keys[i].key_len = sizeof K_F1 - 1;
	memcpy(keys[i].key_ch, K_F1, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_F2 - 1;
	memcpy(keys[i].key_ch, K_F2, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_F3 - 1;
	memcpy(keys[i].key_ch, K_F3, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_F4 - 1;
	memcpy(keys[i].key_ch, K_F4, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_SH_F1 - 1;
	memcpy(keys[i].key_ch, K_CTRL_SH_F1, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_SH_F2 - 1;
	memcpy(keys[i].key_ch, K_CTRL_SH_F2, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_SH_F3 - 1;
	memcpy(keys[i].key_ch, K_CTRL_SH_F3, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_SH_F4 - 1;
	memcpy(keys[i].key_ch, K_CTRL_SH_F4, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_SH_F5 - 1;
	memcpy(keys[i].key_ch, K_CTRL_SH_F5, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_SH_F6 - 1;
	memcpy(keys[i].key_ch, K_CTRL_SH_F6, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_SH_F7 - 1;
	memcpy(keys[i].key_ch, K_CTRL_SH_F7, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_SH_F8 - 1;
	memcpy(keys[i].key_ch, K_CTRL_SH_F8, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_SH_F9 - 1;
	memcpy(keys[i].key_ch, K_CTRL_SH_F9, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_SH_F10 - 1;
	memcpy(keys[i].key_ch, K_CTRL_SH_F10, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_SH_F11 - 1;
	memcpy(keys[i].key_ch, K_CTRL_SH_F11, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_SH_F12 - 1;
	memcpy(keys[i].key_ch, K_CTRL_SH_F12, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_F1 - 1;
	memcpy(keys[i].key_ch, K_CTRL_F1, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_F2 - 1;
	memcpy(keys[i].key_ch, K_CTRL_F2, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_F3 - 1;
	memcpy(keys[i].key_ch, K_CTRL_F3, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_F4 - 1;
	memcpy(keys[i].key_ch, K_CTRL_F4, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_F5 - 1;
	memcpy(keys[i].key_ch, K_CTRL_F5, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_F6 - 1;
	memcpy(keys[i].key_ch, K_CTRL_F6, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_F7 - 1;
	memcpy(keys[i].key_ch, K_CTRL_F7, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_F8 - 1;
	memcpy(keys[i].key_ch, K_CTRL_F8, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_F9 - 1;
	memcpy(keys[i].key_ch, K_CTRL_F9, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_F10 - 1;
	memcpy(keys[i].key_ch, K_CTRL_F10, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_F11 - 1;
	memcpy(keys[i].key_ch, K_CTRL_F11, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_CTRL_F12 - 1;
	memcpy(keys[i].key_ch, K_CTRL_F12, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_HOME - 1;
	keys[i].key_func = bufBEGIN;
	memcpy(keys[i].key_ch, K_HOME, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_END - 1;
	keys[i].key_func = bufEND;
	memcpy(keys[i].key_ch, K_END, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_UP - 1;
	keys[i].key_func = bufUP;
	memcpy(keys[i].key_ch, K_UP, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_DOWN - 1;
	keys[i].key_func = bufDOWN;
	memcpy(keys[i].key_ch, K_DOWN, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_RIGHT - 1;
	keys[i].key_func = bufRIGHT;
	memcpy(keys[i].key_ch, K_RIGHT, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_LEFT - 1;
	keys[i].key_func = bufLEFT;
	memcpy(keys[i].key_ch, K_LEFT, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_BTAB - 1;
	keys[i].key_func = bufBTAB;
	memcpy(keys[i].key_ch, K_BTAB, keys[i].key_len);
	i++;
	// 4 bytes
	keys[i].key_len = sizeof K_INS - 1;
	keys[i].key_func = bufMODE;
	memcpy(keys[i].key_ch, K_INS, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_DEL - 1;
	keys[i].key_func = bufDEL;
	memcpy(keys[i].key_ch, K_DEL, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_PGUP - 1;
	memcpy(keys[i].key_ch, K_PGUP, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_PGDN - 1;
	memcpy(keys[i].key_ch, K_PGDN, keys[i].key_len);
	i++;
	// 5 bytes
	keys[i].key_len = sizeof K_F5 - 1;
	memcpy(keys[i].key_ch, K_F5, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_F6 - 1;
	memcpy(keys[i].key_ch, K_F6, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_F7 - 1;
	memcpy(keys[i].key_ch, K_F7, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_F8 - 1;
	memcpy(keys[i].key_ch, K_F8, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_F9 - 1;
	memcpy(keys[i].key_ch, K_F9, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_F10 - 1;
	memcpy(keys[i].key_ch, K_F10, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_F11 - 1;
	memcpy(keys[i].key_ch, K_F11, keys[i].key_len);
	i++;
	keys[i].key_len = sizeof K_F12 - 1;
	memcpy(keys[i].key_ch, K_F12, keys[i].key_len);
	i++;

	buffer->line_keys = keys;
	return buffer;
}

/*
 * cliInitLine() Init CLI input line terminal
 * @buffer = CLI buffer
 * return: none
*/
int
cliInitLine(linebuffer_t * __restrict buffer)
{
	struct termios t;

	memset(&t, 0, sizeof t);
	tcgetattr(buffer->line_in, &t);
	t.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO | ECHOCTL | ECHOE | ECHOK | ECHOKE | ECHONL | ECHOPRT);
	t.c_iflag |= IGNBRK;
	t.c_cc[VMIN] = 1;
	t.c_cc[VTIME] = 0;
	return tcsetattr(buffer->line_in, TCSANOW, &t);
}

/*
 * cliReadLine() Read line from opened CLI session
 * @buffer = CLI buffer
 * return: NULL if error or !=NULL readed line, must be free after use!
*/
char *
cliReadLine(linebuffer_t * __restrict buffer)
{
	int code, readLen;
	register int i;
	struct pollfd fds;
	char buf[BUFSIZ], *str = NULL;

	if (!buffer) {
		cli_SetErr(EINVAL, "Error:: invalid input parameters ...");
		return NULL;
	}

	memset(&fds, 0, sizeof fds);
	fds.fd = buffer->line_in;
	fds.events = POLLIN;

	printfCR(buffer, 1);
	while (42) {
		if (poll(&fds, 1, -1) < 1) {
			LOGERR;
			return str;
		}

		memset(buf, 0, sizeof buf);
		readLen = read(buffer->line_in, buf, BUFSIZ);
		if (readLen == -1) {
			LOGERR;
			return str;
		}
		if (!readLen) {
			if (buffer->line_buf)
				str = strdup(buffer->line_buf);
			else
				cli_SetErr(EPIPE, "Error:: unknown state ...");
			return str;
		}

recheck:
		for (code = RETCODE_OK, i = MAX_BINDKEY - 1; i > -1; i--)
			if (readLen >= buffer->line_keys[i].key_len && 
					!memcmp(buffer->line_keys[i].key_ch, buf, 
						buffer->line_keys[i].key_len)) {
				readLen -= buffer->line_keys[i].key_len;
				if (readLen)
					memmove(buf, buf + buffer->line_keys[i].key_len, readLen);
				else
					memset(buf, 0, buffer->line_keys[i].key_len);

				if (buffer->line_keys[i].key_func)
					if ((code = buffer->line_keys[i].key_func(i, buffer)))
						readLen = 0;

				if (readLen)
					goto recheck;
				else
					break;
			}

		if (code)
			break;
	}

	if (code != RETCODE_ERR && code != RETCODE_EOF && buffer->line_buf)
		str = strdup(buffer->line_buf);
	return str;
}


/*
 * cliNetLoop() CLI network main loop binded to socket
 * @buffer = CLI buffer
 * @csHistFile = History file name
 * @sock = client socket
 * return: RETCODE_ERR error, RETCODE_OK ok
*/
int
cliNetLoop(linebuffer_t * __restrict buffer, const char *csHistFile, int sock)
{
	u_char buf[BUFSIZ];
	int pty, r, s, alen, flg, attrlen = 0, ret = 0;
	fd_set fds;
	struct timeval tv = { DEFAULT_SOCK_TIMEOUT, 0 };
	struct telnetAttrs *a, Attr[10];

	switch (forkpty(&pty, NULL, NULL, NULL)) {
		case -1:
			LOGERR;
			return -1;
		case 0:
			if (!buffer) {
				cli_SetErr(EINVAL, "Error:: invalid input parameters ...");
				return -1;
			} else
				close(sock);

			cliInitLine(buffer);
			ret = cliLoop(buffer, csHistFile) < 0 ? 1 : 0;
			cliEnd(buffer);

			exit(ret);
		default:
			telnet_SetCmd(Attr + 0, DO, TELOPT_TTYPE);
			telnet_SetCmd(Attr + 1, WILL, TELOPT_ECHO);
			telnet_Set_SubOpt(Attr + 2, TELOPT_LFLOW, LFLOW_OFF, NULL, 0);
			telnet_Set_SubOpt(Attr + 3, TELOPT_LFLOW, LFLOW_RESTART_XON, NULL, 0);
			telnet_SetCmd(Attr + 4, DO, TELOPT_LINEMODE);
			if ((ret = telnetSend(sock, Attr, 5, NULL, 0, 0)) == -1) {
				cli_Errno = telnet_GetErrno();
				strlcpy(cli_Error, telnet_GetError(), STRSIZ);
				return -1;
			} else
				flg = 0;

			while (42) {
				FD_ZERO(&fds);
				FD_SET(sock, &fds);
				FD_SET(pty, &fds);
				if ((ret = select(FD_SETSIZE, &fds, NULL, NULL, &tv)) < 1) {
					if (!ret)
						cli_SetErr(ETIMEDOUT, "Client session timeout ...");

					break;
				}

				r = FD_ISSET(sock, &fds) ? sock : pty;
				s = FD_ISSET(sock, &fds) ? pty : sock;

				if (FD_ISSET(sock, &fds)) {
					memset(buf, 0, BUFSIZ);
					if ((ret = telnetRecv(sock, &a, &alen, buf, BUFSIZ)) < 0) {
						if (a)
							free(a);

						if (-2 == ret)
							continue;
						// EOF
						if (-3 == ret)
							shutdown(sock, SHUT_RD);
						else {
							cli_Errno = telnet_GetErrno();
							strlcpy(cli_Error, telnet_GetError(), STRSIZ);
						}
						break;
					}
					attrlen = 0;
					if (1 == flg && alen) {
						telnet_SetCmd(&Attr[attrlen++], DONT, TELOPT_SGA);
						telnet_SetCmd(&Attr[attrlen++], DO, TELOPT_ECHO);
					}
					if (2 == flg && alen) {
						telnet_SetCmd(&Attr[attrlen++], WILL, TELOPT_ECHO);
						telnet_Set_SubOpt(&Attr[attrlen++], TELOPT_LFLOW, 
								LFLOW_OFF, NULL, 0);
						telnet_Set_SubOpt(&Attr[attrlen++], TELOPT_LFLOW, 
								LFLOW_RESTART_XON, NULL, 0);
						telnet_SetCmd(&Attr[attrlen++], DONT, TELOPT_LINEMODE);
					}
					if (a)
						free(a);

					if ((ret = write(pty, buf, ret)) == -1) {
						LOGERR;
						break;
					}
				}

				if (FD_ISSET(pty, &fds)) {
					memset(buf, 0, BUFSIZ);
					if ((ret = read(pty, buf, BUFSIZ)) == -1) {
						LOGERR;
						break;
					}

					if ((ret = telnetSend(sock, Attr, pty == s ? 0 : attrlen, buf, ret, 0)) == -1) {
						cli_Errno = telnet_GetErrno();
						strlcpy(cli_Error, telnet_GetError(), STRSIZ);
						break;
					} else
						flg++;
				}
			}

			close(pty);
	}

	return ret;
}

/*
 * cliLoop() CLI main loop
 * @buffer = CLI buffer
 * @csHistFile = History file name
 * return: RETCODE_ERR error, RETCODE_OK ok
*/
int
cliLoop(linebuffer_t * __restrict buffer, const char *csHistFile)
{
	char *line, *s, *t, **app, *items[MAX_PROMPT_ITEMS];
	register int i;
	int ret = RETCODE_OK;
	struct tagCommand *cmd;

	/* --- main body of CLI --- */

	if (cli_loadHistory(buffer, csHistFile) == RETCODE_ERR)
		return RETCODE_ERR;

	do {
		line = cliReadLine(buffer);
		if (!line) {
			printfNL(buffer, 0);
			break;
		} else
			cli_addHistory(buffer, NULL);
		// clear whitespaces
		for (s = line; isspace(*s); s++);
		if (*s) {
			for (t = s + strlen(s) - 1; t > s && isspace(*t); t--);
			*++t = 0;
		}

		if (*s) {
			memset(items, 0, sizeof(char*) * MAX_PROMPT_ITEMS);
			for (app = items; app < items + MAX_PROMPT_ITEMS - 1 && (*app = strsep(&s, " \t")); 
					*app ? app++ : app);

			// exec_cmd ...
			i = 0;
			SLIST_FOREACH(cmd, &buffer->line_cmds, cmd_next) {
				if (*items[0] && !strncmp(cmd->cmd_name, items[0], strlen(items[0])))
					break;
				else
					i++;
			}

			if (!cmd) {
				cli_Printf(buffer, "\nCommand '%s' not found!\n", items[0]);
				ret = -1;
			} else
				if (cmd->cmd_func)
					ret = cmd->cmd_func(buffer, i, items);
				else {
					clrscrEOL(buffer);
					printfCR(buffer, 1);
				}
		}

		cli_freeLine(buffer);
		cli_resetHistory(buffer);
		free(line);
	} while (ret < 1);

	cli_saveHistory(buffer, csHistFile, HISTORY_LINES);
	return ret;
}

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>