summaryrefslogtreecommitdiffstats
path: root/src/installer/OpenSLX/OSSetup/Engine.pm
diff options
context:
space:
mode:
Diffstat (limited to 'src/installer/OpenSLX/OSSetup/Engine.pm')
-rw-r--r--src/installer/OpenSLX/OSSetup/Engine.pm1929
1 files changed, 1929 insertions, 0 deletions
diff --git a/src/installer/OpenSLX/OSSetup/Engine.pm b/src/installer/OpenSLX/OSSetup/Engine.pm
new file mode 100644
index 00000000..642a058b
--- /dev/null
+++ b/src/installer/OpenSLX/OSSetup/Engine.pm
@@ -0,0 +1,1929 @@
+# Copyright (c) 2006..2010 - OpenSLX GmbH
+#
+# This program is free software distributed under the GPL version 2.
+# See http://openslx.org/COPYING
+#
+# If you have any feedback please consult http://openslx.org/feedback and
+# send your suggestions, praise, or complaints to feedback@openslx.org
+#
+# General information about OpenSLX can be found at http://openslx.org/
+# -----------------------------------------------------------------------------
+# Engine.pm
+# - provides driver engine for the OSSetup API.
+# -----------------------------------------------------------------------------
+package OpenSLX::OSSetup::Engine;
+
+use strict;
+use warnings;
+
+our (@ISA, @EXPORT, $VERSION);
+$VERSION = 1.01; # API-version . implementation-version
+
+use Exporter;
+@ISA = qw(Exporter);
+
+@EXPORT = qw(
+ %supportedDistros
+);
+
+use Config::General;
+use File::Basename;
+use File::Path;
+use URI;
+
+use OpenSLX::Basics;
+use OpenSLX::ScopedResource;
+use OpenSLX::Syscall;
+use OpenSLX::Utils;
+
+use vars qw(%supportedDistros);
+
+%supportedDistros = (
+ 'debian-3.1' => 'clone,install,update,shell',
+ 'debian-4.0' => 'clone,install,update,shell',
+ 'debian-4.0_amd64' => 'clone,install,update,shell',
+ 'debian-5.0' => 'clone,update,shell',
+ 'fedora-6' => 'clone,install,update,shell',
+ 'fedora-6_x86_64' => 'clone,install,update,shell',
+ 'gentoo-2007.X' => 'clone',
+ 'suse-10.1' => 'clone,install,update,shell',
+ 'suse-10.1_x86_64' => 'clone,install,update,shell',
+ 'suse-10.2' => 'clone,install,update,shell',
+ 'suse-10.2_x86_64' => 'clone,install,update,shell',
+ 'suse-10.3' => 'clone,install,update,shell',
+ 'suse-10.3_x86_64' => 'clone,update,shell',
+ 'suse-11.0' => 'clone,install,update,shell',
+ 'suse-11.0_x86_64' => 'clone,update,shell',
+ 'suse-11.1' => 'clone,install,update,shell',
+ 'suse-11.1_x86_64' => 'clone,update,shell',
+ 'suse-11.2' => 'clone,install,update,shell',
+ 'suse-11.2_x86_64' => 'clone,update,shell',
+ 'suse-11.3' => 'clone,update,shell',
+ 'suse-11.3_x86_64' => 'clone,update,shell',
+ 'scilin-4.7' => 'clone,update,shell',
+ 'scilin-5.3' => 'clone,update,shell',
+ 'scilin-5.4_x86_64' => 'clone,update,shell',
+ 'scilin-5.5_x86_64' => 'clone,update,shell',
+ 'ubuntu-8.04' => 'clone,install,update,shell',
+ 'ubuntu-8.04_amd64' => 'clone,update,shell',
+ 'ubuntu-8.10' => 'clone,install,update,shell',
+ 'ubuntu-8.10_amd64' => 'clone,update,shell',
+ 'ubuntu-9.04' => 'clone,install,update,shell',
+ 'ubuntu-9.04_amd64' => 'clone,update,shell',
+ 'ubuntu-9.10' => 'clone,install,update,shell',
+ 'ubuntu-9.10_amd64' => 'clone,update,shell',
+ 'ubuntu-10.04' => 'clone,install,update,shell',
+ 'ubuntu-10.04_amd64'=> 'clone,update,shell',
+);
+
+my %localHttpServers;
+my $filelisting;
+
+################################################################################
+### interface methods
+################################################################################
+sub new
+{
+ my $class = shift;
+
+ my $self = {};
+
+ return bless $self, $class;
+}
+
+sub initialize
+{
+ my $self = shift;
+ my $vendorOSName = shift;
+ my $actionType = shift;
+
+ if ($vendorOSName eq '<<<default>>>') {
+ die _tr("you can't do that with the default vendor-OS!\n");
+ }
+ if ($vendorOSName !~ m[^([^\-]+\-[^\-]+)(?:\-(.+))?]) {
+ die _tr(
+ "Given vendor-OS has unknown format, " .
+ "expected '<name>-<release>[-<selection>]'\n"
+ );
+ }
+ my $distroName = lc($1);
+ my $selectionName = $2 || 'default';
+ $self->{'vendor-os-name'} = $vendorOSName;
+ $self->{'action-type'} = $actionType;
+ $self->{'distro-name'} = $distroName;
+ $self->{'selection-name'} = $selectionName;
+ $self->{'clone-source'} = '';
+ if (!exists $supportedDistros{$distroName}) {
+ print _tr("Sorry, distro '%s' is unsupported.\n", $distroName);
+ print _tr("List of supported distros:\n\t");
+ print join("\n\t", sort keys %supportedDistros) . "\n";
+ exit 1;
+ }
+ my $support = $supportedDistros{$distroName};
+ if ($actionType eq 'install' && $support !~ m[install]i) {
+ print _tr(
+ "Sorry, distro '%s' can not be installed, only cloned!\n",
+ $distroName
+ );
+ exit 1;
+ }
+ elsif ($actionType eq 'update' && $support !~ m[(update)]i) {
+ print _tr(
+ "Sorry, update support for vendor-OS '%s' has not been " .
+ "implemented!\n",
+ $distroName
+ );
+ exit 1;
+ }
+ elsif ($actionType eq 'shell' && $support !~ m[(shell)]i) {
+ print _tr(
+ "Sorry, vendor-OS '%s' has no support for chrooted shells " .
+ "available!\n",
+ $distroName
+ );
+ exit 1;
+ }
+
+ # load module for the requested distro:
+ my $distro = loadDistroModule({
+ distroName => $distroName,
+ distroScope => 'OpenSLX::OSSetup::Distro',
+ fallbackName => 'Any_Clone',
+ });
+ if (!$distro) {
+ die _tr(
+ 'unable to load any OSSetup::Distro module for vendor-OS %s!',
+ $vendorOSName
+ );
+ }
+
+ $distro->initialize($self);
+ $self->{distro} = $distro;
+
+ # protect against parallel executions of writing OpenSLX scripts
+ $self->{'vendor-os-lock'} = grabLock($vendorOSName);
+
+ if ($actionType =~ m{^(install|update|shell|plugin)}) {
+ # setup path to distribution-specific info:
+ my $sharedDistroInfoDir
+ = "$openslxConfig{'base-path'}/share/distro-info/" .
+ "$self->{'distro-name'}";
+ if (!-d $sharedDistroInfoDir) {
+ die _tr(
+ "unable to find shared distro-info in '%s'\n",
+ $sharedDistroInfoDir
+ );
+ }
+ $self->{'shared-distro-info-dir'} = $sharedDistroInfoDir;
+ my $configDistroInfoDir =
+ "$openslxConfig{'config-path'}/distro-info/$self->{'distro-name'}";
+ if (!-d $configDistroInfoDir) {
+ die _tr(
+ "unable to find configurable distro-info in '%s'\n",
+ $configDistroInfoDir
+ );
+ }
+ $self->{'config-distro-info-dir'} = $configDistroInfoDir;
+
+ my $setupMirrorsIfNecessary = $actionType eq 'install';
+ $self->_readDistroInfo($setupMirrorsIfNecessary);
+ }
+
+ if ($self->{'action-type'} eq 'install'
+ && !exists $self->{'distro-info'}->{'selection'}->{$selectionName})
+ {
+ die(
+ _tr(
+ "selection '%s' is unknown to distro '%s'\n",
+ $selectionName, $self->{'distro-name'}
+ )
+ . _tr("These selections are available:\n\t")
+ . join("\n\t", sort keys %{$self->{'distro-info'}->{'selection'}})
+ . "\n"
+ );
+ }
+
+ $self->{'vendor-os-path'}
+ = "$openslxConfig{'private-path'}/stage1/$self->{'vendor-os-name'}";
+ vlog(1, "vendor-OS path is '$self->{'vendor-os-path'}'");
+
+ if ($actionType =~ m{^(install|update|shell|plugin)}) {
+ $self->_createPackager();
+ $self->_createMetaPackager();
+ }
+
+ return;
+}
+
+sub installVendorOS
+{
+ my $self = shift;
+ my $vendorOSSettings = shift;
+
+ my $installInfoFile = "$self->{'vendor-os-path'}/.openslx-install-info";
+ if (-e $installInfoFile) {
+ die _tr("vendor-OS '%s' already exists, giving up!\n",
+ $self->{'vendor-os-path'});
+ }
+ $self->_createVendorOSPath();
+
+ my $httpServers = OpenSLX::ScopedResource->new({
+ name => 'local-http-servers',
+ acquire => sub { $self->_startLocalURLServersAsNeeded(); 1 },
+ release => sub { $self->_stopLocalURLServers(); 1 },
+ });
+
+ my $baseSystemFile = "$self->{'vendor-os-path'}/.openslx-base-system";
+ if (-e $baseSystemFile) {
+ vlog(0, _tr("found existing base system, continuing...\n"));
+ }
+ else {
+ # basic setup, stage1a-c:
+ $self->_setupStage1A();
+ callInSubprocess(
+ sub {
+ # some tasks that involve a chrooted environment:
+ $self->_changePersonalityIfNeeded();
+ $self->_setupStage1B();
+ $self->_setupStage1C();
+ }
+ );
+ $self->_stage1C_cleanupBasicVendorOS();
+ # just touch the file, in order to indicate a basic system:
+ slxsystem("touch $baseSystemFile");
+ }
+ callInSubprocess(
+ sub {
+ # another task that involves a chrooted environment:
+ $self->_changePersonalityIfNeeded();
+ $self->_setupStage1D();
+ }
+ );
+
+ # create the install-info file, in order to indicate a proper installation:
+ spitFile(
+ $installInfoFile,
+ "SLX_META_PACKAGER=$self->{distro}->{'meta-packager-type'}\n"
+ );
+
+ # base system info file is no longer needed, we have a full system now
+ slxsystem("rm $baseSystemFile");
+
+ $self->_applyVendorOSSettings($vendorOSSettings) unless !$vendorOSSettings;
+
+ vlog(
+ 0,
+ _tr(
+ "Vendor-OS '%s' installed succesfully.\n",
+ $self->{'vendor-os-name'}
+ )
+ );
+
+ # generate default openslx directories for the client
+ $self->_generateDefaultDirs();
+
+ # add the uclibs and tools to the stage1
+ $self->_copyUclibcRootfs();
+ #callInSubprocess(
+ # sub {
+ # $self->_callChrootedFunction({
+ # chrootDir => $self->{'vendor-os-path'},
+ # function => sub {
+ # $self->{'distro'}->addUclibLdconfig();
+ # },
+ # updateConfig => 1,
+ # });
+ #});
+ $self->_touchVendorOS();
+ $self->addInstalledVendorOSToConfigDB();
+ return;
+}
+
+sub cloneVendorOS
+{
+ my $self = shift;
+ my $source = shift;
+
+ if (substr($source, -1, 1) ne '/') {
+ # make sure source path ends with a slash, as otherwise, the
+ # last folder would be copied (but we only want its contents).
+ $source .= '/';
+ }
+
+ $self->{'clone-source'} = $source;
+ my $lastCloneSource = '';
+ my $cloneInfoFile = "$self->{'vendor-os-path'}/.openslx-clone-info";
+ my $isReClone;
+ if (-e $self->{'vendor-os-path'}) {
+ my $installInfoFile = "$self->{'vendor-os-path'}/.openslx-install-info";
+ if (-e $installInfoFile) {
+ # oops, given vendor-os has been installed, not cloned, we complain:
+ croak(
+ _tr(
+ "The vendor-OS '%s' exists but it is no clone, refusing " .
+ "to clobber!\nPlease delete the folder manually, if " .
+ "that's really what you want...\n",
+ $self->{'vendor-os-path'}
+ )
+ );
+ }
+ elsif (-e $cloneInfoFile) {
+ # check if last and current source match:
+ my $cloneInfo = slurpFile($cloneInfoFile);
+ if ($cloneInfo =~ m[^source\s*=\s*(.+?)\s*$]ims) {
+ $lastCloneSource = $1;
+ }
+ if ($source ne $lastCloneSource) {
+ # protect user from confusing sources (still allowed, though):
+ my $yes = _tr('yes');
+ my $no = _tr('no');
+ print _tr(
+ "Last time this vendor-OS was cloned, it has been cloned " .
+ "from '%s', now you specified a different source: '%s'\n" .
+ "Would you still like to proceed (%s/%s)?",
+ $lastCloneSource, $source, $yes, $no
+ );
+ my $answer = <STDIN>;
+ exit 5 unless $answer =~ m[^\s*$yes]i;
+ }
+ $isReClone = 1;
+ }
+ else {
+ # Neither the install-info nor the clone-info file exists. This
+ # probably means that the folder has been created by an older
+ # version of the tools. There's not much we can do, we simply
+ # trust our user and assume that he knows what he's doing.
+ }
+ }
+
+ $self->_createVendorOSPath();
+
+ $self->_clone_fetchSource($source);
+ if ($source ne $lastCloneSource) {
+ spitFile($cloneInfoFile, "source=$source\n");
+ }
+ if ($isReClone) {
+ vlog(
+ 0,
+ _tr(
+ "Vendor-OS '%s' has been re-cloned succesfully.\n",
+ $self->{'vendor-os-name'}
+ )
+ );
+ }
+ else {
+ vlog(
+ 0,
+ _tr(
+ "Vendor-OS '%s' has been cloned succesfully.\n",
+ $self->{'vendor-os-name'}
+ )
+ );
+ }
+
+ # generate default openslx directories for the client
+ $self->_generateDefaultDirs();
+
+ # add the uclibs and tools to the stage1
+ $self->_copyUclibcRootfs();
+ $self->_touchVendorOS();
+ $self->addInstalledVendorOSToConfigDB();
+ return;
+}
+
+sub updateVendorOS
+{
+ my $self = shift;
+
+ if (!-e $self->{'vendor-os-path'}) {
+ die _tr("can't update vendor-OS '%s', since it doesn't exist!\n",
+ $self->{'vendor-os-path'});
+ }
+
+ my $httpServers = OpenSLX::ScopedResource->new({
+ name => 'local-http-servers',
+ acquire => sub { $self->_startLocalURLServersAsNeeded(); 1 },
+ release => sub { $self->_stopLocalURLServers(); 1 },
+ });
+
+ callInSubprocess(
+ sub {
+ $self->_changePersonalityIfNeeded();
+ $self->_updateStage1D();
+ }
+ );
+
+ $self->_generateDefaultDirs();
+ $self->_copyUclibcRootfs();
+ $self->_touchVendorOS();
+ vlog(
+ 0,
+ _tr("Vendor-OS '%s' updated succesfully.\n", $self->{'vendor-os-name'})
+ );
+
+ $self->_installPlugins();
+
+ return;
+}
+
+sub startChrootedShellForVendorOS
+{
+ my $self = shift;
+
+ if (!-e $self->{'vendor-os-path'}) {
+ die _tr(
+ "can't start chrooted shell for vendor-OS '%s', since it doesn't " .
+ "exist!\n",
+ $self->{'vendor-os-path'}
+ );
+ }
+
+ my $httpServers = OpenSLX::ScopedResource->new({
+ name => 'local-http-servers',
+ acquire => sub { $self->_startLocalURLServersAsNeeded(); 1 },
+ release => sub { $self->_stopLocalURLServers(); 1 },
+ });
+
+ callInSubprocess(
+ sub {
+ $self->_changePersonalityIfNeeded();
+ $self->_startChrootedShellInStage1D();
+ }
+ );
+
+ vlog(
+ 0,
+ _tr(
+ "Chrooted shell for vendor-OS '%s' has been closed.\n",
+ $self->{'vendor-os-name'}
+ )
+ );
+ $self->_touchVendorOS();
+
+ return;
+}
+
+sub callChrootedFunctionForVendorOS
+{
+ my $self = shift;
+ my $function = shift;
+ my $updateConfig = shift || 0;
+
+ if (!-e $self->{'vendor-os-path'}) {
+ die _tr(
+ "can't call chrooted function for vendor-OS '%s', since it " .
+ "doesn't exist!\n",
+ $self->{'vendor-os-path'}
+ );
+ }
+
+ # avoid trying to chroot into a 64-bit vendor-OS if the host is only 32-bit:
+ if (!$self->_hostIs64Bit() && -e "$self->{'vendor-os-path'}/lib64") {
+ die _tr("you can't use a 64-bit vendor-OS on a 32-bit host, sorry!\n");
+ }
+
+ my $httpServers = OpenSLX::ScopedResource->new({
+ name => 'local-http-servers',
+ acquire => sub { $self->_startLocalURLServersAsNeeded(); 1 },
+ release => sub { $self->_stopLocalURLServers(); 1 },
+ });
+
+ callInSubprocess(
+ sub {
+ $self->_changePersonalityIfNeeded();
+ $self->_callChrootedFunction({
+ chrootDir => $self->{'vendor-os-path'},
+ function => $function,
+ updateConfig => $updateConfig,
+ });
+ }
+ );
+
+ vlog(
+ 1,
+ _tr(
+ "Chrooted function for vendor-OS '%s' has finished.\n",
+ $self->{'vendor-os-name'}
+ )
+ );
+ $self->_touchVendorOS();
+
+ return 1;
+}
+
+sub removeVendorOS
+{
+ my $self = shift;
+
+ vlog(
+ 0,
+ _tr("removing vendor-OS folder '%s'...", $self->{'vendor-os-path'})
+ );
+ if (system(
+ "find $self->{'vendor-os-path'} -xdev -depth -print0 | xargs -0 rm -r"
+ )) {
+ vlog(
+ 0,
+ _tr("* unable to remove vendor-OS '%s'!", $self->{'vendor-os-path'})
+ );
+ }
+ else {
+ vlog(
+ 0,
+ _tr(
+ "Vendor-OS '%s' removed succesfully.\n",
+ $self->{'vendor-os-name'}
+ )
+ );
+ }
+ $self->removeVendorOSFromConfigDB();
+ return;
+}
+
+sub addInstalledVendorOSToConfigDB
+{
+ my $self = shift;
+
+ if (!-e $self->{'vendor-os-path'}) {
+ die _tr(
+ "can't import vendor-OS '%s', since it doesn't exist!\n",
+ $self->{'vendor-os-path'}
+ );
+ }
+ my $openslxDB = instantiateClass("OpenSLX::ConfigDB");
+ $openslxDB->connect();
+
+ my @plugins;
+
+ # insert new vendor-os if it doesn't already exist in DB:
+ my $vendorOSName = $self->{'vendor-os-name'};
+ my $vendorOS = $openslxDB->fetchVendorOSByFilter({'name' => $vendorOSName});
+ if (defined $vendorOS) {
+ if ($vendorOS->{'clone_source'}
+ && $self->{'clone-source'} ne $vendorOS->{'clone_source'}) {
+ $openslxDB->changeVendorOS(
+ $vendorOS->{id},
+ { 'clone_source' => $self->{'clone-source'} }
+ );
+ vlog(
+ 0,
+ _tr(
+ "Vendor-OS '%s' has been updated in OpenSLX-database.\n",
+ $vendorOSName
+ )
+ );
+ }
+ else {
+ vlog(
+ 0,
+ _tr(
+ "No need to change vendor-OS '%s' in OpenSLX-database.\n",
+ $vendorOSName
+ )
+ );
+ }
+ # fetch installed plugins of this vendor-OS in order to reinstall them
+ @plugins = $openslxDB->fetchInstalledPlugins($vendorOS->{id});
+ }
+ else {
+ my $data = { 'name' => $vendorOSName };
+ if (length($self->{'clone-source'})) {
+ $data->{'clone_source'} = $self->{'clone-source'};
+ }
+ my $id = $openslxDB->addVendorOS($data);
+
+ vlog(
+ 0,
+ _tr(
+ "Vendor-OS '%s' has been added to DB (ID=%s).\n",
+ $vendorOSName, $id
+ )
+ );
+ # fetch plugins from default vendor-OS in order to install those into
+ # this new one
+ @plugins = $openslxDB->fetchInstalledPlugins(0);
+ }
+
+ $openslxDB->disconnect();
+
+ # now that we have the list of plugins, we (re-)install all of them:
+ $self->_installPlugins(\@plugins, defined $vendorOS);
+
+ return;
+}
+
+sub removeVendorOSFromConfigDB
+{
+ my $self = shift;
+
+ my $openslxDB = instantiateClass("OpenSLX::ConfigDB");
+ $openslxDB->connect();
+
+ my $vendorOSName = $self->{'vendor-os-name'};
+ my $vendorOS = $openslxDB->fetchVendorOSByFilter({'name' => $vendorOSName});
+ if (!defined $vendorOS) {
+ vlog(
+ 0,
+ _tr(
+ "Vendor-OS '%s' didn't exist in OpenSLX-database.\n",
+ $vendorOSName
+ )
+ );
+ }
+ else {
+ # remove all exports (and systems) using this vendor-OS and then
+ # remove the vendor-OS itself:
+ my @exports = $openslxDB->fetchExportByFilter(
+ {'vendor_os_id' => $vendorOS->{id}});
+ foreach my $export (@exports) {
+ my $osExportEngine = instantiateClass("OpenSLX::OSExport::Engine");
+ $osExportEngine->initializeFromExisting($export->{name});
+ vlog(
+ 0,
+ _tr(
+ "purging export '%s', since it belongs to the vendor-OS " .
+ "being deleted...",
+ $export->{name}
+ )
+ );
+ $osExportEngine->purgeExport();
+ }
+
+ $openslxDB->removeVendorOS($vendorOS->{id});
+ vlog(
+ 0,
+ _tr("Vendor-OS '%s' has been removed from DB!\n", $vendorOSName)
+ );
+ }
+
+ $openslxDB->disconnect();
+ return;
+}
+
+sub pickKernelFile
+{
+ my $self = shift;
+
+ return $self->{distro}->pickKernelFile(@_);
+}
+
+sub distroName
+{
+ my $self = shift;
+
+ return $self->{'distro-name'};
+}
+
+sub metaPackager
+{
+ my $self = shift;
+
+ return $self->{'meta-packager'};
+}
+
+sub packager
+{
+ my $self = shift;
+
+ return $self->{'packager'};
+}
+
+sub getInstallablePackagesForSelection
+{
+ my $self = shift;
+ my $selKey = shift;
+
+ return if !$selKey;
+
+ my $selection = $self->{'distro-info'}->{selection}->{$selKey};
+ return if !$selection;
+
+ my @pkgs = split m{\s+}, $selection->{packages};
+ my %installedPkgs;
+ @installedPkgs{ $self->{'packager'}->getInstalledPackages() } = ();
+ @pkgs = grep { !exists $installedPkgs{$_} } @pkgs;
+
+ return join ' ', @pkgs;
+}
+
+sub busyboxBinary
+{
+ my $self = shift;
+
+ my $uclibdir = "$openslxConfig{'base-path'}/share/uclib-rootfs";
+
+ return sprintf(
+ "LD_LIBRARY_PATH=%s/lib %s/bin/busybox",
+ $uclibdir,
+ $uclibdir
+ );
+}
+
+################################################################################
+### implementation methods
+################################################################################
+sub _readDistroInfo
+{
+ my $self = shift;
+ my $setupMirrorsIfNecessary = shift || 0;
+
+ vlog(1, "reading configuration info for $self->{'vendor-os-name'}...");
+
+ $self->{'distro-info'} = {
+ 'package-subdir' => '',
+ 'prereq-packages' => '',
+ 'bootstrap-packages' => '',
+ 'metapackager' => {},
+ 'repository' => {},
+ 'selection' => {},
+ 'excludes' => {},
+ };
+
+ # merge user-provided configuration with distro defaults
+ foreach my $file (
+ "$self->{'shared-distro-info-dir'}/settings.default",
+ "$self->{'config-distro-info-dir'}/settings"
+ ) {
+ if (-e $file) {
+ vlog(2, "reading configuration file $file...");
+ my $configObject = Config::General->new(
+ -AllowMultiOptions => 0,
+ -AutoTrue => 1,
+ -ConfigFile => $file,
+ -LowerCaseNames => 1,
+ -SplitPolicy => 'equalsign',
+ );
+ my %config = $configObject->getall();
+ mergeHash($self->{'distro-info'}, \%config);
+ }
+ }
+
+ # fetch mirrors for all repositories (if requested):
+ foreach my $repoKey (keys %{$self->{'distro-info'}->{repository}}) {
+ my $repo = $self->{'distro-info'}->{repository}->{$repoKey};
+ $repo->{key} = $repoKey;
+ # if there is local URL, only that is used, otherwise we fetch the
+ # configured mirrors:
+ if (!$repo->{'local-url'}) {
+ $repo->{urls} = $self->_fetchConfiguredMirrorsForRepository(
+ $repo, $setupMirrorsIfNecessary
+ );
+ }
+ }
+
+ # expand all selections:
+ my $seen = {};
+ foreach my $selKey (keys %{$self->{'distro-info'}->{selection}}) {
+ $self->_expandSelection($selKey, $seen);
+ }
+
+ # dump distro-info, if asked for:
+ if ($openslxConfig{'log-level'} >= 2) {
+ my $repository = $self->{'distro-info'}->{repository};
+ foreach my $r (sort keys %$repository) {
+ vlog(2, "repository '$r':");
+ foreach my $k (sort keys %{$repository->{$r}}) {
+ vlog(3, "\t$k = '$repository->{$r}->{$k}'");
+ }
+ }
+ my $selection = $self->{'distro-info'}->{selection};
+ foreach my $s (sort keys %$selection) {
+ vlog(2, "selection '$s':");
+ foreach my $k (sort keys %{$selection->{$s}}) {
+ vlog(3, "\t$k = '$selection->{$s}->{$k}'");
+ }
+ }
+ my $excludes = $self->{'distro-info'}->{excludes};
+ foreach my $e (sort keys %$excludes) {
+ vlog(2, "excludes for '$e':");
+ foreach my $k (sort keys %{$excludes->{$e}}) {
+ vlog(3, "\t$k = '$excludes->{$e}->{$k}'");
+ }
+ }
+ }
+ return;
+}
+
+sub _fetchConfiguredMirrorsForRepository
+{
+ my $self = shift;
+ my $repoInfo = shift;
+ my $setupMirrorsIfNecessary = shift;
+
+ my $configuredMirrorsFile
+ = "$self->{'config-distro-info-dir'}/mirrors/$repoInfo->{key}";
+ if (!-e $configuredMirrorsFile) {
+ return '' if !$setupMirrorsIfNecessary;
+ vlog(0,
+ _tr(
+ "repo '%s' has no configured mirrors, let's pick some ...",
+ $repoInfo->{name}
+ )
+ );
+ $self->_configureBestMirrorsForRepository($repoInfo);
+ }
+ vlog(2, "reading configured mirrors file '$configuredMirrorsFile'.");
+ my $configObject = Config::General->new(
+ -AllowMultiOptions => 0,
+ -AutoTrue => 1,
+ -ConfigFile => $configuredMirrorsFile,
+ -LowerCaseNames => 1,
+ -SplitPolicy => 'equalsign',
+ );
+ my %config = $configObject->getall();
+
+ return $config{urls};
+}
+
+sub _configureBestMirrorsForRepository
+{
+ my $self = shift;
+ my $repoInfo = shift;
+
+ my $configuredMirrorsFile
+ = "$self->{'config-distro-info-dir'}/mirrors/$repoInfo->{key}";
+
+ if (!-e "$self->{'config-distro-info-dir'}/mirrors") {
+ mkdir "$self->{'config-distro-info-dir'}/mirrors";
+ }
+
+ my $allMirrorsFile
+ = "$self->{'shared-distro-info-dir'}/mirrors/$repoInfo->{key}";
+ my @allMirrors = string2Array(scalar slurpFile($allMirrorsFile));
+
+ my $mirrorsToTryCount = $openslxConfig{'mirrors-to-try-count'} || 20;
+ my $mirrorsToUseCount = $openslxConfig{'mirrors-to-use-count'} || 5;
+ vlog(1,
+ _tr(
+ "selecting the '%s' best mirrors (from a set of '%s') for repo " .
+ "'%s' ...",
+ $mirrorsToUseCount, $mirrorsToTryCount, $repoInfo->{key}
+ )
+ );
+
+ # determine own top-level domain:
+ my $topLevelDomain;
+ if (defined $openslxConfig{'mirrors-preferred-top-level-domain'}) {
+ $topLevelDomain
+ = lc($openslxConfig{'mirrors-preferred-top-level-domain'});
+ }
+ else {
+ my $FQDN = getFQDN();
+ $FQDN =~ m{\.(\w+)$};
+ $topLevelDomain = lc($1);
+ }
+
+ # select up to $mirrorsToTryCount "close" mirrors from the array ...
+ my @tryMirrors
+ = grep {
+ my $uri = URI->new($_);
+ my $host = $uri->host();
+ $host =~ m{\.(\w+)$} && lc($1) eq $topLevelDomain;
+ }
+ @allMirrors;
+
+ my $tryList = join("\n\t", @tryMirrors);
+ vlog(1,
+ _tr(
+ "mirrors matching the preferred top level domain ('%s'):\n\t%s\n",
+ $topLevelDomain, $tryList
+ )
+ );
+
+ if (@tryMirrors > $mirrorsToTryCount) {
+ # shrink array to $mirrorsToTryCount elements
+ vlog(1, _tr("shrinking list to %s mirrors\n", $mirrorsToTryCount));
+ $#tryMirrors = $mirrorsToTryCount;
+ }
+ elsif (@tryMirrors < $mirrorsToTryCount) {
+ # we need more mirrors, try adding some others randomly:
+ vlog(1,
+ _tr(
+ "filling list with %s more random mirrors:\n",
+ $mirrorsToTryCount - @tryMirrors
+ )
+ );
+
+ # fill @untriedMirrors with the mirrors not already contained
+ # in @tryMirrors ...
+ my @untriedMirrors
+ = grep {
+ my $mirror = $_;
+ !grep { $mirror eq $_ } @tryMirrors;
+ } @allMirrors;
+
+ # ... and pick randomly until we have reached the limit or there are
+ # no more unused mirrors left
+ foreach my $count (@tryMirrors..$mirrorsToTryCount-1) {
+ last if !@untriedMirrors;
+ my $index = int(rand(scalar @untriedMirrors));
+ my $randomMirror = splice(@untriedMirrors, $index, 1);
+ push @tryMirrors, $randomMirror;
+ vlog(1, "\t$randomMirror\n");
+ }
+ }
+
+ # just make sure we are not going to try/use more mirros than we have
+ # available
+ if ($mirrorsToTryCount > @tryMirrors) {
+ $mirrorsToTryCount = @tryMirrors;
+ }
+ if ($mirrorsToUseCount > $mirrorsToTryCount) {
+ $mirrorsToUseCount = $mirrorsToTryCount;
+ }
+
+ # ... fetch a file from all of these mirrors and measure the time taken ...
+ vlog(0,
+ _tr(
+ "testing %s mirrors to determine the fastest %s ...\n",
+ $mirrorsToTryCount, $mirrorsToUseCount
+ )
+ );
+ my %mirrorSpeed;
+ my $veryGoodSpeedCount = 0;
+ foreach my $mirror (@tryMirrors) {
+ if ($veryGoodSpeedCount >= $mirrorsToUseCount) {
+ # we already have enough mirrors with very good speed,
+ # it makes no sense to test any others. We simply set the
+ # time of the remaining mirrors to some large value, so they
+ # won't get picked:
+ $mirrorSpeed{$mirror} = 10000;
+ next;
+ }
+
+ # test the current mirror and record the result
+ my $time = $self->_speedTestMirror(
+ $mirror, $repoInfo->{'file-for-speedtest'}
+ );
+ $mirrorSpeed{$mirror} = $time;
+ if ($time <= 1) {
+ $veryGoodSpeedCount++;
+ }
+ }
+
+ # ... now select the best (fastest) $mirrorsToUseCount mirrors ...
+ my @bestMirrors
+ = (
+ sort {
+ $mirrorSpeed{$a} <=> $mirrorSpeed{$b};
+ }
+ @tryMirrors
+ )[0..$mirrorsToUseCount-1];
+
+ vlog(0,
+ _tr(
+ "picked these '%s' mirrors for repo '%s':\n\t%s\n",
+ $mirrorsToUseCount, $repoInfo->{name}, join("\n\t", @bestMirrors)
+ )
+ );
+
+ # ... and write them into the configuration file:
+ my $configObject = Config::General->new(
+ -AllowMultiOptions => 0,
+ -AutoTrue => 1,
+ -LowerCaseNames => 1,
+ -SplitPolicy => 'equalsign',
+ );
+ $configObject->save_file($configuredMirrorsFile, {
+ 'urls' => join("\n", @bestMirrors),
+ });
+ return;
+}
+
+sub _speedTestMirror
+{
+ my $self = shift;
+ my $mirror = shift;
+ my $file = shift;
+
+ vlog(0, _tr("\ttesting mirror '%s' ...\n", $mirror));
+
+ # do an explicit DNS-lookup as we do not want to include the time that takes
+ # in the speedtest
+ my $uri = URI->new($mirror);
+ my $hostName = $uri->host();
+ if (!gethostbyname($hostName)) {
+ # unable to resolve host, we pretend it took really long
+ return 10000;
+ }
+
+ # now measure the time it takes to download the file
+ my $wgetCmd = $self->busyboxBinary();
+ $wgetCmd .= " wget -q -O - $mirror/$file >/dev/null";
+ my $start = time();
+ if (slxsystem($wgetCmd)) {
+ # just return any large number that is unlikely to be selected
+ return 10000;
+ }
+ my $time = time() - $start;
+ vlog(0, "\tfetched '$file' in $time seconds\n");
+ return $time;
+}
+
+sub _expandSelection
+{
+ my $self = shift;
+ my $selKey = shift;
+ my $seen = shift;
+
+ return if $seen->{$selKey};
+ $seen->{$selKey} = 1;
+
+ return if !exists $self->{'distro-info'}->{selection}->{$selKey};
+ my $selection = $self->{'distro-info'}->{selection}->{$selKey};
+
+ if ($selection->{base}) {
+ # add all packages from base selection(s) to the current one:
+ my $basePackages = '';
+ for my $base (split ',', $selection->{base}) {
+ my $baseSelection = $self->{'distro-info'}->{selection}->{$base}
+ or die _tr(
+ 'base-selection "%s" is unknown (referenced in "%s")!',
+ $base, $selKey
+ );
+ $self->_expandSelection($base, $seen);
+ $basePackages .= $baseSelection->{packages} || '';
+ }
+ my $packages = $selection->{packages} || '';
+ $selection->{packages} = $basePackages . "\n" . $packages;
+ }
+ return;
+}
+
+sub _applyVendorOSSettings
+{
+ my $self = shift;
+ my $vendorOSSettings = shift;
+
+ if (exists $vendorOSSettings->{'root-password'}) {
+ # hashes password according to requirements of current distro and
+ # writes it to /etc/shadow
+ $self->{distro}->setPasswordForUser(
+ 'root', $vendorOSSettings->{'root-password'}
+ );
+ }
+
+ return;
+}
+
+sub _createVendorOSPath
+{
+ my $self = shift;
+
+ if (slxsystem("mkdir -p $self->{'vendor-os-path'}")) {
+ die _tr("unable to create directory '%s', giving up! (%s)\n",
+ $self->{'vendor-os-path'}, $!);
+ }
+ return;
+}
+
+sub _touchVendorOS
+{
+ my $self = shift;
+
+ # touch root folder, as we are using this folder to determine the
+ # 'age' of the vendor-OS when trying to determine whether or not we
+ # need to re-export this vendor-OS:
+ slxsystem("touch $self->{'vendor-os-path'}");
+ return;
+}
+
+sub _generateDefaultDirs
+{
+ my $self = shift;
+ my $targetRoot = shift || $self->{'vendor-os-path'};
+
+ vlog(0, _tr("Creating default directories ...\n"));
+
+ my $OPENSLX_DEFAULT_DIR = "/opt/openslx";
+ my $OPENSLX_DEFAULT_CONFDIR = "/etc/opt/openslx";
+ my $OPENSLX_DEFAULT_LOGDIR = "/var/log/openslx";
+ my $OPENSLX_DEFAULT_VARDIR = "/var/opt/openslx";
+ my $OPENSLX_DEFAULT_BINDIR = "$OPENSLX_DEFAULT_VARDIR/bin";
+ my $OPENSLX_DEFAULT_LIBDIR = "$OPENSLX_DEFAULT_VARDIR/lib";
+ my $OPENSLX_DEFAULT_VIRTDIR = "/var/lib/virt";
+ mkpath("$targetRoot/$OPENSLX_DEFAULT_DIR", 0, 0755);
+ mkpath("$targetRoot/$OPENSLX_DEFAULT_CONFDIR", 0, 0755);
+ mkpath("$targetRoot/$OPENSLX_DEFAULT_LOGDIR", 0, 0755);
+ mkpath("$targetRoot/$OPENSLX_DEFAULT_BINDIR", 0, 0755);
+ mkpath("$targetRoot/$OPENSLX_DEFAULT_LIBDIR", 0, 0755);
+ mkpath("$targetRoot/$OPENSLX_DEFAULT_VIRTDIR", 0, 0755);
+ #system("chmod 0755 $targetRoot/$OPENSLX_DEFAULT_DIR");
+
+ my $openslxConfig = "# Default OpenSLX directories defined by satge 1\n";
+ $openslxConfig .= "# Version 0.2\n\n";
+ $openslxConfig .= "OPENSLX_DEFAULT_DIR=$OPENSLX_DEFAULT_DIR\n";
+ $openslxConfig .= "OPENSLX_DEFAULT_CONFDIR=$OPENSLX_DEFAULT_CONFDIR\n";
+ $openslxConfig .= "OPENSLX_DEFAULT_LOGDIR=$OPENSLX_DEFAULT_LOGDIR\n";
+ $openslxConfig .= "OPENSLX_DEFAULT_VARDIR=$OPENSLX_DEFAULT_VARDIR\n";
+ $openslxConfig .= "OPENSLX_DEFAULT_BINDIR=$OPENSLX_DEFAULT_BINDIR\n";
+ $openslxConfig .= "OPENSLX_DEFAULT_LIBDIR=$OPENSLX_DEFAULT_LIBDIR\n";
+ $openslxConfig .= "OPENSLX_DEFAULT_VIRTDIR=$OPENSLX_DEFAULT_VIRTDIR\n";
+
+ spitFile("$targetRoot/$OPENSLX_DEFAULT_CONFDIR/openslx.conf",
+ "$openslxConfig");
+
+ # add BINDIR to PATH
+ my $openslxDefaultPath = "PATH=$OPENSLX_DEFAULT_BINDIR:\$PATH\n";
+ $openslxDefaultPath .= "export PATH\n";
+ spitFile("$targetRoot/etc/profile.d/openslx-binpath.sh",
+ "$openslxDefaultPath");
+}
+
+sub _copyUclibcRootfs
+{
+ my $self = shift;
+ my $targetRoot = shift || $self->{'vendor-os-path'};
+ my $distro = $self->{distro};
+
+ vlog(0, _tr("copying uclibc-rootfs into vendor-OS ...\n"));
+
+ my $target = "$targetRoot/opt/openslx/uclib-rootfs";
+
+ if (system("mkdir -p $target")) {
+ die _tr("unable to create directory '%s', giving up! (%s)\n",
+ $target, $!);
+ }
+
+ my $uclibcRootfs = "$openslxConfig{'base-path'}/share/uclib-rootfs";
+ my @excludes = qw(
+ dialog
+ kexec
+ libcurses.so*
+ libncurses.so*
+ );
+ my $exclOpts = join ' ', map { "--exclude $_" } @excludes;
+ vlog(3, _tr("using exclude-filter:\n%s\n", $exclOpts));
+ my $rsyncFH;
+ my $rsyncCmd
+ = "rsync -aq --delete-excluded --exclude-from=- $uclibcRootfs/ $target";
+ vlog(2, "executing: $rsyncCmd\n");
+ # if we're doing a fresh install we need to create /lib, /bin first
+ mkdir "$targetRoot/lib";
+ mkdir "$targetRoot/bin";
+
+ open($rsyncFH, '|-', $rsyncCmd)
+ or die _tr("unable to start rsync for source '%s', giving up! (%s)",
+ $uclibcRootfs, $!);
+ print $rsyncFH $exclOpts;
+ close($rsyncFH)
+ or die _tr("unable to copy to target '%s', giving up! (%s)",
+ $target, $!);
+
+ # write version of uclibc-rootfs original into a file in order to be
+ # able to check the up-to-date state later (in the config-demuxer)
+ slxsystem("slxversion >${target}.version");
+
+ return;
+}
+
+sub _createPackager
+{
+ my $self = shift;
+
+ my $packagerClass
+ = "OpenSLX::OSSetup::Packager::$self->{distro}->{'packager-type'}";
+ my $packager = instantiateClass($packagerClass);
+ $packager->initialize($self);
+ $self->{'packager'} = $packager;
+ return;
+}
+
+sub _createMetaPackager
+{
+ my $self = shift;
+
+ my $metaPackagerType = $self->{distro}->{'meta-packager-type'};
+
+ my $installInfoFile = "$self->{'vendor-os-path'}/.openslx-install-info";
+ if (-e $installInfoFile) {
+ # activate the meta-packager that was used when installing the os:
+ my $installInfo = slurpFile($installInfoFile);
+ if ($installInfo =~ m[SLX_META_PACKAGER=(\w+)]) {
+ $metaPackagerType = $1;
+ }
+ }
+
+ my $metaPackagerClass = "OpenSLX::OSSetup::MetaPackager::$metaPackagerType";
+ my $metaPackager = instantiateClass($metaPackagerClass);
+ $metaPackager->initialize($self);
+ $self->{'meta-packager'} = $metaPackager;
+ return;
+}
+
+sub _sortRepositoryURLs
+{
+ my $self = shift;
+ my $repoInfo = shift;
+
+ my @URLs
+ = defined $repoInfo->{'local-url'}
+ ? $repoInfo->{'local-url'}
+ : string2Array($repoInfo->{urls});
+ if (!@URLs) {
+ die(
+ _tr(
+ "repository '%s' has no URLs defined, " .
+ "unable to fetch anything!",
+ $repoInfo->{name},
+ )
+ );
+ }
+
+ return \@URLs;
+}
+
+sub _downloadBaseFiles
+{
+ my $self = shift;
+ my $files = shift;
+ use vars qw(@filelisting);
+
+ my $pkgSubdir = $self->{'distro-info'}->{'package-subdir'};
+ my @URLs = @{$self->{'baseURLs'}};
+ my $maxTryCount = $openslxConfig{'ossetup-max-try-count'};
+ my @possiblefiles;
+
+ my @foundFiles;
+ foreach my $fileVariantStr (@$files) {
+ my $tryCount = 0;
+ next unless $fileVariantStr =~ m[\S];
+ my $foundFile;
+try_next_url:
+ my $url = $URLs[$self->{'baseURL-index'}];
+ $url .= "/$pkgSubdir" if length($pkgSubdir);
+
+ foreach my $file (split '\s+', $fileVariantStr) {
+ #substitute given name with real filename including version number
+ my $basefile = basename($file);
+ my $basedir = dirname($file)."/";
+ @possiblefiles = grep(/^$basefile/,@filelisting);
+ #selecting always the first occurence
+ if ($possiblefiles[0]) {
+ $basefile = $possiblefiles[0];
+ } elsif ($basefile =~ /.*\.rpm/) {
+ vlog(5,"Trying suggested name because of .rpm-ending.");
+ } else {
+ die _tr("file <$basefile> not found in list received from" .
+ "mirror");
+ }
+ vlog(2, "fetching <$basefile>...");
+ if (slxsystem("wget", "-c", "-O", "$basefile",
+ "$url/$basedir$basefile") == 0) {
+ $foundFile = $basefile;
+ last;
+ }
+ elsif (-e $basefile) {
+ vlog(0, "removing left-over '$basefile' and trying again...");
+ unlink $basefile;
+ redo;
+ }
+ }
+ if (!defined $foundFile) {
+ if ($tryCount < $maxTryCount) {
+ $tryCount++;
+ $self->{'baseURL-index'}
+ = ($self->{'baseURL-index'} + 1) % scalar(@URLs);
+ vlog(
+ 0,
+ _tr(
+ "switching to mirror '%s'.",
+ $URLs[$self->{'baseURL-index'}]
+ )
+ );
+ goto try_next_url;
+ }
+ die _tr("unable to fetch '%s' from any source!\n", $fileVariantStr);
+ }
+ push @foundFiles, $foundFile;
+ }
+ return @foundFiles;
+}
+
+sub _startLocalURLServersAsNeeded
+{
+ my $self = shift;
+
+ my $port = 5080;
+ my %portForURL;
+ foreach my $repoInfo (values %{$self->{'distro-info'}->{repository}}) {
+ my $localURL = $repoInfo->{'local-url'} || '';
+ next if !$localURL;
+ next if $localURL =~ m[^\w+:]; # anything /w a protcol-spec is non-local
+ if (!exists $localHttpServers{$localURL}) {
+ my $pid
+ = executeInSubprocess(
+ $self->busyboxBinary(), "httpd", '-p', $port, '-h', '/',
+ '-f'
+ );
+ vlog(1,
+ _tr(
+ "started local HTTP-server for URL '%s' on port '%s'.",
+ $localURL, $port
+ )
+ );
+ $repoInfo->{'local-url'} = "http://localhost:$port$localURL";
+ $localHttpServers{$localURL}->{pid} = $pid;
+ $localHttpServers{$localURL}->{url} = $repoInfo->{'local-url'};
+ $port++;
+ }
+ else {
+ $repoInfo->{'local-url'} = $localHttpServers{$localURL}->{url};
+ }
+ }
+ return;
+}
+
+sub _stopLocalURLServers
+{
+ my $self = shift;
+
+ while (my ($localURL, $serverInfo) = each %localHttpServers) {
+ vlog(1, _tr("stopping local HTTP-server for URL '%s'.", $localURL));
+ kill TERM => $serverInfo->{pid};
+ }
+}
+
+sub _setupStage1A
+{
+ my $self = shift;
+
+ vlog(1, "setting up stage1a for $self->{'vendor-os-name'}...");
+
+ # specify individual paths for the respective substages:
+ $self->{stage1aDir} = "$self->{'vendor-os-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 (slxsystem("mkdir -p $stage1cDir")) {
+ die _tr("unable to create directory '%s', giving up! (%s)\n",
+ $stage1cDir, $!);
+ }
+
+ $self->_stage1A_setupUclibcEnvironment();
+ $self->_stage1A_copyPrerequiredFiles();
+ $self->_stage1A_copyTrustedPackageKeys();
+ $self->_stage1A_createRequiredFiles();
+ return;
+}
+
+sub _stage1A_setupUclibcEnvironment
+{
+ my $self = shift;
+ $self->_copyUclibcRootfs("$self->{stage1aDir}/$self->{stage1bSubdir}");
+ my $source = "$self->{stage1bSubdir}/opt/openslx/uclib-rootfs";
+ my $target = "$self->{stage1aDir}";
+ slxsystem("ln -sf $source/bin $target/bin");
+ slxsystem("ln -sf $source/lib $target/lib");
+ slxsystem("ln -sf $source/usr $target/usr");
+ $self->_stage1A_setupResolver();
+
+ return;
+}
+
+
+sub _stage1A_setupResolver
+{
+ my $self = shift;
+ my $libcFolder = shift;
+
+ #if (!defined $libcFolder) {
+ # warn _tr("unable to determine libc-target-folder, will use /lib!");
+ # $libcFolder = '/lib';
+ #}
+
+ copyFile('/etc/resolv.conf', "$self->{stage1aDir}/etc");
+ copyFile('/etc/nsswitch.conf', "$self->{stage1aDir}/etc");
+ spitFile("$self->{stage1aDir}/etc/hosts", "127.0.0.1 localhost\n");
+ #copyFile("$libcFolder/libresolv*", "$self->{stage1aDir}$libcFolder");
+ #copyFile("$libcFolder/libnss_dns*", "$self->{stage1aDir}$libcFolder");
+ #copyFile("$libcFolder/libnss_files*", "$self->{stage1aDir}$libcFolder");
+
+ my $stage1cDir
+ = "$self->{'stage1aDir'}/$self->{'stage1bSubdir'}/" .
+ "$self->{'stage1cSubdir'}";
+ copyFile('/etc/resolv.conf', "$stage1cDir/etc");
+ return;
+}
+
+sub _stage1A_copyPrerequiredFiles
+{
+ my $self = shift;
+
+ return unless -d "$self->{'shared-distro-info-dir'}/prereqfiles";
+
+ vlog(2, "copying folder with pre-required files...");
+ my $stage1cDir
+ = "$self->{'stage1aDir'}/$self->{'stage1bSubdir'}/" .
+ "$self->{'stage1cSubdir'}";
+ my $cmd = qq[
+ tar -cp -C $self->{'shared-distro-info-dir'}/prereqfiles . \\
+ | tar -xp -C $stage1cDir
+ ];
+ if (slxsystem($cmd)) {
+ die _tr(
+ "unable to copy folder with pre-required files to folder '%s'" .
+ "(%s)\n",
+ $stage1cDir, $!
+ );
+ }
+ vlog(2, "fix pre-required files...");
+ $self->{distro}->fixPrerequiredFiles($stage1cDir);
+ return;
+}
+
+sub _stage1A_copyTrustedPackageKeys
+{
+ my $self = shift;
+
+ vlog(2, "copying folder with trusted package keys...");
+ my $stage1bDir = "$self->{'stage1aDir'}/$self->{'stage1bSubdir'}";
+ foreach my $folder (
+ $self->{'shared-distro-info-dir'}, $self->{'config-distro-info-dir'}
+ ) {
+ next unless -d "$folder/trusted-package-keys";
+ my $cmd = qq[
+ tar -cp -C $folder trusted-package-keys \\
+ | tar -xp -C $stage1bDir
+ ];
+ if (slxsystem($cmd)) {
+ die _tr(
+ "unable to copy folder with trusted package keys to folder " .
+ "'%s' (%s)\n",
+ "$stage1bDir/trusted-package-keys", $!
+ );
+ }
+ slxsystem("chmod 444 $stage1bDir/trusted-package-keys/*");
+
+ # install ultimately trusted keys (from distributor):
+ my $stage1cDir = "$stage1bDir/$self->{'stage1cSubdir'}";
+ my $keyDir = "$self->{'shared-distro-info-dir'}/trusted-package-keys";
+ if (-e "$keyDir/pubring.gpg") {
+ copyFile("$keyDir/pubring.gpg", "$stage1cDir/usr/lib/rpm/gnupg");
+ }
+ }
+ return;
+}
+
+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 (!-e "$stage1cDir/dev/null"
+ && slxsystem("mknod $stage1cDir/dev/null c 1 3"))
+ {
+ die _tr(
+ "unable to create node '%s' (%s)\n", "$stage1cDir/dev/null", $!
+ );
+ }
+ return;
+}
+
+sub _setupStage1B
+{
+ my $self = shift;
+
+ vlog(1, "setting up stage1b for $self->{'vendor-os-name'}...");
+ $self->_stage1B_chrootAndBootstrap();
+ return;
+}
+
+sub _stage1B_chrootAndBootstrap
+{
+ my $self = shift;
+
+ # give packager a chance to copy required files into stage1a-folder:
+ $self->{packager}->prepareBootstrap($self->{stage1aDir});
+
+ $self->_callChrootedFunction({
+ chrootDir => $self->{stage1aDir},
+ function => sub {
+ # chdir into slxbootstrap, as we want to drop packages into there:
+ chdir "/$self->{stage1bSubdir}"
+ or die _tr(
+ "unable to chdir into '%s' (%s)\n",
+ "/$self->{stage1bSubdir}", $!
+ );
+
+ # fetch prerequired packages and use them to bootstrap the packager:
+ $self->{'baseURLs'} = $self->_sortRepositoryURLs(
+ $self->{'distro-info'}->{repository}->{base}
+ );
+ $self->{'baseURL-index'} = 0;
+ my @pkgs =
+ string2Array($self->{'distro-info'}->{'prereq-packages'});
+ vlog(
+ 2,
+ "downloading these prereq packages:\n\t" . join("\n\t", @pkgs)
+ );
+ my @prereqPkgs = $self->_downloadBaseFiles(\@pkgs);
+ $self->{'prereq-packages'} = \@prereqPkgs;
+ $self->{packager}->bootstrap(\@prereqPkgs);
+
+ @pkgs =
+ string2Array($self->{'distro-info'}->{'bootstrap-packages'});
+ push(
+ @pkgs,
+ string2Array(
+ $self->{'distro-info'}->{'metapackager'}
+ ->{$self->{distro}->{'meta-packager-type'}}->{packages}
+ )
+ );
+ vlog(
+ 2,
+ "downloading bootstrap packages:\n\t" . join("\n\t", @pkgs)
+ );
+ my @bootstrapPkgs = $self->_downloadBaseFiles(\@pkgs);
+ $self->{'bootstrap-packages'} = \@bootstrapPkgs;
+ },
+ });
+ return;
+}
+
+sub _setupStage1C
+{
+ my $self = shift;
+
+ vlog(1, "setting up stage1c for $self->{'vendor-os-name'}...");
+ $self->_stage1C_chrootAndInstallBasicVendorOS();
+ return;
+}
+
+sub _stage1C_chrootAndInstallBasicVendorOS
+{
+ my $self = shift;
+
+ my $stage1bDir = "/$self->{stage1bSubdir}";
+ chrootInto($stage1bDir);
+
+ my $stage1cDir = "/$self->{stage1cSubdir}";
+
+ # import any additional trusted package keys to rpm-DB:
+ my $keyDir = "/trusted-package-keys";
+ my $keyDirDH;
+ if (opendir($keyDirDH, $keyDir)) {
+ my @keyFiles
+ = map { "$keyDir/$_" }
+ grep { $_ !~ m[^(\.\.?|pubring.gpg)$] }
+ readdir($keyDirDH);
+ closedir($keyDirDH);
+ $self->{packager}->importTrustedPackageKeys(\@keyFiles, $stage1cDir);
+ }
+
+ # install bootstrap packages
+ $self->{packager}->installPackages(
+ $self->{'bootstrap-packages'}, $stage1cDir
+ );
+ return;
+}
+
+sub _stage1C_cleanupBasicVendorOS
+{
+ my $self = shift;
+
+ my $stage1cDir
+ = "$self->{'stage1aDir'}/$self->{'stage1bSubdir'}/" .
+ "$self->{'stage1cSubdir'}";
+ if (slxsystem("mv $stage1cDir/* $self->{'vendor-os-path'}/")) {
+ die _tr(
+ "unable to move final setup to '%s' (%s)\n",
+ $self->{'vendor-os-path'}, $!
+ );
+ }
+ if (slxsystem("rm -rf $self->{stage1aDir}")) {
+ die _tr(
+ "unable to remove temporary folder '%s' (%s)\n",
+ $self->{stage1aDir}, $!
+ );
+ }
+ return;
+}
+
+sub _setupStage1D
+{
+ my $self = shift;
+
+ vlog(1, "setting up stage1d for $self->{'vendor-os-name'}...");
+
+ $self->_callChrootedFunction({
+ chrootDir => $self->{'vendor-os-path'},
+ function => sub {
+ $self->_stage1D_setupPackageSources();
+ $self->_stage1D_updateBasicVendorOS();
+ $self->{distro}->preSystemInstallationHook();
+ my $ok = eval {
+ $self->_stage1D_installPackageSelection();
+ 1;
+ };
+ my $err = $ok ? undef : $@;
+ $self->{distro}->postSystemInstallationHook();
+ die $err if defined $err;
+ },
+ updateConfig => 1,
+ });
+ return;
+}
+
+sub _updateStage1D
+{
+ my $self = shift;
+
+ vlog(1, "updating $self->{'vendor-os-name'}...");
+
+ $self->_callChrootedFunction({
+ chrootDir => $self->{'vendor-os-path'},
+ function => sub {
+ $self->_stage1D_updateBasicVendorOS();
+ },
+ updateConfig => 1,
+ });
+ return;
+}
+
+sub _startChrootedShellInStage1D
+{
+ my $self = shift;
+
+ vlog(0, "starting chrooted shell for $self->{'vendor-os-name'}");
+ vlog(0, "---------------------------------------");
+ vlog(0, "- please type 'exit' if you are done! -");
+ vlog(0, "---------------------------------------");
+
+ $self->_callChrootedFunction({
+ chrootDir => $self->{'vendor-os-path'},
+ function => sub {
+ # will hang until user exits manually:
+ slxsystem($openslxConfig{'default-shell'});
+ },
+ updateConfig => 1,
+ });
+ return;
+}
+
+sub _callChrootedFunction
+{
+ my $self = shift;
+ my $params = shift;
+
+ checkParams($params, {
+ 'chrootDir' => '!',
+ 'function' => '!',
+ 'updateConfig' => '?',
+ });
+
+ my $distro = $self->{distro};
+ my $distroSession = OpenSLX::ScopedResource->new({
+ name => 'ossetup::distro::session',
+ acquire => sub { $distro->startSession($params->{chrootDir}); 1 },
+ release => sub { $distro->finishSession(); 1 },
+ });
+
+ die $@ if ! eval {
+ # invoke given function:
+ $params->{function}->();
+ $distro->updateDistroConfig() if $params->{updateConfig};
+ 1;
+ };
+
+ return;
+}
+
+sub _stage1D_setupPackageSources
+{
+ my $self = shift;
+
+ vlog(1, "setting up package sources for meta packager...");
+ my $selectionName = $self->{'selection-name'};
+ my $pkgExcludes
+ = $self->{'distro-info'}->{excludes}->{$selectionName}->{packages};
+ my $excludeList = join ' ', string2Array($pkgExcludes);
+ $self->{'meta-packager'}->initPackageSources();
+ my ($rk, $repo);
+ while (($rk, $repo) = each %{$self->{'distro-info'}->{repository}}) {
+ vlog(2, "setting up package source $rk...");
+ $self->{'meta-packager'}->setupPackageSource(
+ $rk, $repo, $excludeList, $self->_sortRepositoryURLs($repo)
+ );
+ }
+ return;
+}
+
+sub _stage1D_updateBasicVendorOS
+{
+ my $self = shift;
+
+ vlog(1, "updating basic vendor-os...");
+ $self->{'meta-packager'}->updateBasicVendorOS();
+ return;
+}
+
+sub _stage1D_installPackageSelection
+{
+ my $self = shift;
+
+ my $selectionName = $self->{'selection-name'};
+
+ vlog(1, "installing package selection <$selectionName>...");
+ my $selection = $self->{'distro-info'}->{selection}->{$selectionName};
+ my @pkgs = string2Array($selection->{packages});
+ my @installedPkgs = $self->{'packager'}->getInstalledPackages();
+ @pkgs = grep {
+ my $pkg = $_;
+ if (grep { $_ eq $pkg; } @installedPkgs) {
+ vlog(1, "package '$pkg' filtered, it is already installed.");
+ 0;
+ }
+ else {
+ 1;
+ }
+ } @pkgs;
+ if (!@pkgs) {
+ vlog(
+ 0,
+ _tr(
+ "No packages listed for selection '%s', nothing to do.",
+ $selectionName
+ )
+ );
+ }
+ else {
+ vlog(1, "installing these packages:\n" . join("\n\t", @pkgs));
+ $self->{'meta-packager'}->installPackages(join(' ', @pkgs), 1);
+ }
+ return;
+}
+
+sub _clone_fetchSource
+{
+ my $self = shift;
+ my $source = shift;
+
+ vlog(
+ 0,
+ _tr(
+ "Cloning vendor-OS from '%s' to '%s'...\n", $source,
+ $self->{'vendor-os-path'}
+ )
+ );
+ my $excludeIncludeList = $self->_clone_determineIncludeExcludeList();
+ vlog(1, "using exclude-include-filter:\n$excludeIncludeList\n");
+ my $additionalRsyncOptions = $ENV{SLX_RSYNC_OPTIONS} || '';
+ my $rsyncCmd
+ = "rsync -av --delete --exclude-from=- $additionalRsyncOptions"
+ . " $source $self->{'vendor-os-path'}";
+ vlog(2, "executing: $rsyncCmd\n");
+ my $rsyncFH;
+ open($rsyncFH, '|-', $rsyncCmd)
+ or croak(
+ _tr(
+ "unable to start rsync for source '%s', giving up! (%s)\n",
+ $source, $!
+ )
+ );
+ print $rsyncFH $excludeIncludeList;
+ if (!close($rsyncFH)) {
+ print "rsync-result=", 0+$!, "\n";
+ croak _tr(
+ "unable to clone from source '%s', giving up! (%s)\n", $source, $!
+ );
+ }
+ return;
+}
+
+sub _clone_determineIncludeExcludeList
+{
+ my $self = shift;
+
+ my $localFilterFile
+ = "$openslxConfig{'config-path'}/distro-info/clone-filter";
+ my $includeExcludeList
+ = slurpFile($localFilterFile, { failIfMissing => 0 });
+ $includeExcludeList .= $self->{distro}->{'clone-filter'};
+ $includeExcludeList =~ s[^\s+][]igms;
+
+ # remove any leading whitespace, as rsync doesn't like it
+ return $includeExcludeList;
+}
+
+sub _installPlugins
+{
+ my $self = shift;
+ my $plugins = shift;
+ my $isReInstall = shift;
+
+ if (!$plugins) {
+ $plugins = [];
+ my $openslxDB = instantiateClass("OpenSLX::ConfigDB");
+ $openslxDB->connect();
+ # fetch plugins from existing vendor-OS
+ my $vendorOS = $openslxDB->fetchVendorOSByFilter({
+ 'name' => $self->{'vendor-os-name'}
+ });
+ if ($vendorOS) {
+ push @$plugins, $openslxDB->fetchInstalledPlugins($vendorOS->{id});
+ $isReInstall = 1;
+ }
+ $openslxDB->disconnect();
+ }
+
+ return if ! @$plugins;
+
+ require OpenSLX::OSPlugin::Engine;
+ vlog(
+ 0,
+ $isReInstall
+ ? _tr("reinstalling plugins...\n")
+ : _tr("installing default plugins...\n")
+ );
+ for my $pluginInfo (
+ sort {
+ $self->_sortPluginsByDependency($a->{plugin_name},
+ $b->{plugin_name});
+ } @$plugins
+ ) {
+ my $pluginName = $pluginInfo->{plugin_name};
+ my $pluginEngine = OpenSLX::OSPlugin::Engine->new();
+ vlog(0, _tr("\t%s\n", $pluginName));
+ $pluginEngine->initialize(
+ $pluginName, $self->{'vendor-os-name'}, $pluginInfo->{attrs}
+ );
+ $pluginEngine->installPlugin();
+ }
+ vlog(0, _tr("done with plugins.\n"));
+
+ return;
+}
+
+sub _sortPluginsByDependency
+{
+ my $self = shift;
+ my $pluginNameA = shift;
+ my $pluginNameB = shift;
+
+ my $pluginA = OpenSLX::OSPlugin::Roster->getPlugin($pluginNameA);
+ if ($pluginA->dependsOnPlugin($pluginNameB)) {
+ return 1;
+ }
+ my $pluginB = OpenSLX::OSPlugin::Roster->getPlugin($pluginNameB);
+ if ($pluginB->dependsOnPlugin($pluginNameA)) {
+ return -1;
+ }
+ return 0;
+}
+
+################################################################################
+### utility methods
+################################################################################
+sub _changePersonalityIfNeeded
+{
+ my $self = shift;
+
+ my $distroName = $self->{'distro-name'};
+ if ($self->_hostIs64Bit() && $distroName !~ m[_64]) {
+ vlog(2, 'entering 32-bit personality');
+ OpenSLX::Syscall->enter32BitPersonality();
+ }
+ return;
+}
+
+sub _hostIs64Bit
+{
+ my $self = shift;
+
+ $self->{arch} = `uname -m` unless defined $self->{arch};
+ return ($self->{arch} =~ m[64]);
+}
+
+1;
+
+=pod
+
+=head1 NAME
+
+OpenSLX::OSSetup::Engine - driver engine for OSSetup API
+
+=head1 SYNOPSIS
+
+...
+
+=cut
+