# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*- # # This file is MPL/GPL dual-licensed under the following terms: # # The contents of this file are subject to the Mozilla Public License # Version 1.1 (the "License"); you may not use this file except in # compliance with the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See # the License for the specific language governing rights and # limitations under the License. # # The Original Code is PLIF 1.0. # The Initial Developer of the Original Code is Ian Hickson. # # Alternatively, the contents of this file may be used under the terms # of the GNU General Public License Version 2 or later (the "GPL"), in # which case the provisions of the GPL are applicable instead of those # above. If you wish to allow use of your version of this file only # under the terms of the GPL and not to allow others to use your # version of this file under the MPL, indicate your decision by # deleting the provisions above and replace them with the notice and # other provisions required by the GPL. If you do not delete the # provisions above, a recipient may use your version of this file # under either the MPL or the GPL. package PLIF; use strict; # require strict adherence to perl standards use vars qw($AUTOLOAD); # it's a package global use Carp qw(cluck confess); # stack trace versions of warn and die use POSIX qw(strftime); # timestamps in debug output my $DEBUG = 9; # level of warnings and dumps to print to STDERR (none go to user) my $USER = 1; # level of errors to report to user (all go to STDERR) my @FATAL = (); # a list of pointers to functions that want to report errors to the user my $LOCKED = 0; # set to '1' while we are calling the error reporting code 1; # PLIF = Program Logic Insulation Framework # Levels are assumed to be something along the following: # Things that should never come up during normal operation: # 0 = total failure: e.g. no input or output devices # 1 = fatal errors: e.g. missing databases, broken connections, out of disk space # 2 = security: e.g. warnings about repeated cracking attempts # 3 = non-fatal errors: e.g. propagation of eval() errors as warnings # 4 = important warnings (e.g. unexpected but possibly legitimate lack of data) # # Useful debugging information: # 5 = important events (e.g. application started) # 6 = debugging remarks for the section currently under test # 7 = typical checkpoints (e.g. someone tried to do some output) # 8 = frequently hit typical checkpoints # 9 = verbose debugging information # 10 = ridiculously verbose debugging spam # # No code in CVS should do anything at level 6, it is reserved for # personal debugging. # Note. All of the methods described in this class except for the # propertyGet, propertySet and propertyExists methods are class # methods. You can call "$class->notImplemented" without a problem. # provide a standard virtual constructor # if already created, merely return $self sub create { my $class = shift; if (ref($class)) { return $class; # already created, return self } else { my $self = $class->bless(@_); # call our real constructor $self->serviceInit(@_); return $self; } } # provide a constructor that always constructs a new copy of the # class. This is used to create service instances. sub serviceCreate { my $class = shift; if (ref($class)) { $class = ref($class); } my $self = $class->bless(@_); # call our real constructor $self->serviceInstanceInit(@_); return $self; } sub init {} # stub for services sub serviceInit { my $self = shift; $self->init(@_); } sub serviceInstanceInit { my $self = shift; $self->init(@_); } # provide a constructor that always constructs a new copy of the # class. This is used by services that implement factories for objects # implemented in the same class (e.g., session objects do this). sub objectCreate { my $class = shift; if (ref($class)) { $class = ref($class); } my $self = $class->bless(@_); # call our real constructor $self->objectInit(@_); return $self; } sub objectInit {} # stub for objects # internals of create and objectCreate sub bless { my $class = shift; my $self = {}; CORE::bless($self, $class); return $self; } # provide method-like access for any scalars in $self sub AUTOLOAD { my $self = shift; my $name = $AUTOLOAD; $name =~ s/^.*://o; # strip fully-qualified portion if ($self->propertyImpliedAccessAllowed($name)) { if (scalar(@_) == 1) { return $self->propertySet($name, @_); } elsif (scalar(@_) == 0) { if ($self->propertyExists($name)) { return $self->propertyGet($name); } else { return $self->propertyGetUndefined($name); } } } $self->methodMissing($name, @_); } sub propertySet { # this is not a class method my $self = shift; my($name, $value) = @_; return $self->{$name} = $value; } sub propertyExists { # this is not a class method my $self = shift; my($name) = @_; $self->assert($name, 0, 'propertyExists() cannot be called without arguments'); return exists($self->{$name}); } sub propertyImpliedAccessAllowed { # this is not (supposed to be) a class method # my $self = shift; # my($name) = @_; # $self->assert($name, 0, 'propertyImpliedAccessAllowed() cannot be called without arguments'); return 1; } sub propertyGet { # this is not a class method my $self = shift; my($name) = @_; return $self->{$name}; } sub propertyGetUndefined { return undef; } sub methodMissing { my $self = shift; my($method) = @_; $self->error(0, "Internal Error: Tried to access non-existent method '$method' in object '$self'"); } # DEBUGGING AIDS sub dump { my $self = shift; my($level, @data) = @_; if ($self->isAtDebugLevel($level)) { my $time = strftime('%Y-%m-%d %H:%M:%S UTC', gmtime()); foreach (@data) { print STDERR "$0.$$ \@ $time: ($level) $_\n"; } } } sub warn { my $self = shift; my($level, @data) = @_; if ($self->isAtDebugLevel($level)) { $self->dump($level, ('-'x12).' Start of Warning Stack Trace '.('-'x12)); cluck(@data); # warn with stack trace $self->dump($level, ('-'x12). ('-'x30) .('-'x12)); } } sub error { my $self = shift; my($level, @data) = @_; $self->dump(9, "error raised: $data[0]"); if ($self->isAtUserLevel($level) and not $LOCKED) { # XXX this gets called even in eval{} blocks $LOCKED = 1; $self->dump(10, 'calling @FATAL error handlers...'); foreach my $entry (@FATAL) { eval { &{$entry->[1]}(@data); }; if ($@) { $self->warn(3, 'Error occured during \@FATAL callback of object \''.($entry->[0])."': $@"); } } $self->dump(10, 'done calling @FATAL error handlers'); $LOCKED = 0; } $self->dump(9, 'Environment dump:'); foreach my $key (sort keys %ENV) { $self->dump(9, "$key = $ENV{$key}"); } $self->dump(9, 'Stack trace:'); confess(@data); # die with stack trace } # this should not be called with the @data containing a trailing dot sub assert { my $self = shift; my($condition, $level, @data) = @_; if (not $condition) { $self->error($level, @data); } } sub debug { my $self = shift; $self->dump(6, @_); } sub notImplemented { my $self = shift; $self->error(0, 'Internal Error: Method not implemented'); } # returns true only if the argument is a debug level that is at least # as important as the local value of $DEBUG. sub isAtDebugLevel { my $self = shift; my($level) = @_; return ($level <= $DEBUG); } # returns true only if the argument is a debug level that is at least # as important as the local value of $USER. sub isAtUserLevel { my $self = shift; my($level) = @_; return ($level <= $USER); } # returns a reference to the $DEBUG variable for configuration # purposes sub getDebugLevel { return \$DEBUG; } # returns a reference to the $USER variable for configuration purposes sub getUserLevel { return \$USER; } # returns a reference to the @FATAL variable for modules that have # very exotic needs sub getFatalHandlerList { return \@FATAL; } # returns a reference to the $LOCKED variable for modules that which # to block @FATAL reporting sub getFatalHandlerLock { return \$LOCKED; } # if you call this, make sure that you call the next function too, # guarenteed, otherwise you will never be freed until the app dies. # of course, if you _are_ the app then I guess it's ok... sub enableErrorReporting { my $self = shift; push(@FATAL, [$self, sub { $self->fatalError(@_); }]); } sub disableErrorReporting { my $self = shift; my @OLDFATAL = @FATAL; @FATAL = (); foreach my $entry (@OLDFATAL) { if ($entry->[0] != $self) { push(@FATAL, $entry); } } } sub fatalError {} # stub sub DESTROY { my $self = shift; $self->dump(10, "Called destructor of object $self..."); }