libvirt-virsh参数解析代码解读

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014104588/article/details/51622953
virsh是libvirt的一个命令行工具。libvirt想知道用户是在请求什么操作,都是通过分析出入的参数来确定的。
virsh中解析参数的函数是virshParseArgv。该函数的具体调用时virshParseArgv(ctl, argc, argv)。ctl是一个全局的结构体变量,argc是传入的参数个数,argv是参数指针数组。
virshParseArgv函数的代码如下:
static bool
virshParseArgv(vshControl *ctl, int argc, char **argv)
{
    int arg, len, debug, keepalive;
    size_t i;
    int longindex = -1;
    virshControlPtr priv = ctl->privData;
    struct option opt[] = {
        {"connect", required_argument, NULL, 'c'},
        {"debug", required_argument, NULL, 'd'},
        {"escape", required_argument, NULL, 'e'},
        {"help", no_argument, NULL, 'h'},
        {"keepalive-interval", required_argument, NULL, 'k'},
        {"keepalive-count", required_argument, NULL, 'K'},
        {"log", required_argument, NULL, 'l'},
        {"quiet", no_argument, NULL, 'q'},
        {"readonly", no_argument, NULL, 'r'},
        {"timing", no_argument, NULL, 't'},
        {"version", optional_argument, NULL, 'v'},
        {NULL, 0, NULL, 0}
    };

    /* Standard (non-command) options. The leading + ensures that no
     * argument reordering takes place, so that command options are
     * not confused with top-level virsh options. */
    while ((arg = getopt_long(argc, argv, "+:c:d:e:hk:K:l:qrtvV", opt, &longindex)) != -1) {
        switch (arg) {
        case 'c':
            VIR_FREE(ctl->connname);
            ctl->connname = vshStrdup(ctl, optarg);
            break;
        case 'd':
            if (virStrToLong_i(optarg, NULL, 10, &debug) < 0) {
                vshError(ctl, _("option %s takes a numeric argument"),
                         longindex == -1 ? "-d" : "--debug");
                exit(EXIT_FAILURE);
            }
            if (debug < VSH_ERR_DEBUG || debug > VSH_ERR_ERROR)
                vshError(ctl, _("ignoring debug level %d out of range [%d-%d]"),
                         debug, VSH_ERR_DEBUG, VSH_ERR_ERROR);
            else
                ctl->debug = debug;
            break;
        case 'e':
            len = strlen(optarg);

            if ((len == 2 && *optarg == '^' &&
                 virshAllowedEscapeChar(optarg[1])) ||
                (len == 1 && *optarg != '^')) {
                priv->escapeChar = optarg;
            } else {
                vshError(ctl, _("Invalid string '%s' for escape sequence"),
                         optarg);
                exit(EXIT_FAILURE);
            }
            break;
        case 'h':
            virshUsage();
            exit(EXIT_SUCCESS);
            break;
        case 'k':
            if (virStrToLong_i(optarg, NULL, 0, &keepalive) < 0) {
                vshError(ctl,
                         _("Invalid value for option %s"),
                         longindex == -1 ? "-k" : "--keepalive-interval");
                exit(EXIT_FAILURE);
            }

            if (keepalive < 0) {
                vshError(ctl,
                         _("option %s requires a positive integer argument"),
                         longindex == -1 ? "-k" : "--keepalive-interval");
                exit(EXIT_FAILURE);
            }
            ctl->keepalive_interval = keepalive;
            break;
        case 'K':
            if (virStrToLong_i(optarg, NULL, 0, &keepalive) < 0) {
                vshError(ctl,
                         _("Invalid value for option %s"),
                         longindex == -1 ? "-K" : "--keepalive-count");
                exit(EXIT_FAILURE);
            }

            if (keepalive < 0) {
                vshError(ctl,
                         _("option %s requires a positive integer argument"),
                         longindex == -1 ? "-K" : "--keepalive-count");
                exit(EXIT_FAILURE);
            }
            ctl->keepalive_count = keepalive;
            break;
        case 'l':
            vshCloseLogFile(ctl);
            ctl->logfile = vshStrdup(ctl, optarg);
            vshOpenLogFile(ctl);
            break;
        case 'q':
            ctl->quiet = true;
            break;
        case 't':
            ctl->timing = true;
            break;
        case 'r':
            priv->readonly = true;
            break;
        case 'v':
            if (STRNEQ_NULLABLE(optarg, "long")) {
                puts(VERSION);
                exit(EXIT_SUCCESS);
            }
            /* fall through */
        case 'V':
            virshShowVersion(ctl);
            exit(EXIT_SUCCESS);
        case ':':
            for (i = 0; opt[i].name != NULL; i++) {
                if (opt[i].val == optopt)
                    break;
            }
            if (opt[i].name)
                vshError(ctl, _("option '-%c'/'--%s' requires an argument"),
                         optopt, opt[i].name);
            else
                vshError(ctl, _("option '-%c' requires an argument"), optopt);
            exit(EXIT_FAILURE);
        case '?':
            if (optopt)
                vshError(ctl, _("unsupported option '-%c'. See --help."), optopt);
            else
                vshError(ctl, _("unsupported option '%s'. See --help."), argv[optind - 1]);
            exit(EXIT_FAILURE);
        default:
            vshError(ctl, _("unknown option"));
            exit(EXIT_FAILURE);
        }
        longindex = -1;
    }

    if (argc == optind) {  /*no option , command is onli "virsh"*/
        ctl->imode = true;
    } else {
        /* parse command */
        ctl->imode = false;
        if (argc - optind == 1) {
            vshDebug(ctl, VSH_ERR_INFO, "commands: \"%s\"\n", argv[optind]);
            return vshCommandStringParse(ctl, argv[optind]);
        } else {
            return vshCommandArgvParse(ctl, argc - optind, argv + optind);
        }
    }
    return true;
}
 首先调用getopt_long函数解析命令行传入的参数是否有符合"+:c:d:e:hk:K:l:qrtvV"格式。该格式标示如果有'+','c','d','e','k','l'选项,则该选项后必须跟参数,参数可以紧跟在选项后,也可以用空格隔开。
 如果有-c选项时,表明需要更改连接,ctl->connname = vshStrdup(ctl, optarg);表示将传入的连接字符串传给ctl->conname。在参数解析完毕后,会调用virshReconnect函数进行连接,此时需要用到ctl->connname。
 如果有-d选项时,表明需要更改日志等级,将传入的等级赋值给ctl->debug。
 如果有-h选项时,表明是显示帮助信息,调用virshUsage来显示帮助信息。
 如果有-l选项时,表明是更改日志文件,先关闭以前的日志文件,然后打开新的日志文件。
 如果有-q选项时,表明是结束virsh进程,当virsh是循环模式时,设置ctl->quiet = true。会让描述符监听线程退出。
 如果最后argc == optind,则说明用户是执行virsh的循环模式。此时并没有输入具体的除了配置操作意外的操作,此时将ctl->imode = true,表明是循环模式。
 如果最后argc - optind == 1,则说明用户只输入了一个不带参数的命令,比如virsh list,此时调用vshCommandStringParse(ctl, argv[optind])来解析该命令。
 如果最后argc - optind > 1,则说明用户输入了带参数的命令,比如virsh start vm,此时调用vshCommandArgvParse(ctl, argc - optind, argv + optind)来解析该命令。
 首先先分析vshCommandStringParse函数。
