您的位置:首页 > 编程语言

libvirt-virsh参数解析代码解读

2016-06-09 22:20 267 查看
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参数解析部分结束。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: