1:重要的结构体

  读取配置文件信息到全局的结构体struct server_config_t server_config中,这个结构在很多文件中都有引用到很重要。

/* dhcpd.h */

struct server_config_t {
u_int32_t server; /* Our IP, in network order */
u_int32_t start; /* Start address of leases, network order */
u_int32_t end; /* End of leases, network order */
struct option_set *options;/* List of DHCP options loaded from the config file */
char *interface; /* The name of the interface to use */
int ifindex; /* Index number of the interface to use */
unsigned char arp[]; /* Our arp address */
unsigned long lease; /* lease time in seconds (host order) */
unsigned long max_leases; /* maximum number of leases (including reserved address) */
char remaining; /* should the lease file be interpreted as lease time remaining, or
       as the time the lease expires */
unsigned long auto_time; /* how long should udhcpd wait before writing a config file.
       if this is zero, it will only write one on SIGUSR1 */
unsigned long decline_time;/* how long an address is reserved if a client returns a
     decline message */
unsigned long conflict_time;/* how long an arp conflict offender is leased for */
unsigned long offer_time; /* how long an offered address is reserved */
unsigned long min_lease; /* minimum lease a client can request*/
char *lease_file;
char *pidfile;
char *notify_file; /* What to run whenever leases are written */
u_int32_t siaddr; /* next server bootp option */
char *sname; /* bootp server name */
char *boot_file; /* bootp boot file option */
};

  英文释意也很明白,比较重要的有struct option_set *options;成员,它是一个指向记录配置文件中对opt配置的链表的指针,并且data以CLV方式存储,结构如下:

/* dhcpd.h */

struct option_set {
unsigned char *data;
struct option_set *next;
};

2:读入配置文件

/* dhcpd.c */