bool
vshCommandStringParse(vshControl *ctl, char *cmdstr)
{
    vshCommandParser parser;

    if (cmdstr == NULL || *cmdstr == '\0')
        return false;

    parser.pos = cmdstr;
    parser.getNextArg = vshCommandStringGetArg;
    return vshCommandParse(ctl, &parser);
}

parser.pos指向第一个命令名字。vshCommandStringGetArg函数用来取出传入的操作名字。进入vshCommandParse函数

static bool
vshCommandParse(vshControl *ctl, vshCommandParser *parser)
{
    char *tkdata = NULL;
    vshCmd *clast = NULL;
    vshCmdOpt *first = NULL;

    if (ctl->cmd) {
        vshCommandFree(ctl->cmd);
        ctl->cmd = NULL;
    }

    while (1) {
        vshCmdOpt *last = NULL;
        const vshCmdDef *cmd = NULL;
        vshCommandToken tk;
        bool data_only = false;
        uint32_t opts_need_arg = 0;
        uint32_t opts_required = 0;
        uint32_t opts_seen = 0;

        first = NULL;

        while (1) {
            const vshCmdOptDef *opt = NULL;

            tkdata = NULL;
            tk = parser->getNextArg(ctl, parser, &tkdata);

            if (tk == VSH_TK_ERROR)
                goto syntaxError;
            if (tk != VSH_TK_ARG) {
                VIR_FREE(tkdata);
                break;
            }

            if (cmd == NULL) {
                /* first token must be command name */
                if (!(cmd = vshCmddefSearch(tkdata))) {
                    vshError(ctl, _("unknown command: '%s'"), tkdata);
                    goto syntaxError;   /* ... or ignore this command only? */
                }
                if (vshCmddefOptParse(cmd, &opts_need_arg,
                                      &opts_required) < 0) {
                    vshError(ctl,
                             _("internal error: bad options in command: '%s'"),
                             tkdata);
                    goto syntaxError;
                }
                VIR_FREE(tkdata);
            } else if (data_only) {
                goto get_data;
            } else if (tkdata[0] == '-' && tkdata[1] == '-' &&
                       c_isalnum(tkdata[2])) {
                char *optstr = strchr(tkdata + 2, '=');
                int opt_index = 0;

                if (optstr) {
                    *optstr = '\0'; /* convert the '=' to '\0' */
                    optstr = vshStrdup(ctl, optstr + 1);
                }
                /* Special case 'help' to ignore all spurious options */
                if (!(opt = vshCmddefGetOption(ctl, cmd, tkdata + 2,
                                               &opts_seen, &opt_index,
                                               &optstr))) {
                    VIR_FREE(optstr);
                    if (STREQ(cmd->name, "help"))
                        continue;
                    goto syntaxError;
                }
                VIR_FREE(tkdata);

                if (opt->type != VSH_OT_BOOL) {
                    /* option data */
                    if (optstr)
                        tkdata = optstr;
                    else
                        tk = parser->getNextArg(ctl, parser, &tkdata);
                    if (tk == VSH_TK_ERROR)
                        goto syntaxError;
                    if (tk != VSH_TK_ARG) {
                        vshError(ctl,
                                 _("expected syntax: --%s <%s>"),
                                 opt->name,
                                 opt->type ==
                                 VSH_OT_INT ? _("number") : _("string"));
                        goto syntaxError;
                    }
                    if (opt->type != VSH_OT_ARGV)
                        opts_need_arg &= ~(1 << opt_index);
                } else {
                    tkdata = NULL;
                    if (optstr) {
                        vshError(ctl, _("invalid '=' after option --%s"),
                                 opt->name);
                        VIR_FREE(optstr);
                        goto syntaxError;
                    }
                }
            } else if (tkdata[0] == '-' && tkdata[1] == '-' &&
                       tkdata[2] == '\0') {
                data_only = true;
                continue;
            } else {
 get_data:
                /* Special case 'help' to ignore spurious data */
                if (!(opt = vshCmddefGetData(cmd, &opts_need_arg,
                                             &opts_seen)) &&
                     STRNEQ(cmd->name, "help")) {
                    vshError(ctl, _("unexpected data '%s'"), tkdata);
                    goto syntaxError;
                }
            }
            if (opt) {
                /* save option */
                vshCmdOpt *arg = vshMalloc(ctl, sizeof(vshCmdOpt));

                arg->def = opt;
                arg->data = tkdata;
                arg->next = NULL;
                tkdata = NULL;

                if (!first)
                    first = arg;
                if (last)
                    last->next = arg;
                last = arg;

                vshDebug(ctl, VSH_ERR_INFO, "%s: %s(%s): %s\n",
                         cmd->name,
                         opt->name,
                         opt->type != VSH_OT_BOOL ? _("optdata") : _("bool"),
                         opt->type != VSH_OT_BOOL ? arg->data : _("(none)"));
            }
        }

        /* command parsed -- allocate new struct for the command */
        if (cmd) {
            vshCmd *c = vshMalloc(ctl, sizeof(vshCmd));
            vshCmdOpt *tmpopt = first;

            /* if we encountered --help, replace parsed command with
             * 'help <cmdname>' */
            for (tmpopt = first; tmpopt; tmpopt = tmpopt->next) {
                if (STRNEQ(tmpopt->def->name, "help"))
                    continue;

                const vshCmdDef *help = vshCmddefSearch("help");
                vshCommandOptFree(first);
                first = vshMalloc(ctl, sizeof(vshCmdOpt));
                first->def = help->opts;
                first->data = vshStrdup(ctl, cmd->name);
                first->next = NULL;

                cmd = help;
                opts_required = 0;
                opts_seen = 0;
                break;
            }

            c->opts = first;
            c->def = cmd;
            c->next = NULL;

            if (vshCommandCheckOpts(ctl, c, opts_required, opts_seen) < 0) {
                VIR_FREE(c);
                goto syntaxError;
            }

            if (!ctl->cmd)
                ctl->cmd = c;
            if (clast)
                clast->next = c;
            clast = c;
        }

        if (tk == VSH_TK_END)
            break;
    }

    return true;

 syntaxError:
    if (ctl->cmd) {
        vshCommandFree(ctl->cmd);
        ctl->cmd = NULL;
    }
    if (first)
        vshCommandOptFree(first);
    VIR_FREE(tkdata);
    return false;
}

对于无参数操作,第一次调用getNextArg即vshCommandStringGetArg是取出操作的名字。第二次返回VSH_TK_END,然后退出循环。当第一次取出操作的名字后,会进入到下面代码块

            if (cmd == NULL) {
                /* first token must be command name */
                if (!(cmd = vshCmddefSearch(tkdata))) {
                    vshError(ctl, _("unknown command: '%s'"), tkdata);
                    goto syntaxError;   /* ... or ignore this command only? */
                }
                if (vshCmddefOptParse(cmd, &opts_need_arg,
                                      &opts_required) < 0) {
                    vshError(ctl,
                             _("internal error: bad options in command: '%s'"),
                             tkdata);
                    goto syntaxError;
                }
                VIR_FREE(tkdata);
            }
首先利用操作的名字调用vshCmddefSearch寻找操作函数。该函数内部最终会调用vshCmdDefSearchGrp,该函数代码如下:
static const vshCmdDef *
vshCmdDefSearchGrp(const char *cmdname)
{
    const vshCmdGrp *g;
    const vshCmdDef *c;

    for (g = cmdGroups; g->name; g++) {
        for (c = g->commands; c->name; c++) {
            if (STREQ(c->name, cmdname))
                return c;
        }
    }

    return NULL;
}

vshCmdDefSearchGrp利用操作名字在cmdGroup数组中寻找对应的操作结构体。最总返回的结构体变量指针格式如下:

