`
univasity
  • 浏览: 811700 次
  • 性别: 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。

相关推荐

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

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

    获取USN日志基本信息

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

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

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

    everything源码模拟

    USN日志是一个记录文件系统变化的系统,每次文件或目录发生更改,如创建、修改、删除等,都会在USN日志中留下记录。这个日志提供了实时更新索引的可能性,使得我们可以快速获取到文件系统的最新状态。 在模拟...

    C#快速NTFS硬盘文件索引

    利用USN日志,开发者可以构建实时的文件索引,以便快速响应文件系统的变动。 在C#中实现NTFS硬盘文件索引,首先需要理解如何与USN日志交互。这通常涉及到使用Windows API,如`CreateFile`函数打开USN日志数据卷,...

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

    这个项目可能是一个自研的解决方案,其目标是创建一个Java版的"Everything"工具,这是一个快速的文件搜索工具,但目前可能还处于未完成状态。 首先,让我们深入理解“不使用任何jar包读取docx文件”。docx文件本质...

    Everything软件及源码学习资料

    这是因为Everything通过读取NTFS文件系统的USN日志(Update Sequence Number Journal)和MFT(Master File Table)来构建索引,这两种数据结构记录了文件系统的所有变化,使得搜索结果始终保持最新。 《探索...

    everything

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

    Usn__Use.rar_USN

    USN日志是Windows操作系统中一个重要的系统组件,它记录了文件系统中所有更改的序列号,用于跟踪文件和目录的变化。在Windows NTFS文件系统中,每当文件有变动,如创建、修改、删除等,USN日志都会更新一条记录。 ...

    Everything快速搜索

    在日常使用中,"Everything"不仅可以帮助用户快速找到硬盘上的文件,还能在大量数据中定位特定信息,对于开发者查找代码、设计师寻找素材、文档工作者整理文件等都非常实用。而且,"Everything"的资源占用极低,即使...

    探索Everything背后的技术1

    Everything应用了Change Journal的特性,实现了快速获取磁盘上所有文件和文件夹名称的功能。它通过顺序遍历MFT中的每条数据,查找每个文件和目录的USN字段,这样就可以非常高效地列出卷上的所有文件。此外,通过监控...

    Everything-1.2.1.371

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

    Everything山寨版源码

    "Everything"是一款高效快速的文件搜索工具,尤其在大量文件的环境下,它的性能表现非常出色。这个"Everything山寨版源码"显然是一款基于"Everything"的仿制品或修改版,其核心也是通过读取NTFS文件系统的USN...

    华为USN9810V900R011产品概述.pdf

    华为USN9810是华为公司推出的一款高端核心路由器,主要应用于移动通信网络的核心层,特别在中国移动4G网络工程中扮演着重要角色。V900R011是该产品的某一版本,它在2019年7月进行了更新,体现了华为在技术研发上的...

    人工智能-项目实践-信息检索-用C++实现的基于NTFS下MFT和USN-Journal检索与类正则表达式-NFA-DFA字符串

    通过直接访问MFT,可以快速获取文件系统的结构信息,提高搜索效率。 USN Journal(更新序列号日志)是NTFS文件系统用来跟踪文件系统变化的日志,记录了文件的创建、删除、修改等操作。利用USN Journal,程序可以...

    USN9810硬件数据配置

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

    filesearch_everything.rar

    在IT行业中,尤其是在系统开发和数据检索领域,"filesearch_everything.rar"这个压缩包可能包含了一个关于快速文件搜索工具的实现,特别是涉及到USN(Update Sequence Number)日志的利用。USN日志是Windows操作系统...

    Everything:基于 NTFS 文件系统,实现 Windows 文件的实时搜索

    不同于 Windows 自带的搜索功能,Everything 不依赖于文件名中的关键词,而是直接读取 NTFS 文件系统的 USN 日志,这意味着即使文件内容发生变化,它也能快速更新索引。输入关键词后,搜索结果几乎是即时显示的,...

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

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

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

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

Global site tag (gtag.js) - Google Analytics