FAT32基本结构
FAT32文件系统由4部分构成:DRB和保留扇区,FAT1,FAT2和数据区。其中数据区中有根目录、子目录和数据三部分。
DBR的结构
开始的3字节是一个跳转指令,指出引导代码的开始位置,EB 58就是跳转到0x58+2(相对于当前位置,当前位置是2)。之后的BPB结构存储了和该分区有关的重要信息。BPB中重要的字段如下:
打开磁盘首先进入的是DBR,此时FILE_BEGIN指针为零,是指对DBR的开始位置而言偏移0,保留扇区数的作用是可以通过它,获得FAT 1相对于FILE_BEGIN的偏移。FAT的个数一般是2个,在知道每个FAT占用的扇区数之后,可以通过该字段,获得根目录相对FILE_BEGIN的偏移。根目录的相对开始扇区号=保留扇区数+FAT个数*每个FAT的扇区数。如果要计算绝对地址,则需要用到Hidden Sectors,它指的是该分区DBR之前的扇区数。找文件找簇链,使用相对地址就可以。
目录项的结构
获得根目录相对扇区号之后,打开到根目录,根目录里面存放着根目录下文件和子目录的目录项,每个项占32字节。
如果是文件项,该项会指出文件起始簇号和大小。通过起始簇号,找到FAT中对应条目,找到整个文件内容。
如果是子目录项,该项会指出子目录所在位置,通过子目录项,进入到子目录中,子目录内也是一个个目录项,每个32字节,不过开始的前两个项是“.”和“…”。
目录项根据文件/子目录的名字长短,分为短目录项和长目录项两种。对于文件名,如果前缀长度小于等于8字节且后缀名小于等于3字节,则它的目录项是短目录项;如果文件前缀大于8字节或者后缀大于3字节,则它的目录项是由1条短目录项和n条长目录项构成。对于子目录名,如果比8字节长,它的目录项就是由1条短目录项和n条长目录项构成,否则只有1条短目录项。
短目录项的重要字段
虽然说是短目录项,但是每个文件/子目录,都至少有1个短目录项,里面最重要的字段是起始簇号,如果是文件,在FAT表中通过起始簇号,可以找到文件内容;如果是子目录,通过它可以跳转到子目录中。名字太长的文件和子目录,也会有1个短目录项,这其中的前11个字节没有实质性意义,前8个字节中的文件名是文件头6字节的文件名大写+’~1’,而簇号是有意义的,虽然是长文件名的文件或目录,它的起始簇号,是存在这一个短目录项中的,长目录项项中的这一字段往往置0。
如果文件前缀名太短,比8字节还短,或者后缀比3字节短,在对应字段中,是会用0x20进行填充,而不是0x00。在检索进行文件比较的时候,需要注意该填充。长目录项中的填充方法则与这个不同。
短目录项的0x0B属性字段占1个字节,这8位,置位不同位有不同的含义。短目录项的这一位绝对不会是0x0F,因为0x0F对应着的是长目录项,通过这一位来判断目录项是短目录项还是长目录项:
长目录项重要字段
长目录项的0x0B字段一定是0x0F。长目录项的作用是存储文件名。①②③按照Unicode编码方式存储文件名,在一个长文件目录项中,文件名是按照①②③的顺序拼接的。可能会有这种情况:长文件名在①或者②中就正好够了,那么剩下没有用到的字段,会首先填充一个0x00,Unicode下,0x00后面还会接着一个0x00,之后填充0xFF。在拼接长文件名时,可以完全复制,两个连续的0x00作为字符串截断,虽然之后有0xFF,但是不影响字符串比较。
第1字节属性字节,低5位置位含义如下:
对于一个长文件名的文件或者目录,序号的含义是,从高地址往低地址走,会经过1个短目录项和n个长目录项,序号值从1开始,每经过一个长目录项,该值加1。走到最后一个长目录项,第6位会置位1,如下面的图中所示,最后一个长目录项的该位为0x42,含义是这是第2个长目录项,且是最后一个长目录项。
&emsp一个文件或子目录的名字比较长,它的前6字节会出现在短目录项中,接着,完整的包括那6字节的文件名可以在长目录项中找到。存储顺序是文件名1+文件名2,内部按照①②③的顺序,如下图所示:
FAT表项
FAT像一个巨大的链表,每个表项4字节。在目录中找到一个文件的开始簇号,来查FAT表,找到这个簇号对应的FAT表项。如果该表项是0x0FFFFFFF,表示是该文件的最后一簇,否则该表项指向的是该文件占用的下一个簇号。
解析文件路径(英文路径)
路径:E/testdir1/longlonglongsubdir/nothing.txt
- 首先,读出E,打开文件到达E分区的DBR。
- 解析路径:testdir1只有8字节,在根目录中对应1条短目录项; longlonglongsubdir超长了,对应1条短目录项+n条长目录项;nothing.txt超长了,对应1条短目录项+n条长目录项。
- 进入根目录,顺序寻找testdir1这个短目录项,如果读到的目录项的第1个字节是0x00,表明读到了未用的目录项,还没有找到,则路径错误。
- 进入testdir1目录,寻找longlonglongsubdir,方法是先找短目录项,比对前6个字节,如果相同,再倒回去把长目录项中的文件名拼接好,和longlonglongsubdir进行比对,如果相同,则说明找到。如果读到的目录项的第1个字节是0x00,表明读到了未用的目录项,还没有找到,则路径错误。
- 进入到longlonglongsubdir,寻找nothing.txt,也是先比较前6个字节是否相同,再拼接文件名比对。如果找到了,获得nothing.txt的起始簇号。
- 在FAT中,找到文件起始簇号对应的条目,如果是0x0FFFFFFF,则已经到达文件的最后一簇,否则该条目表示的是文件占用的下一个簇号,进行遍历。
源代码
#include <windows.h>
#include <winioctl.h> //DDK驱动开发与控制
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <stdint.h>
#include <cstring>
#define onesector 512 //扇区512
uint8_t lpBuffer[onesector] = { 0 };//用于读512字节
uint8_t lpBuffer2[onesector] = { 0 };//用于临时读512字节
//DBR结构,EBR应该也是类似管理
struct DBR {
uint8_t jumpcode[3];//EB 58 90
uint8_t OEM[8];//OEM代号
uint8_t bytes_per_sector[2];//扇区字节数
uint8_t secotrs_per_cluster;//每簇扇区数
uint8_t reserve_sectors[2];//包括DBR自己在内的FAT之前的扇区个数
uint8_t FATnum;//FAT个数,一般为2
uint8_t unimportant1[11];
uint8_t DBR_LBA[4];//该分区的DBR所在的相对扇区号,如果是扩展分区,是相对于扩展分区首的
uint8_t totalsectors[4];//本分区的总扇区数
uint8_t sectors_per_FAT[4];//每个FAT的扇区数
uint8_t unimportant2[4];
uint8_t root_cluster_number[4];//根目录簇号
uint8_t file_info[2];
uint8_t backup_DBR[2];//备份引导扇区的相对于DBR的扇区号,一般为6,内容和DBR一模一样
uint8_t zero1[12];
uint8_t extBPB[26];//扩展BPB
uint8_t osboot[422];//引导代码和55AA
};
struct FDT {
char content[32];
};
//短文件目录项32字节
struct shortFDT {
uint8_t filename[8];//第一部分文件名
uint8_t extname[3];//文件扩展名
uint8_t attr;//属性 0F则说明是长文件需要索引到非0F,然后倒着读回来
uint8_t reserve;
uint8_t time1;
uint8_t creattime[2];
uint8_t createdate[2];
uint8_t visittime[2];
uint8_t high_cluster[2];//文件起始簇号高16位
uint8_t changetim2[2];
uint8_t changedate[2];
uint8_t low_cluster[2];//文件起始簇号低16位
uint8_t filelen[4];//文件长度
};
struct longFDT {
char flag;//如果是0x4*,第6位置位了,说明是最后一个长文件目录,各位是下面还有几个
char name1[10];
char attr;//如果是长文件名,除了最下面一个,都是0F
char reserve;
char checksum;
char name2[12];
char rel_cluster[2];//相对起始簇号
char name3[4];
};
//文件名可能很长,方便检索文件名
struct long_FDT_list {
struct longFDT lfdt;
struct long_FDT_list * up;//上面的(低地址)
struct long_FDT_list * down;
};
struct findfilepath {
char prename[100];
char rearname[10];
int flag;//1是短文件,2是长文件,3是短目录,4是长目录;是3/4则next非空
struct findfilepath *next;
};
uint16_t uint8to16(uint8_t twouint8[2]) {
return *(uint16_t*)twouint8;
}
uint32_t uint8to32(uint8_t fouruint8[4]) {
return *(uint32_t*)fouruint8;
}
uint64_t uint8to64(uint8_t eightuint8[8]) {
return *(uint64_t*)eightuint8;
}
int compareuint8(uint8_t * a, uint8_t *b)
{
if (sizeof(a) != sizeof(b))
return 0;
for (int i = 0; i < sizeof(a); i++)
{
if (a[i] != b[i])
return 0;
}
return 1;
}
int comparestr(char * a, char *b)
{
if (strlen(a) != strlen(b))
return 0;
for (int i = 0; i < strlen(a); i++)
{
if (a[i] != b[i])
return 0;
}
return 1;
}
int compare_nobs_str(char * a, char *b)
{
if (strlen(a) != strlen(b))
return 0;
for (int i = 0; i < strlen(a); i++)
{
if (a[i] != b[i] || toupper(a[i]) != toupper(b[i]))//大小写不敏感
return 0;
}
return 1;
}
void show_onesector(uint8_t sector[onesector])
{
for (int i = 0; i < onesector; i++)
{
if (sector[i] < 16)
printf("0%X ", sector[i]);
else
printf("%X ", sector[i]);
if ((i + 1) % 16 == 0)
printf("\n");
}
}
//第一步,寻找分区
int whichpartition(char partition_char) {
int partition = -1;
switch (partition_char) {
case 'C':
case 'c':
partition = 0;
break;
case 'D':
case 'd':
partition = 1;
break;
case 'E':
case 'e':
partition = 2;
break;
case 'F':
case 'f':
partition = 3;
break;
case 'G':
case 'g':
partition = 4;
break;
case 'H':
case 'h':
partition = 5;
break;
case 'I':
case 'i':
partition = 6;
break;
case 'J':
case 'j':
partition = 7;
break;
default:
partition = -1;
break;
}
return partition;
}
//简单的英文的两个转一个,不会出现两个连续的00,出现就表示已经到结尾了
//文件不区分大小写的,全部toupper,记得那个'.'!!!!是存在里面的!!!!把读出来的点删掉
void double2single(char *filename, char *upperfilename, int num) {
int j = 0;
for (int i = 0; i < num; i = i + 2)
{ //如果是字母,返回非零,否则返回零!!!
if (isalpha(filename[i]) == 0)
{
upperfilename[j] = filename[i];
}
else
upperfilename[j] = toupper(filename[i]);
j++;
}
}
//文件不区分大小写的,全部toupper
void short2upper(char*filename) {
for (int i = 0; i < strlen(filename); i++)
filename[i] = toupper(filename[i]);
}
//下一步解析,如果已经到了文件尾,这次解析出的是文件名,那么返回0[[后面没有斜杠了'/']
//否则还要再次调用解析,是目录返回1[后面还有一个斜杠]
//否则出错,返回-1
//如果返回1,或者0,下一次的目录/文件名,保存在nextname里面[由调用者维护,不检查长度]
//indexptr是用来保存当前检索到了哪里,下一次直接继续[调用者维护]
int nextpath(char *filepath, char *nextname, int *indexptr) {
int flag = -1;
int firstindex = 0, lastindex = 0;
int filepathlen = strlen(filepath);
short2upper(filepath);
if (*indexptr >= filepathlen)//6个空间,索引最大为5
return -1;
//解析文件名
while (1) {
//判断是否结束或者出错
if (*indexptr >= filepathlen && flag == -1)//如果出错
{
flag = -1;//出错了
break;
}
else if (*indexptr == filepathlen && flag == 1) {
flag = 0;//读完了,假设到文件了
break;
}
if (filepath[*indexptr] == ':')//如果在一开始是冒号,往后读内容
{
;
}
if (filepath[*indexptr] == '/' && flag == -1)//如果是第一个反斜杠,标志开始
{
if ((*indexptr) + 1 < filepathlen) {
firstindex = (*indexptr) + 1;
//printf("firstindex = %d\n",firstindex);
flag = 1;//暂时等于1
}
else {
flag = -1; break;//出错了
}
}
else if (filepath[*indexptr] == '/' && flag != -1)//解析完一个了
{
lastindex = (*indexptr) - 1;//收尾了
//printf("lastindex = %d\n",lastindex);
break;
}
(*indexptr)++;
}
if (flag != -1)
{
memset(nextname, 0, sizeof(nextname));
if (flag == 1)//目录
strncpy(nextname, &filepath[firstindex], lastindex - firstindex + 1);//复制字符串
else
strncpy(nextname, &filepath[firstindex], filepathlen - firstindex);//复制字符串
//printf("next name=%s\n",nextname);
}
return flag;
}
//把文件名存放为文件名和后缀名,后缀名大于3的,也都是用长文件名格式;含有空格的也都是长文件名
int dividefilename(char*filename, char*prename, char*rearname) {
//printf("dividename is %s %d\n",filename,strlen(filename));
int dotindex = 0;
for (int i = 0; i < strlen(filename); i++) {
if (filename[i] == '.')//找到分界了
{
dotindex = i;
//printf("dotindex = %d\n",dotindex);
break;//假设输入是对的
}
}
if (dotindex == 0)
return -1;
strncpy(prename, filename, dotindex);//复制前面文件名
strncpy(rearname, &filename[dotindex + 1], strlen(filename) - dotindex - 1);
//printf("copy filename: %s %s strlen=%d %d\n",prename,rearname,strlen(prename),strlen(rearname));
return 0;
}
void getpathlist(char*filepath, struct findfilepath*&head, int *filenameindex) {
struct findfilepath *p = NULL, *rear = NULL;
int t;//有木有结束
char nextname[100];
char prename[100];
char rearname[10];
int padding = 0;
char paddingnum = 0x20;
while (1) {
memset(nextname, 0, sizeof(nextname));
memset(prename, 0, sizeof(prename));
memset(rearname, 0, sizeof(rearname));
t = nextpath(filepath, nextname, filenameindex);
if (t != -1)//正常情况是得到目录名
{
if (t == 1)//目录名
{
printf("目录为%s 长度为%d\n", nextname, strlen(nextname));
p = (struct findfilepath*)malloc(sizeof(struct findfilepath));
p->next = NULL;
memset(p->prename, 0, sizeof(p->prename));
memset(p->rearname, 0, sizeof(p->rearname));
if (strlen(nextname) > 8)//需要长文件存
{
p->flag = 4;//长目录
strncpy(p->prename, nextname, strlen(nextname));//长目录直接存
//printf("长目录 %s %d\n",p->prename,strlen(p->prename));
}
else
{
p->flag = 3;//短目录
strncpy(p->prename, nextname, strlen(nextname));//短目录补0x20
paddingnum = 8 - strlen(p->prename);
if (paddingnum > 0)
memset(&p->prename[strlen(p->prename)], 0x20, paddingnum);
//printf("短目录 %s %d\n",p->prename,strlen(p->prename));
}
if (rear == NULL)
{
head = p; rear = p;
}
else
{
rear->next = p; rear = p;
}
}
else//t==0,是文件名
{
printf("文件为%s 长度为%d\n", nextname, strlen(nextname));
p = (struct findfilepath*)malloc(sizeof(struct findfilepath));
p->next = NULL;
memset(p->prename, 0, strlen(p->prename));
memset(p->rearname, 0, strlen(p->rearname));
dividefilename(nextname, prename, rearname);
if (strlen(prename) > 8 || strlen(rearname) > 3)//需要长文件存
{
p->flag = 2;//长文件
strncpy(p->prename, prename, strlen(prename));
strncpy(p->rearname, rearname, strlen(rearname));
//printf("长文件 %s.%s\n",p->prename,p->rearname);
}
else
{
p->flag = 1;//短文件
strncpy(p->prename, prename, strlen(prename));
paddingnum = 8 - strlen(p->prename);
if (paddingnum > 0)
memset(&p->prename[strlen(p->prename)], 0x20, paddingnum);
strncpy(p->rearname, rearname, strlen(rearname));
paddingnum = 3 - strlen(p->rearname);
if (paddingnum > 0)
memset(&p->rearname[strlen(p->rearname)], 0x20, paddingnum);
//printf("短文件 %s.%s\n",p->prename,p->rearname);
}
if (rear == NULL)
{
head = p; rear = p;
}
else
{
rear->next = p; rear = p;
}
}
}
else
break;
}
}
//解析短文件名
void readshortfileFDT(struct shortFDT the_short_FDT, ULONGLONG FAT1_reladdr, HANDLE hDevice) {
uint8_t cluster[4] = { the_short_FDT.low_cluster[0],the_short_FDT.low_cluster[1],
the_short_FDT.high_cluster[0],the_short_FDT.high_cluster[1] };
uint32_t firstcluster = uint8to32(cluster);//起始簇号
printf("文件开始簇号是%08X\n", firstcluster);
printf("簇链:\n%08X\n", firstcluster);
int next_sector = firstcluster / 128;//在第几个扇区?512*beg_sector
int next_byte = (firstcluster % 128) * 4;
uint32_t FATentry = 0;
uint8_t Buffer[512] = { 0 };
int needreadmore = 1;
int seqindex = 0;
BOOL bRet;
DWORD dwCB;
LARGE_INTEGER offset;
offset.QuadPart = FAT1_reladdr + (ULONGLONG)(512 * next_sector);
SetFilePointer(hDevice, offset.LowPart, &offset.HighPart, FILE_BEGIN);
bRet = ReadFile(hDevice, Buffer, 512, &dwCB, NULL);
while (needreadmore == 1) {
FATentry = *(uint32_t*)&Buffer[next_byte];
printf("%08X\n", FATentry);
if (FATentry != 0x0FFFFFFF)//尚未结束
{
if (next_sector != FATentry / 128)
{
next_sector = FATentry / 128;
offset.QuadPart = FAT1_reladdr + (ULONGLONG)(512 * next_sector);
SetFilePointer(hDevice, offset.LowPart, &offset.HighPart, FILE_BEGIN);
memset(Buffer, 0, 512);
bRet = ReadFile(hDevice, Buffer, 512, &dwCB, NULL);
}
next_byte = (FATentry % 128) * 4;
}
else
needreadmore = 0;
}
}
//解析短目录名,通过相对于根目录,找到绝对地址
ULONGLONG readshortdirFDT(struct shortFDT the_short_FDT, ULONGLONG root_reladdr, HANDLE hDevice, int secotrs_per_cluster) {
uint8_t cluster[4] = { the_short_FDT.low_cluster[0],the_short_FDT.low_cluster[1],
the_short_FDT.high_cluster[0],the_short_FDT.high_cluster[1] };
uint32_t firstcluster = uint8to32(cluster);//起始簇号
printf("目录开始簇号是%08X\n", firstcluster);
ULONGLONG nextaddr = (ULONGLONG)((firstcluster - 2)*secotrs_per_cluster * 512) + root_reladdr;
return nextaddr;
}
void findfile(struct findfilepath * head, char* partition) {
struct findfilepath *p = NULL;
DISK_GEOMETRY *pdg; // 保存磁盘参数的结构体
BOOL bResult; // generic results flag
ULONGLONG DiskSize; // size of the drive, in bytes
HANDLE hDevice; // 设备句柄
DWORD junk; // discard resultscc
//通过CreateFile来获得设备的句柄,打开对应的盘
hDevice = CreateFile(TEXT(partition), // 设备名称,这里指硬盘甚至可以是分区/扩展分区名,不区分大小写
GENERIC_READ, // 读
FILE_SHARE_READ | FILE_SHARE_WRITE, // share mode
NULL, // default security attributes
OPEN_EXISTING, // disposition
0, // file attributes
NULL); // do not copy file attributes
if (hDevice == INVALID_HANDLE_VALUE) // cannot open the drive
{
printf("May be no permission!Or no such partition!\n");
return;
}
//通过DeviceIoControl函数与设备进行IO,为后面做准备
bResult = DeviceIoControl(hDevice, // 设备的句柄
IOCTL_DISK_GET_DRIVE_GEOMETRY, // 控制码,指明设备的类型
NULL, 0, // no input buffer
pdg,
sizeof(*pdg), // output buffer 输出,保存磁盘参数信息
&junk, // # bytes returned
(LPOVERLAPPED)NULL); // synchronous I/O
LARGE_INTEGER offset;//读取位置
offset.QuadPart = (ULONGLONG)0;//0
SetFilePointer(hDevice, offset.LowPart, &offset.HighPart, FILE_BEGIN);//从这个位置开始读,DBR是FILE_BEGIN,相对位移!!!
DWORD dwCB;
struct DBR the_DBR;
//从这个位置开始读DBR,一开始的512字节有些信息有用
BOOL bRet = ReadFile(hDevice, &the_DBR, 512, &dwCB, NULL);
ULONGLONG FAT1_reladdr = (ULONGLONG)uint8to16(the_DBR.reserve_sectors) *
(ULONGLONG)512;//得到FAT1的具体地址,但是偏移需要用相对偏移
ULONGLONG root_reladdr = FAT1_reladdr + (ULONGLONG)(the_DBR.FATnum) *
(ULONGLONG)uint8to32(the_DBR.sectors_per_FAT)*(ULONGLONG)512;//根目录的起始相对位置,根目录是在第[01]2簇
offset.QuadPart = root_reladdr;
SetFilePointer(hDevice, offset.LowPart, &offset.HighPart, FILE_BEGIN);
memset(lpBuffer, 0, sizeof(lpBuffer));
bRet = ReadFile(hDevice, lpBuffer, 512, &dwCB, NULL);
//show_onesector(lpBuffer);
char prename[101] = { 0 };
char rearname[10] = { 0 };
int needreadmore = 0;//在这一层目录需要继续读扇区,还没有找到文件/恰好被长文件分割了
//1次只能读512字节整数倍,16个FDT,每个32字节
struct FDT tempFDT[16] = { 0 }, temp_tempFDT[16] = { 0 };//后面的给长目录和文件准备
struct shortFDT the_short_FDT = { 0 };
struct longFDT the_long_FDT[10];
int longfdtindex = 0;//索引项
int index = 0;
int tempindex = 0;
LARGE_INTEGER temp_offset;//临时倒退找长文件项的
int findflag = 0, mayfindflag = 0;
p = head;
int i = 0;
while (p != NULL)//还没有找到,一层层找
{
memset(tempFDT, 0, sizeof(tempFDT));
memcpy(tempFDT, lpBuffer, 512);
if (p->next == NULL && (p->flag == 1 || p->flag == 2))//如果在这一层是文件
{
if (p->flag == 1)//是短文件,找到以后可以用短文件解析
{
findflag = 0; index = 0;
while (findflag == 0) {
if (tempFDT[index].content[0] == 0x00) {
printf("\n路径错误!\n");
return;
}
if (tempFDT[index].content[11] != 0x0F)
{
memset(prename, 0, sizeof(prename));
memset(rearname, 0, sizeof(rearname));
strncpy(prename, &tempFDT[index].content[0], 8);
strncpy(rearname, &tempFDT[index].content[8], 3);
if (!strcmp(prename, p->prename) && !strcmp(rearname, p->rearname))
{
findflag = 1;
memcpy(&the_short_FDT, &tempFDT[index], 32);
readshortfileFDT(the_short_FDT, FAT1_reladdr, hDevice);
}
}
if (index < 15 && findflag == 0)//没找到
index++;
else if (index == 15 && findflag == 0)
{
index = 0;
memset(tempFDT, 0, sizeof(tempFDT));
memset(lpBuffer, 0, sizeof(lpBuffer));
offset.QuadPart = offset.QuadPart + (ULONGLONG)512;
SetFilePointer(hDevice, offset.LowPart, &offset.HighPart, FILE_BEGIN);
bRet = ReadFile(hDevice, lpBuffer, 512, &dwCB, NULL);
memcpy(tempFDT, lpBuffer, 512);
}
}
}
else if (p->flag == 2)
{
while (findflag == 0) {
if (tempFDT[index].content[0] == 0x00) {
printf("\n路径错误!\n");
return;
}
if ((tempFDT[index].content[11] & 0x10) == 0)//文件项,先找找文件名头四个对不对
{
memset(prename, 0, sizeof(prename));
memcpy(prename, &tempFDT[index].content[0], 8);
if (!strnicmp(prename, p->prename, 4))
{
memset(&the_short_FDT, 0, 32);
memcpy(&the_short_FDT, &tempFDT[index], 32);//里面存着长目录的地址信息
tempindex = index;//不能影响环境!!!!
memset(prename, 0, sizeof(prename));
memcpy(temp_tempFDT, tempFDT, sizeof(tempFDT));
while (1) {
if (tempindex > 0)//如果暂时不需要倒退
{
tempindex--;
if (temp_tempFDT[tempindex].content[11] == 0x0F)//的确没完
{
memset(&the_long_FDT[longfdtindex], 0, 32);
memcpy(&the_long_FDT[longfdtindex], &temp_tempFDT[tempindex], 32);
longfdtindex++;//下一个索引
}
else//读完了,处理字符串
{
for (int i = 0, k = 0; i < longfdtindex; i++)
{
for (int j = 0; j < 3; j++)//复制三部分
{
if (j == 0) {
double2single(the_long_FDT[i].name1,
&prename[k], 10);
k = k + 5;
}
else if (j == 1) {
double2single(the_long_FDT[i].name2,
&prename[k], 12);
k = k + 6;
}
else {
double2single(the_long_FDT[i].name3,
&prename[k], 4);
k = k + 2;
}
}
}
//printf("文件名是 %s.%s\n", p->prename, p->rearname);
//printf("拼的文件名是 %s\n", prename);
if (!strnicmp(prename, p->prename, strlen(p->prename)) &&
!strnicmp(prename + strlen(p->prename) + 1, p->rearname,
strlen(p->rearname)))
{
findflag = 1;
goto findlongname;
}
else
{
findflag = 0;
goto noyet;
}
}
}
else {//倒退回去,继续读
tempindex = 16;//后面一进入就要做一次减法
memset(temp_tempFDT, 0, sizeof(temp_tempFDT));
memset(lpBuffer, 0, sizeof(lpBuffer));
temp_offset.QuadPart = offset.QuadPart - (ULONGLONG)512;
SetFilePointer(hDevice, temp_offset.LowPart, &temp_offset.HighPart, FILE_BEGIN);
bRet = ReadFile(hDevice, lpBuffer, 512, &dwCB, NULL);
memcpy(temp_tempFDT, lpBuffer, 512);
}
} //end of while(1) 找长文件名目录项
}//end of if 文件前四个相同
noyet://这个不是,虽然文件名前四个相同
longfdtindex = 0;
findlongname://是的,找到了
if (findflag == 1)
readshortfileFDT(the_short_FDT, FAT1_reladdr, hDevice);
}//end of if 如果是一个短文件目录项
if (index < 15 && findflag == 0)//没找到
index++;
else if (index == 15 && findflag == 0)
{
index = 0;
memset(tempFDT, 0, sizeof(tempFDT));
memset(lpBuffer, 0, sizeof(lpBuffer));
offset.QuadPart = offset.QuadPart + (ULONGLONG)512;
SetFilePointer(hDevice, offset.LowPart, &offset.HighPart, FILE_BEGIN);
bRet = ReadFile(hDevice, lpBuffer, 512, &dwCB, NULL);
memcpy(tempFDT, lpBuffer, 512);
}
}//end of while(findflag=0) 已经找到了,调出来
}//长文件,不用读了
}
else if (p->next != NULL && (p->flag == 3 || p->flag == 4))
{
if (p->flag == 3)//如果是短目录
{
findflag = 0; index = 0;
i = 0;
while (findflag == 0) {
if (tempFDT[index].content[0] == 0x00) {
printf("\n路径错误!\n");
return;
}
//注意运算符顺序
if ((tempFDT[index].content[11] & 0x10) != 0)//短目录项
{
memset(prename, 0, sizeof(prename));
memcpy(prename, &tempFDT[index].content[0], 8);
if (!strcmp(prename, p->prename))
{
findflag = 1;
memset(&the_short_FDT, 0, 32);
memcpy(&the_short_FDT, &tempFDT[index], 32);
//更新
offset.QuadPart = readshortdirFDT(the_short_FDT, root_reladdr,
hDevice, the_DBR.secotrs_per_cluster);
}
}
if (index < 15 && findflag == 0)//没找到
index++;
else if (index == 15 && findflag == 0)
{
index = 0;
memset(tempFDT, 0, sizeof(tempFDT));
memset(lpBuffer, 0, sizeof(lpBuffer));
offset.QuadPart = offset.QuadPart + (ULONGLONG)512;
SetFilePointer(hDevice, offset.LowPart, &offset.HighPart, FILE_BEGIN);
bRet = ReadFile(hDevice, lpBuffer, 512, &dwCB, NULL);
}
}
}
else if (p->flag == 4)//长目录,有点难受!!!,不如先找头4个字节!!!,大写的
{
while (findflag == 0) {
if (tempFDT[index].content[0] == 0x00) {
printf("\n路径错误!\n");
return;
}
if ((tempFDT[index].content[11] & 0x10) != 0)//目录项,先找找文件名头四个对不对
{
memset(prename, 0, sizeof(prename));
memcpy(prename, &tempFDT[index].content[0], 8);
if (!strnicmp(prename, p->prename, 4))
{
memset(&the_short_FDT, 0, 32);
memcpy(&the_short_FDT, &tempFDT[index], 32);//里面存着长目录的地址信息
tempindex = index;//不能影响环境!!!!
memset(prename, 0, sizeof(prename));
memcpy(temp_tempFDT, tempFDT, sizeof(tempFDT));
while (1) {
if (tempindex > 0)//如果暂时不需要倒退
{
tempindex--;
if (temp_tempFDT[tempindex].content[11] == 0x0F)//的确没完
{
memset(&the_long_FDT[longfdtindex], 0, 32);
memcpy(&the_long_FDT[longfdtindex], &temp_tempFDT[tempindex], 32);
longfdtindex++;//下一个索引
}
else//读完了,处理字符串
{
for (int i = 0, k = 0; i < longfdtindex; i++)
{
for (int j = 0; j < 3; j++)//复制三部分
{
if (j == 0) {
double2single(the_long_FDT[i].name1,
&prename[k], 10);
k = k + 5;
}
else if (j == 1) {
double2single(the_long_FDT[i].name2,
&prename[k], 12);
k = k + 6;
}
else {
double2single(the_long_FDT[i].name3,
&prename[k], 4);
k = k + 2;
}
}
}
//printf("文件目录是 %s\n", p->prename);
//printf("拼的目录是 %s\n", prename);
if (!strcmp(prename, p->prename))
{
findflag = 1;
goto finddir;
}
else
{
findflag = 0;
goto notyet;
}
}
}
else {//倒退回去,继续读
tempindex = 16;//后面一进入就要做一次减法
memset(temp_tempFDT, 0, sizeof(temp_tempFDT));
memset(lpBuffer, 0, sizeof(lpBuffer));
temp_offset.QuadPart = offset.QuadPart - (ULONGLONG)512;
SetFilePointer(hDevice, temp_offset.LowPart, &temp_offset.HighPart, FILE_BEGIN);
bRet = ReadFile(hDevice, lpBuffer, 512, &dwCB, NULL);
memcpy(temp_tempFDT, lpBuffer, 512);
}
}
}//end of while(1) 找长文件名目录项
notyet://这个不是,虽然文件名前四个相同
longfdtindex = 0;
finddir://是的,找到了
if (findflag == 1)
offset.QuadPart = readshortdirFDT(the_short_FDT, root_reladdr,
hDevice, the_DBR.secotrs_per_cluster);
}//end of if 如果是一个短文件目录项
if (index < 15 && findflag == 0)//没找到
index++;
else if (index == 15 && findflag == 0)
{
index = 0;
memset(tempFDT, 0, sizeof(tempFDT));
memset(lpBuffer, 0, sizeof(lpBuffer));
offset.QuadPart = offset.QuadPart + (ULONGLONG)512;
SetFilePointer(hDevice, offset.LowPart, &offset.HighPart, FILE_BEGIN);
bRet = ReadFile(hDevice, lpBuffer, 512, &dwCB, NULL);
memcpy(tempFDT, lpBuffer, 512);
}
}//end of while(findflag=0) 已经找到了,调出来
}//end of if 如果是当前解析的是长文件目录
//!!!是目录,继续读下一步扇区!!!在上面复制回来
memset(tempFDT, 0, sizeof(tempFDT));
memset(lpBuffer, 0, sizeof(lpBuffer));
SetFilePointer(hDevice, offset.LowPart, &offset.HighPart, FILE_BEGIN);
bRet = ReadFile(hDevice, lpBuffer, 512, &dwCB, NULL);
memcpy(tempFDT, lpBuffer, 512);
}
findflag = 0;
p = p->next;//下一层路径
}
CloseHandle(hDevice);//如果打开成功,需要关闭
}
void freepathlist(struct findfilepath *&head) {
struct findfilepath *p = NULL;
p = head;
while (p != NULL) {
p = p->next;
free(p);
}
}
int main()
{
DISK_GEOMETRY pdg; // 保存磁盘参数的结构体
BOOL bResult; // generic results flag
ULONGLONG DiskSize; // size of the drive, in bytes
printf("请输入文件路径:[假设输入是对的,注意斜杠方向,如E:/testdir/hello.txt]\n");
char filepath[101] = { 0 };//文件名可能很长,方便检索文件名
gets(filepath);
struct findfilepath *p = NULL, *head = NULL;
char partition[]="\\\\.\\C:";
partition[4]=filepath[0];
int filenameindex = 1;//从1开始解析
//指针s要用引用类型,不然是副本,内存泄漏
getpathlist(filepath, head, &filenameindex);
findfile(head, partition);
freepathlist(head);
system("pause");
return 0;
}
验证
长文件名/目录名
在FAT32分区格式,E:/testdir1/longlonglongsubdir/nothing.txt文件。testdir1是8字节,短目录项,而longlonglongsubdir和nothing.txt都要用到长目录项。
分析结果,文件路径解析正确,testdir1的开始簇号是0x3号,longlonglongsubdir的开始簇号是0x5A2号,nothing.txt文件的开始簇号是0x5A3号。
打开winHex验证结果,E盘的根目录开始地址是0x4FF99E200,里面存放着包括testdir1在内的内容,找到该条目,高位簇号为0,低位为0x0003。
根目录是2号簇,地址0x4FF99E200,在此基础上加上0x1000得到3号簇,也就是0x4FF99F200处,找到的确有longlonglongsubdir一项,该项所在簇号为0x5A2,相对于根目录偏移0x5A0个簇,也就是5A0000个字节偏移。
打开到这,在longlonglongsubdir中,的确看到有nothing.txt的条目,是1个短目录项,开始簇号为5A3。
继续找到FAT1中,寻找簇号为0x5A3的条目。FAT1的起始地址是0x4FF1A2A00,每个条目4字节,0x5A3在FAT中的起始偏移地址是0x1688,该项目是0x0FFFFFFF,是该文件的最后一簇。
占多个簇
继续在FAT中寻找,找到0x5A5对应的条目,与打印出来的是一致的:
后续需要考虑的
在上面,是假设目录区,无论是根目录还是子目录中的目录项,都是小于128项的,都在一个簇内。如果根目录中多于128项,而且根目录后3号簇很可能被占用了,根目录的下一簇应该需要在FAT表中查找,如果文件名/目录名很长,不仅跨扇区了(代码中考虑了跨扇区的情况,回溯,能够拼出文件名),还跨簇了,这种情况代码中没有考虑。
上面长文件名拼接时,用大小为10的数组存储长目录项而不是链表或者更大数组,如果长目录项超过10个,就会出现问题。
暂时没有考虑中文的因素,所以在处理文件名的时候比较简单粗暴,短文件名直接变成大写进行比对,长文件名两个两个字节读取,取第一个字节。实际在处理的时候,应该考虑Unicode编码的问题,windows的FAT32中,长文件名的Unicode编码是UTF-16LE,需要使用到MultiByteToWideChar()函数和WideCharToMultiByte()函数进行转换处理。
参考链接
详解FAT32文件系统
http://www.blogfshare.com/detail-fat32-filesys.html
这位博主tql,很多文章都写得非常非常非常地好,而且是正确的!