第四載、FAT12文件系統剖析2

在上一章節《FAT12文件系統剖析1》中,我們把a.img啓動軟盤使用FreeDos系統格式化爲FAT12文件系統的組織方式,並且向a.img軟盤寫入兩個文件,同時通過FreeDos操作系統也查看了a.img軟盤中的文件。之所以這麼做,是因爲主引導程序不可以超過512字節,所以主引導程序需要在基本的初始化工作完成後加載啓動介質(這裏是軟盤a.img)中的啓動程序到內存,然後跳轉到對應內存處執行。

上一節我們是通過FreeDos操作系統來查看a.img軟盤中FAT12根目錄的目標文件,那麼在程序中我們怎去判斷目標文件是否存在FAT12根目錄中?要解決這個問題,我們首先來了解FAT12的根目錄在文件系統中的位置

上圖中直接給了計算根目錄大小的公式:根目錄個數 * 每個根目錄項大小 / 每個扇區大小 = 根目錄佔用的扇區數量,
其中根目錄個數在上一章節我們已經求出,爲0xe0,十進制爲224,不熟悉的可以參考上一章節。每個根目錄項大小爲32字節,每個扇區大小爲512字節,那麼有 224 * 32 / 512 = 14,即根目錄文件項在FAT12文件系統中總共佔用14個扇區,也就是7168字節。

除了上邊說的根目錄區,我們還需要了解下根目錄項,根目錄由根目錄項組成,上文中的224,就是FAT12文件系統有224個根目錄項,一個根目錄項代表根目錄中的一個文件索引,每個根目錄項中,包含了這個文件的基本信息,如下:

下邊需要做的實驗,便是讀取FAT12中每個根目錄項的內容並打印(代碼在上章代碼基礎上增加更目錄項的打印)

#include <QtCore/QCoreApplication>
#include <QFile>
#include <QDataStream>
#include <QDebug>

#pragma pack(push)
#pragma pack(1)

struct Fat12Header
{
    char BS_OEMName[8];
    ushort BPB_BytsPerSec;  //每扇區字節數,默認512字節
    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);   //偏移開頭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);  //偏移512字節的最後兩字節處

        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);

    //BPB_RootEntCnt爲最大根目錄文件數
    if( file.open(QIODevice::ReadOnly) && (0 <= i) && (i < rf.BPB_RootEntCnt) )
    {
        QDataStream in(&file);

        //定位到19扇區的各個根目錄項開始處
        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;
        }
    }
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString imgPath = "E:\\data.img";

    Fat12Header f12;

    qDebug() << "Read Header:";
    PrintHeader(f12, imgPath);     //打印FAT12系統頭信息
    qDebug() << endl;

    qDebug() << "Print Root Entry:";
    PrintRootEntry(f12, imgPath);   //打印所有根目錄項信息
    qDebug() << endl;

    qDebug() << "Print File LOADER.BIN:";
    RootEntry ret = FindRootEntry(f12, imgPath, "LOADER.BIN");   //打印文件loader.bin對應的根目錄項信息
    if( ret.DIR_Name[0] != '\0' )
    {
        qDebug() << "DIR_Name: " << hex << ret.DIR_Name;
        qDebug() << "DIR_Attr: " << hex << ret.DIR_Attr;
        qDebug() << "DIR_WrtDate: " << hex << ret.DIR_WrtDate;
        qDebug() << "DIR_WrtTime: " << hex << ret.DIR_WrtTime;
        qDebug() << "DIR_FstClus: " << hex << ret.DIR_FstClus;
        qDebug() << "DIR_FileSize: " << hex << ret.DIR_FileSize;
    }
    qDebug() << endl;

    return a.exec();
}

運行結果如下:

從輸出看到,已經成功打印了我們上章節往a.img軟盤拷貝的兩個文件,test.txt和loader.bin ,置於其他兩個文件輸出,暫時不需要理會,這是FAT12文件系統默認的處理方式;同時也可以打印指定的文件信息。

以上我們打印了根目錄目錄項的信息和指定文件的文件信息,那麼下邊需要做的就是讀取文件指定文件內容,在上述讀取的目錄項信息中,對我們比較有用的是如下三個成員:

理論上有了文件名、文件數據存儲起始位置、文件大小,就可以讀取文件的內容,但是在FAT12中,需要知道的是:文件數據不是連續存儲的,可能是存儲於不同的扇區中,也就是分散存儲的。爲了讀取分散在不同扇區的內容,需要藉助FAT表項來組織存儲在不同扇區上的數據,這裏可以理解爲數據結構中的鏈表,雖然是分散的,但是讀取過程給人的感覺是讀的一片連續的內存。

FAT12文件系統中有兩個FAT表項(FAT1和FAT2),他們內容是一樣的,這裏我們只關注FAT1即可,下圖爲FAT表與數據區物理組織示意圖:

