123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- /* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
- *
- * You can redistribute this program and/or modify it under the terms of
- * the GNU Lesser Public License as published by the Free Software Foundation,
- * either version 3 of the License, or (at your option) any later version.
- */
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Text;
- using Utilities;
- namespace DiskAccessLibrary.FileSystems.NTFS
- {
- public class FileRecordSegment
- {
- public const string ValidSignature = "FILE";
- public const int EndMarkerLength = 4;
- public const int NTFS30UpdateSequenceArrayOffset = 0x2A; // NTFS 3.0 and earlier (up to Windows 2000)
- public const int NTFS31UpdateSequenceArrayOffset = 0x30; // NTFS 3.1 and later (XP and later)
- [Flags]
- public enum FileRecordFlags : ushort
- {
- None = 0x0000,
- InUse = 0x0001,
- IsDirectory = 0x0002,
- IsMetaFile = 0x0004,
- HasViewIndex = 0x0008,
- }
- /* Start of header */
- /* Start of MULTI_SECTOR_HEADER */
- public string Signature = ValidSignature;
- // ushort UpdateSequenceArrayOffset;
- // ushort UpdateSequenceArraySize; // number of (2 byte) words
- /* End of MULTI_SECTOR_HEADER */
- public ulong LogFileSequenceNumber;
- public ushort SequenceNumber; // This value is incremented each time that a file record segment is freed
- public ushort HardLinkCount;
- // ushort FirstAttributeOffset;
- private FileRecordFlags m_flags;
- // uint SegmentRealSize;
- // uint SegmentAllocatedSize;
- private ulong BaseFileRecordSegmentNumber; // If this is the base file record, the value is 0
- public ushort NextAttributeId; // Starting from 0
- // 2 zeros - padding
- public uint MftSegmentNumberXP; // Self-reference (on XP+)
- public ushort UpdateSequenceNumber; // a.k.a. USN
- /* End of header */
- private long m_mftSegmentNumber; // We use our own segment number to support NTFS 3.0 (note that MftSegmentNumberXP is UInt32, which is another reason to avoid it)
- private List<AttributeRecord> m_immediateAttributes = new List<AttributeRecord>(); // Attribute records that are stored in the base file record
- public FileRecordSegment(byte[] buffer, int bytesPerSector, long segmentNumber) : this(buffer, 0, bytesPerSector, segmentNumber)
- {
- }
- public FileRecordSegment(byte[] buffer, int offset, int bytesPerSector, long segmentNumber)
- {
- Signature = ByteReader.ReadAnsiString(buffer, offset + 0x00, 4);
- ushort updateSequenceArrayOffset = LittleEndianConverter.ToUInt16(buffer, offset + 0x04);
- ushort updateSequenceArraySize = LittleEndianConverter.ToUInt16(buffer, offset + 0x06);
- LogFileSequenceNumber = LittleEndianConverter.ToUInt64(buffer, offset + 0x08);
- SequenceNumber = LittleEndianConverter.ToUInt16(buffer, offset + 0x10);
- HardLinkCount = LittleEndianConverter.ToUInt16(buffer, offset + 0x12);
- ushort firstAttributeOffset = LittleEndianConverter.ToUInt16(buffer, offset + 0x14);
- m_flags = (FileRecordFlags)LittleEndianConverter.ToUInt16(buffer, offset + 0x16);
- uint segmentRealSize = LittleEndianConverter.ToUInt32(buffer, offset + 0x18);
- uint segmentAllocatedSize = LittleEndianConverter.ToUInt32(buffer, offset + 0x1C);
- BaseFileRecordSegmentNumber = LittleEndianConverter.ToUInt64(buffer, offset + 0x20);
- NextAttributeId = LittleEndianConverter.ToUInt16(buffer, offset + 0x28);
- // 2 zeros - padding
- MftSegmentNumberXP = LittleEndianConverter.ToUInt32(buffer, offset + 0x2C);
- // There is an UpdateSequenceNumber for the FileRecordSegment,
- // and an entry in the UpdateSequenceArray for each sector of the record
- // The last two bytes of each sector contains this entry for integrity-check purposes
- int position = offset + updateSequenceArrayOffset;
- UpdateSequenceNumber = LittleEndianConverter.ToUInt16(buffer, position);
- position += 2;
- // This stores the data that was supposed to be placed at the end of each sector, and was replaced with an UpdateSequenceNumber
- List<byte[]> updateSequenceReplacementData = new List<byte[]>();
- for (int index = 0; index < updateSequenceArraySize - 1; index++)
- {
- byte[] endOfSectorBytes = new byte[2];
- endOfSectorBytes[0] = buffer[position + 0];
- endOfSectorBytes[1] = buffer[position + 1];
- updateSequenceReplacementData.Add(endOfSectorBytes);
- position += 2;
- }
- MultiSectorHelper.DecodeSegmentBuffer(buffer, offset, UpdateSequenceNumber, updateSequenceReplacementData);
- // read attributes
- position = offset + firstAttributeOffset;
- while (!IsEndMarker(buffer, position))
- {
- AttributeRecord attribute = AttributeRecord.FromBytes(buffer, position);
-
- m_immediateAttributes.Add(attribute);
- position += (int)attribute.StoredRecordLength;
- if (position > buffer.Length)
- {
- throw new InvalidDataException("Improper attribute length");
- }
- }
- m_mftSegmentNumber = segmentNumber;
- }
- /// <param name="segmentLength">This refers to the maximum length of FileRecord as defined in the Volume's BootRecord</param>
- public byte[] GetBytes(int segmentLength, int bytesPerCluster, ushort minorNTFSVersion)
- {
- int strideCount = segmentLength / MultiSectorHelper.BytesPerStride;
- ushort updateSequenceArraySize = (ushort)(1 + strideCount);
- ushort updateSequenceArrayOffset;
- if (minorNTFSVersion == 0)
- {
- updateSequenceArrayOffset = NTFS30UpdateSequenceArrayOffset;
- }
- else
- {
- updateSequenceArrayOffset = NTFS31UpdateSequenceArrayOffset;
- }
- ushort firstAttributeOffset = GetFirstAttributeOffset(segmentLength, minorNTFSVersion);
- byte[] buffer = new byte[segmentLength];
- ByteWriter.WriteAnsiString(buffer, 0, Signature, 4);
- LittleEndianWriter.WriteUInt16(buffer, 0x04, updateSequenceArrayOffset);
- LittleEndianWriter.WriteUInt16(buffer, 0x06, updateSequenceArraySize);
- LittleEndianWriter.WriteUInt64(buffer, 0x08, LogFileSequenceNumber);
- LittleEndianWriter.WriteUInt16(buffer, 0x10, SequenceNumber);
- LittleEndianWriter.WriteUInt16(buffer, 0x12, HardLinkCount);
- LittleEndianWriter.WriteUInt16(buffer, 0x14, firstAttributeOffset);
- LittleEndianWriter.WriteUInt16(buffer, 0x16, (ushort)m_flags);
- LittleEndianWriter.WriteInt32(buffer, 0x1C, segmentLength);
- LittleEndianWriter.WriteUInt64(buffer, 0x20, BaseFileRecordSegmentNumber);
- LittleEndianWriter.WriteUInt16(buffer, 0x28, NextAttributeId);
- if (minorNTFSVersion == 1)
- {
- LittleEndianWriter.WriteUInt32(buffer, 0x2C, MftSegmentNumberXP);
- }
- // write attributes
- int position = firstAttributeOffset;
- foreach (AttributeRecord attribute in m_immediateAttributes)
- {
- byte[] attributeBytes = attribute.GetBytes(bytesPerCluster);
- ByteWriter.WriteBytes(buffer, position, attributeBytes);
- position += attributeBytes.Length;
- }
- byte[] marker = GetEndMarker();
- ByteWriter.WriteBytes(buffer, position, marker);
- position += marker.Length;
- position += 4; // record (length) is aligned to 8-byte boundary
- uint segmentRealSize = (uint)position;
- LittleEndianWriter.WriteUInt32(buffer, 0x18, segmentRealSize);
- // write UpdateSequenceNumber and UpdateSequenceReplacementData
- List<byte[]> updateSequenceReplacementData = MultiSectorHelper.EncodeSegmentBuffer(buffer, 0, segmentLength, UpdateSequenceNumber);
- position = updateSequenceArrayOffset;
- LittleEndianWriter.WriteUInt16(buffer, position, UpdateSequenceNumber);
- position += 2;
- foreach (byte[] endOfSectorBytes in updateSequenceReplacementData)
- {
- ByteWriter.WriteBytes(buffer, position, endOfSectorBytes);
- position += 2;
- }
- return buffer;
- }
- public AttributeRecord GetImmediateAttributeRecord(AttributeType type)
- {
- foreach (AttributeRecord attribute in m_immediateAttributes)
- {
- if (attribute.AttributeType == type)
- {
- return attribute;
- }
- }
- return null;
- }
- /// <summary>
- /// Indicates that the file / directory wasn't deleted
- /// </summary>
- public bool IsInUse
- {
- get
- {
- return (m_flags & FileRecordFlags.InUse) != 0;
- }
- set
- {
- m_flags &= ~FileRecordFlags.InUse;
- }
- }
- public bool IsDirectory
- {
- get
- {
- return (m_flags & FileRecordFlags.IsDirectory) != 0;
- //return (GetAttributeRecord(AttributeType.IndexRoot) != null);
- }
- }
-
- public List<AttributeRecord> ImmediateAttributes
- {
- get
- {
- return m_immediateAttributes;
- }
- }
- public bool IsBaseFileRecord
- {
- get
- {
- // If this is the base file record, the value is 0
- // http://msdn.microsoft.com/en-us/library/bb470124%28v=vs.85%29.aspx
- return (BaseFileRecordSegmentNumber == 0);
- }
- }
- public bool HasAttributeList
- {
- get
- {
- AttributeRecord attributeList = GetImmediateAttributeRecord(AttributeType.AttributeList);
- return (attributeList != null);
- }
- }
- /*
- public uint RecordRealSize
- {
- get
- {
- return m_recordRealSize;
- }
- }*/
- public override bool Equals(object obj)
- {
- if (obj is FileRecordSegment)
- {
- return ((FileRecordSegment)obj).MftSegmentNumber == MftSegmentNumber;
- }
- return base.Equals(obj);
- }
- public override int GetHashCode()
- {
- return MftSegmentNumber.GetHashCode();
- }
- public long MftSegmentNumber
- {
- get
- {
- return m_mftSegmentNumber;
- }
- }
- public static bool IsEndMarker(byte[] buffer, int offset)
- {
- uint type = LittleEndianConverter.ToUInt32(buffer, offset + 0x00);
- return (type == 0xFFFFFFFF);
- }
- /// <summary>
- /// Get file record end marker
- /// </summary>
- public static byte[] GetEndMarker()
- {
- byte[] buffer = new byte[4];
- Array.Copy(LittleEndianConverter.GetBytes(0xFFFFFFFF), buffer, 4);
- return buffer;
- }
- public static ushort GetFirstAttributeOffset(int segmentLength, ushort minorNTFSVersion)
- {
- int strideCount = segmentLength / MultiSectorHelper.BytesPerStride;
- ushort updateSequenceArraySize = (ushort)(1 + strideCount);
- ushort updateSequenceArrayOffset;
- if (minorNTFSVersion == 0)
- {
- updateSequenceArrayOffset = NTFS30UpdateSequenceArrayOffset;
- }
- else
- {
- updateSequenceArrayOffset = NTFS31UpdateSequenceArrayOffset;
- }
- // aligned to 8 byte boundary
- // Note: I had an issue with 4 byte boundary under Windows 7 using disk with 2048 bytes per sector.
- // Windows used an 8 byte boundary.
- ushort firstAttributeOffset = (ushort)(Math.Ceiling((double)(updateSequenceArrayOffset + updateSequenceArraySize * 2) / 8) * 8);
- return firstAttributeOffset;
- }
- public static bool ContainsFileRecordSegment(byte[] recordBytes)
- {
- return ContainsFileRecordSegment(recordBytes, 0);
- }
- public static bool ContainsFileRecordSegment(byte[] recordBytes, int offset)
- {
- string fileSignature = ByteReader.ReadAnsiString(recordBytes, offset, 4);
- return (fileSignature == ValidSignature);
- }
- public static bool ContainsMftSegmentNumber(List<FileRecordSegment> list, long mftSegmentNumber)
- {
- foreach (FileRecordSegment segment in list)
- {
- if (segment.MftSegmentNumber == mftSegmentNumber)
- {
- return true;
- }
- }
- return false;
- }
- }
- }
|