Files
STM32F4-Base/test/host/common/fatfs_fake.c
T
wandering1 0f7b5a87fb chore(编号): 存量编号全量转语义短语,门禁识别面与语料同批重打
登记簿、文档与代码注释里的存量字母加数字编号(N-x / S-x / T-x / E-x / A-x / 卡号)
一律改写为语义短语,净删 2,028 处引用;删除基线与待确认、并行分道方案、排期详述、
采集保护收尾 4 篇(内容已并入 docs/任务规划/README 与登记簿)。

门禁机器与三张账同批重打:check_ids.py 新增变更记录行豁免与第二根护栏
undecidable_total_ceiling;check_test_traceability.py 只加 stdout/stderr UTF-8
reconfigure(TC_RE 未动);check_cubemx_survival.py 的 --write 固定 newline=LF;
id_registry / id_aliases(+19 条 retired-to-phrase)/ id_known_backlog 重打。

test/ 56 个文件同步 TC 标签语义化,其中 18 个 TU 改了 CHECK / printf 里的标签文字,
是本批唯一改变构建产物的文件集合。

为什么合成一笔、不拆四笔(实测撞闸,非偏好):pre-commit 跑钩子前会 stash 未 staged
部分,拆开后中间提交必然是「新规则 + 旧语料」——实跑可判定 85.15% < 基线 86.00%、
不可判定 1,453 > 上限 1,166,编号门禁直接拦下。编号识别面与它描述的语料只能同码移动。

实跑基线(工作区全量):754 文件 / 8,230 处编号字面量 / 可判定 7,079 = 86.01%
(基线 86.0%,容差 0.1pp)/ 不可判定绝对数 1,151(上限 1,166)/ 校验 1-6 错误级 0 /
告警 = 悬空 2(App/algo/freq.c 的 S2,属 §4.4 同号不同义)+ 孤儿 50。

基线从 84.0 抬到 86.0 的理由见 docs/门禁说明/id-check.md §7.6:本批真删了 2,028 处
引用,可判定面随真实内容上升,不是把分母做小。同批新增排除 docs/硬件平台/ 语料,使
识别面少 264 处——芯片手册位号与网表料号不属编号空间(registry 有 excluded_paths_note)。

⚠ 余量只剩 0.11pp(约 10 处)与绝对数 15 处:把「可判定的引用」改成散文会立刻撞第二
根护栏。另遗留一项:id_aliases 里 retired 的 N / S / T / A 别名没有 scope(U1~U5 有),
今后有人新写一个 A-4,校验 3 不会响,只能靠人,建议下批补 scope。

固件不变证据:115 个 .c/.h 剥注释并归一字符串后与 HEAD 逐字节全等,残留代码差异 0;
App / Src / Drivers 的字符串字面量零改动。@build 0 错误 0 警告;40/40 主机套件、2,889
断言全通过;覆盖率 line 89.2% / func 92.6%;pre-commit 48 钩子 --all-files 全绿;
check_doc_links --all 通过(176 文档,章节引用无法判定 0);逐字节卫生核过:14 个带
BOM 文件与 HEAD 一致、0 个 CR、0 个 U+FFFD,字符串字面量纯 ASCII。

.cubemx_survival_baseline.json 的 watchdog-module 哈希 56bf27 变 2bb051 属合法:
Src/freertos.c 的 USER CODE 区内只有注释文字随编号改写,代码未变。

同批带上体积同码凭证与 RAM 台账行(@build 之后盖戳):RW_IRAM1 79,008 B / 60.3%,
RW_CCM 57,376 B / 87.9%,阈值 92%。
2026-10-26 12:28:14 +08:00

