inspector: Replace code for listing applications with new core API.
[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 =head1 XML FORMAT
150
151 The virt-inspector XML is described precisely in a RELAX NG schema
152 which is supplied with libguestfs.  This section is just an overview.
153
154 The top-level element is E<lt>operatingsystemsE<gt>, and it contains
155 one or more E<lt>operatingsystemE<gt> elements.  You would only see
156 more than one E<lt>operatingsystemE<gt> element if the virtual machine
157 is multi-boot, which is vanishingly rare in real world VMs.
158
159 =head2 E<lt>operatingsystemE<gt>
160
161 In the E<lt>operatingsystemE<gt> tag are various optional fields that
162 describe the operating system, its architecture, the descriptive
163 "product name" string, the type of OS and so on, as in this example:
164
165  <operatingsystems>
166    <operatingsystem>
167      <root>/dev/sda2</root>
168      <name>windows</name>
169      <arch>i386</arch>
170      <distro>windows</distro>
171      <product_name>Windows 7 Enterprise</product_name>
172      <major_version>6</major_version>
173      <minor_version>1</minor_version>
174      <windows_systemroot>/Windows</windows_systemroot>
175
176 These fields are derived from the libguestfs inspection API, and
177 you can find more details in L<guestfs(3)/INSPECTION>.
178
179 The E<lt>rootE<gt> element is the root filesystem device, but from the
180 point of view of libguestfs (block devices may have completely
181 different names inside the VM itself).
182
183 =cut
184
185 # Start the XML output.
186 my $xml = new XML::Writer (DATA_MODE => 1, DATA_INDENT => 2);
187
188 $xml->startTag ("operatingsystems");
189
190 my $root;
191 foreach $root (@roots) {
192     # Note that output_applications requires the filesystems
193     # to be mounted up.
194     my %fses = $g->inspect_get_mountpoints ($root);
195     my @fses = sort { length $a <=> length $b } keys %fses;
196     foreach (@fses) {
197         $g->mount_ro ($fses{$_}, $_);
198     }
199
200     $xml->startTag ("operatingsystem");
201
202     # Basic OS fields.
203     $xml->dataElement (root => canonicalize ($root));
204
205     my $s;
206     $s = $g->inspect_get_type ($root);
207     $xml->dataElement (name => $s) if $s ne "unknown";
208     $s = $g->inspect_get_arch ($root);
209     $xml->dataElement (arch => $s) if $s ne "unknown";
210     $s = $g->inspect_get_distro ($root);
211     $xml->dataElement (distro => $s) if $s ne "unknown";
212     $s = $g->inspect_get_product_name ($root);
213     $xml->dataElement (product_name => $s) if $s ne "unknown";
214     $s = $g->inspect_get_major_version ($root);
215     $xml->dataElement (major_version => $s);
216     $s = $g->inspect_get_minor_version ($root);
217     $xml->dataElement (minor_version => $s);
218     $s = $g->inspect_get_package_format ($root);
219     $xml->dataElement (package_format => $s) if $s ne "unknown";
220     $s = $g->inspect_get_package_management ($root);
221     $xml->dataElement (package_management => $s) if $s ne "unknown";
222
223     eval {
224         $s = $g->inspect_get_windows_systemroot ($root);
225         $xml->dataElement (windows_systemroot => $s);
226     };
227
228     # Mountpoints.
229     output_mountpoints ($root, \@fses, \%fses);
230
231     # Filesystems.
232     output_filesystems ($root);
233
234     # Package format / management and applications.
235     output_applications ($root);
236
237     $xml->endTag("operatingsystem");
238
239     $g->umount_all ();
240 }
241
242 # End the XML output.
243 $xml->endTag ("operatingsystems");
244 $xml->end ();
245
246 =head2 E<lt>mountpointsE<gt>
247
248 Un*x-like guests typically have multiple filesystems which are mounted
249 at various mountpoints, and these are described in the
250 E<lt>mountpointsE<gt> element which looks like this:
251
252  <operatingsystems>
253    <operatingsystem>
254      ...
255      <mountpoints>
256        <mountpoint dev="/dev/vg_f13x64/lv_root">/</mountpoint>
257        <mountpoint dev="/dev/sda1">/boot</mountpoint>
258      </mountpoints>
259
260 As with E<lt>rootE<gt>, devices are from the point of view of
261 libguestfs, and may have completely different names inside the guest.
262 Only mountable filesystems appear in this list, not things like swap
263 devices.
264
265 =cut
266
267 sub output_mountpoints
268 {
269     local $_;
270     my $root = shift;
271     my $fskeys = shift;
272     my $fshash = shift;
273
274     $xml->startTag ("mountpoints");
275     foreach (@$fskeys) {
276         $xml->dataElement ("mountpoint", $_,
277                            dev => canonicalize ($fshash->{$_}));
278     }
279     $xml->endTag ("mountpoints");
280 }
281
282 =head2 E<lt>filesystemsE<gt>
283
284 E<lt>filesystemsE<gt> is like E<lt>mountpointsE<gt> but covers I<all>
285 filesystems belonging to the guest, including swap and empty
286 partitions.  (In the rare case of a multi-boot guest, it covers
287 filesystems belonging to this OS or shared by this OS and other OSes).
288
289 You might see something like this:
290
291  <operatingsystems>
292    <operatingsystem>
293      ...
294      <filesystems>
295        <filesystem dev="/dev/vg_f13x64/lv_root">
296          <type>ext4</type>
297          <label>Fedora-13-x86_64</label>
298          <uuid>e6a4db1e-15c2-477b-ac2a-699181c396aa</uuid>
299        </filesystem>
300
301 The optional elements within E<lt>filesystemE<gt> are the filesystem
302 type, the label, and the UUID.
303
304 =cut
305
306 sub output_filesystems
307 {
308     local $_;
309     my $root = shift;
310
311     $xml->startTag ("filesystems");
312
313     my @fses = $g->inspect_get_filesystems ($root);
314     @fses = sort @fses;
315     foreach (@fses) {
316         $xml->startTag ("filesystem",
317                         dev => canonicalize ($_));
318
319         eval {
320             my $type = $g->vfs_type ($_);
321             $xml->dataElement (type => $type)
322                 if defined $type && $type ne "";
323         };
324
325         eval {
326             my $label = $g->vfs_label ($_);
327             $xml->dataElement (label => $label)
328                 if defined $label && $label ne "";
329         };
330
331         eval {
332             my $uuid = $g->vfs_uuid ($_);
333             $xml->dataElement (uuid => $uuid)
334                 if defined $uuid && $uuid ne "";
335         };
336
337         $xml->endTag ("filesystem");
338     }
339
340     $xml->endTag ("filesystems");
341 }
342
343 =head2 E<lt>applicationsE<gt>
344
345 The related elements E<lt>package_formatE<gt>,
346 E<lt>package_managementE<gt> and E<lt>applicationsE<gt> describe
347 applications installed in the virtual machine.  At the moment we are
348 only able to list RPMs and Debian packages installed, but in future we
349 will support other Linux distros and Windows.
350
351 E<lt>package_formatE<gt>, if present, describes the packaging
352 system used.  Typical values would be C<rpm> and C<deb>.
353
354 E<lt>package_managementE<gt>, if present, describes the package
355 manager.  Typical values include C<yum>, C<up2date> and C<apt>
356
357 E<lt>applicationsE<gt> lists the packages or applications
358 installed.
359
360  <operatingsystems>
361    <operatingsystem>
362      ...
363      <applications>
364        <application>
365          <name>coreutils</name>
366          <version>8.5</version>
367          <release>1</release>
368        </application>
369
370 The version and release fields may not be available for some types
371 guests.  Other fields are possible, see
372 L<guestfs(3)/guestfs_inspect_list_applications>.
373
374 =cut
375
376 sub output_applications
377 {
378     local $_;
379     my $root = shift;
380
381     my @apps = $g->inspect_list_applications ($root);
382
383     if (@apps) {
384         $xml->startTag ("applications");
385         foreach (@apps) {
386             $xml->startTag ("application");
387             $xml->dataElement (name => $_->{app_name});
388             $xml->dataElement (display_name => $_->{app_display_name})
389                 if $_->{app_display_name} ne "";
390             $xml->dataElement (epoch => $_->{app_epoch})
391                 if $_->{app_epoch} != 0;
392             $xml->dataElement (version => $_->{app_version})
393                 if $_->{app_version} ne "";
394             $xml->dataElement (release => $_->{app_release})
395                 if $_->{app_release} ne "";
396             $xml->dataElement (install_path => $_->{app_install_path})
397                 if $_->{app_install_path} ne "";
398             $xml->dataElement (publisher => $_->{app_publisher})
399                 if $_->{app_publisher} ne "";
400             $xml->dataElement (url => $_->{app_url})
401                 if $_->{app_url} ne "";
402             $xml->dataElement (source_package => $_->{app_source_package})
403                 if $_->{app_source_package} ne "";
404             $xml->dataElement (summary => $_->{app_summary})
405                 if $_->{app_summary} ne "";
406             $xml->dataElement (description => $_->{app_description})
407                 if $_->{app_description} ne "";
408             $xml->endTag ("application");
409         }
410         $xml->endTag ("applications");
411     }
412 }
413
414 # The reverse of device name translation, see
415 # BLOCK DEVICE NAMING in guestfs(3).
416 sub canonicalize
417 {
418     local $_ = shift;
419
420     if (m{^/dev/[hv]d([a-z]\d)$}) {
421         return "/dev/sd$1";
422     }
423     $_;
424 }
425
426 =head1 USING XPATH
427
428 You can use the XPath query language, and/or the xpath tool, in order
429 to select parts of the XML.
430
431 For example:
432
433  $ virt-inspector Guest | xpath //filesystems
434  Found 1 nodes:
435  -- NODE --
436  <filesystems>
437       <filesystem dev="/dev/vg_f13x64/lv_root">
438         <type>ext4</type>
439  [etc]
440
441  $ virt-inspector Guest | \
442      xpath "string(//filesystem[@dev='/dev/sda1']/type)"
443  Query didn't return a nodeset. Value: ext4
444
445 =head1 SHELL QUOTING
446
447 Libvirt guest names can contain arbitrary characters, some of which
448 have meaning to the shell such as C<#> and space.  You may need to
449 quote or escape these characters on the command line.  See the shell
450 manual page L<sh(1)> for details.
451
452 =head1 SEE ALSO
453
454 L<guestfs(3)>,
455 L<guestfish(1)>,
456 L<Sys::Guestfs(3)>,
457 L<Sys::Guestfs::Lib(3)>,
458 L<Sys::Virt(3)>,
459 L<http://www.w3.org/TR/xpath/>,
460 L<http://libguestfs.org/>.
461
462 =head1 AUTHORS
463
464 =over 4
465
466 =item *
467
468 Richard W.M. Jones L<http://people.redhat.com/~rjones/>
469
470 =item *
471
472 Matthew Booth L<mbooth@redhat.com>
473
474 =back
475
476 =head1 COPYRIGHT
477
478 Copyright (C) 2010 Red Hat Inc.
479
480 This program is free software; you can redistribute it and/or modify
481 it under the terms of the GNU General Public License as published by
482 the Free Software Foundation; either version 2 of the License, or
483 (at your option) any later version.
484
485 This program is distributed in the hope that it will be useful,
486 but WITHOUT ANY WARRANTY; without even the implied warranty of
487 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
488 GNU General Public License for more details.
489
490 You should have received a copy of the GNU General Public License
491 along with this program; if not, write to the Free Software
492 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.