构造C语言解析yaml文件的库

构造C语言解析yaml文件的库

yaml官方提供了一套文件解析的方法,动态库是libyaml.so。官方地址:https://yaml.org/spec/1.1/

但是这个库的解析方式还是太原始,不好用。我们想一个比较好用的方式获取数据。想到suricata(库在github上,地址:https://github.com/OISF/suricata)有一套yaml的解析方法。于是把suricata的代码拿过来,封装一下,改成了lib库,方便以后使用。

不过suricata的解析方法不支持 锚点 引用,使用时要注意配置文件的编写

可以下载直接使用,代码也在下边列出来了,方便随时分析学习。

【2025-01-07】最近整理一下,把代码放在了 gitee 上:https://gitee.com/madbullsite/libparseyaml

目录结构

代码 Makefile

libs=libmyyaml.so

LIBS = -lyaml -lbsd -g -Wall
DLFLAGS = -shared -fPIC

%.o: %.c
        gcc -fPIC -o $@ -c $^ -g


all: cmptest

libmyyaml.so: conf-yaml.o conf-yaml-loader.o
        gcc $(DLFLAGS) -o $@ $^ $(LIBS)

install: $(libs)
        mv $(libs)      /usr/local/lib/
        cp conf-yaml.h conf-yaml-loader.h queue.h /usr/local/include/
        ldconfig

cmptest: install test

test: test.o
        gcc -o $@ $^ -lmyyaml $(LIBS)

clean:
        rm -f /usr/local/lib/$(libs) *.o test $(libs)

代码 conf-yaml-loader.c

#include <stdio.h>
#include <stdlib.h>
#include <yaml.h>
#include <limits.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <bsd/string.h>

#include "conf-yaml.h"
#include "conf-yaml-loader.h"

// 支持的版本号
#define YAML_VERSION_MAJOR 1
#define YAML_VERSION_MINOR 1

/* The maximum level of recursion allowed while parsing the YAML
 * file. */
#define RECURSION_LIMIT 128
/* Sometimes we'll have to create a node name on the fly (integer
 * conversion, etc), so this is a default length to allocate that will
 * work most of the time. */
#define DEFAULT_NAME_LEN 16

#define MANGLE_ERRORS_MAX 10
static int mangle_errors = 0;

// yaml 配置文件路径
static char *conf_dirname = NULL;

static int ConfYamlParse(yaml_parser_t *parser, ConfNode *parent, int inseq, int rlevel, int state);

/* Configuration processing states. */
enum conf_state {
    CONF_KEY = 0,
    CONF_VAL,
    CONF_INCLUDE,
};

/**
 * 转换不支持的字符 _ 转换成 -
 */
static void
Mangle(char *string)
{
    char *c;

    while ((c = strchr(string, '_')))
        *c = '-';

    return;
}

/**
 * 设置配置文件的目录,include引入的时候,如果是相对路径,则根据设置的路径拼接
 *      全局变量 conf_dirname 赋值
 * 参数   配置文件路径
 */
static void
ConfYamlSetConfDirname(const char *filename)
{
    char *ep;

    ep = strrchr(filename, '\\');
    if (ep == NULL)
        ep = strrchr(filename, '/');

    if (ep == NULL) { // 没找到,用 . 作为当前目录
        conf_dirname = strdup(".");
        if (conf_dirname == NULL) {
            fprintf(stderr, "ERROR: Failed to allocate memory while loading configuration.\n");
        }
    }
    else { // 找到,copy出目录副本,并报错
        conf_dirname = strdup(filename);
        if (conf_dirname == NULL) {
            fprintf(stderr, "ERROR: Failed to allocate memory while loading configuration.\n");
        }
        conf_dirname[ep - filename] = '\0';
    }
}

/**
 * 在配置里引入一个文件
 * 参数
 *     parent    解析的数据都放在此节点
 *     filename  引入的文件名
 * 返回值   0 - 解析成功
 *         1 - 解析失败
 */
int ConfYamlHandleInclude(ConfNode *parent, const char *filename)
{
    yaml_parser_t parser; 
    char include_filename[PATH_MAX];
    FILE *file = NULL;
    int ret = -1;

    // 重新初始化新的解析句柄
    if (yaml_parser_initialize(&parser) != 1) {
        fprintf(stderr, "Failed to initialize YAML parser\n");
        return -1;
    }

    // 判断是否为绝对路径
    if (PathIsAbsolute(filename)) {
        strlcpy(include_filename, filename, sizeof(include_filename));
    }
    else {
        // 拼写出路径
        snprintf(include_filename, sizeof(include_filename), "%s/%s",
            conf_dirname, filename);
    }

    file = fopen(include_filename, "r");
    if (file == NULL) {
        fprintf(stderr, "Failed to open configuration include file %s: %s\n", include_filename,
                strerror(errno));
        goto done;
    }

    // 设置要解析的文件句柄
    yaml_parser_set_input_file(&parser, file);

    // include引入的文件 解析
    if (ConfYamlParse(&parser, parent, 0, 0, 0) != 0) {
        fprintf(stderr, "Failed to include configuration file %s\n", filename);
        goto done;
    }

    ret = 0;

done:
    yaml_parser_delete(&parser);
    if (file != NULL) {
        fclose(file);
    }

    return ret;
}

/**
 * 解析yaml
 *
 * 参数     parser 活动的 yaml_parser_t 指针
 *         parent 父节点 node
 *         inseq 列表的解析
 *         rlevel 
 *         state 当前 解析状态
 *
 * 返回值:0 - success, -1 - failure.
 *
 * 调用的地方: 
 *        1、 首次文档解析
 *        2、 include引入的文件 解析
 *        3、 列表的解析
 *        4、 mapping的解析
 */
static int ConfYamlParse(yaml_parser_t *parser, ConfNode *parent, int inseq, int rlevel, int state)
{
    ConfNode *node = parent;
    yaml_event_t event;
    memset(&event, 0, sizeof(event));
    int done = 0;
    int seq_idx = 0;   // 列表的索引, 从 0 开始
    int retval = 0;
    int was_empty = -1;
    int include_count = 0;

    // rlevel 给递归层级增加,从新调用一次,就增加一次
    if (rlevel++ > RECURSION_LIMIT) {
        fprintf(stderr, "Recursion limit reached while parsing "
                   "configuration file, aborting.\n");
        return -1;
    }

    while (!done) {
        if (!yaml_parser_parse(parser, &event)) {  // 解析下一个事件
            fprintf(stderr, "Failed to parse configuration file at line %" PRIuMAX ": %s\n",
                    (uintmax_t)parser->problem_mark.line, parser->problem);
            retval = -1;
            break;
        }

        if (event.type == YAML_DOCUMENT_START_EVENT) {   // 文档事件
            fprintf(stdout, "event.type=YAML_DOCUMENT_START_EVENT; state=%d\n", state);
            /* 验证 YAML 版本号 */
            yaml_version_directive_t *ver =
                event.data.document_start.version_directive;
            if (ver == NULL) {
                fprintf(stderr, "ERROR: Invalid configuration file.\n");
                fprintf(stderr, "The configuration file must begin with the following two lines: %%YAML "
                           "1.1 and ---\n");
                goto fail;
            }
            int major = ver->major;
            int minor = ver->minor;
            if (!(major == YAML_VERSION_MAJOR && minor == YAML_VERSION_MINOR)) {
                fprintf(stderr, "ERROR: Invalid YAML version.  Must be 1.1\n");
                goto fail;
            }
        }
        else if (event.type == YAML_SCALAR_EVENT) { // 标量
            char *value = (char *)event.data.scalar.value;   // 值
            char *tag = (char *)event.data.scalar.tag;       // 键
            fprintf(stdout, "event.type=YAML_SCALAR_EVENT; state=%d; value=%s; "
                "tag=%s; inseq=%d\n", state, value, tag, inseq);

            // 跳过没有值的空标量
            if (state == CONF_KEY && strlen(value) == 0) {
                goto next;
            }

            /* If the value is unquoted, certain strings in YAML represent NULL. */
            // 如果值没有引用,YAML中有些字符串表示NULL
            if ((inseq || state == CONF_VAL) &&
                    event.data.scalar.style == YAML_PLAIN_SCALAR_STYLE) { 
                // 表示NULL的一些值
                if (strlen(value) == 0 || strcmp(value, "~") == 0 || strcmp(value, "null") == 0 ||
                        strcmp(value, "Null") == 0 || strcmp(value, "NULL") == 0) {
                    value = NULL;
                }
            }
            // 列表的处理
            if (inseq) {
                // 引入文件状态的处理
                if (state == CONF_INCLUDE) {
                    if (value != NULL) {
                        fprintf(stdout, "Including configuration file %s.\n", value);
                        if (ConfYamlHandleInclude(parent, value) != 0) {
                            goto fail;
                        }
                    }
                    goto next;
                }
                // 把 索引作为 路径节点名字, 从 0 开始
                char sequence_node_name[DEFAULT_NAME_LEN];
                snprintf(sequence_node_name, DEFAULT_NAME_LEN, "%d", seq_idx++);
                ConfNode *seq_node = NULL;
                if (was_empty < 0) {
                    // initialize was_empty
                    if (TAILQ_EMPTY(&parent->head)) {
                        was_empty = 1;
                    } else {
                        was_empty = 0;
                    }
                }
                // 判断节点是否已经存在
                if (was_empty == 0) {
                    seq_node = ConfNodeLookupChild(parent, sequence_node_name);
                }
                if (seq_node != NULL) {
                    // 这里先从兄弟队列里删除,20行之后等会还会加上,这里不会清数据
                    TAILQ_REMOVE(&parent->head, seq_node, next);
                }
                else {
                    // 创建节点,并赋值
                    seq_node = ConfNodeNew();
                    if (unlikely(seq_node == NULL)) {
                        goto fail;
                    }
                    seq_node->name = strdup(sequence_node_name);
                    if (unlikely(seq_node->name == NULL)) {
                        free(seq_node);
                        goto fail;
                    }
                    if (value != NULL) { // 如果value有值,则赋值
                        seq_node->val = strdup(value);
                        if (unlikely(seq_node->val == NULL)) {
                            free(seq_node->name);
                            goto fail;
                        }
                    } else {   // 否则为 NULL
                        seq_node->val = NULL;
                    }
                }
                // 把节点插入到
                TAILQ_INSERT_TAIL(&parent->head, seq_node, next);
            }
            else {
                if (state == CONF_INCLUDE) {
                    fprintf(stdout, "Including configuration file %s.\n", value);
                    // 解析子文件,parent用来接收解析的数据,把值给到处理函数
                    if (ConfYamlHandleInclude(parent, value) != 0) {
                        goto fail;
                    }
                    // 解析完了,修改解析状态为:需要解析KEY
                    state = CONF_KEY;
                }
                else if (state == CONF_KEY) {

                    if (strcmp(value, "include") == 0) {
                        // 修改状态:包含子文件状态
                        state = CONF_INCLUDE;
                        if (++include_count > 1) {
                            fprintf(stderr, "Multipline \"include\" fields at the same level are "
                                         "deprecated and will not work in Suricata 8, please move "
                                         "to an array of include files: line: %zu\n",
                                    parser->mark.line);
                        }
                        goto next;
                    }

                    if (parent->is_seq) {
                        if (parent->val == NULL) {
                            parent->val = strdup(value);
                            if (parent->val && strchr(parent->val, '_'))
                                // _ 转换成 - 
                                Mangle(parent->val);
                        }
                    }

                    if (strchr(value, '.') != NULL) {
                        node = ConfNodeGetNodeOrCreate(parent, value, 0);
                        if (node == NULL) {
                            /* Error message already logged. */
                            goto fail;
                        }
                    } else {
                        ConfNode *existing = ConfNodeLookupChild(parent, value);
                        if (existing != NULL) {
                            if (!existing->final) {
                                fprintf(stdout, "Configuration node '%s' redefined.\n", existing->name);
                                ConfNodePrune(existing);
                            }
                            node = existing;
                        } else {
                            node = ConfNodeNew();
                            node->name = strdup(value);
                            node->parent = parent;
                            if (node->name && strchr(node->name, '_')) {
                                if (!(parent->name &&
                                            ((strcmp(parent->name, "address-groups") == 0) ||
                                                    (strcmp(parent->name, "port-groups") == 0)))) {
                                    // _ 转换成 - 
                                    Mangle(node->name);
                                    if (mangle_errors < MANGLE_ERRORS_MAX) {
                                        fprintf(stderr,
                                                "%s is deprecated. Please use %s on line %" PRIuMAX 
                                                ".\n",
                                                value, node->name,
                                                (uintmax_t)parser->mark.line + 1);
                                        mangle_errors++;
                                        if (mangle_errors >= MANGLE_ERRORS_MAX)
                                            fprintf(stderr,
                                                    "not showing more "
                                                    "parameter name warnings.\n");
                                    }
                                }
                            }
                            TAILQ_INSERT_TAIL(&parent->head, node, next);
                        }
                    }
                    // 修改状态,需要获得值
                    state = CONF_VAL;
                }
                else {
                    if (value != NULL && (tag != NULL) && (strcmp(tag, "!include") == 0)) {
                        fprintf(stdout, "Including configuration file %s at "
                            "parent node %s.\n", value, node->name);
                        if (ConfYamlHandleInclude(node, value) != 0)
                            goto fail;
                    } else if (!node->final && value != NULL) {
                        if (node->val != NULL)
                            free(node->val);
                        node->val = strdup(value);
                    }
                    // 修改状态:需要解析KEY
                    state = CONF_KEY;
                }
            }
        }
        else if (event.type == YAML_SEQUENCE_START_EVENT) {
            fprintf(stdout, "event.type=YAML_SEQUENCE_START_EVENT; state=%d\n", state);
            // 如果处理列表是include列表,使用 当前节点
            // 状态如果是 CONF_INCLUDE   下一级解析传递 parent节点,否则传递 当前节点
            // inseq 标记为 1 此次处理的是被别的文件引入的
            // 列表 解析进入 递归调用
            if (ConfYamlParse(parser, state == CONF_INCLUDE ? parent : node, 1, rlevel,
                        state == CONF_INCLUDE ? CONF_INCLUDE : 0) != 0)
                goto fail;
            // 设置此节点是 列表
            node->is_seq = 1;
            // 修改状态:需要解析KEY
            state = CONF_KEY;
        }
        else if (event.type == YAML_SEQUENCE_END_EVENT) {      // 列表结束
            fprintf(stderr, "event.type=YAML_SEQUENCE_END_EVENT; state=%d\n", state);
            done = 1; // 结束,函数退出
        }
        else if (event.type == YAML_MAPPING_START_EVENT) {      // MAPPING 开始事件
            fprintf(stdout, "event.type=YAML_MAPPING_START_EVENT; state=%d\n", state);
            if (state == CONF_INCLUDE) {
                fprintf(stderr, "Include fields cannot be a mapping: line %zu\n", parser->mark.line);
                goto fail;
            }
            if (inseq) { // 列表里 mapping 的处理方式
                char sequence_node_name[DEFAULT_NAME_LEN];
                // 把 索引作为 路径节点名字, 从 0 开始
                snprintf(sequence_node_name, DEFAULT_NAME_LEN, "%d", seq_idx++);
                // 查找对应的 节点
                ConfNode *seq_node = ConfNodeLookupChild(node,
                    sequence_node_name);
                if (seq_node != NULL) {
                    // 先从解析结果里删除,后面再添加
                    TAILQ_REMOVE(&node->head, seq_node, next);
                }
                else {
                    // 没有节点,就创建节点
                    seq_node = ConfNodeNew();
                    if (unlikely(seq_node == NULL)) {
                        goto fail;
                    }
                    seq_node->name = strdup(sequence_node_name);
                    if (unlikely(seq_node->name == NULL)) {
                        free(seq_node);
                        goto fail;
                    }
                }
                seq_node->is_seq = 1;
                TAILQ_INSERT_TAIL(&node->head, seq_node, next);
                // mapping的解析,进入递归调用
                if (ConfYamlParse(parser, seq_node, 0, rlevel, 0) != 0)
                    goto fail;
            }
            else {
                if (ConfYamlParse(parser, node, inseq, rlevel, 0) != 0)
                    goto fail;
            }
            state = CONF_KEY;
        }
        else if (event.type == YAML_MAPPING_END_EVENT) {
            fprintf(stdout, "event.type=YAML_MAPPING_END_EVENT; state=%d\n", state);
            done = 1; // 结束,函数退出
        }
        else if (event.type == YAML_STREAM_END_EVENT) {
            fprintf(stdout, "event.type=YAML_STREAM_END_EVENT; state=%d\n", state);
            done = 1; // 结束,函数退出
        }

    next:
        // 清理本次事件,进入下一循环,继续处理数据
        yaml_event_delete(&event);
        continue;

    fail:
        // 失败跳出循环
        yaml_event_delete(&event);
        retval = -1;
        break;
    }

    rlevel--;
    return retval;
}

/**
 * 解析yaml文件
 * 返回值:0 - success, -1 - failure.
 */
int
ConfYamlLoadFile(const char *filename)
{
    FILE *infile;
    yaml_parser_t parser; // yaml解析句柄
    int ret;
    ConfNode *root = ConfGetRootNode();

    if (yaml_parser_initialize(&parser) != 1) {
        fprintf(stderr, "failed to initialize yaml parser.\n");
        return -1;
    }

    struct stat stat_buf;
    if (stat(filename, &stat_buf) == 0) {
        if (stat_buf.st_mode & S_IFDIR) {
            fprintf(stderr, "yaml argument is not a file but a directory: %s. "
                       "Please specify the yaml file in your -c option.\n",
                    filename);
            yaml_parser_delete(&parser);
            return -1;
        }
    }

    // coverity[toctou : FALSE]
    infile = fopen(filename, "r");
    if (infile == NULL) {
        fprintf(stderr, "failed to open file: %s: %s\n", filename, strerror(errno));
        yaml_parser_delete(&parser);
        return -1;
    }

    if (conf_dirname == NULL) {
        // 设置配置文件路径
        ConfYamlSetConfDirname(filename);
    }

    // 设置要解析的文件句柄
    yaml_parser_set_input_file(&parser, infile);
    ret = ConfYamlParse(&parser, root, 0, 0, 0);
    yaml_parser_delete(&parser);
    fclose(infile);

    return ret;
}

/**
 * 加载解析 yaml文件的 字符串
 */
int
ConfYamlLoadString(const char *string, size_t len)
{
    ConfNode *root = ConfGetRootNode();
    yaml_parser_t parser;
    int ret;

    if (yaml_parser_initialize(&parser) != 1) {
        fprintf(stderr, "Failed to initialize yaml parser.\n");
        exit(EXIT_FAILURE);
    }
    yaml_parser_set_input_string(&parser, (const unsigned char *)string, len);
    ret = ConfYamlParse(&parser, root, 0, 0, 0);
    yaml_parser_delete(&parser);

    return ret;
}

/**
 * 解析一个文件 filename,把解析的数据放在 prefix 节点下
 * 如果没有此节点,就创建一个空节点,并把解析的数据挂载此节点下。
 *
 * 返回值    0 - 成功
 *          1 - 失败
 */
int
ConfYamlLoadFileWithPrefix(const char *filename, const char *prefix)
{
    FILE *infile;
    yaml_parser_t parser;
    int ret;
    // 找到这个节点
    ConfNode *root = ConfGetNode(prefix);

    if (yaml_parser_initialize(&parser) != 1) {
        fprintf(stderr, "failed to initialize yaml parser.\n");
        return -1;
    }

    // 检测文件
    struct stat stat_buf;
    if (stat(filename, &stat_buf) == 0) {
        if (stat_buf.st_mode & S_IFDIR) {
            fprintf(stderr, "yaml argument is not a file but a directory: %s. "
                       "Please specify the yaml file in your -c option.\n",
                    filename);
            return -1;
        }
    }

    // 打开文件
    infile = fopen(filename, "r");
    if (infile == NULL) {
        fprintf(stderr, "failed to open file: %s: %s\n", filename, strerror(errno));
        yaml_parser_delete(&parser);
        return -1;
    }

    if (conf_dirname == NULL) {
        ConfYamlSetConfDirname(filename);
    }

    if (root == NULL) { 
        // 没找到此节点,添加一个空节点,值设置为 "<prefix root node>"
        ConfSet(prefix, "<prefix root node>");
        // 再次获取此节点
        root = ConfGetNode(prefix);
        if (root == NULL) {
            // 获取不到就算了,不解析了
            fclose(infile);
            yaml_parser_delete(&parser);
            return -1;
        }
    }
    // 设置yaml文件描述符
    yaml_parser_set_input_file(&parser, infile);
    ret = ConfYamlParse(&parser, root, 0, 0, 0);
    yaml_parser_delete(&parser);
    fclose(infile);

    return ret;
}

代码 conf-yaml-loader.h

#ifndef __CONF_YAML_LOADER_H__
#define __CONF_YAML_LOADER_H__

#include "conf-yaml.h"

int ConfYamlLoadFile(const char *);
int ConfYamlLoadString(const char *, size_t);
int ConfYamlLoadFileWithPrefix(const char *filename, const char *prefix);
int ConfYamlHandleInclude(ConfNode *parent, const char *filename);

void ConfYamlRegisterTests(void);

#endif /* !__CONF_YAML_LOADER_H__ */

代码 conf-yaml.c

#include <stdio.h>
#include <stdlib.h>
#include <yaml.h>
#include <limits.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <bsd/string.h>

#include "conf-yaml.h"

/** 节点全名称最大长度。全名称指,以 . 连接各路径的最终名称 */
#define NODE_NAME_MAX 1024


// 全局变量,保存 解析后的 yaml结构根的地址
static ConfNode *yaml_root = NULL;
// 地址备份使用
static ConfNode *yaml_root_backup = NULL;


/**
 * 获得节点,如果没有找到,则创建节点,并加入到指定位置并返回
 * 在全节点查找
 *
 * 参数:    parent 用来作为父节点的节点
 *          name 想要获取的节点名称
 *          final 设置final标记
 * 返回值:  成功则返回  找到的或者创建的node
 *         失败则返回  NULL
 */
ConfNode *ConfNodeGetNodeOrCreate(ConfNode *parent, const char *name, int final)
{
    ConfNode *node = NULL;
    char node_name[NODE_NAME_MAX];
    char *key;
    char *next;

    if (strlcpy(node_name, name, sizeof(node_name)) >= sizeof(node_name)) {
        fprintf(stderr, "Configuration name too long: %s\n", name);
        return NULL;
    }

    key = node_name;

    do {
        if ((next = strchr(key, '.')) != NULL)
            *next++ = '\0';
        if ((node = ConfNodeLookupChild(parent, key)) == NULL) {
            // 没找到,创建,并设置name
            node = ConfNodeNew(); 
            if (unlikely(node == NULL)) {
                fprintf(stderr, "Failed to allocate memory for configuration.\n");
                goto end;
            }
            node->name = strdup(key);
            if (unlikely(node->name == NULL)) {
                ConfNodeFree(node);
                node = NULL;
                fprintf(stderr, "Failed to allocate memory for configuration.\n");
                goto end;
            }
            node->parent = parent;
            node->final = final;
            // 在父节点插入刚申请的节点
            TAILQ_INSERT_TAIL(&parent->head, node, next);
        }
        // 接续准备下一级查找或者创建
        key = next;
        parent = node;
    } while (next != NULL);

end:
    // 最后返回节点
    return node;
}

/**
 * 封装 ConfNodeGetNodeOrCreate, 从 yaml_root 查找节点
 */
static ConfNode *ConfGetNodeOrCreate(const char *name, int final)
{
    return ConfNodeGetNodeOrCreate(yaml_root, name, final);
}

/**
 * 初始化配置系统,给全局变量 yaml_root 申请 ConfNode 节点
 */
void ConfInit(void)
{
    if (yaml_root != NULL) {
        fprintf(stdout, "already initialized\n");
        return;
    }
    yaml_root = ConfNodeNew();
    if (yaml_root == NULL) {
        fprintf(stderr, "ERROR: Failed to allocate memory for yaml_root configuration node, "
                   "aborting.\n");
    }
    fprintf(stdout, "configuration module initialized\n");
}

/**
 * 申请一个 ConfNode 节点
 *
 * 返回值 成功 - 返回申请的配置节点 node
 *       失败 - NULL
 */
ConfNode *ConfNodeNew(void)
{
    ConfNode *new;

    new = calloc(1, sizeof(*new));
    if (unlikely(new == NULL)) {
        return NULL;
    }
    TAILQ_INIT(&new->head);

    return new;
}

/**
 * 释放节点,并递归释放所有子节点
 */
void ConfNodeFree(ConfNode *node)
{
    ConfNode *tmp;

    while ((tmp = TAILQ_FIRST(&node->head))) { // 没有了子节点
        // 从兄弟节点上去掉
        TAILQ_REMOVE(&node->head, tmp, next);
        // 释放节点和子节点
        ConfNodeFree(tmp);
    }

    if (node->name != NULL)
        free(node->name);
    if (node->val != NULL)
        free(node->val);
    free(node);
}

/**
 * 根据节点名字返回节点
 * 从 yaml_root 开始查找,输入 节点的全路径
 * 深度查找
 *
 * 返回值  查询到节点,返回此节点。
 *        如果节点没找到,返回NULL
 */
ConfNode *ConfGetNode(const char *name)
{
    ConfNode *node = yaml_root;
    char node_name[NODE_NAME_MAX];
    char *key;
    char *next;

    if (strlcpy(node_name, name, sizeof(node_name)) >= sizeof(node_name)) {
        fprintf(stderr, "Configuration name too long: %s\n", name);
        return NULL;
    }

    key = node_name;
    do {
        if ((next = strchr(key, '.')) != NULL)
            *next++ = '\0';
        // root 节点是没有数据的,所有从孩子节点中查找
        node = ConfNodeLookupChild(node, key);
        key = next;
    } while (next != NULL && node != NULL);

    return node;
}

/**
 * 返回 yaml_root 值
 */
ConfNode *ConfGetRootNode(void)
{
    return yaml_root;
}

/**
 * 设置配置值,
 * 如果节点找到,并且是final则返回失败
 *
 * 返回值    1 - 设置成功
 *          0 - 设置失败
 */
int ConfSet(const char *name, const char *val)
{
    // 找到节点,没有就创建节点
    ConfNode *node = ConfGetNodeOrCreate(name, 0);
    if (node == NULL || node->final) {
        return 0;
    }
    if (node->val != NULL)
        free(node->val);
    node->val = strdup(val);
    if (unlikely(node->val == NULL)) {
        return 0;
    }
    return 1;
}

/**
 * 从字符串解析yaml节点
 * 举例:  aaa.bbb.ccc = hello world
 * 会被设置成  aaa的子节点bbb的子节点ccc的val值 = "hello world"
 *
 * 返回值    1 - value被设置上
 *          0 - value没有被设置
 */
int ConfSetFromString(const char *input, int final)
{
    int retval = 0;
    char *name = strdup(input), *val = NULL;
    if (unlikely(name == NULL)) {
        goto done;
    }
    val = strchr(name, '=');
    if (val == NULL) {
        goto done;
    }
    *val++ = '\0';

    // 处理name的数据,去掉后方的空白字符
    while (isspace((int)name[strlen(name) - 1])) {
        name[strlen(name) - 1] = '\0';
    }

    // 处理val的数据,去掉前方的空白字符
    while (isspace((int)*val)) {
        val++;
    }

    // 添加 节点  根据 final 设置
    if (final) {
        if (!ConfSetFinal(name, val)) {
            goto done;
        }
    }
    else {
        if (!ConfSet(name, val)) {
            goto done;
        }
    }

    retval = 1;
done:
    if (name != NULL) {
        free(name);
    }
    return retval;
}

/**
 * 设置最终值,并设置为最终值,不能被覆盖,final设置为1
 *
 * 返回值     1 - 设置成功
 *           0 - 设置失败
 */
int ConfSetFinal(const char *name, const char *val)
{
    ConfNode *node = ConfGetNodeOrCreate(name, 1);
    if (node == NULL) {
        return 0;
    }
    if (node->val != NULL)
        free(node->val);
    node->val = strdup(val);
    if (unlikely(node->val == NULL)) {
        return 0;
    }
    node->final = 1;
    return 1;
}

/**
 * 根据名字获取节点值,从yaml_root结点开始查询
 * 输入全路径 和 指向val值的指针的地址。
 * 给 *vptr 赋值的是数据原地址,不是副本。
 * 深度查找
 *
 * 返回值  1 - 节点找到   0 - 没找到节点
 */
int ConfGet(const char *name, const char **vptr)
{
    ConfNode *node = ConfGetNode(name);
    if (node == NULL) {
        fprintf(stdout, "failed to lookup configuration parameter '%s'\n", name);
        return 0;
    }
    else {
        *vptr = node->val;
        return 1;
    }
}

/**
 * 根据 name 查找指定节点 base 的子节点,并把节点值的地址给到 *vptr
 * 只在子节点查找
 *
 * 返回值     1 - 成功
 *           0 - 失败
 */
int ConfGetChildValue(const ConfNode *base, const char *name, const char **vptr)
{
    ConfNode *node = ConfNodeLookupChild(base, name);

    if (node == NULL) {
        fprintf(stdout, "failed to lookup configuration parameter '%s'\n", name);
        return 0;
    }
    else {
        if (node->val == NULL)
            return 0;
        *vptr = node->val;
        return 1;
    }
}

/**
 * 从base的子节点中获取名字是name的节点,
 * 如果base节点的子节点找不到则从默认的节点dflt的子节点查找name节点
 * 如果两个都找不到,则返回NULL
 *
 * 返回值:     找到,返回name的节点
 *            找不到,返回NULL
 */
ConfNode *ConfGetChildWithDefault(const ConfNode *base, const ConfNode *dflt,
    const char *name)
{
    ConfNode *node = ConfNodeLookupChild(base, name);
    if (node != NULL)
        return node;

    /* Get 'default' value */
    if (dflt) {
        return ConfNodeLookupChild(dflt, name);
    }
    return NULL;
}

int ConfGetChildValueWithDefault(const ConfNode *base, const ConfNode *dflt,
    const char *name, const char **vptr)
{
    int ret = ConfGetChildValue(base, name, vptr);
    /* Get 'default' value */
    if (ret == 0 && dflt) {
        return ConfGetChildValue(dflt, name, vptr);
    }
    return ret;
}

/**
 * 尝试找到 name 节点并把值转换成 integer 类型的值,赋值给 *val
 *
 * 返回值    1 - 名字被找到,并且争取转换成 integer
 *          0 - 失败
 */
int ConfGetInt(const char *name, intmax_t *val)
{
    const char *strval = NULL;
    intmax_t tmpint;
    char *endptr;

    if (ConfGet(name, &strval) == 0)
        return 0;

    if (strval == NULL) {
        fprintf(stderr, "malformed integer value "
                   "for %s: NULL\n",
                name);
        return 0;
    }

    errno = 0;
    tmpint = strtoimax(strval, &endptr, 0);
    if (strval[0] == '\0' || *endptr != '\0') {
        fprintf(stderr, "malformed integer value "
                   "for %s: '%s'\n",
                name, strval);
        return 0;
    }
    if (errno == ERANGE && (tmpint == INTMAX_MAX || tmpint == INTMAX_MIN)) {
        fprintf(stderr, "integer value for %s out "
                   "of range: '%s'\n",
                name, strval);
        return 0;
    }

    *val = tmpint;
    return 1;
}

/**
 * 从给定的节点 base 的子节点中,查找名字为 name 的配置,然后转换成int类型给 *val 赋值
 *
 * 返回值    1 - 名字被找到,并且正确转换成 integer
 *          0 - 失败
 */
int ConfGetChildValueInt(const ConfNode *base, const char *name, intmax_t *val)
{
    const char *strval = NULL;
    intmax_t tmpint;
    char *endptr;

    if (ConfGetChildValue(base, name, &strval) == 0)
        return 0;
    errno = 0;
    tmpint = strtoimax(strval, &endptr, 0);
    if (strval[0] == '\0' || *endptr != '\0') {
        fprintf(stderr, "malformed integer value "
                   "for %s with base %s: '%s'\n",
                name, base->name, strval);
        return 0;
    }
    if (errno == ERANGE && (tmpint == INTMAX_MAX || tmpint == INTMAX_MIN)) {
        fprintf(stderr, "integer value for %s with "
                   " base %s out of range: '%s'\n",
                name, base->name, strval);
        return 0;
    }

    *val = tmpint;
    return 1;

}


/**
 * 给 ConfGetChildValueInt 加了个壳,base 的子节点 找不到就从 dflt 的子节点里找
 *
 * 返回值    1 - 名字被找到,并且正确转换成 integer
 *          0 - 失败
 */
int ConfGetChildValueIntWithDefault(const ConfNode *base, const ConfNode *dflt,
    const char *name, intmax_t *val)
{
    int ret = ConfGetChildValueInt(base, name, val);
    /* Get 'default' value */
    if (ret == 0 && dflt) {
        return ConfGetChildValueInt(dflt, name, val);
    }
    return ret;
}

/**
 * 根据name用boolean类型检索一个配置值
 *
 * 返回值    1 - 名字被找到,并且正确转换成 Bool 类型 1 或者 0
 *          0 - 失败
 */
int ConfGetBool(const char *name, int *val)
{
    const char *strval = NULL;

    *val = 0;
    if (ConfGet(name, &strval) != 1)
        return 0;

    // 判断是否为真
    *val = ConfValIsTrue(strval);

    return 1;
}

/**
 * 根据name在 base 节点的子节点中用boolean类型检索一个配置值,把转换的值赋值给 *val
 *
 * 返回值    1 - 名字被找到,并且正确转换成 Bool 类型 1 或者 0
 *          0 - 失败
 */
int ConfGetChildValueBool(const ConfNode *base, const char *name, int *val)
{
    const char *strval = NULL;

    *val = 0;
    if (ConfGetChildValue(base, name, &strval) == 0)
        return 0;

    *val = ConfValIsTrue(strval);

    return 1;
}

/**
 * ConfGetChildValueBool的一个封装,如果base的子节点没有,从默认节点 dflt中获取
 *
 * 返回值    1 - 名字被找到,并且正确转换成 Bool 类型 1 或者 0
 *          0 - 失败
 */
int ConfGetChildValueBoolWithDefault(const ConfNode *base, const ConfNode *dflt,
    const char *name, int *val)
{
    int ret = ConfGetChildValueBool(base, name, val);
    /* Get 'default' value */
    if (ret == 0 && dflt) {
        return ConfGetChildValueBool(dflt, name, val);
    }
    return ret;
}


/**
 * 判断是否为真
 * 判断是否为 1, yes, true or on
 *
 * 返回值     1 - 是真
 *           0 - 不是真
 */
int ConfValIsTrue(const char *val)
{
    const char *trues[] = {"1", "yes", "true", "on"};
    size_t u;

    for (u = 0; u < sizeof(trues) / sizeof(trues[0]); u++) {
        if (strcasecmp(val, trues[u]) == 0) {
            return 1;
        }
    }

    return 0;
}

/**
 * 判断是否为假,为假返回 1 
 * 判断是否为 0, no, false or off
 *
 * 返回值     1 - 是假
 *           0 - 不是假
 */
int ConfValIsFalse(const char *val)
{
    const char *falses[] = {"0", "no", "false", "off"};
    size_t u;

    for (u = 0; u < sizeof(falses) / sizeof(falses[0]); u++) {
        if (strcasecmp(val, falses[u]) == 0) {
            return 1;
        }
    }

    return 0;
}

/**
 * 使用 dobule 检索一个配置的值给 *val
 * 
 * 返回值        1 - 成功
 *              0 - 失败
 */
int ConfGetDouble(const char *name, double *val)
{
    const char *strval = NULL;
    double tmpdo;
    char *endptr;

    if (ConfGet(name, &strval) == 0)
        return 0;

    errno = 0;
    tmpdo = strtod(strval, &endptr);
    if (strval[0] == '\0' || *endptr != '\0')
        return 0;
    if (errno == ERANGE)
        return 0;

    *val = tmpdo;
    return 1;
}

/**
 * 使用 float 检索一个配置的值给 *val
 * 
 * 返回值        1 - 成功
 *              0 - 失败
 */
int ConfGetFloat(const char *name, float *val)
{
    const char *strval = NULL;
    double tmpfl;
    char *endptr;

    if (ConfGet(name, &strval) == 0)
        return 0;

    errno = 0;
    tmpfl = strtof(strval, &endptr);
    if (strval[0] == '\0' || *endptr != '\0')
        return 0;
    if (errno == ERANGE)
        return 0;

    *val = tmpfl;
    return 1;
}

/**
 * 删除一个给定的节点
 */
void ConfNodeRemove(ConfNode *node)
{
    if (node->parent != NULL)
        // 从兄弟节点上去掉 next 
        TAILQ_REMOVE(&node->parent->head, node, next);
    ConfNodeFree(node);
}

/**
 *
 * 根据名字删除节点
 *
 * 返回值  1 - 成功删除  0 - 没有发现节点
 */
int ConfRemove(const char *name)
{
    ConfNode *node;

    node = ConfGetNode(name);
    if (node == NULL)
        return 0;
    else {
        ConfNodeRemove(node);
        return 1;
    }
}

/**
 * 备份 yaml_root 指向的结构的地址 给 yaml_root_backup
 * 没有数据拷贝
 */
void ConfCreateContextBackup(void)
{
    yaml_root_backup = yaml_root;
    yaml_root = NULL;

    return;
}

/**
 * 还原 yaml_root_backup 指向的结构的地址 给 yaml_root,
 * 没有数据拷贝
 */
void ConfRestoreContextBackup(void)
{
    yaml_root = yaml_root_backup;
    yaml_root_backup = NULL;

    return;
}

/**
 * 清理 yaml_root 节点
 */
void ConfDeInit(void)
{
    if (yaml_root != NULL) {
        ConfNodeFree(yaml_root);
        yaml_root = NULL;
    }

    fprintf(stdout, "configuration module de-initialized\n");
}


/**
 * 打印 key 的名称
 */
static char *ConfPrintNameArray(char **name_arr, int level)
{
    static char name[128*128];
    int i;

    name[0] = '\0';
    for (i = 0; i <= level; i++) {
        strlcat(name, name_arr[i], sizeof(name));
        if (i < level)
            strlcat(name, ".", sizeof(name));
    }

    return name;
}

/**
 * 打印节点下子节点的数据到标准输出
 * 注意:不打印当前节点
 */
void ConfNodeDump(const ConfNode *node, const char *prefix)
{
    ConfNode *child;

    // 递归调用不重复申请这两个变量
    static char *name[128]; // 128个名字的指针数组
    static int level = -1;  // 层级,yaml_root 属于第0层

    level++;
    // 遍历子节点
    TAILQ_FOREACH(child, &node->head, next) {

        // 申请新的name空间
        name[level] = strdup(child->name);
        if (unlikely(name[level] == NULL)) {
            continue;
        }
        if (prefix == NULL) {
            printf("%s = %s\n", ConfPrintNameArray(name, level),
                child->val);
        }
        else {
            printf("%s.%s = %s\n", prefix,
                ConfPrintNameArray(name, level), child->val);
        }
        ConfNodeDump(child, prefix);
        free(name[level]);
    }
    level--;
}

/**
 * 把 yaml_root 打印到标准输出
 */
void ConfDump(void)
{
    ConfNodeDump(yaml_root, NULL);
}

/*
 * 判断给定的节点有没有子节点 
 * 返回   包含节点 返回 true
 *       不包含节点  返回 false
 */
bool ConfNodeHasChildren(const ConfNode *node)
{
    if (TAILQ_EMPTY(&node->head)) {
        return false;
    }
    return true;
}

/*
 * 根据名字 从 子结点中 根据名字查找,不进行递归查找
 * 没找到返回 NULL 
 * 找到返回 ConfNode 节点地址
 * 只在子节点查找
 */
ConfNode *ConfNodeLookupChild(const ConfNode *node, const char *name)
{
    ConfNode *child;

    if (node == NULL || name == NULL) {
        return NULL;
    }

    // 遍历子节点
    TAILQ_FOREACH(child, &node->head, next) {
        if (child->name != NULL && strcmp(child->name, name) == 0)
            return child;
    }

    return NULL;
}

/**
 * 从子节点中,根据名字找到对应的值
 * 只在子节点查找
 *
 * 参数:     node  父节点
 *           name  节点名字,父节点的路径
 *
 * 返回值:   找到节点,则返回值val的地址
 *           没有找到节点,返回 NULL 
 */
const char *ConfNodeLookupChildValue(const ConfNode *node, const char *name)
{
    ConfNode *child;

    child = ConfNodeLookupChild(node, name);
    if (child != NULL)
        return child->val;

    return NULL;
}

/**
 * 在指定的节点下,查找一个key的值
 * 只在子节点查找
 *
 * 返回值     匹配上         返回匹配的 节点
 *           没匹配上       返回NULL
 */
ConfNode *ConfNodeLookupKeyValue(const ConfNode *base, const char *key,
    const char *value)
{
    ConfNode *child;

    TAILQ_FOREACH(child, &base->head, next) {
        if (!strncmp(child->val, key, strlen(child->val))) {
            ConfNode *subchild;
            TAILQ_FOREACH(subchild, &child->head, next) {
                if ((!strcmp(subchild->name, key)) && (!strcmp(subchild->val, value))) {
                    return child;
                }
            }
        }
    }

    return NULL;
}

/**
 * 测试一个配置节点是否有值
 * 只在子节点查找
 *
 * 返回值            1 - 有节点,也有值
 *                  0 - 没找到节点或者节点没值
 */
int ConfNodeChildValueIsTrue(const ConfNode *node, const char *key)
{
    const char *val;

    val = ConfNodeLookupChildValue(node, key);

    return val != NULL ? ConfValIsTrue(val) : 0;
}

/**
 * 加载一个include引入的yaml文件
 * 
 *  \retval str Pointer to the string path + sig_file
 */
char *ConfLoadCompleteIncludePath(const char *file)
{
    const char *defaultpath = NULL;
    char *path = NULL;

    // 相对路径拼写路径
    if (PathIsRelative(file)) {
        // 获取 include-path 配置文件的相对路径
        if (ConfGet("include-path", &defaultpath) == 1) {
            fprintf(stdout, "Default path: %s\n", defaultpath);
            size_t path_len = sizeof(char) * (strlen(defaultpath) +
                          strlen(file) + 2);
            path = malloc(path_len);
            if (unlikely(path == NULL))
                return NULL;
            strlcpy(path, defaultpath, path_len);
            if (path[strlen(path) - 1] != '/')
                strlcat(path, "/", path_len);
            strlcat(path, file, path_len);
       } else {
            path = strdup(file);
            if (unlikely(path == NULL))
                return NULL;
        }
    } else {
        path = strdup(file);
        if (unlikely(path == NULL))
            return NULL;
    }
    return path;
}

/**
 * 清空节点 node 里的value值,
 * 如果 node 里的子节点是可被覆盖的,则一并删除。
 * node 节点本身并不被删除,node->name 保留,node->val 被删除
 */
void ConfNodePrune(ConfNode *node)
{
    ConfNode *item, *it;

    for (item = TAILQ_FIRST(&node->head); item != NULL; item = it) {    // 遍历所有子节点
        it = TAILQ_NEXT(item, next) ;
        if (!item->final) {          // 不是最终参数(可被覆盖)
            ConfNodePrune(item) ;    // 递归删除子节点下的数据和自己本身
            if (TAILQ_EMPTY(&item->head)) {              // 子节点为空
                TAILQ_REMOVE(&node->head, item, next) ;  // 节点从数据结构上脱离
                if (item->name != NULL)
                    free(item->name) ;  // 释放 name 
                if (item->val != NULL)
                    free(item->val) ;   // 释放 val,可能调用不到,val 在 递归调用 ConfNodePrune是释放了。且 val 指向了 NULL
                free(item);             // 释放节点
            }
        }
    }

    // 1、没有子节点的节点,val在此处被删除
    // 2、递归前的 node 节点(需要被清理的节点),val值在这里清理
    // 3、node->val = NULL 为了防止重复释放 val
    if (node->val != NULL) {
        free(node->val);
        node->val = NULL;
    }
}

/**
 * 判断节点是不是列表
 *
 * 返回值     1 - 是列表
 *           0 - 不是列表
 */
int ConfNodeIsSequence(const ConfNode *node)
{
    return node->is_seq == 0 ? 0 : 1;
}

/**
 * 判断路径是不是绝对路径
 *
 *  返回值    1 - 是绝对路径
 *           0 - 不是绝对路径 --相对路径 
 */
int PathIsAbsolute(const char *path)
{
    if (strlen(path) > 1 && path[0] == '/') {
        return 1;
    }
    return 0;
}

/**
 *  判断路径是不是相对路径
 *
 *  返回值    1 - 是相对路径
 *           0 - 不是相对路径 --绝对路径
 */
int PathIsRelative(const char *path)
{
    return PathIsAbsolute(path) ? 0 : 1;
}

/**
 * 尝试找到 LIST 的所有数据
 *
 * 返回值    >=0 - 找到的数量
 *          -1  - 失败
 */
int ConfGetList(const char *name, const char *(**val)[])
{
    int ret = -1 ;

    // 获取节点
    ConfNode * base = ConfGetNode(name) ;
    if( base == NULL ) {
        return -1 ;
    } 

    char idx_str[8] ;

    // 查看有多少个子节点
    int idx = 0 ;
    snprintf(idx_str, sizeof(idx_str), "%d", idx) ;
    while(ConfNodeLookupChild(base, idx_str)) {
        idx++ ;
        snprintf(idx_str, sizeof(idx_str), "%d", idx) ;
    }
    
    const char *(*tmpval)[] = calloc(1, sizeof(void *) * idx) ;
    const char * vptr ;

    // 获取每个子节点的value
    idx = 0 ;
    snprintf(idx_str, sizeof(idx_str), "%d", idx) ;
    while( ret = ConfGetChildValue( base, idx_str, &vptr ) ) {
        (*tmpval)[idx] = vptr ;
        idx++ ;
        snprintf(idx_str, sizeof(idx_str), "%d", idx) ;
    }
    *val = tmpval ;
    return idx ;    
}

代码 conf-yaml.h

#ifndef __CONF_YAML_H__
#define __CONF_YAML_H__

#include <inttypes.h>
#include <stdbool.h>

#include "queue.h"

/**
 * yaml配置文件解析结构
 */
typedef struct ConfNode_ {
    char *name; // 指针,赋值需要申请空间
    char *val;  // 赋值需要申请空间

    int is_seq;   // 标记此节点是列表的一个节点

    int final;    // 最终标记,设置为 final 之后,值不能被覆盖

    struct ConfNode_ *parent;         // 父节点
    TAILQ_HEAD(, ConfNode_) head;     // 孩子节点们
    TAILQ_ENTRY(ConfNode_) next;      // 兄弟节点们
} ConfNode;


/**
 * 默认日志地址
 */
#define DEFAULT_LOG_DIR "/var/log/yaml.log"
#define DEFAULT_DATA_DIR DATA_DIR

#define unlikely(expr) __builtin_expect(!!(expr), 0)

void ConfInit(void);
void ConfDeInit(void);
ConfNode *ConfGetRootNode(void);
int ConfGet(const char *name, const char **vptr);
int ConfGetInt(const char *name, intmax_t *val);
int ConfGetBool(const char *name, int *val);
int ConfGetDouble(const char *name, double *val);
int ConfGetFloat(const char *name, float *val);
int ConfSet(const char *name, const char *val);
int ConfSetFromString(const char *input, int final);
int ConfSetFinal(const char *name, const char *val);
void ConfDump(void);
void ConfNodeDump(const ConfNode *node, const char *prefix);
ConfNode *ConfNodeNew(void);
void ConfNodeFree(ConfNode *);
ConfNode *ConfGetNode(const char *key);
void ConfCreateContextBackup(void);
void ConfRestoreContextBackup(void);
ConfNode *ConfNodeLookupChild(const ConfNode *node, const char *key);
const char *ConfNodeLookupChildValue(const ConfNode *node, const char *key);
void ConfNodeRemove(ConfNode *);
void ConfRegisterTests(void);
int ConfNodeChildValueIsTrue(const ConfNode *node, const char *key);
int ConfValIsTrue(const char *val);
int ConfValIsFalse(const char *val);
void ConfNodePrune(ConfNode *node);
int ConfRemove(const char *name);
bool ConfNodeHasChildren(const ConfNode *node);

ConfNode *ConfGetChildWithDefault(const ConfNode *base, const ConfNode *dflt, const char *name);
ConfNode *ConfNodeLookupKeyValue(const ConfNode *base, const char *key, const char *value);
int ConfGetChildValue(const ConfNode *base, const char *name, const char **vptr);
int ConfGetChildValueInt(const ConfNode *base, const char *name, intmax_t *val);
int ConfGetChildValueBool(const ConfNode *base, const char *name, int *val);
int ConfGetChildValueWithDefault(const ConfNode *base, const ConfNode *dflt, const char *name, const char **vptr);
int ConfGetChildValueIntWithDefault(const ConfNode *base, const ConfNode *dflt, const char *name, intmax_t *val);
int ConfGetChildValueBoolWithDefault(const ConfNode *base, const ConfNode *dflt, const char *name, int *val);
char *ConfLoadCompleteIncludePath(const char *);
int ConfNodeIsSequence(const ConfNode *node);
ConfNode *ConfSetIfaceNode(const char *ifaces_node_name, const char *iface);
int ConfSetRootAndDefaultNodes(
        const char *ifaces_node_name, const char *iface, ConfNode **if_root, ConfNode **if_default);
ConfNode *ConfNodeGetNodeOrCreate(ConfNode *parent, const char *name, int final);

int PathIsAbsolute(const char *path) ;
int PathIsRelative(const char *path) ;
int ConfGetList(const char *name, const char *(**val)[]) ;

#endif /* !__CONF_YAML_H__ */

代码 queue.h

#ifndef YAML_QUEUE_H
#define YAML_QUEUE_H

if defined(HAVE_SYS_QUEUE_H) && !defined(__clang_analyzer__)
#include <sys/queue.h>
#endif

#if defined(__clang_analyzer__)
#define _Q_ASSERT(a) assert((a))
#else
#define _Q_ASSERT(a)
#endif

/* The BSDs have removed CIRCLEQ but it still exists in Linux.
 *
 * This implementation from OpenBSD sys/queue.h v1.40 as it still has
 * CIRCLEQ and includes the safe variations.
 * - _Q_INVALIDATE for some extra debugging has been removed as its
 *   not available on the Linux version of CIRCLEQ.
 */

#ifndef CIRCLEQ_HEAD

/*
 * Circular queue definitions.
 */
#define CIRCLEQ_HEAD(name, type)                    \
struct name {                                \
    struct type *cqh_first;        /* first element */        \
    struct type *cqh_last;        /* last element */        \
}

#define CIRCLEQ_HEAD_INITIALIZER(head)                    \
    { CIRCLEQ_END(&head), CIRCLEQ_END(&head) }

#define CIRCLEQ_ENTRY(type)                        \
struct {                                \
    struct type *cqe_next;        /* next element */        \
    struct type *cqe_prev;        /* previous element */        \
}

/*
 * Circular queue access methods
 */
#define    CIRCLEQ_FIRST(head)        ((head)->cqh_first)
#define    CIRCLEQ_LAST(head)        ((head)->cqh_last)
#define    CIRCLEQ_NEXT(elm, field)    ((elm)->field.cqe_next)
#define    CIRCLEQ_PREV(elm, field)    ((elm)->field.cqe_prev)
#define    CIRCLEQ_EMPTY(head)                        \
    (CIRCLEQ_FIRST(head) == CIRCLEQ_END(head))

#define CIRCLEQ_FOREACH(var, head, field)                \
    for((var) = CIRCLEQ_FIRST(head);                \
        (var) != CIRCLEQ_END(head);                    \
        (var) = CIRCLEQ_NEXT(var, field))

#define    CIRCLEQ_FOREACH_SAFE(var, head, field, tvar)            \
    for ((var) = CIRCLEQ_FIRST(head);                \
        (var) != CIRCLEQ_END(head) &&                \
        ((tvar) = CIRCLEQ_NEXT(var, field), 1);            \
        (var) = (tvar))

#define CIRCLEQ_FOREACH_REVERSE(var, head, field)            \
    for((var) = CIRCLEQ_LAST(head);                    \
        (var) != CIRCLEQ_END(head);                    \
        (var) = CIRCLEQ_PREV(var, field))

#define    CIRCLEQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar)    \
    for ((var) = CIRCLEQ_LAST(head, headname);            \
        (var) != CIRCLEQ_END(head) &&                 \
        ((tvar) = CIRCLEQ_PREV(var, headname, field), 1);        \
        (var) = (tvar))

/*
 * Circular queue functions.
 */
#define    CIRCLEQ_INIT(head) do {                        \
    (head)->cqh_first = CIRCLEQ_END(head);                \
    (head)->cqh_last = CIRCLEQ_END(head);                \
} while (0)

