'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.rei...@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 _______________________________________________ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel