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

Everything研究之快速获取USN记录的文件路径

阅读更多

<!-- 发觉越是没事干,记忆越差,乘还记得点什么,记录下以备份 -->


继上一篇关于USN的探索,我们能对USN进行了简单的操作,但是有一样基本的东西却没有做到——我们还无法获取到USN记录的文件路径,而这恰恰是非常必要的。

<开始探索>
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;


这是每个USN记录的结构,通过这个结构我们能获取到的文件名和其他一些信息,但并不包含其所在的路径。


前面的文章中曾稍微提到过其中的FileReferenceNumber和 ParentFileReferenceNumber是关键。


是有这么一种方法,通过微软提供的NtQueryInformationFile 函数获取,具体实现如下:

/// <summary>
/// GetPathByFileReferenceNumber() 通过文件的映射ID获取文件路径.
/// </summary>
/// <param name="hVol">HANDLE对象,指向驱动盘
/// </param>
/// <param name="frn">文件的映射ID,包含在USN_RECORD中
/// </param>
/// <param name="pathBuffer">储存返回值的char*
/// </param>
/// <param name="bufferSize">指定返回值的长度
/// </param>
/// <returns>BOOL
/// </returns>
/// <remarks>
/// 只支持NTFS3.0及以上的驱动盘,并且在Win7下运行该方法需要获取管理员权限
/// </remarks>
BOOL GetPathByFileReferenceNumber(__in HANDLE hVol, __in DWORDLONG frn, __out char* pathBuffer, __in int bufferSize)
{

     BOOL result = FALSE;

     HANDLE hFile;

     //printf("frn: %I64x\n", frn);
     // 将FileReferenceNumber转为UNICODE_STR
     UNICODE_STRING fidstr;
     CoverFileReferenceNumberToFileIdStr(frn, &fidstr);
     //ULONG fid[2] = {0x00000892, 0x00020000};//{i.nFileIndexLow, i.nFileIndexHigh};
    //UNICODE_STRING fidstr = {8, 8, (PWSTR) fid};

     // 构建用于寻找的OBJECT_ATTRIBUTES
     OBJECT_ATTRIBUTES oa = {0};
     oa.Length = sizeof(OBJECT_ATTRIBUTES);
     oa.ObjectName = &fidstr;
     oa.RootDirectory = hVol;
     oa.Attributes = OBJ_CASE_INSENSITIVE;
    //InitializeObjectAttributes (&oa, &fidstr, OBJ_CASE_INSENSITIVE, d, NULL);

     IO_STATUS_BLOCK ioStatusBlock = {0};
     // 通过FILE_ID打开文件,获取文件句柄
    ULONG status = NtCreatefile(&hFile,
                                  FILE_GENERIC_READ,
                                        &oa,
                                       &ioStatusBlock,
                                        NULL,
                                        FILE_ATTRIBUTE_READONLY,
                                        FILE_SHARE_READ | FILE_SHARE_WRITE,
                                        FILE_OPEN,
                                        FILE_OPEN_BY_FILE_ID | FILE_OPEN_FOR_BACKUP_INTENT,
                                        NULL,
                                        0);
        //printf("status: %X, handle: %x\n", status, hFile);
          //printf("error: %d, t: %x\n", GetLastError(), iosb);

          if(0==status){
              
               //FILE_NAME_INFORMATION* info = (FILE_NAME_INFORMATION*)malloc(BUF_LEN);
               //int allocSize = BUF_LEN;
               // 获取文件名称信息
               status = NtQueryInformationFile(hFile,
                                                &ioStatusBlock,
                                                       info,
                                                       allocSize,
                                                       FileNameInformation);

               if(0==status){
                   
                    // 获取到的名字是wchar*, 将其转为char*
                    int dwMinSize = (*info).FileNameLength;
                    WideCharToMultiByte(CP_OEMCP,NULL,(*info).FileName,dwMinSize/2,pathBuffer,dwMinSize,NULL,FALSE);

                    result = TRUE;
               }

               //free(info);
               CloseHandle(hFile);
          }

          return result;
}
 

 

虽然这是不错的方法,但是太慢了,简单测试了一下,20W个文件要14秒多,一台机器何止如此少的文件数...远远达不到Everything的快速。

但是MSDN和网上我都翻遍了,毫无收获。 那难道真的还有其他方法?


<寻觅新思路>

于是我将手头所有的信息汇集起来,将信息都打印到文本上仔细地审查了一遍。


下面是一部分打印出来的信息:
***********************
<盘符>
文件名
FileReferenceNumber - 对应地址
ParentFileReferenceNumber - 对应地址