#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do {        \
    (elm)->field.cqe_next = (listelm)->field.cqe_next;        \
    (elm)->field.cqe_prev = (listelm);                \
    if ((listelm)->field.cqe_next == CIRCLEQ_END(head))        \
        (head)->cqh_last = (elm);                \
    else                                \
        (listelm)->field.cqe_next->field.cqe_prev = (elm);    \
    (listelm)->field.cqe_next = (elm);                \
} while (0)

#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do {        \
    (elm)->field.cqe_next = (listelm);                \
    (elm)->field.cqe_prev = (listelm)->field.cqe_prev;        \
    if ((listelm)->field.cqe_prev == CIRCLEQ_END(head))        \
        (head)->cqh_first = (elm);                \
    else                                \
        (listelm)->field.cqe_prev->field.cqe_next = (elm);    \
    (listelm)->field.cqe_prev = (elm);                \
} while (0)

#define CIRCLEQ_INSERT_HEAD(head, elm, field) do {            \
    (elm)->field.cqe_next = (head)->cqh_first;            \
    (elm)->field.cqe_prev = CIRCLEQ_END(head);            \
    if ((head)->cqh_last == CIRCLEQ_END(head))            \
        (head)->cqh_last = (elm);                \
    else                                \
        (head)->cqh_first->field.cqe_prev = (elm);        \
    (head)->cqh_first = (elm);                    \
} while (0)

