Discuz! Board

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 1080|回复: 1
打印 上一主题 下一主题

everything原理解释与简单实现。

[复制链接]

1228

主题

1996

帖子

7570

积分

认证用户组

Rank: 5Rank: 5

积分
7570
跳转到指定楼层
楼主
发表于 2022-5-13 17:05:44 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
外面疫情正严重,老师也还没开始上课,我都在家宅了一个多月了。。。实在是闲的无聊。学习?怎么可能,学习是不可能学习的,这辈子都不可能学习的。

前几天,在和大佬聊天的时候,偶然听说了一个名叫everything的软件,可以很方便的查询电脑内的文件位置,据说可以秒出结果。只不过只针对NTFS文件系统的盘。在网上查了下,才知道是依赖于NTFS系统自身的特性。everything使用了NTFS文件系统自身的USN Journal。这个USN是什么东西呢,它就相当于NTFS的一个秘书,对NTFS里任何一个文件的操作都会被如实的记录下来。在NTFS分区内,文件信息存储在MFT内,MFT表里的记录包含了文件和目录的所有信息,例如文件名/目录名、路径、大小、属性等。此外,还会存储该文件/目录最后一次变化所对应的USN,系统在更新USN Journal时,也会更新这个字段。

everything在第一次启动时,会获取MFT内的所有记录,将其保存在数据库内,以后每次启动时只要获取一下USN Journal的文件更新信息就好了。所以查询时是查的数据库内的数据而非直接在电脑内查,速度自然快得多。