const vshCmdDef domManagementCmds[] = {
    {.name = "attach-device",
     .handler = cmdAttachDevice,
     .opts = opts_attach_device,
     .info = info_attach_device,
     .flags = 0
    }
    }

寻找到操作对应的结构体变量后,然后调用vshCmddefOptParse(cmd, &opts_need_arg, &opts_required)来确定需要的参数。对于virsh list不需要参数所以opts_need_arg和opts_required都为0。最总执行完vshCommandParse函数后,结构体变量的关系是ctl->cmd->def = cmd;

如果是带参数的操作,则会调用vshCommandArgvParse函数,该函数的定义如下:

bool
vshCommandArgvParse(vshControl *ctl, int nargs, char **argv)
{
    vshCommandParser parser;

    if (nargs <= 0)
        return false;

    parser.arg_pos = argv;
    parser.arg_end = argv + nargs;
    parser.getNextArg = vshCommandArgvGetArg;
    return vshCommandParse(ctl, &parser);
}

进入vshCommandParse函数,以virsh list –all分析
首先第一个解析的list,也会进入到if(cmd == NULL)代码块,同时opts_need_arg和opts_required都为0,第二次解析到”–all”,然后进入到下面的代码块

 else if (tkdata[0] == '-' && tkdata[1] == '-' &&
                       c_isalnum(tkdata[2])) {
                char *optstr = strchr(tkdata + 2, '=');
                int opt_index = 0;

                if (optstr) {
                    *optstr = '\0'; /* convert the '=' to '\0' */
                    optstr = vshStrdup(ctl, optstr + 1);
                }
                /* Special case 'help' to ignore all spurious options */
                if (!(opt = vshCmddefGetOption(ctl, cmd, tkdata + 2,
                                               &opts_seen, &opt_index,
                                               &optstr))) {
                    VIR_FREE(optstr);
                    if (STREQ(cmd->name, "help"))
                        continue;
                    goto syntaxError;
                }
                VIR_FREE(tkdata);

                if (opt->type != VSH_OT_BOOL) {
                    /* option data */
                    if (optstr)
                        tkdata = optstr;
                    else
                        tk = parser->getNextArg(ctl, parser, &tkdata);
                    if (tk == VSH_TK_ERROR)
                        goto syntaxError;
                    if (tk != VSH_TK_ARG) {
                        vshError(ctl,
                                 _("expected syntax: --%s <%s>"),
                                 opt->name,
                                 opt->type ==
                                 VSH_OT_INT ? _("number") : _("string"));
                        goto syntaxError;
                    }
                    if (opt->type != VSH_OT_ARGV)
                        opts_need_arg &= ~(1 << opt_index);
                } else {
                    tkdata = NULL;
                    if (optstr) {
                        vshError(ctl, _("invalid '=' after option --%s"),
                                 opt->name);
                        VIR_FREE(optstr);
                        goto syntaxError;
                    }
                }
            }