#define CIRCLEQ_INSERT_TAIL(head, elm, field) do {            \
    (elm)->field.cqe_next = CIRCLEQ_END(head);            \
    (elm)->field.cqe_prev = (head)->cqh_last;            \
    if ((head)->cqh_first == CIRCLEQ_END(head))            \
        (head)->cqh_first = (elm);                \
    else                                \
        (head)->cqh_last->field.cqe_next = (elm);        \
    (head)->cqh_last = (elm);                    \
} while (0)

#define    CIRCLEQ_REMOVE(head, elm, field) do {                \
    if ((elm)->field.cqe_next == CIRCLEQ_END(head))            \
        (head)->cqh_last = (elm)->field.cqe_prev;        \
    else                                \
        (elm)->field.cqe_next->field.cqe_prev =            \
            (elm)->field.cqe_prev;                \
    if ((elm)->field.cqe_prev == CIRCLEQ_END(head))            \
        (head)->cqh_first = (elm)->field.cqe_next;        \
    else                                \
        (elm)->field.cqe_prev->field.cqe_next =            \
            (elm)->field.cqe_next;                \
} while (0)

#define CIRCLEQ_REPLACE(head, elm, elm2, field) do {            \
    if (((elm2)->field.cqe_next = (elm)->field.cqe_next) ==        \
        CIRCLEQ_END(head))                        \
        (head)->cqh_last = (elm2);                \
    else                                \
        (elm2)->field.cqe_next->field.cqe_prev = (elm2);    \
    if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) ==        \
        CIRCLEQ_END(head))                        \
        (head)->cqh_first = (elm2);                \
    else                                \
        (elm2)->field.cqe_prev->field.cqe_next = (elm2);    \
} while (0)

