mdev.c分析(二)

mdev.c分析 作者:jwwzhh :

原文地址:http://routeadd-net202.114.6.yulei.blog.chinaunix.net/uid-10928782-id-2182045.html

make_device(scratch, 0);

/* mknod in /dev based on a path like "/sys/block/hda/hda1"
 * NB1: path parameter needs to have SCRATCH_SIZE scratch bytes
 * after NUL, but we promise to not mangle (IOW: to restore if needed)
 * path string.
 * NB2: "mdev -s" may call us many times, do not leak memory/fds!
 */

static void make_device(char *path, int delete) //这里的path是不包含"/dev"的。例如/sys/block/loop0/dev。它传进来的是/sys/block/loop0。delete如果为0,那么说明要创建设备节点,如果为1,那么说明要删除设备节点。
{
    char *device_name;
    int major, minor, type, len;
    mode_t mode;
    parser_t *parser;

    
/* Try to read major/minor string. Note that the kernel puts \n after
     * the data, so we don't need to worry about null terminating the string
     * because sscanf() will stop at the first nondigit, which \n is.
     * We also depend on path having writeable space after it.
     */

    major = -1;
    if (!delete) {
        char *dev_maj_min = path + strlen(path);

        strcpy(dev_maj_min, "/dev");
        len = open_read_close(path, dev_maj_min + 1, 64);  //打开path文件读取最大64字节到dev_maj_min+1缓存上。
        *dev_maj_min = '\0';
        if (len < 1) {   //也就是没有数据读取到,或者说读数据时发生错误。
            if (!ENABLE_FEATURE_MDEV_EXEC)  //由于它在/include/autoconf.h:#define ENABLE_FEATURE_MDEV_EXEC 1,所以继续执行。注释也说了,没有dev文件,但是可以基于设备名运行脚本
                return;
            
/* no "dev" file, but we can still run scripts
             * based on device name */

        } else if (sscanf(++dev_maj_min, "%u:%u", &major, &minor) != 2)  //如果读取的主设备号和次设备号有问题,那么主设备号赋值为-1。{
            major = -1;
        }
    }

    /* Determine device name, type, major and minor */
    device_name = (char*) bb_basename(path); //获取设备名。这个函数就是返回,例如:path = /sys/block/loop0,返回loop0
    
