C语言读取FAT32分区文件簇链

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

  1. 首先,读出E,打开文件到达E分区的DBR。
  2. 解析路径:testdir1只有8字节,在根目录中对应1条短目录项; longlonglongsubdir超长了,对应1条短目录项+n条长目录项;nothing.txt超长了,对应1条短目录项+n条长目录项。
  3. 进入根目录,顺序寻找testdir1这个短目录项,如果读到的目录项的第1个字节是0x00,表明读到了未用的目录项,还没有找到,则路径错误。
  4. 进入testdir1目录,寻找longlonglongsubdir,方法是先找短目录项,比对前6个字节,如果相同,再倒回去把长目录项中的文件名拼接好,和longlonglongsubdir进行比对,如果相同,则说明找到。如果读到的目录项的第1个字节是0x00,表明读到了未用的目录项,还没有找到,则路径错误。
  5. 进入到longlonglongsubdir,寻找nothing.txt,也是先比较前6个字节是否相同,再拼接文件名比对。如果找到了,获得nothing.txt的起始簇号。
  6. 在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,很多文章都写得非常非常非常地好,而且是正确的!

  • 10
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值