a53c76a91b894ce9ce04e666f62987d7fd345c2e
[libguestfs.git] / inspector / virt-inspector
1 #!/usr/bin/perl -w
2 # virt-inspector
3 # Copyright (C) 2010 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);
24 use Pod::Usage;
25 use Getopt::Long;
26 use File::Temp qw/tempfile/;
27 use File::Basename;
28 use XML::Writer;
29 use Locale::TextDomain 'libguestfs';
30
31 =encoding utf8
32
33 =head1 NAME
34
35 virt-inspector - Display operating system version and other information about a virtual machine
36
37 =head1 SYNOPSIS
38
39  virt-inspector [--connect URI] domname
40
41  virt-inspector guest.img [guest.img ...]
42
43 =head1 DESCRIPTION
44
45 B<virt-inspector> examines a virtual machine or disk image and tries
46 to determine the version of the operating system and other information
47 about the virtual machine.
48
49 Virt-inspector produces XML output for feeding into other programs.
50
51 In the normal usage, use C<virt-inspector domname> where C<domname> is
52 the libvirt domain (see: C<virsh list --all>).
53
54 You can also run virt-inspector directly on disk images from a single
55 virtual machine.  Use C<virt-inspector guest.img>.  In rare cases a
56 domain has several block devices, in which case you should list them
57 one after another, with the first corresponding to the guest's
58 C</dev/sda>, the second to the guest's C</dev/sdb> and so on.
59
60 Virt-inspector can only inspect and report upon I<one domain at a
61 time>.  To inspect several virtual machines, you have to run
62 virt-inspector several times (for example, from a shell script
63 for-loop).
64
65 Because virt-inspector needs direct access to guest images, it won't
66 normally work over remote libvirt connections.
67
68 =head1 OPTIONS
69
70 =over 4
71
72 =cut
73
74 my $help;
75
76 =item B<--help>
77
78 Display brief help.
79
80 =cut
81
82 my $version;
83
84 =item B<--version>
85
86 Display version number and exit.
87
88 =cut
89
90 my $uri;
91
92 =item B<--connect URI> | B<-c URI>
93
94 If using libvirt, connect to the given I<URI>.  If omitted,
95 then we connect to the default libvirt hypervisor.
96
97 Libvirt is only used if you specify a C<domname> on the
98 command line.  If you specify guest block devices directly,
99 then libvirt is not used at all.
100
101 =cut
102
103 my $format;
104
105 =item B<--format> raw
106
107 Specify the format of disk images given on the command line.  If this
108 is omitted then the format is autodetected from the content of the
109 disk image.
110
111 If disk images are requested from libvirt, then this program asks
112 libvirt for this information.  In this case, the value of the format
113 parameter is ignored.
114
115 If working with untrusted raw-format guest disk images, you should
116 ensure the format is always specified.
117
118 =back
119
120 =cut
121
122 GetOptions ("help|?" => \$help,
123             "version" => \$version,
124             "connect|c=s" => \$uri,
125             "format=s" => \$format,
126     ) or pod2usage (2);
127 pod2usage (1) if $help;
128 if ($version) {
129     my $g = Sys::Guestfs->new ();
130     my %h = $g->version ();
131     print "$h{major}.$h{minor}.$h{release}$h{extra}\n";
132     exit
133 }
134 pod2usage (__"virt-inspector: no image or VM names given") if @ARGV == 0;
135
136 my @args = (\@ARGV);
137 push @args, address => $uri if defined $uri;
138 push @args, format => $format if defined $format;
139 my $g = open_guest (@args);
140
141 $g->launch ();
142
143 my @roots = $g->inspect_os ();
144 if (@roots == 0) {
145     die __x("{prog}: No operating system could be detected inside this disk image.\n\nThis may be because the file is not a disk image, or is not a virtual machine\nimage, or because the OS type is not understood by libguestfs.\n\nIf you feel this is an error, please file a bug report including as much\ninformation about the disk image as possible.\n",
146             prog => basename ($0));
147 }
148
149 # Start the XML output.
150 my $xml = new XML::Writer (DATA_MODE => 1, DATA_INDENT => 2);
151
152 $xml->startTag ("operatingsystems");
153
154 my $root;
155 foreach $root (@roots) {
156     my %fses = $g->inspect_get_mountpoints ($root);
157     my @fses = sort { length $a <=> length $b } keys %fses;
158     foreach (@fses) {
159         $g->mount_ro ($fses{$_}, $_);
160     }
161
162     $xml->startTag ("operatingsystem");
163
164     # Basic OS fields.
165     $xml->dataElement (root => canonicalize ($root));
166
167     my ($s, $distro, $major_version);
168     $s = $g->inspect_get_type ($root);
169     $xml->dataElement (name => $s) if $s ne "unknown";
170     $s = $g->inspect_get_arch ($root);
171     $xml->dataElement (arch => $s) if $s ne "unknown";
172     $distro = $g->inspect_get_distro ($root);
173     $xml->dataElement (distro => $distro) if $distro ne "unknown";
174     $s = $g->inspect_get_product_name ($root);
175     $xml->dataElement (product_name => $s) if $s ne "unknown";
176     $major_version = $g->inspect_get_major_version ($root);
177     $xml->dataElement (major_version => $major_version);
178     $s = $g->inspect_get_minor_version ($root);
179     $xml->dataElement (minor_version => $s);
180
181     eval {
182         $s = $g->inspect_get_windows_systemroot ($root);
183         $xml->dataElement (windows_systemroot => $s);
184     };
185
186     # Mountpoints.
187     output_mountpoints ($root, \@fses, \%fses);
188
189     # Filesystems.
190     output_filesystems ($root);
191
192     # Package format / management and applications.
193     output_applications ($root, $distro, $major_version);
194
195     $xml->endTag("operatingsystem");
196
197     $g->umount_all ();
198 }
199
200 # End the XML output.
201 $xml->endTag ("operatingsystems");
202 $xml->end ();
203
204 sub output_mountpoints
205 {
206     local $_;
207     my $root = shift;
208     my $fskeys = shift;
209     my $fshash = shift;
210
211     $xml->startTag ("mountpoints");
212     foreach (@$fskeys) {
213         $xml->dataElement ("mountpoint", $_,
214                            dev => canonicalize ($fshash->{$_}));
215     }
216     $xml->endTag ("mountpoints");
217 }
218
219 sub output_filesystems
220 {
221     local $_;
222     my $root = shift;
223
224     $xml->startTag ("filesystems");
225
226     my @fses = $g->inspect_get_filesystems ($root);
227     foreach (@fses) {
228         $xml->startTag ("filesystem",
229                         dev => canonicalize ($_));
230
231         eval {
232             my $type = $g->vfs_type ($_);
233             $xml->dataElement (type => $type)
234                 if defined $type && $type ne "";
235         };
236
237         eval {
238             my $label = $g->vfs_label ($_);
239             $xml->dataElement (label => $label)
240                 if defined $label && $label ne "";
241         };
242
243         eval {
244             my $uuid = $g->vfs_uuid ($_);
245             $xml->dataElement (uuid => $uuid)
246                 if defined $uuid && $uuid ne "";
247         };
248
249         $xml->endTag ("filesystem");
250     }
251
252     $xml->endTag ("filesystems");
253 }
254
255 sub output_applications
256 {
257     local $_;
258     my $root = shift;
259     my $distro = shift;
260     my $major_version = shift;
261
262     # Based on the distro, take a guess at the package format
263     # and package management.
264     my ($package_format, $package_management);
265     if (defined $distro) {
266         if ($distro eq "debian") {
267             $package_format = "dpkg";
268             $package_management = "apt";
269         }
270         elsif ($distro eq "fedora") {
271             $package_format = "rpm";
272             $package_management = "yum";
273         }
274         elsif ($distro =~ /redhat/ || $distro =~ /rhel/) {
275             if ($major_version >= 5) {
276                 $package_format = "rpm";
277                 $package_management = "yum";
278             } else {
279                 $package_format = "rpm";
280                 $package_management = "up2date";
281             }
282         }
283         # else unknown.
284     }
285
286     $xml->dataElement (package_format => $package_format)
287         if defined $package_format;
288     $xml->dataElement (package_management => $package_management)
289         if defined $package_management;
290
291     # Do we know how to get a list of applications?
292     if (defined $package_format) {
293         if ($package_format eq "rpm") {
294             output_applications_rpm ($root);
295         }
296         # else no we don't.
297     }
298 }
299
300 sub output_applications_rpm
301 {
302     local $_;
303     my $root = shift;
304
305     # Previous virt-inspector ran the 'rpm' program from the guest.
306     # This is insecure, and unnecessary because we can get the same
307     # information directly from the RPM database.
308
309     my @applications;
310
311     eval {
312         my ($fh, $filename) = tempfile (UNLINK => 1);
313         my $fddev = "/dev/fd/" . fileno ($fh);
314         $g->download ("/var/lib/rpm/Name", $fddev);
315         close $fh or die "close: $!";
316
317         # Read the database with the Berkeley DB dump tool.
318         my $cmd = "db_dump -p '$filename'";
319         open PIPE, "$cmd |" or die "close: $!";
320         while (<PIPE>) {
321             chomp;
322             last if /^HEADER=END$/;
323         }
324         while (<PIPE>) {
325             chomp;
326             last if /^DATA=END$/;
327
328             # First character on each data line is a space.
329             if (length $_ > 0 && substr ($_, 0, 1) eq ' ') {
330                 $_ = substr ($_, 1);
331             }
332             # Name should never contain non-printable chars.
333             die "name contains non-printable chars" if /\\/;
334             push @applications, $_;
335
336             $_ = <PIPE>; # discard value
337         }
338         close PIPE or die "close: $!";
339     };
340     if (!$@ && @applications > 0) {
341         @applications = sort @applications;
342         $xml->startTag ("applications");
343         foreach (@applications) {
344             $xml->startTag ("application");
345             $xml->dataElement (name => $_);
346             $xml->endTag ("application");
347         }
348         $xml->endTag ("applications");
349     }
350 }
351
352 # The reverse of device name translation, see
353 # BLOCK DEVICE NAMING in guestfs(3).
354 sub canonicalize
355 {
356     local $_ = shift;
357
358     if (m{^/dev/[hv]d([a-z]\d)$}) {
359         return "/dev/sd$1";
360     }
361     $_;
362 }
363
364 =head1 SHELL QUOTING
365
366 Libvirt guest names can contain arbitrary characters, some of which
367 have meaning to the shell such as C<#> and space.  You may need to
368 quote or escape these characters on the command line.  See the shell
369 manual page L<sh(1)> for details.
370
371 =head1 SEE ALSO
372
373 L<guestfs(3)>,
374 L<guestfish(1)>,
375 L<Sys::Guestfs(3)>,
376 L<Sys::Guestfs::Lib(3)>,
377 L<Sys::Virt(3)>,
378 L<http://libguestfs.org/>.
379
380 =head1 AUTHORS
381
382 =over 4
383
384 =item *
385
386 Richard W.M. Jones L<http://people.redhat.com/~rjones/>
387
388 =item *
389
390 Matthew Booth L<mbooth@redhat.com>
391
392 =back
393
394 =head1 COPYRIGHT
395
396 Copyright (C) 2010 Red Hat Inc.
397
398 This program is free software; you can redistribute it and/or modify
399 it under the terms of the GNU General Public License as published by
400 the Free Software Foundation; either version 2 of the License, or
401 (at your option) any later version.
402
403 This program is distributed in the hope that it will be useful,
404 but WITHOUT ANY WARRANTY; without even the implied warranty of
405 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
406 GNU General Public License for more details.
407
408 You should have received a copy of the GNU General Public License
409 along with this program; if not, write to the Free Software
410 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.