File:  [ELWIX - Embedded LightWeight unIX -] / libaitcli / src / aitcli.c
Revision 1.19: download - view: text, annotated - select for diffs - revision graph
Mon Dec 5 22:23:38 2022 UTC (17 months, 2 weeks ago) by misho
Branches: MAIN
CVS tags: cli4_6, cli4_5, HEAD, CLI4_5, CLI4_4
version 4.4

/*************************************************************************
* (C) 2010 AITNET ltd - Sofia/Bulgaria - <misho@aitbg.com>
*  by Michael Pounov <misho@openbsd-bg.org>
*
* $Author: misho $
* $Id: aitcli.c,v 1.19 2022/12/05 22:23:38 misho Exp $
*
**************************************************************************
The ELWIX and AITNET software is distributed under the following
terms:

All of the documentation and software included in the ELWIX and AITNET
Releases is copyrighted by ELWIX - Sofia/Bulgaria <info@elwix.org>

Copyright 2004 - 2022
	by Michael Pounov <misho@elwix.org>.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software
   must display the following acknowledgement:
This product includes software developed by Michael Pounov <misho@elwix.org>
ELWIX - Embedded LightWeight unIX and its contributors.
4. Neither the name of AITNET nor the names of its contributors
   may be used to endorse or promote products derived from this software
   without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY AITNET AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
*/
#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
int
cli_GetErrno()
{
	return cli_Errno;
}

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

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

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

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

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

	if (buf && buf->line_prompt) {
		ign = write(buf->line_out, K_CR, 1);

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

static inline void
printfEOL(linebuffer_t * __restrict buf, int len, int prompt)
{
	int ign __attribute__((unused));

	if (buf) {
		if (prompt && buf->line_prompt) {
			ign = write(buf->line_out, K_CR, 1);
			ign = write(buf->line_out, buf->line_prompt, buf->line_bol);
		}

		ign = 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)
{
	int ign __attribute__((unused));

	if (buf && prompt && buf->line_prompt) {
		ign = write(buf->line_out, K_CR, 1);
		ign = write(buf->line_out, buf->line_prompt, buf->line_bol);
	}
}

static inline void
printfNL(linebuffer_t * __restrict buf, int prompt)
{
	int ign __attribute__((unused));

	if (buf) {
		ign = write(buf->line_out, K_ENTER, 1);

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

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

static int
bufCHAR(int idx, void * __restrict cli_buffer)
{
	linebuffer_t *buf = cli_buffer;
	int pos;
	int ign __attribute__((unused));

	if (!cli_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;

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

	if (buf->line_mode == LINEMODE_INS) {
		ign = 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 cli_buffer)
{
	if (!cli_buffer || idx < 0 || idx > MAX_BINDKEY)
		return RETCODE_ERR;

	printfCR(cli_buffer, 1);
	return RETCODE_EOL;
}

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

	return RETCODE_EOF;
}

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

	if (!cli_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 cli_buffer)
{
	linebuffer_t *buf = cli_buffer;
	int pos;

	if (!cli_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 cli_buffer)
{
	if (!cli_buffer || idx < 0 || idx > MAX_BINDKEY)
		return RETCODE_ERR;

	clrscrEOL(cli_buffer);
	cli_freeLine(cli_buffer);

	printfCR(cli_buffer, 1);
	return RETCODE_OK;
}

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

	if (!cli_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 cli_buffer)
{
	linebuffer_t *buf = cli_buffer;

	if (!cli_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 cli_buffer)
{
	linebuffer_t *buf = cli_buffer;

	if (!cli_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 cli_buffer)
{
	linebuffer_t *buf = cli_buffer;

	if (!cli_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 cli_buffer)
{
	linebuffer_t *buf = cli_buffer;

	if (!cli_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 cli_buffer)
{
	linebuffer_t *buf = cli_buffer;

	if (!cli_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 cli_buffer)
{
	linebuffer_t *buf = cli_buffer;

	if (!cli_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 cli_buffer)
{
	linebuffer_t *buf = cli_buffer;

	if (!cli_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 cli_buffer)
{
	linebuffer_t *buf = cli_buffer;
	char *str, *s, **app, *items[MAX_PROMPT_ITEMS], szLine[STRSIZ];
	register int i, j;
	struct tagCommand *cmd, *c;
	int pos, ret = RETCODE_OK;
	int ign __attribute__((unused));

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

	str = e_strdup(buf->line_buf);
	if (!str)
		return RETCODE_ERR;
	else {
		s = str;
		str_Trim(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 & (1 << 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 & (1 << 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);
		ign = 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:
	e_free(str);
	return ret;
}

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

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

	cli_Cmd_Help(buf, buf->line_level, NULL);

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

static int
bufEndNode(int idx, void * __restrict cli_buffer)
{
	linebuffer_t *buf = cli_buffer;

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

	if (buf->line_level > 0) {
		printfNL(cli_buffer, 0);
		buf->line_level--;
		cli_Printf(buf, "Enter to config level %d\n", buf->line_level);
	}

	return bufCLR(idx, cli_buffer);
}


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

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

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

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


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

	if (!key || !cli_buffer) {
		cli_SetErr(EINVAL, "Invalid input parameters ...");
		return RETCODE_ERR;
	}

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

	return RETCODE_OK;
}


/*
 * cli_addCommand() - Add command to CLI session
 *
 * @cli_buffer = CLI buffer
 * @csCmd = Command name
 * @cliLevel = Level in CLI, -1 view from all levels, 0 hidden, >0 mask levels
 * @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 cli_buffer, const char *csCmd, 
		int cliLevel, cmd_func_t funcCmd, 
		const char *csInfo, const char *csHelp)
{
	struct tagCommand *cmd;

	if (!cli_buffer || !csCmd) {
		cli_SetErr(EINVAL, "Invalid input parameters ...");
		return RETCODE_ERR;
	}

	cmd = e_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(&cli_buffer->line_cmds, cmd, cmd_next);
	return RETCODE_OK;
}

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

	if (!cli_buffer || !csCmd) {
		cli_SetErr(EINVAL, "Invalid input parameters ...");
		return RETCODE_ERR;
	}

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

	return ret;
}

/*
 * cli_updCommand() - Update command in CLI session
 *
 * @cli_buffer = CLI buffer
 * @csCmd = Command name
 * @cliLevel = Level in CLI, -1 view from all levels, 0 hidden, >0 mask levels
 * @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 cli_buffer, const char *csCmd, 
		int cliLevel, cmd_func_t funcCmd, 
		const char *csInfo, const char *csHelp)
{
	struct tagCommand *cmd;
	int ret = RETCODE_OK;

	if (!cli_buffer || !csCmd) {
		cli_SetErr(EINVAL, "Invalid input parameters ...");
		return RETCODE_ERR;
	}

	SLIST_FOREACH(cmd, &cli_buffer->line_cmds, cmd_next)
		if ((!cmd->cmd_level || cmd->cmd_level == cliLevel) && 
				!strcmp(cmd->cmd_name, csCmd)) {
			if (!cmd->cmd_level)
				cmd->cmd_level = cliLevel;
			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
 *
 * @cli_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 cli_buffer, const char * __restrict str)
{
	struct tagHistory *h;

	if (!cli_buffer) {
		cli_SetErr(EINVAL, "Invalid input parameters ...");
		return RETCODE_ERR;
	}

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

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

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

		memcpy(h->hist_line, cli_buffer->line_buf, (h->hist_len = cli_buffer->line_len));
		str_Trim(h->hist_line);
		h->hist_len = strlen(h->hist_line);
	}

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

/*
 * cli_saveHistory() - Save history to file
 *
 * @cli_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 cli_buffer, const char *histfile, int lines)
{
	FILE *f;
	mode_t mode;
	char szFName[MAXPATHLEN];
	struct tagHistory *h;

	if (!cli_buffer) {
		cli_SetErr(EINVAL, "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, &cli_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
 *
 * @cli_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 cli_buffer, const char *histfile)
{
	FILE *f;
	char szFName[MAXPATHLEN], buf[BUFSIZ];
	struct tagHistory *h;

	if (!cli_buffer) {
		cli_SetErr(EINVAL, "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
			str_Trim(buf);

		if (!(h = e_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(&cli_buffer->line_history, h, hist_next);
	}

	fclose(f);

	return RETCODE_OK;
}

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


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

	if (cli_buffer) {
		if (cli_buffer->line_buf)
			e_free(cli_buffer->line_buf);

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

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

	return code;
}

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

		if (prompt) {
			cli_buffer->line_prompt = e_strdup(prompt);
			if (cli_buffer->line_prompt) {
				cli_buffer->line_bol = strlen(cli_buffer->line_prompt);
				cli_buffer->line_eol = cli_buffer->line_bol;
				cli_buffer->line_len = 1 + cli_buffer->line_eol;
			} else
				LOGERR;
		}
	} else
		cli_SetErr(EINVAL, "Invalid input parameters ...");
}


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

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

		if (cli_buffer->line_prompt)
			e_free(cli_buffer->line_prompt);

		if (cli_buffer->line_keys)
			e_free(cli_buffer->line_keys);
		if (cli_buffer->line_buf)
			e_free(cli_buffer->line_buf);

		e_free(cli_buffer);
		cli_buffer = NULL;
	} else
		cli_SetErr(EINVAL, "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 *cli_buffer;
	bindkey_t *keys;
	register int i;
	char szPrompt[STRSIZ + 16] = {[0 ... STRSIZ + 15] = 0};

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

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

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

		if (prompt) {
			strlcpy(cli_buffer->line_porigin, prompt, sizeof cli_buffer->line_porigin);
			snprintf(szPrompt, sizeof szPrompt, "%s{%d}> ", cli_buffer->line_porigin, cli_buffer->line_level);
			cli_buffer->line_prompt = e_strdup(szPrompt);
			if (!cli_buffer->line_prompt) {
				LOGERR;
				e_free(cli_buffer);
				return NULL;
			} else
				cli_buffer->line_eol = cli_buffer->line_bol = 
					strlen(cli_buffer->line_prompt);
		} else
			cli_buffer->line_mode = LINEMODE_OVER;
	}
	cli_buffer->line_buf = e_malloc(BUFSIZ);
	if (!cli_buffer->line_buf) {
		LOGERR;
		if (cli_buffer->line_prompt)
			e_free(cli_buffer->line_prompt);
		e_free(cli_buffer);
		return NULL;
	} else {
		memset(cli_buffer->line_buf, 0, BUFSIZ);
		cli_buffer->line_len = 1 + cli_buffer->line_eol;
	}
	keys = e_calloc(MAX_BINDKEY + 1, sizeof(bindkey_t));
	if (!keys) {
		LOGERR;
		if (cli_buffer->line_prompt)
			e_free(cli_buffer->line_prompt);
		e_free(cli_buffer->line_buf);
		e_free(cli_buffer);
		return NULL;
	} else
		memset(keys, 0, sizeof(bindkey_t) * (MAX_BINDKEY + 1));

	/* add helper functions */
	cli_addCommand(cli_buffer, "exit", 1, cli_Cmd_Exit, "exit <cr>", "Exit from console");
	cli_addCommand(cli_buffer, "help", -1, cli_Cmd_Help, "help [command] <cr>", "Help screen");
	cli_addCommand(cli_buffer, "-------", -1, NULL, "-------------------------", NULL);
	cli_addCommand(cli_buffer, "where", -1, cli_Cmd_WhereAmI, "where <cr>", 
			"Query current level of console");
	cli_addCommand(cli_buffer, "top", -1, cli_Cmd_Top, "top <cr>", "Top level of console");
	cli_addCommand(cli_buffer, "end", -1, cli_Cmd_End, "end <cr>", "End level of console");
	cli_addCommand(cli_buffer, "config", -1, cli_Cmd_Config, "config <cr>", 
			"Config next level of console");
	cli_addCommand(cli_buffer, "-------", -1, 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 (cli_buffer->line_prompt && (i == *K_CTRL_H || i == *K_BACKSPACE))
			keys[i].key_func = bufBS;
		if (i == *K_CTRL_C)
			keys[i].key_func = bufCLR;
		if (cli_buffer->line_prompt && i == *K_CTRL_A)
			keys[i].key_func = bufBEGIN;
		if (cli_buffer->line_prompt && i == *K_CTRL_E)
			keys[i].key_func = bufEND;
		if (cli_buffer->line_prompt && i == *K_TAB)
			keys[i].key_func = bufComp;
		if (i == *K_CTRL_Z)
			keys[i].key_func = bufEndNode;
		if (i >= *K_SPACE && i < *K_BACKSPACE)
			keys[i].key_func = bufCHAR;
		if (i > *K_BACKSPACE && i < 0xff)
			keys[i].key_func = bufCHAR;
		if (cli_buffer->line_prompt && 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;
	if (cli_buffer->line_prompt)
		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;
	if (cli_buffer->line_prompt)
		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;
	if (cli_buffer->line_prompt)
		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;
	if (cli_buffer->line_prompt)
		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;
	if (cli_buffer->line_prompt)
		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;
	if (cli_buffer->line_prompt)
		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;
	if (cli_buffer->line_prompt)
		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;
	if (cli_buffer->line_prompt)
		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;
	if (cli_buffer->line_prompt)
		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++;

	cli_buffer->line_keys = keys;
	return cli_buffer;
}

/*
 * cliSetLine() - Set CLI input line terminal
 *
 * @cli_buffer = CLI buffer
 * @old = Old terminal settings
 * return: -1 error or 0 ok
*/
int
cliSetLine(linebuffer_t * __restrict cli_buffer, struct termios * __restrict old)
{
	struct termios t;

	memset(&t, 0, sizeof t);
	tcgetattr(cli_buffer->line_in, &t);
	if (old)
		memcpy(old, &t, sizeof(struct termios));
	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(cli_buffer->line_in, TCSANOW, &t);
}

/*
 * cliResetLine() - Reset CLI input line terminal
 *
 * @cli_buffer = CLI buffer
 * @old = Original terminal settings
 * return: -1 error or 0 ok
*/
int
cliResetLine(linebuffer_t * __restrict cli_buffer, struct termios * __restrict orig)
{
	return tcsetattr(cli_buffer->line_in, TCSANOW, orig);
}

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

	if (!cli_buffer) {
		cli_SetErr(EINVAL, "Invalid input parameters ...");
		return NULL;
	} else if (timeout > 0)
		timeout *= 1000;	/* convert from sec to ms */

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

	printfCR(cli_buffer, 1);
	while (42) {
		if ((ret = poll(&fds, 1, timeout)) < 1) {
			if (!ret) {
				cli_buffer->line_kill = 1;
				if (str) {
					e_free(str);
					str = NULL;
				}
			} else
				LOGERR;
			return str;
		}

		memset(buf, 0, sizeof buf);
		readLen = read(cli_buffer->line_in, buf, BUFSIZ);
		if (readLen < 1) {
			if (readLen)
				LOGERR;
			return NULL;
		}

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

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

				if (readLen)
					goto recheck;
				else
					break;
			}

		if (code)
			break;
	}

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


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

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

			ret = cliLoop(cli_buffer, csHistFile, timeout) < 0 ? 1 : 0;
			cliEnd(cli_buffer);

			_exit(ret);
		default:
			cli_telnet_SetCmd(Attr + 0, DO, TELOPT_TTYPE);
			cli_telnet_SetCmd(Attr + 1, WILL, TELOPT_ECHO);
			cli_telnet_Set_SubOpt(Attr + 2, TELOPT_LFLOW, LFLOW_OFF, NULL, 0);
			cli_telnet_Set_SubOpt(Attr + 3, TELOPT_LFLOW, LFLOW_RESTART_XON, NULL, 0);
			cli_telnet_SetCmd(Attr + 4, DO, TELOPT_LINEMODE);
			if ((ret = cli_telnetSend(sock, Attr, 5, NULL, 0, 0)) == -1)
				return -1;
			else
				flg = 0;

			while (42) {
				if (waitpid(pid, &stat, WNOHANG))
					break;

				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;
				}

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

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

						if (-2 == ret)
							continue;
						// EOF
						if (-3 == ret)
							shutdown(sock, SHUT_RD);
						break;
					}
					attrlen = 0;
					if (1 == flg && alen) {
						cli_telnet_SetCmd(&Attr[attrlen++], DONT, TELOPT_SGA);
						cli_telnet_SetCmd(&Attr[attrlen++], DO, TELOPT_ECHO);
					}
					if (2 == flg && alen) {
						cli_telnet_SetCmd(&Attr[attrlen++], WILL, TELOPT_ECHO);
						cli_telnet_Set_SubOpt(&Attr[attrlen++], TELOPT_LFLOW, 
								LFLOW_OFF, NULL, 0);
						cli_telnet_Set_SubOpt(&Attr[attrlen++], TELOPT_LFLOW, 
								LFLOW_RESTART_XON, NULL, 0);
						cli_telnet_SetCmd(&Attr[attrlen++], DONT, TELOPT_LINEMODE);
					}
					if (a)
						e_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) {
						if (ret)
							LOGERR;
						break;
					}

					if ((ret = cli_telnetSend(sock, Attr, pty == s ? 0 : attrlen, 
									buf, ret, 0)) == -1)
						break;
					else
						flg++;
				}
			}

			close(pty);
	}

	return ret;
}

/*
 * cliRun() - CLI run command line
 *
 * @cli_buffer = CLI buffer
 * @psInput = Input command line
 * @prompt = Display prompt after command
 * return: RETCODE_ERR error, RETCODE_OK ok
*/
int
cliRun(linebuffer_t * __restrict cli_buffer, char *psInput, int prompt)
{
	char *line, *s, *t, **app, *items[MAX_PROMPT_ITEMS];
	register int i;
	int ret = RETCODE_OK;
	struct tagCommand *cmd;

	if (!psInput)
		return RETCODE_ERR;
	else
		line = psInput;

	// clear whitespaces
	for (s = line; isspace((int) *s); s++);
	if (*s) {
		for (t = s + strlen(s) - 1; t > s && isspace((int) *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, &cli_buffer->line_cmds, cmd_next) {
			if (!(cmd->cmd_level & (1 << cli_buffer->line_level)))
				continue;
			if (*items[0] && !strncmp(cmd->cmd_name, items[0], strlen(items[0])))
				break;
			else
				i++;
		}

		if (!cmd) {
			cli_Printf(cli_buffer, "%sCommand '%s' not found!\n", 
					cli_buffer->line_prompt ? "\n" : "", items[0]);
			ret = RETCODE_ERR;
		} else
			if (cmd->cmd_func) {
				if (prompt && cli_buffer->line_prompt)
					cli_Printf(cli_buffer, "\n");
				ret = cmd->cmd_func(cli_buffer, 
						cli_buffer->line_level, items);
			} else if (prompt) {
				clrscrEOL(cli_buffer);
				printfCR(cli_buffer, 1);
			}
	}

	return ret;
}

/*
 * cliLoop() - CLI main loop
 *
 * @cli_buffer = CLI buffer
 * @csHistFile = History file name
 * @timeout = Session timeout (-1 infinit)
 * return: RETCODE_ERR error, RETCODE_OK ok
*/
int
cliLoop(linebuffer_t * __restrict cli_buffer, const char *csHistFile, int timeout)
{
	char *line;
	int ret = RETCODE_OK;
	struct termios t;

	/* --- main body of CLI --- */
	cliSetLine(cli_buffer, &t);

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

	do {
		line = cliReadLine(cli_buffer, timeout);
		if (!line) {
			printfNL(cli_buffer, 0);
			break;
		} else
			cli_addHistory(cli_buffer, NULL);

		ret = cliRun(cli_buffer, line, 42);

		cli_freeLine(cli_buffer);
		cli_resetHistory(cli_buffer);
		e_free(line);
	} while (!cli_buffer->line_kill);

	cli_saveHistory(cli_buffer, csHistFile, HISTORY_LINES);

	/* --- restore tty --- */
	cliResetLine(cli_buffer, &t);

	return ret;
}

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