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