232 lines
8.7 KiB
C
232 lines
8.7 KiB
C
/* 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 */
|