Skip to content

Commit

Permalink
settings: create and import raw images
Browse files Browse the repository at this point in the history
Resolves #3637
  • Loading branch information
osy committed Feb 25, 2022
1 parent 52fd7ac commit cfceb5d
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 9 deletions.
3 changes: 3 additions & 0 deletions Platform/Shared/VMConfigDriveCreateView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ struct VMConfigDriveCreateView: View {
.foregroundColor(.blue)
}).buttonStyle(.plain)
}
Toggle(isOn: $driveImage.isRawImage) {
Text("Raw Image")
}.help(Text("Advanced. If checked, a raw disk image is used. Raw disk image does not support snapshots and will not dynamically expand in size."))
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions Platform/Shared/VMDriveImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@
// limitations under the License.
//

import Combine

@available(iOS 14, macOS 11, *)
class VMDriveImage: ObservableObject {
@Published var size: Int = 10240
@Published var removable: Bool = false
@Published var imageTypeString: String? = UTMDiskImageType.disk.description
@Published var interface: String? = "none"
@Published var isRawImage: Bool = false
private var cancellable = [AnyCancellable]()

var imageType: UTMDiskImageType {
get {
Expand All @@ -31,10 +35,20 @@ class VMDriveImage: ObservableObject {
}
}

init() {
cancellable.append($interface.sink { newInterface in
guard let newInterface = newInterface else {
return
}
self.isRawImage = !UTMQemuConfiguration.shouldConvertQcow2(forInterface: newInterface)
})
}

func reset(forSystemTarget target: String?, architecture: String?, removable: Bool) {
self.removable = removable
self.imageType = removable ? .CD : .disk
self.interface = UTMQemuConfiguration.defaultDriveInterface(forTarget: target, architecture: architecture, type: imageType)
self.size = removable ? 0 : 10240
self.isRawImage = false
}
}
35 changes: 27 additions & 8 deletions Platform/UTMData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -264,13 +264,14 @@ class UTMData: ObservableObject {
return ProcessInfo.processInfo.globallyUniqueString
}

/// Generate a unique QCOW2 drive name for QEMU
/// Generate a unique drive name for QEMU
/// - Parameters:
/// - type: Image type
/// - ext: Image extension
/// - forConfig: UTM QEMU configuration that will hold this drive
/// - Returns: Unique name for a non-existing item in the .utm data path
private func newDefaultDrivePath(type: UTMDiskImageType, forConfig: UTMQemuConfiguration) -> String {
let nameForId = { (i: Int) in "\(type.description)-\(i).qcow2" }
private func newDefaultDrivePath(type: UTMDiskImageType, extension ext: String, forConfig: UTMQemuConfiguration) -> String {
let nameForId = { (i: Int) in "\(type.description)-\(i).\(ext)" }
for i in 0..<1000 {
let name = nameForId(i)
let file = forConfig.imagesPath.appendingPathComponent(name)
Expand Down Expand Up @@ -614,8 +615,9 @@ class UTMData: ObservableObject {
/// - config: QEMU configuration to add to
/// - imageType: Disk image type
/// - interface: Interface to add to
/// - raw: If false, convert to QCOW2
/// - copy: Make a copy of the file (if false, file will be moved)
func importDrive(_ drive: URL, for config: UTMQemuConfiguration, imageType: UTMDiskImageType, on interface: String, copy: Bool) async throws {
func importDrive(_ drive: URL, for config: UTMQemuConfiguration, imageType: UTMDiskImageType, on interface: String, raw: Bool, copy: Bool) async throws {
_ = drive.startAccessingSecurityScopedResource()
defer { drive.stopAccessingSecurityScopedResource() }

Expand All @@ -636,7 +638,7 @@ class UTMData: ObservableObject {
}
if copy {
#if os(macOS)
if UTMQemuConfiguration.shouldConvertQcow2(forInterface: interface) {
if !raw {
dstPath.deletePathExtension()
dstPath.appendPathExtension("qcow2")
path = dstPath.lastPathComponent
Expand All @@ -645,6 +647,7 @@ class UTMData: ObservableObject {
try fileManager.copyItem(at: drive, to: dstPath)
}
#else
assert(raw) // currently do not support QCOW2 conversion
try fileManager.copyItem(at: drive, to: dstPath)
#endif
} else {
Expand All @@ -665,7 +668,7 @@ class UTMData: ObservableObject {
} else {
interface = "none"
}
try await importDrive(drive, for: config, imageType: imageType, on: interface, copy: copy)
try await importDrive(drive, for: config, imageType: imageType, on: interface, raw: true, copy: copy)
}

/// Create a new QCOW2 disk image
Expand All @@ -680,7 +683,7 @@ class UTMData: ObservableObject {
guard drive.size > 0 else {
throw NSLocalizedString("Invalid drive size.", comment: "UTMData")
}
path = newDefaultDrivePath(type: drive.imageType, forConfig: config)
path = newDefaultDrivePath(type: drive.imageType, extension: drive.isRawImage ? "raw" : "qcow2", forConfig: config)
let imagesPath = config.imagesPath
let dstPath = imagesPath.appendingPathComponent(path)
if !fileManager.fileExists(atPath: imagesPath.path) {
Expand All @@ -689,7 +692,9 @@ class UTMData: ObservableObject {

// create drive
try await Task.detached {
if !GenerateDefaultQcow2File(dstPath as CFURL, drive.size) {
if drive.isRawImage {
try self.generateNewRawDrive(at: dstPath, sizeInMib: drive.size)
} else if !GenerateDefaultQcow2File(dstPath as CFURL, drive.size) {
throw NSLocalizedString("Disk creation failed.", comment: "UTMData")
}
}.value
Expand Down Expand Up @@ -748,6 +753,20 @@ class UTMData: ObservableObject {
}
}

/// Create a new raw disk image
/// - Parameters:
/// - url: Destination of the drive image
/// - sizeInMib: Size of the drive image in MiB
private func generateNewRawDrive(at url: URL, sizeInMib: Int) throws {
let bytesInMib = 1048576
guard fileManager.createFile(atPath: url.path, contents: nil, attributes: nil) else {
throw NSLocalizedString("Cannot create disk iamge.", comment: "UTMData")
}
let handle = try FileHandle(forWritingTo: url)
try handle.truncate(atOffset: UInt64(sizeInMib * bytesInMib))
try handle.close()
}

// MARK: - Other utility functions

/// In some regions, iOS will prompt the user for network access
Expand Down
2 changes: 1 addition & 1 deletion Platform/macOS/VMConfigDrivesButtons.swift
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ struct VMConfigDrivesButtons<Config: ObservableObject & UTMConfigurable>: View {
if await newQemuDrive.removable {
try await data.createDrive(newQemuDrive, for: qemuConfig, with: url)
} else {
try await data.importDrive(url, for: qemuConfig, imageType: newQemuDrive.imageType, on: newQemuDrive.interface!, copy: true)
try await data.importDrive(url, for: qemuConfig, imageType: newQemuDrive.imageType, on: newQemuDrive.interface!, raw: newQemuDrive.isRawImage, copy: true)
}
} else if let appleConfig = await config as? UTMAppleConfiguration {
let name = url.lastPathComponent
Expand Down

0 comments on commit cfceb5d

Please sign in to comment.