Annotation of libaitcli/src/aitcli.c, revision 1.1.1.1.2.17

1.1       misho       1: /*************************************************************************
                      2: * (C) 2010 AITNET ltd - Sofia/Bulgaria - <misho@aitbg.com>
                      3: *  by Michael Pounov <misho@openbsd-bg.org>
                      4: *
                      5: * $Author: misho $
1.1.1.1.2.17! misho       6: * $Id: aitcli.c,v 1.1.1.1.2.16 2010/06/04 11:25:45 misho Exp $
1.1       misho       7: *
                      8: *************************************************************************/
                      9: #include "global.h"
                     10: 
                     11: 
                     12: #pragma GCC visibility push(hidden)
                     13: 
                     14: cliCommands_t cli_stdCmds[] = {
                     15:        { "test", cli_Cmd_Unsupported, "Test - Don`t use default command structure!", "test <cr>", cli_Comp_Filename }, 
                     16:        { "-------", NULL, "---------------------", NULL, NULL }, 
                     17:        { "help", cli_Cmd_Help, "Help screen", "help [command] <cr>", NULL }, 
                     18:        { "exit", cli_Cmd_Exit, "Exit from console", "exit <cr>", NULL }, 
                     19:        { NULL, NULL, NULL, NULL }
                     20: };
                     21: 
                     22: // ------------------------------------------------
                     23: 
                     24: int cli_Errno;
                     25: char cli_Error[STRSIZ];
                     26: 
1.1.1.1.2.15  misho      27: char cli_pending_special_char;
1.1       misho      28: 
1.1.1.1.2.15  misho      29: #pragma GCC visibility pop
1.1       misho      30: 
1.1.1.1.2.11  misho      31: 
1.1.1.1.2.4  misho      32: static void cli_Null_Prep_Term(int meta)
                     33: {
                     34: }
                     35: 
1.1.1.1.2.15  misho      36: static int cli_Net_rl_GetCh(FILE *s)
                     37: {
                     38:        int ch = rl_getc(s);
                     39: 
                     40:        if (!cli_pending_special_char && 0x1b == ch) {
                     41:                cli_pending_special_char = ch;
                     42:                return ch;
                     43:        }
                     44:        if (cli_pending_special_char && 0x5b == ch) {
                     45:                cli_pending_special_char = ch;
                     46:                return ch;
                     47:        }
                     48:        if (0x5b == cli_pending_special_char) {
                     49:                cli_pending_special_char = 0;
                     50:                return ch;
                     51:        }
                     52: 
                     53:        cli_pending_special_char = 0;
                     54:        fputc(ch, rl_outstream);
                     55:        fflush(rl_outstream);
                     56:        return ch;
                     57: }
                     58: 
                     59: #if 0
                     60: static void cli_Line_Handler(char *line)
                     61: {
                     62:        int len;
                     63:        static char cli_Buffer[BUFSIZ];
                     64:        static int cli_BufferLen, cli_BufferPos;
                     65: 
                     66:        if (!line) {    // EOF
                     67:                fwrite("\x4", 1, 1, rl_outstream);      // ctrl+D
                     68:                goto end;
                     69:        } else
                     70:                len = strlen(line);
                     71:        if (BUFSIZ - 2 < len)
                     72:                cli_BufferPos = cli_BufferLen = 0;
                     73:        else {
                     74:                if (BUFSIZ - 2 < len + cli_BufferLen) {
                     75:                        if (BUFSIZ - 2 >= cli_BufferLen - cli_BufferPos + len) {
                     76:                                cli_BufferLen -= cli_BufferPos;
                     77:                                memmove(cli_Buffer, cli_Buffer + cli_BufferPos, cli_BufferLen);
                     78:                        } else
                     79:                                cli_BufferLen = 0;
                     80: 
                     81:                        cli_BufferPos = 0;
                     82:                }
                     83: 
                     84:                memcpy(cli_Buffer + cli_BufferLen, line, len);
                     85:                cli_BufferLen += len;
                     86:                cli_Buffer[cli_BufferLen++] = '\r';
                     87:                cli_Buffer[cli_BufferLen++] = '\n';
                     88:        }
                     89: 
                     90:        fwrite(cli_Buffer, 1, cli_BufferLen, rl_outstream);
                     91:        if (!cli_pending_special_char) {
                     92:                fwrite("\r", 1, 1, rl_outstream);
                     93:                if (*line)
                     94:                        add_history(line);
                     95:        }
                     96: 
                     97:        free(line);
                     98: end:
                     99:        rl_callback_handler_remove();
                    100:        if (cli_pending_special_char) {
                    101:                fwrite(&cli_pending_special_char, 1, 1, rl_outstream);
                    102:                cli_pending_special_char = 0;
                    103:        }
                    104:        fflush(rl_outstream);
                    105: }
1.1.1.1.2.11  misho     106: #endif
                    107: 
1.1.1.1.2.4  misho     108: 
1.1       misho     109: // cli_GetErrno() Get error code of last operation
                    110: inline int cli_GetErrno()
                    111: {
                    112:        return cli_Errno;
                    113: }
                    114: 
                    115: // io_GetError() Get error text of last operation
                    116: inline const char *cli_GetError()
                    117: {
                    118:        return cli_Error;
                    119: }
                    120: 
                    121: // cli_SetErr() Set error to variables for internal use!!!
                    122: inline void cli_SetErr(int eno, char *estr, ...)
                    123: {
                    124:        va_list lst;
                    125: 
                    126:        cli_Errno = eno;
                    127:        memset(cli_Error, 0, STRSIZ);
                    128:        va_start(lst, estr);
                    129:        vsnprintf(cli_Error, STRSIZ, estr, lst);
                    130:        va_end(lst);
                    131: }
                    132: 
                    133: // ------------------------------------------------------------
                    134: 
                    135: /*
                    136:  * cli_Printf() Printf CLI features
                    137:  * @out = Output stream
                    138:  * @csFormat = Printf format string
1.1.1.1.2.4  misho     139:  * return: -1 error, != -1 printed chars
1.1       misho     140: */
                    141: inline int cli_Printf(FILE *out, const char *csFormat, ...)
                    142: {
                    143:        va_list lst;
                    144:        int ret;
                    145: 
                    146:        va_start(lst, csFormat);
                    147: 
                    148:        ret = vfprintf(out, csFormat, lst);
                    149:        if (-1 == ret)
                    150:                LOGERR;
                    151: 
                    152:        va_end(lst);
                    153:        return ret;
                    154: }
                    155: 
                    156: 
                    157: /*
                    158:  * cliComp() Initialize completion CLI features
                    159:  * @cmdComplete = Completion function
                    160:  * @cmdEntry = Compentry function
                    161:  * return: none
                    162: */
                    163: inline void cliComp(cli_Completion_t *cmdComplete, cli_CompEntry_t *cmdEntry)
                    164: {
                    165:        // command completon
                    166:        rl_attempted_completion_function = cmdComplete;
                    167:        rl_completion_entry_function = cmdEntry;
                    168: }
                    169: 
                    170: /*
1.1.1.1.2.1  misho     171:  * cliTTY() Initialize I/O TTY CLI features
1.1.1.1.2.3  misho     172:  * @term = terminal name
1.1.1.1.2.1  misho     173:  * @inp = input handle
                    174:  * @out = output handle
1.1.1.1.2.4  misho     175:  * @win = window size
                    176:  * return: -1 error, != -1 ok
                    177: */
                    178: inline int cliTTY(const char *term, FILE *inp, FILE *out, struct winsize *win)
                    179: {
                    180:        if (term)
                    181:                rl_terminal_name = term;
                    182: 
                    183:        if (inp)
                    184:                rl_instream = inp;
                    185:        if (out)
                    186:                rl_outstream = out;
                    187: 
                    188:        if (win)
                    189:               if (ioctl(!rl_outstream ? STDOUT_FILENO : fileno(rl_outstream), TIOCSWINSZ, win) == -1) {
                    190:                       LOGERR;
                    191:                       return -1;
                    192:               }
                    193: 
                    194:        return 0;
                    195: }
                    196: 
                    197: /*
1.1.1.1.2.5  misho     198:  * cli_ReadHistory() Read CLI History from file
                    199:  * @csFile = history file name, if NULL default history name is ".aitcli.history"
                    200:  * return: -1 error; != -1 readed ok
                    201: */
                    202: inline int cli_ReadHistory(const char *csFile)
                    203: {
                    204:        return read_history(!csFile ? ".aitcli.history" : csFile);
                    205: }
                    206: 
                    207: /*
                    208:  * cli_WriteHistory() Write CLI History to file
                    209:  * @csFile = history file name, if NULL default history name is ".aitcli.history"
1.1.1.1.2.6  misho     210:  * @lineNum = save number of history entry lines, if -1 all lines saved without limit
1.1.1.1.2.5  misho     211:  * return: -1 error; != -1 readed ok
                    212: */
