diff options
author | Richard Jones <rjones@trick.home.annexia.org> | 2009-09-23 12:37:26 +0100 |
---|---|---|
committer | Richard Jones <rjones@trick.home.annexia.org> | 2009-09-23 12:37:26 +0100 |
commit | b488436cc54288fcae8988493749f2e6c87f274c (patch) | |
tree | 6d5e8bbbd56048507c64490e623b18f9c7e1cd62 /inspector/virt-inspector | |
parent | 75b6338da3e6e33c02b931623f074ab5a76a1788 (diff) | |
download | libguestfs-b488436cc54288fcae8988493749f2e6c87f274c.tar.gz libguestfs-b488436cc54288fcae8988493749f2e6c87f274c.tar.xz libguestfs-b488436cc54288fcae8988493749f2e6c87f274c.zip |
Rename virt-[tool].pl as virt-[tool]
Diffstat (limited to 'inspector/virt-inspector')
-rwxr-xr-x | inspector/virt-inspector | 874 |
1 files changed, 874 insertions, 0 deletions
diff --git a/inspector/virt-inspector b/inspector/virt-inspector new file mode 100755 index 00000000..86b1795b --- /dev/null +++ b/inspector/virt-inspector @@ -0,0 +1,874 @@ +#!/usr/bin/perl -w +# virt-inspector +# Copyright (C) 2009 Red Hat Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +use warnings; +use strict; + +use Sys::Guestfs; +use Sys::Guestfs::Lib qw(open_guest get_partitions resolve_windows_path + inspect_all_partitions inspect_partition + inspect_operating_systems mount_operating_system inspect_in_detail); +use Pod::Usage; +use Getopt::Long; +use Data::Dumper; +use XML::Writer; +use Locale::TextDomain 'libguestfs'; + +# Optional: +eval "use YAML::Any;"; + +=encoding utf8 + +=head1 NAME + +virt-inspector - Display OS version, kernel, drivers, mount points, applications, etc. in a virtual machine + +=head1 SYNOPSIS + + virt-inspector [--connect URI] domname + + virt-inspector guest.img [guest.img ...] + +=head1 DESCRIPTION + +B<virt-inspector> examines a virtual machine and tries to determine +the version of the OS, the kernel version, what drivers are installed, +whether the virtual machine is fully virtualized (FV) or +para-virtualized (PV), what applications are installed and more. + +Virt-inspector can produce output in several formats, including a +readable text report, and XML for feeding into other programs. + +Virt-inspector should only be run on I<inactive> virtual machines. +The program tries to determine that the machine is inactive and will +refuse to run if it thinks you are trying to inspect a running domain. + +In the normal usage, use C<virt-inspector domname> where C<domname> is +the libvirt domain (see: C<virsh list --all>). + +You can also run virt-inspector directly on disk images from a single +virtual machine. Use C<virt-inspector guest.img>. In rare cases a +domain has several block devices, in which case you should list them +one after another, with the first corresponding to the guest's +C</dev/sda>, the second to the guest's C</dev/sdb> and so on. + +Virt-inspector can only inspect and report upon I<one domain at a +time>. To inspect several virtual machines, you have to run +virt-inspector several times (for example, from a shell script +for-loop). + +Because virt-inspector needs direct access to guest images, it won't +normally work over remote libvirt connections. + +=head1 OPTIONS + +=over 4 + +=cut + +my $help; + +=item B<--help> + +Display brief help. + +=cut + +my $version; + +=item B<--version> + +Display version number and exit. + +=cut + +my $uri; + +=item B<--connect URI> | B<-c URI> + +If using libvirt, connect to the given I<URI>. If omitted, +then we connect to the default libvirt hypervisor. + +Libvirt is only used if you specify a C<domname> on the +command line. If you specify guest block devices directly, +then libvirt is not used at all. + +=cut + +my $output = "text"; + +=back + +The following options select the output format. Use only one of them. +The default is a readable text report. + +=over 4 + +=item B<--text> (default) + +Plain text report. + +=item B<--none> + +Produce no output at all. + +=item B<--xml> + +If you select I<--xml> then you get XML output which can be fed +to other programs. + +=item B<--yaml> + +If you select I<--yaml> then you get YAML output which can be fed +to other programs. + +=item B<--perl> + +If you select I<--perl> then you get Perl structures output which +can be used directly in another Perl program. + +=item B<--fish> + +=item B<--ro-fish> + +If you select I<--fish> then we print a L<guestfish(1)> command +line which will automatically mount up the filesystems on the +correct mount points. Try this for example: + + guestfish $(virt-inspector --fish guest.img) + +I<--ro-fish> is the same, but the I<--ro> option is passed to +guestfish so that the filesystems are mounted read-only. + +=item B<--query> + +In "query mode" we answer common questions about the guest, such +as whether it is fullvirt or needs a Xen hypervisor to run. + +See section I<QUERY MODE> below. + +=cut + +my $windows_registry; + +=item B<--windows-registry> + +If this item is passed, I<and> the guest is Windows, I<and> the +external program C<reged> is available (see SEE ALSO section), then we +attempt to parse the Windows registry. This allows much more +information to be gathered for Windows guests. + +This is quite an expensive and slow operation, so we don't do it by +default. + +=back + +=cut + +GetOptions ("help|?" => \$help, + "version" => \$version, + "connect|c=s" => \$uri, + "text" => sub { $output = "text" }, + "none" => sub { $output = "none" }, + "xml" => sub { $output = "xml" }, + "yaml" => sub { $output = "yaml" }, + "perl" => sub { $output = "perl" }, + "fish" => sub { $output = "fish" }, + "guestfish" => sub { $output = "fish" }, + "ro-fish" => sub { $output = "ro-fish" }, + "ro-guestfish" => sub { $output = "ro-fish" }, + "query" => sub { $output = "query" }, + "windows-registry" => \$windows_registry, + ) or pod2usage (2); +pod2usage (1) if $help; +if ($version) { + my $g = Sys::Guestfs->new (); + my %h = $g->version (); + print "$h{major}.$h{minor}.$h{release}$h{extra}\n"; + exit +} +pod2usage (__"virt-inspector: no image or VM names given") if @ARGV == 0; + +my $rw = 0; +$rw = 1 if $output eq "fish"; +my $g; +my @images; +if ($uri) { + my ($conn, $dom); + ($g, $conn, $dom, @images) = + open_guest (\@ARGV, rw => $rw, address => $uri); +} else { + my ($conn, $dom); + ($g, $conn, $dom, @images) = + open_guest (\@ARGV, rw => $rw); +} + +$g->launch (); + +=head1 OUTPUT FORMAT + + Operating system(s) + ------------------- + Linux (distro + version) + Windows (version) + | + | + +--- Filesystems ---------- Installed apps --- Kernel & drivers + ----------- -------------- ---------------- + mount point => device List of apps Extra information + mount point => device and versions about kernel(s) + ... and drivers + swap => swap device + (plus lots of extra information + about each filesystem) + +The output of virt-inspector is a complex two-level data structure. + +At the top level is a list of the operating systems installed on the +guest. (For the vast majority of guests, only a single OS is +installed.) The data returned for the OS includes the name (Linux, +Windows), the distribution and version. + +The diagram above shows what we return for each OS. + +With the I<--xml> option the output is mapped into an XML document. +Unfortunately there is no clear schema for this document +(contributions welcome) but you can get an idea of the format by +looking at other documents and as a last resort the source for this +program. + +With the I<--fish> or I<--ro-fish> option the mount points are mapped to +L<guestfish(1)> command line parameters, so that you can go in +afterwards and inspect the guest with everything mounted in the +right place. For example: + + guestfish $(virt-inspector --ro-fish guest.img) + ==> guestfish --ro -a guest.img -m /dev/VG/LV:/ -m /dev/sda1:/boot + +=cut + +# List of possible filesystems. +my @partitions = get_partitions ($g); + +# Now query each one to build up a picture of what's in it. +my %fses = + inspect_all_partitions ($g, \@partitions, + use_windows_registry => $windows_registry); + +#print "fses -----------\n"; +#print Dumper(\%fses); + +my $oses = inspect_operating_systems ($g, \%fses); + +#print "oses -----------\n"; +#print Dumper($oses); + +# Mount up the disks so we can check for applications +# and kernels. Skip this if the output is "*fish" because +# we don't need to know. + +if ($output !~ /.*fish$/) { + my $root_dev; + foreach $root_dev (sort keys %$oses) { + my $os = $oses->{$root_dev}; + mount_operating_system ($g, $os); + inspect_in_detail ($g, $os); + $g->umount_all (); + } +} + +#---------------------------------------------------------------------- +# Output. + +if ($output eq "fish" || $output eq "ro-fish") { + my @osdevs = keys %$oses; + # This only works if there is a single OS. + die __"--fish output is only possible with a single OS\n" if @osdevs != 1; + + my $root_dev = $osdevs[0]; + + if ($output eq "ro-fish") { + print "--ro "; + } + + print "-a $_ " foreach @images; + + my $mounts = $oses->{$root_dev}->{mounts}; + # Have to mount / first. Luckily '/' is early in the ASCII + # character set, so this should be OK. + foreach (sort keys %$mounts) { + print "-m $mounts->{$_}:$_ " if $_ ne "swap" && $_ ne "none"; + } + print "\n" +} + +# Perl output. +elsif ($output eq "perl") { + print Dumper(%$oses); +} + +# YAML output +elsif ($output eq "yaml") { + die __"virt-inspector: no YAML support\n" + unless exists $INC{"YAML/Any.pm"}; + + print Dump(%$oses); +} + +# Plain text output (the default). +elsif ($output eq "text") { + output_text (); +} + +# XML output. +elsif ($output eq "xml") { + output_xml (); +} + +# Query mode. +elsif ($output eq "query") { + output_query (); +} + +sub output_text +{ + output_text_os ($oses->{$_}) foreach sort keys %$oses; +} + +sub output_text_os +{ + my $os = shift; + + print $os->{os}, " " if exists $os->{os}; + print $os->{distro}, " " if exists $os->{distro}; + print $os->{arch}, " " if exists $os->{arch}; + print $os->{major_version} if exists $os->{major_version}; + print ".", $os->{minor_version} if exists $os->{minor_version}; + print " "; + print "on ", $os->{root_device}, ":\n"; + + print __" Mountpoints:\n"; + my $mounts = $os->{mounts}; + foreach (sort keys %$mounts) { + printf " %-30s %s\n", $mounts->{$_}, $_ + } + + print __" Filesystems:\n"; + my $filesystems = $os->{filesystems}; + foreach (sort keys %$filesystems) { + print " $_:\n"; + print " label: $filesystems->{$_}{label}\n" + if exists $filesystems->{$_}{label}; + print " UUID: $filesystems->{$_}{uuid}\n" + if exists $filesystems->{$_}{uuid}; + print " type: $filesystems->{$_}{fstype}\n" + if exists $filesystems->{$_}{fstype}; + print " content: $filesystems->{$_}{content}\n" + if exists $filesystems->{$_}{content}; + } + + if (exists $os->{modprobe_aliases}) { + my %aliases = %{$os->{modprobe_aliases}}; + my @keys = sort keys %aliases; + if (@keys) { + print __" Modprobe aliases:\n"; + foreach (@keys) { + printf " %-30s %s\n", $_, $aliases{$_}->{modulename} + } + } + } + + if (exists $os->{initrd_modules}) { + my %modvers = %{$os->{initrd_modules}}; + my @keys = sort keys %modvers; + if (@keys) { + print __" Initrd modules:\n"; + foreach (@keys) { + my @modules = @{$modvers{$_}}; + print " $_:\n"; + print " $_\n" foreach @modules; + } + } + } + + print __" Applications:\n"; + my @apps = @{$os->{apps}}; + foreach (@apps) { + print " $_->{name} $_->{version}\n" + } + + print __" Kernels:\n"; + my @kernels = @{$os->{kernels}}; + foreach (@kernels) { + print " $_->{version} ($_->{arch})\n"; + my @modules = @{$_->{modules}}; + foreach (@modules) { + print " $_\n"; + } + } + + if (exists $os->{root}->{registry}) { + print __" Windows Registry entries:\n"; + # These are just lumps of text - dump them out. + foreach (@{$os->{root}->{registry}}) { + print "$_\n"; + } + } +} + +sub output_xml +{ + my $xml = new XML::Writer(DATA_MODE => 1, DATA_INDENT => 2); + + $xml->startTag("operatingsystems"); + output_xml_os ($oses->{$_}, $xml) foreach sort keys %$oses; + $xml->endTag("operatingsystems"); + + $xml->end(); +} + +sub output_xml_os +{ + my ($os, $xml) = @_; + + $xml->startTag("operatingsystem"); + + foreach ( [ "name" => "os" ], + [ "distro" => "distro" ], + [ "arch" => "arch" ], + [ "major_version" => "major_version" ], + [ "minor_version" => "minor_version" ], + [ "package_format" => "package_format" ], + [ "package_management" => "package_management" ], + [ "root" => "root_device" ] ) { + $xml->dataElement($_->[0], $os->{$_->[1]}) if exists $os->{$_->[1]}; + } + + $xml->startTag("mountpoints"); + my $mounts = $os->{mounts}; + foreach (sort keys %$mounts) { + $xml->dataElement("mountpoint", $_, "dev" => $mounts->{$_}); + } + $xml->endTag("mountpoints"); + + $xml->startTag("filesystems"); + my $filesystems = $os->{filesystems}; + foreach (sort keys %$filesystems) { + $xml->startTag("filesystem", "dev" => $_); + + foreach my $field ( [ "label" => "label" ], + [ "uuid" => "uuid" ], + [ "type" => "fstype" ], + [ "content" => "content" ], + [ "spec" => "spec" ] ) { + $xml->dataElement($field->[0], $filesystems->{$_}{$field->[1]}) + if exists $filesystems->{$_}{$field->[1]}; + } + + $xml->endTag("filesystem"); + } + $xml->endTag("filesystems"); + + if (exists $os->{modprobe_aliases}) { + my %aliases = %{$os->{modprobe_aliases}}; + my @keys = sort keys %aliases; + if (@keys) { + $xml->startTag("modprobealiases"); + foreach (@keys) { + $xml->startTag("alias", "device" => $_); + + foreach my $field ( [ "modulename" => "modulename" ], + [ "augeas" => "augeas" ], + [ "file" => "file" ] ) { + $xml->dataElement($field->[0], $aliases{$_}->{$field->[1]}); + } + + $xml->endTag("alias"); + } + $xml->endTag("modprobealiases"); + } + } + + if (exists $os->{initrd_modules}) { + my %modvers = %{$os->{initrd_modules}}; + my @keys = sort keys %modvers; + if (@keys) { + $xml->startTag("initrds"); + foreach (@keys) { + my @modules = @{$modvers{$_}}; + $xml->startTag("initrd", "version" => $_); + $xml->dataElement("module", $_) foreach @modules; + $xml->endTag("initrd"); + } + $xml->endTag("initrds"); + } + } + + $xml->startTag("applications"); + my @apps = @{$os->{apps}}; + foreach (@apps) { + $xml->startTag("application"); + $xml->dataElement("name", $_->{name}); + $xml->dataElement("version", $_->{version}); + $xml->endTag("application"); + } + $xml->endTag("applications"); + + if(defined($os->{boot}) && defined($os->{boot}->{configs})) { + my $default = $os->{boot}->{default}; + my $configs = $os->{boot}->{configs}; + + $xml->startTag("boot"); + for(my $i = 0; $i < scalar(@$configs); $i++) { + my $config = $configs->[$i]; + + my @attrs = (); + push(@attrs, ("default" => 1)) if($default == $i); + $xml->startTag("config", @attrs); + $xml->dataElement("title", $config->{title}); + $xml->dataElement("kernel", $config->{kernel}->{version}) + if(defined($config->{kernel})); + $xml->dataElement("cmdline", $config->{cmdline}) + if(defined($config->{cmdline})); + $xml->endTag("config"); + } + $xml->endTag("boot"); + } + + $xml->startTag("kernels"); + my @kernels = @{$os->{kernels}}; + foreach (@kernels) { + $xml->startTag("kernel", + "version" => $_->{version}, + "arch" => $_->{arch}); + $xml->startTag("modules"); + my @modules = @{$_->{modules}}; + foreach (@modules) { + $xml->dataElement("module", $_); + } + $xml->endTag("modules"); + $xml->dataElement("path", $_->{path}) if(defined($_->{path})); + $xml->dataElement("package", $_->{package}) if(defined($_->{package})); + $xml->endTag("kernel"); + } + $xml->endTag("kernels"); + + if (exists $os->{root}->{registry}) { + $xml->startTag("windowsregistryentries"); + # These are just lumps of text - dump them out. + foreach (@{$os->{root}->{registry}}) { + $xml->dataElement("windowsregistryentry", $_); + } + $xml->endTag("windowsregistryentries"); + } + + $xml->endTag("operatingsystem"); +} + +=head1 QUERY MODE + +When you use C<virt-inspector --query>, the output is a series of +lines of the form: + + windows=no + linux=yes + fullvirt=yes + xen_pv_drivers=no + +(each answer is usually C<yes> or C<no>, or the line is completely +missing if we could not determine the answer at all). + +If the guest is multiboot, you can get apparently conflicting answers +(eg. C<windows=yes> and C<linux=yes>, or a guest which is both +fullvirt and has a Xen PV kernel). This is normal, and just means +that the guest can do both things, although it might require operator +intervention such as selecting a boot option when the guest is +booting. + +This section describes the full range of answers possible. + +=over 4 + +=cut + +sub output_query +{ + output_query_windows (); + output_query_linux (); + output_query_rhel (); + output_query_fedora (); + output_query_debian (); + output_query_fullvirt (); + output_query_xen_domU_kernel (); + output_query_xen_pv_drivers (); + output_query_virtio_drivers (); + output_query_kernel_arch (); + output_query_userspace_arch (); +} + +=item windows=(yes|no) + +Answer C<yes> if Microsoft Windows is installed in the guest. + +=cut + +sub output_query_windows +{ + my $windows = "no"; + foreach my $os (keys %$oses) { + $windows="yes" if $oses->{$os}->{os} eq "windows"; + } + print "windows=$windows\n"; +} + +=item linux=(yes|no) + +Answer C<yes> if a Linux kernel is installed in the guest. + +=cut + +sub output_query_linux +{ + my $linux = "no"; + foreach my $os (keys %$oses) { + $linux="yes" if $oses->{$os}->{os} eq "linux"; + } + print "linux=$linux\n"; +} + +=item rhel=(yes|no) + +Answer C<yes> if the guest contains Red Hat Enterprise Linux. + +=cut + +sub output_query_rhel +{ + my $rhel = "no"; + foreach my $os (keys %$oses) { + $rhel="yes" if ($oses->{$os}->{os} eq "linux" && + $oses->{$os}->{distro} eq "rhel"); + } + print "rhel=$rhel\n"; +} + +=item fedora=(yes|no) + +Answer C<yes> if the guest contains the Fedora Linux distribution. + +=cut + +sub output_query_fedora +{ + my $fedora = "no"; + foreach my $os (keys %$oses) { + $fedora="yes" if $oses->{$os}->{os} eq "linux" && $oses->{$os}->{distro} eq "fedora"; + } + print "fedora=$fedora\n"; +} + +=item debian=(yes|no) + +Answer C<yes> if the guest contains the Debian Linux distribution. + +=cut + +sub output_query_debian +{ + my $debian = "no"; + foreach my $os (keys %$oses) { + $debian="yes" if $oses->{$os}->{os} eq "linux" && $oses->{$os}->{distro} eq "debian"; + } + print "debian=$debian\n"; +} + +=item fullvirt=(yes|no) + +Answer C<yes> if there is at least one operating system kernel +installed in the guest which runs fully virtualized. Such a guest +would require a hypervisor which supports full system virtualization. + +=cut + +sub output_query_fullvirt +{ + # The assumption is full-virt, unless all installed kernels + # are identified as paravirt. + # XXX Fails on Windows guests. + foreach my $os (keys %$oses) { + foreach my $kernel (@{$oses->{$os}->{kernels}}) { + my $is_pv = $kernel->{version} =~ m/xen/; + unless ($is_pv) { + print "fullvirt=yes\n"; + return; + } + } + } + print "fullvirt=no\n"; +} + +=item xen_domU_kernel=(yes|no) + +Answer C<yes> if there is at least one Linux kernel installed in +the guest which is compiled as a Xen DomU (a Xen paravirtualized +guest). + +=cut + +sub output_query_xen_domU_kernel +{ + foreach my $os (keys %$oses) { + foreach my $kernel (@{$oses->{$os}->{kernels}}) { + my $is_xen = $kernel->{version} =~ m/xen/; + if ($is_xen) { + print "xen_domU_kernel=yes\n"; + return; + } + } + } + print "xen_domU_kernel=no\n"; +} + +=item xen_pv_drivers=(yes|no) + +Answer C<yes> if the guest has Xen paravirtualized drivers installed +(usually the kernel itself will be fully virtualized, but the PV +drivers have been installed by the administrator for performance +reasons). + +=cut + +sub output_query_xen_pv_drivers +{ + foreach my $os (keys %$oses) { + foreach my $kernel (@{$oses->{$os}->{kernels}}) { + foreach my $module (@{$kernel->{modules}}) { + if ($module =~ m/xen-/) { + print "xen_pv_drivers=yes\n"; + return; + } + } + } + } + print "xen_pv_drivers=no\n"; +} + +=item virtio_drivers=(yes|no) + +Answer C<yes> if the guest has virtio paravirtualized drivers +installed. Virtio drivers are commonly used to improve the +performance of KVM. + +=cut + +sub output_query_virtio_drivers +{ + foreach my $os (keys %$oses) { + foreach my $kernel (@{$oses->{$os}->{kernels}}) { + foreach my $module (@{$kernel->{modules}}) { + if ($module =~ m/virtio_/) { + print "virtio_drivers=yes\n"; + return; + } + } + } + } + print "virtio_drivers=no\n"; +} + +=item userspace_arch=(x86_64|...) + +Print the architecture of userspace. + +NB. For multi-boot VMs this can print several lines. + +=cut + +sub output_query_userspace_arch +{ + my %arches; + + foreach my $os (keys %$oses) { + $arches{$oses->{$os}->{arch}} = 1 if exists $oses->{$os}->{arch}; + } + + foreach (sort keys %arches) { + print "userspace_arch=$_\n"; + } +} + +=item kernel_arch=(x86_64|...) + +Print the architecture of the kernel. + +NB. For multi-boot VMs this can print several lines. + +=cut + +sub output_query_kernel_arch +{ + my %arches; + + foreach my $os (keys %$oses) { + foreach my $kernel (@{$oses->{$os}->{kernels}}) { + $arches{$kernel->{arch}} = 1 if exists $kernel->{arch}; + } + } + + foreach (sort keys %arches) { + print "kernel_arch=$_\n"; + } +} + +=back + +=head1 SEE ALSO + +L<guestfs(3)>, +L<guestfish(1)>, +L<Sys::Guestfs(3)>, +L<Sys::Guestfs::Lib(3)>, +L<Sys::Virt(3)>, +L<http://libguestfs.org/>. + +For Windows registry parsing we require the C<reged> program +from L<http://home.eunet.no/~pnordahl/ntpasswd/>. + +=head1 AUTHOR + +Richard W.M. Jones L<http://et.redhat.com/~rjones/> + +Matthew Booth L<mbooth@redhat.com> + +=head1 COPYRIGHT + +Copyright (C) 2009 Red Hat Inc. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |