c语言实现指定路径文件读取_Windows下读取盘符中的所有文件及路径(读取NTFS下的USN日志文件)...

由于所做的课题需要得到电脑中的所有文件,并且需要像everything一样能对文件进行搜索归类,所以我就去研究了一下everything的原理,其中,everything是通过读取usn文件来获取整个盘符的所有文件的,今天来整理一下它是怎么实现的。(以下代码只做逻辑示例,所以不会是优美的代码。。。)

USN日志USN Journal)是NTFS的一个特性,全称更新序列号码日志(UpdateSequenceNumberJournal),或称更改日志(Change Journal),相当于NTFS的秘书,它会记录下NTFS盘符内一切的改动,并储存为USN_RECORD的格式。

整个实现过程一共分为以下五步:

一、判断盘符是否为NTFS格式

前面说了,USN日志是NTFS的一个特性,所以这也展示了它的局限性-----只有NTFS下才能使用。我们可以通过函数GetVolumeInformation()获取相关的信息进行判断:

使用可参考:https://docs.microsoft.com/zh-cn/windows/win32/api/fileapi/nf-fileapi-getvolumeinformationa

BOOL 

可以看出,在上面的函数中,倒数第二项参数代表的就是盘符的格式类型,所以,我们可以通过使用这个函数的lpFileSystemNameBuffer参数来判断是否为NTFS格式,下面是代码示例:

        char Root[] = "D:";
 	//判断驱动盘是否是NTFS格式
	char sysNameBuf[MAX_PATH]={0};
	int status= GetVolumeInformation(Root, 
				   NULL, // 驱动盘名缓冲,这里我们不需要  
                                   0,  
                                   NULL,  
                                   NULL,  
                                   NULL,  
                                   sysNameBuf, // 驱动盘的系统名( FAT/NTFS)  
                                   MAX_PATH);
		printf(" 文件系统名 : %sn" , Root);
		if(0==strcmp(sysNameBuf,"NTFS")){
			printf(" 该驱动盘是NTFS 格式 n" );
		}
		else{
			printf(" 该驱动盘非 NTFS 格式 n" );
		}

二、获取盘符句柄

获取盘符句柄使用CreateFileA函数,该函数创建或打开文件或I / O设备。最常用的I / O设备有:文件,文件流,目录,物理磁盘,卷,控制台缓冲区,磁带驱动器,通信资源,邮件槽和管道。该函数返回一个句柄,可用于根据文件或设备以及指定的标志和属性访问各种类型的I / O的文件或设备。

使用可参考:https://docs.microsoft.com/zh-cn/windows/win32/api/fileapi/nf-fileapi-createfilea

HANDLE CreateFileA(
  LPCSTR                lpFileName,				//要创建或打开的文件或设备的名称
  DWORD                 dwDesiredAccess,			//请求访问文件或设备
  DWORD                 dwShareMode,				//请求的文件或设备共享模式
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,		//指向SECURITY_ATTRIBUTES 结构的指针,可以为NULL。
  DWORD                 dwCreationDisposition,		//对存在或不存在的文件或设备执行的操作。对文件以外的设备,此参数通常设置为OPEN_EXISTING。
  DWORD                 dwFlagsAndAttributes,		//文件或设备属性和标志,FILE_ATTRIBUTE_NORMAL是文件的最常见默认值。 
  HANDLE                hTemplateFile				//具有GENERIC_READ访问权限的模板文件的有效句柄,可以为NULL。
);

通过使用CreateFileA()函数就可以返回盘符句柄,下面是参考代码:

        char fileName[MAX_PATH];  
	fileName[0] = '0';
	// 传入的文件名必须为.C:的形式  
	strcpy_s(fileName, ".");  
	//char Root[] = "C:";
	strcat_s(fileName, Root);  
	// 为了方便操作,这里转为string进行去尾  
	string fileNameStr = (string)fileName;  
	fileNameStr.erase(fileNameStr.find_last_of(":")+1);  
	printf("驱动盘地址: %sn", fileNameStr.data());  
	// 调用该函数需要管理员权限  
	HANDLE 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){
    	printf("获取驱动盘句柄成功n ");  
	}else{  
    	printf("获取驱动盘句柄失败 —— handle:%x error:%dn", hVol, GetLastError());  
	}

三、初始化USN文件

USN Journal并非一开始就存在的,需要手动打开。我们可以使用函数DeviceIoControl()并通过参数FSCTL_CREATE_USN_JOURNAL来操作。

