Files
luma/Sources/LumaCore/VirtualMachines/Virtualization/VirtualizationLinuxImage.swift
Ole André Vadla Ravnås 0e933206fb Boot virtual machines from the target picker
Add a machine abstraction to LumaCore, with QEMU as its first
backend: templates mirroring frida-core's test guests, a QMP client,
and a display that maps QEMU's surface over D-Bus rather than
copying frames.

Leave readiness to the user: drive the guest, mark it ready, and come
back to that snapshot later. Keep the machine in the project, so
booting it again resumes where it was marked. Hand the stub to
addBareboneDevice, so it joins the picker beside the other devices.

Show the display while booting, and afterwards in a Machines panel,
which the side panel now models as an enum to share its slot.
2026-08-20 14:52:52 +02:00

99 lines
4.0 KiB
Swift

import Foundation
#if canImport(Virtualization)
import Virtualization
/// A Linux guest boots what it is handed -- a kernel, whatever ramdisk goes
/// with it, and a root filesystem if there is one -- so nothing is installed
/// and nothing is kept between boots.
@available(macOS 13, *)
struct VirtualizationLinuxImage {
let kernel: URL
let ramdisk: URL?
let commandLine: String
let disk: URL?
let symbols: URL?
let identity: URL
init(request: VirtualMachineLaunchRequest) throws {
guard let kernel = request.text(VirtualizationParameter.kernel), !kernel.isEmpty else {
throw VirtualMachineError.launchFailed(reason: "No kernel was chosen")
}
self.kernel = URL(fileURLWithPath: kernel)
let ramdisk = request.text(VirtualizationParameter.ramdisk) ?? ""
self.ramdisk = ramdisk.isEmpty ? nil : URL(fileURLWithPath: ramdisk)
let disk = request.text(VirtualizationParameter.disk) ?? ""
self.disk = disk.isEmpty ? nil : URL(fileURLWithPath: disk)
let symbols = request.text(VirtualizationParameter.symbols) ?? ""
self.symbols = symbols.isEmpty ? nil : URL(fileURLWithPath: symbols)
let commandLine = request.text(VirtualizationParameter.commandLine) ?? ""
self.commandLine = commandLine.isEmpty ? VirtualizationLinuxImage.defaultCommandLine : commandLine
self.identity = request.storageDirectory.appendingPathComponent("MachineIdentifier")
}
func makeConfiguration(memory: Int, processors: Int) throws -> VZVirtualMachineConfiguration {
let bootLoader = VZLinuxBootLoader(kernelURL: kernel)
bootLoader.initialRamdiskURL = ramdisk
bootLoader.commandLine = commandLine
let configuration = VZVirtualMachineConfiguration()
configuration.bootLoader = bootLoader
configuration.platform = makePlatform()
configuration.cpuCount = processors
configuration.memorySize = UInt64(memory) * 1024 * 1024
let screen = VZVirtioGraphicsDeviceConfiguration()
screen.scanouts = [VZVirtioGraphicsScanoutConfiguration(widthInPixels: 1920, heightInPixels: 1200)]
configuration.graphicsDevices = [screen]
if let disk {
configuration.storageDevices = [
VZVirtioBlockDeviceConfiguration(attachment: try VZDiskImageStorageDeviceAttachment(url: disk, readOnly: false))
]
}
let network = VZVirtioNetworkDeviceConfiguration()
network.attachment = VZNATNetworkDeviceAttachment()
configuration.networkDevices = [network]
let console = VZVirtioConsoleDeviceConfiguration()
let port = VZVirtioConsolePortConfiguration()
port.isConsole = true
console.ports[0] = port
configuration.consoleDevices = [console]
configuration.keyboards = [VZUSBKeyboardConfiguration()]
configuration.pointingDevices = [VZUSBScreenCoordinatePointingDeviceConfiguration()]
configuration.socketDevices = [VZVirtioSocketDeviceConfiguration()]
configuration.entropyDevices = [VZVirtioEntropyDeviceConfiguration()]
configuration.memoryBalloonDevices = [VZVirtioTraditionalMemoryBalloonDeviceConfiguration()]
return configuration
}
/// Saved state names the machine it came from, so the guest is given the
/// same identity every boot rather than a fresh one.
private func makePlatform() -> VZGenericPlatformConfiguration {
let platform = VZGenericPlatformConfiguration()
if let stored = try? Data(contentsOf: identity), let identifier = VZGenericMachineIdentifier(dataRepresentation: stored) {
platform.machineIdentifier = identifier
} else {
try? platform.machineIdentifier.dataRepresentation.write(to: identity)
}
return platform
}
/// The last console named is the one a shell lands on, so the screen is
/// put last: a guest told only about the serial port draws nothing.
private static let defaultCommandLine = "console=hvc0 console=tty0"
}
#endif