USN_RECORD 所有值都可以,除了 TimeStamp = 0?
USN_RECORD all values ok, except TimeStamp = 0?
使用下面的记录结构和函数,我可以高速获取文件名、参考编号、版本等。 (尝试使用 Delphi、Win 7)
问题是我没有得到文件 DateTime (TimeStamp.QuadPart = 0)
我已经添加了工作代码。为了测试,我将文件名(显示正确)和时间戳(显示为空)添加到 memo1
procedure Tform_main.Button1Click(Sender: TObject);
begin
FillFileListFromUSNJournal('C') ;
end;
procedure Tform_main.FillFileListFromUSNJournal(pDrive:Char);
var
ARootHandle: Cardinal;
AMFTEnumBuff: Pointer;
begin
ARootHandle := GetRootHandle(pDrive);
if AllocMFTEnumBuffer(ARootHandle,AMFTEnumBuff) then
EnumMFTEntries(ARootHandle, AMFTEnumBuff, MFTEnumCallback, @pDrive) ;
end;
function Tform_main.MFTEnumCallback(AUSN: PUSNRecord; Extra: Pointer): Boolean;
var
AName,ProgressMsg: String;
Drive: Char;
begin
Drive := PChar(Extra)^;
Result := True;
USNRecFromPointer(AUSN) ;
end;
{
** uMFT.pas
** zm
** created: 13.11.2010
**
* Copyright (c) 2010, Zeljko Marjanovic <xxxxxxr@gmail.com>
* This code is licensed under MPL 1.1
* For details, see http://www.mozilla.org/MPL/MPL-1.1.html
*
}
unit uMFT;
interface
uses
Windows, SysUtils;
const
FILE_DEVICE_FILE_SYSTEM = [=11=]000009;
METHOD_NEITHER = 3;
METHOD_BUFFERED = 0;
FILE_ANY_ACCESS = 0;
FILE_SPECIAL_ACCESS = 0;
FILE_READ_ACCESS = 1;
FILE_WRITE_ACCESS = 2;
ERROR_JOURNAL_DELETE_IN_PROGRESS = 1178;
ERROR_JOURNAL_NOT_ACTIVE = 1179;
ERROR_JOURNAL_ENTRY_DELETED = 1181;
FSCTL_GET_OBJECT_ID = 09c;
FSCTL_ENUM_USN_DATA = (FILE_DEVICE_FILE_SYSTEM shl 16) or (FILE_ANY_ACCESS shl 14) or (44 shl 2) or METHOD_NEITHER;
FSCTL_READ_USN_JOURNAL = (FILE_DEVICE_FILE_SYSTEM shl 16) or (FILE_ANY_ACCESS shl 14) or (46 shl 2) or METHOD_NEITHER;
FSCTL_CREATE_USN_JOURNAL = (FILE_DEVICE_FILE_SYSTEM shl 16) or (FILE_ANY_ACCESS shl 14) or (57 shl 2) or METHOD_NEITHER;
FSCTL_QUERY_USN_JOURNAL = (FILE_DEVICE_FILE_SYSTEM shl 16) or (FILE_ANY_ACCESS shl 14) or (61 shl 2) or METHOD_BUFFERED;
FSCTL_DELETE_USN_JOURNAL = (FILE_DEVICE_FILE_SYSTEM shl 16) or (FILE_ANY_ACCESS shl 14) or (62 shl 2) or METHOD_BUFFERED;
USN_PAGE_SIZE = 00;
USN_REASON_DATA_OVERWRITE = [=11=]000001;
USN_REASON_DATA_EXTEND = [=11=]000002;
USN_REASON_DATA_TRUNCATION = [=11=]000004;
USN_REASON_NAMED_DATA_OVERWRITE = [=11=]000010;
USN_REASON_NAMED_DATA_EXTEND = [=11=]000020;
USN_REASON_NAMED_DATA_TRUNCATION = [=11=]000040;
USN_REASON_FILE_CREATE = [=11=]000100;
USN_REASON_FILE_DELETE = [=11=]000200;
USN_REASON_EA_CHANGE = [=11=]000400;
USN_REASON_SECURITY_CHANGE = [=11=]000800;
USN_REASON_RENAME_OLD_NAME = [=11=]001000;
USN_REASON_RENAME_NEW_NAME = [=11=]002000;
USN_REASON_INDEXABLE_CHANGE = [=11=]004000;
USN_REASON_BASIC_INFO_CHANGE = [=11=]008000;
USN_REASON_HARD_LINK_CHANGE = [=11=]010000;
USN_REASON_COMPRESSION_CHANGE = [=11=]020000;
USN_REASON_ENCRYPTION_CHANGE = [=11=]040000;
USN_REASON_OBJECT_ID_CHANGE = [=11=]080000;
USN_REASON_REPARSE_POINT_CHANGE = [=11=]100000;
USN_REASON_STREAM_CHANGE = [=11=]200000;
USN_REASON_CLOSE = 000000;
USN_DELETE_FLAG_DELETE = [=11=]000001;
USN_DELETE_FLAG_NOTIFY = [=11=]000002;
USN_DELETE_VALID_FLAGS = [=11=]000003;
USNREC_MAJVER_OFFSET = 4;
USNREC_MINVER_OFFSET = 8;
USNREC_FR_OFFSET = 8;
USNREC_PFR_OFFSET = 16;
USNREC_USN_OFFSET = 24;
USNREC_TIMESTAMP_OFFSET = 32;
USNREC_REASON_OFFSET = 40;
USNREC_SINFO_OFFSET = 44;
USNREC_SECID_OFFSET = 48;
USNREC_FA_OFFSET = 52;
USNREC_FNL_OFFSET = 56;
USNREC_FN_OFFSET = 58;
IOCTL_DISK_BASE = [=11=]000007;
IOCTL_DISK_GET_PARTITION_INFO = (IOCTL_DISK_BASE shl 16) or (FILE_READ_ACCESS shl 14) or ([=11=]01 shl 2) or METHOD_BUFFERED;
PARTITION_IFS = ;
type
USN_JOURNAL_DATA = record
UsnJournalID: UInt64;
FirstUsn: Int64;
NextUsn: Int64;
LowestValidUsn: Int64;
MaxUsn: Int64;
MaximumSize: UInt64;
AllocationDelta: UInt64;
end;
TUSNJournalData = USN_JOURNAL_DATA;
PUSNJournalData = ^TUSNJournalData;
MFT_ENUM_DATA = record
StartFileReferenceNumber: UInt64;
LowUsn: Int64;
HighUsn: Int64;
end;
TMFTEnumData = MFT_ENUM_DATA;
PMFTEnumData = ^TMFTEnumData;
CREATE_USN_JOURNAL_DATA = record
MaximumSize: UInt64;
AllocationDelta: UInt64;
end;
TCreateUSNJournalData = CREATE_USN_JOURNAL_DATA;
PCreateUSNJournalData = ^TCreateUSNJournalData;
USN_RECORD = record
RecordLength: Cardinal;
MajorVersion: Word;
MinorVersion: Word;
FileReferenceNumber: UInt64;
ParentFileReferenceNumber: UInt64;
Usn: Int64;
TimeStamp: LARGE_INTEGER;
Reason: Cardinal;
SourceInfo: Cardinal;
SecurityId: Cardinal;
FileAttributes: Cardinal;
FileNameLength: Word;
FileNameOffset: Word;
FileName: PWideChar;// PWChar; [ss]
end;
TUSNRecord = USN_RECORD;
PUSNRecord = ^TUSNRecord;
TMFTEnumCallback = function(AUSN: PUSNRecord; Extra: Pointer = nil): Boolean of object;
PUInt64 = ^UInt64;
EMFTException = class(Exception);
TUSNRecChangeType = (uceNew, uceDeleted, uceRenamed);
function USNRecFromPointer(const P: Pointer): TUSNRecord;
function GetRootHandle(const Drive: Char): Cardinal;
function CreateUSNJournal(ARootHandle: Cardinal; MaxSize, AllocationDelta: UInt64): Boolean;
function AllocMFTEnumBuffer(ARootHandle: Cardinal; var AMFTEnumBuff: Pointer): boolean;
function EnumMFTEntries(ARootHandle: Cardinal; AMFTEnumBuff: Pointer; EnumCallBack: TMFTEnumCallback;Extra: Pointer = nil): Boolean;
implementation
uses main ;
function USNRecFromPointer(const P: Pointer): TUSNRecord;
var
PA: PAnsiChar;
begin
PA := PAnsiChar(P);
Result.RecordLength := PInteger(PA)^ ;
Result.MajorVersion := PInteger(PA + USNREC_MAJVER_OFFSET)^;
Result.MinorVersion := PInteger(PA + USNREC_MINVER_OFFSET)^;
Result.FileReferenceNumber := PUInt64(PA + USNREC_FR_OFFSET)^;
Result.ParentFileReferenceNumber := PUInt64(PA + USNREC_PFR_OFFSET)^;
Result.USN := PInt64(PA + USNREC_USN_OFFSET)^;
Result.TimeStamp.QuadPart := PInt64(PA + USNREC_TIMESTAMP_OFFSET)^;
Result.Reason := PCardinal(PA + USNREC_REASON_OFFSET)^;
Result.SourceInfo := PCardinal(PA + USNREC_SINFO_OFFSET)^;
Result.SecurityId := PCardinal(PA + USNREc_SECID_OFFSET)^;
Result.FileAttributes := PCardinal(PA + USNREC_FA_OFFSET)^;
Result.FileNameLength := PWord(PA + USNREC_FNL_OFFSET)^;
Result.FileNameOffset := PWord(PA + USNREC_FN_OFFSET)^;
Result.FileName := PWideChar(Integer(P) + Result.FileNameOffset);
if form_Main.memo1.lines.Count<100 then
form_Main.memo1.lines.add(Result.FileName+' '+InttoStr(Result.TimeStamp.QuadPart)) ;
end;
// this requires admin privileges
function GetRootHandle(const Drive: Char): Cardinal;
var
W: WideString;
RootHandle: Cardinal;
Flags: Cardinal;
Access: Cardinal;
begin
Flags := 0;
W := '\.\' + Drive + ':';
Access := GENERIC_READ;
RootHandle := CreateFileW(PWChar(W), Access, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, Flags, 0);
if RootHandle <> INVALID_HANDLE_VALUE then
Result := RootHandle
else
raise EMFTException.Create(SysErrorMessage(GetLastError));
end;
function CreateUSNJournal(ARootHandle: Cardinal; MaxSize, AllocationDelta: UInt64): Boolean;
var
BytesRet: Cardinal;
CreateData: TCreateUSNJournalData;
begin
CreateData.MaximumSize := MaxSize;
CreateData.AllocationDelta := AllocationDelta;
Result := DeviceIoControl(ARootHandle, FSCTL_CREATE_USN_JOURNAL, @CreateData, sizeof(TCreateUSNJournalData), nil, 0, BytesRet, nil);
end;
function AllocMFTEnumBuffer(ARootHandle: Cardinal; var AMFTEnumBuff: Pointer): boolean;
var
USNBuf: TUSNJournalData;
ErrCode,BytesRet: Cardinal;
EnumBuf: PMFTEnumData;
begin
result:=false;
if DeviceIoControl(ARootHandle, FSCTL_QUERY_USN_JOURNAL, nil, 0, @USNBuf, sizeof(TUSNJournalData), BytesRet, nil) then
begin
GetMem(EnumBuf, sizeof(TMFTEnumData));
EnumBuf.StartFileReferenceNumber := 0;
EnumBuf.LowUsn := 0;
EnumBuf.HighUsn := USNBuf.NextUsn;
AMFTEnumBuff := EnumBuf;
result:=true;
exit;
end;
ErrCode:=GetLastError;
if ErrCode = ERROR_JOURNAL_NOT_ACTIVE then
begin
// journal does not exist, create a new1 one
if CreateUSNJournal(ARootHandle, 000000, 0000) then
begin
if DeviceIoControl(ARootHandle, FSCTL_QUERY_USN_JOURNAL, nil, 0, @USNBuf, sizeof(TUSNJournalData), BytesRet, nil) then
begin
GetMem(EnumBuf, sizeof(TMFTEnumData));
EnumBuf.StartFileReferenceNumber := 0;
EnumBuf.LowUsn := 0;
EnumBuf.HighUsn := USNBuf.NextUsn;
AMFTEnumBuff := EnumBuf;
result:=true;
exit;
end;
ErrCode:=GetLastError;
end
else
begin
ErrCode:=GetLastError;
end;
end;
end;
//v main function
function EnumMFTEntries(ARootHandle: Cardinal; AMFTEnumBuff: Pointer; EnumCallBack: TMFTEnumCallback;Extra: Pointer): Boolean;
const
BUF_SIZE = sizeof(UInt64) + 000;
var
P: Pointer;
MFTEnum: Pointer;
BytesRet: Cardinal;
PUSN: PUSNRecord;
// TUSN: TUSNRecord;
begin
Result := False;
if (ARootHandle = INVALID_HANDLE_VALUE) or (AMFTEnumBuff = nil) then
Exit;
MFTEnum := AMFTEnumBuff;
GetMem(P, BUF_SIZE);
try
ZeroMemory(P, BUF_SIZE);
while DeviceIoControl(ARootHandle, FSCTL_ENUM_USN_DATA, MFTEnum, sizeof(TMFTEnumData), P, BUF_SIZE, BytesRet, nil) do
begin
PUSN := PUSNRecord(Integer(P) + sizeof(Int64));
while BytesRet > 60 do
begin
if (not EnumCallBack(PUSN, Extra)) then
Exit;
if PUSN.RecordLength > 0 then
Dec(BytesRet, PUSN.RecordLength)
else
break;
PUSN := PUSNRecord(Cardinal(PUSN) + PUSN.RecordLength);
if form_Main.memo1.lines.Count>100 then
break ;
end;
CopyMemory(MFTEnum, P, sizeof(Int64));
end;
Result := True;
finally
FreeMem(P);
end;
end;
//^ main function
end.
系统returns0,在页面上读取"Remarks" for FSCTL_READ_FILE_USN_DATA:
https://msdn.microsoft.com/en-us/library/windows/desktop/aa364584(v=vs.85).aspx
创建文件示例:
H: THandle;
[...]
H := CreateFile(PWChar('\.\' + DiskName + ':\' + FileName),
GENERIC_READ, FILE_SHARE_READ, nil, OPEN_ALWAYS, FILE_FLAG_BACKUP_SEMANTICS, 0);
[...]
if (H = INVALID_HANDLE_VALUE) then //GetLastError()
Memo.Lines.Add(FileName + ' 0')
else
begin
GetfileTime(H, @FTime, nil, nil);
Memo.Lines.Add(FileName + ' ' + DateTimeToStr(FileTimeToDateTime(FTime)));
end;
[...]
CloseHandle(H);
时间戳出现 0 因为 FSCTL_QUERY_USN_JOURNAL
没有填充该信息。这在控制代码的 documentation and I cannot find any official documentation where it is. But there are places that mention a USN_RECORD
is only partially populated. Like in this link which is given from USN_RECORD_V2
's documentation 中没有明确说明,尽管我什至不确定前者 link 谈论的 FSCTL 是什么。
无论如何,这符合我能找到的所有示例,包括 Microsoft 的 own。下面是此代码的 Delphi 翻译。使用 FSCTL_QUERY_USN_JOURNAL
控制代码检索的 USN 信息用于使用 FSCTL_READ_USN_JOURNAL
的 DeviceIoControl
调用以检索详细信息。
直接翻译的例外情况很少,一个是文件名的检索,我不明白C++代码是怎么做的。另一个是打印时间戳,因为这是您的要求。此外,您需要为卷句柄或内存等资源添加错误检查和保护块。
该代码使用了您在问题中包含的辅助单元的声明。我根据我发现的第 3 方免费软件实用程序检查了时间戳 here,它们是一致的。
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
windows,
uMFT;
type
USN = LONGLONG;
const
BUF_LEN = 4096;
var
hVol: THandle;
JournalData: TUSNJournalData;
dwBytes: DWORD;
dwRetBytes: DWORD;
ReadData: TReadUSNJournalData;
i: Integer;
Buffer: array [0..BUF_LEN - 1] of Byte;
UsnRecord: PUSNRecord;
FileName: PWideChar;
SysTime: TSystemTime;
begin
hVol := CreateFile( '\.\c:', GENERIC_READ,
FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
if hVol = INVALID_HANDLE_VALUE then begin
Writeln(Format('CreateFile failed (%d)', [GetLastError]));
Exit;
end;
if not DeviceIoControl(hVol, FSCTL_QUERY_USN_JOURNAL, nil, 0, @JournalData,
SizeOf(JournalData), dwBytes, nil) then begin
Writeln(Format('Query journal failed (%d)', [GetLastError]));
Exit;
end;
ZeroMemory(@ReadData, SizeOf(ReadData));
ReadData.ReasonMask := $FFFFFFFF;
ReadData.UsnJournalID := JournalData.UsnJournalID;
Writeln(Format('Journal ID: %x', [JournalData.UsnJournalID]));
Writeln(Format('FirstUsn: %x' + sLineBreak, [JournalData.FirstUsn]));
for i := 0 to 10 do begin
FillChar(Buffer, BUF_LEN, 0);
if not DeviceIoControl(hVol, FSCTL_READ_USN_JOURNAL, @ReadData,
SizeOf(ReadData), @Buffer, BUF_LEN, dwBytes, nil) then begin
Writeln(Format('Read journal failed (%d)', [GetLastError]));
Exit;
end;
dwRetBytes := dwBytes - SizeOf(USN);
// Find the first record
UsnRecord := PUsnRecord(NativeInt(@Buffer) + SizeOf(USN));
Writeln('****************************************');
while dwRetBytes > 0 do begin
Writeln(Format('USN: %x', [UsnRecord.Usn]));
GetMem(FileName, UsnRecord.FileNameLength + SizeOf(Char));
Move(Pointer(NativeInt(UsnRecord) + UsnRecord.FileNameOffset)^, FileName^,
UsnRecord.FileNameLength);
FileName[UsnRecord.FileNameLength div 2] := #0;
Writeln(Format('File name: %s', [FileName]));
FreeMem(FileName);
FileTimeToSystemTime(@UsnRecord.TimeStamp, SysTime);
Writeln(Format('Time stamp: %s', [DateTimeToStr(SystemTimeToDateTime(SysTime))]));
Writeln(Format('Reason: %x', [UsnRecord.Reason]));
Writeln;
dwRetBytes := dwRetBytes - UsnRecord.RecordLength;
// Find the next record
UsnRecord := PUsnRecord(NativeInt(UsnRecord) + UsnRecord.RecordLength);
end;
end;
CloseHandle(hVol);
Writeln;
Writeln('End of sample');
Readln;
end.
使用下面的记录结构和函数,我可以高速获取文件名、参考编号、版本等。 (尝试使用 Delphi、Win 7)
问题是我没有得到文件 DateTime (TimeStamp.QuadPart = 0)
我已经添加了工作代码。为了测试,我将文件名(显示正确)和时间戳(显示为空)添加到 memo1
procedure Tform_main.Button1Click(Sender: TObject);
begin
FillFileListFromUSNJournal('C') ;
end;
procedure Tform_main.FillFileListFromUSNJournal(pDrive:Char);
var
ARootHandle: Cardinal;
AMFTEnumBuff: Pointer;
begin
ARootHandle := GetRootHandle(pDrive);
if AllocMFTEnumBuffer(ARootHandle,AMFTEnumBuff) then
EnumMFTEntries(ARootHandle, AMFTEnumBuff, MFTEnumCallback, @pDrive) ;
end;
function Tform_main.MFTEnumCallback(AUSN: PUSNRecord; Extra: Pointer): Boolean;
var
AName,ProgressMsg: String;
Drive: Char;
begin
Drive := PChar(Extra)^;
Result := True;
USNRecFromPointer(AUSN) ;
end;
{
** uMFT.pas
** zm
** created: 13.11.2010
**
* Copyright (c) 2010, Zeljko Marjanovic <xxxxxxr@gmail.com>
* This code is licensed under MPL 1.1
* For details, see http://www.mozilla.org/MPL/MPL-1.1.html
*
}
unit uMFT;
interface
uses
Windows, SysUtils;
const
FILE_DEVICE_FILE_SYSTEM = [=11=]000009;
METHOD_NEITHER = 3;
METHOD_BUFFERED = 0;
FILE_ANY_ACCESS = 0;
FILE_SPECIAL_ACCESS = 0;
FILE_READ_ACCESS = 1;
FILE_WRITE_ACCESS = 2;
ERROR_JOURNAL_DELETE_IN_PROGRESS = 1178;
ERROR_JOURNAL_NOT_ACTIVE = 1179;
ERROR_JOURNAL_ENTRY_DELETED = 1181;
FSCTL_GET_OBJECT_ID = 09c;
FSCTL_ENUM_USN_DATA = (FILE_DEVICE_FILE_SYSTEM shl 16) or (FILE_ANY_ACCESS shl 14) or (44 shl 2) or METHOD_NEITHER;
FSCTL_READ_USN_JOURNAL = (FILE_DEVICE_FILE_SYSTEM shl 16) or (FILE_ANY_ACCESS shl 14) or (46 shl 2) or METHOD_NEITHER;
FSCTL_CREATE_USN_JOURNAL = (FILE_DEVICE_FILE_SYSTEM shl 16) or (FILE_ANY_ACCESS shl 14) or (57 shl 2) or METHOD_NEITHER;
FSCTL_QUERY_USN_JOURNAL = (FILE_DEVICE_FILE_SYSTEM shl 16) or (FILE_ANY_ACCESS shl 14) or (61 shl 2) or METHOD_BUFFERED;
FSCTL_DELETE_USN_JOURNAL = (FILE_DEVICE_FILE_SYSTEM shl 16) or (FILE_ANY_ACCESS shl 14) or (62 shl 2) or METHOD_BUFFERED;
USN_PAGE_SIZE = 00;
USN_REASON_DATA_OVERWRITE = [=11=]000001;
USN_REASON_DATA_EXTEND = [=11=]000002;
USN_REASON_DATA_TRUNCATION = [=11=]000004;
USN_REASON_NAMED_DATA_OVERWRITE = [=11=]000010;
USN_REASON_NAMED_DATA_EXTEND = [=11=]000020;
USN_REASON_NAMED_DATA_TRUNCATION = [=11=]000040;
USN_REASON_FILE_CREATE = [=11=]000100;
USN_REASON_FILE_DELETE = [=11=]000200;
USN_REASON_EA_CHANGE = [=11=]000400;
USN_REASON_SECURITY_CHANGE = [=11=]000800;
USN_REASON_RENAME_OLD_NAME = [=11=]001000;
USN_REASON_RENAME_NEW_NAME = [=11=]002000;
USN_REASON_INDEXABLE_CHANGE = [=11=]004000;
USN_REASON_BASIC_INFO_CHANGE = [=11=]008000;
USN_REASON_HARD_LINK_CHANGE = [=11=]010000;
USN_REASON_COMPRESSION_CHANGE = [=11=]020000;
USN_REASON_ENCRYPTION_CHANGE = [=11=]040000;
USN_REASON_OBJECT_ID_CHANGE = [=11=]080000;
USN_REASON_REPARSE_POINT_CHANGE = [=11=]100000;
USN_REASON_STREAM_CHANGE = [=11=]200000;
USN_REASON_CLOSE = 000000;
USN_DELETE_FLAG_DELETE = [=11=]000001;
USN_DELETE_FLAG_NOTIFY = [=11=]000002;
USN_DELETE_VALID_FLAGS = [=11=]000003;
USNREC_MAJVER_OFFSET = 4;
USNREC_MINVER_OFFSET = 8;
USNREC_FR_OFFSET = 8;
USNREC_PFR_OFFSET = 16;
USNREC_USN_OFFSET = 24;
USNREC_TIMESTAMP_OFFSET = 32;
USNREC_REASON_OFFSET = 40;
USNREC_SINFO_OFFSET = 44;
USNREC_SECID_OFFSET = 48;
USNREC_FA_OFFSET = 52;
USNREC_FNL_OFFSET = 56;
USNREC_FN_OFFSET = 58;
IOCTL_DISK_BASE = [=11=]000007;
IOCTL_DISK_GET_PARTITION_INFO = (IOCTL_DISK_BASE shl 16) or (FILE_READ_ACCESS shl 14) or ([=11=]01 shl 2) or METHOD_BUFFERED;
PARTITION_IFS = ;
type
USN_JOURNAL_DATA = record
UsnJournalID: UInt64;
FirstUsn: Int64;
NextUsn: Int64;
LowestValidUsn: Int64;
MaxUsn: Int64;
MaximumSize: UInt64;
AllocationDelta: UInt64;
end;
TUSNJournalData = USN_JOURNAL_DATA;
PUSNJournalData = ^TUSNJournalData;
MFT_ENUM_DATA = record
StartFileReferenceNumber: UInt64;
LowUsn: Int64;
HighUsn: Int64;
end;
TMFTEnumData = MFT_ENUM_DATA;
PMFTEnumData = ^TMFTEnumData;
CREATE_USN_JOURNAL_DATA = record
MaximumSize: UInt64;
AllocationDelta: UInt64;
end;
TCreateUSNJournalData = CREATE_USN_JOURNAL_DATA;
PCreateUSNJournalData = ^TCreateUSNJournalData;
USN_RECORD = record
RecordLength: Cardinal;
MajorVersion: Word;
MinorVersion: Word;
FileReferenceNumber: UInt64;
ParentFileReferenceNumber: UInt64;
Usn: Int64;
TimeStamp: LARGE_INTEGER;
Reason: Cardinal;
SourceInfo: Cardinal;
SecurityId: Cardinal;
FileAttributes: Cardinal;
FileNameLength: Word;
FileNameOffset: Word;
FileName: PWideChar;// PWChar; [ss]
end;
TUSNRecord = USN_RECORD;
PUSNRecord = ^TUSNRecord;
TMFTEnumCallback = function(AUSN: PUSNRecord; Extra: Pointer = nil): Boolean of object;
PUInt64 = ^UInt64;
EMFTException = class(Exception);
TUSNRecChangeType = (uceNew, uceDeleted, uceRenamed);
function USNRecFromPointer(const P: Pointer): TUSNRecord;
function GetRootHandle(const Drive: Char): Cardinal;
function CreateUSNJournal(ARootHandle: Cardinal; MaxSize, AllocationDelta: UInt64): Boolean;
function AllocMFTEnumBuffer(ARootHandle: Cardinal; var AMFTEnumBuff: Pointer): boolean;
function EnumMFTEntries(ARootHandle: Cardinal; AMFTEnumBuff: Pointer; EnumCallBack: TMFTEnumCallback;Extra: Pointer = nil): Boolean;
implementation
uses main ;
function USNRecFromPointer(const P: Pointer): TUSNRecord;
var
PA: PAnsiChar;
begin
PA := PAnsiChar(P);
Result.RecordLength := PInteger(PA)^ ;
Result.MajorVersion := PInteger(PA + USNREC_MAJVER_OFFSET)^;
Result.MinorVersion := PInteger(PA + USNREC_MINVER_OFFSET)^;
Result.FileReferenceNumber := PUInt64(PA + USNREC_FR_OFFSET)^;
Result.ParentFileReferenceNumber := PUInt64(PA + USNREC_PFR_OFFSET)^;
Result.USN := PInt64(PA + USNREC_USN_OFFSET)^;
Result.TimeStamp.QuadPart := PInt64(PA + USNREC_TIMESTAMP_OFFSET)^;
Result.Reason := PCardinal(PA + USNREC_REASON_OFFSET)^;
Result.SourceInfo := PCardinal(PA + USNREC_SINFO_OFFSET)^;
Result.SecurityId := PCardinal(PA + USNREc_SECID_OFFSET)^;
Result.FileAttributes := PCardinal(PA + USNREC_FA_OFFSET)^;
Result.FileNameLength := PWord(PA + USNREC_FNL_OFFSET)^;
Result.FileNameOffset := PWord(PA + USNREC_FN_OFFSET)^;
Result.FileName := PWideChar(Integer(P) + Result.FileNameOffset);
if form_Main.memo1.lines.Count<100 then
form_Main.memo1.lines.add(Result.FileName+' '+InttoStr(Result.TimeStamp.QuadPart)) ;
end;
// this requires admin privileges
function GetRootHandle(const Drive: Char): Cardinal;
var
W: WideString;
RootHandle: Cardinal;
Flags: Cardinal;
Access: Cardinal;
begin
Flags := 0;
W := '\.\' + Drive + ':';
Access := GENERIC_READ;
RootHandle := CreateFileW(PWChar(W), Access, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, Flags, 0);
if RootHandle <> INVALID_HANDLE_VALUE then
Result := RootHandle
else
raise EMFTException.Create(SysErrorMessage(GetLastError));
end;
function CreateUSNJournal(ARootHandle: Cardinal; MaxSize, AllocationDelta: UInt64): Boolean;
var
BytesRet: Cardinal;
CreateData: TCreateUSNJournalData;
begin
CreateData.MaximumSize := MaxSize;
CreateData.AllocationDelta := AllocationDelta;
Result := DeviceIoControl(ARootHandle, FSCTL_CREATE_USN_JOURNAL, @CreateData, sizeof(TCreateUSNJournalData), nil, 0, BytesRet, nil);
end;
function AllocMFTEnumBuffer(ARootHandle: Cardinal; var AMFTEnumBuff: Pointer): boolean;
var
USNBuf: TUSNJournalData;
ErrCode,BytesRet: Cardinal;
EnumBuf: PMFTEnumData;
begin
result:=false;
if DeviceIoControl(ARootHandle, FSCTL_QUERY_USN_JOURNAL, nil, 0, @USNBuf, sizeof(TUSNJournalData), BytesRet, nil) then
begin
GetMem(EnumBuf, sizeof(TMFTEnumData));
EnumBuf.StartFileReferenceNumber := 0;
EnumBuf.LowUsn := 0;
EnumBuf.HighUsn := USNBuf.NextUsn;
AMFTEnumBuff := EnumBuf;
result:=true;
exit;
end;
ErrCode:=GetLastError;
if ErrCode = ERROR_JOURNAL_NOT_ACTIVE then
begin
// journal does not exist, create a new1 one
if CreateUSNJournal(ARootHandle, 000000, 0000) then
begin
if DeviceIoControl(ARootHandle, FSCTL_QUERY_USN_JOURNAL, nil, 0, @USNBuf, sizeof(TUSNJournalData), BytesRet, nil) then
begin
GetMem(EnumBuf, sizeof(TMFTEnumData));
EnumBuf.StartFileReferenceNumber := 0;
EnumBuf.LowUsn := 0;
EnumBuf.HighUsn := USNBuf.NextUsn;
AMFTEnumBuff := EnumBuf;
result:=true;
exit;
end;
ErrCode:=GetLastError;
end
else
begin
ErrCode:=GetLastError;
end;
end;
end;
//v main function
function EnumMFTEntries(ARootHandle: Cardinal; AMFTEnumBuff: Pointer; EnumCallBack: TMFTEnumCallback;Extra: Pointer): Boolean;
const
BUF_SIZE = sizeof(UInt64) + 000;
var
P: Pointer;
MFTEnum: Pointer;
BytesRet: Cardinal;
PUSN: PUSNRecord;
// TUSN: TUSNRecord;
begin
Result := False;
if (ARootHandle = INVALID_HANDLE_VALUE) or (AMFTEnumBuff = nil) then
Exit;
MFTEnum := AMFTEnumBuff;
GetMem(P, BUF_SIZE);
try
ZeroMemory(P, BUF_SIZE);
while DeviceIoControl(ARootHandle, FSCTL_ENUM_USN_DATA, MFTEnum, sizeof(TMFTEnumData), P, BUF_SIZE, BytesRet, nil) do
begin
PUSN := PUSNRecord(Integer(P) + sizeof(Int64));
while BytesRet > 60 do
begin
if (not EnumCallBack(PUSN, Extra)) then
Exit;
if PUSN.RecordLength > 0 then
Dec(BytesRet, PUSN.RecordLength)
else
break;
PUSN := PUSNRecord(Cardinal(PUSN) + PUSN.RecordLength);
if form_Main.memo1.lines.Count>100 then
break ;
end;
CopyMemory(MFTEnum, P, sizeof(Int64));
end;
Result := True;
finally
FreeMem(P);
end;
end;
//^ main function
end.
系统returns0,在页面上读取"Remarks" for FSCTL_READ_FILE_USN_DATA: https://msdn.microsoft.com/en-us/library/windows/desktop/aa364584(v=vs.85).aspx 创建文件示例:
H: THandle;
[...]
H := CreateFile(PWChar('\.\' + DiskName + ':\' + FileName),
GENERIC_READ, FILE_SHARE_READ, nil, OPEN_ALWAYS, FILE_FLAG_BACKUP_SEMANTICS, 0);
[...]
if (H = INVALID_HANDLE_VALUE) then //GetLastError()
Memo.Lines.Add(FileName + ' 0')
else
begin
GetfileTime(H, @FTime, nil, nil);
Memo.Lines.Add(FileName + ' ' + DateTimeToStr(FileTimeToDateTime(FTime)));
end;
[...]
CloseHandle(H);
时间戳出现 0 因为 FSCTL_QUERY_USN_JOURNAL
没有填充该信息。这在控制代码的 documentation and I cannot find any official documentation where it is. But there are places that mention a USN_RECORD
is only partially populated. Like in this link which is given from USN_RECORD_V2
's documentation 中没有明确说明,尽管我什至不确定前者 link 谈论的 FSCTL 是什么。
无论如何,这符合我能找到的所有示例,包括 Microsoft 的 own。下面是此代码的 Delphi 翻译。使用 FSCTL_QUERY_USN_JOURNAL
控制代码检索的 USN 信息用于使用 FSCTL_READ_USN_JOURNAL
的 DeviceIoControl
调用以检索详细信息。
直接翻译的例外情况很少,一个是文件名的检索,我不明白C++代码是怎么做的。另一个是打印时间戳,因为这是您的要求。此外,您需要为卷句柄或内存等资源添加错误检查和保护块。
该代码使用了您在问题中包含的辅助单元的声明。我根据我发现的第 3 方免费软件实用程序检查了时间戳 here,它们是一致的。
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
windows,
uMFT;
type
USN = LONGLONG;
const
BUF_LEN = 4096;
var
hVol: THandle;
JournalData: TUSNJournalData;
dwBytes: DWORD;
dwRetBytes: DWORD;
ReadData: TReadUSNJournalData;
i: Integer;
Buffer: array [0..BUF_LEN - 1] of Byte;
UsnRecord: PUSNRecord;
FileName: PWideChar;
SysTime: TSystemTime;
begin
hVol := CreateFile( '\.\c:', GENERIC_READ,
FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
if hVol = INVALID_HANDLE_VALUE then begin
Writeln(Format('CreateFile failed (%d)', [GetLastError]));
Exit;
end;
if not DeviceIoControl(hVol, FSCTL_QUERY_USN_JOURNAL, nil, 0, @JournalData,
SizeOf(JournalData), dwBytes, nil) then begin
Writeln(Format('Query journal failed (%d)', [GetLastError]));
Exit;
end;
ZeroMemory(@ReadData, SizeOf(ReadData));
ReadData.ReasonMask := $FFFFFFFF;
ReadData.UsnJournalID := JournalData.UsnJournalID;
Writeln(Format('Journal ID: %x', [JournalData.UsnJournalID]));
Writeln(Format('FirstUsn: %x' + sLineBreak, [JournalData.FirstUsn]));
for i := 0 to 10 do begin
FillChar(Buffer, BUF_LEN, 0);
if not DeviceIoControl(hVol, FSCTL_READ_USN_JOURNAL, @ReadData,
SizeOf(ReadData), @Buffer, BUF_LEN, dwBytes, nil) then begin
Writeln(Format('Read journal failed (%d)', [GetLastError]));
Exit;
end;
dwRetBytes := dwBytes - SizeOf(USN);
// Find the first record
UsnRecord := PUsnRecord(NativeInt(@Buffer) + SizeOf(USN));
Writeln('****************************************');
while dwRetBytes > 0 do begin
Writeln(Format('USN: %x', [UsnRecord.Usn]));
GetMem(FileName, UsnRecord.FileNameLength + SizeOf(Char));
Move(Pointer(NativeInt(UsnRecord) + UsnRecord.FileNameOffset)^, FileName^,
UsnRecord.FileNameLength);
FileName[UsnRecord.FileNameLength div 2] := #0;
Writeln(Format('File name: %s', [FileName]));
FreeMem(FileName);
FileTimeToSystemTime(@UsnRecord.TimeStamp, SysTime);
Writeln(Format('Time stamp: %s', [DateTimeToStr(SystemTimeToDateTime(SysTime))]));
Writeln(Format('Reason: %x', [UsnRecord.Reason]));
Writeln;
dwRetBytes := dwRetBytes - UsnRecord.RecordLength;
// Find the next record
UsnRecord := PUsnRecord(NativeInt(UsnRecord) + UsnRecord.RecordLength);
end;
end;
CloseHandle(hVol);
Writeln;
Writeln('End of sample');
Readln;
end.