inspect: Add detection of Arch Linux.
[libguestfs.git] / inspector / virt-inspector
index dc8847f..9309b27 100755 (executable)
@@ -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,17 +42,11 @@ virt-inspector - Display OS version, kernel, drivers, mount points, applications
 
 =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.
+B<virt-inspector> 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 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.
+Virt-inspector produces XML output for feeding into other programs.
 
 In the normal usage, use C<virt-inspector domname> where C<domname> is
 the libvirt domain (see: C<virsh list --all>).
@@ -110,71 +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<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)
+my $format;
 
-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<--format> raw
 
-=item B<--query>
+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.
 
-In "query mode" we answer common questions about the guest, such
-as whether it is fullvirt or needs a Xen hypervisor to run.
+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.
 
-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.
+If working with untrusted raw-format guest disk images, you should
+ensure the format is always specified.
 
 =back
 
@@ -183,17 +122,7 @@ default.
 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) {
@@ -204,640 +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.
-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 ();
-    }
+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.
-
-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;
+=head1 XML FORMAT
 
-    my $root_dev = $osdevs[0];
-
-    if ($output eq "ro-fish") {
-        print "--ro ";
-    }
+The virt-inspector XML is described precisely in a RELAX NG schema
+which is supplied with libguestfs.  This section is just an overview.
 
-    print "-a $_ " foreach @images;
+The top-level element is E<lt>operatingsystemsE<gt>, and it contains
+one or more E<lt>operatingsystemE<gt> elements.  You would only see
+more than one E<lt>operatingsystemE<gt> element if the virtual machine
+is multi-boot, which is vanishingly rare in real world VMs.
 
-    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"
-}
+=head2 E<lt>operatingsystemE<gt>
 
-# Perl output.
-elsif ($output eq "perl") {
-    print Dumper(%$oses);
-}
+In the E<lt>operatingsystemE<gt> 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:
 
-# YAML output
-elsif ($output eq "yaml") {
-    die __"virt-inspector: no YAML support\n"
-        unless exists $INC{"YAML/Any.pm"};
+ <operatingsystems>
+   <operatingsystem>
+     <root>/dev/sda2</root>
+     <name>windows</name>
+     <arch>i386</arch>
+     <distro>windows</distro>
+     <product_name>Windows 7 Enterprise</product_name>
+     <major_version>6</major_version>
+     <minor_version>1</minor_version>
+     <windows_systemroot>/Windows</windows_systemroot>
 
-    print Dump(%$oses);
-}
+These fields are derived from the libguestfs inspection API, and
+you can find more details in L<guestfs(3)/INSPECTION>.
 
-# Plain text output (the default).
-elsif ($output eq "text") {
-    output_text ();
-}
+The E<lt>rootE<gt> 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).
 
-# XML output.
-elsif ($output eq "xml") {
-    output_xml ();
-}
+=cut
 
-# Query mode.
-elsif ($output eq "query") {
-    output_query ();
-}
+# Start the XML output.
+my $xml = new XML::Writer (DATA_MODE => 1, DATA_INDENT => 2);
 
-sub output_text
-{
-    output_text_os ($oses->{$_}) foreach sort keys %$oses;
-}
+$xml->startTag ("operatingsystems");
 
-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->{$_}, $_
+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);
+    };
 
-    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" ],
-              [ "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 E<lt>mountpointsE<gt>
 
-        $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
+E<lt>mountpointsE<gt> element which looks like this:
 
-                $xml->endTag("alias");
-            }
-            $xml->endTag("modprobealiases");
-        }
-    }
+ <operatingsystems>
+   <operatingsystem>
+     ...
+     <mountpoints>
+       <mountpoint dev="/dev/vg_f13x64/lv_root">/</mountpoint>
+       <mountpoint dev="/dev/sda1">/boot</mountpoint>
+     </mountpoints>
 
-    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 E<lt>rootE<gt>, 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("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");
-    }
+=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
+=head2 E<lt>filesystemsE<gt>
 
