+#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$/) {
+ # Temporary directory for use by check_for_initrd.
+ my $dir = tempdir (CLEANUP => 1);
+
+ 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, $dir);
+ }
+
+ $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;
+
+ my @lines;
+ eval { @lines = $g->read_lines ("/etc/modprobe.conf"); };
+ return if $@ || !@lines;
+
+ my %modprobe_aliases;
+
+ foreach (@lines) {
+ $modprobe_aliases{$1} = $2 if /^\s*alias\s+(\S+)\s+(\S+)/;
+ }
+
+ $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 $dir = 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 = ();
+ # We have to download these to a temporary file.
+ $g->download ("/boot/$initrd", "$dir/initrd");
+
+ my $cmd = "zcat $dir/initrd | file -";
+ open P, "$cmd |" or die "$cmd: $!";
+ my $lines;
+ { local $/ = undef; $lines = <P>; }
+ close P;
+ if ($lines =~ /ext\d filesystem data/) {
+ # Before initramfs came along, these were compressed
+ # ext2 filesystems. We could run another libguestfs
+ # instance to unpack these, but punt on them for now. (XXX)
+ warn "initrd image is unsupported ext2/3/4 filesystem\n";
+ }
+ elsif ($lines =~ /cpio/) {
+ my $cmd = "zcat $dir/initrd | cpio --quiet -it";
+ open P, "$cmd |" or die "$cmd: $!";
+ while (<P>) {
+ push @modules, $1
+ if m,([^/]+)\.ko$, || m,([^/]+)\.o$,;
+ }
+ close P;
+ unlink "$dir/initrd";
+ $initrd_modules{$version} = \@modules;
+ }
+ else {
+ # What?
+ warn "unrecognized initrd image: $lines\n";
+ }
+ }
+ }
+
+ $oses{$root_dev}->{initrd_modules} = \%initrd_modules;
+}
+
+#----------------------------------------------------------------------
+# Output.
+
+if ($output eq "fish" || $output eq "ro-fish") {
+ my @osdevs = keys %oses;
+ # This only works if there is a single OS.
+ die "--fish output is only possible with a single OS\n" if @osdevs != 1;
+
+ my $root_dev = $osdevs[0];
+
+ if ($output eq "ro-fish") {
+ print "--ro ";
+ }
+
+ print "-a $_ " foreach @images;
+
+ my $mounts = $oses{$root_dev}->{mounts};
+ # Have to mount / first. Luckily '/' is early in the ASCII
+ # character set, so this should be OK.
+ foreach (sort keys %$mounts) {
+ print "-m $mounts->{$_}:$_ " if $_ ne "swap";
+ }
+ print "\n"
+}
+
+# Perl output.
+elsif ($output eq "perl") {
+ print Dumper(\%oses);
+}
+
+# Plain text output (the default).
+elsif ($output eq "text") {
+ output_text ();
+}
+
+# XML output.
+elsif ($output eq "xml") {
+ output_xml ();
+}
+
+# Query mode.
+elsif ($output eq "query") {
+ output_query ();
+}
+
+sub output_text
+{
+ output_text_os ($oses{$_}) foreach sort keys %oses;
+}
+
+sub output_text_os
+{
+ my $os = shift;
+
+ print $os->{os}, " " if exists $os->{os};
+ print $os->{distro}, " " if exists $os->{distro};
+ print $os->{version}, " " if exists $os->{version};
+ print "on ", $os->{root_device}, ":\n";
+
+ print " Mountpoints:\n";
+ my $mounts = $os->{mounts};
+ foreach (sort keys %$mounts) {
+ printf " %-30s %s\n", $mounts->{$_}, $_
+ }
+
+ print " Filesystems:\n";
+ my $filesystems = $os->{filesystems};
+ foreach (sort keys %$filesystems) {
+ print " $_:\n";
+ print " label: $filesystems->{$_}{label}\n"
+ if exists $filesystems->{$_}{label};
+ print " UUID: $filesystems->{$_}{uuid}\n"
+ if exists $filesystems->{$_}{uuid};
+ print " type: $filesystems->{$_}{fstype}\n"
+ if exists $filesystems->{$_}{fstype};
+ print " content: $filesystems->{$_}{content}\n"
+ if exists $filesystems->{$_}{content};
+ }
+
+ if (exists $os->{modprobe_aliases}) {
+ my %aliases = %{$os->{modprobe_aliases}};
+ my @keys = sort keys %aliases;
+ if (@keys) {
+ print " Modprobe aliases:\n";
+ foreach (@keys) {
+ printf " %-30s %s\n", $_, $aliases{$_}
+ }
+ }
+ }
+
+ if (exists $os->{initrd_modules}) {
+ my %modvers = %{$os->{initrd_modules}};
+ my @keys = sort keys %modvers;
+ if (@keys) {
+ print " Initrd modules:\n";
+ foreach (@keys) {
+ my @modules = @{$modvers{$_}};
+ print " $_:\n";
+ print " $_\n" foreach @modules;
+ }
+ }
+ }
+
+ print " Applications:\n";
+ my @apps = @{$os->{apps}};
+ foreach (@apps) {
+ print " $_->{name} $_->{version}\n"
+ }
+
+ print " Kernels:\n";
+ my @kernels = @{$os->{kernels}};
+ foreach (@kernels) {
+ print " $_->{version}\n";
+ my @modules = @{$_->{modules}};
+ foreach (@modules) {
+ print " $_\n";
+ }
+ }
+
+ if (exists $os->{root}->{registry}) {
+ print " Windows Registry entries:\n";
+ # These are just lumps of text - dump them out.
+ foreach (@{$os->{root}->{registry}}) {
+ print "$_\n";
+ }
+ }
+}
+
+sub output_xml
+{
+ print "<operatingsystems>\n";
+ output_xml_os ($oses{$_}) foreach sort keys %oses;
+ print "</operatingsystems>\n";
+}
+
+sub output_xml_os
+{
+ my $os = shift;
+
+ print "<operatingsystem>\n";
+
+ print "<os>", $os->{os}, "</os>\n" if exists $os->{os};
+ print "<distro>", $os->{distro}, "</distro>\n" if exists $os->{distro};
+ print "<version>", $os->{version}, "</version>\n" if exists $os->{version};
+ print "<root>", $os->{root_device}, "</root>\n";
+
+ print "<mountpoints>\n";
+ my $mounts = $os->{mounts};
+ foreach (sort keys %$mounts) {
+ printf "<mountpoint dev='%s'>%s</mountpoint>\n",
+ $mounts->{$_}, $_
+ }
+ print "</mountpoints>\n";
+
+ print "<filesystems>\n";
+ my $filesystems = $os->{filesystems};
+ foreach (sort keys %$filesystems) {
+ print "<filesystem dev='$_'>\n";
+ print "<label>$filesystems->{$_}{label}</label>\n"
+ if exists $filesystems->{$_}{label};
+ print "<uuid>$filesystems->{$_}{uuid}</uuid>\n"
+ if exists $filesystems->{$_}{uuid};
+ print "<type>$filesystems->{$_}{fstype}</type>\n"
+ if exists $filesystems->{$_}{fstype};
+ print "<content>$filesystems->{$_}{content}</content>\n"
+ if exists $filesystems->{$_}{content};
+ print "</filesystem>\n";
+ }
+ print "</filesystems>\n";
+
+ if (exists $os->{modprobe_aliases}) {
+ my %aliases = %{$os->{modprobe_aliases}};
+ my @keys = sort keys %aliases;
+ if (@keys) {
+ print "<modprobealiases>\n";
+ foreach (@keys) {
+ printf "<alias device=\"%s\">%s</alias>\n", $_, $aliases{$_}
+ }
+ print "</modprobealiases>\n";
+ }
+ }
+
+ if (exists $os->{initrd_modules}) {
+ my %modvers = %{$os->{initrd_modules}};
+ my @keys = sort keys %modvers;
+ if (@keys) {
+ print "<initrds>\n";
+ foreach (@keys) {
+ my @modules = @{$modvers{$_}};
+ print "<initrd version=\"$_\">\n";
+ print "<module>$_</module>\n" foreach @modules;
+ print "</initrd>\n";
+ }
+ print "</initrds>\n";
+ }
+ }
+
+ print "<applications>\n";
+ my @apps = @{$os->{apps}};
+ foreach (@apps) {
+ print "<application>\n";
+ print "<name>$_->{name}</name><version>$_->{version}</version>\n";
+ print "</application>\n";
+ }
+ print "</applications>\n";
+
+ print "<kernels>\n";
+ my @kernels = @{$os->{kernels}};
+ foreach (@kernels) {
+ print "<kernel>\n";
+ print "<version>$_->{version}</version>\n";
+ print "<modules>\n";
+ my @modules = @{$_->{modules}};
+ foreach (@modules) {
+ print "<module>$_</module>\n";
+ }
+ print "</modules>\n";
+ print "</kernel>\n";
+ }
+ print "</kernels>\n";
+
+ if (exists $os->{root}->{registry}) {
+ print "<windowsregistryentries>\n";
+ # These are just lumps of text - dump them out.
+ foreach (@{$os->{root}->{registry}}) {
+ print "<windowsregistryentry>\n";
+ print escape_xml($_), "\n";
+ print "</windowsregistryentry>\n";
+ }
+ print "</windowsregistryentries>\n";
+ }
+
+ print "</operatingsystem>\n";
+}
+
+sub escape_xml
+{
+ local $_ = shift;
+
+ s/&/&/g;
+ s/</</g;
+ s/>/>/g;
+ return $_;
+}
+
+=head1 QUERY MODE