续>>

/******************************************

 2010.11.10更新了代码,调整了一处地方,lowUsn的设置。

 

******************************************/

 

第四步:获取 USN Journal 文件的基本信息

MSDN: http://msdn.microsoft.com/en-us/library/aa364583%28v=VS.85%29.aspx

 

[[

他是这么一个结构:

typedef struct {

  DWORDLONG UsnJournalID;

  USN       FirstUsn;

  USN       NextUsn;

  USN       LowestValidUsn;

  USN       MaxUsn;

  DWORDLONG MaximumSize;

  DWORDLONG AllocationDelta;

} USN_JOURNAL_DATA, *PUSN_JOURNAL_DATA;

 

其中的 UsnJournalID ,FirstUsn ,NextUsn 是我们后续操作需要用到的。

 

获取他很简单,通过DeviceIoControl() 配合 FSCTL_QUERY_USN_JOURNAL 来实现。

]]

 

 

给出一个实现参考 :

Cpp代码  收藏代码

  1. bool getBasicInfoSuccess = false;  

  2.   

  3. /** 

  4.  * step 04. 获取USN日志基本信息(用于后续操作) 

  5.  * msdn:http://msdn.microsoft.com/en-us/library/aa364583%28v=VS.85%29.aspx 

  6.  */  

  7. DWORD br;  

  8. status = DeviceIoControl(hVol,  

  9.                          FSCTL_QUERY_USN_JOURNAL,  

  10.                          NULL,  

  11.                          0,  

  12.              &UsnInfo,  

  13.              sizeof(UsnInfo),  

  14.              &br,  

  15.              NULL);  

  16.   

  17. if(0!=status){  

  18.     getBasicInfoSuccess = true;  

  19. }else{  

  20.     printf("获取USN日志基本信息失败 —— status:%x error:%d\n", status, GetLastError());  

  21. }  

 

第五步:列出 USN Journal 文件的数据

前面提到 USN 日志的数据是以 USN_RECORD 形式储存的,在 MSDN 的介绍:

http://msdn.microsoft.com/en-us/library/aa365722%28VS.85%29.aspx

[[

同时分享一个网上找到的带中文注释的 USN_RECORD : ( 原文链接 )

typedef struct {

DWORD RecordLength; // 记录长度

WORD MajorVersion; // 主版本

WORD MinorVersion; // 次版本

DWORDLONG FileReferenceNumber; // 文件引用数

DWORDLONG ParentFileReferenceNumber; // 父目录引用数

USN Usn; // USN

LARGE_INTEGER TimeStamp; // 时间戳

DWORD Reason; // 原因

DWORD SourceInfo; // 源信息

DWORD SecurityId; // 安全

ID DWORD FileAttributes; // 文件属性

WORD FileNameLength; // 文件长度

WORD FileNameOffset; // penultimate of original version 2.0 < 文件名偏移 >

DWORD ExtraInfo1; // Hypothetically added in version 2.1

DWORD ExtraInfo2; // Hypothetically added in version 2.2

DWORD ExtraInfo3; // Hypothetically added in version 2.3

WCHAR FileName[1]; // variable length always at the end < 文件名第一位的指针 >

} USN_RECORD, *PUSN_RECORD;

 

其中 <> 是我补充上的,要实现类似 Everything 的搜索,我们主要关注下面几个参数:

FileReferenceNumber, ParentFileReferenceNumber -> 可以用来找回文件路径

FileNameLength, FileName[1] -> 储存了文件名

]]

 

需要枚举 USN 的数据,我们需要使用 DeviceIoControl() 和 FSCTL_ENUM_USN_DATA 配合。

MSDN : http://msdn.microsoft.com/en-us/library/aa364563%28v=VS.85%29.aspx

 

这里需要提供一个 MTF_ENUM_DATA 的结构作为参数 , 正如描述所指:

Enumerates the update sequence number (USN) data between two specified boundaries to obtain master file table (MFT) records.

 

这些 USN_RECORD 储存在一个叫 MFT 的表里, MTF_ENUM_DATA 的作用就是制定一个范围 。对这个我也不是很了解,不管他,把参数填上看看结果再说。

 

