文章目录
1. 实现FAT12文件管理
已实现如下功能,并测试正确:
- 打印当前目录下所有文件和目录名
- 打印文件/目录的文件控制块
- 打印整个文件分配表
- 切换目录
- 创建目录、创建文件
- 删除目录、删除文件
2. 功能1:打印当前目录下所有文件和目录名
2.1 实现思路及伪代码
2.1.1 实现思路
ls 函数用于列出目录内容或显示单个文件信息,基于 Windows API 和 FAT12 文件系统:
- 路径处理:若 dir 为空,构建当前目录路径;否则构建指定路径。检查路径是否有效,若是文件则显示信息并返回,若是目录则追加 * 准备枚举。
- 目录枚举:用 FindFirstFile 和 FindNextFile 遍历目录,跳过 . 和 …。
- 信息显示:为每个文件/目录生成属性字符串(D/F/R/H),根据大小(nFileSizeHigh/nFileSizeLow)显示字节或 GB,格式化输出。
- 资源清理:遍历后关闭。
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 文件系统。
-
输入验证与路径构建:
- 检查输入路径
path是否为空,若为空则报错并返回。 - 使用
build_full_path构建完整路径full_path,并通过FindFirstFile验证路径是否存在,若不存在则报错并返回。
- 检查输入路径
-
查找 FCB:
- 首先在根目录中查找文件/目录的 FCB:
- 调用
read_directory不从属于根目录的子目录中查找。 - 比较文件名(忽略大小写)以匹配目标文件。
- 调用
- 若未在根目录找到且当前路径非根目录,则尝试在当前目录查找:
- 找到当前目录的 FCB,获取其起始簇号,计算偏移并读取目录内容。
- 在子目录中查找目标文件。
- 若仍未找到,报错并返回。
- 首先在根目录中查找文件/目录的 FCB:
-
打印 FCB 信息:
- 以 16 进制格式打印 FCB 的 32 字节数据。
- 解析并显示文件名(8.3 格式)、属性、起始簇号和文件大小。
- 若起始簇号非 0,打印簇链,追踪 FAT 表中的下一簇,直到文件结束(EOF)、坏簇或限制(20 个簇)。
-
资源清理:
- 关闭
FindFirstFile的句柄。
- 关闭
3.1.2 伪代码
函数 print_fcb(路径 path)
如果 path 为空
打印 "错误: 需要路径"
返回
full_path = 构建路径(path)
hFind = FindFirstFile(full_path)
如果 hFind 无效
打印 "文件或目录不存在"
返回
关闭 hFind
entries = 读取根目录(get_root_dir_offset(), get_root_dir_size())
filename = 获取文件名(path)
target_entry = NULL
遍历 entries
entry_name = 格式化文件名(entries[i])
如果 entry_name 匹配 filename(忽略大小写)
target_entry = entries[i]
打印 "在根目录找到: filename"
退出循环
如果 target_entry 为空 且 当前路径不是根目录
遍历 entries
如果 entries[i] 是目录 且 匹配当前路径
dir_cluster = entries[i].start_cluster
dir_offset = cluster_to_offset(dir_cluster)
dir_entries = 读取目录(dir_offset, 簇大小)
遍历 dir_entries
如果 dir_entries[j] 匹配 filename
target_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_cluster
cluster_count = 0
当 cluster 在 0x002 到 0xFEF 且 cluster_count < 20
打印 cluster
cluster = 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)
{
// 获取当前目录的FCB
for (int i = 0; i < count; i++)
{
char entry_name[13];
format_filename(&entries[i], entry_name);
// 找到当前目录的FCB
if ((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 结构和相关辅助函数。
-
FAT 表信息:
- 调用
get_fat_offset和get_fat_size获取 FAT 表的起始位置和大小。 - 计算 FAT 表条目总数(每 1.5 字节一个条目)。
- 打印 FAT 表的起始位置、大小和条目总数。
- 调用
-
打印 FAT 条目:
- 遍历最多前 100 个 FAT 条目(或实际条目数),通过
get_fat_entry获取每个簇的值。 - 根据簇号和值打印状态:
- 簇号 < 2:保留值。
- 值 = 0x000:空闲簇。
- 值在 0x002 到 0xFEF:已分配,显示下一簇。
- 值 = 0xFF7:坏簇。
- 值在 0xFF8 到 0xFFF:文件结束(EOF)。
- 其他:保留值。
- 遍历最多前 100 个 FAT 条目(或实际条目数),通过
-
根目录信息:
- 调用
get_root_dir_offset和get_root_dir_size获取根目录的起始位置和大小。 - 打印根目录信息(起始位置、大小、最大条目数)。
- 使用
read_directory读取根目录内容,获取目录条目。 - 遍历条目,调用
format_filename格式化文件名,打印序号、文件名、类型(目录/文件)、大小和起始簇号。
- 调用
-
格式化输出:
- 使用表格格式输出 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_display
value = 获取 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 到 count
filename = 格式化文件名(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 文件系统上下文。其核心逻辑如下:
-
输入验证:
- 若输入路径
dir为空,打印当前目录路径(current_path)并返回。
- 若输入路径
-
特殊路径处理:
- 若
dir为..,返回上一级目录:通过strrchr找到current_path中最后一个反斜杠,截断路径。若已在根目录(\),保持不变。 - 若
dir为.,保持当前目录不变,打印并返回。 - 若
dir为\或/,将当前路径设为根目录(\),打印并返回。
- 若
-
常规路径处理:
- 使用
build_full_path构建完整路径full_path。 - 通过
GetFileAttributes检查路径是否存在(若无效,报错返回)及是否为目录(若不是,报错返回)。 - 更新
current_path:- 若
dir以\或/开头,为绝对路径,直接复制到current_path。 - 否则为相对路径,若当前路径非根目录,添加反斜杠分隔符,再追加
dir。
- 若
- 标准化路径,将正斜杠替换为反斜杠。
- 使用
-
输出:
- 打印更新后的当前目录路径。
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。其核心逻辑如下:
-
输入验证:
- 检查输入目录名
dir是否为空,若为空,打印错误信息并返回。
- 检查输入目录名
-
路径构建与检查:
- 使用
build_full_path构建完整路径full_path。 - 通过
GetFileAttributes检查路径是否已存在(文件或目录),若存在,打印错误并返回。
- 使用
-
创建目录:
- 调用
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。其核心逻辑如下:
-
输入验证:
- 检查输入文件名
filename是否为空,若为空,打印错误信息并返回。
- 检查输入文件名
-
路径构建与存在性检查:
- 使用
build_full_path构建完整路径full_path。 - 通过
GetFileAttributes检查文件是否已存在(文件或目录)。
- 使用
-
处理现有文件:
- 若文件存在,调用
CreateFile以打开现有文件(OPEN_EXISTING模式)。 - 获取当前系统时间(
GetSystemTime),转换为文件时间(SystemTimeToFileTime),并用SetFileTime更新时间戳。 - 关闭文件句柄,打印时间戳更新成功的消息。
- 若文件存在,调用
-
创建新文件:
- 若文件不存在,调用
CreateFile以创建新文件(CREATE_NEW模式)。 - 若创建成功,关闭句柄并打印成功消息;若失败,打印错误信息及错误码(通过
GetLastError获取)。
- 若文件不存在,调用
6.2.2 伪代码
函数 touch(文件名 filename)
如果 filename 为空
打印 "错误: 需要文件名"
返回
full_path = 构建路径(filename)
attrs = 获取文件属性(full_path)
如果 attrs 有效
打印 "文件或目录已存在: filename"
hFile = 打开文件(full_path, 写权限, 打开现有文件)
如果 hFile 有效
获取当前系统时间 st
ft = 转换为文件时间(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。
-
输入验证:
- 检查输入路径
path是否为空,若为空,打印错误信息并返回。
- 检查输入路径
-
路径构建与存在性检查:
- 使用
build_full_path构建完整路径full_path。 - 通过
GetFileAttributes检查路径是否存在,若不存在,打印错误并返回。
- 使用
-
删除操作:
- 检查路径是否为目录(
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 解码,适配中文 Windows
result = 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.stdout
except 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.stdout
except 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 B
pattern = 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:
continue
logging.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
# 检查容量是否大于 100GB
if 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_output
return {'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 output
def main():
try:
disk_number = '1' # 直接指定磁盘 1
print("警告:格式化 U 盘将删除磁盘 1 (14GB) 上的所有数据!请确保已备份重要文件。")
confirm_backup = input("是否已备份磁盘 1 的数据?(输入 'y' 确认,其他退出):")
if confirm_backup.lower() != 'y':
logging.info("用户取消操作(未备份数据)")
print("操作已取消,请备份数据后再试。")
return
# 验证磁盘 1
disk_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("操作已取消")
return
confirm_format = input(f"\n确认格式化 Disk {disk_number} 为 FAT12(8MB 简单卷)?(输入 'y' 确认,其他退出):")
if confirm_format.lower() != 'y':
logging.info("用户取消操作(格式化确认)")
print("操作已取消")
return
output = 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.1150
Copyright (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.1150
Copyright (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.1150
Copyright (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 NT
u8 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开始,所以需要减去2
return 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)
{
// 获取当前目录的FCB
for (int i = 0; i < count; i++)
{
char entry_name[13];
format_filename(&entries[i], entry_name);
// 找到当前目录的FCB
if ((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");
}
}

4534

被折叠的 条评论
为什么被折叠?



