[Libguestfs] [PATCH 3/4] Add SUSE converter

Mike Latimer mlatimer at suse.com
Tue Sep 24 00:32:23 UTC 2013


The SUSE converter itself, based on the RedHat converter. This supports
converting SLES 10/11 and openSUSE 10/11/12/13.

---
 MANIFEST                              |    1 +
 lib/Sys/VirtConvert/Converter/SUSE.pm | 2527 +++++++++++++++++++++++++++++++++
 2 files changed, 2528 insertions(+)

diff --git a/MANIFEST b/MANIFEST
index 3724fde..615ec32 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -17,6 +17,7 @@ lib/Sys/VirtConvert/Connection/VMwareOVASource.pm
 lib/Sys/VirtConvert/Connection/Volume.pm
 lib/Sys/VirtConvert/Converter.pm
 lib/Sys/VirtConvert/Converter/RedHat.pm
+lib/Sys/VirtConvert/Converter/SUSE.pm
 lib/Sys/VirtConvert/Converter/Windows.pm
 lib/Sys/VirtConvert/ExecHelper.pm
 lib/Sys/VirtConvert/GuestfsHandle.pm
diff --git a/lib/Sys/VirtConvert/Converter/SUSE.pm b/lib/Sys/VirtConvert/Converter/SUSE.pm
new file mode 100644
index 0000000..7227385
--- /dev/null
+++ b/lib/Sys/VirtConvert/Converter/SUSE.pm
@@ -0,0 +1,2527 @@
+# Sys::VirtConvert::Converter::SUSE
+# Copyright (C) 2009-2012 SUSE Inc.
+#
+# Based on Sys::VirtConvert::Converter::RedHat
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+use strict;
+use warnings;
+
+
+# Functions common between grub legacy and grub2. All bootloader
+# interactions should eventually go through Bootloader::Tools.
+package Sys::VirtConvert::Converter::SUSE::Grub;
+
+use Sys::VirtConvert::Util;
+use Locale::TextDomain 'virt-v2v';
+
+sub get_initrd
+{
+    my $self = shift;
+    my ($path) = @_;
+
+    my $g = $self->{g};
+
+    # The default name of the initrd is the same as the kernel name, with
+    # initrd replacing vmlinuz. This can be confirmed with grubby, or
+    # Bootloader::Tools->GetSection, but for now just do the replacement
+    # if the file exists.
+    my $initrd;
+    ($initrd = $path) =~ s/vmlinuz/initrd/;
+    if ($g->exists($initrd)) {
+        return $initrd;
+    }
+
+    v2vdie __x('Didn\'t find initrd for kernel {path}', path => $path);
+}
+
+sub get_default_image
+{
+    my $self = shift;
+    my ($path) = @_;
+
+    my $g = $self->{g};
+
+    my $default = $g->command(['/usr/bin/perl',
+                  '-MBootloader::Tools',
+                  '-e', 'InitLibrary(); '.
+                  'my $default=Bootloader::Tools::GetDefaultSection(); '.
+                  'print $default->{image};']);
+    # If the image starts with (hd*,*), remove it.
+    $default =~ s/^\(hd.*\)//;
+
+    return $default;
+}
+
+sub set_default_image
+{
+    my $self = shift;
+    my ($path) = @_;
+
+    my $g = $self->{g};
+
+    # Using the image path to set a default image is not always reliable.
+    # To be safe, get the image name, then set that as the default image.
+    $g->command(['/usr/bin/perl',
+           '-MBootloader::Tools',
+           '-e', 'InitLibrary(); '.
+           'my @sections = '.
+           'GetSectionList(type=>image, image=>"'.$path.'"); '.
+           'my $section = GetSection(@sections); '.
+           'my $newdefault = $section->{name}; '.
+           'SetGlobals(default, "$newdefault");']);
+}
+
+sub check_efi
+{
+    my $self = shift;
+    my $g = $self->{g};
+
+    # Check the first partition of each device looking for an EFI boot
+    # partition. We can't be sure which device is the boot device, so we just
+    # check them all.
+    foreach my $device ($g->list_devices()) {
+        my $guid = eval { $g->part_get_gpt_type($device, 1) };
+        next unless defined($guid);
+
+        if ($guid eq 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B') {
+            $self->convert_efi($device);
+            last;
+        }
+    }
+}
+
+
+# Methods for inspecting and manipulating grub legacy
+package Sys::VirtConvert::Converter::SUSE::GrubLegacy;
+
+use Sys::VirtConvert::Util;
+
+use File::Basename;
+use Locale::TextDomain 'virt-v2v';
+
+ at Sys::VirtConvert::Converter::SUSE::GrubLegacy::ISA =
+    qw(Sys::VirtConvert::Converter::SUSE::Grub);
+
+sub new
+{
+    my $class = shift;
+    my ($g, $root) = @_;
+
+    my $self = {};
+    bless($self, $class);
+
+    $self->{g} = $g;
+    $self->{root} = $root;
+
+    # Look for an EFI configuration
+    foreach my $cfg ($g->glob_expand('/boot/*efi/*/grub.conf')) {
+        $self->check_efi();
+    }
+
+    # Look for the grub configuration file
+    foreach my $path ('/boot/grub/menu.lst', '/boot/grub/grub.conf')
+    {
+        if ($g->exists($path)) {
+            $self->{grub_conf} = $path;
+            last;
+        }
+    }
+
+    # We can't continue without a config file
+    die unless defined ($self->{grub_conf});
+
+    # Find the path which needs to be prepended to paths in grub.conf to
+    # make them absolute
+    # Look for the most specific mount point discovered
+
+    # Default to / (no prefix required)
+    $self->{grub_fs} ||= "";
+
+    my %mounts = $g->inspect_get_mountpoints($root);
+    foreach my $path ('/boot/grub', '/boot') {
+        if (exists($mounts{$path})) {
+            $self->{grub_fs} = $path;
+            last;
+        }
+    }
+
+    # Initialise augeas
+    eval {
+        # Check grub_conf is included by the Grub lens
+        my $found = 0;
+        foreach my $incl ($g->aug_match("/augeas/load/Grub/incl")) {
+            if ($g->aug_get($incl) eq $self->{grub_conf}) {
+                $found = 1;
+                last;
+            }
+        }
+
+        # If it wasn't there, add it
+        unless ($found) {
+            $g->aug_set("/augeas/load/Grub/incl[last()+1]", $self->{grub_conf});
+
+            # Make augeas pick up the new configuration
+            $g->aug_load();
+        }
+    };
+    augeas_error($g, $@) if ($@);
+
+    return $self;
+}
+
+sub list_kernels
+{
+    my $self = shift;
+
+    my $g = $self->{g};
+    my $grub_conf = $self->{grub_conf};
+    my $grub_fs = $self->{grub_fs};
+
+    # Look for a kernel, starting with the default
+    my @paths = eval {
+        # Get a list of all kernels
+        my @ret = $g->aug_match("/files$grub_conf/title/kernel");
+
+        # Get the default kernel from grub if it's set
+        # Doesn't matter if there isn't one (aug_get will fail)
+        my $default = eval {
+            my $idx = $g->aug_get("/files$grub_conf/default");
+
+            # Grub indices are zero-based, augeas is 1-based
+            "/files$grub_conf/title[".($idx + 1)."]/kernel";
+        };
+
+        if (defined($default)) {
+            # Remove the default from the list and put it at the beginning
+            @ret = grep {!m{$default}} @ret;
+            unshift(@ret, $default);
+        }
+
+        @ret;
+    };
+    augeas_error($g, $@) if ($@);
+
+    my @kernels;
+    my %checked;
+    foreach my $path (@paths) {
+        next if $checked{$path};
+        $checked{$path} = 1;
+
+        my $kernel = eval { $g->aug_get($path) };
+        augeas_error($g, $@) if ($@);
+
+        # Prepend the grub filesystem to the kernel path
+        $kernel = "$grub_fs$kernel" if defined $grub_fs;
+
+        # Check the kernel exists
+        if ($g->exists($kernel)) {
+            push(@kernels, $kernel);
+        }
+
+        else {
+            logmsg WARN, __x('grub refers to {path}, which doesn\'t exist.',
+                             path => $kernel);
+        }
+    }
+
+    return @kernels;
+}
+
+sub update_console
+{
+    my $self = shift;
+    my ($remove) = @_;
+
+    my $g = $self->{g};
+    my $grub_conf = $self->{grub_conf};
+
+    eval {
+        # Update any kernel console lines
+        my $deleted = 0;
+        foreach my $augpath
+            ($g->aug_match("/files$grub_conf/title/kernel/console"))
+        {
+            my $console = $g->aug_get($augpath);
+            if ($console =~ /\b(x|h)vc0\b/) {
+                if ($remove) {
+                    $g->aug_defvar("delconsole$deleted", $augpath);
+                    $deleted++;
+                } else {
+                    $console =~ s/\b(x|h)vc0\b/ttyS0/g;
+                    $g->aug_set($augpath, $console);
+                }
+            }
+
+            if ($remove && $console =~ /\bttyS0\b/) {
+                $g->aug_rm($augpath);
+            }
+        }
+
+        for (my $i = 0; $i < $deleted; $i++) {
+            $g->aug_rm('$delconsole'.$i);
+        }
+    };
+    augeas_error($g, $@) if ($@);
+}
+
+sub check
+{
+    my $self = shift;
+    my ($path) = @_;
+
+    my $g = $self->{g};
+    my $grub_conf = $self->{grub_conf};
+    my $grub_fs   = $self->{grub_fs};
+    $path =~ /^$grub_fs(.*)/
+       or v2vdie __x('Kernel {kernel} is not under grub tree {grub}',
+                     kernel => $path, grub => $grub_fs);
+    my $grub_path = $1;
+
+    # Nothing to do if the kernel already has a grub entry
+    return if $g->aug_match("/files$grub_conf/title/kernel".
+                            "[. = '$grub_path']") ne '';
+
+    my $kernel =
+        Sys::VirtConvert::Converter::SUSE::_inspect_linux_kernel($g, $path);
+    my $version = $kernel->{version};
+    my $grub_initrd = dirname($path)."/initrd-$version";
+
+    # No point in dying if /etc/SuSE-release can't be read
+    my ($title) = eval { $g->read_lines('/etc/SuSE-release') };
+    $title ||= 'Linux';
+
+    # Set title to just the release string
+    $title =~ s/ \(.*//;
+    $title .= " - $version";
+
+    # Doesn't matter if there's no default
+    my $default = eval { $g->aug_get("/files$grub_conf/default") };
+
+    if (defined($default)) {
+        $g->aug_defvar('template',
+             "/files$grub_conf/title[".($default + 1).']');
+    }
+
+    # If there's no default, take the first entry with a kernel
+    else {
+        my ($match) = $g->aug_match("/files$grub_conf/title/kernel");
+        die("No template kernel found in grub.") unless defined($match);
+
+        $match =~ s/\/kernel$//;
+        $g->aug_defvar('template', $match);
+    }
+
+    # Add a new title node at the end
+    $g->aug_defnode('new', "/files$grub_conf/title[last()+1]", $title);
+
+    # N.B. Don't change the order of root, kernel and initrd below, or the
+    # guest will not boot.
+
+    # Copy root from the template
+    $g->aug_set('$new/root', $g->aug_get('$template/root'));
+
+    # Set kernel and initrd to the new values
+    $g->aug_set('$new/kernel', $grub_path);
+    $g->aug_set('$new/initrd', $grub_initrd);
+
+    # Copy all kernel command-line arguments
+    foreach my $arg ($g->aug_match('$template/kernel/*')) {
+        # kernel arguments don't necessarily have values
+        my $val = eval { $g->aug_get($arg) };
+
+        $arg =~ /([^\/]*)$/;
+        $arg = $1;
+
+        if (defined($val)) {
+            $g->aug_set('$new/kernel/'.$arg, $val);
+        } else {
+            $g->aug_clear('$new/kernel/'.$arg);
+        }
+    }
+}
+
+sub write
+{
+    my $self = shift;
+    my ($path) = @_;
+
+    my $g = $self->{g};
+    my $grub_conf = $self->{grub_conf};
+    my $grub_fs   = $self->{grub_fs};
+
+    $path =~ /^$grub_fs(.*)/
+       or v2vdie __x('Kernel {kernel} is not under grub tree {grub}',
+                     kernel => $path, grub => $grub_fs);
+    my $grub_path = $1;
+
+    # Find the grub entry for the given kernel
+    eval {
+        my ($aug_path) =
+            $g->aug_match("/files$grub_conf/title/kernel[. = '$grub_path']");
+
+        v2vdie __x('Didn\'t find grub entry for kernel {kernel}',
+                   kernel => $path)
+            unless defined($aug_path);
+
+        $aug_path =~ m{/files$grub_conf/title(?:\[(\d+)\])?/kernel}
+            or die($aug_path);
+        my $grub_index = defined($1) ? $1 - 1 : 0;
+
+        $g->aug_set("/files$grub_conf/default", $grub_index);
+        $g->aug_save();
+    };
+    augeas_error($g, $@) if ($@);
+}
+
+# For Grub legacy, all we have to do is re-install grub in the correct place.
+sub convert_efi
+{
+    my $self = shift;
+    my ($device) = @_;
+
+    my $g = $self->{g};
+
+    $g->cp('/etc/grub.conf', '/boot/grub/grub.conf');
+    $g->ln_sf('/boot/grub/grub.conf', '/etc/grub.conf');
+
+    # Reload to pick up grub.conf in its new location
+    eval { $g->aug_load() };
+    augeas_error($g, $@) if ($@);
+
+    $g->command(['grub-install.unsupported', $device]);
+}
+
+
+# Methods for inspecting and manipulating grub2. Note that we don't actually
+# attempt to use grub2's configuration because it's utterly insane. Instead,
+# we reverse engineer the way the config is automatically generated and use
+# that instead.
+package Sys::VirtConvert::Converter::SUSE::Grub2;
+
+use Sys::VirtConvert::Util qw(:DEFAULT augeas_error);
+use Locale::TextDomain 'virt-v2v';
+
+ at Sys::VirtConvert::Converter::SUSE::Grub2::ISA =
+    qw(Sys::VirtConvert::Converter::SUSE::Grub);
+
+sub new
+{
+    my $class = shift;
+    my ($g, $root, $config) = @_;
+
+    my $self = {};
+    bless($self, $class);
+
+    $self->{g} = $g;
+    $self->{root} = $root;
+    $self->{config} = $config;
+
+    # Look for an EFI configuration
+    foreach my $cfg ($g->glob_expand('/boot/grub-efi/*/grub.cfg')) {
+        $self->check_efi();
+    }
+
+    # Check we have a grub2 configuration
+    if ($g->exists('/boot/grub2/grub.cfg')) {
+        $self->{cfg} = '/boot/grub2/grub.cfg';
+    }
+    die unless exists $self->{cfg};
+
+    return $self;
+}
+
+sub list_kernels
+{
+    my $self = shift;
+    my $g = $self->{g};
+
+    my @kernels;
+
+    # Start by adding the default kernel
+    my $default = $self->get_default_image;
+    push(@kernels, $default) if length($default) > 0;
+
+    # This is how the grub2 config generator enumerates kernels
+    foreach my $kernel ($g->glob_expand('/boot/kernel-*'),
+                        $g->glob_expand('/boot/vmlinuz-*'),
+                        $g->glob_expand('/vmlinuz-*'))
+    {
+        push(@kernels, $kernel)
+           unless $kernel =~ /\.(?:dpkg-.*|rpmsave|rpmnew)$/;
+    }
+
+    return @kernels;
+}
+
+sub update_console
+{
+    my $self = shift;
+    my ($remove) = @_;
+
+    my $g = $self->{g};
+
+    my $cmdline =
+        eval { $g->aug_get('/files/etc/default/grub/'.
+                          'GRUB_CMDLINE_LINUX_DEFAULT') };
+
+    if (defined($cmdline) && $cmdline =~ /\bconsole=(?:x|h)vc0\b/) {
+        if ($remove) {
+            $cmdline =~ s/\bconsole=(?:x|h)vc0\b\s*//g;
+        } else {
+            $cmdline =~ s/\bconsole=(?:x|h)vc0\b/console=ttyS0/g;
+        }
+
+        eval {
+            $g->aug_set('/files/etc/default/grub/'.
+                       'GRUB_CMDLINE_LINUX_DEFAULT', $cmdline);
+            $g->aug_save();
+        };
+        augeas_error($g, $@) if ($@);
+
+        # We need to re-generate the grub config if we've updated this file
+        $g->command(['grub2-mkconfig', '-o', $self->{cfg}]);
+    }
+}
+
+sub check
+{
+    # Nothing required for grub2
+}
+
+sub write
+{
+    my $self = shift;
+    my ($path) = @_;
+
+    my $g = $self->{g};
+
+    my $default = $self->get_default_image;
+
+    if ($default ne $path) {
+        eval {
+            $self->set_default_image($path);
+        };
+        if ($@) {
+            logmsg WARN, __x('Unable to set default kernel to {path}',
+                             path => $path);
+        }
+    }
+}
+
+# For grub2, we :
+#   Turn the EFI partition into a BIOS Boot Partition
+#   Remove the former EFI partition from fstab
+#   Install the non-EFI version of grub
+#   Install grub2 in the BIOS Boot Partition
+#   Regenerate grub.cfg
+sub convert_efi
+{
+    my $self = shift;
+    my ($device) = @_;
+
+    my $g = $self->{g};
+
+    # EFI systems boot using grub2-efi, and probably don't have the base grub2
+    # package installed.
+    Sys::VirtConvert::Convert::SUSE::_install_any
+        (undef, ['grub2'], undef, $g, $self->{root}, $self->{config}, $self)
+        or v2vdie __x('Failed to install non-EFI grub2');
+
+    # Relabel the EFI boot partition as a BIOS boot partition
+    $g->part_set_gpt_type($device, 1, '21686148-6449-6E6F-744E-656564454649');
+
+    # Delete the fstab entry for the EFI boot partition
+    eval {
+        foreach my $node ($g->aug_match("/files/etc/fstab/*[file = '/boot/efi']"))
+        {
+            $g->aug_rm($node);
+        }
+    };
+    augeas_error($g, $@) if $@;
+
+    # Install grub2 in the BIOS boot partition. This overwrites the previous
+    # contents of the EFI boot partition.
+    $g->command(['grub2-install', $device]);
+
+    # Re-generate the grub2 config, and put it in the correct place
+    $g->command(['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg']);
+}
+
+
+package Sys::VirtConvert::Converter::SUSE;
+
+use Sys::VirtConvert::Util qw(:DEFAULT augeas_error scsi_first_cmp);
+use Locale::TextDomain 'virt-v2v';
+
+=pod
+
+=head1 NAME
+
+Sys::VirtConvert::Converter::SUSE - Convert a SUSE based guest to run on KVM
+
+=head1 SYNOPSIS
+
+ use Sys::VirtConvert::Converter;
+
+ Sys::VirtConvert::Converter->convert($g, $root, $meta);
+
+=head1 DESCRIPTION
+
+Sys::VirtConvert::Converter::SUSE converts a SUSE based guest to run on KVM.
+
+=head1 METHODS
+
+=over
+
+=cut
+
+sub _is_sles_family
+{
+    my ($g, $root) = @_;
+
+    return ($g->inspect_get_type($root) eq 'linux') &&
+           ($g->inspect_get_distro($root) =~ /^(sles|suse-based)$/);
+}
+
+=item Sys::VirtConvert::Converter::SUSE->can_handle(g, root)
+
+Return 1 if Sys::VirtConvert::Converter::SUSE can convert the given guest
+
+=cut
+
+sub can_handle
+{
+    my $class = shift;
+
+    my ($g, $root) = @_;
+
+    return ($g->inspect_get_type($root) eq 'linux' &&
+            (_is_sles_family($g, $root) ||
+            $g->inspect_get_distro($root) eq 'opensuse'));
+}
+
+=item Sys::VirtConvert::Converter::SUSE->convert(g, root, config, meta,
+      options)
+
+Convert a SUSE based guest. Assume that can_handle has previously returned 1.
+
+=over
+
+=item g
+
+An initialised Sys::Guestfs handle
+
+=item root
+
+The root device of this operating system.
+
+=item config
+
+An initialised Sys::VirtConvert::Config
+
+=item meta
+
+Guest metadata.
+
+=item options
+
+A hashref of options which can influence the conversion
+
+=back
+
+=cut
+
+sub convert
+{
+    my $class = shift;
+
+    my ($g, $root, $config, $meta, $options) = @_;
+
+    _clean_rpmdb($g);
+    _init_selinux($g);
+    _init_augeas($g);
+
+    my $grub;
+    $grub = eval
+        { Sys::VirtConvert::Converter::SUSE::Grub2->new($g, $root, $config) };
+    $grub = eval
+        { Sys::VirtConvert::Converter::SUSE::GrubLegacy->new($g, $root) }
+        unless defined($grub);
+    v2vdie __('No grub configuration found') unless defined($grub);
+
+    # Un-configure HV specific attributes which don't require a direct
+    # replacement
+    _unconfigure_hv($g, $root);
+
+    # Try to install the virtio capability
+    my $virtio = _install_capability('virtio', $g, $root, $config, $meta, $grub);
+
+    # Get an appropriate kernel, or install one if none is available
+    my $kernel = _configure_kernel($virtio, $g, $root, $config, $meta, $grub);
+
+    # Install user custom packages
+    if (! _install_capability('user-custom', $g, $root, $config, $meta, $grub)) {
+        logmsg WARN, __('Failed to install user-custom packages');
+    }
+
+    # Configure the rest of the system
+    my $remove_serial_console = exists($options->{NO_SERIAL_CONSOLE});
+    _configure_console($g, $grub, $remove_serial_console);
+
+    _configure_display_driver($g, $root, $config, $meta, $grub);
+    _remap_block_devices($meta, $virtio, $g, $root, $grub);
+    _configure_kernel_modules($g, $virtio);
+    _configure_boot($kernel, $virtio, $g, $root, $grub);
+
+    my %guestcaps;
+
+    $guestcaps{block} = $virtio == 1 ? 'virtio' : 'ide';
+    $guestcaps{net}   = $virtio == 1 ? 'virtio' : 'e1000';
+    $guestcaps{arch}  = _get_os_arch($g, $root, $grub);
+    $guestcaps{acpi}  = _supports_acpi($g, $root, $guestcaps{arch});
+
+    return \%guestcaps;
+}
+
+sub _init_selinux
+{
+    my ($g) = @_;
+
+    # Assume SELinux isn't in use if load_policy isn't available
+    return if(!$g->exists('/usr/sbin/load_policy'));
+
+    # Actually loading the policy has proven to be problematic. We make whatever
+    # changes are necessary, and make the guest relabel on the next boot.
+    $g->touch('/.autorelabel');
+}
+
+sub _init_augeas
+{
+    my ($g) = @_;
+
+    # Initialise augeas
+    eval { $g->aug_init("/", 1) };
+    augeas_error($g, $@) if ($@);
+}
+
+# Execute an augeas modprobe query against all possible modprobe locations
+sub _aug_modprobe
+{
+    my ($g, $query) = @_;
+
+    my @paths;
+    for my $pattern ('/files/etc/conf.modules/alias',
+                     '/files/etc/modules.conf/alias',
+                     '/files/etc/modprobe.conf/alias',
+                     '/files/etc/modprobe.d/*/alias') {
+        push(@paths, $g->aug_match($pattern.'['.$query.']'));
+    }
+
+    return @paths;
+}
+
+# Check how new modules should be configured. Possibilities, in descending
+# order of preference, are:
+#   modprobe.d/
+#   modprobe.conf
+#   modules.conf
+#   conf.modules
+sub _discover_modpath
+{
+    my ($g) = @_;
+
+    my $modpath;
+
+    # Note that we're checking in ascending order of preference so that the last
+    # discovered method will be chosen
+
+    foreach my $file ('/etc/conf.modules', '/etc/modules.conf') {
+        if($g->exists($file)) {
+            $modpath = $file;
+        }
+    }
+
+    if($g->exists("/etc/modprobe.conf")) {
+        $modpath = "modprobe.conf";
+    }
+
+    # If the modprobe.d directory exists, create new entries in
+    # modprobe.d/virtv2v-added.conf
+    if($g->exists("/etc/modprobe.d")) {
+        $modpath = "modprobe.d/virtv2v-added.conf";
+    }
+
+    v2vdie __('Unable to find any valid modprobe configuration')
+        unless defined($modpath);
+
+    return $modpath;
+}
+
+sub _configure_kernel_modules
+{
+    my ($g, $virtio, $modpath) = @_;
+
+    # Make a note of whether we've added scsi_hostadapter
+    # For simplicity we always ensure this is set for virtio disks.
+    my $scsi_hostadapter = 0;
+
+    eval {
+        foreach my $path (_aug_modprobe($g, ". =~ regexp('eth[0-9]+')"))
+        {
+            $g->aug_set($path.'/modulename',
+                        $virtio == 1 ? 'virtio_net' : 'e1000');
+        }
+
+        my @paths = _aug_modprobe($g, ". =~ regexp('scsi_hostadapter.*')");
+        if ($virtio) {
+            # There's only 1 scsi controller in the converted guest.
+            # Convert only the first scsi_hostadapter entry to virtio.
+
+            # Note that we delete paths in reverse order. This means we don't
+            # have to worry about alias indices being changed.
+            while (@paths > 1) {
+                $g->aug_rm(pop(@paths));
+            }
+
+            if (@paths == 1) {
+                $g->aug_set(pop(@paths).'/modulename', 'virtio_blk');
+                $scsi_hostadapter = 1;
+            }
+        }
+
+        else {
+            # There's no scsi controller in an IDE guest
+            while (@paths) {
+                $g->aug_rm(pop(@paths));
+            }
+        }
+
+        # Display a warning about any leftover xen modules which we haven't
+        # converted
+        my @xen_modules = qw(xennet xen-vnif xenblk xen-vbd);
+        my $query = '('.join('|', @xen_modules).')';
+
+        foreach my $path (_aug_modprobe($g, "modulename =~ regexp('$query')")) {
+            my $device = $g->aug_get($path);
+            my $module = $g->aug_get($path.'/modulename');
+
+            logmsg WARN, __x("Don't know how to update ".
+                             '{device}, which loads the {module} module.',
+                             device => $device, module => $module);
+        }
+
+        # Add an explicit scsi_hostadapter if it wasn't there before
+        if ($virtio && !$scsi_hostadapter) {
+            my $modpath = _discover_modpath($g);
+
+            $g->aug_set("/files$modpath/alias[last()+1]",
+                        'scsi_hostadapter');
+            $g->aug_set("/files$modpath/alias[last()]/modulename",
+                        'virtio_blk');
+        }
+
+        $g->aug_save();
+    };
+    augeas_error($g, $@) if $@;
+}
+
+# Configure a console on ttyS0. Make sure existing console references use it.
+# N.B. Note that the RHEL 6 xen guest kernel presents a console device called
+# /dev/hvc0, where previous xen guest kernels presented /dev/xvc0. The regular
+# kernel running under KVM also presents a virtio console device called
+# /dev/hvc0, so ideally we would just leave it alone. However, RHEL 6 libvirt
+# doesn't yet support this device so we can't attach to it. We therefore use
+# /dev/ttyS0 for RHEL 6 anyway.
+#
+# If the target doesn't support a serial console, we want to remove all
+# references to it instead.
+sub _configure_console
+{
+    my ($g, $grub, $remove) = @_;
+
+    # Look for gettys which use xvc0 or hvc0
+    foreach my $augpath ($g->aug_match("/files/etc/inittab/*/process")) {
+        my $proc = $g->aug_get($augpath);
+
+        # If the process mentions xvc0, change it to ttyS0
+        if ($proc =~ /\b(x|h)vc0\b/) {
+            if ($remove) {
+                $g->aug_rm($augpath.'/..');
+            } else {
+                $proc =~ s/\b(x|h)vc0\b/ttyS0/g;
+                $g->aug_set($augpath, $proc);
+            }
+        }
+
+        if ($remove && $proc =~ /\bttyS0\b/) {
+            $g->aug_rm($augpath.'/..');
+        }
+    }
+
+    # Replace any mention of xvc0 or hvc0 in /etc/securetty with ttyS0
+    foreach my $augpath ($g->aug_match('/files/etc/securetty/*')) {
+        my $tty = $g->aug_get($augpath);
+
+        if($tty eq "xvc0" || $tty eq "hvc0") {
+            if ($remove) {
+                $g->aug_rm($augpath);
+            } else {
+                $g->aug_set($augpath, 'ttyS0');
+            }
+        }
+
+        if ($remove && $tty eq 'ttyS0') {
+            $g->aug_rm($augpath);
+        }
+    }
+
+    $grub->update_console($remove);
+
+    eval { $g->aug_save() };
+    augeas_error($g, $@) if ($@);
+}
+
+sub _configure_display_driver
+{
+    my ($g, $root, $config, $meta, $grub) = @_;
+
+    # Update the display driver if it exists
+    my $updated = 0;
+    eval {
+        my $xorg;
+
+        # Check which X configuration we have, and make augeas load it if
+        # necessary
+        if (! $g->exists('/etc/X11/xorg.conf') &&
+            $g->exists('/etc/X11/XF86Config'))
+        {
+            $g->aug_set('/augeas/load/Xorg/incl[last()+1]',
+                        '/etc/X11/XF86Config');
+
+            # Reload to pick up the new configuration
+            $g->aug_load();
+
+            $xorg = '/etc/X11/XF86Config';
+        } else {
+            $xorg = '/etc/X11/xorg.conf';
+        }
+
+        foreach my $path ($g->aug_match('/files'.$xorg.'/Device/Driver')) {
+            $g->aug_set($path, 'cirrus');
+            $updated = 1;
+        }
+
+        # Remove VendorName and BoardName if present
+        foreach my $path
+            ($g->aug_match('/files'.$xorg.'/Device/VendorName'),
+             $g->aug_match('/files'.$xorg.'/Device/BoardName'))
+        {
+            $g->aug_rm($path);
+        }
+
+        $g->aug_save();
+    };
+
+    # Propagate augeas errors
+    augeas_error($g, $@) if ($@);
+
+    # If we updated the X driver, check if X itself is actually installed. If it
+    # is, ensure the cirrus driver is installed.
+    if ($updated &&
+        ($g->exists('/usr/bin/X') || $g->exists('/usr/bin/X11/X')) &&
+        !_install_capability('cirrus', $g, $root, $config, $meta, $grub))
+    {
+        logmsg WARN, __('Display driver was updated to cirrus, but unable to '.
+                        'install cirrus driver. X may not function correctly');
+    }
+}
+
+# If the guest was shutdown uncleanly, it's possible that transient state was
+# left lying around in the rpm database. Given that nothing is using the
+# rpmdb at this point, it's safe to delete these files.
+sub _clean_rpmdb
+{
+    my $g = shift;
+
+    foreach my $f ($g->glob_expand('/var/lib/rpm/__db.00?')) {
+        $g->rm($f);
+    }
+}
+
+# Use various methods to try to work out what Linux kernel we've got.
+# Returns a hashref containing:
+#   path => path to kernel (same as $path variable passed in)
+#   package => base package name (eg. "kernel", "kernel-PAE")
+#   version => version string
+#   modules => array ref list of modules (paths to *.ko files)
+#   arch => architecture of the kernel
+sub _inspect_linux_kernel
+{
+    my ($g, $path) = @_;
+
+    my %kernel = ();
+
+    $kernel{path} = $path;
+
+    # If this is a packaged kernel, try to work out the name of the package
+    # which installed it. This lets us know what to install to replace it with,
+    # e.g. kernel, kernel-pae, kernel-smp, kernel-bigsmp, kernel-default
+    my $package = eval { $g->command(['rpm', '-qf', '--qf',
+                                      '%{NAME}', $path]) };
+    $kernel{package} = $package if defined($package);;
+
+    # Try to get the kernel version by running file against it
+    my $version;
+    my $filedesc = $g->file($path);
+    if($filedesc =~ /^$path: Linux kernel .*\bversion\s+(\S+)\b/) {
+        $version = $1;
+    }
+
+    # Sometimes file can't work out the kernel version, for example because it's
+    # a Xen PV kernel. In this case try to guess the version from the filename
+    else {
+        if($path =~ m{/boot/vmlinuz-(.*)}) {
+            $version = $1;
+
+            # Check /lib/modules/$version exists
+            if(!$g->is_dir("/lib/modules/$version")) {
+                warn __x("Didn't find modules directory {modules} for kernel ".
+                         "{path}\n", modules => "/lib/modules/$version",
+                         path => $path);
+
+                # Give up
+                return undef;
+            }
+        } else {
+            warn __x("Couldn't guess kernel version number from path for ".
+                     "kernel {path}\n", path => $path);
+
+            # Give up
+            return undef;
+        }
+    }
+
+    $kernel{version} = $version;
+
+    # List modules.
+    my @modules;
+    my $any_module;
+    my $prefix = "/lib/modules/$version";
+    foreach my $module ($g->find ($prefix)) {
+        if ($module =~ m{/([^/]+)\.(?:ko|o)$}) {
+            $any_module = "$prefix$module" unless defined $any_module;
+            push @modules, $1;
+        }
+    }
+
+    $kernel{modules} = \@modules;
+
+    # Determine kernel architecture by looking at the arch
+    # of any kernel module.
+    $kernel{arch} = $g->file_architecture ($any_module);
+
+    return \%kernel;
+}
+
+sub _configure_kernel
+{
+    my ($virtio, $g, $root, $config, $meta, $grub) = @_;
+
+    # Pick first appropriate kernel returned by list_kernels
+    my $boot_kernel;
+    foreach my $path ($grub->list_kernels()) {
+        my $kernel = _inspect_linux_kernel($g, $path);
+        my $version = $kernel->{version};
+
+        # Skip foreign kernels
+        next if _is_hv_kernel($g, $version);
+
+        # If we're configuring virtio, check this kernel supports it
+        next if ($virtio && !_supports_virtio($version, $g));
+
+        $boot_kernel = $version;
+        last;
+    }
+
+    # Warn if there is no installed virtio capable kernel
+    logmsg NOTICE, __x('virtio capable guest, but no virtio kernel found.')
+        if ($virtio && !defined($boot_kernel));
+
+    # If none of the installed kernels are appropriate, install a new one
+    if(!defined($boot_kernel)) {
+        my ($kernel_pkg, $kernel_arch, undef) =
+           _discover_kernel($g, $root, $grub);
+
+        # If the guest is using a Xen PV kernel, choose an appropriate
+        # normal kernel replacement
+        if ($kernel_pkg eq "kernel-xen" || $kernel_pkg eq "kernel-xen-base") {
+            $kernel_pkg = _get_replacement_kernel_name($g, $root,
+                                                       $kernel_arch, $meta);
+
+            # Check there isn't already one installed
+            my ($kernel) = _get_installed("$kernel_pkg.$kernel_arch", $g);
+            $boot_kernel = $kernel->[1].'-'.$kernel->[2].'.'.$kernel_arch
+                if defined($kernel);
+        }
+
+        if (!defined($boot_kernel)) {
+            # List of kernels before the new kernel installation
+            my @k_before = $g->glob_expand('/boot/vmlinuz-*');
+
+            if (_install_any([[$kernel_pkg, $kernel_arch]], undef, undef,
+                             $g, $root, $config, $grub))
+            {
+                # Figure out which kernel has just been installed
+                my $version;
+                foreach my $k ($g->glob_expand('/boot/vmlinuz-*')) {
+                    if (!grep(/^$k$/, @k_before)) {
+                        # Check which directory in /lib/modules the kernel rpm
+                        # creates
+                        foreach my $file
+                                ($g->command_lines(['rpm', '-qlf', $k]))
+                        {
+                            next unless ($file =~ m{^/lib/modules/([^/]+)$});
+
+                            if ($g->is_dir("/lib/modules/$1")) {
+                                $boot_kernel = $1;
+                                last;
+                            }
+                        }
+
+                        last if defined($boot_kernel);
+                    }
+                }
+
+                v2vdie __x('Couldn\'t determine version of installed kernel')
+                    unless defined($boot_kernel);
+            } else {
+                v2vdie __x('Failed to find a {name} package to install',
+                           name => "$kernel_pkg");
+            }
+        }
+    }
+
+    # Check we have a bootable kernel.
+    v2vdie __('No bootable kernels installed, and no replacement '.
+              "is available.\nUnable to continue.")
+        unless defined($boot_kernel);
+
+    # Ensure DEFAULTKERNEL is set to boot kernel package name
+    # It's not fatal if this rpm command fails
+    my ($kernel_pkg) =
+        eval { $g->command_lines(['rpm', '-qf', "/lib/modules/$boot_kernel",
+                                         '--qf', '%{NAME}\n']) };
+    if (defined($kernel_pkg) && $g->exists('/etc/sysconfig/kernel')) {
+        eval {
+            foreach my $path ($g->aug_match('/files/etc/sysconfig/kernel'.
+                                            '/DEFAULTKERNEL/value'))
+            {
+                $g->aug_set($path, $kernel_pkg);
+            }
+
+            $g->aug_save();
+        };
+        # Propagate augeas errors
+        augeas_error($g, $@) if ($@);
+    }
+
+    return $boot_kernel;
+}
+
+sub _configure_boot
+{
+    my ($kernel, $virtio, $g, $root, $grub) = @_;
+
+    if($virtio) {
+        # The order of modules here is deliberately the same as the order
+        # specified in the postinstall script of kmod-virtio in RHEL 3. The
+        # reason is that the probing order determines the major number of vdX
+        # block devices. If we change it, RHEL 3 KVM guests won't boot.
+        _prepare_bootable($g, $root, $grub, $kernel, "virtio", "virtio_ring",
+                                                     "virtio_blk", "virtio_net",
+                                                     "virtio_pci");
+    } else {
+        _prepare_bootable($g, $root, $grub, $kernel, "sym53c8xx");
+    }
+}
+
+# Get the target architecture from the default boot kernel
+sub _get_os_arch
+{
+    my ($g, $root, $grub) = @_;
+
+    # Get the arch of the default kernel
+    my @kernels = $grub->list_kernels();
+    my $path = $kernels[0] if @kernels > 0;
+    my $kernel = _inspect_linux_kernel($g, $path) if defined($path);
+    my $arch = $kernel->{arch} if defined($kernel);
+
+    # Use the libguestfs-detected arch if the above failed
+    $arch = $g->inspect_get_arch($root) unless defined($arch);
+
+    # Default to x86_64 if we still didn't find an architecture
+    return 'x86_64' unless defined($arch);
+
+    # We want an i586 guest for i[346]86
+    return 'i586' if($arch =~ /^i[346]86$/);
+
+    return $arch;
+}
+
+# Determine if a specific kernel is hypervisor-specific
+sub _is_hv_kernel
+{
+    my ($g, $version) = @_;
+
+    # Xen PV kernels can be distinguished from other kernels by their inclusion
+    # of the xennet driver
+    foreach my $entry ($g->find("/lib/modules/$version/")) {
+        return 1 if $entry =~ /(^|\/)xennet\.k?o$/;
+    }
+
+    return 0;
+}
+
+sub _remove_applications
+{
+    my ($g, @apps) = @_;
+
+    # Nothing to do if we were given an empty list
+    return if scalar(@apps) == 0;
+
+    $g->command(['rpm', '-e', @apps]);
+
+    # Make augeas reload in case the removal changed anything
+    eval { $g->aug_load() };
+    augeas_error($g, $@) if ($@);
+}
+
+sub _get_application_owner
+{
+    my ($file, $g) = @_;
+
+    return $g->command(['rpm', '-qf', $file]);
+}
+
+sub _unconfigure_hv
+{
+    my ($g, $root) = @_;
+
+    my @apps = $g->inspect_list_applications($root);
+
+    _unconfigure_xen($g, \@apps);
+    _unconfigure_vbox($g, \@apps);
+    _unconfigure_vmware($g, \@apps);
+    _unconfigure_citrix($g, \@apps);
+}
+
+# Unconfigure Xen specific guest modifications
+sub _unconfigure_xen
+{
+    my ($g, $apps) = @_;
+
+    # Remove xen modules from INITRD_MODULES and DOMU_INITRD_MODULES variables
+    my $sysconfig = '/etc/sysconfig/kernel';
+    my @variables = qw(INITRD_MODULES DOMU_INITRD_MODULES);
+    my @xen_modules = qw(xennet xen-vnif xenblk xen-vbd);
+    my $modified;
+
+    foreach my $var (@variables) {
+        foreach my $xen_mod (@xen_modules) {
+            foreach my $entry
+                ($g->aug_match("/files$sysconfig/$var/value[. = '$xen_mod']"))
+            {
+                $g->aug_rm("$entry");
+                $modified = 1;
+            }
+        }
+    }
+    $g->aug_save if ($modified == 1);
+}
+
+# Unconfigure VirtualBox specific guest modifications
+sub _unconfigure_vbox
+{
+    my ($g, $apps) = @_;
+
+    # Uninstall VirtualBox Guest Additions
+    my @remove;
+    foreach my $app (@$apps) {
+        my $name = $app->{app_name};
+
+        if ($name eq "virtualbox-guest-additions") {
+            push(@remove, $name);
+        }
+    }
+    _remove_applications($g, @remove);
+
+    # VirtualBox Guest Additions may have been installed from tarball, in which
+    # case the above won't detect it. Look for the uninstall tool, and run it
+    # if it's present.
+    #
+    # Note that it's important we do this early in the conversion process, as
+    # this uninstallation script naively overwrites configuration files with
+    # versions it cached prior to installation.
+    my $vboxconfig = '/var/lib/VBoxGuestAdditions/config';
+    if ($g->exists($vboxconfig)) {
+        my $vboxuninstall;
+        foreach (split /\n/, $g->cat($vboxconfig)) {
+            if ($_ =~ /^INSTALL_DIR=(.*$)/) {
+                $vboxuninstall = $1 . '/uninstall.sh';
+            }
+        }
+        if ($g->exists($vboxuninstall)) {
+            eval { $g->command([$vboxuninstall]) };
+            logmsg WARN, __x('VirtualBox Guest Additions were detected, but '.
+                             'uninstallation failed. The error message was: '.
+                             '{error}', error => $@) if $@;
+
+            # Reload augeas to detect changes made by vbox tools uninstallation
+            eval { $g->aug_load() };
+            augeas_error($g, $@) if $@;
+        }
+    }
+}
+
+# Unconfigure VMware specific guest modifications
+sub _unconfigure_vmware
+{
+    my ($g, $apps) = @_;
+
+    # YUM does not ship with SUSE, but is available through OSS repos
+    # Look for any configured vmware yum repos, and disable them
+    foreach my $repo ($g->aug_match(
+        '/files/etc/yum.repos.d/*/*'.
+        '[baseurl =~ regexp(\'https?://([^/]+\.)?vmware\.com/.*\')]'))
+    {
+        eval {
+            $g->aug_set($repo.'/enabled', 0);
+            $g->aug_save();
+        };
+        augeas_error($g, $@) if ($@);
+    }
+
+    # Uninstall VMwareTools
+    my @remove;
+    my @libraries;
+    foreach my $app (@$apps) {
+        my $name = $app->{app_name};
+
+        if ($name =~ /^vmware-tools-/) {
+            if ($name =~ /^vmware-tools-libraries-/) {
+                push(@libraries, $name);
+            } else {
+                push (@remove, $name);
+            }
+        }
+        elsif ($name eq "VMwareTools" || $name =~ /^(?:kmod-)?vmware-tools-/) {
+            push(@remove, $name);
+        }
+    }
+
+    # VMware tools includes 'libraries' packages which provide custom versions
+    # of core functionality. We need to install non-custom versions of
+    # everything provided by these packages before attempting to uninstall
+    # them, or we'll hit dependency issues
+    if (@libraries > 0) {
+        # We only support removal of these libraries packages on systems which
+        # use yum.
+        # FIXME zypper can't resolve dependencies (yet). Leave the yum check
+        # in place for now.
+        if ($g->exists('/usr/bin/yum')) {
+            _net_run($g, sub {
+                foreach my $library (@libraries) {
+                    eval {
+                        my @provides = $g->command_lines
+                                (['rpm', '-q', '--provides', $library]);
+
+                        # The packages also explicitly provide themselves.
+                        # Filter this out.
+                        @provides = grep {$_ !~ /$library/}
+
+                        # Trim whitespace
+                                    map { s/^\s*(\S+)\s*$/$1/; $_ } @provides;
+
+                        # Install the dependencies with yum. We use yum
+                        # explicitly here, as up2date wouldn't work anyway and
+                        # local install is impractical due to the large number
+                        # of required dependencies out of our control.
+                        my %alts;
+                        foreach my $alt ($g->command_lines
+                                   (['yum', '-q', 'resolvedep', @provides]))
+                        {
+                            $alts{$alt} = 1;
+                        }
+
+                        my @replacements = keys(%alts);
+                        $g->command(['yum', 'install', '-y', @replacements])
+                            if @replacements > 0;
+
+                        push(@remove, $library);
+                    };
+                    logmsg WARN, __x('Failed to install replacement '.
+                                     'dependencies for {lib}. Package will '.
+                                     'not be uninstalled. Error was: {error}',
+                                     lib => $library, error => $@) if $@;
+                }
+            });
+        }
+    }
+
+    _remove_applications($g, @remove);
+
+    # VMwareTools may have been installed from tarball, in which case the above
+    # won't detect it. Look for the uninstall tool, and run it if it's present.
+    #
+    # Note that it's important we do this early in the conversion process, as
+    # this uninstallation script naively overwrites configuration files with
+    # versions it cached prior to installation.
+    my $vmwaretools = '/usr/bin/vmware-uninstall-tools.pl';
+    if ($g->exists($vmwaretools)) {
+        eval { $g->command([$vmwaretools]) };
+        logmsg WARN, __x('VMware Tools was detected, but uninstallation '.
+                         'failed. The error message was: {error}',
+                         error => $@) if $@;
+
+        # Reload augeas to detect changes made by vmware tools uninstallation
+        eval { $g->aug_load() };
+        augeas_error($g, $@) if $@;
+    }
+}
+
+sub _unconfigure_citrix
+{
+    my ($g, $apps) = @_;
+
+    # Look for xe-guest-utilities*
+    my @remove;
+    foreach my $app (@$apps) {
+        my $name = $app->{app_name};
+
+        if($name =~ /^xe-guest-utilities(-.*)?$/) {
+            push(@remove, $name);
+        }
+    }
+    _remove_applications($g, @remove);
+
+    # Installing these guest utilities automatically unconfigures ttys in
+    # /etc/inittab if the system uses it. We need to put them back.
+    if (scalar(@remove) > 0) {
+        eval {
+            my $updated = 0;
+            for my $commentp ($g->aug_match('/files/etc/inittab/#comment')) {
+                my $comment = $g->aug_get($commentp);
+
+                # The entries in question are named 1-6, and will normally be
+                # active in runlevels 2-5. They will be gettys. We could be
+                # extremely prescriptive here, but allow for a reasonable amount
+                # of variation just in case.
+                next unless $comment =~ /^([1-6]):([2-5]+):respawn:(.*)/;
+
+                my $name = $1;
+                my $runlevels = $2;
+                my $process = $3;
+
+                next unless $process =~ /getty/;
+
+                # Create a new entry immediately after the comment
+                $g->aug_insert($commentp, $name, 0);
+                $g->aug_set("/files/etc/inittab/$name/runlevels", $runlevels);
+                $g->aug_set("/files/etc/inittab/$name/action", 'respawn');
+                $g->aug_set("/files/etc/inittab/$name/process", $process);
+
+                # Create a variable to point to the comment node so we can
+                # delete it later. If we deleted it here it would invalidate
+                # subsquent comment paths returned by aug_match.
+                $g->aug_defvar("delete$updated", $commentp);
+
+                $updated++;
+            }
+
+            # Delete all the comments
+            my $i = 0;
+            while ($i < $updated) {
+                $g->aug_rm("\$delete$i");
+                $i++;
+            }
+
+            $g->aug_save();
+        };
+        augeas_error($g, $@) if ($@);
+    }
+}
+
+sub _install_capability
+{
+    my ($name, $g, $root, $config, $meta, $grub) = @_;
+
+    my $cap = eval { $config->match_capability($g, $root, $name) };
+    if ($@) {
+        warn($@);
+        return 0;
+    }
+
+    if (!defined($cap)) {
+        logmsg WARN, __x('{name} capability not found in configuration',
+                         name => $name);
+        return 0;
+    }
+
+    my @install;
+    my @update;
+    my $kernel;
+    foreach my $name (keys(%$cap)) {
+        my $props = $cap->{$name};
+        my $ifinstalled = $props->{ifinstalled};
+
+        # Parse epoch, version and release from minversion
+        my ($min_epoch, $min_version, $min_release);
+        if (exists($props->{minversion})) {
+            eval {
+                ($min_epoch, $min_version, $min_release) =
+                    _parse_evr($props->{minversion});
+            };
+            v2vdie __x('Unrecognised format for {field} in config: '.
+                       '{value}. {field} must be in the format '.
+                       '[epoch:]version[-release].',
+                       field => 'minversion', value => $props->{minversion})
+                if $@;
+        }
+
+        # Kernels are special
+        if ($name eq 'kernel') {
+            my ($kernel_pkg, $kernel_arch, $kernel_rpmver) =
+                _discover_kernel($g, $root, $grub);
+
+            # If we didn't establish a kernel version, assume we have to update
+            # it.
+            if (!defined($kernel_rpmver)) {
+                $kernel = [$kernel_pkg, $kernel_arch];
+            }
+
+            else {
+                my ($kernel_epoch, $kernel_ver, $kernel_release) =
+                    eval { _parse_evr($kernel_rpmver) };
+                if ($@) {
+                    # Don't die here, just make best effort to do a version
+                    # comparison by directly comparing the full strings
+                    $kernel_epoch = undef;
+                    $kernel_ver = $kernel_rpmver;
+                    $kernel_release = undef;
+
+                    $min_epoch = undef;
+                    $min_version = $props->{minversion};
+                    $min_release = undef;
+                }
+
+                # If the guest is using a Xen PV kernel, choose an appropriate
+                # normal kernel replacement
+                if ($kernel_pkg eq "kernel-xen" || $kernel_pkg eq "kernel-xen-base")
+                {
+                    $kernel_pkg =
+                        _get_replacement_kernel_name($g, $root, $kernel_arch,
+                                                     $meta);
+
+                    # Check if we've already got an appropriate kernel
+                    my ($inst) =
+                        _get_installed("$kernel_pkg.$kernel_arch", $g);
+
+                    if (!defined($inst) ||
+                        (defined($min_version) &&
+                         _evr_cmp($inst->[0], $inst->[1], $inst->[2],
+                                  $min_epoch, $min_version, $min_release) < 0))
+                    {
+                        # filter out xen from release field
+                        if (defined($kernel_release) &&
+                            $kernel_release =~ /^(\S+?)(xen)?$/)
+                        {
+                            $kernel_release = $1;
+                        }
+
+                        # If the guest kernel is new enough, but PV, try to
+                        # replace it with an equivalent version FV kernel
+                        if (!defined($min_version) ||
+                            _evr_cmp($kernel_epoch, $kernel_ver,
+                                     $kernel_release,
+                                     $min_epoch, $min_version,
+                                     $min_release) >= 0)
+                        {
+                            $kernel = [[$kernel_pkg, $kernel_arch,
+                                       $kernel_epoch, $kernel_ver,
+                                       $kernel_release]];
+                        }
+
+                        # Otherwise, just grab the latest
+                        else {
+                            $kernel = [[$kernel_pkg, $kernel_arch]];
+                        }
+                    }
+                }
+
+                # If the kernel is too old, grab the latest replacement
+                elsif (defined($min_version) &&
+                       _evr_cmp($kernel_epoch, $kernel_ver, $kernel_release,
+                                $min_epoch, $min_version, $min_release) < 0)
+                {
+                    $kernel = [[$kernel_pkg, $kernel_arch]];
+                }
+            }
+        }
+
+        else {
+            my @installed = _get_installed($name, $g);
+
+            # Ignore an 'ifinstalled' dep if it's not currently installed
+            next if (@installed == 0 && $ifinstalled);
+
+            # Ok if any version is installed and no minversion was specified
+            next if (@installed > 0 && !defined($min_version));
+
+            if (defined($min_version)) {
+                # Check if any installed version meets the minimum version
+                my $found = 0;
+                foreach my $app (@installed) {
+                    my ($epoch, $version, $release) = @$app;
+
+                    if (_evr_cmp($app->[0], $app->[1], $app->[2],
+                                 $min_epoch, $min_version, $min_release) >= 0) {
+                        $found = 1;
+                        last;
+                    }
+                }
+
+                # Install the latest available version of the dep if it wasn't
+                # found. NOTE - No version requirements are enforced.
+                if (!$found) {
+                    if (@installed == 0) {
+                        push(@install, [$name]);
+                    } else {
+                        push(@update, [$name]);
+                    }
+                }
+            } else {
+                push(@install, [$name]);
+            }
+        }
+    }
+
+    # Capability is already installed
+    if (!defined($kernel) && @install == 0 && @update == 0) {
+        return 1;
+    }
+
+    my $success = _install_any($kernel, \@install, \@update,
+                               $g, $root, $config, $grub);
+
+    return $success;
+}
+
+
+sub _net_run {
+    my ($g, $sub) = @_;
+
+    my $resolv_bak = $g->exists('/etc/resolv.conf');
+    $g->mv('/etc/resolv.conf', '/etc/resolv.conf.v2vtmp') if ($resolv_bak);
+
+    # XXX We should get the nameserver from the appliance here, but
+    # there's no current api other than debug to do this.
+    $g->write_file('/etc/resolv.conf', "nameserver 169.254.2.3", 0);
+
+    eval &$sub();
+    my $err = $@;
+
+    $g->mv('/etc/resolv.conf.v2vtmp', '/etc/resolv.conf') if ($resolv_bak);
+
+    die $err if $err;
+}
+
+sub _install_any
+{
+    my ($kernel, $install, $update, $g, $root, $config, $grub) = @_;
+
+    # If we're installing a kernel, check which kernels are there first
+    my @k_before = $g->glob_expand('/boot/vmlinuz-*') if defined($kernel);
+
+    # If a SLES11 kernel is being added, add -base kernel
+    if (defined($kernel)) {
+        if (_is_sles_family($g, $root) &&
+           $g->inspect_get_major_version($root) == 11) {
+            $kernel->[1][0] = $kernel->[0][0].'-base';
+            for my $count (1..4) {
+                if (defined($kernel->[0][$count])) {
+                    $kernel->[1][$count] = $kernel->[0][$count];
+                }
+            }
+        }
+    }
+
+    # Due to bnc#836521, root can be set to (hd*), instead of (hd*,*)
+    # For the time being, workaround the problem
+    my $pbl_fix = _modify_perlBootloader($g) if defined($kernel);
+
+    my $success = 0;
+    _net_run($g, sub {
+        eval {
+            # Try to fetch these dependencies using the guest's native update
+            # tool
+            $success = _install_zypper($kernel, $install, $update, $g);
+
+            # Fall back to local config if the above didn't work
+            $success = _install_config($kernel, $install, $update,
+                                       $g, $root, $config)
+                unless ($success);
+        };
+        warn($@) if $@;
+    });
+
+    # Restore the previous bootloader file if previously fixed (to ensure
+    # future updates don't complain
+    _restore_perlBootloader($g) if ($pbl_fix == 1);
+
+    # Make augeas reload to pick up any altered configuration
+    eval { $g->aug_load() };
+    augeas_error($g, $@) if ($@);
+
+    # Check that the kernel we installed has a grub entry.
+    if (defined($kernel)) {
+        foreach my $k ($g->glob_expand('/boot/vmlinuz-*')) {
+            if (!grep(/^$k$/, @k_before)) {
+                $grub->check($k);
+                last;
+            }
+        }
+    }
+
+    return $success;
+}
+
+sub _install_zypper
+{
+    my ($kernel, $install, $update, $g) = @_;
+
+    # Check this system has zypper
+    return 0 unless ($g->exists('/usr/bin/zypper'));
+
+    # Install or update the kernel?
+    # If it isn't installed (because we're replacing a PV kernel), we need to
+    # install
+    # If we're installing a specific version, we need to install
+    # If the kernel package we're installing is already installed and we're
+    # just upgrading to the latest version, we need to update
+    if (defined($kernel)) {
+        my @installed = _get_installed($kernel->[0][0], $g);
+
+        # Don't modify the contents of $install and $update in case we fall
+        # through and they're reused in another function
+        if (@installed == 0 || defined($kernel->[0][2])) {
+            my @tmp = defined($install) ? @$install : ();
+            for my $count (0..1) {
+                if (defined($kernel->[$count])) {
+                    push(@tmp, $kernel->[$count]);
+                }
+            }
+            $install = \@tmp;
+        } else {
+            my @tmp = defined($update) ? @$update : ();
+            for my $count (0..1) {
+                if (defined($kernel->[$count])) {
+                    push(@tmp, $kernel->[$count]);
+                }
+            }
+            $update = \@tmp;
+        }
+    }
+
+    my $success = 1;
+    # Error when installing: "No provider of 'pkg' found."
+    # (Not an) Error when updating: "Package 'pkg' is not available in your
+    # repositories. Cannot reinstall, upgrade, or downgrade."
+    ZYPPER: foreach my $task (
+        [ "install", $install, qr/(^No package|already installed)/ ],
+        [ "update", $update, qr/(^No Packages|not available)/ ]
+    ) {
+        my ($action, $list, $failure) = @$task;
+
+        # Build a list of packages to install
+        my @pkgs;
+        foreach my $entry (@$list) {
+            next unless (defined($entry));
+
+            # zypper doesn't need arch or epoch
+            my ($name, undef, undef, $version, $release) = @$entry;
+
+            # Construct n-v-r
+            my $pkg = $name;
+            $pkg .= "-$version" if (defined($version));
+            $pkg .= "-$release" if (defined($release));
+
+            push(@pkgs, "$pkg");
+        }
+
+        if (@pkgs) {
+            logmsg NOTICE, __x('Installing the following packages '.
+                           'through zypper:');
+            foreach my $pkg (@pkgs) {
+                logmsg NOTICE, __x("   $pkg");
+            }
+            my @output =
+                eval { $g->command(['/usr/bin/zypper', '-n', $action,
+                     @pkgs]) };
+            if ($@) {
+                # Catch 'No provider' errors and only issue a warning, as
+                # an install from virt-v2v repo will be attempted next.
+                if ($@ =~ /No provider/) {
+                    logmsg WARN, __x('No provider of {package} found.',
+                                   package => @pkgs);
+                } else {
+                    logmsg WARN, __x('Failed to install packages. '.
+                                   'Error was: {error}', error => $@);
+                }
+                $success = 0;
+                last ZYPPER;
+            }
+            foreach my $line (@output) {
+                # Don't report an error or results if package is already installed
+                # or not found in a repo
+                if ($line =~ /$failure/) {
+                    $success = 0;
+                    last ZYPPER;
+                }
+            }
+        }
+    }
+
+    return $success;
+}
+
+sub _install_config
+{
+    my ($kernel_naevr, $install, $update, $g, $root, $config) = @_;
+
+    my ($kernel, $user);
+    if (defined($kernel_naevr)) {
+        foreach my $kernel_entry (@$kernel_naevr) {
+            my ($kernel_pkg, $kernel_arch) = @$kernel_entry;
+            my ($tmp_kernel, $tmp_user);
+            ($tmp_kernel, $tmp_user) =
+                $config->match_app($g, $root, $kernel_pkg, $kernel_arch);
+            push(@$kernel, $tmp_kernel);
+            foreach my $tmp_app (@$tmp_user) {
+                if (defined($tmp_app)) {
+                    push(@$user, $tmp_app);
+                }
+            }
+        }
+    } else {
+        $user = [];
+    }
+
+    foreach my $pkg (@$install, @$update) {
+        push(@$user, $pkg->[0]);
+    }
+
+    my @missing;
+    if (defined($kernel)) {
+        foreach my $pkg (@$kernel) {
+            my $transfer_path = $config->get_transfer_path($pkg);
+            if (!defined($transfer_path) || !$g->exists($transfer_path)) {
+                push(@missing, $pkg);
+            }
+        }
+    }
+
+    my @user_paths = _get_deppaths($g, $root, $config,
+                                   \@missing, $g->inspect_get_arch($root), @$user);
+
+    # We can't proceed if there are any files missing
+    v2vdie __x('Installation failed because the following '.
+               'files referenced in the configuration file are '.
+               'required, but missing: {list}',
+               list => join(' ', @missing)) if scalar(@missing) > 0;
+    # Install any non-kernel requirements
+    _install_rpms($g, $config, 1, @user_paths);
+
+    if (defined($kernel)) {
+        _install_rpms($g, $config, 0, (@$kernel));
+    }
+
+    return 1;
+}
+
+# Install a set of rpms
+sub _install_rpms
+{
+    local $_;
+    my ($g, $config, $update, @rpms) = @_;
+
+    # Nothing to do if we got an empty set
+    return if(scalar(@rpms) == 0);
+
+    # All paths are relative to the transfer mount. Need to make them absolute.
+    # No need to check get_transfer_path here as all paths have been previously
+    # checked
+    @rpms = map { $_ = $config->get_transfer_path($_) } @rpms;
+
+    logmsg NOTICE, __x('Falling back to local virt-v2v repo:');
+    foreach my $pkg (@rpms) {
+        (my $pkgname = $pkg) =~ s/(.*\/)//;
+        logmsg NOTICE, __x("   $pkgname");
+    }
+
+    eval { $g->command(['rpm', $update == 1 ? '-U' : '-i', @rpms]) };
+    if ($@) {
+        my $error = $@;
+        # Try to isolate error to a meaningful string
+        $error =~ /^(command.*error:)\s+(.*:)\s+(.*)\s(at \/usr\/.*)/;
+        logmsg WARN, __x('Failed to install packages. '.
+                         'Error was: {error}', error => "$2 $3");
+}
+
+    # Reload augeas in case the rpm installation changed anything
+    eval { $g->aug_load() };
+    augeas_error($g, $@) if($@);
+}
+
+# Return a list of dependency paths which need to be installed for the given
+# apps
+sub _get_deppaths
+{
+    my ($g, $root, $config, $missing, $arch, @apps) = @_;
+
+    my %required;
+    foreach my $app (@apps) {
+        my ($path, $deps) = $config->match_app($g, $root, $app, $arch);
+
+        my $transfer_path = $config->get_transfer_path($path);
+        my $exists = defined($transfer_path) && $g->exists($transfer_path);
+
+        if (!$exists) {
+            push(@$missing, $path);
+        }
+
+        if (!$exists || !_newer_installed($transfer_path, $g, $config)) {
+            $required{$path} = 1;
+
+            foreach my $deppath (_get_deppaths($g, $root, $config,
+                                               $missing, $arch, @$deps))
+            {
+                $required{$deppath} = 1;
+            }
+        }
+
+    }
+
+    return keys(%required);
+}
+
+# Return 1 if the requested rpm, or a newer version, is installed
+# Return 0 otherwise
+sub _newer_installed
+{
+    my ($rpm, $g, $config) = @_;
+
+    my ($name, $epoch, $version, $release, $arch) =
+        _get_nevra($rpm, $g, $config);
+
+    my @installed = _get_installed("$name.$arch", $g);
+
+    # Search installed rpms matching <name>.<arch>
+    foreach my $pkg (@installed) {
+        next if _evr_cmp($pkg->[0], $pkg->[1], $pkg->[2],
+                         $epoch, $version, $release) < 0;
+        return 1;
+    }
+
+    return 0;
+}
+
+sub _get_nevra
+{
+    my ($rpm, $g, $config) = @_;
+
+    # Get NEVRA for the rpm to be installed
+    my $nevra = $g->command(['rpm', '-qp', '--qf',
+                             '%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}',
+                             $rpm]);
+
+    $nevra =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$/
+        or die("Unexpected return from rpm command: $nevra");
+    my ($name, $epoch, $version, $release, $arch) = ($1, $2, $3, $4, $5);
+
+    # Ensure epoch is always numeric
+    $epoch = 0 if('(none)' eq $epoch);
+
+    return ($name, $epoch, $version, $release, $arch);
+}
+
+# Inspect the guest to work out what kernel package is in use
+# Returns ($kernel_pkg, $kernel_arch)
+sub _discover_kernel
+{
+    my ($g, $root, $grub) = @_;
+
+    # Get a current bootable kernel, preferrably the default
+    my $kernel_pkg;
+    my $kernel_arch;
+    my $kernel_ver;
+
+    foreach my $path ($grub->list_kernels()) {
+        my $kernel = _inspect_linux_kernel($g, $path);
+
+        # Check its architecture is known
+        $kernel_arch = $kernel->{arch};
+        next unless (defined($kernel_arch));
+
+        # Get the kernel package name
+        $kernel_pkg = $kernel->{package};
+
+        # Get the kernel package version
+        $kernel_ver = $kernel->{version};
+
+        last;
+    }
+
+    # Default to 'kernel' if package name wasn't discovered
+    $kernel_pkg = "kernel" if (!defined($kernel_pkg));
+
+    # Default the kernel architecture to the userspace architecture if it wasn't
+    # directly detected
+    $kernel_arch = $g->inspect_get_arch($root) unless defined($kernel_arch);
+
+    # The supported 32 bit kernel architecture is i586.
+    $kernel_arch = 'i586' if ('i386' eq $kernel_arch);
+
+    return ($kernel_pkg, $kernel_arch, $kernel_ver);
+}
+
+sub _get_replacement_kernel_name
+{
+    my ($g, $root, $arch, $meta) = @_;
+
+    # Make an informed choice about a replacement kernel for distros we know
+    # about
+
+    # SLES 11
+    if (_is_sles_family($g, $root) &&
+        $g->inspect_get_major_version($root) eq '11') {
+        if ($arch eq 'i586') {
+            # If the guest has > 10G RAM, give it a pae kernel
+            if ($meta->{memory} > 10 * 1024 * 1024 * 1024) {
+                return 'kernel-pae';
+            }
+
+            else {
+                return 'kernel-default';
+            }
+       }
+
+        # There's only 1 kernel package on SLES 11 x86_64
+        else {
+            return 'kernel-default';
+        }
+    }
+
+    # SLES 10
+    elsif (_is_sles_family($g, $root) &&
+        $g->inspect_get_major_version($root) eq '10') {
+        if ($arch eq 'i586') {
+            # If the guest has > 10G RAM, give it a bigsmp kernel
+            if ($meta->{memory} > 10 * 1024 * 1024 * 1024) {
+                return 'kernel-bigsmp';
+            }
+
+            # SMP kernel for guests with >1 CPU
+            elsif ($meta->{cpus} > 1) {
+                return 'kernel-smp';
+            }
+
+            else {
+                return 'kernel-default';
+            }
+        }
+
+        else {
+            if ($meta->{cpus} > 1) {
+                return 'kernel-smp';
+            }
+
+            else {
+                return 'kernel-default';
+            }
+        }
+    }
+
+    # For other distros, be conservative and just return 'kernel'
+    return 'kernel-default';
+}
+
+sub _get_installed
+{
+    my ($name, $g) = @_;
+
+    my $rpmcmd = ['rpm', '-q', '--qf', '%{EPOCH} %{VERSION} %{RELEASE}\n',
+                  $name];
+    my @output = eval { $g->command_lines($rpmcmd) };
+    if ($@) {
+        # RPM command returned non-zero. This might be because there was
+        # actually an error, or might just be because the package isn't
+        # installed.
+        # Unfortunately, rpm sent its error to stdout instead of stderr, and
+        # command_lines only gives us stderr in $@. To get round this we'll
+        # execute the command again, sending all output to stdout and ignoring
+        # failure. If the output contains 'not installed', we'll assume it's not
+        # a real error.
+        my $error = $g->sh("LANG=C '".join("' '", @$rpmcmd)."' 2>&1 ||:");
+
+        return () if ($error =~ /not installed/);
+
+        v2vdie __x('Error running {command}: {error}',
+                   command => join(' ', @$rpmcmd), error => $error);
+    }
+
+    my @installed = ();
+    foreach my $installed (@output) {
+        $installed =~ /^(\S+)\s+(\S+)\s+(\S+)$/
+            or die("Unexpected return from rpm command: $installed");
+        my ($epoch, $version, $release) = ($1, $2, $3);
+
+        # Ensure epoch is always numeric
+        $epoch = 0 if('(none)' eq $epoch);
+
+        push(@installed, [$epoch, $version, $release]);
+    }
+
+    return sort { _evr_cmp($a->[0], $a->[1], $a->[2],
+                           $b->[0], $b->[1], $b->[2]) } @installed;
+}
+
+sub _parse_evr
+{
+    my ($evr) = @_;
+
+    $evr =~ /^(?:(\d+):)?([^-]+)(?:-(\S+))?$/ or die();
+
+    my $epoch = $1;
+    my $version = $2;
+    my $release = $3;
+
+    return ($epoch, $version, $release);
+}
+
+sub _evr_cmp
+{
+    my ($e1, $v1, $r1, $e2, $v2, $r2) = @_;
+
+    # Treat epoch as zero if undefined
+    $e1 ||= 0;
+    $e2 ||= 0;
+
+    return -1 if ($e1 < $e2);
+    return 1 if ($e1 > $e2);
+
+    # version must be defined
+    my $cmp = _rpmvercmp($v1, $v2);
+    return $cmp if ($cmp != 0);
+
+    # Treat release as the empty string if undefined
+    $r1 ||= "";
+    $r2 ||= "";
+
+    return _rpmvercmp($r1, $r2);
+}
+
+# An implementation of rpmvercmp. Compares two rpm version/release numbers and
+# returns -1/0/1 as appropriate.
+# Note that this is intended to have the exact same behaviour as the real
+# rpmvercmp, not be in any way sane.
+sub _rpmvercmp
+{
+    my ($a, $b) = @_;
+
+    # Simple equality test
+    return 0 if($a eq $b);
+
+    my @aparts;
+    my @bparts;
+
+    # [t]ransformation
+    # [s]tring
+    # [l]ist
+    foreach my $t ([$a => \@aparts],
+                   [$b => \@bparts]) {
+        my $s = $t->[0];
+        my $l = $t->[1];
+
+        # We split not only on non-alphanumeric characters, but also on the
+        # boundary of digits and letters. This corresponds to the behaviour of
+        # rpmvercmp because it does 2 types of iteration over a string. The
+        # first iteration skips non-alphanumeric characters. The second skips
+        # over either digits or letters only, according to the first character
+        # of $a.
+        @$l = split(/(?<=[[:digit:]])(?=[[:alpha:]]) | # digit<>alpha
+                     (?<=[[:alpha:]])(?=[[:digit:]]) | # alpha<>digit
+                     [^[:alnum:]]+                # sequence of non-alphanumeric
+                    /x, $s);
+    }
+
+    # Find the minimun of the number of parts of $a and $b
+    my $parts = scalar(@aparts) < scalar(@bparts) ?
+                scalar(@aparts) : scalar(@bparts);
+
+    for(my $i = 0; $i < $parts; $i++) {
+        my $acmp = $aparts[$i];
+        my $bcmp = $bparts[$i];
+
+        # Return 1 if $a is numeric and $b is not
+        if($acmp =~ /^[[:digit:]]/) {
+            return 1 if($bcmp !~ /^[[:digit:]]/);
+
+            # Drop leading zeroes
+            $acmp =~ /^0*(.*)$/;
+            $acmp = $1;
+            $bcmp =~ /^0*(.*)$/;
+            $bcmp = $1;
+
+            # We do a string comparison of 2 numbers later. At this stage, if
+            # they're of differing lengths, one is larger.
+            return 1 if(length($acmp) > length($bcmp));
+            return -1 if(length($bcmp) > length($acmp));
+        }
+
+        # Return -1 if $a is letters and $b is not
+        else {
+            return -1 if($bcmp !~ /^[[:alpha:]]/);
+        }
+
+        # Return only if they differ
+        return -1 if($acmp lt $bcmp);
+        return 1 if($acmp gt $bcmp);
+    }
+
+    # We got here because all the parts compared so far have been equal, and one
+    # or both have run out of parts.
+
+    # Whichever has the greatest number of parts is the largest
+    return -1 if(scalar(@aparts) < scalar(@bparts));
+    return 1  if(scalar(@aparts) > scalar(@bparts));
+
+    # We can get here if the 2 strings differ only in non-alphanumeric
+    # separators.
+    return 0;
+}
+
+sub _remap_block_devices
+{
+    my ($meta, $virtio, $g, $root, $grub) = @_;
+
+    my @devices = map { $_->{device} } @{$meta->{disks}};
+    @devices = sort { scsi_first_cmp($a, $b) } @devices;
+
+    # @devices contains an ordered list of libvirt device names. Because
+    # libvirt uses a similar naming scheme to Linux, these will mostly be the
+    # same names as used by the guest. They are ordered as they were passed to
+    # libguestfs, which means their device name in the appliance can be
+    # inferred.
+
+    # If the guest is using libata, IDE drives could have different names in the
+    # guest from their libvirt device names.
+
+    # Modern SUSE distros use libata, and IDE devices are presented as sdX
+    my $libata = 1;
+
+    # Disable libata for SUSE7/8, although this platform is unsupported
+    if (_is_sles_family($g, $root)) {
+        my $major_version = $g->inspect_get_major_version($root);
+        if ($major_version eq '7' ||
+            $major_version eq '8')
+        {
+            $libata = 0;
+        }
+    }
+
+    # Create a hash to track any hd->sd conversions, as any references to
+    # hd devices (in fstab, menu.lst, etc) will need to be converted later.
+    my %idemap;
+
+    if ($libata) {
+        # If there are any IDE devices, the guest will have named these sdX
+        # after any SCSI devices. i.e. If we have disks hda, hdb, sda and sdb,
+        # these will have been presented to the guest as sdc, sdd, sda and sdb
+        # respectively.
+        #
+        # N.B. I think the IDE/SCSI ordering situation may be somewhat more
+        # complicated than previously thought. This code originally put IDE
+        # drives first, but this hasn't worked for an OVA file. Given that
+        # this is a weird and somewhat unlikely situation I'm going with SCSI
+        # first until we have a more comprehensive solution.
+
+        my $idedev;
+        my @newdevices;
+        my $suffix = 'a';
+        foreach my $device (@devices) {
+            if ($device =~ /(?:h|s)d[a-z]+/) {
+                $idedev = $device;
+                $device = 'sd'.$suffix++;
+                $idemap{$device} = $idedev if ($device ne $idedev);
+            }
+            push(@newdevices, $device);
+        }
+        @devices = @newdevices;
+    }
+
+    # We now assume that @devices contains an ordered list of device names, as
+    # used by the guest. Create a map of old guest device names to new guest
+    # device names.
+    my %map;
+
+    # Everything will be converted to either vdX, sdX or hdX
+    my $prefix;
+    if ($virtio) {
+        $prefix = 'vd';
+    } elsif ($libata) {
+        $prefix = 'sd';
+    } else {
+        $prefix = 'hd'
+    }
+
+    my $letter = 'a';
+    foreach my $device (@devices) {
+        my $mapped = $prefix.$letter;
+
+
+        # If a Xen guest has non-PV devices, Xen also simultaneously presents
+        # these as xvd devices. i.e. hdX and xvdX both exist and are the same
+        # device.
+        # This mapping is also useful for P2V conversion of Citrix Xenserver
+        # guests done in HVM mode. Disks are detected as sdX, although the guest
+        # uses xvdX natively.
+        if ($device =~ /^(?:h|s)d([a-z]+)/) {
+            $map{'xvd'.$1} = $mapped;
+        }
+        $map{$device} = $mapped;
+        # Also add a mapping for previously saved hd->sd conversions
+        #if (defined($idemap{$device}) && (($idemap{$device}) ne "")){
+        if (defined($idemap{$device})){
+            my $idedevice;
+            $idedevice = $idemap{$device};
+            $map{$idedevice} = $mapped;
+        }
+
+        $letter++;
+    }
+
+    eval {
+        # Update bare device references in fstab and grub's menu.lst and device.map
+        foreach my $spec ($g->aug_match('/files/etc/fstab/*/spec'),
+                          $g->aug_match("/files$grub->{grub_conf}/*/kernel/root"),
+                          $g->aug_match("/files$grub->{grub_conf}/*/kernel/resume"),
+                          $g->aug_match('/files/boot/grub/device.map/*'.
+                                            '[label() != "#comment"]'))
+        {
+            my $device = $g->aug_get($spec);
+
+            # Match device names and partition numbers
+            my $name; my $part;
+            foreach my $r (qr{^/dev/(cciss/c\d+d\d+)(?:p(\d+))?$},
+                           qr{^/dev/([a-z]+)(\d*)?$}) {
+                if ($device =~ $r) {
+                    $name = $1;
+                    $part = $2;
+                    last;
+                }
+            }
+
+            # Ignore this entry if it isn't a device name
+            next unless defined($name);
+
+            # Ignore md and floppy devices, which don't need to be mapped
+            next if $name =~ /(md|fd)/;
+
+            # Ignore this entry if it refers to a device we don't know anything
+            # about. The user will have to fix this post-conversion.
+            if (!exists($map{$name})) {
+                my $warned = 0;
+                for my $file ('/etc/fstab', '/boot/grub/device.map') {
+                    if ($spec =~ m{^/files$file}) {
+                        logmsg WARN, __x('{file} references unknown device '.
+                                         '{device}. This entry must be '.
+                                         'manually fixed after conversion.',
+                                         file => $file, device => $device);
+                        $warned = 1;
+                    }
+                }
+
+                # Shouldn't happen. Not fatal if it does, though.
+                if (!$warned) {
+                    logmsg WARN, 'Please report this warning as a bug. '.
+                                 "augeas path $spec refers to unknown device ".
+                                 "$device. This entry must be manually fixed ".
+                                 'after conversion.'
+                }
+
+                next;
+            }
+
+            my $mapped = '/dev/'.$map{$name};
+            $mapped .= $part if defined($part);
+            $g->aug_set($spec, $mapped);
+        }
+
+        $g->aug_save();
+    };
+
+    augeas_error($g, $@) if ($@);
+
+    # Delete cached (and now out of date) blkid info if it exists
+    foreach my $blkidtab ('/etc/blkid/blkid.tab', '/etc/blkid.tab') {
+        $g->rm($blkidtab) if ($g->exists($blkidtab));
+    }
+}
+
+sub _prepare_bootable
+{
+    my ($g, $root, $grub, $version, @modules) = @_;
+
+    my $path = "/boot/vmlinuz-$version";
+    $grub->write($path);
+    my $grub_initrd = $grub->get_initrd($path);
+
+    # Backup the original initrd
+    $g->mv($grub_initrd, "$grub_initrd.pre-v2v")
+    if $g->exists($grub_initrd);
+
+    # dracut does not exist in SUSE, but might in the future
+    if ($g->exists('/sbin/dracut')) {
+        $g->command(['/sbin/dracut', '--add-drivers', join(" ", @modules),
+                     $grub_initrd, $version]);
+    }
+
+    elsif ($g->exists('/sbin/mkinitrd')) {
+        # Create a new initrd which probes the required kernel modules
+        my @module_args = ();
+        push(@module_args, "-m \"");
+        foreach my $module (@modules) {
+            push(@module_args, "$module ");
+        }
+        push(@module_args, "\"");
+
+        my @env;
+
+        $g->sh(join(' ', @env).'/sbin/mkinitrd '.join(' ', @module_args).
+               " -i $grub_initrd -k /boot/vmlinuz-$version");
+    }
+
+    else {
+        v2vdie __('Didn\'t find mkinitrd or dracut. Unable to update initrd.');
+    }
+
+}
+
+# Return 1 if the guest supports ACPI, 0 otherwise
+sub _supports_acpi
+{
+    my ($g, $root, $arch) = @_;
+
+    # Blacklist configurations which are known to fail
+    # possibly SLES 8, x86_64 (Similar to RHEL 3?)
+    if (_is_sles_family($g, $root) && $g->inspect_get_major_version($root) == 8 &&
+        $arch eq 'x86_64') {
+        return 0;
+    }
+
+    return 1;
+}
+
+sub _supports_virtio
+{
+    my ($kernel, $g) = @_;
+
+    my %checklist = (
+        "virtio_net" => 0,
+        "virtio_blk" => 0
+    );
+
+    # Search the installed kernel's modules for the virtio drivers
+    foreach my $module ($g->find("/lib/modules/$kernel")) {
+        foreach my $driver (keys(%checklist)) {
+            if($module =~ m{/$driver\.(?:o|ko)$}) {
+                $checklist{$driver} = 1;
+            }
+        }
+    }
+
+    # Check we've got all the drivers in the checklist
+    foreach my $driver (keys(%checklist)) {
+        if(!$checklist{$driver}) {
+            return 0;
+        }
+    }
+
+    return 1;
+}
+
+# The next two functions are a temporary workaround to bnc#836521. The actual
+# fix is in a new perl-Bootloader, which will likely not be on most guests.
+sub _modify_perlBootloader
+{
+    my ($g) = @_;
+    my $module = $g->sh('rpm -ql perl-Bootloader | grep GRUB.pm');
+    chomp($module);
+    my $module_bak = "$module.v2vtmp";
+
+    if ($g->grep('/dev/(?:vx', "$module")) {
+        $g->mv($module, $module_bak);
+        $g->sh("/usr/bin/sed -e's/vx/xv/' $module_bak > $module");
+
+        return 1;
+    }
+
+    return 0;
+}
+
+sub _restore_perlBootloader
+{
+    my ($g) = @_;
+    my $module = $g->sh('rpm -ql perl-Bootloader | grep GRUB.pm');
+    chomp($module);
+    my $module_bak = "$module.v2vtmp";
+
+    if ($g->exists($module_bak)) {
+        $g->mv($module_bak, $module);
+    }
+}
+
+=back
+
+=head1 COPYRIGHT
+
+Copyright (c) 2013 SUSE LINUX Products GmbH, Nuernberg, Germany.
+
+=head1 LICENSE
+
+Please see the file COPYING.LIB for the full license.
+
+=head1 SEE ALSO
+
+L<Sys::VirtConvert::Converter(3pm)>,
+L<Sys::VirtConvert(3pm)>,
+L<virt-v2v(1)>,
+L<http://libguestfs.org/>.
+
+=cut
+
+1;
-- 
1.8.1.4




More information about the Libguestfs mailing list