/* http://kernel.org/doc/pending/hotplug.txt says that only
     * "/sys/block/..." is for block devices. "/sys/bus" etc is not.
     * But since 2.6.25 block devices are also in /sys/class/block,
     * we use strstr("/block/") to forestall future surprises. */

    type = S_IFCHR;  //假设这个设备是字符设备
    if (strstr(path, "/block/"))  //如果在path字符串中找到字符串"/block/",那么返回第一次找到的位置,如果没有找到,那么返回NULL,如果找到也就是说该设备是块设备。
        type = S_IFBLK;  //这个设备是块设备

    /* Make path point to "subsystem/device_name" */
    if (path[5] == 'b') /* legacy /sys/block? */
        path += sizeof("/sys/") - 1;
    else
        path += sizeof("/sys/class/") - 1;

    /* If we have config file, look up user settings */
    if (ENABLE_FEATURE_MDEV_CONF)  //是否使用了mdev的配置文件。这个配置文件是/etc/mdev.conf。
        parser = config_open2("/etc/mdev.conf", fopen_for_read); //通过zalloc分配了parser结构体。并把文件的打开句柄赋值给这个结构体的fp成员

    do {
        int keep_matching;
        struct bb_uidgid_t ugid;
        char *tokens[4];
        char *command = NULL;
        char *alias = NULL;
        char aliaslink = aliaslink; /* for compiler */

        /* Defaults in case we won't match any line */
        ugid.uid = ugid.gid = 0;
        keep_matching = 0;
        mode = 0660;

        if (ENABLE_FEATURE_MDEV_CONF
         && config_read(parser, tokens, 4, 3, "# \t", PARSE_NORMAL)  //读取配置文件的一行命令数据,并进行词的解析,参数parser传递进去了配置文件的句柄。4,标志着一行命令数据解析成的最多词(最后一个词可以有好几个词组成)。3,标志着一行命令行数据解析出来的词至少要有的数。"# \t"表示词间的界定符,PARSE_NORMAL标志着解析命令时的一些方式。返回值:parser的lineno为当前的命令行数据的行数。tokens解析出来的词的指针索引。函数返回值:标志着一行命令行数据解析出来的词的数,如果为0说明整个配置文件解析完毕,或者是发生了错误。
        ) {
            char *val;
            char *str_to_match;
            regmatch_t off[+ 9 * ENABLE_FEATURE_MDEV_RENAME_REGEXP];//regmatch_t由#include <regx.h>包含进来的。定义如下

typedef struct {

regoff_t rm_so;

regoff_t rm_eo;

}regmatch_t
pcreposix.h:typedef int regoff_t;
./include/autoconf.h:#define ENABLE_FEATURE_MDEV_RENAME_REGEXP 1


            val = tokens[0];
            keep_matching = ('-' == val[0]); //如果第一个词可以有'-'字符开头,它的意思是在分析完当前的行后,就算是匹配了,也可以继续配置文件的后续行。不过在分析的时候,它不起作用,把它滤除。
            val += keep_matching; /* swallow leading dash */ //如果第一个词有'-'开始,那么把它去掉。

            
/* Match against either "subsystem/device_name"
             * or "device_name" alone */

            str_to_match = strchr(val, '/') ? path : device_name;  //如果val里有'/'字符,那么str_to_match=path,否则为device_name。

            /* Fields: regex uid:gid mode [alias] [cmd] */

            if (val[0] == '@') { //第一个词,如果第一个字符为@,说明是设备的主设备号和次设备号。其格式为@主设备号,次设备号(-次设备号的最大值)
                /* @major,minor[-minor2] */
                
/* (useful when name is ambiguous:
                 * "/sys/class/usb/lp0" and
                 * "/sys/class/printer/lp0") */

                int cmaj, cmin0, cmin1, sc;
                if (major < 0)
                    continue; /* no dev, no match */
                sc = sscanf(val, "@%u,%u-%u", &cmaj, &cmin0, &cmin1);
                if (sc < 1 || major != cmaj
                 || (sc == 2 && minor != cmin0)
                 || (sc == 3 && (minor < cmin0 || minor > cmin1))
                ) {
                    continue; /* this line doesn't match */  //本行数据不匹配
                }
                goto line_matches;  //说明匹配
            }
            if (val[0] == '$') {  第一个词,如果第一个字符为'$',说明是环境变量的匹配。格式为xx=xxx。它是一个正则表达式,用来匹配其环境变量
                /* regex to match an environment variable */
                char *eq = strchr(++val, '=');
                if (!eq)  //如果val里找不到'='字符
                    continue;
                *eq = '\0';  //如果找到'='字符,那么把'='字符改成'\0'字符。
                str_to_match = getenv(val);  //获取环境变量val。
                if (!str_to_match)           //如果没有
                    continue;
                str_to_match -= strlen(val) + 1;  //环境变量中,获取该环境变量的整个语句
                *eq = '=';  //重新连接回去。
            }
            /* else: regex to match [subsystem/]device_name */  //其它的就是[子系统/]设备名,注意这里使用的是正则表达式。

            {
                regex_t match; //regex_t在pcreposix.h中定义:
typedef struct {
  void *re_pcre;
  size_t re_nsub;
  size_t re_erroffset;
} regex_t;

                int result;

                xregcomp(&match, val, REG_EXTENDED); //val正则表达式是配置文件的第1个词,为设备名正则表达式,或者为环境变量正则表达式。
                result = regexec(&match, str_to_match, ARRAY_SIZE(off), off, 0); //str_to_match是要去与正则表达式去匹配的字符串,它有三种可能:1,如果正则表达式里有'/',那么使用的是设备dev文件所在的目录字符串。2,如果没有'/',那么是设备dev文件所在的目录的目录名(不包括路径)。3,如果正则表达式中有'=',也就是说它是一个标示的是一个环境变量。那么这里就是通过getenv()获得的环境变量。不过它包括了“环境变量名=”。
                regfree(&match);
                
//bb_error_msg("matches:");

                
//for (int i = 0; i < ARRAY_SIZE(off); i++) {

                
//    if (off[i].rm_so < 0) continue;

                
//    bb_error_msg("match %d: '%.*s'\n", i,

                
//        (int)(off[i].rm_eo - off[i].rm_so),

                
//        device_name + off[i].rm_so);

                
//}


                /* If no match, skip rest of line */
                /* (regexec returns whole pattern as "range" 0) */
                if (result || off[0].rm_so
                 || ((int)off[0].rm_eo != (int)strlen(str_to_match)) //说明不匹配。
                ) {
                    continue; /* this line doesn't match */
                }
            }
 line_matches:
            
/* This line matches. Stop parsing after parsing
             * the rest the line unless keep_matching == 1 */


            /* 2nd field: uid:gid - device ownership */
            if (get_uidgid(&ugid, tokens[1], 1) == 0) //第2个词就是uid:gid。设备的所有者。tokens[1]-->ugid.
                bb_error_msg("unknown user/group %s on line %d", tokens[1], parser->lineno);

            /* 3rd field: mode - device permissions */
            /* mode = strtoul(tokens[2], NULL, 8); */
            bb_parse_mode(tokens[2], &mode);  //第3个词描述的是设备权限

            val = tokens[3];    //第4个词描述的是要做什么,这个词可是可以包括多个词。
            /* 4th field (opt): >|=alias */

            if (ENABLE_FEATURE_MDEV_RENAME && val) { //假如第4个词为:">ab%1sad%2 @bcd",那么,这个if语句的执行结果是alias="ab%2sad%1",这里的%2和%1是由str_to_match匹配第一个词而得的。i就是匹配的序号。val=@bcd。
                aliaslink = val[0];
                if (aliaslink == '>' || aliaslink == '=') { //第一个字符为如果为'>'或'=',代表是要改变设备的名字。">ab%1sad%2 @bcd"
                    char *a, *s, *st;
                    char *p;
                    unsigned i, n;

                    a = val;
                    s = strchrnul(val, ' ');
                    st = strchrnul(val, '\t');
                    if (st < s)
                        s = st;   //第4个词的第一个分隔符' ','\t'的位置
                    val = (s[0] && s[1]) ? s+: NULL;  //val指向值。
                    s[0] = '\0';  //断句

                    if (ENABLE_FEATURE_MDEV_RENAME_REGEXP) {  //重命名使用了正则表达式。
                        /* substitute %1..9 with off[1..9], if any */
                        n = 0;
                        s = a;
                        while (*s)
                            if (*s++ == '%')
                                n++;

                        p = alias = xzalloc(strlen(a) + n * strlen(str_to_match));
                        s = a + 1;
                        while (*s) {
                            *= *s;
                            if ('%' == *s) {
                                i = (s[1] - '0');
                                if (<= 9 && off[i].rm_so >= 0) {
                                    n = off[i].rm_eo - off[i].rm_so;
                                    strncpy(p, str_to_match + off[i].rm_so, n);
                                    p += n - 1;
                                    s++;
                                }
                            }
                            p++;
                            s++;
                        }
                    } else {
                        alias = xstrdup(+ 1);  //重命名不使用正则表达式。
                    }
                }
            }

            if (ENABLE_FEATURE_MDEV_EXEC && val) {  //如果第4个词中的第2个小词存在,它必须含有字符'$',或'@',或'*'。
                const char *= "$@*";
                const char *s2 = strchr(s, val[0]);

                if (!s2) {  //如果val[0]不是$@*字符之一,那么就是说发生错误了。
                    bb_error_msg("bad line %u", parser->lineno);
                    if (ENABLE_FEATURE_MDEV_RENAME)
                        free(alias);
                    continue;
                }

                
/* Are we running this command now?
                 * Run $cmd on delete, @cmd on create, *cmd on both
                 */

                if (s2-!= delete)  //如果第4个词的第2个小词的第一个字母是字符'@',或'*',那么备份。其实这里的注释已经比较清晰了,$,在删除的时候运行,@在创建时运行,*在创建时运行,及在删除时也运行。
                    command = xstrdup(val + 1);
            }
        }

        /* End of field parsing */  //发现的行匹配,且参数解析完毕。

                                    //如果没有配置文件,那么直接运行到这里。

        /* "Execute" the line we found */  //接下来就是要创建设备节点以及相应的命令执行。
        {
            const char *node_name;

            node_name = device_name;
            if (ENABLE_FEATURE_MDEV_RENAME && alias)  //如果第4个词的第一个小词是以'>','='字符开头,也就是说有别名。那么获取别名。
                node_name = alias = build_alias(alias, device_name);  //创建alias标示的文件夹,当然如果alias只是一个文件名时,当然不会创建文件夹,如果alias不是文件名,那么用使用alias标示文件夹路径,device_name作为文件名。

            if (!delete && major >= 0) {
                if (mknod(node_name, mode | type, makedev(major, minor)) && errno != EEXIST)  //创建设备节点。注意,在前面已经执行了xchdir("/dev");也就是说当前路径就是在/dev下。
                    bb_perror_msg("can't create %s", node_name);
                if (major == root_major && minor == root_minor)
                    symlink(node_name, "root");  //如果这个dev就是根设备节点,那么创建一个软链接。root->node_name。这个
                if (ENABLE_FEATURE_MDEV_CONF) {
                    chmod(node_name, mode);      //dev设备权限设置
                    chown(node_name, ugid.uid, ugid.gid);  //dev设备属主设置
                }
                if (ENABLE_FEATURE_MDEV_RENAME && alias) {
                    if (aliaslink == '>')  //如果第4个词的第一个字母是'>',那么还要创建软链接:device_name->node_name
                        symlink(node_name, device_name);
                }
            }

            if (ENABLE_FEATURE_MDEV_EXEC && command) {
                /* setenv will leak memory, use putenv/unsetenv/free */
                char *= xasprintf("%s=%s", "MDEV", node_name);
                char *s1 = xasprintf("%s=%s", "SUBSYSTEM", subsystem);
                putenv(s);
                putenv(s1);
                if (system(command) == -1)  //执行命令
                    bb_perror_msg("can't run '%s'", command);
                unsetenv("SUBSYSTEM");
                free(s1);
                unsetenv("MDEV");
                free(s);
                free(command);
            }

            if (delete) {
                if (ENABLE_FEATURE_MDEV_RENAME && alias) {
                    if (aliaslink == '>')
                        unlink(device_name);
                }
                unlink(node_name);
            }

            if (ENABLE_FEATURE_MDEV_RENAME)
                free(alias);
        }

        
/* We found matching line.
         * Stop unless it was prefixed with '-' */

        if (ENABLE_FEATURE_MDEV_CONF && !keep_matching)  //如果第一个词是以'-'开头的,那么还要执行该dev文件在mdev.conf配置文件的匹配处理。
            break;

    /* end of "while line is read from /etc/mdev.conf" */
    } while (ENABLE_FEATURE_MDEV_CONF);

    if (ENABLE_FEATURE_MDEV_CONF)
        config_close(parser);
}

