Move the inspection analysis code into Sys::Guestfs::Lib library.
[libguestfs.git] / inspector / virt-inspector.pl
1 #!/usr/bin/perl -w
2 # virt-inspector
3 # Copyright (C) 2009 Red Hat Inc.
4 #
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.
9 #
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.
14 #
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.
18
19 use warnings;
20 use strict;
21
22 use Sys::Guestfs;
23 use Sys::Guestfs::Lib qw(open_guest get_partitions resolve_windows_path
24   inspect_all_partitions inspect_partition
25   inspect_operating_systems mount_operating_system inspect_in_detail);
26 use Pod::Usage;
27 use Getopt::Long;
28 use Data::Dumper;
29 use File::Temp qw/tempdir/;
30 use XML::Writer;
31
32 # Optional:
33 eval "use YAML::Any;";
34
35 =encoding utf8
36
37 =head1 NAME
38
39 virt-inspector - Display OS version, kernel, drivers, mount points, applications, etc. in a virtual machine
40
41 =head1 SYNOPSIS
42
43  virt-inspector [--connect URI] domname
44
45  virt-inspector guest.img [guest.img ...]
46
47 =head1 DESCRIPTION
48
49 B<virt-inspector> examines a virtual machine and tries to determine
50 the version of the OS, the kernel version, what drivers are installed,
51 whether the virtual machine is fully virtualized (FV) or
52 para-virtualized (PV), what applications are installed and more.
53
54 Virt-inspector can produce output in several formats, including a
55 readable text report, and XML for feeding into other programs.
56
57 Virt-inspector should only be run on I<inactive> virtual machines.
58 The program tries to determine that the machine is inactive and will
59 refuse to run if it thinks you are trying to inspect a running domain.
60
61 In the normal usage, use C<virt-inspector domname> where C<domname> is
62 the libvirt domain (see: C<virsh list --all>).
63
64 You can also run virt-inspector directly on disk images from a single
65 virtual machine.  Use C<virt-inspector guest.img>.  In rare cases a
66 domain has several block devices, in which case you should list them
67 one after another, with the first corresponding to the guest's
68 C</dev/sda>, the second to the guest's C</dev/sdb> and so on.
69
70 Virt-inspector can only inspect and report upon I<one domain at a
71 time>.  To inspect several virtual machines, you have to run
72 virt-inspector several times (for example, from a shell script
73 for-loop).
74
75 Because virt-inspector needs direct access to guest images, it won't
76 normally work over remote libvirt connections.
77
78 =head1 OPTIONS
79
80 =over 4
81
82 =cut
83
84 my $help;
85
86 =item B<--help>
87
88 Display brief help.
89
90 =cut
91
92 my $uri;
93
94 =item B<--connect URI> | B<-c URI>
95
96 If using libvirt, connect to the given I<URI>.  If omitted,
97 then we connect to the default libvirt hypervisor.
98
99 Libvirt is only used if you specify a C<domname> on the
100 command line.  If you specify guest block devices directly,
101 then libvirt is not used at all.
102
103 =cut
104
105 my $force;
106
107 =item B<--force>
108
109 Force reading a particular guest even if it appears to be active.  In
110 earlier versions of virt-inspector, this could be dangerous (for
111 example, corrupting the guest's disk image).  However in more recent
112 versions, it should not cause corruption, but might cause
113 virt-inspector to crash or produce incorrect results.
114
115 =cut
116
117 my $output = "text";
118
119 =back
120
121 The following options select the output format.  Use only one of them.
122 The default is a readable text report.
123
124 =over 4
125
126 =item B<--text> (default)
127
128 Plain text report.
129
130 =item B<--none>
131
132 Produce no output at all.
133
134 =item B<--xml>
135
136 If you select I<--xml> then you get XML output which can be fed
137 to other programs.
138
139 =item B<--yaml>
140
141 If you select I<--yaml> then you get YAML output which can be fed
142 to other programs.
143
144 =item B<--perl>
145
146 If you select I<--perl> then you get Perl structures output which
147 can be used directly in another Perl program.
148
149 =item B<--fish>
150
151 =item B<--ro-fish>
152
153 If you select I<--fish> then we print a L<guestfish(1)> command
154 line which will automatically mount up the filesystems on the
155 correct mount points.  Try this for example:
156
157  guestfish $(virt-inspector --fish guest.img)
158
159 I<--ro-fish> is the same, but the I<--ro> option is passed to
160 guestfish so that the filesystems are mounted read-only.
161
162 =item B<--query>
163
164 In "query mode" we answer common questions about the guest, such
165 as whether it is fullvirt or needs a Xen hypervisor to run.
166
167 See section I<QUERY MODE> below.
168
169 =cut
170
171 my $windows_registry;
172
173 =item B<--windows-registry>
174
175 If this item is passed, I<and> the guest is Windows, I<and> the
176 external program C<reged> is available (see SEE ALSO section), then we
177 attempt to parse the Windows registry.  This allows much more
178 information to be gathered for Windows guests.
179
180 This is quite an expensive and slow operation, so we don't do it by
181 default.
182
183 =back
184
185 =cut
186
187 GetOptions ("help|?" => \$help,
188             "connect|c=s" => \$uri,
189             "force" => \$force,
190             "text" => sub { $output = "text" },
191             "none" => sub { $output = "none" },
192             "xml" => sub { $output = "xml" },
193             "yaml" => sub { $output = "yaml" },
194             "perl" => sub { $output = "perl" },
195             "fish" => sub { $output = "fish" },
196             "guestfish" => sub { $output = "fish" },
197             "ro-fish" => sub { $output = "ro-fish" },
198             "ro-guestfish" => sub { $output = "ro-fish" },
199             "query" => sub { $output = "query" },
200             "windows-registry" => \$windows_registry,
201     ) or pod2usage (2);
202 pod2usage (1) if $help;
203 pod2usage ("$0: no image or VM names given") if @ARGV == 0;
204
205 my $rw = 0;
206 $rw = 1 if $output eq "fish";
207 my $g;
208 if ($uri) {
209     $g = open_guest (\@ARGV, rw => $rw, address => $uri);
210 } else {
211     $g = open_guest (\@ARGV, rw => $rw);
212 }
213
214 $g->launch ();
215 $g->wait_ready ();
216
217 =head1 OUTPUT FORMAT
218
219  Operating system(s)
220  -------------------
221  Linux (distro + version)
222  Windows (version)
223     |
224     |
225     +--- Filesystems ---------- Installed apps --- Kernel & drivers
226          -----------            --------------     ----------------
227          mount point => device  List of apps       Extra information
228          mount point => device  and versions       about kernel(s)
229               ...                                  and drivers
230          swap => swap device
231          (plus lots of extra information
232          about each filesystem)
233
234 The output of virt-inspector is a complex two-level data structure.
235
236 At the top level is a list of the operating systems installed on the
237 guest.  (For the vast majority of guests, only a single OS is
238 installed.)  The data returned for the OS includes the name (Linux,
239 Windows), the distribution and version.
240
241 The diagram above shows what we return for each OS.
242
243 With the I<--xml> option the output is mapped into an XML document.
244 Unfortunately there is no clear schema for this document
245 (contributions welcome) but you can get an idea of the format by
246 looking at other documents and as a last resort the source for this
247 program.
248
249 With the I<--fish> or I<--ro-fish> option the mount points are mapped to
250 L<guestfish(1)> command line parameters, so that you can go in
251 afterwards and inspect the guest with everything mounted in the
252 right place.  For example:
253
254  guestfish $(virt-inspector --ro-fish guest.img)
255  ==> guestfish --ro -a guest.img -m /dev/VG/LV:/ -m /dev/sda1:/boot
256
257 =cut
258
259 # List of possible filesystems.
260 my @partitions = get_partitions ($g);
261
262 # Now query each one to build up a picture of what's in it.
263 my %fses =
264     inspect_all_partitions ($g, \@partitions,
265       use_windows_registry => $windows_registry);
266
267 #print "fses -----------\n";
268 #print Dumper(\%fses);
269
270 my $oses = inspect_operating_systems ($g, \%fses);
271
272 #print "oses -----------\n";
273 #print Dumper($oses);
274
275 # Mount up the disks so we can check for applications
276 # and kernels.  Skip this if the output is "*fish" because
277 # we don't need to know.
278
279 if ($output !~ /.*fish$/) {
280     my $root_dev;
281     foreach $root_dev (sort keys %$oses) {
282         my $os = $oses->{$root_dev};
283         mount_operating_system ($g, $os);
284         inspect_in_detail ($g, $os);
285         $g->umount_all ();
286     }
287 }
288
289 #----------------------------------------------------------------------
290 # Output.
291
292 if ($output eq "fish" || $output eq "ro-fish") {
293     my @osdevs = keys %$oses;
294     # This only works if there is a single OS.
295     die "--fish output is only possible with a single OS\n" if @osdevs != 1;
296
297     my $root_dev = $osdevs[0];
298
299     if ($output eq "ro-fish") {
300         print "--ro ";
301     }
302
303     print "-a $_ " foreach @ARGV;
304
305     my $mounts = $oses->{$root_dev}->{mounts};
306     # Have to mount / first.  Luckily '/' is early in the ASCII
307     # character set, so this should be OK.
308     foreach (sort keys %$mounts) {
309         print "-m $mounts->{$_}:$_ " if $_ ne "swap";
310     }
311     print "\n"
312 }
313
314 # Perl output.
315 elsif ($output eq "perl") {
316     print Dumper(%$oses);
317 }
318
319 # YAML output
320 elsif ($output eq "yaml") {
321     die "virt-inspector: no YAML support\n"
322         unless exists $INC{"YAML/Any.pm"};
323
324     print Dump(%$oses);
325 }
326
327 # Plain text output (the default).
328 elsif ($output eq "text") {
329     output_text ();
330 }
331
332 # XML output.
333 elsif ($output eq "xml") {
334     output_xml ();
335 }
336
337 # Query mode.
338 elsif ($output eq "query") {
339     output_query ();
340 }
341
342 sub output_text
343 {
344     output_text_os ($oses->{$_}) foreach sort keys %$oses;
345 }
346
347 sub output_text_os
348 {
349     my $os = shift;
350
351     print $os->{os}, " " if exists $os->{os};
352     print $os->{distro}, " " if exists $os->{distro};
353     print $os->{version}, " " if exists $os->{version};
354     print "on ", $os->{root_device}, ":\n";
355
356     print "  Mountpoints:\n";
357     my $mounts = $os->{mounts};
358     foreach (sort keys %$mounts) {
359         printf "    %-30s %s\n", $mounts->{$_}, $_
360     }
361
362     print "  Filesystems:\n";
363     my $filesystems = $os->{filesystems};
364     foreach (sort keys %$filesystems) {
365         print "    $_:\n";
366         print "      label: $filesystems->{$_}{label}\n"
367             if exists $filesystems->{$_}{label};
368         print "      UUID: $filesystems->{$_}{uuid}\n"
369             if exists $filesystems->{$_}{uuid};
370         print "      type: $filesystems->{$_}{fstype}\n"
371             if exists $filesystems->{$_}{fstype};
372         print "      content: $filesystems->{$_}{content}\n"
373             if exists $filesystems->{$_}{content};
374     }
375
376     if (exists $os->{modprobe_aliases}) {
377         my %aliases = %{$os->{modprobe_aliases}};
378         my @keys = sort keys %aliases;
379         if (@keys) {
380             print "  Modprobe aliases:\n";
381             foreach (@keys) {
382                 printf "    %-30s %s\n", $_, $aliases{$_}->{modulename}
383             }
384         }
385     }
386
387     if (exists $os->{initrd_modules}) {
388         my %modvers = %{$os->{initrd_modules}};
389         my @keys = sort keys %modvers;
390         if (@keys) {
391             print "  Initrd modules:\n";
392             foreach (@keys) {
393                 my @modules = @{$modvers{$_}};
394                 print "    $_:\n";
395                 print "      $_\n" foreach @modules;
396             }
397         }
398     }
399
400     print "  Applications:\n";
401     my @apps =  @{$os->{apps}};
402     foreach (@apps) {
403         print "    $_->{name} $_->{version}\n"
404     }
405
406     print "  Kernels:\n";
407     my @kernels = @{$os->{kernels}};
408     foreach (@kernels) {
409         print "    $_->{version}\n";
410         my @modules = @{$_->{modules}};
411         foreach (@modules) {
412             print "      $_\n";
413         }
414     }
415
416     if (exists $os->{root}->{registry}) {
417         print "  Windows Registry entries:\n";
418         # These are just lumps of text - dump them out.
419         foreach (@{$os->{root}->{registry}}) {
420             print "$_\n";
421         }
422     }
423 }
424
425 sub output_xml
426 {
427     my $xml = new XML::Writer(DATA_MODE => 1, DATA_INDENT => 2);
428
429     $xml->startTag("operatingsystems");
430     output_xml_os ($oses->{$_}, $xml) foreach sort keys %$oses;
431     $xml->endTag("operatingsystems");
432
433     $xml->end();
434 }
435
436 sub output_xml_os
437 {
438     my ($os, $xml) = @_;
439
440     $xml->startTag("operatingsystem");
441
442     foreach ( [ "name" => "os" ],
443               [ "distro" => "distro" ],
444               [ "version" => "version" ],
445               [ "root" => "root_device" ] ) {
446         $xml->dataElement($_->[0], $os->{$_->[1]}) if exists $os->{$_->[1]};
447     }
448
449     $xml->startTag("mountpoints");
450     my $mounts = $os->{mounts};
451     foreach (sort keys %$mounts) {
452         $xml->dataElement("mountpoint", $_, "dev" => $mounts->{$_});
453     }
454     $xml->endTag("mountpoints");
455
456     $xml->startTag("filesystems");
457     my $filesystems = $os->{filesystems};
458     foreach (sort keys %$filesystems) {
459         $xml->startTag("filesystem", "dev" => $_);
460
461         foreach my $field ( [ "label" => "label" ],
462                             [ "uuid" => "uuid" ],
463                             [ "type" => "fstype" ],
464                             [ "content" => "content" ],
465                             [ "spec" => "spec" ] ) {
466             $xml->dataElement($field->[0], $filesystems->{$_}{$field->[1]})
467                 if exists $filesystems->{$_}{$field->[1]};
468         }
469
470         $xml->endTag("filesystem");
471     }
472     $xml->endTag("filesystems");
473
474     if (exists $os->{modprobe_aliases}) {
475         my %aliases = %{$os->{modprobe_aliases}};
476         my @keys = sort keys %aliases;
477         if (@keys) {
478             $xml->startTag("modprobealiases");
479             foreach (@keys) {
480                 $xml->startTag("alias", "device" => $_);
481
482                 foreach my $field ( [ "modulename" => "modulename" ],
483                                     [ "augeas" => "augeas" ],
484                                     [ "file" => "file" ] ) {
485                     $xml->dataElement($field->[0], $aliases{$_}->{$field->[1]});
486                 }
487
488                 $xml->endTag("alias");
489             }
490             $xml->endTag("modprobealiases");
491         }
492     }
493
494     if (exists $os->{initrd_modules}) {
495         my %modvers = %{$os->{initrd_modules}};
496         my @keys = sort keys %modvers;
497         if (@keys) {
498             $xml->startTag("initrds");
499             foreach (@keys) {
500                 my @modules = @{$modvers{$_}};
501                 $xml->startTag("initrd", "version" => $_);
502                 $xml->dataElement("module", $_) foreach @modules;
503                 $xml->endTag("initrd");
504             }
505             $xml->endTag("initrds");
506         }
507     }
508
509     $xml->startTag("applications");
510     my @apps =  @{$os->{apps}};
511     foreach (@apps) {
512         $xml->startTag("application");
513         $xml->dataElement("name", $_->{name});
514         $xml->dataElement("version", $_->{version});
515         $xml->endTag("application");
516     }
517     $xml->endTag("applications");
518
519     $xml->startTag("kernels");
520     my @kernels = @{$os->{kernels}};
521     foreach (@kernels) {
522         $xml->startTag("kernel", "version" => $_->{version});
523         $xml->startTag("modules");
524         my @modules = @{$_->{modules}};
525         foreach (@modules) {
526             $xml->dataElement("module", $_);
527         }
528         $xml->endTag("modules");
529         $xml->endTag("kernel");
530     }
531     $xml->endTag("kernels");
532
533     if (exists $os->{root}->{registry}) {
534         $xml->startTag("windowsregistryentries");
535         # These are just lumps of text - dump them out.
536         foreach (@{$os->{root}->{registry}}) {
537             $xml->dataElement("windowsregistryentry", $_);
538         }
539         $xml->endTag("windowsregistryentries");
540     }
541
542     $xml->endTag("operatingsystem");
543 }
544
545 =head1 QUERY MODE
546
547 When you use C<virt-inspector --query>, the output is a series of
548 lines of the form:
549
550  windows=no
551  linux=yes
552  fullvirt=yes
553  xen_pv_drivers=no
554
555 (each answer is usually C<yes> or C<no>, or the line is completely
556 missing if we could not determine the answer at all).
557
558 If the guest is multiboot, you can get apparently conflicting answers
559 (eg. C<windows=yes> and C<linux=yes>, or a guest which is both
560 fullvirt and has a Xen PV kernel).  This is normal, and just means
561 that the guest can do both things, although it might require operator
562 intervention such as selecting a boot option when the guest is
563 booting.
564
565 This section describes the full range of answers possible.
566
567 =over 4
568
569 =cut
570
571 sub output_query
572 {
573     output_query_windows ();
574     output_query_linux ();
575     output_query_rhel ();
576     output_query_fedora ();
577     output_query_debian ();
578     output_query_fullvirt ();
579     output_query_xen_domU_kernel ();
580     output_query_xen_pv_drivers ();
581     output_query_virtio_drivers ();
582 }
583
584 =item windows=(yes|no)
585
586 Answer C<yes> if Microsoft Windows is installed in the guest.
587
588 =cut
589
590 sub output_query_windows
591 {
592     my $windows = "no";
593     foreach my $os (keys %$oses) {
594         $windows="yes" if $oses->{$os}->{os} eq "windows";
595     }
596     print "windows=$windows\n";
597 }
598
599 =item linux=(yes|no)
600
601 Answer C<yes> if a Linux kernel is installed in the guest.
602
603 =cut
604
605 sub output_query_linux
606 {
607     my $linux = "no";
608     foreach my $os (keys %$oses) {
609         $linux="yes" if $oses->{$os}->{os} eq "linux";
610     }
611     print "linux=$linux\n";
612 }
613
614 =item rhel=(yes|no)
615
616 Answer C<yes> if the guest contains Red Hat Enterprise Linux.
617
618 =cut
619
620 sub output_query_rhel
621 {
622     my $rhel = "no";
623     foreach my $os (keys %$oses) {
624         $rhel="yes" if $oses->{$os}->{os} eq "linux" && $oses->{$os}->{distro} eq "redhat";
625     }
626     print "rhel=$rhel\n";
627 }
628
629 =item fedora=(yes|no)
630
631 Answer C<yes> if the guest contains the Fedora Linux distribution.
632
633 =cut
634
635 sub output_query_fedora
636 {
637     my $fedora = "no";
638     foreach my $os (keys %$oses) {
639         $fedora="yes" if $oses->{$os}->{os} eq "linux" && $oses->{$os}->{distro} eq "fedora";
640     }
641     print "fedora=$fedora\n";
642 }
643
644 =item debian=(yes|no)
645
646 Answer C<yes> if the guest contains the Debian Linux distribution.
647
648 =cut
649
650 sub output_query_debian
651 {
652     my $debian = "no";
653     foreach my $os (keys %$oses) {
654         $debian="yes" if $oses->{$os}->{os} eq "linux" && $oses->{$os}->{distro} eq "debian";
655     }
656     print "debian=$debian\n";
657 }
658
659 =item fullvirt=(yes|no)
660
661 Answer C<yes> if there is at least one operating system kernel
662 installed in the guest which runs fully virtualized.  Such a guest
663 would require a hypervisor which supports full system virtualization.
664
665 =cut
666
667 sub output_query_fullvirt
668 {
669     # The assumption is full-virt, unless all installed kernels
670     # are identified as paravirt.
671     # XXX Fails on Windows guests.
672     foreach my $os (keys %$oses) {
673         foreach my $kernel (@{$oses->{$os}->{kernels}}) {
674             my $is_pv = $kernel->{version} =~ m/xen/;
675             unless ($is_pv) {
676                 print "fullvirt=yes\n";
677                 return;
678             }
679         }
680     }
681     print "fullvirt=no\n";
682 }
683
684 =item xen_domU_kernel=(yes|no)
685
686 Answer C<yes> if there is at least one Linux kernel installed in
687 the guest which is compiled as a Xen DomU (a Xen paravirtualized
688 guest).
689
690 =cut
691
692 sub output_query_xen_domU_kernel
693 {
694     foreach my $os (keys %$oses) {
695         foreach my $kernel (@{$oses->{$os}->{kernels}}) {
696             my $is_xen = $kernel->{version} =~ m/xen/;
697             if ($is_xen) {
698                 print "xen_domU_kernel=yes\n";
699                 return;
700             }
701         }
702     }
703     print "xen_domU_kernel=no\n";
704 }
705
706 =item xen_pv_drivers=(yes|no)
707
708 Answer C<yes> if the guest has Xen paravirtualized drivers installed
709 (usually the kernel itself will be fully virtualized, but the PV
710 drivers have been installed by the administrator for performance
711 reasons).
712
713 =cut
714
715 sub output_query_xen_pv_drivers
716 {
717     foreach my $os (keys %$oses) {
718         foreach my $kernel (@{$oses->{$os}->{kernels}}) {
719             foreach my $module (@{$kernel->{modules}}) {
720                 if ($module =~ m/xen-/) {
721                     print "xen_pv_drivers=yes\n";
722                     return;
723                 }
724             }
725         }
726     }
727     print "xen_pv_drivers=no\n";
728 }
729
730 =item virtio_drivers=(yes|no)
731
732 Answer C<yes> if the guest has virtio paravirtualized drivers
733 installed.  Virtio drivers are commonly used to improve the
734 performance of KVM.
735
736 =cut
737
738 sub output_query_virtio_drivers
739 {
740     foreach my $os (keys %$oses) {
741         foreach my $kernel (@{$oses->{$os}->{kernels}}) {
742             foreach my $module (@{$kernel->{modules}}) {
743                 if ($module =~ m/virtio_/) {
744                     print "virtio_drivers=yes\n";
745                     return;
746                 }
747             }
748         }
749     }
750     print "virtio_drivers=no\n";
751 }
752
753 =back
754
755 =head1 SEE ALSO
756
757 L<guestfs(3)>,
758 L<guestfish(1)>,
759 L<Sys::Guestfs(3)>,
760 L<Sys::Guestfs::Lib(3)>,
761 L<Sys::Virt(3)>,
762 L<http://libguestfs.org/>.
763
764 For Windows registry parsing we require the C<reged> program
765 from L<http://home.eunet.no/~pnordahl/ntpasswd/>.
766
767 =head1 AUTHOR
768
769 Richard W.M. Jones L<http://et.redhat.com/~rjones/>
770
771 =head1 COPYRIGHT
772
773 Copyright (C) 2009 Red Hat Inc.
774
775 This program is free software; you can redistribute it and/or modify
776 it under the terms of the GNU General Public License as published by
777 the Free Software Foundation; either version 2 of the License, or
778 (at your option) any later version.
779
780 This program is distributed in the hope that it will be useful,
781 but WITHOUT ANY WARRANTY; without even the implied warranty of
782 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
783 GNU General Public License for more details.
784
785 You should have received a copy of the GNU General Public License
786 along with this program; if not, write to the Free Software
787 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.