2 # Copyright (C) 2009 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;
24 use File::Temp qw/tempdir/;
25 use Locale::TextDomain 'libguestfs';
28 eval "use Sys::Virt;";
29 eval "use XML::XPath;";
30 eval "use XML::XPath::XMLParser;";
36 Sys::Guestfs::Lib - Useful functions for using libguestfs from Perl
40 use Sys::Guestfs::Lib qw(open_guest inspect_all_partitions ...);
42 $g = open_guest ($name);
44 %fses = inspect_all_partitions ($g, \@partitions);
46 (and many more calls - see the rest of this manpage)
50 C<Sys::Guestfs::Lib> is an extra library of useful functions for using
51 the libguestfs API from Perl. It also provides tighter integration
54 The basic libguestfs API is not covered by this manpage. Please refer
55 instead to L<Sys::Guestfs(3)> and L<guestfs(3)>. The libvirt API is
56 also not covered. For that, see L<Sys::Virt(3)>.
58 =head1 BASIC FUNCTIONS
64 use vars qw(@EXPORT_OK @ISA);
67 @EXPORT_OK = qw(open_guest feature_available
68 get_partitions resolve_windows_path
69 inspect_all_partitions inspect_partition
70 inspect_operating_systems mount_operating_system inspect_in_detail
71 inspect_linux_kernel);
75 $g = open_guest ($name);
77 $g = open_guest ($name, rw => 1, ...);
79 $g = open_guest ($name, address => $uri, ...);
81 $g = open_guest ([$img1, $img2, ...], address => $uri, ...);
83 ($g, $conn, $dom, @images) = open_guest ($name);
85 This function opens a libguestfs handle for either the libvirt domain
86 called C<$name>, or the disk image called C<$name>. Any disk images
87 found through libvirt or specified explicitly are attached to the
90 The C<Sys::Guestfs> handle C<$g> is returned, or if there was an error
91 it throws an exception. To catch errors, wrap the call in an eval
94 The first parameter is either a string referring to a libvirt domain
95 or a disk image, or (if a guest has several disk images) an arrayref
96 C<[$img1, $img2, ...]>.
98 The handle is I<read-only> by default. Use the optional parameter
99 C<rw =E<gt> 1> to open a read-write handle. However if you open a
100 read-write handle, this function will refuse to use active libvirt
103 The handle is still in the config state when it is returned, so you
104 have to call C<$g-E<gt>launch ()>.
106 The optional C<address> parameter can be added to specify the libvirt
107 URI. In addition, L<Sys::Virt(3)> lists other parameters which are
108 passed through to C<Sys::Virt-E<gt>new> unchanged.
110 The implicit libvirt handle is closed after this function, I<unless>
111 you call the function in C<wantarray> context, in which case the
112 function returns a tuple of: the open libguestfs handle, the open
113 libvirt handle, and the open libvirt domain handle, and a list of
114 images. (This is useful if you want to do other things like pulling
115 the XML description of the guest). Note that if this is a straight
116 disk image, then C<$conn> and C<$dom> will be C<undef>.
118 If the C<Sys::Virt> module is not available, then libvirt is bypassed,
119 and this function can only open disk images.
129 my $readwrite = $params{rw};
132 if (ref ($first) eq "ARRAY") {
134 } elsif (ref ($first) eq "SCALAR") {
137 die __"open_guest: first parameter must be a string or an arrayref"
144 die __x("guest image {imagename} does not exist or is not readable",
149 die __"open_guest: no libvirt support (install Sys::Virt, XML::XPath and XML::XPath::XMLParser)"
150 unless exists $INC{"Sys/Virt.pm"} &&
151 exists $INC{"XML/XPath.pm"} &&
152 exists $INC{"XML/XPath/XMLParser.pm"};
154 die __"open_guest: too many domains listed on command line"
157 $conn = Sys::Virt->new (readonly => 1, @_);
158 die __"open_guest: cannot connect to libvirt" unless $conn;
160 my @doms = $conn->list_defined_domains ();
161 my $isitinactive = 1;
162 unless ($readwrite) {
163 # In the case where we want read-only access to a domain,
164 # allow the user to specify an active domain too.
165 push @doms, $conn->list_domains ();
169 if ($_->get_name () eq $images[0]) {
177 die __x("{imagename} is not the name of an inactive libvirt domain\n",
178 imagename => $images[0]);
180 die __x("{imagename} is not the name of a libvirt domain\n",
181 imagename => $images[0]);
185 # Get the names of the image(s).
186 my $xml = $dom->get_xml_description ();
188 my $p = XML::XPath->new (xml => $xml);
189 my @disks = $p->findnodes ('//devices/disk/source/@dev');
190 push (@disks, $p->findnodes ('//devices/disk/source/@file'));
192 die __x("{imagename} seems to have no disk devices\n",
193 imagename => $images[0])
196 @images = map { $_->getData } @disks;
199 # We've now got the list of @images, so feed them to libguestfs.
200 my $g = Sys::Guestfs->new ();
205 $g->add_drive_ro ($_);
209 return wantarray ? ($g, $conn, $dom, @images) : $g
212 =head2 feature_available
214 $bool = feature_available ($g, $feature [, $feature ...]);
216 This function is a useful wrapper around the basic
217 C<$g-E<gt>available> call.
219 C<$g-E<gt>available> tests for availability of a list of features and
220 dies with an error if any is not available.
222 This call tests for the list of features and returns true if all are
223 available, or false otherwise.
225 For a list of features you can test for, see L<guestfs(3)/AVAILABILITY>.
229 sub feature_available {
232 eval { $g->available (\@_); };
236 =head2 get_partitions
238 @partitions = get_partitions ($g);
240 This function takes an open libguestfs handle C<$g> and returns all
241 partitions and logical volumes found on it.
243 What is returned is everything that could contain a filesystem (or
244 swap). Physical volumes are excluded from the list, and so are any
245 devices which are partitioned (eg. C</dev/sda> would not be returned
246 if C</dev/sda1> exists).
254 my @partitions = $g->list_partitions ();
255 my @pvs = $g->pvs ();
256 @partitions = grep { ! _is_pv ($_, @pvs) } @partitions;
259 @lvs = $g->lvs () if feature_available ($g, "lvm2");
261 return sort (@lvs, @partitions);
269 return 1 if $_ eq $t;
274 =head2 resolve_windows_path
276 $path = resolve_windows_path ($g, $path);
278 $path = resolve_windows_path ($g, "/windows/system");
279 ==> "/WINDOWS/System"
280 or undef if no path exists
282 This function, which is specific to FAT/NTFS filesystems (ie. Windows
283 guests), lets you look up a case insensitive C<$path> in the
284 filesystem and returns the true, case sensitive path as required by
285 the underlying kernel or NTFS-3g driver.
287 If C<$path> does not exist then this function returns C<undef>.
289 The C<$path> parameter must begin with C</> character and be separated
290 by C</> characters. Do not use C<\>, drive names, etc.
294 sub resolve_windows_path
300 eval { $r = $g->case_sensitive_path ($path); };
304 =head2 file_architecture
306 $arch = file_architecture ($g, $path)
308 The C<file_architecture> function lets you get the architecture for a
309 particular binary or library in the guest. By "architecture" we mean
310 what processor it is compiled for (eg. C<i586> or C<x86_64>).
312 The function works on at least the following types of files:
318 many types of Un*x binary
322 many types of Un*x shared library
326 Windows Win32 and Win64 binaries
330 Windows Win32 and Win64 DLLs
332 Win32 binaries and DLLs return C<i386>.
334 Win64 binaries and DLLs return C<x86_64>.
342 Linux new-style initrd images
346 some non-x86 Linux vmlinuz kernels
350 What it can't do currently:
356 static libraries (libfoo.a)
360 Linux old-style initrd as compressed ext2 filesystem (RHEL 3)
364 x86 Linux vmlinuz kernels
366 x86 vmlinuz images (bzImage format) consist of a mix of 16-, 32- and
367 compressed code, and are horribly hard to unpack. If you want to find
368 the architecture of a kernel, use the architecture of the associated
369 initrd or kernel module(s) instead.
375 sub _elf_arch_to_canonical
379 if ($_ eq "Intel 80386") {
381 } elsif ($_ eq "Intel 80486") {
382 return "i486"; # probably not in the wild
383 } elsif ($_ eq "x86-64") {
385 } elsif ($_ eq "AMD x86-64") {
387 } elsif (/SPARC32/) {
389 } elsif (/SPARC V9/) {
391 } elsif ($_ eq "IA-64") {
393 } elsif (/64.*PowerPC/) {
395 } elsif (/PowerPC/) {
398 warn __x("returning non-canonical architecture type '{arch}'",
404 my @_initrd_binaries = ("nash", "modprobe", "sh", "bash");
406 sub file_architecture
412 # Our basic tool is 'file' ...
413 my $file = $g->file ($path);
415 if ($file =~ /ELF.*(?:executable|shared object|relocatable), (.+?),/) {
416 # ELF executable or shared object. We need to convert
417 # what file(1) prints into the canonical form.
418 return _elf_arch_to_canonical ($1);
419 } elsif ($file =~ /PE32 executable/) {
420 return "i386"; # Win32 executable or DLL
421 } elsif ($file =~ /PE32\+ executable/) {
422 return "x86_64"; # Win64 executable or DLL
425 elsif ($file =~ /cpio archive/) {
426 # Probably an initrd.
428 if ($file =~ /gzip/) {
430 } elsif ($file =~ /bzip2/) {
434 # Download and unpack it to find a binary file.
435 my $dir = tempdir (CLEANUP => 1);
436 $g->download ($path, "$dir/initrd");
438 my $bins = join " ", map { "bin/$_" } @_initrd_binaries;
439 my $cmd = "cd $dir && $zcat initrd | cpio --quiet -id $bins";
440 my $r = system ($cmd);
441 die __x("cpio command failed: {error}", error => $?)
444 foreach my $bin (@_initrd_binaries) {
445 if (-f "$dir/bin/$bin") {
446 $_ = `file $dir/bin/$bin`;
447 if (/ELF.*executable, (.+?),/) {
448 return _elf_arch_to_canonical ($1);
453 die __x("file_architecture: no known binaries found in initrd image: {path}",
457 die __x("file_architecture: unknown architecture: {path}",
461 =head1 OPERATING SYSTEM INSPECTION FUNCTIONS
463 The functions in this section can be used to inspect the operating
464 system(s) available inside a virtual machine image. For example, you
465 can find out if the VM is Linux or Windows, how the partitions are
466 meant to be mounted, and what applications are installed.
468 If you just want a simple command-line interface to this
469 functionality, use the L<virt-inspector(1)> tool. The documentation
470 below covers the case where you want to access this functionality from
473 Once you have the list of partitions (from C<get_partitions>) there
474 are several steps involved:
480 Look at each partition separately and find out what is on it.
482 The information you get back includes whether the partition contains a
483 filesystem or swapspace, what sort of filesystem (eg. ext3, ntfs), and
484 a first pass guess at the content of the filesystem (eg. Linux boot,
487 The result of this step is a C<%fs> hash of information, one hash for
490 See: C<inspect_partition>, C<inspect_all_partitions>
494 Work out the relationship between partitions.
496 In this step we work out how partitions are related to each other. In
497 the case of a single-boot VM, we work out how the partitions are
498 mounted in respect of each other (eg. C</dev/sda1> is mounted as
499 C</boot>). In the case of a multi-boot VM where there are several
500 roots, we may identify several operating system roots, and mountpoints
503 The result of this step is a single hash called C<%oses> which is
504 described in more detail below, but at the top level looks like:
507 '/dev/VG/Root1' => \%os1,
508 '/dev/VG/Root2' => \%os2,
514 '/' => '/dev/VG/Root1',
515 '/boot' => '/dev/sda1',
520 (example shows a multi-boot VM containing two root partitions).
522 See: C<inspect_operating_systems>
528 Previous to this point we've essentially been looking at each
529 partition in isolation. Now we construct a true guest filesystem by
530 mounting up all of the disks. Only once everything is mounted up can
531 we run commands in the OS context to do more detailed inspection.
533 See: C<mount_operating_system>
537 Check for kernels and applications.
539 This step now does more detailed inspection, where we can look for
540 kernels, applications and more installed in the guest.
542 The result of this is an enhanced C<%os> hash.
544 See: C<inspect_in_detail>
550 This library does not contain functions for generating output based on
551 the analysis steps above. Use a command line tool such as
552 L<virt-inspector(1)> to get useful output.
556 =head2 inspect_all_partitions
558 %fses = inspect_all_partitions ($g, \@partitions);
560 %fses = inspect_all_partitions ($g, \@partitions, use_windows_registry => 1);
562 This calls C<inspect_partition> for each partition in the list
565 The result is a hash which maps partition name to C<\%fs> hashref.
567 The contents of the C<%fs> hash and the meaning of the
568 C<use_windows_registry> flag are explained below.
572 # Turn /dev/vd* and /dev/hd* into canonical device names
573 # (see BLOCK DEVICE NAMING in guestfs(3)).
575 sub _canonical_dev ($)
578 return "/dev/sd$1" if $dev =~ m{^/dev/[vh]d(\w+)};
582 sub inspect_all_partitions
588 return map { _canonical_dev ($_) => inspect_partition ($g, $_, @_) } @parts;
591 =head2 inspect_partition
593 \%fs = inspect_partition ($g, $partition);
595 \%fs = inspect_partition ($g, $partition, use_windows_registry => 1);
597 This function inspects the device named C<$partition> in isolation and
598 tries to determine what it is. It returns information such as whether
599 the partition is formatted, and with what, whether it is mountable,
600 and what it appears to contain (eg. a Windows root, or a Linux /usr).
602 If C<use_windows_registry> is set to 1, then we will try to download
603 and parse the content of the Windows registry (for Windows root
604 devices). However since this is an expensive and error-prone
605 operation, we don't do this by default. It also requires the external
606 program C<reged>, patched to remove numerous crashing bugs in the
609 The returned value is a hashref C<\%fs> which may contain the
610 following top-level keys (any key can be missing):
616 Filesystem type, eg. "ext2" or "ntfs"
620 Apparent filesystem OS, eg. "linux" or "windows"
624 If set, the partition is a swap partition.
636 If set, the partition could be mounted by libguestfs.
640 Filesystem content, if we could determine it. One of: "linux-grub",
641 "linux-root", "linux-usrlocal", "linux-usr", "windows-root".
645 (For Linux root partitions only).
646 Operating system distribution. One of: "fedora", "rhel", "centos",
647 "scientific", "debian".
651 (For Linux root partitions only)
652 The package format used by the guest distribution. One of: "rpm", "dpkg".
654 =item package_management
656 (For Linux root partitions only)
657 The package management tool used by the guest distribution. One of: "rhn",
660 =item os_major_version
662 (For root partitions only).
663 Operating system major version number.
665 =item os_minor_version
667 (For root partitions only).
668 Operating system minor version number.
672 (For Linux root partitions only).
673 The contents of the C</etc/fstab> file.
677 (For Windows root partitions only).
678 The contents of the C</boot.ini> (NTLDR) file.
682 The value is an arrayref, which is a list of Windows registry
683 file contents, in Windows C<.REG> format.
689 sub inspect_partition
693 my $dev = shift; # LV or partition name.
696 my $use_windows_registry = $params{use_windows_registry};
698 my %r; # Result hash.
700 # First try 'file(1)' on it.
701 my $file = $g->file ($dev);
702 if ($file =~ /ext2 filesystem data/) {
705 } elsif ($file =~ /ext3 filesystem data/) {
708 } elsif ($file =~ /ext4 filesystem data/) {
711 } elsif ($file =~ m{Linux/i386 swap file}) {
717 # If it's ext2/3/4, then we want the UUID and label.
718 if (exists $r{fstype} && $r{fstype} =~ /^ext/) {
719 $r{uuid} = $g->get_e2uuid ($dev);
720 $r{label} = $g->get_e2label ($dev);
723 # Try mounting it, fnarrr.
725 $r{is_mountable} = 1;
726 eval { $g->mount_ro ($dev, "/") };
728 # It's not mountable, probably empty or some format
729 # we don't understand.
730 $r{is_mountable} = 0;
735 if ($g->is_file ("/grub/menu.lst") ||
736 $g->is_file ("/grub/grub.conf")) {
737 $r{content} = "linux-grub";
738 _check_grub ($g, \%r);
743 if ($g->is_dir ("/etc") && $g->is_dir ("/bin") &&
744 $g->is_file ("/etc/fstab")) {
745 $r{content} = "linux-root";
747 _check_linux_root ($g, \%r);
752 if ($g->is_dir ("/etc") && $g->is_dir ("/bin") &&
753 $g->is_dir ("/share") && !$g->exists ("/local") &&
754 !$g->is_file ("/etc/fstab")) {
755 $r{content} = "linux-usrlocal";
760 if ($g->is_dir ("/etc") && $g->is_dir ("/bin") &&
761 $g->is_dir ("/share") && $g->exists ("/local") &&
762 !$g->is_file ("/etc/fstab")) {
763 $r{content} = "linux-usr";
768 if ($g->is_file ("/AUTOEXEC.BAT") ||
769 $g->is_file ("/autoexec.bat") ||
770 $g->is_dir ("/Program Files") ||
771 $g->is_dir ("/WINDOWS") ||
772 $g->is_file ("/boot.ini") ||
773 $g->is_file ("/ntldr")) {
774 $r{fstype} = "ntfs"; # XXX this is a guess
775 $r{fsos} = "windows";
776 $r{content} = "windows-root";
778 _check_windows_root ($g, \%r, $use_windows_registry);
788 sub _check_linux_root
794 # Look into /etc to see if we recognise the operating system.
795 # N.B. don't use $g->is_file here, because it might be a symlink
796 if ($g->exists ("/etc/redhat-release")) {
797 $r->{package_format} = "rpm";
799 $_ = $g->cat ("/etc/redhat-release");
800 if (/Fedora release (\d+)(?:\.(\d+))?/) {
801 $r->{osdistro} = "fedora";
802 $r->{os_major_version} = "$1";
803 $r->{os_minor_version} = "$2" if(defined($2));
804 $r->{package_management} = "yum";
807 elsif (/(Red Hat Enterprise Linux|CentOS|Scientific Linux)/) {
810 if($distro eq "Red Hat Enterprise Linux") {
811 $r->{osdistro} = "rhel";
814 elsif($distro eq "CentOS") {
815 $r->{osdistro} = "centos";
816 $r->{package_management} = "yum";
819 elsif($distro eq "Scientific Linux") {
820 $r->{osdistro} = "scientific";
821 $r->{package_management} = "yum";
824 # Shouldn't be possible
827 if (/$distro.*release (\d+).*Update (\d+)/) {
828 $r->{os_major_version} = "$1";
829 $r->{os_minor_version} = "$2";
832 elsif (/$distro.*release (\d+)(?:\.(\d+))?/) {
833 $r->{os_major_version} = "$1";
836 $r->{os_minor_version} = "$2";
838 $r->{os_minor_version} = "0";
842 # Package management in RHEL changed in version 5
843 if ($r->{osdistro} eq "rhel") {
844 if ($r->{os_major_version} >= 5) {
845 $r->{package_management} = "yum";
847 $r->{package_management} = "rhn";
853 $r->{osdistro} = "redhat-based";
855 } elsif ($g->is_file ("/etc/debian_version")) {
856 $r->{package_format} = "dpkg";
857 $r->{package_management} = "apt";
859 $_ = $g->cat ("/etc/debian_version");
860 if (/(\d+)\.(\d+)/) {
861 $r->{osdistro} = "debian";
862 $r->{os_major_version} = "$1";
863 $r->{os_minor_version} = "$2";
865 $r->{osdistro} = "debian";
869 # Parse the contents of /etc/fstab. This is pretty vital so
870 # we can determine where filesystems are supposed to be mounted.
871 eval "\$_ = \$g->cat ('/etc/fstab');";
873 my @lines = split /\n/;
876 my @fields = split /[ \t]+/;
878 my $spec = $fields[0]; # first column (dev/label/uuid)
879 my $file = $fields[1]; # second column (mountpoint)
880 if ($spec =~ m{^/} ||
881 $spec =~ m{^LABEL=} ||
882 $spec =~ m{^UUID=} ||
884 push @fstab, [$spec, $file]
888 $r->{fstab} = \@fstab if @fstab;
891 # Determine the architecture of this root.
893 foreach ("/bin/bash", "/bin/ls", "/bin/echo", "/bin/rm", "/bin/sh") {
894 if ($g->is_file ($_)) {
895 $arch = file_architecture ($g, $_);
900 $r->{arch} = $arch if defined $arch;
903 # We only support NT. The control file /boot.ini contains a list of
904 # Windows installations and their %systemroot%s in a simple text
907 # XXX We could parse this better. This won't work if /boot.ini is on
908 # a different drive from the %systemroot%, and in other unusual cases.
910 sub _check_windows_root
915 my $use_windows_registry = shift;
917 my $boot_ini = resolve_windows_path ($g, "/boot.ini");
918 $r->{boot_ini} = $boot_ini;
920 if (defined $r->{boot_ini}) {
921 $_ = $g->cat ($boot_ini);
922 my @lines = split /\n/;
928 } elsif (m/^default=.*?\\(\w+)$/i) {
931 } elsif (m/\\(\w+)=/) {
937 if (defined $systemroot) {
938 $r->{systemroot} = resolve_windows_path ($g, "/$systemroot");
939 if (defined $r->{systemroot}) {
940 _check_windows_arch ($g, $r, $r->{systemroot});
941 if ($use_windows_registry) {
942 _check_windows_registry ($g, $r, $r->{systemroot});
949 # Find Windows userspace arch.
951 sub _check_windows_arch
956 my $systemroot = shift;
959 resolve_windows_path ($g, $r->{systemroot} . "/system32/cmd.exe");
960 $r->{arch} = file_architecture ($g, $cmd_exe) if $cmd_exe;
963 sub _check_windows_registry
968 my $systemroot = shift;
970 # Download the system registry files. Only download the
971 # interesting ones, and we don't bother with user profiles at all.
973 my $configdir = resolve_windows_path ($g, "$systemroot/system32/config");
974 if (defined $configdir) {
975 my $softwaredir = resolve_windows_path ($g, "$configdir/software");
976 if (defined $softwaredir) {
977 _load_windows_registry ($g, $r, $softwaredir,
978 "HKEY_LOCAL_MACHINE\\SOFTWARE");
980 my $systemdir = resolve_windows_path ($g, "$configdir/system");
981 if (defined $systemdir) {
982 _load_windows_registry ($g, $r, $systemdir,
983 "HKEY_LOCAL_MACHINE\\System");
988 sub _load_windows_registry
996 my $dir = tempdir (CLEANUP => 1);
998 $g->download ($regfile, "$dir/reg");
1000 # 'reged' command is particularly noisy. Redirect stdout and
1001 # stderr to /dev/null temporarily.
1002 open SAVEOUT, ">&STDOUT";
1003 open SAVEERR, ">&STDERR";
1004 open STDOUT, ">/dev/null";
1005 open STDERR, ">/dev/null";
1007 my @cmd = ("reged", "-x", "$dir/reg", "$prefix", "\\", "$dir/out");
1008 my $res = system (@cmd);
1012 open STDOUT, ">&SAVEOUT";
1013 open STDERR, ">&SAVEERR";
1017 unless ($res == 0) {
1018 warn __x("reged command failed: {errormsg}", errormsg => $?);
1022 # Some versions of reged segfault on inputs. If that happens we
1023 # may get no / partial output file. Anyway, if it exists, load
1026 unless (open F, "$dir/out") {
1027 warn __x("no output from reged command: {errormsg}", errormsg => $!);
1030 { local $/ = undef; $content = <F>; }
1034 @registry = @{$r->{registry}} if exists $r->{registry};
1035 push @registry, $content;
1036 $r->{registry} = \@registry;
1045 # Grub version, if we care.
1048 =head2 inspect_operating_systems
1050 \%oses = inspect_operating_systems ($g, \%fses);
1052 This function works out how partitions are related to each other. In
1053 the case of a single-boot VM, we work out how the partitions are
1054 mounted in respect of each other (eg. C</dev/sda1> is mounted as
1055 C</boot>). In the case of a multi-boot VM where there are several
1056 roots, we may identify several operating system roots, and mountpoints
1059 This function returns a hashref C<\%oses> which at the top level looks
1063 '/dev/VG/Root' => \%os,
1066 (There can be multiple roots for a multi-boot VM).
1068 The C<\%os> hash contains the following keys (any can be omitted):
1074 Operating system type, eg. "linux", "windows".
1078 Operating system userspace architecture, eg. "i386", "x86_64".
1082 Operating system distribution, eg. "debian".
1086 Operating system major version, eg. "4".
1090 Operating system minor version, eg "3".
1094 The value is a reference to the root partition C<%fs> hash.
1098 The value is the name of the root partition (as a string).
1103 The value is a hashref like this:
1106 '/' => '/dev/VG/Root',
1107 '/boot' => '/dev/sda1',
1112 Filesystems (including swap devices and unmounted partitions).
1113 The value is a hashref like this:
1116 '/dev/sda1' => \%fs,
1117 '/dev/VG/Root' => \%fs,
1118 '/dev/VG/Swap' => \%fs,
1125 sub inspect_operating_systems
1133 foreach (sort keys %$fses) {
1134 if ($fses->{$_}->{is_root}) {
1136 root => $fses->{$_},
1139 _get_os_version ($g, \%r);
1140 _assign_mount_points ($g, $fses, \%r);
1154 $r->{os} = $r->{root}->{fsos} if exists $r->{root}->{fsos};
1155 $r->{distro} = $r->{root}->{osdistro} if exists $r->{root}->{osdistro};
1156 $r->{major_version} = $r->{root}->{os_major_version}
1157 if exists $r->{root}->{os_major_version};
1158 $r->{minor_version} = $r->{root}->{os_minor_version}
1159 if exists $r->{root}->{os_minor_version};
1160 $r->{package_format} = $r->{root}->{package_format}
1161 if exists $r->{root}->{package_format};
1162 $r->{package_management} = $r->{root}->{package_management}
1163 if exists $r->{root}->{package_management};
1164 $r->{arch} = $r->{root}->{arch} if exists $r->{root}->{arch};
1167 sub _assign_mount_points
1174 $r->{mounts} = { "/" => $r->{root_device} };
1175 $r->{filesystems} = { $r->{root_device} => $r->{root} };
1177 # Use /etc/fstab if we have it to mount the rest.
1178 if (exists $r->{root}->{fstab}) {
1179 my @fstab = @{$r->{root}->{fstab}};
1181 my ($spec, $file) = @$_;
1183 my ($dev, $fs) = _find_filesystem ($g, $fses, $spec);
1185 $r->{mounts}->{$file} = $dev;
1186 $r->{filesystems}->{$dev} = $fs;
1187 if (exists $fs->{used}) {
1192 $fs->{spec} = $spec;
1198 # Find filesystem by device name, LABEL=.. or UUID=..
1199 sub _find_filesystem
1205 if (/^LABEL=(.*)/) {
1207 foreach (sort keys %$fses) {
1208 if (exists $fses->{$_}->{label} &&
1209 $fses->{$_}->{label} eq $label) {
1210 return ($_, $fses->{$_});
1213 warn __x("unknown filesystem label {label}\n", label => $label);
1215 } elsif (/^UUID=(.*)/) {
1217 foreach (sort keys %$fses) {
1218 if (exists $fses->{$_}->{uuid} &&
1219 $fses->{$_}->{uuid} eq $uuid) {
1220 return ($_, $fses->{$_});
1223 warn __x("unknown filesystem UUID {uuid}\n", uuid => $uuid);
1226 return ($_, $fses->{$_}) if exists $fses->{$_};
1228 # The following is to handle the case where an fstab entry specifies a
1229 # specific device rather than its label or uuid, and the libguestfs
1230 # appliance has named the device differently due to the use of a
1232 # This will work as long as the underlying drivers recognise devices in
1234 if (m{^/dev/hd(.*)} && exists $fses->{"/dev/sd$1"}) {
1235 return ("/dev/sd$1", $fses->{"/dev/sd$1"});
1237 if (m{^/dev/xvd(.*)} && exists $fses->{"/dev/sd$1"}) {
1238 return ("/dev/sd$1", $fses->{"/dev/sd$1"});
1240 if (m{^/dev/mapper/(.*)-(.*)$} && exists $fses->{"/dev/$1/$2"}) {
1241 return ("/dev/$1/$2", $fses->{"/dev/$1/$2"});
1244 return () if m{/dev/cdrom};
1246 warn __x("unknown filesystem {fs}\n", fs => $_);
1251 =head2 mount_operating_system
1253 mount_operating_system ($g, \%os, [$ro]);
1255 This function mounts the operating system described in the
1256 C<%os> hash according to the C<mounts> table in that hash (see
1257 C<inspect_operating_systems>).
1259 The partitions are mounted read-only unless the third parameter
1260 is specified as zero explicitly.
1262 To reverse the effect of this call, use the standard
1263 libguestfs API call C<$g-E<gt>umount_all ()>.
1267 sub mount_operating_system
1272 my $ro = shift; # Read-only?
1274 $ro = 1 unless defined $ro; # ro defaults to 1 if unspecified
1276 my $mounts = $os->{mounts};
1278 # Have to mount / first. Luckily '/' is early in the ASCII
1279 # character set, so this should be OK.
1280 foreach (sort keys %$mounts) {
1281 if($_ ne "swap" && $_ ne "none" && ($_ eq '/' || $g->is_dir ($_))) {
1283 $g->mount_ro ($mounts->{$_}, $_)
1285 $g->mount ($mounts->{$_}, $_)
1291 =head2 inspect_in_detail
1293 mount_operating_system ($g, \%os);
1294 inspect_in_detail ($g, \%os);
1297 The C<inspect_in_detail> function inspects the mounted operating
1298 system for installed applications, installed kernels, kernel modules,
1299 system architecture, and more.
1301 It adds extra keys to the existing C<%os> hash reflecting what it
1302 finds. These extra keys are:
1308 List of applications.
1312 Boot configurations. A hash containing:
1318 An array of boot configurations. Each array entry is a hash containing:
1324 A reference to the expanded initrd structure (see below) for the initrd used by
1325 this boot configuration.
1329 A reference to the expanded kernel structure (see below) for the kernel used by
1330 this boot configuration.
1334 The human readable name of the configuration.
1338 The kernel command line.
1344 The index of the default configuration in the configs array.
1348 The path of the filesystem containing the grub partition.
1356 This is a hash of kernel version =E<gt> a hash with the following keys:
1366 Kernel architecture (eg. C<x86-64>).
1374 The path to the kernel's vmlinuz file.
1378 If the kernel was installed in a package, the name of that package.
1382 =item modprobe_aliases
1385 The contents of the modprobe configuration.
1387 =item initrd_modules
1390 The kernel modules installed in the initrd. The value is
1391 a hashref of kernel version to list of modules.
1397 sub inspect_in_detail
1403 _check_for_applications ($g, $os);
1404 _check_for_kernels ($g, $os);
1405 if ($os->{os} eq "linux") {
1406 _find_modprobe_aliases ($g, $os);
1410 sub _check_for_applications
1418 my $osn = $os->{os};
1419 if ($osn eq "linux") {
1420 my $package_format = $os->{package_format};
1421 if (defined $package_format && $package_format eq "rpm") {
1422 my @lines = $g->command_lines
1425 "--qf", "%{name} %{epoch} %{version} %{release} %{arch}\n"]);
1427 if (m/^(.*) (.*) (.*) (.*) (.*)$/) {
1429 $epoch = "" if $epoch eq "(none)";
1441 } elsif ($osn eq "windows") {
1443 # I worked out a general plan for this, but haven't
1444 # implemented it yet. We can iterate over /Program Files
1445 # looking for *.EXE files, which we download, then use
1446 # i686-pc-mingw32-windres on, to find the VERSIONINFO
1447 # section, which has a lot of useful information.
1450 $os->{apps} = \@apps;
1453 # Find the path which needs to be prepended to paths in grub.conf to make them
1455 sub _find_grub_prefix
1459 my $fses = $os->{filesystems};
1460 die("filesystems undefined") unless(defined($fses));
1462 # Look for the filesystem which contains grub
1464 foreach my $dev (keys(%$fses)) {
1465 my $fsinfo = $fses->{$dev};
1466 if(exists($fsinfo->{content}) && $fsinfo->{content} eq "linux-grub") {
1472 my $mounts = $os->{mounts};
1473 die("mounts undefined") unless(defined($mounts));
1475 # Find where the filesystem is mounted
1476 if(defined($grubdev)) {
1477 foreach my $mount (keys(%$mounts)) {
1478 if($mounts->{$mount} eq $grubdev) {
1479 return "" if($mount eq '/');
1484 die("$grubdev defined in filesystems, but not in mounts");
1487 # If we didn't find it, look for /boot/grub/menu.lst, then try to work out
1488 # what filesystem it's on. We use menu.lst rather than grub.conf because
1489 # debian only uses menu.lst, and anaconda creates a symlink for it.
1490 die(__"Can't find grub on guest") unless($g->exists('/boot/grub/menu.lst'));
1492 # Look for the most specific mount point in mounts
1493 foreach my $path qw(/boot/grub /boot /) {
1494 if(exists($mounts->{$path})) {
1495 return "" if($path eq '/');
1500 die("Couldn't determine which filesystem holds /boot/grub/menu.lst");
1503 sub _check_for_kernels
1507 if ($os->{os} eq "linux" && feature_available ($g, "augeas")) {
1508 # Iterate over entries in grub.conf, populating $os->{boot}
1509 # For every kernel we find, inspect it and add to $os->{kernels}
1511 my $grub = _find_grub_prefix($g, $os);
1519 # ->{title} = "Fedora (2.6.29.6-213.fc11.i686.PAE)"
1520 # ->{kernel} = \kernel
1521 # ->{cmdline} = "ro root=/dev/mapper/vg_mbooth-lv_root rhgb"
1522 # ->{initrd} = \initrd
1523 # ->{default} = \config
1524 # ->{grub_fs} = "/boot"
1526 $g->aug_init("/", 16);
1529 # Get all configurations from grub
1530 foreach my $bootable
1531 ($g->aug_match("/files/etc/grub.conf/title"))
1534 $config{title} = $g->aug_get($bootable);
1537 eval { $grub_kernel = $g->aug_get("$bootable/kernel"); };
1539 warn __x("Grub entry {title} has no kernel",
1540 title => $config{title});
1543 # Check we've got a kernel entry
1544 if(defined($grub_kernel)) {
1545 my $path = "$grub$grub_kernel";
1547 # Reconstruct the kernel command line
1549 foreach my $arg ($g->aug_match("$bootable/kernel/*")) {
1550 $arg =~ m{/kernel/([^/]*)$}
1551 or die("Unexpected return from aug_match: $arg");
1555 eval { $value = $g->aug_get($arg); };
1557 if(defined($value)) {
1558 push(@args, "$name=$value");
1563 $config{cmdline} = join(' ', @args) if(scalar(@args) > 0);
1566 inspect_linux_kernel($g, $path, $os->{package_format});
1568 # Check the kernel was recognised
1569 if(defined($kernel)) {
1570 # Put this kernel on the top level kernel list
1571 $os->{kernels} ||= [];
1572 push(@{$os->{kernels}}, $kernel);
1574 $config{kernel} = $kernel;
1576 # Look for an initrd entry
1579 $initrd = $g->aug_get("$bootable/initrd");
1584 _inspect_initrd($g, $os, "$grub$initrd",
1585 $kernel->{version});
1587 warn __x("Grub entry {title} does not specify an ".
1588 "initrd", title => $config{title});
1593 push(@configs, \%config);
1597 # Create the top level boot entry
1599 $boot{configs} = \@configs;
1600 $boot{grub_fs} = $grub;
1602 # Add the default configuration
1604 $boot{default} = $g->aug_get("/files/etc/grub.conf/default");
1607 warn __"No grub default specified";
1610 $os->{boot} = \%boot;
1613 elsif ($os->{os} eq "windows") {
1618 =head2 inspect_linux_kernel
1620 my $kernel_hash = inspect_linux_kernel($g, $vmlinuz_path, $package_format);
1622 inspect_linux_kernel returns a hash describing the target linux kernel. For the
1623 contents of the hash, see the I<kernels> structure described under
1624 L</inspect_in_detail>.
1628 sub inspect_linux_kernel
1630 my ($g, $path, $package_format) = @_;
1634 $kernel{path} = $path;
1636 # If this is a packaged kernel, try to work out the name of the package
1637 # which installed it. This lets us know what to install to replace it with,
1638 # e.g. kernel, kernel-smp, kernel-hugemem, kernel-PAE
1639 if($package_format eq "rpm") {
1641 eval { $package = $g->command(['rpm', '-qf', '--qf',
1642 '%{NAME}', $path]); };
1643 $kernel{package} = $package if defined($package);;
1646 # Try to get the kernel version by running file against it
1648 my $filedesc = $g->file($path);
1649 if($filedesc =~ /^$path: Linux kernel .*\bversion\s+(\S+)\b/) {
1653 # Sometimes file can't work out the kernel version, for example because it's
1654 # a Xen PV kernel. In this case try to guess the version from the filename
1656 if($path =~ m{/boot/vmlinuz-(.*)}) {
1659 # Check /lib/modules/$version exists
1660 if(!$g->is_dir("/lib/modules/$version")) {
1661 warn __x("Didn't find modules directory {modules} for kernel ".
1662 "{path}", modules => "/lib/modules/$version",
1669 warn __x("Couldn't guess kernel version number from path for ".
1670 "kernel {path}", path => $path);
1677 $kernel{version} = $version;
1682 my $prefix = "/lib/modules/$version";
1683 foreach my $module ($g->find ($prefix)) {
1684 if ($module =~ m{/([^/]+)\.(?:ko|o)$}) {
1685 $any_module = "$prefix$module" unless defined $any_module;
1690 $kernel{modules} = \@modules;
1692 # Determine kernel architecture by looking at the arch
1693 # of any kernel module.
1694 $kernel{arch} = file_architecture ($g, $any_module);
1699 # Find all modprobe aliases. Specifically, this looks in the following
1701 # * /etc/conf.modules
1702 # * /etc/modules.conf
1703 # * /etc/modprobe.conf
1704 # * /etc/modprobe.d/*
1706 sub _find_modprobe_aliases
1713 $g->aug_init("/", 16);
1715 # Register additional paths to the Modprobe lens
1716 $g->aug_set("/augeas/load/Modprobe/incl[last()+1]", "/etc/modules.conf");
1717 $g->aug_set("/augeas/load/Modprobe/incl[last()+1]", "/etc/conf.modules");
1719 # Make augeas reload
1722 my %modprobe_aliases;
1724 for my $pattern qw(/files/etc/conf.modules/alias
1725 /files/etc/modules.conf/alias
1726 /files/etc/modprobe.conf/alias
1727 /files/etc/modprobe.d/*/alias) {
1728 for my $path ( $g->aug_match($pattern) ) {
1729 $path =~ m{^/files(.*)/alias(?:\[\d*\])?$}
1730 or die __x("{path} doesn't match augeas pattern",
1735 $alias = $g->aug_get($path);
1738 $modulename = $g->aug_get($path.'/modulename');
1741 $aliasinfo{modulename} = $modulename;
1742 $aliasinfo{augeas} = $path;
1743 $aliasinfo{file} = $file;
1745 $modprobe_aliases{$alias} = \%aliasinfo;
1749 $os->{modprobe_aliases} = \%modprobe_aliases;
1752 # Get a listing of device drivers from an initrd
1755 my ($g, $os, $path, $version) = @_;
1759 # Disregard old-style compressed ext2 files and only work with real
1760 # compressed cpio files, since cpio takes ages to (fail to) process anything
1762 if ($g->file ($path) =~ /cpio/) {
1764 @modules = $g->initrd_list ($path);
1767 @modules = grep { m{([^/]+)\.(?:ko|o)$} } @modules;
1769 warn __x("{filename}: could not read initrd format",
1770 filename => "$path");
1774 # Add to the top level initrd_modules entry
1775 $os->{initrd_modules} ||= {};
1776 $os->{initrd_modules}->{$version} = \@modules;
1785 Copyright (C) 2009 Red Hat Inc.
1789 Please see the file COPYING.LIB for the full license.
1793 L<virt-inspector(1)>,
1796 L<http://libguestfs.org/>,
1798 L<http://libvirt.org/>,