/* Copyright (C) 2014-2016 Tal Aloni . 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.Text; using DiskAccessLibrary; using Utilities; namespace DiskAccessLibrary.LogicalDiskManager { [Flags] // as reported by DMDiag public enum PrivateHeaderFlags : uint { Shared = 0x01, // private when the flag is absent NoAutoImport = 0x02, // autoimport when the flag is absent } public class PrivateHeader { public const int Length = 512; public const int PrivateHeaderSectorIndex = 6; public const string PrivateHeaderSignature = "PRIVHEAD"; public string Signature = PrivateHeaderSignature; //private uint Checksum; // Versions: 2.11 for Windows XP \ Server 2003 // 2.12 for Windows 7 \ Server 2008 // 2.12 for Veritas Storage Foundation 4.0 (regardless of whether Windows Disk Management compatible group is checked or not) public ushort MajorVersion; public ushort MinorVersion; public DateTime LastUpdateDT; // Set when UpdateSequenceNumber is being updated public ulong UpdateSequenceNumber; // as reported by DMDiag, kept in sync with UpdateSequenceNumber of the most recent TOCBlock public ulong PrimaryPrivateHeaderLBA; // Within the private region public ulong SecondaryPrivateHeaderLBA; // Within the private region public string DiskGuidString; public string HostGuidString; public string DiskGroupGuidString; public string DiskGroupName; // Veritas will limit DiskGroupName to 18 characters, Windows will use the NetBIOS name (limited to 15 characters) and will append 'Dg0' public uint BytesPerSector; // a.k.a. iosize public PrivateHeaderFlags Flags; public ushort PublicRegionSliceNumber; public ushort PrivateRegionSliceNumber; /// /// 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]) /// public ulong PublicRegionStartLBA; public ulong PublicRegionSizeLBA; public ulong PrivateRegionStartLBA; public ulong PrivateRegionSizeLBA; /// /// PrimaryTocLBA / SecondaryTocLBA: on write operation, the updated TOC will be written to PreviousPrimaryTocLBA / PreviousSecondaryTocLBA, /// and then PrimaryTocLBA / SecondaryTocLBA will be updated to point to it. /// public ulong PrimaryTocLBA; // Note: We have the previous TOC (update-sequence-number wise) adjacent, see PreviousPrimaryTocLBA public ulong SecondaryTocLBA; // Note: We have the previous TOC adjacent, see PreviousSecondaryTocLBA public uint NumberOfConfigs; // in private region? public uint NumberOfLogs; // in private region? public ulong ConfigSizeLBA; // all config regions in private region in total? public ulong LogSizeLBA; // all log regions in private region in total? public uint DiskSignature; public Guid DiskSetGuid; public Guid DiskSetGuidRepeat; private bool m_isChecksumValid; public PrivateHeader(byte[] buffer) { if (buffer.Length > Length) { // Checksum only applies to the first 512 bytes (even when the sector size > 512 bytes) buffer = ByteReader.ReadBytes(buffer, 0, 512); } Signature = ByteReader.ReadAnsiString(buffer, 0x00, 8); uint checksum = BigEndianConverter.ToUInt32(buffer, 0x08); MajorVersion = BigEndianConverter.ToUInt16(buffer, 0x0C); MinorVersion = BigEndianConverter.ToUInt16(buffer, 0x0E); LastUpdateDT = DateTime.FromFileTimeUtc(BigEndianConverter.ToInt64(buffer, 0x10)); UpdateSequenceNumber = BigEndianConverter.ToUInt64(buffer, 0x18); PrimaryPrivateHeaderLBA = BigEndianConverter.ToUInt64(buffer, 0x20); SecondaryPrivateHeaderLBA = BigEndianConverter.ToUInt64(buffer, 0x28); DiskGuidString = ByteReader.ReadAnsiString(buffer, 0x30, 0x40).Trim('\0'); HostGuidString = ByteReader.ReadAnsiString(buffer, 0x70, 0x40).Trim('\0'); DiskGroupGuidString = ByteReader.ReadAnsiString(buffer, 0xB0, 0x40).Trim('\0'); DiskGroupName = ByteReader.ReadAnsiString(buffer, 0xF0, 31).Trim('\0'); BytesPerSector = BigEndianConverter.ToUInt32(buffer, 0x10F); Flags = (PrivateHeaderFlags)BigEndianConverter.ToUInt32(buffer, 0x113); PublicRegionSliceNumber = BigEndianConverter.ToUInt16(buffer, 0x117); PrivateRegionSliceNumber = BigEndianConverter.ToUInt16(buffer, 0x119); PublicRegionStartLBA = BigEndianConverter.ToUInt64(buffer, 0x11B); PublicRegionSizeLBA = BigEndianConverter.ToUInt64(buffer, 0x123); PrivateRegionStartLBA = BigEndianConverter.ToUInt64(buffer, 0x12B); PrivateRegionSizeLBA = BigEndianConverter.ToUInt64(buffer, 0x133); PrimaryTocLBA = BigEndianConverter.ToUInt64(buffer, 0x13B); SecondaryTocLBA = BigEndianConverter.ToUInt64(buffer, 0x143); NumberOfConfigs = BigEndianConverter.ToUInt32(buffer, 0x14B); NumberOfLogs = BigEndianConverter.ToUInt32(buffer, 0x14F); ConfigSizeLBA = BigEndianConverter.ToUInt64(buffer, 0x153); LogSizeLBA = BigEndianConverter.ToUInt64(buffer, 0x15B); DiskSignature = BigEndianConverter.ToUInt32(buffer, 0x163); DiskSetGuid = BigEndianConverter.ToGuid(buffer, 0x167); DiskSetGuidRepeat = BigEndianConverter.ToGuid(buffer, 0x177); BigEndianWriter.WriteUInt32(buffer, 0x08, (uint)0); // we exclude the checksum field from checksum calculations m_isChecksumValid = (checksum == CalculateChecksum(buffer)); } /// /// Private header may need to be padded with zeros in order to fill an entire sector /// public byte[] GetBytes() { byte[] buffer = new byte[Length]; ByteWriter.WriteAnsiString(buffer, 0x00, Signature, 8); // we'll write the checksum later BigEndianWriter.WriteUInt16(buffer, 0x0C, MajorVersion); BigEndianWriter.WriteUInt16(buffer, 0x0E, MinorVersion); BigEndianWriter.WriteInt64(buffer, 0x10, LastUpdateDT.ToFileTimeUtc()); BigEndianWriter.WriteUInt64(buffer, 0x18, UpdateSequenceNumber); BigEndianWriter.WriteUInt64(buffer, 0x20, PrimaryPrivateHeaderLBA); BigEndianWriter.WriteUInt64(buffer, 0x28, SecondaryPrivateHeaderLBA); ByteWriter.WriteAnsiString(buffer, 0x30, DiskGuidString, 0x40); ByteWriter.WriteAnsiString(buffer, 0x70, HostGuidString, 0x40); ByteWriter.WriteAnsiString(buffer, 0xB0, DiskGroupGuidString, 0x40); ByteWriter.WriteAnsiString(buffer, 0xF0, DiskGroupName, 31); BigEndianWriter.WriteUInt32(buffer, 0x10F, BytesPerSector); BigEndianWriter.WriteUInt32(buffer, 0x113, (uint)Flags); BigEndianWriter.WriteUInt16(buffer, 0x117, PublicRegionSliceNumber); BigEndianWriter.WriteUInt16(buffer, 0x119, PrivateRegionSliceNumber); BigEndianWriter.WriteUInt64(buffer, 0x11B, PublicRegionStartLBA); BigEndianWriter.WriteUInt64(buffer, 0x123, PublicRegionSizeLBA); BigEndianWriter.WriteUInt64(buffer, 0x12B, PrivateRegionStartLBA); BigEndianWriter.WriteUInt64(buffer, 0x133, PrivateRegionSizeLBA); BigEndianWriter.WriteUInt64(buffer, 0x13B, PrimaryTocLBA); BigEndianWriter.WriteUInt64(buffer, 0x143, SecondaryTocLBA); BigEndianWriter.WriteUInt32(buffer, 0x14B, NumberOfConfigs); BigEndianWriter.WriteUInt32(buffer, 0x14F, NumberOfLogs); BigEndianWriter.WriteUInt64(buffer, 0x153, ConfigSizeLBA); BigEndianWriter.WriteUInt64(buffer, 0x15B, LogSizeLBA); BigEndianWriter.WriteUInt32(buffer, 0x163, DiskSignature); BigEndianWriter.WriteGuidBytes(buffer, 0x167, DiskSetGuid); BigEndianWriter.WriteGuidBytes(buffer, 0x177, DiskSetGuidRepeat); uint checksum = CalculateChecksum(buffer); BigEndianWriter.WriteUInt32(buffer, 0x08, checksum); return buffer; } public static PrivateHeader ReadFromDisk(Disk disk) { MasterBootRecord mbr = MasterBootRecord.ReadFromDisk(disk); if (mbr.IsGPTBasedDisk) { return ReadFromGPTBasedDisk(disk); } else { return ReadFromMBRBasedDisk(disk); } } public static PrivateHeader ReadFromMBRBasedDisk(Disk disk) { // check for private header at the last sector of the disk PrivateHeader privateHeader = ReadFromDiskEnd(disk, true); if (privateHeader != null) { if (privateHeader.IsChecksumValid) { return privateHeader; } else { // primary has invalid checksum, try secondary private header long sectorIndex = (long)(privateHeader.PrivateRegionStartLBA + privateHeader.SecondaryPrivateHeaderLBA); return ReadFromDisk(disk, sectorIndex, false); } } else { // maybe the disk was cloned to a bigger disk, check sector 6 return ReadFromDiskStart(disk); } } public static PrivateHeader ReadFromGPTBasedDisk(Disk disk) { List entries = GuidPartitionTable.ReadEntriesFromDisk(disk); int index = GuidPartitionEntryCollection.GetIndexOfPartitionTypeGuid(entries, GPTPartition.PrivateRegionPartitionTypeGuid); // the private header will be located at the last sector of the private region PrivateHeader privateHeader = PrivateHeader.ReadFromDisk(disk, (long)entries[index].LastLBA, true); if (privateHeader != null) { if (privateHeader.IsChecksumValid) { return privateHeader; } else { // primary has invalid checksum, try secondary private header long sectorIndex = (long)(privateHeader.PrivateRegionStartLBA + privateHeader.SecondaryPrivateHeaderLBA); return ReadFromDisk(disk, sectorIndex, false); } } return null; } public static PrivateHeader ReadFromDiskEnd(Disk disk, bool returnPrivateHeaderWithInvalidChecksum) { long sectorCount = disk.Size / disk.BytesPerSector; long sectorIndex = sectorCount - 1; return ReadFromDisk(disk, sectorIndex, returnPrivateHeaderWithInvalidChecksum); } public static PrivateHeader ReadFromDiskStart(Disk disk) { return ReadFromDisk(disk, PrivateHeaderSectorIndex, false); } public static PrivateHeader ReadFromDisk(Disk disk, long sectorIndex, bool returnPrivateHeaderWithInvalidChecksum) { byte[] sector = disk.ReadSector(sectorIndex); string signature = ByteReader.ReadAnsiString(sector, 0x00, 8); if (signature == PrivateHeaderSignature) { PrivateHeader privateHeader = new PrivateHeader(sector); if (privateHeader.IsChecksumValid || returnPrivateHeaderWithInvalidChecksum) { return privateHeader; } } return null; } public static void WriteToDisk(Disk disk, PrivateHeader privateHeader) { byte[] bytes = privateHeader.GetBytes(); if (disk.BytesPerSector > Length) { bytes = ByteUtils.Concatenate(bytes, new byte[disk.BytesPerSector - PrivateHeader.Length]); } disk.WriteSectors((long)(privateHeader.PrivateRegionStartLBA + privateHeader.PrimaryPrivateHeaderLBA), bytes); disk.WriteSectors((long)(privateHeader.PrivateRegionStartLBA + privateHeader.SecondaryPrivateHeaderLBA), bytes); // update sector 6 if a Private Header is already present there byte[] sector = disk.ReadSector(PrivateHeaderSectorIndex); string signature = ByteReader.ReadAnsiString(sector, 0x00, 8); if (signature == PrivateHeaderSignature) { disk.WriteSectors(PrivateHeaderSectorIndex, bytes); } } public Guid DiskGuid { get { return new Guid(this.DiskGuidString); } set { this.DiskGuidString = value.ToString(); } } public Guid DiskGroupGuid { get { return new Guid(this.DiskGroupGuidString); } set { this.DiskGroupGuidString = value.ToString(); } } public bool IsChecksumValid { get { return m_isChecksumValid; } } public static uint CalculateChecksum(byte[] bytes) { uint result = 0; for (int index = 0; index < bytes.Length; index++) { result += bytes[index]; } return result; } } }