Re: [Libguestfs] [libguestfs] * initial FrugalWare support (#4)
by Richard W.M. Jones
On Mon, Oct 21, 2013 at 12:04:44PM -0700, DeX77 wrote:
> This enables initial FrugalWare Support in libguestfs. It is not yet complete, as not all needed packages are yet available.
I'd prefer that patches are posted on the mailing list for review,
even a preliminary review.
You don't need to sign up, but there may be a short delay before
you post is approved.
Does this need changes to supermin too?
Rich.
--
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
libguestfs lets you edit virtual machines. Supports shell scripting,
bindings from many languages. http://libguestfs.org
11 years, 2 months
Notes on getting libguestfs to work on Mac OS X
by Richard W.M. Jones
Supplied by Pene on IRC who got libguestfs to compile and run on
Mac OS X 10.9 with qemu 1.6.0. My notes in [] below.
Rich.
libguestfs on Mac OS X, recipe so far:
--------------------------------------
- libtool-kill-dependency_libs.sh: replace chmod --reference="$output.tmp" "$output" -> chmod `stat -f "%p" "$output.tmp"` "$output"
- src/proto.c: replace if (!(*xdrp) (&xdr, args)) -> if (!(*xdrp) (&xdr, args, 0))
- src/proto.c: replace if (xdrp && ret && !xdrp (&xdr, ret)) -> if (xdrp && ret && !xdrp (&xdr, ret, 0))
- src/launch-direct.c: comment out - if (qemu_supports (g, data, "-nodefaults")) ADD_CMDLINE ("-nodefaults");
[This works around a qemu bug:
qemu: qemu_mutex_lock: Invalid argument
when using the -nodefaults parameter]
- fuse/guestunmount.c: replace execlp ("fusermount", "fusermount", "-u", mountpoint, NULL); -> execlp ("/sbin/umount", "fusermount", mountpoint, NULL);
- fuse/guestunmount.c: replace execlp ("/sbin/fuser", "fuser", "-v", "-m", mountpoint, NULL); -> execlp ("/usr/bin/fuser", "fuser", "-c", mountpoint, NULL);
- gnulib/lib/error.c: replace extern char *program_name; -> extern char *program_name = "libguestfs";
- gnulib/lib/open_memstream.c: add from http://lists.gnu.org/archive/html/bug-gnulib/2010-04/msg00379.html, remove check for #if !HAVE_FUNOPEN
- gnulib/lib/stdio.in.h, gnulib/lib/Makefile.in: add new open_memstream.c
[Mac OS X lacks open_memstream and gnulib doesn't supply it. Note
that open_memstream is part of POSIX so this is a bug in OS X]
- configure with: ./configure --disable-appliance --disable-daemon --disable-probes --disable-ruby --disable-php CFLAGS="-I/opt/local/include" LDFLAGS="-L/opt/local/lib" LIBS="-lintl" FUSE_CFLAGS="-I/usr/local/include/osxfuse/fuse -D_FILE_OFFSET_BITS=64" FUSE_LIBS="-losxfuse"
--
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
libguestfs lets you edit virtual machines. Supports shell scripting,
bindings from many languages. http://libguestfs.org
11 years, 2 months
augeas changes in stable branches
by Olaf Hering
commit f59b87f7f18d9df89ff9940a317ff1fb452cbd28 breaks compile with older
augeas (0.7.4). AFAIK there is no requirement for augeas 1.0 until 1.24.
augeas.c: In function 'do_aug_init':
augeas.c:102:38: error: 'AUG_NO_ERR_CLOSE' undeclared (first use in this function)
Olaf
11 years, 2 months
ANNOUNCE: CVE-2013-4419: insecure temporary directory handling for guestfish's network socket
by Richard W.M. Jones
This issue has been assigned CVE-2013-4419.
https://bugzilla.redhat.com/show_bug.cgi?id=1016960
(Note this bug is private, but will be made public shortly)
----------------------------------------------------------------------
When using the guestfish --remote or guestfish --listen options,
guestfish would create a socket in a known location
(/tmp/.guestfish-$UID/socket-$PID).
The location has to be a known one in order for both ends to
communicate. However no checking was done that the containing
directory (/tmp/.guestfish-$UID) is owned by the user. Thus another
user could create this directory and potentially modify sockets owned
by another user's guestfish client or server.
Thanks: Michael Scherer for discovering this issue.
----------------------------------------------------------------------
You can remediate this issue in one of three ways:
(1) Apply the attached patch to libguestfs and rebuild from source.
(2) Run the following command on your system before using the
guestfish --listen option. Pay attention to any errors from mkdir,
which might indicate that the directory has been hijacked.
rm -rf /tmp/.guestfish-`id -u`
mkdir -m 0700 /tmp/.guestfish-`id -u`
(3) Wait for new packages to become available shortly. This afternoon
I will build packages for Fedora, which will be available through
updates-testing. Packages will be available for RHEL 6 shortly
through RHEL channels. Debian and SuSE maintainers were made aware of
this issue and will provide packages.
----------------------------------------------------------------------
Rich.
--
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
libguestfs lets you edit virtual machines. Supports shell scripting,
bindings from many languages. http://libguestfs.org
11 years, 2 months
[Hivex] OS X and iconv
by Alex Nelson
Hi all,
The linking problem I found yesterday appears to be because of the built-in iconv in OS X. So, this is a request for patch or pointers: What needs to happen with configure.ac or the iconv.m4 macro file to link an iconv library _besides_ the one in /usr/local/lib? That's the tl;dr, full explanation follows.
That library lacks the x86_64 architecture:
$ file /usr/local/lib/libiconv.dylib
libiconv.dylib: Mach-O universal binary with 2 architectures
libiconv.dylib (for architecture i386): Mach-O dynamically linked shared library i386
libiconv.dylib (for architecture ppc): Mach-O dynamically linked shared library ppc
Make complains, though it could stand to complain with more violence to the build:
[snip]
Making all in tools
make[3]: Nothing to be done for `all'.
CC libhivex_la-handle.lo
CC libhivex_la-node.lo
CC libhivex_la-offset-list.lo
CC libhivex_la-utf16.lo
CC libhivex_la-util.lo
CC libhivex_la-value.lo
CC libhivex_la-visit.lo
CC libhivex_la-write.lo
CCLD libhivex.la
ld: warning: ld: warning: ignoring file /usr/local/lib/libiconv.dylib, missing required architecture x86_64 in file /usr/local/lib/libiconv.dylib (2 slices)ignoring file ./hivex.syms, file was built for unsupported file format ( 0x23 0x20 0x68 0x69 0x76 0x65 0x78 0x20 0x67 0x65 0x6E 0x65 0x72 0x61 0x74 0x65 ) which is not the architecture being linked (x86_64): ./hivex.syms
/opt/local/bin/ranlib: file: .libs/libhivex.a(fd-hook.o) has no symbols
ranlib: file: .libs/libhivex.a(fd-hook.o) has no symbols
Making all in images
[snip]
MacPorts' iconv works fine:
$ file /opt/local/lib/libiconv.dylib
/opt/local/lib/libiconv.dylib: Mach-O 64-bit dynamically linked shared library x86_64
In theory, XCode's does too [1], though I tried that suggestion and it didn't work for me.
The way to get compilation to work is to remove the iconv library files from /usr/local/lib [2, 3]. I tried moving them to a temporary folder, and when I did that and rebuilt, Hivexml ran without a hitch.
So, what would work in configure.ac to get Hivex to remove /usr/local/lib from the library paths for OS X, but just for iconv?
Or is the correct answer to file a complaint with Apple and commence thumb-twiddling?
--Alex
[1] http://stackoverflow.com/a/18170376/1207160
[2] http://stackoverflow.com/a/4929905/1207160
[3] https://trac.macports.org/ticket/28302
11 years, 2 months
[PATCH] virt-v2v: Convert RedHat.pm to Linux.pm - for SUSE support
by Mike Latimer
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';
+
+@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';
+
+@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';
-
-@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';
-
-@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
11 years, 2 months
[Hivex] [PATCH] lib: Promote byte_conversions.h #include to hivex-internal.h
by Alex Nelson
This patch addresses a build failure in OS X. Running git-bisect on a
straightforward build (bootstrap, autogen.sh, configure, make, make
install) showed this as the "Bad commit:"
3e7c039799cddc45517350cc917eb10715f33fec
The issue is that hivex-internal.h uses le32toh in a static inline
function. In case `configure` doesn't find le32toh, byte_conversions.h
defines it. But hivex-internal.h doesn't include the safety definition.
OS X demonstrates this a problem. Neither endian.h nor byteswap.h are
found with `configure` in OS X 10.8.5 (XCode 5), but the headers are
both found in Fedora 19 and Ubuntu 13.04. This patch to configure.ac
further logs that only ntohl is available for byte swaps:
@@ -153,6 +153,8 @@ AC_REPLACE_FUNCS([mmap])
dnl Functions.
AC_CHECK_FUNCS([bindtextdomain])
+AC_CHECK_FUNCS([le32toh ntohl bswap_32 __bswap_32])
+
(As an aside, it's curious that a missing byteswap.h didn't cause
hivex-internal.h to fail to build.)
(As another aside, this is an interesting example of lazy symbol
binding failing with an inline function.)
The problem is resolved by having hivex-internal.h include
byte_conversions.h. This obviates most of the other direct inclusions
of byte_conversions.h. (One persists under sh/.)
This patch builds and runs hivexml on images/large fine in Ubuntu 13.04
and Fedora 19. It also allows builds to succeed in OS X, but doesn't
run hivexml for an unrelated reason. The _iconv_open
symbol-not-found issue, that I thought was previously resolved
(491ba0f7a761c7ffd50e0eaa4d892f78d538eb2b), resurfaced.
Signed-off-by: Alex Nelson <a.nelson(a)prometheuscomputing.com>
---
lib/handle.c | 1 -
lib/hivex-internal.h | 2 ++
lib/node.c | 1 -
lib/utf16.c | 1 -
lib/value.c | 1 -
lib/visit.c | 1 -
lib/write.c | 1 -
7 files changed, 2 insertions(+), 6 deletions(-)
diff --git a/lib/handle.c b/lib/handle.c
index 982a6fb..62a8644 100644
--- a/lib/handle.c
+++ b/lib/handle.c
@@ -44,7 +44,6 @@
#include "hivex.h"
#include "hivex-internal.h"
-#include "byte_conversions.h"
static uint32_t
header_checksum (const hive_h *h)
diff --git a/lib/hivex-internal.h b/lib/hivex-internal.h
index 53499cb..f391b98 100644
--- a/lib/hivex-internal.h
+++ b/lib/hivex-internal.h
@@ -22,6 +22,8 @@
#include <stdarg.h>
#include <stddef.h>
+#include "byte_conversions.h"
+
struct hive_h {
char *filename;
int fd;
diff --git a/lib/node.c b/lib/node.c
index ed72b51..5090dbe 100644
--- a/lib/node.c
+++ b/lib/node.c
@@ -34,7 +34,6 @@
#include "hivex.h"
#include "hivex-internal.h"
-#include "byte_conversions.h"
hive_node_h
hivex_root (hive_h *h)
diff --git a/lib/utf16.c b/lib/utf16.c
index d0f2e45..844715c 100644
--- a/lib/utf16.c
+++ b/lib/utf16.c
@@ -27,7 +27,6 @@
#include "hivex.h"
#include "hivex-internal.h"
-#include "byte_conversions.h"
char *
_hivex_windows_utf16_to_utf8 (/* const */ char *input, size_t len)
diff --git a/lib/value.c b/lib/value.c
index d713cdc..6d0f266 100644
--- a/lib/value.c
+++ b/lib/value.c
@@ -32,7 +32,6 @@
#include "hivex.h"
#include "hivex-internal.h"
-#include "byte_conversions.h"
int
_hivex_get_values (hive_h *h, hive_node_h node,
diff --git a/lib/visit.c b/lib/visit.c
index bd95b6c..87c755f 100644
--- a/lib/visit.c
+++ b/lib/visit.c
@@ -32,7 +32,6 @@
#include "hivex.h"
#include "hivex-internal.h"
-#include "byte_conversions.h"
int
hivex_visit (hive_h *h, const struct hivex_visitor *visitor, size_t len,
diff --git a/lib/write.c b/lib/write.c
index bc2251c..dbb8292 100644
--- a/lib/write.c
+++ b/lib/write.c
@@ -41,7 +41,6 @@
#include "hivex.h"
#include "hivex-internal.h"
-#include "byte_conversions.h"
/*----------------------------------------------------------------------
* Writing.
--
1.8.3.4 (Apple Git-47)
11 years, 2 months
[PATCH] btrfs: Fix improper memmove usage in do_btrfs_subvolume_list
by John Eckersberg
The third parameter (number of bytes to copy) was given as an offset
relative to dest, when it should be relative to src. This fixes some
valgrind warnings I happened across.
---
daemon/btrfs.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/daemon/btrfs.c b/daemon/btrfs.c
index c3247ac..765dec6 100644
--- a/daemon/btrfs.c
+++ b/daemon/btrfs.c
@@ -473,7 +473,7 @@ do_btrfs_subvolume_list (const mountable_t *fs)
#undef XSTRTOU64
- memmove (line, line + ovector[6], ovector[7] + 1);
+ memmove (line, line + ovector[6], ovector[7] - ovector[6] + 1);
this->btrfssubvolume_path = line;
}
--
1.8.3.1
11 years, 2 months
[PATCH 1/5] sysprep: remove tmp files
by Wanlong Gao
This removes tmp files under /tmp and /var/tmp.
Signed-off-by: Wanlong Gao <gaowanlong(a)cn.fujitsu.com>
---
sysprep/Makefile.am | 1 +
sysprep/sysprep_operation_tmp_files.ml | 52 ++++++++++++++++++++++++++++++++++
2 files changed, 53 insertions(+)
create mode 100644 sysprep/sysprep_operation_tmp_files.ml
diff --git a/sysprep/Makefile.am b/sysprep/Makefile.am
index fcd17fc..b89345a 100644
--- a/sysprep/Makefile.am
+++ b/sysprep/Makefile.am
@@ -65,6 +65,7 @@ operations = \
ssh_hostkeys \
ssh_userdir \
sssd_db_log \
+ tmp_files \
udev_persistent_net \
user_account \
utmp yum_uuid
diff --git a/sysprep/sysprep_operation_tmp_files.ml b/sysprep/sysprep_operation_tmp_files.ml
new file mode 100644
index 0000000..a2187df
--- /dev/null
+++ b/sysprep/sysprep_operation_tmp_files.ml
@@ -0,0 +1,52 @@
+(* virt-sysprep
+ * Copyright (C) 2013 Fujitsu Limited.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *)
+
+open Sysprep_operation
+open Common_gettext.Gettext
+
+module G = Guestfs
+
+let tmp_files_perform g root =
+ let typ = g#inspect_get_type root in
+ if typ <> "windows" then (
+ let paths = [ "/tmp/*";
+ "/var/tmp/*"; ] in
+ List.iter (
+ fun path ->
+ let files = g#glob_expand path in
+ Array.iter (
+ fun file ->
+ g#rm_rf file;
+ ) files;
+ ) paths;
+
+ []
+ )
+ else []
+
+let op = {
+ defaults with
+ name = "tmp-files";
+ enabled_by_default = true;
+ heading = s_"Remove the tmp files";
+ pod_description = Some (s_"\
+This removes the temporary files under C</tmp> and C</var/tmp>.");
+ perform_on_filesystems = Some tmp_files_perform;
+}
+
+let () = register_operation op
--
1.8.4.27.g0a41de8
11 years, 2 months