# Engine.pm - provides driver enginge for the OSSetup API.
#
# (c) 2006 - OpenSLX.com
#
# Oliver Tappe <ot@openslx.com>
#
package OpenSLX::OSSetup::Engine;
use vars qw($VERSION);
$VERSION = 1.01; # API-version . implementation-version
use strict;
use Carp;
use File::Basename;
use OpenSLX::Basics;
################################################################################
### interface methods
################################################################################
sub new
{
my $class = shift;
my $self = {
};
return bless $self, $class;
}
sub initialize
{
my $self = shift;
my $distroName = shift;
my $protectSystemPath = shift;
# load module for the requested distro:
$distroName = uc($distroName);
$distroName =~ tr[-.][_];
my $distroModule = "OpenSLX::OSSetup::Distro::$distroName";
unless (eval "require $distroModule") {
if ($! == 2) {
die _tr("Distro-module <%s> not found!\n", $distroModule);
} else {
die _tr("Unable to load distro-module <%s> (%s)\n", $distroModule, $@);
}
}
my $modVersion = $distroModule->VERSION;
if ($modVersion < 1.01) {
die _tr('Could not load module <%s> (Version <%s> required, but <%s> found)',
$distroModule, 1.01, $modVersion);
}
$distroModule->import;
my $distro = $distroModule->new;
$distro->initialize($self);
$self->{distro} = $distro;
# setup path to distribution-specific info:
my $distroInfoDir = "../lib/distro-info/$distro->{'base-name'}";
if (!-d $distroInfoDir) {
die _tr("unable to find distro-info for system '%s'", $distro->{'base-name'})."\n";
}
$self->{'distro-info-dir'} = $distroInfoDir;
$self->readDistroInfo();
$self->setupSystemPaths($protectSystemPath);
$self->createPackager();
$self->createMetaPackager();
}
sub setupStage1A
{
my $self = shift;
vlog 1, "setting up stage1a for $self->{distro}->{'base-name'}...";
$self->stage1A_createBusyboxEnvironment();
$self->stage1A_setupResolver();
$self->stage1A_copyPrerequiredFiles();
$self->stage1A_copyTrustedPackageKeys();
$self->stage1A_createRequiredFiles();
}
sub setupStage1B
{
my $self = shift;
vlog 1, "setting up stage1b for $self->{distro}->{'base-name'}...";
$self->stage1B_chrootAndBootstrap();
}
sub setupStage1C
{
my $self = shift;
vlog 1, "setting up stage1c for $self->{distro}->{'base-name'}...";
$self->stage1C_chrootAndInstallBasicSystem();
}
sub setupStage1D
{
my $self = shift;
vlog 1, "setting up stage1d for $self->{distro}->{'base-name'}...";
$self->stage1D_setupPackageSources();
$self->stage1D_updateBasicSystem();
$self->stage1D_installPackageSelection();
}
sub setupStage1
{
my $self = shift;
$self->setupStage1A();
my $pid = fork();
if (!$pid) {
# child, execute the tasks that involve a chrooted environment:
$self->setupStage1B();
$self->setupStage1C();
exit 0;
}
# parent, wait for child to do its work inside the chroot
waitpid($pid, 0);
if ($?) {
exit $?;
}
$self->stage1C_cleanupBasicSystem();
$self->setupStage1D();
}
sub setupRepositories
{
my $self = shift;
$self->setupStage1D();
}
sub fixPrerequiredFiles
{
}
################################################################################
### implementation methods
################################################################################
sub readDistroInfo
{
my $self = shift;
vlog 1, "reading configuration info for $self->{distro}->{'base-name'}...";
# merge user-provided configuration distro defaults...
my %repository = %{$self->{distro}->{config}->{repository}};
my %selection = %{$self->{distro}->{config}->{selection}};
my $package_subdir = $self->{distro}->{config}->{'package-subdir'};
my $prereq_packages = $self->{distro}->{config}->{'prereq-packages'};
my $bootstrap_prereq_packages
= $self->{distro}->{config}->{'bootstrap-prereq-packages'};
my $bootstrap_packages = $self->{distro}->{config}->{'bootstrap-packages'};
my $file = "$self->{'distro-info-dir'}/settings.local";
if (-e $file) {
vlog 3, "reading configuration file $file...";
my $config = slurpFile($file);
if (!eval $config) {
die _tr("error in config-file <%s> (%s)", $file, $@)."\n";
}
}
# ...and store merged config:
$self->{'distro-info'} = {
'package-subdir' => $package_subdir,
'prereq-packages' => $prereq_packages,
'bootstrap-prereq-packages' => $bootstrap_prereq_packages,
'bootstrap-packages' => $bootstrap_packages,
'repository' => \%repository,
'selection' => \%selection,
};
if ($openslxConfig{'verbose-level'} >= 2) {
# dump distro-info, if asked for:
foreach my $r (sort keys %repository) {
vlog 2, "repository '$r':";
foreach my $k (sort keys %{$repository{$r}}) {
vlog 2, "\t$k = '$repository{$r}->{$k}'";
}
}
foreach my $s (sort keys %selection) {
my @selLines = split "\n", $selection{$s};
vlog 2, "selection '$s':";
foreach my $sl (@selLines) {
vlog 2, "\t$sl";
}
}
}
}
sub setupSystemPaths
{
my $self = shift;
my $protectSystemPath = shift;
$self->{'system-path'}
= "$openslxConfig{'stage1-path'}/$self->{distro}->{'base-name'}";
vlog 1, "system will be installed to '$self->{'system-path'}'";
if ($protectSystemPath && -e $self->{'system-path'}) {
die _tr("'%s' already exists, giving up!", $self->{'system-path'});
}
# specify individual paths for the respective substages:
$self->{stage1aDir} = "$self->{'system-path'}/stage1a";
$self->{stage1bSubdir} = 'slxbootstrap';
$self->{stage1cSubdir} = 'slxfinal';
# we create *all* of the above folders by creating stage1cDir:
my $stage1cDir
= "$self->{'stage1aDir'}/$self->{'stage1bSubdir'}/$self->{'stage1cSubdir'}";
if (system("mkdir -p $stage1cDir")) {
die _tr("unable to create directory '%s', giving up! (%s)",
$stage1cDir, $!);
}
}
sub createPackager
{
my $self = shift;
my $packagerModule
= "OpenSLX::OSSetup::Packager::$self->{distro}->{'packager-type'}";
unless (eval "require $packagerModule") {
if ($! == 2) {
die _tr("Packager-module <%s> not found!\n", $packagerModule);
} else {
die _tr("Unable to load packager-module <%s> (%s)\n", $packagerModule, $@);
}
}
my $modVersion = $packagerModule->VERSION;
if ($modVersion < 1.01) {
die _tr('Could not load module <%s> (Version <%s> required, but <%s> found)',
$packagerModule, 1.01, $modVersion);
}
$packagerModule->import;
my $packager = $packagerModule->new;
$packager->initialize($self);
$self->{'packager'} = $packager;
}
sub createMetaPackager
{
my $self = shift;
my $metaPackagerModule
= "OpenSLX::OSSetup::MetaPackager::$self->{distro}->{'meta-packager-type'}";
unless (eval "require $metaPackagerModule") {
if ($! == 2) {
die _tr("Meta-packager-module <%s> not found!\n", $metaPackagerModule);
} else {
die _tr("Unable to load meta-packager-module <%s> (%s)\n", $metaPackagerModule, $@);
}
}
my $modVersion = $metaPackagerModule->VERSION;
if ($modVersion < 1.01) {
die _tr('Could not load module <%s> (Version <%s> required, but <%s> found)',
$metaPackagerModule, 1.01, $modVersion);
}
$metaPackagerModule->import;
my $metaPackager = $metaPackagerModule->new;
$metaPackager->initialize($self);
$self->{'meta-packager'} = $metaPackager;
}
sub selectBaseURL
{
my $self = shift;
my $repoInfo = shift;
my $baseURL = $repoInfo->{url};
if (!defined $baseURL) {
my @baseURLs = string2Array($repoInfo->{urls});
# TODO: insert a closest mirror algorithm here!
$baseURL = $baseURLs[0];
}
return $baseURL;
}
sub stage1A_createBusyboxEnvironment
{
my $self = shift;
# copy busybox and all required binaries into stage1a-dir:
vlog 1, "creating busybox-environment...";
copyFile("$openslxConfig{'share-path'}/busybox/busybox",
"$self->{stage1aDir}/bin");
# determine all required libraries and copy those, too:
vlog 2, "calling slxldd for busybox";
my $requiredLibsStr = `slxldd $openslxConfig{'share-path'}/busybox/busybox`;
chomp $requiredLibsStr;
vlog 2, "slxldd results:\n$requiredLibsStr";
foreach my $lib (split "\n", $requiredLibsStr) {
vlog 3, "copying lib '$lib'";
my $libDir = dirname($lib);
copyFile($lib, "$self->{stage1aDir}/$libDir");
}
# create all needed links to busybox:
my $links
= slurpFile("$openslxConfig{'share-path'}/busybox/busybox.links");
foreach my $linkTarget (split "\n", $links) {
linkFile('/bin/busybox', "$self->{stage1aDir}/$linkTarget");
}
}
sub stage1A_setupResolver
{
my $self = shift;
copyFile('/etc/resolv.conf', "$self->{stage1aDir}/etc");
copyFile('/lib/libresolv*', "$self->{stage1aDir}/lib");
copyFile('/lib/libnss_dns*', "$self->{stage1aDir}/lib");
my $stage1cDir
= "$self->{'stage1aDir'}/$self->{'stage1bSubdir'}/$self->{'stage1cSubdir'}";
copyFile('/etc/resolv.conf', "$stage1cDir/etc");
}
sub stage1A_copyPrerequiredFiles
{
my $self = shift;
return unless -d "$self->{'distro-info-dir'}/prereqfiles";
vlog 2, "copying folder with pre-required files...";
my $stage1cDir
= "$self->{'stage1aDir'}/$self->{'stage1bSubdir'}/$self->{'stage1cSubdir'}";
my $cmd = qq[
tar --exclude=.svn -cp -C $self->{'distro-info-dir'}/prereqfiles . \\
| tar -xp -C $stage1cDir
];
if (system($cmd)) {
die _tr("unable to copy folder with pre-required files to folder <%s> (%s)",
$stage1cDir, $!);
}
$self->{distro}->fixPrerequiredFiles($stage1cDir);
}
sub stage1A_copyTrustedPackageKeys
{
my $self = shift;
return unless -d "$self->{'distro-info-dir'}/trusted-package-keys";
vlog 2, "copying folder with trusted package keys...";
my $stage1bDir
= "$self->{'stage1aDir'}/$self->{'stage1bSubdir'}";
my $cmd = qq[
tar --exclude=.svn -cp -C $self->{'distro-info-dir'} trusted-package-keys \\
| tar -xp -C $stage1bDir
];
if (system($cmd)) {
die _tr("unable to copy folder with trusted package keys to folder <%s> (%s)",
$stage1bDir, $!);
}
system("chmod 444 $stage1bDir/trusted-package-keys/*");
# install ultimately trusted keys (from distributor):
my $stage1cDir
= "$stage1bDir/$self->{'stage1cSubdir'}";
my $keyDir = "$self->{'distro-info-dir'}/trusted-package-keys";
copyFile("$keyDir/pubring.gpg", "$stage1cDir/usr/lib/rpm/gnupg");
}
sub stage1A_createRequiredFiles
{
my $self = shift;
vlog 2, "creating required files...";
# fake all files required by stage1b (by creating them empty):
my $stage1bDir
= "$self->{'stage1aDir'}/$self->{'stage1bSubdir'}";
foreach my $fake (@{$self->{distro}->{'stage1b-faked-files'}}) {
fakeFile("$stage1bDir/$fake");
}
# fake all files required by stage1c (by creating them empty):
my $stage1cDir
= "$stage1bDir/$self->{'stage1cSubdir'}";
foreach my $fake (@{$self->{distro}->{'stage1c-faked-files'}}) {
fakeFile("$stage1cDir/$fake");
}
mkdir "$stage1cDir/dev";
if (system("mknod $stage1cDir/dev/null c 1 3")) {
die _tr("unable to create node <%s> (%s)", "$stage1cDir/dev/null", $!);
}
}
sub stage1B_chrootAndBootstrap
{
my $self = shift;
vlog 2, "chrooting into $self->{stage1aDir}...";
# chdir into stage1aDir...
chdir $self->{stage1aDir}
or die _tr("unable to chdir into <%s> (%s)", $self->{stage1aDir}, $!);
# ...do chroot
chroot "."
or die _tr("unable to chroot into <%s> (%s)", $self->{stage1aDir}, $!);
$ENV{PATH} = "/bin:/sbin:/usr/bin:/usr/sbin";
# chdir into slxbootstrap, as we want to drop packages into there:
chdir "/$self->{stage1bSubdir}"
or die _tr("unable to chdir into <%s> (%s)", "/$self->{stage1bSubdir}", $!);
# fetch prerequired packages:
my $baseURL
= $self->selectBaseURL($self->{'distro-info'}->{repository}->{base});
my $pkgDirURL = $baseURL;
if (length($self->{'distro-info'}->{'package-subdir'})) {
$pkgDirURL .= "/$self->{'distro-info'}->{'package-subdir'}";
}
my @pkgs = string2Array($self->{'distro-info'}->{'prereq-packages'});
my @prereqPkgs = downloadFilesFrom(\@pkgs, $pkgDirURL);
$self->{packager}->unpackPackages(\@prereqPkgs);
@pkgs = string2Array($self->{'distro-info'}->{'bootstrap-prereq-packages'});
my @bootstrapPrereqPkgs = downloadFilesFrom(\@pkgs, $pkgDirURL);
$self->{'local-bootstrap-prereq-packages'} = \@bootstrapPrereqPkgs;
@pkgs = string2Array($self->{'distro-info'}->{'bootstrap-packages'});
my @bootstrapPkgs = downloadFilesFrom(\@pkgs, $pkgDirURL);
my @allPkgs = (@prereqPkgs, @bootstrapPrereqPkgs, @bootstrapPkgs);
$self->{'local-bootstrap-packages'} = \@allPkgs;
}
sub stage1C_chrootAndInstallBasicSystem
{
my $self = shift;
my $stage1bDir = "/$self->{stage1bSubdir}";
vlog 2, "chrooting into $stage1bDir...";
# chdir into stage1bDir...
chdir $stage1bDir
or die _tr("unable to chdir into <%s> (%s)", $stage1bDir, $!);
# ...do chroot
chroot "."
or die _tr("unable to chroot into <%s> (%s)", $stage1bDir, $!);
$ENV{PATH} = "/bin:/sbin:/usr/bin:/usr/sbin";
my $stage1cDir = "/$self->{stage1cSubdir}";
# install all prerequired bootstrap packages
$self->{packager}->installPrerequiredPackages(
$self->{'local-bootstrap-prereq-packages'}, $stage1cDir
);
# import any additional trusted package keys to rpm-DB:
my $keyDir = "/trusted-package-keys";
opendir(KEYDIR, $keyDir)
or die _tr("unable to opendir <%s> (%s)", $keyDir, $!);
my @keyFiles
= map { "$keyDir/$_" }
grep { $_ !~ m[^(\.\.?|pubring.gpg)$] }
readdir(KEYDIR);
closedir(KEYDIR);
$self->{packager}->importTrustedPackageKeys(\@keyFiles, $stage1cDir);
# install all other bootstrap packages
$self->{packager}->installPackages(
$self->{'local-bootstrap-packages'}, $stage1cDir
);
}
sub stage1C_cleanupBasicSystem
{
my $self = shift;
my $stage1cDir
= "$self->{'stage1aDir'}/$self->{'stage1bSubdir'}/$self->{'stage1cSubdir'}";
if (system("mv $stage1cDir/* $self->{'system-path'}/")) {
die _tr("unable to move final setup to <%s> (%s)",
$self->{'system-path'}, $!);
}
if (system("rm -rf $self->{stage1aDir}")) {
die _tr("unable to remove temporary folder <%s> (%s)",
$self->{stage1aDir}, $!);
}
}
sub stage1D_setupPackageSources()
{
my $self = shift;
vlog 1, "setting up package sources for meta packager...";
my ($rk, $repo);
while(($rk, $repo) = each %{$self->{'distro-info'}->{repository}}) {
vlog 2, "setting up package source $rk...";
$self->{'meta-packager'}->setupPackageSource($rk, $repo);
}
}
sub stage1D_updateBasicSystem()
{
my $self = shift;
# chdir into systemDir...
my $systemDir = $self->{'system-path'};
vlog 2, "chrooting into $systemDir...";
chdir $systemDir
or die _tr("unable to chdir into <%s> (%s)", $systemDir, $!);
# ...do chroot
chroot "."
or die _tr("unable to chroot into <%s> (%s)", $systemDir, $!);
vlog 1, "updating basic system...";
$self->{'meta-packager'}->updateBasicSystem();
}
sub stage1D_installPackageSelection
{
my $self = shift;
vlog 1, "installing package selection...";
}
################################################################################
### utility functions
################################################################################
sub copyFile
{
my $fileName = shift;
my $dirName = shift;
my $baseName = basename($fileName);
my $targetName = "$dirName/$baseName";
if (!-e $targetName) {
my $targetDir = dirname($targetName);
system("mkdir -p $targetDir") unless -d $targetDir;
if (system("cp -p $fileName $targetDir/")) {
die _tr("unable to copy file '%s' to dir '%s' (%s)",
$fileName, $targetDir, $!);
}
}
}
sub fakeFile
{
my $fullPath = shift;
my $targetDir = dirname($fullPath);
system("mkdir", "-p", $targetDir) unless -d $targetDir;
if (system("touch", $fullPath)) {
die _tr("unable to create file '%s' (%s)",
$fullPath, $!);
}
}
sub linkFile
{
my $linkTarget = shift;
my $linkName = shift;
my $targetDir = dirname($linkName);
system("mkdir -p $targetDir") unless -d $targetDir;
if (system("ln -s $linkTarget $linkName")) {
die _tr("unable to create link '%s' to '%s' (%s)",
$linkName, $linkTarget, $!);
}
}
sub slurpFile
{
my $file = shift;
open(F, "< $file")
or die _tr("could not open file '%s' for reading! (%s)", $file, $!);
$/ = undef;
my $text = <F>;
close(F);
return $text;
}
sub string2Array
{
my $str = shift;
return
grep { length($_) > 0 }
map { $_ =~ s[^\s*(.+?)\s*$][$1]; $_ }
split "\n", $str;
}
sub downloadFilesFrom
{
my $files = shift;
my $baseURL = shift;
my @foundFiles;
foreach my $fileVariantStr (@$files) {
next unless $fileVariantStr =~ m[\S];
my $foundFile;
foreach my $file (split '\s+', $fileVariantStr) {
vlog 2, "fetching <$file>...";
if (system("wget", "$baseURL/$file") == 0) {
$foundFile = basename($file);
last;
}
}
if (!defined $foundFile) {
die _tr("unable to fetch <%s> from <%s> (%s)", $fileVariantStr,
$baseURL, $!);
}
push @foundFiles, $foundFile;
}
return @foundFiles;
}
1;
################################################################################
=pod
=head1 NAME
OpenSLX::OSSetup::System::Base - the base class for all OSSetup backends
=head1 SYNOPSIS
package OpenSLX::OSSetup::coolnewOS;
use vars qw(@ISA $VERSION);
@ISA = ('OpenSLX::OSSetup::Base');
$VERSION = 1.01;
use coolnewOS;
sub new
{
my $class = shift;
my $self = {};
return bless $self, $class;
}
# override all methods of OpenSLX::OSSetup::Base in order to implement
# a full OS-setup backend
...
I<The synopsis above outlines a class that implements a
OSSetup backend for the (imaginary) operating system B<coolnewOS>>
=head1 DESCRIPTION
This class defines the OSSetup interface for the OpenSLX.
Aim of the OSSetup abstraction is to make it possible to install a large set
of different operating systems transparently.
...
=cut