Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial socket support draft #5081

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions Platform/Main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<sockaddr_un>.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<sockaddr_un>.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()
}

Expand Down