Files
skystack/QemuVmManager.Core/QemuCommandBuilder.cs

536 lines
19 KiB
C#

using QemuVmManager.Models;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Linq;
namespace QemuVmManager.Core;
public class QemuCommandBuilder
{
private readonly VmConfiguration _config;
private readonly List<string> _arguments = new();
private readonly VirtualizationType _virtualizationType;
public QemuCommandBuilder(VmConfiguration config, VirtualizationType virtualizationType = VirtualizationType.TCG)
{
_config = config;
_virtualizationType = virtualizationType;
}
public string BuildCommand()
{
_arguments.Clear();
// Basic QEMU command
_arguments.Add("qemu-system-x86_64");
// Machine and CPU configuration
AddMachineConfiguration();
AddCpuConfiguration();
AddMemoryConfiguration();
// Storage configuration
AddStorageConfiguration();
// Network configuration
Console.WriteLine("Debug: About to add network configuration");
AddNetworkConfiguration();
Console.WriteLine("Debug: Network configuration added");
// Display configuration
AddDisplayConfiguration();
// Boot configuration
AddBootConfiguration();
// Advanced features
AddAdvancedConfiguration();
// Extra arguments
_arguments.AddRange(_config.Advanced.ExtraArgs);
// Handle CPU model based on virtualization type
if (_virtualizationType == VirtualizationType.TCG || _virtualizationType == VirtualizationType.HyperV)
{
// Replace 'host' CPU model with 'qemu64' for TCG compatibility
for (int i = 0; i < _arguments.Count; i++)
{
if (_arguments[i] == "-cpu" && i + 1 < _arguments.Count && _arguments[i + 1] == "host")
{
_arguments[i + 1] = "qemu64";
}
}
}
// Remove -enable-kvm if not using KVM
if (_virtualizationType != VirtualizationType.KVM)
{
_arguments.RemoveAll(arg => arg == "-enable-kvm");
}
// Add WHPX-specific configurations
if (_virtualizationType == VirtualizationType.HyperV)
{
AddWHPXSpecificConfiguration();
}
return string.Join(" ", _arguments);
}
private string GetCustomPortForwards()
{
try
{
var configFile = "port-forwards.json";
if (!File.Exists(configFile))
return string.Empty;
var json = File.ReadAllText(configFile);
var portForwards = System.Text.Json.JsonSerializer.Deserialize<List<QemuVmManager.Models.PortForward>>(json) ?? new List<QemuVmManager.Models.PortForward>();
// Filter port forwards for this VM
var vmForwards = portForwards.Where(pf => pf.VmName == _config.Name).ToList();
if (!vmForwards.Any())
return string.Empty;
// Build hostfwd arguments for custom port forwards
var forwardArgs = new List<string>();
foreach (var pf in vmForwards)
{
forwardArgs.Add($"hostfwd=tcp::{pf.HostPort}-:{pf.VmPort}");
}
return string.Join(",", forwardArgs);
}
catch (Exception ex)
{
Console.WriteLine($"Warning: Could not load custom port forwards: {ex.Message}");
return string.Empty;
}
}
private void AddMachineConfiguration()
{
_arguments.Add("-machine");
switch (_virtualizationType)
{
case VirtualizationType.KVM:
_arguments.Add("type=q35,accel=kvm:tcg");
break;
case VirtualizationType.HyperV:
// Use WHPX hardware acceleration
_arguments.Add("type=pc-i440fx-10.1,accel=whpx:tcg,kernel-irqchip=off");
break;
case VirtualizationType.HAXM:
_arguments.Add("type=q35,accel=hax:tcg");
break;
case VirtualizationType.HVF:
_arguments.Add("type=q35,accel=hvf:tcg");
break;
case VirtualizationType.TCG:
default:
_arguments.Add("type=q35,accel=tcg");
break;
}
}
private void AddCpuConfiguration()
{
var cpu = _config.Cpu;
_arguments.Add("-cpu");
_arguments.Add(cpu.Model);
// For WHPX, use simpler CPU configuration to avoid exit code 4
if (_virtualizationType == VirtualizationType.HyperV)
{
_arguments.Add("-smp");
_arguments.Add($"{Math.Min(cpu.Cores, 2)},cores={Math.Min(cpu.Cores, 2)},sockets=1,threads=1");
}
else
{
_arguments.Add("-smp");
_arguments.Add($"{cpu.Cores},cores={cpu.Cores},sockets={cpu.Sockets},threads={cpu.Threads}");
}
}
private void AddMemoryConfiguration()
{
var memory = _config.Memory;
// For WHPX, use smaller memory allocation to avoid exit code 4
if (_virtualizationType == VirtualizationType.HyperV)
{
var whpxMemorySize = Math.Min(memory.Size, 4096); // Limit to 4GB for WHPX
_arguments.Add("-m");
_arguments.Add($"{whpxMemorySize}{memory.Unit}");
}
else
{
_arguments.Add("-m");
_arguments.Add($"{memory.Size}{memory.Unit}");
}
}
private void AddStorageConfiguration()
{
var storage = _config.Storage;
// Add disks
for (int i = 0; i < storage.Disks.Count; i++)
{
var disk = storage.Disks[i];
var driveLetter = (char)('a' + i);
_arguments.Add("-drive");
var driveArgs = $"file={disk.Path.Replace('\\', '/')},format={disk.Format},cache={disk.Cache},id=drive{i},if=none";
_arguments.Add(driveArgs);
// For WHPX, only use the first disk on IDE to avoid conflicts
if (_virtualizationType == VirtualizationType.HyperV && i > 0)
{
Console.WriteLine($"Warning: Additional disk {i} disabled for WHPX compatibility to avoid IDE conflicts. Disk: {disk.Path}");
continue;
}
// Add device
_arguments.Add("-device");
if (disk.Interface == "virtio" && _virtualizationType != VirtualizationType.HyperV)
{
// Use virtio for better performance, but avoid with WHPX due to MSI issues
_arguments.Add($"virtio-blk-pci,drive=drive{i}");
}
else
{
// Use IDE for WHPX compatibility
_arguments.Add($"ide-hd,drive=drive{i}");
}
}
// Add CD-ROM if specified
if (!string.IsNullOrEmpty(storage.Cdrom))
{
if (_virtualizationType == VirtualizationType.HyperV)
{
// For WHPX, use the second IDE controller for CD-ROM
_arguments.Add("-drive");
_arguments.Add($"file={storage.Cdrom.Replace('\\', '/')},media=cdrom,if=ide,index=1");
}
else
{
// Use standard CD-ROM for other virtualization types
_arguments.Add("-cdrom");
_arguments.Add(storage.Cdrom.Replace('\\', '/'));
}
}
}
private void AddNetworkConfiguration()
{
var network = _config.Network;
Console.WriteLine($"Debug: Network configuration - Interfaces count: {network.Interfaces.Count}");
for (int i = 0; i < network.Interfaces.Count; i++)
{
var nic = network.Interfaces[i];
_arguments.Add("-netdev");
string netdevArgs;
// Configure network backend based on type
if (nic.Type == "bridge" && !string.IsNullOrEmpty(nic.Bridge))
{
if (OperatingSystem.IsMacOS())
{
// On macOS, use host networking with automatic DHCP (built into QEMU)
// The 'net=' parameter automatically enables DHCP server
var hostPort = 10000 + i; // Use different ports for multiple interfaces
// Build base netdev arguments
var baseArgs = $"user,id=net{i},net=192.168.100.0/24,host=192.168.100.1";
// Add built-in port forwards
var builtInForwards = $"hostfwd=tcp::{hostPort}-:22,hostfwd=tcp::{hostPort + 1}-:80,hostfwd=tcp::{hostPort + 2}-:443";
// Add custom port forwards from configuration
var customForwards = GetCustomPortForwards();
if (!string.IsNullOrEmpty(customForwards))
{
netdevArgs = $"{baseArgs},{builtInForwards},{customForwards}";
}
else
{
netdevArgs = $"{baseArgs},{builtInForwards}";
}
_arguments.Add(netdevArgs);
Console.WriteLine($"Info: Using host-based networking with automatic DHCP on macOS");
Console.WriteLine("Benefits:");
Console.WriteLine("✅ Automatic IP assignment via QEMU's built-in DHCP");
Console.WriteLine("✅ Direct host network access (bypasses QEMU DNS issues)");
Console.WriteLine("✅ DNS requests go directly to host's DNS servers");
Console.WriteLine("✅ Internet access through host network");
Console.WriteLine("✅ Automatic port forwarding for common services");
Console.WriteLine($"✅ Host ports: {hostPort} (SSH), {hostPort + 1} (HTTP), {hostPort + 2} (HTTPS)");
Console.WriteLine("");
Console.WriteLine("📝 Network Configuration (Inside VM):");
Console.WriteLine(" The VM will get an IP automatically via QEMU's DHCP.");
Console.WriteLine(" If DHCP fails, manually configure:");
Console.WriteLine(" sudo ip addr add 192.168.100.x/24 dev enp0s3");
Console.WriteLine(" sudo ip route add default via 192.168.100.1");
Console.WriteLine(" DNS: nameserver 192.168.100.1 or 8.8.8.8");
_arguments.Add("-device");
var deviceArgs = "";
// Use different network models based on virtualization type
if (nic.Model == "virtio-net-pci" && _virtualizationType == VirtualizationType.HyperV)
{
// Use e1000 for WHPX compatibility to avoid MSI issues
deviceArgs = $"e1000,netdev=net{i}";
}
else
{
deviceArgs = $"{nic.Model},netdev=net{i}";
}
if (!string.IsNullOrEmpty(nic.Mac))
{
deviceArgs += $",mac={nic.Mac}";
}
_arguments.Add(deviceArgs);
}
else
{
// Use TAP networking for Linux/Windows
netdevArgs = $"tap,id=net{i},script=no,downscript=no";
_arguments.Add(netdevArgs);
_arguments.Add("-device");
var deviceArgs = "";
// Use different network models based on virtualization type
if (nic.Model == "virtio-net-pci" && _virtualizationType == VirtualizationType.HyperV)
{
// Use e1000 for WHPX compatibility to avoid MSI issues
deviceArgs = $"e1000,netdev=net{i}";
}
else
{
deviceArgs = $"{nic.Model},netdev=net{i}";
}
if (!string.IsNullOrEmpty(nic.Mac))
{
deviceArgs += $",mac={nic.Mac}";
}
_arguments.Add(deviceArgs);
}
}
else
{
// Use user network as fallback (NAT only)
netdevArgs = $"user,id=net{i}";
_arguments.Add(netdevArgs);
_arguments.Add("-device");
var deviceArgs = "";
// Use different network models based on virtualization type
if (nic.Model == "virtio-net-pci" && _virtualizationType == VirtualizationType.HyperV)
{
// Use e1000 for WHPX compatibility to avoid MSI issues
deviceArgs = $"e1000,netdev=net{i}";
}
else
{
deviceArgs = $"{nic.Model},netdev=net{i}";
}
if (!string.IsNullOrEmpty(nic.Mac))
{
deviceArgs += $",mac={nic.Mac}";
}
_arguments.Add(deviceArgs);
}
}
}
private void AddDisplayConfiguration()
{
var display = _config.Display;
_arguments.Add("-display");
// Use appropriate display backend based on OS
string displayType = display.Type;
if (OperatingSystem.IsMacOS())
{
// On macOS, GTK is not available, use cocoa instead
if (displayType == "gtk")
{
displayType = "cocoa";
}
}
// Handle VNC display type with proper argument
if (displayType == "vnc")
{
var vncPort = display.SpicePort > 0 ? display.SpicePort : 5900;
// QEMU VNC display numbering: vnc=0.0.0.0:0 means port 5900, vnc=0.0.0.0:1 means port 5901, etc.
// So for port 5900, we use display 0, for port 5901, we use display 1, etc.
var displayNumber = vncPort - 5900;
if (displayNumber < 0) displayNumber = 0; // Default to display 0 if port < 5900
displayType = $"vnc=0.0.0.0:{displayNumber}";
}
_arguments.Add(displayType);
// For X11 forwarding compatibility, use std VGA instead of virtio
// This avoids GLX/OpenGL issues when forwarding over X11
if (displayType.StartsWith("gtk") || displayType.StartsWith("x11"))
{
_arguments.Add("-vga");
_arguments.Add("std"); // Use standard VGA for better X11 compatibility
// Add software rendering options to avoid GLX issues
_arguments.Add("-device");
_arguments.Add("virtio-gpu-pci,edid=off"); // Use virtio-gpu without EDID
}
else if (displayType.StartsWith("vnc"))
{
// For VNC, use std VGA for better compatibility
_arguments.Add("-vga");
_arguments.Add("std");
}
else
{
_arguments.Add("-vga");
_arguments.Add(display.Vga);
}
if (display.EnableSpice)
{
_arguments.Add("-spice");
_arguments.Add($"port={display.SpicePort},addr=127.0.0.1,disable-ticketing=on");
}
}
private void AddBootConfiguration()
{
var boot = _config.Boot;
if (boot.Order.Count > 0)
{
_arguments.Add("-boot");
_arguments.Add($"order={string.Join("", boot.Order)}");
}
if (!string.IsNullOrEmpty(boot.Kernel))
{
_arguments.Add("-kernel");
_arguments.Add(boot.Kernel);
}
if (!string.IsNullOrEmpty(boot.Initrd))
{
_arguments.Add("-initrd");
_arguments.Add(boot.Initrd);
}
if (!string.IsNullOrEmpty(boot.Cmdline))
{
_arguments.Add("-append");
_arguments.Add(boot.Cmdline);
}
}
private void AddAdvancedConfiguration()
{
var advanced = _config.Advanced;
if (advanced.EnableAudio)
{
_arguments.Add("-device");
_arguments.Add("intel-hda");
_arguments.Add("-device");
_arguments.Add("hda-duplex");
}
if (advanced.EnableUsb)
{
_arguments.Add("-usb");
_arguments.Add("-device");
_arguments.Add("usb-tablet");
}
// Disable virtio devices for WHPX to avoid MSI issues
if (advanced.EnableBalloon && _virtualizationType != VirtualizationType.HyperV)
{
_arguments.Add("-device");
_arguments.Add("virtio-balloon-pci");
}
if (advanced.EnableVirtioRng && _virtualizationType != VirtualizationType.HyperV)
{
_arguments.Add("-device");
_arguments.Add("virtio-rng-pci");
}
if (advanced.EnableVirtioFs && _virtualizationType != VirtualizationType.HyperV)
{
_arguments.Add("-device");
_arguments.Add("virtio-fs-pci");
}
// Add shared folders (disabled for compatibility)
// Note: 9p filesystem support is not available in all QEMU builds
// For now, shared folders are disabled to ensure compatibility
if (advanced.SharedFolders.Any())
{
// Log that shared folders are disabled
Console.WriteLine($"Warning: Shared folders are disabled for compatibility. {advanced.SharedFolders.Count} folder(s) configured but not used.");
}
}
private void AddWHPXSpecificConfiguration()
{
// Add WHPX-specific configurations to avoid MSI issues
// Disable MSI for better WHPX compatibility
_arguments.Add("-global");
_arguments.Add("pcie-root-port.msi=off");
// Use legacy interrupt mode for better compatibility
_arguments.Add("-global");
_arguments.Add("pcie-root-port.msix=off");
// Add additional WHPX optimizations
_arguments.Add("-global");
_arguments.Add("pcie-root-port.ari=off");
// Add WHPX-specific optimizations
_arguments.Add("-rtc");
_arguments.Add("base=localtime");
// Memory allocation optimizations for WHPX
// Note: -mem-path is not needed for WHPX and can cause issues
// Memory preallocation is not available in this QEMU build
// Add additional WHPX optimizations to avoid exit code 4
_arguments.Add("-no-reboot");
_arguments.Add("-no-shutdown");
// Use simpler interrupt handling (removed KVM-specific option)
// Disable some features that might cause issues with WHPX
// _arguments.Add("-no-acpi"); // Commented out as it might cause issues
}
}