当前位置: 首页 > news >正文

实现FAT12文件管理

文章目录

  • 1. 实现FAT12文件管理
  • 2. 功能1:打印当前目录下所有文件和目录名
    • 2.1 实现思路及伪代码
      • 2.1.1 实现思路
      • 2.1.2 伪代码
    • 2.2 完整的源代码
    • 2.3 测试
  • 3. 功能2:打印文件/目录的文件控制块
    • 3.1 实现思路及伪代码
      • 3.1.1 实现思路
      • 3.1.2 伪代码
    • 3.2 完整的源代码
    • 3.3 测试
  • 4. 功能3:打印整个文件分配表
    • 4.1 实现思路及伪代码
      • 4.1.1 实现思路
      • 4.1.2 伪代码
    • 4.2 完整的源代码
    • 4.3 测试
  • 5. 功能4:切换目录
    • 5.1 实现思路及伪代码
      • 5.1.1 实现思路
      • 5.1.2 伪代码
    • 5.2 完整的源代码
    • 5.3 测试
  • 6. 功能5:创建目录、创建文件
    • 6.1 mkdir实现思路及伪代码
      • 6.1.1 实现思路
      • 6.1.2 伪代码
    • 6.2 touch实现思路及伪代码
      • 6.2.1 实现思路
      • 6.2.2 伪代码
    • 6.2 完整的源代码
      • 6.2.1 void mkdir(const char *dir)
      • 6.2.2 void touch(const char *filename)
    • 6.3 测试
  • 7. 功能6:删除目录、删除文件
    • 7.1 实现思路及伪代码
      • 7.1.1 实现思路
      • 7.1.2 伪代码
    • 7.2 完整的源代码
    • 7.3 测试
  • 8. 挑战性任务:实现FAT12格式化
    • 8.1 完整的源代码
    • 8.2 测试结果
  • 9. 附录代码
    • 9.1 fs.c
  • 10.参考链接
    • FAT12文件系统结构介绍
    • 实现FAT12

1. 实现FAT12文件管理

已实现如下功能,并测试正确:

  • 打印当前目录下所有文件和目录名
  • 打印文件/目录的文件控制块
  • 打印整个文件分配表
  • 切换目录
  • 创建目录、创建文件
  • 删除目录、删除文件

2. 功能1:打印当前目录下所有文件和目录名

2.1 实现思路及伪代码

2.1.1 实现思路

ls 函数用于列出目录内容或显示单个文件信息,基于 Windows API 和 FAT12 文件系统:

  1. 路径处理:若 dir 为空,构建当前目录路径;否则构建指定路径。检查路径是否有效,若是文件则显示信息并返回,若是目录则追加 * 准备枚举。
  2. 目录枚举:用 FindFirstFile 和 FindNextFile 遍历目录,跳过 . 和 …。
  3. 信息显示:为每个文件/目录生成属性字符串(D/F/R/H),根据大小(nFileSizeHigh/nFileSizeLow)显示字节或 GB,格式化输出。
  4. 资源清理:遍历后关闭。

2.1.2 伪代码

函数 ls(目录路径 dir)如果 dir 为空full_path = 当前目录路径search_path = full_path + "*"否则full_path = 构建路径(dir)如果 full_path 无效打印 "无法访问"返回如果 full_path 是文件显示文件信息(full_path)返回search_path = full_path + "\*"hFind = FindFirstFile(search_path)如果 hFind 无效打印 "目录为空或无法访问"返回打印表头 "名称 属性 大小"循环 FindNextFile(hFind, findData)如果 findData.cFileName 是 "." 或 ".."继续attr_str = ""如果 findData 是目录attr_str += "D"否则attr_str += "F"如果 findData 只读attr_str += "R"如果 findData 隐藏attr_str += "H"打印 findData.cFileName, attr_str, 大小(高位>0 ? GB : bytes)FindClose(hFind)
结束函数

2.2 完整的源代码

