Fixed partition parsing of APFS and HFSPlus systems

This commit is contained in:
Carl Johnsen
2026-02-24 11:37:03 +01:00
parent 1fc37ab165
commit 71bc154303
@@ -101,19 +101,79 @@ namespace Duplicati.UnitTest.DiskImage
{
var output = RunProcess("diskutil", $"info /dev/{partition}");
var mountLine = output
.Split('\n')
.FirstOrDefault(l => l.Trim().StartsWith("Mount Point:"))
?? throw new InvalidOperationException($"Failed to get mount point for partition {partition} of disk {diskIdentifier}");
// Check if this is an APFS Physical Store
var isApfsPhysicalStore = output.Contains("APFS Physical Store") ||
output.Split('\n').Any(l => l.Trim().StartsWith("Partition Type:") && l.Contains("Apple_APFS"));
var mountPoint = mountLine.Split([':'], 2)[1].Trim();
if (!string.IsNullOrEmpty(mountPoint) && mountPoint != "Not mounted")
mountPoints.Add(mountPoint);
if (isApfsPhysicalStore)
{
// For APFS, get the container and then get volumes from it
var containerLine = output
.Split('\n')
.FirstOrDefault(l => l.Trim().StartsWith("APFS Container:"));
if (containerLine != null)
{
var containerId = containerLine.Split([':'], 2)[1].Trim();
// Get volumes from the container (they are like disk5s1, disk5s2, etc.)
var containerVolumes = GetApfsContainerVolumes(containerId);
foreach (var volume in containerVolumes)
{
var volumeOutput = RunProcess("diskutil", $"info {volume}");
var mountPoint = ExtractMountPoint(volumeOutput);
if (!string.IsNullOrEmpty(mountPoint))
mountPoints.Add(mountPoint);
}
}
}
else
{
// Regular partition - extract mount point directly
var mountPoint = ExtractMountPoint(output);
if (!string.IsNullOrEmpty(mountPoint))
mountPoints.Add(mountPoint);
}
}
return [.. mountPoints];
}
private string[] GetApfsContainerVolumes(string containerId)
{
var output = RunProcess("diskutil", $"list {containerId}");
// Parse volumes from container listing (format: disk5s1, disk5s2, etc.)
return output
.Split('\n')
.Select(x => x.Trim())
.Where(x => x.StartsWith(containerId + "s", StringComparison.OrdinalIgnoreCase))
.Select(x => x.Split([' '], StringSplitOptions.RemoveEmptyEntries).FirstOrDefault())
.Where(x => x is not null)
.Select(x => x!)
.ToArray();
}
private string? ExtractMountPoint(string diskutilInfoOutput)
{
// Look for "Mount Point:" line
var mountLine = diskutilInfoOutput
.Split('\n')
.FirstOrDefault(l => l.Trim().StartsWith("Mount Point:"));
if (mountLine == null)
return null;
var mountPoint = mountLine.Split([':'], 2)[1].Trim();
// Return null if not mounted or not applicable
if (string.IsNullOrEmpty(mountPoint) ||
mountPoint == "Not mounted" ||
mountPoint == "Not applicable (no file system)")
return null;
return mountPoint;
}
/// <inheritdoc />
public string[] InitializeDisk(string diskIdentifier, Proprietary.DiskImage.PartitionTableType tableType, (Proprietary.DiskImage.FileSystemType, long)[] partitions)
{