keepalived源码浅析——parser关键字解析

来源:互联网 发布:人工智能在工厂的使用 编辑:程序博客网 时间:2024/05/22 13:45
        keepalived采用关键字分层的方式来组织配置文件, 在上面摘取的配置文件片段中,共有4层关键字,位于第零层的关键字有:global_defs, vrrp_instance,  vritual_server等, 位于第一层的关键字有:notification_email, state, delay_loop, real_server等, 位于第二层的关键字有weight, SSL_GET, 位于第三层的关键字有url等, 位于第四层的关键字有path, digest等。若把位于同一层的关键字组织成一个列表(或者叫向量vector), 则该列表具有这样的特性:它的每一个元素都是一个关键字, 而该关键字可能指向下一层的关键字列表,如此反复。

  keepalived的配置文件还支持include的用法, 可以在当前配置文件中用include newconf的方式包含下一个配置文件, 且下一个配置文件里面也可以用include包含下下一个配置文件。而且一个配置文件里面也可以用多个include语句包含多个配置文件进行。此外, keepalived还支持在传递配置文件名字时, 采用包含正则表达式的记法, 如:你可以传递一个”*.conf”作为配置文件的名字, 对应的是解析当前目录下所有以.conf结尾的文件。

位于parser.c文件中的init_data(char *conf_file, vector (*init_keywords) (void))函数是解析配置文件的入口点, 该函数第一个参数为需要解析的配置文件名(可以由正则表达式构成,如*.conf), 第二个参数为函数指针,指向关键字组织结构(实际取值为check_init_keywords 或者vrrp_init_keywords)。

  

 

解析配置文件过程中用到的几个全局变量包括current_keywords(当前关键字列表)、keywords(层次为0的关键字列表, 整个关键字组织的最顶层)、current_stream(当前打开的文件的流, 即文件指针, 指向当前打开的文件,因为keepalived支持嵌套的文件,所以必须设置这样的变量)、current_conf_file(当前解析的文件的名字, 同上, 是为支持嵌套的配置文件而设立的)、kw_level(当前关键字列表的层次, 处理下一层关键字列表之前递增, 回退到上一层关键字列表时递减)。

  全局流程如下: 在init_data()中,调用read_conf_file(conf_file), 把要解析的配置文件的名字传递给read_conf_file()函数;read_conf_file()函数先利用glob库取得所有的conf_file(尤其是带正则表达式符号的名字), 然后依次根据 当前关键字列表current_keywords, 调用process_stream(current_keywords)进行配置文件的解析。解析 的方法是依次读取配置文件中的一行, 把该行转化为一个字符串数组, 再根据该字符串数组的第一个元素(即该行的第一个字符串)进行以下四种情况的处理:


 1)若该字符串是井号或者感叹号(#or!) 表明这是一行注释, 不做处理, 继续读取下一行;

 2)若该字符串为右大括号(”}”),且不是层次0的关键字列表, 意味着当前层次的关键字解析结束, 直接退出process_stream()函数; 

3)若该字符串为”include”, 说明包含嵌套配置文件, 则在保存了当前的文件流和配置文件名字后,递归调用read_conf_file(conf_file)处理include进行来的配置文件, 处理结束后, 再恢复之前保存的文件流和配置文件名字, 继续原配置文件的处理;

 4)若该字符串不属于上述三种情况, 那它预期是一个关键字, 则遍历当前关键字列表current_keywords, 并逐一和该字符串进行配置, 若找到了确切的关键字, 则调用 该关键字的handler()进行处理, 若该关键字还关联着下一层次的关键字列表, 则先递增关键字列表层次变量kw_level, 再递归调用process_stream(keyword->sub)处理下一层次的关键字列表, 处理结束后再递减kw_level。 

若该字符串不属于于当前关键字列表中的关键字, 则忽略该行。配置文件的解析结束发生在读取到EOF, 而 一个合法的配置文件,一般都 是从层次0开始处理关键字, 中间会依次处理下一层次的关键字列表, 而最终又回归到层次0的关键字列表中

 

parser.h 源码内容:

