# Copyright (c) 2006, 2007 - 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/ # ----------------------------------------------------------------------------- # SquashFS.pm # - provides SquashFS-specific overrides of the OpenSLX::OSExport::ExportType # API. # ----------------------------------------------------------------------------- package OpenSLX::OSExport::FileSystem::SquashFS; use strict; use warnings; use base qw(OpenSLX::OSExport::FileSystem::Base); use File::Basename; use OpenSLX::Basics; use OpenSLX::ConfigDB qw(:support); use OpenSLX::OSExport::FileSystem::Base 1; use OpenSLX::Utils; ################################################################################ ### interface methods ################################################################################ sub new { my $class = shift; my $self = {'name' => 'sqfs',}; return bless $self, $class; } sub initialize { my $self = shift; my $engine = shift; my $blockDevice = shift || confess('need to pass in block-device!'); $self->SUPER::initialize($engine); $self->{'block-device'} = $blockDevice; my $exportBasePath = "$openslxConfig{'public-path'}/export"; $self->{'export-path'} = "$exportBasePath/sqfs/$engine->{'vendor-os-name'}"; return; } sub exportVendorOS { my $self = shift; my $source = shift; my $includeExcludeList = $self->_determineIncludeExcludeList(); # in order to do the filtering as part of mksquashfs, we need to map # our internal (rsync-)filter format to regexes: $includeExcludeList = $self->_mapRsyncFilter2Regex($source, $includeExcludeList); vlog(1, _tr("using include-exclude-filter:\n%s\n", $includeExcludeList)); my $target = $self->{'export-path'}; my $sourceTime = (stat($source))[9] || 0; my $targetTime = (stat($target))[9] || 0; vlog(2, "source-time=$sourceTime target-time=$targetTime"); if ($targetTime && $sourceTime < $targetTime) { vlog( 0, "!!! creation of squashfs skipped, as vendor-OS hasn't changed since last export!\n" . "!!! Use 'touch $source' to force an export." ); } else { $self->_createSquashFS($source, $target, $includeExcludeList); } $self->_addBlockDeviceTagToExport($target); return; } sub purgeExport { my $self = shift; my $target = $self->{'export-path'}; if ($self->_removeBlockDeviceTagFromExport($target)) { # no more tags, we can remove the image: if (slxsystem("rm $target")) { vlog(0, _tr("unable to remove export '%s'!", $target)); return 0; } } return 1; } sub checkRequirements { my $self = shift; my $vendorOSPath = shift; # determine most appropriate kernel version ... my $kernelVer = $self->_pickKernelVersion($vendorOSPath); # ... and check if that kernel-version provides all the required modules my @blockModNames = $self->{'block-device'}->requiredBlockDeviceModules(); foreach my $blockModName (@blockModNames) { my $blockMod = $self->_locateKernelModule($vendorOSPath, "$blockModName.ko", ["$vendorOSPath/lib/modules/$kernelVer/kernel/drivers/block"]); if (!defined $blockMod) { warn _tr( "unable to find blockdevice-module '%s' for kernel version '%s'.", $blockModName, $kernelVer ); return; } } my $squashfsMod = $self->_locateKernelModule( $vendorOSPath, 'squashfs.ko', [ "$vendorOSPath/lib/modules/$kernelVer/kernel/fs/squashfs", "$vendorOSPath/lib/modules/$kernelVer/kernel/fs" ] ); if (!defined $squashfsMod) { warn _tr("unable to find squashfs-module for kernel version '%s'.", $kernelVer); return; } return 1; } sub addExportToConfigDB { my $self = shift; my $export = shift; my $openslxDB = shift; $export->{port} = $self->{'block-device'}->getExportPort($openslxDB); my $res = $openslxDB->addExport($export); return $res; } sub generateExportURI { my $self = shift; my $export = shift; my $vendorOS = shift; my $URI = $self->{'block-device'}->generateExportURI($export); $URI .= '/squashfs'; return $URI; } sub requiredFSMods { my $self = shift; my @mods = $self->{'block-device'}->requiredBlockDeviceModules(); push @mods, 'squashfs '; return @mods; } sub requiredFSTools { my $self = shift; return $self->{'block-device'}->requiredBlockDeviceTools(); } sub showExportConfigInfo { my $self = shift; my $export = shift; $self->{'block-device'}->showExportConfigInfo($export); return; } ################################################################################ ### implementation methods ################################################################################ ################################################################################# # Branch to choose the appropriate squashfs version according to target system # ################################################################################ sub _createSquashFS { my $self = shift; my $source = shift; my $target = shift; my $includeExcludeList = shift; my @versionString = split(/-/, $self->{engine}->{'vendor-os-name'}); my $mksquashfsVersion = $self->{engine}->{distro}->getSquashfsVersion($versionString[1]); vlog(0, "found version string $versionString[1] out of $self->{engine}->{'vendor-os-name'}"); vlog(0, "use squashfs version: $mksquashfsVersion"); $self->_createSquashFS_prepare($target); my $mksquashfsParams; $mksquashfsVersion == '3.2' && do {$mksquashfsParams = $self->_createSquashFS_setup_3_2($includeExcludeList)}; $mksquashfsVersion == '3.3' && do {$mksquashfsParams = $self->_createSquashFS_setup_3_3($includeExcludeList)}; $mksquashfsVersion == '4.0' && do {$mksquashfsParams = $self->_createSquashFS_setup_4($includeExcludeList)}; $self->_createSquashFS_run($source, $target, $mksquashfsParams); return; } sub _createSquashFS_prepare { my $self = shift; my $target = shift; system("rm -f $target"); # mksquasfs isn't significantly faster if fs already exists, but it # causes the filesystem to grow somewhat, so we remove it in order to # get the smallest FS-file possible. my $baseDir = dirname($target); if (!-e $baseDir) { if (system("mkdir -p $baseDir")) { die _tr("unable to create directory '%s', giving up! (%s)\n", $baseDir, $!); } } return; } sub _createSquashFS_run { my $self = shift; my $source = shift; my $target = shift; my $params = shift; # ... invoke mksquashfs ... vlog(0, _tr("invoking mksquashfs...")); my $mksquashfsBinary = "$openslxConfig{'base-path'}/share/squashfs/$params->{binary}"; my $res = system("$mksquashfsBinary $source $target $params->{cmdlineOptions}"); unlink($params->{tmpfile}); # ... remove filter file if done if ($res) { die _tr( "unable to create squashfs for source '%s' as target '%s', giving up! (%s)", $source, $target, $!); } return; } sub _createSquashFS_setup_4 { my $self = shift; my $includeExcludeList = shift; # dump filter to a file ... my $filterFile = "/tmp/slx-nbdsquash-filter-$$"; my $includeFile = "/tmp/slx-nbdsquash-includes-$$"; spitFile($filterFile, $includeExcludeList); slxsystem("sed $filterFile -e '/^-/d' -e 's/^+[ \t]*//' > $includeFile"); slxsystem("sed -i $filterFile -e '/^+/d' -e 's/^-[ \t]*//'"); my $result = { binary => "mksquashfs_4", cmdlineOptions => "-wildcards -ef $filterFile", tmpfile => "$filterFile" }; return $result; } sub _createSquashFS_setup_3_3 { my $self = shift; my $includeExcludeList = shift; # dump filter to a file ... my $filterFile = "/tmp/slx-nbdsquash-filter-$$"; my $includeFile = "/tmp/slx-nbdsquash-includes-$$"; spitFile($filterFile, $includeExcludeList); slxsystem("sed $filterFile -e '/^-/d' -e 's/^+[ \t]*//' > $includeFile"); slxsystem("sed -i $filterFile -e '/^+/d' -e 's/^-[ \t]*//'"); my $result = { binary => "mksquashfs_3_3", cmdlineOptions => "-wildcards -ef $filterFile", tmpfile => "$filterFile" }; return $result; } sub _createSquashFS_setup_3_2 { my $self = shift; my $includeExcludeList = shift; # dump filter to a file ... my $filterFile = "/tmp/slx-nbdsquash-filter-$$"; spitFile($filterFile, $includeExcludeList); my $result = { binary => "mksquashfs_3_2", cmdlineOptions => "-ff $filterFile", tmpfile => "$filterFile" }; return $result; } sub _determineIncludeExcludeList { my $self = shift; # Rsync uses a first match strategy, so we mix the local specifications # in front of the filterset given by the package (as the local filters # should always overrule the vendor filters): my $distroName = $self->{engine}->{'distro-name'}; my $localFilterFile = "$openslxConfig{'config-path'}/distro-info/$distroName/export-filter"; my $includeExcludeList = slurpFile($localFilterFile, { failIfMissing => 0 }); $includeExcludeList .= $self->{engine}->{distro}->{'export-filter'}; $includeExcludeList =~ s[^\s+][]igms; # remove any leading whitespace, as rsync doesn't like it return $includeExcludeList; } sub _mapRsyncFilter2Regex { my $self = shift; my $sourcePath = shift; my $rsyncFilter = shift; return join( "\n", map { if ($_ =~ m[^([-+]\s*)(.+?)\s*$]) { my $action = $1; my $regex = $2; $regex =~ s[\*\*][.+]g; # '**' matches everything $regex =~ s[\*][[^/]+]g; # '*' matches anything except slashes $regex =~ s[\?][[^/]?]g; # '*' matches any single char except slash $regex =~ s[\?][[^/]?]g; # '*' matches any single char except slash $regex =~ s[\.][\\.]g; # escape any dots if (substr($regex, 0, 1) eq '/') { # absolute path given, need to extend by source-path: "$action^$sourcePath$regex\$"; } else { # filename pattern given, need to anchor to the end only: "$action$regex\$"; } } else { $_; } } split "\n", $rsyncFilter ); } sub _addBlockDeviceTagToExport { my $self = shift; my $target = shift; my $tagName = "$target" . '@' . lc($self->{'block-device'}->{name}); linkFile(basename($target), $tagName); return; } sub _removeBlockDeviceTagFromExport { my $self = shift; my $target = shift; my $tagName = "$target" . '@' . lc($self->{'block-device'}->{name}); slxsystem("rm $tagName"); # now find out whether or not there are any other tags left: my $vendorOSName = basename($target); opendir(DIR, dirname($target)); my @tags = grep { /^$vendorOSName\@/ } readdir(DIR); return @tags ? 0 : 1; # return 1 if no more tags (i.e. it is safe to remove the image) } 1;