// Copyright (c) 2026 Duplicati Inc. All rights reserved. using System; using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Duplicati.Library.Interface; using Duplicati.Proprietary.DiskImage.Disk; using Duplicati.Proprietary.DiskImage.General; using Duplicati.Proprietary.DiskImage.Partition; namespace Duplicati.Proprietary.DiskImage.SourceItems; /// /// Represents the partition table as a source entry for backup/restore purposes. /// This allows the partition table (MBR or GPT) to be backed up and restored separately. /// internal class PartitionTableSourceEntry(string parentPath, IPartitionTable table, IRawDisk disk) : DiskImageEntryBase(System.IO.Path.Combine(parentPath, $"partition_table_{table.TableType}")) { public override bool IsFolder => false; public override bool IsMetaEntry => true; public override long Size => GetTableSize(); public override Task OpenRead(CancellationToken cancellationToken) { return table.GetPartitionTableDataAsync(cancellationToken); } public override async IAsyncEnumerable Enumerate([EnumeratorCancellation] CancellationToken cancellationToken) { // Partition table is a leaf node - no children await Task.CompletedTask; yield break; } public override async Task> GetMinorMetadata(CancellationToken cancellationToken) { var metadata = await base.GetMinorMetadata(cancellationToken); // Add partition table metadata metadata["diskimage:Type"] = "partition_table"; metadata["partition_table:Type"] = table.TableType.ToString(); metadata["partition_table:Size"] = Size.ToString(); metadata["disk:SectorSize"] = disk.SectorSize.ToString(); // Add type-specific metadata if (table.TableType == PartitionTableType.MBR) { metadata["partition_table:MbrSize"] = "512"; } else if (table.TableType == PartitionTableType.GPT) { metadata["partition_table:HasProtectiveMbr"] = "true"; metadata["partition_table:HeaderSize"] = "92"; // GPT header is 92 bytes } return metadata; } private long GetTableSize() { try { // Try to get the size by reading the table data using var stream = table.GetPartitionTableDataAsync(CancellationToken.None).Result; return stream.Length; } catch { // Fallback to estimated sizes return table.TableType switch { PartitionTableType.MBR => 512, PartitionTableType.GPT => 512 + 512 + (128 * 4), // Protective MBR + Header + 4 entries _ => 0 }; } } }