#endif /* !CIRCLEQ_HEAD */

/* Required by local implementation as well as _SAFE variations. */
#ifndef CIRCLEQ_END
#define CIRCLEQ_END(head)       ((void *)(head))
#endif /* !CIRCLEQ_END */

#ifndef CIRCLEQ_FOREACH_SAFE
#define    CIRCLEQ_FOREACH_SAFE(var, head, field, tvar)            \
    for ((var) = CIRCLEQ_FIRST(head);                \
        (var) != CIRCLEQ_END(head) &&                \
        ((tvar) = CIRCLEQ_NEXT(var, field), 1);            \
        (var) = (tvar))

#define    CIRCLEQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar)    \
    for ((var) = CIRCLEQ_LAST(head, headname);            \
        (var) != CIRCLEQ_END(head) &&                 \
        ((tvar) = CIRCLEQ_PREV(var, headname, field), 1);        \
        (var) = (tvar))
#endif /* !CIRCLEQ_FOREACH_SAFE */

/*
 * Complete TAILQ implementation as sys/queue.h is not available on Windows
 * and used by Suricata.
 *
 * This implementation copied from FreeBSD sys/queue.h with the addition
 * of our _Q_ASSERT macros to satisfy scan-build.
 */
#ifndef TAILQ_HEAD

