UTMQemuConfigurationQEMU.swift 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. //
  2. // Copyright © 2022 osy. All rights reserved.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. import Foundation
  17. /// Tweaks and advanced QEMU settings.
  18. struct UTMQemuConfigurationQEMU: Codable {
  19. /// Where to store the debug log. File may not exist yet. This property is not saved to file.
  20. var debugLogURL: URL?
  21. /// EFI variables if EFI boot is enabled. This property is not saved to file.
  22. var efiVarsURL: URL?
  23. /// TPM data file if TPM is enabled. This property is not saved to file.
  24. var tpmDataURL: URL?
  25. /// If true, write standard output to debug.log in the VM bundle.
  26. var hasDebugLog: Bool = false
  27. /// If true, use UEFI boot on supported architectures.
  28. var hasUefiBoot: Bool = false
  29. /// If true, create a virtio-rng device on supported targets.
  30. var hasRNGDevice: Bool = false
  31. /// If true, create a virtio-balloon device on supported targets.
  32. var hasBalloonDevice: Bool = false
  33. /// If true, create a vTPM device with an emulated backend.
  34. var hasTPMDevice: Bool = false
  35. /// If true, use HVF hypervisor instead of TCG emulation.
  36. var hasHypervisor: Bool = false
  37. /// If true, enable total store ordering.
  38. var hasTSO: Bool = false
  39. /// If true, attempt to sync RTC with the local time.
  40. var hasRTCLocalTime: Bool = false
  41. /// If true, emulate a PS/2 controller instead of relying on USB emulation.
  42. var hasPS2Controller: Bool = false
  43. /// QEMU machine property that overrides the default property defined by UTM.
  44. var machinePropertyOverride: String?
  45. /// Additional QEMU arguments.
  46. var additionalArguments: [QEMUArgument] = []
  47. /// If true, changes to the VM will not be committed to disk. Not saved.
  48. var isDisposable: Bool = false
  49. /// Set to true to request guest tools install. Not saved.
  50. var isGuestToolsInstallRequested: Bool = false
  51. /// Set to true to request UEFI variable reset. Not saved.
  52. var isUefiVariableResetRequested: Bool = false
  53. enum CodingKeys: String, CodingKey {
  54. case hasDebugLog = "DebugLog"
  55. case hasUefiBoot = "UEFIBoot"
  56. case hasRNGDevice = "RNGDevice"
  57. case hasBalloonDevice = "BalloonDevice"
  58. case hasTPMDevice = "TPMDevice"
  59. case hasHypervisor = "Hypervisor"
  60. case hasTSO = "TSO"
  61. case hasRTCLocalTime = "RTCLocalTime"
  62. case hasPS2Controller = "PS2Controller"
  63. case machinePropertyOverride = "MachinePropertyOverride"
  64. case additionalArguments = "AdditionalArguments"
  65. }
  66. init() {
  67. }
  68. init(from decoder: Decoder) throws {
  69. let values = try decoder.container(keyedBy: CodingKeys.self)
  70. hasDebugLog = try values.decode(Bool.self, forKey: .hasDebugLog)
  71. hasUefiBoot = try values.decode(Bool.self, forKey: .hasUefiBoot)
  72. hasRNGDevice = try values.decode(Bool.self, forKey: .hasRNGDevice)
  73. hasBalloonDevice = try values.decode(Bool.self, forKey: .hasBalloonDevice)
  74. hasTPMDevice = try values.decode(Bool.self, forKey: .hasTPMDevice)
  75. hasHypervisor = try values.decode(Bool.self, forKey: .hasHypervisor)
  76. hasTSO = try values.decodeIfPresent(Bool.self, forKey: .hasTSO) ?? false
  77. hasRTCLocalTime = try values.decode(Bool.self, forKey: .hasRTCLocalTime)
  78. hasPS2Controller = try values.decode(Bool.self, forKey: .hasPS2Controller)
  79. machinePropertyOverride = try values.decodeIfPresent(String.self, forKey: .machinePropertyOverride)
  80. additionalArguments = try values.decode([QEMUArgument].self, forKey: .additionalArguments)
  81. if let dataURL = decoder.userInfo[.dataURL] as? URL {
  82. debugLogURL = dataURL.appendingPathComponent(QEMUPackageFileName.debugLog.rawValue)
  83. efiVarsURL = dataURL.appendingPathComponent(QEMUPackageFileName.efiVariables.rawValue)
  84. tpmDataURL = dataURL.appendingPathComponent(QEMUPackageFileName.tpmData.rawValue)
  85. }
  86. }
  87. func encode(to encoder: Encoder) throws {
  88. var container = encoder.container(keyedBy: CodingKeys.self)
  89. try container.encode(hasDebugLog, forKey: .hasDebugLog)
  90. try container.encode(hasUefiBoot, forKey: .hasUefiBoot)
  91. try container.encode(hasRNGDevice, forKey: .hasRNGDevice)
  92. try container.encode(hasBalloonDevice, forKey: .hasBalloonDevice)
  93. try container.encode(hasTPMDevice, forKey: .hasTPMDevice)
  94. try container.encode(hasHypervisor, forKey: .hasHypervisor)
  95. try container.encode(hasTSO, forKey: .hasTSO)
  96. try container.encode(hasRTCLocalTime, forKey: .hasRTCLocalTime)
  97. try container.encode(hasPS2Controller, forKey: .hasPS2Controller)
  98. try container.encodeIfPresent(machinePropertyOverride, forKey: .machinePropertyOverride)
  99. try container.encode(additionalArguments, forKey: .additionalArguments)
  100. }
  101. }
  102. // MARK: - Default construction
  103. extension UTMQemuConfigurationQEMU {
  104. init(forArchitecture architecture: QEMUArchitecture, target: any QEMUTarget) {
  105. self.init()
  106. let rawTarget = target.rawValue
  107. if rawTarget.hasPrefix("pc") || rawTarget.hasPrefix("q35") {
  108. hasUefiBoot = true
  109. hasRNGDevice = true
  110. } else if (architecture == .arm || architecture == .aarch64) && (rawTarget.hasPrefix("virt-") || rawTarget == "virt") {
  111. hasUefiBoot = true
  112. hasRNGDevice = true
  113. }
  114. hasHypervisor = architecture.hasHypervisorSupport
  115. }
  116. }
  117. // MARK: - Conversion of old config format
  118. extension UTMQemuConfigurationQEMU {
  119. init(migrating oldConfig: UTMLegacyQemuConfiguration) {
  120. self.init()
  121. hasDebugLog = oldConfig.debugLogEnabled
  122. hasUefiBoot = oldConfig.systemBootUefi
  123. hasRNGDevice = oldConfig.systemRngEnabled
  124. hasHypervisor = oldConfig.useHypervisor
  125. hasRTCLocalTime = oldConfig.rtcUseLocalTime
  126. hasPS2Controller = oldConfig.forcePs2Controller
  127. machinePropertyOverride = oldConfig.systemMachineProperties
  128. if let oldAddArgs = oldConfig.systemArguments {
  129. additionalArguments = oldAddArgs.map({ QEMUArgument($0) })
  130. }
  131. debugLogURL = oldConfig.existingPath?.appendingPathComponent(QEMUPackageFileName.debugLog.rawValue)
  132. efiVarsURL = oldConfig.existingPath?.appendingPathComponent(UTMLegacyQemuConfiguration.diskImagesDirectory).appendingPathComponent(QEMUPackageFileName.efiVariables.rawValue)
  133. }
  134. }
  135. // MARK: - Saving data
  136. extension UTMQemuConfigurationQEMU {
  137. @MainActor mutating func saveData(to dataURL: URL, for system: UTMQemuConfigurationSystem) async throws -> [URL] {
  138. var existing: [URL] = []
  139. if hasUefiBoot {
  140. let fileManager = FileManager.default
  141. // save EFI variables
  142. let resourceURL = Bundle.main.url(forResource: "qemu", withExtension: nil)!
  143. let templateVarsURL: URL
  144. if system.architecture == .arm || system.architecture == .aarch64 {
  145. templateVarsURL = resourceURL.appendingPathComponent("edk2-arm-vars.fd")
  146. } else if system.architecture == .i386 || system.architecture == .x86_64 {
  147. templateVarsURL = resourceURL.appendingPathComponent("edk2-i386-vars.fd")
  148. } else {
  149. throw UTMQemuConfigurationError.uefiNotSupported
  150. }
  151. let varsURL = dataURL.appendingPathComponent(QEMUPackageFileName.efiVariables.rawValue)
  152. if !fileManager.fileExists(atPath: varsURL.path) {
  153. try await Task.detached {
  154. try FileManager.default.copyItem(at: templateVarsURL, to: varsURL)
  155. }.value
  156. }
  157. efiVarsURL = varsURL
  158. existing.append(varsURL)
  159. }
  160. let possibleTpmDataURL = dataURL.appendingPathComponent(QEMUPackageFileName.tpmData.rawValue)
  161. if hasTPMDevice {
  162. tpmDataURL = possibleTpmDataURL
  163. existing.append(tpmDataURL!)
  164. } else if FileManager.default.fileExists(atPath: possibleTpmDataURL.path) {
  165. existing.append(possibleTpmDataURL) // do not delete any existing TPM data
  166. }
  167. if hasDebugLog {
  168. let debugLogURL = dataURL.appendingPathComponent(QEMUPackageFileName.debugLog.rawValue)
  169. existing.append(debugLogURL)
  170. }
  171. return existing
  172. }
  173. }