创建VSS快照后,我希望能够查询USN期刊。这是可能的还是USN日志无法从VSS快照访问?
我的目标是能够在两个VSS快照之间的增量备份中使用USN日志。备份的过程是
我现在失败的是我试图在VSS快照上获得最高USN条目的部分
我可以从命令行模拟如下
C:\>vssadmin list shadows
vssadmin 1.1 - Volume Shadow Copy Service administrative command-line tool
(C) Copyright 2001-2005 Microsoft Corp.
Contents of shadow copy set ID: {54fc99fb-65f2-4558-8e12-9308979327f0}
Contained 1 shadow copies at creation time: 5/10/2012 6:44:19 PM
Shadow Copy ID: {a2d2c155-9916-47d3-96fd-94fae1c2f802}
Original Volume: (T:)\\?\Volume{a420b1fa-9744-11e1-9082-889ffaf52b70}\
Shadow Copy Volume: \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy25
Originating Machine: computer
Service Machine: computer
Provider: 'Microsoft Software Shadow Copy provider 1.0'
Type: Backup
Attributes: Differential
C:\>fsutil usn queryjournal \\?\Volume{a420b1fa-9744-11e1-9082-889ffaf52b70}
Usn Journal ID : 0x01cd2ebe9c795b57
First Usn : 0x0000000000000000
Next Usn : 0x000000000001b5f8
Lowest Valid Usn : 0x0000000000000000
Max Usn : 0x7fffffffffff0000
Maximum Size : 0x0000000000100000
Allocation Delta : 0x0000000000040000
C:\>fsutil usn queryjournal \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy25
Error: The volume change journal is not active.
如果可能的话,任何想法我都在做错了吗?
答案 0 :(得分:5)
这个问题对我正在进行的项目非常重要,所以我终于(几乎)100%正常工作了。
注意:以下所有代码段都在C#
中感谢Hannes de Jager之前的回答,他指出了正确的方向和文档,我现在可以从VSS快照或常规API无法使用的任何其他特殊设备读取USN日志;就我而言,我的意思是使用VDDK(用于VM磁盘的VMware SDK)安装VMware快照。
我还重复使用或导入了来自伟大项目的代码:
来自StCroixSkipper(http://www.dreamincode.net/forums/blog/1017-stcroixskippers-blog/)的C#中的USN期刊探索者。仅使用官方API读取USN(因此没有VSS),但提供有用的pinvokes和Win32 API结构以及有关USN如何工作的一般信息
AlphaFS(https://github.com/alphaleonis/AlphaFS/),它模仿System.IO
命名空间的大部分内容,但允许访问特殊的Windows路径(VSS快照,原始设备),并提供有用的扩展。 / p>
如果有其他人感兴趣,我会分享我现在使用的代码,仍处于相当粗糙的状态,但正在工作。
它是如何运作的?
首先,您必须访问所需的Usn期刊组件。它们位于设备的根目录中,作为隐藏条目中的ADS(备用数据流)。无法使用标准System.IO
命名空间访问它们,这就是我之前告诉我使用AlphaFS项目的原因,但是CreateFile()
和ReadFile()
的设置应该足够了。
1/2
条目\$Extend\$UsnJrnl:$Max
包含有关期刊当前状态的全局信息。最重要的部分是usn日志ID(可以用来检查日志是否还没有被重置,如果你想比较多个VSS快照)和最低有效的USN日志序列号。
USN期刊结构:
// can be directly extracted from $MAX entry using Bitconverter.ToUint64
public struct USN_JOURNAL_DATA{
public UInt64 MaximumSize; //offset 0
public UInt64 AllocationDelta; // offset 8
public UInt64 UsnJournalID; // offset 16
public Int64 LowestValidUsn; // offset 24
}
2/2
条目\$Extend\$UsnJrnl:$J
包含日记记录。它是一个稀疏文件,因此它的磁盘使用率远低于它的大小。
要回答最初的问题,如何从先前的VSS快照中了解Max使用的USN序列并将其与另一个快照的快照进行比较?
好吧,NextUsn值简单地等于$Usnjrnl:$J
条目的大小。
在“新”vss快照USN日志中,如果要解析两个快照之间更改的记录,可以在开始解析记录之前寻找“参考”VSS快照最大USN。
一般来说,每个USN日记帐分录都是唯一的ID(USN编号),它是日记帐分录本身所在的$J
内的偏移量。
每个条目都有一个可变大小,所以要按顺序读取,我们必须计算:
next entry offset inside $J =
offset of current entry (or its USN sequennce number + length of current entry
幸运的是,记录长度也是USN进入记录的一个字段。够了,这是USN记录类:
public class UsnEntry : IComparable<UsnEntry>{
private const int FR_OFFSET = 8;
private const int PFR_OFFSET = 16;
private const int USN_OFFSET = 24;
private const int REASON_OFFSET = 40;
private const int FA_OFFSET = 52;
private const int FNL_OFFSET = 56;
private const int FN_OFFSET = 58;
public UInt32 RecordLength {get; private set;}
public Int64 USN {get; private set;}
public UInt64 FileReferenceNumber {get;private set;}
public UInt64 ParentFileReferenceNumber {get; private set;}
public UInt32 Reason{get; set;}
public string Name {get; private set;}
public string OldName{get; private set;}
private UInt32 _fileAttributes;
public bool IsFolder{
get{
bool bRtn = false;
if (0 != (_fileAttributes & Win32Api.FILE_ATTRIBUTE_DIRECTORY))
bRtn = true;
return bRtn;
}
}
public bool IsFile{
get{
bool bRtn = false;
if (0 == (_fileAttributes & Win32Api.FILE_ATTRIBUTE_DIRECTORY))
bRtn = true;
return bRtn;
}
}
/// <summary>
/// USN Record Constructor
/// </summary>
/// <param name="p">Buffer pointer to first byte of the USN Record</param>
public UsnEntry(IntPtr ptrToUsnRecord){
RecordLength = (UInt32)Marshal.ReadInt32(ptrToUsnRecord); //record size
FileReferenceNumber = (UInt64)Marshal.ReadInt64(ptrToUsnRecord, FR_OFFSET);
ParentFileReferenceNumber = (UInt64)Marshal.ReadInt64(ptrToUsnRecord, PFR_OFFSET);
USN = (Int64)Marshal.ReadInt64(ptrToUsnRecord, USN_OFFSET);
Reason = (UInt32)Marshal.ReadInt32(ptrToUsnRecord, REASON_OFFSET);
_fileAttributes = (UInt32)Marshal.ReadInt32(ptrToUsnRecord, FA_OFFSET);
short fileNameLength = Marshal.ReadInt16(ptrToUsnRecord, FNL_OFFSET);
short fileNameOffset = Marshal.ReadInt16(ptrToUsnRecord, FN_OFFSET);
Name = Marshal.PtrToStringUni(new IntPtr(ptrToUsnRecord.ToInt32() + fileNameOffset), fileNameLength / sizeof(char));
}
public int CompareTo(UsnEntry other){
return string.Compare(this.Name, other.Name, true);
}
public override string ToString(){
return string.Format ("[UsnEntry: RecordLength={0}, USN={1}, FileReferenceNumber={2}, ParentFileReferenceNumber={3}, Reason={4}, Name={5}, OldName={6}, IsFolder={7}, IsFile={8}", RecordLength, USN, (int)FileReferenceNumber, (int)ParentFileReferenceNumber, Reason, Name, OldName, IsFolder, IsFile);
}
}
我尝试隔离可以解析USN日志的代码的最小部分,并从最低有效日期开始提取其条目。请记住,记录的长度可变;还要注意,有些记录指向下一个空的记录(前4个字节,通常是记录长度,为零)。在这种情况下,我寻求4个字节并重试解析,直到我得到下一个记录。使用Python编写类似解析工具的人也报告了这种行为,所以我想这里不是太错了。
string vol = @"\\?\path_to_your_VSS_snapshot";
string maxHandle = vol + @"\$Extend\$UsnJrnl:$Max";
string rawJournal= vol + @"\$Extend\$UsnJrnl:$J";
// cannot use regular System.IO here, but pinvoking ReadFile() should be enough
FileStream maxStream = Alphaleonis.Win32.Filesystem.File.OpenRead(maxHandle);
byte[] maxData = new byte[32];
maxStream.Read(maxData, 0, 32);
//first valid entry
long lowestUsn = BitConverter.ToInt64(maxData, 24);
// max (last) entry, is the size of the $J ADS
IntPtr journalDataHandle = Win32Api.CreateFile(rawJournal,
0,
Win32Api.FILE_SHARE_READ| Win32Api.FILE_SHARE_WRITE,
IntPtr.Zero, Win32Api.OPEN_EXISTING,
0, IntPtr.Zero);
Win32Api.BY_HANDLE_FILE_INFORMATION fileInfo = new Win32Api.BY_HANDLE_FILE_INFORMATION();
Win32Api.GetFileInformationByHandle(journalDataHandle, out fileInfo);
Win32Api.CloseHandle(journalDataHandle);
long lastUsn = fileInfo.FileSizeLow;
int read = 0;
byte[] usnrecord;
byte[] usnraw = new byte[4]; // first byte array is to store the record length
// same here : pinvoke ReadFile() to avoid AlphaFS dependancy
FileStream rawJStream = Alphaleonis.Win32.Filesystem.File.OpenRead(rawJournal);
int recordSize = 0;
long pos = lowestUsn;
while(pos < newUsnState.NextUsn){
seeked = rawJStream.Seek(pos, SeekOrigin.Begin);
read = rawJStream.Read(usnraw, 0, usnraw.Length);
recordSize = BitConverter.ToInt32(usnraw, 0);
if(recordSize == 0){
pos = pos+4;
continue;
}
usnrecord = new byte[recordSize];
rawJStream.Read(usnrecord, 4, recordSize-4);
Array.Copy(usnraw, 0, usnrecord, 0, 4);
fixed (byte* p = usnrecord){
IntPtr ptr = (IntPtr)p;
// here we use the previously defined UsnEntry class
Win32Api.UsnEntry entry = new Win32Api.UsnEntry(ptr);
Console.WriteLine ("entry: "+entry.ToString());
ptr = IntPtr.Zero;
}
pos += recordSize;
}
以下是我使用的pinvokes:
public class Win32Api{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BY_HANDLE_FILE_INFORMATION{
public uint FileAttributes;
public FILETIME CreationTime;
public FILETIME LastAccessTime;
public FILETIME LastWriteTime;
public uint VolumeSerialNumber;
public uint FileSizeHigh;
public uint FileSizeLow;
public uint NumberOfLinks;
/*public uint FileIndexHigh;
public uint FileIndexLow;*/
public FileID FileIndex;
}
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool
GetFileInformationByHandle(
IntPtr hFile,
out BY_HANDLE_FILE_INFORMATION lpFileInformation);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr
CreateFile(string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
IntPtr lpSecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplateFile);
}
这绝对不是世界上最好的代码,但我认为它将为任何必须做同样事情的人提供一个良好的起点。
答案 1 :(得分:2)
你可能想再次提出Ruben的回答:
通过读取捕捉的VSS卷内的特殊文件,可以读取快照卷中的USN Journal。 如果Windows API不允许您阅读快照卷的USN日志,那么这可能是一个可行的选择,虽然我确信它感觉像是一个黑客。
事情是,尽管NTFS没有开放式规范,但它已被多个项目所解决,其中包括NTFS驱动程序的Linux实现。 Ruben为您发布的文档最初是为了帮助开发此驱动程序而编写的。
就像我提到的那样,USN Journal内容位于NTFS卷上的一个特殊文件中(就像NTFS中的许多内容一样,例如NTFS主文件表。实际上,据说NTFS中的所有内容都是文件)。 NTFS中的特殊文件以美元符号$开头,而jou正在查找的文件名为$ UsnJrnl,后者又位于名为$ Extend的特殊目录中。所以在你的C:卷上该文件是
C:\$Extend\$UsnJrnl
或者你的快照是
\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy25\$Extend\$UsnJrnl
您要查找的信息位于名为$ J流的Alternate Data Stream中,并且它具有此格式的条目(请参阅Ruben提到的文档):
Offset(in hex) Size Description
0x00 4 Size of entry
0x04 2 Major Version
0x06 2 Minor Version
0x08 8 MFT Reference
0x10 8 Parent MFT Reference
0x18 8 Offset of this entry in $J
0x20 8 Timestamp
0x28 4 Reason (see table below)
0x2B 4 SourceInfo (see table below)
0x30 4 SecurityID
0x34 4 FileAttributes
0x38 2 Size of filename (in bytes)
0x3A 2 Offset to filename
0x3C V Filename
V+0x3C P Padding (align to 8 bytes)
所以你可以阅读这个特殊文件的$ J流来获得你想要的USN条目。我想告诉你如何得出你需要的USN号,但我有点生疏了。如果我再次弄明白,我会更新这个答案。但是看看以这种方式阅读特殊文件,它非常有趣;-)。我已经使用此方法读取未安装的VHD文件中的主文件表(特殊文件$ MFT),以便枚举VHD内卷上的所有文件。
答案 2 :(得分:1)
我认为在未安装卷的情况下,无法使用WinAPI接口查询USN日志。
您可以尝试打开文件“$ UsnJrnl”并手动解析所需的信息。
请参阅: