FileRecordSegment.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. /* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  2. *
  3. * You can redistribute this program and/or modify it under the terms of
  4. * the GNU Lesser Public License as published by the Free Software Foundation,
  5. * either version 3 of the License, or (at your option) any later version.
  6. */
  7. using System;
  8. using System.Collections.Generic;
  9. using System.IO;
  10. using System.Text;
  11. using Utilities;
  12. namespace DiskAccessLibrary.FileSystems.NTFS
  13. {
  14. public class FileRecordSegment
  15. {
  16. public const string ValidSignature = "FILE";
  17. public const int EndMarkerLength = 4;
  18. public const int NTFS30UpdateSequenceArrayOffset = 0x2A; // NTFS 3.0 and earlier (up to Windows 2000)
  19. public const int NTFS31UpdateSequenceArrayOffset = 0x30; // NTFS 3.1 and later (XP and later)
  20. [Flags]
  21. public enum FileRecordFlags : ushort
  22. {
  23. None = 0x0000,
  24. InUse = 0x0001,
  25. IsDirectory = 0x0002,
  26. IsMetaFile = 0x0004,
  27. HasViewIndex = 0x0008,
  28. }
  29. /* Start of header */
  30. /* Start of MULTI_SECTOR_HEADER */
  31. public string Signature = ValidSignature;
  32. // ushort UpdateSequenceArrayOffset;
  33. // ushort UpdateSequenceArraySize; // number of (2 byte) words
  34. /* End of MULTI_SECTOR_HEADER */
  35. public ulong LogFileSequenceNumber;
  36. public ushort SequenceNumber; // This value is incremented each time that a file record segment is freed
  37. public ushort HardLinkCount;
  38. // ushort FirstAttributeOffset;
  39. private FileRecordFlags m_flags;
  40. // uint SegmentRealSize;
  41. // uint SegmentAllocatedSize;
  42. private ulong BaseFileRecordSegmentNumber; // If this is the base file record, the value is 0
  43. public ushort NextAttributeId; // Starting from 0
  44. // 2 zeros - padding
  45. public uint MftSegmentNumberXP; // Self-reference (on XP+)
  46. public ushort UpdateSequenceNumber; // a.k.a. USN
  47. /* End of header */
  48. 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)
  49. private List<AttributeRecord> m_immediateAttributes = new List<AttributeRecord>(); // Attribute records that are stored in the base file record
  50. public FileRecordSegment(byte[] buffer, int bytesPerSector, long segmentNumber) : this(buffer, 0, bytesPerSector, segmentNumber)
  51. {
  52. }
  53. public FileRecordSegment(byte[] buffer, int offset, int bytesPerSector, long segmentNumber)
  54. {
  55. Signature = ByteReader.ReadAnsiString(buffer, offset + 0x00, 4);
  56. ushort updateSequenceArrayOffset = LittleEndianConverter.ToUInt16(buffer, offset + 0x04);
  57. ushort updateSequenceArraySize = LittleEndianConverter.ToUInt16(buffer, offset + 0x06);
  58. LogFileSequenceNumber = LittleEndianConverter.ToUInt64(buffer, offset + 0x08);
  59. SequenceNumber = LittleEndianConverter.ToUInt16(buffer, offset + 0x10);
  60. HardLinkCount = LittleEndianConverter.ToUInt16(buffer, offset + 0x12);
  61. ushort firstAttributeOffset = LittleEndianConverter.ToUInt16(buffer, offset + 0x14);
  62. m_flags = (FileRecordFlags)LittleEndianConverter.ToUInt16(buffer, offset + 0x16);
  63. uint segmentRealSize = LittleEndianConverter.ToUInt32(buffer, offset + 0x18);
  64. uint segmentAllocatedSize = LittleEndianConverter.ToUInt32(buffer, offset + 0x1C);
  65. BaseFileRecordSegmentNumber = LittleEndianConverter.ToUInt64(buffer, offset + 0x20);
  66. NextAttributeId = LittleEndianConverter.ToUInt16(buffer, offset + 0x28);
  67. // 2 zeros - padding
  68. MftSegmentNumberXP = LittleEndianConverter.ToUInt32(buffer, offset + 0x2C);
  69. // There is an UpdateSequenceNumber for the FileRecordSegment,
  70. // and an entry in the UpdateSequenceArray for each sector of the record
  71. // The last two bytes of each sector contains this entry for integrity-check purposes
  72. int position = offset + updateSequenceArrayOffset;
  73. UpdateSequenceNumber = LittleEndianConverter.ToUInt16(buffer, position);
  74. position += 2;
  75. // This stores the data that was supposed to be placed at the end of each sector, and was replaced with an UpdateSequenceNumber
  76. List<byte[]> updateSequenceReplacementData = new List<byte[]>();
  77. for (int index = 0; index < updateSequenceArraySize - 1; index++)
  78. {
  79. byte[] endOfSectorBytes = new byte[2];
  80. endOfSectorBytes[0] = buffer[position + 0];
  81. endOfSectorBytes[1] = buffer[position + 1];
  82. updateSequenceReplacementData.Add(endOfSectorBytes);
  83. position += 2;
  84. }
  85. MultiSectorHelper.DecodeSegmentBuffer(buffer, offset, UpdateSequenceNumber, updateSequenceReplacementData);
  86. // read attributes
  87. position = offset + firstAttributeOffset;
  88. while (!IsEndMarker(buffer, position))
  89. {
  90. AttributeRecord attribute = AttributeRecord.FromBytes(buffer, position);
  91. m_immediateAttributes.Add(attribute);
  92. position += (int)attribute.StoredRecordLength;
  93. if (position > buffer.Length)
  94. {
  95. throw new InvalidDataException("Improper attribute length");
  96. }
  97. }
  98. m_mftSegmentNumber = segmentNumber;
  99. }
  100. /// <param name="segmentLength">This refers to the maximum length of FileRecord as defined in the Volume's BootRecord</param>
  101. public byte[] GetBytes(int segmentLength, int bytesPerCluster, ushort minorNTFSVersion)
  102. {
  103. int strideCount = segmentLength / MultiSectorHelper.BytesPerStride;
  104. ushort updateSequenceArraySize = (ushort)(1 + strideCount);
  105. ushort updateSequenceArrayOffset;
  106. if (minorNTFSVersion == 0)
  107. {
  108. updateSequenceArrayOffset = NTFS30UpdateSequenceArrayOffset;
  109. }
  110. else
  111. {
  112. updateSequenceArrayOffset = NTFS31UpdateSequenceArrayOffset;
  113. }
  114. ushort firstAttributeOffset = GetFirstAttributeOffset(segmentLength, minorNTFSVersion);
  115. byte[] buffer = new byte[segmentLength];
  116. ByteWriter.WriteAnsiString(buffer, 0, Signature, 4);
  117. LittleEndianWriter.WriteUInt16(buffer, 0x04, updateSequenceArrayOffset);
  118. LittleEndianWriter.WriteUInt16(buffer, 0x06, updateSequenceArraySize);
  119. LittleEndianWriter.WriteUInt64(buffer, 0x08, LogFileSequenceNumber);
  120. LittleEndianWriter.WriteUInt16(buffer, 0x10, SequenceNumber);
  121. LittleEndianWriter.WriteUInt16(buffer, 0x12, HardLinkCount);
  122. LittleEndianWriter.WriteUInt16(buffer, 0x14, firstAttributeOffset);
  123. LittleEndianWriter.WriteUInt16(buffer, 0x16, (ushort)m_flags);
  124. LittleEndianWriter.WriteInt32(buffer, 0x1C, segmentLength);
  125. LittleEndianWriter.WriteUInt64(buffer, 0x20, BaseFileRecordSegmentNumber);
  126. LittleEndianWriter.WriteUInt16(buffer, 0x28, NextAttributeId);
  127. if (minorNTFSVersion == 1)
  128. {
  129. LittleEndianWriter.WriteUInt32(buffer, 0x2C, MftSegmentNumberXP);
  130. }
  131. // write attributes
  132. int position = firstAttributeOffset;
  133. foreach (AttributeRecord attribute in m_immediateAttributes)
  134. {
  135. byte[] attributeBytes = attribute.GetBytes(bytesPerCluster);
  136. ByteWriter.WriteBytes(buffer, position, attributeBytes);
  137. position += attributeBytes.Length;
  138. }
  139. byte[] marker = GetEndMarker();
  140. ByteWriter.WriteBytes(buffer, position, marker);
  141. position += marker.Length;
  142. position += 4; // record (length) is aligned to 8-byte boundary
  143. uint segmentRealSize = (uint)position;
  144. LittleEndianWriter.WriteUInt32(buffer, 0x18, segmentRealSize);
  145. // write UpdateSequenceNumber and UpdateSequenceReplacementData
  146. List<byte[]> updateSequenceReplacementData = MultiSectorHelper.EncodeSegmentBuffer(buffer, 0, segmentLength, UpdateSequenceNumber);
  147. position = updateSequenceArrayOffset;
  148. LittleEndianWriter.WriteUInt16(buffer, position, UpdateSequenceNumber);
  149. position += 2;
  150. foreach (byte[] endOfSectorBytes in updateSequenceReplacementData)
  151. {
  152. ByteWriter.WriteBytes(buffer, position, endOfSectorBytes);
  153. position += 2;
  154. }
  155. return buffer;
  156. }
  157. public AttributeRecord GetImmediateAttributeRecord(AttributeType type)
  158. {
  159. foreach (AttributeRecord attribute in m_immediateAttributes)
  160. {
  161. if (attribute.AttributeType == type)
  162. {
  163. return attribute;
  164. }
  165. }
  166. return null;
  167. }
  168. /// <summary>
  169. /// Indicates that the file / directory wasn't deleted
  170. /// </summary>
  171. public bool IsInUse
  172. {
  173. get
  174. {
  175. return (m_flags & FileRecordFlags.InUse) != 0;
  176. }
  177. set
  178. {
  179. m_flags &= ~FileRecordFlags.InUse;
  180. }
  181. }
  182. public bool IsDirectory
  183. {
  184. get
  185. {
  186. return (m_flags & FileRecordFlags.IsDirectory) != 0;
  187. //return (GetAttributeRecord(AttributeType.IndexRoot) != null);
  188. }
  189. }
  190. public List<AttributeRecord> ImmediateAttributes
  191. {
  192. get
  193. {
  194. return m_immediateAttributes;
  195. }
  196. }
  197. public bool IsBaseFileRecord
  198. {
  199. get
  200. {
  201. // If this is the base file record, the value is 0
  202. // http://msdn.microsoft.com/en-us/library/bb470124%28v=vs.85%29.aspx
  203. return (BaseFileRecordSegmentNumber == 0);
  204. }
  205. }
  206. public bool HasAttributeList
  207. {
  208. get
  209. {
  210. AttributeRecord attributeList = GetImmediateAttributeRecord(AttributeType.AttributeList);
  211. return (attributeList != null);
  212. }
  213. }
  214. /*
  215. public uint RecordRealSize
  216. {
  217. get
  218. {
  219. return m_recordRealSize;
  220. }
  221. }*/
  222. public override bool Equals(object obj)
  223. {
  224. if (obj is FileRecordSegment)
  225. {
  226. return ((FileRecordSegment)obj).MftSegmentNumber == MftSegmentNumber;
  227. }
  228. return base.Equals(obj);
  229. }
  230. public override int GetHashCode()
  231. {
  232. return MftSegmentNumber.GetHashCode();
  233. }
  234. public long MftSegmentNumber
  235. {
  236. get
  237. {
  238. return m_mftSegmentNumber;
  239. }
  240. }
  241. public static bool IsEndMarker(byte[] buffer, int offset)
  242. {
  243. uint type = LittleEndianConverter.ToUInt32(buffer, offset + 0x00);
  244. return (type == 0xFFFFFFFF);
  245. }
  246. /// <summary>
  247. /// Get file record end marker
  248. /// </summary>
  249. public static byte[] GetEndMarker()
  250. {
  251. byte[] buffer = new byte[4];
  252. Array.Copy(LittleEndianConverter.GetBytes(0xFFFFFFFF), buffer, 4);
  253. return buffer;
  254. }
  255. public static ushort GetFirstAttributeOffset(int segmentLength, ushort minorNTFSVersion)
  256. {
  257. int strideCount = segmentLength / MultiSectorHelper.BytesPerStride;
  258. ushort updateSequenceArraySize = (ushort)(1 + strideCount);
  259. ushort updateSequenceArrayOffset;
  260. if (minorNTFSVersion == 0)
  261. {
  262. updateSequenceArrayOffset = NTFS30UpdateSequenceArrayOffset;
  263. }
  264. else
  265. {
  266. updateSequenceArrayOffset = NTFS31UpdateSequenceArrayOffset;
  267. }
  268. // aligned to 8 byte boundary
  269. // Note: I had an issue with 4 byte boundary under Windows 7 using disk with 2048 bytes per sector.
  270. // Windows used an 8 byte boundary.
  271. ushort firstAttributeOffset = (ushort)(Math.Ceiling((double)(updateSequenceArrayOffset + updateSequenceArraySize * 2) / 8) * 8);
  272. return firstAttributeOffset;
  273. }
  274. public static bool ContainsFileRecordSegment(byte[] recordBytes)
  275. {
  276. return ContainsFileRecordSegment(recordBytes, 0);
  277. }
  278. public static bool ContainsFileRecordSegment(byte[] recordBytes, int offset)
  279. {
  280. string fileSignature = ByteReader.ReadAnsiString(recordBytes, offset, 4);
  281. return (fileSignature == ValidSignature);
  282. }
  283. public static bool ContainsMftSegmentNumber(List<FileRecordSegment> list, long mftSegmentNumber)
  284. {
  285. foreach (FileRecordSegment segment in list)
  286. {
  287. if (segment.MftSegmentNumber == mftSegmentNumber)
  288. {
  289. return true;
  290. }
  291. }
  292. return false;
  293. }
  294. }
  295. }