Qt/C++ 了解NTFS文件系统,了解MFT(Master File Table)主文件表(一)

简单了解NTFS文件系统中的MFT(Master File Table)主文件表,介绍GitHub开源项目NTFS-File-Search,统计Qt库和Windows Api函数,以及使用NTFS-File-Search项目中的方法读取盘符所有目录文件速度示例。

系列文章目录

整个专栏系列是根据GitHub开源项目NTFS-File-Search获取分区所有文件/目录列表的思路。
具体的如下:

  1. Qt/C++ 了解NTFS文件系统,了解MFT(Master File Table)主文件表(一)
    介绍NTFS文件系统,对比通过MFT(Master File Table)主文件表获取数据的优劣,简单介绍开源项目NTFS-File-Search,以及了解借鉴NTFS系统中所参考的所有文章。
  2. Qt/C++ 了解NTFS文件系统,解析盘符引导扇区数据获取MFT(Master File Table)主文件表偏移地址
    读取$Boot引导分区扇区数据,获取单个簇大小(4096字节),$MFT元数据大小(1024字节)和$MFT元数据的起始簇号,计算出$MFT元数据在磁盘的偏移地址。
  3. Qt/C++ 了解NTFS文件系统,获取首张MFT表数据,解析文件记录头内容找到第一个属性偏移地址
    解析$MFT元数据结构,根据$MFT元数据文件记录头获取属性的偏移地址,用于后面解析0x80 $Data属性,获取Run List数据列表
  4. Qt/C++ 了解NTFS文件系统,解析MFT主文件表中的常驻属性与非常驻属性
    简单介绍$MFT元数据中的常驻属性与非常驻属性结构.
  5. Qt/C++ 了解NTFS文件系统,解析0x80 $Data属性,获取Run List数据列表
    根据0x80 $Data属性,找到存放所有$MFT元数据的区间列表(Run List数据列表)
  6. Qt/C++ 了解NTFS文件系统,遍历Run Lists数据列表,读取0x30 $FILE_NAME属性,获取所有文件/目录数据
    根据前面获取的 获取Run List数据列表,
    遍历所有Run List数据列表读取所有$MFT元数据,并解析 $FILE_NAME属性和$STANDARD_INFORMATION属性获取文件或目录的实际大小,名称,磁盘分配大小,记录号 ,文件属性等信息
  7. Qt/C++ 深入了解NTFS文件系统,解析0x90 $INDEX_ROOT和0xA0 $INDEX_ALLOCATION 属性,获取索引(例如目录)的B+树的根节点和子节点
    解析x90 $INDEX_ROOT和0xA0 $INDEX_ALLOCATION 属性结构,获取索引(例如目录)的B+树的根节点和子节点,即可通过MFT RECORD ID定位到MFT RECORD表,加载当前MFT RECORD的子目录/文件数据.
  8. Qt/C++ 深入了解NTFS文件系统,解析0x20 $ATTRIBUTE_LIST,获取MFT RECORD记录表的其他扩展属性和ID对应的地址偏移量
    在尝试遍历NTFS文件系统中的所有文件目录时,发现包含很多子文件的文件夹,并没有0xA0 $INDEX_ALLOCATION 属性,只有个0x20 $ATTRIBUTE_LIST属性,于是通过解析x20 $ATTRIBUTE_LIST属性获取其他单个MFT RECORD表无法包含的扩展属性.并且通过扩展属性中的MFT RECORD ID找到属性所在的MFT RECORD记录表


前言

在工作中,遇到需要快速扫描电脑系统盘符文件目录的功能,在使用多线程优化扫描后,客户还是觉得不满意,说是WizTree软件几秒就能扫描完所有文档,
我一看,好家伙,我100Gb的硬盘50多万个的文件9.92秒全扫描完毕,盲猜是驱动级别开发,直说实现不了。
后来了解到是通过读取NTFS文件系统的MFT(Master File Table)表获取的文件目录列表.

MFT(Master File Table,主文件表)是Windows操作系统中NTFS(New Technology File System,新技术文件系统)的关键组成部分,用于存储文件和目录的元数据信息。MFT类似于Unix和Linux系统中的inode,但在实现上有所不同。
MFT包含以下信息:

  • 文件和目录的属性:包括权限、所有者、创建时间、修改时间、访问时间等。
  • 文件数据的物理位置:MFT记录了文件数据在磁盘上的存储位置。
  • 文件名:MFT中存储了文件和目录的名称。

