From c15776af0c57a02202e21395874f0155af40809f Mon Sep 17 00:00:00 2001 From: Moritz Angermann Date: Thu, 2 Mar 2023 11:15:18 +0800 Subject: [PATCH] Initial socket support draft This adds support for a utm.socket that can be used to list, start and stop VMs. --- Platform/Main.swift | 108 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/Platform/Main.swift b/Platform/Main.swift index 7b9a948ef..4b8d606af 100644 --- a/Platform/Main.swift +++ b/Platform/Main.swift @@ -29,6 +29,29 @@ let logger = Logger(label: "com.utmapp.UTM") { label in ]) } +func readChannelLines(buf: DispatchData, channel: DispatchIO, handleMsg: @escaping (String) -> (), eof: @escaping () -> ()) { + channel.read(offset: 0, length: 1, queue: .global()) { _, data, error in + if(error != 0) { return print(error) } + // this will be what e.g. ^D will send. + if(data!.isEmpty) { + return eof() + } + let char = data!.withUnsafeBytes { $0[0] as Int8 } + var buf_ = buf + // \r or \n + if(char == 10 || char == 13) { + if(!buf_.isEmpty) { + let msg = buf_.withUnsafeBytes { String(utf8String: $0) } + handleMsg(msg!) + buf_ = DispatchData.empty + } + } else { + buf_.append(data!) + } + readChannelLines(buf: buf_, channel: channel, handleMsg: handleMsg, eof: eof) + } +} + @main class Main { static var jitAvailable = true @@ -61,6 +84,91 @@ class Main { // register defaults registerDefaultsFromSettingsBundle() #endif + if CommandLine.arguments.contains("--with-socket") { + let socketQueue = DispatchQueue(label: "socket", qos: .background, attributes: .concurrent) + let prompt = "UTM> " + func writeMsg(channel: DispatchIO, msg: String) { + channel.write(offset: 0, data: msg.data(using: .utf8)!.withUnsafeBytes { DispatchData(bytes: $0) }, queue: socketQueue, ioHandler: { done, data, error in }) + } + func writeLine(channel: DispatchIO, msg: String) { + writeMsg(channel: channel, msg: msg.appending("\n")) + } + func writePrompt(channel: DispatchIO) { + writeMsg(channel: channel, msg: prompt) + } + socketQueue.async { + let sock = "utm.socket" + print(URL(fileURLWithPath: sock)) + try? FileManager.default.removeItem(atPath: sock) + let fd = socket(AF_UNIX, SOCK_STREAM, 0) + var addr = sockaddr_un() + addr.sun_family = sa_family_t(AF_UNIX) + addr.sun_len = UInt8(sock.utf8CString.count) + _ = withUnsafeMutablePointer(to: &addr.sun_path) { ptr in + strncpy(UnsafeMutableRawPointer(ptr).assumingMemoryBound(to: CChar.self), sock, sock.utf8CString.count) + } + var ret: Int32 = 0 + withUnsafePointer(to: &addr) { ptr in + ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { ar in + ret = bind(fd, ar, socklen_t(MemoryLayout.size)) + } + } + guard ret == 0 else { print("Failed to bind to address"); return } + listen(fd,1) + let data: UTMData = UTMData() + while(true) { + var clientaddr = sockaddr_un() + var clientlen = socklen_t(MemoryLayout.size) + let clientfd = withUnsafeMutablePointer(to: &clientaddr) { addrptr in + withUnsafeMutablePointer(to: &clientlen) { lenptr in + return accept(fd, UnsafeMutableRawPointer(addrptr).assumingMemoryBound(to: sockaddr.self), lenptr) + } + } + let channel = DispatchIO(type: .stream, fileDescriptor: clientfd, queue: .global()) { error in + print("channel error: \(error)") + } + writePrompt(channel: channel) + readChannelLines(buf: DispatchData.empty, channel: channel, handleMsg: { msg in + socketQueue.async { + let tokens = msg.split(separator: " ") +// print("[Debug] \(tokens)") + if(!tokens.isEmpty) { + let resp: String + switch tokens.first! { + case "list": + let header = "UUID Status Name" + let lines = [header] + data.virtualMachines.map { + let status = $0.stateLabel.padding(toLength: 8, withPad: " ", startingAt: 0) + return "\($0.id) \(status) \($0.config.name)" + } + resp = lines.joined(separator: "\n") + case "start": + if let vm: UTMVirtualMachine = data.virtualMachines.first(where: { "\($0.id)" == tokens[1] }) { + vm.requestVmStart() + let status = vm.stateLabel.padding(toLength: 8, withPad: " ", startingAt: 0) + resp = "\(vm.id) \(status) \(vm.config.name)" + } else { + resp = "Uknown UUID: \(tokens[1])" + } + case "stop": + if let vm: UTMVirtualMachine = data.virtualMachines.first(where: { "\($0.id)" == tokens[1] }) { + vm.requestVmStop() + let status = vm.stateLabel.padding(toLength: 8, withPad: " ", startingAt: 0) + resp = "\(vm.id) \(status) \(vm.config.name)" + } else { + resp = "Uknown UUID: \(tokens[1])" + } + default: + resp = "Unknown command: \(tokens.first!)" + } + writeLine(channel: channel, msg: resp) + } + writePrompt(channel: channel) + } + }, eof: { close(clientfd) }) + } + } + } UTMApp.main() }