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