vshCmddefGetOption返回all参数的详细信息,该代码块定义后,因为opt已经获得了值,则会进入下面的代码块

if (opt) {
                /* save option */
                vshCmdOpt *arg = vshMalloc(ctl, sizeof(vshCmdOpt));

                arg->def = opt;
                arg->data = tkdata;
                arg->next = NULL;
                tkdata = NULL;

                if (!first)
                    first = arg;
                if (last)
                    last->next = arg;
                last = arg;

                vshDebug(ctl, VSH_ERR_INFO, "%s: %s(%s): %s\n",
                         cmd->name,
                         opt->name,
                         opt->type != VSH_OT_BOOL ? _("optdata") : _("bool"),
                         opt->type != VSH_OT_BOOL ? arg->data : _("(none)"));
            }

可以看见arg->def = opt。first = last = arg。退出最内层的循环后,就进入了下面的代码块

if (cmd) {
            vshCmd *c = vshMalloc(ctl, sizeof(vshCmd));
            vshCmdOpt *tmpopt = first;

            /* if we encountered --help, replace parsed command with
             * 'help <cmdname>' */
            for (tmpopt = first; tmpopt; tmpopt = tmpopt->next) {
                if (STRNEQ(tmpopt->def->name, "help"))
                    continue;

                const vshCmdDef *help = vshCmddefSearch("help");
                vshCommandOptFree(first);
                first = vshMalloc(ctl, sizeof(vshCmdOpt));
                first->def = help->opts;
                first->data = vshStrdup(ctl, cmd->name);
                first->next = NULL;

                cmd = help;
                opts_required = 0;
                opts_seen = 0;
                break;
            }

            c->opts = first;
            c->def = cmd;
            c->next = NULL;

            if (vshCommandCheckOpts(ctl, c, opts_required, opts_seen) < 0) {
                VIR_FREE(c);
                goto syntaxError;
            }

            if (!ctl->cmd)
                ctl->cmd = c;
            if (clast)
                clast->next = c;
            clast = c;
        }

