summaryrefslogblamecommitdiffstats
path: root/lib/OpenSLX/Basics.pm
blob: e675ee52d7f744f596c5824aeabe77bd918856a5 (plain) (tree)
1
2
3
4
5
6
7
8
                                         
 

                                                                    
 

                                                                         
 




                                                                               





                                   
                
                        

             






                                                   

  
                                                        
                 




                                                                                





                                                                   







                                                                          
                                                                          
                  


                                                    
                                                   
                                             






                                                                         
         

                                                 
                               

         


                                                                            
                                       

                                        

                                                                            
                          
                                                 
                                                  



                                                                       
                                                  
 

                                                                                   
 


                                                                                

                                        
                                                

                                                                 
                                                                

                                                             
                                                 


                                                                                  
                                                            


                                                                           
                                                          

                                                       
                                                      

                                          
                                                              

  

                     







                                                                                

                                                


                               










                                                                                
                                           
                                                            


                                                                             

                                                           
                                                             
                 
                                  




                                           
                                              
                                                                                              
                                                

                                                     
 














                                                                                        
                                                        




                                             


                                                            


                                                          
                                                                        















                                                                                
                                                                       





                                                                       
 



                                                             







                                                                                 
                                                             











                                                                                      

                                                                                      




                                                                                       





                                                                                                       



                                                        


                                                                                                   














                                                                                




                                       
                                                                                
                    












                                                                 


                                                                                







                                                                                








                                            






                                                                                




















                                                                                
                                                                




                                                                                

             
                                                    
                             
                       

                                                                                       


                                                       

                                                                                    

                             




                                                                                


                        

                                  

                                              
                
                           
                                    



                                                                                

       

                                 
                        

                                  

                                              
                
                           
                                   


         


                                                                                
                                     




                                                                   
                        





                                                                                


                                                                                                    




                           
  
# 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/
# -----------------------------------------------------------------------------
# Basics.pm
#	- provides basic functionality of the OpenSLX config-db.
# -----------------------------------------------------------------------------
package OpenSLX::Basics;

use strict;
use vars qw(@ISA @EXPORT $VERSION);

use Exporter;
$VERSION = 1.01;
@ISA     = qw(Exporter);

@EXPORT = qw(
  &openslxInit %openslxConfig %cmdlineConfig
  &_tr &trInit
  &warn &die
  &callInSubprocess &executeInSubprocess &slxsystem
  &vlog
  &instantiateClass
  &addCleanupFunction &removeCleanupFunction
);

use vars qw(%openslxConfig %cmdlineConfig %openslxPath);
use subs qw(die);

################################################################################
### Module implementation
################################################################################
use Carp;
use
  Carp::Heavy; # use it here to have it loaded immediately, not at 
               # the time when carp() is being invoked (which might
               # be at a point in time where the script executes in
               # a chrooted environment, such that the module can't
               # be loaded anymore).
use FindBin;
use Getopt::Long;
use POSIX qw(locale_h);

my %translations;

# this hash will hold the active openslx configuration,
# the initial content is based on environment variables or default values.
# Each value may be overridden from config files and/or cmdline arguments.
%openslxConfig = (
	'db-name' => $ENV{SLX_DB_NAME} || 'openslx',
	'db-spec' => $ENV{SLX_DB_SPEC},
	'db-type' => $ENV{SLX_DB_TYPE} || 'SQLite',
	'locale'         => setlocale(LC_MESSAGES),
	'locale-charmap' => `locale charmap`,
	'base-path'      => $ENV{SLX_BASE_PATH} || '/opt/openslx',
	'config-path'    => $ENV{SLX_CONFIG_PATH} || '/etc/opt/openslx',
	'private-path'   => $ENV{SLX_PRIVATE_PATH} || '/var/opt/openslx',
	'public-path'    => $ENV{SLX_PUBLIC_PATH} || '/srv/openslx',
	'temp-path'      => $ENV{SLX_TEMP_PATH} || '/tmp',
	'verbose-level'  => $ENV{SLX_VERBOSE_LEVEL} || '0',

	#
	# options useful during development only:
	#
	'debug-confess' => '0',

	#
	# extended settings follow, which are only supported by slxsettings,
	# but not by any other script:
	#
	'ossetup-max-try-count' => '5',
);
chomp($openslxConfig{'locale-charmap'});

