UTM/Configuration/UTMAppleConfigurationBoot.s...

195 lines
7.9 KiB
Swift

//
// Copyright © 2022 osy. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import Virtualization
@available(iOS, unavailable, message: "Apple Virtualization not available on iOS")
@available(macOS 11, *)
struct UTMAppleConfigurationBoot: Codable {
enum OperatingSystem: String, CaseIterable, QEMUConstant {
case none = "None"
case linux = "Linux"
case macOS = "macOS"
var prettyValue: String {
switch self {
case .none: return NSLocalizedString("None", comment: "UTMAppleConfigurationBoot")
case .linux: return NSLocalizedString("Linux", comment: "UTMAppleConfigurationBoot")
case .macOS: return NSLocalizedString("macOS", comment: "UTMAppleConfigurationBoot")
}
}
}
var operatingSystem: OperatingSystem
var linuxKernelURL: URL?
var linuxCommandLine: String?
var linuxInitialRamdiskURL: URL?
var efiVariableStorageURL: URL?
var hasUefiBoot: Bool = false
/// IPSW for installing macOS. Not saved.
var macRecoveryIpswURL: URL?
private enum CodingKeys: String, CodingKey {
case operatingSystem = "OperatingSystem"
case linuxKernelPath = "LinuxKernelPath"
case linuxCommandLine = "LinuxCommandLine"
case linuxInitialRamdiskPath = "LinuxInitialRamdiskPath"
case efiVariableStoragePath = "EfiVariableStoragePath"
case hasUefiBoot = "UEFIBoot"
}
init(from decoder: Decoder) throws {
guard let dataURL = decoder.userInfo[.dataURL] as? URL else {
throw UTMConfigurationError.invalidDataURL
}
let container = try decoder.container(keyedBy: CodingKeys.self)
operatingSystem = try container.decode(OperatingSystem.self, forKey: .operatingSystem)
hasUefiBoot = try container.decodeIfPresent(Bool.self, forKey: .hasUefiBoot) ?? false
#if !arch(arm64)
if #available(macOS 12, *) {
} else {
guard operatingSystem != .macOS else {
throw UTMAppleConfigurationError.platformUnsupported
}
}
#endif
if let linuxKernelPath = try container.decodeIfPresent(String.self, forKey: .linuxKernelPath) {
linuxKernelURL = dataURL.appendingPathComponent(linuxKernelPath)
}
linuxCommandLine = try container.decodeIfPresent(String.self, forKey: .linuxCommandLine)
if let linuxInitialRamdiskPath = try container.decodeIfPresent(String.self, forKey: .linuxInitialRamdiskPath) {
linuxInitialRamdiskURL = dataURL.appendingPathComponent(linuxInitialRamdiskPath)
}
if let efiVariableStoragePath = try container.decodeIfPresent(String.self, forKey: .efiVariableStoragePath) {
efiVariableStorageURL = dataURL.appendingPathComponent(efiVariableStoragePath)
}
}
init(for operatingSystem: OperatingSystem, linuxKernelURL: URL? = nil) throws {
self.operatingSystem = operatingSystem
self.linuxKernelURL = linuxKernelURL
if operatingSystem == .linux && linuxKernelURL == nil {
self.hasUefiBoot = true
}
}
init(from linux: VZLinuxBootLoader) {
self.operatingSystem = .linux
self.linuxKernelURL = linux.kernelURL
self.linuxCommandLine = linux.commandLine
self.linuxInitialRamdiskURL = linux.initialRamdiskURL
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(operatingSystem, forKey: .operatingSystem)
try container.encode(hasUefiBoot, forKey: .hasUefiBoot)
if operatingSystem == .linux {
try container.encodeIfPresent(linuxKernelURL?.lastPathComponent, forKey: .linuxKernelPath)
try container.encodeIfPresent(linuxCommandLine, forKey: .linuxCommandLine)
try container.encodeIfPresent(linuxInitialRamdiskURL?.lastPathComponent, forKey: .linuxInitialRamdiskPath)
try container.encodeIfPresent(efiVariableStorageURL?.lastPathComponent, forKey: .efiVariableStoragePath)
}
}
func vzBootloader() -> VZBootLoader? {
switch operatingSystem {
case .none:
return nil
case .linux:
if #available(macOS 13, *), let efiVariableStorageURL = efiVariableStorageURL, hasUefiBoot {
let efi = VZEFIBootLoader()
efi.variableStore = VZEFIVariableStore(url: efiVariableStorageURL)
return efi
}
guard let linuxKernelURL = linuxKernelURL else {
return nil
}
let linux = VZLinuxBootLoader(kernelURL: linuxKernelURL)
linux.initialRamdiskURL = linuxInitialRamdiskURL
if let linuxCommandLine = linuxCommandLine {
linux.commandLine = linuxCommandLine
}
return linux
case .macOS:
#if arch(arm64)
if #available(macOS 12, *) {
return VZMacOSBootLoader()
}
#endif
return nil
}
}
}
// MARK: - Conversion of old config format
@available(iOS, unavailable, message: "Apple Virtualization not available on iOS")
@available(macOS 11, *)
extension UTMAppleConfigurationBoot {
init(migrating oldBoot: Bootloader) {
switch oldBoot.operatingSystem {
case .macOS: operatingSystem = .macOS
case .Linux: operatingSystem = .linux
}
linuxKernelURL = oldBoot.linuxKernelURL
linuxCommandLine = oldBoot.linuxCommandLine
linuxInitialRamdiskURL = oldBoot.linuxInitialRamdiskURL
}
}
// MARK: - Saving data
@available(iOS, unavailable, message: "Apple Virtualization not available on iOS")
@available(macOS 11, *)
extension UTMAppleConfigurationBoot {
@MainActor mutating func saveData(to dataURL: URL) async throws -> [URL] {
var urls = [URL]()
if operatingSystem == .linux && !hasUefiBoot {
guard let linuxKernelURL = linuxKernelURL else {
throw UTMAppleConfigurationError.kernelNotSpecified
}
let kernelUrl = try await UTMAppleConfiguration.copyItemIfChanged(from: linuxKernelURL, to: dataURL)
self.linuxKernelURL = kernelUrl
urls.append(kernelUrl)
if let linuxInitialRamdiskURL = linuxInitialRamdiskURL {
let ramdiskUrl = try await UTMAppleConfiguration.copyItemIfChanged(from: linuxInitialRamdiskURL, to: dataURL)
self.linuxInitialRamdiskURL = ramdiskUrl
urls.append(ramdiskUrl)
}
self.efiVariableStorageURL = nil
}
if hasUefiBoot {
guard #available(macOS 13, *) else {
throw UTMAppleConfigurationError.platformUnsupported
}
let fileManager = FileManager.default
let efiVariableStorageURL = dataURL.appendingPathComponent(QEMUPackageFileName.efiVariables.rawValue)
if !fileManager.fileExists(atPath: efiVariableStorageURL.path) {
_ = try VZEFIVariableStore(creatingVariableStoreAt: efiVariableStorageURL)
}
self.linuxKernelURL = nil
self.linuxInitialRamdiskURL = nil
self.linuxCommandLine = nil
self.efiVariableStorageURL = efiVariableStorageURL
urls.append(efiVariableStorageURL)
}
return urls
}
}