typedef struct parser_t {
    FILE *fp;        //mdev.conf配置文件句柄
    char *line;      //指向读取一行数据的起始地址。
    char *data;      //备份行数据到data。注意,这里的行是非空的,且不是注释行。
    int lineno;      //在配置文件中读取一行的行号值。
} parser_t;


ssize_t FAST_FUNC open_read_close(const char *filename, void *buf, size_t size)
{
    int fd = open(filename, O_RDONLY);
    if (fd < 0)
        return fd;
    return read_close(fd, buf, size);
}

ssize_t FAST_FUNC read_close(int fd, void *buf, size_t size)
{
    /*int e;*/
    size = full_read(fd, buf, size);
    /*e = errno;*/
    close(fd);
    /*errno = e;*/
    return size;
}

/*
 * Read all of the supplied buffer from a file.
 * This does multiple reads as necessary.
 * Returns the amount read, or -1 on an error.
 * A short read is returned on an end of file.
 */

ssize_t FAST_FUNC full_read(int fd, void *buf, size_t len)
{
    ssize_t cc;
    ssize_t total;

    total = 0;

    while (len) {  //如果已经读取了要读取的字节数,那么退出while循环。
        cc = safe_read(fd, buf, len);

        if (cc < 0) {
            if (total) {
                /* we already have some! */
                /* user can do another read to know the error code */
                return total;  //读时发生错误,但是已经有读取数据,那么返回有读取的数据
            }
            return cc; /* read() returns -1 on failure. */  //返回读取失败
        }
        if (cc == 0)  //说明文件已被读完。
            break;
        buf = ((char *)buf) + cc; //缓存偏移
        total += cc;              //获取的字节数
        len -= cc;                //还剩下的要读字节数
    }

    return total;
}

