PrivateHeader.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. /* Copyright (C) 2014-2016 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.Text;
  10. using DiskAccessLibrary;
  11. using Utilities;
  12. namespace DiskAccessLibrary.LogicalDiskManager
  13. {
  14. [Flags]
  15. // as reported by DMDiag
  16. public enum PrivateHeaderFlags : uint
  17. {
  18. Shared = 0x01, // private when the flag is absent
  19. NoAutoImport = 0x02, // autoimport when the flag is absent
  20. }
  21. public class PrivateHeader
  22. {
  23. public const int Length = 512;
  24. public const int PrivateHeaderSectorIndex = 6;
  25. public const string PrivateHeaderSignature = "PRIVHEAD";
  26. public string Signature = PrivateHeaderSignature;
  27. //private uint Checksum;
  28. // Versions: 2.11 for Windows XP \ Server 2003
  29. // 2.12 for Windows 7 \ Server 2008
  30. // 2.12 for Veritas Storage Foundation 4.0 (regardless of whether Windows Disk Management compatible group is checked or not)
  31. public ushort MajorVersion;
  32. public ushort MinorVersion;
  33. public DateTime LastUpdateDT; // Set when UpdateSequenceNumber is being updated
  34. public ulong UpdateSequenceNumber; // as reported by DMDiag, kept in sync with UpdateSequenceNumber of the most recent TOCBlock
  35. public ulong PrimaryPrivateHeaderLBA; // Within the private region
  36. public ulong SecondaryPrivateHeaderLBA; // Within the private region
  37. public string DiskGuidString;
  38. public string HostGuidString;
  39. public string DiskGroupGuidString;
  40. public string DiskGroupName; // Veritas will limit DiskGroupName to 18 characters, Windows will use the NetBIOS name (limited to 15 characters) and will append 'Dg0'
  41. public uint BytesPerSector; // a.k.a. iosize
  42. public PrivateHeaderFlags Flags;
  43. public ushort PublicRegionSliceNumber;
  44. public ushort PrivateRegionSliceNumber;
  45. /// <summary>
  46. /// MBR: Windows XP / Server 2003 will ignore this value and will use the first sector of the second disk track (usually sector number 63, as there are usually 63 sectors per track [0-62])
  47. /// </summary>
  48. public ulong PublicRegionStartLBA;
  49. public ulong PublicRegionSizeLBA;
  50. public ulong PrivateRegionStartLBA;
  51. public ulong PrivateRegionSizeLBA;
  52. /// <summary>
  53. /// PrimaryTocLBA / SecondaryTocLBA: on write operation, the updated TOC will be written to PreviousPrimaryTocLBA / PreviousSecondaryTocLBA,
  54. /// and then PrimaryTocLBA / SecondaryTocLBA will be updated to point to it.
  55. /// </summary>
  56. public ulong PrimaryTocLBA; // Note: We have the previous TOC (update-sequence-number wise) adjacent, see PreviousPrimaryTocLBA
  57. public ulong SecondaryTocLBA; // Note: We have the previous TOC adjacent, see PreviousSecondaryTocLBA
  58. public uint NumberOfConfigs; // in private region?
  59. public uint NumberOfLogs; // in private region?
  60. public ulong ConfigSizeLBA; // all config regions in private region in total?
  61. public ulong LogSizeLBA; // all log regions in private region in total?
  62. public uint DiskSignature;
  63. public Guid DiskSetGuid;
  64. public Guid DiskSetGuidRepeat;
  65. private bool m_isChecksumValid;
  66. public PrivateHeader(byte[] buffer)
  67. {
  68. if (buffer.Length > Length)
  69. {
  70. // Checksum only applies to the first 512 bytes (even when the sector size > 512 bytes)
  71. buffer = ByteReader.ReadBytes(buffer, 0, 512);
  72. }
  73. Signature = ByteReader.ReadAnsiString(buffer, 0x00, 8);
  74. uint checksum = BigEndianConverter.ToUInt32(buffer, 0x08);
  75. MajorVersion = BigEndianConverter.ToUInt16(buffer, 0x0C);
  76. MinorVersion = BigEndianConverter.ToUInt16(buffer, 0x0E);
  77. LastUpdateDT = DateTime.FromFileTimeUtc(BigEndianConverter.ToInt64(buffer, 0x10));
  78. UpdateSequenceNumber = BigEndianConverter.ToUInt64(buffer, 0x18);
  79. PrimaryPrivateHeaderLBA = BigEndianConverter.ToUInt64(buffer, 0x20);
  80. SecondaryPrivateHeaderLBA = BigEndianConverter.ToUInt64(buffer, 0x28);
  81. DiskGuidString = ByteReader.ReadAnsiString(buffer, 0x30, 0x40).Trim('\0');
  82. HostGuidString = ByteReader.ReadAnsiString(buffer, 0x70, 0x40).Trim('\0');
  83. DiskGroupGuidString = ByteReader.ReadAnsiString(buffer, 0xB0, 0x40).Trim('\0');
  84. DiskGroupName = ByteReader.ReadAnsiString(buffer, 0xF0, 31).Trim('\0');
  85. BytesPerSector = BigEndianConverter.ToUInt32(buffer, 0x10F);
  86. Flags = (PrivateHeaderFlags)BigEndianConverter.ToUInt32(buffer, 0x113);
  87. PublicRegionSliceNumber = BigEndianConverter.ToUInt16(buffer, 0x117);
  88. PrivateRegionSliceNumber = BigEndianConverter.ToUInt16(buffer, 0x119);
  89. PublicRegionStartLBA = BigEndianConverter.ToUInt64(buffer, 0x11B);
  90. PublicRegionSizeLBA = BigEndianConverter.ToUInt64(buffer, 0x123);
  91. PrivateRegionStartLBA = BigEndianConverter.ToUInt64(buffer, 0x12B);
  92. PrivateRegionSizeLBA = BigEndianConverter.ToUInt64(buffer, 0x133);
  93. PrimaryTocLBA = BigEndianConverter.ToUInt64(buffer, 0x13B);
  94. SecondaryTocLBA = BigEndianConverter.ToUInt64(buffer, 0x143);
  95. NumberOfConfigs = BigEndianConverter.ToUInt32(buffer, 0x14B);
  96. NumberOfLogs = BigEndianConverter.ToUInt32(buffer, 0x14F);
  97. ConfigSizeLBA = BigEndianConverter.ToUInt64(buffer, 0x153);
  98. LogSizeLBA = BigEndianConverter.ToUInt64(buffer, 0x15B);
  99. DiskSignature = BigEndianConverter.ToUInt32(buffer, 0x163);
  100. DiskSetGuid = BigEndianConverter.ToGuid(buffer, 0x167);
  101. DiskSetGuidRepeat = BigEndianConverter.ToGuid(buffer, 0x177);
  102. BigEndianWriter.WriteUInt32(buffer, 0x08, (uint)0); // we exclude the checksum field from checksum calculations
  103. m_isChecksumValid = (checksum == CalculateChecksum(buffer));
  104. }
  105. /// <summary>
  106. /// Private header may need to be padded with zeros in order to fill an entire sector
  107. /// </summary>
  108. public byte[] GetBytes()
  109. {
  110. byte[] buffer = new byte[Length];
  111. ByteWriter.WriteAnsiString(buffer, 0x00, Signature, 8);
  112. // we'll write the checksum later
  113. BigEndianWriter.WriteUInt16(buffer, 0x0C, MajorVersion);
  114. BigEndianWriter.WriteUInt16(buffer, 0x0E, MinorVersion);
  115. BigEndianWriter.WriteInt64(buffer, 0x10, LastUpdateDT.ToFileTimeUtc());
  116. BigEndianWriter.WriteUInt64(buffer, 0x18, UpdateSequenceNumber);
  117. BigEndianWriter.WriteUInt64(buffer, 0x20, PrimaryPrivateHeaderLBA);
  118. BigEndianWriter.WriteUInt64(buffer, 0x28, SecondaryPrivateHeaderLBA);
  119. ByteWriter.WriteAnsiString(buffer, 0x30, DiskGuidString, 0x40);
  120. ByteWriter.WriteAnsiString(buffer, 0x70, HostGuidString, 0x40);
  121. ByteWriter.WriteAnsiString(buffer, 0xB0, DiskGroupGuidString, 0x40);
  122. ByteWriter.WriteAnsiString(buffer, 0xF0, DiskGroupName, 31);
  123. BigEndianWriter.WriteUInt32(buffer, 0x10F, BytesPerSector);
  124. BigEndianWriter.WriteUInt32(buffer, 0x113, (uint)Flags);
  125. BigEndianWriter.WriteUInt16(buffer, 0x117, PublicRegionSliceNumber);
  126. BigEndianWriter.WriteUInt16(buffer, 0x119, PrivateRegionSliceNumber);
  127. BigEndianWriter.WriteUInt64(buffer, 0x11B, PublicRegionStartLBA);
  128. BigEndianWriter.WriteUInt64(buffer, 0x123, PublicRegionSizeLBA);
  129. BigEndianWriter.WriteUInt64(buffer, 0x12B, PrivateRegionStartLBA);
  130. BigEndianWriter.WriteUInt64(buffer, 0x133, PrivateRegionSizeLBA);
  131. BigEndianWriter.WriteUInt64(buffer, 0x13B, PrimaryTocLBA);
  132. BigEndianWriter.WriteUInt64(buffer, 0x143, SecondaryTocLBA);
  133. BigEndianWriter.WriteUInt32(buffer, 0x14B, NumberOfConfigs);
  134. BigEndianWriter.WriteUInt32(buffer, 0x14F, NumberOfLogs);
  135. BigEndianWriter.WriteUInt64(buffer, 0x153, ConfigSizeLBA);
  136. BigEndianWriter.WriteUInt64(buffer, 0x15B, LogSizeLBA);
  137. BigEndianWriter.WriteUInt32(buffer, 0x163, DiskSignature);
  138. BigEndianWriter.WriteGuidBytes(buffer, 0x167, DiskSetGuid);
  139. BigEndianWriter.WriteGuidBytes(buffer, 0x177, DiskSetGuidRepeat);
  140. uint checksum = CalculateChecksum(buffer);
  141. BigEndianWriter.WriteUInt32(buffer, 0x08, checksum);
  142. return buffer;
  143. }
  144. public static PrivateHeader ReadFromDisk(Disk disk)
  145. {
  146. MasterBootRecord mbr = MasterBootRecord.ReadFromDisk(disk);
  147. if (mbr.IsGPTBasedDisk)
  148. {
  149. return ReadFromGPTBasedDisk(disk);
  150. }
  151. else
  152. {
  153. return ReadFromMBRBasedDisk(disk);
  154. }
  155. }
  156. public static PrivateHeader ReadFromMBRBasedDisk(Disk disk)
  157. {
  158. // check for private header at the last sector of the disk
  159. PrivateHeader privateHeader = ReadFromDiskEnd(disk, true);
  160. if (privateHeader != null)
  161. {
  162. if (privateHeader.IsChecksumValid)
  163. {
  164. return privateHeader;
  165. }
  166. else
  167. {
  168. // primary has invalid checksum, try secondary private header
  169. long sectorIndex = (long)(privateHeader.PrivateRegionStartLBA + privateHeader.SecondaryPrivateHeaderLBA);
  170. return ReadFromDisk(disk, sectorIndex, false);
  171. }
  172. }
  173. else
  174. {
  175. // maybe the disk was cloned to a bigger disk, check sector 6
  176. return ReadFromDiskStart(disk);
  177. }
  178. }
  179. public static PrivateHeader ReadFromGPTBasedDisk(Disk disk)
  180. {
  181. List<GuidPartitionEntry> entries = GuidPartitionTable.ReadEntriesFromDisk(disk);
  182. int index = GuidPartitionEntryCollection.GetIndexOfPartitionTypeGuid(entries, GPTPartition.PrivateRegionPartitionTypeGuid);
  183. // the private header will be located at the last sector of the private region
  184. PrivateHeader privateHeader = PrivateHeader.ReadFromDisk(disk, (long)entries[index].LastLBA, true);
  185. if (privateHeader != null)
  186. {
  187. if (privateHeader.IsChecksumValid)
  188. {
  189. return privateHeader;
  190. }
  191. else
  192. {
  193. // primary has invalid checksum, try secondary private header
  194. long sectorIndex = (long)(privateHeader.PrivateRegionStartLBA + privateHeader.SecondaryPrivateHeaderLBA);
  195. return ReadFromDisk(disk, sectorIndex, false);
  196. }
  197. }
  198. return null;
  199. }
  200. public static PrivateHeader ReadFromDiskEnd(Disk disk, bool returnPrivateHeaderWithInvalidChecksum)
  201. {
  202. long sectorCount = disk.Size / disk.BytesPerSector;
  203. long sectorIndex = sectorCount - 1;
  204. return ReadFromDisk(disk, sectorIndex, returnPrivateHeaderWithInvalidChecksum);
  205. }
  206. public static PrivateHeader ReadFromDiskStart(Disk disk)
  207. {
  208. return ReadFromDisk(disk, PrivateHeaderSectorIndex, false);
  209. }
  210. public static PrivateHeader ReadFromDisk(Disk disk, long sectorIndex, bool returnPrivateHeaderWithInvalidChecksum)
  211. {
  212. byte[] sector = disk.ReadSector(sectorIndex);
  213. string signature = ByteReader.ReadAnsiString(sector, 0x00, 8);
  214. if (signature == PrivateHeaderSignature)
  215. {
  216. PrivateHeader privateHeader = new PrivateHeader(sector);
  217. if (privateHeader.IsChecksumValid || returnPrivateHeaderWithInvalidChecksum)
  218. {
  219. return privateHeader;
  220. }
  221. }
  222. return null;
  223. }
  224. public static void WriteToDisk(Disk disk, PrivateHeader privateHeader)
  225. {
  226. byte[] bytes = privateHeader.GetBytes();
  227. if (disk.BytesPerSector > Length)
  228. {
  229. bytes = ByteUtils.Concatenate(bytes, new byte[disk.BytesPerSector - PrivateHeader.Length]);
  230. }
  231. disk.WriteSectors((long)(privateHeader.PrivateRegionStartLBA + privateHeader.PrimaryPrivateHeaderLBA), bytes);
  232. disk.WriteSectors((long)(privateHeader.PrivateRegionStartLBA + privateHeader.SecondaryPrivateHeaderLBA), bytes);
  233. // update sector 6 if a Private Header is already present there
  234. byte[] sector = disk.ReadSector(PrivateHeaderSectorIndex);
  235. string signature = ByteReader.ReadAnsiString(sector, 0x00, 8);
  236. if (signature == PrivateHeaderSignature)
  237. {
  238. disk.WriteSectors(PrivateHeaderSectorIndex, bytes);
  239. }
  240. }
  241. public Guid DiskGuid
  242. {
  243. get
  244. {
  245. return new Guid(this.DiskGuidString);
  246. }
  247. set
  248. {
  249. this.DiskGuidString = value.ToString();
  250. }
  251. }
  252. public Guid DiskGroupGuid
  253. {
  254. get
  255. {
  256. return new Guid(this.DiskGroupGuidString);
  257. }
  258. set
  259. {
  260. this.DiskGroupGuidString = value.ToString();
  261. }
  262. }
  263. public bool IsChecksumValid
  264. {
  265. get
  266. {
  267. return m_isChecksumValid;
  268. }
  269. }
  270. public static uint CalculateChecksum(byte[] bytes)
  271. {
  272. uint result = 0;
  273. for (int index = 0; index < bytes.Length; index++)
  274. {
  275. result += bytes[index];
  276. }
  277. return result;
  278. }
  279. }
  280. }