2026-02-06 06:39:22 +01:00
|
|
|
// 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;
|
2026-03-05 15:30:36 +01:00
|
|
|
using Duplicati.Proprietary.DiskImage.General;
|
2026-02-06 06:39:22 +01:00
|
|
|
using Duplicati.Proprietary.DiskImage.Partition;
|
|
|
|
|
|
|
|
|
|
namespace Duplicati.Proprietary.DiskImage.SourceItems;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 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.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal class PartitionTableSourceEntry(string parentPath, IPartitionTable table, IRawDisk disk)
|
2026-02-12 16:54:35 +01:00
|
|
|
: DiskImageEntryBase(System.IO.Path.Combine(parentPath, $"partition_table_{table.TableType}"))
|
2026-02-06 06:39:22 +01:00
|
|
|
{
|
|
|
|
|
public override bool IsFolder => false;
|
|
|
|
|
public override bool IsMetaEntry => true;
|
|
|
|
|
public override long Size => GetTableSize();
|
|
|
|
|
|
|
|
|
|
public override Task<Stream> OpenRead(CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
return table.GetPartitionTableDataAsync(cancellationToken);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override async IAsyncEnumerable<ISourceProviderEntry> Enumerate([EnumeratorCancellation] CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
// Partition table is a leaf node - no children
|
|
|
|
|
await Task.CompletedTask;
|
|
|
|
|
yield break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override async Task<Dictionary<string, string?>> 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
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|