#ifndef _PARSER_H#define _PARSER_H/* system includes */#include <stdlib.h>#include <stdio.h>#include <string.h>#include <stdint.h>#include <syslog.h>#include <ctype.h>/* local includes */#include "vector.h"/* Global definitions */#define CONF "/etc/keepalived/keepalived.conf"         //默认的配置文件位置#define EOB  "}"                                       //配置文件中的分隔符#define MAXBUF1024                                 //字符串最大长度/* ketword definition */                               //关键字结构体的定义typedef struct _keyword {char *string;void (*handler) (vector_t *);vector_t *sub;} keyword_t;/* Reloading helpers */                               //设置是否重载#define SET_RELOAD      (reload = 1)#define UNSET_RELOAD    (reload = 0)#define RELOAD_DELAY    5/* global vars exported */extern vector_t *keywords;extern FILE *current_stream;extern int reload;/* Prototypes */extern void keyword_alloc(vector_t *, char *, void (*handler) (vector_t *));extern void keyword_alloc_sub(vector_t *, char *, void (*handler) (vector_t *));extern void install_keyword_root(char *, void (*handler) (vector_t *));extern void install_sublevel(void);extern void install_sublevel_end(void);extern void install_keyword(char *, void (*handler) (vector_t *));extern void dump_keywords(vector_t *, int);extern void free_keywords(vector_t *);extern vector_t *alloc_strvec(char *);extern int read_line(char *, int);extern vector_t *read_value_block(void);extern void alloc_value_block(vector_t *, void (*alloc_func) (vector_t *));extern void *set_value(vector_t *);extern void process_stream(vector_t *);extern void init_data(char *, vector_t * (*init_keywords) (void));#endif



 

关键字相关数据结构

  作为关键字, 首先需要有一个名字name来区分, 当解析配置文件时遇到该关键字要进行什么操作则由函数handler来表达, 最后单独的一个关键字是作为整个层次关键字中的一员,需要有相应的成员去关联下一级别的关键字,这个任务由 sub指针完成。具体的关键字定义如下:

1 /* ketword definition */2 struct keyword {3     char *string;4     void (*handler) (vector);5     vector sub;6 };


 

  位于同一层次的关键字构成一个列表(vector,向量), C语言中没有直接的vector定义, keepalived采用定义了自己的列表(vector), 如下:
1 /* vector definition */2 struct _vector {3     unsigned int allocated;4     void **slot;5 };6 typedef struct _vector *vector;

上面的定义是很常见的, allocated对应列表的大小, 而slot则对应列表的内容 留意到slot的类型为void**, 这意味着这样的列表存储的内容可以是任意数据类型的指针(在下方中, 我们将看到, 在层次关键字结构中, 它用来存储指向关键字结构体keyword的指针)。

  关键字的层次是用变量sublevel进行组织的, sublevel初始为0, 表示关键字的层次从0开始, 添加下一层次的关键字之前需要递增sublevel变量, 需要回到上一层关键字之前要递减sublevel变量。

  每层关键字对应一个或多个关键列表(取决于该列表由哪些关键字派生出来)。第零层关键字是必定存在的,所以定义 了一个全局的关键字列表keywords(注意keepalived中以带s或者_vec结尾的名字区分单个关键字和关键字列表)

 

关键字层次组织

  下面分别从check_init_keywords(void) (位于check_parser.c文件中) 和 install_http_check_keyword(void)函数中摘取相关语句组成一个分层的关键字结构。摘取的语句如下:

 1 install_keyword_root("virtual_server_group", &vsg_handler); 2 install_keyword_root("virtual_server", &vs_handler); 3 install_keyword("delay_loop", &delay_handler); 4 install_keyword("real_server", &rs_handler); 5 install_sublevel(); 6 install_keyword("weight", &weight_handler); 7 install_keyword("HTTP_GET", &http_get_handler); 8 install_sublevel(); 9 install_keyword("connect_timeout", &connect_to_handler);10 install_keyword("url", &url_handler);11 install_sublevel();12 install_keyword("path", &path_handler);13 install_keyword("digest", &digest_handler);14 install_sublevel_end();15 install_sublevel_end();16 install_sublevel_end();

对应的关键字层次组织图如下:

 

上图中层次1中的第2个real_server在代码中对应的语句并没有写出来, 在这里画出是为了展现同一层次的关键字如何关联各自的下一层关键字。此外,关键字的sub指针实际上指向一个列表(vector),为了更形象地表达这个点, 图中采用了由sub指针指向列表中的各个元素的记法。 keywords对应全局的关键字列表(即层次0的关键字), delay_loop、real_server等位于层次1, weight, HTTP_GET等位于层次2, connect_timeout, url等位于层次3, path、digest位于层次4。

 

