Initial commit

This commit is contained in:
2025-08-30 18:55:20 -04:00
commit eb00a5472f
19 changed files with 4263 additions and 0 deletions

View File

@@ -0,0 +1,312 @@
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<bool> 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<bool> 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<DiskInfo> 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<bool> 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<bool> 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; }
}