DeviceIoControl获取文件LCN

在灾备项目组做CDP(Continual Data Protection),刚刚入门,最近看了下关于磁盘的操作,记录一下DeviceIoControl()。

NTFS中的文件结构

NTFS文件系统中,磁盘文件存储管理的最小单位是簇(Cluster)。一个簇由连续的若干个扇区构成。对于一个文件来说,我们将其在磁盘上占用的连续的簇称为簇流(Cluster Runs,不知道怎么翻译,有人称之为簇流,我也就这么叫吧),一个文件可以占用多个簇流。当文件的内容较小时,会与该文件的常驻属性一同放置在MFT的项中,每个MFT项的大小在引导扇区中被定义,但目前所有的NTFS文件系统的MFT项的大小均为1024Bytes。当文件内容较大时,就会占用额外的簇空间来存储数据,称之为非常驻属性。

非常驻属性被存储在簇流空间,簇流就是一组连续的簇,用起始簇号和长度表示一个簇流。例如,一个非常驻属性分成三部分,第一部分存储在簇号为30,31,32,33和34的簇中,那么该簇流的起始簇号就是30,流长度为5。第二部分存储在簇号为66和67的簇中,则该簇流起始簇号为66,流长度为2。第三部分存储在簇号为39~42的簇中,该簇流的起始簇号就是39,流长度为4。该属性一共占用了三个簇流,像下图这样(截图,侵删):


NTFS使用VCN(Virtual Cluster Number,虚拟簇号)和LCN(Logical Cluster Number,逻辑簇号)来描述文件或部分文件的位置。一个文件的VCN从0开始,是文件内容的内部编号,也就是说,将文件按照逻辑簇的大小进行划分,然后从0开始编号,就是所谓的VCN。而LCN就是文件系统的逻辑簇号,对于一个卷区来说,每一个簇的逻辑簇号是独一无二的。通过这种VCN和LCN的映射关系,我们就能在文件系统中定位文件。返回上面的例子,逻辑簇号30~34存储了文件内容的第一部分,对应的VCN为0~4,第二部分存储在逻辑簇号为66~67的簇中,其对应的VCN为5~6,第三部分存储在逻辑簇号39~42的簇中,对应的VCN为7~10。于是有下图(截图,侵删):


winioctl.h中的两个结构

//==================== FSCTL_GET_RETRIEVAL_POINTERS ======================
//
// Structure for FSCTL_GET_RETRIEVAL_POINTERS
//

typedef struct {

    LARGE_INTEGER StartingVcn;

} STARTING_VCN_INPUT_BUFFER, *PSTARTING_VCN_INPUT_BUFFER;

typedef struct RETRIEVAL_POINTERS_BUFFER {

    DWORD ExtentCount;
    LARGE_INTEGER StartingVcn;
    struct {
        LARGE_INTEGER NextVcn;
        LARGE_INTEGER Lcn;
    } Extents[1];

} RETRIEVAL_POINTERS_BUFFER, *PRETRIEVAL_POINTERS_BUFFER;

第一个比较简单,定义了文件的起始VCN,使用的时候置为0即可。

第二个结构定义了文件的VCN和LCN,用来在文件系统中定位文件。ExtentCount是文件的簇流个数,每一个簇流对应一个Extents,而Extents中定义了下一个簇流的起始VCN和本簇流的起始LCN。

DeviceIoControl()的参数解释

DeviceIoControl(
    _In_ HANDLE hDevice,
    _In_ DWORD dwIoControlCode,
    _In_reads_bytes_opt_(nInBufferSize) LPVOID lpInBuffer,
    _In_ DWORD nInBufferSize,
    _Out_writes_bytes_to_opt_(nOutBufferSize, *lpBytesReturned) LPVOID lpOutBuffer,
    _In_ DWORD nOutBufferSize,
    _Out_opt_ LPDWORD lpBytesReturned,
    _Inout_opt_ LPOVERLAPPED lpOverlapped
    );

