UTMPipeInterface.swift 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. //
  2. // Copyright © 2024 osy. All rights reserved.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. import Foundation
  17. import QEMUKit
  18. class UTMPipeInterface: NSObject, QEMUInterface {
  19. weak var connectDelegate: QEMUInterfaceConnectDelegate?
  20. var monitorOutPipeURL: URL!
  21. var monitorInPipeURL: URL!
  22. var guestAgentOutPipeURL: URL!
  23. var guestAgentInPipeURL: URL!
  24. private var pipeIOQueue = DispatchQueue(label: "UTMPipeInterface")
  25. private var qemuMonitorPort: Port!
  26. private var qemuGuestAgentPort: Port!
  27. func start() throws {
  28. try initializePipe(at: monitorOutPipeURL)
  29. try initializePipe(at: monitorInPipeURL)
  30. try initializePipe(at: guestAgentOutPipeURL)
  31. try initializePipe(at: guestAgentInPipeURL)
  32. }
  33. func connect() throws {
  34. pipeIOQueue.async { [self] in
  35. do {
  36. try openQemuPipes()
  37. connectDelegate?.qemuInterface(self, didCreateMonitorPort: qemuMonitorPort)
  38. connectDelegate?.qemuInterface(self, didCreateGuestAgentPort: qemuGuestAgentPort)
  39. } catch {
  40. connectDelegate?.qemuInterface(self, didErrorWithMessage: error.localizedDescription)
  41. }
  42. }
  43. }
  44. func disconnect() {
  45. cleanupPipes()
  46. }
  47. }
  48. extension UTMPipeInterface {
  49. class Port: NSObject, QEMUPort {
  50. let readPipe: FileHandle
  51. let writePipe: FileHandle
  52. var readDataHandler: readDataHandler_t?
  53. var errorHandler: errorHandler_t?
  54. var disconnectHandler: disconnectHandler_t?
  55. let isOpen: Bool = true
  56. init(readPipe: FileHandle, writePipe: FileHandle) {
  57. self.readPipe = readPipe
  58. self.writePipe = writePipe
  59. super.init()
  60. readPipe.readabilityHandler = { fileHandle in
  61. self.readDataHandler?(fileHandle.availableData)
  62. }
  63. }
  64. func write(_ data: Data) {
  65. writePipe.write(data)
  66. }
  67. }
  68. private var fileManager: FileManager {
  69. FileManager.default
  70. }
  71. private func initializePipe(at url: URL) throws {
  72. if fileManager.fileExists(atPath: url.path) {
  73. try fileManager.removeItem(at: url)
  74. }
  75. guard mkfifo(url.path, S_IRUSR | S_IWUSR) == 0 else {
  76. throw ServerError.failedToCreatePipe(errno)
  77. }
  78. }
  79. private func openPipe(at url: URL, forReading isRead: Bool) throws -> FileHandle {
  80. let fileHandle: FileHandle
  81. if isRead {
  82. fileHandle = try FileHandle(forReadingFrom: url)
  83. } else {
  84. fileHandle = try FileHandle(forWritingTo: url)
  85. }
  86. return fileHandle
  87. }
  88. private func cleanupPipes() {
  89. // unblock any un-opened pipes
  90. _ = try? FileHandle(forUpdating: monitorOutPipeURL)
  91. _ = try? FileHandle(forUpdating: monitorInPipeURL)
  92. _ = try? FileHandle(forUpdating: guestAgentOutPipeURL)
  93. _ = try? FileHandle(forUpdating: guestAgentInPipeURL)
  94. pipeIOQueue.sync {
  95. if let monitorOutPipeURL = monitorOutPipeURL {
  96. try? fileManager.removeItem(at: monitorOutPipeURL)
  97. }
  98. if let monitorInPipeURL = monitorInPipeURL {
  99. try? fileManager.removeItem(at: monitorInPipeURL)
  100. }
  101. if let guestAgentOutPipeURL = guestAgentOutPipeURL {
  102. try? fileManager.removeItem(at: guestAgentOutPipeURL)
  103. }
  104. if let guestAgentInPipeURL = guestAgentInPipeURL {
  105. try? fileManager.removeItem(at: guestAgentInPipeURL)
  106. }
  107. qemuMonitorPort = nil
  108. qemuGuestAgentPort = nil
  109. }
  110. }
  111. private func openQemuPipes() throws {
  112. let qmpReadPipe = try openPipe(at: monitorOutPipeURL, forReading: true)
  113. let qmpWritePipe = try openPipe(at: monitorInPipeURL, forReading: false)
  114. qemuMonitorPort = Port(readPipe: qmpReadPipe, writePipe: qmpWritePipe)
  115. let qgaReadPipe = try openPipe(at: guestAgentOutPipeURL, forReading: true)
  116. let qgaWritePipe = try openPipe(at: guestAgentInPipeURL, forReading: false)
  117. qemuGuestAgentPort = Port(readPipe: qgaReadPipe, writePipe: qgaWritePipe)
  118. }
  119. }
  120. extension UTMPipeInterface {
  121. enum ServerError: LocalizedError {
  122. case failedToCreatePipe(Int32)
  123. var errorDescription: String? {
  124. switch self {
  125. case .failedToCreatePipe(_):
  126. return NSLocalizedString("Failed to create pipe for communications.", comment: "UTMPipeInterface")
  127. }
  128. }
  129. }
  130. }