udhcpd源码分析2--读取配置文件
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
udhcpd源码分析2--读取配置⽂件
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[6]; /* 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 = -1;
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, 0, sizeof(struct server_config_t));
/* 读取配置⽂件到server_config结构中供全局使⽤ */
if (argc < 2)
read_config(DHCPD_CONF_FILE);/* use default config file */
else read_config(argv[1]);/* 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[80], orig[80], *token, *line;
int i;
/* 先将默认配置解析到server_config<全局的配置信息结构体>结构中 */
for (i = 0; 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);
return0;
}
/* 将外部配置⽂件⼀⾏⼀⾏解析 */
while (fgets(buffer, 80, in)) {
/* fgets可能会将'\n'读⼊,如有就将其替换为'\0' */
if (strchr(buffer, '\n')) *(strchr(buffer, '\n')) = '\0';
strncpy(orig, buffer, 80);
/* 以#开头的⾏配置跳过 */
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 > 0 && isspace(line[i - 1]); i--);
line[i] = '\0';
/* token就是key值 line即此⾏的配置信息 */
for (i = 0; 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);
return1;
}
这是我见过⽤C来截取字符串⽐较精巧的设计(⽤strchr,strspn,strcspn三个字符串处理函数实现),以后有读取配置⽂件的编码需求完全可以参考其做法,最后在得到的token和line即配置项名字和配置内容.这⾥有⼀个辅助结构体数组不得不提:
/* 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), "254"},
{"remaining", read_yn, &(server_config.remaining), "yes"},
{"auto_time", read_u32, &(server_config.auto_time), "7200"},
{"decline_time",read_u32, &(server_config.decline_time),"3600"},
{"conflict_time",read_u32,&(server_config.conflict_time),"3600"},
{"offer_time", read_u32, &(server_config.offer_time), "60"},
{"min_lease", read_u32, &(server_config.min_lease), "60"},
{"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[14];
int (*handler)(char *line, void *var);
void *var;
char def[30];
};
在通过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 = 1;
if (!inet_aton(line, addr)) {
/* 当line不是ip地址⽽是主机或域名时解析出IP地址并保存 */
if ((host = gethostbyname(line)))
addr->s_addr = *((unsigned long *) host->h_addr_list[0]);
else retval = 0;
}
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);
return1;
}
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, 0);
return endptr[0] == '\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 = 1;
if (!strcasecmp("yes", line))
*dest = 1;
else if (!strcasecmp("no", line))
*dest = 0;
else retval = 0;
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 = 0, length = 0;
char buffer[255];
u_int16_t result_u16;
u_int32_t result_u32;
int i;
if (!(opt = strtok(line, " \t="))) return0;
/* 通过name找到结构体数组options中的对应项,和此name的code值联系起来 */
for (i = 0; options[i].code; i++)
if (!strcmp(options[i].name, opt))
option = &(options[i]);
if (!option) return0;
do {
val = strtok(NULL, ", \t");
if (val) {
length = option_lengths[option->flags & TYPE_MASK];
retval = 0;
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 = 0;
if (retval) retval = read_ip(val, buffer + 4);
break;
case OPTION_STRING:
length = strlen(val);
if (length > 0) {
if (length > 254) length = 254;
memcpy(buffer, val, length);
retval = 1;
}
break;
case OPTION_BOOLEAN:
retval = read_yn(val, buffer);
break;
case OPTION_U8:
buffer[0] = strtoul(val, &endptr, 0);
retval = (endptr[0] == '\0');
break;
case OPTION_U16:
result_u16 = htons(strtoul(val, &endptr, 0));
memcpy(buffer, &result_u16, 2);
retval = (endptr[0] == '\0');
break;
case OPTION_S16:
result_u16 = htons(strtol(val, &endptr, 0));
memcpy(buffer, &result_u16, 2);
retval = (endptr[0] == '\0');
break;
case OPTION_U32:
result_u32 = htonl(strtoul(val, &endptr, 0));
memcpy(buffer, &result_u32, 4);
retval = (endptr[0] == '\0');
break;
case OPTION_S32:
result_u32 = htonl(strtol(val, &endptr, 0));
memcpy(buffer, &result_u32, 4);
retval = (endptr[0] == '\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 <= 255) {
existing->data = realloc(existing->data,
existing->data[OPT_LEN] + length + 2);
memcpy(existing->data + existing->data[OPT_LEN] + 2, 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 + 2);
new->data[OPT_CODE] = option->code;
new->data[OPT_LEN] = length;
memcpy(new->data + 2, 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:将配置信息的名字,处理⽅法,保存位置,默认值组织⼀个结构体数组,这样的管理⽅式结构清晰且易于扩展。