/* * 模块名称:cfg_file * 模块功能:通用配置文件引擎(双端共享核心)——INI 段/键表解析校验、外科手术式 * 保存(保留注释/未知内容,缺失段/键写回)、出厂模板生成、沉降去抖轮询 * 与分段变更回调;平台文件操作经 cfg_file_store 后端 * 适用平台:STM32F407ZGTx / PC 模拟器(两端共用) * 作者:王建锋 * 创建日期:2026-09-14 * 修改记录: * v1.0 2026-09-14 王建锋 创建初始版本(P3:段键表引擎 + net_cfg 集成) * v1.1 2026-09-18 王建锋 P0-1:临时缓冲改经 cfg_store_alloc(MCU = FreeRTOS 堆), * 移除对 C 库 malloc 的全部依赖(MicroLIB C 堆仅 512B, * 原 4 处 KB 级 malloc 真机必然失败);增编译期堆峰值预算 * 校验 + 失败上报自检记录 * 函数说明:引擎状态非重入——调用方须收敛同一任务(defaultTask:poll/save,见 P1-3; * 启动期 load 先于任务竞争);FS 卷并发由 FatFS REENTRANT 卷锁保证 * (见《配置文件设计说明》D15 A+ 方案) */ /* 头文件包含区 */ #include #include /* strtoul(键值解码);堆分配一律走 cfg_store_alloc,禁 malloc */ #include #include #include "cfg_file.h" #include "cfg_file_store.h" #include "sys_log.h" /* 配置读写失败上报自检记录(双端共享接口,MCU 端 preboot/队列安全) */ #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 /* 保存输出缓冲上限(经 cfg_store_alloc 取,见下方堆峰值预算) */ #define CFG_LINE_TEST_MAX 127u /* 受管键行匹配的行长上限(超长必非受管键行) */ #define CFG_SNAP_MAX 64u /* 变更检测段快照缓冲(挂回调段 data_size 须 ≤ 此值) */ #define CFG_NAME_MAX 16u /* 段名缓冲(15 字符 + NUL) */ #define CFG_HEAP_BUDGET 10240u /* 暂态堆峰值预算:读缓冲(1B NUL) + 输出缓冲之和上限 */ /* 编译期钉死暂态堆峰值:放大 CFG_FILE_MAX_LEN / CFG_OUT_MAX 会先在此编译失败,强制 * 改动者重新核对平台堆余量(MCU = FreeRTOS 堆,当前实测余量见《RAM使用分析与优化方案》), * 而不是在真机上退化成运行期分配失败。用 typedef 负数组实现(ARMCC 5 C99 无 C11 * _Static_assert);当前峰值 4097 + 5120 = 9217B ≤ 10240B */ typedef char cfg_heap_peak_check_t[(CFG_FILE_MAX_LEN + 1u + CFG_OUT_MAX) <= CFG_HEAP_BUDGET ? 1 : -1]; /* 静态变量定义区 */ 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 * 限定条件:成功时调用方须 cfg_store_free(*p_pbuf)(MCU = FreeRTOS 堆,勿用 C 库 free) */ 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 *)cfg_store_alloc(CFG_FILE_MAX_LEN + 1u); if (p_buf == NULL) { DBG_ERROR("read: alloc %uB FAIL (heap free %uB)", (unsigned)(CFG_FILE_MAX_LEN + 1u), (unsigned)cfg_store_heap_free()); return -1; } r = cfg_store_read(cfg_store_config_path(), p_buf, CFG_FILE_MAX_LEN, &nread); if (r != 0) { cfg_store_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 *)cfg_store_alloc(CFG_OUT_MAX); size_t len = 0u; int r; if (p_buf == NULL) { DBG_ERROR("template: alloc %uB FAIL (heap free %uB)", (unsigned)CFG_OUT_MAX, (unsigned)cfg_store_heap_free()); syslog_post(SYSLOG_SELFTEST, "cfg heap alloc fail"); 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); if (r != 0) { syslog_post(SYSLOG_SELFTEST, "cfg template write fail"); } } cfg_store_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"); syslog_post(SYSLOG_SELFTEST, "cfg load read fail"); return -1; } s_parse((char *)p_buf, -1); cfg_store_free(p_buf); DBG_INFO("load: OK (%luB, heap free %uB)", (unsigned long)st.size, (unsigned)cfg_store_heap_free()); 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"); syslog_post(SYSLOG_SELFTEST, "cfg save read fail"); return -1; } /* Pass 1 在独立副本上切分(避免破坏原文的行结束符); * 副本在 p_out 分配前释放,堆峰值维持 p_in + p_out(≤ CFG_HEAP_BUDGET, * 编译期已校验,见文件常量区) */ p_in1 = (uint8_t *)cfg_store_alloc(in_len + 1u); if (p_in1 == NULL) { cfg_store_free(p_in); DBG_ERROR("save: alloc %uB FAIL (heap free %uB)", (unsigned)(in_len + 1u), (unsigned)cfg_store_heap_free()); syslog_post(SYSLOG_SELFTEST, "cfg heap alloc fail"); return -1; } (void)memcpy(p_in1, p_in, in_len + 1u); s_surgical_pass1((char *)p_in1); cfg_store_free(p_in1); p_out = (char *)cfg_store_alloc(CFG_OUT_MAX); if (p_out == NULL) { cfg_store_free(p_in); DBG_ERROR("save: alloc %uB FAIL (heap free %uB)", (unsigned)CFG_OUT_MAX, (unsigned)cfg_store_heap_free()); syslog_post(SYSLOG_SELFTEST, "cfg heap alloc fail"); return -1; } r = s_surgical((char *)p_in, in_len, p_out, CFG_OUT_MAX, &out_len); cfg_store_free(p_in); if (r != 0) { cfg_store_free(p_out); DBG_ERROR("save: output overflow, aborted (file untouched)"); syslog_post(SYSLOG_SELFTEST, "cfg save overflow"); return -1; } r = s_write_doc((const uint8_t *)p_out, out_len); cfg_store_free(p_out); if (r != 0) { DBG_ERROR("save: file write FAIL"); syslog_post(SYSLOG_SELFTEST, "cfg save 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) { cfg_store_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); } } cfg_store_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; }