参考
https://blog.51cto.com/13475106/category6.html
及狄泰软件相关课程
在上一节中,我们成功的将虚拟软盘进行了格式化,而且将文件存入其中,那么如何在根目录区中查找文件呢?为了方便起见,再次给出FAT12文件系统的格式,如下所示:

在FAT12文件系统中,一簇包含一个扇区。从上图可见,目录文件项的偏移为19个扇区,其本身大小占用了14个扇区。根目录区中是一个一个的目录项,目录项的具体成员与分布如下所示:

每一个目录项代表根目录中一个文件的索引,其大小为32字节,根据目录项中文件开始的簇号和文件大小,我们可以找到文件的具体内容。下面我们先来读取一下FAT12文件系统根目录的信息,步骤如下:
1、创建RootEntry结构体类型
2、使用文件流顺序读取每个项的内容
3、解析并打印相关的信息
在Qt creater中编写程序如下:
#include <QtCore/QCoreApplication>
#include <QFile>
#include <QDataStream>
#include <QDebug>
#include <QVector>
#include <QByteArray>
#pragma pack(push)
#pragma pack(1)
struct Fat12Header
{
char BS_OEMName[8];
ushort BPB_BytsPerSec;
uchar BPB_SecPerClus;
ushort BPB_RsvdSecCnt;
uchar BPB_NumFATs;
ushort BPB_RootEntCnt;
ushort BPB_TotSec16;
uchar BPB_Media;
ushort BPB_FATSz16;
ushort BPB_SecPerTrk;
ushort BPB_NumHeads;
uint BPB_HiddSec;
uint BPB_TotSec32;
uchar BS_DrvNum;
uchar BS_Reserved1;
uchar BS_BootSig;
uint BS_VolID;
char BS_VolLab[11];
char BS_FileSysType[8];
};
struct RootEntry
{
char DIR_Name[11];
uchar DIR_Attr;
uchar reserve[10];
ushort DIR_WrtTime;
ushort DIR_WrtDate;
ushort DIR_FstClus;
uint DIR_FileSize;
};
#pragma pack(pop)
void PrintHeader(Fat12Header& rf, QString p)
{
QFile file(p);
if( file.open(QIODevice::ReadOnly) )
{
QDataStream in(&file);
file.seek(3);
in.readRawData(reinterpret_cast<char*>(&rf), sizeof(rf));
rf.BS_OEMName[7] = 0;
rf.BS_VolLab[10] = 0;
rf.BS_FileSysType[7] = 0;
qDebug() << "BS_OEMName: " << rf.BS_OEMName;
qDebug() << "BPB_BytsPerSec: " << hex << rf.BPB_BytsPerSec;
qDebug() << "BPB_SecPerClus: " << hex << rf.BPB_SecPerClus;
qDebug() << "BPB_RsvdSecCnt: " << hex << rf.BPB_RsvdSecCnt;
qDebug() << "BPB_NumFATs: " << hex << rf.BPB_NumFATs;
qDebug() << "BPB_RootEntCnt: " << hex << rf.BPB_RootEntCnt;
qDebug() << "BPB_TotSec16: " << hex << rf.BPB_TotSec16;
qDebug() << "BPB_Media: " << hex << rf.BPB_Media;
qDebug() << "BPB_FATSz16: " << hex << rf.BPB_FATSz16;
qDebug() << "BPB_SecPerTrk: " << hex << rf.BPB_SecPerTrk;
qDebug() << "BPB_NumHeads: " << hex << rf.BPB_NumHeads;
qDebug() << "BPB_HiddSec: " << hex << rf.BPB_HiddSec;
qDebug() << "BPB_TotSec32: " << hex << rf.BPB_TotSec32;
qDebug() << "BS_DrvNum: " << hex << rf.BS_DrvNum;
qDebug() << "BS_Reserved1: " << hex << rf.BS_Reserved1;
qDebug() << "BS_BootSig: " << hex << rf.BS_BootSig;
qDebug() << "BS_VolID: " << hex << rf.BS_VolID;
qDebug() << "BS_VolLab: " << rf.BS_VolLab;
qDebug() << "BS_FileSysType: " << rf.BS_FileSysType;
file.seek(510);
uchar b510 = 0;
uchar b511 = 0;
in.readRawData(reinterpret_cast<char*>(&b510), sizeof(b510));
in.readRawData(reinterpret_cast<char*>(&b511), sizeof(b511));
qDebug() << "Byte 510: " << hex << b510;
qDebug() << "Byte 511: " << hex << b511;
}
file.close();
}
RootEntry FindRootEntry(Fat12Header& rf, QString p, int i)
{
RootEntry ret = {{0}};
QFile file(p);
if( file.open(QIODevice::ReadOnly) && (0 <= i) && (i < rf.BPB_RootEntCnt) )
{
QDataStream in(&file);
file.seek(19 * rf.BPB_BytsPerSec + i * sizeof(RootEntry));
in.readRawData(reinterpret_cast<char*>(&ret), sizeof(ret));
}
file.close();
return ret;
}
void PrintRootEntry(Fat12Header& rf, QString p)
{
for(int i=0; i<rf.BPB_RootEntCnt; i++)
{
RootEntry re = FindRootEntry(rf, p, i);
if( re.DIR_Name[0] != '\0' )
{
qDebug() << i << ":";
qDebug() << "DIR_Name: " << hex << re.DIR_Name;
qDebug() << "DIR_Attr: " << hex << re.DIR_Attr;
qDebug() << "DIR_WrtDate: " << hex << re.DIR_WrtDate;
qDebug() << "DIR_WrtTime: " << hex << re.DIR_WrtTime;
qDebug() << "DIR_FstClus: " << hex << re.DIR_FstClus;
qDebug() << "DIR_FileSize: " << hex << re.DIR_FileSize;
}
}
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QString img = "data.img";
Fat12Header f12;
qDebug() << "Print Header:";
PrintHeader(f12, img);
qDebug() << endl;
qDebug() << "Print Root Entry";
PrintRootEntry(f12, img);
return a.exec();
}
以上程序中,PrintHeader首先将FAT12文件系统的第一扇区信息读入到f12结构中,PrintRootEntry函数中需要用到这个结构中的信息,打印结果如下:

