FAT12文件系统

写在前面

本文首先罗列了FAT12文件系统的数据结构,然后写出自己在学习、编码过程中遇到的问题及思考,最后进行编码实现:

  1. 输出引导扇区结构数据。
  2. 输出根目录区结构数据。
  3. 在文件系统内存入一份文本,通过在FAT表中查询簇号输出数据区的文件内容。

FAT12文件系统结构

FAT12文件系统分为5个区,引导扇区中记录了整个文件系统的基础信息。FAT1表与FAT2表完全一致,属于相互备份的关系,里面存储了簇号,与数据区存在特殊的关系,后续会详细介绍。根目录区记录了文件系统内所有存储的文件信息,包括文件名、簇号、属性、时间等等。数据区不言而喻,是存储文件主体内容的地方。

序号 扇区位置 扇区个数 备注
01 0 1 引导扇区
02 1 9 FAT1 表
03 10 9 FAT2 表
04 19 14 根目录区
05 33 2847 数据区

引导扇区结构

标识 偏移量 类型 大小 默认值 描述
BS_JmpBoot 0 db 3 - 跳转指令
BS_OEMName 3 db 8 FREEDOS OEM字符串,必须为 8 个字符,不足会以空格填充
BPB_BytePerSec 11 dw 2 0x200 每个扇区字节数
BPB_SecPerClus 12 db 1 1 每簇占用的扇区数
BPB_RsvdSecCnt 14 dw 2 1 Boot占用的扇区数
BPB_NumFATs 16 db 1 2 FAT表的数量
BPB_RootEntCnt 17 dw 2 0xE0 根目录可容纳的目录项数
BPB_TotSec16 19 dw 2 0xB40 逻辑扇区总数
BPB_Media 21 db 1 0xF0 媒体描述符
BPB_FATSz16 22 dw 2 9 每个FAT占用扇区数
BPB_SecPerTrk 24 dw 2 0x12 每个磁道扇区数
BPB_NumHeads 26 dw 2 2 磁头数
BPB_HiddSec 28 dd 4 0 隐藏扇区数
BPB_TotSec32 32 dd 4 0 若BPB_TotSec16是0,则在这里记录扇区总数
BS_DrvNum 36 db 1 0 中断 13(int 13h)的驱动器号
BS_Reserved1 37 db 1 0 未使用
BS_Bootsig 38 db 1 0x29 扩展引导标志
BS_VolID 39 dd 4 0 卷序列号
BS_VolLab 43 db 11 - 卷标,必须为11个字符,不足会以空格填充
BS_FileSysType 54 db 8 FAT12 文件系统类型,必须是8个字符,不足以空格填充
BOOT_Code 62 db 448 0x00 引导代码,由偏移0字节(BS_JmpBoot)跳转过来
END 510 db 2 0x55, 0xAA 系统引导标识,引导扇区结束标识

FAT表结构

FAT 项 可取值 描述
0 BPB_Media 磁盘标识字,低字节需与 BPB_Media 数值保持一致
1 FFFh 表示第一个簇已占用
2 ~ N 000h 可用簇
002h~FEFh 已用簇
FF0h~FF6h 保留簇
FF7h 坏簇
FF8h~FFFh 文件的最后一个簇

根目录区

名称 偏移 长度 描述
DIR_Name 0x00 11 文件名 8B,扩展名 3B
DIR_Attr 0x0B 1 文件属性
保留 0x0C 10 保留位
DIR_WrtTime 0x16 2 最后一次写入时间
DIR_WrtDate 0x18 2 最后一次写入日期
DIR_FstCtus 0x1A 2 起始簇号
DIR_FileSize 0x1C 4 文件大小

编码须知

  • 以1字节对齐
#pragma pack(push)
#pragma pack(1)
struct结构体
#pragma pack(pop)
  • 输出11个字节的字符串,可以在输出时加入描述符,而不需要在字符串结尾加入\0
printf("DIR_Name: %11.11s\n", re.DIR_Name);
  • 根据FAT表在数据区查找文件内容
    重点:FAT表中的每个表项只占用12比特(1.5字节)
  1. FAT表起始地址为第二个簇,MBR结构中BPB_FATSz16参数记录了每个FAT表所占簇的个数,占用9个,那么需要偏移一个簇开始读文件。大小为FAT簇个数*每个簇所占字节数。

  2. 此时获得的字符串即为FAT表的内存数据,那么需要以12bit为步长,获取其值,存入一个新的数据结构中【我malloc申请了一段内存,指针类型:unsigned short,指针变量名:cluster_list】,接下来的数据转化按照网上的讲解,我分析了好久,咨询了大神后,得到如下清晰易懂的步骤:

先取前两个字节,得到数据0x3412,然后左移4bit,得到0x412
再取后两个字节,得到数据0x5634,然后右移4bit,得到0x563
image

个人猜想,或许是设计此文件结构的人,是为了在固定的内存空间中,保留更多的数据段。并且让FAT表更好的与数据区进行对应,才想到如此方法压缩数据的吧

  1. FAT表与数据区对应关系
    下图是我自身理解的过程,其中某些名字,并非官方命名。
    image

编码:查看FAT12的MBR信息

#include <fcntl.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

#define uint8 unsigned char
#define uint16 unsigned short
#define uint32 unsigned int

#pragma pack(push)
#pragma pack(1)
typedef struct _fat12header
{
	uint8 BS_JmpBoot[3];
	uint8 BS_OEMName[8];
	uint16 BPB_BytePerSec;
	uint8 BPB_SecPerClus;
	uint16 BPB_RsvdSecCnt;
	uint8 BPB_NumFATs;
	uint16 BPB_RootEntCnt;
	uint16 BPB_TotSec16;
	uint8 BPB_Media;
	uint16 BPB_FATSz16;
	uint16 BPB_SecPerTrk;
	uint16 BPB_NumHeads;
	uint32 BPB_HiddSec;
	uint32 BPB_TotSec32;
	uint8 BS_DrvNum;
	uint8 BS_Reserved1;
	uint8 BS_Bootsig;
	uint8 BS_VolID[4];
	uint8 BS_VolLab[11];
	uint8 BS_FileSysType[8];
	uint8 BOOT_Code[448];
	uint8 MBR_Flag[2];
}fat12header;

#pragma pack(pop)

void printheader(fat12header* fat12, const char* path)
{
	FILE* fp = fopen(path, "r");
	fread(fat12, sizeof(fat12header), 1, fp);

	fat12->BS_OEMName[7] = 0;
	fat12->BS_VolLab[10] = 0;
	fat12->BS_FileSysType[7] = 0;

	printf("BS_OEMName: %s\n", fat12->BS_OEMName);
	printf("BPB_BytePerSec: %d\n", fat12->BPB_BytePerSec);
	printf("BPB_SecPerClus: %d\n", fat12->BPB_SecPerClus);
	printf("BPB_RsvdSecCnt: %d\n", fat12->BPB_RsvdSecCnt);
	printf("BPB_NumFATs: %d\n", fat12->BPB_NumFATs);
	printf("BPB_RootEntCnt: %d\n", fat12->BPB_RootEntCnt);
	printf("BPB_TotSec16: %d\n", fat12->BPB_TotSec16);
	printf("BPB_Media: %d\n", fat12->BPB_Media);
	printf("BPB_FATSz16: %d\n", fat12->BPB_FATSz16);
	printf("BPB_SecPerTrk: %d\n", fat12->BPB_SecPerTrk);
	printf("BPB_NumHeads: %d\n", fat12->BPB_NumHeads);
	printf("BPB_HiddSec: %d\n", fat12->BPB_HiddSec);
	printf("BPB_TotSec32: %d\n", fat12->BPB_TotSec32);
	printf("BS_DrvNum: %d\n", fat12->BS_DrvNum);
	printf("BS_Reserved1: %d\n", fat12->BS_Reserved1);
	printf("BS_Bootsig: 0x%x\n", fat12->BS_Bootsig);
	printf("BS_VolID: %d %d %d %d\n", fat12->BS_VolID[0], fat12->BS_VolID[1], fat12->BS_VolID[2], fat12->BS_VolID[3]);
	printf("BS_VolLab: %s\n", fat12->BS_VolLab);
	printf("BS_FileSysType: %s\n", fat12->BS_FileSysType);

	printf("Byte 510: 0x%x\n", fat12->MBR_Flag[0]);
	printf("Byte 511: 0x%x\n", fat12->MBR_Flag[1]);

	fclose(fp);
	return;
}

int main()
{
	fat12header fat12 = {0};
	printheader(&fat12, "data.img");
	return 0;
}

image

编码:查看根目录区结构

#include <fcntl.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

#define uint8 unsigned char
#define uint16 unsigned short
#define uint32 unsigned int

