mirror of https://github.com/cirruslabs/tart.git
Support block devices on Sonoma (#611)
* Support block devices on Sonoma * Updated docs * Removed unused error
This commit is contained in:
parent
c9e49ceb39
commit
50ce44c3eb
|
|
@ -1,5 +1,6 @@
|
|||
import ArgumentParser
|
||||
import Cocoa
|
||||
import Darwin
|
||||
import Dispatch
|
||||
import SwiftUI
|
||||
import Virtualization
|
||||
|
|
@ -51,10 +52,17 @@ struct Run: AsyncParsableCommand {
|
|||
var vncExperimental: Bool = false
|
||||
|
||||
@Option(help: ArgumentHelp("""
|
||||
Additional disk attachments with an optional read-only specifier\n(e.g. --disk=\"disk.bin\" --disk=\"ubuntu.iso:ro\")
|
||||
Additional disk attachments with an optional read-only specifier\n(e.g. --disk=\"disk.bin\" --disk=\"ubuntu.iso:ro\" --disk=\"/dev/disk0\")
|
||||
""", discussion: """
|
||||
Can be either a disk image file or a block device like a local SSD on AWS EC2 Mac instances.
|
||||
|
||||
Learn how to create a disk image using Disk Utility here:
|
||||
https://support.apple.com/en-gb/guide/disk-utility/dskutl11888/mac
|
||||
|
||||
To work with block devices 'tart' binary must be executed as root which affects locating Tart VMs.
|
||||
To workaround this issue pass TART_HOME explicitly:
|
||||
|
||||
sudo TART_HOME="$HOME/.tart" tart run sonoma --disk=/dev/disk0
|
||||
""", valueName: "path[:ro]"))
|
||||
var disk: [String] = []
|
||||
|
||||
|
|
@ -146,20 +154,6 @@ struct Run: AsyncParsableCommand {
|
|||
|
||||
let additionalDiskAttachments = try additionalDiskAttachments()
|
||||
|
||||
// Error out if the disk is locked by the host (e.g. it was mounted in Finder),
|
||||
// see https://github.com/cirruslabs/tart/issues/323 for more details.
|
||||
for additionalDiskAttachment in additionalDiskAttachments {
|
||||
// Read-only attachments do not seem to acquire the lock
|
||||
if additionalDiskAttachment.isReadOnly {
|
||||
continue
|
||||
}
|
||||
|
||||
if try !FileLock(lockURL: additionalDiskAttachment.url).trylock() {
|
||||
throw RuntimeError.DiskAlreadyInUse("disk \(additionalDiskAttachment.url.path) seems to be already in use, "
|
||||
+ "unmount it first in Finder")
|
||||
}
|
||||
}
|
||||
|
||||
var serialPorts: [VZSerialPortConfiguration] = []
|
||||
if serial {
|
||||
let tty_fd = createPTY()
|
||||
|
|
@ -181,7 +175,7 @@ struct Run: AsyncParsableCommand {
|
|||
vm = try VM(
|
||||
vmDir: vmDir,
|
||||
network: userSpecifiedNetwork(vmDir: vmDir) ?? NetworkShared(),
|
||||
additionalDiskAttachments: additionalDiskAttachments,
|
||||
additionalStorageDevices: additionalDiskAttachments,
|
||||
directorySharingDevices: directoryShares() + rosettaDirectoryShare(),
|
||||
serialPorts: serialPorts,
|
||||
suspendable: suspendable
|
||||
|
|
@ -361,22 +355,43 @@ struct Run: AsyncParsableCommand {
|
|||
}
|
||||
}
|
||||
|
||||
func additionalDiskAttachments() throws -> [VZDiskImageStorageDeviceAttachment] {
|
||||
var result: [VZDiskImageStorageDeviceAttachment] = []
|
||||
func additionalDiskAttachments() throws -> [VZStorageDeviceConfiguration] {
|
||||
var result: [VZStorageDeviceConfiguration] = []
|
||||
let readOnlySuffix = ":ro"
|
||||
let expandedDiskPaths = disk.map { NSString(string:$0).expandingTildeInPath }
|
||||
|
||||
for rawDisk in expandedDiskPaths {
|
||||
if rawDisk.hasSuffix(readOnlySuffix) {
|
||||
result.append(try VZDiskImageStorageDeviceAttachment(
|
||||
url: URL(fileURLWithPath: String(rawDisk.prefix(rawDisk.count - readOnlySuffix.count))),
|
||||
readOnly: true
|
||||
))
|
||||
let diskReadOnly = rawDisk.hasSuffix(readOnlySuffix)
|
||||
let diskPath = diskReadOnly ? String(rawDisk.prefix(rawDisk.count - readOnlySuffix.count)) : rawDisk
|
||||
let diskURL = URL(fileURLWithPath: diskPath)
|
||||
|
||||
// check if `diskPath` is a block device or a directory
|
||||
if pathHasMode(diskPath, mode: S_IFBLK) || pathHasMode(diskPath, mode: S_IFDIR) {
|
||||
print("Using block device\n")
|
||||
guard #available(macOS 14, *) else {
|
||||
throw UnsupportedOSError("attaching block devices", "are")
|
||||
}
|
||||
let fileHandle = FileHandle(forUpdatingAtPath: diskPath)
|
||||
guard fileHandle != nil else {
|
||||
if ProcessInfo.processInfo.userName != "root" {
|
||||
throw RuntimeError.VMConfigurationError("need to run as root to work with block devices")
|
||||
}
|
||||
throw RuntimeError.VMConfigurationError("block device \(diskURL.url.path) seems to be already in use, unmount it first via 'diskutil unmount'")
|
||||
}
|
||||
let attachment = try VZDiskBlockDeviceStorageDeviceAttachment(fileHandle: fileHandle!, readOnly: diskReadOnly, synchronizationMode: .full)
|
||||
result.append(VZVirtioBlockDeviceConfiguration(attachment: attachment))
|
||||
} else {
|
||||
result.append(try VZDiskImageStorageDeviceAttachment(
|
||||
url: URL(fileURLWithPath: rawDisk),
|
||||
readOnly: false
|
||||
))
|
||||
// Error out if the disk is locked by the host (e.g. it was mounted in Finder),
|
||||
// see https://github.com/cirruslabs/tart/issues/323 for more details.
|
||||
if try !diskReadOnly && !FileLock(lockURL: diskURL).trylock() {
|
||||
throw RuntimeError.DiskAlreadyInUse("disk \(diskURL.url.path) seems to be already in use, unmount it first in Finder")
|
||||
}
|
||||
|
||||
let diskImageAttachment = try VZDiskImageStorageDeviceAttachment(
|
||||
url: diskURL,
|
||||
readOnly: diskReadOnly
|
||||
)
|
||||
result.append(VZVirtioBlockDeviceConfiguration(attachment: diskImageAttachment))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -622,3 +637,12 @@ extension String {
|
|||
URL(fileURLWithPath: NSString(string: self).expandingTildeInPath)
|
||||
}
|
||||
}
|
||||
|
||||
func pathHasMode(_ path: String, mode: mode_t) -> Bool {
|
||||
var st = stat()
|
||||
let statRes = stat(path, &st)
|
||||
guard statRes != -1 else {
|
||||
return false
|
||||
}
|
||||
return (Int32(st.st_mode) & Int32(mode)) == Int32(mode)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
|||
|
||||
init(vmDir: VMDirectory,
|
||||
network: Network = NetworkShared(),
|
||||
additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment] = [],
|
||||
additionalStorageDevices: [VZStorageDeviceConfiguration] = [],
|
||||
directorySharingDevices: [VZDirectorySharingDeviceConfiguration] = [],
|
||||
serialPorts: [VZSerialPortConfiguration] = [],
|
||||
suspendable: Bool = false
|
||||
|
|
@ -58,7 +58,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
|||
self.network = network
|
||||
configuration = try Self.craftConfiguration(diskURL: vmDir.diskURL,
|
||||
nvramURL: vmDir.nvramURL, vmConfig: config,
|
||||
network: network, additionalDiskAttachments: additionalDiskAttachments,
|
||||
network: network, additionalStorageDevices: additionalStorageDevices,
|
||||
directorySharingDevices: directorySharingDevices,
|
||||
serialPorts: serialPorts,
|
||||
suspendable: suspendable
|
||||
|
|
@ -142,7 +142,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
|||
ipswURL: URL,
|
||||
diskSizeGB: UInt16,
|
||||
network: Network = NetworkShared(),
|
||||
additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment] = [],
|
||||
additionalStorageDevices: [VZStorageDeviceConfiguration] = [],
|
||||
directorySharingDevices: [VZDirectorySharingDeviceConfiguration] = [],
|
||||
serialPorts: [VZSerialPortConfiguration] = []
|
||||
) async throws {
|
||||
|
|
@ -190,7 +190,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
|||
self.network = network
|
||||
configuration = try Self.craftConfiguration(diskURL: vmDir.diskURL, nvramURL: vmDir.nvramURL,
|
||||
vmConfig: config, network: network,
|
||||
additionalDiskAttachments: additionalDiskAttachments,
|
||||
additionalStorageDevices: additionalStorageDevices,
|
||||
directorySharingDevices: directorySharingDevices,
|
||||
serialPorts: serialPorts
|
||||
)
|
||||
|
|
@ -277,7 +277,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
|||
nvramURL: URL,
|
||||
vmConfig: VMConfig,
|
||||
network: Network = NetworkShared(),
|
||||
additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment],
|
||||
additionalStorageDevices: [VZStorageDeviceConfiguration],
|
||||
directorySharingDevices: [VZDirectorySharingDeviceConfiguration],
|
||||
serialPorts: [VZSerialPortConfiguration],
|
||||
suspendable: Bool = false
|
||||
|
|
@ -326,11 +326,11 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
|||
}
|
||||
|
||||
// Storage
|
||||
var attachments = [try VZDiskImageStorageDeviceAttachment(url: diskURL, readOnly: false)]
|
||||
attachments.append(contentsOf: additionalDiskAttachments)
|
||||
configuration.storageDevices = attachments.map {
|
||||
VZVirtioBlockDeviceConfiguration(attachment: $0)
|
||||
}
|
||||
var devices: [VZStorageDeviceConfiguration] = [
|
||||
VZVirtioBlockDeviceConfiguration(attachment: try VZDiskImageStorageDeviceAttachment(url: diskURL, readOnly: false))
|
||||
]
|
||||
devices.append(contentsOf: additionalStorageDevices)
|
||||
configuration.storageDevices = devices
|
||||
|
||||
// Entropy
|
||||
if !suspendable {
|
||||
|
|
|
|||
Loading…
Reference in New Issue