我们看到了START.BIN文件和A.TXT文件,这就是上一节我们存入的两个文件,根目录项中已经保存了它们的信息,而我们也成功的读取了目录项信息。除此之外还有两个奇怪的文件,分别为Aa和As,这两个文件是FreeDos格式化时就存入的,和我们无关,因此不必关心它。
当查找文件时,我们只需根据文件名找到对应的目录项,当文件名和目录项中的DIR_Name相等时,即是我们想找的文件,根据对应的目录项我们又可以找到文件起始的存储位置DIR_FstClus和文件大小DIR_FileSize,有了这两项,再加上FAT表的信息,我们就可以读取文件的具体内容了,从本文的第一张图可以看到,FAT12文件系统中有两张FAT表,分别为FAT1和FAT2,它们是完全一样的,互为备份。
FAT表是一个关系图,类似于链表,它记录了文件数据的先后关系,每一个FAT表项占用12bit,而前两个FAT表项是规定不使用的。
FAT表项表示的数据是以簇为单位存取的,由于FAT12文件系统中一簇只包含一扇区,每一个表项表示文件数据所占用扇区的位置,而文件数据起始扇区的位置是保存在文件目录项的DIR_FstClus中的。知道了一个FAT表项的位置之后,我们可以根据这个FAT表项中的内容找到存储数据的扇区,而下一个表项的位置也是根据上一个表项中的内容所代表的的位置找到的。听起来比较烧脑,下面我们来一个实际的例子。FAT表和数据存储示意图如下:

根据上图进行分析,DIR_FstClus代表了文件数据的起始扇区的位置C,而下一个文件数据所在的扇区在哪呢?我们先在FAT表中找到位置C处的FAT表项,该表项中的内容就是下一个文件数据所在的扇区O,我们再在FAT表中找到O处的FAT表项,即可得到文件数据的下一个扇区Z,再根据Z找到Z位置处的FAT表项,又得到了文件数据的下一个扇区位置Q,再根据Q找到Q位置处的FAT表项,又得到了文件数据的下一个扇区S,再根据S找到S位置处的FAT表项,此时,该表项中为NULL,即表示文件结束了。以上就是一个文件内容读取的过程,这个过程类似于一个链表的查找,如下图所示:

下面我们进行读取文件内容的实验,步骤如下:
1、在根目录区中查找目标文件对应的项
2、获取目标文件起始扇区号和文件大小
3、根据FAT表中的记录数据的先后关系读取文件内容
首先给出几个需要注意的事项,如下图所示:


FAT表项中的每一项占用12bit也就是1.5字节,这是存储在软盘中的情况,而在内存中,我们为了操作方便,就将这1.5字节用2字节来存储,这就需要一个转换的过程,我们的思路是,先将整个FAT表全部读入内存,然后将这些数据进行一定的转换,使每个表项用2字节表示。转换示意图如下:

