Skip to content

Commit 8e84a1e

Browse files
authored
Merge pull request #5919 from gnattu/main
fix: Add workarounds to prevent filesystem corruption on Apple Virtualization VMs.
2 parents 9dc2e94 + e63b53a commit 8e84a1e

9 files changed

+58
-5
lines changed

Configuration/UTMAppleConfiguration.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -265,11 +265,13 @@ extension UTMAppleConfiguration {
265265
}
266266
if !ignoringDrives {
267267
vzconfig.storageDevices = try drives.compactMap { drive in
268-
guard let attachment = try drive.vzDiskImage() else {
268+
guard let attachment = try drive.vzDiskImage(useFsWorkAround: system.boot.operatingSystem == .linux) else {
269269
return nil
270270
}
271271
if #available(macOS 13, *), drive.isExternal {
272272
return VZUSBMassStorageDeviceConfiguration(attachment: attachment)
273+
} else if #available(macOS 14, *), drive.isNvme, system.boot.operatingSystem == .linux {
274+
return VZNVMExpressControllerDeviceConfiguration(attachment: attachment)
273275
} else {
274276
return VZVirtioBlockDeviceConfiguration(attachment: attachment)
275277
}

Configuration/UTMAppleConfigurationDrive.swift

+16-3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive {
2525
var sizeMib: Int = 0
2626
var isReadOnly: Bool
2727
var isExternal: Bool
28+
var isNvme: Bool
2829
var imageURL: URL?
2930
var imageName: String?
3031

@@ -36,6 +37,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive {
3637

3738
private enum CodingKeys: String, CodingKey {
3839
case isReadOnly = "ReadOnly"
40+
case isNvme = "Nvme"
3941
case imageName = "ImageName"
4042
case bookmark = "Bookmark" // legacy only
4143
case identifier = "Identifier"
@@ -55,12 +57,14 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive {
5557
sizeMib = newSize
5658
isReadOnly = false
5759
isExternal = false
60+
isNvme = false
5861
}
5962

60-
init(existingURL url: URL?, isExternal: Bool = false) {
63+
init(existingURL url: URL?, isExternal: Bool = false, isNvme: Bool = false) {
6164
self.imageURL = url
6265
self.isReadOnly = isExternal
6366
self.isExternal = isExternal
67+
self.isNvme = isNvme
6468
}
6569

6670
init(from decoder: Decoder) throws {
@@ -83,6 +87,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive {
8387
isExternal = true
8488
}
8589
isReadOnly = try container.decodeIfPresent(Bool.self, forKey: .isReadOnly) ?? isExternal
90+
isNvme = try container.decodeIfPresent(Bool.self, forKey: .isNvme) ?? false
8691
id = try container.decode(String.self, forKey: .identifier)
8792
}
8893

@@ -92,12 +97,18 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive {
9297
try container.encodeIfPresent(imageName, forKey: .imageName)
9398
}
9499
try container.encode(isReadOnly, forKey: .isReadOnly)
100+
try container.encode(isNvme, forKey: .isNvme)
95101
try container.encode(id, forKey: .identifier)
96102
}
97103

98-
func vzDiskImage() throws -> VZDiskImageStorageDeviceAttachment? {
104+
func vzDiskImage(useFsWorkAround: Bool = false) throws -> VZDiskImageStorageDeviceAttachment? {
99105
if let imageURL = imageURL {
100-
return try VZDiskImageStorageDeviceAttachment(url: imageURL, readOnly: isReadOnly)
106+
// Use cached caching mode for virtio drive to prevent fs corruption on linux when possible
107+
if #available(macOS 12.0, *), !isNvme, useFsWorkAround {
108+
return try VZDiskImageStorageDeviceAttachment(url: imageURL, readOnly: isReadOnly, cachingMode: .cached, synchronizationMode: .full)
109+
} else {
110+
return try VZDiskImageStorageDeviceAttachment(url: imageURL, readOnly: isReadOnly)
111+
}
101112
} else {
102113
return nil
103114
}
@@ -107,6 +118,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive {
107118
imageName?.hash(into: &hasher)
108119
sizeMib.hash(into: &hasher)
109120
isReadOnly.hash(into: &hasher)
121+
isNvme.hash(into: &hasher)
110122
isExternal.hash(into: &hasher)
111123
id.hash(into: &hasher)
112124
}
@@ -127,6 +139,7 @@ extension UTMAppleConfigurationDrive {
127139
sizeMib = oldDrive.sizeMib
128140
isReadOnly = oldDrive.isReadOnly
129141
isExternal = oldDrive.isExternal
142+
isNvme = false
130143
imageURL = oldDrive.imageURL
131144
}
132145
}

Platform/Shared/VMWizardState.swift

+6-1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ enum VMBootDevice: Int, Identifiable {
135135
@Published var sharingReadOnly: Bool = false
136136
@Published var name: String?
137137
@Published var isOpenSettingsAfterCreation: Bool = false
138+
@Published var useNvmeAsDiskInterface = false
138139

139140
/// SwiftUI BUG: on macOS 12, when VoiceOver is enabled and isBusy changes the disable state of a button being clicked,
140141
var isNeverDisabledWorkaround: Bool {
@@ -342,7 +343,11 @@ enum VMBootDevice: Int, Identifiable {
342343
}
343344
}
344345
if !isSkipDiskCreate {
345-
config.drives.append(UTMAppleConfigurationDrive(newSize: storageSizeGib * bytesInGib / bytesInMib))
346+
var newDisk = UTMAppleConfigurationDrive(newSize: storageSizeGib * bytesInGib / bytesInMib)
347+
if #available(macOS 14, *), useNvmeAsDiskInterface {
348+
newDisk.isNvme = true
349+
}
350+
config.drives.append(newDisk)
346351
}
347352
if #available(macOS 12, *), let sharingDirectoryURL = sharingDirectoryURL {
348353
config.sharedDirectories = [UTMAppleConfigurationSharedDirectory(directoryURL: sharingDirectoryURL, isReadOnly: sharingReadOnly)]

Platform/ja.lproj/Localizable.strings

+6
Original file line numberDiff line numberDiff line change
@@ -1141,3 +1141,9 @@
11411141

11421142
// UTMQemuMonitor.m
11431143
"Guest panic" = "ゲストがパニック状態に陥りました";
1144+
1145+
/* VMConfigAppleDriveDetailsView
1146+
VMConfigAppleDriveCreateView*/
1147+
"Use NVMe Interface" = "NVMe を使用します";
1148+
"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 ゲストのみ利用可能です。このインターフェースは遅いですが、ファイルシステムエラーが発生する可能性が低いです。";
1149+

Platform/macOS/VMConfigAppleDriveCreateView.swift

+6
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,17 @@ struct VMConfigAppleDriveCreateView: View {
3333
if newValue {
3434
config.sizeMib = 0
3535
config.isReadOnly = true
36+
config.isNvme = false
3637
} else {
3738
config.sizeMib = 10240
3839
config.isReadOnly = false
3940
}
4041
}
42+
if #available(macOS 14, *), !config.isExternal {
43+
Toggle(isOn: $config.isNvme.animation(), label: {
44+
Text("Use NVMe Interface")
45+
}).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.")
46+
}
4147
if !config.isExternal {
4248
SizeTextField($config.sizeMib)
4349
}

Platform/macOS/VMConfigAppleDriveDetailsView.swift

+6
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ struct VMConfigAppleDriveDetailsView: View {
2828
TextField("Name", text: .constant(config.imageURL?.lastPathComponent ?? NSLocalizedString("(New Drive)", comment: "VMConfigAppleDriveDetailsView")))
2929
.disabled(true)
3030
Toggle("Read Only?", isOn: $config.isReadOnly)
31+
if #available(macOS 14, *), !config.isExternal {
32+
Toggle(isOn: $config.isNvme,
33+
label: {
34+
Text("Use NVMe Interface")
35+
}).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.")
36+
}
3137
if #unavailable(macOS 12) {
3238
Button {
3339
requestDriveDelete = config

Platform/zh-HK.lproj/Localizable.strings

+5
Original file line numberDiff line numberDiff line change
@@ -2297,3 +2297,8 @@
22972297

22982298
/* No comment provided by engineer. */
22992299
"Zoom" = "縮放";
2300+
2301+
/* VMConfigAppleDriveDetailsView
2302+
VMConfigAppleDriveCreateView*/
2303+
"Use NVMe Interface" = "使用 NVMe 磁碟介面";
2304+
"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 客戶機器。這個介面速度較慢,但較不容易遇到檔案系統錯誤。";

Platform/zh-Hans.lproj/Localizable.strings

+5
Original file line numberDiff line numberDiff line change
@@ -2297,3 +2297,8 @@
22972297

22982298
/* No comment provided by engineer. */
22992299
"Zoom" = "缩放";
2300+
2301+
/* VMConfigAppleDriveDetailsView
2302+
VMConfigAppleDriveCreateView*/
2303+
"Use NVMe Interface" = "使用 NVMe 磁盘接口";
2304+
"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 客户机。此接口速度较慢,但不太容易遇到文件系统错误。";

Platform/zh-Hant.lproj/Localizable.strings

+5
Original file line numberDiff line numberDiff line change
@@ -2002,3 +2002,8 @@
20022002
/* No comment provided by engineer. */
20032003
"Zoom" = "縮放";
20042004

2005+
/* VMConfigAppleDriveDetailsView
2006+
VMConfigAppleDriveCreateView*/
2007+
"Use NVMe Interface" = "使用 NVMe 磁碟介面";
2008+
"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 客戶機器。這個介面速度較慢,但較不容易遇到檔案系統錯誤。";
2009+

0 commit comments

Comments
 (0)