grub-mkimage源码分析---1

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;
}

这里其实就是多复制一个结束符。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值