1.1.1.1.2.6  misho     213: inline int cli_WriteHistory(const char *csFile, int lineNum)
1.1.1.1.2.5  misho     214: {
1.1.1.1.2.6  misho     215:        int ret;
                    216:        const char *psFile = !csFile ? ".aitcli.history" : csFile;
                    217: 
                    218:        ret = write_history(psFile);
                    219:        if (-1 != ret && -1 != lineNum)
                    220:                history_truncate_file(psFile, lineNum);
                    221: 
                    222:        return ret;
1.1.1.1.2.5  misho     223: }
                    224: 
                    225: /*
1.1.1.1.2.4  misho     226:  * cliNetInit() Initialize Readline if CLI bind to socket
                    227:  * @csProg = program name
                    228:  * @pty = Master pty
                    229:  * @term = stdin termios
                    230:  * return: none
                    231: */
                    232: void cliNetInit(const char *csProg, int pty, struct termios *term)
                    233: {
                    234:        struct termios t;
1.1.1.1.2.10  misho     235:        int on = 1;
1.1.1.1.2.4  misho     236: 
1.1.1.1.2.10  misho     237:        memset(&t, 0, sizeof t);
                    238:        if (term)
1.1.1.1.2.4  misho     239:                t = *term;
1.1.1.1.2.10  misho     240:        else {
                    241:                t.c_lflag = TTYDEF_LFLAG;
                    242:                t.c_iflag = TTYDEF_IFLAG;
                    243:                t.c_oflag = TTYDEF_OFLAG;
1.1.1.1.2.15  misho     244:                t.c_cflag = TTYDEF_CFLAG;
1.1.1.1.2.10  misho     245:                cfsetspeed(&t, B9600);
1.1.1.1.2.4  misho     246:        }
                    247: 
1.1.1.1.2.10  misho     248:        t.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO | ECHOCTL | ECHOE | ECHOK | ECHOKE | ECHONL | ECHOPRT);
1.1.1.1.2.15  misho     249: //     t.c_iflag &= ~(ICRNL | BRKINT | INPCK | ISTRIP | IXON);
                    250:        t.c_iflag &= ~ICRNL;
                    251:        t.c_iflag |= IGNBRK;
1.1.1.1.2.10  misho     252:        t.c_cc[VMIN] = 1;
                    253:        t.c_cc[VTIME] = 0;
                    254:        tcsetattr(pty, TCSANOW, &t);
1.1.1.1.2.4  misho     255: 
1.1.1.1.2.10  misho     256:        ioctl(pty, TIOCPKT, &on);
1.1.1.1.2.4  misho     257: 
1.1.1.1.2.11  misho     258:        rl_readline_name = csProg;
                    259:        rl_variable_bind("editing-mode", "emacs");
                    260: 
