313 lines
9.2 KiB
C#
313 lines
9.2 KiB
C#
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; }
|
|
}
|