3 # Copyright (C) 2009 Red Hat Inc.
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.
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.
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.
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);
30 use String::ShellQuote qw(shell_quote);
31 use Locale::TextDomain 'libguestfs';
34 eval "use YAML::Any;";
40 virt-inspector - Display OS version, kernel, drivers, mount points, applications, etc. in a virtual machine
44 virt-inspector [--connect URI] domname
46 virt-inspector guest.img [guest.img ...]
50 B<virt-inspector> examines a virtual machine and tries to determine
51 the version of the OS, the kernel version, what drivers are installed,
52 whether the virtual machine is fully virtualized (FV) or
53 para-virtualized (PV), what applications are installed and more.
55 Virt-inspector can produce output in several formats, including a
56 readable text report, and XML for feeding into other programs.
58 In the normal usage, use C<virt-inspector domname> where C<domname> is
59 the libvirt domain (see: C<virsh list --all>).
61 You can also run virt-inspector directly on disk images from a single
62 virtual machine. Use C<virt-inspector guest.img>. In rare cases a
63 domain has several block devices, in which case you should list them
64 one after another, with the first corresponding to the guest's
65 C</dev/sda>, the second to the guest's C</dev/sdb> and so on.
67 Virt-inspector can only inspect and report upon I<one domain at a
68 time>. To inspect several virtual machines, you have to run
69 virt-inspector several times (for example, from a shell script
72 Because virt-inspector needs direct access to guest images, it won't
73 normally work over remote libvirt connections.
93 Display version number and exit.
99 =item B<--connect URI> | B<-c URI>
101 If using libvirt, connect to the given I<URI>. If omitted,
102 then we connect to the default libvirt hypervisor.
104 Libvirt is only used if you specify a C<domname> on the
105 command line. If you specify guest block devices directly,
106 then libvirt is not used at all.
114 The following options select the output format. Use only one of them.
115 The default is a readable text report.
119 =item B<--text> (default)
125 Produce no output at all.
129 If you select I<--xml> then you get XML output which can be fed
134 If you select I<--yaml> then you get YAML output which can be fed
139 If you select I<--perl> then you get Perl structures output which
140 can be used directly in another Perl program.
146 If you select I<--fish> then we print a L<guestfish(1)> command
147 line which will automatically mount up the filesystems on the
148 correct mount points. Try this for example:
150 guestfish $(virt-inspector --fish guest.img)
152 I<--ro-fish> is the same, but the I<--ro> option is passed to
153 guestfish so that the filesystems are mounted read-only.
157 In "query mode" we answer common questions about the guest, such
158 as whether it is fullvirt or needs a Xen hypervisor to run.
160 See section I<QUERY MODE> below.
164 my $windows_registry;
166 =item B<--windows-registry>
168 This flag is ignored for compatibility with earlier releases of the
171 In this version, if L<Win::Hivex(3)> is available, then we attempt to
172 parse information out of the Registry for any Windows guest.
178 GetOptions ("help|?" => \$help,
179 "version" => \$version,
180 "connect|c=s" => \$uri,
181 "text" => sub { $output = "text" },
182 "none" => sub { $output = "none" },
183 "xml" => sub { $output = "xml" },
184 "yaml" => sub { $output = "yaml" },
185 "perl" => sub { $output = "perl" },
186 "fish" => sub { $output = "fish" },
187 "guestfish" => sub { $output = "fish" },
188 "ro-fish" => sub { $output = "ro-fish" },
189 "ro-guestfish" => sub { $output = "ro-fish" },
190 "query" => sub { $output = "query" },
191 "windows-registry" => \$windows_registry,
193 pod2usage (1) if $help;
195 my $g = Sys::Guestfs->new ();
196 my %h = $g->version ();
197 print "$h{major}.$h{minor}.$h{release}$h{extra}\n";
200 pod2usage (__"virt-inspector: no image or VM names given") if @ARGV == 0;
204 # XXX This is a bug: Originally we intended to open the guest with
205 # rw=>1 in order to tell Sys::Guestfs::Lib that we should disallow
206 # active domains. However this also has the effect of opening the
207 # disk image in write mode, and in any case we don't use this option
208 # in guestfish any more since we moved all the inspection code into
209 # the core library. We should drop the fish output modes completely.
210 $rw = 1 if $output eq "fish";
216 ($g, $conn, $dom, @images) =
217 open_guest (\@ARGV, rw => $rw, address => $uri);
220 ($g, $conn, $dom, @images) =
221 open_guest (\@ARGV, rw => $rw);
230 Linux (distro + version)
234 +--- Filesystems ---------- Installed apps --- Kernel & drivers
235 ----------- -------------- ----------------
236 mount point => device List of apps Extra information
237 mount point => device and versions about kernel(s)
240 (plus lots of extra information
241 about each filesystem)
243 The output of virt-inspector is a complex two-level data structure.
245 At the top level is a list of the operating systems installed on the
246 guest. (For the vast majority of guests, only a single OS is
247 installed.) The data returned for the OS includes the name (Linux,
248 Windows), the distribution and version.
250 The diagram above shows what we return for each OS.
252 With the I<--xml> option the output is mapped into an XML document.
253 There is a RELAX-NG schema for this XML in the file
254 I<virt-inspector.rng> which normally ships with virt-inspector, or can
255 be found in the source.
257 With the I<--fish> or I<--ro-fish> option the mount points are mapped to
258 L<guestfish(1)> command line parameters, so that you can go in
259 afterwards and inspect the guest with everything mounted in the
260 right place. For example:
262 guestfish $(virt-inspector --ro-fish guest.img)
263 ==> guestfish --ro -a guest.img -m /dev/VG/LV:/ -m /dev/sda1:/boot
267 # List of possible filesystems.
268 my @partitions = get_partitions ($g);
270 # Now query each one to build up a picture of what's in it.
272 inspect_all_partitions ($g, \@partitions);
274 #print "fses -----------\n";
275 #print Dumper(\%fses);
277 my $oses = inspect_operating_systems ($g, \%fses);
279 #print "oses -----------\n";
280 #print Dumper($oses);
282 # Mount up the disks so we can check for applications
283 # and kernels. Skip this if the output is "*fish" because
284 # we don't need to know.
286 if ($output !~ /.*fish$/) {
288 foreach $root_dev (sort keys %$oses) {
289 my $os = $oses->{$root_dev};
290 mount_operating_system ($g, $os);
291 inspect_in_detail ($g, $os);
296 #----------------------------------------------------------------------
299 if ($output eq "fish" || $output eq "ro-fish") {
300 my @osdevs = keys %$oses;
301 # This only works if there is a single OS.
302 die __"--fish output is only possible with a single OS\n" if @osdevs != 1;
304 my $root_dev = $osdevs[0];
306 if ($output eq "ro-fish") {
311 printf "-a %s ", shell_quote ($_);
314 my $mounts = $oses->{$root_dev}->{mounts};
315 # Have to mount / first. Luckily '/' is early in the ASCII
316 # character set, so this should be OK.
317 foreach (sort keys %$mounts) {
318 if ($_ ne "swap" && $_ ne "none") {
319 printf "-m %s ", shell_quote ("$mounts->{$_}:$_");
326 elsif ($output eq "perl") {
327 print Dumper(%$oses);
331 elsif ($output eq "yaml") {
332 die __"virt-inspector: no YAML support, try installing perl-YAML or libyaml-perl\n"
333 unless exists $INC{"YAML/Any.pm"};
338 # Plain text output (the default).
339 elsif ($output eq "text") {
344 elsif ($output eq "xml") {
349 elsif ($output eq "query") {
355 output_text_os ($oses->{$_}) foreach sort keys %$oses;
362 print $os->{os}, " " if exists $os->{os};
363 print $os->{distro}, " " if exists $os->{distro};
364 print $os->{arch}, " " if exists $os->{arch};
365 print $os->{major_version} if exists $os->{major_version};
366 print ".", $os->{minor_version} if exists $os->{minor_version};
367 print " (", $os->{product_name}, ")" if exists $os->{product_name};
369 print "on ", $os->{root_device}, ":\n";
371 print __" Mountpoints:\n";
372 my $mounts = $os->{mounts};
373 foreach (sort keys %$mounts) {
374 printf " %-30s %s\n", $mounts->{$_}, $_
377 print __" Filesystems:\n";
378 my $filesystems = $os->{filesystems};
379 foreach (sort keys %$filesystems) {
381 print " label: $filesystems->{$_}{label}\n"
382 if exists $filesystems->{$_}{label};
383 print " UUID: $filesystems->{$_}{uuid}\n"
384 if exists $filesystems->{$_}{uuid};
385 print " type: $filesystems->{$_}{fstype}\n"
386 if exists $filesystems->{$_}{fstype};
387 print " content: $filesystems->{$_}{content}\n"
388 if exists $filesystems->{$_}{content};
391 if (exists $os->{modprobe_aliases}) {
392 my %aliases = %{$os->{modprobe_aliases}};
393 my @keys = sort keys %aliases;
395 print __" Modprobe aliases:\n";
397 printf " %-30s %s\n", $_, $aliases{$_}->{modulename}
402 if (exists $os->{initrd_modules}) {
403 my %modvers = %{$os->{initrd_modules}};
404 my @keys = sort keys %modvers;
406 print __" Initrd modules:\n";
408 my @modules = @{$modvers{$_}};
410 print " $_\n" foreach @modules;
415 print __" Applications:\n";
416 my @apps = @{$os->{apps}};
418 print " $_->{name} $_->{version}\n"
421 if ($os->{kernels}) {
422 print __" Kernels:\n";
423 my @kernels = @{$os->{kernels}};
425 print " $_->{version} ($_->{arch})\n";
426 my @modules = @{$_->{modules}};
433 if (exists $os->{root}->{registry}) {
434 print __" Windows Registry entries:\n";
435 # These are just lumps of text - dump them out.
436 foreach (@{$os->{root}->{registry}}) {
444 my $xml = new XML::Writer(DATA_MODE => 1, DATA_INDENT => 2);
446 $xml->startTag("operatingsystems");
447 output_xml_os ($oses->{$_}, $xml) foreach sort keys %$oses;
448 $xml->endTag("operatingsystems");
457 $xml->startTag("operatingsystem");
459 foreach ( [ "name" => "os" ],
460 [ "distro" => "distro" ],
461 [ "product_name" => "product_name" ],
462 [ "arch" => "arch" ],
463 [ "major_version" => "major_version" ],
464 [ "minor_version" => "minor_version" ],
465 [ "package_format" => "package_format" ],
466 [ "package_management" => "package_management" ],
467 [ "root" => "root_device" ] ) {
468 $xml->dataElement($_->[0], $os->{$_->[1]}) if exists $os->{$_->[1]};
471 $xml->startTag("mountpoints");
472 my $mounts = $os->{mounts};
473 foreach (sort keys %$mounts) {
474 $xml->dataElement("mountpoint", $_, "dev" => $mounts->{$_});
476 $xml->endTag("mountpoints");
478 $xml->startTag("filesystems");
479 my $filesystems = $os->{filesystems};
480 foreach (sort keys %$filesystems) {
481 $xml->startTag("filesystem", "dev" => $_);
483 foreach my $field ( [ "label" => "label" ],
484 [ "uuid" => "uuid" ],
485 [ "type" => "fstype" ],
486 [ "content" => "content" ],
487 [ "spec" => "spec" ] ) {
488 $xml->dataElement($field->[0], $filesystems->{$_}{$field->[1]})
489 if exists $filesystems->{$_}{$field->[1]};
492 $xml->endTag("filesystem");
494 $xml->endTag("filesystems");
496 if (exists $os->{modprobe_aliases}) {
497 my %aliases = %{$os->{modprobe_aliases}};
498 my @keys = sort keys %aliases;
500 $xml->startTag("modprobealiases");
502 $xml->startTag("alias", "device" => $_);
504 foreach my $field ( [ "modulename" => "modulename" ],
505 [ "augeas" => "augeas" ],
506 [ "file" => "file" ] ) {
507 $xml->dataElement($field->[0], $aliases{$_}->{$field->[1]});
510 $xml->endTag("alias");
512 $xml->endTag("modprobealiases");
516 if (exists $os->{initrd_modules}) {
517 my %modvers = %{$os->{initrd_modules}};
518 my @keys = sort keys %modvers;
520 $xml->startTag("initrds");
522 my @modules = @{$modvers{$_}};
523 $xml->startTag("initrd", "version" => $_);
524 $xml->dataElement("module", $_) foreach @modules;
525 $xml->endTag("initrd");
527 $xml->endTag("initrds");
531 $xml->startTag("applications");
532 my @apps = @{$os->{apps}};
534 $xml->startTag("application");
535 $xml->dataElement("name", $_->{name});
536 $xml->dataElement("epoch", $_->{epoch}) if defined $_->{epoch};
537 $xml->dataElement("version", $_->{version});
538 $xml->dataElement("release", $_->{release});
539 $xml->dataElement("arch", $_->{arch});
540 $xml->endTag("application");
542 $xml->endTag("applications");
544 if(defined($os->{boot}) && defined($os->{boot}->{configs})) {
545 my $default = $os->{boot}->{default};
546 my $configs = $os->{boot}->{configs};
548 $xml->startTag("boot");
549 for(my $i = 0; $i < scalar(@$configs); $i++) {
550 my $config = $configs->[$i];
553 push(@attrs, ("default" => 1)) if($default == $i);
554 $xml->startTag("config", @attrs);
555 $xml->dataElement("title", $config->{title});
556 $xml->dataElement("kernel", $config->{kernel}->{version})
557 if(defined($config->{kernel}));
558 $xml->dataElement("cmdline", $config->{cmdline})
559 if(defined($config->{cmdline}));
560 $xml->endTag("config");
562 $xml->endTag("boot");
565 if ($os->{kernels}) {
566 $xml->startTag("kernels");
567 my @kernels = @{$os->{kernels}};
569 $xml->startTag("kernel",
570 "version" => $_->{version},
571 "arch" => $_->{arch});
572 $xml->startTag("modules");
573 my @modules = @{$_->{modules}};
575 $xml->dataElement("module", $_);
577 $xml->endTag("modules");
578 $xml->dataElement("path", $_->{path}) if(defined($_->{path}));
579 $xml->dataElement("package", $_->{package}) if(defined($_->{package}));
580 $xml->endTag("kernel");
582 $xml->endTag("kernels");
585 if (exists $os->{root}->{registry}) {
586 $xml->startTag("windowsregistryentries");
587 # These are just lumps of text - dump them out.
588 foreach (@{$os->{root}->{registry}}) {
589 $xml->dataElement("windowsregistryentry", $_);
591 $xml->endTag("windowsregistryentries");
594 $xml->endTag("operatingsystem");
599 When you use C<virt-inspector --query>, the output is a series of
607 (each answer is usually C<yes> or C<no>, or the line is completely
608 missing if we could not determine the answer at all).
610 If the guest is multiboot, you can get apparently conflicting answers
611 (eg. C<windows=yes> and C<linux=yes>, or a guest which is both
612 fullvirt and has a Xen PV kernel). This is normal, and just means
613 that the guest can do both things, although it might require operator
614 intervention such as selecting a boot option when the guest is
617 This section describes the full range of answers possible.
625 output_query_windows ();
626 output_query_linux ();
627 output_query_rhel ();
628 output_query_fedora ();
629 output_query_debian ();
630 output_query_fullvirt ();
631 output_query_xen_domU_kernel ();
632 output_query_xen_pv_drivers ();
633 output_query_virtio_drivers ();
634 output_query_kernel_arch ();
635 output_query_userspace_arch ();
638 =item windows=(yes|no)
640 Answer C<yes> if Microsoft Windows is installed in the guest.
644 sub output_query_windows
647 foreach my $os (keys %$oses) {
648 $windows="yes" if $oses->{$os}->{os} eq "windows";
650 print "windows=$windows\n";
655 Answer C<yes> if a Linux kernel is installed in the guest.
659 sub output_query_linux
662 foreach my $os (keys %$oses) {
663 $linux="yes" if $oses->{$os}->{os} eq "linux";
665 print "linux=$linux\n";
670 Answer C<yes> if the guest contains Red Hat Enterprise Linux.
674 sub output_query_rhel
677 foreach my $os (keys %$oses) {
678 $rhel="yes" if ($oses->{$os}->{os} eq "linux" &&
679 $oses->{$os}->{distro} eq "rhel");
681 print "rhel=$rhel\n";
684 =item fedora=(yes|no)
686 Answer C<yes> if the guest contains the Fedora Linux distribution.
690 sub output_query_fedora
693 foreach my $os (keys %$oses) {
694 $fedora="yes" if $oses->{$os}->{os} eq "linux" && $oses->{$os}->{distro} eq "fedora";
696 print "fedora=$fedora\n";
699 =item debian=(yes|no)
701 Answer C<yes> if the guest contains the Debian Linux distribution.
705 sub output_query_debian
708 foreach my $os (keys %$oses) {
709 $debian="yes" if $oses->{$os}->{os} eq "linux" && $oses->{$os}->{distro} eq "debian";
711 print "debian=$debian\n";
714 =item fullvirt=(yes|no)
716 Answer C<yes> if there is at least one operating system kernel
717 installed in the guest which runs fully virtualized. Such a guest
718 would require a hypervisor which supports full system virtualization.
722 sub output_query_fullvirt
724 # The assumption is full-virt, unless all installed kernels
725 # are identified as paravirt.
726 # XXX Fails on Windows guests.
727 foreach my $os (keys %$oses) {
728 foreach my $kernel (@{$oses->{$os}->{kernels}}) {
729 my $is_pv = $kernel->{version} =~ m/xen/;
731 print "fullvirt=yes\n";
736 print "fullvirt=no\n";
739 =item xen_domU_kernel=(yes|no)
741 Answer C<yes> if there is at least one Linux kernel installed in
742 the guest which is compiled as a Xen DomU (a Xen paravirtualized
747 sub output_query_xen_domU_kernel
749 foreach my $os (keys %$oses) {
750 foreach my $kernel (@{$oses->{$os}->{kernels}}) {
751 my $is_xen = $kernel->{version} =~ m/xen/;
753 print "xen_domU_kernel=yes\n";
758 print "xen_domU_kernel=no\n";
761 =item xen_pv_drivers=(yes|no)
763 Answer C<yes> if the guest has Xen paravirtualized drivers installed
764 (usually the kernel itself will be fully virtualized, but the PV
765 drivers have been installed by the administrator for performance
770 sub output_query_xen_pv_drivers
772 foreach my $os (keys %$oses) {
773 foreach my $kernel (@{$oses->{$os}->{kernels}}) {
774 foreach my $module (@{$kernel->{modules}}) {
775 if ($module =~ m/xen-/) {
776 print "xen_pv_drivers=yes\n";
782 print "xen_pv_drivers=no\n";
785 =item virtio_drivers=(yes|no)
787 Answer C<yes> if the guest has virtio paravirtualized drivers
788 installed. Virtio drivers are commonly used to improve the
793 sub output_query_virtio_drivers
795 foreach my $os (keys %$oses) {
796 foreach my $kernel (@{$oses->{$os}->{kernels}}) {
797 foreach my $module (@{$kernel->{modules}}) {
798 if ($module =~ m/virtio_/) {
799 print "virtio_drivers=yes\n";
805 print "virtio_drivers=no\n";
808 =item userspace_arch=(x86_64|...)
810 Print the architecture of userspace.
812 NB. For multi-boot VMs this can print several lines.
816 sub output_query_userspace_arch
820 foreach my $os (keys %$oses) {
821 $arches{$oses->{$os}->{arch}} = 1 if exists $oses->{$os}->{arch};
824 foreach (sort keys %arches) {
825 print "userspace_arch=$_\n";
829 =item kernel_arch=(x86_64|...)
831 Print the architecture of the kernel.
833 NB. For multi-boot VMs this can print several lines.
837 sub output_query_kernel_arch
841 foreach my $os (keys %$oses) {
842 foreach my $kernel (@{$oses->{$os}->{kernels}}) {
843 $arches{$kernel->{arch}} = 1 if exists $kernel->{arch};
847 foreach (sort keys %arches) {
848 print "kernel_arch=$_\n";
856 Libvirt guest names can contain arbitrary characters, some of which
857 have meaning to the shell such as C<#> and space. You may need to
858 quote or escape these characters on the command line. See the shell
859 manual page L<sh(1)> for details.
866 L<Sys::Guestfs::Lib(3)>,
868 L<http://libguestfs.org/>.
872 Richard W.M. Jones L<http://people.redhat.com/~rjones/>
874 Matthew Booth L<mbooth@redhat.com>
878 Copyright (C) 2009 Red Hat Inc.
880 This program is free software; you can redistribute it and/or modify
881 it under the terms of the GNU General Public License as published by
882 the Free Software Foundation; either version 2 of the License, or
883 (at your option) any later version.
885 This program is distributed in the hope that it will be useful,
886 but WITHOUT ANY WARRANTY; without even the implied warranty of
887 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
888 GNU General Public License for more details.
890 You should have received a copy of the GNU General Public License
891 along with this program; if not, write to the Free Software
892 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.