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
109 The implicit libvirt handle is closed after this function, I<unless>
110 you call the function in C<wantarray> context, in which case the
111 function returns a tuple of: the open libguestfs handle, the open
112 libvirt handle, and the open libvirt domain handle, and a list of
113 images. (This is useful if you want to do other things like pulling
114 the XML description of the guest). Note that if this is a straight
115 disk image, then C<$conn> and C<$dom> will be C<undef>.
117 If the C<Sys::Virt> module is not available, then libvirt is bypassed,
118 and this function can only open disk images.
128 my $rw = $params{rw};
129 my $address = $params{address};
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 my @libvirt_args = ();
158 push @libvirt_args, address => $address if defined $address;
160 $conn = Sys::Virt->new (readonly => 1, @libvirt_args);
161 die __"open_guest: cannot connect to libvirt" unless $conn;
163 my @doms = $conn->list_defined_domains ();
164 my $isitinactive = 1;
166 # In the case where we want read-only access to a domain,
167 # allow the user to specify an active domain too.
168 push @doms, $conn->list_domains ();
172 if ($_->get_name () eq $images[0]) {
180 die __x("{imagename} is not the name of an inactive libvirt domain\n",
181 imagename => $images[0]);
183 die __x("{imagename} is not the name of a libvirt domain\n",
184 imagename => $images[0]);
188 # Get the names of the image(s).
189 my $xml = $dom->get_xml_description ();
191 my $p = XML::XPath->new (xml => $xml);
192 my @disks = $p->findnodes ('//devices/disk/source/@dev');
193 push (@disks, $p->findnodes ('//devices/disk/source/@file'));
195 die __x("{imagename} seems to have no disk devices\n",
196 imagename => $images[0])
199 @images = map { $_->getData } @disks;
202 # We've now got the list of @images, so feed them to libguestfs.
203 my $g = Sys::Guestfs->new ();
208 $g->add_drive_ro ($_);
212 return wantarray ? ($g, $conn, $dom, @images) : $g
215 =head2 feature_available
217 $bool = feature_available ($g, $feature [, $feature ...]);
219 This function is a useful wrapper around the basic
220 C<$g-E<gt>available> call.
222 C<$g-E<gt>available> tests for availability of a list of features and
223 dies with an error if any is not available.
225 This call tests for the list of features and returns true if all are
226 available, or false otherwise.
228 For a list of features you can test for, see L<guestfs(3)/AVAILABILITY>.
232 sub feature_available {
235 eval { $g->available (\@_); };
239 =head2 get_partitions
241 @partitions = get_partitions ($g);
243 This function takes an open libguestfs handle C<$g> and returns all
244 partitions and logical volumes found on it.
246 What is returned is everything that could contain a filesystem (or
247 swap). Physical volumes are excluded from the list, and so are any
248 devices which are partitioned (eg. C</dev/sda> would not be returned
249 if C</dev/sda1> exists).
257 my @partitions = $g->list_partitions ();
258 my @pvs = $g->pvs ();
259 @partitions = grep { ! _is_pv ($_, @pvs) } @partitions;
262 @lvs = $g->lvs () if feature_available ($g, "lvm2");
264 return sort (@lvs, @partitions);
272 return 1 if $_ eq $t;
277 =head2 resolve_windows_path
279 $path = resolve_windows_path ($g, $path);
281 $path = resolve_windows_path ($g, "/windows/system");
282 ==> "/WINDOWS/System"
283 or undef if no path exists
285 This function, which is specific to FAT/NTFS filesystems (ie. Windows
286 guests), lets you look up a case insensitive C<$path> in the
287 filesystem and returns the true, case sensitive path as required by
288 the underlying kernel or NTFS-3g driver.
290 If C<$path> does not exist then this function returns C<undef>.
292 The C<$path> parameter must begin with C</> character and be separated
293 by C</> characters. Do not use C<\>, drive names, etc.
297 sub resolve_windows_path
303 eval { $r = $g->case_sensitive_path ($path); };
307 =head2 file_architecture
309 $arch = file_architecture ($g, $path)
311 The C<file_architecture> function lets you get the architecture for a
312 particular binary or library in the guest. By "architecture" we mean
313 what processor it is compiled for (eg. C<i586> or C<x86_64>).
315 The function works on at least the following types of files:
321 many types of Un*x binary
325 many types of Un*x shared library
329 Windows Win32 and Win64 binaries
333 Windows Win32 and Win64 DLLs
335 Win32 binaries and DLLs return C<i386>.
337 Win64 binaries and DLLs return C<x86_64>.
345 Linux new-style initrd images
349 some non-x86 Linux vmlinuz kernels
353 What it can't do currently:
359 static libraries (libfoo.a)
363 Linux old-style initrd as compressed ext2 filesystem (RHEL 3)
367 x86 Linux vmlinuz kernels
369 x86 vmlinuz images (bzImage format) consist of a mix of 16-, 32- and
370 compressed code, and are horribly hard to unpack. If you want to find
371 the architecture of a kernel, use the architecture of the associated
372 initrd or kernel module(s) instead.
378 sub _elf_arch_to_canonical
382 if ($_ eq "Intel 80386") {
384 } elsif ($_ eq "Intel 80486") {
385 return "i486"; # probably not in the wild
386 } elsif ($_ eq "x86-64") {
388 } elsif ($_ eq "AMD x86-64") {
390 } elsif (/SPARC32/) {
392 } elsif (/SPARC V9/) {
394 } elsif ($_ eq "IA-64") {
396 } elsif (/64.*PowerPC/) {
398 } elsif (/PowerPC/) {
401 warn __x("returning non-canonical architecture type '{arch}'",
407 my @_initrd_binaries = ("nash", "modprobe", "sh", "bash");
409 sub file_architecture
415 # Our basic tool is 'file' ...
416 my $file = $g->file ($path);
418 if ($file =~ /ELF.*(?:executable|shared object|relocatable), (.+?),/) {
419 # ELF executable or shared object. We need to convert
420 # what file(1) prints into the canonical form.
421 return _elf_arch_to_canonical ($1);
422 } elsif ($file =~ /PE32 executable/) {
423 return "i386"; # Win32 executable or DLL
424 } elsif ($file =~ /PE32\+ executable/) {
425 return "x86_64"; # Win64 executable or DLL
428 elsif ($file =~ /cpio archive/) {
429 # Probably an initrd.
431 if ($file =~ /gzip/) {
433 } elsif ($file =~ /bzip2/) {
437 # Download and unpack it to find a binary file.
438 my $dir = tempdir (CLEANUP => 1);
439 $g->download ($path, "$dir/initrd");
441 my $bins = join " ", map { "bin/$_" } @_initrd_binaries;
442 my $cmd = "cd $dir && $zcat initrd | cpio --quiet -id $bins";
443 my $r = system ($cmd);
444 die __x("cpio command failed: {error}", error => $?)
447 foreach my $bin (@_initrd_binaries) {
448 if (-f "$dir/bin/$bin") {
449 $_ = `file $dir/bin/$bin`;
450 if (/ELF.*executable, (.+?),/) {
451 return _elf_arch_to_canonical ($1);
456 die __x("file_architecture: no known binaries found in initrd image: {path}",
460 die __x("file_architecture: unknown architecture: {path}",
464 =head1 OPERATING SYSTEM INSPECTION FUNCTIONS
466 The functions in this section can be used to inspect the operating
467 system(s) available inside a virtual machine image. For example, you
468 can find out if the VM is Linux or Windows, how the partitions are
469 meant to be mounted, and what applications are installed.
471 If you just want a simple command-line interface to this
472 functionality, use the L<virt-inspector(1)> tool. The documentation
473 below covers the case where you want to access this functionality from
476 Once you have the list of partitions (from C<get_partitions>) there
477 are several steps involved:
483 Look at each partition separately and find out what is on it.
485 The information you get back includes whether the partition contains a
486 filesystem or swapspace, what sort of filesystem (eg. ext3, ntfs), and
487 a first pass guess at the content of the filesystem (eg. Linux boot,
490 The result of this step is a C<%fs> hash of information, one hash for
493 See: C<inspect_partition>, C<inspect_all_partitions>
497 Work out the relationship between partitions.
499 In this step we work out how partitions are related to each other. In
500 the case of a single-boot VM, we work out how the partitions are
501 mounted in respect of each other (eg. C</dev/sda1> is mounted as
502 C</boot>). In the case of a multi-boot VM where there are several
503 roots, we may identify several operating system roots, and mountpoints
506 The result of this step is a single hash called C<%oses> which is
507 described in more detail below, but at the top level looks like:
510 '/dev/VG/Root1' => \%os1,
511 '/dev/VG/Root2' => \%os2,
517 '/' => '/dev/VG/Root1',
518 '/boot' => '/dev/sda1',
523 (example shows a multi-boot VM containing two root partitions).
525 See: C<inspect_operating_systems>
531 Previous to this point we've essentially been looking at each
532 partition in isolation. Now we construct a true guest filesystem by
533 mounting up all of the disks. Only once everything is mounted up can
534 we run commands in the OS context to do more detailed inspection.
536 See: C<mount_operating_system>
540 Check for kernels and applications.
542 This step now does more detailed inspection, where we can look for
543 kernels, applications and more installed in the guest.
545 The result of this is an enhanced C<%os> hash.
547 See: C<inspect_in_detail>
553 This library does not contain functions for generating output based on
554 the analysis steps above. Use a command line tool such as
555 L<virt-inspector(1)> to get useful output.
559 =head2 inspect_all_partitions
561 %fses = inspect_all_partitions ($g, \@partitions);
563 %fses = inspect_all_partitions ($g, \@partitions, use_windows_registry => 1);
565 This calls C<inspect_partition> for each partition in the list
568 The result is a hash which maps partition name to C<\%fs> hashref.
570 The contents of the C<%fs> hash and the meaning of the
571 C<use_windows_registry> flag are explained below.
575 # Turn /dev/vd* and /dev/hd* into canonical device names
576 # (see BLOCK DEVICE NAMING in guestfs(3)).
578 sub _canonical_dev ($)
581 return "/dev/sd$1" if $dev =~ m{^/dev/[vh]d(\w+)};
585 sub inspect_all_partitions
591 return map { _canonical_dev ($_) => inspect_partition ($g, $_, @_) } @parts;
594 =head2 inspect_partition
596 \%fs = inspect_partition ($g, $partition);
598 \%fs = inspect_partition ($g, $partition, use_windows_registry => 1);
600 This function inspects the device named C<$partition> in isolation and
601 tries to determine what it is. It returns information such as whether
602 the partition is formatted, and with what, whether it is mountable,
603 and what it appears to contain (eg. a Windows root, or a Linux /usr).
605 If C<use_windows_registry> is set to 1, then we will try to download
606 and parse the content of the Windows registry (for Windows root
607 devices). However since this is an expensive and error-prone
608 operation, we don't do this by default. It also requires the external
609 program C<reged>, patched to remove numerous crashing bugs in the
612 The returned value is a hashref C<\%fs> which may contain the
613 following top-level keys (any key can be missing):
619 Filesystem type, eg. "ext2" or "ntfs"
623 Apparent filesystem OS, eg. "linux" or "windows"
627 If set, the partition is a swap partition.
639 If set, the partition could be mounted by libguestfs.
643 Filesystem content, if we could determine it. One of: "linux-grub",
644 "linux-root", "linux-usrlocal", "linux-usr", "windows-root".
648 (For Linux root partitions only).
649 Operating system distribution. One of: "fedora", "rhel", "centos",
650 "scientific", "debian".
654 (For Linux root partitions only)
655 The package format used by the guest distribution. One of: "rpm", "dpkg".
657 =item package_management
659 (For Linux root partitions only)
660 The package management tool used by the guest distribution. One of: "rhn",
663 =item os_major_version
665 (For root partitions only).
666 Operating system major version number.
668 =item os_minor_version
670 (For root partitions only).
671 Operating system minor version number.
675 (For Linux root partitions only).
676 The contents of the C</etc/fstab> file.
680 (For Windows root partitions only).
681 The contents of the C</boot.ini> (NTLDR) file.
685 The value is an arrayref, which is a list of Windows registry
686 file contents, in Windows C<.REG> format.
692 sub inspect_partition
696 my $dev = shift; # LV or partition name.
699 my $use_windows_registry = $params{use_windows_registry};
701 my %r; # Result hash.
703 # First try 'file(1)' on it.
704 my $file = $g->file ($dev);
705 if ($file =~ /ext2 filesystem data/) {
708 } elsif ($file =~ /ext3 filesystem data/) {
711 } elsif ($file =~ /ext4 filesystem data/) {
714 } elsif ($file =~ m{Linux/i386 swap file}) {
720 # If it's ext2/3/4, then we want the UUID and label.
721 if (exists $r{fstype} && $r{fstype} =~ /^ext/) {
722 $r{uuid} = $g->get_e2uuid ($dev);
723 $r{label} = $g->get_e2label ($dev);
726 # Try mounting it, fnarrr.
728 $r{is_mountable} = 1;
729 eval { $g->mount_ro ($dev, "/") };
731 # It's not mountable, probably empty or some format
732 # we don't understand.
733 $r{is_mountable} = 0;
738 if ($g->is_file ("/grub/menu.lst") ||
739 $g->is_file ("/grub/grub.conf")) {
740 $r{content} = "linux-grub";
741 _check_grub ($g, \%r);
746 if ($g->is_dir ("/etc") && $g->is_dir ("/bin") &&
747 $g->is_file ("/etc/fstab")) {
748 $r{content} = "linux-root";
750 _check_linux_root ($g, \%r);
755 if ($g->is_dir ("/etc") && $g->is_dir ("/bin") &&
756 $g->is_dir ("/share") && !$g->exists ("/local") &&
757 !$g->is_file ("/etc/fstab")) {
758 $r{content} = "linux-usrlocal";
763 if ($g->is_dir ("/etc") && $g->is_dir ("/bin") &&
764 $g->is_dir ("/share") && $g->exists ("/local") &&
765 !$g->is_file ("/etc/fstab")) {
766 $r{content} = "linux-usr";
771 if ($g->is_file ("/AUTOEXEC.BAT") ||
772 $g->is_file ("/autoexec.bat") ||
773 $g->is_dir ("/Program Files") ||
774 $g->is_dir ("/WINDOWS") ||
775 $g->is_file ("/boot.ini") ||
776 $g->is_file ("/ntldr")) {
777 $r{fstype} = "ntfs"; # XXX this is a guess
778 $r{fsos} = "windows";
779 $r{content} = "windows-root";
781 _check_windows_root ($g, \%r, $use_windows_registry);
791 sub _check_linux_root
797 # Look into /etc to see if we recognise the operating system.
798 # N.B. don't use $g->is_file here, because it might be a symlink
799 if ($g->exists ("/etc/redhat-release")) {
800 $r->{package_format} = "rpm";
802 $_ = $g->cat ("/etc/redhat-release");
803 if (/Fedora release (\d+)(?:\.(\d+))?/) {
804 $r->{osdistro} = "fedora";
805 $r->{os_major_version} = "$1";
806 $r->{os_minor_version} = "$2" if(defined($2));
807 $r->{package_management} = "yum";
810 elsif (/(Red Hat Enterprise Linux|CentOS|Scientific Linux)/) {
813 if($distro eq "Red Hat Enterprise Linux") {
814 $r->{osdistro} = "rhel";
817 elsif($distro eq "CentOS") {
818 $r->{osdistro} = "centos";
819 $r->{package_management} = "yum";
822 elsif($distro eq "Scientific Linux") {
823 $r->{osdistro} = "scientific";
824 $r->{package_management} = "yum";
827 # Shouldn't be possible
830 if (/$distro.*release (\d+).*Update (\d+)/) {
831 $r->{os_major_version} = "$1";
832 $r->{os_minor_version} = "$2";
835 elsif (/$distro.*release (\d+)(?:\.(\d+))?/) {
836 $r->{os_major_version} = "$1";
839 $r->{os_minor_version} = "$2";
841 $r->{os_minor_version} = "0";
845 # Package management in RHEL changed in version 5
846 if ($r->{osdistro} eq "rhel") {
847 if ($r->{os_major_version} >= 5) {
848 $r->{package_management} = "yum";
850 $r->{package_management} = "rhn";
856 $r->{osdistro} = "redhat-based";
858 } elsif ($g->is_file ("/etc/debian_version")) {
859 $r->{package_format} = "dpkg";
860 $r->{package_management} = "apt";
862 $_ = $g->cat ("/etc/debian_version");
863 if (/(\d+)\.(\d+)/) {
864 $r->{osdistro} = "debian";
865 $r->{os_major_version} = "$1";
866 $r->{os_minor_version} = "$2";
868 $r->{osdistro} = "debian";
872 # Parse the contents of /etc/fstab. This is pretty vital so
873 # we can determine where filesystems are supposed to be mounted.
874 eval "\$_ = \$g->cat ('/etc/fstab');";
876 my @lines = split /\n/;
879 my @fields = split /[ \t]+/;
881 my $spec = $fields[0]; # first column (dev/label/uuid)
882 my $file = $fields[1]; # second column (mountpoint)
883 if ($spec =~ m{^/} ||
884 $spec =~ m{^LABEL=} ||
885 $spec =~ m{^UUID=} ||
887 push @fstab, [$spec, $file]
891 $r->{fstab} = \@fstab if @fstab;
894 # Determine the architecture of this root.
896 foreach ("/bin/bash", "/bin/ls", "/bin/echo", "/bin/rm", "/bin/sh") {
897 if ($g->is_file ($_)) {
898 $arch = file_architecture ($g, $_);
903 $r->{arch} = $arch if defined $arch;
906 # We only support NT. The control file /boot.ini contains a list of
907 # Windows installations and their %systemroot%s in a simple text
910 # XXX We could parse this better. This won't work if /boot.ini is on
911 # a different drive from the %systemroot%, and in other unusual cases.
913 sub _check_windows_root
918 my $use_windows_registry = shift;
920 my $boot_ini = resolve_windows_path ($g, "/boot.ini");
921 $r->{boot_ini} = $boot_ini;
923 if (defined $r->{boot_ini}) {
924 $_ = $g->cat ($boot_ini);
925 my @lines = split /\n/;
931 } elsif (m/^default=.*?\\(\w+)$/i) {
934 } elsif (m/\\(\w+)=/) {
940 if (defined $systemroot) {
941 $r->{systemroot} = resolve_windows_path ($g, "/$systemroot");
942 if (defined $r->{systemroot}) {
943 _check_windows_arch ($g, $r, $r->{systemroot});
944 if ($use_windows_registry) {
945 _check_windows_registry ($g, $r, $r->{systemroot});
952 # Find Windows userspace arch.
954 sub _check_windows_arch
959 my $systemroot = shift;
962 resolve_windows_path ($g, $r->{systemroot} . "/system32/cmd.exe");
963 $r->{arch} = file_architecture ($g, $cmd_exe) if $cmd_exe;
966 sub _check_windows_registry
971 my $systemroot = shift;
973 # Download the system registry files. Only download the
974 # interesting ones, and we don't bother with user profiles at all.
976 my $configdir = resolve_windows_path ($g, "$systemroot/system32/config");
977 if (defined $configdir) {
978 my $softwaredir = resolve_windows_path ($g, "$configdir/software");
979 if (defined $softwaredir) {
980 _load_windows_registry ($g, $r, $softwaredir,
981 "HKEY_LOCAL_MACHINE\\SOFTWARE");
983 my $systemdir = resolve_windows_path ($g, "$configdir/system");
984 if (defined $systemdir) {
985 _load_windows_registry ($g, $r, $systemdir,
986 "HKEY_LOCAL_MACHINE\\System");
991 sub _load_windows_registry
999 my $dir = tempdir (CLEANUP => 1);
1001 $g->download ($regfile, "$dir/reg");
1003 # 'reged' command is particularly noisy. Redirect stdout and
1004 # stderr to /dev/null temporarily.
1005 open SAVEOUT, ">&STDOUT";
1006 open SAVEERR, ">&STDERR";
1007 open STDOUT, ">/dev/null";
1008 open STDERR, ">/dev/null";
1010 my @cmd = ("reged", "-x", "$dir/reg", "$prefix", "\\", "$dir/out");
1011 my $res = system (@cmd);
1015 open STDOUT, ">&SAVEOUT";
1016 open STDERR, ">&SAVEERR";
1020 unless ($res == 0) {
1021 warn __x("reged command failed: {errormsg}", errormsg => $?);
1025 # Some versions of reged segfault on inputs. If that happens we
1026 # may get no / partial output file. Anyway, if it exists, load
1029 unless (open F, "$dir/out") {
1030 warn __x("no output from reged command: {errormsg}", errormsg => $!);
1033 { local $/ = undef; $content = <F>; }
1037 @registry = @{$r->{registry}} if exists $r->{registry};
1038 push @registry, $content;
1039 $r->{registry} = \@registry;
1048 # Grub version, if we care.
1051 =head2 inspect_operating_systems
1053 \%oses = inspect_operating_systems ($g, \%fses);
1055 This function works out how partitions are related to each other. In
1056 the case of a single-boot VM, we work out how the partitions are
1057 mounted in respect of each other (eg. C</dev/sda1> is mounted as
1058 C</boot>). In the case of a multi-boot VM where there are several
1059 roots, we may identify several operating system roots, and mountpoints
1062 This function returns a hashref C<\%oses> which at the top level looks
1066 '/dev/VG/Root' => \%os,
1069 (There can be multiple roots for a multi-boot VM).
1071 The C<\%os> hash contains the following keys (any can be omitted):
1077 Operating system type, eg. "linux", "windows".
1081 Operating system userspace architecture, eg. "i386", "x86_64".
1085 Operating system distribution, eg. "debian".
1089 Operating system major version, eg. "4".
1093 Operating system minor version, eg "3".
1097 The value is a reference to the root partition C<%fs> hash.
1101 The value is the name of the root partition (as a string).
1106 The value is a hashref like this:
1109 '/' => '/dev/VG/Root',
1110 '/boot' => '/dev/sda1',
1115 Filesystems (including swap devices and unmounted partitions).
1116 The value is a hashref like this:
1119 '/dev/sda1' => \%fs,
1120 '/dev/VG/Root' => \%fs,
1121 '/dev/VG/Swap' => \%fs,
1128 sub inspect_operating_systems
1136 foreach (sort keys %$fses) {
1137 if ($fses->{$_}->{is_root}) {
1139 root => $fses->{$_},
1142 _get_os_version ($g, \%r);
1143 _assign_mount_points ($g, $fses, \%r);
1157 $r->{os} = $r->{root}->{fsos} if exists $r->{root}->{fsos};
1158 $r->{distro} = $r->{root}->{osdistro} if exists $r->{root}->{osdistro};
1159 $r->{major_version} = $r->{root}->{os_major_version}
1160 if exists $r->{root}->{os_major_version};
1161 $r->{minor_version} = $r->{root}->{os_minor_version}
1162 if exists $r->{root}->{os_minor_version};
1163 $r->{package_format} = $r->{root}->{package_format}
1164 if exists $r->{root}->{package_format};
1165 $r->{package_management} = $r->{root}->{package_management}
1166 if exists $r->{root}->{package_management};
1167 $r->{arch} = $r->{root}->{arch} if exists $r->{root}->{arch};
1170 sub _assign_mount_points
1177 $r->{mounts} = { "/" => $r->{root_device} };
1178 $r->{filesystems} = { $r->{root_device} => $r->{root} };
1180 # Use /etc/fstab if we have it to mount the rest.
1181 if (exists $r->{root}->{fstab}) {
1182 my @fstab = @{$r->{root}->{fstab}};
1184 my ($spec, $file) = @$_;
1186 my ($dev, $fs) = _find_filesystem ($g, $fses, $spec);
1188 $r->{mounts}->{$file} = $dev;
1189 $r->{filesystems}->{$dev} = $fs;
1190 if (exists $fs->{used}) {
1195 $fs->{spec} = $spec;
1201 # Find filesystem by device name, LABEL=.. or UUID=..
1202 sub _find_filesystem
1208 if (/^LABEL=(.*)/) {
1210 foreach (sort keys %$fses) {
1211 if (exists $fses->{$_}->{label} &&
1212 $fses->{$_}->{label} eq $label) {
1213 return ($_, $fses->{$_});
1216 warn __x("unknown filesystem label {label}\n", label => $label);
1218 } elsif (/^UUID=(.*)/) {
1220 foreach (sort keys %$fses) {
1221 if (exists $fses->{$_}->{uuid} &&
1222 $fses->{$_}->{uuid} eq $uuid) {
1223 return ($_, $fses->{$_});
1226 warn __x("unknown filesystem UUID {uuid}\n", uuid => $uuid);
1229 return ($_, $fses->{$_}) if exists $fses->{$_};
1231 # The following is to handle the case where an fstab entry specifies a
1232 # specific device rather than its label or uuid, and the libguestfs
1233 # appliance has named the device differently due to the use of a
1235 # This will work as long as the underlying drivers recognise devices in
1237 if (m{^/dev/hd(.*)} && exists $fses->{"/dev/sd$1"}) {
1238 return ("/dev/sd$1", $fses->{"/dev/sd$1"});
1240 if (m{^/dev/xvd(.*)} && exists $fses->{"/dev/sd$1"}) {
1241 return ("/dev/sd$1", $fses->{"/dev/sd$1"});
1243 if (m{^/dev/mapper/(.*)-(.*)$} && exists $fses->{"/dev/$1/$2"}) {
1244 return ("/dev/$1/$2", $fses->{"/dev/$1/$2"});
1247 return () if m{/dev/cdrom};
1249 warn __x("unknown filesystem {fs}\n", fs => $_);
1254 =head2 mount_operating_system
1256 mount_operating_system ($g, \%os, [$ro]);
1258 This function mounts the operating system described in the
1259 C<%os> hash according to the C<mounts> table in that hash (see
1260 C<inspect_operating_systems>).
1262 The partitions are mounted read-only unless the third parameter
1263 is specified as zero explicitly.
1265 To reverse the effect of this call, use the standard
1266 libguestfs API call C<$g-E<gt>umount_all ()>.
1270 sub mount_operating_system
1275 my $ro = shift; # Read-only?
1277 $ro = 1 unless defined $ro; # ro defaults to 1 if unspecified
1279 my $mounts = $os->{mounts};
1281 # Have to mount / first. Luckily '/' is early in the ASCII
1282 # character set, so this should be OK.
1283 foreach (sort keys %$mounts) {
1284 if($_ ne "swap" && $_ ne "none" && ($_ eq '/' || $g->is_dir ($_))) {
1286 $g->mount_ro ($mounts->{$_}, $_)
1288 $g->mount_options ("", $mounts->{$_}, $_)
1294 =head2 inspect_in_detail
1296 mount_operating_system ($g, \%os);
1297 inspect_in_detail ($g, \%os);
1300 The C<inspect_in_detail> function inspects the mounted operating
1301 system for installed applications, installed kernels, kernel modules,
1302 system architecture, and more.
1304 It adds extra keys to the existing C<%os> hash reflecting what it
1305 finds. These extra keys are:
1311 List of applications.
1315 Boot configurations. A hash containing:
1321 An array of boot configurations. Each array entry is a hash containing:
1327 A reference to the expanded initrd structure (see below) for the initrd used by
1328 this boot configuration.
1332 A reference to the expanded kernel structure (see below) for the kernel used by
1333 this boot configuration.
1337 The human readable name of the configuration.
1341 The kernel command line.
1347 The index of the default configuration in the configs array.
1351 The path of the filesystem containing the grub partition.
1359 This is a hash of kernel version =E<gt> a hash with the following keys:
1369 Kernel architecture (eg. C<x86-64>).
1377 The path to the kernel's vmlinuz file.
1381 If the kernel was installed in a package, the name of that package.
1385 =item modprobe_aliases
1388 The contents of the modprobe configuration.
1390 =item initrd_modules
1393 The kernel modules installed in the initrd. The value is
1394 a hashref of kernel version to list of modules.
1400 sub inspect_in_detail
1406 _check_for_applications ($g, $os);
1407 _check_for_kernels ($g, $os);
1408 if ($os->{os} eq "linux") {
1409 _find_modprobe_aliases ($g, $os);
1413 sub _check_for_applications
1421 my $osn = $os->{os};
1422 if ($osn eq "linux") {
1423 my $package_format = $os->{package_format};
1424 if (defined $package_format && $package_format eq "rpm") {
1425 my @lines = $g->command_lines
1428 "--qf", "%{name} %{epoch} %{version} %{release} %{arch}\n"]);
1430 if (m/^(.*) (.*) (.*) (.*) (.*)$/) {
1432 undef $epoch if $epoch eq "(none)";
1444 } elsif ($osn eq "windows") {
1446 # I worked out a general plan for this, but haven't
1447 # implemented it yet. We can iterate over /Program Files
1448 # looking for *.EXE files, which we download, then use
1449 # i686-pc-mingw32-windres on, to find the VERSIONINFO
1450 # section, which has a lot of useful information.
1453 $os->{apps} = \@apps;
1456 # Find the path which needs to be prepended to paths in grub.conf to make them
1458 sub _find_grub_prefix
1462 my $fses = $os->{filesystems};
1463 die("filesystems undefined") unless(defined($fses));
1465 # Look for the filesystem which contains grub
1467 foreach my $dev (keys(%$fses)) {
1468 my $fsinfo = $fses->{$dev};
1469 if(exists($fsinfo->{content}) && $fsinfo->{content} eq "linux-grub") {
1475 my $mounts = $os->{mounts};
1476 die("mounts undefined") unless(defined($mounts));
1478 # Find where the filesystem is mounted
1479 if(defined($grubdev)) {
1480 foreach my $mount (keys(%$mounts)) {
1481 if($mounts->{$mount} eq $grubdev) {
1482 return "" if($mount eq '/');
1487 die("$grubdev defined in filesystems, but not in mounts");
1490 # If we didn't find it, look for /boot/grub/menu.lst, then try to work out
1491 # what filesystem it's on. We use menu.lst rather than grub.conf because
1492 # debian only uses menu.lst, and anaconda creates a symlink for it.
1493 die(__"Can't find grub on guest") unless($g->exists('/boot/grub/menu.lst'));
1495 # Look for the most specific mount point in mounts
1496 foreach my $path qw(/boot/grub /boot /) {
1497 if(exists($mounts->{$path})) {
1498 return "" if($path eq '/');
1503 die("Couldn't determine which filesystem holds /boot/grub/menu.lst");
1506 sub _check_for_kernels
1510 if ($os->{os} eq "linux" && feature_available ($g, "augeas")) {
1511 # Iterate over entries in grub.conf, populating $os->{boot}
1512 # For every kernel we find, inspect it and add to $os->{kernels}
1514 my $grub = _find_grub_prefix($g, $os);
1522 # ->{title} = "Fedora (2.6.29.6-213.fc11.i686.PAE)"
1523 # ->{kernel} = \kernel
1524 # ->{cmdline} = "ro root=/dev/mapper/vg_mbooth-lv_root rhgb"
1525 # ->{initrd} = \initrd
1526 # ->{default} = \config
1527 # ->{grub_fs} = "/boot"
1529 $g->aug_init("/", 16);
1532 # Get all configurations from grub
1533 foreach my $bootable
1534 ($g->aug_match("/files/etc/grub.conf/title"))
1537 $config{title} = $g->aug_get($bootable);
1540 eval { $grub_kernel = $g->aug_get("$bootable/kernel"); };
1542 warn __x("Grub entry {title} has no kernel",
1543 title => $config{title});
1546 # Check we've got a kernel entry
1547 if(defined($grub_kernel)) {
1548 my $path = "$grub$grub_kernel";
1550 # Reconstruct the kernel command line
1552 foreach my $arg ($g->aug_match("$bootable/kernel/*")) {
1553 $arg =~ m{/kernel/([^/]*)$}
1554 or die("Unexpected return from aug_match: $arg");
1558 eval { $value = $g->aug_get($arg); };
1560 if(defined($value)) {
1561 push(@args, "$name=$value");
1566 $config{cmdline} = join(' ', @args) if(scalar(@args) > 0);
1569 inspect_linux_kernel($g, $path, $os->{package_format});
1571 # Check the kernel was recognised
1572 if(defined($kernel)) {
1573 # Put this kernel on the top level kernel list
1574 $os->{kernels} ||= [];
1575 push(@{$os->{kernels}}, $kernel);
1577 $config{kernel} = $kernel;
1579 # Look for an initrd entry
1582 $initrd = $g->aug_get("$bootable/initrd");
1587 _inspect_initrd($g, $os, "$grub$initrd",
1588 $kernel->{version});
1590 warn __x("Grub entry {title} does not specify an ".
1591 "initrd", title => $config{title});
1596 push(@configs, \%config);
1600 # Create the top level boot entry
1602 $boot{configs} = \@configs;
1603 $boot{grub_fs} = $grub;
1605 # Add the default configuration
1607 $boot{default} = $g->aug_get("/files/etc/grub.conf/default");
1610 warn __"No grub default specified";
1613 $os->{boot} = \%boot;
1616 elsif ($os->{os} eq "windows") {
1621 =head2 inspect_linux_kernel
1623 my $kernel_hash = inspect_linux_kernel($g, $vmlinuz_path, $package_format);
1625 inspect_linux_kernel returns a hash describing the target linux kernel. For the
1626 contents of the hash, see the I<kernels> structure described under
1627 L</inspect_in_detail>.
1631 sub inspect_linux_kernel
1633 my ($g, $path, $package_format) = @_;
1637 $kernel{path} = $path;
1639 # If this is a packaged kernel, try to work out the name of the package
1640 # which installed it. This lets us know what to install to replace it with,
1641 # e.g. kernel, kernel-smp, kernel-hugemem, kernel-PAE
1642 if($package_format eq "rpm") {
1644 eval { $package = $g->command(['rpm', '-qf', '--qf',
1645 '%{NAME}', $path]); };
1646 $kernel{package} = $package if defined($package);;
1649 # Try to get the kernel version by running file against it
1651 my $filedesc = $g->file($path);
1652 if($filedesc =~ /^$path: Linux kernel .*\bversion\s+(\S+)\b/) {
1656 # Sometimes file can't work out the kernel version, for example because it's
1657 # a Xen PV kernel. In this case try to guess the version from the filename
1659 if($path =~ m{/boot/vmlinuz-(.*)}) {
1662 # Check /lib/modules/$version exists
1663 if(!$g->is_dir("/lib/modules/$version")) {
1664 warn __x("Didn't find modules directory {modules} for kernel ".
1665 "{path}", modules => "/lib/modules/$version",
1672 warn __x("Couldn't guess kernel version number from path for ".
1673 "kernel {path}", path => $path);
1680 $kernel{version} = $version;
1685 my $prefix = "/lib/modules/$version";
1686 foreach my $module ($g->find ($prefix)) {
1687 if ($module =~ m{/([^/]+)\.(?:ko|o)$}) {
1688 $any_module = "$prefix$module" unless defined $any_module;
1693 $kernel{modules} = \@modules;
1695 # Determine kernel architecture by looking at the arch
1696 # of any kernel module.
1697 $kernel{arch} = file_architecture ($g, $any_module);
1702 # Find all modprobe aliases. Specifically, this looks in the following
1704 # * /etc/conf.modules
1705 # * /etc/modules.conf
1706 # * /etc/modprobe.conf
1707 # * /etc/modprobe.d/*
1709 sub _find_modprobe_aliases
1716 $g->aug_init("/", 16);
1718 # Register additional paths to the Modprobe lens
1719 $g->aug_set("/augeas/load/Modprobe/incl[last()+1]", "/etc/modules.conf");
1720 $g->aug_set("/augeas/load/Modprobe/incl[last()+1]", "/etc/conf.modules");
1722 # Make augeas reload
1725 my %modprobe_aliases;
1727 for my $pattern qw(/files/etc/conf.modules/alias
1728 /files/etc/modules.conf/alias
1729 /files/etc/modprobe.conf/alias
1730 /files/etc/modprobe.d/*/alias) {
1731 for my $path ( $g->aug_match($pattern) ) {
1732 $path =~ m{^/files(.*)/alias(?:\[\d*\])?$}
1733 or die __x("{path} doesn't match augeas pattern",
1738 $alias = $g->aug_get($path);
1741 $modulename = $g->aug_get($path.'/modulename');
1744 $aliasinfo{modulename} = $modulename;
1745 $aliasinfo{augeas} = $path;
1746 $aliasinfo{file} = $file;
1748 $modprobe_aliases{$alias} = \%aliasinfo;
1752 $os->{modprobe_aliases} = \%modprobe_aliases;
1755 # Get a listing of device drivers from an initrd
1758 my ($g, $os, $path, $version) = @_;
1762 # Disregard old-style compressed ext2 files and only work with real
1763 # compressed cpio files, since cpio takes ages to (fail to) process anything
1765 if ($g->file ($path) =~ /cpio/) {
1767 @modules = $g->initrd_list ($path);
1770 @modules = grep { m{([^/]+)\.(?:ko|o)$} } @modules;
1772 warn __x("{filename}: could not read initrd format",
1773 filename => "$path");
1777 # Add to the top level initrd_modules entry
1778 $os->{initrd_modules} ||= {};
1779 $os->{initrd_modules}->{$version} = \@modules;
1788 Copyright (C) 2009 Red Hat Inc.
1792 Please see the file COPYING.LIB for the full license.
1796 L<virt-inspector(1)>,
1799 L<http://libguestfs.org/>,
1801 L<http://libvirt.org/>,