diff --git a/Duplicati/UnitTest/DiskImage/MacOSDiskImageHelper.cs b/Duplicati/UnitTest/DiskImage/MacOSDiskImageHelper.cs index b1848b766..088f465f3 100644 --- a/Duplicati/UnitTest/DiskImage/MacOSDiskImageHelper.cs +++ b/Duplicati/UnitTest/DiskImage/MacOSDiskImageHelper.cs @@ -64,55 +64,13 @@ namespace Duplicati.UnitTest.DiskImage // Create a raw disk image using hdiutil // Use UDIF format with "Free Space" filesystem (unformatted) - var psi = new ProcessStartInfo - { - FileName = "hdiutil", - Arguments = $"create -size {sizeB} -type UDIF -layout NONE -o \"{imagePath}\"", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; + RunProcess("hdiutil", $"create -size {sizeB} -type UDIF -layout NONE -o \"{imagePath}\""); - using (var process = new Process { StartInfo = psi }) - { - process.Start(); - string output = process.StandardOutput.ReadToEnd(); - string error = process.StandardError.ReadToEnd(); - process.WaitForExit(); + var output = RunProcess("hdiutil", $"attach -nomount \"{imagePath}\""); - if (process.ExitCode != 0) - throw new InvalidOperationException($"Failed to create disk image '{imagePath}': {error}"); - } - - // Attach the disk image without mounting - psi = new ProcessStartInfo - { - FileName = "hdiutil", - Arguments = $"attach -nomount \"{imagePath}\"", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - string diskDevice; - using (var process = new Process { StartInfo = psi }) - { - process.Start(); - string output = process.StandardOutput.ReadToEnd(); - string error = process.StandardError.ReadToEnd(); - process.WaitForExit(); - - if (process.ExitCode != 0) - throw new InvalidOperationException($"Failed to attach disk image: {error}"); - - // Parse the disk device from output (e.g., "/dev/disk4") - diskDevice = output.Trim().Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault()?.Trim() - ?? throw new InvalidOperationException("Failed to get disk device from hdiutil attach output"); - - _lastDiskDevice = diskDevice; - } + // Parse the disk device from output (e.g., "/dev/disk4") + var diskDevice = output.Trim().Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault()?.Trim() + ?? throw new InvalidOperationException("Failed to get disk device from hdiutil attach output"); // Return the raw device path for direct I/O return diskDevice.Replace("/dev/disk", "/dev/rdisk"); @@ -120,20 +78,7 @@ namespace Duplicati.UnitTest.DiskImage private string[] GetPartitionNames(string diskIdentifier) { - var psi = new ProcessStartInfo - { - FileName = "diskutil", - Arguments = $"list {diskIdentifier}", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using var process = new Process { StartInfo = psi }; - process.Start(); - string output = process.StandardOutput.ReadToEnd(); - process.WaitForExit(); + var output = RunProcess("diskutil", $"list {diskIdentifier}"); // Very hardcoded to disks with < 10 partitions and no identifiers with spaces. Should be sufficient for test purposes. return output @@ -154,20 +99,7 @@ namespace Duplicati.UnitTest.DiskImage foreach (var partition in partitions) { - var psi = new ProcessStartInfo - { - FileName = "diskutil", - Arguments = $"info /dev/{partition}", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using var process = new Process { StartInfo = psi }; - process.Start(); - string output = process.StandardOutput.ReadToEnd(); - process.WaitForExit(); + var output = RunProcess("diskutil", $"info /dev/{partition}"); var mountLine = output .Split('\n') @@ -208,24 +140,7 @@ namespace Duplicati.UnitTest.DiskImage : ["Free Space \"\" 0"]; var partitionArgs = string.Join(" ", partitionStrings); - var psi = new ProcessStartInfo - { - FileName = "diskutil", - Arguments = $"partitionDisk {diskIdentifier} {tableType.ToString().ToUpperInvariant()} {partitionArgs}", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using var process = new Process { StartInfo = psi }; - process.Start(); - string output = process.StandardOutput.ReadToEnd(); - string error = process.StandardError.ReadToEnd(); - process.WaitForExit(); - - if (process.ExitCode != 0) - throw new InvalidOperationException($"Failed to initialize disk: {error}"); + RunProcess("diskutil", $"partitionDisk {diskIdentifier} {tableType.ToString().ToUpperInvariant()} {partitionArgs}"); if (partitions.Length == 0) return []; @@ -242,24 +157,7 @@ namespace Duplicati.UnitTest.DiskImage return mountPoints; // Mount all volumes on the disk - var psi = new ProcessStartInfo - { - FileName = "diskutil", - Arguments = $"mountDisk {diskIdentifier}", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using var process = new Process { StartInfo = psi }; - process.Start(); - string output = process.StandardOutput.ReadToEnd(); - string error = process.StandardError.ReadToEnd(); - process.WaitForExit(); - - if (process.ExitCode != 0) - throw new InvalidOperationException($"Failed to mount disk: {error}"); + RunProcess("diskutil", $"mountDisk {diskIdentifier}"); // Find the mount points for the mounted partitions return GetMountPoints(diskIdentifier); @@ -268,42 +166,14 @@ namespace Duplicati.UnitTest.DiskImage public void Unmount(string diskIdentifier) { // Unmount all volumes on the disk - var psi = new ProcessStartInfo - { - FileName = "diskutil", - Arguments = $"unmountDisk {diskIdentifier}", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using var process = new Process { StartInfo = psi }; - process.Start(); - string output = process.StandardOutput.ReadToEnd(); - string error = process.StandardError.ReadToEnd(); - process.WaitForExit(); - - if (process.ExitCode != 0) - throw new InvalidOperationException($"Failed to mount disk: {error}"); + RunProcess("diskutil", $"unmountDisk {diskIdentifier}"); } /// public void FlushDisk(string diskIdentifier) { // On macOS, use sync to flush all filesystem buffers - var psi = new ProcessStartInfo - { - FileName = "sync", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using var process = new Process { StartInfo = psi }; - process.Start(); - process.WaitForExit(); + RunProcess("sync", ""); } /// @@ -311,25 +181,7 @@ namespace Duplicati.UnitTest.DiskImage { if (diskIdentifier is null) { - var psi = new ProcessStartInfo - { - FileName = "hdiutil", - Arguments = $"info", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using var process = new Process { StartInfo = psi }; - process.Start(); - string output = process.StandardOutput.ReadToEnd(); - process.WaitForExit(); - - if (process.ExitCode != 0) - { - throw new InvalidOperationException($"Failed to get hdiutil info: {process.StandardError.ReadToEnd()}"); - } + var output = RunProcess("hdiutil", $"info"); diskIdentifier = output .Split("================================================") @@ -367,23 +219,7 @@ namespace Duplicati.UnitTest.DiskImage try { - var psi = new ProcessStartInfo - { - FileName = "diskutil", - Arguments = $"eject {diskIdentifier}", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using var process = new Process { StartInfo = psi }; - process.Start(); - string output = process.StandardOutput.ReadToEnd(); - string error = process.StandardError.ReadToEnd(); - process.WaitForExit(); - if (process.ExitCode != 0) - TestContext.Progress.WriteLine($"Warning: Failed to eject disk {diskIdentifier}: {error}"); + RunProcess("diskutil", $"eject {diskIdentifier}"); } catch (Exception ex) { @@ -407,20 +243,7 @@ namespace Duplicati.UnitTest.DiskImage // On macOS, check if running as root (UID 0) try { - var psi = new ProcessStartInfo - { - FileName = "id", - Arguments = "-u", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using var process = new Process { StartInfo = psi }; - process.Start(); - string output = process.StandardOutput.ReadToEnd().Trim(); - process.WaitForExit(); + var output = RunProcess("id", "-u"); if (int.TryParse(output, out int uid)) { @@ -438,24 +261,7 @@ namespace Duplicati.UnitTest.DiskImage /// public Proprietary.DiskImage.PartitionTableGeometry GetPartitionTable(string diskIdentifier) { - var psi = new ProcessStartInfo - { - FileName = "diskutil", - Arguments = $"info {diskIdentifier}", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using var process = new Process { StartInfo = psi }; - process.Start(); - string output = process.StandardOutput.ReadToEnd(); - string error = process.StandardError.ReadToEnd(); - process.WaitForExit(); - - if (process.ExitCode != 0) - throw new InvalidOperationException($"Process diskutil exited with code {process.ExitCode}: {error}"); + var output = RunProcess("diskutil", $"info {diskIdentifier}"); var lines = output .Split('\n') @@ -516,24 +322,7 @@ namespace Duplicati.UnitTest.DiskImage var partitions = new List(); foreach (var name in names) { - var psi = new ProcessStartInfo - { - FileName = "diskutil", - Arguments = $"info /dev/{name}", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using var process = new Process { StartInfo = psi }; - process.Start(); - string output = process.StandardOutput.ReadToEnd(); - string error = process.StandardError.ReadToEnd(); - process.WaitForExit(); - - if (process.ExitCode != 0) - throw new InvalidOperationException($"Process diskutil exited with code {process.ExitCode}: {error}"); + var output = RunProcess("diskutil", $"info /dev/{name}"); var lines = output .Split('\n') @@ -601,6 +390,35 @@ namespace Duplicati.UnitTest.DiskImage return partitions.ToArray(); } + private string RunProcess(string fileName, string arguments) + { + var psi = new ProcessStartInfo + { + FileName = fileName, + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using var process = new Process { StartInfo = psi }; + process.Start(); + string output = process.StandardOutput.ReadToEnd(); + string error = process.StandardError.ReadToEnd(); + process.WaitForExit(); + + if (process.ExitCode != 0) + { + Console.WriteLine($"Error running process {fileName} {arguments}"); + Console.WriteLine($"Error: {error}"); + Console.WriteLine($"Output: {output}"); + throw new InvalidOperationException($"Process {fileName} exited with code {process.ExitCode}: {error}"); + } + + return output; + } + /// public void Dispose() {