How to get "NextUSN" log entry for VSS snapshot?

after taking a snapshot of the VSS, I would like to request the USN log. Is it possible or is the USN log inaccessible from a VSS snapshot?

my goal is to use USN log in incremental backup between two VSS snapshots . The backup process will consist of

  • take a snapshot of the VSS and back up the volume, taking into account the USN entries for each file
  • ... use the file system, add / delete / modify files
  • take a second VSS snapshot, then use the USN log to discover everything that changed during step # 2

what I am crashing right now is the part in which I am trying to capture the highest USN record in a VSS snapshot

  • create snapshot vss
  • open snapshot using CreateFile (\? \ GLOBALROOT \ Device \ HarddiskVolumeShadowCopy25)
  • DeviceIoControl (FSCTL_QUERY_USN_JOURNAL) - this does not work with GLE: 1179 "volume change log is inactive"

I can simulate this from the command line as follows

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. 

Any ideas what I am doing wrong, if possible?

+8
winapi volume-shadow-service
source share
4 answers

This question was very important for the project I am working on, so I finally got (almost) 100% work.

Note. All of the code snippets below are in C #

Thanks to previous Hannes de Jager answers that pointed me in the right direction and documentation, I can now read the USN log from a VSS snapshot or any other special device that the regular API cannot work with; in my case, I mean VMware snapshots mounted using VDDK (VMware SDK for virtual disks).

I also reused or imported code from large projects:

In case anyone else is interested, I share the code that I'm using now, still in a rather rough state, but it works.

How it works?

First, you must use the required Usn log components. They are located at the root of the device as ADS (alternative data streams) in a hidden record. They cannot be accessed using the standard System.IO namespace, so I said earlier that I used the AlphaFS project, but for this I would have to hide CreateFile() and ReadFile() .

1/2

The \$Extend\$UsnJrnl:$Max contains global information about the current state of the log. The most important parts are the log identifier usn (which you can use to verify that the log was not reset if you want to compare multiple VSS snapshots) and the lowest valid USN log sequence number.

USN Journal Structure:

  // 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

The \$Extend\$UsnJrnl:$J entry contains the journal entries. This is a sparse file, so its disk usage is much lower than its size.

To answer the initial question, how can I find out Max, the USN sequence used, from the previous VSS snapshot and compare it with a snapshot of another snapshot? Well, the NextUsn value is simply equal to the size of the $Usnjrnl:$J entry.

In your β€œnew” USN USN snapshot log, you can find a β€œreference” VSS max USN snapshot before you start analyzing records if you want to analyze the records changed between two snapshots.

Generally speaking, each USN journal entry as a unique identifier (USN), which is the offset within $J in which the journal entry itself is located. Each record has a variable size, so for sequential reading we must calculate:

 next entry offset inside $J = offset of current entry (or its USN sequennce number + length of current entry 

Fortunately, the record length is also the record field of the USN record. Stop saying here is the USN record class:

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

I tried to isolate the smallest part of the code that can parse the USN log and retrieve its entries, starting with the lowest valid. Remember that a record has a variable length; also note that some records point to the next empty record (the first 4 bytes, which are usually the length of the record, are nullified). In this case, I search for 4 bytes and repeat the parsing until I get the next record. This behavior has also been reported by people who wrote similar parsing tools in Python, so I think I'm not too wrong.

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

Here are the pinwa that I use:

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

This is definitely not the best piece of code on earth, but I think it will be a good starting point for those who need to do the same.

+5
source share

You might want to give Ruben an answer to a second thought:

The USN journal in the truncated volume is definitely read by reading a special file inside the truncated VSS volume. If the Windows API does not allow you to read the truncated volume USN log, this may be a viable option , although I am sure it seems like a hack.

The fact is that although NTFS does not have an open specification, it was developed by more than one project, including Linux versions of NTFS drivers. The document that Ruben published for you was originally written to help develop this driver.

As I already mentioned, the contents of the USN log are located in a special file on your NTFS volume (as, for example, in NTFS, for example, in the NTFS file table. In fact, everything in NTFS is a file). Special files in NTFS start with the dollar sign $, and the one you are looking for is called $ UsnJrnl, which in turn is located in a special directory called $ Extend. So, on your C volume: this file is located

 C:\$Extend\$UsnJrnl 

or for a snapshot it will be

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

The information you are looking for is in the Alternate Data Stream named $ J stream and has entries in this format (see the document):

 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) 

So, you can read the stream $ J of this special file to get the desired USN record. I want to tell you how to get the USN you need, but I'm a little rusty. If I find out again, I will update this answer. But take a look at reading special files this way, it's pretty funny ;-). I used this method to read the table of main files (a special $ MFT file) inside an unmounted VHD file to list all the files on the volume inside VHD.

+2
source share

I think it is not possible to use the WinAPI interface to query the USN log until the volume is installed.

You can try to open the file "$ UsnJrnl" and manually analyze the necessary information.

Cm:

NTFS Documentation by Richard Russon and Yuval Fledel

+1
source share

Perhaps this may be useful: journal entries do not cross cluster boundaries. Each cluster (usually 8 sectors per cluster) begins with a new record. If at the end of this cluster the next record does not fit into the remaining cluster space, this space is filled with zero, and the next record is saved at the beginning of the next cluster (it is unfortunate that this is not expressed in "record size"). Therefore, you do not need to parse this space - just go to the next cluster (do not forget to use the log RUN to get the next active disk cluster). Robert

Btw. You can use USN (the offset of this entry in $ J), the cluster number and the position of this entry in the cluster to verify that the entry is correct.

0
source share

All Articles