From bc5e5d85002d9044fa39ccb9609f1ed3cfd715da Mon Sep 17 00:00:00 2001 From: Carl Johnsen Date: Tue, 24 Feb 2026 14:56:31 +0100 Subject: [PATCH] Introduced waiting for disks during disk mount --- .../DiskImage/MacOSDiskImageHelper.cs | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/Duplicati/UnitTest/DiskImage/MacOSDiskImageHelper.cs b/Duplicati/UnitTest/DiskImage/MacOSDiskImageHelper.cs index a69fc152c..c6447afc3 100644 --- a/Duplicati/UnitTest/DiskImage/MacOSDiskImageHelper.cs +++ b/Duplicati/UnitTest/DiskImage/MacOSDiskImageHelper.cs @@ -221,7 +221,60 @@ namespace Duplicati.UnitTest.DiskImage // Mount all volumes on the disk RunProcess("diskutil", $"mountDisk {diskIdentifier}"); - // Find the mount points for the mounted partitions + return WaitForMountPoints(diskIdentifier); + } + + /// + /// Waits for mount points to be fully established after a mount operation. + /// Uses exponential backoff starting from a short delay to minimize wait time + /// in the common case while still handling slow mounts. + /// + /// After mounting (especially after raw partition table writes during restore), + /// macOS may need time to fully recognize filesystems and establish mount points + /// in /Volumes/. Without this retry loop, mount points may be: + /// - Not yet available (empty array) + /// - In a transient location like /private/var/ instead of /Volumes/ + /// - Mounted but with empty directories (filesystem not yet recognized) + /// + /// The disk identifier to check mount points for. + /// An array of mount point paths. + private string[] WaitForMountPoints(string diskIdentifier) + { + const int maxRetries = 10; + const int initialDelayMs = 100; + const int maxDelayMs = 1000; + + var delayMs = initialDelayMs; + for (int i = 0; i < maxRetries; i++) + { + var mountPoints = GetMountPoints(diskIdentifier); + + if (mountPoints.Length > 0) + { + // Check if any mount points are in a transient location (not under /Volumes/) + if (mountPoints.Any(mp => !mp.StartsWith("/Volumes/"))) + { + Thread.Sleep(delayMs); + delayMs = Math.Min(delayMs * 2, maxDelayMs); + continue; + } + + // Check if all mount points have filesystem entries (not empty) + var allNonEmpty = mountPoints.All(mp => + { + try { return Directory.GetFileSystemEntries(mp).Length > 0; } + catch { return false; } + }); + + if (allNonEmpty || i == maxRetries - 1) + return mountPoints; + } + + Thread.Sleep(delayMs); + delayMs = Math.Min(delayMs * 2, maxDelayMs); + } + + // Final attempt - return whatever we have return GetMountPoints(diskIdentifier); }