Files
STM32F4-Base/App/cfg_file.c
T

1191 lines
37 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称: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;
}