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