精华内容
下载资源
问答
  • 开发cli 命令行工具,命令行开发框架func基于node
    2021-08-04 11:19:03

    node 开发命令行工具的最佳框架:func

    优点

    • 使用node 开发(无2次学习成本)
    • 体积非常小 ≈ 7kb
    • 优雅的语法
    • 极少的依赖,最大优化运行速度和npm下载时间
    • 模板支持,立即获得最佳实践

    缺点:文档不好不好找 百度都查不到。

    快速开始

    超轻量级就3步:

    1. 创建项目:npm init func
    2. 安装依赖:npm i
    3. 开始开发:npm start

    附上相关地址
    4. githud 项目地址:https://github.com/unix/func
    5. 文档地址:https://func.unix.bio/(英文有点恶心可以使用谷歌浏览器的网页翻译,翻译看)

    更多相关内容
  • 本门课程,大喵将会打着大家从零打造一款属于大家自己的 CLI命令行脚手架工具,本课程主要面向新手同学,对命令行工具开发,前端工具开发感兴趣的同学,可以通过本门课程学习到如何使用Node.JS开发一款适配自身项目...
  • 主要介绍了手把手教你如何使用nodejs编写cli命令行,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • thinkphp3.1.2 需要使用cli方法运行脚本 折腾了一天才搞定 3.1.2的版本真的很古老 解决 增加cli.php入口文件 define ('APP_NAME','App'); define ('APP_PATH','./App/'); define('APP_DEBUG', true); define('MODE_...
  • node cli 命令行工具实现,开发自己的个性化命令行工具,解放不必要的重复劳动,提升工作效率,开开心心的敲代码,哦耶

    node cli 命令行工具

    初始化项目

    mkdir tl && cd tl
    npm init
    

    修改package.json文件,添加bin字段,表明tl 是可执行文件,执行的文件是index.js

    {
      "name": "y",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "bin": {
        "tl": "./index.js"
      },
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "license": "ISC",
    }
    

    创建index.js

    #!/usr/bin/env node
    
    console.log("你好")
    
    

    安装

    node i -g
    

    此时就可以在命令行中执行tl命令了

    交互

    安装commander、inquirer、chalk(可选)

    commander: 是完整的 node.js 命令行解决方案,能够帮助我们快速的实现cli命令;
    inquirer: 是交互式命令行用户接口的集合,能够帮助我们快速的实现类似vue creat项目时的各种选择功能;
    chalk: 是命令行美化工具;5.x版本只支持ES Module;

    因为使用了chalk并且它只支持ES Module,所以需要修改package.json文件,添加type字段;也可以安装chalk4.x版本;

    {
      ...
      "type": "module",
      ...
    }
    
    

    创建命令

    首行的#!/usr/bin/env node是不可省略的,它表明了这是个node的可执行文件

    #!/usr/bin/env node
    
    import { Command } from 'commander'
    import { readFile } from 'fs/promises'
    import fs from 'fs'
    import path from 'path'
    
    const program = new Command()
    
    const packageJson = JSON.parse(
      await readFile(new URL('./package.json', import.meta.url))
    )
    
    program.version(packageJson.version, '-v, --version', 'cli的最新版本')
    
    program
      .command('gitreset')
        .description('执行回退到上一个版本')
        .action(async () => {
          try {
            await exec('git add .') // 解决未add的情况下不能reset
            handleExeRes(await exec('git reset --hard HEAD'))
          } catch (err) {
            console.log(chalk.red(`err: ${err}`))
          }
        })
    
    program.parse(process.argv)
    

    此时执行

    tl -h
    

    就可以看到已经有git reset命令了

    创建交互式命令

    使用inquirer完成用户交互式命令,最后拿到用户选择的结果做对应的操作;
    如提前创建各种项目模板,如果用户选择的是vue3+ts,那么git clone vue3+ts的项目

    import inquirer from 'inquirer'
    
    const prompList = [
      ...
    ]
    export default function (program) {
      program
        .command('create [projectName]')
        .description('创建新项目')
        .action((projectName) => {
          if (!projectName) {
            prompList.unshift({
              type: 'input',
              message: '项目名称:',
              name: 'projectName',
              default: 'newProject',
            })
          }
    
          inquirer.prompt(prompList).then((answers) => {
            console.log({ projectName, ...answers }) // 返回的结果,做处理
          })
        })
    }
    
    

    优化

    当command多了后全写在index.js中就很麻烦,于是将gitreset命令放到command目录中

    如图:

    import chalk from 'chalk'
    import child_process from 'child_process'
    import util from 'util'
    import { handleExeRes } from '../utils.js'
    
    const exec = util.promisify(child_process.exec)
    
    export default function (program) {
      program
        .command('gitreset')
        .description('执行回退到上一个版本')
        .action(async () => {
          try {
            await exec('git add .') // 解决未add的情况下不能reset
            handleExeRes(await exec('git reset --hard HEAD'))
          } catch (err) {
            console.log(chalk.red(`err: ${err}`))
          }
        })
    }
    
    

    这样index.js中就很清爽了,只需要import,然后执行就好了;

    但是每添加一个命令都要引入一次有点麻烦,再优化一下,让它能够自动的引入commond下的命令

    #!/usr/bin/env node
    
    import { Command } from 'commander'
    import { readFile } from 'fs/promises'
    import fs from 'fs'
    import { dirname, resolve } from 'path'
    import { fileURLToPath } from 'url'
    
    const program = new Command()
    const __dirname = dirname(fileURLToPath(import.meta.url))
    
    const packageJson = JSON.parse(
      await readFile(new URL('./package.json', import.meta.url))
    )
    
    program.version(packageJson.version, '-v, --version', 'cli的最新版本')
    
    try {
      // 自动导入添加command
      const commandFiles = await fs.promises.readdir(resolve(__dirname, 'command'))
      for (let i = 0; i < commandFiles.length; i++) {
        const fileName = commandFiles[i]
        let model = await import(`./command/${fileName}`)
        if (typeof model?.default === 'function') {
          model.default(program)
        }
      }
    
      // 自动导入添加options
      const optionFiles = await fs.promises.readdir(resolve(__dirname, 'options'))
      for (let i = 0; i < optionFiles.length; i++) {
        const fileName = optionFiles[i]
        let model = await import(`./options/${fileName}`)
        model.default(program)
      }
    } catch (err) {
      console.log('import err: ?>>>> ', err)
    }
    program.parse(process.argv)
    
    
    

    完整项目地址: https://github.com/bfclouds/tl

    展开全文
  • and-cli命令行工具,用于管理软件应用程序的开发。 入门 从克隆的或分支的存储库的根目录中,运行以下命令: npm install && npm run build && ./dist/and-cli.js install 这是做什么的: 安装依赖项并运行项目的...
  • CLI 命令行实用程序开发基础 一、简介 命令行界面(英语:command-line interface,缩写:CLI)是在图形用户界面得到普及之前使用最为广泛的用户界面,它通常不支持鼠标,用户通过键盘输入指令,计算机接收到指令后...

    CLI 命令行实用程序开发基础

    一、简介

    命令行界面(英语:command-line interface,缩写:CLI)是在图形用户界面得到普及之前使用最为广泛的用户界面,它通常不支持鼠标,用户通过键盘输入指令,计算机接收到指令后,予以执行。也有人称之为字符用户界面(CUI)。
    通常认为,命令行界面(CLI)没有图形用户界面(GUI)那么方便用户操作。因为,命令行界面的软件通常需要用户记忆操作的命令,但是,由于其本身的特点,命令行界面要较图形用户界面节约计算机系统的资源。在熟记命令的前提下,使用命令行界面往往要较使用图形用户界面的操作速度要快。所以,图形用户界面的操作系统中,都保留着可选的命令行界面。
    ——百度百科

    本篇博客的内容为使用 golang 开发 开发 Linux 命令行实用程序 中的 selpg,要求能够用使用 selpg这一章节的要求测试程序。

    :务必先看一下以上链接中的内容,这是我们接下来要实现的东西。

    二、开发过程

    首先要明确需求。这个任务是将一个已有的c语言程序改写为go语言程序,使得原来程序的输入输出关系不变,所以任务的难点主要在于找到go语言和c语言相对应的表达。

    1. selpg_args结构

    结构变量与c语言版本大致相同,不过page_type的类型由整型变为布尔值,这样是为了设计的方便,后面会讲到。

    type selpg_args struct {
    	start_page int
    	end_page int
    	page_len int
    	page_type bool
    	in_filename string
    	print_dest string
    }
    
    2. main函数

    注:因为程序中包含main函数,所以包必须是main

    package main
    
    var progname string
    func main() {
        progname = os.Args[0]
        var sa selpg_args
        flag.IntVarP(&sa.start_page, "start_page", "s", -1, "start page")
        flag.IntVarP(&sa.end_page, "end_page", "e", -1, "end page")
        flag.IntVarP(&sa.page_len, "page_len", "l", 15, "page len")
        flag.BoolVarP(&sa.page_type, "page_type", "f", false, "page_type")
        flag.StringVarP(&sa.print_dest, "print_dest", "d", "", "print destination")
    
        flag.Parse()
        if flag.NArg() > 0 {
            sa.in_filename = flag.Arg(0)
        }
        check_args(sa)
        process_input(sa)
    
    }
    

    main函数主要做了以下几个工作:

    • 获取参数
    • 检查参数
    • 执行命令

    获取参数时用到了两个库,一个是os,另一个是pflag。

    os.Args是一个参数的数组,它相当于c语言中的args。

    flag介绍
    flag库简而言之是一个自动获取参数的库,它比os.Args聪明一点,不只是简单的将选项和参数存入一个字符串数组并等待进一步处理,而是能识别跟在选项后面的参数(包括类型和值)并保存在变量中,可以被直接使用。
    举个简单的例子,输入$ selpg -s1 -e2时(selpg是我们要实现的命令行程序,如果不知道这是什么,请先点击简介中的链接查看文档),os.Args会简单地存储‘selpg’、‘-s1’、‘-e2’这三个字符串,但并不明白这三个参数的含义,而flag可以将跟在选项-s和-e后面的参数识别出来并存在变量start_page和end_page中,程序可以在接下来使用它们。

    flag的简单使用如下:

     var port int
     flag.IntVar(&port, "port",  8000, "specify port to use.  defaults to 8000.")
     flag.Parse()
    
     fmt.Printf("port = %d\n", port)
     fmt.Printf("other args: %+v\n", flag.Args())
    

    在这个命令中(不管是什么命令)我们要获取一个选项port后跟着的参数,并将其存在port变量中。
    flag.IntVar的四个参数分别为参数存放的地址、选项名、默认值、参数提示。
    所以在命令行输入$ command -port 80 时flag会自动将80这个值赋给port。

    flag.Parse运用当前flag已经定义的规则,遍历整条命令,找到某些选项对应的参数并给对应的变量赋值,如果一些已定义的选项没有出现在命令中,则对应的变量赋默认值。

    顺带一提,flag.Args()是一个参数数组,但只存储了那些没有跟在选项后的参数,比如命令$command -s 1 -e 2 input_file,flag.Args()中只有input_file。

    但是在我们的程序中用的不是flag库,而是和它有点区别的pflag库,后者的一个优点是跟在选项后的参数可以与选项有空格隔开,也可以不用隔开。
    $command -s 1 -e 2 和
    $command -s1 -e2是等价的,这为我们提供了很大的方便,因为c语言版本实现中默认输入是第二种。

    安装和使用pflag
    pflag并不是go的默认库,所以需要手动安装
    go get github.com/spf13/pflag
    安装后可以在程序中这样引用

    import  (
    	flag "github.com/spf13/pflag"
    )
    

    flag是程序中使用的库名。(如果同时引用了原flag库最好将这个名字改为pflag以区分)。

    3. 检查参数

    好了,现在已经获取了参数,下一步需要检查参数是否合法,主要做了以下几个检查:

    • 参数是否大于等于三个(至少包含程序名、起始页、结束页)
    • 起始页和结束页是否是一个合法的整数(大于0小于INT_MAX)
    • 起始页是否不大于结束页
    • 如果有输入文件,该输入文件是否存在

    在不满足以上任一条件时,程序会打印错误信息,并退出。

    func check_args(sa selpg_args) {
        if len(os.Args) < 3 {
            fmt.Fprintf(os.Stderr, "%s: not enough arguments\n", progname)
            usage()
            os.Exit(1)
        }
        if sa.start_page < 1 || sa.start_page > INT_MAX - 1 {
            fmt.Fprintf(os.Stderr, "%s: invalid start page %d\n", progname, sa.start_page)
            usage()
            os.Exit(2)
        }
        if sa.end_page < 1 || sa.end_page > INT_MAX - 1 {
            fmt.Fprintf(os.Stderr, "%s: invalid end page %d\n", progname, sa.end_page)
            usage()
            os.Exit(3)
        }
        if sa.end_page < sa.start_page {
            fmt.Fprintf(os.Stderr, "%s: end page should not be less than start pag\n", progname)
            usage()
            os.Exit(3)
        }
        if sa.page_len < 1 || sa.page_len > INT_MAX - 1 {
            fmt.Fprintf(os.Stderr, "%s: invalid page length %d\n", progname, sa.page_len)
            usage()
            os.Exit(4)
        }
        if sa.in_filename != "" {
            if _, err := os.Stat(sa.in_filename); os.IsNotExist(err) {
                fmt.Fprintf(os.Stderr, "%s: input file \"%s\" does not exist\n", progname, sa.in_filename)
    			os.Exit(5);
            }
        }
    
    }
    
    4. 执行命令

    首先需要解决的问题是输入流与输出流。
    在c语言版本中输入是带缓冲的,输出是通过popen创建一个子进程并获得一条到达它的管道,使得程序的输出作为该子进程的输入。

    在我们的go语言版本中处理是类似的(因为恰好有相对应的机制)。

    输入流用bufio.Reader,如果输入命令中不包含输入文件,则输入默认来自标准输入,否则尝试打开文件,将文件流作为输入。

    var reader *bufio.Reader
    if sa.in_filename == "" {
        reader = bufio.NewReader(os.Stdin)
    } else {
        fin, err := os.Open(sa.in_filename)
        if err != nil {
            fmt.Fprintf(os.Stderr, "%s: could not open input file \"%s\"\n", progname, sa.in_filename)
            os.Exit(6)
        }
        reader = bufio.NewReader(fin)
        defer fin.Close()
    }
    

    输出稍微复杂一点,我们先看一个通过管道连接两个命令行进程的方法。
    在这个例子中首先分别创建了生产者子进程和消费者子进程,然后将生产者的输出流设为消费者的输入管道,这样就实现了生产者产生的输出通过管道作为消费者的输入。

    //通过管道连接两个命令行进程的方法
    func main() {
        generator := exec.Command("cmd1")
        consumer := exec.Command("cmd2")
        pipe, err := consumer.StdinPipe()
        generator.Stdout = pipe
    }
    

    我们的程序也应用了这种方法,(如果输入命令中没有目的地选项,则输出默认为标准输出;)首先通过exec.Command创建了一个子进程,执行打印,打印的目的地为输入命令中的目的地,然后我们将程序的输出流writer设为打印进程的输入管道,这样就实现了将读取的内容通过管道写到目的文件中。

    var writer io.WriteCloser 
    if sa.print_dest == "" {
        writer = os.Stdout
    } else {
        cmd := exec.Command("lp","-d"+ sa.print_dest)
        var err error
        if writer, err = cmd.StdinPipe(); err != nil {
            fmt.Fprintf(os.Stderr, "%s: could not open pipe to \"%s\"\n", progname, sa.print_dest)
            fmt.Println(err)
            os.Exit(7)
        }
        if err = cmd.Start(); err != nil {
            fmt.Fprintf(os.Stderr, "%s: cmd start error\n", progname)
            fmt.Println(err)
            os.Exit(8)
        }
    }
    

    输入和输出流都搞定,开始读和写
    这里基本套了c语言版本的思路,不过将两种不同页类型(定长和不定长)的读取统一在一个循环内。因为不定长页是以‘\f’为结束符的,所以可以看成一个页只有一行,所以用reader.ReadString每读“一行”,不定长页就增加了一页。

    这里可以解释一下为什么page_type要用布尔类型了。因为pflag是要识别选项后面跟着的参数的,但-f后面不跟参数,如果用整数类型会报错,而布尔类型的一个特点是不能直接在选项后面跟参数,如果要跟参数,必须用一个等号连接,比如
    selpg -s1 -e2 -f=true,如果不跟参数,单独只出现-f也相当于-f=true,所以我们恰好利用这个特点,如果没有出现-f选项,则page_type为默认值false(对应定长页),如果出现-f选项,则page_type设为true(对应不定长页)。

    line_ctr, page_ctr, page_len := 1, 1, sa.page_len
    ptFlag := '\n'
    if sa.page_type {
        ptFlag = '\f'
        page_len = 1
    }
    
    //使用reader读取所有页的数据,并将要求范围内的页写入writer
    for {
        line, crc := reader.ReadString(byte(ptFlag));
        if crc != nil && len(line) == 0 {
            break
        }
        if line_ctr > page_len {
            page_ctr++
            line_ctr = 1
        }
        if page_ctr >= sa.start_page && page_ctr <= sa.end_page {
            _, err := writer.Write([]byte(line))
            if err != nil {
                fmt.Println(err)
                os.Exit(9)
            }
        }
        line_ctr++
    }
    

    最后检查一下读取是否成功以及是否完成

    if page_ctr < sa.start_page {
        fmt.Fprintf(os.Stderr,
            "\n%s: start_page (%d) greater than total pages (%d),"+
                " no output written\n", progname, sa.start_page, page_ctr)
    } else if page_ctr < sa.end_page {
        fmt.Fprintf(os.Stderr, "\n%s: end_page (%d) greater than total pages (%d),"+
            " less output than expected\n", progname, sa.end_page, page_ctr)
    }
    

    三、程序测试

    首先编译和安装selpg。
    go install practice/CLI/selpg

    根据要求,用c语言版本文档中的使用selpg章节进行测试。

    1.selpg -s1 -e1 selpg.go

    在这里插入图片描述
    正好打印了第一页(一页15行)。

    2.selpg -s1 -e1 < input_file 重定向标准输入

    输出与上一条相同。

    3.ls -l | selpg -s1 -e2 将ls -l输出作为selpg的输入
    在这里插入图片描述
    4.selpg -s1 -e20 selpg.go > temp 标准输出重定向到temp
    在这里插入图片描述
    5.selpg -s1 -e3 selpg.go -l5 修改页长度
    在这里插入图片描述
    6. selpg -s10 -e20 -f selpg.go 不定长页
    在这里插入图片描述

    四、项目地址

    gitee地址

    展开全文
  • Cli 命令行

    2021-03-10 16:15:16
    在GUI出现之前,最基础的就是Cli命令行交互工具,Command Line Interface. 在Github上,有关于clilib的文件。https://github.com/dparrish/libcli 可以将上述代码移植到自己的系统中,然后和网络(也许还可以在串口...

    Cli 命令行简介

    在GUI出现之前,最基础的就是Cli命令行交互工具,Command Line Interface.

    在Github上,有关于clilib的文件。https://github.com/dparrish/libcli
    可以将上述代码移植到自己的系统中,然后和网络(也许还可以在串口上)对接,这样就可以使用cli命令行模式。

    Libcli provides a shared C library for including a Cisco-like command-line interface into other software.

    It’s a telnet interface which supports command-line editing, history, authentication and callbacks for a user-definable function tree.

    To compile:

    $ make
    $ make install

    Note - as of version 1.10.5 you have a compile time decision on using select() or poll() in cli_loop(). The default is to use the legacy ‘select()’ call. If built with ‘CFLAGS=-DLIBCLI_USE_POLL make’ then the poll() system call will be used instead. One additional check is being made now in cli_loop() to ensure that the passed file descriptor is in range. If not, an error message will be sent and the cli_loop() will exit in the child process with CLI_ERROR.

    This will install libcli.so into /usr/local/lib. If you want to change the location, edit Makefile.

    There is a test application built called clitest. Run this and telnet to port 8000.

    By default, a single username and password combination is enabled.

    Username: fred
    Password: nerk
    Get help by entering help or hitting ?.

    libcli provides support for using the arrow keys for command-line editing. Up and Down arrows will cycle through the command history, and Left & Right can be used for editing the current command line.

    libcli also works out the shortest way of entering a command, so if you have a command show users | grep foobar defined, you can enter sh us | g foobar if that is the shortest possible way of doing it.

    Enter sh? at the command line to get a list of commands starting with sh

    A few commands are defined in every libcli program:

    help
    quit
    exit
    logout
    history
    Use in your own code:

    First of all, make sure you #include <libcli.h> in your C code, and link with -lcli.

    If you have any trouble with this, have a look at clitest.c for a demonstration.

    Start your program off with a cli_init(). This sets up the internal data structures required.

    When a user connects, they are presented with a greeting if one is set using the cli_set_banner(banner) function.

    By default, the command-line session is not authenticated, which means users will get full access as soon as they connect. As this may not be always the best thing, 2 methods of authentication are available.

    First, you can add username / password combinations with the cli_allow_user(username, password) function. When a user connects, they can connect with any of these username / password combinations.

    Secondly, you can add a callback using the cli_set_auth_callback(callback) function. This function is passed the username and password as char *, and must return CLI_OK if the user is to have access and CLI_ERROR if they are not.

    The library itself will take care of prompting the user for credentials.

    Commands are built using a tree-like structure. You define commands with the cli_register_command(parent, command, callback, privilege, mode, help) function.

    parent is a cli_command * reference to a previously added command. Using a parent you can build up complex commands.

    e.g. to provide commands show users, show sessions and show people, use the following sequence:

    cli_command *c = cli_register_command(NULL, “show”, NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL);
    cli_register_command(c, “sessions”, fn_sessions, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, “Show the sessions connected”);
    cli_register_command(c, “users”, fn_users, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, “Show the users connected”);
    cli_register_command(c, “people”, fn_people, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, “Show a list of the people I like”);
    If callback is NULL, the command can be used as part of a tree, but cannot be individually run.

    If you decide later that you don’t want a command to be run, you can call cli_unregister_command(command). You can use this to build dynamic command trees.

    It is possible to carry along a user-defined context to all command callbacks using cli_set_context(cli, context) and cli_get_context(cli) functions.

    You are responsible for accepting a TCP connection, and for creating a process or thread to run the cli. Once you are ready to process the connection, call cli_loop(cli, sock) to interact with the user on the given socket. Note that as mentioned above, if the select() call is used and sock is out of range (>= FD_SETSIZE) then cli_loop() will display an error in both the parent process and to the remote TCP connection before exiting that routine.

    This function will return when the user exits the cli, either by breaking the connection or entering quit.

    Call cli_done() to free the data structures.

    Code

    libcli.h

    #ifndef __LIBCLI_H__
    #define __LIBCLI_H__
    
    // vim:sw=4 tw=120 et
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    #include <stdarg.h>
    #include <stdio.h>
    #include <sys/time.h>
    
    #define LIBCLI_VERSION_MAJOR 1
    #define LIBCLI_VERISON_MINOR 10
    #define LIBCLI_VERISON_REVISION 6
    #define LIBCLI_VERSION ((LIBCLI_VERSION_MAJOR << 16) | (LIBCLI_VERSION_MINOR << 8) | LIBCLI_VERSION_REVISION)
    
    #define CLI_OK 0
    #define CLI_ERROR -1
    #define CLI_QUIT -2
    #define CLI_ERROR_ARG -3
    #define CLI_AMBIGUOUS -4
    #define CLI_UNRECOGNIZED -5
    #define CLI_MISSING_ARGUMENT -6
    #define CLI_MISSING_VALUE -7
    #define CLI_BUILDMODE_START -8
    #define CLI_BUILDMODE_ERROR -9
    #define CLI_BUILDMODE_EXTEND -10
    #define CLI_BUILDMODE_CANCEL -11
    #define CLI_BUILDMODE_EXIT -12
    #define CLI_INCOMPLETE_COMMAND -13
    
    #define MAX_HISTORY 256
    
    #define PRIVILEGE_UNPRIVILEGED 0
    #define PRIVILEGE_PRIVILEGED 15
    #define MODE_ANY -1
    #define MODE_EXEC 0
    #define MODE_CONFIG 1
    
    #define LIBCLI_HAS_ENABLE 1
    
    #define PRINT_PLAIN 0
    #define PRINT_FILTERED 0x01
    #define PRINT_BUFFERED 0x02
    
    #define CLI_MAX_LINE_LENGTH 4096
    #define CLI_MAX_LINE_WORDS 128
    
    struct cli_def {
      int completion_callback;
      struct cli_command *commands;
      int (*auth_callback)(const char *, const char *);
      int (*regular_callback)(struct cli_def *cli);
      int (*enable_callback)(const char *);
      char *banner;
      struct unp *users;
      char *enable_password;
      char *history[MAX_HISTORY];
      char showprompt;
      char *promptchar;
      char *hostname;
      char *modestring;
      int privilege;
      int mode;
      int state;
      struct cli_filter *filters;
      void (*print_callback)(struct cli_def *cli, const char *string);
      FILE *client;
      /* internal buffers */
      void *conn;
      void *service;
    //  char *commandname;  // temporary buffer for cli_command_name() to prevent leak
      char *buffer;
      unsigned buf_size;
      struct timeval timeout_tm;
      time_t idle_timeout;
      int (*idle_timeout_callback)(struct cli_def *);
      time_t last_action;
      int telnet_protocol;
      void *user_context;
      struct cli_optarg_pair *found_optargs;
      int transient_mode;
      int disallow_buildmode;
      struct cli_pipeline *pipeline;
      struct cli_buildmode *buildmode;
    };
    
    struct cli_filter {
      int (*filter)(struct cli_def *cli, const char *string, void *data);
      void *data;
      struct cli_filter *next;
    };
    
    enum command_types {
      CLI_ANY_COMMAND,
      CLI_REGULAR_COMMAND,
      CLI_FILTER_COMMAND,
      CLI_BUILDMODE_COMMAND,
    };
    
    struct cli_command {
      char *command;
      char *full_command_name;
      int (*callback)(struct cli_def *, const char *, char **, int);
      unsigned int unique_len;
      char *help;
      int privilege;
      int mode;
      struct cli_command *previous;
      struct cli_command *next;
      struct cli_command *children;
      struct cli_command *parent;
      struct cli_optarg *optargs;
      int (*filter)(struct cli_def *cli, const char *string, void *data);
      int (*init)(struct cli_def *cli, int, char **, struct cli_filter *filt);
      int command_type;
      int flags;
    };
    
    struct cli_comphelp {
      int comma_separated;
      char **entries;
      int num_entries;
    };
    
    enum optarg_flags {
      CLI_CMD_OPTIONAL_FLAG = 1 << 0,
      CLI_CMD_OPTIONAL_ARGUMENT = 1 << 1,
      CLI_CMD_ARGUMENT = 1 << 2,
      CLI_CMD_ALLOW_BUILDMODE = 1 << 3,
      CLI_CMD_OPTION_MULTIPLE = 1 << 4,
      CLI_CMD_OPTION_SEEN = 1 << 5,
      CLI_CMD_TRANSIENT_MODE = 1 << 6,
      CLI_CMD_DO_NOT_RECORD = 1 << 7,
      CLI_CMD_REMAINDER_OF_LINE = 1 << 8,
      CLI_CMD_HYPHENATED_OPTION = 1 << 9,
      CLI_CMD_SPOT_CHECK = 1 << 10,
    };
    
    struct cli_optarg {
      char *name;
      int flags;
      char *help;
      int mode;
      int privilege;
      unsigned int unique_len;
      int (*get_completions)(struct cli_def *, const char *, const char *, struct cli_comphelp *);
      int (*validator)(struct cli_def *, const char *, const char *);
      int (*transient_mode)(struct cli_def *, const char *, const char *);
      struct cli_optarg *next;
    };
    
    struct cli_optarg_pair {
      char *name;
      char *value;
      struct cli_optarg_pair *next;
    };
    
    struct cli_pipeline_stage {
      struct cli_command *command;
      struct cli_optarg_pair *found_optargs;
      char **words;
      int num_words;
      int status;
      int first_unmatched;
      int first_optarg;
      int stage_num;
      char *error_word;
    };
    
    struct cli_pipeline {
      char *cmdline;
      char *words[CLI_MAX_LINE_WORDS];
      int num_words;
      int num_stages;
      struct cli_pipeline_stage stage[CLI_MAX_LINE_WORDS];
      struct cli_pipeline_stage *current_stage;
    };
    
    struct cli_buildmode {
      struct cli_command *command;
      struct cli_optarg_pair *found_optargs;
      char *cname;
      int mode;
      int transient_mode;
      char *mode_text;
    };
    
    struct cli_def *cli_init(void);
    int cli_done(struct cli_def *cli);
    struct cli_command *cli_register_command(struct cli_def *cli, struct cli_command *parent, const char *command,
                                             int (*callback)(struct cli_def *, const char *, char **, int), int privilege,
                                             int mode, const char *help);
    int cli_unregister_command(struct cli_def *cli, const char *command);
    int cli_run_command(struct cli_def *cli, const char *command);
    int cli_loop(struct cli_def *cli, int sockfd);
    int cli_file(struct cli_def *cli, FILE *fh, int privilege, int mode);
    void cli_set_auth_callback(struct cli_def *cli, int (*auth_callback)(const char *, const char *));
    void cli_set_enable_callback(struct cli_def *cli, int (*enable_callback)(const char *));
    void cli_allow_user(struct cli_def *cli, const char *username, const char *password);
    void cli_allow_enable(struct cli_def *cli, const char *password);
    void cli_deny_user(struct cli_def *cli, const char *username);
    void cli_set_banner(struct cli_def *cli, const char *banner);
    void cli_set_hostname(struct cli_def *cli, const char *hostname);
    void cli_set_promptchar(struct cli_def *cli, const char *promptchar);
    void cli_set_modestring(struct cli_def *cli, const char *modestring);
    int cli_set_privilege(struct cli_def *cli, int privilege);
    int cli_set_configmode(struct cli_def *cli, int mode, const char *config_desc);
    void cli_reprompt(struct cli_def *cli);
    void cli_regular(struct cli_def *cli, int (*callback)(struct cli_def *cli));
    void cli_regular_interval(struct cli_def *cli, int seconds);
    void cli_print(struct cli_def *cli, const char *format, ...) __attribute__((format(printf, 2, 3)));
    void cli_bufprint(struct cli_def *cli, const char *format, ...) __attribute__((format(printf, 2, 3)));
    void cli_vabufprint(struct cli_def *cli, const char *format, va_list ap);
    void cli_error(struct cli_def *cli, const char *format, ...) __attribute__((format(printf, 2, 3)));
    void cli_print_callback(struct cli_def *cli, void (*callback)(struct cli_def *, const char *));
    void cli_free_history(struct cli_def *cli);
    void cli_set_idle_timeout(struct cli_def *cli, unsigned int seconds);
    void cli_set_idle_timeout_callback(struct cli_def *cli, unsigned int seconds, int (*callback)(struct cli_def *));
    void cli_dump_optargs_and_args(struct cli_def *cli, const char *text, char *argv[], int argc);
    
    // Enable or disable telnet protocol negotiation.
    // Note that this is enabled by default and must be changed before cli_loop() is run.
    void cli_telnet_protocol(struct cli_def *cli, int telnet_protocol);
    
    // Set/get user context
    void cli_set_context(struct cli_def *cli, void *context);
    void *cli_get_context(struct cli_def *cli);
    
    void cli_free_comphelp(struct cli_comphelp *comphelp);
    int cli_add_comphelp_entry(struct cli_comphelp *comphelp, const char *entry);
    void cli_set_transient_mode(struct cli_def *cli, int transient_mode);
    struct cli_command *cli_register_filter(struct cli_def *cli, const char *command,
                                            int (*init)(struct cli_def *cli, int, char **, struct cli_filter *filt),
                                            int (*filter)(struct cli_def *, const char *, void *), int privilege, int mode,
                                            const char *help);
    int cli_unregister_filter(struct cli_def *cli, const char *command);
    struct cli_optarg *cli_register_optarg(struct cli_command *cmd, const char *name, int flags, int priviledge, int mode,
                                           const char *help,
                                           int (*get_completions)(struct cli_def *cli, const char *, const char *,
                                                                  struct cli_comphelp *),
                                           int (*validator)(struct cli_def *cli, const char *, const char *),
                                           int (*transient_mode)(struct cli_def *, const char *, const char *));
    int cli_optarg_addhelp(struct cli_optarg *optarg, const char *helpname, const char *helptext);
    char *cli_find_optarg_value(struct cli_def *cli, char *name, char *find_after);
    struct cli_optarg_pair *cli_get_all_found_optargs(struct cli_def *cli);
    int cli_unregister_optarg(struct cli_command *cmd, const char *name);
    char *cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_after);
    int cli_set_optarg_value(struct cli_def *cli, const char *name, const char *value, int allow_multiple);
    void cli_unregister_all_optarg(struct cli_command *c);
    void cli_unregister_all_filters(struct cli_def *cli);
    void cli_unregister_all_commands(struct cli_def *cli);
    void cli_unregister_all(struct cli_def *cli, struct cli_command *command);
    
    /*
     * Expose some previous internal routines.  Just in case someone was using those
     * with an explicit reference, the original routines (cli_int_*) internally point
     * to the newly public routines.
     */
    int cli_help(struct cli_def *cli, const char *command, char *argv[], int argc);
    int cli_history(struct cli_def *cli, const char *command, char *argv[], int argc);
    int cli_exit(struct cli_def *cli, const char *command, char *argv[], int argc);
    int cli_quit(struct cli_def *cli, const char *command, char *argv[], int argc);
    int cli_enable(struct cli_def *cli, const char *command, char *argv[], int argc);
    int cli_disable(struct cli_def *cli, const char *command, char *argv[], int argc);
    
    #ifdef __cplusplus
    }
    #endif
    
    #endif
    

    libcli.c

    // vim:sw=2 tw=120 et
    
    #ifdef WIN32
    #include <windows.h>
    #include <winsock2.h>
    #endif
    
    #define _GNU_SOURCE
    #include <errno.h>
    #include <memory.h>
    #include <stdarg.h>
    #include <stdio.h>
    #include <stdlib.h>
    #if !defined(__APPLE__) && !defined(__FreeBSD__)
    #include <malloc.h>
    #endif
    #include <ctype.h>
    #include <string.h>
    #include <time.h>
    #include <unistd.h>
    #ifndef WIN32
    #include <regex.h>
    #endif
    #if defined(LIBCLI_USE_POLL) && !defined(WIN32)
    #include <poll.h>
    #define CLI_SOCKET_WAIT_PERROR "poll"
    #else
    #define CLI_SOCKET_WAIT_PERROR "select"
    #endif
    #include "libcli.h"
    
    #ifdef __GNUC__
    #define UNUSED(d) d __attribute__((unused))
    #else
    #define UNUSED(d) d
    #endif
    
    #define MATCH_REGEX 1
    #define MATCH_INVERT 2
    
    #ifdef WIN32
    // Stupid windows has multiple namespaces for filedescriptors, with different read/write functions required for each ..
    int read(int fd, void *buf, unsigned int count) {
      return recv(fd, buf, count, 0);
    }
    
    int write(int fd, const void *buf, unsigned int count) {
      return send(fd, buf, count, 0);
    }
    
    int vasprintf(char **strp, const char *fmt, va_list args) {
      int size;
      va_list argCopy;
    
      // Do initial vsnprintf on a copy of the va_list
      va_copy(argCopy, args);
      size = vsnprintf(NULL, 0, fmt, argCopy);
      va_end(argCopy);
      if ((*strp = malloc(size + 1)) == NULL) {
        return -1;
      }
    
      size = vsnprintf(*strp, size + 1, fmt, args);
      return size;
    }
    
    int asprintf(char **strp, const char *fmt, ...) {
      va_list args;
      int size;
    
      va_start(args, fmt);
      size = vasprintf(strp, fmt, args);
    
      va_end(args);
      return size;
    }
    
    int fprintf(FILE *stream, const char *fmt, ...) {
      va_list args;
      int size;
      char *buf;
    
      va_start(args, fmt);
      size = vasprintf(&buf, fmt, args);
      if (size < 0) {
        goto out;
      }
      size = write(stream->_file, buf, size);
      free(buf);
    
    out:
      va_end(args);
      return size;
    }
    
    // Dummy definitions to allow compilation on Windows
    int regex_dummy() {
      return 0;
    };
    #define regfree(...) regex_dummy()
    #define regexec(...) regex_dummy()
    #define regcomp(...) regex_dummy()
    #define regex_t int
    #define REG_NOSUB 0
    #define REG_EXTENDED 0
    #define REG_ICASE 0
    #endif
    
    enum cli_states {
      STATE_LOGIN,
      STATE_PASSWORD,
      STATE_NORMAL,
      STATE_ENABLE_PASSWORD,
      STATE_ENABLE,
    };
    
    struct unp {
      char *username;
      char *password;
      struct unp *next;
    };
    
    struct cli_filter_cmds {
      const char *cmd;
      const char *help;
    };
    
    // Free and zero (to avoid double-free)
    #define free_z(p) \
      do {            \
        if (p) {      \
          free(p);    \
          (p) = 0;    \
        }             \
      } while (0)
    
    // Forward defines of *INTERNAL* library function as static here
    static int cli_search_flags_validator(struct cli_def *cli, const char *word, const char *value);
    static int cli_match_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt);
    static int cli_range_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt);
    static int cli_count_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt);
    static int cli_match_filter(struct cli_def *cli, const char *string, void *data);
    static int cli_range_filter(struct cli_def *cli, const char *string, void *data);
    static int cli_count_filter(struct cli_def *cli, const char *string, void *data);
    static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage *stage, struct cli_command *cmd,
                                      char lastchar, struct cli_comphelp *comphelp);
    static int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stage, char *mode_text);
    static char *cli_int_buildmode_extend_cmdline(char *, char *word);
    static void cli_int_free_buildmode(struct cli_def *cli);
    static void cli_free_command(struct cli_def *cli, struct cli_command *cmd);
    static int cli_int_unregister_command_core(struct cli_def *cli, const char *command, int command_type);
    static int cli_int_unregister_buildmode_command(struct cli_def *cli, const char *command) __attribute__((unused));
    static struct cli_command *cli_int_register_buildmode_command(struct cli_def *cli, struct cli_command *parent,
                                                                  const char *command,
                                                                  int (*callback)(struct cli_def *cli, const char *,
                                                                                  char **, int),
                                                                  int flags, int privilege, int mode, const char *help);
    static void cli_int_buildmode_reset_unset_help(struct cli_def *cli);
    static int cli_int_buildmode_cmd_cback(struct cli_def *cli, const char *command, char *argv[], int argc);
    static int cli_int_buildmode_flag_cback(struct cli_def *cli, const char *command, char *argv[], int argc);
    static int cli_int_buildmode_flag_multiple_cback(struct cli_def *cli, const char *command, char *argv[], int argc);
    static int cli_int_buildmode_cancel_cback(struct cli_def *cli, const char *command, char *argv[], int argc);
    static int cli_int_buildmode_execute_cback(struct cli_def *cli, const char *command, char *argv[], int argc);
    static int cli_int_buildmode_show_cback(struct cli_def *cli, const char *command, char *argv[], int argc);
    static int cli_int_buildmode_unset_cback(struct cli_def *cli, const char *command, char *argv[], int argc);
    static int cli_int_buildmode_unset_completor(struct cli_def *cli, const char *name, const char *word,
                                                 struct cli_comphelp *comphelp);
    static int cli_int_buildmode_unset_validator(struct cli_def *cli, const char *name, const char *value);
    static int cli_int_execute_buildmode(struct cli_def *cli);
    static void cli_int_free_found_optargs(struct cli_optarg_pair **optarg_pair);
    static void cli_int_unset_optarg_value(struct cli_def *cli, const char *name);
    static struct cli_pipeline *cli_int_generate_pipeline(struct cli_def *cli, const char *command);
    static int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline);
    static int cli_int_execute_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline);
    inline void cli_int_show_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline);
    static void cli_int_free_pipeline(struct cli_pipeline *pipeline);
    static struct cli_command *cli_register_command_core(struct cli_def *cli, struct cli_command *parent,
                                                         struct cli_command *c);
    static void cli_int_wrap_help_line(char *nameptr, char *helpptr, struct cli_comphelp *comphelp);
    static int cli_socket_wait(int sockfd, struct timeval *tm);
    
    static char DELIM_OPT_START[] = "[";
    static char DELIM_OPT_END[] = "]";
    static char DELIM_ARG_START[] = "<";
    static char DELIM_ARG_END[] = ">";
    static char DELIM_NONE[] = "";
    
    static ssize_t _write(int fd, const void *buf, size_t count) {
      size_t written = 0;
      ssize_t thisTime = 0;
      while (count != written) {
        thisTime = write(fd, (char *)buf + written, count - written);
        if (thisTime == -1) {
          if (errno == EINTR)
            continue;
          else
            return -1;
        }
        written += thisTime;
      }
      return written;
    }
    
    char *cli_int_command_name(struct cli_def *cli, struct cli_command *command) {
      char *name;
      char *o;
    
      if (command->full_command_name) {
        free(command->full_command_name);
        command->full_command_name = NULL;
      }
    
      if (!(name = calloc(1, 1))) return NULL;
    
      while (command) {
        o = name;
        if (asprintf(&name, "%s%s%s", command->command, *o ? " " : "", o) == -1) {
          fprintf(stderr, "Couldn't allocate memory for command_name: %s", strerror(errno));
          free(o);
          return NULL;
        }
        command = command->parent;
        free(o);
      }
      return name;
    }
    
    char *cli_command_name(struct cli_def *cli, struct cli_command *command) {
      return command->full_command_name;
    }
    
    void cli_set_auth_callback(struct cli_def *cli, int (*auth_callback)(const char *, const char *)) {
      cli->auth_callback = auth_callback;
    }
    
    void cli_set_enable_callback(struct cli_def *cli, int (*enable_callback)(const char *)) {
      cli->enable_callback = enable_callback;
    }
    
    void cli_allow_user(struct cli_def *cli, const char *username, const char *password) {
      struct unp *u, *n;
      if (!(n = malloc(sizeof(struct unp)))) {
        fprintf(stderr, "Couldn't allocate memory for user: %s", strerror(errno));
        return;
      }
      if (!(n->username = strdup(username))) {
        fprintf(stderr, "Couldn't allocate memory for username: %s", strerror(errno));
        free(n);
        return;
      }
      if (!(n->password = strdup(password))) {
        fprintf(stderr, "Couldn't allocate memory for password: %s", strerror(errno));
        free(n->username);
        free(n);
        return;
      }
      n->next = NULL;
    
      if (!cli->users) {
        cli->users = n;
      } else {
        for (u = cli->users; u && u->next; u = u->next)
          ;
        if (u) u->next = n;
      }
    }
    
    void cli_allow_enable(struct cli_def *cli, const char *password) {
      free_z(cli->enable_password);
      if (!(cli->enable_password = strdup(password))) {
        fprintf(stderr, "Couldn't allocate memory for enable password: %s", strerror(errno));
      }
    }
    
    void cli_deny_user(struct cli_def *cli, const char *username) {
      struct unp *u, *p = NULL;
      if (!cli->users) return;
      for (u = cli->users; u; u = u->next) {
        if (strcmp(username, u->username) == 0) {
          if (p)
            p->next = u->next;
          else
            cli->users = u->next;
          free(u->username);
          free(u->password);
          free(u);
          break;
        }
        p = u;
      }
    }
    
    void cli_set_banner(struct cli_def *cli, const char *banner) {
      free_z(cli->banner);
      if (banner && *banner) cli->banner = strdup(banner);
    }
    
    void cli_set_hostname(struct cli_def *cli, const char *hostname) {
      free_z(cli->hostname);
      if (hostname && *hostname) cli->hostname = strdup(hostname);
    }
    
    void cli_set_promptchar(struct cli_def *cli, const char *promptchar) {
      free_z(cli->promptchar);
      cli->promptchar = strdup(promptchar);
    }
    
    static int cli_build_shortest(struct cli_def *cli, struct cli_command *commands) {
      struct cli_command *c, *p;
      char *cp, *pp;
      unsigned len;
    
      for (c = commands; c; c = c->next) {
        c->unique_len = strlen(c->command);
        if ((c->mode != MODE_ANY && c->mode != cli->mode) || c->privilege > cli->privilege) continue;
    
        c->unique_len = 1;
        for (p = commands; p; p = p->next) {
          if (c == p) continue;
          if (c->command_type != p->command_type) continue;
          if ((p->mode != MODE_ANY && p->mode != cli->mode) || p->privilege > cli->privilege) continue;
    
          cp = c->command;
          pp = p->command;
          len = 1;
    
          while (*cp && *pp && *cp++ == *pp++) len++;
    
          if (len > c->unique_len) c->unique_len = len;
        }
    
        if (c->children) cli_build_shortest(cli, c->children);
      }
    
      return CLI_OK;
    }
    
    int cli_set_privilege(struct cli_def *cli, int priv) {
      int old = cli->privilege;
      cli->privilege = priv;
    
      if (priv != old) {
        cli_set_promptchar(cli, priv == PRIVILEGE_PRIVILEGED ? "# " : "> ");
        cli_build_shortest(cli, cli->commands);
      }
    
      return old;
    }
    
    void cli_set_modestring(struct cli_def *cli, const char *modestring) {
      free_z(cli->modestring);
      if (modestring) cli->modestring = strdup(modestring);
    }
    
    int cli_set_configmode(struct cli_def *cli, int mode, const char *config_desc) {
      int old = cli->mode;
      cli->mode = mode;
    
      if (mode != old) {
        if (!cli->mode) {
          // Not config mode
          cli_set_modestring(cli, NULL);
        } else if (config_desc && *config_desc) {
          char string[64];
          snprintf(string, sizeof(string), "(config-%s)", config_desc);
          cli_set_modestring(cli, string);
        } else {
          cli_set_modestring(cli, "(config)");
        }
    
        cli_build_shortest(cli, cli->commands);
      }
    
      return old;
    }
    
    struct cli_command *cli_register_command_core(struct cli_def *cli, struct cli_command *parent, struct cli_command *c) {
      struct cli_command *p = NULL;
    
      if (!c) return NULL;
    
      c->parent = parent;
    
      /* Go build the 'full command name' now that told it who its parent is.
       * If this fails, clean it up and return a NULL w/o proceeding.
       */
      if (!(c->full_command_name = cli_int_command_name(cli, c))) {
        cli_free_command(cli, c);
        return NULL;
      }
      /*
       * Figure out we have a chain, or would be the first element on it.
       * If we'd be the first element, assign as such.
       * Otherwise find the lead element so we can trace it below.
       */
    
      if (parent) {
        if (!parent->children) {
          parent->children = c;
        } else {
          p = parent->children;
        }
      } else {
        if (!cli->commands) {
          cli->commands = c;
        } else {
          p = cli->commands;
        }
      }
    
      /*
       * If we have a chain (p is not null), run down to the last element and place this command at the end
       */
      for (; p && p->next; p = p->next)
        ;
    
      if (p) {
        p->next = c;
        c->previous = p;
      }
      return c;
    }
    
    struct cli_command *cli_register_command(struct cli_def *cli, struct cli_command *parent, const char *command,
                                             int (*callback)(struct cli_def *cli, const char *, char **, int),
                                             int privilege, int mode, const char *help) {
      struct cli_command *c;
    
      if (!command) return NULL;
      if (!(c = calloc(sizeof(struct cli_command), 1))) return NULL;
      c->command_type = CLI_REGULAR_COMMAND;
      c->callback = callback;
      c->next = NULL;
      if (!(c->command = strdup(command))) {
        free(c);
        return NULL;
      }
    
      c->privilege = privilege;
      c->mode = mode;
      if (help && !(c->help = strdup(help))) {
        free(c->command);
        free(c);
        return NULL;
      }
    
      return cli_register_command_core(cli, parent, c);
    }
    
    static void cli_free_command(struct cli_def *cli, struct cli_command *cmd) {
      struct cli_command *c, *p;
    
      for (c = cmd->children; c;) {
        p = c->next;
        cli_free_command(cli, c);
        c = p;
      }
    
      free(cmd->command);
      if (cmd->help) free(cmd->help);
      if (cmd->optargs) cli_unregister_all_optarg(cmd);
      if (cmd->full_command_name) free(cmd->full_command_name);
      /*
       * Ok, update the pointers of anyone who pointed to us.
       * We have 3 pointers to worry about - parent, previous, and next.
       * We don't have to worry about children since they've been cleared above.
       * If both cli->command points to us we need to update cli->command to point to whatever command is 'next'.
       * Otherwise ensure that any item before/behind us points around us.
       *
       * Important - there is no provision for deleting a discrete subcommand.
       * For example, suppose we define foo, then bar with foo as the parent, then baz with bar as the parent.  We cannot
       * delete 'bar' and have a new chain of foo -> baz.
       * The above freeing of children prevents this in the first place.
       */
    
      if (cmd == cli->commands) {
        cli->commands = cmd->next;
        if (cmd->next) {
          cmd->next->parent = NULL;
          cmd->next->previous = NULL;
        }
      } else {
        if (cmd->previous) {
          cmd->previous->next = cmd->next;
        }
        if (cmd->next) {
          cmd->next->previous = cmd->previous;
        }
      }
      free(cmd);
    }
    
    int cli_int_unregister_command_core(struct cli_def *cli, const char *command, int command_type) {
      struct cli_command *c, *p = NULL;
    
      if (!command) return -1;
      if (!cli->commands) return CLI_OK;
    
      for (c = cli->commands; c;) {
        p = c->next;
        if (strcmp(c->command, command) == 0 && c->command_type == command_type) {
          cli_free_command(cli, c);
          return CLI_OK;
        }
        c = p;
      }
    
      return CLI_OK;
    }
    
    int cli_unregister_command(struct cli_def *cli, const char *command) {
      return cli_int_unregister_command_core(cli, command, CLI_REGULAR_COMMAND);
    }
    
    int cli_show_help(struct cli_def *cli, struct cli_command *c) {
      struct cli_command *p;
    
      for (p = c; p; p = p->next) {
        if (p->command && p->callback && cli->privilege >= p->privilege && (p->mode == cli->mode || p->mode == MODE_ANY)) {
          cli_error(cli, "  %-20s %s", cli_command_name(cli, p), (p->help != NULL ? p->help : ""));
        }
    
        if (p->children) cli_show_help(cli, p->children);
      }
    
      return CLI_OK;
    }
    
    int cli_enable(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) {
      if (cli->privilege == PRIVILEGE_PRIVILEGED) return CLI_OK;
    
      if (!cli->enable_password && !cli->enable_callback) {
        // No password required, set privilege immediately.
        cli_set_privilege(cli, PRIVILEGE_PRIVILEGED);
        cli_set_configmode(cli, MODE_EXEC, NULL);
      } else {
        // Require password entry
        cli->state = STATE_ENABLE_PASSWORD;
      }
    
      return CLI_OK;
    }
    
    int cli_disable(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) {
      cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED);
      cli_set_configmode(cli, MODE_EXEC, NULL);
      return CLI_OK;
    }
    
    int cli_help(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) {
      cli_error(cli, "\nCommands available:");
      cli_show_help(cli, cli->commands);
      return CLI_OK;
    }
    
    int cli_history(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) {
      int i;
    
      cli_error(cli, "\nCommand history:");
      for (i = 0; i < MAX_HISTORY; i++) {
        if (cli->history[i]) cli_error(cli, "%3d. %s", i, cli->history[i]);
      }
    
      return CLI_OK;
    }
    
    int cli_quit(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) {
      cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED);
      cli_set_configmode(cli, MODE_EXEC, NULL);
      return CLI_QUIT;
    }
    
    int cli_exit(struct cli_def *cli, const char *command, char *argv[], int argc) {
      if (cli->mode == MODE_EXEC) return cli_quit(cli, command, argv, argc);
    
      if (cli->mode > MODE_CONFIG)
        cli_set_configmode(cli, MODE_CONFIG, NULL);
      else
        cli_set_configmode(cli, MODE_EXEC, NULL);
    
      cli->service = NULL;
      return CLI_OK;
    }
    
    int cli_int_idle_timeout(struct cli_def *cli) {
      cli_print(cli, "Idle timeout");
      return CLI_QUIT;
    }
    
    int cli_int_configure_terminal(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]),
                                   UNUSED(int argc)) {
      cli_set_configmode(cli, MODE_CONFIG, NULL);
      return CLI_OK;
    }
    
    struct cli_def *cli_init() {
      struct cli_def *cli;
      struct cli_command *c;
    
      if (!(cli = calloc(sizeof(struct cli_def), 1))) return 0;
    
      cli->buf_size = 1024;
      if (!(cli->buffer = calloc(cli->buf_size, 1))) {
        cli_done(cli);
        return 0;
      }
      cli->telnet_protocol = 1;
    
      cli_register_command(cli, 0, "help", cli_help, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Show available commands");
      cli_register_command(cli, 0, "quit", cli_quit, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Disconnect");
      cli_register_command(cli, 0, "logout", cli_quit, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Disconnect");
      cli_register_command(cli, 0, "exit", cli_exit, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Exit from current mode");
      cli_register_command(cli, 0, "history", cli_history, PRIVILEGE_UNPRIVILEGED, MODE_ANY,
                           "Show a list of previously run commands");
      cli_register_command(cli, 0, "enable", cli_enable, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Turn on privileged commands");
      cli_register_command(cli, 0, "disable", cli_disable, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Turn off privileged commands");
    
      c = cli_register_command(cli, 0, "configure", 0, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Enter configuration mode");
      if (!c) {
        cli_done(cli);
        return 0;
      }
      cli_register_command(cli, c, "terminal", cli_int_configure_terminal, PRIVILEGE_PRIVILEGED, MODE_EXEC,
                           "Conlfigure from the terminal");
    
      // And now the built in filters
      c = cli_register_filter(cli, "begin", cli_range_filter_init, cli_range_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY,
                              "Begin with lines that match");
      if (!c) {
        cli_done(cli);
        return 0;
      }
      cli_register_optarg(c, "range_start", CLI_CMD_ARGUMENT | CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED, MODE_ANY,
                          "Begin showing lines that match", NULL, NULL, NULL);
    
      c = cli_register_filter(cli, "between", cli_range_filter_init, cli_range_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY,
                              "Between lines that match");
      if (!c) {
        cli_done(cli);
        return 0;
      }
      cli_register_optarg(c, "range_start", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_ANY,
                          "Begin showing lines that match", NULL, NULL, NULL);
      cli_register_optarg(c, "range_end", CLI_CMD_ARGUMENT | CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED, MODE_ANY,
                          "Stop showing lines that match", NULL, NULL, NULL);
    
      cli_register_filter(cli, "count", cli_count_filter_init, cli_count_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY,
                          "Count of lines");
    
      c = cli_register_filter(cli, "exclude", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY,
                              "Exclude lines that match");
      if (!c) {
        cli_done(cli);
        return 0;
      }
      cli_register_optarg(c, "search_pattern", CLI_CMD_ARGUMENT | CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED,
                          MODE_ANY, "Search pattern", NULL, NULL, NULL);
    
      c = cli_register_filter(cli, "include", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY,
                              "Include lines that match");
      if (!c) {
        cli_done(cli);
        return 0;
      }
      cli_register_optarg(c, "search_pattern", CLI_CMD_ARGUMENT | CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED,
                          MODE_ANY, "Search pattern", NULL, NULL, NULL);
    
      c = cli_register_filter(cli, "grep", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY,
                              "Include lines that match regex (options: -v, -i, -e)");
      if (!c) {
        cli_done(cli);
        return 0;
      }
      cli_register_optarg(c, "search_flags", CLI_CMD_HYPHENATED_OPTION, PRIVILEGE_UNPRIVILEGED, MODE_ANY,
                          "Search flags (-[ivx]", NULL, cli_search_flags_validator, NULL);
      cli_register_optarg(c, "search_pattern", CLI_CMD_ARGUMENT | CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED,
                          MODE_ANY, "Search pattern", NULL, NULL, NULL);
    
      c = cli_register_filter(cli, "egrep", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY,
                              "Include lines that match extended regex");
      if (!c) {
        cli_done(cli);
        return 0;
      }
      cli_register_optarg(c, "search_flags", CLI_CMD_HYPHENATED_OPTION, PRIVILEGE_UNPRIVILEGED, MODE_ANY,
                          "Search flags (-[ivx]", NULL, cli_search_flags_validator, NULL);
      cli_register_optarg(c, "search_pattern", CLI_CMD_ARGUMENT | CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED,
                          MODE_ANY, "Search pattern", NULL, NULL, NULL);
    
      cli->privilege = cli->mode = -1;
      cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED);
      cli_set_configmode(cli, MODE_EXEC, 0);
    
      // Default to 1 second timeout intervals
      cli->timeout_tm.tv_sec = 1;
      cli->timeout_tm.tv_usec = 0;
    
      // Set default idle timeout callback, but no timeout
      cli_set_idle_timeout_callback(cli, 0, cli_int_idle_timeout);
      return cli;
    }
    
    void cli_unregister_tree(struct cli_def *cli, struct cli_command *command, int command_type) {
      struct cli_command *c, *p = NULL;
    
      if (!command) command = cli->commands;
    
      for (c = command; c;) {
        p = c->next;
        if (c->command_type == command_type || command_type == CLI_ANY_COMMAND) {
          if (c == cli->commands) cli->commands = c->next;
          // Unregister all child commands
          cli_free_command(cli, c);
        }
        c = p;
      }
    }
    
    void cli_unregister_all(struct cli_def *cli, struct cli_command *command) {
      cli_unregister_tree(cli, command, CLI_REGULAR_COMMAND);
    }
    
    int cli_done(struct cli_def *cli) {
      if (!cli) return CLI_OK;
      struct unp *u = cli->users, *n;
    
      cli_free_history(cli);
    
      // Free all users
      while (u) {
        if (u->username) free(u->username);
        if (u->password) free(u->password);
        n = u->next;
        free(u);
        u = n;
      }
    
      if (cli->buildmode) cli_int_free_buildmode(cli);
      cli_unregister_tree(cli, cli->commands, CLI_ANY_COMMAND);
      free_z(cli->promptchar);
      free_z(cli->modestring);
      free_z(cli->banner);
      free_z(cli->promptchar);
      free_z(cli->hostname);
      free_z(cli->buffer);
      free_z(cli);
    
      return CLI_OK;
    }
    
    static int cli_add_history(struct cli_def *cli, const char *cmd) {
      int i;
      for (i = 0; i < MAX_HISTORY; i++) {
        if (!cli->history[i]) {
          if (i == 0 || strcasecmp(cli->history[i - 1], cmd))
            if (!(cli->history[i] = strdup(cmd))) return CLI_ERROR;
          return CLI_OK;
        }
      }
      // No space found, drop one off the beginning of the list
      free(cli->history[0]);
      for (i = 0; i < MAX_HISTORY - 1; i++) cli->history[i] = cli->history[i + 1];
      if (!(cli->history[MAX_HISTORY - 1] = strdup(cmd))) return CLI_ERROR;
      return CLI_OK;
    }
    
    void cli_free_history(struct cli_def *cli) {
      int i;
      for (i = 0; i < MAX_HISTORY; i++) {
        if (cli->history[i]) free_z(cli->history[i]);
      }
    }
    
    static char *cli_int_return_newword(const char *start, const char *end) {
      int len = end - start;
      char *to = NULL;
      char *newword = NULL;
    
      // allocate space (including terminal NULL, then go through and deal with escaping characters as we copy them
    
      if (!(newword = calloc(len + 1, 1))) return 0;
      to = newword;
      while (start != end) {
        if (*start == '\\')
          start++;
        else
          *to++ = *start++;
      }
      return newword;
    }
    
    static int cli_parse_line(const char *line, char *words[], int max_words) {
      int nwords = 0;
      const char *p = line;
      const char *word_start = 0;
      int inquote = 0;
    
      while (*p) {
        if (!isspace(*p)) {
          word_start = p;
          break;
        }
        p++;
      }
    
      while (nwords < max_words - 1) {
        if (*p == '\\' && *(p + 1)) {
          p += 2;
        }
    
        /*
         * a 'word' terminates at:
         *   - end-of-string, whitespace (if not inside quotes)
         *   - start of quoted section (if word_start != NULL)
         *   - end of a quoted section
         *   - whitespace/pipe unless inside quotes
         */
    
        if (!*p || *p == inquote || (word_start && !inquote && (isspace(*p) || *p == '|'))) {
          // if we have a word start, extract from there to this character dealing with escapes
          if (word_start) {
            if (!(words[nwords++] = cli_int_return_newword(word_start, p))) return 0;
          }
    
          // now figure out how to proceed
    
          // if at end_of_string we're done
          if (!*p) break;
    
          // found matching quote, eat it
          if (inquote) p++;  // Skip over trailing quote if we have one
          inquote = 0;
          word_start = 0;
        } else if (!inquote && (*p == '"' || *p == '\'')) {
          if (word_start && word_start != p) {
            if (!(words[nwords++] = cli_int_return_newword(word_start, p))) return 0;
          }
          inquote = *p++;
          word_start = p;
        } else {
          if (!word_start) {
            if (*p == '|') {
              if (!(words[nwords++] = strdup("|"))) return 0;
            } else if (!isspace(*p))
              word_start = p;
          }
    
          p++;
        }
      }
    
      return nwords;
    }
    
    static char *join_words(int argc, char **argv) {
      char *p;
      int len = 0;
      int i;
    
      for (i = 0; i < argc; i++) {
        if (i) len += 1;
    
        len += strlen(argv[i]);
      }
    
      p = malloc(len + 1);
      if (!p) return NULL;
      p[0] = 0;
    
      for (i = 0; i < argc; i++) {
        if (i) strcat(p, " ");
    
        strcat(p, argv[i]);
      }
    
      return p;
    }
    
    int cli_run_command(struct cli_def *cli, const char *command) {
      int rc = CLI_ERROR;
      struct cli_pipeline *pipeline;
    
      // Split command into pipeline stages
      pipeline = cli_int_generate_pipeline(cli, command);
    
      // cli_int_validate_pipeline will deal with buildmode command setup, and return CLI_BUILDMODE_START if found.
      if (pipeline) rc = cli_int_validate_pipeline(cli, pipeline);
    
      if (rc == CLI_OK) {
        rc = cli_int_execute_pipeline(cli, pipeline);
      }
      cli_int_free_pipeline(pipeline);
      return rc;
    }
    
    void cli_get_completions(struct cli_def *cli, const char *command, char lastchar, struct cli_comphelp *comphelp) {
      struct cli_command *c = NULL;
      struct cli_command *n = NULL;
    
      int i;
      int command_type;
      struct cli_pipeline *pipeline = NULL;
      struct cli_pipeline_stage *stage;
      char *delim_start = DELIM_NONE;
      char *delim_end = DELIM_NONE;
    
      if (!(pipeline = cli_int_generate_pipeline(cli, command))) goto out;
    
      stage = &pipeline->stage[pipeline->num_stages - 1];
    
      // Check to see if either *no* input, or if the lastchar is a tab.
      if ((!stage->words[0] || (command[strlen(command) - 1] == ' ')) && (stage->words[stage->num_words - 1]))
        stage->num_words++;
    
      if (cli->buildmode)
        command_type = CLI_BUILDMODE_COMMAND;
      else if (pipeline->num_stages == 1)
        command_type = CLI_REGULAR_COMMAND;
      else
        command_type = CLI_FILTER_COMMAND;
    
      for (c = cli->commands, i = 0; c && i < stage->num_words; c = n) {
        char *strptr = NULL;
        char *nameptr = NULL;
        n = c->next;
    
        if (c->command_type != command_type) continue;
        if (cli->privilege < c->privilege) continue;
        if (c->mode != cli->mode && c->mode != MODE_ANY) continue;
        if (stage->words[i] && strncasecmp(c->command, stage->words[i], strlen(stage->words[i]))) continue;
    
        // Special case for 'buildmode' - skip if the argument for this command was seen, unless MULTIPLE flag is set
        if (cli->buildmode) {
          struct cli_optarg *optarg;
          for (optarg = cli->buildmode->command->optargs; optarg; optarg = optarg->next) {
            if (!strcmp(optarg->name, c->command)) break;
          }
          if (optarg && cli_find_optarg_value(cli, optarg->name, NULL) && !(optarg->flags & (CLI_CMD_OPTION_MULTIPLE)))
            continue;
        }
        if (i < stage->num_words - 1) {
          if (stage->words[i] && (strlen(stage->words[i]) < c->unique_len) && strcmp(stage->words[i], c->command)) continue;
    
          n = c->children;
    
          // If we have no more children, we've matched the *command* - remember this
          if (!c->children) break;
    
          i++;
          continue;
        }
    
        if (lastchar == '?') {
          delim_start = DELIM_NONE;
          delim_end = DELIM_NONE;
    
          // Note that buildmode commands need to see if that command is some optinal value
    
          if (command_type == CLI_BUILDMODE_COMMAND) {
            if (c->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_OPTIONAL_ARGUMENT)) {
              delim_start = DELIM_OPT_START;
              delim_end = DELIM_OPT_END;
            }
          }
          if (asprintf(&nameptr, "%s%s%s", delim_start, c->command, delim_end) != -1) {
            if (asprintf(&strptr, "  %s", nameptr) != -1) {
              cli_int_wrap_help_line(strptr, c->help, comphelp);
              free_z(strptr);
            }
            free(nameptr);
          }
        } else {
          cli_add_comphelp_entry(comphelp, c->command);
        }
      }
    
    out:
      if (c) {
        // Advance past first word of stage
        i++;
        stage->command = c;
        stage->first_unmatched = i;
        if (c->optargs) {
          cli_int_parse_optargs(cli, stage, c, lastchar, comphelp);
        } else if (lastchar == '?') {
          // Special case for getting help with no defined optargs....
          comphelp->num_entries = -1;
        }
        if (stage->status) {
          // if we had an error here we need to redraw the commandline
          cli_reprompt(cli);
        }
      }
    
      cli_int_free_pipeline(pipeline);
    }
    
    static void cli_clear_line(int sockfd, char *cmd, int l, int cursor) {
      // Use cmd as our buffer, and overwrite contents as needed.
      // Backspace to beginning
      memset((char *)cmd, '\b', cursor);
      _write(sockfd, cmd, cursor);
    
      // Overwrite existing cmd with spaces
      memset((char *)cmd, ' ', l);
      _write(sockfd, cmd, l);
    
      // ..and backspace again to beginning
      memset((char *)cmd, '\b', l);
      _write(sockfd, cmd, l);
    
      // Null cmd buffer
      memset((char *)cmd, 0, l);
    }
    
    void cli_reprompt(struct cli_def *cli) {
      if (!cli) return;
      cli->showprompt = 1;
    }
    
    void cli_regular(struct cli_def *cli, int (*callback)(struct cli_def *cli)) {
      if (!cli) return;
      cli->regular_callback = callback;
    }
    
    void cli_regular_interval(struct cli_def *cli, int seconds) {
      if (seconds < 1) seconds = 1;
      cli->timeout_tm.tv_sec = seconds;
      cli->timeout_tm.tv_usec = 0;
    }
    
    #define DES_PREFIX "{crypt}"  // To distinguish clear text from DES crypted
    #define MD5_PREFIX "$1$"
    
    // returns 0 on fail/error, 1 if password checks out
    static int pass_matches(const char *pass, const char *attempt) {
      int des;
      if ((des = !strncasecmp(pass, DES_PREFIX, sizeof(DES_PREFIX) - 1))) pass += sizeof(DES_PREFIX) - 1;
    
    #ifndef WIN32
      // TODO(dparrish): Find a small crypt(3) function for use on windows
      if (des || !strncmp(pass, MD5_PREFIX, sizeof(MD5_PREFIX) - 1)) attempt = crypt(attempt, pass);
    #endif
      if (!attempt) {
        // silent return here...
        return 0;
      }
      return !strcmp(pass, attempt);
    }
    
    #define CTRL(c) (c - '@')
    
    static int show_prompt(struct cli_def *cli, int sockfd) {
      int len = 0;
    
      if (cli->hostname) len += write(sockfd, cli->hostname, strlen(cli->hostname));
    
      if (cli->modestring) len += write(sockfd, cli->modestring, strlen(cli->modestring));
      if (cli->buildmode) {
        len += write(sockfd, "[", 1);
        len += write(sockfd, cli->buildmode->cname, strlen(cli->buildmode->cname));
        len += write(sockfd, "...", 3);
        if (cli->buildmode->mode_text) len += write(sockfd, cli->buildmode->mode_text, strlen(cli->buildmode->mode_text));
        len += write(sockfd, "]", 1);
      }
      return len + write(sockfd, cli->promptchar, strlen(cli->promptchar));
    }
    
    int cli_loop(struct cli_def *cli, int sockfd) {
      int n, l, oldl = 0, is_telnet_option = 0, skip = 0, esc = 0, cursor = 0;
      char *cmd = NULL, *oldcmd = 0;
      char *username = NULL, *password = NULL;
    
      cli_build_shortest(cli, cli->commands);
      cli->state = STATE_LOGIN;
    
      cli_free_history(cli);
      if (cli->telnet_protocol) {
        static const char *negotiate =
            "\xFF\xFB\x03"
            "\xFF\xFB\x01"
            "\xFF\xFD\x03"
            "\xFF\xFD\x01";
        _write(sockfd, negotiate, strlen(negotiate));
      }
    
      if ((cmd = malloc(CLI_MAX_LINE_LENGTH)) == NULL) return CLI_ERROR;
    
    #ifdef WIN32
      /*
       * OMG, HACK
       */
      if (!(cli->client = fdopen(_open_osfhandle(sockfd, 0), "w+"))) return CLI_ERROR;
      cli->client->_file = sockfd;
    #else
      if (!(cli->client = fdopen(sockfd, "w+"))) {
        free(cmd);
        return CLI_ERROR;
      }
    #endif
    
    #ifndef LIBCLI_USE_POLL
      /* Do a range check *early*, and punt if we were passed a file descriptor
       * that is out of the valid range
       */
      if (sockfd >= FD_SETSIZE) {
        fprintf(stderr, "CLI_LOOP() called with sockfd > FD_SETSIZE - aborting\n");
        cli_error(cli, "CLI_LOOP() called with sockfd > FD_SETSIZE - exiting cli_loop\n");
        return CLI_ERROR;
      }
    #endif
      setbuf(cli->client, NULL);
      if (cli->banner) cli_error(cli, "%s", cli->banner);
    
      // Set the last action now so we don't time immediately
      if (cli->idle_timeout) time(&cli->last_action);
    
      // Start off in unprivileged mode
      cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED);
      cli_set_configmode(cli, MODE_EXEC, NULL);
    
      // No auth required?
      if (!cli->users && !cli->auth_callback) cli->state = STATE_NORMAL;
    
      while (1) {
        signed int in_history = 0;
        unsigned char lastchar = '\0';
        unsigned char c = '\0';
        struct timeval tm;
    
        cli->showprompt = 1;
    
        if (oldcmd) {
          l = cursor = oldl;
          oldcmd[l] = 0;
          cli->showprompt = 1;
          oldcmd = NULL;
          oldl = 0;
        } else {
          memset(cmd, 0, CLI_MAX_LINE_LENGTH);
          l = 0;
          cursor = 0;
        }
    
        memcpy(&tm, &cli->timeout_tm, sizeof(tm));
    
        while (1) {
          int sr;
    
          /*
           * Ensure our transient mode is reset to the starting mode on *each* loop traversal transient mode is valid only
           * while a command is being evaluated/executed.  Also explicitly set the disallow_buildmode flag based on whether
           * or not cli->buildmode is NULL or not.  The cli->buildmode flag can be changed during process, but the
           * enable/disable needs to be set before any processing is entered.
           */
          cli->transient_mode = cli->mode;
          cli->disallow_buildmode = (cli->buildmode) ? 1 : 0;
    
          if (cli->showprompt) {
            if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) _write(sockfd, "\r\n", 2);
    
            switch (cli->state) {
              case STATE_LOGIN:
                _write(sockfd, "Username: ", strlen("Username: "));
                break;
    
              case STATE_PASSWORD:
                _write(sockfd, "Password: ", strlen("Password: "));
                break;
    
              case STATE_NORMAL:
              case STATE_ENABLE:
                show_prompt(cli, sockfd);
                _write(sockfd, cmd, l);
                if (cursor < l) {
                  int n = l - cursor;
                  while (n--) _write(sockfd, "\b", 1);
                }
                break;
    
              case STATE_ENABLE_PASSWORD:
                _write(sockfd, "Password: ", strlen("Password: "));
                break;
            }
    
            cli->showprompt = 0;
          }
    
          if ((sr = cli_socket_wait(sockfd, &tm)) < 0) {
            if (errno == EINTR) continue;
            perror(CLI_SOCKET_WAIT_PERROR);
            l = -1;
            break;
          }
    
          if (sr == 0) {
            // Timeout every second
            if (cli->regular_callback && cli->regular_callback(cli) != CLI_OK) {
              l = -1;
              break;
            }
    
            if (cli->idle_timeout) {
              if (time(NULL) - cli->last_action >= cli->idle_timeout) {
                if (cli->idle_timeout_callback) {
                  // Call the callback and continue on if successful
                  if (cli->idle_timeout_callback(cli) == CLI_OK) {
                    // Reset the idle timeout counter
                    time(&cli->last_action);
                    continue;
                  }
                }
                // Otherwise, break out of the main loop
                l = -1;
                break;
              }
            }
    
            memcpy(&tm, &cli->timeout_tm, sizeof(tm));
            continue;
          }
    
          if ((n = read(sockfd, &c, 1)) < 0) {
            if (errno == EINTR) continue;
    
            perror("read");
            l = -1;
            break;
          }
    
          if (cli->idle_timeout) time(&cli->last_action);
    
          if (n == 0) {
            l = -1;
            break;
          }
    
          if (skip) {
            skip--;
            continue;
          }
    
          if (c == 255 && !is_telnet_option) {
            is_telnet_option++;
            continue;
          }
    
          if (is_telnet_option) {
            if (c >= 251 && c <= 254) {
              is_telnet_option = c;
              continue;
            }
    
            if (c != 255) {
              is_telnet_option = 0;
              continue;
            }
    
            is_telnet_option = 0;
          }
    
          // Handle ANSI arrows
          if (esc) {
            if (esc == '[') {
              // Remap to readline control codes
              switch (c) {
                case 'A':  // Up
                  c = CTRL('P');
                  break;
    
                case 'B':  // Down
                  c = CTRL('N');
                  break;
    
                case 'C':  // Right
                  c = CTRL('F');
                  break;
    
                case 'D':  // Left
                  c = CTRL('B');
                  break;
    
                default:
                  c = 0;
              }
    
              esc = 0;
            } else {
              esc = (c == '[') ? c : 0;
              continue;
            }
          }
    
          if (c == 0) continue;
          if (c == '\n') continue;
    
          if (c == '\r') {
            if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) _write(sockfd, "\r\n", 2);
            break;
          }
    
          if (c == 27) {
            esc = 1;
            continue;
          }
    
          if (c == CTRL('C')) {
            _write(sockfd, "\a", 1);
            continue;
          }
    
          // Back word, backspace/delete
          if (c == CTRL('W') || c == CTRL('H') || c == 0x7f) {
            int back = 0;
    
            if (c == CTRL('W')) {
              // Word
              int nc = cursor;
    
              if (l == 0 || cursor == 0) continue;
    
              while (nc && cmd[nc - 1] == ' ') {
                nc--;
                back++;
              }
    
              while (nc && cmd[nc - 1] != ' ') {
                nc--;
                back++;
              }
            } else {
              // Char
              if (l == 0 || cursor == 0) {
                _write(sockfd, "\a", 1);
                continue;
              }
    
              back = 1;
            }
    
            if (back) {
              while (back--) {
                if (l == cursor) {
                  cmd[--cursor] = 0;
                  if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) _write(sockfd, "\b \b", 3);
                } else {
                  int i;
                  if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) {
                    // Back up one space, then write current buffer followed by a space
                    _write(sockfd, "\b", 1);
                    _write(sockfd, cmd + cursor, l - cursor);
                    _write(sockfd, " ", 1);
    
                    // Move everything one char left
                    memmove(cmd + cursor - 1, cmd + cursor, l - cursor);
    
                    // Set former last char to null
                    cmd[l - 1] = 0;
    
                    // And reposition cursor
                    for (i = l; i >= cursor; i--) _write(sockfd, "\b", 1);
                  }
                  cursor--;
                }
                l--;
              }
    
              continue;
            }
          }
    
          // Redraw
          if (c == CTRL('L')) {
            int i;
            int cursorback = l - cursor;
    
            if (cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD) continue;
    
            _write(sockfd, "\r\n", 2);
            show_prompt(cli, sockfd);
            _write(sockfd, cmd, l);
    
            for (i = 0; i < cursorback; i++) _write(sockfd, "\b", 1);
    
            continue;
          }
    
          // Clear line
          if (c == CTRL('U')) {
            if (cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD)
              memset(cmd, 0, l);
            else
              cli_clear_line(sockfd, cmd, l, cursor);
    
            l = cursor = 0;
            continue;
          }
    
          // Kill to EOL
          if (c == CTRL('K')) {
            if (cursor == l) continue;
    
            if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) {
              int cptr;
              for (cptr = cursor; cptr < l; cptr++) _write(sockfd, " ", 1);
    
              for (cptr = cursor; cptr < l; cptr++) _write(sockfd, "\b", 1);
            }
    
            memset(cmd + cursor, 0, l - cursor);
            l = cursor;
            continue;
          }
    
          // EOT
          if (c == CTRL('D')) {
            if (cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD) break;
    
            if (l) continue;
    
            l = -1;
            break;
          }
    
          // Disable
          if (c == CTRL('Z')) {
            if (cli->mode != MODE_EXEC) {
              if (cli->buildmode) cli_int_free_buildmode(cli);
              cli_clear_line(sockfd, cmd, l, cursor);
              cli_set_configmode(cli, MODE_EXEC, NULL);
              l = cursor = 0;
              cli->showprompt = 1;
            }
    
            continue;
          }
    
          // TAB completion
          if (c == CTRL('I')) {
            struct cli_comphelp comphelp = {0};
    
            if (cli->state == STATE_LOGIN || cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD) continue;
            if (cursor != l) continue;
    
            cli_get_completions(cli, cmd, c, &comphelp);
            if (comphelp.num_entries == 0) {
              _write(sockfd, "\a", 1);
            } else if (lastchar == CTRL('I')) {
              // Double tab
              int i;
              for (i = 0; i < comphelp.num_entries; i++) {
                if (i % 4 == 0)
                  _write(sockfd, "\r\n", 2);
                else
                  _write(sockfd, " ", 1);
                _write(sockfd, comphelp.entries[i], strlen(comphelp.entries[i]));
              }
              _write(sockfd, "\r\n", 2);
              cli->showprompt = 1;
            } else if (comphelp.num_entries == 1) {
              // Single completion - show *unless* the optional/required 'prefix' is present
              if (comphelp.entries[0][0] != '[' && comphelp.entries[0][0] != '<') {
                for (; l > 0; l--, cursor--) {
                  if (cmd[l - 1] == ' ' || cmd[l - 1] == '|' || (comphelp.comma_separated && cmd[l - 1] == ',')) break;
                  _write(sockfd, "\b", 1);
                }
                strcpy((cmd + l), comphelp.entries[0]);
                l += strlen(comphelp.entries[0]);
                cmd[l++] = ' ';
                cursor = l;
                _write(sockfd, comphelp.entries[0], strlen(comphelp.entries[0]));
                _write(sockfd, " ", 1);
                // And now forget the tab, since we just found a single match
                lastchar = '\0';
              } else {
                // Yes, we had a match, but it wasn't required - remember the tab in case the user double tabs....
                lastchar = CTRL('I');
              }
            } else if (comphelp.num_entries > 1) {
              /*
               * More than one completion.
               * Show as many characters as we can until the completions start to differ.
               */
              lastchar = c;
              int i, j, k = 0;
              char *tptr = comphelp.entries[0];
    
              /*
               * Quickly try to see where our entries differ.
               * Corner cases:
               * - If all entries are optional, don't show *any* options unless user has provided a letter.
               * - If any entry starts with '<' then don't fill in anything.
               */
    
              // Skip a leading '['
              k = strlen(tptr);
              if (*tptr == '[')
                tptr++;
              else if (*tptr == '<')
                k = 0;
    
              for (i = 1; k != 0 && i < comphelp.num_entries; i++) {
                char *wptr = comphelp.entries[i];
    
                if (*wptr == '[')
                  wptr++;
                else if (*wptr == '<')
                  k = 0;
    
                for (j = 0; (j < k) && (j < (int)strlen(wptr)); j++) {
                  if (strncasecmp(tptr + j, wptr + j, 1)) break;
                }
                k = j;
              }
    
              // Try to show minimum match string if we have a non-zero k and the first letter of the last word is not '['.
              if (k && comphelp.entries[comphelp.num_entries - 1][0] != '[') {
                for (; l > 0; l--, cursor--) {
                  if (cmd[l - 1] == ' ' || cmd[l - 1] == '|' || (comphelp.comma_separated && cmd[l - 1] == ',')) break;
                  _write(sockfd, "\b", 1);
                }
                strncpy(cmd + l, tptr, k);
                l += k;
                cursor = l;
                _write(sockfd, tptr, k);
    
              } else {
                _write(sockfd, "\a", 1);
              }
            }
            cli_free_comphelp(&comphelp);
            continue;
          }
    
          // '?' at end of line - generate applicable 'help' messages
          if (c == '?' && cursor == l) {
            struct cli_comphelp comphelp = {0};
            int i;
            int show_cr = 1;
    
            if (cli->state == STATE_LOGIN || cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD) continue;
            if (cursor != l) continue;
    
            cli_get_completions(cli, cmd, c, &comphelp);
            if (comphelp.num_entries == 0) {
              _write(sockfd, "\a", 1);
            } else if (comphelp.num_entries > 0) {
              cli->showprompt = 1;
              _write(sockfd, "\r\n", 2);
              for (i = 0; i < (int)comphelp.num_entries; i++) {
                if (comphelp.entries[i][2] != '[') show_cr = 0;
                cli_error(cli, "%s", comphelp.entries[i]);
              }
              if (show_cr) cli_error(cli, "  <cr>");
            }
    
            cli_free_comphelp(&comphelp);
    
            if (comphelp.num_entries >= 0) continue;
          }
    
          // History
          if (c == CTRL('P') || c == CTRL('N')) {
            int history_found = 0;
    
            if (cli->state == STATE_LOGIN || cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD) continue;
    
            if (c == CTRL('P')) {
              // Up
              in_history--;
              if (in_history < 0) {
                for (in_history = MAX_HISTORY - 1; in_history >= 0; in_history--) {
                  if (cli->history[in_history]) {
                    history_found = 1;
                    break;
                  }
                }
              } else {
                if (cli->history[in_history]) history_found = 1;
              }
            } else {
              // Down
              in_history++;
              if (in_history >= MAX_HISTORY || !cli->history[in_history]) {
                int i = 0;
                for (i = 0; i < MAX_HISTORY; i++) {
                  if (cli->history[i]) {
                    in_history = i;
                    history_found = 1;
                    break;
                  }
                }
              } else {
                if (cli->history[in_history]) history_found = 1;
              }
            }
            if (history_found && cli->history[in_history]) {
              // Show history item
              cli_clear_line(sockfd, cmd, l, cursor);
              memset(cmd, 0, CLI_MAX_LINE_LENGTH);
              strncpy(cmd, cli->history[in_history], CLI_MAX_LINE_LENGTH - 1);
              l = cursor = strlen(cmd);
              _write(sockfd, cmd, l);
            }
    
            continue;
          }
    
          // Left/right cursor motion
          if (c == CTRL('B') || c == CTRL('F')) {
            if (c == CTRL('B')) {
              // Left
              if (cursor) {
                if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) _write(sockfd, "\b", 1);
    
                cursor--;
              }
            } else {
              // Right
              if (cursor < l) {
                if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) _write(sockfd, &cmd[cursor], 1);
    
                cursor++;
              }
            }
    
            continue;
          }
    
          if (c == CTRL('A')) {
            // Start of line
            if (cursor) {
              if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) {
                _write(sockfd, "\r", 1);
                show_prompt(cli, sockfd);
              }
    
              cursor = 0;
            }
    
            continue;
          }
    
          if (c == CTRL('E')) {
            // End of line
            if (cursor < l) {
              if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD)
                _write(sockfd, &cmd[cursor], l - cursor);
    
              cursor = l;
            }
    
            continue;
          }
    
          if (cursor == l) {
            // Normal character typed.
            // Append to end of line if not at end-of-buffer.
            if (l < CLI_MAX_LINE_LENGTH - 1) {
              cmd[cursor] = c;
              l++;
              cursor++;
            } else {
              // End-of-buffer, ensure null terminated
              cmd[cursor] = 0;
              _write(sockfd, "\a", 1);
              continue;
            }
          } else {
            // Middle of text
            int i;
            // Move everything one character to the right
            memmove(cmd + cursor + 1, cmd + cursor, l - cursor);
    
            // Insert new character
            cmd[cursor] = c;
    
            // IMPORTANT - if at end of buffer, set last char to NULL and don't change length, otherwise bump length by 1
            if (l == CLI_MAX_LINE_LENGTH - 1) {
              cmd[l] = 0;
            } else {
              l++;
            }
    
            // Write buffer, then backspace to where we were
            _write(sockfd, cmd + cursor, l - cursor);
    
            for (i = 0; i < (l - cursor); i++) _write(sockfd, "\b", 1);
            cursor++;
          }
    
          if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) {
            if (c == '?' && cursor == l) {
              _write(sockfd, "\r\n", 2);
              oldcmd = cmd;
              oldl = cursor = l - 1;
              break;
            }
            _write(sockfd, &c, 1);
          }
    
          oldcmd = 0;
          oldl = 0;
          lastchar = c;
        }
    
        if (l < 0) break;
    
        if (cli->state == STATE_LOGIN) {
          if (l == 0) continue;
    
          // Require login
          free_z(username);
          if (!(username = strdup(cmd))) return 0;
          cli->state = STATE_PASSWORD;
          cli->showprompt = 1;
        } else if (cli->state == STATE_PASSWORD) {
          // Require password
          int allowed = 0;
    
          free_z(password);
          if (!(password = strdup(cmd))) return 0;
          if (cli->auth_callback) {
            if (cli->auth_callback(username, password) == CLI_OK) allowed++;
          }
    
          if (!allowed) {
            struct unp *u;
            for (u = cli->users; u; u = u->next) {
              if (!strcmp(u->username, username) && pass_matches(u->password, password)) {
                allowed++;
                break;
              }
            }
          }
    
          if (allowed) {
            cli_error(cli, " ");
            cli->state = STATE_NORMAL;
          } else {
            cli_error(cli, "\n\nAccess denied");
            free_z(username);
            free_z(password);
            cli->state = STATE_LOGIN;
          }
    
          cli->showprompt = 1;
        } else if (cli->state == STATE_ENABLE_PASSWORD) {
          int allowed = 0;
          if (cli->enable_password) {
            // Check stored static enable password
            if (pass_matches(cli->enable_password, cmd)) allowed++;
          }
    
          if (!allowed && cli->enable_callback) {
            // Check callback
            if (cli->enable_callback(cmd)) allowed++;
          }
    
          if (allowed) {
            cli->state = STATE_ENABLE;
            cli_set_privilege(cli, PRIVILEGE_PRIVILEGED);
          } else {
            cli_error(cli, "\n\nAccess denied");
            cli->state = STATE_NORMAL;
          }
        } else {
          int rc;
          if (l == 0) continue;
          if (cmd[l - 1] != '?' && strcasecmp(cmd, "history") != 0) cli_add_history(cli, cmd);
    
          rc = cli_run_command(cli, cmd);
          switch (rc) {
            case CLI_BUILDMODE_ERROR:
              // Unable to enter buildmode successfully
              cli_print(cli, "Failure entering build mode for '%s'", cli->buildmode->cname);
              cli_int_free_buildmode(cli);
              continue;
            case CLI_BUILDMODE_CANCEL:
              // Called if user enters 'cancel'
              cli_print(cli, "Canceling build mode for '%s'", cli->buildmode->cname);
              cli_int_free_buildmode(cli);
              break;
            case CLI_BUILDMODE_EXIT:
              // Called when user enters exit - rebuild *entire* command line.
              // Recall all located optargs
              cli->found_optargs = cli->buildmode->found_optargs;
              rc = cli_int_execute_buildmode(cli);
              break;
            case CLI_QUIT:
              break;
            case CLI_BUILDMODE_START:
            case CLI_BUILDMODE_EXTEND:
            default:
              break;
          }
    
          // Process is done if we get a CLI_QUIT,
          if (rc == CLI_QUIT) break;
        }
    
        // Update the last_action time now as the last command run could take a long time to return
        if (cli->idle_timeout) time(&cli->last_action);
      }
    
      cli_free_history(cli);
      free_z(username);
      free_z(password);
      free_z(cmd);
    
      fclose(cli->client);
      cli->client = 0;
      return CLI_OK;
    }
    
    int cli_file(struct cli_def *cli, FILE *fh, int privilege, int mode) {
      int oldpriv = cli_set_privilege(cli, privilege);
      int oldmode = cli_set_configmode(cli, mode, NULL);
      char buf[CLI_MAX_LINE_LENGTH];
    
      while (1) {
        char *p;
        char *cmd;
        char *end;
    
        // End of file
        if (fgets(buf, CLI_MAX_LINE_LENGTH - 1, fh) == NULL) break;
    
        if ((p = strpbrk(buf, "#\r\n"))) *p = 0;
    
        cmd = buf;
        while (isspace(*cmd)) cmd++;
    
        if (!*cmd) continue;
    
        for (p = end = cmd; *p; p++)
          if (!isspace(*p)) end = p;
    
        *++end = 0;
        if (strcasecmp(cmd, "quit") == 0) break;
    
        if (cli_run_command(cli, cmd) == CLI_QUIT) break;
      }
    
      cli_set_privilege(cli, oldpriv);
      cli_set_configmode(cli, oldmode, NULL);
    
      return CLI_OK;
    }
    
    static void _print(struct cli_def *cli, int print_mode, const char *format, va_list ap) {
      int n;
      char *p = NULL;
    
      if (!cli) return;
    
      n = vasprintf(&p, format, ap);
      if (n < 0) return;
      if (cli->buffer) free(cli->buffer);
      cli->buffer = p;
      cli->buf_size = n;
    
      p = cli->buffer;
      do {
        char *next = strchr(p, '\n');
        struct cli_filter *f = (print_mode & PRINT_FILTERED) ? cli->filters : 0;
        int print = 1;
    
        if (next)
          *next++ = 0;
        else if (print_mode & PRINT_BUFFERED)
          break;
    
        while (print && f) {
          print = (f->filter(cli, p, f->data) == CLI_OK);
          f = f->next;
        }
        if (print) {
          if (cli->print_callback)
            cli->print_callback(cli, p);
          else if (cli->client)
            fprintf(cli->client, "%s\r\n", p);
        }
    
        p = next;
      } while (p);
    
      if (p && *p) {
        if (p != cli->buffer) memmove(cli->buffer, p, strlen(p));
      } else
        *cli->buffer = 0;
    }
    
    void cli_bufprint(struct cli_def *cli, const char *format, ...) {
      va_list ap;
    
      va_start(ap, format);
      _print(cli, PRINT_BUFFERED | PRINT_FILTERED, format, ap);
      va_end(ap);
    }
    
    void cli_vabufprint(struct cli_def *cli, const char *format, va_list ap) {
      _print(cli, PRINT_BUFFERED, format, ap);
    }
    
    void cli_print(struct cli_def *cli, const char *format, ...) {
      va_list ap;
    
      va_start(ap, format);
      _print(cli, PRINT_FILTERED, format, ap);
      va_end(ap);
    }
    
    void cli_error(struct cli_def *cli, const char *format, ...) {
      va_list ap;
    
      va_start(ap, format);
      _print(cli, PRINT_PLAIN, format, ap);
      va_end(ap);
    }
    
    struct cli_match_filter_state {
      int flags;
      union {
        char *string;
        regex_t re;
      } match;
    };
    
    int cli_search_flags_validator(struct cli_def *cli, const char *word, const char *value) {
      // Valid search flags starts with a hyphen, then any number of i, v, or e characters.
    
      if ((*value++ == '-') && (*value) && (strspn(value, "vie") == strlen(value))) return CLI_OK;
      return CLI_ERROR;
    }
    
    int cli_match_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt) {
      struct cli_match_filter_state *state;
      char *search_pattern = cli_get_optarg_value(cli, "search_pattern", NULL);
      char *search_flags = cli_get_optarg_value(cli, "search_flags", NULL);
    
      filt->filter = cli_match_filter;
      filt->data = state = calloc(sizeof(struct cli_match_filter_state), 1);
      if (!state) return CLI_ERROR;
    
      if (!strcmp(cli->pipeline->current_stage->words[0], "include")) {
        state->match.string = search_pattern;
      } else if (!strcmp(cli->pipeline->current_stage->words[0], "exclude")) {
        state->match.string = search_pattern;
        state->flags = MATCH_INVERT;
    #ifndef WIN32
      } else {
        int rflags = REG_NOSUB;
        if (!strcmp(cli->pipeline->current_stage->words[0], "grep")) {
          state->flags = MATCH_REGEX;
        } else if (!strcmp(cli->pipeline->current_stage->words[0], "egrep")) {
          state->flags = MATCH_REGEX;
          rflags |= REG_EXTENDED;
        }
        if (search_flags) {
          char *p = search_flags++;
          while (*p) {
            switch (*p++) {
              case 'v':
                state->flags |= MATCH_INVERT;
                break;
    
              case 'i':
                rflags |= REG_ICASE;
                break;
    
              case 'e':
                // Implies next term is search string, so stop processing flags
                break;
            }
          }
        }
        if (regcomp(&state->match.re, search_pattern, rflags)) {
          if (cli->client) fprintf(cli->client, "Invalid pattern \"%s\"\r\n", search_pattern);
          return CLI_ERROR;
        }
      }
    #else
      } else {
        // No regex functions in windows, so return an error.
        return CLI_ERROR;
      }
    #endif
    
      return CLI_OK;
    }
    
    int cli_match_filter(UNUSED(struct cli_def *cli), const char *string, void *data) {
      struct cli_match_filter_state *state = data;
      int r = CLI_ERROR;
    
      if (!string) {
        if (state->flags & MATCH_REGEX) regfree(&state->match.re);
    
        free(state);
        return CLI_OK;
      }
    
      if (state->flags & MATCH_REGEX) {
        if (!regexec(&state->match.re, string, 0, NULL, 0)) r = CLI_OK;
      } else {
        if (strstr(string, state->match.string)) r = CLI_OK;
      }
    
      if (state->flags & MATCH_INVERT) {
        if (r == CLI_OK)
          r = CLI_ERROR;
        else
          r = CLI_OK;
      }
    
      return r;
    }
    
    struct cli_range_filter_state {
      int matched;
      char *from;
      char *to;
    };
    
    int cli_range_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt) {
      struct cli_range_filter_state *state;
      char *from = cli_get_optarg_value(cli, "range_start", NULL);
      char *to = cli_get_optarg_value(cli, "range_end", NULL);
    
      // Do not have to check from/to since we would not have gotten here if we were missing a required argument.
      // Note that since those pointers are not NULL, we don't have to strdup them or free them - just use the pointer
      //    from the command line processing and continue
    
      filt->filter = cli_range_filter;
      filt->data = state = calloc(sizeof(struct cli_range_filter_state), 1);
      if (state) {
        state->from = from;
        state->to = to;
        return CLI_OK;
      } else {
        return CLI_ERROR;
      }
    }
    
    int cli_range_filter(UNUSED(struct cli_def *cli), const char *string, void *data) {
      struct cli_range_filter_state *state = data;
      int r = CLI_ERROR;
    
      if (!string) {
        free_z(state);
        return CLI_OK;
      }
    
      if (!state->matched) state->matched = !!strstr(string, state->from);
    
      if (state->matched) {
        r = CLI_OK;
        if (state->to && strstr(string, state->to)) state->matched = 0;
      }
    
      return r;
    }
    
    int cli_count_filter_init(struct cli_def *cli, int argc, UNUSED(char **argv), struct cli_filter *filt) {
      if (argc > 1) {
        if (cli->client) fprintf(cli->client, "Count filter does not take arguments\r\n");
    
        return CLI_ERROR;
      }
    
      filt->filter = cli_count_filter;
      if (!(filt->data = calloc(sizeof(int), 1))) return CLI_ERROR;
    
      return CLI_OK;
    }
    
    int cli_count_filter(struct cli_def *cli, const char *string, void *data) {
      int *count = data;
    
      if (!string) {
        // Print count
        if (cli->client) fprintf(cli->client, "%d\r\n", *count);
    
        free(count);
        return CLI_OK;
      }
    
      while (isspace(*string)) string++;
    
      // Only count non-blank lines
      if (*string) (*count)++;
    
      return CLI_ERROR;
    }
    
    void cli_print_callback(struct cli_def *cli, void (*callback)(struct cli_def *, const char *)) {
      cli->print_callback = callback;
    }
    
    void cli_set_idle_timeout(struct cli_def *cli, unsigned int seconds) {
      if (seconds < 1) seconds = 0;
      cli->idle_timeout = seconds;
      time(&cli->last_action);
    }
    
    void cli_set_idle_timeout_callback(struct cli_def *cli, unsigned int seconds, int (*callback)(struct cli_def *)) {
      cli_set_idle_timeout(cli, seconds);
      cli->idle_timeout_callback = callback;
    }
    
    void cli_telnet_protocol(struct cli_def *cli, int telnet_protocol) {
      cli->telnet_protocol = !!telnet_protocol;
    }
    void cli_set_context(struct cli_def *cli, void *context) {
      cli->user_context = context;
    }
    
    void *cli_get_context(struct cli_def *cli) {
      return cli->user_context;
    }
    
    struct cli_command *cli_register_filter(struct cli_def *cli, const char *command,
                                            int (*init)(struct cli_def *cli, int, char **, struct cli_filter *),
                                            int (*filter)(struct cli_def *, const char *, void *), int privilege, int mode,
                                            const char *help) {
      struct cli_command *c;
    
      if (!command) return NULL;
      if (!(c = calloc(sizeof(struct cli_command), 1))) return NULL;
    
      c->command_type = CLI_FILTER_COMMAND;
      c->init = init;
      c->filter = filter;
      c->next = NULL;
      if (!(c->command = strdup(command))) {
        free(c);
        return NULL;
      }
    
      c->privilege = privilege;
      c->mode = mode;
      if (help && !(c->help = strdup(help))) {
        free(c->command);
        free(c);
        return NULL;
      }
    
      // Filters are all registered at the top level.
      return cli_register_command_core(cli, NULL, c);
    }
    
    int cli_unregister_filter(struct cli_def *cli, const char *command) {
      return cli_int_unregister_command_core(cli, command, CLI_FILTER_COMMAND);
    }
    
    void cli_int_free_found_optargs(struct cli_optarg_pair **optarg_pair) {
      struct cli_optarg_pair *c;
    
      if (!optarg_pair || !*optarg_pair) return;
    
      for (c = *optarg_pair; c;) {
        *optarg_pair = c->next;
        free_z(c->name);
        free_z(c->value);
        free_z(c);
        c = *optarg_pair;
      }
    }
    
    char *cli_find_optarg_value(struct cli_def *cli, char *name, char *find_after) {
      char *value = NULL;
      struct cli_optarg_pair *optarg_pair;
      if (!name || !cli->found_optargs) return NULL;
    
      for (optarg_pair = cli->found_optargs; optarg_pair && !value; optarg_pair = optarg_pair->next) {
        if (strcmp(optarg_pair->name, name) == 0) {
          if (find_after && find_after == optarg_pair->value) {
            find_after = NULL;
            continue;
          }
          value = optarg_pair->value;
        }
      }
      return value;
    }
    
    static void cli_optarg_build_shortest(struct cli_optarg *optarg) {
      struct cli_optarg *c, *p;
      char *cp, *pp;
      unsigned int len;
    
      for (c = optarg; c; c = c->next) {
        c->unique_len = 1;
        for (p = optarg; p; p = p->next) {
          if (c == p) continue;
          cp = c->name;
          pp = p->name;
          len = 1;
          while (*cp && *pp && *cp++ == *pp++) len++;
          if (len > c->unique_len) c->unique_len = len;
        }
      }
    }
    
    void cli_free_optarg(struct cli_optarg *optarg) {
      free_z(optarg->help);
      free_z(optarg->name);
      free_z(optarg);
    }
    
    int cli_optarg_addhelp(struct cli_optarg *optarg, const char *helpname, const char *helptext) {
      char *tstr;
    
      // put a vertical tab (\v), the new helpname, a horizontal tab (\t), and then the new help text
      if ((!optarg) || (asprintf(&tstr, "%s\v%s\t%s", optarg->help, helpname, helptext) == -1)) {
        return CLI_ERROR;
      } else {
        free(optarg->help);
        optarg->help = tstr;
      }
      return CLI_OK;
    }
    
    struct cli_optarg *cli_register_optarg(struct cli_command *cmd, const char *name, int flags, int privilege, int mode,
                                           const char *help,
                                           int (*get_completions)(struct cli_def *cli, const char *, const char *,
                                                                  struct cli_comphelp *),
                                           int (*validator)(struct cli_def *cli, const char *, const char *),
                                           int (*transient_mode)(struct cli_def *cli, const char *, const char *)) {
      struct cli_optarg *optarg = NULL;
      struct cli_optarg *lastopt = NULL;
      struct cli_optarg *ptr = NULL;
      int retval = CLI_ERROR;
    
      // Name must not already exist with this priv/mode
      for (ptr = cmd->optargs, lastopt = NULL; ptr; lastopt = ptr, ptr = ptr->next) {
        if (!strcmp(name, ptr->name) && ptr->mode == mode && ptr->privilege == privilege) {
          goto CLEANUP;
        }
      }
      if (!(optarg = calloc(sizeof(struct cli_optarg), 1))) goto CLEANUP;
      if (!(optarg->name = strdup(name))) goto CLEANUP;
      if (help && !(optarg->help = strdup(help))) goto CLEANUP;
    
      optarg->mode = mode;
      optarg->privilege = privilege;
      optarg->get_completions = get_completions;
      optarg->validator = validator;
      optarg->transient_mode = transient_mode;
      optarg->flags = flags;
    
      if (lastopt)
        lastopt->next = optarg;
      else
        cmd->optargs = optarg;
      cli_optarg_build_shortest(cmd->optargs);
      retval = CLI_OK;
    
    CLEANUP:
      if (retval != CLI_OK) {
        cli_free_optarg(optarg);
        optarg = NULL;
      }
      return optarg;
    }
    
    int cli_unregister_optarg(struct cli_command *cmd, const char *name) {
      struct cli_optarg *ptr;
      struct cli_optarg *lastptr;
      int retval = CLI_ERROR;
      // Iterate looking for this option name, stopping at end or if name matches
      for (lastptr = NULL, ptr = cmd->optargs; ptr && strcmp(ptr->name, name); lastptr = ptr, ptr = ptr->next)
        ;
    
      // If ptr, then we found the optarg to delete
      if (ptr) {
        if (lastptr) {
          // Not first optarg
          lastptr->next = ptr->next;
          ptr->next = NULL;
        } else {
          // First optarg
          cmd->optargs = ptr->next;
          ptr->next = NULL;
        }
        cli_free_optarg(ptr);
        cli_optarg_build_shortest(cmd->optargs);
        retval = CLI_OK;
      }
      return retval;
    }
    
    void cli_unregister_all_optarg(struct cli_command *c) {
      struct cli_optarg *o, *p;
    
      for (o = c->optargs; o; o = p) {
        p = o->next;
        cli_free_optarg(o);
      }
    }
    
    void cli_int_unset_optarg_value(struct cli_def *cli, const char *name) {
      struct cli_optarg_pair **p, *c;
      for (p = &cli->found_optargs, c = *p; *p;) {
        c = *p;
    
        if (!strcmp(c->name, name)) {
          *p = c->next;
          free_z(c->name);
          free_z(c->value);
          free_z(c);
        } else {
          p = &(*p)->next;
        }
      }
    }
    
    int cli_set_optarg_value(struct cli_def *cli, const char *name, const char *value, int allow_multiple) {
      struct cli_optarg_pair *optarg_pair, **anchor;
      int rc = CLI_ERROR;
    
      for (optarg_pair = cli->found_optargs, anchor = &cli->found_optargs; optarg_pair;
           anchor = &optarg_pair->next, optarg_pair = optarg_pair->next) {
        // Break if we found this name *and* allow_multiple is false
        if (!strcmp(optarg_pair->name, name) && !allow_multiple) {
          break;
        }
      }
      // If we *didn't* find this, then allocate a new entry before proceeding
      if (!optarg_pair) {
        optarg_pair = (struct cli_optarg_pair *)calloc(1, sizeof(struct cli_optarg_pair));
        *anchor = optarg_pair;
      }
      // Set the value
      if (optarg_pair) {
        // Name is null only if we didn't find it
        if (!optarg_pair->name) optarg_pair->name = strdup(name);
    
        // Value may be overwritten, so free any old value.
        if (optarg_pair->value) free_z(optarg_pair->value);
        optarg_pair->value = strdup(value);
    
        rc = CLI_OK;
      }
      return rc;
    }
    
    struct cli_optarg_pair *cli_get_all_found_optargs(struct cli_def *cli) {
      if (cli) return cli->found_optargs;
      return NULL;
    }
    
    char *cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_after) {
      char *value = NULL;
      struct cli_optarg_pair *optarg_pair;
    
      for (optarg_pair = cli->found_optargs; !value && optarg_pair; optarg_pair = optarg_pair->next) {
        // Check next entry if this isn't our name
        if (strcasecmp(optarg_pair->name, name)) continue;
    
        // Did we have a find_after, then ignore anything up until our find_after match
        if (find_after && optarg_pair->value == find_after) {
          find_after = NULL;
          continue;
        } else if (!find_after) {
          value = optarg_pair->value;
        }
      }
      return value;
    }
    
    void cli_int_free_buildmode(struct cli_def *cli) {
      if (!cli || !cli->buildmode) return;
      cli_unregister_tree(cli, cli->commands, CLI_BUILDMODE_COMMAND);
      cli->mode = cli->buildmode->mode;
      free_z(cli->buildmode->mode_text);
      cli_int_free_found_optargs(&cli->buildmode->found_optargs);
      free_z(cli->buildmode);
    }
    
    int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stage, char *mode_text) {
      struct cli_optarg *optarg;
      struct cli_command *c;
      struct cli_optarg *o;
      struct cli_buildmode *buildmode;
      struct cli_optarg *buildmodeOptarg = NULL;
      int rc = CLI_BUILDMODE_START;
    
      if (!(buildmode = (struct cli_buildmode *)calloc(1, sizeof(struct cli_buildmode)))) {
        cli_error(cli, "Unable to build buildmode mode for command %s", stage->command->command);
        rc = CLI_BUILDMODE_ERROR;
        goto out;
      }
    
      // Clean up any shrapnel from earlier - shouldn't be any but....
      if (cli->buildmode) cli_int_free_buildmode(cli);
    
      // Assign it so cli_int_register_buildmode_command() has something to work with
      cli->buildmode = buildmode;
      cli->buildmode->mode = cli->mode;
      cli->buildmode->transient_mode = cli->transient_mode;
      if (mode_text) cli->buildmode->mode_text = strdup(mode_text);
    
      // Need this to verify we have all *required* arguments
      cli->buildmode->command = stage->command;
    
      // Build new *limited* list of commands from this commands optargs
      // Currently we only allow a single entry point to a buildmode, so advance t that
      // optarg and proceed from there.
      for (buildmodeOptarg = stage->command->optargs;
           buildmodeOptarg && !(buildmodeOptarg->flags & CLI_CMD_ALLOW_BUILDMODE); buildmodeOptarg = buildmodeOptarg->next)
        ;
    
      // Now start at this argument and flesh out the rest of the commands available for this buildmode
      for (optarg = buildmodeOptarg; optarg; optarg = optarg->next) {
        // Don't allow anything that could redefine our mode or buildmode mode, or redefine exit/cancel/show/unset
        if (!strcmp(optarg->name, "cancel") || !strcmp(optarg->name, "execute") || !strcmp(optarg->name, "show") ||
            !strcmp(optarg->name, "unset")) {
          cli_error(cli, "Default buildmode command conflicts with optarg named %s", optarg->name);
          rc = CLI_BUILDMODE_ERROR;
          goto out;
        }
        if (optarg->flags & (CLI_CMD_ALLOW_BUILDMODE | CLI_CMD_TRANSIENT_MODE | CLI_CMD_SPOT_CHECK)) continue;
        // accept the first optarg allowing buildmode, but reject any subsequent one
        if (optarg->flags & CLI_CMD_ALLOW_BUILDMODE && (optarg != buildmodeOptarg)) continue;
        if (optarg->mode != cli->mode && optarg->mode != cli->transient_mode)
          continue;
        else if (optarg->flags & (CLI_CMD_OPTIONAL_ARGUMENT | CLI_CMD_ARGUMENT)) {
          if ((c = cli_int_register_buildmode_command(cli, NULL, optarg->name, cli_int_buildmode_cmd_cback, optarg->flags,
                                                      optarg->privilege, cli->mode, optarg->help))) {
            cli_register_optarg(c, optarg->name, CLI_CMD_ARGUMENT | (optarg->flags & CLI_CMD_OPTION_MULTIPLE),
                                optarg->privilege, cli->mode, optarg->help, optarg->get_completions, optarg->validator,
                                NULL);
          } else {
            rc = CLI_BUILDMODE_ERROR;
            goto out;
          }
        } else {
          if (optarg->flags & CLI_CMD_OPTION_MULTIPLE) {
            if (!cli_int_register_buildmode_command(cli, NULL, optarg->name, cli_int_buildmode_flag_multiple_cback,
                                                    optarg->flags, optarg->privilege, cli->mode, optarg->help)) {
              rc = CLI_BUILDMODE_ERROR;
              goto out;
            }
          } else {
            if (!cli_int_register_buildmode_command(cli, NULL, optarg->name, cli_int_buildmode_flag_cback, optarg->flags,
                                                    optarg->privilege, cli->mode, optarg->help)) {
              rc = CLI_BUILDMODE_ERROR;
              goto out;
            }
          }
        }
      }
      cli->buildmode->cname = cli_command_name(cli, stage->command);
      // Now add the four 'always there' commands to cancel current mode and to execute the command, show settings, and
      // unset
      c = cli_int_register_buildmode_command(cli, NULL, "cancel", cli_int_buildmode_cancel_cback, 0, PRIVILEGE_UNPRIVILEGED,
                                             cli->mode, "Cancel command");
      if (!c) {
        rc = CLI_BUILDMODE_ERROR;
        goto out;
      }
      c = cli_int_register_buildmode_command(cli, NULL, "execute", cli_int_buildmode_execute_cback, 0,
                                             PRIVILEGE_UNPRIVILEGED, cli->mode, "Execute command");
      if (!c) {
        rc = CLI_BUILDMODE_ERROR;
        goto out;
      }
      c = cli_int_register_buildmode_command(cli, NULL, "show", cli_int_buildmode_show_cback, 0, PRIVILEGE_UNPRIVILEGED,
                                             cli->mode, "Show current settings");
      if (!c) {
        rc = CLI_BUILDMODE_ERROR;
        goto out;
      }
      c = cli_int_register_buildmode_command(cli, NULL, "unset", cli_int_buildmode_unset_cback, 0, PRIVILEGE_UNPRIVILEGED,
                                             cli->mode, "Unset a setting");
      if (!c) {
        rc = CLI_BUILDMODE_ERROR;
        goto out;
      }
      o = cli_register_optarg(c, "setting", CLI_CMD_ARGUMENT | CLI_CMD_DO_NOT_RECORD, PRIVILEGE_UNPRIVILEGED, cli->mode,
                              "setting to clear", cli_int_buildmode_unset_completor, cli_int_buildmode_unset_validator,
                              NULL);
      if (!o) {
        rc = CLI_BUILDMODE_ERROR;
        goto out;
      }
    
    out:
      // And lastly set the initial help menu for the unset command
      cli_int_buildmode_reset_unset_help(cli);
      if (rc != CLI_BUILDMODE_START) cli_int_free_buildmode(cli);
      return rc;
    }
    
    int cli_int_unregister_buildmode_command(struct cli_def *cli, const char *command) {
      return cli_int_unregister_command_core(cli, command, CLI_BUILDMODE_COMMAND);
    }
    
    struct cli_command *cli_int_register_buildmode_command(struct cli_def *cli, struct cli_command *parent,
                                                           const char *command,
                                                           int (*callback)(struct cli_def *cli, const char *, char **, int),
                                                           int flags, int privilege, int mode, const char *help) {
      struct cli_command *c;
    
      if (!command) return NULL;
      if (!(c = calloc(sizeof(struct cli_command), 1))) return NULL;
    
      c->flags = flags;
      c->callback = callback;
      c->next = NULL;
      if (!(c->command = strdup(command))) {
        free(c);
        return NULL;
      }
    
      c->command_type = CLI_BUILDMODE_COMMAND;
      c->privilege = privilege;
      c->mode = mode;
      if (help && !(c->help = strndup(help, strchrnul(help, '\v') - help))) {
        free(c->command);
        free(c);
        return NULL;
      }
    
      // Buildmode commmands are all registered at the top level
      return cli_register_command_core(cli, NULL, c);
    }
    
    int cli_int_execute_buildmode(struct cli_def *cli) {
      struct cli_optarg *optarg = NULL;
      int rc = CLI_OK;
      char *cmdline;
      char *value = NULL;
    
      cmdline = strdup(cli_command_name(cli, cli->buildmode->command));
      if (!cmdline) {
        cli_error(cli, "Unable to allocate memory to process buildmode commandline");
        rc = CLI_ERROR;
      }
      for (optarg = cli->buildmode->command->optargs; rc == CLI_OK && optarg; optarg = optarg->next) {
        value = NULL;
        do {
          if (cli->privilege < optarg->privilege) continue;
          if ((optarg->mode != cli->buildmode->mode) && (optarg->mode != cli->buildmode->transient_mode) &&
              (optarg->mode != MODE_ANY))
            continue;
    
          value = cli_get_optarg_value(cli, optarg->name, value);
          if (!value && optarg->flags & CLI_CMD_ARGUMENT) {
            cli_error(cli, "Missing required argument %s", optarg->name);
            rc = CLI_MISSING_ARGUMENT;
          } else if (value) {
            if (optarg->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_ARGUMENT)) {
              if (!(cmdline = cli_int_buildmode_extend_cmdline(cmdline, value))) {
                cli_error(cli, "Unable to allocate memory to process buildmode commandline");
                rc = CLI_ERROR;
              }
            } else {
              if (!(cmdline = cli_int_buildmode_extend_cmdline(cmdline, optarg->name))) {
                cli_error(cli, "Unable to allocate memory to process buildmode commandline");
                rc = CLI_ERROR;
              }
              if (!(cmdline = cli_int_buildmode_extend_cmdline(cmdline, value))) {
                cli_error(cli, "Unable to allocate memory to process buildmode commandline");
                rc = CLI_ERROR;
              }
            }
          }
        } while (rc == CLI_OK && value && optarg->flags & CLI_CMD_OPTION_MULTIPLE);
      }
    
      if (rc == CLI_OK) {
        cli_int_free_buildmode(cli);
        cli_add_history(cli, cmdline);
        // disallow processing of buildmode so we don't wind up in a potential loop
        // main loop will also set as required
        cli->disallow_buildmode = 1;
        rc = cli_run_command(cli, cmdline);
      }
      free_z(cmdline);
      return rc;
    }
    
    char *cli_int_buildmode_extend_cmdline(char *cmdline, char *word) {
      char *tptr = NULL;
      char *cptr = NULL;
      size_t oldlen = strlen(cmdline);
      size_t wordlen = strlen(word);
      char quoteChar[2] = "";
    
      // by default we don't add quotes, but if the word is empty, we *must* to preserve that empty string
      // we also need to quote it if there is a space in the word, or any unescaped quotes, so we'll have
      // to walk the word....
      cptr = word;
      if (!wordlen) {
        quoteChar[0] = '"';
      }
      for (cptr = word; *cptr; cptr++) {
        if (*cptr == '\\' && *(cptr + 1)) {
          cptr++;  // skip over escapes blindly
        } else if ((*cptr == ' ') && (quoteChar[0] == '\0')) {
          // if we found a space we need quotes, select double unless we've already selected something
          quoteChar[0] = '"';
        } else if (*cptr == '"') {
          // if our first unescaped quote is a double, then we wrap the string in single quotes
          quoteChar[0] = '\'';
          break;
        } else if (*cptr == '\'') {
          // if our first unescaped quote is a single, then we wrap the string in double quotes
          quoteChar[0] = '"';
          break;
        }
      }
    
      // Allocate enough space to hold the old string, a space, possible quote, the new string,
      // another possible quote, and the final null terminator).
    
      if ((tptr = (char *)realloc(cmdline, oldlen + 1 + 1 + wordlen + 1 + 1))) {
        strcat(tptr, " ");
        strcat(tptr, quoteChar);
        strcat(tptr, word);
        strcat(tptr, quoteChar);
      }
      return tptr;
    }
    
    // Any time we set or unset a buildmode setting, we need to regerate the 'help' menu for the unset command
    void cli_int_buildmode_reset_unset_help(struct cli_def *cli) {
      struct cli_command *cmd;
    
      // find the buildmode unset command
      for (cmd = cli->commands; cmd; cmd = cmd->next) {
        if ((cmd->command_type == CLI_BUILDMODE_COMMAND) && !strcmp(cmd->command, "unset")) break;
      }
    
      if (cmd) {
        struct cli_optarg *optarg;
        for (optarg = cmd->optargs; optarg && strcmp(optarg->name, "setting"); optarg = optarg->next)
          ;
    
        if (optarg) {
          char *endOfMainHelp;
          struct cli_optarg_pair *optarg_pair;
          /*
           * This will ensure that any previously added help is not propogated - this left over space will be freed by the
           * cli_optarg_addhelp() calls a few lines down
           */
          if ((endOfMainHelp = strchr(optarg->help, '\v'))) *endOfMainHelp = '\0';
    
          for (optarg_pair = cli->found_optargs; optarg_pair; optarg_pair = optarg_pair->next) {
            // Only show vars that are also current 'commands'
            struct cli_command *c = cli->commands;
            for (; c; c = c->next) {
              if (c->command_type != CLI_BUILDMODE_COMMAND) continue;
              if (!strcmp(c->command, optarg_pair->name)) {
                char *tmphelp;
                if (asprintf(&tmphelp, "unset %s", optarg_pair->name) >= 0) {
                  cli_optarg_addhelp(optarg, optarg_pair->name, tmphelp);
                  free_z(tmphelp);
                }
              }
            }
          }
        }
      }
    }
    
    int cli_int_buildmode_cmd_cback(struct cli_def *cli, const char *command, char *argv[], int argc) {
      int rc = CLI_BUILDMODE_EXTEND;
    
      if (argc) {
        cli_error(cli, "Extra arguments on command line, command ignored.");
        rc = CLI_ERROR;
      }
      cli_int_buildmode_reset_unset_help(cli);
      return rc;
    }
    
    // A 'flag' callback has no optargs, so we need to set it ourself based on *this* command
    int cli_int_buildmode_flag_cback(struct cli_def *cli, const char *command, char *argv[], int argc) {
      int rc = CLI_BUILDMODE_EXTEND;
    
      if (argc) {
        cli_error(cli, "Extra arguments on command line, command ignored.");
        rc = CLI_ERROR;
      }
      if (cli_set_optarg_value(cli, command, command, 0)) {
        cli_error(cli, "Problem setting value for optional flag %s", command);
        rc = CLI_ERROR;
      }
      cli_int_buildmode_reset_unset_help(cli);
      return rc;
    }
    
    // A 'flag' callback has no optargs, so we need to set it ourself based on *this* command
    int cli_int_buildmode_flag_multiple_cback(struct cli_def *cli, const char *command, char *argv[], int argc) {
      int rc = CLI_BUILDMODE_EXTEND;
    
      if (argc) {
        cli_error(cli, "Extra arguments on command line, command ignored.");
        rc = CLI_ERROR;
      }
      if (cli_set_optarg_value(cli, command, command, CLI_CMD_OPTION_MULTIPLE)) {
        cli_error(cli, "Problem setting value for optional flag %s", command);
        rc = CLI_ERROR;
      }
    
      cli_int_buildmode_reset_unset_help(cli);
      return rc;
    }
    
    int cli_int_buildmode_cancel_cback(struct cli_def *cli, const char *command, char *argv[], int argc) {
      int rc = CLI_BUILDMODE_CANCEL;
    
      if (argc > 0) {
        cli_error(cli, "Extra arguments on command line, command ignored.");
        rc = CLI_ERROR;
      }
      return rc;
    }
    
    int cli_int_buildmode_execute_cback(struct cli_def *cli, const char *command, char *argv[], int argc) {
      int rc = CLI_BUILDMODE_EXIT;
    
      if (argc > 0) {
        cli_error(cli, "Extra arguments on command line, command ignored.");
        rc = CLI_ERROR;
      }
      return rc;
    }
    
    int cli_int_buildmode_show_cback(struct cli_def *cli, const char *command, char *argv[], int argc) {
      struct cli_optarg_pair *optarg_pair;
    
      for (optarg_pair = cli->found_optargs; optarg_pair; optarg_pair = optarg_pair->next) {
        // Only show vars that are also current 'commands'
        struct cli_command *c = cli->commands;
        for (; c; c = c->next) {
          if (c->command_type != CLI_BUILDMODE_COMMAND) continue;
          if (!strcmp(c->command, optarg_pair->name)) {
            cli_print(cli, "  %-20s = %s", optarg_pair->name, optarg_pair->value);
            break;
          }
        }
      }
      return CLI_OK;
    }
    
    int cli_int_buildmode_unset_cback(struct cli_def *cli, const char *command, char *argv[], int argc) {
      // Iterate over our 'set' variables to see if that variable is also a 'valid' command right now
      struct cli_command *c;
    
      // have to catch this one here due to how buildmode works
      if (!argv[0] || !*argv[0]) {
        cli_error(cli, "Incomplete command, missing required argument 'setting' for command  'unset'");
        return CLI_ERROR;
      }
      // Is this 'optarg' to remove one of the current commands?
      for (c = cli->commands; c; c = c->next) {
        if (c->command_type != CLI_BUILDMODE_COMMAND) continue;
        if (cli->privilege < c->privilege) continue;
        if ((cli->buildmode->mode != c->mode) && (cli->buildmode->transient_mode != c->mode) && (c->mode != MODE_ANY))
          continue;
        if (strcmp(c->command, argv[0])) continue;
        // Go fry anything by this name
    
        cli_int_unset_optarg_value(cli, argv[0]);
        cli_int_buildmode_reset_unset_help(cli);
        break;
      }
    
      return CLI_OK;
    }
    
    // Generate a list of variables that *have* been set
    int cli_int_buildmode_unset_completor(struct cli_def *cli, const char *name, const char *word,
                                          struct cli_comphelp *comphelp) {
      struct cli_optarg_pair *optarg_pair;
    
      for (optarg_pair = cli->found_optargs; optarg_pair; optarg_pair = optarg_pair->next) {
        // Only complete vars that could be set by current 'commands'
        struct cli_command *c = cli->commands;
        for (; c; c = c->next) {
          if (c->command_type != CLI_BUILDMODE_COMMAND) continue;
          if ((!strcmp(c->command, optarg_pair->name)) && (!word || !strncmp(word, optarg_pair->name, strlen(word)))) {
            cli_add_comphelp_entry(comphelp, optarg_pair->name);
          }
        }
      }
      return CLI_OK;
    }
    
    int cli_int_buildmode_unset_validator(struct cli_def *cli, const char *name, const char *value) {
      struct cli_optarg_pair *optarg_pair;
    
      if (!name || !*name) {
        cli_error(cli, "No setting given to unset");
        return CLI_ERROR;
      }
      for (optarg_pair = cli->found_optargs; optarg_pair; optarg_pair = optarg_pair->next) {
        // Only complete vars that could be set by current 'commands'
        struct cli_command *c = cli->commands;
        for (; c; c = c->next) {
          if (c->command_type != CLI_BUILDMODE_COMMAND) continue;
          if (!strcmp(c->command, optarg_pair->name) && value && !strcmp(optarg_pair->name, value)) {
            return CLI_OK;
          }
        }
      }
      return CLI_ERROR;
    }
    
    void cli_set_transient_mode(struct cli_def *cli, int transient_mode) {
      cli->transient_mode = transient_mode;
    }
    
    int cli_add_comphelp_entry(struct cli_comphelp *comphelp, const char *entry) {
      int retval = CLI_ERROR;
      if (comphelp && entry) {
        char *dupelement = strdup(entry);
        char **duparray = (char **)realloc((void *)comphelp->entries, sizeof(char *) * (comphelp->num_entries + 1));
        if (dupelement && duparray) {
          comphelp->entries = duparray;
          comphelp->entries[comphelp->num_entries++] = dupelement;
          retval = CLI_OK;
        } else {
          free_z(dupelement);
          free_z(duparray);
        }
      }
      return retval;
    }
    
    void cli_free_comphelp(struct cli_comphelp *comphelp) {
      if (comphelp) {
        int idx;
    
        for (idx = 0; idx < comphelp->num_entries; idx++) free_z(comphelp->entries[idx]);
        free_z(comphelp->entries);
      }
    }
    
    static int cli_int_locate_command(struct cli_def *cli, struct cli_command *commands, int command_type, int start_word,
                                      struct cli_pipeline_stage *stage) {
      struct cli_command *c, *again_config = NULL, *again_any = NULL;
      int c_words = stage->num_words;
    
      for (c = commands; c; c = c->next) {
        if (c->command_type != command_type) continue;
        if (cli->privilege < c->privilege) continue;
    
        if (strncasecmp(c->command, stage->words[start_word], c->unique_len)) continue;
        if (strncasecmp(c->command, stage->words[start_word], strlen(stage->words[start_word]))) continue;
    
      AGAIN:
        if (c->mode == cli->mode || (c->mode == MODE_ANY && again_any != NULL)) {
          int rc = CLI_OK;
    
          // Found a word!
          if (!c->children) {
            // Last word
            if (!c->callback && !c->filter) {
              cli_error(cli, "No callback for \"%s\"", cli_command_name(cli, c));
              return CLI_ERROR;
            }
          } else {
            if (start_word == c_words - 1) {
              if (c->callback) goto CORRECT_CHECKS;
    
              cli_error(cli, "Incomplete command");
              return CLI_ERROR;
            }
            rc = cli_int_locate_command(cli, c->children, command_type, start_word + 1, stage);
            if (rc == CLI_ERROR_ARG) {
              if (c->callback) {
                rc = CLI_OK;
                goto CORRECT_CHECKS;
              }
              // show the command from word 0 up until the 'bad' word at start_word+1
              cli_error(cli, "Invalid command \"%s %s\"", cli_command_name(cli, c), stage->words[start_word + 1]);
              return CLI_ERROR;
            }
            return rc;
          }
    
          if (!c->callback && !c->filter) {
            cli_error(cli, "Internal server error processing \"%s\"", cli_command_name(cli, c));
            return CLI_ERROR;
          }
    
        CORRECT_CHECKS:
    
          if (rc == CLI_OK) {
            stage->command = c;
            stage->first_unmatched = start_word + 1;
            stage->first_optarg = stage->first_unmatched;
            // cli_int_parse_optargs will display any detected errors...
            cli_int_parse_optargs(cli, stage, c, '\0', NULL);
            rc = stage->status;
          }
          return rc;
        } else if (cli->mode > MODE_CONFIG && c->mode == MODE_CONFIG) {
          // Command matched but from another mode, remember it if we fail to find correct command
          again_config = c;
        } else if (c->mode == MODE_ANY) {
          // Command matched but for any mode, remember it if we fail to find correct command
          again_any = c;
        }
      }
    
      // Drop out of config submode if we have matched command on MODE_CONFIG
      if (again_config) {
        c = again_config;
        cli_set_configmode(cli, MODE_CONFIG, NULL);
        goto AGAIN;
      }
      if (again_any) {
        c = again_any;
        goto AGAIN;
      }
    
      // display this if we matched against absolutely nothing....
      if (start_word == 0) cli_error(cli, "Invalid command \"%s\"", stage->words[start_word]);
    
      return CLI_ERROR_ARG;
    }
    
    int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) {
      int i;
      int rc = CLI_OK;
      int command_type;
    
      if (!pipeline) return CLI_ERROR;
      cli->pipeline = pipeline;
    
      cli->found_optargs = NULL;
    
      // If the line is totally empty this is not an error, but we need to return
      // CLI_ERROR to avoid processing it
      if (pipeline->num_words == 0) return CLI_ERROR;
    
      for (i = 0; i < pipeline->num_stages; i++) {
        // And double check each stage for an empty line - this *is* an error
        if (pipeline->stage[i].num_words == 0) {
          cli_error(cli, "Empty command given");
          return CLI_ERROR;
        }
    
        // In 'buildmode' we only have one pipeline, but we need to recall if we had started with any optargs
        if (cli->buildmode && i == 0)
          command_type = CLI_BUILDMODE_COMMAND;
        else if (i > 0)
          command_type = CLI_FILTER_COMMAND;
        else
          command_type = CLI_REGULAR_COMMAND;
    
        cli->pipeline->current_stage = &pipeline->stage[i];
        if (cli->buildmode)
          cli->found_optargs = cli->buildmode->found_optargs;
        else
          cli->found_optargs = NULL;
        rc = cli_int_locate_command(cli, cli->commands, command_type, 0, &pipeline->stage[i]);
    
        // And save our found optargs for later use
        if (cli->buildmode)
          cli->buildmode->found_optargs = cli->found_optargs;
        else
          pipeline->stage[i].found_optargs = cli->found_optargs;
    
        if (rc != CLI_OK) break;
      }
      cli->pipeline = NULL;
    
      return rc;
    }
    
    void cli_int_free_pipeline(struct cli_pipeline *pipeline) {
      int i;
      if (!pipeline) return;
      for (i = 0; i < pipeline->num_stages; i++) cli_int_free_found_optargs(&pipeline->stage[i].found_optargs);
      for (i = 0; i < pipeline->num_words; i++) free_z(pipeline->words[i]);
      free_z(pipeline->cmdline);
      free_z(pipeline);
    }
    
    void cli_int_show_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) {
      int i, j;
      struct cli_pipeline_stage *stage;
      char **word;
      struct cli_optarg_pair *optarg_pair;
    
      for (i = 0, word = pipeline->words; i < pipeline->num_words; i++, word++) printf("[%s] ", *word);
      fprintf(stderr, "\n");
      fprintf(stderr, "#stages=%d, #words=%d\n", pipeline->num_stages, pipeline->num_words);
    
      for (i = 0; i < pipeline->num_stages; i++) {
        stage = &(pipeline->stage[i]);
        fprintf(stderr, "  #%d(%d words) first_unmatched=%d: ", i, stage->num_words, stage->first_unmatched);
        for (j = 0; j < stage->num_words; j++) {
          fprintf(stderr, " [%s]", stage->words[j]);
        }
        fprintf(stderr, "\n");
    
        if (stage->command) {
          fprintf(stderr, "  Command: %s\n", stage->command->command);
        }
        for (optarg_pair = stage->found_optargs; optarg_pair; optarg_pair = optarg_pair->next) {
          fprintf(stderr, "    %s: %s\n", optarg_pair->name, optarg_pair->value);
        }
      }
    }
    
    // Take an array of words and return a pipeline, using '|' to split command into different 'stages'.
    // Pipeline is broken down by '|' characters and within each p.
    struct cli_pipeline *cli_int_generate_pipeline(struct cli_def *cli, const char *command) {
      int i;
      struct cli_pipeline_stage *stage;
      char **word;
      struct cli_pipeline *pipeline = NULL;
    
      cli->found_optargs = NULL;
      if (cli->buildmode) cli->found_optargs = cli->buildmode->found_optargs;
      if (!command) return NULL;
      while (*command && isspace(*command)) command++;
    
      if (!(pipeline = (struct cli_pipeline *)calloc(1, sizeof(struct cli_pipeline)))) return NULL;
      pipeline->cmdline = (char *)strdup(command);
    
      pipeline->num_words = cli_parse_line(command, pipeline->words, CLI_MAX_LINE_WORDS);
    
      pipeline->stage[0].num_words = 0;
      stage = &pipeline->stage[0];
      word = pipeline->words;
      stage->words = word;
      for (i = 0; i < pipeline->num_words; i++, word++) {
        if (*word[0] == '|') {
          if (cli->buildmode) {
            // Can't allow filters in buildmode commands
            cli_int_free_pipeline(pipeline);
            cli_error(cli, "\nPipelines are not allowed in buildmode");
            return NULL;
          }
          stage->stage_num = pipeline->num_stages;
          stage++;
          stage->num_words = 0;
          pipeline->num_stages++;
          stage->words = word + 1;  // First word of the next stage is one past where we are (possibly NULL)
        } else {
          stage->num_words++;
        }
      }
      stage->stage_num = pipeline->num_stages;
      pipeline->num_stages++;
      return pipeline;
    }
    
    int cli_int_execute_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) {
      int stage_num;
      int rc = CLI_OK;
      struct cli_filter **filt = &cli->filters;
    
      if (!pipeline | !cli) return CLI_ERROR;
    
      cli->pipeline = pipeline;
      for (stage_num = 1; stage_num < pipeline->num_stages; stage_num++) {
        struct cli_pipeline_stage *stage = &pipeline->stage[stage_num];
        pipeline->current_stage = stage;
        cli->found_optargs = stage->found_optargs;
        *filt = calloc(sizeof(struct cli_filter), 1);
        if (*filt) {
          if ((rc = stage->command->init(cli, stage->num_words, stage->words, *filt) != CLI_OK)) {
            break;
          }
          filt = &(*filt)->next;
        }
      }
      pipeline->current_stage = NULL;
    
      // Did everything init?  If so, execute, otherwise skip execution
      if ((rc == CLI_OK) && pipeline->stage[0].command->callback) {
        struct cli_pipeline_stage *stage = &pipeline->stage[0];
    
        pipeline->current_stage = &pipeline->stage[0];
        if (pipeline->current_stage->command->command_type == CLI_BUILDMODE_COMMAND)
          cli->found_optargs = cli->buildmode->found_optargs;
        else
          cli->found_optargs = pipeline->stage[0].found_optargs;
        rc = stage->command->callback(cli, cli_command_name(cli, stage->command), stage->words + stage->first_unmatched,
                                      stage->num_words - stage->first_unmatched);
        if (pipeline->current_stage->command->command_type == CLI_BUILDMODE_COMMAND)
          cli->buildmode->found_optargs = cli->found_optargs;
        pipeline->current_stage = NULL;
      }
    
      // Now teardown any filters
      while (cli->filters) {
        struct cli_filter *filt = cli->filters;
        if (filt->filter) filt->filter(cli, NULL, cli->filters->data);
        cli->filters = filt->next;
        free_z(filt);
      }
      cli->found_optargs = NULL;
      cli->pipeline = NULL;
      return rc;
    }
    
    /*
     *  Attempt quick dirty wrapping of helptext taking into account the offset from name, embedded
     *  cr/lf in helptext, and trying to split on last white-text before the right margin.  If there is
     *  no identifiable whitespace to split on, then the split will be done on the last character to fit
     *  that line (currently max line with is 80 characters).  
     *  The firstcolumn width will be a greater of 22 characters or the width of nameptr, which ever is 
     *  greater, and will be offset from the rest of the line by one space.  However, if nameptr is
     *  greater than 22 characters it will be put on a line by itself.  The first column will be formatted
     *  as spaces (22 of em) for all subsequent lines.
    .
     *  This routine assumes any 'indenting' of the nameptr field has already been done, and is solely
     *  concerned about wrapping the combination of nameptr and helpptr to look 'nice'.  
     */
     
    #define MAX(a,b) ((a) >(b) ? (a) : (b))
    #define MAXWIDTHCOL1 22
    
    void cli_int_wrap_help_line(char *nameptr, char *helpptr, struct cli_comphelp *comphelp) {
      int maxwidth = 80;  // temporary assumption, to be fixed later when libcli 'understands' screen dimensions
      int availwidth;
      int namewidth;
      int toprint;
      char *crlf;
      char *line;
      char emptystring[] = "";
    
      if (!helpptr) helpptr = emptystring;
      /*
       * Now we need to iterate one or more times to only print out at most
       * maxwidth - leftwidth characters of helpptr.  Note that there are no
       * tabs in helpptr, so each 'char' displays as one char
       */
    
      do {
        if ((nameptr != emptystring) && (strlen(nameptr) > MAXWIDTHCOL1)) {
          if (asprintf(&line, "%s", nameptr) < 0) break;
          cli_add_comphelp_entry(comphelp, line);
          free_z(line);
          nameptr = emptystring;
          namewidth = MAXWIDTHCOL1;
        }
        namewidth = MAX(MAXWIDTHCOL1,strlen(nameptr));
        availwidth = maxwidth - namewidth -1; // subtract 1 for space separating col1 from rest of line
        toprint = strlen(helpptr);
        if (toprint > availwidth) {
          toprint = availwidth;
          while ((toprint >= 0) && !isspace(helpptr[toprint])) toprint--;
          if (toprint < 0) {
            // if we backed up and found no whitespace, dump as much as we can
            toprint = availwidth;
          }
        }  // see if we might have an embedded carriage return or line feed
        if ((crlf = strpbrk(helpptr, "\n\r"))) {
          // crlf is a pointer - see if it is 'before' the toprint index
          if ((crlf - helpptr) < toprint) {
            // ok, crlf is before the wrap, so have line break here.
            toprint = (crlf - helpptr);
          }
        }
    
        if (asprintf(&line, "%-*.*s %.*s", namewidth, namewidth, nameptr, toprint, helpptr) < 0) break;
        cli_add_comphelp_entry(comphelp, line);
        free_z(line);
    
        nameptr = emptystring;
        helpptr += toprint;
        // regardless of how long the command is, indent by 20 chars on all following lines
        namewidth = 22;
        // advance to first non whitespace
        while (helpptr && isspace(*helpptr)) helpptr++;
      } while (*helpptr);
    }
    
    static void cli_get_optarg_comphelp(struct cli_def *cli, struct cli_optarg *optarg, struct cli_comphelp *comphelp,
                                        int num_candidates, const char lastchar, const char *anchor_word,
                                        const char *next_word) {
      int help_insert = 0;
      char *delim_start = DELIM_NONE;
      char *delim_end = DELIM_NONE;
      int (*get_completions)(struct cli_def *, const char *, const char *, struct cli_comphelp *) = NULL;
      char *tptr = NULL;
    
      // If we've already seen a value by this exact name, skip it, unless the multiple flag is set
      if (cli_find_optarg_value(cli, optarg->name, NULL) && !(optarg->flags & (CLI_CMD_OPTION_MULTIPLE))) return;
    
      get_completions = optarg->get_completions;
      if (optarg->flags & CLI_CMD_OPTIONAL_FLAG) {
        if (!(anchor_word && !strncmp(anchor_word, optarg->name, strlen(anchor_word)))) {
          delim_start = DELIM_OPT_START;
          delim_end = DELIM_OPT_END;
          get_completions = NULL;  // No point, completor of field is the name itself
        }
      } else if (optarg->flags & CLI_CMD_HYPHENATED_OPTION) {
        delim_start = DELIM_OPT_START;
        delim_end = DELIM_OPT_END;
      } else if (optarg->flags & CLI_CMD_ARGUMENT) {
        delim_start = DELIM_ARG_START;
        delim_end = DELIM_ARG_END;
      } else if (optarg->flags & CLI_CMD_OPTIONAL_ARGUMENT) {
        /*
         * Optional args can match against the name or the value.
         * Here 'anchor_word' is the name, and 'next_word' is 'value' for said optional argument.
         * So if anchor_word==next_word we're looking at the 'name' of the optarg, otherwise we know the name and are going
         * against the value.
         */
        if (anchor_word != next_word) {
          // Matching against optional argument 'value'
          help_insert = 0;
          if (!get_completions) {
            delim_start = DELIM_ARG_START;
            delim_end = DELIM_ARG_END;
          }
        } else {
          // Matching against optional argument 'name'
          help_insert = 1;
          get_completions = NULL;  // Matching against the name, not the following field value
          if (!(anchor_word && !strncmp(anchor_word, optarg->name, strlen(anchor_word)))) {
            delim_start = DELIM_OPT_START;
            delim_end = DELIM_OPT_END;
          }
        }
      }
    
      // Fill in with help text or completor value(s) as indicated
      if (lastchar == '?') {
        /*
         *  Note - help is a bit complex, and we could optimize it.  But it isn't done often,
         *  so we're always going to do it on the fly.
         *  Help will consist of '\v' separated lines.  Each line except the first is also '\t'
         *  separated into the name/text fields.  If a line does not have a '\t' separated then the
         *  name will be the name of the optarg, and the help will be that entire line.  The *first*
         *  does get some tweaks to how the name and help is displayed.
         *  The first pass through will be indented 2 spaces on the left with the formated name occupying
         *  20 spaces (expanding if more than 20).  If the command is a 'buildmode' command the first
         *  character of the 'text' will be an asterisk.  The 'rest' of the line (assuming an 80 character '
         *  wide line for now) will be used to wrap the 'text' field honoring embedded newlines, and trying to
         *  wrap on nearest preceeding whitespace when it hits a boundary.  Subsequent lines will be indented
         *  by an additional 2 spaces, and will drop the asterisk.
         */
        char *working = NULL;
        char *nameptr = NULL;
        char *helpptr = NULL;
        char *lineptr = NULL;
        char *savelineptr = NULL;
        char *savetabptr = NULL;
        char *tname = NULL;
        int indent = 2;
        int helplen;
        char emptystring[] = "";
    
        /*
         * Print out actual text into a working buffer that we can then call 'strtok_r' on it.  This lets
         * us prepend some optional fields nice and easily.  At this point it is one big string, so we can
         * iterate over it making changes (strtok_r) as needed.
         */
        if (help_insert) {
          helplen = asprintf(&working, "%s%s%s%s%s", (optarg->flags & CLI_CMD_ALLOW_BUILDMODE) ? "* " : "", "type '",
                             optarg->name, "' to select ", optarg->name);
        } else {
          helplen = asprintf(&working, "%s%s", (optarg->flags & CLI_CMD_ALLOW_BUILDMODE) ? "* " : "", optarg->help);
        }
    
        // pull the first line
        helpptr = strtok_r(working, "\v", &savelineptr);
        nameptr = optarg->name;
    
        if (helplen < 0) {
          helpptr = emptystring;
          working = NULL;
        }
    
        // break things up into tab separated entities - always show the first entry
        do {
          char *leftcolumn;
          if (asprintf(&tname, "%s%s%s", delim_start, nameptr, delim_end) == -1) break;
          if (asprintf(&leftcolumn, "%*.*s%s", indent, indent, "", tname) == -1) break;
    
          cli_int_wrap_help_line(leftcolumn, helpptr, comphelp);
    
          // clear out any delimiter settings and set indent for any subtext
          delim_start = DELIM_NONE;
          delim_end = DELIM_NONE;
          indent = 4;
          free_z(tname);
          free_z(leftcolumn);
    
          // we may not need to show all off the 'extra help', so loop here
          do {
            lineptr = strtok_r(NULL, "\v", &savelineptr);
            if (lineptr) {
              nameptr = strtok_r(lineptr, "\t", &savetabptr);
              helpptr = strtok_r(NULL, "\t", &savetabptr);
            }
          } while (lineptr && nameptr && helpptr && (next_word && (strncmp(next_word, nameptr, strlen(next_word)))));
        } while (lineptr && nameptr && helpptr);
        free_z(working);
      } else if (lastchar == CTRL('I')) {
        if (get_completions) {
          (*get_completions)(cli, optarg->name, next_word, comphelp);
        } else if ((!anchor_word || !strncmp(anchor_word, optarg->name, strlen(anchor_word))) &&
                   (asprintf(&tptr, "%s%s%s", delim_start, optarg->name, delim_end) != -1)) {
          cli_add_comphelp_entry(comphelp, tptr);
          free_z(tptr);
        }
      }
    }
    
    static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage *stage, struct cli_command *cmd,
                                      char lastchar, struct cli_comphelp *comphelp) {
      struct cli_optarg *optarg = NULL, *oaptr = NULL;
      int word_idx, value_idx, word_incr, candidate_idx;
      struct cli_optarg *candidates[CLI_MAX_LINE_WORDS];
      char *value;
      int num_candidates = 0;
      int is_last_word = 0;
      int (*validator)(struct cli_def *, const char *name, const char *value);
    
      if (cli->buildmode)
        cli->found_optargs = cli->buildmode->found_optargs;
      else
        cli->found_optargs = stage->found_optargs;
      /*
       * Tab completion and help are *only* allowed at end of string, but we need to process the entire command to know what
       * has already been found.  There should be no ambiguities before the 'last' word.
       * Note specifically that for tab completions and help the *last* word can be a null pointer.
       */
      stage->error_word = NULL;
    
      /* Start our optarg and word pointers at the beginning.
       * optarg will be incremented *only* when an argument is identified.
       * word_idx will be incremented either by 1 (optflag or argument) or 2 (optional argument).
       */
      word_idx = stage->first_unmatched;
      optarg = cmd->optargs;
      num_candidates = 0;
      while (optarg && word_idx < stage->num_words && num_candidates <= 1) {
        num_candidates = 0;
        word_incr = 1;  // Assume we're only incrementing by a word - if we match an optional argument bump to 2
    
        /*
         * The initial loop here is to identify candidates based matching *this* word in order against:
         * - An exact match of the word to the optinal flag/argument name (yield exactly one match and exit the loop)
         * - A partial match for optional flag/argument name
         * - Candidate an argument.
         */
    
        for (oaptr = optarg; oaptr; oaptr = oaptr->next) {
          // Skip this option unless it matches privileges, MODE_ANY, the current mode, or the transient_mode
          if (cli->privilege < oaptr->privilege) continue;
          if ((oaptr->mode != cli->mode) && (oaptr->mode != cli->transient_mode) && (oaptr->mode != MODE_ANY)) continue;
    
          /*
           * Special cases:
           * - spot check
           * - a hyphenated option a hyphenated option
           * - an optional flag without validator, but the word matches the optarg name
           * - an optional flag with a validator *and* the word passes the validator,
           * - an optional argument where the word matches the argument name
           * a hit on any of these special cases is an automatic *only* candidate.
           *
           * Otherwise if the word is 'blank', could be an argument, or matches 'enough' of an option/flag it is a
           * candidate.
           * Once we accept an argument as a candidate, we're done looking for candidates as straight arguments are
           * required.
           */
          if (oaptr->flags & CLI_CMD_SPOT_CHECK && num_candidates == 0) {
            stage->status = (*oaptr->validator)(cli, NULL, NULL);
            if (stage->status != CLI_OK) {
              stage->error_word = stage->words[word_idx];
              cli_reprompt(cli);
              goto done;
            }
          } else if (stage->words[word_idx] && stage->words[word_idx][0] == '-' &&
                     (oaptr->flags & (CLI_CMD_HYPHENATED_OPTION))) {
            candidates[0] = oaptr;
            num_candidates = 1;
            break;
          } else if (stage->words[word_idx] && (oaptr->flags & CLI_CMD_OPTIONAL_FLAG) &&
                     ((oaptr->validator && (oaptr->validator(cli, oaptr->name, stage->words[word_idx]) == CLI_OK)) ||
                      (!oaptr->validator && !strcmp(oaptr->name, stage->words[word_idx])))) {
            candidates[0] = oaptr;
            num_candidates = 1;
            break;
          } else if (stage->words[word_idx] && (oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT) &&
                     !strcmp(oaptr->name, stage->words[word_idx])) {
            candidates[0] = oaptr;
            num_candidates = 1;
            break;
          } else if (!stage->words[word_idx] || (oaptr->flags & CLI_CMD_ARGUMENT) ||
                     !strncasecmp(oaptr->name, stage->words[word_idx], strlen(stage->words[word_idx]))) {
            candidates[num_candidates++] = oaptr;
          }
          if (oaptr->flags & CLI_CMD_ARGUMENT) {
            break;
          }
        }
    
        /*
         * Iterate over the list of candidates for this word.  There are several early exit cases to consider:
         * - If we have no candidates then we're done - any remaining words must be processed by the command callback
         * - If we have more than one candidate evaluating for execution punt hard after complaining.
         * - If we have more than one candidate and we're not at end-of-line (
         */
        if (num_candidates == 0) break;
        if (num_candidates > 1 && (lastchar == '\0' || word_idx < (stage->num_words - 1))) {
          stage->error_word = stage->words[word_idx];
          stage->status = CLI_AMBIGUOUS;
          cli_error(cli, "\nAmbiguous option/argument for command %s", stage->command->command);
          goto done;
        }
    
        /*
         * So now we could have one or more candidates.  We need to call get help/completions *only* if this is the
         * 'last-word'.
         * Remember that last word for optional arguments is last or next to last....
         */
        if (lastchar != '\0') {
          int called_comphelp = 0;
          for (candidate_idx = 0; candidate_idx < num_candidates; candidate_idx++) {
            oaptr = candidates[candidate_idx];
    
            // Need to know *which* word we're trying to complete for optional_args, hence the difference calls
            if (((oaptr->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_ARGUMENT)) && (word_idx == (stage->num_words - 1))) ||
                (oaptr->flags & (CLI_CMD_OPTIONAL_ARGUMENT | CLI_CMD_HYPHENATED_OPTION) &&
                 word_idx == (stage->num_words - 1))) {
              cli_get_optarg_comphelp(cli, oaptr, comphelp, num_candidates, lastchar, stage->words[word_idx],
                                      stage->words[word_idx]);
              called_comphelp = 1;
            } else if (oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT && word_idx == (stage->num_words - 2)) {
              cli_get_optarg_comphelp(cli, oaptr, comphelp, num_candidates, lastchar, stage->words[word_idx],
                                      stage->words[word_idx + 1]);
              called_comphelp = 1;
            }
          }
          // If we were 'end-of-word' and looked for completions/help, return to user
          if (called_comphelp) {
            stage->status = CLI_OK;
            goto done;
          }
        }
    
        // Set some values for use later - makes code much easier to read
        value = stage->words[word_idx];
        value_idx = word_idx;
        oaptr = candidates[0];
        validator = oaptr->validator;
        if ((oaptr->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_ARGUMENT) && word_idx == (stage->num_words - 1)) ||
            (oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT && word_idx == (stage->num_words - 2))) {
          is_last_word = 1;
        }
    
        if (oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT) {
          word_incr = 2;
          if (!stage->words[word_idx + 1] && lastchar == '\0') {
            // Hit an optional argument that does not have a value with it
            cli_error(cli, "Optional argument %s requires a value", stage->words[word_idx]);
            stage->error_word = stage->words[word_idx];
            stage->status = CLI_MISSING_VALUE;
            goto done;
          }
          value = stage->words[word_idx + 1];
          value_idx = word_idx + 1;
        }
    
        /*
         * We're not at end of string and doing help/completions.
         * So see if our value is 'valid', to save it, and see if we have any extra processing to do such as a transient
         * mode check or enter build mode.
         */
    
        if (!validator || (*validator)(cli, oaptr->name, value) == CLI_OK) {
          if (oaptr->flags & CLI_CMD_DO_NOT_RECORD) {
            // We want completion and validation, but then leave this 'value' to be seen - used *only* by buildmode as
            // argv[0] with argc=1
            break;
          } else {
            // Need to combine remaining words if the CLI_CMD_REMAINDER_OF_LINE flag it set, then we're done processing
            int set_value_return = 0;
    
            if (oaptr->flags & CLI_CMD_REMAINDER_OF_LINE) {
              char *combined = NULL;
              combined = join_words(stage->num_words - word_idx, stage->words + word_idx);
              if (!combined) {
                cli_error(cli, "%sUnable to allocate memory for command processing", lastchar == '\0' ? "" : "\n");
                cli_reprompt(cli);
                stage->error_word = stage->words[word_idx];
                stage->status = CLI_ERROR;
                goto done;
              }
              set_value_return = cli_set_optarg_value(cli, oaptr->name, combined, 0);
              free_z(combined);
            } else {
              set_value_return = cli_set_optarg_value(cli, oaptr->name, value, oaptr->flags & CLI_CMD_OPTION_MULTIPLE);
            }
    
            if (set_value_return != CLI_OK) {
              cli_error(cli, "%sProblem setting value for command argument %s", lastchar == '\0' ? "" : "\n",
                        stage->words[word_idx]);
              cli_reprompt(cli);
              stage->error_word = stage->words[word_idx];
              stage->status = CLI_ERROR;
              goto done;
            }
          }
        } else {
          cli_error(cli, "%sProblem parsing command setting %s with value %s", lastchar == '\0' ? "" : "\n", oaptr->name,
                    stage->words[value_idx]);
          cli_reprompt(cli);
          stage->error_word = stage->words[word_idx];
          stage->status = CLI_ERROR;
          goto done;
        }
    
        // If this optarg can set the transient mode, then evaluate it if we're not at last word
        if (oaptr->transient_mode && oaptr->transient_mode(cli, oaptr->name, value)) {
          stage->error_word = stage->words[word_idx];
          stage->status = CLI_ERROR;
          goto done;
        }
    
        // Only process CLI_CMD_ALLOW_BUILDMODE if we're not already in buildmode, parsing command (stage 0), and this is
        // the last word
        if (!cli->disallow_buildmode && (stage->status == CLI_OK) && (oaptr->flags & CLI_CMD_ALLOW_BUILDMODE) &&
            is_last_word) {
          stage->status = cli_int_enter_buildmode(cli, stage, value);
          goto done;
        }
    
        // Optional flags and arguments can appear multiple times, and in any order.  We only advance
        // from our starting optarg if the matching optarg is a true argument.
        if (oaptr->flags & CLI_CMD_ARGUMENT) {
          // Advance past this argument entry
          optarg = oaptr->next;
        }
    
        word_idx += word_incr;
        stage->first_unmatched = word_idx;
      }
    
      // If we're evaluating the command for execution, ensure we have all required arguments.
      if (lastchar == '\0') {
        for (; optarg; optarg = optarg->next) {
          if (cli->privilege < optarg->privilege) continue;
          if ((optarg->mode != cli->mode) && (optarg->mode != cli->transient_mode) && (optarg->mode != MODE_ANY)) continue;
          if (optarg->flags & CLI_CMD_DO_NOT_RECORD) continue;
          if (optarg->flags & CLI_CMD_ARGUMENT) {
            cli_error(cli, "Incomplete command, missing required argument '%s' for command '%s'", optarg->name,
                      cmd->command);
            stage->status = CLI_MISSING_ARGUMENT;
            goto done;
          }
        }
      }
    
    done:
      if (cli->buildmode)
        cli->buildmode->found_optargs = cli->found_optargs;
      else
        stage->found_optargs = cli->found_optargs;
      return;
    }
    
    void cli_unregister_all_commands(struct cli_def *cli) {
      cli_unregister_tree(cli, cli->commands, CLI_REGULAR_COMMAND);
    }
    
    void cli_unregister_all_filters(struct cli_def *cli) {
      cli_unregister_tree(cli, cli->commands, CLI_FILTER_COMMAND);
    }
    
    /*
     * Several routines were declared as internal, but would be useful for external use also
     * Rename them so they can be exposed, but have original routines simply call the 'public' ones
     */
    
    int cli_int_quit(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) {
      return cli_quit(cli, command, argv, argc);
    }
    
    int cli_int_help(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) {
      return cli_help(cli, command, argv, argc);
    }
    
    int cli_int_history(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) {
      return cli_history(cli, command, argv, argc);
    }
    
    int cli_int_exit(struct cli_def *cli, const char *command, char *argv[], int argc) {
      return cli_exit(cli, command, argv, argc);
    }
    
    int cli_int_enable(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) {
      return cli_enable(cli, command, argv, argc);
    }
    
    int cli_int_disable(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) {
      return cli_disable(cli, command, argv, argc);
    }
    
    void cli_dump_optargs_and_args(struct cli_def *cli, const char *text, char *argv[], int argc) {
      int i;
      struct cli_optarg_pair *optargs;
      cli_print(cli, "%s: mode = %d, transient_mode = %d", text, cli->mode, cli->transient_mode);
      cli_print(cli, "Identified optargs");
      for (optargs = cli_get_all_found_optargs(cli), i = 0; optargs; optargs = optargs->next, i++)
        cli_print(cli, "%2d  %s=%s", i, optargs->name, optargs->value);
      cli_print(cli, "Extra args");
      for (i = 0; i < argc; i++) cli_print(cli, "%2d %s", i, argv[i]);
    }
    
    static int cli_socket_wait(int sockfd, struct timeval *tm) {
    #if defined(LIBCLI_USE_POLL) && !defined(WIN32)
      struct pollfd pfd = {
          .fd = sockfd,
          .events = POLLIN,
      };
    
      return poll(&pfd, 1, (tm->tv_sec * 1000) + (tm->tv_usec / 1000));
    #else
      fd_set r;
      FD_ZERO(&r);
      FD_SET(sockfd, &r);
      return select(sockfd + 1, &r, NULL, NULL, tm);
    #endif
    }
    

    clitest

    #include <errno.h>
    #include <limits.h>
    #include <stdio.h>
    #include <sys/types.h>
    #ifdef WIN32
    #include <windows.h>
    #include <winsock2.h>
    #else
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <sys/socket.h>
    #endif
    #include <signal.h>
    #include <stdlib.h>
    #include <string.h>
    #include <strings.h>
    #include <unistd.h>
    
    #include "libcli.h"
    
    // vim:sw=4 tw=120 et
    
    #define CLITEST_PORT 8000
    #define MODE_CONFIG_INT 10
    
    #ifdef __GNUC__
    #define UNUSED(d) d __attribute__((unused))
    #else
    #define UNUSED(d) d
    #endif
    
    unsigned int regular_count = 0;
    unsigned int debug_regular = 0;
    
    struct my_context {
      int value;
      char *message;
    };
    
    #ifdef WIN32
    typedef int socklen_t;
    
    int winsock_init() {
      WORD wVersionRequested;
      WSADATA wsaData;
      int err;
    
      // Start up sockets
      wVersionRequested = MAKEWORD(2, 2);
    
      err = WSAStartup(wVersionRequested, &wsaData);
      if (err != 0) {
        // Tell the user that we could not find a usable WinSock DLL.
        return 0;
      }
    
      /*
       * Confirm that the WinSock DLL supports 2.2
       * Note that if the DLL supports versions greater than 2.2 in addition to
       * 2.2, it will still return 2.2 in wVersion since that is the version we
       * requested.
       * */
      if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
        // Tell the user that we could not find a usable WinSock DLL.
        WSACleanup();
        return 0;
      }
      return 1;
    }
    #endif
    
    int cmd_test(struct cli_def *cli, const char *command, char *argv[], int argc) {
      int i;
      cli_print(cli, "called %s with \"%s\"", __func__, command);
      cli_print(cli, "%d arguments:", argc);
      for (i = 0; i < argc; i++) cli_print(cli, "        %s", argv[i]);
    
      return CLI_OK;
    }
    
    int cmd_set(struct cli_def *cli, UNUSED(const char *command), char *argv[], int argc) {
      if (argc < 2 || strcmp(argv[0], "?") == 0) {
        cli_print(cli, "Specify a variable to set");
        return CLI_OK;
      }
    
      if (strcmp(argv[1], "?") == 0) {
        cli_print(cli, "Specify a value");
        return CLI_OK;
      }
    
      if (strcmp(argv[0], "regular_interval") == 0) {
        unsigned int sec = 0;
        if (!argv[1] && !*argv[1]) {
          cli_print(cli, "Specify a regular callback interval in seconds");
          return CLI_OK;
        }
        sscanf(argv[1], "%u", &sec);
        if (sec < 1) {
          cli_print(cli, "Specify a regular callback interval in seconds");
          return CLI_OK;
        }
        cli->timeout_tm.tv_sec = sec;
        cli->timeout_tm.tv_usec = 0;
        cli_print(cli, "Regular callback interval is now %d seconds", sec);
        return CLI_OK;
      }
    
      cli_print(cli, "Setting \"%s\" to \"%s\"", argv[0], argv[1]);
      return CLI_OK;
    }
    
    int cmd_config_int(struct cli_def *cli, UNUSED(const char *command), char *argv[], int argc) {
      if (argc < 1) {
        cli_print(cli, "Specify an interface to configure");
        return CLI_OK;
      }
    
      if (strcmp(argv[0], "?") == 0)
        cli_print(cli, "  test0/0");
    
      else if (strcasecmp(argv[0], "test0/0") == 0)
        cli_set_configmode(cli, MODE_CONFIG_INT, "test");
      else
        cli_print(cli, "Unknown interface %s", argv[0]);
    
      return CLI_OK;
    }
    
    int cmd_config_int_exit(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) {
      cli_set_configmode(cli, MODE_CONFIG, NULL);
      return CLI_OK;
    }
    
    int cmd_show_regular(struct cli_def *cli, UNUSED(const char *command), char *argv[], int argc) {
      cli_print(cli, "cli_regular() has run %u times", regular_count);
      return CLI_OK;
    }
    
    int cmd_debug_regular(struct cli_def *cli, UNUSED(const char *command), char *argv[], int argc) {
      debug_regular = !debug_regular;
      cli_print(cli, "cli_regular() debugging is %s", debug_regular ? "enabled" : "disabled");
      return CLI_OK;
    }
    
    int cmd_context(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) {
      struct my_context *myctx = (struct my_context *)cli_get_context(cli);
      cli_print(cli, "User context has a value of %d and message saying %s", myctx->value, myctx->message);
      return CLI_OK;
    }
    
    int check_auth(const char *username, const char *password) {
      if (strcasecmp(username, "fred") != 0) return CLI_ERROR;
      if (strcasecmp(password, "nerk") != 0) return CLI_ERROR;
      return CLI_OK;
    }
    
    int regular_callback(struct cli_def *cli) {
      regular_count++;
      if (debug_regular) {
        cli_print(cli, "Regular callback - %u times so far", regular_count);
        cli_reprompt(cli);
      }
      return CLI_OK;
    }
    
    int check_enable(const char *password) {
      return !strcasecmp(password, "topsecret");
    }
    
    int idle_timeout(struct cli_def *cli) {
      cli_print(cli, "Custom idle timeout");
      return CLI_QUIT;
    }
    
    void pc(UNUSED(struct cli_def *cli), const char *string) {
      printf("%s\n", string);
    }
    
    #define MODE_POLYGON_TRIANGLE 20
    #define MODE_POLYGON_RECTANGLE 21
    
    int cmd_perimeter(struct cli_def *cli, const char *command, char *argv[], int argc) {
      struct cli_optarg_pair *optargs = cli_get_all_found_optargs(cli);
      int i = 1, numSides = 0;
      int perimeter = 0;
      int verbose_count = 0;
      char *verboseArg;
      char *shapeName = NULL;
    
      cli_print(cli, "perimeter callback, with %d args", argc);
      for (; optargs; optargs = optargs->next) cli_print(cli, "%d, %s=%s", i++, optargs->name, optargs->value);
    
      verboseArg = NULL;
      while ((verboseArg = cli_get_optarg_value(cli, "verbose", verboseArg))) {
        verbose_count++;
      }
      cli_print(cli, "verbose argument was seen  %d times", verbose_count);
    
      shapeName = cli_get_optarg_value(cli, "shape", NULL);
      if (!shapeName) {
        cli_error(cli, "No shape name given");
        return CLI_ERROR;
      } else if (strcmp(shapeName, "triangle") == 0) {
        numSides = 3;
      } else if (strcmp(shapeName, "rectangle") == 0) {
        numSides = 4;
      } else {
        cli_error(cli, "Unrecognized shape given");
        return CLI_ERROR;
      }
      for (i = 1; i <= numSides; i++) {
        char sidename[50], *value;
        int length;
        snprintf(sidename, 50, "side_%d", i);
        value = cli_get_optarg_value(cli, sidename, NULL);
        length = strtol(value, NULL, 10);
        perimeter += length;
      }
      cli_print(cli, "Perimeter is %d", perimeter);
      return CLI_OK;
    }
    
    const char *KnownShapes[] = {"rectangle", "triangle", NULL};
    
    int shape_completor(struct cli_def *cli, const char *name, const char *value, struct cli_comphelp *comphelp) {
      const char **shape;
      int rc = CLI_OK;
      printf("shape_completor called with <%s>\n", value);
      for (shape = KnownShapes; *shape && (rc == CLI_OK); shape++) {
        if (!value || !strncmp(*shape, value, strlen(value))) {
          rc = cli_add_comphelp_entry(comphelp, *shape);
        }
      }
      return rc;
    }
    
    int shape_validator(struct cli_def *cli, const char *name, const char *value) {
      const char **shape;
    
      printf("shape_validator called with <%s>\n", value);
      for (shape = KnownShapes; *shape; shape++) {
        if (!strcmp(value, *shape)) return CLI_OK;
      }
      return CLI_ERROR;
    }
    
    int verbose_validator(struct cli_def *cli, const char *name, const char *value) {
      printf("verbose_validator called\n");
      return CLI_OK;
    }
    
    // note that we're setting a 'custom' optarg tag/value pair as an example here
    int shape_transient_eval(struct cli_def *cli, const char *name, const char *value) {
      printf("shape_transient_eval called with <%s>\n", value);
      if (!strcmp(value, "rectangle")) {
        cli_set_transient_mode(cli, MODE_POLYGON_RECTANGLE);
        cli_set_optarg_value(cli, "duplicateShapeValue", value, 0);
        return CLI_OK;
      } else if (!strcmp(value, "triangle")) {
        cli_set_transient_mode(cli, MODE_POLYGON_TRIANGLE);
        cli_set_optarg_value(cli, "duplicateShapeValue", value, 0);
        return CLI_OK;
      }
      cli_error(cli, "unrecognized value for setting %s -> %s", name, value);
      return CLI_ERROR;
    }
    
    const char *KnownColors[] = {"black",    "white",     "gray",      "red",        "blue",
                                 "green",    "lightred",  "lightblue", "lightgreen", "darkred",
                                 "darkblue", "darkgreen", "lavender",  "yellow",     NULL};
    
    int color_completor(struct cli_def *cli, const char *name, const char *word, struct cli_comphelp *comphelp) {
      // Attempt to show matches against the following color strings
      const char **color;
      int rc = CLI_OK;
      printf("color_completor called with <%s>\n", word);
      for (color = KnownColors; *color && (rc == CLI_OK); color++) {
        if (!word || !strncmp(*color, word, strlen(word))) {
          rc = cli_add_comphelp_entry(comphelp, *color);
        }
      }
      return rc;
    }
    
    int color_validator(struct cli_def *cli, const char *name, const char *value) {
      const char **color;
      int rc = CLI_ERROR;
      printf("color_validator called for %s\n", name);
      for (color = KnownColors; *color; color++) {
        if (!strcmp(value, *color)) return CLI_OK;
      }
      return rc;
    }
    
    int side_length_validator(struct cli_def *cli, const char *name, const char *value) {
      // Verify 'value' is a positive number
      long len;
      char *endptr;
      int rc = CLI_OK;
    
      printf("side_length_validator called\n");
      errno = 0;
      len = strtol(value, &endptr, 10);
      if ((endptr == value) || (*endptr != '\0') || ((errno == ERANGE) && ((len == LONG_MIN) || (len == LONG_MAX))))
        return CLI_ERROR;
      return rc;
    }
    
    int transparent_validator(struct cli_def *cli, const char *name, const char *value) {
      return strcasecmp("transparent", value) ? CLI_ERROR : CLI_OK;
    }
    
    int check1_validator(struct cli_def *cli, UNUSED(const char *name), UNUSED(const char *value)) {
      char *color;
      char *transparent;
    
      printf("check1_validator called \n");
      color = cli_get_optarg_value(cli, "color", NULL);
      transparent = cli_get_optarg_value(cli, "transparent", NULL);
    
      if (!color && !transparent) {
        cli_error(cli, "\nMust supply either a color or transparent!");
        return CLI_ERROR;
      } else if (color && !strcmp(color, "black") && transparent) {
        cli_error(cli, "\nCan not have a transparent black object!");
        return CLI_ERROR;
      }
      return CLI_OK;
    }
    
    int cmd_deep_dive(struct cli_def *cli, const char *command, char *argv[], int argc) {
      cli_print(cli, "Raw commandline was <%s>", cli->pipeline->cmdline);
      return CLI_OK;
    }
    
    int int_validator(struct cli_def *cli, const char *name, const char *value) {
      // Verify 'value' is a positive number
      long len;
      char *endptr;
      int rc = CLI_OK;
    
      printf("int_validator called\n");
      errno = 0;
      len = strtol(value, &endptr, 10);
      if ((endptr == value) || (*endptr != '\0') || ((errno == ERANGE) && ((len == LONG_MIN) || (len == LONG_MAX))))
        return CLI_ERROR;
      return rc;
    }
    
    int cmd_string(struct cli_def *cli, const char *command, char *argv[], int argc) {
      int i;
      cli_print(cli, "Raw commandline was <%s>", cli->pipeline->cmdline);
      cli_print(cli, "Value for text argument is <%s>", cli_get_optarg_value(cli, "text", NULL));
    
      cli_print(cli, "Found %d 'extra' arguments after 'text' argument was processed", argc);
      for (i = 0; i != argc; i++) {
        cli_print(cli, "  Extra arg %d = <%s>", i + 1, argv[i]);
      }
      return CLI_OK;
    }
    int cmd_long_name(struct cli_def *cli, const char *command, char *argv[], int argc) {
      int i;
      cli_print(cli, "Raw commandline was <%s>", cli->pipeline->cmdline);
      cli_print(cli, "Value for text argument is <%s>", cli_get_optarg_value(cli, "text", NULL));
    
      cli_print(cli, "Found %d 'extra' arguments after 'text' argument was processed", argc);
      for (i = 0; i != argc; i++) {
        cli_print(cli, "  Extra arg %d = <%s>", i + 1, argv[i]);
      }
      return CLI_OK;
    }
    
    void run_child(int x) {
      struct cli_command *c;
      struct cli_def *cli;
      struct cli_optarg *o;
    
      // Prepare a small user context
      char mymessage[] = "I contain user data!";
      struct my_context myctx;
      myctx.value = 5;
      myctx.message = mymessage;
    
      cli = cli_init();
      cli_set_banner(cli, "libcli test environment");
      cli_set_hostname(cli, "router");
      cli_telnet_protocol(cli, 1);
      cli_regular(cli, regular_callback);
    
      // change regular update to 5 seconds rather than default of 1 second
      cli_regular_interval(cli, 5);
    
      // set 60 second idle timeout
      cli_set_idle_timeout_callback(cli, 60, idle_timeout);
      cli_register_command(cli, NULL, "test", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL);
      cli_register_command(cli, NULL, "simple", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL);
      cli_register_command(cli, NULL, "simon", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL);
      cli_register_command(cli, NULL, "set", cmd_set, PRIVILEGE_PRIVILEGED, MODE_EXEC, NULL);
      c = cli_register_command(cli, NULL, "show", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL);
      cli_register_command(cli, c, "regular", cmd_show_regular, PRIVILEGE_UNPRIVILEGED, MODE_EXEC,
                           "Show the how many times cli_regular has run");
      cli_register_command(cli, c, "counters", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC,
                           "Show the counters that the system uses");
      cli_register_command(cli, c, "junk", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL);
      cli_register_command(cli, NULL, "interface", cmd_config_int, PRIVILEGE_PRIVILEGED, MODE_CONFIG,
                           "Configure an interface");
      cli_register_command(cli, NULL, "exit", cmd_config_int_exit, PRIVILEGE_PRIVILEGED, MODE_CONFIG_INT,
                           "Exit from interface configuration");
      cli_register_command(cli, NULL, "address", cmd_test, PRIVILEGE_PRIVILEGED, MODE_CONFIG_INT, "Set IP address");
      c = cli_register_command(cli, NULL, "debug", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL);
      cli_register_command(cli, c, "regular", cmd_debug_regular, PRIVILEGE_UNPRIVILEGED, MODE_EXEC,
                           "Enable cli_regular() callback debugging");
    
      // Register some commands/subcommands to demonstrate opt/arg and buildmode operations
    
      c = cli_register_command(
          cli, NULL, "perimeter", cmd_perimeter, PRIVILEGE_UNPRIVILEGED, MODE_EXEC,
          "Calculate perimeter of polygon\nhas embedded "
          "newline\nand_a_really_long_line_that_is_much_longer_than_80_columns_to_show_that_wrap_case");
      o = cli_register_optarg(c, "transparent", CLI_CMD_OPTIONAL_FLAG, PRIVILEGE_UNPRIVILEGED, MODE_EXEC,
                              "Set transparent flag", NULL, NULL, NULL);
      cli_optarg_addhelp(o, "transparent", "(any case)set to transparent");
    
      cli_register_optarg(
          c, "verbose", CLI_CMD_OPTIONAL_FLAG | CLI_CMD_OPTION_MULTIPLE, PRIVILEGE_UNPRIVILEGED, MODE_EXEC,
          "Set verbose flag with some humongously long string \nwithout any embedded newlines in it to test with", NULL,
          NULL, NULL);
      o = cli_register_optarg(c, "color", CLI_CMD_OPTIONAL_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Set color",
                              color_completor, color_validator, NULL);
      cli_optarg_addhelp(o, "black", "the color 'black'");
      cli_optarg_addhelp(o, "white", "the color 'white'");
      cli_optarg_addhelp(o, "gray", "the color 'gray'");
      cli_optarg_addhelp(o, "red", "the color 'red'");
      cli_optarg_addhelp(o, "blue", "the color 'blue'");
      cli_optarg_addhelp(o, "green", "the color 'green'");
      cli_optarg_addhelp(o, "lightred", "the color 'lightred'");
      cli_optarg_addhelp(o, "lightblue", "the color 'lightblue'");
      cli_optarg_addhelp(o, "lightgreen", "the color 'lightgreen'");
      cli_optarg_addhelp(o, "darkred", "the color 'darkred'");
      cli_optarg_addhelp(o, "darkblue", "the color 'darkblue'");
      cli_optarg_addhelp(o, "darkgreen", "the color 'darkgreen'");
      cli_optarg_addhelp(o, "lavender", "the color 'lavender'");
      cli_optarg_addhelp(o, "yellow", "the color 'yellow'");
    
      cli_register_optarg(c, "__check1__", CLI_CMD_SPOT_CHECK, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL, NULL,
                          check1_validator, NULL);
      o = cli_register_optarg(c, "shape", CLI_CMD_ARGUMENT | CLI_CMD_ALLOW_BUILDMODE, PRIVILEGE_UNPRIVILEGED, MODE_EXEC,
                              "Specify shape(shows subtext on help)", shape_completor, shape_validator,
                              shape_transient_eval);
      cli_optarg_addhelp(o, "triangle", "specify a triangle");
      cli_optarg_addhelp(o, "rectangle", "specify a rectangle");
    
      cli_register_optarg(c, "side_1", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_TRIANGLE,
                          "Specify side 1 length", NULL, side_length_validator, NULL);
      cli_register_optarg(c, "side_1"