ssize_t FAST_FUNC safe_read(int fd, void *buf, size_t count)
{
    ssize_t n;

    do {
        n = read(fd, buf, count);
    } while (< 0 && errno == EINTR); //如果是由于信号中断引起的读失败,那么再读。

    return n;
}

 

FILE* FAST_FUNC fopen_for_read(const char *path)
{
    return fopen(path, "r");
}

parser_t* FAST_FUNC config_open2(const char *filename, FILE* FAST_FUNC (*fopen_func)(const char *path))
{
    FILE* fp;
    parser_t *parser;

    fp = fopen_func(filename);
    if (!fp)
        return NULL;
    parser = xzalloc(sizeof(*parser));
    parser->fp = fp;
    return parser;
}

// Die if we can't allocate and zero size bytes of memory.

void* FAST_FUNC xzalloc(size_t size)
{
    void *ptr = xmalloc(size);
    memset(ptr, 0, size);
    return ptr;
}

// Die if we can't allocate size bytes of memory.

void* FAST_FUNC xmalloc(size_t size)
{
    void *ptr = malloc(size);
    if (ptr == NULL && size != 0)
        bb_error_msg_and_die(bb_msg_memory_exhausted);
    return ptr;
}

struct bb_uidgid_t {
    uid_t uid;
    gid_t gid;
};

