using System; using System.Collections.Generic; using UAF; using UAF.Interfaces.Logging; using System.IO; using System.Runtime.InteropServices; using System.Xml.Linq; using System.Diagnostics; namespace DataFileAnalyzer { /// /// Stores informations associated to the File Reference Numbers of a folder of a NTFS volume. /// class NtfsFolderDictionary { private ILog m_log = new NullLog(); private string m_folder; private UInt64 m_folderFRN; private string m_drive; private List m_excludeFolders = new List(); private List m_excludedFRNs = new List(); private IntPtr m_usnJournalRootHandle = IntPtr.Zero; /// /// Informations about a FRN /// private class FRNInfo { public string Name { get; set; } public UInt64 ParentFRN { get; set; } public string NormalizedFullPath { get; set; } public bool IsFolder { get; set; } public FRNInfo(string _name, UInt64 _parentFrn, bool _isFolder) { Name = _name; ParentFRN = _parentFrn; NormalizedFullPath = null; IsFolder = _isFolder; } } private Dictionary m_frnInfos = new Dictionary(); /// /// Constructor /// public NtfsFolderDictionary(ILog _log) { m_log = _log; m_folder = null; m_drive = null; m_excludeFolders = null; m_usnJournalRootHandle = IntPtr.Zero; } /// /// The data folder of the library has changed /// public void OnDataFolderChanged(string _folder, List _excludedFolders) { m_folder = _folder; m_drive = Path.GetPathRoot(_folder); m_excludeFolders = new List(_excludedFolders); } /// /// Set the journal handle /// public void SetJournalHandle(IntPtr _journalHandle) { m_usnJournalRootHandle = _journalHandle; } /// /// Does _normalizedPath points to a file in this folder? /// private bool IsPathInFolder(string _normalizedPath) { if (_normalizedPath.StartsWith(m_folder)) { foreach (string folder in m_excludeFolders) { if (_normalizedPath.StartsWith(folder)) return false; } return true; } return false; } /// /// Get all the files of the folder /// public IEnumerable GetAllFiles() { foreach (KeyValuePair pair in m_frnInfos) { FRNInfo info = pair.Value; if (!info.IsFolder) yield return info.NormalizedFullPath; } } /// /// Get the normalized file path of a FRN /// public string GetFRNFullPath(UInt64 _frn) { FRNInfo info; if (m_frnInfos.TryGetValue(_frn, out info)) return info.NormalizedFullPath; return null; } /// /// Is this FRN in the dictionary? /// public bool IsFRNInDictionary(UInt64 _frn) { return m_frnInfos.ContainsKey(_frn); } /// /// Is _childFrn a child of _parentFrn? /// private bool IsChildOf(UInt64 _childFrn, UInt64 _parentFrn) { FRNInfo info; while (m_frnInfos.TryGetValue(_childFrn, out info)) { if (info.ParentFRN == _parentFrn) return true; _childFrn = info.ParentFRN; } return false; } /// /// Get the children of an element of the dictionary /// private void GetChildrenOf(UInt64 _frn, out List> _children) { _children = new List>(); foreach (KeyValuePair pair in m_frnInfos) { if (pair.Value.ParentFRN == _frn || IsChildOf(pair.Key, _frn)) _children.Add(pair); } } /// /// Is _frn a child of _rootFrn given all the FRN infos _allInfos? /// private static bool IsFRNInFolder(UInt64 _frn, UInt64 _rootFrn, List _excludedFRNs, Dictionary _allInfos) { if (_frn == _rootFrn) return true; if (_excludedFRNs.Contains(_frn)) return false; FRNInfo info = null; while (_allInfos.TryGetValue(_frn, out info)) { if (info.ParentFRN == _rootFrn) return true; if (_excludedFRNs.Contains(info.ParentFRN)) return false; _frn = info.ParentFRN; } return false; } /// /// Parse the root folder to initialize the dictionary /// public bool InitFromFolder() { m_frnInfos.Clear(); m_excludedFRNs.Clear(); DateTime startTime = DateTime.UtcNow; if (!Win32Api.GetFileReferenceFromPath(m_folder, out m_folderFRN)) { m_log.Error("NtfsVolumeDictionary could not initialize!"); return false; } UInt64 excludedFRN; foreach (string excludedFolder in m_excludeFolders) { if (!Win32Api.GetFileReferenceFromPath(excludedFolder, out excludedFRN)) { m_log.Error("NtfsVolumeDictionary could not initialize!"); return false; } m_excludedFRNs.Add(excludedFRN); } List addedFiles; AddFolder(null, 0, m_folderFRN, out addedFiles); m_log.Message("NtfsVolumeDictionary: Init done in {0}. {1} files and folders in data folder.", (DateTime.UtcNow - startTime).ToReadableString(), m_frnInfos.Count); return true; } /// /// DEPRECATED: We can miss files in the folder if the journal has been (re)created /// since the folder was created. /// Read the MFT entrie before _highUsn to init the dictionary. /// public bool InitFromMFT(Int64 _highUsn, out Int64 _lastUsnRead) { _lastUsnRead = 0; m_frnInfos.Clear(); m_excludedFRNs.Clear(); DateTime startTime = DateTime.UtcNow; if (!Win32Api.GetFileReferenceFromPath(m_folder, out m_folderFRN)) { m_log.Error("NtfsVolumeDictionary could not initialize!"); return false; } UInt64 excludedFRN; foreach (string excludedFolder in m_excludeFolders) { if (!Win32Api.GetFileReferenceFromPath(excludedFolder, out excludedFRN)) { m_log.Error("NtfsVolumeDictionary could not initialize!"); return false; } m_excludedFRNs.Add(excludedFRN); } Win32Api.MFT_ENUM_DATA enumData; enumData.StartFileReferenceNumber = 0; enumData.LowUsn = 0; enumData.HighUsn = _highUsn; Int32 enumDataSize = Marshal.SizeOf(enumData); IntPtr enumDataBuffer = Marshal.AllocHGlobal(enumDataSize); Win32Api.ZeroMemory(enumDataBuffer, enumDataSize); Marshal.StructureToPtr(enumData, enumDataBuffer, true); int bufferSize = sizeof(UInt64) + 10000; IntPtr bufferPointer = Marshal.AllocHGlobal(bufferSize); Win32Api.ZeroMemory(bufferPointer, bufferSize); uint bytesRead = 0; Dictionary allFRNs = new Dictionary(); while (Win32Api.DeviceIoControl( m_usnJournalRootHandle, Win32Api.FSCTL_ENUM_USN_DATA, enumDataBuffer, enumDataSize, bufferPointer, bufferSize, out bytesRead, IntPtr.Zero)) { // Skip the next USN stored at the beginning of the buffer bytesRead -= sizeof(Int64); IntPtr usnRecordPointer = IntPtr.Add(bufferPointer, sizeof(Int64)); while (bytesRead > 0) { Win32Api.UsnEntry usnEntry = new Win32Api.UsnEntry(usnRecordPointer); FRNInfo frnInfo = new FRNInfo(usnEntry.Name.ToLower(), usnEntry.ParentFileReferenceNumber, usnEntry.IsFolder); allFRNs.Add(usnEntry.FileReferenceNumber, frnInfo); if (usnEntry.USN > _lastUsnRead) _lastUsnRead = usnEntry.USN; usnRecordPointer = new IntPtr(usnRecordPointer.ToInt32() + usnEntry.RecordLength); usnRecordPointer = IntPtr.Add(usnRecordPointer, (int)usnEntry.RecordLength); bytesRead -= usnEntry.RecordLength; } Marshal.WriteInt64(enumDataBuffer, Marshal.ReadInt64(bufferPointer, 0)); } Marshal.FreeHGlobal(enumDataBuffer); Marshal.FreeHGlobal(bufferPointer); DateTime endMFTParseTime = DateTime.UtcNow; // Add the FRN infos to the dictionary if they are in the right folder foreach (KeyValuePair pair in allFRNs) { FRNInfo frnInfo = pair.Value; if (IsFRNInFolder(pair.Key, m_folderFRN, m_excludedFRNs, allFRNs)) m_frnInfos.Add(pair.Key, frnInfo); } // Compute the full paths foreach (KeyValuePair pair in m_frnInfos) ComputeFullPath(pair.Key, pair.Value); DateTime utcNow = DateTime.UtcNow; m_log.Message("NtfsVolumeDictionary: Init done in {0}. MFT parsing done in {3}. {1} files parsed. {2} files in data folder.", (utcNow - startTime).ToReadableString(), allFRNs.Count, m_frnInfos.Count, (utcNow - endMFTParseTime).ToReadableString()); return true; } /// /// Build the full path of a FRN info /// private void ComputeFullPath(UInt64 _frn, FRNInfo _info) { if (_frn != m_folderFRN) { string fullPath = _info.Name; FRNInfo parentInfo = _info; while (parentInfo.ParentFRN != m_folderFRN && m_frnInfos.TryGetValue(parentInfo.ParentFRN, out parentInfo)) fullPath = parentInfo.Name + '/' + fullPath; _info.NormalizedFullPath = m_folder + '/' + fullPath; } else { _info.NormalizedFullPath = m_folder; } } /// /// Add a file to the dictionary /// public void AddFile( string _name, UInt64 _frn, UInt64 _parentFrn) { FRNInfo newInfo = new FRNInfo(_name.ToLower(), _parentFrn, false); m_frnInfos[_frn] = newInfo; ComputeFullPath(_frn, newInfo); } /// /// Remove a file from the dictionary /// public void RemoveFile(UInt64 _frn) { m_frnInfos.Remove(_frn); } /// /// Rename or move file /// public void MoveFile(UInt64 _frn, string _newName, UInt64 _newParentFrn) { FRNInfo info; if (m_frnInfos.TryGetValue(_frn, out info)) { info.Name = _newName.ToLower(); info.ParentFRN = _newParentFrn; ComputeFullPath(_frn, info); } } /// /// Add a folder to the dictionary. All the sub-folders and files are added. /// _addedFiles is filled with the full paths of the files added to the dictionary. /// WARNING: Rough version. Could be optimized. /// public void AddFolder(string _name, UInt64 _frn, UInt64 _parentFrn, out List _addedFiles) { _addedFiles = new List(); UInt64 frn; FRNInfo newInfo; Dictionary folderPathToFRN = new Dictionary(); string folderFullPath; if (string.IsNullOrEmpty(_name) && _parentFrn == m_folderFRN) { // We're adding the root folder FRNInfo rootInfo = new FRNInfo(Path.GetFileName(m_folder), 0, true); rootInfo.NormalizedFullPath = m_folder; m_frnInfos[_parentFrn] = rootInfo; folderFullPath = m_folder; folderPathToFRN.Add(m_folder, m_folderFRN); } else { FRNInfo parentInfo; if (!m_frnInfos.TryGetValue(_parentFrn, out parentInfo)) return; folderFullPath = parentInfo.NormalizedFullPath + '/' + _name.ToLower(); // Check that this is not an excluded folder if (!IsPathInFolder(folderFullPath)) return; folderPathToFRN.Add(parentInfo.NormalizedFullPath, _parentFrn); } if (Win32Api.GetFileReferenceFromPath(folderFullPath, out frn)) { BasicFileEnumerator enumerator = new BasicFileEnumerator(); foreach (string path in enumerator.GetAllFolders(folderFullPath, m_excludeFolders)) { if (Win32Api.GetFileReferenceFromPath(path, out frn)) { string fileName = Path.GetFileName(path); string parentPath = PathUtils.Normalize(Path.GetDirectoryName(path)); UInt64 parentFrn; if (folderPathToFRN.TryGetValue(parentPath, out parentFrn)) { newInfo = new FRNInfo(fileName.ToLower(), parentFrn, true); newInfo.NormalizedFullPath = path; m_frnInfos[frn] = newInfo; folderPathToFRN.Add(path, frn); } } } foreach (string path in enumerator.GetAllFiles(folderFullPath, m_excludeFolders)) { if (Win32Api.GetFileReferenceFromPath(path, out frn)) { string fileName = Path.GetFileName(path); string parentPath = PathUtils.Normalize(Path.GetDirectoryName(path)); UInt64 parentFrn; if (folderPathToFRN.TryGetValue(parentPath, out parentFrn)) { newInfo = new FRNInfo(fileName.ToLower(), parentFrn, false); newInfo.NormalizedFullPath = PathUtils.Normalize(path); m_frnInfos[frn] = newInfo; _addedFiles.Add(newInfo.NormalizedFullPath); } } } } } /// /// Remove a folder from the dictionary. All the sub-folders and files /// will be removed. Add all the removed files to _removedFiles. /// WARNING: Rough version. Could be optimized. /// public void RemoveFolder(UInt64 _frn, out List _removedFiles) { _removedFiles = new List(); FRNInfo info; if (m_frnInfos.TryGetValue(_frn, out info) && info.IsFolder) { List> removedList; GetChildrenOf(_frn, out removedList); m_frnInfos.Remove(_frn); foreach (KeyValuePair pair in removedList) { FRNInfo childInfo = pair.Value; if (!childInfo.IsFolder) _removedFiles.Add(childInfo.NormalizedFullPath); m_frnInfos.Remove(pair.Key); } } } /// /// Move a folder inside the dictionary. All the sub-folders and files /// will be updated accordingly. Add the old path of all the moved files /// to _removedFiles and add their new path to _addedFiles. /// WARNING: Rough version. Could be optimized. /// public void MoveFolder(UInt64 _frn, string _newName, UInt64 _newParentFrn, out List _addedFiles, out List _removedFiles) { _addedFiles = new List(); _removedFiles = new List(); FRNInfo info; if (m_frnInfos.TryGetValue(_frn, out info) && info.IsFolder) { List> childrenList; GetChildrenOf(_frn, out childrenList); info.ParentFRN = _newParentFrn; info.Name = _newName; ComputeFullPath(_frn, info); foreach (KeyValuePair pair in childrenList) { UInt64 childKey = pair.Key; FRNInfo childInfo = pair.Value; if (!childInfo.IsFolder) { _removedFiles.Add(childInfo.NormalizedFullPath); ComputeFullPath(childKey, childInfo); _addedFiles.Add(childInfo.NormalizedFullPath); } else { ComputeFullPath(childKey, childInfo); } } } } /// /// Save current state in _parentElement /// public bool Save(XElement _parentElement) { XElement element = new XElement("FRNInfosList"); _parentElement.Add(element); foreach (KeyValuePair pair in m_frnInfos) { XElement frnElement = new XElement("FRNInfo"); element.Add(frnElement); element.SetAttributeValue("RootFRN", m_folderFRN); frnElement.SetAttributeValue("FRN", pair.Key); FRNInfo info = pair.Value; frnElement.SetAttributeValue("ParentFRN", info.ParentFRN); frnElement.SetAttributeValue("Name", info.Name); frnElement.SetAttributeValue("FullPath", info.NormalizedFullPath); Debug.Assert(!string.IsNullOrEmpty(info.NormalizedFullPath)); frnElement.SetAttributeValue("IsFolder", info.IsFolder); } return true; } /// /// Load current state from _parentElement /// public bool Load(XElement _parentElement) { m_frnInfos.Clear(); XElement element = _parentElement.Element("FRNInfosList"); if (element == null) return false; if (!UInt64.TryParse(element.Attribute("RootFRN").Value, out m_folderFRN)) return false; foreach (XElement frnElement in element.Elements("FRNInfo")) { UInt64 frn; UInt64 parentFrn; string name = frnElement.Attribute("Name").Value; string fullPath = frnElement.Attribute("FullPath").Value; bool isFolder; if ( UInt64.TryParse(frnElement.Attribute("FRN").Value, out frn) && UInt64.TryParse(frnElement.Attribute("ParentFRN").Value, out parentFrn) && !string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(fullPath) && Boolean.TryParse(frnElement.Attribute("IsFolder").Value, out isFolder)) { FRNInfo newInfo = new FRNInfo(name, parentFrn, isFolder); newInfo.NormalizedFullPath = fullPath; m_frnInfos.Add(frn, newInfo); } else { m_frnInfos.Clear(); return false; } } return true; } } }