/*************************************************************************
* (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>