#pragma pack(push)
#pragma pack(1)
typedef struct _fat12header
{
	uint8 BS_JmpBoot[3];
	uint8 BS_OEMName[8];
	uint16 BPB_BytePerSec;
	uint8 BPB_SecPerClus;
	uint16 BPB_RsvdSecCnt;
	uint8 BPB_NumFATs;
	uint16 BPB_RootEntCnt;
	uint16 BPB_TotSec16;
	uint8 BPB_Media;
	uint16 BPB_FATSz16;
	uint16 BPB_SecPerTrk;
	uint16 BPB_NumHeads;
	uint32 BPB_HiddSec;
	uint32 BPB_TotSec32;
	uint8 BS_DrvNum;
	uint8 BS_Reserved1;
	uint8 BS_Bootsig;
	uint8 BS_VolID[4];
	uint8 BS_VolLab[11];
	uint8 BS_FileSysType[8];
	uint8 BOOT_Code[448];
	uint8 MBR_Flag[2];
}fat12header;

typedef struct _rootentry
{
	uint8 DIR_Name[11];
	uint8 DIR_Attr;
	uint8 reserve[10];
	uint16 DIR_WrtTime;
	uint16 DIR_WrtDate;
	uint16 DIR_FstClus;
	uint32 DIR_FileSize;
}rootentry;

#pragma pack(pop)

void printheader(fat12header* fat12, const char* path)
{
	FILE* fp = fopen(path, "r");
	fread(fat12, sizeof(fat12header), 1, fp);

	fat12->BS_OEMName[7] = 0;
	fat12->BS_VolLab[10] = 0;
	fat12->BS_FileSysType[7] = 0;

	fclose(fp);
	return;
}

void findrootentry(fat12header* rf, const char* path, int i, rootentry* re)
{
	FILE* fp = fopen(path, "rb");
	if(fp && (0 <= i) &&(i < rf->BPB_RootEntCnt))
	{
		fseek(fp, 19*rf->BPB_BytePerSec+i*sizeof(rootentry), SEEK_SET);
		fread((void*)re, 1, sizeof(rootentry), fp);
	}
	fclose(fp);
	return;
}

void printrootentry(fat12header* rf, const char* path)
{
	for(int i = 0; i < rf->BPB_RootEntCnt;i++)
	{
		rootentry re = {0};
		findrootentry(rf, path, i, &re);
		if(0 == re.DIR_FstClus || '\0' == re.DIR_Name[0])
            continue;

		printf("====%d==== : \n", i);
		printf("DIR_Name: %11.11s\n", re.DIR_Name);
		printf("DIR_Attr: 0x%x\n", re.DIR_Attr);
		printf("DIR_WrtTime: %d\n", re.DIR_WrtTime);
		printf("DIR_WrtDate: %d\n", re.DIR_WrtDate);
		printf("DIR_FstClus: %d\n", re.DIR_FstClus);
		printf("DIR_FileSize: %d\n", re.DIR_FileSize);
	}
}

int main()
{
	fat12header fat12 = {0};
	printheader(&fat12, "data.img");
	printrootentry(&fat12, "data.img");
	return 0;
}

MacOS M1,结果为:

image

Ubuntu,结果为:
image

编码:根据根目录区文件名,加载文件数据

#include <fcntl.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define uint8 unsigned char
#define uint16 unsigned short
#define uint32 unsigned int

#pragma pack(push)
#pragma pack(1)
typedef struct _fat12header
{
	uint8 BS_JmpBoot[3];
	uint8 BS_OEMName[8];
	uint16 BPB_BytePerSec;
	uint8 BPB_SecPerClus;
	uint16 BPB_RsvdSecCnt;
	uint8 BPB_NumFATs;
	uint16 BPB_RootEntCnt;
	uint16 BPB_TotSec16;
	uint8 BPB_Media;
	uint16 BPB_FATSz16;
	uint16 BPB_SecPerTrk;
	uint16 BPB_NumHeads;
	uint32 BPB_HiddSec;
	uint32 BPB_TotSec32;
	uint8 BS_DrvNum;
	uint8 BS_Reserved1;
	uint8 BS_Bootsig;
	uint8 BS_VolID[4];
	uint8 BS_VolLab[11];
	uint8 BS_FileSysType[8];
	uint8 BOOT_Code[448];
	uint8 MBR_Flag[2];
}fat12header;

typedef struct _rootentry
{
	uint8 DIR_Name[11];
	uint8 DIR_Attr;
	uint8 reserve[10];
	uint16 DIR_WrtTime;
	uint16 DIR_WrtDate;
	uint16 DIR_FstClus;
	uint32 DIR_FileSize;
}rootentry;

#pragma pack(pop)

void printheader(fat12header* fat12, const char* path)
{
	FILE* fp = fopen(path, "r");
	fread(fat12, sizeof(fat12header), 1, fp);

	fat12->BS_OEMName[7] = 0;
	fat12->BS_VolLab[10] = 0;
	fat12->BS_FileSysType[7] = 0;

	fclose(fp);
	return;
}

