在上一节中,我们成功的将虚拟软盘进行了格式化,而且将文件存入其中,那么如何在根目录区中查找文件呢?为了方便起见,再次给出FAT12文件系统的格式,如下所示:
在FAT12文件系统中,一簇包含一个扇区。从上图可见,目录文件项的偏移为19个扇区,其本身大小占用了14个扇区。根目录区中是一个一个的目录项,目录项的具体成员与分布如下所示:
每一个目录项代表根目录中一个文件的索引,其大小为32字节,根据目录项中文件开始的簇号和文件大小,我们可以找到文件的具体内容。下面我们先来读取一下FAT12文件系统根目录的信息,步骤如下:
1、创建RootEntry结构体类型
2、使用文件流顺序读取每个项的内容
3、解析并打印相关的信息
在Qt creater中编写程序如下:
1 #include <QtCore/QCoreApplication> 2 #include <QFile> 3 #include <QDataStream> 4 #include <QDebug> 5 #include <QVector> 6 #include <QByteArray> 7 8 #pragma pack(push) 9 #pragma pack(1) 10 11 struct Fat12Header 12 { 13 char BS_OEMName[8]; 14 ushort BPB_BytsPerSec; 15 uchar BPB_SecPerClus; 16 ushort BPB_RsvdSecCnt; 17 uchar BPB_NumFATs; 18 ushort BPB_RootEntCnt; 19 ushort BPB_TotSec16; 20 uchar BPB_Media; 21 ushort BPB_FATSz16; 22 ushort BPB_SecPerTrk; 23 ushort BPB_NumHeads; 24 uint BPB_HiddSec; 25 uint BPB_TotSec32; 26 uchar BS_DrvNum; 27 uchar BS_Reserved1; 28 uchar BS_BootSig; 29 uint BS_VolID; 30 char BS_VolLab[11]; 31 char BS_FileSysType[8]; 32 }; 33 34 struct RootEntry 35 { 36 char DIR_Name[11]; 37 uchar DIR_Attr; 38 uchar reserve[10]; 39 ushort DIR_WrtTime; 40 ushort DIR_WrtDate; 41 ushort DIR_FstClus; 42 uint DIR_FileSize; 43 }; 44 45 #pragma pack(pop) 46 47 void PrintHeader(Fat12Header& rf, QString p) 48 { 49 QFile file(p); 50 51 if( file.open(QIODevice::ReadOnly) ) 52 { 53 QDataStream in(&file); 54 55 file.seek(3); 56 57 in.readRawData(reinterpret_cast<char*>(&rf), sizeof(rf)); 58 59 rf.BS_OEMName[7] = 0; 60 rf.BS_VolLab[10] = 0; 61 rf.BS_FileSysType[7] = 0; 62 63 qDebug() << "BS_OEMName: " << rf.BS_OEMName; 64 qDebug() << "BPB_BytsPerSec: " << hex << rf.BPB_BytsPerSec; 65 qDebug() << "BPB_SecPerClus: " << hex << rf.BPB_SecPerClus; 66 qDebug() << "BPB_RsvdSecCnt: " << hex << rf.BPB_RsvdSecCnt; 67 qDebug() << "BPB_NumFATs: " << hex << rf.BPB_NumFATs; 68 qDebug() << "BPB_RootEntCnt: " << hex << rf.BPB_RootEntCnt; 69 qDebug() << "BPB_TotSec16: " << hex << rf.BPB_TotSec16; 70 qDebug() << "BPB_Media: " << hex << rf.BPB_Media; 71 qDebug() << "BPB_FATSz16: " << hex << rf.BPB_FATSz16; 72 qDebug() << "BPB_SecPerTrk: " << hex << rf.BPB_SecPerTrk; 73 qDebug() << "BPB_NumHeads: " << hex << rf.BPB_NumHeads; 74 qDebug() << "BPB_HiddSec: " << hex << rf.BPB_HiddSec; 75 qDebug() << "BPB_TotSec32: " << hex << rf.BPB_TotSec32; 76 qDebug() << "BS_DrvNum: " << hex << rf.BS_DrvNum; 77 qDebug() << "BS_Reserved1: " << hex << rf.BS_Reserved1; 78 qDebug() << "BS_BootSig: " << hex << rf.BS_BootSig; 79 qDebug() << "BS_VolID: " << hex << rf.BS_VolID; 80 qDebug() << "BS_VolLab: " << rf.BS_VolLab; 81 qDebug() << "BS_FileSysType: " << rf.BS_FileSysType; 82 83 file.seek(510); 84 85 uchar b510 = 0; 86 uchar b511 = 0; 87 88 in.readRawData(reinterpret_cast<char*>(&b510), sizeof(b510)); 89 in.readRawData(reinterpret_cast<char*>(&b511), sizeof(b511)); 90 91 qDebug() << "Byte 510: " << hex << b510; 92 qDebug() << "Byte 511: " << hex << b511; 93 } 94 95 file.close(); 96 } 97 98 RootEntry FindRootEntry(Fat12Header& rf, QString p, int i) 99 { 100 RootEntry ret = {{0}}; 101 102 QFile file(p); 103 104 if( file.open(QIODevice::ReadOnly) && (0 <= i) && (i < rf.BPB_RootEntCnt) ) 105 { 106 QDataStream in(&file); 107 108 file.seek(19 * rf.BPB_BytsPerSec + i * sizeof(RootEntry)); 109 110 in.readRawData(reinterpret_cast<char*>(&ret), sizeof(ret)); 111 } 112 113 file.close(); 114 115 return ret; 116 } 117 156 void PrintRootEntry(Fat12Header& rf, QString p) 157 { 158 for(int i=0; i<rf.BPB_RootEntCnt; i++) 159 { 160 RootEntry re = FindRootEntry(rf, p, i); 161 162 if( re.DIR_Name[0] != '\0' ) 163 { 164 qDebug() << i << ":"; 165 qDebug() << "DIR_Name: " << hex << re.DIR_Name; 166 qDebug() << "DIR_Attr: " << hex << re.DIR_Attr; 167 qDebug() << "DIR_WrtDate: " << hex << re.DIR_WrtDate; 168 qDebug() << "DIR_WrtTime: " << hex << re.DIR_WrtTime; 169 qDebug() << "DIR_FstClus: " << hex << re.DIR_FstClus; 170 qDebug() << "DIR_FileSize: " << hex << re.DIR_FileSize; 171 } 172 } 173 } 174 245 int main(int argc, char *argv[]) 246 { 247 QCoreApplication a(argc, argv); 248 QString img = "E:\\a.img"; 249 Fat12Header f12; 250 qDebug() << "Print Header:"; 251 252 PrintHeader(f12, img); 253 254 qDebug() << endl; 255 256 qDebug() << "Print Root Entry"; 257 PrintRootEntry(f12, img); 258 259 return a.exec(); 260 }
以上程序中,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,即表示文件结束了。以上就是一个文件内容读取的过程,每一个FAT表项的连接类似于一个链表。