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 函数获取,具体实现如下:

Cpp代码  收藏代码
  1. /// <summary>
  2. /// GetPathByFileReferenceNumber() 通过文件的映射ID获取文件路径.
  3. /// </summary>
  4. /// <param name=”hVol”>HANDLE对象,指向驱动盘
  5. /// </param>
  6. /// <param name=”frn”>文件的映射ID,包含在USN_RECORD中
  7. /// </param>
  8. /// <param name=”pathBuffer”>储存返回值的char*
  9. /// </param>
  10. /// <param name=”bufferSize”>指定返回值的长度
  11. /// </param>
  12. /// <returns>BOOL
  13. /// </returns>
  14. /// <remarks>
  15. /// 只支持NTFS3.0及以上的驱动盘,并且在Win7下运行该方法需要获取管理员权限
  16. /// </remarks>
  17. BOOL GetPathByFileReferenceNumber(__in HANDLE hVol, __in DWORDLONG frn, __out char* pathBuffer, __in int bufferSize)
  18. {
  19.      BOOL result = FALSE;
  20.      HANDLE hFile;
  21.      //printf(“frn: %I64x\n”, frn);
  22.      // 将FileReferenceNumber转为UNICODE_STR
  23.      UNICODE_STRING fidstr;
  24.      CoverFileReferenceNumberToFileIdStr(frn, &fidstr);
  25.      //ULONG fid[2] = {0x00000892, 0x00020000};//{i.nFileIndexLow, i.nFileIndexHigh};
  26.     //UNICODE_STRING fidstr = {8, 8, (PWSTR) fid};
  27.      // 构建用于寻找的OBJECT_ATTRIBUTES
  28.      OBJECT_ATTRIBUTES oa = {0};
  29.      oa.Length = sizeof(OBJECT_ATTRIBUTES);
  30.      oa.ObjectName = &fidstr;
  31.      oa.RootDirectory = hVol;
  32.      oa.Attributes = OBJ_CASE_INSENSITIVE;
  33.     //InitializeObjectAttributes (&oa, &fidstr, OBJ_CASE_INSENSITIVE, d, NULL);
  34.      IO_STATUS_BLOCK ioStatusBlock = {0};
  35.      // 通过FILE_ID打开文件,获取文件句柄
  36.     ULONG status = NtCreatefile(&hFile,
  37.                                   FILE_GENERIC_READ,
  38.                                         &oa,
  39.                                        &ioStatusBlock,
  40.                                         NULL,
  41.                                         FILE_ATTRIBUTE_READONLY,
  42.                                         FILE_SHARE_READ | FILE_SHARE_WRITE,
  43.                                         FILE_OPEN,
  44.                                         FILE_OPEN_BY_FILE_ID | FILE_OPEN_FOR_BACKUP_INTENT,
  45.                                         NULL,
  46.                                         0);
  47.         //printf(“status: %X, handle: %x\n”, status, hFile);
  48.           //printf(“error: %d, t: %x\n”, GetLastError(), iosb);
  49.           if(0==status){
  50.                //FILE_NAME_INFORMATION* info = (FILE_NAME_INFORMATION*)malloc(BUF_LEN);
  51.                //int allocSize = BUF_LEN;
  52.                // 获取文件名称信息
  53.                status = NtQueryInformationFile(hFile,
  54.                                                 &ioStatusBlock,
  55.                                                        info,
  56.                                                        allocSize,
  57.                                                        FileNameInformation);
  58.                if(0==status){
  59.                     // 获取到的名字是wchar*, 将其转为char*
  60.                     int dwMinSize = (*info).FileNameLength;
  61.                     WideCharToMultiByte(CP_OEMCP,NULL,(*info).FileName,dwMinSize/2,pathBuffer,dwMinSize,NULL,FALSE);
  62.                     result = TRUE;
  63.                }
  64.                //free(info);
  65.                CloseHandle(hFile);
  66.           }
  67.           return result;
  68. }

 

 

虽然这是不错的方法,但是太慢了,简单测试了一下,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值的元素,建立路径,如此来回反复。

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

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

Java代码  收藏代码
  1. static final long rootFileReferenceNumber = Long.parseLong(“1407374883553285”);
  2. static final String EndSymbol = “\\”;
  3. private Hashtable<Long, Vector> hashByPfrn; // 根据pfrn作为key值对记录进行分类储存
  4. private Hashtable<Long, String> hashPaths; // 用于辅助递归
  5. public static main(String[] args){
  6.     for(所有USN记录){
  7.         addData(.frn, .fileName, .pfrn, .filePath, .fileAttribute);
  8.     }
  9.     buildPath(rootFileReferenceNumber, “E:\\”);
  10. }
  11. /**
  12.  * 添加数据
  13.  * @param frn
  14.  * @param fileName
  15.  * @param pfrn
  16.  * @param filePath
  17.  * @param fileAttribute
  18.  */
  19. private void addData(long frn, String fileName, long pfrn, String filePath, int fileAttribute) {
  20.     Vector<NtfsVolumeData> v = (Vector<NtfsVolumeData>) hashByPfrn.get(pfrn);
  21.     NtfsVolumeData record = new NtfsVolumeData(frn, fileName, pfrn, filePath, fileAttribute);
  22.     if (v == null) {
  23.         v = new Vector<NtfsVolumeData>();
  24.         v.add(record);
  25.         hashByPfrn.put(pfrn, v);
  26.     } else {
  27.         v.add(record);
  28.     }
  29.     fileCounter++;
  30. }
  31. /**
  32.  * 建立路径
  33.  * @param rootKey
  34.  * @param rootName
  35.  */
  36. private void buildPath(long rootKey, String rootName) {
  37.      hashPaths.clear();
  38.      long key = rootKey;
  39.      hashPaths.put(key, rootName);
  40.      buildPath(key, hashPaths);
  41. }
  42. /**
  43.  * 递归建立路径
  44.  * @param key
  45.  * @param hashPaths
  46.  */
  47. private void buildPath(long key, Hashtable hashPaths) {
  48.      // 获取到属于该key的记录
  49.      Vector<NtfsVolumeData> records = (Vector<NtfsVolumeData>) hashByPfrn.get(key);
  50.      if (records == null || records.size() <= 0) {
  51.          return;
  52.      }
  53.      // 获取该key对应的路径
  54.      String filePath = (String) hashPaths.get(key);
  55.      // 设置路径
  56.      for (NtfsVolumeData record : records) {
  57.          record.setParentPath(filePath);
  58.          /**
  59.           * 对带有目录属性的记录
  60.           */
  61.          if (0 != (record.getFileAttributes() & UsnRecordAttributeValues.FILE_ATTRIBUTE_DIRECTORY)) {
  62.              // 记录当前记录路径为新路径
  63.              hashPaths.put(record.frn, record.getFullPath() + EndSymbol);
  64.              // 再进一步搜索
  65.              buildPath(record.frn, hashPaths);
  66.          }
  67.      }
  68. }

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注