使用可参考:https://docs.microsoft.com/zh-cn/windows/win32/api/winioctl/ni-winioctl-fsctl_create_usn_journal

DeviceIoControl()函数:

BOOL DeviceIoControl(		//-----------操作完成,返回非零值 
  HANDLE       hDevice,                        
  DWORD        dwIoControlCode,
  LPVOID       lpInBuffer,
  DWORD        nInBufferSize,
  LPVOID       lpOutBuffer,
  DWORD        nOutBufferSize,
  LPDWORD      lpBytesReturned,
  LPOVERLAPPED lpOverlapped
);

DeviceIoControl()函数通过参数FSCTL_CREATE_USN_JOURNAL来初始化USN文件:

BOOL 
WINAPI 
DeviceIoControl( (HANDLE) hDevice,              // handle to volume
                 FSCTL_CREATE_USN_JOURNAL,      // dwIoControlCode
		(LPVOID) lpInBuffer,           // input buffer
                 (DWORD) nInBufferSize,         // size of input buffer
                 NULL,                          // lpOutBuffer
                 0,                             // nOutBufferSize
		(LPDWORD) lpBytesReturned,     // number of bytes returned
                 (LPOVERLAPPED) lpOverlapped ); // OVERLAPPED structure

下面是参考代码:

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){
		printf("初始化USN日志文件成功n");  
	}else{  
    	printf("初始化USN日志文件失败 —— status:%x error:%dn", status, GetLastError());  
	}

四、获取USN日志基本信息(用于后续操作)

使用DeviceIoControl()函数通过参数FSCTL_QUERY_USN_JOURNAL来查询有关当前更新序列号(USN)的信息更改日志,其记录及其容量:

参考使用:https://docs.microsoft.com/zh-cn/windows/win32/api/winioctl/ni-winioctl-fsctl_query_usn_journal

BOOL 
WINAPI 
DeviceIoControl( (HANDLE)       Device,          // handle to volume
                 (DWORD) FSCTL_QUERY_USN_JOURNAL,// dwIoControlCode(LPVOID)       
                  NULL,            // lpInBuffer(DWORD)        
                  0,               // nInBufferSize(LPVOID)       
                  lpOutBuffer,     // output buffer
                 (DWORD)        nOutBufferSize,  // size of output buffer
                 (LPDWORD)      lpBytesReturned, // number of bytes returned
                 (LPOVERLAPPED) lpOverlapped );  // OVERLAPPED structure

其中,输出缓冲区得到USN日志的基本信息,有以下结构:

typedef struct {
  DWORDLONG UsnJournalID;
  USN       FirstUsn;
  USN       NextUsn;
  USN       LowestValidUsn;
  USN       MaxUsn;
  DWORDLONG MaximumSize;
  DWORDLONG AllocationDelta;
} USN_JOURNAL_DATA, *PUSN_JOURNAL_DATA;

下面是参考代码:

bool getBasicInfoSuccess = false;
 	USN_JOURNAL_DATA UsnInfo;
	status = DeviceIoControl(hVol,  
                        FSCTL_QUERY_USN_JOURNAL,  
                        NULL,  
                        0,  
             		&UsnInfo,  
             		sizeof(UsnInfo),  
             		&br,
             		NULL);  
	if(0!=status){
    	printf("获取USN日志基本信息成功n");
	}else{  
    	printf("获取USN日志基本信息失败 —— status:%x error:%dn", status, GetLastError());  
	}

五、列出USN文件数据

使用DeviceIoControl()函数通过参数FSCTL_ENUM_USN_DATA来枚举两个指定边界之间的更新序列号(USN)数据,以获取主文件表(MFT)记录。每次调用FSCTL_ENUM_USN_DATA都会将后续调用的起始点检索为输出缓冲区中的第一个条目。

使用参考:https://docs.microsoft.com/zh-cn/windows/win32/api/winioctl/ni-winioctl-fsctl_enum_usn_data

BOOL 
WINAPI 
DeviceIoControl( (HANDLE) hDevice,              // handle to volume
                 (DWORD) FSCTL_ENUM_USN_DATA,   // dwIoControlCode
                 (LPVOID) lpInBuffer,           // input buffer
                 (DWORD) nInBufferSize,         // size of input buffer
                 (LPVOID) lpOutBuffer,          // output buffer
                 (DWORD) nOutBufferSize,        // size of output buffer
                 (LPDWORD) lpBytesReturned,     // number of bytes returned
                 (LPOVERLAPPED) lpOverlapped ); // OVERLAPPED structure);