<C:\>
Program Files
frn:281474976710716 - C:\Program Files
pfrn:1407374883553285 - C:\

Common Files
frn:281474976710717 - C:\Program Files\Common Files
pfrn:281474976710716 - C:\Program Files

microsoft shared
frn:281474976710718 - C:\Program Files\Common Files\microsoft shared
pfrn:281474976710717 - C:\Program Files\Common Files

<D:\>
Program Files
frn:281474976710691 - D:\Program Files
pfrn:1407374883553285 - D:\

Thunder Network
frn:281474976710692 - D:\Program Files\Thunder Network
pfrn:281474976710691 - D:\Program Files

Thunder
frn:281474976710693 - D:\Program Files\Thunder Network\Thunder
pfrn:281474976710692 - D:\Program Files\Thunder Network

<E:\>
实况8中超风云秋风DIY版
frn:281474976710694 - E:\实况8中超风云秋风DIY版
pfrn:1407374883553285 - E:\

WE8.exe
frn:281474976710698 - E:\实况8中超风云秋风DIY版\WE8.exe
pfrn:281474976710694 - E:\实况8中超风云秋风DIY版

**********************
首先注意下加粗的地方,指向的路径是根目录(盘符),不难发现他们对应的ReferenceNumber都是一致的,经测试无论U盘,外置硬盘还是删了USN再建,我发现都是一致的,而且就是1407374883553285,不知会不会和机器本身有关,具体有待验证,不过我们还是得出一条结论:
<根目录的ReferenceNumber是一个与盘符无关的特定不变的数值——1407374883553285>

再具体看数据,以上面<E:\>的数据作为样例,整理如下:

name                             frn                                     pfrn                              path
WE8.exe                        281474976710698       281474976710694      E:\实况8中超风云秋风DIY版\WE8.exe
实况8中超风云秋风DIY版    281474976710694       1407374883553285    E:\实况8中超风云秋风DIY版
E:                                 1407374883553285      /                                  E:\

前面3个信息是我们能通过USN直接获取的,path是我们要获取的。

而最后一行(1407374883553285 ->E:\ )是我们前面验证的。

 

基于这个,如果我们要获取"实况8中超风云秋风DIY版 "所在的路径,就可以根据他的pfrn(1407374883553285 )关联到E:\ ,然后一组合,“E:\ 实况8中超风云秋风DIY版 ”就出来了。如果是“WE8.exe ”呢?那就必须先获取“实况8中超风云秋风DIY版 ”的路径才能建立起来。


按这样的规律,只要我们能自上而下地建立起联系就能获取到所有文件的完整路径了。其实这就是一个树的结构。

 

 

<具体实现>
为了能自上而下地建立起来,在我们读取USN的时候就要先做一些处理。

1.按每个目录属性的记录的frn作为key值,每个以该key作为pfrn的记录作为内容,构建一个哈希表(Hashtable<Long, Vector>)。

这样,当我们读取完所有记录后就会得到一个这样的哈希表结构:
key                               value
(目录属性)记录的frn         array{...}[pfrn为key值的记录]

2.给定一个首要条件,建立第一级关联
根据盘符名(如E:\)->1407374883553285,找到key=1407374883553285下的所有记录,将其路径逐一标记为"盘符+机身名字"

3.递归遍历,获取所有记录的路径
遍历已建立路径的每个记录,找到以该记录的frn为key值的元素,建立路径,如此来回反复。


最后就能建立起完整的路径表。当然这样的局限性就是需要先获取到所有的数据。


这里提供一个大概的参考:

static final long rootFileReferenceNumber = Long.parseLong("1407374883553285");
static final String EndSymbol = "\\";

private Hashtable<Long, Vector> hashByPfrn; // 根据pfrn作为key值对记录进行分类储存
private Hashtable<Long, String> hashPaths; // 用于辅助递归

public static main(String[] args){
    for(所有USN记录){
        addData(.frn, .fileName, .pfrn, .filePath, .fileAttribute);
    }
    buildPath(rootFileReferenceNumber, "E:\\");
}

/**
 * 添加数据
 * @param frn
 * @param fileName
 * @param pfrn
 * @param filePath
 * @param fileAttribute
 */
private void addData(long frn, String fileName, long pfrn, String filePath, int fileAttribute) {
    Vector<NtfsVolumeData> v = (Vector<NtfsVolumeData>) hashByPfrn.get(pfrn);
    NtfsVolumeData record = new NtfsVolumeData(frn, fileName, pfrn, filePath, fileAttribute);
    if (v == null) {
        v = new Vector<NtfsVolumeData>();
        v.add(record);
        hashByPfrn.put(pfrn, v);
    } else {
        v.add(record);
    }
    fileCounter++;
}

