这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
130 changes: 58 additions & 72 deletions Sources/tart/Commands/Run.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,35 @@ 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 "fsync":
self = .fsync
case "full":
self = .full
case "":
self = .full
default:
return nil
throw RuntimeError.VMConfigurationError("unsupported disk synchronization mode: \"\(description)\"")
}
}
}

public var description: String {
switch self {
case .none:
return "none"
case .fsync:
return "fsync"
case .full:
return "full"
@unknown default:
return "unknown"
extension VZDiskImageSynchronizationMode {
public init(_ description: String) throws {
switch description {
case "none":
self = .none
case "fsync":
self = .fsync
case "full":
self = .full
case "":
self = .full
default:
throw RuntimeError.VMConfigurationError("unsupported disk image synchronization mode: \"\(description)\"")
}
}
}
Expand Down Expand Up @@ -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\"."))
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(),
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -809,66 +817,44 @@ struct AdditionalDisk {
url: diskFileURL,
readOnly: diskReadOnly,
cachingMode: .automatic,
synchronizationMode: try Self.parseImageSyncMode(syncModeRaw)
synchronizationMode: try VZDiskImageSynchronizationMode(syncModeRaw)
)

return VZVirtioBlockDeviceConfiguration(attachment: diskImageAttachment)
}

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)\"")
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/tart/VM.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down