1.1.1.1.2.10  misho     261:        rl_instream = fdopen(pty, "r");
1.1.1.1.2.15  misho     262:        rl_outstream = NULL;
1.1.1.1.2.1  misho     263: }
                    264: 
                    265: /*
1.1.1.1.2.7  misho     266:  * cliNetExec() Execute net CLI main loop
                    267:  * @cmdList = Commands list
                    268:  * @csPrompt = Prompt text
                    269:  * @sock = client socket
                    270:  * @term = stdin termios
                    271:  * @win = window size of tty
                    272:  * return: -1 error, 0 = exit w/^+D, 1 done.
                    273: */
                    274: int cliNetExec(cliCommands_t *cmdList, const char *csPrompt, int sock, struct termios *term, struct winsize *win)
                    275: {
1.1.1.1.2.16  misho     276:        int pty, ret = 0, alen, attrlen, flg;
1.1.1.1.2.7  misho     277:        fd_set fds;
                    278:        struct timeval tv = { DEFAULT_SOCK_TIMEOUT, 0 };
                    279:        u_char buf[BUFSIZ];
1.1.1.1.2.12  misho     280:        struct telnetAttrs *a, Attr[10];
1.1.1.1.2.7  misho     281: 
                    282:        switch (forkpty(&pty, NULL, term, win)) {
                    283:                case -1:
                    284:                        LOGERR;
                    285:                        return -1;
                    286:                case 0:
                    287:                        close(sock);
                    288: 
1.1.1.1.2.16  misho     289:                        rl_prep_term_function = cli_Null_Prep_Term;
1.1.1.1.2.15  misho     290:                        rl_getc_function = cli_Net_rl_GetCh;
1.1.1.1.2.10  misho     291: 
                    292:                        cliNetInit(getprogname(), STDIN_FILENO, term);
1.1.1.1.2.15  misho     293:                        ret = cliExec(cmdList, csPrompt) < 0 ? 1 : 0;
                    294:                        /* spawn Shell mode */
                    295:                        /*
1.1.1.1.2.10  misho     296:                        execl("/bin/tcsh", "tcsh", NULL);
1.1.1.1.2.15  misho     297:                        */
1.1.1.1.2.7  misho     298:                        _exit(ret);
                    299:                default:
1.1.1.1.2.15  misho     300:                        cliNetInit(getprogname(), pty, term);
                    301: 
                    302:                        /* spawn Shell mode */
                    303:                        /*
                    304:                        telnet_SetCmd(Attr + 0, DO, TELOPT_TTYPE);
                    305:                        telnet_SetCmd(Attr + 1, WILL, TELOPT_ECHO);
                    306:                        telnet_Set_SubOpt(Attr + 2, TELOPT_LFLOW, LFLOW_OFF, NULL, 0);
                    307:                        telnet_Set_SubOpt(Attr + 3, TELOPT_LFLOW, LFLOW_RESTART_XON, NULL, 0);
                    308:                        telnet_SetCmd(Attr + 4, DO, TELOPT_LINEMODE);
                    309:                        */
1.1.1.1.2.13  misho     310:                        telnet_SetCmd(Attr + 0, DO, TELOPT_TTYPE);
                    311:                        telnet_SetCmd(Attr + 1, WILL, TELOPT_ECHO);
1.1.1.1.2.14  misho     312:                        telnet_Set_SubOpt(Attr + 2, TELOPT_LFLOW, LFLOW_OFF, NULL, 0);
                    313:                        telnet_Set_SubOpt(Attr + 3, TELOPT_LFLOW, LFLOW_RESTART_XON, NULL, 0);
                    314:                        telnet_SetCmd(Attr + 4, DO, TELOPT_LINEMODE);
1.1.1.1.2.13  misho     315:                        if ((ret = telnetSend(sock, Attr, 5, NULL, 0, 0)) == -1) {
1.1.1.1.2.7  misho     316:                                cli_Errno = telnet_GetErrno();
                    317:                                strlcpy(cli_Error, telnet_GetError(), STRSIZ);
                    318:                                return -1;
1.1.1.1.2.13  misho     319:                        } else
                    320:                                flg = 0;
1.1.1.1.2.7  misho     321: 
                    322:                        while (42) {
                    323:                                FD_ZERO(&fds);
                    324:                                FD_SET(sock, &fds);
                    325:                                FD_SET(pty, &fds);
1.1.1.1.2.10  misho     326:                                if ((ret = select(FD_SETSIZE, &fds, NULL, NULL, &tv)) < 1) {
                    327:                                        if (!ret)
                    328:                                                cli_SetErr(ETIMEDOUT, "Client session timeout ...");
                    329: 
1.1.1.1.2.7  misho     330:                                        break;
1.1.1.1.2.10  misho     331:                                }
1.1.1.1.2.7  misho     332: 
1.1.1.1.2.16  misho     333:                                if (FD_ISSET(sock, &fds)) {
                    334:                                        memset(buf, 0, BUFSIZ);
                    335:                                        if ((ret = telnetRecv(sock, &a, &alen, buf, BUFSIZ)) < 0) {
                    336:                                                if (a)
                    337:                                                        free(a);
                    338: 
                    339:                                                if (-2 == ret)
                    340:                                                        continue;
                    341:                                                // EOF
                    342:                                                if (-3 == ret)
                    343:                                                        shutdown(sock, SHUT_RD);
                    344:                                                else {
                    345:                                                        cli_Errno = telnet_GetErrno();
                    346:                                                        strlcpy(cli_Error, telnet_GetError(), STRSIZ);
                    347:                                                }
                    348:                                                break;
                    349:                                        }
1.1.1.1.2.10  misho     350:                                        if (a)
                    351:                                                free(a);
1.1.1.1.2.16  misho     352:                                        if (alen) {
                    353:                                                attrlen = 0;
                    354:                                                if (1 == flg && alen) {
                    355:                                                        telnet_SetCmd(&Attr[attrlen++], DONT, TELOPT_SGA);
                    356:                                                        telnet_SetCmd(&Attr[attrlen++], DO, TELOPT_ECHO);
                    357:                                                }
                    358:                                                if (2 == flg && alen) {
                    359:                                                        telnet_SetCmd(&Attr[attrlen++], WILL, TELOPT_ECHO);
                    360:                                                        telnet_Set_SubOpt(&Attr[attrlen++], TELOPT_LFLOW, 
                    361:                                                                        LFLOW_OFF, NULL, 0);
                    362:                                                        telnet_Set_SubOpt(&Attr[attrlen++], TELOPT_LFLOW, 
                    363:                                                                        LFLOW_RESTART_XON, NULL, 0);
                    364:                                                        telnet_SetCmd(&Attr[attrlen++], DONT, TELOPT_LINEMODE);
                    365:                                                }
                    366:                                                if ((ret = telnetSend(sock, Attr, attrlen, buf, ret, 0)) == -1) {
                    367:                                                        cli_Errno = telnet_GetErrno();
                    368:                                                        strlcpy(cli_Error, telnet_GetError(), STRSIZ);
                    369:                                                        break;
                    370:                                                }
                    371:                                        }
                    372: 
                    373:                                        if ((ret = write(pty, buf, ret)) == -1) {
                    374:                                                LOGERR;
                    375:                                                break;
                    376:                                        }
                    377:                                }
1.1.1.1.2.10  misho     378: 
1.1.1.1.2.16  misho     379:                                if (FD_ISSET(pty, &fds)) {
                    380:                                        memset(buf, 0, BUFSIZ);
                    381:                                        if ((ret = read(pty, buf, BUFSIZ)) < 1) {
                    382:                                                if (!ret)
                    383:                                                        shutdown(sock, SHUT_WR);
                    384:                                                else
                    385:                                                        LOGERR;
                    386:                                                break;
                    387:                                        }
                    388:                                        attrlen = 0;
                    389:                                        if (1 == flg && alen) {
                    390:                                                telnet_SetCmd(&Attr[attrlen++], DONT, TELOPT_SGA);
                    391:                                                telnet_SetCmd(&Attr[attrlen++], DO, TELOPT_ECHO);
                    392:                                        }
                    393:                                        if (2 == flg && alen) {
                    394:                                                telnet_SetCmd(&Attr[attrlen++], WILL, TELOPT_ECHO);
                    395:                                                telnet_Set_SubOpt(&Attr[attrlen++], TELOPT_LFLOW, 
                    396:                                                                LFLOW_OFF, NULL, 0);
                    397:                                                telnet_Set_SubOpt(&Attr[attrlen++], TELOPT_LFLOW, 
                    398:                                                                LFLOW_RESTART_XON, NULL, 0);
                    399:                                                telnet_SetCmd(&Attr[attrlen++], DONT, TELOPT_LINEMODE);
                    400:                                        }
                    401:                                        if ((ret = telnetSend(sock, Attr, attrlen, buf, ret, 0)) == -1) {
1.1.1.1.2.7  misho     402:                                                cli_Errno = telnet_GetErrno();
                    403:                                                strlcpy(cli_Error, telnet_GetError(), STRSIZ);
1.1.1.1.2.16  misho     404:                                                break;
1.1.1.1.2.7  misho     405:                                        }
                    406:                                }
                    407:                        }
                    408: 
                    409:                        close(pty);
                    410:        }
                    411: 
                    412:        return ret;
                    413: }
                    414: 
                    415: /*
1.1       misho     416:  * cliExec() Execute CLI main loop
                    417:  * @cmdList = Commands list
                    418:  * @csPrompt = Prompt text
                    419:  * return: -1 error, 0 = exit w/^+D, 1 done.
                    420: */
