#!/usr/bin/perl -w # virt-inspector # Copyright (C) 2010 Red Hat Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. use warnings; use strict; use Sys::Guestfs; use Sys::Guestfs::Lib qw(open_guest); use Pod::Usage; use Getopt::Long; use File::Temp qw/tempfile/; use File::Basename; use XML::Writer; use Locale::TextDomain 'libguestfs'; =encoding utf8 =head1 NAME virt-inspector - Display operating system version and other information about a virtual machine =head1 SYNOPSIS virt-inspector [--connect URI] domname virt-inspector guest.img [guest.img ...] =head1 DESCRIPTION B examines a virtual machine or disk image and tries to determine the version of the operating system and other information about the virtual machine. Virt-inspector produces XML output for feeding into other programs. In the normal usage, use C where C is the libvirt domain (see: C). You can also run virt-inspector directly on disk images from a single virtual machine. Use C. In rare cases a domain has several block devices, in which case you should list them one after another, with the first corresponding to the guest's C, the second to the guest's C and so on. Virt-inspector can only inspect and report upon I. To inspect several virtual machines, you have to run virt-inspector several times (for example, from a shell script for-loop). Because virt-inspector needs direct access to guest images, it won't normally work over remote libvirt connections. =head1 OPTIONS =over 4 =cut my $help; =item B<--help> Display brief help. =cut my $version; =item B<--version> Display version number and exit. =cut my $uri; =item B<--connect URI> | B<-c URI> If using libvirt, connect to the given I. If omitted, then we connect to the default libvirt hypervisor. Libvirt is only used if you specify a C on the command line. If you specify guest block devices directly, then libvirt is not used at all. =cut my $format; =item B<--format> raw Specify the format of disk images given on the command line. If this is omitted then the format is autodetected from the content of the disk image. If disk images are requested from libvirt, then this program asks libvirt for this information. In this case, the value of the format parameter is ignored. If working with untrusted raw-format guest disk images, you should ensure the format is always specified. =back =cut GetOptions ("help|?" => \$help, "version" => \$version, "connect|c=s" => \$uri, "format=s" => \$format, ) or pod2usage (2); pod2usage (1) if $help; if ($version) { my $g = Sys::Guestfs->new (); my %h = $g->version (); print "$h{major}.$h{minor}.$h{release}$h{extra}\n"; exit } pod2usage (__"virt-inspector: no image or VM names given") if @ARGV == 0; my @args = (\@ARGV); push @args, address => $uri if defined $uri; push @args, format => $format if defined $format; my $g = open_guest (@args); $g->launch (); my @roots = $g->inspect_os (); if (@roots == 0) { 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", prog => basename ($0)); } =head1 XML FORMAT The virt-inspector XML is described precisely in a RELAX NG schema which is supplied with libguestfs. This section is just an overview. The top-level element is EoperatingsystemsE, and it contains one or more EoperatingsystemE elements. You would only see more than one EoperatingsystemE element if the virtual machine is multi-boot, which is vanishingly rare in real world VMs. =head2 EoperatingsystemE In the EoperatingsystemE tag are various optional fields that describe the operating system, its architecture, the descriptive "product name" string, the type of OS and so on, as in this example: /dev/sda2 windows i386 windows Windows 7 Enterprise 6 1 /Windows These fields are derived from the libguestfs inspection API, and you can find more details in L. The ErootE element is the root filesystem device, but from the point of view of libguestfs (block devices may have completely different names inside the VM itself). =cut # Start the XML output. my $xml = new XML::Writer (DATA_MODE => 1, DATA_INDENT => 2); $xml->startTag ("operatingsystems"); my $root; foreach $root (@roots) { # Note that output_applications requires the filesystems # to be mounted up. my %fses = $g->inspect_get_mountpoints ($root); my @fses = sort { length $a <=> length $b } keys %fses; foreach (@fses) { $g->mount_ro ($fses{$_}, $_); } $xml->startTag ("operatingsystem"); # Basic OS fields. $xml->dataElement (root => canonicalize ($root)); my $s; $s = $g->inspect_get_type ($root); $xml->dataElement (name => $s) if $s ne "unknown"; $s = $g->inspect_get_arch ($root); $xml->dataElement (arch => $s) if $s ne "unknown"; $s = $g->inspect_get_distro ($root); $xml->dataElement (distro => $s) if $s ne "unknown"; $s = $g->inspect_get_product_name ($root); $xml->dataElement (product_name => $s) if $s ne "unknown"; $s = $g->inspect_get_major_version ($root); $xml->dataElement (major_version => $s); $s = $g->inspect_get_minor_version ($root); $xml->dataElement (minor_version => $s); $s = $g->inspect_get_package_format ($root); $xml->dataElement (package_format => $s) if $s ne "unknown"; $s = $g->inspect_get_package_management ($root); $xml->dataElement (package_management => $s) if $s ne "unknown"; eval { $s = $g->inspect_get_windows_systemroot ($root); $xml->dataElement (windows_systemroot => $s); }; # Mountpoints. output_mountpoints ($root, \@fses, \%fses); # Filesystems. output_filesystems ($root); # Package format / management and applications. output_applications ($root); $xml->endTag("operatingsystem"); $g->umount_all (); } # End the XML output. $xml->endTag ("operatingsystems"); $xml->end (); =head2 EmountpointsE Un*x-like guests typically have multiple filesystems which are mounted at various mountpoints, and these are described in the EmountpointsE element which looks like this: ... / /boot As with ErootE, devices are from the point of view of libguestfs, and may have completely different names inside the guest. Only mountable filesystems appear in this list, not things like swap devices. =cut sub output_mountpoints { local $_; my $root = shift; my $fskeys = shift; my $fshash = shift; $xml->startTag ("mountpoints"); foreach (@$fskeys) { $xml->dataElement ("mountpoint", $_, dev => canonicalize ($fshash->{$_})); } $xml->endTag ("mountpoints"); } =head2 EfilesystemsE EfilesystemsE is like EmountpointsE but covers I filesystems belonging to the guest, including swap and empty partitions. (In the rare case of a multi-boot guest, it covers filesystems belonging to this OS or shared by this OS and other OSes). You might see something like this: ... ext4 e6a4db1e-15c2-477b-ac2a-699181c396aa The optional elements within EfilesystemE are the filesystem type, the label, and the UUID. =cut sub output_filesystems { local $_; my $root = shift; $xml->startTag ("filesystems"); my @fses = $g->inspect_get_filesystems ($root); @fses = sort @fses; foreach (@fses) { $xml->startTag ("filesystem", dev => canonicalize ($_)); eval { my $type = $g->vfs_type ($_); $xml->dataElement (type => $type) if defined $type && $type ne ""; }; eval { my $label = $g->vfs_label ($_); $xml->dataElement (label => $label) if defined $label && $label ne ""; }; eval { my $uuid = $g->vfs_uuid ($_); $xml->dataElement (uuid => $uuid) if defined $uuid && $uuid ne ""; }; $xml->endTag ("filesystem"); } $xml->endTag ("filesystems"); } =head2 EapplicationsE The related elements Epackage_formatE, Epackage_managementE and EapplicationsE describe applications installed in the virtual machine. At the moment we are only able to list RPMs and Debian packages installed, but in future we will support other Linux distros and Windows. Epackage_formatE, if present, describes the packaging system used. Typical values would be C and C. Epackage_managementE, if present, describes the package manager. Typical values include C, C and C EapplicationsE lists the packages or applications installed. ... coreutils 8.5 1 The version and release fields may not be available for some types guests. Other fields are possible, see L. =cut sub output_applications { local $_; my $root = shift; my @apps = $g->inspect_list_applications ($root); if (@apps) { $xml->startTag ("applications"); foreach (@apps) { $xml->startTag ("application"); $xml->dataElement (name => $_->{app_name}); $xml->dataElement (display_name => $_->{app_display_name}) if $_->{app_display_name} ne ""; $xml->dataElement (epoch => $_->{app_epoch}) if $_->{app_epoch} != 0; $xml->dataElement (version => $_->{app_version}) if $_->{app_version} ne ""; $xml->dataElement (release => $_->{app_release}) if $_->{app_release} ne ""; $xml->dataElement (install_path => $_->{app_install_path}) if $_->{app_install_path} ne ""; $xml->dataElement (publisher => $_->{app_publisher}) if $_->{app_publisher} ne ""; $xml->dataElement (url => $_->{app_url}) if $_->{app_url} ne ""; $xml->dataElement (source_package => $_->{app_source_package}) if $_->{app_source_package} ne ""; $xml->dataElement (summary => $_->{app_summary}) if $_->{app_summary} ne ""; $xml->dataElement (description => $_->{app_description}) if $_->{app_description} ne ""; $xml->endTag ("application"); } $xml->endTag ("applications"); } } # The reverse of device name translation, see # BLOCK DEVICE NAMING in guestfs(3). sub canonicalize { local $_ = shift; if (m{^/dev/[hv]d([a-z]\d)$}) { return "/dev/sd$1"; } $_; } =head1 USING XPATH You can use the XPath query language, and/or the xpath tool, in order to select parts of the XML. For example: $ virt-inspector Guest | xpath //filesystems Found 1 nodes: -- NODE -- ext4 [etc] $ virt-inspector Guest | \ xpath "string(//filesystem[@dev='/dev/sda1']/type)" Query didn't return a nodeset. Value: ext4 =head1 SHELL QUOTING Libvirt guest names can contain arbitrary characters, some of which have meaning to the shell such as C<#> and space. You may need to quote or escape these characters on the command line. See the shell manual page L for details. =head1 SEE ALSO L, L, L, L, L, L, L. =head1 AUTHORS =over 4 =item * Richard W.M. Jones L =item * Matthew Booth L =back =head1 COPYRIGHT Copyright (C) 2010 Red Hat Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.