[[

关键就是如何构建一个 MFT_ENUM_DATA 的结构来指定获取数据的范围,我们来看一下这个结构:

typedef struct {

  DWORDLONG StartFileReferenceNumber;

  USN       LowUsn;

  USN       HighUsn;

} MFT_ENUM_DATA, *PMFT_ENUM_DATA;

 

最后我在 MSDN 中找到关键的一段话:

The first call to FSCTL_ENUM_USN_DATA during an enumeration must have the StartFileReferenceNumber member set to (DWORDLONG)0. Each call to FSCTL_ENUM_USN_DATA retrieves the starting point for the subsequent call as the first entry in the output buffer. Subsequent calls must be made with StartFileReferenceNumber set to this value. For more information, seeFSCTL_ENUM_USN_DATA .

大概意思是:

初始时将 StartFileReferenceNumber 设置为 0 ,然后每次进行枚举后获取起始点进行下一次操作,这个新的起始点包含在返回的 buffer 的开头。

 

我们不难发现,这里指的 buffer 就是 DeviceIoControl 函数中的:

BOOL DeviceIoControl(

  (HANDLE) hDevice,            // handle to volume

  FSCTL_ENUM_USN_DATA,         // dwIoControlCode

  (LPVOID) lpInBuffer,         // input buffer

  (DWORD) nInBufferSize,       // size of input buffer

  (LPVOID) lpOutBuffer,        // output buffer < 这里>

  (DWORD) nOutBufferSize,      // size of output buffer

  (LPDWORD) lpBytesReturned,   // number of bytes returned

  (LPOVERLAPPED) lpOverlapped  // OVERLAPPED structure

);

 

在 MFT_ENUM_DATA 中 还有另外两个参数:

LowUsn, HighUsn

分别制定范围的起止。

 

我们这里要获取所有USN 的数据,需要知道整个USN 的起止位置。可通过 DeviceIoControl() 配合FSCTL_QUERY_USN_JOURNAL 来获取一个 USN_JOURNAL_DATA 结构体,里面包含我们需要的信息 ( 后续的删除操作也要用到这些信息 ) :

typedef struct {

  DWORDLONG UsnJournalID;

  USN       FirstUsn;

  USN       NextUsn;

  USN       LowestValidUsn;

  USN       MaxUsn;

  DWORDLONG MaximumSize;

  DWORDLONG AllocationDelta;

} USN_JOURNAL_DATA, *PUSN_JOURNAL_DATA;

 

其中的FirstUsn 和NextUsn 就分别对应了LowUsn 和HighUsn.

]]

 

首先需要构建一个MFT_ENUM_DATA ,我们用上一步获取到的USN_JOURNAL_DATA 进行填充。  

这里给出一个实现参考:

 

Cpp代码  收藏代码

  1. MFT_ENUM_DATA med;  

  2. med.StartFileReferenceNumber = 0;  

  3. med.LowUsn = 0;//UsnInfo.FirstUsn; 这里经测试发现,如果用FirstUsn有时候不正确,导致获取到不完整的数据,还是直接写0好.  

  4. med.HighUsn = UsnInfo.NextUsn;   

 

 

有了MFT_ENUM_DATA ,就可以获取到USN 的数据了。

MSDN上也提供了一个例子可做参考 ,虽然是FSCTL_READ_USN_JOURNAL,和我们要执行的ENUM是类似的。

 

枚举USN数据,这里给出实现参考:

