3 # Copyright (C) 2009 Red Hat Inc.
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.
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.
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.
28 eval "use Sys::Virt;";
34 virt-inspector - Display OS version, kernel, drivers, mount points, applications, etc. in a virtual machine
38 virt-inspector [--connect URI] domname
40 virt-inspector guest.img [guest.img ...]
44 B<virt-inspector> examines a virtual machine and tries to determine
45 the version of the OS, the kernel version, what drivers are installed,
46 whether the virtual machine is fully virtualized (FV) or
47 para-virtualized (PV), what applications are installed and more.
49 Virt-inspector can produce output in several formats, including a
50 readable text report, and XML for feeding into other programs.
52 Virt-inspector should only be run on I<inactive> virtual machines.
53 The program tries to determine that the machine is inactive and will
54 refuse to run if it thinks you are trying to inspect a running domain.
56 In the normal usage, use C<virt-inspector domname> where C<domname> is
57 the libvirt domain (see: C<virsh list --all>).
59 You can also run virt-inspector directly on disk images from a single
60 virtual machine. Use C<virt-inspector guest.img>. In rare cases a
61 domain has several block devices, in which case you should list them
62 one after another, with the first corresponding to the guest's
63 C</dev/sda>, the second to the guest's C</dev/sdb> and so on.
65 Virt-inspector can only inspect and report upon I<one domain at a
66 time>. To inspect several virtual machines, you have to run
67 virt-inspector several times (for example, from a shell script
70 Because virt-inspector needs direct access to guest images, it won't
71 normally work over remote libvirt connections.
89 =item B<--connect URI> | B<-c URI>
91 If using libvirt, connect to the given I<URI>. If omitted,
92 then we connect to the default libvirt hypervisor.
94 Libvirt is only used if you specify a C<domname> on the
95 command line. If you specify guest block devices directly,
96 then libvirt is not used at all.
104 Force reading a particular guest even if it appears to
105 be active, or if the guest image is writable. This is
106 dangerous and can even corrupt the guest image.
112 =item B<--text> (default)
120 Select the output format. The default is a readable text report.
122 If you select I<--xml> then you get XML output which can be fed
125 If you select I<--fish> then we print a L<guestfish(1)> command
126 line which will automatically mount up the filesystems on the
127 correct mount points. Try this for example:
129 eval `virt-inspector --fish guest.img`
131 I<--ro-fish> is the same, but the I<--ro> option is passed to
132 guestfish so that the filesystems are mounted read-only.
138 GetOptions ("help|?" => \$help,
139 "connect|c=s" => \$uri,
141 "xml" => sub { $output = "xml" },
142 "fish" => sub { $output = "fish" },
143 "guestfish" => sub { $output = "fish" },
144 "ro-fish" => sub { $output = "ro-fish" },
145 "ro-guestfish" => sub { $output = "ro-fish" })
147 pod2usage (1) if $help;
148 pod2usage ("$0: no image or VM names given") if @ARGV == 0;
150 # Domain name or guest image(s)?
156 # Until we get an 'add_drive_ro' call, we must check that qemu
157 # will only open this image in readonly mode.
158 # XXX Remove this hack at some point ... or at least push it
163 die "guest image $_ does not exist or is not readable\n"
164 } elsif (-w $_ && !$force) {
165 die ("guest image $_ is writable! REFUSING TO PROCEED.\n".
166 "You can use --force to override this BUT that action\n".
167 "MAY CORRUPT THE DISK IMAGE.\n");
171 die "no libvirt support (install Sys::Virt)"
172 unless exists $INC{"Sys/Virt.pm"};
174 pod2usage ("$0: too many domains listed on command line") if @ARGV > 1;
178 $vmm = Sys::Virt->new (uri => $uri, readonly => 1);
180 $vmm = Sys::Virt->new (readonly => 1);
182 die "cannot connect to libvirt $uri\n" unless $vmm;
184 my @doms = $vmm->list_defined_domains ();
187 if ($_->get_name () eq $ARGV[0]) {
192 die "$ARGV[0] is not the name of an inactive libvirt domain\n"
195 # Get the names of the image(s).
196 my $xml = $dom->get_xml_description ();
198 my $p = new XML::XPath::XMLParser (xml => $xml);
199 my $disks = $p->find ("//devices/disk");
201 foreach ($disks->get_nodelist) {
202 print XML::XPath::XMLParser::as_string($_);
208 # We've now got the list of @images, so feed them to libguestfs.
209 my $g = Sys::Guestfs->new ();
210 $g->add_drive ($_) foreach @images;
214 # We want to get the list of LVs and partitions (ie. anything that
215 # could contain a filesystem). Discard any partitions which are PVs.
216 my @partitions = $g->list_partitions ();
217 my @pvs = $g->pvs ();
221 return 1 if $_ eq $t;
225 @partitions = grep { ! is_pv ($_) } @partitions;
227 my @lvs = $g->lvs ();
233 Linux (distro + version)
237 +--- Filesystems ---------- Installed apps --- Kernel & drivers
238 ----------- -------------- ----------------
239 mount point => device List of apps Extra information
240 mount point => device and versions about kernel(s)
243 (plus lots of extra information
244 about each filesystem)
246 The output of virt-inspector is a complex two-level data structure.
248 At the top level is a list of the operating systems installed on the
249 guest. (For the vast majority of guests, only a single OS is
250 installed.) The data returned for the OS includes the name (Linux,
251 Windows), the distribution and version.
253 The diagram above shows what we return for each OS.
255 With the I<--xml> option the output is mapped into an XML document.
256 Unfortunately there is no clear schema for this document
257 (contributions welcome) but you can get an idea of the format by
258 looking at other documents and as a last resort the source for this
261 With the I<--fish> or I<--ro-fish> option the mount points are mapped to
262 L<guestfish(1)> command line parameters, so that you can go in
263 afterwards and inspect the guest with everything mounted in the
264 right place. For example:
266 eval `virt-inspector --ro-fish guest.img`
267 ==> guestfish --ro -a guest.img -m /dev/VG/LV:/ -m /dev/sda1:/boot
271 # List of possible filesystems.
272 my @devices = sort (@lvs, @partitions);
274 # Now query each one to build up a picture of what's in it.
275 my %fses = map { $_ => check_fs ($_) } @devices;
277 # Now the complex checking code itself.
278 # check_fs takes a device name (LV or partition name) and returns
279 # a hashref containing everything we can find out about the device.
282 my $dev = shift; # LV or partition name.
284 my %r; # Result hash.
286 # First try 'file(1)' on it.
287 my $file = $g->file ($dev);
288 if ($file =~ /ext2 filesystem data/) {
291 } elsif ($file =~ /ext3 filesystem data/) {
294 } elsif ($file =~ /ext4 filesystem data/) {
297 } elsif ($file =~ m{Linux/i386 swap file}) {
303 # If it's ext2/3/4, then we want the UUID and label.
304 if (exists $r{fstype} && $r{fstype} =~ /^ext/) {
305 $r{uuid} = $g->get_e2uuid ($dev);
306 $r{label} = $g->get_e2label ($dev);
309 # Try mounting it, fnarrr.
311 $r{is_mountable} = 1;
312 eval { $g->mount_ro ($dev, "/") };
314 # It's not mountable, probably empty or some format
315 # we don't understand.
316 $r{is_mountable} = 0;
321 if ($g->is_file ("/grub/menu.lst") ||
322 $g->is_file ("/grub/grub.conf")) {
323 $r{content} = "linux-grub";
329 if ($g->is_dir ("/etc") && $g->is_dir ("/bin") &&
330 $g->is_file ("/etc/fstab")) {
331 $r{content} = "linux-root";
333 check_linux_root (\%r);
338 if ($g->is_dir ("/etc") && $g->is_dir ("/bin") &&
339 $g->is_dir ("/share") && !$g->exists ("/local") &&
340 !$g->is_file ("/etc/fstab")) {
341 $r{content} = "linux-usrlocal";
346 if ($g->is_dir ("/etc") && $g->is_dir ("/bin") &&
347 $g->is_dir ("/share") && $g->exists ("/local") &&
348 !$g->is_file ("/etc/fstab")) {
349 $r{content} = "linux-usr";
354 if ($g->is_file ("/AUTOEXEC.BAT") ||
355 $g->is_file ("/autoexec.bat") ||
356 $g->is_dir ("/Program Files") ||
357 $g->is_dir ("/WINDOWS") ||
358 $g->is_file ("/ntldr")) {
359 $r{fstype} = "ntfs"; # XXX this is a guess
360 $r{fsos} = "windows";
361 $r{content} = "windows-root";
363 check_windows_root (\%r);
378 # Look into /etc to see if we recognise the operating system.
379 if ($g->is_file ("/etc/redhat-release")) {
380 $_ = $g->cat ("/etc/redhat-release");
381 if (/Fedora release (\d+\.\d+)/) {
382 $r->{osdistro} = "fedora";
383 $r->{osversion} = "$1"
384 } elsif (/(Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\d+).*Update (\d+)/) {
385 $r->{osdistro} = "redhat";
386 $r->{osversion} = "$2.$3";
387 } elsif (/(Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\d+(?:\.(\d+))?)/) {
388 $r->{osdistro} = "redhat";
389 $r->{osversion} = "$2";
391 $r->{osdistro} = "redhat";
393 } elsif ($g->is_file ("/etc/debian_version")) {
394 $_ = $g->cat ("/etc/debian_version");
396 $r->{osdistro} = "debian";
397 $r->{osversion} = "$1";
399 $r->{osdistro} = "debian";
403 # Parse the contents of /etc/fstab. This is pretty vital so
404 # we can determine where filesystems are supposed to be mounted.
405 eval "\$_ = \$g->cat ('/etc/fstab');";
407 my @lines = split /\n/;
410 my @fields = split /[ \t]+/;
412 my $spec = $fields[0]; # first column (dev/label/uuid)
413 my $file = $fields[1]; # second column (mountpoint)
414 if ($spec =~ m{^/} ||
415 $spec =~ m{^LABEL=} ||
416 $spec =~ m{^UUID=} ||
418 push @fstab, [$spec, $file]
422 $r->{fstab} = \@fstab if @fstab;
426 sub check_windows_root
431 # XXX Windows version.
432 # List of applications.
440 # XXX Kernel versions, grub version.
443 #print Dumper (\%fses);
445 # Now find out how many operating systems we've got. Usually just one.
449 foreach (sort keys %fses) {
450 if ($fses{$_}->{is_root}) {
455 get_os_version (\%r);
456 assign_mount_points (\%r);
466 $r->{os} = $r->{root}->{fsos} if exists $r->{root}->{fsos};
467 $r->{distro} = $r->{root}->{osdistro} if exists $r->{root}->{osdistro};
468 $r->{version} = $r->{root}->{osversion} if exists $r->{root}->{osversion};
471 sub assign_mount_points
476 $r->{mounts} = { "/" => $r->{root_device} };
477 $r->{filesystems} = { $r->{root_device} => $r->{root} };
479 # Use /etc/fstab if we have it to mount the rest.
480 if (exists $r->{root}->{fstab}) {
481 my @fstab = @{$r->{root}->{fstab}};
483 my ($spec, $file) = @$_;
485 my ($dev, $fs) = find_filesystem ($spec);
487 $r->{mounts}->{$file} = $dev;
488 $r->{filesystems}->{$dev} = $fs;
489 if (exists $fs->{used}) {
499 # Find filesystem by device name, LABEL=.. or UUID=..
506 foreach (sort keys %fses) {
507 if (exists $fses{$_}->{label} &&
508 $fses{$_}->{label} eq $label) {
509 return ($_, $fses{$_});
512 warn "unknown filesystem label $label\n";
514 } elsif (/^UUID=(.*)/) {
516 foreach (sort keys %fses) {
517 if (exists $fses{$_}->{uuid} &&
518 $fses{$_}->{uuid} eq $uuid) {
519 return ($_, $fses{$_});
522 warn "unknown filesystem UUID $uuid\n";
525 return ($_, $fses{$_}) if exists $fses{$_};
526 warn "unknown filesystem $_\n";
531 print Dumper (\%oses);
548 Richard W.M. Jones L<http://et.redhat.com/~rjones/>
552 Copyright (C) 2009 Red Hat Inc.
554 This program is free software; you can redistribute it and/or modify
555 it under the terms of the GNU General Public License as published by
556 the Free Software Foundation; either version 2 of the License, or
557 (at your option) any later version.
559 This program is distributed in the hope that it will be useful,
560 but WITHOUT ANY WARRANTY; without even the implied warranty of
561 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
562 GNU General Public License for more details.
564 You should have received a copy of the GNU General Public License
565 along with this program; if not, write to the Free Software
566 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.