2 # Copyright (C) 2009-2010 Red Hat Inc.
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2 of the License, or (at your option) any later version.
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # Lesser General Public License for more details.
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 package Sys::Guestfs::Lib;
23 # The minor part of this version number is incremented when some
24 # change is made to this module. The major part is incremented if we
25 # make a change which is not backwards compatible. It is not related
26 # to the libguestfs version number.
27 use vars qw($VERSION);
33 use File::Temp qw/tempdir/;
34 use Locale::TextDomain 'libguestfs';
37 eval "use Sys::Virt;";
38 eval "use XML::XPath;";
39 eval "use XML::XPath::XMLParser;";
40 eval "use Win::Hivex;";
46 Sys::Guestfs::Lib - Useful functions for using libguestfs from Perl
50 use Sys::Guestfs::Lib qw(open_guest inspect_all_partitions ...);
52 $g = open_guest ($name);
54 %fses = inspect_all_partitions ($g, \@partitions);
56 (and many more calls - see the rest of this manpage)
60 C<Sys::Guestfs::Lib> is an extra library of useful functions for using
61 the libguestfs API from Perl. It also provides tighter integration
64 The basic libguestfs API is not covered by this manpage. Please refer
65 instead to L<Sys::Guestfs(3)> and L<guestfs(3)>. The libvirt API is
66 also not covered. For that, see L<Sys::Virt(3)>.
68 =head1 BASIC FUNCTIONS
74 use vars qw(@EXPORT_OK @ISA);
77 @EXPORT_OK = qw(open_guest feature_available
78 get_partitions resolve_windows_path
79 inspect_all_partitions inspect_partition
80 inspect_operating_systems mount_operating_system inspect_in_detail
81 inspect_linux_kernel);
85 $g = open_guest ($name);
87 $g = open_guest ($name, rw => 1, ...);
89 $g = open_guest ($name, address => $uri, ...);
91 $g = open_guest ([$img1, $img2, ...], address => $uri, ...);
93 ($g, $conn, $dom, @images) = open_guest ($name);
95 This function opens a libguestfs handle for either the libvirt domain
96 called C<$name>, or the disk image called C<$name>. Any disk images
97 found through libvirt or specified explicitly are attached to the
100 The C<Sys::Guestfs> handle C<$g> is returned, or if there was an error
101 it throws an exception. To catch errors, wrap the call in an eval
104 The first parameter is either a string referring to a libvirt domain
105 or a disk image, or (if a guest has several disk images) an arrayref
106 C<[$img1, $img2, ...]>.
108 The handle is I<read-only> by default. Use the optional parameter
109 C<rw =E<gt> 1> to open a read-write handle. However if you open a
110 read-write handle, this function will refuse to use active libvirt
113 The handle is still in the config state when it is returned, so you
114 have to call C<$g-E<gt>launch ()>.
116 The optional C<address> parameter can be added to specify the libvirt
119 The implicit libvirt handle is closed after this function, I<unless>
120 you call the function in C<wantarray> context, in which case the
121 function returns a tuple of: the open libguestfs handle, the open
122 libvirt handle, and the open libvirt domain handle, and a list of
123 images. (This is useful if you want to do other things like pulling
124 the XML description of the guest). Note that if this is a straight
125 disk image, then C<$conn> and C<$dom> will be C<undef>.
127 If the C<Sys::Virt> module is not available, then libvirt is bypassed,
128 and this function can only open disk images.
130 The optional C<interface> parameter can be used to open devices with
131 C<add_drive{,_ro}_with_if>. See
132 L<Sys::Guestfs/guestfs_add_drive_with_if> for more details.
142 my $rw = $params{rw};
143 my $address = $params{address};
144 my $interface = $params{interface};
147 if (ref ($first) eq "ARRAY") {
149 } elsif (ref ($first) eq "SCALAR") {
152 croak __"open_guest: first parameter must be a string or an arrayref"
155 # Check each element of @images is defined.
156 # (See https://bugzilla.redhat.com/show_bug.cgi?id=601092#c3).
158 croak __"open_guest: first argument contains undefined element"
166 croak __x("guest image {imagename} does not exist or is not readable",
171 die __"open_guest: no libvirt support (install Sys::Virt, XML::XPath and XML::XPath::XMLParser)"
172 unless exists $INC{"Sys/Virt.pm"} &&
173 exists $INC{"XML/XPath.pm"} &&
174 exists $INC{"XML/XPath/XMLParser.pm"};
176 die __"open_guest: too many domains listed on command line"
179 my @libvirt_args = ();
180 push @libvirt_args, address => $address if defined $address;
182 $conn = Sys::Virt->new (readonly => 1, @libvirt_args);
183 die __"open_guest: cannot connect to libvirt" unless $conn;
185 my @doms = $conn->list_defined_domains ();
186 my $isitinactive = 1;
188 # In the case where we want read-only access to a domain,
189 # allow the user to specify an active domain too.
190 push @doms, $conn->list_domains ();
194 if ($_->get_name () eq $images[0]) {
202 die __x("{imagename} is not the name of an inactive libvirt domain\n",
203 imagename => $images[0]);
205 die __x("{imagename} is not the name of a libvirt domain\n",
206 imagename => $images[0]);
210 # Get the names of the image(s).
211 my $xml = $dom->get_xml_description ();
213 my $p = XML::XPath->new (xml => $xml);
214 my @disks = $p->findnodes ('//devices/disk/source/@dev');
215 push (@disks, $p->findnodes ('//devices/disk/source/@file'));
217 die __x("{imagename} seems to have no disk devices\n",
218 imagename => $images[0])
221 @images = map { $_->getData } @disks;
224 # We've now got the list of @images, so feed them to libguestfs.
225 my $g = Sys::Guestfs->new ();
229 $g->add_drive_with_if ($_, $interface);
235 $g->add_drive_ro_with_if ($_, $interface);
237 $g->add_drive_ro ($_);
242 return wantarray ? ($g, $conn, $dom, @images) : $g
245 =head2 feature_available
247 $bool = feature_available ($g, $feature [, $feature ...]);
249 This function is a useful wrapper around the basic
250 C<$g-E<gt>available> call.
252 C<$g-E<gt>available> tests for availability of a list of features and
253 dies with an error if any is not available.
255 This call tests for the list of features and returns true if all are
256 available, or false otherwise.
258 For a list of features you can test for, see L<guestfs(3)/AVAILABILITY>.
262 sub feature_available {
265 eval { $g->available (\@_); };
269 =head2 get_partitions
271 @partitions = get_partitions ($g);
273 This function takes an open libguestfs handle C<$g> and returns all
274 partitions and logical volumes found on it.
276 What is returned is everything that could contain a filesystem (or
277 swap). Physical volumes are not normally included from the list
278 except if they contain a filesystem directly. Nor are devices which
279 are partitioned (eg. C</dev/sda> would not be returned if C</dev/sda1>
289 # Look to see if any devices directly contain filesystems (RHBZ#590167).
290 my @devices = $g->list_devices ();
291 my @fses_on_device = ();
293 eval { $g->mount_ro ($_, "/"); };
294 push @fses_on_device, $_ unless $@;
298 my @partitions = $g->list_partitions ();
299 my @pvs = $g->pvs ();
300 @partitions = grep { ! _is_pv ($_, @pvs) } @partitions;
303 @lvs = $g->lvs () if feature_available ($g, "lvm2");
305 return sort (@fses_on_device, @lvs, @partitions);
313 return 1 if $_ eq $t;
318 =head2 resolve_windows_path
320 $path = resolve_windows_path ($g, $path);
322 $path = resolve_windows_path ($g, "/windows/system");
323 ==> "/WINDOWS/System"
324 or undef if no path exists
326 This function, which is specific to FAT/NTFS filesystems (ie. Windows
327 guests), lets you look up a case insensitive C<$path> in the
328 filesystem and returns the true, case sensitive path as required by
329 the underlying kernel or NTFS-3g driver.
331 If C<$path> does not exist then this function returns C<undef>.
333 The C<$path> parameter must begin with C</> character and be separated
334 by C</> characters. Do not use C<\>, drive names, etc.
338 sub resolve_windows_path
344 eval { $r = $g->case_sensitive_path ($path); };
348 =head2 file_architecture
350 Deprecated function. Replace any calls to this function with:
352 $g->file_architecture ($path);
356 sub file_architecture
361 return $g->file_architecture ($path);
364 =head1 OPERATING SYSTEM INSPECTION FUNCTIONS
366 The functions in this section can be used to inspect the operating
367 system(s) available inside a virtual machine image. For example, you
368 can find out if the VM is Linux or Windows, how the partitions are
369 meant to be mounted, and what applications are installed.
371 If you just want a simple command-line interface to this
372 functionality, use the L<virt-inspector(1)> tool. The documentation
373 below covers the case where you want to access this functionality from
376 Once you have the list of partitions (from C<get_partitions>) there
377 are several steps involved:
383 Look at each partition separately and find out what is on it.
385 The information you get back includes whether the partition contains a
386 filesystem or swapspace, what sort of filesystem (eg. ext3, ntfs), and
387 a first pass guess at the content of the filesystem (eg. Linux boot,
390 The result of this step is a C<%fs> hash of information, one hash for
393 See: C<inspect_partition>, C<inspect_all_partitions>
397 Work out the relationship between partitions.
399 In this step we work out how partitions are related to each other. In
400 the case of a single-boot VM, we work out how the partitions are
401 mounted in respect of each other (eg. C</dev/sda1> is mounted as
402 C</boot>). In the case of a multi-boot VM where there are several
403 roots, we may identify several operating system roots, and mountpoints
406 The result of this step is a single hash called C<%oses> which is
407 described in more detail below, but at the top level looks like:
410 '/dev/VG/Root1' => \%os1,
411 '/dev/VG/Root2' => \%os2,
417 '/' => '/dev/VG/Root1',
418 '/boot' => '/dev/sda1',
423 (example shows a multi-boot VM containing two root partitions).
425 See: C<inspect_operating_systems>
431 Previous to this point we've essentially been looking at each
432 partition in isolation. Now we construct a true guest filesystem by
433 mounting up all of the disks. Only once everything is mounted up can
434 we run commands in the OS context to do more detailed inspection.
436 See: C<mount_operating_system>
440 Check for kernels and applications.
442 This step now does more detailed inspection, where we can look for
443 kernels, applications and more installed in the guest.
445 The result of this is an enhanced C<%os> hash.
447 See: C<inspect_in_detail>
453 This library does not contain functions for generating output based on
454 the analysis steps above. Use a command line tool such as
455 L<virt-inspector(1)> to get useful output.
459 =head2 inspect_all_partitions
461 %fses = inspect_all_partitions ($g, \@partitions);
463 This calls C<inspect_partition> for each partition in the list
466 The result is a hash which maps partition name to C<\%fs> hashref.
468 The contents of the C<%fs> hash is explained below.
472 # Turn /dev/vd* and /dev/hd* into canonical device names
473 # (see BLOCK DEVICE NAMING in guestfs(3)).
475 sub _canonical_dev ($)
478 return "/dev/sd$1" if $dev =~ m{^/dev/[vh]d(\w+)};
482 sub inspect_all_partitions
488 return map { _canonical_dev ($_) => inspect_partition ($g, $_) } @parts;
491 =head2 inspect_partition
493 \%fs = inspect_partition ($g, $partition);
495 This function inspects the device named C<$partition> in isolation and
496 tries to determine what it is. It returns information such as whether
497 the partition is formatted, and with what, whether it is mountable,
498 and what it appears to contain (eg. a Windows root, or a Linux /usr).
500 If the Perl module L<Win::Hivex(3)> is installed, then additional
501 information is made available for Windows guests, if we can locate and
502 read their registries.
504 The returned value is a hashref C<\%fs> which may contain the
505 following top-level keys (any key can be missing):
511 Filesystem type, eg. "ext2" or "ntfs"
515 Apparent filesystem OS, eg. "linux" or "windows"
519 If set, the partition is a swap partition.
531 If set, the partition could be mounted by libguestfs.
535 Filesystem content, if we could determine it. One of: "linux-grub",
536 "linux-root", "linux-usrlocal", "linux-usr", "windows-root".
540 (For Linux root partitions only).
541 Operating system distribution. One of: "fedora", "rhel", "centos",
542 "scientific", "debian".
546 (For Linux root partitions only)
547 The package format used by the guest distribution. One of: "rpm", "deb".
549 =item package_management
551 (For Linux root partitions only)
552 The package management tool used by the guest distribution. One of: "rhn",
555 =item os_major_version
557 (For root partitions only).
558 Operating system major version number.
560 =item os_minor_version
562 (For root partitions only).
563 Operating system minor version number.
567 (For Linux root partitions only).
568 The contents of the C</etc/fstab> file.
572 (For Windows root partitions only).
573 The contents of the C</boot.ini> (NTLDR) file.
577 The value is an arrayref, which is a list of Windows registry
578 file contents, in Windows C<.REG> format.
584 sub inspect_partition
588 my $dev = shift; # LV or partition name.
590 my %r; # Result hash.
592 # First try 'file(1)' on it.
593 my $file = $g->file ($dev);
594 if ($file =~ /ext2 filesystem data/) {
597 } elsif ($file =~ /ext3 filesystem data/) {
600 } elsif ($file =~ /ext4 filesystem data/) {
603 } elsif ($file =~ m{Linux/i386 swap file}) {
609 # If it's ext2/3/4, then we want the UUID and label.
610 if (exists $r{fstype} && $r{fstype} =~ /^ext/) {
611 $r{uuid} = $g->get_e2uuid ($dev);
612 $r{label} = $g->get_e2label ($dev);
615 # Try mounting it, fnarrr.
617 $r{is_mountable} = 1;
618 eval { $g->mount_ro ($dev, "/") };
620 # It's not mountable, probably empty or some format
621 # we don't understand.
622 $r{is_mountable} = 0;
627 if ($g->is_file ("/grub/menu.lst") ||
628 $g->is_file ("/grub/grub.conf")) {
629 $r{content} = "linux-grub";
630 _check_grub ($g, \%r);
635 if ($g->is_dir ("/etc") && $g->is_dir ("/bin") &&
636 $g->is_file ("/etc/fstab")) {
637 $r{content} = "linux-root";
639 _check_linux_root ($g, \%r);
644 if ($g->is_dir ("/etc") && $g->is_dir ("/bin") &&
645 $g->is_dir ("/share") && !$g->exists ("/local") &&
646 !$g->is_file ("/etc/fstab")) {
647 $r{content} = "linux-usrlocal";
652 if ($g->is_dir ("/etc") && $g->is_dir ("/bin") &&
653 $g->is_dir ("/share") && $g->exists ("/local") &&
654 !$g->is_file ("/etc/fstab")) {
655 $r{content} = "linux-usr";
660 if ($g->is_file ("/AUTOEXEC.BAT") ||
661 $g->is_file ("/autoexec.bat") ||
662 $g->is_dir ("/Program Files") ||
663 $g->is_dir ("/WINDOWS") ||
664 $g->is_file ("/boot.ini") ||
665 $g->is_file ("/ntldr")) {
666 $r{fstype} = "ntfs"; # XXX this is a guess
667 $r{fsos} = "windows";
668 $r{content} = "windows-root";
670 _check_windows_root ($g, \%r);
680 sub _check_linux_root
686 # Look into /etc to see if we recognise the operating system.
687 # N.B. don't use $g->is_file here, because it might be a symlink
688 if ($g->exists ("/etc/redhat-release")) {
689 $r->{package_format} = "rpm";
691 $_ = $g->cat ("/etc/redhat-release");
692 if (/Fedora release (\d+)(?:\.(\d+))?/) {
693 chomp; $r->{product_name} = $_;
694 $r->{osdistro} = "fedora";
695 $r->{os_major_version} = "$1";
696 $r->{os_minor_version} = "$2" if(defined($2));
697 $r->{package_management} = "yum";
700 elsif (/(Red Hat Enterprise Linux|CentOS|Scientific Linux)/) {
701 chomp; $r->{product_name} = $_;
705 if($distro eq "Red Hat Enterprise Linux") {
706 $r->{osdistro} = "rhel";
709 elsif($distro eq "CentOS") {
710 $r->{osdistro} = "centos";
711 $r->{package_management} = "yum";
714 elsif($distro eq "Scientific Linux") {
715 $r->{osdistro} = "scientific";
716 $r->{package_management} = "yum";
719 # Shouldn't be possible
722 if (/$distro.*release (\d+).*Update (\d+)/) {
723 $r->{os_major_version} = "$1";
724 $r->{os_minor_version} = "$2";
727 elsif (/$distro.*release (\d+)(?:\.(\d+))?/) {
728 $r->{os_major_version} = "$1";
731 $r->{os_minor_version} = "$2";
733 $r->{os_minor_version} = "0";
737 # Package management in RHEL changed in version 5
738 if ($r->{osdistro} eq "rhel") {
739 if ($r->{os_major_version} >= 5) {
740 $r->{package_management} = "yum";
742 $r->{package_management} = "rhn";
748 $r->{osdistro} = "redhat-based";
750 } elsif ($g->is_file ("/etc/debian_version")) {
751 $r->{package_format} = "deb";
752 $r->{package_management} = "apt";
754 $_ = $g->cat ("/etc/debian_version");
755 if (/(\d+)\.(\d+)/) {
756 chomp; $r->{product_name} = $_;
757 $r->{osdistro} = "debian";
758 $r->{os_major_version} = "$1";
759 $r->{os_minor_version} = "$2";
761 $r->{osdistro} = "debian";
765 # Parse the contents of /etc/fstab. This is pretty vital so
766 # we can determine where filesystems are supposed to be mounted.
767 eval "\$_ = \$g->cat ('/etc/fstab');";
769 my @lines = split /\n/;
772 my @fields = split /[ \t]+/;
774 my $spec = $fields[0]; # first column (dev/label/uuid)
775 my $file = $fields[1]; # second column (mountpoint)
776 if ($spec =~ m{^/} ||
777 $spec =~ m{^LABEL=} ||
778 $spec =~ m{^UUID=} ||
780 push @fstab, [$spec, $file]
784 $r->{fstab} = \@fstab if @fstab;
787 # Determine the architecture of this root.
789 foreach ("/bin/bash", "/bin/ls", "/bin/echo", "/bin/rm", "/bin/sh") {
790 if ($g->is_file ($_)) {
791 $arch = file_architecture ($g, $_);
796 $r->{arch} = $arch if defined $arch;
799 # We only support NT. The control file /boot.ini contains a list of
800 # Windows installations and their %systemroot%s in a simple text
803 # XXX We don't handle the case where /boot.ini is on a different
804 # partition very well (Windows Vista and later).
806 sub _check_windows_root
812 my $boot_ini = resolve_windows_path ($g, "/boot.ini");
813 $r->{boot_ini} = $boot_ini;
816 if (defined $r->{boot_ini}) {
817 $_ = $g->cat ($boot_ini);
818 my @lines = split /\n/;
823 } elsif (m/^default=.*?\\(\w+)$/i) {
826 } elsif (m/\\(\w+)=/) {
833 if (!defined $systemroot) {
834 # Last ditch ... try to guess %systemroot% location.
835 foreach ("windows", "winnt") {
836 my $dir = resolve_windows_path ($g, "/$_/system32");
844 if (defined $systemroot) {
845 $r->{systemroot} = resolve_windows_path ($g, "/$systemroot");
846 if (defined $r->{systemroot}) {
847 _check_windows_arch ($g, $r, $r->{systemroot});
848 _check_windows_registry ($g, $r, $r->{systemroot});
853 # Find Windows userspace arch.
855 sub _check_windows_arch
860 my $systemroot = shift;
863 resolve_windows_path ($g, $r->{systemroot} . "/system32/cmd.exe");
864 $r->{arch} = file_architecture ($g, $cmd_exe) if $cmd_exe;
867 sub _check_windows_registry
872 my $systemroot = shift;
874 # Download the system registry files. Only download the
875 # interesting ones (SOFTWARE and SYSTEM). We don't bother with
878 return unless exists $INC{"Win/Hivex.pm"};
880 my $configdir = resolve_windows_path ($g, "$systemroot/system32/config");
881 return unless defined $configdir;
883 my $tmpdir = tempdir (CLEANUP => 1);
885 my $software = resolve_windows_path ($g, "$configdir/software");
887 if (defined $software) {
889 $g->download ($software, "$tmpdir/software");
890 $software_hive = Win::Hivex->open ("$tmpdir/software");
893 $r->{windows_software_hive} = $software;
896 my $system = resolve_windows_path ($g, "$configdir/system");
898 if (defined $system) {
900 $g->download ($system, "$tmpdir/system");
901 $system_hive = Win::Hivex->open ("$tmpdir/system");
904 $r->{windows_system_hive} = $system;
907 # Get the ProductName, major and minor version, etc.
908 if (defined $software_hive) {
911 $cv_node = $software_hive->root;
912 $cv_node = $software_hive->node_get_child ($cv_node, $_)
913 foreach ("Microsoft", "Windows NT", "CurrentVersion");
918 my @values = $software_hive->node_values ($cv_node);
921 my $k = $software_hive->value_key ($_);
922 if ($k eq "ProductName") {
923 $_ = $software_hive->value_string ($_);
924 $r->{product_name} = $_ if defined $_;
925 } elsif ($k eq "CurrentVersion") {
926 $_ = $software_hive->value_string ($_);
927 if (defined $_ && m/^(\d+)\.(\d+)/) {
928 $r->{os_major_version} = $1;
929 $r->{os_minor_version} = $2;
931 } elsif ($k eq "CurrentBuild") {
932 $_ = $software_hive->value_string ($_);
933 $r->{windows_current_build} = $_ if defined $_;
934 } elsif ($k eq "SoftwareType") {
935 $_ = $software_hive->value_string ($_);
936 $r->{windows_software_type} = $_ if defined $_;
937 } elsif ($k eq "CurrentType") {
938 $_ = $software_hive->value_string ($_);
939 $r->{windows_current_type} = $_ if defined $_;
940 } elsif ($k eq "RegisteredOwner") {
941 $_ = $software_hive->value_string ($_);
942 $r->{windows_registered_owner} = $_ if defined $_;
943 } elsif ($k eq "RegisteredOrganization") {
944 $_ = $software_hive->value_string ($_);
945 $r->{windows_registered_organization} = $_ if defined $_;
946 } elsif ($k eq "InstallationType") {
947 $_ = $software_hive->value_string ($_);
948 $r->{windows_installation_type} = $_ if defined $_;
949 } elsif ($k eq "EditionID") {
950 $_ = $software_hive->value_string ($_);
951 $r->{windows_edition_id} = $_ if defined $_;
952 } elsif ($k eq "ProductID") {
953 $_ = $software_hive->value_string ($_);
954 $r->{windows_product_id} = $_ if defined $_;
967 # Grub version, if we care.
970 =head2 inspect_operating_systems
972 \%oses = inspect_operating_systems ($g, \%fses);
974 This function works out how partitions are related to each other. In
975 the case of a single-boot VM, we work out how the partitions are
976 mounted in respect of each other (eg. C</dev/sda1> is mounted as
977 C</boot>). In the case of a multi-boot VM where there are several
978 roots, we may identify several operating system roots, and mountpoints
981 This function returns a hashref C<\%oses> which at the top level looks
985 '/dev/VG/Root' => \%os,
988 There can be multiple roots for a multi-boot VM, but this function
989 will throw an error if no roots (ie. OSes) could be found.
991 The C<\%os> hash contains the following keys (any can be omitted):
997 Operating system type, eg. "linux", "windows".
1001 Operating system userspace architecture, eg. "i386", "x86_64".
1005 Operating system distribution, eg. "debian".
1009 Free text product name.
1013 Operating system major version, eg. "4".
1017 Operating system minor version, eg "3".
1021 The value is a reference to the root partition C<%fs> hash.
1025 The value is the name of the root partition (as a string).
1030 The value is a hashref like this:
1033 '/' => '/dev/VG/Root',
1034 '/boot' => '/dev/sda1',
1039 Filesystems (including swap devices and unmounted partitions).
1040 The value is a hashref like this:
1043 '/dev/sda1' => \%fs,
1044 '/dev/VG/Root' => \%fs,
1045 '/dev/VG/Swap' => \%fs,
1052 sub inspect_operating_systems
1060 foreach (sort keys %$fses) {
1061 if ($fses->{$_}->{is_root}) {
1063 root => $fses->{$_},
1066 _get_os_version ($g, \%r);
1067 _assign_mount_points ($g, $fses, \%r);
1072 # If we didn't find any operating systems then it's an error (RHBZ#591142).
1073 if (0 == keys %oses) {
1074 die __"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 virt-inspector.\n\nIf you feel this is an error, please file a bug report including as much\ninformation about the disk image as possible.\n";
1086 $r->{os} = $r->{root}->{fsos} if exists $r->{root}->{fsos};
1087 $r->{product_name} = $r->{root}->{product_name}
1088 if exists $r->{root}->{product_name};
1089 $r->{distro} = $r->{root}->{osdistro} if exists $r->{root}->{osdistro};
1090 $r->{major_version} = $r->{root}->{os_major_version}
1091 if exists $r->{root}->{os_major_version};
1092 $r->{minor_version} = $r->{root}->{os_minor_version}
1093 if exists $r->{root}->{os_minor_version};
1094 $r->{package_format} = $r->{root}->{package_format}
1095 if exists $r->{root}->{package_format};
1096 $r->{package_management} = $r->{root}->{package_management}
1097 if exists $r->{root}->{package_management};
1098 $r->{arch} = $r->{root}->{arch} if exists $r->{root}->{arch};
1101 sub _assign_mount_points
1108 $r->{mounts} = { "/" => $r->{root_device} };
1109 $r->{filesystems} = { $r->{root_device} => $r->{root} };
1111 # Use /etc/fstab if we have it to mount the rest.
1112 if (exists $r->{root}->{fstab}) {
1113 my @fstab = @{$r->{root}->{fstab}};
1115 my ($spec, $file) = @$_;
1117 my ($dev, $fs) = _find_filesystem ($g, $fses, $spec);
1119 $r->{mounts}->{$file} = $dev;
1120 $r->{filesystems}->{$dev} = $fs;
1121 if (exists $fs->{used}) {
1126 $fs->{spec} = $spec;
1132 # Find filesystem by device name, LABEL=.. or UUID=..
1133 sub _find_filesystem
1139 if (/^LABEL=(.*)/) {
1141 foreach (sort keys %$fses) {
1142 if (exists $fses->{$_}->{label} &&
1143 $fses->{$_}->{label} eq $label) {
1144 return ($_, $fses->{$_});
1147 warn __x("unknown filesystem label {label}\n", label => $label);
1149 } elsif (/^UUID=(.*)/) {
1151 foreach (sort keys %$fses) {
1152 if (exists $fses->{$_}->{uuid} &&
1153 $fses->{$_}->{uuid} eq $uuid) {
1154 return ($_, $fses->{$_});
1157 warn __x("unknown filesystem UUID {uuid}\n", uuid => $uuid);
1160 return ($_, $fses->{$_}) if exists $fses->{$_};
1162 # The following is to handle the case where an fstab entry specifies a
1163 # specific device rather than its label or uuid, and the libguestfs
1164 # appliance has named the device differently due to the use of a
1166 # This will work as long as the underlying drivers recognise devices in
1168 if (m{^/dev/hd(.*)} && exists $fses->{"/dev/sd$1"}) {
1169 return ("/dev/sd$1", $fses->{"/dev/sd$1"});
1171 if (m{^/dev/xvd(.*)} && exists $fses->{"/dev/sd$1"}) {
1172 return ("/dev/sd$1", $fses->{"/dev/sd$1"});
1174 if (m{^/dev/mapper/(.*)-(.*)$} && exists $fses->{"/dev/$1/$2"}) {
1175 return ("/dev/$1/$2", $fses->{"/dev/$1/$2"});
1178 return () if m{/dev/cdrom};
1180 warn __x("unknown filesystem {fs}\n", fs => $_);
1185 =head2 mount_operating_system
1187 mount_operating_system ($g, \%os, [$ro]);
1189 This function mounts the operating system described in the
1190 C<%os> hash according to the C<mounts> table in that hash (see
1191 C<inspect_operating_systems>).
1193 The partitions are mounted read-only unless the third parameter
1194 is specified as zero explicitly.
1196 To reverse the effect of this call, use the standard
1197 libguestfs API call C<$g-E<gt>umount_all ()>.
1201 sub mount_operating_system
1206 my $ro = shift; # Read-only?
1208 $ro = 1 unless defined $ro; # ro defaults to 1 if unspecified
1210 my $mounts = $os->{mounts};
1212 # Have to mount / first. Luckily '/' is early in the ASCII
1213 # character set, so this should be OK.
1214 foreach (sort keys %$mounts) {
1215 if($_ ne "swap" && $_ ne "none" && ($_ eq '/' || $g->is_dir ($_))) {
1217 $g->mount_ro ($mounts->{$_}, $_)
1219 $g->mount_options ("", $mounts->{$_}, $_)
1225 =head2 inspect_in_detail
1227 mount_operating_system ($g, \%os);
1228 inspect_in_detail ($g, \%os);
1231 The C<inspect_in_detail> function inspects the mounted operating
1232 system for installed applications, installed kernels, kernel modules,
1233 system architecture, and more.
1235 It adds extra keys to the existing C<%os> hash reflecting what it
1236 finds. These extra keys are:
1242 List of applications.
1246 Boot configurations. A hash containing:
1252 An array of boot configurations. Each array entry is a hash containing:
1258 A reference to the expanded initrd structure (see below) for the initrd used by
1259 this boot configuration.
1263 A reference to the expanded kernel structure (see below) for the kernel used by
1264 this boot configuration.
1268 The human readable name of the configuration.
1272 The kernel command line.
1278 The index of the default configuration in the configs array.
1282 The path of the filesystem containing the grub partition.
1290 This is a hash of kernel version =E<gt> a hash with the following keys:
1300 Kernel architecture (eg. C<x86-64>).
1308 The path to the kernel's vmlinuz file.
1312 If the kernel was installed in a package, the name of that package.
1316 =item modprobe_aliases
1319 The contents of the modprobe configuration.
1321 =item initrd_modules
1324 The kernel modules installed in the initrd. The value is
1325 a hashref of kernel version to list of modules.
1331 sub inspect_in_detail
1337 _check_for_applications ($g, $os);
1338 _check_for_kernels ($g, $os);
1339 if ($os->{os} eq "linux") {
1340 _find_modprobe_aliases ($g, $os);
1344 sub _check_for_applications
1352 my $osn = $os->{os};
1353 if ($osn eq "linux") {
1354 my $package_format = $os->{package_format};
1355 if (defined $package_format && $package_format eq "rpm") {
1358 @lines = $g->command_lines
1361 "%{name} %{epoch} %{version} %{release} %{arch}\n"]);
1364 warn(__x("Error running rpm -qa: {error}", error => $@)) if ($@);
1366 @lines = sort @lines;
1368 if (m/^(.*) (.*) (.*) (.*) (.*)$/) {
1370 undef $epoch if $epoch eq "(none)";
1381 } elsif (defined $package_format && $package_format eq "deb") {
1384 @lines = $g->command_lines
1386 "-f", '${Package} ${Version} ${Architecture} ${Status}\n',
1390 warn(__x("Error running dpkg-query: {error}", error => $@)) if ($@);
1392 @lines = sort @lines;
1394 if (m/^(.*) (.*) (.*) (.*) (.*) (.*)$/) {
1395 if ( $6 eq "installed" ) {
1406 } elsif ($osn eq "windows") {
1408 # I worked out a general plan for this, but haven't
1409 # implemented it yet. We can iterate over /Program Files
1410 # looking for *.EXE files, which we download, then use
1411 # i686-pc-mingw32-windres on, to find the VERSIONINFO
1412 # section, which has a lot of useful information.
1415 $os->{apps} = \@apps;
1418 # Find the path which needs to be prepended to paths in grub.conf to make them
1420 sub _find_grub_prefix
1424 my $fses = $os->{filesystems};
1425 die("filesystems undefined") unless(defined($fses));
1427 # Look for the filesystem which contains grub
1429 foreach my $dev (keys(%$fses)) {
1430 my $fsinfo = $fses->{$dev};
1431 if(exists($fsinfo->{content}) && $fsinfo->{content} eq "linux-grub") {
1437 my $mounts = $os->{mounts};
1438 die("mounts undefined") unless(defined($mounts));
1440 # Find where the filesystem is mounted
1441 if(defined($grubdev)) {
1442 foreach my $mount (keys(%$mounts)) {
1443 if($mounts->{$mount} eq $grubdev) {
1444 return "" if($mount eq '/');
1449 die("$grubdev defined in filesystems, but not in mounts");
1452 # If we didn't find it, look for /boot/grub/menu.lst, then try to work out
1453 # what filesystem it's on. We use menu.lst rather than grub.conf because
1454 # debian only uses menu.lst, and anaconda creates a symlink for it.
1455 die(__"Can't find grub on guest") unless($g->exists('/boot/grub/menu.lst'));
1457 # Look for the most specific mount point in mounts
1458 foreach my $path qw(/boot/grub /boot /) {
1459 if(exists($mounts->{$path})) {
1460 return "" if($path eq '/');
1465 die("Couldn't determine which filesystem holds /boot/grub/menu.lst");
1468 sub _check_for_kernels
1472 if ($os->{os} eq "linux" && feature_available ($g, "augeas")) {
1473 # Iterate over entries in grub.conf, populating $os->{boot}
1474 # For every kernel we find, inspect it and add to $os->{kernels}
1476 my $grub = _find_grub_prefix($g, $os);
1477 my $grub_conf = "/etc/grub.conf";
1479 # Debian and other's have no /etc/grub.conf:
1480 if ( ! -f "$grub_conf" ) {
1481 $grub_conf = "$grub/grub/menu.lst";
1490 # ->{title} = "Fedora (2.6.29.6-213.fc11.i686.PAE)"
1491 # ->{kernel} = \kernel
1492 # ->{cmdline} = "ro root=/dev/mapper/vg_mbooth-lv_root rhgb"
1493 # ->{initrd} = \initrd
1494 # ->{default} = \config
1495 # ->{grub_fs} = "/boot"
1497 $g->aug_init("/", 16);
1500 # Get all configurations from grub
1501 foreach my $bootable
1502 ($g->aug_match("/files/$grub_conf/title"))
1505 $config{title} = $g->aug_get($bootable);
1508 eval { $grub_kernel = $g->aug_get("$bootable/kernel"); };
1510 warn __x("Grub entry {title} has no kernel",
1511 title => $config{title});
1514 # Check we've got a kernel entry
1515 if(defined($grub_kernel)) {
1516 my $path = "$grub$grub_kernel";
1518 # Reconstruct the kernel command line
1520 foreach my $arg ($g->aug_match("$bootable/kernel/*")) {
1521 $arg =~ m{/kernel/([^/]*)$}
1522 or die("Unexpected return from aug_match: $arg");
1526 eval { $value = $g->aug_get($arg); };
1528 if(defined($value)) {
1529 push(@args, "$name=$value");
1534 $config{cmdline} = join(' ', @args) if(scalar(@args) > 0);
1537 if ($g->exists($path)) {
1539 inspect_linux_kernel($g, $path, $os->{package_format});
1541 warn __x("grub refers to {path}, which doesn't exist\n",
1545 # Check the kernel was recognised
1546 if(defined($kernel)) {
1547 # Put this kernel on the top level kernel list
1548 $os->{kernels} ||= [];
1549 push(@{$os->{kernels}}, $kernel);
1551 $config{kernel} = $kernel;
1553 # Look for an initrd entry
1556 $initrd = $g->aug_get("$bootable/initrd");
1561 _inspect_initrd($g, $os, "$grub$initrd",
1562 $kernel->{version});
1564 warn __x("Grub entry {title} does not specify an ".
1565 "initrd", title => $config{title});
1570 push(@configs, \%config);
1574 # Create the top level boot entry
1576 $boot{configs} = \@configs;
1577 $boot{grub_fs} = $grub;
1579 # Add the default configuration
1581 $boot{default} = $g->aug_get("/files/$grub_conf/default");
1584 $os->{boot} = \%boot;
1587 elsif ($os->{os} eq "windows") {
1592 =head2 inspect_linux_kernel
1594 my $kernel_hash = inspect_linux_kernel($g, $vmlinuz_path, $package_format);
1596 inspect_linux_kernel returns a hash describing the target linux kernel. For the
1597 contents of the hash, see the I<kernels> structure described under
1598 L</inspect_in_detail>.
1602 sub inspect_linux_kernel
1604 my ($g, $path, $package_format) = @_;
1608 $kernel{path} = $path;
1610 # If this is a packaged kernel, try to work out the name of the package
1611 # which installed it. This lets us know what to install to replace it with,
1612 # e.g. kernel, kernel-smp, kernel-hugemem, kernel-PAE
1613 if($package_format eq "rpm") {
1615 eval { $package = $g->command(['rpm', '-qf', '--qf',
1616 '%{NAME}', $path]); };
1617 $kernel{package} = $package if defined($package);;
1620 # Try to get the kernel version by running file against it
1622 my $filedesc = $g->file($path);
1623 if($filedesc =~ /^$path: Linux kernel .*\bversion\s+(\S+)\b/) {
1627 # Sometimes file can't work out the kernel version, for example because it's
1628 # a Xen PV kernel. In this case try to guess the version from the filename
1630 if($path =~ m{/boot/vmlinuz-(.*)}) {
1633 # Check /lib/modules/$version exists
1634 if(!$g->is_dir("/lib/modules/$version")) {
1635 warn __x("Didn't find modules directory {modules} for kernel ".
1636 "{path}", modules => "/lib/modules/$version",
1643 warn __x("Couldn't guess kernel version number from path for ".
1644 "kernel {path}", path => $path);
1651 $kernel{version} = $version;
1656 my $prefix = "/lib/modules/$version";
1657 foreach my $module ($g->find ($prefix)) {
1658 if ($module =~ m{/([^/]+)\.(?:ko|o)$}) {
1659 $any_module = "$prefix$module" unless defined $any_module;
1664 $kernel{modules} = \@modules;
1666 # Determine kernel architecture by looking at the arch
1667 # of any kernel module.
1668 $kernel{arch} = file_architecture ($g, $any_module);
1673 # Find all modprobe aliases. Specifically, this looks in the following
1675 # * /etc/conf.modules
1676 # * /etc/modules.conf
1677 # * /etc/modprobe.conf
1678 # * /etc/modprobe.d/*
1680 sub _find_modprobe_aliases
1687 $g->aug_init("/", 16);
1689 my %modprobe_aliases;
1691 for my $pattern qw(/files/etc/conf.modules/alias
1692 /files/etc/modules.conf/alias
1693 /files/etc/modprobe.conf/alias
1694 /files/etc/modprobe.d/*/alias) {
1695 for my $path ( $g->aug_match($pattern) ) {
1696 $path =~ m{^/files(.*)/alias(?:\[\d*\])?$}
1697 or die __x("{path} doesn't match augeas pattern",
1702 $alias = $g->aug_get($path);
1705 $modulename = $g->aug_get($path.'/modulename');
1708 $aliasinfo{modulename} = $modulename;
1709 $aliasinfo{augeas} = $path;
1710 $aliasinfo{file} = $file;
1712 $modprobe_aliases{$alias} = \%aliasinfo;
1716 $os->{modprobe_aliases} = \%modprobe_aliases;
1719 # Get a listing of device drivers from an initrd
1722 my ($g, $os, $path, $version) = @_;
1726 # Disregard old-style compressed ext2 files and only work with real
1727 # compressed cpio files, since cpio takes ages to (fail to) process anything
1729 if ($g->exists($path) && $g->file($path) =~ /cpio/) {
1731 @modules = $g->initrd_list ($path);
1734 @modules = grep { m{([^/]+)\.(?:ko|o)$} } @modules;
1736 warn __x("{filename}: could not read initrd format",
1737 filename => "$path");
1741 # Add to the top level initrd_modules entry
1742 $os->{initrd_modules} ||= {};
1743 $os->{initrd_modules}->{$version} = \@modules;
1752 Copyright (C) 2009 Red Hat Inc.
1756 Please see the file COPYING.LIB for the full license.
1760 L<virt-inspector(1)>,
1763 L<http://libguestfs.org/>,
1765 L<http://libvirt.org/>,