/include/linux/types.h
typedef __kernel_uid32_t uid_t;
typedef __kernel_gid32_t gid_t;

typedef unsigned int __kernel_uid32_t;
typedef unsigned int __kernel_gid32_t;

/include/asm-arm/posix_types.h

 

/*
 * Config file parser
 */

enum {
    PARSE_COLLAPSE = 0x00010000, 
// treat consecutive delimiters as one

    PARSE_TRIM = 0x00020000, 
// trim leading and trailing delimiters

// TODO: COLLAPSE and TRIM seem to always go in pair

    PARSE_GREEDY = 0x00040000, 
// last token takes entire remainder of the line

    PARSE_MIN_DIE = 0x00100000, 
// die if < min tokens found

    
// keep a copy of current line

    PARSE_KEEP_COPY = 0x00200000 * ENABLE_FEATURE_CROND_D,
// PARSE_ESCAPE = 0x00400000, // process escape sequences in tokens

    
// NORMAL is:

    
// * remove leading and trailing delimiters and collapse

    
// multiple delimiters into one

    
// * warn and continue if less than mintokens delimiters found

    
// * grab everything into last token

    PARSE_NORMAL = PARSE_COLLAPSE | PARSE_TRIM | PARSE_GREEDY,
};

 

#define config_read(parser, tokens, max, min, str, flags) \
    config_read(parser, tokens, ((flags) | (((min) & 0xFF) << 8) | ((max) & 0xFF)), str)

 

/*
0. If parser is NULL return 0.
1. Read a line from config file. If nothing to read then return 0.
   Handle continuation character. Advance lineno for each physical line.
   Discard everything past comment character.
2. if PARSE_TRIM is set (default), remove leading and trailing delimiters.
3. If resulting line is empty goto 1.
4. Look for first delimiter. If !PARSE_COLLAPSE or !PARSE_TRIM is set then
   remember the token as empty.
5. Else (default) if number of seen tokens is equal to max number of tokens
   (token is the last one) and PARSE_GREEDY is set then the remainder
   of the line is the last token.
   Else (token is not last or PARSE_GREEDY is not set) just replace
   first delimiter with '\0' thus delimiting the token.
6. Advance line pointer past the end of token. If number of seen tokens
   is less than required number of tokens then goto 4.
7. Check the number of seen tokens is not less the min number of tokens.
   Complain or die otherwise depending on PARSE_MIN_DIE.
8. Return the number of seen tokens.

mintokens > 0 make config_read() print error message if less than mintokens
(but more than 0) are found. Empty lines are always skipped (not warned about).
*/