/*
 * Tail queue declarations.
 */
#define    TAILQ_HEAD(name, type)                        \
struct name {                                \
    struct type *tqh_first;    /* first element */            \
    struct type **tqh_last;    /* addr of last next element */        \
}

#define    TAILQ_HEAD_INITIALIZER(head)                    \
    { NULL, &(head).tqh_first }

#define    TAILQ_ENTRY(type)                        \
struct {                                \
    struct type *tqe_next;    /* next element */            \
    struct type **tqe_prev;    /* address of previous next element */    \
}

/*
 * Tail queue functions.
 */
#define    TAILQ_EMPTY(head)    ((head)->tqh_first == NULL)

#define    TAILQ_FIRST(head)    ((head)->tqh_first)

#define    TAILQ_FOREACH(var, head, field)                    \
    for ((var) = TAILQ_FIRST((head));                \
        (var);                            \
        (var) = TAILQ_NEXT((var), field))

#define    TAILQ_FOREACH_REVERSE(var, head, headname, field)        \
    for ((var) = TAILQ_LAST((head), headname);            \
        (var);                            \
        (var) = TAILQ_PREV((var), headname, field))

#define    TAILQ_INIT(head) do {                        \
    TAILQ_FIRST((head)) = NULL;                    \
    (head)->tqh_last = &TAILQ_FIRST((head));            \
} while (0)