因为参数里没有”helo”所以,for循环直接跳过去。最后结构体变量之间的关系是:ctl->cmd->opt=first, ctl->cmd->def = cmd, first就是命令的参数信息链表。

如果是virsh start vm1之类的参数不可省的命令(list后的–all参数可省可不省),还要分析参数个数是否匹配。
一开始分析命令名字的步骤和其他命令一样。然后第二次利用vshCmddefOptParse函数获取参数信息,经查询virsh start 命令的参数的信息如下:

    static const vshCmdOptDef opts_start[] = {
    VIRSH_COMMON_OPT_DOMAIN(N_("name of the inactive domain")),
#ifndef WIN32
    {.name = "console",
     .type = VSH_OT_BOOL,
     .help = N_("attach to console after creation")
    },
#endif
    {.name = "paused",
     .type = VSH_OT_BOOL,
     .help = N_("leave the guest paused after creation")
    },
    {.name = "autodestroy",
     .type = VSH_OT_BOOL,
     .help = N_("automatically destroy the guest when virsh disconnects")
    },
    {.name = "bypass-cache",
     .type = VSH_OT_BOOL,
     .help = N_("avoid file system cache when loading")
    },
    {.name = "force-boot",
     .type = VSH_OT_BOOL,
     .help = N_("force fresh boot by discarding any managed save")
    },
    {.name = "pass-fds",
     .type = VSH_OT_STRING,
     .help = N_("pass file descriptors N,M,... to the guest")
    },
    {.name = NULL}
};

最后opts_need_arg = 1<<5,opts_required为0。1<<5就表示需要第五个(从0开始)参数。
解析完命令名字后,第二次解析的字符串就是命令所需参数,如果解析到了,就说明操作有参数,在参数解析阶段不会判断参数是否正确,只确定参数是否存在。命令的参数解析到后,利用vshCmddefGetData来获得参数的详细信息。同时opts_seen = 1 << 5表示,表示参数列表中的第五个已经获取到了。在获取到命令的参数的结构体后,会进入到下面的代码块:

if (opt) {
                /* save option */
                vshCmdOpt *arg = vshMalloc(ctl, sizeof(vshCmdOpt));

                arg->def = opt;
                arg->data = tkdata;
                arg->next = NULL;
                tkdata = NULL;

                if (!first)
                    first = arg;
                if (last)
                    last->next = arg;
                last = arg;

                vshDebug(ctl, VSH_ERR_INFO, "%s: %s(%s): %s\n",
                         cmd->name,
                         opt->name,
                         opt->type != VSH_OT_BOOL ? _("optdata") : _("bool"),
                         opt->type != VSH_OT_BOOL ? arg->data : _("(none)"));
            }

可以看到以first开头的链表存储了参数的名字和具体信息。arg->data = tkdata就是复制参数的名字的地址。
最后vshCommandCheckOpts就是判断命令参数有没有获取正确。

至此,virsh参数解析部分结束。

猜你喜欢

转载自blog.csdn.net/u014104588/article/details/51622953