图中原始FAT即为整个FAT表刚读入内存中的情况,i, i+1, i+2三个字节表示j, j+1两个FAT表项,现在我们要把j 和j+1用两个字节表示,转换方法也在上图中给出了,先取出第i+1个字节,然后和0x0F按位与,相当于取它的低四位,这低四位是要作为第j个表项的高四位的(上图中,第i+1字节的高四位划到了第j个表项中,我们忽略这个细节),因此要左移八位,然后和第i个字节按位或,这样就把它们组合在一起了。而第i+1字节的高四位要作为第j+1个表项的低四位,所以先将低i+1字节右移四位,将i+2字节左移四位,然后按位或,即可得到第j+1个表项。
下图给出了一个第 j 项更具体的转换过程:

还有一个需要注意的细节是数据区的起始地址为2,而不是0,什么意思呢?也就是当FAT表项中给出数据扇区的地址为2时,代表数据存储在数据区偏移为0的扇区。也就是FAT表项中给出的扇区号减去2,才代表真正的数据扇区的偏移位置。这是由于FAT表中的前两个表项保留不使用的原因造成的,如果FAT表项内容为1,那下一个要查找的就是第一个FAT表项,而前两个表项是保留的,因此,FAT表项的内容只能从2开始。
下面直接给出读取文件内容的程序:
#include <QtCore/QCoreApplication>
#include <QFile>
#include <QDataStream>
#include <QDebug>
#include <QVector>
#include <QByteArray>
#pragma pack(push)
#pragma pack(1)
struct Fat12Header
{
char BS_OEMName[8];
ushort BPB_BytsPerSec;
uchar BPB_SecPerClus;
ushort BPB_RsvdSecCnt;
uchar BPB_NumFATs;
ushort BPB_RootEntCnt;
ushort BPB_TotSec16;
uchar BPB_Media;
ushort BPB_FATSz16;
ushort BPB_SecPerTrk;
ushort BPB_NumHeads;
uint BPB_HiddSec;
uint BPB_TotSec32;
uchar BS_DrvNum;
uchar BS_Reserved1;
uchar BS_BootSig;
uint BS_VolID;
char BS_VolLab[11];
char BS_FileSysType[8];
};
struct RootEntry
{
char DIR_Name[11];
uchar DIR_Attr;
uchar reserve[10];
ushort DIR_WrtTime;
ushort DIR_WrtDate;
ushort DIR_FstClus;
uint DIR_FileSize;
};
#pragma pack(pop)
void PrintHeader(Fat12Header& rf, QString p)
{
QFile file(p);
if( file.open(QIODevice::ReadOnly) )
{
QDataStream in(&file);
file.seek(3);
in.readRawData(reinterpret_cast<char*>(&rf), sizeof(rf));
rf.BS_OEMName[7] = 0;
rf.BS_VolLab[10] = 0;
rf.BS_FileSysType[7] = 0;
qDebug() << "BS_OEMName: " << rf.BS_OEMName;
qDebug() << "BPB_BytsPerSec: " << hex << rf.BPB_BytsPerSec;
qDebug() << "BPB_SecPerClus: " << hex << rf.BPB_SecPerClus;
qDebug() << "BPB_RsvdSecCnt: " << hex << rf.BPB_RsvdSecCnt;
qDebug() << "BPB_NumFATs: " << hex << rf.BPB_NumFATs;
qDebug() << "BPB_RootEntCnt: " << hex << rf.BPB_RootEntCnt;
qDebug() << "BPB_TotSec16: " << hex << rf.BPB_TotSec16;
qDebug() << "BPB_Media: " << hex << rf.BPB_Media;
qDebug() << "BPB_FATSz16: " << hex << rf.BPB_FATSz16;
qDebug() << "BPB_SecPerTrk: " << hex << rf.BPB_SecPerTrk;
qDebug() << "BPB_NumHeads: " << hex << rf.BPB_NumHeads;
qDebug() << "BPB_HiddSec: " << hex << rf.BPB_HiddSec;
qDebug() << "BPB_TotSec32: " << hex << rf.BPB_TotSec32;
qDebug() << "BS_DrvNum: " << hex << rf.BS_DrvNum;
qDebug() << "BS_Reserved1: " << hex << rf.BS_Reserved1;
qDebug() << "BS_BootSig: " << hex << rf.BS_BootSig;
qDebug() << "BS_VolID: " << hex << rf.BS_VolID;
qDebug() << "BS_VolLab: " << rf.BS_VolLab;
qDebug() << "BS_FileSysType: " << rf.BS_FileSysType;
file.seek(510);
uchar b510 = 0;
uchar b511 = 0;
in.readRawData(reinterpret_cast<char*>(&b510), sizeof(b510));
in.readRawData(reinterpret_cast<char*>(&b511), sizeof(b511));
qDebug() << "Byte 510: " << hex << b510;
qDebug() << "Byte 511: " << hex << b511;
}
file.close();
}
RootEntry FindRootEntry(Fat12Header& rf, QString p, int i)
{
RootEntry ret = {{0}};
QFile file(p);
if( file.open(QIODevice::ReadOnly) && (0 <= i) && (i < rf.BPB_RootEntCnt) )
{
QDataStream in(&file);
file.seek(19 * rf.BPB_BytsPerSec + i * sizeof(RootEntry));
in.readRawData(reinterpret_cast<char*>(&ret), sizeof(ret));
}
file.close();
return ret;
}
RootEntry FindRootEntry(Fat12Header& rf, QString p, QString fn)
{
RootEntry ret = {{0}};
for(int i=0; i<rf.BPB_RootEntCnt; i++)
{
RootEntry re = FindRootEntry(rf, p, i);
if( re.DIR_Name[0] != '\0' )
{
int d = fn.lastIndexOf(".");
QString name = QString(re.DIR_Name).trimmed();
if( d >= 0 )
{
QString n = fn.mid(0, d);
QString p = fn.mid(d + 1);
if( name.startsWith(n) && name.endsWith(p) )
{
ret = re;
break;
}
}
else
{
if( fn == name )
{
ret = re;
break;
}
}
}
}
return ret;
}
void PrintRootEntry(Fat12Header& rf, QString p)
{
for(int i=0; i<rf.BPB_RootEntCnt; i++)
{
RootEntry re = FindRootEntry(rf, p, i);
if( re.DIR_Name[0] != '\0' )
{
qDebug() << i << ":";
qDebug() << "DIR_Name: " << hex << re.DIR_Name;
qDebug() << "DIR_Attr: " << hex << re.DIR_Attr;
qDebug() << "DIR_WrtDate: " << hex << re.DIR_WrtDate;
qDebug() << "DIR_WrtTime: " << hex << re.DIR_WrtTime;
qDebug() << "DIR_FstClus: " << hex << re.DIR_FstClus;
qDebug() << "DIR_FileSize: " << hex << re.DIR_FileSize;
}
}
}
QVector<ushort> ReadFat(Fat12Header& rf, QString p)
{
QFile file(p);
int size = rf.BPB_BytsPerSec * 9;
uchar* fat = new uchar[size];
QVector<ushort> ret(size * 2 / 3, 0xFFFF);
if( file.open(QIODevice::ReadOnly) )
{
QDataStream in(&file);
file.seek(rf.BPB_BytsPerSec * 1);
in.readRawData(reinterpret_cast<char*>(fat), size);
for(int i=0, j=0; i<size; i+=3, j+=2)
{
ret[j] = static_cast<ushort>((fat[i+1] & 0x0F) << 8) | fat[i];
ret[j+1] = static_cast<ushort>(fat[i+2] << 4) | ((fat[i+1] >> 4) & 0x0F);
}
}
file.close();
delete[] fat;
return ret;
}
QByteArray ReadFileContent(Fat12Header& rf, QString p, QString fn)
{
QByteArray ret;
RootEntry re = FindRootEntry(rf, p, fn);
if( re.DIR_Name[0] != '\0' )
{
QVector<ushort> vec = ReadFat(rf, p);
QFile file(p);
if( file.open(QIODevice::ReadOnly) )
{
char buf[512] = {0};
QDataStream in(&file);
int count = 0;
ret.resize(re.DIR_FileSize);
for(int i=0, j=re.DIR_FstClus; j<0xFF7; i+=512, j=vec[j])
{
file.seek(rf.BPB_BytsPerSec * (33 + j - 2));
in.readRawData(buf, sizeof(buf));
for(uint k=0; k<sizeof(buf); k++)
{
if( count < ret.size() )
{
ret[i+k] = buf[k];
count++;
}
}
}
}
file.close();
}
return ret;
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QString img = "data.img";
Fat12Header f12;
qDebug() << "Read Header:";
PrintHeader(f12, img);
qDebug() << endl;
qDebug() << "Print File Content:";
QString content = QString(ReadFileContent(f12, img, "TEST.TXT"));
qDebug() << content;
return a.exec();
}
读取test.txt文件的结果如下:

读取LOADER.BIN文件的结果如下:

读取的内容和我们写入的内容是一致的。