其中,hDevice是要操作对象的句柄,dwIoControlCode使用FSCTL_GET_RETRIEVAL_POINTERS来获取文件的簇信息,第三和第四个参数对应了上述第一个结构,第五和第六个参数对应了上述第二个结构,lpBytesReturned是驱动程序实际返回给应用程序的数据字节数地址,最后一个参数用于重叠操作,这里置为NULL即可。

当然,对于常驻MFT的小文件,由于没有占用额外的簇空间,下面的代码是读取不到的。

源码

C代码如下,VS2015编译通过:

// FileCopy.cpp : 定义控制台应用程序的入口点。
//

#include<stdio.h> 
#include<windows.h>
#include<winioctl.h>
#include<tchar.h>
#include<Shlwapi.h>
#include<math.h>
using namespace std;
//#pragma comment(lib,"Shlwapi.lib")
//#pragma comment(lib,"Kernel32.lib")

typedef struct clusterNode
{
	//存储文件逻辑簇号的链表
	ULONGLONG LCN;
	clusterNode *next;
}clusterNode, *clusterLinkList;

void freeClusterLinkList(clusterLinkList Head)
{
	//释放链表
	clusterNode *p;
	while (Head != NULL) {
		p = Head;
		Head = Head->next;
		free(p);
	}
	free(Head);
}