Cpp代码  收藏代码

  1. #define BUF_LEN 4096  

  2.   

  3. CHAR buffer[BUF_LEN]; // 用于储存记录的缓冲 , 尽量足够地大  

  4.   

  5. DWORD usnDataSize;  

  6.   

  7. PUSN_RECORD UsnRecord;  

  8.   

  9. while (0!=DeviceIoControl(hVol,  

  10.                           FSCTL_ENUM_USN_DATA,  

  11.                           &med,  

  12.                           sizeof (med),  

  13.                           buffer,  

  14.                           BUF_LEN,  

  15.                           &usnDataSize,  

  16.                           NULL))  

  17. {  

  18.   

  19.     DWORD dwRetBytes = usnDataSize - sizeof (USN);  

  20.   

  21.     // 找到第一个 USN 记录  

  22.     // from MSDN(http://msdn.microsoft.com/en-us/library/aa365736%28v=VS.85%29.aspx ):  

  23.     // return a USN followed by zero or more change journal records, each in a USN_RECORD structure.  

  24.     UsnRecord = (PUSN_RECORD)(((PCHAR)buffer)+sizeof (USN));  

  25.   

  26.     printf(" ********************************** \n" );  

  27.   

  28.     while (dwRetBytes>0){  

  29.   

  30.         // 打印获取到的信息  

  31.         const int strLen = UsnRecord->FileNameLength;  

  32.         char fileName[MAX_PATH] = {0};  

  33.         WideCharToMultiByte(CP_OEMCP,NULL,UsnRecord->FileName,strLen/2,fileName,strLen,NULL,FALSE);  

  34.   

  35.        printf("FileName: %s\n" , fileName);  

  36.        // 下面两个 file reference number 可以用来获取文件的路径信息  

  37.        printf("FileReferenceNumber: %xI64\n" , UsnRecord->FileReferenceNumber);  

  38.        printf("ParentFileReferenceNumber: %xI64\n" , UsnRecord->ParentFileReferenceNumber);  

  39.        printf("\n" );  

  40.   

  41.        // 获取下一个记录  

  42.        DWORD recordLen = UsnRecord->RecordLength;  

  43.        dwRetBytes -= recordLen;  

  44.        UsnRecord = (PUSN_RECORD)(((PCHAR)UsnRecord)+recordLen);  

  45.       

  46.     }  

  47.   

  48.     // 获取下一页数据, MTF 大概是分多页来储存的吧?  

  49.     // from MSDN(http://msdn.microsoft.com/en-us/library/aa365736%28v=VS.85%29.aspx ):  

  50.     // The USN returned as the first item in the output buffer is the USN of the next record number to be retrieved.  

  51.     // Use this value to continue reading records from the end boundary forward.  

  52.     med.StartFileReferenceNumber = *(USN *)&buffer;  

  53.   

  54. }   

 

 

现在只要对列出的数据进行筛选,基本就可以实现 Everything 的快速搜索了。

还需要结合索引的建立,才能达到更快的速度。

 

下面再介绍下如何删除 USN Journal 文件,当你不再需要他的时候。

 

第六步:删除 USN Journal 文件

MSDN 参考: http://msdn.microsoft.com/en-us/library/aa363928%28v=VS.85%29.aspx

 

实现参考:

Cpp代码  收藏代码

  1. /** 

  2.  * step 06. 删除 USN 日志文件 ( 当然也可以不删除 ) 

  3.  */  

  4. DELETE_USN_JOURNAL_DATA dujd;  

  5. dujd.UsnJournalID = UsnInfo.UsnJournalID;  

  6. dujd.DeleteFlags = USN_DELETE_FLAG_DELETE;  

  7.   

  8. int status = DeviceIoControl(hVol,  

  9.                              FSCTL_DELETE_USN_JOURNAL,  

  10.                              &dujd,  

  11.                              sizeof (dujd),  

  12.                              NULL,  

  13.                              0,  

  14.                              &br,  

  15.                              NULL);  

  16.   

  17. if (0!=status){  

  18.     printf(" 成功删除 USN 日志文件 !\n" );  

  19. }else {  

  20.     printf(" 删除 USN 日志文件失败 —— status:%x error:%d\n" , status, GetLastError());  

  21. }   

 

 

拓展实现:通过 File Reference Number 获取文件路径

这个这里暂时不讲了,感兴趣的可以参考文章最后给出的链接的实现。

C++上可以通过使用NtCreateFile()和NtQueryFileInformation()来实现路径获取。
基本MSDN上有比较详细的说明了。

===================================================================================================================
>>最后
附上一个完整的C++实现的例子:
  
 
NftsUsnJournalDemo_2010.11.10_.rar 

另外,还有一个在国外BLOG找到的C#实现的例子,比较完成,注释也很清晰。我也参考了不少。
原BLOG上是以文本贴出代码的,我这里整理成可编译的项目提供下载。
原文BLOG:
http://www.dreamincode.net/forums/blog/1017-stcroixskippers-blog/ 
整理的项目:
 UsnJournalProject_v1.3.rar


===================================================================================================================
以上都是出于个人兴趣的研究,接触C++实在迫不得已……Java某些部分的局限性太大了。


上面很多东西都是GOOGLE+MSDN完成的,若有疏忽请指教。


下一步准备将这些基本操作封装成DLL,再通过JNI供JAVA使用。欢迎交流。