File Coverage

blib/lib/BerkeleyDB/Easy/Error.pm
Criterion Covered Total %
statement 33 120 27.5
branch 0 38 0.0
condition 0 27 0.0
subroutine 9 21 42.8
pod 0 2 0.0
total 42 208 20.1


line stmt bran cond sub pod time code
1             package BerkeleyDB::Easy::Error;
2            
3 1     1   5 use strict;
  1         1  
  1         34  
4 1     1   6 use warnings;
  1         2  
  1         24  
5            
6 1     1   6 use BerkeleyDB::Easy::Common;
  1         1  
  1         321  
7            
8             our (@ISA, @EXPORT, %EXPORT_TAGS, %Errors);
9            
10             BEGIN {
11 1     1   330 constant->import({ CODE => 0, DESC => 1, SKIP => 3 });
12             }
13            
14             BEGIN {
15 1     1   18 @ISA = qw(Exporter);
16 1         2 @EXPORT = ();
17 1         4 %EXPORT_TAGS = (
18             subs => [qw(_exception _throw _assign _const _lookup _caller _carp)],
19             );
20            
21             # All our constants get BDB_ prefix. And they're dualvars, with neg
22             # values to avoid stepping on toes.
23 1         10 %Errors = (
24             BDB_DEFAULT => [-900, q(Default error) ],
25             BDB_UNKNOWN => [-404, q(Unknown error) ],
26             BDB_PLACE => [-666, q(Placeholder error) ],
27             BDB_HANDLE => [-902, q(Failed to create BerkeleyDB handle) ],
28             BDB_TYPE => [-901, q(Invalid BerkeleyDB database type) ],
29             BDB_FLAG => [-903, q(Invalid options flag) ],
30             BDB_PARAM => [-904, q(Invalid options parameter) ],
31             BDB_CONST => [-905, q(Invalid constant function) ],
32             );
33            
34             # Create constants and make them available for export under the
35             # 'errors' tag.
36 1         5 for my $name (keys %Errors) {
37 8         13 my $code = $Errors{$name}->[CODE];
38 8         173 constant->import($name, Scalar::Util::dualvar($code, $name));
39 8         10 push @{$EXPORT_TAGS{errors}}, $name;
  8         21  
40             }
41            
42             # Export everything.
43 1         3 push @EXPORT, map @{$EXPORT_TAGS{$_}}, keys %EXPORT_TAGS;
  2         177  
44             }
45            
46             #
47             # Define the attributes for exception objects. _install called with a
48             # single argument as done here creates a simple named accessor
49             #
50             for (qw(code name time level desc detail package file line sub trace)) {
51             __PACKAGE__->_install($_);
52             }
53            
54             #
55             # Stringify an exception object. Create default message if none is set.
56             #
57             sub stringify {
58 0     0 0   my $self = shift;
59            
60 0   0       $self->{message} ||= join q(. ), grep $_,
61             $self->{desc},
62             $self->{detail};
63            
64 0   0       $self->{string} ||= sprintf q([%s] %s (%d): %s %s),
65             $self->{sub},
66             $self->{name},
67             $self->{code},
68             $self->{message},
69             $self->{trace};
70             }
71            
72 0     0 0   sub numberify { shift->{code} }
73            
74 1         5 use overload fallback => 1,
75             q("") => q(stringify),
76 1     1   1645 q(0+) => q(numberify);
  1         1148  
77            
78             #
79             # Throw an exception. First, get it's severity level and ignore it if
80             # appropriate. Otherwise call _exception to build the error object and
81             # _log to log it and warn/die as necessary.
82             #
83             sub _throw {
84 0     0     my ($self, $error, $extra, $flag) = @_;
85            
86 0           DEBUG and do {
87             my $code = int($error) || q(?);
88             $self->_debug(qq(Throwing "$error" ($code)));
89             };
90            
91 0           my $level = $self->_assign($error);
92 0 0         if ($level == BDB_IGNORE) {
93 0           TRACE and $self->_trace(qq(Ignoring exception: $error));
94 0           return;
95             }
96            
97 0           my $exc = $self->_exception($error, $extra, $flag);
98 0           $exc->{level} = $level;
99 0           $self->_log($level, $exc);
100            
101 0           $exc;
102             }
103            
104             #
105             # Build an exception.
106             # (Internal method, used by _throw)
107             #
108             sub _exception {
109 0     0     my ($self, $error, $extra, $flag) = @_;
110            
111 0   0 0     our $HiRes ||= !!$self->_try(sub { require Time::HiRes });
  0            
112 0 0 0       my %exc = (
113             time => ($HiRes ? Time::HiRes::time() : time),
114             code => (int $error || int BDB_UNKNOWN),
115             );
116            
117             # Populate package, file, line and sub attributes.
118             # If VERBOSE, get a full stack trace.
119 0           my $caller = $self->_caller(SKIP);
120 0           $exc{$_} = $caller->{$_} for qw(package file line sub);
121 0           $exc{trace} = BDB_VERBOSE
122             ? $self->_carp
123             : qq(at $exc{file} line $exc{line}.);
124            
125 0           my @detail = $extra;
126            
127             # TODO: a lot of this needs to be reworked. Misbehaving parts
128             # commented out.
129            
130             # Gnarly logic here to determine where the error came from
131             # and consolidate diagnostic messages that were squirreled away
132             # into a nice object. From perlvar:
133             #
134             # $! = $OS_ERROR = $ERRNO : current value of the C errno integer.
135             # $^E = $EXTENDED_OS_ERROR : Error information specific to the current
136             # operating system. At the moment, this differs from $! under only
137             # VMS, OS/2, and Win32 (and for MacPerl). On all other platforms,
138             # $^E is always just the same as $! .
139            
140             # DB_ prefix means error is from BerkeleyDB (the C library).
141             # Parse the exception into name and desc.
142             # If $! or $^E are also set, put them in the 'detail' field.
143 0 0         if ($error =~ /^DB_/) {
    0          
144 0           @exc{qw(name desc)} = $error =~ /^(DB_\w+):\s*(.+?)\.?$/;
145 0 0 0       push @detail, $!, ($^E ne $! and $^E) unless $flag;
146             }
147            
148             # Perl/OS error. Look up name from errno. Put $^E into 'detail'.
149             # If $flag is set, we never localized $! (due to optimization setting)
150             # so its value could be stale. In that case, skip this check.
151             # elsif ($! and not $flag) {
152             # @exc{qw(name desc)} = ($self->_lookup($!), $!);
153             # push @detail, ($^E ne $! and $^E);
154             # }
155            
156             # Extended OS error. Usually won't appear without $!, but handle the
157             # possibility just in case. If $flag is set, we never localized $^E.
158             # elsif ($^E and not $flag) {
159             # @exc{qw(name desc)} = ($self->_lookup($^E), $^E);
160             # }
161            
162             # BDB_ prefix means error was generated internally.
163             elsif ($error =~ /^BDB_/) {
164 0           @exc{qw(name desc)} = ($error, $Errors{$error}->[DESC]);
165             }
166            
167             # Fallback. Not sure where error originated.
168             else {
169 0           @exc{qw(name desc)} = ($self->_lookup($error), $error);
170             }
171            
172             # BerkeleyDB.pm error. Should only happen when there's a BerkeleyDB
173             # (C library) error during initialization. In that case, the BDB.pm
174             # error global will usually contain additional info.
175 0 0         if ($BerkeleyDB::Error) {
176 0           my $match = qr/(?::\s*)?([^:]+?)\.?$/;
177 0           my ($err ) = $BerkeleyDB::Error =~ $match;
178 0           my ($desc) = $exc{desc} =~ $match;
179 0 0         push @detail, $err if $err ne $desc;
180             }
181            
182             # @detail may have accumulated multiple messages. Join them into one str.
183 0           $exc{detail} = join q(. ), map ucfirst, grep $_, @detail;
184            
185 0           bless \%exc, $self->_Error;
186             }
187            
188             #
189             # Look up or set the severity level of an error. Sets the level when the
190             # second argument ($level) is provided. This is done in the constructor
191             # if the user opts to assign non-default severity levels to one or more
192             # errors when a handle is created.
193             #
194             sub _assign {
195 0     0     my ($self, $error, $level) = @_;
196 0 0         return BDB_ERROR unless ref $self;
197            
198             # Look up error code from string
199 0 0         $error = $self->_const($error) if not int $error;
200 0           my $code = int $error;
201            
202             # The BerkeleyDB.pm handle object is inside-out since it's an XS library.
203             # Our handle is the same object reblessed into our class, so we can't
204             # store any attributes on it. Instead, look up the address and use it as
205             # the key for a class-global %Config hash, where we store instance
206             # settings.
207            
208 0           my $handle = $self->_handle->[0];
209 0   0       our $Config ||= {};
210            
211             # Set severity level if we got $level
212 0 0         if ($level) {
213 1     1   864 no strict 'refs';
  1         3  
  1         658  
214 0 0         defined ${_Common . q(::Levels)}{$level}
  0            
215             or $self->_throw(BDB_FLAG, qq(Invalid error level "$level"));
216 0           $Config->{$handle}{$code} = $level;
217             }
218            
219             # Return user-supplied severity level or the default.
220 0 0 0       $Config->{$handle}{$code}
221             or $Config->{$handle}{int BDB_DEFAULT}
222             or BDB_ERROR;
223             }
224            
225             #
226             # Resolve a system error name to its errno integer code.
227             # (Complement to _lookup. Internal method, used by _assign)
228             #
229             # Convenience function for option parsing, for when the user
230             # gives us a string erorr name instead of an int or dualvar.
231             #
232             sub _const {
233 0     0     my ($self, $name) = @_;
234            
235 0           DEBUG and $self->_debug(qq(Resolving constant: $name));
236            
237 0           my $caller = $self->_caller(SKIP)->{package};
238 0           my $fullname = qq(&$caller\::$name);
239            
240             # Resolve the name to a coderef. Look in our caller, this module,
241             # BerkeleyDB, and Errno, in that order.
242             my $func = $caller->can($name)
243             || $self->can($name)
244             || do { BerkeleyDB->can($name) }
245 0 0 0       || do { require Errno; Errno->can($name) }
246             or $self->_throw(BDB_CONST, qq(Sub $fullname is undefined));
247            
248             # Now that we have a coderef, try calling it to get the error code.
249             # Catch any exceptions and repackage them into an error object.
250 0     0     my $return = $self->_try(sub { $self->_wrap($func) }, sub {
251 0     0     my ($error) = $_ =~ /^(.*?)(?: at .+ line \d+)?\.?$/m;
252 0           $self->_throw(BDB_CONST, qq(Sub $fullname died "$error"));
253 0           });
254            
255             # Make sure what we got is an integer. (Well, this doesn't actually go
256             # that far, but it's in the ballpark.)
257 0 0         int $return or $self->_throw(
258             BDB_CONST, qq(Sub $fullname returned non-integer "$return"),
259             );
260            
261 0           $return;
262             }
263            
264             #
265             # Lookup a system error name from its integer errno code.
266             # (Complement to _const. Internal method, used by _exception)
267             #
268             # Used by _exception to show a user-friendly/googleable error name
269             # instead of an integer errno. Creates a hash mapping all the exportable
270             # POSIX constants from Errno. There are a lot, so we delay doing this until
271             # needed, then cache it.
272             #
273             sub _lookup {
274 0     0     my ($self, $error) = @_;
275 0           my $code = int $error;
276            
277 0 0         if ($code) {
278 0           require Errno;
279            
280 0           my $posix = (our $Posix ||= {
281 0   0       map { Errno->$_ => $_ } @{$Errno::EXPORT_TAGS{POSIX}}
  0            
282             })->{$code};
283 0 0         return $posix if $posix;
284            
285 0           local $! = $code;
286 1     1   882 my @name = grep $!{$_}, keys %!;
  1         1579  
  1         314  
  0            
287 0 0         return $name[0] if @name == 1;
288            
289             # Otherwise, if @name > 1, the errno is ambigious because multiple
290             # errors share the same code. Many do, so not a frivolous check.
291             }
292            
293 0           $self->_warn(qq(Can't resolve error code "$code"));
294 0           BDB_UNKNOWN;
295             }
296            
297             #
298             # Walk down the callstack until we get the first package that isn't us.
299             # (Internal method, used by _const and _exception)
300             #
301             sub _caller {
302 0     0     my ($self, $frame) = @_;
303 0           my $base = $self->_Base;
304            
305 0           my ($pkg, $file, $line, $sub);
306 0           while (($pkg, $file, $line, $sub) = (caller $frame++)[0..3]) {
307 0 0         last if $pkg !~ /$base/;
308             }
309            
310             # Something went wrong.
311             # Don't $self->_warn again or we'll end up back here.
312 0 0         warn qq(Can't figure out who called into $base) unless $pkg;
313 0           { package => $pkg, file => $file, line => $line, sub => $sub };
314             }
315            
316             #
317             # Get a stack trace, excluding packages that belong to this distribution.
318             # (Internal method, used by _exception)
319             #
320             sub _carp {
321 0     0     my $self = shift;
322            
323 0   0       our $Classes ||= do {
324 1     1   7 no strict 'refs';
  1         2  
  1         233  
325 0           [ map { $self->$_ } @{${_Common . q(::EXPORT_TAGS)}{class}} ];
  0            
  0            
  0            
326             };
327            
328 0           require Carp;
329 0           local %Carp::Internal;
330 0           $Carp::Internal{$_}++ for @$Classes;
331 0           (my $trace = Carp::longmess()) =~ s/^\s+//;
332 0           $trace;
333             }
334            
335             INFO and __PACKAGE__->_info(q(Error.pm finished loading));
336            
337             1;