libvirt-virsh参数解析代码解读

来源:互联网 发布:映射的端口号怎么看 编辑:程序博客网 时间:2024/05/20 14:20
virsh是libvirt的一个命令行工具。libvirt想知道用户是在请求什么操作,都是通过分析出入的参数来确定的。virsh中解析参数的函数是virshParseArgv。该函数的具体调用时virshParseArgv(ctl, argc, argv)。ctl是一个全局的结构体变量,argc是传入的参数个数,argv是参数指针数组。virshParseArgv函数的代码如下:
static boolvirshParseArgv(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函数。
boolvshCommandStringParse(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 boolvshCommandParse(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函数,该函数的定义如下:

boolvshCommandArgvParse(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参数解析部分结束。

0 0
原创粉丝点击