File:  [ELWIX - Embedded LightWeight unIX -] / libaitcli / src / aitcli.c
Revision 1.1.1.1.2.17: download - view: text, annotated - select for diffs - revision graph
Fri Jun 4 11:30:31 2010 UTC (14 years, 1 month ago) by misho
Branches: cli1_0
Diff to: branchpoint 1.1.1.1: preferred, colored
remove syslog final

/*************************************************************************
* (C) 2010 AITNET ltd - Sofia/Bulgaria - <misho@aitbg.com>
*  by Michael Pounov <misho@openbsd-bg.org>
*
* $Author: misho $
* $Id: aitcli.c,v 1.1.1.1.2.17 2010/06/04 11:30:31 misho Exp $
*
*************************************************************************/
#include "global.h"


#pragma GCC visibility push(hidden)

cliCommands_t cli_stdCmds[] = {
	{ "test", cli_Cmd_Unsupported, "Test - Don`t use default command structure!", "test <cr>", cli_Comp_Filename }, 
	{ "-------", NULL, "---------------------", NULL, NULL }, 
	{ "help", cli_Cmd_Help, "Help screen", "help [command] <cr>", NULL }, 
	{ "exit", cli_Cmd_Exit, "Exit from console", "exit <cr>", NULL }, 
	{ NULL, NULL, NULL, NULL }
};

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

int cli_Errno;
char cli_Error[STRSIZ];

char cli_pending_special_char;

#pragma GCC visibility pop


static void cli_Null_Prep_Term(int meta)
{
}

static int cli_Net_rl_GetCh(FILE *s)
{
	int ch = rl_getc(s);

	if (!cli_pending_special_char && 0x1b == ch) {
		cli_pending_special_char = ch;
		return ch;
	}
	if (cli_pending_special_char && 0x5b == ch) {
		cli_pending_special_char = ch;
		return ch;
	}
	if (0x5b == cli_pending_special_char) {
		cli_pending_special_char = 0;
		return ch;
	}

	cli_pending_special_char = 0;
	fputc(ch, rl_outstream);
	fflush(rl_outstream);
	return ch;
}

#if 0
static void cli_Line_Handler(char *line)
{
	int len;
	static char cli_Buffer[BUFSIZ];
	static int cli_BufferLen, cli_BufferPos;

	if (!line) {	// EOF
		fwrite("\x4", 1, 1, rl_outstream);	// ctrl+D
		goto end;
	} else
		len = strlen(line);
	if (BUFSIZ - 2 < len)
		cli_BufferPos = cli_BufferLen = 0;
	else {
		if (BUFSIZ - 2 < len + cli_BufferLen) {
			if (BUFSIZ - 2 >= cli_BufferLen - cli_BufferPos + len) {
				cli_BufferLen -= cli_BufferPos;
				memmove(cli_Buffer, cli_Buffer + cli_BufferPos, cli_BufferLen);
			} else
				cli_BufferLen = 0;

			cli_BufferPos = 0;
		}

		memcpy(cli_Buffer + cli_BufferLen, line, len);
		cli_BufferLen += len;
		cli_Buffer[cli_BufferLen++] = '\r';
		cli_Buffer[cli_BufferLen++] = '\n';
	}

	fwrite(cli_Buffer, 1, cli_BufferLen, rl_outstream);
	if (!cli_pending_special_char) {
		fwrite("\r", 1, 1, rl_outstream);
		if (*line)
			add_history(line);
	}

	free(line);
end:
	rl_callback_handler_remove();
	if (cli_pending_special_char) {
		fwrite(&cli_pending_special_char, 1, 1, rl_outstream);
		cli_pending_special_char = 0;
	}
	fflush(rl_outstream);
}
#endif


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

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

/*
 * cli_Printf() Printf CLI features
 * @out = Output stream
 * @csFormat = Printf format string
 * return: -1 error, != -1 printed chars
*/
inline int cli_Printf(FILE *out, const char *csFormat, ...)
{
	va_list lst;
	int ret;

	va_start(lst, csFormat);

	ret = vfprintf(out, csFormat, lst);
	if (-1 == ret)
		LOGERR;

	va_end(lst);
	return ret;
}


