`
univasity
  • 浏览: 800652 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

Everything研究之读取NTFS下的USN日志文件(2)

阅读更多

续>>

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

 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 FirstUsnNextUsn 是我们后续操作需要用到的。

 

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

]]

 

 

给出一个实现参考 :

bool getBasicInfoSuccess = false;

/**
 * step 04. 获取USN日志基本信息(用于后续操作)
 * msdn:http://msdn.microsoft.com/en-us/library/aa364583%28v=VS.85%29.aspx
 */
DWORD br;
status = DeviceIoControl(hVol,
                         FSCTL_QUERY_USN_JOURNAL,
                         NULL,
                         0,
			 &UsnInfo,
			 sizeof(UsnInfo),
			 &br,
			 NULL);

if(0!=status){
    getBasicInfoSuccess = true;
}else{
    printf("获取USN日志基本信息失败 —— status:%x error:%d\n", status, GetLastError());
}

 

第五步:列出 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, see FSCTL_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;

 

其中的FirstUsnNextUsn 就分别对应了LowUsnHighUsn.

]]

 

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

这里给出一个实现参考:

 

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

 

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

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

 

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

#define BUF_LEN 4096

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

DWORD usnDataSize;

PUSN_RECORD UsnRecord;

while (0!=DeviceIoControl(hVol,
                          FSCTL_ENUM_USN_DATA,
                          &med,
                          sizeof (med),
                          buffer,
                          BUF_LEN,
                          &usnDataSize,
                          NULL))
{

    DWORD dwRetBytes = usnDataSize - sizeof (USN);

    // 找到第一个 USN 记录
    // from MSDN(http://msdn.microsoft.com/en-us/library/aa365736%28v=VS.85%29.aspx ):
    // return a USN followed by zero or more change journal records, each in a USN_RECORD structure.
    UsnRecord = (PUSN_RECORD)(((PCHAR)buffer)+sizeof (USN));

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

    while (dwRetBytes>0){

        // 打印获取到的信息
        const int strLen = UsnRecord->FileNameLength;
        char fileName[MAX_PATH] = {0};
        WideCharToMultiByte(CP_OEMCP,NULL,UsnRecord->FileName,strLen/2,fileName,strLen,NULL,FALSE);

       printf("FileName: %s\n" , fileName);
       // 下面两个 file reference number 可以用来获取文件的路径信息
       printf("FileReferenceNumber: %xI64\n" , UsnRecord->FileReferenceNumber);
       printf("ParentFileReferenceNumber: %xI64\n" , UsnRecord->ParentFileReferenceNumber);
       printf("\n" );

       // 获取下一个记录
       DWORD recordLen = UsnRecord->RecordLength;
       dwRetBytes -= recordLen;
       UsnRecord = (PUSN_RECORD)(((PCHAR)UsnRecord)+recordLen);
    
    }

    // 获取下一页数据, MTF 大概是分多页来储存的吧?
    // 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;

} 
 

 

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

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

 

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

 

第六步:删除 USN Journal 文件

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

 

实现参考:

/**
 * step 06. 删除 USN 日志文件 ( 当然也可以不删除 )
 */
DELETE_USN_JOURNAL_DATA dujd;
dujd.UsnJournalID = UsnInfo.UsnJournalID;
dujd.DeleteFlags = USN_DELETE_FLAG_DELETE;

int status = DeviceIoControl(hVol,
                             FSCTL_DELETE_USN_JOURNAL,
                             &dujd,
                             sizeof (dujd),
                             NULL,
                             0,
                             &br,
                             NULL);

if (0!=status){
    printf(" 成功删除 USN 日志文件 !\n" );
}else {
    printf(" 删除 USN 日志文件失败 —— status:%x error:%d\n" , status, GetLastError());
} 
 

 

拓展实现:通过 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使用。欢迎交流。

 

9
0
分享到:
评论
3 楼 univasity 2010-12-30  
lslin 写道
博主你好!
一直都在使用Everything,实在是太赞了。以前知道Everything的大概原理,但是自己却不回实现,看了博主的介绍后对受益匪浅。
不过,在初始化USN日记的时候,我遇到了以下问题(我用的是VC++6.0编译器):

error C2065: 'CREATE_USN_JOURNAL_DATA' : undeclared identifier
error C2146: syntax error : missing ';' before identifier 'cujd'
error C2065: 'cujd' : undeclared identifier
error C2228: left of '.MaximumSize' must have class/struct/union type
error C2228: left of '.AllocationDelta' must have class/struct/union type
error C2065: 'FSCTL_CREATE_USN_JOURNAL' : undeclared identifier

我已经在原文件中加入了以下语句:
#include <winioctl.h>

请问博主知道是怎么回事吗?
我看了一下你提供的源代码,发现你只是include了Windows.h,这样也可以使用'CREATE_USN_JOURNAL_DATA' 这个结构体吗?
谢谢!


提示未定义的错误。我用2010的,可能版本问题。

这些结构体如果没有可以自己定义,具体参考MSDN,直接将结构体代码拷贝到使用前的位置就可以。

我引用的这些是被定义在一个叫WinIoCtl.h的头文件。你需要的结构体如下:

#define FSCTL_CREATE_USN_JOURNAL        CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 57,  METHOD_NEITHER, FILE_ANY_ACCESS) // CREATE_USN_JOURNAL_DATA,

typedef struct {

    DWORDLONG MaximumSize;
    DWORDLONG AllocationDelta;

} CREATE_USN_JOURNAL_DATA, *PCREATE_USN_JOURNAL_DATA;

VC++6下我暂时无法测试,希望能帮到你。
2 楼 lslin 2010-12-25  
博主你好!
一直都在使用Everything,实在是太赞了。以前知道Everything的大概原理,但是自己却不回实现,看了博主的介绍后对受益匪浅。
不过,在初始化USN日记的时候,我遇到了以下问题(我用的是VC++6.0编译器):

error C2065: 'CREATE_USN_JOURNAL_DATA' : undeclared identifier
error C2146: syntax error : missing ';' before identifier 'cujd'
error C2065: 'cujd' : undeclared identifier
error C2228: left of '.MaximumSize' must have class/struct/union type
error C2228: left of '.AllocationDelta' must have class/struct/union type
error C2065: 'FSCTL_CREATE_USN_JOURNAL' : undeclared identifier

我已经在原文件中加入了以下语句:
#include <winioctl.h>

请问博主知道是怎么回事吗?
我看了一下你提供的源代码,发现你只是include了Windows.h,这样也可以使用'CREATE_USN_JOURNAL_DATA' 这个结构体吗?
谢谢!
1 楼 laorer 2010-11-08  
嗯,当时也想看下 everything的实现机制,不过太懒了,一直没去找,多谢博主

相关推荐

Global site tag (gtag.js) - Google Analytics