fix(log,net): 处置主机单测照出的 10 条现状,撤销 1 条误报

用户裁定「全部修」。第 1 轮修复跨三名队友、写范围互斥、零越界;
11 条登记现状 → 10 条已处置 + 1 条撤销(非阻塞 recv 「看不见 EOF」是误报:
net_recv_locked:1885 的非阻塞分支已把 CLOSED||session_dead 判成 EOF(0),
动手前读码才发现,误报比漏报更贵)。

存储与日志(App/log/sys_log_mcu.c、soe_store.c、cfg_file_store_mcu.c、sys_log.h)
  A 卷忙整批丢日志:批量路径补 rc==-2 分支整批回投(不计故障/不打日志/
    保序);此前一批最多 8 条既不落盘也不回投,而文档写着「条目不丢」
  B rc==-1 文案与行为相反:改 write fail, requeue + 正常→故障节流
  C 删永不可达的截断支;容量契约写死 sys_log.h(不足一律 -1、不写调用方字节)
  D 自愈触发集违规(比原报告更严重):新增代码侧唯一入口 s_is_selfheal_code,
    触发集收敛为 {DISK_ERR,INT_ERR,NO_FILESYSTEM,DENIED},移出名字/参数类码——
    旧集合在 8.3 同名同因下每轮删一次自己的历史日志且永不自愈,正是存储架构
    §10.1 第 10 条严禁的归集;soe_store 侧同源取更保守子集(打开失败绝不 unlink)
  E 注释里的 FR_NO_SYSTEM 不存在,改 FR_NOT_READY / FR_NO_FILESYSTEM
  F syslog_post 满态留痕节流(A 修好后「队列常驻满」成必然场景,不修等于用
    新刷屏换掉静默丢);丢弃语义一字未改

NET(net_socket.c/.h、net_types.h、net_socket_sim.c、lftpd.c)
  IP 字节序统一到 POSIX 网络序:改 net_inet_addr(&s_addr[0]==第一段)+ 出向
    IP 数组按内存字节顺位拆装(进与出同时换族→芯片侧数组净效果不变,550h 钉住)
    + 模拟器逐字同步;ntoa 函数体未动,只改对它注释里取了废弃族值的例子——
    那正是两套约定长期共存不被发现的原因。lftpd 两处算术核对确认无需改
  UDP 绕过 netTask:两入口加执行任务上下文守卫,新码 NET_ERR_NOT_IN_NETTASK
    (-12,重试永远无意义);结论口径=危险面已结构性关掉、串行化改造仍未做。
    附带关掉第二条更隐蔽的路:非 netTask 自旋 recvfrom 期间控制块被 close 并
    把同一 sockfd 分给新会话→旧自旋者消费新会话数据报

控制台(console_log.c/.h):冷却改独立 s_con_cooling 标志(旧写法在 tick==0 那
  拍冷却等于没记);con_append 的判空位+推进 head+整行拷贝收进同一临界区(旧
  为区外 check-then-act,环 >95% 满时可静默覆盖且既不打 [ERR] 也不计 dropped);
  Instance==NULL 与僵尸通道分诊(未初始化绝不 reinit);三本账分离 + 新读口
  dbg_console_tx_timeout();[ERR] 行改为发成功才销账

测试:net_socket 452、console_log 127、sys_log_mcu 104、soe_store 27、
cfg_file_store_mcu 34;回归 net_listener 34 / net_events 34 / net_pending 43 /
net_select 22 / lftpd 35 / lftpd_inet 28 / lftpd_io 33 / iec104_serv 28 /
nand_port 149 / vol_lock 33 / soe 32 / cfg_file 151 全绿。原「按现状钉住」的
用例全部翻正向;console_log 用「逐条退回旧实现」做变异自证,失败集合单调增长
6→11→18→22(把一条本来空转的断言逼了出来)。
游标抬升:TC-NETSOCK-545→560、TC-SYSLOG-100→107、TC-CONLOG-135→167。

文档:控制台设计说明 §4.1 口径与 §13-O2 销项 + v1.5;运行与自检记录 §4.4/§4.5
四条;存储架构 §10.1 第 10 条推论(指向 s_is_selfheal_code);TRAP-NET-21 后续
条目;BSD 指南新增 §9.0 s_addr 唯一约定;覆盖率口径 §9「第 1 轮修复处置」表 +
后续项 5 条 + 台架复验 7 条 + 新坑 8 条 + 变更记录。

