Mike Latimer
2013-Oct-03 05:46 UTC
[Libguestfs] [PATCH] virt-v2v: Convert RedHat.pm to Linux.pm - for SUSE support
This is a proposed patch which changes the RedHat.pm converter to Linux.pm,
and adds support for SUSE guest conversion. This is first approach recommended
by Matt Booth in:
https://www.redhat.com/archives/libguestfs/2013-September/msg00076.html
Some aspects of this patch still need additional testing, and a couple of
changes are not foolproof (such as the lack of grub2 support in the menu.lst
changes in _remap_block_devices). However, it is complete enough that I'd
like some feedback before continuing.
Note - This patch alone is not enough to support SUSE guests, as changes to
virt-v2v.db are also required. After these changes are flushed out, I'll
post
all the patches in one thread.
-Mike
---
lib/Sys/VirtConvert/Converter/Linux.pm | 2880 +++++++++++++++++++++++++++++++
lib/Sys/VirtConvert/Converter/RedHat.pm | 2489 --------------------------
2 files changed, 2880 insertions(+), 2489 deletions(-)
create mode 100644 lib/Sys/VirtConvert/Converter/Linux.pm
delete mode 100644 lib/Sys/VirtConvert/Converter/RedHat.pm
diff --git a/lib/Sys/VirtConvert/Converter/Linux.pm
b/lib/Sys/VirtConvert/Converter/Linux.pm
new file mode 100644
index 0000000..f3d13df
--- /dev/null
+++ b/lib/Sys/VirtConvert/Converter/Linux.pm
@@ -0,0 +1,2880 @@
+# Sys::VirtConvert::Converter::Linux
+# Copyright (C) 2009-2012 Red Hat Inc.
+# Copyright (C) 2013 SUSE Inc.
+#
+# 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 supported by grubby or perl-Bootloader, and therefore common
+# between gruby legacy grub2
+package Sys::VirtConvert::Converter::Linux::Grub;
+
+use Sys::VirtConvert::Util;
+use Locale::TextDomain 'virt-v2v';
+
+sub get_initrd
+{
+ my $self = shift;
+ my ($path) = @_;
+ my $initrd;
+
+ my $g = $self->{g};
+
+ if ($g->exists('/sbin/grubby')) {
+ foreach my $line ($g->command_lines(['grubby',
'--info', $path])) {
+ return $1 if $line =~ /^initrd=(\S+)/;
+ }
+ } else {
+ # If grubby did not work, try perl-Bootloader (for SUSE environments)
+ $initrd = eval { $g->command(['/usr/bin/perl',
+ '-MBootloader::Tools',
+ '-e', 'InitLibrary(); '.
+ 'my @sections = '.
+ 'GetSectionList(type=>image,
image=>"'.$path.'"); '.
+ 'my $section = GetSection(@sections); '.
+ 'my $initrd = $section->{initrd}; '.
+ 'print $initrd;']) };
+
+ if (defined($initrd)) {
+ # If the initrd starts with (hd*,*), remove it.
+ $initrd =~ s/^\(hd.*\)//;
+ return $initrd;
+ }
+ }
+
+ # If all else fails, use heuristics if the file exists
+ (($initrd = $path) =~ s/vmlinuz/initrd/) if (!defined($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 $default;
+
+ my $g = $self->{g};
+
+ if ($g->exists('/sbin/grubby')) {
+ $default = $g->command(['grubby',
'--default-kernel']);
+ } else {
+ $default = eval { $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.*\)//;
+ }
+
+ chomp($default);
+ return $default;
+}
+
+sub set_default_image
+{
+ my $self = shift;
+ my ($path) = @_;
+
+ my $g = $self->{g};
+
+ if ($g->exists('/sbin/grubby')) {
+ $g->command(['grubby', '--set-default', $path]);
+ } else {
+ # 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.
+ eval { $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::Linux::GrubLegacy;
+
+use Sys::VirtConvert::Util;
+
+use File::Basename;
+use Locale::TextDomain 'virt-v2v';
+
+ at Sys::VirtConvert::Converter::Linux::GrubLegacy::ISA +
qw(Sys::VirtConvert::Converter::Linux::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/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, $root) = @_;
+
+ 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
+ my @entries = $g->aug_match("/files$grub_conf/title/kernel".
+ "[. = '$grub_path']");
+ return if scalar(@entries) > 0;
+
+ my $kernel +
Sys::VirtConvert::Converter::Linux::_inspect_linux_kernel($g, $path);
+ my $version = $kernel->{version};
+ my $grub_initrd = dirname($path)."/initrd-$version";
+
+ # No point in dying if /etc/(distro)-release can't be read
+ my ($title) = eval { $g->inspect_get_product_name($root) };
+ $title ||= 'Linux';
+
+ # Remove codename or architecture
+ $title =~ s/ \(.*\)//;
+ # Remove release string and add version (like new-kernel-pkg)
+ $title =~ s/ release.*//;
+ $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', $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::Linux::Grub2;
+
+use Sys::VirtConvert::Util qw(:DEFAULT augeas_error);
+use Locale::TextDomain 'virt-v2v';
+
+ at Sys::VirtConvert::Converter::Linux::Grub2::ISA +
qw(Sys::VirtConvert::Converter::Linux::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/efi/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 $grub_cmdline;
+ if ($g->exists('/etc/sysconfig/grub')) {
+ $grub_cmdline = '/files/etc/sysconfig/grub/GRUB_CMDLINE_LINUX';
+ } else {
+ $grub_cmdline =
'/files/etc/default/grub/GRUB_CMDLINE_LINUX_DEFAULT';
+ }
+
+ my $cmdline + eval { $g->aug_get($grub_cmdline) };
+
+ 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($grub_cmdline, $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::Linux::_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
+ foreach my $node ($g->aug_match("/files/etc/fstab/*[file =
'/boot/efi']")) {
+ $g->aug_rm($node);
+ }
+ eval { $g->aug_save(); };
+ augeas_error($g, $@) if $@;
+
+ # Install grub2 in the BIOS beot 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::Linux;
+
+use Sys::VirtConvert::Util qw(:DEFAULT augeas_error scsi_first_cmp);
+use Locale::TextDomain 'virt-v2v';
+
+=pod
+
+=head1 NAME
+
+Sys::VirtConvert::Converter::Linux - Convert a Linux based guest to run on KVM
+
+=head1 SYNOPSIS
+
+ use Sys::VirtConvert::Converter;
+
+ Sys::VirtConvert::Converter->convert($g, $root, $meta);
+
+=head1 DESCRIPTION
+
+Sys::VirtConvert::Converter::Linux converts a Linux based guest to use KVM.
+
+=head1 METHODS
+
+=over
+
+=cut
+
+sub _is_rhel_family
+{
+ my ($g, $root) = @_;
+
+ return ($g->inspect_get_type($root) eq 'linux') &&
+ ($g->inspect_get_distro($root) =~
/^(rhel|centos|scientificlinux|redhat-based)$/);
+}
+
+sub _is_suse_family
+{
+ my ($g, $root) = @_;
+
+ return ($g->inspect_get_type($root) eq 'linux') &&
+ ($g->inspect_get_distro($root) =~
/^(sles|suse-based|opensuse)$/);
+}
+
+=item Sys::VirtConvert::Converter::Linux->can_handle(g, root)
+
+Return 1 if Sys::VirtConvert::Converter::Linux can convert the given guest
+
+=cut
+
+sub can_handle
+{
+ my $class = shift;
+
+ my ($g, $root) = @_;
+
+ if ($g->inspect_get_type($root) eq 'linux') {
+ return (_is_rhel_family($g, $root) ||
+ ($g->inspect_get_distro($root) eq 'fedora') ||
+ _is_suse_family($g, $root));
+ }
+}
+
+=item Sys::VirtConvert::Converter::Linux->convert(g, root, config, meta,
options)
+
+Convert a Linux 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::Linux::Grub2->new($g, $root, $config)
};
+ $grub = eval
+ { Sys::VirtConvert::Converter::Linux::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);
+
+ my $driver = _get_display_driver($g, $root);
+ _configure_display_driver($g, $root, $config, $meta, $grub, $driver);
+ _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
+ # We need this on RHEL 4/virtio because mkinitrd can't detect root on
+ # virtio. 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, whereas 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
+ # RHEL 6 doesn't use /etc/inittab, but this doesn't hurt
+ 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, $driver) = @_;
+
+ # 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, $driver);
+ $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 specified driver is installed.
+ if ($updated &&
+ ($g->exists('/usr/bin/X') ||
$g->exists('/usr/bin/X11/X')) &&
+ !_install_capability($driver, $g, $root, $config, $meta, $grub))
+ {
+ logmsg WARN, __x('Display driver was updated to {driver}, but
unable '.
+ 'to install {driver} driver. X may not function
'.
+ 'correctly', driver => $driver);
+ }
+}
+
+# If the guest was shutdown uncleanly, it's possible that transient state
was
+# left lying around in the rpm database. Given we know 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-smp, kernel-hugemem, kernel-PAE
+ 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. It is safe to
+ # continue and attempt to install a virtio kernel next.
+ 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 =~ /^kernel-xen/) {
+ $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.$kernel_arch");
+ }
+ }
+ }
+
+ # 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 RHEL3. 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);
+
+ # Change i386 to i[56]86
+ $arch = _set_32bit_arch($g, $root, $arch);
+
+ 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, $root, \@apps);
+ _unconfigure_vbox($g, \@apps);
+ _unconfigure_vmware($g, \@apps);
+ _unconfigure_citrix($g, \@apps);
+}
+
+# Unconfigure Xen specific guest modifications
+sub _unconfigure_xen
+{
+ my ($g, $root, $apps) = @_;
+
+ # Look for kmod-xenpv-*, which can be found on RHEL 3 machines
+ my @remove;
+ foreach my $app (@$apps) {
+ my $name = $app->{app_name};
+
+ if($name =~ /^kmod-xenpv(-.*)?$/) {
+ push(@remove, $name);
+ }
+ }
+ _remove_applications($g, @remove);
+
+ # Undo related nastiness if kmod-xenpv was installed
+ if(scalar(@remove) > 0) {
+ # kmod-xenpv modules may have been manually copied to other kernels.
+ # Hunt them down and destroy them.
+ foreach my $dir (grep(m{/xenpv$}, $g->find('/lib/modules')))
{
+ $dir = '/lib/modules/'.$dir;
+
+ # Check it's a directory
+ next unless($g->is_dir($dir));
+
+ # Check it's not owned by an installed application
+ eval { _get_application_owner($dir, $g) };
+
+ # Remove it if _get_application_owner didn't find an owner
+ if($@) {
+ $g->rm_rf($dir);
+ }
+ }
+
+ # rc.local may contain an insmod or modprobe of the xen-vbd driver
+ my @rc_local = eval { $g->read_lines('/etc/rc.local') };
+ if ($@) {
+ logmsg WARN, __x('Unable to open /etc/rc.local: {error}',
+ error => $@);
+ }
+
+ else {
+ my $size = 0;
+
+ foreach my $line (@rc_local) {
+ if($line =~ /\b(insmod|modprobe)\b.*\bxen-vbd/) {
+ $line = '#'.$line;
+ }
+
+ $size += length($line) + 1;
+ }
+
+ $g->write_file('/etc/rc.local', join("\n",
@rc_local)."\n", $size);
+ }
+ }
+
+ if (_is_suse_family($g, $root)) {
+ # Remove xen modules from INITRD_MODULES and DOMU_INITRD_MODULES
+ 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 (defined($modified));
+ }
+}
+
+# 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) = @_;
+
+ # 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.
+ 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 @upgrade;
+ 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
upgrade
+ # 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 =~ /^kernel-xen/)
+ {
+ $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/xenU from release field
+ if (defined($kernel_release) &&
+ $kernel_release =~ /^(\S+?)(xen)?(U)?$/)
+ {
+ $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
+ if (!$found) {
+ if (@installed == 0) {
+ push(@install, [$name]);
+ } else {
+ push(@upgrade, [$name]);
+ }
+ }
+ } else {
+ push(@install, [$name]);
+ }
+ }
+ }
+
+ # Capability is already installed
+ if (!defined($kernel) && @install == 0 && @upgrade == 0) {
+ return 1;
+ }
+
+ my $success = _install_any($kernel, \@install, \@upgrade,
+ $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, $upgrade, $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_suse_family($g, $root) &&
+ ($g->inspect_get_major_version($root) == 11)) {
+ my $tmp;
+ push(@$tmp, $kernel);
+ $tmp->[1][0] = $tmp->[0][0].'-base';
+ for my $count (1..4) {
+ if (defined($tmp->[0][$count])) {
+ $tmp->[1][$count] = $tmp->[0][$count];
+ }
+ }
+ $kernel = $tmp;
+ }
+ }
+
+ # Workaround for SUSE bnc#836521
+ my $pbl_fix = _modify_perlBootloader($g) if (defined($kernel) &&
+ (_is_suse_family($g, $root)));
+
+ my $success = 0;
+ _net_run($g, sub {
+ eval {
+ # Try to fetch these dependencies using the guest's native
update
+ # tool
+ if (_is_suse_family($g, $root)) {
+ $success = _install_zypper($kernel, $install, $upgrade, $g);
+ } else {
+ $success = _install_up2date($kernel, $install, $upgrade, $g);
+ $success = _install_yum($kernel, $install, $upgrade, $g)
+ unless ($success);
+ }
+
+ # Fall back to local config if the above didn't work
+ $success = _install_config($kernel, $install, $upgrade,
+ $g, $root, $config)
+ unless ($success);
+ };
+ warn($@) if $@;
+ });
+
+ # Undo the previous workaround for SUSE bnc#836521
+ _restore_perlBootloader($g) if ($pbl_fix == 1);
+
+ # Make augeas reload to pick up any altered configuration
+ eval { $g->aug_load() };
+ augeas_error($g, $@) if ($@);
+
+ # Installing a new kernel in RHEL 5 under libguestfs fails to add a grub
+ # entry. Find the kernel we installed and ensure it has a grub entry.
+ if (defined($kernel)) {
+ foreach my $k ($g->glob_expand('/boot/vmlinuz-*')) {
+ if (!grep(/^$k$/, @k_before)) {
+ $grub->check($k, $root);
+ last;
+ }
+ }
+ }
+
+ return $success;
+}
+
+sub _install_up2date
+{
+ my ($kernel, $install, $upgrade, $g) = @_;
+
+ # Check this system has actions.packages
+ return 0 unless ($g->exists('/usr/bin/up2date'));
+
+ # Check this system is registered to rhn
+ return 0 unless ($g->exists('/etc/sysconfig/rhn/systemid'));
+
+ my @pkgs;
+ foreach my $pkg ($kernel, @$install, @$upgrade) {
+ next unless defined($pkg);
+
+ # up2date doesn't do arch
+ my ($name, undef, $epoch, $version, $release) = @$pkg;
+
+ $epoch ||= "";
+ $version ||= "";
+ $release ||= "";
+
+ push(@pkgs, "['$name', '$version',
'$release', '$epoch']");
+ }
+
+ eval {
+ $g->command(['/usr/bin/python', '-c',
+ "import sys;
sys.path.append('/usr/share/rhn'); ".
+ "import actions.packages;
".
+ "actions.packages.cfg['forceInstall'] = 1;
".
+ "ret =
actions.packages.update([".join(',', @pkgs)."]); ".
+ "sys.exit(ret[0]);
"]);
+ };
+ if ($@) {
+ logmsg WARN, __x('Failed to install packages using up2date. '.
+ 'Error message was: {error}', error => $@);
+ return 0;
+ }
+
+ return 1;
+}
+
+sub _install_yum
+{
+ my ($kernel, $install, $upgrade, $g) = @_;
+
+ # Check this system has yum installed
+ return 0 unless ($g->exists('/usr/bin/yum'));
+
+ # Install or upgrade 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 upgrade
+ if (defined($kernel)) {
+ my @installed = _get_installed($kernel->[0], $g);
+
+ # Don't modify the contents of $install and $upgrade in case we
fall
+ # through and they're reused in another function
+ if (@installed == 0 || defined($kernel->[2])) {
+ my @tmp = defined($install) ? @$install : ();
+ push(@tmp, $kernel);
+ $install = \@tmp;
+ } else {
+ my @tmp = defined($upgrade) ? @$upgrade : ();
+ push(@tmp, $kernel);
+ $upgrade = \@tmp;
+ }
+ }
+
+ my $success = 1;
+ YUM: foreach my $task (
+ [ "install", $install, qr/(^No package|already installed)/ ],
+ [ "upgrade", $upgrade, qr/^No Packages/ ]
+ ) {
+ my ($action, $list, $failure) = @$task;
+
+ # We can't do these all in a single transaction, because yum offers
us
+ # no way to tell if a transaction partially succeeded
+ foreach my $entry (@$list) {
+ next unless (defined($entry));
+
+ # You can't specify epoch without architecture to yum, so we
just
+ # ignore epoch and hope
+ my ($name, undef, undef, $version, $release) = @$entry;
+
+ # Construct n-v-r
+ my $pkg = $name;
+ $pkg .= "-$version" if (defined($version));
+ $pkg .= "-$release" if (defined($release));
+
+ my @output + eval { $g->sh_lines("LANG=C
/usr/bin/yum -y $action $pkg") };
+ if ($@) {
+ logmsg WARN, __x('Failed to install packages using yum.
'.
+ 'Output was: {output}', output =>
$@);
+ $success = 0;
+ last YUM;
+ }
+
+ foreach my $line (@output) {
+ # Yum probably just isn't configured. Don't bother with
an error
+ # message
+ if ($line =~ /$failure/) {
+ $success = 0;
+ last YUM;
+ }
+ }
+ }
+ }
+
+ 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) {
+ 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.
'.
+ 'Checking local virt-v2v repo.',
+ 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, $upgrade, $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, @$upgrade) {
+ 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, $upgrade, @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;
+
+ $g->command(['rpm', $upgrade == 1 ? '-U' : '-i',
@rpms]);
+
+ # 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;
+ }
+ }
+
+ # For x86_64, also check if there is any i386 or i686 version
installed.
+ # If there is, check if it needs to be upgraded.
+ if ($arch eq 'x86_64') {
+ $path = undef;
+ $deps = undef;
+
+ # It's not an error if no i386 package is available
+ eval {
+ ($path, $deps) = $config->match_app($g, $root, $app,
'i386');
+ };
+
+ if (defined($path)) {
+ $transfer_path = $config->get_transfer_path($path);
+ if (!defined($transfer_path) || !$g->exists($transfer_path))
{
+ push(@$missing, $path);
+
+ foreach my $deppath (_get_deppaths($g, $root, $config,
+ $missing, 'i386',
@$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);
+
+ # Change i386 to i[56]86
+ $kernel_arch = _set_32bit_arch($g, $root, $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
+
+ # RedHat kernels
+ if (_is_rhel_family($g, $root)) {
+ # RHEL 5
+ if ($g->inspect_get_major_version($root) eq '5') {
+ if ($arch eq 'i686') {
+ # XXX: This assumes that PAE will be available in the
hypervisor.
+ # While this is almost certainly true, it's theoretically
possible
+ # that it isn't. The information we need is available in
the
+ # capabilities XML. If PAE isn't available, we should
choose
+ # 'kernel'.
+ return 'kernel-PAE';
+ }
+
+ # There's only 1 kernel package on RHEL 5 x86_64
+ else {
+ return 'kernel';
+ }
+ }
+
+ # RHEL 4
+ elsif ($g->inspect_get_major_version($root) eq '4') {
+ if ($arch eq 'i686') {
+ # If the guest has > 10G RAM, give it a hugemem kernel
+ if ($meta->{memory} > 10 * 1024 * 1024 * 1024) {
+ return 'kernel-hugemem';
+ }
+
+ # SMP kernel for guests with >1 CPU
+ elsif ($meta->{cpus} > 1) {
+ return 'kernel-smp';
+ }
+
+ else {
+ return 'kernel';
+ }
+ }
+
+ else {
+ if ($meta->{cpus} > 8) {
+ return 'kernel-largesmp';
+ }
+
+ elsif ($meta->{cpus} > 1) {
+ return 'kernel-smp';
+ }
+ else {
+ return 'kernel';
+ }
+ }
+ }
+
+ # RHEL 3 didn't have a xen kernel
+ }
+
+ # SUSE kernels
+ elsif (_is_suse_family($g, $root)) {
+ # SLES/openSUSE 11+
+ if ($g->inspect_get_major_version($root) ge '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/openSUSE 10
+ elsif ($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';
+ }
+ }
+ }
+ }
+
+ # XXX: Could do with a history of Fedora kernels in here
+
+ # For other distros, be conservative and just return 'kernel'
+ return 'kernel';
+}
+
+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 distros use libata, and IDE devices are presented as sdX
+ my $libata = 1;
+
+ # RHEL 2, 3 and 4 didn't use libata
+ # RHEL 5 does use libata, but udev rules call IDE devices hdX anyway
+ if (_is_rhel_family($g, $root)) {
+ my $major_version = $g->inspect_get_major_version($root);
+ if ($major_version eq '2' ||
+ $major_version eq '3' ||
+ $major_version eq '4' ||
+ $major_version eq '5')
+ {
+ $libata = 0;
+ }
+ }
+ # Fedora has used libata since FC7, which is long out of support. We assume
+ # that all Fedora distributions in use use libata.
+
+ # 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})) {
+ 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);
+
+ if ($g->exists('/sbin/dracut')) {
+ $g->command(['/sbin/dracut', '--add-drivers',
join(" ", @modules),
+ $grub_initrd, $version]);
+ }
+
+ elsif (_is_suse_family($g, $root) &&
($g->exists('/sbin/mkinitrd'))) {
+ $g->sh('/sbin/mkinitrd -m "'.join(' ',
@modules).'" '.
+ ' -i '.$grub_initrd.' -k
/boot/vmlinuz-'.$version);
+ }
+
+ # Default to original mkinitrd, if not SLES and dracut does not exist
+ elsif ($g->exists('/sbin/mkinitrd')) {
+ # Create a new initrd which probes the required kernel modules
+ my @module_args = ();
+ foreach my $module (@modules) {
+ push(@module_args, "--with=$module");
+ }
+
+ # We explicitly modprobe ext2 here. This is required by mkinitrd on
+ # RHEL 3, and shouldn't hurt on other OSs. We don't care if
this
+ # fails.
+ eval { $g->modprobe('ext2') };
+
+ # loop is a module in RHEL 5. Try to load it. Doesn't matter for
+ # other OSs if it doesn't exist, but RHEL 5 will complain:
+ # All of your loopback devices are in use.
+ eval { $g->modprobe('loop') };
+
+ my @env;
+
+ # RHEL 4 mkinitrd determines if the root filesystem is on LVM by
+ # checking if the device name (after following symlinks) starts with
+ # /dev/mapper. However, on recent kernels/udevs, /dev/mapper/foo is
+ # just a symlink to /dev/dm-X. This means that RHEL 4 mkinitrd
+ # running in the appliance fails to detect root on LVM. We check
+ # ourselves if root is on LVM, and frig RHEL 4's mkinitrd if it is
+ # by setting root_lvm=1 in its environment. This overrides an
+ # internal variable in mkinitrd, and is therefore extremely nasty
+ # and applicable only to a particular version of mkinitrd.
+ if (_is_rhel_family($g, $root) &&
+ $g->inspect_get_major_version($root) eq '4')
+ {
+ push(@env, 'root_lvm=1') if ($g->is_lv($root));
+ }
+
+ $g->sh(join(' ', @env).' /sbin/mkinitrd '.join('
', @module_args).
+ " $grub_initrd $version");
+ }
+
+ else {
+ v2vdie __('Didn\'t find mkinitrd or dracut. Unable to update
initrd.');
+ }
+
+ # Disable kudzu in the guest
+ # Kudzu will detect the changed network hardware at boot time and either:
+ # require manual intervention, or
+ # disable the network interface
+ # Neither of these behaviours is desirable.
+ if ($g->exists('/etc/init.d/kudzu')) {
+ $g->command(['/sbin/chkconfig', 'kudzu',
'off']);
+ }
+}
+
+# Return 1 if the guest supports ACPI, 0 otherwise
+sub _supports_acpi
+{
+ my ($g, $root, $arch) = @_;
+
+ # Blacklist configurations which are known to fail
+ # RHEL 3, x86_64
+ if (_is_rhel_family($g, $root) &&
$g->inspect_get_major_version($root) == 3 &&
+ $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;
+}
+
+sub _get_display_driver
+{
+ my ($g, $root) = @_;
+
+ if (_is_suse_family($g, $root)) {
+ return 'cirrus';
+ } else {
+ return 'qxl';
+ }
+}
+
+# RedHat and SUSE use different 32bit architectures (i686 -vs- i586)
+sub _set_32bit_arch
+{
+ my ($g, $root, $arch) = @_;
+
+ # We want an i586 or i686 guest for i[345]86
+ if ($arch =~ /^i[345]86$/) {
+ if (_is_sles_family($g, $root)) {
+ $arch = 'i586';
+ } else {
+ $arch = 'i686';
+ }
+ }
+
+ return $arch;
+}
+
+# The next two functions are a SUSE-specific temporary workaround to
+# bnc#836521. This is required to prevent root being set to (hd*) instead
+# of the correct (hd*,*). 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) 2009-2012 Red Hat Inc.
+
+=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;
diff --git a/lib/Sys/VirtConvert/Converter/RedHat.pm
b/lib/Sys/VirtConvert/Converter/RedHat.pm
deleted file mode 100644
index 612ab2e..0000000
--- a/lib/Sys/VirtConvert/Converter/RedHat.pm
+++ /dev/null
@@ -1,2489 +0,0 @@
-# Sys::VirtConvert::Converter::RedHat
-# Copyright (C) 2009-2012 Red Hat Inc.
-#
-# 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 supported by grubby, and therefore common between gruby legacy and
-# grub2
-package Sys::VirtConvert::Converter::RedHat::Grub;
-
-use Sys::VirtConvert::Util;
-use Locale::TextDomain 'virt-v2v';
-
-sub get_initrd
-{
- my $self = shift;
- my ($path) = @_;
-
- my $g = $self->{g};
-
- foreach my $line ($g->command_lines(['grubby', '--info',
$path])) {
- return $1 if $line =~ /^initrd=(\S+)/;
- }
-
- v2vdie __x('Didn\'t find initrd for kernel {path}', path =>
$path);
-}
-
-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::RedHat::GrubLegacy;
-
-use Sys::VirtConvert::Util;
-
-use File::Basename;
-use Locale::TextDomain 'virt-v2v';
-
- at Sys::VirtConvert::Converter::RedHat::GrubLegacy::ISA -
qw(Sys::VirtConvert::Converter::RedHat::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/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
- my @entries = $g->aug_match("/files$grub_conf/title/kernel[. =
'$grub_path']");
- return if scalar(@entries) > 0;
-
- my $kernel -
Sys::VirtConvert::Converter::RedHat::_inspect_linux_kernel($g, $path);
- my $version = $kernel->{version};
- my $grub_initrd = dirname($path)."/initrd-$version";
-
- # No point in dying if /etc/redhat-release can't be read
- my ($title) = eval { $g->read_lines('/etc/redhat-release') };
- $title ||= 'Linux';
-
- # This is how new-kernel-pkg does it
- $title =~ s/ release.*//;
- $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', $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::RedHat::Grub2;
-
-use Sys::VirtConvert::Util qw(:DEFAULT augeas_error);
-use Locale::TextDomain 'virt-v2v';
-
- at Sys::VirtConvert::Converter::RedHat::Grub2::ISA -
qw(Sys::VirtConvert::Converter::RedHat::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/efi/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 = $g->command(['grubby',
'--default-kernel']);
- chomp($default);
- 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/sysconfig/grub/GRUB_CMDLINE_LINUX') };
-
- 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/sysconfig/grub/GRUB_CMDLINE_LINUX',
$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 = $g->command(['grubby',
'--default-kernel']);
- chomp($default);
-
- if ($default ne $path) {
- $g->command(['grubby', '--set-default', $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::RedHat::_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
- foreach my $node ($g->aug_match("/files/etc/fstab/*[file =
'/boot/efi']")) {
- $g->aug_rm($node);
- }
- eval { $g->aug_save(); };
- 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::RedHat;
-
-use Sys::VirtConvert::Util qw(:DEFAULT augeas_error scsi_first_cmp);
-use Locale::TextDomain 'virt-v2v';
-
-=pod
-
-=head1 NAME
-
-Sys::VirtConvert::Converter::RedHat - Convert a Red Hat based guest to run on
KVM
-
-=head1 SYNOPSIS
-
- use Sys::VirtConvert::Converter;
-
- Sys::VirtConvert::Converter->convert($g, $root, $meta);
-
-=head1 DESCRIPTION
-
-Sys::VirtConvert::Converter::RedHat converts a Red Hat based guest to use KVM.
-
-=head1 METHODS
-
-=over
-
-=cut
-
-sub _is_rhel_family
-{
- my ($g, $root) = @_;
-
- return ($g->inspect_get_type($root) eq 'linux') &&
- ($g->inspect_get_distro($root) =~
/^(rhel|centos|scientificlinux|redhat-based)$/);
-}
-
-=item Sys::VirtConvert::Converter::RedHat->can_handle(g, root)
-
-Return 1 if Sys::VirtConvert::Converter::RedHat can convert the given guest
-
-=cut
-
-sub can_handle
-{
- my $class = shift;
-
- my ($g, $root) = @_;
-
- return ($g->inspect_get_type($root) eq 'linux' &&
- (_is_rhel_family($g, $root) || $g->inspect_get_distro($root) eq
'fedora'));
-}
-
-=item Sys::VirtConvert::Converter::RedHat->convert(g, root, config, meta,
options)
-
-Convert a Red Hat 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::RedHat::Grub2->new($g,
$root, $config) };
- $grub = eval { Sys::VirtConvert::Converter::RedHat::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);
- _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
- # We need this on RHEL 4/virtio because mkinitrd can't detect root on
- # virtio. 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 $@;
-}
-
-# We 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, whereas 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
- # RHEL 6 doesn't use /etc/inittab, but this doesn't hurt
- 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, 'qxl');
- $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 qxl driver is installed.
- if ($updated &&
- ($g->exists('/usr/bin/X') ||
$g->exists('/usr/bin/X11/X')) &&
- !_install_capability('qxl', $g, $root, $config, $meta, $grub))
- {
- logmsg WARN, __('Display driver was updated to qxl, but unable to
'.
- 'install qxl 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 we know 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-smp, kernel-hugemem, kernel-PAE
- 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;
- }
-
- # There should be an installed virtio capable kernel if virtio was
installed
- die("virtio configured, 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-xenU") {
- $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.$kernel_arch");
- }
- }
- }
-
- # 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 RHEL3. 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 i686 guest for i[345]86
- return 'i686' if($arch =~ /^i[345]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) = @_;
-
- # Look for kmod-xenpv-*, which can be found on RHEL 3 machines
- my @remove;
- foreach my $app (@$apps) {
- my $name = $app->{app_name};
-
- if($name =~ /^kmod-xenpv(-.*)?$/) {
- push(@remove, $name);
- }
- }
- _remove_applications($g, @remove);
-
- # Undo related nastiness if kmod-xenpv was installed
- if(scalar(@remove) > 0) {
- # kmod-xenpv modules may have been manually copied to other kernels.
- # Hunt them down and destroy them.
- foreach my $dir (grep(m{/xenpv$}, $g->find('/lib/modules')))
{
- $dir = '/lib/modules/'.$dir;
-
- # Check it's a directory
- next unless($g->is_dir($dir));
-
- # Check it's not owned by an installed application
- eval { _get_application_owner($dir, $g) };
-
- # Remove it if _get_application_owner didn't find an owner
- if($@) {
- $g->rm_rf($dir);
- }
- }
-
- # rc.local may contain an insmod or modprobe of the xen-vbd driver
- my @rc_local = eval { $g->read_lines('/etc/rc.local') };
- if ($@) {
- logmsg WARN, __x('Unable to open /etc/rc.local: {error}',
- error => $@);
- }
-
- else {
- my $size = 0;
-
- foreach my $line (@rc_local) {
- if($line =~ /\b(insmod|modprobe)\b.*\bxen-vbd/) {
- $line = '#'.$line;
- }
-
- $size += length($line) + 1;
- }
-
- $g->write_file('/etc/rc.local', join("\n",
@rc_local)."\n", $size);
- }
- }
-}
-
-# 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) = @_;
-
- # 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.
- 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 @upgrade;
- 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
upgrade
- # 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-xenU")
- {
- $kernel_pkg -
_get_replacement_kernel_name($g, $root, $kernel_arch,
- $meta);
-
- # Check if we've got 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/xenU from release field
- if (defined($kernel_release) &&
- $kernel_release =~ /^(\S+?)(xen)?(U)?$/)
- {
- $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
- if (!$found) {
- if (@installed == 0) {
- push(@install, [$name]);
- } else {
- push(@upgrade, [$name]);
- }
- }
- } else {
- push(@install, [$name]);
- }
- }
- }
-
- # Capability is already installed
- if (!defined($kernel) && @install == 0 && @upgrade == 0) {
- return 1;
- }
-
- my $success = _install_any($kernel, \@install, \@upgrade,
- $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, $upgrade, $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);
-
- my $success = 0;
- _net_run($g, sub {
- eval {
- # Try to fetch these dependencies using the guest's native
update
- # tool
- $success = _install_up2date($kernel, $install, $upgrade, $g);
- $success = _install_yum($kernel, $install, $upgrade, $g)
- unless ($success);
-
- # Fall back to local config if the above didn't work
- $success = _install_config($kernel, $install, $upgrade,
- $g, $root, $config)
- unless ($success);
- };
- warn($@) if $@;
- });
-
- # Make augeas reload to pick up any altered configuration
- eval { $g->aug_load() };
- augeas_error($g, $@) if ($@);
-
- # Installing a new kernel in RHEL 5 under libguestfs fails to add a grub
- # entry. Find the kernel we installed and ensure it 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_up2date
-{
- my ($kernel, $install, $upgrade, $g) = @_;
-
- # Check this system has actions.packages
- return 0 unless ($g->exists('/usr/bin/up2date'));
-
- # Check this system is registered to rhn
- return 0 unless ($g->exists('/etc/sysconfig/rhn/systemid'));
-
- my @pkgs;
- foreach my $pkg ($kernel, @$install, @$upgrade) {
- next unless defined($pkg);
-
- # up2date doesn't do arch
- my ($name, undef, $epoch, $version, $release) = @$pkg;
-
- $epoch ||= "";
- $version ||= "";
- $release ||= "";
-
- push(@pkgs, "['$name', '$version',
'$release', '$epoch']");
- }
-
- eval {
- $g->command(['/usr/bin/python', '-c',
- "import sys;
sys.path.append('/usr/share/rhn'); ".
- "import actions.packages;
".
- "actions.packages.cfg['forceInstall'] = 1;
".
- "ret =
actions.packages.update([".join(',', @pkgs)."]); ".
- "sys.exit(ret[0]);
"]);
- };
- if ($@) {
- logmsg WARN, __x('Failed to install packages using up2date. '.
- 'Error message was: {error}', error => $@);
- return 0;
- }
-
- return 1;
-}
-
-sub _install_yum
-{
- my ($kernel, $install, $upgrade, $g) = @_;
-
- # Check this system has yum installed
- return 0 unless ($g->exists('/usr/bin/yum'));
-
- # Install or upgrade 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 upgrade
- if (defined($kernel)) {
- my @installed = _get_installed($kernel->[0], $g);
-
- # Don't modify the contents of $install and $upgrade in case we
fall
- # through and they're reused in another function
- if (@installed == 0 || defined($kernel->[2])) {
- my @tmp = defined($install) ? @$install : ();
- push(@tmp, $kernel);
- $install = \@tmp;
- } else {
- my @tmp = defined($upgrade) ? @$upgrade : ();
- push(@tmp, $kernel);
- $upgrade = \@tmp;
- }
- }
-
- my $success = 1;
- YUM: foreach my $task (
- [ "install", $install, qr/(^No package|already installed)/ ],
- [ "upgrade", $upgrade, qr/^No Packages/ ]
- ) {
- my ($action, $list, $failure) = @$task;
-
- # We can't do these all in a single transaction, because yum offers
us
- # no way to tell if a transaction partially succeeded
- foreach my $entry (@$list) {
- next unless (defined($entry));
-
- # You can't specify epoch without architecture to yum, so we
just
- # ignore epoch and hope
- my ($name, undef, undef, $version, $release) = @$entry;
-
- # Construct n-v-r
- my $pkg = $name;
- $pkg .= "-$version" if (defined($version));
- $pkg .= "-$release" if (defined($release));
-
- my @output - eval { $g->sh_lines("LANG=C
/usr/bin/yum -y $action $pkg") };
- if ($@) {
- logmsg WARN, __x('Failed to install packages using yum.
'.
- 'Output was: {output}', output =>
$@);
- $success = 0;
- last YUM;
- }
-
- foreach my $line (@output) {
- # Yum probably just isn't configured. Don't bother with
an error
- # message
- if ($line =~ /$failure/) {
- $success = 0;
- last YUM;
- }
- }
- }
- }
-
- return $success;
-}
-
-sub _install_config
-{
- my ($kernel_naevr, $install, $upgrade, $g, $root, $config) = @_;
-
- my ($kernel, $user);
- if (defined($kernel_naevr)) {
- my ($kernel_pkg, $kernel_arch) = @$kernel_naevr;
-
- ($kernel, $user) - $config->match_app($g, $root,
$kernel_pkg, $kernel_arch);
- } else {
- $user = [];
- }
-
- foreach my $pkg (@$install, @$upgrade) {
- push(@$user, $pkg->[0]);
- }
-
- my @missing;
- if (defined($kernel)) {
- my $transfer_path = $config->get_transfer_path($kernel);
- if (!defined($transfer_path) || !$g->exists($transfer_path)) {
- push(@missing, $kernel);
- }
- }
-
- 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, $upgrade, @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;
-
- $g->command(['rpm', $upgrade == 1 ? '-U' : '-i',
@rpms]);
-
- # 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;
- }
- }
-
- # For x86_64, also check if there is any i386 or i686 version
installed.
- # If there is, check if it needs to be upgraded.
- if ($arch eq 'x86_64') {
- $path = undef;
- $deps = undef;
-
- # It's not an error if no i386 package is available
- eval {
- ($path, $deps) = $config->match_app($g, $root, $app,
'i386');
- };
-
- if (defined($path)) {
- $transfer_path = $config->get_transfer_path($path);
- if (!defined($transfer_path) || !$g->exists($transfer_path))
{
- push(@$missing, $path);
-
- foreach my $deppath (_get_deppaths($g, $root, $config,
- $missing, 'i386',
@$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);
-
- # We haven't supported anything other than i686 for the kernel on 32
bit for
- # a very long time.
- $kernel_arch = 'i686' 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
-
- # RHEL 5
- if (_is_rhel_family($g, $root) &&
$g->inspect_get_major_version($root) eq '5') {
- if ($arch eq 'i686') {
- # XXX: This assumes that PAE will be available in the hypervisor.
- # While this is almost certainly true, it's theoretically
possible
- # that it isn't. The information we need is available in the
- # capabilities XML. If PAE isn't available, we should choose
- # 'kernel'.
- return 'kernel-PAE';
- }
-
- # There's only 1 kernel package on RHEL 5 x86_64
- else {
- return 'kernel';
- }
- }
-
- # RHEL 4
- elsif (_is_rhel_family($g, $root) &&
$g->inspect_get_major_version($root) eq '4') {
- if ($arch eq 'i686') {
- # If the guest has > 10G RAM, give it a hugemem kernel
- if ($meta->{memory} > 10 * 1024 * 1024 * 1024) {
- return 'kernel-hugemem';
- }
-
- # SMP kernel for guests with >1 CPU
- elsif ($meta->{cpus} > 1) {
- return 'kernel-smp';
- }
-
- else {
- return 'kernel';
- }
- }
-
- else {
- if ($meta->{cpus} > 8) {
- return 'kernel-largesmp';
- }
-
- elsif ($meta->{cpus} > 1) {
- return 'kernel-smp';
- }
- else {
- return 'kernel';
- }
- }
- }
-
- # RHEL 3 didn't have a xen kernel
-
- # XXX: Could do with a history of Fedora kernels in here
-
- # For other distros, be conservative and just return 'kernel'
- return 'kernel';
-}
-
-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 iepoch 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) = @_;
-
- 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 distros use libata, and IDE devices are presented as sdX
- my $libata = 1;
-
- # RHEL 2, 3 and 4 didn't use libata
- # RHEL 5 does use libata, but udev rules call IDE devices hdX anyway
- if (_is_rhel_family($g, $root)) {
- my $major_version = $g->inspect_get_major_version($root);
- if ($major_version eq '2' ||
- $major_version eq '3' ||
- $major_version eq '4' ||
- $major_version eq '5')
- {
- $libata = 0;
- }
- }
- # Fedora has used libata since FC7, which is long out of support. We assume
- # that all Fedora distributions in use use libata.
-
- 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 @newdevices;
- my $suffix = 'a';
- foreach my $device (@devices) {
- $device = 'sd'.$suffix++ if ($device =~ /(?:h|s)d[a-z]+/);
- 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;
- $letter++;
- }
-
- eval {
- # Update bare device references in fstab and grub's device.map
- foreach my $spec ($g->aug_match('/files/etc/fstab/*/spec'),
-
$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);
-
- 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 = ();
- foreach my $module (@modules) {
- push(@module_args, "--with=$module");
- }
-
- # We explicitly modprobe ext2 here. This is required by mkinitrd on
- # RHEL 3, and shouldn't hurt on other OSs. We don't care if
this
- # fails.
- eval { $g->modprobe('ext2') };
-
- # loop is a module in RHEL 5. Try to load it. Doesn't matter for
- # other OSs if it doesn't exist, but RHEL 5 will complain:
- # All of your loopback devices are in use.
- eval { $g->modprobe('loop') };
-
- my @env;
-
- # RHEL 4 mkinitrd determines if the root filesystem is on LVM by
- # checking if the device name (after following symlinks) starts with
- # /dev/mapper. However, on recent kernels/udevs, /dev/mapper/foo is
- # just a symlink to /dev/dm-X. This means that RHEL 4 mkinitrd
- # running in the appliance fails to detect root on LVM. We check
- # ourselves if root is on LVM, and frig RHEL 4's mkinitrd if it is
- # by setting root_lvm=1 in its environment. This overrides an
- # internal variable in mkinitrd, and is therefore extremely nasty
- # and applicable only to a particular version of mkinitrd.
- if (_is_rhel_family($g, $root) &&
- $g->inspect_get_major_version($root) eq '4')
- {
- push(@env, 'root_lvm=1') if ($g->is_lv($root));
- }
-
- $g->sh(join(' ', @env).' /sbin/mkinitrd '.join('
', @module_args).
- " $grub_initrd $version");
- }
-
- else {
- v2vdie __('Didn\'t find mkinitrd or dracut. Unable to update
initrd.');
- }
-
- # Disable kudzu in the guest
- # Kudzu will detect the changed network hardware at boot time and either:
- # require manual intervention, or
- # disable the network interface
- # Neither of these behaviours is desirable.
- if ($g->exists('/etc/init.d/kudzu')) {
- $g->command(['/sbin/chkconfig', 'kudzu',
'off']);
- }
-}
-
-# Return 1 if the guest supports ACPI, 0 otherwise
-sub _supports_acpi
-{
- my ($g, $root, $arch) = @_;
-
- # Blacklist configurations which are known to fail
- # RHEL 3, x86_64
- if (_is_rhel_family($g, $root) &&
$g->inspect_get_major_version($root) == 3 &&
- $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;
-}
-
-=back
-
-=head1 COPYRIGHT
-
-Copyright (C) 2009-2012 Red Hat Inc.
-
-=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
Matthew Booth
2013-Oct-03 16:34 UTC
[Libguestfs] [PATCH] virt-v2v: Convert RedHat.pm to Linux.pm - for SUSE support
This approach looks great. I think there are a couple of bits to look at (see below), but probably not much. Matt On Wed, 2013-10-02 at 23:46 -0600, Mike Latimer wrote:> This is a proposed patch which changes the RedHat.pm converter to Linux.pm, > and adds support for SUSE guest conversion. This is first approach recommended > by Matt Booth in: > > https://www.redhat.com/archives/libguestfs/2013-September/msg00076.html > > Some aspects of this patch still need additional testing, and a couple of > changes are not foolproof (such as the lack of grub2 support in the menu.lst > changes in _remap_block_devices). However, it is complete enough that I'd > like some feedback before continuing. > > Note - This patch alone is not enough to support SUSE guests, as changes to > virt-v2v.db are also required. After these changes are flushed out, I'll post > all the patches in one thread. > > -MikeN.B. I've cut the original below and replaced it with the output of git show, which deals with the rename sensibly. The patch is identical, though.> diff --git a/lib/Sys/VirtConvert/Converter/RedHat.pm b/lib/Sys/VirtConvert/Converter/Linux.pm > similarity index 79% > rename from lib/Sys/VirtConvert/Converter/RedHat.pm > rename to lib/Sys/VirtConvert/Converter/Linux.pm > index 612ab2e..f3d13df 100644 > --- a/lib/Sys/VirtConvert/Converter/RedHat.pm > +++ b/lib/Sys/VirtConvert/Converter/Linux.pm> @@ -500,7 +584,7 @@ sub convert_efi > eval { $g->aug_save(); }; > augeas_error($g, $@) if $@; > > - # Install grub2 in the BIOS boot partition. This overwrites the previous > + # Install grub2 in the BIOS beot partition. This overwrites the previousTypo> # contents of the EFI boot partition. > $g->command(['grub2-install', $device]);> @@ -1002,8 +1101,9 @@ sub _configure_kernel > last; > } > > - # There should be an installed virtio capable kernel if virtio was installed > - die("virtio configured, but no virtio kernel found") > + # Warn if there is no installed virtio capable kernel. It is safe to > + # continue and attempt to install a virtio kernel next. > + logmsg NOTICE, __x('virtio capable guest, but no virtio kernel found.') > if ($virtio && !defined($boot_kernel));No. This indicates an error in the program rather than something the user can fix. This is also why it's a plain die with no translation.> > # If none of the installed kernels are appropriate, install a new one> @@ -1055,7 +1155,7 @@ sub _configure_kernel > unless defined($boot_kernel); > } else { > v2vdie __x('Failed to find a {name} package to install', > - name => "kernel_pkg.$kernel_arch"); > + name => "$kernel_pkg.$kernel_arch");Thanks :)> @@ -1631,14 +1752,38 @@ sub _install_any > # 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_suse_family($g, $root) && > + ($g->inspect_get_major_version($root) == 11)) { > + my $tmp; > + push(@$tmp, $kernel); > + $tmp->[1][0] = $tmp->[0][0].'-base'; > + for my $count (1..4) { > + if (defined($tmp->[0][$count])) { > + $tmp->[1][$count] = $tmp->[0][$count]; > + } > + } > + $kernel = $tmp; > + } > + }For sanity, I think we need $kernel to be the same data structure in all cases. Lets either unconditionally convert $kernel into a list (of lists), or see of -base can be added to the @install list or something like that. Bear in mind, though, that in the former case you'd also have to update _install_yum and _install_up2date. (I haven't bothered commenting on all the hunks this change would also affect below.)> @@ -2235,6 +2541,10 @@ sub _remap_block_devices > # Fedora has used libata since FC7, which is long out of support. We assume > # that all Fedora distributions in use use libata. > > + # 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, > @@ -2247,10 +2557,15 @@ sub _remap_block_devices > # 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) { > - $device = 'sd'.$suffix++ if ($device =~ /(?:h|s)d[a-z]+/); > + if ($device =~ /(?:h|s)d[a-z]+/) { > + $idedev = $device; > + $device = 'sd'.$suffix++; > + $idemap{$device} = $idedev if ($device ne $idedev); > + } > push(@newdevices, $device); > } > @devices = @newdevices; > @@ -2286,12 +2601,21 @@ sub _remap_block_devices > $map{'xvd'.$1} = $mapped; > } > $map{$device} = $mapped; > + # Also add a mapping for previously saved hd->sd conversions > + if (defined($idemap{$device})) { > + my $idedevice; > + $idedevice = $idemap{$device}; > + $map{$idedevice} = $mapped; > + } > +I've had a change to think about this change properly, and I'm pretty sure it's not required. The reason is that the only place %idemap is modified is in the section guarded: if ($libata) { ... } If the guest is using libata it will not have any references to /dev/hdX devices, as IDE devices are presented as SCSI devices. That's a NAK to this bit of the patch. Instead, you need to work out when SLES switched to libata and set $libata accordingly above.
Mike Latimer
2013-Oct-03 17:48 UTC
Re: [Libguestfs] [PATCH] virt-v2v: Convert RedHat.pm to Linux.pm - for SUSE support
Thanks for the comments, Matt. On Thursday, October 03, 2013 05:34:27 PM Matthew Booth wrote:> > - # There should be an installed virtio capable kernel if virtio was > > installed - die("virtio configured, but no virtio kernel found") > > + # Warn if there is no installed virtio capable kernel. It is safe to > > + # continue and attempt to install a virtio kernel next. > > + logmsg NOTICE, __x('virtio capable guest, but no virtio kernel > > found.')> > > if ($virtio && !defined($boot_kernel)); > > No. This indicates an error in the program rather than something the > user can fix. This is also why it's a plain die with no translation.If a virtio kernel must be installed prior to _configure_kernel, why is there the ability to install a replacement kernel immediately after this message? It doesn't really matter to SUSE though - adding 'kernel' to the deps for virtio in virt-v2v.db will trigger the installation of the correct kernel in the install_capabilities step.> > @@ -1631,14 +1752,38 @@ sub _install_any > > > > # 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_suse_family($g, $root) && > > + ($g->inspect_get_major_version($root) == 11)) { > > + my $tmp; > > + push(@$tmp, $kernel); > > + $tmp->[1][0] = $tmp->[0][0].'-base'; > > + for my $count (1..4) { > > + if (defined($tmp->[0][$count])) { > > + $tmp->[1][$count] = $tmp->[0][$count]; > > + } > > + } > > + $kernel = $tmp; > > + } > > + } > > For sanity, I think we need $kernel to be the same data structure in > all cases. Lets either unconditionally convert $kernel into a list (of > lists), or see of -base can be added to the @install list or something > like that. Bear in mind, though, that in the former case you'd also > have to update _install_yum and _install_up2date.The problem with @install is that it is processed separately from $kernel in _install_config. This causes the two packages to be installed by separate rpm commands, and the dependencies cannot be satisfied. This is why I had to add the -base package to $kernel somehow... (Note - This doesn't effect zypper installs at all as it will satisfy dependencies automatically.) I'll think about it some more...> > @@ -2235,6 +2541,10 @@ sub _remap_block_devices...> I've had a change to think about this change properly, and I'm pretty > sure it's not required. The reason is that the only place %idemap is > modified is in the section guarded: > > if ($libata) { > ... > } > > If the guest is using libata it will not have any references to > /dev/hdX devices, as IDE devices are presented as SCSI devices. That's > a NAK to this bit of the patch. Instead, you need to work out when SLES > switched to libata and set $libata accordingly above.If there are never any hdX devices in libata machines, why are they renamed in the first place? The code I proposed just tracks any such changes and adds them to the list of remaps to perform. (i.e. if hda --> sda, and sda --> vda, then hda --> vda as well.) However, SLES seems to be similar to RHEL5, where libata is in use, but udev calls them hdX anyway (for hvm guests). I can add SLES to the list of $libata=0 environments, but wasn't sure if that was the best long term approach. Thanks, Mike