#undef config_read
int FAST_FUNC config_read(parser_t *parser, char **tokens, unsigned flags, const char *delims)
{
    char *line;
    int ntokens, mintokens;
    int t, len;

    ntokens = flags & 0xFF;
    mintokens = (flags & 0xFF00) >> 8;

    if (parser == NULL)
        return 0;

again:
    memset(tokens, 0, sizeof(tokens[0]) * ntokens);
    config_free_data(parser);

    /* Read one line (handling continuations with backslash) */
    line = bb_get_chunk_with_continuation(parser->fp, &len, &parser->lineno); //函数功能,读取配置文件的一行数据,如果一行数据通过连接符'\'分成两行,那么把它合并起来。参数parser->fp就是配置文件的句柄,&len返回的是读取一行数据的长度,&parser->lineno返回的从配置文件中已经读取的命令行行数。函数返回值:就是读取的命令行数据的起始地址。
    if (line == NULL)
        return 0;           //说明配置文件已经读取完了,还有一种可能是内存不够(当然这种情况,一般是不存在的)
    parser->line = line;

    /* Strip trailing line-feed if any */
    if (len && line[len-1] == '\n')  //去掉换行符。
        line[len-1] = '\0';

    /* Skip token in the start of line? */
    if (flags & PARSE_TRIM)
        line += strspn(line, delims + 1);  //去除行数据前面的' '或'\t'字符。

    if (line[0] == '\0' || line[0] == delims[0])  //如果line指向的字符串为空或者line[0] = '#',也就说刚读取的一行,要么是空的,要么就是注释。丢弃,重新读取一行。
        goto again;

    if (flags & PARSE_KEEP_COPY)  //这里的flagsPARSE_NORMAL    = PARSE_COLLAPSE | PARSE_TRIM | PARSE_GREEDY,也就是说,这里不会执行。其实,这里的意思就是是否备份。
        parser->data = xstrdup(line);

    /* Tokenize the line */
    for (= 0; *line && *line != delims[0] && t < ntokens; t++) { //退出条件,1,line指向的地方已经没有数据了。2,*line = "#",即后面是注释。也就是说这一行从这里开始就是注释了。3,一行语句只能有ntokens个词。多余的也略去。
        /* Pin token */
        tokens[t] = line;   //tokens字符串指针数组保存了词。

        /* Combine remaining arguments? */
        if ((!= (ntokens-1)) || !(flags & PARSE_GREEDY)) { //如果t小于ntokens-1,也就是需要分析的前一个词,如果flags的PARSE_GREEDY位为0,那么就算是t等于ntokens-1也还是通过下面进行词的识别。而在我们这里的flags=PARSE_NORMAL,它包括了PARSE_GREEDY。
            /* Vanilla token, find next delimiter */
            line += strcspn(line, delims[0] ? delims : delims + 1);  //查找下一个界定符,如果没有找到,那么返回字符串的尾巴'\0'位置
        } else {
            /* Combining, find comment char if any */
            line = strchrnul(line, delims[0]);    //查找'#'字符,如果没有找到,那么返回字符串的尾巴'\0'位置,这里和上面的有一个显著的区别是,最后一个tokens[ntokens-1],它可以包括多个词。

            /* Trim any extra delimiters from the end */
            if (flags & PARSE_TRIM) {  //在我们这里flags=PARSE_NORMAL,它包含了PARSE_TRIM
                while (strchr(delims + 1, line[-1]) != NULL)  //把最后一个词(这个词可以包括好几个词)后面的无效字符滤除。
                    line--;
            }
        }

        /* Token not terminated? */  //给每个词添加结束符。
        if (line[0] == delims[0])
            *line = '\0';
        else if (line[0] != '\0')
            *(line++) = '\0';

#if 0 /* unused so far */
        if (flags & PARSE_ESCAPE) {
            const char *from;
            char *to;

            from = to = tokens[t];
            while (*from) {
                if (*from == '\\') {
                    from++;
                    *to++ = bb_process_escape_sequence(&from);
                } else {
                    *to++ = *from++;
                }
            }
            *to = '\0';
        }
#endif

        /* Skip possible delimiters */
        if (flags & PARSE_COLLAPSE)
            line += strspn(line, delims + 1);  //去掉前面的"# \t"的字符。
    }

    if (< mintokens) {  //如果一条命令语句的词少于mintokens,那么继续读取配置文件的下一行目录,或者(如果flags的位PARSE_MIN_DIE被置一)就进入die状态。
        bb_error_msg("bad line %u: %d tokens found, %d needed",
                parser->lineno, t, mintokens);
        if (flags & PARSE_MIN_DIE)
            xfunc_die();
        goto again;
    }

    return t;
}

static void config_free_data(parser_t *parser)
{
    free(parser->line);
    parser->line = NULL;
    if (PARSE_KEEP_COPY) { /* compile-time constant */
        free(parser->data);
        parser->data = NULL;
    }
}

/* This function reads an entire line from a text file, up to a newline
 * or NUL byte, inclusive. It returns a malloc'ed char * which
 * must be free'ed by the caller. If end is NULL '\n' isn't considered
 * end of line. If end isn't NULL, length of the chunk is stored in it.
 * If lineno is not NULL, *lineno is incremented for each line,
 * and also trailing '\' is recognized as line continuation.
 *
 * Returns NULL if EOF/error. */

