Explorar o código

Merge pull request #5919 from gnattu/main

fix: Add workarounds to prevent filesystem corruption on Apple Virtualization VMs.
osy hai 1 ano
pai
achega
8e84a1e4ed

+ 3 - 1
Configuration/UTMAppleConfiguration.swift

@@ -265,11 +265,13 @@ extension UTMAppleConfiguration {
         }
         }
         if !ignoringDrives {
         if !ignoringDrives {
             vzconfig.storageDevices = try drives.compactMap { drive in
             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
                     return nil
                 }
                 }
                 if #available(macOS 13, *), drive.isExternal {
                 if #available(macOS 13, *), drive.isExternal {
                     return VZUSBMassStorageDeviceConfiguration(attachment: attachment)
                     return VZUSBMassStorageDeviceConfiguration(attachment: attachment)
+                } else if #available(macOS 14, *), drive.isNvme, system.boot.operatingSystem == .linux {
+                    return VZNVMExpressControllerDeviceConfiguration(attachment: attachment)
                 } else {
                 } else {
                     return VZVirtioBlockDeviceConfiguration(attachment: attachment)
                     return VZVirtioBlockDeviceConfiguration(attachment: attachment)
                 }
                 }

+ 16 - 3
Configuration/UTMAppleConfigurationDrive.swift

@@ -25,6 +25,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive {
     var sizeMib: Int = 0
     var sizeMib: Int = 0
     var isReadOnly: Bool
     var isReadOnly: Bool
     var isExternal: Bool
     var isExternal: Bool
+    var isNvme: Bool
     var imageURL: URL?
     var imageURL: URL?
     var imageName: String?
     var imageName: String?
     
     
@@ -36,6 +37,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive {
     
     
     private enum CodingKeys: String, CodingKey {
     private enum CodingKeys: String, CodingKey {
         case isReadOnly = "ReadOnly"
         case isReadOnly = "ReadOnly"
+        case isNvme = "Nvme"
         case imageName = "ImageName"
         case imageName = "ImageName"
         case bookmark = "Bookmark" // legacy only
         case bookmark = "Bookmark" // legacy only
         case identifier = "Identifier"
         case identifier = "Identifier"
@@ -55,12 +57,14 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive {
         sizeMib = newSize
         sizeMib = newSize
         isReadOnly = false
         isReadOnly = false
         isExternal = 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.imageURL = url
         self.isReadOnly = isExternal
         self.isReadOnly = isExternal
         self.isExternal = isExternal
         self.isExternal = isExternal
+        self.isNvme = isNvme
     }
     }
     
     
     init(from decoder: Decoder) throws {
     init(from decoder: Decoder) throws {
@@ -83,6 +87,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive {
             isExternal = true
             isExternal = true
         }
         }
         isReadOnly = try container.decodeIfPresent(Bool.self, forKey: .isReadOnly) ?? isExternal
         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)
         id = try container.decode(String.self, forKey: .identifier)
     }
     }
     
     
@@ -92,12 +97,18 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive {
             try container.encodeIfPresent(imageName, forKey: .imageName)
             try container.encodeIfPresent(imageName, forKey: .imageName)
         }
         }
         try container.encode(isReadOnly, forKey: .isReadOnly)
         try container.encode(isReadOnly, forKey: .isReadOnly)
+        try container.encode(isNvme, forKey: .isNvme)
         try container.encode(id, forKey: .identifier)
         try container.encode(id, forKey: .identifier)
     }
     }
     
     
-    func vzDiskImage() throws -> VZDiskImageStorageDeviceAttachment? {
+    func vzDiskImage(useFsWorkAround: Bool = false) throws -> VZDiskImageStorageDeviceAttachment? {
         if let imageURL = imageURL {
         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: .full)
+            } else {
+                return try VZDiskImageStorageDeviceAttachment(url: imageURL, readOnly: isReadOnly)
+            }
         } else {
         } else {
             return nil
             return nil
         }
         }
@@ -107,6 +118,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive {
         imageName?.hash(into: &hasher)
         imageName?.hash(into: &hasher)
         sizeMib.hash(into: &hasher)
         sizeMib.hash(into: &hasher)
         isReadOnly.hash(into: &hasher)
         isReadOnly.hash(into: &hasher)
+        isNvme.hash(into: &hasher)
         isExternal.hash(into: &hasher)
         isExternal.hash(into: &hasher)
         id.hash(into: &hasher)
         id.hash(into: &hasher)
     }
     }
