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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
关于msg的文章目中处理Error Msg的方法小结不针对任何人的说: 看到有人把Error Msg写死到代码里,我就有上去忽他一巴掌的冲动。比如如下代码: int funcFoo() { UpdateData(); int error_code = 0; if (!IsInPutsUseName()) error_code = -1; else if(!IsInputAllPsw()) error_code = -2; else if(!IsTwoPswTheSame()) error_code = -3; else if(!IsThePswMachWithDatabase()) error_code = -4; if (error_code != 0) { switch(error_code) { case -1 : MessageBox("请输入用户名,然后重试!", "未输入用户名", MB_OK | MB_ICONWARNING); break; case -2 : MessageBox("请输入所有的密码,然后重试!", "未输入密码", MB_OK | MB_ICONWARNING); break; case -3 : MessageBox("您输入的两次密码不一致,请重新输入!", "密码不一致", MB_OK | MB_ICONWARNING); break; case -4 : MessageBox("您输入的密码错误,请重新输入", "密码错误", MB_OK | MB_ICONWARNING); break; case default : break; } DeleteInputPsw(); return error_code; } UpdateData(false); return error_code; } 理由如下: 1. 这样的源码不易阅读。假设用户提供的一个错误信息,我要追踪其源码,我去那里找呀?可能有很多处地方都会有重复的类似的MSG出现,比如"请输入用户名,然后重试!"和"请您输入用户名,然后重试!"就会被认为是2条不同的MSG。这样很难排错。 2. 用户那里有可能弹出你估计之外的错误。实际上我们经常遇到这种情况,某程序崩溃探出一个错误号,没信息。因为没有对应好。 3. 不利于发展为多语言版本?(你指望专业翻译在你的代码里搜索字符串?) 4.不利于全局统计。估计自己都不知道自己的工程里已经存在了多少种MSG字符串了吧? 5.专业软件的错误信息是应该由专业语言措辞人员去对应的,而不是由程序员决定最终的版本。比如我代码里写一个errorcode:992,“没输密码!”,就会被专业措辞人员修饰为"请输入用户名,然后重试!" 解决的方法 也有多种,各有其优点和不足之处,写在这里供大家参考: 1.最古老的做法,是把信息写入一个文本文件里面: // xxxxxxxxxx 一些注释 xxxxxxxxxxx // xxxxxxxxxx 一些注释 xxxxxxxxxxx // xxxxx Error Code : 998 // xxxxx Msg : "请输入用户名,然后重试!" // xxxxx 描述: ... #define Error_998_MSG "请输入用户名,然后重试!" // xxxxxxxxxx 一些注释 xxxxxxxxxxx // xxxxxxxxxx 一些注释 xxxxxxxxxxx // xxxxx Error Code : 999 // xxxxx Msg : "xxxxxxxxx, xxxxxxxxxxxxxxx!" // xxxxx 描述: ... #define Error_999_MSG "xxxxxxxxxxxxxxxx!" 诸如此类的做法,当系统启动的时候把这些资源读入内存中 优点:已经基本上解决了上述所说的弊病; 缺点:跨平台交互不容易,尤其是文件内码不同的情况,比如utf-8环境、ansi char环境、unicode环境........... 2. 写入xml文件里面:(推荐) 这种做法和上述方法类似,不过解决了上述方法的缺点。常用于Web相关的开发。 优点:标记语言,交互方便。扩展方便,功能强大且无限制。 缺点:编写人员必须熟悉xml语法,或者有专用的用于简化生成这个xml文件的程序。 3. 写入数据库里面: 应用这种方法的也很多,我就遇到若干这种做法的项目。形象一点的说,你可以参考Sql Server中的错误信息,它也是写在一个系统表里面。 优点:利用SQL的优势,编写、修改都很方便,程序员、翻译、维护人员和用户都很轻松。客户甚至可以自己修改MSG信息。 缺点:一般这种做法只用于数据库相关的应用,比如MIS系统。另外,如果出现数据库根本连接不上的错误,这种错误信息还要当作特例解决。 另外,交互-速度较慢。 今天在论坛上偶见此问题,遂废话一堆,抛砖引玉,希望大家批评指正。 -------------

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值