parser.c 源码内容:

#include <glob.h>#include <unistd.h>#include <libgen.h>#include <errno.h>#include "parser.h"#include "memory.h"#include "logger.h"/* global vars */vector_t *keywords;vector_t *current_keywords;FILE *current_stream;char *current_conf_file;         //当前的配置文件 可能包含include尽量的int reload = 0;/* local vars */static int sublevel = 0;voidkeyword_alloc(vector_t *keywords_vec, char *string, void (*handler) (vector_t *)) // 为要新增加的关键字分配内存空间,再用string和handler构造一个关键字, 添加新的关键字到关键字列表keywords_vec中{keyword_t *keyword;vector_alloc_slot(keywords_vec);keyword = (keyword_t *) MALLOC(sizeof(keyword_t));keyword->string = string;keyword->handler = handler;vector_set_slot(keywords_vec, keyword);}voidkeyword_alloc_sub(vector_t *keywords_vec, char *string, void (*handler) (vector_t *))// 为当前关键字列表keywords_vec的最后 一个关键字的第sublevel层关键字列表的最后一个元素的下一层关键字列表添加一个名字为string, 处理函数为handler的关键字{int i = 0;keyword_t *keyword;/* fetch last keyword */keyword = vector_slot(keywords_vec, vector_size(keywords_vec) - 1);/* position to last sub level */for (i = 0; i < sublevel; i++)keyword =    vector_slot(keyword->sub, vector_size(keyword->sub) - 1);/* First sub level allocation */if (!keyword->sub)keyword->sub = vector_alloc();/* add new sub keyword */keyword_alloc(keyword->sub, string, handler);}/* Exported helpers */voidinstall_sublevel(void)  // 表示将处理下一层次的关键字列表, 具体的实现是递增sublevel{sublevel++;}voidinstall_sublevel_end(void) // 表示回到上一层关键字列表, 具体的实现是递减sublevel{sublevel--;}voidinstall_keyword_root(char *string, void (*handler) (vector_t *)) // 为层次0(对应全局变量keywords)的关键字列表添加一个名字为string, 处理函数为handler的关键字{keyword_alloc(keywords, string, handler);}voidinstall_keyword(char *string, void (*handler) (vector_t *))// 为层次为sublevel的关键字列表添加一个名字为string, 处理函数为handler的关键字{keyword_alloc_sub(keywords, string, handler);}voiddump_keywords(vector_t *keydump, int level)  //打印关键字{int i, j;keyword_t *keyword_vec;for (i = 0; i < vector_size(keydump); i++) {keyword_vec = vector_slot(keydump, i);for (j = 0; j < level; j++)printf("  ");printf("Keyword : %s\n", keyword_vec->string);if (keyword_vec->sub)dump_keywords(keyword_vec->sub, level + 1);}}voidfree_keywords(vector_t *keywords_vec)//逐层释放关键字{keyword_t *keyword_vec;int i;for (i = 0; i < vector_size(keywords_vec); i++) {keyword_vec = vector_slot(keywords_vec, i);if (keyword_vec->sub)free_keywords(keyword_vec->sub);FREE(keyword_vec);}vector_free(keywords_vec);}vector_t *alloc_strvec(char *string){char *cp, *start, *token;int str_len;vector_t *strvec;if (!string)return NULL;cp = string;/* Skip white spaces */while (isspace((int) *cp) && *cp != '\0')cp++;/* Return if there is only white spaces */if (*cp == '\0')return NULL;/* Return if string begin with a comment */if (*cp == '!' || *cp == '#')return NULL;/* Create a vector and alloc each command piece */strvec = vector_alloc();while (1) {start = cp;if (*cp == '"') {cp++;token = MALLOC(2);*(token) = '"';*(token + 1) = '\0';} else {while (!isspace((int) *cp) && *cp != '\0' && *cp != '"')cp++;str_len = cp - start;token = MALLOC(str_len + 1);memcpy(token, start, str_len);*(token + str_len) = '\0';}/* Alloc & set the slot */vector_alloc_slot(strvec);vector_set_slot(strvec, token);while (isspace((int) *cp) && *cp != '\0')cp++;if (*cp == '\0' || *cp == '!' || *cp == '#')return strvec;}}void read_conf_file(char *conf_file) //函数首先取得配置文件名字conf_file对应的所有配置文件.然后依次取出每个配置文件进行处理                                                                                                                                                      {FILE *stream;char *path;int ret;glob_t globbuf;globbuf.gl_offs = 0;glob(conf_file, 0, NULL, &globbuf);int i;for(i = 0; i < globbuf.gl_pathc; i++){log_message(LOG_INFO, "Opening file '%s'.", globbuf.gl_pathv[i]);stream = fopen(globbuf.gl_pathv[i], "r");if (!stream) {log_message(LOG_INFO, "Configuration file '%s' open problem (%s)..."       , globbuf.gl_pathv[i], strerror(errno));return;}current_stream = stream;current_conf_file = globbuf.gl_pathv[i];char prev_path[MAXBUF];path = getcwd(prev_path, MAXBUF);if (!path) {log_message(LOG_INFO, "getcwd(%s) error (%s)"    , prev_path, strerror(errno));}char *confpath = strdup(globbuf.gl_pathv[i]);dirname(confpath);ret = chdir(confpath);if (ret < 0) {log_message(LOG_INFO, "chdir(%s) error (%s)"    , confpath, strerror(errno));}process_stream(current_keywords);fclose(stream);ret = chdir(prev_path);if (ret < 0) {log_message(LOG_INFO, "chdir(%s) error (%s)"    , prev_path, strerror(errno));}}globfree(&globbuf);}intcheck_include(char *buf) // 该函数检查该行数据是否包含”include”, 若不含”include”直接返回0, 否则会进一步解析include进来文件,包含返回1 否则返回0                                                                                                                                   {char *str;vector_t *strvec;char *path;int ret;strvec = alloc_strvec(buf);  //将字符串转换成列表if (!strvec){return 0;}str = vector_slot(strvec, 0);if (!strcmp(str, EOB)) {free_strvec(strvec);return 0;}if(!strcmp("include", str) && vector_size(strvec) == 2){char *conf_file = vector_slot(strvec, 1);FILE *prev_stream = current_stream;char *prev_conf_file = current_conf_file;char prev_path[MAXBUF];path = getcwd(prev_path, MAXBUF);if (!path) {log_message(LOG_INFO, "getcwd(%s) error (%s)\n"    , prev_path, strerror(errno));}read_conf_file(conf_file);current_stream = prev_stream;current_conf_file = prev_conf_file;ret = chdir(prev_path);if (ret < 0) {log_message(LOG_INFO, "chdir(%s) error (%s)\n"    , prev_path, strerror(errno));}return 1;}free_strvec(strvec);return 0;}intread_line(char *buf, int size) // 该函数用来从当前文件流中读取一行数据 若读到非”include”语句则返回该行数据(通过参数), 若遇到EOF, 则返回0{int ch;do {int count = 0;memset(buf, 0, MAXBUF);while ((ch = fgetc(current_stream)) != EOF && (int) ch != '\n'   && (int) ch != '\r') {if (count < size)buf[count] = (int) ch;elsebreak;count++;}} while (check_include(buf) == 1);return (ch == EOF) ? 0 : 1;}vector_t *read_value_block(void){char *buf;int i;char *str = NULL;char *dup;vector_t *vec = NULL;vector_t *elements = vector_alloc();buf = (char *) MALLOC(MAXBUF);while (read_line(buf, MAXBUF)) {vec = alloc_strvec(buf);if (vec) {str = vector_slot(vec, 0);if (!strcmp(str, EOB)) {free_strvec(vec);break;}if (vector_size(vec))for (i = 0; i < vector_size(vec); i++) {str = vector_slot(vec, i);dup = (char *) MALLOC(strlen(str) + 1);memcpy(dup, str, strlen(str));vector_alloc_slot(elements);vector_set_slot(elements, dup);}free_strvec(vec);}memset(buf, 0, MAXBUF);}FREE(buf);return elements;}voidalloc_value_block(vector_t *strvec, void (*alloc_func) (vector_t *)){char *buf;char *str = NULL;vector_t *vec = NULL;buf = (char *) MALLOC(MAXBUF);while (read_line(buf, MAXBUF)) {vec = alloc_strvec(buf);if (vec) {str = vector_slot(vec, 0);if (!strcmp(str, EOB)) {free_strvec(vec);break;}if (vector_size(vec))(*alloc_func) (vec);free_strvec(vec);}memset(buf, 0, MAXBUF);}FREE(buf);}void *set_value(vector_t *strvec){char *str = vector_slot(strvec, 1);int size = strlen(str);int i = 0;int len = 0;char *alloc = NULL;char *tmp;if (*str == '"') {for (i = 2; i < vector_size(strvec); i++) {str = vector_slot(strvec, i);len += strlen(str);if (!alloc)alloc =    (char *) MALLOC(sizeof (char *) *    (len + 1));else {alloc =    REALLOC(alloc, sizeof (char *) * (len + 1));tmp = vector_slot(strvec, i-1);if (*str != '"' && *tmp != '"')strncat(alloc, " ", 1);}if (i != vector_size(strvec)-1)strncat(alloc, str, strlen(str));}} else {alloc = MALLOC(sizeof (char *) * (size + 1));memcpy(alloc, str, size);}return alloc;}/* recursive configuration stream handler */static int kw_level = 0;voidprocess_stream(vector_t *keywords_vec){int i;keyword_t *keyword_vec;char *str;char *buf;vector_t *strvec;vector_t *prev_keywords = current_keywords;current_keywords = keywords_vec;buf = zalloc(MAXBUF);while (read_line(buf, MAXBUF)) {strvec = alloc_strvec(buf);memset(buf,0, MAXBUF);if (!strvec)continue;str = vector_slot(strvec, 0);if (!strcmp(str, EOB) && kw_level > 0) {free_strvec(strvec);break;}for (i = 0; i < vector_size(keywords_vec); i++) {keyword_vec = vector_slot(keywords_vec, i);if (!strcmp(keyword_vec->string, str)) {if (keyword_vec->handler)(*keyword_vec->handler) (strvec);if (keyword_vec->sub) {kw_level++;process_stream(keyword_vec->sub);kw_level--;}break;}}free_strvec(strvec);}current_keywords = prev_keywords;free(buf);return;}/* Data initialization */voidinit_data(char *conf_file, vector_t * (*init_keywords) (void)) // 数据的初始化  分配一个关键字列表 使其成为当前的关键字列表 解析配置文件后释放{/* Init Keywords structure */keywords = vector_alloc();(*init_keywords) ();#if 0/* Dump configuration */vector_dump(keywords);dump_keywords(keywords, 0);#endif/* Stream handling */current_keywords = keywords;read_conf_file((conf_file) ? conf_file : CONF);free_keywords(keywords);}


 

 

 

 