实现思路,首先利用GetLogicalDriveStrings函数获取电脑内所有的系统盘符,然后利用GetVolumeInformationA函数判断是否是NTFS文件系统,如果是,使用CreateFileA函数获取系统盘句柄,然后使用DeviceIoControl函数,使用FSCTL_CREATE_USN_JOURNAL参数初始化USN文件,再使用FSCTL_QUERY_USN_JOURNAL参数获取信息,接下来使用FSCTL_ENUM_USN_DATA参数获取系统的所有的文件信息,由于MFT表内没有记录文件的路径,只有文件号(FileReferenceNumber)和父文件号(ParentFileReferenceNumber),而Windows自身没有提供相应的API来完成这个功能,所以这部分只能自己实现。在获取了所有文件信息后,可以使用FSCTL_READ_USN_JOURNAL参数监控系统中的文件变化,最后记得删除USN文件。
————————————————
版权声明:本文为CSDN博主「pengpeng02」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_41529799/article/details/104264758
  1. #include <bits/stdc++.h>
  2. #include <windows.h>
  3. #include <winbase.h>
  4. #include <mysql.h>
  5. #include <conio.h>
  6. #include <io.h>
  7. #include <fstream>
  8. #include <sys/stat.h>
  9. #include <regex>
  10. using namespace std;
  11. #define ll long long
  12. #define buf_len 4096//4K
  13. struct node
  14. {
  15.         LARGE_INTEGER TimeStamp;
  16.         DWORD Reason;
  17.         ll frn;
  18.         char* FileName;
  19. };
  20. MYSQL mysql;//mysql连接
  21. MYSQL_RES *res;//返回行的一个查询结果集
  22. MYSQL_ROW rows;//一个行数据的类型安全(type-safe)的表示
  23. string sql;//sql 语句
  24. char buf[buf_len];//缓冲区,大小为4K
  25. HANDLE handle;//驱动盘句柄
  26. const ll TrueRoot=1407374883553285;//根目录的文件号,似乎所有的NTFS文件系统的根文件号都是这个,与盘无关,甚至与机器无关
  27. int num=0;//文件个数
  28. map<long long, vector<pair<long long, string> > >ma;//因为一个目录内不一定只有一个子目录或文件,所以是1对多映射
  29. map<long long, string> mm;//
  30. vector<node> ve;
  31. void getUSNUpdate(USN_JOURNAL_DATA ujd)//理论上方法应该没问题,但获取的东西根本不是我想要的,所以这个函数似乎没用
  32. {
  33.         DWORD usnDataSize;//USN Journal数据的大小
  34.         PUSN_RECORD usnRecord;
  35.         READ_USN_JOURNAL_DATA rujd = { 0, (DWORD)-1, 0, 0, 0, ujd.UsnJournalID };
  36.         for(; DeviceIoControl(handle,FSCTL_READ_USN_JOURNAL,&rujd,sizeof(rujd),buf,buf_len,&usnDataSize,NULL); \
  37.                 rujd.StartUsn=*(USN*)&buf)
  38.         {
  39.                 DWORD len = usnDataSize-sizeof(USN); //本页USN记录的长度
  40.                 usnRecord=(PUSN_RECORD)((PCHAR)buf+sizeof(USN));//获取第一个USN记录
  41.                 if(len<=0) break;
  42.                 while(len)
  43.                 {
  44.                         const int Len = usnRecord->FileNameLength;//将宽字符文件名转换为多字符,方便阅读
  45.                         char fileName[MAX_PATH] = {0};
  46.                         WideCharToMultiByte(CP_ACP,NULL,usnRecord->FileName,Len/2,fileName,Len,NULL,NULL);
  47.                         ve.push_back({usnRecord->TimeStamp,usnRecord->Reason,usnRecord->FileReferenceNumber,fileName});
  48.                         DWORD recordLen = usnRecord->RecordLength;
  49.                         len -= recordLen;
  50.                         usnRecord=(PUSN_RECORD)((PCHAR)usnRecord+recordLen); //获取下一个USN记录
  51.                 }
  52.         }
  53. }
  54. void closeUsn(USN_JOURNAL_DATA &ujd,DWORD &br)//关闭USN Journal数据文件
  55. {
  56.         DELETE_USN_JOURNAL_DATA dujd= {ujd.UsnJournalID,USN_DELETE_FLAG_DELETE};
  57.         DeviceIoControl(handle,FSCTL_DELETE_USN_JOURNAL,&dujd,sizeof(dujd),NULL,0,&br,NULL);
  58. }
  59. void getUsnData(USN_JOURNAL_DATA &ujd)//获取USN Journal数据
  60. {
  61.         DWORD usnDataSize;//USN Journal数据的大小
  62.         PUSN_RECORD usnRecord;
  63.         MFT_ENUM_DATA med= {0,0,ujd.NextUsn};
  64.         for(; DeviceIoControl(handle,FSCTL_ENUM_USN_DATA,&med,sizeof(med),buf,buf_len,&usnDataSize,NULL); \
  65.                 med.StartFileReferenceNumber = *(USN *)&buf)//获取下一页的USN记录,应该吧,我是这么理解的
  66.         {
  67.                 DWORD len = usnDataSize-sizeof(USN); //本页USN记录的长度
  68.                 usnRecord=(PUSN_RECORD)((PCHAR)buf+sizeof(USN));//获取第一个USN记录
  69.                 while(len)
  70.                 {
  71.                         const int Len = usnRecord->FileNameLength;//将宽字符文件名转换为多字符,方便阅读
  72.                         char fileName[MAX_PATH] = {0};
  73.                         WideCharToMultiByte(CP_ACP,NULL,usnRecord->FileName,Len/2,fileName,Len,NULL,NULL);
  74.                         ma[usnRecord->ParentFileReferenceNumber].push_back(make_pair(usnRecord->FileReferenceNumber,fileName));//存储信息
  75.                         DWORD recordLen = usnRecord->RecordLength;
  76.                         len -= recordLen;
  77.                         usnRecord=(PUSN_RECORD)((PCHAR)usnRecord+recordLen); //获取下一个USN记录
  78.                 }
  79.         }
  80. }
  81. void getUsnLogInformation(DWORD &br)
  82. {
  83.         USN_JOURNAL_DATA ujd;
  84.         if(DeviceIoControl(handle,FSCTL_QUERY_USN_JOURNAL,NULL,0,&ujd,sizeof(ujd),&br,NULL))
  85.         {
  86.                 getUsnData(ujd);
  87. //                getUSNUpdate(ujd);
  88.                 closeUsn(ujd,br);
  89.         }
  90.         else printf("获取USN Journal信息失败%d\n",GetLastError());
  91. }
  92. void initUsnLog()
  93. {
  94.         DWORD br;
  95.         CREATE_USN_JOURNAL_DATA cujd= {0,0};
  96.         if(DeviceIoControl(handle,FSCTL_CREATE_USN_JOURNAL,&cujd,sizeof(cujd),NULL,0,&br,NULL))
  97.                 getUsnLogInformation(br);
  98.         else printf("初始化USN Journal文件失败,%d\n",GetLastError());
  99. }
  100. void getHandle(char *name)//获取驱动盘句柄
  101. {
  102.         string Name="\\\\.\\"+(string)name;//转换成string类型方便进行去尾 ,即字符'\'
  103.         Name.erase(Name.find_last_of(":")+1);
  104.         //需要管理员权限,我实在是做不到在代码里加了,找不到一个有用的方法
  105.         handle = CreateFileA(&Name[0],GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,\
  106.                              NULL,OPEN_EXISTING,FILE_ATTRIBUTE_READONLY,NULL);//打开USN日志文件
  107.         if(handle != INVALID_HANDLE_VALUE) initUsnLog();
  108.         else printf("获取驱动盘句柄失败,error:%d\n",GetLastError());
  109. }
  110. bool checkNTFS(char* name)//检查是否是NTFS文件系统盘
  111. {
  112.         char nameBuf[15];
  113.         if(GetVolumeInformationA(name,NULL,0,NULL,NULL,NULL,nameBuf,15))//获取磁盘信息
  114.                 return strcmp(nameBuf,"NTFS") ? 0 : 1;
  115.         else printf("获取磁盘信息失败,error:%d\n",GetLastError());
  116. }
  117. void replace(string &str)
  118. {
  119.         int pos;
  120.         pos = str.find_last_of("\\");
  121.         str.replace(pos,string("\\").length(),"\\\\");
  122. //    while(pos != -1){
  123. //        // str.length()求字符的长度,注意str必须是string类型
  124. //        str.replace(pos,string("\\").length(),"\\\\");
  125. //        pos = str.find("\\");
  126. //    }
  127. }
  128. void build(string path,string name,long long pfrn)//采用dfs进行建树,路径,文件名和父文件号
  129. {
  130.         if(path[path.length()-1]=='\\') path.erase(path.length()-1);
  131. //        cout<<path<<endl;
  132.         vector<pair<long long,string> >tmp = ma[pfrn];
  133.         if(tmp.size())
  134.                 for(auto i:tmp)
  135.                 {
  136.                         build(path+"\\\\"+name,i.second,i.first);
  137.                 }
  138.         else
  139.         {
  140.                 mm[pfrn]=path+"\\"+name, num++;
  141. //                replace(path);
  142.                 sql="insert into myfile values (\""+path+"\", \""+name+"\");";
  143. //                if(path[0]=='E')cout<<path<<endl;
  144.                 if(mysql_query(&mysql,sql.c_str()))
  145.                 {
  146.                         printf("sql语句 %s 执行出现异常:%s\n",sql.c_str(),mysql_error(&mysql));
  147.                 }
  148.         }
  149. //        queue<pair<ll,string> > q;//因为dfs比bfs稍微快一点点,所以就没有使用bfs了
  150. //        q.push(make_pair(pfrn,path));
  151. //        while(!q.empty())
  152. //        {
  153. //                pair<ll,string> no=q.front();
  154. //                q.pop();
  155. //                vector<pair<long long,string> >tmp = ma[no.first];
  156. //                if(tmp.size())
  157. //                {
  158. //                        for(auto i:tmp)
  159. //                        {
  160. //                                q.push(make_pair(i.first,no.second+"\\"+i.second));
  161. //                        }
  162. //                }
  163. //                else
  164. //                {
  165. //                        mm[no.first]=no.second;num++;
  166. //                }
  167. //        }
  168. }
  169. void dfsFindFile(string path,string name,regex r)//自己原来用dfs写(抄)的文件查找,还没改
  170. {
  171. //        cout<<path<<endl;
  172.         long hFile=0;//设定文件句柄
  173.         struct _finddata_t info;
  174.         string str;
  175.         if((hFile=_findfirst((path+"\\*").c_str(),&info)) != -1)
  176.                 //第一个参数为文件名,第二个参数为_finddata_t结构体指针。若查找成功,返回文件句柄,若失败,返回-1。
  177.         {
  178.                 do
  179.                 {
  180.                         string tmp=info.name;
  181.                         if(tmp[0]=='



  182. )continue;
  183.                         tmp=path+"\\"+info.name;
  184. //                        if(regex_search(tmp,r))
  185.                         if(toLower(tmp).find(toLower(name)) != string::npos)
  186. //                        {
  187. //                                cout<<tmp<<endl;
  188. //                                continue;
  189. //                        }
  190.                         if(info.attrib & _A_SUBDIR)//_A_SUBDIR表示是文件夹
  191.                         {
  192.                                 if(strcmp(info.name, ".") != 0 && strcmp(info.name, "..") != 0)
  193.                                 {
  194.                                         dfsFindFile(path+"\\"+info.name,name,r);
  195.                                 }
  196.                         }
  197.                         else
  198.                         {
  199.                                 if(regex_search(info.name,r))
  200.                                 {
  201.                                         cout<<tmp<<endl;
  202.                                 }
  203. //                                cout<<path+"\\"+info.name<<endl;
  204.                         }
  205.                 }
  206.                 while(_findnext(hFile,&info) != -1);
  207.                 _findclose(hFile);
  208.                 //寻找下一个,成功返回0,否则-1
  209.         }
  210. }
  211. void getDriver()//get all logical driver
  212. {
  213.         DWORD bufLen = GetLogicalDriveStrings(0,NULL);//复制缓冲区需要的字符串的长度
  214.         char driverBuf[bufLen];//接收缓冲区内容的数组,用于存储系统盘符
  215.         //最后得到的结果形如 'C' ':' '\' '\0' 'D' ':' '\' '\0' '\0'
  216.         GetLogicalDriveStrings(bufLen,driverBuf);//获取整个系统盘符名字符串
  217.         char* driverName = driverBuf;//方便从头开始遍历整个数组
  218.         while(*driverName != '\0')//因为有跳过'\0',所以如果遇到'\0'就说明已经得到了所有的盘了
  219.         {
  220.                 cout<<driverName<<endl;
  221.                 if(checkNTFS(driverName))//检查是否是NTFS文件系统盘
  222.                 {
  223.                         clock_t a,b;
  224.                         getHandle(driverName);//获取驱动盘句柄
  225.                         a=clock();
  226.                         build(driverName,"\\",TrueRoot);//建立文件树
  227.                         b=clock();
  228.                         cout<<b-a<<endl;
  229.                         cout<<driverName<<" "<<num<<endl;
  230. //                        for(auto i:ve) if(mm[i.frn] != "") cout<<mm[i.frn]<<" :  \n" ;
  231.                         ma.clear();
  232.                         mm.clear();
  233.                         ve.clear();
  234.                         num=0;
  235.                 }
  236.                 else
  237.                 {
  238.                         //dfsFindFile();
  239.                 }
  240.                 driverName += strlen(driverName)+1;// 让指针跳到下一个盘符名并跳过'\0'
  241.         }
  242. }
  243. void connect()
  244. {
  245.         mysql_init(&mysql);
  246.         if(!mysql_real_connect(&mysql,"localhost","root","passwd","test",3306,NULL,0)) cout<<"password wrong\n";
  247.         else
  248.         {
  249.                 cout<<"ok\n";
  250.                 sql="create database if not exists everything default charset utf8;";
  251.                 if(mysql_query(&mysql,sql.c_str()))
  252.                         printf("sql语句 %s 执行出现异常:%s\n",sql.c_str(),mysql_error(&mysql));
  253.                 mysql_query(&mysql,"use everything;");
  254.                 sql="create table if not exists myfile (path varchar(3000), name varchar(500));";
  255.                 if(mysql_query(&mysql,sql.c_str()))
  256.                         printf("sql语句 %s 执行出现异常:%s\n",sql.c_str(),mysql_error(&mysql));
  257.                 mysql_query(&mysql,"truncate table myfile;");//因为无法获取更新信息,只能用最傻的方法——每次都重新获取文件信息
  258.                 if(mysql_query(&mysql, "set names gbk"))//设置gbk编码,不然在数据库内中文字符会出现乱码
  259.                         printf("编码设置失败,%s\n",mysql_error(&mysql));
  260.         }
  261. }
  262. void query()//查询,没写完,日后再实现
  263. {
  264.         sql="select * from myfile limit 100";
  265.         if(!mysql_query(&mysql,sql.c_str()))
  266.         {
  267.                 if(res=mysql_store_result(&mysql))
  268.                 {
  269.                         printf("返回数据行数:%d\n",mysql_affected_rows(&mysql));
  270.                         while(rows=mysql_fetch_row(res))
  271.                         {
  272.                                 for(int t=0; t <mysql_num_fields(res); t++)
  273.                                 {
  274.                                         printf("%s:  ",rows[t]);
  275.                                 }
  276.                                 printf("\n");
  277.                         }
  278.                         mysql_free_result(res);
  279.                 }
  280.         }
  281. //        string name;
  282. //        while(cin>>name)
  283. //        {
  284. //
  285. //        }
  286. }
  287. int main()
  288. {
  289.         clock_t s,e;
  290.         s=clock();//开始计时
  291.         connect();
  292.         getDriver();
  293. //        query();
  294.         mysql_close(&mysql);
  295.         e=clock();//结束计时
  296.         cout<<"总用时 : "<<e-s<<" ms.\n";
  297.         getchar();//避免一闪而过
  298. }
