using System.Diagnostics; using QemuVmManager.Models; namespace QemuVmManager.Core; public class DiskManager { private readonly string _diskDirectory; public DiskManager(string diskDirectory = "vm-disks") { _diskDirectory = diskDirectory; Directory.CreateDirectory(_diskDirectory); } public async Task CreateDiskImageAsync(DiskConfiguration diskConfig) { try { // Ensure the directory exists var directory = Path.GetDirectoryName(diskConfig.Path); if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } // Check if disk already exists if (File.Exists(diskConfig.Path)) { return true; // Disk already exists } // Create disk image using qemu-img var sizeInBytes = diskConfig.Size * 1024 * 1024 * 1024; // Convert GB to bytes var sizeInMB = diskConfig.Size * 1024; // Convert GB to MB for qemu-img var process = new Process { StartInfo = new ProcessStartInfo { FileName = "qemu-img", Arguments = $"create -f {diskConfig.Format} \"{diskConfig.Path}\" {sizeInMB}M", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true } }; var started = process.Start(); if (!started) { throw new InvalidOperationException("Failed to start qemu-img process"); } await process.WaitForExitAsync(); if (process.ExitCode != 0) { var error = await process.StandardError.ReadToEndAsync(); throw new InvalidOperationException($"Failed to create disk image: {error}"); } return true; } catch (Exception ex) { throw new InvalidOperationException($"Failed to create disk image '{diskConfig.Path}': {ex.Message}", ex); } } public async Task CreateDiskImagesForVmAsync(VmConfiguration vmConfig) { try { foreach (var disk in vmConfig.Storage.Disks) { await CreateDiskImageAsync(disk); } return true; } catch (Exception ex) { throw new InvalidOperationException($"Failed to create disk images for VM '{vmConfig.Name}': {ex.Message}", ex); } } public bool ValidateDiskImage(string diskPath, string format) { try { if (!File.Exists(diskPath)) { return false; } // Use qemu-img info to validate the disk var process = new Process { StartInfo = new ProcessStartInfo { FileName = "qemu-img", Arguments = $"info -f {format} \"{diskPath}\"", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true } }; var started = process.Start(); if (!started) { return false; } process.WaitForExit(); return process.ExitCode == 0; } catch { return false; } } public async Task GetDiskInfoAsync(string diskPath, string format) { try { if (!File.Exists(diskPath)) { return new DiskInfo { Exists = false }; } var process = new Process { StartInfo = new ProcessStartInfo { FileName = "qemu-img", Arguments = $"info -f {format} \"{diskPath}\"", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true } }; var started = process.Start(); if (!started) { return new DiskInfo { Exists = false }; } await process.WaitForExitAsync(); if (process.ExitCode != 0) { return new DiskInfo { Exists = false }; } var output = await process.StandardOutput.ReadToEndAsync(); return ParseDiskInfo(output, diskPath); } catch { return new DiskInfo { Exists = false }; } } private DiskInfo ParseDiskInfo(string qemuImgOutput, string diskPath) { var info = new DiskInfo { Path = diskPath, Exists = true }; var lines = qemuImgOutput.Split('\n', StringSplitOptions.RemoveEmptyEntries); foreach (var line in lines) { var trimmedLine = line.Trim(); if (trimmedLine.StartsWith("virtual size:")) { var sizePart = trimmedLine.Substring("virtual size:".Length).Trim(); info.VirtualSize = sizePart; } else if (trimmedLine.StartsWith("disk size:")) { var sizePart = trimmedLine.Substring("disk size:".Length).Trim(); info.DiskSize = sizePart; } else if (trimmedLine.StartsWith("format:")) { var formatPart = trimmedLine.Substring("format:".Length).Trim(); info.Format = formatPart; } } return info; } public async Task ResizeDiskAsync(string diskPath, string format, long newSizeGB) { try { if (!File.Exists(diskPath)) { throw new FileNotFoundException($"Disk image not found: {diskPath}"); } var newSizeMB = newSizeGB * 1024; // Convert GB to MB var process = new Process { StartInfo = new ProcessStartInfo { FileName = "qemu-img", Arguments = $"resize -f {format} \"{diskPath}\" {newSizeMB}M", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true } }; var started = process.Start(); if (!started) { throw new InvalidOperationException("Failed to start qemu-img process"); } await process.WaitForExitAsync(); if (process.ExitCode != 0) { var error = await process.StandardError.ReadToEndAsync(); throw new InvalidOperationException($"Failed to resize disk image: {error}"); } return true; } catch (Exception ex) { throw new InvalidOperationException($"Failed to resize disk image '{diskPath}': {ex.Message}", ex); } } public async Task ConvertDiskAsync(string sourcePath, string sourceFormat, string targetPath, string targetFormat) { try { var process = new Process { StartInfo = new ProcessStartInfo { FileName = "qemu-img", Arguments = $"convert -f {sourceFormat} -O {targetFormat} \"{sourcePath}\" \"{targetPath}\"", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true } }; var started = process.Start(); if (!started) { throw new InvalidOperationException("Failed to start qemu-img process"); } await process.WaitForExitAsync(); if (process.ExitCode != 0) { var error = await process.StandardError.ReadToEndAsync(); throw new InvalidOperationException($"Failed to convert disk image: {error}"); } return true; } catch (Exception ex) { throw new InvalidOperationException($"Failed to convert disk image: {ex.Message}", ex); } } public bool DeleteDiskImage(string diskPath) { try { if (File.Exists(diskPath)) { File.Delete(diskPath); return true; } return false; } catch { return false; } } } public class DiskInfo { public string Path { get; set; } = string.Empty; public bool Exists { get; set; } public string? VirtualSize { get; set; } public string? DiskSize { get; set; } public string? Format { get; set; } }