#define    TAILQ_INSERT_AFTER(head, listelm, elm, field) do {        \
    if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\
        TAILQ_NEXT((elm), field)->field.tqe_prev =         \
            &TAILQ_NEXT((elm), field);                \
    else                                \
        (head)->tqh_last = &TAILQ_NEXT((elm), field);        \
    TAILQ_NEXT((listelm), field) = (elm);                \
    (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field);        \
} while (0)

#define    TAILQ_INSERT_BEFORE(listelm, elm, field) do {            \
    (elm)->field.tqe_prev = (listelm)->field.tqe_prev;        \
    TAILQ_NEXT((elm), field) = (listelm);                \
    *(listelm)->field.tqe_prev = (elm);                \
    (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field);        \
} while (0)

#define    TAILQ_INSERT_HEAD(head, elm, field) do {            \
    if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL)    \
        TAILQ_FIRST((head))->field.tqe_prev =            \
            &TAILQ_NEXT((elm), field);                \
    else                                \
        (head)->tqh_last = &TAILQ_NEXT((elm), field);        \
    TAILQ_FIRST((head)) = (elm);                    \
    (elm)->field.tqe_prev = &TAILQ_FIRST((head));            \
} while (0)

#define    TAILQ_INSERT_TAIL(head, elm, field) do {            \
    _Q_ASSERT((elm));                        \
    _Q_ASSERT((head));                        \
    TAILQ_NEXT((elm), field) = NULL;                \
    (elm)->field.tqe_prev = (head)->tqh_last;            \
    *(head)->tqh_last = (elm);                    \
    _Q_ASSERT(*(head)->tqh_last);                    \
    (head)->tqh_last = &TAILQ_NEXT((elm), field);            \
} while (0)

