232 lines
7.9 KiB
Swift
232 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
|
|
|
|
/// Settings for single QEMU disk device
|
|
struct UTMQemuConfigurationDrive: UTMConfigurationDrive {
|
|
static let latestInterfaceVersion = 1
|
|
|
|
/// If not removable, this is the name of the file in the bundle.
|
|
var imageName: String?
|
|
|
|
/// Size of the image when creating a new image (in MiB). Not saved.
|
|
var sizeMib: Int = 0
|
|
|
|
/// If true, the drive image will be mounted as read-only.
|
|
var isReadOnly: Bool = false
|
|
|
|
/// If true, a bookmark is stored in the package.
|
|
var isExternal: Bool = false
|
|
|
|
/// If valid, will point to the actual location of the drive image. Not saved.
|
|
var imageURL: URL?
|
|
|
|
/// Unique identifier for this drive
|
|
private(set) var id: String = UUID().uuidString
|
|
|
|
/// Type of the image.
|
|
var imageType: QEMUDriveImageType = .none
|
|
|
|
/// Interface of the image (only valid when type is CD/Disk).
|
|
var interface: QEMUDriveInterface = .none
|
|
|
|
/// Interface version for backwards compatibility
|
|
var interfaceVersion: Int = Self.latestInterfaceVersion
|
|
|
|
/// If true, the created image will be raw format and not QCOW2. Not saved.
|
|
var isRawImage: Bool = false
|
|
|
|
/// If initialized, returns a default interface for an image type. Not saved.
|
|
var defaultInterfaceForImageType: ((QEMUDriveImageType) -> QEMUDriveInterface)?
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case imageName = "ImageName"
|
|
case imageType = "ImageType"
|
|
case interface = "Interface"
|
|
case interfaceVersion = "InterfaceVersion"
|
|
case identifier = "Identifier"
|
|
case isReadOnly = "ReadOnly"
|
|
}
|
|
|
|
init() {
|
|
}
|
|
|
|
init(from decoder: Decoder) throws {
|
|
guard let dataURL = decoder.userInfo[.dataURL] as? URL else {
|
|
throw UTMConfigurationError.invalidDataURL
|
|
}
|
|
let values = try decoder.container(keyedBy: CodingKeys.self)
|
|
if let imageName = try values.decodeIfPresent(String.self, forKey: .imageName) {
|
|
self.imageName = imageName
|
|
imageURL = dataURL.appendingPathComponent(imageName)
|
|
isExternal = false
|
|
} else {
|
|
isExternal = true
|
|
}
|
|
isReadOnly = try values.decodeIfPresent(Bool.self, forKey: .isReadOnly) ?? isExternal
|
|
imageType = try values.decode(QEMUDriveImageType.self, forKey: .imageType)
|
|
interface = try values.decode(QEMUDriveInterface.self, forKey: .interface)
|
|
interfaceVersion = try values.decodeIfPresent(Int.self, forKey: .interfaceVersion) ?? 0
|
|
id = try values.decode(String.self, forKey: .identifier)
|
|
}
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
if !isExternal {
|
|
try container.encodeIfPresent(imageURL?.lastPathComponent, forKey: .imageName)
|
|
}
|
|
try container.encode(isReadOnly, forKey: .isReadOnly)
|
|
try container.encode(imageType, forKey: .imageType)
|
|
if imageType == .cd || imageType == .disk {
|
|
try container.encode(interface, forKey: .interface)
|
|
} else {
|
|
try container.encode(QEMUDriveInterface.none, forKey: .interface)
|
|
}
|
|
try container.encode(interfaceVersion, forKey: .interfaceVersion)
|
|
try container.encode(id, forKey: .identifier)
|
|
}
|
|
|
|
func hash(into hasher: inout Hasher) {
|
|
imageName?.hash(into: &hasher)
|
|
sizeMib.hash(into: &hasher)
|
|
isReadOnly.hash(into: &hasher)
|
|
isExternal.hash(into: &hasher)
|
|
id.hash(into: &hasher)
|
|
imageType.hash(into: &hasher)
|
|
interface.hash(into: &hasher)
|
|
interfaceVersion.hash(into: &hasher)
|
|
isRawImage.hash(into: &hasher)
|
|
}
|
|
|
|
func clone() -> UTMQemuConfigurationDrive {
|
|
var cloned = self
|
|
cloned.id = UUID().uuidString
|
|
return cloned
|
|
}
|
|
}
|
|
|
|
// MARK: - Default interface
|
|
|
|
extension UTMQemuConfigurationDrive {
|
|
static func defaultInterface(forArchitecture architecture: QEMUArchitecture, target: any QEMUTarget, imageType: QEMUDriveImageType) -> QEMUDriveInterface {
|
|
let rawTarget = target.rawValue
|
|
if rawTarget.hasPrefix("virt-") || rawTarget == "virt" || rawTarget.hasPrefix("pseries") {
|
|
if imageType == .cd {
|
|
return .usb
|
|
} else {
|
|
return .virtio
|
|
}
|
|
} else if architecture == .sparc || architecture == .sparc64 {
|
|
return .scsi
|
|
} else {
|
|
return .ide
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Conversion of old config format
|
|
|
|
extension UTMQemuConfigurationDrive {
|
|
init(migrating oldConfig: UTMLegacyQemuConfiguration, at index: Int) {
|
|
self.init()
|
|
imageName = oldConfig.driveImagePath(for: index)
|
|
imageType = convertImageType(from: oldConfig.driveImageType(for: index))
|
|
interface = convertInterface(from: oldConfig.driveInterfaceType(for: index))
|
|
interfaceVersion = 0
|
|
isExternal = oldConfig.driveRemovable(for: index)
|
|
isReadOnly = isExternal
|
|
var oldId = oldConfig.driveName(for: index) ?? UUID().uuidString
|
|
if oldId.hasPrefix("drive") {
|
|
oldId.removeFirst(5)
|
|
}
|
|
if oldId.isEmpty {
|
|
oldId = UUID().uuidString
|
|
}
|
|
id = oldId
|
|
let dataURL = oldConfig.existingPath?.appendingPathComponent(QEMUPackageFileName.images.rawValue)
|
|
if let imageName = imageName {
|
|
imageURL = dataURL?.appendingPathComponent(imageName)
|
|
}
|
|
|
|
}
|
|
|
|
private func convertImageType(from type: UTMDiskImageType) -> QEMUDriveImageType {
|
|
switch type {
|
|
case .none:
|
|
return .none
|
|
case .disk:
|
|
return .disk
|
|
case .CD:
|
|
return .cd
|
|
case .BIOS:
|
|
return .bios
|
|
case .kernel:
|
|
return .linuxKernel
|
|
case .initrd:
|
|
return .linuxInitrd
|
|
case .DTB:
|
|
return .linuxDtb
|
|
case .max:
|
|
return .none
|
|
@unknown default:
|
|
return .none
|
|
}
|
|
}
|
|
|
|
private func convertInterface(from str: String?) -> QEMUDriveInterface {
|
|
if str == "ide" {
|
|
return .ide
|
|
} else if str == "scsi" {
|
|
return .scsi
|
|
} else if str == "sd" {
|
|
return .sd
|
|
} else if str == "mtd" {
|
|
return .mtd
|
|
} else if str == "floppy" {
|
|
return .floppy
|
|
} else if str == "pflash" {
|
|
return .pflash
|
|
} else if str == "virtio" {
|
|
return .virtio
|
|
} else if str == "nvme" {
|
|
return .nvme
|
|
} else if str == "usb" {
|
|
return .usb
|
|
} else {
|
|
return .none
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - New drive
|
|
|
|
extension UTMQemuConfigurationDrive {
|
|
init(forArchitecture architecture: QEMUArchitecture, target: any QEMUTarget, isExternal: Bool = false) {
|
|
self.isExternal = isExternal
|
|
self.imageType = isExternal ? .cd : .disk
|
|
self.isRawImage = false
|
|
self.imageName = nil
|
|
self.sizeMib = 10240
|
|
self.isReadOnly = isExternal
|
|
self.imageURL = nil
|
|
self.id = UUID().uuidString
|
|
self.defaultInterfaceForImageType = { Self.defaultInterface(forArchitecture: architecture, target: target, imageType: $0) }
|
|
self.interface = defaultInterfaceForImageType!(imageType)
|
|
self.interfaceVersion = Self.latestInterfaceVersion
|
|
}
|
|
}
|