/* * 模块名称:假 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 #include #include #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; }