aboutsummaryrefslogtreecommitdiffstats
path: root/virsh-ga
diff options
context:
space:
mode:
authorGuilhem Moulin <guilhem@libreoffice.org>2016-10-23 00:34:05 +0200
committerGuilhem Moulin <guilhem@libreoffice.org>2016-10-23 00:38:45 +0200
commit83bf907908ac713d334bf3ed4424989c86be9294 (patch)
tree91580d47239b3597e621f4419faa743919ff771b /virsh-ga
parentcbf0cecd44a6b422e208f3043f2ceaf7fd0a25a9 (diff)
Use the QEMU Guest Agent to retrive public key material.
Unlike filesystem passthrough (9p VirtFS), this allows us to create guests remotely without using sudo privileges. (We can't do this with VirtFS currently due to lack of relabelling, and the kernel won't let us `chgrp libvirt-qemu` without sudoing.)
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});
+ }
+}