Skip to content

WIP : Run as snapshot for macos "Apple Virtualisation" #3792

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
62b6acf
Added .runAsSnapshot to UTMAppleConfiguration.swift
PicoCreator Mar 18, 2022
96df2a6
Adding runAsSnapshot toggle on the UI
PicoCreator Mar 19, 2022
7e5783b
Experimental "runAsSnapshot" for apple VM
PicoCreator Mar 19, 2022
fe07b9e
Fixing beginers swift errors
PicoCreator Mar 19, 2022
f2b20ef
Fixing of compilation errors (swift noob here)
PicoCreator Mar 19, 2022
ff2357b
more swift compilation fixes
PicoCreator Mar 19, 2022
cc8d6e3
more swift syntax fixes
PicoCreator Mar 19, 2022
5e48986
more swift tweaks
PicoCreator Mar 19, 2022
5cfdf7d
still trying =|
PicoCreator Mar 19, 2022
e31e008
fixing the let try (i should install the xcode)
PicoCreator Mar 19, 2022
d4b9380
more attempts at fixing the apple config
PicoCreator Mar 19, 2022
d7ce877
Still figuring out swift
PicoCreator Mar 19, 2022
9935034
Added comments for copyItem
PicoCreator Mar 19, 2022
70f594e
Merge branch 'utmapp:master' into runAsSnapshot-for-macos
PicoCreator Apr 7, 2022
eb20232
Made suggested change, for triggering cleanupDriveSnapShot when VM stops
PicoCreator Apr 7, 2022
4b1507b
renamed resetDriveSnapshot with setupDriveSnapshot
PicoCreator Apr 7, 2022
ff191aa
Standardise SnapShot to Snapshot
PicoCreator Apr 7, 2022
4a18aaa
Apply suggested swift code formatting
PicoCreator Apr 7, 2022
6cc2c6e
Additional swift formatting change
PicoCreator Apr 7, 2022
f7f2f70
commenting clarification for copyItem
PicoCreator Apr 7, 2022
5e65160
Adding system wide "isRunAsSnapshot"
PicoCreator Apr 7, 2022
24813eb
Systemwide --snapshot toggle
PicoCreator Apr 7, 2022
7112e26
Fixed wrong var ref
PicoCreator Apr 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 71 additions & 1 deletion Configuration/UTMAppleConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ final class UTMAppleConfiguration: UTMConfigurable, Codable, ObservableObject {

@Published var isSerialEnabled: Bool
@Published var isConsoleDisplay: Bool
@Published var isRunAsSnapshot: Bool

@available(macOS 12, *)
var isKeyboardEnabled: Bool {
Expand Down Expand Up @@ -337,6 +338,7 @@ final class UTMAppleConfiguration: UTMConfigurable, Codable, ObservableObject {
case isConsoleDisplay
case isKeyboardEnabled
case isPointingEnabled
case isRunAsSnapshot
}

init() {
Expand All @@ -357,6 +359,7 @@ final class UTMAppleConfiguration: UTMConfigurable, Codable, ObservableObject {
isAppleVirtualization = true
isSerialEnabled = false
isConsoleDisplay = false
isRunAsSnapshot = false
memorySize = 4 * 1024 * 1024 * 1024
cpuCount = 4
}
Expand Down Expand Up @@ -398,6 +401,7 @@ final class UTMAppleConfiguration: UTMConfigurable, Codable, ObservableObject {
isEntropyEnabled = try values.decode(Bool.self, forKey: .isEntropyEnabled)
isSerialEnabled = try values.decode(Bool.self, forKey: .isSerialEnabled)
isConsoleDisplay = try values.decode(Bool.self, forKey: .isConsoleDisplay)
isRunAsSnapshot = try values.decode(Bool.self, forKey: .isRunAsSnapshot)
name = try values.decode(String.self, forKey: .name)
architecture = try values.decode(String.self, forKey: .architecture)
icon = try values.decodeIfPresent(String.self, forKey: .icon)
Expand Down Expand Up @@ -435,6 +439,7 @@ final class UTMAppleConfiguration: UTMConfigurable, Codable, ObservableObject {
try container.encode(isEntropyEnabled, forKey: .isEntropyEnabled)
try container.encode(isSerialEnabled, forKey: .isSerialEnabled)
try container.encode(isConsoleDisplay, forKey: .isConsoleDisplay)
try container.encode(isRunAsSnapshot, forKey: .isRunAsSnapshot)
try container.encode(name, forKey: .name)
try container.encode(architecture, forKey: .architecture)
try container.encodeIfPresent(icon, forKey: .icon)
Expand Down Expand Up @@ -616,6 +621,29 @@ final class UTMAppleConfiguration: UTMConfigurable, Codable, ObservableObject {
}
return urls
}

/// Remove the snapshot URL image, this can be done as part of VM cleanup
func cleanupDriveSnapshot() throws {
for i in diskImages.indices {
try diskImages[i].cleanupDriveSnapshot()
}
}

/// Perform a snapshot clone of the current image URL to the snapshot URL
/// this is required for the snapshotURL image to "work"
func setupDriveSnapshot() throws {
for i in diskImages.indices {
// Apply --snapshot on all volumes if its configured systemwide
if self.isRunAsSnapshot {
diskImages[i].runAsSnapshot = true
}

// Setup the --snapshot on a per drive level
if diskImages[i].runAsSnapshot {
try diskImages[i].setupDriveSnapshot()
}
}
}
}

struct Bootloader: Codable {
Expand Down Expand Up @@ -864,13 +892,15 @@ struct DiskImage: Codable, Hashable, Identifiable {
var sizeMib: Int
var isReadOnly: Bool
var isExternal: Bool
var runAsSnapshot: Bool
var imageURL: URL?
private var uuid = UUID() // for identifiable

private enum CodingKeys: String, CodingKey {
case sizeMib
case isReadOnly
case isExternal
case runAsSnapshot
case imagePath
case imageBookmark
}
Expand All @@ -891,12 +921,14 @@ struct DiskImage: Codable, Hashable, Identifiable {
sizeMib = newSize
isReadOnly = false
isExternal = false
runAsSnapshot = false
}

init(importImage url: URL, isReadOnly: Bool = false, isExternal: Bool = false) {
init(importImage url: URL, isReadOnly: Bool = false, isExternal: Bool = false, runAsSnapshot: Bool = false) {
self.imageURL = url
self.isReadOnly = isReadOnly
self.isExternal = isExternal
self.runAsSnapshot = runAsSnapshot
if let attributes = try? url.resourceValues(forKeys: [.fileSizeKey]), let fileSize = attributes.fileSize {
sizeMib = fileSize / bytesInMib
} else {
Expand All @@ -912,6 +944,7 @@ struct DiskImage: Codable, Hashable, Identifiable {
sizeMib = try container.decode(Int.self, forKey: .sizeMib)
isReadOnly = try container.decode(Bool.self, forKey: .isReadOnly)
isExternal = try container.decode(Bool.self, forKey: .isExternal)
runAsSnapshot = try container.decode(Bool.self, forKey: .runAsSnapshot)
if !isExternal, let imagePath = try container.decodeIfPresent(String.self, forKey: .imagePath) {
imageURL = dataURL.appendingPathComponent(imagePath)
} else if let bookmark = try container.decodeIfPresent(Data.self, forKey: .imageBookmark) {
Expand All @@ -925,6 +958,7 @@ struct DiskImage: Codable, Hashable, Identifiable {
try container.encode(sizeMib, forKey: .sizeMib)
try container.encode(isReadOnly, forKey: .isReadOnly)
try container.encode(isExternal, forKey: .isExternal)
try container.encode(runAsSnapshot, forKey: .runAsSnapshot)
if !isExternal {
try container.encodeIfPresent(imageURL?.lastPathComponent, forKey: .imagePath)
} else {
Expand All @@ -941,7 +975,43 @@ struct DiskImage: Codable, Hashable, Identifiable {
}
}

/// Returns the snapshot equivalent URL for the current image
/// Does not actually prepare the snapshot (this is done via setupDriveSnapshot)
func snapshotURL() throws -> URL? {
return imageURL?.appendingPathComponent(".snapshot")
}

/// Remove the snapshot URL image, this can be done as part of VM cleanup
func cleanupDriveSnapshot() throws {
if let snapshotURL = try snapshotURL() {
// The file may not exists, if so nothing happens
try FileManager.default.removeItem(at: snapshotURL)
}
}

/// Perform a snapshot clone of the current image URL to the snapshot URL
/// this is required for the snapshotURL image to "work"
func setupDriveSnapshot() throws {
// Perform any needed cleanup first
try cleanupDriveSnapshot()

// and make a copy of the provided imageURL
if let snapshotURL = try snapshotURL(), let imageURL = imageURL {
// lets setup the snapshot file
// AFAICT this does a shallow copy on APFS drives
try FileManager.default.copyItem(at: imageURL, to: snapshotURL)
}
}

/// Return the VZDiskImageStorageDeviceAttachment using the snapshotURL if runAsSnapshot is enabled
/// else returns using the imageURL if its configured.
func vzDiskImage() throws -> VZDiskImageStorageDeviceAttachment? {
if runAsSnapshot, let snapshotURL = try snapshotURL() {
return try VZDiskImageStorageDeviceAttachment(url: snapshotURL, readOnly: isReadOnly)
} else {
return nil
}

if let imageURL = imageURL {
return try VZDiskImageStorageDeviceAttachment(url: imageURL, readOnly: isReadOnly)
} else {
Expand Down
10 changes: 10 additions & 0 deletions Managers/UTMAppleVirtualMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,12 @@ import Virtualization
}
}
}


// This perform any cleanup for the "--snapshot" feature,
// if it was initialized previously
try appleConfig.cleanupDriveSnapshot()

}

override func vmStop(force: Bool) async throws {
Expand Down Expand Up @@ -324,6 +330,10 @@ import Virtualization
fsConfig.share = self?.makeDirectoryShare(from: newShares)
}
}

// This perform any reset's needed for the "--snapshot" feature (if its in use)
try appleConfig.setupDriveSnapshot()

apple = VZVirtualMachine(configuration: appleConfig.apple, queue: vmQueue)
apple.delegate = self
}
Expand Down
1 change: 1 addition & 0 deletions Platform/macOS/VMConfigAppleDriveDetailsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ struct VMConfigAppleDriveDetailsView: View {
TextField("Name", text: .constant(diskImage.imageURL?.lastPathComponent ?? NSLocalizedString("(New Drive)", comment: "VMConfigAppleDriveDetailsView")))
.disabled(true)
Toggle("Read Only?", isOn: $diskImage.isReadOnly)
Toggle("Run using a snapshot? (similar to qemu --snapshot)", isOn: $diskImage.runAsSnapshot)
Button(action: onDelete) {
Label("Delete Drive", systemImage: "externaldrive.badge.minus")
.foregroundColor(.red)
Expand Down
3 changes: 3 additions & 0 deletions Platform/macOS/VMConfigAppleSystemView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ struct VMConfigAppleSystemView: View {
Toggle("Enable Keyboard", isOn: $config.isKeyboardEnabled)
Toggle("Enable Pointer", isOn: $config.isPointingEnabled)
}

// System wide --snapshot toggle, if enabled this would be set as "true" on all drives
Toggle("Enable 'Run using a snapshot' on all drives", isOn: $config.isRunAsSnapshot)
}
}
}
Expand Down