void findrootentry(fat12header* rf, const char* path, int i, rootentry* re)
{
	FILE* fp = fopen(path, "rb");
	if(fp && (0 <= i) &&(i < rf->BPB_RootEntCnt))
	{
		fseek(fp, 19*rf->BPB_BytePerSec+i*sizeof(rootentry), SEEK_SET);
		fread((void*)re, 1, sizeof(rootentry), fp);
	}
	fclose(fp);
	return;
}

int searchrootentry(fat12header* rf, rootentry* re, const char* path, const char* filename)
{
	for(int i = 0; i < rf->BPB_RootEntCnt;i++)
	{
		findrootentry(rf, path, i, re);
		if(0 == re->DIR_FstClus || '\0' == re->DIR_Name[0])
            continue;

		char dirname[11] = {0};
		char *token = NULL;
		token = strtok(re->DIR_Name, " ");
		sprintf(dirname, "%s", token);
		token = strtok(NULL, " ");
		sprintf(dirname, "%s.%s", dirname, token);

		if(0 != strcmp(dirname, filename))
			continue;

		printf("%s\n", dirname);
		return 1;
	}
	return 0;
}

void readfat(fat12header* rf, const char* path, uint16* cluster_list)
{
	int fat_size = rf->BPB_FATSz16 * rf->BPB_BytePerSec;
	uint8* fat = (uint8*)malloc(fat_size);
	FILE* fp = fopen(path, "rb");
	if(fp)
	{
		fseek(fp, rf->BPB_BytePerSec * 1, SEEK_SET);
		fread((uint8*)fat, 1, fat_size, fp);
		for(int i = 0; i < fat_size; i+=3)
		{
			*cluster_list = ((uint16)(fat[i+1] & 0xF) << 8) + (uint16)fat[i];
			cluster_list += 1;

			*cluster_list = (fat[i+1] >> 4) + ((uint16)fat[i+2] << 4);
			cluster_list += 1;
		}
	}
	fclose(fp);
	free(fat);
}

void printfilecontent(fat12header* rf, rootentry* re, const char* path, const char* filename)
{
	// 根据目录项的名字,查找该文件的信息
	int flag = searchrootentry(rf, re, path, filename);
	if(0 == flag)
		return;

	// 将FAT项全部转化并存入 cluster_list 链表
	int fat_size = rf->BPB_FATSz16 * rf->BPB_BytePerSec;
	int cluster_list_size = fat_size*8/12;
	uint16* cluster_list = (uint16*)malloc(cluster_list_size*2);
	readfat(rf, path, cluster_list);

	short DIR_FstClus = re->DIR_FstClus;
	FILE* fp = fopen(path, "rb");
	while(fp)
	{
		// 计算簇地址
		short DIR_FstClus_addr = 33 * rf->BPB_SecPerClus * rf->BPB_BytePerSec + (DIR_FstClus - 2) * rf->BPB_SecPerClus * rf->BPB_BytePerSec;
		//printf("DIR_FstClus:%d\tDIR_FstClus_addr:0x%x\n", DIR_FstClus, DIR_FstClus_addr);
		char *filemessage = (char*)malloc(re->DIR_FileSize);
		fseek(fp, DIR_FstClus_addr, SEEK_SET);
		fread((void*)filemessage, 1, rf->BPB_SecPerClus * rf->BPB_BytePerSec, fp);
		printf("%s", filemessage);
		free(filemessage);

		DIR_FstClus = cluster_list[DIR_FstClus];
		if(DIR_FstClus >= 0xFF8)
			break;
	}
	fclose(fp);
	free(cluster_list);
}

int main()
{
	fat12header fat12 = {0};
	rootentry re = {0};
	printheader(&fat12, "data.img");
	printfilecontent(&fat12, &re, "data.img", "LOADER.BIN");
	return 0;
}

image

写在后面(心得)

如果我们站在FAT12文件系统上面看它,无非就是设计者制定了一套数据结构和规则,使用者按照它的规则去使用。
上学的时候学数据结构,总觉得数组、队列、链表、树之类的,只需要多记多练甚至考前背一背,就没什么难的。
刚工作时,见到的数据结构比较大,为了让其内存空间足够小会用union。为了节约数据拷贝的内存,声明时会按字节数对齐。
如今经历了FAT12,感觉它更难了,更多的是设计能力。

posted @ 2022-09-05 19:28  Qing-Huan  阅读(888)  评论(0编辑  收藏  举报