# specification of cmdline arguments that are shared by all openslx-scripts:
my %openslxCmdlineArgs = (
	# name of database, defaults to 'openslx'
	'db-name=s' => \$cmdlineConfig{'db-name'},

	# full specification of database, a special string defining the
	# precise database to connect to (the contents of this string
	# depend on db-type)
	'db-spec=s' => \$cmdlineConfig{'db-spec'},

	# type of database to connect to (SQLite, mysql, ...), defaults to 'SQLite'
	'db-type=s' => \$cmdlineConfig{'db-type'},

	# activates debug mode, this will show the lines where any error occured
	# (followed by a stacktrace):
	'debug-confess' => \$cmdlineConfig{'debug-confess'},

	# locale to use for translations
	'locale=s' => \$cmdlineConfig{'locale'},

	# locale-charmap to use for I/O (iso-8859-1, utf-8, etc.)
	'locale-charmap=s' => \$cmdlineConfig{'locale-charmap'},

	# file to write logging output to, defaults to STDERR
	'logfile=s' => \$cmdlineConfig{'locale'},

	# path to private data (which is *not* accesible by clients and contains
	# database, vendorOSes and all local extensions [system specific scripts])
	'private-path=s' => \$cmdlineConfig{'private-path'},

	# path to public data (which is accesible by clients and contains
	# PXE-configurations, kernels, initramfs and client configurations)
	'public-path=s' => \$cmdlineConfig{'public-path'},

	# path to temporary data (used during demuxing)
	'temp-path=s' => \$cmdlineConfig{'temp-path'},

	# level of logging verbosity (0-3)
	'verbose-level=i' => \$cmdlineConfig{'verbose-level'},
);

my %cleanupFunctions;

# filehandle used for logging:
my $openslxLog = *STDERR;

# ------------------------------------------------------------------------------
sub vlog
{
	my $minLevel = shift;
	return if $minLevel > $openslxConfig{'verbose-level'};
	my $str = join("", '-' x $minLevel, @_);
	if (substr($str, -1, 1) ne "\n") {
		$str .= "\n";
	}
	print $openslxLog $str;
}

# ------------------------------------------------------------------------------
sub openslxInit
{
	# evaluate cmdline arguments:
	Getopt::Long::Configure('no_pass_through');
	GetOptions(%openslxCmdlineArgs) or return 0;

	# try to read and evaluate config files:
	my $configPath = $cmdlineConfig{'config-path'}
	  || $openslxConfig{'config-path'};
	my $sharePath = "$openslxConfig{'base-path'}/share";
	foreach my $f ("$sharePath/settings.default", "$configPath/settings",
		"$ENV{HOME}/.openslx/settings")
	{
		next unless open(CONFIG, "<$f");
		if ($cmdlineConfig{'verbose-level'} >= 2) {
			vlog(0, "reading config-file $f...");
		}
		while (<CONFIG>) {
			chomp;
			s/#.*//;
			s/^\s+//;
			s/\s+$//;
			next unless length;
			if (!/^(\w+)=(.*)$/) {
				die _tr("config-file <%s> has incorrect syntax here:\n\t%s\n",
					$f, $_);
			}
			my ($key, $value) = ($1, $2);

			# N.B.: the config files are used by shell-scripts, too, so in
			# order to comply with shell-style, the config files use shell
			# syntax and an uppercase, underline-as-separator format.
			# Internally, we use lowercase, minus-as-separator format, so we
			# need to convert the environment variable names to our own
			# internal style here (e.g. 'SLX_BASE_PATH' to 'base-path'):
			$key =~ s[^SLX_][];
			$key =~ tr/[A-Z]_/[a-z]-/;
			$openslxConfig{$key} = $value;
		}
		close CONFIG;
	}

	# push any cmdline argument into our config hash, possibly overriding any
	# setting from the config files:
	while (my ($key, $val) = each(%cmdlineConfig)) {
		next unless defined $val;
		$openslxConfig{$key} = $val;
	}

	if (defined $openslxConfig{'logfile'}
		&& open(LOG, ">>$openslxConfig{'logfile'}"))
	{
		$openslxLog = *LOG;
	}
	if ($openslxConfig{'verbose-level'} >= 2) {
		foreach my $k (sort keys %openslxConfig) {
			vlog(2, "config-dump: $k = $openslxConfig{$k}");
		}
	}

	# setup translation "engine":
	trInit();

	return 1;
}

# ------------------------------------------------------------------------------
sub trInit
{
	# set the specified locale...
	setlocale('LC_ALL', $openslxConfig{'locale'});

	# ...and activate automatic charset conversion on all I/O streams:
	binmode(STDIN,  ":encoding($openslxConfig{'locale-charmap'})");
	binmode(STDOUT, ":encoding($openslxConfig{'locale-charmap'})");
	binmode(STDERR, ":encoding($openslxConfig{'locale-charmap'})");
	use open ':locale';

	my $locale = $openslxConfig{'locale'};
	if (lc($locale) eq 'c') {

		# treat locale 'c' as equivalent for 'posix':
		$locale = 'posix';
	}

	if (lc($locale) ne 'posix') {
		# parse locale and canonicalize it (e.g. to 'de_DE') and generate
		# two filenames from it (language+country and language only):
		if ($locale !~ m{^\s*([^_]+)(?:_(\w+))?}) {
			die "locale $locale has unknown format!?!";
		}
		my @locales;
		if (defined $2) {
			push @locales, lc($1) . '_' . uc($2);
		}
		push @locales, lc($1);

		# try to load any of the Translation modules (starting with the more
		# specific one [language+country]):
		my $loadedTranslationModule;
		foreach my $trName (@locales) {
			my $trModule = "OpenSLX::Translations::$trName";
			if (eval "require $trModule") {
				# Access OpenSLX::Translations::<locale>::translations
				# via a symbolic reference...
				no strict 'refs';
				my $translationsRef = \%{"${trModule}::translations"};

				# ...and copy the available translations into our hash:
				foreach my $k (keys %{$translationsRef}) {
					$translations{$k} = $translationsRef->{$k};
				}
				$loadedTranslationModule = $trModule;
				vlog(
					1,
					_tr(
						"translations module %s loaded successfully", $trModule
					)
				);
				last;
			}
		}
		if (!defined $loadedTranslationModule) {
			vlog(1,
				"unable to load any translations module for locale '$locale' ($!)."
			);
		}
	}
}