-When you use C<virt-inspector --query>, the output is a series of
-lines of the form:
+E<lt>filesystemsE<gt> is like E<lt>mountpointsE<gt> but covers I<all>
+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).
 
- windows=no
- linux=yes
- fullvirt=yes
- xen_pv_drivers=no
+You might see something like this:
 
-(each answer is usually C<yes> or C<no>, or the line is completely
-missing if we could not determine the answer at all).
+ <operatingsystems>
+   <operatingsystem>
+     ...
+     <filesystems>
+       <filesystem dev="/dev/vg_f13x64/lv_root">
+         <type>ext4</type>
+         <label>Fedora-13-x86_64</label>
+         <uuid>e6a4db1e-15c2-477b-ac2a-699181c396aa</uuid>
+       </filesystem>
 
-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
+The optional elements within E<lt>filesystemE<gt> 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<yes> 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
+        eval {
+            my $type = $g->vfs_type ($_);
+            $xml->dataElement (type => $type)
+                if defined $type && $type ne "";
+        };
 
-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.
+        eval {
+            my $label = $g->vfs_label ($_);
+            $xml->dataElement (label => $label)
+                if defined $label && $label ne "";
+        };
 
-=cut
+        eval {
+            my $uuid = $g->vfs_uuid ($_);
+            $xml->dataElement (uuid => $uuid)
+                if defined $uuid && $uuid ne "";
+        };
 
-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<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";
+    $xml->endTag ("filesystems");
 }
 
-=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";
-}
+=head2 E<lt>applicationsE<gt>
 
-=item debian=(yes|no)
+The related elements E<lt>package_formatE<gt>,
+E<lt>package_managementE<gt> and E<lt>applicationsE<gt> 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.
 
-Answer C<yes> if the guest contains the Debian Linux distribution.
+E<lt>package_formatE<gt>, if present, describes the packaging
+system used.  Typical values would be C<rpm> and C<deb>.
 
-=cut
+E<lt>package_managementE<gt>, if present, describes the package
+manager.  Typical values include C<yum>, C<up2date> and C<apt>
 
-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";
-}
+E<lt>applicationsE<gt> lists the packages or applications
+installed.
 
-=item fullvirt=(yes|no)
+ <operatingsystems>
+   <operatingsystem>
+     ...
+     <applications>
+       <application>
+         <name>coreutils</name>
+         <version>8.5</version>
+         <release>1</release>
+       </application>
 
-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.
+(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<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;
-            }
+    # 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<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
+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 (<PIPE>) {
+            chomp;
+            last if /^HEADER=END$/;
+        }
+        while (<PIPE>) {
+            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, $_;
+
+            $_ = <PIPE>; # 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<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
+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 (<FILE>) {
+            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;
+    local $_ = shift;
 
-    foreach my $os (keys %$oses) {
-        $arches{$oses->{$os}->{arch}} = 1 if exists $oses->{$os}->{arch};
-    }
-
-    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
-
-sub output_query_kernel_arch
-{
-    my %arches;
+ $ virt-inspector Guest | xpath //filesystems
+ Found 1 nodes:
+ -- NODE --
+ <filesystems>
+      <filesystem dev="/dev/vg_f13x64/lv_root">
+        <type>ext4</type>
+ [etc]
 
-    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<sh(1)> for details.
 
 =head1 SEE ALSO
 
@@ -846,20 +570,26 @@ L<guestfish(1)>,
 L<Sys::Guestfs(3)>,
 L<Sys::Guestfs::Lib(3)>,
 L<Sys::Virt(3)>,
+L<http://www.w3.org/TR/xpath/>,
 L<http://libguestfs.org/>.
 
-For Windows registry parsing we require the C<reged> program
-from L<http://home.eunet.no/~pnordahl/ntpasswd/>.
+=head1 AUTHORS
 
-=head1 AUTHOR
+=over 4
+
+=item *
+
+Richard W.M. Jones L<http://people.redhat.com/~rjones/>
 
-Richard W.M. Jones L<http://et.redhat.com/~rjones/>
+=item *
 
 Matthew Booth L<mbooth@redhat.com>
 
+=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