后续项(本轮刻意不做,理由见 §9):UDP 改走 netMsgQueue(需 per-task 结果槽,
net_msg_t 按值拷贝、禁止加回传字段,且要真机回归);sim_syslog.c 同源不可达
支(本轮编译验证不到,不交没编译过的改动);syslog_post 丢弃计数;
dbg_console_tx_timeout() 读端接线(STALL 行是被解析的证据行,加字段要连解析端
与台架判据一起改);104 来源地址日志(现网无输出点)。

gate: @build 0 errors 0 warnings(Code=213720 RO=18860 RW=1204 ZI=131500)。
pre-commit run --all-files -> 39 hooks, 36 Passed; failed = mem-numbers +
mem-budget(受管快照与本树首次构建产物的漂移,非本批增量)+ id-check(已修,
现 0 台账外新账,可判定 84.63% > 基线 82.6%)。实测水位 RW_IRAM1 57.4%、
RW_CCM 87.9%,均低于 92% 红线;RAM/Flash 本批增量为数个 static 变量。
test-coverage / cppcheck / coding-standard / bsp-err / layer-deps / doc-links
全 Passed;全量覆盖率 33/33 套件、line 89.3%、func 92.3%、逐文件下降 0。
按仓内分支约定 --no-verify 提交,台账 --write 回填留待主工作树合并后。
This commit is contained in:
2026-10-24 19:43:56 +08:00
parent 794906cdc8
commit 5a806d8a26
29 changed files with 1690 additions and 278 deletions
+168 -23
View File
@@ -16,6 +16,24 @@
* v1.3 2026-10-02 王建锋 新增 sys_log_evidence():把看门狗停喂现场
* 证据落 /log/wdev.log(掉电不丢)+ flush 诱导
* (round 7 纯取证,见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录)
* v1.5 2026-10-24 王建锋 主机单测批次照出的缺陷一起修,本文件内 5 条
* (A/B/C/D/F;E 在 App/cfg/cfg_file_store_mcu.c):
* A 卷忙整批静默丢日志——syslog_drain 批量路径补
* rc==-2 分支把 done 置 0,整批回投队列(不计故障、
* 不打日志),与 -3 慢路径遇 -2 的处置对齐;
* B 同分支文案 "drop N entries" 与行为相反(其实全
* 部回投),改成如实描述并按"正常→故障"节流一条;
* C 删掉 syslog_get_record 里永不可达的截断支,容量
* 要求写进函数头与 sys_log.h(宁可拒绝不静默截断);
* D 自愈触发集按语义拆类(新增 s_is_selfheal_code),
* 把 FR_INVALID_NAME / FR_NO_PATH / FR_NO_FILE /
* FR_INVALID_OBJECT 移出 unlink 重建集合——旧集合在
* 名字/参数类错误下每轮删一次自己的历史日志且永不
* 自愈,违反存储架构 §10.1 第 10 条
* F syslog_post 的丢弃留痕按通道节流(队列满 / 启动前
* 数组满各立 volatile 抑制位,与 B 的 drain 侧那位分
* 开):卷被长期占用 + A 项回投让队列常驻满时,串口
* 不再按投递频率刷 "queue full, drop"
* v1.4 2026-10-04 王建锋 P2-9:syslog_mcu_mounted() 改为幂等 + 卷锁保护;
* defaultTask 每 2s 复查,建 /log 曾失败时自动重试
* (原"仅调用一次"→ 失败后 drain 永久 no-op)。
@@ -65,6 +83,22 @@ static osMessageQueueId_t s_queue; /* 运行期消息队列(s
static const osMessageQueueAttr_t s_queue_attr = { .name = "syslog" };
static volatile uint8_t s_fs_ready; /* 卷挂载 + /log 目录就绪后置位 */
static volatile uint8_t s_mount_fail_reported; /* P2-9:建目录失败日志去重(成功复位) */
/* B 项整改(2026-10-24):syslog_drain 批量写失败"整批回投"这条留痕的节流位——
* 只在"从正常转入故障"的那一次打一条,成功一轮后复位。与 s_append_many 内部的
* s_batch_fault 各管一层:那位压的是"这个文件写坏了",本位压的是"这批条目回投了"。
* 唯一写者 defaultTask,免锁。 */
static uint8_t s_requeue_reported[SYSLOG_COUNT];
/* F 项整改(2026-10-24,Lead 批准并入本轮):syslog_post 的丢弃留痕节流。
* 场景:卷被 ftpTask 长期占用 -> A 项修复后整批回投 -> 队列常驻满 -> 此后每一条
* 日志投递都会"队列满 + 打一条 DBG_ERROR",串口按业务投递频率刷屏(日志本身
* 变成负载)。与上面 s_requeue_reported **分开**是两个必然:那位归 defaultTask
* (唯一写者,免锁),本位由**任意上下文**的投递者读写,且恢复时机是"下一次投递
* 成功"而不是"下一批落盘成功"。两个位各管一个通道:队列 / 启动前待发数组。
* volatile + 单字节:Cortex-M 上对齐字节读写原子;跨任务竞态的最坏后果是"同一轮
* 故障多打一行"或"少打一行",不会漏丢条目、也不会长时间静默(下一次成功会复位)。 */
static volatile uint8_t s_post_qfull_reported; /* 队列满丢弃:已留痕? */
static volatile uint8_t s_post_pboot_full_reported;/* 启动前数组满丢弃:已留痕? */
/* N-24(2026-10-15,报告 N-16 读侧):8 条滑动读窗口 + 写者代数。
* 窗口 512 B 走静态(不吃任务栈;defaultTask/uiTask 最坏链见 stack_report.py,
@@ -80,6 +114,72 @@ static volatile uint32_t s_rd_gen[SYSLOG_COUNT]; /* 每次成功落盘 / unli
/* 函数定义区 */
/*
* ── 自愈触发集逐码依据(配套下方 s_is_selfheal_code;2026-10-24 逐条核对
* Lib/FatFs/ff.c,行号即该文件)────────────────────────────────────────
* 旧实现把 FR_INVALID_NAME / FR_NO_PATH / FR_NO_FILE / FR_INVALID_OBJECT 一并
* 放进触发集,违反《STM32F4-Base 存储架构说明》§10.1 第 10 条:名字/参数类错误
* 码严禁归入"卷损坏"处置集——这类码下 unlink 自己的文件必然同样失败(同名同因),
* 后果是每轮排空删一次自己的历史日志、且永远修不好(run.log 1024 条 /
* selftest.log 全是被删对象)。
*
* 进触发集(删了这个文件真能修好):
* FR_DISK_ERR(1) 低层 I/O 失败(ff.c:1683/1739/1785 等)——陈旧簇链或坏
* 目录项,unlink 后重建换新链有效;
* FR_INT_ERR(2) 内部一致性断言失败(ff.c:1379/1460/3869)——本文件的目录
* 项/簇号已不自洽,只能废弃重建;
* FR_NO_FILESYSTEM(13) 卷上找不到有效 FAT(ff.c:3483 起)——本模块**不**做任何
* 卷级动作(全模块无 f_mount/f_mkfs),只对本文件废弃重建
* 一次并留痕,卷怎么办归挂载门;
* FR_DENIED(7) 两条真实成因都能被 unlink 治好:目标被置只读属性
* (ff.c:3808/3859),以及目录区无空目录项(ff.c:1850 由
* FR_NO_FILE 改写而来)——后者删掉自己这个文件正好腾出一
* 个目录项。陷阱一那类"缺写位"的 FR_DENIED 出现在 f_write
* 上,不走本判定。
*
* 不进触发集(一律立即失败,不 unlink、不重开、不计卷损坏):
* FR_INVALID_NAME(6) 8.3 短名违规 / 非法字符 / 以点开头(非 LFN 配置下的
* create_name)——unlink 同名同因失败,永不自愈;
* FR_NO_PATH(5) 中间路径段缺失(ff.c:3144/4357)= /log 目录不在:重建
* 文件不解决目录,补目录归 syslog_mcu_mounted() 的每 2s 重试
* (见该函数注释里的幂等改造说明),两侧各管一层;
* FR_NO_FILE(4) 本模块用 FA_WRITE|FA_OPEN_APPEND(含 FA_OPEN_ALWAYS),
* "文件不存在"会被直接创建出来;返回本码的唯一现实场景是
* 同名对象是目录(ff.c:3854),unlink 一个目录修不好它;
* FR_INVALID_OBJECT(9) 仅 ff.c:3774 的 !fp 分支,即句柄指针为 NULL——本模块用
* 函数级 static FIL,地址恒非 0,属不可达码;
* FR_NOT_READY(3) / FR_NOT_ENABLED(12) / FR_TIMEOUT(15)——卷未挂载、无工作区、
* FF_FS_REENTRANT 取卷超时,属挂载门与并发范畴,与"这个
* 文件坏了"无关。
*
* 两点边界,免得后人当成漏改:
* 1) f_write 之后那条"失败/短写 -> unlink 重建重试一次"**不接**本判定:其前提
* 是已经打开成功,名字/参数类错误到不了那里;而簇分配失败是 FR_OK + 短写
* (根本没有错误码可判),必须无条件重试。
* 2) App/log/soe_store.c 对同一张表取**更保守的子集**:那边**打开失败连介质类
* 码也不 unlink**。原因不是它漏改,而是两个文件的对象不同——run.log 是滚动
* 运行日志,文件坏了重建损失有限;soe.log 是**事件序列**,里面是一条条已经
* 发生过的保护事件,"打开失败就删掉整个文件"等于**销毁事件证据**(SOE 的存
* 在即目的,删了比写不进去更糟)。所以 SOE 侧的出路是等卷/目录恢复(条目留
* 在待发环与队列里,不丢),只有"已经打开成功之后写失败"才允许废弃重建。
* 两侧资料块必须同步修改、互指(彼处在 App/log/soe_store.c 的同名资料块)。
*/
/*
* 函数功能:判定一个 f_open 返回码是否属于"把自己的日志文件删掉重建一次"能修好
* 的那一类,即 L1 文件级自愈的触发集(D 项整改,逐码依据见上方资料块)
* 入口参数:fr - f_open 的返回码 FRESULT
* 返回值:1 - 属触发集,可 unlink 后重开一次;0 - 不属,一律立即失败、不 unlink、
* 不计卷损坏 int
* 限定条件:纯分类、无任何副作用;调用点只有 s_append_rec() 与 s_append_many()
* 的"打开失败"分支
* 函数说明:拆成函数是为了两处调用点共用同一张表,避免历史上"改一处漏一处"
*/
static int s_is_selfheal_code(FRESULT fr) {
return ((fr == FR_DISK_ERR) || (fr == FR_INT_ERR) ||
(fr == FR_NO_FILESYSTEM) || (fr == FR_DENIED)) ? 1 : 0;
}
/*
* 函数功能:格式化一条 64 B 定长记录(时间戳 + 空格 + epoch + 空格 + 描述)
* 入口参数:id - 日志 ID(当前不参与编码,仅为接口对称) syslog_id_t
@@ -155,10 +255,9 @@ static int s_append_rec(syslog_id_t id, const uint8_t *p_rec) {
* FA_OPEN_APPEND(0x30) 不含 FA_WRITE(0x02),不带写位打开的文件 f_write
* 恒返回 FR_DENIED(7)——2026-09-20 台架实测 /log 写失败 ×5/开机 真根因 */
fr = f_open(&f, s_paths[id], FA_WRITE | FA_OPEN_APPEND);
if (fr != FR_OK && (fr == FR_DENIED || fr == FR_INVALID_OBJECT ||
fr == FR_INT_ERR || fr == FR_DISK_ERR ||
fr == FR_NO_FILE || fr == FR_NO_PATH ||
fr == FR_INVALID_NAME || fr == FR_NO_FILESYSTEM)) {
/* D 项整改(2026-10-24):只有"删了这个文件能修好"的介质/结构类错误码才
* 进这里;名字/参数类一律不 unlink —— 判据见 s_is_selfheal_code() */
if ((fr != FR_OK) && (s_is_selfheal_code(fr) != 0)) {
(void)f_unlink(s_paths[id]);
fr = f_open(&f, s_paths[id], FA_WRITE | FA_OPEN_APPEND);
}
@@ -255,10 +354,9 @@ static int s_append_many(syslog_id_t id, const uint8_t *p_buf, uint32_t n) {
total = (UINT)(n * SYSLOG_REC_SIZE);
fr = f_open(&f, s_paths[id], FA_WRITE | FA_OPEN_APPEND);
if (fr != FR_OK && (fr == FR_DENIED || fr == FR_INVALID_OBJECT ||
fr == FR_INT_ERR || fr == FR_DISK_ERR ||
fr == FR_NO_FILE || fr == FR_NO_PATH ||
fr == FR_INVALID_NAME || fr == FR_NO_FILESYSTEM)) {
/* D 项整改(2026-10-24):只有"删了这个文件能修好"的介质/结构类错误码才
* 进这里;名字/参数类一律不 unlink —— 判据见 s_is_selfheal_code() */
if ((fr != FR_OK) && (s_is_selfheal_code(fr) != 0)) {
(void)f_unlink(s_paths[id]);
fr = f_open(&f, s_paths[id], FA_WRITE | FA_OPEN_APPEND);
}
@@ -524,19 +622,36 @@ void syslog_drain(void) {
break; /* 写失败:该条已丢,尾部回投 */
}
}
} else if (rc == -1) {
} else if (rc == -2) {
/* A 项整改(2026-10-24,主机单测照出):卷忙 = **整批回投队列**,
* 静默顺延——不计故障、不打日志(本函数每 2s 排空一次,一打就刷屏),
* 与上面 -3 慢路径遇 -2 的处置对齐。旧实现在这里没有分支:done 保持 n
* → 下面的回投循环空转 → 最多 8 条既不落盘也不回投,静默丢失。 */
done = 0u;
DBG_ERROR("drop %lu entries id=%d", (unsigned long)n, (int)bid);
} else if (rc == -1) {
/* B 项整改(2026-10-24):旧文案写 "drop %lu entries",但本分支
* done = 0 → 整批其实**全部回投**、一条没丢(下轮重试),文案与行为
* 相反会把排障方向带错;改成如实描述,并加"正常→故障"节流(持续故障
* 时每 2s 一条会自己刷成新一轮刷屏,真丢另有下面 hold-over drop 兜底)。 */
done = 0u;
if (s_requeue_reported[bid] == 0u) {
s_requeue_reported[bid] = 1u;
DBG_ERROR("write fail, requeue %lu entries id=%d",
(unsigned long)n, (int)bid);
}
}
/* rc == -2(卷忙):整批回投队列,静默顺延,不计故障 */
for (i = done; i < n; i++) {
if (osMessageQueuePut(s_queue, &s_batch_src[i], 0u, 0u) != osOK) {
DBG_ERROR("hold-over drop (queue full): id=%d", (int)s_batch_src[i].id);
}
}
/* 本批消耗的 preboot 条目:已落盘的删除;未落盘的已转投队列,同样删除
* (否则会重复落盘) */
if (rc == 0) {
s_requeue_reported[bid] = 0u; /* 恢复正常:允许下一次故障重新留痕 */
}
/* 本批消耗的 preboot 条目:已落盘的删除;未落盘的(-1/-2/-3 尾部)已在
* 上面转投队列,同样从数组删除——**由队列接手重投**,不是丢弃
* (A 项整改前 -2 走不到回投,这里删掉就等于吞掉) */
if (n_pre > 0u) {
memmove(&s_preboot[0], &s_preboot[n_pre],
sizeof(syslog_entry_t) * (size_t)(s_preboot_cnt - n_pre));
@@ -558,6 +673,12 @@ void syslog_drain(void) {
* desc - 描述串(UTF-8,静态存储期,超长截断) const char *
* 返回值:无
* 限定条件:FreeRTOS 启动前走静态待发数组;desc 必须为静态/常量串
* 函数说明:丢弃留痕按"正常→故障"节流(F 项整改 2026-10-24):每个通道只在
* 转入故障的那一次打一条,恢复(下一次成功收下)后复位。
* **丢弃语义本身没变**(队列满/数组满仍然是丢),变的只有留痕次数——
* 卷被 FTP 长期占用时 A 项修复让整批回投、队列常驻满,若这里不节流,
* 串口就按业务投递频率刷"queue full, drop",日志自己变成负载。
* 抑制位是 volatile 单字节、由任意上下文读写:竞态最坏只多打/少打一行。
*/
void syslog_post(syslog_id_t id, const char *desc) {
syslog_entry_t e;
@@ -569,15 +690,33 @@ void syslog_post(syslog_id_t id, const char *desc) {
e.desc = desc;
if (s_queue != NULL && osKernelGetState() == osKernelRunning) {
if (osMessageQueuePut(s_queue, &e, 0u, 0u) != osOK) {
DBG_ERROR("queue full, drop: %s", desc);
/* F 项:只报"从正常转入故障"那一次;期间条目照常丢弃(丢弃语义不变,
* 变的只是留痕次数)。下一次投递成功即复位。 */
if (s_post_qfull_reported == 0u) {
s_post_qfull_reported = 1u;
DBG_ERROR("queue full, drop: %s", desc);
}
} else {
/* 恢复正常:清抑制位,下一次故障重新留痕。这里刻意无条件写
* (曾试成"仅置位时才清",被 cppcheck duplicateConditionalAssign
* 判成等价赋值并拦提交闸;本函数上方已做过一次条目拷贝,多一条
* STR 可以忽略,别为它加 inline-suppr)。 */
s_post_qfull_reported = 0u;
}
} else {
uint8_t n = s_preboot_cnt;
if (n < SYSLOG_QUEUE_MAX) {
s_preboot[n] = e;
s_preboot_cnt = n + 1u;
} else {
s_post_pboot_full_reported = 0u; /* 恢复正常:同上,无条件复位 */
} else if (s_post_pboot_full_reported == 0u) {
/* 同一纪律的另一通道:启动前数组只有 SYSLOG_QUEUE_MAX 槽,挂载慢 +
* 开机事件密集时会在整个"未就绪窗口"里每条投递打一行(比队列通道更早
* 发生、也更密集)。数组重新收下第一条即视为恢复。 */
s_post_pboot_full_reported = 1u;
DBG_ERROR("preboot full, drop: %s", desc);
} else {
/* 故障期间静默,条目照常丢弃 */
}
}
}
@@ -603,11 +742,14 @@ uint32_t syslog_count(syslog_id_t id) {
* 函数功能:读取第 idx 条记录(时间戳 + 描述,均 NUL 结尾)
* 入口参数:id - 日志 ID syslog_id_t
* idx - 记录序号(0 起) uint32_t
* p_ts - 时间戳输出缓冲(≥SYSLOG_TS_LEN+1 字节,不可为 NULL) char *
* ts_len - p_ts 容量 size_t
* p_desc - 描述输出缓冲(≥SYSLOG_DESC_MAX+1 字节,不可为 NULL) char *
* desc_len - p_desc 容量 size_t
* 返回值:0 - 成功;-1 - 越界/读失败 int
* p_ts - 时间戳输出缓冲(不可为 NULL) char *
* ts_len - p_ts 容量,**必须 > SYSLOG_TS_LEN**(即至少 20 字节) size_t
* p_desc - 描述输出缓冲(不可为 NULL) char *
* desc_len - p_desc 容量,**必须 > SYSLOG_DESC_MAX**(即至少 29 字节) size_t
* 返回值:0 - 成功;-1 - 越界/读失败/缓冲容量不足(不截断、不写入调用方缓冲) int
* 限定条件:容量不足一律拒绝而不是截断(C 项整改 2026-10-24;理由见函数体注释)。
* 记录里的描述字段本身已在写入侧截到 SYSLOG_DESC_MAX,所以按上面 minimum
* 给缓冲就一定装得下,读侧无需第二道截断。
*/
int syslog_get_record(syslog_id_t id, uint32_t idx, char *p_ts, size_t ts_len, char *p_desc, size_t desc_len) {
uint8_t rec[SYSLOG_REC_SIZE];
@@ -624,9 +766,12 @@ int syslog_get_record(syslog_id_t id, uint32_t idx, char *p_ts, size_t ts_len, c
while (dl < SYSLOG_DESC_MAX && rec[SYSLOG_DESC_OFFS + dl] != '\0') {
dl++;
}
if (dl >= desc_len) {
dl = desc_len - 1u;
}
/* C 项整改(2026-10-24):这里原来还有一句 "if (dl >= desc_len) dl = desc_len
* - 1u"——**永不可达**:上面的守卫已把 desc_len <= SYSLOG_DESC_MAX 全部挡掉,
* 而 dl 由定长记录的内容决定、恒 <= SYSLOG_DESC_MAX。删掉它,容量要求改由
* 函数头与 sys_log.h 写死:**宁可整条拒绝,也不做静默截断**(UI 拿到截断串
* 会显示成一条被删短过的日志,比读不到更难查)。desc_len 的校验仍保留,
* 它是"调用方给够缓冲"这条契约的执行点,不是截断开关。 */
memcpy(p_desc, rec + SYSLOG_DESC_OFFS, dl);
p_desc[dl] = '\0';
return 0;