Files

232 lines
8.7 KiB
C
Raw Permalink Normal View History

2026-08-05 13:58:13 +08:00
/* fat32_lfn.h — Deshab FAT32 VFAT 长文件名(LFN)解析共享库(static inline,无库)
*
* 前置条件:包含者已定义 u8/u16/u32 基础类型(与 fat32_io.h 一致)。
*
* FAT32 LFN 原理:
* 目录项中 attr==0x0F 的是 LFN 片段项。每项含 13 个 UTF-16 LE 字符
* (name1[5] + name2[6] + name3[2]),按逆序存储——序号最高(带 0x40
* 标志)的项最先出现,序号递减,最后紧跟一个 8.3 短目录项作为"主项"。
* 短名项的 checksum(由 name11 计算)用于校验整条 LFN 链完整性。
*
* 用法(在目录遍历循环中):
* fat32_lfn_buf lfn;
* fat32_lfn_init(&lfn);
* // 外层簇遍历循环——lfn 必须跨簇存活(LFN 链可能跨簇边界)
* while (遍历目录簇) {
* for (每个 32 字节目录项 e) {
* if (name[0]==0) 目录结束;
* if (name[0]==0xE5) { fat32_lfn_init(&lfn); continue; }
* int is_short = fat32_lfn_process(&lfn, entry_ptr);
* if (is_short) {
* // lfn.valid==1 时有完整 LFN,可 fat32_lfn_to_ascii 取名
* // 用 fat32_lfn_match 匹配长名或 8.3 短名
* }
* }
* }
*
* 注意:fat32_lfn_buf 必须在簇遍历循环**外**声明,跨簇切换时不清空,
* 以支持跨簇边界的 LFN 链。
*/
#ifndef DESHAB_FAT32_LFN_H
#define DESHAB_FAT32_LFN_H
/* LFN 最大字符数(FAT32 规范最多 20 个 LFN 项 = 260 字符) */
#define FAT32_LFN_MAX 260
/* LFN 收集缓冲(栈/局部变量分配) */
typedef struct {
u16 chars[FAT32_LFN_MAX]; /* UTF-16 LE 累积字符(正序) */
int count; /* 有效字符数(不含 0x0000 终止符/0xFFFF 填充) */
u8 checksum; /* LFN 链 checksum(来自 0x40 标志项) */
int valid; /* 1=LFN 链完整且 checksum 与短名项匹配 */
} fat32_lfn_buf;
/* 初始化/清空 LFN 缓冲 */
static inline void fat32_lfn_init(fat32_lfn_buf *buf) {
buf->count = 0;
buf->valid = 0;
buf->checksum = 0;
for (int i = 0; i < FAT32_LFN_MAX; i++) buf->chars[i] = 0;
}
/* 计算 8.3 短名(11 字节)的 LFN checksum。
* 标准 FAT32 算法:cksum = ((cksum & 1) << 7) + (cksum >> 1) + name[i] */
static inline u8 fat32_lfn_checksum(const u8 name11[11]) {
u8 cksum = 0;
for (int i = 0; i < 11; i++) {
cksum = (u8)(((cksum & 1) << 7) + (cksum >> 1) + name11[i]);
}
return cksum;
}
/* 处理一个 32 字节目录项。自动判断 LFN 项 / 短名项 / 已删除项。
*
* 调用方对目录遍历中的每个条目(跳过 name[0]==0 结束符后)调用此函数。
* - LFN 项(attr==0x0F):收集 13 个 UTF-16 字符到 buf,返回 0
* - 已删除项(name[0]==0xE5):清空 buf,返回 0
* - 短名项:校验 checksum,设 buf->valid,返回 1
*
* 返回 1=刚处理的是短名项(此时可检查 buf->valid 判断是否有有效 LFN),
* 0=LFN 项或已删除项。
*
* 注意:遇到带 0x40 标志的 LFN 项(链开始)时自动重置 buf。
* 遇到已删除项时自动清空 buf(防止残留 LFN 污染下一个文件)。 */
static inline int fat32_lfn_process(fat32_lfn_buf *buf, const u8 *entry) {
u8 attr = entry[11];
/* 已删除项:清空 LFN 缓冲 */
if (entry[0] == 0xE5) {
fat32_lfn_init(buf);
return 0;
}
/* LFN 项(attr == 0x0F) */
if (attr == 0x0F) {
u8 seq = entry[0];
u8 ord = (u8)(seq & 0x3F); /* 序号 1-20 */
if (ord == 0 || ord > 20) {
fat32_lfn_init(buf);
return 0;
}
/* 带 0x40 标志 = LFN 链开始(最高序号项),重置缓冲 */
if (seq & 0x40) {
int prev_cksum = buf->checksum;
fat32_lfn_init(buf);
buf->checksum = entry[13];
(void)prev_cksum;
}
/* 提取 13 个 UTF-16 LE 字符,放到 (ord-1)*13 位置(正序) */
int base = (ord - 1) * 13;
if (base + 13 > FAT32_LFN_MAX) {
fat32_lfn_init(buf);
return 0;
}
/* name1: entry[1..10] = 5 个 UTF-16 LE 字符 */
for (int i = 0; i < 5; i++) {
u16 c = (u16)entry[1 + i * 2] | ((u16)entry[2 + i * 2] << 8);
buf->chars[base + i] = c;
}
/* name2: entry[14..25] = 6 个 UTF-16 LE 字符 */
for (int i = 0; i < 6; i++) {
u16 c = (u16)entry[14 + i * 2] | ((u16)entry[15 + i * 2] << 8);
buf->chars[base + 5 + i] = c;
}
/* name3: entry[28..31] = 2 个 UTF-16 LE 字符 */
for (int i = 0; i < 2; i++) {
u16 c = (u16)entry[28 + i * 2] | ((u16)entry[29 + i * 2] << 8);
buf->chars[base + 11 + i] = c;
}
return 0; /* LFN 项,还不是短名项 */
}
/* 短名项:校验 checksum */
u8 cksum = fat32_lfn_checksum(entry);
if (buf->checksum != 0 && buf->checksum == cksum) {
/* LFN 链有效——计算实际字符数(截断于 0x0000 终止符或 0xFFFF 填充符) */
int n = 0;
for (int i = 0; i < FAT32_LFN_MAX; i++) {
if (buf->chars[i] == 0x0000 || buf->chars[i] == 0xFFFF) break;
n++;
}
buf->count = n;
buf->valid = 1;
} else {
/* checksum 不匹配或无 LFN 链:清空,回退 8.3 */
fat32_lfn_init(buf);
}
return 1; /* 短名项处理完毕 */
}
/* 从 LFN 缓冲提取 ASCII 名(UTF-16→ASCII 缩窄,仅 BMP U+0000..U+007F)。
* out_cap 为 out 缓冲容量(含 NUL)。
* 返回字符串长度(不含 NUL),-1=无有效 LFN 或含非 ASCII 字符。 */
static inline int fat32_lfn_to_ascii(const fat32_lfn_buf *buf, char *out, int out_cap) {
if (!buf->valid || buf->count == 0 || out_cap <= 0) return -1;
int n = buf->count;
if (n >= out_cap) n = out_cap - 1;
for (int i = 0; i < n; i++) {
u16 c = buf->chars[i];
if (c > 0x7F) return -1; /* 含非 ASCII 字符 */
out[i] = (char)(u8)c;
}
out[n] = 0;
return n;
}
/* ASCII 大小写不敏感字符串比较 */
static inline int fat32_lfn_streq_ci(const char *a, const char *b) {
if (!a || !b) return 0;
while (*a && *b) {
char ca = *a, cb = *b;
if (ca >= 'a' && ca <= 'z') ca = (char)(ca - 32);
if (cb >= 'a' && cb <= 'z') cb = (char)(cb - 32);
if (ca != cb) return 0;
a++; b++;
}
return *a == *b;
}
/* 判断 ASCII 字符串是否以指定后缀结尾(大小写不敏感)。
* 例如 fat32_lfn_ends_with_ci("libtest.so", ".so") → 1 */
static inline int fat32_lfn_ends_with_ci(const char *s, const char *suffix) {
if (!s || !suffix) return 0;
int sl = 0, xl = 0;
while (s[sl]) sl++;
while (suffix[xl]) xl++;
if (xl > sl || xl == 0) return 0;
for (int i = 0; i < xl; i++) {
char a = s[sl - xl + i];
char b = suffix[i];
if (a >= 'a' && a <= 'z') a = (char)(a - 32);
if (b >= 'a' && b <= 'z') b = (char)(b - 32);
if (a != b) return 0;
}
return 1;
}
/* 匹配目录项:优先匹配 LFN 长名(大小写不敏感),回退 8.3 短名。
* buf : LFN 收集缓冲(短名项处理后)
* name11 : 短名项的 11 字节 8.3 名
* target_long: 目标长名(ASCII,可为 NULL=不匹配长名)
* target83 : 目标 8.3 名(11 字节,可为 NULL=不匹配 8.3)
* 返回 1=匹配,0=不匹配。
* FAT32 规范:文件名查找大小写不敏感。 */
static inline int fat32_lfn_match(const fat32_lfn_buf *buf, const u8 name11[11],
const char *target_long, const char *target83) {
/* 1. 优先匹配 LFN 长名 */
if (buf->valid && buf->count > 0 && target_long) {
char ascii[FAT32_LFN_MAX];
int n = fat32_lfn_to_ascii(buf, ascii, sizeof(ascii));
if (n >= 0 && fat32_lfn_streq_ci(ascii, target_long)) return 1;
}
/* 2. 回退 8.3 短名匹配(精确比较 11 字节) */
if (target83 && name11) {
for (int i = 0; i < 11; i++) {
if (name11[i] != (u8)target83[i]) return 0;
}
return 1;
}
return 0;
}
/* 从 8.3 短名(11 字节)生成可显示 ASCII 字符串(如 "README.TXT")。
* 与 fat32_io.h 的 f32_name_from_83 功能相同,独立提供以避免循环依赖。 */
static inline void fat32_lfn_short_to_str(const u8 name11[11], char out[13]) {
int p = 0;
for (int j = 0; j < 8; j++) {
if (name11[j] == ' ') break;
out[p++] = (char)name11[j];
}
if (name11[8] != ' ') {
out[p++] = '.';
for (int j = 8; j < 11; j++) {
if (name11[j] == ' ') break;
out[p++] = (char)name11[j];
}
}
out[p] = 0;
}
#endif /* DESHAB_FAT32_LFN_H */