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