void ls(const char *dir)
{char search_path[MAX_PATH];char full_path[MAX_PATH];if (strlen(dir) == 0){// 列出当前目录build_full_path(full_path, "");strcpy(search_path, full_path);strcat(search_path, "*");}else{// 列出指定目录build_full_path(full_path, dir);// 检查是否是目录DWORD attrs = GetFileAttributes(full_path);if (attrs == INVALID_FILE_ATTRIBUTES){printf("无法访问: %s\n", dir);return;}if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)){// 是文件,直接显示该文件信息display_file_info(full_path);return;}// 确保目录路径以反斜杠结尾if (full_path[strlen(full_path) - 1] != '\\'){strcat(full_path, "\\");}strcpy(search_path, full_path);strcat(search_path, "*");}WIN32_FIND_DATA findData;HANDLE hFind = FindFirstFile(search_path, &findData);if (hFind == INVALID_HANDLE_VALUE){printf("目录为空或无法访问\n");return;}printf("名称\t\t\t属性\t大小\n");printf("----------------------------------------\n");do{// 跳过 . 和 .. 目录if (strcmp(findData.cFileName, ".") == 0 || strcmp(findData.cFileName, "..") == 0){continue;}char attr_str[10] = "";if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){strcat(attr_str, "D");}else{strcat(attr_str, "F");}if (findData.dwFileAttributes & FILE_ATTRIBUTE_READONLY){strcat(attr_str, "R");}if (findData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN){strcat(attr_str, "H");}// 修复格式化字符串问题DWORD fileSizeHigh = findData.nFileSizeHigh;DWORD fileSizeLow = findData.nFileSizeLow;printf("%-20s\t%-5s\t", findData.cFileName, attr_str);if (fileSizeHigh > 0){printf("%u GB+\n", fileSizeHigh);}else{printf("%u bytes\n", fileSizeLow);}} while (FindNextFile(hFind, &findData));FindClose(hFind);
}

2.3 测试

ls
名称                    属性    大小
----------------------------------------
System Volume Information       DH      0 bytes
test-2                  D       0 bytes
cd test-2
当前目录: \test-2
ls
名称                    属性    大小
----------------------------------------
disk.c                  F       6002 bytes
disk.h                  F       1466 bytes
fs.c                    F       21604 bytes
fs.h                    F       268 bytes
main.c                  F       2425 bytes
README.md               F       8286 bytes
types.h                 F       132 bytes
11.py                   F       763 bytes
fat12.exe               F       74998 bytes

3. 功能2:打印文件/目录的文件控制块

3.1 实现思路及伪代码

3.1.1 实现思路

print_fcb 函数用于打印指定文件或目录的 FAT12 文件控制块(FCB)信息,基于 Windows API 和 FAT12 文件系统。

  1. 输入验证与路径构建

    • 检查输入路径 path 是否为空,若为空则报错并返回。
    • 使用 build_full_path 构建完整路径 full_path,并通过 FindFirstFile 验证路径是否存在,若不存在则报错并返回。
  2. 查找 FCB

    • 首先在根目录中查找文件/目录的 FCB:
      • 调用 read_directory不从属于根目录的子目录中查找。
      • 比较文件名(忽略大小写)以匹配目标文件。
    • 若未在根目录找到且当前路径非根目录,则尝试在当前目录查找:
      • 找到当前目录的 FCB,获取其起始簇号,计算偏移并读取目录内容。
      • 在子目录中查找目标文件。
    • 若仍未找到,报错并返回。
  3. 打印 FCB 信息

    • 以 16 进制格式打印 FCB 的 32 字节数据。
    • 解析并显示文件名(8.3 格式)、属性、起始簇号和文件大小。
    • 若起始簇号非 0,打印簇链,追踪 FAT 表中的下一簇,直到文件结束(EOF)、坏簇或限制(20 个簇)。
  4. 资源清理

    • 关闭 FindFirstFile 的句柄。

3.1.2 伪代码

函数 print_fcb(路径 path)如果 path 为空打印 "错误: 需要路径"返回full_path = 构建路径(path)hFind = FindFirstFile(full_path)如果 hFind 无效打印 "文件或目录不存在"返回关闭 hFindentries = 读取根目录(get_root_dir_offset(), get_root_dir_size())filename = 获取文件名(path)target_entry = NULL遍历 entriesentry_name = 格式化文件名(entries[i])如果 entry_name 匹配 filename(忽略大小写)target_entry = entries[i]打印 "在根目录找到: filename"退出循环如果 target_entry 为空 且 当前路径不是根目录遍历 entries如果 entries[i] 是目录 且 匹配当前路径dir_cluster = entries[i].start_clusterdir_offset = cluster_to_offset(dir_cluster)dir_entries = 读取目录(dir_offset, 簇大小)遍历 dir_entries如果 dir_entries[j] 匹配 filenametarget_entry = dir_entries[j]打印 "在当前目录找到: filename"退出循环退出循环如果 target_entry 为空打印 "无法找到FCB: path"返回打印 "FCB信息 (16进制)"遍历 target_entry 的 32 字节打印每字节的 16 进制值,格式化每行 16 字节打印 "详细解析"formatted_name = 格式化文件名(target_entry)打印 formatted_name打印 8.3 格式文件名打印属性(target_entry.attr)打印起始簇号(target_entry.start_cluster)打印文件大小(target_entry.file_size)如果 target_entry.start_cluster 非 0打印 "簇链"cluster = target_entry.start_clustercluster_count = 0当 cluster 在 0x002 到 0xFEF 且 cluster_count < 20打印 clustercluster = get_next_cluster(cluster)cluster_count += 1如果 cluster_count >= 20打印 "...(可能更多簇)"退出循环如果 cluster 是 EOF打印 "EOF"否则如果 cluster 是坏簇打印 "坏簇"否则打印 cluster
结束函数

3.2 完整的源代码

void print_fcb(const char *path)
{if (strlen(path) == 0){printf("错误: 需要指定文件或目录路径\n");return;}char full_path[MAX_PATH];build_full_path(full_path, path);WIN32_FIND_DATA find_data;HANDLE hFind = FindFirstFile(full_path, &find_data);if (hFind == INVALID_HANDLE_VALUE){printf("文件或目录不存在: %s\n", path);return;}FindClose(hFind);// 查找该文件在根目录或子目录中的FCB// 首先检查是否在根目录fat_dir_entry_t entries[bpb.root_ent_cnt];int count = read_directory(get_root_dir_offset(), get_root_dir_size(), entries, bpb.root_ent_cnt);const char *filename = get_filename(path);fat_dir_entry_t *target_entry = NULL;// 在条目中查找匹配的文件名for (int i = 0; i < count; i++){char entry_name[13];format_filename(&entries[i], entry_name);if (_stricmp(entry_name, filename) == 0){target_entry = &entries[i];printf("在根目录中找到文件/目录: %s\n", filename);break;}}// 如果找不到,并且当前不是根目录,尝试在当前目录查找if (target_entry == NULL && strcmp(current_path, "\\") != 0){// 获取当前目录的FCBfor (int i = 0; i < count; i++){char entry_name[13];format_filename(&entries[i], entry_name);// 找到当前目录的FCBif ((entries[i].attr & ATTR_DIRECTORY) &&strstr(current_path + 1, entry_name) == current_path + 1){// 读取该目录的内容u16 dir_cluster = entries[i].start_cluster;u32 dir_offset = cluster_to_offset(dir_cluster);// 假设目录占用一个簇u32 dir_size = bpb.sec_per_clus * bpb.byte_per_sec;fat_dir_entry_t dir_entries[100]; // 假设不超过100个条目int dir_count = read_directory(dir_offset, dir_size, dir_entries, 100);// 在子目录内查找文件for (int j = 0; j < dir_count; j++){char subentry_name[13];format_filename(&dir_entries[j], subentry_name);if (_stricmp(subentry_name, filename) == 0){target_entry = &dir_entries[j];printf("在当前目录中找到文件/目录: %s\n", filename);break;}}break;}}}if (target_entry == NULL){printf("无法找到文件/目录的FCB: %s\n", path);return;}// 打印FCB信息printf("\nFCB信息 (16进制):\n");u8 *entry_bytes = (u8 *)target_entry;for (int i = 0; i < 32; i++){printf("%02X ", entry_bytes[i]);if ((i + 1) % 16 == 0)printf("\n");else if ((i + 1) % 8 == 0)printf(" ");}printf("\n详细解析:\n");char formatted_name[13];format_filename(target_entry, formatted_name);printf("文件名: %s\n", formatted_name);printf("8.3格式: ");for (int i = 0; i < 8; i++)printf("%c", target_entry->name[i]);printf(".");for (int i = 0; i < 3; i++)printf("%c", target_entry->ext[i]);printf("\n");print_attributes(target_entry->attr);printf("起始簇号: %u\n", target_entry->start_cluster);printf("文件大小: %u字节\n", target_entry->file_size);// 如果是目录或文件,打印簇链if (target_entry->start_cluster != 0){printf("\n簇链:\n");u16 cluster = target_entry->start_cluster;int cluster_count = 0;while (cluster >= 0x002 && cluster <= 0xFEF && cluster_count < 20){printf("%u -> ", cluster);cluster = get_next_cluster(cluster);cluster_count++;// 防止簇链成环导致的无限循环if (cluster_count >= 20){printf("...(可能有更多簇)");break;}}if (cluster >= 0xFF8 && cluster <= 0xFFF){printf("EOF");}else if (cluster == 0xFF7){printf("坏簇");}else{printf("0x%03X", cluster);}printf("\n");}
}

3.3 测试

FCB信息 (16进制):
54 45 53 54 2D 32 20 20  20 20 20 10 08 60 90 78
B6 5A B6 5A 00 00 19 76  B6 5A C1 00 00 00 00 00详细解析:
文件名: TEST-2
8.3格式: TEST-2  .
属性: 目录
起始簇号: 193
文件大小: 0字节簇链:
193 -> EOF

4. 功能3:打印整个文件分配表

4.1 实现思路及伪代码

4.1.1 实现思路

print_fat 函数用于打印 FAT12 文件系统的文件分配表(FAT)和根目录信息,基于 FAT12 结构和相关辅助函数。

  1. FAT 表信息

    • 调用 get_fat_offsetget_fat_size 获取 FAT 表的起始位置和大小。
    • 计算 FAT 表条目总数(每 1.5 字节一个条目)。
    • 打印 FAT 表的起始位置、大小和条目总数。
  2. 打印 FAT 条目

    • 遍历最多前 100 个 FAT 条目(或实际条目数),通过 get_fat_entry 获取每个簇的值。
    • 根据簇号和值打印状态:
      • 簇号 < 2:保留值。
      • 值 = 0x000:空闲簇。
      • 值在 0x002 到 0xFEF:已分配,显示下一簇。
      • 值 = 0xFF7:坏簇。
      • 值在 0xFF8 到 0xFFF:文件结束(EOF)。
      • 其他:保留值。
  3. 根目录信息

    • 调用 get_root_dir_offsetget_root_dir_size 获取根目录的起始位置和大小。
    • 打印根目录信息(起始位置、大小、最大条目数)。
    • 使用 read_directory 读取根目录内容,获取目录条目。
    • 遍历条目,调用 format_filename 格式化文件名,打印序号、文件名、类型(目录/文件)、大小和起始簇号。
  4. 格式化输出

    • 使用表格格式输出 FAT 表和根目录信息,确保对齐美观。

4.1.2 伪代码

函数 print_fat()fat_offset = 获取 FAT 表起始位置fat_size = 获取 FAT 表大小打印 "FAT表起始位置: fat_offset 字节"打印 "FAT表大小: fat_size 字节"entries_count = fat_size * 2 / 3打印 "FAT表条目总数: entries_count"打印表头 "簇号 值 状态"max_display = min(100, entries_count)循环 i 从 0 到 max_displayvalue = 获取 FAT 条目(i)打印 i, value(16进制)如果 i < 2打印 "保留值"否则如果 value == 0x000打印 "空闲簇"否则如果 value 在 0x002 到 0xFEF打印 "已分配,下一簇为 value"否则如果 value == 0xFF7打印 "坏簇"否则如果 value 在 0xFF8 到 0xFFF打印 "文件结束标记(EOF)"否则打印 "保留值"打印 "根目录区信息"打印 "根目录区起始位置: get_root_dir_offset() 字节"打印 "根目录区大小: get_root_dir_size() 字节"打印 "最大条目数: bpb.root_ent_cnt"entries = 读取根目录(get_root_dir_offset(), get_root_dir_size())count = entries 数量打印 "根目录包含 count 个条目"打印表头 "序号 名称 类型 大小 起始簇"循环 i 从 0 到 countfilename = 格式化文件名(entries[i])打印 i, filename, (entries[i].attr 是目录 ? "目录" : "文件"), entries[i].file_size, entries[i].start_cluster
结束函数

4.2 完整的源代码

void print_fat(void)
{u32 fat_offset = get_fat_offset();u32 fat_size = get_fat_size();printf("FAT表起始位置: %u字节\n", fat_offset);printf("FAT表大小: %u字节\n", fat_size);// 计算FAT表可以存储的条目数量u32 entries_count = (fat_size * 2) / 3; // 每1.5字节一个条目printf("FAT表条目总数: %u\n\n", entries_count);printf("簇号\t值\t状态\n");printf("----------------------\n");// 打印前100个FAT条目int max_display = 100;if (entries_count < max_display){max_display = entries_count;}for (u16 i = 0; i < max_display; i++){u16 value = get_fat_entry(i);printf("%u\t0x%03X\t", i, value);// 解释FAT条目的含义if (i < 2){printf("保留值");}else if (value == 0x000){printf("空闲簇");}else if (value >= 0x002 && value <= 0xFEF){printf("已分配,下一簇为%u", value);}else if (value == 0xFF7){printf("坏簇");}else if (value >= 0xFF8 && value <= 0xFFF){printf("文件结束标记(EOF)");}else{printf("保留值");}printf("\n");}// 打印根目录区信息printf("\n根目录区信息:\n");printf("根目录区起始位置: %u字节\n", get_root_dir_offset());printf("根目录区大小: %u字节\n", get_root_dir_size());printf("最大条目数: %u\n\n", bpb.root_ent_cnt);fat_dir_entry_t entries[bpb.root_ent_cnt];int count = read_directory(get_root_dir_offset(), get_root_dir_size(), entries, bpb.root_ent_cnt);printf("根目录包含%d个条目:\n", count);printf("序号\t名称\t\t类型\t大小\t起始簇\n");printf("----------------------------------------\n");for (int i = 0; i < count; i++){char filename[13];format_filename(&entries[i], filename);printf("%d\t%-12s\t%s\t%u\t%u\n",i,filename,(entries[i].attr & ATTR_DIRECTORY) ? "目录" : "文件",entries[i].file_size,entries[i].start_cluster);}
}

4.3 测试

5. 功能4:切换目录

5.1 实现思路及伪代码

5.1.1 实现思路

cd 函数用于更改当前工作目录,基于 Windows API 和 FAT12 文件系统上下文。其核心逻辑如下:

  1. 输入验证

    • 若输入路径 dir 为空,打印当前目录路径(current_path)并返回。
  2. 特殊路径处理

    • dir..,返回上一级目录:通过 strrchr 找到 current_path 中最后一个反斜杠,截断路径。若已在根目录(\),保持不变。
    • dir.,保持当前目录不变,打印并返回。
    • dir\/,将当前路径设为根目录(\),打印并返回。
  3. 常规路径处理

    • 使用 build_full_path 构建完整路径 full_path
    • 通过 GetFileAttributes 检查路径是否存在(若无效,报错返回)及是否为目录(若不是,报错返回)。
    • 更新 current_path
      • dir\/ 开头,为绝对路径,直接复制到 current_path
      • 否则为相对路径,若当前路径非根目录,添加反斜杠分隔符,再追加 dir
    • 标准化路径,将正斜杠替换为反斜杠。
  4. 输出

    • 打印更新后的当前目录路径。

5.1.2 伪代码

函数 cd(目录路径 dir)如果 dir 为空打印 "当前目录: current_path"返回如果 dir 是 ".."last_slash = current_path 中最后一个 '\'如果 last_slash 存在 且 不在开头截断 last_slash 及其后面的内容否则current_path = "\"打印 "当前目录: current_path"返回如果 dir 是 "."打印 "当前目录: current_path"返回如果 dir 是 "\" 或 "/"current_path = "\"打印 "当前目录: current_path"返回full_path = 构建路径(dir)attrs = 获取文件属性(full_path)如果 attrs 无效打印 "目录不存在: dir"返回如果 attrs 不是目录打印 "不是目录: dir"返回如果 dir 以 "\" 或 "/" 开头current_path = dir否则如果 current_path 不是 "\"如果 current_path 不以 "\" 结尾current_path 追加 "\"current_path 追加 dir将 current_path 中的 "/" 替换为 "\"打印 "当前目录: current_path"
结束函数

5.2 完整的源代码

void cd(const char *dir)
{if (strlen(dir) == 0){printf("当前目录: %s\n", current_path);return;}char full_path[MAX_PATH];// 特殊情况处理if (strcmp(dir, "..") == 0){// 返回上一级目录char *last_slash = strrchr(current_path, '\\');if (last_slash != NULL && last_slash != current_path){*last_slash = '\0';}else{// 如果已经是根目录,保持为 '\'strcpy(current_path, "\\");}printf("当前目录: %s\n", current_path);return;}else if (strcmp(dir, ".") == 0){// 当前目录,不做改变printf("当前目录: %s\n", current_path);return;}else if (strcmp(dir, "\\") == 0 || strcmp(dir, "/") == 0){// 回到根目录strcpy(current_path, "\\");printf("当前目录: %s\n", current_path);return;}// 构建完整路径并检查目录是否存在build_full_path(full_path, dir);DWORD attrs = GetFileAttributes(full_path);if (attrs == INVALID_FILE_ATTRIBUTES){printf("目录不存在: %s\n", dir);return;}if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)){printf("不是目录: %s\n", dir);return;}// 更新当前路径if (dir[0] == '\\' || dir[0] == '/'){// 绝对路径strcpy(current_path, dir);}else{// 相对路径if (strcmp(current_path, "\\") != 0){// 不在根目录,需要添加分隔符if (current_path[strlen(current_path) - 1] != '\\'){strcat(current_path, "\\");}}strcat(current_path, dir);}// 标准化路径,替换正斜杠for (char *p = current_path; *p; p++){if (*p == '/')*p = '\\';}printf("当前目录: %s\n", current_path);
}

5.3 测试

fat
FAT表起始位置: 4096字节
FAT表大小: 6144字节
FAT表条目总数: 4096簇号    值      状态
----------------------
0       0x000   保留值
1       0x001   保留值
2       0xFFF   文件结束标记(EOF)
3       0xFFF   文件结束标记(EOF)
4       0x005   已分配,下一簇为5
5       0x006   已分配,下一簇为6
6       0x007   已分配,下一簇为7
7       0x008   已分配,下一簇为8
8       0x009   已分配,下一簇为9
9       0x00A   已分配,下一簇为10
10      0x00B   已分配,下一簇为11
11      0x00C   已分配,下一簇为12
12      0x00D   已分配,下一簇为13
13      0x00E   已分配,下一簇为14
14      0x00F   已分配,下一簇为15
15      0x010   已分配,下一簇为16
16      0x011   已分配,下一簇为17
17      0x012   已分配,下一簇为18
18      0x013   已分配,下一簇为19
19      0x014   已分配,下一簇为20
20      0x015   已分配,下一簇为21
21      0x016   已分配,下一簇为22
22      0x017   已分配,下一簇为23
23      0x018   已分配,下一簇为24
24      0x019   已分配,下一簇为25
25      0x01A   已分配,下一簇为26
26      0x01B   已分配,下一簇为27
27      0x01C   已分配,下一簇为28
28      0x01D   已分配,下一簇为29
29      0x01E   已分配,下一簇为30
30      0x01F   已分配,下一簇为31
31      0x020   已分配,下一簇为32
32      0x021   已分配,下一簇为33
33      0x022   已分配,下一簇为34
34      0x023   已分配,下一簇为35
35      0x024   已分配,下一簇为36
36      0x025   已分配,下一簇为37
37      0x026   已分配,下一簇为38
38      0x027   已分配,下一簇为39
39      0x028   已分配,下一簇为40
40      0xFFF   文件结束标记(EOF)
41      0x000   空闲簇
42      0x000   空闲簇
43      0x000   空闲簇
44      0x000   空闲簇
45      0x000   空闲簇
46      0x000   空闲簇
47      0x000   空闲簇
48      0x000   空闲簇
49      0x000   空闲簇
50      0x000   空闲簇
51      0x000   空闲簇
52      0x000   空闲簇
53      0x000   空闲簇
54      0x000   空闲簇
55      0x000   空闲簇
56      0x000   空闲簇
57      0x000   空闲簇
58      0x000   空闲簇
59      0x000   空闲簇
60      0x000   空闲簇
61      0x000   空闲簇
62      0x000   空闲簇
63      0x000   空闲簇
64      0x000   空闲簇
65      0x000   空闲簇
66      0x000   空闲簇
67      0x000   空闲簇
68      0x000   空闲簇
69      0x000   空闲簇
70      0x000   空闲簇
71      0x000   空闲簇
72      0x000   空闲簇
73      0x000   空闲簇
74      0x000   空闲簇
75      0x000   空闲簇
76      0x000   空闲簇
77      0x000   空闲簇
78      0x000   空闲簇
79      0x000   空闲簇
80      0x000   空闲簇
81      0x000   空闲簇
82      0x000   空闲簇
83      0x000   空闲簇
84      0x000   空闲簇
85      0x000   空闲簇
86      0x000   空闲簇
87      0x000   空闲簇
88      0x000   空闲簇
89      0x000   空闲簇
90      0x000   空闲簇
91      0x000   空闲簇
92      0x000   空闲簇
93      0x000   空闲簇
94      0x000   空闲簇
95      0x000   空闲簇
96      0x000   空闲簇
97      0x000   空闲簇
98      0x000   空闲簇
99      0x000   空闲簇根目录区信息:
根目录区起始位置: 16384字节
根目录区大小: 16384字节
最大条目数: 512根目录包含5个条目:
序号    名称            类型    大小    起始簇
----------------------------------------
0       新加卷          文件    0       0
1       B.              文件    110     0
2       S               文件    6619245 0
3       SYSTEM~1        目录    0       2
4       TEST-2          目录    0       193

6. 功能5:创建目录、创建文件

6.1 mkdir实现思路及伪代码

6.1.1 实现思路

mkdir 函数用于在 FAT12 文件系统中创建新目录,基于 Windows API。其核心逻辑如下:

  1. 输入验证

    • 检查输入目录名 dir 是否为空,若为空,打印错误信息并返回。
  2. 路径构建与检查

    • 使用 build_full_path 构建完整路径 full_path
    • 通过 GetFileAttributes 检查路径是否已存在(文件或目录),若存在,打印错误并返回。
  3. 创建目录

    • 调用 CreateDirectory 创建新目录。
    • 若创建成功,打印成功信息;否则,打印失败信息及错误码(通过 GetLastError 获取)。

6.1.2 伪代码

函数 mkdir(目录名 dir)如果 dir 为空打印 "错误: 需要目录名"返回full_path = 构建路径(dir)attrs = 获取文件属性(full_path)如果 attrs 有效打印 "错误: 目录或文件已存在: dir"返回如果 CreateDirectory(full_path) 成功打印 "目录创建成功: dir"否则打印 "目录创建失败: dir,错误码: GetLastError()"
结束函数

6.2 touch实现思路及伪代码


6.2.1 实现思路

touch 函数用于在 FAT12 文件系统中创建新文件或更新现有文件的时间戳,基于 Windows API。其核心逻辑如下:

  1. 输入验证

    • 检查输入文件名 filename 是否为空,若为空,打印错误信息并返回。
  2. 路径构建与存在性检查

    • 使用 build_full_path 构建完整路径 full_path
    • 通过 GetFileAttributes 检查文件是否已存在(文件或目录)。
  3. 处理现有文件

    • 若文件存在,调用 CreateFile 以打开现有文件(OPEN_EXISTING 模式)。
    • 获取当前系统时间(GetSystemTime),转换为文件时间(SystemTimeToFileTime),并用 SetFileTime 更新时间戳。
    • 关闭文件句柄,打印时间戳更新成功的消息。
  4. 创建新文件

    • 若文件不存在,调用 CreateFile 以创建新文件(CREATE_NEW 模式)。
    • 若创建成功,关闭句柄并打印成功消息;若失败,打印错误信息及错误码(通过 GetLastError 获取)。

6.2.2 伪代码

函数 touch(文件名 filename)如果 filename 为空打印 "错误: 需要文件名"返回full_path = 构建路径(filename)attrs = 获取文件属性(full_path)如果 attrs 有效打印 "文件或目录已存在: filename"hFile = 打开文件(full_path, 写权限, 打开现有文件)如果 hFile 有效获取当前系统时间 stft = 转换为文件时间(st)设置文件时间(hFile, ft)关闭 hFile打印 "已更新文件时间戳: filename"返回hFile = 创建文件(full_path, 写权限, 创建新文件)如果 hFile 无效打印 "文件创建失败: filename,错误码: GetLastError()"返回关闭 hFile打印 "文件创建成功: filename"
结束函数

6.2 完整的源代码

6.2.1 void mkdir(const char *dir)

void mkdir(const char *dir)
{if (strlen(dir) == 0){printf("错误: 需要目录名\n");return;}char full_path[MAX_PATH];build_full_path(full_path, dir);// 检查目录是否已存在DWORD attrs = GetFileAttributes(full_path);if (attrs != INVALID_FILE_ATTRIBUTES){printf("错误: 目录或文件已存在: %s\n", dir);return;}// 创建目录if (CreateDirectory(full_path, NULL)){printf("目录创建成功: %s\n", dir);}else{printf("目录创建失败: %s,错误码: %lu\n", dir, GetLastError());}
}

6.2.2 void touch(const char *filename)

void touch(const char *filename)
{if (strlen(filename) == 0){printf("错误: 需要文件名\n");return;}char full_path[MAX_PATH];build_full_path(full_path, filename);// 检查文件是否已存在DWORD attrs = GetFileAttributes(full_path);if (attrs != INVALID_FILE_ATTRIBUTES){printf("文件或目录已存在: %s\n", filename);// 更新文件时间戳HANDLE hFile = CreateFile(full_path, GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, NULL);if (hFile != INVALID_HANDLE_VALUE){FILETIME ft;SYSTEMTIME st;GetSystemTime(&st);SystemTimeToFileTime(&st, &ft);SetFileTime(hFile, NULL, NULL, &ft);CloseHandle(hFile);printf("已更新文件时间戳: %s\n", filename);}return;}// 创建空文件HANDLE hFile = CreateFile(full_path,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,NULL);if (hFile == INVALID_HANDLE_VALUE){printf("文件创建失败: %s,错误码: %lu\n", filename, GetLastError());return;}CloseHandle(hFile);printf("文件创建成功: %s\n", filename);
}

6.3 测试

mkdir hello-world
目录创建成功: hello-world
cd hello-world
当前目录: \hello-world
ls
名称                    属性    大小
----------------------------------------
touch hello everyone.txt
文件创建成功: hello everyone.txt
ls
名称                    属性    大小
----------------------------------------
hello everyone.txt      F       0 bytes
touch test.txt
文件创建成功: test.txt
ls
名称                    属性    大小
----------------------------------------
hello everyone.txt      F       0 bytes
test.txt                F       0 bytes

7. 功能6:删除目录、删除文件

7.1 实现思路及伪代码

7.1.1 实现思路

rm 函数用于在 FAT12 文件系统中删除指定文件或目录,基于 Windows API。

  1. 输入验证

    • 检查输入路径 path 是否为空,若为空,打印错误信息并返回。
  2. 路径构建与存在性检查

    • 使用 build_full_path 构建完整路径 full_path
    • 通过 GetFileAttributes 检查路径是否存在,若不存在,打印错误并返回。
  3. 删除操作

    • 检查路径是否为目录(FILE_ATTRIBUTE_DIRECTORY):
      • 若为目录,调用 RemoveDirectory 删除,成功则打印成功信息,失败则打印错误信息及错误码,并提示只能删除空目录。
      • 若为文件,调用 DeleteFile 删除,成功则打印成功信息,失败则打印错误信息及错误码。

7.1.2 伪代码

函数 rm(路径 path)如果 path 为空打印 "错误: 需要指定文件或目录"返回full_path = 构建路径(path)attrs = 获取文件属性(full_path)如果 attrs 无效打印 "文件或目录不存在: path"返回如果 attrs 是目录如果 RemoveDirectory(full_path) 成功打印 "目录删除成功: path"否则打印 "目录删除失败: path,错误码: GetLastError()"打印 "注意: 只能删除空目录"否则如果 DeleteFile(full_path) 成功打印 "文件删除成功: path"否则打印 "文件删除失败: path,错误码: GetLastError()"
结束函数

7.2 完整的源代码

void rm(const char *path)
{if (strlen(path) == 0){printf("错误: 需要指定文件或目录\n");return;}char full_path[MAX_PATH];build_full_path(full_path, path);// 检查文件/目录是否存在DWORD attrs = GetFileAttributes(full_path);if (attrs == INVALID_FILE_ATTRIBUTES){printf("文件或目录不存在: %s\n", path);return;}if (attrs & FILE_ATTRIBUTE_DIRECTORY){// 是目录if (RemoveDirectory(full_path)){printf("目录删除成功: %s\n", path);}else{printf("目录删除失败: %s,错误码: %lu\n", path, GetLastError());printf("注意: 只能删除空目录\n");}}else{// 是文件if (DeleteFile(full_path)){printf("文件删除成功: %s\n", path);}else{printf("文件删除失败: %s,错误码: %lu\n", path, GetLastError());}}
}

7.3 测试

ls
名称                    属性    大小
----------------------------------------
hello everyone.txt      F       0 bytes
test.txt                F       0 bytes
rm test.txt
文件删除成功: test.txt
ls
名称                    属性    大小
----------------------------------------
hello everyone.txt      F       0 bytes
cd .
当前目录: \hello-world
ls
名称                    属性    大小
----------------------------------------
hello everyone.txt      F       0 bytes
rm hello everyone.txt
文件删除成功: hello everyone.txt
cd ..
当前目录: \
rm hello-world
目录删除成功: hello-world
ls
名称                    属性    大小
----------------------------------------
System Volume Information       DH      0 bytes
test-2                  D       0 bytes

8. 挑战性任务:实现FAT12格式化

8.1 完整的源代码

import subprocess
import re
import os
import logging# 配置日志
logging.basicConfig(filename='format_usb_log.txt',level=logging.INFO,format='%(asctime)s - %(levelname)s - %(message)s',encoding='utf-8'  # 日志文件使用 UTF-8
)def run_diskpart(commands):"""执行 diskpart 命令并返回输出"""try:with open('diskpart_script.txt', 'w', encoding='utf-8') as f:f.write('\n'.join(commands) + '\n')# 使用 GBK 解码,适配中文 Windowsresult = subprocess.run(['diskpart', '/s', 'diskpart_script.txt'],capture_output=True,text=True,encoding='gbk',timeout=30)logging.info(f"diskpart 命令执行: {commands}")logging.info(f"diskpart 输出: {result.stdout}")return result.stdoutexcept UnicodeDecodeError:logging.warning("GBK 解码失败,尝试 GB2312")try:result = subprocess.run(['diskpart', '/s', 'diskpart_script.txt'],capture_output=True,text=True,encoding='gb2312',timeout=30)logging.info(f"diskpart 命令执行: {commands}")logging.info(f"diskpart 输出: {result.stdout}")return result.stdoutexcept UnicodeDecodeError as e:logging.error(f"diskpart 输出编码错误: {e}")raise Exception(f"diskpart 输出编码错误,请检查系统语言设置!")except subprocess.TimeoutExpired:logging.error("diskpart 命令超时")raise Exception("diskpart 命令执行超时!")except subprocess.CalledProcessError as e:logging.error(f"diskpart 命令失败: {e}")raise Exception("diskpart 命令失败,请检查权限或磁盘状态!")def verify_disk(disk_number):"""验证指定磁盘是否存在并获取信息"""output = run_diskpart(['list disk'])logging.info(f"list disk 输出: {output}")print("list disk 输出:\n", output)lines = output.splitlines()# 优化正则表达式,匹配中文输出和 0 Bpattern = r'磁盘\s+(\d+)\s+(?:联机|Online)\s+([\d\s]+[GM]B)\s+([\d\s]+[GM]B|[0-9\s]*B)\s*(?:[*]\s*)?(?:[*]\s*)?'for line in lines:# 跳过无关行(如标题、版权信息)if not line.startswith('  磁盘 ') or '--------' in line:continuelogging.info(f"解析行: {line}")if f'磁盘 {disk_number}' in line and ('联机' in line or 'Online' in line):match = re.search(pattern, line)if match:num, size, free = match.groups()logging.info(f"匹配成功: 磁盘 {num}, 大小 {size}, 可用 {free}")# 提取磁盘容量(转换为 GB)size_value = float(re.search(r'(\d+)', size).group(1))size_unit = re.search(r'[GM]B', size).group(0)if size_unit == 'MB':size_value /= 1024  # 转换为 GB# 检查容量是否大于 100GBif size_value > 100:logging.error(f"磁盘 {disk_number} 容量 {size} 超过 100GB,终止操作")raise Exception(f"错误:磁盘 {disk_number} 容量 {size} 超过 100GB,可能是电脑硬盘,为安全起见已终止操作!")detail_output = run_diskpart([f'select disk {disk_number}', 'detail disk'])logging.info(f"detail disk 输出: {detail_output}")is_removable = 'Removable Media' in detail_output or '可移动媒体' in detail_outputreturn {'number': num, 'size': size, 'free': free, 'removable': is_removable}else:logging.warning(f"正则表达式未匹配行: {line}")logging.error(f"未找到磁盘 {disk_number}")raise Exception(f"未找到磁盘 {disk_number} 或磁盘不在线!")def format_usb_to_fat12(disk_number):"""格式化指定磁盘为 FAT12(8MB 简单卷)"""commands = [f'select disk {disk_number}','clean','create partition primary size=8','select partition 1','format fs=fat quick label=USB_FAT12','assign','exit']logging.info("开始格式化 U 盘")output = run_diskpart(commands)logging.info("格式化完成")return outputdef main():try:disk_number = '1'  # 直接指定磁盘 1print("警告:格式化 U 盘将删除磁盘 1 (14GB) 上的所有数据!请确保已备份重要文件。")confirm_backup = input("是否已备份磁盘 1 的数据?(输入 'y' 确认,其他退出):")if confirm_backup.lower() != 'y':logging.info("用户取消操作(未备份数据)")print("操作已取消,请备份数据后再试。")return# 验证磁盘 1disk_info = verify_disk(disk_number)print(f"\n目标磁盘信息:")print(f"磁盘编号:Disk {disk_info['number']}")print(f"总大小:{disk_info['size']}")print(f"可用空间:{disk_info['free']}")print(f"是否可移动:{'是' if disk_info['removable'] else '否'}")if not disk_info['removable']:print("警告:磁盘 1 不是可移动设备,可能是内置硬盘!")confirm_removable = input("是否继续格式化?(输入 'y' 确认,其他退出):")if confirm_removable.lower() != 'y':logging.info("用户取消操作(非可移动磁盘)")print("操作已取消")returnconfirm_format = input(f"\n确认格式化 Disk {disk_number} 为 FAT12(8MB 简单卷)?(输入 'y' 确认,其他退出):")if confirm_format.lower() != 'y':logging.info("用户取消操作(格式化确认)")print("操作已取消")returnoutput = format_usb_to_fat12(disk_number)print("\n格式化完成!")print(output)except Exception as e:logging.error(f"操作失败: {e}")print(f"错误:{e}")finally:# 清理临时文件if os.path.exists('diskpart_script.txt'):os.remove('diskpart_script.txt')if __name__ == "__main__":# 检查管理员权限try:subprocess.run(['whoami'], capture_output=True, check=True)except PermissionError:print("错误:请以管理员身份运行此脚本!")logging.error("脚本未以管理员身份运行")exit(1)main()

8.2 测试结果

D:\Py-code\Daily\FAT12>python format_usb_fat12.py
警告:格式化 U 盘将删除磁盘 1 (14GB) 上的所有数据!请确保已备份重要文件。
是否已备份磁盘 1 的数据?(输入 'y' 确认,其他退出):y
list disk 输出:Microsoft DiskPart 版本 10.0.26100.1150Copyright (C) Microsoft Corporation.
在计算机上: ANNIHILATION磁盘 ###  状态           大小     可用     Dyn  Gpt--------  -------------  -------  -------  ---  ---磁盘 0    联机              953 GB      0 B        *磁盘 1    联机               14 GB      0 B错误:未找到磁盘 1 或磁盘不在线!D:\Py-code\Daily\FAT12>python format_usb_fat12.py
警告:格式化 U 盘将删除磁盘 1 (14GB) 上的所有数据!请确保已备份重要文件。
是否已备份磁盘 1 的数据?(输入 'y' 确认,其他退出):y
list disk 输出:Microsoft DiskPart 版本 10.0.26100.1150Copyright (C) Microsoft Corporation.
在计算机上: ANNIHILATION磁盘 ###  状态           大小     可用     Dyn  Gpt--------  -------------  -------  -------  ---  ---磁盘 0    联机              953 GB      0 B        *磁盘 1    联机               14 GB      0 B目标磁盘信息:
磁盘编号:Disk 1
总大小:14 GB
可用空间:0 B
是否可移动:否
警告:磁盘 1 不是可移动设备,可能是内置硬盘!
是否继续格式化?(输入 'y' 确认,其他退出):y确认格式化 Disk 1 为 FAT12(8MB 简单卷)?(输入 'y' 确认,其他退出):y格式化完成!Microsoft DiskPart 版本 10.0.26100.1150Copyright (C) Microsoft Corporation.
在计算机上: ANNIHILATION磁盘 1 现在是所选磁盘。DiskPart 成功地清除了磁盘。DiskPart 成功地创建了指定分区。分区 1 现在是所选分区。0 百分比已完成100 百分比已完成DiskPart 成功格式化该卷。DiskPart 成功地分配了驱动器号或装载点。退出 DiskPart...

9. 附录代码

9.1 fs.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <windows.h>
#include "fs.h"
#include "disk.h"
#include "types.h"// 当前目录路径,初始为根目录
static char current_path[MAX_PATH] = "\\";// 全局卷标路径,例如 "F:\"
static char volume_path[10] = "?:\\";// 初始化卷标路径
void init_volume_path(char vol_name)
{volume_path[0] = vol_name;
}// 获取文件名(替代PathFindFileName)
const char *get_filename(const char *path)
{const char *filename = path;const char *p = path;while (*p){if (*p == '\\' || *p == '/'){filename = p + 1;}p++;}return filename;
}// 构建完整的文件系统路径
void build_full_path(char *full_path, const char *rel_path)
{strcpy(full_path, volume_path);// 如果是根目录,直接返回卷标路径if (strcmp(current_path, "\\") == 0){if (rel_path[0] == '\\' || rel_path[0] == '/'){strcat(full_path, rel_path + 1);}else{strcat(full_path, rel_path);}}else{// 不是根目录,需要拼接当前路径strcat(full_path, current_path + 1); // 跳过第一个反斜杠// 确保路径末尾有反斜杠if (full_path[strlen(full_path) - 1] != '\\'){strcat(full_path, "\\");}// 添加相对路径if (rel_path[0] == '\\' || rel_path[0] == '/'){strcat(full_path, rel_path + 1);}else{strcat(full_path, rel_path);}}// 替换正斜杠为反斜杠for (char *p = full_path; *p; p++){if (*p == '/')*p = '\\';}
}// 获取文件属性并显示信息
void display_file_info(const char *path)
{WIN32_FIND_DATA findData;HANDLE hFind = FindFirstFile(path, &findData);if (hFind == INVALID_HANDLE_VALUE){printf("找不到文件或目录: %s\n", path);return;}char filename[MAX_PATH];strcpy(filename, get_filename(path));char attr_str[10] = "";if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){strcat(attr_str, "D");}else{strcat(attr_str, "F");}if (findData.dwFileAttributes & FILE_ATTRIBUTE_READONLY){strcat(attr_str, "R");}if (findData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN){strcat(attr_str, "H");}// 修复格式化字符串问题DWORD fileSizeHigh = findData.nFileSizeHigh;DWORD fileSizeLow = findData.nFileSizeLow;printf("%-20s\t%-5s\t", filename, attr_str);if (fileSizeHigh > 0){printf("%u GB+\n", fileSizeHigh);}else{printf("%u bytes\n", fileSizeLow);}FindClose(hFind);
}/** 显示文件列表*/
void ls(const char *dir)
{char search_path[MAX_PATH];char full_path[MAX_PATH];if (strlen(dir) == 0){// 列出当前目录build_full_path(full_path, "");strcpy(search_path, full_path);strcat(search_path, "*");}else{// 列出指定目录build_full_path(full_path, dir);// 检查是否是目录DWORD attrs = GetFileAttributes(full_path);if (attrs == INVALID_FILE_ATTRIBUTES){printf("无法访问: %s\n", dir);return;}if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)){// 是文件,直接显示该文件信息display_file_info(full_path);return;}// 确保目录路径以反斜杠结尾if (full_path[strlen(full_path) - 1] != '\\'){strcat(full_path, "\\");}strcpy(search_path, full_path);strcat(search_path, "*");}WIN32_FIND_DATA findData;HANDLE hFind = FindFirstFile(search_path, &findData);if (hFind == INVALID_HANDLE_VALUE){printf("目录为空或无法访问\n");return;}printf("名称\t\t\t属性\t大小\n");printf("----------------------------------------\n");do{// 跳过 . 和 .. 目录if (strcmp(findData.cFileName, ".") == 0 || strcmp(findData.cFileName, "..") == 0){continue;}char attr_str[10] = "";if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){strcat(attr_str, "D");}else{strcat(attr_str, "F");}if (findData.dwFileAttributes & FILE_ATTRIBUTE_READONLY){strcat(attr_str, "R");}if (findData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN){strcat(attr_str, "H");}// 修复格式化字符串问题DWORD fileSizeHigh = findData.nFileSizeHigh;DWORD fileSizeLow = findData.nFileSizeLow;printf("%-20s\t%-5s\t", findData.cFileName, attr_str);if (fileSizeHigh > 0){printf("%u GB+\n", fileSizeHigh);}else{printf("%u bytes\n", fileSizeLow);}} while (FindNextFile(hFind, &findData));FindClose(hFind);
}/** 切换目录*/
void cd(const char *dir)
{if (strlen(dir) == 0){printf("当前目录: %s\n", current_path);return;}char full_path[MAX_PATH];// 特殊情况处理if (strcmp(dir, "..") == 0){// 返回上一级目录char *last_slash = strrchr(current_path, '\\');if (last_slash != NULL && last_slash != current_path){*last_slash = '\0';}else{// 如果已经是根目录,保持为 '\'strcpy(current_path, "\\");}printf("当前目录: %s\n", current_path);return;}else if (strcmp(dir, ".") == 0){// 当前目录,不做改变printf("当前目录: %s\n", current_path);return;}else if (strcmp(dir, "\\") == 0 || strcmp(dir, "/") == 0){// 回到根目录strcpy(current_path, "\\");printf("当前目录: %s\n", current_path);return;}// 构建完整路径并检查目录是否存在build_full_path(full_path, dir);DWORD attrs = GetFileAttributes(full_path);if (attrs == INVALID_FILE_ATTRIBUTES){printf("目录不存在: %s\n", dir);return;}if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)){printf("不是目录: %s\n", dir);return;}// 更新当前路径if (dir[0] == '\\' || dir[0] == '/'){// 绝对路径strcpy(current_path, dir);}else{// 相对路径if (strcmp(current_path, "\\") != 0){// 不在根目录,需要添加分隔符if (current_path[strlen(current_path) - 1] != '\\'){strcat(current_path, "\\");}}strcat(current_path, dir);}// 标准化路径,替换正斜杠for (char *p = current_path; *p; p++){if (*p == '/')*p = '\\';}printf("当前目录: %s\n", current_path);
}/** 创建目录*/
void mkdir(const char *dir)
{if (strlen(dir) == 0){printf("错误: 需要目录名\n");return;}char full_path[MAX_PATH];build_full_path(full_path, dir);// 检查目录是否已存在DWORD attrs = GetFileAttributes(full_path);if (attrs != INVALID_FILE_ATTRIBUTES){printf("错误: 目录或文件已存在: %s\n", dir);return;}// 创建目录if (CreateDirectory(full_path, NULL)){printf("目录创建成功: %s\n", dir);}else{printf("目录创建失败: %s,错误码: %lu\n", dir, GetLastError());}
}/** 创建文件*/
void touch(const char *filename)
{if (strlen(filename) == 0){printf("错误: 需要文件名\n");return;}char full_path[MAX_PATH];build_full_path(full_path, filename);// 检查文件是否已存在DWORD attrs = GetFileAttributes(full_path);if (attrs != INVALID_FILE_ATTRIBUTES){printf("文件或目录已存在: %s\n", filename);// 更新文件时间戳HANDLE hFile = CreateFile(full_path, GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, NULL);if (hFile != INVALID_HANDLE_VALUE){FILETIME ft;SYSTEMTIME st;GetSystemTime(&st);SystemTimeToFileTime(&st, &ft);SetFileTime(hFile, NULL, NULL, &ft);CloseHandle(hFile);printf("已更新文件时间戳: %s\n", filename);}return;}// 创建空文件HANDLE hFile = CreateFile(full_path,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,NULL);if (hFile == INVALID_HANDLE_VALUE){printf("文件创建失败: %s,错误码: %lu\n", filename, GetLastError());return;}CloseHandle(hFile);printf("文件创建成功: %s\n", filename);
}/** 删除文件/目录*/
void rm(const char *path)
{if (strlen(path) == 0){printf("错误: 需要指定文件或目录\n");return;}char full_path[MAX_PATH];build_full_path(full_path, path);// 检查文件/目录是否存在DWORD attrs = GetFileAttributes(full_path);if (attrs == INVALID_FILE_ATTRIBUTES){printf("文件或目录不存在: %s\n", path);return;}if (attrs & FILE_ATTRIBUTE_DIRECTORY){// 是目录if (RemoveDirectory(full_path)){printf("目录删除成功: %s\n", path);}else{printf("目录删除失败: %s,错误码: %lu\n", path, GetLastError());printf("注意: 只能删除空目录\n");}}else{// 是文件if (DeleteFile(full_path)){printf("文件删除成功: %s\n", path);}else{printf("文件删除失败: %s,错误码: %lu\n", path, GetLastError());}}
}// FAT12目录条目结构
#pragma pack(1)
typedef struct
{u8 name[8];             // 文件名u8 ext[3];              // 扩展名u8 attr;                // 属性u8 reserved;            // 保留给Windows NTu8 creation_time_ms;    // 创建时间的毫秒部分u16 creation_time;      // 创建时间u16 creation_date;      // 创建日期u16 last_access_date;   // 最后访问日期u16 high_start_cluster; // 高16位起始簇号 (FAT32)u16 last_write_time;    // 最后修改时间u16 last_write_date;    // 最后修改日期u16 start_cluster;      // 文件起始簇号u32 file_size;          // 文件大小
} fat_dir_entry_t;
#pragma pack()// 属性值定义
#define ATTR_READ_ONLY 0x01
#define ATTR_HIDDEN 0x02
#define ATTR_SYSTEM 0x04
#define ATTR_VOLUME_ID 0x08
#define ATTR_DIRECTORY 0x10
#define ATTR_ARCHIVE 0x20
#define ATTR_LFN 0x0F // 长文件名属性 (所有位的组合)// 计算FAT和根目录区的偏移
static u32 get_fat_offset()
{return bpb.rsvd_sec_cnt * bpb.byte_per_sec;
}static u32 get_fat_size()
{return bpb.sec_per_fat * bpb.byte_per_sec;
}static u32 get_root_dir_offset()
{return get_fat_offset() + (bpb.num_fats * get_fat_size());
}static u32 get_root_dir_size()
{return bpb.root_ent_cnt * 32; // 每个目录项32字节
}static u32 get_data_offset()
{return get_root_dir_offset() + get_root_dir_size();
}// 计算指定簇号在磁盘中的偏移位置
static u32 cluster_to_offset(u16 cluster)
{// 数据区从簇号2开始,所以需要减去2return get_data_offset() + (cluster - 2) * bpb.sec_per_clus * bpb.byte_per_sec;
}// 从FAT表中获取下一个簇号
static u16 get_next_cluster(u16 cluster)
{u32 fat_offset = get_fat_offset();u32 fat_entry_offset = (cluster * 3) / 2; // 每个FAT表项1.5字节u8 buffer[2];disk_read(buffer, fat_offset + fat_entry_offset, 2);u16 next_cluster;if (cluster % 2 == 0){// 偶数簇号: 取12位低位next_cluster = (buffer[0] | ((buffer[1] & 0x0F) << 8));}else{// 奇数簇号: 取高4位和下一个字节next_cluster = ((buffer[0] & 0xF0) >> 4) | (buffer[1] << 4);}return next_cluster;
}// 获取FAT表中特定条目的值
static u16 get_fat_entry(u16 cluster)
{if (cluster < 2){return cluster; // 0和1是特殊值,直接返回}return get_next_cluster(cluster);
}// 读取目录内容
static int read_directory(u32 dir_offset, u32 dir_size, fat_dir_entry_t *entries, int max_entries)
{int count = 0;u32 offset = 0;while (offset < dir_size && count < max_entries){fat_dir_entry_t entry;disk_read(&entry, dir_offset + offset, sizeof(fat_dir_entry_t));// 检查是否是空条目或已删除条目if (entry.name[0] == 0){// 已到达目录末尾break;}if (entry.name[0] != 0xE5){ // 0xE5表示条目已删除entries[count++] = entry;}offset += sizeof(fat_dir_entry_t);}return count;
}// 将FAT12文件名转换为可读格式
static void format_filename(fat_dir_entry_t *entry, char *output)
{int i;// 复制文件名部分(去除填充空格)for (i = 0; i < 8 && entry->name[i] != ' '; i++){output[i] = entry->name[i];}// 如果有扩展名,添加点和扩展名if (entry->ext[0] != ' '){output[i++] = '.';for (int j = 0; j < 3 && entry->ext[j] != ' '; j++){output[i++] = entry->ext[j];}}output[i] = '\0';
}// 打印文件属性
static void print_attributes(u8 attr)
{printf("属性: ");if (attr & ATTR_READ_ONLY)printf("只读 ");if (attr & ATTR_HIDDEN)printf("隐藏 ");if (attr & ATTR_SYSTEM)printf("系统 ");if (attr & ATTR_VOLUME_ID)printf("卷标 ");if (attr & ATTR_DIRECTORY)printf("目录 ");if (attr & ATTR_ARCHIVE)printf("归档 ");if (attr & ATTR_LFN)printf("长文件名 ");printf("\n");
}/** 打印文件分配表*/
void print_fat(void)
{u32 fat_offset = get_fat_offset();u32 fat_size = get_fat_size();printf("FAT表起始位置: %u字节\n", fat_offset);printf("FAT表大小: %u字节\n", fat_size);// 计算FAT表可以存储的条目数量u32 entries_count = (fat_size * 2) / 3; // 每1.5字节一个条目printf("FAT表条目总数: %u\n\n", entries_count);printf("簇号\t值\t状态\n");printf("----------------------\n");// 打印前100个FAT条目int max_display = 100;if (entries_count < max_display){max_display = entries_count;}for (u16 i = 0; i < max_display; i++){u16 value = get_fat_entry(i);printf("%u\t0x%03X\t", i, value);// 解释FAT条目的含义if (i < 2){printf("保留值");}else if (value == 0x000){printf("空闲簇");}else if (value >= 0x002 && value <= 0xFEF){printf("已分配,下一簇为%u", value);}else if (value == 0xFF7){printf("坏簇");}else if (value >= 0xFF8 && value <= 0xFFF){printf("文件结束标记(EOF)");}else{printf("保留值");}printf("\n");}// 打印根目录区信息printf("\n根目录区信息:\n");printf("根目录区起始位置: %u字节\n", get_root_dir_offset());printf("根目录区大小: %u字节\n", get_root_dir_size());printf("最大条目数: %u\n\n", bpb.root_ent_cnt);fat_dir_entry_t entries[bpb.root_ent_cnt];int count = read_directory(get_root_dir_offset(), get_root_dir_size(), entries, bpb.root_ent_cnt);printf("根目录包含%d个条目:\n", count);printf("序号\t名称\t\t类型\t大小\t起始簇\n");printf("----------------------------------------\n");for (int i = 0; i < count; i++){char filename[13];format_filename(&entries[i], filename);printf("%d\t%-12s\t%s\t%u\t%u\n",i,filename,(entries[i].attr & ATTR_DIRECTORY) ? "目录" : "文件",entries[i].file_size,entries[i].start_cluster);}
}/** 打印文件控制块(FCB)*/
void print_fcb(const char *path)
{if (strlen(path) == 0){printf("错误: 需要指定文件或目录路径\n");return;}char full_path[MAX_PATH];build_full_path(full_path, path);WIN32_FIND_DATA find_data;HANDLE hFind = FindFirstFile(full_path, &find_data);if (hFind == INVALID_HANDLE_VALUE){printf("文件或目录不存在: %s\n", path);return;}FindClose(hFind);// 查找该文件在根目录或子目录中的FCB// 首先检查是否在根目录fat_dir_entry_t entries[bpb.root_ent_cnt];int count = read_directory(get_root_dir_offset(), get_root_dir_size(), entries, bpb.root_ent_cnt);const char *filename = get_filename(path);fat_dir_entry_t *target_entry = NULL;// 在条目中查找匹配的文件名for (int i = 0; i < count; i++){char entry_name[13];format_filename(&entries[i], entry_name);if (_stricmp(entry_name, filename) == 0){target_entry = &entries[i];printf("在根目录中找到文件/目录: %s\n", filename);break;}}// 如果找不到,并且当前不是根目录,尝试在当前目录查找if (target_entry == NULL && strcmp(current_path, "\\") != 0){// 获取当前目录的FCBfor (int i = 0; i < count; i++){char entry_name[13];format_filename(&entries[i], entry_name);// 找到当前目录的FCBif ((entries[i].attr & ATTR_DIRECTORY) &&strstr(current_path + 1, entry_name) == current_path + 1){// 读取该目录的内容u16 dir_cluster = entries[i].start_cluster;u32 dir_offset = cluster_to_offset(dir_cluster);// 假设目录占用一个簇u32 dir_size = bpb.sec_per_clus * bpb.byte_per_sec;fat_dir_entry_t dir_entries[100]; // 假设不超过100个条目int dir_count = read_directory(dir_offset, dir_size, dir_entries, 100);// 在子目录内查找文件for (int j = 0; j < dir_count; j++){char subentry_name[13];format_filename(&dir_entries[j], subentry_name);if (_stricmp(subentry_name, filename) == 0){target_entry = &dir_entries[j];printf("在当前目录中找到文件/目录: %s\n", filename);break;}}break;}}}if (target_entry == NULL){printf("无法找到文件/目录的FCB: %s\n", path);return;}// 打印FCB信息printf("\nFCB信息 (16进制):\n");u8 *entry_bytes = (u8 *)target_entry;for (int i = 0; i < 32; i++){printf("%02X ", entry_bytes[i]);if ((i + 1) % 16 == 0)printf("\n");else if ((i + 1) % 8 == 0)printf(" ");}printf("\n详细解析:\n");char formatted_name[13];format_filename(target_entry, formatted_name);printf("文件名: %s\n", formatted_name);printf("8.3格式: ");for (int i = 0; i < 8; i++)printf("%c", target_entry->name[i]);printf(".");for (int i = 0; i < 3; i++)printf("%c", target_entry->ext[i]);printf("\n");print_attributes(target_entry->attr);printf("起始簇号: %u\n", target_entry->start_cluster);printf("文件大小: %u字节\n", target_entry->file_size);// 如果是目录或文件,打印簇链if (target_entry->start_cluster != 0){printf("\n簇链:\n");u16 cluster = target_entry->start_cluster;int cluster_count = 0;while (cluster >= 0x002 && cluster <= 0xFEF && cluster_count < 20){printf("%u -> ", cluster);cluster = get_next_cluster(cluster);cluster_count++;// 防止簇链成环导致的无限循环if (cluster_count >= 20){printf("...(可能有更多簇)");break;}}if (cluster >= 0xFF8 && cluster <= 0xFFF){printf("EOF");}else if (cluster == 0xFF7){printf("坏簇");}else{printf("0x%03X", cluster);}printf("\n");}
}

10.参考链接

FAT12文件系统结构介绍

实现FAT12

http://www.xdnf.cn/news/587305.html

相关文章:

  • 线性回归模型的参数估计
  • AutoMapper .net Framework 的 Model转换扩展方法
  • python学习 day5
  • 部署人工智能Qlib量化投资平台
  • 你通俗易懂的理解——线程、多线程与线程池
  • 架构实践中,指标体系如何科学建立?构建指标体系的五层结构模型是什么?不同架构风格下的指标体系有怎样的差异?
  • 腾讯2025年校招笔试真题手撕(二)
  • 欧拉降幂(JAVA)蓝桥杯乘积幂次
  • Windows 平台 TCP 通信开发指南
  • Redisson分布式锁案列和源码解读
  • WebBuilder快速开发平台:企业级开发的未来
  • 语义分割的image
  • linux arm架构下如何搭建内网穿透
  • linux 下 scp 传文件时保留文件夹中的原格式属性
  • vue3+element-plus+pinia完整搭建好看简洁的管理后台
  • 关于Python编程语言的详细介绍,结合其核心特性、应用领域和发展现状,以结构化方式呈现:
  • 邮箱验证码登录流程
  • [每日一题] 3362. 零数组变换 iii
  • MapReduce-Top N程序编写与运行
  • 修改 vue-pdf 源码升级 pdfjs-dist 包, 以解决部分 pdf 文件显示花屏问题
  • 基于大模型的胫腓骨干骨折全周期预测与治疗方案研究报告
  • 五分钟学会如何封装Jsckson工具类
  • OpenCV CUDA 模块图像过滤------创建一个高斯滤波器函数createGaussianFilter()
  • Python中的并发编程
  • Java集合框架与三层架构实战指南:从基础到企业级应用
  • OceanBase 系统表查询与元数据查询完全指南
  • 使用web3工具结合fiscobcos网络部署调用智能合约
  • JAVA:柔性一致性策略 BASE 原则
  • tasklet上下文内存分配触发might_alloc检查及同步回收调用链
  • 【C++】笔试强训 第一天