From 05065625bf10fbc7b266481d35b782d8f6f66188 Mon Sep 17 00:00:00 2001 From: Naveenraj M <22456988+naveenrajm7@users.noreply.github.com> Date: Fri, 4 Oct 2024 01:33:50 -0600 Subject: [PATCH] scripting: add export command --- Scripting/UTM.sdef | 9 ++++++ Scripting/UTMScripting.swift | 1 + Scripting/UTMScriptingExportCommand.swift | 31 +++++++++++++++++++ .../UTMScriptingVirtualMachineImpl.swift | 10 ++++++ UTM.xcodeproj/project.pbxproj | 4 +++ utmctl/UTMCtl.swift | 22 +++++++++++++ utmctl/utmctl-unsigned.entitlements | 2 +- 7 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 Scripting/UTMScriptingExportCommand.swift diff --git a/Scripting/UTM.sdef b/Scripting/UTM.sdef index 506164656..5414dba70 100644 --- a/Scripting/UTM.sdef +++ b/Scripting/UTM.sdef @@ -92,6 +92,15 @@ + + + + + + + + + diff --git a/Scripting/UTMScripting.swift b/Scripting/UTMScripting.swift index 3814b1f4f..34e39948c 100644 --- a/Scripting/UTMScripting.swift +++ b/Scripting/UTMScripting.swift @@ -208,6 +208,7 @@ extension SBObject: UTMScriptingWindow {} @objc optional func stopBy(_ by: UTMScriptingStopMethod) // Shuts down a running virtual machine. @objc optional func delete() // Delete a virtual machine. All data will be deleted, there is no confirmation! @objc optional func duplicateWithProperties(_ withProperties: [AnyHashable : Any]!) // Copy an virtual machine and all its data. + @objc optional func exportTo(_ to: URL!) // Export a virtual machine to a specified location. @objc optional func openFileAt(_ at: String!, for for_: UTMScriptingOpenMode, updating: Bool) -> UTMScriptingGuestFile // Open a file on the guest. You must close the file when you are done to prevent leaking guest resources. @objc optional func executeAt(_ at: String!, withArguments: [String]!, withEnvironment: [String]!, usingInput: String!, base64Encoding: Bool, outputCapturing: Bool) -> UTMScriptingGuestProcess // Execute a command or script on the guest. @objc optional func queryIp() -> [Any] // Query the guest for all IP addresses on its network interfaces (excluding loopback). diff --git a/Scripting/UTMScriptingExportCommand.swift b/Scripting/UTMScriptingExportCommand.swift new file mode 100644 index 000000000..7704161b5 --- /dev/null +++ b/Scripting/UTMScriptingExportCommand.swift @@ -0,0 +1,31 @@ +// +// Copyright © 2024 naveenrajm7. 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 + +@MainActor + +@objc(UTMScriptingExportCommand) +class UTMScriptingExportCommand: NSCloneCommand, UTMScriptable { + override func performDefaultImplementation() -> Any? { + if let scriptingVM = keySpecifier.objectsByEvaluatingSpecifier as? UTMScriptingVirtualMachineImpl { + scriptingVM.export(self) + return nil + } else { + return super.performDefaultImplementation() + } + } +} diff --git a/Scripting/UTMScriptingVirtualMachineImpl.swift b/Scripting/UTMScriptingVirtualMachineImpl.swift index f3b8b99d6..51302848f 100644 --- a/Scripting/UTMScriptingVirtualMachineImpl.swift +++ b/Scripting/UTMScriptingVirtualMachineImpl.swift @@ -180,6 +180,16 @@ class UTMScriptingVirtualMachineImpl: NSObject, UTMScriptable { } } } + + @objc func export(_ command: NSCloneCommand) { + let exportUrl = command.evaluatedArguments?["file"] as? URL + withScriptCommand(command) { [self] in + guard vm.state == .stopped else { + throw ScriptingError.notStopped + } + try await data.export(vm: box, to: exportUrl!) + } + } } // MARK: - Guest agent suite diff --git a/UTM.xcodeproj/project.pbxproj b/UTM.xcodeproj/project.pbxproj index 5f2e09525..9b18a3d66 100644 --- a/UTM.xcodeproj/project.pbxproj +++ b/UTM.xcodeproj/project.pbxproj @@ -270,6 +270,7 @@ 85EC516627CC8D10004A51DE /* VMConfigAdvancedNetworkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85EC516327CC8C98004A51DE /* VMConfigAdvancedNetworkView.swift */; }; B329049C270FE136002707AC /* AltKit in Frameworks */ = {isa = PBXBuildFile; productRef = B329049B270FE136002707AC /* AltKit */; }; B3DDF57226E9BBA300CE47F0 /* AltKit in Frameworks */ = {isa = PBXBuildFile; productRef = B3DDF57126E9BBA300CE47F0 /* AltKit */; }; + CD77BE422CAB51B40074ADD2 /* UTMScriptingExportCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD77BE412CAB519F0074ADD2 /* UTMScriptingExportCommand.swift */; }; CE020BA324AEDC7C00B44AB6 /* UTMData.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE020BA224AEDC7C00B44AB6 /* UTMData.swift */; }; CE020BA424AEDC7C00B44AB6 /* UTMData.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE020BA224AEDC7C00B44AB6 /* UTMData.swift */; }; CE020BA724AEDEF000B44AB6 /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = CE020BA624AEDEF000B44AB6 /* Logging */; }; @@ -1762,6 +1763,7 @@ C03453AF2709E35100AD51AD /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; C03453B02709E35200AD51AD /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; C8958B6D243634DA002D86B4 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; + CD77BE412CAB519F0074ADD2 /* UTMScriptingExportCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMScriptingExportCommand.swift; sourceTree = ""; }; CE020BA224AEDC7C00B44AB6 /* UTMData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMData.swift; sourceTree = ""; }; CE020BAA24AEE00000B44AB6 /* UTMLoggingSwift.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMLoggingSwift.swift; sourceTree = ""; }; CE020BB524B14F8400B44AB6 /* UTMVirtualMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMVirtualMachine.swift; sourceTree = ""; }; @@ -3006,6 +3008,7 @@ CE25125429C80CD4000790AB /* UTMScriptingCreateCommand.swift */, CE25125029C806AF000790AB /* UTMScriptingDeleteCommand.swift */, CE25125229C80A18000790AB /* UTMScriptingCloneCommand.swift */, + CD77BE412CAB519F0074ADD2 /* UTMScriptingExportCommand.swift */, ); path = Scripting; sourceTree = ""; @@ -3811,6 +3814,7 @@ CEF01DB52B6724A300725A0F /* UTMSpiceVirtualMachine.swift in Sources */, 8432329A28C3084A00CFBC97 /* GlobalFileImporter.swift in Sources */, CE19392826DCB094005CEC17 /* RAMSlider.swift in Sources */, + CD77BE422CAB51B40074ADD2 /* UTMScriptingExportCommand.swift in Sources */, 2C33B3AA2566C9B100A954A6 /* VMContextMenuModifier.swift in Sources */, 84BB993A2899E8D500DF28B2 /* VMHeadlessSessionState.swift in Sources */, CE2D955A24AD4F980059923A /* VMToolbarModifier.swift in Sources */, diff --git a/utmctl/UTMCtl.swift b/utmctl/UTMCtl.swift index 2b78dd723..4f48a7ea3 100644 --- a/utmctl/UTMCtl.swift +++ b/utmctl/UTMCtl.swift @@ -36,6 +36,7 @@ struct UTMCtl: ParsableCommand { IPAddress.self, Clone.self, Delete.self, + Export.self, USB.self ] ) @@ -522,6 +523,27 @@ extension UTMCtl { } } +extension UTMCtl { + struct Export: UTMAPICommand { + static var configuration = CommandConfiguration( + abstract: "Export a virtual machine and all its data to a specified location." + ) + + @OptionGroup var environment: EnvironmentOptions + + @OptionGroup var identifer: VMIdentifier + + @Option var path: String + + func run(with application: UTMScriptingApplication) throws { + let vm = try virtualMachine(forIdentifier: identifer, in: application) + // TODO: Make sure the URL is writable as required by data.export + let exportUrl = URL(fileURLWithPath: path) + vm.exportTo!(exportUrl) + } + } +} + extension UTMCtl { struct USB: ParsableCommand { static var configuration = CommandConfiguration( diff --git a/utmctl/utmctl-unsigned.entitlements b/utmctl/utmctl-unsigned.entitlements index 97d410104..65ed91648 100644 --- a/utmctl/utmctl-unsigned.entitlements +++ b/utmctl/utmctl-unsigned.entitlements @@ -3,7 +3,7 @@ com.apple.security.app-sandbox - + com.apple.security.network.client com.apple.security.scripting-targets