/// <summary> /// Constructs a new USN helper instance /// </summary> /// <param name="volumeRoot">The root volume where the USN lookup is performed</param> internal USNJournal(string volumeRoot) { if (Utility.Utility.IsClientLinux) { throw new Interface.UserInformationException(Strings.USNHelper.LinuxNotSupportedError, "UsnOnLinuxNotSupported"); } m_volume = Utility.Utility.AppendDirSeparator(volumeRoot); try { var device = GetDeviceNameFromPath(m_volume); m_volumeHandle = Win32USN.CreateFile(device, Win32USN.FileAccess.GenericRead, Win32USN.FileShare.ReadWrite, IntPtr.Zero, Win32USN.CreationDisposition.OpenExisting, Win32USN.FileAttributes.BackupSemantics, IntPtr.Zero); if (m_volumeHandle == null || m_volumeHandle.IsInvalid) { throw new Win32Exception(Marshal.GetLastWin32Error()); } Win32USN.ControlWithOutput(m_volumeHandle, Win32USN.FsCtl.QueryUSNJournal, ref m_journal); m_volumeRootRefNumber = GetFileRefNumber(volumeRoot); } catch { Dispose(); throw; } }
/// <summary> /// Constructs a new USN helper instance /// </summary> /// <param name="path">The path to the folder to perform USN services</param> /// <param name="volumeRoot">The root volume where the USN lookup is performed</param> internal USNHelper(string path, string volumeRoot) { if (Utility.Utility.IsClientLinux) { throw new Duplicati.Library.Interface.UserInformationException(Strings.USNHelper.LinuxNotSupportedError, "UsnOnLinuxNotSupported"); } if (!System.IO.Path.IsPathRooted(path)) { throw new Exception(string.Format("Path {0} is not rooted", path)); } m_path = Utility.Utility.AppendDirSeparator(path); try { string devicename = @"\\.\" + System.IO.Path.GetPathRoot(path).TrimEnd('\\'); if (volumeRoot != null) { volumeRoot = volumeRoot.TrimEnd('\\'); } m_volumeHandle = Win32USN.CreateFile(volumeRoot == null ? devicename : volumeRoot, Win32USN.EFileAccess.GenericRead, Win32USN.EFileShare.ReadWrite, IntPtr.Zero, Win32USN.ECreationDisposition.OpenExisting, Win32USN.EFileAttributes.BackupSemantics, IntPtr.Zero); if (m_volumeHandle == null || m_volumeHandle.IsInvalid) { throw new Win32Exception(Marshal.GetLastWin32Error()); } uint bytesReturned = 0; if (!Win32USN.DeviceIoControl(m_volumeHandle, Win32USN.EIOControlCode.FsctlQueryUsnJournal, null, 0, out m_journal, (uint)Marshal.SizeOf(typeof(Win32USN.USN_JOURNAL_DATA)), ref bytesReturned, IntPtr.Zero)) { throw new Win32Exception(Marshal.GetLastWin32Error()); } Win32USN.BY_HANDLE_FILE_INFORMATION fileInfo; using (SafeFileHandle driveHandle = Win32USN.CreateFile(System.IO.Path.GetPathRoot(path), Win32USN.EFileAccess.GenericRead, Win32USN.EFileShare.ReadWrite, IntPtr.Zero, Win32USN.ECreationDisposition.OpenExisting, Win32USN.EFileAttributes.BackupSemantics, IntPtr.Zero)) if (!Win32USN.GetFileInformationByHandle(driveHandle, out fileInfo)) { throw new Win32Exception(Marshal.GetLastWin32Error()); } m_volumeRootFileNameReferenceNumber = ((ulong)fileInfo.FileIndexHigh << 32) | ((ulong)fileInfo.FileIndexLow); } catch { if (m_volumeHandle != null) { m_volumeHandle.Dispose(); m_volumeHandle = null; } throw; } if (this.FileSystemEntries.Count == 0) { throw new Exception(Strings.USNHelper.SafeGuardError); } }
/// <summary> /// Unused internal function that can be used to read all USN records /// </summary> /// <param name="lastUsn">The USN number to start from</param> private void GetChangedItems(long lastUsn) { const int ALLOCATED_MEMORY = 64 * 1024; IntPtr allocatedMemory = IntPtr.Zero; List <KeyValuePair <string, Win32USN.USN_RECORD> > records = new List <KeyValuePair <string, Win32USN.USN_RECORD> >(); try { uint bytesRead = 0; bool more = true; allocatedMemory = Marshal.AllocHGlobal(ALLOCATED_MEMORY); Win32USN.READ_USN_JOURNAL_DATA startParams = new Win32USN.READ_USN_JOURNAL_DATA(); startParams.UsnJournalID = m_journal.UsnJournalID; startParams.StartUsn = lastUsn; startParams.ReasonMask = Win32USN.USNReason.USN_REASON_ANY; startParams.ReturnOnlyOnClose = 0; startParams.Timeout = 0; startParams.BytesToWaitFor = 0; while (more) { if (!Win32USN.DeviceIoControl(m_volumeHandle, Win32USN.EIOControlCode.FsctlReadUsnJournal, startParams, (uint)Marshal.SizeOf(typeof(Win32USN.READ_USN_JOURNAL_DATA)), allocatedMemory, ALLOCATED_MEMORY, ref bytesRead, IntPtr.Zero)) { int errorCode = Marshal.GetLastWin32Error(); //If we get no error or EOF the enumeration is completed if (errorCode == Win32USN.ERROR_HANDLE_EOF || errorCode == Win32USN.ERROR_SUCCESS) { break; } else { throw new Win32Exception(errorCode); } } startParams.StartUsn = ExtractUsnEntries(bytesRead, allocatedMemory, records, out more); } //Records now contains all Usn entries } finally { if (allocatedMemory != IntPtr.Zero) { Marshal.FreeHGlobal(allocatedMemory); allocatedMemory = IntPtr.Zero; } } }
/// <summary> /// Returns a list of all files and folders changed since the USN /// </summary> /// <param name="startUsn">The USN number to start the list from, set to zero to get all</param> /// <returns>A list of files and folders changed since the USN</returns> private List <KeyValuePair <string, Win32USN.USN_RECORD> > BuildUSNTable(long startUsn) { const int ALLOCATED_MEMORY = 64 * 1024; IntPtr allocatedMemory = IntPtr.Zero; List <KeyValuePair <string, Win32USN.USN_RECORD> > records = new List <KeyValuePair <string, Win32USN.USN_RECORD> >(); try { uint bytesRead = 0; bool more = true; allocatedMemory = Marshal.AllocHGlobal(ALLOCATED_MEMORY); Win32USN.MFT_ENUM_DATA startParams = new Win32USN.MFT_ENUM_DATA(); startParams.StartFileReferenceNumber = 0; startParams.LowUsn = Math.Max(startUsn, m_journal.LowestValidUsn); startParams.HighUsn = m_journal.NextUsn; while (more) { if (!Win32USN.DeviceIoControl(m_volumeHandle, Win32USN.EIOControlCode.FsctlEnumUsnData, ref startParams, (uint)Marshal.SizeOf(typeof(Win32USN.MFT_ENUM_DATA)), allocatedMemory, ALLOCATED_MEMORY, ref bytesRead, IntPtr.Zero)) { int errorCode = Marshal.GetLastWin32Error(); //If we get no error or EOF the enumeration is completed if (errorCode == Win32USN.ERROR_HANDLE_EOF || errorCode == Win32USN.ERROR_SUCCESS) { break; } else { throw new Win32Exception(errorCode); } } startParams.StartFileReferenceNumber = (ulong)ExtractUsnEntries(bytesRead, allocatedMemory, records, out more); } return(ParseRecordList(records)); } finally { if (allocatedMemory != IntPtr.Zero) { Marshal.FreeHGlobal(allocatedMemory); allocatedMemory = IntPtr.Zero; } } }
/// <summary> /// Get NTFS file reference number (FRN) from path /// </summary> /// <param name="filePath">Input path</param> /// <returns>NTFS file reference number</returns> private static ulong GetFileRefNumber(string filePath) { Win32USN.BY_HANDLE_FILE_INFORMATION fileInfo; using (var driveHandle = Win32USN.CreateFile(filePath, Win32USN.FileAccess.GenericRead, Win32USN.FileShare.ReadWrite, IntPtr.Zero, Win32USN.CreationDisposition.OpenExisting, Win32USN.FileAttributes.BackupSemantics, IntPtr.Zero)) { if (!Win32USN.GetFileInformationByHandle(driveHandle, out fileInfo)) { throw new Win32Exception(Marshal.GetLastWin32Error()); } } return(((ulong)fileInfo.FileIndexHigh << 32) | fileInfo.FileIndexLow); }
/// <summary> /// Retrieves a USN_RECORD_V2 by file reference number (FRN, not USN!) /// </summary> /// <param name="frn">File reference number</param> /// <returns>Returned entry if successful; null otherwise</returns> private Record GetRecordByFileRef(ulong frn) { var enumData = new Win32USN.MFT_ENUM_DATA { StartFileReferenceNumber = frn, LowUsn = 0, HighUsn = m_journal.NextUsn }; var bufferSize = 512; byte[] entryData; while (!Win32USN.ControlWithInput(m_volumeHandle, Win32USN.FsCtl.EnumUSNData, ref enumData, bufferSize, out entryData)) { var e = Marshal.GetLastWin32Error(); if (e != Win32USN.ERROR_INSUFFICIENT_BUFFER) { return(null); } // retry, increasing buffer size bufferSize *= 2; } // not really a foreach: we only check the first record foreach (var rec in EnumerateRecords(entryData)) { if (rec.UsnRecord.FileReferenceNumber == frn) { return(rec); } break; } return(null); }
/// <summary> /// Returns collection of USN records, starting at startUSN /// </summary> /// <param name="startUsn">The USN number to start the list from, set to zero to get all</param> /// <returns>A list of files and folders changed since the USN</returns> private ICollection <Record> GetRawRecords(long startUsn) { var records = new List <Record>(); var readData = new Win32USN.READ_USN_JOURNAL_DATA_V0 { StartUsn = Math.Max(startUsn, m_journal.LowestValidUsn), ReasonMask = Win32USN.USNReason.USN_REASON_BASIC_INFO_CHANGE | Win32USN.USNReason.USN_REASON_DATA_EXTEND | Win32USN.USNReason.USN_REASON_DATA_OVERWRITE | Win32USN.USNReason.USN_REASON_DATA_TRUNCATION | Win32USN.USNReason.USN_REASON_EA_CHANGE | Win32USN.USNReason.USN_REASON_FILE_CREATE | Win32USN.USNReason.USN_REASON_FILE_DELETE | Win32USN.USNReason.USN_REASON_HARD_LINK_CHANGE | Win32USN.USNReason.USN_REASON_NAMED_DATA_EXTEND | Win32USN.USNReason.USN_REASON_NAMED_DATA_OVERWRITE | Win32USN.USNReason.USN_REASON_NAMED_DATA_TRUNCATION | Win32USN.USNReason.USN_REASON_RENAME_NEW_NAME | Win32USN.USNReason.USN_REASON_RENAME_OLD_NAME | Win32USN.USNReason.USN_REASON_REPARSE_POINT_CHANGE | Win32USN.USNReason.USN_REASON_SECURITY_CHANGE | Win32USN.USNReason.USN_REASON_STREAM_CHANGE, ReturnOnlyOnClose = 0, Timeout = 0, BytesToWaitFor = 0, UsnJournalID = m_journal.UsnJournalID }; var bufferSize = 4096; // larger buffer returns more record, but pervents user from cancelling operation for a longer time while (readData.StartUsn < m_journal.NextUsn) { if (!Win32USN.ControlWithInput(m_volumeHandle, Win32USN.FsCtl.ReadUSNJournal, ref readData, bufferSize, out var entryData)) { var e = Marshal.GetLastWin32Error(); if (e == Win32USN.ERROR_HANDLE_EOF || e == Win32USN.ERROR_SUCCESS) { break; } if (e == Win32USN.ERROR_INSUFFICIENT_BUFFER) { bufferSize = bufferSize * 2; continue; } if (e == Win32USN.ERROR_JOURNAL_ENTRY_DELETED) { throw new UsnJournalSoftFailureException(Strings.USNHelper.JournalEntriesDeleted, new Win32Exception(e)); } throw new Win32Exception(e); } records.AddRange(EnumerateRecords(entryData).TakeWhile(rec => rec.UsnRecord.Usn >= startUsn && rec.UsnRecord.Usn < m_journal.NextUsn)); readData.StartUsn = Marshal.ReadInt64(entryData, 0); } return(records); }