Winapi 如何获得';下一步';VSS快照的日志条目?

Winapi 如何获得';下一步';VSS快照的日志条目?,winapi,volume-shadow-service,Winapi,Volume Shadow Service,创建VSS快照后,我希望能够查询USN日志。这是可能的还是无法从VSS快照访问USN日志 我的目标是能够在两个VSS快照之间的增量备份中使用USN日志。备份的过程将是 拍摄VSS快照并备份卷,记录每个文件的USN条目 …使用文件系统,添加/删除/修改文件 拍摄第二个VSS快照,然后使用USN日志检测在步骤2期间更改的任何内容 我现在失败的是,我试图在VSS快照上获得最高的USN条目 创建VSS快照 使用CreateFile(\?\GLOBALROOT\Device\HarddiskVolumeS

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

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

  • 拍摄VSS快照并备份卷,记录每个文件的USN条目
  • …使用文件系统,添加/删除/修改文件
  • 拍摄第二个VSS快照,然后使用USN日志检测在步骤2期间更改的任何内容
  • 我现在失败的是,我试图在VSS快照上获得最高的USN条目

  • 创建VSS快照
  • 使用CreateFile(\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy25)打开快照
  • DeviceIoControl(FSCTL\u QUERY\u USN\u 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.
    

    如果可能的话,你知道我做错了什么吗?

    我认为在卷未装入时,不可能使用WinAPI接口查询USN日志

    您可以尝试打开文件“$UsnJrnl”并手动解析所需的信息

    见:


    你可能想重新考虑一下鲁本的答案:

    通过读取快照VSS卷内的特殊文件,快照卷中的USN日志绝对可读如果Windows API不允许您阅读快照卷的USN日志,那么这可能是一个可行的选择,尽管我确信这感觉像是一种黑客行为

    问题是,尽管NTFS没有一个开放的规范,但它已经被不止一个项目所解决,其中包括NTFS驱动程序的Linux实现。Ruben为您发布的文档最初是为了帮助开发此驱动程序而编写的

    正如我提到的,USN日志内容位于NTFS卷上的一个特殊文件中(与NTFS中的许多内容类似,例如NTFS主文件表。实际上,据说NTFS中的所有内容都是一个文件)。NTFS中的特殊文件以美元符号$开头,jou要查找的文件名为$UsnJrnl,该文件位于名为$Extend的特殊目录中。所以在你的C:卷上,这个文件是

    C:\$Extend\$UsnJrnl 
    
    或者对你来说是

    \?\GLOBALROOT\Device\HarddiskVolumeShadowCopy25\$Extend\$UsnJrnl
    
    您要查找的信息位于一个名为$J的流中,它有以下格式的条目(请参阅Ruben的参考文档):


    因此,您可以读取此特殊文件的$J流,以获取所需的USN条目。我想告诉你如何推导你需要的USN编号,但我有点生疏了。如果我再弄明白,我会更新这个答案。但是看看用这种方式读取特殊文件,它非常有趣;-)。我使用此方法读取未安装的VHD文件中的主文件表(特殊文件$MFT),以便枚举VHD中卷上的所有文件

    这个问题对我正在进行的项目非常重要,所以我最终(几乎)100%地解决了这个问题

    注意:以下所有代码段都是C语言的

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

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

    • C#中的USN日志资源管理器,来自StCroixSkipper()。仅使用官方API读取USN(因此这里没有VSS),但提供有用的pinvokes和Win32 API结构以及有关USN工作方式的一般信息

    • AlphaFS(),它模仿了
      System.IO
      命名空间的大部分内容,但允许访问特殊的windows路径(VSS快照、原始设备),并提供了有用的扩展

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

    它是如何工作的

    首先,您必须访问所需的Usn日志组件。它们作为隐藏条目中的ADS(备用数据流)位于设备的根目录下。无法使用标准的
    System.IO
    命名空间访问它们,这就是我之前告诉过我使用AlphaFS项目的原因,但是pinvoking
    CreateFile()
    ReadFile()
    应该足够了

    1/2

    条目
    \$Extend\$UsnJrnl:$Max
    包含有关日志当前状态的全局信息。最重要的部分是usn日志ID(如果要比较多个VSS快照,可以使用该ID检查日志是否已重置)和最低有效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快照中知道最大使用USN序列,并将其与另一个快照的序列进行比较? 嗯,NextUsn值只是等于
    $Usnjrnl:$J
    条目的大小

    在“新”vss快照USN日志上,如果要分析在两个快照之间更改的记录,可以在开始分析记录之前查找“引用”vss快照max 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);
            }
        }
    
    这绝对不是最好的
    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);
            }
        }
    
    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;
    }
    
    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);
    
    
    }