第一章(NTFS格式及USN日志)
背景介绍
- Windows平台的Everything文件查找速度非常快,优势在于利用了NTFS的USN日志,以及Windows上的文件监测机制
- 我们也可以仿照类似原理,通过查询USN日志、监测Windows平台文件修改、使用SQLite数据库存储文件节点,并提供文件信息查询功能

项目仓库
- 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日志,快速获取系统中的所有文件节点信息,并建立相应的数据库以供查询
相关代码
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;
}
-
- 控制系统生成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;
}
-
- 查询系统生成的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;
}
-
- 查询具体的USN日志,其中包含了所有文件的节点信息,包括了文件节点的Reference Number,Parent Reference Number等等,其类似于一个父子链表,通过Reference Number指定了文件之间的父子关系(目录和目录内的文件)。此处获取所有文件节点信息后,还需要我们手动为所有节点排序,获得文件的详细路径.
HRESULT MiniThing::RecordUsn(VOID)
{
MFT_ENUM_DATA med = { 0, 0, m_usnInfo.NextUsn };
med.MaxMajorVersion = 2;
char buffer[0x1000];
DWORD usnDataSize = 0;
PUSN_RECORD pUsnRecord;
while (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)
{
wchar_t* pWchar = new wchar_t[pUsnRecord->FileNameLength / 2 + 1];
memcpy(pWchar, pUsnRecord->FileName, pUsnRecord->FileNameLength);
pWchar[pUsnRecord->FileNameLength / 2] = 0x00;
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;
DWORD recordLen = pUsnRecord->RecordLength;
dwRetBytes -= recordLen;
pUsnRecord = (PUSN_RECORD)(((PCHAR)pUsnRecord) + recordLen);
}
med.StartFileReferenceNumber = *(USN*)&buffer;
}
return S_OK;
}
-
- 至此,所有文件节点信息已经获取,需要对于信息进行排序,下一章再叙述。