From f62949b6f4beb5429b04071cedc57b1f6523630d Mon Sep 17 00:00:00 2001 From: Fedor Korotkov Date: Fri, 17 Mar 2023 09:52:13 -0400 Subject: [PATCH] Option to pass externally created serial console (#448) * Option to pass externally created serial console See https://github.com/cirruslabs/tart/pull/364#issuecomment-1472111742 for details * Fixed compilation * Apply suggestions from code review Co-authored-by: Nikolay Edigaryev --------- Co-authored-by: Nikolay Edigaryev --- Sources/tart/Commands/Run.swift | 42 +++++++++++++++++++++++++++--- Sources/tart/VM.swift | 26 +++++------------- Sources/tart/VMStorageHelper.swift | 3 +++ 3 files changed, 47 insertions(+), 24 deletions(-) diff --git a/Sources/tart/Commands/Run.swift b/Sources/tart/Commands/Run.swift index 2040fcf..23da9da 100644 --- a/Sources/tart/Commands/Run.swift +++ b/Sources/tart/Commands/Run.swift @@ -17,18 +17,24 @@ struct Run: AsyncParsableCommand { @Flag(help: ArgumentHelp( "Don't open a UI window.", - discussion: "Useful for integrating Tart VMs into other tools.\nUse `tart ip` in order to get an IP for SSHing or VNCing into the VM.")) + discussion: "Useful for integrating Tart VMs into other tools.\nUse `tart ip` in order to get an IP for SSHing or VNCing into the VM.")) var noGraphics: Bool = false @Flag(help: ArgumentHelp( "Open serial console in /dev/ttySXX", - discussion: "Useful for debugging Linux Kernel")) + discussion: "Useful for debugging Linux Kernel.")) var serial: Bool = false + @Option(help: ArgumentHelp( + "Attach an externally created serial console", + discussion: "Alternative to `--serial` flag for programmatic integrations." + )) + var serialPath: String? + @Flag(help: "Force open a UI window, even when VNC is enabled.") var graphics: Bool = false - @Flag(help: "Boot into recovery mode") + @Flag(help: "Boot into recovery mode") var recovery: Bool = false @Flag(help: ArgumentHelp( @@ -123,12 +129,30 @@ struct Run: AsyncParsableCommand { } } + var serialPorts: [VZSerialPortConfiguration] = [] + if serial { + let tty_fd = createPTY() + if (tty_fd < 0) { + throw RuntimeError.VMConfigurationError("Failed to create PTY") + } + let tty_read = FileHandle.init(fileDescriptor: tty_fd) + let tty_write = FileHandle.init(fileDescriptor: tty_fd) + serialPorts.append(createSerialPortConfiguration(tty_read, tty_write)) + } else if serialPath != nil { + let tty_read = FileHandle.init(forReadingAtPath: serialPath!) + let tty_write = FileHandle.init(forWritingAtPath: serialPath!) + if (tty_read == nil || tty_write == nil) { + throw RuntimeError.VMConfigurationError("Failed to open PTY") + } + serialPorts.append(createSerialPortConfiguration(tty_read!, tty_write!)) + } + vm = try VM( vmDir: vmDir, network: userSpecifiedNetwork(vmDir: vmDir) ?? NetworkShared(), additionalDiskAttachments: additionalDiskAttachments, directorySharingDevices: directoryShares() + rosettaDirectoryShare(), - serial: serial + serialPorts: serialPorts ) let vncImpl: VNC? = try { @@ -204,6 +228,16 @@ struct Run: AsyncParsableCommand { } } + private func createSerialPortConfiguration(_ tty_read: FileHandle, _ tty_write: FileHandle) -> VZVirtioConsoleDeviceSerialPortConfiguration { + let serialPortConfiguration = VZVirtioConsoleDeviceSerialPortConfiguration() + let serialPortAttachment = VZFileHandleSerialPortAttachment( + fileHandleForReading: tty_read, + fileHandleForWriting: tty_write) + + serialPortConfiguration.attachment = serialPortAttachment + return serialPortConfiguration + } + func isInteractiveSession() -> Bool { isatty(STDOUT_FILENO) == 1 } diff --git a/Sources/tart/VM.swift b/Sources/tart/VM.swift index bd569d1..996c885 100644 --- a/Sources/tart/VM.swift +++ b/Sources/tart/VM.swift @@ -41,7 +41,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject { network: Network = NetworkShared(), additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment] = [], directorySharingDevices: [VZDirectorySharingDeviceConfiguration] = [], - serial: Bool = false + serialPorts: [VZSerialPortConfiguration] = [] ) throws { name = vmDir.name config = try VMConfig.init(fromURL: vmDir.configURL) @@ -56,7 +56,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject { nvramURL: vmDir.nvramURL, vmConfig: config, network: network, additionalDiskAttachments: additionalDiskAttachments, directorySharingDevices: directorySharingDevices, - serial: serial + serialPorts: serialPorts ) virtualMachine = VZVirtualMachine(configuration: configuration) @@ -135,7 +135,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject { network: Network = NetworkShared(), additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment] = [], directorySharingDevices: [VZDirectorySharingDeviceConfiguration] = [], - serial: Bool = false + serialPorts: [VZSerialPortConfiguration] = [] ) async throws { var ipswURL = ipswURL @@ -183,7 +183,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject { vmConfig: config, network: network, additionalDiskAttachments: additionalDiskAttachments, directorySharingDevices: directorySharingDevices, - serial: serial + serialPorts: serialPorts ) virtualMachine = VZVirtualMachine(configuration: configuration) @@ -266,7 +266,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject { network: Network = NetworkShared(), additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment], directorySharingDevices: [VZDirectorySharingDeviceConfiguration], - serial: Bool + serialPorts: [VZSerialPortConfiguration] ) throws -> VZVirtualMachineConfiguration { let configuration = VZVirtualMachineConfiguration() @@ -314,21 +314,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject { configuration.directorySharingDevices = directorySharingDevices // Serial Port - if serial { - let tty_fd = createPTY() - if(tty_fd > -1){ - let tty_read = FileHandle.init(fileDescriptor: tty_fd) - let tty_write = FileHandle.init(fileDescriptor: tty_fd) - - - configuration.serialPorts = [VZVirtioConsoleDeviceSerialPortConfiguration()] - let serialPortAttachment = VZFileHandleSerialPortAttachment( - fileHandleForReading: tty_read, - fileHandleForWriting: tty_write) - - configuration.serialPorts[0].attachment = serialPortAttachment - } - } + configuration.serialPorts = serialPorts try configuration.validate() diff --git a/Sources/tart/VMStorageHelper.swift b/Sources/tart/VMStorageHelper.swift index 48c2398..ed65cc5 100644 --- a/Sources/tart/VMStorageHelper.swift +++ b/Sources/tart/VMStorageHelper.swift @@ -41,6 +41,7 @@ extension Error { } enum RuntimeError : Error { + case VMConfigurationError(_ message: String) case VMDoesNotExist(name: String) case VMMissingFiles(_ message: String) case VMNotRunning(_ message: String) @@ -65,6 +66,8 @@ protocol HasExitCode { extension RuntimeError : CustomStringConvertible { public var description: String { switch self { + case .VMConfigurationError(let message): + return message case .VMDoesNotExist(let name): return "the specified VM \"\(name)\" does not exist" case .VMMissingFiles(let message):