#define    TAILQ_LAST(head, headname)                    \
    (*(((struct headname *)((head)->tqh_last))->tqh_last))

#define    TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)

#define    TAILQ_PREV(elm, headname, field)                \
    (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))

#define    TAILQ_REMOVE(head, elm, field) do {                \
    if ((TAILQ_NEXT((elm), field)) != NULL)                \
        TAILQ_NEXT((elm), field)->field.tqe_prev =         \
            (elm)->field.tqe_prev;                \
    else                                \
        (head)->tqh_last = (elm)->field.tqe_prev;        \
    _Q_ASSERT((head)->tqh_first != (elm));                \
    *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field);        \
} while (0)

#endif /* !TAILQ_HEAD */

/* Not included in Linux, but are in FreeBSD and friends.
 *
 * This implementation from FreeBSD's sys/queue.h.
 */
#ifndef TAILQ_FOREACH_SAFE
#define    TAILQ_FOREACH_SAFE(var, head, field, tvar)            \
    for ((var) = TAILQ_FIRST((head));                \
        (var) && ((tvar) = TAILQ_NEXT((var), field), 1);        \
        (var) = (tvar))
#endif

#endif    /* !YAML_QUEUE_H */

测试代码 test.c

#include <stdio.h>
#include <stdlib.h>

