8

VSSスナップショットを作成した後、USNジャーナルにクエリを実行できるようにしたいと思います。これは可能ですか、それともUSNジャーナルにVSSスナップショットからアクセスできませんか?

私の目標は、2つのVSSスナップショット間の増分バックアップでUSNジャーナルを使用できるようにすることです。バックアップのプロセスは次のようになります

  1. 各ファイルのUSNエントリに注意して、VSSスナップショットを取得してボリュームをバックアップします
  2. ...ファイルシステムを使用し、ファイルを追加/削除/変更します
  3. 2番目の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.

これが可能であれば、私が間違ってやっているアイデアはありますか?

4

4 に答える 4

7

この質問は、私が取り組んでいるプロジェクトにとって非常に重要だったので、ようやく(ほぼ)100%機能するようになりました。

注:以下のコードスニペットはすべてC#です。

正しい方向とドキュメントを教えてくれたHannesdeJagerからの以前の回答のおかげで、VSSスナップショットまたは通常のAPIでは機能しないその他の特別なデバイスからUSNジャーナルを読み取ることができるようになりました。私の場合、VDDK(VMware SDK for VMディスク)を使用してマウントされたVMwareスナップショットを意味します。

また、すばらしいプロジェクトからのコードを再利用またはインポートしました。

  • StCroixSkipper(http://www.dreamincode.net/forums/blog/1017-stcroixskippers-blog/)のC#のUSNジャーナルエクスプローラー。公式APIを使用してUSNを読み取るだけです(ここではVSSはありません)が、有用なピンボークとWin32 API構造、およびUSNの動作に関する一般的な情報を提供します

  • AlphaFS(https://github.com/alphaleonis/AlphaFS/)。名前空間の大部分を模倣しSystem.IOますが、特別なWindowsパス(VSSスナップショット、rawデバイス)にアクセスでき、便利な拡張機能も提供します。

他の誰かが興味を持っている場合に備えて、私は今使用しているコードを共有します。まだかなり粗雑な状態ですが、機能しています。

それはどのように機能しますか?

まず、必要な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スナップショットから最大使用USNシーケンスを知り、それを別のスナップショットのシーケンスと比較するにはどうすればよいでしょうか。NextUsnの値は、$Usnjrnl:$Jエントリのサイズと同じです。

2つのスナップショット間で変更されたレコードを解析する場合は、「新しい」vssスナップショットUSNジャーナルで、レコードの解析を開始する前に「参照」VSSスナップショットの最大USNを探すことができます。

一般的に、各USNジャーナルエントリは$J、ジャーナルエントリ自体が配置されている内部のオフセットである一意のID(USN番号)として表示されます。各エントリのサイズは可変であるため、順番に読み取るには、次のように計算する必要があります。

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;
}

これが私が使うピンボークです:

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);


}

これは間違いなく地球上で最高のコードではありませんが、同じことをしなければならない人にとっては良い出発点になると思います。

于 2012-09-24T12:56:16.583 に答える
2

あなたはルーベンの答えを考え直したいかもしれません:

スナップされたボリューム内のUSNジャーナルは、スナップされたVSSボリューム内の特別なファイルを読み取ることで確実に読み取ることができます。Windows APIで、スナップされたボリュームのUSNジャーナルを読み取ることができない場合は、ハックのように感じると思いますが、これは実行可能なオプションである可能性があります。

NTFSにはオープンな仕様はありませんが、NTFSドライバーのLinux実装など、複数のプロジェクトによって理解されています。ルーベンがあなたのために投稿したドキュメントは、もともとこのドライバーの開発を支援するために書かれました。

前述したように、USNジャーナルのコンテンツはNTFSボリューム上の特別なファイルにあります(NTFSの多くのものと同様です。たとえばNTFSマスターファイルテーブル。実際には、NTFSのすべてがファイルであると言われています)。NTFSの特別なファイルはドル記号$で始まり、jouが探しているのは$ UsnJrnlという名前で、これは$Extendという名前の特別なディレクトリにあります。したがって、C:ボリュームでは、そのファイルは

C:\$Extend\$UsnJrnl 

またはスナップショットの場合は

\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy25\$Extend\$UsnJrnl

探している情報は、$ Jストリームという名前の代替データストリームにあり、次の形式のエントリがあります(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内のボリューム上のすべてのファイルを列挙しました。

于 2012-08-18T22:53:01.093 に答える
1

ボリュームがマウントされていない間は、WinAPIインターフェイスを使用してUSNジャーナルを照会することは不可能だと思います。

ファイル「$UsnJrnl」を開いて、必要な情報を手動で解析してみてください。

見る:

RichardRussonとYuvalFledelによるNTFSドキュメント

于 2012-05-18T20:57:09.660 に答える
0

おそらくこれは役に立つかもしれません:ジャーナルエントリはクラスター境界を越えません。すべてのクラスター(通常、クラスターごとに8セクター)は、新しいエントリで始まります。このクラスターの終わりに向かって、次のエントリが残りのクラスタースペースに収まらない場合、このスペースはゼロで埋められ、次のエントリは次のクラスターの開始時に保存されません(これがで表現されていないのは残念です「エントリのサイズ」)。したがって、このスペースを解析する必要はありません。次のクラスターにジャンプするだけです(!ジャーナルのRUNを使用して、次の有効なディスククラスターを取得することを忘れないでください)。ロバート

ところで。USN($ J内のこのエントリのオフセット)、クラスタ番号、およびクラスタ内のこのエントリの位置を使用して、エントリの有効性を確認できます。

于 2017-02-23T21:29:21.850 に答える