aboutsummaryrefslogtreecommitdiffstats
path: root/virsh-ga
diff options
context:
space:
mode:
Diffstat (limited to 'virsh-ga')
-rwxr-xr-xvirsh-ga92
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});
+ }
+}