char* FAST_FUNC bb_get_chunk_with_continuation(FILE *file, int *end, int *lineno)
{
    int ch;
    int idx = 0;
    char *linebuf = NULL;
    int linebufsz = 0;

下面的while循环,阅读分析后可知:
end和lineno都不为NULL
退出while循环条件
1,已经到了文件的末尾
2,文件有数据为0
3,如果end不为NULL,ch='\n',lineno为NULL。
4,如果end不为NULL,ch='\n',lineno不为NULL,ch的前一个字符是'\\'。
也就是说,当遇到换行符,而这时又没有使用'\'行间连接符,那么退出。

linebuf和文件内容基本一致,但是还是做了些改变:
如果在文件中有通过'\'连接两行,那么去掉'\'字符和换行符。

也就是说,这个while循环主要是为了获取一行数据,包括使用'\'连接符的多行合成一行。
    while ((ch = getc(file)) != EOF) {  //从文件中读取一字节数据,如果到文章的尾,会返回EOF,于是退出这个while循环。
        /* grow the line buffer as necessary */
        if (idx >= linebufsz) {
            linebufsz += 256;
            linebuf = xrealloc(linebuf, linebufsz);  //分配一个linebufsz大小的内存(如果linebuf本来能够向后扩展满足容量,那么不会释放原先的内存,也不会另外申请内存,而是扩展原来已分配的内存。),把linebuf字符串的内容拷贝到新分配的缓存内,并返回新分配的缓存首地址。
        }
        linebuf[idx++] = (char) ch;  //把从文件中读取到得值赋值给linebuf字符串数组中。
        if (!ch)  //如果数据是空的,那么退出while循环。
            break;
        if (end && ch == '\n') {  //如果end不为NULL,且ch='\n'。
            if (lineno == NULL)   //如果lineno为NULL,不过在config_read调用这个函数时,lineo不是NULL
                break;            //那么退出
            (*lineno)++;          //增加配置文件已经读取的行数。
            if (idx < 2 || linebuf[idx-2] != '\\') //如果有转义字符,那么去除转义字符,也就是说,比如,文件中的内容有"abcd\efg",经过这个if语句及下面的idx -= 2后,linebuf中的内容为"abcdfg"。不过这里是不可能的了,前面还有一个if(end && ch == '\n'),也就是说只有这种情况下"abcd\\nefg"会变成"abcdefg"。哦,终于明白了,这里的意思就是说一行有时写不下时会使用'\'作为标记下一行和这一行在语法分析上而言它们是同一行的。这里就是去除"\\\n"这两个字符。
                break;
            idx -= 2;             //
        }
    }
    if (end)
        *end = idx;  //一行数据的结束位置。
    if (linebuf) {
        
// huh, does fgets discard prior data on error like this?

        
// I don't think so....

        
//if (ferror(file)) {

        
//    free(linebuf);

        
//    return NULL;

        
//}

        linebuf = xrealloc(linebuf, idx + 1);  //最终的数据例如:"abcdef\n\0"
        linebuf[idx] = '\0';
    }
    return linebuf;
}

 

// Die if we can't resize previously allocated memory. (This returns a pointer

// to the new memory, which may or may not be the same as the old memory.

// It'll copy the contents to a new chunk and free the old one if necessary.)

void* FAST_FUNC xrealloc(void *ptr, size_t size)
{
    ptr = realloc(ptr, size);
    if (ptr == NULL && size != 0)
        bb_error_msg_and_die(bb_msg_memory_exhausted);
    return ptr;
}

/* Builds an alias path.
 * This function potentionally reallocates the alias parameter.
 * Only used for ENABLE_FEATURE_MDEV_RENAME
 */

static char *build_alias(char *alias, const char *device_name)
{
    char *dest;

    /* ">bar/": rename to bar/device_name */
    /* ">bar[/]baz": rename to bar[/]baz */
    dest = strrchr(alias, '/');
    if (dest) { /* ">bar/[baz]" ? */
        *dest = '\0'; /* mkdir bar */
        bb_make_directory(alias, 0755, FILEUTILS_RECUR);  //创建文件夹
        *dest = '/';
        if (dest[1] == '\0') { /* ">bar/" => ">bar/device_name" */这里也就是说,如果alias不包括文件名,也就是说它只是文件夹路径。那么把device_name作为文件名,添加进来。
            dest = alias;
            alias = concat_path_file(alias, device_name);
            free(dest);
        }
    }

    return alias;
}