/*
 * cliComp() Initialize completion CLI features
 * @cmdComplete = Completion function
 * @cmdEntry = Compentry function
 * return: none
*/
inline void cliComp(cli_Completion_t *cmdComplete, cli_CompEntry_t *cmdEntry)
{
	// command completon
	rl_attempted_completion_function = cmdComplete;
	rl_completion_entry_function = cmdEntry;
}

/*
 * cliTTY() Initialize I/O TTY CLI features
 * @term = terminal name
 * @inp = input handle
 * @out = output handle
 * @win = window size
 * return: -1 error, != -1 ok
*/
inline int cliTTY(const char *term, FILE *inp, FILE *out, struct winsize *win)
{
	if (term)
		rl_terminal_name = term;

	if (inp)
		rl_instream = inp;
	if (out)
		rl_outstream = out;

	if (win)
	       if (ioctl(!rl_outstream ? STDOUT_FILENO : fileno(rl_outstream), TIOCSWINSZ, win) == -1) {
		       LOGERR;
		       return -1;
	       }

	return 0;
}

/*
 * cli_ReadHistory() Read CLI History from file
 * @csFile = history file name, if NULL default history name is ".aitcli.history"
 * return: -1 error; != -1 readed ok
*/
inline int cli_ReadHistory(const char *csFile)
{
	return read_history(!csFile ? ".aitcli.history" : csFile);
}

/*
 * cli_WriteHistory() Write CLI History to file
 * @csFile = history file name, if NULL default history name is ".aitcli.history"
 * @lineNum = save number of history entry lines, if -1 all lines saved without limit
 * return: -1 error; != -1 readed ok
*/
inline int cli_WriteHistory(const char *csFile, int lineNum)
{
	int ret;
	const char *psFile = !csFile ? ".aitcli.history" : csFile;

	ret = write_history(psFile);
	if (-1 != ret && -1 != lineNum)
		history_truncate_file(psFile, lineNum);

	return ret;
}

/*
 * cliNetInit() Initialize Readline if CLI bind to socket
 * @csProg = program name
 * @pty = Master pty
 * @term = stdin termios
 * return: none
*/
void cliNetInit(const char *csProg, int pty, struct termios *term)
{
	struct termios t;
	int on = 1;

	memset(&t, 0, sizeof t);
	if (term)
		t = *term;
	else {
		t.c_lflag = TTYDEF_LFLAG;
		t.c_iflag = TTYDEF_IFLAG;
		t.c_oflag = TTYDEF_OFLAG;
		t.c_cflag = TTYDEF_CFLAG;
		cfsetspeed(&t, B9600);
	}

	t.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO | ECHOCTL | ECHOE | ECHOK | ECHOKE | ECHONL | ECHOPRT);
//	t.c_iflag &= ~(ICRNL | BRKINT | INPCK | ISTRIP | IXON);
	t.c_iflag &= ~ICRNL;
	t.c_iflag |= IGNBRK;
	t.c_cc[VMIN] = 1;
	t.c_cc[VTIME] = 0;
	tcsetattr(pty, TCSANOW, &t);

	ioctl(pty, TIOCPKT, &on);

	rl_readline_name = csProg;
	rl_variable_bind("editing-mode", "emacs");

	rl_instream = fdopen(pty, "r");
	rl_outstream = NULL;
}

