UTMScriptingGuestFileImpl.swift 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. //
  2. // Copyright © 2023 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 QEMUKitInternal
  18. @MainActor
  19. @objc(UTMScriptingGuestFileImpl)
  20. class UTMScriptingGuestFileImpl: NSObject, UTMScriptable {
  21. @objc private(set) var id: Int
  22. weak private var parent: UTMScriptingVirtualMachineImpl?
  23. init(from handle: Int, parent: UTMScriptingVirtualMachineImpl) {
  24. self.id = handle
  25. self.parent = parent
  26. }
  27. override var objectSpecifier: NSScriptObjectSpecifier? {
  28. guard let parent = parent else {
  29. return nil
  30. }
  31. guard let parentDescription = parent.classDescription as? NSScriptClassDescription else {
  32. return nil
  33. }
  34. let parentSpecifier = parent.objectSpecifier
  35. return NSUniqueIDSpecifier(containerClassDescription: parentDescription,
  36. containerSpecifier: parentSpecifier,
  37. key: "openFiles",
  38. uniqueID: id)
  39. }
  40. private func seek(to offset: Int, whence: AEKeyword?, using guestAgent: QEMUGuestAgent) async throws {
  41. let seek: QEMUGuestAgentSeek
  42. if let whence = whence {
  43. switch UTMScriptingWhence(rawValue: whence) {
  44. case .startPosition: seek = .set
  45. case .currentPosition: seek = .cur
  46. case .endPosition: seek = .end
  47. default: seek = .set
  48. }
  49. } else {
  50. seek = .set
  51. }
  52. try await guestAgent.guestFileSeek(id, offset: offset, whence: seek)
  53. }
  54. @objc func read(_ command: NSScriptCommand) {
  55. let id = self.id
  56. let offset = command.evaluatedArguments?["offset"] as? Int
  57. let whence = command.evaluatedArguments?["whence"] as? AEKeyword
  58. let length = command.evaluatedArguments?["length"] as? Int
  59. let isBase64Encoded = command.evaluatedArguments?["isBase64Encoded"] as? Bool ?? false
  60. let isClosing = command.evaluatedArguments?["isClosing"] as? Bool ?? true
  61. withScriptCommand(command) { [self] in
  62. guard let guestAgent = await parent?.guestAgent else {
  63. throw UTMScriptingVirtualMachineImpl.ScriptingError.guestAgentNotRunning
  64. }
  65. defer {
  66. if isClosing {
  67. guestAgent.guestFileClose(id)
  68. }
  69. }
  70. if let offset = offset {
  71. try await seek(to: offset, whence: whence, using: guestAgent)
  72. }
  73. if let length = length {
  74. let data = try await guestAgent.guestFileRead(id, count: length)
  75. return textFromData(data, isBase64Encoded: isBase64Encoded)
  76. }
  77. var data: Data
  78. var allData = Data()
  79. repeat {
  80. data = try await guestAgent.guestFileRead(id, count: 4096)
  81. allData += data
  82. } while data.count > 0
  83. return textFromData(allData, isBase64Encoded: isBase64Encoded)
  84. }
  85. }
  86. @objc func pull(_ command: NSScriptCommand) {
  87. let id = self.id
  88. let file = command.evaluatedArguments?["file"] as? URL
  89. let isClosing = command.evaluatedArguments?["isClosing"] as? Bool ?? true
  90. withScriptCommand(command) { [self] in
  91. guard let guestAgent = await parent?.guestAgent else {
  92. throw UTMScriptingVirtualMachineImpl.ScriptingError.guestAgentNotRunning
  93. }
  94. defer {
  95. if isClosing {
  96. guestAgent.guestFileClose(id)
  97. }
  98. }
  99. guard let file = file else {
  100. throw UTMScriptingVirtualMachineImpl.ScriptingError.invalidParameter
  101. }
  102. try await guestAgent.guestFileSeek(id, offset: 0, whence: .set)
  103. _ = file.startAccessingSecurityScopedResource()
  104. defer {
  105. file.stopAccessingSecurityScopedResource()
  106. }
  107. let handle = try FileHandle(forWritingTo: file)
  108. var data: Data
  109. repeat {
  110. data = try await guestAgent.guestFileRead(id, count: 4096)
  111. try handle.write(contentsOf: data)
  112. } while data.count > 0
  113. }
  114. }
  115. @objc func write(_ command: NSScriptCommand) {
  116. let id = self.id
  117. let data = command.evaluatedArguments?["data"] as? String
  118. let offset = command.evaluatedArguments?["offset"] as? Int
  119. let whence = command.evaluatedArguments?["whence"] as? AEKeyword
  120. let isBase64Encoded = command.evaluatedArguments?["isBase64Encoded"] as? Bool ?? false
  121. let isClosing = command.evaluatedArguments?["isClosing"] as? Bool ?? true
  122. withScriptCommand(command) { [self] in
  123. guard let guestAgent = await parent?.guestAgent else {
  124. throw UTMScriptingVirtualMachineImpl.ScriptingError.guestAgentNotRunning
  125. }
  126. defer {
  127. if isClosing {
  128. guestAgent.guestFileClose(id)
  129. }
  130. }
  131. guard let data = dataFromText(data, isBase64Encoded: isBase64Encoded) else {
  132. throw UTMScriptingVirtualMachineImpl.ScriptingError.invalidParameter
  133. }
  134. if let offset = offset {
  135. try await seek(to: offset, whence: whence, using: guestAgent)
  136. }
  137. try await guestAgent.guestFileWrite(id, data: data)
  138. try await guestAgent.guestFileFlush(id)
  139. }
  140. }
  141. @objc func push(_ command: NSScriptCommand) {
  142. let id = self.id
  143. let file = command.evaluatedArguments?["file"] as? URL
  144. let isClosing = command.evaluatedArguments?["isClosing"] as? Bool ?? true
  145. withScriptCommand(command) { [self] in
  146. guard let guestAgent = await parent?.guestAgent else {
  147. throw UTMScriptingVirtualMachineImpl.ScriptingError.guestAgentNotRunning
  148. }
  149. defer {
  150. if isClosing {
  151. guestAgent.guestFileClose(id)
  152. }
  153. }
  154. guard let file = file else {
  155. throw UTMScriptingVirtualMachineImpl.ScriptingError.invalidParameter
  156. }
  157. try await guestAgent.guestFileSeek(id, offset: 0, whence: .set)
  158. _ = file.startAccessingSecurityScopedResource()
  159. defer {
  160. file.stopAccessingSecurityScopedResource()
  161. }
  162. let handle = try FileHandle(forReadingFrom: file)
  163. var data: Data
  164. repeat {
  165. data = try handle.read(upToCount: 4096) ?? Data()
  166. try await guestAgent.guestFileWrite(id, data: data)
  167. } while data.count > 0
  168. }
  169. }
  170. @objc func close(_ command: NSScriptCommand) {
  171. withScriptCommand(command) { [self] in
  172. guard let guestAgent = await parent?.guestAgent else {
  173. throw UTMScriptingVirtualMachineImpl.ScriptingError.guestAgentNotRunning
  174. }
  175. try await guestAgent.guestFileClose(id)
  176. }
  177. }
  178. }