/**
 * 建立路径
 * @param rootKey
 * @param rootName
 */
private void buildPath(long rootKey, String rootName) {
     hashPaths.clear();
     long key = rootKey;
     hashPaths.put(key, rootName);
     buildPath(key, hashPaths);
}

/**
 * 递归建立路径
 * @param key
 * @param hashPaths
 */
private void buildPath(long key, Hashtable hashPaths) {
     // 获取到属于该key的记录
     Vector<NtfsVolumeData> records = (Vector<NtfsVolumeData>) hashByPfrn.get(key);
     if (records == null || records.size() <= 0) {
         return;
     }
     // 获取该key对应的路径
     String filePath = (String) hashPaths.get(key);
     // 设置路径
     for (NtfsVolumeData record : records) {
         record.setParentPath(filePath);
         /**
          * 对带有目录属性的记录
          */
         if (0 != (record.getFileAttributes() & UsnRecordAttributeValues.FILE_ATTRIBUTE_DIRECTORY)) {
             // 记录当前记录路径为新路径
             hashPaths.put(record.frn, record.getFullPath() + EndSymbol);
             // 再进一步搜索
             buildPath(record.frn, hashPaths);
         }
     }
}
 

 

 

 

 

 

 

 

 

 

 

3
0
分享到:
评论
9 楼 univasity 2012-04-29  
univasity 写道
artwalkx 写道
……

4.我记得有位老兄用C++实现了,或许可以参考下...我找找哈~~


找到了,哈哈,保存网页是个好习惯~~
http://hi.baidu.com/chenxiong0115/blog/item/b31e573a3d8bd6e715cecbb6.html

希望有帮助,我C++不太懂...

