原文地址:http://univasity.iteye.com/blog/805234;
我在第一次使用 Everything 时,对其速度确实感到惊讶,后来了解到是通过操作 USN 实现的,并且有一定的局限性(只有 NTFS 下才能使用)。
近来清闲无事(失业了),搞些自己的小项目玩玩。其中也要处理到本地搜索这块,首先我想到的就是Everything 。
我仔细地将官网和他论坛的帖子都看了遍,基本没找到什么讲到原理的。倒是官网上提供了一个 Everything 的SDK 下载,是一个 IPC 的实现(我不是很懂,大概是程序关联之类的),需要 Everything 在后台跑才能调用操作。我也试了下效果,可以实现的,就是这样太麻烦了。
经过多日的搜搜,我发现了一个帖子,讨论的正是对 USN 的操作,同时我搜到了回帖者的 BLOG ,这一切让我有了开始的基础 …
言归正传:
初步认识 USN :
USN Journal 相当于 NTFS 的秘书,为他记录下改动的一切,并储存为 USN_RECORD 的格式。
更多的介绍请看以下链接:
Keeping an Eye on Your NTFS Drives: the Windows 2000 Change Journal Explained
fsutil_usn
NTFS文件系统 USN日志
下面来分享下近日研究的成果,一步步来探索 Everything 神奇的速度 USN的使用(Everything的快不单是用了USN,还需要建立索引,原来表达有误,改过来)
整个实现分为 6 步:
1. 判断驱动盘是否为 NTFS 格式
2. 获取驱动盘句柄
3. 初始化 USN 日志文件
4. 获取 USN 基本信息
5. 列出 USN 日志的所有数据
6. 删除 USN 日志文件
第一步:判断驱动盘是否 NTFS 格式
我们可以通过 GetVolumeInformation() 函数获取相关的信息进行判断。
可参考 MSDN : http://msdn.microsoft.com/en-us/library/aa364993%28VS.85%29.aspx
[[
这里我还找到了一个中文的说明:
GetVolumeInformation(
lpRootPathName: PChar; { 磁盘驱动器代码字符串}
lpVolumeNameBuffer: PChar; { 磁盘驱动器卷标名称}
nVolumeNameSize: DWORD; { 磁盘驱动器卷标名称长度}
lpVolumeSerialNumber: PDWORD; { 磁盘驱动器卷标序列号}
var lpMaximumComponentLength: DWORD; { 系统允许的最大文件名长度}
var lpFileSystemFlags: DWORD; { 文件系统标识}
lpFileSystemNameBuffer: PChar; { 文件操作系统名称}
nFileSystemNameSize: DWORD { 文件操作系统名称长度}
): BOOL;
上图可以看到,最后一个就是格式类型了,对应 lpFileSystemNameBuffer 。
]]
下面给出C++ 的实现作为参考:
/**
* step 01. 判断驱动盘是否 NTFS 格式
*/
char sysNameBuf[MAX_PATH] = {0};
int status = GetVolumeInformationA(volName,
NULL, // 驱动盘名缓冲,这里我们不需要
0,
NULL,
NULL,
NULL,
sysNameBuf, // 驱动盘的系统名( FAT/NTFS)
MAX_PATH);
if (0!=status){
printf(" 文件系统名 : %s\n" , sysNameBuf);
// 比较字符串
if (0==strcmp(sysNameBuf, "NTFS" )){
isNTFS = true ;
}else {
printf(" 该驱动盘非 NTFS 格式 \n" );
}
}
USN Journal 并非一开始就存在的,需要手动打开。我们可以使用函数 DeviceIoControl() 并通过参数FSCTL_CREATE_USN_JOURNAL 来操作。但仔细看 MSDN 会发现,需要先通过 CreateFile() 获取一个驱动盘的句柄。很多的后续操作都要用到这个句柄。
第二步:获取驱动盘句柄
可参考 MSDN : http://msdn.microsoft.com/en-us/library/aa363858%28VS.85%29.aspx
[[
对于我们目前的操作,注意看最后 Remarks ,
”Physical Disks and Volumes ” 中的一段:
The following requirements must be met for such a call to succeed:
大概意思是,要成功执行需要满足一下条件:
1. 使用者需要获取管理员权限
2. dwCreationDisposition 参数 ( 倒数第三个 ) 必须带有 OPEN_EXISTIN 标识
3. 当打开一个驱动盘或软盘 , dwShareMode 参数 ( 第三个 ) 必须带有 FILE_SHARE_WRITE 标识
再有就是 ”Files ” 中的一段 :
Windows Server 2003 and Windows XP/2000:
If CREATE_ALWAYS and FILE_ATTRIBUTE_NORMAL are specified, CreateFile fails and sets the last error to ERROR_ACCESS_DENIED if the file exists and has the FILE_ATTRIBUTE_HIDDEN or FILE_ATTRIBUTE_SYSTEM attribute. To avoid the error, specify the same attributes as the existing file.
大概意思是:
在 windows2003,xp 和 2000 中如果设定了 CREATE_ALWAYS 和 FILE_ATTRIBUTE_NORMAL 两个属性,如果文件存在,并带有属性 FILE_ATTRIBUTE_HIDDEN 或 FILE_ATTRIBUTE_SYSTEM 的话, CreateFile 会失败,并且返回的错误信息为 ERROR_ACCESS_DENIED 。要避免这个错误,需要制定与文件本身相同的属性。
>> 所以我们这里尽量不使用 CREATE_ALWAYS 和 FILE_ATTRIBUTE_NORMAL 。我这里使用FILE_ATTRIBUTE_HIDDEN 。
]]
照样贴上例子(我很少用 C++ ,写得不好,仅参考) :
/**
* step 02. 获取驱动盘句柄
*/
char fileName[MAX_PATH];
fileName[0] = '\0';
// 传入的文件名必须为\\.\C:的形式
strcpy(fileName, "\\\\.\\");
strcat(fileName, volName);
// 为了方便操作,这里转为string进行去尾
string fileNameStr = (string)fileName;
fileNameStr.erase(fileNameStr.find_last_of(":")+1);
printf("驱动盘地址: %s\n", fileNameStr.data());
// 调用该函数需要管理员权限
hVol = CreateFileA(fileNameStr.data(),
GENERIC_READ | GENERIC_WRITE, // 可以为0
FILE_SHARE_READ | FILE_SHARE_WRITE, // 必须包含有FILE_SHARE_WRITE
NULL, // 这里不需要
OPEN_EXISTING, // 必须包含OPEN_EXISTING, CREATE_ALWAYS可能会导致错误
FILE_ATTRIBUTE_READONLY, // FILE_ATTRIBUTE_NORMAL可能会导致错误
NULL); // 这里不需要
if(INVALID_HANDLE_VALUE!=hVol){
getHandleSuccess = true;
}else{
printf("获取驱动盘句柄失败 —— handle:%x error:%d\n", hVol, GetLastError());
}
第三步:打开 USN Journal 文件
MSDN : http://msdn.microsoft.com/en-us/library/aa364558%28v=VS.85%29.aspx
代码参考:
/**
* step 03. 初始化USN日志文件
*/
DWORD br;
CREATE_USN_JOURNAL_DATA cujd;
cujd.MaximumSize = 0; // 0表示使用默认值
cujd.AllocationDelta = 0; // 0表示使用默认值
status = DeviceIoControl(hVol,
FSCTL_CREATE_USN_JOURNAL,
&cujd,
sizeof(cujd),
NULL,
0,
&br,
NULL);
if(0!=status){
initUsnJournalSuccess = true;
}else{
printf("初始化USN日志文件失败 —— status:%x error:%d\n", status, GetLastError());
}
这时如果你能成功创建 USN 日志,那么对 USN 的探索即将开始 …
>> 目前你手上有的资源是 :
1. 某个 NTFS 驱动盘的 HANDLE;
2. 该驱动盘的 USN 日记已成功创建 .
--------------------------------------------- 未完待续 ------------------------------------------