grub-mkimage源码分析—1
本章开始分析grub-mkimage的源码,首先来看grub-mkimage文件的生成过程,从Makefile开始看。grub-mkimage目标定义在grub源码的顶层Makefile文件中。
grub-mkimage
Makefile
grub-mkimage$(EXEEXT): $(grub_mkimage_OBJECTS) $(grub_mkimage_DEPENDENCIES) $(EXTRA_grub_mkimage_DEPENDENCIES)
@rm -f grub-mkimage$(EXEEXT)
$(AM_V_CCLD)$(grub_mkimage_LINK) $(grub_mkimage_OBJECTS) $(grub_mkimage_LDADD) $(LIBS)
grub_mkimage_OBJECTS目标包含了链接grub-mkimage所需的最主要的目标文件。grub_mkimage_DEPENDENCIES目标包含了诸多grub生成的静态库文件。EXTRA_grub_mkimage_DEPENDENCIES为空定义。grub_mkimage_LINK为链接命令,grub_mkimage_LDADD包含了链接时所需的选项。下面重点看一下grub_mkimage_OBJECTS的定义。
grub-mkimage->grub_mkimage_OBJECTS
Makefile
grub_mkimage_OBJECTS = $(am_grub_mkimage_OBJECTS) \
$(nodist_grub_mkimage_OBJECTS)
其中nodist_grub_mkimage_OBJECTS为空定义。am_grub_mkimage_OBJECTS的定义如下,
am_grub_mkimage_OBJECTS = util/grub_mkimage-grub-mkimage.$(OBJEXT) \
util/grub_mkimage-mkimage.$(OBJEXT) \
util/grub_mkimage-grub-mkimage32.$(OBJEXT) \
util/grub_mkimage-grub-mkimage64.$(OBJEXT) \
util/grub_mkimage-resolve.$(OBJEXT) \
grub-core/kern/emu/grub_mkimage-argp_common.$(OBJEXT) \
grub-core/osdep/grub_mkimage-init.$(OBJEXT) \
grub-core/osdep/grub_mkimage-config.$(OBJEXT) \
util/grub_mkimage-config.$(OBJEXT)
grub_mkimage-grub-mkimage.o由util/grub-mkimage.c编译而成。
util/grub_mkimage-grub-mkimage32.o由util/grub-mkimage32.c编译而成。
util/grub_mkimage-grub-mkimage64.o由util/grub-mkimage64.c编译而成。
util/grub_mkimage-resolve.o由util/resolve.c编译而成。
grub-core/kern/emu/grub_mkimage-argp_common.o由grub-core/kern/emu/argp_common.c编译而成。
grub-core/osdep/grub_mkimage-init.o由grub-core/osdep/init.c编译而成。
grub-core/osdep/grub_mkimage-config.o由grub-core/osdep/config.c编译而成。
util/grub_mkimage-config.o由util/config.c编译而成。
编译链接后,生成了grub-mkimage可执行文件,其入口函数定义在grub-mkimage.c文件中,下面开始看源码。
main
util/grub-mkimage.c
int main (int argc, char *argv[]) {
FILE *fp = stdout;
struct arguments arguments;
unsigned i;
grub_util_host_init (&argc, &argv);
memset (&arguments, 0, sizeof (struct arguments));
arguments.comp = GRUB_COMPRESSION_AUTO;
arguments.modules_max = argc + 1;
arguments.modules = xmalloc ((arguments.modules_max + 1)
* sizeof (arguments.modules[0]));
memset (arguments.modules, 0, (arguments.modules_max + 1)
* sizeof (arguments.modules[0]));
if (argp_parse (&argp, argc, argv, 0, 0, &arguments) != 0) {
fprintf (stderr, "%s", _("Error in parsing command line arguments\n"));
exit(1);
}
...
本章只看main函数的第一部分。stdout为标准输出文件句柄,用来输出执行过程中的信息。grub_util_host_init函数的主要作用是将应用程序名,即”grub-mkimage”设置到全局变量program_name中。arguments结构保存了用于制作映像的各个参数,首先清0该结构,然后设置压缩方式comp为GRUB_COMPRESSION_AUTO。
设置模块的最大数量为参数的最大数量加1,并分配内存。然后调用argp_parse对传入的参数进行解析,将解析结果保存在arguments中,用于后面的处理,传入的参数argp是grub-mkimage这个可执行程序自定义的参数解析器。
main->grub_util_host_init
grub-core/osdep/basic/init.c
void grub_util_host_init (int *argc __attribute__ ((unused)), char ***argv) {
set_program_name ((*argv)[0]);
}
void set_program_name (const char *argv0) {
const char *slash;
const char *base;
slash = strrchr (argv0, '/');
base = (slash != NULL ? slash + 1 : argv0);
if (base - argv0 >= 7 && strncmp (base - 7, "/.libs/", 7) == 0) {
argv0 = base;
if (strncmp (base, "lt-", 3) == 0) {
argv0 = base + 3;
}
}
program_name = argv0;
}
grub_util_host_init调用set_program_name设置程序名,argv数组的第一个元素即应用程序名。
set_program_name函数首先通过strrchr函数找到参数中最后一个下划线出现的位置,接下来计算的base就指向最后文件名的起始地址。如果最终文件名之前的路径长度大于7个字符,并且最后的目录名为”/.libs/”,则检查路径后是不是”lt-“,即”/.libs/lt-“,此时最终的文件名argv0需要忽略掉该前缀”lt-“。最后把最终文件名设置到全局变量program_name中。
接下来看本章的重点,即__argp_parse函数。
main->argp_parse
grub-core/gnulib/argp-parse.c
error_t __argp_parse (const struct argp *argp, int argc, char **argv, unsigned flags,
int *end_index, void *input) {
error_t err;
struct parser parser;
int arg_ebadkey = 0;
if (! (flags & ARGP_NO_HELP)) {
struct argp_child *child = alloca (4 * sizeof (struct argp_child));
struct argp *top_argp = alloca (sizeof (struct argp));
memset (top_argp, 0, sizeof (*top_argp));
top_argp->children = child;
memset (child, 0, 4 * sizeof (struct argp_child));
if (argp)
(child++)->argp = argp;
(child++)->argp = &argp_default_argp;
if (argp_program_version || argp_program_version_hook)
(child++)->argp = &argp_version_argp;
child->argp = 0;
argp = top_argp;
}
err = parser_init (&parser, argp, argc, argv, flags, input);
if (! err) {
while (! err)
err = parser_parse_next (&parser, &arg_ebadkey);
err = parser_finalize (&parser, err, arg_ebadkey, end_index);
}
return err;
}
__argp_parse函数完成参数数组argv的解析,并将解析的结果保存到返回参数input中,即main函数中的arguments结构体。
argp结构体代表参数的解析器,多个argp结构体通过argp_child结构构成树形结构。传入的参数flags为0,因此进入第一个if,分配4个argp_child结构体和一个顶层解析器top_argp,其中前三个argp_child指向默认的解析器,第四个argp_child默认为空,标识子解析器的结束。其中三个默认的解析器分别为argp、argp_default_argp和argp_version_argp,定义如下,
static struct argp argp = {
options, argp_parser, N_("[OPTION]... [MODULES]"),
N_("Make a bootable image of GRUB."),
NULL, help_filter, NULL
};
static const struct argp argp_default_argp =
{argp_default_options, &argp_default_parser, NULL, NULL, NULL, NULL, "libc"};
static const struct argp argp_version_argp =
{argp_version_options, &argp_version_parser, NULL, NULL, NULL, NULL, "libc"};
parser_init函数根据上面定义的默认解析器构造了一个parser结构体,其中保存了如何解析一个具体的命令或参数的方法。接下来通过一个while循环依次解析命令行中的每个选项,并将主要的解析结果保存在input中,最后通过parser_finalize完成一些收尾工作。下面重点看parser_init函数和parser_parse_next函数。
parser_init
main->argp_parse->parser_init
grub-core/gnulib/argp-parse.c
static error_t parser_init (struct parser *parser, const struct argp *argp,
int argc, char **argv, int flags, void *input) {
error_t err = 0;
struct group *group;
struct parser_sizes szs;
struct _getopt_data opt_data = _GETOPT_DATA_INITIALIZER;
char *storage;
size_t glen, gsum;
size_t clen, csum;
size_t llen, lsum;
size_t slen, ssum;
szs.short_len = (flags & ARGP_NO_ARGS) ? 0 : 1;
szs.long_len = 0;
szs.num_groups = 0;
szs.num_child_inputs = 0;
if (argp)
calc_sizes (argp, &szs);
glen = (szs.num_groups + 1) * sizeof (struct group);
clen = szs.num_child_inputs * sizeof (void *);
llen = (szs.long_len + 1) * sizeof (struct option);
slen = szs.short_len + 1;
gsum = glen;
csum = alignto (gsum + clen, alignof (struct option));
lsum = csum + llen;
ssum = lsum + slen;
parser->storage = malloc (ssum);
if (! parser->storage)
return ENOMEM;
storage = parser->storage;
parser->groups = parser->storage;
parser->child_inputs = (void **) (storage + gsum);
parser->long_opts = (struct option *) (storage + csum);
parser->short_opts = storage + lsum;
parser->opt_data = opt_data;
memset (parser->child_inputs, 0, clen);
parser_convert (parser, argp, flags);
memset (&parser->state, 0, sizeof (struct argp_state));
parser->state.root_argp = parser->argp;
parser->state.argc = argc;
parser->state.argv = argv;
parser->state.flags = flags;
parser->state.err_stream = stderr;
parser->state.out_stream = stdout;
parser->state.next = 0;
parser->state.pstate = parser;
parser->try_getopt = 1;
if (parser->groups < parser->egroup)
parser->groups->input = input;
for (group = parser->groups;
group < parser->egroup && (!err || err == EBADKEY);
group++) {
if (group->parent)
group->input = group->parent->child_inputs[group->parent_index];
if (!group->parser && group->argp->children && group->argp->children->argp)
group->child_inputs[0] = group->input;
err = group_parse (group, &parser->state, ARGP_KEY_INIT, 0);
}
...
if (parser->state.argv == argv && argv[0])
parser->state.name = __argp_base_name (argv[0]);
else
parser->state.name = __argp_short_program_name ();
return 0;
}
parser_init函数首先通过calc_sizes函数计算为默认的解析器需要分配的内存空间,这些分配的内存空间即将存储parser的信息。其中glen长度对应的内存用于存放多个group结构,每个group用来代表一个默认的解析器;clen对应的内存用于存放每个group结构的input成员变量,该input成员变量在真正的解析函数group_parse中传入,用于保存解析的返回结果。llen对应的内存空间用于存放每个group中有关长名称选项的解析方法,slen对应的内存空间用于存放每个group中有关短名称选项的解析方法。
再往下的gsum、csum、lsum和ssum分别保存了前面glen、clen、llen和slen内存空间对应的相对内存起始地址。最后的ssum也对应了存储所有解析方法需要的总内存空间大小,接下来malloc该内存,起始地址保存到storage中,并设置前面四块内存的起始地址到groups、child_inputs、long_opts和short_opts中。最后设置的opt_data用于作为后面解析参数的中间变量,初始化为_GETOPT_DATA_INITIALIZER,定义为{1,1},其中第一个1对应成员变量optind,表示解析时argv数组的下标,第二个1对应成员变量opterr,表示是否需要打印解析时的错误信息。
然后通过parser_convert函数对默认的解析器进行分析,形成一个解析的模型,将该模型设置到parser中。接下来初始化state成员变量,其中的root_argp表示顶层的解析器,对应__argp_parse函数中创建的top_argp。argc和argv对应grub-mkimage传入的参数个数和参数地址。next变量对应参数数组中下一个要解析的数组下标,因此初始化为0。最后设置try_getopt为1,表示可以开始解析,当解析到最后一个参数时,会将其置0,大部分情况下,就表示解析完毕。
parser的egroup表示默认解析器中有效的解析器个数,例如,对于顶层的解析器top_argp,没有对应的parse函数指针和选项列表,因此被判定为一个无效的解析器,又例如,对于默认定义的4个解析器,第4个解析器用于标识二层解析器的结束(成员变量全为0),因此也是一个无效的解析器。正常情况下egroup大于group,因此接下来设置解析模型中第一个group的成员变量input指向参数input,对应main函数中的arguments。
接下来遍历group数组,parent指针指向父group,如果不为空,就根据父group的child_inputs数组设置子group的input指针(input指针初始化为0),这里能保证input指针的正确性是因为在parser_convert函数中构建group树形结构时采用的是深度优先遍历。最后,如果一个无效的group包含子group,并且子group有效,就将该无效group的input指针赋予子group数组中的第一个group的input指针。最后通过group_parse函数对每个group进行初始化,传入的key值为ARGP_KEY_INIT,如果某个group的parse函数中定义了该key值对应的函数,就会执行该函数进行初始化。
最后,正常情况下会进入第一个if语句,调用__argp_base_name宏获得应用程序名(其实就是argv[0]),并设置到state的name成员变量中。
main->argp_parse->parser_init->calc_sizes
grub-core/gnulib/argp-parse.c
static void calc_sizes (const struct argp *argp, struct parser_sizes *szs) {
const struct argp_child *child = argp->children;
const struct argp_option *opt = argp->options;
if (opt || argp->parser) {
szs->num_groups++;
if (opt) {
int num_opts = 0;
while (!__option_is_end (opt++))
num_opts++;
szs->short_len += num_opts * 3;
szs->long_len += num_opts;
}
}
if (child)
while (child->argp) {
calc_sizes ((child++)->argp, szs);
szs->num_child_inputs++;
}
}
calc_sizes函数用于计算为默认的解析器构造模型需要分配多少空间。传入的参数argp是由__argp_parse函数分配的顶层解析器top_argp,top_argp是一个无效的解析器,其成员变量除了children都为0,因此接下来会在最后的while循环中递归调用calc_sizes解析其子解析器,也即argp,argp_default_argp和argp_version_argp。对于默认的三个子解析器,其argp_option和parser都不为空,因此进入第一个if语句,此时递增num_groups,用于统计了有效的解析器的个数。如果该解析器存在选项,就通过__option_is_end辨别是否为最后一个选项,循环统计选项的个数num_opts,
#define __option_is_end _option_is_end
ARGP_EI int __NTH (__option_is_end (const struct argp_option *__opt)) {
return !__opt->key && !__opt->name && !__opt->doc && !__opt->group;
}
并根据当前选项个数递增short_len和long_len的值,分别表示用于存放短名称选项和长名称选项的内存大小,之所以将short_len设置为选项个数num_opts的3倍是因为每个短名称选项后面有可能有若干个参数,需要用剩余的2个字节存储参数的个数信息。
main->argp_parse->parser_init->parser_convert
grub-core/gnulib/argp-parse.c
static void parser_convert (struct parser *parser, const struct argp *argp, int flags) {
struct parser_convert_state cvt;
cvt.parser = parser;
cvt.short_end = parser->short_opts;
cvt.long_end = parser->long_opts;
cvt.child_inputs_end = parser->child_inputs;
if (flags & ARGP_IN_ORDER)
*cvt.short_end++ = '-';
else if (flags & ARGP_NO_ARGS)
*cvt.short_end++ = '+';
*cvt.short_end = '\0';
cvt.long_end->name = NULL;
parser->argp = argp;
if (argp)
parser->egroup = convert_options (argp, 0, 0, parser->groups, &cvt);
else
parser->egroup = parser->groups;
}
parser_convert函数对默认的解析器进行分析,将分析的结果存入parser中分配的内存空间storage处。其核心函数为convert_options,下面会分析。首先创建一个parser_convert_state结构,该结构是一个中间变量,然后对该结构进行初始化,传入的argp是前面的顶层解析器top_argp,因此最后会通过convert_options进行解析,返回的egroup表示从顶层解析器top_argp开始,往下有效解析器的个数。
main->argp_parse->parser_init->parser_convert->convert_options
grub-core/gnulib/argp-parse.c
static struct group * convert_options (const struct argp *argp,
struct group *parent, unsigned parent_index,
struct group *group, struct parser_convert_state *cvt) {
const struct argp_option *real = argp->options;
const struct argp_child *children = argp->children;
if (real || argp->parser) {
const struct argp_option *opt;
if (real)
for (opt = real; !__option_is_end (opt); opt++) {
if (! (opt->flags & OPTION_ALIAS))
real = opt;
if (! (real->flags & OPTION_DOC)) {
if (__option_is_short (opt)) {
*cvt->short_end++ = opt->key;
if (real->arg) {
*cvt->short_end++ = ':';
if (real->flags & OPTION_ARG_OPTIONAL)
*cvt->short_end++ = ':';
}
*cvt->short_end = '\0';
}
if (opt->name && find_long_option (cvt->parser->long_opts, opt->name) < 0) {
cvt->long_end->name = opt->name;
cvt->long_end->has_arg = (real->arg ? (real->flags & OPTION_ARG_OPTIONAL
? optional_argument : required_argument) : no_argument);
cvt->long_end->flag = 0;
cvt->long_end->val = ((opt->key ? opt->key : real->key) & USER_MASK)
+ (((group - cvt->parser->groups) + 1) << USER_BITS);
(++cvt->long_end)->name = NULL;
}
}
}
group->parser = argp->parser;
group->argp = argp;
group->short_end = cvt->short_end;
group->args_processed = 0;
group->parent = parent;
group->parent_index = parent_index;
group->input = 0;
group->hook = 0;
group->child_inputs = 0;
if (children) {
unsigned num_children = 0;
while (children[num_children].argp)
num_children++;
group->child_inputs = cvt->child_inputs_end;
cvt->child_inputs_end += num_children;
}
parent = group++;
}else
parent = 0;
if (children) {
unsigned index = 0;
while (children->argp)
group = convert_options (children++->argp, parent, index++, group, cvt);
}
return group;
}
convert_options是分析解析器的核心函数,默认的解析器argp只是定义了该解析器支持的参数格式,例如有些参数是短名称选项(例如-d),有些参数是长名称选项(例如-help),有些参数是非选项参数(例如main.c),经过convert_options分析后,会将所有解析器中相同格式的解析方法放入连续的内存空间中。
首先看一下默认解析器argp中的argp_option,
static struct argp_option options[] = {
{"directory", 'd', N_("DIR"), 0,
N_("use images and modules under DIR [default=%s/<platform>]"), 0},
{"prefix", 'p', N_("DIR"), 0, N_("set prefix directory"), 0},
{"memdisk", 'm', N_("FILE"), 0,
N_("embed FILE as a memdisk image\n"
"Implies `-p (memdisk)/boot/grub' and overrides any prefix supplied previously,"
" but the prefix itself can be overridden by later options"), 0},
{"config", 'c', N_("FILE"), 0, N_("embed FILE as an early config"), 0},
{"pubkey", 'k', N_("FILE"), 0, N_("embed FILE as public key for signature checking"), 0},
{"note", 'n', 0, 0, N_("add NOTE segment for CHRP IEEE1275"), 0},
{"output", 'o', N_("FILE"), 0, N_("output a generated image to FILE [default=stdout]"), 0},
{"format", 'O', N_("FORMAT"), 0, 0, 0},
{"compression", 'C', "(xz|none|auto)", 0, N_("choose the compression to use for core image"), 0},
{"verbose", 'v', 0, 0, N_("print verbose messages."), 0},
{ 0, 0, 0, 0, 0, 0 }
};
可以看出,该解析器全部定义了短名称选项。
初始传入的参数argp是顶层解析器top_argp,是一个无效的解析器,即除了children,其他成员变量都为0,因此直接执行到convert_options函数的最后,通过while循环递归调用convert_options函数对子解析器进行解析。
如果argp是一个有效的解析器,且包含选项列表options,就遍历该选项列表,如果一个选项的标志位既不包含OPTION_ALIAS也不包含OPTION_DOC,就通过__option_is_short函数检查该选项的key值是否在短名称选项key值的范围内,短名称选项的key值对应的ASCII码在32-126范围内,也即可显示字符。如果确定是一个短名称选项,就将该选项对应的key值首先存入short_end中,也即short_opts中,接下来如果该选项包含参数arg,就存入一个冒号,如果flags标志位中包含了OPTION_ARG_OPTIONAL,表示是可选参数,此时再存入一个冒号。如果是一个长名称选项,为了避免重复,首先通过find_long_option函数在long_opts中查找是否包含了相同名称的选项,如果存在,就什么也不做;反之,首先存入长名称选项的名称name,存入参数类型has_arg,标志位flag,val稍微麻烦点,其高位字节用于存入group索引,低位字节存入选项key值,高低位使用USER_BITS划分界限,USER_MASK是USER_BITS对应的低位掩码,注意在存入低位字节时,要兼容到标志位包含OPTION_ALIAS的选项。
解析完一个解析器的所有选项之后,就要对该解析器对应的group结构做一个整体的设置,其中设置了解析器的解析函数parser,解析器指针argp,短名称选项的分析结果地址short_end,父解析器parent,子解析在父解析器所有child的索引parent_index。再往下,如果一个解析器还包含子解析器,即children不为空,就要预留空间,group的child_inputs指向下一层第一个子解析器对应group结构的起始地址。因为是深度遍历解析器,接下来设置父解析器parent指向当前解析器对应的group结构地址。
main->argp_parse->parser_init->group_parse
grub-core/gnulib/argp-parse.c
static error_t group_parse (struct group *group, struct argp_state *state, int key, char *arg) {
if (group->parser) {
error_t err;
state->hook = group->hook;
state->input = group->input;
state->child_inputs = group->child_inputs;
state->arg_num = group->args_processed;
err = (*group->parser)(key, arg, state);
group->hook = state->hook;
return err;
} else
return EBADKEY;
}
group_parse函数调用每个解析器对应的函数parser解析参数arg,传入的参数key值即选项的key值,arg为参数地址,state变量用于保存解析结果和一些中间变量,例如对于默认的解析器argp,input用于保存解析结果,hook为钩子函数,用于返回时被调用。
parser_parse_next
parser_init函数解析默认的解析器,将结果保存在parser结构体中,接下来就利用parser通过parser_parse_next函数对传入的参数进行解析了,因为涉及到短名称选项、长名称选项和非选项参数类型的解析,考虑到篇幅和时间问题,下面省略和改写了部分代码,仅分析针对短名称选项的解析代码。
main->argp_parse->parser_parse_next
grub-core/gnulib/argp-parse.c
static error_t parser_parse_next (struct parser *parser, int *arg_ebadkey) {
...
if (parser->try_getopt && !parser->state.quoted) {
parser->opt_data.optind = parser->state.next;
opt = _getopt_long_r (parser->state.argc, parser->state.argv,
parser->short_opts, parser->long_opts, 0,
&parser->opt_data);
parser->state.next = parser->opt_data.optind;
if (opt == KEY_END) {
parser->try_getopt = 0;
}
}
...
err = parser_parse_opt (parser, opt, parser->opt_data.optarg);
...
return err;
}
parser_parse_next函数主要分为两部分,第一部分是通过_getopt_long_r函数从argv参数数组中取出下一个参数opt,第二部分通过parser_parse_opt函数分析取出的参数opt并进行相应的设置。
parser的try_getopt被初始化为1,state.quoted默认置0,进入第一个if语句。
next起始为0,表示下一个被解析的参数在argv数组中的下标,赋值给optind变量,optind表示当前即系的参数在argv数组中的下标。
接下来通过_getopt_long_r函数从argv数组中获取下一个带分析的参数,对于短名称选项而言,对应的字符,即key值,保存到返回值opt中,然后将optind指向下一个带解析参数的下标。如果返回值opt为KEY_END,则解析完毕,将try_getopt置0。
最后通过parser_parse_opt函数分析刚刚解析出来的参数,其中optarg表示某个选项对应的参数地址。
main->argp_parse->parser_parse_next->_getopt_long_r
grub-core/gnulib/getopt1.c
int _getopt_long_r (int argc, char **argv, const char *options,
const struct option *long_options, int *opt_index,
struct _getopt_data *d) {
return _getopt_internal_r (argc, argv, options, long_options, opt_index,
0, d, 0);
}
_getopt_long_r函数继而调用_getopt_internal_r函数进行解析,其中参数argc和argv就是传给grub-mkimage可执行文件的参数个数argc和参数地址argv。options和long_options分别保存了短名称选项和长名称选项的解析方法,这些方法在前面的parser_convert函数中创建。
_getopt_internal_r第一部分
main->argp_parse->parser_parse_next->_getopt_long_r->_getopt_internal_r
grub-core/gnulib/getopt.c
int _getopt_internal_r (int argc, char **argv, const char *optstring,
const struct option *longopts, int *longind,
int long_only, struct _getopt_data *d, int posixly_correct) {
...
d->optarg = NULL;
if (d->optind == 0 || !d->__initialized) {
if (d->optind == 0)
d->optind = 1;
optstring = _getopt_initialize (argc, argv, optstring, d,
posixly_correct);
d->__initialized = 1;
}
...
# define NONOPTION_P (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0')
if (d->__nextchar == NULL || *d->__nextchar == '\0') {
...
if (d->__ordering == PERMUTE) {
...
while (d->optind < argc && NONOPTION_P)
d->optind++;
}
...
if (d->optind == argc) {
...
return -1;
}
...
d->__nextchar = (argv[d->optind] + 1);
}
由前面的分析可知,此时的数组下标optind为0,并且还没有有初始化,也即__initialized为0,因此进入第一个if循环。如果数组下标optind为0,则将其置1,表示略过grub-mkimage这个应用程序名。然后调用_getopt_initialize函数进行初始化,并置位标志__initialized表示初始化完成。
_getopt_initialize函数主要设置了解析方式,默认的解析方式会先扫描带选项的参数,再扫描非选项参数。
这里定义的NONOPTION_P宏用来判断是否是一个选项参数或非选项参数,‘-’表示选项参数的开始字符,接下来通过一个while循环找到下一个选项参数对应的数组下标optind。
最后的__nextchar指向当前找到的选项名,也即key值,加1是因为要跳过选项最前边的‘-’符号。
_getopt_internal_r第二部分
main->argp_parse->parser_parse_next->_getopt_long_r->_getopt_internal_r
grub-core/gnulib/getopt.c
char c = *d->__nextchar++;
const char *temp = strchr (optstring, c);
if (*d->__nextchar == '\0')
++d->optind;
if (temp == NULL || c == ':' || c == ';') {
d->optopt = c;
return '?';
}
...
if (temp[1] == ':') {
if (temp[2] == ':') {
if (*d->__nextchar != '\0') {
d->optarg = d->__nextchar;
d->optind++;
} else
d->optarg = NULL;
d->__nextchar = NULL;
} else {
if (*d->__nextchar != '\0') {
d->optarg = d->__nextchar;
d->optind++;
} else
d->optarg = argv[d->optind++];
d->__nextchar = NULL;
}
}
return c;
}
字符c对应_getopt_internal_r函数第一部分最后获得的选项key值。strchr函数获得optstring中首先出现c字符的位置,optstring也即parser的short_opts成员变量,对应短名称选项的解析方法。如果__nextchar往下一个字符为空字符,则表示该选项没有参数。
如果在optstring中没有找到对应字符c的位置,或者该选项字符c是冒号或者分号,则出现错误,返回一个问号。接下来temp[1]如果为冒号,表示选项需要携带参数,当temp[2]也为冒号时,表示参数可选。
当参数可选时,如果__nextchar不为空,则存入参数的地址__nextchar至optarg中,并递增optind,否则无可选参数,最后将__nextchar指针置为空。当需要一个参数,如果__nextchar不为空,则一样存入参数的地址至optarg中,递增optind。如果__nextchar为空,就从argv数组的下一个参数处取值,这种情况有可能是shell的兼容问题。最后返回对应的选项字符c。
main->argp_parse->parser_parse_next->parser_parse_opt
grub-core/gnulib/argp-parse.c
static error_t parser_parse_opt (struct parser *parser, int opt, char *val) {
int group_key = opt >> USER_BITS;
error_t err = EBADKEY;
if (group_key == 0) {
struct group *group;
char *short_index = strchr (parser->short_opts, opt);
if (short_index)
for (group = parser->groups; group < parser->egroup; group++)
if (group->short_end > short_index) {
err = group_parse (group, &parser->state, opt,
parser->opt_data.optarg);
break;
}
} else
...
return err;
}
对于普通选项,group_key必然为0,因此在parser的groups数组中查找选项opt对应的group,调用group_parse函数进行解析。
这里以d选项为例,根据默认解析器argp的定义,group_parse函数最终调用argp_parser函数进行解析。
main->argp_parse->parser_parse_next->parser_parse_opt->argp_parser
util/grub-mkimage.c
static error_t argp_parser (int key, char *arg, struct argp_state *state) {
struct arguments *arguments = state->input;
switch (key) {
...
case 'd':
if (arguments->dir)
free (arguments->dir);
arguments->dir = xstrdup (arg);
break;
...
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
state的input即是grub-mkimage的main函数中的arguments,这里注意对于三个默认的argp(argp、argp_default_argp和argp_version_argp),只有argp的state->input为arguments,剩余两个的state->input都为0,因为剩余两个argp的argp_parser_t函数不使用arguments。
如果arguments的dir指针存在,就通过free函数释放该内存,然后通过xstrdup函数设置dir目录。
char * xstrdup (const char *str) {
size_t len;
char *newstr;
len = strlen (str);
newstr = (char *) xmalloc (len + 1);
memcpy (newstr, str, len + 1);
return newstr;
}
这里其实就是多复制一个结束符。