如何获取VSS快照的"NextUSN"日记条目?

stu*_*uck 8 winapi volume-shadow-service

在创建VSS快照后,我希望能够查询USN期刊.这是可能的还是USN日志无法从VSS快照访问?

我的目标是能够在两个VSS快照之间的增量备份中使用USN日志.备份的过程是

  1. 获取VSS快照并备份卷,记下每个文件的USN条目
  2. ...使用文件系统,添加/删除/修改文件
  3. 获取第二个VSS快照,然后使用USN日志检测在步骤#2中发生更改的任何内容

我现在失败的是我试图在VSS快照上获得最高USN条目的部分

  1. 创建VSS快照
  2. 使用CreateFile打开快照(\?\ GLOBALROOT\Device\HarddiskVolumeShadowCopy25)
  3. DeviceIoControl(FSCTL_QUERY_USN_JOURNAL) - 使用GLE失败:1179"卷更改日志未激活"

我可以从命令行模拟这个,如下所示

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.
Run Code Online (Sandbox Code Playgroud)

如果可能的话,任何想法我做错了吗?

mba*_*emy 6

这个问题对我正在进行的项目非常重要,所以我终于得到了(几乎)100%的工作.

注意:以下所有代码段都在C#中

感谢Hannes de Jager之前的回答,他向我指出了正确的方向和文档,我现在可以从VSS快照或常规API无法使用的任何其他特殊设备读取USN日志; 就我而言,我的意思是使用VDDK(用于VM磁盘的VMware SDK)安装VMware快照.

我还重用或导入了来自伟大项目的代码:

如果其他人感兴趣,我会分享我现在使用的代码,仍处于相当粗糙的状态,但正在工作.

它是如何工作的?

首先,您必须访问所需的Usn期刊组件.它们位于设备的根目录中,作为隐藏条目中的ADS(备用数据流).无法使用标准System.IO命名空间访问它们,这就是为什么我之前告诉我使用AlphaFS项目,但是使用pinvoking 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
    }
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

幸运的是,记录长度也是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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

我试图隔离可解析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;
}
Run Code Online (Sandbox Code Playgroud)

这是我使用的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);


}
Run Code Online (Sandbox Code Playgroud)

这绝对不是世界上最好的代码,但我认为它将为任何必须做同样事情的人提供一个良好的起点.