4428ecda0c71c4a5676ef84a4512a14b4163dc75
[libguestfs.git] / inspector / virt-inspector
1 #!/usr/bin/perl -w
2 # virt-inspector
3 # Copyright (C) 2009 Red Hat Inc.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 use warnings;
20 use strict;
21
22 use Sys::Guestfs;
23 use Sys::Guestfs::Lib qw(open_guest get_partitions resolve_windows_path
24   inspect_all_partitions inspect_partition
25   inspect_operating_systems mount_operating_system inspect_in_detail);
26 use Pod::Usage;
27 use Getopt::Long;
28 use Data::Dumper;
29 use XML::Writer;
30 use Locale::TextDomain 'libguestfs';
31
32 # Optional:
33 eval "use YAML::Any;";
34
35 =encoding utf8
36
37 =head1 NAME
38
39 virt-inspector - Display OS version, kernel, drivers, mount points, applications, etc. in a virtual machine
40
41 =head1 SYNOPSIS
42
43  virt-inspector [--connect URI] domname
44
45  virt-inspector guest.img [guest.img ...]
46
47 =head1 DESCRIPTION
48
49 B<virt-inspector> examines a virtual machine and tries to determine
50 the version of the OS, the kernel version, what drivers are installed,
51 whether the virtual machine is fully virtualized (FV) or
52 para-virtualized (PV), what applications are installed and more.
53
54 Virt-inspector can produce output in several formats, including a
55 readable text report, and XML for feeding into other programs.
56
57 In the normal usage, use C<virt-inspector domname> where C<domname> is
58 the libvirt domain (see: C<virsh list --all>).
59
60 You can also run virt-inspector directly on disk images from a single
61 virtual machine.  Use C<virt-inspector guest.img>.  In rare cases a
62 domain has several block devices, in which case you should list them
63 one after another, with the first corresponding to the guest's
64 C</dev/sda>, the second to the guest's C</dev/sdb> and so on.
65
66 Virt-inspector can only inspect and report upon I<one domain at a
67 time>.  To inspect several virtual machines, you have to run
68 virt-inspector several times (for example, from a shell script
69 for-loop).
70
71 Because virt-inspector needs direct access to guest images, it won't
72 normally work over remote libvirt connections.
73
74 =head1 OPTIONS
75
76 =over 4
77
78 =cut
79
80 my $help;
81
82 =item B<--help>
83
84 Display brief help.
85
86 =cut
87
88 my $version;
89
90 =item B<--version>
91
92 Display version number and exit.
93
94 =cut
95
96 my $uri;
97
98 =item B<--connect URI> | B<-c URI>
99
100 If using libvirt, connect to the given I<URI>.  If omitted,
101 then we connect to the default libvirt hypervisor.
102
103 Libvirt is only used if you specify a C<domname> on the
104 command line.  If you specify guest block devices directly,
105 then libvirt is not used at all.
106
107 =cut
108
109 my $output = "text";
110
111 =back
112
113 The following options select the output format.  Use only one of them.
114 The default is a readable text report.
115
116 =over 4
117
118 =item B<--text> (default)
119
120 Plain text report.
121
122 =item B<--none>
123
124 Produce no output at all.
125
126 =item B<--xml>
127
128 If you select I<--xml> then you get XML output which can be fed
129 to other programs.
130
131 =item B<--yaml>
132
133 If you select I<--yaml> then you get YAML output which can be fed
134 to other programs.
135
136 =item B<--perl>
137
138 If you select I<--perl> then you get Perl structures output which
139 can be used directly in another Perl program.
140
141 =item B<--fish>
142
143 =item B<--ro-fish>
144
145 If you select I<--fish> then we print a L<guestfish(1)> command
146 line which will automatically mount up the filesystems on the
147 correct mount points.  Try this for example:
148
149  guestfish $(virt-inspector --fish guest.img)
150
151 I<--ro-fish> is the same, but the I<--ro> option is passed to
152 guestfish so that the filesystems are mounted read-only.
153
154 =item B<--query>
155
156 In "query mode" we answer common questions about the guest, such
157 as whether it is fullvirt or needs a Xen hypervisor to run.
158
159 See section I<QUERY MODE> below.
160
161 =cut
162
163 my $windows_registry;
164
165 =item B<--windows-registry>
166
167 This flag is ignored for compatibility with earlier releases of the
168 software.
169
170 In this version, if L<Win::Hivex(3)> is available, then we attempt to
171 parse information out of the Registry for any Windows guest.
172
173 =back
174
175 =cut
176
177 GetOptions ("help|?" => \$help,
178             "version" => \$version,
179             "connect|c=s" => \$uri,
180             "text" => sub { $output = "text" },
181             "none" => sub { $output = "none" },
182             "xml" => sub { $output = "xml" },
183             "yaml" => sub { $output = "yaml" },
184             "perl" => sub { $output = "perl" },
185             "fish" => sub { $output = "fish" },
186             "guestfish" => sub { $output = "fish" },
187             "ro-fish" => sub { $output = "ro-fish" },
188             "ro-guestfish" => sub { $output = "ro-fish" },
189             "query" => sub { $output = "query" },
190             "windows-registry" => \$windows_registry,
191     ) or pod2usage (2);
192 pod2usage (1) if $help;
193 if ($version) {
194     my $g = Sys::Guestfs->new ();
195     my %h = $g->version ();
196     print "$h{major}.$h{minor}.$h{release}$h{extra}\n";
197     exit
198 }
199 pod2usage (__"virt-inspector: no image or VM names given") if @ARGV == 0;
200
201 my $rw = 0;
202 $rw = 1 if $output eq "fish";
203 my $g;
204 my @images;
205 if ($uri) {
206     my ($conn, $dom);
207     ($g, $conn, $dom, @images) =
208         open_guest (\@ARGV, rw => $rw, address => $uri);
209 } else {
210     my ($conn, $dom);
211     ($g, $conn, $dom, @images) =
212         open_guest (\@ARGV, rw => $rw);
213 }
214
215 $g->launch ();
216
217 =head1 OUTPUT FORMAT
218
219  Operating system(s)
220  -------------------
221  Linux (distro + version)
222  Windows (version)
223     |
224     |
225     +--- Filesystems ---------- Installed apps --- Kernel & drivers
226          -----------            --------------     ----------------
227          mount point => device  List of apps       Extra information
228          mount point => device  and versions       about kernel(s)
229               ...                                  and drivers
230          swap => swap device
231          (plus lots of extra information
232          about each filesystem)
233
234 The output of virt-inspector is a complex two-level data structure.
235
236 At the top level is a list of the operating systems installed on the
237 guest.  (For the vast majority of guests, only a single OS is
238 installed.)  The data returned for the OS includes the name (Linux,
239 Windows), the distribution and version.
240
241 The diagram above shows what we return for each OS.
242
243 With the I<--xml> option the output is mapped into an XML document.
244 There is a RELAX-NG schema for this XML in the file
245 I<virt-inspector.rng> which normally ships with virt-inspector, or can
246 be found in the source.
247
248 With the I<--fish> or I<--ro-fish> option the mount points are mapped to
249 L<guestfish(1)> command line parameters, so that you can go in
250 afterwards and inspect the guest with everything mounted in the
251 right place.  For example:
252
253  guestfish $(virt-inspector --ro-fish guest.img)
254  ==> guestfish --ro -a guest.img -m /dev/VG/LV:/ -m /dev/sda1:/boot
255
256 =cut
257
258 # List of possible filesystems.
259 my @partitions = get_partitions ($g);
260
261 # Now query each one to build up a picture of what's in it.
262 my %fses =
263     inspect_all_partitions ($g, \@partitions);
264
265 #print "fses -----------\n";
266 #print Dumper(\%fses);
267
268 my $oses = inspect_operating_systems ($g, \%fses);
269
270 #print "oses -----------\n";
271 #print Dumper($oses);
272
273 # Mount up the disks so we can check for applications
274 # and kernels.  Skip this if the output is "*fish" because
275 # we don't need to know.
276
277 if ($output !~ /.*fish$/) {
278     my $root_dev;
279     foreach $root_dev (sort keys %$oses) {
280         my $os = $oses->{$root_dev};
281         mount_operating_system ($g, $os);
282         inspect_in_detail ($g, $os);
283         $g->umount_all ();
284     }
285 }
286
287 #----------------------------------------------------------------------
288 # Output.
289
290 if ($output eq "fish" || $output eq "ro-fish") {
291     my @osdevs = keys %$oses;
292     # This only works if there is a single OS.
293     die __"--fish output is only possible with a single OS\n" if @osdevs != 1;
294
295     my $root_dev = $osdevs[0];
296
297     if ($output eq "ro-fish") {
298         print "--ro ";
299     }
300
301     print "-a $_ " foreach @images;
302
303     my $mounts = $oses->{$root_dev}->{mounts};
304     # Have to mount / first.  Luckily '/' is early in the ASCII
305     # character set, so this should be OK.
306     foreach (sort keys %$mounts) {
307         print "-m $mounts->{$_}:$_ " if $_ ne "swap" && $_ ne "none";
308     }
309     print "\n"
310 }
311
312 # Perl output.
313 elsif ($output eq "perl") {
314     print Dumper(%$oses);
315 }
316
317 # YAML output
318 elsif ($output eq "yaml") {
319     die __"virt-inspector: no YAML support\n"
320         unless exists $INC{"YAML/Any.pm"};
321
322     print Dump(%$oses);
323 }
324
325 # Plain text output (the default).
326 elsif ($output eq "text") {
327     output_text ();
328 }
329
330 # XML output.
331 elsif ($output eq "xml") {
332     output_xml ();
333 }
334
335 # Query mode.
336 elsif ($output eq "query") {
337     output_query ();
338 }
339
340 sub output_text
341 {
342     output_text_os ($oses->{$_}) foreach sort keys %$oses;
343 }
344
345 sub output_text_os
346 {
347     my $os = shift;
348
349     print $os->{os}, " " if exists $os->{os};
350     print $os->{distro}, " " if exists $os->{distro};
351     print $os->{arch}, " " if exists $os->{arch};
352     print $os->{major_version} if exists $os->{major_version};
353     print ".", $os->{minor_version} if exists $os->{minor_version};
354     print " (", $os->{product_name}, ")" if exists $os->{product_name};
355     print " ";
356     print "on ", $os->{root_device}, ":\n";
357
358     print __"  Mountpoints:\n";
359     my $mounts = $os->{mounts};
360     foreach (sort keys %$mounts) {
361         printf "    %-30s %s\n", $mounts->{$_}, $_
362     }
363
364     print __"  Filesystems:\n";
365     my $filesystems = $os->{filesystems};
366     foreach (sort keys %$filesystems) {
367         print "    $_:\n";
368         print "      label: $filesystems->{$_}{label}\n"
369             if exists $filesystems->{$_}{label};
370         print "      UUID: $filesystems->{$_}{uuid}\n"
371             if exists $filesystems->{$_}{uuid};
372         print "      type: $filesystems->{$_}{fstype}\n"
373             if exists $filesystems->{$_}{fstype};
374         print "      content: $filesystems->{$_}{content}\n"
375             if exists $filesystems->{$_}{content};
376     }
377
378     if (exists $os->{modprobe_aliases}) {
379         my %aliases = %{$os->{modprobe_aliases}};
380         my @keys = sort keys %aliases;
381         if (@keys) {
382             print __"  Modprobe aliases:\n";
383             foreach (@keys) {
384                 printf "    %-30s %s\n", $_, $aliases{$_}->{modulename}
385             }
386         }
387     }
388
389     if (exists $os->{initrd_modules}) {
390         my %modvers = %{$os->{initrd_modules}};
391         my @keys = sort keys %modvers;
392         if (@keys) {
393             print __"  Initrd modules:\n";
394             foreach (@keys) {
395                 my @modules = @{$modvers{$_}};
396                 print "    $_:\n";
397                 print "      $_\n" foreach @modules;
398             }
399         }
400     }
401
402     print __"  Applications:\n";
403     my @apps =  @{$os->{apps}};
404     foreach (@apps) {
405         print "    $_->{name} $_->{version}\n"
406     }
407
408     if ($os->{kernels}) {
409         print __"  Kernels:\n";
410         my @kernels = @{$os->{kernels}};
411         foreach (@kernels) {
412             print "    $_->{version} ($_->{arch})\n";
413             my @modules = @{$_->{modules}};
414             foreach (@modules) {
415                 print "      $_\n";
416             }
417         }
418     }
419
420     if (exists $os->{root}->{registry}) {
421         print __"  Windows Registry entries:\n";
422         # These are just lumps of text - dump them out.
423         foreach (@{$os->{root}->{registry}}) {
424             print "$_\n";
425         }
426     }
427 }
428
429 sub output_xml
430 {
431     my $xml = new XML::Writer(DATA_MODE => 1, DATA_INDENT => 2);
432
433     $xml->startTag("operatingsystems");
434     output_xml_os ($oses->{$_}, $xml) foreach sort keys %$oses;
435     $xml->endTag("operatingsystems");
436
437     $xml->end();
438 }
439
440 sub output_xml_os
441 {
442     my ($os, $xml) = @_;
443
444     $xml->startTag("operatingsystem");
445
446     foreach ( [ "name" => "os" ],
447               [ "distro" => "distro" ],
448               [ "product_name" => "product_name" ],
449               [ "arch" => "arch" ],
450               [ "major_version" => "major_version" ],
451               [ "minor_version" => "minor_version" ],
452               [ "package_format" => "package_format" ],
453               [ "package_management" => "package_management" ],
454               [ "root" => "root_device" ] ) {
455         $xml->dataElement($_->[0], $os->{$_->[1]}) if exists $os->{$_->[1]};
456     }
457
458     $xml->startTag("mountpoints");
459     my $mounts = $os->{mounts};
460     foreach (sort keys %$mounts) {
461         $xml->dataElement("mountpoint", $_, "dev" => $mounts->{$_});
462     }
463     $xml->endTag("mountpoints");
464
465     $xml->startTag("filesystems");
466     my $filesystems = $os->{filesystems};
467     foreach (sort keys %$filesystems) {
468         $xml->startTag("filesystem", "dev" => $_);
469
470         foreach my $field ( [ "label" => "label" ],
471                             [ "uuid" => "uuid" ],
472                             [ "type" => "fstype" ],
473                             [ "content" => "content" ],
474                             [ "spec" => "spec" ] ) {
475             $xml->dataElement($field->[0], $filesystems->{$_}{$field->[1]})
476                 if exists $filesystems->{$_}{$field->[1]};
477         }
478
479         $xml->endTag("filesystem");
480     }
481     $xml->endTag("filesystems");
482
483     if (exists $os->{modprobe_aliases}) {
484         my %aliases = %{$os->{modprobe_aliases}};
485         my @keys = sort keys %aliases;
486         if (@keys) {
487             $xml->startTag("modprobealiases");
488             foreach (@keys) {
489                 $xml->startTag("alias", "device" => $_);
490
491                 foreach my $field ( [ "modulename" => "modulename" ],
492                                     [ "augeas" => "augeas" ],
493                                     [ "file" => "file" ] ) {
494                     $xml->dataElement($field->[0], $aliases{$_}->{$field->[1]});
495                 }
496
497                 $xml->endTag("alias");
498             }
499             $xml->endTag("modprobealiases");
500         }
501     }
502
503     if (exists $os->{initrd_modules}) {
504         my %modvers = %{$os->{initrd_modules}};
505         my @keys = sort keys %modvers;
506         if (@keys) {
507             $xml->startTag("initrds");
508             foreach (@keys) {
509                 my @modules = @{$modvers{$_}};
510                 $xml->startTag("initrd", "version" => $_);
511                 $xml->dataElement("module", $_) foreach @modules;
512                 $xml->endTag("initrd");
513             }
514             $xml->endTag("initrds");
515         }
516     }
517
518     $xml->startTag("applications");
519     my @apps =  @{$os->{apps}};
520     foreach (@apps) {
521         $xml->startTag("application");
522         $xml->dataElement("name", $_->{name});
523         $xml->dataElement("epoch", $_->{epoch}) if defined $_->{epoch};
524         $xml->dataElement("version", $_->{version});
525         $xml->dataElement("release", $_->{release});
526         $xml->dataElement("arch", $_->{arch});
527         $xml->endTag("application");
528     }
529     $xml->endTag("applications");
530
531     if(defined($os->{boot}) && defined($os->{boot}->{configs})) {
532         my $default = $os->{boot}->{default};
533         my $configs = $os->{boot}->{configs};
534
535         $xml->startTag("boot");
536         for(my $i = 0; $i < scalar(@$configs); $i++) {
537             my $config = $configs->[$i];
538
539             my @attrs = ();
540             push(@attrs, ("default" => 1)) if($default == $i);
541             $xml->startTag("config", @attrs);
542             $xml->dataElement("title", $config->{title});
543             $xml->dataElement("kernel", $config->{kernel}->{version})
544                 if(defined($config->{kernel}));
545             $xml->dataElement("cmdline", $config->{cmdline})
546                 if(defined($config->{cmdline}));
547             $xml->endTag("config");
548         }
549         $xml->endTag("boot");
550     }
551
552     if ($os->{kernels}) {
553         $xml->startTag("kernels");
554         my @kernels = @{$os->{kernels}};
555         foreach (@kernels) {
556             $xml->startTag("kernel",
557                            "version" => $_->{version},
558                            "arch" => $_->{arch});
559             $xml->startTag("modules");
560             my @modules = @{$_->{modules}};
561             foreach (@modules) {
562                 $xml->dataElement("module", $_);
563             }
564             $xml->endTag("modules");
565             $xml->dataElement("path", $_->{path}) if(defined($_->{path}));
566             $xml->dataElement("package", $_->{package}) if(defined($_->{package}));
567             $xml->endTag("kernel");
568         }
569         $xml->endTag("kernels");
570     }
571
572     if (exists $os->{root}->{registry}) {
573         $xml->startTag("windowsregistryentries");
574         # These are just lumps of text - dump them out.
575         foreach (@{$os->{root}->{registry}}) {
576             $xml->dataElement("windowsregistryentry", $_);
577         }
578         $xml->endTag("windowsregistryentries");
579     }
580
581     $xml->endTag("operatingsystem");
582 }
583
584 =head1 QUERY MODE
585
586 When you use C<virt-inspector --query>, the output is a series of
587 lines of the form:
588
589  windows=no
590  linux=yes
591  fullvirt=yes
592  xen_pv_drivers=no
593
594 (each answer is usually C<yes> or C<no>, or the line is completely
595 missing if we could not determine the answer at all).
596
597 If the guest is multiboot, you can get apparently conflicting answers
598 (eg. C<windows=yes> and C<linux=yes>, or a guest which is both
599 fullvirt and has a Xen PV kernel).  This is normal, and just means
600 that the guest can do both things, although it might require operator
601 intervention such as selecting a boot option when the guest is
602 booting.
603
604 This section describes the full range of answers possible.
605
606 =over 4
607
608 =cut
609
610 sub output_query
611 {
612     output_query_windows ();
613     output_query_linux ();
614     output_query_rhel ();
615     output_query_fedora ();
616     output_query_debian ();
617     output_query_fullvirt ();
618     output_query_xen_domU_kernel ();
619     output_query_xen_pv_drivers ();
620     output_query_virtio_drivers ();
621     output_query_kernel_arch ();
622     output_query_userspace_arch ();
623 }
624
625 =item windows=(yes|no)
626
627 Answer C<yes> if Microsoft Windows is installed in the guest.
628
629 =cut
630
631 sub output_query_windows
632 {
633     my $windows = "no";
634     foreach my $os (keys %$oses) {
635         $windows="yes" if $oses->{$os}->{os} eq "windows";
636     }
637     print "windows=$windows\n";
638 }
639
640 =item linux=(yes|no)
641
642 Answer C<yes> if a Linux kernel is installed in the guest.
643
644 =cut
645
646 sub output_query_linux
647 {
648     my $linux = "no";
649     foreach my $os (keys %$oses) {
650         $linux="yes" if $oses->{$os}->{os} eq "linux";
651     }
652     print "linux=$linux\n";
653 }
654
655 =item rhel=(yes|no)
656
657 Answer C<yes> if the guest contains Red Hat Enterprise Linux.
658
659 =cut
660
661 sub output_query_rhel
662 {
663     my $rhel = "no";
664     foreach my $os (keys %$oses) {
665         $rhel="yes" if ($oses->{$os}->{os} eq "linux" &&
666                         $oses->{$os}->{distro} eq "rhel");
667     }
668     print "rhel=$rhel\n";
669 }
670
671 =item fedora=(yes|no)
672
673 Answer C<yes> if the guest contains the Fedora Linux distribution.
674
675 =cut
676
677 sub output_query_fedora
678 {
679     my $fedora = "no";
680     foreach my $os (keys %$oses) {
681         $fedora="yes" if $oses->{$os}->{os} eq "linux" && $oses->{$os}->{distro} eq "fedora";
682     }
683     print "fedora=$fedora\n";
684 }
685
686 =item debian=(yes|no)
687
688 Answer C<yes> if the guest contains the Debian Linux distribution.
689
690 =cut
691
692 sub output_query_debian
693 {
694     my $debian = "no";
695     foreach my $os (keys %$oses) {
696         $debian="yes" if $oses->{$os}->{os} eq "linux" && $oses->{$os}->{distro} eq "debian";
697     }
698     print "debian=$debian\n";
699 }
700
701 =item fullvirt=(yes|no)
702
703 Answer C<yes> if there is at least one operating system kernel
704 installed in the guest which runs fully virtualized.  Such a guest
705 would require a hypervisor which supports full system virtualization.
706
707 =cut
708
709 sub output_query_fullvirt
710 {
711     # The assumption is full-virt, unless all installed kernels
712     # are identified as paravirt.
713     # XXX Fails on Windows guests.
714     foreach my $os (keys %$oses) {
715         foreach my $kernel (@{$oses->{$os}->{kernels}}) {
716             my $is_pv = $kernel->{version} =~ m/xen/;
717             unless ($is_pv) {
718                 print "fullvirt=yes\n";
719                 return;
720             }
721         }
722     }
723     print "fullvirt=no\n";
724 }
725
726 =item xen_domU_kernel=(yes|no)
727
728 Answer C<yes> if there is at least one Linux kernel installed in
729 the guest which is compiled as a Xen DomU (a Xen paravirtualized
730 guest).
731
732 =cut
733
734 sub output_query_xen_domU_kernel
735 {
736     foreach my $os (keys %$oses) {
737         foreach my $kernel (@{$oses->{$os}->{kernels}}) {
738             my $is_xen = $kernel->{version} =~ m/xen/;
739             if ($is_xen) {
740                 print "xen_domU_kernel=yes\n";
741                 return;
742             }
743         }
744     }
745     print "xen_domU_kernel=no\n";
746 }
747
748 =item xen_pv_drivers=(yes|no)
749
750 Answer C<yes> if the guest has Xen paravirtualized drivers installed
751 (usually the kernel itself will be fully virtualized, but the PV
752 drivers have been installed by the administrator for performance
753 reasons).
754
755 =cut
756
757 sub output_query_xen_pv_drivers
758 {
759     foreach my $os (keys %$oses) {
760         foreach my $kernel (@{$oses->{$os}->{kernels}}) {
761             foreach my $module (@{$kernel->{modules}}) {
762                 if ($module =~ m/xen-/) {
763                     print "xen_pv_drivers=yes\n";
764                     return;
765                 }
766             }
767         }
768     }
769     print "xen_pv_drivers=no\n";
770 }
771
772 =item virtio_drivers=(yes|no)
773
774 Answer C<yes> if the guest has virtio paravirtualized drivers
775 installed.  Virtio drivers are commonly used to improve the
776 performance of KVM.
777
778 =cut
779
780 sub output_query_virtio_drivers
781 {
782     foreach my $os (keys %$oses) {
783         foreach my $kernel (@{$oses->{$os}->{kernels}}) {
784             foreach my $module (@{$kernel->{modules}}) {
785                 if ($module =~ m/virtio_/) {
786                     print "virtio_drivers=yes\n";
787                     return;
788                 }
789             }
790         }
791     }
792     print "virtio_drivers=no\n";
793 }
794
795 =item userspace_arch=(x86_64|...)
796
797 Print the architecture of userspace.
798
799 NB. For multi-boot VMs this can print several lines.
800
801 =cut
802
803 sub output_query_userspace_arch
804 {
805     my %arches;
806
807     foreach my $os (keys %$oses) {
808         $arches{$oses->{$os}->{arch}} = 1 if exists $oses->{$os}->{arch};
809     }
810
811     foreach (sort keys %arches) {
812         print "userspace_arch=$_\n";
813     }
814 }
815
816 =item kernel_arch=(x86_64|...)
817
818 Print the architecture of the kernel.
819
820 NB. For multi-boot VMs this can print several lines.
821
822 =cut
823
824 sub output_query_kernel_arch
825 {
826     my %arches;
827
828     foreach my $os (keys %$oses) {
829         foreach my $kernel (@{$oses->{$os}->{kernels}}) {
830             $arches{$kernel->{arch}} = 1 if exists $kernel->{arch};
831         }
832     }
833
834     foreach (sort keys %arches) {
835         print "kernel_arch=$_\n";
836     }
837 }
838
839 =back
840
841 =head1 SEE ALSO
842
843 L<guestfs(3)>,
844 L<guestfish(1)>,
845 L<Sys::Guestfs(3)>,
846 L<Sys::Guestfs::Lib(3)>,
847 L<Sys::Virt(3)>,
848 L<http://libguestfs.org/>.
849
850 =head1 AUTHORS
851
852 Richard W.M. Jones L<http://people.redhat.com/~rjones/>
853
854 Matthew Booth L<mbooth@redhat.com>
855
856 =head1 COPYRIGHT
857
858 Copyright (C) 2009 Red Hat Inc.
859
860 This program is free software; you can redistribute it and/or modify
861 it under the terms of the GNU General Public License as published by
862 the Free Software Foundation; either version 2 of the License, or
863 (at your option) any later version.
864
865 This program is distributed in the hope that it will be useful,
866 but WITHOUT ANY WARRANTY; without even the implied warranty of
867 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
868 GNU General Public License for more details.
869
870 You should have received a copy of the GNU General Public License
871 along with this program; if not, write to the Free Software
872 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.