當我們讀取一個文件項的內容時,可以獲取DIR_FstClus(即存儲的第一個扇區的位置),這裏用C表示,當我們讀完C內存處的數據庫時,需要讀下一個內存數據,對應到FAT表中查找位置,得到內存O的地址,然後讀取數據區中O內存處的數據,一次類推,直到最後一個內存S對應的FAT表項沒有內存地址(讀取結束),這個過程,可以把數據區的C->O->Z->Q->S共5片內存的數據全部讀取完成。FAT表與數據區的關係更直觀一點則如下邏輯示意圖:

邏輯示意圖跟數據結構裏邊的鏈表是一樣的。

以上是FAT表與數據區的基本關係,下邊就通過FAT表來讀取a.img軟盤中的文件內容,在此之前有一些小點需要注意:


以下是代碼,在上文代碼基礎上增加兩個函數:獲取FAT表ReadFat、讀取文件內容ReadFileContent
 

#include <QtCore/QCoreApplication>
#include <QFile>
#include <QDataStream>
#include <QDebug>

#pragma pack(push)
#pragma pack(1)

struct Fat12Header
{
    char BS_OEMName[8];
    ushort BPB_BytsPerSec;  //每扇區字節數,默認512字節
    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);   //偏移開頭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);  //偏移512字節的最後兩字節處

        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);

    //BPB_RootEntCnt爲最大根目錄文件數
    if( file.open(QIODevice::ReadOnly) && (0 <= i) && (i < rf.BPB_RootEntCnt) )
    {
        QDataStream in(&file);

        //定位到19扇區的各個根目錄項開始處
        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;
        }
    }
}

//獲取FAT表
QVector<ushort> ReadFat(Fat12Header& rf, QString p)
{
    QFile file(p);
    int size = rf.BPB_BytsPerSec * 9; //FAT表佔用的大小(9個扇區字節)
    uchar* fat = new uchar[size];
    QVector<ushort> ret(size * 2 / 3, 0xFFFF); //每個FAT表項佔用1.5字節,FAT表個數爲: 佔用內存 / 1.5,默認值爲0xFFFF

    if( file.open(QIODevice::ReadOnly) )
    {
        QDataStream in(&file);

        file.seek(rf.BPB_BytsPerSec * 1); //定位到第一個扇區(FAT表起始扇區)

        in.readRawData(reinterpret_cast<char*>(fat), size); //讀取FAT表內容

        //分配規劃FAT表
        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); //獲取FAT表
        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])
            {
                //定位到文件數據扇區(33區),數據區起始地址對應編號爲2,所以要-2
                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 imgPath = "E:\\data.img";

    Fat12Header f12;

    qDebug() << "Read Header:";
    PrintHeader(f12, imgPath);     //打印FAT12系統頭信息
    qDebug() << endl;

    qDebug() << "Print Root Entry:";
    PrintRootEntry(f12, imgPath);   //打印所有根目錄項信息
    qDebug() << endl;

    qDebug() << "Print File LOADER.BIN:";
    RootEntry ret = FindRootEntry(f12, imgPath, "LOADER.BIN");   //打印文件loader.bin對應的根目錄項信息
    if( ret.DIR_Name[0] != '\0' )
    {
        qDebug() << "DIR_Name: " << hex << ret.DIR_Name;
        qDebug() << "DIR_Attr: " << hex << ret.DIR_Attr;
        qDebug() << "DIR_WrtDate: " << hex << ret.DIR_WrtDate;
        qDebug() << "DIR_WrtTime: " << hex << ret.DIR_WrtTime;
        qDebug() << "DIR_FstClus: " << hex << ret.DIR_FstClus;
        qDebug() << "DIR_FileSize: " << hex << ret.DIR_FileSize;
    }
    qDebug() << endl;

    qDebug() << "Print File Content:";

    QString content = QString(ReadFileContent(f12, imgPath, "TEST.TXT"));

    qDebug() << content;

    return a.exec();
}
main函數中調用讀取文件內容函數ReadFileContent,讀取TEST.TXT文件內容

如上便是TEXT.TXT文件中的內容。

如果對文件內容的讀取不大理解,暫時不需要深究,只要知道FAT12文件系統是通過FAT表來將不同扇區的文件數據組織起來的,也就是數據結構中的單鏈表思想。

總結:
    1、FAT12根目錄記錄了文件的文件名起始簇號文件長度
    2、通過查找根目錄區能夠確定是否存在目標文件
    3、FAT12文件數據離散的分佈於存儲介質不同的扇區中
    4、文件數據通過FAT表進行關聯,採用了單鏈表的思想

學自 --《狄泰軟件學院》- 門徒操作系統

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章