tests: Fix read_file test.
[libguestfs.git] / inspector / virt-inspector.pl
index 5b225b2..d2acf06 100755 (executable)
@@ -20,12 +20,14 @@ use warnings;
 use strict;
 
 use Sys::Guestfs;
-use Sys::Guestfs::Lib qw(open_guest get_partitions);
+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 File::Temp qw/tempdir/;
 use XML::Writer;
+use Locale::TextDomain 'libguestfs';
 
 # Optional:
 eval "use YAML::Any;";
@@ -87,6 +89,14 @@ Display brief help.
 
 =cut
 
+my $version;
+
+=item B<--version>
+
+Display version number and exit.
+
+=cut
+
 my $uri;
 
 =item B<--connect URI> | B<-c URI>
@@ -100,18 +110,6 @@ then libvirt is not used at all.
 
 =cut
 
-my $force;
-
-=item B<--force>
-
-Force reading a particular guest even if it appears to be active.  In
-earlier versions of virt-inspector, this could be dangerous (for
-example, corrupting the guest's disk image).  However in more recent
-versions, it should not cause corruption, but might cause
-virt-inspector to crash or produce incorrect results.
-
-=cut
-
 my $output = "text";
 
 =back
@@ -183,8 +181,8 @@ default.
 =cut
 
 GetOptions ("help|?" => \$help,
+           "version" => \$version,
            "connect|c=s" => \$uri,
-           "force" => \$force,
            "text" => sub { $output = "text" },
            "none" => sub { $output = "none" },
            "xml" => sub { $output = "xml" },
@@ -198,15 +196,26 @@ GetOptions ("help|?" => \$help,
            "windows-registry" => \$windows_registry,
     ) or pod2usage (2);
 pod2usage (1) if $help;
-pod2usage ("$0: no image or VM names given") if @ARGV == 0;
+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) {
-    $g = open_guest (\@ARGV, rw => $rw, address => $uri);
+    my ($conn, $dom);
+    ($g, $conn, $dom, @images) =
+       open_guest (\@ARGV, rw => $rw, address => $uri);
 } else {
-    $g = open_guest (\@ARGV, rw => $rw);
+    my ($conn, $dom);
+    ($g, $conn, $dom, @images) =
+       open_guest (\@ARGV, rw => $rw);
 }
 
 $g->launch ();
@@ -255,629 +264,42 @@ right place.  For example:
 =cut
 
 # List of possible filesystems.
-my @devices = get_partitions ($g);
+my @partitions = get_partitions ($g);
 
 # Now query each one to build up a picture of what's in it.
-my %fses = map { $_ => check_fs ($_) } @devices;
-
-# Now the complex checking code itself.
-# check_fs takes a device name (LV or partition name) and returns
-# a hashref containing everything we can find out about the device.
-sub check_fs {
-    local $_;
-    my $dev = shift;           # LV or partition name.
-
-    my %r;                     # Result hash.
-
-    # First try 'file(1)' on it.
-    my $file = $g->file ($dev);
-    if ($file =~ /ext2 filesystem data/) {
-       $r{fstype} = "ext2";
-       $r{fsos} = "linux";
-    } elsif ($file =~ /ext3 filesystem data/) {
-       $r{fstype} = "ext3";
-       $r{fsos} = "linux";
-    } elsif ($file =~ /ext4 filesystem data/) {
-       $r{fstype} = "ext4";
-       $r{fsos} = "linux";
-    } elsif ($file =~ m{Linux/i386 swap file}) {
-       $r{fstype} = "swap";
-       $r{fsos} = "linux";
-       $r{is_swap} = 1;
-    }
-
-    # If it's ext2/3/4, then we want the UUID and label.
-    if (exists $r{fstype} && $r{fstype} =~ /^ext/) {
-       $r{uuid} = $g->get_e2uuid ($dev);
-       $r{label} = $g->get_e2label ($dev);
-    }
-
-    # Try mounting it, fnarrr.
-    if (!$r{is_swap}) {
-       $r{is_mountable} = 1;
-       eval { $g->mount_ro ($dev, "/") };
-       if ($@) {
-           # It's not mountable, probably empty or some format
-           # we don't understand.
-           $r{is_mountable} = 0;
-           goto OUT;
-       }
-
-       # Grub /boot?
-       if ($g->is_file ("/grub/menu.lst") ||
-           $g->is_file ("/grub/grub.conf")) {
-           $r{content} = "linux-grub";
-           check_grub (\%r);
-           goto OUT;
-       }
-
-       # Linux root?
-       if ($g->is_dir ("/etc") && $g->is_dir ("/bin") &&
-           $g->is_file ("/etc/fstab")) {
-           $r{content} = "linux-root";
-           $r{is_root} = 1;
-           check_linux_root (\%r);
-           goto OUT;
-       }
-
-       # Linux /usr/local.
-       if ($g->is_dir ("/etc") && $g->is_dir ("/bin") &&
-           $g->is_dir ("/share") && !$g->exists ("/local") &&
-           !$g->is_file ("/etc/fstab")) {
-           $r{content} = "linux-usrlocal";
-           goto OUT;
-       }
-
-       # Linux /usr.
-       if ($g->is_dir ("/etc") && $g->is_dir ("/bin") &&
-           $g->is_dir ("/share") && $g->exists ("/local") &&
-           !$g->is_file ("/etc/fstab")) {
-           $r{content} = "linux-usr";
-           goto OUT;
-       }
-
-       # Windows root?
-       if ($g->is_file ("/AUTOEXEC.BAT") ||
-           $g->is_file ("/autoexec.bat") ||
-           $g->is_dir ("/Program Files") ||
-           $g->is_dir ("/WINDOWS") ||
-           $g->is_file ("/boot.ini") ||
-           $g->is_file ("/ntldr")) {
-           $r{fstype} = "ntfs"; # XXX this is a guess
-           $r{fsos} = "windows";
-           $r{content} = "windows-root";
-           $r{is_root} = 1;
-           check_windows_root (\%r);
-           goto OUT;
-       }
-    }
-
-  OUT:
-    $g->umount_all ();
-    return \%r;
-}
-
-sub check_linux_root
-{
-    local $_;
-    my $r = shift;
-
-    # Look into /etc to see if we recognise the operating system.
-    if ($g->is_file ("/etc/redhat-release")) {
-       $_ = $g->cat ("/etc/redhat-release");
-       if (/Fedora release (\d+\.\d+)/) {
-           $r->{osdistro} = "fedora";
-           $r->{osversion} = "$1"
-       } elsif (/(Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\d+).*Update (\d+)/) {
-           $r->{osdistro} = "redhat";
-           $r->{osversion} = "$2.$3";
-        } elsif (/(Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\d+(?:\.(\d+))?)/) {
-           $r->{osdistro} = "redhat";
-           $r->{osversion} = "$2";
-       } else {
-           $r->{osdistro} = "redhat";
-       }
-    } elsif ($g->is_file ("/etc/debian_version")) {
-       $_ = $g->cat ("/etc/debian_version");
-       if (/(\d+\.\d+)/) {
-           $r->{osdistro} = "debian";
-           $r->{osversion} = "$1";
-       } else {
-           $r->{osdistro} = "debian";
-       }
-    }
-
-    # Parse the contents of /etc/fstab.  This is pretty vital so
-    # we can determine where filesystems are supposed to be mounted.
-    eval "\$_ = \$g->cat ('/etc/fstab');";
-    if (!$@ && $_) {
-       my @lines = split /\n/;
-       my @fstab;
-       foreach (@lines) {
-           my @fields = split /[ \t]+/;
-           if (@fields >= 2) {
-               my $spec = $fields[0]; # first column (dev/label/uuid)
-               my $file = $fields[1]; # second column (mountpoint)
-               if ($spec =~ m{^/} ||
-                   $spec =~ m{^LABEL=} ||
-                   $spec =~ m{^UUID=} ||
-                   $file eq "swap") {
-                   push @fstab, [$spec, $file]
-               }
-           }
-       }
-       $r->{fstab} = \@fstab if @fstab;
-    }
-}
-
-# We only support NT.  The control file /boot.ini contains a list of
-# Windows installations and their %systemroot%s in a simple text
-# format.
-#
-# XXX We could parse this better.  This won't work if /boot.ini is on
-# a different drive from the %systemroot%, and in other unusual cases.
-
-sub check_windows_root
-{
-    local $_;
-    my $r = shift;
-
-    my $boot_ini = resolve_windows_path ("/", "boot.ini");
-    $r->{boot_ini} = $boot_ini;
-
-    if (defined $r->{boot_ini}) {
-       $_ = $g->cat ($boot_ini);
-       my @lines = split /\n/;
-       my $section;
-       my $systemroot;
-       foreach (@lines) {
-           if (m/\[.*\]/) {
-               $section = $1;
-           } elsif (m/^default=.*?\\(\w+)$/i) {
-               $systemroot = $1;
-               last;
-           } elsif (m/\\(\w+)=/) {
-               $systemroot = $1;
-               last;
-           }
-       }
-
-       if (defined $systemroot) {
-           $r->{systemroot} = resolve_windows_path ("/", $systemroot);
-           if (defined $r->{systemroot} && $windows_registry) {
-               check_windows_registry ($r, $r->{systemroot});
-           }
-       }
-    }
-}
-
-sub check_windows_registry
-{
-    local $_;
-    my $r = shift;
-    my $systemroot = shift;
-
-    # Download the system registry files.  Only download the
-    # interesting ones, and we don't bother with user profiles at all.
-    my $system32 = resolve_windows_path ($systemroot, "system32");
-    if (defined $system32) {
-       my $config = resolve_windows_path ($system32, "config");
-       if (defined $config) {
-           my $software = resolve_windows_path ($config, "software");
-           if (defined $software) {
-               load_windows_registry ($r, $software,
-                                      "HKEY_LOCAL_MACHINE\\SOFTWARE");
-           }
-           my $system = resolve_windows_path ($config, "system");
-           if (defined $system) {
-               load_windows_registry ($r, $system,
-                                      "HKEY_LOCAL_MACHINE\\System");
-           }
-       }
-    }
-}
-
-sub load_windows_registry
-{
-    local $_;
-    my $r = shift;
-    my $regfile = shift;
-    my $prefix = shift;
-
-    my $dir = tempdir (CLEANUP => 1);
-
-    $g->download ($regfile, "$dir/reg");
-
-    # 'reged' command is particularly noisy.  Redirect stdout and
-    # stderr to /dev/null temporarily.
-    open SAVEOUT, ">&STDOUT";
-    open SAVEERR, ">&STDERR";
-    open STDOUT, ">/dev/null";
-    open STDERR, ">/dev/null";
-
-    my @cmd = ("reged", "-x", "$dir/reg", "$prefix", "\\", "$dir/out");
-    my $res = system (@cmd);
-
-    close STDOUT;
-    close STDERR;
-    open STDOUT, ">&SAVEOUT";
-    open STDERR, ">&SAVEERR";
-    close SAVEOUT;
-    close SAVEERR;
-
-    unless ($res == 0) {
-       warn "reged command failed: $?";
-       return;
-    }
-
-    # Some versions of reged segfault on inputs.  If that happens we
-    # may get no / partial output file.  Anyway, if it exists, load
-    # it.
-    my $content;
-    unless (open F, "$dir/out") {
-       warn "no output from reged command: $!";
-       return;
-    }
-    { local $/ = undef; $content = <F>; }
-    close F;
-
-    my @registry = ();
-    @registry = @{$r->{registry}} if exists $r->{registry};
-    push @registry, $content;
-    $r->{registry} = \@registry;
-}
-
-# Because of case sensitivity, the actual path might have a different
-# name, and ntfs-3g is always case sensitive.  Find out what the real
-# path is.  Returns the correct full path, or undef.
-sub resolve_windows_path
-{
-    local $_;
-    my $parent = shift;                # Must exist, with correct case.
-    my $dir = shift;
-
-    foreach ($g->ls ($parent)) {
-       if (lc ($_) eq lc ($dir)) {
-           if ($parent eq "/") {
-               return "/$_"
-           } else {
-               return "$parent/$_"
-           }
-       }
-    }
-
-    undef;
-}
-
-sub check_grub
-{
-    local $_;
-    my $r = shift;
-
-    # Grub version, if we care.
-}
-
-#print Dumper (\%fses);
-
-#----------------------------------------------------------------------
-# Now find out how many operating systems we've got.  Usually just one.
-
-my %oses = ();
-
-foreach (sort keys %fses) {
-    if ($fses{$_}->{is_root}) {
-       my %r = (
-           root => $fses{$_},
-           root_device => $_
-       );
-       get_os_version (\%r);
-       assign_mount_points (\%r);
-       $oses{$_} = \%r;
-    }
-}
-
-sub get_os_version
-{
-    local $_;
-    my $r = shift;
-
-    $r->{os} = $r->{root}->{fsos} if exists $r->{root}->{fsos};
-    $r->{distro} = $r->{root}->{osdistro} if exists $r->{root}->{osdistro};
-    $r->{version} = $r->{root}->{osversion} if exists $r->{root}->{osversion};
-}
-
-sub assign_mount_points
-{
-    local $_;
-    my $r = shift;
-
-    $r->{mounts} = { "/" => $r->{root_device} };
-    $r->{filesystems} = { $r->{root_device} => $r->{root} };
-
-    # Use /etc/fstab if we have it to mount the rest.
-    if (exists $r->{root}->{fstab}) {
-       my @fstab = @{$r->{root}->{fstab}};
-       foreach (@fstab) {
-           my ($spec, $file) = @$_;
-
-           my ($dev, $fs) = find_filesystem ($spec);
-           if ($dev) {
-               $r->{mounts}->{$file} = $dev;
-               $r->{filesystems}->{$dev} = $fs;
-               if (exists $fs->{used}) {
-                   $fs->{used}++
-               } else {
-                   $fs->{used} = 1
-               }
-                $fs->{spec} = $spec;
-           }
-       }
-    }
-}
-
-# Find filesystem by device name, LABEL=.. or UUID=..
-sub find_filesystem
-{
-    local $_ = shift;
-
-    if (/^LABEL=(.*)/) {
-       my $label = $1;
-       foreach (sort keys %fses) {
-           if (exists $fses{$_}->{label} &&
-               $fses{$_}->{label} eq $label) {
-               return ($_, $fses{$_});
-           }
-       }
-       warn "unknown filesystem label $label\n";
-       return ();
-    } elsif (/^UUID=(.*)/) {
-       my $uuid = $1;
-       foreach (sort keys %fses) {
-           if (exists $fses{$_}->{uuid} &&
-               $fses{$_}->{uuid} eq $uuid) {
-               return ($_, $fses{$_});
-           }
-       }
-       warn "unknown filesystem UUID $uuid\n";
-       return ();
-    } else {
-       return ($_, $fses{$_}) if exists $fses{$_};
-
-        # The following is to handle the case where an fstab entry specifies a
-        # specific device rather than its label or uuid, and the libguestfs
-        # appliance has named the device differently due to the use of a
-        # different driver.
-        # This will work as long as the underlying drivers recognise devices in
-        # the same order.
-       if (m{^/dev/hd(.*)} && exists $fses{"/dev/sd$1"}) {
-           return ("/dev/sd$1", $fses{"/dev/sd$1"});
-       }
-       if (m{^/dev/xvd(.*)} && exists $fses{"/dev/sd$1"}) {
-           return ("/dev/sd$1", $fses{"/dev/sd$1"});
-       }
+my %fses =
+    inspect_all_partitions ($g, \@partitions,
+      use_windows_registry => $windows_registry);
 
-       return () if m{/dev/cdrom};
+#print "fses -----------\n";
+#print Dumper(\%fses);
 
-       warn "unknown filesystem $_\n";
-       return ();
-    }
-}
+my $oses = inspect_operating_systems ($g, \%fses);
 
-#print Dumper(\%oses);
+#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 $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) {
-           $g->mount_ro ($mounts->{$_}, $_)
-               if $_ ne "swap" && ($_ eq '/' || $g->is_dir ($_));
-       }
-
-       check_for_applications ($root_dev);
-       check_for_kernels ($root_dev);
-       if ($oses{$root_dev}->{os} eq "linux") {
-           check_for_modprobe_aliases ($root_dev);
-           check_for_initrd ($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 ();
     }
 }
 
