#!/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}); } }