zoukankan      html  css  js  c++  java
  • init进程解析rc文件的相关函数分析

    init进程的源码文件位于system/core/init,其中解析rc文件语法的代码放在五个函数中, init_parse_config_file (init_parser.c), read_file (util.c), parse_config (init_parser.c), next_token (parser.c), lookup_keyword (init_parser.c)。下面一个一个看这几个函数的具体实现。

    首先是 init_parser_config_file 函数在init的main函数中被调用

    1 init_parse_config_file("/init.rc");

    可以看到直接解析init.rc文件,其会import其他rc文件

    函数内容如下

     1 int init_parse_config_file(const char *fn)
     2 {
     3     char *data;
     4     data = read_file(fn, 0);
     5     if (!data) return -1;
     6 
     7     parse_config(fn, data);
     8     DUMP();
     9     return 0;
    10 }

    可以看到非常简单,先把rc文件读到内存里,再调用 parse_config 函数解析读到内存中的文件内容,最后把接触出来的数据结构dump出来。

    先看读取文件内容部分

     1 /* reads a file, making sure it is terminated with 
      */
     2 void *read_file(const char *fn, unsigned *_sz)
     3 {
     4     char *data;
     5     int sz;
     6     int fd;
     7     struct stat sb;
     8 
     9     data = 0;
    10     fd = open(fn, O_RDONLY);
    11     if(fd < 0) return 0;
    12 
    13     // for security reasons, disallow world-writable
    14     // or group-writable files
    15     if (fstat(fd, &sb) < 0) {
    16         ERROR("fstat failed for '%s'
    ", fn);
    17         goto oops;
    18     }
    19     if ((sb.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
    20         ERROR("skipping insecure file '%s'
    ", fn);
    21         goto oops;
    22     }
    23 
    24     sz = lseek(fd, 0, SEEK_END);
    25     if(sz < 0) goto oops;
    26 
    27     if(lseek(fd, 0, SEEK_SET) != 0) goto oops;
    28 
    29     data = (char*) malloc(sz + 2);
    30     if(data == 0) goto oops;
    31 
    32     if(read(fd, data, sz) != sz) goto oops;
    33     close(fd);
    34     data[sz] = '
    ';
    35     data[sz+1] = 0;
    36     if(_sz) *_sz = sz;
    37     return data;
    38 
    39 oops:
    40     close(fd);
    41     if(data != 0) free(data);
    42     return 0;
    43 }

    内容很简单,要注意两点,一是权限问题,对于可以对其他用户随意写(S_IWOTH)以及组内可写的文件,不会读取其内容;二是读出来的数据在比文件附加了两个字节,一个是换行 ' ' ,一个是NULL '' ,以便随后的解析中可以判断文件结束——EOF

    把文件内容读到内存之后就可以用函数 parse_config 解析了,其内容如下

     1 static void parse_config(const char *fn, char *s)
     2 {
     3     struct parse_state state;
     4     struct listnode import_list;
     5     struct listnode *node;
     6     char *args[INIT_PARSER_MAXARGS];
     7     int nargs;
     8 
     9 #ifdef MTK_INIT
    10     NOTICE("<<Parsing %s>>
    ", fn);
    11 #endif
    12 
    13     nargs = 0;
    14     state.filename = fn;
    15     state.line = 0;
    16     state.ptr = s;
    17     state.nexttoken = 0;
    18     state.parse_line = parse_line_no_op;
    19 
    20     list_init(&import_list);
    21     state.priv = &import_list;
    22 
    23     for (;;) {
    24         switch (next_token(&state)) {
    25         case T_EOF:
    26             state.parse_line(&state, 0, 0);
    27             goto parser_done;
    28         case T_NEWLINE:
    29             state.line++;
    30             if (nargs) {
    31                 int kw = lookup_keyword(args[0]);
    32                 if (kw_is(kw, SECTION)) {
    33                     state.parse_line(&state, 0, 0);
    34                     parse_new_section(&state, kw, nargs, args);
    35                 } else {
    36                     state.parse_line(&state, nargs, args);
    37                 }
    38                 nargs = 0;
    39             }
    40             break;
    41         case T_TEXT:
    42             if (nargs < INIT_PARSER_MAXARGS) {
    43                 args[nargs++] = state.text;
    44             }
    45             break;
    46         }
    47     }
    48 
    49 parser_done:
    50     list_for_each(node, &import_list) {
    51          struct import *import = node_to_item(node, struct import, list);
    52          int ret;
    53 
    54          INFO("importing '%s'", import->filename);
    55          ret = init_parse_config_file(import->filename);
    56          if (ret)
    57              ERROR("could not import file '%s' from '%s'
    ",
    58                    import->filename, fn);
    59     }
    60 }

    做语法解析一般分两步,一是进行词法分析,二是进行语法分析。词法分析会把一个一个字符聚合成词法单元,比如if,for等,这里是on,service等。随后将一组按语法规则分割好的词法单元送给语法分析部分。这里做词法分析的是 next_token 函数,根据返回值决定解析出的词法单元性质。rc文件是以一行为单位分割词法单元,所以解析到新行( ' ' )词法单元后,之前已经聚合好的词法单元会被解析语义,即 case T_NEWLINE 内的部分,生成内部相依的数据结构,比如action,service等。在此之前会把收集到的一个个词法单元放进 args字符数组里。若是检测到文件结束,则该文件解析结束,进入 parse_done标签部分,递归解析被import进来的其他rc文件。

    记录解析状态的结构体是 parse_state ,其定义与parser.h文件中,内容如下

     1 struct parse_state
     2 {
     3     char *ptr;
     4     char *text;
     5     int line;
     6     int nexttoken;
     7     void *context;
     8     void (*parse_line)(struct parse_state *state, int nargs, char **args);
     9     const char *filename;
    10     void *priv;
    11 };

    其中, ptr 为内容buffer的游标,记录解析到哪里了; text 为解析出来的词法单元; line 为解析到的行数; nexttoken 记录语法解析器下一个要处理的词法单元的性质; context 存放接触出来后映射到的具体内容,比如service,action等; parse_line 为用来进行单行解析的函数; filename 为解析的rc文件的名称; priv 为import进来的rc文件的 listnode 结构体。

    记录解析到的词法单元的性质的三个宏也定义与parser.h文件中,如下

    1 #define T_EOF 0
    2 #define T_TEXT 1
    3 #define T_NEWLINE 2

    语法分析部分会根据返回的词法单元的性质,做不同的动作,或者解析结束,或者继续聚合词法单元,或者进行语法解析,映射相依内容到内部表示的数据结构。

    所以再看 parse_config 函数,先初始化 parse_state 相关的字段, nexttoken 为0,其值代表需要当前正在进行解析的词法单元要继续解析,没遇到换行符。这个字段的唯一一种应用就是在解析出换行符后,设置其为 T_NEWLINE ,词法扫描器 next_token 的返回值为 T_TEXT ,这样再聚合完最后一个词法单元后,再次进入 next_token 函数之后就可以直接返回 T_NEWLINE ,进入解析单行的函数中去。

    随后进入死循环,利用词法扫描器扫描rc文件内容,扫到词法单元进行聚合,扫到换行符解析聚合好的词法单元,扫到文件结尾结束解析当前rc文件,继续递归解析被import进来的rc文件。

    下面看看词法扫描器 next_token ,其内容如下

      1 int next_token(struct parse_state *state)
      2 {
      3     char *x = state->ptr;
      4     char *s;
      5 
      6     if (state->nexttoken) {
      7         int t = state->nexttoken;
      8         state->nexttoken = 0;
      9         return t;
     10     }
     11 
     12     for (;;) {
     13         switch (*x) {
     14         case 0:
     15             state->ptr = x;
     16             return T_EOF;
     17         case '
    ':
     18             x++;
     19             state->ptr = x;
     20             return T_NEWLINE;
     21         case ' ':
     22         case '	':
     23         case '
    ':
     24             x++;
     25             continue;
     26         case '#':
     27             while (*x && (*x != '
    ')) x++;
     28             if (*x == '
    ') {
     29                 state->ptr = x+1;
     30                 return T_NEWLINE;
     31             } else {
     32                 state->ptr = x;
     33                 return T_EOF;
     34             }
     35         default:
     36             goto text;
     37         }
     38     }
     39 
     40 textdone:
     41     state->ptr = x;
     42     *s = 0;
     43     return T_TEXT;
     44 text:
     45     state->text = s = x;
     46 textresume:
     47     for (;;) {
     48         switch (*x) {
     49         case 0:
     50             goto textdone;
     51         case ' ':
     52         case '	':
     53         case '
    ':
     54             x++;
     55             goto textdone;
     56         case '
    ':
     57             state->nexttoken = T_NEWLINE;
     58             x++;
     59             goto textdone;
     60         case '"':
     61             x++;
     62             for (;;) {
     63                 switch (*x) {
     64                 case 0:
     65                         /* unterminated quoted thing */
     66                     state->ptr = x;
     67                     return T_EOF;
     68                 case '"':
     69                     x++;
     70                     goto textresume;
     71                 default:
     72                     *s++ = *x++;
     73                 }
     74             }
     75             break;
     76         case '\':
     77             x++;
     78             switch (*x) {
     79             case 0:
     80                 goto textdone;
     81             case 'n':
     82                 *s++ = '
    ';
     83                 break;
     84             case 'r':
     85                 *s++ = '
    ';
     86                 break;
     87             case 't':
     88                 *s++ = '	';
     89                 break;
     90             case '\':
     91                 *s++ = '\';
     92                 break;
     93             case '
    ':
     94                     /*  <cr> <lf> -> line continuation */
     95                 if (x[1] != '
    ') {
     96                     x++;
     97                     continue;
     98                 }
     99             case '
    ':
    100                     /*  <lf> -> line continuation */
    101                 state->line++;
    102                 x++;
    103                     /* eat any extra whitespace */
    104                 while((*x == ' ') || (*x == '	')) x++;
    105                 continue;
    106             default:
    107                     /* unknown escape -- just copy */
    108                 *s++ = *x++;
    109             }
    110             continue;
    111         default:
    112             *s++ = *x++;
    113         }
    114     }
    115     return T_EOF;
    116 }

    首先初始化游标x,其指向当前扫描的缓存中要解析的字符

    第一部分,要判断 nexttoken ,如果不为0,及代表词法单元中有新行,要重新初始化为0并直接返回,再做语法解析。

    第二部分,要跳过空格,制表,回车,注释,空的换行,并向前移动缓存指针,继续解析新的内容。若是空的换行,返回之后会发现 nargs 为0,不做语法解析。

    三部分,如果遇到了字符串,则有三个标签对应不同的情况:texttextresumetextdone。遇到换行符跳到textdone标签下执行代码,遇到字符跳到text标签下执行代码,遇到双引号,要原样返回双引号之内的内容,即不跳过空格,回车等字符,则在遇到右边的双引号后再跳到textresume标签下继续执行解析。

    先看text标签下的内容。首先初始化词法单元指针 text 和执行词法单元尾部的指针s,为xx为游标,指向当前扫描位置。随后扫描每个字符,,遇到字符串则继续向前移动sx,遇到空格,制表,回车,换行符,代表词法单元构建完成,则把游标x向前移动到下一个要解析的字符,然后跳转到textdone标签,换行符还会设置 nexttokenT_NEWLINE,代表遇到换行,下次解析可以直接返回遇到了换行,开始单行解析。

    textdone标签,会设置parse stateptr指针为当前x,然后在s执行的地方(内容为换行符,空格,制表,回车),将其设为0,代表字符串结束。最后返回T_TEXT,表明进行词法单元聚合。

    解析的字符有两种特殊情况。

    一是双引号字符,在其后遇到0代表文件解析结束,直接返回T_EOF;遇到其他字符继续扫描,遇到另一个双引号代表被扫描的字符串结束,跳转到textresume标签继续扫描其他字符。

    二是转义字符 '\' ,先把游标x向前移动一个字符,下一个字符如果是:遇到0,则什么也不做,跳转到textdone标签,被替换为,代表字符串结束,然后返回词法单元;遇到 'n''r''t''\'将相依字符转义,将原来位置的字符置换为相依功能的转义字符,然后继续解析下一个字符;遇到回车符,若下一个字符为换行符,则与换行符处的处理一致,若下一个字符不是换行符,则将游标x向前移动一下,将转义字符替换为 之后的一个字符(若为空格之类的在下次此处理时自动判断词法单元结束),代表之后的字符接着上一行;遇到换行字符,代表之后的字符接着上一行,不是新行,则跳过空格和制表符,继续下边的扫描,即没有textdone;遇到其他的字符不管,原样送回给语法解析器解析。

  • 相关阅读:
    spark shuffle 机制
    hive explain 源码分析
    前端jQurey
    js-dom操作
    前端JS
    前端CSS
    Redis和MongoDB区别
    MySQL数据库备份
    MySQL索引
    python连接mysql服务端
  • 原文地址:https://www.cnblogs.com/cascle/p/4895457.html
Copyright © 2011-2022 走看看