fix #2612: allow input-data in guest exec and make command optional

'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@proxmox.com>
This commit is contained in:
Stefan Reiter 2020-02-27 11:47:41 +01:00 committed by Thomas Lamprecht
parent 29eb909ee0
commit d8f61794f6
3 changed files with 58 additions and 16 deletions

View File

@ -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;
}});

View File

@ -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};

View File

@ -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;
}