[pve-devel] [PATCH qemu-server] fix #2612: allow input-data in guest exec and make command optional

Stefan Reiter s.reiter at proxmox.com
Thu Feb 27 11:47:41 CET 2020


'input-data' can be used to pass arbitrary data to a guest when running
an agent command with 'guest-exec'. Most guest-agent implementations
treat this as STDIN to the command given by "path"/"arg", but some go as
far as relying solely on this parameter, and even fail if "path" or
"arg" are set (e.g. Mikrotik Cloud Hosted Router) - thus "command" needs
to be made optional.

Via the API, an arbitrary string can be passed, on the command line ('qm
guest exec'), an additional '--pass-stdin' flag allows to forward STDIN
of the qm process to 'input-data', with a size limitation of 1 MiB to
not overwhelm QMP.

Without 'input-data' (API) or '--pass-stdin' (CLI) behaviour is unchanged.

Signed-off-by: Stefan Reiter <s.reiter at proxmox.com>
---

Tested with:

Debian-VM:
    qm guest exec 101 --pass-stdin -- "file" "-" < PVE/QemuServer.pm
    "out-data": "/dev/stdin: Perl5 module source, ASCII text\n"

Mikrotik CHR (ver. 6.46.3):
    echo -n ":ip address print;" | qm guest exec 199 --pass-stdin
    "out-data" : "Flags: X - disabled, I - invalid, D - dynamic ..." [snipped]

...and running the same and a few more commands via the API/pvesh.


 PVE/API2/Qemu/Agent.pm  | 15 ++++++++++++---
 PVE/CLI/qm.pm           | 28 ++++++++++++++++++++++++----
 PVE/QemuServer/Agent.pm | 31 ++++++++++++++++++++++---------
 3 files changed, 58 insertions(+), 16 deletions(-)

diff --git a/PVE/API2/Qemu/Agent.pm b/PVE/API2/Qemu/Agent.pm
index 1bb4dd6..1981cad 100644
--- a/PVE/API2/Qemu/Agent.pm
+++ b/PVE/API2/Qemu/Agent.pm
@@ -276,7 +276,13 @@ __PACKAGE__->register_method({
 		type => 'string',
 		format => 'string-alist',
 		description => 'The command as a list of program + arguments',
-	    }
+		optional => 1,
+	    },
+	    'input-data' => {
+		type => 'string',
+		description => "Data to pass as 'input-data' to the guest. Usually treated as STDIN to 'command'.",
+		optional => 1,
+	    },
 	},
     },
     returns => {
@@ -292,9 +298,12 @@ __PACKAGE__->register_method({
 	my ($param) = @_;
 
 	my $vmid = $param->{vmid};
-	my $cmd = [PVE::Tools::split_list($param->{command})];
+	my $cmd = undef;
+	if ($param->{command}) {
+	    $cmd = [PVE::Tools::split_list($param->{command})];
+	}
 
-	my $res = PVE::QemuServer::Agent::qemu_exec($vmid, $cmd);
+	my $res = PVE::QemuServer::Agent::qemu_exec($vmid, $param->{'input-data'}, $cmd);
 	return $res;
     }});
 
diff --git a/PVE/CLI/qm.pm b/PVE/CLI/qm.pm
index 87f5d84..94bfaf8 100755
--- a/PVE/CLI/qm.pm
+++ b/PVE/CLI/qm.pm
@@ -700,6 +700,12 @@ __PACKAGE__->register_method({
 		optional => 1,
 		default => 30,
 	    },
+	    'pass-stdin' => {
+		type => 'boolean',
+		description => "When set, read STDIN until EOF and forward to guest agent via 'input-data' (usually treated as STDIN to process launched by guest agent).",
+		optional => 1,
+		default => 0,
+	    },
 	    'extra-args' => get_standard_option('extra-args'),
 	},
     },
@@ -711,14 +717,28 @@ __PACKAGE__->register_method({
 
 	my $vmid = $param->{vmid};
 	my $sync = $param->{synchronous} // 1;
-	if (!$param->{'extra-args'} || !@{$param->{'extra-args'}}) {
-	    raise_param_exc( { 'extra-args' => "No command given" });
-	}
+	my $pass_stdin = $param->{'pass-stdin'};
 	if (defined($param->{timeout}) && !$sync) {
 	    raise_param_exc({ synchronous => "needs to be set for 'timeout'"});
 	}
 
-	my $res = PVE::QemuServer::Agent::qemu_exec($vmid, $param->{'extra-args'});
+	my $input_data = undef;
+	if ($pass_stdin) {
+	    $input_data = '';
+	    while (my $line = <STDIN>) {
+		$input_data .= $line;
+		if (length($input_data) > 1024*1024) {
+		    # not sure how QEMU handles large amounts of data being
+		    # passed into the QMP socket, so limit to be safe
+		    die "'input-data' (STDIN) is limited to 1 MiB, aborting\n";
+		}
+	    }
+	}
+
+	my $args = $param->{'extra-args'};
+	$args = undef if !$args || !@$args;
+
+	my $res = PVE::QemuServer::Agent::qemu_exec($vmid, $input_data, $args);
 
 	if ($sync) {
 	    my $pid = $res->{pid};
diff --git a/PVE/QemuServer/Agent.pm b/PVE/QemuServer/Agent.pm
index 9fec4fb..02e9856 100644
--- a/PVE/QemuServer/Agent.pm
+++ b/PVE/QemuServer/Agent.pm
@@ -5,7 +5,7 @@ use warnings;
 
 use PVE::QemuServer;
 use PVE::QemuServer::Monitor;
-use MIME::Base64 qw(decode_base64);
+use MIME::Base64 qw(decode_base64 encode_base64);
 use JSON;
 use base 'Exporter';
 
@@ -67,18 +67,31 @@ sub agent_cmd {
 }
 
 sub qemu_exec {
-    my ($vmid, $cmd) = @_;
-
-
-    my $path = shift @$cmd;
-    my $arguments = $cmd;
+    my ($vmid, $input_data, $cmd) = @_;
 
     my $args = {
-	path => $path,
-	arg => $arguments,
 	'capture-output' => JSON::true,
     };
-    my $res = agent_cmd($vmid, "exec", $args, "can't execute command '$path $arguments'");
+
+    if ($cmd) {
+	$args->{path} = shift @$cmd;
+	$args->{arg} = $cmd;
+    }
+
+    $args->{'input-data'} = encode_base64($input_data, '') if defined($input_data);
+
+    die "command or input-data (or both) required\n"
+	if !defined($args->{'input-data'}) && !defined($args->{path});
+
+    my $errmsg = "can't execute command";
+    if ($cmd) {
+	$errmsg .= " ($args->{path} $args->{arg})";
+    }
+    if (defined($input_data)) {
+	$errmsg .= " (input-data given)";
+    }
+
+    my $res = agent_cmd($vmid, "exec", $args, $errmsg);
 
     return $res;
 }
-- 
2.20.1





More information about the pve-devel mailing list