@@ -127,6 +139,7 @@ extension UTMAppleConfigurationDrive {
         sizeMib = oldDrive.sizeMib
         sizeMib = oldDrive.sizeMib
         isReadOnly = oldDrive.isReadOnly
         isReadOnly = oldDrive.isReadOnly
         isExternal = oldDrive.isExternal
         isExternal = oldDrive.isExternal
+        isNvme = false
         imageURL = oldDrive.imageURL
         imageURL = oldDrive.imageURL
     }
     }
 }
 }

+ 6 - 1
Platform/Shared/VMWizardState.swift

@@ -135,6 +135,7 @@ enum VMBootDevice: Int, Identifiable {
     @Published var sharingReadOnly: Bool = false
     @Published var sharingReadOnly: Bool = false
     @Published var name: String?
     @Published var name: String?
     @Published var isOpenSettingsAfterCreation: Bool = false
     @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, 
     /// SwiftUI BUG: on macOS 12, when VoiceOver is enabled and isBusy changes the disable state of a button being clicked, 
     var isNeverDisabledWorkaround: Bool {
     var isNeverDisabledWorkaround: Bool {
@@ -342,7 +343,11 @@ enum VMBootDevice: Int, Identifiable {
             }
             }
         }
         }
         if !isSkipDiskCreate {
         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 {
         if #available(macOS 12, *), let sharingDirectoryURL = sharingDirectoryURL {
             config.sharedDirectories = [UTMAppleConfigurationSharedDirectory(directoryURL: sharingDirectoryURL, isReadOnly: sharingReadOnly)]
             config.sharedDirectories = [UTMAppleConfigurationSharedDirectory(directoryURL: sharingDirectoryURL, isReadOnly: sharingReadOnly)]

+ 6 - 0
Platform/ja.lproj/Localizable.strings

@@ -1141,3 +1141,9 @@
 
 
 // UTMQemuMonitor.m
 // UTMQemuMonitor.m
 "Guest panic" = "ゲストがパニック状態に陥りました";
 "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 ゲストのみ利用可能です。このインターフェースは遅いですが、ファイルシステムエラーが発生する可能性が低いです。";
+

+ 6 - 0
Platform/macOS/VMConfigAppleDriveCreateView.swift

@@ -33,11 +33,17 @@ struct VMConfigAppleDriveCreateView: View {
                     if newValue {
                     if newValue {
                         config.sizeMib = 0
                         config.sizeMib = 0
                         config.isReadOnly = true
                         config.isReadOnly = true
+                        config.isNvme = false
                     } else {
                     } else {
                         config.sizeMib = 10240
                         config.sizeMib = 10240
                         config.isReadOnly = false
                         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 {
                 if !config.isExternal {
                     SizeTextField($config.sizeMib)
                     SizeTextField($config.sizeMib)
                 }
                 }

+ 6 - 0
Platform/macOS/VMConfigAppleDriveDetailsView.swift

@@ -28,6 +28,12 @@ struct VMConfigAppleDriveDetailsView: View {
             TextField("Name", text: .constant(config.imageURL?.lastPathComponent ?? NSLocalizedString("(New Drive)", comment: "VMConfigAppleDriveDetailsView")))
             TextField("Name", text: .constant(config.imageURL?.lastPathComponent ?? NSLocalizedString("(New Drive)", comment: "VMConfigAppleDriveDetailsView")))
                 .disabled(true)
                 .disabled(true)
             Toggle("Read Only?", isOn: $config.isReadOnly)
             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) {
             if #unavailable(macOS 12) {
                 Button {
                 Button {
                     requestDriveDelete = config
                     requestDriveDelete = config

+ 5 - 0
Platform/zh-HK.lproj/Localizable.strings

@@ -2297,3 +2297,8 @@
 
 
 /* No comment provided by engineer. */
 /* No comment provided by engineer. */
 "Zoom" = "縮放";
 "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 客戶機器。這個介面速度較慢,但較不容易遇到檔案系統錯誤。";

+ 5 - 0
Platform/zh-Hans.lproj/Localizable.strings

@@ -2297,3 +2297,8 @@
 
 
 /* No comment provided by engineer. */
 /* No comment provided by engineer. */
 "Zoom" = "缩放";
 "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 客户机。此接口速度较慢,但不太容易遇到文件系统错误。";

+ 5 - 0
Platform/zh-Hant.lproj/Localizable.strings

@@ -2002,3 +2002,8 @@
 /* No comment provided by engineer. */
 /* No comment provided by engineer. */
 "Zoom" = "縮放";
 "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 客戶機器。這個介面速度較慢,但較不容易遇到檔案系統錯誤。";
+