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