-sub check_for_applications
-{
-    local $_;
-    my $root_dev = shift;
-
-    my @apps;
-
-    my $os = $oses{$root_dev}->{os};
-    if ($os eq "linux") {
-       my $distro = $oses{$root_dev}->{distro};
-       if (defined $distro && ($distro eq "redhat" || $distro eq "fedora")) {
-           my @lines = $g->command_lines
-               (["rpm",
-                 "-q", "-a",
-                 "--qf", "%{name} %{epoch} %{version} %{release} %{arch}\n"]);
-           foreach (@lines) {
-               if (m/^(.*) (.*) (.*) (.*) (.*)$/) {
-                   my $epoch = $2;
-                   $epoch = "" if $epoch eq "(none)";
-                   my $app = {
-                       name => $1,
-                       epoch => $epoch,
-                       version => $3,
-                       release => $4,
-                       arch => $5
-                   };
-                   push @apps, $app
-               }
-           }
-       }
-    } elsif ($os eq "windows") {
-       # XXX
-       # I worked out a general plan for this, but haven't
-       # implemented it yet.  We can iterate over /Program Files
-       # looking for *.EXE files, which we download, then use
-       # i686-pc-mingw32-windres on, to find the VERSIONINFO
-       # section, which has a lot of useful information.
-    }
-
-    $oses{$root_dev}->{apps} = \@apps;
-}
-
-sub check_for_kernels
-{
-    local $_;
-    my $root_dev = shift;
-
-    my @kernels;
-
-    my $os = $oses{$root_dev}->{os};
-    if ($os eq "linux") {
-       # Installed kernels will have a corresponding /lib/modules/<version>
-       # directory, which is the easiest way to find out what kernels
-       # are installed, and what modules are available.
-       foreach ($g->ls ("/lib/modules")) {
-           if ($g->is_dir ("/lib/modules/$_")) {
-               my %kernel;
-               $kernel{version} = $_;
-
-               # List modules.
-               my @modules;
-               foreach ($g->find ("/lib/modules/$_")) {
-                   if (m,/([^/]+)\.ko$, || m,([^/]+)\.o$,) {
-                       push @modules, $1;
-                   }
-               }
-
-               $kernel{modules} = \@modules;
-
-               push @kernels, \%kernel;
-           }
-       }
-
-    } elsif ($os eq "windows") {
-       # XXX
-    }
-
-    $oses{$root_dev}->{kernels} = \@kernels;
-}
-
-# Check /etc/modprobe.conf to see if there are any specified
-# drivers associated with network (ethX) or hard drives.  Normally
-# one might find something like:
-#
-#  alias eth0 xennet
-#  alias scsi_hostadapter xenblk
-#
-# XXX This doesn't look beyond /etc/modprobe.conf, eg. in /etc/modprobe.d/
-
-sub check_for_modprobe_aliases
-{
-    local $_;
-    my $root_dev = shift;
-
-    # Initialise augeas
-    my $success = 0;
-    $success = $g->aug_init("/", 16);
-
-    # Register /etc/modules.conf and /etc/conf.modules to the Modprobe lens
-    my @results;
-    @results = $g->aug_match("/augeas/load/Modprobe/incl");
-
-    # Calculate the next index of /augeas/load/Modprobe/incl
-    my $i = 1;
-    foreach ( @results ) {
-        next unless m{/augeas/load/Modprobe/incl\[(\d*)]};
-        $i = $1 + 1 if ($1 == $i);
-    }
-
-    $success = $g->aug_set("/augeas/load/Modprobe/incl[$i]",
-                           "/etc/modules.conf");
-    $i++;
-    $success = $g->aug_set("/augeas/load/Modprobe/incl[$i]",
-                                  "/etc/conf.modules");
-
-    # Make augeas reload
-    $success = $g->aug_load();
-
-    my %modprobe_aliases;
-
-    for my $pattern qw(/files/etc/conf.modules/alias
-                       /files/etc/modules.conf/alias
-                       /files/etc/modprobe.conf/alias
-                       /files/etc/modprobe.d/*/alias) {
-        @results = $g->aug_match($pattern);
-
-        for my $path ( @results ) {
-            $path =~ m{^/files(.*)/alias(?:\[\d*\])?$}
-                or die("$path doesn't match augeas pattern");
-            my $file = $1;
-
-            my $alias;
-            $alias = $g->aug_get($path);
-
-            my $modulename;
-            $modulename = $g->aug_get($path.'/modulename');
-
-            my %aliasinfo;
-            $aliasinfo{modulename} = $modulename;
-            $aliasinfo{augeas} = $path;
-            $aliasinfo{file} = $file;
-
-            $modprobe_aliases{$alias} = \%aliasinfo;
-        }
-    }
-
-    $oses{$root_dev}->{modprobe_aliases} = \%modprobe_aliases;
-}
-
-# Get a listing of device drivers in any initrd corresponding to a
-# kernel.  This is an indication of what can possibly be booted.
-
-sub check_for_initrd
-{
-    local $_;
-    my $root_dev = shift;
-
-    my %initrd_modules;
-
-    foreach my $initrd ($g->ls ("/boot")) {
-       if ($initrd =~ m/^initrd-(.*)\.img$/ && $g->is_file ("/boot/$initrd")) {
-           my $version = $1;
-           my @modules;
-
-           eval {
-               @modules = $g->initrd_list ("/boot/$initrd");
-           };
-           unless ($@) {
-               @modules = grep { m,([^/]+)\.ko$, || m,([^/]+)\.o$, } @modules;
-               $initrd_modules{$version} = \@modules
-           } else {
-               warn "/boot/$initrd: could not read initrd format"
-           }
-       }
-    }
-
-    $oses{$root_dev}->{initrd_modules} = \%initrd_modules;
-}
-
 #----------------------------------------------------------------------
 # Output.
 
 if ($output eq "fish" || $output eq "ro-fish") {
-    my @osdevs = keys %oses;
+    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;
+    die __"--fish output is only possible with a single OS\n" if @osdevs != 1;
 
     my $root_dev = $osdevs[0];
 
@@ -885,28 +307,28 @@ if ($output eq "fish" || $output eq "ro-fish") {
        print "--ro ";
     }
 
-    print "-a $_ " foreach @ARGV;
+    print "-a $_ " foreach @images;
 
-    my $mounts = $oses{$root_dev}->{mounts};
+    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";
+       print "-m $mounts->{$_}:$_ " if $_ ne "swap" && $_ ne "none";
     }
     print "\n"
 }
 
 # Perl output.
 elsif ($output eq "perl") {
-    print Dumper(\%oses);
+    print Dumper(%$oses);
 }
 
 # YAML output
 elsif ($output eq "yaml") {
-    die "virt-inspector: no YAML support\n"
+    die __"virt-inspector: no YAML support\n"
        unless exists $INC{"YAML/Any.pm"};
 
-    print Dump(\%oses);
+    print Dump(%$oses);
 }
 
 # Plain text output (the default).
@@ -926,7 +348,7 @@ elsif ($output eq "query") {
 
 sub output_text
 {
-    output_text_os ($oses{$_}) foreach sort keys %oses;
+    output_text_os ($oses->{$_}) foreach sort keys %$oses;
 }
 
 sub output_text_os
@@ -935,16 +357,19 @@ sub output_text_os
 
     print $os->{os}, " " if exists $os->{os};
     print $os->{distro}, " " if exists $os->{distro};
-    print $os->{version}, " " if exists $os->{version};
+    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";
+    print __"  Mountpoints:\n";
     my $mounts = $os->{mounts};
     foreach (sort keys %$mounts) {
        printf "    %-30s %s\n", $mounts->{$_}, $_
     }
 
-    print "  Filesystems:\n";
+    print __"  Filesystems:\n";
     my $filesystems = $os->{filesystems};
     foreach (sort keys %$filesystems) {
        print "    $_:\n";
@@ -962,7 +387,7 @@ sub output_text_os
        my %aliases = %{$os->{modprobe_aliases}};
        my @keys = sort keys %aliases;
        if (@keys) {
-           print "  Modprobe aliases:\n";
+           print __"  Modprobe aliases:\n";
            foreach (@keys) {
                printf "    %-30s %s\n", $_, $aliases{$_}->{modulename}
            }
@@ -973,7 +398,7 @@ sub output_text_os
        my %modvers = %{$os->{initrd_modules}};
        my @keys = sort keys %modvers;
        if (@keys) {
-           print "  Initrd modules:\n";
+           print __"  Initrd modules:\n";
            foreach (@keys) {
                my @modules = @{$modvers{$_}};
                print "    $_:\n";
@@ -982,16 +407,16 @@ sub output_text_os
        }
     }
 
-    print "  Applications:\n";
+    print __"  Applications:\n";
     my @apps =  @{$os->{apps}};
     foreach (@apps) {
        print "    $_->{name} $_->{version}\n"
     }
 
-    print "  Kernels:\n";
+    print __"  Kernels:\n";
     my @kernels = @{$os->{kernels}};
     foreach (@kernels) {
-       print "    $_->{version}\n";
+       print "    $_->{version} ($_->{arch})\n";
        my @modules = @{$_->{modules}};
        foreach (@modules) {
            print "      $_\n";
@@ -999,7 +424,7 @@ sub output_text_os
     }
 
     if (exists $os->{root}->{registry}) {
-       print "  Windows Registry entries:\n";
+       print __"  Windows Registry entries:\n";
        # These are just lumps of text - dump them out.
        foreach (@{$os->{root}->{registry}}) {
            print "$_\n";
@@ -1012,7 +437,7 @@ 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;
+    output_xml_os ($oses->{$_}, $xml) foreach sort keys %$oses;
     $xml->endTag("operatingsystems");
 
     $xml->end();
@@ -1026,7 +451,11 @@ sub output_xml_os
 
     foreach ( [ "name" => "os" ],
               [ "distro" => "distro" ],
-              [ "version" => "version" ],
+              [ "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]};
     }
@@ -1104,7 +533,9 @@ sub output_xml_os
     $xml->startTag("kernels");
     my @kernels = @{$os->{kernels}};
     foreach (@kernels) {
-        $xml->startTag("kernel", "version" => $_->{version});
+        $xml->startTag("kernel",
+                      "version" => $_->{version},
+                      "arch" => $_->{arch});
         $xml->startTag("modules");
        my @modules = @{$_->{modules}};
        foreach (@modules) {
@@ -1164,6 +595,8 @@ sub output_query
     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)
@@ -1175,8 +608,8 @@ Answer C<yes> if Microsoft Windows is installed in the guest.
 sub output_query_windows
 {
     my $windows = "no";
-    foreach my $os (keys %oses) {
-       $windows="yes" if $oses{$os}->{os} eq "windows";
+    foreach my $os (keys %$oses) {
+       $windows="yes" if $oses->{$os}->{os} eq "windows";
     }
     print "windows=$windows\n";
 }
@@ -1190,8 +623,8 @@ Answer C<yes> if a Linux kernel is installed in the guest.
 sub output_query_linux
 {
     my $linux = "no";
-    foreach my $os (keys %oses) {
-       $linux="yes" if $oses{$os}->{os} eq "linux";
+    foreach my $os (keys %$oses) {
+       $linux="yes" if $oses->{$os}->{os} eq "linux";
     }
     print "linux=$linux\n";
 }
@@ -1205,8 +638,9 @@ Answer C<yes> if the guest contains Red Hat Enterprise Linux.
 sub output_query_rhel
 {
     my $rhel = "no";
-    foreach my $os (keys %oses) {
-       $rhel="yes" if $oses{$os}->{os} eq "linux" && $oses{$os}->{distro} eq "redhat";
+    foreach my $os (keys %$oses) {
+       $rhel="yes" if ($oses->{$os}->{os} eq "linux" &&
+                        $oses->{$os}->{distro} eq "rhel");
     }
     print "rhel=$rhel\n";
 }
@@ -1220,8 +654,8 @@ Answer C<yes> if the guest contains the Fedora Linux distribution.
 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";
+    foreach my $os (keys %$oses) {
+       $fedora="yes" if $oses->{$os}->{os} eq "linux" && $oses->{$os}->{distro} eq "fedora";
     }
     print "fedora=$fedora\n";
 }
@@ -1235,8 +669,8 @@ Answer C<yes> if the guest contains the Debian Linux distribution.
 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";
+    foreach my $os (keys %$oses) {
+       $debian="yes" if $oses->{$os}->{os} eq "linux" && $oses->{$os}->{distro} eq "debian";
     }
     print "debian=$debian\n";
 }
@@ -1254,8 +688,8 @@ 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}}) {
+    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";
@@ -1276,8 +710,8 @@ guest).
 
 sub output_query_xen_domU_kernel
 {
-    foreach my $os (keys %oses) {
-       foreach my $kernel (@{$oses{$os}->{kernels}}) {
+    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";
@@ -1299,8 +733,8 @@ reasons).
 
 sub output_query_xen_pv_drivers
 {
-    foreach my $os (keys %oses) {
-       foreach my $kernel (@{$oses{$os}->{kernels}}) {
+    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";
@@ -1322,8 +756,8 @@ performance of KVM.
 
 sub output_query_virtio_drivers
 {
-    foreach my $os (keys %oses) {
-       foreach my $kernel (@{$oses{$os}->{kernels}}) {
+    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";
@@ -1335,6 +769,50 @@ sub output_query_virtio_drivers
     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
@@ -1353,6 +831,8 @@ from L<http://home.eunet.no/~pnordahl/ntpasswd/>.
 
 Richard W.M. Jones L<http://et.redhat.com/~rjones/>
 
+Matthew Booth L<mbooth@redhat.com>
+
 =head1 COPYRIGHT
 
 Copyright (C) 2009 Red Hat Inc.