1191 lines
37 KiB
C
1191 lines
37 KiB
C
/*
|
||
* 模块名称:cfg_file
|
||
* 模块功能:通用配置文件引擎(双端共享核心)——INI 段/键表解析校验、外科手术式
|
||
* 保存(保留注释/未知内容,缺失段/键写回)、出厂模板生成、沉降去抖轮询
|
||
* 与分段变更回调;平台文件操作经 cfg_file_store 后端
|
||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||
* 作者:王建锋
|
||
* 创建日期:2026-09-14
|
||
* 修改记录:
|
||
* v1.0 2026-09-14 王建锋 创建初始版本(P3:段键表引擎 + net_cfg 集成)
|
||
* 函数说明:引擎状态非重入——调用方须收敛同一任务(netTask:poll/save;
|
||
* 启动期 load 先于任务竞争);FS 卷并发由 FatFS REENTRANT 卷锁保证
|
||
* (见《配置文件设计说明》D15 A+ 方案)
|
||
*/
|
||
|
||
/* 头文件包含区 */
|
||
#include <string.h>
|
||
|
||
#include <stdlib.h>
|
||
|
||
#include <stdio.h>
|
||
|
||
#include <ctype.h>
|
||
|
||
#include "cfg_file.h"
|
||
|
||
#include "cfg_file_store.h"
|
||
|
||
#define DBG_TAG "[CFG]"
|
||
#include "dbg_log.h"
|
||
|
||
/* 常量定义区 */
|
||
|
||
/* 文件头注释块(首次生成文件时写入;后续保存逐行保留) */
|
||
static const char s_doc_header[] =
|
||
"; ============================================================\r\n"
|
||
"; STM32F4-Base 通用配置文件\r\n"
|
||
"; 格式: INI (UTF-8 / CRLF),本文件为配置唯一来源\r\n"
|
||
"; - 以 ';' 或 '#' 开头的行为注释,保存时保留\r\n"
|
||
"; - 未知段/键保存时保留;缺失段/键保存时写回\r\n"
|
||
"; - 编辑后经 FTP 上传或设备端保存,设备 4s 沉降后自动重载\r\n"
|
||
"; ============================================================\r\n";
|
||
|
||
#define CFG_OUT_MAX 5120u /* 保存输出缓冲(输入复用读缓冲;堆峰值 ≈ 9KB 暂态) */
|
||
#define CFG_LINE_TEST_MAX 127u /* 受管键行匹配的行长上限(超长必非受管键行) */
|
||
#define CFG_SNAP_MAX 64u /* 变更检测段快照缓冲(挂回调段 data_size 须 ≤ 此值) */
|
||
#define CFG_NAME_MAX 16u /* 段名缓冲(15 字符 + NUL) */
|
||
|
||
/* 静态变量定义区 */
|
||
|
||
typedef struct {
|
||
char name[CFG_NAME_MAX];
|
||
void *p_data;
|
||
const void *p_defaults;
|
||
size_t data_size;
|
||
const cfg_key_def_t *p_keys;
|
||
uint8_t key_count;
|
||
void (*p_cb)(void);
|
||
uint8_t used;
|
||
} cfg_sec_t;
|
||
|
||
static cfg_sec_t s_sections[CFG_FILE_MAX_SECTIONS];
|
||
|
||
static cfg_file_stat_t s_base; /* 变更基线 (size, mtime 令牌) */
|
||
static cfg_file_stat_t s_last; /* 上次 poll 的 stat */
|
||
static uint8_t s_stable; /* 连续相同轮询计数(沉降去抖) */
|
||
static uint8_t s_base_valid; /* 基线是否已建立 */
|
||
static uint8_t s_snap[CFG_SNAP_MAX];
|
||
|
||
static int s_sec_hdr[CFG_FILE_MAX_SECTIONS]; /* 段头行号(0xFFFF = 未见) */
|
||
static uint16_t s_key_line[CFG_FILE_MAX_SECTIONS][CFG_FILE_MAX_KEYS]; /* 受管键行号(0xFFFF = 未见) */
|
||
|
||
/* 函数定义区 */
|
||
|
||
/*
|
||
* 函数功能:短字符串大小写无关比较
|
||
* 入口参数:p_a / p_b - 两个 NUL 结尾字符串 const char *
|
||
* 返回值:1 - 相等(忽略大小写);0 - 不等 int
|
||
* 限定条件:p_a/p_b 非空
|
||
*/
|
||
static int s_str_ci_eq(const char *p_a, const char *p_b) {
|
||
for (;;) {
|
||
char ca = *p_a;
|
||
char cb = *p_b;
|
||
|
||
if (ca >= 'A' && ca <= 'Z') {
|
||
ca = (char)(ca + ('a' - 'A'));
|
||
}
|
||
if (cb >= 'A' && cb <= 'Z') {
|
||
cb = (char)(cb + ('a' - 'A'));
|
||
}
|
||
if (ca != cb) {
|
||
return 0;
|
||
}
|
||
if (ca == '\0') {
|
||
return 1;
|
||
}
|
||
p_a++;
|
||
p_b++;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:取下一行(行末 NUL 化、剥 CR、前导空白左移)并推进缓冲位置
|
||
* 入口参数:p_p - 当前位置指针(入/出) char **
|
||
* 返回值:行首指针 char *
|
||
* 限定条件:*p_p 指向可写缓冲内
|
||
* 函数说明:行界定 = '\n'(缺失时到 NUL);会修改缓冲(行末字节,
|
||
* 含 CRLF 的 '\r'),保存路径 Pass 1 须用一次性副本
|
||
*/
|
||
static char *s_split_line(char **p_p) {
|
||
char *p_line = *p_p;
|
||
char *p_nl;
|
||
char *p_ws;
|
||
size_t l;
|
||
|
||
p_nl = strchr(p_line, '\n');
|
||
if (p_nl != NULL) {
|
||
*p_nl = '\0';
|
||
*p_p = p_nl + 1;
|
||
}
|
||
else {
|
||
*p_p = p_line + strlen(p_line) + 1;
|
||
}
|
||
l = strlen(p_line);
|
||
if (l > 0u && p_line[l - 1u] == '\r') {
|
||
p_line[l - 1u] = '\0';
|
||
}
|
||
p_ws = p_line;
|
||
while (*p_ws == ' ' || *p_ws == '\t') {
|
||
p_ws++;
|
||
}
|
||
if (p_ws != p_line) {
|
||
(void)memmove(p_line, p_ws, strlen(p_ws) + 1u);
|
||
}
|
||
return p_line;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:识别段名行(非修改式)
|
||
* 入口参数:p_line - 行文本 const char *
|
||
* p_name - 段名输出缓冲 char *
|
||
* name_cap- 缓冲容量 size_t
|
||
* 返回值:1 - 是段名行(p_name 已填);0 - 不是 int
|
||
* 限定条件:p_line/p_name 非空
|
||
*/
|
||
static int s_match_section(const char *p_line, char *p_name, size_t name_cap) {
|
||
const char *p;
|
||
const char *p_br;
|
||
size_t n = 0u;
|
||
|
||
if (p_line[0] != '[') {
|
||
return 0;
|
||
}
|
||
p_br = strchr(p_line, ']');
|
||
if (p_br == NULL || (size_t)(p_br - p_line) >= name_cap) {
|
||
return 0;
|
||
}
|
||
p = p_line + 1;
|
||
while (p != p_br && (n + 1u) < name_cap) {
|
||
p_name[n++] = (char)*p;
|
||
p++;
|
||
}
|
||
p_name[n] = '\0';
|
||
while (n > 0u && (p_name[n - 1u] == ' ' || p_name[n - 1u] == '\t')) {
|
||
p_name[--n] = '\0';
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:拆分键值行(修改式:键在 '=' 处 NUL 化,值区空白修剪)
|
||
* 入口参数:p_line - 行文本(可写副本) char *
|
||
* p_key - 键名输出(指向 p_line 内) char **
|
||
* 返回值:值文本指针;NULL - 不是键值行 char *
|
||
* 限定条件:仅用于一次性解析(缓冲可被修改)
|
||
*/
|
||
static char *s_key_value(char *p_line, char **p_key) {
|
||
char *p_eq = strchr(p_line, '=');
|
||
char *p_val;
|
||
size_t kl;
|
||
size_t vl;
|
||
|
||
if (p_eq == NULL) {
|
||
return NULL;
|
||
}
|
||
*p_eq = '\0';
|
||
kl = (size_t)(p_eq - p_line);
|
||
while (kl > 0u && (p_line[kl - 1u] == ' ' || p_line[kl - 1u] == '\t')) {
|
||
p_line[--kl] = '\0';
|
||
}
|
||
*p_key = p_line;
|
||
p_val = p_eq + 1;
|
||
{
|
||
char *p_ws = p_val;
|
||
while (*p_ws == ' ' || *p_ws == '\t') {
|
||
p_ws++;
|
||
}
|
||
if (p_ws != p_val) {
|
||
(void)memmove(p_val, p_ws, strlen(p_ws) + 1u);
|
||
}
|
||
}
|
||
vl = strlen(p_val);
|
||
while (vl > 0u && (p_val[vl - 1u] == ' ' || p_val[vl - 1u] == '\t')) {
|
||
p_val[--vl] = '\0';
|
||
}
|
||
return p_val;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:RAM 字段编码为 INI 值文本
|
||
* 入口参数:p_key - 键定义 const cfg_key_def_t *
|
||
* p_src - RAM 字段 const uint8_t *
|
||
* p_dst - 输出缓冲 char *
|
||
* cap - 缓冲容量 size_t
|
||
* 返回值:成功 = 文本长度;失败 = -1(超长/非法字段长度) int
|
||
* 限定条件:p_src/p_dst 非空
|
||
*/
|
||
static int s_encode(const cfg_key_def_t *p_key, const uint8_t *p_src, char *p_dst, size_t cap) {
|
||
int n;
|
||
|
||
switch (p_key->type) {
|
||
case CFG_VAL_UINT: {
|
||
unsigned long v = 0uL;
|
||
size_t i;
|
||
|
||
if (p_key->len != 1u && p_key->len != 2u && p_key->len != 4u) {
|
||
return -1;
|
||
}
|
||
for (i = 0u; i < p_key->len; i++) {
|
||
v |= (unsigned long)p_src[i] << (8u * i);
|
||
}
|
||
n = snprintf(p_dst, cap, "%lu", v);
|
||
break;
|
||
}
|
||
case CFG_VAL_IP4:
|
||
n = snprintf(p_dst, cap, "%u.%u.%u.%u",
|
||
(unsigned)p_src[0], (unsigned)p_src[1],
|
||
(unsigned)p_src[2], (unsigned)p_src[3]);
|
||
break;
|
||
case CFG_VAL_MAC:
|
||
n = snprintf(p_dst, cap, "%02x:%02x:%02x:%02x:%02x:%02x",
|
||
(unsigned)p_src[0], (unsigned)p_src[1], (unsigned)p_src[2],
|
||
(unsigned)p_src[3], (unsigned)p_src[4], (unsigned)p_src[5]);
|
||
break;
|
||
case CFG_VAL_STR: {
|
||
size_t slen = strlen((const char *)p_src);
|
||
if (slen >= p_key->len) {
|
||
slen = p_key->len - 1u;
|
||
}
|
||
(void)memcpy(p_dst, p_src, slen);
|
||
p_dst[slen] = '\0';
|
||
n = (int)slen;
|
||
break;
|
||
}
|
||
default:
|
||
return -1;
|
||
}
|
||
if (n < 0 || (size_t)n >= cap) {
|
||
return -1;
|
||
}
|
||
return n;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:解析无符号十进制值(小端写入)
|
||
* 入口参数:p_dst - 目标字段 uint8_t *
|
||
* len - 字段字节数(1/2/4) size_t
|
||
* p_val - 值文本 const char *
|
||
* 返回值:0 - 成功;-1 - 非法(空/非数字/超范围/带后缀) int
|
||
* 限定条件:无
|
||
*/
|
||
static int s_decode_uint(uint8_t *p_dst, size_t len, const char *p_val) {
|
||
const unsigned long max_v = (len == 1u) ? 0xFFu : ((len == 2u) ? 0xFFFFu : 0xFFFFFFFFuL);
|
||
char *p_end = NULL;
|
||
unsigned long v;
|
||
size_t i;
|
||
|
||
if (len != 1u && len != 2u && len != 4u) {
|
||
return -1;
|
||
}
|
||
if (p_val[0] == '\0') {
|
||
return -1;
|
||
}
|
||
v = strtoul(p_val, &p_end, 10);
|
||
if (p_end == p_val || *p_end != '\0' || v > max_v) {
|
||
return -1;
|
||
}
|
||
for (i = 0u; i < len; i++) {
|
||
p_dst[i] = (uint8_t)(v >> (8u * i));
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:解析点分 IPv4("a.b.c.d",每段 0~255)
|
||
* 入口参数:p_dst - 4 字节目标 uint8_t *
|
||
* p_val - 值文本 const char *
|
||
* 返回值:0 - 成功;-1 - 非法 int
|
||
* 限定条件:无
|
||
*/
|
||
static int s_decode_ip4(uint8_t *p_dst, const char *p_val) {
|
||
int i;
|
||
const char *p = p_val;
|
||
|
||
for (i = 0; i < 4; i++) {
|
||
unsigned v = 0u;
|
||
int nd = 0;
|
||
|
||
if (i != 0) {
|
||
if (*p != '.') {
|
||
return -1;
|
||
}
|
||
p++;
|
||
}
|
||
if (!isdigit((unsigned char)*p)) {
|
||
return -1;
|
||
}
|
||
while (isdigit((unsigned char)*p) && nd < 3) {
|
||
v = (unsigned)(v * 10u + (unsigned)(*p - '0'));
|
||
p++;
|
||
nd++;
|
||
}
|
||
if (v > 255u) {
|
||
return -1;
|
||
}
|
||
p_dst[i] = (uint8_t)v;
|
||
}
|
||
return (*p == '\0') ? 0 : -1;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:解析十六进制 MAC(分隔符兼容 ':' / '-' / '.',大小写均可)
|
||
* 入口参数:p_dst - 6 字节目标 uint8_t *
|
||
* p_val - 值文本 const char *
|
||
* 返回值:0 - 成功;-1 - 非法 int
|
||
* 限定条件:无
|
||
*/
|
||
static int s_decode_mac(uint8_t *p_dst, const char *p_val) {
|
||
int i;
|
||
const char *p = p_val;
|
||
|
||
for (i = 0; i < 12; i++) {
|
||
int v;
|
||
|
||
if ((i & 1) == 0 && i != 0) {
|
||
if (*p != ':' && *p != '-' && *p != '.') {
|
||
return -1;
|
||
}
|
||
p++;
|
||
}
|
||
if (*p >= '0' && *p <= '9') {
|
||
v = *p - '0';
|
||
}
|
||
else if (*p >= 'a' && *p <= 'f') {
|
||
v = *p - 'a' + 10;
|
||
}
|
||
else if (*p >= 'A' && *p <= 'F') {
|
||
v = *p - 'A' + 10;
|
||
}
|
||
else {
|
||
return -1;
|
||
}
|
||
if ((i & 1) == 0) {
|
||
p_dst[i >> 1] = (uint8_t)v;
|
||
}
|
||
else {
|
||
p_dst[i >> 1] = (uint8_t)((p_dst[i >> 1] << 4) | (uint8_t)v);
|
||
}
|
||
p++;
|
||
}
|
||
return (*p == '\0') ? 0 : -1;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:解析字符串值(NUL 结尾写入)
|
||
* 入口参数:p_dst - 目标缓冲 uint8_t *
|
||
* len - 缓冲长度 size_t
|
||
* p_val - 值文本 const char *
|
||
* 返回值:0 - 成功;-1 - 超长(含 NUL > len) int
|
||
* 限定条件:无
|
||
*/
|
||
static int s_decode_str(uint8_t *p_dst, size_t len, const char *p_val) {
|
||
size_t vlen = strlen(p_val);
|
||
|
||
if (len < vlen + 1u) {
|
||
return -1;
|
||
}
|
||
(void)memcpy(p_dst, p_val, vlen);
|
||
p_dst[vlen] = '\0';
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:值文本解码到 RAM 字段(按键类型分派)
|
||
* 入口参数:p_key - 键定义 const cfg_key_def_t *
|
||
* p_dst - 目标字段 uint8_t *
|
||
* p_val - 值文本 const char *
|
||
* 返回值:0 - 成功;-1 - 非法(调用方留工厂默认并留痕) int
|
||
* 限定条件:无
|
||
*/
|
||
static int s_decode(const cfg_key_def_t *p_key, uint8_t *p_dst, const char *p_val) {
|
||
switch (p_key->type) {
|
||
case CFG_VAL_UINT:
|
||
return s_decode_uint(p_dst, p_key->len, p_val);
|
||
case CFG_VAL_IP4:
|
||
return s_decode_ip4(p_dst, p_val);
|
||
case CFG_VAL_MAC:
|
||
return s_decode_mac(p_dst, p_val);
|
||
case CFG_VAL_STR:
|
||
return s_decode_str(p_dst, p_key->len, p_val);
|
||
default:
|
||
return -1;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:按各段 RAM 副本生成完整配置文档(文件头注释 + 全部段/键)
|
||
* 入口参数:p_buf - 输出缓冲 char *
|
||
* cap - 缓冲容量 size_t
|
||
* p_len - 输出长度 size_t *
|
||
* 返回值:0 - 成功;-1 - 缓冲不足/编码失败 int
|
||
* 限定条件:p_buf/p_len 非空;调用前各段 RAM 副本须为期望落盘值
|
||
*/
|
||
static int s_gen_document(char *p_buf, size_t cap, size_t *p_len) {
|
||
size_t off;
|
||
int i;
|
||
int k;
|
||
size_t hlen;
|
||
|
||
if (p_buf == NULL || p_len == NULL) {
|
||
return -1;
|
||
}
|
||
hlen = strlen(s_doc_header);
|
||
if (cap < hlen + 1u) {
|
||
return -1;
|
||
}
|
||
(void)memcpy(p_buf, s_doc_header, hlen + 1u);
|
||
off = hlen;
|
||
|
||
for (i = 0; i < (int)CFG_FILE_MAX_SECTIONS; i++) {
|
||
const cfg_sec_t *p_sec = &s_sections[i];
|
||
|
||
if (p_sec->used == 0) {
|
||
continue;
|
||
}
|
||
if (cap < off + 112u) {
|
||
return -1;
|
||
}
|
||
off += (size_t)snprintf(p_buf + off, cap - off, "\r\n[%s]\r\n", p_sec->name);
|
||
|
||
for (k = 0; k < (int)p_sec->key_count; k++) {
|
||
const cfg_key_def_t *p_key = &p_sec->p_keys[k];
|
||
char v[80];
|
||
|
||
if (s_encode(p_key, (const uint8_t *)p_sec->p_data + p_key->offset, v, sizeof(v)) < 0) {
|
||
return -1;
|
||
}
|
||
if (cap < off + 112u) {
|
||
return -1;
|
||
}
|
||
off += (size_t)snprintf(p_buf + off, cap - off, "%s=%s\r\n", p_key->name, v);
|
||
}
|
||
}
|
||
*p_len = off;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读配置文件到堆缓冲(NUL 结尾)
|
||
* 入口参数:p_pbuf - 缓冲指针输出 uint8_t **
|
||
* p_len - 实际长度输出 size_t *
|
||
* 返回值:0 - 成功;-1 - 分配/读失败(缓冲已释放) int
|
||
* 限定条件:成功时调用方须 free(*p_pbuf)
|
||
*/
|
||
static int s_load_file(uint8_t **p_pbuf, size_t *p_len) {
|
||
uint8_t *p_buf;
|
||
size_t nread = 0u;
|
||
int r;
|
||
|
||
if (p_pbuf == NULL || p_len == NULL) {
|
||
return -1;
|
||
}
|
||
*p_pbuf = NULL;
|
||
p_buf = (uint8_t *)malloc(CFG_FILE_MAX_LEN + 1u);
|
||
if (p_buf == NULL) {
|
||
DBG_ERROR("read: malloc %uB FAIL", (unsigned)(CFG_FILE_MAX_LEN + 1u));
|
||
return -1;
|
||
}
|
||
r = cfg_store_read(cfg_store_config_path(), p_buf, CFG_FILE_MAX_LEN, &nread);
|
||
if (r != 0) {
|
||
free(p_buf);
|
||
return -1;
|
||
}
|
||
p_buf[nread] = '\0';
|
||
*p_pbuf = p_buf;
|
||
*p_len = nread;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:经后端原子写配置文档
|
||
* 入口参数:p_buf - 文档数据 const uint8_t *
|
||
* len - 数据长度 size_t
|
||
* 返回值:0 - 成功;-1 - 写失败 int
|
||
* 限定条件:p_buf 非空
|
||
*/
|
||
static int s_write_doc(const uint8_t *p_buf, size_t len) {
|
||
if (p_buf == NULL) {
|
||
return -1;
|
||
}
|
||
return cfg_store_write_atomic(cfg_store_config_path(), p_buf, len);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:按当前 RAM 副本重建出厂模板写盘
|
||
* 入口参数:无
|
||
* 返回值:0 - 成功;-1 - 失败 int
|
||
* 限定条件:各段 RAM 副本须为期望落盘值
|
||
*/
|
||
static int s_write_template(void) {
|
||
char *p_buf = (char *)malloc(CFG_OUT_MAX);
|
||
size_t len = 0u;
|
||
int r;
|
||
|
||
if (p_buf == NULL) {
|
||
DBG_ERROR("template: malloc %uB FAIL", (unsigned)CFG_OUT_MAX);
|
||
return -1;
|
||
}
|
||
r = s_gen_document(p_buf, CFG_OUT_MAX, &len);
|
||
if (r == 0) {
|
||
r = s_write_doc((const uint8_t *)p_buf, len);
|
||
}
|
||
free(p_buf);
|
||
return r;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:解析 INI 文本,将有效键写入段 RAM 副本
|
||
* 入口参数:p_buf - 文件文本(可修改,NUL 结尾) char *
|
||
* only_sec - 仅解析该段下标;-1 = 全部段 int
|
||
* 返回值:无
|
||
* 限定条件:调用前对应段须已复位工厂默认;无效键 = 留工厂默认并留痕
|
||
*/
|
||
static void s_parse(char *p_buf, int only_sec) {
|
||
char *p = p_buf;
|
||
char p_name[CFG_NAME_MAX];
|
||
int sec_idx = -1;
|
||
|
||
while (*p != '\0') {
|
||
char *p_line = s_split_line(&p);
|
||
|
||
if (p_line[0] == '\0' || p_line[0] == ';' || p_line[0] == '#') {
|
||
continue;
|
||
}
|
||
|
||
if (s_match_section(p_line, p_name, sizeof(p_name)) != 0) {
|
||
int i;
|
||
sec_idx = -1;
|
||
for (i = 0; i < (int)CFG_FILE_MAX_SECTIONS; i++) {
|
||
if (s_sections[i].used != 0 && s_str_ci_eq(p_name, s_sections[i].name) != 0) {
|
||
sec_idx = i;
|
||
break;
|
||
}
|
||
}
|
||
continue;
|
||
}
|
||
|
||
if (sec_idx < 0 || (only_sec >= 0 && sec_idx != only_sec)) {
|
||
continue;
|
||
}
|
||
|
||
{
|
||
char *p_key = NULL;
|
||
const char *p_val = s_key_value(p_line, &p_key);
|
||
|
||
if (p_val != NULL) {
|
||
const cfg_sec_t *p_sec = &s_sections[sec_idx];
|
||
int k;
|
||
|
||
for (k = 0; k < (int)p_sec->key_count; k++) {
|
||
if (s_str_ci_eq(p_key, p_sec->p_keys[k].name) != 0) {
|
||
if (s_decode(&p_sec->p_keys[k],
|
||
(uint8_t *)p_sec->p_data + p_sec->p_keys[k].offset,
|
||
p_val) != 0) {
|
||
DBG_ERROR("parse: [%s] %s value invalid, keep factory default",
|
||
p_sec->name, p_key);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:向输出缓冲追加一行原文(行文本 + CRLF;空行 = 分隔空行)
|
||
* 入口参数:p_out - 输出缓冲 char *
|
||
* out_cap - 缓冲容量 size_t
|
||
* p_off - 当前偏移(入/出) size_t *
|
||
* p_line - 行文本 const char *
|
||
* 返回值:0 - 成功;-1 - 溢出(偏移不变) int
|
||
* 限定条件:p_line 非空
|
||
*/
|
||
static int s_emit_line(char *p_out, size_t out_cap, size_t *p_off, const char *p_line) {
|
||
int n;
|
||
|
||
n = snprintf(p_out + *p_off, out_cap - *p_off, "%s\r\n", p_line);
|
||
if (n < 0 || (size_t)n >= out_cap - *p_off) {
|
||
return -1;
|
||
}
|
||
*p_off += (size_t)n;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:向输出缓冲追加"键=值"行
|
||
* 入口参数:p_out - 输出缓冲 char *
|
||
* out_cap - 缓冲容量 size_t
|
||
* p_off - 当前偏移(入/出) size_t *
|
||
* p_key - 键名 const char *
|
||
* p_val - 值文本 const char *
|
||
* 返回值:0 - 成功;-1 - 溢出(偏移不变) int
|
||
* 限定条件:p_key/p_val 非空
|
||
*/
|
||
static int s_emit_kv(char *p_out, size_t out_cap, size_t *p_off, const char *p_key, const char *p_val) {
|
||
int n;
|
||
|
||
n = snprintf(p_out + *p_off, out_cap - *p_off, "%s=%s\r\n", p_key, p_val);
|
||
if (n < 0 || (size_t)n >= out_cap - *p_off) {
|
||
return -1;
|
||
}
|
||
*p_off += (size_t)n;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:向输出缓冲追加段头行
|
||
* 入口参数:p_out - 输出缓冲 char *
|
||
* out_cap - 缓冲容量 size_t
|
||
* p_off - 当前偏移(入/出) size_t *
|
||
* p_name - 段名 const char *
|
||
* 返回值:0 - 成功;-1 - 溢出(偏移不变) int
|
||
* 限定条件:p_name 非空
|
||
*/
|
||
static int s_emit_header(char *p_out, size_t out_cap, size_t *p_off, const char *p_name) {
|
||
int n;
|
||
|
||
n = snprintf(p_out + *p_off, out_cap - *p_off, "[%s]\r\n", p_name);
|
||
if (n < 0 || (size_t)n >= out_cap - *p_off) {
|
||
return -1;
|
||
}
|
||
*p_off += (size_t)n;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:外科手术式保存变换(保留注释/未知内容,受管段/键按 RAM 副本改写)
|
||
* 入口参数:p_in - 原文本(行末已 NUL 化的可修改缓冲) char *
|
||
* in_len - 原文长度 size_t
|
||
* p_out - 输出缓冲 char *
|
||
* out_cap - 输出容量 size_t
|
||
* p_out_len - 输出长度 size_t *
|
||
* 返回值:0 - 成功;-1 - 溢出(调用方放弃保存,原文件不动) int
|
||
* 限定条件:调用前 s_sec_hdr/s_key_line 已由 Pass 1 填充
|
||
*/
|
||
static int s_surgical(char *p_in, size_t in_len, char *p_out, size_t out_cap, size_t *p_out_len) {
|
||
size_t off = 0u;
|
||
|
||
(void)in_len;
|
||
|
||
/* ---- Pass 2:逐行输出 ---- */
|
||
{
|
||
char *p = p_in;
|
||
int i_sec = -1;
|
||
int line_no = 0;
|
||
int i;
|
||
int k;
|
||
char p_name[CFG_NAME_MAX];
|
||
|
||
while (*p != '\0') {
|
||
const char *p_line = s_split_line(&p);
|
||
|
||
if (s_match_section(p_line, p_name, sizeof(p_name)) != 0) {
|
||
int idx = -1;
|
||
for (i = 0; i < (int)CFG_FILE_MAX_SECTIONS; i++) {
|
||
if (s_sections[i].used != 0 && s_str_ci_eq(p_name, s_sections[i].name) != 0) {
|
||
idx = i;
|
||
break;
|
||
}
|
||
}
|
||
if (idx >= 0) {
|
||
if (s_emit_header(p_out, out_cap, &off, s_sections[idx].name) != 0) {
|
||
return -1;
|
||
}
|
||
for (k = 0; k < (int)s_sections[idx].key_count; k++) {
|
||
if (s_key_line[idx][k] == 0xFFFFu) {
|
||
const cfg_key_def_t *p_key = &s_sections[idx].p_keys[k];
|
||
char v[80];
|
||
if (s_encode(p_key, (const uint8_t *)s_sections[idx].p_data + p_key->offset,
|
||
v, sizeof(v)) == 0) {
|
||
if (s_emit_kv(p_out, out_cap, &off, p_key->name, v) != 0) {
|
||
return -1;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
i_sec = idx;
|
||
}
|
||
else {
|
||
if (s_emit_line(p_out, out_cap, &off, p_line) != 0) {
|
||
return -1;
|
||
}
|
||
i_sec = -1;
|
||
}
|
||
}
|
||
else if (i_sec >= 0) {
|
||
int is_key = 0;
|
||
for (k = 0; k < (int)s_sections[i_sec].key_count; k++) {
|
||
if (s_key_line[i_sec][k] == (uint16_t)line_no) {
|
||
is_key = 1;
|
||
break;
|
||
}
|
||
}
|
||
if (is_key) {
|
||
const cfg_key_def_t *p_key = &s_sections[i_sec].p_keys[k];
|
||
char v[80];
|
||
if (s_encode(p_key, (const uint8_t *)s_sections[i_sec].p_data + p_key->offset,
|
||
v, sizeof(v)) == 0) {
|
||
if (s_emit_kv(p_out, out_cap, &off, p_key->name, v) != 0) {
|
||
return -1;
|
||
}
|
||
}
|
||
else {
|
||
if (s_emit_line(p_out, out_cap, &off, p_line) != 0) {
|
||
return -1;
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
if (s_emit_line(p_out, out_cap, &off, p_line) != 0) {
|
||
return -1;
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
if (s_emit_line(p_out, out_cap, &off, p_line) != 0) {
|
||
return -1;
|
||
}
|
||
}
|
||
line_no++;
|
||
}
|
||
|
||
/* 文件中未见的受管段 → 文末整段补写 */
|
||
for (i = 0; i < (int)CFG_FILE_MAX_SECTIONS; i++) {
|
||
const cfg_sec_t *p_sec = &s_sections[i];
|
||
|
||
if (p_sec->used == 0 || s_sec_hdr[i] != 0xFFFF) {
|
||
continue;
|
||
}
|
||
if (s_emit_line(p_out, out_cap, &off, "") != 0) {
|
||
return -1;
|
||
}
|
||
if (s_emit_header(p_out, out_cap, &off, p_sec->name) != 0) {
|
||
return -1;
|
||
}
|
||
for (k = 0; k < (int)p_sec->key_count; k++) {
|
||
const cfg_key_def_t *p_key = &p_sec->p_keys[k];
|
||
char v[80];
|
||
if (s_encode(p_key, (const uint8_t *)p_sec->p_data + p_key->offset,
|
||
v, sizeof(v)) == 0) {
|
||
if (s_emit_kv(p_out, out_cap, &off, p_key->name, v) != 0) {
|
||
return -1;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
*p_out_len = off;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:保存 Pass 1——定位段头行号与各受管键行号
|
||
* 入口参数:p_in - 原文本(可修改) char *
|
||
* 返回值:无
|
||
* 限定条件:行末被 NUL 化且不复原——调用方须传一次性副本
|
||
*/
|
||
static void s_surgical_pass1(char *p_in) {
|
||
char *p = p_in;
|
||
int line_no = 0;
|
||
int i;
|
||
int k;
|
||
int i_cur = -1;
|
||
char p_name[CFG_NAME_MAX];
|
||
char lbuf[CFG_LINE_TEST_MAX];
|
||
|
||
for (i = 0; i < (int)CFG_FILE_MAX_SECTIONS; i++) {
|
||
s_sec_hdr[i] = 0xFFFF;
|
||
for (k = 0; k < (int)CFG_FILE_MAX_KEYS; k++) {
|
||
s_key_line[i][k] = 0xFFFFu;
|
||
}
|
||
}
|
||
|
||
while (*p != '\0') {
|
||
const char *p_line = s_split_line(&p);
|
||
|
||
if (p_line[0] == '\0' || p_line[0] == ';' || p_line[0] == '#') {
|
||
line_no++;
|
||
continue;
|
||
}
|
||
|
||
if (s_match_section(p_line, p_name, sizeof(p_name)) != 0) {
|
||
i_cur = -1;
|
||
for (i = 0; i < (int)CFG_FILE_MAX_SECTIONS; i++) {
|
||
if (s_sections[i].used != 0 && s_str_ci_eq(p_name, s_sections[i].name) != 0) {
|
||
s_sec_hdr[i] = line_no;
|
||
i_cur = i;
|
||
break;
|
||
}
|
||
}
|
||
line_no++;
|
||
continue;
|
||
}
|
||
|
||
if (i_cur >= 0 && strlen(p_line) <= CFG_LINE_TEST_MAX) {
|
||
/* 行副本上做键名匹配(不破坏原文,供 Pass 2 原样保留) */
|
||
(void)snprintf(lbuf, sizeof(lbuf), "%s", p_line);
|
||
{
|
||
char *p_key = NULL;
|
||
const char *p_val = s_key_value(lbuf, &p_key);
|
||
|
||
if (p_val != NULL) {
|
||
for (k = 0; k < (int)s_sections[i_cur].key_count; k++) {
|
||
if (s_key_line[i_cur][k] == 0xFFFFu &&
|
||
s_str_ci_eq(p_key, s_sections[i_cur].p_keys[k].name) != 0) {
|
||
s_key_line[i_cur][k] = (uint16_t)line_no;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
line_no++;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:注册配置段
|
||
* 入口参数:p_reg - 段注册描述符(不可为 NULL) const cfg_section_reg_t *
|
||
* 返回值:0 - 成功;-1 - 参数非法/段名重复/段数超限 int
|
||
* 限定条件:启动早期调用一次
|
||
*/
|
||
int cfg_file_register(const cfg_section_reg_t *p_reg) {
|
||
int i;
|
||
int k;
|
||
int nsecs = 0;
|
||
|
||
if (p_reg == NULL || p_reg->name == NULL || p_reg->p_data == NULL ||
|
||
p_reg->p_defaults == NULL || p_reg->p_keys == NULL) {
|
||
return -1;
|
||
}
|
||
if (p_reg->key_count == 0u || p_reg->key_count > CFG_FILE_MAX_KEYS) {
|
||
return -1;
|
||
}
|
||
if (p_reg->data_size == 0u || strlen(p_reg->name) >= CFG_NAME_MAX) {
|
||
return -1;
|
||
}
|
||
if (p_reg->p_cb != NULL && p_reg->data_size > CFG_SNAP_MAX) {
|
||
return -1;
|
||
}
|
||
|
||
for (i = 0; i < (int)CFG_FILE_MAX_SECTIONS; i++) {
|
||
if (s_sections[i].used != 0) {
|
||
if (s_str_ci_eq(s_sections[i].name, p_reg->name) != 0) {
|
||
DBG_ERROR("register: section '%s' duplicated", p_reg->name);
|
||
return -1;
|
||
}
|
||
nsecs++;
|
||
}
|
||
}
|
||
if (nsecs >= (int)CFG_FILE_MAX_SECTIONS) {
|
||
return -1;
|
||
}
|
||
|
||
for (k = 0; k < (int)p_reg->key_count; k++) {
|
||
const cfg_key_def_t *p_key = &p_reg->p_keys[k];
|
||
|
||
if (p_key->name == NULL || p_key->len == 0u) {
|
||
return -1;
|
||
}
|
||
if (p_key->type == CFG_VAL_UINT &&
|
||
(p_key->len != 1u && p_key->len != 2u && p_key->len != 4u)) {
|
||
return -1;
|
||
}
|
||
if (p_key->type == CFG_VAL_IP4 && p_key->len != 4u) {
|
||
return -1;
|
||
}
|
||
if (p_key->type == CFG_VAL_MAC && p_key->len != 6u) {
|
||
return -1;
|
||
}
|
||
if (p_key->type == CFG_VAL_STR && p_key->len > 79u) {
|
||
return -1;
|
||
}
|
||
}
|
||
|
||
for (i = 0; i < (int)CFG_FILE_MAX_SECTIONS; i++) {
|
||
if (s_sections[i].used == 0) {
|
||
cfg_sec_t *p_sec = &s_sections[i];
|
||
|
||
(void)memcpy(p_sec->name, p_reg->name, strlen(p_reg->name) + 1u);
|
||
p_sec->p_data = p_reg->p_data;
|
||
p_sec->p_defaults = p_reg->p_defaults;
|
||
p_sec->data_size = p_reg->data_size;
|
||
p_sec->p_keys = p_reg->p_keys;
|
||
p_sec->key_count = p_reg->key_count;
|
||
p_sec->p_cb = p_reg->p_cb;
|
||
p_sec->used = 1;
|
||
DBG_INFO("register: section '%s' (%u keys)", p_sec->name, (unsigned)p_reg->key_count);
|
||
return 0;
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:从配置文件加载全部段
|
||
* 入口参数:无
|
||
* 返回值:0 - 正常(含回退工厂默认);-1 - 严重错误 int
|
||
* 限定条件:至少注册一个段
|
||
*/
|
||
int cfg_file_load(void) {
|
||
cfg_file_stat_t st;
|
||
int i;
|
||
int nsecs = 0;
|
||
|
||
for (i = 0; i < (int)CFG_FILE_MAX_SECTIONS; i++) {
|
||
if (s_sections[i].used != 0) {
|
||
nsecs++;
|
||
}
|
||
}
|
||
if (nsecs == 0) {
|
||
return -1;
|
||
}
|
||
|
||
/* 各段先复位工厂默认(文件 = 配置唯一来源:缺失/损坏的键 = 工厂默认) */
|
||
for (i = 0; i < (int)CFG_FILE_MAX_SECTIONS; i++) {
|
||
if (s_sections[i].used != 0) {
|
||
(void)memcpy(s_sections[i].p_data, s_sections[i].p_defaults, s_sections[i].data_size);
|
||
}
|
||
}
|
||
|
||
if (cfg_store_stat(cfg_store_config_path(), &st) != 0 || st.size == 0u) {
|
||
/* 文件缺失/空(首启):生成出厂模板写盘 */
|
||
if (s_write_template() != 0) {
|
||
DBG_ERROR("load: template write FAIL");
|
||
return -1;
|
||
}
|
||
DBG_INFO("load: file missing, factory template generated");
|
||
return 0;
|
||
}
|
||
|
||
if (st.size > CFG_FILE_MAX_LEN) {
|
||
/* 文件超长 = 损坏:留工厂默认 + 重建模板 */
|
||
if (s_write_template() != 0) {
|
||
DBG_ERROR("load: corrupt file, template write FAIL");
|
||
return -1;
|
||
}
|
||
DBG_ERROR("load: file %luB > %uB, corrupt, factory template regenerated",
|
||
(unsigned long)st.size, (unsigned)CFG_FILE_MAX_LEN);
|
||
return 0;
|
||
}
|
||
|
||
{
|
||
uint8_t *p_buf = NULL;
|
||
size_t len = 0u;
|
||
|
||
if (s_load_file(&p_buf, &len) != 0) {
|
||
DBG_ERROR("load: file read FAIL, factory defaults kept");
|
||
return -1;
|
||
}
|
||
s_parse((char *)p_buf, -1);
|
||
free(p_buf);
|
||
DBG_INFO("load: OK (%luB)", (unsigned long)st.size);
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:保存全部段到配置文件
|
||
* 入口参数:无
|
||
* 返回值:0 - 成功;-1 - 写失败(原文件保持不动) int
|
||
* 限定条件:至少注册一个段
|
||
*/
|
||
int cfg_file_save(void) {
|
||
int i;
|
||
int nsecs = 0;
|
||
cfg_file_stat_t st;
|
||
|
||
for (i = 0; i < (int)CFG_FILE_MAX_SECTIONS; i++) {
|
||
if (s_sections[i].used != 0) {
|
||
nsecs++;
|
||
}
|
||
}
|
||
if (nsecs == 0) {
|
||
return -1;
|
||
}
|
||
|
||
if (cfg_store_stat(cfg_store_config_path(), &st) != 0 || st.size == 0u ||
|
||
st.size > CFG_FILE_MAX_LEN) {
|
||
/* 文件缺失/空/超长 = 损坏:按当前 RAM 副本重建完整文档 */
|
||
if (s_write_template() != 0) {
|
||
DBG_ERROR("save: template write FAIL");
|
||
return -1;
|
||
}
|
||
DBG_INFO("save: OK (document regenerated)");
|
||
return 0;
|
||
}
|
||
|
||
/* 外科手术式保存:保留注释/未知段/未知键 */
|
||
{
|
||
uint8_t *p_in = NULL; /* 加载原文(pristine,供 Pass 2 重切分) */
|
||
uint8_t *p_in1 = NULL; /* Pass 1 专用副本(s_split_line 破坏性 NUL 化后丢弃) */
|
||
char *p_out = NULL;
|
||
size_t in_len = 0u;
|
||
size_t out_len = 0u;
|
||
int r;
|
||
|
||
if (s_load_file(&p_in, &in_len) != 0) {
|
||
DBG_ERROR("save: file read FAIL");
|
||
return -1;
|
||
}
|
||
|
||
/* Pass 1 在独立副本上切分(避免破坏原文的行结束符);
|
||
* 副本在 p_out 分配前释放,堆峰值维持 p_in + p_out(约 9KB) */
|
||
p_in1 = (uint8_t *)malloc(in_len + 1u);
|
||
if (p_in1 == NULL) {
|
||
free(p_in);
|
||
DBG_ERROR("save: malloc %uB FAIL", (unsigned)(in_len + 1u));
|
||
return -1;
|
||
}
|
||
(void)memcpy(p_in1, p_in, in_len + 1u);
|
||
|
||
s_surgical_pass1((char *)p_in1);
|
||
|
||
free(p_in1);
|
||
|
||
p_out = (char *)malloc(CFG_OUT_MAX);
|
||
if (p_out == NULL) {
|
||
free(p_in);
|
||
DBG_ERROR("save: malloc %uB FAIL", (unsigned)CFG_OUT_MAX);
|
||
return -1;
|
||
}
|
||
|
||
r = s_surgical((char *)p_in, in_len, p_out, CFG_OUT_MAX, &out_len);
|
||
|
||
free(p_in);
|
||
if (r != 0) {
|
||
free(p_out);
|
||
DBG_ERROR("save: output overflow, aborted (file untouched)");
|
||
return -1;
|
||
}
|
||
r = s_write_doc((const uint8_t *)p_out, out_len);
|
||
free(p_out);
|
||
if (r != 0) {
|
||
DBG_ERROR("save: file write FAIL");
|
||
return -1;
|
||
}
|
||
DBG_INFO("save: OK (surgical, %uB -> %uB)", (unsigned)in_len, (unsigned)out_len);
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:配置文件变更轮询(沉降去抖 + 重载 + 分段变更回调)
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:单一任务周期调用(2s)
|
||
*/
|
||
void cfg_file_poll(void) {
|
||
cfg_file_stat_t st;
|
||
|
||
if (cfg_store_stat(cfg_store_config_path(), &st) != 0) {
|
||
return; /* 文件缺失/读失败:保留当前 RAM 配置 */
|
||
}
|
||
|
||
if (s_base_valid == 0u) {
|
||
s_base = st;
|
||
s_last = st;
|
||
s_stable = 0u;
|
||
s_base_valid = 1u;
|
||
return;
|
||
}
|
||
|
||
if (st.size == s_last.size && st.mtime == s_last.mtime) {
|
||
if (s_stable < CFG_FILE_SETTLE_CNT) {
|
||
s_stable++;
|
||
}
|
||
}
|
||
else {
|
||
s_last = st;
|
||
s_stable = 1u;
|
||
}
|
||
|
||
if (s_stable < CFG_FILE_SETTLE_CNT ||
|
||
(st.size == s_base.size && st.mtime == s_base.mtime)) {
|
||
return; /* 未沉降或与基线一致 */
|
||
}
|
||
|
||
/* 已沉降:重载配置文件 */
|
||
{
|
||
uint8_t *p_buf = NULL;
|
||
size_t len = 0u;
|
||
int i;
|
||
|
||
if (s_load_file(&p_buf, &len) != 0) {
|
||
return; /* 读失败:保留,下次 poll 重试 */
|
||
}
|
||
|
||
if (len > CFG_FILE_MAX_LEN) {
|
||
free(p_buf);
|
||
for (i = 0; i < (int)CFG_FILE_MAX_SECTIONS; i++) {
|
||
if (s_sections[i].used != 0) {
|
||
(void)memcpy(s_sections[i].p_data, s_sections[i].p_defaults,
|
||
s_sections[i].data_size);
|
||
}
|
||
}
|
||
(void)s_write_template();
|
||
DBG_ERROR("poll: file corrupt (> %uB), factory template regenerated",
|
||
(unsigned)CFG_FILE_MAX_LEN);
|
||
}
|
||
else {
|
||
for (i = 0; i < (int)CFG_FILE_MAX_SECTIONS; i++) {
|
||
const cfg_sec_t *p_sec = &s_sections[i];
|
||
|
||
if (p_sec->used == 0) {
|
||
continue;
|
||
}
|
||
if (p_sec->p_cb != NULL) {
|
||
(void)memcpy(s_snap, p_sec->p_data, p_sec->data_size);
|
||
(void)memcpy(p_sec->p_data, p_sec->p_defaults, p_sec->data_size);
|
||
s_parse((char *)p_buf, i);
|
||
if (memcmp(s_snap, p_sec->p_data, p_sec->data_size) != 0) {
|
||
DBG_INFO("poll: section '%s' changed, reload", p_sec->name);
|
||
p_sec->p_cb();
|
||
}
|
||
}
|
||
else {
|
||
(void)memcpy(p_sec->p_data, p_sec->p_defaults, p_sec->data_size);
|
||
s_parse((char *)p_buf, i);
|
||
}
|
||
}
|
||
free(p_buf);
|
||
DBG_INFO("poll: config file reloaded");
|
||
}
|
||
|
||
/* 重载后刷新基线(回调触发的写盘不引发二次重载) */
|
||
{
|
||
cfg_file_stat_t st2;
|
||
if (cfg_store_stat(cfg_store_config_path(), &st2) == 0) {
|
||
s_base = st2;
|
||
}
|
||
else {
|
||
s_base = st;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:取段 RAM 副本指针
|
||
* 入口参数:p_name - 段名 const char *
|
||
* 返回值:段 RAM 副本指针;未注册返回 NULL void *
|
||
* 限定条件:p_name 非空
|
||
*/
|
||
void *cfg_file_section_data(const char *p_name) {
|
||
int i;
|
||
|
||
if (p_name == NULL) {
|
||
return NULL;
|
||
}
|
||
for (i = 0; i < (int)CFG_FILE_MAX_SECTIONS; i++) {
|
||
if (s_sections[i].used != 0 && s_str_ci_eq(s_sections[i].name, p_name) != 0) {
|
||
return s_sections[i].p_data;
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|