复制代码
回复

使用道具 举报

1228

主题

1996

帖子

7570

积分

认证用户组

Rank: 5Rank: 5

积分
7570
沙发
 楼主| 发表于 2022-5-13 17:06:08 | 只看该作者
1.先介绍一下Everything和Windows搜索是有区别的,以下列几点:
1. Everything只能搜索文件名和文件夹名,Windows搜索可以搜索文件名和文件内容;
2. Everything只能搜索NTFS格式的文件系统,Windows搜索可以搜索任意文件系统(例如FAT32,exFAT,NTFS);

2.那么Everything的工作原理是如何呢?
Master File Table (MTF)
在NTFS文件系统中,有一个特殊的表,称为MTF表。所有文件夹和文件的名称都被存储在该表中,访问该表的速度非常快,使应用程序可以不遍历文件系统就能获取当前卷(磁盘)中的所有文件的名称和路径。

USN journal
NTFS还有一个日志功能。所有对文件系统有修改的操作都被记录在了一个journal日志文件中。

Everything的原理
程序启动时,扫描系统所有NTFS卷(磁盘)的MTF表,将文件名称以一种利于字符串检索的算法形式存储在Everything的index索引数据库中。

系统运行过程中,Everything还会监控NTFS卷的journal日志文件,如果文件系统中的文件发生改变,Everything会更新它的index索引数据库。

当用户搜索文件时,Everything利用字符串查找算法,在index索引数据库中查找,可以直接搜索到文件。


转至
作者:知乎用户
链接:https://www.zhihu.com/question/22862246/answer/23020795
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|firemail ( 粤ICP备15085507号-1 )

GMT+8, 2024-4-20 21:31 , Processed in 0.056252 second(s), 18 queries .

Powered by Discuz! X3

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表