// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.IO; using System.Threading; using System.Threading.Tasks; using Duplicati.Proprietary.DiskImage; using Duplicati.Proprietary.DiskImage.Disk; using Duplicati.Proprietary.DiskImage.General; #nullable enable namespace Duplicati.UnitTest.DiskImage { /// /// Helper methods for disk image unit tests. /// Provides common operations for creating, initializing, and managing disk images. /// internal static class DiskImageTestHelpers { private const long MiB = 1024 * 1024; /// /// Creates a raw disk interface for the specified disk identifier based on the current platform. /// /// The disk identifier (e.g., \\.\PhysicalDriveN on Windows, /dev/loopN on Linux). /// An initialized IRawDisk instance for the specified platform. /// Thrown when the current OS is not supported. internal static IRawDisk CreateRawDiskForIdentifier(string diskIdentifier) { if (OperatingSystem.IsWindows()) return new Duplicati.Proprietary.DiskImage.Disk.Windows(diskIdentifier); else if (OperatingSystem.IsLinux()) return new Duplicati.Proprietary.DiskImage.Disk.Linux(diskIdentifier); else if (OperatingSystem.IsMacOS()) return new Duplicati.Proprietary.DiskImage.Disk.Mac(diskIdentifier); else throw new PlatformNotSupportedException("Unsupported operating system."); } /// /// Creates a disk image file with the specified parameters. /// /// The disk image helper. /// The path where the disk image will be created. /// The size of the disk in bytes. /// The partition table type (GPT, MBR, etc.). /// Array of tuples specifying filesystem type and size for each partition. /// The disk identifier for the created disk. internal static string CreateDiskWithPartitions( IDiskImageHelper diskHelper, string imagePath, long size, PartitionTableType tableType, (FileSystemType, long)[] partitions) { var diskIdentifier = diskHelper.CreateDisk(imagePath, size); diskHelper.InitializeDisk(diskIdentifier, tableType, partitions); diskHelper.FlushDisk(diskIdentifier); diskHelper.Unmount(diskIdentifier); return diskIdentifier; } /// /// Fills a partition with well-known test data (repeating pattern of 0x00-0xFF). /// /// The raw disk interface. /// The starting offset of the partition in bytes. /// The size of the partition in bytes. /// Cancellation token. internal static async Task FillPartitionWithTestDataAsync( IRawDisk rawDisk, long partitionOffset, long partitionSize, CancellationToken cancellationToken = default) { const int bufferSize = 64 * 1024; // 64KB chunks var buffer = new byte[bufferSize]; long bytesWritten = 0; long currentOffset = partitionOffset; while (bytesWritten < partitionSize) { cancellationToken.ThrowIfCancellationRequested(); // Fill buffer with well-known pattern (repeating 0x00-0xFF) for (int i = 0; i < buffer.Length; i++) { buffer[i] = (byte)((bytesWritten + i) & 0xFF); } var remaining = partitionSize - bytesWritten; var toWrite = (int)Math.Min(bufferSize, remaining); await rawDisk.WriteBytesAsync(currentOffset, buffer.AsMemory(0, toWrite), cancellationToken); bytesWritten += toWrite; currentOffset += toWrite; } } /// /// Gets the file extension for disk images based on the current platform. /// /// The platform-specific disk image extension (vhdx, img, or dmg). internal static string GetPlatformDiskImageExtension() { if (OperatingSystem.IsWindows()) return "vhdx"; else if (OperatingSystem.IsLinux()) return "img"; else if (OperatingSystem.IsMacOS()) return "dmg"; else throw new PlatformNotSupportedException("Unsupported operating system."); } /// /// Deletes a file if it exists, catching any exceptions. /// /// The path to the file to delete. internal static void SafeDeleteFile(string? filePath) { if (string.IsNullOrEmpty(filePath)) return; try { if (File.Exists(filePath)) File.Delete(filePath); } catch (Exception ex) { Console.WriteLine($"Warning: Failed to delete file {filePath}: {ex.Message}"); } } } }