8 楼 univasity 2012-04-29  
artwalkx 写道
谢谢你的回复哈,还有几点不清楚,我怕乱了,分条问:
1、也就是说对于每个盘符,需要枚举usn一次是吗?
2、这样统计的话,逻辑分区与主分区是没有区别的吧?
3、路径还原的方法是?java代码我没看懂,自己是这样做的:两个容器(<文件名,父指针>, hash<当前指针,struct(父指针,文件名)>,但这样内存占用有点大了,有啥好办法吗?
4、同3,去掉第一个容器,直接在hash表找文件名,一是慢,二是加上模糊查找,哎,更慢……


1.对,每个盘的USN文件都是独立的;
2.具体细节没有研究过,但基本操作没区别;
3.我的思路是,通过pfrn(父级/上层目录ID)与frn(当前文件ID)的关系,建立一个树结构。确实是需要一定的内存,但数量是和你磁盘的文件条数对等的。暂时没有想到更好的方法,如果你不需要一次获取所有文件的完整路径的话,可以用微软提供的函数——获取单个文件的路径(文章一开始有说)。
4.我记得有位老兄用C++实现了,或许可以参考下...我找找哈~~
7 楼 artwalkx 2012-04-22  
谢谢你的回复哈,还有几点不清楚,我怕乱了,分条问:
1、也就是说对于每个盘符,需要枚举usn一次是吗?
2、这样统计的话,逻辑分区与主分区是没有区别的吧?
3、路径还原的方法是?java代码我没看懂,自己是这样做的:两个容器(<文件名,父指针>, hash<当前指针,struct(父指针,文件名)>,但这样内存占用有点大了,有啥好办法吗?
4、同3,去掉第一个容器,直接在hash表找文件名,一是慢,二是加上模糊查找,哎,更慢……
6 楼 univasity 2012-04-18  
artwalkx 写道
你好,请问下,根目录都是1407374883553285, 怎么区别盘符(c: d: e: ...)呢? 不是很懂java哈

不能区分,每个盘的USN数据要独立处理,因为系统只会确保不同盘的USN中的数据ID是唯一的(每个盘的USN数据都是独立的)。我这里说的是,不管是哪个分区,其根目录(如C:\, D:\)在USN中对应的ID值总是1407374883553285,用这个值找到根目录,在通过ParentFileReferenceNumber和FileReferenceNumber的关系进行递归遍历,最终构造出完整的目录树...
5 楼 artwalkx 2012-04-17  
你好,请问下,根目录都是1407374883553285, 怎么区别盘符(c: d: e: ...)呢? 不是很懂java哈
4 楼 univasity 2011-12-28  
zhuoyu 写道
其实ReferenceNumber就是MFT表中的文件记录号,1407374883553285这个值写成16进制就是5000000000005,低四位就是00000005,对应MFT表中的‘.’,即当前盘符的根目录。

谢谢分享!
3 楼 zhuoyu 2011-12-26  
其实ReferenceNumber就是MFT表中的文件记录号,1407374883553285这个值写成16进制就是5000000000005,低四位就是00000005,对应MFT表中的‘.’,即当前盘符的根目录。
2 楼 univasity 2011-03-23  
soul_fly 写道
我测试了在我电脑上根目录值也为1407374883553285。

 
1 楼 soul_fly 2011-03-23  
我测试了在我电脑上根目录值也为1407374883553285。

相关推荐

    获取USN日志基本信息

    关于获取USN基本日志信息的C++ code block 上运行,因为是自己参照网上VC版本改编的可能会有些许疏漏。

    读取USN,建立索引优化后的全盘快速搜索工具

    原理是读取USN文件日志,然后内建索引加快文件搜索速度。 1、列表文件支持批量处理(删除、复制、复制文件名路径、打开、重命名),或者引用系统菜单。 2、支持拼音首字母缩写搜索,指定文件夹内搜索,多关键词搜索...

    C#制作的windows系统文件快速搜索工具,读取USN,易用性与速度都已优化的很好。程序为免安装的exe文件。

    原理是读取ntfs的USN文件日志,然后内建索引加速文件搜索过程。 1、列表文件支持批量处理(删除、复制、复制文件名路径、打开、重命名),或者引用系统菜单。 2、支持拼音首字母缩写搜索,指定文件夹内搜索,多...

    everything

    这种快速,是因为Everything的索引无需逐一扫描硬盘文件,而是直接读取NTFS文件系统的USN日志。这当然是既省力,又合理的做法。 第二个快速体现在搜索速度。在搜索框中键入字符后,搜索结果——或许称为过滤结果更...

    Everything-1.2.1.371

    这种快速,是因为Everything的索引无需逐一扫描硬盘文件,而是直接读取NTFS磁盘USN日志。这当然是既省力,又合理的做法。 第二个快速体现在搜索速度。在搜索框中键入字符后,搜索结果——或许称为过滤结果更准确...

    USN9810硬件数据配置

    华为MME USN9810硬件配置,最新架构的osta2.0介绍,基本的配置原则和步骤。

    Everything 全球速度最快的电脑文件搜索工具 快到你吐血喷饭

    这种快速,是因为Everything的索引无需逐一扫描硬盘文件,而是直接读取NTFS文件系统的USN日志。这当然是既省力,又合理的做法。 第二个快速体现在搜索速度。在搜索框中键入字符后,搜索结果——或许称为过滤结果更...

    华为USN9810V900R011产品概述.pdf

    华为USN9810V900R011产品概述.pdf

    很小但极高速的本地文件搜索工具Everything

    原理是:Everything是靠读取NTFS文件系统中的USN日志来完成的,所以如果你的硬盘不是NTFS的,那就没有什么用了。 这是免安装版,解压使用时,默认是日文界面,要把那个.lng文件放到Everything的执行文件所在文件夹...

    Everything1.3.4.67中文版(支持Win7).rar

    Everything在首次使用时会建立一个索引数据库,通过读取NTFS USN日志来建立文件索引树,生成的索引文件非常小。在搜索栏中输入要搜索的文件名,瞬间就能够帮助你找到要找的文件。支持使用搜索语法及正则表达式语法...

    Everything山寨版源码

    everything山寨版 源码 原理都是读取NTFS的USN信息

    USN物料资源编码2222

    USN物料资源编码2222

    Everything软件及源码学习资料

    包含Everything工具和共享源码,以及NTFS磁盘搜索机制(USN MFT)的资料,希望能对有兴趣的同学提供帮助。

    USN Seminar Papers

    无线网络传感器路由算法基础论文合集,适合刚接触网线网络传感器领域的新手阅读

    page_usn.json

    page_usn.json

    C#快速NTFS硬盘文件索引

    C#快速NTFS硬盘文件索引,基于 USN编程,范例很好,非本人原创。。。

    everything源码模拟

    模拟everything的简单实现,大体思路: ntfs磁盘分区会有一个觉usn的日志系统,从这个日志系统中可以拿到类似全盘索引之类的数据,然后搜索时从这个数据格式出发,效率会比较高

    java版everything+纯java不用任何包读取office中word文件

    原创资源物超所值.不使用任何jar包读取docx文件.Everything的java版本未完成但程序框架已经写出来了其中还包括java读取压缩文件解包压缩文件等各种文件操作类

    Usn__Use.rar_USN

    使用WriteFile 块读写数据,好不容易才找到的

Global site tag (gtag.js) - Google Analytics