[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