MFT的作用和重要性:

  • 高效性:MFT提供了高效的文件系统索引机制,允许系统快速访问和管理文件。
  • 节省空间:相比于在文件名中保存文件属性,使用MFT可以节省空间,尤其是在大量小文件存在的情况下。
  • 支持硬链接:通过MFT,多个文件名可以指向同一个MFT条目,实现硬链接的功能。
  • 支持文件系统的元数据管理:MFT存储了文件和目录的元数据信息,包括权限、所有者、时间戳等,这些信息对文件系统的管理和安全性至关重要。
  • 提高系统稳定性:MFT的使用可以提高文件系统的稳定性和可靠性,使文件系统更加高效地管理文件和目录。

总的来说,MFT是NTFS文件系统的核心组成部分,对于Windows文件系统的性能和可靠性具有重要意义。
摘要出自:MFT(Master File Table,主文件表)是Windows操作系统中NTFS(New Technology File System,新技术文件系统)的关键组成部分,用于存储文件和目录的元数据信息。MFT类似于Unix和Linux系统中的inode,但在实现上有所不同。

一开始我是从 Everything原理及实现 了解到Master File Table (MTF)这个技术点,
奈何这篇文章作者是用JAVA写的示例,看不大懂,
于是在github开始找找有没有使用C/C++读取MFT表数据的示例;
最终找到一个:NTFS-File-Search

NTFS-File-Search
相对轻量级的应用程序,用于快速查询位于NTFS卷上的文件/目录。它通过读取和解析位于MFT(主文件表)中的文件记录来工作。
为Windows操作系统设计
虽然Linux支持NTFS驱动器,但这个程序是专门为Windows设计的。
大多数与linux不兼容的代码只是因为Win32 API数据类型。因此,将数据类型和(很少的)函数调用转换为它们支持的对应项应该不会太困难

!感谢大佬,膜拜大佬!
有了这个示例的代码以及结合各篇介绍MFT的文章,那么学习读取解析MFT表结构就变得简单了,后面所有读取MFT表数据的代码示例基本都是出自NTFS-File-Search


实际对比

为了验证读取MFT表获取文件目录的数据有多快,我尝试使用QT自带的库和Windows Api函数来遍历系统盘文件目录,作一个对比。

1.使用Qt QDir获取所有文件/文件夹

使用QDir 遍历文件目录,代码如下(示例):

//! Qt版本统计文件或文件夹数量
void QTraversalDirectory(const QString dirPath)
{
    QDir dir(dirPath);
    //entryInfoList(QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Files)
    QFileInfoList entries = dir.entryInfoList((QDir::Filters)(QDir::NoDotAndDotDot | QDir::AllDirs| QDir::Files),QDir::Name);
    for (const QFileInfo &entry : entries) {
        if (entry.isDir()) {
            NumberFolders++;
            // 递归遍历子目录
            QTraversalDirectory(entry.absoluteFilePath());
        } else {
            NumberDocuments++;
            AllocatedSize+=entry.size();
        }
    }
}
  /*! 输出统计
  QDateTime(2024-08-31 14:01:08.642 中国标准时间 Qt::LocalTime)
  QDateTime(2024-08-31 14:02:24.099 中国标准时间 Qt::LocalTime)
  75
  [NumberFolders]  218370
  [NumberDocuments]  452870
  [AllocatedSize]  76198538442
  */

耗时大概75秒左右,而且也没有统计系统文件/文件夹,包括一些系统盘的隐藏文件夹或隐藏文件都没有统计,

2.使用Windows api函数获取盘符文件/文件夹

使用FindFirstFileFindNextFile函数遍历文件目录
代码如下(示例):

