internal static unsafe ulong CountAttributes(NtfsFileRecord *record) { ulong result = 0; NtfsAttribute *scannedAttribute = (NtfsAttribute *)((byte *)record + record->AttributesOffset); while (!scannedAttribute->IsLast) { result++; scannedAttribute = (NtfsAttribute *)((byte *)scannedAttribute + scannedAttribute->Length); } return(result); }
internal unsafe void BinaryDumpContent() { Stream attributeDataStream = null; try { NtfsAttribute *dataAttribute = GetAttribute(NtfsAttributeType.AttributeData, out attributeDataStream); if (null == dataAttribute) { throw new ApplicationException(); } if (dataAttribute->IsResident) { NtfsResidentAttribute *realDataAttribute = (NtfsResidentAttribute *)dataAttribute; Helpers.BinaryDump((byte *)realDataAttribute + realDataAttribute->ValueOffset, realDataAttribute->ValueLength); } else { NtfsNonResidentAttribute *realDataAttribute = (NtfsNonResidentAttribute *)dataAttribute; byte[] localBuffer = new byte[16 * 1024]; fixed(byte *pBuffer = localBuffer) { if (null == attributeDataStream) { attributeDataStream = realDataAttribute->OpenDataStream(); } while (true) { int readLength = attributeDataStream.Read(localBuffer, 0, localBuffer.Length); if (-1 == readLength) { break; } Helpers.BinaryDump(pBuffer, (uint)readLength); if (readLength < localBuffer.Length) { break; } } } } } finally { if (null != attributeDataStream) { attributeDataStream.Close(); } } }
private unsafe bool HandleData(NtfsAttribute *candidateAttribute, Stream attributeData, out bool retry) { if (null != attributeData) { retry = false; // Delegate the continuation decision to our caller. return(_callback(candidateAttribute, attributeData)); } if ((null != _nameFilter) && !_nameFilter(candidateAttribute)) { // The name filter failed. Go on with next entry. retry = false; return(true); } // The caller is interested in the attribute, either because there is no // name filter. We want the full data. retry = true; // We will land in the conditional block above. return(true); }
/// <summary>Retrieve the Nth attribute of a given kind from a file record.</summary> /// <param name="kind">Searched attribute type.</param> /// <param name="dataStream">On return this parameter value is a null reference most of /// the time. Otherwise for attributes that are retrieved from an /// <see cref="NtfsAttributeListAttribute"/> instance, this is a stream that support /// attribute data content reading.</param> /// <param name="order">Attribute rank. Default is first. This is usefull for some kind of /// attributes such as Data one that can appear several times in a record.</param> /// <param name="nameFilter">An optional name filter delegate that will sort out those /// attributes we want to retrieve based on their name.</param> /// <returns>The retrieved attribute or a null reference if not found.</returns> internal unsafe NtfsAttribute *GetAttribute(NtfsAttributeType kind, out Stream dataStream, uint order = 1, AttributeNameFilterDelegate nameFilter = null) { NtfsAttribute *result = null; Stream retrievedDataStream = null; EnumerateRecordAttributes(delegate(NtfsAttribute * found, Stream attributeDataStream) { if (kind == found->AttributeType) { if (0 == --order) { result = found; retrievedDataStream = attributeDataStream; return(false); } } return(true); }, kind, nameFilter); dataStream = retrievedDataStream; return(result); }
/// <summary>Enumerate attributes bound to this <see cref="NtfsFileRecord"/>, optionally filtering /// on attribute name.</summary> /// <param name="header">The file record.</param> /// <param name="callback"></param> /// <param name="searchedAttributeType"></param> /// <param name="nameFilter">An optional name filter that will be provided with the basic attribute /// properties (including name) in order to decide if data should be retrieved. This is especially /// usefull for <see cref="NtfsAttributeListAttribute"/> attributes that may reference lengthy /// attributes data which are expensive to retrieve.</param> internal static unsafe void EnumerateRecordAttributes(NtfsFileRecord *header, RecordAttributeEnumeratorCallbackDelegate callback, NtfsAttributeType searchedAttributeType, AttributeNameFilterDelegate nameFilter) { // Walk attributes, seeking for the searched one. NtfsAttribute *currentAttribute = (NtfsAttribute *)((byte *)header + header->AttributesOffset); NtfsAttributeListAttribute *pendingAttributeList = null; NtfsAttribute *[] candidates = new NtfsAttribute *[MaxAttributeCount]; int candidatesCount = 0; for (int attributeIndex = 0; attributeIndex < header->NextAttributeNumber; attributeIndex++) { if (currentAttribute->IsLast) { break; } if (header->BytesInUse < ((byte *)currentAttribute - (byte *)header)) { break; } if (NtfsAttributeType.EndOfListMarker == currentAttribute->AttributeType) { break; } // If we found an AttributeListAttribute, we must go one level deeper to // complete the enumeration. if (NtfsAttributeType.AttributeAttributeList == currentAttribute->AttributeType) { if (null != pendingAttributeList) { // No more than one attribute of this kind per file record. throw new ApplicationException(); } if (NtfsAttributeType.AttributeAttributeList == searchedAttributeType) { if (!callback(currentAttribute, null)) { return; } } // Defer handling pendingAttributeList = (NtfsAttributeListAttribute *)currentAttribute; break; } if (candidatesCount >= MaxAttributeCount) { throw new ApplicationException(); } if ((NtfsAttributeType.Any == searchedAttributeType) || (currentAttribute->AttributeType == searchedAttributeType)) { candidates[candidatesCount++] = currentAttribute; } currentAttribute = (NtfsAttribute *)((byte *)currentAttribute + currentAttribute->Length); } if (NtfsAttributeType.AttributeAttributeList == searchedAttributeType) { // Either we already found one such attribute and invoked the callback or found none and // we can return immediately. Should we have found several such attributes we would have // risen an exception. return; } if (null == pendingAttributeList) { // We already walked every attributes and captured those that matched the type filter if // any. Invoke callbak on each such attribute. for (int candidateIndex = 0; candidateIndex < candidatesCount; candidateIndex++) { if (!callback(candidates[candidateIndex], null)) { return; } } // We are done. return; } NtfsAttributeListAttribute.Dump(pendingAttributeList); // HandleAttributeListAttributeEntry // We have an attribute list attribute. Delegate him the enumeration. NtfsPartition currentPartition = NtfsPartition.Current; NtfsAttributeListAttribute.EnumerateEntries((NtfsAttribute *)pendingAttributeList, searchedAttributeType, new ListEntryHandler(searchedAttributeType, nameFilter, callback).HandleListEntry); return; }
private unsafe void _Run() { NtfsPartition partition = Partition; IPartitionClusterData clusterData = null; try { // Note : We could also use the NtfsWellKnownMetadataFiles.Extend entry to // locate the directory, then find the $UsnJrnl entry directly from there. string fileName = @"$UsnJrnl"; NtfsIndexEntryHeader *fileDescriptor = partition.FindFile(fileName, NtfsWellKnownMetadataFiles.Extend); if (null == fileDescriptor) { throw new System.IO.FileNotFoundException(fileName); } IPartitionClusterData fileData = null; try { NtfsFileRecord *fileRecord = partition.GetFileRecord(fileDescriptor->FileReference, ref fileData); fileRecord->AssertRecordType(); // We retrieve the first attribute here. Stream dataStream; NtfsAttribute *jAttribute = fileRecord->GetAttribute(NtfsAttributeType.AttributeData, out dataStream, 1, _isDollarJAttributeNameFilter); if (null == jAttribute) { throw new ApplicationException(); } if (jAttribute->IsResident) { // Seems this is never the case. throw new NotSupportedException("CODE REVIEW REQUIRED"); } NtfsNonResidentAttribute *jNrAttribute = (NtfsNonResidentAttribute *)jAttribute; jNrAttribute->Dump(); byte[] buffer = new byte[NtfsPartition.Current.ClusterSize]; DateTime sparseReadStartTime = DateTime.UtcNow; TimeSpan sparseReadDuration; if (null == dataStream) { dataStream = jNrAttribute->OpenDataStream(); throw new ApplicationException("CODE REVIEW REQUIRED"); } int totalReads = 0; bool nonNullByteFound = false; while (true) { int readCount = dataStream.Read(buffer, 0, buffer.Length); if (-1 == readCount) { sparseReadDuration = DateTime.UtcNow - sparseReadStartTime; break; } if (nonNullByteFound) { for (int index = 0; index < readCount; index++) { if (0 == buffer[index]) { continue; } sparseReadDuration = DateTime.UtcNow - sparseReadStartTime; Console.WriteLine("{0} null leading bytes found after {1} secs.", totalReads + index, (int)sparseReadDuration.TotalSeconds); nonNullByteFound = true; break; } } totalReads += readCount; if (nonNullByteFound) { Helpers.BinaryDump(buffer, (uint)readCount); } } if (!nonNullByteFound) { sparseReadDuration = DateTime.UtcNow - sparseReadStartTime; Console.WriteLine("{0} null leading bytes found after {1} secs.", totalReads, (int)sparseReadDuration.TotalSeconds); } throw new NotImplementedException(); NtfsAttribute *rawAttribute = fileRecord->GetAttribute(NtfsAttributeType.AttributeData, out dataStream, 1); if (null == rawAttribute) { throw new ApplicationException(); } if ("$Max" != rawAttribute->Name) { throw new ApplicationException(); } if (rawAttribute->IsResident) { NtfsResidentAttribute *reMaxAttribute = (NtfsResidentAttribute *)rawAttribute; if (FeaturesContext.InvariantChecksEnabled) { if (0x20 != reMaxAttribute->ValueLength) { throw new ApplicationException(); } } MaxAttribute *maxAttribute = (MaxAttribute *)((byte *)reMaxAttribute + reMaxAttribute->ValueOffset); } else { throw new NotSupportedException(); } rawAttribute = fileRecord->GetAttribute(NtfsAttributeType.AttributeData, out dataStream, 2); if (null == rawAttribute) { throw new ApplicationException(); } if ("$J" != rawAttribute->Name) { throw new ApplicationException(); } throw new NotImplementedException(); } finally { if (null != fileData) { fileData.Dispose(); } } } finally { if (null != clusterData) { clusterData.Dispose(); } } }
private static unsafe bool IsDollarJAttribute(NtfsAttribute *candidate) { bool isJAttribute = "$J" == candidate->Name; return(isJAttribute); }
/// <summary>The caller enumerating entries might be interested in the content of /// the currently scanned entry. This might be either for additional filtering at /// attribute level (including attribute name) or for full attribute data processing. /// </summary> /// <param name="entry">The scanned list entry of interest. Should a single /// attribute span several entries, this one is guaranteed to be the first one for the /// attribute.</param> /// <param name="remainingBytesInList">Number of bytes remaining in list, relatively to the /// entry address.</param> /// <param name="entryReferencedAttributeHandler">The callback that will be invoked /// with the referenced attribute with or without full attribute data depending on /// the value of <paramref name="dataIncluded"/></param> /// <param name="dataIncluded"></param> /// <returns>true if caller should continue process data, false if it should stop.</returns> private static unsafe bool HandleEntryReferencedAttribute(ListEntry *entry, uint remainingBytesInList, EntryListReferencedAttributeHandlerDelegate entryReferencedAttributeHandler, bool dataIncluded) { ushort currentAttributeNumber = entry->AttributeNumber; NtfsAttributeType currentAttributeType = entry->AttributeType; byte * baseAddress = (byte *)entry; uint relativeOffset = 0; // The last callback invocation decided it needs some more data before deciding // what to do. IPartitionClusterData clusterData = null; NtfsPartition currentPartition = NtfsPartition.Current; using (PartitionDataDisposableBatch batch = PartitionDataDisposableBatch.CreateNew()) { while (true) { ListEntry * scannedEntry = (ListEntry *)baseAddress; ulong mainFileReferenceNumber = entry->FileReferenceNumber; List <ulong> entries = new List <ulong>(); Stream dataStream = null; if (dataIncluded) { // Read each record and prepare for data retrieval. while (true) { entries.Add(scannedEntry->FileReferenceNumber); relativeOffset += scannedEntry->EntryLength; if (relativeOffset >= remainingBytesInList) { // Take care not to go further than the end of the list. break; } scannedEntry = (ListEntry *)(baseAddress + relativeOffset); if ((currentAttributeNumber != scannedEntry->AttributeNumber) || (currentAttributeType != scannedEntry->AttributeType)) { break; } } dataStream = new MultiRecordAttributeDataStream(entries); } // Retrieve attribute itself. NtfsFileRecord *mainFileRecord = currentPartition.GetFileRecord(mainFileReferenceNumber, ref clusterData); if (null == mainFileRecord) { throw new ApplicationException(); } NtfsAttribute *retrievedAttribute = (NtfsAttribute *)((byte *)mainFileRecord + mainFileRecord->AttributesOffset); // Invoke callback. bool retry; if (!entryReferencedAttributeHandler(retrievedAttribute, dataStream, out retry)) { // After attribute has been processed, it has been decided no other list // entry should be performed. return(false); } if (!retry) { // After attribute has been processed, it has been decided that no // additional data from this attribute is required. However the enumeration // of other list entries should continue. return(true); } if (dataIncluded) { throw new InvalidOperationException(); } // Attribute has been processed, however not enough data was available for a // final decision. We loop and include all data now. dataIncluded = true; } } }
/// <summary></summary> /// <param name="from">The NtfsAttributeListAttribute to be used for enumeration.</param> /// <param name="searchedAttributeType">The type of the searched attribute or /// <see cref="NtfsAttributeType.Any"/> if the caller is interested in all kinds of /// attributes.</param> /// <param name="listEntryHandler">A callback to be invoked on each entry matching /// the attribute type selection criteria.</param> /// <remarks>WARNING : This might seems counterintuitive to have this method at a class /// level instead of making it an instance one. This is because we absolutely don't want /// it to be invoked on an object reference that is subject to being moved in memory by /// the GC. Forcing the caller to provide a pointer makes her responsible for enforcing /// the pinning requirements.</remarks> internal static unsafe void EnumerateEntries(NtfsAttribute *from, NtfsAttributeType searchedAttributeType, EntryEnumeratorCallbackDelegate listEntryHandler) { if (null == from) { throw new ArgumentNullException(); } if (NtfsAttributeType.AttributeAttributeList != from->AttributeType) { throw new ArgumentException(); } IPartitionClusterData listAttributeData = null; // Address of first ListeEntry item for this attribute. ListEntry *listBase = null; uint listLength; try { if (from->IsResident) { NtfsResidentAttribute *listAttribute = (NtfsResidentAttribute *)from; listBase = (ListEntry *)((byte *)from + listAttribute->ValueOffset); listLength = listAttribute->ValueLength; } else { NtfsNonResidentAttribute *listAttribute = (NtfsNonResidentAttribute *)from; listAttributeData = listAttribute->GetData(); if (null == listAttributeData) { throw new ApplicationException(); } listBase = (ListEntry *)listAttributeData.Data; ulong candidateLength = listAttribute->DataSize; if (uint.MaxValue < candidateLength) { throw new ApplicationException(); } listLength = (uint)candidateLength; } if (null == listBase) { throw new ApplicationException(); } NtfsAttributeType currentAttributeType = NtfsAttributeType.Any; ushort currentAttributeNumber = ushort.MaxValue; ListEntry * scannedEntry; for (uint offset = 0; offset < listLength; offset += scannedEntry->EntryLength) { scannedEntry = (ListEntry *)((byte *)listBase + offset); if ((currentAttributeNumber == scannedEntry->AttributeNumber) && (currentAttributeType == scannedEntry->AttributeType)) { // The entry is a continuation of the previous one. Ignore it. It should // have been processed by a previous loop if required. continue; } currentAttributeNumber = scannedEntry->AttributeNumber; currentAttributeType = scannedEntry->AttributeType; if ((NtfsAttributeType.Any != searchedAttributeType) && (scannedEntry->AttributeType != searchedAttributeType)) { // This entry doesn't match the search criteria on attribute type. continue; } EntryListReferencedAttributeHandlerDelegate attributeDataHandler; bool includeData; if (!listEntryHandler(scannedEntry, out attributeDataHandler, out includeData)) { // The callback doesn't wish to continue with other list entries. return; } if (null == attributeDataHandler) { // The callback doesn't wish to retrieve the attribute itself for the // currently scanned entry. continue; } // The last callback invocation decided it needs some data from the attribute // itself before deciding what to do. if (!HandleEntryReferencedAttribute(scannedEntry, listLength - offset, attributeDataHandler, includeData)) { return; } } } finally { if (null != listAttributeData) { listAttributeData.Dispose(); } } }