tart run: replace --sync with --root-disk-opts (#879)

* VZDiskImageSynchronizationMode's "description" field is a dead code

* Re-use the VZDiskImageSynchronizationMode extension

* tart run: replace --sync with --root-disk-opts

* VM: support root disk synchronization mode on macOS
This commit is contained in:
Nikolay Edigaryev 2024-08-05 15:17:58 +04:00 committed by GitHub
parent 5f2199ef3e
commit 3892cdb00d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 60 additions and 74 deletions

View File

@ -12,8 +12,24 @@ var vm: VM?
struct IPNotFound: Error {
}
extension VZDiskImageSynchronizationMode : LosslessStringConvertible {
public init?(_ description: String) {
@available(macOS 14, *)
extension VZDiskSynchronizationMode {
public init(_ description: String) throws {
switch description {
case "none":
self = .none
case "full":
self = .full
case "":
self = .full
default:
throw RuntimeError.VMConfigurationError("unsupported disk synchronization mode: \"\(description)\"")
}
}
}
extension VZDiskImageSynchronizationMode {
public init(_ description: String) throws {
switch description {
case "none":
self = .none
@ -21,21 +37,10 @@ extension VZDiskImageSynchronizationMode : LosslessStringConvertible {
self = .fsync
case "full":
self = .full
case "":
self = .full
default:
return nil
}
}
public var description: String {
switch self {
case .none:
return "none"
case .fsync:
return "fsync"
case .full:
return "full"
@unknown default:
return "unknown"
throw RuntimeError.VMConfigurationError("unsupported disk image synchronization mode: \"\(description)\"")
}
}
}
@ -168,13 +173,19 @@ struct Run: AsyncParsableCommand {
@Flag(help: ArgumentHelp("Restrict network access to the host-only network"))
var netHost: Bool = false
@Option(help: ArgumentHelp("Set the root disk synchronization mode (Linux images only). Possible values: none, fsync, full.",
@Option(help: ArgumentHelp("Set the root disk options (e.g. --root-disk-opts=\"ro\" or --root-disk-opts=\"sync=none\")",
discussion: """
'full' synchronizes data with the drive and ensures that it is written to permanent storage.
'fsync' synchronizes data with the drive but doesn't guarantee that it is written to permanent storage.
'none' doesn't synchronize data with the drive.
"""))
var sync: String = "full"
Options are comma-separated and are as follows:
* ro attach the root disk in read-only mode instead of the default read-write (e.g. --root-disk-opts="ro")
* sync=none disable data synchronization with the permanent storage to increase performance at the cost of a higher chance of data loss (e.g. --root-disk-opts="sync=none")
* sync=fsync enable data synchronization with the permanent storage, but don't ensure that it was actually written (e.g. --root-disk-opts="sync=fsync")
* sync=full enable data synchronization with the permanent storage and ensure that it was actually written (e.g. --root-disk-opts="sync=full")
""", valueName: "options"))
var rootDiskOpts: String = ""
#if arch(arm64)
@Flag(help: ArgumentHelp("Disables audio and entropy devices and switches to only Mac-specific input devices.", discussion: "Useful for running a VM that can be suspended via \"tart suspend\"."))
@ -231,10 +242,6 @@ struct Run: AsyncParsableCommand {
throw ValidationError("Seems you have a disk targeting x86 architecture (hence amd64 in the name). Please use an 'arm64' version of the disk.")
}
}
if(VZDiskImageSynchronizationMode(sync) == nil) {
throw ValidationError("Invalid disk synchronization mode: \(sync)")
}
}
@MainActor
@ -279,6 +286,9 @@ struct Run: AsyncParsableCommand {
serialPorts.append(createSerialPortConfiguration(tty_read!, tty_write!))
}
// Parse root disk options
let diskOptions = DiskOptions(rootDiskOpts)
vm = try VM(
vmDir: vmDir,
network: userSpecifiedNetwork(vmDir: vmDir) ?? NetworkShared(),
@ -288,7 +298,7 @@ struct Run: AsyncParsableCommand {
suspendable: suspendable,
audio: !noAudio,
clipboard: !noClipboard,
sync: VZDiskImageSynchronizationMode(sync) ?? .full
sync: VZDiskImageSynchronizationMode(diskOptions.syncModeRaw)
)
let vncImpl: VNC? = try {
@ -741,13 +751,11 @@ struct AdditionalDisk {
throw UnsupportedOSError("attaching Network Block Devices", "are")
}
let syncMode = try parseSyncMode(syncModeRaw)
let nbdAttachment = try VZNetworkBlockDeviceStorageDeviceAttachment(
url: diskURL!,
timeout: 30,
isForcedReadOnly: diskReadOnly,
synchronizationMode: syncMode
synchronizationMode: try VZDiskSynchronizationMode(syncModeRaw)
)
return VZVirtioBlockDeviceConfiguration(attachment: nbdAttachment)
@ -775,7 +783,7 @@ struct AdditionalDisk {
}
let blockAttachment = try VZDiskBlockDeviceStorageDeviceAttachment(fileHandle: FileHandle(fileDescriptor: fd, closeOnDealloc: true),
readOnly: diskReadOnly, synchronizationMode: try parseSyncMode(syncModeRaw))
readOnly: diskReadOnly, synchronizationMode: try VZDiskSynchronizationMode(syncModeRaw))
return VZVirtioBlockDeviceConfiguration(attachment: blockAttachment)
}
@ -809,7 +817,7 @@ struct AdditionalDisk {
url: diskFileURL,
readOnly: diskReadOnly,
cachingMode: .automatic,
synchronizationMode: try Self.parseImageSyncMode(syncModeRaw)
synchronizationMode: try VZDiskImageSynchronizationMode(syncModeRaw)
)
return VZVirtioBlockDeviceConfiguration(attachment: diskImageAttachment)
@ -817,58 +825,36 @@ struct AdditionalDisk {
static func parseOptions(_ parseFrom: String) -> (String, Bool, String) {
var arguments = parseFrom.split(separator: ":")
let options = arguments.last!.split(separator: ",")
var readOnly: Bool = false
var syncModeRaw: String = ""
let options = DiskOptions(String(arguments.last!))
if options.foundAtLeastOneOption {
arguments.removeLast()
}
var foundAtLeastOneOption: Bool = false
return (arguments.joined(separator: ":"), options.readOnly, options.syncModeRaw)
}
}
struct DiskOptions {
var readOnly: Bool = false
var syncModeRaw: String = ""
var foundAtLeastOneOption: Bool = false
init(_ parseFrom: String) {
let options = parseFrom.split(separator: ",")
for option in options {
switch true {
case option == "ro":
readOnly = true
foundAtLeastOneOption = true
self.readOnly = true
self.foundAtLeastOneOption = true
case option.hasPrefix("sync="):
syncModeRaw = String(option.dropFirst("sync=".count))
foundAtLeastOneOption = true
self.syncModeRaw = String(option.dropFirst("sync=".count))
self.foundAtLeastOneOption = true
default:
continue
}
}
if foundAtLeastOneOption {
arguments.removeLast()
}
return (arguments.joined(separator: ":"), readOnly, syncModeRaw)
}
@available(macOS 14, *)
static func parseSyncMode(_ parseFrom: String) throws -> VZDiskSynchronizationMode {
switch parseFrom {
case "none":
return .none
case "full":
return .full
case "":
return .full
default:
throw RuntimeError.VMConfigurationError("unsupported disk synchronization mode: \"\(parseFrom)\"")
}
}
static func parseImageSyncMode(_ parseFrom: String) throws -> VZDiskImageSynchronizationMode {
switch parseFrom {
case "none":
return .none
case "full":
return .full
case "":
return .full
default:
throw RuntimeError.VMConfigurationError("unsupported disk image synchronization mode: \"\(parseFrom)\"")
}
}
}

View File

@ -354,7 +354,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
let attachment: VZDiskImageStorageDeviceAttachment = vmConfig.os == .linux ?
// Use "cached" caching mode for virtio drive to prevent fs corruption on linux
try VZDiskImageStorageDeviceAttachment(url: diskURL, readOnly: false, cachingMode: .cached, synchronizationMode: sync) :
try VZDiskImageStorageDeviceAttachment(url: diskURL, readOnly: false)
try VZDiskImageStorageDeviceAttachment(url: diskURL, readOnly: false, cachingMode: .automatic, synchronizationMode: sync)
var device: VZStorageDeviceConfiguration
if #available(macOS 14, *), vmConfig.os == .linux {