上贴地址:
http://hi.csdn.net/link.php?url=http://topic.csdn.net%2Fu%2F20091218%2F15%2F43f7e6e0-244f-4d45-9564-0e32cd1974d2.html
7. 主文件列表(MFT)记录详解
了解了NTFS的基本结构后,现在来详细分析MFT表内的记录,它是NTFS格式存储的关键内容。
“常驻属性”和“非常驻属性”
前面我们知道,磁盘上的每一个文件(目录)对应MFT表内的一条记录,记录的大小是1KB,记录的内容是文件的所有属性,包括文件本身的数据也当成属性来存储。那么问题来了,对于很多文件来说,本身的数据就大于1KB,那么记录是怎么存储的呢?NTFS系统是这么规定的,当文件属性太大的时候(比如文件的数据属性),NTFS系统会将该属性值存放到卷中某个位置,而MFT记录的内容只会记录该属性索引到外部的地址,以及索引区的大小。这种属性被称为文件的“非常驻属性”。与之相反,能够保存在FMT内的的属性叫做“常驻属性”。
下表是MFT记录的属性列表,每一个MFT记录只包含其中的一部分属性,不会包含所有的属性。
属性号
属性名
属性描述
0x10
$STANDRD_INFORMATION(标准属性)
包括基本文件属性,如只读、存档;时间标记,如文件的创建时间和最近一次修改的时间;有多少目录指向本文件
0x20
$ATTRIBUTE_LIST(属性列表)
当一个文件需要使用多个MFT文件记录时,用来表示该文件的属性列表
0x30
$FILE_NAME(文件名属性)
这是以Unicode字符表示的,由于MS-DOS不能正确识别Win32子系统创建的文件名,当Win32子系统创建一个文件名时,MTFS会自动生成一个备用的MS-DOS文件名,所以一个文件可以有多种文件名属性。
0x40
$VOLUME_VERSION(卷版本)
卷版本号
0x50
$SECURITY_DEscriptOR(安全描述符)
这是为了向后兼容而被保留的,主要用于保护文件以防止未授权访问。
0x60
$VOLUME_NAME(卷名)
卷名称或卷标识
0x70
$VOLUME_INFORMATION(卷信息)
卷信息
0x80
$DATA(数据属性)
这是文件的内容
0x90
$INDEX_ROOT(索引根属性)
索引根
0xA0
$INDEX_ALLOCATION(索引分配属性)
索引分配
0xB0
$BITMAP(位图属性)
位图
0xC0
$SYMBOLIC_LINK(符号链接)
符号链接
0xD0
$EA_INFORMATION(EA信息)
扩充属性信息:主要为与OS/2兼容
0xE0
$EA
扩充属性:主要为与OS/2兼容
0x100
$OBJECT_ID
对象ID:一个具有64个字节的标识符,其中最低的16个字节对卷来说是唯一的
读到这里,我们来实际编码读点数据来加深印象。
第一步:读入引导扇区
定义一个引导区的结构
Type
PBOOT_SEQUENCE = TBOOT_SEQUENCE^;
TBOOT_SEQUENCE = packed record // 引导扇区数据结构
_jmpcode : array[1..3] of Byte;
cOEMID: array[1..8] of Char;
wBytesPerSector: Word;
bSectorsPerCluster: Byte;
wSectorsReservedAtBegin: Word;
Mbz1: Byte;
Mbz2: Word;
Reserved1: Word;
bMediaDescriptor: Byte;
Mbz3: Word;
wSectorsPerTrack: Word;
wSides: Word;
dwSpecialHiddenSectors: DWord;
Reserved2: DWord;
Reserved3: DWord;
TotalSectors: Int64;
MftStartLcn: Int64;
Mft2StartLcn: Int64;
ClustersPerFileRecord: DWord;
ClustersPerIndexBlock: DWord;
VolumeSerialNumber: Int64;
_loadercode: array[1..430] of Byte;
wSignature: Word;
end;
procedure SaveDebugInfo(var buffer; bufSize: Cardinal; filename: string);
var
debugF : TFileStream;
begin
debugF := TFileStream.Create(filename,fmCreate);
try
debugF.Write(buffer,bufSize);
finally
debugF.Free;
end;
end;
procedure Button1Click(Sender: Tobject);
var
hDevice : THANDLE;
PbootSequence : PBOOT_SEQUENCE;
dwRead : Cardinal;
begin
hDevice := CreateFile( PChar('\\.\E'),
GENERIC_READ,
FILE_SHARE_READ or FILE_SHARE_WRITE,
nil,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
if (hDevice = INVALID_HANDLE_VALUE) then
begin
Showmessage('无法读写磁盘!');
CloseHandle(hDevice);
exit;
end;
New(PBootSequence);
ZeroMemory(PBootSequence, SizeOf(TBOOT_SEQUENCE));
SetFilePointer(hDevice, 0, nil, FILE_BEGIN);
ReadFile(hDevice,PBootSequence^, 512,dwread,nil);
SaveDebugInfo(PBootSequence^,sizeof(TBOOT_SEQUENCE),'boot_sector.bin');
Dispose(PBootSequence);
Closehandle(hDevice);
End;
读入的引导区为下图:
每扇区字节数(红色):0x0200
每簇扇区数(蓝色): 0x08
$MFT的逻辑簇号(黄色): 0x0000000000000004
每MFT记录簇数(绿色): 0x000000F6
第二步 读入MFT
我们知道MFT的第一条记录是本身。现在已经知道了
1. 每扇区字节数(红色):0x0200
2. 每簇扇区数(蓝色): 0x08
3. $MFT的逻辑簇号(黄色): 0x0000000000000004
4. 每MFT记录簇数(绿色): 0x000000F6
可以计算出$MFT的位置和MFT中每条记录的大小(通常是1KB):
$MFT位置(偏移量) := $MFT的逻辑簇号 *每簇扇区数 *每扇区字节数
每MFT记录簇数不能直接拿来计算每条记录的大小,要看它是否小于0x80,计算公式如下:
if (每MFT记录簇数 < $80) then
每MFT记录字节数 := 每MFT记录簇数*每簇字节数;
else
每MFT记录字节数 := 1 shl ($100 -每MFT记录簇数)
我们现在获得$MFT的偏移动位置及$MFT中每条记录的大小,这样我们就可以读出$MFT中的第一条记录,即对应$MFT文件自身的那条记录。
代码:
SetLength(存放数据的缓冲区, 每MFT记录字节数);
SetFilePointer(hDevice, Int64Rec($MFT位置).Lo,
@Int64Rec($MFT位置).Hi, FILE_BEGIN);
Readfile(hDevice, PChar(存放数据的缓冲区)^, 每MFT记录字节数, dwread, nil);
SaveDebugInfo(存放数据的缓冲区, 每MFT记录字节数,'MFT.bin');
读到的对应$FMT文件的记录为:
完整内容Blog地址:http://blog.csdn.net/A00553344/archive/2009/12/19/5039884.aspx