#ifdef COMBINED_BINARY
int udhcpd_main(int argc, char *argv[])
#else
int main(int argc, char *argv[])
#endif
{
fd_set rfds;
struct timeval tv;
int server_socket = -;
int bytes, retval;
struct dhcpMessage packet;
unsigned char *state;
unsigned char *server_id, *requested;
u_int32_t server_id_align, requested_align;
unsigned long timeout_end;
struct option_set *option;
struct dhcpOfferedAddr *lease;
int pid_fd;
int max_sock;
int sig; OPEN_LOG("udhcpd");
LOG(LOG_INFO, "udhcp server (v%s) started", VERSION); memset(&server_config, , sizeof(struct server_config_t)); /* 读取配置文件到server_config结构中供全局使用 */
if (argc < )
read_config(DHCPD_CONF_FILE);/* use default config file */
else read_config(argv[]);/* use designated config file */

  在files.c里的read_config函数是读取的入口:

/* files.c */

/*
配置文件每一行的格式为'key(空格or\t)value'的格式(特殊:opt key(空格or\t)value),value值的类型有以下几种
分别对应以下的处理方法 value值类型 处理方法
ip read_ip
string read_str
number read_u32
yes/no read_yn
opt read_opt
*/
int read_config(char *file)
{
FILE *in;
char buffer[], orig[], *token, *line;
int i; /* 先将默认配置解析到server_config<全局的配置信息结构体>结构中 */
for (i = ; strlen(keywords[i].keyword); i++)
if (strlen(keywords[i].def))
keywords[i].handler(keywords[i].def, keywords[i].var); if (!(in = fopen(file, "r"))) {
LOG(LOG_ERR, "unable to open config file: %s", file);
return ;
}
/* 将外部配置文件一行一行解析 */
while (fgets(buffer, , in)) { /* fgets可能会将'\n'读入,如有就将其替换为'\0' */
if (strchr(buffer, '\n')) *(strchr(buffer, '\n')) = '\0';
strncpy(orig, buffer, ); /* 以#开头的行配置跳过 */
if (strchr(buffer, '#')) *(strchr(buffer, '#')) = '\0';
token = buffer + strspn(buffer, " \t");
if (*token == '\0') continue; line = token + strcspn(token, " \t=");
if (*line == '\0') continue;
*line = '\0';/* 截取行得到token(keyword) */
line++; /* 获得首尾无空白符的配置信息 */
/* eat leading whitespace */
line = line + strspn(line, " \t=");
/* eat trailing whitespace */
for (i = strlen(line); i > && isspace(line[i - ]); i--);
line[i] = '\0';
/* token就是key值 line即此行的配置信息 */ for (i = ; strlen(keywords[i].keyword); i++)
/* 确认key值正确(忽略大小写) */
if (!strcasecmp(token, keywords[i].keyword))
/* 将此行的配置更新到server_config中 */
if (!keywords[i].handler(line, keywords[i].var)) {
/* 如果更新失败就使用默认配置 */
LOG(LOG_ERR, "unable to parse '%s'", orig);
/* reset back to the default value */
keywords[i].handler(keywords[i].def, keywords[i].var);
}
}
fclose(in);
return ;
}

  这是我见过用C来截取字符串比较精巧的设计(用strchr,strspn,strcspn三个字符串处理函数实现),以后有读取配置文件的编码需求完全可以参考其做法,最后在得到的tokenline即配置项名字和配置内容.这里有一个辅助结构体数组不得不提:

/* files.c */

//struct config_keyword 将key、处理方法、要保存的地址、默认配置四项组在一起
static struct config_keyword keywords[] = {
/* keyword[14] handler variable address default[20] */
{"start", read_ip, &(server_config.start), "192.168.0.20"},
{"end", read_ip, &(server_config.end), "192.168.0.254"},
{"interface", read_str, &(server_config.interface), "eth0"},
{"option", read_opt, &(server_config.options), ""},
{"opt", read_opt, &(server_config.options), ""},
{"max_leases", read_u32, &(server_config.max_leases), ""},
{"remaining", read_yn, &(server_config.remaining), "yes"},
{"auto_time", read_u32, &(server_config.auto_time), ""},
{"decline_time",read_u32, &(server_config.decline_time),""},
{"conflict_time",read_u32,&(server_config.conflict_time),""},
{"offer_time", read_u32, &(server_config.offer_time), ""},
{"min_lease", read_u32, &(server_config.min_lease), ""},
{"lease_file", read_str, &(server_config.lease_file), "/var/lib/misc/udhcpd.leases"},
{"pidfile", read_str, &(server_config.pidfile), "/var/run/udhcpd.pid"},
{"notify_file", read_str, &(server_config.notify_file), ""},
{"siaddr", read_ip, &(server_config.siaddr), "0.0.0.0"},
{"sname", read_str, &(server_config.sname), ""},
{"boot_file", read_str, &(server_config.boot_file), ""},
/*ADDME: static lease */
{"", NULL, NULL, ""}
};

它的结构体原型为:
/* files.h */

struct config_keyword {
char keyword[];
int (*handler)(char *line, void *var);
void *var;
char def[];
};

  在通过read_config函数的解析后得到的token就去匹配keyword,匹配到了就调用其函数指针handler,传入line和对应的结构体成员地址进行赋值,一气呵成!(通过一个结构体数组将所有的配置关键字和对应的处理方式罗列出来,不仅看起来条理清晰而且在需要添加新的配置项时也很方便,设计思路真的值得参考).

  上面讲到过,根据配置项的默认值对应不同的处理函数,下面讲述一下这些处理函数:

/* files.c */

static int read_ip(char *line, void *arg)

/* 将字符串格式的ip地址转换为u_int32_t保存在地址arg中 */
/* on these functions, make sure you datatype matches */
static int read_ip(char *line, void *arg)
{
struct in_addr *addr = arg;
struct hostent *host;
int retval = ; if (!inet_aton(line, addr)) {
/* 当line不是ip地址而是主机或域名时解析出IP地址并保存 */
if ((host = gethostbyname(line)))
addr->s_addr = *((unsigned long *) host->h_addr_list[]);
else retval = ;
}
return retval;
}

static int read_str(char *line, void *arg)

/*
首先free掉arg指向的内存,然后根据字符串line大小分配合适内存,把字符串line拷贝
到分配的内存中(strdup函数的功能),让arg指向新分配的内存
*/
static int read_str(char *line, void *arg)
{
char **dest = arg; if (*dest) free(*dest);/* 使用前先free以防止内存泄露 */
*dest = strdup(line); return ;
}

static int read_u32(char *line, void *arg)

/* 将line指向的内容转换为unsigned long 的类型保存于arg指向的内存中 */
static int read_u32(char *line, void *arg)
{
u_int32_t *dest = arg;
char *endptr;
*dest = strtoul(line, &endptr, );
return endptr[] == '\0';
}

static int read_yn(char *line, void *arg)

/* line为yes<忽略大小写> arg所指成员赋1,否则赋值0 */
static int read_yn(char *line, void *arg)
{
char *dest = arg;
int retval = ; if (!strcasecmp("yes", line))
*dest = ;
else if (!strcasecmp("no", line))
*dest = ;
else retval = ; return retval;
}

  最后一个read_opt函数显得比较特殊,因为它的配置文件格式是opt/option name value,所以通过read_config函数之后,token是opt/option,line是name value,这样的line还得进行一次类似read_config函数的解析得到toke=name和line=value,最后将这个添加到struct option_set *options;指向的一个单链表中,类似的解析就在read_opt函数中进行.

3:配置文件中选项的读取

  read_opt的思路和read_config函数的思路是相似的,先介绍一个重要的结构体数组options

/* options.c */

/* supported options are easily added here */
struct dhcp_option options[] = {
/* name[10] flags code */
{"subnet", OPTION_IP | OPTION_REQ, 0x01},
{"timezone", OPTION_S32, 0x02},
{"router", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x03},
{"timesvr", OPTION_IP | OPTION_LIST, 0x04},
{"namesvr", OPTION_IP | OPTION_LIST, 0x05},
{"dns", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x06},
{"logsvr", OPTION_IP | OPTION_LIST, 0x07},
{"cookiesvr", OPTION_IP | OPTION_LIST, 0x08},
{"lprsvr", OPTION_IP | OPTION_LIST, 0x09},
{"hostname", OPTION_STRING | OPTION_REQ, 0x0c},
{"bootsize", OPTION_U16, 0x0d},
{"domain", OPTION_STRING | OPTION_REQ, 0x0f},
{"swapsvr", OPTION_IP, 0x10},
{"rootpath", OPTION_STRING, 0x11},
{"ipttl", OPTION_U8, 0x17},
{"mtu", OPTION_U16, 0x1a},
{"broadcast", OPTION_IP | OPTION_REQ, 0x1c},
{"ntpsrv", OPTION_IP | OPTION_LIST, 0x2a},
{"wins", OPTION_IP | OPTION_LIST, 0x2c},
{"requestip", OPTION_IP, 0x32},
{"lease", OPTION_U32, 0x33},
{"dhcptype", OPTION_U8, 0x35},
{"serverid", OPTION_IP, 0x36},
{"message", OPTION_STRING, 0x38},
{"tftp", OPTION_STRING, 0x42},
{"bootfile", OPTION_STRING, 0x43},
{"", 0x00, 0x00}
};

  这里建立了配置选项关键字及flag和code之间的联系.再看read_opt函数

/* files.c */

/*
函数将line中的name和value解析出来,通过name值定位到结构体数组
的options[X]、value通过函数转换得到的buffer、buffer的长度length、
及链表的头指针opt_list一起交由函数attach_option处理
*/
/* read a dhcp option and add it to opt_list */
static int read_opt(char *line, void *arg)
{
struct option_set **opt_list = arg;
char *opt, *val, *endptr;
struct dhcp_option *option = NULL;
int retval = , length = ;
char buffer[];
u_int16_t result_u16;
u_int32_t result_u32;
int i; if (!(opt = strtok(line, " \t="))) return ; /* 通过name找到结构体数组options中的对应项,和此name的code值联系起来 */
for (i = ; options[i].code; i++)
if (!strcmp(options[i].name, opt))
option = &(options[i]); if (!option) return ; do {
val = strtok(NULL, ", \t");
if (val) {
length = option_lengths[option->flags & TYPE_MASK];
retval = ;
switch (option->flags & TYPE_MASK) {
case OPTION_IP:
retval = read_ip(val, buffer);
break;
case OPTION_IP_PAIR:
retval = read_ip(val, buffer);
if (!(val = strtok(NULL, ", \t/-"))) retval = ;
if (retval) retval = read_ip(val, buffer + );
break;
case OPTION_STRING:
length = strlen(val);
if (length > ) {
if (length > ) length = ;
memcpy(buffer, val, length);
retval = ;
}
break;
case OPTION_BOOLEAN:
retval = read_yn(val, buffer);
break;
case OPTION_U8:
buffer[] = strtoul(val, &endptr, );
retval = (endptr[] == '\0');
break;
case OPTION_U16:
result_u16 = htons(strtoul(val, &endptr, ));
memcpy(buffer, &result_u16, );
retval = (endptr[] == '\0');
break;
case OPTION_S16:
result_u16 = htons(strtol(val, &endptr, ));
memcpy(buffer, &result_u16, );
retval = (endptr[] == '\0');
break;
case OPTION_U32:
result_u32 = htonl(strtoul(val, &endptr, ));
memcpy(buffer, &result_u32, );
retval = (endptr[] == '\0');
break;
case OPTION_S32:
result_u32 = htonl(strtol(val, &endptr, ));
memcpy(buffer, &result_u32, );
retval = (endptr[] == '\0');
break;
default:
break;
}
/* 把选项值放在以code升序的单链表中 */
if (retval)
attach_option(opt_list, option, buffer, length);
};
} while (val && retval && option->flags & OPTION_LIST);
return retval;
}

  进入attach_option函数:

/* options.c */

/* add an option to the opt_list */
void attach_option(struct option_set **opt_list, struct dhcp_option *option, char *buffer, int length)
{
struct option_set *existing, *new, **curr; /* add it to an existing option */
/* 如果此opt已存在链表中则将buff添加到原值的后面 OPT_CODE=0,OPT_LEN=1*/
if ((existing = find_option(*opt_list, option->code))) {
DEBUG(LOG_INFO, "Attaching option %s to existing member of list", option->name);
if (option->flags & OPTION_LIST) {
if (existing->data[OPT_LEN] + length <= ) {
existing->data = realloc(existing->data,
existing->data[OPT_LEN] + length + );
memcpy(existing->data + existing->data[OPT_LEN] + , buffer, length);
/*
byte byte length*byte length
- - - - - - - - - - - - - - - - - - - - - - - --
| | | | |
| code | length | buff_old | buff_new |
| | | | |
- - - - - - - - - - - - - - - - - - - - - - - --
*/
existing->data[OPT_LEN] += length;
} /* else, ignore the data, we could put this in a second option in the future */
} /* else, ignore the new data */
} else {
/* 按照code值顺序添加新节点到链表中 */
DEBUG(LOG_INFO, "Attaching option %s to list", option->name); /* make a new option */
new = malloc(sizeof(struct option_set));
new->data = malloc(length + );
new->data[OPT_CODE] = option->code;
new->data[OPT_LEN] = length;
memcpy(new->data + , buffer, length); /* curr始终作为指向新节点指针的指针 */
curr = opt_list;
while (*curr && (*curr)->data[OPT_CODE] < option->code)
curr = &(*curr)->next; new->next = *curr;
*curr = new;
}
}

  这就是上面讲述的使用CLV的方式存储选项配置,这个配置选项链表是升序的单链表,传入的是指向链表头部的指针的指针(这点很重要,以前单链表的操作会考虑插入作为第一个节点这种特殊情况,这里就不用).

4:总结

  配置文件的读取模块大概就是以上的内容,通过分析实现源码觉得有很多实现方式很值得参考.

可作为轮子用的有:

1:通过strchr,strspn,strcspn三个字符串处理函数截取配置信息

2:attach_option函数中对于升序链表的操作

值得参考的设计思路:

1:将配置信息的名字,处理方法,保存位置,默认值组织一个结构体数组,这样的管理方式结构清晰且易于扩展

udhcpd源码分析2--读取配置文件的更多相关文章

  1. mybatis源码分析之02配置文件解析

    该篇正式开始学习mybatis的源码,本篇主要学习mybatis是如何加载配置文件mybatis-config.xml的, 先从测试代码入手. public class V1Test { public ...

  2. Mybatis 源码分析--Configuration.xml配置文件加载到内存

    (补充知识点: 1 byte(字节)=8 bit(位) 通常一个标准英文字母占一个字节位置,一个标准汉字占两个字节位置:字符的例子有:字母.数字系统或标点符号) 1.创建SqlSessionFacto ...

  3. udhcpd源码分析4--获取client报文及发包动作

    1:重要的结构体 获取的报文是UDP的payload部分,结构体struct dhcpMessage描述了dhcp报文的结构. /* packet.h */ struct dhcpMessage { ...

  4. udhcpd源码分析3--IP租赁管理

    1:重要的结构体 全局链表的成员struct dhcpOfferedAddr *leases 记录了当前租赁出去的IP信息 /* leases.h */ struct dhcpOfferedAddr ...

  5. Heritrix源码分析(三) 修改配置文件order.xml加快你的抓取速度(转)

    本博客属原创文章,欢迎转载!转载请务必注明出处:http://guoyunsky.iteye.com/blog/629891       本博客已迁移到本人独立博客: http://www.yun5u ...

  6. lucene源码分析(2)读取过程实例

    1.官方提供的代码demo Analyzer analyzer = new StandardAnalyzer(); // Store the index in memory: Directory di ...

  7. 03_HibernateSessionFactory源码分析

    文章导读: 讲解了一个线程为什么要使用同一个connection, 我们分析了HiberatenSessionFactory的实现机制, 然后根据Hibernate的写法重构了我们的代码. 最后测试可 ...

  8. mybatis源码分析之06二级缓存

    上一篇整合redis框架作为mybatis的二级缓存, 该篇从源码角度去分析mybatis是如何做到的. 通过上一篇文章知道,整合redis时需要在FemaleMapper.xml中添加如下配置 &l ...

  9. 【Spring源码分析】配置文件读取流程

    前言 Spring配置文件读取流程本来是和http://www.cnblogs.com/xrq730/p/6285358.html一文放在一起的,这两天在看Spring自定义标签的时候,感觉对Spri ...

随机推荐

  1. [SHELL]linux环境变量

  2. ubuntu16.04图形界面安装中文输入法,中文展示

    打开system Settings 设置   打开设置语言   安装Language Support   点击installed languages 选择chinese 打勾,安装   安装IBus框 ...

  3. 【模板】DFS

    int dx[] = { 0,1,0,-1 }; int dy[] = { 1,0,-1,0 }; void dfs()//参数用来表示状态 { if (到达终点状态) { ...//根据题意来添加 ...

  4. asp.net mvc5 模式的现象思考

    .net mv5简化了一些应用逻辑,与其说是mvc架构模式,不如说应用.net Entity更好. 现在你只需要去随便创建一个类 相关数据 然后用一个类去继承 DbContext 定义一个 DbSet ...

  5. Solium代码测试框架

    Solium, 在solid中,Linter用于标识和修复样式&安全问题 //调用测试 solium -d contracts --fix 源代码名称:Solium 源代码网址:http:// ...

  6. struts2之form标签theme属性详解

    struts2中theme属性包括xhtml,html,simple,ajax .默认是xhtml theme:设置struts2标签的主题,默认为xhtml. theme=xhtml时:会默认额外生 ...

  7. 从oracle导入hive

    sqoop import --connect jdbc:oracle:thin:@10.39.1.43:1521/rcrm --username bi_query --password ####### ...

  8. 算法与数据结构3.1 stack

    ★实验任务 一天,小 L 发现了一台支持一下操作的机器: IN x:将整数 x 入栈 POP:将栈顶元素出栈 ASUB:出栈两个数,将两数差的绝对值入栈 COPY:将栈顶元素(如果有的话)复制一份,入 ...

  9. TCP 的有限状态机

    TCP 有限状态机的图中每一个方框都是 TCP 可能具有的状态. 每个方框中的大写英文字符串是 TCP 标准所使用的 TCP 连接状态名. 状态之间的箭头表示可能发生的状态变迁. 箭头旁边的字,表明引 ...

  10. autoCAD 2008 Win7 64位, win8 64位 安装 燕秀工具箱 yanxiu.cui 文件下载

    Win7 64位, win8 64位 安装 燕秀工具箱 , 提示没有权限. 网站上下载燕秀工具箱, 安装后. 提示权限不够. 解决办法如下; 1. CAD, 权限修改. 2. 下载 yanxiu.cu ...