From 271adc38e1645391c5f29f1011a83c6f74fd4541 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Sun, 20 Sep 2026 17:06:51 +0800 Subject: [PATCH] =?UTF-8?q?FatFs=E7=9F=AD=E5=90=8D=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E6=8B=92=E7=BB=9D=E7=82=B9=E5=8A=A0DEBUG=E6=89=93=E5=8D=B0?= =?UTF-8?q?=E5=B9=B6=E5=8F=B0=E6=9E=B6=E5=AE=9E=E8=AF=81+AGENTS.md?= =?UTF-8?q?=E5=9B=BA=E5=8C=96=E4=B8=A5=E6=A0=BC8.3=E5=91=BD=E5=90=8D?= =?UTF-8?q?=E7=BA=A6=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 1 + Lib/FatFs/ff.c | 50 +++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d1ed3c3b..939bc3a6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -44,6 +44,7 @@ STM32F407ZGTx @168MHz(HSE 8MHz → PLL M=4/N=168/P=2);**仅 Keil MDK-ARM v - **内存预算紧张**:新增任何任务/缓冲/栈前必须先核算 RAM 余量(`docs/内存管理/`、`待处理内容.md`) - **自研代码禁用 C 库堆分配**(2026-09-18,P0-1):禁止 `malloc/calloc/realloc/strdup/free`——本机工具链 MicroLIB 的 C 堆由 `startup_stm32f407xx.s` 的 `Heap_Size EQU 0x200` 限定为 **512 B**,KB 级分配必然返回 NULL 且**模拟器(宿主 CRT 堆)完全正常、照不出来**。需要动态内存只用 `pvPortMalloc/vPortFree`(FreeRTOS 堆 `configTOTAL_HEAP_SIZE`),或像配置引擎那样把分配收进平台后端(`cfg_store_alloc/free/heap_free`:MCU=FreeRTOS 堆、模拟器=宿主堆,共享核心零分配符号);分配失败必须有 `FAIL` 日志 + 自检上报,暂态峰值要写编译期预算 - **FatFs 打开模式陷阱(2026-09-20 台架实测,`Lib/FatFs` 移植版)**:`FA_OPEN_APPEND`(0x30) = `FA_OPEN_ALWAYS|FA_SEEKEND`,**不含 `FA_WRITE`(0x02)**——不带写位打开的文件 `f_write` 恒返回 `FR_DENIED`(7)(/log 写失败真根因);追加写必须用 `FA_WRITE|FA_OPEN_APPEND`。该移植的 `f_write` 簇分配失败时返回 `FR_OK`+短写(无错误码),判失败必须校验写回字节数。标志位为旧式编号(`FA_READ`=0x01 / `FA_WRITE`=0x02 / `FA_CREATE_NEW`=0x04 / `FA_CREATE_ALWAYS`=0x08 / `FA_OPEN_ALWAYS`=0x10 / `FA_OPEN_APPEND`=0x30 / `FA_SEEKEND`=0x20(ff.c 内部定义)),与上游 FatFs R0.14 不同,勿照抄上游代码示例 +- **FatFs 文件名约束(2026-09-20 台架实证,`.fschk` 无限重格式化事故根因)**:本移植 `Lib/FatFs/ffconf.h` 为 `FF_USE_LFN=0`,**只支持严格 8.3 短名**——主名≤8 字符、扩展≤3 字符、**不得以点开头**(`.fschk` 会被 `ff.c create_short` 把点后当扩展域解析超限 → `f_open` 恒返 `FR_INVALID_NAME`(6),纯 RAM 名字判定、不碰盘)。ff.c `create_short` 全部拒绝点已加 DEBUG 打印:`[ERR][FATFS] SFN reject (原因) seg='名字片段' bad_ch=坏字符`,非法名字调用看开机日志即可定位。自愈类逻辑**严禁**把名字/参数类错误码(`FR_INVALID_NAME` 等)归入卷损坏触发集(整卷重格式化不可逆) | Task | 栈 (words) | 优先级 | |---|---|---| diff --git a/Lib/FatFs/ff.c b/Lib/FatFs/ff.c index 05ca02f0..284d3254 100644 --- a/Lib/FatFs/ff.c +++ b/Lib/FatFs/ff.c @@ -23,6 +23,21 @@ #include "ff.h" /* Declarations of FatFs API */ #include "diskio.h" /* Declarations of device I/O functions */ +/* 项目定制(2026-09-20,".fschk 无限重格式化"事故复盘): + * FF_USE_LFN=0 时 f_open/f_create/f_unlink/f_mkdir 等一切文件 API + * 最终都汇聚到 create_short() 做 8.3 短名解析——这是名字合法性的 + * 唯一咽喉点。在拒绝瞬间打印名字片段+原因,让"非法文件名"这类 + * 纯 RAM 判定错误从开机日志直接可见,不再需要拿错误码反查枚举表。 + * 注意:字符串字面量必须纯 ASCII(ARMCC 对无 BOM 文件按 CP936 解析)。 */ +#if FF_USE_LFN == 0 +#define DBG_TAG "[FATFS]" +#include "dbg_log.h" +#define FF_DBG_SFN_REJECT(why, seg, ch) \ + DBG_ERROR("SFN reject (%s) seg='%.24s' bad_ch=0x%02X" \ + " - strict 8.3 only: body<=8 ext<=3 no leading dot", \ + (why), (seg), (int)(ch)) +#endif + /*-------------------------------------------------------------------------- @@ -2993,7 +3008,12 @@ static FRESULT create_name ( /* FR_OK: successful, FR_INVALID_NAME: could not cr if (c != '.' || si >= 3) break; sfn[i++] = c; } - if (!IsSeparator(c) && c > ' ') return FR_INVALID_NAME; + if (!IsSeparator(c) && c > ' ') { +#if FF_USE_LFN == 0 + FF_DBG_SFN_REJECT("heading dot", p, c); +#endif + return FR_INVALID_NAME; + } *path = p + si; /* Return pointer to the next segment */ sfn[NSFLAG] = (c <= ' ') ? NS_LAST | NS_DOT : NS_DOT; /* Set last segment flag if end of the path */ return FR_OK; @@ -3007,7 +3027,12 @@ static FRESULT create_name ( /* FR_OK: successful, FR_INVALID_NAME: could not cr break; } if (c == '.' || i >= ni) { /* End of body or field overflow? */ - if (ni == 11 || c != '.') return FR_INVALID_NAME; /* Field overflow or invalid dot? */ + if (ni == 11 || c != '.') { +#if FF_USE_LFN == 0 + FF_DBG_SFN_REJECT("ext overflow or bad dot", p, c); +#endif + return FR_INVALID_NAME; /* Field overflow or invalid dot? */ + } i = 8; ni = 11; /* Enter file extension field */ continue; } @@ -3022,17 +3047,32 @@ static FRESULT create_name ( /* FR_OK: successful, FR_INVALID_NAME: could not cr #endif if (dbc_1st(c)) { /* Check if it is a DBC 1st byte */ d = (BYTE)p[si++]; /* Get 2nd byte */ - if (!dbc_2nd(d) || i >= ni - 1) return FR_INVALID_NAME; /* Reject invalid DBC */ + if (!dbc_2nd(d) || i >= ni - 1) { +#if FF_USE_LFN == 0 + FF_DBG_SFN_REJECT("invalid DBC", p, c); +#endif + return FR_INVALID_NAME; /* Reject invalid DBC */ + } sfn[i++] = c; sfn[i++] = d; } else { /* SBC */ - if (strchr("*+,:;<=>[]|\"\?\x7F", (int)c)) return FR_INVALID_NAME; /* Reject illegal chrs for SFN */ + if (strchr("*+,:;<=>[]|\"\?\x7F", (int)c)) { +#if FF_USE_LFN == 0 + FF_DBG_SFN_REJECT("illegal char", p, c); +#endif + return FR_INVALID_NAME; /* Reject illegal chrs for SFN */ + } if (IsLower(c)) c -= 0x20; /* To upper */ sfn[i++] = c; } } *path = &p[si]; /* Return pointer to the next segment */ - if (i == 0) return FR_INVALID_NAME; /* Reject nul string */ + if (i == 0) { +#if FF_USE_LFN == 0 + FF_DBG_SFN_REJECT("empty name", p, c); +#endif + return FR_INVALID_NAME; /* Reject nul string */ + } if (sfn[0] == DDEM) sfn[0] = RDDEM; /* If the first character collides with DDEM, replace it with RDDEM */ sfn[NSFLAG] = (c <= ' ' || p[si] <= ' ') ? NS_LAST : 0; /* Set last segment flag if end of the path */