/*
 * cliNetExec() Execute net CLI main loop
 * @cmdList = Commands list
 * @csPrompt = Prompt text
 * @sock = client socket
 * @term = stdin termios
 * @win = window size of tty
 * return: -1 error, 0 = exit w/^+D, 1 done.
*/
int cliNetExec(cliCommands_t *cmdList, const char *csPrompt, int sock, struct termios *term, struct winsize *win)
{
	int pty, ret = 0, alen, attrlen, flg;
	fd_set fds;
	struct timeval tv = { DEFAULT_SOCK_TIMEOUT, 0 };
	u_char buf[BUFSIZ];
	struct telnetAttrs *a, Attr[10];

	switch (forkpty(&pty, NULL, term, win)) {
		case -1:
			LOGERR;
			return -1;
		case 0:
			close(sock);

			rl_prep_term_function = cli_Null_Prep_Term;
			rl_getc_function = cli_Net_rl_GetCh;

			cliNetInit(getprogname(), STDIN_FILENO, term);
			ret = cliExec(cmdList, csPrompt) < 0 ? 1 : 0;
			/* spawn Shell mode */
			/*
			execl("/bin/tcsh", "tcsh", NULL);
			*/
			_exit(ret);
		default:
			cliNetInit(getprogname(), pty, term);

			/* spawn Shell mode */
			/*
			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);
			*/
			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;
				}

				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;
					}
					if (a)
						free(a);
					if (alen) {
						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 ((ret = telnetSend(sock, Attr, attrlen, buf, ret, 0)) == -1) {
							cli_Errno = telnet_GetErrno();
							strlcpy(cli_Error, telnet_GetError(), STRSIZ);
							break;
						}
					}

					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)
							shutdown(sock, SHUT_WR);
						else
							LOGERR;
						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 ((ret = telnetSend(sock, Attr, attrlen, buf, ret, 0)) == -1) {
						cli_Errno = telnet_GetErrno();
						strlcpy(cli_Error, telnet_GetError(), STRSIZ);
						break;
					}
				}
			}

			close(pty);
	}

	return ret;
}

/*
 * cliExec() Execute CLI main loop
 * @cmdList = Commands list
 * @csPrompt = Prompt text
 * return: -1 error, 0 = exit w/^+D, 1 done.
*/
int cliExec(cliCommands_t *cmdList, const char *csPrompt)
{
	char *line, *s, *t, **app, *items[MAX_PROMPT_ITEMS];
	int ret = 0;
	register int i;
	cliCommands_t *cmd = NULL;
	FILE *out;

	inline int inline_help()
	{
		cli_Cmd_Help(cmdList ? cmdList : cli_stdCmds, -1, out, NULL);
		rl_on_new_line();
		return 0;
	}

	char **cli_stdCompletion(const char *text, int start, int end)
	{
		register int i;
		char **matches = NULL;

		char *cmdCompGet(const char *text, int state)
		{
			int len = strlen(text);

			for (i = state; cmdList[i].cmd_name; i++) {
				if (strncmp(cmdList[i].cmd_name, "---", 3) && 
						!strncmp(cmdList[i].cmd_name, text, len))
					return strdup(cmdList[i].cmd_name);
			}

			return NULL;
		}

		if (!start)
			matches = rl_completion_matches(text, cmdCompGet);
		else
			for (i = 0; cmdList[i].cmd_name; i++) {
				if (!cmdList[i].cmd_comp)
					continue;
				if (!strncmp(rl_line_buffer, cmdList[i].cmd_name, strlen(cmdList[i].cmd_name)))
					matches = rl_completion_matches(text, cmdList[i].cmd_comp);
			}

		return matches;
	}
	char *cli_stdCompEntry(const char *ignore, int invoking_key)
	{
		return NULL;
	}

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

	out = rl_outstream;
	if (!out)
		out = stdout;

	rl_bind_key('?', inline_help);
	if (!rl_attempted_completion_function) 
		cliComp(cli_stdCompletion, cli_stdCompEntry);

	do {
		line = readline(csPrompt);
		if (!line) {	// ^+d
			cli_Printf(out, "\n");
			break;
		}
		// 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) {
			add_history(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);

			/*
			for (i = 0; i < MAX_PROMPT_ITEMS; i++)
				cli_Printf(out, "i=%d %s\n", i, items[i]);
				*/

			// exec_cmd ...
			for (cmd = NULL, i = 0; cmdList[i].cmd_name; i++)
				if (*items[0] && !strncmp(cmdList[i].cmd_name, items[0], strlen(items[0]))) {
					cmd = &cmdList[i];
					break;
				}
			if (!cmd) {
				cli_Printf(out, "Command '%s' not found!\n", items[0]);
				ret = -1;
			} else
				ret = cmd->cmd_func(cmdList, i, out, items);
		}

		free(line);
	} while (ret < 1);

	return ret;
}

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