clusterNode *getFileClusters(TCHAR* fileName, ULONG *fileSize, ULONG *clusterNumber, ULONG *sectorsPerCluster, ULONG *bytesPerSector) {
	//记录文件信息
	FILE * fp;
	fp = fopen("F://fileInfo.txt", "a+");
	if (fp == NULL) {
		printf("Open F://fileInfo.txt error\n");
		return NULL;
	}
	//获取卷区信息
	DWORD sPc, bPs, fc;//分别表示簇内扇区数、扇区内字节数、磁盘剩余簇数
	wchar_t driverRoot[4] = { 0 };
	memset(driverRoot, 0, sizeof(driverRoot));
	memcpy(driverRoot, fileName, 6);
	if (!GetDiskFreeSpace(driverRoot, &sPc, &bPs, &fc, NULL))//获取磁盘剩余空间容量
	{
		printf("GetDiskFreeSpace Error %d\n", GetLastError());
		return NULL;
	}
	ULONGLONG lsPc = (ULONGLONG)sPc;//转unsigned long long
	ULONGLONG lfc = (ULONGLONG)fc;
	ULONGLONG lbPs = (ULONGLONG)bPs;
	*sectorsPerCluster = sPc;
	*bytesPerSector = bPs;
	//创建/打开文件
	HANDLE hFile = INVALID_HANDLE_VALUE;
	hFile = CreateFile(fileName, FILE_READ_ATTRIBUTES,
		FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
		NULL,
		OPEN_EXISTING,
		NULL,
		NULL);
	if (hFile == INVALID_HANDLE_VALUE)
	{
		printf("Open file error %d\n", GetLastError());
		return NULL;
	}
	//获取文件大小
	*fileSize = GetFileSize(hFile, NULL);//文件大小
										 //获取文件占用逻辑簇号
	DWORD clusterCnt = DWORD(ceil(*fileSize * 1.0 / bPs / sPc));//文件占用簇数
	*clusterNumber = clusterCnt;
	fprintf(fp, "fileInfo:\nfileName : %ls\nfileSize : %lu Bytes\nclusterCount : %lu\nsPC : %lu\nbPS : %lu\n", fileName, *fileSize, *clusterNumber, *sectorsPerCluster, *bytesPerSector);
	printf("fileInfo:\nfileName : %ls\nfileSize : %lu Bytes\nclusterCount : %lu\nsPC : %lu\nbPS : %lu\n", fileName, *fileSize, *clusterNumber, *sectorsPerCluster, *bytesPerSector);
	STARTING_VCN_INPUT_BUFFER inputVcn;//是一个LARGE_INTEGER,表示文件的起始VCN
	inputVcn.StartingVcn.QuadPart = 0; //起始VCN=0
	BOOL result = FALSE;
	//DWORD dwBytesReturned = 0;
	LPDWORD dwBytesReturned = NULL;
	PRETRIEVAL_POINTERS_BUFFER rpBuffer;
	ULONGLONG rpBufferSize = sizeof(RETRIEVAL_POINTERS_BUFFER) + sizeof(rpBuffer->Extents)*clusterCnt;//考虑最差情况,所有的簇流均只包含 一个独立的簇
	rpBuffer = (PRETRIEVAL_POINTERS_BUFFER)malloc(rpBufferSize);
	result = DeviceIoControl(//直接发送控制代码到指定的设备驱动程序,返回非零表示成功
		hFile,
		FSCTL_GET_RETRIEVAL_POINTERS,
		&inputVcn,//应用程序传递给驱动程序的数据缓冲区地址
		sizeof(STARTING_VCN_INPUT_BUFFER),
		rpBuffer,//驱动程序返回给应用程序的数据缓冲区地址
				 //sizeof(RETRIEVAL_POINTERS_BUFFER) + sizeof(rpBuffer->Extents),
		rpBufferSize,
		(DWORD*)dwBytesReturned,//驱动程序实际返回给应用程序的数据字节数地址
		NULL);
	if (result == TRUE)
	{
		LARGE_INTEGER preVcn = rpBuffer->StartingVcn;
		LARGE_INTEGER Lcn;
		DWORD CnCount, r;
		clusterNode *fileClusters = NULL;//带头结点的逻辑簇号链
		fileClusters = (clusterNode*)malloc(sizeof(clusterNode));
		fileClusters->LCN = -1;
		fileClusters->next = NULL;
		clusterNode *rearCluster = fileClusters;//尾指针
		fprintf(fp, "RETRIEVAL_POINTERS_BUFFER->ExtentCount : %lu\n\n", rpBuffer->ExtentCount);
		int  cnt = 0;

		for (r = 0; r<rpBuffer->ExtentCount; r++)   //ExtentCount 簇流的个数(每个簇流中有几个连续的簇)  
		{
			Lcn = rpBuffer->Extents[r].Lcn;//本簇流起始逻辑簇号
										   //簇流中连续簇的个数等于 下一个簇流的起始 Vcn 号 减去 上一个 簇流的 起始 Vcn号   
			for (CnCount = (ULONG)(rpBuffer->Extents[r].NextVcn.QuadPart - preVcn.QuadPart); CnCount; CnCount--, Lcn.QuadPart++)
			{
				//fprintf(fp, "ERRORCODE->%d  preVcn--->%lld  NextVcn--->%lld  LCN--->%lld  clusterCNT-->%d\n", GetLastError(), preVcn.QuadPart, rpBuffer->Extents[r].NextVcn.QuadPart, Lcn.QuadPart, cnt++);
				clusterNode *cNode = (clusterNode*)malloc(sizeof(clusterNode));
				cNode->LCN = Lcn.QuadPart;//保存每个簇流中簇的 Lcn 号
				cNode->next = NULL;
				rearCluster->next = cNode;
				rearCluster = cNode;
			}
			preVcn = rpBuffer->Extents[r].NextVcn;
		}
		if (INVALID_HANDLE_VALUE != hFile)
		{
			CloseHandle(hFile);
			hFile = INVALID_HANDLE_VALUE;
		}
		fclose(fp);
		return fileClusters;
	}
	else {
		printf("DeviceIoControl Error : %d\n", GetLastError());
		fclose(fp);
		return NULL;
	}
}

int   main(int   argc, char   *argv[]) {
	clusterLinkList fileClusters = NULL;
	ULONG clusterSize, clusterCount;
	ULONG fileSize, blockSize;
	ULONG sectorsPerCluster, bytesPerSector;
	fileClusters = getFileClusters(L"F://TEST.txt", &fileSize, &clusterCount, §orsPerCluster, &bytesPerSector);
	clusterNode *p = fileClusters->next;
	while (p != NULL) {
		printf("LCN -> %lld\n", p->LCN);
		p = p->next;
	}
	freeClusterLinkList(fileClusters);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值