using System; using System.Linq; using System.Threading; using System.Threading.Tasks; using Duplicati.Proprietary.DiskImage.Raw; namespace Duplicati.Proprietary.DiskImage.Partition; /// /// Factory for creating partition table instances with auto-detection. /// Parses the MBR first to determine if the disk uses GPT or MBR partition tables. /// public static class PartitionTableFactory { // MBR constants private const int MbrSize = 512; private const ushort MbrBootSignature = 0xAA55; private const byte ProtectiveMbrType = 0xEE; // GPT constants private const int HeaderSize = 92; private const long GptSignature = 0x5452415020494645; // "EFI PART" in little-endian /// /// Creates a partition table instance by auto-detecting the partition table type. /// Reads the MBR first, which determines if we have a GPT or MBR partition table. /// /// The raw disk to analyze. /// Cancellation token. /// The appropriate partition table instance (GPT or MBR), or null if detection fails. public static async Task CreateAsync(IRawDisk disk, CancellationToken cancellationToken) { if (disk == null) throw new ArgumentNullException(nameof(disk)); // Read the first sector (LBA 0 - MBR) var mbrBytes = new byte[MbrSize]; using var stream = await disk.ReadBytesAsync(0, MbrSize, cancellationToken).ConfigureAwait(false); await stream.ReadAtLeastAsync(mbrBytes, MbrSize, cancellationToken: cancellationToken).ConfigureAwait(false); // Detect partition table type from MBR var tableType = DetectPartitionTableType(mbrBytes); return tableType switch { PartitionTableType.GPT => await CreateGPTAsync(disk, mbrBytes, cancellationToken).ConfigureAwait(false), PartitionTableType.MBR => await CreateMBRAsync(disk, mbrBytes, cancellationToken).ConfigureAwait(false), _ => null }; } /// /// Creates a partition table instance from raw byte array with auto-detection. /// /// The raw disk bytes. /// The sector size (typically 512 or 4096). /// Cancellation token. /// The appropriate partition table instance (GPT or MBR), or null if detection fails. public static async Task CreateAsync(byte[] bytes, int sectorSize, CancellationToken cancellationToken) { if (bytes == null) throw new ArgumentNullException(nameof(bytes)); if (bytes.Length < MbrSize) throw new ArgumentException($"Byte array must be at least {MbrSize} bytes long.", nameof(bytes)); if (sectorSize <= 0) throw new ArgumentException("Sector size must be positive.", nameof(sectorSize)); // Read the first sector (LBA 0 - MBR) var mbrBytes = bytes[0..sectorSize]; // Detect partition table type from MBR var tableType = DetectPartitionTableType(mbrBytes); return tableType switch { PartitionTableType.GPT => await CreateGPTAsync(bytes, sectorSize, cancellationToken).ConfigureAwait(false), PartitionTableType.MBR => await CreateMBRAsync(mbrBytes, sectorSize, cancellationToken).ConfigureAwait(false), _ => null }; } /// /// Detects the partition table type by examining the MBR. /// private static PartitionTableType DetectPartitionTableType(byte[] mbrBytes) { // Check for valid MBR boot signature ushort bootSignature = BitConverter.ToUInt16(mbrBytes, 510); if (bootSignature != MbrBootSignature) return PartitionTableType.Unknown; // Check for GPT protective MBR (type 0xEE in first partition entry) byte partitionType = mbrBytes[450]; // If first partition entry has type 0xEE, this is likely a GPT disk if (partitionType == ProtectiveMbrType) { return PartitionTableType.GPT; } // Otherwise, it's a traditional MBR disk return PartitionTableType.MBR; } /// /// Checks if the disk has a valid GPT header at LBA 1. /// This is used to confirm GPT detection when protective MBR is present. /// private static async Task HasValidGptHeaderAsync(IRawDisk disk, CancellationToken cancellationToken) { try { var sectorSize = disk.SectorSize; var headerBytes = new byte[HeaderSize]; // Read a sector at LBA 1 (GPT header) using var stream = await disk.ReadBytesAsync(sectorSize, sectorSize, cancellationToken).ConfigureAwait(false); await stream.ReadAtLeastAsync(headerBytes, HeaderSize, cancellationToken: cancellationToken).ConfigureAwait(false); var signature = BitConverter.ToInt64(headerBytes, 0); return signature == GptSignature; } catch { return false; } } /// /// Checks if the byte array has a valid GPT header at sector 1. /// private static bool HasValidGptHeader(byte[] bytes, int sectorSize) { try { if (bytes.Length < sectorSize + HeaderSize) return false; var headerBytes = bytes[sectorSize..(sectorSize + HeaderSize)]; var signature = BitConverter.ToInt64(headerBytes, 0); return signature == GptSignature; } catch { return false; } } private static async Task CreateGPTAsync(IRawDisk disk, byte[] mbrBytes, CancellationToken cancellationToken) { var gpt = new GPT(); // Verify GPT signature at LBA 1 if (!await HasValidGptHeaderAsync(disk, cancellationToken).ConfigureAwait(false)) { // Fall back to parsing as MBR if GPT header is invalid return await CreateMBRAsync(disk, mbrBytes, cancellationToken).ConfigureAwait(false); } await gpt.ParseAsync(disk, cancellationToken).ConfigureAwait(false); return gpt; } private static async Task CreateGPTAsync(byte[] mbrBytes, int sectorSize, CancellationToken cancellationToken) { var gpt = new GPT(); // Verify GPT signature at LBA 1 if (!HasValidGptHeader(mbrBytes, sectorSize)) { // Fall back to parsing as MBR if GPT header is invalid return await CreateMBRAsync(mbrBytes, sectorSize, cancellationToken).ConfigureAwait(false); } await gpt.ParseAsync(mbrBytes, sectorSize, cancellationToken).ConfigureAwait(false); return gpt; } private static async Task CreateMBRAsync(IRawDisk disk, byte[] mbrBytes, CancellationToken cancellationToken) { var mbr = new MBR(); await mbr.ParseAsync(disk, cancellationToken).ConfigureAwait(false); return mbr; } private static async Task CreateMBRAsync(byte[] mbrBytes, int sectorSize, CancellationToken cancellationToken) { var mbr = new MBR(); await mbr.ParseAsync(mbrBytes, sectorSize, cancellationToken).ConfigureAwait(false); return mbr; } }