From 8fb6a4f4656bf0c12e6d09a30049c98f1a3ed4c0 Mon Sep 17 00:00:00 2001 From: gnattu Date: Thu, 23 Nov 2023 10:32:23 +0800 Subject: [PATCH 1/5] config(apple): use nvme as default disk interface on macOS 14+ A workaround for #4840 --- Configuration/UTMAppleConfiguration.swift | 2 ++ Configuration/UTMAppleConfigurationDrive.swift | 10 +++++++++- Platform/Shared/VMWizardState.swift | 12 +++++++++++- Platform/ja.lproj/Localizable.strings | 6 ++++++ Platform/macOS/VMConfigAppleDriveCreateView.swift | 6 ++++++ Platform/macOS/VMConfigAppleDriveDetailsView.swift | 6 ++++++ Platform/zh-HK.lproj/Localizable.strings | 4 ++++ Platform/zh-Hans.lproj/Localizable.strings | 5 +++++ Platform/zh-Hant.lproj/Localizable.strings | 5 +++++ 9 files changed, 54 insertions(+), 2 deletions(-) diff --git a/Configuration/UTMAppleConfiguration.swift b/Configuration/UTMAppleConfiguration.swift index 40fa78f3f..27a0ea3d3 100644 --- a/Configuration/UTMAppleConfiguration.swift +++ b/Configuration/UTMAppleConfiguration.swift @@ -270,6 +270,8 @@ extension UTMAppleConfiguration { } if #available(macOS 13, *), drive.isExternal { return VZUSBMassStorageDeviceConfiguration(attachment: attachment) + } else if #available(macOS 14, *), drive.isNvme, system.boot.operatingSystem == .linux { + return VZNVMExpressControllerDeviceConfiguration(attachment: attachment) } else { return VZVirtioBlockDeviceConfiguration(attachment: attachment) } diff --git a/Configuration/UTMAppleConfigurationDrive.swift b/Configuration/UTMAppleConfigurationDrive.swift index ef53dbbf6..4725f5220 100644 --- a/Configuration/UTMAppleConfigurationDrive.swift +++ b/Configuration/UTMAppleConfigurationDrive.swift @@ -25,6 +25,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive { var sizeMib: Int = 0 var isReadOnly: Bool var isExternal: Bool + var isNvme: Bool var imageURL: URL? var imageName: String? @@ -36,6 +37,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive { private enum CodingKeys: String, CodingKey { case isReadOnly = "ReadOnly" + case isNvme = "Nvme" case imageName = "ImageName" case bookmark = "Bookmark" // legacy only case identifier = "Identifier" @@ -55,12 +57,14 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive { sizeMib = newSize isReadOnly = false isExternal = false + isNvme = false } - init(existingURL url: URL?, isExternal: Bool = false) { + init(existingURL url: URL?, isExternal: Bool = false, isNvme: Bool = false) { self.imageURL = url self.isReadOnly = isExternal self.isExternal = isExternal + self.isNvme = isNvme } init(from decoder: Decoder) throws { @@ -83,6 +87,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive { isExternal = true } isReadOnly = try container.decodeIfPresent(Bool.self, forKey: .isReadOnly) ?? isExternal + isNvme = try container.decodeIfPresent(Bool.self, forKey: .isNvme) ?? false id = try container.decode(String.self, forKey: .identifier) } @@ -92,6 +97,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive { try container.encodeIfPresent(imageName, forKey: .imageName) } try container.encode(isReadOnly, forKey: .isReadOnly) + try container.encode(isNvme, forKey: .isNvme) try container.encode(id, forKey: .identifier) } @@ -107,6 +113,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive { imageName?.hash(into: &hasher) sizeMib.hash(into: &hasher) isReadOnly.hash(into: &hasher) + isNvme.hash(into: &hasher) isExternal.hash(into: &hasher) id.hash(into: &hasher) } @@ -127,6 +134,7 @@ extension UTMAppleConfigurationDrive { sizeMib = oldDrive.sizeMib isReadOnly = oldDrive.isReadOnly isExternal = oldDrive.isExternal + isNvme = false imageURL = oldDrive.imageURL } } diff --git a/Platform/Shared/VMWizardState.swift b/Platform/Shared/VMWizardState.swift index b4803cb00..7ab6e01f0 100644 --- a/Platform/Shared/VMWizardState.swift +++ b/Platform/Shared/VMWizardState.swift @@ -119,6 +119,7 @@ enum VMWizardOS: String, Identifiable { #if os(macOS) @Published var systemMemoryMib: Int = 4096 @Published var storageSizeGib: Int = 64 + @Published var useNvmeAsDiskInterface = false #else @Published var systemMemoryMib: Int = 512 @Published var storageSizeGib: Int = 8 @@ -251,6 +252,11 @@ enum VMWizardOS: String, Identifiable { if #available(macOS 12, *) { if operatingSystem != .Linux { nextPage = .summary // only support linux currently + } else { + if #available(macOS 14, *) { + // Use NVMe as the default disk interface to avoid filesystem corruption on macOS 14+, only available for Linux + useNvmeAsDiskInterface = true + } } } else { nextPage = .summary @@ -324,7 +330,11 @@ enum VMWizardOS: String, Identifiable { } } if !isSkipDiskCreate { - config.drives.append(UTMAppleConfigurationDrive(newSize: storageSizeGib * bytesInGib / bytesInMib)) + var newDisk = UTMAppleConfigurationDrive(newSize: storageSizeGib * bytesInGib / bytesInMib) + if #available(macOS 14, *), useNvmeAsDiskInterface { + newDisk.isNvme = true + } + config.drives.append(newDisk) } if #available(macOS 12, *), let sharingDirectoryURL = sharingDirectoryURL { config.sharedDirectories = [UTMAppleConfigurationSharedDirectory(directoryURL: sharingDirectoryURL, isReadOnly: sharingReadOnly)] diff --git a/Platform/ja.lproj/Localizable.strings b/Platform/ja.lproj/Localizable.strings index ef338056a..66aa61392 100644 --- a/Platform/ja.lproj/Localizable.strings +++ b/Platform/ja.lproj/Localizable.strings @@ -1041,3 +1041,9 @@ // UTMQemuMonitor.m "Guest panic" = "ゲストがパニック状態に陥りました"; + +/* VMConfigAppleDriveDetailsView + VMConfigAppleDriveCreateView*/ +"Use NVMe Interface" = "NVMe を使用します"; +"If checked, use NVMe instead of virtio as the disk interface, available on macOS 14+ for Linux guests only. This interface is slower but less likely to encounter filesystem errors." = "チェックされている場合、ディスクインターフェースとして virtio の代わりに NVMe を使用します。macOS 14+ では Linux ゲストのみ利用可能です。このインターフェースは遅いですが、ファイルシステムエラーが発生する可能性が低いです。"; + diff --git a/Platform/macOS/VMConfigAppleDriveCreateView.swift b/Platform/macOS/VMConfigAppleDriveCreateView.swift index 5d5244c74..74806d66b 100644 --- a/Platform/macOS/VMConfigAppleDriveCreateView.swift +++ b/Platform/macOS/VMConfigAppleDriveCreateView.swift @@ -33,11 +33,17 @@ struct VMConfigAppleDriveCreateView: View { if newValue { config.sizeMib = 0 config.isReadOnly = true + config.isNvme = false } else { config.sizeMib = 10240 config.isReadOnly = false } } + if #available(macOS 14, *), !config.isExternal { + Toggle(isOn: $config.isNvme.animation(), label: { + Text("Use NVMe Interface") + }).help("If checked, use NVMe instead of virtio as the disk interface, available on macOS 14+ for Linux guests only. This interface is slower but less likely to encounter filesystem errors.") + } if !config.isExternal { SizeTextField($config.sizeMib) } diff --git a/Platform/macOS/VMConfigAppleDriveDetailsView.swift b/Platform/macOS/VMConfigAppleDriveDetailsView.swift index b88cd0415..d96cfbbe1 100644 --- a/Platform/macOS/VMConfigAppleDriveDetailsView.swift +++ b/Platform/macOS/VMConfigAppleDriveDetailsView.swift @@ -28,6 +28,12 @@ struct VMConfigAppleDriveDetailsView: View { TextField("Name", text: .constant(config.imageURL?.lastPathComponent ?? NSLocalizedString("(New Drive)", comment: "VMConfigAppleDriveDetailsView"))) .disabled(true) Toggle("Read Only?", isOn: $config.isReadOnly) + if #available(macOS 14, *), !config.isExternal { + Toggle(isOn: $config.isNvme, + label: { + Text("Use NVMe Interface") + }).help("If checked, use NVMe instead of virtio as the disk interface, available on macOS 14+ for Linux guests only. This interface is slower but less likely to encounter filesystem errors.") + } if #unavailable(macOS 12) { Button { requestDriveDelete = config diff --git a/Platform/zh-HK.lproj/Localizable.strings b/Platform/zh-HK.lproj/Localizable.strings index f2bd35fc3..7dd027100 100644 --- a/Platform/zh-HK.lproj/Localizable.strings +++ b/Platform/zh-HK.lproj/Localizable.strings @@ -2016,3 +2016,7 @@ /* No comment provided by engineer. */ "Zoom" = "縮放"; +/* VMConfigAppleDriveDetailsView + VMConfigAppleDriveCreateView*/ +"Use NVMe Interface" = "使用 NVMe 磁碟介面"; +"If checked, use NVMe instead of virtio as the disk interface, available on macOS 14+ for Linux guests only. This interface is slower but less likely to encounter filesystem errors." = "如果勾選,將使用 NVMe 而非 virtio 作為磁碟介面,僅在 macOS 14+ 中適用於 Linux 客戶機器。這個介面速度較慢,但較不容易遇到檔案系統錯誤。"; diff --git a/Platform/zh-Hans.lproj/Localizable.strings b/Platform/zh-Hans.lproj/Localizable.strings index 875129212..9555e15d6 100644 --- a/Platform/zh-Hans.lproj/Localizable.strings +++ b/Platform/zh-Hans.lproj/Localizable.strings @@ -1225,6 +1225,7 @@ /* ContentView */ "Your version of iOS does not support running VMs while unmodified. You must either run UTM while jailbroken or with a remote debugger attached. See https://getutm.app/install/ for more details." = "你的 iOS 版本不支持在未经修改的情况下运行虚拟机,必须在越狱时运行 UTM,或者连接远程调试器。有关更多详细信息,请参阅 https://getutm.app/install/。"; +<<<<<<< HEAD // Additonal Strings (Unable to be extracted by Xcode) /* No comment provided by engineer. */ @@ -2016,3 +2017,7 @@ /* No comment provided by engineer. */ "Zoom" = "缩放"; +/* VMConfigAppleDriveDetailsView + VMConfigAppleDriveCreateView*/ +"Use NVMe Interface" = "使用 NVMe 磁盘接口"; +"If checked, use NVMe instead of virtio as the disk interface, available on macOS 14+ for Linux guests only. This interface is slower but less likely to encounter filesystem errors." = "如果选中,使用 NVMe 而不是 virtio 作为磁盘接口,仅适用于 macOS 14+ 上的 Linux 客户机。此接口速度较慢,但不太容易遇到文件系统错误。"; diff --git a/Platform/zh-Hant.lproj/Localizable.strings b/Platform/zh-Hant.lproj/Localizable.strings index ee2dd422b..8070b72bd 100644 --- a/Platform/zh-Hant.lproj/Localizable.strings +++ b/Platform/zh-Hant.lproj/Localizable.strings @@ -2002,3 +2002,8 @@ /* No comment provided by engineer. */ "Zoom" = "縮放"; +/* VMConfigAppleDriveDetailsView + VMConfigAppleDriveCreateView*/ +"Use NVMe Interface" = "使用 NVMe 磁碟介面"; +"If checked, use NVMe instead of virtio as the disk interface, available on macOS 14+ for Linux guests only. This interface is slower but less likely to encounter filesystem errors." = "如果選取,將使用 NVMe 而非 virtio 作為磁碟介面,僅在 macOS 14+ 中適用於 Linux 客戶機器。這個介面速度較慢,但較不容易遇到檔案系統錯誤。"; + From 781b06530c44ae7e93c8f419f796949662de3bf7 Mon Sep 17 00:00:00 2001 From: gnattu Date: Fri, 24 Nov 2023 15:48:40 +0800 Subject: [PATCH 2/5] config(apple): use cachingMode: .cached for virtio drive on Linux This also helps prevent filesystem corruption on Apple Virtualization and works for pre-Sonoma hosts. --- Configuration/UTMAppleConfiguration.swift | 2 +- Configuration/UTMAppleConfigurationDrive.swift | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Configuration/UTMAppleConfiguration.swift b/Configuration/UTMAppleConfiguration.swift index 27a0ea3d3..08c8c206a 100644 --- a/Configuration/UTMAppleConfiguration.swift +++ b/Configuration/UTMAppleConfiguration.swift @@ -265,7 +265,7 @@ extension UTMAppleConfiguration { } if !ignoringDrives { vzconfig.storageDevices = try drives.compactMap { drive in - guard let attachment = try drive.vzDiskImage() else { + guard let attachment = try drive.vzDiskImage(useFsWorkAround: system.boot.operatingSystem == .linux) else { return nil } if #available(macOS 13, *), drive.isExternal { diff --git a/Configuration/UTMAppleConfigurationDrive.swift b/Configuration/UTMAppleConfigurationDrive.swift index 4725f5220..9f902f207 100644 --- a/Configuration/UTMAppleConfigurationDrive.swift +++ b/Configuration/UTMAppleConfigurationDrive.swift @@ -101,9 +101,14 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive { try container.encode(id, forKey: .identifier) } - func vzDiskImage() throws -> VZDiskImageStorageDeviceAttachment? { + func vzDiskImage(useFsWorkAround: Bool = false) throws -> VZDiskImageStorageDeviceAttachment? { if let imageURL = imageURL { - return try VZDiskImageStorageDeviceAttachment(url: imageURL, readOnly: isReadOnly) + // Use cached caching mode for virtio drive to prevent fs corruption on linux when possible + if #available(macOS 12.0, *), !isNvme, useFsWorkAround { + return try VZDiskImageStorageDeviceAttachment(url: imageURL, readOnly: isReadOnly, cachingMode: .cached, synchronizationMode: .fsync) + } else { + return try VZDiskImageStorageDeviceAttachment(url: imageURL, readOnly: isReadOnly) + } } else { return nil } From fafadd77b8f0630dfd5d0cf04ad3221edbf3131c Mon Sep 17 00:00:00 2001 From: gnattu Date: Fri, 24 Nov 2023 18:43:35 +0800 Subject: [PATCH 3/5] config(apple): use .full for synchronizationMode as it is the framwork's default --- Configuration/UTMAppleConfigurationDrive.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configuration/UTMAppleConfigurationDrive.swift b/Configuration/UTMAppleConfigurationDrive.swift index 9f902f207..e14488c35 100644 --- a/Configuration/UTMAppleConfigurationDrive.swift +++ b/Configuration/UTMAppleConfigurationDrive.swift @@ -105,7 +105,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive { if let imageURL = imageURL { // Use cached caching mode for virtio drive to prevent fs corruption on linux when possible if #available(macOS 12.0, *), !isNvme, useFsWorkAround { - return try VZDiskImageStorageDeviceAttachment(url: imageURL, readOnly: isReadOnly, cachingMode: .cached, synchronizationMode: .fsync) + return try VZDiskImageStorageDeviceAttachment(url: imageURL, readOnly: isReadOnly, cachingMode: .cached, synchronizationMode: .full) } else { return try VZDiskImageStorageDeviceAttachment(url: imageURL, readOnly: isReadOnly) } From a142f248d4f28813be22f82c0c5463186eb7dac1 Mon Sep 17 00:00:00 2001 From: gnattu Date: Sun, 10 Dec 2023 21:51:51 +0800 Subject: [PATCH 4/5] fix: iOS build cannot find useNvmeAsDiskInterface if defined in macro --- Platform/Shared/VMWizardState.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Platform/Shared/VMWizardState.swift b/Platform/Shared/VMWizardState.swift index 7ab6e01f0..aa1b4702d 100644 --- a/Platform/Shared/VMWizardState.swift +++ b/Platform/Shared/VMWizardState.swift @@ -119,7 +119,6 @@ enum VMWizardOS: String, Identifiable { #if os(macOS) @Published var systemMemoryMib: Int = 4096 @Published var storageSizeGib: Int = 64 - @Published var useNvmeAsDiskInterface = false #else @Published var systemMemoryMib: Int = 512 @Published var storageSizeGib: Int = 8 @@ -130,6 +129,7 @@ enum VMWizardOS: String, Identifiable { @Published var sharingReadOnly: Bool = false @Published var name: String? @Published var isOpenSettingsAfterCreation: Bool = false + @Published var useNvmeAsDiskInterface = false /// SwiftUI BUG: on macOS 12, when VoiceOver is enabled and isBusy changes the disable state of a button being clicked, var isNeverDisabledWorkaround: Bool { From 5b84f4bd728689b5e01280f62be4ed34cf44b5ba Mon Sep 17 00:00:00 2001 From: gnattu Date: Sat, 30 Mar 2024 16:22:31 +0800 Subject: [PATCH 5/5] fix: prefer virtio with cached mode enabled --- Platform/Shared/VMWizardState.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Platform/Shared/VMWizardState.swift b/Platform/Shared/VMWizardState.swift index aa1b4702d..036814b71 100644 --- a/Platform/Shared/VMWizardState.swift +++ b/Platform/Shared/VMWizardState.swift @@ -252,11 +252,6 @@ enum VMWizardOS: String, Identifiable { if #available(macOS 12, *) { if operatingSystem != .Linux { nextPage = .summary // only support linux currently - } else { - if #available(macOS 14, *) { - // Use NVMe as the default disk interface to avoid filesystem corruption on macOS 14+, only available for Linux - useNvmeAsDiskInterface = true - } } } else { nextPage = .summary