UTM/Services/UTMPipeInterface.swift

153 lines
5.0 KiB
Swift

//
// Copyright © 2024 osy. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import QEMUKit
class UTMPipeInterface: NSObject, QEMUInterface {
weak var connectDelegate: QEMUInterfaceConnectDelegate?
var monitorOutPipeURL: URL!
var monitorInPipeURL: URL!
var guestAgentOutPipeURL: URL!
var guestAgentInPipeURL: URL!
private var pipeIOQueue = DispatchQueue(label: "UTMPipeInterface")
private var qemuMonitorPort: Port!
private var qemuGuestAgentPort: Port!
func start() throws {
try initializePipe(at: monitorOutPipeURL)
try initializePipe(at: monitorInPipeURL)
try initializePipe(at: guestAgentOutPipeURL)
try initializePipe(at: guestAgentInPipeURL)
}
func connect() throws {
pipeIOQueue.async { [self] in
do {
try openQemuPipes()
connectDelegate?.qemuInterface(self, didCreateMonitorPort: qemuMonitorPort)
connectDelegate?.qemuInterface(self, didCreateGuestAgentPort: qemuGuestAgentPort)
} catch {
connectDelegate?.qemuInterface(self, didErrorWithMessage: error.localizedDescription)
}
}
}
func disconnect() {
cleanupPipes()
}
}
extension UTMPipeInterface {
class Port: NSObject, QEMUPort {
let readPipe: FileHandle
let writePipe: FileHandle
var readDataHandler: readDataHandler_t?
var errorHandler: errorHandler_t?
var disconnectHandler: disconnectHandler_t?
let isOpen: Bool = true
init(readPipe: FileHandle, writePipe: FileHandle) {
self.readPipe = readPipe
self.writePipe = writePipe
super.init()
readPipe.readabilityHandler = { fileHandle in
self.readDataHandler?(fileHandle.availableData)
}
}
func write(_ data: Data) {
writePipe.write(data)
}
}
private var fileManager: FileManager {
FileManager.default
}
private func initializePipe(at url: URL) throws {
if fileManager.fileExists(atPath: url.path) {
try fileManager.removeItem(at: url)
}
guard mkfifo(url.path, S_IRUSR | S_IWUSR) == 0 else {
throw ServerError.failedToCreatePipe(errno)
}
}
private func openPipe(at url: URL, forReading isRead: Bool) throws -> FileHandle {
let fileHandle: FileHandle
if isRead {
fileHandle = try FileHandle(forReadingFrom: url)
} else {
fileHandle = try FileHandle(forWritingTo: url)
}
return fileHandle
}
private func cleanupPipes() {
// unblock any un-opened pipes
_ = try? FileHandle(forUpdating: monitorOutPipeURL)
_ = try? FileHandle(forUpdating: monitorInPipeURL)
_ = try? FileHandle(forUpdating: guestAgentOutPipeURL)
_ = try? FileHandle(forUpdating: guestAgentInPipeURL)
pipeIOQueue.sync {
if let monitorOutPipeURL = monitorOutPipeURL {
try? fileManager.removeItem(at: monitorOutPipeURL)
}
if let monitorInPipeURL = monitorInPipeURL {
try? fileManager.removeItem(at: monitorInPipeURL)
}
if let guestAgentOutPipeURL = guestAgentOutPipeURL {
try? fileManager.removeItem(at: guestAgentOutPipeURL)
}
if let guestAgentInPipeURL = guestAgentInPipeURL {
try? fileManager.removeItem(at: guestAgentInPipeURL)
}
qemuMonitorPort = nil
qemuGuestAgentPort = nil
}
}
private func openQemuPipes() throws {
let qmpReadPipe = try openPipe(at: monitorOutPipeURL, forReading: true)
let qmpWritePipe = try openPipe(at: monitorInPipeURL, forReading: false)
qemuMonitorPort = Port(readPipe: qmpReadPipe, writePipe: qmpWritePipe)
let qgaReadPipe = try openPipe(at: guestAgentOutPipeURL, forReading: true)
let qgaWritePipe = try openPipe(at: guestAgentInPipeURL, forReading: false)
qemuGuestAgentPort = Port(readPipe: qgaReadPipe, writePipe: qgaWritePipe)
}
}
extension UTMPipeInterface {
enum ServerError: LocalizedError {
case failedToCreatePipe(Int32)
var errorDescription: String? {
switch self {
case .failedToCreatePipe(_):
return NSLocalizedString("Failed to create pipe for communications.", comment: "UTMPipeInterface")
}
}
}
}