Directory sharing support (#211)

This commit is contained in:
Nikolay Edigaryev 2022-08-30 10:26:16 +04:00 committed by GitHub
parent 17dcf942c1
commit 0cad2e454c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 75 additions and 10 deletions

View File

@ -40,7 +40,7 @@ struct Create: AsyncParsableCommand {
if #available(macOS 13, *) {
_ = try await VM.linux(vmDir: tmpVMDir, diskSizeGB: diskSize)
} else {
throw UnsupportedOSError()
throw UnsupportedOSError("Linux VMs", "are")
}
}

View File

@ -37,13 +37,21 @@ struct Run: AsyncParsableCommand {
@Flag var withSoftnet: Bool = false
@Option(help: ArgumentHelp("""
Additional disk attachments with an optional read-only specifier\n(e.g. --disk=\"disk.bin\" --disk=\"disk.bin:ro\")
Additional disk attachments with an optional read-only specifier\n(e.g. --disk=\"disk.bin\" --disk=\"ubuntu.iso:ro\")
""", discussion: """
Learn how to create a disk image using Disk Utility here:
https://support.apple.com/en-gb/guide/disk-utility/dskutl11888/mac
"""))
""", valueName: "path[:ro]"))
var disk: [String] = []
@Option(help: ArgumentHelp("""
Additional directory shares with an optional read-only specifier\n(e.g. --dir=\"build:~/src/build\" --dir=\"sources:~/src/sources:ro\")
""", discussion: """
All shared directories are automatically mounted to "/Volumes/My Shared Files" directory on macOS,
while on Linux you have to do it manually: "mount -t virtiofs com.apple.virtio-fs.automount /mount/point".
""", valueName: "name:path[:ro]"))
var dir: [String] = []
func validate() throws {
if vnc && vncExperimental {
throw ValidationError("--vnc and --vnc-experimental are mutually exclusive")
@ -56,7 +64,8 @@ struct Run: AsyncParsableCommand {
vm = try VM(
vmDir: vmDir,
withSoftnet: withSoftnet,
additionalDiskAttachments: additionalDiskAttachments()
additionalDiskAttachments: additionalDiskAttachments(),
directoryShares: directoryShares()
)
let vncImpl: VNC? = try {
@ -135,6 +144,34 @@ struct Run: AsyncParsableCommand {
return result
}
func directoryShares() throws -> [DirectoryShare] {
var result: [DirectoryShare] = []
for rawDir in dir {
let splits = rawDir.split(maxSplits: 2) { $0 == ":" }
if splits.count < 2 {
throw ValidationError("invalid --dir syntax: should at least include name and path, colon-separated")
}
var readOnly: Bool = false
if splits.count == 3 {
if splits[2] == "ro" {
readOnly = true
} else {
throw ValidationError("invalid --dir syntax: optional read-only specifier can only be \"ro\"")
}
}
let (name, path) = (String(splits[0]), String(splits[1]))
result.append(DirectoryShare(name: name, path: URL(fileURLWithPath: path), readOnly: readOnly))
}
return result
}
private func runUI() {
let nsApp = NSApplication.shared
nsApp.setActivationPolicy(.regular)

View File

@ -11,7 +11,11 @@ struct DownloadFailed: Error {
}
struct UnsupportedOSError: Error, CustomStringConvertible {
private(set) var description: String = "error: Linux VMs are only supported on macOS 13.0 (Ventura) or newer"
let description: String
init(_ what: String, _ plural: String) {
description = "error: \(what) \(plural) only supported on macOS 13.0 (Ventura) or newer"
}
}
struct UnsupportedArchitectureError: Error {
@ -34,7 +38,8 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
init(vmDir: VMDirectory,
withSoftnet: Bool = false,
additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment] = []
additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment] = [],
directoryShares: [DirectoryShare] = []
) throws {
name = vmDir.name
config = try VMConfig.init(fromURL: vmDir.configURL)
@ -50,7 +55,8 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
let configuration = try Self.craftConfiguration(diskURL: vmDir.diskURL,
nvramURL: vmDir.nvramURL, vmConfig: config,
softnet: softnet, additionalDiskAttachments: additionalDiskAttachments)
softnet: softnet, additionalDiskAttachments: additionalDiskAttachments,
directoryShares: directoryShares)
virtualMachine = VZVirtualMachine(configuration: configuration)
super.init()
@ -149,7 +155,8 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
let configuration = try Self.craftConfiguration(diskURL: vmDir.diskURL, nvramURL: vmDir.nvramURL,
vmConfig: config, softnet: softnet,
additionalDiskAttachments: additionalDiskAttachments)
additionalDiskAttachments: additionalDiskAttachments,
directoryShares: [])
virtualMachine = VZVirtualMachine(configuration: configuration)
super.init()
@ -228,7 +235,8 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
nvramURL: URL,
vmConfig: VMConfig,
softnet: Softnet? = nil,
additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment]
additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment],
directoryShares: [DirectoryShare]
) throws -> VZVirtualMachineConfiguration {
let configuration = VZVirtualMachineConfiguration()
@ -278,6 +286,20 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
// Entropy
configuration.entropyDevices = [VZVirtioEntropyDeviceConfiguration()]
// Directory share
if #available(macOS 13, *) {
var directories: [String : VZSharedDirectory] = Dictionary()
directoryShares.forEach { directories[$0.name] = VZSharedDirectory(url: $0.path, readOnly: $0.readOnly) }
let automountTag = VZVirtioFileSystemDeviceConfiguration.macOSGuestAutomountTag
let sharingDevice = VZVirtioFileSystemDeviceConfiguration(tag: automountTag)
sharingDevice.share = VZMultipleDirectoryShare(directories: directories)
configuration.directorySharingDevices = [sharingDevice]
} else if !directoryShares.isEmpty {
throw UnsupportedOSError("directory sharing", "is")
}
try configuration.validate()
return configuration
@ -298,3 +320,9 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
sema.signal()
}
}
struct DirectoryShare {
let name: String
let path: URL
let readOnly: Bool
}

View File

@ -94,7 +94,7 @@ struct VMConfig: Codable {
if #available(macOS 13, *) {
platform = try Linux(from: decoder)
} else {
throw UnsupportedOSError()
throw UnsupportedOSError("Linux VMs", "are")
}
}
cpuCountMin = try container.decode(Int.self, forKey: .cpuCountMin)