#include "conf-yaml.h"
#include "conf-yaml-loader.h"


const char * yaml_path = "./tt.yaml" ;


int main(int argc, int argv) {

    ConfInit() ;
    ConfYamlLoadFile(yaml_path) ;

    ConfNode *yaml_root = ConfGetRootNode() ;

    printf("\n\n\n\n==============  result ===============\n\n") ;
    ConfDump() ;

    const char * vptr ;

    char * tnm = "score.0.name" ;
    ConfGet(tnm, &vptr) ;
    printf("\n\n---------%s: %s\n", tnm, vptr) ;

    tnm = "aa" ;
    ConfGet(tnm, &vptr) ;
    printf("---------%s: %s\n", tnm, vptr) ;

    tnm = "t-inc.cc" ;
    ConfGet(tnm, &vptr) ;
    printf("---------%s: %s\n", tnm, vptr) ;
    
    const char *(*val)[] ;
    int ret = ConfGetList("score.1.fruit", &val) ;
    printf("ret [%d] \n", ret) ;
    for(int i = 0  ; i < ret ; ++i ) {
        printf("idx [%d] [%s]\n", i, (*val)[i]) ;
    }
    free(val) ;
    return 0 ;
}

测试数据 tt.yaml

%YAML 1.1
---

# --- 代表文档的开始
score:
  # - 代表这些是数组
  - name: Hanmeimei    # #号往后全是注释
    age: 11
    sessions:
     chineses: 100
     math: 100
     english: 100
     science: 100
  - name: Lilei
    age: 12

    # 中括号代表是一个列表
    # 列表中的数据字段可以不一致
    fruit: [apple, pear, banana, peach, orange]

    # 只要下一级的数据缩进长度相同,且比本级长就可以
    sessions:
           chineses: 95
           math: 95
           english: 95
           science: 95
...
# ... 代表文档的结束

%YAML 1.1
---
teachers:
    &anchorrrr
  - name: Qiaofeng
    age: 32
    sex: 1
    address: >
        CN
        Shandong
        Qingdao
  - name: Wangyuyan
    age: 19
    sex: 2
    # !!表示用字符串解释下边的数据
    banner: !!str
        Hello,       
        my name is Wangyuyan.
        I am your english teacher.
    address: |
      CN
      Shandong
      Jinan
...

%YAML 1.1
---
root:
   pppp:
       name: Murongfu
       age: 28
anchor_t:
   kongfu: Douzhuanxingyi

include: t1.yaml
t_inc:
    include: t1.yaml

...

测试数据 t1.yaml

%YAML 1.1
---

aa: aaaaaaaaaa
bb: bbbbbbbbbbbbbb
cc: ccccccccccccccc

编译测试

最后打印结果:

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注