X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=inspector%2Fvirt-inspector;h=9309b272cf7f96cbf8cde553dd4792a45b946208;hp=a7a65146bd4d65d4da8dafc7f50bbdc2274cdc97;hb=b4618fb060b40ba70f2be28b1b1ad625722a7edf;hpb=ebfcb7f23df4546977628dc718982730682a68c0 diff --git a/inspector/virt-inspector b/inspector/virt-inspector index a7a6514..9309b27 100755 --- a/inspector/virt-inspector +++ b/inspector/virt-inspector @@ -1,6 +1,6 @@ #!/usr/bin/perl -w # virt-inspector -# Copyright (C) 2009 Red Hat Inc. +# Copyright (C) 2010 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 @@ -20,23 +20,19 @@ 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 Sys::Guestfs::Lib qw(open_guest); use Pod::Usage; use Getopt::Long; -use Data::Dumper; +use File::Temp qw/tempfile/; +use File::Basename; 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 +virt-inspector - Display operating system version and other information about a virtual machine =head1 SYNOPSIS @@ -46,13 +42,11 @@ virt-inspector - Display OS version, kernel, drivers, mount points, applications =head1 DESCRIPTION -B 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. +B examines a virtual machine or disk image and tries +to determine the version of the operating system and other information +about the virtual machine. -Virt-inspector can produce output in several formats, including a -readable text report, and XML for feeding into other programs. +Virt-inspector produces XML output for feeding into other programs. In the normal usage, use C where C is the libvirt domain (see: C). @@ -106,69 +100,20 @@ 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 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. +my $format; -=item B<--query> +=item B<--format> raw -In "query mode" we answer common questions about the guest, such -as whether it is fullvirt or needs a Xen hypervisor to run. +Specify the format of disk images given on the command line. If this +is omitted then the format is autodetected from the content of the +disk image. -See section I below. +If disk images are requested from libvirt, then this program asks +libvirt for this information. In this case, the value of the format +parameter is ignored. -=cut - -my $windows_registry; - -=item B<--windows-registry> - -This flag is ignored for compatibility with earlier releases of the -software. - -In this version, if L is available, then we attempt to -parse information out of the Registry for any Windows guest. +If working with untrusted raw-format guest disk images, you should +ensure the format is always specified. =back @@ -177,17 +122,7 @@ parse information out of the Registry for any Windows guest. 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, + "format=s" => \$format, ) or pod2usage (2); pod2usage (1) if $help; if ($version) { @@ -198,645 +133,435 @@ if ($version) { } 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); -} +my @args = (\@ARGV); +push @args, address => $uri if defined $uri; +push @args, format => $format if defined $format; +my $g = open_guest (@args); $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. -There is a RELAX-NG schema for this XML in the file -I which normally ships with virt-inspector, or can -be found in the source. - -With the I<--fish> or I<--ro-fish> option the mount points are mapped to -L 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); - -#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 (); - } +my @roots = $g->inspect_os (); +if (@roots == 0) { + die __x("{prog}: No operating system could be detected inside this disk image.\n\nThis may be because the file is not a disk image, or is not a virtual machine\nimage, or because the OS type is not understood by libguestfs.\n\nIf you feel this is an error, please file a bug report including as much\ninformation about the disk image as possible.\n", + prog => basename ($0)); } -#---------------------------------------------------------------------- -# Output. +=head1 XML FORMAT -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; +The virt-inspector XML is described precisely in a RELAX NG schema +which is supplied with libguestfs. This section is just an overview. - my $root_dev = $osdevs[0]; +The top-level element is EoperatingsystemsE, and it contains +one or more EoperatingsystemE elements. You would only see +more than one EoperatingsystemE element if the virtual machine +is multi-boot, which is vanishingly rare in real world VMs. - if ($output eq "ro-fish") { - print "--ro "; - } +=head2 EoperatingsystemE - print "-a $_ " foreach @images; +In the EoperatingsystemE tag are various optional fields that +describe the operating system, its architecture, the descriptive +"product name" string, the type of OS and so on, as in this example: - 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" -} + + + /dev/sda2 + windows + i386 + windows + Windows 7 Enterprise + 6 + 1 + /Windows -# Perl output. -elsif ($output eq "perl") { - print Dumper(%$oses); -} +These fields are derived from the libguestfs inspection API, and +you can find more details in L. -# YAML output -elsif ($output eq "yaml") { - die __"virt-inspector: no YAML support\n" - unless exists $INC{"YAML/Any.pm"}; +The ErootE element is the root filesystem device, but from the +point of view of libguestfs (block devices may have completely +different names inside the VM itself). - print Dump(%$oses); -} - -# Plain text output (the default). -elsif ($output eq "text") { - output_text (); -} +=cut -# XML output. -elsif ($output eq "xml") { - output_xml (); -} +# Start the XML output. +my $xml = new XML::Writer (DATA_MODE => 1, DATA_INDENT => 2); -# Query mode. -elsif ($output eq "query") { - output_query (); -} +$xml->startTag ("operatingsystems"); -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 " (", $os->{product_name}, ")" if exists $os->{product_name}; - print " "; - print "on ", $os->{root_device}, ":\n"; - - print __" Mountpoints:\n"; - my $mounts = $os->{mounts}; - foreach (sort keys %$mounts) { - printf " %-30s %s\n", $mounts->{$_}, $_ +my $root; +foreach $root (@roots) { + my %fses = $g->inspect_get_mountpoints ($root); + my @fses = sort { length $a <=> length $b } keys %fses; + foreach (@fses) { + $g->mount_ro ($fses{$_}, $_); } - 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}; - } + $xml->startTag ("operatingsystem"); - 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} - } - } - } + # Basic OS fields. + $xml->dataElement (root => canonicalize ($root)); - 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; - } - } - } + my ($s, $distro, $major_version); + $s = $g->inspect_get_type ($root); + $xml->dataElement (name => $s) if $s ne "unknown"; + $s = $g->inspect_get_arch ($root); + $xml->dataElement (arch => $s) if $s ne "unknown"; + $distro = $g->inspect_get_distro ($root); + $xml->dataElement (distro => $distro) if $distro ne "unknown"; + $s = $g->inspect_get_product_name ($root); + $xml->dataElement (product_name => $s) if $s ne "unknown"; + $major_version = $g->inspect_get_major_version ($root); + $xml->dataElement (major_version => $major_version); + $s = $g->inspect_get_minor_version ($root); + $xml->dataElement (minor_version => $s); - print __" Applications:\n"; - my @apps = @{$os->{apps}}; - foreach (@apps) { - print " $_->{name} $_->{version}\n" - } + eval { + $s = $g->inspect_get_windows_systemroot ($root); + $xml->dataElement (windows_systemroot => $s); + }; - if ($os->{kernels}) { - print __" Kernels:\n"; - my @kernels = @{$os->{kernels}}; - foreach (@kernels) { - print " $_->{version} ($_->{arch})\n"; - my @modules = @{$_->{modules}}; - foreach (@modules) { - print " $_\n"; - } - } - } + # Mountpoints. + output_mountpoints ($root, \@fses, \%fses); - 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"; - } - } -} + # Filesystems. + output_filesystems ($root); -sub output_xml -{ - my $xml = new XML::Writer(DATA_MODE => 1, DATA_INDENT => 2); + # Package format / management and applications. + output_applications ($root, $distro, $major_version); - $xml->startTag("operatingsystems"); - output_xml_os ($oses->{$_}, $xml) foreach sort keys %$oses; - $xml->endTag("operatingsystems"); + $xml->endTag("operatingsystem"); - $xml->end(); + $g->umount_all (); } -sub output_xml_os -{ - my ($os, $xml) = @_; - - $xml->startTag("operatingsystem"); - - foreach ( [ "name" => "os" ], - [ "distro" => "distro" ], - [ "product_name" => "product_name" ], - [ "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]}; - } +# End the XML output. +$xml->endTag ("operatingsystems"); +$xml->end (); - $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]}; - } +=head2 EmountpointsE - $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]}); - } +Un*x-like guests typically have multiple filesystems which are mounted +at various mountpoints, and these are described in the +EmountpointsE element which looks like this: - $xml->endTag("alias"); - } - $xml->endTag("modprobealiases"); - } - } + + + ... + + / + /boot + - 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"); - } - } +As with ErootE, devices are from the point of view of +libguestfs, and may have completely different names inside the guest. +Only mountable filesystems appear in this list, not things like swap +devices. - $xml->startTag("applications"); - my @apps = @{$os->{apps}}; - foreach (@apps) { - $xml->startTag("application"); - $xml->dataElement("name", $_->{name}); - $xml->dataElement("epoch", $_->{epoch}) if defined $_->{epoch}; - $xml->dataElement("version", $_->{version}); - $xml->dataElement("release", $_->{release}); - $xml->dataElement("arch", $_->{arch}); - $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"); - } +=cut - if ($os->{kernels}) { - $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"); - } +sub output_mountpoints +{ + local $_; + my $root = shift; + my $fskeys = shift; + my $fshash = shift; - 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->startTag ("mountpoints"); + foreach (@$fskeys) { + $xml->dataElement ("mountpoint", $_, + dev => canonicalize ($fshash->{$_})); } - - $xml->endTag("operatingsystem"); + $xml->endTag ("mountpoints"); } -=head1 QUERY MODE - -When you use C, the output is a series of -lines of the form: +=head2 EfilesystemsE - windows=no - linux=yes - fullvirt=yes - xen_pv_drivers=no +EfilesystemsE is like EmountpointsE but covers I +filesystems belonging to the guest, including swap and empty +partitions. (In the rare case of a multi-boot guest, it covers +filesystems belonging to this OS or shared by this OS and other OSes). -(each answer is usually C or C, or the line is completely -missing if we could not determine the answer at all). +You might see something like this: -If the guest is multiboot, you can get apparently conflicting answers -(eg. C and C, 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. + + + ... + + + ext4 + + e6a4db1e-15c2-477b-ac2a-699181c396aa + -This section describes the full range of answers possible. - -=over 4 +The optional elements within EfilesystemE are the filesystem +type, the label, and the UUID. =cut -sub output_query +sub output_filesystems { - 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 (); -} + local $_; + my $root = shift; -=item windows=(yes|no) + $xml->startTag ("filesystems"); -Answer C if Microsoft Windows is installed in the guest. + my @fses = $g->inspect_get_filesystems ($root); + @fses = sort @fses; + foreach (@fses) { + $xml->startTag ("filesystem", + dev => canonicalize ($_)); -=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"; -} + eval { + my $type = $g->vfs_type ($_); + $xml->dataElement (type => $type) + if defined $type && $type ne ""; + }; -=item linux=(yes|no) + eval { + my $label = $g->vfs_label ($_); + $xml->dataElement (label => $label) + if defined $label && $label ne ""; + }; -Answer C if a Linux kernel is installed in the guest. + eval { + my $uuid = $g->vfs_uuid ($_); + $xml->dataElement (uuid => $uuid) + if defined $uuid && $uuid ne ""; + }; -=cut - -sub output_query_linux -{ - my $linux = "no"; - foreach my $os (keys %$oses) { - $linux="yes" if $oses->{$os}->{os} eq "linux"; + $xml->endTag ("filesystem"); } - print "linux=$linux\n"; -} - -=item rhel=(yes|no) -Answer C 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"; + $xml->endTag ("filesystems"); } -=item fedora=(yes|no) +=head2 EapplicationsE -Answer C if the guest contains the Fedora Linux distribution. +The related elements Epackage_formatE, +Epackage_managementE and EapplicationsE describe +applications installed in the virtual machine. At the moment we are +only able to list RPMs and Debian packages installed, but in future we +will support other Linux distros and Windows. -=cut +Epackage_formatE, if present, describes the packaging +system used. Typical values would be C and C. -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"; -} +Epackage_managementE, if present, describes the package +manager. Typical values include C, C and C -=item debian=(yes|no) +EapplicationsE lists the packages or applications +installed. -Answer C if the guest contains the Debian Linux distribution. + + + ... + + + coreutils + 8.5 + 1 + -=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 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. +(The version and release fields may not be available for +some package types). =cut -sub output_query_fullvirt +sub output_applications { - # 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; + local $_; + my $root = shift; + my $distro = shift; + my $major_version = shift; + + # Based on the distro, take a guess at the package format + # and package management. + my ($package_format, $package_management); + if (defined $distro) { + if ($distro eq "archlinux") { + $package_format = "pacman"; + $package_management = "pacman"; + } + elsif ($distro eq "debian") { + $package_format = "deb"; + $package_management = "apt"; + } + elsif ($distro eq "fedora") { + $package_format = "rpm"; + $package_management = "yum"; + } + elsif ($distro eq "pardus") { + $package_format = "pisi"; + $package_management = "pisi"; + } + elsif ($distro =~ /redhat/ || $distro =~ /rhel/) { + if ($major_version >= 5) { + $package_format = "rpm"; + $package_management = "yum"; + } else { + $package_format = "rpm"; + $package_management = "up2date"; } } + # else unknown. } - print "fullvirt=no\n"; -} -=item xen_domU_kernel=(yes|no) + $xml->dataElement (package_format => $package_format) + if defined $package_format; + $xml->dataElement (package_management => $package_management) + if defined $package_management; -Answer C 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; - } + # Do we know how to get a list of applications? + if (defined $package_format) { + if ($package_format eq "rpm") { + output_applications_rpm ($root); + } + elsif ($package_format eq "deb") { + output_applications_deb ($root); } } - print "xen_domU_kernel=no\n"; } -=item xen_pv_drivers=(yes|no) - -Answer C 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 +sub output_applications_rpm { - 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; - } + local $_; + my $root = shift; + + # Previous virt-inspector ran the 'rpm' program from the guest. + # This is insecure, and unnecessary because we can get the same + # information directly from the RPM database. + + my @applications; + + eval { + my ($fh, $filename) = tempfile (UNLINK => 1); + my $fddev = "/dev/fd/" . fileno ($fh); + $g->download ("/var/lib/rpm/Name", $fddev); + close $fh or die "close: $!"; + + # Read the database with the Berkeley DB dump tool. + my $cmd = "db_dump -p '$filename'"; + open PIPE, "$cmd |" or die "close: $!"; + while () { + chomp; + last if /^HEADER=END$/; + } + while () { + chomp; + last if /^DATA=END$/; + + # First character on each data line is a space. + if (length $_ > 0 && substr ($_, 0, 1) eq ' ') { + $_ = substr ($_, 1); } + # Name should never contain non-printable chars. + die "name contains non-printable chars" if /\\/; + push @applications, $_; + + $_ = ; # discard value } + close PIPE or die "close: $!"; + }; + if (!$@ && @applications > 0) { + @applications = sort @applications; + $xml->startTag ("applications"); + foreach (@applications) { + $xml->startTag ("application"); + $xml->dataElement (name => $_); + $xml->endTag ("application"); + } + $xml->endTag ("applications"); } - print "xen_pv_drivers=no\n"; } -=item virtio_drivers=(yes|no) - -Answer C 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 +sub output_applications_deb { - 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; + local $_; + my $root = shift; + + my @applications; + + eval { + my ($fh, $filename) = tempfile (UNLINK => 1); + my $fddev = "/dev/fd/" . fileno ($fh); + $g->download ("/var/lib/dpkg/status", $fddev); + close $fh or die "close: $!"; + + # Read the file. Each package is separated by a blank line. + open FILE, $filename or die "$filename: $!"; + my ($name, $installed, $version, $release); + while () { + chomp; + if (/^Package: (.*)/) { + $name = $1; + } elsif (/^Status: .*\binstalled\b/) { + $installed = 1; + } elsif (/^Version: (.*?)-(.*)/) { + $version = $1; + $release = $2; + } elsif ($_ eq "") { + if ($installed && + defined $name && defined $version && defined $release) { + push @applications, [ $name, $version, $release ]; } + $name = undef; + $installed = undef; + $version = undef; + $release = undef; } } + close FILE or die "$filename: $!"; + }; + if (!$@ && @applications > 0) { + @applications = sort { $a->[0] cmp $b->[0] } @applications; + $xml->startTag ("applications"); + foreach (@applications) { + $xml->startTag ("application"); + $xml->dataElement (name => $_->[0]); + $xml->dataElement (version => $_->[1]); + $xml->dataElement (release => $_->[2]); + $xml->endTag ("application"); + } + $xml->endTag ("applications"); } - 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 +# The reverse of device name translation, see +# BLOCK DEVICE NAMING in guestfs(3). +sub canonicalize { - my %arches; - - foreach my $os (keys %$oses) { - $arches{$oses->{$os}->{arch}} = 1 if exists $oses->{$os}->{arch}; - } + local $_ = shift; - foreach (sort keys %arches) { - print "userspace_arch=$_\n"; + if (m{^/dev/[hv]d([a-z]\d)$}) { + return "/dev/sd$1"; } + $_; } -=item kernel_arch=(x86_64|...) +=head1 USING XPATH -Print the architecture of the kernel. +You can use the XPath query language, and/or the xpath tool, in order +to select parts of the XML. -NB. For multi-boot VMs this can print several lines. +For example: -=cut + $ virt-inspector Guest | xpath //filesystems + Found 1 nodes: + -- NODE -- + + + ext4 + [etc] -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}; - } - } + $ virt-inspector Guest | \ + xpath "string(//filesystem[@dev='/dev/sda1']/type)" + Query didn't return a nodeset. Value: ext4 - foreach (sort keys %arches) { - print "kernel_arch=$_\n"; - } -} +=head1 SHELL QUOTING -=back +Libvirt guest names can contain arbitrary characters, some of which +have meaning to the shell such as C<#> and space. You may need to +quote or escape these characters on the command line. See the shell +manual page L for details. =head1 SEE ALSO @@ -845,17 +570,26 @@ L, L, L, L, +L, L. -=head1 AUTHOR +=head1 AUTHORS + +=over 4 + +=item * + +Richard W.M. Jones L -Richard W.M. Jones L +=item * Matthew Booth L +=back + =head1 COPYRIGHT -Copyright (C) 2009 Red Hat Inc. +Copyright (C) 2010 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