--- libaitcli/src/aitcli.c 2010/06/04 13:51:21 1.2.2.3 +++ libaitcli/src/aitcli.c 2010/06/07 16:16:12 1.2.2.12 @@ -3,24 +3,15 @@ * by Michael Pounov * * $Author: misho $ -* $Id: aitcli.c,v 1.2.2.3 2010/06/04 13:51:21 misho Exp $ +* $Id: aitcli.c,v 1.2.2.12 2010/06/07 16:16:12 misho Exp $ * *************************************************************************/ #include "global.h" +#include "cli.h" #pragma GCC visibility push(hidden) -/* -commands_t cli_stdCmds[] = { - { "test", cli_Cmd_Unsupported, "Test - Don`t use default command structure!", "test ", cli_Comp_Filename }, - { "-------", NULL, "---------------------", NULL, NULL }, - { "help", cli_Cmd_Help, "Help screen", "help [command] ", NULL }, - { "exit", cli_Cmd_Exit, "Exit from console", "exit ", NULL }, - { NULL, NULL, NULL, NULL } -}; -*/ - // ------------------------------------------------ int cli_Errno; @@ -96,13 +87,14 @@ printfCR(linebuffer_t * __restrict buf, int prompt) } static inline void -printfCLI(linebuffer_t * __restrict buf, const unsigned char *text, int textlen, int prompt) +printfNL(linebuffer_t * __restrict buf, int prompt) { - if (buf && text && textlen) { - if (prompt && buf->line_prompt) - write(buf->line_out, buf->line_prompt, buf->line_bol); + if (buf) { + write(buf->line_out, K_ENTER, 1); - write(buf->line_out, text, textlen); + if (prompt) + if (prompt && buf->line_prompt) + write(buf->line_out, buf->line_prompt, buf->line_bol); } } @@ -132,8 +124,8 @@ bufCHAR(int idx, void * __restrict buffer) write(buf->line_out, buf->line_keys[idx].key_ch, buf->line_keys[idx].key_len); if (buf->line_mode == LINEMODE_INS) { - printfCLI(buf, (const u_char*) buf->line_buf + pos + buf->line_keys[idx].key_len, - buf->line_len - buf->line_eol, 0); + 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; @@ -152,12 +144,11 @@ bufEOL(int idx, void * __restrict buffer) static int bufEOF(int idx, void * __restrict buffer) { - linebuffer_t *buf = buffer; - + /* if (!buffer || idx < 0 || idx > MAX_BINDKEY) return RETCODE_ERR; + */ - write(buf->line_out, buf->line_keys[idx].key_ch, buf->line_keys[idx].key_len); return RETCODE_EOF; } @@ -371,9 +362,136 @@ bufDEL(int idx, void * __restrict buffer) 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]))) { + j++; + c = cmd; + strlcat(szLine, " ", STRSIZ); + strlcat(szLine, cmd->cmd_name, STRSIZ); + } + + printf("i=%d j=%d c=%p name=%s comp=%p\n", i, j, c, c->cmd_name, c->cmd_comp); + if (i > 1 && j == 1 && c && c->cmd_comp) { + /* we are on argument of command and has complition callback */ + printf("ima comp!\n"); + goto endcomp; + } + } 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) + 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); + } + 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); + printfCR(buf, 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_BindKey() Bind function to key * @key = key structure * @buffer = CLI buffer @@ -401,9 +519,123 @@ cli_BindKey(bindkey_t * __restrict key, linebuffer_t * /* + * 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 + * @anComp = Completion array terminated with NULL element, -1 complete commands, NULL nothing + * 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, const char **anComp) +{ + struct tagCommand *cmd; + + if (!buffer || !csCmd || !funcCmd) { + 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_comp = (char**) anComp; + 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 + * @anComp = Completion array terminated with NULL element, -1 complete commands, NULL nothing, + * update only if funcCmd is not NULL + * 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, const char **anComp) +{ + 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; + cmd->cmd_comp = (char**) anComp; + } + 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 text + * @str = Add custom text or if NULL use readed line from CLI buffer * return: RETCODE_ERR error, RETCODE_OK ok */ int @@ -513,10 +745,8 @@ cli_loadHistory(linebuffer_t * __restrict buffer, cons strlcpy(szFName, histfile, MAXPATHLEN); f = fopen(szFName, "r"); - if (!f) { - LOGERR; - return RETCODE_ERR; - } + if (!f) + return RETCODE_OK; while (fgets(buf, BUFSIZ, f)) { if (!*buf || *buf == '#') @@ -620,8 +850,13 @@ 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); @@ -669,6 +904,7 @@ cliInit(int fin, int fout, const char *prompt) buffer->line_out = fout; TAILQ_INIT(&buffer->line_history); + SLIST_INIT(&buffer->line_cmds); if (prompt) { buffer->line_prompt = strdup(prompt); @@ -702,6 +938,11 @@ cliInit(int fin, int fout, const char *prompt) } else memset(keys, 0, sizeof(bindkey_t) * (MAX_BINDKEY + 1)); + /* add helper functions */ + cli_addCommand(buffer, "exit", 0, cli_Cmd_Exit, "exit ", "Exit from console", NULL); + cli_addCommand(buffer, "help", 0, cli_Cmd_Help, "help [command] ", "Help screen", + (const char**) -1); + /* fill key bindings */ // ascii chars & ctrl+chars for (i = 0; i < 256; i++) { @@ -720,10 +961,14 @@ cliInit(int fin, int fout, const char *prompt) 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++) { @@ -897,65 +1142,97 @@ cliInit(int fin, int fout, const char *prompt) return buffer; } - - /* - * cliNetInit() Initialize Readline if CLI bind to socket - * @csProg = program name - * @pty = Master pty - * @term = stdin termios - * return: none + * cliReadLine() Read line from opened CLI session + * @buffer = CLI buffer + * return: NULL if error or !=NULL readed line, must be free after use! */ -/* -void cliNetInit(const char *csProg, int pty, struct termios *term) +char * +cliReadLine(linebuffer_t * __restrict buffer) { - struct termios t; - int on = 1; + int code, readLen; + register int i; + struct pollfd fds; + char buf[BUFSIZ], *str = NULL; - 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); + if (!buffer) { + cli_SetErr(EINVAL, "Error:: invalid input parameters ..."); + return NULL; } - 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); + memset(&fds, 0, sizeof fds); + fds.fd = buffer->line_in; + fds.events = POLLIN; - ioctl(pty, TIOCPKT, &on); + printfCR(buffer, 1); + while (42) { + if (poll(&fds, 1, -1) < 1) { + LOGERR; + return str; + } - rl_readline_name = csProg; - rl_variable_bind("editing-mode", "emacs"); + 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; + } - rl_instream = fdopen(pty, "r"); - rl_outstream = NULL; +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; } -*/ + + /* - * cliNetExec() Execute net CLI main loop - * @cmdList = Commands list - * @csPrompt = Prompt text + * cliNetLoop() CLI network main loop binded to socket + * @buffer = CLI buffer + * @csHistFile = History file name * @sock = client socket * @term = stdin termios * @win = window size of tty - * return: -1 error, 0 = exit w/^+D, 1 done. + * return: RETCODE_ERR error, RETCODE_OK ok */ int -cliNetExec(commands_t *cmdList, const char *csPrompt, int sock, struct termios *term, struct winsize *win) +cliNetLoop(linebuffer_t * __restrict buffer, const char *csHistFile, int sock, + struct termios *term, struct winsize *win) { - int pty, ret = 0, r, s, alen, attrlen, flg; + u_char buf[BUFSIZ]; + int pty, r, s, alen, attrlen, flg, ret = 0; 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)) { @@ -965,11 +1242,17 @@ cliNetExec(commands_t *cmdList, const char *csPrompt, case 0: close(sock); - ret = cliExec(cmdList, csPrompt) < 0 ? 1 : 0; + if (buffer) { + ret = cliLoop(buffer, csHistFile) < 0 ? 1 : 0; + cliEnd(buffer); + } else + cli_SetErr(EINVAL, "Error:: invalid input parameters ..."); + /* spawn Shell mode */ /* execl("/bin/tcsh", "tcsh", NULL); */ + _exit(ret); default: /* spawn Shell mode */ @@ -1045,26 +1328,20 @@ cliNetExec(commands_t *cmdList, const char *csPrompt, } /* - * cliExec() Execute CLI main loop - * @cmdList = Commands list - * @csPrompt = Prompt text - * return: -1 error, 0 = exit w/^+D, 1 done. + * cliLoop() CLI main loop + * @buffer = CLI buffer + * @csHistFile = History file name + * return: RETCODE_ERR error, RETCODE_OK ok */ -/* -int cliExec(cliCommands_t *cmdList, const char *csPrompt) +int +cliLoop(linebuffer_t * __restrict buffer, const char *csHistFile) { char *line, *s, *t, **app, *items[MAX_PROMPT_ITEMS]; - int ret = 0; register int i; - cliCommands_t *cmd = NULL; - FILE *out; + int ret = RETCODE_OK; + struct tagCommand *cmd; - 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) { @@ -1096,27 +1373,22 @@ int cliExec(cliCommands_t *cmdList, const char *csProm 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); + if (cli_loadHistory(buffer, csHistFile) == RETCODE_ERR) + return RETCODE_ERR; do { - line = readline(csPrompt); - if (!line) { // ^+d - cli_Printf(out, "\n"); + line = cliReadLine(buffer); + if (!line) { + printfNL(buffer, 0); break; - } + } else + cli_addHistory(buffer, NULL); // clear whitespaces for (s = line; isspace(*s); s++); if (*s) { @@ -1125,28 +1397,31 @@ int cliExec(cliCommands_t *cmdList, const char *csProm } 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); // 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]; + 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(out, "Command '%s' not found!\n", items[0]); + cli_Printf(buffer, "\nCommand '%s' not found!\n", items[0]); ret = -1; } else - ret = cmd->cmd_func(cmdList, i, out, items); + ret = cmd->cmd_func(buffer, i, items); } + cli_freeLine(buffer); + cli_resetHistory(buffer); free(line); } while (ret < 1); + cli_saveHistory(buffer, csHistFile, HISTORY_LINES); return ret; } -*/