3 # Copyright (C) 2009 Red Hat Inc.
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 use Sys::Guestfs::Lib qw(open_guest get_partitions);
27 use File::Temp qw/tempdir/;
31 eval "use YAML::Any;";
37 virt-inspector - Display OS version, kernel, drivers, mount points, applications, etc. in a virtual machine
41 virt-inspector [--connect URI] domname
43 virt-inspector guest.img [guest.img ...]
47 B<virt-inspector> examines a virtual machine and tries to determine
48 the version of the OS, the kernel version, what drivers are installed,
49 whether the virtual machine is fully virtualized (FV) or
50 para-virtualized (PV), what applications are installed and more.
52 Virt-inspector can produce output in several formats, including a
53 readable text report, and XML for feeding into other programs.
55 Virt-inspector should only be run on I<inactive> virtual machines.
56 The program tries to determine that the machine is inactive and will
57 refuse to run if it thinks you are trying to inspect a running domain.
59 In the normal usage, use C<virt-inspector domname> where C<domname> is
60 the libvirt domain (see: C<virsh list --all>).
62 You can also run virt-inspector directly on disk images from a single
63 virtual machine. Use C<virt-inspector guest.img>. In rare cases a
64 domain has several block devices, in which case you should list them
65 one after another, with the first corresponding to the guest's
66 C</dev/sda>, the second to the guest's C</dev/sdb> and so on.
68 Virt-inspector can only inspect and report upon I<one domain at a
69 time>. To inspect several virtual machines, you have to run
70 virt-inspector several times (for example, from a shell script
73 Because virt-inspector needs direct access to guest images, it won't
74 normally work over remote libvirt connections.
92 =item B<--connect URI> | B<-c URI>
94 If using libvirt, connect to the given I<URI>. If omitted,
95 then we connect to the default libvirt hypervisor.
97 Libvirt is only used if you specify a C<domname> on the
98 command line. If you specify guest block devices directly,
99 then libvirt is not used at all.
107 Force reading a particular guest even if it appears to be active. In
108 earlier versions of virt-inspector, this could be dangerous (for
109 example, corrupting the guest's disk image). However in more recent
110 versions, it should not cause corruption, but might cause
111 virt-inspector to crash or produce incorrect results.
119 The following options select the output format. Use only one of them.
120 The default is a readable text report.
124 =item B<--text> (default)
130 Produce no output at all.
134 If you select I<--xml> then you get XML output which can be fed
139 If you select I<--yaml> then you get YAML output which can be fed
144 If you select I<--perl> then you get Perl structures output which
145 can be used directly in another Perl program.
151 If you select I<--fish> then we print a L<guestfish(1)> command
152 line which will automatically mount up the filesystems on the
153 correct mount points. Try this for example:
155 guestfish $(virt-inspector --fish guest.img)
157 I<--ro-fish> is the same, but the I<--ro> option is passed to
158 guestfish so that the filesystems are mounted read-only.
162 In "query mode" we answer common questions about the guest, such
163 as whether it is fullvirt or needs a Xen hypervisor to run.
165 See section I<QUERY MODE> below.
169 my $windows_registry;
171 =item B<--windows-registry>
173 If this item is passed, I<and> the guest is Windows, I<and> the
174 external program C<reged> is available (see SEE ALSO section), then we
175 attempt to parse the Windows registry. This allows much more
176 information to be gathered for Windows guests.
178 This is quite an expensive and slow operation, so we don't do it by
185 GetOptions ("help|?" => \$help,
186 "connect|c=s" => \$uri,
188 "text" => sub { $output = "text" },
189 "none" => sub { $output = "none" },
190 "xml" => sub { $output = "xml" },
191 "yaml" => sub { $output = "yaml" },
192 "perl" => sub { $output = "perl" },
193 "fish" => sub { $output = "fish" },
194 "guestfish" => sub { $output = "fish" },
195 "ro-fish" => sub { $output = "ro-fish" },
196 "ro-guestfish" => sub { $output = "ro-fish" },
197 "query" => sub { $output = "query" },
198 "windows-registry" => \$windows_registry,
200 pod2usage (1) if $help;
201 pod2usage ("$0: no image or VM names given") if @ARGV == 0;
204 $rw = 1 if $output eq "fish";
207 $g = open_guest (\@ARGV, rw => $rw, address => $uri);
209 $g = open_guest (\@ARGV, rw => $rw);
219 Linux (distro + version)
223 +--- Filesystems ---------- Installed apps --- Kernel & drivers
224 ----------- -------------- ----------------
225 mount point => device List of apps Extra information
226 mount point => device and versions about kernel(s)
229 (plus lots of extra information
230 about each filesystem)
232 The output of virt-inspector is a complex two-level data structure.
234 At the top level is a list of the operating systems installed on the
235 guest. (For the vast majority of guests, only a single OS is
236 installed.) The data returned for the OS includes the name (Linux,
237 Windows), the distribution and version.
239 The diagram above shows what we return for each OS.
241 With the I<--xml> option the output is mapped into an XML document.
242 Unfortunately there is no clear schema for this document
243 (contributions welcome) but you can get an idea of the format by
244 looking at other documents and as a last resort the source for this
247 With the I<--fish> or I<--ro-fish> option the mount points are mapped to
248 L<guestfish(1)> command line parameters, so that you can go in
249 afterwards and inspect the guest with everything mounted in the
250 right place. For example:
252 guestfish $(virt-inspector --ro-fish guest.img)
253 ==> guestfish --ro -a guest.img -m /dev/VG/LV:/ -m /dev/sda1:/boot
257 # List of possible filesystems.
258 my @devices = get_partitions ($g);
260 # Now query each one to build up a picture of what's in it.
261 my %fses = map { $_ => check_fs ($_) } @devices;
263 # Now the complex checking code itself.
264 # check_fs takes a device name (LV or partition name) and returns
265 # a hashref containing everything we can find out about the device.
268 my $dev = shift; # LV or partition name.
270 my %r; # Result hash.
272 # First try 'file(1)' on it.
273 my $file = $g->file ($dev);
274 if ($file =~ /ext2 filesystem data/) {
277 } elsif ($file =~ /ext3 filesystem data/) {
280 } elsif ($file =~ /ext4 filesystem data/) {
283 } elsif ($file =~ m{Linux/i386 swap file}) {
289 # If it's ext2/3/4, then we want the UUID and label.
290 if (exists $r{fstype} && $r{fstype} =~ /^ext/) {
291 $r{uuid} = $g->get_e2uuid ($dev);
292 $r{label} = $g->get_e2label ($dev);
295 # Try mounting it, fnarrr.
297 $r{is_mountable} = 1;
298 eval { $g->mount_ro ($dev, "/") };
300 # It's not mountable, probably empty or some format
301 # we don't understand.
302 $r{is_mountable} = 0;
307 if ($g->is_file ("/grub/menu.lst") ||
308 $g->is_file ("/grub/grub.conf")) {
309 $r{content} = "linux-grub";
315 if ($g->is_dir ("/etc") && $g->is_dir ("/bin") &&
316 $g->is_file ("/etc/fstab")) {
317 $r{content} = "linux-root";
319 check_linux_root (\%r);
324 if ($g->is_dir ("/etc") && $g->is_dir ("/bin") &&
325 $g->is_dir ("/share") && !$g->exists ("/local") &&
326 !$g->is_file ("/etc/fstab")) {
327 $r{content} = "linux-usrlocal";
332 if ($g->is_dir ("/etc") && $g->is_dir ("/bin") &&
333 $g->is_dir ("/share") && $g->exists ("/local") &&
334 !$g->is_file ("/etc/fstab")) {
335 $r{content} = "linux-usr";
340 if ($g->is_file ("/AUTOEXEC.BAT") ||
341 $g->is_file ("/autoexec.bat") ||
342 $g->is_dir ("/Program Files") ||
343 $g->is_dir ("/WINDOWS") ||
344 $g->is_file ("/boot.ini") ||
345 $g->is_file ("/ntldr")) {
346 $r{fstype} = "ntfs"; # XXX this is a guess
347 $r{fsos} = "windows";
348 $r{content} = "windows-root";
350 check_windows_root (\%r);
365 # Look into /etc to see if we recognise the operating system.
366 if ($g->is_file ("/etc/redhat-release")) {
367 $_ = $g->cat ("/etc/redhat-release");
368 if (/Fedora release (\d+\.\d+)/) {
369 $r->{osdistro} = "fedora";
370 $r->{osversion} = "$1"
371 } elsif (/(Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\d+).*Update (\d+)/) {
372 $r->{osdistro} = "redhat";
373 $r->{osversion} = "$2.$3";
374 } elsif (/(Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\d+(?:\.(\d+))?)/) {
375 $r->{osdistro} = "redhat";
376 $r->{osversion} = "$2";
378 $r->{osdistro} = "redhat";
380 } elsif ($g->is_file ("/etc/debian_version")) {
381 $_ = $g->cat ("/etc/debian_version");
383 $r->{osdistro} = "debian";
384 $r->{osversion} = "$1";
386 $r->{osdistro} = "debian";
390 # Parse the contents of /etc/fstab. This is pretty vital so
391 # we can determine where filesystems are supposed to be mounted.
392 eval "\$_ = \$g->cat ('/etc/fstab');";
394 my @lines = split /\n/;
397 my @fields = split /[ \t]+/;
399 my $spec = $fields[0]; # first column (dev/label/uuid)
400 my $file = $fields[1]; # second column (mountpoint)
401 if ($spec =~ m{^/} ||
402 $spec =~ m{^LABEL=} ||
403 $spec =~ m{^UUID=} ||
405 push @fstab, [$spec, $file]
409 $r->{fstab} = \@fstab if @fstab;
413 # We only support NT. The control file /boot.ini contains a list of
414 # Windows installations and their %systemroot%s in a simple text
417 # XXX We could parse this better. This won't work if /boot.ini is on
418 # a different drive from the %systemroot%, and in other unusual cases.
420 sub check_windows_root
425 my $boot_ini = resolve_windows_path ("/", "boot.ini");
426 $r->{boot_ini} = $boot_ini;
428 if (defined $r->{boot_ini}) {
429 $_ = $g->cat ($boot_ini);
430 my @lines = split /\n/;
436 } elsif (m/^default=.*?\\(\w+)$/i) {
439 } elsif (m/\\(\w+)=/) {
445 if (defined $systemroot) {
446 $r->{systemroot} = resolve_windows_path ("/", $systemroot);
447 if (defined $r->{systemroot} && $windows_registry) {
448 check_windows_registry ($r, $r->{systemroot});
454 sub check_windows_registry
458 my $systemroot = shift;
460 # Download the system registry files. Only download the
461 # interesting ones, and we don't bother with user profiles at all.
462 my $system32 = resolve_windows_path ($systemroot, "system32");
463 if (defined $system32) {
464 my $config = resolve_windows_path ($system32, "config");
465 if (defined $config) {
466 my $software = resolve_windows_path ($config, "software");
467 if (defined $software) {
468 load_windows_registry ($r, $software,
469 "HKEY_LOCAL_MACHINE\\SOFTWARE");
471 my $system = resolve_windows_path ($config, "system");
472 if (defined $system) {
473 load_windows_registry ($r, $system,
474 "HKEY_LOCAL_MACHINE\\System");
480 sub load_windows_registry
487 my $dir = tempdir (CLEANUP => 1);
489 $g->download ($regfile, "$dir/reg");
491 # 'reged' command is particularly noisy. Redirect stdout and
492 # stderr to /dev/null temporarily.
493 open SAVEOUT, ">&STDOUT";
494 open SAVEERR, ">&STDERR";
495 open STDOUT, ">/dev/null";
496 open STDERR, ">/dev/null";
498 my @cmd = ("reged", "-x", "$dir/reg", "$prefix", "\\", "$dir/out");
499 my $res = system (@cmd);
503 open STDOUT, ">&SAVEOUT";
504 open STDERR, ">&SAVEERR";
509 warn "reged command failed: $?";
513 # Some versions of reged segfault on inputs. If that happens we
514 # may get no / partial output file. Anyway, if it exists, load
517 unless (open F, "$dir/out") {
518 warn "no output from reged command: $!";
521 { local $/ = undef; $content = <F>; }
525 @registry = @{$r->{registry}} if exists $r->{registry};
526 push @registry, $content;
527 $r->{registry} = \@registry;
530 # Because of case sensitivity, the actual path might have a different
531 # name, and ntfs-3g is always case sensitive. Find out what the real
532 # path is. Returns the correct full path, or undef.
533 sub resolve_windows_path
536 my $parent = shift; # Must exist, with correct case.
539 foreach ($g->ls ($parent)) {
540 if (lc ($_) eq lc ($dir)) {
541 if ($parent eq "/") {
557 # Grub version, if we care.
560 #print Dumper (\%fses);
562 #----------------------------------------------------------------------
563 # Now find out how many operating systems we've got. Usually just one.
567 foreach (sort keys %fses) {
568 if ($fses{$_}->{is_root}) {
573 get_os_version (\%r);
574 assign_mount_points (\%r);
584 $r->{os} = $r->{root}->{fsos} if exists $r->{root}->{fsos};
585 $r->{distro} = $r->{root}->{osdistro} if exists $r->{root}->{osdistro};
586 $r->{version} = $r->{root}->{osversion} if exists $r->{root}->{osversion};
589 sub assign_mount_points
594 $r->{mounts} = { "/" => $r->{root_device} };
595 $r->{filesystems} = { $r->{root_device} => $r->{root} };
597 # Use /etc/fstab if we have it to mount the rest.
598 if (exists $r->{root}->{fstab}) {
599 my @fstab = @{$r->{root}->{fstab}};
601 my ($spec, $file) = @$_;
603 my ($dev, $fs) = find_filesystem ($spec);
605 $r->{mounts}->{$file} = $dev;
606 $r->{filesystems}->{$dev} = $fs;
607 if (exists $fs->{used}) {
618 # Find filesystem by device name, LABEL=.. or UUID=..
625 foreach (sort keys %fses) {
626 if (exists $fses{$_}->{label} &&
627 $fses{$_}->{label} eq $label) {
628 return ($_, $fses{$_});
631 warn "unknown filesystem label $label\n";
633 } elsif (/^UUID=(.*)/) {
635 foreach (sort keys %fses) {
636 if (exists $fses{$_}->{uuid} &&
637 $fses{$_}->{uuid} eq $uuid) {
638 return ($_, $fses{$_});
641 warn "unknown filesystem UUID $uuid\n";
644 return ($_, $fses{$_}) if exists $fses{$_};
646 # The following is to handle the case where an fstab entry specifies a
647 # specific device rather than its label or uuid, and the libguestfs
648 # appliance has named the device differently due to the use of a
650 # This will work as long as the underlying drivers recognise devices in
652 if (m{^/dev/hd(.*)} && exists $fses{"/dev/sd$1"}) {
653 return ("/dev/sd$1", $fses{"/dev/sd$1"});
655 if (m{^/dev/xvd(.*)} && exists $fses{"/dev/sd$1"}) {
656 return ("/dev/sd$1", $fses{"/dev/sd$1"});
659 return () if m{/dev/cdrom};
661 warn "unknown filesystem $_\n";
666 #print Dumper(\%oses);
668 #----------------------------------------------------------------------
669 # Mount up the disks so we can check for applications
670 # and kernels. Skip this if the output is "*fish" because
671 # we don't need to know.
673 if ($output !~ /.*fish$/) {
675 foreach $root_dev (sort keys %oses) {
676 my $mounts = $oses{$root_dev}->{mounts};
677 # Have to mount / first. Luckily '/' is early in the ASCII
678 # character set, so this should be OK.
679 foreach (sort keys %$mounts) {
680 $g->mount_ro ($mounts->{$_}, $_)
681 if $_ ne "swap" && ($_ eq '/' || $g->is_dir ($_));
684 check_for_applications ($root_dev);
685 check_for_kernels ($root_dev);
686 if ($oses{$root_dev}->{os} eq "linux") {
687 check_for_modprobe_aliases ($root_dev);
688 check_for_initrd ($root_dev);
695 sub check_for_applications
698 my $root_dev = shift;
702 my $os = $oses{$root_dev}->{os};
703 if ($os eq "linux") {
704 my $distro = $oses{$root_dev}->{distro};
705 if (defined $distro && ($distro eq "redhat" || $distro eq "fedora")) {
706 my @lines = $g->command_lines
709 "--qf", "%{name} %{epoch} %{version} %{release} %{arch}\n"]);
711 if (m/^(.*) (.*) (.*) (.*) (.*)$/) {
713 $epoch = "" if $epoch eq "(none)";
725 } elsif ($os eq "windows") {
727 # I worked out a general plan for this, but haven't
728 # implemented it yet. We can iterate over /Program Files
729 # looking for *.EXE files, which we download, then use
730 # i686-pc-mingw32-windres on, to find the VERSIONINFO
731 # section, which has a lot of useful information.
734 $oses{$root_dev}->{apps} = \@apps;
737 sub check_for_kernels
740 my $root_dev = shift;
744 my $os = $oses{$root_dev}->{os};
745 if ($os eq "linux") {
746 # Installed kernels will have a corresponding /lib/modules/<version>
747 # directory, which is the easiest way to find out what kernels
748 # are installed, and what modules are available.
749 foreach ($g->ls ("/lib/modules")) {
750 if ($g->is_dir ("/lib/modules/$_")) {
752 $kernel{version} = $_;
756 foreach ($g->find ("/lib/modules/$_")) {
757 if (m,/([^/]+)\.ko$, || m,([^/]+)\.o$,) {
762 $kernel{modules} = \@modules;
764 push @kernels, \%kernel;
768 } elsif ($os eq "windows") {
772 $oses{$root_dev}->{kernels} = \@kernels;
775 # Check /etc/modprobe.conf to see if there are any specified
776 # drivers associated with network (ethX) or hard drives. Normally
777 # one might find something like:
780 # alias scsi_hostadapter xenblk
782 # XXX This doesn't look beyond /etc/modprobe.conf, eg. in /etc/modprobe.d/
784 sub check_for_modprobe_aliases
787 my $root_dev = shift;
791 $success = $g->aug_init("/", 16);
793 # Register /etc/modules.conf and /etc/conf.modules to the Modprobe lens
795 @results = $g->aug_match("/augeas/load/Modprobe/incl");
797 # Calculate the next index of /augeas/load/Modprobe/incl
799 foreach ( @results ) {
800 next unless m{/augeas/load/Modprobe/incl\[(\d*)]};
801 $i = $1 + 1 if ($1 == $i);
804 $success = $g->aug_set("/augeas/load/Modprobe/incl[$i]",
805 "/etc/modules.conf");
807 $success = $g->aug_set("/augeas/load/Modprobe/incl[$i]",
808 "/etc/conf.modules");
811 $success = $g->aug_load();
813 my %modprobe_aliases;
815 for my $pattern qw(/files/etc/conf.modules/alias
816 /files/etc/modules.conf/alias
817 /files/etc/modprobe.conf/alias
818 /files/etc/modprobe.d/*/alias) {
819 @results = $g->aug_match($pattern);
821 for my $path ( @results ) {
822 $path =~ m{^/files(.*)/alias(?:\[\d*\])?$}
823 or die("$path doesn't match augeas pattern");
827 $alias = $g->aug_get($path);
830 $modulename = $g->aug_get($path.'/modulename');
833 $aliasinfo{modulename} = $modulename;
834 $aliasinfo{augeas} = $path;
835 $aliasinfo{file} = $file;
837 $modprobe_aliases{$alias} = \%aliasinfo;
841 $oses{$root_dev}->{modprobe_aliases} = \%modprobe_aliases;
844 # Get a listing of device drivers in any initrd corresponding to a
845 # kernel. This is an indication of what can possibly be booted.
850 my $root_dev = shift;
854 foreach my $initrd ($g->ls ("/boot")) {
855 if ($initrd =~ m/^initrd-(.*)\.img$/ && $g->is_file ("/boot/$initrd")) {
860 @modules = $g->initrd_list ("/boot/$initrd");
863 @modules = grep { m,([^/]+)\.ko$, || m,([^/]+)\.o$, } @modules;
864 $initrd_modules{$version} = \@modules
866 warn "/boot/$initrd: could not read initrd format"
871 $oses{$root_dev}->{initrd_modules} = \%initrd_modules;
874 #----------------------------------------------------------------------
877 if ($output eq "fish" || $output eq "ro-fish") {
878 my @osdevs = keys %oses;
879 # This only works if there is a single OS.
880 die "--fish output is only possible with a single OS\n" if @osdevs != 1;
882 my $root_dev = $osdevs[0];
884 if ($output eq "ro-fish") {
888 print "-a $_ " foreach @ARGV;
890 my $mounts = $oses{$root_dev}->{mounts};
891 # Have to mount / first. Luckily '/' is early in the ASCII
892 # character set, so this should be OK.
893 foreach (sort keys %$mounts) {
894 print "-m $mounts->{$_}:$_ " if $_ ne "swap";
900 elsif ($output eq "perl") {
901 print Dumper(\%oses);
905 elsif ($output eq "yaml") {
906 die "virt-inspector: no YAML support\n"
907 unless exists $INC{"YAML/Any.pm"};
912 # Plain text output (the default).
913 elsif ($output eq "text") {
918 elsif ($output eq "xml") {
923 elsif ($output eq "query") {
929 output_text_os ($oses{$_}) foreach sort keys %oses;
936 print $os->{os}, " " if exists $os->{os};
937 print $os->{distro}, " " if exists $os->{distro};
938 print $os->{version}, " " if exists $os->{version};
939 print "on ", $os->{root_device}, ":\n";
941 print " Mountpoints:\n";
942 my $mounts = $os->{mounts};
943 foreach (sort keys %$mounts) {
944 printf " %-30s %s\n", $mounts->{$_}, $_
947 print " Filesystems:\n";
948 my $filesystems = $os->{filesystems};
949 foreach (sort keys %$filesystems) {
951 print " label: $filesystems->{$_}{label}\n"
952 if exists $filesystems->{$_}{label};
953 print " UUID: $filesystems->{$_}{uuid}\n"
954 if exists $filesystems->{$_}{uuid};
955 print " type: $filesystems->{$_}{fstype}\n"
956 if exists $filesystems->{$_}{fstype};
957 print " content: $filesystems->{$_}{content}\n"
958 if exists $filesystems->{$_}{content};
961 if (exists $os->{modprobe_aliases}) {
962 my %aliases = %{$os->{modprobe_aliases}};
963 my @keys = sort keys %aliases;
965 print " Modprobe aliases:\n";
967 printf " %-30s %s\n", $_, $aliases{$_}->{modulename}
972 if (exists $os->{initrd_modules}) {
973 my %modvers = %{$os->{initrd_modules}};
974 my @keys = sort keys %modvers;
976 print " Initrd modules:\n";
978 my @modules = @{$modvers{$_}};
980 print " $_\n" foreach @modules;
985 print " Applications:\n";
986 my @apps = @{$os->{apps}};
988 print " $_->{name} $_->{version}\n"
992 my @kernels = @{$os->{kernels}};
994 print " $_->{version}\n";
995 my @modules = @{$_->{modules}};
1001 if (exists $os->{root}->{registry}) {
1002 print " Windows Registry entries:\n";
1003 # These are just lumps of text - dump them out.
1004 foreach (@{$os->{root}->{registry}}) {
1012 my $xml = new XML::Writer(DATA_MODE => 1, DATA_INDENT => 2);
1014 $xml->startTag("operatingsystems");
1015 output_xml_os ($oses{$_}, $xml) foreach sort keys %oses;
1016 $xml->endTag("operatingsystems");
1023 my ($os, $xml) = @_;
1025 $xml->startTag("operatingsystem");
1027 foreach ( [ "name" => "os" ],
1028 [ "distro" => "distro" ],
1029 [ "version" => "version" ],
1030 [ "root" => "root_device" ] ) {
1031 $xml->dataElement($_->[0], $os->{$_->[1]}) if exists $os->{$_->[1]};
1034 $xml->startTag("mountpoints");
1035 my $mounts = $os->{mounts};
1036 foreach (sort keys %$mounts) {
1037 $xml->dataElement("mountpoint", $_, "dev" => $mounts->{$_});
1039 $xml->endTag("mountpoints");
1041 $xml->startTag("filesystems");
1042 my $filesystems = $os->{filesystems};
1043 foreach (sort keys %$filesystems) {
1044 $xml->startTag("filesystem", "dev" => $_);
1046 foreach my $field ( [ "label" => "label" ],
1047 [ "uuid" => "uuid" ],
1048 [ "type" => "fstype" ],
1049 [ "content" => "content" ],
1050 [ "spec" => "spec" ] ) {
1051 $xml->dataElement($field->[0], $filesystems->{$_}{$field->[1]})
1052 if exists $filesystems->{$_}{$field->[1]};
1055 $xml->endTag("filesystem");
1057 $xml->endTag("filesystems");
1059 if (exists $os->{modprobe_aliases}) {
1060 my %aliases = %{$os->{modprobe_aliases}};
1061 my @keys = sort keys %aliases;
1063 $xml->startTag("modprobealiases");
1065 $xml->startTag("alias", "device" => $_);
1067 foreach my $field ( [ "modulename" => "modulename" ],
1068 [ "augeas" => "augeas" ],
1069 [ "file" => "file" ] ) {
1070 $xml->dataElement($field->[0], $aliases{$_}->{$field->[1]});
1073 $xml->endTag("alias");
1075 $xml->endTag("modprobealiases");
1079 if (exists $os->{initrd_modules}) {
1080 my %modvers = %{$os->{initrd_modules}};
1081 my @keys = sort keys %modvers;
1083 $xml->startTag("initrds");
1085 my @modules = @{$modvers{$_}};
1086 $xml->startTag("initrd", "version" => $_);
1087 $xml->dataElement("module", $_) foreach @modules;
1088 $xml->endTag("initrd");
1090 $xml->endTag("initrds");
1094 $xml->startTag("applications");
1095 my @apps = @{$os->{apps}};
1097 $xml->startTag("application");
1098 $xml->dataElement("name", $_->{name});
1099 $xml->dataElement("version", $_->{version});
1100 $xml->endTag("application");
1102 $xml->endTag("applications");
1104 $xml->startTag("kernels");
1105 my @kernels = @{$os->{kernels}};
1106 foreach (@kernels) {
1107 $xml->startTag("kernel", "version" => $_->{version});
1108 $xml->startTag("modules");
1109 my @modules = @{$_->{modules}};
1110 foreach (@modules) {
1111 $xml->dataElement("module", $_);
1113 $xml->endTag("modules");
1114 $xml->endTag("kernel");
1116 $xml->endTag("kernels");
1118 if (exists $os->{root}->{registry}) {
1119 $xml->startTag("windowsregistryentries");
1120 # These are just lumps of text - dump them out.
1121 foreach (@{$os->{root}->{registry}}) {
1122 $xml->dataElement("windowsregistryentry", $_);
1124 $xml->endTag("windowsregistryentries");
1127 $xml->endTag("operatingsystem");
1132 When you use C<virt-inspector --query>, the output is a series of
1140 (each answer is usually C<yes> or C<no>, or the line is completely
1141 missing if we could not determine the answer at all).
1143 If the guest is multiboot, you can get apparently conflicting answers
1144 (eg. C<windows=yes> and C<linux=yes>, or a guest which is both
1145 fullvirt and has a Xen PV kernel). This is normal, and just means
1146 that the guest can do both things, although it might require operator
1147 intervention such as selecting a boot option when the guest is
1150 This section describes the full range of answers possible.
1158 output_query_windows ();
1159 output_query_linux ();
1160 output_query_rhel ();
1161 output_query_fedora ();
1162 output_query_debian ();
1163 output_query_fullvirt ();
1164 output_query_xen_domU_kernel ();
1165 output_query_xen_pv_drivers ();
1166 output_query_virtio_drivers ();
1169 =item windows=(yes|no)
1171 Answer C<yes> if Microsoft Windows is installed in the guest.
1175 sub output_query_windows
1178 foreach my $os (keys %oses) {
1179 $windows="yes" if $oses{$os}->{os} eq "windows";
1181 print "windows=$windows\n";
1184 =item linux=(yes|no)
1186 Answer C<yes> if a Linux kernel is installed in the guest.
1190 sub output_query_linux
1193 foreach my $os (keys %oses) {
1194 $linux="yes" if $oses{$os}->{os} eq "linux";
1196 print "linux=$linux\n";
1201 Answer C<yes> if the guest contains Red Hat Enterprise Linux.
1205 sub output_query_rhel
1208 foreach my $os (keys %oses) {
1209 $rhel="yes" if $oses{$os}->{os} eq "linux" && $oses{$os}->{distro} eq "redhat";
1211 print "rhel=$rhel\n";
1214 =item fedora=(yes|no)
1216 Answer C<yes> if the guest contains the Fedora Linux distribution.
1220 sub output_query_fedora
1223 foreach my $os (keys %oses) {
1224 $fedora="yes" if $oses{$os}->{os} eq "linux" && $oses{$os}->{distro} eq "fedora";
1226 print "fedora=$fedora\n";
1229 =item debian=(yes|no)
1231 Answer C<yes> if the guest contains the Debian Linux distribution.
1235 sub output_query_debian
1238 foreach my $os (keys %oses) {
1239 $debian="yes" if $oses{$os}->{os} eq "linux" && $oses{$os}->{distro} eq "debian";
1241 print "debian=$debian\n";
1244 =item fullvirt=(yes|no)
1246 Answer C<yes> if there is at least one operating system kernel
1247 installed in the guest which runs fully virtualized. Such a guest
1248 would require a hypervisor which supports full system virtualization.
1252 sub output_query_fullvirt
1254 # The assumption is full-virt, unless all installed kernels
1255 # are identified as paravirt.
1256 # XXX Fails on Windows guests.
1257 foreach my $os (keys %oses) {
1258 foreach my $kernel (@{$oses{$os}->{kernels}}) {
1259 my $is_pv = $kernel->{version} =~ m/xen/;
1261 print "fullvirt=yes\n";
1266 print "fullvirt=no\n";
1269 =item xen_domU_kernel=(yes|no)
1271 Answer C<yes> if there is at least one Linux kernel installed in
1272 the guest which is compiled as a Xen DomU (a Xen paravirtualized
1277 sub output_query_xen_domU_kernel
1279 foreach my $os (keys %oses) {
1280 foreach my $kernel (@{$oses{$os}->{kernels}}) {
1281 my $is_xen = $kernel->{version} =~ m/xen/;
1283 print "xen_domU_kernel=yes\n";
1288 print "xen_domU_kernel=no\n";
1291 =item xen_pv_drivers=(yes|no)
1293 Answer C<yes> if the guest has Xen paravirtualized drivers installed
1294 (usually the kernel itself will be fully virtualized, but the PV
1295 drivers have been installed by the administrator for performance
1300 sub output_query_xen_pv_drivers
1302 foreach my $os (keys %oses) {
1303 foreach my $kernel (@{$oses{$os}->{kernels}}) {
1304 foreach my $module (@{$kernel->{modules}}) {
1305 if ($module =~ m/xen-/) {
1306 print "xen_pv_drivers=yes\n";
1312 print "xen_pv_drivers=no\n";
1315 =item virtio_drivers=(yes|no)
1317 Answer C<yes> if the guest has virtio paravirtualized drivers
1318 installed. Virtio drivers are commonly used to improve the
1323 sub output_query_virtio_drivers
1325 foreach my $os (keys %oses) {
1326 foreach my $kernel (@{$oses{$os}->{kernels}}) {
1327 foreach my $module (@{$kernel->{modules}}) {
1328 if ($module =~ m/virtio_/) {
1329 print "virtio_drivers=yes\n";
1335 print "virtio_drivers=no\n";
1345 L<Sys::Guestfs::Lib(3)>,
1347 L<http://libguestfs.org/>.
1349 For Windows registry parsing we require the C<reged> program
1350 from L<http://home.eunet.no/~pnordahl/ntpasswd/>.
1354 Richard W.M. Jones L<http://et.redhat.com/~rjones/>
1358 Copyright (C) 2009 Red Hat Inc.
1360 This program is free software; you can redistribute it and/or modify
1361 it under the terms of the GNU General Public License as published by
1362 the Free Software Foundation; either version 2 of the License, or
1363 (at your option) any later version.
1365 This program is distributed in the hope that it will be useful,
1366 but WITHOUT ANY WARRANTY; without even the implied warranty of
1367 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1368 GNU General Public License for more details.
1370 You should have received a copy of the GNU General Public License
1371 along with this program; if not, write to the Free Software
1372 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.