1078 lines
30 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.
/*
* 模块名称:假 FatFs 内存卷(实现)
* 模块功能:实现 fatfs_fake.h 声明的九个 f_* 原型与卷控制/注入/观测接口。
* 由测试单元 #include 本 .c 使用(照 test/host/common/ch395f_fake.c 的做法)。
*
* 语义与 Lib/FatFs/ff.c 的对应关系全部写在 fatfs_fake.h 文件头;本文件只在
* 每条"复刻点"上标一句 ff.c 的出处,不再重复论证。
*
* 刻意不做的事:
* - 不做真 FAT 扇区/簇链模拟——被测的是应用侧对 FRESULT 与写回字节数的处置,
* 不是 ff.c 自己(ff.c 属 Lib/ 第三方,不进覆盖率分母);
* - 不做线程安全:三个被测模块都是"唯一写者 + 卷锁串行化",假卷跟着单线程即可;
* - 不实现 f_mount/f_mkfs/f_readdir:调用即链接失败(见头文件说明)。
*
* 创建日期:2026-10-24(P2 主机单测覆盖率批次,task-2)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "fatfs_fake.h"
/* ff.c 内部标志(ff.h 不导出 FA_SEEKEND,本移植 FA_OPEN_APPEND = 0x10|0x20) */
#define FF_FAKE_SEEKEND 0x20u
#define FF_FAKE_CREATE_MASK (FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW)
/* f_open 成功时写进 fp->obj.fs 的"已挂载卷"指针(真实 ff.c 存的就是 FATFS*,
* 失败清 0)。用本文件里一个真对象的地址而不是硬造整数:MSVC x64 下
* (FATFS *)0xF01DBEEE 会刷 C4312「转换到大得多的类型」。 */
static FATFS s_vol;
#define FF_FAKE_FS_MAGIC (&s_vol)
typedef struct {
int used;
int is_dir;
uint32_t size; /* 逻辑长度(可大于已存内容) */
uint32_t stored; /* data[] 里真实存了多少字节 */
uint16_t fdate;
uint16_t ftime;
char path[FF_FAKE_PATH_MAX]; /* 归一化:全大写、以 '/' 开头、无重复分隔符 */
uint8_t data[FF_FAKE_CLUSTER];
} ff_fake_ent_t;
typedef struct {
int used;
int any_path; /* 1 = 不限路径 */
int op;
int act;
int skip; /* >0:本次命中先放行(skip--),不注入 */
FRESULT rc;
UINT n;
int times;
char path[FF_FAKE_PATH_MAX];
} ff_fake_inj_t;
ff_fake_obs_t g_ff_obs;
static ff_fake_ent_t s_ent[FF_FAKE_MAX_ENT];
static ff_fake_inj_t s_inj[FF_FAKE_INJ_MAX];
static uint16_t s_dostime_d = 0x5A21u; /* 2025-10-06 的 FAT 日期编码(任意基准) */
static uint16_t s_dostime_t = 0x6E40u; /* 13:52:00 */
static uint8_t s_dirty[FF_FAKE_MAX_ENT];
/* ─────────────────────────── 小工具 ─────────────────────────── */
static void s_guard(int op)
{
g_ff_obs.calls[op]++;
if (g_ff_obs.calls[op] > FF_FAKE_GUARD) {
(void)fprintf(stderr, "[FATAL] fatfs_fake: op %d call guard tripped\n", op);
exit(2);
}
}
static char s_up(char c)
{
if ((c >= 'a') && (c <= 'z')) {
return (char)(c - 32);
}
return c;
}
static int s_ieq(const char *a, const char *b)
{
while ((*a != '\0') && (*b != '\0')) {
if (s_up(*a) != s_up(*b)) {
return 0;
}
a++;
b++;
}
return (*a == *b) ? 1 : 0;
}
static int s_is_sep(char c)
{
return ((c == '/') || (c == '\\')) ? 1 : 0;
}
/* 真实 ff.c create_name 非 LFN 分支的逐字模拟:主名 <=8、扩展 <=3、非法字符集。
* 返回 1 = 合法短名段。 */
/* 非法字符单独判,不用字符串表:ff.c 的表里带双引号与反斜杠,
* 写进主机侧字符串字面量既易错又踩本仓"字面量必须纯 ASCII 且不含双引号"的坑 */
static int s_sfn_bad(char c)
{
switch (c) {
case '*': case '+': case ',': case ';':
case '<': case '=': case '>':
case '[': case ']': case '|':
case '"': case '?': case 0x7F:
return 1;
default:
return 0;
}
}
static int s_sfn_ok(const char *seg, int len)
{
int i = 0;
int ni = 8;
int k;
if (len <= 0) {
return 0; /* 真实 ff.c:i == 0 -> FR_INVALID_NAME */
}
for (k = 0; k < len; k++) {
char c = seg[k];
if ((c == '.') || (i >= ni)) { /* 主名结束 / 字段溢出? */
if (ni == 11) {
return 0; /* 扩展域已开:再来点或再溢出 = 非法(.fschk 死在这) */
}
if (c != '.') {
return 0; /* 主名溢出 */
}
i = 8;
ni = 11; /* 进入扩展域 */
continue;
}
if (s_sfn_bad(c) != 0) {
return 0; /* 非法字符(ff.c create_name 的 strchr 表) */
}
if (s_is_sep(c)) {
return 0; /* 段里不该有分隔符(调用方已切段) */
}
i++;
}
return (i == 0) ? 0 : 1;
}
/* 归一化路径:剥盘符 + 剥前导分隔符 + 全大写 + 折叠重复分隔符 + 去尾部分隔符。
* 返回 FRESULT;out 形如 "/LOG/RUN.LOG",根为 "/"。
* 盘符判定照 ff.c get_ldnumber:本移植 FF_VOLUMES=1,只有 "0:" 合法,
* 其它 "N:" 形态(含路径中间的冒号)一律 FR_INVALID_DRIVE。 */
static FRESULT s_norm(const char *path, char *out, int out_sz)
{
int o = 0;
int i = 0;
const char *p = path;
if (path == NULL) {
return FR_INVALID_NAME;
}
/* 盘符前缀:到第一个 ':' 之前的整体必须是单个数字 0 */
{
const char *q = p;
while ((*q != '\0') && (*q != ':')) {
q++;
}
if (*q == ':') {
if ((q == p + 1) && (p[0] == '0')) {
p = q + 1;
} else {
return FR_INVALID_DRIVE;
}
}
}
out[0] = '/';
o = 1;
while (s_is_sep(*p)) {
p++;
}
while (*p != '\0') {
const char *seg;
int len;
while (s_is_sep(*p)) {
p++;
}
if (*p == '\0') {
break; /* 尾部多余分隔符:折叠掉 */
}
seg = p;
len = 0;
while ((p[len] != '\0') && (!s_is_sep(p[len]))) {
len++;
}
if (!s_sfn_ok(seg, len)) {
return FR_INVALID_NAME;
}
if ((o + len + 1) >= out_sz) {
return FR_INVALID_NAME; /* 路径过长:假件一律以名字类错误拒绝 */
}
if (i != 0) {
out[o++] = '/';
}
{
int k;
for (k = 0; k < len; k++) {
out[o++] = s_up(seg[k]);
}
}
out[o] = '\0';
i++;
p += len;
}
if (o == 1) {
return FR_INVALID_NAME; /* 空路径 = 卷根本身:真实 f_open 报 NS_NONAME */
}
return FR_OK;
}
/* 取父目录路径:"/LOG/RUN.LOG" -> "/LOG","/LOG" -> "/" */
static void s_parent(const char *norm, char *out)
{
const char *slash = strrchr(norm, '/');
if ((slash == NULL) || (slash == norm)) {
out[0] = '/';
out[1] = '\0';
} else {
int n = (int)(slash - norm);
(void)memcpy(out, norm, (size_t)n);
out[n] = '\0';
}
}
static int s_find(const char *norm)
{
int i;
for (i = 0; i < (int)FF_FAKE_MAX_ENT; i++) {
if ((s_ent[i].used != 0) && (strcmp(s_ent[i].path, norm) == 0)) {
return i;
}
}
return -1;
}
static int s_alloc(void)
{
int i;
for (i = 0; i < (int)FF_FAKE_MAX_ENT; i++) {
if (s_ent[i].used == 0) {
return i;
}
}
return -1;
}
static int s_has_children(const char *dir_norm)
{
int i;
int dl = (int)strlen(dir_norm);
for (i = 0; i < (int)FF_FAKE_MAX_ENT; i++) {
if (s_ent[i].used == 0) {
continue;
}
if (((int)strlen(s_ent[i].path) > dl) &&
(strncmp(s_ent[i].path, dir_norm, (size_t)dl) == 0) &&
(s_ent[i].path[dl] == '/')) {
return 1;
}
}
return 0;
}
/* 中间目录段必须存在(真实 follow_path:非末段找不到 -> FR_NO_PATH) */
static FRESULT s_check_parent(const char *norm)
{
char par[FF_FAKE_PATH_MAX];
int idx;
s_parent(norm, par);
if (strcmp(par, "/") == 0) {
return FR_OK;
}
idx = s_find(par);
if (idx < 0) {
return FR_NO_PATH;
}
if (s_ent[idx].is_dir == 0) {
return FR_NO_PATH; /* 真实 follow_path:不是子目录 -> FR_NO_PATH */
}
return FR_OK;
}
/* 命中注入则返回 1 并把 rc/n 带出 */
static int s_hit(int op, const char *norm, FRESULT *rc, int *act, UINT *n)
{
int i;
for (i = 0; i < (int)FF_FAKE_INJ_MAX; i++) {
if (s_inj[i].used == 0) {
continue;
}
if (s_inj[i].op != op) {
continue;
}
if ((s_inj[i].any_path == 0) && (!s_ieq(s_inj[i].path, norm))) {
continue;
}
if (s_inj[i].skip > 0) {
/* 路径匹配上了才算"放行一次"——不然别的路径/别的 op 会把 skip 吃掉,
* 注入永远打不到真正要打击的那一次调用 */
s_inj[i].skip--;
return 0;
}
*rc = s_inj[i].rc;
*act = s_inj[i].act;
*n = s_inj[i].n;
s_inj[i].times--;
if (s_inj[i].times <= 0) {
s_inj[i].used = 0;
}
return 1;
}
return 0;
}
/* 定长缓冲的字符串拷贝(不用 strncpy/strcpy:MSVC /W4 报 C4996 弃用,噪音) */
static void s_keep(char *dst, const char *src)
{
int i;
for (i = 0; (i < ((int)FF_FAKE_PATH_MAX) - 1) && (src[i] != '\0'); i++) {
dst[i] = src[i];
}
dst[i] = '\0';
}
/* 同 s_keep,但目标容量由调用方给(FILINFO.fname 是 13 字节,与路径上限不同) */
static void s_keep_n(char *dst, int dst_sz, const char *src)
{
int i;
for (i = 0; (i < (dst_sz - 1)) && (src[i] != '\0'); i++) {
dst[i] = src[i];
}
dst[i] = '\0';
}
/* ─────────────────────────── 世界控制 ─────────────────────────── */
void ff_fake_reset(void)
{
(void)memset(&g_ff_obs, 0, sizeof(g_ff_obs));
(void)memset(s_ent, 0, sizeof(s_ent));
(void)memset(s_inj, 0, sizeof(s_inj));
(void)memset(s_dirty, 0, sizeof(s_dirty));
s_dostime_d = 0x5A21u;
s_dostime_t = 0x6E40u;
}
void ff_fake_set_dostime(uint16_t fdate, uint16_t ftime)
{
s_dostime_d = fdate;
s_dostime_t = ftime;
}
static void s_add_inj_skip(const char *path, int op, int act, FRESULT rc, UINT n, int skip, int times)
{
int i;
char norm[FF_FAKE_PATH_MAX];
if (times <= 0) {
return;
}
if (path == NULL) {
for (i = 0; i < (int)FF_FAKE_INJ_MAX; i++) {
if (s_inj[i].used == 0) {
s_inj[i].used = 1;
s_inj[i].any_path = 1;
s_inj[i].op = op;
s_inj[i].act = act;
s_inj[i].rc = rc;
s_inj[i].n = n;
s_inj[i].times = times;
s_inj[i].skip = skip;
s_inj[i].path[0] = '\0';
return;
}
}
return;
}
/* 注入路径也走一遍归一化,好让大小写/前导斜杠的差异不影响命中 */
if (s_norm(path, norm, (int)sizeof(norm)) != FR_OK) {
norm[0] = '/';
norm[1] = '\0';
}
for (i = 0; i < (int)FF_FAKE_INJ_MAX; i++) {
if (s_inj[i].used == 0) {
s_inj[i].used = 1;
s_inj[i].any_path = 0;
s_inj[i].op = op;
s_inj[i].act = act;
s_inj[i].rc = rc;
s_inj[i].n = n;
s_inj[i].times = times;
s_inj[i].skip = skip;
s_keep(s_inj[i].path, norm);
return;
}
}
}
void ff_fake_inject_rc(const char *path, int op, FRESULT rc, int times)
{
s_add_inj_skip(path, op, FF_FAKE_ACT_RC, rc, 0u, 0, times);
}
/* 前 skip 次命中放行,之后 times 次注入 rc(见头文件注释的用途说明) */
void ff_fake_inject_rc_skip(const char *path, int op, FRESULT rc, int skip, int times)
{
s_add_inj_skip(path, op, FF_FAKE_ACT_RC, rc, 0u, skip, times);
}
void ff_fake_inject_write_short(const char *path, UINT n, int times)
{
s_add_inj_skip(path, FF_FAKE_OP_WRITE, FF_FAKE_ACT_WSHORT, FR_OK, n, 0, times);
}
void ff_fake_inject_read_short(const char *path, UINT n, int times)
{
s_add_inj_skip(path, FF_FAKE_OP_READ, FF_FAKE_ACT_RSHORT, FR_OK, n, 0, times);
}
/* ─────────────────────────── 卷直读直写 ─────────────────────────── */
int ff_fake_exists(const char *path)
{
char norm[FF_FAKE_PATH_MAX];
int idx;
if (s_norm(path, norm, (int)sizeof(norm)) != FR_OK) {
return 0;
}
idx = s_find(norm);
if (idx < 0) {
return 0;
}
return (s_ent[idx].is_dir != 0) ? 2 : 1;
}
uint32_t ff_fake_size(const char *path)
{
char norm[FF_FAKE_PATH_MAX];
int idx;
if (s_norm(path, norm, (int)sizeof(norm)) != FR_OK) {
return 0xFFFFFFFFu;
}
idx = s_find(norm);
return (idx < 0) ? 0xFFFFFFFFu : s_ent[idx].size;
}
uint32_t ff_fake_time_token(const char *path)
{
char norm[FF_FAKE_PATH_MAX];
int idx;
if (s_norm(path, norm, (int)sizeof(norm)) != FR_OK) {
return 0u;
}
idx = s_find(norm);
if (idx < 0) {
return 0u;
}
return ((uint32_t)s_ent[idx].fdate << 16) | (uint32_t)s_ent[idx].ftime;
}
uint32_t ff_fake_fetch(const char *path, uint8_t *p_buf, uint32_t off, uint32_t cap)
{
char norm[FF_FAKE_PATH_MAX];
int idx;
uint32_t n;
if ((p_buf == NULL) || (s_norm(path, norm, (int)sizeof(norm)) != FR_OK)) {
return 0u;
}
idx = s_find(norm);
if (idx < 0) {
return 0u;
}
if (off >= s_ent[idx].stored) {
return 0u;
}
n = s_ent[idx].stored - off;
if (n > cap) {
n = cap;
}
(void)memcpy(p_buf, &s_ent[idx].data[off], n);
return n;
}
int ff_fake_mkfile(const char *path, const uint8_t *p_data, uint32_t len)
{
char norm[FF_FAKE_PATH_MAX];
int idx;
if (s_norm(path, norm, (int)sizeof(norm)) != FR_OK) {
return -1;
}
idx = s_find(norm);
if (idx < 0) {
idx = s_alloc();
if (idx < 0) {
return -1;
}
s_ent[idx].used = 1;
s_ent[idx].is_dir = 0;
s_keep(s_ent[idx].path, norm);
}
if (len > FF_FAKE_CLUSTER) {
len = FF_FAKE_CLUSTER;
}
s_ent[idx].stored = len;
s_ent[idx].size = len;
if (len > 0u) {
if (p_data != NULL) {
(void)memcpy(s_ent[idx].data, p_data, len);
} else {
/* p_data == NULL = "只铺长度、内容清零":造"已有 N 条记录"的前置状态
* 时用这条,不必真把 N*64 字节写进池子 */
(void)memset(s_ent[idx].data, 0, len);
}
}
s_ent[idx].fdate = s_dostime_d;
s_ent[idx].ftime = s_dostime_t;
return 0;
}
/* 只给逻辑长度:造"已有 1024 条记录 = 65536 字节"这类前置状态,
* 内容不真存(簇池只有 2 KB),读到的超出部分按 0 处理 */
int ff_fake_mkfile_size(const char *path, uint32_t size)
{
if (ff_fake_mkfile(path, NULL, 0u) != 0) {
return -1;
}
{
char norm[FF_FAKE_PATH_MAX];
int idx;
(void)s_norm(path, norm, (int)sizeof(norm));
idx = s_find(norm);
if (idx < 0) {
return -1;
}
s_ent[idx].size = size;
s_ent[idx].stored = 0u;
}
return 0;
}
int ff_fake_mkdir_direct(const char *path)
{
char norm[FF_FAKE_PATH_MAX];
int idx;
if (s_norm(path, norm, (int)sizeof(norm)) != FR_OK) {
return -1;
}
idx = s_find(norm);
if (idx >= 0) {
s_ent[idx].is_dir = 1;
return 0;
}
idx = s_alloc();
if (idx < 0) {
return -1;
}
s_ent[idx].used = 1;
s_ent[idx].is_dir = 1;
s_ent[idx].size = 0u;
s_ent[idx].stored = 0u;
s_keep(s_ent[idx].path, norm);
return 0;
}
int ff_fake_count_entries(void)
{
int i;
int n = 0;
for (i = 0; i < (int)FF_FAKE_MAX_ENT; i++) {
if (s_ent[i].used != 0) {
n++;
}
}
return n;
}
/* ─────────────────────────── f_* 实现 ─────────────────────────── */
static void s_inval(FIL *fp)
{
fp->obj.fs = NULL; /* 真实 ff.c:失败即 fp->obj.fs = 0 */
fp->obj.sclust = 0u;
fp->obj.objsize = 0u;
fp->fptr = 0u;
fp->flag = 0u;
}
FRESULT f_open(FIL *fp, const TCHAR *path, BYTE mode)
{
char norm[FF_FAKE_PATH_MAX];
FRESULT fr;
FRESULT inj_rc = FR_OK;
int inj_act = 0;
UINT inj_n = 0u;
int hit = 0;
int idx;
s_guard(FF_FAKE_OP_OPEN);
if (fp == NULL) {
return FR_INVALID_OBJECT;
}
s_inval(fp);
fr = s_norm(path, norm, (int)sizeof(norm));
if (fr != FR_OK) {
g_ff_obs.last_open_rc = (int)fr;
return fr;
}
hit = s_hit(FF_FAKE_OP_OPEN, norm, &inj_rc, &inj_act, &inj_n);
if ((hit != 0) && (inj_act == FF_FAKE_ACT_RC)) {
g_ff_obs.last_open_mode = mode;
s_keep(g_ff_obs.last_open_path, norm);
g_ff_obs.last_open_rc = (int)inj_rc;
return inj_rc;
}
g_ff_obs.last_open_mode = mode;
s_keep(g_ff_obs.last_open_path, norm);
idx = s_find(norm);
if (idx >= 0) { /* 对象已存在 */
if ((mode & FA_CREATE_NEW) != 0u) {
g_ff_obs.last_open_rc = (int)FR_EXIST;
return FR_EXIST; /* 真实 f_open:Cannot create as new file */
}
if (s_ent[idx].is_dir != 0) { /* 对目录本身开文件 */
if ((mode & FF_FAKE_CREATE_MASK) != 0u) {
g_ff_obs.last_open_rc = (int)FR_DENIED; /* 真实:Cannot overwrite (DIR) */
return FR_DENIED;
}
g_ff_obs.last_open_rc = (int)FR_NO_FILE; /* 真实 f_open:3850 */
return FR_NO_FILE;
}
if ((mode & FA_CREATE_ALWAYS) != 0u) {
s_ent[idx].size = 0u; /* 截零(真实 ff.c 复位目录项 + 删簇链) */
s_ent[idx].stored = 0u;
s_dirty[idx] = 1u;
}
} else { /* 对象不存在 */
/* 真实 ff.c:中间目录段不存在时 follow_path 就已经是 FR_NO_PATH 了,
* **先判父目录再判创建位**,否则 "/log 未建 + 只给 FA_READ" 会被误报成
* FR_NO_FILE,被测代码的自愈分支就走不到 */
fr = s_check_parent(norm);
if (fr != FR_OK) {
g_ff_obs.last_open_rc = (int)fr;
return fr;
}
if ((mode & FF_FAKE_CREATE_MASK) == 0u) {
g_ff_obs.last_open_rc = (int)FR_NO_FILE;
return FR_NO_FILE;
}
idx = s_alloc();
if (idx < 0) {
g_ff_obs.last_open_rc = (int)FR_DENIED; /* 目录满(真实 dir_register) */
return FR_DENIED;
}
s_ent[idx].used = 1;
s_ent[idx].is_dir = 0;
s_ent[idx].size = 0u;
s_ent[idx].stored = 0u;
s_keep(s_ent[idx].path, norm);
s_dirty[idx] = 1u;
}
/* 句柄:mode 原值进 flag(真实 ff.c 同做法),因此"只给 FA_OPEN_APPEND"
* 的句柄在 f_write 里必然拿 FR_DENIED —— 陷阱一的复刻度量 */
fp->obj.fs = FF_FAKE_FS_MAGIC;
fp->obj.sclust = (DWORD)idx + 1u;
fp->obj.objsize = (FSIZE_t)s_ent[idx].size;
fp->flag = mode;
fp->err = 0u;
fp->fptr = ((mode & FF_FAKE_SEEKEND) != 0u) ? (FSIZE_t)s_ent[idx].size : 0u;
g_ff_obs.last_open_rc = (int)FR_OK;
return FR_OK;
}
static int s_chk(FIL *fp, int *p_idx)
{
if ((fp == NULL) || (fp->obj.fs != FF_FAKE_FS_MAGIC)) {
return 0; /* FR_INVALID_OBJECT */
}
if ((fp->obj.sclust == 0u) || (fp->obj.sclust > (DWORD)FF_FAKE_MAX_ENT)) {
return 0;
}
*p_idx = (int)fp->obj.sclust - 1;
if (s_ent[*p_idx].used == 0) {
return 0;
}
return 1;
}
FRESULT f_write(FIL *fp, const void *p_buff, UINT btw, UINT *bw)
{
FRESULT fr;
FRESULT inj_rc = FR_OK;
int inj_act = 0;
UINT inj_n = 0u;
int hit = 0;
int idx = -1;
uint32_t pos;
uint32_t room;
uint32_t n;
if (bw != NULL) {
*bw = 0u;
}
s_guard(FF_FAKE_OP_WRITE);
g_ff_obs.last_write_btw = btw;
g_ff_obs.last_write_bw = 0u;
if (!s_chk(fp, &idx)) {
g_ff_obs.last_write_rc = (int)FR_INVALID_OBJECT;
return FR_INVALID_OBJECT;
}
/* 陷阱一:只给 FA_OPEN_APPEND(0x30) 不含 FA_WRITE(0x02) -> 恒返 FR_DENIED */
if ((fp->flag & FA_WRITE) == 0u) {
g_ff_obs.last_write_rc = (int)FR_DENIED;
return FR_DENIED;
}
hit = s_hit(FF_FAKE_OP_WRITE, s_ent[idx].path, &inj_rc, &inj_act, &inj_n);
if ((hit != 0) && (inj_act == FF_FAKE_ACT_RC)) {
g_ff_obs.last_write_rc = (int)inj_rc;
return inj_rc;
}
if (p_buff == NULL) {
g_ff_obs.last_write_rc = (int)FR_INT_ERR;
return FR_INT_ERR;
}
pos = (uint32_t)fp->fptr;
/* pos 可能已经越过簇池上沿(ff_fake_mkfile_size 造的"已有 64 KB 记录"就是这种),
* 等价于"一个簇都分配不出来":短写到 0 字节,仍然 FR_OK */
room = (pos >= FF_FAKE_CLUSTER) ? 0u : (FF_FAKE_CLUSTER - pos);
n = btw;
if ((hit != 0) && (inj_act == FF_FAKE_ACT_WSHORT)) {
if (n > inj_n) {
n = inj_n; /* 定点短写:恰好写 inj_n 字节后"当成功"返回 */
}
}
if (n > room) {
n = room; /* 真实 ff.c:4102 clst == 0 -> break:FR_OK + 短写 */
}
if (n > 0u) {
(void)memcpy(&s_ent[idx].data[pos], p_buff, n);
if ((pos + n) > s_ent[idx].stored) {
s_ent[idx].stored = pos + n;
}
}
fp->fptr = (FSIZE_t)(pos + n);
if (fp->fptr > fp->obj.objsize) {
fp->obj.objsize = fp->fptr;
}
s_ent[idx].size = (uint32_t)fp->obj.objsize;
s_dirty[idx] = 1u;
if (bw != NULL) {
*bw = n;
}
g_ff_obs.last_write_bw = n;
g_ff_obs.last_write_rc = (int)FR_OK;
fr = FR_OK; /* 短写也是 FR_OK:判失败只能靠 *bw(§10.1 第 11 条) */
return fr;
}
FRESULT f_read(FIL *fp, void *p_buff, UINT btr, UINT *br)
{
FRESULT inj_rc = FR_OK;
int inj_act = 0;
UINT inj_n = 0u;
int hit = 0;
int idx = -1;
uint32_t pos;
uint32_t remain;
uint32_t n;
if (br != NULL) {
*br = 0u;
}
s_guard(FF_FAKE_OP_READ);
g_ff_obs.last_read_btr = btr;
g_ff_obs.last_read_br = 0u;
if (!s_chk(fp, &idx)) {
return FR_INVALID_OBJECT;
}
if ((fp->flag & FA_READ) == 0u) { /* 真实 f_read:Check access mode */
return FR_DENIED;
}
hit = s_hit(FF_FAKE_OP_READ, s_ent[idx].path, &inj_rc, &inj_act, &inj_n);
if ((hit != 0) && (inj_act == FF_FAKE_ACT_RC)) {
return inj_rc;
}
if (p_buff == NULL) {
return FR_INT_ERR;
}
pos = (uint32_t)fp->fptr;
remain = ((uint32_t)fp->obj.objsize > pos) ? ((uint32_t)fp->obj.objsize - pos) : 0u;
n = btr;
if (n > remain) {
n = remain; /* 真实 f_read:btr 被 remain 截断后照常 FR_OK */
}
if ((hit != 0) && (inj_act == FF_FAKE_ACT_RSHORT) && (n > inj_n)) {
n = inj_n;
}
{
uint32_t k;
uint8_t *dst = (uint8_t *)p_buff;
for (k = 0u; k < n; k++) {
dst[k] = ((pos + k) < s_ent[idx].stored) ? s_ent[idx].data[pos + k] : 0u;
}
}
fp->fptr = (FSIZE_t)(pos + n);
if (br != NULL) {
*br = n;
}
g_ff_obs.last_read_br = n;
return FR_OK;
}
FRESULT f_lseek(FIL *fp, FSIZE_t ofs)
{
int idx = -1;
uint32_t want = (uint32_t)ofs;
s_guard(FF_FAKE_OP_LSEEK);
g_ff_obs.last_lseek_ofs = ofs;
if (!s_chk(fp, &idx)) {
return FR_INVALID_OBJECT;
}
if (want > (uint32_t)fp->obj.objsize) {
if ((fp->flag & FA_WRITE) == 0u) {
want = (uint32_t)fp->obj.objsize; /* 只读句柄:钳到文件尾,不报错 */
} else if (want > FF_FAKE_CLUSTER) {
return FR_DENIED; /* 可写但要超出簇池 = 簇分配不出来 */
} else {
s_ent[idx].size = want; /* 可写句柄:扩展(真实 ff.c 补零簇链) */
fp->obj.objsize = (FSIZE_t)want;
s_dirty[idx] = 1u;
}
}
fp->fptr = (FSIZE_t)want;
return FR_OK;
}
FRESULT f_close(FIL *fp)
{
int idx = -1;
FRESULT inj_rc = FR_OK;
int inj_act = 0;
UINT inj_n = 0u;
int hit = 0;
s_guard(FF_FAKE_OP_CLOSE);
if (!s_chk(fp, &idx)) {
return FR_INVALID_OBJECT;
}
hit = s_hit(FF_FAKE_OP_CLOSE, s_ent[idx].path, &inj_rc, &inj_act, &inj_n);
if ((hit != 0) && (inj_act == FF_FAKE_ACT_RC)) {
return inj_rc; /* 真实 f_close 会因 sync 失败返错且释放句柄 */
}
if (s_dirty[idx] != 0u) { /* 真实 f_close:sync 里用 get_fattime 回写目录项 */
s_ent[idx].fdate = s_dostime_d;
s_ent[idx].ftime = s_dostime_t;
s_dirty[idx] = 0u;
}
s_inval(fp);
return FR_OK;
}
FRESULT f_stat(const TCHAR *path, FILINFO *fno)
{
char norm[FF_FAKE_PATH_MAX];
FRESULT fr;
FRESULT inj_rc = FR_OK;
int inj_act = 0;
UINT inj_n = 0u;
int hit = 0;
int idx;
s_guard(FF_FAKE_OP_STAT);
fr = s_norm(path, norm, (int)sizeof(norm));
if (fr != FR_OK) {
return fr;
}
hit = s_hit(FF_FAKE_OP_STAT, norm, &inj_rc, &inj_act, &inj_n);
if ((hit != 0) && (inj_act == FF_FAKE_ACT_RC)) {
return inj_rc;
}
idx = s_find(norm);
if (idx < 0) {
/* 真实 follow_path:末段找不到 -> FR_NO_FILE,但**中间段**找不到 -> FR_NO_PATH */
fr = s_check_parent(norm);
return (fr == FR_OK) ? FR_NO_FILE : fr;
}
if (fno != NULL) {
(void)memset(fno, 0, sizeof(*fno));
fno->fsize = (FSIZE_t)s_ent[idx].size;
fno->fdate = s_ent[idx].fdate;
fno->ftime = s_ent[idx].ftime;
fno->fattrib = (s_ent[idx].is_dir != 0) ? AM_DIR : AM_ARC;
{
const char *base = strrchr(norm, '/');
base = (base != NULL) ? (base + 1) : norm;
s_keep_n(fno->fname, (int)sizeof(fno->fname), base);
}
}
return FR_OK;
}
FRESULT f_unlink(const TCHAR *path)
{
char norm[FF_FAKE_PATH_MAX];
FRESULT fr;
FRESULT inj_rc = FR_OK;
int inj_act = 0;
UINT inj_n = 0u;
int hit = 0;
int idx;
s_guard(FF_FAKE_OP_UNLINK);
fr = s_norm(path, norm, (int)sizeof(norm));
if (fr != FR_OK) {
return fr;
}
s_keep(g_ff_obs.last_unlink_path, norm);
hit = s_hit(FF_FAKE_OP_UNLINK, norm, &inj_rc, &inj_act, &inj_n);
if ((hit != 0) && (inj_act == FF_FAKE_ACT_RC)) {
return inj_rc;
}
idx = s_find(norm);
if (idx < 0) {
fr = s_check_parent(norm);
return (fr == FR_OK) ? FR_NO_FILE : fr;
}
if ((s_ent[idx].is_dir != 0) && (s_has_children(norm) != 0)) {
return FR_DENIED; /* 真实 f_unlink:目录非空不可删 */
}
s_ent[idx].used = 0;
s_dirty[idx] = 0u;
return FR_OK;
}
FRESULT f_mkdir(const TCHAR *path)
{
char norm[FF_FAKE_PATH_MAX];
FRESULT fr;
FRESULT inj_rc = FR_OK;
int inj_act = 0;
UINT inj_n = 0u;
int hit = 0;
int idx;
s_guard(FF_FAKE_OP_MKDIR);
fr = s_norm(path, norm, (int)sizeof(norm));
if (fr != FR_OK) {
return fr;
}
s_keep(g_ff_obs.last_mkdir_path, norm);
hit = s_hit(FF_FAKE_OP_MKDIR, norm, &inj_rc, &inj_act, &inj_n);
if ((hit != 0) && (inj_act == FF_FAKE_ACT_RC)) {
return inj_rc;
}
if (s_find(norm) >= 0) {
return FR_EXIST; /* 真实 f_mkdir:已存在 -> FR_EXIST */
}
fr = s_check_parent(norm);
if (fr != FR_OK) {
return fr;
}
idx = s_alloc();
if (idx < 0) {
return FR_DENIED;
}
s_ent[idx].used = 1;
s_ent[idx].is_dir = 1;
s_ent[idx].size = 0u;
s_ent[idx].stored = 0u;
s_ent[idx].fdate = s_dostime_d;
s_ent[idx].ftime = s_dostime_t;
s_keep(s_ent[idx].path, norm);
return FR_OK;
}
FRESULT f_rename(const TCHAR *path_old, const TCHAR *path_new)
{
char nold[FF_FAKE_PATH_MAX];
char nnew[FF_FAKE_PATH_MAX];
FRESULT fr;
FRESULT inj_rc = FR_OK;
int inj_act = 0;
UINT inj_n = 0u;
int hit = 0;
int idx;
int dup;
s_guard(FF_FAKE_OP_RENAME);
fr = s_norm(path_old, nold, (int)sizeof(nold));
if (fr != FR_OK) {
return fr;
}
fr = s_norm(path_new, nnew, (int)sizeof(nnew));
if (fr != FR_OK) {
return fr;
}
s_keep(g_ff_obs.last_rename_old, nold);
s_keep(g_ff_obs.last_rename_new, nnew);
hit = s_hit(FF_FAKE_OP_RENAME, nold, &inj_rc, &inj_act, &inj_n);
if ((hit != 0) && (inj_act == FF_FAKE_ACT_RC)) {
return inj_rc;
}
idx = s_find(nold);
if (idx < 0) {
fr = s_check_parent(nold);
return (fr == FR_OK) ? FR_NO_FILE : fr;
}
dup = s_find(nnew);
if (dup >= 0) {
/* 台架实证的根因:本移植(与上游同语义)"new name already in use"
* 一律 FR_EXIST、不覆盖。原子写因此必须先 unlink 目标再 rename。 */
return FR_EXIST;
}
fr = s_check_parent(nnew);
if (fr != FR_OK) {
return fr;
}
if (s_ent[idx].is_dir != 0) { /* 目录改名:同步搬走全部子项 */
int dl = (int)strlen(nold);
int i;
for (i = 0; i < (int)FF_FAKE_MAX_ENT; i++) {
if ((s_ent[i].used == 0) || (i == idx)) {
continue;
}
if (((int)strlen(s_ent[i].path) > dl) &&
(strncmp(s_ent[i].path, nold, (size_t)dl) == 0) &&
(s_ent[i].path[dl] == '/')) {
char tmp[FF_FAKE_PATH_MAX];
int m;
for (m = 0; nnew[m] != '\0'; m++) {
tmp[m] = nnew[m];
}
s_keep_n(&tmp[m], (int)sizeof(tmp) - m, &s_ent[i].path[dl]);
s_keep(s_ent[i].path, tmp);
}
}
}
s_keep(s_ent[idx].path, nnew);
s_ent[idx].fdate = s_dostime_d;
s_ent[idx].ftime = s_dostime_t;
return FR_OK;
}