1.1.1.1.2.2  misho     421: int cliExec(cliCommands_t *cmdList, const char *csPrompt)
1.1       misho     422: {
                    423:        char *line, *s, *t, **app, *items[MAX_PROMPT_ITEMS];
                    424:        int ret = 0;
                    425:        register int i;
                    426:        cliCommands_t *cmd = NULL;
1.1.1.1.2.2  misho     427:        FILE *out;
1.1       misho     428: 
                    429:        inline int inline_help()
                    430:        {
                    431:                cli_Cmd_Help(cmdList ? cmdList : cli_stdCmds, -1, out, NULL);
                    432:                rl_on_new_line();
                    433:                return 0;
                    434:        }
                    435: 
                    436:        char **cli_stdCompletion(const char *text, int start, int end)
                    437:        {
                    438:                register int i;
                    439:                char **matches = NULL;
                    440: 
                    441:                char *cmdCompGet(const char *text, int state)
                    442:                {
                    443:                        int len = strlen(text);
                    444: 
                    445:                        for (i = state; cmdList[i].cmd_name; i++) {
                    446:                                if (strncmp(cmdList[i].cmd_name, "---", 3) && 
                    447:                                                !strncmp(cmdList[i].cmd_name, text, len))
                    448:                                        return strdup(cmdList[i].cmd_name);
                    449:                        }
                    450: 
                    451:                        return NULL;
                    452:                }
                    453: 
                    454:                if (!start)
                    455:                        matches = rl_completion_matches(text, cmdCompGet);
                    456:                else
                    457:                        for (i = 0; cmdList[i].cmd_name; i++) {
                    458:                                if (!cmdList[i].cmd_comp)
                    459:                                        continue;
                    460:                                if (!strncmp(rl_line_buffer, cmdList[i].cmd_name, strlen(cmdList[i].cmd_name)))
                    461:                                        matches = rl_completion_matches(text, cmdList[i].cmd_comp);
                    462:                        }
                    463: 
                    464:                return matches;
                    465:        }
                    466:        char *cli_stdCompEntry(const char *ignore, int invoking_key)
                    467:        {
                    468:                return NULL;
                    469:        }
                    470: 
                    471:        /* --- main body of CLI --- */
                    472: 
1.1.1.1.2.2  misho     473:        out = rl_outstream;
                    474:        if (!out)
                    475:                out = stdout;
                    476: 
1.1       misho     477:        rl_bind_key('?', inline_help);
                    478:        if (!rl_attempted_completion_function) 
                    479:                cliComp(cli_stdCompletion, cli_stdCompEntry);
                    480: 
                    481:        do {
                    482:                line = readline(csPrompt);
                    483:                if (!line) {    // ^+d
                    484:                        cli_Printf(out, "\n");
                    485:                        break;
                    486:                }
                    487:                // clear whitespaces
                    488:                for (s = line; isspace(*s); s++);
                    489:                if (*s) {
                    490:                        for (t = s + strlen(s) - 1; t > s && isspace(*t); t--);
                    491:                        *++t = 0;
                    492:                }
                    493: 
                    494:                if (*s) {
                    495:                        add_history(s);
                    496: 
                    497:                        memset(items, 0, sizeof(char*) * MAX_PROMPT_ITEMS);
                    498:                        for (app = items; app < items + MAX_PROMPT_ITEMS - 1 && (*app = strsep(&s, " \t")); 
                    499:                                        *app ? app++ : app);
                    500: 
                    501:                        /*
                    502:                        for (i = 0; i < MAX_PROMPT_ITEMS; i++)
                    503:                                cli_Printf(out, "i=%d %s\n", i, items[i]);
                    504:                                */
                    505: 
                    506:                        // exec_cmd ...
                    507:                        for (cmd = NULL, i = 0; cmdList[i].cmd_name; i++)
                    508:                                if (*items[0] && !strncmp(cmdList[i].cmd_name, items[0], strlen(items[0]))) {
                    509:                                        cmd = &cmdList[i];
                    510:                                        break;
                    511:                                }
                    512:                        if (!cmd) {
                    513:                                cli_Printf(out, "Command '%s' not found!\n", items[0]);
                    514:                                ret = -1;
                    515:                        } else
                    516:                                ret = cmd->cmd_func(cmdList, i, out, items);
                    517:                }
                    518: 
                    519:                free(line);
                    520:        } while (ret < 1);
                    521: 
                    522:        return ret;
                    523: }

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