关键字操作介绍

 

  如上所介绍,keepalived通过install_keyword_root()、 install_keyword()、 Install_sublevel()和install_sublevel_end()等操作构建关键字层次组织。 下面对这些关键字操作及它们内部调用 的函数进行介绍。

  install_keyword_root(char *string, void (*handler) (vector))为层次0(对应全局变量keywords)的关键字列表添加一个名字为string, 处理函数为handler的关键字。 具体的实现是通过调用keyword_alloc(keywords, string, handler)来实现的。

  install_keyword(char *string, void (*handler) (vector))为层次为sublevel的关键字列表添加一个名字为string, 处理函数为handler的关键字。 具体 的实现是通过调用keyword_alloc_sub(keywords, string, handler)来实现的。

  install_ sublevel(void)表示将处理下一层次的关键字列表, 具体的实现是递增sublevel。

  install_sublevel_end(void)表示回到上一层关键字列表, 具体的实现是递减sublevel。

  keyword_alloc(vector keywords_vec, char *string, void (*handler) (vector))为关键字列表keywords_vec添加一个名字为string, 处理函数为handler的关键字。具体的实现是先调用vector_alloc_slot(keywords_vec)为要新增加的关键字分配内存空间,  再用string和handler构造一个关键字, 最后调用vector_set_slot(keywords_vec, keyword)添加新的关键字到关键字列表keywords_vec中。

  keyword_alloc_sub(vector keywords_vec, char *string, void (*handler) (vector))为当前关键字列表keywords_vec的最后 一个关键字的第sublevel层关键字列表的最后一个元素的下一层关键字列表添加一个名字为string, 处理函数为handler的关键字。具体的实现是先判断当前关键字列表keywords_vec的最后 一个关键字的第sublevel层关键字列表的最后一个元素的下一层关键字列表是否存在, 若不存在的话, 调用vector_alloc()分配下一层的关键字列表的内存空间。 最后调用keyword_alloc(keyword->sub, string, handler)为该层添加新的关键字。

        根据上面的总体流程, 在整个解析 配置文件的过程中有5个关键的函数: init_data(char *conf_file, vector (*init_keywords) (void))、 read_conf_file(char *conf_file)、process_stream(vector keywords_vec)、 read_line(char *buf, int size) 和check_include(char *buf)。下面分别对这个5个函数 进行分析。

  init_data(char *conf_file, vector (*init_keywords) (void))该函数首先调用vector_alloc()分配一个关键字列表, 并令全局变量keywords指向该列表, 然后设置当前的关键字列表current_keywords为keywords, 表示从层次0的关键字列表开始解析配置文件, 接着调用read_conf_file(conf_file)进行具体的配置文件解析, 结束后,释放一开始分配的关键字列表keywords。

  read_conf_file(char *conf_file)该函数首先调用glob(conf_file, 0, NULL, &globbuf)取得配置文件名字conf_file对应的所有配置文件, 然后依次取出每个配置文件进行处理。每个配置文件具体是这样处理的:先用fopen()打开该配置文件, 并保存文件流到当前文件流变量current_stream中, 并保存当前配置文件名字到current_conf_file中, 同时为了在被递归调用后可以回退到当前的配置文件继续处理, 它还保存了当前的路径到prev_path中, 然后调用process_stream(current_keywords)根据当前的关键字层次解析配置文件。直到由glob()取得的所有的配置文件解析完毕才退出read_conf_file()函数。

  process_stream(vector keywords_vec)该函数以当前关键字列表为参数, 并从当前的文件流current_stream中取得配置文件中的内容, 进行相应的处理。 具体地, 先保存当前的关键字列表到prev_keywords中(同样地, 为了后面的递归调用结束后可以回退到当前的处理), 然后循环调用read_line(buf, MAXBUF)从当前 文件流中读取一行, 若读取到的是EOF, 则退出循环;否则调用alloc_strvec(buf)把读取的该行数据转化为字符串列表, 并取得该列表的第一个元素(即行首的字符串)到str中,若str是大括号(“}”), 且当前 的关键字列表层次变量kw_level大于0, 则退出循环, 结束当前层次的关键字处理; 否则依次遍历当前关键字列表, 若逐一匹配str, 若找到与str相同的关键字, 则调用该关键字的handler()处理该行数据,若该关键字存在下一层次关键字列表, 则递增关键字列表变量kw_level, 再递归调用process_stream(keyword->sub)处理下一层次的关键字列表, 处理结束后,再递减关键字列表变量kw_level。实际上, 一个process_stream()对应着一层关键字列表处理(这里要注意一点, 即同一层次的关键字列表可以有多个, 只要它们的上一层关键字不同即可)。 (可参考下面的流程图)

 

 

    read_line(char *buf, int size)该函数用来从当前文件流中读取一行数据,并调用check_include(检测是否以”include”开头,若是的话,进入”include”的情况处理(下面的check_include()函数详细介绍),  且不返回该行数据给调用者, 继续读取下一行数据。若读到非”include”语句则返回该行数据(通过参数), 若遇到EOF, 则返回0( return 0)。

  check_include(char *buf)该函数检查该行数据是否包含”include”, 若不含”include”直接返回0, 否则会进一步解析include进来的文件。 具体地, 调用alloc_strvec(buf) 把该行数据转化为字符串列表, 然后判断行首的字符串是否为”include”, 是的话, 先保存当前的文件流current_stream到prev_stream, 当前的配置文件current_conf_file到prev_conf_file,当前的路径到prev_path中, 然后递归调用read_conf_file(conf_file)函数解析include进来的配置文件, 解析结束后, 还原current_stream, current_conf_file为之前保存的值。

 

本文参考和引用:http://www.cnblogs.com/quiet/archive/2012/03/18/2404826.html

原创粉丝点击