> 文章列表 > 如何手写一个文件索引工具everything(第一章)

如何手写一个文件索引工具everything(第一章)

如何手写一个文件索引工具everything(第一章)

第一章(NTFS格式及USN日志)

背景介绍

  • Windows平台的Everything文件查找速度非常快,优势在于利用了NTFS的USN日志,以及Windows上的文件监测机制
  • 我们也可以仿照类似原理,通过查询USN日志、监测Windows平台文件修改、使用SQLite数据库存储文件节点,并提供文件信息查询功能
    如何手写一个文件索引工具everything(第一章)

项目仓库

  • https://gitee.com/alanosong/MiniThing

NTFS格式

  • NTFS(New Technology File System)是微软随Windows系统开发的一种文件格式,专门为网络和磁盘配额、文件加密等管理安全特性设计。比起FAT格式,NTFS属于一种较为新型的磁盘格式。
  • 比起FAT格式,NTFS文件格式支持更大的分区,可以达到2TB。而FAT32可支持的最大分区只有32GB。
  • NTFS可以更有效地管理磁盘空间,避免磁盘空间的浪费。NTFS采用了更小的簇组,利用率更高。
  • NTFS更加安全稳定。NTFS拥有许多安全性能方面的选项,还提供文件加密支持,保障数据的安全性。同时,NTFS还能有效阻止没有授权的用户访问文件。
  • NTFS可自动修复磁盘出错的信息。例如,在当Windows系统向NTFS分区写入文件时,会保留文件的一份拷贝,然后检查向磁盘中所写的文件是否与内存中的一致。如果出现不一致的情况,Windows就把相应的扇区标为坏扇区而不再使用它(簇重映射)。之后,Windows系统会通过内存中保留的文件重新拷贝写入磁盘。在磁盘读写发生错误时,NTFS会报告错误信息,并告知相应的应用程序数据已经丢失。

USN日志

  • USN Journal 相当于 NTFS 的秘书,为磁盘记录下改动的一切,并储存为 USN_RECORD 的格式。
  • 因此我们可以通过查询系统的USN日志,快速获取系统中的所有文件节点信息,并建立相应的数据库以供查询

相关代码

    1. 判断磁盘是否为NTFS格式,这是首要条件
BOOL MiniThing::IsNtfs(VOID)
{BOOL isNtfs = FALSE;char sysNameBuf[MAX_PATH] = { 0 };int len = WstringToChar(m_volumeName + L"\\\\", nullptr);char* pVol = new char[len];WstringToChar(m_volumeName + L"\\\\", pVol);BOOL status = GetVolumeInformationA(pVol,NULL,0,NULL,NULL,NULL,sysNameBuf,MAX_PATH);if (FALSE != status){std::cout << "File system name : " << sysNameBuf << std::endl;if (0 == strcmp(sysNameBuf, "NTFS")){isNtfs = true;}else{std::cout << "File system not NTFS format !!!" << std::endl;GetSystemError();}}return isNtfs;
}
    1. 控制系统生成USN记录,方便我们查询,这一步通过Win32的DeviceIoControl()接口来实现
HRESULT MiniThing::CreateUsn(VOID)
{HRESULT ret = S_OK;DWORD br;CREATE_USN_JOURNAL_DATA cujd;cujd.MaximumSize = 0;cujd.AllocationDelta = 0;BOOL status = DeviceIoControl(m_hVol,FSCTL_CREATE_USN_JOURNAL,&cujd,sizeof(cujd),NULL,0,&br,NULL);if (FALSE != status){std::cout << "Create usn file success" << std::endl;ret = S_OK;}else{std::cout << "Create usn file failed" << std::endl;GetSystemError();ret = E_FAIL;}return ret;
}
    1. 查询系统生成的USN相关信息,为下一步获取具体的文件日志做准备
HRESULT MiniThing::QueryUsn(VOID)
{HRESULT ret = S_OK;DWORD br;BOOL status = DeviceIoControl(m_hVol,FSCTL_QUERY_USN_JOURNAL,NULL,0,&m_usnInfo,sizeof(m_usnInfo),&br,NULL);if (FALSE != status){std::cout << "Query usn info success" << std::endl;}else{ret = E_FAIL;std::cout << "Query usn info failed" << std::endl;GetSystemError();}return ret;
}
    1. 查询具体的USN日志,其中包含了所有文件的节点信息,包括了文件节点的Reference Number,Parent Reference Number等等,其类似于一个父子链表,通过Reference Number指定了文件之间的父子关系(目录和目录内的文件)。此处获取所有文件节点信息后,还需要我们手动为所有节点排序,获得文件的详细路径.
HRESULT MiniThing::RecordUsn(VOID)
{MFT_ENUM_DATA med = { 0, 0, m_usnInfo.NextUsn };med.MaxMajorVersion = 2;// Used to record usn info, must big enoughchar buffer[0x1000];DWORD usnDataSize = 0;PUSN_RECORD pUsnRecord;// Find the first USN record// return a USN followed by zero or more change journal records, each in a USN_RECORD structurewhile (FALSE != DeviceIoControl(m_hVol,FSCTL_ENUM_USN_DATA,&med,sizeof(med),buffer,_countof(buffer),&usnDataSize,NULL)){DWORD dwRetBytes = usnDataSize - sizeof(USN);pUsnRecord = (PUSN_RECORD)(((PCHAR)buffer) + sizeof(USN));DWORD cnt = 0;while (dwRetBytes > 0){// Here FileNameLength may count in bytes, and each wchar_t occupy 2 byteswchar_t* pWchar = new wchar_t[pUsnRecord->FileNameLength / 2 + 1];memcpy(pWchar, pUsnRecord->FileName, pUsnRecord->FileNameLength);pWchar[pUsnRecord->FileNameLength / 2] = 0x00;// wcsncpy_s(pWchar, pUsnRecord->FileNameLength / 2, pUsnRecord->FileName, pUsnRecord->FileNameLength / 2);std::wstring fileNameWstr = WcharToWstring(pWchar);delete pWchar;UsnInfo usnInfo = { 0 };usnInfo.fileNameWstr = fileNameWstr;usnInfo.pParentRef = pUsnRecord->ParentFileReferenceNumber;usnInfo.pSelfRef = pUsnRecord->FileReferenceNumber;usnInfo.timeStamp = pUsnRecord->TimeStamp;m_usnRecordMap[usnInfo.pSelfRef] = usnInfo;// Get the next USN recordDWORD recordLen = pUsnRecord->RecordLength;dwRetBytes -= recordLen;pUsnRecord = (PUSN_RECORD)(((PCHAR)pUsnRecord) + recordLen);}// Get next page USN record// from MSDN(http://msdn.microsoft.com/en-us/library/aa365736%28v=VS.85%29.aspx ):  // The USN returned as the first item in the output buffer is the USN of the next record number to be retrieved.  // Use this value to continue reading records from the end boundary forward.  med.StartFileReferenceNumber = *(USN*)&buffer;}return S_OK;
}
    1. 至此,所有文件节点信息已经获取,需要对于信息进行排序,下一章再叙述。

KTV音响