void TraversalDirectory(QString dirPath)
{
    //! 遍历通配符找到的相关文件
    //! https://learn.microsoft.com/zh-cn/windows/win32/api/fileapi/nf-fileapi-findfirstfilew
     QString rootdir=dirPath+"\\*";
     WIN32_FIND_DATA fileData;
     HANDLE hFind;
     LPCWSTR searchPath=utf8_to_wchar(rootdir.toStdString().c_str());
     hFind = FindFirstFile(searchPath, &fileData);
     if (hFind != INVALID_HANDLE_VALUE) {
         // 遍历文件
            do {
                 if(fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                 {
                     //! 文件夹目录
                     if((wcscmp(fileData.cFileName, L".") == 0 || wcscmp(fileData.cFileName, L"..") == 0))
                         continue;
                     QString driectory=QString::fromWCharArray(fileData.cFileName);
                     QString _rootpath=dirPath+"\\"+driectory;
                     TraversalDirectory(_rootpath);
                     NumberFolders+=1;
                 }
                 else
                 {
                     qint64 filesize= (qint64)(fileData.nFileSizeHigh * ((qint64)MAXDWORD+1)) + (qint64)fileData.nFileSizeLow;
                     AllocatedSize+=filesize;
                     NumberDocuments+=1;
                 }
            } while (FindNextFile(hFind, &fileData));
         // 关闭句柄
         FindClose(hFind);
     }
     sfree(searchPath);
}
/*! 输出统计
QDateTime(2024-08-31 13:42:30.556 中国标准时间 Qt::LocalTime)
QDateTime(2024-08-31 13:46:00.995 中国标准时间 Qt::LocalTime)
210s
[NumberFolders]  247408
[NumberDocuments]  591151
[AllocatedSize]  114432276945
*/

耗时大概210秒左右,所有的隐藏文件和隐藏的文件夹都获取到了,但是很费时。

3.使用NTFS-File-Search示例遍历目录

将NTFS-File-Search项目示例移植到Qt开发环境中,调用获取C盘目录,简单统计
代码如下(示例):

void Parint_ALLMFT()
{
    const WCHAR szVolume[7] = { L'\\', L'\\', L'.', L'\\', L'C', L':' };
    qDebug()<<"[szVolume] "<<QString::fromWCharArray(szVolume);
    QDateTime StartTime=QDateTime::currentDateTime();
    QDateTime endt=QDateTime::currentDateTime();
    qint64 NumberFolders=0;
    qint64 NumberDocuments=0;
    PNTFS_FILE_ENTRY	pFileResults;
    UINT64				nResultCount;
    g_pSourceVolume = new NTFS_Volume();
    if (!(g_pSourceVolume->Open(szVolume) && g_vSearcher.SetVolume(g_pSourceVolume)))
    {
        goto error;
    }
    //清除筛选条件
    g_vSearcher.ClearFileFilters();
    BOOL bSuccess = g_vSearcher.FindFiles(FILE_SEARCH_FLAG_FIND_ALL,
                                          &pFileResults,
                                          &nResultCount
                                          );
    PNTFS_FILE_ENTRY pEnumResult = pFileResults;
    qDebug()<<" nResultCount : "<<nResultCount;
    for (UINT64 i = 0; i < nResultCount; ++i)
    {
//        qDebug()<<"[MFT Record Number "<<QString::number(pEnumResult->MFTFileId.MftRecordIndex,10)<<"] -> "<<QString::fromWCharArray(pEnumResult->lpszFileName);
        if (pEnumResult->NextEntryOffset == 0) {
            break;
        }
        if(pEnumResult->IsDirectory)
            NumberFolders++;
        else
            NumberDocuments++;

        pEnumResult = POINTER_ADD(PNTFS_FILE_ENTRY, pFileResults, pEnumResult->NextEntryOffset);
    }
    endt=QDateTime::currentDateTime();
    qDebug()<<"Start : "<<StartTime;
    qDebug()<<"End : "<<endt;
    qDebug()<<StartTime.secsTo(endt)<<"s";
    qDebug()<<"[NumberFolders] "  <<QString::number(NumberFolders,10);
    qDebug()<<"[NumberDocuments] "<<QString::number(NumberDocuments,10);
    goto out;
error:
    qDebug("Error: failed to search volume files\n");
out:
    /* Close the volume */
    g_pSourceVolume->Close();
    delete g_pSourceVolume;
}


/*
Start :  QDateTime(2024-09-12 11:46:59.229 中国标准时间 Qt::LocalTime)
End :  QDateTime(2024-09-12 11:47:09.505 中国标准时间 Qt::LocalTime)
10s
[NumberFolders]  "282110"
[NumberDocuments]  "641776"
*/

耗时十几秒,获取了C盘所有的文件目录,这扫描速度确实快…


总结

通过读取MFT(Master File Table)主文件表,获取盘符文件目录结构,确实是最快的
可能由于NTFS版本等等问题,NTFS-File-Search示例中读取文件的分配大小和实际大小存在问题,也出现过无效文件的问题,只有通过QFileInfo重新获取文件大小。总体上是不影响使用,建议多参考借鉴学习。

参考文章

下一篇:Qt/C++ 了解NTFS文件系统,解析盘符引导扇区数据获取MFT(Master File Table)主文件表偏移地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

得鹿梦鱼、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值