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); return 0; } /* 將外部配置文件一行一行解析 */ 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); return 1; }
這是我見過用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); return 1; }
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="))) return 0; /* 通過name找到結構體數組options中的對應項,和此name的code值聯系起來 */ for (i = 0; options[i].code; i++) if (!strcmp(options[i].name, opt)) option = &(options[i]); if (!option) return 0; 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:將配置信息的名字,處理方法,保存位置,默認值組織一個結構體數組,這樣的管理方式結構清晰且易於擴展