通过步骤四可以得到USN_JOURNAL_DATA UsnInfo,通过UsnInfo可以得到USN的指定边界;最后把输出缓冲区的USN文件数据传到USN_RECORD,USN_RECORD有以下结构:

typedef struct {
  DWORD         RecordLength;
  WORD          MajorVersion;
  WORD          MinorVersion;
  DWORDLONG     FileReferenceNumber;
  DWORDLONG     ParentFileReferenceNumber;
  USN           Usn;
  LARGE_INTEGER TimeStamp;
  DWORD         Reason;
  DWORD         SourceInfo;
  DWORD         SecurityId;
  DWORD         FileAttributes;
  WORD          FileNameLength;
  WORD          FileNameOffset;
  WCHAR         FileName[1];
} USN_RECORD, *PUSN_RECORD;

这里需要提供一个MTF_ENUM_DATA的结构作为参数,USN_RECORD会储存在MFT的表里,MTF_ENUM_DATA的作用就是制定一个范围。MTF_ENUM_DATA有以下结构:

typedef struct {
  DWORDLONG StartFileReferenceNumber;
  USN       LowUsn;
  USN       HighUsn;
} MFT_ENUM_DATA, *PMFT_ENUM_DATA;

下面是参考代码:

        MFT_ENUM_DATA med;  
	med.StartFileReferenceNumber = 0;  
	med.LowUsn = 0;//UsnInfo.FirstUsn;  
	med.HighUsn = UsnInfo.NextUsn;   
	CHAR buffer[BUF_LEN]; // 用于储存记录的缓冲 , 尽量足够地大 
	DWORD usnDataSize;  
  	PUSN_RECORD UsnRecord;
	while (0!=DeviceIoControl(hVol,  
                          FSCTL_ENUM_USN_DATA,  
                          &med,  
                          sizeof (med),  
                          buffer,  
                          BUF_LEN,  
                          &usnDataSize,  
                          NULL))
	{
    	DWORD dwRetBytes = usnDataSize - sizeof (USN);
    	UsnRecord = (PUSN_RECORD)(((PCHAR)buffer)+sizeof (USN));
    	//printf(" ********************************** n" );
    	while (dwRetBytes>0){
        	// 打印获取到的信息  
        	const int strLen = UsnRecord->FileNameLength;  
        	char fileName[MAX_PATH] = {0};  
        	WideCharToMultiByte(CP_OEMCP,NULL,UsnRecord->FileName,strLen/2,fileName,strLen,NULL,FALSE);
       		printf("FileName: %sn" , fileName); 
       		// 下面两个 filereferencenumber 可以用来获取文件的路径信息  
       		printf("FileReferenceNumber: %xI64n" , UsnRecord->FileReferenceNumber);  
       		printf("ParentFileReferenceNumber: %xI64n" , UsnRecord->ParentFileReferenceNumber);  
       		printf("n" );
       		char *pathBuffer[BUF_LEN];
       		// 获取下一个记录  
       		DWORD recordLen = UsnRecord->RecordLength;  
       		dwRetBytes -= recordLen;  
       		UsnRecord = (PUSN_RECORD)(((PCHAR)UsnRecord)+recordLen);
    	}
    	med.StartFileReferenceNumber = *(USN *)&buffer;
	}   

通过以上五步,就可以获得特定的盘符内的所有文件了。以上代码,输出了所有文件的文件名(FileName),文件引用号(FileReferenceNumber)以及父文件引用号(ParentFileReferenceNumber),接下来,我们就可以通过他们来得到文件的路径了。

得到文件路径:

首先,通过以上代码输出,我们可以得到,该文件的父文件引用号(ParentFileReferenceNumber)等于该文件的父文件的文件引用号(FileReferenceNumber),打个比方:

28c789d3d1580e224baf0eea2422b599.png

如图,第一章.pptx和成绩表.xlsx的父文件引用号(ParentFileReferenceNumber)为12,而数学文件夹的文件引用号(FileReferenceNumber)也为12,所以第一章.pptx和成绩表.xlsx是在数学文件夹内的;而数学文件夹的父文件引用号(ParentFileReferenceNumber)为11,等于科目文件夹的文件引用号(FileReferenceNumber),所以,数学文件夹就在科目文件夹内。

按照以上这个规律,我们就能知道各个文件的路径了。

我的具体实现方法:

首先,我们要先知道所有文件的数据,把它存储起来(可以用map<key,value>,其中key值为FileReferenceNumber,value为文件信息对象);

然后遍历map,对于一个文件,不断根据ParentFileReferenceNumber向上找其父文件;

最后路径等于父文件路径+“”+当前文件名。

下面是一个单独的小例子:

FrnFilePath.cpp

#include<iostream>
#include<string>
using namespace std;


class FrnFilePath{
	public :
		string filename;
		string frn;
		string pfrn;
		string path;
		FrnFilePath(string filename,string frn,string pfrn,string path){
			this->filename=filename;
			this->frn=frn;
			this->pfrn=pfrn;
			this->path=path;
		}
		FrnFilePath(){}
};

fileInfo.cpp

//定义一个类,包含文件名,frn,pfrn
#include <string>
using namespace std;
class fileInfo{
	public:
		string filename;
		string frn;
		string pfrn;
		fileInfo(string filename,string frn,string pfrn){
			this->filename=filename;
			this->frn=frn;
			this->pfrn=pfrn;
		}
}; 

main.cpp

#include<iostream>
#include <Windows.h>
#include <string>
#include<list>
#include <algorithm>
#include<map>
#include<stack>
#include"fileInfo.cpp"
#include"FrnFilePath.cpp"
using namespace std;
#define BUF_LEN 4096

list<fileInfo> result;
map <string,FrnFilePath> fileHash;

void fun3(fileInfo &fi){
	FrnFilePath *ff=new FrnFilePath(fi.filename,fi.frn,fi.pfrn, "");
	fileHash.insert(std::make_pair(fi.frn,*ff));
}

void clear(stack<string> treestack){
	while(!treestack.empty())

   {    
          treestack.pop();
	}
}
static string BuildPath(FrnFilePath currentNode, FrnFilePath parentNode) //组合路径
{
	return parentNode.path+""+currentNode.filename;
}


int main(){
	string drivefrn="9";
	FrnFilePath *ff=new FrnFilePath("D:",drivefrn,"","D:");
	fileHash.insert(std::make_pair(drivefrn,*ff));
	
	fileInfo *fi=new fileInfo("a","1","2");
	result.push_back(*fi);
	fi=new fileInfo("b","2","4");
	result.push_back(*fi);
	fi=new fileInfo("c","5","3");
	result.push_back(*fi);
	fi=new fileInfo("d","4","6");
	result.push_back(*fi);
	fi=new fileInfo("e","3","9");
	result.push_back(*fi);
	fi=new fileInfo("f","6","9");
	result.push_back(*fi);
	
	
	for_each(result.begin(), result.end(), fun3);
	
	stack<string> treestack;
	map<string,FrnFilePath>::iterator iter;
	iter = fileHash.begin();
	while(iter != fileHash.end()){
		clear(treestack);

		FrnFilePath currentValue = iter->second;
		FrnFilePath *parent;
		if ((currentValue.path.empty())&& !currentValue.pfrn.empty()&&fileHash.find(currentValue.pfrn)!=fileHash.end()){
			FrnFilePath parentValue = fileHash[currentValue.pfrn];
			while (parentValue.path.empty()&&!parentValue.pfrn.empty()&&fileHash.find(parentValue.pfrn)!=fileHash.end()){
				FrnFilePath temp=currentValue;
				currentValue = parentValue;

				if (!currentValue.pfrn.empty()&&fileHash.find(currentValue.pfrn)!=fileHash.end()){
					treestack.push(temp.frn);
                    parentValue = fileHash[currentValue.pfrn];
                }
				else{
					parent=&parentValue;
					parent=NULL;
                    break;
				}
			}
			if (parent != NULL){
				 currentValue.path = BuildPath(currentValue, parentValue);
				 while (!treestack.empty()){
					string walkedKey = treestack.top();
					treestack.pop();
                    FrnFilePath walkedNode = fileHash[walkedKey];
                    FrnFilePath parentNode = fileHash[walkedNode.pfrn];
                    walkedNode.path = BuildPath(walkedNode, parentNode);
                    currentValue.path+=walkedNode.path;
				}
				iter->second.path=currentValue.path;
			}
		}
		iter++;
	}
	iter = fileHash.begin();
    while(iter != fileHash.end()){
		cout<<iter->second.filename<<" "<<iter->second.path<<endl;
		iter++;
	}
}

输出为:

1fd999237bd3b2af28d09f75b89c78d1.png
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值