diff options
Diffstat (limited to 'virsh-ga')
-rwxr-xr-x | virsh-ga | 92 |
1 files changed, 92 insertions, 0 deletions
diff --git a/virsh-ga b/virsh-ga new file mode 100755 index 0000000..2b26c82 --- /dev/null +++ b/virsh-ga @@ -0,0 +1,92 @@ +#!/usr/bin/perl + +# Send command to a guest's QEMU Agent to access its file. +# Usage: virsh-ga [--connect=URI] DOMAIN COMMAND [ARGUMENT..] +# +# Return value: +# - 0 on success, +# - 128 if the QEMU agent is not connected, and +# - 1 on error. + + +# Clean up PATH +$ENV{PATH} = join ':', qw{/usr/bin /bin}; +delete @ENV{qw/IFS CDPATH ENV BASH_ENV/}; + +use warnings; +use strict; + +use Symbol 'gensym'; +use IO::Select (); +use IPC::Open3 'open3'; +use JSON (); +use MIME::Base64 'decode_base64'; +use Getopt::Long qw/:config posix_default no_ignore_case gnu_getopt auto_version auto_help/; + +my %OPTIONS; +GetOptions(\%OPTIONS, qw/connect|c=s/) or die; +my $DOMAIN = shift // die; +my $COMMAND = shift // die; + +my @VIRSH = ('virsh'); +push @VIRSH, '--connect='.$OPTIONS{connect} if defined $OPTIONS{connect}; + +sub ga_send(@) { + my @command = (@VIRSH, 'qemu-agent-command', '--block', $DOMAIN, JSON->new->encode({ @_ })); + my $pid = open3(my $in, my $out, my $err = gensym(), @command); + my $sel = IO::Select::->new($out, $err); + my %str; + while (my @fhs = $sel->can_read) { + foreach my $fh (@fhs) { + my $x = $fh->getline; + if (defined $x) { + $str{$fh} .= $x; + } else { + $sel->remove($fh); + } + } + } + waitpid $pid, 0; + close $_ foreach ($in, $out, $err); + if ($? == 0) { + my $h = JSON->new->utf8->allow_nonref->decode($str{$out}); + return $h->{return}; + } + elsif ($str{$err} eq "error: Guest agent is not responding: QEMU guest agent is not connected\n") { + exit 128; + } else { + die $str{$err}; + } +} + +# the JSON domain definition can be found in QEMU's qga/qapi-schema.json +if ($COMMAND eq 'info') { + ga_send(execute => 'guest-info'); +} +elsif ($COMMAND eq 'ping') { + ga_send(execute => 'guest-ping'); +} +elsif ($COMMAND eq 'cat') { + die unless @ARGV; + foreach my $path (@ARGV) { + my $fh = ga_send(execute => 'guest-file-open', arguments => {path => $path, mode => 'r'}); + + my ($b64, $eof); + do { + # keep reading until we reach EOF + my $r = ga_send(execute => 'guest-file-read', arguments => {handle => $fh}); + $b64 .= $r->{'buf-b64'}; + $eof = $r->{eof}; + } until ($eof); + print decode_base64($b64); + + ga_send(execute => 'guest-file-close', arguments => {handle => $fh}); + } +} +elsif ($COMMAND eq 'touch') { + die unless @ARGV; + foreach my $path (@ARGV) { + my $fh = ga_send(execute => 'guest-file-open', arguments => {path => $path, mode => 'a'}); + ga_send(execute => 'guest-file-close', arguments => {handle => $fh}); + } +} |