Skip to content

Commit

Permalink
Directory sharing support (#211)
Browse files Browse the repository at this point in the history
  • Loading branch information
edigaryev authored Aug 30, 2022
1 parent 17dcf94 commit 0cad2e4
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 10 deletions.
2 changes: 1 addition & 1 deletion Sources/tart/Commands/Create.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}

Expand Down
43 changes: 40 additions & 3 deletions Sources/tart/Commands/Run.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -56,7 +64,8 @@ struct Run: AsyncParsableCommand {
vm = try VM(
vmDir: vmDir,
withSoftnet: withSoftnet,
additionalDiskAttachments: additionalDiskAttachments()
additionalDiskAttachments: additionalDiskAttachments(),
directoryShares: directoryShares()
)

let vncImpl: VNC? = try {
Expand Down Expand Up @@ -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)
Expand Down
38 changes: 33 additions & 5 deletions Sources/tart/VM.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand All @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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
Expand All @@ -298,3 +320,9 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
sema.signal()
}
}

struct DirectoryShare {
let name: String
let path: URL
let readOnly: Bool
}
2 changes: 1 addition & 1 deletion Sources/tart/VMConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 0cad2e4

Please sign in to comment.