# ------------------------------------------------------------------------------
sub _tr
{
	my $trOrig = shift;

	my $trKey = $trOrig;
	$trKey =~ s[\n][\\n]g;
	$trKey =~ s[\t][\\t]g;

	my $formatStr = $translations{$trKey};
	if (!defined $formatStr) {
		$formatStr = $trOrig;
	}
	return sprintf($formatStr, @_);
}

# ------------------------------------------------------------------------------
sub callInSubprocess
{
	my $childFunc = shift;

	my $pid = fork();
	if (!$pid) {
		# child...
		# ...execute the given function and exit:
		&$childFunc();
		exit 0;
	}

	# parent...
	# ...pass on interrupt- and terminate-signals to child...
	local $SIG{INT}  = sub { kill 'INT',  $pid; waitpid($pid, 0); exit $? };
	local $SIG{TERM} = sub { kill 'TERM', $pid; waitpid($pid, 0); exit $? };

	# ...and wait for child to do its work:
	waitpid($pid, 0);
	if ($?) {
		exit $?;
	}
}

# ------------------------------------------------------------------------------
sub executeInSubprocess
{
	my @cmdlineArgs = @_;

	my $pid = fork();
	if (!$pid) {
		# child...
		# ...exec the given cmdline:
		exec(@cmdlineArgs);
	}

	# parent...
	return $pid;
}

# ------------------------------------------------------------------------------
sub addCleanupFunction
{
	my $name = shift;
	my $func = shift;

	$cleanupFunctions{$name} = $func;
}

# ------------------------------------------------------------------------------
sub removeCleanupFunction
{
	my $name = shift;

	delete $cleanupFunctions{$name};
}

# ------------------------------------------------------------------------------
sub invokeCleanupFunctions
{
	my @funcNames = keys %cleanupFunctions;
	foreach my $name (@funcNames) {
		vlog(2, "invoking cleanup function '$name'...");
		$cleanupFunctions{$name}->();
	}
}

# ------------------------------------------------------------------------------
sub slxsystem
{
	vlog(2, _tr("executing: %s", join ' ', @_));
	my $res = system(@_);
	if ($res > 0) {
		# check if child got killed, if so we stop, too (unless the signal is
		# SIGPIPE, which we ignore in order to loop over failed FTP connections
		# and the like):
		my $signalNo = $res & 127;
		if ($signalNo > 0 && $signalNo != 13) {
			die _tr("child-process reveived signal '%s', parent stops!",
				$signalNo);
			exit;
		}
	}
	return $res;
}

# ------------------------------------------------------------------------------
sub warn
{
	my $msg = shift;
	$msg =~ s[^\*\*\* ][]igms;
	$msg =~ s[^][*** ]igms;
	if ($openslxConfig{'debug-confess'}) {
		Carp::cluck $msg;
	} else {
		chomp $msg;
		CORE::warn "$msg\n";
	}
}

# ------------------------------------------------------------------------------
sub die
{
	invokeCleanupFunctions();

	my $msg = shift;
	$msg =~ s[^\*\*\* ][]igms;
	$msg =~ s[^][*** ]igms;
	if ($openslxConfig{'debug-confess'}) {
		confess $msg;
	} else {
		chomp $msg;
		CORE::die "$msg\n";
	}
}

# ------------------------------------------------------------------------------
sub instantiateClass
{
	my $class            = shift;
	my $requestedVersion = shift;

	unless (eval "require $class") {
		if ($! == 2) {
			die _tr("Class <%s> not found!\n", $class);
		} else {
			die _tr("Unable to load class <%s> (%s)\n", $class, $@);
		}
	}
	if (defined $requestedVersion) {
		my $classVersion = $class->VERSION;
		if ($classVersion < $requestedVersion) {
			die _tr(
				'Could not load class <%s> (Version <%s> required, but <%s> found)',
				$class, $requestedVersion, $classVersion);
		}
	}
	return $class->new;
}

1;