mirror of
https://git.proxmox.com/git/qemu-server
synced 2026-01-04 17:44:11 +00:00
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:
parent
29eb909ee0
commit
d8f61794f6
@ -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;
|
||||
}});
|
||||
|
||||
|
||||
@ -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};
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user